diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 00000000000..85d79a2b503 --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1,283 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Repository Overview + +Project Calico is a large monorepo providing container networking and security for Kubernetes. The codebase contains ~2000 Go files across 30+ components, supporting multiple dataplanes (eBPF, iptables, nftables, Windows, VPP). + +**Primary language:** Go (also C/eBPF, Python, Shell, TypeScript/React) +**Build system:** Make + Docker-based reproducible builds +**CI/CD:** Semaphore CI (configuration in `.semaphore/`) +**Default branch:** `master` (not `main`) +**Separate docs repo** https://github.com/tigera/docs/ + +## Gotchas + +- **NEVER** run `make ci` or `make cd` locally — destructive CI-only targets +- **NEVER** run `make test` at root — takes hours. Always test components individually. +- **ALWAYS** run `make fix-changed` before committing — CI rejects formatting errors +- **ALWAYS** remove `FIt`/`FDescribe` before committing — pre-commit hook rejects Ginkgo focused tests +- **ALWAYS** commit generated files alongside source changes + +## Essential Build Commands + +**Prerequisites:** Docker, Make, Git, Linux environment (Ubuntu 24.04+ recommended) + +### Building Components + +```bash +# Build specific component (2-5 minutes, RECOMMENDED) +make -C felix build +make -C typha build +make -C node build +make -C calicoctl build +make -C kube-controllers build + +# Build all images (WARNING: 30+ minutes) +make image + +# Build for specific architecture +make -C felix build ARCH=arm64 +``` + +### Running Tests + +```bash +# Unit tests for a component via Make (runs in Docker, rebuilds tooling) +make -C felix ut +make -C calicoctl test +make -C typha test + +# Unit tests via go test (faster, no Docker overhead — use for quick iteration) +go test ./felix/calc/... +go test ./libcalico-go/lib/... + +# Components with separate go.mod (must cd first) +cd api && go test ./... +cd lib/std && go test ./... +cd lib/httpmachinery && go test ./... + +# Felix FV (functional verification) tests +# IMPORTANT: Always use Makefile targets — they build required tooling and set up permissions +make -C felix fv GINKGO_ARGS="-ginkgo.v" + +# Run specific FV tests by pattern +make -C felix fv GINKGO_FOCUS="TestName" GINKGO_ARGS="-ginkgo.v" + +# Felix FV in eBPF mode (BPF-SAFE tests only) +make -C felix fv-bpf GINKGO_FOCUS="TestName" GINKGO_ARGS="-ginkgo.v" + +# Felix FV in nftables mode +make -C felix fv GINKGO_ARGS="-ginkgo.v" FELIX_FV_NFTABLES=Enabled +``` + +### Felix Testing Notes + +- Felix FV tests are in `felix/fv/`, using **Ginkgo v2** (`github.com/onsi/ginkgo/v2`) +- Test IDs include all nested Context/Describe headings +- **Always run FVs via Makefile** — builds required tooling and sets up permissions +- Use `GINKGO_FOCUS="regex"` to target specific tests, `GINKGO_ARGS` for extra flags +- Useful flags: `-ginkgo.dryRun` (list tests), `-ginkgo.v` (verbose), `FV_FELIX_LOG_LEVEL=debug` +- **Prefer vanilla `go test` for new packages.** Only use Ginkgo if established pattern exists. +- Felix "brain" is the calculation graph in `felix/calc/` — changes require calc graph "FV" tests (`felix/calc/calc_graph_fv_test.go`) + +### Validation and Formatting + +```bash +make yaml-lint # Quick YAML validation (~30 seconds) +make check-go-mod # Go module validation +make check-dockerfiles # Dockerfile linting +make check-language # Language/content checks +make go-vet # Go static analysis (requires: make -C felix clone-libbpf) +make verify-go-mods # Cross-component module check +make golangci-lint # Run golangci-lint (--timeout 8m) +make fix-changed # Auto-fix formatting for changed files (RECOMMENDED) +make pre-commit # Run pre-commit checks in Docker +``` + +### Code Generation + +```bash +# Regenerate all generated files (APIs, protobuf, manifests, CI config, etc.) +make generate + +# Individual generation targets +make protobuf # Regenerate protobuf files +make gen-manifests # Update manifests/ from helm charts +make gen-semaphore-yaml # Regenerate .semaphore/semaphore.yml from templates +``` + +## Generated Files (DO NOT edit directly) + +| Generated file | Edit this instead | Regenerate with | +|---|---|---| +| `.semaphore/semaphore.yml` | `.semaphore/semaphore.yml.d/` templates | `make gen-semaphore-yaml` | +| `manifests/` | `charts/` | `make gen-manifests` | +| `*.pb.go` protobuf files | `.proto` sources | `make protobuf` | + +After regenerating, commit the generated files alongside your source changes. + +## Code Conventions + +### Go Import Order + +Three groups separated by blank lines: stdlib, external, calico-internal: +```go +import ( + "fmt" + "net" + + "k8s.io/api/core/v1" + + "github.com/projectcalico/calico/libcalico-go/lib/apis" +) +``` + +Run `make fix-changed` to auto-fix import ordering. Do not run `goimports` or `go fmt` directly — the project uses a custom 3-step pipeline (`hack/format-changed-files.sh`). + +### Copyright Headers + +All new `.go` files require: +```go +// Copyright (c) Tigera, Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// ... +``` + +eBPF files in `felix/bpf-gpl/` require dual Apache/GPL headers with SPDX identifiers. The pre-commit hook validates license headers. + +## Repository Architecture + +### Component Dependency Order + +Core components (dependency order): +``` +api/ - Calico API definitions (CRDs, protobuf), separate go.mod +libcalico-go/ - Core Go client library and data model +typha/ - Datastore fan-out proxy for scaling (reduces etcd load) +felix/ - Core per-host networking agent (eBPF/iptables/nftables dataplane) +node/ - Node initialization container (includes Felix, confd, BIRD, startup scripts) +calicoctl/ - CLI tool for Calico management +kube-controllers/ - Kubernetes-specific controllers (namespace, pod, node, serviceaccount) +cni-plugin/ - Kubernetes CNI integration +confd/ - Configuration management daemon +app-policy/ - Application layer policy (L7) +apiserver/ - Kubernetes API aggregation layer +``` + +Additional components: +``` +goldmane/ - Log aggregation and flow log storage +guardian/ - Secure tunnel proxy for management cluster connections +pod2daemon/ - Flex volume driver for injecting credentials into pods +key-cert-provisioner/ - TLS certificate provisioner for Calico components +whisker/ - Flow log UI (TypeScript/React frontend) +whisker-backend/ - Backend for whisker flow log UI +e2e/ - End-to-end test suites +release/ - Release tooling and automation +lib/std/ - Internal shared Go library (separate go.mod) +lib/httpmachinery/ - Internal HTTP utility library (separate go.mod) +``` + +### Key Architectural Concepts + +**Felix** is the core per-host agent responsible for: +- Programming dataplane (eBPF, iptables, nftables) +- Maintaining routing tables +- Processing policy and programming ACLs +- Source: `felix/daemon/daemon.go` +- **Calculation graph** (`felix/calc/`): DAG that processes datastore updates and calculates dataplane state. Changes here require calc graph FV tests. + +**Typha** is a fan-out proxy that: +- Sits between Felix instances and the datastore (etcd/K8s API) +- Reduces load on datastore by caching and fanning out to multiple Felix instances +- Optional but recommended for clusters >50 nodes + +**Node container** orchestrates node initialization: +- Runs Felix, confd, and BIRD in a single container +- Handles CNI plugin installation +- Source: `node/pkg/lifecycle/startup/startup.go` + +### Go Module Structure + +- Root `go.mod` (`github.com/projectcalico/calico`) is the primary module for most components +- `api/go.mod` (`github.com/projectcalico/api`) is separate (API exported as independent repo) +- `lib/std/go.mod` and `lib/httpmachinery/go.mod` are internal libraries +- When adding Go dependencies: `cd && go mod tidy && cd .. && make check-go-mod` + +### Docker Build System + +- All builds run inside Docker containers using `calico/go-build` (version pinned in `metadata.mk`) +- Base images configured in `metadata.mk` +- Build cache in `.go-pkg-cache/` (speeds up rebuilds) +- Supported architectures: amd64, arm64, ppc64le, s390x (plus Windows builds) +- Cross-compilation via `ARCH=` and binfmt registration (`calico/binfmt`) + +## Common Development Workflows + +### Making Code Changes + +1. Create feature branch from `master` +2. Make changes to relevant component(s) +3. Run component-specific tests: `make -C test` or `go test ./...` +4. Run validation: `make yaml-lint` (if YAML changed) +5. If APIs/config/CI changed: `make generate` +6. **MANDATORY:** Run `make fix-changed` to fix formatting +7. Commit changes (generated files must be included) +8. Push and create PR + +### Updating Helm Charts and Manifests + +- Charts are in `charts/` +- After editing chart templates: `make gen-manifests` +- This regenerates `manifests/` directory (mostly auto-generated) +- Commit both chart changes and regenerated manifests + +### Working with eBPF Code + +- eBPF programs: `felix/bpf-gpl/` (GPL v2.0 license for Linux compatibility) +- Apache licensed BPF code: `felix/bpf-apache/` +- Before building: `make -C felix clone-libbpf` +- BPF tooling configured in `metadata.mk` (LIBBPF_VERSION, BPFTOOL_IMAGE) + +### Cherry-picking to Release Branches + +1. Merge PR to master first +2. Use `hack/cherry-pick-pull` to create the cherry-pick PR: + ```bash + SRC_UPSTREAM_REMOTE=origin DST_UPSTREAM_REMOTE=origin FORK_REMOTE= CHERRY_PICK=1 \ + ./hack/cherry-pick-pull origin/release-vX.YY + ``` + +## Critical Files and Locations + +**Build Configuration:** +- `metadata.mk` - Version pins, tool versions, registry config (all tool/image versions pinned here) +- `lib.Makefile` - Shared Makefile logic for all components +- `Makefile` - Root orchestration + +**Component Entry Points:** +- `felix/daemon/daemon.go` - Felix main entry point +- `felix/calc/` - Felix calculation graph (policy processing brain) +- `felix/dataplane/` - Dataplane implementations (eBPF, iptables, nftables) +- `node/pkg/lifecycle/startup/startup.go` - Node initialization +- `calicoctl/calicoctl/calicoctl.go` - CLI entry point + +**Testing:** +- `felix/fv/` - Felix functional verification tests (Ginkgo v2-based) +- Component unit tests co-located with source code +- Felix FV supports batching: `FV_NUM_BATCHES` / `FV_BATCHES_TO_RUN` to split across CI jobs +- Race detector enabled by default on amd64/arm64 (`FV_RACE_DETECTOR_ENABLED`) + +## PR Requirements + +Every PR needs one docs label (`docs-pr-required`, `docs-completed`, or `docs-not-required`) and one release note label (`release-note-required` or `release-note-not-required`). Optional: `cherry-pick-candidate` (bug fix backports), `needs-operator-pr` (requires operator change). + +## Additional Resources + +- **Developer Guide:** `DEVELOPER_GUIDE.md` +- **Contributing Guide:** `CONTRIBUTING.md` +- **User Documentation:** https://docs.tigera.io/calico/latest/about +- **Hack docs:** `hack/docs/` diff --git a/.claude/skills/ci-reproduce-on-gcp-vm/SKILL.md b/.claude/skills/ci-reproduce-on-gcp-vm/SKILL.md new file mode 100644 index 00000000000..b16dee37a15 --- /dev/null +++ b/.claude/skills/ci-reproduce-on-gcp-vm/SKILL.md @@ -0,0 +1,145 @@ +--- +name: ci-reproduce-on-gcp-vm +description: Reproduce CI test failures on a GCP VM matching the CI environment. Use when a CI job fails and the issue cannot be reproduced locally (e.g., kernel-dependent BPF verifier failures, kernel version-specific bugs). +--- + +## Overview + +CI runs Felix tests on GCP VMs using specific Ubuntu image families. The local dev machine may have a different kernel, so some failures only reproduce on the CI kernel. This skill creates a GCP VM matching the CI environment, runs the failing test, and cleans up. + +## Prerequisites + +- `gcloud` CLI authenticated with access to the `tigera-dev` project +- The calico repo checked out locally with the failing branch + +## Step 1: Identify the CI Image Family + +Check `felix/.semaphore/fv-prologue` to find the image family for the failing CI job. The mapping is based on `FELIX_TEST_GROUP`: + +| Test group pattern | IMAGE_FAMILY | Ubuntu version | +|---|---|---| +| `22.04` | `ubuntu-2204-lts` | 22.04 Jammy | +| `24.04` | `ubuntu-2404-lts-amd64` | 24.04 Noble | +| `25.10` | `ubuntu-2510-amd64` | 25.10 Plucky | + +For example, the `bpf-24.04-ipt-with-ut` test group uses `ubuntu-2404-lts-amd64`. + +If in doubt, read `felix/.semaphore/fv-prologue` and `.semaphore/vms/vm-bootstrap.sh` for the latest mappings. + +## Step 2: Create the VM + +```bash +zone=us-central1-a +vm_name=-debug +image_family=ubuntu-2404-lts-amd64 # from Step 1 + +gcloud config set project tigera-dev +gcloud --quiet compute instances create "${vm_name}" \ + --zone=${zone} \ + --image-family=${image_family} \ + --image-project=ubuntu-os-cloud \ + --machine-type=n4-highcpu-4 \ + --boot-disk-size=20G \ + --boot-disk-type=hyperdisk-balanced +``` + +The machine type and disk size above match CI defaults (see `felix/.semaphore/fv-prologue`). + +## Step 3: Wait for SSH and Install Dependencies + +The VM bootstrap in CI is done by `.semaphore/vms/vm-bootstrap.sh`. Replicate its key steps: + +```bash +ssh_cmd="gcloud --quiet compute ssh --zone=${zone} ubuntu@${vm_name} --" + +# Wait for SSH +for i in $(seq 1 10); do + ${ssh_cmd} echo "SSH ready" && break + sleep 2 +done + +# Install prerequisites +${ssh_cmd} "sudo apt-get update -y && sudo apt-get install -y --no-install-recommends apt-transport-https ca-certificates curl software-properties-common" + +# Add Docker repo (DEB822 format, matching .semaphore/vms/vm-bootstrap.sh) +${ssh_cmd} "sudo install -d -m 0755 /etc/apt/keyrings && curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /tmp/docker.gpg && sudo mv /tmp/docker.gpg /etc/apt/keyrings/docker.asc && sudo chmod 0644 /etc/apt/keyrings/docker.asc" +${ssh_cmd} "ubuntu_codename=\$(. /etc/os-release && echo \"\${UBUNTU_CODENAME:-\$VERSION_CODENAME}\") && printf '%s\n' 'Types: deb' 'URIs: https://download.docker.com/linux/ubuntu' \"Suites: \${ubuntu_codename}\" 'Components: stable' 'Architectures: amd64' 'Signed-By: /etc/apt/keyrings/docker.asc' | sudo tee /etc/apt/sources.list.d/docker.sources > /dev/null" +${ssh_cmd} "sudo apt-get update -y" + +# Install Docker and tools — pin versions to match CI (see .semaphore/vms/vm-bootstrap.sh) +# Noble (24.04): docker-ce=5:27.5.1-1~ubuntu.24.04~noble +# Jammy (22.04): docker-ce=5:20.10.14~3-0~ubuntu-jammy +# If unsure, omit the version pin to get the latest. +${ssh_cmd} "sudo apt-get install -y --no-install-recommends docker-ce docker-ce-cli docker-buildx-plugin containerd.io git make iproute2 wireguard" + +# Post-install setup +${ssh_cmd} "sudo usermod -a -G docker ubuntu" +${ssh_cmd} "sudo modprobe ipip" + +# Configure Docker with IPv6 (required by many FV tests) +${ssh_cmd} 'echo "{\"ipv6\": true, \"fixed-cidr-v6\": \"2001:db8:1::/64\"}" | sudo tee /etc/docker/daemon.json' +${ssh_cmd} "sudo systemctl restart docker" + +# Match CI's sysctl setting (loose reverse path filtering) +${ssh_cmd} "sudo sysctl -w net.ipv4.conf.all.rp_filter=2" +``` + +## Step 4: Clone Repo and Checkout Branch + +```bash +# Get the current branch name +branch=$(git rev-parse --abbrev-ref HEAD) +remote_url=$(git remote get-url origin) + +${ssh_cmd} "git clone ${remote_url} calico && cd calico && git checkout ${branch}" +``` + +If the remote is an SSH URL and the VM doesn't have SSH keys, use the HTTPS URL instead: +```bash +# Convert git@github.com:user/repo.git to https://github.com/user/repo.git +https_url=$(echo "${remote_url}" | sed 's|git@github.com:|https://github.com/|') +${ssh_cmd} "git clone ${https_url} calico && cd calico && git checkout ${branch}" +``` + +## Step 5: Run the Failing Test + +Check the kernel version first to confirm it differs from local: +```bash +${ssh_cmd} "uname -r" +``` + +Then run the specific test. Common patterns: + +```bash +# BPF unit test (e.g., verifier loadability) +${ssh_cmd} "cd calico/felix && make FOCUS=TestPrecompiledBinariesAreLoadable ut-bpf" + +# Specific BPF unit test +${ssh_cmd} "cd calico/felix && make FOCUS=TestNATNodePortNoFWD ut-bpf" + +# Felix FV test +${ssh_cmd} "cd calico/felix && make fv GINKGO_FOCUS='TestName'" + +# BPF FV test +${ssh_cmd} "cd calico/felix && make fv-bpf GINKGO_FOCUS='TestName'" +``` + +The first run will be slow (pulls Docker build images). Subsequent runs are faster. + +## Step 6: Clean Up + +Always delete the VM when done: + +```bash +gcloud --quiet compute instances delete ${vm_name} --zone=${zone} +``` + +## Reference: CI Configuration Files + +| File | Purpose | +|---|---| +| `felix/.semaphore/fv-prologue` | Maps test groups to image families, sets env vars | +| `.semaphore/vms/vm-bootstrap.sh` | VM startup script (Docker install, sysctl, IPv6) | +| `.semaphore/vms/run-tests-on-vms` | Orchestrates VM creation and test execution | +| `.semaphore/vms/configure-test-vm` | Per-VM configuration after bootstrap | +| `.semaphore/semaphore.yml.d/blocks/20-felix.yml` | Felix CI job definitions and test groups | diff --git a/.claude/skills/design-kubernetes-api/SKILL.md b/.claude/skills/design-kubernetes-api/SKILL.md new file mode 100644 index 00000000000..13a08fb0c37 --- /dev/null +++ b/.claude/skills/design-kubernetes-api/SKILL.md @@ -0,0 +1,129 @@ +--- +name: design-kubernetes-api +description: Designs a best-practice, extensible Kubernetes API resource. Use when designing new API resources for Calico. +--- + +## Workflow + +- Start by finding out from the user, the proposed name, purpose and meaning of the new resource. + - Namespaced or not? + - Expected arity. + - Relationship to other resources (ownership/peer/selection-based links?). +- Try to get a full list of the current aspects of the resource that need to be configured and the "direction of travel" for extensions to those in future. +- Propose and iterate on a custom resource design. +- Follow the best practices outlined in the Kubernetes API conventions + at https://raw.githubusercontent.com/kubernetes/community/refs/heads/master/contributors/devel/sig-architecture/api-conventions.md +- In particular: + - Avoid booleans, in favour of enums that can be extended later. If in doubt, use Enabled/Disabled in place of a bool. + *NO* fooBarEnabled: true | false + *YES* fooBar: Enabled | Disabled + - Follow naming schemes laid out there: + - lowerCamel for field names + - UpperCamel for enum values +- Place a lot of weight on future maintenance and extension. + - Many Calico resources (such as FelixConfiguration and NetworkPolicy are poor examples, they use large "bag" structs). + - Group aspects of the object into sub-structs + - Make use of the union pattern for controlling alternation with efficient kubebuilder annotations instead of requiring custom inter-field validation. +- Consider ergonomics of using the new resource with kubectl; propose suitable kubebuilder annotations to print useful columns. +- Consider including a status sub-resource if the resource will naturally be operated upon by a kubernetes controller. +- When designing policy-related structs or similar, it's generally better to have separate structs for "ingress" and "egress" rule types, even if they are identical right now. Over time they tend to diverge. +- Avoid custom int-or-string fields in most cases. It's OK to use existing types but it's generally better to use unions. +- Review the naming and structure critically for consistency. Pay attention to suffix and prefix usage. Fields representing similar concepts should "read as siblings". + +### Union pattern + +This is a common pattern in Kubernetes APIs that allows extension over time. It avoids bloating a parent struct with many fields that are mutually exclusive. For example, the various flavours of port/protocol match in a network policy rule may be expressed like this: + +``` +port: + number: 8080 +``` + +or + +``` +port: + range: + min: 8000 + max: 8080 +``` + +or + +``` +port: + name: some-named-port +``` + +Pros: +- YAML read well. +- Expresses alternation structurally rather than with out-of-band validation. +- Go structs can express the kube-builder validation efficiently with these annotations on the struct. + ``` + // +kubebuilder:validation:MaxProperties=1 + // +kubebuilder:validation:MinProperties=1 // If one must be provided + ``` +- Always extensible by adding new alternation option. +Cons: +- We're stuck with the top-level name, "ports" in my example. Choose wisely and opt for something general. +- Can "stutter" if the obvious name for an inner field is the same as the outer. +- Can suffer combinatorial explosion if later want to mix and match formerly-orthogonal aspects. (For example, extending the above to support some new match.) + This is generally manageable, and it can be side-stepped if needed by introducing a general purpose node. Contrived example: + +``` +port: + mustMatchAll: + - range: + min: 8000 + max: 8080 + - portRemainder: + modulo: 3 + remainder: 1 +``` + +### Selectors + +Calico API's typically use our selector syntax instead of Kubernetes +matchLabels and similar. + +New APIs should avoid matching multiple resource types with the same selector. +We've learned that matching workload endpoints, host endpoints and network +sets with the same selector is confusing, for example. Prefer one selector per +type of thing that is matched. + +Be critical of the need for a selector, referencing a single item by +name/namespace is often simpler for the user. For example, we've found that +most uses of network sets simply use a name label on the network set and select +on that. + +When selecting namespaced resources, use split namespaceSelector and +itemTypeSelector. + +### Calico naming conventions. + +- Our API is slightly more general than Kubernetes, "pods" and OpenStack VMs map to "workloads" in our model. +- When referring to concepts from outside calico, try to use the most broadly adopted industry terms. Consider alternatives and back up your choices. + +## Output guide: + +- Generally best to iterate on the YAML to start with until it looks good - this is how the user will interact with it, but give the option to present the Go structs too. +- When presenting yaml, it's helpful to show the allowed alternatives inline, even if they wouldn't be valid in a real resource. For example, you could present a union like this: +``` +port: + number: 8080 + + # Or... + range: + min: 8000 + max: 8080 + + # Or... + namedPort: foo + +``` +- Go structs should include + - Comments suitable for end-user documentation (these will be picked up by the CRD schema generator). + - kubebuilder annotations for default values (where appropriate) and validation of enums and integer ranges. + - json field tags as needed + - for complex validation of custom types (avoid if possible) validation tags on the fields (these are interpreted by our custom validator in libcalico-go/lib/validator. +- Review the go structs field by field, explaining the proposed kube-builder annotations and validation. diff --git a/.claude/skills/implement-calico-api-resource/SKILL.md b/.claude/skills/implement-calico-api-resource/SKILL.md new file mode 100644 index 00000000000..e3412dbc85e --- /dev/null +++ b/.claude/skills/implement-calico-api-resource/SKILL.md @@ -0,0 +1,586 @@ +--- +name: implement-calico-api-resource +description: Implements a new Calico API resource by plumbing it through all layers of the codebase. Use after API design is complete (see design-kubernetes-api skill). +--- + +## Prerequisites + +Before using this skill, ensure you have: +- A designed API resource (Go structs with kubebuilder annotations, json tags, etc.) — use the `design-kubernetes-api` skill first. +- Know whether the resource is **namespaced** or **cluster-scoped**. +- Know the **Kind**, **plural name**, and any **short names** for kubectl. + +## Overview of Layers + +Adding a new Calico API resource touches these layers (in dependency order): + +1. **API type definition** (`api/pkg/apis/projectcalico/v3/`) +2. **Code generation** (deepcopy, clients, informers, listers, OpenAPI) +3. **CRD operator types** (`libcalico-go/lib/apis/crd.projectcalico.org/v1/`) +4. **CRD v1 scheme registration** (`libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/scheme.go`) +5. **Backend model registration** (`libcalico-go/lib/backend/model/resource.go`) +6. **K8s backend resource client** (`libcalico-go/lib/backend/k8s/resources/`) +7. **K8s backend client registration** (`libcalico-go/lib/backend/k8s/client.go`) +8. **Namespace helper** (if namespaced) (`libcalico-go/lib/namespace/resource.go`) +9. **Validator** (`libcalico-go/lib/validator/v3/validator.go`) +10. **clientv3 typed client** (`libcalico-go/lib/clientv3/`) +11. **Apiserver registry** (storage, strategy, REST) (`apiserver/pkg/registry/projectcalico/`) +12. **Apiserver Calico storage adapter** (`apiserver/pkg/storage/calico/`) +13. **Apiserver storage interface switch** (`apiserver/pkg/storage/calico/storage_interface.go`) +14. **Apiserver converter** (`apiserver/pkg/storage/calico/converter.go`) +15. **Apiserver REST storage provider** (`apiserver/pkg/registry/projectcalico/rest/storage_calico.go`) +16. **Felix syncer** (if Felix needs this resource) (`libcalico-go/lib/backend/syncersv1/felixsyncer/`) +17. **RBAC** (Helm chart RBAC for node/operator) +18. **Manifests and CRD YAML** (generated) + +## Workflow + +Work through the following steps in order. Each step references specific files and patterns to follow. + +### Step 1: API Type Definition + +Create the Go type file in `api/pkg/apis/projectcalico/v3/`. + +**File:** `api/pkg/apis/projectcalico/v3/.go` + +Follow this pattern (using BGPFilter as a clean example): + +```go +// Copyright (c) Tigera, Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 ... + +package v3 + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +const ( + KindMyResource = "MyResource" + KindMyResourceList = "MyResourceList" +) + +// +genclient:nonNamespaced (cluster-scoped only) +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// MyResourceList contains a list of MyResource resources. +type MyResourceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` + Items []MyResource `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +// For cluster-scoped: +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Cluster,shortName={myres} + +// For namespaced: +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Namespaced,shortName={myres} + +// MyResource represents . +type MyResource struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` + Spec MyResourceSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` +} + +// MyResourceSpec contains the specification for a MyResource resource. +type MyResourceSpec struct { + // ... fields with kubebuilder validation annotations +} + +// NewMyResource creates a new (zeroed) MyResource struct with TypeMetadata initialised. +func NewMyResource() *MyResource { + return &MyResource{ + TypeMeta: metav1.TypeMeta{ + Kind: KindMyResource, + APIVersion: GroupVersionCurrent, + }, + } +} +``` + +**Key annotations:** +- `+genclient` — generates typed client code +- `+genclient:nonNamespaced` — for cluster-scoped resources (put on BOTH the list type and the main type) +- `+k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object` — generates DeepCopyObject() +- `+kubebuilder:resource:scope=Cluster|Namespaced` — CRD scope +- `+kubebuilder:resource:shortName={...}` — kubectl short names + +**Gotcha:** The `List` type needs `+genclient:nonNamespaced` too (for cluster-scoped resources), and `+k8s:deepcopy-gen:interfaces=...` on both types. Do NOT put `+kubebuilder:resource` on the List type — only on the main resource type. + +### Step 2: Register in API Scheme + +**File:** `api/pkg/apis/projectcalico/v3/register.go` + +Add both types to the `AllKnownTypes` slice: + +```go +AllKnownTypes = []runtime.Object{ + // ... existing types ... + &MyResource{}, + &MyResourceList{}, +} +``` + +### Step 3: Run API Code Generation + +```bash +cd api && make gen-files +``` + +This generates the DeepCopy methods, typed Kubernetes client, informers, listers, and OpenAPI schema needed for compilation of downstream layers: +- `api/pkg/apis/projectcalico/v3/zz_generated.deepcopy.go` — DeepCopy methods +- `api/pkg/client/clientset_generated/` — typed Kubernetes client +- `api/pkg/client/informers_generated/` — informer factories +- `api/pkg/client/listers_generated/` — listers +- `api/pkg/openapi/generated.openapi.go` — OpenAPI schema + +Run this early so the remaining steps can compile against the generated types. A full `make generate` at the project root is still needed later (Step 19) to pick up CRDs, manifests, and other downstream generated files. + +### Step 4: CRD Operator Types (crd.projectcalico.org/v1) + +Calico has a dual-CRD system. Older clusters use `crd.projectcalico.org/v1` CRDs, newer ones use `projectcalico.org/v3`. New resources need a type definition in the v1 scheme for backward compatibility. + +**File:** `libcalico-go/lib/apis/crd.projectcalico.org/v1/_types.go` + +```go +package v1 + +import ( + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Cluster // or Namespaced +type MyResource struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec v3.MyResourceSpec `json:"spec,omitempty"` +} +``` + +Note: the v1 type re-uses the v3 Spec struct — only the top-level wrapper differs. + +### Step 5: Register in CRD v1 Scheme + +**File:** `libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/scheme.go` + +Add the type to the `BuilderCRDv1()` function's `AddKnownTypes` call: + +```go +&apiv3.MyResource{}, +&apiv3.MyResourceList{}, +``` + +### Step 6: Backend Model Registration + +**File:** `libcalico-go/lib/backend/model/resource.go` + +Add to the `init()` function: + +```go +registerResourceInfo[apiv3.MyResource](apiv3.KindMyResource, "myresources") +``` + +The plural name here must match the CRD plural. This enables the generic `ResourceKey`-based storage path. + +### Step 7: K8s Backend Resource Client + +**File:** `libcalico-go/lib/backend/k8s/resources/.go` + +For simple resources that use the v3 CRDs directly (typical for new resources): + +```go +package resources + +import ( + "reflect" + + apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "k8s.io/client-go/rest" +) + +const ( + MyResourceResourceName = "MyResources" +) + +func NewMyResourceClient(r rest.Interface, group BackingAPIGroup) K8sResourceClient { + return &customResourceClient{ + restClient: r, + resource: MyResourceResourceName, + k8sResourceType: reflect.TypeOf(apiv3.MyResource{}), + k8sListType: reflect.TypeOf(apiv3.MyResourceList{}), + kind: apiv3.KindMyResource, + apiGroup: group, + } +} +``` + +**Gotcha:** The `resource` field is the CRD resource name (plural, PascalCase used by the REST client). + +### Step 8: Register in K8s Backend Client + +**File:** `libcalico-go/lib/backend/k8s/client.go` + +Add to the resource client registration block: + +```go +c.registerResourceClient( + reflect.TypeOf(model.ResourceKey{}), + reflect.TypeOf(model.ResourceListOptions{}), + apiv3.KindMyResource, + resources.NewMyResourceClient(restClient, group), +) +``` + +If there is any CRD cleanup or garbage-collection mechanism for v3 kinds in this client, ensure your new kind is included there as appropriate. + +### Step 9: Namespace Helper (if namespaced) + +**File:** `libcalico-go/lib/namespace/resource.go` + +If your resource is namespaced, add its Kind to the `IsNamespaced` switch: + +```go +func IsNamespaced(kind string) bool { + switch kind { + case // ... existing cases ..., + apiv3.KindMyResource: + return true + // ... + } +} +``` + +### Step 10: Validator + +**File:** `libcalico-go/lib/validator/v3/validator.go` + +**Prefer kubebuilder annotations** for validation wherever possible (e.g., `+kubebuilder:validation:Enum`, `+kubebuilder:validation:Pattern`, `+kubebuilder:validation:Required`). Kubebuilder annotations generate CRD schema validation that is enforced by the Kubernetes API server. The Go struct validator in this file is only executed by the `crd.projectcalico.org/v1` code path — it is **not** executed for `projectcalico.org/v3` CRDs, so any validation that only lives here will be silently skipped on newer clusters. + +If your resource needs cross-field or complex validation that cannot be expressed with kubebuilder annotations, register a struct validator: + +```go +// In the init/registration function: +registerStructValidator(validate, validateMyResourceSpec, api.MyResourceSpec{}) + +// Validation function: +func validateMyResourceSpec(structLevel validator.StructLevel) { + spec := structLevel.Current().Interface().(api.MyResourceSpec) + // ... validation logic ... +} +``` + +Simple resources may not need custom validation if kubebuilder annotations are sufficient. + +### Step 11: clientv3 Typed Client + +**File:** `libcalico-go/lib/clientv3/.go` + +Create the typed client interface and implementation: + +```go +package clientv3 + +import ( + "context" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "github.com/projectcalico/calico/libcalico-go/lib/options" + validator "github.com/projectcalico/calico/libcalico-go/lib/validator/v3" + "github.com/projectcalico/calico/libcalico-go/lib/watch" +) + +// MyResourceInterface has methods to work with MyResource resources. +type MyResourceInterface interface { + Create(ctx context.Context, res *v3.MyResource, opts options.SetOptions) (*v3.MyResource, error) + Update(ctx context.Context, res *v3.MyResource, opts options.SetOptions) (*v3.MyResource, error) + Delete(ctx context.Context, name string, opts options.DeleteOptions) (*v3.MyResource, error) + Get(ctx context.Context, name string, opts options.GetOptions) (*v3.MyResource, error) + List(ctx context.Context, opts options.ListOptions) (*v3.MyResourceList, error) + Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) +} + +// myResources implements MyResourceInterface +type myResources struct { + client client +} + +func (r myResources) Create(ctx context.Context, res *v3.MyResource, opts options.SetOptions) (*v3.MyResource, error) { + if err := validator.Validate(res); err != nil { + return nil, err + } + out, err := r.client.resources.Create(ctx, opts, v3.KindMyResource, res) + if out != nil { + return out.(*v3.MyResource), err + } + return nil, err +} + +// ... Update, Delete, Get, List, Watch follow the same pattern. +// For namespaced: Delete/Get take (namespace, name) instead of just name. +// For namespaced: use namespace parameter instead of noNamespace constant. +``` + +**File:** `libcalico-go/lib/clientv3/interface.go` + +Add the client interface: + +```go +type MyResourceClient interface { + MyResources() MyResourceInterface +} +``` + +Add `MyResourceClient` to the main `Interface` interface. + +**File:** `libcalico-go/lib/clientv3/client.go` + +Add the accessor method: + +```go +func (c client) MyResources() MyResourceInterface { + return myResources{client: c} +} +``` + +### Step 12: Apiserver Registry + +Create a new directory: `apiserver/pkg/registry/projectcalico//` + +**File:** `apiserver/pkg/registry/projectcalico//storage.go` + +Follow the pattern from `apiserver/pkg/registry/projectcalico/ipamconfig/storage.go`: +- `EmptyObject()` returns `&calico.MyResource{}` +- `NewList()` returns `&calico.MyResourceList{}` +- `NewREST()` creates the registry.Store with: + - `KeyRootFunc` / `KeyFunc` using `opts.KeyRootFunc(namespaced)` / `opts.KeyFunc(namespaced)` + - `NoNamespaceKeyFunc` (cluster-scoped) or `NamespaceKeyFunc` (namespaced) + - `DefaultQualifiedResource: calico.Resource("myresources")` + +**File:** `apiserver/pkg/registry/projectcalico//strategy.go` + +Follow the pattern from `apiserver/pkg/registry/projectcalico/ipamconfig/strategy.go`: +- `NamespaceScoped()` returns true/false based on resource scope +- `GetAttrs`, `MatchMyResource`, `MyResourceToSelectableFields` functions + +### Step 13: Apiserver Calico Storage Adapter + +**File:** `apiserver/pkg/storage/calico/_storage.go` + +Follow the pattern from `apiserver/pkg/storage/calico/ipamconfig_storage.go`. This wires the apiserver to the libcalico-go clientv3: + +```go +func NewMyResourceStorage(opts Options) (registry.DryRunnableStorage, factory.DestroyFunc) { + c := CreateClientFromConfig() + createFn := func(ctx context.Context, c clientv3.Interface, obj resourceObject, opts clientOpts) (resourceObject, error) { + oso := opts.(options.SetOptions) + res := obj.(*api.MyResource) + return c.MyResources().Create(ctx, res, oso) + } + // ... update, get, delete, list, watch functions ... + // Build resourceStore with converter +} +``` + +Include a converter struct that handles `convertToLibcalico`, `convertToAAPI`, and `convertToAAPIList`. For simple resources where the API type is the same in both layers, the converter is a straightforward field copy. + +### Step 14: Apiserver Storage Interface Switch + +**File:** `apiserver/pkg/storage/calico/storage_interface.go` + +Add a case to the `NewStorage` switch: + +```go +case "projectcalico.org/myresources": + return NewMyResourceStorage(opts) +``` + +### Step 15: Apiserver Converter + +**File:** `apiserver/pkg/storage/calico/converter.go` + +Add a case to the `convertToAAPI` function: + +```go +case *v3.MyResource: + aapi := &v3.MyResource{} + MyResourceConverter{}.convertToAAPI(obj, aapi) + return aapi +``` + +### Step 16: Apiserver REST Storage Provider + +**File:** `apiserver/pkg/registry/projectcalico/rest/storage_calico.go` + +1. Add import for the new registry package +2. Create REST options and server.Options (follow the existing pattern) +3. Add to the storage map: + +```go +storage["myresources"] = rESTInPeace(calico.NewREST(scheme, *myresourceOpts)) +``` + +### Step 17: Syncers (Conditional) + +**Only needed if a component (Felix, confd, etc.) needs to watch this resource.** + +New resources use `ResourceKey` and are passed directly through the syncer layer without transformation. This means you typically do NOT need an update processor. + +**File:** `libcalico-go/lib/backend/syncersv1/felixsyncer/felixsyncerv1.go` + +Add to the appropriate section (always-on or leader-only): + +```go +{ + ListInterface: model.ResourceListOptions{Kind: apiv3.KindMyResource}, +}, +``` + +No `UpdateProcessor` is needed for resources using `ResourceKey` — they pass through as-is to Felix/confd. + +**Alternative: Components using Kubernetes informers** — Some components (kube-controllers, webhooks) use the generated Kubernetes informers from `api/pkg/client/informers_generated/` instead of the syncer layer. These components automatically pick up new resources after code generation (Step 3) without additional plumbing. Check if your consuming component uses: +- **Syncer** (Felix, Typha via syncer): needs explicit registration in the syncer. +- **Informers** (kube-controllers, etc.): automatically available after codegen, but may need wiring in the controller. + +### Step 18: calicoctl Resource Manager + +**File:** `calicoctl/calicoctl/resourcemgr/.go` + +Register the resource for calicoctl CRUD commands (create, get, update, delete, replace). This is a single file with an `init()` function — no other calicoctl files need changing. + +```go +package resourcemgr + +import ( + "context" + + api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" + "github.com/projectcalico/calico/libcalico-go/lib/options" +) + +func init() { + registerResource( + api.NewMyResource(), + newMyResourceList(), + false, // isNamespaced + []string{"myresource", "myresources"}, + []string{"NAME"}, + []string{"NAME"}, + map[string]string{ + "NAME": "{{.ObjectMeta.Name}}", + }, + func(ctx context.Context, client client.Interface, resource ResourceObject) (ResourceObject, error) { + r := resource.(*api.MyResource) + return client.MyResources().Create(ctx, r, options.SetOptions{}) + }, + func(ctx context.Context, client client.Interface, resource ResourceObject) (ResourceObject, error) { + r := resource.(*api.MyResource) + return client.MyResources().Update(ctx, r, options.SetOptions{}) + }, + func(ctx context.Context, client client.Interface, resource ResourceObject) (ResourceObject, error) { + r := resource.(*api.MyResource) + return client.MyResources().Delete(ctx, r.Name, options.DeleteOptions{ResourceVersion: r.ResourceVersion}) + }, + func(ctx context.Context, client client.Interface, resource ResourceObject) (ResourceObject, error) { + r := resource.(*api.MyResource) + return client.MyResources().Get(ctx, r.Name, options.GetOptions{ResourceVersion: r.ResourceVersion}) + }, + func(ctx context.Context, client client.Interface, resource ResourceObject) (ResourceListObject, error) { + r := resource.(*api.MyResource) + return client.MyResources().List(ctx, options.ListOptions{ResourceVersion: r.ResourceVersion, Name: r.Name}) + }, + ) +} + +func newMyResourceList() *api.MyResourceList { + return &api.MyResourceList{ + TypeMeta: metav1.TypeMeta{ + Kind: api.KindMyResourceList, + APIVersion: api.GroupVersionCurrent, + }, + } +} +``` + +For **namespaced** resources: set `isNamespaced` to `true`, add `"NAMESPACE": "{{.ObjectMeta.Namespace}}"` to the headings map, and pass `r.Namespace` as the first argument to Delete/Get/List client calls. + +The `init()` function auto-registers the resource — calicoctl's generic CRUD commands, help text, and resource name resolution all pick it up automatically. + +### Step 19: RBAC + +**File:** `charts/calico/templates/calico-node-rbac.yaml` (and/or operator role templates) + +Add RBAC rules for the new resource if components need to access it: + +```yaml +- apiGroups: ["projectcalico.org"] + resources: ["myresources"] + verbs: ["get", "list", "watch"] +``` + +### Step 20: Full Generation, Formatting, and Commit + +```bash +# Regenerate everything — CRDs, manifests, CI config, and any remaining generated files +make generate + +# This also runs make fix-changed automatically at the end. +# Verify +make yaml-lint +make check-go-mod +``` + +**Gotcha:** `make generate` at the project root produces many downstream files beyond the `api/` directory — CRD YAML in `manifests/`, Helm chart outputs, Semaphore CI config, etc. You MUST commit all generated files alongside your source changes. CI will reject PRs with stale generated files. + +## Checklist Summary + +Use this checklist to verify completeness: + +- [ ] API type file in `api/pkg/apis/projectcalico/v3/` +- [ ] Registered in `api/pkg/apis/projectcalico/v3/register.go` (`AllKnownTypes`) +- [ ] Code generation run (`cd api && make gen-files`) +- [ ] CRD v1 type in `libcalico-go/lib/apis/crd.projectcalico.org/v1/` +- [ ] CRD v1 scheme registration in `.../scheme/scheme.go` +- [ ] Backend model registered in `libcalico-go/lib/backend/model/resource.go` +- [ ] K8s backend resource client in `libcalico-go/lib/backend/k8s/resources/` +- [ ] K8s backend client registration in `libcalico-go/lib/backend/k8s/client.go` +- [ ] Namespace helper updated (if namespaced) in `libcalico-go/lib/namespace/resource.go` +- [ ] Validator in `libcalico-go/lib/validator/v3/validator.go` (if needed) +- [ ] clientv3 typed client in `libcalico-go/lib/clientv3/` +- [ ] clientv3 interface updated in `libcalico-go/lib/clientv3/interface.go` +- [ ] clientv3 client accessor in `libcalico-go/lib/clientv3/client.go` +- [ ] Apiserver registry (storage.go + strategy.go) in `apiserver/pkg/registry/projectcalico//` +- [ ] Apiserver Calico storage adapter in `apiserver/pkg/storage/calico/_storage.go` +- [ ] Apiserver storage interface switch in `apiserver/pkg/storage/calico/storage_interface.go` +- [ ] Apiserver converter case in `apiserver/pkg/storage/calico/converter.go` +- [ ] Apiserver REST storage provider in `apiserver/pkg/registry/projectcalico/rest/storage_calico.go` +- [ ] Felix syncer (if needed) in `libcalico-go/lib/backend/syncersv1/felixsyncer/felixsyncerv1.go` +- [ ] calicoctl resource manager in `calicoctl/calicoctl/resourcemgr/.go` +- [ ] RBAC rules in Helm charts +- [ ] Formatting applied (`make fix-changed`) +- [ ] All generated files committed + +## Common Gotchas + +1. **Forgetting to regenerate:** Always run `cd api && make gen-files` after changing API types, and `make generate` at root level for CRDs and manifests. + +2. **CRD plural mismatch:** The plural name must be consistent across `model/resource.go`, the apiserver storage interface, the REST storage provider, and the CRD YAML. Kubernetes lowercases everything. + +3. **Missing scheme registration:** Resources must be registered in BOTH `api/pkg/apis/projectcalico/v3/register.go` (the API scheme) AND `libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/scheme.go` (the CRD v1 scheme). + +4. **Dual CRD versions:** Calico supports both `crd.projectcalico.org/v1` and `projectcalico.org/v3` CRDs. New resources need type definitions and resource clients that handle both API groups, or at minimum a type in the v1 scheme. + +5. **Syncer vs Informer confusion:** Felix/Typha use the syncer layer (explicit registration needed). Kube-controllers and webhooks use Kubernetes informers (automatic from codegen). Check which pattern your consuming component uses. + +6. **ResourceKey passthrough:** New resources should use `ResourceKey`/`ResourceListOptions` for the syncer. They do NOT need update processors — the resource passes through as-is. Only legacy resources that need v1-to-v3 conversion use update processors. + +7. **Status subresource:** If your resource has a Status field, you need additional apiserver plumbing for the `/status` subresource endpoint (see `kubecontrollersconfig` for an example). diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 568e0b49ac0..31714ead58a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,5 +4,14 @@ # By default, changes require review from the core maintainers. * @projectcalico/core-maintainers -# The docs team will be the default owners for the docs directory. -calico/* @docs +# Gateway and Proxy components +third_party/envoy-gateway/ @projectcalico/ingress-maintainers +third_party/envoy-proxy/ @projectcalico/ingress-maintainers +third_party/envoy-ratelimit/ @projectcalico/ingress-maintainers + +# Application Layer Policy (Dikastes, L7 policy enforcement with Envoy/Istio) +app-policy/ @projectcalico/ingress-maintainers + +# E2E runs +.semaphore/end-to-end/ @projectcalico/e2e-maintainers +e2e/ @projectcalico/e2e-maintainers diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 961d2ae8744..9f74918a1c4 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,58 +1,8 @@ -## Description - - - -## Related issues/PRs - - - -## Todos - -- [ ] Tests -- [ ] Documentation -- [ ] Release note - -## Release Note - - + + +**Release note:** ```release-note TBD ``` - -## Reminder for the reviewer - -Make sure that this PR has the correct labels and milestone set. - -Every PR needs one `docs-*` label. - -- `docs-pr-required`: This change requires a change to the documentation that has not been completed yet. -- `docs-completed`: This change has all necessary documentation completed. -- `docs-not-required`: This change has no user-facing impact and requires no docs. - -Every PR needs one `release-note-*` label. - -- `release-note-required`: This PR has user-facing changes. Most PRs should have this label. -- `release-note-not-required`: This PR has no user-facing changes. - -Other optional labels: - -- `cherry-pick-candidate`: This PR should be cherry-picked to an earlier release. For bug fixes only. -- `needs-operator-pr`: This PR is related to install and requires a corresponding change to the operator. diff --git a/.github/workflows/calico-github-issues-bot-trigger.yml b/.github/workflows/calico-github-issues-bot-trigger.yml new file mode 100644 index 00000000000..058b9fe085f --- /dev/null +++ b/.github/workflows/calico-github-issues-bot-trigger.yml @@ -0,0 +1,76 @@ +name: Trigger Calico Github Issues Bot + +on: + issues: + types: [opened] + issue_comment: + types: [created] + +permissions: + contents: read + issues: read + +jobs: + trigger-calico-github-issues-bot: + runs-on: ubuntu-latest + + # Ignore PR comments and bot comments + if: > + (github.event_name == 'issues') || + ( + github.event_name == 'issue_comment' && + github.event.issue.pull_request == null && + github.event.comment.user.type != 'Bot' + ) + + steps: + - name: Print debug info + run: | + echo "Event: ${{ github.event_name }}" + echo "Issue number: ${{ github.event.issue.number }}" + echo "Comment ID: ${{ github.event.comment.id }}" + echo "Author: ${{ github.event.comment.user.login }}" + + - name: Trigger Calico Github Issues Bot + env: + CALI_BOT_PAT: ${{ secrets.CALI_BOT_PAT }} + run: | + set -euo pipefail + + if [ -z "$CALI_BOT_PAT" ]; then + echo "CALI_BOT_PAT is not set" + exit 1 + fi + + EVENT_TYPE="" + COMMENT_ID="" + COMMENT_AUTHOR="" + + if [ "${{ github.event_name }}" = "issues" ]; then + EVENT_TYPE="issue_opened" + else + EVENT_TYPE="comment_created" + COMMENT_ID="${{ github.event.comment.id }}" + COMMENT_AUTHOR="${{ github.event.comment.user.login }}" + fi + + echo "Dispatching Calico Github Issues Bot with event: $EVENT_TYPE" + + curl --fail --show-error --silent -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $CALI_BOT_PAT" \ + https://api.github.com/repos/tigera/cali-bot/actions/workflows/process-issue.yml/dispatches \ + -d @- < + github.event.pull_request.merged == true && + github.event.pull_request.base.ref == 'master' + runs-on: ubuntu-latest + + steps: + - name: Print information about the PR + run: | + echo "PR number: ${{ github.event.pull_request.number }}" + echo "Merge commit SHA: ${{ github.event.pull_request.merge_commit_sha }}" + echo "Base branch: ${{ github.event.pull_request.base.ref }}" + + - name: Trigger Merge Cat + run: | + curl -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.TIGERA_BOT_PAT }}" \ + https://api.github.com/repos/tigera/merge-queue-bot/actions/workflows/221254945/dispatches \ + -d @- <<'EOF' + { + "ref": "master", + "inputs": { + "trigger_pr_number": "${{ github.event.pull_request.number }}", + "trigger_merge_sha": "${{ github.event.pull_request.merge_commit_sha }}", + "triggered_by_action": "OSS_MERGE" + } + } + EOF diff --git a/.gitignore b/.gitignore index 10d086f268a..78c52f2961c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,15 @@ -.go-pkg-cache/ -report/ -bin/ *.created* *.pyc +.go-pkg-cache/ .idea .vscode -vendor/ artifacts/ -postrelease-checks.xml +bin/ +coverprofile.out crypto/tmp +postrelease-checks.xml +report/ +vendor/ venv/ /* Produced as part of release */ diff --git a/.semaphore/cleanup.yml b/.semaphore/cleanup.yml index 604402d7d47..2c28fcf5fcd 100644 --- a/.semaphore/cleanup.yml +++ b/.semaphore/cleanup.yml @@ -1,5 +1,5 @@ version: v1.0 -name: Felix +name: Clean up cloud resources agent: machine: type: f1-standard-2 @@ -23,8 +23,7 @@ blocks: jobs: - name: Clean up GCE instances commands: - - cd felix - - ./.semaphore/clean-up-vms ${VM_PREFIX} + - ./.semaphore/vms/clean-up-vms ${VM_PREFIX} secrets: - name: google-service-account-for-gce @@ -35,13 +34,22 @@ blocks: commands: - checkout - az login --service-principal -u "${AZ_SP_ID}" -p "${AZ_SP_PASSWORD}" --tenant "${AZ_TENANT_ID}" --output none - - cd process/testing/util + - export AZURE_SUBSCRIPTION_ID=$AZ_SUBSCRIPTION_ID + - export AZURE_TENANT_ID=$AZ_TENANT_ID + - export AZURE_CLIENT_ID=$AZ_SP_ID + - export AZURE_CLIENT_SECRET=$AZ_SP_PASSWORD jobs: - name: Clean up windows felix resources commands: - - ./delete-az-rg.sh ${USER}-capz-win-felix-${SEMAPHORE_WORKFLOW_ID:0:8}-rg + - export RG_PREFIX=${USER}-win-felix-${SEMAPHORE_GIT_SHA:0:4}-pr${SEMAPHORE_GIT_PR_NUMBER} + - export AZURE_RESOURCE_GROUP=${RG_PREFIX}-rg + - cd ~/calico/process/testing/aso && make dist-clean - name: Clean up windows cni resources commands: - - ./delete-az-rg.sh ${USER}-capz-win-cni-${SEMAPHORE_WORKFLOW_ID:0:8}-rg + - export RG_PREFIX=${USER}-win-cni-${SEMAPHORE_GIT_SHA:0:4}-pr${SEMAPHORE_GIT_PR_NUMBER} + - export AZURE_RESOURCE_GROUP=${RG_PREFIX}-overlay-rg + - cd ~/calico/process/testing/aso && make dist-clean + - export AZURE_RESOURCE_GROUP=${RG_PREFIX}-l2bridge-rg + - cd ~/calico/process/testing/aso && make dist-clean secrets: - name: banzai-secrets diff --git a/.semaphore/collect-artifacts b/.semaphore/collect-artifacts new file mode 100755 index 00000000000..ef25393efd2 --- /dev/null +++ b/.semaphore/collect-artifacts @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +# Copyright (c) 2020-2025 Tigera, Inc. All rights reserved. +# +# 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. + +# No set -e so that we continue to collect artifacts even if some fail. +set -x + +my_dir="$(dirname "$0")" +repo_dir="$my_dir/.." + +cd "$repo_dir" || exit 1 + +mkdir -p "artifacts" + +if [ "$COMPONENT" = "felix" ]; then + + echo "Collecting artifacts for felix..." + for file in /tmp/core_felix* ; do + if [ -f "$file" ]; then + echo "Adding core file: $file" + sudo chmod a+r "$file" + out_file="artifacts/$(basename "$file").xz" + xz < "$file" > "$out_file" + fi + done + races="felix/fv/data-races.log" + if [ -s "$races" ]; then + # Only save the log if it's non-empty. + xz < "$races" > artifacts/data-races.log.xz + fi + mkdir -p "artifacts/report" + cp felix/report/*.xml "artifacts/report" || echo "Failed to copy XML reports (maybe there are none)." + cp felix/report/*.json "artifacts/report" || echo "Failed to copy JSON reports (maybe there are none)." + +elif [ "$COMPONENT" = "node" ]; then + + echo "Collecting artifacts for node..." + mkdir -p "artifacts/report" + cp node/report/*.xml "artifacts/report" || echo "Failed to copy reports." + +fi + +exit 0 diff --git a/.semaphore/end-to-end/pipelines/benchmarking.yml b/.semaphore/end-to-end/pipelines/benchmarking.yml new file mode 100644 index 00000000000..2e8e0635239 --- /dev/null +++ b/.semaphore/end-to-end/pipelines/benchmarking.yml @@ -0,0 +1,103 @@ +version: v1.0 + +name: banzai-calico Benchmarking + +# This file runs performance benchmarking tests for Calico. + +agent: + machine: + type: c1-standard-1 + os_image: ubuntu2204 + +execution_time_limit: + hours: 12 + +global_job_config: + prologue: + commands_file: ../scripts/global_prologue.sh + epilogue: + always: + commands: + - ~/calico/.semaphore/end-to-end/scripts/global_epilogue.sh + secrets: + - name: marvin-github-ssh-private-key + - name: banzai-secrets + env_vars: + - name: SEMAPHORE_ARTIFACT_EXPIRY + value: 1w + - name: PRODUCT + value: calico + - name: GOOGLE_PROJECT + value: unique-caldron-775 + - name: GOOGLE_NETWORK + value: semaphore-autotest + - name: OPENSHIFT_BASE_DOMAIN + value: openshift.ci.aws.eng.tigera.net + - name: KOPS_STATE_STORE_NAME + value: kops-tigera-dev-ci + - name: KOPS_AWS_DNS_ZONE + value: kops.ci.aws.eng.tigera.net + - name: RANCHER_DNS_NAME + value: rancher.ci.gcp.eng.tigera.net + - name: FUNCTIONAL_AREA + value: "benchmarking.yml" + - name: BENCHMARKER_IMAGE + value: gcr.io/unique-caldron-775/banzai-tests/benchmark:master + - name: STO_RELEASE + value: v0.16.0 + - name: USE_PRIVATE_REGISTRY + value: "true" + - name: PRIVATE_REGISTRY + value: "quay.io/" + +blocks: + - name: Benchmarks + dependencies: [] + task: + jobs: + - name: Benchmark tests Calico + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: PRODUCT + values: ["calico"] + env_vars: + - name: NUM_ADDON_PODS + value: "300" + - name: USE_HASH_RELEASE + value: "true" + - name: VPC_SUBNETS + value: '["172.16.101.0/24"]' + - name: K8S_VERSION + value: "stable" + - name: NUM_INFRA_NODES + value: "3" + - name: AWS_NODE_INSTANCE_TYPE + value: m5.large + - name: DATAPLANE + value: "CalicoIptables" + - name: PROVISIONER + value: "aws-kubeadm" + - name: INSTALLER + value: "operator" + - name: TEST_TYPE + value: "benchmark" + - name: BENCHMARKS + value: "benchmarker" + - name: BENCHMARKER_TEST_CONFIGS + value: > + '[{"testtype":"sto","numpolicies":[10,100,1000,10000],"encap":"none","dataplane":"iptables"},{"testtype":"qperf","numpolicies":[10,100,1000,10000],"encap":"none","dataplane":"iptables"},{"testtype":"iperf","numpolicies":[10,100,1000,10000],"encap":"none","dataplane":"iptables"},{"testtype":"sto","numpolicies":[10,100,1000,10000],"encap":"vxlan","dataplane":"iptables"},{"testtype":"qperf","numpolicies":[10,100,1000,10000],"encap":"vxlan","dataplane":"iptables"},{"testtype":"iperf","numpolicies":[10,100,1000,10000],"encap":"vxlan","dataplane":"iptables"},{"testtype":"sto","numpolicies":[10,100,1000,10000],"encap":"none","dataplane":"bpf"},{"testtype":"qperf","numpolicies":[10,100,1000,10000],"encap":"none","dataplane":"bpf"},{"testtype":"iperf","numpolicies":[10,100,1000,10000],"encap":"none","dataplane":"bpf"},{"testtype":"sto","numpolicies":[10,100,1000,10000],"encap":"vxlan","dataplane":"bpf"},{"testtype":"qperf","numpolicies":[10,100,1000,10000],"encap":"vxlan","dataplane":"bpf"},{"testtype":"iperf","numpolicies":[10,100,1000,10000],"encap":"vxlan","dataplane":"bpf"}]' +promotions: + - name: Cleanup jobs + pipeline_file: cleanup.yml + auto_promote: + when: "result = 'stopped'" + +after_pipeline: + task: + jobs: + - name: Reports + commands: + - test-results gen-pipeline-report --force diff --git a/.semaphore/end-to-end/pipelines/bpf.yml b/.semaphore/end-to-end/pipelines/bpf.yml new file mode 100644 index 00000000000..8f30f88de57 --- /dev/null +++ b/.semaphore/end-to-end/pipelines/bpf.yml @@ -0,0 +1,697 @@ +version: v1.0 + +name: BPF +# This file contains e2e runs for BPF. + +agent: + machine: + type: c1-standard-1 + os_image: ubuntu2204 + +execution_time_limit: + hours: 12 + +global_job_config: + prologue: + commands_file: ../scripts/global_prologue.sh + epilogue: + always: + commands: + - ~/calico/.semaphore/end-to-end/scripts/global_epilogue.sh + secrets: + - name: marvin-github-ssh-private-key + - name: banzai-secrets + env_vars: + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|\[Conformance\]|Host-Protection) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|Feature:SCTP) + - name: DATAPLANE + value: "CalicoBPF" + - name: INSTALLER + value: "operator" + - name: FUNCTIONAL_AREA + value: "bpf.yml" + - name: USE_PRIVATE_REGISTRY + value: "true" + - name: PRIVATE_REGISTRY + value: "quay.io/" + +blocks: + # these are from the old arm64 file + - name: ARM64 - smoke tests + dependencies: [] + task: + env_vars: + - name: INSTALLER + value: operator + - name: STERN_CHECK + value: DISABLED + jobs: + - name: Kubeadm - ARM64 + execution_time_limit: + hours: 4 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: PROVISIONER + value: gcp-kubeadm + - name: GOOGLE_MACHINE_TYPE + value: t2a-standard-2 + - name: K8S_VERSION + value: "stable-1" + - name: ENABLE_WIREGUARD + value: "true" + matrix: + - env_var: CLUSTER_IMAGE + values: ["ubuntu-2204-lts-arm64", "rocky-linux-9-arm64"] + + - name: AKS - ARM64 + execution_time_limit: + hours: 4 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: K8S_VERSION + values: ["stable-1"] + - env_var: AZ_NETWORK_PLUGIN + values: ["azure", "none"] + - env_var: CLUSTER_MODE + values: [""] + env_vars: + - name: PROVISIONER + value: azr-aks + - name: AZ_VM_SIZE + value: "Standard_D4plds_v5" + + # these are from the old bpf file + - name: BPF run matrix + dependencies: [] + task: + jobs: + - name: AWS encap + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: USE_VXLAN + values: ["true", "false"] + env_vars: + - name: PROVISIONER + value: "aws-kubeadm" + - name: IPAM_TEST_POOL_SUBNET + value: "10.0.0.0/29" + + - name: AWS single subnet + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: ENABLE_WIREGUARD + values: ["true", "false"] + env_vars: + - name: VPC_SUBNETS + value: '["172.16.101.0/24"]' + - name: IPAM_TEST_POOL_SUBNET + value: "10.0.0.0/29" + - name: PROVISIONER + value: "aws-kubeadm" + - name: ENCAPSULATION_TYPE + value: "None" + - name: BGP_STATUS + value: Enabled + - name: BPF_NETWORK_BOOTSTRAP + value: "Enabled" + - name: KUBE_PROXY_MANAGEMENT + value: "Enabled" + + - name: AWS-Talos, no encap, DSR + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: ENABLE_EXTERNAL_NODE + value: "false" # No code for external nodes on Talos yet + - name: VPC_SUBNETS + value: '["172.16.101.0/24"]' + - name: IPAM_TEST_POOL_SUBNET + value: "10.0.0.0/29" + - name: ENABLE_BPF_DSR + value: "true" + - name: PROVISIONER + value: "aws-talos" + - name: ENCAPSULATION_TYPE + value: "None" + - name: BGP_STATUS + value: Enabled + + - name: GCP BPF DP, DSR + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: PROVISIONER + value: "gcp-kubeadm" + - name: ENABLE_BPF_DSR + value: "true" + - name: ENABLE_IP_FORWARDING + value: "true" + - name: BPF_NETWORK_BOOTSTRAP + value: "Enabled" + - name: KUBE_PROXY_MANAGEMENT + value: "Enabled" + + - name: GCP BPF DP encap w/ NodeLocal DNSCache + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: USE_VXLAN + values: ["true", "false"] + env_vars: + - name: PROVISIONER + value: "gcp-kubeadm" + - name: CLUSTER_IMAGE + value: "ubuntu-2204-lts" + - name: ENABLE_NODE_LOCAL_DNS_CACHE + value: "true" + + - name: AKS w/ AKS-CNI + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: PROVISIONER + values: + - "azr-aks" + env_vars: + - name: INSTALLER + value: "operator" + - name: ENABLE_EXTERNAL_NODE # No code in banzai-core yet for external nodes on Azure + value: "false" + # External node is false, so we should skip external node tests: "outside.the.cluster|external.to.nodeport" + # This uses AKS-CNI, so Strict Affinity test does not apply: "StrictAffinity" + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|Conformance|Dataplane:BPF|Feature:NetworkPolicy) --ginkgo.skip=(Slow|Disruptive|calient|allow.ingress.access.from.updated.pod|Dataplane:LB|outside.the.cluster|external.to.nodeport|StrictAffinity|Feature:SCTP) + + - name: AKS w/ Calico-CNI + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: PROVISIONER + value: "azr-aks" + - name: INSTALLER + value: "operator" + - name: CNI_PLUGIN + value: "Calico" + - name: AZ_NETWORK_PLUGIN + value: "none" + - name: AZ_CREATE_MODE + value: AKS + - name: ENABLE_EXTERNAL_NODE # No code in banzai-core yet for external nodes on Azure + value: "false" + # External node is false, so we should skip external node tests: "outside.the.cluster|external.to.nodeport" + # This uses AKS-CNI, so Strict Affinity test does not apply: "StrictAffinity" + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|Conformance|Dataplane:BPF|Feature:NetworkPolicy) --ginkgo.skip=(Slow|Disruptive|calient|allow.ingress.access.from.updated.pod|Dataplane:LB|outside.the.cluster|external.to.nodeport|StrictAffinity|Feature:SCTP) + + - name: BPF + AKS w AKS CNI + Overlay + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: ENABLE_EXTERNAL_NODE # No code in banzai-core yet for external nodes on Azure + value: "false" + - name: NETWORK_PLUGIN_MODE + value: "overlay" + - name: AZ_NETWORK_PLUGIN + value: "azure" + - name: IPV4_POD_CIDR + value: "10.244.0.0/16" + - name: INSTALLER + value: "operator" + - name: PROVISIONER + value: "azr-aks" + - name: AZ_CREATE_MODE + value: AKS + # External node is false, so we should skip external node tests: "ExternalNode|outside.the.cluster|external.to.nodeport" + # This uses AKS-CNI, so Strict Affinity and MTU tests do not apply: "MTU|StrictAffinity" + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|\[Conformance\]|Feature:NetworkPolicy) --ginkgo.skip=(Slow|Disruptive|calient|allow.ingress.access.from.updated.pod|Dataplane:LB|outside.the.cluster|MTU|ExternalNode|external.to.nodeport|StrictAffinity|Feature:SCTP) + + - name: aws-kops RHEL8.2 + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: PROVISIONER + value: "aws-kops" + - name: K8S_VERSION + value: "stable-3" + - name: CLUSTER_IMAGE + value: "ami-02f147dfb8be58a10" # This is the Hourly image. For Access image, use "ami-03cda30b7a23bda26" + - name: USE_VENDORED_CNI + value: "false" + # The skip "EndpointSlice\|Services\sshould" is needed to skip Endpoint slice tests which are only possible in k8s 1.21+ + # The skip "Ingress.API|IngressClass.API" is needed to skip Ingress tests which are only possible in k8s 1.21+ + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|Conformance|Dataplane:BPF|Feature:NetworkPolicy) --ginkgo.skip=(Slow|Disruptive|calient|allow.ingress.access.from.updated.pod|EndpointSlice|Services\sshould|Ingress.API|IngressClass.API|Feature:SCTP) + + - name: aws-lb-bpf + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: ENCAPSULATION_TYPE + values: ["IPIP", "VXLAN"] + env_vars: + - name: PROVISIONER + value: "aws-eks" + - name: EKS_CNI + value: "calico" + - name: IPV4_POD_CIDR + value: "172.16.0.0/16" + - name: IPAM_TEST_POOL_SUBNET + value: "10.0.0.0/29" + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(Dataplane:LB) + - name: BGP_STATUS + value: Enabled + # Skipping these upstream k8s tests since they are failing because of limitations of Calico CNI on EKS: + # - [IT] "Proxy version v1 A set of valid responses are returned for both pod and service Proxy" + # - [IT] "Proxy version v1 should proxy through a service and a pod" + # - [IT] "ReplicaSet should serve a basic image on each replica with a public image" + # - [IT] "ReplicationController should serve a basic image on each replica with a public image" + # - [It] "DNS should provide DNS for ExternalName services" + # - [It] "DNS should provide DNS for pods for Hostname" + # - [It] "DNS should provide DNS for pods for Subdomain" + # - [It] "DNS should provide DNS for services" + # - [It] "DNS should provide DNS for the cluster" + # - [It] "DNS should resolve DNS of partial qualified names for services" + # See details: https://docs.tigera.io/calico/latest/getting-started/kubernetes/managed-public-cloud/eks#install-eks-with-calico-networking:~:text=Calico%20networking%20cannot,on%20this%20setting. + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[calico\]|\[Conformance\]|Host-Protection) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|Feature:SCTP|both.pod.and.service.Proxy\b|proxy.through.a.service.and.a.pod|replica.with.a.public.image|DNS.for.ExternalName.services|DNS.for.pods.for.Hostname|DNS.for.pods.for.Subdomain|DNS.for.services|DNS.for.the.cluster|DNS.qualified.names.for.services) + + - name: aws-lb-iptables + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: ENCAPSULATION_TYPE + values: ["IPIP", "VXLAN"] + env_vars: + - name: PROVISIONER + value: "aws-eks" + - name: EKS_CNI + value: "calico" + - name: IPV4_POD_CIDR + value: "172.16.0.0/16" + - name: IPAM_TEST_POOL_SUBNET + value: "10.0.0.0/29" + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(Dataplane:LB) + - name: DATAPLANE + value: "CalicoIptables" + - name: BGP_STATUS + value: Enabled + # Skipping these upstream k8s tests since they are failing because of limitations of Calico CNI on EKS: + # - [IT] "Proxy version v1 A set of valid responses are returned for both pod and service Proxy" + # - [IT] "Proxy version v1 should proxy through a service and a pod" + # - [IT] "ReplicaSet should serve a basic image on each replica with a public image" + # - [IT] "ReplicationController should serve a basic image on each replica with a public image" + # - [It] "DNS should provide DNS for ExternalName services" + # - [It] "DNS should provide DNS for pods for Hostname" + # - [It] "DNS should provide DNS for pods for Subdomain" + # - [It] "DNS should provide DNS for services" + # - [It] "DNS should provide DNS for the cluster" + # - [It] "DNS should resolve DNS of partial qualified names for services" + # See details: https://docs.tigera.io/calico/latest/getting-started/kubernetes/managed-public-cloud/eks#install-eks-with-calico-networking:~:text=Calico%20networking%20cannot,on%20this%20setting. + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[calico\]|\[Conformance\]|Host-Protection) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|Feature:SCTP|both.pod.and.service.Proxy\b|proxy.through.a.service.and.a.pod|replica.with.a.public.image|DNS.for.ExternalName.services|DNS.for.pods.for.Hostname|DNS.for.pods.for.Subdomain|DNS.for.services|DNS.for.the.cluster|DNS.qualified.names.for.services) + + - name: EKS w/ aws CNI + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: EKS_CNI + values: ["aws"] + env_vars: + - name: PROVISIONER + value: "aws-eks" + - name: INSTALLER + value: "operator" + - name: AWS_K8S_CNI_VERSION + value: "NA" + - name: NUM_ADDON_PODS + value: "45" # there are 3 nodes, each with 3 ENIs and each ENI can have 12 IPs. + + - name: EKS w/ calico CNI + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: EKS_CNI + values: ["calico"] + env_vars: + - name: ENCAPSULATION_TYPE + value: "VXLAN" + - name: PROVISIONER + value: "aws-eks" + - name: INSTALLER + value: "operator" + - name: IPV4_POD_CIDR + value: "172.16.0.0/16" + - name: IPAM_TEST_POOL_SUBNET + value: "10.0.0.0/29" + # See https://tigera.atlassian.net/browse/CORE-6725 for the extra skips. + # Skipping these upstream k8s tests since they are failing because of limitations of Calico CNI on EKS: + # - [IT] "Proxy version v1 A set of valid responses are returned for both pod and service Proxy" + # - [IT] "Proxy version v1 should proxy through a service and a pod" + # - [IT] "ReplicaSet should serve a basic image on each replica with a public image" + # - [IT] "ReplicationController should serve a basic image on each replica with a public image" + # - [It] "DNS should provide DNS for ExternalName services" + # - [It] "DNS should provide DNS for pods for Hostname" + # - [It] "DNS should provide DNS for pods for Subdomain" + # - [It] "DNS should provide DNS for services" + # - [It] "DNS should provide DNS for the cluster" + # - [It] "DNS should resolve DNS of partial qualified names for services" + # See details: https://docs.tigera.io/calico/latest/getting-started/kubernetes/managed-public-cloud/eks#install-eks-with-calico-networking:~:text=Calico%20networking%20cannot,on%20this%20setting. + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|\[Conformance\]|\[Dataplane:BPF\]) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|v1.should.proxy.through.a.service|DNS.should.resolve|DNS.should.provide|IPAM.StrictAffinity|Feature:SCTP|both.pod.and.service.Proxy\b|proxy.through.a.service.and.a.pod|replica.with.a.public.image|DNS.for.ExternalName.services|DNS.for.pods.for.Hostname|DNS.for.pods.for.Subdomain|DNS.for.services|DNS.for.the.cluster|DNS.qualified.names.for.services) + + - name: bpf-eks-aws-cni-lb + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: EKS_CNI + values: ["aws"] + env_vars: + - name: PROVISIONER + value: "aws-eks" + - name: INSTALLER + value: "operator" + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(Dataplane:LB) + - name: NUM_ADDON_PODS + value: "45" # there are 3 nodes, each with 3 ENIs and each ENI can have 12 IPs. + + - name: aws-kubeadm dual stack + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: IP_FAMILY + values: + - dual + - env_var: DATAPLANE + values: + - CalicoBPF + - CalicoIptables + env_vars: + - name: PROVISIONER + value: "aws-kubeadm" + - name: INSTALLER + value: "operator" + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[Conformance\]|\[Dataplane:BPF\]) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|v1.should.proxy.through.a.service|DNS.should.resolve|DNS.should.provide|IPAM.StrictAffinity|Feature:SCTP|DataPath|HostPorts|WireGuard|HostPort|EndpointSlices.pointing.to.API.Server|sig-calico-enterprise) + - name: K8S_E2E_DOCKER_EXTRA_FLAGS + value: --env IP_FAMILY + - name: NAT_OUTGOING_V6 + value: "Enabled" + - name: NAT_OUTGOING + value: "Enabled" + - name: ENCAPSULATION_TYPE_V6 + value: "VXLAN" + - name: ENCAPSULATION_TYPE + value: "VXLAN" + - name: IPV6_POD_CIDR + value: "fd00:10:244::/64" + - name: IPV4_POD_CIDR + value: "192.168.0.0/16" + + - name: Kops w/ calico CNI IPv6 + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: DATAPLANE + values: + - CalicoBPF + - CalicoIptables + - env_var: IP_FAMILY + values: + - ipv6 + env_vars: + - name: PROVISIONER + value: "aws-kops" + - name: INSTALLER + value: "operator" + # See https://tigera.atlassian.net/browse/CORE-6725 for the extra skips. + # Hopefully we'll fix the DataPath ones soon. + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|\[Conformance\]|\[Dataplane:BPF\]) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|v1.should.proxy.through.a.service|DNS.should.resolve|DNS.should.provide|IPAM.StrictAffinity|Feature:SCTP|DataPath|HostPorts|WireGuard|HostPort|EndpointSlices.pointing.to.API.Server) + - name: K8S_VERSION + value: "stable-1" + - name: NAT_OUTGOING_V6 + value: "Enabled" + - name: ENCAPSULATION_TYPE_V6 + value: "VXLAN" + - name: BGP_STATUS + value: "Disabled" + - name: IPV6_POD_CIDR + value: "fd00:10:244::/64" + - name: IPV4_POD_CIDR + value: "" + - name: CNI_PLUGIN + value: "Calico" + - name: USE_VENDORED_CNI + value: "false" + + - name: Kops w/ calico CNI IPv4 + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: DATAPLANE + values: + - CalicoBPF + - CalicoIptables + - env_var: IP_FAMILY + values: [ipv4] + env_vars: + - name: PROVISIONER + value: "aws-kops" + - name: INSTALLER + value: "operator" + # See https://tigera.atlassian.net/browse/CORE-6725 for the extra skips. + # Hopefully we'll fix the DataPath ones soon. + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|\[Conformance\]|\[Dataplane:BPF\]) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|v1.should.proxy.through.a.service|DNS.should.resolve|DNS.should.provide|IPAM.StrictAffinity|Feature:SCTP|DataPath|HostPorts|WireGuard|HostPort|EndpointSlices.pointing.to.API.Server) + - name: NAT_OUTGOING + value: "Enabled" + - name: ENCAPSULATION_TYPE + value: "VXLAN" + - name: BGP_STATUS + value: "Disabled" + - name: CNI_PLUGIN + value: "Calico" + - name: USE_VENDORED_CNI + value: "false" + + env_vars: + - name: ENABLE_EXTERNAL_NODE + value: "true" + - name: K8S_E2E_DOCKER_EXTRA_FLAGS + value: --env EKS_CNI + - name: CLUSTER_NODES + value: "3" + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|Conformance|Dataplane:BPF|Feature:NetworkPolicy) --ginkgo.skip=(Slow|Disruptive|calient|allow.ingress.access.from.updated.pod|Dataplane:LB|Feature:SCTP) + + - name: BPF mini-scale runs + dependencies: [] + task: + jobs: + - name: gcp-bpf - mini-scale + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: PROVISIONER + value: "gcp-kubeadm" + - name: TEST_TYPE + value: "scale-test" + - name: NUM_INFRA_NODES + value: "3" + - name: SCALE_TEST_TTFP50_THRESHOLD + value: "0.006" + - name: SCALE_TEST_TTFP99_THRESHOLD + value: "0.6" + + - name: 9K MTU runs + dependencies: [] + task: + jobs: + - name: aws-bpf - 9k MTU + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: ENCAPSULATION_TYPE + values: + - "None" + env_vars: + - name: VPC_SUBNETS + value: '["172.16.101.0/24"]' # single subnet because no encapsulation is being used + - name: NETWORK_MTU + value: "9001" + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|Conformance|Dataplane:BPF|Feature:NetworkPolicy) --ginkgo.skip=(Slow|Disruptive|calient|allow.ingress.access.from.updated.pod|Dataplane:LB|Feature:SCTP) + + - name: aws-iptables - 9k MTU + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: ENCAPSULATION_TYPE + values: + - "None" + env_vars: + - name: VPC_SUBNETS + value: '["172.16.101.0/24"]' # single subnet because no encapsulation is being used + - name: NETWORK_MTU + value: "9001" + - name: DATAPLANE + value: "CalicoIptables" + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|\[Conformance\]) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|\[DataPath\]|WireGuard|Feature:SCTP) + + env_vars: + - name: CLUSTER_NODES + value: "3" + # Single VPC only + - name: VPC_SUBNETS + value: '["172.16.101.0/24"]' + - name: IPAM_TEST_POOL_SUBNET + value: "10.0.0.0/29" + - name: PROVISIONER + value: aws-kubeadm + - name: K8S_E2E_DOCKER_EXTRA_FLAGS + value: --env NETWORK_MTU + - name: ENABLE_EXTERNAL_NODE + value: "true" + + - name: BPF mode switch + dependencies: [] + task: + jobs: + - name: gcp-bpf - mode switch + execution_time_limit: + hours: 1 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: PROVISIONER + value: "gcp-kubeadm" + - name: ENABLE_EXTERNAL_NODE + value: "true" + - name: INSTALLER + value: "operator" + - name: DATAPLANE + value: "CalicoIptables" + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(Dataplane:BPF.*ModeSwitch.*external-node-to-nodePort) + + - name: Wireguard + dependencies: [] + task: + jobs: + - name: BPF + AKS w AKS CNI + Overlay + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: ENABLE_EXTERNAL_NODE # No code in banzai-core yet for external nodes on Azure + value: "false" + - name: NETWORK_PLUGIN_MODE + value: "overlay" + - name: AZ_NETWORK_PLUGIN + value: "azure" + - name: IPV4_POD_CIDR + value: "10.244.0.0/16" + - name: INSTALLER + value: "operator" + - name: PROVISIONER + value: "azr-aks" + - name: AZ_CREATE_MODE + value: AKS + # External node is false, so we should skip external node tests: "ExternalNode|outside.the.cluster|external.to.nodeport" + # This uses AKS-CNI, so Strict Affinity and MTU tests do not apply: "MTU|StrictAffinity" + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|\[Conformance\]|Feature:NetworkPolicy) --ginkgo.skip=(Slow|Disruptive|calient|allow.ingress.access.from.updated.pod|Dataplane:LB|outside.the.cluster|MTU|ExternalNode|external.to.nodeport|StrictAffinity|Feature:SCTP) + + # this is from the old every-week file + - name: usage reporting with bpf + dependencies: [] + task: + jobs: + - name: mainline + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: K8S_VERSION + values: ["stable"] + env_vars: + - name: PROVISIONER + value: aws-kubeadm + - name: CLUSTER_NODES + value: "1" + - name: ENABLE_EXTERNAL_NODE + value: "true" + - name: USE_HASH_RELEASE + value: "false" + - name: ENABLE_USAGE + value: "true" + - name: TEST_TYPE + value: usage-reporting + - name: USAGE_REPORTING_COUNTRY + value: "US" + secrets: + - name: dynamo-read-creds + +promotions: + - name: Cleanup jobs + pipeline_file: cleanup.yml + auto_promote: + when: "result = 'stopped'" + +after_pipeline: + task: + jobs: + - name: Reports + commands: + - test-results gen-pipeline-report --force diff --git a/.semaphore/end-to-end/pipelines/certification.yml b/.semaphore/end-to-end/pipelines/certification.yml new file mode 100644 index 00000000000..4a0d15afb5e --- /dev/null +++ b/.semaphore/end-to-end/pipelines/certification.yml @@ -0,0 +1,143 @@ +version: v1.0 + +name: banzai-calico Openshift Certification + +# This file is used to perform testing for Openshift Certification. +# New releases are run against this pipeline and it generates artifacts that are sent to Redhat to update certification. + +agent: + machine: + type: c1-standard-1 + os_image: ubuntu2204 + +execution_time_limit: + hours: 12 + +global_job_config: + prologue: + commands_file: ../scripts/global_prologue.sh + epilogue: + always: + commands: + - ~/calico/.semaphore/end-to-end/scripts/global_epilogue.sh + secrets: + - name: marvin-github-ssh-private-key + - name: banzai-secrets + - name: redhat-certification + env_vars: + - name: FUNCTIONAL_AREA + value: "certification.yml" + - name: TEST_TYPE + value: "ocp-cert" + - name: STERN_CHECK + value: "DISABLED" + - name: PROVISIONER + value: aws-openshift + - name: INSTALLER + value: operator + - name: DATAPLANE + value: "CalicoIptables" + +blocks: + - name: Standalone cluster tests + dependencies: [] + task: + agent: + machine: + type: f1-standard-2 + os_image: ubuntu2204 + jobs: + - name: Standalone cluster tests (cert) + execution_time_limit: + hours: 6 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: OPENSHIFT_VERSION + values: ["4.20.1", "4.19.17", "4.18.27"] + env_vars: + - name: OPENSHIFT_CLUSTER_TYPE + value: "Standalone" + - name: TEST_TYPE + value: "ocp-cert" + - name: ENABLE_OCP_VIRT + value: "true" + + - name: HCP setup hosting + dependencies: [] + task: + jobs: + # only define one provisioner per job. if you wish to create multiple hosting clusters, + # use separate jobs per hosting cluster and update the `HOSTING_CLUSTER` value in hosted block + # do not forget to add the `HOSTING_CLUSTER` value to the "teardown hosting" block + - name: HCP setup hosting + execution_time_limit: + hours: 3 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: OPENSHIFT_VERSION + value: "4.20.1" + - name: OPENSHIFT_CLUSTER_TYPE + value: "HCP-hosting" + - name: HOSTING_CLUSTER + value: "hcp-shared-hosting" + - name: HCP_STAGE + value: "setup-hosting" + + - name: HCP hosted setup and tests + dependencies: ["HCP setup hosting"] + task: + agent: + machine: + type: f1-standard-2 + os_image: ubuntu2204 + jobs: + - name: HCP tests - hosted (cert) + execution_time_limit: + hours: 6 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: OPENSHIFT_VERSION + values: ["4.20.1", "4.19.17", "4.18.27"] + env_vars: + - name: HCP_STAGE + value: "hosted" + - name: OPENSHIFT_CLUSTER_TYPE + value: "HCP-hosted" + - name: HOSTING_CLUSTER + value: "hcp-shared-hosting" + - name: TEST_TYPE + value: "ocp-cert" + +promotions: + - name: Cleanup jobs + pipeline_file: cleanup.yml + auto_promote: + when: "result = 'stopped'" + +after_pipeline: + task: + secrets: + - name: banzai-secrets + jobs: + - name: Reports + commands: + - test-results gen-pipeline-report --force + - name: Destroy Hosting Clusters + execution_time_limit: + hours: 3 + commands: + - checkout + - source ~/calico/.semaphore/end-to-end/scripts/global_prologue.sh + - unset CLUSTER_NAME + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + - ~/calico/.semaphore/end-to-end/scripts/global_epilogue.sh + env_vars: + - name: HCP_STAGE + value: "destroy-hosting" + matrix: + - env_var: HOSTING_CLUSTER + values: + - "hcp-shared-hosting" diff --git a/.semaphore/end-to-end/pipelines/iptables.yml b/.semaphore/end-to-end/pipelines/iptables.yml new file mode 100644 index 00000000000..3d33ec997ea --- /dev/null +++ b/.semaphore/end-to-end/pipelines/iptables.yml @@ -0,0 +1,780 @@ +version: v1.0 + +name: IPTables + +agent: + machine: + type: c1-standard-1 + os_image: ubuntu2204 + +execution_time_limit: + hours: 12 + +global_job_config: + prologue: + commands_file: ../scripts/global_prologue.sh + epilogue: + always: + commands: + - ~/calico/.semaphore/end-to-end/scripts/global_epilogue.sh + secrets: + - name: marvin-github-ssh-private-key + - name: banzai-secrets + env_vars: + # This should match the default flags with the additional skip of the "Proxy version v1". There is a problem with the server pod failing to bind to the port for listening. + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|\[Conformance\]) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|\[DataPath\]|Proxy.version.v1.should.proxy.through.a.service.and.a.pod|WireGuard) + - name: DATAPLANE + value: "CalicoIptables" + - name: IPV4_POD_CIDR + value: "192.168.0.0/16" + - name: FUNCTIONAL_AREA + value: "iptables.yml" + - name: USE_PRIVATE_REGISTRY + value: "true" + - name: PRIVATE_REGISTRY + value: "quay.io/" + +blocks: + # this is from the old openshift file + - name: Openshift OCP + dependencies: [] + task: + jobs: + - name: hashrelease + execution_time_limit: + hours: 6 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: OPENSHIFT_VERSION + values: ["4.18.29", "4.19.20", "4.20.6"] + env_vars: + - name: PROVISIONER + value: aws-openshift + - name: INSTALLER + value: operator + + # this is from the old arm64 file + - name: ARM64 - smoke tests + dependencies: [] + task: + env_vars: + - name: INSTALLER + value: operator + - name: STERN_CHECK + value: DISABLED + jobs: + - name: AKS - ARM64 + execution_time_limit: + hours: 4 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: K8S_VERSION + values: ["stable-1"] + - env_var: AZ_NETWORK_PLUGIN + values: ["azure", "none"] + - env_var: CLUSTER_MODE + values: [""] + + env_vars: + - name: PROVISIONER + value: azr-aks + - name: AZ_VM_SIZE + value: "Standard_D4plds_v5" + + - name: EKS - ARM64 - Calico CNI + execution_time_limit: + hours: 4 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: K8S_VERSION + values: ["stable-1"] + env_vars: + - name: PROVISIONER + value: aws-eks + - name: EKS_CNI + value: "calico" + - name: IPV4_POD_CIDR + value: "172.16.0.0/16" + - name: AWS_NODE_INSTANCE_TYPE + value: "t4g.large" + # Details for why we skip these: https://docs.tigera.io/calico/latest/getting-started/kubernetes/managed-public-cloud/eks#install-eks-with-calico-networking:~:text=Calico%20networking%20cannot,on%20this%20setting. + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[calico\]|\[Conformance\]) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|\[DataPath\]|both.pod.and.service.Proxy\b|proxy.through.a.service.and.a.pod|replica.with.a.public.image|DNS.for.ExternalName.services|DNS.for.pods.for.Hostname|DNS.for.pods.for.Subdomain|DNS.for.services|DNS.for.the.cluster|DNS.qualified.names.for.services) + + - name: AKS + dependencies: [] + task: + jobs: + - name: AKS w Calico CNI + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: CNI_PLUGIN + values: + - "Calico" + env_vars: + - name: ENCAPSULATION_TYPE + value: "VXLAN" + # bz will ignore this env unless CNI_PLUGIN=Calico. + - name: CNI_POD_SETUP_TIMEOUT_SECONDS + value: "15" + + - name: AKS w AKS CNI + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: AZ_NETWORK_PLUGIN + value: "azure" + - name: ENCAPSULATION_TYPE + value: "None" + - name: IPV4_POD_CIDR + value: "10.244.0.0/16" + - name: INSTALLER # This is a "quick" workaround for a semver error we're getting with Helm, likely a banzai issue. + value: "operator" + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|\[Conformance\]) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|IPAM.StrictAffinity) + - name: AKS Private w AKS CNI + Overlay + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: NETWORK_PLUGIN_MODE + value: "overlay" + - name: AZ_NETWORK_PLUGIN + value: "azure" + - name: IPV4_POD_CIDR + value: "10.244.0.0/16" + - name: INSTALLER # This is a "quick" workaround for a semver error we're getting with Helm, likely a banzai issue. + value: "operator" + # External node is not supported on Azure yet, so we should skip external node tests: "ExternalNode|outside.the.cluster|external.to.nodeport" + # This uses AKS-CNI, so Strict Affinity and MTU tests do not apply: "MTU|StrictAffinity" + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|\[Conformance\]|Feature:NetworkPolicy) --ginkgo.skip=(Slow|Disruptive|calient|allow.ingress.access.from.updated.pod|Dataplane:LB|outside.the.cluster|MTU|ExternalNode|external.to.nodeport|StrictAffinity|Feature:SCTP) + - name: CLUSTER_MODE + value: "private" + + - name: AKS migrate from vendored Calico CNI to Calico CNI + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: INSTALLER + value: operator + - name: USE_VENDORED_CNI # This sets up the cluster with AKS vendored CNI + value: "true" + - name: CNI_PLUGIN + value: Calico # This (together with USE_VENDORED_CNI) sets up the cluster with AKS vendored Calico + - name: DESIRED_POLICY + value: Calico # This converts the cluster to unmanaged-Calico policy + + env_vars: + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|\[Conformance\]) --ginkgo.skip=(\[Slow\]|\[Disruptive\]) + - name: PROVISIONER + value: "azr-aks" + - name: INSTALLER + value: "helmerator" + - name: K8S_VERSION + value: "stable-1" + - name: AZ_CREATE_MODE + value: AKS + + - name: dual-stack k8s-e2e + dependencies: [] + task: + agent: + machine: + type: f1-standard-2 + os_image: ubuntu2204 + jobs: + - name: kdd + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: MANIFEST_FILE + values: ["calico.yaml", "calico-typha.yaml"] + - name: etcd + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: INSTALL_ETCD_POD + value: "true" + - name: MANIFEST_FILE + value: calico-etcd.yaml + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|\[Conformance\]) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|\[DataPath\]|WireGuard|EndpointSlice) + env_vars: + - name: K8S_VERSION + value: "stable-1" # Normally this would be stable, but kind doesn't always support stable immediately + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|\[Conformance\]) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|\[DataPath\]|WireGuard) + - name: PROVISIONER + value: local-kind + - name: ENABLE_DUAL_STACK + value: "true" + - name: K8S_E2E_EXTRA_FLAGS + value: --feature-gates=dualstack=true + - name: KIND_CONFIG + value: | + kind: Cluster + apiVersion: kind.x-k8s.io/v1alpha4 + networking: + disableDefaultCNI: true + ipFamily: dual + podSubnet: "192.168.0.0/16,fd00:10:244::/64" + nodes: + # the control plane node + - role: control-plane + - role: worker + - role: worker + - role: worker + + - name: AutoHEP test + dependencies: [] + task: + agent: + machine: + type: f1-standard-2 + os_image: ubuntu2204 + jobs: + - name: Test with AutoHEP enabled + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: ENABLE_AUTOHEP + value: "true" + - name: PROVISIONER + value: "local-kind" + + - name: IPVS + dependencies: [] + task: + jobs: + - name: ipvs + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: K8S_VERSION + values: ["stable"] + env_vars: + - name: PROVISIONER + value: gcp-kubeadm + - name: KUBE_PROXY_MODE + value: ipvs + + - name: usage reporting + dependencies: [] + task: + agent: + machine: + type: f1-standard-2 + os_image: ubuntu2204 + jobs: + - name: mainline + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: K8S_VERSION + values: ["stable"] + env_vars: + - name: PROVISIONER + value: local-kind + - name: MANIFEST_FILE + value: calico.yaml + - name: ENABLE_USAGE + value: "true" + - name: TEST_TYPE + value: usage-reporting + secrets: + - name: dynamo-read-creds + + # This is the old staging runs + - name: k8s beta runs + dependencies: [] + task: + jobs: + - name: k8s beta + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: K8S_VERSION + values: ["beta"] + - env_var: MANIFEST_FILE + values: ["calico.yaml"] + - env_var: KUBE_PROXY_MODE + values: ["iptables"] + env_vars: + - name: USE_HASH_RELEASE + value: "true" + - name: PROVISIONER + value: "gcp-kubeadm" + + - name: k8s distros + dependencies: [] + task: + jobs: + - name: install required + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: PROVISIONER + values: ["gcp-rancher"] + env_vars: + - name: RKE_VERSION + value: "v1.7.1" # https://github.com/rancher/rke/releases/tag/v1.7.1 + - name: K8S_VERSION + value: "v1.31.3" # RKE 1.7.1. supports: v1.31.3, v1.30.7, v1.29.11, v1.28.15 + + - name: WireGuard VXLAN k8s-e2e + dependencies: [] + task: + jobs: + - name: wireguard-vxlan-aws-kubeadm + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: K8S_VERSION + values: ["stable"] + env_vars: + - name: ENABLE_EXTERNAL_NODE + value: "true" + - name: USE_HASH_RELEASE + value: "true" + - name: ENABLE_WIREGUARD + value: "true" + - name: USE_VXLAN + value: "true" + - name: PROVISIONER + value: "aws-kubeadm" + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(WireGuard) + # Single VPC only + - name: VPC_SUBNETS + value: '["172.16.101.0/24"]' + - name: IPAM_TEST_POOL_SUBNET + value: "10.0.0.0/29" + + - name: WireGuard k8s-e2e + dependencies: [] + task: + jobs: + - name: aws-kubeadm-calico-cni with/without IP-in-IP + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: K8S_VERSION + values: ["stable"] + - env_var: DISABLE_IPIP + values: ["true", "false"] + env_vars: + - name: ENABLE_EXTERNAL_NODE + value: "true" + - name: USE_HASH_RELEASE + value: "true" + - name: ENABLE_WIREGUARD + value: "true" + - name: PROVISIONER + value: "aws-kubeadm" + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(WireGuard) + # Single VPC only + - name: VPC_SUBNETS + value: '["172.16.101.0/24"]' + - name: IPAM_TEST_POOL_SUBNET + value: "10.0.0.0/29" + + - name: Operator Migration + dependencies: [] + task: + agent: + machine: + type: f1-standard-2 + os_image: ubuntu2204 + jobs: + - name: kind dual-stack + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: PROVISIONER + value: local-kind + - name: PRODUCT + value: calico + - name: INSTALLER + value: "manual" + - name: USE_HASH_RELEASE + value: "true" + - name: OPERATOR_MIGRATE + value: "true" + - name: IP_FAMILY + value: dual + - name: IPV6_POD_CIDR + value: "fd00:10:244::/64" + matrix: + - env_var: K8S_VERSION + values: ["stable-1"] + + - name: kind IPv6 + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: PROVISIONER + value: local-kind + - name: PRODUCT + value: calico + - name: INSTALLER + value: "manual" + - name: USE_HASH_RELEASE + value: "true" + - name: OPERATOR_MIGRATE + value: "true" + - name: IP_FAMILY + value: ipv6 + - name: IPV4_POD_CIDR + value: "" + - name: IPV6_POD_CIDR + value: "fd00:10:244::/64" + matrix: + - env_var: K8S_VERSION + values: ["stable-1"] + + # This is the old wireguard tests + - name: WireGuard regression k8s-e2e + dependencies: [] + task: + jobs: + - name: kdd - new + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: K8S_VERSION + values: ["stable"] + env_vars: + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|\[Conformance\]) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|\[DataPath\]) + - name: DISABLE_IPIP + value: "true" + # Single VPC only + - name: VPC_SUBNETS + value: '["172.16.101.0/24"]' + - name: IPAM_TEST_POOL_SUBNET + value: "10.0.0.0/29" + - name: PROVISIONER + value: "aws-kubeadm" + + - name: kdd - old + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: K8S_VERSION + values: ["stable-3"] + env_vars: + # The skip "EndpointSlice\|Services\sshould" is needed to skip Endpoint slice tests which are only possible in k8s 1.21+ + # The skip "Ingress.API|IngressClass.API" is needed to skip Ingress tests which are only possible in k8s 1.21+ + # The skip "Proxy.version.v1.*pod.and.service.Proxy.\[" is needed to skip an upstream test that only works on k8s 1.24+ + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|\[Conformance\]) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|\[DataPath\]|EndpointSlice|Services\sshould|Ingress.API|IngressClass.API|Proxy.version.v1.*pod.and.service.Proxy.\[) + - name: DISABLE_IPIP + value: "true" + # Single VPC only + - name: VPC_SUBNETS + value: '["172.16.101.0/24"]' + - name: IPAM_TEST_POOL_SUBNET + value: "10.0.0.0/29" + - name: PROVISIONER + value: "aws-kubeadm" + + - name: AKS w AKS CNI + Overlay + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: ENABLE_EXTERNAL_NODE # No code in banzai-core yet for external nodes on Azure + value: "false" + - name: NETWORK_PLUGIN_MODE + value: "overlay" + - name: AZ_NETWORK_PLUGIN + value: "azure" + - name: IPV4_POD_CIDR + value: "10.244.0.0/16" + - name: INSTALLER + value: "operator" + - name: PROVISIONER + value: "azr-aks" + - name: AZ_CREATE_MODE + value: AKS + # External node is false, so we should skip external node tests: "ExternalNode|outside.the.cluster|external.to.nodeport" + # This uses AKS-CNI, so Strict Affinity and MTU tests do not apply: "MTU|StrictAffinity" + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|\[Conformance\]|Feature:NetworkPolicy) --ginkgo.skip=(Slow|Disruptive|calient|allow.ingress.access.from.updated.pod|Dataplane:LB|outside.the.cluster|MTU|ExternalNode|external.to.nodeport|StrictAffinity|Feature:SCTP) + + env_vars: + - name: USE_HASH_RELEASE + value: "true" + - name: ENABLE_WIREGUARD + value: "true" + + ### + # Focused tests + ### + - name: Focused WireGuard E2E (AKS w/ Azure CNI) + dependencies: [] + task: + jobs: + - name: kdd + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: PROVISIONER + values: ["azr-aks"] + - env_var: K8S_VERSION + values: ["stable-3", "stable"] + - env_var: CNI_PLUGIN + values: ["AzureVNET"] + env_vars: + - name: ENCAPSULATION_TYPE + value: "None" + - name: INSTALLER + value: "helmerator" + - name: USE_HASH_RELEASE + value: "true" + - name: ENABLE_WIREGUARD + value: "true" + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=WireGuard --ginkgo.skip=(\[Slow\]|\[Disruptive\]|\[DataPath\]) + + - name: Focused WireGuard E2E (AKS with Calico CNI) + dependencies: [] + task: + jobs: + - name: kdd + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: PROVISIONER + values: ["azr-aks"] + - env_var: K8S_VERSION + values: ["stable-3", "stable"] + - env_var: CNI_PLUGIN + values: ["Calico"] + env_vars: + - name: ENCAPSULATION_TYPE + value: "VXLAN" + - name: INSTALLER + value: "helmerator" + - name: USE_HASH_RELEASE + value: "true" + - name: ENABLE_WIREGUARD + value: "true" + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=WireGuard --ginkgo.skip=(\[Slow\]|\[Disruptive\]|\[DataPath\]) + - name: AKS_CNI + value: "calico" + + - name: Focused WireGuard E2E (EKS) + dependencies: [] + task: + jobs: + - name: kdd (AWS CNI) + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: CNI_PLUGIN + value: aws + + - name: kdd (Calico CNI) + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: CNI_PLUGIN + value: calico + # Skipping these upstream k8s tests since they are failing because of limitations of Calico CNI on EKS: + # - [IT] "Proxy version v1 A set of valid responses are returned for both pod and service Proxy" + # - [IT] "Proxy version v1 should proxy through a service and a pod" + # - [IT] "ReplicaSet should serve a basic image on each replica with a public image" + # - [IT] "ReplicationController should serve a basic image on each replica with a public image" + # - [IT] "ReplicationController should serve a basic image on each replica with a public image" + # - [It] "DNS should provide DNS for ExternalName services" + # - [It] "DNS should provide DNS for pods for Hostname" + # - [It] "DNS should provide DNS for pods for Subdomain" + # - [It] "DNS should provide DNS for services" + # - [It] "DNS should provide DNS for the cluster" + # See details: https://docs.tigera.io/calico/latest/getting-started/kubernetes/managed-public-cloud/eks#install-eks-with-calico-networking:~:text=Calico%20networking%20cannot,on%20this%20setting. + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=WireGuard --ginkgo.skip=(\[Slow\]|\[Disruptive\]|\[DataPath\]|both.pod.and.service.Proxy\b|proxy.through.a.service.and.a.pod|replica.with.a.public.image|DNS.for.ExternalName.services|DNS.for.pods.for.Hostname|DNS.for.pods.for.Subdomain|DNS.for.services|DNS.for.the.cluster|DNS.qualified.names.for.services) + env_vars: + - name: AWS_K8S_CNI_VERSION + value: "NA" + - name: PROVISIONER + value: "aws-eks" + - name: K8S_VERSION + value: "stable-1" + - name: INSTALLER + value: "helmerator" + - name: USE_HASH_RELEASE + value: "true" + - name: ENABLE_WIREGUARD + value: "true" + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=WireGuard --ginkgo.skip=(\[Slow\]|\[Disruptive\]|\[DataPath\]) + + # This is the old every-other-week tests + - name: Distro support + dependencies: [] + task: + jobs: + - name: kdd + execution_time_limit: + hours: 5 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: CLUSTER_IMAGE + values: + [ + "ubuntu-2404-lts", + "ubuntu-2204-lts", + "rhel-10", + "centos-stream-8", + "rhel-9", + "rocky-linux-8", + ] + - env_var: PROVISIONER + values: ["gcp-kubeadm"] + + # This is the old every-other-day tests + - name: datastore k8s-e2e + dependencies: [] + task: + jobs: + - name: kdd - old + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: K8S_VERSION + values: ["stable-3"] + - env_var: MANIFEST_FILE + values: ["calico.yaml", "calico-typha.yaml", "canal.yaml"] + - name: kdd - new + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: K8S_VERSION + values: ["stable"] + - env_var: MANIFEST_FILE + values: ["calico.yaml", "calico-typha.yaml", "canal.yaml"] + env_vars: + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|\[Conformance\]) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|\[DataPath\]|WireGuard) + + - name: etcd + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: K8S_VERSION + values: ["stable-3"] + env_vars: + - name: INSTALL_ETCD_POD + value: "true" + - name: MANIFEST_FILE + value: calico-etcd.yaml + env_vars: + - name: PROVISIONER + value: gcp-kubeadm + + - name: "Migration" + dependencies: [] + task: + agent: + machine: + type: f1-standard-2 + os_image: ubuntu2204 + jobs: + - name: "Flannel migration" + execution_time_limit: + hours: 7 + commands: + - ./scripts/body_flannel-migration.sh + matrix: + - env_var: DOWNLEVEL_MANIFEST + values: + - "https://raw.githubusercontent.com/projectcalico/calico/v3.28.2/manifests/canal.yaml" + - "https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml" + env_vars: + - name: K8S_VERSION + value: "stable-1" + - name: PROVISIONER + value: "local-kind" + - name: MIGRATION_MANIFEST + value: "manifests/flannel-migration/migration-job.yaml" + - name: CALICO_MANIFEST + value: "manifests/flannel-migration/calico.yaml" + - name: INSTALLER + value: "manual" + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|\[Conformance\]) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|\[DataPath\]|WireGuard) + - name: USE_HASH_RELEASE + value: "true" + +promotions: + - name: Cleanup jobs + pipeline_file: cleanup.yml + auto_promote: + when: "result = 'stopped'" + +after_pipeline: + task: + jobs: + - name: Reports + commands: + - test-results gen-pipeline-report --force diff --git a/.semaphore/end-to-end/pipelines/nftables.yml b/.semaphore/end-to-end/pipelines/nftables.yml new file mode 100644 index 00000000000..3e1e0757c48 --- /dev/null +++ b/.semaphore/end-to-end/pipelines/nftables.yml @@ -0,0 +1,188 @@ +version: v1.0 + +name: NFTables + +agent: + machine: + type: c1-standard-1 # Adequate for most e2e tests, since clusters run on separate machines + os_image: ubuntu2204 + +execution_time_limit: + hours: 12 + +global_job_config: + prologue: + commands_file: ../scripts/global_prologue.sh + epilogue: + always: + commands: + - ~/calico/.semaphore/end-to-end/scripts/global_epilogue.sh + secrets: + - name: marvin-github-ssh-private-key + - name: banzai-secrets + env_vars: + # The skips for sig-* are tests that are not relevant to OSS Calico + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|\[Conformance\]) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|sig-node|sig-apps|sig-storage|sig-scheduling|sig-calico-enterprise|\[DataPath\]|WireGuard) + - name: K8S_VERSION + value: "stable-3" + - name: FUNCTIONAL_AREA + value: "nftables.yml" + - name: DATAPLANE + value: CalicoNftables + - name: KUBE_PROXY_MODE + value: "nftables" + - name: INSTALLER + value: "operator" + - name: USE_PRIVATE_REGISTRY + value: "true" + - name: ENABLE_AUTOHEP + value: "true" + - name: PRIVATE_REGISTRY + value: "quay.io/" # Pull from quay to avoid Docker Hub rate limits + +blocks: + - name: Nftables mode, arm64, WG + dependencies: [] + task: + jobs: + - name: Nftables mode, arm64, WG + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: K8S_VERSION + values: ["stable"] + - env_var: CLUSTER_IMAGE + values: + - "ubuntu-2204-lts-arm64" + - "rocky-linux-9-arm64" + env_vars: + - name: GOOGLE_REGION + value: us-central1 # Needed for arm machines + - name: GOOGLE_ZONE + value: us-central1-a # Needed for arm machines + - name: PROVISIONER + value: gcp-kubeadm + - name: STERN_CHECK + value: DISABLED # Doesn't have an ARM build + - name: GOOGLE_MACHINE_TYPE + value: t2a-standard-2 + - name: ENABLE_WIREGUARD + value: "true" + + - name: Nftables, Talos + dependencies: [] + task: + jobs: + - name: Nftables, Talos + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: K8S_VERSION + values: ["stable"] + env_vars: + - name: PROVISIONER + value: aws-talos + - name: ENCAPSULATION_TYPE + value: "VXLAN" + + - name: Nftables mode, EKS + dependencies: [] + task: + jobs: + - name: EKS w/ aws CNI + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: CNI_PLUGIN + values: ["AmazonVPC"] + env_vars: + - name: PROVISIONER + value: "aws-eks" + - name: K8S_VERSION + value: "stable-1" + + - name: EKS w/ Calico CNI + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: CNI_PLUGIN + values: ["Calico"] + env_vars: + - name: INSTALLER + value: "helmerator" + - name: PROVISIONER + value: "aws-eks" + - name: K8S_VERSION + value: "stable-1" + - name: IPV4_POD_CIDR + value: "172.16.0.0/16" # Because nodes have 192.168.0.0/16 from VPC + - name: IPAM_TEST_POOL_SUBNET + value: "10.0.0.0/29" + # Details for why we skip these: https://docs.tigera.io/calico/latest/getting-started/kubernetes/managed-public-cloud/eks#install-eks-with-calico-networking:~:text=Calico%20networking%20cannot,on%20this%20setting. + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|\[Conformance\]) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|sig-node|sig-apps|sig-storage|sig-scheduling|sig-calico-enterprise|\[DataPath\]|WireGuard|both.pod.and.service.Proxy\b|proxy.through.a.service.and.a.pod|replica.with.a.public.image|DNS.for.ExternalName.services|DNS.for.pods.for.Hostname|DNS.for.pods.for.Subdomain|DNS.for.services|DNS.for.the.cluster|DNS.qualified.names.for.services) + + - name: Nftables mode, KinD + dependencies: [] + task: + agent: + machine: + type: f1-standard-2 # Because KinD clusters run on the test runner, they need more powerful machines + os_image: ubuntu2204 + env_vars: + - name: K8S_VERSION + value: "stable-3" + - name: PROVISIONER + value: local-kind + - name: ENABLE_DUAL_STACK + value: "true" + - name: IPV6_POD_CIDR + value: "fd00:10:244::/64" + jobs: + - name: Nftables mode, vxlan, dual-stack + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: IP_FAMILY + value: dual + - name: ENCAPSULATION_TYPE_V6 + value: "VXLAN" + - name: ENCAPSULATION_TYPE + value: "VXLAN" + - name: IPV4_POD_CIDR + value: "192.168.0.0/16" + + - name: Nftables mode, ipv6-only + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: IP_FAMILY + value: ipv6 + - name: ENCAPSULATION_TYPE_V6 + value: "None" + +promotions: + - name: Cleanup jobs + pipeline_file: cleanup.yml + auto_promote: + when: "result = 'stopped'" + +after_pipeline: + task: + jobs: + - name: Reports + commands: + - test-results gen-pipeline-report --force diff --git a/.semaphore/end-to-end/pipelines/patch-verification.yml b/.semaphore/end-to-end/pipelines/patch-verification.yml new file mode 100644 index 00000000000..dc136aedebc --- /dev/null +++ b/.semaphore/end-to-end/pipelines/patch-verification.yml @@ -0,0 +1,517 @@ +version: v1.0 + +name: banzai-calico patch verification + +agent: + machine: + type: c1-standard-1 + os_image: ubuntu2204 + +execution_time_limit: + hours: 12 + +global_job_config: + prologue: + commands_file: ../scripts/global_prologue.sh + epilogue: + always: + commands: + - ~/calico/.semaphore/end-to-end/scripts/global_epilogue.sh + secrets: + - name: marvin-github-ssh-private-key + - name: banzai-secrets + env_vars: + # The intent here is to run a representative set of tests that cover the main functionality of Calico + # Using tests that usually pass. + # So we're running the Conformance tests, some RBAC tests, and some Observe tests. + # and skipping the slow, disruptive, and some other tests that are not relevant to Calico OSS + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[Conformance\]|Observe|RBAC) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|sig-node|sig-apps|sig-storage|sig-scheduling|sig-calico-enterprise) + - name: K8S_VERSION + value: "stable-3" + - name: FUNCTIONAL_AREA + value: "patch-verification.yml" + - name: IPV4_POD_CIDR + value: "192.168.0.0/16" + - name: USE_PRIVATE_REGISTRY + value: "true" + - name: PRIVATE_REGISTRY + value: "quay.io/" + +promotions: + - name: Cleanup jobs + pipeline_file: cleanup.yml + auto_promote: + when: "result = 'stopped'" + +after_pipeline: + task: + jobs: + - name: Reports + commands: + - test-results gen-pipeline-report --force + +blocks: + # The intent of this file is to provide scattergun coverage across many dimensions, so that we can + # run this against proposed patch releases and have reasonable confidence that they aren't going to regress things. + # + # Dimensions considered here: + # install type: manifest/operator/helmerator + # CNIs: awscni/azurecni/flannel/calicocni + # interop with k8s versions: stable/stable-3 + # cpu arch: amd64/arm64 + # dataplane: iptables/nftables/bpf/windows + # ipfamily: ipv4/dual-stack/ipv6 + # platforms: EKS/AKS/OpenShift/kubeadm/RKE2 crossed with versions + # datastore: etcd/kdd + # windows versions: 1809/2022/other + # windows container runtime: containerd/docker + # features: alp/wireguard/autohep/private clusters/windows/bpf lb interop/bpf DSR/nodelocal dns + # encapsulation: vxlan/ipip/none + # node OS: ubuntu2204/ubuntu2404/amazonlinux2023/amazonlinux2/rhel10/RHCOS + # migrations: flannel/canal/manifest + # kubeproxy mode: iptables/ipvs/nftables/none + # benchmarks? + # upgrades? + + # So the runs we need: + + # manifest, flannel migrated to calico, stable, amd64, iptables, dualstack, aws-kubeadm, vxlan, kdd, ubuntu2204, iptables-kube-proxy + # manifest, canal, stable, amd64, iptables, ipv4, gcp-kubeadm, kdd, ubuntu2204, iptables-kube-proxy + # manifest, canal migrated to calico, stable, amd64, iptables, ipv4, gcp-kubeadm, kdd, ubuntu2204, iptables-kube-proxy + # manifest, calico, stable, amd64, iptables, ipv4, gcp-kubeadm, etcd, ubuntu2404, ipip encap, ipvs-kube-proxy + # manifest migrated to operator, calico, stable, amd64, iptables, ipv4, gcp-kubeadm, kdd, ubuntu2404, ipip encap, nftables-kube-proxy + + # operator, azurecni, stable-1, amd64, bpf, ipv4, AKS, kdd, wireguard, ubuntu?, no-kube-proxy, private-cluster, autohep + # helmerator, calicocni, stable-1, amd64, iptables, ipv6, kind, kdd, n/a, no-kube-proxy, autohep + # operator, calicocni, 4.18, amd64, bpf, ipv4, openshift, RHCOS, kdd, n/a, no-kube-proxy, autohep + # helmerator, calicocni, stable-3, arm64, bpf, ipv4, EKS, kdd, wireguard, amazonlinux2023, no-kube-proxy, nodelocaldns, [bpf lb interop, bpf DSR] + # operator, awscni, stable-1, amd64, bpf, ipv4, EKS, kdd, amazonlinux2, no-kube-proxy, + # helmerator, calicocni, stable, amd64, bpf, ipv4, aws-kubeadm, kdd, windows 2022, containerd, ubuntu2404, autohep, vxlan + # operator, calicocni, stable-3, amd64, iptables, ipv4, aws-kubeadm, kdd, windows 1809, docker, ubuntu2204, autohep, none encap, nftables-kube-proxy + # operator, calicocni, stable-1, amd64, bpf, ipv4, gcp-rke2, kdd, ubuntu2204, autohep, vxlan encap, nodelocaldns + + # The above runs attempt to cover all the dimensions we identified above, taking into the account the constraints on the combinations. + + - name: "KinD tests" + dependencies: [] + task: + agent: + machine: + type: f1-standard-2 + os_image: ubuntu2204 + jobs: + # helmerator, calicocni, stable-1, amd64, bpf, ipv6, kind, kdd, n/a, no-kube-proxy, autohep + - name: "IPv6" + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: CNI_PLUGIN + value: "Calico" + - name: K8S_VERSION + value: "stable-1" + - name: PROVISIONER + value: "local-kind" + - name: DATAPLANE + value: CalicoIptables + - name: IP_FAMILY + value: ipv6 + - name: K8S_E2E_DOCKER_EXTRA_FLAGS + value: --env IP_FAMILY + - name: IPV6_POD_CIDR + value: "fd00:10:244::/56" + - name: IPV4_POD_CIDR + value: "" + - name: INSTALLER + value: "helmerator" + - name: ENABLE_AUTO_HEP # autohep + value: "true" + + - name: "Manifests" + dependencies: [] + task: + agent: + machine: + type: c1-standard-1 + os_image: ubuntu2204 + jobs: + # manifest, flannel migrated to calico, stable-1, amd64, iptables, dualstack, local-kind, vxlan, kdd, iptables-kube-proxy + # BUT couldn't get Flannel to work with ipv6 in KinD - something about the way KinD sets up the cluster + - name: "Flannel migration" + execution_time_limit: + hours: 7 + commands: + - ./scripts/body_flannel-migration.sh + env_vars: + - name: DOWNLEVEL_MANIFEST + value: "https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml" + - name: K8S_VERSION + value: "stable" + - name: AWS_NODE_INSTANCE_TYPE + value: t3.large + - name: CLUSTER_IMAGE + value: "jammy-22.04" + - name: PROVISIONER + value: "aws-kubeadm" + - name: DATAPLANE + value: CalicoIptables + - name: IP_FAMILY + value: ipv4 + # value: dual + - name: K8S_E2E_DOCKER_EXTRA_FLAGS + value: --env IP_FAMILY # --feature-gates=dualstack=true + # - name: NAT_OUTGOING_V6 + # value: "Enabled" + - name: NAT_OUTGOING + value: "Enabled" + # - name: ENCAPSULATION_TYPE_V6 + # value: "VXLAN" + - name: ENCAPSULATION_TYPE + value: "VXLAN" + # - name: IPV6_POD_CIDR + # value: "fd00:10:244::/56" + - name: IPV4_POD_CIDR + value: "192.168.0.0/16" + - name: MIGRATION_MANIFEST + value: "manifests/flannel-migration/migration-job.yaml" + - name: CALICO_MANIFEST + value: "manifests/flannel-migration/calico.yaml" + - name: INSTALLER + value: "manual" + + # manifest, canal, stable, amd64, iptables, ipv4, gcp-kubeadm, kdd, ubuntu2204, iptables-kube-proxy + - name: "Canal" + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: MANIFEST_FILE + value: "canal.yaml" + - name: K8S_VERSION + value: "stable" + - name: GOOGLE_MACHINE_TYPE + value: "e2-standard-2" + - name: CLUSTER_IMAGE + value: "ubuntu-2204-lts" + - name: PROVISIONER + value: "gcp-kubeadm" + - name: DATAPLANE + value: CalicoIptables + - name: IP_FAMILY + value: ipv4 + - name: K8S_E2E_DOCKER_EXTRA_FLAGS + value: --env IP_FAMILY + - name: NAT_OUTGOING + value: "Enabled" + - name: INSTALLER + value: "manual" + + # manifest, canal migrated to calico, stable, amd64, iptables, ipv4, gcp-kubeadm, kdd, ubuntu2204, iptables-kube-proxy + - name: "Canal migration" + execution_time_limit: + hours: 7 + commands: + - ./scripts/body_flannel-migration.sh + env_vars: + - name: DOWNLEVEL_MANIFEST + value: "https://raw.githubusercontent.com/projectcalico/calico/v3.28.2/manifests/canal.yaml" + - name: K8S_VERSION + value: "stable" + - name: GOOGLE_MACHINE_TYPE + value: "e2-standard-2" + - name: CLUSTER_IMAGE + value: "ubuntu-2204-lts" + - name: PROVISIONER + value: "gcp-kubeadm" + - name: DATAPLANE + value: CalicoIptables + - name: IP_FAMILY + value: ipv4 + - name: K8S_E2E_DOCKER_EXTRA_FLAGS + value: --env IP_FAMILY + - name: MIGRATION_MANIFEST + value: "manifests/flannel-migration/migration-job.yaml" + - name: CALICO_MANIFEST + value: "manifests/flannel-migration/calico.yaml" + - name: INSTALLER + value: "manual" + # manifest, calico, stable, amd64, iptables, ipv4, gcp-kubeadm, etcd, ubuntu2404, ipip encap, ipvs-kube-proxy + - name: "Calico etcd" + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: MANIFEST_FILE + value: "calico-etcd.yaml" + - name: INSTALL_ETCD_POD + value: "true" + - name: K8S_VERSION + value: "stable" + - name: GOOGLE_MACHINE_TYPE + value: "e2-standard-2" + - name: CLUSTER_IMAGE + value: "ubuntu-2404-lts-amd64" + - name: PROVISIONER + value: "gcp-kubeadm" + - name: ENCAPSULATION_TYPE + value: "IPIP" + - name: DATAPLANE + value: CalicoIptables + - name: IP_FAMILY + value: ipv4 + - name: KUBE_PROXY_MODE + value: ipvs + - name: K8S_E2E_DOCKER_EXTRA_FLAGS + value: --env IP_FAMILY + - name: NAT_OUTGOING + value: "Enabled" + - name: INSTALLER + value: "manual" + + # manifest migrated to operator, calico, stable, amd64, iptables, ipv4, gcp-kubeadm, kdd, ubuntu2404, ipip encap, nftables-kube-proxy + - name: operator migration + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: MANIFEST_FILE + value: "calico.yaml" + - name: K8S_VERSION + value: "stable" + - name: GOOGLE_MACHINE_TYPE + value: "e2-standard-2" + - name: CLUSTER_IMAGE + value: "ubuntu-2404-lts-amd64" + - name: PROVISIONER + value: "gcp-kubeadm" + - name: ENCAPSULATION_TYPE + value: "IPIP" + - name: DATAPLANE + value: CalicoNftables + - name: IP_FAMILY + value: ipv4 + - name: KUBE_PROXY_MODE + value: nftables + - name: K8S_E2E_DOCKER_EXTRA_FLAGS + value: --env IP_FAMILY + - name: NAT_OUTGOING + value: "Enabled" + - name: INSTALLER + value: "manual" + - name: OPERATOR_MIGRATE + value: "true" + + - name: "Operator/Helmerator" + dependencies: [] + task: + agent: + machine: + type: c1-standard-1 + os_image: ubuntu2204 + jobs: + # operator, azurecni, stable-1, amd64, bpf, ipv4, AKS, kdd, wireguard, ubuntu?, no-kube-proxy, private-cluster, autohep + - name: AKS w AKS CNI + Overlay + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: NETWORK_PLUGIN_MODE + value: "overlay" + - name: CNI_PLUGIN + value: "AzureVNET" + - name: IPV4_POD_CIDR + value: "10.244.0.0/16" + - name: INSTALLER + value: "operator" + # - name: CLUSTER_MODE + # value: "private" # I couldn't get this to provision, so I've skipped this for now + - name: PROVISIONER + value: "azr-aks" + - name: K8S_VERSION + value: "stable-1" + - name: AZ_CREATE_MODE + value: AKS + - name: K8S_VERSION + value: "stable-1" + - name: IP_FAMILY + value: ipv4 + + # operator, calicocni, 4.18, amd64, bpf, ipv4, openshift, RHCOS, kdd, n/a, no-kube-proxy, autohep + - name: Openshift OCP + execution_time_limit: + hours: 6 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: OPENSHIFT_VERSION + value: "4.18.17" + - name: PROVISIONER + value: aws-openshift + - name: INSTALLER + value: operator + - name: DATAPLANE + value: "CalicoBPF" + - name: IP_FAMILY + value: ipv4 + + # helmerator, calicocni, stable-3, arm64, bpf, ipv4, EKS, kdd, wireguard, amazonlinux2023, no-kube-proxy, nodelocaldns, [bpf lb interop, bpf DSR] + - name: EKS w/ calico CNI + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: ENCAPSULATION_TYPE + value: "VXLAN" + - name: PROVISIONER + value: "aws-eks" + - name: EKS_AMI_FAMILY + value: AmazonLinux2023 + - name: INSTALLER + value: "helmerator" + - name: IP_FAMILY + value: "ipv4" + - name: IPV4_POD_CIDR + value: "172.16.0.0/16" + - name: IPAM_TEST_POOL_SUBNET + value: "10.0.0.0/29" + - name: K8S_VERSION + value: "stable-3" + - name: CNI_PLUGIN + value: "Calico" + - name: AWS_NODE_INSTANCE_TYPE + value: "t3.large" # amd64 instance type + # value: "t4g.large" # ARM64 instance type (hacked out for now to see if the operator problem applies to all archs) + - name: DATAPLANE + value: CalicoBPF + - name: ENABLE_WIREGUARD + value: "true" + - name: ENABLE_NODE_LOCAL_DNS_CACHE # nodelocaldns + value: "true" + # The extra skip here is for the upstream DNS tests. They require access to pods via the control plane, and with Calico CNI, the EKS control plane can't access pods. + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[Conformance\]|Observe|RBAC) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|sig-node|sig-apps|sig-storage|sig-scheduling|sig-calico-enterprise|DNS.should.........DNS) + + # operator, awscni, stable-1, amd64, bpf, ipv4, EKS, kdd, amazonlinux2, no-kube-proxy, + - name: EKS w/ aws CNI + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: PROVISIONER + value: "aws-eks" + - name: INSTALLER + value: "operator" + - name: IP_FAMILY + value: "ipv4" + - name: IPV4_POD_CIDR + value: "172.16.0.0/16" + - name: IPAM_TEST_POOL_SUBNET + value: "10.0.0.0/29" + - name: K8S_VERSION + value: "stable-1" + - name: CNI_PLUGIN + value: "AmazonVPC" + - name: AWS_K8S_CNI_VERSION + value: "NA" + - name: AWS_NODE_INSTANCE_TYPE + value: "t3.large" # amd64 instance type + - name: DATAPLANE + value: CalicoBPF + + # helmerator, calicocni, stable, amd64, bpf, ipv4, aws-kubeadm, kdd, windows 2022, containerd, ubuntu2404, autohep, vxlan + - name: windows - aws-kubeadm - containerd + execution_time_limit: + hours: 5 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: INSTALLER + value: "helmerator" + - name: PROVISIONER + value: aws-kubeadm + - name: K8S_VERSION + value: "stable" + - name: DATAPLANE + value: CalicoBPF + - name: CNI_PLUGIN + value: "Calico" + - name: IP_FAMILY + value: ipv4 + - name: WINDOWS_OS_VERSION + value: "2022" + - name: WINDOWS_CONTAINER_RUNTIME + value: "containerd:2.1.1" + - name: ENCAPSULATION_TYPE + value: "VXLAN" + - name: ENABLE_AUTO_HEP + value: "true" + + # operator, calicocni, stable-3, amd64, nftables, ipv4, aws-kubeadm, kdd, windows 1809, docker, ubuntu2204, autohep, none encap, nftables-kube-proxy + - name: windows - aws-kubeadm - docker + execution_time_limit: + hours: 5 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: INSTALLER + value: "helmerator" + - name: PROVISIONER + value: aws-kubeadm + - name: K8S_VERSION + value: "stable-3" + - name: DATAPLANE + value: CalicoNftables + - name: CNI_PLUGIN + value: "Calico" + - name: IP_FAMILY + value: ipv4 + - name: WINDOWS_OS_VERSION + value: "1809" + - name: ENCAPSULATION_TYPE + value: "None" + - name: VPC_SUBNETS + value: '["172.16.101.0/24"]' # single subnet because no encapsulation is being used + - name: ENABLE_AUTO_HEP + value: "true" + - name: KUBE_PROXY_MODE + value: nftables + - name: CLUSTER_IMAGE + value: "jammy-22.04" + - name: ENABLE_AUTO_HEP + value: "true" + + # operator, calicocni, stable-1, amd64, bpf, ipv4, gcp-rke2, kdd, ubuntu2204, autohep, vxlan encap, nodelocaldns + - name: RKE2 + execution_time_limit: + hours: 5 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: INSTALLER + value: "operator" + - name: PROVISIONER + value: gcp-rke2 + - name: K8S_VERSION + value: "stable-1" + - name: DATAPLANE + value: CalicoBPF + - name: CNI_PLUGIN + value: "Calico" + - name: IP_FAMILY + value: ipv4 + - name: ENCAPSULATION_TYPE + value: "VXLAN" + - name: CLUSTER_IMAGE + value: "ubuntu-2204-lts" + - name: ENABLE_AUTO_HEP + value: "true" + # - name: ENABLE_NODE_LOCAL_DNS_CACHE # nodelocaldns - doesn't appear to work with RKE2 + # value: "true" diff --git a/.semaphore/end-to-end/pipelines/test.yml b/.semaphore/end-to-end/pipelines/test.yml index a1028de9fd7..8e17d695e3a 100644 --- a/.semaphore/end-to-end/pipelines/test.yml +++ b/.semaphore/end-to-end/pipelines/test.yml @@ -20,7 +20,7 @@ global_job_config: epilogue: always: commands: - - ./.semaphore/end-to-end/scripts/global_epilogue.sh + - ~/calico/.semaphore/end-to-end/scripts/global_epilogue.sh secrets: - name: marvin-github-ssh-private-key - name: banzai-secrets @@ -33,6 +33,10 @@ global_job_config: value: "test.yml" - name: K8S_E2E_FLAGS value: --ginkgo.focus=(\[sig-calico\].*\[Conformance\]) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|\[DataPath\]) + - name: USE_PRIVATE_REGISTRY + value: "true" + - name: PRIVATE_REGISTRY + value: "quay.io/" blocks: - name: calico tests @@ -43,7 +47,7 @@ blocks: execution_time_limit: hours: 4 commands: - - ./.semaphore/end-to-end/scripts/body_standard.sh + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh matrix: - env_var: PROVISIONER values: ["gcp-kubeadm"] diff --git a/.semaphore/end-to-end/pipelines/upgrade.yml b/.semaphore/end-to-end/pipelines/upgrade.yml index 2739fe0db8f..1f1a4ee78b1 100644 --- a/.semaphore/end-to-end/pipelines/upgrade.yml +++ b/.semaphore/end-to-end/pipelines/upgrade.yml @@ -20,7 +20,7 @@ global_job_config: epilogue: always: commands: - - ./.semaphore/end-to-end/scripts/global_epilogue.sh + - ~/calico/.semaphore/end-to-end/scripts/global_epilogue.sh secrets: - name: marvin-github-ssh-private-key - name: banzai-secrets @@ -29,7 +29,7 @@ global_job_config: value: k8s-e2e - name: PRODUCT value: calico - - name: RELEASE_STREAM # in this context, this is the version to upgrade from + - name: RELEASE_STREAM # in this context, this is the version to upgrade from value: v3.29 - name: USE_HASH_RELEASE value: "false" @@ -47,8 +47,10 @@ global_job_config: # and skipping the slow, disruptive, and some other tests that are not relevant to Calico OSS - name: K8S_E2E_FLAGS value: --ginkgo.focus=(\[Conformance\]|Observe|RBAC) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|\[DataPath\]|sig-node|sig-apps|sig-storage|sig-scheduling|sig-calico-enterprise) - - name: BANZAI_CORE_BRANCH - value: lwr-retry-calicoctl-install + - name: USE_PRIVATE_REGISTRY + value: "true" + - name: PRIVATE_REGISTRY + value: "quay.io/" blocks: - name: calico tests @@ -59,7 +61,7 @@ blocks: execution_time_limit: hours: 4 commands: - - ./.semaphore/end-to-end/scripts/body_standard.sh + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh env_vars: - name: PROVISIONER value: azr-aks @@ -79,7 +81,7 @@ blocks: execution_time_limit: hours: 4 commands: - - ./.semaphore/end-to-end/scripts/body_standard.sh + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh env_vars: - name: PROVISIONER value: aws-eks @@ -100,7 +102,7 @@ blocks: execution_time_limit: hours: 4 commands: - - ./.semaphore/end-to-end/scripts/body_standard.sh + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh matrix: - env_var: PROVISIONER values: ["aws-kubeadm"] @@ -115,7 +117,7 @@ blocks: execution_time_limit: hours: 4 commands: - - ./.semaphore/end-to-end/scripts/body_standard.sh + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh matrix: - env_var: PROVISIONER values: ["gcp-kubeadm"] @@ -130,13 +132,13 @@ blocks: execution_time_limit: hours: 4 commands: - - ./.semaphore/end-to-end/scripts/body_standard.sh + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh env_vars: - name: PROVISIONER value: aws-openshift matrix: - env_var: OPENSHIFT_VERSION - values: ["4.16.32"] + values: ["4.19.20"] - env_var: DATAPLANE values: ["CalicoIptables"] - env_var: INSTALLER @@ -146,7 +148,7 @@ blocks: execution_time_limit: hours: 4 commands: - - ./.semaphore/end-to-end/scripts/body_standard.sh + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh env_vars: - name: PROVISIONER value: gcp-rke2 diff --git a/.semaphore/end-to-end/pipelines/vpp.yml b/.semaphore/end-to-end/pipelines/vpp.yml new file mode 100644 index 00000000000..eaaaaa7863b --- /dev/null +++ b/.semaphore/end-to-end/pipelines/vpp.yml @@ -0,0 +1,273 @@ +version: v1.0 + +name: banzai-calico vpp +# This file contains e2e runs for VPP. + +agent: + machine: + type: c1-standard-1 + os_image: ubuntu2204 + +execution_time_limit: + hours: 12 + +global_job_config: + prologue: + commands_file: ../scripts/global_prologue.sh + epilogue: + always: + commands: + - ~/calico/.semaphore/end-to-end/scripts/global_epilogue.sh || true + secrets: + - name: marvin-github-ssh-private-key + - name: banzai-secrets + env_vars: + - name: K8S_E2E_DOCKER_EXTRA_FLAGS + value: "-e CLIENT_TYPE=k8s" + - name: K8S_VERSION + value: "stable-1" + - name: INSTALLER + value: operator + - name: ENABLE_CLOUD_PROVIDER + value: "false" # in-tree providers not supported in k8s 1.27+ + - name: FUNCTIONAL_AREA + value: "vpp.yml" + - name: GS_BUCKET + value: "vpp-results" + - name: DATAPLANE + value: CalicoVPP + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|Dataplane:BPF) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|wireguard|Wireguard|WireGuard|calient|doNotTrack|performanceHints|Tiered-Policy) + - name: USE_LATEST_RELEASE + value: "false" + - name: ENABLE_EXTERNAL_NODE + value: "true" + - name: ENCAPSULATION_TYPE + value: "IPIP" + - name: IPV4_POD_CIDR + value: "192.168.0.0/16" + - name: BGP_STATUS + value: "Enabled" + - name: CNI_PLUGIN + value: "Calico" + - name: USE_PRIVATE_REGISTRY + value: "true" + - name: PRIVATE_REGISTRY + value: "quay.io/" + - name: RELEASE_STREAM + value: "master" + - name: VPP_VERSION + value: "master" + +blocks: + - name: k8s-e2e ST + dependencies: [] + task: + jobs: + - name: v3.28 regression test + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: PROVISIONER + values: + - "gcp-kubeadm" + - env_var: VPP_MANIFEST_FILE + values: + - "calico-vpp-nohuge.yaml" + - env_var: RELEASE_STREAM + values: ["v3.28"] + - env_var: VPP_VERSION + values: ["v3.28.0"] + + - name: VPP-kadm + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: PROVISIONER + values: + - "gcp-kubeadm" + - "aws-kubeadm" + - env_var: VPP_MANIFEST_FILE + values: + - "calico-vpp-nohuge.yaml" + + - name: VPP-openshift + execution_time_limit: + hours: 8 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: PROVISIONER + values: + - "aws-openshift" + + - name: VPP-kadm-Huge + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: PROVISIONER + values: + - "gcp-kubeadm" + - "aws-kubeadm" + - env_var: VPP_MANIFEST_FILE + values: + - "calico-vpp-dpdk.yaml" + env_vars: + - name: ENABLE_HUGEPAGES + value: "true" + + - name: VPP-kadm-VX-Huge + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: PROVISIONER + values: + - "gcp-kubeadm" + - env_var: VPP_MANIFEST_FILE + values: + - "calico-vpp-dpdk.yaml" + env_vars: + - name: ENABLE_HUGEPAGES + value: "true" + - name: ENCAPSULATION_TYPE + value: "VXLAN" + - name: BGP_STATUS + value: "Enabled" + + - name: VPP-kadm-WG-Huge + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: PROVISIONER + values: + - "gcp-kubeadm" + - env_var: VPP_MANIFEST_FILE + values: + - "calico-vpp-dpdk.yaml" + env_vars: + - name: ENABLE_HUGEPAGES + value: "true" + - name: ENABLE_WIREGUARD + value: "true" + + - name: VPP-EKS + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: EKS_CNI + values: + - calico + - env_var: VPP_MANIFEST_FILE + values: + - calico-vpp-eks.yaml + env_vars: + - name: PROVISIONER + value: aws-eks + - name: IPV4_POD_CIDR + value: "172.16.0.0/16" + - name: FAILSAFE_443 + value: "true" + # Skipping these upstream k8s tests since they are failing because EKS control plane nodes can't reach the pod net: + # - [IT] "Proxy version v1 A set of valid responses are returned for both pod and service Proxy" + # - [IT] "Proxy version v1 should proxy through a service and a pod" + # - [IT] "ReplicaSet should serve a basic image on each replica with a public image" + # - [IT] "ReplicationController should serve a basic image on each replica with a public image" + # - [It] "DNS should provide DNS for ExternalName services" + # - [It] "DNS should provide DNS for pods for Hostname" + # - [It] "DNS should provide DNS for pods for Subdomain" + # - [It] "DNS should provide DNS for services" + # - [It] "DNS should provide DNS for the cluster" + # - [It] "DNS should resolve DNS of partial qualified names for services" + # See details: https://docs.tigera.io/calico/latest/getting-started/kubernetes/managed-public-cloud/eks#install-eks-with-calico-networking:~:text=Calico%20networking%20cannot,on%20this%20setting. + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|Dataplane-BPF) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|wireguard|Wireguard|WireGuard|calient|doNotTrack|performanceHints|Tiered-Policy|both.pod.and.service.Proxy\b|proxy.through.a.service.and.a.pod|replica.with.a.public.image|DNS.for.ExternalName.services|DNS.for.pods.for.Hostname|DNS.for.pods.for.Subdomain|DNS.for.services|DNS.for.the.cluster|DNS.qualified.names.for.services) + + - name: VPP-EKS-dpdk + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: EKS_CNI + values: + - calico + - env_var: VPP_MANIFEST_FILE + values: + - calico-vpp-eks-dpdk.yaml + env_vars: + - name: PROVISIONER + value: aws-eks + - name: ENABLE_HUGEPAGES + value: "true" + - name: IPV4_POD_CIDR + value: "172.16.0.0/16" + - name: FAILSAFE_443 + value: "true" + # Skipping these upstream k8s tests since they are failing because EKS control plane nodes can't reach the pod net: + # - [IT] "Proxy version v1 A set of valid responses are returned for both pod and service Proxy" + # - [IT] "Proxy version v1 should proxy through a service and a pod" + # - [IT] "ReplicaSet should serve a basic image on each replica with a public image" + # - [IT] "ReplicationController should serve a basic image on each replica with a public image" + # - [It] "DNS should provide DNS for ExternalName services" + # - [It] "DNS should provide DNS for pods for Hostname" + # - [It] "DNS should provide DNS for pods for Subdomain" + # - [It] "DNS should provide DNS for services" + # - [It] "DNS should provide DNS for the cluster" + # - [It] "DNS should resolve DNS of partial qualified names for services" + # See details: https://docs.tigera.io/calico/latest/getting-started/kubernetes/managed-public-cloud/eks#install-eks-with-calico-networking:~:text=Calico%20networking%20cannot,on%20this%20setting. + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\]|Dataplane-BPF) --ginkgo.skip=(\[Slow\]|\[Disruptive\]|wireguard|Wireguard|WireGuard|calient|doNotTrack|performanceHints|Tiered-Policy|both.pod.and.service.Proxy\b|proxy.through.a.service.and.a.pod|replica.with.a.public.image|DNS.for.ExternalName.services|DNS.for.pods.for.Hostname|DNS.for.pods.for.Subdomain|DNS.for.services|DNS.for.the.cluster|DNS.qualified.names.for.services) + + - name: Iptables (Openshift) + execution_time_limit: + hours: 6 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: PROVISIONER + values: + - "aws-openshift" + env_vars: + - name: DATAPLANE + value: "CalicoIptables" + + - name: VPP-kadm-IPSec-Huge + execution_time_limit: + hours: 7 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + matrix: + - env_var: PROVISIONER + values: + - "aws-kubeadm" + - env_var: VPP_MANIFEST_FILE + values: + - "calico-vpp-dpdk.yaml" + env_vars: + - name: ENABLE_HUGEPAGES + value: "true" + - name: ENABLE_VPP_IPSEC + value: "true" + +promotions: + - name: Cleanup jobs + pipeline_file: cleanup.yml + auto_promote: + when: "result = 'stopped'" + +after_pipeline: + task: + jobs: + - name: Reports + commands: + - test-results gen-pipeline-report --force diff --git a/.semaphore/end-to-end/pipelines/windows.yml b/.semaphore/end-to-end/pipelines/windows.yml new file mode 100644 index 00000000000..575341aed8a --- /dev/null +++ b/.semaphore/end-to-end/pipelines/windows.yml @@ -0,0 +1,122 @@ +version: v1.0 + +name: banzai-calico Windows + +agent: + machine: + type: f1-standard-2 + os_image: ubuntu2204 + +execution_time_limit: + hours: 12 + +global_job_config: + prologue: + commands_file: ../scripts/global_prologue.sh + epilogue: + always: + commands: + - ~/calico/.semaphore/end-to-end/scripts/global_epilogue.sh + secrets: + - name: marvin-github-ssh-private-key + - name: banzai-secrets + env_vars: + - name: CREATE_WINDOWS_NODES + value: "true" + - name: K8S_E2E_FLAGS + value: --ginkgo.focus=(\[sig-calico\].*\[RunsOnWindows\]) --ginkgo.skip=(\[LinuxOnly\]) + - name: FUNCTIONAL_AREA + value: "windows.yml" + - name: USE_PRIVATE_REGISTRY + value: "true" + - name: PRIVATE_REGISTRY + value: "quay.io/" + - name: INSTALLER + value: "operator" + - name: RUN_LOCAL_TESTS # run e2e tests from locally built binary + value: "true" + +# Dimensions +# Provisioners: azr-aso, azr-aks +# Installer: operator +# Windows OS: Windows Server 2022 +# k8s Versions: stable-1, stable +# linux dataplane: bpf, iptables, nftables +# encapsulation: vxlan, no-encap +# CNI: Calico, Azure CNI + +# Runs: +# azr-aso, 2022, stable, bpf, vxlan +# azr-aso, 2022, stable-1, nftables, vxlan +# azr-aks, 2022, stable-1, iptables, no-encap, azure CNI + +blocks: + - name: Calico Windows + dependencies: [] + task: + jobs: + - name: "azr-aso, 2022, stable, bpf, vxlan" + execution_time_limit: + hours: 5 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: PROVISIONER + value: azr-aso + - name: K8S_VERSION + value: stable-1 + - name: DATAPLANE + value: CalicoBPF + - name: ENCAPSULATION_TYPE + value: VXLAN + - name: WINDOWS_OS + value: "2022" + + - name: "azr-aso, 2022, stable-1, nftables, vxlan" + execution_time_limit: + hours: 5 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: PROVISIONER + value: azr-aso + - name: K8S_VERSION + value: stable-1 + - name: DATAPLANE + value: CalicoNftables + - name: ENCAPSULATION_TYPE + value: VXLAN + - name: WINDOWS_OS + value: "2022" + + - name: "azr-aks, 2022, stable-1, iptables, no encap, azure CNI" + execution_time_limit: + hours: 5 + commands: + - ~/calico/.semaphore/end-to-end/scripts/body_standard.sh + env_vars: + - name: PROVISIONER + value: azr-aks + - name: K8S_VERSION + value: stable-1 + - name: DATAPLANE + value: CalicoIptables + - name: ENCAPSULATION_TYPE + value: "None" + - name: WINDOWS_OS + value: "2022" + - name: USE_VENDORED_CNI + value: "true" + +promotions: + - name: Cleanup jobs + pipeline_file: cleanup.yml + auto_promote: + when: "result = 'stopped'" + +after_pipeline: + task: + jobs: + - name: Reports + commands: + - test-results gen-pipeline-report --force diff --git a/.semaphore/end-to-end/scripts/body_standard.sh b/.semaphore/end-to-end/scripts/body_standard.sh index 6f1d33022dc..eb73d1ded3f 100755 --- a/.semaphore/end-to-end/scripts/body_standard.sh +++ b/.semaphore/end-to-end/scripts/body_standard.sh @@ -8,58 +8,96 @@ else VERBOSE="" fi -cd "${BZ_HOME}" +if [[ "${HCP_ENABLED}" == "true" ]]; then + echo "[INFO] starting hcp job..." -echo "[INFO] starting bz provision..." -bz provision $VERBOSE | tee >(gzip --stdout > ${BZ_LOGS_DIR}/provision.log.gz) + echo "[INFO] starting hcp provision..." + hcp-provision.sh |& tee ${BZ_LOGS_DIR}/provision.log -cache delete $SEMAPHORE_JOB_ID -cache store ${SEMAPHORE_JOB_ID} ${BZ_HOME} + cache delete ${SEMAPHORE_JOB_ID} + cache store ${SEMAPHORE_JOB_ID} ${BZ_HOME} -echo "[INFO] starting bz install..." -bz install $VERBOSE | tee >(gzip --stdout > ${BZ_LOGS_DIR}/install.log.gz) + echo "[INFO] Test logs will be available here after the run: ${SEMAPHORE_ORGANIZATION_URL}/artifacts/jobs/${SEMAPHORE_JOB_ID}?path=semaphore%2Flogs" + echo "[INFO] Alternatively, you can view logs while job is running using 'sem attach ${SEMAPHORE_JOB_ID}' and then 'tail -f ${BZ_LOGS_DIR}/${TEST_TYPE}-tests.log'" -# Put the bin dir into the PATH -export PATH=$PATH:${BZ_LOCAL_DIR}/bin + echo "[INFO] starting hcp testing..." + hcp-test.sh |& tee ${BZ_LOGS_DIR}/${TEST_TYPE}-tests.log -if [[ "${ENABLE_EXTERNAL_NODE}" == "true" ]]; then - export EXT_USER=ubuntu - EXT_IP=$(cat "${BZ_LOCAL_DIR}"/external_ip) - export EXT_IP - export EXT_KEY=${BZ_LOCAL_DIR}/external_key - export K8S_E2E_DOCKER_EXTRA_FLAGS="-v $EXT_KEY:/key --env EXT_USER --env EXT_KEY=/key --env EXT_IP $K8S_E2E_DOCKER_EXTRA_FLAGS" - echo "EXT_USER=ubuntu EXT_IP=$EXT_IP, EXT_KEY=$EXT_KEY" - echo "K8S_E2E_DOCKER_EXTRA_FLAGS=$K8S_E2E_DOCKER_EXTRA_FLAGS" -fi +else + echo "[INFO] starting job..." + echo "[INFO] BZ_HOME=${BZ_HOME}" -if [ -n "${IPAM_TEST_POOL_SUBNET}" ]; then - export K8S_E2E_DOCKER_EXTRA_FLAGS="$K8S_E2E_DOCKER_EXTRA_FLAGS --env IPAM_TEST_POOL_SUBNET" - echo "IPAM_TEST_POOL_SUBNET=$IPAM_TEST_POOL_SUBNET" -fi + cd "${BZ_HOME}" + if [[ "${HCP_STAGE}" == "hosting" || "${HCP_STAGE}" == "destroy-hosting" ]]; then + : # Skip provisioning for hosting stages as cluster already exists + else + echo "[INFO] starting bz provision..." + bz provision $VERBOSE |& tee >(gzip --stdout > ${BZ_LOGS_DIR}/provision.log.gz) -if [ "${ENABLE_DSR}" == "true" ]; then - echo "[INFO] Enabling DSR" - KUBECONFIG=${BZ_LOCAL_DIR}/kubeconfig kubectl set env -n kube-system ds/calico-node FELIX_BPFExternalServiceMode="DSR" -fi + cache delete $SEMAPHORE_JOB_ID + cache store ${SEMAPHORE_JOB_ID} ${BZ_HOME} -if [ "${FAILSAFE_443}" == "true" ]; then - KUBECONFIG=${BZ_LOCAL_DIR}/kubeconfig kubectl patch felixconfiguration default --type=merge -p '{"spec":{"failsafeOutboundHostPorts": [{"protocol": "udp", "port":53},{"protocol": "udp", "port":67},{"protocol": "tcp", "port":179},{"protocol": "tcp", "port":2379},{"protocol": "tcp", "port":2380},{"protocol": "tcp", "port":5473},{"protocol": "tcp", "port":443},{"protocol": "tcp", "port":6666},{"protocol": "tcp", "port":6667}]}}' -fi + echo "[INFO] starting bz install..." + bz install $VERBOSE |& tee >(gzip --stdout > ${BZ_LOGS_DIR}/install.log.gz) + if [[ "${HCP_STAGE}" == "setup-hosting" ]]; then + echo "[INFO] HCP_STAGE=${HCP_STAGE}, storing hosting cluster profile in cache" + cache store ${SEMAPHORE_WORKFLOW_ID}-hosting-${HOSTING_CLUSTER} ${BZ_HOME} + fi + fi -# Perform the operator migration following the instructions here: -# https://projectcalico.docs.tigera.io/maintenance/operator-migration -if [[ -n "$OPERATOR_MIGRATE" ]]; then - ${HOME}/${SEMAPHORE_GIT_DIR}/.semaphore/end-to-end/scripts/test_scripts/operator_migrate.sh -fi + # Put the bin dir into the PATH + export PATH=$PATH:${BZ_LOCAL_DIR}/bin -if [[ -n "$UPLEVEL_RELEASE_STREAM" ]]; then - echo "[INFO] starting bz upgrade..." - bz upgrade $VERBOSE | tee >(gzip --stdout > ${BZ_LOGS_DIR}/upgrade.log.gz) -fi + if [[ "${ENABLE_EXTERNAL_NODE}" == "true" ]]; then + export EXT_USER=ubuntu + EXT_IP=$(cat "${BZ_LOCAL_DIR}"/external_ip) + export EXT_IP + export EXT_KEY=${BZ_LOCAL_DIR}/external_key + export K8S_E2E_DOCKER_EXTRA_FLAGS="-v $EXT_KEY:/key --env EXT_USER --env EXT_KEY=/key --env EXT_IP $K8S_E2E_DOCKER_EXTRA_FLAGS" + echo "EXT_USER=ubuntu EXT_IP=$EXT_IP, EXT_KEY=$EXT_KEY" + echo "K8S_E2E_DOCKER_EXTRA_FLAGS=$K8S_E2E_DOCKER_EXTRA_FLAGS" + fi + + if [ -n "${IPAM_TEST_POOL_SUBNET}" ]; then + export K8S_E2E_DOCKER_EXTRA_FLAGS="$K8S_E2E_DOCKER_EXTRA_FLAGS --env IPAM_TEST_POOL_SUBNET" + echo "IPAM_TEST_POOL_SUBNET=$IPAM_TEST_POOL_SUBNET" + fi + + if [ "${FAILSAFE_443}" == "true" ]; then + KUBECONFIG=${BZ_LOCAL_DIR}/kubeconfig kubectl patch felixconfiguration default --type=merge -p '{"spec":{"failsafeOutboundHostPorts": [{"protocol": "udp", "port":53},{"protocol": "udp", "port":67},{"protocol": "tcp", "port":179},{"protocol": "tcp", "port":2379},{"protocol": "tcp", "port":2380},{"protocol": "tcp", "port":5473},{"protocol": "tcp", "port":443},{"protocol": "tcp", "port":6666},{"protocol": "tcp", "port":6667}]}}' + fi -echo "[INFO] Test logs will be available here after the run: ${SEMAPHORE_ORGANIZATION_URL}/artifacts/jobs/${SEMAPHORE_JOB_ID}?path=semaphore%2Flogs" -echo "[INFO] Alternatively, you can view logs while job is running using 'sem attach ${SEMAPHORE_JOB_ID}' and then 'tail -f ${BZ_LOGS_DIR}/${TEST_TYPE}-tests.log'" + # Perform the operator migration following the instructions here: + # https://projectcalico.docs.tigera.io/maintenance/operator-migration + if [[ -n "$OPERATOR_MIGRATE" ]]; then + ${HOME}/${SEMAPHORE_GIT_DIR}/.semaphore/end-to-end/scripts/test_scripts/operator_migrate.sh |& tee >(gzip --stdout > ${BZ_LOGS_DIR}/operator_migrate.log.gz) + fi + # Perform the AKS migration following the instructions here: + # https://docs.tigera.io/calico/latest/getting-started/kubernetes/managed-public-cloud/aks-migrate + if [[ -n "$DESIRED_POLICY" ]]; then + echo "[INFO] starting AKS migration..." + bz addons run aks-migrate:setup + fi -echo "[INFO] starting bz testing..." -bz tests $VERBOSE | tee >(gzip --stdout > ${BZ_LOGS_DIR}/${TEST_TYPE}-tests.log.gz) + if [[ -n "$UPLEVEL_RELEASE_STREAM" ]]; then + echo "[INFO] starting bz upgrade..." + bz upgrade $VERBOSE | tee >(gzip --stdout > ${BZ_LOGS_DIR}/upgrade.log.gz) + fi + + if [[ ${MCM_STAGE:-} != *-mgmt* ]] && [[ ${HCP_STAGE:-} != *-hosting* ]]; then + echo "[INFO] Test logs will be available here after the run: ${SEMAPHORE_ORGANIZATION_URL}/artifacts/jobs/${SEMAPHORE_JOB_ID}?path=semaphore%2Flogs" + echo "[INFO] Alternatively, you can view logs while job is running using 'sem attach ${SEMAPHORE_JOB_ID}' and then 'tail -f ${BZ_LOGS_DIR}/${TEST_TYPE}-tests.log'" + + if [[ -n "$RUN_LOCAL_TESTS" ]]; then + echo "[INFO] starting e2e testing from local binary..." + pushd ${HOME}/calico + make -C e2e build |& tee >(gzip --stdout > ${BZ_LOGS_DIR}/${TEST_TYPE}-tests.log.gz) + KUBECONFIG=${BZ_LOCAL_DIR}/kubeconfig PRODUCT=calico ./e2e/bin/k8s/e2e.test ${K8S_E2E_FLAGS} |& tee -a >(gzip --stdout > ${BZ_LOGS_DIR}/${TEST_TYPE}-tests.log.gz) + popd + else + echo "[INFO] starting bz testing..." + bz tests $VERBOSE |& tee >(gzip --stdout > ${BZ_LOGS_DIR}/${TEST_TYPE}-tests.log.gz) + fi + fi +fi diff --git a/.semaphore/end-to-end/scripts/global_epilogue.sh b/.semaphore/end-to-end/scripts/global_epilogue.sh index 9a679d494bc..3154da6b296 100755 --- a/.semaphore/end-to-end/scripts/global_epilogue.sh +++ b/.semaphore/end-to-end/scripts/global_epilogue.sh @@ -1,6 +1,67 @@ #!/usr/bin/env bash set -eo pipefail +delete_artifacts() { + echo "[INFO] Deleting artifacts that are already pushed" + # Validate variables before destructive commands + if [[ -n "${BZ_LOCAL_DIR}" && -n "${DIAGS_ARCHIVE_FILENAME}" ]]; then + sudo rm -rf "${BZ_LOCAL_DIR}/${DIAGS_ARCHIVE_FILENAME}" || true + else + echo "[WARN] BZ_LOCAL_DIR or DIAGS_ARCHIVE_FILENAME is unset or empty, skipping deletion of archive file." + fi + if [[ -n "${BZ_LOCAL_DIR}" ]]; then + sudo rm -rf "${BZ_LOCAL_DIR}/diags" || true + else + echo "[WARN] BZ_LOCAL_DIR is unset or empty, skipping deletion of diags directory." + fi + sudo rm -rf ~/.cache/* /var/lib/apt/lists/* || true +} + +delete_calicoctl() { + echo "[INFO] Deleting calicoctl" + if [[ -n "${BZ_LOGS_DIR}" ]]; then + sudo rm -rf "${BZ_LOGS_DIR}/bin/kubectl-calico" || true + sudo rm -rf "${BZ_LOGS_DIR}/bin/calico-*" || true + else + echo "[WARN] BZ_LOGS_DIR is unset or empty, skipping deletion of calicoctl binaries." + fi +} + +delete_gcloud() { + if [[ ! $PROVISIONER =~ ^gcp-.* ]]; then + sudo NEEDRESTART_SUSPEND=1 NEEDRESTART_MODE=a apt remove google-cloud-cli google-cloud-cli-gke-gcloud-auth-plugin -y && sudo needrestart -r a || true + fi +} + +run_non_hcp_reports_and_diags() { + if [[ "$SEMAPHORE_JOB_RESULT" != "passed" || "$TEST_TYPE" == "ocp-cert" ]]; then + echo "[INFO] global_epilogue: capturing diags" + bz diags $VERBOSE |& tee ${BZ_LOGS_DIR}/diagnostic.log || true + artifact push job ${BZ_LOCAL_DIR}/${DIAGS_ARCHIVE_FILENAME} --destination semaphore/diags.tgz || true + if [[ "$GS_BUCKET" != "" ]]; then + echo "[INFO] bucket_upload: capturing diags" + gsutil cp ${BZ_LOCAL_DIR}/${DIAGS_ARCHIVE_FILENAME} gs://${GS_BUCKET}/${METADATA}/${DIAGS_ARCHIVE_FILENAME} || true + fi + fi + + delete_artifacts; delete_calicoctl; delete_gcloud + + REPORT_DIR=${REPORT_DIR:-"${BZ_LOCAL_DIR}/report/${TEST_TYPE}"} + echo "[INFO] global_epilogue: pushing report artifacts" + artifact push job ${REPORT_DIR} --destination semaphore/test-results || true + cp ${REPORT_DIR}/junit.xml . || true + + echo "[INFO] publish new semaphore test results" + test_publish=0 + test-results publish ${REPORT_DIR}/junit.xml --name "$SEMAPHORE_JOB_NAME" || test_publish=1 + echo "[INFO] Status of Publishing test results to Semaphore: ${test_publish}" +} + +run_non_hcp_destroy() { + echo "[INFO] global_epilogue: destroy" + bz destroy $VERBOSE |& tee ${BZ_LOGS_DIR}/destroy.log || true +} + echo "[INFO] starting global_epilogue" cd "${BZ_HOME}" @@ -29,44 +90,75 @@ if [[ "$GS_BUCKET" != "" ]]; then METADATA=${METADATA}/$(date -d @${SEMAPHORE_PIPELINE_STARTED_AT} -u +%H:%M) fi -if [[ "$SEMAPHORE_JOB_RESULT" != "passed" ]] || [[ "$TEST_TYPE" == "ocp-cert" ]]; then - echo "[INFO] global_epilogue: capturing diags" - bz diags $VERBOSE | tee ${BZ_LOGS_DIR}/diagnostic.log || true - artifact push job ${BZ_LOCAL_DIR}/${DIAGS_ARCHIVE_FILENAME} --destination semaphore/diags.tgz || true - if [[ "$GS_BUCKET" != "" ]]; then - echo "[INFO] bucket_upload: capturing diags" - gsutil cp ${BZ_LOCAL_DIR}/${DIAGS_ARCHIVE_FILENAME} gs://${GS_BUCKET}/${METADATA}/${DIAGS_ARCHIVE_FILENAME} || true +case "${HCP_STAGE:-}" in + setup-hosting|hosting|destroy-hosting) + # Do nothing for hosting stages + ;; + *) + echo "[INFO] create report and push test results to Lens" + cd ./scripts + echo "[INFO] downloading lens uploader script" + curl -H "Authorization: token ${GITHUB_ACCESS_TOKEN}" \ + -H "Accept: application/vnd.github.v3.raw" \ + -o ./run-lens.sh \ + https://raw.githubusercontent.com/tigera/banzai-lens/main/uploader/run-lens.sh || true + echo "[INFO] running Lens uploader script" + chmod +x ./run-lens.sh || true + ./run-lens.sh || true + docker system prune -f || true + ;; +esac + +echo "[INFO] BZ_HOME=${BZ_HOME}" +cd "${BZ_HOME}" + +if [[ "${HCP_ENABLED}" == "true" ]]; then + # if test isn't passed OR if test_type is ocp-certification, capture diags + if [[ "$SEMAPHORE_JOB_RESULT" != "passed" || "$TEST_TYPE" == "ocp-cert" ]]; then + echo "[INFO] global_epilogue: hcp: capturing diags" + hcp-diags.sh |& tee ${BZ_LOGS_DIR}/diagnostic.log || true + artifact push job ${BZ_PROFILES_PATH}/.diags --destination semaphore/diags || true fi - rm -rf "${BZ_LOCAL_DIR:?}/${DIAGS_ARCHIVE_FILENAME:?}" - rm -rf ${BZ_LOCAL_DIR}/diags -fi -echo "[INFO] global_epilogue: pushing report artifacts" -artifact push job ${REPORT_DIR} --destination semaphore/test-results || true -test-results publish ${REPORT_DIR}/junit.xml --name "$SEMAPHORE_JOB_NAME" || true -if [[ "$GS_BUCKET" != "" ]]; then - echo "[INFO] bucket_upload: pushing report artifacts" - gsutil cp ${REPORT_DIR}/junit.xml gs://${GS_BUCKET}/${METADATA}/junit.xml || true + echo "[INFO] global_epilogue: hcp: pushing report artifacts" + artifact push job ${BZ_PROFILES_PATH}/.report --destination semaphore/test-results || true - echo "[INFO] bucket_upload: pushing log artifacts" - gsutil cp -r -z ${BZ_LOGS_DIR}/* gs://${GS_BUCKET}/${METADATA}/logs/ || true -fi + echo "[INFO] publish new semaphore test results" + test-results publish semaphore/test-results/junit.xml || true -echo "[INFO] create and push test results to Lens" -echo "[INFO] downloading lens uploader script" -curl --retry 9 --retry-all-errors -H "Authorization: token ${GITHUB_ACCESS_TOKEN}" \ - -H "Accept: application/vnd.github.v3.raw" \ - -o ./run-lens.sh \ - https://raw.githubusercontent.com/tigera/banzai-lens/main/uploader/run-lens.sh || true -echo "[INFO] running Lens Uploader" -chmod +x ./run-lens.sh || true -./run-lens.sh || true -cd "${BZ_HOME}" + delete_artifacts; delete_calicoctl; delete_gcloud -echo "[INFO] global_epilogue: destroy" -bz destroy $VERBOSE | tee >(gzip --stdout > ${BZ_LOGS_DIR}/destroy.log.gz) || true -echo "[INFO] global_epilogue: pushing log artifacts" -artifact push job ${BZ_LOGS_DIR} --destination semaphore/logs || true + echo "[INFO] global_epilogue: hcp destroy" + hcp-destroy.sh |& tee ${BZ_LOGS_DIR}/destroy.log || true -echo "[INFO] global_epilogue: deleting cache" -cache delete $SEMAPHORE_JOB_ID + echo "[INFO] global_epilogue: hcp: pushing log artifacts" + artifact push job ${BZ_LOGS_DIR} --destination semaphore/logs || true +else + case "${HCP_STAGE:-}" in + setup-hosting) + artifact push workflow ${BZ_LOCAL_DIR}/kubeconfig -f --destination hosting-${HOSTING_CLUSTER}-kubeconfig + # setup-hosting preserves cache; no deletion + ;; + hosting) + # Hosting stages keep the cluster up; skip diags/destroy. + # Hosting cluster is persistent and should not have its cache deleted + : + ;; + destroy-hosting) + run_non_hcp_destroy + echo "[INFO] global_epilogue: deleting hosting cache" + artifact yank workflow hosting-${HOSTING_CLUSTER}-kubeconfig + cache delete ${SEMAPHORE_WORKFLOW_ID}-hosting-${HOSTING_CLUSTER} + cache delete "$(basename "$PWD")" + ;; + *) + run_non_hcp_reports_and_diags + run_non_hcp_destroy + echo "[INFO] global_epilogue: deleting job cache" + cache delete ${SEMAPHORE_JOB_ID} + ;; + esac + + echo "[INFO] global_epilogue: pushing log artifacts" + artifact push job ${BZ_LOGS_DIR} --destination semaphore/logs || true +fi diff --git a/.semaphore/end-to-end/scripts/global_prologue.sh b/.semaphore/end-to-end/scripts/global_prologue.sh index 5c8f88ebe00..aa6999f10ba 100755 --- a/.semaphore/end-to-end/scripts/global_prologue.sh +++ b/.semaphore/end-to-end/scripts/global_prologue.sh @@ -23,8 +23,9 @@ echo "[INFO] starting prologue" echo "[INFO] Clean out language tools we don't use to free up disk" sudo rm -rf ~/{.kerl,.kiex,.npm,.nvm,.phpbrew,.rbenv,.sbt} /opt/{apache-maven*,firefox*,scala} /usr/lib/jvm /usr/local/{aws2,golang,phantomjs*} /root/.local/share/heroku /usr/local/lib/heroku -# Set up the DNS resolver to use OpenDNS servers, which appear to be more reliable. +echo "[INFO] overriding DNS..." echo "nameserver 208.67.222.222" | sudo tee /etc/resolv.conf +echo "nameserver 8.8.8.8" | sudo tee -a /etc/resolv.conf echo "[INFO] checkout..." checkout @@ -41,8 +42,8 @@ RANDOM_TOKEN2=$(LC_ALL=C tr -dc 'a-z0-9' $out_file - echo "# To update, modify the template and then run 'make gen-semaphore-yaml'." >>$out_file - - cat semaphore.yml.d/01-preamble.yml >>$out_file - cat semaphore.yml.d/02-global_job_config.yml >>$out_file - cat semaphore.yml.d/03-promotions.yml >>$out_file - - # use sed to properly indent blocks - echo "blocks:" >>$out_file - - ls semaphore.yml.d/blocks/*.yml | sort | xargs cat | sed -e 's/^./ &/' >>$out_file - - cat semaphore.yml.d/99-after_pipeline.yml >>$out_file -done - -grep -o --perl '\$\{CHANGE_IN\(\K[^)]+' --no-filename semaphore.yml | \ - sort --reverse -u | \ - while read -r dep; do - sed -i "s&\${CHANGE_IN($dep)}&true&g" semaphore-scheduled-builds.yml - done - -pushd .. -go run ./hack/cmd/deps replace-sem-change-in ./.semaphore/semaphore.yml -popd - -sed -i "s/\${FORCE_RUN}/false/g" semaphore.yml -sed -i "s/\${FORCE_RUN}/true/g" semaphore-scheduled-builds.yml diff --git a/.semaphore/publish-artifacts b/.semaphore/publish-artifacts index 0c52920dca6..ed82398699f 100755 --- a/.semaphore/publish-artifacts +++ b/.semaphore/publish-artifacts @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# Copyright (c) 2020 Tigera, Inc. All rights reserved. +# Copyright (c) 2020-2025 Tigera, Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -22,8 +22,9 @@ shopt -s nullglob my_dir=$(dirname "$0") repo_dir=$my_dir/.. +artifacts_dir="$repo_dir/artifacts" -cd "$repo_dir/artifacts" || exit 1 +cd "$artifacts_dir" || exit 1 for file in *; do artifact push job "$file" diff --git a/.semaphore/push-images/alp.yml b/.semaphore/push-images/alp.yml index 17be3d478fa..c276c98c59f 100644 --- a/.semaphore/push-images/alp.yml +++ b/.semaphore/push-images/alp.yml @@ -4,10 +4,8 @@ agent: machine: type: f1-standard-2 os_image: ubuntu2204 - execution_time_limit: minutes: 60 - global_job_config: env_vars: - name: DEV_REGISTRIES @@ -24,9 +22,9 @@ global_job_config: - echo $DOCKER_TOKEN | docker login --username "$DOCKER_USER" --password-stdin - echo $QUAY_TOKEN | docker login --username "$QUAY_USER" --password-stdin quay.io - export BRANCH_NAME=$SEMAPHORE_GIT_BRANCH - + - ssh-add ~/.ssh/id_rsa blocks: - - name: Publish ALP images + - name: Publish ALP images and multi-arch manifests dependencies: [] skip: when: "branch !~ '.+'" @@ -34,14 +32,4 @@ blocks: jobs: - name: Linux multi-arch commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C app-policy cd CONFIRM=true; fi - - name: Publish ALP multi-arch manifests - dependencies: - - Publish ALP images - skip: - when: "branch !~ '.+'" - task: - jobs: - - name: Linux multi-arch manifests - commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C app-policy push-manifests-with-tag CONFIRM=true; fi + - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C app-policy cd push-manifests-with-tag CONFIRM=true; fi diff --git a/.semaphore/push-images/apiserver.yml b/.semaphore/push-images/apiserver.yml index c432f466b15..afc489af11a 100644 --- a/.semaphore/push-images/apiserver.yml +++ b/.semaphore/push-images/apiserver.yml @@ -4,10 +4,8 @@ agent: machine: type: f1-standard-2 os_image: ubuntu2204 - execution_time_limit: minutes: 60 - global_job_config: env_vars: - name: DEV_REGISTRIES @@ -24,9 +22,9 @@ global_job_config: - echo $DOCKER_TOKEN | docker login --username "$DOCKER_USER" --password-stdin - echo $QUAY_TOKEN | docker login --username "$QUAY_USER" --password-stdin quay.io - export BRANCH_NAME=$SEMAPHORE_GIT_BRANCH - + - ssh-add ~/.ssh/id_rsa blocks: - - name: Publish apiserver images + - name: Publish apiserver images and multi-arch manifests dependencies: [] skip: when: "branch !~ '.+'" @@ -34,14 +32,4 @@ blocks: jobs: - name: Linux multi-arch commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C apiserver cd CONFIRM=true; fi - - name: Publish apiserver multi-arch manifests - dependencies: - - Publish apiserver images - skip: - when: "branch !~ '.+'" - task: - jobs: - - name: Linux multi-arch manifests - commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C apiserver push-manifests-with-tag CONFIRM=true; fi + - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C apiserver cd push-manifests-with-tag CONFIRM=true; fi diff --git a/.semaphore/push-images/calicoctl.yml b/.semaphore/push-images/calicoctl.yml index e3d0833fdfe..e99acfc1d5f 100644 --- a/.semaphore/push-images/calicoctl.yml +++ b/.semaphore/push-images/calicoctl.yml @@ -4,10 +4,8 @@ agent: machine: type: f1-standard-2 os_image: ubuntu2204 - execution_time_limit: minutes: 60 - global_job_config: env_vars: - name: DEV_REGISTRIES @@ -24,9 +22,9 @@ global_job_config: - echo $DOCKER_TOKEN | docker login --username "$DOCKER_USER" --password-stdin - echo $QUAY_TOKEN | docker login --username "$QUAY_USER" --password-stdin quay.io - export BRANCH_NAME=$SEMAPHORE_GIT_BRANCH - + - ssh-add ~/.ssh/id_rsa blocks: - - name: Publish calicoctl images + - name: Publish calicoctl images and multi-arch manifests dependencies: [] skip: when: "branch !~ '.+'" @@ -34,14 +32,4 @@ blocks: jobs: - name: Linux multi-arch commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C calicoctl cd CONFIRM=true; fi - - name: Publish calicoctl multi-arch manifests - dependencies: - - Publish calicoctl images - skip: - when: "branch !~ '.+'" - task: - jobs: - - name: Linux multi-arch manifests - commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C calicoctl push-manifests-with-tag CONFIRM=true; fi + - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C calicoctl cd push-manifests-with-tag CONFIRM=true; fi diff --git a/.semaphore/push-images/cni-plugin.yml b/.semaphore/push-images/cni-plugin.yml index 725fb48f908..4938f97665d 100644 --- a/.semaphore/push-images/cni-plugin.yml +++ b/.semaphore/push-images/cni-plugin.yml @@ -4,10 +4,8 @@ agent: machine: type: f1-standard-2 os_image: ubuntu2204 - execution_time_limit: minutes: 60 - global_job_config: env_vars: - name: DEV_REGISTRIES @@ -24,9 +22,9 @@ global_job_config: - echo $DOCKER_TOKEN | docker login --username "$DOCKER_USER" --password-stdin - echo $QUAY_TOKEN | docker login --username "$QUAY_USER" --password-stdin quay.io - export BRANCH_NAME=$SEMAPHORE_GIT_BRANCH - + - ssh-add ~/.ssh/id_rsa blocks: - - name: Publish cni-plugin images + - name: Publish cni-plugin images and multi-arch manifests dependencies: [] skip: when: "branch !~ '.+'" @@ -34,17 +32,7 @@ blocks: jobs: - name: Linux multi-arch commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C cni-plugin cd CONFIRM=true; fi + - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C cni-plugin cd push-manifests-with-tag CONFIRM=true; fi - name: Windows commands: - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C cni-plugin release-windows CONFIRM=true; fi - - name: Publish cni-plugin multi-arch manifests - dependencies: - - Publish cni-plugin images - skip: - when: "branch !~ '.+'" - task: - jobs: - - name: Linux multi-arch manifests - commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C cni-plugin push-manifests-with-tag CONFIRM=true; fi diff --git a/.semaphore/push-images/e2e-test.yml b/.semaphore/push-images/e2e-test.yml index c8657d7616e..d57844f41ca 100644 --- a/.semaphore/push-images/e2e-test.yml +++ b/.semaphore/push-images/e2e-test.yml @@ -4,10 +4,8 @@ agent: machine: type: f1-standard-2 os_image: ubuntu2204 - execution_time_limit: minutes: 30 - global_job_config: secrets: - name: k8s-e2e-push @@ -17,7 +15,7 @@ global_job_config: # Log into quay.io with the tigeradev credentials (to push to quay.io/tigeradev/rapidclient) - echo $QUAY_TOKEN | docker login --username "$QUAY_USER" --password-stdin quay.io - checkout - + - ssh-add ~/.ssh/id_rsa blocks: - name: Publish e2e test utility images dependencies: [] diff --git a/.semaphore/push-images/envoy.yml b/.semaphore/push-images/envoy.yml index 01f2f38ac5c..2771ab079c2 100644 --- a/.semaphore/push-images/envoy.yml +++ b/.semaphore/push-images/envoy.yml @@ -4,10 +4,8 @@ agent: machine: type: f1-standard-2 os_image: ubuntu2204 - execution_time_limit: minutes: 60 - global_job_config: env_vars: - name: DEV_REGISTRIES @@ -24,9 +22,9 @@ global_job_config: - echo $DOCKER_TOKEN | docker login --username "$DOCKER_USER" --password-stdin - echo $QUAY_TOKEN | docker login --username "$QUAY_USER" --password-stdin quay.io - export BRANCH_NAME=$SEMAPHORE_GIT_BRANCH - + - ssh-add ~/.ssh/id_rsa blocks: - - name: Publish envoy images + - name: Publish envoy images and multi-arch manifests dependencies: [] skip: when: "branch !~ '.+'" @@ -34,18 +32,6 @@ blocks: jobs: - name: Linux multi-arch commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C third_party/envoy-gateway cd CONFIRM=true; fi - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C third_party/envoy-proxy cd CONFIRM=true; fi - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C third_party/envoy-ratelimit cd CONFIRM=true; fi - - name: Publish envoy multi-arch manifests - dependencies: - - Publish envoy images - skip: - when: "branch !~ '.+'" - task: - jobs: - - name: Linux multi-arch manifests - commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C third_party/envoy-gateway push-manifests-with-tag CONFIRM=true; fi - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C third_party/envoy-proxy push-manifests-with-tag CONFIRM=true; fi - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C third_party/envoy-ratelimit push-manifests-with-tag CONFIRM=true; fi + - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C third_party/envoy-gateway cd push-manifests-with-tag CONFIRM=true; fi + - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C third_party/envoy-proxy cd push-manifests-with-tag CONFIRM=true; fi + - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C third_party/envoy-ratelimit cd push-manifests-with-tag CONFIRM=true; fi diff --git a/.semaphore/push-images/goldmane.yml b/.semaphore/push-images/goldmane.yml index 9d358bff4eb..d5d552f0a27 100644 --- a/.semaphore/push-images/goldmane.yml +++ b/.semaphore/push-images/goldmane.yml @@ -4,10 +4,8 @@ agent: machine: type: f1-standard-2 os_image: ubuntu2204 - execution_time_limit: minutes: 60 - global_job_config: env_vars: - name: DEV_REGISTRIES @@ -24,9 +22,9 @@ global_job_config: - echo $DOCKER_TOKEN | docker login --username "$DOCKER_USER" --password-stdin - echo $QUAY_TOKEN | docker login --username "$QUAY_USER" --password-stdin quay.io - export BRANCH_NAME=$SEMAPHORE_GIT_BRANCH - + - ssh-add ~/.ssh/id_rsa blocks: - - name: Publish goldmane images + - name: Publish goldmane images and multi-arch manifests dependencies: [] skip: when: "branch !~ '.+'" @@ -34,14 +32,4 @@ blocks: jobs: - name: Linux multi-arch commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C goldmane cd CONFIRM=true; fi - - name: Publish goldmane multi-arch manifests - dependencies: - - Publish goldmane images - skip: - when: "branch !~ '.+'" - task: - jobs: - - name: Linux multi-arch manifests - commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C goldmane push-manifests-with-tag CONFIRM=true; fi + - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C goldmane cd push-manifests-with-tag CONFIRM=true; fi diff --git a/.semaphore/push-images/guardian.yml b/.semaphore/push-images/guardian.yml index 6f774f10c54..59882c7008b 100644 --- a/.semaphore/push-images/guardian.yml +++ b/.semaphore/push-images/guardian.yml @@ -4,10 +4,8 @@ agent: machine: type: f1-standard-2 os_image: ubuntu2204 - execution_time_limit: minutes: 60 - global_job_config: env_vars: - name: DEV_REGISTRIES @@ -24,9 +22,9 @@ global_job_config: - echo $DOCKER_TOKEN | docker login --username "$DOCKER_USER" --password-stdin - echo $QUAY_TOKEN | docker login --username "$QUAY_USER" --password-stdin quay.io - export BRANCH_NAME=$SEMAPHORE_GIT_BRANCH - + - ssh-add ~/.ssh/id_rsa blocks: - - name: Publish guardian images + - name: Publish guardian images and multi-arch manifests dependencies: [] skip: when: "branch !~ '.+'" @@ -34,14 +32,4 @@ blocks: jobs: - name: Linux multi-arch commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C guardian cd CONFIRM=true; fi - - name: Publish guardian multi-arch manifests - dependencies: - - Publish guardian images - skip: - when: "branch !~ '.+'" - task: - jobs: - - name: Linux multi-arch manifests - commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C guardian push-manifests-with-tag CONFIRM=true; fi + - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C guardian cd push-manifests-with-tag CONFIRM=true; fi diff --git a/.semaphore/push-images/key-cert-provisioner.yml b/.semaphore/push-images/key-cert-provisioner.yml index fa0babe532b..890e025ebd9 100644 --- a/.semaphore/push-images/key-cert-provisioner.yml +++ b/.semaphore/push-images/key-cert-provisioner.yml @@ -4,10 +4,8 @@ agent: machine: type: f1-standard-2 os_image: ubuntu2204 - execution_time_limit: minutes: 60 - global_job_config: env_vars: - name: DEV_REGISTRIES @@ -24,9 +22,9 @@ global_job_config: - echo $DOCKER_TOKEN | docker login --username "$DOCKER_USER" --password-stdin - echo $QUAY_TOKEN | docker login --username "$QUAY_USER" --password-stdin quay.io - export BRANCH_NAME=$SEMAPHORE_GIT_BRANCH - + - ssh-add ~/.ssh/id_rsa blocks: - - name: Publish key-cert-provisioner images + - name: Publish key-cert-provisioner images and multi-arch manifests dependencies: [] skip: when: "branch !~ '.+'" @@ -34,14 +32,4 @@ blocks: jobs: - name: Linux multi-arch commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C key-cert-provisioner cd CONFIRM=true; fi - - name: Publish key-cert-provisioner multi-arch manifests - dependencies: - - Publish key-cert-provisioner images - skip: - when: "branch !~ '.+'" - task: - jobs: - - name: Linux multi-arch manifests - commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C key-cert-provisioner push-manifests-with-tag CONFIRM=true; fi + - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C key-cert-provisioner cd push-manifests-with-tag CONFIRM=true; fi diff --git a/.semaphore/push-images/kube-controllers.yml b/.semaphore/push-images/kube-controllers.yml index bb9957fae9b..ac03006e0e8 100644 --- a/.semaphore/push-images/kube-controllers.yml +++ b/.semaphore/push-images/kube-controllers.yml @@ -4,10 +4,8 @@ agent: machine: type: f1-standard-2 os_image: ubuntu2204 - execution_time_limit: minutes: 60 - global_job_config: env_vars: - name: DEV_REGISTRIES @@ -24,9 +22,9 @@ global_job_config: - echo $DOCKER_TOKEN | docker login --username "$DOCKER_USER" --password-stdin - echo $QUAY_TOKEN | docker login --username "$QUAY_USER" --password-stdin quay.io - export BRANCH_NAME=$SEMAPHORE_GIT_BRANCH - + - ssh-add ~/.ssh/id_rsa blocks: - - name: Publish kube-controllers images + - name: Publish kube-controllers images and multi-arch manifests dependencies: [] skip: when: "branch !~ '.+'" @@ -34,14 +32,4 @@ blocks: jobs: - name: "kube-controllers" commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C kube-controllers cd CONFIRM=true; fi - - name: Publish kube-controllers multi-arch manifests - dependencies: - - Publish kube-controllers images - skip: - when: "branch !~ '.+'" - task: - jobs: - - name: Linux multi-arch manifests - commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C kube-controllers push-manifests-with-tag CONFIRM=true; fi + - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C kube-controllers cd push-manifests-with-tag CONFIRM=true; fi diff --git a/.semaphore/push-images/mock-node.yml b/.semaphore/push-images/mock-node.yml index 84938a748f5..f1cc6d69590 100644 --- a/.semaphore/push-images/mock-node.yml +++ b/.semaphore/push-images/mock-node.yml @@ -4,10 +4,8 @@ agent: machine: type: f1-standard-2 os_image: ubuntu2204 - execution_time_limit: minutes: 60 - global_job_config: env_vars: - name: DEV_REGISTRIES @@ -24,9 +22,9 @@ global_job_config: - echo $DOCKER_TOKEN | docker login --username "$DOCKER_USER" --password-stdin - echo $QUAY_TOKEN | docker login --username "$QUAY_USER" --password-stdin quay.io - export BRANCH_NAME=$SEMAPHORE_GIT_BRANCH - + - ssh-add ~/.ssh/id_rsa blocks: - - name: Publish mock-node images + - name: Publish mock-node images and multi-arch manifests dependencies: [] skip: when: "branch !~ '.+'" @@ -34,14 +32,4 @@ blocks: jobs: - name: Linux multi-arch commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C test-tools/mocknode cd CONFIRM=true; fi - - name: Publish mock-node multi-arch manifests - dependencies: - - Publish mock-node images - skip: - when: "branch !~ '.+'" - task: - jobs: - - name: Linux multi-arch manifests - commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C test-tools/mocknode push-manifests-with-tag CONFIRM=true; fi + - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C test-tools/mocknode cd push-manifests-with-tag CONFIRM=true; fi diff --git a/.semaphore/push-images/node.yml b/.semaphore/push-images/node.yml index 9ebb8e7d2d1..fe52fe89eec 100644 --- a/.semaphore/push-images/node.yml +++ b/.semaphore/push-images/node.yml @@ -4,10 +4,8 @@ agent: machine: type: f1-standard-2 os_image: ubuntu2204 - execution_time_limit: minutes: 60 - global_job_config: env_vars: - name: DEV_REGISTRIES @@ -24,7 +22,7 @@ global_job_config: - echo $DOCKER_TOKEN | docker login --username "$DOCKER_USER" --password-stdin - echo $QUAY_TOKEN | docker login --username "$QUAY_USER" --password-stdin quay.io - export BRANCH_NAME=$SEMAPHORE_GIT_BRANCH - + - ssh-add ~/.ssh/id_rsa blocks: - name: "Publish node images" dependencies: [] diff --git a/.semaphore/push-images/packaging.yaml b/.semaphore/push-images/packaging.yaml index 2ceb935fc8f..da698e3efd1 100644 --- a/.semaphore/push-images/packaging.yaml +++ b/.semaphore/push-images/packaging.yaml @@ -4,10 +4,8 @@ agent: machine: type: f1-standard-2 os_image: ubuntu2404 - execution_time_limit: minutes: 60 - global_job_config: secrets: - name: google-service-account-for-tigera-infra @@ -33,7 +31,7 @@ global_job_config: # notably being much faster because it uses Semaphore's cache to fetch # files from. - install-package --no-install-recommends devscripts moreutils patchelf jq - + - ssh-add ~/.ssh/id_rsa blocks: # This promotion is only _automatic_ for the master branch. For other # branches, and PRs, it is available but not automatic. This means a diff --git a/.semaphore/push-images/pod2daemon.yml b/.semaphore/push-images/pod2daemon.yml index 59bff89aae8..54ed589c15f 100644 --- a/.semaphore/push-images/pod2daemon.yml +++ b/.semaphore/push-images/pod2daemon.yml @@ -4,10 +4,8 @@ agent: machine: type: f1-standard-2 os_image: ubuntu2204 - execution_time_limit: minutes: 60 - global_job_config: env_vars: - name: DEV_REGISTRIES @@ -24,9 +22,9 @@ global_job_config: - echo $DOCKER_TOKEN | docker login --username "$DOCKER_USER" --password-stdin - echo $QUAY_TOKEN | docker login --username "$QUAY_USER" --password-stdin quay.io - export BRANCH_NAME=$SEMAPHORE_GIT_BRANCH - + - ssh-add ~/.ssh/id_rsa blocks: - - name: Publish pod2daemon images + - name: Publish pod2daemon images and multi-arch manifests dependencies: [] skip: when: "branch !~ '.+'" @@ -34,14 +32,4 @@ blocks: jobs: - name: Linux multi-arch commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C pod2daemon cd CONFIRM=true; fi - - name: Publish pod2daemon multi-arch manifests - dependencies: - - Publish pod2daemon images - skip: - when: "branch !~ '.+'" - task: - jobs: - - name: Linux multi-arch manifests - commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C pod2daemon push-manifests-with-tag CONFIRM=true; fi + - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C pod2daemon cd push-manifests-with-tag CONFIRM=true; fi diff --git a/.semaphore/push-images/typha.yml b/.semaphore/push-images/typha.yml index 4d3bc5d399e..eac267339c4 100644 --- a/.semaphore/push-images/typha.yml +++ b/.semaphore/push-images/typha.yml @@ -4,10 +4,8 @@ agent: machine: type: f1-standard-2 os_image: ubuntu2204 - execution_time_limit: minutes: 60 - global_job_config: env_vars: - name: DEV_REGISTRIES @@ -24,9 +22,9 @@ global_job_config: - echo $DOCKER_TOKEN | docker login --username "$DOCKER_USER" --password-stdin - echo $QUAY_TOKEN | docker login --username "$QUAY_USER" --password-stdin quay.io - export BRANCH_NAME=$SEMAPHORE_GIT_BRANCH - + - ssh-add ~/.ssh/id_rsa blocks: - - name: Publish typha images + - name: Publish typha images and multi-arch manifests dependencies: [] skip: when: "branch !~ '.+'" @@ -34,14 +32,4 @@ blocks: jobs: - name: Linux multi-arch commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C typha cd CONFIRM=true; fi - - name: Publish typha multi-arch manifests - dependencies: - - Publish typha images - skip: - when: "branch !~ '.+'" - task: - jobs: - - name: Linux multi-arch manifests - commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C typha push-manifests-with-tag CONFIRM=true; fi + - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C typha cd push-manifests-with-tag CONFIRM=true; fi diff --git a/.semaphore/push-images/whisker-backend.yml b/.semaphore/push-images/whisker-backend.yml index 1e1841f6b1e..7abdb32cf34 100644 --- a/.semaphore/push-images/whisker-backend.yml +++ b/.semaphore/push-images/whisker-backend.yml @@ -4,10 +4,8 @@ agent: machine: type: f1-standard-2 os_image: ubuntu2204 - execution_time_limit: minutes: 60 - global_job_config: env_vars: - name: DEV_REGISTRIES @@ -24,9 +22,9 @@ global_job_config: - echo $DOCKER_TOKEN | docker login --username "$DOCKER_USER" --password-stdin - echo $QUAY_TOKEN | docker login --username "$QUAY_USER" --password-stdin quay.io - export BRANCH_NAME=$SEMAPHORE_GIT_BRANCH - + - ssh-add ~/.ssh/id_rsa blocks: - - name: Publish whisker backend images + - name: Publish whisker backend images and multi-arch manifests dependencies: [] skip: when: "branch !~ '.+'" @@ -34,14 +32,4 @@ blocks: jobs: - name: Linux multi-arch commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C whisker-backend cd CONFIRM=true; fi - - name: Publish whisker backend multi-arch manifests - dependencies: - - Publish whisker backend images - skip: - when: "branch !~ '.+'" - task: - jobs: - - name: Linux multi-arch manifests - commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C whisker-backend push-manifests-with-tag CONFIRM=true; fi + - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C whisker-backend cd push-manifests-with-tag CONFIRM=true; fi diff --git a/.semaphore/push-images/whisker.yml b/.semaphore/push-images/whisker.yml index fd5a150f65d..e108e280593 100644 --- a/.semaphore/push-images/whisker.yml +++ b/.semaphore/push-images/whisker.yml @@ -4,10 +4,8 @@ agent: machine: type: f1-standard-2 os_image: ubuntu2204 - execution_time_limit: minutes: 60 - global_job_config: env_vars: - name: DEV_REGISTRIES @@ -24,9 +22,9 @@ global_job_config: - echo $DOCKER_TOKEN | docker login --username "$DOCKER_USER" --password-stdin - echo $QUAY_TOKEN | docker login --username "$QUAY_USER" --password-stdin quay.io - export BRANCH_NAME=$SEMAPHORE_GIT_BRANCH - + - ssh-add ~/.ssh/id_rsa blocks: - - name: Publish whisker images + - name: Publish whisker images and multi-arch manifests dependencies: [] skip: when: "branch !~ '.+'" @@ -34,14 +32,4 @@ blocks: jobs: - name: Linux multi-arch commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C whisker cd CONFIRM=true; fi - - name: Publish whisker multi-arch manifests - dependencies: - - Publish whisker images - skip: - when: "branch !~ '.+'" - task: - jobs: - - name: Linux multi-arch manifests - commands: - - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C whisker push-manifests-with-tag CONFIRM=true; fi + - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make -C whisker cd push-manifests-with-tag CONFIRM=true; fi diff --git a/.semaphore/release/cut-branch.yml b/.semaphore/release/cut-branch.yml index 5b462d93b0d..947d2e02384 100644 --- a/.semaphore/release/cut-branch.yml +++ b/.semaphore/release/cut-branch.yml @@ -9,13 +9,12 @@ execution_time_limit: global_job_config: secrets: - # Secret for GitHub access - name: marvin-github-ssh-private-key prologue: commands: - chmod 0600 ~/.keys/* - - ssh-add ~/ - # Unshallow the git repository to get latest tags + - ssh-add ~/.keys/* + - checkout - retry git fetch --quiet --unshallow blocks: @@ -24,8 +23,4 @@ blocks: jobs: - name: Cut Branch commands: - - ./bin/release branch cut - prologue: - commands: - - cd release - - make build + - make create-release-branch diff --git a/.semaphore/release/hashrelease.yml b/.semaphore/release/hashrelease.yml index 63d89809176..7bec02e3c5c 100644 --- a/.semaphore/release/hashrelease.yml +++ b/.semaphore/release/hashrelease.yml @@ -8,20 +8,25 @@ execution_time_limit: hours: 6 global_job_config: + priority: + - value: 90 + when: "branch =~ '.*'" secrets: - name: oss-release-secrets # Github SSH secret for pulling private repositories. - name: private-repo # Secret for GitHub API access. - name: marvin-github-token - # Secret for pushing to the docs box. - - name: docs-ssh # Secret for image registries - name: quay-hashrelease - name: docker - name: iss-image-scanning # Secrets for Slack notifications - name: releasebot-slack + # OIDC-related credentials + - name: oidc-credentials-gcp-projectcalico-org + - name: oidc-credentials-gcp-oss-hashrelease-publish + prologue: commands: - chmod 0600 ~/.keys/* @@ -32,15 +37,22 @@ global_job_config: # Log in to container registries needed for release. - echo $DOCKER_TOKEN | docker login --username "$DOCKER_USER" --password-stdin - echo $QUAY_TOKEN | docker login --username "$QUAY_USER" --password-stdin quay.io + - echo $QUAY_TOKEN | helm registry login --username "$QUAY_USER" --password-stdin quay.io # Credentials for accessing gcloud - - gcloud auth activate-service-account --key-file=${HASHRELEASE_SERVER_CREDENTIALS} - - export GOOGLE_APPLICATION_CREDENTIALS=$HOME/secrets/gcr-credentials.json - - gcloud auth activate-service-account --key-file=${GOOGLE_APPLICATION_CREDENTIALS} - # Manually log in to GCR until we can test the gcr credentials helper - - cat ${GOOGLE_APPLICATION_CREDENTIALS} | docker login -u _json_key --password-stdin https://gcr.io - - cat ${GOOGLE_APPLICATION_CREDENTIALS} | docker login -u _json_key --password-stdin https://eu.gcr.io - - cat ${GOOGLE_APPLICATION_CREDENTIALS} | docker login -u _json_key --password-stdin https://asia.gcr.io - - cat ${GOOGLE_APPLICATION_CREDENTIALS} | docker login -u _json_key --password-stdin https://us.gcr.io + - echo "${SEMAPHORE_OIDC_TOKEN}" > "${SEMAPHORE_TOKEN_FILE_PATH}" + - | + gcloud iam workload-identity-pools create-cred-config \ + $OIDC_GCP_POOL_URI \ + --quiet \ + --service-account="${OIDC_SERVICE_ACCOUNT_EMAIL}" \ + --service-account-token-lifetime-seconds="${OIDC_SERVICE_ACCOUNT_LIFETIME}" \ + --output-file="${GOOGLE_APPLICATION_CREDENTIALS}" \ + --credential-source-file="${SEMAPHORE_TOKEN_FILE_PATH}" \ + --credential-source-type="text" + - gcloud auth login --quiet --cred-file="${GOOGLE_APPLICATION_CREDENTIALS}" + - gcloud config set project --quiet "${OIDC_GCP_PROJECT_NAME}" + # Configure docker to use the gcloud credential helper + - gcloud auth configure-docker --quiet blocks: - name: Publish hashrelease diff --git a/.semaphore/release/post-release.yml b/.semaphore/release/post-release.yml new file mode 100644 index 00000000000..1764a0504dd --- /dev/null +++ b/.semaphore/release/post-release.yml @@ -0,0 +1,25 @@ +version: v1.0 +name: Post release +agent: + machine: + type: f1-standard-2 + os_image: ubuntu2204 +execution_time_limit: + minutes: 15 +blocks: + - name: "Release cut validation" + task: + secrets: + - name: marvin-github-token + - name: marvin-github-ssh-private-key + prologue: + commands: + - chmod 0600 ~/.keys/* + - ssh-add ~/.keys/* + - checkout + - retry git fetch --quiet --unshallow + - export GITHUB_TOKEN=${MARVIN_GITHUB_TOKEN} + jobs: + - name: "Post release checks" + commands: + - if [ -z "${SEMAPHORE_GIT_PR_NUMBER}" ]; then make postrelease-checks; fi diff --git a/.semaphore/release/release.yml b/.semaphore/release/release.yml index 563844274b9..b11dad5c9c5 100644 --- a/.semaphore/release/release.yml +++ b/.semaphore/release/release.yml @@ -3,7 +3,7 @@ name: Publish official release agent: machine: type: f1-standard-4 - os_image: ubuntu2204 + os_image: ubuntu2404 execution_time_limit: hours: 14 blocks: @@ -30,6 +30,7 @@ blocks: # Log in to container registries needed for release. - echo $DOCKER_TOKEN | docker login --username "$DOCKER_USER" --password-stdin - echo $QUAY_TOKEN | docker login --username "$QUAY_USER" --password-stdin quay.io + - echo $QUAY_TOKEN | helm registry login --username "$QUAY_USER" --password-stdin quay.io # Credentials for accessing gcloud, needed to push images to gcr - export GOOGLE_APPLICATION_CREDENTIALS=$HOME/secrets/gcr-credentials.json - gcloud auth activate-service-account --key-file=${GOOGLE_APPLICATION_CREDENTIALS} @@ -71,7 +72,7 @@ blocks: - gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS # Install more tools - sudo apt update - - sudo apt install -y moreutils patchelf jq + - sudo apt install -y moreutils patchelf jq devscripts jobs: - name: "Build Openstack Packages" execution_time_limit: diff --git a/.semaphore/run-and-monitor b/.semaphore/run-and-monitor index d1cec2a8f0e..854cdf55c8e 100755 --- a/.semaphore/run-and-monitor +++ b/.semaphore/run-and-monitor @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# Copyright (c) 2022 Tigera, Inc. All rights reserved. +# Copyright (c) 2022-2025 Tigera, Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ my_dir="$(dirname $0)" repo_dir="${my_dir}/.." artifacts_dir="$repo_dir/artifacts" log_file_name="$1" -log_file="${artifacts_dir}/$1" +log_file="${artifacts_dir}/$log_file_name" mkdir -p "${artifacts_dir}" diff --git a/.semaphore/semaphore-scheduled-builds.yml b/.semaphore/semaphore-scheduled-builds.yml index bcc9f51a1c0..d3642edd1f8 100644 --- a/.semaphore/semaphore-scheduled-builds.yml +++ b/.semaphore/semaphore-scheduled-builds.yml @@ -1,5 +1,6 @@ -# !! WARNING, DO NOT EDIT !! This file is generated from semaphore.yml.tpl. -# To update, modify the template and then run 'make gen-semaphore-yaml'. +# !! WARNING, DO NOT EDIT !! This file is generated from the templates +# in /.semaphore/semaphore.yml.d. To update, modify the relevant +# template and then run 'make gen-semaphore-yaml'. version: v1.0 name: Calico execution_time_limit: @@ -16,23 +17,122 @@ auto_cancel: global_job_config: secrets: - name: docker-hub + - name: google-service-account-for-gce + env_vars: + - name: GOOGLE_PROJECT + value: unique-caldron-775 + - name: GCS_BUILD_CACHE_BUCKET + value: calico-transient-build-artifacts-europe-west3 + - name: BUILDKIT_PROGRESS + value: plain + - name: SEMAPHORE_AGENT_UPLOAD_JOB_LOGS + value: when-trimmed prologue: commands: - - checkout - - export REPO_DIR="$(pwd)" - - mkdir artifacts - # Semaphore is doing shallow clone on a commit without tags. - # unshallow it for GIT_VERSION:=$(shell git describe --tags --dirty --always) - - if [[ "${SEMAPHORE_BLOCK_NAME}" != "Prerequisites" ]]; then retry git fetch --unshallow; fi + - export CALICO_DIR_NAME=${SEMAPHORE_GIT_DIR} + - export GCS_CACHE_DIR=gs://$GCS_BUILD_CACHE_BUCKET/cache/${CALICO_DIR_NAME} + - export GCS_WORKFLOW_DIR=gs://$GCS_BUILD_CACHE_BUCKET/workflow/${SEMAPHORE_WORKFLOW_ID} + - export GOOGLE_APPLICATION_CREDENTIALS=$HOME/secrets/secret.google-service-account-key.json + - |- + if [[ $(uname -m) != "x86_64" || -n "$ARCH" ]]; then + echo "Non-x86_64 build: $(uname -m) / $ARCH"; + export BUILD_CACHE_GROUP=$BUILD_CACHE_GROUP-$(uname -m)-$ARCH; + fi + - gcloud auth activate-service-account --key-file="$GOOGLE_APPLICATION_CREDENTIALS" || true + - gcloud config set project ${GOOGLE_PROJECT} || true + # Try to restore the working copy from the cache, if available. Then, + # for PRs, also try to restore the build cache. (We skip this on branch + # builds to ensure branches build cleanly.) + - sudo apt-get install -y zstd || true + - restored_wc_cache=false + - |- + if [[ -n "${SEMAPHORE_GIT_BRANCH}" && -z "$SKIP_CACHE_RESTORE" ]]; then + echo "Looking for cached working copy for branch ${SEMAPHORE_GIT_BRANCH}" + gcs_branch_cache_dir="${GCS_CACHE_DIR}/${SEMAPHORE_GIT_BRANCH}" + if gcloud storage cp "$gcs_branch_cache_dir/working-copy.tar.zstd" /tmp/working-copy.tar.zstd; then + echo "Restoring cache for branch ${SEMAPHORE_GIT_BRANCH}" + tar --use-compress-program=zstd -xf /tmp/working-copy.tar.zstd & tar_pid=$! + downloaded_build_cache=false + if [[ -n "$BUILD_CACHE_GROUP" && -n "${SEMAPHORE_GIT_PR_NUMBER}" ]]; then + if gcloud storage cp "$gcs_branch_cache_dir/build-cache-${BUILD_CACHE_GROUP}.tar.zstd" /tmp/build-cache.tar.zstd; then + echo "Found build cache for group ${BUILD_CACHE_GROUP}" + downloaded_build_cache=true + fi + fi + if wait $tar_pid; then + restored_wc_cache=true + else + echo "ERROR: Failed to extract working copy" + restored_wc_cache=false + fi + if ${downloaded_build_cache}; then + echo "Extracting build cache" + tar --use-compress-program=zstd -xf /tmp/build-cache.tar.zstd -C . + rm /tmp/build-cache.tar.zstd + fi + cd "${CALICO_DIR_NAME}" + fi + fi || true + - |- + if [[ ${restored_wc_cache} != true && -z "$SKIP_CHECKOUT" ]]; then + echo "No cache restored, doing a fresh checkout..." + cd $HOME + rm -rf "${CALICO_DIR_NAME}" + git clone --filter=blob:none $SEMAPHORE_GIT_URL $CALICO_DIR_NAME + cd "${CALICO_DIR_NAME}" || exit 1 + fi + - |- + if [[ -z "$SKIP_CHECKOUT" ]]; then + git fetch origin --tags --prune-tags ${SEMAPHORE_GIT_SHA} + if [ "$SEMAPHORE_GIT_REF_TYPE" = "branch" ]; then + git checkout ${SEMAPHORE_GIT_SHA} -B "${SEMAPHORE_GIT_BRANCH}" + else + git checkout ${SEMAPHORE_GIT_SHA} + fi + CALICO_DIR="$(pwd)" + export CALICO_DIR + mkdir -p artifacts + fi - echo $DOCKERHUB_PASSWORD | docker login --username "$DOCKERHUB_USERNAME" --password-stdin epilogue: commands: - - cd "$REPO_DIR" + - cd $HOME + - |- + if [[ -z "${SEMAPHORE_GIT_PR_NUMBER}" && -n "$BUILD_CACHE_GROUP" && "$STORE_BUILD_CACHE" = "true" ]]; then + echo "On branch, storing build cache group ${BUILD_CACHE_GROUP}" + tar --use-compress-program="zstd -3" -cf /tmp/build-cache.tar.zstd go ${CALICO_DIR_NAME}/.go-pkg-cache + gcs_branch_cache_dir="${GCS_CACHE_DIR}/${SEMAPHORE_GIT_BRANCH}" + gcloud storage cp /tmp/build-cache.tar.zstd "$gcs_branch_cache_dir/build-cache-${BUILD_CACHE_GROUP}.tar.zstd" + fi + - cd "$CALICO_DIR" - .semaphore/publish-artifacts promotions: # Manual promotion for publishing a hashrelease. - name: Publish hashrelease pipeline_file: release/hashrelease.yml + parameters: + env_vars: + - required: true + options: + - "true" + - "false" + default_value: "false" + description: "Build container images. If 'true', container images will be built as part of the hashrelease process." + name: BUILD_CONTAINER_IMAGES + - required: true + options: + - "true" + - "false" + default_value: "false" + description: "create tarball of images. If 'true', a tarball of images will be added to the release.tgz. Requires BUILD_CONTAINER_IMAGES to also be 'true'." + name: ARCHIVE_IMAGES + - required: true + options: + - "true" + - "false" + default_value: "false" + description: "publish images. If 'true', images will be pushed to the container registry/registries as part of the hashrelease process. Requires BUILD_CONTAINER_IMAGES to also be 'true'." + name: PUBLISH_IMAGES # Manual promotion for publishing a release. - name: Publish official release pipeline_file: release/release.yml @@ -118,24 +218,158 @@ promotions: blocks: - name: Check images availability run: - when: "true or change_in(['/charts/', '/manifests/'], {pipeline_file: 'ignore'})" + when: "true or (branch !~ 'build-v.*' and change_in(['/charts/', '/manifests/'], {pipeline_file: 'ignore'}))" dependencies: [] task: jobs: - name: Check images availability commands: - make check-images-availability - - name: Prerequisites + - name: Check SemaphoreCI files + run: + when: >- + true or + change_in(['/.semaphore', '/hack', '/Makefile', '/lib.Makefile', '/metadata.mk'], {pipeline_file: 'track'}) + dependencies: [] + task: + jobs: + - name: Check semaphore files + commands: + - make gen-semaphore-yaml + - make check-dirty + - name: Check YAML files + run: + when: >- + true or + change_in(['/**/*.yml', '/**/*.yaml', '/Makefile', '/lib.Makefile', '/metadata.mk'], {pipeline_file: 'track'}) dependencies: [] task: + jobs: + - name: Check YAML files + commands: + - make yaml-lint + - name: Check Go + run: + when: >- + true or + change_in(['/go.mod', '/go.sum', '/**/*.go', '/**/deps.txt', '/Makefile', '/lib.Makefile', '/metadata.mk'], {pipeline_file: 'ignore'}) + dependencies: [] + task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "preflight-go-checks" agent: machine: - type: f1-standard-2 + type: f1-standard-4 os_image: ubuntu2204 + jobs: + - name: Check Go files + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + commands: + - make check-ginkgo-v2 + - make check-go-mod + - make verify-go-mods + - make gen-deps-files + - make go-vet + - >- + if [ -n "$SEMAPHORE_GIT_PR_NUMBER" ]; then + make fix-changed + else + make fix-all + fi + - make check-dirty + - name: Check Python + run: + when: >- + true or + change_in(['/networking-calico', '/lib.Makefile', '/metadata.mk'], {pipeline_file: 'ignore'}) + dependencies: [] + task: + jobs: + - name: Check Python files + commands: + - make -C networking-calico fmtpy + - make -C networking-calico flake8 + - make check-dirty + - name: Check language/Dockerfiles + run: + when: >- + true or + change_in(['/**/*'], {pipeline_file: 'track', exclude: ['libbpf', '.[a-z]*']}) + dependencies: [] + task: + jobs: + - name: Check language/Dockerfiles + commands: + # Both of these are very quick grep checks so we combine them into one job. + - make check-language + - make check-dockerfiles + - name: Check protobufs + run: + when: >- + true or + change_in(['/**/*.pb.go', '/**/*.proto', '/Makefile', '/lib.Makefile', '/metadata.mk'], {pipeline_file: 'ignore'}) + dependencies: [] + task: + jobs: + - name: Check protobufs + commands: + - make protobuf fix-changed + - make check-dirty + - name: Manifests and API docs + run: + when: >- + true or + change_in(['/api', '/manifests', '/charts', '/libcalico-go/lib/apis', '/libcalico-go/config', '/libcalico-go/Makefile', '/felix/config', '/felix/docs', '/felix/Makefile', '/goldmane', '/Makefile', '/lib.Makefile', '/metadata.mk'], {pipeline_file: 'ignore'}) + dependencies: [] + task: + jobs: + - name: Check manifests and API docs + commands: + - make -C lib gen-files + - make -C api gen-files + - make -C libcalico-go gen-files + - make -C felix gen-files + - make -C goldmane gen-files + - make gen-manifests + - make fix-changed + - make check-ocp-no-crds + - make check-dirty + - name: Store working copy + run: + when: "branch =~ '.*'" + dependencies: [] + task: + env_vars: + - name: SKIP_CACHE_RESTORE + value: "true" + jobs: + - name: Save working copy to cache + commands: + - cd .. + - tar --use-compress-program="zstd -3" -cf /tmp/working-copy.tar.zstd $CALICO_DIR_NAME + - gcs_branch_cache_dir="${GCS_CACHE_DIR}/${SEMAPHORE_GIT_BRANCH}" + - gcloud storage cp /tmp/working-copy.tar.zstd "$gcs_branch_cache_dir/working-copy.tar.zstd" + - name: Prerequisites + skip: + # Always skip, this is a dummy job used to group prerequisite checks. + when: "true" + dependencies: + - Check SemaphoreCI files + - Check YAML files + - Check Go + - Check Python + - Check language/Dockerfiles + - Check protobufs + - Manifests and API docs + - Store working copy + task: jobs: - name: Pre-flight checks commands: - - make ci-preflight-checks + - "true" - name: API run: when: "true or change_in(['/metadata.mk', '/lib.Makefile', '/api/'], {pipeline_file: 'ignore', exclude: ['/**/.gitignore', '/**/README.md', '/**/LICENSE']})" @@ -148,10 +382,10 @@ blocks: commands: - cd api jobs: - - name: make ci + - name: "API: CI" commands: - ../.semaphore/run-and-monitor make-ci.log make ci - - name: apiserver + - name: API Server run: when: "true" execution_time_limit: @@ -159,14 +393,20 @@ blocks: dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "apiserver" prologue: commands: - cd apiserver jobs: - - name: make ci + - name: "API Server: CI" commands: - ../.semaphore/run-and-monitor make-ci.log make ci - - name: Build binary + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: "API Server: Build" matrix: - env_var: ARCH values: @@ -175,7 +415,7 @@ blocks: - s390x commands: - ../.semaphore/run-and-monitor image-$ARCH.log make build ARCH=$ARCH - - name: app-policy + - name: Application Layer Policy run: when: "true" dependencies: @@ -185,7 +425,7 @@ blocks: commands: - cd app-policy jobs: - - name: app-policy tests + - name: "Application Layer Policy: CI" commands: - ../.semaphore/run-and-monitor ci.log make ci - name: calicoctl @@ -194,32 +434,44 @@ blocks: dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "calicoctl" prologue: commands: - cd calicoctl jobs: - - name: calicoctl tests + - name: "calicoctl: CI" commands: - ../.semaphore/run-and-monitor ci.log make ci - - name: cni-plugin + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: CNI Plugin run: when: "true" dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "cni-plugin" prologue: commands: - cd cni-plugin jobs: - - name: cni-plugin tests + - name: "CNI Plugin: CI" commands: - ../.semaphore/run-and-monitor ci.log make ci - - name: build windows cni-plugin images + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: "CNI Plugin: build windows images" commands: - ../.semaphore/run-and-monitor ci.log make image-windows - - name: "cni-plugin: Windows" + - name: "CNI Plugin: Windows" run: - when: "true" + when: "false or true" dependencies: - Prerequisites task: @@ -235,9 +487,7 @@ blocks: - export AZURE_CLIENT_SECRET=$AZ_SP_PASSWORD - export REPORT_DIR=/home/semaphore/calico/process/testing/winfv-cni-plugin/report - export LOGS_DIR=~/fv.log - - export SHORT_WORKFLOW_ID=$(echo ${SEMAPHORE_WORKFLOW_ID} | sha256sum | cut -c -8) - - export CLUSTER_NAME=sem-${SEMAPHORE_PROJECT_NAME}-pr${SEMAPHORE_GIT_PR_NUMBER}-${SHORT_WORKFLOW_ID} - - export SUFFIX=${CLUSTER_NAME} + - export RG_PREFIX=${USER}-win-cni-${SEMAPHORE_GIT_SHA:0:4}-pr${SEMAPHORE_GIT_PR_NUMBER} - cd cni-plugin - ../.semaphore/run-and-monitor build.log make bin/windows/calico.exe bin/windows/calico-ipam.exe bin/windows/win-fv.exe epilogue: @@ -245,35 +495,42 @@ blocks: commands: - artifact push job ${REPORT_DIR} --destination semaphore/test-results || true - artifact push job ${LOGS_DIR} --destination semaphore/logs || true - - cd ~/calico/process/testing/winfv-cni-plugin/aso && make dist-clean + - cd ~/calico/process/testing/aso && make dist-clean env_vars: + - name: BUILD_CACHE_GROUP + value: "cni-plugin" - name: SEMAPHORE_ARTIFACT_EXPIRY value: 2w - name: AZURE_LOCATION value: eastus2 - name: KUBE_VERSION - value: v1.29.7 + value: v1.33.7 jobs: - - name: Containerd - Windows FV - overlay + - name: "CNI Plugin: Windows Containerd FV - overlay" execution_time_limit: minutes: 60 commands: - export BACKEND=overlay - - export AZURE_RESOURCE_GROUP=${USER}-capz-win-cni-${SEMAPHORE_WORKFLOW_ID:0:8}-${BACKEND}-rg - - ../.semaphore/run-and-monitor win-fv-containerd.log ./.semaphore/run-win-fv.sh - - name: Containerd - Windows FV - l2bridge + - export AZURE_RESOURCE_GROUP=${RG_PREFIX}-${BACKEND}-rg + - echo AZURE_RESOURCE_GROUP=${AZURE_RESOURCE_GROUP} + - ../.semaphore/run-and-monitor win-fv-containerd.log ~/calico/process/testing/winfv-cni-plugin/run-win-fv.sh + - name: "CNI Plugin: Windows Containerd FV - l2bridge" execution_time_limit: minutes: 60 commands: - export BACKEND=l2bridge - - export AZURE_RESOURCE_GROUP=${USER}-capz-win-cni-${SEMAPHORE_WORKFLOW_ID:0:8}-${BACKEND}-rg - - ../.semaphore/run-and-monitor win-fv-containerd.log ./.semaphore/run-win-fv.sh + - export AZURE_RESOURCE_GROUP=${RG_PREFIX}-${BACKEND}-rg + - echo AZURE_RESOURCE_GROUP=${AZURE_RESOURCE_GROUP} + - ../.semaphore/run-and-monitor win-fv-containerd.log ~/calico/process/testing/winfv-cni-plugin/run-win-fv.sh - name: confd run: when: "true" dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "confd" prologue: commands: - cd confd @@ -283,20 +540,10 @@ blocks: minutes: 60 commands: - ../.semaphore/run-and-monitor ci.log make ci - - name: crypto - run: - when: "true" - dependencies: - - Prerequisites - task: - prologue: - commands: - - cd crypto - jobs: - - name: "crypto tests" - commands: - - ../.semaphore/run-and-monitor ci.log make ci - - name: e2e script & pipeline validations + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: E2E script & pipeline validations run: when: "true or change_in(['/.semaphore/end-to-end/'], {pipeline_file: 'ignore', exclude: ['/**/.gitignore', '/**/README.md', '/**/LICENSE']})" execution_time_limit: @@ -305,24 +552,30 @@ blocks: - Prerequisites task: jobs: - - name: Test Pipeline files are valid yaml + - name: "E2E script & pipeline validations: validate yaml" commands: - ./.semaphore/end-to-end/scripts/test_scripts/e2e-validation.sh - - name: e2e tests + - name: E2E tests run: when: "true" dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "e2e-tests" agent: machine: type: f1-standard-4 - os_image: ubuntu2004 + os_image: ubuntu2204 jobs: - - name: Conformance e2e tests + - name: "E2E tests: Conformance" commands: - .semaphore/run-and-monitor e2e-test.log make e2e-test - - name: envoy-gateway + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: Envoy Gateway run: when: "true or change_in(['/metadata.mk', '/lib.Makefile', '/third_party/envoy-gateway'], {pipeline_file: 'ignore', exclude: ['/**/.gitignore', '/**/README.md', '/**/LICENSE']})" execution_time_limit: @@ -334,10 +587,10 @@ blocks: commands: - cd third_party/envoy-gateway jobs: - - name: make ci + - name: "Envoy Gateway: CI" commands: - ../../.semaphore/run-and-monitor make-ci.log make ci - - name: Build binary + - name: "Envoy Gateway: build multi-arch binaries" matrix: - env_var: ARCH values: @@ -346,7 +599,7 @@ blocks: - s390x commands: - ../../.semaphore/run-and-monitor image-$ARCH.log make build ARCH=$ARCH - - name: envoy-proxy + - name: Envoy Proxy run: when: "true or change_in(['/metadata.mk', '/lib.Makefile', '/third_party/envoy-proxy'], {pipeline_file: 'ignore', exclude: ['/**/.gitignore', '/**/README.md', '/**/LICENSE']})" execution_time_limit: @@ -358,10 +611,10 @@ blocks: commands: - cd third_party/envoy-proxy jobs: - - name: make ci + - name: "Envoy Proxy: CI" commands: - ../../.semaphore/run-and-monitor make-ci.log make ci - - name: Build binary + - name: "Envoy Proxy: build multi-arch binaries" matrix: - env_var: ARCH values: @@ -370,7 +623,7 @@ blocks: - s390x commands: - ../../.semaphore/run-and-monitor image-$ARCH.log make build ARCH=$ARCH - - name: envoy-ratelimit + - name: "Envoy Ratelimit" run: when: "true or change_in(['/metadata.mk', '/lib.Makefile', '/third_party/envoy-ratelimit'], {pipeline_file: 'ignore', exclude: ['/**/.gitignore', '/**/README.md', '/**/LICENSE']})" execution_time_limit: @@ -382,10 +635,10 @@ blocks: commands: - cd third_party/envoy-ratelimit jobs: - - name: make ci + - name: "Envoy Ratelimit: CI" commands: - ../../.semaphore/run-and-monitor make-ci.log make ci - - name: Build binary + - name: "Envoy Ratelimit: build multi-arch binaries" matrix: - env_var: ARCH values: @@ -400,33 +653,62 @@ blocks: dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "felix" prologue: commands: - cd felix - - cache restore go-pkg-cache - - cache restore go-mod-cache + - gcloud config set project unique-caldron-775 + - gcloud auth activate-service-account --key-file=$HOME/secrets/secret.google-service-account-key.json + secrets: + - name: google-service-account-for-gce jobs: - - name: Build and run UT, k8sfv + - name: "Felix: Build and cache FV prerequisites" + priority: + # This job is on the critical path so we increase its priority. + - value: 51 + when: "pull_request =~ '.+'" + env_vars: + - name: STORE_BUILD_CACHE + value: "true" execution_time_limit: - minutes: 60 + minutes: 20 + commands: + - make build image ut-bpf-prereqs fv-prereqs + - ./.semaphore/cache-test-artifacts + - name: "Felix: UT" + priority: + # This job is on the critical path so we increase its priority. + - value: 51 + when: "pull_request =~ '.+'" + execution_time_limit: + minutes: 30 commands: - - make build image fv-prereqs - - "cache store bin-${SEMAPHORE_GIT_SHA} bin" - - "cache store fv.test-${SEMAPHORE_GIT_SHA} fv/fv.test" - - cache store go-pkg-cache .go-pkg-cache - - "cache store go-mod-cache ${HOME}/go/pkg/mod/cache" - - docker save -o /tmp/calico-felix-test.tar calico/felix-test:latest-amd64 - - "cache store felix-image-${SEMAPHORE_GIT_SHA} /tmp/calico-felix-test.tar" - - docker save -o /tmp/felixtest-typha.tar felix-test/typha:latest-amd64 - - "cache store felixtest-typha-image-${SEMAPHORE_GIT_SHA} /tmp/felixtest-typha.tar" - ../.semaphore/run-and-monitor ut.log make ut + - name: "Felix: k8sfv" + priority: + # This job is on the critical path so we increase its priority. + - value: 51 + when: "pull_request =~ '.+'" + execution_time_limit: + minutes: 15 + commands: - ../.semaphore/run-and-monitor k8sfv-typha.log make k8sfv-test JUST_A_MINUTE=true USE_TYPHA=true - ../.semaphore/run-and-monitor k8sfv-no-typha.log make k8sfv-test JUST_A_MINUTE=true USE_TYPHA=false - - name: Static checks + - name: "Felix: Static checks" + priority: + # This job is on the critical path so we increase its priority. + - value: 51 + when: "pull_request =~ '.+'" execution_time_limit: - minutes: 60 + minutes: 15 commands: - ../.semaphore/run-and-monitor static-checks.log make static-checks + epilogue: + always: + commands: + - 'test-results publish --name "${SEMAPHORE_JOB_NAME}" ./report/*.xml || true' - name: "Felix: multi-arch build" run: when: "true" @@ -436,10 +718,8 @@ blocks: prologue: commands: - cd felix - - cache restore go-pkg-cache - - cache restore go-mod-cache jobs: - - name: Build binary + - name: "Felix: build multi-arch binaries" matrix: - env_var: ARCH values: @@ -461,10 +741,8 @@ blocks: prologue: commands: - cd felix - - cache restore go-pkg-cache - - cache restore go-mod-cache jobs: - - name: Build binary + - name: "Felix: build arm64 binaries" commands: - ../.semaphore/run-and-monitor build-arm64.log make build ARCH=arm64 - name: "Felix: Build Windows binaries" @@ -473,12 +751,15 @@ blocks: dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "felix" jobs: - - name: Build Windows binaries + - name: "Felix: Build Windows binaries" commands: - cd felix - make bin/calico-felix.exe fv/win-fv.exe - - name: "Felix: Windows FV capz" + - name: "Felix: Windows FV" run: when: "false or true" dependencies: ["Felix: Build Windows binaries"] @@ -494,174 +775,181 @@ blocks: - export AZURE_TENANT_ID=$AZ_TENANT_ID - export AZURE_CLIENT_ID=$AZ_SP_ID - export AZURE_CLIENT_SECRET=$AZ_SP_PASSWORD - - export AZURE_SUBSCRIPTION_ID_B64="$(echo -n "$AZ_SUBSCRIPTION_ID" | base64 | tr -d '\n')" - - export AZURE_TENANT_ID_B64="$(echo -n "$AZ_TENANT_ID" | base64 | tr -d '\n')" - - export AZURE_CLIENT_ID_B64="$(echo -n "$AZ_SP_ID" | base64 | tr -d '\n')" - - export AZURE_CLIENT_SECRET_B64="$(echo -n "$AZ_SP_PASSWORD" | base64 | tr -d '\n')" + - export REPORT_DIR=/home/semaphore/calico/process/testing/winfv-felix/report + - export LOGS_DIR=~/fv.log + - export RG_PREFIX=${USER}-win-felix-${SEMAPHORE_GIT_SHA:0:4}-pr${SEMAPHORE_GIT_PR_NUMBER} - cd felix epilogue: always: commands: - - artifact push job ${REPORT_DIR} --destination test-results || true + - artifact push job ${REPORT_DIR} --destination semaphore/test-results || true + - artifact push job ${LOGS_DIR} --destination semaphore/logs || true + - cd ~/calico/process/testing/aso && make dist-clean env_vars: - - name: FV_PROVISIONER - value: "capz" + - name: BUILD_CACHE_GROUP + value: "felix" - name: FV_TYPE value: "calico-felix" - name: SEMAPHORE_ARTIFACT_EXPIRY value: 2w - name: CONTAINERD_VERSION - value: 1.7.22 + value: 1.7.28 jobs: - - name: CAPZ - Windows FV + - name: "Felix: Windows FV" commands: - - ./.semaphore/run-win-fv - - name: "Felix: FV Tests" + - export AZURE_RESOURCE_GROUP=${RG_PREFIX}-rg + - echo AZURE_RESOURCE_GROUP=${AZURE_RESOURCE_GROUP} + - ../.semaphore/run-and-monitor win-fv-felix.log ~/calico/process/testing/winfv-felix/run-win-fv.sh + - name: "Felix: iptables tests on Ubuntu 22.04" run: when: "true" dependencies: - "Felix: Build" task: - agent: - machine: - type: f1-standard-4 - os_image: ubuntu2204 - prologue: - commands: - - cd felix - - cache restore go-pkg-cache - - cache restore go-mod-cache - - "cache restore bin-${SEMAPHORE_GIT_SHA}" - - "cache restore fv.test-${SEMAPHORE_GIT_SHA}" - - "cache restore felix-image-${SEMAPHORE_GIT_SHA}" - - "cache restore felixtest-typha-image-${SEMAPHORE_GIT_SHA}" - - |- - if [ -s /etc/docker/daemon.json ]; then - sudo sed -i '$d' /etc/docker/daemon.json && sudo sed -i '$s/$/,/' /etc/docker/daemon.json && sudo bash -c ' cat >> /etc/docker/daemon.json << EOF - "ipv6": true, - "fixed-cidr-v6": "2001:db8:1::/64" - } - EOF - ' ; else sudo bash -c ' cat > /etc/docker/daemon.json << EOF - { - "ipv6": true, - "fixed-cidr-v6": "2001:db8:1::/64" - } - EOF - ' ; fi - - sudo systemctl restart docker - # Load in the docker images pre-built by the build job. - - docker load -i /tmp/calico-felix-test.tar - - docker tag calico/felix-test:latest-amd64 felix-test:latest-amd64 - - rm /tmp/calico-felix-test.tar - - docker load -i /tmp/felixtest-typha.tar - - docker tag felix-test/typha:latest-amd64 typha:latest-amd64 - - rm /tmp/felixtest-typha.tar - # Pre-loading the IPIP module prevents a flake where the first felix to use IPIP loads the module and - # routing in that first felix container chooses different source IPs than the tests are expecting. - - sudo modprobe ipip jobs: - - name: FV Test matrix + - name: "Felix: iptables tests on Ubuntu 22.04" execution_time_limit: - minutes: 120 + minutes: 60 + env_vars: + - name: FELIX_TEST_GROUP + value: "22.04-ipt" commands: - - make check-wireguard - - ../.semaphore/run-and-monitor fv-${SEMAPHORE_JOB_INDEX}.log make fv-no-prereqs FV_BATCHES_TO_RUN="${SEMAPHORE_JOB_INDEX}" FV_NUM_BATCHES=${SEMAPHORE_JOB_COUNT} - parallelism: 3 - - name: nftables FV Test matrix + - source felix/.semaphore/fv-prologue + - export NUM_FV_BATCHES=16 + - .semaphore/vms/run-tests-on-vms ${VM_PREFIX} + epilogue: + always: + commands: + - source felix/.semaphore/fv-epilogue + secrets: + - name: google-service-account-for-gce + - name: "Felix: nftables tests on Ubuntu 22.04" + run: + when: "true" + dependencies: + - "Felix: Build" + task: + jobs: + - name: "Felix: nftables tests on Ubuntu 22.04" execution_time_limit: - minutes: 120 + minutes: 60 env_vars: - - name: FELIX_FV_NFTABLES - value: "Enabled" + - name: FELIX_TEST_GROUP + value: "22.04-nft" commands: - - make check-wireguard - - ../.semaphore/run-and-monitor fv-${SEMAPHORE_JOB_INDEX}.log make fv-no-prereqs FV_BATCHES_TO_RUN="${SEMAPHORE_JOB_INDEX}" FV_NUM_BATCHES=${SEMAPHORE_JOB_COUNT} - parallelism: 3 + - source felix/.semaphore/fv-prologue + - export NUM_FV_BATCHES=16 + - .semaphore/vms/run-tests-on-vms ${VM_PREFIX} epilogue: always: commands: - - ./.semaphore/collect-artifacts - - ./.semaphore/publish-artifacts - - test-results publish /home/semaphore/calico/felix/report/fv_suite.xml --name "felix-fv-${SEMAPHORE_JOB_INDEX}" || true - - test-results publish /home/semaphore/calico/felix/report/fv_nft_suite.xml --name "felix-fv-nft-${SEMAPHORE_JOB_INDEX}" || true - - name: "Felix: BPF UT/FV tests on Ubuntu 24.04" + - source felix/.semaphore/fv-epilogue + secrets: + - name: google-service-account-for-gce + - name: "Felix: BPF tests on Ubuntu 24.04 (iptables)" run: when: "true" dependencies: - - Prerequisites + - "Felix: Build" task: - prologue: - commands: - - cd felix - - export GOOGLE_APPLICATION_CREDENTIALS=$HOME/secrets/secret.google-service-account-key.json - - export SHORT_WORKFLOW_ID=$(echo ${SEMAPHORE_WORKFLOW_ID} | sha256sum | cut -c -8) - - export ZONE=europe-west3-c - - export VM_PREFIX=sem-${SEMAPHORE_PROJECT_NAME}-${SHORT_WORKFLOW_ID}-felix-ipt- - - echo VM_PREFIX=${VM_PREFIX} - - export REPO_NAME=$(basename $(pwd)) - - export NUM_FV_BATCHES=8 - - export RUN_UT=true - - export FV_FOCUS=BPF-SAFE - - export IMAGE=ubuntu-2404-noble-amd64-v20250502a - - export UBUNTU_VERSION=noble - - export DOCKER_VERSION=5:27.5.1-1~ubuntu.24.04~noble - - mkdir artifacts - - ./.semaphore/create-test-vms ${VM_PREFIX} jobs: - - name: UT/FV tests on new kernel + - name: "Felix: BPF tests on Ubuntu 24.04" execution_time_limit: - minutes: 180 + minutes: 60 + env_vars: + - name: FELIX_TEST_GROUP + value: "bpf-24.04-ipt-with-ut" commands: - - ./.semaphore/run-tests-on-vms ${VM_PREFIX} + - source felix/.semaphore/fv-prologue + - export NUM_FV_BATCHES=60 + - .semaphore/vms/run-tests-on-vms ${VM_PREFIX} epilogue: always: commands: - - ./.semaphore/collect-artifacts-from-vms ${VM_PREFIX} - - ./.semaphore/publish-artifacts - - ./.semaphore/clean-up-vms ${VM_PREFIX} + - source felix/.semaphore/fv-epilogue secrets: - name: google-service-account-for-gce - - name: "Felix: BPF UT/FV tests on Ubuntu 22.04 (nftables)" + - name: "Felix: BPF tests on Ubuntu 22.04 (nftables)" run: when: "true" dependencies: - - Prerequisites + - "Felix: Build" + task: + jobs: + - name: "Felix: BPF tests on Ubuntu 22.04 (nftables)" + execution_time_limit: + minutes: 60 + env_vars: + - name: FELIX_TEST_GROUP + value: "bpf-22.04-nft-with-ut" + - name: FELIX_FV_BPFATTACHTYPE + value: "tc" + commands: + - source felix/.semaphore/fv-prologue + # Limit to BPF conntrack tests to reduce overall runtime. + - export FV_FOCUS='_BPF_.*ct=true' + - export NUM_FV_BATCHES=10 + - .semaphore/vms/run-tests-on-vms ${VM_PREFIX} + epilogue: + always: + commands: + - source felix/.semaphore/fv-epilogue + secrets: + - name: google-service-account-for-gce + - name: "Felix: BPF program-loading check on 25.10 (nftables)" + run: + when: "true" + dependencies: + - "Felix: Build" task: - prologue: - commands: - - cd felix - - export GOOGLE_APPLICATION_CREDENTIALS=$HOME/secrets/secret.google-service-account-key.json - - export SHORT_WORKFLOW_ID=$(echo ${SEMAPHORE_WORKFLOW_ID} | sha256sum | cut -c -8) - - export ZONE=europe-west3-c - - export VM_PREFIX=sem-${SEMAPHORE_PROJECT_NAME}-${SHORT_WORKFLOW_ID}-felix-nft- - - echo VM_PREFIX=${VM_PREFIX} - - export REPO_NAME=$(basename $(pwd)) - - export NUM_FV_BATCHES=4 - - export RUN_UT=true - - export FV_FOCUS='_BPF_.*ct=true' - - mkdir artifacts - - ./.semaphore/create-test-vms ${VM_PREFIX} jobs: - - name: UT/FV tests on new kernel + - name: "Felix: BPF program-loading check on 25.10" + execution_time_limit: + minutes: 30 env_vars: - - name: FELIX_FV_NFTABLES - value: "Enabled" + - name: FELIX_TEST_GROUP + value: "bpf-25.10-nft-no-fv-with-ut" - name: FELIX_FV_BPFATTACHTYPE value: "tc" + commands: + - source felix/.semaphore/fv-prologue + # Only run the UT that checks that the precompiled BPF binaries are loadable. + - export UT_FOCUS="TestPrecompiledBinariesAreLoadable" + - .semaphore/vms/run-tests-on-vms ${VM_PREFIX} + epilogue: + always: + commands: + - source felix/.semaphore/fv-epilogue + secrets: + - name: google-service-account-for-gce + - name: "Felix: BPF tests on Ubuntu 25.10 with jitharden=2 (nftables)" + run: + when: "true" + dependencies: + - "Felix: Build" + task: + jobs: + - name: "Felix: BPF tests on Ubuntu 25.10 with jitharden=2" execution_time_limit: - minutes: 180 + minutes: 60 + env_vars: + - name: FELIX_TEST_GROUP + value: "bpf-25.10-nft-with-ut-jitharden" + - name: FELIX_FV_BPFATTACHTYPE + value: "tc" commands: - - ./.semaphore/run-tests-on-vms ${VM_PREFIX} + - source felix/.semaphore/fv-prologue + # Limit to BPF TCP+conntrack tests to reduce overall runtime. + - export FV_FOCUS='_BPF_.*tcp.*ct=true' + - export NUM_FV_BATCHES=16 + - .semaphore/vms/run-tests-on-vms ${VM_PREFIX} epilogue: always: commands: - - ./.semaphore/collect-artifacts-from-vms ${VM_PREFIX} - - ./.semaphore/publish-artifacts - - ./.semaphore/clean-up-vms ${VM_PREFIX} + - source felix/.semaphore/fv-epilogue secrets: - name: google-service-account-for-gce - - name: goldmane + - name: Goldmane run: when: "true" execution_time_limit: @@ -669,6 +957,9 @@ blocks: dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "goldmane" prologue: commands: # The Makefile sometimes tries to rebuild the protobuf files on non-amd architectures @@ -677,10 +968,13 @@ blocks: - touch goldmane/proto/api.pb.go - cd goldmane jobs: - - name: make ci + - name: "Goldmane: CI" commands: - ../.semaphore/run-and-monitor make-ci.log make ci - - name: Build binary + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: "Goldmane: build multi-arch" matrix: - env_var: ARCH values: @@ -689,7 +983,7 @@ blocks: - s390x commands: - ../.semaphore/run-and-monitor image-$ARCH.log make build ARCH=$ARCH - - name: guardian + - name: Guardian run: when: "true" execution_time_limit: @@ -697,14 +991,20 @@ blocks: dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "guardian" prologue: commands: - cd guardian jobs: - - name: make ci + - name: "Guardian: CI" commands: - ../.semaphore/run-and-monitor make-ci.log make ci - - name: Build binary + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: "Guardian: build multi-arch" matrix: - env_var: ARCH values: @@ -719,14 +1019,29 @@ blocks: dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "kube-controllers" prologue: commands: - cd kube-controllers jobs: - - name: "kube-controllers: tests" + - name: "kube-controllers: CI" commands: - ../.semaphore/run-and-monitor ci.log make ci - - name: lib + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + # Run a subset of the kube-controllers CI tests that use the projectcalico.org/v3 API group for CRDs. + - name: "kube-controllers: CI (projectcalico.org/v3)" + commands: + - ../.semaphore/run-and-monitor ut.log make image ut + env_vars: + - name: CALICO_API_GROUP + value: "projectcalico.org/v3" + - name: STORE_BUILD_CACHE + value: "true" + - name: Libraries (lib) run: when: "true or change_in(['/lib/'], {pipeline_file: 'ignore'})" execution_time_limit: @@ -738,7 +1053,7 @@ blocks: commands: - cd lib jobs: - - name: make ci + - name: "Libraries: CI" commands: - ../.semaphore/run-and-monitor make-ci.log make ci - name: libcalico-go @@ -747,30 +1062,57 @@ blocks: dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "libcalico-go" prologue: commands: - cd libcalico-go jobs: - - name: "libcalico-go: tests" + # Run the tests twice, once for each backing API version. + - name: "libcalico-go: CI (crd.projectcalico.org/v1)" + commands: + - ../.semaphore/run-and-monitor make-ci.log make ci + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: "libcalico-go: CI (projectcalico.org/v3)" commands: - ../.semaphore/run-and-monitor make-ci.log make ci + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: CALICO_API_GROUP + value: projectcalico.org/v3 + - name: GINKGO_SKIP + value: "etcdv3 backend" - name: "Node: Build" run: when: "true" dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "node" agent: machine: type: f1-standard-4 - os_image: ubuntu2004 + os_image: ubuntu2204 prologue: commands: - cd node jobs: - name: "Node: CI" + priority: + # This job is on the critical path so we increase its priority. + - value: 51 + when: "pull_request =~ '.+'" commands: - ../.semaphore/run-and-monitor ci.log make ci + env_vars: + - name: STORE_BUILD_CACHE + value: "true" - name: "Node: multi-arch build" run: when: "true" @@ -781,7 +1123,7 @@ blocks: commands: - cd node jobs: - - name: Build image + - name: "Node: build multi-arch images" matrix: - env_var: ARCH values: @@ -789,10 +1131,10 @@ blocks: - s390x commands: - ../.semaphore/run-and-monitor image-$ARCH.log make image ARCH=$ARCH - - name: Build Windows archive + - name: "Node: build Windows archive" commands: - ../.semaphore/run-and-monitor build-windows-archive.log make build-windows-archive - - name: Build Windows image + - name: "Node: build Windows image" commands: - ../.semaphore/run-and-monitor build-windows-image.log make image-windows - name: "Node: Build - native arm64 runner" @@ -808,7 +1150,7 @@ blocks: commands: - cd node jobs: - - name: Build image + - name: "Node: build arm64 image" commands: - ../.semaphore/run-and-monitor build-arm64.log make image ARCH=arm64 - name: "Node: kind-cluster tests" @@ -816,32 +1158,41 @@ blocks: # KinD tests also build and run all the other images. when: "true" dependencies: - - Prerequisites + - "Prerequisites" task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "node" + - name: CI_GROUP_LABEL + value: "node" + - name: CI_JOB_LABEL + value: "kind-cluster-tests" prologue: commands: - - cd node + # Set up access to GCP buckets. - export GOOGLE_APPLICATION_CREDENTIALS=$HOME/secrets/secret.google-service-account-key.json + - gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS + - gcloud config set project unique-caldron-775 + - cd node - export SHORT_WORKFLOW_ID=$(echo ${SEMAPHORE_WORKFLOW_ID} | sha256sum | cut -c -8) - export ZONE=europe-west3-c - export VM_PREFIX=sem-${SEMAPHORE_PROJECT_NAME}-${SHORT_WORKFLOW_ID}-kind- - echo VM_PREFIX=${VM_PREFIX} - - export REPO_NAME=$(basename $(pwd)) + - export COMPONENT=node - export VM_DISK_SIZE=80GB - mkdir artifacts - - ../.semaphore/vms/create-test-vms ${ZONE} ${VM_PREFIX} + - ./.semaphore/cache-test-artifacts jobs: - name: "Node: kind-cluster tests" execution_time_limit: minutes: 120 commands: - - ../.semaphore/vms/run-tests-on-vms ${ZONE} ${VM_PREFIX} + - ../.semaphore/vms/run-tests-on-vms ${VM_PREFIX} epilogue: always: commands: - - ../.semaphore/vms/publish-artifacts - - ../.semaphore/vms/clean-up-vms ${ZONE} ${VM_PREFIX} - - test-results publish ./report/*.xml --name "node-kind-tests" || true + - '../.semaphore/vms/publish-reports "Node: KinD STs"' + - ../.semaphore/vms/clean-up-vms ${VM_PREFIX} secrets: - name: google-service-account-for-gce - name: pod2daemon @@ -854,10 +1205,10 @@ blocks: commands: - cd pod2daemon jobs: - - name: pod2daemon tests + - name: "pod2daemon: CI" commands: - ../.semaphore/run-and-monitor ci.log make ci - - test-results publish ./report/*.xml --name "pod2daemon-ut-tests" || true + - 'test-results publish ./report/*.xml --name "pod2daemon: UT" || true' - name: "Tools (hack directory)" run: when: "true or change_in(['/metadata.mk', '/lib.Makefile', '/hack/', '/api/', '/libcalico-go/'], {pipeline_file: 'ignore', exclude: ['/**/.gitignore', '/**/README.md', '/**/LICENSE']})" @@ -868,7 +1219,7 @@ blocks: commands: - cd hack jobs: - - name: "Tools (hack directory)" + - name: "Tools (hack directory): CI" commands: - ../.semaphore/run-and-monitor make-ci.log make ci - name: Typha @@ -877,15 +1228,23 @@ blocks: dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "typha" prologue: commands: - cd typha jobs: - name: "Typha: UT and FV tests" commands: - - ../.semaphore/run-and-monitor make-ci.log make ci EXCEPT=k8sfv-test - - name: "Typha: UT and FV tests on UBI-minimal" + - export TEST_SUITE_PREFIX="calico/base" + - ../.semaphore/run-and-monitor make-ci.log make ci + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: "Typha: FV tests on UBI-minimal" commands: + - 'export TEST_SUITE_PREFIX="UBI-minimal"' - ../.semaphore/run-and-monitor make-fv-ubi.log make clean image fv env_vars: - name: USE_UBI_AS_CALICO_BASE @@ -893,20 +1252,27 @@ blocks: epilogue: always: commands: - - | - for f in /home/semaphore/calico/typha/report/*; do - NAME=$(basename $f) - test-results compile --name typha-$NAME $f $NAME.json || true - done - for f in /home/semaphore/calico/typha/pkg/report/*; do - NAME=$(basename $f) - test-results compile --name typha-$NAME $f $NAME.json || true - done - test-results combine *.xml.json report.json || true - artifact push job report.json -d test-results/junit.json || true - artifact push workflow report.json -d test-results/${SEMAPHORE_PIPELINE_ID}/${SEMAPHORE_JOB_ID}.json || true - - test-results publish /home/semaphore/calico/felix/report/k8sfv_suite.xml --name "typha-k8sfv" || true - - name: whisker-backend + - 'test-results publish --ignore-missing ./report/*.xml ../felix/report/*.xml --suite-prefix="$TEST_SUITE_PREFIX" --name "Typha" || true' + - name: webhooks + run: + when: "true" + dependencies: + - Prerequisites + task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "webhooks" + prologue: + commands: + - cd webhooks + jobs: + - name: "webhooks: CI" + commands: + - ../.semaphore/run-and-monitor make-ci.log make ci + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: Whisker backend run: when: "true" execution_time_limit: @@ -918,10 +1284,10 @@ blocks: commands: - cd whisker-backend jobs: - - name: make ci + - name: "Whisker backend: CI" commands: - ../.semaphore/run-and-monitor make-ci.log make ci - - name: Build binary + - name: "Whisker backend: build multi-arch" matrix: - env_var: ARCH values: @@ -930,7 +1296,7 @@ blocks: - s390x commands: - ../.semaphore/run-and-monitor image-$ARCH.log make build ARCH=$ARCH - - name: whisker + - name: Whisker run: when: "true or change_in(['/metadata.mk', '/lib.Makefile', '/whisker/'], {pipeline_file: 'ignore', exclude: ['/**/.gitignore', '/**/README.md', '/**/LICENSE']})" execution_time_limit: @@ -938,14 +1304,20 @@ blocks: dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "whisker" prologue: commands: - cd whisker jobs: - - name: make ci + - name: "Whisker: CI" commands: - ../.semaphore/run-and-monitor make-ci.log make ci - - name: Build binary + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: "Whisker: build multi-arch images" matrix: - env_var: ARCH values: @@ -960,64 +1332,22 @@ blocks: dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "key-cert-provisioner" prologue: commands: - cd key-cert-provisioner jobs: - - name: key-cert-provisioner tests + - name: "key-cert-provisioner: CI" commands: - ../.semaphore/run-and-monitor ci.log make ci - - name: "OpenStack integration (Yoga)" - run: - when: "true or change_in(['/metadata.mk', '/lib.Makefile', '/networking-calico/'], {pipeline_file: 'ignore'})" - dependencies: - - Prerequisites - task: - agent: - machine: - type: f1-standard-2 - os_image: ubuntu2004 - prologue: - commands: - - cd networking-calico - jobs: - - name: "Unit and FV tests (tox) on Yoga" - commands: - - ../.semaphore/run-and-monitor tox.log make tox-yoga - - name: "Mainline ST (DevStack + Tempest) on Yoga" - commands: - # For some reason python3-wrapt is pre-installed on a Semaphore ubuntu2004 node, but with - # a version (1.11.2) that is different from the version that OpenStack needs (1.13.3), and - # this was causing the DevStack setup to fail, because pip doesn't know how to uninstall - # or replace the existing version. Happily we do know that, so let's do it upfront here. - - sudo apt-get remove -y python3-wrapt || true - # Install all the packages that would trigger an initramfs update using a workaround - # for limited /boot partition space in the ubuntu2004 image - - sudo apt update - - sudo rsync -av /boot/ /boot2/ - - sudo mount --bind /boot2 /boot - - sudo apt install -y cryptsetup lsscsi open-iscsi thin-provisioning-tools - - sudo umount /boot - - sudo rsync -av /boot2/ /boot/ --exclude "*.new" --exclude "*.dpkg-bak" --delete --inplace - - sudo rm -rf /boot2/ - # Set NC_PLUGIN_REPO and NC_PLUGIN_REF so that DevStack will use the networking-calico - # code from the current PR or branch. The following checkout is to make sure of having a - # local ref that we can specify. - - git checkout -b devstack-test - - export NC_PLUGIN_REPO=$(dirname $(pwd)) - - export NC_PLUGIN_REF=$(git rev-parse --abbrev-ref HEAD) - - sudo git config --system --add safe.directory ${NC_PLUGIN_REPO}/.git - - TEMPEST=true DEVSTACK_BRANCH=unmaintained/yoga ./devstack/bootstrap.sh - epilogue: - on_fail: - commands: - - mkdir logs - - sudo journalctl > logs/journalctl.txt - - artifact push job logs - + env_vars: + - name: STORE_BUILD_CACHE + value: "true" - name: "OpenStack integration (Caracal)" run: - when: "true or change_in(['/networking-calico/'], {pipeline_file: 'ignore'})" + when: "true or change_in(['/metadata.mk', '/lib.Makefile', '/networking-calico/', '/.semaphore/semaphore.yml.d/blocks/40-openstack.yml'], {pipeline_file: 'ignore'})" dependencies: - Prerequisites task: @@ -1029,10 +1359,10 @@ blocks: commands: - cd networking-calico jobs: - - name: "Unit and FV tests (tox) on Caracal" + - name: "OpenStack: Unit and FV tests (tox) on Caracal" commands: - ../.semaphore/run-and-monitor tox.log make tox-caracal - - name: "Mainline ST (DevStack + Tempest) on Caracal" + - name: "OpenStack: Mainline ST (DevStack + Tempest) on Caracal" commands: # For some reason python3-wrapt is pre-installed on a Semaphore ubuntu2004 node, but with # a version (1.11.2) that is different from the version that OpenStack needs (1.13.3), and @@ -1046,7 +1376,10 @@ blocks: - export NC_PLUGIN_REPO=$(dirname $(pwd)) - export NC_PLUGIN_REF=$(git rev-parse --abbrev-ref HEAD) - sudo git config --system --add safe.directory ${NC_PLUGIN_REPO}/.git - - TEMPEST=true DEVSTACK_BRANCH=stable/2024.1 ./devstack/bootstrap.sh + # The BIRD config in our DevStack setup is thoroughly broken and generates loads of errors + # in the log. But we don't need it for a single node setup anyway, so let's disable it. + - export CALICO_BGP_MODE=none + - TEMPEST=true OPENSTACK_RELEASE=caracal ./devstack/bootstrap.sh epilogue: always: commands: @@ -1059,14 +1392,20 @@ blocks: dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "mock-node" prologue: commands: - cd test-tools/mocknode jobs: - - name: Mock node + - name: "Mock node: CI" commands: - ../../.semaphore/run-and-monitor make-ci.log make ci - - name: release tooling + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: Release tooling run: when: "true" execution_time_limit: @@ -1078,11 +1417,11 @@ blocks: commands: - cd release jobs: - - name: ci + - name: "Release tooling: CI" commands: - ../.semaphore/run-and-monitor release-ci.log make ci - - test-results publish --name "release-tool-ut-tests" ./report/*.xml || true - - name: build binary + - 'test-results publish --name "Release tool: UT" ./report/*.xml || true' + - name: "Release tooling: build" commands: - ../.semaphore/run-and-monitor release-build.log make build - cache store release-${SEMAPHORE_GIT_SHA} bin diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index 8c8fd03dac9..cfef23c59f6 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -1,5 +1,6 @@ -# !! WARNING, DO NOT EDIT !! This file is generated from semaphore.yml.tpl. -# To update, modify the template and then run 'make gen-semaphore-yaml'. +# !! WARNING, DO NOT EDIT !! This file is generated from the templates +# in /.semaphore/semaphore.yml.d. To update, modify the relevant +# template and then run 'make gen-semaphore-yaml'. version: v1.0 name: Calico execution_time_limit: @@ -16,23 +17,122 @@ auto_cancel: global_job_config: secrets: - name: docker-hub + - name: google-service-account-for-gce + env_vars: + - name: GOOGLE_PROJECT + value: unique-caldron-775 + - name: GCS_BUILD_CACHE_BUCKET + value: calico-transient-build-artifacts-europe-west3 + - name: BUILDKIT_PROGRESS + value: plain + - name: SEMAPHORE_AGENT_UPLOAD_JOB_LOGS + value: when-trimmed prologue: commands: - - checkout - - export REPO_DIR="$(pwd)" - - mkdir artifacts - # Semaphore is doing shallow clone on a commit without tags. - # unshallow it for GIT_VERSION:=$(shell git describe --tags --dirty --always) - - if [[ "${SEMAPHORE_BLOCK_NAME}" != "Prerequisites" ]]; then retry git fetch --unshallow; fi + - export CALICO_DIR_NAME=${SEMAPHORE_GIT_DIR} + - export GCS_CACHE_DIR=gs://$GCS_BUILD_CACHE_BUCKET/cache/${CALICO_DIR_NAME} + - export GCS_WORKFLOW_DIR=gs://$GCS_BUILD_CACHE_BUCKET/workflow/${SEMAPHORE_WORKFLOW_ID} + - export GOOGLE_APPLICATION_CREDENTIALS=$HOME/secrets/secret.google-service-account-key.json + - |- + if [[ $(uname -m) != "x86_64" || -n "$ARCH" ]]; then + echo "Non-x86_64 build: $(uname -m) / $ARCH"; + export BUILD_CACHE_GROUP=$BUILD_CACHE_GROUP-$(uname -m)-$ARCH; + fi + - gcloud auth activate-service-account --key-file="$GOOGLE_APPLICATION_CREDENTIALS" || true + - gcloud config set project ${GOOGLE_PROJECT} || true + # Try to restore the working copy from the cache, if available. Then, + # for PRs, also try to restore the build cache. (We skip this on branch + # builds to ensure branches build cleanly.) + - sudo apt-get install -y zstd || true + - restored_wc_cache=false + - |- + if [[ -n "${SEMAPHORE_GIT_BRANCH}" && -z "$SKIP_CACHE_RESTORE" ]]; then + echo "Looking for cached working copy for branch ${SEMAPHORE_GIT_BRANCH}" + gcs_branch_cache_dir="${GCS_CACHE_DIR}/${SEMAPHORE_GIT_BRANCH}" + if gcloud storage cp "$gcs_branch_cache_dir/working-copy.tar.zstd" /tmp/working-copy.tar.zstd; then + echo "Restoring cache for branch ${SEMAPHORE_GIT_BRANCH}" + tar --use-compress-program=zstd -xf /tmp/working-copy.tar.zstd & tar_pid=$! + downloaded_build_cache=false + if [[ -n "$BUILD_CACHE_GROUP" && -n "${SEMAPHORE_GIT_PR_NUMBER}" ]]; then + if gcloud storage cp "$gcs_branch_cache_dir/build-cache-${BUILD_CACHE_GROUP}.tar.zstd" /tmp/build-cache.tar.zstd; then + echo "Found build cache for group ${BUILD_CACHE_GROUP}" + downloaded_build_cache=true + fi + fi + if wait $tar_pid; then + restored_wc_cache=true + else + echo "ERROR: Failed to extract working copy" + restored_wc_cache=false + fi + if ${downloaded_build_cache}; then + echo "Extracting build cache" + tar --use-compress-program=zstd -xf /tmp/build-cache.tar.zstd -C . + rm /tmp/build-cache.tar.zstd + fi + cd "${CALICO_DIR_NAME}" + fi + fi || true + - |- + if [[ ${restored_wc_cache} != true && -z "$SKIP_CHECKOUT" ]]; then + echo "No cache restored, doing a fresh checkout..." + cd $HOME + rm -rf "${CALICO_DIR_NAME}" + git clone --filter=blob:none $SEMAPHORE_GIT_URL $CALICO_DIR_NAME + cd "${CALICO_DIR_NAME}" || exit 1 + fi + - |- + if [[ -z "$SKIP_CHECKOUT" ]]; then + git fetch origin --tags --prune-tags ${SEMAPHORE_GIT_SHA} + if [ "$SEMAPHORE_GIT_REF_TYPE" = "branch" ]; then + git checkout ${SEMAPHORE_GIT_SHA} -B "${SEMAPHORE_GIT_BRANCH}" + else + git checkout ${SEMAPHORE_GIT_SHA} + fi + CALICO_DIR="$(pwd)" + export CALICO_DIR + mkdir -p artifacts + fi - echo $DOCKERHUB_PASSWORD | docker login --username "$DOCKERHUB_USERNAME" --password-stdin epilogue: commands: - - cd "$REPO_DIR" + - cd $HOME + - |- + if [[ -z "${SEMAPHORE_GIT_PR_NUMBER}" && -n "$BUILD_CACHE_GROUP" && "$STORE_BUILD_CACHE" = "true" ]]; then + echo "On branch, storing build cache group ${BUILD_CACHE_GROUP}" + tar --use-compress-program="zstd -3" -cf /tmp/build-cache.tar.zstd go ${CALICO_DIR_NAME}/.go-pkg-cache + gcs_branch_cache_dir="${GCS_CACHE_DIR}/${SEMAPHORE_GIT_BRANCH}" + gcloud storage cp /tmp/build-cache.tar.zstd "$gcs_branch_cache_dir/build-cache-${BUILD_CACHE_GROUP}.tar.zstd" + fi + - cd "$CALICO_DIR" - .semaphore/publish-artifacts promotions: # Manual promotion for publishing a hashrelease. - name: Publish hashrelease pipeline_file: release/hashrelease.yml + parameters: + env_vars: + - required: true + options: + - "true" + - "false" + default_value: "false" + description: "Build container images. If 'true', container images will be built as part of the hashrelease process." + name: BUILD_CONTAINER_IMAGES + - required: true + options: + - "true" + - "false" + default_value: "false" + description: "create tarball of images. If 'true', a tarball of images will be added to the release.tgz. Requires BUILD_CONTAINER_IMAGES to also be 'true'." + name: ARCHIVE_IMAGES + - required: true + options: + - "true" + - "false" + default_value: "false" + description: "publish images. If 'true', images will be pushed to the container registry/registries as part of the hashrelease process. Requires BUILD_CONTAINER_IMAGES to also be 'true'." + name: PUBLISH_IMAGES # Manual promotion for publishing a release. - name: Publish official release pipeline_file: release/release.yml @@ -118,24 +218,158 @@ promotions: blocks: - name: Check images availability run: - when: "false or change_in(['/charts/', '/manifests/'], {pipeline_file: 'ignore'})" + when: "false or (branch !~ 'build-v.*' and change_in(['/charts/', '/manifests/'], {pipeline_file: 'ignore'}))" dependencies: [] task: jobs: - name: Check images availability commands: - make check-images-availability - - name: Prerequisites + - name: Check SemaphoreCI files + run: + when: >- + false or + change_in(['/.semaphore', '/hack', '/Makefile', '/lib.Makefile', '/metadata.mk'], {pipeline_file: 'track'}) + dependencies: [] + task: + jobs: + - name: Check semaphore files + commands: + - make gen-semaphore-yaml + - make check-dirty + - name: Check YAML files + run: + when: >- + false or + change_in(['/**/*.yml', '/**/*.yaml', '/Makefile', '/lib.Makefile', '/metadata.mk'], {pipeline_file: 'track'}) dependencies: [] task: + jobs: + - name: Check YAML files + commands: + - make yaml-lint + - name: Check Go + run: + when: >- + false or + change_in(['/go.mod', '/go.sum', '/**/*.go', '/**/deps.txt', '/Makefile', '/lib.Makefile', '/metadata.mk'], {pipeline_file: 'ignore'}) + dependencies: [] + task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "preflight-go-checks" agent: machine: - type: f1-standard-2 + type: f1-standard-4 os_image: ubuntu2204 + jobs: + - name: Check Go files + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + commands: + - make check-ginkgo-v2 + - make check-go-mod + - make verify-go-mods + - make gen-deps-files + - make go-vet + - >- + if [ -n "$SEMAPHORE_GIT_PR_NUMBER" ]; then + make fix-changed + else + make fix-all + fi + - make check-dirty + - name: Check Python + run: + when: >- + false or + change_in(['/networking-calico', '/lib.Makefile', '/metadata.mk'], {pipeline_file: 'ignore'}) + dependencies: [] + task: + jobs: + - name: Check Python files + commands: + - make -C networking-calico fmtpy + - make -C networking-calico flake8 + - make check-dirty + - name: Check language/Dockerfiles + run: + when: >- + false or + change_in(['/**/*'], {pipeline_file: 'track', exclude: ['libbpf', '.[a-z]*']}) + dependencies: [] + task: + jobs: + - name: Check language/Dockerfiles + commands: + # Both of these are very quick grep checks so we combine them into one job. + - make check-language + - make check-dockerfiles + - name: Check protobufs + run: + when: >- + false or + change_in(['/**/*.pb.go', '/**/*.proto', '/Makefile', '/lib.Makefile', '/metadata.mk'], {pipeline_file: 'ignore'}) + dependencies: [] + task: + jobs: + - name: Check protobufs + commands: + - make protobuf fix-changed + - make check-dirty + - name: Manifests and API docs + run: + when: >- + false or + change_in(['/api', '/manifests', '/charts', '/libcalico-go/lib/apis', '/libcalico-go/config', '/libcalico-go/Makefile', '/felix/config', '/felix/docs', '/felix/Makefile', '/goldmane', '/Makefile', '/lib.Makefile', '/metadata.mk'], {pipeline_file: 'ignore'}) + dependencies: [] + task: + jobs: + - name: Check manifests and API docs + commands: + - make -C lib gen-files + - make -C api gen-files + - make -C libcalico-go gen-files + - make -C felix gen-files + - make -C goldmane gen-files + - make gen-manifests + - make fix-changed + - make check-ocp-no-crds + - make check-dirty + - name: Store working copy + run: + when: "branch =~ '.*'" + dependencies: [] + task: + env_vars: + - name: SKIP_CACHE_RESTORE + value: "true" + jobs: + - name: Save working copy to cache + commands: + - cd .. + - tar --use-compress-program="zstd -3" -cf /tmp/working-copy.tar.zstd $CALICO_DIR_NAME + - gcs_branch_cache_dir="${GCS_CACHE_DIR}/${SEMAPHORE_GIT_BRANCH}" + - gcloud storage cp /tmp/working-copy.tar.zstd "$gcs_branch_cache_dir/working-copy.tar.zstd" + - name: Prerequisites + skip: + # Always skip, this is a dummy job used to group prerequisite checks. + when: "true" + dependencies: + - Check SemaphoreCI files + - Check YAML files + - Check Go + - Check Python + - Check language/Dockerfiles + - Check protobufs + - Manifests and API docs + - Store working copy + task: jobs: - name: Pre-flight checks commands: - - make ci-preflight-checks + - "true" - name: API run: when: "false or change_in(['/metadata.mk', '/lib.Makefile', '/api/'], {pipeline_file: 'ignore', exclude: ['/**/.gitignore', '/**/README.md', '/**/LICENSE']})" @@ -148,25 +382,31 @@ blocks: commands: - cd api jobs: - - name: make ci + - name: "API: CI" commands: - ../.semaphore/run-and-monitor make-ci.log make ci - - name: apiserver + - name: API Server run: - when: "change_in(['/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/client/clientset_generated/clientset/*.go','/api/pkg/client/clientset_generated/clientset/scheme/*.go','/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/api/pkg/openapi/*.go','/apiserver/**','/crypto/pkg/tls/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/v3/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/k8s/scheme/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/pkg/buildinfo/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/crypto/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/pkg/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-apiserver.yml','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/client/clientset_generated/clientset/*.go','/api/pkg/client/clientset_generated/clientset/scheme/*.go','/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/api/pkg/openapi/*.go','/apiserver/**','/crypto/pkg/tls/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/pkg/buildinfo/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/crypto/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/pkg/**/*_test.go']})" execution_time_limit: minutes: 30 dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "apiserver" prologue: commands: - cd apiserver jobs: - - name: make ci + - name: "API Server: CI" commands: - ../.semaphore/run-and-monitor make-ci.log make ci - - name: Build binary + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: "API Server: Build" matrix: - env_var: ARCH values: @@ -175,9 +415,9 @@ blocks: - s390x commands: - ../.semaphore/run-and-monitor image-$ARCH.log make build ARCH=$ARCH - - name: app-policy + - name: Application Layer Policy run: - when: "change_in(['/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/**','/crypto/pkg/tls/*.go','/felix/bpf/tc/defs/*.go','/felix/calc/*.go','/felix/config/*.go','/felix/dataplane/ipsets/*.go','/felix/dataplane/linux/dataplanedefs/*.go','/felix/deltatracker/*.go','/felix/dispatcher/*.go','/felix/environment/*.go','/felix/generictables/*.go','/felix/hashutils/*.go','/felix/idalloc/*.go','/felix/ip/*.go','/felix/ipsets/*.go','/felix/iptables/*.go','/felix/iptables/cmdshim/*.go','/felix/k8sutils/*.go','/felix/labelindex/*.go','/felix/labelindex/ipsetmember/*.go','/felix/labelindex/labelnamevalueindex/*.go','/felix/labelindex/labelrestrictionindex/*.go','/felix/logutils/*.go','/felix/markbits/*.go','/felix/multidict/*.go','/felix/netlinkshim/*.go','/felix/nftables/*.go','/felix/proto/*.go','/felix/rules/*.go','/felix/serviceindex/*.go','/felix/stringutils/*.go','/felix/types/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/apis/v3/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/k8s/scheme/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/node/pkg/lifecycle/utils/*.go','/pkg/buildinfo/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/crypto/**/*_test.go','/felix/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-app-policy.yml','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/**','/crypto/pkg/tls/*.go','/felix/bpf/tc/defs/*.go','/felix/calc/*.go','/felix/config/*.go','/felix/dataplane/ipsets/*.go','/felix/dataplane/linux/dataplanedefs/*.go','/felix/deltatracker/*.go','/felix/dispatcher/*.go','/felix/environment/*.go','/felix/generictables/*.go','/felix/idalloc/*.go','/felix/ip/*.go','/felix/ipsets/*.go','/felix/iptables/*.go','/felix/iptables/cmdshim/*.go','/felix/k8sutils/*.go','/felix/labelindex/*.go','/felix/labelindex/ipsetmember/*.go','/felix/labelindex/labelnamevalueindex/*.go','/felix/labelindex/labelrestrictionindex/*.go','/felix/logutils/*.go','/felix/markbits/*.go','/felix/multidict/*.go','/felix/netlinkshim/*.go','/felix/nftables/*.go','/felix/proto/*.go','/felix/rules/*.go','/felix/serviceindex/*.go','/felix/stringutils/*.go','/felix/types/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/consistenthash/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/node/pkg/lifecycle/utils/*.go','/pkg/buildinfo/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/crypto/**/*_test.go','/felix/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go']})" dependencies: - Prerequisites task: @@ -185,41 +425,53 @@ blocks: commands: - cd app-policy jobs: - - name: app-policy tests + - name: "Application Layer Policy: CI" commands: - ../.semaphore/run-and-monitor ci.log make ci - name: calicoctl run: - when: "change_in(['/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/calicoctl/**','/crypto/pkg/tls/*.go','/hack/test/certs/','/kube-controllers/pkg/config/*.go','/kube-controllers/pkg/controllers/loadbalancer/*.go','/kube-controllers/pkg/controllers/utils/*.go','/lib.Makefile','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/apis/v3/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/k8s/scheme/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/upgrade/converters/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/pkg/buildinfo/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/crypto/**/*_test.go','/kube-controllers/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/pkg/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-calicoctl.yml','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/calicoctl/**','/crypto/pkg/tls/*.go','/hack/test/certs/','/kube-controllers/pkg/config/*.go','/kube-controllers/pkg/controllers/loadbalancer/*.go','/kube-controllers/pkg/controllers/utils/*.go','/kube-controllers/pkg/converter/*.go','/lib.Makefile','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/pkg/buildinfo/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/crypto/**/*_test.go','/kube-controllers/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/pkg/**/*_test.go']})" dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "calicoctl" prologue: commands: - cd calicoctl jobs: - - name: calicoctl tests + - name: "calicoctl: CI" commands: - ../.semaphore/run-and-monitor ci.log make ci - - name: cni-plugin + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: CNI Plugin run: - when: "change_in(['/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/cni-plugin/**','/crypto/pkg/tls/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/v3/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/k8s/scheme/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/node/pkg/cni/*.go','/pkg/buildinfo/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/crypto/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-cni-plugin.yml','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/cni-plugin/**','/crypto/pkg/tls/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/ipam/vmipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/kubevirt/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/node/pkg/cni/*.go','/pkg/buildinfo/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/crypto/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go']})" dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "cni-plugin" prologue: commands: - cd cni-plugin jobs: - - name: cni-plugin tests + - name: "CNI Plugin: CI" commands: - ../.semaphore/run-and-monitor ci.log make ci - - name: build windows cni-plugin images + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: "CNI Plugin: build windows images" commands: - ../.semaphore/run-and-monitor ci.log make image-windows - - name: "cni-plugin: Windows" + - name: "CNI Plugin: Windows" run: - when: "change_in(['/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/cni-plugin/**','/crypto/pkg/tls/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/v3/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/k8s/scheme/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/node/pkg/cni/*.go','/pkg/buildinfo/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/crypto/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go']})" + when: "false or change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-cni-plugin.yml','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/cni-plugin/**','/crypto/pkg/tls/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/ipam/vmipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/kubevirt/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/node/pkg/cni/*.go','/pkg/buildinfo/*.go','/process/testing/aso','/process/testing/utils','/process/testing/winfv-cni-plugin'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/crypto/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go']})" dependencies: - Prerequisites task: @@ -235,9 +487,7 @@ blocks: - export AZURE_CLIENT_SECRET=$AZ_SP_PASSWORD - export REPORT_DIR=/home/semaphore/calico/process/testing/winfv-cni-plugin/report - export LOGS_DIR=~/fv.log - - export SHORT_WORKFLOW_ID=$(echo ${SEMAPHORE_WORKFLOW_ID} | sha256sum | cut -c -8) - - export CLUSTER_NAME=sem-${SEMAPHORE_PROJECT_NAME}-pr${SEMAPHORE_GIT_PR_NUMBER}-${SHORT_WORKFLOW_ID} - - export SUFFIX=${CLUSTER_NAME} + - export RG_PREFIX=${USER}-win-cni-${SEMAPHORE_GIT_SHA:0:4}-pr${SEMAPHORE_GIT_PR_NUMBER} - cd cni-plugin - ../.semaphore/run-and-monitor build.log make bin/windows/calico.exe bin/windows/calico-ipam.exe bin/windows/win-fv.exe epilogue: @@ -245,35 +495,42 @@ blocks: commands: - artifact push job ${REPORT_DIR} --destination semaphore/test-results || true - artifact push job ${LOGS_DIR} --destination semaphore/logs || true - - cd ~/calico/process/testing/winfv-cni-plugin/aso && make dist-clean + - cd ~/calico/process/testing/aso && make dist-clean env_vars: + - name: BUILD_CACHE_GROUP + value: "cni-plugin" - name: SEMAPHORE_ARTIFACT_EXPIRY value: 2w - name: AZURE_LOCATION value: eastus2 - name: KUBE_VERSION - value: v1.29.7 + value: v1.33.7 jobs: - - name: Containerd - Windows FV - overlay + - name: "CNI Plugin: Windows Containerd FV - overlay" execution_time_limit: minutes: 60 commands: - export BACKEND=overlay - - export AZURE_RESOURCE_GROUP=${USER}-capz-win-cni-${SEMAPHORE_WORKFLOW_ID:0:8}-${BACKEND}-rg - - ../.semaphore/run-and-monitor win-fv-containerd.log ./.semaphore/run-win-fv.sh - - name: Containerd - Windows FV - l2bridge + - export AZURE_RESOURCE_GROUP=${RG_PREFIX}-${BACKEND}-rg + - echo AZURE_RESOURCE_GROUP=${AZURE_RESOURCE_GROUP} + - ../.semaphore/run-and-monitor win-fv-containerd.log ~/calico/process/testing/winfv-cni-plugin/run-win-fv.sh + - name: "CNI Plugin: Windows Containerd FV - l2bridge" execution_time_limit: minutes: 60 commands: - export BACKEND=l2bridge - - export AZURE_RESOURCE_GROUP=${USER}-capz-win-cni-${SEMAPHORE_WORKFLOW_ID:0:8}-${BACKEND}-rg - - ../.semaphore/run-and-monitor win-fv-containerd.log ./.semaphore/run-win-fv.sh + - export AZURE_RESOURCE_GROUP=${RG_PREFIX}-${BACKEND}-rg + - echo AZURE_RESOURCE_GROUP=${AZURE_RESOURCE_GROUP} + - ../.semaphore/run-and-monitor win-fv-containerd.log ~/calico/process/testing/winfv-cni-plugin/run-win-fv.sh - name: confd run: - when: "change_in(['/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/confd/**','/crypto/pkg/tls/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/v3/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/k8s/scheme/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/bgpsyncer/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/pkg/buildinfo/*.go','/typha/pkg/discovery/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncclientutils/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/tlsutils/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/crypto/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/pkg/**/*_test.go','/typha/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-confd.yml','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/confd/**','/crypto/pkg/tls/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/bgpsyncer/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/pkg/buildinfo/*.go','/typha/pkg/discovery/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncclientutils/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/tlsutils/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/crypto/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/pkg/**/*_test.go','/typha/**/*_test.go']})" dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "confd" prologue: commands: - cd confd @@ -283,20 +540,10 @@ blocks: minutes: 60 commands: - ../.semaphore/run-and-monitor ci.log make ci - - name: crypto - run: - when: "change_in(['/crypto/**','/hack/test/certs/','/lib.Makefile','/metadata.mk'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md']})" - dependencies: - - Prerequisites - task: - prologue: - commands: - - cd crypto - jobs: - - name: "crypto tests" - commands: - - ../.semaphore/run-and-monitor ci.log make ci - - name: e2e script & pipeline validations + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: E2E script & pipeline validations run: when: "false or change_in(['/.semaphore/end-to-end/'], {pipeline_file: 'ignore', exclude: ['/**/.gitignore', '/**/README.md', '/**/LICENSE']})" execution_time_limit: @@ -305,24 +552,30 @@ blocks: - Prerequisites task: jobs: - - name: Test Pipeline files are valid yaml + - name: "E2E script & pipeline validations: validate yaml" commands: - ./.semaphore/end-to-end/scripts/test_scripts/e2e-validation.sh - - name: e2e tests + - name: E2E tests run: - when: "change_in(['/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/api/pkg/openapi/*.go','/apiserver/cmd/apiserver/*.go','/apiserver/cmd/apiserver/server/*.go','/apiserver/pkg/apiserver/*.go','/apiserver/pkg/printers/projectcalico/*.go','/apiserver/pkg/rbac/*.go','/apiserver/pkg/registry/projectcalico/authorizer/*.go','/apiserver/pkg/registry/projectcalico/bgpconfiguration/*.go','/apiserver/pkg/registry/projectcalico/bgpfilter/*.go','/apiserver/pkg/registry/projectcalico/bgppeer/*.go','/apiserver/pkg/registry/projectcalico/blockaffinity/*.go','/apiserver/pkg/registry/projectcalico/caliconodestatus/*.go','/apiserver/pkg/registry/projectcalico/clusterinformation/*.go','/apiserver/pkg/registry/projectcalico/felixconfig/*.go','/apiserver/pkg/registry/projectcalico/globalnetworkset/*.go','/apiserver/pkg/registry/projectcalico/globalpolicy/*.go','/apiserver/pkg/registry/projectcalico/hostendpoint/*.go','/apiserver/pkg/registry/projectcalico/ipamconfig/*.go','/apiserver/pkg/registry/projectcalico/ippool/*.go','/apiserver/pkg/registry/projectcalico/ipreservation/*.go','/apiserver/pkg/registry/projectcalico/kubecontrollersconfig/*.go','/apiserver/pkg/registry/projectcalico/networkpolicy/*.go','/apiserver/pkg/registry/projectcalico/networkset/*.go','/apiserver/pkg/registry/projectcalico/profile/*.go','/apiserver/pkg/registry/projectcalico/rest/*.go','/apiserver/pkg/registry/projectcalico/server/*.go','/apiserver/pkg/registry/projectcalico/stagedglobalnetworkpolicy/*.go','/apiserver/pkg/registry/projectcalico/stagedkubernetesnetworkpolicy/*.go','/apiserver/pkg/registry/projectcalico/stagednetworkpolicy/*.go','/apiserver/pkg/registry/projectcalico/tier/*.go','/apiserver/pkg/registry/projectcalico/util/*.go','/apiserver/pkg/storage/calico/*.go','/apiserver/pkg/storage/etcd/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/calicoctl/calicoctl/*.go','/calicoctl/calicoctl/commands/*.go','/calicoctl/calicoctl/commands/argutils/*.go','/calicoctl/calicoctl/commands/clientmgr/*.go','/calicoctl/calicoctl/commands/cluster/*.go','/calicoctl/calicoctl/commands/common/*.go','/calicoctl/calicoctl/commands/constants/*.go','/calicoctl/calicoctl/commands/datastore/*.go','/calicoctl/calicoctl/commands/datastore/migrate/*.go','/calicoctl/calicoctl/commands/file/*.go','/calicoctl/calicoctl/commands/ipam/*.go','/calicoctl/calicoctl/commands/node/*.go','/calicoctl/calicoctl/commands/resourceloader/*.go','/calicoctl/calicoctl/resourcemgr/*.go','/calicoctl/calicoctl/util/*.go','/calicoctl/calicoctl/util/yaml/*.go','/calicoctl/tests/fv/helper/*.go','/cni-plugin/cmd/calico/*.go','/cni-plugin/cmd/install/*.go','/cni-plugin/internal/pkg/azure/*.go','/cni-plugin/internal/pkg/utils/*.go','/cni-plugin/internal/pkg/utils/cri/*.go','/cni-plugin/pkg/dataplane/*.go','/cni-plugin/pkg/dataplane/grpc/*.go','/cni-plugin/pkg/dataplane/grpc/proto/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/install/*.go','/cni-plugin/pkg/ipamplugin/*.go','/cni-plugin/pkg/k8s/*.go','/cni-plugin/pkg/plugin/*.go','/cni-plugin/pkg/types/*.go','/cni-plugin/pkg/upgrade/*.go','/cni-plugin/pkg/wait/*.go','/cni-plugin/tests/*.go','/confd/etc','/confd/pkg/backends/*.go','/confd/pkg/backends/calico/*.go','/confd/pkg/config/*.go','/confd/pkg/log/*.go','/confd/pkg/resource/template/*.go','/confd/pkg/run/*.go','/crypto/pkg/tls/*.go','/e2e/**','/felix/aws/*.go','/felix/bpf-apache','/felix/bpf-gpl','/felix/bpf/*.go','/felix/bpf/arp/*.go','/felix/bpf/asm/*.go','/felix/bpf/bpfdefs/*.go','/felix/bpf/bpfmap/*.go','/felix/bpf/conntrack/*.go','/felix/bpf/conntrack/cleanupv1/*.go','/felix/bpf/conntrack/timeouts/*.go','/felix/bpf/conntrack/v2/*.go','/felix/bpf/conntrack/v3/*.go','/felix/bpf/conntrack/v4/*.go','/felix/bpf/counters/*.go','/felix/bpf/events/*.go','/felix/bpf/failsafes/*.go','/felix/bpf/filter/*.go','/felix/bpf/hook/*.go','/felix/bpf/ifstate/*.go','/felix/bpf/ipsets/*.go','/felix/bpf/jump/*.go','/felix/bpf/legacy/*.go','/felix/bpf/libbpf/*.go','/felix/bpf/maps/*.go','/felix/bpf/nat/*.go','/felix/bpf/perf/*.go','/felix/bpf/polprog/*.go','/felix/bpf/profiling/*.go','/felix/bpf/proxy/*.go','/felix/bpf/qos/*.go','/felix/bpf/routes/*.go','/felix/bpf/state/*.go','/felix/bpf/tc/*.go','/felix/bpf/tc/defs/*.go','/felix/bpf/utils/*.go','/felix/bpf/xdp/*.go','/felix/cachingmap/*.go','/felix/calc/*.go','/felix/cmd/calico-bpf/commands/*.go','/felix/collector/*.go','/felix/collector/flowlog/*.go','/felix/collector/goldmane/*.go','/felix/collector/local/*.go','/felix/collector/types/*.go','/felix/collector/types/counter/*.go','/felix/collector/types/endpoint/*.go','/felix/collector/types/metric/*.go','/felix/collector/types/tuple/*.go','/felix/collector/utils/*.go','/felix/config/*.go','/felix/conntrack/*.go','/felix/daemon/*.go','/felix/dataplane/*.go','/felix/dataplane/common/*.go','/felix/dataplane/external/*.go','/felix/dataplane/inactive/*.go','/felix/dataplane/ipsets/*.go','/felix/dataplane/linux/*.go','/felix/dataplane/linux/dataplanedefs/*.go','/felix/dataplane/linux/qos/*.go','/felix/deltatracker/*.go','/felix/dispatcher/*.go','/felix/environment/*.go','/felix/ethtool/*.go','/felix/generictables/*.go','/felix/hashutils/*.go','/felix/idalloc/*.go','/felix/ifacemonitor/*.go','/felix/ip/*.go','/felix/ipsets/*.go','/felix/iptables/*.go','/felix/iptables/cmdshim/*.go','/felix/jitter/*.go','/felix/k8sutils/*.go','/felix/labelindex/*.go','/felix/labelindex/ipsetmember/*.go','/felix/labelindex/labelnamevalueindex/*.go','/felix/labelindex/labelrestrictionindex/*.go','/felix/linkaddrs/*.go','/felix/logutils/*.go','/felix/markbits/*.go','/felix/multidict/*.go','/felix/netlinkshim/*.go','/felix/netlinkshim/handlemgr/*.go','/felix/nfnetlink/*.go','/felix/nfnetlink/nfnl/*.go','/felix/nfnetlink/pkt/*.go','/felix/nftables/*.go','/felix/policysync/*.go','/felix/proto/*.go','/felix/proto/protoconv/*.go','/felix/routerule/*.go','/felix/routetable/*.go','/felix/routetable/ownershippol/*.go','/felix/rules/*.go','/felix/serviceindex/*.go','/felix/statusrep/*.go','/felix/stringutils/*.go','/felix/throttle/*.go','/felix/timeshim/*.go','/felix/types/*.go','/felix/usagerep/*.go','/felix/vxlanfdb/*.go','/felix/wireguard/*.go','/goldmane/cmd/*.go','/goldmane/cmd/flowgen/*.go','/goldmane/cmd/health/*.go','/goldmane/cmd/stream/*.go','/goldmane/pkg/client/*.go','/goldmane/pkg/daemon/*.go','/goldmane/pkg/emitter/*.go','/goldmane/pkg/flowgen/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/internal/utils/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/kube-controllers/cmd/check-status/*.go','/kube-controllers/cmd/kube-controllers/*.go','/kube-controllers/cmd/wrapper/*.go','/kube-controllers/pkg/cache/*.go','/kube-controllers/pkg/config/*.go','/kube-controllers/pkg/controllers/controller/*.go','/kube-controllers/pkg/controllers/flannelmigration/*.go','/kube-controllers/pkg/controllers/loadbalancer/*.go','/kube-controllers/pkg/controllers/namespace/*.go','/kube-controllers/pkg/controllers/networkpolicy/*.go','/kube-controllers/pkg/controllers/node/*.go','/kube-controllers/pkg/controllers/pod/*.go','/kube-controllers/pkg/controllers/serviceaccount/*.go','/kube-controllers/pkg/controllers/utils/*.go','/kube-controllers/pkg/converter/*.go','/kube-controllers/pkg/status/*.go','/lib.Makefile','/lib/httpmachinery/pkg/apiutil/*.go','/lib/httpmachinery/pkg/codec/*.go','/lib/httpmachinery/pkg/context/*.go','/lib/httpmachinery/pkg/header/*.go','/lib/httpmachinery/pkg/server/*.go','/lib/httpmachinery/pkg/server/adaptors/gorilla/*.go','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/apis/v3/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/k8s/scheme/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/bgpsyncer/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/nodestatussyncer/*.go','/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/multireadbuf/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/upgrade/converters/*.go','/libcalico-go/lib/upgrade/migrator/*.go','/libcalico-go/lib/upgrade/migrator/clients/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/libcalico-go/lib/writelogger/*.go','/metadata.mk','/node/cmd/calico-ipam/*.go','/node/cmd/calico-node/*.go','/node/cmd/calico-node/bpf/*.go','/node/cmd/calico/*.go','/node/cmd/mountns/*.go','/node/pkg/allocateip/*.go','/node/pkg/calicoclient/*.go','/node/pkg/cni/*.go','/node/pkg/flowlogs/*.go','/node/pkg/health/*.go','/node/pkg/health/bird/*.go','/node/pkg/hostpathinit/*.go','/node/pkg/lifecycle/shutdown/*.go','/node/pkg/lifecycle/startup/*.go','/node/pkg/lifecycle/startup/autodetection/*.go','/node/pkg/lifecycle/startup/autodetection/ipv4/*.go','/node/pkg/lifecycle/utils/*.go','/node/pkg/nodeinit/*.go','/node/pkg/status/*.go','/node/pkg/status/populators/*.go','/node/windows-packaging/tests/*.go','/pkg/buildinfo/*.go','/pkg/cmdwrapper/*.go','/pod2daemon/binder/*.go','/pod2daemon/csidriver/*.go','/pod2daemon/csidriver/driver/*.go','/pod2daemon/flexvol/*.go','/pod2daemon/flexvol/creds/*.go','/pod2daemon/nodeagent/*.go','/pod2daemon/proto/*.go','/pod2daemon/workloadapi/*.go','/typha/cmd/calico-typha/*.go','/typha/cmd/typha-client/*.go','/typha/pkg/calc/*.go','/typha/pkg/config/*.go','/typha/pkg/daemon/*.go','/typha/pkg/discovery/*.go','/typha/pkg/jitter/*.go','/typha/pkg/k8s/*.go','/typha/pkg/logutils/*.go','/typha/pkg/promutils/*.go','/typha/pkg/snapcache/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncclientutils/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/syncserver/*.go','/typha/pkg/tlsutils/*.go','/whisker','/whisker-backend/cmd/*.go','/whisker-backend/cmd/app/*.go','/whisker-backend/pkg/apis/v1/*.go','/whisker-backend/pkg/config/*.go','/whisker-backend/pkg/handlers/v1/*.go','apiserver/**/*Dockerfile*','apiserver/Makefile','apiserver/deps.txt','calicoctl/**/*Dockerfile*','calicoctl/Makefile','calicoctl/deps.txt','cni-plugin/**/*Dockerfile*','cni-plugin/Makefile','cni-plugin/deps.txt','goldmane/**/*Dockerfile*','goldmane/Makefile','goldmane/deps.txt','kube-controllers/**/*Dockerfile*','kube-controllers/Makefile','kube-controllers/deps.txt','node/**/*Dockerfile*','node/Makefile','node/deps.txt','pod2daemon/**/*Dockerfile*','pod2daemon/Makefile','pod2daemon/deps.txt','typha/**/*Dockerfile*','typha/Makefile','typha/deps.txt','whisker-backend/**/*Dockerfile*','whisker-backend/Makefile','whisker-backend/deps.txt','whisker/**/*Dockerfile*','whisker/Makefile','whisker/deps.txt'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/apiserver/**/*_test.go','/app-policy/**/*_test.go','/calicoctl/**/*_test.go','/cni-plugin/**/*_test.go','/confd/**/*_test.go','/crypto/**/*_test.go','/felix/**/*_test.go','/goldmane/**/*_test.go','/kube-controllers/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go','/whisker-backend/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-e2e.yml','/Makefile','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/client/clientset_generated/clientset/*.go','/api/pkg/client/clientset_generated/clientset/scheme/*.go','/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/*.go','/api/pkg/client/informers_generated/externalversions/*.go','/api/pkg/client/informers_generated/externalversions/internalinterfaces/*.go','/api/pkg/client/informers_generated/externalversions/projectcalico/*.go','/api/pkg/client/informers_generated/externalversions/projectcalico/v3/*.go','/api/pkg/client/listers_generated/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/api/pkg/openapi/*.go','/apiserver/cmd/apiserver/*.go','/apiserver/cmd/apiserver/server/*.go','/apiserver/pkg/apiserver/*.go','/apiserver/pkg/printers/projectcalico/*.go','/apiserver/pkg/rbac/*.go','/apiserver/pkg/registry/projectcalico/authorizer/*.go','/apiserver/pkg/registry/projectcalico/bgpconfiguration/*.go','/apiserver/pkg/registry/projectcalico/bgpfilter/*.go','/apiserver/pkg/registry/projectcalico/bgppeer/*.go','/apiserver/pkg/registry/projectcalico/blockaffinity/*.go','/apiserver/pkg/registry/projectcalico/caliconodestatus/*.go','/apiserver/pkg/registry/projectcalico/clusterinformation/*.go','/apiserver/pkg/registry/projectcalico/felixconfig/*.go','/apiserver/pkg/registry/projectcalico/globalnetworkset/*.go','/apiserver/pkg/registry/projectcalico/globalpolicy/*.go','/apiserver/pkg/registry/projectcalico/hostendpoint/*.go','/apiserver/pkg/registry/projectcalico/ipamconfig/*.go','/apiserver/pkg/registry/projectcalico/ippool/*.go','/apiserver/pkg/registry/projectcalico/ipreservation/*.go','/apiserver/pkg/registry/projectcalico/kubecontrollersconfig/*.go','/apiserver/pkg/registry/projectcalico/networkpolicy/*.go','/apiserver/pkg/registry/projectcalico/networkset/*.go','/apiserver/pkg/registry/projectcalico/profile/*.go','/apiserver/pkg/registry/projectcalico/rest/*.go','/apiserver/pkg/registry/projectcalico/server/*.go','/apiserver/pkg/registry/projectcalico/stagedglobalnetworkpolicy/*.go','/apiserver/pkg/registry/projectcalico/stagedkubernetesnetworkpolicy/*.go','/apiserver/pkg/registry/projectcalico/stagednetworkpolicy/*.go','/apiserver/pkg/registry/projectcalico/tier/*.go','/apiserver/pkg/registry/projectcalico/util/*.go','/apiserver/pkg/storage/calico/*.go','/apiserver/pkg/storage/etcd/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/calicoctl/calicoctl/*.go','/calicoctl/calicoctl/commands/*.go','/calicoctl/calicoctl/commands/argutils/*.go','/calicoctl/calicoctl/commands/clientmgr/*.go','/calicoctl/calicoctl/commands/cluster/*.go','/calicoctl/calicoctl/commands/common/*.go','/calicoctl/calicoctl/commands/constants/*.go','/calicoctl/calicoctl/commands/datastore/*.go','/calicoctl/calicoctl/commands/datastore/migrate/*.go','/calicoctl/calicoctl/commands/file/*.go','/calicoctl/calicoctl/commands/ipam/*.go','/calicoctl/calicoctl/commands/node/*.go','/calicoctl/calicoctl/resourcemgr/*.go','/calicoctl/calicoctl/util/*.go','/calicoctl/calicoctl/util/yaml/*.go','/calicoctl/tests/fv/helper/*.go','/cni-plugin/cmd/calico/*.go','/cni-plugin/cmd/install/*.go','/cni-plugin/internal/pkg/azure/*.go','/cni-plugin/internal/pkg/utils/*.go','/cni-plugin/internal/pkg/utils/cri/*.go','/cni-plugin/pkg/dataplane/*.go','/cni-plugin/pkg/dataplane/grpc/*.go','/cni-plugin/pkg/dataplane/grpc/proto/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/install/*.go','/cni-plugin/pkg/ipamplugin/*.go','/cni-plugin/pkg/k8s/*.go','/cni-plugin/pkg/plugin/*.go','/cni-plugin/pkg/types/*.go','/cni-plugin/pkg/upgrade/*.go','/cni-plugin/pkg/wait/*.go','/cni-plugin/tests/*.go','/confd/etc','/confd/pkg/backends/*.go','/confd/pkg/backends/calico/*.go','/confd/pkg/backends/types/*.go','/confd/pkg/config/*.go','/confd/pkg/log/*.go','/confd/pkg/resource/template/*.go','/confd/pkg/run/*.go','/crypto/pkg/tls/*.go','/e2e/**','/felix/aws/*.go','/felix/bpf-apache','/felix/bpf-gpl','/felix/bpf/*.go','/felix/bpf/arp/*.go','/felix/bpf/asm/*.go','/felix/bpf/bpfdefs/*.go','/felix/bpf/bpfmap/*.go','/felix/bpf/conntrack/*.go','/felix/bpf/conntrack/cleanupv1/*.go','/felix/bpf/conntrack/timeouts/*.go','/felix/bpf/conntrack/v2/*.go','/felix/bpf/conntrack/v3/*.go','/felix/bpf/conntrack/v4/*.go','/felix/bpf/consistenthash/*.go','/felix/bpf/counters/*.go','/felix/bpf/events/*.go','/felix/bpf/failsafes/*.go','/felix/bpf/filter/*.go','/felix/bpf/hook/*.go','/felix/bpf/ifstate/*.go','/felix/bpf/ipfrags/*.go','/felix/bpf/ipsets/*.go','/felix/bpf/jump/*.go','/felix/bpf/legacy/*.go','/felix/bpf/libbpf/*.go','/felix/bpf/maps/*.go','/felix/bpf/nat/*.go','/felix/bpf/perf/*.go','/felix/bpf/polprog/*.go','/felix/bpf/profiling/*.go','/felix/bpf/proxy/*.go','/felix/bpf/qos/*.go','/felix/bpf/routes/*.go','/felix/bpf/state/*.go','/felix/bpf/tc/*.go','/felix/bpf/tc/defs/*.go','/felix/bpf/utils/*.go','/felix/bpf/xdp/*.go','/felix/cachingmap/*.go','/felix/calc/*.go','/felix/cmd/calico-bpf/commands/*.go','/felix/collector/*.go','/felix/collector/flowlog/*.go','/felix/collector/goldmane/*.go','/felix/collector/local/*.go','/felix/collector/types/*.go','/felix/collector/types/counter/*.go','/felix/collector/types/endpoint/*.go','/felix/collector/types/metric/*.go','/felix/collector/types/tuple/*.go','/felix/collector/utils/*.go','/felix/config/*.go','/felix/conntrack/*.go','/felix/daemon/*.go','/felix/dataplane/*.go','/felix/dataplane/common/*.go','/felix/dataplane/external/*.go','/felix/dataplane/inactive/*.go','/felix/dataplane/ipsets/*.go','/felix/dataplane/linux/*.go','/felix/dataplane/linux/dataplanedefs/*.go','/felix/dataplane/linux/qos/*.go','/felix/deltatracker/*.go','/felix/dispatcher/*.go','/felix/environment/*.go','/felix/ethtool/*.go','/felix/generictables/*.go','/felix/idalloc/*.go','/felix/ifacemonitor/*.go','/felix/ip/*.go','/felix/ipsets/*.go','/felix/iptables/*.go','/felix/iptables/cmdshim/*.go','/felix/jitter/*.go','/felix/k8sutils/*.go','/felix/labelindex/*.go','/felix/labelindex/ipsetmember/*.go','/felix/labelindex/labelnamevalueindex/*.go','/felix/labelindex/labelrestrictionindex/*.go','/felix/linkaddrs/*.go','/felix/logutils/*.go','/felix/markbits/*.go','/felix/multidict/*.go','/felix/netlinkshim/*.go','/felix/netlinkshim/handlemgr/*.go','/felix/nfnetlink/*.go','/felix/nfnetlink/nfnl/*.go','/felix/nfnetlink/pkt/*.go','/felix/nftables/*.go','/felix/policysync/*.go','/felix/proto/*.go','/felix/proto/protoconv/*.go','/felix/routerule/*.go','/felix/routetable/*.go','/felix/routetable/ownershippol/*.go','/felix/rules/*.go','/felix/serviceindex/*.go','/felix/statusrep/*.go','/felix/stringutils/*.go','/felix/throttle/*.go','/felix/timeshim/*.go','/felix/types/*.go','/felix/usagerep/*.go','/felix/vxlanfdb/*.go','/felix/wireguard/*.go','/goldmane/cmd/*.go','/goldmane/cmd/flowgen/*.go','/goldmane/cmd/health/*.go','/goldmane/cmd/stream/*.go','/goldmane/pkg/client/*.go','/goldmane/pkg/daemon/*.go','/goldmane/pkg/emitter/*.go','/goldmane/pkg/flowgen/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/internal/utils/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/kube-controllers/cmd/check-status/*.go','/kube-controllers/cmd/kube-controllers/*.go','/kube-controllers/cmd/wrapper/*.go','/kube-controllers/pkg/cache/*.go','/kube-controllers/pkg/config/*.go','/kube-controllers/pkg/controllers/controller/*.go','/kube-controllers/pkg/controllers/flannelmigration/*.go','/kube-controllers/pkg/controllers/ippool/*.go','/kube-controllers/pkg/controllers/loadbalancer/*.go','/kube-controllers/pkg/controllers/namespace/*.go','/kube-controllers/pkg/controllers/networkpolicy/*.go','/kube-controllers/pkg/controllers/node/*.go','/kube-controllers/pkg/controllers/pod/*.go','/kube-controllers/pkg/controllers/serviceaccount/*.go','/kube-controllers/pkg/controllers/utils/*.go','/kube-controllers/pkg/converter/*.go','/kube-controllers/pkg/status/*.go','/lib.Makefile','/lib/httpmachinery/pkg/apiutil/*.go','/lib/httpmachinery/pkg/codec/*.go','/lib/httpmachinery/pkg/context/*.go','/lib/httpmachinery/pkg/header/*.go','/lib/httpmachinery/pkg/server/*.go','/lib/httpmachinery/pkg/server/adaptors/gorilla/*.go','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/bgpsyncer/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/nodestatussyncer/*.go','/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/consistenthash/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/ipam/vmipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/kubevirt/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/multireadbuf/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/libcalico-go/lib/writelogger/*.go','/metadata.mk','/node/cmd/calico-ipam/*.go','/node/cmd/calico-node/*.go','/node/cmd/calico-node/bpf/*.go','/node/cmd/calico/*.go','/node/cmd/mountns/*.go','/node/pkg/allocateip/*.go','/node/pkg/calicoclient/*.go','/node/pkg/cni/*.go','/node/pkg/flowlogs/*.go','/node/pkg/health/*.go','/node/pkg/health/bird/*.go','/node/pkg/hostpathinit/*.go','/node/pkg/lifecycle/shutdown/*.go','/node/pkg/lifecycle/startup/*.go','/node/pkg/lifecycle/startup/autodetection/*.go','/node/pkg/lifecycle/startup/autodetection/ipv4/*.go','/node/pkg/lifecycle/utils/*.go','/node/pkg/nodeinit/*.go','/node/pkg/status/*.go','/node/pkg/status/populators/*.go','/node/windows-packaging/tests/*.go','/pkg/buildinfo/*.go','/pkg/cmdwrapper/*.go','/pod2daemon/binder/*.go','/pod2daemon/csidriver/*.go','/pod2daemon/csidriver/driver/*.go','/pod2daemon/flexvol/*.go','/pod2daemon/flexvol/creds/*.go','/pod2daemon/nodeagent/*.go','/pod2daemon/proto/*.go','/pod2daemon/workloadapi/*.go','/typha/cmd/calico-typha/*.go','/typha/cmd/typha-client/*.go','/typha/pkg/calc/*.go','/typha/pkg/config/*.go','/typha/pkg/daemon/*.go','/typha/pkg/discovery/*.go','/typha/pkg/jitter/*.go','/typha/pkg/k8s/*.go','/typha/pkg/logutils/*.go','/typha/pkg/promutils/*.go','/typha/pkg/snapcache/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncclientutils/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/syncserver/*.go','/typha/pkg/tlsutils/*.go','/whisker','/whisker-backend/cmd/*.go','/whisker-backend/cmd/app/*.go','/whisker-backend/pkg/apis/v1/*.go','/whisker-backend/pkg/config/*.go','/whisker-backend/pkg/handlers/v1/*.go','apiserver/**/*Dockerfile*','apiserver/Makefile','apiserver/deps.txt','calicoctl/**/*Dockerfile*','calicoctl/Makefile','calicoctl/deps.txt','cni-plugin/**/*Dockerfile*','cni-plugin/Makefile','cni-plugin/deps.txt','goldmane/**/*Dockerfile*','goldmane/Makefile','goldmane/deps.txt','kube-controllers/**/*Dockerfile*','kube-controllers/Makefile','kube-controllers/deps.txt','node/**/*Dockerfile*','node/Makefile','node/deps.txt','pod2daemon/**/*Dockerfile*','pod2daemon/Makefile','pod2daemon/deps.txt','typha/**/*Dockerfile*','typha/Makefile','typha/deps.txt','whisker-backend/**/*Dockerfile*','whisker-backend/Makefile','whisker-backend/deps.txt','whisker/**/*Dockerfile*','whisker/Makefile','whisker/deps.txt'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/apiserver/**/*_test.go','/app-policy/**/*_test.go','/calicoctl/**/*_test.go','/cni-plugin/**/*_test.go','/confd/**/*_test.go','/crypto/**/*_test.go','/felix/**/*_test.go','/goldmane/**/*_test.go','/kube-controllers/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go','/whisker-backend/**/*_test.go']})" dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "e2e-tests" agent: machine: type: f1-standard-4 - os_image: ubuntu2004 + os_image: ubuntu2204 jobs: - - name: Conformance e2e tests + - name: "E2E tests: Conformance" commands: - .semaphore/run-and-monitor e2e-test.log make e2e-test - - name: envoy-gateway + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: Envoy Gateway run: when: "false or change_in(['/metadata.mk', '/lib.Makefile', '/third_party/envoy-gateway'], {pipeline_file: 'ignore', exclude: ['/**/.gitignore', '/**/README.md', '/**/LICENSE']})" execution_time_limit: @@ -334,10 +587,10 @@ blocks: commands: - cd third_party/envoy-gateway jobs: - - name: make ci + - name: "Envoy Gateway: CI" commands: - ../../.semaphore/run-and-monitor make-ci.log make ci - - name: Build binary + - name: "Envoy Gateway: build multi-arch binaries" matrix: - env_var: ARCH values: @@ -346,7 +599,7 @@ blocks: - s390x commands: - ../../.semaphore/run-and-monitor image-$ARCH.log make build ARCH=$ARCH - - name: envoy-proxy + - name: Envoy Proxy run: when: "false or change_in(['/metadata.mk', '/lib.Makefile', '/third_party/envoy-proxy'], {pipeline_file: 'ignore', exclude: ['/**/.gitignore', '/**/README.md', '/**/LICENSE']})" execution_time_limit: @@ -358,10 +611,10 @@ blocks: commands: - cd third_party/envoy-proxy jobs: - - name: make ci + - name: "Envoy Proxy: CI" commands: - ../../.semaphore/run-and-monitor make-ci.log make ci - - name: Build binary + - name: "Envoy Proxy: build multi-arch binaries" matrix: - env_var: ARCH values: @@ -370,7 +623,7 @@ blocks: - s390x commands: - ../../.semaphore/run-and-monitor image-$ARCH.log make build ARCH=$ARCH - - name: envoy-ratelimit + - name: "Envoy Ratelimit" run: when: "false or change_in(['/metadata.mk', '/lib.Makefile', '/third_party/envoy-ratelimit'], {pipeline_file: 'ignore', exclude: ['/**/.gitignore', '/**/README.md', '/**/LICENSE']})" execution_time_limit: @@ -382,10 +635,10 @@ blocks: commands: - cd third_party/envoy-ratelimit jobs: - - name: make ci + - name: "Envoy Ratelimit: CI" commands: - ../../.semaphore/run-and-monitor make-ci.log make ci - - name: Build binary + - name: "Envoy Ratelimit: build multi-arch binaries" matrix: - env_var: ARCH values: @@ -396,50 +649,77 @@ blocks: - ../../.semaphore/run-and-monitor image-$ARCH.log make build ARCH=$ARCH - name: "Felix: Build" run: - when: "change_in(['/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/types/*.go','/crypto/pkg/tls/*.go','/felix/**','/goldmane/pkg/client/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/apis/v3/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/k8s/scheme/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/bgpsyncer/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/nodestatussyncer/*.go','/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/multireadbuf/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/upgrade/converters/*.go','/libcalico-go/lib/upgrade/migrator/*.go','/libcalico-go/lib/upgrade/migrator/clients/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/libcalico-go/lib/writelogger/*.go','/metadata.mk','/node/pkg/lifecycle/utils/*.go','/pkg/buildinfo/*.go','/pod2daemon/binder/*.go','/typha/cmd/calico-typha/*.go','/typha/cmd/typha-client/*.go','/typha/pkg/calc/*.go','/typha/pkg/config/*.go','/typha/pkg/daemon/*.go','/typha/pkg/discovery/*.go','/typha/pkg/jitter/*.go','/typha/pkg/k8s/*.go','/typha/pkg/logutils/*.go','/typha/pkg/promutils/*.go','/typha/pkg/snapcache/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/syncserver/*.go','/typha/pkg/tlsutils/*.go','typha/**/*Dockerfile*','typha/Makefile','typha/deps.txt'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/app-policy/**/*_test.go','/cni-plugin/**/*_test.go','/crypto/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-felix.yml','/.semaphore/vms/','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/types/*.go','/crypto/pkg/tls/*.go','/felix/**','/goldmane/pkg/client/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/bgpsyncer/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/nodestatussyncer/*.go','/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/consistenthash/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/multireadbuf/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/testutils/stacktrace/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/libcalico-go/lib/writelogger/*.go','/metadata.mk','/node/pkg/lifecycle/utils/*.go','/pkg/buildinfo/*.go','/pod2daemon/binder/*.go','/typha/cmd/calico-typha/*.go','/typha/cmd/typha-client/*.go','/typha/pkg/calc/*.go','/typha/pkg/config/*.go','/typha/pkg/daemon/*.go','/typha/pkg/discovery/*.go','/typha/pkg/jitter/*.go','/typha/pkg/k8s/*.go','/typha/pkg/logutils/*.go','/typha/pkg/promutils/*.go','/typha/pkg/snapcache/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/syncserver/*.go','/typha/pkg/tlsutils/*.go','typha/**/*Dockerfile*','typha/Makefile','typha/deps.txt'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/app-policy/**/*_test.go','/cni-plugin/**/*_test.go','/crypto/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go']})" dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "felix" prologue: commands: - cd felix - - cache restore go-pkg-cache - - cache restore go-mod-cache + - gcloud config set project unique-caldron-775 + - gcloud auth activate-service-account --key-file=$HOME/secrets/secret.google-service-account-key.json + secrets: + - name: google-service-account-for-gce jobs: - - name: Build and run UT, k8sfv + - name: "Felix: Build and cache FV prerequisites" + priority: + # This job is on the critical path so we increase its priority. + - value: 51 + when: "pull_request =~ '.+'" + env_vars: + - name: STORE_BUILD_CACHE + value: "true" execution_time_limit: - minutes: 60 + minutes: 20 + commands: + - make build image ut-bpf-prereqs fv-prereqs + - ./.semaphore/cache-test-artifacts + - name: "Felix: UT" + priority: + # This job is on the critical path so we increase its priority. + - value: 51 + when: "pull_request =~ '.+'" + execution_time_limit: + minutes: 30 commands: - - make build image fv-prereqs - - "cache store bin-${SEMAPHORE_GIT_SHA} bin" - - "cache store fv.test-${SEMAPHORE_GIT_SHA} fv/fv.test" - - cache store go-pkg-cache .go-pkg-cache - - "cache store go-mod-cache ${HOME}/go/pkg/mod/cache" - - docker save -o /tmp/calico-felix-test.tar calico/felix-test:latest-amd64 - - "cache store felix-image-${SEMAPHORE_GIT_SHA} /tmp/calico-felix-test.tar" - - docker save -o /tmp/felixtest-typha.tar felix-test/typha:latest-amd64 - - "cache store felixtest-typha-image-${SEMAPHORE_GIT_SHA} /tmp/felixtest-typha.tar" - ../.semaphore/run-and-monitor ut.log make ut + - name: "Felix: k8sfv" + priority: + # This job is on the critical path so we increase its priority. + - value: 51 + when: "pull_request =~ '.+'" + execution_time_limit: + minutes: 15 + commands: - ../.semaphore/run-and-monitor k8sfv-typha.log make k8sfv-test JUST_A_MINUTE=true USE_TYPHA=true - ../.semaphore/run-and-monitor k8sfv-no-typha.log make k8sfv-test JUST_A_MINUTE=true USE_TYPHA=false - - name: Static checks + - name: "Felix: Static checks" + priority: + # This job is on the critical path so we increase its priority. + - value: 51 + when: "pull_request =~ '.+'" execution_time_limit: - minutes: 60 + minutes: 15 commands: - ../.semaphore/run-and-monitor static-checks.log make static-checks + epilogue: + always: + commands: + - 'test-results publish --name "${SEMAPHORE_JOB_NAME}" ./report/*.xml || true' - name: "Felix: multi-arch build" run: - when: "change_in(['/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/types/*.go','/crypto/pkg/tls/*.go','/felix/**','/goldmane/pkg/client/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/apis/v3/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/k8s/scheme/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/node/pkg/lifecycle/utils/*.go','/pkg/buildinfo/*.go','/pod2daemon/binder/*.go','/typha/pkg/discovery/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/tlsutils/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/app-policy/**/*_test.go','/cni-plugin/**/*_test.go','/crypto/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-felix.yml','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/types/*.go','/crypto/pkg/tls/*.go','/felix/**','/goldmane/pkg/client/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/consistenthash/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/testutils/stacktrace/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/node/pkg/lifecycle/utils/*.go','/pkg/buildinfo/*.go','/pod2daemon/binder/*.go','/typha/pkg/discovery/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/tlsutils/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/app-policy/**/*_test.go','/cni-plugin/**/*_test.go','/crypto/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go']})" dependencies: - "Felix: Build" task: prologue: commands: - cd felix - - cache restore go-pkg-cache - - cache restore go-mod-cache jobs: - - name: Build binary + - name: "Felix: build multi-arch binaries" matrix: - env_var: ARCH values: @@ -451,7 +731,7 @@ blocks: - ../.semaphore/run-and-monitor build-$ARCH.log make build ARCH=$ARCH - name: "Felix: Build - native arm64 runner" run: - when: "change_in(['/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/types/*.go','/crypto/pkg/tls/*.go','/felix/**','/goldmane/pkg/client/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/apis/v3/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/k8s/scheme/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/node/pkg/lifecycle/utils/*.go','/pkg/buildinfo/*.go','/pod2daemon/binder/*.go','/typha/pkg/discovery/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/tlsutils/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/app-policy/**/*_test.go','/cni-plugin/**/*_test.go','/crypto/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-felix.yml','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/types/*.go','/crypto/pkg/tls/*.go','/felix/**','/goldmane/pkg/client/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/consistenthash/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/testutils/stacktrace/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/node/pkg/lifecycle/utils/*.go','/pkg/buildinfo/*.go','/pod2daemon/binder/*.go','/typha/pkg/discovery/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/tlsutils/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/app-policy/**/*_test.go','/cni-plugin/**/*_test.go','/crypto/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go']})" dependencies: - "Felix: Build" task: @@ -461,26 +741,27 @@ blocks: prologue: commands: - cd felix - - cache restore go-pkg-cache - - cache restore go-mod-cache jobs: - - name: Build binary + - name: "Felix: build arm64 binaries" commands: - ../.semaphore/run-and-monitor build-arm64.log make build ARCH=arm64 - name: "Felix: Build Windows binaries" run: - when: "change_in(['/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/types/*.go','/crypto/pkg/tls/*.go','/felix/**','/goldmane/pkg/client/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/apis/v3/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/k8s/scheme/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/node/pkg/lifecycle/utils/*.go','/pkg/buildinfo/*.go','/pod2daemon/binder/*.go','/typha/pkg/discovery/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/tlsutils/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/app-policy/**/*_test.go','/cni-plugin/**/*_test.go','/crypto/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-felix.yml','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/types/*.go','/crypto/pkg/tls/*.go','/felix/**','/goldmane/pkg/client/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/consistenthash/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/testutils/stacktrace/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/node/pkg/lifecycle/utils/*.go','/pkg/buildinfo/*.go','/pod2daemon/binder/*.go','/typha/pkg/discovery/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/tlsutils/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/app-policy/**/*_test.go','/cni-plugin/**/*_test.go','/crypto/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go']})" dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "felix" jobs: - - name: Build Windows binaries + - name: "Felix: Build Windows binaries" commands: - cd felix - make bin/calico-felix.exe fv/win-fv.exe - - name: "Felix: Windows FV capz" + - name: "Felix: Windows FV" run: - when: "false or change_in(['/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/types/*.go','/crypto/pkg/tls/*.go','/felix/**','/goldmane/pkg/client/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/apis/v3/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/k8s/scheme/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/node/pkg/lifecycle/utils/*.go','/pkg/buildinfo/*.go','/pod2daemon/binder/*.go','/process','/typha/pkg/discovery/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/tlsutils/*.go','process/**/*Dockerfile*','process/Makefile','process/deps.txt'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/app-policy/**/*_test.go','/cni-plugin/**/*_test.go','/crypto/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go']})" + when: "false or change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-felix.yml','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/types/*.go','/crypto/pkg/tls/*.go','/felix/**','/goldmane/pkg/client/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/consistenthash/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/testutils/stacktrace/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/node/pkg/lifecycle/utils/*.go','/pkg/buildinfo/*.go','/pod2daemon/binder/*.go','/process/testing/aso','/process/testing/utils','/process/testing/winfv-felix','/typha/pkg/discovery/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/tlsutils/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/app-policy/**/*_test.go','/cni-plugin/**/*_test.go','/crypto/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go']})" dependencies: ["Felix: Build Windows binaries"] task: secrets: @@ -494,181 +775,191 @@ blocks: - export AZURE_TENANT_ID=$AZ_TENANT_ID - export AZURE_CLIENT_ID=$AZ_SP_ID - export AZURE_CLIENT_SECRET=$AZ_SP_PASSWORD - - export AZURE_SUBSCRIPTION_ID_B64="$(echo -n "$AZ_SUBSCRIPTION_ID" | base64 | tr -d '\n')" - - export AZURE_TENANT_ID_B64="$(echo -n "$AZ_TENANT_ID" | base64 | tr -d '\n')" - - export AZURE_CLIENT_ID_B64="$(echo -n "$AZ_SP_ID" | base64 | tr -d '\n')" - - export AZURE_CLIENT_SECRET_B64="$(echo -n "$AZ_SP_PASSWORD" | base64 | tr -d '\n')" + - export REPORT_DIR=/home/semaphore/calico/process/testing/winfv-felix/report + - export LOGS_DIR=~/fv.log + - export RG_PREFIX=${USER}-win-felix-${SEMAPHORE_GIT_SHA:0:4}-pr${SEMAPHORE_GIT_PR_NUMBER} - cd felix epilogue: always: commands: - - artifact push job ${REPORT_DIR} --destination test-results || true + - artifact push job ${REPORT_DIR} --destination semaphore/test-results || true + - artifact push job ${LOGS_DIR} --destination semaphore/logs || true + - cd ~/calico/process/testing/aso && make dist-clean env_vars: - - name: FV_PROVISIONER - value: "capz" + - name: BUILD_CACHE_GROUP + value: "felix" - name: FV_TYPE value: "calico-felix" - name: SEMAPHORE_ARTIFACT_EXPIRY value: 2w - name: CONTAINERD_VERSION - value: 1.7.22 + value: 1.7.28 jobs: - - name: CAPZ - Windows FV + - name: "Felix: Windows FV" commands: - - ./.semaphore/run-win-fv - - name: "Felix: FV Tests" + - export AZURE_RESOURCE_GROUP=${RG_PREFIX}-rg + - echo AZURE_RESOURCE_GROUP=${AZURE_RESOURCE_GROUP} + - ../.semaphore/run-and-monitor win-fv-felix.log ~/calico/process/testing/winfv-felix/run-win-fv.sh + - name: "Felix: iptables tests on Ubuntu 22.04" run: - when: "change_in(['/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/types/*.go','/crypto/pkg/tls/*.go','/felix/**','/goldmane/pkg/client/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/apis/v3/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/k8s/scheme/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/bgpsyncer/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/nodestatussyncer/*.go','/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/multireadbuf/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/upgrade/converters/*.go','/libcalico-go/lib/upgrade/migrator/*.go','/libcalico-go/lib/upgrade/migrator/clients/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/libcalico-go/lib/writelogger/*.go','/metadata.mk','/node/pkg/lifecycle/utils/*.go','/pkg/buildinfo/*.go','/pod2daemon/binder/*.go','/typha/cmd/calico-typha/*.go','/typha/cmd/typha-client/*.go','/typha/pkg/calc/*.go','/typha/pkg/config/*.go','/typha/pkg/daemon/*.go','/typha/pkg/discovery/*.go','/typha/pkg/jitter/*.go','/typha/pkg/k8s/*.go','/typha/pkg/logutils/*.go','/typha/pkg/promutils/*.go','/typha/pkg/snapcache/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/syncserver/*.go','/typha/pkg/tlsutils/*.go','typha/**/*Dockerfile*','typha/Makefile','typha/deps.txt'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/app-policy/**/*_test.go','/cni-plugin/**/*_test.go','/crypto/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-felix.yml','/.semaphore/vms/','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/types/*.go','/crypto/pkg/tls/*.go','/felix/**','/goldmane/pkg/client/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/bgpsyncer/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/nodestatussyncer/*.go','/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/consistenthash/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/multireadbuf/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/testutils/stacktrace/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/libcalico-go/lib/writelogger/*.go','/metadata.mk','/node/pkg/lifecycle/utils/*.go','/pkg/buildinfo/*.go','/pod2daemon/binder/*.go','/typha/cmd/calico-typha/*.go','/typha/cmd/typha-client/*.go','/typha/pkg/calc/*.go','/typha/pkg/config/*.go','/typha/pkg/daemon/*.go','/typha/pkg/discovery/*.go','/typha/pkg/jitter/*.go','/typha/pkg/k8s/*.go','/typha/pkg/logutils/*.go','/typha/pkg/promutils/*.go','/typha/pkg/snapcache/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/syncserver/*.go','/typha/pkg/tlsutils/*.go','typha/**/*Dockerfile*','typha/Makefile','typha/deps.txt'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/app-policy/**/*_test.go','/cni-plugin/**/*_test.go','/crypto/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go']})" dependencies: - "Felix: Build" task: - agent: - machine: - type: f1-standard-4 - os_image: ubuntu2204 - prologue: - commands: - - cd felix - - cache restore go-pkg-cache - - cache restore go-mod-cache - - "cache restore bin-${SEMAPHORE_GIT_SHA}" - - "cache restore fv.test-${SEMAPHORE_GIT_SHA}" - - "cache restore felix-image-${SEMAPHORE_GIT_SHA}" - - "cache restore felixtest-typha-image-${SEMAPHORE_GIT_SHA}" - - |- - if [ -s /etc/docker/daemon.json ]; then - sudo sed -i '$d' /etc/docker/daemon.json && sudo sed -i '$s/$/,/' /etc/docker/daemon.json && sudo bash -c ' cat >> /etc/docker/daemon.json << EOF - "ipv6": true, - "fixed-cidr-v6": "2001:db8:1::/64" - } - EOF - ' ; else sudo bash -c ' cat > /etc/docker/daemon.json << EOF - { - "ipv6": true, - "fixed-cidr-v6": "2001:db8:1::/64" - } - EOF - ' ; fi - - sudo systemctl restart docker - # Load in the docker images pre-built by the build job. - - docker load -i /tmp/calico-felix-test.tar - - docker tag calico/felix-test:latest-amd64 felix-test:latest-amd64 - - rm /tmp/calico-felix-test.tar - - docker load -i /tmp/felixtest-typha.tar - - docker tag felix-test/typha:latest-amd64 typha:latest-amd64 - - rm /tmp/felixtest-typha.tar - # Pre-loading the IPIP module prevents a flake where the first felix to use IPIP loads the module and - # routing in that first felix container chooses different source IPs than the tests are expecting. - - sudo modprobe ipip jobs: - - name: FV Test matrix + - name: "Felix: iptables tests on Ubuntu 22.04" execution_time_limit: - minutes: 120 + minutes: 60 + env_vars: + - name: FELIX_TEST_GROUP + value: "22.04-ipt" commands: - - make check-wireguard - - ../.semaphore/run-and-monitor fv-${SEMAPHORE_JOB_INDEX}.log make fv-no-prereqs FV_BATCHES_TO_RUN="${SEMAPHORE_JOB_INDEX}" FV_NUM_BATCHES=${SEMAPHORE_JOB_COUNT} - parallelism: 3 - - name: nftables FV Test matrix + - source felix/.semaphore/fv-prologue + - export NUM_FV_BATCHES=16 + - .semaphore/vms/run-tests-on-vms ${VM_PREFIX} + epilogue: + always: + commands: + - source felix/.semaphore/fv-epilogue + secrets: + - name: google-service-account-for-gce + - name: "Felix: nftables tests on Ubuntu 22.04" + run: + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-felix.yml','/.semaphore/vms/','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/types/*.go','/crypto/pkg/tls/*.go','/felix/**','/goldmane/pkg/client/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/bgpsyncer/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/nodestatussyncer/*.go','/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/consistenthash/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/multireadbuf/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/testutils/stacktrace/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/libcalico-go/lib/writelogger/*.go','/metadata.mk','/node/pkg/lifecycle/utils/*.go','/pkg/buildinfo/*.go','/pod2daemon/binder/*.go','/typha/cmd/calico-typha/*.go','/typha/cmd/typha-client/*.go','/typha/pkg/calc/*.go','/typha/pkg/config/*.go','/typha/pkg/daemon/*.go','/typha/pkg/discovery/*.go','/typha/pkg/jitter/*.go','/typha/pkg/k8s/*.go','/typha/pkg/logutils/*.go','/typha/pkg/promutils/*.go','/typha/pkg/snapcache/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/syncserver/*.go','/typha/pkg/tlsutils/*.go','typha/**/*Dockerfile*','typha/Makefile','typha/deps.txt'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/app-policy/**/*_test.go','/cni-plugin/**/*_test.go','/crypto/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go']})" + dependencies: + - "Felix: Build" + task: + jobs: + - name: "Felix: nftables tests on Ubuntu 22.04" execution_time_limit: - minutes: 120 + minutes: 60 env_vars: - - name: FELIX_FV_NFTABLES - value: "Enabled" + - name: FELIX_TEST_GROUP + value: "22.04-nft" commands: - - make check-wireguard - - ../.semaphore/run-and-monitor fv-${SEMAPHORE_JOB_INDEX}.log make fv-no-prereqs FV_BATCHES_TO_RUN="${SEMAPHORE_JOB_INDEX}" FV_NUM_BATCHES=${SEMAPHORE_JOB_COUNT} - parallelism: 3 + - source felix/.semaphore/fv-prologue + - export NUM_FV_BATCHES=16 + - .semaphore/vms/run-tests-on-vms ${VM_PREFIX} epilogue: always: commands: - - ./.semaphore/collect-artifacts - - ./.semaphore/publish-artifacts - - test-results publish /home/semaphore/calico/felix/report/fv_suite.xml --name "felix-fv-${SEMAPHORE_JOB_INDEX}" || true - - test-results publish /home/semaphore/calico/felix/report/fv_nft_suite.xml --name "felix-fv-nft-${SEMAPHORE_JOB_INDEX}" || true - - name: "Felix: BPF UT/FV tests on Ubuntu 24.04" + - source felix/.semaphore/fv-epilogue + secrets: + - name: google-service-account-for-gce + - name: "Felix: BPF tests on Ubuntu 24.04 (iptables)" run: - when: "change_in(['/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/types/*.go','/crypto/pkg/tls/*.go','/felix/**','/goldmane/pkg/client/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/apis/v3/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/k8s/scheme/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/bgpsyncer/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/nodestatussyncer/*.go','/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/multireadbuf/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/upgrade/converters/*.go','/libcalico-go/lib/upgrade/migrator/*.go','/libcalico-go/lib/upgrade/migrator/clients/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/libcalico-go/lib/writelogger/*.go','/metadata.mk','/node/pkg/lifecycle/utils/*.go','/pkg/buildinfo/*.go','/pod2daemon/binder/*.go','/typha/cmd/calico-typha/*.go','/typha/cmd/typha-client/*.go','/typha/pkg/calc/*.go','/typha/pkg/config/*.go','/typha/pkg/daemon/*.go','/typha/pkg/discovery/*.go','/typha/pkg/jitter/*.go','/typha/pkg/k8s/*.go','/typha/pkg/logutils/*.go','/typha/pkg/promutils/*.go','/typha/pkg/snapcache/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/syncserver/*.go','/typha/pkg/tlsutils/*.go','typha/**/*Dockerfile*','typha/Makefile','typha/deps.txt'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/app-policy/**/*_test.go','/cni-plugin/**/*_test.go','/crypto/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-felix.yml','/.semaphore/vms/','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/types/*.go','/crypto/pkg/tls/*.go','/felix/**','/goldmane/pkg/client/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/bgpsyncer/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/nodestatussyncer/*.go','/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/consistenthash/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/multireadbuf/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/testutils/stacktrace/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/libcalico-go/lib/writelogger/*.go','/metadata.mk','/node/pkg/lifecycle/utils/*.go','/pkg/buildinfo/*.go','/pod2daemon/binder/*.go','/typha/cmd/calico-typha/*.go','/typha/cmd/typha-client/*.go','/typha/pkg/calc/*.go','/typha/pkg/config/*.go','/typha/pkg/daemon/*.go','/typha/pkg/discovery/*.go','/typha/pkg/jitter/*.go','/typha/pkg/k8s/*.go','/typha/pkg/logutils/*.go','/typha/pkg/promutils/*.go','/typha/pkg/snapcache/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/syncserver/*.go','/typha/pkg/tlsutils/*.go','typha/**/*Dockerfile*','typha/Makefile','typha/deps.txt'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/app-policy/**/*_test.go','/cni-plugin/**/*_test.go','/crypto/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go']})" dependencies: - - Prerequisites + - "Felix: Build" task: - prologue: - commands: - - cd felix - - export GOOGLE_APPLICATION_CREDENTIALS=$HOME/secrets/secret.google-service-account-key.json - - export SHORT_WORKFLOW_ID=$(echo ${SEMAPHORE_WORKFLOW_ID} | sha256sum | cut -c -8) - - export ZONE=europe-west3-c - - export VM_PREFIX=sem-${SEMAPHORE_PROJECT_NAME}-${SHORT_WORKFLOW_ID}-felix-ipt- - - echo VM_PREFIX=${VM_PREFIX} - - export REPO_NAME=$(basename $(pwd)) - - export NUM_FV_BATCHES=8 - - export RUN_UT=true - - export FV_FOCUS=BPF-SAFE - - export IMAGE=ubuntu-2404-noble-amd64-v20250502a - - export UBUNTU_VERSION=noble - - export DOCKER_VERSION=5:27.5.1-1~ubuntu.24.04~noble - - mkdir artifacts - - ./.semaphore/create-test-vms ${VM_PREFIX} jobs: - - name: UT/FV tests on new kernel + - name: "Felix: BPF tests on Ubuntu 24.04" execution_time_limit: - minutes: 180 + minutes: 60 + env_vars: + - name: FELIX_TEST_GROUP + value: "bpf-24.04-ipt-with-ut" commands: - - ./.semaphore/run-tests-on-vms ${VM_PREFIX} + - source felix/.semaphore/fv-prologue + - export NUM_FV_BATCHES=60 + - .semaphore/vms/run-tests-on-vms ${VM_PREFIX} epilogue: always: commands: - - ./.semaphore/collect-artifacts-from-vms ${VM_PREFIX} - - ./.semaphore/publish-artifacts - - ./.semaphore/clean-up-vms ${VM_PREFIX} + - source felix/.semaphore/fv-epilogue secrets: - name: google-service-account-for-gce - - name: "Felix: BPF UT/FV tests on Ubuntu 22.04 (nftables)" + - name: "Felix: BPF tests on Ubuntu 22.04 (nftables)" run: - when: "change_in(['/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/types/*.go','/crypto/pkg/tls/*.go','/felix/**','/goldmane/pkg/client/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/apis/v3/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/k8s/scheme/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/bgpsyncer/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/nodestatussyncer/*.go','/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/multireadbuf/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/upgrade/converters/*.go','/libcalico-go/lib/upgrade/migrator/*.go','/libcalico-go/lib/upgrade/migrator/clients/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/libcalico-go/lib/writelogger/*.go','/metadata.mk','/node/pkg/lifecycle/utils/*.go','/pkg/buildinfo/*.go','/pod2daemon/binder/*.go','/typha/cmd/calico-typha/*.go','/typha/cmd/typha-client/*.go','/typha/pkg/calc/*.go','/typha/pkg/config/*.go','/typha/pkg/daemon/*.go','/typha/pkg/discovery/*.go','/typha/pkg/jitter/*.go','/typha/pkg/k8s/*.go','/typha/pkg/logutils/*.go','/typha/pkg/promutils/*.go','/typha/pkg/snapcache/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/syncserver/*.go','/typha/pkg/tlsutils/*.go','typha/**/*Dockerfile*','typha/Makefile','typha/deps.txt'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/app-policy/**/*_test.go','/cni-plugin/**/*_test.go','/crypto/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-felix.yml','/.semaphore/vms/','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/types/*.go','/crypto/pkg/tls/*.go','/felix/**','/goldmane/pkg/client/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/bgpsyncer/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/nodestatussyncer/*.go','/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/consistenthash/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/multireadbuf/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/testutils/stacktrace/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/libcalico-go/lib/writelogger/*.go','/metadata.mk','/node/pkg/lifecycle/utils/*.go','/pkg/buildinfo/*.go','/pod2daemon/binder/*.go','/typha/cmd/calico-typha/*.go','/typha/cmd/typha-client/*.go','/typha/pkg/calc/*.go','/typha/pkg/config/*.go','/typha/pkg/daemon/*.go','/typha/pkg/discovery/*.go','/typha/pkg/jitter/*.go','/typha/pkg/k8s/*.go','/typha/pkg/logutils/*.go','/typha/pkg/promutils/*.go','/typha/pkg/snapcache/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/syncserver/*.go','/typha/pkg/tlsutils/*.go','typha/**/*Dockerfile*','typha/Makefile','typha/deps.txt'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/app-policy/**/*_test.go','/cni-plugin/**/*_test.go','/crypto/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go']})" dependencies: - - Prerequisites + - "Felix: Build" + task: + jobs: + - name: "Felix: BPF tests on Ubuntu 22.04 (nftables)" + execution_time_limit: + minutes: 60 + env_vars: + - name: FELIX_TEST_GROUP + value: "bpf-22.04-nft-with-ut" + - name: FELIX_FV_BPFATTACHTYPE + value: "tc" + commands: + - source felix/.semaphore/fv-prologue + # Limit to BPF conntrack tests to reduce overall runtime. + - export FV_FOCUS='_BPF_.*ct=true' + - export NUM_FV_BATCHES=10 + - .semaphore/vms/run-tests-on-vms ${VM_PREFIX} + epilogue: + always: + commands: + - source felix/.semaphore/fv-epilogue + secrets: + - name: google-service-account-for-gce + - name: "Felix: BPF program-loading check on 25.10 (nftables)" + run: + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-felix.yml','/.semaphore/vms/','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/types/*.go','/crypto/pkg/tls/*.go','/felix/**','/goldmane/pkg/client/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/bgpsyncer/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/nodestatussyncer/*.go','/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/consistenthash/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/multireadbuf/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/testutils/stacktrace/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/libcalico-go/lib/writelogger/*.go','/metadata.mk','/node/pkg/lifecycle/utils/*.go','/pkg/buildinfo/*.go','/pod2daemon/binder/*.go','/typha/cmd/calico-typha/*.go','/typha/cmd/typha-client/*.go','/typha/pkg/calc/*.go','/typha/pkg/config/*.go','/typha/pkg/daemon/*.go','/typha/pkg/discovery/*.go','/typha/pkg/jitter/*.go','/typha/pkg/k8s/*.go','/typha/pkg/logutils/*.go','/typha/pkg/promutils/*.go','/typha/pkg/snapcache/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/syncserver/*.go','/typha/pkg/tlsutils/*.go','typha/**/*Dockerfile*','typha/Makefile','typha/deps.txt'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/app-policy/**/*_test.go','/cni-plugin/**/*_test.go','/crypto/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go']})" + dependencies: + - "Felix: Build" task: - prologue: - commands: - - cd felix - - export GOOGLE_APPLICATION_CREDENTIALS=$HOME/secrets/secret.google-service-account-key.json - - export SHORT_WORKFLOW_ID=$(echo ${SEMAPHORE_WORKFLOW_ID} | sha256sum | cut -c -8) - - export ZONE=europe-west3-c - - export VM_PREFIX=sem-${SEMAPHORE_PROJECT_NAME}-${SHORT_WORKFLOW_ID}-felix-nft- - - echo VM_PREFIX=${VM_PREFIX} - - export REPO_NAME=$(basename $(pwd)) - - export NUM_FV_BATCHES=4 - - export RUN_UT=true - - export FV_FOCUS='_BPF_.*ct=true' - - mkdir artifacts - - ./.semaphore/create-test-vms ${VM_PREFIX} jobs: - - name: UT/FV tests on new kernel + - name: "Felix: BPF program-loading check on 25.10" + execution_time_limit: + minutes: 30 env_vars: - - name: FELIX_FV_NFTABLES - value: "Enabled" + - name: FELIX_TEST_GROUP + value: "bpf-25.10-nft-no-fv-with-ut" - name: FELIX_FV_BPFATTACHTYPE value: "tc" + commands: + - source felix/.semaphore/fv-prologue + # Only run the UT that checks that the precompiled BPF binaries are loadable. + - export UT_FOCUS="TestPrecompiledBinariesAreLoadable" + - .semaphore/vms/run-tests-on-vms ${VM_PREFIX} + epilogue: + always: + commands: + - source felix/.semaphore/fv-epilogue + secrets: + - name: google-service-account-for-gce + - name: "Felix: BPF tests on Ubuntu 25.10 with jitharden=2 (nftables)" + run: + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-felix.yml','/.semaphore/vms/','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/types/*.go','/crypto/pkg/tls/*.go','/felix/**','/goldmane/pkg/client/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/bgpsyncer/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/nodestatussyncer/*.go','/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/consistenthash/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/multireadbuf/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/testutils/stacktrace/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/libcalico-go/lib/writelogger/*.go','/metadata.mk','/node/pkg/lifecycle/utils/*.go','/pkg/buildinfo/*.go','/pod2daemon/binder/*.go','/typha/cmd/calico-typha/*.go','/typha/cmd/typha-client/*.go','/typha/pkg/calc/*.go','/typha/pkg/config/*.go','/typha/pkg/daemon/*.go','/typha/pkg/discovery/*.go','/typha/pkg/jitter/*.go','/typha/pkg/k8s/*.go','/typha/pkg/logutils/*.go','/typha/pkg/promutils/*.go','/typha/pkg/snapcache/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/syncserver/*.go','/typha/pkg/tlsutils/*.go','typha/**/*Dockerfile*','typha/Makefile','typha/deps.txt'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/app-policy/**/*_test.go','/cni-plugin/**/*_test.go','/crypto/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go']})" + dependencies: + - "Felix: Build" + task: + jobs: + - name: "Felix: BPF tests on Ubuntu 25.10 with jitharden=2" execution_time_limit: - minutes: 180 + minutes: 60 + env_vars: + - name: FELIX_TEST_GROUP + value: "bpf-25.10-nft-with-ut-jitharden" + - name: FELIX_FV_BPFATTACHTYPE + value: "tc" commands: - - ./.semaphore/run-tests-on-vms ${VM_PREFIX} + - source felix/.semaphore/fv-prologue + # Limit to BPF TCP+conntrack tests to reduce overall runtime. + - export FV_FOCUS='_BPF_.*tcp.*ct=true' + - export NUM_FV_BATCHES=16 + - .semaphore/vms/run-tests-on-vms ${VM_PREFIX} epilogue: always: commands: - - ./.semaphore/collect-artifacts-from-vms ${VM_PREFIX} - - ./.semaphore/publish-artifacts - - ./.semaphore/clean-up-vms ${VM_PREFIX} + - source felix/.semaphore/fv-epilogue secrets: - name: google-service-account-for-gce - - name: goldmane + - name: Goldmane run: - when: "change_in(['/crypto/pkg/tls/*.go','/goldmane/**','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/set/*.go','/metadata.mk'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/crypto/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-goldmane.yml','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/crypto/pkg/tls/*.go','/felix/proto/*.go','/felix/types/*.go','/goldmane/**','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/set/*.go','/metadata.mk'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/crypto/**/*_test.go','/felix/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go']})" execution_time_limit: minutes: 30 dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "goldmane" prologue: commands: # The Makefile sometimes tries to rebuild the protobuf files on non-amd architectures @@ -677,10 +968,13 @@ blocks: - touch goldmane/proto/api.pb.go - cd goldmane jobs: - - name: make ci + - name: "Goldmane: CI" commands: - ../.semaphore/run-and-monitor make-ci.log make ci - - name: Build binary + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: "Goldmane: build multi-arch" matrix: - env_var: ARCH values: @@ -689,22 +983,28 @@ blocks: - s390x commands: - ../.semaphore/run-and-monitor image-$ARCH.log make build ARCH=$ARCH - - name: guardian + - name: Guardian run: - when: "change_in(['/crypto/pkg/tls/*.go','/guardian/**','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/cryptoutils/*.go','/lib/std/time/*.go','/libcalico-go/lib/logutils/*.go','/metadata.mk','/pkg/buildinfo/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/crypto/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/pkg/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-guardian.yml','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/crypto/pkg/tls/*.go','/guardian/**','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/cryptoutils/*.go','/lib/std/time/*.go','/libcalico-go/lib/logutils/*.go','/metadata.mk','/pkg/buildinfo/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/crypto/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/pkg/**/*_test.go']})" execution_time_limit: minutes: 30 dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "guardian" prologue: commands: - cd guardian jobs: - - name: make ci + - name: "Guardian: CI" commands: - ../.semaphore/run-and-monitor make-ci.log make ci - - name: Build binary + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: "Guardian: build multi-arch" matrix: - env_var: ARCH values: @@ -715,18 +1015,33 @@ blocks: - ../.semaphore/run-and-monitor image-$ARCH.log make build ARCH=$ARCH - name: kube-controllers run: - when: "change_in(['/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/crypto/pkg/tls/*.go','/felix/bpf/tc/defs/*.go','/felix/calc/*.go','/felix/config/*.go','/felix/dataplane/ipsets/*.go','/felix/dataplane/linux/dataplanedefs/*.go','/felix/deltatracker/*.go','/felix/dispatcher/*.go','/felix/environment/*.go','/felix/fv/connectivity/*.go','/felix/fv/containers/*.go','/felix/fv/tcpdump/*.go','/felix/fv/utils/*.go','/felix/generictables/*.go','/felix/hashutils/*.go','/felix/idalloc/*.go','/felix/ip/*.go','/felix/ipsets/*.go','/felix/iptables/*.go','/felix/iptables/cmdshim/*.go','/felix/k8sutils/*.go','/felix/labelindex/*.go','/felix/labelindex/ipsetmember/*.go','/felix/labelindex/labelnamevalueindex/*.go','/felix/labelindex/labelrestrictionindex/*.go','/felix/logutils/*.go','/felix/markbits/*.go','/felix/multidict/*.go','/felix/netlinkshim/*.go','/felix/nftables/*.go','/felix/proto/*.go','/felix/rules/*.go','/felix/serviceindex/*.go','/felix/stringutils/*.go','/felix/types/*.go','/hack/test/certs/','/kube-controllers/**','/lib.Makefile','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/apis/v3/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/k8s/scheme/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/node/pkg/lifecycle/utils/*.go','/pkg/buildinfo/*.go','/pkg/cmdwrapper/*.go','/typha/pkg/config/*.go','/typha/pkg/logutils/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/crypto/**/*_test.go','/felix/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go','/typha/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-kube-controllers.yml','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/client/clientset_generated/clientset/*.go','/api/pkg/client/clientset_generated/clientset/scheme/*.go','/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/*.go','/api/pkg/client/informers_generated/externalversions/*.go','/api/pkg/client/informers_generated/externalversions/internalinterfaces/*.go','/api/pkg/client/informers_generated/externalversions/projectcalico/*.go','/api/pkg/client/informers_generated/externalversions/projectcalico/v3/*.go','/api/pkg/client/listers_generated/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/crypto/pkg/tls/*.go','/felix/bpf/tc/defs/*.go','/felix/calc/*.go','/felix/config/*.go','/felix/dataplane/ipsets/*.go','/felix/dataplane/linux/dataplanedefs/*.go','/felix/deltatracker/*.go','/felix/dispatcher/*.go','/felix/environment/*.go','/felix/fv/connectivity/*.go','/felix/fv/containers/*.go','/felix/fv/tcpdump/*.go','/felix/fv/utils/*.go','/felix/generictables/*.go','/felix/idalloc/*.go','/felix/ip/*.go','/felix/ipsets/*.go','/felix/iptables/*.go','/felix/iptables/cmdshim/*.go','/felix/k8sutils/*.go','/felix/labelindex/*.go','/felix/labelindex/ipsetmember/*.go','/felix/labelindex/labelnamevalueindex/*.go','/felix/labelindex/labelrestrictionindex/*.go','/felix/logutils/*.go','/felix/markbits/*.go','/felix/multidict/*.go','/felix/netlinkshim/*.go','/felix/nftables/*.go','/felix/proto/*.go','/felix/rules/*.go','/felix/serviceindex/*.go','/felix/stringutils/*.go','/felix/types/*.go','/hack/test/certs/','/kube-controllers/**','/lib.Makefile','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/consistenthash/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/testutils/stacktrace/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/node/pkg/lifecycle/utils/*.go','/pkg/buildinfo/*.go','/pkg/cmdwrapper/*.go','/typha/pkg/config/*.go','/typha/pkg/logutils/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/crypto/**/*_test.go','/felix/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/node/**/*_test.go','/pkg/**/*_test.go','/typha/**/*_test.go']})" dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "kube-controllers" prologue: commands: - cd kube-controllers jobs: - - name: "kube-controllers: tests" + - name: "kube-controllers: CI" commands: - ../.semaphore/run-and-monitor ci.log make ci - - name: lib + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + # Run a subset of the kube-controllers CI tests that use the projectcalico.org/v3 API group for CRDs. + - name: "kube-controllers: CI (projectcalico.org/v3)" + commands: + - ../.semaphore/run-and-monitor ut.log make image ut + env_vars: + - name: CALICO_API_GROUP + value: "projectcalico.org/v3" + - name: STORE_BUILD_CACHE + value: "true" + - name: Libraries (lib) run: when: "false or change_in(['/lib/'], {pipeline_file: 'ignore'})" execution_time_limit: @@ -738,42 +1053,69 @@ blocks: commands: - cd lib jobs: - - name: make ci + - name: "Libraries: CI" commands: - ../.semaphore/run-and-monitor make-ci.log make ci - name: libcalico-go run: - when: "change_in(['/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/crypto/pkg/tls/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/**','/metadata.mk','/typha/pkg/discovery/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/tlsutils/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/crypto/**/*_test.go','/lib/**/*_test.go','/typha/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-libcalico-go.yml','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/crypto/pkg/tls/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/**','/metadata.mk','/typha/pkg/discovery/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/tlsutils/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/crypto/**/*_test.go','/lib/**/*_test.go','/typha/**/*_test.go']})" dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "libcalico-go" prologue: commands: - cd libcalico-go jobs: - - name: "libcalico-go: tests" + # Run the tests twice, once for each backing API version. + - name: "libcalico-go: CI (crd.projectcalico.org/v1)" commands: - ../.semaphore/run-and-monitor make-ci.log make ci + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: "libcalico-go: CI (projectcalico.org/v3)" + commands: + - ../.semaphore/run-and-monitor make-ci.log make ci + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: CALICO_API_GROUP + value: projectcalico.org/v3 + - name: GINKGO_SKIP + value: "etcdv3 backend" - name: "Node: Build" run: - when: "change_in(['/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/cni-plugin/internal/pkg/azure/*.go','/cni-plugin/internal/pkg/utils/*.go','/cni-plugin/internal/pkg/utils/cri/*.go','/cni-plugin/pkg/dataplane/*.go','/cni-plugin/pkg/dataplane/grpc/*.go','/cni-plugin/pkg/dataplane/grpc/proto/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/ipamplugin/*.go','/cni-plugin/pkg/k8s/*.go','/cni-plugin/pkg/plugin/*.go','/cni-plugin/pkg/types/*.go','/cni-plugin/pkg/upgrade/*.go','/cni-plugin/pkg/wait/*.go','/confd/etc','/confd/pkg/backends/*.go','/confd/pkg/backends/calico/*.go','/confd/pkg/config/*.go','/confd/pkg/log/*.go','/confd/pkg/resource/template/*.go','/confd/pkg/run/*.go','/crypto/pkg/tls/*.go','/felix/aws/*.go','/felix/bpf-apache','/felix/bpf-gpl','/felix/bpf/*.go','/felix/bpf/arp/*.go','/felix/bpf/asm/*.go','/felix/bpf/bpfdefs/*.go','/felix/bpf/bpfmap/*.go','/felix/bpf/conntrack/*.go','/felix/bpf/conntrack/cleanupv1/*.go','/felix/bpf/conntrack/timeouts/*.go','/felix/bpf/conntrack/v2/*.go','/felix/bpf/conntrack/v3/*.go','/felix/bpf/conntrack/v4/*.go','/felix/bpf/counters/*.go','/felix/bpf/events/*.go','/felix/bpf/failsafes/*.go','/felix/bpf/filter/*.go','/felix/bpf/hook/*.go','/felix/bpf/ifstate/*.go','/felix/bpf/ipsets/*.go','/felix/bpf/jump/*.go','/felix/bpf/legacy/*.go','/felix/bpf/libbpf/*.go','/felix/bpf/maps/*.go','/felix/bpf/nat/*.go','/felix/bpf/perf/*.go','/felix/bpf/polprog/*.go','/felix/bpf/profiling/*.go','/felix/bpf/proxy/*.go','/felix/bpf/qos/*.go','/felix/bpf/routes/*.go','/felix/bpf/state/*.go','/felix/bpf/tc/*.go','/felix/bpf/tc/defs/*.go','/felix/bpf/utils/*.go','/felix/bpf/xdp/*.go','/felix/cachingmap/*.go','/felix/calc/*.go','/felix/cmd/calico-bpf/commands/*.go','/felix/collector/*.go','/felix/collector/flowlog/*.go','/felix/collector/goldmane/*.go','/felix/collector/local/*.go','/felix/collector/types/*.go','/felix/collector/types/counter/*.go','/felix/collector/types/endpoint/*.go','/felix/collector/types/metric/*.go','/felix/collector/types/tuple/*.go','/felix/collector/utils/*.go','/felix/config/*.go','/felix/conntrack/*.go','/felix/daemon/*.go','/felix/dataplane/*.go','/felix/dataplane/common/*.go','/felix/dataplane/external/*.go','/felix/dataplane/inactive/*.go','/felix/dataplane/ipsets/*.go','/felix/dataplane/linux/*.go','/felix/dataplane/linux/dataplanedefs/*.go','/felix/dataplane/linux/qos/*.go','/felix/deltatracker/*.go','/felix/dispatcher/*.go','/felix/environment/*.go','/felix/ethtool/*.go','/felix/generictables/*.go','/felix/hashutils/*.go','/felix/idalloc/*.go','/felix/ifacemonitor/*.go','/felix/ip/*.go','/felix/ipsets/*.go','/felix/iptables/*.go','/felix/iptables/cmdshim/*.go','/felix/jitter/*.go','/felix/k8sutils/*.go','/felix/labelindex/*.go','/felix/labelindex/ipsetmember/*.go','/felix/labelindex/labelnamevalueindex/*.go','/felix/labelindex/labelrestrictionindex/*.go','/felix/linkaddrs/*.go','/felix/logutils/*.go','/felix/markbits/*.go','/felix/multidict/*.go','/felix/netlinkshim/*.go','/felix/netlinkshim/handlemgr/*.go','/felix/nfnetlink/*.go','/felix/nfnetlink/nfnl/*.go','/felix/nfnetlink/pkt/*.go','/felix/nftables/*.go','/felix/policysync/*.go','/felix/proto/*.go','/felix/proto/protoconv/*.go','/felix/routerule/*.go','/felix/routetable/*.go','/felix/routetable/ownershippol/*.go','/felix/rules/*.go','/felix/serviceindex/*.go','/felix/statusrep/*.go','/felix/stringutils/*.go','/felix/throttle/*.go','/felix/timeshim/*.go','/felix/types/*.go','/felix/usagerep/*.go','/felix/vxlanfdb/*.go','/felix/wireguard/*.go','/goldmane/pkg/client/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/apis/v3/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/k8s/scheme/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/bgpsyncer/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/nodestatussyncer/*.go','/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/upgrade/converters/*.go','/libcalico-go/lib/upgrade/migrator/*.go','/libcalico-go/lib/upgrade/migrator/clients/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/node/**','/pkg/buildinfo/*.go','/pod2daemon/binder/*.go','/typha/pkg/discovery/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncclientutils/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/tlsutils/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/app-policy/**/*_test.go','/cni-plugin/**/*_test.go','/confd/**/*_test.go','/crypto/**/*_test.go','/felix/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-node.yml','/Makefile','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/cni-plugin/internal/pkg/azure/*.go','/cni-plugin/internal/pkg/utils/*.go','/cni-plugin/internal/pkg/utils/cri/*.go','/cni-plugin/pkg/dataplane/*.go','/cni-plugin/pkg/dataplane/grpc/*.go','/cni-plugin/pkg/dataplane/grpc/proto/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/ipamplugin/*.go','/cni-plugin/pkg/k8s/*.go','/cni-plugin/pkg/plugin/*.go','/cni-plugin/pkg/types/*.go','/cni-plugin/pkg/upgrade/*.go','/cni-plugin/pkg/wait/*.go','/confd/etc','/confd/pkg/backends/*.go','/confd/pkg/backends/calico/*.go','/confd/pkg/backends/types/*.go','/confd/pkg/config/*.go','/confd/pkg/log/*.go','/confd/pkg/resource/template/*.go','/confd/pkg/run/*.go','/crypto/pkg/tls/*.go','/felix/aws/*.go','/felix/bpf-apache','/felix/bpf-gpl','/felix/bpf/*.go','/felix/bpf/arp/*.go','/felix/bpf/asm/*.go','/felix/bpf/bpfdefs/*.go','/felix/bpf/bpfmap/*.go','/felix/bpf/conntrack/*.go','/felix/bpf/conntrack/cleanupv1/*.go','/felix/bpf/conntrack/timeouts/*.go','/felix/bpf/conntrack/v2/*.go','/felix/bpf/conntrack/v3/*.go','/felix/bpf/conntrack/v4/*.go','/felix/bpf/consistenthash/*.go','/felix/bpf/counters/*.go','/felix/bpf/events/*.go','/felix/bpf/failsafes/*.go','/felix/bpf/filter/*.go','/felix/bpf/hook/*.go','/felix/bpf/ifstate/*.go','/felix/bpf/ipfrags/*.go','/felix/bpf/ipsets/*.go','/felix/bpf/jump/*.go','/felix/bpf/legacy/*.go','/felix/bpf/libbpf/*.go','/felix/bpf/maps/*.go','/felix/bpf/nat/*.go','/felix/bpf/perf/*.go','/felix/bpf/polprog/*.go','/felix/bpf/profiling/*.go','/felix/bpf/proxy/*.go','/felix/bpf/qos/*.go','/felix/bpf/routes/*.go','/felix/bpf/state/*.go','/felix/bpf/tc/*.go','/felix/bpf/tc/defs/*.go','/felix/bpf/utils/*.go','/felix/bpf/xdp/*.go','/felix/cachingmap/*.go','/felix/calc/*.go','/felix/cmd/calico-bpf/commands/*.go','/felix/collector/*.go','/felix/collector/flowlog/*.go','/felix/collector/goldmane/*.go','/felix/collector/local/*.go','/felix/collector/types/*.go','/felix/collector/types/counter/*.go','/felix/collector/types/endpoint/*.go','/felix/collector/types/metric/*.go','/felix/collector/types/tuple/*.go','/felix/collector/utils/*.go','/felix/config/*.go','/felix/conntrack/*.go','/felix/daemon/*.go','/felix/dataplane/*.go','/felix/dataplane/common/*.go','/felix/dataplane/external/*.go','/felix/dataplane/inactive/*.go','/felix/dataplane/ipsets/*.go','/felix/dataplane/linux/*.go','/felix/dataplane/linux/dataplanedefs/*.go','/felix/dataplane/linux/qos/*.go','/felix/deltatracker/*.go','/felix/dispatcher/*.go','/felix/environment/*.go','/felix/ethtool/*.go','/felix/generictables/*.go','/felix/idalloc/*.go','/felix/ifacemonitor/*.go','/felix/ip/*.go','/felix/ipsets/*.go','/felix/iptables/*.go','/felix/iptables/cmdshim/*.go','/felix/jitter/*.go','/felix/k8sutils/*.go','/felix/labelindex/*.go','/felix/labelindex/ipsetmember/*.go','/felix/labelindex/labelnamevalueindex/*.go','/felix/labelindex/labelrestrictionindex/*.go','/felix/linkaddrs/*.go','/felix/logutils/*.go','/felix/markbits/*.go','/felix/multidict/*.go','/felix/netlinkshim/*.go','/felix/netlinkshim/handlemgr/*.go','/felix/nfnetlink/*.go','/felix/nfnetlink/nfnl/*.go','/felix/nfnetlink/pkt/*.go','/felix/nftables/*.go','/felix/policysync/*.go','/felix/proto/*.go','/felix/proto/protoconv/*.go','/felix/routerule/*.go','/felix/routetable/*.go','/felix/routetable/ownershippol/*.go','/felix/rules/*.go','/felix/serviceindex/*.go','/felix/statusrep/*.go','/felix/stringutils/*.go','/felix/throttle/*.go','/felix/timeshim/*.go','/felix/types/*.go','/felix/usagerep/*.go','/felix/vxlanfdb/*.go','/felix/wireguard/*.go','/goldmane/pkg/client/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/bgpsyncer/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/nodestatussyncer/*.go','/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/consistenthash/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/ipam/vmipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/kubevirt/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/node/**','/pkg/buildinfo/*.go','/pod2daemon/binder/*.go','/typha/pkg/discovery/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncclientutils/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/tlsutils/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/app-policy/**/*_test.go','/cni-plugin/**/*_test.go','/confd/**/*_test.go','/crypto/**/*_test.go','/felix/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go']})" dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "node" agent: machine: type: f1-standard-4 - os_image: ubuntu2004 + os_image: ubuntu2204 prologue: commands: - cd node jobs: - name: "Node: CI" + priority: + # This job is on the critical path so we increase its priority. + - value: 51 + when: "pull_request =~ '.+'" commands: - ../.semaphore/run-and-monitor ci.log make ci + env_vars: + - name: STORE_BUILD_CACHE + value: "true" - name: "Node: multi-arch build" run: - when: "change_in(['/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/cni-plugin/internal/pkg/azure/*.go','/cni-plugin/internal/pkg/utils/*.go','/cni-plugin/internal/pkg/utils/cri/*.go','/cni-plugin/pkg/dataplane/*.go','/cni-plugin/pkg/dataplane/grpc/*.go','/cni-plugin/pkg/dataplane/grpc/proto/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/ipamplugin/*.go','/cni-plugin/pkg/k8s/*.go','/cni-plugin/pkg/plugin/*.go','/cni-plugin/pkg/types/*.go','/cni-plugin/pkg/upgrade/*.go','/cni-plugin/pkg/wait/*.go','/confd/etc','/confd/pkg/backends/*.go','/confd/pkg/backends/calico/*.go','/confd/pkg/config/*.go','/confd/pkg/log/*.go','/confd/pkg/resource/template/*.go','/confd/pkg/run/*.go','/crypto/pkg/tls/*.go','/felix/aws/*.go','/felix/bpf-apache','/felix/bpf-gpl','/felix/bpf/*.go','/felix/bpf/arp/*.go','/felix/bpf/asm/*.go','/felix/bpf/bpfdefs/*.go','/felix/bpf/bpfmap/*.go','/felix/bpf/conntrack/*.go','/felix/bpf/conntrack/cleanupv1/*.go','/felix/bpf/conntrack/timeouts/*.go','/felix/bpf/conntrack/v2/*.go','/felix/bpf/conntrack/v3/*.go','/felix/bpf/conntrack/v4/*.go','/felix/bpf/counters/*.go','/felix/bpf/events/*.go','/felix/bpf/failsafes/*.go','/felix/bpf/filter/*.go','/felix/bpf/hook/*.go','/felix/bpf/ifstate/*.go','/felix/bpf/ipsets/*.go','/felix/bpf/jump/*.go','/felix/bpf/legacy/*.go','/felix/bpf/libbpf/*.go','/felix/bpf/maps/*.go','/felix/bpf/nat/*.go','/felix/bpf/perf/*.go','/felix/bpf/polprog/*.go','/felix/bpf/profiling/*.go','/felix/bpf/proxy/*.go','/felix/bpf/qos/*.go','/felix/bpf/routes/*.go','/felix/bpf/state/*.go','/felix/bpf/tc/*.go','/felix/bpf/tc/defs/*.go','/felix/bpf/utils/*.go','/felix/bpf/xdp/*.go','/felix/cachingmap/*.go','/felix/calc/*.go','/felix/cmd/calico-bpf/commands/*.go','/felix/collector/*.go','/felix/collector/flowlog/*.go','/felix/collector/goldmane/*.go','/felix/collector/local/*.go','/felix/collector/types/*.go','/felix/collector/types/counter/*.go','/felix/collector/types/endpoint/*.go','/felix/collector/types/metric/*.go','/felix/collector/types/tuple/*.go','/felix/collector/utils/*.go','/felix/config/*.go','/felix/conntrack/*.go','/felix/daemon/*.go','/felix/dataplane/*.go','/felix/dataplane/common/*.go','/felix/dataplane/external/*.go','/felix/dataplane/inactive/*.go','/felix/dataplane/ipsets/*.go','/felix/dataplane/linux/*.go','/felix/dataplane/linux/dataplanedefs/*.go','/felix/dataplane/linux/qos/*.go','/felix/deltatracker/*.go','/felix/dispatcher/*.go','/felix/environment/*.go','/felix/ethtool/*.go','/felix/generictables/*.go','/felix/hashutils/*.go','/felix/idalloc/*.go','/felix/ifacemonitor/*.go','/felix/ip/*.go','/felix/ipsets/*.go','/felix/iptables/*.go','/felix/iptables/cmdshim/*.go','/felix/jitter/*.go','/felix/k8sutils/*.go','/felix/labelindex/*.go','/felix/labelindex/ipsetmember/*.go','/felix/labelindex/labelnamevalueindex/*.go','/felix/labelindex/labelrestrictionindex/*.go','/felix/linkaddrs/*.go','/felix/logutils/*.go','/felix/markbits/*.go','/felix/multidict/*.go','/felix/netlinkshim/*.go','/felix/netlinkshim/handlemgr/*.go','/felix/nfnetlink/*.go','/felix/nfnetlink/nfnl/*.go','/felix/nfnetlink/pkt/*.go','/felix/nftables/*.go','/felix/policysync/*.go','/felix/proto/*.go','/felix/proto/protoconv/*.go','/felix/routerule/*.go','/felix/routetable/*.go','/felix/routetable/ownershippol/*.go','/felix/rules/*.go','/felix/serviceindex/*.go','/felix/statusrep/*.go','/felix/stringutils/*.go','/felix/throttle/*.go','/felix/timeshim/*.go','/felix/types/*.go','/felix/usagerep/*.go','/felix/vxlanfdb/*.go','/felix/wireguard/*.go','/goldmane/pkg/client/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/apis/v3/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/k8s/scheme/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/bgpsyncer/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/nodestatussyncer/*.go','/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/upgrade/converters/*.go','/libcalico-go/lib/upgrade/migrator/*.go','/libcalico-go/lib/upgrade/migrator/clients/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/node/**','/pkg/buildinfo/*.go','/pod2daemon/binder/*.go','/typha/pkg/discovery/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncclientutils/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/tlsutils/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/app-policy/**/*_test.go','/cni-plugin/**/*_test.go','/confd/**/*_test.go','/crypto/**/*_test.go','/felix/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-node.yml','/Makefile','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/cni-plugin/internal/pkg/azure/*.go','/cni-plugin/internal/pkg/utils/*.go','/cni-plugin/internal/pkg/utils/cri/*.go','/cni-plugin/pkg/dataplane/*.go','/cni-plugin/pkg/dataplane/grpc/*.go','/cni-plugin/pkg/dataplane/grpc/proto/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/ipamplugin/*.go','/cni-plugin/pkg/k8s/*.go','/cni-plugin/pkg/plugin/*.go','/cni-plugin/pkg/types/*.go','/cni-plugin/pkg/upgrade/*.go','/cni-plugin/pkg/wait/*.go','/confd/etc','/confd/pkg/backends/*.go','/confd/pkg/backends/calico/*.go','/confd/pkg/backends/types/*.go','/confd/pkg/config/*.go','/confd/pkg/log/*.go','/confd/pkg/resource/template/*.go','/confd/pkg/run/*.go','/crypto/pkg/tls/*.go','/felix/aws/*.go','/felix/bpf-apache','/felix/bpf-gpl','/felix/bpf/*.go','/felix/bpf/arp/*.go','/felix/bpf/asm/*.go','/felix/bpf/bpfdefs/*.go','/felix/bpf/bpfmap/*.go','/felix/bpf/conntrack/*.go','/felix/bpf/conntrack/cleanupv1/*.go','/felix/bpf/conntrack/timeouts/*.go','/felix/bpf/conntrack/v2/*.go','/felix/bpf/conntrack/v3/*.go','/felix/bpf/conntrack/v4/*.go','/felix/bpf/consistenthash/*.go','/felix/bpf/counters/*.go','/felix/bpf/events/*.go','/felix/bpf/failsafes/*.go','/felix/bpf/filter/*.go','/felix/bpf/hook/*.go','/felix/bpf/ifstate/*.go','/felix/bpf/ipfrags/*.go','/felix/bpf/ipsets/*.go','/felix/bpf/jump/*.go','/felix/bpf/legacy/*.go','/felix/bpf/libbpf/*.go','/felix/bpf/maps/*.go','/felix/bpf/nat/*.go','/felix/bpf/perf/*.go','/felix/bpf/polprog/*.go','/felix/bpf/profiling/*.go','/felix/bpf/proxy/*.go','/felix/bpf/qos/*.go','/felix/bpf/routes/*.go','/felix/bpf/state/*.go','/felix/bpf/tc/*.go','/felix/bpf/tc/defs/*.go','/felix/bpf/utils/*.go','/felix/bpf/xdp/*.go','/felix/cachingmap/*.go','/felix/calc/*.go','/felix/cmd/calico-bpf/commands/*.go','/felix/collector/*.go','/felix/collector/flowlog/*.go','/felix/collector/goldmane/*.go','/felix/collector/local/*.go','/felix/collector/types/*.go','/felix/collector/types/counter/*.go','/felix/collector/types/endpoint/*.go','/felix/collector/types/metric/*.go','/felix/collector/types/tuple/*.go','/felix/collector/utils/*.go','/felix/config/*.go','/felix/conntrack/*.go','/felix/daemon/*.go','/felix/dataplane/*.go','/felix/dataplane/common/*.go','/felix/dataplane/external/*.go','/felix/dataplane/inactive/*.go','/felix/dataplane/ipsets/*.go','/felix/dataplane/linux/*.go','/felix/dataplane/linux/dataplanedefs/*.go','/felix/dataplane/linux/qos/*.go','/felix/deltatracker/*.go','/felix/dispatcher/*.go','/felix/environment/*.go','/felix/ethtool/*.go','/felix/generictables/*.go','/felix/idalloc/*.go','/felix/ifacemonitor/*.go','/felix/ip/*.go','/felix/ipsets/*.go','/felix/iptables/*.go','/felix/iptables/cmdshim/*.go','/felix/jitter/*.go','/felix/k8sutils/*.go','/felix/labelindex/*.go','/felix/labelindex/ipsetmember/*.go','/felix/labelindex/labelnamevalueindex/*.go','/felix/labelindex/labelrestrictionindex/*.go','/felix/linkaddrs/*.go','/felix/logutils/*.go','/felix/markbits/*.go','/felix/multidict/*.go','/felix/netlinkshim/*.go','/felix/netlinkshim/handlemgr/*.go','/felix/nfnetlink/*.go','/felix/nfnetlink/nfnl/*.go','/felix/nfnetlink/pkt/*.go','/felix/nftables/*.go','/felix/policysync/*.go','/felix/proto/*.go','/felix/proto/protoconv/*.go','/felix/routerule/*.go','/felix/routetable/*.go','/felix/routetable/ownershippol/*.go','/felix/rules/*.go','/felix/serviceindex/*.go','/felix/statusrep/*.go','/felix/stringutils/*.go','/felix/throttle/*.go','/felix/timeshim/*.go','/felix/types/*.go','/felix/usagerep/*.go','/felix/vxlanfdb/*.go','/felix/wireguard/*.go','/goldmane/pkg/client/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/bgpsyncer/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/nodestatussyncer/*.go','/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/consistenthash/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/ipam/vmipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/kubevirt/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/node/**','/pkg/buildinfo/*.go','/pod2daemon/binder/*.go','/typha/pkg/discovery/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncclientutils/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/tlsutils/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/app-policy/**/*_test.go','/cni-plugin/**/*_test.go','/confd/**/*_test.go','/crypto/**/*_test.go','/felix/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go']})" dependencies: - "Node: Build" task: @@ -781,7 +1123,7 @@ blocks: commands: - cd node jobs: - - name: Build image + - name: "Node: build multi-arch images" matrix: - env_var: ARCH values: @@ -789,15 +1131,15 @@ blocks: - s390x commands: - ../.semaphore/run-and-monitor image-$ARCH.log make image ARCH=$ARCH - - name: Build Windows archive + - name: "Node: build Windows archive" commands: - ../.semaphore/run-and-monitor build-windows-archive.log make build-windows-archive - - name: Build Windows image + - name: "Node: build Windows image" commands: - ../.semaphore/run-and-monitor build-windows-image.log make image-windows - name: "Node: Build - native arm64 runner" run: - when: "change_in(['/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/cni-plugin/internal/pkg/azure/*.go','/cni-plugin/internal/pkg/utils/*.go','/cni-plugin/internal/pkg/utils/cri/*.go','/cni-plugin/pkg/dataplane/*.go','/cni-plugin/pkg/dataplane/grpc/*.go','/cni-plugin/pkg/dataplane/grpc/proto/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/ipamplugin/*.go','/cni-plugin/pkg/k8s/*.go','/cni-plugin/pkg/plugin/*.go','/cni-plugin/pkg/types/*.go','/cni-plugin/pkg/upgrade/*.go','/cni-plugin/pkg/wait/*.go','/confd/etc','/confd/pkg/backends/*.go','/confd/pkg/backends/calico/*.go','/confd/pkg/config/*.go','/confd/pkg/log/*.go','/confd/pkg/resource/template/*.go','/confd/pkg/run/*.go','/crypto/pkg/tls/*.go','/felix/aws/*.go','/felix/bpf-apache','/felix/bpf-gpl','/felix/bpf/*.go','/felix/bpf/arp/*.go','/felix/bpf/asm/*.go','/felix/bpf/bpfdefs/*.go','/felix/bpf/bpfmap/*.go','/felix/bpf/conntrack/*.go','/felix/bpf/conntrack/cleanupv1/*.go','/felix/bpf/conntrack/timeouts/*.go','/felix/bpf/conntrack/v2/*.go','/felix/bpf/conntrack/v3/*.go','/felix/bpf/conntrack/v4/*.go','/felix/bpf/counters/*.go','/felix/bpf/events/*.go','/felix/bpf/failsafes/*.go','/felix/bpf/filter/*.go','/felix/bpf/hook/*.go','/felix/bpf/ifstate/*.go','/felix/bpf/ipsets/*.go','/felix/bpf/jump/*.go','/felix/bpf/legacy/*.go','/felix/bpf/libbpf/*.go','/felix/bpf/maps/*.go','/felix/bpf/nat/*.go','/felix/bpf/perf/*.go','/felix/bpf/polprog/*.go','/felix/bpf/profiling/*.go','/felix/bpf/proxy/*.go','/felix/bpf/qos/*.go','/felix/bpf/routes/*.go','/felix/bpf/state/*.go','/felix/bpf/tc/*.go','/felix/bpf/tc/defs/*.go','/felix/bpf/utils/*.go','/felix/bpf/xdp/*.go','/felix/cachingmap/*.go','/felix/calc/*.go','/felix/cmd/calico-bpf/commands/*.go','/felix/collector/*.go','/felix/collector/flowlog/*.go','/felix/collector/goldmane/*.go','/felix/collector/local/*.go','/felix/collector/types/*.go','/felix/collector/types/counter/*.go','/felix/collector/types/endpoint/*.go','/felix/collector/types/metric/*.go','/felix/collector/types/tuple/*.go','/felix/collector/utils/*.go','/felix/config/*.go','/felix/conntrack/*.go','/felix/daemon/*.go','/felix/dataplane/*.go','/felix/dataplane/common/*.go','/felix/dataplane/external/*.go','/felix/dataplane/inactive/*.go','/felix/dataplane/ipsets/*.go','/felix/dataplane/linux/*.go','/felix/dataplane/linux/dataplanedefs/*.go','/felix/dataplane/linux/qos/*.go','/felix/deltatracker/*.go','/felix/dispatcher/*.go','/felix/environment/*.go','/felix/ethtool/*.go','/felix/generictables/*.go','/felix/hashutils/*.go','/felix/idalloc/*.go','/felix/ifacemonitor/*.go','/felix/ip/*.go','/felix/ipsets/*.go','/felix/iptables/*.go','/felix/iptables/cmdshim/*.go','/felix/jitter/*.go','/felix/k8sutils/*.go','/felix/labelindex/*.go','/felix/labelindex/ipsetmember/*.go','/felix/labelindex/labelnamevalueindex/*.go','/felix/labelindex/labelrestrictionindex/*.go','/felix/linkaddrs/*.go','/felix/logutils/*.go','/felix/markbits/*.go','/felix/multidict/*.go','/felix/netlinkshim/*.go','/felix/netlinkshim/handlemgr/*.go','/felix/nfnetlink/*.go','/felix/nfnetlink/nfnl/*.go','/felix/nfnetlink/pkt/*.go','/felix/nftables/*.go','/felix/policysync/*.go','/felix/proto/*.go','/felix/proto/protoconv/*.go','/felix/routerule/*.go','/felix/routetable/*.go','/felix/routetable/ownershippol/*.go','/felix/rules/*.go','/felix/serviceindex/*.go','/felix/statusrep/*.go','/felix/stringutils/*.go','/felix/throttle/*.go','/felix/timeshim/*.go','/felix/types/*.go','/felix/usagerep/*.go','/felix/vxlanfdb/*.go','/felix/wireguard/*.go','/goldmane/pkg/client/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/apis/v3/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/k8s/scheme/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/bgpsyncer/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/nodestatussyncer/*.go','/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/upgrade/converters/*.go','/libcalico-go/lib/upgrade/migrator/*.go','/libcalico-go/lib/upgrade/migrator/clients/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/node/**','/pkg/buildinfo/*.go','/pod2daemon/binder/*.go','/typha/pkg/discovery/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncclientutils/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/tlsutils/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/app-policy/**/*_test.go','/cni-plugin/**/*_test.go','/confd/**/*_test.go','/crypto/**/*_test.go','/felix/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-node.yml','/Makefile','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/cni-plugin/internal/pkg/azure/*.go','/cni-plugin/internal/pkg/utils/*.go','/cni-plugin/internal/pkg/utils/cri/*.go','/cni-plugin/pkg/dataplane/*.go','/cni-plugin/pkg/dataplane/grpc/*.go','/cni-plugin/pkg/dataplane/grpc/proto/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/ipamplugin/*.go','/cni-plugin/pkg/k8s/*.go','/cni-plugin/pkg/plugin/*.go','/cni-plugin/pkg/types/*.go','/cni-plugin/pkg/upgrade/*.go','/cni-plugin/pkg/wait/*.go','/confd/etc','/confd/pkg/backends/*.go','/confd/pkg/backends/calico/*.go','/confd/pkg/backends/types/*.go','/confd/pkg/config/*.go','/confd/pkg/log/*.go','/confd/pkg/resource/template/*.go','/confd/pkg/run/*.go','/crypto/pkg/tls/*.go','/felix/aws/*.go','/felix/bpf-apache','/felix/bpf-gpl','/felix/bpf/*.go','/felix/bpf/arp/*.go','/felix/bpf/asm/*.go','/felix/bpf/bpfdefs/*.go','/felix/bpf/bpfmap/*.go','/felix/bpf/conntrack/*.go','/felix/bpf/conntrack/cleanupv1/*.go','/felix/bpf/conntrack/timeouts/*.go','/felix/bpf/conntrack/v2/*.go','/felix/bpf/conntrack/v3/*.go','/felix/bpf/conntrack/v4/*.go','/felix/bpf/consistenthash/*.go','/felix/bpf/counters/*.go','/felix/bpf/events/*.go','/felix/bpf/failsafes/*.go','/felix/bpf/filter/*.go','/felix/bpf/hook/*.go','/felix/bpf/ifstate/*.go','/felix/bpf/ipfrags/*.go','/felix/bpf/ipsets/*.go','/felix/bpf/jump/*.go','/felix/bpf/legacy/*.go','/felix/bpf/libbpf/*.go','/felix/bpf/maps/*.go','/felix/bpf/nat/*.go','/felix/bpf/perf/*.go','/felix/bpf/polprog/*.go','/felix/bpf/profiling/*.go','/felix/bpf/proxy/*.go','/felix/bpf/qos/*.go','/felix/bpf/routes/*.go','/felix/bpf/state/*.go','/felix/bpf/tc/*.go','/felix/bpf/tc/defs/*.go','/felix/bpf/utils/*.go','/felix/bpf/xdp/*.go','/felix/cachingmap/*.go','/felix/calc/*.go','/felix/cmd/calico-bpf/commands/*.go','/felix/collector/*.go','/felix/collector/flowlog/*.go','/felix/collector/goldmane/*.go','/felix/collector/local/*.go','/felix/collector/types/*.go','/felix/collector/types/counter/*.go','/felix/collector/types/endpoint/*.go','/felix/collector/types/metric/*.go','/felix/collector/types/tuple/*.go','/felix/collector/utils/*.go','/felix/config/*.go','/felix/conntrack/*.go','/felix/daemon/*.go','/felix/dataplane/*.go','/felix/dataplane/common/*.go','/felix/dataplane/external/*.go','/felix/dataplane/inactive/*.go','/felix/dataplane/ipsets/*.go','/felix/dataplane/linux/*.go','/felix/dataplane/linux/dataplanedefs/*.go','/felix/dataplane/linux/qos/*.go','/felix/deltatracker/*.go','/felix/dispatcher/*.go','/felix/environment/*.go','/felix/ethtool/*.go','/felix/generictables/*.go','/felix/idalloc/*.go','/felix/ifacemonitor/*.go','/felix/ip/*.go','/felix/ipsets/*.go','/felix/iptables/*.go','/felix/iptables/cmdshim/*.go','/felix/jitter/*.go','/felix/k8sutils/*.go','/felix/labelindex/*.go','/felix/labelindex/ipsetmember/*.go','/felix/labelindex/labelnamevalueindex/*.go','/felix/labelindex/labelrestrictionindex/*.go','/felix/linkaddrs/*.go','/felix/logutils/*.go','/felix/markbits/*.go','/felix/multidict/*.go','/felix/netlinkshim/*.go','/felix/netlinkshim/handlemgr/*.go','/felix/nfnetlink/*.go','/felix/nfnetlink/nfnl/*.go','/felix/nfnetlink/pkt/*.go','/felix/nftables/*.go','/felix/policysync/*.go','/felix/proto/*.go','/felix/proto/protoconv/*.go','/felix/routerule/*.go','/felix/routetable/*.go','/felix/routetable/ownershippol/*.go','/felix/rules/*.go','/felix/serviceindex/*.go','/felix/statusrep/*.go','/felix/stringutils/*.go','/felix/throttle/*.go','/felix/timeshim/*.go','/felix/types/*.go','/felix/usagerep/*.go','/felix/vxlanfdb/*.go','/felix/wireguard/*.go','/goldmane/pkg/client/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/bgpsyncer/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/nodestatussyncer/*.go','/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/consistenthash/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/ipam/vmipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/kubevirt/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/node/**','/pkg/buildinfo/*.go','/pod2daemon/binder/*.go','/typha/pkg/discovery/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncclientutils/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/tlsutils/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/app-policy/**/*_test.go','/cni-plugin/**/*_test.go','/confd/**/*_test.go','/crypto/**/*_test.go','/felix/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go']})" dependencies: - "Node: Build" task: @@ -808,45 +1150,54 @@ blocks: commands: - cd node jobs: - - name: Build image + - name: "Node: build arm64 image" commands: - ../.semaphore/run-and-monitor build-arm64.log make image ARCH=arm64 - name: "Node: kind-cluster tests" run: # KinD tests also build and run all the other images. - when: "change_in(['/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/api/pkg/openapi/*.go','/apiserver/cmd/apiserver/*.go','/apiserver/cmd/apiserver/server/*.go','/apiserver/pkg/apiserver/*.go','/apiserver/pkg/printers/projectcalico/*.go','/apiserver/pkg/rbac/*.go','/apiserver/pkg/registry/projectcalico/authorizer/*.go','/apiserver/pkg/registry/projectcalico/bgpconfiguration/*.go','/apiserver/pkg/registry/projectcalico/bgpfilter/*.go','/apiserver/pkg/registry/projectcalico/bgppeer/*.go','/apiserver/pkg/registry/projectcalico/blockaffinity/*.go','/apiserver/pkg/registry/projectcalico/caliconodestatus/*.go','/apiserver/pkg/registry/projectcalico/clusterinformation/*.go','/apiserver/pkg/registry/projectcalico/felixconfig/*.go','/apiserver/pkg/registry/projectcalico/globalnetworkset/*.go','/apiserver/pkg/registry/projectcalico/globalpolicy/*.go','/apiserver/pkg/registry/projectcalico/hostendpoint/*.go','/apiserver/pkg/registry/projectcalico/ipamconfig/*.go','/apiserver/pkg/registry/projectcalico/ippool/*.go','/apiserver/pkg/registry/projectcalico/ipreservation/*.go','/apiserver/pkg/registry/projectcalico/kubecontrollersconfig/*.go','/apiserver/pkg/registry/projectcalico/networkpolicy/*.go','/apiserver/pkg/registry/projectcalico/networkset/*.go','/apiserver/pkg/registry/projectcalico/profile/*.go','/apiserver/pkg/registry/projectcalico/rest/*.go','/apiserver/pkg/registry/projectcalico/server/*.go','/apiserver/pkg/registry/projectcalico/stagedglobalnetworkpolicy/*.go','/apiserver/pkg/registry/projectcalico/stagedkubernetesnetworkpolicy/*.go','/apiserver/pkg/registry/projectcalico/stagednetworkpolicy/*.go','/apiserver/pkg/registry/projectcalico/tier/*.go','/apiserver/pkg/registry/projectcalico/util/*.go','/apiserver/pkg/storage/calico/*.go','/apiserver/pkg/storage/etcd/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/calicoctl/calicoctl/*.go','/calicoctl/calicoctl/commands/*.go','/calicoctl/calicoctl/commands/argutils/*.go','/calicoctl/calicoctl/commands/clientmgr/*.go','/calicoctl/calicoctl/commands/cluster/*.go','/calicoctl/calicoctl/commands/common/*.go','/calicoctl/calicoctl/commands/constants/*.go','/calicoctl/calicoctl/commands/datastore/*.go','/calicoctl/calicoctl/commands/datastore/migrate/*.go','/calicoctl/calicoctl/commands/file/*.go','/calicoctl/calicoctl/commands/ipam/*.go','/calicoctl/calicoctl/commands/node/*.go','/calicoctl/calicoctl/commands/resourceloader/*.go','/calicoctl/calicoctl/resourcemgr/*.go','/calicoctl/calicoctl/util/*.go','/calicoctl/calicoctl/util/yaml/*.go','/calicoctl/tests/fv/helper/*.go','/cni-plugin/cmd/calico/*.go','/cni-plugin/cmd/install/*.go','/cni-plugin/internal/pkg/azure/*.go','/cni-plugin/internal/pkg/utils/*.go','/cni-plugin/internal/pkg/utils/cri/*.go','/cni-plugin/pkg/dataplane/*.go','/cni-plugin/pkg/dataplane/grpc/*.go','/cni-plugin/pkg/dataplane/grpc/proto/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/install/*.go','/cni-plugin/pkg/ipamplugin/*.go','/cni-plugin/pkg/k8s/*.go','/cni-plugin/pkg/plugin/*.go','/cni-plugin/pkg/types/*.go','/cni-plugin/pkg/upgrade/*.go','/cni-plugin/pkg/wait/*.go','/cni-plugin/tests/*.go','/confd/etc','/confd/pkg/backends/*.go','/confd/pkg/backends/calico/*.go','/confd/pkg/config/*.go','/confd/pkg/log/*.go','/confd/pkg/resource/template/*.go','/confd/pkg/run/*.go','/crypto/pkg/tls/*.go','/felix/aws/*.go','/felix/bpf-apache','/felix/bpf-gpl','/felix/bpf/*.go','/felix/bpf/arp/*.go','/felix/bpf/asm/*.go','/felix/bpf/bpfdefs/*.go','/felix/bpf/bpfmap/*.go','/felix/bpf/conntrack/*.go','/felix/bpf/conntrack/cleanupv1/*.go','/felix/bpf/conntrack/timeouts/*.go','/felix/bpf/conntrack/v2/*.go','/felix/bpf/conntrack/v3/*.go','/felix/bpf/conntrack/v4/*.go','/felix/bpf/counters/*.go','/felix/bpf/events/*.go','/felix/bpf/failsafes/*.go','/felix/bpf/filter/*.go','/felix/bpf/hook/*.go','/felix/bpf/ifstate/*.go','/felix/bpf/ipsets/*.go','/felix/bpf/jump/*.go','/felix/bpf/legacy/*.go','/felix/bpf/libbpf/*.go','/felix/bpf/maps/*.go','/felix/bpf/nat/*.go','/felix/bpf/perf/*.go','/felix/bpf/polprog/*.go','/felix/bpf/profiling/*.go','/felix/bpf/proxy/*.go','/felix/bpf/qos/*.go','/felix/bpf/routes/*.go','/felix/bpf/state/*.go','/felix/bpf/tc/*.go','/felix/bpf/tc/defs/*.go','/felix/bpf/utils/*.go','/felix/bpf/xdp/*.go','/felix/cachingmap/*.go','/felix/calc/*.go','/felix/cmd/calico-bpf/commands/*.go','/felix/collector/*.go','/felix/collector/flowlog/*.go','/felix/collector/goldmane/*.go','/felix/collector/local/*.go','/felix/collector/types/*.go','/felix/collector/types/counter/*.go','/felix/collector/types/endpoint/*.go','/felix/collector/types/metric/*.go','/felix/collector/types/tuple/*.go','/felix/collector/utils/*.go','/felix/config/*.go','/felix/conntrack/*.go','/felix/daemon/*.go','/felix/dataplane/*.go','/felix/dataplane/common/*.go','/felix/dataplane/external/*.go','/felix/dataplane/inactive/*.go','/felix/dataplane/ipsets/*.go','/felix/dataplane/linux/*.go','/felix/dataplane/linux/dataplanedefs/*.go','/felix/dataplane/linux/qos/*.go','/felix/deltatracker/*.go','/felix/dispatcher/*.go','/felix/environment/*.go','/felix/ethtool/*.go','/felix/generictables/*.go','/felix/hashutils/*.go','/felix/idalloc/*.go','/felix/ifacemonitor/*.go','/felix/ip/*.go','/felix/ipsets/*.go','/felix/iptables/*.go','/felix/iptables/cmdshim/*.go','/felix/jitter/*.go','/felix/k8sutils/*.go','/felix/labelindex/*.go','/felix/labelindex/ipsetmember/*.go','/felix/labelindex/labelnamevalueindex/*.go','/felix/labelindex/labelrestrictionindex/*.go','/felix/linkaddrs/*.go','/felix/logutils/*.go','/felix/markbits/*.go','/felix/multidict/*.go','/felix/netlinkshim/*.go','/felix/netlinkshim/handlemgr/*.go','/felix/nfnetlink/*.go','/felix/nfnetlink/nfnl/*.go','/felix/nfnetlink/pkt/*.go','/felix/nftables/*.go','/felix/policysync/*.go','/felix/proto/*.go','/felix/proto/protoconv/*.go','/felix/routerule/*.go','/felix/routetable/*.go','/felix/routetable/ownershippol/*.go','/felix/rules/*.go','/felix/serviceindex/*.go','/felix/statusrep/*.go','/felix/stringutils/*.go','/felix/throttle/*.go','/felix/timeshim/*.go','/felix/types/*.go','/felix/usagerep/*.go','/felix/vxlanfdb/*.go','/felix/wireguard/*.go','/goldmane/cmd/*.go','/goldmane/cmd/flowgen/*.go','/goldmane/cmd/health/*.go','/goldmane/cmd/stream/*.go','/goldmane/pkg/client/*.go','/goldmane/pkg/daemon/*.go','/goldmane/pkg/emitter/*.go','/goldmane/pkg/flowgen/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/internal/utils/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/kube-controllers/cmd/check-status/*.go','/kube-controllers/cmd/kube-controllers/*.go','/kube-controllers/cmd/wrapper/*.go','/kube-controllers/pkg/cache/*.go','/kube-controllers/pkg/config/*.go','/kube-controllers/pkg/controllers/controller/*.go','/kube-controllers/pkg/controllers/flannelmigration/*.go','/kube-controllers/pkg/controllers/loadbalancer/*.go','/kube-controllers/pkg/controllers/namespace/*.go','/kube-controllers/pkg/controllers/networkpolicy/*.go','/kube-controllers/pkg/controllers/node/*.go','/kube-controllers/pkg/controllers/pod/*.go','/kube-controllers/pkg/controllers/serviceaccount/*.go','/kube-controllers/pkg/controllers/utils/*.go','/kube-controllers/pkg/converter/*.go','/kube-controllers/pkg/status/*.go','/lib.Makefile','/lib/httpmachinery/pkg/apiutil/*.go','/lib/httpmachinery/pkg/codec/*.go','/lib/httpmachinery/pkg/context/*.go','/lib/httpmachinery/pkg/header/*.go','/lib/httpmachinery/pkg/server/*.go','/lib/httpmachinery/pkg/server/adaptors/gorilla/*.go','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/apis/v3/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/k8s/scheme/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/bgpsyncer/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/nodestatussyncer/*.go','/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/multireadbuf/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/upgrade/converters/*.go','/libcalico-go/lib/upgrade/migrator/*.go','/libcalico-go/lib/upgrade/migrator/clients/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/libcalico-go/lib/writelogger/*.go','/metadata.mk','/node/**','/node/pkg/cni/*.go','/pkg/buildinfo/*.go','/pkg/cmdwrapper/*.go','/pod2daemon/binder/*.go','/pod2daemon/csidriver/*.go','/pod2daemon/csidriver/driver/*.go','/pod2daemon/flexvol/*.go','/pod2daemon/flexvol/creds/*.go','/pod2daemon/nodeagent/*.go','/pod2daemon/proto/*.go','/pod2daemon/workloadapi/*.go','/typha/cmd/calico-typha/*.go','/typha/cmd/typha-client/*.go','/typha/pkg/calc/*.go','/typha/pkg/config/*.go','/typha/pkg/daemon/*.go','/typha/pkg/discovery/*.go','/typha/pkg/jitter/*.go','/typha/pkg/k8s/*.go','/typha/pkg/logutils/*.go','/typha/pkg/promutils/*.go','/typha/pkg/snapcache/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncclientutils/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/syncserver/*.go','/typha/pkg/tlsutils/*.go','/whisker','/whisker-backend/cmd/*.go','/whisker-backend/cmd/app/*.go','/whisker-backend/pkg/apis/v1/*.go','/whisker-backend/pkg/config/*.go','/whisker-backend/pkg/handlers/v1/*.go','apiserver/**/*Dockerfile*','apiserver/Makefile','apiserver/deps.txt','calicoctl/**/*Dockerfile*','calicoctl/Makefile','calicoctl/deps.txt','cni-plugin/**/*Dockerfile*','cni-plugin/Makefile','cni-plugin/deps.txt','goldmane/**/*Dockerfile*','goldmane/Makefile','goldmane/deps.txt','kube-controllers/**/*Dockerfile*','kube-controllers/Makefile','kube-controllers/deps.txt','pod2daemon/**/*Dockerfile*','pod2daemon/Makefile','pod2daemon/deps.txt','typha/**/*Dockerfile*','typha/Makefile','typha/deps.txt','whisker-backend/**/*Dockerfile*','whisker-backend/Makefile','whisker-backend/deps.txt','whisker/**/*Dockerfile*','whisker/Makefile','whisker/deps.txt'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/apiserver/**/*_test.go','/app-policy/**/*_test.go','/calicoctl/**/*_test.go','/cni-plugin/**/*_test.go','/confd/**/*_test.go','/crypto/**/*_test.go','/felix/**/*_test.go','/goldmane/**/*_test.go','/kube-controllers/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go','/whisker-backend/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-node.yml','/.semaphore/vms/','/Makefile','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/client/clientset_generated/clientset/*.go','/api/pkg/client/clientset_generated/clientset/scheme/*.go','/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/*.go','/api/pkg/client/informers_generated/externalversions/*.go','/api/pkg/client/informers_generated/externalversions/internalinterfaces/*.go','/api/pkg/client/informers_generated/externalversions/projectcalico/*.go','/api/pkg/client/informers_generated/externalversions/projectcalico/v3/*.go','/api/pkg/client/listers_generated/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/api/pkg/openapi/*.go','/apiserver/cmd/apiserver/*.go','/apiserver/cmd/apiserver/server/*.go','/apiserver/pkg/apiserver/*.go','/apiserver/pkg/printers/projectcalico/*.go','/apiserver/pkg/rbac/*.go','/apiserver/pkg/registry/projectcalico/authorizer/*.go','/apiserver/pkg/registry/projectcalico/bgpconfiguration/*.go','/apiserver/pkg/registry/projectcalico/bgpfilter/*.go','/apiserver/pkg/registry/projectcalico/bgppeer/*.go','/apiserver/pkg/registry/projectcalico/blockaffinity/*.go','/apiserver/pkg/registry/projectcalico/caliconodestatus/*.go','/apiserver/pkg/registry/projectcalico/clusterinformation/*.go','/apiserver/pkg/registry/projectcalico/felixconfig/*.go','/apiserver/pkg/registry/projectcalico/globalnetworkset/*.go','/apiserver/pkg/registry/projectcalico/globalpolicy/*.go','/apiserver/pkg/registry/projectcalico/hostendpoint/*.go','/apiserver/pkg/registry/projectcalico/ipamconfig/*.go','/apiserver/pkg/registry/projectcalico/ippool/*.go','/apiserver/pkg/registry/projectcalico/ipreservation/*.go','/apiserver/pkg/registry/projectcalico/kubecontrollersconfig/*.go','/apiserver/pkg/registry/projectcalico/networkpolicy/*.go','/apiserver/pkg/registry/projectcalico/networkset/*.go','/apiserver/pkg/registry/projectcalico/profile/*.go','/apiserver/pkg/registry/projectcalico/rest/*.go','/apiserver/pkg/registry/projectcalico/server/*.go','/apiserver/pkg/registry/projectcalico/stagedglobalnetworkpolicy/*.go','/apiserver/pkg/registry/projectcalico/stagedkubernetesnetworkpolicy/*.go','/apiserver/pkg/registry/projectcalico/stagednetworkpolicy/*.go','/apiserver/pkg/registry/projectcalico/tier/*.go','/apiserver/pkg/registry/projectcalico/util/*.go','/apiserver/pkg/storage/calico/*.go','/apiserver/pkg/storage/etcd/*.go','/app-policy/checker/*.go','/app-policy/policystore/*.go','/app-policy/types/*.go','/calicoctl/calicoctl/*.go','/calicoctl/calicoctl/commands/*.go','/calicoctl/calicoctl/commands/argutils/*.go','/calicoctl/calicoctl/commands/clientmgr/*.go','/calicoctl/calicoctl/commands/cluster/*.go','/calicoctl/calicoctl/commands/common/*.go','/calicoctl/calicoctl/commands/constants/*.go','/calicoctl/calicoctl/commands/datastore/*.go','/calicoctl/calicoctl/commands/datastore/migrate/*.go','/calicoctl/calicoctl/commands/file/*.go','/calicoctl/calicoctl/commands/ipam/*.go','/calicoctl/calicoctl/commands/node/*.go','/calicoctl/calicoctl/resourcemgr/*.go','/calicoctl/calicoctl/util/*.go','/calicoctl/calicoctl/util/yaml/*.go','/calicoctl/tests/fv/helper/*.go','/cni-plugin/cmd/calico/*.go','/cni-plugin/cmd/install/*.go','/cni-plugin/internal/pkg/azure/*.go','/cni-plugin/internal/pkg/utils/*.go','/cni-plugin/internal/pkg/utils/cri/*.go','/cni-plugin/pkg/dataplane/*.go','/cni-plugin/pkg/dataplane/grpc/*.go','/cni-plugin/pkg/dataplane/grpc/proto/*.go','/cni-plugin/pkg/dataplane/linux/*.go','/cni-plugin/pkg/install/*.go','/cni-plugin/pkg/ipamplugin/*.go','/cni-plugin/pkg/k8s/*.go','/cni-plugin/pkg/plugin/*.go','/cni-plugin/pkg/types/*.go','/cni-plugin/pkg/upgrade/*.go','/cni-plugin/pkg/wait/*.go','/cni-plugin/tests/*.go','/confd/etc','/confd/pkg/backends/*.go','/confd/pkg/backends/calico/*.go','/confd/pkg/backends/types/*.go','/confd/pkg/config/*.go','/confd/pkg/log/*.go','/confd/pkg/resource/template/*.go','/confd/pkg/run/*.go','/crypto/pkg/tls/*.go','/felix/aws/*.go','/felix/bpf-apache','/felix/bpf-gpl','/felix/bpf/*.go','/felix/bpf/arp/*.go','/felix/bpf/asm/*.go','/felix/bpf/bpfdefs/*.go','/felix/bpf/bpfmap/*.go','/felix/bpf/conntrack/*.go','/felix/bpf/conntrack/cleanupv1/*.go','/felix/bpf/conntrack/timeouts/*.go','/felix/bpf/conntrack/v2/*.go','/felix/bpf/conntrack/v3/*.go','/felix/bpf/conntrack/v4/*.go','/felix/bpf/consistenthash/*.go','/felix/bpf/counters/*.go','/felix/bpf/events/*.go','/felix/bpf/failsafes/*.go','/felix/bpf/filter/*.go','/felix/bpf/hook/*.go','/felix/bpf/ifstate/*.go','/felix/bpf/ipfrags/*.go','/felix/bpf/ipsets/*.go','/felix/bpf/jump/*.go','/felix/bpf/legacy/*.go','/felix/bpf/libbpf/*.go','/felix/bpf/maps/*.go','/felix/bpf/nat/*.go','/felix/bpf/perf/*.go','/felix/bpf/polprog/*.go','/felix/bpf/profiling/*.go','/felix/bpf/proxy/*.go','/felix/bpf/qos/*.go','/felix/bpf/routes/*.go','/felix/bpf/state/*.go','/felix/bpf/tc/*.go','/felix/bpf/tc/defs/*.go','/felix/bpf/utils/*.go','/felix/bpf/xdp/*.go','/felix/cachingmap/*.go','/felix/calc/*.go','/felix/cmd/calico-bpf/commands/*.go','/felix/collector/*.go','/felix/collector/flowlog/*.go','/felix/collector/goldmane/*.go','/felix/collector/local/*.go','/felix/collector/types/*.go','/felix/collector/types/counter/*.go','/felix/collector/types/endpoint/*.go','/felix/collector/types/metric/*.go','/felix/collector/types/tuple/*.go','/felix/collector/utils/*.go','/felix/config/*.go','/felix/conntrack/*.go','/felix/daemon/*.go','/felix/dataplane/*.go','/felix/dataplane/common/*.go','/felix/dataplane/external/*.go','/felix/dataplane/inactive/*.go','/felix/dataplane/ipsets/*.go','/felix/dataplane/linux/*.go','/felix/dataplane/linux/dataplanedefs/*.go','/felix/dataplane/linux/qos/*.go','/felix/deltatracker/*.go','/felix/dispatcher/*.go','/felix/environment/*.go','/felix/ethtool/*.go','/felix/generictables/*.go','/felix/idalloc/*.go','/felix/ifacemonitor/*.go','/felix/ip/*.go','/felix/ipsets/*.go','/felix/iptables/*.go','/felix/iptables/cmdshim/*.go','/felix/jitter/*.go','/felix/k8sutils/*.go','/felix/labelindex/*.go','/felix/labelindex/ipsetmember/*.go','/felix/labelindex/labelnamevalueindex/*.go','/felix/labelindex/labelrestrictionindex/*.go','/felix/linkaddrs/*.go','/felix/logutils/*.go','/felix/markbits/*.go','/felix/multidict/*.go','/felix/netlinkshim/*.go','/felix/netlinkshim/handlemgr/*.go','/felix/nfnetlink/*.go','/felix/nfnetlink/nfnl/*.go','/felix/nfnetlink/pkt/*.go','/felix/nftables/*.go','/felix/policysync/*.go','/felix/proto/*.go','/felix/proto/protoconv/*.go','/felix/routerule/*.go','/felix/routetable/*.go','/felix/routetable/ownershippol/*.go','/felix/rules/*.go','/felix/serviceindex/*.go','/felix/statusrep/*.go','/felix/stringutils/*.go','/felix/throttle/*.go','/felix/timeshim/*.go','/felix/types/*.go','/felix/usagerep/*.go','/felix/vxlanfdb/*.go','/felix/wireguard/*.go','/goldmane/cmd/*.go','/goldmane/cmd/flowgen/*.go','/goldmane/cmd/health/*.go','/goldmane/cmd/stream/*.go','/goldmane/pkg/client/*.go','/goldmane/pkg/daemon/*.go','/goldmane/pkg/emitter/*.go','/goldmane/pkg/flowgen/*.go','/goldmane/pkg/goldmane/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/internal/utils/*.go','/goldmane/pkg/server/*.go','/goldmane/pkg/storage/*.go','/goldmane/pkg/stream/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/kube-controllers/cmd/check-status/*.go','/kube-controllers/cmd/kube-controllers/*.go','/kube-controllers/cmd/wrapper/*.go','/kube-controllers/pkg/cache/*.go','/kube-controllers/pkg/config/*.go','/kube-controllers/pkg/controllers/controller/*.go','/kube-controllers/pkg/controllers/flannelmigration/*.go','/kube-controllers/pkg/controllers/ippool/*.go','/kube-controllers/pkg/controllers/loadbalancer/*.go','/kube-controllers/pkg/controllers/namespace/*.go','/kube-controllers/pkg/controllers/networkpolicy/*.go','/kube-controllers/pkg/controllers/node/*.go','/kube-controllers/pkg/controllers/pod/*.go','/kube-controllers/pkg/controllers/serviceaccount/*.go','/kube-controllers/pkg/controllers/utils/*.go','/kube-controllers/pkg/converter/*.go','/kube-controllers/pkg/status/*.go','/lib.Makefile','/lib/httpmachinery/pkg/apiutil/*.go','/lib/httpmachinery/pkg/codec/*.go','/lib/httpmachinery/pkg/context/*.go','/lib/httpmachinery/pkg/header/*.go','/lib/httpmachinery/pkg/server/*.go','/lib/httpmachinery/pkg/server/adaptors/gorilla/*.go','/lib/std/chanutil/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/config/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/bgpsyncer/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/nodestatussyncer/*.go','/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/consistenthash/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/dispatcher/*.go','/libcalico-go/lib/epstatusfile/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/ipam/vmipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/kubevirt/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/multireadbuf/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/netlinkutils/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/packedmap/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/libcalico-go/lib/writelogger/*.go','/metadata.mk','/node/**','/node/pkg/cni/*.go','/pkg/buildinfo/*.go','/pkg/cmdwrapper/*.go','/pod2daemon/binder/*.go','/pod2daemon/csidriver/*.go','/pod2daemon/csidriver/driver/*.go','/pod2daemon/flexvol/*.go','/pod2daemon/flexvol/creds/*.go','/pod2daemon/nodeagent/*.go','/pod2daemon/proto/*.go','/pod2daemon/workloadapi/*.go','/typha/cmd/calico-typha/*.go','/typha/cmd/typha-client/*.go','/typha/pkg/calc/*.go','/typha/pkg/config/*.go','/typha/pkg/daemon/*.go','/typha/pkg/discovery/*.go','/typha/pkg/jitter/*.go','/typha/pkg/k8s/*.go','/typha/pkg/logutils/*.go','/typha/pkg/promutils/*.go','/typha/pkg/snapcache/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncclientutils/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/syncserver/*.go','/typha/pkg/tlsutils/*.go','/whisker','/whisker-backend/cmd/*.go','/whisker-backend/cmd/app/*.go','/whisker-backend/pkg/apis/v1/*.go','/whisker-backend/pkg/config/*.go','/whisker-backend/pkg/handlers/v1/*.go','apiserver/**/*Dockerfile*','apiserver/Makefile','apiserver/deps.txt','calicoctl/**/*Dockerfile*','calicoctl/Makefile','calicoctl/deps.txt','cni-plugin/**/*Dockerfile*','cni-plugin/Makefile','cni-plugin/deps.txt','goldmane/**/*Dockerfile*','goldmane/Makefile','goldmane/deps.txt','kube-controllers/**/*Dockerfile*','kube-controllers/Makefile','kube-controllers/deps.txt','pod2daemon/**/*Dockerfile*','pod2daemon/Makefile','pod2daemon/deps.txt','typha/**/*Dockerfile*','typha/Makefile','typha/deps.txt','whisker-backend/**/*Dockerfile*','whisker-backend/Makefile','whisker-backend/deps.txt','whisker/**/*Dockerfile*','whisker/Makefile','whisker/deps.txt'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/apiserver/**/*_test.go','/app-policy/**/*_test.go','/calicoctl/**/*_test.go','/cni-plugin/**/*_test.go','/confd/**/*_test.go','/crypto/**/*_test.go','/felix/**/*_test.go','/goldmane/**/*_test.go','/kube-controllers/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/pkg/**/*_test.go','/pod2daemon/**/*_test.go','/typha/**/*_test.go','/whisker-backend/**/*_test.go']})" dependencies: - - Prerequisites + - "Prerequisites" task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "node" + - name: CI_GROUP_LABEL + value: "node" + - name: CI_JOB_LABEL + value: "kind-cluster-tests" prologue: commands: - - cd node + # Set up access to GCP buckets. - export GOOGLE_APPLICATION_CREDENTIALS=$HOME/secrets/secret.google-service-account-key.json + - gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS + - gcloud config set project unique-caldron-775 + - cd node - export SHORT_WORKFLOW_ID=$(echo ${SEMAPHORE_WORKFLOW_ID} | sha256sum | cut -c -8) - export ZONE=europe-west3-c - export VM_PREFIX=sem-${SEMAPHORE_PROJECT_NAME}-${SHORT_WORKFLOW_ID}-kind- - echo VM_PREFIX=${VM_PREFIX} - - export REPO_NAME=$(basename $(pwd)) + - export COMPONENT=node - export VM_DISK_SIZE=80GB - mkdir artifacts - - ../.semaphore/vms/create-test-vms ${ZONE} ${VM_PREFIX} + - ./.semaphore/cache-test-artifacts jobs: - name: "Node: kind-cluster tests" execution_time_limit: minutes: 120 commands: - - ../.semaphore/vms/run-tests-on-vms ${ZONE} ${VM_PREFIX} + - ../.semaphore/vms/run-tests-on-vms ${VM_PREFIX} epilogue: always: commands: - - ../.semaphore/vms/publish-artifacts - - ../.semaphore/vms/clean-up-vms ${ZONE} ${VM_PREFIX} - - test-results publish ./report/*.xml --name "node-kind-tests" || true + - '../.semaphore/vms/publish-reports "Node: KinD STs"' + - ../.semaphore/vms/clean-up-vms ${VM_PREFIX} secrets: - name: google-service-account-for-gce - name: pod2daemon run: - when: "change_in(['/hack/test/certs/','/lib.Makefile','/libcalico-go/lib/logutils/*.go','/metadata.mk','/pod2daemon/**'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/libcalico-go/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-pod2daemon.yml','/hack/test/certs/','/lib.Makefile','/libcalico-go/lib/logutils/*.go','/metadata.mk','/pod2daemon/**'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/libcalico-go/**/*_test.go']})" dependencies: - Prerequisites task: @@ -854,10 +1205,10 @@ blocks: commands: - cd pod2daemon jobs: - - name: pod2daemon tests + - name: "pod2daemon: CI" commands: - ../.semaphore/run-and-monitor ci.log make ci - - test-results publish ./report/*.xml --name "pod2daemon-ut-tests" || true + - 'test-results publish ./report/*.xml --name "pod2daemon: UT" || true' - name: "Tools (hack directory)" run: when: "false or change_in(['/metadata.mk', '/lib.Makefile', '/hack/', '/api/', '/libcalico-go/'], {pipeline_file: 'ignore', exclude: ['/**/.gitignore', '/**/README.md', '/**/LICENSE']})" @@ -868,24 +1219,32 @@ blocks: commands: - cd hack jobs: - - name: "Tools (hack directory)" + - name: "Tools (hack directory): CI" commands: - ../.semaphore/run-and-monitor make-ci.log make ci - name: Typha run: - when: "change_in(['/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/crypto/pkg/tls/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/apis/v3/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/k8s/scheme/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/bgpsyncer/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/nodestatussyncer/*.go','/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/ipam/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/multireadbuf/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/upgrade/converters/*.go','/libcalico-go/lib/upgrade/migrator/*.go','/libcalico-go/lib/upgrade/migrator/clients/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom/*.go','/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/libcalico-go/lib/writelogger/*.go','/metadata.mk','/pkg/buildinfo/*.go','/typha/**'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/crypto/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/pkg/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-typha.yml','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/crypto/pkg/tls/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/apis/v1/*.go','/libcalico-go/lib/apis/v1/unversioned/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/syncersv1/bgpsyncer/*.go','/libcalico-go/lib/backend/syncersv1/dedupebuffer/*.go','/libcalico-go/lib/backend/syncersv1/felixsyncer/*.go','/libcalico-go/lib/backend/syncersv1/nodestatussyncer/*.go','/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/*.go','/libcalico-go/lib/backend/syncersv1/updateprocessors/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/debugserver/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/health/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/metricsserver/*.go','/libcalico-go/lib/multireadbuf/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/prometheus/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/scope/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/validator/v1/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/libcalico-go/lib/writelogger/*.go','/metadata.mk','/pkg/buildinfo/*.go','/typha/**'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/crypto/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/pkg/**/*_test.go']})" dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "typha" prologue: commands: - cd typha jobs: - name: "Typha: UT and FV tests" commands: - - ../.semaphore/run-and-monitor make-ci.log make ci EXCEPT=k8sfv-test - - name: "Typha: UT and FV tests on UBI-minimal" + - export TEST_SUITE_PREFIX="calico/base" + - ../.semaphore/run-and-monitor make-ci.log make ci + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: "Typha: FV tests on UBI-minimal" commands: + - 'export TEST_SUITE_PREFIX="UBI-minimal"' - ../.semaphore/run-and-monitor make-fv-ubi.log make clean image fv env_vars: - name: USE_UBI_AS_CALICO_BASE @@ -893,22 +1252,29 @@ blocks: epilogue: always: commands: - - | - for f in /home/semaphore/calico/typha/report/*; do - NAME=$(basename $f) - test-results compile --name typha-$NAME $f $NAME.json || true - done - for f in /home/semaphore/calico/typha/pkg/report/*; do - NAME=$(basename $f) - test-results compile --name typha-$NAME $f $NAME.json || true - done - test-results combine *.xml.json report.json || true - artifact push job report.json -d test-results/junit.json || true - artifact push workflow report.json -d test-results/${SEMAPHORE_PIPELINE_ID}/${SEMAPHORE_JOB_ID}.json || true - - test-results publish /home/semaphore/calico/felix/report/k8sfv_suite.xml --name "typha-k8sfv" || true - - name: whisker-backend + - 'test-results publish --ignore-missing ./report/*.xml ../felix/report/*.xml --suite-prefix="$TEST_SUITE_PREFIX" --name "Typha" || true' + - name: webhooks run: - when: "change_in(['/crypto/pkg/tls/*.go','/goldmane/pkg/client/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/httpmachinery/pkg/apiutil/*.go','/lib/httpmachinery/pkg/codec/*.go','/lib/httpmachinery/pkg/context/*.go','/lib/httpmachinery/pkg/header/*.go','/lib/httpmachinery/pkg/server/*.go','/lib/httpmachinery/pkg/server/adaptors/gorilla/*.go','/lib/std/cryptoutils/*.go','/lib/std/testutils/json/*.go','/lib/std/time/*.go','/libcalico-go/lib/logutils/*.go','/metadata.mk','/whisker-backend/**'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/crypto/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-webhooks.yml','/api/examples/list-gnp/*.go','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/client/clientset_generated/clientset/*.go','/api/pkg/client/clientset_generated/clientset/scheme/*.go','/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/*.go','/api/pkg/defaults/*.go','/api/pkg/lib/numorstring/*.go','/api/pkg/openapi/*.go','/apiserver/cmd/apiserver/*.go','/apiserver/cmd/apiserver/server/*.go','/apiserver/pkg/apiserver/*.go','/apiserver/pkg/printers/projectcalico/*.go','/apiserver/pkg/rbac/*.go','/apiserver/pkg/registry/projectcalico/authorizer/*.go','/apiserver/pkg/registry/projectcalico/bgpconfiguration/*.go','/apiserver/pkg/registry/projectcalico/bgpfilter/*.go','/apiserver/pkg/registry/projectcalico/bgppeer/*.go','/apiserver/pkg/registry/projectcalico/blockaffinity/*.go','/apiserver/pkg/registry/projectcalico/caliconodestatus/*.go','/apiserver/pkg/registry/projectcalico/clusterinformation/*.go','/apiserver/pkg/registry/projectcalico/felixconfig/*.go','/apiserver/pkg/registry/projectcalico/globalnetworkset/*.go','/apiserver/pkg/registry/projectcalico/globalpolicy/*.go','/apiserver/pkg/registry/projectcalico/hostendpoint/*.go','/apiserver/pkg/registry/projectcalico/ipamconfig/*.go','/apiserver/pkg/registry/projectcalico/ippool/*.go','/apiserver/pkg/registry/projectcalico/ipreservation/*.go','/apiserver/pkg/registry/projectcalico/kubecontrollersconfig/*.go','/apiserver/pkg/registry/projectcalico/networkpolicy/*.go','/apiserver/pkg/registry/projectcalico/networkset/*.go','/apiserver/pkg/registry/projectcalico/profile/*.go','/apiserver/pkg/registry/projectcalico/rest/*.go','/apiserver/pkg/registry/projectcalico/server/*.go','/apiserver/pkg/registry/projectcalico/stagedglobalnetworkpolicy/*.go','/apiserver/pkg/registry/projectcalico/stagedkubernetesnetworkpolicy/*.go','/apiserver/pkg/registry/projectcalico/stagednetworkpolicy/*.go','/apiserver/pkg/registry/projectcalico/tier/*.go','/apiserver/pkg/registry/projectcalico/util/*.go','/apiserver/pkg/storage/calico/*.go','/apiserver/pkg/storage/etcd/*.go','/crypto/pkg/tls/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/lib/apiconfig/*.go','/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/backend/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/etcdv3/*.go','/libcalico-go/lib/backend/k8s/*.go','/libcalico-go/lib/backend/k8s/conversion/*.go','/libcalico-go/lib/backend/k8s/resources/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/backend/watchersyncer/*.go','/libcalico-go/lib/clientv3/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/hash/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/options/*.go','/libcalico-go/lib/resources/*.go','/libcalico-go/lib/selector/*.go','/libcalico-go/lib/selector/parser/*.go','/libcalico-go/lib/selector/tokenizer/*.go','/libcalico-go/lib/set/*.go','/libcalico-go/lib/validator/v3/*.go','/libcalico-go/lib/watch/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/pkg/buildinfo/*.go','/webhooks/**','api/**/*Dockerfile*','api/Makefile','api/deps.txt','apiserver/**/*Dockerfile*','apiserver/Makefile','apiserver/deps.txt'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/apiserver/**/*_test.go','/crypto/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/pkg/**/*_test.go']})" + dependencies: + - Prerequisites + task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "webhooks" + prologue: + commands: + - cd webhooks + jobs: + - name: "webhooks: CI" + commands: + - ../.semaphore/run-and-monitor make-ci.log make ci + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: Whisker backend + run: + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/20-whisker-backend.yml','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/crypto/pkg/tls/*.go','/felix/proto/*.go','/felix/types/*.go','/goldmane/pkg/client/*.go','/goldmane/pkg/internal/flowcache/*.go','/goldmane/pkg/types/*.go','/goldmane/proto/*.go','/hack/test/certs/','/lib.Makefile','/lib/httpmachinery/pkg/apiutil/*.go','/lib/httpmachinery/pkg/codec/*.go','/lib/httpmachinery/pkg/context/*.go','/lib/httpmachinery/pkg/header/*.go','/lib/httpmachinery/pkg/server/*.go','/lib/httpmachinery/pkg/server/adaptors/gorilla/*.go','/lib/std/cryptoutils/*.go','/lib/std/testutils/json/*.go','/lib/std/time/*.go','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/metadata.mk','/whisker-backend/**'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/crypto/**/*_test.go','/felix/**/*_test.go','/goldmane/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go']})" execution_time_limit: minutes: 30 dependencies: @@ -918,10 +1284,10 @@ blocks: commands: - cd whisker-backend jobs: - - name: make ci + - name: "Whisker backend: CI" commands: - ../.semaphore/run-and-monitor make-ci.log make ci - - name: Build binary + - name: "Whisker backend: build multi-arch" matrix: - env_var: ARCH values: @@ -930,7 +1296,7 @@ blocks: - s390x commands: - ../.semaphore/run-and-monitor image-$ARCH.log make build ARCH=$ARCH - - name: whisker + - name: Whisker run: when: "false or change_in(['/metadata.mk', '/lib.Makefile', '/whisker/'], {pipeline_file: 'ignore', exclude: ['/**/.gitignore', '/**/README.md', '/**/LICENSE']})" execution_time_limit: @@ -938,14 +1304,20 @@ blocks: dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "whisker" prologue: commands: - cd whisker jobs: - - name: make ci + - name: "Whisker: CI" commands: - ../.semaphore/run-and-monitor make-ci.log make ci - - name: Build binary + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: "Whisker: build multi-arch images" matrix: - env_var: ARCH values: @@ -956,68 +1328,26 @@ blocks: - ../.semaphore/run-and-monitor image-$ARCH.log make build ARCH=$ARCH - name: key-cert-provisioner run: - when: "change_in(['/hack/test/certs/','/key-cert-provisioner/**','/lib.Makefile','/metadata.mk'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/30-key-cert-provisioner.yml','/hack/test/certs/','/key-cert-provisioner/**','/lib.Makefile','/metadata.mk'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md']})" dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "key-cert-provisioner" prologue: commands: - cd key-cert-provisioner jobs: - - name: key-cert-provisioner tests + - name: "key-cert-provisioner: CI" commands: - ../.semaphore/run-and-monitor ci.log make ci - - name: "OpenStack integration (Yoga)" - run: - when: "false or change_in(['/metadata.mk', '/lib.Makefile', '/networking-calico/'], {pipeline_file: 'ignore'})" - dependencies: - - Prerequisites - task: - agent: - machine: - type: f1-standard-2 - os_image: ubuntu2004 - prologue: - commands: - - cd networking-calico - jobs: - - name: "Unit and FV tests (tox) on Yoga" - commands: - - ../.semaphore/run-and-monitor tox.log make tox-yoga - - name: "Mainline ST (DevStack + Tempest) on Yoga" - commands: - # For some reason python3-wrapt is pre-installed on a Semaphore ubuntu2004 node, but with - # a version (1.11.2) that is different from the version that OpenStack needs (1.13.3), and - # this was causing the DevStack setup to fail, because pip doesn't know how to uninstall - # or replace the existing version. Happily we do know that, so let's do it upfront here. - - sudo apt-get remove -y python3-wrapt || true - # Install all the packages that would trigger an initramfs update using a workaround - # for limited /boot partition space in the ubuntu2004 image - - sudo apt update - - sudo rsync -av /boot/ /boot2/ - - sudo mount --bind /boot2 /boot - - sudo apt install -y cryptsetup lsscsi open-iscsi thin-provisioning-tools - - sudo umount /boot - - sudo rsync -av /boot2/ /boot/ --exclude "*.new" --exclude "*.dpkg-bak" --delete --inplace - - sudo rm -rf /boot2/ - # Set NC_PLUGIN_REPO and NC_PLUGIN_REF so that DevStack will use the networking-calico - # code from the current PR or branch. The following checkout is to make sure of having a - # local ref that we can specify. - - git checkout -b devstack-test - - export NC_PLUGIN_REPO=$(dirname $(pwd)) - - export NC_PLUGIN_REF=$(git rev-parse --abbrev-ref HEAD) - - sudo git config --system --add safe.directory ${NC_PLUGIN_REPO}/.git - - TEMPEST=true DEVSTACK_BRANCH=unmaintained/yoga ./devstack/bootstrap.sh - epilogue: - on_fail: - commands: - - mkdir logs - - sudo journalctl > logs/journalctl.txt - - artifact push job logs - + env_vars: + - name: STORE_BUILD_CACHE + value: "true" - name: "OpenStack integration (Caracal)" run: - when: "false or change_in(['/networking-calico/'], {pipeline_file: 'ignore'})" + when: "false or change_in(['/metadata.mk', '/lib.Makefile', '/networking-calico/', '/.semaphore/semaphore.yml.d/blocks/40-openstack.yml'], {pipeline_file: 'ignore'})" dependencies: - Prerequisites task: @@ -1029,10 +1359,10 @@ blocks: commands: - cd networking-calico jobs: - - name: "Unit and FV tests (tox) on Caracal" + - name: "OpenStack: Unit and FV tests (tox) on Caracal" commands: - ../.semaphore/run-and-monitor tox.log make tox-caracal - - name: "Mainline ST (DevStack + Tempest) on Caracal" + - name: "OpenStack: Mainline ST (DevStack + Tempest) on Caracal" commands: # For some reason python3-wrapt is pre-installed on a Semaphore ubuntu2004 node, but with # a version (1.11.2) that is different from the version that OpenStack needs (1.13.3), and @@ -1046,7 +1376,10 @@ blocks: - export NC_PLUGIN_REPO=$(dirname $(pwd)) - export NC_PLUGIN_REF=$(git rev-parse --abbrev-ref HEAD) - sudo git config --system --add safe.directory ${NC_PLUGIN_REPO}/.git - - TEMPEST=true DEVSTACK_BRANCH=stable/2024.1 ./devstack/bootstrap.sh + # The BIRD config in our DevStack setup is thoroughly broken and generates loads of errors + # in the log. But we don't need it for a single node setup anyway, so let's disable it. + - export CALICO_BGP_MODE=none + - TEMPEST=true OPENSTACK_RELEASE=caracal ./devstack/bootstrap.sh epilogue: always: commands: @@ -1055,20 +1388,26 @@ blocks: - artifact push job logs - name: Mock node run: - when: "change_in(['/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/crypto/pkg/tls/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/lib/apis/v3/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/pkg/buildinfo/*.go','/test-tools/mocknode/**','/typha/pkg/discovery/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/tlsutils/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/crypto/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/pkg/**/*_test.go','/typha/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/60-mock-node.yml','/api/pkg/apis/projectcalico/v3/*.go','/api/pkg/lib/numorstring/*.go','/crypto/pkg/tls/*.go','/hack/test/certs/','/lib.Makefile','/lib/std/uniquelabels/*.go','/lib/std/uniquestr/*.go','/libcalico-go/lib/apis/internalapi/*.go','/libcalico-go/lib/backend/api/*.go','/libcalico-go/lib/backend/encap/*.go','/libcalico-go/lib/backend/model/*.go','/libcalico-go/lib/errors/*.go','/libcalico-go/lib/json/*.go','/libcalico-go/lib/logutils/*.go','/libcalico-go/lib/names/*.go','/libcalico-go/lib/namespace/*.go','/libcalico-go/lib/net/*.go','/libcalico-go/lib/readlogger/*.go','/libcalico-go/lib/winutils/*.go','/metadata.mk','/pkg/buildinfo/*.go','/test-tools/mocknode/**','/typha/pkg/discovery/*.go','/typha/pkg/syncclient/*.go','/typha/pkg/syncproto/*.go','/typha/pkg/tlsutils/*.go'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/api/**/*_test.go','/crypto/**/*_test.go','/lib/**/*_test.go','/libcalico-go/**/*_test.go','/pkg/**/*_test.go','/typha/**/*_test.go']})" dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "mock-node" prologue: commands: - cd test-tools/mocknode jobs: - - name: Mock node + - name: "Mock node: CI" commands: - ../../.semaphore/run-and-monitor make-ci.log make ci - - name: release tooling + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: Release tooling run: - when: "change_in(['/hack/test/certs/','/lib.Makefile','/libcalico-go/lib/logutils/*.go','/metadata.mk','/release/**'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/libcalico-go/**/*_test.go']})" + when: "change_in(['/.semaphore/semaphore.yml.d/01-preamble.yml','/.semaphore/semaphore.yml.d/02-global_job_config.yml','/.semaphore/semaphore.yml.d/03-promotions.yml','/.semaphore/semaphore.yml.d/09-blocks.yml','/.semaphore/semaphore.yml.d/99-after_pipeline.yml','/.semaphore/semaphore.yml.d/blocks/90-release.yml','/hack/test/certs/','/lib.Makefile','/libcalico-go/lib/logutils/*.go','/metadata.mk','/release/**'], {pipeline_file: 'ignore', exclude: ['/**/*.md','/**/.gitignore','/**/AUTHORS*','/**/CONTRIBUTING*','/**/DEVELOPER_GUIDE*','/**/LICENSE*','/**/README*','/**/SECURITY.md','/libcalico-go/**/*_test.go']})" execution_time_limit: minutes: 30 dependencies: @@ -1078,11 +1417,11 @@ blocks: commands: - cd release jobs: - - name: ci + - name: "Release tooling: CI" commands: - ../.semaphore/run-and-monitor release-ci.log make ci - - test-results publish --name "release-tool-ut-tests" ./report/*.xml || true - - name: build binary + - 'test-results publish --name "Release tool: UT" ./report/*.xml || true' + - name: "Release tooling: build" commands: - ../.semaphore/run-and-monitor release-build.log make build - cache store release-${SEMAPHORE_GIT_SHA} bin diff --git a/.semaphore/semaphore.yml.d/02-global_job_config.yml b/.semaphore/semaphore.yml.d/02-global_job_config.yml index 634bda78365..370d8664f36 100644 --- a/.semaphore/semaphore.yml.d/02-global_job_config.yml +++ b/.semaphore/semaphore.yml.d/02-global_job_config.yml @@ -1,16 +1,92 @@ global_job_config: secrets: - name: docker-hub + - name: google-service-account-for-gce + env_vars: + - name: GOOGLE_PROJECT + value: unique-caldron-775 + - name: GCS_BUILD_CACHE_BUCKET + value: calico-transient-build-artifacts-europe-west3 + - name: BUILDKIT_PROGRESS + value: plain + - name: SEMAPHORE_AGENT_UPLOAD_JOB_LOGS + value: when-trimmed prologue: commands: - - checkout - - export REPO_DIR="$(pwd)" - - mkdir artifacts - # Semaphore is doing shallow clone on a commit without tags. - # unshallow it for GIT_VERSION:=$(shell git describe --tags --dirty --always) - - if [[ "${SEMAPHORE_BLOCK_NAME}" != "Prerequisites" ]]; then retry git fetch --unshallow; fi + - export CALICO_DIR_NAME=${SEMAPHORE_GIT_DIR} + - export GCS_CACHE_DIR=gs://$GCS_BUILD_CACHE_BUCKET/cache/${CALICO_DIR_NAME} + - export GCS_WORKFLOW_DIR=gs://$GCS_BUILD_CACHE_BUCKET/workflow/${SEMAPHORE_WORKFLOW_ID} + - export GOOGLE_APPLICATION_CREDENTIALS=$HOME/secrets/secret.google-service-account-key.json + - |- + if [[ $(uname -m) != "x86_64" || -n "$ARCH" ]]; then + echo "Non-x86_64 build: $(uname -m) / $ARCH"; + export BUILD_CACHE_GROUP=$BUILD_CACHE_GROUP-$(uname -m)-$ARCH; + fi + - gcloud auth activate-service-account --key-file="$GOOGLE_APPLICATION_CREDENTIALS" || true + - gcloud config set project ${GOOGLE_PROJECT} || true + # Try to restore the working copy from the cache, if available. Then, + # for PRs, also try to restore the build cache. (We skip this on branch + # builds to ensure branches build cleanly.) + - sudo apt-get install -y zstd || true + - restored_wc_cache=false + - |- + if [[ -n "${SEMAPHORE_GIT_BRANCH}" && -z "$SKIP_CACHE_RESTORE" ]]; then + echo "Looking for cached working copy for branch ${SEMAPHORE_GIT_BRANCH}" + gcs_branch_cache_dir="${GCS_CACHE_DIR}/${SEMAPHORE_GIT_BRANCH}" + if gcloud storage cp "$gcs_branch_cache_dir/working-copy.tar.zstd" /tmp/working-copy.tar.zstd; then + echo "Restoring cache for branch ${SEMAPHORE_GIT_BRANCH}" + tar --use-compress-program=zstd -xf /tmp/working-copy.tar.zstd & tar_pid=$! + downloaded_build_cache=false + if [[ -n "$BUILD_CACHE_GROUP" && -n "${SEMAPHORE_GIT_PR_NUMBER}" ]]; then + if gcloud storage cp "$gcs_branch_cache_dir/build-cache-${BUILD_CACHE_GROUP}.tar.zstd" /tmp/build-cache.tar.zstd; then + echo "Found build cache for group ${BUILD_CACHE_GROUP}" + downloaded_build_cache=true + fi + fi + if wait $tar_pid; then + restored_wc_cache=true + else + echo "ERROR: Failed to extract working copy" + restored_wc_cache=false + fi + if ${downloaded_build_cache}; then + echo "Extracting build cache" + tar --use-compress-program=zstd -xf /tmp/build-cache.tar.zstd -C . + rm /tmp/build-cache.tar.zstd + fi + cd "${CALICO_DIR_NAME}" + fi + fi || true + - |- + if [[ ${restored_wc_cache} != true && -z "$SKIP_CHECKOUT" ]]; then + echo "No cache restored, doing a fresh checkout..." + cd $HOME + rm -rf "${CALICO_DIR_NAME}" + git clone --filter=blob:none $SEMAPHORE_GIT_URL $CALICO_DIR_NAME + cd "${CALICO_DIR_NAME}" || exit 1 + fi + - |- + if [[ -z "$SKIP_CHECKOUT" ]]; then + git fetch origin --tags --prune-tags ${SEMAPHORE_GIT_SHA} + if [ "$SEMAPHORE_GIT_REF_TYPE" = "branch" ]; then + git checkout ${SEMAPHORE_GIT_SHA} -B "${SEMAPHORE_GIT_BRANCH}" + else + git checkout ${SEMAPHORE_GIT_SHA} + fi + CALICO_DIR="$(pwd)" + export CALICO_DIR + mkdir -p artifacts + fi - echo $DOCKERHUB_PASSWORD | docker login --username "$DOCKERHUB_USERNAME" --password-stdin epilogue: commands: - - cd "$REPO_DIR" + - cd $HOME + - |- + if [[ -z "${SEMAPHORE_GIT_PR_NUMBER}" && -n "$BUILD_CACHE_GROUP" && "$STORE_BUILD_CACHE" = "true" ]]; then + echo "On branch, storing build cache group ${BUILD_CACHE_GROUP}" + tar --use-compress-program="zstd -3" -cf /tmp/build-cache.tar.zstd go ${CALICO_DIR_NAME}/.go-pkg-cache + gcs_branch_cache_dir="${GCS_CACHE_DIR}/${SEMAPHORE_GIT_BRANCH}" + gcloud storage cp /tmp/build-cache.tar.zstd "$gcs_branch_cache_dir/build-cache-${BUILD_CACHE_GROUP}.tar.zstd" + fi + - cd "$CALICO_DIR" - .semaphore/publish-artifacts diff --git a/.semaphore/semaphore.yml.d/03-promotions.yml b/.semaphore/semaphore.yml.d/03-promotions.yml index 5987be7f18c..f8b38dcddad 100644 --- a/.semaphore/semaphore.yml.d/03-promotions.yml +++ b/.semaphore/semaphore.yml.d/03-promotions.yml @@ -2,6 +2,29 @@ promotions: # Manual promotion for publishing a hashrelease. - name: Publish hashrelease pipeline_file: release/hashrelease.yml + parameters: + env_vars: + - required: true + options: + - "true" + - "false" + default_value: "false" + description: "Build container images. If 'true', container images will be built as part of the hashrelease process." + name: BUILD_CONTAINER_IMAGES + - required: true + options: + - "true" + - "false" + default_value: "false" + description: "create tarball of images. If 'true', a tarball of images will be added to the release.tgz. Requires BUILD_CONTAINER_IMAGES to also be 'true'." + name: ARCHIVE_IMAGES + - required: true + options: + - "true" + - "false" + default_value: "false" + description: "publish images. If 'true', images will be pushed to the container registry/registries as part of the hashrelease process. Requires BUILD_CONTAINER_IMAGES to also be 'true'." + name: PUBLISH_IMAGES # Manual promotion for publishing a release. - name: Publish official release pipeline_file: release/release.yml diff --git a/.semaphore/semaphore.yml.d/09-blocks.yml b/.semaphore/semaphore.yml.d/09-blocks.yml new file mode 100644 index 00000000000..6a40527eaad --- /dev/null +++ b/.semaphore/semaphore.yml.d/09-blocks.yml @@ -0,0 +1 @@ +blocks: diff --git a/.semaphore/semaphore.yml.d/blocks/10-check-images.yml b/.semaphore/semaphore.yml.d/blocks/10-check-images.yml index 2a5b4d4edc0..3b2889897fe 100644 --- a/.semaphore/semaphore.yml.d/blocks/10-check-images.yml +++ b/.semaphore/semaphore.yml.d/blocks/10-check-images.yml @@ -1,6 +1,6 @@ - name: Check images availability run: - when: "${FORCE_RUN} or change_in(['/charts/', '/manifests/'], {pipeline_file: 'ignore'})" + when: "${FORCE_RUN} or (branch !~ 'build-v.*' and change_in(['/charts/', '/manifests/'], {pipeline_file: 'ignore'}))" dependencies: [] task: jobs: diff --git a/.semaphore/semaphore.yml.d/blocks/10-prerequisites.yml b/.semaphore/semaphore.yml.d/blocks/10-prerequisites.yml index 9be330b5cfb..55227f7f95e 100644 --- a/.semaphore/semaphore.yml.d/blocks/10-prerequisites.yml +++ b/.semaphore/semaphore.yml.d/blocks/10-prerequisites.yml @@ -1,11 +1,145 @@ -- name: Prerequisites +- name: Check SemaphoreCI files + run: + when: >- + ${FORCE_RUN} or + change_in(['/.semaphore', '/hack', '/Makefile', '/lib.Makefile', '/metadata.mk'], {pipeline_file: 'track'}) + dependencies: [] + task: + jobs: + - name: Check semaphore files + commands: + - make gen-semaphore-yaml + - make check-dirty +- name: Check YAML files + run: + when: >- + ${FORCE_RUN} or + change_in(['/**/*.yml', '/**/*.yaml', '/Makefile', '/lib.Makefile', '/metadata.mk'], {pipeline_file: 'track'}) + dependencies: [] + task: + jobs: + - name: Check YAML files + commands: + - make yaml-lint +- name: Check Go + run: + when: >- + ${FORCE_RUN} or + change_in(['/go.mod', '/go.sum', '/**/*.go', '/**/deps.txt', '/Makefile', '/lib.Makefile', '/metadata.mk'], {pipeline_file: 'ignore'}) dependencies: [] task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "preflight-go-checks" agent: machine: - type: f1-standard-2 + type: f1-standard-4 os_image: ubuntu2204 + jobs: + - name: Check Go files + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + commands: + - make check-ginkgo-v2 + - make check-go-mod + - make verify-go-mods + - make gen-deps-files + - make go-vet + - >- + if [ -n "$SEMAPHORE_GIT_PR_NUMBER" ]; then + make fix-changed + else + make fix-all + fi + - make check-dirty +- name: Check Python + run: + when: >- + ${FORCE_RUN} or + change_in(['/networking-calico', '/lib.Makefile', '/metadata.mk'], {pipeline_file: 'ignore'}) + dependencies: [] + task: + jobs: + - name: Check Python files + commands: + - make -C networking-calico fmtpy + - make -C networking-calico flake8 + - make check-dirty +- name: Check language/Dockerfiles + run: + when: >- + ${FORCE_RUN} or + change_in(['/**/*'], {pipeline_file: 'track', exclude: ['libbpf', '.[a-z]*']}) + dependencies: [] + task: + jobs: + - name: Check language/Dockerfiles + commands: + # Both of these are very quick grep checks so we combine them into one job. + - make check-language + - make check-dockerfiles +- name: Check protobufs + run: + when: >- + ${FORCE_RUN} or + change_in(['/**/*.pb.go', '/**/*.proto', '/Makefile', '/lib.Makefile', '/metadata.mk'], {pipeline_file: 'ignore'}) + dependencies: [] + task: + jobs: + - name: Check protobufs + commands: + - make protobuf fix-changed + - make check-dirty +- name: Manifests and API docs + run: + when: >- + ${FORCE_RUN} or + change_in(['/api', '/manifests', '/charts', '/libcalico-go/lib/apis', '/libcalico-go/config', '/libcalico-go/Makefile', '/felix/config', '/felix/docs', '/felix/Makefile', '/goldmane', '/Makefile', '/lib.Makefile', '/metadata.mk'], {pipeline_file: 'ignore'}) + dependencies: [] + task: + jobs: + - name: Check manifests and API docs + commands: + - make -C lib gen-files + - make -C api gen-files + - make -C libcalico-go gen-files + - make -C felix gen-files + - make -C goldmane gen-files + - make gen-manifests + - make fix-changed + - make check-ocp-no-crds + - make check-dirty +- name: Store working copy + run: + when: "branch =~ '.*'" + dependencies: [] + task: + env_vars: + - name: SKIP_CACHE_RESTORE + value: "true" + jobs: + - name: Save working copy to cache + commands: + - cd .. + - tar --use-compress-program="zstd -3" -cf /tmp/working-copy.tar.zstd $CALICO_DIR_NAME + - gcs_branch_cache_dir="${GCS_CACHE_DIR}/${SEMAPHORE_GIT_BRANCH}" + - gcloud storage cp /tmp/working-copy.tar.zstd "$gcs_branch_cache_dir/working-copy.tar.zstd" +- name: Prerequisites + skip: + # Always skip, this is a dummy job used to group prerequisite checks. + when: "true" + dependencies: + - Check SemaphoreCI files + - Check YAML files + - Check Go + - Check Python + - Check language/Dockerfiles + - Check protobufs + - Manifests and API docs + - Store working copy + task: jobs: - name: Pre-flight checks commands: - - make ci-preflight-checks + - "true" diff --git a/.semaphore/semaphore.yml.d/blocks/20-api.yml b/.semaphore/semaphore.yml.d/blocks/20-api.yml index 157312ccb12..10c98b2b3bb 100644 --- a/.semaphore/semaphore.yml.d/blocks/20-api.yml +++ b/.semaphore/semaphore.yml.d/blocks/20-api.yml @@ -10,6 +10,6 @@ commands: - cd api jobs: - - name: make ci + - name: "API: CI" commands: - ../.semaphore/run-and-monitor make-ci.log make ci diff --git a/.semaphore/semaphore.yml.d/blocks/20-apiserver.yml b/.semaphore/semaphore.yml.d/blocks/20-apiserver.yml index d8bd11eb995..cf6fc00ed9a 100644 --- a/.semaphore/semaphore.yml.d/blocks/20-apiserver.yml +++ b/.semaphore/semaphore.yml.d/blocks/20-apiserver.yml @@ -1,4 +1,4 @@ -- name: apiserver +- name: API Server run: when: "${CHANGE_IN(apiserver)}" execution_time_limit: @@ -6,14 +6,20 @@ dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "apiserver" prologue: commands: - cd apiserver jobs: - - name: make ci + - name: "API Server: CI" commands: - ../.semaphore/run-and-monitor make-ci.log make ci - - name: Build binary + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: "API Server: Build" matrix: - env_var: ARCH values: diff --git a/.semaphore/semaphore.yml.d/blocks/20-app-policy.yml b/.semaphore/semaphore.yml.d/blocks/20-app-policy.yml index 7861c90e385..1d6cb0c04e8 100644 --- a/.semaphore/semaphore.yml.d/blocks/20-app-policy.yml +++ b/.semaphore/semaphore.yml.d/blocks/20-app-policy.yml @@ -1,4 +1,4 @@ -- name: app-policy +- name: Application Layer Policy run: when: "${CHANGE_IN(app-policy)}" dependencies: @@ -8,6 +8,6 @@ commands: - cd app-policy jobs: - - name: app-policy tests + - name: "Application Layer Policy: CI" commands: - ../.semaphore/run-and-monitor ci.log make ci diff --git a/.semaphore/semaphore.yml.d/blocks/20-calicoctl.yml b/.semaphore/semaphore.yml.d/blocks/20-calicoctl.yml index 05954b5a688..6fa09449b8e 100644 --- a/.semaphore/semaphore.yml.d/blocks/20-calicoctl.yml +++ b/.semaphore/semaphore.yml.d/blocks/20-calicoctl.yml @@ -4,10 +4,16 @@ dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "calicoctl" prologue: commands: - cd calicoctl jobs: - - name: calicoctl tests + - name: "calicoctl: CI" commands: - ../.semaphore/run-and-monitor ci.log make ci + env_vars: + - name: STORE_BUILD_CACHE + value: "true" diff --git a/.semaphore/semaphore.yml.d/blocks/20-cni-plugin.yml b/.semaphore/semaphore.yml.d/blocks/20-cni-plugin.yml index 8eb3aae0d2b..8eb697f8548 100644 --- a/.semaphore/semaphore.yml.d/blocks/20-cni-plugin.yml +++ b/.semaphore/semaphore.yml.d/blocks/20-cni-plugin.yml @@ -1,22 +1,28 @@ -- name: cni-plugin +- name: CNI Plugin run: when: "${CHANGE_IN(cni-plugin)}" dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "cni-plugin" prologue: commands: - cd cni-plugin jobs: - - name: cni-plugin tests + - name: "CNI Plugin: CI" commands: - ../.semaphore/run-and-monitor ci.log make ci - - name: build windows cni-plugin images + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: "CNI Plugin: build windows images" commands: - ../.semaphore/run-and-monitor ci.log make image-windows -- name: "cni-plugin: Windows" +- name: "CNI Plugin: Windows" run: - when: "${CHANGE_IN(cni-plugin)}" + when: "false or ${CHANGE_IN(cni-plugin,non-go:/process/testing/aso,non-go:/process/testing/utils,non-go:/process/testing/winfv-cni-plugin)}" dependencies: - Prerequisites task: @@ -32,9 +38,7 @@ - export AZURE_CLIENT_SECRET=$AZ_SP_PASSWORD - export REPORT_DIR=/home/semaphore/calico/process/testing/winfv-cni-plugin/report - export LOGS_DIR=~/fv.log - - export SHORT_WORKFLOW_ID=$(echo ${SEMAPHORE_WORKFLOW_ID} | sha256sum | cut -c -8) - - export CLUSTER_NAME=sem-${SEMAPHORE_PROJECT_NAME}-pr${SEMAPHORE_GIT_PR_NUMBER}-${SHORT_WORKFLOW_ID} - - export SUFFIX=${CLUSTER_NAME} + - export RG_PREFIX=${USER}-win-cni-${SEMAPHORE_GIT_SHA:0:4}-pr${SEMAPHORE_GIT_PR_NUMBER} - cd cni-plugin - ../.semaphore/run-and-monitor build.log make bin/windows/calico.exe bin/windows/calico-ipam.exe bin/windows/win-fv.exe epilogue: @@ -42,26 +46,30 @@ commands: - artifact push job ${REPORT_DIR} --destination semaphore/test-results || true - artifact push job ${LOGS_DIR} --destination semaphore/logs || true - - cd ~/calico/process/testing/winfv-cni-plugin/aso && make dist-clean + - cd ~/calico/process/testing/aso && make dist-clean env_vars: + - name: BUILD_CACHE_GROUP + value: "cni-plugin" - name: SEMAPHORE_ARTIFACT_EXPIRY value: 2w - name: AZURE_LOCATION value: eastus2 - name: KUBE_VERSION - value: v1.29.7 + value: v1.33.7 jobs: - - name: Containerd - Windows FV - overlay + - name: "CNI Plugin: Windows Containerd FV - overlay" execution_time_limit: minutes: 60 commands: - export BACKEND=overlay - - export AZURE_RESOURCE_GROUP=${USER}-capz-win-cni-${SEMAPHORE_WORKFLOW_ID:0:8}-${BACKEND}-rg - - ../.semaphore/run-and-monitor win-fv-containerd.log ./.semaphore/run-win-fv.sh - - name: Containerd - Windows FV - l2bridge + - export AZURE_RESOURCE_GROUP=${RG_PREFIX}-${BACKEND}-rg + - echo AZURE_RESOURCE_GROUP=${AZURE_RESOURCE_GROUP} + - ../.semaphore/run-and-monitor win-fv-containerd.log ~/calico/process/testing/winfv-cni-plugin/run-win-fv.sh + - name: "CNI Plugin: Windows Containerd FV - l2bridge" execution_time_limit: minutes: 60 commands: - export BACKEND=l2bridge - - export AZURE_RESOURCE_GROUP=${USER}-capz-win-cni-${SEMAPHORE_WORKFLOW_ID:0:8}-${BACKEND}-rg - - ../.semaphore/run-and-monitor win-fv-containerd.log ./.semaphore/run-win-fv.sh + - export AZURE_RESOURCE_GROUP=${RG_PREFIX}-${BACKEND}-rg + - echo AZURE_RESOURCE_GROUP=${AZURE_RESOURCE_GROUP} + - ../.semaphore/run-and-monitor win-fv-containerd.log ~/calico/process/testing/winfv-cni-plugin/run-win-fv.sh diff --git a/.semaphore/semaphore.yml.d/blocks/20-confd.yml b/.semaphore/semaphore.yml.d/blocks/20-confd.yml index 74a8b297241..35b14ce0892 100644 --- a/.semaphore/semaphore.yml.d/blocks/20-confd.yml +++ b/.semaphore/semaphore.yml.d/blocks/20-confd.yml @@ -4,6 +4,9 @@ dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "confd" prologue: commands: - cd confd @@ -13,3 +16,6 @@ minutes: 60 commands: - ../.semaphore/run-and-monitor ci.log make ci + env_vars: + - name: STORE_BUILD_CACHE + value: "true" diff --git a/.semaphore/semaphore.yml.d/blocks/20-crypto.yml b/.semaphore/semaphore.yml.d/blocks/20-crypto.yml deleted file mode 100644 index 1d216ecad58..00000000000 --- a/.semaphore/semaphore.yml.d/blocks/20-crypto.yml +++ /dev/null @@ -1,13 +0,0 @@ -- name: crypto - run: - when: "${CHANGE_IN(crypto)}" - dependencies: - - Prerequisites - task: - prologue: - commands: - - cd crypto - jobs: - - name: "crypto tests" - commands: - - ../.semaphore/run-and-monitor ci.log make ci diff --git a/.semaphore/semaphore.yml.d/blocks/20-e2e-validation.yml b/.semaphore/semaphore.yml.d/blocks/20-e2e-validation.yml index a606e64023a..9512602aae8 100644 --- a/.semaphore/semaphore.yml.d/blocks/20-e2e-validation.yml +++ b/.semaphore/semaphore.yml.d/blocks/20-e2e-validation.yml @@ -1,4 +1,4 @@ -- name: e2e script & pipeline validations +- name: E2E script & pipeline validations run: when: "${FORCE_RUN} or change_in(['/.semaphore/end-to-end/'], {pipeline_file: 'ignore', exclude: ['/**/.gitignore', '/**/README.md', '/**/LICENSE']})" execution_time_limit: @@ -7,6 +7,6 @@ - Prerequisites task: jobs: - - name: Test Pipeline files are valid yaml + - name: "E2E script & pipeline validations: validate yaml" commands: - ./.semaphore/end-to-end/scripts/test_scripts/e2e-validation.sh diff --git a/.semaphore/semaphore.yml.d/blocks/20-e2e.yml b/.semaphore/semaphore.yml.d/blocks/20-e2e.yml index a905bea1e76..4e050ecddc5 100644 --- a/.semaphore/semaphore.yml.d/blocks/20-e2e.yml +++ b/.semaphore/semaphore.yml.d/blocks/20-e2e.yml @@ -1,14 +1,20 @@ -- name: e2e tests +- name: E2E tests run: when: "${CHANGE_IN(e2e,node,typha,apiserver,cni-plugin,pod2daemon,calicoctl,kube-controllers,goldmane,whisker,whisker-backend)}" dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "e2e-tests" agent: machine: type: f1-standard-4 - os_image: ubuntu2004 + os_image: ubuntu2204 jobs: - - name: Conformance e2e tests + - name: "E2E tests: Conformance" commands: - .semaphore/run-and-monitor e2e-test.log make e2e-test + env_vars: + - name: STORE_BUILD_CACHE + value: "true" diff --git a/.semaphore/semaphore.yml.d/blocks/20-envoy-gateway.yml b/.semaphore/semaphore.yml.d/blocks/20-envoy-gateway.yml index 4c8ca96320d..6858cf2f6ae 100644 --- a/.semaphore/semaphore.yml.d/blocks/20-envoy-gateway.yml +++ b/.semaphore/semaphore.yml.d/blocks/20-envoy-gateway.yml @@ -1,4 +1,4 @@ -- name: envoy-gateway +- name: Envoy Gateway run: when: "${FORCE_RUN} or change_in(['/metadata.mk', '/lib.Makefile', '/third_party/envoy-gateway'], {pipeline_file: 'ignore', exclude: ['/**/.gitignore', '/**/README.md', '/**/LICENSE']})" execution_time_limit: @@ -10,10 +10,10 @@ commands: - cd third_party/envoy-gateway jobs: - - name: make ci + - name: "Envoy Gateway: CI" commands: - ../../.semaphore/run-and-monitor make-ci.log make ci - - name: Build binary + - name: "Envoy Gateway: build multi-arch binaries" matrix: - env_var: ARCH values: diff --git a/.semaphore/semaphore.yml.d/blocks/20-envoy-proxy.yml b/.semaphore/semaphore.yml.d/blocks/20-envoy-proxy.yml index 9a84503c3dd..43965b909d9 100644 --- a/.semaphore/semaphore.yml.d/blocks/20-envoy-proxy.yml +++ b/.semaphore/semaphore.yml.d/blocks/20-envoy-proxy.yml @@ -1,4 +1,4 @@ -- name: envoy-proxy +- name: Envoy Proxy run: when: "${FORCE_RUN} or change_in(['/metadata.mk', '/lib.Makefile', '/third_party/envoy-proxy'], {pipeline_file: 'ignore', exclude: ['/**/.gitignore', '/**/README.md', '/**/LICENSE']})" execution_time_limit: @@ -10,10 +10,10 @@ commands: - cd third_party/envoy-proxy jobs: - - name: make ci + - name: "Envoy Proxy: CI" commands: - ../../.semaphore/run-and-monitor make-ci.log make ci - - name: Build binary + - name: "Envoy Proxy: build multi-arch binaries" matrix: - env_var: ARCH values: diff --git a/.semaphore/semaphore.yml.d/blocks/20-envoy-ratelimit.yml b/.semaphore/semaphore.yml.d/blocks/20-envoy-ratelimit.yml index a2162d87dd7..51f29ef0d17 100644 --- a/.semaphore/semaphore.yml.d/blocks/20-envoy-ratelimit.yml +++ b/.semaphore/semaphore.yml.d/blocks/20-envoy-ratelimit.yml @@ -1,4 +1,4 @@ -- name: envoy-ratelimit +- name: "Envoy Ratelimit" run: when: "${FORCE_RUN} or change_in(['/metadata.mk', '/lib.Makefile', '/third_party/envoy-ratelimit'], {pipeline_file: 'ignore', exclude: ['/**/.gitignore', '/**/README.md', '/**/LICENSE']})" execution_time_limit: @@ -10,10 +10,10 @@ commands: - cd third_party/envoy-ratelimit jobs: - - name: make ci + - name: "Envoy Ratelimit: CI" commands: - ../../.semaphore/run-and-monitor make-ci.log make ci - - name: Build binary + - name: "Envoy Ratelimit: build multi-arch binaries" matrix: - env_var: ARCH values: diff --git a/.semaphore/semaphore.yml.d/blocks/20-felix.yml b/.semaphore/semaphore.yml.d/blocks/20-felix.yml index ff994b34bc2..4864130c4ae 100644 --- a/.semaphore/semaphore.yml.d/blocks/20-felix.yml +++ b/.semaphore/semaphore.yml.d/blocks/20-felix.yml @@ -1,36 +1,65 @@ - name: "Felix: Build" run: - when: "${CHANGE_IN(felix,typha)}" + when: "${CHANGE_IN(felix,typha,non-go:/.semaphore/vms/)}" dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "felix" prologue: commands: - cd felix - - cache restore go-pkg-cache - - cache restore go-mod-cache + - gcloud config set project unique-caldron-775 + - gcloud auth activate-service-account --key-file=$HOME/secrets/secret.google-service-account-key.json + secrets: + - name: google-service-account-for-gce jobs: - - name: Build and run UT, k8sfv + - name: "Felix: Build and cache FV prerequisites" + priority: + # This job is on the critical path so we increase its priority. + - value: 51 + when: "pull_request =~ '.+'" + env_vars: + - name: STORE_BUILD_CACHE + value: "true" execution_time_limit: - minutes: 60 + minutes: 20 + commands: + - make build image ut-bpf-prereqs fv-prereqs + - ./.semaphore/cache-test-artifacts + - name: "Felix: UT" + priority: + # This job is on the critical path so we increase its priority. + - value: 51 + when: "pull_request =~ '.+'" + execution_time_limit: + minutes: 30 commands: - - make build image fv-prereqs - - "cache store bin-${SEMAPHORE_GIT_SHA} bin" - - "cache store fv.test-${SEMAPHORE_GIT_SHA} fv/fv.test" - - cache store go-pkg-cache .go-pkg-cache - - "cache store go-mod-cache ${HOME}/go/pkg/mod/cache" - - docker save -o /tmp/calico-felix-test.tar calico/felix-test:latest-amd64 - - "cache store felix-image-${SEMAPHORE_GIT_SHA} /tmp/calico-felix-test.tar" - - docker save -o /tmp/felixtest-typha.tar felix-test/typha:latest-amd64 - - "cache store felixtest-typha-image-${SEMAPHORE_GIT_SHA} /tmp/felixtest-typha.tar" - ../.semaphore/run-and-monitor ut.log make ut + - name: "Felix: k8sfv" + priority: + # This job is on the critical path so we increase its priority. + - value: 51 + when: "pull_request =~ '.+'" + execution_time_limit: + minutes: 15 + commands: - ../.semaphore/run-and-monitor k8sfv-typha.log make k8sfv-test JUST_A_MINUTE=true USE_TYPHA=true - ../.semaphore/run-and-monitor k8sfv-no-typha.log make k8sfv-test JUST_A_MINUTE=true USE_TYPHA=false - - name: Static checks + - name: "Felix: Static checks" + priority: + # This job is on the critical path so we increase its priority. + - value: 51 + when: "pull_request =~ '.+'" execution_time_limit: - minutes: 60 + minutes: 15 commands: - ../.semaphore/run-and-monitor static-checks.log make static-checks + epilogue: + always: + commands: + - 'test-results publish --name "${SEMAPHORE_JOB_NAME}" ./report/*.xml || true' - name: "Felix: multi-arch build" run: when: "${CHANGE_IN(felix)}" @@ -40,10 +69,8 @@ prologue: commands: - cd felix - - cache restore go-pkg-cache - - cache restore go-mod-cache jobs: - - name: Build binary + - name: "Felix: build multi-arch binaries" matrix: - env_var: ARCH values: @@ -65,10 +92,8 @@ prologue: commands: - cd felix - - cache restore go-pkg-cache - - cache restore go-mod-cache jobs: - - name: Build binary + - name: "Felix: build arm64 binaries" commands: - ../.semaphore/run-and-monitor build-arm64.log make build ARCH=arm64 - name: "Felix: Build Windows binaries" @@ -77,14 +102,17 @@ dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "felix" jobs: - - name: Build Windows binaries + - name: "Felix: Build Windows binaries" commands: - cd felix - make bin/calico-felix.exe fv/win-fv.exe -- name: "Felix: Windows FV capz" +- name: "Felix: Windows FV" run: - when: "false or ${CHANGE_IN(felix,process)}" + when: "false or ${CHANGE_IN(felix,non-go:/process/testing/aso,non-go:/process/testing/utils,non-go:/process/testing/winfv-felix)}" dependencies: ["Felix: Build Windows binaries"] task: secrets: @@ -98,170 +126,177 @@ - export AZURE_TENANT_ID=$AZ_TENANT_ID - export AZURE_CLIENT_ID=$AZ_SP_ID - export AZURE_CLIENT_SECRET=$AZ_SP_PASSWORD - - export AZURE_SUBSCRIPTION_ID_B64="$(echo -n "$AZ_SUBSCRIPTION_ID" | base64 | tr -d '\n')" - - export AZURE_TENANT_ID_B64="$(echo -n "$AZ_TENANT_ID" | base64 | tr -d '\n')" - - export AZURE_CLIENT_ID_B64="$(echo -n "$AZ_SP_ID" | base64 | tr -d '\n')" - - export AZURE_CLIENT_SECRET_B64="$(echo -n "$AZ_SP_PASSWORD" | base64 | tr -d '\n')" + - export REPORT_DIR=/home/semaphore/calico/process/testing/winfv-felix/report + - export LOGS_DIR=~/fv.log + - export RG_PREFIX=${USER}-win-felix-${SEMAPHORE_GIT_SHA:0:4}-pr${SEMAPHORE_GIT_PR_NUMBER} - cd felix epilogue: always: commands: - - artifact push job ${REPORT_DIR} --destination test-results || true + - artifact push job ${REPORT_DIR} --destination semaphore/test-results || true + - artifact push job ${LOGS_DIR} --destination semaphore/logs || true + - cd ~/calico/process/testing/aso && make dist-clean env_vars: - - name: FV_PROVISIONER - value: "capz" + - name: BUILD_CACHE_GROUP + value: "felix" - name: FV_TYPE value: "calico-felix" - name: SEMAPHORE_ARTIFACT_EXPIRY value: 2w - name: CONTAINERD_VERSION - value: 1.7.22 + value: 1.7.28 jobs: - - name: CAPZ - Windows FV + - name: "Felix: Windows FV" commands: - - ./.semaphore/run-win-fv -- name: "Felix: FV Tests" + - export AZURE_RESOURCE_GROUP=${RG_PREFIX}-rg + - echo AZURE_RESOURCE_GROUP=${AZURE_RESOURCE_GROUP} + - ../.semaphore/run-and-monitor win-fv-felix.log ~/calico/process/testing/winfv-felix/run-win-fv.sh +- name: "Felix: iptables tests on Ubuntu 22.04" run: - when: "${CHANGE_IN(felix,typha)}" + when: "${CHANGE_IN(felix,typha,non-go:/.semaphore/vms/)}" dependencies: - "Felix: Build" task: - agent: - machine: - type: f1-standard-4 - os_image: ubuntu2204 - prologue: - commands: - - cd felix - - cache restore go-pkg-cache - - cache restore go-mod-cache - - "cache restore bin-${SEMAPHORE_GIT_SHA}" - - "cache restore fv.test-${SEMAPHORE_GIT_SHA}" - - "cache restore felix-image-${SEMAPHORE_GIT_SHA}" - - "cache restore felixtest-typha-image-${SEMAPHORE_GIT_SHA}" - - |- - if [ -s /etc/docker/daemon.json ]; then - sudo sed -i '$d' /etc/docker/daemon.json && sudo sed -i '$s/$/,/' /etc/docker/daemon.json && sudo bash -c ' cat >> /etc/docker/daemon.json << EOF - "ipv6": true, - "fixed-cidr-v6": "2001:db8:1::/64" - } - EOF - ' ; else sudo bash -c ' cat > /etc/docker/daemon.json << EOF - { - "ipv6": true, - "fixed-cidr-v6": "2001:db8:1::/64" - } - EOF - ' ; fi - - sudo systemctl restart docker - # Load in the docker images pre-built by the build job. - - docker load -i /tmp/calico-felix-test.tar - - docker tag calico/felix-test:latest-amd64 felix-test:latest-amd64 - - rm /tmp/calico-felix-test.tar - - docker load -i /tmp/felixtest-typha.tar - - docker tag felix-test/typha:latest-amd64 typha:latest-amd64 - - rm /tmp/felixtest-typha.tar - # Pre-loading the IPIP module prevents a flake where the first felix to use IPIP loads the module and - # routing in that first felix container chooses different source IPs than the tests are expecting. - - sudo modprobe ipip jobs: - - name: FV Test matrix + - name: "Felix: iptables tests on Ubuntu 22.04" execution_time_limit: - minutes: 120 + minutes: 60 + env_vars: + - name: FELIX_TEST_GROUP + value: "22.04-ipt" commands: - - make check-wireguard - - ../.semaphore/run-and-monitor fv-${SEMAPHORE_JOB_INDEX}.log make fv-no-prereqs FV_BATCHES_TO_RUN="${SEMAPHORE_JOB_INDEX}" FV_NUM_BATCHES=${SEMAPHORE_JOB_COUNT} - parallelism: 3 - - name: nftables FV Test matrix + - source felix/.semaphore/fv-prologue + - export NUM_FV_BATCHES=16 + - .semaphore/vms/run-tests-on-vms ${VM_PREFIX} + epilogue: + always: + commands: + - source felix/.semaphore/fv-epilogue + secrets: + - name: google-service-account-for-gce +- name: "Felix: nftables tests on Ubuntu 22.04" + run: + when: "${CHANGE_IN(felix,typha,non-go:/.semaphore/vms/)}" + dependencies: + - "Felix: Build" + task: + jobs: + - name: "Felix: nftables tests on Ubuntu 22.04" execution_time_limit: - minutes: 120 + minutes: 60 env_vars: - - name: FELIX_FV_NFTABLES - value: "Enabled" + - name: FELIX_TEST_GROUP + value: "22.04-nft" commands: - - make check-wireguard - - ../.semaphore/run-and-monitor fv-${SEMAPHORE_JOB_INDEX}.log make fv-no-prereqs FV_BATCHES_TO_RUN="${SEMAPHORE_JOB_INDEX}" FV_NUM_BATCHES=${SEMAPHORE_JOB_COUNT} - parallelism: 3 + - source felix/.semaphore/fv-prologue + - export NUM_FV_BATCHES=16 + - .semaphore/vms/run-tests-on-vms ${VM_PREFIX} epilogue: always: commands: - - ./.semaphore/collect-artifacts - - ./.semaphore/publish-artifacts - - test-results publish /home/semaphore/calico/felix/report/fv_suite.xml --name "felix-fv-${SEMAPHORE_JOB_INDEX}" || true - - test-results publish /home/semaphore/calico/felix/report/fv_nft_suite.xml --name "felix-fv-nft-${SEMAPHORE_JOB_INDEX}" || true -- name: "Felix: BPF UT/FV tests on Ubuntu 24.04" + - source felix/.semaphore/fv-epilogue + secrets: + - name: google-service-account-for-gce +- name: "Felix: BPF tests on Ubuntu 24.04 (iptables)" run: - when: "${CHANGE_IN(felix,typha)}" + when: "${CHANGE_IN(felix,typha,non-go:/.semaphore/vms/)}" dependencies: - - Prerequisites + - "Felix: Build" task: - prologue: - commands: - - cd felix - - export GOOGLE_APPLICATION_CREDENTIALS=$HOME/secrets/secret.google-service-account-key.json - - export SHORT_WORKFLOW_ID=$(echo ${SEMAPHORE_WORKFLOW_ID} | sha256sum | cut -c -8) - - export ZONE=europe-west3-c - - export VM_PREFIX=sem-${SEMAPHORE_PROJECT_NAME}-${SHORT_WORKFLOW_ID}-felix-ipt- - - echo VM_PREFIX=${VM_PREFIX} - - export REPO_NAME=$(basename $(pwd)) - - export NUM_FV_BATCHES=8 - - export RUN_UT=true - - export FV_FOCUS=BPF-SAFE - - export IMAGE=ubuntu-2404-noble-amd64-v20250502a - - export UBUNTU_VERSION=noble - - export DOCKER_VERSION=5:27.5.1-1~ubuntu.24.04~noble - - mkdir artifacts - - ./.semaphore/create-test-vms ${VM_PREFIX} jobs: - - name: UT/FV tests on new kernel + - name: "Felix: BPF tests on Ubuntu 24.04" execution_time_limit: - minutes: 180 + minutes: 60 + env_vars: + - name: FELIX_TEST_GROUP + value: "bpf-24.04-ipt-with-ut" commands: - - ./.semaphore/run-tests-on-vms ${VM_PREFIX} + - source felix/.semaphore/fv-prologue + - export NUM_FV_BATCHES=60 + - .semaphore/vms/run-tests-on-vms ${VM_PREFIX} epilogue: always: commands: - - ./.semaphore/collect-artifacts-from-vms ${VM_PREFIX} - - ./.semaphore/publish-artifacts - - ./.semaphore/clean-up-vms ${VM_PREFIX} + - source felix/.semaphore/fv-epilogue secrets: - name: google-service-account-for-gce -- name: "Felix: BPF UT/FV tests on Ubuntu 22.04 (nftables)" +- name: "Felix: BPF tests on Ubuntu 22.04 (nftables)" run: - when: "${CHANGE_IN(felix,typha)}" + when: "${CHANGE_IN(felix,typha,non-go:/.semaphore/vms/)}" dependencies: - - Prerequisites + - "Felix: Build" task: - prologue: - commands: - - cd felix - - export GOOGLE_APPLICATION_CREDENTIALS=$HOME/secrets/secret.google-service-account-key.json - - export SHORT_WORKFLOW_ID=$(echo ${SEMAPHORE_WORKFLOW_ID} | sha256sum | cut -c -8) - - export ZONE=europe-west3-c - - export VM_PREFIX=sem-${SEMAPHORE_PROJECT_NAME}-${SHORT_WORKFLOW_ID}-felix-nft- - - echo VM_PREFIX=${VM_PREFIX} - - export REPO_NAME=$(basename $(pwd)) - - export NUM_FV_BATCHES=4 - - export RUN_UT=true - - export FV_FOCUS='_BPF_.*ct=true' - - mkdir artifacts - - ./.semaphore/create-test-vms ${VM_PREFIX} jobs: - - name: UT/FV tests on new kernel + - name: "Felix: BPF tests on Ubuntu 22.04 (nftables)" + execution_time_limit: + minutes: 60 env_vars: - - name: FELIX_FV_NFTABLES - value: "Enabled" + - name: FELIX_TEST_GROUP + value: "bpf-22.04-nft-with-ut" - name: FELIX_FV_BPFATTACHTYPE value: "tc" + commands: + - source felix/.semaphore/fv-prologue + # Limit to BPF conntrack tests to reduce overall runtime. + - export FV_FOCUS='_BPF_.*ct=true' + - export NUM_FV_BATCHES=10 + - .semaphore/vms/run-tests-on-vms ${VM_PREFIX} + epilogue: + always: + commands: + - source felix/.semaphore/fv-epilogue + secrets: + - name: google-service-account-for-gce +- name: "Felix: BPF program-loading check on 25.10 (nftables)" + run: + when: "${CHANGE_IN(felix,typha,non-go:/.semaphore/vms/)}" + dependencies: + - "Felix: Build" + task: + jobs: + - name: "Felix: BPF program-loading check on 25.10" execution_time_limit: - minutes: 180 + minutes: 30 + env_vars: + - name: FELIX_TEST_GROUP + value: "bpf-25.10-nft-no-fv-with-ut" + - name: FELIX_FV_BPFATTACHTYPE + value: "tc" + commands: + - source felix/.semaphore/fv-prologue + # Only run the UT that checks that the precompiled BPF binaries are loadable. + - export UT_FOCUS="TestPrecompiledBinariesAreLoadable" + - .semaphore/vms/run-tests-on-vms ${VM_PREFIX} + epilogue: + always: + commands: + - source felix/.semaphore/fv-epilogue + secrets: + - name: google-service-account-for-gce +- name: "Felix: BPF tests on Ubuntu 25.10 with jitharden=2 (nftables)" + run: + when: "${CHANGE_IN(felix,typha,non-go:/.semaphore/vms/)}" + dependencies: + - "Felix: Build" + task: + jobs: + - name: "Felix: BPF tests on Ubuntu 25.10 with jitharden=2" + execution_time_limit: + minutes: 60 + env_vars: + - name: FELIX_TEST_GROUP + value: "bpf-25.10-nft-with-ut-jitharden" + - name: FELIX_FV_BPFATTACHTYPE + value: "tc" commands: - - ./.semaphore/run-tests-on-vms ${VM_PREFIX} + - source felix/.semaphore/fv-prologue + # Limit to BPF TCP+conntrack tests to reduce overall runtime. + - export FV_FOCUS='_BPF_.*tcp.*ct=true' + - export NUM_FV_BATCHES=16 + - .semaphore/vms/run-tests-on-vms ${VM_PREFIX} epilogue: always: commands: - - ./.semaphore/collect-artifacts-from-vms ${VM_PREFIX} - - ./.semaphore/publish-artifacts - - ./.semaphore/clean-up-vms ${VM_PREFIX} + - source felix/.semaphore/fv-epilogue secrets: - name: google-service-account-for-gce diff --git a/.semaphore/semaphore.yml.d/blocks/20-goldmane.yml b/.semaphore/semaphore.yml.d/blocks/20-goldmane.yml index 13ba8662f6c..5f2d4fc2633 100644 --- a/.semaphore/semaphore.yml.d/blocks/20-goldmane.yml +++ b/.semaphore/semaphore.yml.d/blocks/20-goldmane.yml @@ -1,4 +1,4 @@ -- name: goldmane +- name: Goldmane run: when: "${CHANGE_IN(goldmane)}" execution_time_limit: @@ -6,6 +6,9 @@ dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "goldmane" prologue: commands: # The Makefile sometimes tries to rebuild the protobuf files on non-amd architectures @@ -14,10 +17,13 @@ - touch goldmane/proto/api.pb.go - cd goldmane jobs: - - name: make ci + - name: "Goldmane: CI" commands: - ../.semaphore/run-and-monitor make-ci.log make ci - - name: Build binary + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: "Goldmane: build multi-arch" matrix: - env_var: ARCH values: diff --git a/.semaphore/semaphore.yml.d/blocks/20-guardian.yml b/.semaphore/semaphore.yml.d/blocks/20-guardian.yml index 8fb0a804aa0..6cb23c2b47f 100644 --- a/.semaphore/semaphore.yml.d/blocks/20-guardian.yml +++ b/.semaphore/semaphore.yml.d/blocks/20-guardian.yml @@ -1,4 +1,4 @@ -- name: guardian +- name: Guardian run: when: "${CHANGE_IN(guardian)}" execution_time_limit: @@ -6,14 +6,20 @@ dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "guardian" prologue: commands: - cd guardian jobs: - - name: make ci + - name: "Guardian: CI" commands: - ../.semaphore/run-and-monitor make-ci.log make ci - - name: Build binary + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: "Guardian: build multi-arch" matrix: - env_var: ARCH values: diff --git a/.semaphore/semaphore.yml.d/blocks/20-kube-controllers.yml b/.semaphore/semaphore.yml.d/blocks/20-kube-controllers.yml index 9584a028151..1a903595136 100644 --- a/.semaphore/semaphore.yml.d/blocks/20-kube-controllers.yml +++ b/.semaphore/semaphore.yml.d/blocks/20-kube-controllers.yml @@ -4,10 +4,26 @@ dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "kube-controllers" prologue: commands: - cd kube-controllers jobs: - - name: "kube-controllers: tests" + - name: "kube-controllers: CI" commands: - ../.semaphore/run-and-monitor ci.log make ci + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + + # Run a subset of the kube-controllers CI tests that use the projectcalico.org/v3 API group for CRDs. + - name: "kube-controllers: CI (projectcalico.org/v3)" + commands: + - ../.semaphore/run-and-monitor ut.log make image ut + env_vars: + - name: CALICO_API_GROUP + value: "projectcalico.org/v3" + - name: STORE_BUILD_CACHE + value: "true" diff --git a/.semaphore/semaphore.yml.d/blocks/20-lib.yml b/.semaphore/semaphore.yml.d/blocks/20-lib.yml index 7e883aab4dc..14021007ac5 100644 --- a/.semaphore/semaphore.yml.d/blocks/20-lib.yml +++ b/.semaphore/semaphore.yml.d/blocks/20-lib.yml @@ -1,4 +1,4 @@ -- name: lib +- name: Libraries (lib) run: when: "${FORCE_RUN} or change_in(['/lib/'], {pipeline_file: 'ignore'})" execution_time_limit: @@ -10,6 +10,6 @@ commands: - cd lib jobs: - - name: make ci + - name: "Libraries: CI" commands: - ../.semaphore/run-and-monitor make-ci.log make ci diff --git a/.semaphore/semaphore.yml.d/blocks/20-libcalico-go.yml b/.semaphore/semaphore.yml.d/blocks/20-libcalico-go.yml index 3fc007e41bf..8b2c6f51a66 100644 --- a/.semaphore/semaphore.yml.d/blocks/20-libcalico-go.yml +++ b/.semaphore/semaphore.yml.d/blocks/20-libcalico-go.yml @@ -4,10 +4,28 @@ dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "libcalico-go" prologue: commands: - cd libcalico-go jobs: - - name: "libcalico-go: tests" + # Run the tests twice, once for each backing API version. + - name: "libcalico-go: CI (crd.projectcalico.org/v1)" commands: - ../.semaphore/run-and-monitor make-ci.log make ci + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + + - name: "libcalico-go: CI (projectcalico.org/v3)" + commands: + - ../.semaphore/run-and-monitor make-ci.log make ci + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: CALICO_API_GROUP + value: projectcalico.org/v3 + - name: GINKGO_SKIP + value: "etcdv3 backend" diff --git a/.semaphore/semaphore.yml.d/blocks/20-node.yml b/.semaphore/semaphore.yml.d/blocks/20-node.yml index bb869477aa3..03f017a1e9e 100644 --- a/.semaphore/semaphore.yml.d/blocks/20-node.yml +++ b/.semaphore/semaphore.yml.d/blocks/20-node.yml @@ -4,17 +4,27 @@ dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "node" agent: machine: type: f1-standard-4 - os_image: ubuntu2004 + os_image: ubuntu2204 prologue: commands: - cd node jobs: - name: "Node: CI" + priority: + # This job is on the critical path so we increase its priority. + - value: 51 + when: "pull_request =~ '.+'" commands: - ../.semaphore/run-and-monitor ci.log make ci + env_vars: + - name: STORE_BUILD_CACHE + value: "true" - name: "Node: multi-arch build" run: when: "${CHANGE_IN(node)}" @@ -25,7 +35,7 @@ commands: - cd node jobs: - - name: Build image + - name: "Node: build multi-arch images" matrix: - env_var: ARCH values: @@ -33,10 +43,10 @@ - s390x commands: - ../.semaphore/run-and-monitor image-$ARCH.log make image ARCH=$ARCH - - name: Build Windows archive + - name: "Node: build Windows archive" commands: - ../.semaphore/run-and-monitor build-windows-archive.log make build-windows-archive - - name: Build Windows image + - name: "Node: build Windows image" commands: - ../.semaphore/run-and-monitor build-windows-image.log make image-windows - name: "Node: Build - native arm64 runner" @@ -52,39 +62,48 @@ commands: - cd node jobs: - - name: Build image + - name: "Node: build arm64 image" commands: - ../.semaphore/run-and-monitor build-arm64.log make image ARCH=arm64 - name: "Node: kind-cluster tests" run: # KinD tests also build and run all the other images. - when: "${CHANGE_IN(node,typha,apiserver,cni-plugin,pod2daemon,calicoctl,kube-controllers,goldmane,whisker,whisker-backend)}" + when: "${CHANGE_IN(node,typha,apiserver,cni-plugin,pod2daemon,calicoctl,kube-controllers,goldmane,whisker,whisker-backend,non-go:/.semaphore/vms/)}" dependencies: - - Prerequisites + - "Prerequisites" task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "node" + - name: CI_GROUP_LABEL + value: "node" + - name: CI_JOB_LABEL + value: "kind-cluster-tests" prologue: commands: - - cd node + # Set up access to GCP buckets. - export GOOGLE_APPLICATION_CREDENTIALS=$HOME/secrets/secret.google-service-account-key.json + - gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS + - gcloud config set project unique-caldron-775 + - cd node - export SHORT_WORKFLOW_ID=$(echo ${SEMAPHORE_WORKFLOW_ID} | sha256sum | cut -c -8) - export ZONE=europe-west3-c - export VM_PREFIX=sem-${SEMAPHORE_PROJECT_NAME}-${SHORT_WORKFLOW_ID}-kind- - echo VM_PREFIX=${VM_PREFIX} - - export REPO_NAME=$(basename $(pwd)) + - export COMPONENT=node - export VM_DISK_SIZE=80GB - mkdir artifacts - - ../.semaphore/vms/create-test-vms ${ZONE} ${VM_PREFIX} + - ./.semaphore/cache-test-artifacts jobs: - name: "Node: kind-cluster tests" execution_time_limit: minutes: 120 commands: - - ../.semaphore/vms/run-tests-on-vms ${ZONE} ${VM_PREFIX} + - ../.semaphore/vms/run-tests-on-vms ${VM_PREFIX} epilogue: always: commands: - - ../.semaphore/vms/publish-artifacts - - ../.semaphore/vms/clean-up-vms ${ZONE} ${VM_PREFIX} - - test-results publish ./report/*.xml --name "node-kind-tests" || true + - '../.semaphore/vms/publish-reports "Node: KinD STs"' + - ../.semaphore/vms/clean-up-vms ${VM_PREFIX} secrets: - name: google-service-account-for-gce diff --git a/.semaphore/semaphore.yml.d/blocks/20-pod2daemon.yml b/.semaphore/semaphore.yml.d/blocks/20-pod2daemon.yml index afb47531efb..cce870e8d29 100644 --- a/.semaphore/semaphore.yml.d/blocks/20-pod2daemon.yml +++ b/.semaphore/semaphore.yml.d/blocks/20-pod2daemon.yml @@ -8,7 +8,7 @@ commands: - cd pod2daemon jobs: - - name: pod2daemon tests + - name: "pod2daemon: CI" commands: - ../.semaphore/run-and-monitor ci.log make ci - - test-results publish ./report/*.xml --name "pod2daemon-ut-tests" || true + - 'test-results publish ./report/*.xml --name "pod2daemon: UT" || true' diff --git a/.semaphore/semaphore.yml.d/blocks/20-tools.yml b/.semaphore/semaphore.yml.d/blocks/20-tools.yml index cb297ea743d..bf265702f7e 100644 --- a/.semaphore/semaphore.yml.d/blocks/20-tools.yml +++ b/.semaphore/semaphore.yml.d/blocks/20-tools.yml @@ -8,6 +8,6 @@ commands: - cd hack jobs: - - name: "Tools (hack directory)" + - name: "Tools (hack directory): CI" commands: - ../.semaphore/run-and-monitor make-ci.log make ci diff --git a/.semaphore/semaphore.yml.d/blocks/20-typha.yml b/.semaphore/semaphore.yml.d/blocks/20-typha.yml index 49106db6eb2..3e377a0598b 100644 --- a/.semaphore/semaphore.yml.d/blocks/20-typha.yml +++ b/.semaphore/semaphore.yml.d/blocks/20-typha.yml @@ -4,15 +4,23 @@ dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "typha" prologue: commands: - cd typha jobs: - name: "Typha: UT and FV tests" commands: - - ../.semaphore/run-and-monitor make-ci.log make ci EXCEPT=k8sfv-test - - name: "Typha: UT and FV tests on UBI-minimal" + - export TEST_SUITE_PREFIX="calico/base" + - ../.semaphore/run-and-monitor make-ci.log make ci + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: "Typha: FV tests on UBI-minimal" commands: + - 'export TEST_SUITE_PREFIX="UBI-minimal"' - ../.semaphore/run-and-monitor make-fv-ubi.log make clean image fv env_vars: - name: USE_UBI_AS_CALICO_BASE @@ -20,16 +28,4 @@ epilogue: always: commands: - - | - for f in /home/semaphore/calico/typha/report/*; do - NAME=$(basename $f) - test-results compile --name typha-$NAME $f $NAME.json || true - done - for f in /home/semaphore/calico/typha/pkg/report/*; do - NAME=$(basename $f) - test-results compile --name typha-$NAME $f $NAME.json || true - done - test-results combine *.xml.json report.json || true - artifact push job report.json -d test-results/junit.json || true - artifact push workflow report.json -d test-results/${SEMAPHORE_PIPELINE_ID}/${SEMAPHORE_JOB_ID}.json || true - - test-results publish /home/semaphore/calico/felix/report/k8sfv_suite.xml --name "typha-k8sfv" || true + - 'test-results publish --ignore-missing ./report/*.xml ../felix/report/*.xml --suite-prefix="$TEST_SUITE_PREFIX" --name "Typha" || true' diff --git a/.semaphore/semaphore.yml.d/blocks/20-webhooks.yml b/.semaphore/semaphore.yml.d/blocks/20-webhooks.yml new file mode 100644 index 00000000000..450bd7c0bbf --- /dev/null +++ b/.semaphore/semaphore.yml.d/blocks/20-webhooks.yml @@ -0,0 +1,19 @@ +- name: webhooks + run: + when: "${CHANGE_IN(webhooks,api,apiserver)}" + dependencies: + - Prerequisites + task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "webhooks" + prologue: + commands: + - cd webhooks + jobs: + - name: "webhooks: CI" + commands: + - ../.semaphore/run-and-monitor make-ci.log make ci + env_vars: + - name: STORE_BUILD_CACHE + value: "true" diff --git a/.semaphore/semaphore.yml.d/blocks/20-whisker-backend.yml b/.semaphore/semaphore.yml.d/blocks/20-whisker-backend.yml index a5f310bbf02..dd9d8c8c17c 100644 --- a/.semaphore/semaphore.yml.d/blocks/20-whisker-backend.yml +++ b/.semaphore/semaphore.yml.d/blocks/20-whisker-backend.yml @@ -1,4 +1,4 @@ -- name: whisker-backend +- name: Whisker backend run: when: "${CHANGE_IN(whisker-backend)}" execution_time_limit: @@ -10,10 +10,10 @@ commands: - cd whisker-backend jobs: - - name: make ci + - name: "Whisker backend: CI" commands: - ../.semaphore/run-and-monitor make-ci.log make ci - - name: Build binary + - name: "Whisker backend: build multi-arch" matrix: - env_var: ARCH values: diff --git a/.semaphore/semaphore.yml.d/blocks/20-whisker.yml b/.semaphore/semaphore.yml.d/blocks/20-whisker.yml index e1e739ebdb1..e206493863d 100644 --- a/.semaphore/semaphore.yml.d/blocks/20-whisker.yml +++ b/.semaphore/semaphore.yml.d/blocks/20-whisker.yml @@ -1,4 +1,4 @@ -- name: whisker +- name: Whisker run: when: "${FORCE_RUN} or change_in(['/metadata.mk', '/lib.Makefile', '/whisker/'], {pipeline_file: 'ignore', exclude: ['/**/.gitignore', '/**/README.md', '/**/LICENSE']})" execution_time_limit: @@ -6,14 +6,20 @@ dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "whisker" prologue: commands: - cd whisker jobs: - - name: make ci + - name: "Whisker: CI" commands: - ../.semaphore/run-and-monitor make-ci.log make ci - - name: Build binary + env_vars: + - name: STORE_BUILD_CACHE + value: "true" + - name: "Whisker: build multi-arch images" matrix: - env_var: ARCH values: diff --git a/.semaphore/semaphore.yml.d/blocks/30-key-cert-provisioner.yml b/.semaphore/semaphore.yml.d/blocks/30-key-cert-provisioner.yml index 2917ad234cf..4f14131ba53 100644 --- a/.semaphore/semaphore.yml.d/blocks/30-key-cert-provisioner.yml +++ b/.semaphore/semaphore.yml.d/blocks/30-key-cert-provisioner.yml @@ -4,10 +4,16 @@ dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "key-cert-provisioner" prologue: commands: - cd key-cert-provisioner jobs: - - name: key-cert-provisioner tests + - name: "key-cert-provisioner: CI" commands: - ../.semaphore/run-and-monitor ci.log make ci + env_vars: + - name: STORE_BUILD_CACHE + value: "true" diff --git a/.semaphore/semaphore.yml.d/blocks/40-openstack.yml b/.semaphore/semaphore.yml.d/blocks/40-openstack.yml index 2a402b91a81..8e24e5f9d94 100644 --- a/.semaphore/semaphore.yml.d/blocks/40-openstack.yml +++ b/.semaphore/semaphore.yml.d/blocks/40-openstack.yml @@ -1,54 +1,6 @@ -- name: "OpenStack integration (Yoga)" - run: - when: "${FORCE_RUN} or change_in(['/metadata.mk', '/lib.Makefile', '/networking-calico/'], {pipeline_file: 'ignore'})" - dependencies: - - Prerequisites - task: - agent: - machine: - type: f1-standard-2 - os_image: ubuntu2004 - prologue: - commands: - - cd networking-calico - jobs: - - name: "Unit and FV tests (tox) on Yoga" - commands: - - ../.semaphore/run-and-monitor tox.log make tox-yoga - - name: "Mainline ST (DevStack + Tempest) on Yoga" - commands: - # For some reason python3-wrapt is pre-installed on a Semaphore ubuntu2004 node, but with - # a version (1.11.2) that is different from the version that OpenStack needs (1.13.3), and - # this was causing the DevStack setup to fail, because pip doesn't know how to uninstall - # or replace the existing version. Happily we do know that, so let's do it upfront here. - - sudo apt-get remove -y python3-wrapt || true - # Install all the packages that would trigger an initramfs update using a workaround - # for limited /boot partition space in the ubuntu2004 image - - sudo apt update - - sudo rsync -av /boot/ /boot2/ - - sudo mount --bind /boot2 /boot - - sudo apt install -y cryptsetup lsscsi open-iscsi thin-provisioning-tools - - sudo umount /boot - - sudo rsync -av /boot2/ /boot/ --exclude "*.new" --exclude "*.dpkg-bak" --delete --inplace - - sudo rm -rf /boot2/ - # Set NC_PLUGIN_REPO and NC_PLUGIN_REF so that DevStack will use the networking-calico - # code from the current PR or branch. The following checkout is to make sure of having a - # local ref that we can specify. - - git checkout -b devstack-test - - export NC_PLUGIN_REPO=$(dirname $(pwd)) - - export NC_PLUGIN_REF=$(git rev-parse --abbrev-ref HEAD) - - sudo git config --system --add safe.directory ${NC_PLUGIN_REPO}/.git - - TEMPEST=true DEVSTACK_BRANCH=unmaintained/yoga ./devstack/bootstrap.sh - epilogue: - on_fail: - commands: - - mkdir logs - - sudo journalctl > logs/journalctl.txt - - artifact push job logs - - name: "OpenStack integration (Caracal)" run: - when: "${FORCE_RUN} or change_in(['/networking-calico/'], {pipeline_file: 'ignore'})" + when: "${FORCE_RUN} or change_in(['/metadata.mk', '/lib.Makefile', '/networking-calico/', '/.semaphore/semaphore.yml.d/blocks/40-openstack.yml'], {pipeline_file: 'ignore'})" dependencies: - Prerequisites task: @@ -60,10 +12,10 @@ commands: - cd networking-calico jobs: - - name: "Unit and FV tests (tox) on Caracal" + - name: "OpenStack: Unit and FV tests (tox) on Caracal" commands: - ../.semaphore/run-and-monitor tox.log make tox-caracal - - name: "Mainline ST (DevStack + Tempest) on Caracal" + - name: "OpenStack: Mainline ST (DevStack + Tempest) on Caracal" commands: # For some reason python3-wrapt is pre-installed on a Semaphore ubuntu2004 node, but with # a version (1.11.2) that is different from the version that OpenStack needs (1.13.3), and @@ -77,7 +29,10 @@ - export NC_PLUGIN_REPO=$(dirname $(pwd)) - export NC_PLUGIN_REF=$(git rev-parse --abbrev-ref HEAD) - sudo git config --system --add safe.directory ${NC_PLUGIN_REPO}/.git - - TEMPEST=true DEVSTACK_BRANCH=stable/2024.1 ./devstack/bootstrap.sh + # The BIRD config in our DevStack setup is thoroughly broken and generates loads of errors + # in the log. But we don't need it for a single node setup anyway, so let's disable it. + - export CALICO_BGP_MODE=none + - TEMPEST=true OPENSTACK_RELEASE=caracal ./devstack/bootstrap.sh epilogue: always: commands: diff --git a/.semaphore/semaphore.yml.d/blocks/60-mock-node.yml b/.semaphore/semaphore.yml.d/blocks/60-mock-node.yml index 79b5a9957fc..82c794a5b4f 100644 --- a/.semaphore/semaphore.yml.d/blocks/60-mock-node.yml +++ b/.semaphore/semaphore.yml.d/blocks/60-mock-node.yml @@ -4,10 +4,16 @@ dependencies: - Prerequisites task: + env_vars: + - name: BUILD_CACHE_GROUP + value: "mock-node" prologue: commands: - cd test-tools/mocknode jobs: - - name: Mock node + - name: "Mock node: CI" commands: - ../../.semaphore/run-and-monitor make-ci.log make ci + env_vars: + - name: STORE_BUILD_CACHE + value: "true" diff --git a/.semaphore/semaphore.yml.d/blocks/90-release.yml b/.semaphore/semaphore.yml.d/blocks/90-release.yml index cff2af51fc9..ec72dc70cfc 100644 --- a/.semaphore/semaphore.yml.d/blocks/90-release.yml +++ b/.semaphore/semaphore.yml.d/blocks/90-release.yml @@ -1,4 +1,4 @@ -- name: release tooling +- name: Release tooling run: when: "${CHANGE_IN(release)}" execution_time_limit: @@ -10,11 +10,11 @@ commands: - cd release jobs: - - name: ci + - name: "Release tooling: CI" commands: - ../.semaphore/run-and-monitor release-ci.log make ci - - test-results publish --name "release-tool-ut-tests" ./report/*.xml || true - - name: build binary + - 'test-results publish --name "Release tool: UT" ./report/*.xml || true' + - name: "Release tooling: build" commands: - ../.semaphore/run-and-monitor release-build.log make build - cache store release-${SEMAPHORE_GIT_SHA} bin diff --git a/.semaphore/vms/clean-up-vms b/.semaphore/vms/clean-up-vms index 958a9c14ce5..f94872eca14 100755 --- a/.semaphore/vms/clean-up-vms +++ b/.semaphore/vms/clean-up-vms @@ -16,8 +16,12 @@ set -e set -x -zone=$1 -vm_prefix=$2 +vm_prefix=$1 +if [ -z "$vm_prefix" ]; then + echo "Usage: $0 " + exit 1 +fi + project=${GCP_PROJECT:-unique-caldron-775} gcp_secret_key=${GCP_SECRET_KEY:-$HOME/secrets/secret.google-service-account-key.json} @@ -27,7 +31,7 @@ gcloud auth activate-service-account --key-file=$gcp_secret_key while true; do gcloud --quiet compute instances list \ "--filter=name~'${vm_prefix}.*'" \ - --zones=${zone} \ + --zones=${ZONE} \ --format='table[no-heading](name)' > instance-list instances="$(cat instance-list)" if [ "${instances}" = "" ]; then @@ -35,6 +39,6 @@ while true; do break fi echo "Instances to delete: $instances" - gcloud --quiet compute instances delete ${instances} --zone=${ZONE} + gcloud --quiet beta compute instances delete ${instances} --zone=${ZONE} --no-graceful-shutdown sleep 1 -done +done diff --git a/.semaphore/vms/configure-test-vm b/.semaphore/vms/configure-test-vm new file mode 100755 index 00000000000..be631fabf9e --- /dev/null +++ b/.semaphore/vms/configure-test-vm @@ -0,0 +1,94 @@ +#!/usr/bin/env bash + +# Copyright (c) 2019-2026 Tigera, Inc. All rights reserved. +# +# 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. + +set -e +set -x + +my_dir="$(dirname "$0")" +vm_name=$1 +zone=${ZONE:-europe-west3-c} +project=${GCP_PROJECT:-unique-caldron-775} + +vm_pub_key="" +for attempt in $(seq 1 20); do + echo "Getting VM ssh key (attempt $attempt)..." + vm_pub_key=$(gcloud compute instances get-guest-attributes "${vm_name}" --zone="${zone}" --query-path="hostkeys/ssh-ed25519" --format='value(value)') || { + echo "Failed to get VM ssh key, retrying..." + sleep 1 + continue + } + echo "VM SSH host key: $vm_pub_key" + if [[ -z "$vm_pub_key" ]]; then + echo "Empty key, retrying..." + sleep 1 + continue + fi + + break +done + +if [[ -z "$vm_pub_key" ]]; then + echo "Failed to get VM ssh key after multiple attempts, exiting." + exit 22 +fi + +vm_ip="$(env VM_NAME="$vm_name" ZONE="$zone" "$my_dir/vm-ip")" +echo "$vm_ip ssh-ed25519 $vm_pub_key" >> ~/.ssh/known_hosts + +ssh_cmd=( env "VM_NAME=$vm_name" "ZONE=$zone" "$my_dir/on-test-vm" ) + +startup_success=false +for ssh_try in $(seq 1 100); do + echo "Checking startup script completion: $ssh_try" + if "${ssh_cmd[@]}" test -e /var/run/startup-script-complete; then + echo "Startup script completed" + startup_success=true + break + fi + sleep $(( 1 + (RANDOM % 10) )) + + # The ssh key occasionally gets clobbered by ssh rewriting the file. Check + # and re-add if necessary. + if ! grep -F -q "$vm_ip ssh-ed25519 $vm_pub_key" ~/.ssh/known_hosts; then + echo "Server's ssh key went missing; re-adding it to known_hosts" + echo "$vm_ip ssh-ed25519 $vm_pub_key" >> ~/.ssh/known_hosts + fi +done + +if [ "$startup_success" = false ]; then + echo "VM startup script did not complete in time, exiting." + echo "Fetching serial console output for debugging:" + gcloud compute --project="$project" instances get-serial-port-output "$vm_name" --zone="$zone" --port=1 + exit 23 +fi + +set +x +echo "$DOCKERHUB_PASSWORD" | ssh "ubuntu@${vm_ip}" -- docker login --username "$DOCKERHUB_USERNAME" --password-stdin +scp -r -C "$HOME/secrets" "ubuntu@${vm_ip}:/home/ubuntu/secrets" +set -x + +"${ssh_cmd[@]}" "gcloud config set project unique-caldron-775 && \ + gcloud storage cp '${GCS_WORKFLOW_DIR}/${COMPONENT}/fv-artifacts/*' /tmp && \ + tar -xzf /tmp/working-copy.tgz && \ + ${CALICO_DIR_NAME}/${COMPONENT}/.semaphore/load-test-artifacts" + +if [ "$ENABLE_JIT_HARDENING" = "true" ]; then + echo "Enabling BPF JIT hardening on test VM" + "${ssh_cmd[@]}" sudo sysctl -w net.core.bpf_jit_harden=2 +fi + +# Login using the VM's service account. +"${ssh_cmd[@]}" 'gcloud auth configure-docker gcr.io --quiet' diff --git a/.semaphore/vms/create-test-vm b/.semaphore/vms/create-test-vm deleted file mode 100755 index 3a5bab9f66a..00000000000 --- a/.semaphore/vms/create-test-vm +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) 2023 Tigera, Inc. All rights reserved. -# -# 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. - -set -e -set -x - -zone=$1 -vm_name=$2 -disk_size=$3 -project=${GCP_PROJECT:-unique-caldron-775} -gcp_secret_key=${GCP_SECRET_KEY:-$HOME/secrets/secret.google-service-account-key.json} - -gcloud config set project $project -gcloud auth activate-service-account --key-file=$gcp_secret_key - -function create-vm() { - gcloud --quiet compute instances create "${vm_name}" \ - --zone=${zone} \ - --machine-type=n4-standard-4 \ - --image=ubuntu-2004-focal-v20250313 \ - --image-project=ubuntu-os-cloud \ - --boot-disk-size=$disk_size \ - --boot-disk-type=hyperdisk-balanced && \ - ssh_cmd="gcloud --quiet compute ssh --zone=${zone} ubuntu@${vm_name}" - for ssh_try in $(seq 1 10); do - echo "Trying to SSH in: $ssh_try" - ${ssh_cmd} -- echo "Success" && break - sleep 1 - done && \ - ${ssh_cmd} -- sudo apt install apt-transport-https ca-certificates curl software-properties-common && \ - ${ssh_cmd} -- "curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --yes --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg" && \ - ${ssh_cmd} -- "echo 'deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu focal stable' | sudo tee /etc/apt/sources.list.d/docker.list" && \ - ${ssh_cmd} -- sudo apt update -y && \ - ${ssh_cmd} -- sudo apt install -y --no-install-recommends git docker-ce=5:28.1.1-1~ubuntu.20.04~focal docker-ce-cli=5:28.1.1-1~ubuntu.20.04~focal docker-buildx-plugin containerd.io make iproute2 wireguard && \ - ${ssh_cmd} -- sudo usermod -a -G docker ubuntu && \ - ${ssh_cmd} -- sudo modprobe ipip && \ - ${ssh_cmd} -- 'if [ -s /etc/docker/daemon.json ] ; then cat /etc/docker/daemon.json | sed "\$d" | sed "\$s/\$/,/" > /tmp/daemon.json ; else echo -en {\\n > /tmp/daemon.json ; fi' && \ - ${ssh_cmd} -- 'cat >> /tmp/daemon.json << EOF - "ipv6": true, - "fixed-cidr-v6": "2001:db8:1::/64" -} -EOF' && \ - ${ssh_cmd} -- sudo mv /tmp/daemon.json /etc/docker/daemon.json && \ - ${ssh_cmd} -- sudo systemctl restart docker && \ - set +x && \ - echo "$DOCKERHUB_PASSWORD" | gcloud --quiet compute ssh --zone=${zone} "ubuntu@${vm_name}" -- docker login --username "$DOCKERHUB_USERNAME" --password-stdin && \ - set -x && \ - gcloud --quiet compute scp --zone=${zone} --recurse --compress "$(dirname $(pwd))" "ubuntu@${vm_name}:/home/ubuntu/calico" && \ - gcloud --quiet compute scp --zone=${zone} --recurse --compress "$HOME/secrets" "ubuntu@${vm_name}:/home/ubuntu/secrets" -} - -function delete-vm() { - gcloud --quiet compute instances delete "${vm_name}" --zone=${zone} -} - -for attempt in $(seq 1 5); do - echo "Trying to create text VM, attempt ${attempt}" - if create-vm; then - echo "Success!" - exit 0 - else - echo "Failed to create VM. Tearing it down." - delete-vm || true - fi -done - -echo "Out of retries" -exit 1 diff --git a/.semaphore/vms/create-test-vms b/.semaphore/vms/create-test-vms deleted file mode 100755 index b78440bc843..00000000000 --- a/.semaphore/vms/create-test-vms +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) 2023 Tigera, Inc. All rights reserved. -# -# 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. - -set -e - -source ../.semaphore/vms/vm-tests-common.sh - -zone=$1 -vm_name_prefix=$2 -disk_size=${VM_DISK_SIZE:-20GB} -my_dir="$(dirname $0)" -repo_dir="." -artifacts_dir="$repo_dir/artifacts" - -pids=() -for batch in "${batches[@]}"; do - vm_name="$vm_name_prefix$batch" - log_file="$artifacts_dir/create-vm-$batch.log" - echo "Creating test VM $vm_name in background. Redirecting log to $log_file." - "${my_dir}/create-test-vm" "$zone" "$vm_name" "$disk_size" >& "$log_file" & - pids+=( $! ) - sleep 1 -done - -for pid in "${pids[@]}"; do - echo "Waiting for create-test-vm process PID=$pid to finish" - if ! wait "$pid"; then - echo "Creating one of the test VMs failed, exiting. Logs should be attached as artifacts." - exit 1 - fi -done - -echo "All test VMs started." diff --git a/.semaphore/vms/on-test-vm b/.semaphore/vms/on-test-vm index a6f65c06eb3..aee2e25e175 100755 --- a/.semaphore/vms/on-test-vm +++ b/.semaphore/vms/on-test-vm @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Copyright (c) 2023 Tigera, Inc. All rights reserved. +# Copyright (c) 2023-2026 Tigera, Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,9 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -zone=$1 -vm_name=$2 -shift -shift +set -e -exec gcloud --quiet compute ssh "--zone=${zone}" "ubuntu@${vm_name}" -- -A -o ServerAliveInterval=10 "$@" +my_dir="$(dirname "$0")" +vm_ip="$("$my_dir/vm-ip")" +source "$my_dir/ssh-options" + +exec ssh "ubuntu@${vm_ip}" -n -A "${SSH_OPTIONS[@]}" "$@" diff --git a/.semaphore/vms/publish-reports b/.semaphore/vms/publish-reports new file mode 100755 index 00000000000..db85fc2a0d3 --- /dev/null +++ b/.semaphore/vms/publish-reports @@ -0,0 +1,62 @@ +#!/bin/bash + +# Copyright (c) 2025 Tigera, Inc. All rights reserved. +# +# 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. + +# This script publishes xunit test reports that were retrieved from the remote +# VMs and copied to the artifacts directory. It expects the COMPONENT env var +# to be set and it takes one argument, which controls the name of the pushed +# report. + +set -e +set -x + +job_tag="$1" + +my_dir="$(dirname $0)" +repo_dir="$my_dir/../.." +artifacts_dir="${repo_dir}/artifacts" + +echo "Publishing any reports from the artifacts directory." + +cd "$artifacts_dir" + +if [ "$COMPONENT" = "felix" ]; then + # The UTs get lost amongst the FVs, so split them into separate reports. + mapfile -t report_names < <(find . -name '*.xml' | grep '_fv_') + echo "FV reports:" "${report_names[@]}" + json_files=() + if [ ${#report_names[@]} -ne 0 ]; then + # No suite prefix for the FVs, they already generate unique names. + test-results compile --name "Felix: FV" "${report_names[@]}" fv_report.json + json_files+=(fv_report.json) + fi + mapfile -t report_names < <(find . -name '*.xml' | grep -v '_fv_') + echo "UT reports:" "${report_names[@]}" + if [ ${#report_names[@]} -ne 0 ]; then + # Suite prefix ensures that names don't clash when the reports get combined + # into the workflow report. Without it, tests clobber each other and don't + # all show up. + test-results compile --suite-prefix "$job_tag" --name "Felix: UT" "${report_names[@]}" ut_report.json + json_files+=(ut_report.json) + fi + if [ ${#json_files[@]} -ne 0 ]; then + # Simulate test-results publish. + test-results combine "${json_files[@]}" report.json + artifact push job report.json -d test-results/junit.json || true + artifact push workflow report.json -d test-results/${SEMAPHORE_PIPELINE_ID}/${SEMAPHORE_JOB_ID}.json || true + fi +else + find . -name '*.xml' -print0 | xargs -r -0 test-results publish --name "$job_tag" +fi \ No newline at end of file diff --git a/node/tests/st/utils/network.py b/.semaphore/vms/run-on-vms old mode 100644 new mode 100755 similarity index 50% rename from node/tests/st/utils/network.py rename to .semaphore/vms/run-on-vms index e2d559a3ca3..89400e0e9ba --- a/node/tests/st/utils/network.py +++ b/.semaphore/vms/run-on-vms @@ -1,4 +1,5 @@ -# Copyright (c) 2015-2016 Tigera, Inc. All rights reserved. +#!/usr/bin/env bash +# Copyright (c) 2025 Tigera, Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,20 +12,26 @@ # 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. -import logging -import os -logger = logging.getLogger(__name__) +set -e +my_dir="$(dirname "$0")" +repo_dir="$my_dir/../.." -class DummyNetwork(object): - def __init__(self, name): - self.name = name - self.network = name - self.deleted = False - def delete(self, host=None): - pass - def disconnect(self, host, container): - pass - def __str__(self): - return self.name +# COMPONENT might be node or felix; give shellcheck something to work with. +# shellcheck source=../../felix/.semaphore/batches.sh +source "${repo_dir}/${COMPONENT}/.semaphore/batches.sh" + +vm_name_prefix=$1 +shift 1 + +echo +echo "===== Starting tests =====" +echo + +for batch in "${batches[@]}"; do + vm_name="$vm_name_prefix$batch" + VM_NAME="$vm_name" "$my_dir/on-test-vm" "$@" +done + +echo "===== Done =====" diff --git a/.semaphore/vms/run-tests-on-vms b/.semaphore/vms/run-tests-on-vms index 64c0c453edc..ef72e14ce90 100755 --- a/.semaphore/vms/run-tests-on-vms +++ b/.semaphore/vms/run-tests-on-vms @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Copyright (c) 2023 Tigera, Inc. All rights reserved. +# Copyright (c) 2023-2026 Tigera, Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,75 +15,225 @@ set -e -source ../.semaphore/vms/vm-tests-common.sh +# Enable job control so that background processes get their own process groups. +set -m -zone=$1 -vm_name_prefix=$2 my_dir="$(dirname $0)" -repo_dir="." +repo_dir="$my_dir/../.." +vm_name_prefix=$1 artifacts_dir="$repo_dir/artifacts" +zone=${ZONE:-europe-west3-c} +: "${VM_MACHINE_TYPE:=n4-standard-4}" +: "${IMAGE_FAMILY:=ubuntu-minimal-2204-lts}" +: "${MAX_RUN_DURATION:=4h}" +disk_size=${VM_DISK_SIZE:-20GB} -echo -echo "===== Starting tests =====" -echo +source "$my_dir/ssh-options" + +# shellcheck source=../../felix/.semaphore/batches.sh +source "$repo_dir/$COMPONENT/.semaphore/batches.sh" -pids=() +###### Create the test VMs ###### + +names="" +for batch in "${batches[@]}"; do + vm_name="$vm_name_prefix$batch" + if [ -n "$names" ]; then + names="$names,$vm_name" + else + names="$vm_name" + fi +done + +# Labels for the GCP instances. These can only contain alphanumerics, +# dashes, underscores (so we can't just default to the semaphore job name). +# Sanitize the branch name to be compatible with GCP label requirements. +branch_label="${SEMAPHORE_GIT_BRANCH:-unknown}" +branch_label="$(echo "$branch_label" | tr '[:upper:]' '[:lower:]')" +branch_label="${branch_label//[^a-z0-9_-]/-}" +labels="ci-runner=true" +labels+=",ci-job-type=${CI_JOB_TYPE_LABEL:-${SEMAPHORE_GIT_REF_TYPE:-unknown}}" +labels+=",ci-group=${CI_GROUP_LABEL:-unknown}" +labels+=",ci-job=${CI_JOB_LABEL:-unknown}" +labels+=",ci-is-rerun=${SEMAPHORE_PIPELINE_RERUN:-false}" +labels+=",ci-branch=${branch_label}" +labels+=",ci-project=${CALICO_DIR_NAME}" +if [ -n "${SEMAPHORE_GIT_PR_NUMBER}" ]; then + labels+=",ci-pr-number=${SEMAPHORE_GIT_PR_NUMBER}" +fi +if [ -n "${SEMAPHORE_WORKFLOW_ID}" ]; then + labels+=",ci-workflow-id=${SEMAPHORE_WORKFLOW_ID}" +fi +if [ -n "${SEMAPHORE_JOB_ID}" ]; then + labels+=",ci-job-id=${SEMAPHORE_JOB_ID}" +fi +if [ "${SEMAPHORE_WORKFLOW_TRIGGERED_BY_SCHEDULE}" = "true" ]; then + labels+=",ci-scheduled=true" +else + labels+=",ci-scheduled=false" +fi + +# Do a bulk create; this is faster and it saves API quota. +echo "Creating test VMs in bulk..." +gcloud --quiet compute instances bulk create \ + --service-account="semaphore-v2-gcr@unique-caldron-775.iam.gserviceaccount.com" \ + --scopes="https://www.googleapis.com/auth/cloud-platform" \ + --predefined-names="$names" \ + --zone=${zone} \ + --machine-type=${VM_MACHINE_TYPE} \ + --image-family=${IMAGE_FAMILY} \ + --image-project=ubuntu-os-cloud \ + --boot-disk-size=$disk_size \ + --boot-disk-type=hyperdisk-balanced \ + --max-run-duration="${MAX_RUN_DURATION}" \ + --instance-termination-action=DELETE \ + --labels="${labels}" \ + --metadata-from-file startup-script="$my_dir/vm-bootstrap.sh" \ + --metadata block-project-ssh-keys=TRUE,ssh-keys="ubuntu:$(ssh-keygen -y -f $HOME/.ssh/id_rsa)",enable-guest-attributes=TRUE + +###### Configure VMs, run tests and shut them down ###### + +log_monitor_regexps=( + "(?& "$log_file" & + vm_ip="$(env VM_NAME="$vm_name" ZONE="$zone" "$my_dir/vm-ip")" + batch_formatted="$(format_batch_for_log "$batch")" + log_file="$artifacts_dir/test-$batch_formatted.log" + failed_log_file="$artifacts_dir/test-$batch_formatted-FAILED.log" + ssh_cmd=( env "VM_NAME=$vm_name" "ZONE=$zone" "$my_dir/on-test-vm" ) + prefix="[batch=${batch}]" + touch "$log_file" + + # Run the configuration, test, and, teardown in a subshell so we can + # background it. + ( + set +e + + conf_log_file="$artifacts_dir/configure-vm-$batch_formatted.log" + echo "$prefix Configuring test VM $vm_name. Redirecting log to $conf_log_file." + if "${my_dir}/configure-test-vm" "$vm_name" >& "$conf_log_file"; then + echo "$prefix Configuration of VM $vm_name SUCCEEDED." + else + echo "$prefix Configuration of VM $vm_name FAILED. Log file will be uploaded as artifact $conf_log_file. " + exit 1 + fi + + echo "$prefix Test batch $batch STARTING (sending logs to $log_file)..." + run_batch "$my_dir/on-test-vm" "$batch" "$vm_name" "$log_file" + rc=$? + if [ $rc = 0 ]; then + echo "$prefix Test batch $batch SUCCEEDED." + else + mv "$log_file" "$failed_log_file" + echo "$prefix Test batch $batch FAILED. Log file will be uploaded as artifact $failed_log_file." + if [ -n "${GCS_WORKFLOW_DIR:-}" ] && [ -n "${COMPONENT:-}" ]; then + failed_log_upload_dir="${GCS_WORKFLOW_DIR}/${COMPONENT}/failed-fv-logs" + if [ -n "${JOB_TAG:-}" ]; then + failed_log_upload_dir="${failed_log_upload_dir}/${JOB_TAG}" + fi + gcloud storage cp "$failed_log_file" "${failed_log_upload_dir}/" || true + fi + fi + + collect_log_file="$artifacts_dir/collect-artifacts-$batch_formatted.log" + if "${ssh_cmd[@]}" COMPONENT="${COMPONENT}" "${CALICO_DIR_NAME}/.semaphore/collect-artifacts" >& "$collect_log_file"; then + echo "$prefix Remote artifact collection SUCCEEDED" + else + echo "$prefix Remote artifact collection FAILED" + fi + if scp "${SSH_OPTIONS[@]}" -r -C "ubuntu@${vm_ip}:${CALICO_DIR_NAME}/artifacts" "${repo_dir}/artifacts/${batch}" >> "$collect_log_file" 2>&1; then + echo "$prefix Artifact retrieval SUCCEEDED" + else + echo "$prefix Artifact retrieval FAILED" + fi + + echo "$prefix Deleting test VM $vm_name" + if gcloud --quiet beta compute instances delete "$vm_name" --zone="${zone}" --no-graceful-shutdown; then + echo "$prefix Deletion of test VM $vm_name SUCCEEDED" + else + echo "$prefix Deletion of test VM $vm_name FAILED, will retry at end of job. " + fi + exit $rc + ) & pid=$! - pids+=( $pid ) - prefix="[batch=${batch} pid=${pid}]" - echo "$prefix Started test batch in background; monitoring its log ($log_file)." + log_files+=( "$log_file" ) + test_pid+=( "$pid" ) + ( - tail -F $log_file | grep --line-buffered \ - -e "^test.*\.\.\. ok" \ - -e "\.\.\. ERROR$" \ - -e "Failure output:" \ - -e "^ERROR:" \ - -e "^Traceback" \ - -e "^FAILED" \ - -e "^OK$" \ - -e "^XML:" \ - -e "^\[success\]" \ - -e "^\[error\]" \ - -C10 | sed 's/.*/'"${prefix}"' &/'; + # Redirect tail's stdin from /dev/null to prevent waiting + # forever on reads in semaphore CI + tail -f --retry "$log_file" < /dev/null | \ + grep --line-buffered --perl "${monitor_pattern}" -B 2 -A 15 | \ + sed 's/.*/'"${prefix}"' &/' | \ + grep --perl --line-buffered -v '^\[batch=[^\]]+\]\s+$'; ) & mon_pid=$! - monitor_pids+=( $mon_pid ) + monitor_pids+=( "$mon_pid" ) done final_result=0 +# Give the batches time to emit their start-up logs. +sleep 5 echo echo "===== Waiting for background test runners to finish ====" echo summary=() -for i in ${!batches[@]}; do +for i in "${!batches[@]}"; do batch=${batches[$i]} - pid=${pids[$i]} - echo "Waiting for test batch $batch to finish (PID=$pid)..." + pid=${test_pid[$i]} if wait "$pid"; then summary+=( "Test batch $batch SUCCEEDED" ) - echo "===== Test batch $batch SUCCEEDED (PID=$pid). Log file will be uploaded as artifact ${log_files[$i]}. =====" else - summary+=( "Test batch $batch FAILED; Log file will be uploaded as artifact ${log_files[$i]}" ) - echo "===== Test batch $batch FAILED (PID=$pid). Log file will be uploaded as artifact ${log_files[$i]}. =====" + batch_formatted="$(format_batch_for_log "$batch")" + failed_summary_log="$artifacts_dir/test-$batch_formatted-FAILED.log" + if [ ! -f "$failed_summary_log" ] && [ -f "$artifacts_dir/test-$batch_formatted.log" ]; then + failed_summary_log="$artifacts_dir/test-$batch_formatted.log" + fi + summary+=( "Test batch $batch FAILED; Log file will be uploaded as artifact $failed_summary_log" ) final_result=1 fi done @@ -91,7 +241,8 @@ done echo echo "===== Shutting down test monitors =====" for pid in "${monitor_pids[@]}"; do - kill $pid || true + # Note: negative PID to kill the entire process group. + kill -TERM "-$pid" || true done echo "===== Results summary =====" @@ -100,13 +251,6 @@ for s in "${summary[@]}"; do done echo -echo "===== Retrieving report files =====" -for batch in "${batches[@]}"; do - vm_name="$vm_name_prefix$batch" - $my_dir/scp-from-test-vm ${zone} ${vm_name} /home/ubuntu/calico/node/report/* $repo_dir/report/ -done -echo - echo "===== Done, exiting with RC=$final_result =====" exit $final_result diff --git a/.semaphore/vms/scp-from-test-vm b/.semaphore/vms/scp-from-test-vm index c7525249e04..a76e341931a 100755 --- a/.semaphore/vms/scp-from-test-vm +++ b/.semaphore/vms/scp-from-test-vm @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Copyright (c) 2023 Tigera, Inc. All rights reserved. +# Copyright (c) 2023-2025 Tigera, Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,10 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -zone=$1 -vm_name=$2 -shift -shift +vm_name=$1 -exec gcloud --quiet compute scp "ubuntu@${vm_name}:$1" "$2" "--zone=${zone}" --scp-flag="-o ServerAliveInterval=10" +vm_ip=$(gcloud compute instances describe ${vm_name} --zone=${ZONE} --format='value(networkInterfaces[0].accessConfigs[0].natIP)') +exec scp -o ServerAliveInterval=10 "ubuntu@${vm_ip}:$2" "$3" diff --git a/felix/.semaphore/on-test-vm b/.semaphore/vms/ssh-options old mode 100755 new mode 100644 similarity index 74% rename from felix/.semaphore/on-test-vm rename to .semaphore/vms/ssh-options index 8a1115658cf..7bd7d9acca2 --- a/felix/.semaphore/on-test-vm +++ b/.semaphore/vms/ssh-options @@ -1,6 +1,4 @@ -#!/usr/bin/env bash - -# Copyright (c) 2020 Tigera, Inc. All rights reserved. +# Copyright (c) 2026 Tigera, Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,4 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -exec gcloud --quiet compute ssh "--zone=${ZONE}" "ubuntu@${VM_NAME}" -- -o ServerAliveInterval=10 "$@" +SSH_OPTIONS=( + -o ServerAliveInterval=10 + -o StrictHostKeyChecking=yes + -o BatchMode=yes + -o UpdateHostKeys=no +) +export SSH_OPTIONS diff --git a/.semaphore/vms/vm-bootstrap.sh b/.semaphore/vms/vm-bootstrap.sh new file mode 100644 index 00000000000..f1a31154c93 --- /dev/null +++ b/.semaphore/vms/vm-bootstrap.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash + +# Copyright (c) 2025 Tigera, Inc. All rights reserved. +# +# 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. + +# This script is executed as a startup script for our GCP VMs. It runs at +# instance boot time as root. Logs go to the VM's serial console. + +set -xeo pipefail + +# Function to retry a command up to n times with a delay. When we run more than +# 30 or so VMs, apt-get failures become common. +retry() { + local n=10 + for i in $(seq 1 $n); do + "$@" && return 0 + echo "Command '$*' failed, retrying ($i/$n)..." + sleep 5 + done + return 1 +} + +retry apt-get update -y +retry apt-get install -y --no-install-recommends apt-transport-https ca-certificates curl software-properties-common + +# Add Docker's official GPG key: +install -m 0755 -d /etc/apt/keyrings +retry curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc +chmod a+r /etc/apt/keyrings/docker.asc + +ubuntu_codename=$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") + +# Add the repository to Apt sources: +tee /etc/apt/sources.list.d/docker.sources < /tmp/daemon.json +else + echo -en '{' > /tmp/daemon.json +fi +cat >> /tmp/daemon.json << EOF + "ipv6": true, + "fixed-cidr-v6": "2001:db8:1::/64" +} +EOF + + +# FIXME some tests rely on this. Tests used to run on SempahoreCI, which uses +# loose by default, but GCP defaults to strict mode, which breaks some tests. +# Fixing the dependency on loose is tracked by https://tigera.atlassian.net/browse/CORE-12146 +sysctl -w net.ipv4.conf.all.rp_filter=2 + +mv /tmp/daemon.json /etc/docker/daemon.json +systemctl restart docker +touch /var/run/startup-script-complete diff --git a/.semaphore/vms/vm-ip b/.semaphore/vms/vm-ip new file mode 100755 index 00000000000..0dcdaabe38f --- /dev/null +++ b/.semaphore/vms/vm-ip @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Copyright (c) 2023-2026 Tigera, Inc. All rights reserved. +# +# 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. + +set -e + +if [ -z "${VM_NAME}" ]; then + echo "VM_NAME is not set; it should be set to the name of the test VM" 1>&2 + exit 1 +fi +if [ -z "${ZONE}" ]; then + echo "ZONE is not set; it should be set to the GCP zone of the test VM" 1>&2 + exit 1 +fi + +ip_cache_file="/tmp/vm_ip_${VM_NAME}_${ZONE}" +if [ -e "$ip_cache_file" ]; then + vm_ip=$(cat "$ip_cache_file") +else + vm_ip=$(gcloud compute instances describe "${VM_NAME}" --zone="${ZONE}" --format='value(networkInterfaces[0].accessConfigs[0].natIP)') + echo "$vm_ip" > "$ip_cache_file" +fi +echo "$vm_ip" diff --git a/.semaphore/vms/vm-tests-common.sh b/.semaphore/vms/vm-tests-common.sh deleted file mode 100644 index d7cc24a1339..00000000000 --- a/.semaphore/vms/vm-tests-common.sh +++ /dev/null @@ -1,4 +0,0 @@ -# Common definitions for the tests that are run on cloud -# VMs rather than Semaphore VMs - -batches=(k8s-test) diff --git a/.yamllint.yaml b/.yamllint.yaml index 5bb1b380bc2..946c97781bf 100644 --- a/.yamllint.yaml +++ b/.yamllint.yaml @@ -18,6 +18,7 @@ ignore: # template files are not valid yaml - charts/calico/templates/ - charts/tigera-operator/templates/ + - process/testing/aso/infra/templates/ # these dirs are not owned by us, we don't own yamls that they may contain - cni-plugin/containernetworking-plugins/ - cni-plugin/flannel-cni-plugin/ diff --git a/Makefile b/Makefile index 81e8a7ed5e0..2045d2a1f99 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ DOCKER_RUN := mkdir -p ./.go-pkg-cache bin $(GOMOD_CACHE) && \ --net=host \ --init \ $(EXTRA_DOCKER_ARGS) \ + $(DOCKER_GIT_WORKTREE_ARGS) \ -e LOCAL_USER_ID=$(LOCAL_USER_ID) \ -e GOCACHE=/go-cache \ $(GOARCH_FLAGS) \ @@ -48,27 +49,16 @@ clean: $(MAKE) -C release clean rm -rf ./bin -ci-preflight-checks: - $(MAKE) check-go-mod - $(MAKE) verify-go-mods - $(MAKE) check-dockerfiles - $(MAKE) check-language - $(MAKE) generate SKIP_FIX_CHANGED=true - $(MAKE) fix-all - $(MAKE) -C networking-calico fmtpy - $(MAKE) check-ocp-no-crds - $(MAKE) yaml-lint - $(MAKE) check-dirty - $(MAKE) go-vet - $(MAKE) -C networking-calico flake8 - check-go-mod: $(DOCKER_GO_BUILD) ./hack/check-go-mod.sh go-vet: # Go vet will check that libbpf headers can be found; make sure they're available. $(MAKE) -C felix clone-libbpf - $(DOCKER_GO_BUILD) go vet ./... + $(DOCKER_GO_BUILD) go vet --tags fvtests ./... + +update-x-libraries: + $(DOCKER_GO_BUILD) sh -c "go get golang.org/x/... && go mod tidy" check-dockerfiles: ./hack/check-dockerfiles.sh @@ -79,6 +69,9 @@ check-images-availability: bin/crane bin/yq check-language: ./hack/check-language.sh +check-ginkgo-v2: + ./hack/check-ginkgo-v2.sh + check-ocp-no-crds: @echo "Checking for files in manifests/ocp with CustomResourceDefinitions" @CRD_FILES_IN_OCP_DIR=$$(grep "^kind: CustomResourceDefinition" manifests/ocp/* -l || true); if [ ! -z "$$CRD_FILES_IN_OCP_DIR" ]; then echo "ERROR: manifests/ocp should not have any CustomResourceDefinitions, these files should be removed:"; echo "$$CRD_FILES_IN_OCP_DIR"; exit 1; fi @@ -107,22 +100,26 @@ generate: $(MAKE) fix-changed gen-manifests: bin/helm bin/yq - cd ./manifests && \ - OPERATOR_VERSION=$(OPERATOR_VERSION) \ - CALICO_VERSION=$(CALICO_VERSION) \ - ./generate.sh + cd ./manifests && ./generate.sh # Get operator CRDs from the operator repo, OPERATOR_BRANCH must be set -get-operator-crds: var-require-all-OPERATOR_BRANCH - @echo ================================================================ - @echo === Pulling new operator CRDs from branch $(OPERATOR_BRANCH) === - @echo ================================================================ - cd ./charts/tigera-operator/crds/ && \ - for file in operator.tigera.io_*.yaml; do echo "downloading $$file from operator repo" && curl -fsSL https://raw.githubusercontent.com/tigera/operator/$(OPERATOR_BRANCH)/pkg/crds/operator/$${file} -o $${file}; done +get-operator-crds: var-require-all-OPERATOR_ORGANIZATION-OPERATOR_GIT_REPO-OPERATOR_BRANCH + @echo ============================================================================================================== + @echo === Pulling new operator CRDs from $(OPERATOR_ORGANIZATION)/$(OPERATOR_GIT_REPO) branch $(OPERATOR_BRANCH) === + @echo ============================================================================================================== + cd ./charts/crd.projectcalico.org.v1/templates/ && \ + for file in operator.tigera.io_*.yaml; do \ + echo "downloading $$file from operator repo"; \ + curl -fsSL https://raw.githubusercontent.com/$(OPERATOR_ORGANIZATION)/$(OPERATOR_GIT_REPO)/$(OPERATOR_BRANCH)/pkg/imports/crds/operator/$${file} -o $${file}; \ + cp $${file} ../../projectcalico.org.v3/templates/$${file}; \ + done $(MAKE) fix-changed gen-semaphore-yaml: - $(DOCKER_GO_BUILD) sh -c "cd .semaphore && ./generate-semaphore-yaml.sh" + $(DOCKER_GO_BUILD) sh -c "DEFAULT_BRANCH_OVERRIDE=$(DEFAULT_BRANCH_OVERRIDE) \ + SEMAPHORE_GIT_BRANCH=$(SEMAPHORE_GIT_BRANCH) \ + RELEASE_BRANCH_PREFIX=$(RELEASE_BRANCH_PREFIX) \ + go run ./hack/cmd/deps $(DEPS_ARGS) generate-semaphore-yamls" GO_DIRS=$(shell find -name '*.go' | grep -v -e './lib/' -e './pkg/' | grep -o --perl '^./\K[^/]+' | sort -u) DEP_FILES=$(patsubst %, %/deps.txt, $(GO_DIRS)) @@ -140,11 +137,31 @@ $(DEP_FILES): go.mod go.sum $(shell find . -name '*.go') Makefile hack/cmd/deps/ $(DOCKER_GO_BUILD) sh -c "go run ./hack/cmd/deps modules $(dir $@)"; \ } > $@ -# Build the tigera-operator helm chart. -chart: bin/tigera-operator-$(GIT_VERSION).tgz -bin/tigera-operator-$(GIT_VERSION).tgz: bin/helm $(shell find ./charts/tigera-operator -type f) +CHART_DESTINATION ?= ./bin + +# Build helm charts. +chart: $(CHART_DESTINATION)/tigera-operator-$(GIT_VERSION).tgz \ + $(CHART_DESTINATION)/projectcalico.org.v3-$(GIT_VERSION).tgz \ + $(CHART_DESTINATION)/crd.projectcalico.org.v1-$(GIT_VERSION).tgz + +$(CHART_DESTINATION)/tigera-operator-$(GIT_VERSION).tgz: bin/helm $(shell find ./charts/tigera-operator -type f) + mkdir -p $(CHART_DESTINATION) bin/helm package ./charts/tigera-operator \ - --destination ./bin/ \ + --destination $(CHART_DESTINATION)/ \ + --version $(GIT_VERSION) \ + --app-version $(GIT_VERSION) + +$(CHART_DESTINATION)/crd.projectcalico.org.v1-$(GIT_VERSION).tgz: bin/helm $(shell find ./charts/crd.projectcalico.org.v1/ -type f) + mkdir -p $(CHART_DESTINATION) + bin/helm package ./charts/crd.projectcalico.org.v1/ \ + --destination $(CHART_DESTINATION)/ \ + --version $(GIT_VERSION) \ + --app-version $(GIT_VERSION) + +$(CHART_DESTINATION)/projectcalico.org.v3-$(GIT_VERSION).tgz: bin/helm $(shell find ./charts/projectcalico.org.v3/ -type f) + mkdir -p $(CHART_DESTINATION) + bin/helm package ./charts/projectcalico.org.v3/ \ + --destination $(CHART_DESTINATION)/ \ --version $(GIT_VERSION) \ --app-version $(GIT_VERSION) @@ -164,23 +181,33 @@ image: # Run local e2e smoke test against the checked-out code # using a local kind cluster. ############################################################################### -E2E_FOCUS ?= "sig-network.*Conformance|sig-calico.*Conformance" -ADMINPOLICY_SUPPORTED_FEATURES ?= "AdminNetworkPolicy,BaselineAdminNetworkPolicy" -ADMINPOLICY_UNSUPPORTED_FEATURES ?= "" +E2E_FOCUS ?= "sig-network.*Conformance|sig-calico.*Conformance|BGP" +E2E_SKIP ?= "" +K8S_NETPOL_SUPPORTED_FEATURES ?= "ClusterNetworkPolicy" +K8S_NETPOL_UNSUPPORTED_FEATURES ?= "" + +## Create a kind cluster and run all e2e tests. e2e-test: $(MAKE) -C e2e build $(MAKE) -C node kind-k8st-setup - KUBECONFIG=$(KIND_KUBECONFIG) ./e2e/bin/k8s/e2e.test -ginkgo.focus=$(E2E_FOCUS) - KUBECONFIG=$(KIND_KUBECONFIG) ./e2e/bin/adminpolicy/e2e.test \ - -exempt-features=$(ADMINPOLICY_UNSUPPORTED_FEATURES) \ - -supported-features=$(ADMINPOLICY_SUPPORTED_FEATURES) + $(MAKE) e2e-run-test + $(MAKE) e2e-run-cnp-test -e2e-test-adminpolicy: +## Create a kind cluster and run the ClusterNetworkPolicy specific e2e tests. +e2e-test-clusternetworkpolicy: $(MAKE) -C e2e build $(MAKE) -C node kind-k8st-setup - KUBECONFIG=$(KIND_KUBECONFIG) ./e2e/bin/adminpolicy/e2e.test \ - -exempt-features=$(ADMINPOLICY_UNSUPPORTED_FEATURES) \ - -supported-features=$(ADMINPOLICY_SUPPORTED_FEATURES) + $(MAKE) e2e-run-cnp-test + +## Run the general e2e tests against a pre-existing kind cluster. +e2e-run-test: + KUBECONFIG=$(KIND_KUBECONFIG) ./e2e/bin/k8s/e2e.test --ginkgo.focus=$(E2E_FOCUS) --ginkgo.skip=$(E2E_SKIP) + +## Run the ClusterNetworkPolicy specific e2e tests against a pre-existing kind cluster. +e2e-run-cnp-test: + KUBECONFIG=$(KIND_KUBECONFIG) ./e2e/bin/clusternetworkpolicy/e2e.test \ + -exempt-features=$(K8S_NETPOL_UNSUPPORTED_FEATURES) \ + -supported-features=$(K8S_NETPOL_SUPPORTED_FEATURES) ############################################################################### # Release logic below @@ -206,7 +233,7 @@ release: release/bin/release @release/bin/release release build # Publish an already built release. -release-publish: release/bin/release bin/ghr +release-publish: release/bin/release bin/ghr bin/helm @release/bin/release release publish release-public: bin/gh release/bin/release @@ -214,11 +241,11 @@ release-public: bin/gh release/bin/release # Create a release branch. create-release-branch: release/bin/release - @release/bin/release branch cut -git-publish + @release/bin/release branch cut # Test the release code release-test: - $(DOCKER_RUN) $(CALICO_BUILD) ginkgo -cover -r release/pkg + $(DOCKER_RUN) $(CALICO_BUILD) ginkgo -cover -r hack/release/pkg # Currently our openstack builds either build *or* build and publish, # hence why we have two separate jobs here that do almost the same thing. diff --git a/README.md b/README.md index edd77da73e3..1755eaaa3ab 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Project Calico, created and maintained by [Tigera][tigera], is an open-source pr - **Flexible networking**: An array of networking tools at your disposal, including BGP, VXLAN, service advertisement, and more.
- +
## 🤝 Join the Calico Community diff --git a/api/.gitignore b/api/.gitignore index cd8aafca270..35ddbb4ddee 100644 --- a/api/.gitignore +++ b/api/.gitignore @@ -19,4 +19,6 @@ bin/ # IDE files /.idea/ -/.vscode/ \ No newline at end of file +/.vscode/ + +*.orig diff --git a/api/Makefile b/api/Makefile index 7b0e6e9471a..16130d1eab9 100644 --- a/api/Makefile +++ b/api/Makefile @@ -1,3 +1,6 @@ +# The tests in this directory use the projectcalico.org/v3 API group for CRDs. +CALICO_API_GROUP ?= projectcalico.org/v3 + # If ../metadata.mk exists, we're running this logic from within the calico repository. # If it does not, then we're in the api repo and we should use the local metadata.mk. ifneq ("$(wildcard ../metadata.mk)", "") @@ -12,6 +15,7 @@ LOCAL_CHECKS = lint-cache-dir check-copyright BINDIR ?= bin BUILD_DIR ?= build TOP_SRC_DIRS = pkg +KIND_CONFIG = $(KIND_DIR)/kind-single.config ############################################################################## # Download and include ../lib.Makefile before anything else @@ -33,6 +37,7 @@ DOCKER_RUN := mkdir -p ../.go-pkg-cache bin $(GOMOD_CACHE) && \ --net=host \ --init \ $(EXTRA_DOCKER_ARGS) \ + $(DOCKER_GIT_WORKTREE_ARGS) \ -e LOCAL_USER_ID=$(LOCAL_USER_ID) \ -e GOCACHE=/go-cache \ $(GOARCH_FLAGS) \ @@ -52,6 +57,18 @@ build: gen-files examples # Regenerate all files if the gen exes changed or any "types.go" files changed .PHONY: gen-files gen-files .generate_files: lint-cache-dir clean-generated + # Generate CRDs without descriptions + $(DOCKER_RUN) $(CALICO_BUILD) sh -c '$(GIT_CONFIG_SSH) controller-gen crd:allowDangerousTypes=true,crdVersions=v1,deprecatedV1beta1CompatibilityPreserveUnknownFields=false,maxDescLen=0 paths=./pkg/apis/... output:crd:dir=config/crd/' + # Remove the first yaml separator line. + $(DOCKER_RUN) $(CALICO_BUILD) sh -c 'find ./config/crd -name "*.yaml" | xargs sed -i 1d' + # Run prettier to fix indentation + docker run --rm --user $(id -u):$(id -g) -v $(CURDIR)/config/crd/:/work/config/crd/ tmknom/prettier --write --parser=yaml /work + # Patch in manual tweaks to the generated CRDs. + # - Add nullable to IPAM block allocations field to allow null values in the allocations array. + # - Remove the profiles CRD. Profiles are backed by Namespaces in Kubernetes and the CRD is not needed. + patch -p2 < patches/0001-Add-nullable-to-IPAM-block-allocations-field.patch + rm -f config/crd/projectcalico.org_profiles.yaml + # Generate defaults $(DOCKER_RUN) $(CALICO_BUILD) \ sh -c '$(GIT_CONFIG_SSH) defaulter-gen \ @@ -133,8 +150,12 @@ WHAT?=. GINKGO_FOCUS?=.* .PHONY:ut -ut: - $(DOCKER_RUN) --privileged $(CALICO_BUILD) \ +ut: kind-cluster-create + $(DOCKER_RUN) \ + --privileged \ + -e KUBECONFIG=/kubeconfig.yaml \ + -v $(KIND_KUBECONFIG):/kubeconfig.yaml \ + $(CALICO_BUILD) \ sh -c 'cd /go/src/$(PACKAGE_NAME) && ginkgo -r -focus="$(GINKGO_FOCUS)" $(WHAT)' ## Check if generated files are out of date diff --git a/api/admission/networkpolicy.mutatingadmissionpolicy.yaml b/api/admission/networkpolicy.mutatingadmissionpolicy.yaml new file mode 100644 index 00000000000..cce26c10ed9 --- /dev/null +++ b/api/admission/networkpolicy.mutatingadmissionpolicy.yaml @@ -0,0 +1,59 @@ +# This MutatingAdmissionPolicy defaults the types/policyTypes field on Calico policy resources. +apiVersion: admissionregistration.k8s.io/v1beta1 +kind: MutatingAdmissionPolicy +metadata: + name: "policytypes.policy.projectcalico.org" +spec: + paramKind: + kind: NetworkPolicy + apiVersion: projectcalico.org/v3 + matchConditions: + # Apply this mutation only if the 'types' field is missing (or policyTypes for stagednetworkpolicies). + - name: missing-types + expression: "!has(object.spec.types) && !has(object.spec.policyTypes)" + matchConstraints: + resourceRules: + - apiGroups: ["projectcalico.org"] + apiVersions: ["v3"] + operations: ["CREATE", "UPDATE"] + resources: + - networkpolicies + - globalnetworkpolicies + - stagednetworkpolicies + - stagedglobalnetworkpolicies + - stagedkubernetesnetworkpolicies + failurePolicy: Fail + reinvocationPolicy: IfNeeded + variables: + # Determine the policy types based on the presence of ingress and egress rules. + # If both are present, set types to ["Ingress", "Egress"]. + # If only ingress is present, set types to ["Ingress"]. + # If only egress is present, set types to ["Egress"]. + # If neither is present, default to ["Ingress"] for backward compatibility with policies from before types were required. + - name: defaultTypes + expression: | + (has(object.spec.ingress) && has(object.spec.egress)) ? ["Ingress", "Egress"] : + has(object.spec.egress) ? ["Egress"] : ["Ingress"] + + # The logic for StagedKubernetesNetworkPolicy is slightly different from the above: + # - If both ingress and egress are present, set types to ["Ingress", "Egress"]. + # - If only ingress is present, set types to ["Ingress"]. + # - If only egress is present, set types to ["Ingress", "Egress"]. + # - If neither is present, default to ["Ingress"] for backward compatibility. + - name: defaultKubeTypes + expression: | + (has(object.spec.ingress) && has(object.spec.egress)) ? ["Ingress", "Egress"] : + has(object.spec.egress) ? ["Ingress", "Egress"] : ["Ingress"] + + mutations: + # Add the calculated 'types' field to the spec. + - patchType: "JSONPatch" + jsonPatch: + expression: | + [ + JSONPatch{ + op: "add", + path: object.kind == "StagedKubernetesNetworkPolicy" ? "/spec/policyTypes" : "/spec/types", + value: object.kind == "StagedKubernetesNetworkPolicy" ? variables.defaultKubeTypes : variables.defaultTypes + } + ] diff --git a/api/admission/networkpolicy.mutatingadmissionpolicybinding.yaml b/api/admission/networkpolicy.mutatingadmissionpolicybinding.yaml new file mode 100644 index 00000000000..f6f72cfdf10 --- /dev/null +++ b/api/admission/networkpolicy.mutatingadmissionpolicybinding.yaml @@ -0,0 +1,18 @@ +# This MutatingAdmissionPolicyBinding binds the policy types defaulting mutation to the relevant resources. +apiVersion: admissionregistration.k8s.io/v1beta1 +kind: MutatingAdmissionPolicyBinding +metadata: + name: set-policytypes-binding +spec: + policyName: policytypes.policy.projectcalico.org + matchResources: + resourceRules: + - apiGroups: ["projectcalico.org"] + apiVersions: ["v3"] + operations: ["CREATE", "UPDATE"] + resources: + - networkpolicies + - globalnetworkpolicies + - stagednetworkpolicies + - stagedglobalnetworkpolicies + - stagedkubernetesnetworkpolicies diff --git a/api/admission/tierlabel.mutatingadmissionpolicy.yaml b/api/admission/tierlabel.mutatingadmissionpolicy.yaml new file mode 100644 index 00000000000..38bedc7460f --- /dev/null +++ b/api/admission/tierlabel.mutatingadmissionpolicy.yaml @@ -0,0 +1,35 @@ +# This MutatingAdmissionPolicy sets the projectcalico.org/tier label on policy resources +# to match the spec.tier field, defaulting to "default" if not specified. +apiVersion: admissionregistration.k8s.io/v1beta1 +kind: MutatingAdmissionPolicy +metadata: + name: "tierlabel.policy.projectcalico.org" +spec: + matchConstraints: + resourceRules: + - apiGroups: ["projectcalico.org"] + apiVersions: ["v3"] + operations: ["CREATE", "UPDATE"] + resources: + - networkpolicies + - globalnetworkpolicies + - stagednetworkpolicies + - stagedglobalnetworkpolicies + failurePolicy: Fail + reinvocationPolicy: IfNeeded + variables: + - name: tierValue + expression: | + has(object.spec.tier) && object.spec.tier != "" ? object.spec.tier : "default" + mutations: + # Set the projectcalico.org/tier label to match spec.tier. + # Uses ~1 encoding for the / in the label key per RFC 6901 (JSON Pointer). + - patchType: "JSONPatch" + jsonPatch: + expression: | + has(object.metadata.labels) ? + [JSONPatch{op: "add", path: "/metadata/labels/projectcalico.org~1tier", value: variables.tierValue}] : + [ + JSONPatch{op: "add", path: "/metadata/labels", value: {}}, + JSONPatch{op: "add", path: "/metadata/labels/projectcalico.org~1tier", value: variables.tierValue} + ] diff --git a/api/admission/tierlabel.mutatingadmissionpolicybinding.yaml b/api/admission/tierlabel.mutatingadmissionpolicybinding.yaml new file mode 100644 index 00000000000..d916b0208ec --- /dev/null +++ b/api/admission/tierlabel.mutatingadmissionpolicybinding.yaml @@ -0,0 +1,17 @@ +# This MutatingAdmissionPolicyBinding binds the tier label mutation to the relevant resources. +apiVersion: admissionregistration.k8s.io/v1beta1 +kind: MutatingAdmissionPolicyBinding +metadata: + name: set-tier-label-binding +spec: + policyName: tierlabel.policy.projectcalico.org + matchResources: + resourceRules: + - apiGroups: ["projectcalico.org"] + apiVersions: ["v3"] + operations: ["CREATE", "UPDATE"] + resources: + - networkpolicies + - globalnetworkpolicies + - stagednetworkpolicies + - stagedglobalnetworkpolicies diff --git a/api/config/crd/projectcalico.org_bgpconfigurations.yaml b/api/config/crd/projectcalico.org_bgpconfigurations.yaml new file mode 100644 index 00000000000..eaf66755758 --- /dev/null +++ b/api/config/crd/projectcalico.org_bgpconfigurations.yaml @@ -0,0 +1,145 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: bgpconfigurations.projectcalico.org +spec: + group: projectcalico.org + names: + kind: BGPConfiguration + listKind: BGPConfigurationList + plural: bgpconfigurations + shortNames: + - bgpconfig + - bgpconfigs + singular: bgpconfiguration + preserveUnknownFields: false + scope: Cluster + versions: + - name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + asNumber: + format: int32 + type: integer + bindMode: + enum: + - None + - NodeIP + type: string + communities: + items: + properties: + name: + type: string + value: + pattern: ^(\d+):(\d+)$|^(\d+):(\d+):(\d+)$ + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set + ignoredInterfaces: + items: + type: string + type: array + x-kubernetes-list-type: set + listenPort: + maximum: 65535 + minimum: 1 + type: integer + localWorkloadPeeringIPV4: + type: string + localWorkloadPeeringIPV6: + type: string + logSeverityScreen: + default: Info + pattern: ^(?i)(Trace|Debug|Info|Warning|Error|Fatal)?$ + type: string + nodeMeshMaxRestartTime: + type: string + nodeMeshPassword: + properties: + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + nodeToNodeMeshEnabled: + type: boolean + prefixAdvertisements: + items: + properties: + cidr: + format: cidr + type: string + communities: + items: + type: string + type: array + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set + serviceClusterIPs: + items: + properties: + cidr: + format: cidr + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set + serviceExternalIPs: + items: + properties: + cidr: + format: cidr + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set + serviceLoadBalancerAggregation: + default: Enabled + enum: + - Enabled + - Disabled + type: string + serviceLoadBalancerIPs: + items: + properties: + cidr: + format: cidr + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set + type: object + required: + - metadata + - spec + type: object + served: true + storage: true diff --git a/api/config/crd/projectcalico.org_bgpfilters.yaml b/api/config/crd/projectcalico.org_bgpfilters.yaml new file mode 100644 index 00000000000..f41bf4c13b0 --- /dev/null +++ b/api/config/crd/projectcalico.org_bgpfilters.yaml @@ -0,0 +1,207 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: bgpfilters.projectcalico.org +spec: + group: projectcalico.org + names: + kind: BGPFilter + listKind: BGPFilterList + plural: bgpfilters + singular: bgpfilter + preserveUnknownFields: false + scope: Cluster + versions: + - name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + exportV4: + items: + properties: + action: + enum: + - Accept + - Reject + type: string + cidr: + format: cidr + type: string + interface: + type: string + matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn + type: string + prefixLength: + properties: + max: + format: int32 + maximum: 32 + minimum: 0 + type: integer + min: + format: int32 + maximum: 32 + minimum: 0 + type: integer + type: object + x-kubernetes-map-type: atomic + source: + enum: + - RemotePeers + type: string + required: + - action + type: object + x-kubernetes-map-type: atomic + type: array + exportV6: + items: + properties: + action: + enum: + - Accept + - Reject + type: string + cidr: + format: cidr + type: string + interface: + type: string + matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn + type: string + prefixLength: + properties: + max: + format: int32 + maximum: 128 + minimum: 0 + type: integer + min: + format: int32 + maximum: 128 + minimum: 0 + type: integer + type: object + x-kubernetes-map-type: atomic + source: + enum: + - RemotePeers + type: string + required: + - action + type: object + x-kubernetes-map-type: atomic + type: array + importV4: + items: + properties: + action: + enum: + - Accept + - Reject + type: string + cidr: + format: cidr + type: string + interface: + type: string + matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn + type: string + prefixLength: + properties: + max: + format: int32 + maximum: 32 + minimum: 0 + type: integer + min: + format: int32 + maximum: 32 + minimum: 0 + type: integer + type: object + x-kubernetes-map-type: atomic + source: + enum: + - RemotePeers + type: string + required: + - action + type: object + x-kubernetes-map-type: atomic + type: array + importV6: + items: + properties: + action: + enum: + - Accept + - Reject + type: string + cidr: + format: cidr + type: string + interface: + type: string + matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn + type: string + prefixLength: + properties: + max: + format: int32 + maximum: 128 + minimum: 0 + type: integer + min: + format: int32 + maximum: 128 + minimum: 0 + type: integer + type: object + x-kubernetes-map-type: atomic + source: + enum: + - RemotePeers + type: string + required: + - action + type: object + x-kubernetes-map-type: atomic + type: array + type: object + required: + - metadata + - spec + type: object + served: true + storage: true diff --git a/api/config/crd/projectcalico.org_bgppeers.yaml b/api/config/crd/projectcalico.org_bgppeers.yaml new file mode 100644 index 00000000000..95c0f6d567e --- /dev/null +++ b/api/config/crd/projectcalico.org_bgppeers.yaml @@ -0,0 +1,136 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: bgppeers.projectcalico.org +spec: + group: projectcalico.org + names: + kind: BGPPeer + listKind: BGPPeerList + plural: bgppeers + singular: bgppeer + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - description: Selector for the nodes that should have this peering + jsonPath: .spec.nodeSelector + name: Node Selector + type: string + - description: + The node name identifying the Calico node instance that is targeted + by this peer + jsonPath: .spec.node + name: Node + type: string + - description: + The optional Local AS Number to use when peering with this remote + peer + jsonPath: .spec.localASNumber + name: Local ASN + type: string + - description: Selector for the remote nodes to peer with + jsonPath: .spec.peerSelector + name: Peer Selector + type: string + - description: + The IP address of the peer followed by an optional port number + to peer with + jsonPath: .spec.peerIP + name: Peer IP + type: string + - description: The AS Number of the peer + jsonPath: .spec.asNumber + name: Peer ASN + type: string + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + asNumber: + format: int32 + type: integer + filters: + items: + type: string + type: array + keepOriginalNextHop: + type: boolean + keepaliveTime: + type: string + localASNumber: + format: int32 + type: integer + localWorkloadSelector: + type: string + maxRestartTime: + type: string + nextHopMode: + enum: + - Auto + - Self + - Keep + type: string + node: + type: string + nodeSelector: + type: string + numAllowedLocalASNumbers: + format: int32 + type: integer + password: + properties: + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + peerIP: + type: string + peerSelector: + type: string + reachableBy: + type: string + reversePeering: + allOf: + - enum: + - Auto + - Manual + - enum: + - Auto + - Manual + type: string + sourceAddress: + enum: + - UseNodeIP + - None + type: string + ttlSecurity: + type: integer + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} diff --git a/api/config/crd/projectcalico.org_blockaffinities.yaml b/api/config/crd/projectcalico.org_blockaffinities.yaml new file mode 100644 index 00000000000..bb16b6bf881 --- /dev/null +++ b/api/config/crd/projectcalico.org_blockaffinities.yaml @@ -0,0 +1,74 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: blockaffinities.projectcalico.org +spec: + group: projectcalico.org + names: + kind: BlockAffinity + listKind: BlockAffinityList + plural: blockaffinities + shortNames: + - affinity + - affinities + singular: blockaffinity + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - description: The block CIDR + jsonPath: .spec.cidr + name: CIDR + type: string + - description: The node the block is affine to + jsonPath: .spec.node + name: Node + type: string + - description: The type of block affinity + jsonPath: .spec.type + name: Type + type: string + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + cidr: + format: cidr + type: string + deleted: + default: false + type: boolean + node: + type: string + state: + enum: + - "" + - confirmed + - pending + - pendingDeletion + type: string + type: + type: string + required: + - cidr + - deleted + - node + - state + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} diff --git a/api/config/crd/projectcalico.org_caliconodestatuses.yaml b/api/config/crd/projectcalico.org_caliconodestatuses.yaml new file mode 100644 index 00000000000..d3c289a9978 --- /dev/null +++ b/api/config/crd/projectcalico.org_caliconodestatuses.yaml @@ -0,0 +1,227 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: caliconodestatuses.projectcalico.org +spec: + group: projectcalico.org + names: + kind: CalicoNodeStatus + listKind: CalicoNodeStatusList + plural: caliconodestatuses + singular: caliconodestatus + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - description: The name of the node + jsonPath: .spec.node + name: Node + type: string + - description: The types of information to monitor for this calico/node + jsonPath: .spec.classes + name: Classes + type: string + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + classes: + items: + enum: + - Agent + - BGP + - Routes + type: string + type: array + node: + type: string + updatePeriodSeconds: + format: int32 + type: integer + type: object + status: + properties: + agent: + properties: + birdV4: + properties: + lastBootTime: + type: string + lastReconfigurationTime: + type: string + routerID: + type: string + state: + enum: + - Ready + - NotReady + type: string + version: + type: string + type: object + birdV6: + properties: + lastBootTime: + type: string + lastReconfigurationTime: + type: string + routerID: + type: string + state: + enum: + - Ready + - NotReady + type: string + version: + type: string + type: object + type: object + bgp: + properties: + numberEstablishedV4: + type: integer + numberEstablishedV6: + type: integer + numberNotEstablishedV4: + type: integer + numberNotEstablishedV6: + type: integer + peersV4: + items: + properties: + peerIP: + type: string + since: + type: string + state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close + type: string + type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer + type: string + type: object + type: array + peersV6: + items: + properties: + peerIP: + type: string + since: + type: string + state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close + type: string + type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer + type: string + type: object + type: array + required: + - numberEstablishedV4 + - numberEstablishedV6 + - numberNotEstablishedV4 + - numberNotEstablishedV6 + type: object + lastUpdated: + format: date-time + nullable: true + type: string + routes: + properties: + routesV4: + items: + properties: + destination: + type: string + gateway: + type: string + interface: + type: string + learnedFrom: + properties: + peerIP: + type: string + sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer + type: string + type: object + type: + enum: + - FIB + - RIB + type: string + type: object + type: array + routesV6: + items: + properties: + destination: + type: string + gateway: + type: string + interface: + type: string + learnedFrom: + properties: + peerIP: + type: string + sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer + type: string + type: object + type: + enum: + - FIB + - RIB + type: string + type: object + type: array + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} diff --git a/api/config/crd/projectcalico.org_clusterinformations.yaml b/api/config/crd/projectcalico.org_clusterinformations.yaml new file mode 100644 index 00000000000..c21493170f1 --- /dev/null +++ b/api/config/crd/projectcalico.org_clusterinformations.yaml @@ -0,0 +1,62 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: clusterinformations.projectcalico.org +spec: + group: projectcalico.org + names: + kind: ClusterInformation + listKind: ClusterInformationList + plural: clusterinformations + shortNames: + - clusterinfo + - clusterinfos + singular: clusterinformation + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - description: The version of Calico that the cluster is running + jsonPath: .spec.calicoVersion + name: Version + type: string + - description: Whether the datastore is ready for use + jsonPath: .spec.datastoreReady + name: Ready + type: boolean + - description: The types associated with the cluster + jsonPath: .spec.clusterType + name: Types + type: string + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + calicoVersion: + type: string + clusterGUID: + type: string + clusterType: + type: string + datastoreReady: + type: boolean + variant: + type: string + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} diff --git a/api/config/crd/projectcalico.org_felixconfigurations.yaml b/api/config/crd/projectcalico.org_felixconfigurations.yaml new file mode 100644 index 00000000000..68f2f848a34 --- /dev/null +++ b/api/config/crd/projectcalico.org_felixconfigurations.yaml @@ -0,0 +1,591 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: felixconfigurations.projectcalico.org +spec: + group: projectcalico.org + names: + kind: FelixConfiguration + listKind: FelixConfigurationList + plural: felixconfigurations + singular: felixconfiguration + preserveUnknownFields: false + scope: Cluster + versions: + - name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + allowIPIPPacketsFromWorkloads: + type: boolean + allowVXLANPacketsFromWorkloads: + type: boolean + awsSrcDstCheck: + enum: + - DoNothing + - Enable + - Disable + type: string + bpfAttachType: + enum: + - TC + - TCX + type: string + bpfCTLBLogFilter: + type: string + bpfConnectTimeLoadBalancing: + enum: + - TCP + - Enabled + - Disabled + type: string + bpfConnectTimeLoadBalancingEnabled: + type: boolean + bpfConntrackLogLevel: + enum: + - "Off" + - Debug + type: string + bpfConntrackMode: + enum: + - Auto + - Userspace + - BPFProgram + type: string + bpfConntrackTimeouts: + properties: + creationGracePeriod: + pattern: ^(([0-9]*(\.[0-9]*)?(ms|s|h|m|us)+)+|Auto)$ + type: string + genericTimeout: + pattern: ^(([0-9]*(\.[0-9]*)?(ms|s|h|m|us)+)+|Auto)$ + type: string + icmpTimeout: + pattern: ^(([0-9]*(\.[0-9]*)?(ms|s|h|m|us)+)+|Auto)$ + type: string + tcpEstablished: + pattern: ^(([0-9]*(\.[0-9]*)?(ms|s|h|m|us)+)+|Auto)$ + type: string + tcpFinsSeen: + pattern: ^(([0-9]*(\.[0-9]*)?(ms|s|h|m|us)+)+|Auto)$ + type: string + tcpResetSeen: + pattern: ^(([0-9]*(\.[0-9]*)?(ms|s|h|m|us)+)+|Auto)$ + type: string + tcpSynSent: + pattern: ^(([0-9]*(\.[0-9]*)?(ms|s|h|m|us)+)+|Auto)$ + type: string + udpTimeout: + pattern: ^(([0-9]*(\.[0-9]*)?(ms|s|h|m|us)+)+|Auto)$ + type: string + type: object + bpfDSROptoutCIDRs: + items: + type: string + type: array + bpfDataIfacePattern: + type: string + bpfDisableGROForIfaces: + type: string + bpfDisableUnprivileged: + type: boolean + bpfEnabled: + type: boolean + bpfEnforceRPF: + pattern: ^(?i)(Disabled|Strict|Loose)?$ + type: string + bpfExcludeCIDRsFromNAT: + items: + type: string + type: array + bpfExportBufferSizeMB: + type: integer + bpfExtToServiceConnmark: + type: integer + bpfExternalServiceMode: + pattern: ^(?i)(Tunnel|DSR)?$ + type: string + bpfForceTrackPacketsFromIfaces: + items: + type: string + type: array + bpfHostConntrackBypass: + type: boolean + bpfHostNetworkedNATWithoutCTLB: + enum: + - Enabled + - Disabled + type: string + bpfJITHardening: + allOf: + - enum: + - Auto + - Strict + - enum: + - Auto + - Strict + type: string + bpfKubeProxyHealthzPort: + type: integer + bpfKubeProxyIptablesCleanupEnabled: + type: boolean + bpfKubeProxyMinSyncPeriod: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + bpfL3IfacePattern: + type: string + bpfLogFilters: + additionalProperties: + type: string + type: object + bpfLogLevel: + pattern: ^(?i)(Off|Info|Debug)?$ + type: string + bpfMaglevMaxEndpointsPerService: + type: integer + bpfMaglevMaxServices: + type: integer + bpfMapSizeConntrack: + type: integer + bpfMapSizeConntrackCleanupQueue: + minimum: 1 + type: integer + bpfMapSizeConntrackScaling: + pattern: ^(?i)(Disabled|DoubleIfFull)?$ + type: string + bpfMapSizeIPSets: + type: integer + bpfMapSizeIfState: + type: integer + bpfMapSizeNATAffinity: + type: integer + bpfMapSizeNATBackend: + type: integer + bpfMapSizeNATFrontend: + type: integer + bpfMapSizePerCpuConntrack: + type: integer + bpfMapSizeRoute: + type: integer + bpfPSNATPorts: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + bpfPolicyDebugEnabled: + type: boolean + bpfProfiling: + enum: + - Enabled + - Disabled + type: string + bpfRedirectToPeer: + enum: + - Enabled + - Disabled + type: string + cgroupV2Path: + type: string + chainInsertMode: + pattern: ^(?i)(Insert|Append)?$ + type: string + dataplaneDriver: + type: string + dataplaneWatchdogTimeout: + type: string + debugDisableLogDropping: + type: boolean + debugHost: + type: string + debugMemoryProfilePath: + type: string + debugPort: + type: integer + debugSimulateCalcGraphHangAfter: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + debugSimulateDataplaneApplyDelay: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + debugSimulateDataplaneHangAfter: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + defaultEndpointToHostAction: + pattern: ^(?i)(Drop|Accept|Return)?$ + type: string + deviceRouteProtocol: + type: integer + deviceRouteSourceAddress: + type: string + deviceRouteSourceAddressIPv6: + type: string + disableConntrackInvalidCheck: + type: boolean + endpointReportingDelay: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + endpointReportingEnabled: + type: boolean + endpointStatusPathPrefix: + type: string + externalNodesList: + items: + type: string + type: array + failsafeInboundHostPorts: + items: + properties: + net: + type: string + port: + type: integer + protocol: + type: string + required: + - port + type: object + type: array + failsafeOutboundHostPorts: + items: + properties: + net: + type: string + port: + type: integer + protocol: + type: string + required: + - port + type: object + type: array + featureDetectOverride: + pattern: ^([a-zA-Z0-9-_]+=(true|false|),)*([a-zA-Z0-9-_]+=(true|false|))?$ + type: string + featureGates: + pattern: ^([a-zA-Z0-9-_]+=([^=]+),)*([a-zA-Z0-9-_]+=([^=]+))?$ + type: string + floatingIPs: + enum: + - Enabled + - Disabled + type: string + flowLogsCollectorDebugTrace: + type: boolean + flowLogsFlushInterval: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + flowLogsGoldmaneServer: + type: string + flowLogsLocalReporter: + enum: + - Disabled + - Enabled + type: string + flowLogsPolicyEvaluationMode: + enum: + - None + - Continuous + type: string + genericXDPEnabled: + type: boolean + goGCThreshold: + type: integer + goMaxProcs: + type: integer + goMemoryLimitMB: + type: integer + healthEnabled: + type: boolean + healthHost: + type: string + healthPort: + type: integer + healthTimeoutOverrides: + items: + properties: + name: + type: string + timeout: + type: string + required: + - name + - timeout + type: object + type: array + interfaceExclude: + type: string + interfacePrefix: + type: string + interfaceRefreshInterval: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + ipForwarding: + enum: + - Enabled + - Disabled + type: string + ipipEnabled: + type: boolean + ipipMTU: + type: integer + ipsetsRefreshInterval: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + iptablesBackend: + enum: + - Legacy + - NFT + - Auto + pattern: ^(?i)(Auto|Legacy|NFT)?$ + type: string + iptablesFilterAllowAction: + pattern: ^(?i)(Accept|Return)?$ + type: string + iptablesFilterDenyAction: + pattern: ^(?i)(Drop|Reject)?$ + type: string + iptablesLockProbeInterval: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + iptablesMangleAllowAction: + pattern: ^(?i)(Accept|Return)?$ + type: string + iptablesMarkMask: + format: int32 + type: integer + iptablesNATOutgoingInterfaceFilter: + type: string + iptablesPostWriteCheckInterval: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + iptablesRefreshInterval: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + ipv6Support: + type: boolean + kubeNodePortRanges: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + logActionRateLimit: + pattern: ^[1-9]\d{0,3}/(?:second|minute|hour|day)$ + type: string + logActionRateLimitBurst: + maximum: 9999 + minimum: 0 + type: integer + logDebugFilenameRegex: + type: string + logFilePath: + type: string + logPrefix: + pattern: "^([a-zA-Z0-9%: /_-])*$" + type: string + logSeverityFile: + pattern: ^(?i)(Trace|Debug|Info|Warning|Error|Fatal)?$ + type: string + logSeverityScreen: + pattern: ^(?i)(Trace|Debug|Info|Warning|Error|Fatal)?$ + type: string + logSeveritySys: + pattern: ^(?i)(Trace|Debug|Info|Warning|Error|Fatal)?$ + type: string + maxIpsetSize: + type: integer + metadataAddr: + type: string + metadataPort: + type: integer + mtuIfacePattern: + type: string + natOutgoingAddress: + type: string + natOutgoingExclusions: + enum: + - IPPoolsOnly + - IPPoolsAndHostIPs + type: string + natPortRange: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + netlinkTimeout: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + nftablesFilterAllowAction: + pattern: ^(?i)(Accept|Return)?$ + type: string + nftablesFilterDenyAction: + pattern: ^(?i)(Drop|Reject)?$ + type: string + nftablesMangleAllowAction: + pattern: ^(?i)(Accept|Return)?$ + type: string + nftablesMarkMask: + format: int32 + type: integer + nftablesMode: + default: Auto + enum: + - Disabled + - Enabled + - Auto + type: string + nftablesRefreshInterval: + type: string + openstackRegion: + type: string + policySyncPathPrefix: + type: string + programClusterRoutes: + enum: + - Enabled + - Disabled + type: string + prometheusGoMetricsEnabled: + type: boolean + prometheusMetricsCAFile: + type: string + prometheusMetricsCertFile: + type: string + prometheusMetricsClientAuth: + type: string + prometheusMetricsEnabled: + type: boolean + prometheusMetricsHost: + type: string + prometheusMetricsKeyFile: + type: string + prometheusMetricsPort: + type: integer + prometheusProcessMetricsEnabled: + type: boolean + prometheusWireGuardMetricsEnabled: + type: boolean + removeExternalRoutes: + type: boolean + reportingInterval: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + reportingTTL: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + requireMTUFile: + type: boolean + routeRefreshInterval: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + routeSource: + pattern: ^(?i)(WorkloadIPs|CalicoIPAM)?$ + type: string + routeSyncDisabled: + type: boolean + routeTableRange: + properties: + max: + type: integer + min: + type: integer + required: + - max + - min + type: object + routeTableRanges: + items: + properties: + max: + type: integer + min: + type: integer + required: + - max + - min + type: object + type: array + serviceLoopPrevention: + pattern: ^(?i)(Drop|Reject|Disabled)?$ + type: string + sidecarAccelerationEnabled: + type: boolean + usageReportingEnabled: + type: boolean + usageReportingInitialDelay: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + usageReportingInterval: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + useInternalDataplaneDriver: + type: boolean + vxlanEnabled: + type: boolean + vxlanMTU: + type: integer + vxlanMTUV6: + type: integer + vxlanPort: + type: integer + vxlanVNI: + type: integer + windowsManageFirewallRules: + enum: + - Enabled + - Disabled + type: string + wireguardEnabled: + type: boolean + wireguardEnabledV6: + type: boolean + wireguardHostEncryptionEnabled: + type: boolean + wireguardInterfaceName: + type: string + wireguardInterfaceNameV6: + type: string + wireguardKeepAlive: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + wireguardListeningPort: + type: integer + wireguardListeningPortV6: + type: integer + wireguardMTU: + type: integer + wireguardMTUV6: + type: integer + wireguardRoutingRulePriority: + type: integer + wireguardThreadingEnabled: + type: boolean + workloadSourceSpoofing: + pattern: ^(?i)(Disabled|Any)?$ + type: string + xdpEnabled: + type: boolean + xdpRefreshInterval: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + type: object + required: + - metadata + - spec + type: object + served: true + storage: true diff --git a/api/config/crd/projectcalico.org_globalnetworkpolicies.yaml b/api/config/crd/projectcalico.org_globalnetworkpolicies.yaml new file mode 100644 index 00000000000..db22cecf128 --- /dev/null +++ b/api/config/crd/projectcalico.org_globalnetworkpolicies.yaml @@ -0,0 +1,438 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: globalnetworkpolicies.projectcalico.org +spec: + group: projectcalico.org + names: + kind: GlobalNetworkPolicy + listKind: GlobalNetworkPolicyList + plural: globalnetworkpolicies + shortNames: + - gnp + - cgnp + singular: globalnetworkpolicy + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.tier + name: Tier + type: string + - jsonPath: .spec.order + name: Order + type: number + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + applyOnForward: + type: boolean + doNotTrack: + type: boolean + egress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: + properties: + exact: + type: string + prefix: + type: string + type: object + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: + enum: + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + required: + - action + type: object + type: array + ingress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: + properties: + exact: + type: string + prefix: + type: string + type: object + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: + enum: + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + required: + - action + type: object + type: array + namespaceSelector: + type: string + order: + type: number + performanceHints: + items: + enum: + - AssumeNeededOnEveryNode + type: string + type: array + preDNAT: + type: boolean + selector: + type: string + serviceAccountSelector: + type: string + tier: + default: default + type: string + types: + items: + enum: + - Ingress + - Egress + type: string + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-list-type: set + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} diff --git a/api/config/crd/projectcalico.org_globalnetworksets.yaml b/api/config/crd/projectcalico.org_globalnetworksets.yaml new file mode 100644 index 00000000000..bf92d67decb --- /dev/null +++ b/api/config/crd/projectcalico.org_globalnetworksets.yaml @@ -0,0 +1,47 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: globalnetworksets.projectcalico.org +spec: + group: projectcalico.org + names: + kind: GlobalNetworkSet + listKind: GlobalNetworkSetList + plural: globalnetworksets + shortNames: + - gns + singular: globalnetworkset + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} diff --git a/api/config/crd/projectcalico.org_hostendpoints.yaml b/api/config/crd/projectcalico.org_hostendpoints.yaml new file mode 100644 index 00000000000..9bbd6824722 --- /dev/null +++ b/api/config/crd/projectcalico.org_hostendpoints.yaml @@ -0,0 +1,85 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: hostendpoints.projectcalico.org +spec: + group: projectcalico.org + names: + kind: HostEndpoint + listKind: HostEndpointList + plural: hostendpoints + shortNames: + - hep + - heps + singular: hostendpoint + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - description: + The node name identifying the Calico node instance that is targeted + by this HostEndpoint + jsonPath: .spec.node + name: Node + type: string + - description: The name of the interface that is targeted by this HostEndpoint + jsonPath: .spec.interfaceName + name: Interface + type: string + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + expectedIPs: + items: + type: string + type: array + x-kubernetes-list-type: set + interfaceName: + type: string + node: + type: string + ports: + items: + properties: + name: + type: string + port: + maximum: 65535 + minimum: 0 + type: integer + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + required: + - name + - port + - protocol + type: object + type: array + profiles: + items: + type: string + type: array + x-kubernetes-list-type: set + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} diff --git a/api/config/crd/projectcalico.org_ipamblocks.yaml b/api/config/crd/projectcalico.org_ipamblocks.yaml new file mode 100644 index 00000000000..21e82f64daa --- /dev/null +++ b/api/config/crd/projectcalico.org_ipamblocks.yaml @@ -0,0 +1,103 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: ipamblocks.projectcalico.org +spec: + group: projectcalico.org + names: + kind: IPAMBlock + listKind: IPAMBlockList + plural: ipamblocks + singular: ipamblock + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - description: The block CIDR + jsonPath: .spec.cidr + name: CIDR + type: string + - description: The block affinity + jsonPath: .spec.affinity + name: Affinity + type: string + - description: The time at which affinity was claimed + jsonPath: .spec.affinityClaimTime + name: ClaimTime + type: date + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + affinity: + pattern: ^(host|virtual):[a-zA-Z0-9\.\-_]+$ + type: string + affinityClaimTime: + format: date-time + type: string + allocations: + items: + type: integer + # TODO: This nullable is manually added in. We should update controller-gen + # to handle []*int properly itself. + nullable: true + type: array + attributes: + items: + properties: + alternate: + additionalProperties: + type: string + type: object + handle_id: + type: string + secondary: + additionalProperties: + type: string + type: object + type: object + type: array + cidr: + format: cidr + type: string + deleted: + type: boolean + sequenceNumber: + default: 0 + format: int64 + type: integer + sequenceNumberForAllocation: + additionalProperties: + format: int64 + type: integer + type: object + strictAffinity: + type: boolean + unallocated: + items: + type: integer + type: array + required: + - allocations + - attributes + - cidr + - strictAffinity + - unallocated + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} diff --git a/api/config/crd/projectcalico.org_ipamconfigurations.yaml b/api/config/crd/projectcalico.org_ipamconfigurations.yaml new file mode 100644 index 00000000000..9be7e3a9006 --- /dev/null +++ b/api/config/crd/projectcalico.org_ipamconfigurations.yaml @@ -0,0 +1,72 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: ipamconfigurations.projectcalico.org +spec: + group: projectcalico.org + names: + kind: IPAMConfiguration + listKind: IPAMConfigurationList + plural: ipamconfigurations + shortNames: + - ipamconfig + - ipamconfigs + singular: ipamconfiguration + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - description: Whether borrowing IP addresses is allowed + jsonPath: .spec.strictAffinity + name: StrictAffinity + type: boolean + - description: The max number of blocks that can be affine to each host + jsonPath: .spec.maxBlocksPerHost + name: MaxBlocksPerHost + type: integer + - description: Whether or not to auto allocate blocks to hosts + jsonPath: .spec.autoAllocateBlocks + name: AutoAllocateBlocks + type: boolean + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + autoAllocateBlocks: + default: true + type: boolean + kubeVirtVMAddressPersistence: + enum: + - Enabled + - Disabled + type: string + maxBlocksPerHost: + default: 0 + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + strictAffinity: + default: false + type: boolean + required: + - autoAllocateBlocks + - strictAffinity + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} diff --git a/api/config/crd/projectcalico.org_ipamhandles.yaml b/api/config/crd/projectcalico.org_ipamhandles.yaml new file mode 100644 index 00000000000..8b4f60b9aa6 --- /dev/null +++ b/api/config/crd/projectcalico.org_ipamhandles.yaml @@ -0,0 +1,52 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: ipamhandles.projectcalico.org +spec: + group: projectcalico.org + names: + kind: IPAMHandle + listKind: IPAMHandleList + plural: ipamhandles + singular: ipamhandle + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - description: The handle ID + jsonPath: .spec.handleID + name: ID + type: string + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + block: + additionalProperties: + type: integer + type: object + deleted: + type: boolean + handleID: + type: string + required: + - block + - handleID + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} diff --git a/api/config/crd/projectcalico.org_ippools.yaml b/api/config/crd/projectcalico.org_ippools.yaml new file mode 100644 index 00000000000..7d3d13ddf3c --- /dev/null +++ b/api/config/crd/projectcalico.org_ippools.yaml @@ -0,0 +1,147 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: ippools.projectcalico.org +spec: + group: projectcalico.org + names: + kind: IPPool + listKind: IPPoolList + plural: ippools + singular: ippool + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - description: The pool CIDR + jsonPath: .spec.cidr + name: CIDR + type: string + - description: The VXLAN mode for this pool + jsonPath: .spec.vxlanMode + name: VXLAN + type: string + - description: The IPIP mode for this pool + jsonPath: .spec.ipipMode + name: IPIP + type: string + - description: Whether outgoing NAT is enabled for this pool + jsonPath: .spec.natOutgoing + name: NAT + type: boolean + - description: Whether or not this pool is available for IP allocations + jsonPath: .status.conditions[?(@.type=='Allocatable')].status + name: Allocatable + type: string + - description: The age of the pool + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + allowedUses: + items: + enum: + - Workload + - Tunnel + - LoadBalancer + type: string + type: array + x-kubernetes-list-type: set + assignmentMode: + default: Automatic + enum: + - Automatic + - Manual + type: string + blockSize: + maximum: 128 + minimum: 0 + type: integer + cidr: + format: cidr + type: string + disableBGPExport: + type: boolean + disabled: + type: boolean + ipipMode: + enum: + - Never + - Always + - CrossSubnet + type: string + namespaceSelector: + type: string + natOutgoing: + type: boolean + nodeSelector: + type: string + vxlanMode: + enum: + - Never + - Always + - CrossSubnet + type: string + required: + - cidr + type: object + status: + properties: + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + maxLength: 32768 + type: string + observedGeneration: + format: int64 + minimum: 0 + type: integer + reason: + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/api/config/crd/projectcalico.org_ipreservations.yaml b/api/config/crd/projectcalico.org_ipreservations.yaml new file mode 100644 index 00000000000..19f44a1edf8 --- /dev/null +++ b/api/config/crd/projectcalico.org_ipreservations.yaml @@ -0,0 +1,41 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: ipreservations.projectcalico.org +spec: + group: projectcalico.org + names: + kind: IPReservation + listKind: IPReservationList + plural: ipreservations + singular: ipreservation + preserveUnknownFields: false + scope: Cluster + versions: + - name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + reservedCIDRs: + format: cidr + items: + type: string + type: array + x-kubernetes-list-type: set + type: object + required: + - metadata + - spec + type: object + served: true + storage: true diff --git a/api/config/crd/projectcalico.org_kubecontrollersconfigurations.yaml b/api/config/crd/projectcalico.org_kubecontrollersconfigurations.yaml new file mode 100644 index 00000000000..6427bf54a6c --- /dev/null +++ b/api/config/crd/projectcalico.org_kubecontrollersconfigurations.yaml @@ -0,0 +1,276 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: kubecontrollersconfigurations.projectcalico.org +spec: + group: projectcalico.org + names: + kind: KubeControllersConfiguration + listKind: KubeControllersConfigurationList + plural: kubecontrollersconfigurations + shortNames: + - kcc + - kccs + singular: kubecontrollersconfiguration + preserveUnknownFields: false + scope: Cluster + versions: + - name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + controllers: + properties: + loadBalancer: + properties: + assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly + type: string + type: object + namespace: + properties: + reconcilerPeriod: + type: string + type: object + node: + properties: + hostEndpoint: + properties: + autoCreate: + enum: + - Enabled + - Disabled + type: string + createDefaultHostEndpoint: + type: string + templates: + items: + properties: + generateName: + maxLength: 253 + type: string + interfaceCIDRs: + items: + type: string + type: array + x-kubernetes-list-type: set + interfacePattern: + type: string + labels: + additionalProperties: + type: string + type: object + nodeSelector: + type: string + type: object + type: array + type: object + leakGracePeriod: + type: string + reconcilerPeriod: + type: string + syncLabels: + enum: + - Enabled + - Disabled + type: string + type: object + policy: + properties: + reconcilerPeriod: + type: string + type: object + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object + serviceAccount: + properties: + reconcilerPeriod: + type: string + type: object + workloadEndpoint: + properties: + reconcilerPeriod: + type: string + type: object + type: object + debugProfilePort: + format: int32 + maximum: 65535 + minimum: 0 + type: integer + etcdV3CompactionPeriod: + type: string + healthChecks: + default: Enabled + enum: + - Enabled + - Disabled + type: string + logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic + type: string + prometheusMetricsPort: + maximum: 65535 + minimum: 0 + type: integer + required: + - controllers + type: object + status: + properties: + environmentVars: + additionalProperties: + type: string + type: object + runningConfig: + properties: + controllers: + properties: + loadBalancer: + properties: + assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly + type: string + type: object + namespace: + properties: + reconcilerPeriod: + type: string + type: object + node: + properties: + hostEndpoint: + properties: + autoCreate: + enum: + - Enabled + - Disabled + type: string + createDefaultHostEndpoint: + type: string + templates: + items: + properties: + generateName: + maxLength: 253 + type: string + interfaceCIDRs: + items: + type: string + type: array + x-kubernetes-list-type: set + interfacePattern: + type: string + labels: + additionalProperties: + type: string + type: object + nodeSelector: + type: string + type: object + type: array + type: object + leakGracePeriod: + type: string + reconcilerPeriod: + type: string + syncLabels: + enum: + - Enabled + - Disabled + type: string + type: object + policy: + properties: + reconcilerPeriod: + type: string + type: object + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object + serviceAccount: + properties: + reconcilerPeriod: + type: string + type: object + workloadEndpoint: + properties: + reconcilerPeriod: + type: string + type: object + type: object + debugProfilePort: + format: int32 + maximum: 65535 + minimum: 0 + type: integer + etcdV3CompactionPeriod: + type: string + healthChecks: + default: Enabled + enum: + - Enabled + - Disabled + type: string + logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic + type: string + prometheusMetricsPort: + maximum: 65535 + minimum: 0 + type: integer + required: + - controllers + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/api/config/crd/projectcalico.org_networkpolicies.yaml b/api/config/crd/projectcalico.org_networkpolicies.yaml new file mode 100644 index 00000000000..4168f777f13 --- /dev/null +++ b/api/config/crd/projectcalico.org_networkpolicies.yaml @@ -0,0 +1,429 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: networkpolicies.projectcalico.org +spec: + group: projectcalico.org + names: + kind: NetworkPolicy + listKind: NetworkPolicyList + plural: networkpolicies + shortNames: + - cnp + - caliconetworkpolicy + singular: networkpolicy + preserveUnknownFields: false + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.tier + name: Tier + type: string + - jsonPath: .spec.order + name: Order + type: number + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + egress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: + properties: + exact: + type: string + prefix: + type: string + type: object + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: + enum: + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + required: + - action + type: object + type: array + ingress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: + properties: + exact: + type: string + prefix: + type: string + type: object + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: + enum: + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + required: + - action + type: object + type: array + order: + type: number + performanceHints: + items: + enum: + - AssumeNeededOnEveryNode + type: string + type: array + selector: + type: string + serviceAccountSelector: + type: string + tier: + default: default + type: string + types: + items: + enum: + - Ingress + - Egress + type: string + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-list-type: set + type: object + required: + - metadata + - spec + type: object + selectableFields: + - jsonPath: .spec.tier + served: true + storage: true + subresources: {} diff --git a/api/config/crd/projectcalico.org_networksets.yaml b/api/config/crd/projectcalico.org_networksets.yaml new file mode 100644 index 00000000000..a6c708a3310 --- /dev/null +++ b/api/config/crd/projectcalico.org_networksets.yaml @@ -0,0 +1,40 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: networksets.projectcalico.org +spec: + group: projectcalico.org + names: + kind: NetworkSet + listKind: NetworkSetList + plural: networksets + singular: networkset + preserveUnknownFields: false + scope: Namespaced + versions: + - name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + type: object + required: + - metadata + - spec + type: object + served: true + storage: true diff --git a/api/config/crd/projectcalico.org_stagedglobalnetworkpolicies.yaml b/api/config/crd/projectcalico.org_stagedglobalnetworkpolicies.yaml new file mode 100644 index 00000000000..a94dbe90adf --- /dev/null +++ b/api/config/crd/projectcalico.org_stagedglobalnetworkpolicies.yaml @@ -0,0 +1,443 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: stagedglobalnetworkpolicies.projectcalico.org +spec: + group: projectcalico.org + names: + kind: StagedGlobalNetworkPolicy + listKind: StagedGlobalNetworkPolicyList + plural: stagedglobalnetworkpolicies + shortNames: + - sgnp + singular: stagedglobalnetworkpolicy + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.tier + name: Tier + type: string + - jsonPath: .spec.order + name: Order + type: number + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + applyOnForward: + type: boolean + doNotTrack: + type: boolean + egress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: + properties: + exact: + type: string + prefix: + type: string + type: object + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: + enum: + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + required: + - action + type: object + type: array + ingress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: + properties: + exact: + type: string + prefix: + type: string + type: object + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: + enum: + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + required: + - action + type: object + type: array + namespaceSelector: + type: string + order: + type: number + performanceHints: + items: + enum: + - AssumeNeededOnEveryNode + type: string + type: array + preDNAT: + type: boolean + selector: + type: string + serviceAccountSelector: + type: string + stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore + type: string + tier: + default: default + type: string + types: + items: + enum: + - Ingress + - Egress + type: string + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-list-type: set + type: object + required: + - metadata + - spec + type: object + selectableFields: + - jsonPath: .spec.tier + served: true + storage: true + subresources: {} diff --git a/api/config/crd/projectcalico.org_stagedkubernetesnetworkpolicies.yaml b/api/config/crd/projectcalico.org_stagedkubernetesnetworkpolicies.yaml new file mode 100644 index 00000000000..eee2cee2a08 --- /dev/null +++ b/api/config/crd/projectcalico.org_stagedkubernetesnetworkpolicies.yaml @@ -0,0 +1,259 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: stagedkubernetesnetworkpolicies.projectcalico.org +spec: + group: projectcalico.org + names: + kind: StagedKubernetesNetworkPolicy + listKind: StagedKubernetesNetworkPolicyList + plural: stagedkubernetesnetworkpolicies + shortNames: + - sknp + singular: stagedkubernetesnetworkpolicy + preserveUnknownFields: false + scope: Namespaced + versions: + - name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + egress: + items: + properties: + ports: + items: + properties: + endPort: + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + protocol: + type: string + type: object + type: array + x-kubernetes-list-type: atomic + to: + items: + properties: + ipBlock: + properties: + cidr: + type: string + except: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - cidr + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: array + ingress: + items: + properties: + from: + items: + properties: + ipBlock: + properties: + cidr: + type: string + except: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - cidr + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + ports: + items: + properties: + endPort: + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + protocol: + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: array + podSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + policyTypes: + items: + type: string + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-list-type: set + stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore + type: string + type: object + required: + - metadata + - spec + type: object + served: true + storage: true diff --git a/api/config/crd/projectcalico.org_stagednetworkpolicies.yaml b/api/config/crd/projectcalico.org_stagednetworkpolicies.yaml new file mode 100644 index 00000000000..aa932f82f79 --- /dev/null +++ b/api/config/crd/projectcalico.org_stagednetworkpolicies.yaml @@ -0,0 +1,436 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: stagednetworkpolicies.projectcalico.org +spec: + group: projectcalico.org + names: + kind: StagedNetworkPolicy + listKind: StagedNetworkPolicyList + plural: stagednetworkpolicies + shortNames: + - scnp + - snp + singular: stagednetworkpolicy + preserveUnknownFields: false + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.tier + name: Tier + type: string + - jsonPath: .spec.order + name: Order + type: number + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + egress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: + properties: + exact: + type: string + prefix: + type: string + type: object + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: + enum: + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + required: + - action + type: object + type: array + ingress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: + properties: + exact: + type: string + prefix: + type: string + type: object + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: + enum: + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + required: + - action + type: object + type: array + order: + type: number + performanceHints: + items: + enum: + - AssumeNeededOnEveryNode + type: string + type: array + selector: + type: string + serviceAccountSelector: + type: string + stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore + type: string + tier: + default: default + type: string + types: + items: + enum: + - Ingress + - Egress + type: string + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-list-type: set + type: object + required: + - metadata + - spec + type: object + selectableFields: + - jsonPath: .spec.tier + served: true + storage: true + subresources: {} diff --git a/api/config/crd/projectcalico.org_tiers.yaml b/api/config/crd/projectcalico.org_tiers.yaml new file mode 100644 index 00000000000..2ca939df5ae --- /dev/null +++ b/api/config/crd/projectcalico.org_tiers.yaml @@ -0,0 +1,71 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: tiers.projectcalico.org +spec: + group: projectcalico.org + names: + kind: Tier + listKind: TierList + plural: tiers + singular: tier + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - description: Order in which the tier is applied + jsonPath: .spec.order + name: Order + type: integer + - description: Default action for the tier + jsonPath: .spec.defaultAction + name: DefaultAction + type: string + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + defaultAction: + allOf: + - enum: + - Allow + - Deny + - Log + - Pass + - enum: + - Pass + - Deny + type: string + order: + type: number + type: object + required: + - metadata + - spec + type: object + x-kubernetes-validations: + - message: The 'kube-admin' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-admin' ? self.spec.defaultAction == + 'Pass' : true" + - message: The 'kube-baseline' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-baseline' ? self.spec.defaultAction + == 'Pass' : true" + - message: The 'default' tier must have default action 'Deny' + rule: + "self.metadata.name == 'default' ? self.spec.defaultAction == 'Deny' + : true" + served: true + storage: true + subresources: {} diff --git a/api/deps.txt b/api/deps.txt index 53aadecaf43..85a65473c95 100644 --- a/api/deps.txt +++ b/api/deps.txt @@ -2,47 +2,50 @@ This file contains the list of modules that this package depends on in order to trigger CI on changes -go 1.25.1 +go 1.25.7 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc -github.com/emicklei/go-restful/v3 v3.11.0 -github.com/fxamacker/cbor/v2 v2.7.0 +github.com/emicklei/go-restful v2.15.0+incompatible +github.com/emicklei/go-restful/v3 v3.12.2 +github.com/fxamacker/cbor/v2 v2.9.0 github.com/go-logr/logr v1.4.3 github.com/go-openapi/jsonpointer v0.21.0 github.com/go-openapi/jsonreference v0.20.2 github.com/go-openapi/swag v0.23.0 github.com/gogo/protobuf v1.3.2 -github.com/google/gnostic-models v0.6.9 -github.com/google/go-cmp v0.7.0 +github.com/google/gnostic-models v0.7.0 github.com/google/uuid v1.6.0 github.com/jinzhu/copier v0.4.0 github.com/josharian/intern v1.0.0 github.com/json-iterator/go v1.1.12 github.com/mailru/easyjson v0.7.7 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd -github.com/modern-go/reflect2 v1.0.2 +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 github.com/pkg/errors v0.9.1 +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/projectcalico/calico -github.com/spf13/pflag v1.0.7 +github.com/spf13/pflag v1.0.10 github.com/x448/float16 v0.8.4 -golang.org/x/net v0.43.0 -golang.org/x/oauth2 v0.30.0 -golang.org/x/sys v0.35.0 -golang.org/x/term v0.34.0 -golang.org/x/text v0.28.0 -golang.org/x/time v0.12.0 -google.golang.org/protobuf v1.36.7 +go.yaml.in/yaml/v2 v2.4.3 +go.yaml.in/yaml/v3 v3.0.4 +golang.org/x/net v0.49.0 +golang.org/x/oauth2 v0.34.0 +golang.org/x/sys v0.40.0 +golang.org/x/term v0.39.0 +golang.org/x/text v0.33.0 +golang.org/x/time v0.14.0 +google.golang.org/protobuf v1.36.10 gopkg.in/evanphx/json-patch.v4 v4.12.0 gopkg.in/inf.v0 v0.9.1 gopkg.in/yaml.v3 v3.0.1 -k8s.io/api v0.33.5 -k8s.io/apimachinery v0.33.5 -k8s.io/client-go v0.33.5 +k8s.io/api v0.34.3 +k8s.io/apimachinery v0.34.3 +k8s.io/client-go v0.34.3 k8s.io/klog v0.2.0 k8s.io/klog/v2 v2.130.1 -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff -k8s.io/utils v0.0.0-20241210054802-24370beab758 +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 sigs.k8s.io/randfill v1.0.0 -sigs.k8s.io/structured-merge-diff/v4 v4.6.0 +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 sigs.k8s.io/yaml v1.6.0 diff --git a/api/go.mod b/api/go.mod index 14b80fb5ae4..5f68b2c0295 100644 --- a/api/go.mod +++ b/api/go.mod @@ -1,83 +1,89 @@ module github.com/projectcalico/api -go 1.25.1 +go 1.25.7 require ( github.com/jinzhu/copier v0.4.0 - github.com/onsi/ginkgo v1.16.5 - github.com/onsi/gomega v1.37.0 - k8s.io/api v0.33.5 - k8s.io/apimachinery v0.33.5 - k8s.io/client-go v0.33.5 - k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff + github.com/onsi/ginkgo/v2 v2.28.1 + github.com/onsi/gomega v1.39.1 + k8s.io/api v0.34.3 + k8s.io/apimachinery v0.34.3 + k8s.io/client-go v0.34.3 + k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b ) require ( + github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/fsnotify/fsnotify v1.4.9 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gnostic-models v0.6.9 // indirect + github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect + github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect github.com/google/uuid v1.6.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/nxadm/tail v1.4.8 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/x448/float16 v0.8.4 // indirect - golang.org/x/net v0.41.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/mod v0.32.0 // indirect + golang.org/x/net v0.49.0 // indirect golang.org/x/oauth2 v0.27.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/term v0.32.0 // indirect - golang.org/x/text v0.26.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/term v0.39.0 // indirect + golang.org/x/text v0.33.0 // indirect golang.org/x/time v0.9.0 // indirect - google.golang.org/protobuf v1.36.6 // indirect + golang.org/x/tools v0.41.0 // indirect + google.golang.org/protobuf v1.36.7 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect ) replace ( - k8s.io/api => k8s.io/api v0.33.5 - k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.33.5 - k8s.io/apimachinery => k8s.io/apimachinery v0.33.5 - k8s.io/apiserver => k8s.io/apiserver v0.33.5 - k8s.io/cli-runtime => k8s.io/cli-runtime v0.33.5 - k8s.io/client-go => k8s.io/client-go v0.33.5 - k8s.io/cloud-provider => k8s.io/cloud-provider v0.33.5 - k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.33.5 - k8s.io/code-generator => k8s.io/code-generator v0.33.5 - k8s.io/component-base => k8s.io/component-base v0.33.5 - k8s.io/component-helpers => k8s.io/component-helpers v0.33.5 - k8s.io/controller-manager => k8s.io/controller-manager v0.33.5 - k8s.io/cri-api => k8s.io/cri-api v0.33.5 - k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.33.5 - k8s.io/externaljwt => k8s.io/externaljwt v0.33.5 - k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.33.5 - k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.33.5 - k8s.io/kube-proxy => k8s.io/kube-proxy v0.33.5 - k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.33.5 - k8s.io/kubectl => k8s.io/kubectl v0.33.5 - k8s.io/kubelet => k8s.io/kubelet v0.33.5 - k8s.io/metrics => k8s.io/metrics v0.33.5 - k8s.io/mount-utils => k8s.io/mount-utils v0.33.5 - k8s.io/node-api => k8s.io/node-api v0.33.5 - k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.33.5 + k8s.io/api => k8s.io/api v0.34.3 + k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.34.3 + k8s.io/apimachinery => k8s.io/apimachinery v0.34.3 + k8s.io/apiserver => k8s.io/apiserver v0.34.3 + k8s.io/cli-runtime => k8s.io/cli-runtime v0.34.3 + k8s.io/client-go => k8s.io/client-go v0.34.3 + k8s.io/cloud-provider => k8s.io/cloud-provider v0.34.3 + k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.34.3 + k8s.io/code-generator => k8s.io/code-generator v0.34.3 + k8s.io/component-base => k8s.io/component-base v0.34.3 + k8s.io/component-helpers => k8s.io/component-helpers v0.34.3 + k8s.io/controller-manager => k8s.io/controller-manager v0.34.3 + k8s.io/cri-api => k8s.io/cri-api v0.34.3 + k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.34.3 + k8s.io/externaljwt => k8s.io/externaljwt v0.34.3 + k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.34.3 + k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.34.3 + k8s.io/kube-proxy => k8s.io/kube-proxy v0.34.3 + k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.34.3 + k8s.io/kubectl => k8s.io/kubectl v0.34.3 + k8s.io/kubelet => k8s.io/kubelet v0.34.3 + k8s.io/metrics => k8s.io/metrics v0.34.3 + k8s.io/mount-utils => k8s.io/mount-utils v0.34.3 + k8s.io/node-api => k8s.io/node-api v0.34.3 + k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.34.3 ) diff --git a/api/go.sum b/api/go.sum index 885b4bc79b6..afb9c6c5238 100644 --- a/api/go.sum +++ b/api/go.sum @@ -1,17 +1,22 @@ +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= +github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= +github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= +github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= +github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= +github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE= +github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= @@ -20,37 +25,27 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= -github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= +github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -64,143 +59,131 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= +github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= +github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= +github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0= -github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= -github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= +github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= +github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= +github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= +github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= -golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= -golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= +google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.33.5 h1:YR+uhYj05jdRpcksv8kjSliW+v9hwXxn6Cv10aR8Juw= -k8s.io/api v0.33.5/go.mod h1:2gzShdwXKT5yPGiqrTrn/U/nLZ7ZyT4WuAj3XGDVgVs= -k8s.io/apimachinery v0.33.5 h1:NiT64hln4TQXeYR18/ES39OrNsjGz8NguxsBgp+6QIo= -k8s.io/apimachinery v0.33.5/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/client-go v0.33.5 h1:I8BdmQGxInpkMEnJvV6iG7dqzP3JRlpZZlib3OMFc3o= -k8s.io/client-go v0.33.5/go.mod h1:W8PQP4MxbM4ypgagVE65mUUqK1/ByQkSALF9tzuQ6u0= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= -sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= -sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/api/patches/0001-Add-nullable-to-IPAM-block-allocations-field.patch b/api/patches/0001-Add-nullable-to-IPAM-block-allocations-field.patch new file mode 100644 index 00000000000..ef91c963a35 --- /dev/null +++ b/api/patches/0001-Add-nullable-to-IPAM-block-allocations-field.patch @@ -0,0 +1,26 @@ +From 3cc87eafcb5657bf02bfc9a11573684b5577b30b Mon Sep 17 00:00:00 2001 +From: Casey Davenport +Date: Wed, 4 Feb 2026 14:36:34 -0800 +Subject: [PATCH] Add nullable to IPAM block allocations field + +--- + api/config/crd/projectcalico.org_ipamblocks.yaml | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/api/config/crd/projectcalico.org_ipamblocks.yaml b/api/config/crd/projectcalico.org_ipamblocks.yaml +index a6159008d1..20beb8de45 100644 +--- a/api/config/crd/projectcalico.org_ipamblocks.yaml ++++ b/api/config/crd/projectcalico.org_ipamblocks.yaml +@@ -44,6 +44,9 @@ spec: + allocations: + items: + type: integer ++ # TODO: This nullable is manually added in. We should update controller-gen ++ # to handle []*int properly itself. ++ nullable: true + type: array + attributes: + items: +-- +2.34.1 + diff --git a/api/pkg/apis/projectcalico/v3/bgpconfig.go b/api/pkg/apis/projectcalico/v3/bgpconfig.go index 5a30e6b4ff2..45bc04c9f9f 100644 --- a/api/pkg/apis/projectcalico/v3/bgpconfig.go +++ b/api/pkg/apis/projectcalico/v3/bgpconfig.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2021 Tigera, Inc. All rights reserved. +// Copyright (c) 2020-2025 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ const ( KindBGPConfigurationList = "BGPConfigurationList" ) +// +kubebuilder:validation:Enum=None;NodeIP type BindMode string const ( @@ -37,7 +38,7 @@ const ( // BGPConfigurationList is a list of BGPConfiguration resources. type BGPConfigurationList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` Items []BGPConfiguration `json:"items" protobuf:"bytes,2,rep,name=items"` } @@ -45,12 +46,12 @@ type BGPConfigurationList struct { // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - +// +kubebuilder:resource:scope=Cluster,shortName={bgpconfig,bgpconfigs} type BGPConfiguration struct { metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` - Spec BGPConfigurationSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` + Spec BGPConfigurationSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` } // ServiceLoadBalancerAggregation defines how LoadBalancer service IPs should be aggregated for BGP advertisement. @@ -66,25 +67,32 @@ const ( // BGPConfigurationSpec contains the values of the BGP configuration. type BGPConfigurationSpec struct { - // LogSeverityScreen is the log severity above which logs are sent to the stdout. [Default: INFO] + // LogSeverityScreen is the log severity above which logs are sent to the stdout. [Default: Info] + // +kubebuilder:default=Info + // +kubebuilder:validation:Pattern=`^(?i)(Trace|Debug|Info|Warning|Error|Fatal)?$` LogSeverityScreen string `json:"logSeverityScreen,omitempty" validate:"omitempty,logLevel" confignamev1:"loglevel"` // NodeToNodeMeshEnabled sets whether full node to node BGP mesh is enabled. [Default: true] + // +optional NodeToNodeMeshEnabled *bool `json:"nodeToNodeMeshEnabled,omitempty" validate:"omitempty" confignamev1:"node_mesh"` // ASNumber is the default AS number used by a node. [Default: 64512] + // +optional ASNumber *numorstring.ASNumber `json:"asNumber,omitempty" validate:"omitempty" confignamev1:"as_num"` // ServiceLoadBalancerIPs are the CIDR blocks for Kubernetes Service LoadBalancer IPs. // Kubernetes Service status.LoadBalancer.Ingress IPs will only be advertised if they are within one of these blocks. + // +listType=set ServiceLoadBalancerIPs []ServiceLoadBalancerIPBlock `json:"serviceLoadBalancerIPs,omitempty" validate:"omitempty,dive" confignamev1:"svc_loadbalancer_ips"` // ServiceExternalIPs are the CIDR blocks for Kubernetes Service External IPs. // Kubernetes Service ExternalIPs will only be advertised if they are within one of these blocks. + // +listType=set ServiceExternalIPs []ServiceExternalIPBlock `json:"serviceExternalIPs,omitempty" validate:"omitempty,dive" confignamev1:"svc_external_ips"` // ServiceClusterIPs are the CIDR blocks from which service cluster IPs are allocated. // If specified, Calico will advertise these blocks, as well as any cluster IPs within them. + // +listType=set ServiceClusterIPs []ServiceClusterIPBlock `json:"serviceClusterIPs,omitempty" validate:"omitempty,dive" confignamev1:"svc_cluster_ips"` // ServiceLoadBalancerAggregation controls how LoadBalancer service IPs are advertised. @@ -95,9 +103,11 @@ type BGPConfigurationSpec struct { ServiceLoadBalancerAggregation *ServiceLoadBalancerAggregation `json:"serviceLoadBalancerAggregation,omitempty" validate:"omitempty" confignamev1:"svc_loadbalancer_aggregation"` // Communities is a list of BGP community values and their arbitrary names for tagging routes. + // +listType=set Communities []Community `json:"communities,omitempty" validate:"omitempty,dive" confignamev1:"communities"` // PrefixAdvertisements contains per-prefix advertisement configuration. + // +listType=set PrefixAdvertisements []PrefixAdvertisement `json:"prefixAdvertisements,omitempty" validate:"omitempty,dive" confignamev1:"prefix_advertisements"` // ListenPort is the port where BGP protocol should listen. Defaults to 179 @@ -124,6 +134,7 @@ type BGPConfigurationSpec struct { // IgnoredInterfaces indicates the network interfaces that needs to be excluded when reading device routes. // +optional + // +listType=set IgnoredInterfaces []string `json:"ignoredInterfaces,omitempty" validate:"omitempty,dive,ignoredInterface"` // The virtual IPv4 address of the node with which its local workload is expected to peer. @@ -138,21 +149,28 @@ type BGPConfigurationSpec struct { } // ServiceLoadBalancerIPBlock represents a single allowed LoadBalancer IP CIDR block. +// +mapType=atomic type ServiceLoadBalancerIPBlock struct { + // +kubebuilder:validation:Format=cidr CIDR string `json:"cidr,omitempty" validate:"omitempty,net"` } // ServiceExternalIPBlock represents a single allowed External IP CIDR block. +// +mapType=atomic type ServiceExternalIPBlock struct { + // +kubebuilder:validation:Format=cidr CIDR string `json:"cidr,omitempty" validate:"omitempty,net"` } // ServiceClusterIPBlock represents a single allowed ClusterIP CIDR block. +// +mapType=atomic type ServiceClusterIPBlock struct { + // +kubebuilder:validation:Format=cidr CIDR string `json:"cidr,omitempty" validate:"omitempty,net"` } // Community contains standard or large community value and its name. +// +mapType=atomic type Community struct { // Name given to community value. Name string `json:"name,omitempty" validate:"required,name"` @@ -165,8 +183,10 @@ type Community struct { } // PrefixAdvertisement configures advertisement properties for the specified CIDR. +// +mapType=atomic type PrefixAdvertisement struct { // CIDR for which properties should be advertised. + // +kubebuilder:validation:Format=cidr CIDR string `json:"cidr,omitempty" validate:"required,net"` // Communities can be list of either community names already defined in `Specs.Communities` or community value of format `aa:nn` or `aa:nn:mm`. // For standard community use `aa:nn` format, where `aa` and `nn` are 16 bit number. diff --git a/api/pkg/apis/projectcalico/v3/bgpfilter.go b/api/pkg/apis/projectcalico/v3/bgpfilter.go index bf3538d1e57..fa07058a0ad 100644 --- a/api/pkg/apis/projectcalico/v3/bgpfilter.go +++ b/api/pkg/apis/projectcalico/v3/bgpfilter.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022-2023 Tigera, Inc. All rights reserved. +// Copyright (c) 2022-2025 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ const ( // BGPFilterList is a list of BGPFilter resources. type BGPFilterList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` Items []BGPFilter `json:"items" protobuf:"bytes,2,rep,name=items"` } @@ -37,12 +37,12 @@ type BGPFilterList struct { // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - +// +kubebuilder:resource:scope=Cluster type BGPFilter struct { metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` - Spec BGPFilterSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` + Spec BGPFilterSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` } // BGPFilterSpec contains the IPv4 and IPv6 filter rules of the BGP Filter. @@ -61,7 +61,9 @@ type BGPFilterSpec struct { } // BGPFilterRuleV4 defines a BGP filter rule consisting a single IPv4 CIDR block and a filter action for this CIDR. +// +mapType=atomic type BGPFilterRuleV4 struct { + // +kubebuilder:validation:Format=cidr CIDR string `json:"cidr,omitempty" validate:"omitempty,netv4"` PrefixLength *BGPFilterPrefixLengthV4 `json:"prefixLength,omitempty" validate:"omitempty"` @@ -76,7 +78,9 @@ type BGPFilterRuleV4 struct { } // BGPFilterRuleV6 defines a BGP filter rule consisting a single IPv6 CIDR block and a filter action for this CIDR. +// +mapType=atomic type BGPFilterRuleV6 struct { + // +kubebuilder:validation:Format=cidr CIDR string `json:"cidr,omitempty" validate:"omitempty,netv6"` PrefixLength *BGPFilterPrefixLengthV6 `json:"prefixLength,omitempty" validate:"omitempty"` @@ -90,6 +94,7 @@ type BGPFilterRuleV6 struct { Action BGPFilterAction `json:"action" validate:"required,filterAction"` } +// +mapType=atomic type BGPFilterPrefixLengthV4 struct { // +kubebuilder:validation:Minimum=0 // +kubebuilder:validation:Maximum=32 @@ -99,6 +104,7 @@ type BGPFilterPrefixLengthV4 struct { Max *int32 `json:"max,omitempty" validate:"omitempty,bgpFilterPrefixLengthV4"` } +// +mapType=atomic type BGPFilterPrefixLengthV6 struct { // +kubebuilder:validation:Minimum=0 // +kubebuilder:validation:Maximum=128 @@ -108,12 +114,14 @@ type BGPFilterPrefixLengthV6 struct { Max *int32 `json:"max,omitempty" validate:"omitempty,bgpFilterPrefixLengthV6"` } +// +kubebuilder:validation:Enum=RemotePeers type BGPFilterMatchSource string const ( BGPFilterSourceRemotePeers BGPFilterMatchSource = "RemotePeers" ) +// +kubebuilder:validation:Enum=Equal;NotEqual;In;NotIn type BGPFilterMatchOperator string const ( @@ -123,6 +131,7 @@ const ( NotIn BGPFilterMatchOperator = "NotIn" ) +// +kubebuilder:validation:Enum=Accept;Reject type BGPFilterAction string const ( diff --git a/api/pkg/apis/projectcalico/v3/bgpfilter_test.go b/api/pkg/apis/projectcalico/v3/bgpfilter_test.go new file mode 100644 index 00000000000..cd2a57b7747 --- /dev/null +++ b/api/pkg/apis/projectcalico/v3/bgpfilter_test.go @@ -0,0 +1,120 @@ +// Copyright (c) 2025 Tigera, Inc. All rights reserved. + +// 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 v3_test + +import ( + "context" + "os" + "testing" + + . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "github.com/projectcalico/api/pkg/client/clientset_generated/clientset" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/clientcmd" +) + +func setup(t *testing.T) (clientset.Interface, func()) { + // Register gomega with test. + RegisterTestingT(t) + + // Create a client. + cfg, err := clientcmd.BuildConfigFromFlags("", os.Getenv("KUBECONFIG")) + Expect(err).NotTo(HaveOccurred()) + c, err := clientset.NewForConfig(cfg) + Expect(err).NotTo(HaveOccurred()) + + return c, func() {} +} + +func TestBGPFilterValidation(t *testing.T) { + type bgpFilterTest struct { + name string + obj *v3.BGPFilter + valid bool + err string + } + tests := []bgpFilterTest{ + { + name: "basic valid BGPFilter", + obj: &v3.BGPFilter{ + ObjectMeta: metav1.ObjectMeta{Name: "valid-bgpfilter"}, + Spec: v3.BGPFilterSpec{ExportV4: []v3.BGPFilterRuleV4{{CIDR: "10.0.0.0/24", Action: v3.Accept}}}, + }, + valid: true, + }, + + { + name: "invalid BGPFilter with bad action", + obj: &v3.BGPFilter{ + ObjectMeta: metav1.ObjectMeta{Name: "invalid-bgpfilter"}, + Spec: v3.BGPFilterSpec{ExportV4: []v3.BGPFilterRuleV4{ + {CIDR: "10.0.0.0/24", Action: "InvalidAction"}, + }}, + }, + err: "spec.exportV4[0].action", + valid: false, + }, + + { + name: "invalid BGPFilter with bad CIDR", + obj: &v3.BGPFilter{ + ObjectMeta: metav1.ObjectMeta{Name: "invalid-bgpfilter"}, + Spec: v3.BGPFilterSpec{ImportV4: []v3.BGPFilterRuleV4{ + {CIDR: "invalid-cidr", Action: v3.Accept}, + }}, + }, + err: "spec.importV4[0].cidr", + valid: false, + }, + + { + name: "invalid BGPFilter with matchOperator", + obj: &v3.BGPFilter{ + ObjectMeta: metav1.ObjectMeta{Name: "invalid-bgpfilter"}, + Spec: v3.BGPFilterSpec{ExportV6: []v3.BGPFilterRuleV6{ + {CIDR: "fd00:1234:abcd::/64", MatchOperator: "InvalidOperator", Action: v3.Reject}, + }}, + }, + err: "spec.exportV6[0].matchOperator", + valid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c, cleanup := setup(t) + defer cleanup() + + ctx := context.Background() + g := NewGomegaWithT(t) + + // Try to create the BGPFilter object. + created, err := c.ProjectcalicoV3().BGPFilters().Create(ctx, tt.obj, metav1.CreateOptions{}) + if tt.valid { + defer func() { + err := c.ProjectcalicoV3().BGPFilters().Delete(ctx, created.Name, metav1.DeleteOptions{}) + g.Expect(err).NotTo(HaveOccurred(), "Expected BGPFilter to be deleted") + }() + g.Expect(err).NotTo(HaveOccurred(), "Expected BGPFilter to be valid") + } else { + g.Expect(err).To(HaveOccurred(), "Expected BGPFilter to be invalid") + if tt.err != "" { + g.Expect(err.Error()).To(ContainSubstring(tt.err)) + } + } + }) + } +} diff --git a/api/pkg/apis/projectcalico/v3/bgppeer.go b/api/pkg/apis/projectcalico/v3/bgppeer.go index 293609ec70b..97b5e9bd71b 100644 --- a/api/pkg/apis/projectcalico/v3/bgppeer.go +++ b/api/pkg/apis/projectcalico/v3/bgppeer.go @@ -31,20 +31,26 @@ const ( // BGPPeerList is a list of BGPPeer resources. type BGPPeerList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - Items []BGPPeer `json:"items" protobuf:"bytes,2,rep,name=items"` + metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` + Items []BGPPeer `json:"items" protobuf:"bytes,2,rep,name=items"` } // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Cluster +// +kubebuilder:printcolumn:name="Node Selector",type="string",JSONPath=".spec.nodeSelector",description="Selector for the nodes that should have this peering" +// +kubebuilder:printcolumn:name="Node",type="string",JSONPath=".spec.node",description="The node name identifying the Calico node instance that is targeted by this peer" +// +kubebuilder:printcolumn:name="Local ASN",type="string",JSONPath=".spec.localASNumber",description="The optional Local AS Number to use when peering with this remote peer" +// +kubebuilder:printcolumn:name="Peer Selector",type="string",JSONPath=".spec.peerSelector",description="Selector for the remote nodes to peer with" +// +kubebuilder:printcolumn:name="Peer IP",type="string",JSONPath=".spec.peerIP",description="The IP address of the peer followed by an optional port number to peer with" +// +kubebuilder:printcolumn:name="Peer ASN",type="string",JSONPath=".spec.asNumber",description="The AS Number of the peer" type BGPPeer struct { metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` - Spec BGPPeerSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` + Spec BGPPeerSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` } // BGPPeerSpec contains the specification for a BGPPeer resource. @@ -101,7 +107,6 @@ type BGPPeerSpec struct { // Set it to "Self" to configure "next hop self;" in "bird.cfg". // Set it to "Keep" to configure "next hop keep;" in "bird.cfg". // +optional - // +kubebuilder:validation:Enum=Auto;Self;Keep NextHopMode *NextHopMode `json:"nextHopMode,omitempty"` // Optional BGP password for the peerings generated by this BGPPeer resource. @@ -154,6 +159,7 @@ type BGPPeerSpec struct { ReversePeering *ReversePeering `json:"reversePeering,omitempty"` } +// +kubebuilder:validation:Enum=UseNodeIP;None type SourceAddress string const ( @@ -176,6 +182,7 @@ const ( NextHopModeKeep BindMode = "Keep" ) +// +kubebuilder:validation:Enum=Auto;Manual type ReversePeering string const ( diff --git a/api/pkg/apis/projectcalico/v3/blockaffinity.go b/api/pkg/apis/projectcalico/v3/blockaffinity.go index c90de6bbe2e..7ba74ae8be0 100644 --- a/api/pkg/apis/projectcalico/v3/blockaffinity.go +++ b/api/pkg/apis/projectcalico/v3/blockaffinity.go @@ -23,6 +23,7 @@ const ( KindBlockAffinityList = "BlockAffinityList" ) +// +kubebuilder:validation:Enum="";confirmed;pending;pendingDeletion type BlockAffinityState string const ( @@ -34,14 +35,16 @@ const ( // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Cluster,shortName={affinity,affinities} +// +kubebuilder:printcolumn:name="CIDR",type=string,JSONPath=".spec.cidr",description="The block CIDR" +// +kubebuilder:printcolumn:name="Node",type=string,JSONPath=".spec.node",description="The node the block is affine to" +// +kubebuilder:printcolumn:name="Type",type=string,JSONPath=".spec.type",description="The type of block affinity" // BlockAffinity maintains a block affinity's state type BlockAffinity struct { - metav1.TypeMeta `json:",inline"` - // Standard object's metadata. - metav1.ObjectMeta `json:"metadata,omitempty"` - // Specification of the BlockAffinity. - Spec BlockAffinitySpec `json:"spec,omitempty"` + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + Spec BlockAffinitySpec `json:"spec"` } // BlockAffinitySpec contains the specification for a BlockAffinity resource. @@ -52,13 +55,19 @@ type BlockAffinitySpec struct { // The node that this block affinity is assigned to. Node string `json:"node"` + // The type of affinity. + Type string `json:"type,omitempty"` + // The CIDR range this block affinity references. + // +kubebuilder:validation:Format=cidr + // +kubebuilder:validation:Required CIDR string `json:"cidr"` // Deleted indicates whether or not this block affinity is disabled and is // used as part of race-condition prevention. When set to true, clients // should treat this block as if it does not exist. - Deleted bool `json:"deleted,omitempty"` + // +kubebuilder:default=false + Deleted bool `json:"deleted"` } // +genclient:nonNamespaced diff --git a/api/pkg/apis/projectcalico/v3/clusterinfo.go b/api/pkg/apis/projectcalico/v3/clusterinfo.go index c28d81b2239..6850c8bc296 100644 --- a/api/pkg/apis/projectcalico/v3/clusterinfo.go +++ b/api/pkg/apis/projectcalico/v3/clusterinfo.go @@ -23,39 +23,44 @@ const ( KindClusterInformationList = "ClusterInformationList" ) -// +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // ClusterInformationList is a list of ClusterInformation objects. type ClusterInformationList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - Items []ClusterInformation `json:"items" protobuf:"bytes,2,rep,name=items"` + metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` + Items []ClusterInformation `json:"items" protobuf:"bytes,2,rep,name=items"` } // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Cluster,path=clusterinformations,shortName={clusterinfo,clusterinfos} +// +kubebuilder:printcolumn:name="Version",type=string,JSONPath=".spec.calicoVersion",description="The version of Calico that the cluster is running" +// +kubebuilder:printcolumn:name="Ready",type=boolean,JSONPath=".spec.datastoreReady",description="Whether the datastore is ready for use" +// +kubebuilder:printcolumn:name="Types",type=string,JSONPath=".spec.clusterType",description="The types associated with the cluster" type ClusterInformation struct { metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - Spec ClusterInformationSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` + metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` + Spec ClusterInformationSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` } // ClusterInformationSpec contains the values of describing the cluster. type ClusterInformationSpec struct { // ClusterGUID is the GUID of the cluster ClusterGUID string `json:"clusterGUID,omitempty" validate:"omitempty"` + // ClusterType describes the type of the cluster ClusterType string `json:"clusterType,omitempty" validate:"omitempty"` + // CalicoVersion is the version of Calico that the cluster is running CalicoVersion string `json:"calicoVersion,omitempty" validate:"omitempty"` + // DatastoreReady is used during significant datastore migrations to signal to components // such as Felix that it should wait before accessing the datastore. DatastoreReady *bool `json:"datastoreReady,omitempty"` + // Variant declares which variant of Calico should be active. Variant string `json:"variant,omitempty"` } diff --git a/api/pkg/apis/projectcalico/v3/felixconfig.go b/api/pkg/apis/projectcalico/v3/felixconfig.go index 81c6e1ef730..dec26e1d4de 100644 --- a/api/pkg/apis/projectcalico/v3/felixconfig.go +++ b/api/pkg/apis/projectcalico/v3/felixconfig.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2017-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -21,11 +21,13 @@ import ( // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Cluster +// +kubebuilder:resource:shortName={felixconfig,felixconfigs} // FelixConfigurationList contains a list of FelixConfiguration object. type FelixConfigurationList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` Items []FelixConfiguration `json:"items" protobuf:"bytes,2,rep,name=items"` } @@ -33,12 +35,13 @@ type FelixConfigurationList struct { // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Cluster type FelixConfiguration struct { metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` - Spec FelixConfigurationSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` + Spec FelixConfigurationSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` } const ( @@ -46,6 +49,7 @@ const ( KindFelixConfigurationList = "FelixConfigurationList" ) +// +kubebuilder:validation:Enum=Legacy;NFT;Auto type IptablesBackend string const ( @@ -56,11 +60,13 @@ const ( // NFTablesMode is the enum used to enable/disable nftables mode. // +enum +// +kubebuilder:validation:Enum=Disabled;Enabled;Auto type NFTablesMode string const ( - NFTablesModeEnabled = "Enabled" - NFTablesModeDisabled = "Disabled" + NFTablesModeEnabled NFTablesMode = "Enabled" + NFTablesModeDisabled NFTablesMode = "Disabled" + NFTablesModeAuto NFTablesMode = "Auto" ) // +kubebuilder:validation:Enum=DoNothing;Enable;Disable @@ -146,6 +152,16 @@ const ( NATOutgoingExclusionsIPPoolsAndHostIPs NATOutgoingExclusionsType = "IPPoolsAndHostIPs" ) +// +kubebuilder:validation:enum=RequireAndVerifyClientCert;RequireAnyClientCert;VerifyClientCertIfGiven;NoClientCert +type PrometheusMetricsClientAuthType string + +const ( + RequireAndVerifyClientCert PrometheusMetricsClientAuthType = "RequireAndVerifyClientCert" + RequireAnyClientCert PrometheusMetricsClientAuthType = "RequireAnyClientCert" + VerifyClientCertIfGiven PrometheusMetricsClientAuthType = "VerifyClientCertIfGiven" + NoClientCert PrometheusMetricsClientAuthType = "NoClientCert" +) + // FelixConfigurationSpec contains the values of the Felix configuration. type FelixConfigurationSpec struct { // UseInternalDataplaneDriver, if true, Felix will use its internal dataplane programming logic. If false, it @@ -194,23 +210,8 @@ type FelixConfigurationSpec struct { // +kubebuilder:validation:Pattern=`^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$` IptablesPostWriteCheckInterval *metav1.Duration `json:"iptablesPostWriteCheckInterval,omitempty" configv1timescale:"seconds" confignamev1:"IptablesPostWriteCheckIntervalSecs"` - // IptablesLockFilePath is the location of the iptables lock file. You may need to change this - // if the lock file is not in its standard location (for example if you have mapped it into Felix's - // container at a different path). [Default: /run/xtables.lock] - IptablesLockFilePath string `json:"iptablesLockFilePath,omitempty"` - - // IptablesLockTimeout is the time that Felix itself will wait for the iptables lock (rather than delegating the - // lock handling to the `iptables` command). - // - // Deprecated: `iptables-restore` v1.8+ always takes the lock, so enabling this feature results in deadlock. - // [Default: 0s disabled] - // +kubebuilder:validation:Type=string - // +kubebuilder:validation:Pattern=`^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$` - IptablesLockTimeout *metav1.Duration `json:"iptablesLockTimeout,omitempty" configv1timescale:"seconds" confignamev1:"IptablesLockTimeoutSecs"` - - // IptablesLockProbeInterval when IptablesLockTimeout is enabled: the time that Felix will wait between - // attempts to acquire the iptables lock if it is not available. Lower values make Felix more - // responsive when the lock is contended, but use more CPU. [Default: 50ms] + // IptablesLockProbeInterval configures the interval between attempts to claim + // the xtables lock. Shorter intervals are more responsive but use more CPU. [Default: 50ms] // +kubebuilder:validation:Type=string // +kubebuilder:validation:Pattern=`^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$` IptablesLockProbeInterval *metav1.Duration `json:"iptablesLockProbeInterval,omitempty" configv1timescale:"milliseconds" confignamev1:"IptablesLockProbeIntervalMillis"` @@ -325,9 +326,30 @@ type FelixConfigurationSpec struct { // +kubebuilder:validation:Pattern=`^(?i)(Drop|Reject)?$` IptablesFilterDenyAction string `json:"iptablesFilterDenyAction,omitempty" validate:"omitempty,dropReject"` - // LogPrefix is the log prefix that Felix uses when rendering LOG rules. [Default: calico-packet] + // LogPrefix is the log prefix that Felix uses when rendering LOG rules. It is possible to use the following specifiers + // to include extra information in the log prefix. + // - %t: Tier name. + // - %k: Kind (short names). + // - %n: Policy or profile name. + // - %p: Policy or profile name (namespace/name for namespaced kinds or just name for non namespaced kinds). + // Calico includes ": " characters at the end of the generated log prefix. + // Note that iptables shows up to 29 characters for the log prefix and nftables up to 127 characters. Extra characters are truncated. + // [Default: calico-packet] + // +kubebuilder:validation:Pattern=`^([a-zA-Z0-9%: /_-])*$` LogPrefix string `json:"logPrefix,omitempty"` + // LogActionRateLimit sets the rate of hitting a Log action. The value must be in the format "N/unit", + // where N is a number and unit is one of: second, minute, hour, or day. For example: "10/second" or "100/hour". + // +optional + // +kubebuilder:validation:Pattern=`^[1-9]\d{0,3}/(?:second|minute|hour|day)$` + LogActionRateLimit *string `json:"logActionRateLimit,omitempty"` + + // LogActionRateLimitBurst sets the rate limit burst of hitting a Log action when LogActionRateLimit is enabled. + // +optional + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=9999 + LogActionRateLimitBurst *int `json:"logActionRateLimitBurst,omitempty"` + // LogFilePath is the full path to the Felix log. Set to none to disable file logging. [Default: /var/log/calico/felix.log] LogFilePath string `json:"logFilePath,omitempty"` @@ -458,6 +480,22 @@ type FelixConfigurationSpec struct { // set to false. This reduces the number of metrics reported, reducing Prometheus load. [Default: true] PrometheusWireGuardMetricsEnabled *bool `json:"prometheusWireGuardMetricsEnabled,omitempty"` + // PrometheusMetricsCAFile defines the absolute path to the TLS CA certificate file used for securing the /metrics endpoint. + // This certificate must be valid and accessible by the calico-node process. + PrometheusMetricsCAFile *string `json:"prometheusMetricsCAFile,omitempty"` + + // PrometheusMetricsCertFile defines the absolute path to the TLS certificate file used for securing the /metrics endpoint. + // This certificate must be valid and accessible by the calico-node process. + PrometheusMetricsCertFile *string `json:"prometheusMetricsCertFile,omitempty"` + + // PrometheusMetricsKeyFile defines the absolute path to the private key file corresponding to the TLS certificate + // used for securing the /metrics endpoint. The private key must be valid and accessible by the calico-node process. + PrometheusMetricsKeyFile *string `json:"prometheusMetricsKeyFile,omitempty"` + + // PrometheusMetricsClientAuth specifies the client authentication type for the /metrics endpoint. + // This determines how the server validates client certificates. Default is "RequireAndVerifyClientCert". + PrometheusMetricsClientAuth *PrometheusMetricsClientAuthType `json:"prometheusMetricsClientAuth,omitempty" validate:"omitempty,oneof=RequireAndVerifyClientCert RequireAnyClientCert VerifyClientCertIfGiven NoClientCert"` + // FailsafeInboundHostPorts is a list of ProtoPort struct objects including UDP/TCP/SCTP ports and CIDRs that Felix will // allow incoming traffic to host endpoints on irrespective of the security policy. This is useful to avoid accidentally // cutting off a host with incorrect configuration. For backwards compatibility, if the protocol is not specified, @@ -598,8 +636,8 @@ type FelixConfigurationSpec struct { // iptables. [Default: false] GenericXDPEnabled *bool `json:"genericXDPEnabled,omitempty" confignamev1:"GenericXDPEnabled"` - // NFTablesMode configures nftables support in Felix. [Default: Disabled] - // +kubebuilder:validation:Enum=Disabled;Enabled;Auto + // NFTablesMode configures nftables support in Felix. [Default: Auto] + // +kubebuilder:default=Auto NFTablesMode *NFTablesMode `json:"nftablesMode,omitempty"` // NftablesRefreshInterval controls the interval at which Felix periodically refreshes the nftables rules. [Default: 90s] @@ -754,14 +792,9 @@ type FelixConfigurationSpec struct { // +kubebuilder:validation:Pattern=`^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$` BPFKubeProxyMinSyncPeriod *metav1.Duration `json:"bpfKubeProxyMinSyncPeriod,omitempty" validate:"omitempty" configv1timescale:"seconds"` - // BPFKubeProxyHealtzPort, in BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. + // BPFKubeProxyHealthzPort, in BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. // The health check server is used by external load balancers to determine if this node should receive traffic. [Default: 10256] - BPFKubeProxyHealtzPort *int `json:"bpfKubeProxyHealtzPort,omitempty" validate:"omitempty,gte=1,lte=65535" confignamev1:"BPFKubeProxyHealtzPort"` - - // BPFKubeProxyEndpointSlicesEnabled is deprecated and has no effect. BPF - // kube-proxy always accepts endpoint slices. This option will be removed in - // the next release. - BPFKubeProxyEndpointSlicesEnabled *bool `json:"bpfKubeProxyEndpointSlicesEnabled,omitempty" validate:"omitempty"` + BPFKubeProxyHealthzPort *int `json:"bpfKubeProxyHealthzPort,omitempty" validate:"omitempty,gte=1,lte=65535" confignamev1:"BPFKubeProxyHealthzPort"` // BPFPSNATPorts sets the range from which we randomly pick a port if there is a source port // collision. This should be within the ephemeral range as defined by RFC 6056 (1024–65535) and @@ -865,14 +898,12 @@ type FelixConfigurationSpec struct { // [Default: Continuous] FlowLogsPolicyEvaluationMode *FlowLogsPolicyEvaluationModeType `json:"flowLogsPolicyEvaluationMode,omitempty"` - // BPFRedirectToPeer controls which whether it is allowed to forward straight to the - // peer side of the workload devices. It is allowed for any host L2 devices by default - // (L2Only), but it breaks TCP dump on the host side of workload device as it bypasses - // it on ingress. Value of Enabled also allows redirection from L3 host devices like - // IPIP tunnel or Wireguard directly to the peer side of the workload's device. This - // makes redirection faster, however, it breaks tools like tcpdump on the peer side. - // Use Enabled with caution. [Default: L2Only] - //+kubebuilder:validation:Enum=Enabled;Disabled;L2Only + // BPFRedirectToPeer controls whether traffic may be forwarded directly to the peer side of a workload’s device. + // Note that the legacy "L2Only" option is now deprecated and if set it is treated like "Enabled. + // Setting this option to "Enabled" allows direct redirection (including from L3 host devices such as IPIP tunnels or WireGuard), + // which can improve redirection performance but causes the redirected packets to bypass the host‑side ingress path. + // As a result, packet‑capture tools on the host side of the workload device (for example, tcpdump) will not see that traffic. [Default: Enabled] + //+kubebuilder:validation:Enum=Enabled;Disabled BPFRedirectToPeer string `json:"bpfRedirectToPeer,omitempty"` // BPFAttachType controls how are the BPF programs at the network interfaces attached. @@ -1033,6 +1064,23 @@ type FelixConfigurationSpec struct { // RequireMTUFile specifies whether mtu file is required to start the felix. // Optional as to keep the same as previous behavior. [Default: false] RequireMTUFile *bool `json:"requireMTUFile,omitempty"` + + // BPFMaglevMaxEndpointsPerService is the maximum number of endpoints + // expected to be part of a single Maglev-enabled service. + // + // Influences the size of the per-service Maglev lookup-tables generated by Felix + // and thus the amount of memory reserved. + // + // [Default: 100] + // +optional + BPFMaglevMaxEndpointsPerService *int `json:"bpfMaglevMaxEndpointsPerService,omitempty" validate:"omitempty,gt=0,lte=3000"` + + // BPFMaglevMaxServices is the maximum number of expected Maglev-enabled + // services that Felix will allocate lookup-tables for. + // + // [Default: 100] + // +optional + BPFMaglevMaxServices *int `json:"bpfMaglevMaxServices,omitempty" validate:"omitempty,gt=0,lte=3000"` } type HealthTimeoutOverride struct { diff --git a/api/pkg/apis/projectcalico/v3/globalnetworkpolicy.go b/api/pkg/apis/projectcalico/v3/globalnetworkpolicy.go index c17de187810..28d32675b7a 100644 --- a/api/pkg/apis/projectcalico/v3/globalnetworkpolicy.go +++ b/api/pkg/apis/projectcalico/v3/globalnetworkpolicy.go @@ -25,11 +25,13 @@ const ( // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:selectablefield:JSONPath=`.spec.tier` +// +kubebuilder:resource:scope=Cluster // GlobalNetworkPolicyList is a list of Policy objects. type GlobalNetworkPolicyList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` Items []GlobalNetworkPolicy `json:"items" protobuf:"bytes,2,rep,name=items"` } @@ -37,12 +39,16 @@ type GlobalNetworkPolicyList struct { // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Cluster,shortName={gnp,cgnp} +// +kubebuilder:printcolumn:name="Tier",type=string,JSONPath=`.spec.tier` +// +kubebuilder:printcolumn:name="Order",type=number,JSONPath=`.spec.order` +// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` type GlobalNetworkPolicy struct { metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` - Spec GlobalNetworkPolicySpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` + Spec GlobalNetworkPolicySpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` } type GlobalNetworkPolicySpec struct { @@ -51,6 +57,7 @@ type GlobalNetworkPolicySpec struct { // security policies within the tier, the "default" tier is created automatically if it // does not exist, this means for deployments requiring only a single Tier, the tier name // may be omitted on all policy management requests. + // +kubebuilder:default=default Tier string `json:"tier,omitempty" validate:"omitempty,name"` // Order is an optional field that specifies the order in which the policy is applied. // Policies with higher "order" are applied after those with lower @@ -90,6 +97,7 @@ type GlobalNetworkPolicySpec struct { // deployment != "dev" // ! has(label_name) Selector string `json:"selector,omitempty" validate:"selector"` + // Types indicates whether this policy applies to ingress, or to egress, or to both. When // not explicitly specified (and so the value on creation is empty or nil), Calico defaults // Types according to what Ingress and Egress rules are present in the policy. The @@ -104,6 +112,9 @@ type GlobalNetworkPolicySpec struct { // // When the policy is read back again, Types will always be one of these values, never empty // or nil. + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=2 + // +listType=set Types []PolicyType `json:"types,omitempty" validate:"omitempty,dive,policyType"` // DoNotTrack indicates whether packets matched by the rules in this policy should go through @@ -111,8 +122,10 @@ type GlobalNetworkPolicySpec struct { // this policy are applied before any data plane connection tracking, and packets allowed by // this policy are marked as not to be tracked. DoNotTrack bool `json:"doNotTrack,omitempty"` + // PreDNAT indicates to apply the rules in this policy before any DNAT. PreDNAT bool `json:"preDNAT,omitempty"` + // ApplyOnForward indicates to apply the rules in this policy on forward traffic. ApplyOnForward bool `json:"applyOnForward,omitempty"` diff --git a/api/pkg/apis/projectcalico/v3/globalnetworkset.go b/api/pkg/apis/projectcalico/v3/globalnetworkset.go index 136d8edbcb6..5c23d8bfe8f 100644 --- a/api/pkg/apis/projectcalico/v3/globalnetworkset.go +++ b/api/pkg/apis/projectcalico/v3/globalnetworkset.go @@ -29,7 +29,7 @@ const ( // GlobalNetworkSetList is a list of NetworkSet objects. type GlobalNetworkSetList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` Items []GlobalNetworkSet `json:"items" protobuf:"bytes,2,rep,name=items"` } @@ -37,17 +37,20 @@ type GlobalNetworkSetList struct { // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Cluster,scope=Cluster,shortName={gns} +// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` type GlobalNetworkSet struct { metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` - Spec GlobalNetworkSetSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` + Spec GlobalNetworkSetSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` } // GlobalNetworkSetSpec contains the specification for a NetworkSet resource. type GlobalNetworkSetSpec struct { // The list of IP networks that belong to this set. + // +listType=set Nets []string `json:"nets,omitempty" validate:"omitempty,dive,cidr"` } diff --git a/api/pkg/apis/projectcalico/v3/hostendpoint.go b/api/pkg/apis/projectcalico/v3/hostendpoint.go index 7a2b63d5a0c..6d833642a62 100644 --- a/api/pkg/apis/projectcalico/v3/hostendpoint.go +++ b/api/pkg/apis/projectcalico/v3/hostendpoint.go @@ -30,7 +30,7 @@ const ( // HostEndpointList is a list of HostEndpoint objects. type HostEndpointList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` Items []HostEndpoint `json:"items" protobuf:"bytes,2,rep,name=items"` } @@ -38,18 +38,22 @@ type HostEndpointList struct { // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Cluster,shortName={hep,heps} +// +kubebuilder:printcolumn:name="Node",type=string,JSONPath=".spec.node",description="The node name identifying the Calico node instance that is targeted by this HostEndpoint" +// +kubebuilder:printcolumn:name="Interface",type=string,JSONPath=".spec.interfaceName",description="The name of the interface that is targeted by this HostEndpoint" type HostEndpoint struct { metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` - Spec HostEndpointSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` + Spec HostEndpointSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` } // HostEndpointSpec contains the specification for a HostEndpoint resource. type HostEndpointSpec struct { // The node name identifying the Calico node instance. Node string `json:"node,omitempty" validate:"omitempty,name"` + // Either "*", or the name of a specific Linux interface to apply policy to; or empty. "*" // indicates that this HostEndpoint governs all traffic to, from or through the default // network namespace of the host named by the "Node" field; entering and leaving that @@ -65,6 +69,7 @@ type HostEndpointSpec struct { // Note: Only some kinds of policy are implemented for "*" HostEndpoints; initially just // pre-DNAT policy. Please check Calico documentation for the latest position. InterfaceName string `json:"interfaceName,omitempty" validate:"omitempty,interface"` + // The expected IP addresses (IPv4 and IPv6) of the endpoint. // If "InterfaceName" is not present, Calico will look for an interface matching any // of the IPs in the list and apply policy to that. @@ -74,11 +79,15 @@ type HostEndpointSpec struct { // endpoints, the ExpectedIPs field is used for that purpose. (If only the interface // name is specified, Calico does not learn the IPs of the interface for use in match // criteria.) + // +listType=set ExpectedIPs []string `json:"expectedIPs,omitempty" validate:"omitempty,dive,ip"` + // A list of identifiers of security Profile objects that apply to this endpoint. Each // profile is applied in the order that they appear in this list. Profile rules are applied // after the selector-based security policy. + // +listType=set Profiles []string `json:"profiles,omitempty" validate:"omitempty,dive,name"` + // Ports contains the endpoint's named ports, which may be referenced in security policy rules. Ports []EndpointPort `json:"ports,omitempty" validate:"dive"` } @@ -86,7 +95,10 @@ type HostEndpointSpec struct { type EndpointPort struct { Name string `json:"name" validate:"portName"` Protocol numorstring.Protocol `json:"protocol"` - Port uint16 `json:"port" validate:"gt=0"` + + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=65535 + Port uint16 `json:"port" validate:"gt=0"` } // NewHostEndpoint creates a new (zeroed) HostEndpoint struct with the TypeMetadata initialised to the current diff --git a/api/pkg/apis/projectcalico/v3/ipamblocks.go b/api/pkg/apis/projectcalico/v3/ipamblocks.go new file mode 100644 index 00000000000..5c3bf18a6b8 --- /dev/null +++ b/api/pkg/apis/projectcalico/v3/ipamblocks.go @@ -0,0 +1,103 @@ +// Copyright (c) 2019-2020 Tigera, Inc. All rights reserved. + +// 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 v3 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Cluster +// +kubebuilder:printcolumn:name="CIDR",type=string,JSONPath=".spec.cidr",description="The block CIDR" +// +kubebuilder:printcolumn:name="Affinity",type=string,JSONPath=".spec.affinity",description="The block affinity" +// +kubebuilder:printcolumn:name="ClaimTime",type=date,JSONPath=".spec.affinityClaimTime",description="The time at which affinity was claimed" + +// +k8s:openapi-gen=true +type IPAMBlock struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + Spec IPAMBlockSpec `json:"spec"` +} + +// IPAMBlockSpec contains the specification for an IPAMBlock resource. +type IPAMBlockSpec struct { + // The block's CIDR. + // +kubebuilder:validation:Format=cidr + CIDR string `json:"cidr"` + + // Affinity of the block, if this block has one. If set, it will be of the form + // "host:" or "virtual:". If not set, this block is not affine to a host. + // +kubebuilder:validation:Pattern=`^(host|virtual):[a-zA-Z0-9\.\-_]+$` + // +optional + Affinity *string `json:"affinity,omitempty"` + + // Time at which affinity was claimed. + // +optional + AffinityClaimTime *metav1.Time `json:"affinityClaimTime,omitempty"` + + // Array of allocations in-use within this block. nil entries mean the allocation is free. + // For non-nil entries at index i, the index is the ordinal of the allocation within this block + // and the value is the index of the associated attributes in the Attributes array. + Allocations []*int `json:"allocations"` + + // Unallocated is an ordered list of allocations which are free in the block. + Unallocated []int `json:"unallocated"` + + // Attributes is an array of arbitrary metadata associated with allocations in the block. To find + // attributes for a given allocation, use the value of the allocation's entry in the Allocations array + // as the index of the element in this array. + Attributes []AllocationAttribute `json:"attributes"` + + // We store a sequence number that is updated each time the block is written. + // Each allocation will also store the sequence number of the block at the time of its creation. + // When releasing an IP, passing the sequence number associated with the allocation allows us + // to protect against a race condition and ensure the IP hasn't been released and re-allocated + // since the release request. + // + // +kubebuilder:default=0 + // +optional + SequenceNumber uint64 `json:"sequenceNumber"` + + // Map of allocated ordinal within the block to sequence number of the block at + // the time of allocation. Kubernetes does not allow numerical keys for maps, so + // the key is cast to a string. + // +optional + SequenceNumberForAllocation map[string]uint64 `json:"sequenceNumberForAllocation"` + + // Deleted is an internal boolean used to workaround a limitation in the Kubernetes API whereby + // deletion will not return a conflict error if the block has been updated. It should not be set manually. + // +optional + Deleted bool `json:"deleted"` + + // StrictAffinity on the IPAMBlock is deprecated and no longer used by the code. Use IPAMConfig StrictAffinity instead. + DeprecatedStrictAffinity bool `json:"strictAffinity"` +} + +type AllocationAttribute struct { + HandleID *string `json:"handle_id,omitempty"` + ActiveOwnerAttrs map[string]string `json:"secondary,omitempty"` + AlternateOwnerAttrs map[string]string `json:"alternate,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// IPAMBlockList contains a list of IPAMBlock resources. +type IPAMBlockList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + Items []IPAMBlock `json:"items"` +} diff --git a/api/pkg/apis/projectcalico/v3/ipamconfig.go b/api/pkg/apis/projectcalico/v3/ipamconfig.go index d6e16847d74..c49eb28ec27 100644 --- a/api/pkg/apis/projectcalico/v3/ipamconfig.go +++ b/api/pkg/apis/projectcalico/v3/ipamconfig.go @@ -25,11 +25,12 @@ const ( // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Cluster // IPAMConfigurationList contains a list of IPAMConfiguration resources. type IPAMConfigurationList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` Items []IPAMConfiguration `json:"items" protobuf:"bytes,2,rep,name=items"` } @@ -37,23 +38,56 @@ type IPAMConfigurationList struct { // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Cluster,shortName={ipamconfig,ipamconfigs} +// +kubebuilder:printcolumn:name="StrictAffinity",type=boolean,JSONPath=".spec.strictAffinity",description="Whether borrowing IP addresses is allowed" +// +kubebuilder:printcolumn:name="MaxBlocksPerHost",type=integer,JSONPath=".spec.maxBlocksPerHost",description="The max number of blocks that can be affine to each host" +// +kubebuilder:printcolumn:name="AutoAllocateBlocks",type=boolean,JSONPath=".spec.autoAllocateBlocks",description="Whether or not to auto allocate blocks to hosts" // IPAMConfiguration contains information about a block for IP address assignment. type IPAMConfiguration struct { metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` - Spec IPAMConfigurationSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` + Spec IPAMConfigurationSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` } -// IPAMConfigurationSpec contains the specification for an IPPool resource. +// VMAddressPersistence controls whether KubeVirt VirtualMachine workloads +// maintain persistent IP addresses across VM lifecycle events. +type VMAddressPersistence string + +const ( + VMAddressPersistenceEnabled VMAddressPersistence = "Enabled" + VMAddressPersistenceDisabled VMAddressPersistence = "Disabled" +) + +// IPAMConfigurationSpec contains the specification for an IPAMConfiguration resource. type IPAMConfigurationSpec struct { // When StrictAffinity is true, borrowing IP addresses is not allowed. - StrictAffinity bool `json:"strictAffinity" validate:"required"` + // +kubebuilder:default=false + StrictAffinity bool `json:"strictAffinity"` // MaxBlocksPerHost, if non-zero, is the max number of blocks that can be // affine to each host. + // + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=1000000 + // +kubebuilder:default=0 + // +optional MaxBlocksPerHost int32 `json:"maxBlocksPerHost,omitempty"` + + // Whether or not to auto allocate blocks to hosts. + // +kubebuilder:default=true + AutoAllocateBlocks bool `json:"autoAllocateBlocks"` + + // KubeVirtVMAddressPersistence controls whether KubeVirt VirtualMachine workloads + // maintain persistent IP addresses across VM lifecycle events (reboot, migration, pod eviction). + // When Enabled, Calico automatically ensures that KubeVirt VMs retain their IP addresses + // when their underlying pods are recreated during VM operations. + // When Disabled, VMs receive new IP addresses whenever their pods are recreated. + // Defaults to Enabled if not specified. + // +kubebuilder:validation:Enum=Enabled;Disabled + // +optional + KubeVirtVMAddressPersistence *VMAddressPersistence `json:"kubeVirtVMAddressPersistence,omitempty"` } // NewIPAMConfiguration creates a new (zeroed) IPAMConfiguration struct with the TypeMetadata initialised to the current @@ -61,7 +95,7 @@ type IPAMConfigurationSpec struct { func NewIPAMConfiguration() *IPAMConfiguration { return &IPAMConfiguration{ TypeMeta: metav1.TypeMeta{ - Kind: KindIPPool, + Kind: KindIPAMConfiguration, APIVersion: GroupVersionCurrent, }, } diff --git a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom/globalbgpconfig.go b/api/pkg/apis/projectcalico/v3/ipamhandle.go similarity index 51% rename from libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom/globalbgpconfig.go rename to api/pkg/apis/projectcalico/v3/ipamhandle.go index 1b21b53d0c3..56d62af1f8a 100644 --- a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom/globalbgpconfig.go +++ b/api/pkg/apis/projectcalico/v3/ipamhandle.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. +// Copyright (c) 2019-2020 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,33 +12,36 @@ // See the License for the specific language governing permissions and // limitations under the License. -package custom +package v3 -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:printcolumn:name="ID",type=string,JSONPath=".spec.handleID",description="The handle ID" -type GlobalBGPConfig struct { +// +k8s:openapi-gen=true +// +kubebuilder:resource:scope=Cluster +type IPAMHandle struct { metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - Spec GlobalBGPConfigSpec `json:"spec"` + metav1.ObjectMeta `json:"metadata"` + Spec IPAMHandleSpec `json:"spec"` } -type GlobalBGPConfigSpec struct { - // The reason we have Name field in Spec is because k8s metadata - // name field requires the string to be lowercase, so Name field - // in Spec is to preserve the casing. - Name string `json:"name"` - Value string `json:"value"` +// IPAMHandleSpec contains the specification for an IPAMHandle resource. +type IPAMHandleSpec struct { + HandleID string `json:"handleID"` + Block map[string]int `json:"block"` + + // +optional + Deleted bool `json:"deleted"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type GlobalBGPConfigList struct { +// IPAMHandleList contains a list of IPAMHandle resources. +type IPAMHandleList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata"` - Items []GlobalBGPConfig `json:"items"` + Items []IPAMHandle `json:"items"` } diff --git a/api/pkg/apis/projectcalico/v3/ippool.go b/api/pkg/apis/projectcalico/v3/ippool.go index e8be21059a4..fa526a55ae4 100644 --- a/api/pkg/apis/projectcalico/v3/ippool.go +++ b/api/pkg/apis/projectcalico/v3/ippool.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017, 2021 Tigera, Inc. All rights reserved. +// Copyright (c) 2017-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -25,11 +25,12 @@ const ( // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Cluster,shortName={ipp,ipps,pool,pools} // IPPoolList contains a list of IPPool resources. type IPPoolList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` Items []IPPool `json:"items" protobuf:"bytes,2,rep,name=items"` } @@ -37,25 +38,60 @@ type IPPoolList struct { // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Cluster +// +kubebuilder:printcolumn:name="CIDR",type=string,JSONPath=".spec.cidr",description="The pool CIDR" +// +kubebuilder:printcolumn:name="VXLAN",type=string,JSONPath=".spec.vxlanMode",description="The VXLAN mode for this pool" +// +kubebuilder:printcolumn:name="IPIP",type=string,JSONPath=".spec.ipipMode",description="The IPIP mode for this pool" +// +kubebuilder:printcolumn:name="NAT",type=boolean,JSONPath=".spec.natOutgoing",description="Whether outgoing NAT is enabled for this pool" +// +kubebuilder:printcolumn:name="Allocatable",type="string",JSONPath=".status.conditions[?(@.type=='Allocatable')].status",description="Whether or not this pool is available for IP allocations" +// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=".metadata.creationTimestamp",description="The age of the pool" type IPPool struct { metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` - Spec IPPoolSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` + Spec IPPoolSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` + + // +optional + Status *IPPoolStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` +} + +const ( + // IPPoolConditionReady indicates whether the pool is ready to be used for IP address assignment. + IPPoolConditionAllocatable = "Allocatable" +) + +const ( + // IPPoolReasonCIDRInvalid indicates that the pool CIDR overlaps with another IP pool. + IPPoolReasonCIDROverlap = "CIDROverlap" + + // IPPoolReasonTerminating indicates that the pool is terminating and cannot be used for new IP address assignments. + IPPoolReasonTerminating = "Terminating" + + // IPPoolReasonDisabled indicates the pool is administratively disabled and cannot be used for new IP address assignments. + IPPoolReasonDisabled = "PoolDisabled" + + // IPPoolReasonOK indicates that the pool is ready to be used for IP address assignment. + IPPoolReasonOK = "OK" +) + +type IPPoolStatus struct { + Conditions []metav1.Condition `json:"conditions,omitempty" protobuf:"bytes,1,rep,name=conditions"` } // IPPoolSpec contains the specification for an IPPool resource. type IPPoolSpec struct { // The pool CIDR. + // +kubebuilder:validation:Required + // +kubebuilder:validation:Format=cidr CIDR string `json:"cidr" validate:"net"` - // Contains configuration for VXLAN tunneling for this pool. If not specified, - // then this is defaulted to "Never" (i.e. VXLAN tunneling is disabled). + // Contains configuration for VXLAN tunneling for this pool. VXLANMode VXLANMode `json:"vxlanMode,omitempty" validate:"omitempty,vxlanMode"` - // Contains configuration for IPIP tunneling for this pool. If not specified, - // then this is defaulted to "Never" (i.e. IPIP tunneling is disabled). + // Contains configuration for IPIP tunneling for this pool. + // For IPv6 pools, IPIP tunneling must be disabled. IPIPMode IPIPMode `json:"ipipMode,omitempty" validate:"omitempty,ipIpMode"` // When natOutgoing is true, packets sent from Calico networked containers in @@ -69,6 +105,10 @@ type IPPoolSpec struct { DisableBGPExport bool `json:"disableBGPExport,omitempty" validate:"omitempty"` // The block size to use for IP address assignments from this pool. Defaults to 26 for IPv4 and 122 for IPv6. + // The block size must be between 0 and 32 for IPv4 and between 0 and 128 for IPv6. It must also be smaller than + // or equal to the size of the pool CIDR. + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=128 BlockSize int `json:"blockSize,omitempty"` // Allows IPPool to allocate for a specific node by label selector. @@ -78,31 +118,40 @@ type IPPoolSpec struct { // If specified, both namespaceSelector and nodeSelector must match for the pool to be used. NamespaceSelector string `json:"namespaceSelector,omitempty" validate:"omitempty,selector"` - // Deprecated: this field is only used for APIv1 backwards compatibility. - // Setting this field is not allowed, this field is for internal use only. - IPIP *IPIPConfiguration `json:"ipip,omitempty" validate:"omitempty,mustBeNil"` - - // Deprecated: this field is only used for APIv1 backwards compatibility. - // Setting this field is not allowed, this field is for internal use only. - NATOutgoingV1 bool `json:"nat-outgoing,omitempty" validate:"omitempty,mustBeFalse"` - // AllowedUse controls what the IP pool will be used for. If not specified or empty, defaults to // ["Tunnel", "Workload"] for back-compatibility + // +listType=set AllowedUses []IPPoolAllowedUse `json:"allowedUses,omitempty" validate:"omitempty"` // Determines the mode how IP addresses should be assigned from this pool // +optional + // +kubebuilder:default=Automatic AssignmentMode *AssignmentMode `json:"assignmentMode,omitempty" validate:"omitempty,assignmentMode"` } +// IPPoolAllowedUse defines the allowed uses for an IP pool. +// It can be one of "Workload", "Tunnel", or "LoadBalancer". +// - "Workload" means the pool is used for workload IP addresses. +// - "Tunnel" means the pool is used for tunnel IP addresses. +// - "LoadBalancer" means the pool is used for load balancer IP addresses. +// +kubebuilder:validation:Enum=Workload;Tunnel;LoadBalancer type IPPoolAllowedUse string const ( - IPPoolAllowedUseWorkload IPPoolAllowedUse = "Workload" - IPPoolAllowedUseTunnel IPPoolAllowedUse = "Tunnel" + IPPoolAllowedUseWorkload IPPoolAllowedUse = "Workload" + IPPoolAllowedUseTunnel IPPoolAllowedUse = "Tunnel" + + // IPPoolAllowedUseLoadBalancer designates that the pool is used for load balancer IP addresses. + // Not compatible with IPIP or VXLAN. IPPoolAllowedUseLoadBalancer IPPoolAllowedUse = "LoadBalancer" ) +// VXLANMode defines the mode of VXLAN tunneling for an IP pool. +// It can be one of "Never", "Always", or "CrossSubnet". +// - "Never" means VXLAN tunneling is disabled for this pool. +// - "Always" means VXLAN tunneling is used for all traffic to this pool. +// - "CrossSubnet" means VXLAN tunneling is used only when the destination node is on a different subnet. +// +kubebuilder:validation:Enum=Never;Always;CrossSubnet type VXLANMode string const ( @@ -111,6 +160,12 @@ const ( VXLANModeCrossSubnet VXLANMode = "CrossSubnet" ) +// IPIPMode defines the mode of IPIP tunneling for an IP pool. +// It can be one of "Never", "Always", or "CrossSubnet". +// - "Never" means IPIP tunneling is disabled for this pool. +// - "Always" means IPIP tunneling is used for all traffic to this pool. +// - "CrossSubnet" means IPIP tunneling is used only when the destination node is on a different subnet. +// +kubebuilder:validation:Enum=Never;Always;CrossSubnet type IPIPMode string const ( @@ -124,7 +179,7 @@ const ( type EncapMode string const ( - Undefined EncapMode = "" + Never EncapMode = "" Always EncapMode = "always" CrossSubnet EncapMode = "cross-subnet" ) diff --git a/api/pkg/apis/projectcalico/v3/ipreservation.go b/api/pkg/apis/projectcalico/v3/ipreservation.go index f8b43e96c37..70ada9bacef 100644 --- a/api/pkg/apis/projectcalico/v3/ipreservation.go +++ b/api/pkg/apis/projectcalico/v3/ipreservation.go @@ -25,11 +25,12 @@ const ( // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Cluster // IPReservationList contains a list of IPReservation resources. type IPReservationList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` Items []IPReservation `json:"items" protobuf:"bytes,2,rep,name=items"` } @@ -37,6 +38,7 @@ type IPReservationList struct { // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Cluster // IPReservation allows certain IP addresses to be reserved (i.e. prevented from being allocated) by Calico // IPAM. Reservations only block new allocations, they do not cause existing IP allocations to be released. @@ -45,14 +47,16 @@ type IPReservationList struct { // to find a non-reserved IP. type IPReservation struct { metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` - Spec IPReservationSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` + Spec IPReservationSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` } // IPReservationSpec contains the specification for an IPReservation resource. type IPReservationSpec struct { // ReservedCIDRs is a list of CIDRs and/or IP addresses that Calico IPAM will exclude from new allocations. + // +listType=set + // +kubebuilder:validation:Format=cidr ReservedCIDRs []string `json:"reservedCIDRs,omitempty" validate:"cidrs,omitempty"` } diff --git a/api/pkg/apis/projectcalico/v3/kubecontrollersconfig.go b/api/pkg/apis/projectcalico/v3/kubecontrollersconfig.go index 692be53b325..1e121daf5f1 100644 --- a/api/pkg/apis/projectcalico/v3/kubecontrollersconfig.go +++ b/api/pkg/apis/projectcalico/v3/kubecontrollersconfig.go @@ -25,11 +25,12 @@ const ( // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Cluster // KubeControllersConfigurationList contains a list of KubeControllersConfiguration object. type KubeControllersConfigurationList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` Items []KubeControllersConfiguration `json:"items" protobuf:"bytes,2,rep,name=items"` } @@ -37,27 +38,48 @@ type KubeControllersConfigurationList struct { // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Cluster,shortName={kcc,kccs} +// +kubebuilder:subresource:status type KubeControllersConfiguration struct { metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` - Spec KubeControllersConfigurationSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` - Status KubeControllersConfigurationStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` + Spec KubeControllersConfigurationSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` + + // +optional + Status KubeControllersConfigurationStatus `json:"status" protobuf:"bytes,3,opt,name=status"` } +// ControllerMode is used to enable or disable a controller. +// +kubebuilder:validation:Enum=Disabled;Enabled +type ControllerMode string + +const ( + ControllerDisabled ControllerMode = "Disabled" + ControllerEnabled ControllerMode = "Enabled" +) + // KubeControllersConfigurationSpec contains the values of the Kubernetes controllers configuration. type KubeControllersConfigurationSpec struct { // LogSeverityScreen is the log severity above which logs are sent to the stdout. [Default: Info] + // Valid values are: "None", "Debug", "Info", "Warning", "Error", "Fatal", "Panic". + // +kubebuilder:validation:Enum=None;Debug;Info;Warning;Error;Fatal;Panic LogSeverityScreen string `json:"logSeverityScreen,omitempty" validate:"omitempty,logLevel"` // HealthChecks enables or disables support for health checks [Default: Enabled] + // Valid values are: "Enabled", "Disabled". + // +kubebuilder:validation:Enum=Enabled;Disabled + // +kubebuilder:default=Enabled HealthChecks string `json:"healthChecks,omitempty" validate:"omitempty,oneof=Enabled Disabled"` // EtcdV3CompactionPeriod is the period between etcdv3 compaction requests. Set to 0 to disable. [Default: 10m] EtcdV3CompactionPeriod *metav1.Duration `json:"etcdV3CompactionPeriod,omitempty" validate:"omitempty"` // PrometheusMetricsPort is the TCP port that the Prometheus metrics server should bind to. Set to 0 to disable. [Default: 9094] + // Valid values are: 0-65535. + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=65535 PrometheusMetricsPort *int `json:"prometheusMetricsPort,omitempty"` // Controllers enables and configures individual Kubernetes controllers @@ -65,6 +87,10 @@ type KubeControllersConfigurationSpec struct { // DebugProfilePort configures the port to serve memory and cpu profiles on. If not specified, profiling // is disabled. + // Valid values are: 0-65535. + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=65535 + // +optional DebugProfilePort *int32 `json:"debugProfilePort,omitempty"` } @@ -87,6 +113,16 @@ type ControllersConfig struct { // LoadBalancer enables and configures the LoadBalancer controller. Enabled by default, set to nil to disable. LoadBalancer *LoadBalancerControllerConfig `json:"loadBalancer,omitempty"` + + // Migration enables and configures migration controllers. + Migration *MigrationControllerConfig `json:"policyMigration,omitempty"` +} + +type MigrationControllerConfig struct { + // PolicyNameMigrator enables or disables the Policy Name Migrator, which migrates + // old-style Calico backend policy names to use v3 style names. + // +kubebuilder:default=Enabled + PolicyNameMigrator ControllerMode `json:"enabled,omitempty" validate:"omitempty,oneof=Enabled Disabled"` } // NodeControllerConfig configures the node controller, which automatically cleans up configuration @@ -96,6 +132,8 @@ type NodeControllerConfig struct { ReconcilerPeriod *metav1.Duration `json:"reconcilerPeriod,omitempty" validate:"omitempty"` // SyncLabels controls whether to copy Kubernetes node labels to Calico nodes. [Default: Enabled] + // Valid values are: "Enabled", "Disabled". + // +kubebuilder:validation:Enum=Enabled;Disabled SyncLabels string `json:"syncLabels,omitempty" validate:"omitempty,oneof=Enabled Disabled"` // HostEndpoint controls syncing nodes to host endpoints. Disabled by default, set to nil to disable. @@ -109,6 +147,8 @@ type NodeControllerConfig struct { type AutoHostEndpointConfig struct { // AutoCreate enables automatic creation of host endpoints for every node. [Default: Disabled] + // Valid values are: "Enabled", "Disabled". + // +kubebuilder:validation:Enum=Enabled;Disabled AutoCreate string `json:"autoCreate,omitempty" validate:"omitempty,oneof=Enabled Disabled"` CreateDefaultHostEndpoint DefaultHostEndpointMode `json:"createDefaultHostEndpoint,omitempty" validate:"omitempty,createDefaultHostEndpoint"` @@ -117,6 +157,8 @@ type AutoHostEndpointConfig struct { Templates []Template `json:"templates,omitempty" validate:"omitempty"` } +// DefaultHostEndpointMode controls whether a default host endpoint is created for each node. +// Valid values are: "Enabled", "Disabled". type DefaultHostEndpointMode string const ( @@ -131,12 +173,13 @@ type Template struct { // InterfaceCIDRs contains a list of CIDRs used for matching nodeIPs to the AutoHostEndpoint. // If specified, only addresses within these CIDRs will be included in the expected IPs. - // At least one of InterfaceCIDRs and InterfaceSelector must be specified. + // At least one of InterfaceCIDRs and InterfacePattern must be specified. + // +listType=set InterfaceCIDRs []string `json:"interfaceCIDRs,omitempty" validate:"cidrs"` - // InterfaceSelector contains a regex string to match Node interface names. If specified, a HostEndpoint will be created for each matching interface on each selected node. - // At least one of InterfaceCIDRs and InterfaceSelector must be specified. - InterfaceSelector string `json:"interfaceSelector,omitempty" validate:"omitempty,regexp"` + // InterfacePattern contains a regex string to match Node interface names. If specified, a HostEndpoint will be created for each matching interface on each selected node. + // At least one of InterfaceCIDRs and InterfacePattern must be specified. + InterfacePattern string `json:"interfacePattern,omitempty" validate:"omitempty,regexp"` // Labels adds the specified labels to the generated AutoHostEndpoint, labels from node with the same name will be overwritten by values from the template label Labels map[string]string `json:"labels,omitempty" validate:"omitempty,labels"` @@ -174,9 +217,12 @@ type NamespaceControllerConfig struct { } type LoadBalancerControllerConfig struct { + // AssignIPs controls which LoadBalancer Service gets IP assigned from Calico IPAM. + // +kubebuilder:default=AllServices AssignIPs AssignIPs `json:"assignIPs,omitempty" validate:"omitempty,assignIPs"` } +// +kubebuilder:validation:Enum=AllServices;RequestedServicesOnly type AssignIPs string const ( @@ -190,7 +236,8 @@ const ( type KubeControllersConfigurationStatus struct { // RunningConfig contains the effective config that is running in the kube-controllers pod, after // merging the API resource with any environment variables. - RunningConfig KubeControllersConfigurationSpec `json:"runningConfig,omitempty"` + // +optional + RunningConfig *KubeControllersConfigurationSpec `json:"runningConfig,omitempty"` // EnvironmentVars contains the environment variables on the kube-controllers that influenced // the RunningConfig. diff --git a/api/pkg/apis/projectcalico/v3/networkpolicy.go b/api/pkg/apis/projectcalico/v3/networkpolicy.go index 1ff5d5945df..d8487e45b31 100644 --- a/api/pkg/apis/projectcalico/v3/networkpolicy.go +++ b/api/pkg/apis/projectcalico/v3/networkpolicy.go @@ -28,19 +28,23 @@ const ( // NetworkPolicyList is a list of Policy objects. type NetworkPolicyList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` Items []NetworkPolicy `json:"items" protobuf:"bytes,2,rep,name=items"` } // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:selectablefield:JSONPath=`.spec.tier` +// +kubebuilder:resource:shortName={cnp,caliconetworkpolicy} +// +kubebuilder:printcolumn:name="Tier",type=string,JSONPath=`.spec.tier` +// +kubebuilder:printcolumn:name="Order",type=number,JSONPath=`.spec.order` type NetworkPolicy struct { metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` - Spec NetworkPolicySpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` + Spec NetworkPolicySpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` } type NetworkPolicySpec struct { @@ -49,19 +53,24 @@ type NetworkPolicySpec struct { // security policies within the tier, the "default" tier is created automatically if it // does not exist, this means for deployments requiring only a single Tier, the tier name // may be omitted on all policy management requests. + // +kubebuilder:default=default Tier string `json:"tier,omitempty" validate:"omitempty,name"` + // Order is an optional field that specifies the order in which the policy is applied. // Policies with higher "order" are applied after those with lower // order within the same tier. If the order is omitted, it may be considered to be "infinite" - i.e. the // policy will be applied last. Policies with identical order will be applied in // alphanumerical order based on the Policy "Name" within the tier. Order *float64 `json:"order,omitempty"` + // The ordered set of ingress rules. Each rule contains a set of packet match criteria and // a corresponding action to apply. Ingress []Rule `json:"ingress,omitempty" validate:"omitempty,dive"` + // The ordered set of egress rules. Each rule contains a set of packet match criteria and // a corresponding action to apply. Egress []Rule `json:"egress,omitempty" validate:"omitempty,dive"` + // The selector is an expression used to pick out the endpoints that the policy should // be applied to. // @@ -88,6 +97,7 @@ type NetworkPolicySpec struct { // deployment != "dev" // ! has(label_name) Selector string `json:"selector,omitempty" validate:"selector"` + // Types indicates whether this policy applies to ingress, or to egress, or to both. When // not explicitly specified (and so the value on creation is empty or nil), Calico defaults // Types according to what Ingress and Egress are present in the policy. The @@ -102,6 +112,9 @@ type NetworkPolicySpec struct { // // When the policy is read back again, Types will always be one of these values, never empty // or nil. + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=2 + // +listType=set Types []PolicyType `json:"types,omitempty" validate:"omitempty,dive,policyType"` // ServiceAccountSelector is an optional field for an expression used to select a pod based on service accounts. @@ -120,6 +133,7 @@ type NetworkPolicySpec struct { PerformanceHints []PolicyPerformanceHint `json:"performanceHints,omitempty" validate:"omitempty,unique,dive,oneof=AssumeNeededOnEveryNode"` } +// +kubebuilder:validation:Enum=AssumeNeededOnEveryNode type PolicyPerformanceHint string const ( diff --git a/api/pkg/apis/projectcalico/v3/networkpolicy_test.go b/api/pkg/apis/projectcalico/v3/networkpolicy_test.go index f14307ca1da..97460485fb5 100644 --- a/api/pkg/apis/projectcalico/v3/networkpolicy_test.go +++ b/api/pkg/apis/projectcalico/v3/networkpolicy_test.go @@ -17,7 +17,7 @@ package v3_test import ( "reflect" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" ) diff --git a/api/pkg/apis/projectcalico/v3/networkset.go b/api/pkg/apis/projectcalico/v3/networkset.go index 2d1630f335a..722250b9e45 100644 --- a/api/pkg/apis/projectcalico/v3/networkset.go +++ b/api/pkg/apis/projectcalico/v3/networkset.go @@ -24,11 +24,12 @@ const ( ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:shortName={netset,netsets} // NetworkSetList is a list of NetworkSet objects. type NetworkSetList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` Items []NetworkSet `json:"items" protobuf:"bytes,2,rep,name=items"` } @@ -38,14 +39,15 @@ type NetworkSetList struct { type NetworkSet struct { metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` - Spec NetworkSetSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` + Spec NetworkSetSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` } // NetworkSetSpec contains the specification for a NetworkSet resource. type NetworkSetSpec struct { // The list of IP networks that belong to this set. + // +listType=set Nets []string `json:"nets,omitempty" validate:"omitempty,dive,cidr"` } diff --git a/api/pkg/apis/projectcalico/v3/nodestatus.go b/api/pkg/apis/projectcalico/v3/nodestatus.go index fb80a99c563..dbb315035ff 100644 --- a/api/pkg/apis/projectcalico/v3/nodestatus.go +++ b/api/pkg/apis/projectcalico/v3/nodestatus.go @@ -25,11 +25,12 @@ const ( // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Cluster // CalicoNodeStatusList is a list of CalicoNodeStatus resources. type CalicoNodeStatusList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` Items []CalicoNodeStatus `json:"items" protobuf:"bytes,2,rep,name=items"` } @@ -37,13 +38,18 @@ type CalicoNodeStatusList struct { // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Cluster +// +kubebuilder:printcolumn:name="Node",type=string,JSONPath=".spec.node",description="The name of the node" +// +kubebuilder:printcolumn:name="Classes",type=string,JSONPath=".spec.classes",description="The types of information to monitor for this calico/node" type CalicoNodeStatus struct { metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` - Spec CalicoNodeStatusSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` - Status CalicoNodeStatusStatus `json:"status,omitempty" protobuf:"bytes,2,opt,name=status"` + Spec CalicoNodeStatusSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` + + //+optional + Status CalicoNodeStatusStatus `json:"status" protobuf:"bytes,2,opt,name=status"` } // CalicoNodeStatusSpec contains the specification for a CalicoNodeStatus resource. @@ -66,25 +72,31 @@ type CalicoNodeStatusStatus struct { // LastUpdated is a timestamp representing the server time when CalicoNodeStatus object // last updated. It is represented in RFC3339 form and is in UTC. // +nullable - LastUpdated metav1.Time `json:"lastUpdated,omitempty"` + // +optional + LastUpdated metav1.Time `json:"lastUpdated"` // Agent holds agent status on the node. - Agent CalicoNodeAgentStatus `json:"agent,omitempty"` + // +optional + Agent CalicoNodeAgentStatus `json:"agent"` // BGP holds node BGP status. - BGP CalicoNodeBGPStatus `json:"bgp,omitempty"` + // +optional + BGP CalicoNodeBGPStatus `json:"bgp"` // Routes reports routes known to the Calico BGP daemon on the node. - Routes CalicoNodeBGPRouteStatus `json:"routes,omitempty"` + // +optional + Routes CalicoNodeBGPRouteStatus `json:"routes"` } // CalicoNodeAgentStatus defines the observed state of agent status on the node. type CalicoNodeAgentStatus struct { // BIRDV4 represents the latest observed status of bird4. - BIRDV4 BGPDaemonStatus `json:"birdV4,omitempty"` + // +optional + BIRDV4 BGPDaemonStatus `json:"birdV4"` // BIRDV6 represents the latest observed status of bird6. - BIRDV6 BGPDaemonStatus `json:"birdV6,omitempty"` + // +optional + BIRDV6 BGPDaemonStatus `json:"birdV6"` } // CalicoNodeBGPStatus defines the observed state of BGP status on the node. @@ -189,6 +201,7 @@ func NewCalicoNodeStatus() *CalicoNodeStatus { } } +// +kubebuilder:validation:Enum=FIB;RIB type CalicoNodeRouteType string const ( @@ -196,6 +209,7 @@ const ( RouteTypeRIB CalicoNodeRouteType = "RIB" ) +// +kubebuilder:validation:Enum=Kernel;Static;Direct;NodeMesh;BGPPeer type CalicoNodeRouteSourceType string const ( @@ -206,6 +220,7 @@ const ( RouteSourceTypeBGPPeer CalicoNodeRouteSourceType = "BGPPeer" ) +// +kubebuilder:validation:Enum=Agent;BGP;Routes type NodeStatusClassType string const ( @@ -214,6 +229,7 @@ const ( NodeStatusClassTypeRoutes NodeStatusClassType = "Routes" ) +// +kubebuilder:validation:Enum=NodeMesh;NodePeer;GlobalPeer type BGPPeerType string const ( @@ -222,6 +238,7 @@ const ( BGPPeerTypeGlobalPeer BGPPeerType = "GlobalPeer" ) +// +kubebuilder:validation:Enum=Ready;NotReady type BGPDaemonState string const ( @@ -229,6 +246,7 @@ const ( BGPDaemonStateNotReady BGPDaemonState = "NotReady" ) +// +kubebuilder:validation:Enum=Idle;Connect;Active;OpenSent;OpenConfirm;Established;Close type BGPSessionState string const ( diff --git a/api/pkg/apis/projectcalico/v3/policy_common.go b/api/pkg/apis/projectcalico/v3/policy_common.go index f7c1f843944..82bc8fbac54 100644 --- a/api/pkg/apis/projectcalico/v3/policy_common.go +++ b/api/pkg/apis/projectcalico/v3/policy_common.go @@ -19,6 +19,7 @@ import ( ) // PolicyType enumerates the possible values of the PolicySpec Types field. +// +kubebuilder:validation:Enum=Ingress;Egress type PolicyType string const ( @@ -35,9 +36,13 @@ const ( // the positive and negative version of a match and both must be satisfied for the rule to match. type Rule struct { Action Action `json:"action" validate:"action"` + // IPVersion is an optional field that restricts the rule to only match a specific IP // version. + // +kubebuilder:validation:Enum=4;6 + // +optional IPVersion *int `json:"ipVersion,omitempty" validate:"omitempty,ipVersion"` + // Protocol is an optional field that restricts the rule to only apply to traffic of // a specific IP protocol. Required if any of the EntityRules contain Ports // (because ports only apply to certain protocols). @@ -45,18 +50,25 @@ type Rule struct { // Must be one of these string values: "TCP", "UDP", "ICMP", "ICMPv6", "SCTP", "UDPLite" // or an integer in the range 1-255. Protocol *numorstring.Protocol `json:"protocol,omitempty" validate:"omitempty"` + // ICMP is an optional field that restricts the rule to apply to a specific type and // code of ICMP traffic. This should only be specified if the Protocol field is set to // "ICMP" or "ICMPv6". ICMP *ICMPFields `json:"icmp,omitempty" validate:"omitempty"` + // NotProtocol is the negated version of the Protocol field. NotProtocol *numorstring.Protocol `json:"notProtocol,omitempty" validate:"omitempty"` + // NotICMP is the negated version of the ICMP field. NotICMP *ICMPFields `json:"notICMP,omitempty" validate:"omitempty"` + // Source contains the match criteria that apply to source entity. - Source EntityRule `json:"source,omitempty" validate:"omitempty"` + // +optional + Source EntityRule `json:"source,omitzero" validate:"omitempty"` + // Destination contains the match criteria that apply to destination entity. - Destination EntityRule `json:"destination,omitempty" validate:"omitempty"` + // +optional + Destination EntityRule `json:"destination,omitzero" validate:"omitempty"` // HTTP contains match criteria that apply to HTTP requests. HTTP *HTTPMatch `json:"http,omitempty" validate:"omitempty"` @@ -94,10 +106,17 @@ type HTTPMatch struct { type ICMPFields struct { // Match on a specific ICMP type. For example a value of 8 refers to ICMP Echo Request // (i.e. pings). + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=255 + // +optional Type *int `json:"type,omitempty" validate:"omitempty,gte=0,lte=254"` + // Match on a specific ICMP code. If specified, the Type value must also be specified. // This is a technical limitation imposed by the kernel's iptables firewall, which // Calico uses to enforce the rule. + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=255 + // +optional Code *int `json:"code,omitempty" validate:"omitempty,gte=0,lte=255"` } @@ -109,6 +128,7 @@ type ICMPFields struct { type EntityRule struct { // Nets is an optional field that restricts the rule to only apply to traffic that // originates from (or terminates at) IP addresses in any of the given subnets. + // +listType=set Nets []string `json:"nets,omitempty" validate:"omitempty,dive,net"` // Selector is an optional field that contains a selector expression (see Policy for @@ -163,6 +183,7 @@ type EntityRule struct { Ports []numorstring.Port `json:"ports,omitempty" validate:"omitempty,dive"` // NotNets is the negated version of the Nets field. + // listType=set NotNets []string `json:"notNets,omitempty" validate:"omitempty,dive,net"` // NotSelector is the negated version of the Selector field. See Selector field for @@ -191,6 +212,7 @@ type ServiceMatch struct { type ServiceAccountMatch struct { // Names is an optional field that restricts the rule to only apply to traffic that originates from (or terminates // at) a pod running as a service account whose name is in the list. + // +listType=set Names []string `json:"names,omitempty" validate:"omitempty"` // Selector is an optional field that restricts the rule to only apply to traffic that originates from @@ -199,6 +221,7 @@ type ServiceAccountMatch struct { Selector string `json:"selector,omitempty" validate:"omitempty,selector"` } +// +kubebuilder:validation:Enum=Allow;Deny;Log;Pass type Action string const ( @@ -208,6 +231,7 @@ const ( Pass Action = "Pass" ) +// +kubebuilder:validation:Enum=Set;Delete;Learn;Ignore type StagedAction string const ( diff --git a/api/pkg/apis/projectcalico/v3/profile.go b/api/pkg/apis/projectcalico/v3/profile.go index cb296de7e17..ad09507c1f8 100644 --- a/api/pkg/apis/projectcalico/v3/profile.go +++ b/api/pkg/apis/projectcalico/v3/profile.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017,2021 Tigera, Inc. All rights reserved. +// Copyright (c) 2017,2021-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,13 +23,12 @@ const ( KindProfileList = "ProfileList" ) -// +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // ProfileList is a list of Profile objects. type ProfileList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` Items []Profile `json:"items" protobuf:"bytes,2,rep,name=items"` } @@ -37,12 +36,13 @@ type ProfileList struct { // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Cluster type Profile struct { metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` - Spec ProfileSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` + Spec ProfileSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` } // ProfileSpec contains the specification for a security Profile resource. diff --git a/api/pkg/apis/projectcalico/v3/register.go b/api/pkg/apis/projectcalico/v3/register.go index dba0ccf42f6..001b7795c9a 100644 --- a/api/pkg/apis/projectcalico/v3/register.go +++ b/api/pkg/apis/projectcalico/v3/register.go @@ -15,13 +15,18 @@ package v3 import ( + "sync" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/kubernetes/scheme" ) // GroupName is the group name use in this package -const GroupName = "projectcalico.org" +const ( + GroupName = "projectcalico.org" +) // SchemeGroupVersion is group version used to register these objects var ( @@ -30,9 +35,9 @@ var ( ) var ( + once sync.Once SchemeBuilder runtime.SchemeBuilder localSchemeBuilder = &SchemeBuilder - AddToScheme = localSchemeBuilder.AddToScheme AllKnownTypes = []runtime.Object{ &NetworkPolicy{}, &NetworkPolicyList{}, @@ -66,6 +71,10 @@ var ( &CalicoNodeStatusList{}, &IPAMConfiguration{}, &IPAMConfigurationList{}, + &IPAMHandle{}, + &IPAMHandleList{}, + &IPAMBlock{}, + &IPAMBlockList{}, &BlockAffinity{}, &BlockAffinityList{}, &BGPFilter{}, @@ -88,6 +97,18 @@ func init() { localSchemeBuilder.Register(addKnownTypes, addConversionFuncs) } +func AddToGlobalScheme() error { + var err error + once.Do(func() { + err = AddToScheme(scheme.Scheme) + }) + return err +} + +func AddToScheme(scheme *runtime.Scheme) error { + return localSchemeBuilder.AddToScheme(scheme) +} + // Adds the list of known types to api.Scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, AllKnownTypes...) diff --git a/api/pkg/apis/projectcalico/v3/stagedglobalnetworkpolicy.go b/api/pkg/apis/projectcalico/v3/stagedglobalnetworkpolicy.go index 2fe9b222983..d44d8dd612a 100644 --- a/api/pkg/apis/projectcalico/v3/stagedglobalnetworkpolicy.go +++ b/api/pkg/apis/projectcalico/v3/stagedglobalnetworkpolicy.go @@ -26,15 +26,17 @@ const ( // +genclient // +genclient:nonNamespaced +// +kubebuilder:selectablefield:JSONPath=`.spec.tier` // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Cluster,shortName={sgnp} +// +kubebuilder:printcolumn:name="Tier",type=string,JSONPath=`.spec.tier` +// +kubebuilder:printcolumn:name="Order",type=number,JSONPath=`.spec.order` // StagedGlobalNetworkPolicy is a staged GlobalNetworkPolicy. type StagedGlobalNetworkPolicy struct { - metav1.TypeMeta `json:",inline"` - // Standard object's metadata. - metav1.ObjectMeta `json:"metadata,omitempty"` - // Specification of the Policy. - Spec StagedGlobalNetworkPolicySpec `json:"spec,omitempty"` + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + Spec StagedGlobalNetworkPolicySpec `json:"spec"` } type StagedGlobalNetworkPolicySpec struct { @@ -46,6 +48,7 @@ type StagedGlobalNetworkPolicySpec struct { // security policies within the tier, the "default" tier is created automatically if it // does not exist, this means for deployments requiring only a single Tier, the tier name // may be omitted on all policy management requests. + // +kubebuilder:default=default Tier string `json:"tier,omitempty" validate:"omitempty,name"` // Order is an optional field that specifies the order in which the policy is applied. // Policies with higher "order" are applied after those with lower @@ -99,6 +102,9 @@ type StagedGlobalNetworkPolicySpec struct { // // When the policy is read back again, Types will always be one of these values, never empty // or nil. + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=2 + // +listType=set Types []PolicyType `json:"types,omitempty" validate:"omitempty,dive,policyType"` // DoNotTrack indicates whether packets matched by the rules in this policy should go through @@ -167,5 +173,10 @@ func ConvertStagedGlobalPolicyToEnforced(staged *StagedGlobalNetworkPolicy) (Sta enforced := NewGlobalNetworkPolicy() _ = copier.Copy(&enforced.ObjectMeta, &staged.ObjectMeta) _ = copier.Copy(&enforced.Spec, &staged.Spec) + + // Clear fields that should not be copied onto new objects. + enforced.ResourceVersion = "" + enforced.UID = "" + return staged.Spec.StagedAction, enforced } diff --git a/api/pkg/apis/projectcalico/v3/stagedglobalnetworkpolicy_test.go b/api/pkg/apis/projectcalico/v3/stagedglobalnetworkpolicy_test.go index c5a44eae27f..0b934fb4a7b 100644 --- a/api/pkg/apis/projectcalico/v3/stagedglobalnetworkpolicy_test.go +++ b/api/pkg/apis/projectcalico/v3/stagedglobalnetworkpolicy_test.go @@ -17,7 +17,7 @@ package v3_test import ( "reflect" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" diff --git a/api/pkg/apis/projectcalico/v3/stagedkubernetesnetworkpolicy.go b/api/pkg/apis/projectcalico/v3/stagedkubernetesnetworkpolicy.go index 56af78e3d4d..e9758b1442f 100644 --- a/api/pkg/apis/projectcalico/v3/stagedkubernetesnetworkpolicy.go +++ b/api/pkg/apis/projectcalico/v3/stagedkubernetesnetworkpolicy.go @@ -27,14 +27,13 @@ const ( // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:shortName={sknp} // StagedKubernetesNetworkPolicy is a staged GlobalNetworkPolicy. type StagedKubernetesNetworkPolicy struct { - metav1.TypeMeta `json:",inline"` - // Standard object's metadata. - metav1.ObjectMeta `json:"metadata,omitempty"` - // Specification of the Policy. - Spec StagedKubernetesNetworkPolicySpec `json:"spec,omitempty"` + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + Spec StagedKubernetesNetworkPolicySpec `json:"spec"` } type StagedKubernetesNetworkPolicySpec struct { @@ -47,7 +46,8 @@ type StagedKubernetesNetworkPolicySpec struct { // each are combined additively. This field is NOT optional and follows standard // label selector semantics. An empty podSelector matches all pods in this // namespace. - PodSelector metav1.LabelSelector `json:"podSelector,omitempty" protobuf:"bytes,1,opt,name=podSelector"` + // +optional + PodSelector metav1.LabelSelector `json:"podSelector" protobuf:"bytes,1,opt,name=podSelector"` // List of ingress rules to be applied to the selected pods. Traffic is allowed to // a pod if there are no NetworkPolicies selecting the pod @@ -80,6 +80,9 @@ type StagedKubernetesNetworkPolicySpec struct { // an Egress section and would otherwise default to just [ "Ingress" ]). // This field is beta-level in 1.8 // +optional + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=2 + // +listType=set PolicyTypes []networkingv1.PolicyType `json:"policyTypes,omitempty" protobuf:"bytes,4,rep,name=policyTypes,casttype=PolicyType"` } @@ -116,10 +119,15 @@ func NewStagedKubernetesNetworkPolicyList() *StagedKubernetesNetworkPolicyList { // ConvertStagedKubernetesPolicyToK8SEnforced converts a StagedKubernetesNetworkPolicy into a StagedAction, networkingv1 NetworkPolicy pair func ConvertStagedKubernetesPolicyToK8SEnforced(staged *StagedKubernetesNetworkPolicy) (StagedAction, *networkingv1.NetworkPolicy) { - //Convert StagedKubernetesNetworkPolicy to networkingv1.NetworkPolicy + // Convert StagedKubernetesNetworkPolicy to networkingv1.NetworkPolicy enforced := networkingv1.NetworkPolicy{} _ = copier.Copy(&enforced.ObjectMeta, &staged.ObjectMeta) _ = copier.Copy(&enforced.Spec, &staged.Spec) enforced.TypeMeta = metav1.TypeMeta{APIVersion: "networking.k8s.io/v1", Kind: "NetworkPolicy"} + + // Clear fields that should not be copied onto new objects. + enforced.ResourceVersion = "" + enforced.UID = "" + return staged.Spec.StagedAction, &enforced } diff --git a/api/pkg/apis/projectcalico/v3/stagedkubernetesnetworkpolicy_test.go b/api/pkg/apis/projectcalico/v3/stagedkubernetesnetworkpolicy_test.go index 0b323b79905..c8d4ac22241 100644 --- a/api/pkg/apis/projectcalico/v3/stagedkubernetesnetworkpolicy_test.go +++ b/api/pkg/apis/projectcalico/v3/stagedkubernetesnetworkpolicy_test.go @@ -17,7 +17,7 @@ package v3_test import ( "reflect" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" networkingv1 "k8s.io/api/networking/v1" diff --git a/api/pkg/apis/projectcalico/v3/stagednetworkpolicy.go b/api/pkg/apis/projectcalico/v3/stagednetworkpolicy.go index 7866b4ac8ae..0f6c50dddd0 100644 --- a/api/pkg/apis/projectcalico/v3/stagednetworkpolicy.go +++ b/api/pkg/apis/projectcalico/v3/stagednetworkpolicy.go @@ -26,15 +26,17 @@ const ( // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:selectablefield:JSONPath=`.spec.tier` +// +kubebuilder:resource:shortName={scnp,snp} +// +kubebuilder:printcolumn:name="Tier",type=string,JSONPath=`.spec.tier` +// +kubebuilder:printcolumn:name="Order",type=number,JSONPath=`.spec.order` // StagedNetworkPolicy is a staged NetworkPolicy. // StagedNetworkPolicy is the Namespaced-equivalent of the StagedGlobalNetworkPolicy. type StagedNetworkPolicy struct { - metav1.TypeMeta `json:",inline"` - // Standard object's metadata. - metav1.ObjectMeta `json:"metadata,omitempty"` - // Specification of the Policy. - Spec StagedNetworkPolicySpec `json:"spec,omitempty"` + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + Spec StagedNetworkPolicySpec `json:"spec"` } type StagedNetworkPolicySpec struct { @@ -46,6 +48,7 @@ type StagedNetworkPolicySpec struct { // security policies within the tier, the "default" tier is created automatically if it // does not exist, this means for deployments requiring only a single Tier, the tier name // may be omitted on all policy management requests. + // +kubebuilder:default=default Tier string `json:"tier,omitempty" validate:"omitempty,name"` // Order is an optional field that specifies the order in which the policy is applied. // Policies with higher "order" are applied after those with lower @@ -99,6 +102,9 @@ type StagedNetworkPolicySpec struct { // // When the policy is read back again, Types will always be one of these values, never empty // or nil. + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=2 + // +listType=set Types []PolicyType `json:"types,omitempty" validate:"omitempty,dive,policyType"` // ServiceAccountSelector is an optional field for an expression used to select a pod based on service accounts. @@ -154,5 +160,10 @@ func ConvertStagedPolicyToEnforced(staged *StagedNetworkPolicy) (StagedAction, * enforced := NewNetworkPolicy() _ = copier.Copy(&enforced.ObjectMeta, &staged.ObjectMeta) _ = copier.Copy(&enforced.Spec, &staged.Spec) + + // Clear fields that should not be copied onto new objects. + enforced.ResourceVersion = "" + enforced.UID = "" + return staged.Spec.StagedAction, enforced } diff --git a/api/pkg/apis/projectcalico/v3/stagednetworkpolicy_test.go b/api/pkg/apis/projectcalico/v3/stagednetworkpolicy_test.go index 4f01c863474..3188c80d701 100644 --- a/api/pkg/apis/projectcalico/v3/stagednetworkpolicy_test.go +++ b/api/pkg/apis/projectcalico/v3/stagednetworkpolicy_test.go @@ -17,7 +17,7 @@ package v3_test import ( "reflect" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" diff --git a/api/pkg/apis/projectcalico/v3/tier.go b/api/pkg/apis/projectcalico/v3/tier.go index 1e4e4698418..8c576084dd6 100644 --- a/api/pkg/apis/projectcalico/v3/tier.go +++ b/api/pkg/apis/projectcalico/v3/tier.go @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Tigera, Inc. All rights reserved. +// Copyright (c) 2024-2025 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,22 +24,27 @@ const ( // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Cluster +// +kubebuilder:printcolumn:name="Order",type="integer",JSONPath=".spec.order",description="Order in which the tier is applied" +// +kubebuilder:printcolumn:name="DefaultAction",type="string",JSONPath=".spec.defaultAction",description="Default action for the tier" // Tier contains a set of policies that are applied to packets. Multiple tiers may // be created and each tier is applied in the order specified in the tier specification. // Tier is globally-scoped (i.e. not Namespaced). +// +// +kubebuilder:validation:XValidation:rule="self.metadata.name == 'kube-admin' ? self.spec.defaultAction == 'Pass' : true", message="The 'kube-admin' tier must have default action 'Pass'" +// +kubebuilder:validation:XValidation:rule="self.metadata.name == 'kube-baseline' ? self.spec.defaultAction == 'Pass' : true", message="The 'kube-baseline' tier must have default action 'Pass'" +// +kubebuilder:validation:XValidation:rule="self.metadata.name == 'default' ? self.spec.defaultAction == 'Deny' : true", message="The 'default' tier must have default action 'Deny'" type Tier struct { - metav1.TypeMeta `json:",inline"` - // Standard object's metadata. - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - // Specification of the Tier. - Spec TierSpec `json:"spec,omitempty" protobuf:"bytes,2,rep,name=spec"` + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` + Spec TierSpec `json:"spec" protobuf:"bytes,2,rep,name=spec"` } const ( - AdminNetworkPolicyTierOrder = float64(1_000) // 1K - DefaultTierOrder = float64(1_000_000) // 1Million - BaselineAdminNetworkPolicyTierOrder = float64(10_000_000) // 10Million + KubeAdminTierOrder = float64(1_000) // 1K + DefaultTierOrder = float64(1_000_000) // 1Million + KubeBaselineTierOrder = float64(10_000_000) // 10Million ) // TierSpec contains the specification for a security policy tier resource. diff --git a/api/pkg/apis/projectcalico/v3/v3_suite_test.go b/api/pkg/apis/projectcalico/v3/v3_suite_test.go index fe89ca356e6..5cc6e203db8 100644 --- a/api/pkg/apis/projectcalico/v3/v3_suite_test.go +++ b/api/pkg/apis/projectcalico/v3/v3_suite_test.go @@ -17,13 +17,13 @@ package v3_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" ) -func TestV2(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../report/v3_api_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "v3 API Suite", []Reporter{junitReporter}) +func TestV3(t *testing.T) { + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../report/v3_api_suite.xml" + ginkgo.RunSpecs(t, "v3 API Suite", suiteConfig, reporterConfig) } diff --git a/api/pkg/apis/projectcalico/v3/zz_generated.deepcopy.go b/api/pkg/apis/projectcalico/v3/zz_generated.deepcopy.go index 46f3f75ba12..47888a94e98 100644 --- a/api/pkg/apis/projectcalico/v3/zz_generated.deepcopy.go +++ b/api/pkg/apis/projectcalico/v3/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ //go:build !ignore_autogenerated // +build !ignore_autogenerated -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by deepcopy-gen. DO NOT EDIT. @@ -15,6 +15,41 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AllocationAttribute) DeepCopyInto(out *AllocationAttribute) { + *out = *in + if in.HandleID != nil { + in, out := &in.HandleID, &out.HandleID + *out = new(string) + **out = **in + } + if in.ActiveOwnerAttrs != nil { + in, out := &in.ActiveOwnerAttrs, &out.ActiveOwnerAttrs + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.AlternateOwnerAttrs != nil { + in, out := &in.AlternateOwnerAttrs, &out.AlternateOwnerAttrs + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllocationAttribute. +func (in *AllocationAttribute) DeepCopy() *AllocationAttribute { + if in == nil { + return nil + } + out := new(AllocationAttribute) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AutoHostEndpointConfig) DeepCopyInto(out *AutoHostEndpointConfig) { *out = *in @@ -1020,6 +1055,11 @@ func (in *ControllersConfig) DeepCopyInto(out *ControllersConfig) { *out = new(LoadBalancerControllerConfig) **out = **in } + if in.Migration != nil { + in, out := &in.Migration, &out.Migration + *out = new(MigrationControllerConfig) + **out = **in + } return } @@ -1194,11 +1234,6 @@ func (in *FelixConfigurationSpec) DeepCopyInto(out *FelixConfigurationSpec) { *out = new(v1.Duration) **out = **in } - if in.IptablesLockTimeout != nil { - in, out := &in.IptablesLockTimeout, &out.IptablesLockTimeout - *out = new(v1.Duration) - **out = **in - } if in.IptablesLockProbeInterval != nil { in, out := &in.IptablesLockProbeInterval, &out.IptablesLockProbeInterval *out = new(v1.Duration) @@ -1234,6 +1269,16 @@ func (in *FelixConfigurationSpec) DeepCopyInto(out *FelixConfigurationSpec) { *out = new(int) **out = **in } + if in.LogActionRateLimit != nil { + in, out := &in.LogActionRateLimit, &out.LogActionRateLimit + *out = new(string) + **out = **in + } + if in.LogActionRateLimitBurst != nil { + in, out := &in.LogActionRateLimitBurst, &out.LogActionRateLimitBurst + *out = new(int) + **out = **in + } if in.IPIPEnabled != nil { in, out := &in.IPIPEnabled, &out.IPIPEnabled *out = new(bool) @@ -1354,6 +1399,26 @@ func (in *FelixConfigurationSpec) DeepCopyInto(out *FelixConfigurationSpec) { *out = new(bool) **out = **in } + if in.PrometheusMetricsCAFile != nil { + in, out := &in.PrometheusMetricsCAFile, &out.PrometheusMetricsCAFile + *out = new(string) + **out = **in + } + if in.PrometheusMetricsCertFile != nil { + in, out := &in.PrometheusMetricsCertFile, &out.PrometheusMetricsCertFile + *out = new(string) + **out = **in + } + if in.PrometheusMetricsKeyFile != nil { + in, out := &in.PrometheusMetricsKeyFile, &out.PrometheusMetricsKeyFile + *out = new(string) + **out = **in + } + if in.PrometheusMetricsClientAuth != nil { + in, out := &in.PrometheusMetricsClientAuth, &out.PrometheusMetricsClientAuth + *out = new(PrometheusMetricsClientAuthType) + **out = **in + } if in.FailsafeInboundHostPorts != nil { in, out := &in.FailsafeInboundHostPorts, &out.FailsafeInboundHostPorts *out = new([]ProtoPort) @@ -1565,16 +1630,11 @@ func (in *FelixConfigurationSpec) DeepCopyInto(out *FelixConfigurationSpec) { *out = new(v1.Duration) **out = **in } - if in.BPFKubeProxyHealtzPort != nil { - in, out := &in.BPFKubeProxyHealtzPort, &out.BPFKubeProxyHealtzPort + if in.BPFKubeProxyHealthzPort != nil { + in, out := &in.BPFKubeProxyHealthzPort, &out.BPFKubeProxyHealthzPort *out = new(int) **out = **in } - if in.BPFKubeProxyEndpointSlicesEnabled != nil { - in, out := &in.BPFKubeProxyEndpointSlicesEnabled, &out.BPFKubeProxyEndpointSlicesEnabled - *out = new(bool) - **out = **in - } if in.BPFPSNATPorts != nil { in, out := &in.BPFPSNATPorts, &out.BPFPSNATPorts *out = new(numorstring.Port) @@ -1792,6 +1852,16 @@ func (in *FelixConfigurationSpec) DeepCopyInto(out *FelixConfigurationSpec) { *out = new(bool) **out = **in } + if in.BPFMaglevMaxEndpointsPerService != nil { + in, out := &in.BPFMaglevMaxEndpointsPerService, &out.BPFMaglevMaxEndpointsPerService + *out = new(int) + **out = **in + } + if in.BPFMaglevMaxServices != nil { + in, out := &in.BPFMaglevMaxServices, &out.BPFMaglevMaxServices + *out = new(int) + **out = **in + } return } @@ -2167,12 +2237,127 @@ func (in *ICMPFields) DeepCopy() *ICMPFields { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPAMBlock) DeepCopyInto(out *IPAMBlock) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAMBlock. +func (in *IPAMBlock) DeepCopy() *IPAMBlock { + if in == nil { + return nil + } + out := new(IPAMBlock) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *IPAMBlock) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPAMBlockList) DeepCopyInto(out *IPAMBlockList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]IPAMBlock, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAMBlockList. +func (in *IPAMBlockList) DeepCopy() *IPAMBlockList { + if in == nil { + return nil + } + out := new(IPAMBlockList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *IPAMBlockList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPAMBlockSpec) DeepCopyInto(out *IPAMBlockSpec) { + *out = *in + if in.Affinity != nil { + in, out := &in.Affinity, &out.Affinity + *out = new(string) + **out = **in + } + if in.AffinityClaimTime != nil { + in, out := &in.AffinityClaimTime, &out.AffinityClaimTime + *out = (*in).DeepCopy() + } + if in.Allocations != nil { + in, out := &in.Allocations, &out.Allocations + *out = make([]*int, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(int) + **out = **in + } + } + } + if in.Unallocated != nil { + in, out := &in.Unallocated, &out.Unallocated + *out = make([]int, len(*in)) + copy(*out, *in) + } + if in.Attributes != nil { + in, out := &in.Attributes, &out.Attributes + *out = make([]AllocationAttribute, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.SequenceNumberForAllocation != nil { + in, out := &in.SequenceNumberForAllocation, &out.SequenceNumberForAllocation + *out = make(map[string]uint64, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAMBlockSpec. +func (in *IPAMBlockSpec) DeepCopy() *IPAMBlockSpec { + if in == nil { + return nil + } + out := new(IPAMBlockSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IPAMConfiguration) DeepCopyInto(out *IPAMConfiguration) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) return } @@ -2230,6 +2415,11 @@ func (in *IPAMConfigurationList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IPAMConfigurationSpec) DeepCopyInto(out *IPAMConfigurationSpec) { *out = *in + if in.KubeVirtVMAddressPersistence != nil { + in, out := &in.KubeVirtVMAddressPersistence, &out.KubeVirtVMAddressPersistence + *out = new(VMAddressPersistence) + **out = **in + } return } @@ -2243,6 +2433,89 @@ func (in *IPAMConfigurationSpec) DeepCopy() *IPAMConfigurationSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPAMHandle) DeepCopyInto(out *IPAMHandle) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAMHandle. +func (in *IPAMHandle) DeepCopy() *IPAMHandle { + if in == nil { + return nil + } + out := new(IPAMHandle) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *IPAMHandle) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPAMHandleList) DeepCopyInto(out *IPAMHandleList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]IPAMHandle, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAMHandleList. +func (in *IPAMHandleList) DeepCopy() *IPAMHandleList { + if in == nil { + return nil + } + out := new(IPAMHandleList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *IPAMHandleList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPAMHandleSpec) DeepCopyInto(out *IPAMHandleSpec) { + *out = *in + if in.Block != nil { + in, out := &in.Block, &out.Block + *out = make(map[string]int, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAMHandleSpec. +func (in *IPAMHandleSpec) DeepCopy() *IPAMHandleSpec { + if in == nil { + return nil + } + out := new(IPAMHandleSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IPIPConfiguration) DeepCopyInto(out *IPIPConfiguration) { *out = *in @@ -2265,6 +2538,11 @@ func (in *IPPool) DeepCopyInto(out *IPPool) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) + if in.Status != nil { + in, out := &in.Status, &out.Status + *out = new(IPPoolStatus) + (*in).DeepCopyInto(*out) + } return } @@ -2322,11 +2600,6 @@ func (in *IPPoolList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IPPoolSpec) DeepCopyInto(out *IPPoolSpec) { *out = *in - if in.IPIP != nil { - in, out := &in.IPIP, &out.IPIP - *out = new(IPIPConfiguration) - **out = **in - } if in.AllowedUses != nil { in, out := &in.AllowedUses, &out.AllowedUses *out = make([]IPPoolAllowedUse, len(*in)) @@ -2350,6 +2623,29 @@ func (in *IPPoolSpec) DeepCopy() *IPPoolSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPPoolStatus) DeepCopyInto(out *IPPoolStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPPoolStatus. +func (in *IPPoolStatus) DeepCopy() *IPPoolStatus { + if in == nil { + return nil + } + out := new(IPPoolStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IPReservation) DeepCopyInto(out *IPReservation) { *out = *in @@ -2527,7 +2823,11 @@ func (in *KubeControllersConfigurationSpec) DeepCopy() *KubeControllersConfigura // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KubeControllersConfigurationStatus) DeepCopyInto(out *KubeControllersConfigurationStatus) { *out = *in - in.RunningConfig.DeepCopyInto(&out.RunningConfig) + if in.RunningConfig != nil { + in, out := &in.RunningConfig, &out.RunningConfig + *out = new(KubeControllersConfigurationSpec) + (*in).DeepCopyInto(*out) + } if in.EnvironmentVars != nil { in, out := &in.EnvironmentVars, &out.EnvironmentVars *out = make(map[string]string, len(*in)) @@ -2564,6 +2864,22 @@ func (in *LoadBalancerControllerConfig) DeepCopy() *LoadBalancerControllerConfig return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MigrationControllerConfig) DeepCopyInto(out *MigrationControllerConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MigrationControllerConfig. +func (in *MigrationControllerConfig) DeepCopy() *MigrationControllerConfig { + if in == nil { + return nil + } + out := new(MigrationControllerConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NamespaceControllerConfig) DeepCopyInto(out *NamespaceControllerConfig) { *out = *in diff --git a/api/pkg/apis/projectcalico/v3/zz_generated.defaults.go b/api/pkg/apis/projectcalico/v3/zz_generated.defaults.go index 16870ebea91..c28786a1f44 100644 --- a/api/pkg/apis/projectcalico/v3/zz_generated.defaults.go +++ b/api/pkg/apis/projectcalico/v3/zz_generated.defaults.go @@ -1,7 +1,7 @@ //go:build !ignore_autogenerated // +build !ignore_autogenerated -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by defaulter-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/clientset.go b/api/pkg/client/clientset_generated/clientset/clientset.go index 52b1c0f437a..1a83a788dec 100644 --- a/api/pkg/client/clientset_generated/clientset/clientset.go +++ b/api/pkg/client/clientset_generated/clientset/clientset.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/fake/clientset_generated.go b/api/pkg/client/clientset_generated/clientset/fake/clientset_generated.go index a6c7d0d4deb..e0a1765ef6f 100644 --- a/api/pkg/client/clientset_generated/clientset/fake/clientset_generated.go +++ b/api/pkg/client/clientset_generated/clientset/fake/clientset_generated.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/fake/doc.go b/api/pkg/client/clientset_generated/clientset/fake/doc.go index f19120397a9..e306141e0c2 100644 --- a/api/pkg/client/clientset_generated/clientset/fake/doc.go +++ b/api/pkg/client/clientset_generated/clientset/fake/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/fake/register.go b/api/pkg/client/clientset_generated/clientset/fake/register.go index 2839b7d55cf..e2a4ef34b1a 100644 --- a/api/pkg/client/clientset_generated/clientset/fake/register.go +++ b/api/pkg/client/clientset_generated/clientset/fake/register.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/scheme/doc.go b/api/pkg/client/clientset_generated/clientset/scheme/doc.go index 36c3d931a32..d9468049c31 100644 --- a/api/pkg/client/clientset_generated/clientset/scheme/doc.go +++ b/api/pkg/client/clientset_generated/clientset/scheme/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/scheme/register.go b/api/pkg/client/clientset_generated/clientset/scheme/register.go index 8ff6d842ea7..aaa1855a676 100644 --- a/api/pkg/client/clientset_generated/clientset/scheme/register.go +++ b/api/pkg/client/clientset_generated/clientset/scheme/register.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/bgpconfiguration.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/bgpconfiguration.go index 997bf2c9661..35ac42f8726 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/bgpconfiguration.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/bgpconfiguration.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/bgpfilter.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/bgpfilter.go index 71ae5e77c32..5c8eb0cd749 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/bgpfilter.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/bgpfilter.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/bgppeer.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/bgppeer.go index ce34c56c282..1c3e8b76108 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/bgppeer.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/bgppeer.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/blockaffinity.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/blockaffinity.go index deaa74a6583..a8e1664938b 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/blockaffinity.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/blockaffinity.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/caliconodestatus.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/caliconodestatus.go index 3fb52759c3d..abd0d00965c 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/caliconodestatus.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/caliconodestatus.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/clusterinformation.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/clusterinformation.go index 68849e59b0c..7587edf91d8 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/clusterinformation.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/clusterinformation.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/doc.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/doc.go index 04a1a08871a..98de1e2c529 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/doc.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/doc.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/doc.go index dc7a82df923..ad14cc3703b 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/doc.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_bgpconfiguration.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_bgpconfiguration.go index e81e418b03b..32db1e46298 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_bgpconfiguration.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_bgpconfiguration.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_bgpfilter.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_bgpfilter.go index 19bb915f1e6..5abc728b018 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_bgpfilter.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_bgpfilter.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_bgppeer.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_bgppeer.go index 7a79f8d24d9..095e6b204c8 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_bgppeer.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_bgppeer.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_blockaffinity.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_blockaffinity.go index 9c060f48179..29e387086cf 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_blockaffinity.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_blockaffinity.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_caliconodestatus.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_caliconodestatus.go index d7ad81fc287..b91624f06d4 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_caliconodestatus.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_caliconodestatus.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_clusterinformation.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_clusterinformation.go index 2b50a765ea0..6ef78323b20 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_clusterinformation.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_clusterinformation.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_felixconfiguration.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_felixconfiguration.go index e2312d0946a..1639ae1babd 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_felixconfiguration.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_felixconfiguration.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_globalnetworkpolicy.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_globalnetworkpolicy.go index 00154fc34f6..e007753275f 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_globalnetworkpolicy.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_globalnetworkpolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_globalnetworkset.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_globalnetworkset.go index 8b576608807..4670274645e 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_globalnetworkset.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_globalnetworkset.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_hostendpoint.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_hostendpoint.go index b95444fbba6..04fecd30507 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_hostendpoint.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_hostendpoint.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_ipamblock.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_ipamblock.go new file mode 100644 index 00000000000..bf9b468c386 --- /dev/null +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_ipamblock.go @@ -0,0 +1,34 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + projectcalicov3 "github.com/projectcalico/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3" + gentype "k8s.io/client-go/gentype" +) + +// fakeIPAMBlocks implements IPAMBlockInterface +type fakeIPAMBlocks struct { + *gentype.FakeClientWithList[*v3.IPAMBlock, *v3.IPAMBlockList] + Fake *FakeProjectcalicoV3 +} + +func newFakeIPAMBlocks(fake *FakeProjectcalicoV3) projectcalicov3.IPAMBlockInterface { + return &fakeIPAMBlocks{ + gentype.NewFakeClientWithList[*v3.IPAMBlock, *v3.IPAMBlockList]( + fake.Fake, + "", + v3.SchemeGroupVersion.WithResource("ipamblocks"), + v3.SchemeGroupVersion.WithKind("IPAMBlock"), + func() *v3.IPAMBlock { return &v3.IPAMBlock{} }, + func() *v3.IPAMBlockList { return &v3.IPAMBlockList{} }, + func(dst, src *v3.IPAMBlockList) { dst.ListMeta = src.ListMeta }, + func(list *v3.IPAMBlockList) []*v3.IPAMBlock { return gentype.ToPointerSlice(list.Items) }, + func(list *v3.IPAMBlockList, items []*v3.IPAMBlock) { list.Items = gentype.FromPointerSlice(items) }, + ), + fake, + } +} diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_ipamconfiguration.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_ipamconfiguration.go index c73c5e72272..cdf8eb5f550 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_ipamconfiguration.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_ipamconfiguration.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_ipamhandle.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_ipamhandle.go new file mode 100644 index 00000000000..ec4a2ba9aae --- /dev/null +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_ipamhandle.go @@ -0,0 +1,34 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + projectcalicov3 "github.com/projectcalico/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3" + gentype "k8s.io/client-go/gentype" +) + +// fakeIPAMHandles implements IPAMHandleInterface +type fakeIPAMHandles struct { + *gentype.FakeClientWithList[*v3.IPAMHandle, *v3.IPAMHandleList] + Fake *FakeProjectcalicoV3 +} + +func newFakeIPAMHandles(fake *FakeProjectcalicoV3, namespace string) projectcalicov3.IPAMHandleInterface { + return &fakeIPAMHandles{ + gentype.NewFakeClientWithList[*v3.IPAMHandle, *v3.IPAMHandleList]( + fake.Fake, + namespace, + v3.SchemeGroupVersion.WithResource("ipamhandles"), + v3.SchemeGroupVersion.WithKind("IPAMHandle"), + func() *v3.IPAMHandle { return &v3.IPAMHandle{} }, + func() *v3.IPAMHandleList { return &v3.IPAMHandleList{} }, + func(dst, src *v3.IPAMHandleList) { dst.ListMeta = src.ListMeta }, + func(list *v3.IPAMHandleList) []*v3.IPAMHandle { return gentype.ToPointerSlice(list.Items) }, + func(list *v3.IPAMHandleList, items []*v3.IPAMHandle) { list.Items = gentype.FromPointerSlice(items) }, + ), + fake, + } +} diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_ippool.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_ippool.go index ab96e9eebb9..881afc3d5db 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_ippool.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_ippool.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_ipreservation.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_ipreservation.go index 93a35e601fa..0eea4c34311 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_ipreservation.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_ipreservation.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_kubecontrollersconfiguration.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_kubecontrollersconfiguration.go index d27c7bded3c..a280ac19336 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_kubecontrollersconfiguration.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_kubecontrollersconfiguration.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_networkpolicy.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_networkpolicy.go index bf63b805e97..3676b8ac5ff 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_networkpolicy.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_networkpolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_networkset.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_networkset.go index 90cec763b63..d9a68702331 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_networkset.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_networkset.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_profile.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_profile.go index e9e776f49f9..52e7df4577d 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_profile.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_profile.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_projectcalico_client.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_projectcalico_client.go index 06ca77782cc..e22683af5ed 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_projectcalico_client.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_projectcalico_client.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. @@ -54,10 +54,18 @@ func (c *FakeProjectcalicoV3) HostEndpoints() v3.HostEndpointInterface { return newFakeHostEndpoints(c) } +func (c *FakeProjectcalicoV3) IPAMBlocks() v3.IPAMBlockInterface { + return newFakeIPAMBlocks(c) +} + func (c *FakeProjectcalicoV3) IPAMConfigurations() v3.IPAMConfigurationInterface { return newFakeIPAMConfigurations(c) } +func (c *FakeProjectcalicoV3) IPAMHandles(namespace string) v3.IPAMHandleInterface { + return newFakeIPAMHandles(c, namespace) +} + func (c *FakeProjectcalicoV3) IPPools() v3.IPPoolInterface { return newFakeIPPools(c) } diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_stagedglobalnetworkpolicy.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_stagedglobalnetworkpolicy.go index 6016b14bc6a..1f00aa11eb2 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_stagedglobalnetworkpolicy.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_stagedglobalnetworkpolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_stagedkubernetesnetworkpolicy.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_stagedkubernetesnetworkpolicy.go index f2fec3a8bb4..70d882b0289 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_stagedkubernetesnetworkpolicy.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_stagedkubernetesnetworkpolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_stagednetworkpolicy.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_stagednetworkpolicy.go index ee803fac5e7..7006bb42eac 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_stagednetworkpolicy.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_stagednetworkpolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_tier.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_tier.go index 646287d2b8b..d2f00d40065 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_tier.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/fake/fake_tier.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/felixconfiguration.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/felixconfiguration.go index e95d3b79974..e0db965fc5c 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/felixconfiguration.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/felixconfiguration.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/generated_expansion.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/generated_expansion.go index 6e36b150f24..be7fec94a0d 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/generated_expansion.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/generated_expansion.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. @@ -24,8 +24,12 @@ type GlobalNetworkSetExpansion interface{} type HostEndpointExpansion interface{} +type IPAMBlockExpansion interface{} + type IPAMConfigurationExpansion interface{} +type IPAMHandleExpansion interface{} + type IPPoolExpansion interface{} type IPReservationExpansion interface{} diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/globalnetworkpolicy.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/globalnetworkpolicy.go index 6cbacaac8bb..5836e7c229a 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/globalnetworkpolicy.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/globalnetworkpolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/globalnetworkset.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/globalnetworkset.go index 8a1837a35c7..1fa1be56ea6 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/globalnetworkset.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/globalnetworkset.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/hostendpoint.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/hostendpoint.go index 64509fe07f0..fdf542afb5f 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/hostendpoint.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/hostendpoint.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/ipamblock.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/ipamblock.go new file mode 100644 index 00000000000..e7165351865 --- /dev/null +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/ipamblock.go @@ -0,0 +1,54 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// Code generated by client-gen. DO NOT EDIT. + +package v3 + +import ( + context "context" + + projectcalicov3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + scheme "github.com/projectcalico/api/pkg/client/clientset_generated/clientset/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + gentype "k8s.io/client-go/gentype" +) + +// IPAMBlocksGetter has a method to return a IPAMBlockInterface. +// A group's client should implement this interface. +type IPAMBlocksGetter interface { + IPAMBlocks() IPAMBlockInterface +} + +// IPAMBlockInterface has methods to work with IPAMBlock resources. +type IPAMBlockInterface interface { + Create(ctx context.Context, iPAMBlock *projectcalicov3.IPAMBlock, opts v1.CreateOptions) (*projectcalicov3.IPAMBlock, error) + Update(ctx context.Context, iPAMBlock *projectcalicov3.IPAMBlock, opts v1.UpdateOptions) (*projectcalicov3.IPAMBlock, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*projectcalicov3.IPAMBlock, error) + List(ctx context.Context, opts v1.ListOptions) (*projectcalicov3.IPAMBlockList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *projectcalicov3.IPAMBlock, err error) + IPAMBlockExpansion +} + +// iPAMBlocks implements IPAMBlockInterface +type iPAMBlocks struct { + *gentype.ClientWithList[*projectcalicov3.IPAMBlock, *projectcalicov3.IPAMBlockList] +} + +// newIPAMBlocks returns a IPAMBlocks +func newIPAMBlocks(c *ProjectcalicoV3Client) *iPAMBlocks { + return &iPAMBlocks{ + gentype.NewClientWithList[*projectcalicov3.IPAMBlock, *projectcalicov3.IPAMBlockList]( + "ipamblocks", + c.RESTClient(), + scheme.ParameterCodec, + "", + func() *projectcalicov3.IPAMBlock { return &projectcalicov3.IPAMBlock{} }, + func() *projectcalicov3.IPAMBlockList { return &projectcalicov3.IPAMBlockList{} }, + ), + } +} diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/ipamconfiguration.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/ipamconfiguration.go index 1c94e9cd51e..25f69b09125 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/ipamconfiguration.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/ipamconfiguration.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/ipamhandle.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/ipamhandle.go new file mode 100644 index 00000000000..05ce2b0fbba --- /dev/null +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/ipamhandle.go @@ -0,0 +1,54 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// Code generated by client-gen. DO NOT EDIT. + +package v3 + +import ( + context "context" + + projectcalicov3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + scheme "github.com/projectcalico/api/pkg/client/clientset_generated/clientset/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + gentype "k8s.io/client-go/gentype" +) + +// IPAMHandlesGetter has a method to return a IPAMHandleInterface. +// A group's client should implement this interface. +type IPAMHandlesGetter interface { + IPAMHandles(namespace string) IPAMHandleInterface +} + +// IPAMHandleInterface has methods to work with IPAMHandle resources. +type IPAMHandleInterface interface { + Create(ctx context.Context, iPAMHandle *projectcalicov3.IPAMHandle, opts v1.CreateOptions) (*projectcalicov3.IPAMHandle, error) + Update(ctx context.Context, iPAMHandle *projectcalicov3.IPAMHandle, opts v1.UpdateOptions) (*projectcalicov3.IPAMHandle, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*projectcalicov3.IPAMHandle, error) + List(ctx context.Context, opts v1.ListOptions) (*projectcalicov3.IPAMHandleList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *projectcalicov3.IPAMHandle, err error) + IPAMHandleExpansion +} + +// iPAMHandles implements IPAMHandleInterface +type iPAMHandles struct { + *gentype.ClientWithList[*projectcalicov3.IPAMHandle, *projectcalicov3.IPAMHandleList] +} + +// newIPAMHandles returns a IPAMHandles +func newIPAMHandles(c *ProjectcalicoV3Client, namespace string) *iPAMHandles { + return &iPAMHandles{ + gentype.NewClientWithList[*projectcalicov3.IPAMHandle, *projectcalicov3.IPAMHandleList]( + "ipamhandles", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *projectcalicov3.IPAMHandle { return &projectcalicov3.IPAMHandle{} }, + func() *projectcalicov3.IPAMHandleList { return &projectcalicov3.IPAMHandleList{} }, + ), + } +} diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/ippool.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/ippool.go index a304ed34bee..e8dc6342663 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/ippool.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/ippool.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. @@ -25,6 +25,8 @@ type IPPoolsGetter interface { type IPPoolInterface interface { Create(ctx context.Context, iPPool *projectcalicov3.IPPool, opts v1.CreateOptions) (*projectcalicov3.IPPool, error) Update(ctx context.Context, iPPool *projectcalicov3.IPPool, opts v1.UpdateOptions) (*projectcalicov3.IPPool, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, iPPool *projectcalicov3.IPPool, opts v1.UpdateOptions) (*projectcalicov3.IPPool, error) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error Get(ctx context.Context, name string, opts v1.GetOptions) (*projectcalicov3.IPPool, error) diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/ipreservation.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/ipreservation.go index 96e3db80f62..fc553867ffc 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/ipreservation.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/ipreservation.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/kubecontrollersconfiguration.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/kubecontrollersconfiguration.go index 26fbc924f63..c647bc3bad0 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/kubecontrollersconfiguration.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/kubecontrollersconfiguration.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/networkpolicy.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/networkpolicy.go index 2166cc8eab3..8e9776e04d7 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/networkpolicy.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/networkpolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/networkset.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/networkset.go index 0240b8a78e8..bb8d578b502 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/networkset.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/networkset.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/profile.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/profile.go index 780301108ab..bd38541a582 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/profile.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/profile.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/projectcalico_client.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/projectcalico_client.go index b001d115483..b99224d736b 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/projectcalico_client.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/projectcalico_client.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. @@ -24,7 +24,9 @@ type ProjectcalicoV3Interface interface { GlobalNetworkPoliciesGetter GlobalNetworkSetsGetter HostEndpointsGetter + IPAMBlocksGetter IPAMConfigurationsGetter + IPAMHandlesGetter IPPoolsGetter IPReservationsGetter KubeControllersConfigurationsGetter @@ -82,10 +84,18 @@ func (c *ProjectcalicoV3Client) HostEndpoints() HostEndpointInterface { return newHostEndpoints(c) } +func (c *ProjectcalicoV3Client) IPAMBlocks() IPAMBlockInterface { + return newIPAMBlocks(c) +} + func (c *ProjectcalicoV3Client) IPAMConfigurations() IPAMConfigurationInterface { return newIPAMConfigurations(c) } +func (c *ProjectcalicoV3Client) IPAMHandles(namespace string) IPAMHandleInterface { + return newIPAMHandles(c, namespace) +} + func (c *ProjectcalicoV3Client) IPPools() IPPoolInterface { return newIPPools(c) } diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/stagedglobalnetworkpolicy.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/stagedglobalnetworkpolicy.go index 9c338cfda4c..7234db843a7 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/stagedglobalnetworkpolicy.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/stagedglobalnetworkpolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/stagedkubernetesnetworkpolicy.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/stagedkubernetesnetworkpolicy.go index 7082507b920..5fd8043b767 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/stagedkubernetesnetworkpolicy.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/stagedkubernetesnetworkpolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/stagednetworkpolicy.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/stagednetworkpolicy.go index f434e09d19b..f89cc8bbb0b 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/stagednetworkpolicy.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/stagednetworkpolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/tier.go b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/tier.go index 790115525a9..a3147fcef30 100644 --- a/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/tier.go +++ b/api/pkg/client/clientset_generated/clientset/typed/projectcalico/v3/tier.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/api/pkg/client/informers_generated/externalversions/factory.go b/api/pkg/client/informers_generated/externalversions/factory.go index 94267c027a6..de00e835dbb 100644 --- a/api/pkg/client/informers_generated/externalversions/factory.go +++ b/api/pkg/client/informers_generated/externalversions/factory.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/api/pkg/client/informers_generated/externalversions/generic.go b/api/pkg/client/informers_generated/externalversions/generic.go index afe295a8891..9fe5cfca75c 100644 --- a/api/pkg/client/informers_generated/externalversions/generic.go +++ b/api/pkg/client/informers_generated/externalversions/generic.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. @@ -59,8 +59,12 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Projectcalico().V3().GlobalNetworkSets().Informer()}, nil case v3.SchemeGroupVersion.WithResource("hostendpoints"): return &genericInformer{resource: resource.GroupResource(), informer: f.Projectcalico().V3().HostEndpoints().Informer()}, nil + case v3.SchemeGroupVersion.WithResource("ipamblocks"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Projectcalico().V3().IPAMBlocks().Informer()}, nil case v3.SchemeGroupVersion.WithResource("ipamconfigurations"): return &genericInformer{resource: resource.GroupResource(), informer: f.Projectcalico().V3().IPAMConfigurations().Informer()}, nil + case v3.SchemeGroupVersion.WithResource("ipamhandles"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Projectcalico().V3().IPAMHandles().Informer()}, nil case v3.SchemeGroupVersion.WithResource("ippools"): return &genericInformer{resource: resource.GroupResource(), informer: f.Projectcalico().V3().IPPools().Informer()}, nil case v3.SchemeGroupVersion.WithResource("ipreservations"): diff --git a/api/pkg/client/informers_generated/externalversions/internalinterfaces/factory_interfaces.go b/api/pkg/client/informers_generated/externalversions/internalinterfaces/factory_interfaces.go index 6e4a0150bf3..d33ec43c375 100644 --- a/api/pkg/client/informers_generated/externalversions/internalinterfaces/factory_interfaces.go +++ b/api/pkg/client/informers_generated/externalversions/internalinterfaces/factory_interfaces.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/api/pkg/client/informers_generated/externalversions/projectcalico/interface.go b/api/pkg/client/informers_generated/externalversions/projectcalico/interface.go index 90765a9bc6e..f9970cf7535 100644 --- a/api/pkg/client/informers_generated/externalversions/projectcalico/interface.go +++ b/api/pkg/client/informers_generated/externalversions/projectcalico/interface.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/bgpconfiguration.go b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/bgpconfiguration.go index 3d533057337..04a50b5a1fc 100644 --- a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/bgpconfiguration.go +++ b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/bgpconfiguration.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/bgpfilter.go b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/bgpfilter.go index 2cb3c2f9983..cce2d95cb24 100644 --- a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/bgpfilter.go +++ b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/bgpfilter.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/bgppeer.go b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/bgppeer.go index 332bb70f704..61acfdbc0d9 100644 --- a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/bgppeer.go +++ b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/bgppeer.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/blockaffinity.go b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/blockaffinity.go index 945db14f0b6..30a37456e44 100644 --- a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/blockaffinity.go +++ b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/blockaffinity.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/caliconodestatus.go b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/caliconodestatus.go index c490ff9e538..2df625952d6 100644 --- a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/caliconodestatus.go +++ b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/caliconodestatus.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/clusterinformation.go b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/clusterinformation.go index 0c27c7dea77..d29dd2860e1 100644 --- a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/clusterinformation.go +++ b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/clusterinformation.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/felixconfiguration.go b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/felixconfiguration.go index 06d744b2933..5f8b95d89c1 100644 --- a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/felixconfiguration.go +++ b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/felixconfiguration.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/globalnetworkpolicy.go b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/globalnetworkpolicy.go index f2317b5ce26..0c6d2c40b98 100644 --- a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/globalnetworkpolicy.go +++ b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/globalnetworkpolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/globalnetworkset.go b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/globalnetworkset.go index eccd6ba1cb9..14a3cd41d3f 100644 --- a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/globalnetworkset.go +++ b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/globalnetworkset.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/hostendpoint.go b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/hostendpoint.go index 79daeb3bfd3..3eee0cbee3b 100644 --- a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/hostendpoint.go +++ b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/hostendpoint.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/interface.go b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/interface.go index 7e8c17d8c4b..65dc9fd7961 100644 --- a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/interface.go +++ b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/interface.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. @@ -30,8 +30,12 @@ type Interface interface { GlobalNetworkSets() GlobalNetworkSetInformer // HostEndpoints returns a HostEndpointInformer. HostEndpoints() HostEndpointInformer + // IPAMBlocks returns a IPAMBlockInformer. + IPAMBlocks() IPAMBlockInformer // IPAMConfigurations returns a IPAMConfigurationInformer. IPAMConfigurations() IPAMConfigurationInformer + // IPAMHandles returns a IPAMHandleInformer. + IPAMHandles() IPAMHandleInformer // IPPools returns a IPPoolInformer. IPPools() IPPoolInformer // IPReservations returns a IPReservationInformer. @@ -115,11 +119,21 @@ func (v *version) HostEndpoints() HostEndpointInformer { return &hostEndpointInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } +// IPAMBlocks returns a IPAMBlockInformer. +func (v *version) IPAMBlocks() IPAMBlockInformer { + return &iPAMBlockInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} + // IPAMConfigurations returns a IPAMConfigurationInformer. func (v *version) IPAMConfigurations() IPAMConfigurationInformer { return &iPAMConfigurationInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } +// IPAMHandles returns a IPAMHandleInformer. +func (v *version) IPAMHandles() IPAMHandleInformer { + return &iPAMHandleInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // IPPools returns a IPPoolInformer. func (v *version) IPPools() IPPoolInformer { return &iPPoolInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} diff --git a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/ipamblock.go b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/ipamblock.go new file mode 100644 index 00000000000..3959012af59 --- /dev/null +++ b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/ipamblock.go @@ -0,0 +1,87 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// Code generated by informer-gen. DO NOT EDIT. + +package v3 + +import ( + context "context" + time "time" + + apisprojectcalicov3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + clientset "github.com/projectcalico/api/pkg/client/clientset_generated/clientset" + internalinterfaces "github.com/projectcalico/api/pkg/client/informers_generated/externalversions/internalinterfaces" + projectcalicov3 "github.com/projectcalico/api/pkg/client/listers_generated/projectcalico/v3" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// IPAMBlockInformer provides access to a shared informer and lister for +// IPAMBlocks. +type IPAMBlockInformer interface { + Informer() cache.SharedIndexInformer + Lister() projectcalicov3.IPAMBlockLister +} + +type iPAMBlockInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// NewIPAMBlockInformer constructs a new informer for IPAMBlock type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewIPAMBlockInformer(client clientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredIPAMBlockInformer(client, resyncPeriod, indexers, nil) +} + +// NewFilteredIPAMBlockInformer constructs a new informer for IPAMBlock type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredIPAMBlockInformer(client clientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.ProjectcalicoV3().IPAMBlocks().List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.ProjectcalicoV3().IPAMBlocks().Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.ProjectcalicoV3().IPAMBlocks().List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.ProjectcalicoV3().IPAMBlocks().Watch(ctx, options) + }, + }, + &apisprojectcalicov3.IPAMBlock{}, + resyncPeriod, + indexers, + ) +} + +func (f *iPAMBlockInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredIPAMBlockInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *iPAMBlockInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apisprojectcalicov3.IPAMBlock{}, f.defaultInformer) +} + +func (f *iPAMBlockInformer) Lister() projectcalicov3.IPAMBlockLister { + return projectcalicov3.NewIPAMBlockLister(f.Informer().GetIndexer()) +} diff --git a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/ipamconfiguration.go b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/ipamconfiguration.go index 44058dc2cf6..82f304b31a5 100644 --- a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/ipamconfiguration.go +++ b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/ipamconfiguration.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/ipamhandle.go b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/ipamhandle.go new file mode 100644 index 00000000000..71aa87a577c --- /dev/null +++ b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/ipamhandle.go @@ -0,0 +1,88 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// Code generated by informer-gen. DO NOT EDIT. + +package v3 + +import ( + context "context" + time "time" + + apisprojectcalicov3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + clientset "github.com/projectcalico/api/pkg/client/clientset_generated/clientset" + internalinterfaces "github.com/projectcalico/api/pkg/client/informers_generated/externalversions/internalinterfaces" + projectcalicov3 "github.com/projectcalico/api/pkg/client/listers_generated/projectcalico/v3" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// IPAMHandleInformer provides access to a shared informer and lister for +// IPAMHandles. +type IPAMHandleInformer interface { + Informer() cache.SharedIndexInformer + Lister() projectcalicov3.IPAMHandleLister +} + +type iPAMHandleInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewIPAMHandleInformer constructs a new informer for IPAMHandle type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewIPAMHandleInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredIPAMHandleInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredIPAMHandleInformer constructs a new informer for IPAMHandle type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredIPAMHandleInformer(client clientset.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.ProjectcalicoV3().IPAMHandles(namespace).List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.ProjectcalicoV3().IPAMHandles(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.ProjectcalicoV3().IPAMHandles(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.ProjectcalicoV3().IPAMHandles(namespace).Watch(ctx, options) + }, + }, + &apisprojectcalicov3.IPAMHandle{}, + resyncPeriod, + indexers, + ) +} + +func (f *iPAMHandleInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredIPAMHandleInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *iPAMHandleInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apisprojectcalicov3.IPAMHandle{}, f.defaultInformer) +} + +func (f *iPAMHandleInformer) Lister() projectcalicov3.IPAMHandleLister { + return projectcalicov3.NewIPAMHandleLister(f.Informer().GetIndexer()) +} diff --git a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/ippool.go b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/ippool.go index 9b5449e8117..cb04cd16b9d 100644 --- a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/ippool.go +++ b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/ippool.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/ipreservation.go b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/ipreservation.go index e17bcd76d8b..b66b8113872 100644 --- a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/ipreservation.go +++ b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/ipreservation.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/kubecontrollersconfiguration.go b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/kubecontrollersconfiguration.go index 615edaed946..576eaf014ee 100644 --- a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/kubecontrollersconfiguration.go +++ b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/kubecontrollersconfiguration.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/networkpolicy.go b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/networkpolicy.go index c3cadeea020..ad11e9f496c 100644 --- a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/networkpolicy.go +++ b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/networkpolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/networkset.go b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/networkset.go index 1718db73762..0971ff55180 100644 --- a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/networkset.go +++ b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/networkset.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/profile.go b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/profile.go index b2031b7040a..cbfc8d6ba0e 100644 --- a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/profile.go +++ b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/profile.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/stagedglobalnetworkpolicy.go b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/stagedglobalnetworkpolicy.go index d0e3d1dd4f6..7d5457ed754 100644 --- a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/stagedglobalnetworkpolicy.go +++ b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/stagedglobalnetworkpolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/stagedkubernetesnetworkpolicy.go b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/stagedkubernetesnetworkpolicy.go index 32db3500b55..3df5fb9c61a 100644 --- a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/stagedkubernetesnetworkpolicy.go +++ b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/stagedkubernetesnetworkpolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/stagednetworkpolicy.go b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/stagednetworkpolicy.go index b4255b1150c..5a005c72efa 100644 --- a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/stagednetworkpolicy.go +++ b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/stagednetworkpolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/tier.go b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/tier.go index 103892c1d04..3f0996db28c 100644 --- a/api/pkg/client/informers_generated/externalversions/projectcalico/v3/tier.go +++ b/api/pkg/client/informers_generated/externalversions/projectcalico/v3/tier.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/api/pkg/client/listers_generated/projectcalico/v3/bgpconfiguration.go b/api/pkg/client/listers_generated/projectcalico/v3/bgpconfiguration.go index b680b247caa..516e5d473a5 100644 --- a/api/pkg/client/listers_generated/projectcalico/v3/bgpconfiguration.go +++ b/api/pkg/client/listers_generated/projectcalico/v3/bgpconfiguration.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/api/pkg/client/listers_generated/projectcalico/v3/bgpfilter.go b/api/pkg/client/listers_generated/projectcalico/v3/bgpfilter.go index 9ab449ca5f5..8c93dbc928e 100644 --- a/api/pkg/client/listers_generated/projectcalico/v3/bgpfilter.go +++ b/api/pkg/client/listers_generated/projectcalico/v3/bgpfilter.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/api/pkg/client/listers_generated/projectcalico/v3/bgppeer.go b/api/pkg/client/listers_generated/projectcalico/v3/bgppeer.go index 853e16b7b16..93d5125b64e 100644 --- a/api/pkg/client/listers_generated/projectcalico/v3/bgppeer.go +++ b/api/pkg/client/listers_generated/projectcalico/v3/bgppeer.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/api/pkg/client/listers_generated/projectcalico/v3/blockaffinity.go b/api/pkg/client/listers_generated/projectcalico/v3/blockaffinity.go index 622db590815..1188bef6fcc 100644 --- a/api/pkg/client/listers_generated/projectcalico/v3/blockaffinity.go +++ b/api/pkg/client/listers_generated/projectcalico/v3/blockaffinity.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/api/pkg/client/listers_generated/projectcalico/v3/caliconodestatus.go b/api/pkg/client/listers_generated/projectcalico/v3/caliconodestatus.go index 94f39f9f065..de05286a47e 100644 --- a/api/pkg/client/listers_generated/projectcalico/v3/caliconodestatus.go +++ b/api/pkg/client/listers_generated/projectcalico/v3/caliconodestatus.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/api/pkg/client/listers_generated/projectcalico/v3/clusterinformation.go b/api/pkg/client/listers_generated/projectcalico/v3/clusterinformation.go index 094778ee369..a81f740d5ba 100644 --- a/api/pkg/client/listers_generated/projectcalico/v3/clusterinformation.go +++ b/api/pkg/client/listers_generated/projectcalico/v3/clusterinformation.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/api/pkg/client/listers_generated/projectcalico/v3/expansion_generated.go b/api/pkg/client/listers_generated/projectcalico/v3/expansion_generated.go index 7f39346612a..2a56e04de1c 100644 --- a/api/pkg/client/listers_generated/projectcalico/v3/expansion_generated.go +++ b/api/pkg/client/listers_generated/projectcalico/v3/expansion_generated.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by lister-gen. DO NOT EDIT. @@ -44,10 +44,22 @@ type GlobalNetworkSetListerExpansion interface{} // HostEndpointLister. type HostEndpointListerExpansion interface{} +// IPAMBlockListerExpansion allows custom methods to be added to +// IPAMBlockLister. +type IPAMBlockListerExpansion interface{} + // IPAMConfigurationListerExpansion allows custom methods to be added to // IPAMConfigurationLister. type IPAMConfigurationListerExpansion interface{} +// IPAMHandleListerExpansion allows custom methods to be added to +// IPAMHandleLister. +type IPAMHandleListerExpansion interface{} + +// IPAMHandleNamespaceListerExpansion allows custom methods to be added to +// IPAMHandleNamespaceLister. +type IPAMHandleNamespaceListerExpansion interface{} + // IPPoolListerExpansion allows custom methods to be added to // IPPoolLister. type IPPoolListerExpansion interface{} diff --git a/api/pkg/client/listers_generated/projectcalico/v3/felixconfiguration.go b/api/pkg/client/listers_generated/projectcalico/v3/felixconfiguration.go index 3f7ff8b2d84..e1906a94b92 100644 --- a/api/pkg/client/listers_generated/projectcalico/v3/felixconfiguration.go +++ b/api/pkg/client/listers_generated/projectcalico/v3/felixconfiguration.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/api/pkg/client/listers_generated/projectcalico/v3/globalnetworkpolicy.go b/api/pkg/client/listers_generated/projectcalico/v3/globalnetworkpolicy.go index c281750257c..4e23f59f86a 100644 --- a/api/pkg/client/listers_generated/projectcalico/v3/globalnetworkpolicy.go +++ b/api/pkg/client/listers_generated/projectcalico/v3/globalnetworkpolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/api/pkg/client/listers_generated/projectcalico/v3/globalnetworkset.go b/api/pkg/client/listers_generated/projectcalico/v3/globalnetworkset.go index 39c33164eeb..a5bdc3f2d75 100644 --- a/api/pkg/client/listers_generated/projectcalico/v3/globalnetworkset.go +++ b/api/pkg/client/listers_generated/projectcalico/v3/globalnetworkset.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/api/pkg/client/listers_generated/projectcalico/v3/hostendpoint.go b/api/pkg/client/listers_generated/projectcalico/v3/hostendpoint.go index 28030ab2f06..23af7a8f279 100644 --- a/api/pkg/client/listers_generated/projectcalico/v3/hostendpoint.go +++ b/api/pkg/client/listers_generated/projectcalico/v3/hostendpoint.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/api/pkg/client/listers_generated/projectcalico/v3/ipamblock.go b/api/pkg/client/listers_generated/projectcalico/v3/ipamblock.go new file mode 100644 index 00000000000..059ebefd3c2 --- /dev/null +++ b/api/pkg/client/listers_generated/projectcalico/v3/ipamblock.go @@ -0,0 +1,34 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// Code generated by lister-gen. DO NOT EDIT. + +package v3 + +import ( + projectcalicov3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" +) + +// IPAMBlockLister helps list IPAMBlocks. +// All objects returned here must be treated as read-only. +type IPAMBlockLister interface { + // List lists all IPAMBlocks in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*projectcalicov3.IPAMBlock, err error) + // Get retrieves the IPAMBlock from the index for a given name. + // Objects returned here must be treated as read-only. + Get(name string) (*projectcalicov3.IPAMBlock, error) + IPAMBlockListerExpansion +} + +// iPAMBlockLister implements the IPAMBlockLister interface. +type iPAMBlockLister struct { + listers.ResourceIndexer[*projectcalicov3.IPAMBlock] +} + +// NewIPAMBlockLister returns a new IPAMBlockLister. +func NewIPAMBlockLister(indexer cache.Indexer) IPAMBlockLister { + return &iPAMBlockLister{listers.New[*projectcalicov3.IPAMBlock](indexer, projectcalicov3.Resource("ipamblock"))} +} diff --git a/api/pkg/client/listers_generated/projectcalico/v3/ipamconfiguration.go b/api/pkg/client/listers_generated/projectcalico/v3/ipamconfiguration.go index bac0cdb1d9d..5854f8bd673 100644 --- a/api/pkg/client/listers_generated/projectcalico/v3/ipamconfiguration.go +++ b/api/pkg/client/listers_generated/projectcalico/v3/ipamconfiguration.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/api/pkg/client/listers_generated/projectcalico/v3/ipamhandle.go b/api/pkg/client/listers_generated/projectcalico/v3/ipamhandle.go new file mode 100644 index 00000000000..ee48337a12f --- /dev/null +++ b/api/pkg/client/listers_generated/projectcalico/v3/ipamhandle.go @@ -0,0 +1,56 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// Code generated by lister-gen. DO NOT EDIT. + +package v3 + +import ( + projectcalicov3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" +) + +// IPAMHandleLister helps list IPAMHandles. +// All objects returned here must be treated as read-only. +type IPAMHandleLister interface { + // List lists all IPAMHandles in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*projectcalicov3.IPAMHandle, err error) + // IPAMHandles returns an object that can list and get IPAMHandles. + IPAMHandles(namespace string) IPAMHandleNamespaceLister + IPAMHandleListerExpansion +} + +// iPAMHandleLister implements the IPAMHandleLister interface. +type iPAMHandleLister struct { + listers.ResourceIndexer[*projectcalicov3.IPAMHandle] +} + +// NewIPAMHandleLister returns a new IPAMHandleLister. +func NewIPAMHandleLister(indexer cache.Indexer) IPAMHandleLister { + return &iPAMHandleLister{listers.New[*projectcalicov3.IPAMHandle](indexer, projectcalicov3.Resource("ipamhandle"))} +} + +// IPAMHandles returns an object that can list and get IPAMHandles. +func (s *iPAMHandleLister) IPAMHandles(namespace string) IPAMHandleNamespaceLister { + return iPAMHandleNamespaceLister{listers.NewNamespaced[*projectcalicov3.IPAMHandle](s.ResourceIndexer, namespace)} +} + +// IPAMHandleNamespaceLister helps list and get IPAMHandles. +// All objects returned here must be treated as read-only. +type IPAMHandleNamespaceLister interface { + // List lists all IPAMHandles in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*projectcalicov3.IPAMHandle, err error) + // Get retrieves the IPAMHandle from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*projectcalicov3.IPAMHandle, error) + IPAMHandleNamespaceListerExpansion +} + +// iPAMHandleNamespaceLister implements the IPAMHandleNamespaceLister +// interface. +type iPAMHandleNamespaceLister struct { + listers.ResourceIndexer[*projectcalicov3.IPAMHandle] +} diff --git a/api/pkg/client/listers_generated/projectcalico/v3/ippool.go b/api/pkg/client/listers_generated/projectcalico/v3/ippool.go index 125a55f5fc3..81b95523844 100644 --- a/api/pkg/client/listers_generated/projectcalico/v3/ippool.go +++ b/api/pkg/client/listers_generated/projectcalico/v3/ippool.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/api/pkg/client/listers_generated/projectcalico/v3/ipreservation.go b/api/pkg/client/listers_generated/projectcalico/v3/ipreservation.go index a66ad28e63c..33a865fbb7e 100644 --- a/api/pkg/client/listers_generated/projectcalico/v3/ipreservation.go +++ b/api/pkg/client/listers_generated/projectcalico/v3/ipreservation.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/api/pkg/client/listers_generated/projectcalico/v3/kubecontrollersconfiguration.go b/api/pkg/client/listers_generated/projectcalico/v3/kubecontrollersconfiguration.go index 5f4ec80a563..7b1499b1c53 100644 --- a/api/pkg/client/listers_generated/projectcalico/v3/kubecontrollersconfiguration.go +++ b/api/pkg/client/listers_generated/projectcalico/v3/kubecontrollersconfiguration.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/api/pkg/client/listers_generated/projectcalico/v3/networkpolicy.go b/api/pkg/client/listers_generated/projectcalico/v3/networkpolicy.go index 12452c542e9..084eeda7cc7 100644 --- a/api/pkg/client/listers_generated/projectcalico/v3/networkpolicy.go +++ b/api/pkg/client/listers_generated/projectcalico/v3/networkpolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/api/pkg/client/listers_generated/projectcalico/v3/networkset.go b/api/pkg/client/listers_generated/projectcalico/v3/networkset.go index 0778a7dc9a3..d697285039c 100644 --- a/api/pkg/client/listers_generated/projectcalico/v3/networkset.go +++ b/api/pkg/client/listers_generated/projectcalico/v3/networkset.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/api/pkg/client/listers_generated/projectcalico/v3/profile.go b/api/pkg/client/listers_generated/projectcalico/v3/profile.go index adf7a51ad02..85ecac5597f 100644 --- a/api/pkg/client/listers_generated/projectcalico/v3/profile.go +++ b/api/pkg/client/listers_generated/projectcalico/v3/profile.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/api/pkg/client/listers_generated/projectcalico/v3/stagedglobalnetworkpolicy.go b/api/pkg/client/listers_generated/projectcalico/v3/stagedglobalnetworkpolicy.go index 382b8b2b037..ec62800f2ec 100644 --- a/api/pkg/client/listers_generated/projectcalico/v3/stagedglobalnetworkpolicy.go +++ b/api/pkg/client/listers_generated/projectcalico/v3/stagedglobalnetworkpolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/api/pkg/client/listers_generated/projectcalico/v3/stagedkubernetesnetworkpolicy.go b/api/pkg/client/listers_generated/projectcalico/v3/stagedkubernetesnetworkpolicy.go index b2f2455f465..5a60b1a182f 100644 --- a/api/pkg/client/listers_generated/projectcalico/v3/stagedkubernetesnetworkpolicy.go +++ b/api/pkg/client/listers_generated/projectcalico/v3/stagedkubernetesnetworkpolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/api/pkg/client/listers_generated/projectcalico/v3/stagednetworkpolicy.go b/api/pkg/client/listers_generated/projectcalico/v3/stagednetworkpolicy.go index c255a2f54d3..3a00d4d1a4e 100644 --- a/api/pkg/client/listers_generated/projectcalico/v3/stagednetworkpolicy.go +++ b/api/pkg/client/listers_generated/projectcalico/v3/stagednetworkpolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/api/pkg/client/listers_generated/projectcalico/v3/tier.go b/api/pkg/client/listers_generated/projectcalico/v3/tier.go index 16282e9a60f..cdde4a3c7b7 100644 --- a/api/pkg/client/listers_generated/projectcalico/v3/tier.go +++ b/api/pkg/client/listers_generated/projectcalico/v3/tier.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/api/pkg/defaults/defaults.go b/api/pkg/defaults/defaults.go new file mode 100644 index 00000000000..bc1cb130f74 --- /dev/null +++ b/api/pkg/defaults/defaults.go @@ -0,0 +1,37 @@ +// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// +// 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 defaults + +import ( + "fmt" + + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Default applies defaulting logic to the given object if it is a type that we +// know about. It returns true if it made any changes to the object. +func Default(obj v1.Object) (bool, error) { + switch o := obj.(type) { + case *v3.NetworkPolicy: + return defaultNetworkPolicy(o) + case *v3.GlobalNetworkPolicy: + return defaultGlobalNetworkPolicy(o) + case *v3.StagedNetworkPolicy: + return defaultStagedNetworkPolicy(o) + case *v3.StagedGlobalNetworkPolicy: + return defaultStagedGlobalNetworkPolicy(o) + } + return false, fmt.Errorf("no defaulting logic for object of type %T", obj) +} diff --git a/libcalico-go/lib/converter/doc.go b/api/pkg/defaults/defaults_suite_test.go similarity index 61% rename from libcalico-go/lib/converter/doc.go rename to api/pkg/defaults/defaults_suite_test.go index d9a15bc28fc..b43a9b02330 100644 --- a/libcalico-go/lib/converter/doc.go +++ b/api/pkg/defaults/defaults_suite_test.go @@ -1,19 +1,26 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - +// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// // 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 +// 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 defaults_test + +import ( + "testing" + + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" +) -/* -Package converter implements adaptors for conversion between API and Backend -models. -*/ -package converter +func TestDefaults(t *testing.T) { + gomega.RegisterFailHandler(ginkgo.Fail) + ginkgo.RunSpecs(t, "Defaults Suite") +} diff --git a/api/pkg/defaults/defaults_test.go b/api/pkg/defaults/defaults_test.go new file mode 100644 index 00000000000..a770d91587b --- /dev/null +++ b/api/pkg/defaults/defaults_test.go @@ -0,0 +1,272 @@ +// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// +// 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 defaults_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "github.com/projectcalico/api/pkg/defaults" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var _ = Describe("Default", func() { + Context("for unsupported types", func() { + It("should return an error", func() { + obj := &v3.FelixConfiguration{ObjectMeta: metav1.ObjectMeta{Name: "test"}} + _, err := defaults.Default(obj) + Expect(err).To(HaveOccurred()) + }) + }) + + // policyTypeTests defines shared test cases for types/policyTypes defaulting. + // Each test case is {description, ingressRules, egressRules, expectedTypes}. + type policyTypeTest struct { + description string + ingress []v3.Rule + egress []v3.Rule + expectedTypes []v3.PolicyType + } + + policyTypeTests := []policyTypeTest{ + { + description: "no rules - defaults to Ingress", + expectedTypes: []v3.PolicyType{v3.PolicyTypeIngress}, + }, + { + description: "ingress only - defaults to Ingress", + ingress: []v3.Rule{{}}, + expectedTypes: []v3.PolicyType{v3.PolicyTypeIngress}, + }, + { + description: "egress only - defaults to Egress", + egress: []v3.Rule{{}}, + expectedTypes: []v3.PolicyType{v3.PolicyTypeEgress}, + }, + { + description: "both ingress and egress - defaults to both", + ingress: []v3.Rule{{}}, + egress: []v3.Rule{{}}, + expectedTypes: []v3.PolicyType{v3.PolicyTypeIngress, v3.PolicyTypeEgress}, + }, + } + + // tierLabelTests defines shared test cases for tier label defaulting. + type tierLabelTest struct { + description string + tier string + existingLabel string + expectedLabel string + expectChanged bool + } + + tierLabelTests := []tierLabelTest{ + { + description: "empty tier defaults label to 'default'", + tier: "", + expectedLabel: "default", + expectChanged: true, + }, + { + description: "explicit tier sets label", + tier: "net-sec", + expectedLabel: "net-sec", + expectChanged: true, + }, + { + description: "label already correct returns no change", + tier: "net-sec", + existingLabel: "net-sec", + expectedLabel: "net-sec", + expectChanged: false, + }, + { + description: "label already 'default' with empty tier returns no change", + tier: "", + existingLabel: "default", + expectedLabel: "default", + expectChanged: false, + }, + } + + Context("NetworkPolicy", func() { + for _, tt := range policyTypeTests { + tt := tt + It("types: "+tt.description, func() { + p := &v3.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + Spec: v3.NetworkPolicySpec{ + Ingress: tt.ingress, + Egress: tt.egress, + }, + } + changed, err := defaults.Default(p) + Expect(err).NotTo(HaveOccurred()) + Expect(changed).To(BeTrue()) + Expect(p.Spec.Types).To(Equal(tt.expectedTypes)) + }) + } + + It("types: already set - not overwritten", func() { + p := &v3.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + Spec: v3.NetworkPolicySpec{ + Types: []v3.PolicyType{v3.PolicyTypeEgress}, + }, + } + changed, err := defaults.Default(p) + Expect(err).NotTo(HaveOccurred()) + // Still changed because tier label is set. + Expect(p.Spec.Types).To(Equal([]v3.PolicyType{v3.PolicyTypeEgress})) + _ = changed + }) + + for _, tt := range tierLabelTests { + tt := tt + It("tier label: "+tt.description, func() { + p := &v3.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + Spec: v3.NetworkPolicySpec{ + Tier: tt.tier, + Types: []v3.PolicyType{v3.PolicyTypeIngress}, // pre-set to isolate tier label change + }, + } + if tt.existingLabel != "" { + p.Labels = map[string]string{v3.LabelTier: tt.existingLabel} + } + changed, err := defaults.Default(p) + Expect(err).NotTo(HaveOccurred()) + Expect(changed).To(Equal(tt.expectChanged)) + Expect(p.Labels[v3.LabelTier]).To(Equal(tt.expectedLabel)) + }) + } + }) + + Context("GlobalNetworkPolicy", func() { + for _, tt := range policyTypeTests { + tt := tt + It("types: "+tt.description, func() { + p := &v3.GlobalNetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + Spec: v3.GlobalNetworkPolicySpec{ + Ingress: tt.ingress, + Egress: tt.egress, + }, + } + changed, err := defaults.Default(p) + Expect(err).NotTo(HaveOccurred()) + Expect(changed).To(BeTrue()) + Expect(p.Spec.Types).To(Equal(tt.expectedTypes)) + }) + } + + for _, tt := range tierLabelTests { + tt := tt + It("tier label: "+tt.description, func() { + p := &v3.GlobalNetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + Spec: v3.GlobalNetworkPolicySpec{ + Tier: tt.tier, + Types: []v3.PolicyType{v3.PolicyTypeIngress}, + }, + } + if tt.existingLabel != "" { + p.Labels = map[string]string{v3.LabelTier: tt.existingLabel} + } + changed, err := defaults.Default(p) + Expect(err).NotTo(HaveOccurred()) + Expect(changed).To(Equal(tt.expectChanged)) + Expect(p.Labels[v3.LabelTier]).To(Equal(tt.expectedLabel)) + }) + } + }) + + Context("StagedNetworkPolicy", func() { + for _, tt := range policyTypeTests { + tt := tt + It("types: "+tt.description, func() { + p := &v3.StagedNetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + Spec: v3.StagedNetworkPolicySpec{ + Ingress: tt.ingress, + Egress: tt.egress, + }, + } + changed, err := defaults.Default(p) + Expect(err).NotTo(HaveOccurred()) + Expect(changed).To(BeTrue()) + Expect(p.Spec.Types).To(Equal(tt.expectedTypes)) + }) + } + + for _, tt := range tierLabelTests { + tt := tt + It("tier label: "+tt.description, func() { + p := &v3.StagedNetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + Spec: v3.StagedNetworkPolicySpec{ + Tier: tt.tier, + Types: []v3.PolicyType{v3.PolicyTypeIngress}, + }, + } + if tt.existingLabel != "" { + p.Labels = map[string]string{v3.LabelTier: tt.existingLabel} + } + changed, err := defaults.Default(p) + Expect(err).NotTo(HaveOccurred()) + Expect(changed).To(Equal(tt.expectChanged)) + Expect(p.Labels[v3.LabelTier]).To(Equal(tt.expectedLabel)) + }) + } + }) + + Context("StagedGlobalNetworkPolicy", func() { + for _, tt := range policyTypeTests { + tt := tt + It("types: "+tt.description, func() { + p := &v3.StagedGlobalNetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + Spec: v3.StagedGlobalNetworkPolicySpec{ + Ingress: tt.ingress, + Egress: tt.egress, + }, + } + changed, err := defaults.Default(p) + Expect(err).NotTo(HaveOccurred()) + Expect(changed).To(BeTrue()) + Expect(p.Spec.Types).To(Equal(tt.expectedTypes)) + }) + } + + for _, tt := range tierLabelTests { + tt := tt + It("tier label: "+tt.description, func() { + p := &v3.StagedGlobalNetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + Spec: v3.StagedGlobalNetworkPolicySpec{ + Tier: tt.tier, + Types: []v3.PolicyType{v3.PolicyTypeIngress}, + }, + } + if tt.existingLabel != "" { + p.Labels = map[string]string{v3.LabelTier: tt.existingLabel} + } + changed, err := defaults.Default(p) + Expect(err).NotTo(HaveOccurred()) + Expect(changed).To(Equal(tt.expectChanged)) + Expect(p.Labels[v3.LabelTier]).To(Equal(tt.expectedLabel)) + }) + } + }) +}) diff --git a/api/pkg/defaults/networkpolicy.go b/api/pkg/defaults/networkpolicy.go new file mode 100644 index 00000000000..453b7b30f35 --- /dev/null +++ b/api/pkg/defaults/networkpolicy.go @@ -0,0 +1,82 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. +// +// 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 defaults + +import ( + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func defaultNetworkPolicy(p *v3.NetworkPolicy) (bool, error) { + changed := defaultPolicyTypesField(p.Spec.Ingress, p.Spec.Egress, &p.Spec.Types) + changed = setTierLabel(p, p.Spec.Tier) || changed + return changed, nil +} + +func defaultGlobalNetworkPolicy(p *v3.GlobalNetworkPolicy) (bool, error) { + changed := defaultPolicyTypesField(p.Spec.Ingress, p.Spec.Egress, &p.Spec.Types) + changed = setTierLabel(p, p.Spec.Tier) || changed + return changed, nil +} + +func defaultStagedNetworkPolicy(p *v3.StagedNetworkPolicy) (bool, error) { + changed := defaultPolicyTypesField(p.Spec.Ingress, p.Spec.Egress, &p.Spec.Types) + changed = setTierLabel(p, p.Spec.Tier) || changed + return changed, nil +} + +func defaultStagedGlobalNetworkPolicy(p *v3.StagedGlobalNetworkPolicy) (bool, error) { + changed := defaultPolicyTypesField(p.Spec.Ingress, p.Spec.Egress, &p.Spec.Types) + changed = setTierLabel(p, p.Spec.Tier) || changed + return changed, nil +} + +func setTierLabel(obj v1.Object, tier string) bool { + if tier == "" { + tier = "default" + } + labels := obj.GetLabels() + if labels == nil { + labels = map[string]string{} + } + if labels[v3.LabelTier] != tier { + labels[v3.LabelTier] = tier + obj.SetLabels(labels) + return true + } + return false +} + +func defaultPolicyTypesField(ingressRules, egressRules []v3.Rule, types *[]v3.PolicyType) bool { + if len(*types) == 0 { + // Default the Types field according to what inbound and outbound rules are present + // in the policy. + if len(egressRules) == 0 { + // Policy has no egress rules, so apply this policy to ingress only. (Note: + // intentionally including the case where the policy also has no ingress + // rules.) + *types = []v3.PolicyType{v3.PolicyTypeIngress} + } else if len(ingressRules) == 0 { + // Policy has egress rules but no ingress rules, so apply this policy to + // egress only. + *types = []v3.PolicyType{v3.PolicyTypeEgress} + } else { + // Policy has both ingress and egress rules, so apply this policy to both + // ingress and egress. + *types = []v3.PolicyType{v3.PolicyTypeIngress, v3.PolicyTypeEgress} + } + return true + } + return false +} diff --git a/api/pkg/lib/numorstring/numorstring_suite_test.go b/api/pkg/lib/numorstring/numorstring_suite_test.go index 75badd9a69b..3a5153ba8bf 100644 --- a/api/pkg/lib/numorstring/numorstring_suite_test.go +++ b/api/pkg/lib/numorstring/numorstring_suite_test.go @@ -16,13 +16,13 @@ package numorstring_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" ) func TestNumorstring(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/numorstring_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Numorstring Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/numorstring_suite.xml" + ginkgo.RunSpecs(t, "Numorstring Suite", suiteConfig, reporterConfig) } diff --git a/api/pkg/lib/numorstring/numorstring_test.go b/api/pkg/lib/numorstring/numorstring_test.go index aa1acb06f77..fb5f3332528 100644 --- a/api/pkg/lib/numorstring/numorstring_test.go +++ b/api/pkg/lib/numorstring/numorstring_test.go @@ -19,7 +19,7 @@ import ( "fmt" "reflect" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/api/pkg/lib/numorstring" ) diff --git a/api/pkg/openapi/generated.openapi.go b/api/pkg/openapi/generated.openapi.go index e05776c7f62..670f9923ee9 100644 --- a/api/pkg/openapi/generated.openapi.go +++ b/api/pkg/openapi/generated.openapi.go @@ -1,7 +1,7 @@ //go:build !ignore_autogenerated // +build !ignore_autogenerated -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // Code generated by openapi-gen. DO NOT EDIT. @@ -18,6 +18,7 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ + "github.com/projectcalico/api/pkg/apis/projectcalico/v3.AllocationAttribute": schema_pkg_apis_projectcalico_v3_AllocationAttribute(ref), "github.com/projectcalico/api/pkg/apis/projectcalico/v3.AutoHostEndpointConfig": schema_pkg_apis_projectcalico_v3_AutoHostEndpointConfig(ref), "github.com/projectcalico/api/pkg/apis/projectcalico/v3.BGPConfiguration": schema_pkg_apis_projectcalico_v3_BGPConfiguration(ref), "github.com/projectcalico/api/pkg/apis/projectcalico/v3.BGPConfigurationList": schema_pkg_apis_projectcalico_v3_BGPConfigurationList(ref), @@ -71,13 +72,20 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/projectcalico/api/pkg/apis/projectcalico/v3.HostEndpointList": schema_pkg_apis_projectcalico_v3_HostEndpointList(ref), "github.com/projectcalico/api/pkg/apis/projectcalico/v3.HostEndpointSpec": schema_pkg_apis_projectcalico_v3_HostEndpointSpec(ref), "github.com/projectcalico/api/pkg/apis/projectcalico/v3.ICMPFields": schema_pkg_apis_projectcalico_v3_ICMPFields(ref), + "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPAMBlock": schema_pkg_apis_projectcalico_v3_IPAMBlock(ref), + "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPAMBlockList": schema_pkg_apis_projectcalico_v3_IPAMBlockList(ref), + "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPAMBlockSpec": schema_pkg_apis_projectcalico_v3_IPAMBlockSpec(ref), "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPAMConfiguration": schema_pkg_apis_projectcalico_v3_IPAMConfiguration(ref), "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPAMConfigurationList": schema_pkg_apis_projectcalico_v3_IPAMConfigurationList(ref), "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPAMConfigurationSpec": schema_pkg_apis_projectcalico_v3_IPAMConfigurationSpec(ref), + "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPAMHandle": schema_pkg_apis_projectcalico_v3_IPAMHandle(ref), + "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPAMHandleList": schema_pkg_apis_projectcalico_v3_IPAMHandleList(ref), + "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPAMHandleSpec": schema_pkg_apis_projectcalico_v3_IPAMHandleSpec(ref), "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPIPConfiguration": schema_pkg_apis_projectcalico_v3_IPIPConfiguration(ref), "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPPool": schema_pkg_apis_projectcalico_v3_IPPool(ref), "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPPoolList": schema_pkg_apis_projectcalico_v3_IPPoolList(ref), "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPPoolSpec": schema_pkg_apis_projectcalico_v3_IPPoolSpec(ref), + "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPPoolStatus": schema_pkg_apis_projectcalico_v3_IPPoolStatus(ref), "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPReservation": schema_pkg_apis_projectcalico_v3_IPReservation(ref), "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPReservationList": schema_pkg_apis_projectcalico_v3_IPReservationList(ref), "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPReservationSpec": schema_pkg_apis_projectcalico_v3_IPReservationSpec(ref), @@ -86,6 +94,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/projectcalico/api/pkg/apis/projectcalico/v3.KubeControllersConfigurationSpec": schema_pkg_apis_projectcalico_v3_KubeControllersConfigurationSpec(ref), "github.com/projectcalico/api/pkg/apis/projectcalico/v3.KubeControllersConfigurationStatus": schema_pkg_apis_projectcalico_v3_KubeControllersConfigurationStatus(ref), "github.com/projectcalico/api/pkg/apis/projectcalico/v3.LoadBalancerControllerConfig": schema_pkg_apis_projectcalico_v3_LoadBalancerControllerConfig(ref), + "github.com/projectcalico/api/pkg/apis/projectcalico/v3.MigrationControllerConfig": schema_pkg_apis_projectcalico_v3_MigrationControllerConfig(ref), "github.com/projectcalico/api/pkg/apis/projectcalico/v3.NamespaceControllerConfig": schema_pkg_apis_projectcalico_v3_NamespaceControllerConfig(ref), "github.com/projectcalico/api/pkg/apis/projectcalico/v3.NetworkPolicy": schema_pkg_apis_projectcalico_v3_NetworkPolicy(ref), "github.com/projectcalico/api/pkg/apis/projectcalico/v3.NetworkPolicyList": schema_pkg_apis_projectcalico_v3_NetworkPolicyList(ref), @@ -157,9 +166,12 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "k8s.io/api/core/v1.ConfigMapProjection": schema_k8sio_api_core_v1_ConfigMapProjection(ref), "k8s.io/api/core/v1.ConfigMapVolumeSource": schema_k8sio_api_core_v1_ConfigMapVolumeSource(ref), "k8s.io/api/core/v1.Container": schema_k8sio_api_core_v1_Container(ref), + "k8s.io/api/core/v1.ContainerExtendedResourceRequest": schema_k8sio_api_core_v1_ContainerExtendedResourceRequest(ref), "k8s.io/api/core/v1.ContainerImage": schema_k8sio_api_core_v1_ContainerImage(ref), "k8s.io/api/core/v1.ContainerPort": schema_k8sio_api_core_v1_ContainerPort(ref), "k8s.io/api/core/v1.ContainerResizePolicy": schema_k8sio_api_core_v1_ContainerResizePolicy(ref), + "k8s.io/api/core/v1.ContainerRestartRule": schema_k8sio_api_core_v1_ContainerRestartRule(ref), + "k8s.io/api/core/v1.ContainerRestartRuleOnExitCodes": schema_k8sio_api_core_v1_ContainerRestartRuleOnExitCodes(ref), "k8s.io/api/core/v1.ContainerState": schema_k8sio_api_core_v1_ContainerState(ref), "k8s.io/api/core/v1.ContainerStateRunning": schema_k8sio_api_core_v1_ContainerStateRunning(ref), "k8s.io/api/core/v1.ContainerStateTerminated": schema_k8sio_api_core_v1_ContainerStateTerminated(ref), @@ -188,6 +200,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "k8s.io/api/core/v1.EventSource": schema_k8sio_api_core_v1_EventSource(ref), "k8s.io/api/core/v1.ExecAction": schema_k8sio_api_core_v1_ExecAction(ref), "k8s.io/api/core/v1.FCVolumeSource": schema_k8sio_api_core_v1_FCVolumeSource(ref), + "k8s.io/api/core/v1.FileKeySelector": schema_k8sio_api_core_v1_FileKeySelector(ref), "k8s.io/api/core/v1.FlexPersistentVolumeSource": schema_k8sio_api_core_v1_FlexPersistentVolumeSource(ref), "k8s.io/api/core/v1.FlexVolumeSource": schema_k8sio_api_core_v1_FlexVolumeSource(ref), "k8s.io/api/core/v1.FlockerVolumeSource": schema_k8sio_api_core_v1_FlockerVolumeSource(ref), @@ -263,10 +276,12 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "k8s.io/api/core/v1.PodAffinityTerm": schema_k8sio_api_core_v1_PodAffinityTerm(ref), "k8s.io/api/core/v1.PodAntiAffinity": schema_k8sio_api_core_v1_PodAntiAffinity(ref), "k8s.io/api/core/v1.PodAttachOptions": schema_k8sio_api_core_v1_PodAttachOptions(ref), + "k8s.io/api/core/v1.PodCertificateProjection": schema_k8sio_api_core_v1_PodCertificateProjection(ref), "k8s.io/api/core/v1.PodCondition": schema_k8sio_api_core_v1_PodCondition(ref), "k8s.io/api/core/v1.PodDNSConfig": schema_k8sio_api_core_v1_PodDNSConfig(ref), "k8s.io/api/core/v1.PodDNSConfigOption": schema_k8sio_api_core_v1_PodDNSConfigOption(ref), "k8s.io/api/core/v1.PodExecOptions": schema_k8sio_api_core_v1_PodExecOptions(ref), + "k8s.io/api/core/v1.PodExtendedResourceClaimStatus": schema_k8sio_api_core_v1_PodExtendedResourceClaimStatus(ref), "k8s.io/api/core/v1.PodIP": schema_k8sio_api_core_v1_PodIP(ref), "k8s.io/api/core/v1.PodList": schema_k8sio_api_core_v1_PodList(ref), "k8s.io/api/core/v1.PodLogOptions": schema_k8sio_api_core_v1_PodLogOptions(ref), @@ -450,6 +465,54 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA } } +func schema_pkg_apis_projectcalico_v3_AllocationAttribute(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "handle_id": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "secondary": { + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "alternate": { + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + func schema_pkg_apis_projectcalico_v3_AutoHostEndpointConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -458,7 +521,7 @@ func schema_pkg_apis_projectcalico_v3_AutoHostEndpointConfig(ref common.Referenc Properties: map[string]spec.Schema{ "autoCreate": { SchemaProps: spec.SchemaProps{ - Description: "AutoCreate enables automatic creation of host endpoints for every node. [Default: Disabled]", + Description: "AutoCreate enables automatic creation of host endpoints for every node. [Default: Disabled] Valid values are: \"Enabled\", \"Disabled\".", Type: []string{"string"}, Format: "", }, @@ -524,6 +587,7 @@ func schema_pkg_apis_projectcalico_v3_BGPConfiguration(ref common.ReferenceCallb }, }, }, + Required: []string{"metadata", "spec"}, }, }, Dependencies: []string{ @@ -572,7 +636,7 @@ func schema_pkg_apis_projectcalico_v3_BGPConfigurationList(ref common.ReferenceC }, }, }, - Required: []string{"items"}, + Required: []string{"metadata", "items"}, }, }, Dependencies: []string{ @@ -589,7 +653,7 @@ func schema_pkg_apis_projectcalico_v3_BGPConfigurationSpec(ref common.ReferenceC Properties: map[string]spec.Schema{ "logSeverityScreen": { SchemaProps: spec.SchemaProps{ - Description: "LogSeverityScreen is the log severity above which logs are sent to the stdout. [Default: INFO]", + Description: "LogSeverityScreen is the log severity above which logs are sent to the stdout. [Default: Info]", Type: []string{"string"}, Format: "", }, @@ -609,6 +673,11 @@ func schema_pkg_apis_projectcalico_v3_BGPConfigurationSpec(ref common.ReferenceC }, }, "serviceLoadBalancerIPs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, SchemaProps: spec.SchemaProps{ Description: "ServiceLoadBalancerIPs are the CIDR blocks for Kubernetes Service LoadBalancer IPs. Kubernetes Service status.LoadBalancer.Ingress IPs will only be advertised if they are within one of these blocks.", Type: []string{"array"}, @@ -623,6 +692,11 @@ func schema_pkg_apis_projectcalico_v3_BGPConfigurationSpec(ref common.ReferenceC }, }, "serviceExternalIPs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, SchemaProps: spec.SchemaProps{ Description: "ServiceExternalIPs are the CIDR blocks for Kubernetes Service External IPs. Kubernetes Service ExternalIPs will only be advertised if they are within one of these blocks.", Type: []string{"array"}, @@ -637,6 +711,11 @@ func schema_pkg_apis_projectcalico_v3_BGPConfigurationSpec(ref common.ReferenceC }, }, "serviceClusterIPs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, SchemaProps: spec.SchemaProps{ Description: "ServiceClusterIPs are the CIDR blocks from which service cluster IPs are allocated. If specified, Calico will advertise these blocks, as well as any cluster IPs within them.", Type: []string{"array"}, @@ -658,6 +737,11 @@ func schema_pkg_apis_projectcalico_v3_BGPConfigurationSpec(ref common.ReferenceC }, }, "communities": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, SchemaProps: spec.SchemaProps{ Description: "Communities is a list of BGP community values and their arbitrary names for tagging routes.", Type: []string{"array"}, @@ -672,6 +756,11 @@ func schema_pkg_apis_projectcalico_v3_BGPConfigurationSpec(ref common.ReferenceC }, }, "prefixAdvertisements": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, SchemaProps: spec.SchemaProps{ Description: "PrefixAdvertisements contains per-prefix advertisement configuration.", Type: []string{"array"}, @@ -712,6 +801,11 @@ func schema_pkg_apis_projectcalico_v3_BGPConfigurationSpec(ref common.ReferenceC }, }, "ignoredInterfaces": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, SchemaProps: spec.SchemaProps{ Description: "IgnoredInterfaces indicates the network interfaces that needs to be excluded when reading device routes.", Type: []string{"array"}, @@ -829,6 +923,7 @@ func schema_pkg_apis_projectcalico_v3_BGPFilter(ref common.ReferenceCallback) co }, }, }, + Required: []string{"metadata", "spec"}, }, }, Dependencies: []string{ @@ -877,7 +972,7 @@ func schema_pkg_apis_projectcalico_v3_BGPFilterList(ref common.ReferenceCallback }, }, }, - Required: []string{"items"}, + Required: []string{"metadata", "items"}, }, }, Dependencies: []string{ @@ -905,6 +1000,11 @@ func schema_pkg_apis_projectcalico_v3_BGPFilterPrefixLengthV4(ref common.Referen }, }, }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, }, } } @@ -929,6 +1029,11 @@ func schema_pkg_apis_projectcalico_v3_BGPFilterPrefixLengthV6(ref common.Referen }, }, }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, }, } } @@ -979,6 +1084,11 @@ func schema_pkg_apis_projectcalico_v3_BGPFilterRuleV4(ref common.ReferenceCallba }, Required: []string{"action"}, }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, }, Dependencies: []string{ "github.com/projectcalico/api/pkg/apis/projectcalico/v3.BGPFilterPrefixLengthV4"}, @@ -1031,6 +1141,11 @@ func schema_pkg_apis_projectcalico_v3_BGPFilterRuleV6(ref common.ReferenceCallba }, Required: []string{"action"}, }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, }, Dependencies: []string{ "github.com/projectcalico/api/pkg/apis/projectcalico/v3.BGPFilterPrefixLengthV6"}, @@ -1162,6 +1277,7 @@ func schema_pkg_apis_projectcalico_v3_BGPPeer(ref common.ReferenceCallback) comm }, }, }, + Required: []string{"metadata", "spec"}, }, }, Dependencies: []string{ @@ -1210,7 +1326,7 @@ func schema_pkg_apis_projectcalico_v3_BGPPeerList(ref common.ReferenceCallback) }, }, }, - Required: []string{"items"}, + Required: []string{"metadata", "items"}, }, }, Dependencies: []string{ @@ -1455,19 +1571,18 @@ func schema_pkg_apis_projectcalico_v3_BlockAffinity(ref common.ReferenceCallback }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object's metadata.", - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Specification of the BlockAffinity.", - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/api/pkg/apis/projectcalico/v3.BlockAffinitySpec"), + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/api/pkg/apis/projectcalico/v3.BlockAffinitySpec"), }, }, }, + Required: []string{"metadata", "spec"}, }, }, Dependencies: []string{ @@ -1547,6 +1662,13 @@ func schema_pkg_apis_projectcalico_v3_BlockAffinitySpec(ref common.ReferenceCall Format: "", }, }, + "type": { + SchemaProps: spec.SchemaProps{ + Description: "The type of affinity.", + Type: []string{"string"}, + Format: "", + }, + }, "cidr": { SchemaProps: spec.SchemaProps{ Description: "The CIDR range this block affinity references.", @@ -1558,12 +1680,13 @@ func schema_pkg_apis_projectcalico_v3_BlockAffinitySpec(ref common.ReferenceCall "deleted": { SchemaProps: spec.SchemaProps{ Description: "Deleted indicates whether or not this block affinity is disabled and is used as part of race-condition prevention. When set to true, clients should treat this block as if it does not exist.", + Default: false, Type: []string{"boolean"}, Format: "", }, }, }, - Required: []string{"state", "node", "cidr"}, + Required: []string{"state", "node", "cidr", "deleted"}, }, }, } @@ -1874,6 +1997,7 @@ func schema_pkg_apis_projectcalico_v3_CalicoNodeStatus(ref common.ReferenceCallb }, }, }, + Required: []string{"metadata", "spec"}, }, }, Dependencies: []string{ @@ -1922,7 +2046,7 @@ func schema_pkg_apis_projectcalico_v3_CalicoNodeStatusList(ref common.ReferenceC }, }, }, - Required: []string{"items"}, + Required: []string{"metadata", "items"}, }, }, Dependencies: []string{ @@ -2047,6 +2171,7 @@ func schema_pkg_apis_projectcalico_v3_ClusterInformation(ref common.ReferenceCal }, }, }, + Required: []string{"metadata", "spec"}, }, }, Dependencies: []string{ @@ -2095,7 +2220,7 @@ func schema_pkg_apis_projectcalico_v3_ClusterInformationList(ref common.Referenc }, }, }, - Required: []string{"items"}, + Required: []string{"metadata", "items"}, }, }, Dependencies: []string{ @@ -2174,6 +2299,11 @@ func schema_pkg_apis_projectcalico_v3_Community(ref common.ReferenceCallback) co }, }, }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, }, } } @@ -2221,11 +2351,17 @@ func schema_pkg_apis_projectcalico_v3_ControllersConfig(ref common.ReferenceCall Ref: ref("github.com/projectcalico/api/pkg/apis/projectcalico/v3.LoadBalancerControllerConfig"), }, }, + "policyMigration": { + SchemaProps: spec.SchemaProps{ + Description: "Migration enables and configures migration controllers.", + Ref: ref("github.com/projectcalico/api/pkg/apis/projectcalico/v3.MigrationControllerConfig"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/projectcalico/api/pkg/apis/projectcalico/v3.LoadBalancerControllerConfig", "github.com/projectcalico/api/pkg/apis/projectcalico/v3.NamespaceControllerConfig", "github.com/projectcalico/api/pkg/apis/projectcalico/v3.NodeControllerConfig", "github.com/projectcalico/api/pkg/apis/projectcalico/v3.PolicyControllerConfig", "github.com/projectcalico/api/pkg/apis/projectcalico/v3.ServiceAccountControllerConfig", "github.com/projectcalico/api/pkg/apis/projectcalico/v3.WorkloadEndpointControllerConfig"}, + "github.com/projectcalico/api/pkg/apis/projectcalico/v3.LoadBalancerControllerConfig", "github.com/projectcalico/api/pkg/apis/projectcalico/v3.MigrationControllerConfig", "github.com/projectcalico/api/pkg/apis/projectcalico/v3.NamespaceControllerConfig", "github.com/projectcalico/api/pkg/apis/projectcalico/v3.NodeControllerConfig", "github.com/projectcalico/api/pkg/apis/projectcalico/v3.PolicyControllerConfig", "github.com/projectcalico/api/pkg/apis/projectcalico/v3.ServiceAccountControllerConfig", "github.com/projectcalico/api/pkg/apis/projectcalico/v3.WorkloadEndpointControllerConfig"}, } } @@ -2271,6 +2407,11 @@ func schema_pkg_apis_projectcalico_v3_EntityRule(ref common.ReferenceCallback) c Type: []string{"object"}, Properties: map[string]spec.Schema{ "nets": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, SchemaProps: spec.SchemaProps{ Description: "Nets is an optional field that restricts the rule to only apply to traffic that originates from (or terminates at) IP addresses in any of the given subnets.", Type: []string{"array"}, @@ -2320,7 +2461,7 @@ func schema_pkg_apis_projectcalico_v3_EntityRule(ref common.ReferenceCallback) c }, "notNets": { SchemaProps: spec.SchemaProps{ - Description: "NotNets is the negated version of the Nets field.", + Description: "NotNets is the negated version of the Nets field. listType=set", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -2400,6 +2541,7 @@ func schema_pkg_apis_projectcalico_v3_FelixConfiguration(ref common.ReferenceCal }, }, }, + Required: []string{"metadata", "spec"}, }, }, Dependencies: []string{ @@ -2448,7 +2590,7 @@ func schema_pkg_apis_projectcalico_v3_FelixConfigurationList(ref common.Referenc }, }, }, - Required: []string{"items"}, + Required: []string{"metadata", "items"}, }, }, Dependencies: []string{ @@ -2514,22 +2656,9 @@ func schema_pkg_apis_projectcalico_v3_FelixConfigurationSpec(ref common.Referenc Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), }, }, - "iptablesLockFilePath": { - SchemaProps: spec.SchemaProps{ - Description: "IptablesLockFilePath is the location of the iptables lock file. You may need to change this if the lock file is not in its standard location (for example if you have mapped it into Felix's container at a different path). [Default: /run/xtables.lock]", - Type: []string{"string"}, - Format: "", - }, - }, - "iptablesLockTimeout": { - SchemaProps: spec.SchemaProps{ - Description: "IptablesLockTimeout is the time that Felix itself will wait for the iptables lock (rather than delegating the lock handling to the `iptables` command).\n\nDeprecated: `iptables-restore` v1.8+ always takes the lock, so enabling this feature results in deadlock. [Default: 0s disabled]", - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), - }, - }, "iptablesLockProbeInterval": { SchemaProps: spec.SchemaProps{ - Description: "IptablesLockProbeInterval when IptablesLockTimeout is enabled: the time that Felix will wait between attempts to acquire the iptables lock if it is not available. Lower values make Felix more responsive when the lock is contended, but use more CPU. [Default: 50ms]", + Description: "IptablesLockProbeInterval configures the interval between attempts to claim the xtables lock. Shorter intervals are more responsive but use more CPU. [Default: 50ms]", Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), }, }, @@ -2651,11 +2780,25 @@ func schema_pkg_apis_projectcalico_v3_FelixConfigurationSpec(ref common.Referenc }, "logPrefix": { SchemaProps: spec.SchemaProps{ - Description: "LogPrefix is the log prefix that Felix uses when rendering LOG rules. [Default: calico-packet]", + Description: "LogPrefix is the log prefix that Felix uses when rendering LOG rules. It is possible to use the following specifiers to include extra information in the log prefix. - %t: Tier name. - %k: Kind (short names). - %n: Policy or profile name. - %p: Policy or profile name (namespace/name for namespaced kinds or just name for non namespaced kinds). Calico includes \": \" characters at the end of the generated log prefix. Note that iptables shows up to 29 characters for the log prefix and nftables up to 127 characters. Extra characters are truncated. [Default: calico-packet]", + Type: []string{"string"}, + Format: "", + }, + }, + "logActionRateLimit": { + SchemaProps: spec.SchemaProps{ + Description: "LogActionRateLimit sets the rate of hitting a Log action. The value must be in the format \"N/unit\", where N is a number and unit is one of: second, minute, hour, or day. For example: \"10/second\" or \"100/hour\".", Type: []string{"string"}, Format: "", }, }, + "logActionRateLimitBurst": { + SchemaProps: spec.SchemaProps{ + Description: "LogActionRateLimitBurst sets the rate limit burst of hitting a Log action when LogActionRateLimit is enabled.", + Type: []string{"integer"}, + Format: "int32", + }, + }, "logFilePath": { SchemaProps: spec.SchemaProps{ Description: "LogFilePath is the full path to the Felix log. Set to none to disable file logging. [Default: /var/log/calico/felix.log]", @@ -2877,6 +3020,34 @@ func schema_pkg_apis_projectcalico_v3_FelixConfigurationSpec(ref common.Referenc Format: "", }, }, + "prometheusMetricsCAFile": { + SchemaProps: spec.SchemaProps{ + Description: "PrometheusMetricsCAFile defines the absolute path to the TLS CA certificate file used for securing the /metrics endpoint. This certificate must be valid and accessible by the calico-node process.", + Type: []string{"string"}, + Format: "", + }, + }, + "prometheusMetricsCertFile": { + SchemaProps: spec.SchemaProps{ + Description: "PrometheusMetricsCertFile defines the absolute path to the TLS certificate file used for securing the /metrics endpoint. This certificate must be valid and accessible by the calico-node process.", + Type: []string{"string"}, + Format: "", + }, + }, + "prometheusMetricsKeyFile": { + SchemaProps: spec.SchemaProps{ + Description: "PrometheusMetricsKeyFile defines the absolute path to the private key file corresponding to the TLS certificate used for securing the /metrics endpoint. The private key must be valid and accessible by the calico-node process.", + Type: []string{"string"}, + Format: "", + }, + }, + "prometheusMetricsClientAuth": { + SchemaProps: spec.SchemaProps{ + Description: "PrometheusMetricsClientAuth specifies the client authentication type for the /metrics endpoint. This determines how the server validates client certificates. Default is \"RequireAndVerifyClientCert\".", + Type: []string{"string"}, + Format: "", + }, + }, "failsafeInboundHostPorts": { SchemaProps: spec.SchemaProps{ Description: "FailsafeInboundHostPorts is a list of ProtoPort struct objects including UDP/TCP/SCTP ports and CIDRs that Felix will allow incoming traffic to host endpoints on irrespective of the security policy. This is useful to avoid accidentally cutting off a host with incorrect configuration. For backwards compatibility, if the protocol is not specified, it defaults to \"tcp\". If a CIDR is not specified, it will allow traffic from all addresses. To disable all inbound host ports, use the value \"[]\". The default value allows ssh access, DHCP, BGP, etcd and the Kubernetes API. [Default: tcp:22, udp:68, tcp:179, tcp:2379, tcp:2380, tcp:5473, tcp:6443, tcp:6666, tcp:6667 ]", @@ -3097,10 +3268,10 @@ func schema_pkg_apis_projectcalico_v3_FelixConfigurationSpec(ref common.Referenc }, "nftablesMode": { SchemaProps: spec.SchemaProps{ - Description: "NFTablesMode configures nftables support in Felix. [Default: Disabled]", + Description: "NFTablesMode configures nftables support in Felix. [Default: Auto]\n\nPossible enum values:\n - `\"Auto\"`\n - `\"Disabled\"`\n - `\"Enabled\"`", Type: []string{"string"}, Format: "", - Enum: []interface{}{}, + Enum: []interface{}{"Auto", "Disabled", "Enabled"}, }, }, "nftablesRefreshInterval": { @@ -3286,20 +3457,13 @@ func schema_pkg_apis_projectcalico_v3_FelixConfigurationSpec(ref common.Referenc Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), }, }, - "bpfKubeProxyHealtzPort": { + "bpfKubeProxyHealthzPort": { SchemaProps: spec.SchemaProps{ - Description: "BPFKubeProxyHealtzPort, in BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. The health check server is used by external load balancers to determine if this node should receive traffic. [Default: 10256]", + Description: "BPFKubeProxyHealthzPort, in BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. The health check server is used by external load balancers to determine if this node should receive traffic. [Default: 10256]", Type: []string{"integer"}, Format: "int32", }, }, - "bpfKubeProxyEndpointSlicesEnabled": { - SchemaProps: spec.SchemaProps{ - Description: "BPFKubeProxyEndpointSlicesEnabled is deprecated and has no effect. BPF kube-proxy always accepts endpoint slices. This option will be removed in the next release.", - Type: []string{"boolean"}, - Format: "", - }, - }, "bpfPSNATPorts": { SchemaProps: spec.SchemaProps{ Description: "BPFPSNATPorts sets the range from which we randomly pick a port if there is a source port collision. This should be within the ephemeral range as defined by RFC 6056 (1024–65535) and preferably outside the ephemeral ranges used by common operating systems. Linux uses 32768–60999, while others mostly use the IANA defined range 49152–65535. It is not necessarily a problem if this range overlaps with the operating systems. Both ends of the range are inclusive. [Default: 20000:29999]", @@ -3457,7 +3621,7 @@ func schema_pkg_apis_projectcalico_v3_FelixConfigurationSpec(ref common.Referenc }, "bpfRedirectToPeer": { SchemaProps: spec.SchemaProps{ - Description: "BPFRedirectToPeer controls which whether it is allowed to forward straight to the peer side of the workload devices. It is allowed for any host L2 devices by default (L2Only), but it breaks TCP dump on the host side of workload device as it bypasses it on ingress. Value of Enabled also allows redirection from L3 host devices like IPIP tunnel or Wireguard directly to the peer side of the workload's device. This makes redirection faster, however, it breaks tools like tcpdump on the peer side. Use Enabled with caution. [Default: L2Only]", + Description: "BPFRedirectToPeer controls whether traffic may be forwarded directly to the peer side of a workload’s device. Note that the legacy \"L2Only\" option is now deprecated and if set it is treated like \"Enabled. Setting this option to \"Enabled\" allows direct redirection (including from L3 host devices such as IPIP tunnels or WireGuard), which can improve redirection performance but causes the redirected packets to bypass the host‑side ingress path. As a result, packet‑capture tools on the host side of the workload device (for example, tcpdump) will not see that traffic. [Default: Enabled]", Type: []string{"string"}, Format: "", }, @@ -3690,6 +3854,20 @@ func schema_pkg_apis_projectcalico_v3_FelixConfigurationSpec(ref common.Referenc Format: "", }, }, + "bpfMaglevMaxEndpointsPerService": { + SchemaProps: spec.SchemaProps{ + Description: "BPFMaglevMaxEndpointsPerService is the maximum number of endpoints expected to be part of a single Maglev-enabled service.\n\nInfluences the size of the per-service Maglev lookup-tables generated by Felix and thus the amount of memory reserved.\n\n[Default: 100]", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "bpfMaglevMaxServices": { + SchemaProps: spec.SchemaProps{ + Description: "BPFMaglevMaxServices is the maximum number of expected Maglev-enabled services that Felix will allocate lookup-tables for.\n\n[Default: 100]", + Type: []string{"integer"}, + Format: "int32", + }, + }, }, }, }, @@ -3731,6 +3909,7 @@ func schema_pkg_apis_projectcalico_v3_GlobalNetworkPolicy(ref common.ReferenceCa }, }, }, + Required: []string{"metadata", "spec"}, }, }, Dependencies: []string{ @@ -3779,7 +3958,7 @@ func schema_pkg_apis_projectcalico_v3_GlobalNetworkPolicyList(ref common.Referen }, }, }, - Required: []string{"items"}, + Required: []string{"metadata", "items"}, }, }, Dependencies: []string{ @@ -3843,6 +4022,11 @@ func schema_pkg_apis_projectcalico_v3_GlobalNetworkPolicySpec(ref common.Referen }, }, "types": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, SchemaProps: spec.SchemaProps{ Description: "Types indicates whether this policy applies to ingress, or to egress, or to both. When not explicitly specified (and so the value on creation is empty or nil), Calico defaults Types according to what Ingress and Egress rules are present in the policy. The default is:\n\n- [ PolicyTypeIngress ], if there are no Egress rules (including the case where there are\n also no Ingress rules)\n\n- [ PolicyTypeEgress ], if there are Egress rules but no Ingress rules\n\n- [ PolicyTypeIngress, PolicyTypeEgress ], if there are both Ingress and Egress rules.\n\nWhen the policy is read back again, Types will always be one of these values, never empty or nil.", Type: []string{"array"}, @@ -3948,6 +4132,7 @@ func schema_pkg_apis_projectcalico_v3_GlobalNetworkSet(ref common.ReferenceCallb }, }, }, + Required: []string{"metadata", "spec"}, }, }, Dependencies: []string{ @@ -3996,7 +4181,7 @@ func schema_pkg_apis_projectcalico_v3_GlobalNetworkSetList(ref common.ReferenceC }, }, }, - Required: []string{"items"}, + Required: []string{"metadata", "items"}, }, }, Dependencies: []string{ @@ -4012,6 +4197,11 @@ func schema_pkg_apis_projectcalico_v3_GlobalNetworkSetSpec(ref common.ReferenceC Type: []string{"object"}, Properties: map[string]spec.Schema{ "nets": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, SchemaProps: spec.SchemaProps{ Description: "The list of IP networks that belong to this set.", Type: []string{"array"}, @@ -4161,6 +4351,7 @@ func schema_pkg_apis_projectcalico_v3_HostEndpoint(ref common.ReferenceCallback) }, }, }, + Required: []string{"metadata", "spec"}, }, }, Dependencies: []string{ @@ -4209,7 +4400,7 @@ func schema_pkg_apis_projectcalico_v3_HostEndpointList(ref common.ReferenceCallb }, }, }, - Required: []string{"items"}, + Required: []string{"metadata", "items"}, }, }, Dependencies: []string{ @@ -4239,6 +4430,11 @@ func schema_pkg_apis_projectcalico_v3_HostEndpointSpec(ref common.ReferenceCallb }, }, "expectedIPs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, SchemaProps: spec.SchemaProps{ Description: "The expected IP addresses (IPv4 and IPv6) of the endpoint. If \"InterfaceName\" is not present, Calico will look for an interface matching any of the IPs in the list and apply policy to that. Note:\n\tWhen using the selector match criteria in an ingress or egress security Policy\n\tor Profile, Calico converts the selector into a set of IP addresses. For host\n\tendpoints, the ExpectedIPs field is used for that purpose. (If only the interface\n\tname is specified, Calico does not learn the IPs of the interface for use in match\n\tcriteria.)", Type: []string{"array"}, @@ -4254,6 +4450,11 @@ func schema_pkg_apis_projectcalico_v3_HostEndpointSpec(ref common.ReferenceCallb }, }, "profiles": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, SchemaProps: spec.SchemaProps{ Description: "A list of identifiers of security Profile objects that apply to this endpoint. Each profile is applied in the order that they appear in this list. Profile rules are applied after the selector-based security policy.", Type: []string{"array"}, @@ -4294,35 +4495,379 @@ func schema_pkg_apis_projectcalico_v3_ICMPFields(ref common.ReferenceCallback) c return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "ICMPFields defines structure for ICMP and NotICMP sub-struct for ICMP code and type", + Description: "ICMPFields defines structure for ICMP and NotICMP sub-struct for ICMP code and type", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Match on a specific ICMP type. For example a value of 8 refers to ICMP Echo Request (i.e. pings).", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "code": { + SchemaProps: spec.SchemaProps{ + Description: "Match on a specific ICMP code. If specified, the Type value must also be specified. This is a technical limitation imposed by the kernel's iptables firewall, which Calico uses to enforce the rule.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_projectcalico_v3_IPAMBlock(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPAMBlockSpec"), + }, + }, + }, + Required: []string{"metadata", "spec"}, + }, + }, + Dependencies: []string{ + "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPAMBlockSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_projectcalico_v3_IPAMBlockList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "IPAMBlockList contains a list of IPAMBlock resources.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPAMBlock"), + }, + }, + }, + }, + }, + }, + Required: []string{"metadata", "items"}, + }, + }, + Dependencies: []string{ + "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPAMBlock", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_projectcalico_v3_IPAMBlockSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "IPAMBlockSpec contains the specification for an IPAMBlock resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "cidr": { + SchemaProps: spec.SchemaProps{ + Description: "The block's CIDR.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "affinity": { + SchemaProps: spec.SchemaProps{ + Description: "Affinity of the block, if this block has one. If set, it will be of the form \"host:\" or \"virtual:\". If not set, this block is not affine to a host.", + Type: []string{"string"}, + Format: "", + }, + }, + "affinityClaimTime": { + SchemaProps: spec.SchemaProps{ + Description: "Time at which affinity was claimed.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "allocations": { + SchemaProps: spec.SchemaProps{ + Description: "Array of allocations in-use within this block. nil entries mean the allocation is free. For non-nil entries at index i, the index is the ordinal of the allocation within this block and the value is the index of the associated attributes in the Attributes array.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + "unallocated": { + SchemaProps: spec.SchemaProps{ + Description: "Unallocated is an ordered list of allocations which are free in the block.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + "attributes": { + SchemaProps: spec.SchemaProps{ + Description: "Attributes is an array of arbitrary metadata associated with allocations in the block. To find attributes for a given allocation, use the value of the allocation's entry in the Allocations array as the index of the element in this array.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/api/pkg/apis/projectcalico/v3.AllocationAttribute"), + }, + }, + }, + }, + }, + "sequenceNumber": { + SchemaProps: spec.SchemaProps{ + Description: "We store a sequence number that is updated each time the block is written. Each allocation will also store the sequence number of the block at the time of its creation. When releasing an IP, passing the sequence number associated with the allocation allows us to protect against a race condition and ensure the IP hasn't been released and re-allocated since the release request.", + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + "sequenceNumberForAllocation": { + SchemaProps: spec.SchemaProps{ + Description: "Map of allocated ordinal within the block to sequence number of the block at the time of allocation. Kubernetes does not allow numerical keys for maps, so the key is cast to a string.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + }, + }, + }, + "deleted": { + SchemaProps: spec.SchemaProps{ + Description: "Deleted is an internal boolean used to workaround a limitation in the Kubernetes API whereby deletion will not return a conflict error if the block has been updated. It should not be set manually.", + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + "strictAffinity": { + SchemaProps: spec.SchemaProps{ + Description: "StrictAffinity on the IPAMBlock is deprecated and no longer used by the code. Use IPAMConfig StrictAffinity instead.", + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"cidr", "allocations", "unallocated", "attributes", "strictAffinity"}, + }, + }, + Dependencies: []string{ + "github.com/projectcalico/api/pkg/apis/projectcalico/v3.AllocationAttribute", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_pkg_apis_projectcalico_v3_IPAMConfiguration(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "IPAMConfiguration contains information about a block for IP address assignment.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPAMConfigurationSpec"), + }, + }, + }, + Required: []string{"metadata", "spec"}, + }, + }, + Dependencies: []string{ + "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPAMConfigurationSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_projectcalico_v3_IPAMConfigurationList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "IPAMConfigurationList contains a list of IPAMConfiguration resources.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPAMConfiguration"), + }, + }, + }, + }, + }, + }, + Required: []string{"metadata", "items"}, + }, + }, + Dependencies: []string{ + "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPAMConfiguration", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_projectcalico_v3_IPAMConfigurationSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "IPAMConfigurationSpec contains the specification for an IPAMConfiguration resource.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "type": { + "strictAffinity": { SchemaProps: spec.SchemaProps{ - Description: "Match on a specific ICMP type. For example a value of 8 refers to ICMP Echo Request (i.e. pings).", - Type: []string{"integer"}, - Format: "int32", + Description: "When StrictAffinity is true, borrowing IP addresses is not allowed.", + Default: false, + Type: []string{"boolean"}, + Format: "", }, }, - "code": { + "maxBlocksPerHost": { SchemaProps: spec.SchemaProps{ - Description: "Match on a specific ICMP code. If specified, the Type value must also be specified. This is a technical limitation imposed by the kernel's iptables firewall, which Calico uses to enforce the rule.", + Description: "MaxBlocksPerHost, if non-zero, is the max number of blocks that can be affine to each host.", Type: []string{"integer"}, Format: "int32", }, }, + "autoAllocateBlocks": { + SchemaProps: spec.SchemaProps{ + Description: "Whether or not to auto allocate blocks to hosts.", + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + "kubeVirtVMAddressPersistence": { + SchemaProps: spec.SchemaProps{ + Description: "KubeVirtVMAddressPersistence controls whether KubeVirt VirtualMachine workloads maintain persistent IP addresses across VM lifecycle events (reboot, migration, pod eviction). When Enabled, Calico automatically ensures that KubeVirt VMs retain their IP addresses when their underlying pods are recreated during VM operations. When Disabled, VMs receive new IP addresses whenever their pods are recreated. Defaults to Enabled if not specified.", + Type: []string{"string"}, + Format: "", + }, + }, }, + Required: []string{"strictAffinity", "autoAllocateBlocks"}, }, }, } } -func schema_pkg_apis_projectcalico_v3_IPAMConfiguration(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_projectcalico_v3_IPAMHandle(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "IPAMConfiguration contains information about a block for IP address assignment.", - Type: []string{"object"}, + Type: []string{"object"}, Properties: map[string]spec.Schema{ "kind": { SchemaProps: spec.SchemaProps{ @@ -4347,22 +4892,23 @@ func schema_pkg_apis_projectcalico_v3_IPAMConfiguration(ref common.ReferenceCall "spec": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPAMConfigurationSpec"), + Ref: ref("github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPAMHandleSpec"), }, }, }, + Required: []string{"metadata", "spec"}, }, }, Dependencies: []string{ - "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPAMConfigurationSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPAMHandleSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } -func schema_pkg_apis_projectcalico_v3_IPAMConfigurationList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_projectcalico_v3_IPAMHandleList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "IPAMConfigurationList contains a list of IPAMConfiguration resources.", + Description: "IPAMHandleList contains a list of IPAMHandle resources.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "kind": { @@ -4392,45 +4938,59 @@ func schema_pkg_apis_projectcalico_v3_IPAMConfigurationList(ref common.Reference Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPAMConfiguration"), + Ref: ref("github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPAMHandle"), }, }, }, }, }, }, - Required: []string{"items"}, + Required: []string{"metadata", "items"}, }, }, Dependencies: []string{ - "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPAMConfiguration", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPAMHandle", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, } } -func schema_pkg_apis_projectcalico_v3_IPAMConfigurationSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_projectcalico_v3_IPAMHandleSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "IPAMConfigurationSpec contains the specification for an IPPool resource.", + Description: "IPAMHandleSpec contains the specification for an IPAMHandle resource.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "strictAffinity": { + "handleID": { SchemaProps: spec.SchemaProps{ - Description: "When StrictAffinity is true, borrowing IP addresses is not allowed.", - Default: false, - Type: []string{"boolean"}, - Format: "", + Default: "", + Type: []string{"string"}, + Format: "", }, }, - "maxBlocksPerHost": { + "block": { SchemaProps: spec.SchemaProps{ - Description: "MaxBlocksPerHost, if non-zero, is the max number of blocks that can be affine to each host.", - Type: []string{"integer"}, - Format: "int32", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + "deleted": { + SchemaProps: spec.SchemaProps{ + Default: false, + Type: []string{"boolean"}, + Format: "", }, }, }, - Required: []string{"strictAffinity"}, + Required: []string{"handleID", "block"}, }, }, } @@ -4494,11 +5054,17 @@ func schema_pkg_apis_projectcalico_v3_IPPool(ref common.ReferenceCallback) commo Ref: ref("github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPPoolSpec"), }, }, + "status": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPPoolStatus"), + }, + }, }, + Required: []string{"metadata", "spec"}, }, }, Dependencies: []string{ - "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPPoolSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPPoolSpec", "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPPoolStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } @@ -4543,7 +5109,7 @@ func schema_pkg_apis_projectcalico_v3_IPPoolList(ref common.ReferenceCallback) c }, }, }, - Required: []string{"items"}, + Required: []string{"metadata", "items"}, }, }, Dependencies: []string{ @@ -4568,14 +5134,14 @@ func schema_pkg_apis_projectcalico_v3_IPPoolSpec(ref common.ReferenceCallback) c }, "vxlanMode": { SchemaProps: spec.SchemaProps{ - Description: "Contains configuration for VXLAN tunneling for this pool. If not specified, then this is defaulted to \"Never\" (i.e. VXLAN tunneling is disabled).", + Description: "Contains configuration for VXLAN tunneling for this pool.", Type: []string{"string"}, Format: "", }, }, "ipipMode": { SchemaProps: spec.SchemaProps{ - Description: "Contains configuration for IPIP tunneling for this pool. If not specified, then this is defaulted to \"Never\" (i.e. IPIP tunneling is disabled).", + Description: "Contains configuration for IPIP tunneling for this pool. For IPv6 pools, IPIP tunneling must be disabled.", Type: []string{"string"}, Format: "", }, @@ -4603,7 +5169,7 @@ func schema_pkg_apis_projectcalico_v3_IPPoolSpec(ref common.ReferenceCallback) c }, "blockSize": { SchemaProps: spec.SchemaProps{ - Description: "The block size to use for IP address assignments from this pool. Defaults to 26 for IPv4 and 122 for IPv6.", + Description: "The block size to use for IP address assignments from this pool. Defaults to 26 for IPv4 and 122 for IPv6. The block size must be between 0 and 32 for IPv4 and between 0 and 128 for IPv6. It must also be smaller than or equal to the size of the pool CIDR.", Type: []string{"integer"}, Format: "int32", }, @@ -4622,20 +5188,12 @@ func schema_pkg_apis_projectcalico_v3_IPPoolSpec(ref common.ReferenceCallback) c Format: "", }, }, - "ipip": { - SchemaProps: spec.SchemaProps{ - Description: "Deprecated: this field is only used for APIv1 backwards compatibility. Setting this field is not allowed, this field is for internal use only.", - Ref: ref("github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPIPConfiguration"), - }, - }, - "nat-outgoing": { - SchemaProps: spec.SchemaProps{ - Description: "Deprecated: this field is only used for APIv1 backwards compatibility. Setting this field is not allowed, this field is for internal use only.", - Type: []string{"boolean"}, - Format: "", - }, - }, "allowedUses": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, SchemaProps: spec.SchemaProps{ Description: "AllowedUse controls what the IP pool will be used for. If not specified or empty, defaults to [\"Tunnel\", \"Workload\"] for back-compatibility", Type: []string{"array"}, @@ -4661,8 +5219,33 @@ func schema_pkg_apis_projectcalico_v3_IPPoolSpec(ref common.ReferenceCallback) c Required: []string{"cidr"}, }, }, + } +} + +func schema_pkg_apis_projectcalico_v3_IPPoolStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "conditions": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Condition"), + }, + }, + }, + }, + }, + }, + }, + }, Dependencies: []string{ - "github.com/projectcalico/api/pkg/apis/projectcalico/v3.IPIPConfiguration"}, + "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, } } @@ -4700,6 +5283,7 @@ func schema_pkg_apis_projectcalico_v3_IPReservation(ref common.ReferenceCallback }, }, }, + Required: []string{"metadata", "spec"}, }, }, Dependencies: []string{ @@ -4748,7 +5332,7 @@ func schema_pkg_apis_projectcalico_v3_IPReservationList(ref common.ReferenceCall }, }, }, - Required: []string{"items"}, + Required: []string{"metadata", "items"}, }, }, Dependencies: []string{ @@ -4764,6 +5348,11 @@ func schema_pkg_apis_projectcalico_v3_IPReservationSpec(ref common.ReferenceCall Type: []string{"object"}, Properties: map[string]spec.Schema{ "reservedCIDRs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, SchemaProps: spec.SchemaProps{ Description: "ReservedCIDRs is a list of CIDRs and/or IP addresses that Calico IPAM will exclude from new allocations.", Type: []string{"array"}, @@ -4823,6 +5412,7 @@ func schema_pkg_apis_projectcalico_v3_KubeControllersConfiguration(ref common.Re }, }, }, + Required: []string{"metadata", "spec"}, }, }, Dependencies: []string{ @@ -4871,7 +5461,7 @@ func schema_pkg_apis_projectcalico_v3_KubeControllersConfigurationList(ref commo }, }, }, - Required: []string{"items"}, + Required: []string{"metadata", "items"}, }, }, Dependencies: []string{ @@ -4888,14 +5478,14 @@ func schema_pkg_apis_projectcalico_v3_KubeControllersConfigurationSpec(ref commo Properties: map[string]spec.Schema{ "logSeverityScreen": { SchemaProps: spec.SchemaProps{ - Description: "LogSeverityScreen is the log severity above which logs are sent to the stdout. [Default: Info]", + Description: "LogSeverityScreen is the log severity above which logs are sent to the stdout. [Default: Info] Valid values are: \"None\", \"Debug\", \"Info\", \"Warning\", \"Error\", \"Fatal\", \"Panic\".", Type: []string{"string"}, Format: "", }, }, "healthChecks": { SchemaProps: spec.SchemaProps{ - Description: "HealthChecks enables or disables support for health checks [Default: Enabled]", + Description: "HealthChecks enables or disables support for health checks [Default: Enabled] Valid values are: \"Enabled\", \"Disabled\".", Type: []string{"string"}, Format: "", }, @@ -4908,7 +5498,7 @@ func schema_pkg_apis_projectcalico_v3_KubeControllersConfigurationSpec(ref commo }, "prometheusMetricsPort": { SchemaProps: spec.SchemaProps{ - Description: "PrometheusMetricsPort is the TCP port that the Prometheus metrics server should bind to. Set to 0 to disable. [Default: 9094]", + Description: "PrometheusMetricsPort is the TCP port that the Prometheus metrics server should bind to. Set to 0 to disable. [Default: 9094] Valid values are: 0-65535.", Type: []string{"integer"}, Format: "int32", }, @@ -4922,7 +5512,7 @@ func schema_pkg_apis_projectcalico_v3_KubeControllersConfigurationSpec(ref commo }, "debugProfilePort": { SchemaProps: spec.SchemaProps{ - Description: "DebugProfilePort configures the port to serve memory and cpu profiles on. If not specified, profiling is disabled.", + Description: "DebugProfilePort configures the port to serve memory and cpu profiles on. If not specified, profiling is disabled. Valid values are: 0-65535.", Type: []string{"integer"}, Format: "int32", }, @@ -4946,7 +5536,6 @@ func schema_pkg_apis_projectcalico_v3_KubeControllersConfigurationStatus(ref com "runningConfig": { SchemaProps: spec.SchemaProps{ Description: "RunningConfig contains the effective config that is running in the kube-controllers pod, after merging the API resource with any environment variables.", - Default: map[string]interface{}{}, Ref: ref("github.com/projectcalico/api/pkg/apis/projectcalico/v3.KubeControllersConfigurationSpec"), }, }, @@ -4982,8 +5571,28 @@ func schema_pkg_apis_projectcalico_v3_LoadBalancerControllerConfig(ref common.Re Properties: map[string]spec.Schema{ "assignIPs": { SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", + Description: "AssignIPs controls which LoadBalancer Service gets IP assigned from Calico IPAM.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_projectcalico_v3_MigrationControllerConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "enabled": { + SchemaProps: spec.SchemaProps{ + Description: "PolicyNameMigrator enables or disables the Policy Name Migrator, which migrates old-style Calico backend policy names to use v3 style names.", + Type: []string{"string"}, + Format: "", }, }, }, @@ -5046,6 +5655,7 @@ func schema_pkg_apis_projectcalico_v3_NetworkPolicy(ref common.ReferenceCallback }, }, }, + Required: []string{"metadata", "spec"}, }, }, Dependencies: []string{ @@ -5094,7 +5704,7 @@ func schema_pkg_apis_projectcalico_v3_NetworkPolicyList(ref common.ReferenceCall }, }, }, - Required: []string{"items"}, + Required: []string{"metadata", "items"}, }, }, Dependencies: []string{ @@ -5158,6 +5768,11 @@ func schema_pkg_apis_projectcalico_v3_NetworkPolicySpec(ref common.ReferenceCall }, }, "types": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, SchemaProps: spec.SchemaProps{ Description: "Types indicates whether this policy applies to ingress, or to egress, or to both. When not explicitly specified (and so the value on creation is empty or nil), Calico defaults Types according to what Ingress and Egress are present in the policy. The default is:\n\n- [ PolicyTypeIngress ], if there are no Egress rules (including the case where there are\n also no Ingress rules)\n\n- [ PolicyTypeEgress ], if there are Egress rules but no Ingress rules\n\n- [ PolicyTypeIngress, PolicyTypeEgress ], if there are both Ingress and Egress rules.\n\nWhen the policy is read back again, Types will always be one of these values, never empty or nil.", Type: []string{"array"}, @@ -5235,6 +5850,7 @@ func schema_pkg_apis_projectcalico_v3_NetworkSet(ref common.ReferenceCallback) c }, }, }, + Required: []string{"metadata", "spec"}, }, }, Dependencies: []string{ @@ -5283,7 +5899,7 @@ func schema_pkg_apis_projectcalico_v3_NetworkSetList(ref common.ReferenceCallbac }, }, }, - Required: []string{"items"}, + Required: []string{"metadata", "items"}, }, }, Dependencies: []string{ @@ -5299,6 +5915,11 @@ func schema_pkg_apis_projectcalico_v3_NetworkSetSpec(ref common.ReferenceCallbac Type: []string{"object"}, Properties: map[string]spec.Schema{ "nets": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, SchemaProps: spec.SchemaProps{ Description: "The list of IP networks that belong to this set.", Type: []string{"array"}, @@ -5334,7 +5955,7 @@ func schema_pkg_apis_projectcalico_v3_NodeControllerConfig(ref common.ReferenceC }, "syncLabels": { SchemaProps: spec.SchemaProps{ - Description: "SyncLabels controls whether to copy Kubernetes node labels to Calico nodes. [Default: Enabled]", + Description: "SyncLabels controls whether to copy Kubernetes node labels to Calico nodes. [Default: Enabled] Valid values are: \"Enabled\", \"Disabled\".", Type: []string{"string"}, Format: "", }, @@ -5411,6 +6032,11 @@ func schema_pkg_apis_projectcalico_v3_PrefixAdvertisement(ref common.ReferenceCa }, }, }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, }, } } @@ -5448,6 +6074,7 @@ func schema_pkg_apis_projectcalico_v3_Profile(ref common.ReferenceCallback) comm }, }, }, + Required: []string{"metadata", "spec"}, }, }, Dependencies: []string{ @@ -5496,7 +6123,7 @@ func schema_pkg_apis_projectcalico_v3_ProfileList(ref common.ReferenceCallback) }, }, }, - Required: []string{"items"}, + Required: []string{"metadata", "items"}, }, }, Dependencies: []string{ @@ -5786,6 +6413,11 @@ func schema_pkg_apis_projectcalico_v3_ServiceAccountMatch(ref common.ReferenceCa Type: []string{"object"}, Properties: map[string]spec.Schema{ "names": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, SchemaProps: spec.SchemaProps{ Description: "Names is an optional field that restricts the rule to only apply to traffic that originates from (or terminates at) a pod running as a service account whose name is in the list.", Type: []string{"array"}, @@ -5828,6 +6460,11 @@ func schema_pkg_apis_projectcalico_v3_ServiceClusterIPBlock(ref common.Reference }, }, }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, }, } } @@ -5847,6 +6484,11 @@ func schema_pkg_apis_projectcalico_v3_ServiceExternalIPBlock(ref common.Referenc }, }, }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, }, } } @@ -5866,6 +6508,11 @@ func schema_pkg_apis_projectcalico_v3_ServiceLoadBalancerIPBlock(ref common.Refe }, }, }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, }, } } @@ -5919,19 +6566,18 @@ func schema_pkg_apis_projectcalico_v3_StagedGlobalNetworkPolicy(ref common.Refer }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object's metadata.", - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Specification of the Policy.", - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/api/pkg/apis/projectcalico/v3.StagedGlobalNetworkPolicySpec"), + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/api/pkg/apis/projectcalico/v3.StagedGlobalNetworkPolicySpec"), }, }, }, + Required: []string{"metadata", "spec"}, }, }, Dependencies: []string{ @@ -6051,6 +6697,11 @@ func schema_pkg_apis_projectcalico_v3_StagedGlobalNetworkPolicySpec(ref common.R }, }, "types": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, SchemaProps: spec.SchemaProps{ Description: "Types indicates whether this policy applies to ingress, or to egress, or to both. When not explicitly specified (and so the value on creation is empty or nil), Calico defaults Types according to what Ingress and Egress rules are present in the policy. The default is:\n\n- [ PolicyTypeIngress ], if there are no Egress rules (including the case where there are\n also no Ingress rules)\n\n- [ PolicyTypeEgress ], if there are Egress rules but no Ingress rules\n\n- [ PolicyTypeIngress, PolicyTypeEgress ], if there are both Ingress and Egress rules.\n\nWhen the policy is read back again, Types will always be one of these values, never empty or nil.", Type: []string{"array"}, @@ -6146,19 +6797,18 @@ func schema_pkg_apis_projectcalico_v3_StagedKubernetesNetworkPolicy(ref common.R }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object's metadata.", - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Specification of the Policy.", - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/api/pkg/apis/projectcalico/v3.StagedKubernetesNetworkPolicySpec"), + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/api/pkg/apis/projectcalico/v3.StagedKubernetesNetworkPolicySpec"), }, }, }, + Required: []string{"metadata", "spec"}, }, }, Dependencies: []string{ @@ -6264,6 +6914,11 @@ func schema_pkg_apis_projectcalico_v3_StagedKubernetesNetworkPolicySpec(ref comm }, }, "policyTypes": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, SchemaProps: spec.SchemaProps{ Description: "List of rule types that the NetworkPolicy relates to. Valid options are Ingress, Egress, or Ingress,Egress. If this field is not specified, it will default based on the existence of Ingress or Egress rules; policies that contain an Egress section are assumed to affect Egress, and all policies (whether or not they contain an Ingress section) are assumed to affect Ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ \"Egress\" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include \"Egress\" (since such a policy would not include an Egress section and would otherwise default to just [ \"Ingress\" ]). This field is beta-level in 1.8", Type: []string{"array"}, @@ -6310,19 +6965,18 @@ func schema_pkg_apis_projectcalico_v3_StagedNetworkPolicy(ref common.ReferenceCa }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object's metadata.", - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Specification of the Policy.", - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/api/pkg/apis/projectcalico/v3.StagedNetworkPolicySpec"), + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/api/pkg/apis/projectcalico/v3.StagedNetworkPolicySpec"), }, }, }, + Required: []string{"metadata", "spec"}, }, }, Dependencies: []string{ @@ -6442,6 +7096,11 @@ func schema_pkg_apis_projectcalico_v3_StagedNetworkPolicySpec(ref common.Referen }, }, "types": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, SchemaProps: spec.SchemaProps{ Description: "Types indicates whether this policy applies to ingress, or to egress, or to both. When not explicitly specified (and so the value on creation is empty or nil), Calico defaults Types according to what Ingress and Egress are present in the policy. The default is:\n\n- [ PolicyTypeIngress ], if there are no Egress rules (including the case where there are\n also no Ingress rules)\n\n- [ PolicyTypeEgress ], if there are Egress rules but no Ingress rules\n\n- [ PolicyTypeIngress, PolicyTypeEgress ], if there are both Ingress and Egress rules.\n\nWhen the policy is read back again, Types will always be one of these values, never empty or nil.", Type: []string{"array"}, @@ -6500,8 +7159,13 @@ func schema_pkg_apis_projectcalico_v3_Template(ref common.ReferenceCallback) com }, }, "interfaceCIDRs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, SchemaProps: spec.SchemaProps{ - Description: "InterfaceCIDRs contains a list of CIDRs used for matching nodeIPs to the AutoHostEndpoint. If specified, only addresses within these CIDRs will be included in the expected IPs. At least one of InterfaceCIDRs and InterfaceSelector must be specified.", + Description: "InterfaceCIDRs contains a list of CIDRs used for matching nodeIPs to the AutoHostEndpoint. If specified, only addresses within these CIDRs will be included in the expected IPs. At least one of InterfaceCIDRs and InterfacePattern must be specified.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -6514,9 +7178,9 @@ func schema_pkg_apis_projectcalico_v3_Template(ref common.ReferenceCallback) com }, }, }, - "interfaceSelector": { + "interfacePattern": { SchemaProps: spec.SchemaProps{ - Description: "InterfaceSelector contains a regex string to match Node interface names. If specified, a HostEndpoint will be created for each matching interface on each selected node. At least one of InterfaceCIDRs and InterfaceSelector must be specified.", + Description: "InterfacePattern contains a regex string to match Node interface names. If specified, a HostEndpoint will be created for each matching interface on each selected node. At least one of InterfaceCIDRs and InterfacePattern must be specified.", Type: []string{"string"}, Format: "", }, @@ -6573,19 +7237,18 @@ func schema_pkg_apis_projectcalico_v3_Tier(ref common.ReferenceCallback) common. }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object's metadata.", - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Specification of the Tier.", - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/api/pkg/apis/projectcalico/v3.TierSpec"), + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/api/pkg/apis/projectcalico/v3.TierSpec"), }, }, }, + Required: []string{"metadata", "spec"}, }, }, Dependencies: []string{ @@ -8219,7 +8882,7 @@ func schema_k8sio_api_core_v1_Container(ref common.ReferenceCallback) common.Ope }, }, SchemaProps: spec.SchemaProps{ - Description: "List of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. All invalid keys will be reported as an event when the container is starting. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated.", + Description: "List of sources to populate environment variables in the container. The keys defined within a source may consist of any printable ASCII characters except '='. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -8283,11 +8946,30 @@ func schema_k8sio_api_core_v1_Container(ref common.ReferenceCallback) common.Ope }, "restartPolicy": { SchemaProps: spec.SchemaProps{ - Description: "RestartPolicy defines the restart behavior of individual containers in a pod. This field may only be set for init containers, and the only allowed value is \"Always\". For non-init containers or when this field is not specified, the restart behavior is defined by the Pod's restart policy and the container type. Setting the RestartPolicy as \"Always\" for the init container will have the following effect: this init container will be continually restarted on exit until all regular containers have terminated. Once all regular containers have completed, all init containers with restartPolicy \"Always\" will be shut down. This lifecycle differs from normal init containers and is often referred to as a \"sidecar\" container. Although this init container still starts in the init container sequence, it does not wait for the container to complete before proceeding to the next init container. Instead, the next init container starts immediately after this init container is started, or after any startupProbe has successfully completed.", + Description: "RestartPolicy defines the restart behavior of individual containers in a pod. This overrides the pod-level restart policy. When this field is not specified, the restart behavior is defined by the Pod's restart policy and the container type. Additionally, setting the RestartPolicy as \"Always\" for the init container will have the following effect: this init container will be continually restarted on exit until all regular containers have terminated. Once all regular containers have completed, all init containers with restartPolicy \"Always\" will be shut down. This lifecycle differs from normal init containers and is often referred to as a \"sidecar\" container. Although this init container still starts in the init container sequence, it does not wait for the container to complete before proceeding to the next init container. Instead, the next init container starts immediately after this init container is started, or after any startupProbe has successfully completed.", Type: []string{"string"}, Format: "", }, }, + "restartPolicyRules": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Represents a list of rules to be checked to determine if the container should be restarted on exit. The rules are evaluated in order. Once a rule matches a container exit condition, the remaining rules are ignored. If no rule matches the container exit condition, the Container-level restart policy determines the whether the container is restarted or not. Constraints on the rules: - At most 20 rules are allowed. - Rules can have the same action. - Identical rules are not forbidden in validations. When rules are specified, container MUST set RestartPolicy explicitly even it if matches the Pod's RestartPolicy.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ContainerRestartRule"), + }, + }, + }, + }, + }, "volumeMounts": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ @@ -8391,31 +9073,69 @@ func schema_k8sio_api_core_v1_Container(ref common.ReferenceCallback) common.Ope }, "stdin": { SchemaProps: spec.SchemaProps{ - Description: "Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false.", - Type: []string{"boolean"}, + Description: "Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "stdinOnce": { + SchemaProps: spec.SchemaProps{ + Description: "Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false", + Type: []string{"boolean"}, + Format: "", + }, + }, + "tty": { + SchemaProps: spec.SchemaProps{ + Description: "Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ContainerPort", "k8s.io/api/core/v1.ContainerResizePolicy", "k8s.io/api/core/v1.ContainerRestartRule", "k8s.io/api/core/v1.EnvFromSource", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.Lifecycle", "k8s.io/api/core/v1.Probe", "k8s.io/api/core/v1.ResourceRequirements", "k8s.io/api/core/v1.SecurityContext", "k8s.io/api/core/v1.VolumeDevice", "k8s.io/api/core/v1.VolumeMount"}, + } +} + +func schema_k8sio_api_core_v1_ContainerExtendedResourceRequest(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ContainerExtendedResourceRequest has the mapping of container name, extended resource name to the device request name.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "containerName": { + SchemaProps: spec.SchemaProps{ + Description: "The name of the container requesting resources.", + Default: "", + Type: []string{"string"}, Format: "", }, }, - "stdinOnce": { + "resourceName": { SchemaProps: spec.SchemaProps{ - Description: "Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false", - Type: []string{"boolean"}, + Description: "The name of the extended resource in that container which gets backed by DRA.", + Default: "", + Type: []string{"string"}, Format: "", }, }, - "tty": { + "requestName": { SchemaProps: spec.SchemaProps{ - Description: "Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false.", - Type: []string{"boolean"}, + Description: "The name of the request in the special ResourceClaim which corresponds to the extended resource.", + Default: "", + Type: []string{"string"}, Format: "", }, }, }, - Required: []string{"name"}, + Required: []string{"containerName", "resourceName", "requestName"}, }, }, - Dependencies: []string{ - "k8s.io/api/core/v1.ContainerPort", "k8s.io/api/core/v1.ContainerResizePolicy", "k8s.io/api/core/v1.EnvFromSource", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.Lifecycle", "k8s.io/api/core/v1.Probe", "k8s.io/api/core/v1.ResourceRequirements", "k8s.io/api/core/v1.SecurityContext", "k8s.io/api/core/v1.VolumeDevice", "k8s.io/api/core/v1.VolumeMount"}, } } @@ -8541,6 +9261,76 @@ func schema_k8sio_api_core_v1_ContainerResizePolicy(ref common.ReferenceCallback } } +func schema_k8sio_api_core_v1_ContainerRestartRule(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ContainerRestartRule describes how a container exit is handled.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "action": { + SchemaProps: spec.SchemaProps{ + Description: "Specifies the action taken on a container exit if the requirements are satisfied. The only possible value is \"Restart\" to restart the container.", + Type: []string{"string"}, + Format: "", + }, + }, + "exitCodes": { + SchemaProps: spec.SchemaProps{ + Description: "Represents the exit codes to check on container exits.", + Ref: ref("k8s.io/api/core/v1.ContainerRestartRuleOnExitCodes"), + }, + }, + }, + Required: []string{"action"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ContainerRestartRuleOnExitCodes"}, + } +} + +func schema_k8sio_api_core_v1_ContainerRestartRuleOnExitCodes(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ContainerRestartRuleOnExitCodes describes the condition for handling an exited container based on its exit codes.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "operator": { + SchemaProps: spec.SchemaProps{ + Description: "Represents the relationship between the container exit code(s) and the specified values. Possible values are: - In: the requirement is satisfied if the container exit code is in the\n set of specified values.\n- NotIn: the requirement is satisfied if the container exit code is\n not in the set of specified values.", + Type: []string{"string"}, + Format: "", + }, + }, + "values": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Specifies the set of values to check for container exit codes. At most 255 elements are allowed.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + }, + Required: []string{"operator"}, + }, + }, + } +} + func schema_k8sio_api_core_v1_ContainerState(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -9326,7 +10116,7 @@ func schema_k8sio_api_core_v1_EnvFromSource(ref common.ReferenceCallback) common Properties: map[string]spec.Schema{ "prefix": { SchemaProps: spec.SchemaProps{ - Description: "Optional text to prepend to the name of each environment variable. Must be a C_IDENTIFIER.", + Description: "Optional text to prepend to the name of each environment variable. May consist of any printable ASCII characters except '='.", Type: []string{"string"}, Format: "", }, @@ -9360,7 +10150,7 @@ func schema_k8sio_api_core_v1_EnvVar(ref common.ReferenceCallback) common.OpenAP Properties: map[string]spec.Schema{ "name": { SchemaProps: spec.SchemaProps{ - Description: "Name of the environment variable. Must be a C_IDENTIFIER.", + Description: "Name of the environment variable. May consist of any printable ASCII characters except '='.", Default: "", Type: []string{"string"}, Format: "", @@ -9419,11 +10209,17 @@ func schema_k8sio_api_core_v1_EnvVarSource(ref common.ReferenceCallback) common. Ref: ref("k8s.io/api/core/v1.SecretKeySelector"), }, }, + "fileKeyRef": { + SchemaProps: spec.SchemaProps{ + Description: "FileKeyRef selects a key of the env file. Requires the EnvFiles feature gate to be enabled.", + Ref: ref("k8s.io/api/core/v1.FileKeySelector"), + }, + }, }, }, }, Dependencies: []string{ - "k8s.io/api/core/v1.ConfigMapKeySelector", "k8s.io/api/core/v1.ObjectFieldSelector", "k8s.io/api/core/v1.ResourceFieldSelector", "k8s.io/api/core/v1.SecretKeySelector"}, + "k8s.io/api/core/v1.ConfigMapKeySelector", "k8s.io/api/core/v1.FileKeySelector", "k8s.io/api/core/v1.ObjectFieldSelector", "k8s.io/api/core/v1.ResourceFieldSelector", "k8s.io/api/core/v1.SecretKeySelector"}, } } @@ -9528,7 +10324,7 @@ func schema_k8sio_api_core_v1_EphemeralContainer(ref common.ReferenceCallback) c }, }, SchemaProps: spec.SchemaProps{ - Description: "List of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. All invalid keys will be reported as an event when the container is starting. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated.", + Description: "List of sources to populate environment variables in the container. The keys defined within a source may consist of any printable ASCII characters except '='. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -9592,11 +10388,30 @@ func schema_k8sio_api_core_v1_EphemeralContainer(ref common.ReferenceCallback) c }, "restartPolicy": { SchemaProps: spec.SchemaProps{ - Description: "Restart policy for the container to manage the restart behavior of each container within a pod. This may only be set for init containers. You cannot set this field on ephemeral containers.", + Description: "Restart policy for the container to manage the restart behavior of each container within a pod. You cannot set this field on ephemeral containers.", Type: []string{"string"}, Format: "", }, }, + "restartPolicyRules": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Represents a list of rules to be checked to determine if the container should be restarted on exit. You cannot set this field on ephemeral containers.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ContainerRestartRule"), + }, + }, + }, + }, + }, "volumeMounts": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ @@ -9731,7 +10546,7 @@ func schema_k8sio_api_core_v1_EphemeralContainer(ref common.ReferenceCallback) c }, }, Dependencies: []string{ - "k8s.io/api/core/v1.ContainerPort", "k8s.io/api/core/v1.ContainerResizePolicy", "k8s.io/api/core/v1.EnvFromSource", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.Lifecycle", "k8s.io/api/core/v1.Probe", "k8s.io/api/core/v1.ResourceRequirements", "k8s.io/api/core/v1.SecurityContext", "k8s.io/api/core/v1.VolumeDevice", "k8s.io/api/core/v1.VolumeMount"}, + "k8s.io/api/core/v1.ContainerPort", "k8s.io/api/core/v1.ContainerResizePolicy", "k8s.io/api/core/v1.ContainerRestartRule", "k8s.io/api/core/v1.EnvFromSource", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.Lifecycle", "k8s.io/api/core/v1.Probe", "k8s.io/api/core/v1.ResourceRequirements", "k8s.io/api/core/v1.SecurityContext", "k8s.io/api/core/v1.VolumeDevice", "k8s.io/api/core/v1.VolumeMount"}, } } @@ -9836,7 +10651,7 @@ func schema_k8sio_api_core_v1_EphemeralContainerCommon(ref common.ReferenceCallb }, }, SchemaProps: spec.SchemaProps{ - Description: "List of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. All invalid keys will be reported as an event when the container is starting. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated.", + Description: "List of sources to populate environment variables in the container. The keys defined within a source may consist of any printable ASCII characters except '='. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -9900,11 +10715,30 @@ func schema_k8sio_api_core_v1_EphemeralContainerCommon(ref common.ReferenceCallb }, "restartPolicy": { SchemaProps: spec.SchemaProps{ - Description: "Restart policy for the container to manage the restart behavior of each container within a pod. This may only be set for init containers. You cannot set this field on ephemeral containers.", + Description: "Restart policy for the container to manage the restart behavior of each container within a pod. You cannot set this field on ephemeral containers.", Type: []string{"string"}, Format: "", }, }, + "restartPolicyRules": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Represents a list of rules to be checked to determine if the container should be restarted on exit. You cannot set this field on ephemeral containers.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ContainerRestartRule"), + }, + }, + }, + }, + }, "volumeMounts": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ @@ -10032,7 +10866,7 @@ func schema_k8sio_api_core_v1_EphemeralContainerCommon(ref common.ReferenceCallb }, }, Dependencies: []string{ - "k8s.io/api/core/v1.ContainerPort", "k8s.io/api/core/v1.ContainerResizePolicy", "k8s.io/api/core/v1.EnvFromSource", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.Lifecycle", "k8s.io/api/core/v1.Probe", "k8s.io/api/core/v1.ResourceRequirements", "k8s.io/api/core/v1.SecurityContext", "k8s.io/api/core/v1.VolumeDevice", "k8s.io/api/core/v1.VolumeMount"}, + "k8s.io/api/core/v1.ContainerPort", "k8s.io/api/core/v1.ContainerResizePolicy", "k8s.io/api/core/v1.ContainerRestartRule", "k8s.io/api/core/v1.EnvFromSource", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.Lifecycle", "k8s.io/api/core/v1.Probe", "k8s.io/api/core/v1.ResourceRequirements", "k8s.io/api/core/v1.SecurityContext", "k8s.io/api/core/v1.VolumeDevice", "k8s.io/api/core/v1.VolumeMount"}, } } @@ -10402,6 +11236,57 @@ func schema_k8sio_api_core_v1_FCVolumeSource(ref common.ReferenceCallback) commo } } +func schema_k8sio_api_core_v1_FileKeySelector(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "FileKeySelector selects a key of the env file.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "volumeName": { + SchemaProps: spec.SchemaProps{ + Description: "The name of the volume mount containing the env file.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "path": { + SchemaProps: spec.SchemaProps{ + Description: "The path within the volume from which to select the file. Must be relative and may not contain the '..' path or start with '..'.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "key": { + SchemaProps: spec.SchemaProps{ + Description: "The key within the env file. An invalid key will prevent the pod from starting. The keys defined within a source may consist of any printable ASCII characters except '='. During Alpha stage of the EnvFiles feature gate, the key size is limited to 128 characters.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "optional": { + SchemaProps: spec.SchemaProps{ + Description: "Specify whether the file or its key must be defined. If the file or key does not exist, then the env var is not published. If optional is set to true and the specified key does not exist, the environment variable will not be set in the Pod's containers.\n\nIf optional is set to false and the specified key does not exist, an error will be returned during Pod creation.", + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"volumeName", "path", "key"}, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + }, + } +} + func schema_k8sio_api_core_v1_FlexPersistentVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -10711,7 +11596,7 @@ func schema_k8sio_api_core_v1_GlusterfsVolumeSource(ref common.ReferenceCallback Properties: map[string]spec.Schema{ "endpoints": { SchemaProps: spec.SchemaProps{ - Description: "endpoints is the endpoint name that details Glusterfs topology. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod", + Description: "endpoints is the endpoint name that details Glusterfs topology.", Default: "", Type: []string{"string"}, Format: "", @@ -13421,7 +14306,7 @@ func schema_k8sio_api_core_v1_PersistentVolumeClaimSpec(ref common.ReferenceCall }, "volumeAttributesClassName": { SchemaProps: spec.SchemaProps{ - Description: "volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim. If specified, the CSI driver will create or update the volume with the attributes defined in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName, it can be changed after the claim is created. An empty string value means that no VolumeAttributesClass will be applied to the claim but it's not allowed to reset this field to empty string once it is set. If unspecified and the PersistentVolumeClaim is unbound, the default VolumeAttributesClass will be set by the persistentvolume controller if it exists. If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource exists. More info: https://kubernetes.io/docs/concepts/storage/volume-attributes-classes/ (Beta) Using this field requires the VolumeAttributesClass feature gate to be enabled (off by default).", + Description: "volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim. If specified, the CSI driver will create or update the volume with the attributes defined in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName, it can be changed after the claim is created. An empty string or nil value indicates that no VolumeAttributesClass will be applied to the claim. If the claim enters an Infeasible error state, this field can be reset to its previous value (including nil) to cancel the modification. If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource exists. More info: https://kubernetes.io/docs/concepts/storage/volume-attributes-classes/", Type: []string{"string"}, Format: "", }, @@ -13546,14 +14431,14 @@ func schema_k8sio_api_core_v1_PersistentVolumeClaimStatus(ref common.ReferenceCa }, "currentVolumeAttributesClassName": { SchemaProps: spec.SchemaProps{ - Description: "currentVolumeAttributesClassName is the current name of the VolumeAttributesClass the PVC is using. When unset, there is no VolumeAttributeClass applied to this PersistentVolumeClaim This is a beta field and requires enabling VolumeAttributesClass feature (off by default).", + Description: "currentVolumeAttributesClassName is the current name of the VolumeAttributesClass the PVC is using. When unset, there is no VolumeAttributeClass applied to this PersistentVolumeClaim", Type: []string{"string"}, Format: "", }, }, "modifyVolumeStatus": { SchemaProps: spec.SchemaProps{ - Description: "ModifyVolumeStatus represents the status object of ControllerModifyVolume operation. When this is unset, there is no ModifyVolume operation being attempted. This is a beta field and requires enabling VolumeAttributesClass feature (off by default).", + Description: "ModifyVolumeStatus represents the status object of ControllerModifyVolume operation. When this is unset, there is no ModifyVolume operation being attempted.", Ref: ref("k8s.io/api/core/v1.ModifyVolumeStatus"), }, }, @@ -14058,7 +14943,7 @@ func schema_k8sio_api_core_v1_PersistentVolumeSpec(ref common.ReferenceCallback) }, "volumeAttributesClassName": { SchemaProps: spec.SchemaProps{ - Description: "Name of VolumeAttributesClass to which this persistent volume belongs. Empty value is not allowed. When this field is not set, it indicates that this volume does not belong to any VolumeAttributesClass. This field is mutable and can be changed by the CSI driver after a volume has been updated successfully to a new class. For an unbound PersistentVolume, the volumeAttributesClassName will be matched with unbound PersistentVolumeClaims during the binding process. This is a beta field and requires enabling VolumeAttributesClass feature (off by default).", + Description: "Name of VolumeAttributesClass to which this persistent volume belongs. Empty value is not allowed. When this field is not set, it indicates that this volume does not belong to any VolumeAttributesClass. This field is mutable and can be changed by the CSI driver after a volume has been updated successfully to a new class. For an unbound PersistentVolume, the volumeAttributesClassName will be matched with unbound PersistentVolumeClaims during the binding process.", Type: []string{"string"}, Format: "", }, @@ -14375,7 +15260,7 @@ func schema_k8sio_api_core_v1_PodAntiAffinity(ref common.ReferenceCallback) comm }, }, SchemaProps: spec.SchemaProps{ - Description: "The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred.", + Description: "The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and subtracting \"weight\" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -14457,6 +15342,62 @@ func schema_k8sio_api_core_v1_PodAttachOptions(ref common.ReferenceCallback) com } } +func schema_k8sio_api_core_v1_PodCertificateProjection(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodCertificateProjection provides a private key and X.509 certificate in the pod filesystem.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "signerName": { + SchemaProps: spec.SchemaProps{ + Description: "Kubelet's generated CSRs will be addressed to this signer.", + Type: []string{"string"}, + Format: "", + }, + }, + "keyType": { + SchemaProps: spec.SchemaProps{ + Description: "The type of keypair Kubelet will generate for the pod.\n\nValid values are \"RSA3072\", \"RSA4096\", \"ECDSAP256\", \"ECDSAP384\", \"ECDSAP521\", and \"ED25519\".", + Type: []string{"string"}, + Format: "", + }, + }, + "maxExpirationSeconds": { + SchemaProps: spec.SchemaProps{ + Description: "maxExpirationSeconds is the maximum lifetime permitted for the certificate.\n\nKubelet copies this value verbatim into the PodCertificateRequests it generates for this projection.\n\nIf omitted, kube-apiserver will set it to 86400(24 hours). kube-apiserver will reject values shorter than 3600 (1 hour). The maximum allowable value is 7862400 (91 days).\n\nThe signer implementation is then free to issue a certificate with any lifetime *shorter* than MaxExpirationSeconds, but no shorter than 3600 seconds (1 hour). This constraint is enforced by kube-apiserver. `kubernetes.io` signers will never issue certificates with a lifetime longer than 24 hours.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "credentialBundlePath": { + SchemaProps: spec.SchemaProps{ + Description: "Write the credential bundle at this path in the projected volume.\n\nThe credential bundle is a single file that contains multiple PEM blocks. The first PEM block is a PRIVATE KEY block, containing a PKCS#8 private key.\n\nThe remaining blocks are CERTIFICATE blocks, containing the issued certificate chain from the signer (leaf and any intermediates).\n\nUsing credentialBundlePath lets your Pod's application code make a single atomic read that retrieves a consistent key and certificate chain. If you project them to separate files, your application code will need to additionally check that the leaf certificate was issued to the key.", + Type: []string{"string"}, + Format: "", + }, + }, + "keyPath": { + SchemaProps: spec.SchemaProps{ + Description: "Write the key at this path in the projected volume.\n\nMost applications should use credentialBundlePath. When using keyPath and certificateChainPath, your application needs to check that the key and leaf certificate are consistent, because it is possible to read the files mid-rotation.", + Type: []string{"string"}, + Format: "", + }, + }, + "certificateChainPath": { + SchemaProps: spec.SchemaProps{ + Description: "Write the certificate chain at this path in the projected volume.\n\nMost applications should use credentialBundlePath. When using keyPath and certificateChainPath, your application needs to check that the key and leaf certificate are consistent, because it is possible to read the files mid-rotation.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"signerName", "keyType"}, + }, + }, + } +} + func schema_k8sio_api_core_v1_PodCondition(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -14706,6 +15647,49 @@ func schema_k8sio_api_core_v1_PodExecOptions(ref common.ReferenceCallback) commo } } +func schema_k8sio_api_core_v1_PodExtendedResourceClaimStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodExtendedResourceClaimStatus is stored in the PodStatus for the extended resource requests backed by DRA. It stores the generated name for the corresponding special ResourceClaim created by the scheduler.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "requestMappings": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "RequestMappings identifies the mapping of to device request in the generated ResourceClaim.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ContainerExtendedResourceRequest"), + }, + }, + }, + }, + }, + "resourceClaimName": { + SchemaProps: spec.SchemaProps{ + Description: "ResourceClaimName is the name of the ResourceClaim that was generated for the Pod in the namespace of the Pod.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"requestMappings", "resourceClaimName"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ContainerExtendedResourceRequest"}, + } +} + func schema_k8sio_api_core_v1_PodIP(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -15423,7 +16407,7 @@ func schema_k8sio_api_core_v1_PodSpec(ref common.ReferenceCallback) common.OpenA }, "hostNetwork": { SchemaProps: spec.SchemaProps{ - Description: "Host networking requested for this pod. Use the host's network namespace. If this option is set, the ports that will be used must be specified. Default to false.", + Description: "Host networking requested for this pod. Use the host's network namespace. When using HostNetwork you should specify ports so the scheduler is aware. When `hostNetwork` is true, specified `hostPort` fields in port definitions must match `containerPort`, and unspecified `hostPort` fields in port definitions are defaulted to match `containerPort`. Default to false.", Type: []string{"boolean"}, Format: "", }, @@ -15658,7 +16642,7 @@ func schema_k8sio_api_core_v1_PodSpec(ref common.ReferenceCallback) common.OpenA }, "os": { SchemaProps: spec.SchemaProps{ - Description: "Specifies the OS of the containers in the pod. Some pod and container fields are restricted if this is set.\n\nIf the OS field is set to linux, the following fields must be unset: -securityContext.windowsOptions\n\nIf the OS field is set to windows, following fields must be unset: - spec.hostPID - spec.hostIPC - spec.hostUsers - spec.securityContext.appArmorProfile - spec.securityContext.seLinuxOptions - spec.securityContext.seccompProfile - spec.securityContext.fsGroup - spec.securityContext.fsGroupChangePolicy - spec.securityContext.sysctls - spec.shareProcessNamespace - spec.securityContext.runAsUser - spec.securityContext.runAsGroup - spec.securityContext.supplementalGroups - spec.securityContext.supplementalGroupsPolicy - spec.containers[*].securityContext.appArmorProfile - spec.containers[*].securityContext.seLinuxOptions - spec.containers[*].securityContext.seccompProfile - spec.containers[*].securityContext.capabilities - spec.containers[*].securityContext.readOnlyRootFilesystem - spec.containers[*].securityContext.privileged - spec.containers[*].securityContext.allowPrivilegeEscalation - spec.containers[*].securityContext.procMount - spec.containers[*].securityContext.runAsUser - spec.containers[*].securityContext.runAsGroup", + Description: "Specifies the OS of the containers in the pod. Some pod and container fields are restricted if this is set.\n\nIf the OS field is set to linux, the following fields must be unset: -securityContext.windowsOptions\n\nIf the OS field is set to windows, following fields must be unset: - spec.hostPID - spec.hostIPC - spec.hostUsers - spec.resources - spec.securityContext.appArmorProfile - spec.securityContext.seLinuxOptions - spec.securityContext.seccompProfile - spec.securityContext.fsGroup - spec.securityContext.fsGroupChangePolicy - spec.securityContext.sysctls - spec.shareProcessNamespace - spec.securityContext.runAsUser - spec.securityContext.runAsGroup - spec.securityContext.supplementalGroups - spec.securityContext.supplementalGroupsPolicy - spec.containers[*].securityContext.appArmorProfile - spec.containers[*].securityContext.seLinuxOptions - spec.containers[*].securityContext.seccompProfile - spec.containers[*].securityContext.capabilities - spec.containers[*].securityContext.readOnlyRootFilesystem - spec.containers[*].securityContext.privileged - spec.containers[*].securityContext.allowPrivilegeEscalation - spec.containers[*].securityContext.procMount - spec.containers[*].securityContext.runAsUser - spec.containers[*].securityContext.runAsGroup", Ref: ref("k8s.io/api/core/v1.PodOS"), }, }, @@ -15719,10 +16703,17 @@ func schema_k8sio_api_core_v1_PodSpec(ref common.ReferenceCallback) common.OpenA }, "resources": { SchemaProps: spec.SchemaProps{ - Description: "Resources is the total amount of CPU and Memory resources required by all containers in the pod. It supports specifying Requests and Limits for \"cpu\" and \"memory\" resource names only. ResourceClaims are not supported.\n\nThis field enables fine-grained control over resource allocation for the entire pod, allowing resource sharing among containers in a pod.\n\nThis is an alpha field and requires enabling the PodLevelResources feature gate.", + Description: "Resources is the total amount of CPU and Memory resources required by all containers in the pod. It supports specifying Requests and Limits for \"cpu\", \"memory\" and \"hugepages-\" resource names only. ResourceClaims are not supported.\n\nThis field enables fine-grained control over resource allocation for the entire pod, allowing resource sharing among containers in a pod.\n\nThis is an alpha field and requires enabling the PodLevelResources feature gate.", Ref: ref("k8s.io/api/core/v1.ResourceRequirements"), }, }, + "hostnameOverride": { + SchemaProps: spec.SchemaProps{ + Description: "HostnameOverride specifies an explicit override for the pod's hostname as perceived by the pod. This field only specifies the pod's hostname and does not affect its DNS records. When this field is set to a non-empty string: - It takes precedence over the values set in `hostname` and `subdomain`. - The Pod's hostname will be set to this value. - `setHostnameAsFQDN` must be nil or set to false. - `hostNetwork` must be set to false.\n\nThis field must be a valid DNS subdomain as defined in RFC 1123 and contain at most 64 characters. Requires the HostnameOverride feature gate to be enabled.", + Type: []string{"string"}, + Format: "", + }, + }, }, Required: []string{"containers"}, }, @@ -15960,11 +16951,17 @@ func schema_k8sio_api_core_v1_PodStatus(ref common.ReferenceCallback) common.Ope }, }, }, + "extendedResourceClaimStatus": { + SchemaProps: spec.SchemaProps{ + Description: "Status of extended resource claim backed by DRA.", + Ref: ref("k8s.io/api/core/v1.PodExtendedResourceClaimStatus"), + }, + }, }, }, }, Dependencies: []string{ - "k8s.io/api/core/v1.ContainerStatus", "k8s.io/api/core/v1.HostIP", "k8s.io/api/core/v1.PodCondition", "k8s.io/api/core/v1.PodIP", "k8s.io/api/core/v1.PodResourceClaimStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + "k8s.io/api/core/v1.ContainerStatus", "k8s.io/api/core/v1.HostIP", "k8s.io/api/core/v1.PodCondition", "k8s.io/api/core/v1.PodExtendedResourceClaimStatus", "k8s.io/api/core/v1.PodIP", "k8s.io/api/core/v1.PodResourceClaimStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } @@ -17361,7 +18358,7 @@ func schema_k8sio_api_core_v1_ResourceRequirements(ref common.ReferenceCallback) }, }, SchemaProps: spec.SchemaProps{ - Description: "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container.\n\nThis is an alpha field and requires enabling the DynamicResourceAllocation feature gate.\n\nThis field is immutable. It can only be set for containers.", + Description: "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container.\n\nThis field depends on the DynamicResourceAllocation feature gate.\n\nThis field is immutable. It can only be set for containers.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -19139,7 +20136,7 @@ func schema_k8sio_api_core_v1_Taint(ref common.ReferenceCallback) common.OpenAPI }, "timeAdded": { SchemaProps: spec.SchemaProps{ - Description: "TimeAdded represents the time at which the taint was added. It is only written for NoExecute taints.", + Description: "TimeAdded represents the time at which the taint was added.", Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), }, }, @@ -19518,13 +20515,13 @@ func schema_k8sio_api_core_v1_Volume(ref common.ReferenceCallback) common.OpenAP }, "iscsi": { SchemaProps: spec.SchemaProps{ - Description: "iscsi represents an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md", + Description: "iscsi represents an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes/#iscsi", Ref: ref("k8s.io/api/core/v1.ISCSIVolumeSource"), }, }, "glusterfs": { SchemaProps: spec.SchemaProps{ - Description: "glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. Deprecated: Glusterfs is deprecated and the in-tree glusterfs type is no longer supported. More info: https://examples.k8s.io/volumes/glusterfs/README.md", + Description: "glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. Deprecated: Glusterfs is deprecated and the in-tree glusterfs type is no longer supported.", Ref: ref("k8s.io/api/core/v1.GlusterfsVolumeSource"), }, }, @@ -19536,7 +20533,7 @@ func schema_k8sio_api_core_v1_Volume(ref common.ReferenceCallback) common.OpenAP }, "rbd": { SchemaProps: spec.SchemaProps{ - Description: "rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. Deprecated: RBD is deprecated and the in-tree rbd type is no longer supported. More info: https://examples.k8s.io/volumes/rbd/README.md", + Description: "rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. Deprecated: RBD is deprecated and the in-tree rbd type is no longer supported.", Ref: ref("k8s.io/api/core/v1.RBDVolumeSource"), }, }, @@ -19861,11 +20858,17 @@ func schema_k8sio_api_core_v1_VolumeProjection(ref common.ReferenceCallback) com Ref: ref("k8s.io/api/core/v1.ClusterTrustBundleProjection"), }, }, + "podCertificate": { + SchemaProps: spec.SchemaProps{ + Description: "Projects an auto-rotating credential bundle (private key and certificate chain) that the pod can use either as a TLS client or server.\n\nKubelet generates a private key and uses it to send a PodCertificateRequest to the named signer. Once the signer approves the request and issues a certificate chain, Kubelet writes the key and certificate chain to the pod filesystem. The pod does not start until certificates have been issued for each podCertificate projected volume source in its spec.\n\nKubelet will begin trying to rotate the certificate at the time indicated by the signer using the PodCertificateRequest.Status.BeginRefreshAt timestamp.\n\nKubelet can write a single file, indicated by the credentialBundlePath field, or separate files, indicated by the keyPath and certificateChainPath fields.\n\nThe credential bundle is a single file in PEM format. The first PEM entry is the private key (in PKCS#8 format), and the remaining PEM entries are the certificate chain issued by the signer (typically, signers will return their certificate chain in leaf-to-root order).\n\nPrefer using the credential bundle format, since your application code can read it atomically. If you use keyPath and certificateChainPath, your application must make two separate file reads. If these coincide with a certificate rotation, it is possible that the private key and leaf certificate you read may not correspond to each other. Your application will need to check for this condition, and re-read until they are consistent.\n\nThe named signer controls chooses the format of the certificate it issues; consult the signer implementation's documentation to learn how to use the certificates it issues.", + Ref: ref("k8s.io/api/core/v1.PodCertificateProjection"), + }, + }, }, }, }, Dependencies: []string{ - "k8s.io/api/core/v1.ClusterTrustBundleProjection", "k8s.io/api/core/v1.ConfigMapProjection", "k8s.io/api/core/v1.DownwardAPIProjection", "k8s.io/api/core/v1.SecretProjection", "k8s.io/api/core/v1.ServiceAccountTokenProjection"}, + "k8s.io/api/core/v1.ClusterTrustBundleProjection", "k8s.io/api/core/v1.ConfigMapProjection", "k8s.io/api/core/v1.DownwardAPIProjection", "k8s.io/api/core/v1.PodCertificateProjection", "k8s.io/api/core/v1.SecretProjection", "k8s.io/api/core/v1.ServiceAccountTokenProjection"}, } } @@ -19963,13 +20966,13 @@ func schema_k8sio_api_core_v1_VolumeSource(ref common.ReferenceCallback) common. }, "iscsi": { SchemaProps: spec.SchemaProps{ - Description: "iscsi represents an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md", + Description: "iscsi represents an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes/#iscsi", Ref: ref("k8s.io/api/core/v1.ISCSIVolumeSource"), }, }, "glusterfs": { SchemaProps: spec.SchemaProps{ - Description: "glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. Deprecated: Glusterfs is deprecated and the in-tree glusterfs type is no longer supported. More info: https://examples.k8s.io/volumes/glusterfs/README.md", + Description: "glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. Deprecated: Glusterfs is deprecated and the in-tree glusterfs type is no longer supported.", Ref: ref("k8s.io/api/core/v1.GlusterfsVolumeSource"), }, }, @@ -19981,7 +20984,7 @@ func schema_k8sio_api_core_v1_VolumeSource(ref common.ReferenceCallback) common. }, "rbd": { SchemaProps: spec.SchemaProps{ - Description: "rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. Deprecated: RBD is deprecated and the in-tree rbd type is no longer supported. More info: https://examples.k8s.io/volumes/rbd/README.md", + Description: "rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. Deprecated: RBD is deprecated and the in-tree rbd type is no longer supported.", Ref: ref("k8s.io/api/core/v1.RBDVolumeSource"), }, }, @@ -21358,7 +22361,7 @@ func schema_k8sio_api_networking_v1_NetworkPolicySpec(ref common.ReferenceCallba Properties: map[string]spec.Schema{ "podSelector": { SchemaProps: spec.SchemaProps{ - Description: "podSelector selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace.", + Description: "podSelector selects the pods to which this NetworkPolicy object applies. The array of rules is applied to any pods selected by this field. An empty selector matches all pods in the policy's namespace. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is optional. If it is not specified, it defaults to an empty selector.", Default: map[string]interface{}{}, Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"), }, @@ -21423,7 +22426,6 @@ func schema_k8sio_api_networking_v1_NetworkPolicySpec(ref common.ReferenceCallba }, }, }, - Required: []string{"podSelector"}, }, }, Dependencies: []string{ @@ -22635,15 +23637,12 @@ func schema_pkg_apis_meta_v1_InternalEvent(ref common.ReferenceCallback) common. "Object": { SchemaProps: spec.SchemaProps{ Description: "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Bookmark: the object (instance of a type being watched) where\n only ResourceVersion field is set. On successful restart of watch from a\n bookmark resourceVersion, client is guaranteed to not get repeat event\n nor miss any events.\n * If Type is Error: *api.Status is recommended; other types may make sense\n depending on context.", - Ref: ref("k8s.io/apimachinery/pkg/runtime.Object"), }, }, }, Required: []string{"Type", "Object"}, }, }, - Dependencies: []string{ - "k8s.io/apimachinery/pkg/runtime.Object"}, } } diff --git a/apiserver/Dockerfile b/apiserver/Dockerfile index 934c18f4ab6..8e23ad56ce7 100644 --- a/apiserver/Dockerfile +++ b/apiserver/Dockerfile @@ -35,6 +35,14 @@ LABEL org.opencontainers.image.vendor="Project Calico" LABEL org.opencontainers.image.version="${GIT_VERSION}" LABEL org.opencontainers.image.licenses="Apache-2.0" +LABEL description="Aggregated API server which enables calico resources to be managed via kubectl" +LABEL maintainer="maintainers@tigera.io" +LABEL name="Calico API server" +LABEL release=1 +LABEL summary="Aggregated API server which enables calico resources to be managed via kubectl" +LABEL vendor="Project Calico" +LABEL version="${GIT_VERSION}" + COPY --from=source / / USER 10001:10001 diff --git a/apiserver/Makefile b/apiserver/Makefile index ed3c4394969..199702e5e7f 100644 --- a/apiserver/Makefile +++ b/apiserver/Makefile @@ -36,14 +36,19 @@ ifdef UNIT_TESTS UNIT_TEST_FLAGS = -run $(UNIT_TESTS) -v endif +# The API server tests always use the crd.projectcalico.org API group, since +# the server is never used when the projectcalico.org/v3 API is implemented directly as CRDs. +# Define this prior to including ../lib.Makefile so that the variable is available there. +CALICO_API_GROUP := crd.projectcalico.org/v1 + ############################################################################## -# Download and include ../lib.Makefile before anything else +# Include ../lib.Makefile before anything else # Additions to EXTRA_DOCKER_ARGS need to happen before the include since # that variable is evaluated when we declare DOCKER_RUN and siblings. ############################################################################## - include ../lib.Makefile + FIPS ?= false ifeq ($(FIPS),true) @@ -104,7 +109,9 @@ lint-cache-dir: .PHONY: ut ut: lint-cache-dir run-etcd - $(DOCKER_RUN) $(CALICO_BUILD) \ + $(DOCKER_RUN) \ + -e CALICO_API_GROUP=$(CALICO_API_GROUP) \ + $(CALICO_BUILD) \ sh -c '$(GIT_CONFIG_SSH) ETCD_ENDPOINTS="http://127.0.0.1:2379" DATASTORE_TYPE="etcdv3" go test $(UNIT_TEST_FLAGS) \ $(addprefix $(PACKAGE_NAME)/,$(TEST_DIRS))' @@ -172,6 +179,7 @@ fv-etcd: run-kubernetes-server hack-lib fv-kdd: run-kubernetes-server hack-lib $(DOCKER_RUN) \ -v $(CERTS_PATH):/home/user/certs \ + -e CALICO_API_GROUP=$(CALICO_API_GROUP) \ -e KUBECONFIG=/home/user/certs/kubeconfig \ $(CALICO_BUILD) \ sh -c 'DATASTORE_TYPE="kubernetes" test/integration.sh' diff --git a/apiserver/cmd/apiserver/apiserver.go b/apiserver/cmd/apiserver/apiserver.go index 70c33a6ce19..66775027e73 100644 --- a/apiserver/cmd/apiserver/apiserver.go +++ b/apiserver/cmd/apiserver/apiserver.go @@ -37,11 +37,6 @@ func main() { defer logs.FlushLogs() err := feature.DefaultMutableFeatureGate.SetFromMap(map[string]bool{ - // The ConsistentListFromCache feature gate requires our resourceStore - // to support method RequestWatchProgress, which it does not. Force-disable - // the gate. - string(features.ConsistentListFromCache): false, - // WatchList requires watch bookmarks, which our API server does not currently support. // Note that the WatchBookmarks feature is required to be true - we should probably add // support for this! diff --git a/apiserver/cmd/apiserver/server/logging.go b/apiserver/cmd/apiserver/server/logging.go index 38bdcee566c..56a02ae8389 100644 --- a/apiserver/cmd/apiserver/server/logging.go +++ b/apiserver/cmd/apiserver/server/logging.go @@ -83,7 +83,7 @@ func (l logrusKlog) Enabled(level int) bool { return int64(level) == logLevel } -func toLogrusFields(keysAndValues ...interface{}) logrus.Fields { +func toLogrusFields(keysAndValues ...any) logrus.Fields { fields := logrus.Fields{} for i := 0; i < len(keysAndValues); i += 2 { key := fmt.Sprintf("%v", keysAndValues[i]) diff --git a/apiserver/cmd/apiserver/server/options.go b/apiserver/cmd/apiserver/server/options.go index f8c01e362cc..765ba773f52 100644 --- a/apiserver/cmd/apiserver/server/options.go +++ b/apiserver/cmd/apiserver/server/options.go @@ -19,7 +19,6 @@ limitations under the License. package server import ( - "crypto/tls" "fmt" "net" "os" @@ -129,7 +128,10 @@ func (o *CalicoServerOptions) Config() (*apiserver.Config, error) { } serverConfig.SecureServing.CipherSuites = tlsCipherSuites - serverConfig.SecureServing.MinTLSVersion = tls.VersionTLS12 + serverConfig.SecureServing.MinTLSVersion, err = calicotls.ParseTLSVersion(os.Getenv("TLS_MIN_VERSION")) + if err != nil { + return nil, err + } if o.PrintSwagger { o.DisableAuth = true diff --git a/apiserver/cmd/apiserver/server/watch.go b/apiserver/cmd/apiserver/server/watch.go index f04ae057d92..df752c3fa0d 100644 --- a/apiserver/cmd/apiserver/server/watch.go +++ b/apiserver/cmd/apiserver/server/watch.go @@ -66,21 +66,21 @@ func WatchExtensionAuth(ctx context.Context) (bool, error) { ObjectType: &corev1.ConfigMap{}, ResyncPeriod: 0, Handler: cache.ResourceEventHandlerFuncs{ - AddFunc: func(_ interface{}) { + AddFunc: func(_ any) { if synced { logrus.Info("Detected creation of extension-apiserver-authentication ConfigMap") changed = true cancel() } }, - DeleteFunc: func(_ interface{}) { + DeleteFunc: func(_ any) { if synced { logrus.Info("Detected deletion of extension-apiserver-authentication ConfigMap") changed = true cancel() } }, - UpdateFunc: func(old, new interface{}) { + UpdateFunc: func(old, new any) { if synced { o := old.(*corev1.ConfigMap) n := new.(*corev1.ConfigMap) diff --git a/apiserver/deps.txt b/apiserver/deps.txt index b12f42f37b8..b6932b28385 100644 --- a/apiserver/deps.txt +++ b/apiserver/deps.txt @@ -2,22 +2,25 @@ This file contains the list of modules that this package depends on in order to trigger CI on changes -go 1.25.1 -cel.dev/expr v0.20.0 +go 1.25.7 +cel.dev/expr v0.24.0 github.com/NYTimes/gziphandler v1.1.1 github.com/antlr4-go/antlr/v4 v4.13.0 github.com/beorn7/perks v1.0.1 github.com/blang/semver/v4 v4.0.0 -github.com/cenkalti/backoff/v4 v4.3.0 +github.com/cenkalti/backoff/v5 v5.0.2 github.com/cespare/xxhash/v2 v2.3.0 github.com/coreos/go-semver v0.3.1 -github.com/coreos/go-systemd/v22 v22.5.0 +github.com/coreos/go-systemd/v22 v22.6.0 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/distribution/reference v0.6.0 -github.com/emicklei/go-restful/v3 v3.11.0 +github.com/emicklei/go-restful v2.15.0+incompatible +github.com/emicklei/go-restful/v3 v3.12.2 github.com/felixge/httpsnoop v1.0.4 github.com/fsnotify/fsnotify v1.9.0 -github.com/fxamacker/cbor/v2 v2.7.0 +github.com/fxamacker/cbor/v2 v2.9.0 +github.com/go-kit/log v0.2.1 +github.com/go-logfmt/logfmt v0.6.0 github.com/go-logr/logr v1.4.3 github.com/go-logr/stdr v1.2.2 github.com/go-openapi/jsonpointer v0.21.0 @@ -28,13 +31,12 @@ github.com/go-playground/universal-translator v0.18.1 github.com/gogo/protobuf v1.3.2 github.com/golang/protobuf v1.5.4 github.com/google/btree v1.1.3 -github.com/google/cel-go v0.23.2 -github.com/google/gnostic-models v0.6.9 -github.com/google/go-cmp v0.7.0 +github.com/google/cel-go v0.26.0 +github.com/google/gnostic-models v0.7.0 github.com/google/uuid v1.6.0 +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 -github.com/grpc-ecosystem/grpc-gateway v1.16.0 -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 github.com/jinzhu/copier v0.4.0 github.com/josharian/intern v1.0.0 github.com/json-iterator/go v1.1.12 @@ -43,79 +45,81 @@ github.com/kylelemons/godebug v1.1.0 github.com/leodido/go-urn v1.4.0 github.com/mailru/easyjson v0.7.7 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd -github.com/modern-go/reflect2 v1.0.2 +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 github.com/opencontainers/go-digest v1.0.0 +github.com/openshift/custom-resource-status v1.1.2 github.com/pkg/errors v0.9.1 +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/projectcalico/calico -github.com/projectcalico/calico/lib/std v0.0.0-00010101000000-000000000000 -github.com/projectcalico/go-json v0.0.0-20161128004156-6219dc7339ba -github.com/projectcalico/go-yaml-wrapper v0.0.0-20191112210931-090425220c54 -github.com/prometheus/client_golang v1.23.0 +github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 -github.com/prometheus/common v0.65.0 -github.com/prometheus/procfs v0.17.0 +github.com/prometheus/common v0.67.2 +github.com/prometheus/procfs v0.19.2 github.com/sirupsen/logrus v1.9.3 -github.com/spf13/cobra v1.9.1 -github.com/spf13/pflag v1.0.7 +github.com/spf13/cobra v1.10.1 +github.com/spf13/pflag v1.0.10 github.com/stoewer/go-strcase v1.3.0 github.com/x448/float16 v0.8.4 -go.etcd.io/etcd/api/v3 v3.6.4 -go.etcd.io/etcd/client/pkg/v3 v3.6.4 -go.etcd.io/etcd/client/v3 v3.6.4 +go.etcd.io/etcd/api/v3 v3.6.5 +go.etcd.io/etcd/client/pkg/v3 v3.6.5 +go.etcd.io/etcd/client/v3 v3.6.5 go.opentelemetry.io/auto/sdk v1.1.0 -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 -go.opentelemetry.io/otel v1.35.0 -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 -go.opentelemetry.io/otel/metric v1.35.0 -go.opentelemetry.io/otel/sdk v1.35.0 -go.opentelemetry.io/otel/trace v1.35.0 -go.opentelemetry.io/proto/otlp v1.5.0 +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 +go.opentelemetry.io/otel v1.37.0 +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 +go.opentelemetry.io/otel/metric v1.37.0 +go.opentelemetry.io/otel/sdk v1.37.0 +go.opentelemetry.io/otel/trace v1.37.0 +go.opentelemetry.io/proto/otlp v1.7.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 -go.yaml.in/yaml/v2 v2.4.2 -golang.org/x/crypto v0.41.0 -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 -golang.org/x/net v0.43.0 -golang.org/x/oauth2 v0.30.0 -golang.org/x/sync v0.16.0 -golang.org/x/sys v0.35.0 -golang.org/x/term v0.34.0 -golang.org/x/text v0.28.0 -golang.org/x/time v0.12.0 +go.yaml.in/yaml/v2 v2.4.3 +go.yaml.in/yaml/v3 v3.0.4 +golang.org/x/crypto v0.47.0 +golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 +golang.org/x/net v0.49.0 +golang.org/x/oauth2 v0.34.0 +golang.org/x/sync v0.19.0 +golang.org/x/sys v0.40.0 +golang.org/x/term v0.39.0 +golang.org/x/text v0.33.0 +golang.org/x/time v0.14.0 golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 google.golang.org/genproto v0.0.0-20250603155806-513f23925822 -google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 -google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a -google.golang.org/grpc v1.72.2 -google.golang.org/protobuf v1.36.7 +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c +google.golang.org/grpc v1.76.0 +google.golang.org/protobuf v1.36.10 gopkg.in/evanphx/json-patch.v4 v4.12.0 gopkg.in/go-playground/validator.v9 v9.30.2 gopkg.in/inf.v0 v0.9.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1 -gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 -k8s.io/api v0.33.5 -k8s.io/apiextensions-apiserver v0.33.5 -k8s.io/apimachinery v0.33.5 -k8s.io/apiserver v0.33.5 -k8s.io/client-go v0.33.5 -k8s.io/component-base v0.33.5 -k8s.io/component-helpers v0.33.5 -k8s.io/controller-manager v0.33.5 +k8s.io/api v0.34.3 +k8s.io/apiextensions-apiserver v0.34.3 +k8s.io/apimachinery v0.34.3 +k8s.io/apiserver v0.34.3 +k8s.io/client-go v0.34.3 +k8s.io/component-base v0.34.3 +k8s.io/component-helpers v0.34.3 +k8s.io/controller-manager v0.34.3 k8s.io/klog v0.2.0 k8s.io/klog/v2 v2.130.1 -k8s.io/kms v0.33.5 -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff -k8s.io/kubelet v0.33.5 -k8s.io/kubernetes v1.33.5 -k8s.io/utils v0.0.0-20241210054802-24370beab758 -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 +k8s.io/kms v0.34.3 +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b +k8s.io/kubelet v0.34.3 +k8s.io/kubernetes v1.34.3 +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 +kubevirt.io/api v1.8.0-alpha.0 +kubevirt.io/containerized-data-importer-api v1.63.1 +kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 -sigs.k8s.io/network-policy-api v0.1.5 +sigs.k8s.io/network-policy-api v0.1.8-0.20260212153203-412bf65729a5 sigs.k8s.io/randfill v1.0.0 -sigs.k8s.io/structured-merge-diff/v4 v4.6.0 +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 sigs.k8s.io/yaml v1.6.0 diff --git a/apiserver/pkg/apiserver/apiserver.go b/apiserver/pkg/apiserver/apiserver.go index b809142ada5..92e9f9ddba1 100644 --- a/apiserver/pkg/apiserver/apiserver.go +++ b/apiserver/pkg/apiserver/apiserver.go @@ -169,47 +169,47 @@ func (c completedConfig) NewRBACCalculator(calicoLister CalicoResourceLister) (r // k8sRoleGetter implements the RoleGetter interface returning matching Role. type k8sRoleGetter struct { - roleLister rbacv1listers.RoleLister + Lister rbacv1listers.RoleLister } func (r *k8sRoleGetter) GetRole(ctx context.Context, namespace, name string) (*rbacv1.Role, error) { - return r.roleLister.Roles(namespace).Get(name) + return r.Lister.Roles(namespace).Get(name) } // k8sRoleBindingLister implements the RoleBindingLister interface returning RoleBindings. type k8sRoleBindingLister struct { - roleBindingLister rbacv1listers.RoleBindingLister + lister rbacv1listers.RoleBindingLister } func (r *k8sRoleBindingLister) ListRoleBindings(ctx context.Context, namespace string) ([]*rbacv1.RoleBinding, error) { - return r.roleBindingLister.RoleBindings(namespace).List(labels.Everything()) + return r.lister.RoleBindings(namespace).List(labels.Everything()) } // k8sClusterRoleGetter implements the ClusterRoleGetter interface returning matching ClusterRole. type k8sClusterRoleGetter struct { - clusterRoleLister rbacv1listers.ClusterRoleLister + lister rbacv1listers.ClusterRoleLister } func (r *k8sClusterRoleGetter) GetClusterRole(ctx context.Context, name string) (*rbacv1.ClusterRole, error) { - return r.clusterRoleLister.Get(name) + return r.lister.Get(name) } // k8sClusterRoleBindingLister implements the ClusterRoleBindingLister interface. type k8sClusterRoleBindingLister struct { - clusterRoleBindingLister rbacv1listers.ClusterRoleBindingLister + lister rbacv1listers.ClusterRoleBindingLister } func (r *k8sClusterRoleBindingLister) ListClusterRoleBindings(ctx context.Context) ([]*rbacv1.ClusterRoleBinding, error) { - return r.clusterRoleBindingLister.List(labels.Everything()) + return r.lister.List(labels.Everything()) } // k8sNamespaceLister implements the NamespaceLister interface returning Namespaces. type k8sNamespaceLister struct { - namespaceLister corev1listers.NamespaceLister + lister corev1listers.NamespaceLister } func (n *k8sNamespaceLister) ListNamespaces() ([]*corev1.Namespace, error) { - return n.namespaceLister.List(labels.Everything()) + return n.lister.List(labels.Everything()) } func NewCalicoResourceLister(cc api.Client) CalicoResourceLister { diff --git a/apiserver/pkg/printers/projectcalico/caliconodestatus_printer.go b/apiserver/pkg/printers/projectcalico/caliconodestatus_printer.go index 77fe239f12c..0530192fd13 100644 --- a/apiserver/pkg/printers/projectcalico/caliconodestatus_printer.go +++ b/apiserver/pkg/printers/projectcalico/caliconodestatus_printer.go @@ -17,6 +17,7 @@ package projectcalico import ( "fmt" "reflect" + "slices" "strings" calico "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -62,19 +63,14 @@ func printCalicoNodeStatus(status *calico.CalicoNodeStatus, options printers.Gen // Define a function return true if the status contains information about a class. hasClass := func(c calico.NodeStatusClassType) bool { - for _, class := range status.Spec.Classes { - if class == c { - return true - } - } - return false + return slices.Contains(status.Spec.Classes, c) } - var classes string + var classes strings.Builder for _, class := range status.Spec.Classes { - classes += fmt.Sprintf("%s,", class) + classes.WriteString(fmt.Sprintf("%s,", class)) } - classesStr := strings.TrimSuffix(classes, ",") + classesStr := strings.TrimSuffix(classes.String(), ",") var lastUpdatedStr string lastUpdatedDate := status.Status.LastUpdated diff --git a/apiserver/pkg/printers/projectcalico/printers_test.go b/apiserver/pkg/printers/projectcalico/printers_test.go index 0207c25ada4..0079523a7ac 100644 --- a/apiserver/pkg/printers/projectcalico/printers_test.go +++ b/apiserver/pkg/printers/projectcalico/printers_test.go @@ -153,13 +153,13 @@ func TestPrintCalicoNodeStatus(t *testing.T) { { status: status, option: printers.GenerateOptions{}, - expected: []metav1.TableRow{{Cells: []interface{}{"mystatus", + expected: []metav1.TableRow{{Cells: []any{"mystatus", "node0", "Agent,BGP,Routes", "10s", "3m", "3m ago"}}}, }, { status: status, option: printers.GenerateOptions{Wide: true}, - expected: []metav1.TableRow{{Cells: []interface{}{"mystatus", + expected: []metav1.TableRow{{Cells: []any{"mystatus", "node0", "Agent,BGP,Routes", "10s", "3m", "3m ago", "v4(Ready) v6(Ready)", "2/3", "1/1", "2", "1", }}}, @@ -175,7 +175,7 @@ func TestPrintCalicoNodeStatus(t *testing.T) { rows[i].Object.Object = nil } if !reflect.DeepEqual(test.expected, rows) { - t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) + t.Errorf("%d mismatch: %s", i, diff.Diff(test.expected, rows)) } } } diff --git a/apiserver/pkg/rbac/calculator_test.go b/apiserver/pkg/rbac/calculator_test.go index df528b972c3..c10baf38640 100644 --- a/apiserver/pkg/rbac/calculator_test.go +++ b/apiserver/pkg/rbac/calculator_test.go @@ -16,8 +16,9 @@ package rbac_test import ( "encoding/json" + "slices" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" gomegatypes "github.com/onsi/gomega/types" rbac_v1 "k8s.io/api/rbac/v1" @@ -125,12 +126,7 @@ var ( ) func isOneOf(rt ResourceType, rts ...ResourceType) bool { - for _, rtss := range rts { - if rt == rtss { - return true - } - } - return false + return slices.Contains(rts, rt) } var allResourceVerbs []ResourceVerbs @@ -225,7 +221,7 @@ var _ = Describe("RBAC calculator tests", func() { Expect(err).ToNot(HaveOccurred()) Expect(res).To(HaveLen(len(defaultResourceTypes)), "one result for each resource type") - Describe("all resources", func() { + By("all resources", func() { for _, resourceType := range defaultResourceTypes { Expect(res).To(HaveKey(resourceType), "one result for each resource type") Expect(res[resourceNamespaces]).To(haveMatchAllForVerbs( @@ -244,7 +240,7 @@ var _ = Describe("RBAC calculator tests", func() { } }) - Describe("tiered policies", func() { + By("tiered policies", func() { for _, resourceType := range tieredPolicyResources { Expect(res[resourceType]).To(haveOnlyMatchesForVerbs([]Match{ {Tier: "default"}, @@ -256,7 +252,7 @@ var _ = Describe("RBAC calculator tests", func() { } }) - Describe("namespaces", func() { + By("namespaces", func() { Expect(res[resourceNamespaces]).To(haveMatchForVerbs([]Match{ {Namespace: "ns1"}, {Namespace: "ns2"}, @@ -266,7 +262,7 @@ var _ = Describe("RBAC calculator tests", func() { }, VerbGet)) }) - Describe("get tiers", func() { + By("get tiers", func() { Expect(res[resourceTiers]).To(haveMatchForVerbs([]Match{ {Tier: "default"}, {Tier: "tier1"}, @@ -297,7 +293,7 @@ var _ = Describe("RBAC calculator tests", func() { res, err := calc.CalculatePermissions(myUser, allResourceVerbs) Expect(err).ToNot(HaveOccurred()) Expect(res).To(HaveLen(len(defaultResourceTypes))) - Describe("all resources", func() { + By("all resources", func() { for _, resourceType := range defaultResourceTypes { Expect(res).To(HaveKey(resourceType), "one result for each resource type") Expect(res[resourceType]).To(HaveLen(len(AllVerbs)), "one result for each defined verb") @@ -309,7 +305,7 @@ var _ = Describe("RBAC calculator tests", func() { } }) - Describe("tiers", func() { + By("tiers", func() { Expect(res[resourceTiers]).To(haveMatchNoneForVerbs(VerbWatch, VerbCreate, VerbList, VerbUpdate)) Expect(res[resourceTiers]).To(haveMatchAllForVerbs(VerbDelete, VerbPatch)) Expect(res[resourceTiers]).To(haveMatchForVerbs([]Match{ @@ -318,7 +314,7 @@ var _ = Describe("RBAC calculator tests", func() { }, VerbGet)) }) - Describe("tiered policies", func() { + By("tiered policies", func() { // Matches for tiered policy should only contain the gettable Tiers. for _, resourceType := range tieredPolicyResources { Expect(res[resourceType]).To(haveOnlyMatchesForVerbs([]Match{ @@ -328,7 +324,7 @@ var _ = Describe("RBAC calculator tests", func() { } }) - Describe("all other resources", func() { + By("all other resources", func() { for _, resourceType := range []ResourceType{ resourceHostEndpoints, resourceNamespaces, @@ -372,7 +368,7 @@ var _ = Describe("RBAC calculator tests", func() { Expect(err).ToNot(HaveOccurred()) Expect(res).To(HaveLen(len(defaultResourceTypes))) - Describe("cluster-scoped resources", func() { + By("cluster-scoped resources", func() { for _, resourceType := range clusterScopedResources { Expect(res).To(HaveKey(resourceType), "one result for each resource type") Expect(res[resourceType]).To(HaveLen(len(AllVerbs)), "one result for each defined verb") @@ -385,11 +381,11 @@ var _ = Describe("RBAC calculator tests", func() { } }) - Describe("namespaces", func() { + By("namespaces", func() { Expect(res[resourceNamespaces]).To(haveMatchNoneForAllVerbs()) }) - Describe("tiers", func() { + By("tiers", func() { Expect(res[resourceTiers]).To(haveOnlyMatchesForVerbs([]Match{ {Tier: "default"}, {Tier: "tier1"}, @@ -399,7 +395,7 @@ var _ = Describe("RBAC calculator tests", func() { }, VerbGet)) }) - Describe("cluster-scoped tiered policy resources", func() { + By("cluster-scoped tiered policy resources", func() { for _, resourceType := range []ResourceType{ resourceGlobalNetworkPolicies, resourceStagedGlobalNetworkPolicies, @@ -408,7 +404,7 @@ var _ = Describe("RBAC calculator tests", func() { } }) - Describe("namespaced tiered policy resources", func() { + By("namespaced tiered policy resources", func() { for _, resourceType := range []ResourceType{ resourceCalicoNetworkPolicies, resourceStagedCalicoNetworkPolicies, @@ -424,12 +420,12 @@ var _ = Describe("RBAC calculator tests", func() { } }) - Describe("namespaced", func() { + By("namespaced", func() { for _, resourceType := range namespacedResources { Expect(res[resourceType]).To(HaveLen(len(AllVerbs))) if resourceType == resourceCalicoNetworkPolicies || resourceType == resourceStagedCalicoNetworkPolicies { - Describe("tiered policy resources", func() { + By("tiered policy resources", func() { Expect(res[resourceType]).To(haveMatchForVerbs([]Match{ {Namespace: "ns1", Tier: "default"}, {Namespace: "ns1", Tier: "tier1"}, @@ -440,7 +436,7 @@ var _ = Describe("RBAC calculator tests", func() { Expect(res[resourceType]).To(haveMatchNoneForVerbs(VerbGet, VerbDelete, VerbPatch, VerbWatch), resourceType.String()) }) } else { - Describe("resources (excluding tiered-policy resources", func() { + By("resources (excluding tiered-policy resources", func() { Expect(res[resourceType]).To(haveMatchForVerbs([]Match{{Namespace: "ns1"}}, VerbUpdate, VerbCreate, VerbList), resourceType.String()) Expect(res[resourceType]).To(haveMatchNoneForVerbs(VerbGet, VerbDelete, VerbPatch, VerbWatch), resourceType.String()) }) @@ -464,16 +460,16 @@ var _ = Describe("RBAC calculator tests", func() { Expect(res).To(HaveLen(len(defaultResourceTypes))) // namespaced resources that aren't tiered policies should have ns matchers - Describe("namespaced", func() { + By("namespaced", func() { for _, resourceType := range namespacedResources { Expect(res[resourceType]).To(HaveLen(len(AllVerbs)), "one result for each defined verb") if resourceType == resourceCalicoNetworkPolicies || resourceType == resourceStagedCalicoNetworkPolicies { - Describe("tiered policy resources", func() { + By("tiered policy resources", func() { Expect(res[resourceType]).To(haveMatchNoneForAllVerbs(), resourceType.String()) }) } else { - Describe("resources (except tiered-policies)", func() { + By("resources (except tiered-policies)", func() { Expect(res[resourceType]).To(haveMatchForVerbs([]Match{{Namespace: "ns1"}}, VerbUpdate, VerbCreate, VerbList)) Expect(res[resourceType]).To(haveMatchNoneForVerbs(VerbGet, VerbDelete, VerbPatch, VerbWatch)) }) @@ -481,7 +477,7 @@ var _ = Describe("RBAC calculator tests", func() { } }) - Describe("cluster-scoped", func() { + By("cluster-scoped", func() { for _, resourceType := range clusterScopedResources { Expect(res[resourceType]).To(HaveLen(len(AllVerbs)), "one result for each defined verb") Expect(res[resourceType]).To(haveMatchNoneForAllVerbs()) @@ -518,7 +514,7 @@ var _ = Describe("RBAC calculator tests", func() { Expect(err).ToNot(HaveOccurred()) Expect(res).To(HaveLen(len(defaultResourceTypes))) - Describe("tiers", func() { + By("tiers", func() { Expect(res[resourceTiers]).To(Equal(map[Verb][]Match{ VerbGet: { {Tier: "tier2"}, @@ -534,12 +530,12 @@ var _ = Describe("RBAC calculator tests", func() { }) // namespaced resources that aren't tiered policies should have ns matchers - Describe("namespaced", func() { + By("namespaced", func() { for _, resourceType := range namespacedResources { Expect(res[resourceType]).To(HaveLen(len(AllVerbs)), "one result for each defined verb") if resourceType == resourceCalicoNetworkPolicies || resourceType == resourceStagedCalicoNetworkPolicies { - Describe("tiered policy resources", func() { + By("tiered policy resources", func() { Expect(res[resourceType]).To(Equal(map[Verb][]Match{ VerbCreate: nil, VerbPatch: {{Tier: "tier2", Namespace: "ns1"}, {Tier: "tier3", Namespace: "ns1"}}, @@ -551,7 +547,7 @@ var _ = Describe("RBAC calculator tests", func() { }), resourceType.String()) }) } else { - Describe("resources (except tiered-policies)", func() { + By("resources (except tiered-policies)", func() { Expect(res[resourceType]).To(Equal(map[Verb][]Match{ VerbCreate: nil, VerbPatch: {{Namespace: "ns1"}}, @@ -566,7 +562,7 @@ var _ = Describe("RBAC calculator tests", func() { } }) - Describe("cluster-scoped", func() { + By("cluster-scoped", func() { for _, resourceType := range clusterScopedResources { if resourceType == resourceTiers { // handle tiers separately as they have different GET permissions @@ -983,10 +979,5 @@ func expectPresentButEmpty(p Permissions, rvs []ResourceVerbs) { } func contains[T comparable](elems []T, v T) bool { - for _, s := range elems { - if v == s { - return true - } - } - return false + return slices.Contains(elems, v) } diff --git a/apiserver/pkg/rbac/rbac_suite_test.go b/apiserver/pkg/rbac/rbac_suite_test.go index f7162f7e342..37b635c778c 100644 --- a/apiserver/pkg/rbac/rbac_suite_test.go +++ b/apiserver/pkg/rbac/rbac_suite_test.go @@ -17,7 +17,7 @@ package rbac_test import ( "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" diff --git a/apiserver/pkg/registry/projectcalico/authorizer/authorizer.go b/apiserver/pkg/registry/projectcalico/authorizer/authorizer.go index 68589599ad8..d6c8d16cd21 100644 --- a/apiserver/pkg/registry/projectcalico/authorizer/authorizer.go +++ b/apiserver/pkg/registry/projectcalico/authorizer/authorizer.go @@ -80,7 +80,11 @@ func (a *authorizer) AuthorizeTierOperation( logrus.Trace("Checking authorization using tier resource type (user can get tier)") logAuthorizerAttributes(attrs) - decisionGetTier, _, _ = a.Authorize(context.TODO(), attrs) + var reason string + decisionGetTier, reason, err = a.Authorize(ctx, attrs) + if err != nil { + logrus.WithField("reason", reason).Errorf("Error authorizing tier GET request: %v", err) + } }() // Query required access to the tiered policy resource or tier wildcard resource. @@ -113,7 +117,10 @@ func (a *authorizer) AuthorizeTierOperation( logrus.Trace("Checking authorization using tier scoped resource type (policy name match)") logAuthorizerAttributes(attrs) - decisionPolicy, _, _ = a.Authorize(context.TODO(), attrs) + decisionPolicy, _, err = a.Authorize(ctx, attrs) + if err != nil { + logrus.Errorf("Error authorizing tiered policy request: %v", err) + } }() go func() { defer wg.Done() @@ -134,7 +141,10 @@ func (a *authorizer) AuthorizeTierOperation( logrus.Trace("Checking authorization using tier scoped resource type (tier name match)") logAuthorizerAttributes(attrs) - decisionTierWildcard, _, _ = a.Authorize(context.TODO(), attrs) + decisionTierWildcard, _, err = a.Authorize(ctx, attrs) + if err != nil { + logrus.Errorf("Error authorizing tier wildcard request: %v", err) + } }() // Wait for the requests to complete. @@ -142,8 +152,7 @@ func (a *authorizer) AuthorizeTierOperation( // If the user has GET access to the tier and either the policy match or tier wildcard match are authorized // then allow the request. - if decisionGetTier == k8sauth.DecisionAllow && - (decisionPolicy == k8sauth.DecisionAllow || decisionTierWildcard == k8sauth.DecisionAllow) { + if decisionGetTier == k8sauth.DecisionAllow && (decisionPolicy == k8sauth.DecisionAllow || decisionTierWildcard == k8sauth.DecisionAllow) { logrus.Trace("Operation allowed") return nil } diff --git a/apiserver/pkg/registry/projectcalico/globalpolicy/storage.go b/apiserver/pkg/registry/projectcalico/globalpolicy/storage.go index 3b6e9e5c9e1..709645493bd 100644 --- a/apiserver/pkg/registry/projectcalico/globalpolicy/storage.go +++ b/apiserver/pkg/registry/projectcalico/globalpolicy/storage.go @@ -32,6 +32,7 @@ import ( "github.com/projectcalico/calico/apiserver/pkg/registry/projectcalico/authorizer" "github.com/projectcalico/calico/apiserver/pkg/registry/projectcalico/server" "github.com/projectcalico/calico/apiserver/pkg/registry/projectcalico/util" + "github.com/projectcalico/calico/libcalico-go/lib/names" ) // rest implements a RESTStorage for API services against etcd @@ -118,8 +119,7 @@ func (r *REST) List(ctx context.Context, options *metainternalversion.ListOption func (r *REST) Create(ctx context.Context, obj runtime.Object, val rest.ValidateObjectFunc, createOpt *metav1.CreateOptions) (runtime.Object, error) { policy := obj.(*calico.GlobalNetworkPolicy) - // Is Tier prepended. If not prepend default? - tierName, _ := util.GetTierFromPolicyName(policy.Name) + tierName := names.TierOrDefault(policy.Spec.Tier) err := r.authorizer.AuthorizeTierOperation(ctx, policy.Name, tierName) if err != nil { return nil, err @@ -130,8 +130,12 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, val rest.Validate func (r *REST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { - tierName, _ := util.GetTierFromPolicyName(name) - err := r.authorizer.AuthorizeTierOperation(ctx, name, tierName) + obj, err := r.Store.Get(ctx, name, &metav1.GetOptions{}) + if err != nil { + return nil, false, err + } + tierName := names.TierOrDefault(obj.(*calico.GlobalNetworkPolicy).Spec.Tier) + err = r.authorizer.AuthorizeTierOperation(ctx, name, tierName) if err != nil { return nil, false, err } @@ -141,18 +145,26 @@ func (r *REST) Update(ctx context.Context, name string, objInfo rest.UpdatedObje // Get retrieves the item from storage. func (r *REST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { - tierName, _ := util.GetTierFromPolicyName(name) - err := r.authorizer.AuthorizeTierOperation(ctx, name, tierName) + obj, err := r.Store.Get(ctx, name, options) + if err != nil { + return nil, err + } + tierName := names.TierOrDefault(obj.(*calico.GlobalNetworkPolicy).Spec.Tier) + err = r.authorizer.AuthorizeTierOperation(ctx, name, tierName) if err != nil { return nil, err } - return r.Store.Get(ctx, name, options) + return obj, nil } func (r *REST) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { - tierName, _ := util.GetTierFromPolicyName(name) - err := r.authorizer.AuthorizeTierOperation(ctx, name, tierName) + obj, err := r.Store.Get(ctx, name, &metav1.GetOptions{}) + if err != nil { + return nil, false, err + } + tierName := names.TierOrDefault(obj.(*calico.GlobalNetworkPolicy).Spec.Tier) + err = r.authorizer.AuthorizeTierOperation(ctx, name, tierName) if err != nil { return nil, false, err } diff --git a/apiserver/pkg/registry/projectcalico/kubecontrollersconfig/storage.go b/apiserver/pkg/registry/projectcalico/kubecontrollersconfig/storage.go index e9fdab9c9d3..615a717f020 100644 --- a/apiserver/pkg/registry/projectcalico/kubecontrollersconfig/storage.go +++ b/apiserver/pkg/registry/projectcalico/kubecontrollersconfig/storage.go @@ -70,26 +70,15 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat } // NewREST returns a RESTStorage object that will work against API services. -func NewREST(scheme *runtime.Scheme, opts server.Options) (*REST, *StatusREST, error) { +func NewREST(scheme *runtime.Scheme, opts server.Options, statusOpts server.Options) (*REST, *StatusREST, error) { strategy := NewStrategy(scheme) prefix := "/" + opts.ResourcePrefix() - // We adapt the store's keyFunc so that we can use it with the StorageDecorator - // without making any assumptions about where objects are stored in etcd - keyFunc := func(obj runtime.Object) (string, error) { - accessor, err := meta.Accessor(obj) - if err != nil { - return "", err - } - return registry.NoNamespaceKeyFunc( - genericapirequest.NewContext(), - prefix, - accessor.GetName(), - ) - } + statusPrefix := "/" + statusOpts.ResourcePrefix() + storageInterface, dFunc, err := opts.GetStorage( prefix, - keyFunc, + configureKeyFunc(prefix), strategy, func() runtime.Object { return &calico.KubeControllersConfiguration{} }, func() runtime.Object { return &calico.KubeControllersConfigurationList{} }, @@ -100,6 +89,7 @@ func NewREST(scheme *runtime.Scheme, opts server.Options) (*REST, *StatusREST, e if err != nil { return nil, nil, err } + store := ®istry.Store{ NewFunc: func() runtime.Object { return &calico.KubeControllersConfiguration{} }, NewListFunc: func() runtime.Object { return &calico.KubeControllersConfigurationList{} }, @@ -120,12 +110,42 @@ func NewREST(scheme *runtime.Scheme, opts server.Options) (*REST, *StatusREST, e DestroyFunc: dFunc, } + statusStorageInterface, statusDFunc, err := statusOpts.GetStorage( + prefix, + configureKeyFunc(statusPrefix), + strategy, + func() runtime.Object { return &calico.KubeControllersConfiguration{} }, + func() runtime.Object { return &calico.KubeControllersConfigurationList{} }, + GetAttrs, + nil, + nil, + ) + if err != nil { + return nil, nil, err + } + statusStore := *store statusStore.UpdateStrategy = NewStatusStrategy(strategy) + statusStore.Storage = statusStorageInterface + statusStore.DestroyFunc = statusDFunc return &REST{store, opts.ShortNames}, &StatusREST{&statusStore, opts.ShortNames}, nil } +func configureKeyFunc(resourcePrefix string) func(obj runtime.Object) (string, error) { + return func(obj runtime.Object) (string, error) { + accessor, err := meta.Accessor(obj) + if err != nil { + return "", err + } + return registry.NoNamespaceKeyFunc( + genericapirequest.NewContext(), + resourcePrefix, + accessor.GetName(), + ) + } +} + func (r *REST) ShortNames() []string { return r.shortNames } diff --git a/apiserver/pkg/registry/projectcalico/networkpolicy/storage.go b/apiserver/pkg/registry/projectcalico/networkpolicy/storage.go index b9fbae07d37..73e970c443a 100644 --- a/apiserver/pkg/registry/projectcalico/networkpolicy/storage.go +++ b/apiserver/pkg/registry/projectcalico/networkpolicy/storage.go @@ -32,6 +32,7 @@ import ( "github.com/projectcalico/calico/apiserver/pkg/registry/projectcalico/authorizer" "github.com/projectcalico/calico/apiserver/pkg/registry/projectcalico/server" "github.com/projectcalico/calico/apiserver/pkg/registry/projectcalico/util" + "github.com/projectcalico/calico/libcalico-go/lib/names" ) // rest implements a RESTStorage for API services against etcd @@ -117,8 +118,7 @@ func (r *REST) List(ctx context.Context, options *metainternalversion.ListOption func (r *REST) Create(ctx context.Context, obj runtime.Object, val rest.ValidateObjectFunc, createOpt *metav1.CreateOptions) (runtime.Object, error) { policy := obj.(*calico.NetworkPolicy) - // Is Tier prepended. If not prepend default? - tierName, _ := util.GetTierFromPolicyName(policy.Name) + tierName := names.TierOrDefault(policy.Spec.Tier) err := r.authorizer.AuthorizeTierOperation(ctx, policy.Name, tierName) if err != nil { return nil, err @@ -129,8 +129,12 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, val rest.Validate func (r *REST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { - tierName, _ := util.GetTierFromPolicyName(name) - err := r.authorizer.AuthorizeTierOperation(ctx, name, tierName) + obj, err := r.Store.Get(ctx, name, &metav1.GetOptions{}) + if err != nil { + return nil, false, err + } + tierName := names.TierOrDefault(obj.(*calico.NetworkPolicy).Spec.Tier) + err = r.authorizer.AuthorizeTierOperation(ctx, name, tierName) if err != nil { return nil, false, err } @@ -140,18 +144,26 @@ func (r *REST) Update(ctx context.Context, name string, objInfo rest.UpdatedObje // Get retrieves the item from storage. func (r *REST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { - tierName, _ := util.GetTierFromPolicyName(name) - err := r.authorizer.AuthorizeTierOperation(ctx, name, tierName) + obj, err := r.Store.Get(ctx, name, options) + if err != nil { + return nil, err + } + tierName := names.TierOrDefault(obj.(*calico.NetworkPolicy).Spec.Tier) + err = r.authorizer.AuthorizeTierOperation(ctx, name, tierName) if err != nil { return nil, err } - return r.Store.Get(ctx, name, options) + return obj, nil } func (r *REST) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { - tierName, _ := util.GetTierFromPolicyName(name) - err := r.authorizer.AuthorizeTierOperation(ctx, name, tierName) + obj, err := r.Store.Get(ctx, name, &metav1.GetOptions{}) + if err != nil { + return nil, false, err + } + tierName := names.TierOrDefault(obj.(*calico.NetworkPolicy).Spec.Tier) + err = r.authorizer.AuthorizeTierOperation(ctx, name, tierName) if err != nil { return nil, false, err } diff --git a/apiserver/pkg/registry/projectcalico/rest/storage_calico.go b/apiserver/pkg/registry/projectcalico/rest/storage_calico.go index 44d075ee179..bb95fc79938 100644 --- a/apiserver/pkg/registry/projectcalico/rest/storage_calico.go +++ b/apiserver/pkg/registry/projectcalico/rest/storage_calico.go @@ -439,6 +439,28 @@ func (p RESTStorageProvider) NewV3Storage( []string{"kcconfig"}, ) + kubeControllersConfigsStatusRESTOptions, err := restOptionsGetter.GetRESTOptions(calico.Resource("kubecontrollersconfigurations/status"), nil) + if err != nil { + return nil, err + } + kubeControllersConfigsStatusOpts := server.NewOptions( + etcd.Options{ + RESTOptions: kubeControllersConfigsStatusRESTOptions, + Capacity: 1000, + ObjectType: calicokubecontrollersconfig.EmptyObject(), + ScopeStrategy: calicokubecontrollersconfig.NewStrategy(scheme), + NewListFunc: calicokubecontrollersconfig.NewList, + GetAttrsFunc: calicokubecontrollersconfig.GetAttrs, + Trigger: nil, + }, + calicostorage.Options{ + RESTOptions: kubeControllersConfigsStatusRESTOptions, + }, + p.StorageType, + authorizer, + []string{"kcconfig"}, + ) + clusterInformationRESTOptions, err := restOptionsGetter.GetRESTOptions(calico.Resource("clusterinformations"), nil) if err != nil { return nil, err @@ -549,7 +571,7 @@ func (p RESTStorageProvider) NewV3Storage( storage["ipamconfigurations"] = rESTInPeace(calicoipamconfig.NewREST(scheme, *ipamconfigOpts)) storage["blockaffinities"] = rESTInPeace(calicoblockaffinity.NewREST(scheme, *blockAffinityOpts)) - kubeControllersConfigsStorage, kubeControllersConfigsStatusStorage, err := calicokubecontrollersconfig.NewREST(scheme, *kubeControllersConfigsOpts) + kubeControllersConfigsStorage, kubeControllersConfigsStatusStorage, err := calicokubecontrollersconfig.NewREST(scheme, *kubeControllersConfigsOpts, *kubeControllersConfigsStatusOpts) if err != nil { err = fmt.Errorf("unable to create REST storage for a resource due to %v, will die", err) panic(err) diff --git a/apiserver/pkg/registry/projectcalico/stagedglobalnetworkpolicy/storage.go b/apiserver/pkg/registry/projectcalico/stagedglobalnetworkpolicy/storage.go index 325273ed406..865f0d7f9a4 100644 --- a/apiserver/pkg/registry/projectcalico/stagedglobalnetworkpolicy/storage.go +++ b/apiserver/pkg/registry/projectcalico/stagedglobalnetworkpolicy/storage.go @@ -32,6 +32,7 @@ import ( "github.com/projectcalico/calico/apiserver/pkg/registry/projectcalico/authorizer" "github.com/projectcalico/calico/apiserver/pkg/registry/projectcalico/server" "github.com/projectcalico/calico/apiserver/pkg/registry/projectcalico/util" + "github.com/projectcalico/calico/libcalico-go/lib/names" ) // rest implements a RESTStorage for API services against etcd @@ -118,8 +119,7 @@ func (r *REST) List(ctx context.Context, options *metainternalversion.ListOption func (r *REST) Create(ctx context.Context, obj runtime.Object, val rest.ValidateObjectFunc, createOpt *metav1.CreateOptions) (runtime.Object, error) { policy := obj.(*calico.StagedGlobalNetworkPolicy) - // Is Tier prepended. If not prepend default? - tierName, _ := util.GetTierFromPolicyName(policy.Name) + tierName := names.TierOrDefault(policy.Spec.Tier) err := r.authorizer.AuthorizeTierOperation(ctx, policy.Name, tierName) if err != nil { return nil, err @@ -130,8 +130,12 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, val rest.Validate func (r *REST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { - tierName, _ := util.GetTierFromPolicyName(name) - err := r.authorizer.AuthorizeTierOperation(ctx, name, tierName) + obj, err := r.Store.Get(ctx, name, &metav1.GetOptions{}) + if err != nil { + return nil, false, err + } + tierName := names.TierOrDefault(obj.(*calico.StagedGlobalNetworkPolicy).Spec.Tier) + err = r.authorizer.AuthorizeTierOperation(ctx, name, tierName) if err != nil { return nil, false, err } @@ -141,18 +145,26 @@ func (r *REST) Update(ctx context.Context, name string, objInfo rest.UpdatedObje // Get retrieves the item from storage. func (r *REST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { - tierName, _ := util.GetTierFromPolicyName(name) - err := r.authorizer.AuthorizeTierOperation(ctx, name, tierName) + obj, err := r.Store.Get(ctx, name, options) + if err != nil { + return nil, err + } + tierName := names.TierOrDefault(obj.(*calico.StagedGlobalNetworkPolicy).Spec.Tier) + err = r.authorizer.AuthorizeTierOperation(ctx, name, tierName) if err != nil { return nil, err } - return r.Store.Get(ctx, name, options) + return obj, nil } func (r *REST) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { - tierName, _ := util.GetTierFromPolicyName(name) - err := r.authorizer.AuthorizeTierOperation(ctx, name, tierName) + obj, err := r.Store.Get(ctx, name, &metav1.GetOptions{}) + if err != nil { + return nil, false, err + } + tierName := names.TierOrDefault(obj.(*calico.StagedGlobalNetworkPolicy).Spec.Tier) + err = r.authorizer.AuthorizeTierOperation(ctx, name, tierName) if err != nil { return nil, false, err } diff --git a/apiserver/pkg/registry/projectcalico/stagednetworkpolicy/storage.go b/apiserver/pkg/registry/projectcalico/stagednetworkpolicy/storage.go index 24cacda3b6b..4a183cb222f 100644 --- a/apiserver/pkg/registry/projectcalico/stagednetworkpolicy/storage.go +++ b/apiserver/pkg/registry/projectcalico/stagednetworkpolicy/storage.go @@ -32,6 +32,7 @@ import ( "github.com/projectcalico/calico/apiserver/pkg/registry/projectcalico/authorizer" "github.com/projectcalico/calico/apiserver/pkg/registry/projectcalico/server" "github.com/projectcalico/calico/apiserver/pkg/registry/projectcalico/util" + "github.com/projectcalico/calico/libcalico-go/lib/names" ) // rest implements a RESTStorage for API services against etcd @@ -114,8 +115,7 @@ func (r *REST) List(ctx context.Context, options *metainternalversion.ListOption func (r *REST) Create(ctx context.Context, obj runtime.Object, val rest.ValidateObjectFunc, createOpt *metav1.CreateOptions) (runtime.Object, error) { policy := obj.(*calico.StagedNetworkPolicy) - // Is Tier prepended. If not prepend default? - tierName, _ := util.GetTierFromPolicyName(policy.Name) + tierName := names.TierOrDefault(policy.Spec.Tier) err := r.authorizer.AuthorizeTierOperation(ctx, policy.Name, tierName) if err != nil { return nil, err @@ -126,8 +126,12 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, val rest.Validate func (r *REST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { - tierName, _ := util.GetTierFromPolicyName(name) - err := r.authorizer.AuthorizeTierOperation(ctx, name, tierName) + obj, err := r.Store.Get(ctx, name, &metav1.GetOptions{}) + if err != nil { + return nil, false, err + } + tierName := names.TierOrDefault(obj.(*calico.StagedNetworkPolicy).Spec.Tier) + err = r.authorizer.AuthorizeTierOperation(ctx, name, tierName) if err != nil { return nil, false, err } @@ -137,18 +141,26 @@ func (r *REST) Update(ctx context.Context, name string, objInfo rest.UpdatedObje // Get retrieves the item from storage. func (r *REST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { - tierName, _ := util.GetTierFromPolicyName(name) - err := r.authorizer.AuthorizeTierOperation(ctx, name, tierName) + obj, err := r.Store.Get(ctx, name, options) + if err != nil { + return nil, err + } + tierName := names.TierOrDefault(obj.(*calico.StagedNetworkPolicy).Spec.Tier) + err = r.authorizer.AuthorizeTierOperation(ctx, name, tierName) if err != nil { return nil, err } - return r.Store.Get(ctx, name, options) + return obj, nil } func (r *REST) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { - tierName, _ := util.GetTierFromPolicyName(name) - err := r.authorizer.AuthorizeTierOperation(ctx, name, tierName) + obj, err := r.Store.Get(ctx, name, &metav1.GetOptions{}) + if err != nil { + return nil, false, err + } + tierName := names.TierOrDefault(obj.(*calico.StagedNetworkPolicy).Spec.Tier) + err = r.authorizer.AuthorizeTierOperation(ctx, name, tierName) if err != nil { return nil, false, err } diff --git a/apiserver/pkg/registry/projectcalico/util/rbac.go b/apiserver/pkg/registry/projectcalico/util/rbac.go index 1422a73cfa4..8531b498aa9 100644 --- a/apiserver/pkg/registry/projectcalico/util/rbac.go +++ b/apiserver/pkg/registry/projectcalico/util/rbac.go @@ -5,7 +5,6 @@ package util import ( "context" "fmt" - "strings" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "k8s.io/apimachinery/pkg/api/errors" @@ -18,10 +17,6 @@ import ( "github.com/projectcalico/calico/apiserver/pkg/registry/projectcalico/authorizer" ) -const ( - policyDelim = "." -) - // EnsureTierSelector parses the given options and ensures the correct tier selector is set. // It first checks the input selector, if given. Otherwise, it uses a tier selector based on the user's permissions. func EnsureTierSelector(ctx context.Context, options *metainternalversion.ListOptions, authorizer authorizer.TierAuthorizer, calicoResourceLister rbac.CalicoResourceLister) error { @@ -142,12 +137,3 @@ func buildSelectorFromTiers(tiers []string) (*labels.Requirement, error) { return requirement, nil } - -// GetTierFromPolicyName extracts the Tier name from the policy name. -func GetTierFromPolicyName(policyName string) (string, string) { - policySlice := strings.Split(policyName, policyDelim) - if len(policySlice) < 2 { - return "default", policySlice[0] - } - return policySlice[0], policySlice[1] -} diff --git a/apiserver/pkg/registry/projectcalico/util/watch_manager.go b/apiserver/pkg/registry/projectcalico/util/watch_manager.go index d27c938c40d..9d6af5682de 100644 --- a/apiserver/pkg/registry/projectcalico/util/watch_manager.go +++ b/apiserver/pkg/registry/projectcalico/util/watch_manager.go @@ -70,7 +70,7 @@ func (m *WatchManager) OnUpdates(updates []api.Update) { // New Tier added, we need to stop all watches in case there is a user that can watch policies in the new Tier // When the watch is re-established it will contain policies in the newly created Tier logrus.WithField("Tier", u.Key.(model.ResourceKey).Name).Debug("New Tier added, removing all WatchRecords") - m.watchRecords.Range(func(k, v interface{}) bool { + m.watchRecords.Range(func(k, v any) bool { id, ok := k.(string) if !ok { logrus.WithField("id", k).Warn("ID is not a string") diff --git a/apiserver/pkg/storage/calico/bgpConfiguration_storage.go b/apiserver/pkg/storage/calico/bgpConfiguration_storage.go index 6ca1270b143..da00852f1b9 100644 --- a/apiserver/pkg/storage/calico/bgpConfiguration_storage.go +++ b/apiserver/pkg/storage/calico/bgpConfiguration_storage.go @@ -50,10 +50,10 @@ func NewBGPConfigurationStorage(opts Options) (registry.DryRunnableStorage, fact client: c, codec: opts.RESTOptions.StorageConfig.Codec, versioner: APIObjectVersioner{}, - aapiType: reflect.TypeOf(api.BGPConfiguration{}), - aapiListType: reflect.TypeOf(api.BGPConfigurationList{}), - libCalicoType: reflect.TypeOf(api.BGPConfiguration{}), - libCalicoListType: reflect.TypeOf(api.BGPConfigurationList{}), + aapiType: reflect.TypeFor[api.BGPConfiguration](), + aapiListType: reflect.TypeFor[api.BGPConfigurationList](), + libCalicoType: reflect.TypeFor[api.BGPConfiguration](), + libCalicoListType: reflect.TypeFor[api.BGPConfigurationList](), isNamespaced: false, create: createFn, update: updateFn, diff --git a/apiserver/pkg/storage/calico/bgpPeer_storage.go b/apiserver/pkg/storage/calico/bgpPeer_storage.go index a575980dca2..28522b84672 100644 --- a/apiserver/pkg/storage/calico/bgpPeer_storage.go +++ b/apiserver/pkg/storage/calico/bgpPeer_storage.go @@ -50,10 +50,10 @@ func NewBGPPeerStorage(opts Options) (registry.DryRunnableStorage, factory.Destr client: c, codec: opts.RESTOptions.StorageConfig.Codec, versioner: APIObjectVersioner{}, - aapiType: reflect.TypeOf(api.BGPPeer{}), - aapiListType: reflect.TypeOf(api.BGPPeerList{}), - libCalicoType: reflect.TypeOf(api.BGPPeer{}), - libCalicoListType: reflect.TypeOf(api.BGPPeerList{}), + aapiType: reflect.TypeFor[api.BGPPeer](), + aapiListType: reflect.TypeFor[api.BGPPeerList](), + libCalicoType: reflect.TypeFor[api.BGPPeer](), + libCalicoListType: reflect.TypeFor[api.BGPPeerList](), isNamespaced: false, create: createFn, update: updateFn, diff --git a/apiserver/pkg/storage/calico/bgpfilter_storage.go b/apiserver/pkg/storage/calico/bgpfilter_storage.go index 29ded2e0502..6f2122c1d66 100644 --- a/apiserver/pkg/storage/calico/bgpfilter_storage.go +++ b/apiserver/pkg/storage/calico/bgpfilter_storage.go @@ -51,10 +51,10 @@ func NewBGPFilterStorage(opts Options) (registry.DryRunnableStorage, factory.Des client: c, codec: opts.RESTOptions.StorageConfig.Codec, versioner: APIObjectVersioner{}, - aapiType: reflect.TypeOf(v3.BGPFilter{}), - aapiListType: reflect.TypeOf(v3.BGPFilterList{}), - libCalicoType: reflect.TypeOf(v3.BGPFilter{}), - libCalicoListType: reflect.TypeOf(v3.BGPFilterList{}), + aapiType: reflect.TypeFor[v3.BGPFilter](), + aapiListType: reflect.TypeFor[v3.BGPFilterList](), + libCalicoType: reflect.TypeFor[v3.BGPFilter](), + libCalicoListType: reflect.TypeFor[v3.BGPFilterList](), isNamespaced: false, create: createFn, update: updateFn, diff --git a/apiserver/pkg/storage/calico/blockAffinity_storage.go b/apiserver/pkg/storage/calico/blockAffinity_storage.go index 35310c49674..7d709fe32c8 100644 --- a/apiserver/pkg/storage/calico/blockAffinity_storage.go +++ b/apiserver/pkg/storage/calico/blockAffinity_storage.go @@ -26,7 +26,6 @@ import ( "k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage/storagebackend/factory" - libapi "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/options" "github.com/projectcalico/calico/libcalico-go/lib/watch" @@ -60,10 +59,10 @@ func NewBlockAffinityStorage(opts Options) (registry.DryRunnableStorage, factory client: c, codec: opts.RESTOptions.StorageConfig.Codec, versioner: APIObjectVersioner{}, - aapiType: reflect.TypeOf(api.BlockAffinity{}), - aapiListType: reflect.TypeOf(api.BlockAffinityList{}), - libCalicoType: reflect.TypeOf(libapi.BlockAffinity{}), - libCalicoListType: reflect.TypeOf(libapi.BlockAffinityList{}), + aapiType: reflect.TypeFor[api.BlockAffinity](), + aapiListType: reflect.TypeFor[api.BlockAffinityList](), + libCalicoType: reflect.TypeFor[api.BlockAffinity](), + libCalicoListType: reflect.TypeFor[api.BlockAffinityList](), isNamespaced: false, create: createFn, update: updateFn, @@ -77,25 +76,22 @@ func NewBlockAffinityStorage(opts Options) (registry.DryRunnableStorage, factory return dryRunnableStorage, func() {} } -type BlockAffinityConverter struct { -} +type BlockAffinityConverter struct{} func (gc BlockAffinityConverter) convertToLibcalico(aapiObj runtime.Object) resourceObject { - var lcgBlockAffinity *libapi.BlockAffinity + var lcgBlockAffinity *api.BlockAffinity // This is should not be called since block affinities are read-only through the AAPI. log.Error("Block affinity API is read-only. Should not attempt to create/update block affinities through the API.") return lcgBlockAffinity } func (gc BlockAffinityConverter) convertToAAPI(libcalicoObject resourceObject, aapiObj runtime.Object) { - lcgBlockAffinity := libcalicoObject.(*libapi.BlockAffinity) + lcgBlockAffinity := libcalicoObject.(*api.BlockAffinity) aapiBlockAffinity := aapiObj.(*api.BlockAffinity) aapiBlockAffinity.Spec.State = api.BlockAffinityState(lcgBlockAffinity.Spec.State) aapiBlockAffinity.Spec.Node = lcgBlockAffinity.Spec.Node aapiBlockAffinity.Spec.CIDR = lcgBlockAffinity.Spec.CIDR - if lcgBlockAffinity.Spec.Deleted == fmt.Sprintf("%t", true) { - aapiBlockAffinity.Spec.Deleted = true - } + aapiBlockAffinity.Spec.Deleted = lcgBlockAffinity.Spec.Deleted aapiBlockAffinity.TypeMeta = lcgBlockAffinity.TypeMeta aapiBlockAffinity.ObjectMeta = lcgBlockAffinity.ObjectMeta @@ -104,7 +100,7 @@ func (gc BlockAffinityConverter) convertToAAPI(libcalicoObject resourceObject, a } func (gc BlockAffinityConverter) convertToAAPIList(libcalicoListObject resourceListObject, aapiListObj runtime.Object, pred storage.SelectionPredicate) { - lcgBlockAffinityList := libcalicoListObject.(*libapi.BlockAffinityList) + lcgBlockAffinityList := libcalicoListObject.(*api.BlockAffinityList) aapiBlockAffinityList := aapiListObj.(*api.BlockAffinityList) if libcalicoListObject == nil { aapiBlockAffinityList.Items = []api.BlockAffinity{} diff --git a/apiserver/pkg/storage/calico/caliconodestatus_storage.go b/apiserver/pkg/storage/calico/caliconodestatus_storage.go index 5dbbc18c34d..f2be0552319 100644 --- a/apiserver/pkg/storage/calico/caliconodestatus_storage.go +++ b/apiserver/pkg/storage/calico/caliconodestatus_storage.go @@ -50,10 +50,10 @@ func NewCalicoNodeStatusStorage(opts Options) (registry.DryRunnableStorage, fact client: c, codec: opts.RESTOptions.StorageConfig.Codec, versioner: APIObjectVersioner{}, - aapiType: reflect.TypeOf(api.CalicoNodeStatus{}), - aapiListType: reflect.TypeOf(api.CalicoNodeStatusList{}), - libCalicoType: reflect.TypeOf(api.CalicoNodeStatus{}), - libCalicoListType: reflect.TypeOf(api.CalicoNodeStatusList{}), + aapiType: reflect.TypeFor[api.CalicoNodeStatus](), + aapiListType: reflect.TypeFor[api.CalicoNodeStatusList](), + libCalicoType: reflect.TypeFor[api.CalicoNodeStatus](), + libCalicoListType: reflect.TypeFor[api.CalicoNodeStatusList](), isNamespaced: false, create: createFn, update: updateFn, diff --git a/apiserver/pkg/storage/calico/clusterInformation_storage.go b/apiserver/pkg/storage/calico/clusterInformation_storage.go index 06f649774cc..ffabbb2c63c 100644 --- a/apiserver/pkg/storage/calico/clusterInformation_storage.go +++ b/apiserver/pkg/storage/calico/clusterInformation_storage.go @@ -36,10 +36,10 @@ func NewClusterInformationStorage(opts Options) (registry.DryRunnableStorage, fa client: c, codec: opts.RESTOptions.StorageConfig.Codec, versioner: APIObjectVersioner{}, - aapiType: reflect.TypeOf(api.ClusterInformation{}), - aapiListType: reflect.TypeOf(api.ClusterInformationList{}), - libCalicoType: reflect.TypeOf(api.ClusterInformation{}), - libCalicoListType: reflect.TypeOf(api.ClusterInformationList{}), + aapiType: reflect.TypeFor[api.ClusterInformation](), + aapiListType: reflect.TypeFor[api.ClusterInformationList](), + libCalicoType: reflect.TypeFor[api.ClusterInformation](), + libCalicoListType: reflect.TypeFor[api.ClusterInformationList](), isNamespaced: false, get: getFn, list: listFn, diff --git a/apiserver/pkg/storage/calico/converter.go b/apiserver/pkg/storage/calico/converter.go index b04a69efae8..e99ff899b61 100644 --- a/apiserver/pkg/storage/calico/converter.go +++ b/apiserver/pkg/storage/calico/converter.go @@ -22,7 +22,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/storage" - libapi "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" "github.com/projectcalico/calico/libcalico-go/lib/errors" ) @@ -119,13 +118,11 @@ func convertToAAPI(libcalicoObject runtime.Object) (res runtime.Object) { aapi := &v3.CalicoNodeStatus{} CalicoNodeStatusConverter{}.convertToAAPI(obj, aapi) return aapi - case *libapi.IPAMConfig: + case *v3.IPAMConfiguration: aapi := &v3.IPAMConfiguration{} IPAMConfigConverter{}.convertToAAPI(obj, aapi) return aapi - // BlockAffinity works off of the libapi objects since - // the v3 client is used for mostly internal operations. - case *libapi.BlockAffinity: + case *v3.BlockAffinity: aapi := &v3.BlockAffinity{} BlockAffinityConverter{}.convertToAAPI(obj, aapi) return aapi diff --git a/apiserver/pkg/storage/calico/felixConfig_storage.go b/apiserver/pkg/storage/calico/felixConfig_storage.go index 7eb927b43ba..de43f64aa09 100644 --- a/apiserver/pkg/storage/calico/felixConfig_storage.go +++ b/apiserver/pkg/storage/calico/felixConfig_storage.go @@ -50,10 +50,10 @@ func NewFelixConfigurationStorage(opts Options) (registry.DryRunnableStorage, fa client: c, codec: opts.RESTOptions.StorageConfig.Codec, versioner: APIObjectVersioner{}, - aapiType: reflect.TypeOf(api.FelixConfiguration{}), - aapiListType: reflect.TypeOf(api.FelixConfigurationList{}), - libCalicoType: reflect.TypeOf(api.FelixConfiguration{}), - libCalicoListType: reflect.TypeOf(api.FelixConfigurationList{}), + aapiType: reflect.TypeFor[api.FelixConfiguration](), + aapiListType: reflect.TypeFor[api.FelixConfigurationList](), + libCalicoType: reflect.TypeFor[api.FelixConfiguration](), + libCalicoListType: reflect.TypeFor[api.FelixConfigurationList](), isNamespaced: false, create: createFn, update: updateFn, diff --git a/apiserver/pkg/storage/calico/globalNetworkPolicy_storage.go b/apiserver/pkg/storage/calico/globalNetworkPolicy_storage.go index 8886f9b88c6..81b5b07c02b 100644 --- a/apiserver/pkg/storage/calico/globalNetworkPolicy_storage.go +++ b/apiserver/pkg/storage/calico/globalNetworkPolicy_storage.go @@ -51,10 +51,10 @@ func NewGlobalNetworkPolicyStorage(opts Options) (registry.DryRunnableStorage, f client: c, codec: opts.RESTOptions.StorageConfig.Codec, versioner: APIObjectVersioner{}, - aapiType: reflect.TypeOf(api.GlobalNetworkPolicy{}), - aapiListType: reflect.TypeOf(api.GlobalNetworkPolicyList{}), - libCalicoType: reflect.TypeOf(api.GlobalNetworkPolicy{}), - libCalicoListType: reflect.TypeOf(api.GlobalNetworkPolicyList{}), + aapiType: reflect.TypeFor[api.GlobalNetworkPolicy](), + aapiListType: reflect.TypeFor[api.GlobalNetworkPolicyList](), + libCalicoType: reflect.TypeFor[api.GlobalNetworkPolicy](), + libCalicoListType: reflect.TypeFor[api.GlobalNetworkPolicyList](), isNamespaced: false, create: createFn, update: updateFn, diff --git a/apiserver/pkg/storage/calico/globalNetworkSet_storage.go b/apiserver/pkg/storage/calico/globalNetworkSet_storage.go index 6980a154c61..f9f284bffa8 100644 --- a/apiserver/pkg/storage/calico/globalNetworkSet_storage.go +++ b/apiserver/pkg/storage/calico/globalNetworkSet_storage.go @@ -51,10 +51,10 @@ func NewGlobalNetworkSetStorage(opts Options) (registry.DryRunnableStorage, fact client: c, codec: opts.RESTOptions.StorageConfig.Codec, versioner: APIObjectVersioner{}, - aapiType: reflect.TypeOf(api.GlobalNetworkSet{}), - aapiListType: reflect.TypeOf(api.GlobalNetworkSetList{}), - libCalicoType: reflect.TypeOf(api.GlobalNetworkSet{}), - libCalicoListType: reflect.TypeOf(api.GlobalNetworkSetList{}), + aapiType: reflect.TypeFor[api.GlobalNetworkSet](), + aapiListType: reflect.TypeFor[api.GlobalNetworkSetList](), + libCalicoType: reflect.TypeFor[api.GlobalNetworkSet](), + libCalicoListType: reflect.TypeFor[api.GlobalNetworkSetList](), isNamespaced: false, create: createFn, update: updateFn, diff --git a/apiserver/pkg/storage/calico/hostEndpoint_storage.go b/apiserver/pkg/storage/calico/hostEndpoint_storage.go index f50b4232e8e..22f6f98310a 100644 --- a/apiserver/pkg/storage/calico/hostEndpoint_storage.go +++ b/apiserver/pkg/storage/calico/hostEndpoint_storage.go @@ -51,10 +51,10 @@ func NewHostEndpointStorage(opts Options) (registry.DryRunnableStorage, factory. client: c, codec: opts.RESTOptions.StorageConfig.Codec, versioner: APIObjectVersioner{}, - aapiType: reflect.TypeOf(api.HostEndpoint{}), - aapiListType: reflect.TypeOf(api.HostEndpointList{}), - libCalicoType: reflect.TypeOf(api.HostEndpoint{}), - libCalicoListType: reflect.TypeOf(api.HostEndpointList{}), + aapiType: reflect.TypeFor[api.HostEndpoint](), + aapiListType: reflect.TypeFor[api.HostEndpointList](), + libCalicoType: reflect.TypeFor[api.HostEndpoint](), + libCalicoListType: reflect.TypeFor[api.HostEndpointList](), isNamespaced: false, create: createFn, update: updateFn, diff --git a/apiserver/pkg/storage/calico/ipamconfig_storage.go b/apiserver/pkg/storage/calico/ipamconfig_storage.go index b95325231d5..58f4c07a7ca 100644 --- a/apiserver/pkg/storage/calico/ipamconfig_storage.go +++ b/apiserver/pkg/storage/calico/ipamconfig_storage.go @@ -4,7 +4,6 @@ package calico import ( "context" - "fmt" "reflect" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -13,7 +12,6 @@ import ( "k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage/storagebackend/factory" - libapi "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/options" "github.com/projectcalico/calico/libcalico-go/lib/watch" @@ -24,41 +22,38 @@ func NewIPAMConfigurationStorage(opts Options) (registry.DryRunnableStorage, fac c := CreateClientFromConfig() createFn := func(ctx context.Context, c clientv3.Interface, obj resourceObject, opts clientOpts) (resourceObject, error) { oso := opts.(options.SetOptions) - res := obj.(*libapi.IPAMConfig) - if res.Name != libapi.GlobalIPAMConfigName { - return nil, fmt.Errorf("IPAM config resource name has to be default") - } - return c.IPAMConfig().Create(ctx, res, oso) + res := obj.(*api.IPAMConfiguration) + return c.IPAMConfiguration().Create(ctx, res, oso) } updateFn := func(ctx context.Context, c clientv3.Interface, obj resourceObject, opts clientOpts) (resourceObject, error) { oso := opts.(options.SetOptions) - res := obj.(*libapi.IPAMConfig) - return c.IPAMConfig().Update(ctx, res, oso) + res := obj.(*api.IPAMConfiguration) + return c.IPAMConfiguration().Update(ctx, res, oso) } getFn := func(ctx context.Context, c clientv3.Interface, ns string, name string, opts clientOpts) (resourceObject, error) { ogo := opts.(options.GetOptions) - return c.IPAMConfig().Get(ctx, name, ogo) + return c.IPAMConfiguration().Get(ctx, name, ogo) } deleteFn := func(ctx context.Context, c clientv3.Interface, ns string, name string, opts clientOpts) (resourceObject, error) { odo := opts.(options.DeleteOptions) - return c.IPAMConfig().Delete(ctx, name, odo) + return c.IPAMConfiguration().Delete(ctx, name, odo) } listFn := func(ctx context.Context, c clientv3.Interface, opts clientOpts) (resourceListObject, error) { olo := opts.(options.ListOptions) - return c.IPAMConfig().List(ctx, olo) + return c.IPAMConfiguration().List(ctx, olo) } watchFn := func(ctx context.Context, c clientv3.Interface, opts clientOpts) (watch.Interface, error) { olo := opts.(options.ListOptions) - return c.IPAMConfig().Watch(ctx, olo) + return c.IPAMConfiguration().Watch(ctx, olo) } dryRunnableStorage := registry.DryRunnableStorage{Storage: &resourceStore{ client: c, codec: opts.RESTOptions.StorageConfig.Codec, versioner: APIObjectVersioner{}, - aapiType: reflect.TypeOf(api.IPAMConfiguration{}), - aapiListType: reflect.TypeOf(api.IPAMConfigurationList{}), - libCalicoType: reflect.TypeOf(libapi.IPAMConfig{}), - libCalicoListType: reflect.TypeOf(libapi.IPAMConfigList{}), + aapiType: reflect.TypeFor[api.IPAMConfiguration](), + aapiListType: reflect.TypeFor[api.IPAMConfigurationList](), + libCalicoType: reflect.TypeFor[api.IPAMConfiguration](), + libCalicoListType: reflect.TypeFor[api.IPAMConfigurationList](), isNamespaced: false, create: createFn, update: updateFn, @@ -75,14 +70,15 @@ func NewIPAMConfigurationStorage(opts Options) (registry.DryRunnableStorage, fac type IPAMConfigConverter struct{} func (gc IPAMConfigConverter) convertToLibcalico(aapiObj runtime.Object) resourceObject { + // The AAPI and the libcalico-go clientv3 both use the same struct for IPAMConfiguration. aapiIPAMConfig := aapiObj.(*api.IPAMConfiguration) - lcgIPAMConfig := &libapi.IPAMConfig{} + + lcgIPAMConfig := &api.IPAMConfiguration{} lcgIPAMConfig.TypeMeta = aapiIPAMConfig.TypeMeta lcgIPAMConfig.ObjectMeta = aapiIPAMConfig.ObjectMeta - lcgIPAMConfig.Kind = libapi.KindIPAMConfig + lcgIPAMConfig.Kind = api.KindIPAMConfiguration lcgIPAMConfig.APIVersion = api.GroupVersionCurrent - lcgIPAMConfig.Spec.StrictAffinity = aapiIPAMConfig.Spec.StrictAffinity - lcgIPAMConfig.Spec.MaxBlocksPerHost = int(aapiIPAMConfig.Spec.MaxBlocksPerHost) + lcgIPAMConfig.Spec = aapiIPAMConfig.Spec // AutoAllocateBlocks is an internal field and should be set to true. lcgIPAMConfig.Spec.AutoAllocateBlocks = true @@ -91,22 +87,18 @@ func (gc IPAMConfigConverter) convertToLibcalico(aapiObj runtime.Object) resourc } func (gc IPAMConfigConverter) convertToAAPI(libcalicoObject resourceObject, aapiObj runtime.Object) { - lcgIPAMConfig := libcalicoObject.(*libapi.IPAMConfig) + // The AAPI and the libcalico-go clientv3 both use the same struct for IPAMConfiguration. + lcgIPAMConfig := libcalicoObject.(*api.IPAMConfiguration) aapiIPAMConfig := aapiObj.(*api.IPAMConfiguration) // Copy spec but ignore internal field AutoAllocateBlocks. - aapiIPAMConfig.Spec.StrictAffinity = lcgIPAMConfig.Spec.StrictAffinity - aapiIPAMConfig.Spec.MaxBlocksPerHost = int32(lcgIPAMConfig.Spec.MaxBlocksPerHost) + aapiIPAMConfig.Spec = lcgIPAMConfig.Spec aapiIPAMConfig.TypeMeta = lcgIPAMConfig.TypeMeta aapiIPAMConfig.ObjectMeta = lcgIPAMConfig.ObjectMeta - - // libcalico uses a different Kind for these resources - IPAMConfig. - aapiIPAMConfig.APIVersion = api.GroupVersionCurrent - aapiIPAMConfig.Kind = api.KindIPAMConfiguration } func (gc IPAMConfigConverter) convertToAAPIList(libcalicoListObject resourceListObject, aapiListObj runtime.Object, pred storage.SelectionPredicate) { - lcgIPAMConfigList := libcalicoListObject.(*libapi.IPAMConfigList) + lcgIPAMConfigList := libcalicoListObject.(*api.IPAMConfigurationList) aapiIPAMConfigList := aapiListObj.(*api.IPAMConfigurationList) if libcalicoListObject == nil { aapiIPAMConfigList.Items = []api.IPAMConfiguration{} diff --git a/apiserver/pkg/storage/calico/ippool_storage.go b/apiserver/pkg/storage/calico/ippool_storage.go index 27d11fce4ae..38c9799a19f 100644 --- a/apiserver/pkg/storage/calico/ippool_storage.go +++ b/apiserver/pkg/storage/calico/ippool_storage.go @@ -50,10 +50,10 @@ func NewIPPoolStorage(opts Options) (registry.DryRunnableStorage, factory.Destro client: c, codec: opts.RESTOptions.StorageConfig.Codec, versioner: APIObjectVersioner{}, - aapiType: reflect.TypeOf(api.IPPool{}), - aapiListType: reflect.TypeOf(api.IPPoolList{}), - libCalicoType: reflect.TypeOf(api.IPPool{}), - libCalicoListType: reflect.TypeOf(api.IPPoolList{}), + aapiType: reflect.TypeFor[api.IPPool](), + aapiListType: reflect.TypeFor[api.IPPoolList](), + libCalicoType: reflect.TypeFor[api.IPPool](), + libCalicoListType: reflect.TypeFor[api.IPPoolList](), isNamespaced: false, create: createFn, update: updateFn, diff --git a/apiserver/pkg/storage/calico/ipreservation_storage.go b/apiserver/pkg/storage/calico/ipreservation_storage.go index 3a809deeab5..5bed633c3bc 100644 --- a/apiserver/pkg/storage/calico/ipreservation_storage.go +++ b/apiserver/pkg/storage/calico/ipreservation_storage.go @@ -50,10 +50,10 @@ func NewIPReservationStorage(opts Options) (registry.DryRunnableStorage, factory client: c, codec: opts.RESTOptions.StorageConfig.Codec, versioner: APIObjectVersioner{}, - aapiType: reflect.TypeOf(api.IPReservation{}), - aapiListType: reflect.TypeOf(api.IPReservationList{}), - libCalicoType: reflect.TypeOf(api.IPReservation{}), - libCalicoListType: reflect.TypeOf(api.IPReservationList{}), + aapiType: reflect.TypeFor[api.IPReservation](), + aapiListType: reflect.TypeFor[api.IPReservationList](), + libCalicoType: reflect.TypeFor[api.IPReservation](), + libCalicoListType: reflect.TypeFor[api.IPReservationList](), isNamespaced: false, create: createFn, update: updateFn, diff --git a/apiserver/pkg/storage/calico/kubeControllersConfig_storage.go b/apiserver/pkg/storage/calico/kubeControllersConfig_storage.go index 7c6ffaeeddb..f4f65e0d18c 100644 --- a/apiserver/pkg/storage/calico/kubeControllersConfig_storage.go +++ b/apiserver/pkg/storage/calico/kubeControllersConfig_storage.go @@ -20,6 +20,17 @@ import ( // NewKubeControllersConfigurationStorage creates a new libcalico-based storage.Interface implementation for KubeControllersConfigurations func NewKubeControllersConfigurationStorage(opts Options) (registry.DryRunnableStorage, factory.DestroyFunc) { c := CreateClientFromConfig() + return newKubeControllersConfigurationStorage(opts, c, false) +} + +// NewKubeControllersConfigurationStatusStorage creates a storage that uses the UpdateStatus +// method for writes, ensuring the status subresource is used when persisting changes. +func NewKubeControllersConfigurationStatusStorage(opts Options) (registry.DryRunnableStorage, factory.DestroyFunc) { + c := CreateClientFromConfig() + return newKubeControllersConfigurationStorage(opts, c, true) +} + +func newKubeControllersConfigurationStorage(opts Options, c clientv3.Interface, forStatus bool) (registry.DryRunnableStorage, factory.DestroyFunc) { createFn := func(ctx context.Context, c clientv3.Interface, obj resourceObject, opts clientOpts) (resourceObject, error) { oso := opts.(options.SetOptions) res := obj.(*api.KubeControllersConfiguration) @@ -28,6 +39,9 @@ func NewKubeControllersConfigurationStorage(opts Options) (registry.DryRunnableS updateFn := func(ctx context.Context, c clientv3.Interface, obj resourceObject, opts clientOpts) (resourceObject, error) { oso := opts.(options.SetOptions) res := obj.(*api.KubeControllersConfiguration) + if forStatus { + return c.KubeControllersConfiguration().UpdateStatus(ctx, res, oso) + } return c.KubeControllersConfiguration().Update(ctx, res, oso) } getFn := func(ctx context.Context, c clientv3.Interface, ns string, name string, opts clientOpts) (resourceObject, error) { @@ -51,10 +65,10 @@ func NewKubeControllersConfigurationStorage(opts Options) (registry.DryRunnableS client: c, codec: opts.RESTOptions.StorageConfig.Codec, versioner: APIObjectVersioner{}, - aapiType: reflect.TypeOf(api.KubeControllersConfiguration{}), - aapiListType: reflect.TypeOf(api.KubeControllersConfigurationList{}), - libCalicoType: reflect.TypeOf(api.KubeControllersConfiguration{}), - libCalicoListType: reflect.TypeOf(api.KubeControllersConfigurationList{}), + aapiType: reflect.TypeFor[api.KubeControllersConfiguration](), + aapiListType: reflect.TypeFor[api.KubeControllersConfigurationList](), + libCalicoType: reflect.TypeFor[api.KubeControllersConfiguration](), + libCalicoListType: reflect.TypeFor[api.KubeControllersConfigurationList](), isNamespaced: false, create: createFn, update: updateFn, diff --git a/apiserver/pkg/storage/calico/networkSet_storage.go b/apiserver/pkg/storage/calico/networkSet_storage.go index a97bd5f609b..5ab8b50700b 100644 --- a/apiserver/pkg/storage/calico/networkSet_storage.go +++ b/apiserver/pkg/storage/calico/networkSet_storage.go @@ -51,10 +51,10 @@ func NewNetworkSetStorage(opts Options) (registry.DryRunnableStorage, factory.De client: c, codec: opts.RESTOptions.StorageConfig.Codec, versioner: APIObjectVersioner{}, - aapiType: reflect.TypeOf(api.NetworkSet{}), - aapiListType: reflect.TypeOf(api.NetworkSetList{}), - libCalicoType: reflect.TypeOf(api.NetworkSet{}), - libCalicoListType: reflect.TypeOf(api.NetworkSetList{}), + aapiType: reflect.TypeFor[api.NetworkSet](), + aapiListType: reflect.TypeFor[api.NetworkSetList](), + libCalicoType: reflect.TypeFor[api.NetworkSet](), + libCalicoListType: reflect.TypeFor[api.NetworkSetList](), isNamespaced: true, create: createFn, update: updateFn, diff --git a/apiserver/pkg/storage/calico/policy_storage.go b/apiserver/pkg/storage/calico/policy_storage.go index 525528f5f8f..a39a2b6abb0 100644 --- a/apiserver/pkg/storage/calico/policy_storage.go +++ b/apiserver/pkg/storage/calico/policy_storage.go @@ -5,7 +5,6 @@ package calico import ( "context" "reflect" - "strings" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "k8s.io/apimachinery/pkg/runtime" @@ -14,8 +13,6 @@ import ( "k8s.io/apiserver/pkg/storage/storagebackend/factory" "github.com/projectcalico/calico/libcalico-go/lib/clientv3" - cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" - "github.com/projectcalico/calico/libcalico-go/lib/names" "github.com/projectcalico/calico/libcalico-go/lib/options" "github.com/projectcalico/calico/libcalico-go/lib/watch" ) @@ -26,25 +23,11 @@ func NewNetworkPolicyStorage(opts Options) (registry.DryRunnableStorage, factory createFn := func(ctx context.Context, c clientv3.Interface, obj resourceObject, opts clientOpts) (resourceObject, error) { oso := opts.(options.SetOptions) res := obj.(*v3.NetworkPolicy) - if strings.HasPrefix(res.Name, names.K8sNetworkPolicyNamePrefix) { - return nil, cerrors.ErrorOperationNotSupported{ - Operation: "create or apply", - Identifier: obj, - Reason: "kubernetes network policies must be managed through the kubernetes API", - } - } return c.NetworkPolicies().Create(ctx, res, oso) } updateFn := func(ctx context.Context, c clientv3.Interface, obj resourceObject, opts clientOpts) (resourceObject, error) { oso := opts.(options.SetOptions) res := obj.(*v3.NetworkPolicy) - if strings.HasPrefix(res.Name, names.K8sNetworkPolicyNamePrefix) { - return nil, cerrors.ErrorOperationNotSupported{ - Operation: "update or apply", - Identifier: obj, - Reason: "kubernetes network policies must be managed through the kubernetes API", - } - } return c.NetworkPolicies().Update(ctx, res, oso) } getFn := func(ctx context.Context, c clientv3.Interface, ns string, name string, opts clientOpts) (resourceObject, error) { @@ -53,13 +36,6 @@ func NewNetworkPolicyStorage(opts Options) (registry.DryRunnableStorage, factory } deleteFn := func(ctx context.Context, c clientv3.Interface, ns string, name string, opts clientOpts) (resourceObject, error) { odo := opts.(options.DeleteOptions) - if strings.HasPrefix(name, names.K8sNetworkPolicyNamePrefix) { - return nil, cerrors.ErrorOperationNotSupported{ - Operation: "delete", - Identifier: name, - Reason: "kubernetes network policies must be managed through the kubernetes API", - } - } return c.NetworkPolicies().Delete(ctx, ns, name, odo) } listFn := func(ctx context.Context, c clientv3.Interface, opts clientOpts) (resourceListObject, error) { @@ -75,10 +51,10 @@ func NewNetworkPolicyStorage(opts Options) (registry.DryRunnableStorage, factory client: c, codec: opts.RESTOptions.StorageConfig.Codec, versioner: APIObjectVersioner{&k8sStorage.APIObjectVersioner{}}, - aapiType: reflect.TypeOf(v3.NetworkPolicy{}), - aapiListType: reflect.TypeOf(v3.NetworkPolicyList{}), - libCalicoType: reflect.TypeOf(v3.NetworkPolicy{}), - libCalicoListType: reflect.TypeOf(v3.NetworkPolicyList{}), + aapiType: reflect.TypeFor[v3.NetworkPolicy](), + aapiListType: reflect.TypeFor[v3.NetworkPolicyList](), + libCalicoType: reflect.TypeFor[v3.NetworkPolicy](), + libCalicoListType: reflect.TypeFor[v3.NetworkPolicyList](), isNamespaced: true, create: createFn, update: updateFn, diff --git a/apiserver/pkg/storage/calico/policy_storage_test.go b/apiserver/pkg/storage/calico/policy_storage_test.go index cc770876f9b..42259b5fbd5 100644 --- a/apiserver/pkg/storage/calico/policy_storage_test.go +++ b/apiserver/pkg/storage/calico/policy_storage_test.go @@ -109,27 +109,6 @@ func TestNetworkPolicyCreateWithKeyExist(t *testing.T) { } } -func TestNetworkPolicyCreateDisallowK8sPrefix(t *testing.T) { - ctx, store, gnpStore := testSetup(t) - defer testCleanup(t, ctx, store, gnpStore) - name := "knp.default.foo" - ns := "default" - - key := fmt.Sprintf("projectcalico.org/networkpolicies/%s/%s", ns, name) - out := &v3.NetworkPolicy{} - obj := &v3.NetworkPolicy{ObjectMeta: metav1.ObjectMeta{Namespace: ns, Name: name}} - - libcPolicy, _ := store.client.NetworkPolicies().Get(ctx, ns, name, options.GetOptions{}) - if libcPolicy != nil { - t.Fatalf("expecting empty result on key: %s", key) - } - - err := store.Create(ctx, key, obj, out, 0) - if err == nil { - t.Fatalf("Expected Create of a policy with prefix 'knp.default.' to fail") - } -} - func TestNetworkPolicyGet(t *testing.T) { ctx, store, gnpStore := testSetup(t) defer testCleanup(t, ctx, store, gnpStore) @@ -250,20 +229,6 @@ func TestNetworkPolicyConditionalDelete(t *testing.T) { } } -func TestNetworkPolicyDeleteDisallowK8sPrefix(t *testing.T) { - ctx, store, gnpStore := testSetup(t) - defer testCleanup(t, ctx, store, gnpStore) - name := "knp.default.foo" - ns := "default" - - key := fmt.Sprintf("projectcalico.org/networkpolicies/%s/%s", ns, name) - out := &v3.NetworkPolicy{} - err := store.Delete(ctx, key, out, nil, nil, nil, storage.DeleteOptions{}) - if err == nil { - t.Fatalf("Expected deleting a k8s network policy to error") - } -} - func TestNetworkPolicyGetList(t *testing.T) { ctx, store, gnpStore := testSetup(t) defer testCleanup(t, ctx, store, gnpStore) diff --git a/apiserver/pkg/storage/calico/profile_storage.go b/apiserver/pkg/storage/calico/profile_storage.go index dfeedaf5ac3..8704bbfa47f 100644 --- a/apiserver/pkg/storage/calico/profile_storage.go +++ b/apiserver/pkg/storage/calico/profile_storage.go @@ -51,10 +51,10 @@ func NewProfileStorage(opts Options) (registry.DryRunnableStorage, factory.Destr client: c, codec: opts.RESTOptions.StorageConfig.Codec, versioner: APIObjectVersioner{}, - aapiType: reflect.TypeOf(api.Profile{}), - aapiListType: reflect.TypeOf(api.ProfileList{}), - libCalicoType: reflect.TypeOf(api.Profile{}), - libCalicoListType: reflect.TypeOf(api.ProfileList{}), + aapiType: reflect.TypeFor[api.Profile](), + aapiListType: reflect.TypeFor[api.ProfileList](), + libCalicoType: reflect.TypeFor[api.Profile](), + libCalicoListType: reflect.TypeFor[api.ProfileList](), isNamespaced: false, create: createFn, update: updateFn, diff --git a/apiserver/pkg/storage/calico/resource.go b/apiserver/pkg/storage/calico/resource.go index 8cd85a9852e..665af0f92de 100644 --- a/apiserver/pkg/storage/calico/resource.go +++ b/apiserver/pkg/storage/calico/resource.go @@ -44,7 +44,7 @@ type resourceConverter interface { convertToAAPI(resourceObject, runtime.Object) convertToAAPIList(resourceListObject, runtime.Object, storage.SelectionPredicate) } -type clientOpts interface{} +type clientOpts any type ( clientObjectOperator func(context.Context, clientv3.Interface, resourceObject, clientOpts) (resourceObject, error) @@ -90,6 +90,20 @@ func (rs *resourceStore) ReadinessCheck() error { return nil } +func (rs *resourceStore) Stats(_ context.Context) (storage.Stats, error) { + logrus.Error("STUB: Stats() not supported by Calico client.") + return storage.Stats{}, nil +} + +func (rs *resourceStore) SetKeysFunc(_ storage.KeysFunc) { + logrus.Error("STUB: SetKeysFunc() not supported by Calico client.") +} + +func (rs *resourceStore) CompactRevision() int64 { + logrus.Error("STUB: CompactRevision() not supported by Calico client.") + return 0 +} + var _ storage.Interface = (*resourceStore)(nil) func CreateClientFromConfig() clientv3.Interface { @@ -389,15 +403,23 @@ func decode( // } // }) func (rs *resourceStore) GuaranteedUpdate( - ctx context.Context, key string, out runtime.Object, ignoreNotFound bool, - preconditions *storage.Preconditions, userUpdate storage.UpdateFunc, cachedExistingObject runtime.Object, + ctx context.Context, + key string, + out runtime.Object, + ignoreNotFound bool, + preconditions *storage.Preconditions, + userUpdate storage.UpdateFunc, + cachedExistingObject runtime.Object, ) error { logrus.Tracef("GuaranteedUpdate called with key: %v on resource %v\n", key, rs.resourceName) + // If a cachedExistingObject was passed, use that as the initial object, otherwise use Get() to retrieve it var initObj runtime.Object if cachedExistingObject != nil { + logrus.Debugf("Using cached existing object for key: %v on resource %v\n", key, rs.resourceName) initObj = cachedExistingObject } else { + logrus.Debugf("Getting initial object for key: %v on resource %v\n", key, rs.resourceName) initObj = reflect.New(rs.aapiType).Interface().(runtime.Object) opts := storage.GetOptions{IgnoreNotFound: ignoreNotFound} if err := rs.Get(ctx, key, opts, initObj); err != nil { @@ -405,6 +427,7 @@ func (rs *resourceStore) GuaranteedUpdate( return aapiError(err, key) } } + // In either case, extract current state from the initial object curState, err := rs.getStateFromObject(initObj) if err != nil { @@ -427,6 +450,7 @@ func (rs *resourceStore) GuaranteedUpdate( logrus.Errorf("checking preconditions (%s)", err) return err } + // update the object by applying the userUpdate func & encode it updatedObj, ttl, err := userUpdate(curState.obj, *curState.meta) if err != nil { diff --git a/apiserver/pkg/storage/calico/stagedGlobalNetworkPolicy_storage.go b/apiserver/pkg/storage/calico/stagedGlobalNetworkPolicy_storage.go index c39f5e2ac23..055d2c13315 100644 --- a/apiserver/pkg/storage/calico/stagedGlobalNetworkPolicy_storage.go +++ b/apiserver/pkg/storage/calico/stagedGlobalNetworkPolicy_storage.go @@ -63,10 +63,10 @@ func NewStagedGlobalNetworkPolicyStorage(opts Options) (registry.DryRunnableStor client: c, codec: opts.RESTOptions.StorageConfig.Codec, versioner: APIObjectVersioner{}, - aapiType: reflect.TypeOf(v3.StagedGlobalNetworkPolicy{}), - aapiListType: reflect.TypeOf(v3.StagedGlobalNetworkPolicyList{}), - libCalicoType: reflect.TypeOf(v3.StagedGlobalNetworkPolicy{}), - libCalicoListType: reflect.TypeOf(v3.StagedGlobalNetworkPolicyList{}), + aapiType: reflect.TypeFor[v3.StagedGlobalNetworkPolicy](), + aapiListType: reflect.TypeFor[v3.StagedGlobalNetworkPolicyList](), + libCalicoType: reflect.TypeFor[v3.StagedGlobalNetworkPolicy](), + libCalicoListType: reflect.TypeFor[v3.StagedGlobalNetworkPolicyList](), isNamespaced: false, create: createFn, update: updateFn, diff --git a/apiserver/pkg/storage/calico/stagedKubernetesNetworkPolicy_storage.go b/apiserver/pkg/storage/calico/stagedKubernetesNetworkPolicy_storage.go index d65014ed608..40a048136d8 100644 --- a/apiserver/pkg/storage/calico/stagedKubernetesNetworkPolicy_storage.go +++ b/apiserver/pkg/storage/calico/stagedKubernetesNetworkPolicy_storage.go @@ -64,10 +64,10 @@ func NewStagedKubernetesNetworkPolicyStorage(opts Options) (registry.DryRunnable client: c, codec: opts.RESTOptions.StorageConfig.Codec, versioner: &APIObjectVersioner{}, - aapiType: reflect.TypeOf(v3.StagedKubernetesNetworkPolicy{}), - aapiListType: reflect.TypeOf(v3.StagedKubernetesNetworkPolicyList{}), - libCalicoType: reflect.TypeOf(v3.StagedKubernetesNetworkPolicy{}), - libCalicoListType: reflect.TypeOf(v3.StagedKubernetesNetworkPolicyList{}), + aapiType: reflect.TypeFor[v3.StagedKubernetesNetworkPolicy](), + aapiListType: reflect.TypeFor[v3.StagedKubernetesNetworkPolicyList](), + libCalicoType: reflect.TypeFor[v3.StagedKubernetesNetworkPolicy](), + libCalicoListType: reflect.TypeFor[v3.StagedKubernetesNetworkPolicyList](), isNamespaced: true, create: createFn, update: updateFn, diff --git a/apiserver/pkg/storage/calico/stagedNetworkPolicy_storage.go b/apiserver/pkg/storage/calico/stagedNetworkPolicy_storage.go index 500a2d4b608..cc7daefeace 100644 --- a/apiserver/pkg/storage/calico/stagedNetworkPolicy_storage.go +++ b/apiserver/pkg/storage/calico/stagedNetworkPolicy_storage.go @@ -17,7 +17,6 @@ package calico import ( "context" "reflect" - "strings" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "k8s.io/apimachinery/pkg/runtime" @@ -26,8 +25,6 @@ import ( "k8s.io/apiserver/pkg/storage/storagebackend/factory" "github.com/projectcalico/calico/libcalico-go/lib/clientv3" - cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" - "github.com/projectcalico/calico/libcalico-go/lib/names" "github.com/projectcalico/calico/libcalico-go/lib/options" "github.com/projectcalico/calico/libcalico-go/lib/watch" ) @@ -38,25 +35,11 @@ func NewStagedNetworkPolicyStorage(opts Options) (registry.DryRunnableStorage, f createFn := func(ctx context.Context, c clientv3.Interface, obj resourceObject, opts clientOpts) (resourceObject, error) { oso := opts.(options.SetOptions) res := obj.(*v3.StagedNetworkPolicy) - if strings.HasPrefix(res.Name, names.K8sNetworkPolicyNamePrefix) { - return nil, cerrors.ErrorOperationNotSupported{ - Operation: "create or apply", - Identifier: obj, - Reason: "staged kubernetes network policies must be managed through the staged kubernetes network policy API", - } - } return c.StagedNetworkPolicies().Create(ctx, res, oso) } updateFn := func(ctx context.Context, c clientv3.Interface, obj resourceObject, opts clientOpts) (resourceObject, error) { oso := opts.(options.SetOptions) res := obj.(*v3.StagedNetworkPolicy) - if strings.HasPrefix(res.Name, names.K8sNetworkPolicyNamePrefix) { - return nil, cerrors.ErrorOperationNotSupported{ - Operation: "update or apply", - Identifier: obj, - Reason: "staged kubernetes network policies must be managed through the staged kubernetes network policy API", - } - } return c.StagedNetworkPolicies().Update(ctx, res, oso) } getFn := func(ctx context.Context, c clientv3.Interface, ns string, name string, opts clientOpts) (resourceObject, error) { @@ -65,13 +48,6 @@ func NewStagedNetworkPolicyStorage(opts Options) (registry.DryRunnableStorage, f } deleteFn := func(ctx context.Context, c clientv3.Interface, ns string, name string, opts clientOpts) (resourceObject, error) { odo := opts.(options.DeleteOptions) - if strings.HasPrefix(name, names.K8sNetworkPolicyNamePrefix) { - return nil, cerrors.ErrorOperationNotSupported{ - Operation: "delete", - Identifier: name, - Reason: "staged kubernetes network policies must be managed through the staged kubernetes network policy API", - } - } return c.StagedNetworkPolicies().Delete(ctx, ns, name, odo) } listFn := func(ctx context.Context, c clientv3.Interface, opts clientOpts) (resourceListObject, error) { @@ -87,10 +63,10 @@ func NewStagedNetworkPolicyStorage(opts Options) (registry.DryRunnableStorage, f client: c, codec: opts.RESTOptions.StorageConfig.Codec, versioner: &APIObjectVersioner{}, - aapiType: reflect.TypeOf(v3.StagedNetworkPolicy{}), - aapiListType: reflect.TypeOf(v3.StagedNetworkPolicyList{}), - libCalicoType: reflect.TypeOf(v3.StagedNetworkPolicy{}), - libCalicoListType: reflect.TypeOf(v3.StagedNetworkPolicyList{}), + aapiType: reflect.TypeFor[v3.StagedNetworkPolicy](), + aapiListType: reflect.TypeFor[v3.StagedNetworkPolicyList](), + libCalicoType: reflect.TypeFor[v3.StagedNetworkPolicy](), + libCalicoListType: reflect.TypeFor[v3.StagedNetworkPolicyList](), isNamespaced: true, create: createFn, update: updateFn, @@ -104,8 +80,7 @@ func NewStagedNetworkPolicyStorage(opts Options) (registry.DryRunnableStorage, f return dryRunnableStorage, func() {} } -type StagedNetworkPolicyConverter struct { -} +type StagedNetworkPolicyConverter struct{} func (rc StagedNetworkPolicyConverter) convertToLibcalico(aapiObj runtime.Object) resourceObject { aapiPolicy := aapiObj.(*v3.StagedNetworkPolicy) diff --git a/apiserver/pkg/storage/calico/storage_interface.go b/apiserver/pkg/storage/calico/storage_interface.go index ea0406f6274..86cb20a18a0 100644 --- a/apiserver/pkg/storage/calico/storage_interface.go +++ b/apiserver/pkg/storage/calico/storage_interface.go @@ -59,6 +59,8 @@ func NewStorage(opts Options) (registry.DryRunnableStorage, factory.DestroyFunc) return NewFelixConfigurationStorage(opts) case "projectcalico.org/kubecontrollersconfigurations": return NewKubeControllersConfigurationStorage(opts) + case "projectcalico.org/kubecontrollersconfigurations/status": + return NewKubeControllersConfigurationStatusStorage(opts) case "projectcalico.org/clusterinformations": return NewClusterInformationStorage(opts) case "projectcalico.org/caliconodestatuses": diff --git a/apiserver/pkg/storage/calico/tier_storage.go b/apiserver/pkg/storage/calico/tier_storage.go index 8e152905a9a..733b4fb0624 100644 --- a/apiserver/pkg/storage/calico/tier_storage.go +++ b/apiserver/pkg/storage/calico/tier_storage.go @@ -52,10 +52,10 @@ func NewTierStorage(opts Options) (registry.DryRunnableStorage, factory.DestroyF client: c, codec: opts.RESTOptions.StorageConfig.Codec, versioner: APIObjectVersioner{}, - aapiType: reflect.TypeOf(v3.Tier{}), - aapiListType: reflect.TypeOf(v3.TierList{}), - libCalicoType: reflect.TypeOf(v3.Tier{}), - libCalicoListType: reflect.TypeOf(v3.TierList{}), + aapiType: reflect.TypeFor[v3.Tier](), + aapiListType: reflect.TypeFor[v3.TierList](), + libCalicoType: reflect.TypeFor[v3.Tier](), + libCalicoListType: reflect.TypeFor[v3.TierList](), isNamespaced: false, create: createFn, update: updateFn, diff --git a/apiserver/pkg/storage/calico/tier_storage_test.go b/apiserver/pkg/storage/calico/tier_storage_test.go index 6f3301f2c43..3abf6547b2c 100644 --- a/apiserver/pkg/storage/calico/tier_storage_test.go +++ b/apiserver/pkg/storage/calico/tier_storage_test.go @@ -522,20 +522,25 @@ func TestTierList(t *testing.T) { } opts := storage.GetOptions{IgnoreNotFound: false} + + tierPath := func(name string) string { + return fmt.Sprintf("projectcalico.org/tiers/%s", name) + } + defaultTier := makeTier(names.DefaultTierName, "", v3.DefaultTierOrder) - err := store.Get(ctx, "projectcalico.org/tiers/default", opts, defaultTier) + err := store.Get(ctx, tierPath(names.DefaultTierName), opts, defaultTier) if err != nil { t.Fatalf("Get failed: %v", err) } - anpTier := makeTier(names.AdminNetworkPolicyTierName, "", v3.AdminNetworkPolicyTierOrder) - err = store.Get(ctx, "projectcalico.org/tiers/adminnetworkpolicy", opts, anpTier) + kubeAdminTier := makeTier(names.KubeAdminTierName, "", v3.KubeAdminTierOrder) + err = store.Get(ctx, tierPath(names.KubeAdminTierName), opts, kubeAdminTier) if err != nil { t.Fatalf("Get failed: %v", err) } - banpTier := makeTier(names.BaselineAdminNetworkPolicyTierName, "", v3.BaselineAdminNetworkPolicyTierOrder) - err = store.Get(ctx, "projectcalico.org/tiers/baselineadminnetworkpolicy", opts, banpTier) + kubeBaselineTier := makeTier(names.KubeBaselineTierName, "", v3.KubeBaselineTierOrder) + err = store.Get(ctx, tierPath(names.KubeBaselineTierName), opts, kubeBaselineTier) if err != nil { t.Fatalf("Get failed: %v", err) } @@ -559,7 +564,7 @@ func TestTierList(t *testing.T) { }, }, // Tiers are returned in name order. - expectedOut: []*v3.Tier{anpTier, preset[1].storedObj, banpTier, defaultTier}, + expectedOut: []*v3.Tier{preset[1].storedObj, defaultTier, kubeAdminTier, kubeBaselineTier}, }} for i, tt := range tests { diff --git a/apiserver/test/integration/clientset_test.go b/apiserver/test/integration/clientset_test.go index 3c575e107a6..f81cc298fb3 100644 --- a/apiserver/test/integration/clientset_test.go +++ b/apiserver/test/integration/clientset_test.go @@ -31,12 +31,12 @@ import ( v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" calicoclient "github.com/projectcalico/api/pkg/client/clientset_generated/clientset" "github.com/projectcalico/api/pkg/lib/numorstring" + "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" libclient "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/options" ) @@ -45,7 +45,7 @@ import ( func TestGroupVersion(t *testing.T) { rootTestFunc := func() func(t *testing.T) { return func(t *testing.T) { - client, shutdownServer := getFreshApiserverAndClient(t, func() runtime.Object { + client, shutdownServer := getFreshAPIServerAndClient(t, func() runtime.Object { return &v3.NetworkPolicy{} }) defer shutdownServer() @@ -70,7 +70,7 @@ func testGroupVersion(client calicoclient.Interface) error { func TestEtcdHealthCheckerSuccess(t *testing.T) { serverConfig := NewTestServerConfig() - _, _, clientconfig, shutdownServer := withConfigGetFreshApiserverServerAndClient(t, serverConfig) + _, _, clientconfig, shutdownServer := withConfigGetFreshAPIServerServerAndClient(t, serverConfig) t.Log(clientconfig.Host) tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, @@ -80,7 +80,7 @@ func TestEtcdHealthCheckerSuccess(t *testing.T) { var resp *http.Response var err error retryInterval := 500 * time.Millisecond - for i := 0; i < 5; i++ { + for range 5 { resp, err = c.Get(clientconfig.Host + "/healthz") if err != nil || http.StatusOK != resp.StatusCode { success = false @@ -112,7 +112,7 @@ func TestEtcdHealthCheckerSuccess(t *testing.T) { func TestNoName(t *testing.T) { rootTestFunc := func() func(t *testing.T) { return func(t *testing.T) { - client, shutdownServer := getFreshApiserverAndClient(t, func() runtime.Object { + client, shutdownServer := getFreshAPIServerAndClient(t, func() runtime.Object { return &v3.NetworkPolicy{} }) defer shutdownServer() @@ -144,7 +144,7 @@ func TestNetworkPolicyClient(t *testing.T) { const name = "test-networkpolicy" rootTestFunc := func() func(t *testing.T) { return func(t *testing.T) { - client, shutdownServer := getFreshApiserverAndClient(t, func() runtime.Object { + client, shutdownServer := getFreshAPIServerAndClient(t, func() runtime.Object { return &v3.NetworkPolicy{} }) defer shutdownServer() @@ -315,14 +315,12 @@ func testNetworkPolicyClient(client calicoclient.Interface, name string) error { return fmt.Errorf("Error on watch") } var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { for e := range wIface.ResultChan() { fmt.Println("Watch object: ", e) break } - }() + }) err = policyClient.Delete(ctx, defaultTierPolicyName, metav1.DeleteOptions{}) if err != nil { @@ -343,7 +341,7 @@ func TestStagedNetworkPolicyClient(t *testing.T) { const name = "test-networkpolicy" rootTestFunc := func() func(t *testing.T) { return func(t *testing.T) { - client, shutdownServer := getFreshApiserverAndClient(t, func() runtime.Object { + client, shutdownServer := getFreshAPIServerAndClient(t, func() runtime.Object { return &v3.NetworkPolicy{} }) defer shutdownServer() @@ -512,14 +510,12 @@ func testStagedNetworkPolicyClient(client calicoclient.Interface, name string) e return fmt.Errorf("Error on watch") } var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { for e := range wIface.ResultChan() { fmt.Println("Watch object: ", e) break } - }() + }) err = policyClient.Delete(ctx, defaultTierPolicyName, metav1.DeleteOptions{}) if err != nil { @@ -540,7 +536,7 @@ func TestTierClient(t *testing.T) { const name = "test-tier" rootTestFunc := func() func(t *testing.T) { return func(t *testing.T) { - client, shutdownServer := getFreshApiserverAndClient(t, func() runtime.Object { + client, shutdownServer := getFreshAPIServerAndClient(t, func() runtime.Object { return &v3.Tier{} }) defer shutdownServer() @@ -610,7 +606,7 @@ func TestGlobalNetworkPolicyClient(t *testing.T) { const name = "test-globalnetworkpolicy" rootTestFunc := func() func(t *testing.T) { return func(t *testing.T) { - client, shutdownServer := getFreshApiserverAndClient(t, func() runtime.Object { + client, shutdownServer := getFreshAPIServerAndClient(t, func() runtime.Object { return &v3.GlobalNetworkPolicy{} }) defer shutdownServer() @@ -774,7 +770,7 @@ func TestStagedGlobalNetworkPolicyClient(t *testing.T) { const name = "test-stagedglobalnetworkpolicy" rootTestFunc := func() func(t *testing.T) { return func(t *testing.T) { - client, shutdownServer := getFreshApiserverAndClient(t, func() runtime.Object { + client, shutdownServer := getFreshAPIServerAndClient(t, func() runtime.Object { return &v3.StagedGlobalNetworkPolicy{} }) defer shutdownServer() @@ -940,7 +936,7 @@ func TestGlobalNetworkSetClient(t *testing.T) { const name = "test-globalnetworkset" rootTestFunc := func() func(t *testing.T) { return func(t *testing.T) { - client, shutdownServer := getFreshApiserverAndClient(t, func() runtime.Object { + client, shutdownServer := getFreshAPIServerAndClient(t, func() runtime.Object { return &v3.GlobalNetworkSet{} }) defer shutdownServer() @@ -1006,7 +1002,7 @@ func TestNetworkSetClient(t *testing.T) { const name = "test-networkset" rootTestFunc := func() func(t *testing.T) { return func(t *testing.T) { - client, shutdownServer := getFreshApiserverAndClient(t, func() runtime.Object { + client, shutdownServer := getFreshAPIServerAndClient(t, func() runtime.Object { return &v3.NetworkSet{} }) defer shutdownServer() @@ -1076,14 +1072,12 @@ func testNetworkSetClient(client calicoclient.Interface, name string) error { return fmt.Errorf("Error on watch") } var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { for e := range wIface.ResultChan() { fmt.Println("Watch object: ", e) break } - }() + }) err = networkSetClient.Delete(ctx, name, metav1.DeleteOptions{}) if err != nil { @@ -1099,7 +1093,7 @@ func TestIPReservationClient(t *testing.T) { const name = "test-ipreservation" rootTestFunc := func() func(t *testing.T) { return func(t *testing.T) { - client, shutdownServer := getFreshApiserverAndClient(t, func() runtime.Object { + client, shutdownServer := getFreshAPIServerAndClient(t, func() runtime.Object { return &v3.IPReservation{} }) defer shutdownServer() @@ -1166,7 +1160,7 @@ func testIPReservationClient(client calicoclient.Interface, name string) error { // TestHostEndpointClient exercises the HostEndpoint client. func TestHostEndpointClient(t *testing.T) { const name = "test-hostendpoint" - client, shutdownServer := getFreshApiserverAndClient(t, func() runtime.Object { + client, shutdownServer := getFreshAPIServerAndClient(t, func() runtime.Object { return &v3.HostEndpoint{} }) defer shutdownServer() @@ -1175,7 +1169,7 @@ func TestHostEndpointClient(t *testing.T) { }() rootTestFunc := func() func(t *testing.T) { return func(t *testing.T) { - client, shutdownServer := getFreshApiserverAndClient(t, func() runtime.Object { + client, shutdownServer := getFreshAPIServerAndClient(t, func() runtime.Object { return &v3.HostEndpoint{} }) defer shutdownServer() @@ -1265,7 +1259,7 @@ func testHostEndpointClient(client calicoclient.Interface, name string) error { // watch for 2 events go func() { defer done.Done() - for i := 0; i < 2; i++ { + for range 2 { select { case e := <-w.ResultChan(): events = append(events, e) @@ -1277,7 +1271,7 @@ func testHostEndpointClient(client calicoclient.Interface, name string) error { }() // Create two HostEndpoints - for i := 0; i < 2; i++ { + for i := range 2 { hep := createTestHostEndpoint(fmt.Sprintf("hep%d", i), "192.168.0.1", "test-node") _, err = hostEndpointClient.Create(ctx, hep, metav1.CreateOptions{}) if err != nil { @@ -1301,7 +1295,7 @@ func TestIPPoolClient(t *testing.T) { const name = "test-ippool" rootTestFunc := func() func(t *testing.T) { return func(t *testing.T) { - client, shutdownServer := getFreshApiserverAndClient(t, func() runtime.Object { + client, shutdownServer := getFreshAPIServerAndClient(t, func() runtime.Object { return &v3.IPPool{} }) defer shutdownServer() @@ -1370,7 +1364,7 @@ func TestBGPConfigurationClient(t *testing.T) { const name = "test-bgpconfig" rootTestFunc := func() func(t *testing.T) { return func(t *testing.T) { - client, shutdownServer := getFreshApiserverAndClient(t, func() runtime.Object { + client, shutdownServer := getFreshAPIServerAndClient(t, func() runtime.Object { return &v3.BGPConfiguration{} }) defer shutdownServer() @@ -1431,7 +1425,7 @@ func TestBGPPeerClient(t *testing.T) { const name = "test-bgppeer" rootTestFunc := func() func(t *testing.T) { return func(t *testing.T) { - client, shutdownServer := getFreshApiserverAndClient(t, func() runtime.Object { + client, shutdownServer := getFreshAPIServerAndClient(t, func() runtime.Object { return &v3.BGPPeer{} }) defer shutdownServer() @@ -1496,7 +1490,7 @@ func TestProfileClient(t *testing.T) { const name = "kns.namespace-1" rootTestFunc := func() func(t *testing.T) { return func(t *testing.T) { - client, shutdownServer := getFreshApiserverAndClient(t, func() runtime.Object { + client, shutdownServer := getFreshAPIServerAndClient(t, func() runtime.Object { return &v3.Profile{} }) defer shutdownServer() @@ -1561,7 +1555,7 @@ func TestFelixConfigurationClient(t *testing.T) { const name = "test-felixconfig" rootTestFunc := func() func(t *testing.T) { return func(t *testing.T) { - client, shutdownServer := getFreshApiserverAndClient(t, func() runtime.Object { + client, shutdownServer := getFreshAPIServerAndClient(t, func() runtime.Object { return &v3.FelixConfiguration{} }) defer shutdownServer() @@ -1634,7 +1628,7 @@ func TestKubeControllersConfigurationClient(t *testing.T) { const name = "test-kubecontrollersconfig" rootTestFunc := func() func(t *testing.T) { return func(t *testing.T) { - client, shutdownServer := getFreshApiserverAndClient(t, func() runtime.Object { + client, shutdownServer := getFreshAPIServerAndClient(t, func() runtime.Object { return &v3.KubeControllersConfiguration{} }) defer shutdownServer() @@ -1654,7 +1648,7 @@ func testKubeControllersConfigurationClient(client calicoclient.Interface) error kubeControllersConfig := &v3.KubeControllersConfiguration{ ObjectMeta: metav1.ObjectMeta{Name: "default"}, Status: v3.KubeControllersConfigurationStatus{ - RunningConfig: v3.KubeControllersConfigurationSpec{ + RunningConfig: &v3.KubeControllersConfigurationSpec{ Controllers: v3.ControllersConfig{ Node: &v3.NodeControllerConfig{ SyncLabels: v3.Enabled, @@ -1752,7 +1746,7 @@ func testKubeControllersConfigurationClient(client calicoclient.Interface) error // watch for 2 events go func() { defer done.Done() - for i := 0; i < 2; i++ { + for range 2 { select { case e := <-w.ResultChan(): events = append(events, e) @@ -1789,7 +1783,7 @@ func TestClusterInformationClient(t *testing.T) { const name = "default" rootTestFunc := func() func(t *testing.T) { return func(t *testing.T) { - client, shutdownServer := getFreshApiserverAndClient(t, func() runtime.Object { + client, shutdownServer := getFreshAPIServerAndClient(t, func() runtime.Object { return &v3.ClusterInformation{} }) defer shutdownServer() @@ -1846,7 +1840,7 @@ func TestCalicoNodeStatusClient(t *testing.T) { const name = "test-caliconodestatus" rootTestFunc := func() func(t *testing.T) { return func(t *testing.T) { - client, shutdownServer := getFreshApiserverAndClient(t, func() runtime.Object { + client, shutdownServer := getFreshAPIServerAndClient(t, func() runtime.Object { return &v3.CalicoNodeStatus{} }) defer shutdownServer() @@ -1911,25 +1905,27 @@ func testCalicoNodeStatusClient(client calicoclient.Interface, name string) erro // TestIPAMConfigClient exercises the IPAMConfig client. func TestIPAMConfigClient(t *testing.T) { - const name = "test-ipamconfig" rootTestFunc := func() func(t *testing.T) { return func(t *testing.T) { - client, shutdownServer := getFreshApiserverAndClient(t, func() runtime.Object { + client, shutdownServer := getFreshAPIServerAndClient(t, func() runtime.Object { return &v3.IPAMConfiguration{} }) defer shutdownServer() - if err := testIPAMConfigClient(client, name); err != nil { + if err := testIPAMConfigClient(client); err != nil { t.Fatal(err) } } } - if !t.Run(name, rootTestFunc()) { + if !t.Run("test-ipamconfig", rootTestFunc()) { t.Errorf("test-ipamconfig test failed") } } -func testIPAMConfigClient(client calicoclient.Interface, name string) error { +func testIPAMConfigClient(client calicoclient.Interface) error { + logrus.SetLevel(logrus.DebugLevel) + name := "default" + ipamConfigClient := client.ProjectcalicoV3().IPAMConfigurations() ipamConfig := &v3.IPAMConfiguration{ ObjectMeta: metav1.ObjectMeta{Name: name}, @@ -1939,19 +1935,21 @@ func testIPAMConfigClient(client calicoclient.Interface, name string) error { MaxBlocksPerHost: 28, }, } - ctx := context.Background() + ctx := context.Background() _, err := ipamConfigClient.List(ctx, metav1.ListOptions{}) if err != nil { return fmt.Errorf("error listing IPAMConfigurations: %s", err) } - _, err = ipamConfigClient.Create(ctx, ipamConfig, metav1.CreateOptions{}) + // Should not be able to create a non-default IPAM config. + badConfig := ipamConfig.DeepCopy() + badConfig.Name = "not-default" + _, err = ipamConfigClient.Create(ctx, badConfig, metav1.CreateOptions{}) if err == nil { - return fmt.Errorf("should not be able to create ipam config %s ", ipamConfig.Name) + return fmt.Errorf("should not be able to create ipam config %s ", badConfig.Name) } - ipamConfig.Name = "default" ipamConfigNew, err := ipamConfigClient.Create(ctx, ipamConfig, metav1.CreateOptions{}) if err != nil { return fmt.Errorf("error creating the object '%v' (%v)", ipamConfig, err) @@ -2000,7 +1998,7 @@ func TestBlockAffinityClient(t *testing.T) { const name = "test-blockaffinity" rootTestFunc := func() func(t *testing.T) { return func(t *testing.T) { - client, shutdownServer := getFreshApiserverAndClient(t, func() runtime.Object { + client, shutdownServer := getFreshAPIServerAndClient(t, func() runtime.Object { return &v3.BlockAffinity{} }) defer shutdownServer() @@ -2016,7 +2014,7 @@ func TestBlockAffinityClient(t *testing.T) { } func testBlockAffinityClient(client calicoclient.Interface, name string) error { - blockAffinityClient := client.ProjectcalicoV3().BlockAffinities() + v3client := client.ProjectcalicoV3().BlockAffinities() blockAffinity := &v3.BlockAffinity{ ObjectMeta: metav1.ObjectMeta{Name: name}, @@ -2026,22 +2024,19 @@ func testBlockAffinityClient(client calicoclient.Interface, name string) error { State: "pending", }, } - libV3BlockAffinity := &libapiv3.BlockAffinity{ + v3BlockAff := &v3.BlockAffinity{ ObjectMeta: metav1.ObjectMeta{Name: name}, - Spec: libapiv3.BlockAffinitySpec{ + Spec: v3.BlockAffinitySpec{ CIDR: "10.0.0.0/24", Node: "node1", State: "pending", - Deleted: "false", + Deleted: false, }, } ctx := context.Background() // Calico libv3 client instantiation in order to get around the API create restrictions - // TODO: Currently these tests only run on a Kubernetes datastore since profile creation - // does not work in etcd. Figure out how to divide this configuration to etcd once that - // is fixed. config := apiconfig.NewCalicoAPIConfig() config.Spec = apiconfig.CalicoAPIConfigSpec{ DatastoreType: apiconfig.Kubernetes, @@ -2049,57 +2044,61 @@ func testBlockAffinityClient(client calicoclient.Interface, name string) error { EtcdEndpoints: "http://localhost:2379", }, KubeConfig: apiconfig.KubeConfig{ - Kubeconfig: os.Getenv("KUBECONFIG"), + Kubeconfig: os.Getenv("KUBECONFIG"), + CalicoAPIGroup: os.Getenv("CALICO_API_GROUP"), }, } - apiClient, err := libclient.New(*config) + libcalicoClient, err := libclient.New(*config) if err != nil { return fmt.Errorf("unable to create Calico lib v3 client: %s", err) } - _, err = blockAffinityClient.Create(ctx, blockAffinity, metav1.CreateOptions{}) + _, err = v3client.Create(ctx, blockAffinity, metav1.CreateOptions{}) if err == nil { return fmt.Errorf("should not be able to create block affinity %s ", blockAffinity.Name) } // Create the block affinity using the libv3 client. - _, err = apiClient.BlockAffinities().Create(ctx, libV3BlockAffinity, options.SetOptions{}) + _, err = libcalicoClient.BlockAffinities().Create(ctx, v3BlockAff, options.SetOptions{}) if err != nil { - return fmt.Errorf("error creating the object through the Calico v3 API '%v' (%v)", libV3BlockAffinity, err) + return fmt.Errorf("error creating the object through libcalico API '%v' (%v)", v3BlockAff, err) } - blockAffinityNew, err := blockAffinityClient.Get(ctx, name, metav1.GetOptions{}) + blockAffinityNew, err := v3client.Get(ctx, name, metav1.GetOptions{}) if err != nil { return fmt.Errorf("error getting object %s (%s)", name, err) } - blockAffinityList, err := blockAffinityClient.List(ctx, metav1.ListOptions{}) + blockAffinityList, err := v3client.List(ctx, metav1.ListOptions{}) if err != nil { return fmt.Errorf("error listing BlockAffinity (%s)", err) } if blockAffinityList.Items == nil { return fmt.Errorf("items field should not be set to nil") } + if len(blockAffinityList.Items) != 1 { + return fmt.Errorf("expected 1 block affinity got %d", len(blockAffinityList.Items)) + } blockAffinityNew.Spec.State = "confirmed" - _, err = blockAffinityClient.Update(ctx, blockAffinityNew, metav1.UpdateOptions{}) + _, err = v3client.Update(ctx, blockAffinityNew, metav1.UpdateOptions{}) if err == nil { return fmt.Errorf("should not be able to update block affinity %s", blockAffinityNew.Name) } - err = blockAffinityClient.Delete(ctx, name, metav1.DeleteOptions{}) - if nil == err { + err = v3client.Delete(ctx, name, metav1.DeleteOptions{}) + if err == nil { return fmt.Errorf("should not be able to delete block affinity %s", blockAffinity.Name) } // Test watch - w, err := blockAffinityClient.Watch(ctx, metav1.ListOptions{}) + w, err := v3client.Watch(ctx, metav1.ListOptions{}) if err != nil { return fmt.Errorf("error watching block affinities (%s)", err) } - _, err = apiClient.BlockAffinities().Delete(ctx, name, options.DeleteOptions{ResourceVersion: blockAffinityNew.ResourceVersion}) + _, err = libcalicoClient.BlockAffinities().Delete(ctx, name, options.DeleteOptions{ResourceVersion: blockAffinityNew.ResourceVersion}) if err != nil { return fmt.Errorf("error deleting the object through the Calico v3 API '%v' (%v)", name, err) } @@ -2107,25 +2106,20 @@ func testBlockAffinityClient(client calicoclient.Interface, name string) error { // Verify watch var events []watch.Event timeout := time.After(500 * time.Millisecond) - var timeoutErr error + // watch for 2 events -loop: for range 2 { select { case e := <-w.ResultChan(): events = append(events, e) case <-timeout: - timeoutErr = fmt.Errorf("timed out waiting for events") - break loop + return fmt.Errorf("timed out waiting for events") } } - if timeoutErr != nil { - return timeoutErr - } + if len(events) != 2 { return fmt.Errorf("expected 2 watch events got %d", len(events)) } - return nil } @@ -2134,7 +2128,7 @@ func TestBGPFilterClient(t *testing.T) { const name = "test-bgpfilter" rootTestFunc := func() func(t *testing.T) { return func(t *testing.T) { - client, shutdownServer := getFreshApiserverAndClient(t, func() runtime.Object { + client, shutdownServer := getFreshAPIServerAndClient(t, func() runtime.Object { return &v3.BGPFilter{} }) defer shutdownServer() @@ -2265,7 +2259,7 @@ func testBGPFilterClient(client calicoclient.Interface, name string) error { return fmt.Errorf("didn't get the correct object back from the server \n%+v\n%+v", bgpFilter, bgpFilterNew) } - for i := 0; i < size; i++ { + for i := range size { if bgpFilterNew.Spec.ExportV4[i] != bgpFilter.Spec.ExportV4[i] { return fmt.Errorf("didn't get the correct object back from the server. Incorrect ExportV4: \n%+v\n%+v", bgpFilter.Spec.ExportV4, bgpFilterNew.Spec.ExportV4) @@ -2318,7 +2312,7 @@ func TestPolicyWatch(t *testing.T) { const name = "test-policywatch" rootTestFunc := func() func(t *testing.T) { return func(t *testing.T) { - client, shutdownServer := getFreshApiserverAndClient(t, func() runtime.Object { + client, shutdownServer := getFreshAPIServerAndClient(t, func() runtime.Object { return &v3.GlobalNetworkPolicy{} }) defer shutdownServer() diff --git a/apiserver/test/integration/framework.go b/apiserver/test/integration/framework.go index 4ca388b99f8..d89d313cea5 100644 --- a/apiserver/test/integration/framework.go +++ b/apiserver/test/integration/framework.go @@ -50,7 +50,7 @@ func NewTestServerConfig() *TestServerConfig { } } -func withConfigGetFreshApiserverServerAndClient( +func withConfigGetFreshAPIServerServerAndClient( t *testing.T, serverConfig *TestServerConfig, ) (*apiserver.ProjectCalicoServer, @@ -94,7 +94,7 @@ func withConfigGetFreshApiserverServerAndClient( } }() - if err := waitForApiserverUp(secureAddr, serverFailed); err != nil { + if err := waitForAPIServerUp(secureAddr, serverFailed); err != nil { t.Fatalf("%v", err) } if pcs == nil { @@ -112,19 +112,16 @@ func withConfigGetFreshApiserverServerAndClient( return pcs, clientset, cfg, shutdownServer } -func getFreshApiserverAndClient( - t *testing.T, - newEmptyObj func() runtime.Object, -) (calicoclient.Interface, func()) { +func getFreshAPIServerAndClient(t *testing.T, newEmptyObj func() runtime.Object) (calicoclient.Interface, func()) { serverConfig := &TestServerConfig{ etcdServerList: []string{"http://localhost:2379"}, emptyObjFunc: newEmptyObj, } - _, client, _, shutdownFunc := withConfigGetFreshApiserverServerAndClient(t, serverConfig) + _, client, _, shutdownFunc := withConfigGetFreshAPIServerServerAndClient(t, serverConfig) return client, shutdownFunc } -func waitForApiserverUp(serverURL string, stopCh <-chan struct{}) error { +func waitForAPIServerUp(serverURL string, stopCh <-chan struct{}) error { interval := 1 * time.Second timeout := 30 * time.Second startWaiting := time.Now() diff --git a/app-policy/Dockerfile b/app-policy/Dockerfile index 81e9bb02562..c73a82f1881 100644 --- a/app-policy/Dockerfile +++ b/app-policy/Dockerfile @@ -13,8 +13,9 @@ # limitations under the License. ARG CALICO_BASE +ARG UBI_IMAGE -FROM alpine:3 AS builder +FROM ${UBI_IMAGE} AS builder RUN mkdir -p /var/run/dikastes @@ -47,6 +48,15 @@ LABEL org.opencontainers.image.vendor="Project Calico" LABEL org.opencontainers.image.version="${GIT_VERSION}" LABEL org.opencontainers.image.licenses="Apache-2.0" +# Labels for RedHat OCP certification +LABEL description="Calico Dikastes enables Application Layer Policy" +LABEL maintainer="maintainers@tigera.io" +LABEL name="Calico Dikastes" +LABEL release=1 +LABEL summary="Calico Dikastes enables Application Layer Policy" +LABEL vendor="Project Calico" +LABEL version="${GIT_VERSION}" + COPY --from=source / / USER 999 diff --git a/app-policy/Makefile b/app-policy/Makefile index debe52184a2..07e4e9017ea 100644 --- a/app-policy/Makefile +++ b/app-policy/Makefile @@ -145,8 +145,7 @@ bin/LICENSE: ../LICENSE.md .PHONY: ut ## Run the tests in a container. Useful for CI, Mac dev ut: protobuf - mkdir -p report - $(DOCKER_RUN) $(CALICO_BUILD) /bin/bash -c "go test -v $(GINKGO_ARGS) ./... | go-junit-report > ./report/tests.xml" + $(DOCKER_RUN) $(CALICO_BUILD) /bin/bash -c "cd /go/src/$(PACKAGE_NAME) && go test -v ./..." ############################################################################### # CI diff --git a/app-policy/checker/check.go b/app-policy/checker/check.go index cf5cbac395f..4691b8a8e63 100644 --- a/app-policy/checker/check.go +++ b/app-policy/checker/check.go @@ -30,7 +30,6 @@ import ( "github.com/projectcalico/calico/felix/rules" ftypes "github.com/projectcalico/calico/felix/types" "github.com/projectcalico/calico/libcalico-go/lib/logutils" - "github.com/projectcalico/calico/libcalico-go/lib/names" ) var ( @@ -132,29 +131,27 @@ func checkTiers(store *policystore.PolicyStore, ep *proto.WorkloadEndpoint, dir action := NO_MATCH Policy: - for i, name := range policies { - pID := proto.PolicyID{Tier: tier.GetName(), Name: name} - policy := store.PolicyByID[ftypes.ProtoToPolicyID(&pID)] + for i, pID := range policies { + policy := store.PolicyByID[ftypes.ProtoToPolicyID(pID)] action, ruleIndex = checkPolicy(policy, dir, request) - log.Debugf("Policy checked (ordinal=%d, profileId=%v, action=%v)", i, &pID, action) - policyName := getPolicyName(name) + log.Debugf("Policy checked (ordinal=%d, Id=%+v, action=%v)", i, pID, action) switch action { case NO_MATCH: if tierDefaultActionRuleID == nil { - tierDefaultActionRuleID = calc.NewRuleID(tier.GetName(), policyName, policy.GetNamespace(), tierDefaultActionIndex, dir, ruleActionFromStr(tier.DefaultAction)) + tierDefaultActionRuleID = calc.NewRuleID(pID.Kind, tier.GetName(), pID.Name, pID.Namespace, tierDefaultActionIndex, dir, ruleActionFromStr(tier.DefaultAction)) } continue Policy // If the Policy matches, end evaluation (skipping profiles, if any) case ALLOW: s.Code = OK - trace = append(trace, calc.NewRuleID(tier.GetName(), policyName, policy.GetNamespace(), ruleIndex, dir, rules.RuleActionAllow)) + trace = append(trace, calc.NewRuleID(pID.Kind, tier.GetName(), pID.Name, pID.Namespace, ruleIndex, dir, rules.RuleActionAllow)) return case DENY: s.Code = PERMISSION_DENIED - trace = append(trace, calc.NewRuleID(tier.GetName(), policyName, policy.GetNamespace(), ruleIndex, dir, rules.RuleActionDeny)) + trace = append(trace, calc.NewRuleID(pID.Kind, tier.GetName(), pID.Name, pID.Namespace, ruleIndex, dir, rules.RuleActionDeny)) return case PASS: - trace = append(trace, calc.NewRuleID(tier.GetName(), policyName, policy.GetNamespace(), ruleIndex, dir, rules.RuleActionPass)) + trace = append(trace, calc.NewRuleID(pID.Kind, tier.GetName(), pID.Name, pID.Namespace, ruleIndex, dir, rules.RuleActionPass)) // Pass means end evaluation of policies and proceed to next tier (or profiles), if any. break Policy case LOG: @@ -188,11 +185,11 @@ func checkTiers(store *policystore.PolicyStore, ep *proto.WorkloadEndpoint, dir continue case ALLOW: s.Code = OK - trace = append(trace, calc.NewRuleID(profileStr, name, "", ruleIndex, dir, rules.RuleActionAllow)) + trace = append(trace, calc.NewRuleID(v3.KindProfile, profileStr, name, "", ruleIndex, dir, rules.RuleActionAllow)) return case DENY, PASS: s.Code = PERMISSION_DENIED - trace = append(trace, calc.NewRuleID(profileStr, name, "", ruleIndex, dir, rules.RuleActionDeny)) + trace = append(trace, calc.NewRuleID(v3.KindProfile, profileStr, name, "", ruleIndex, dir, rules.RuleActionDeny)) return case LOG: log.Debug("profile should never return LOG action") @@ -203,7 +200,7 @@ func checkTiers(store *policystore.PolicyStore, ep *proto.WorkloadEndpoint, dir } else { log.Debug("0 active profiles, deny request.") s.Code = PERMISSION_DENIED - trace = append(trace, calc.NewRuleID(profileStr, profileStr, "", tierDefaultActionIndex, dir, rules.RuleActionDeny)) + trace = append(trace, calc.NewRuleID(v3.KindProfile, profileStr, profileStr, "", tierDefaultActionIndex, dir, rules.RuleActionDeny)) } return } @@ -299,47 +296,9 @@ func handlePanic(s *status.Status) { } // getPoliciesByDirection returns the list of policy names for the given direction. -func getPoliciesByDirection(dir rules.RuleDir, tier *proto.TierInfo) []string { +func getPoliciesByDirection(dir rules.RuleDir, tier *proto.TierInfo) []*proto.PolicyID { if dir == rules.RuleDirEgress { return tier.EgressPolicies } return tier.IngressPolicies } - -// getPolicyName Removes any namespace and tier prefix to get the name of the policy only; preserves -// the "staged:", knp.default, kanp.adminnetworkpolicy, and kbnp.baselinenetworkpolicy prefixes, if -// present. -// The patterns that are handled are: -// - "/." => "" -// - "/.staged:" => "staged:" -// - "default/knp.default." => "knp.default." -// - "default/staged:knp.default." => "staged:knp.default." -// - "default/knp.default.staged:" => "knp.default.staged:" -// - "kanp.adminnetworkpolicy." => "kanp.adminnetworkpolicy." -// - "kbanp.baselineadminnetworkpolicy." => "kbanp.baselinenetworkpolicy." -func getPolicyName(s string) string { - // Remove namespace if present - if idx := strings.IndexByte(s, '/'); idx >= 0 && idx < len(s)-1 { - s = s[idx+1:] - } - - // Track staged prefix - staged := "" - if strings.HasPrefix(s, "staged:") { - staged = "staged:" - s = s[len("staged:"):] - } - - // If not one of the special prefixes, strip off the tier part - isSpecialPrefix := strings.HasPrefix(s, names.K8sNetworkPolicyNamePrefix) || - strings.HasPrefix(s, names.K8sAdminNetworkPolicyNamePrefix) || - strings.HasPrefix(s, names.K8sBaselineAdminNetworkPolicyNamePrefix) - - if !isSpecialPrefix { - if idx := strings.IndexByte(s, '.'); idx >= 0 && idx < len(s)-1 { - s = s[idx+1:] - } - } - - return staged + s -} diff --git a/app-policy/checker/check_test.go b/app-policy/checker/check_test.go index 828c3e2ef55..61f5cec20d8 100644 --- a/app-policy/checker/check_test.go +++ b/app-policy/checker/check_test.go @@ -22,6 +22,7 @@ import ( authz "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" "github.com/gogo/googleapis/google/rpc" . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/calico/app-policy/policystore" "github.com/projectcalico/calico/felix/ip" @@ -66,12 +67,13 @@ func TestEvaluateEndpointWithMatchingPolicy(t *testing.T) { Tiers: []*proto.TierInfo{ { Name: "tier1", - IngressPolicies: []string{"policy1"}, + IngressPolicies: []*proto.PolicyID{{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}}, DefaultAction: "Deny", }, }, } - store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Tier: "tier1", Name: "policy1"})] = &proto.Policy{ + store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy})] = &proto.Policy{ + Tier: "tier1", InboundRules: []*proto.Rule{ { Action: "allow", @@ -107,12 +109,12 @@ func TestEvaluateEndpointWithNonMatchingPolicyTierDefaultAction(t *testing.T) { tiers: []*proto.TierInfo{ { Name: "tier1", - IngressPolicies: []string{"policy1"}, + IngressPolicies: []*proto.PolicyID{{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}}, DefaultAction: "Deny", }, { Name: "tier2", - IngressPolicies: []string{"policy2"}, + IngressPolicies: []*proto.PolicyID{{Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}}, DefaultAction: "Pass", }, }, @@ -125,12 +127,12 @@ func TestEvaluateEndpointWithNonMatchingPolicyTierDefaultAction(t *testing.T) { tiers: []*proto.TierInfo{ { Name: "tier1", - IngressPolicies: []string{"policy1"}, + IngressPolicies: []*proto.PolicyID{{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}}, DefaultAction: "Pass", }, { Name: "tier2", - IngressPolicies: []string{"policy2"}, + IngressPolicies: []*proto.PolicyID{{Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}}, DefaultAction: "Deny", }, }, @@ -144,8 +146,8 @@ func TestEvaluateEndpointWithNonMatchingPolicyTierDefaultAction(t *testing.T) { t.Run(tt.name, func(t *testing.T) { store := policystore.NewPolicyStore() ep := &proto.WorkloadEndpoint{Tiers: tt.tiers} - store.PolicyByID[types.PolicyID{Tier: "tier1", Name: "policy1"}] = &proto.Policy{} - store.PolicyByID[types.PolicyID{Tier: "tier2", Name: "policy2"}] = &proto.Policy{} + store.PolicyByID[types.PolicyID{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}] = &proto.Policy{Tier: "default"} + store.PolicyByID[types.PolicyID{Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}] = &proto.Policy{Tier: "default"} flow := &MockFlow{Protocol: 6, DestPort: 443} trace := Evaluate(rules.RuleDirIngress, store, ep, flow) @@ -197,12 +199,13 @@ func TestEvaluateEndpointWithNonMatchingProfile(t *testing.T) { Tiers: []*proto.TierInfo{ { Name: "tier1", - EgressPolicies: []string{"policy1"}, + EgressPolicies: []*proto.PolicyID{{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}}, DefaultAction: "Deny", }, }, } - store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Name: "policy1", Tier: "tier1"})] = &proto.Policy{ + store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy})] = &proto.Policy{ + Tier: "tier1", Namespace: "ns1", OutboundRules: []*proto.Rule{ { @@ -241,7 +244,7 @@ func TestEvaluateEndpointWithNonMatchingProfile(t *testing.T) { Expect(trace[0].Index).To(Equal(-1)) Expect(trace[0].Tier).To(Equal("tier1")) Expect(trace[0].Name).To(Equal("policy1")) - Expect(trace[0].Namespace).To(Equal("ns1")) + Expect(trace[0].Namespace).To(Equal("")) // Test with a matching source IP and destination port 80 flow2 := &MockFlow{ @@ -258,7 +261,7 @@ func TestEvaluateEndpointWithNonMatchingProfile(t *testing.T) { Expect(trace[0].Index).To(Equal(0)) Expect(trace[0].Tier).To(Equal("tier1")) Expect(trace[0].Name).To(Equal("policy1")) - Expect(trace[0].Namespace).To(Equal("ns1")) + Expect(trace[0].Namespace).To(Equal("")) // Test with a matching source IP and destination port 443 flow3 := &MockFlow{ @@ -275,7 +278,7 @@ func TestEvaluateEndpointWithNonMatchingProfile(t *testing.T) { Expect(trace[0].Index).To(Equal(1)) Expect(trace[0].Tier).To(Equal("tier1")) Expect(trace[0].Name).To(Equal("policy1")) - Expect(trace[0].Namespace).To(Equal("ns1")) + Expect(trace[0].Namespace).To(Equal("")) } // actionFromString should parse strings in case-insensitive mode. @@ -379,20 +382,22 @@ func TestCheckNoIngressPolicyRulesInTier(t *testing.T) { Tiers: []*proto.TierInfo{ { Name: "tier1", - EgressPolicies: []string{"policy1", "policy2"}, + EgressPolicies: []*proto.PolicyID{{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}, {Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}}, DefaultAction: "Deny", }, }, ProfileIds: []string{"profile1"}, } - store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Tier: "tier1", Name: "policy1"})] = &proto.Policy{ + store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy})] = &proto.Policy{ + Tier: "tier1", OutboundRules: []*proto.Rule{ { Action: "allow", }, }, } - store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Tier: "tier1", Name: "policy2"})] = &proto.Policy{ + store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Name: "policy2", Kind: v3.KindGlobalNetworkPolicy})] = &proto.Policy{ + Tier: "tier1", OutboundRules: []*proto.Rule{ { Action: "allow", @@ -480,12 +485,13 @@ func TestCheckStorePolicyMatch(t *testing.T) { Tiers: []*proto.TierInfo{ { Name: "tier1", - IngressPolicies: []string{"policy1", "policy2"}, + IngressPolicies: []*proto.PolicyID{{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}, {Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}}, DefaultAction: "Deny", }, }, } - store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Tier: "tier1", Name: "policy1"})] = &proto.Policy{ + store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy})] = &proto.Policy{ + Tier: "tier1", InboundRules: []*proto.Rule{ { Action: "deny", @@ -493,7 +499,8 @@ func TestCheckStorePolicyMatch(t *testing.T) { }, }, } - store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Tier: "tier1", Name: "policy2"})] = &proto.Policy{ + store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Name: "policy2", Kind: v3.KindGlobalNetworkPolicy})] = &proto.Policy{ + Tier: "tier1", InboundRules: []*proto.Rule{ { Action: "allow", @@ -574,7 +581,7 @@ func TestCheckStoreProfileOnly(t *testing.T) { Expect(status.Code).To(Equal(PERMISSION_DENIED)) } -// And endpoint with a Tier should not evaluate profiles; there is a default deny on the tier. +// An endpoint with a Tier should not evaluate profiles; there is a default deny on the tier. func TestCheckStorePolicyDefaultDeny(t *testing.T) { RegisterTestingT(t) @@ -583,13 +590,14 @@ func TestCheckStorePolicyDefaultDeny(t *testing.T) { Tiers: []*proto.TierInfo{ { Name: "tier1", - IngressPolicies: []string{"policy1"}, + IngressPolicies: []*proto.PolicyID{{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}}, DefaultAction: "Deny", }, }, ProfileIds: []string{"profile1"}, } - store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Tier: "tier1", Name: "policy1"})] = &proto.Policy{ + store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy})] = &proto.Policy{ + Tier: "tier1", InboundRules: []*proto.Rule{ { Action: "deny", @@ -631,14 +639,15 @@ func TestCheckStorePass(t *testing.T) { store.Endpoint = &proto.WorkloadEndpoint{ Tiers: []*proto.TierInfo{{ Name: "tier1", - IngressPolicies: []string{"policy1", "policy2"}, + IngressPolicies: []*proto.PolicyID{{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}, {Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}}, DefaultAction: "Deny", }}, ProfileIds: []string{"profile1"}, } // Policy1 matches and has action PASS, which means policy2 is not evaluated. - store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Tier: "tier1", Name: "policy1"})] = &proto.Policy{ + store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Kind: v3.KindGlobalNetworkPolicy, Name: "policy1"})] = &proto.Policy{ + Tier: "tier1", InboundRules: []*proto.Rule{ { Action: "next-tier", @@ -646,7 +655,8 @@ func TestCheckStorePass(t *testing.T) { }, }, } - store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Tier: "tier1", Name: "policy2"})] = &proto.Policy{ + store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Kind: v3.KindGlobalNetworkPolicy, Name: "policy2"})] = &proto.Policy{ + Tier: "tier1", InboundRules: []*proto.Rule{ { Action: "deny", @@ -689,7 +699,7 @@ func TestCheckStoreInitFails(t *testing.T) { store.Endpoint = &proto.WorkloadEndpoint{ Tiers: []*proto.TierInfo{{ Name: "tier1", - IngressPolicies: []string{"policy1", "policy2"}, + IngressPolicies: []*proto.PolicyID{{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}, {Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}}, DefaultAction: "Deny", }}, ProfileIds: []string{"profile1"}, @@ -717,19 +727,20 @@ func TestCheckStoreWithInvalidData(t *testing.T) { store.Endpoint = &proto.WorkloadEndpoint{ Tiers: []*proto.TierInfo{{ Name: "tier1", - IngressPolicies: []string{"policy1", "policy2"}, + IngressPolicies: []*proto.PolicyID{{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}, {Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}}, DefaultAction: "Deny", }}, ProfileIds: []string{"profile1"}, } - id := types.ProtoToPolicyID(&proto.PolicyID{Tier: "tier1", Name: "policy1"}) + id := types.ProtoToPolicyID(&proto.PolicyID{Kind: v3.KindGlobalNetworkPolicy, Name: "policy1"}) store.PolicyByID[id] = &proto.Policy{InboundRules: []*proto.Rule{ { Action: "allow", HttpMatch: &proto.HTTPMatch{ Methods: []string{"GET", "POST"}, Paths: []*proto.HTTPMatch_PathMatch{ - {PathMatch: &proto.HTTPMatch_PathMatch_Exact{Exact: "/foo"}}}, + {PathMatch: &proto.HTTPMatch_PathMatch_Exact{Exact: "/foo"}}, + }, }, }, }} @@ -760,22 +771,23 @@ func TestCheckStorePolicyMultiTierMatch(t *testing.T) { Tiers: []*proto.TierInfo{ { Name: "tier1", - IngressPolicies: []string{"policy1"}, + IngressPolicies: []*proto.PolicyID{{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}}, DefaultAction: "Deny", }, { Name: "tier2", - IngressPolicies: []string{"policy2", "policy3"}, + IngressPolicies: []*proto.PolicyID{{Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}, {Name: "policy3", Kind: v3.KindGlobalNetworkPolicy}}, DefaultAction: "Pass", }, { Name: "tier3", - IngressPolicies: []string{"policy4"}, + IngressPolicies: []*proto.PolicyID{{Name: "policy4", Kind: v3.KindGlobalNetworkPolicy}}, DefaultAction: "Deny", }, }, } - store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Tier: "tier1", Name: "policy1"})] = &proto.Policy{ + store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Kind: v3.KindGlobalNetworkPolicy, Name: "policy1"})] = &proto.Policy{ + Tier: "tier1", InboundRules: []*proto.Rule{ { Action: "next-tier", @@ -783,7 +795,8 @@ func TestCheckStorePolicyMultiTierMatch(t *testing.T) { }, }, } - store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Tier: "tier2", Name: "policy2"})] = &proto.Policy{ + store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Name: "policy2", Kind: v3.KindGlobalNetworkPolicy})] = &proto.Policy{ + Tier: "tier2", InboundRules: []*proto.Rule{ { Action: "deny", @@ -793,7 +806,8 @@ func TestCheckStorePolicyMultiTierMatch(t *testing.T) { }, }, } - store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Tier: "tier2", Name: "policy3"})] = &proto.Policy{ + store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Name: "policy3", Kind: v3.KindGlobalNetworkPolicy})] = &proto.Policy{ + Tier: "tier2", InboundRules: []*proto.Rule{ { Action: "allow", @@ -803,7 +817,8 @@ func TestCheckStorePolicyMultiTierMatch(t *testing.T) { }, }, } - store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Tier: "tier3", Name: "policy4"})] = &proto.Policy{ + store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Name: "policy4", Kind: v3.KindGlobalNetworkPolicy})] = &proto.Policy{ + Tier: "tier3", InboundRules: []*proto.Rule{ { Action: "allow", @@ -853,17 +868,18 @@ func TestCheckStorePolicyMultiTierDiffTierMatch(t *testing.T) { Tiers: []*proto.TierInfo{ { Name: "tier1", - IngressPolicies: []string{"policy1", "policy2"}, + IngressPolicies: []*proto.PolicyID{{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}, {Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}}, DefaultAction: "Deny", }, { Name: "tier2", - IngressPolicies: []string{"policy3"}, + IngressPolicies: []*proto.PolicyID{{Name: "policy3", Kind: v3.KindGlobalNetworkPolicy}}, DefaultAction: "Pass", }, }, } - store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Tier: "tier1", Name: "policy1"})] = &proto.Policy{ + store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Kind: v3.KindGlobalNetworkPolicy, Name: "policy1"})] = &proto.Policy{ + Tier: "tier1", InboundRules: []*proto.Rule{ { Action: "deny", @@ -871,7 +887,8 @@ func TestCheckStorePolicyMultiTierDiffTierMatch(t *testing.T) { }, }, } - store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Tier: "tier1", Name: "policy2"})] = &proto.Policy{ + store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Kind: v3.KindGlobalNetworkPolicy, Name: "policy2"})] = &proto.Policy{ + Tier: "tier1", InboundRules: []*proto.Rule{ { Action: "next-tier", @@ -879,7 +896,8 @@ func TestCheckStorePolicyMultiTierDiffTierMatch(t *testing.T) { }, }, } - store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Tier: "tier2", Name: "policy3"})] = &proto.Policy{ + store.PolicyByID[types.ProtoToPolicyID(&proto.PolicyID{Name: "policy3", Kind: v3.KindGlobalNetworkPolicy})] = &proto.Policy{ + Tier: "tier2", InboundRules: []*proto.Rule{ { Action: "allow", @@ -985,32 +1003,6 @@ func TestLookupEndpointKeysFromSrcDst(t *testing.T) { } } -func TestGetPolicyName(t *testing.T) { - tests := []struct { - input string - expected string - }{ - {"default/tier.policy", "policy"}, - {"default/tier.staged:policy", "staged:policy"}, - {"tier.globalpolicy", "globalpolicy"}, - {"tier.staged:globalpolicy", "staged:globalpolicy"}, - {"default/knp.default.policy", "knp.default.policy"}, - {"default/staged:knp.default.policy", "staged:knp.default.policy"}, - {"default/knp.default.staged:policy", "knp.default.staged:policy"}, - {"kanp.adminnetworkpolicy.policy", "kanp.adminnetworkpolicy.policy"}, - {"kbanp.baselineadminnetworkpolicy.policy", "kbanp.baselineadminnetworkpolicy.policy"}, - } - - for _, test := range tests { - t.Run(test.input, func(t *testing.T) { - result := getPolicyName(test.input) - if result != test.expected { - t.Errorf("expected %s, got %s", test.expected, result) - } - }) - } -} - // MockFlow is a mock implementation of the Flow interface for testing purposes. type MockFlow struct { SourceIP net.IP diff --git a/app-policy/checker/match.go b/app-policy/checker/match.go index d43a5a1b8b3..6bb48875d29 100644 --- a/app-policy/checker/match.go +++ b/app-policy/checker/match.go @@ -17,6 +17,7 @@ package checker import ( "fmt" "net" + "slices" "strings" log "github.com/sirupsen/logrus" @@ -202,12 +203,7 @@ func matchName(names []string, name string) bool { log.Debug("No names on rule.") return true } - for _, n := range names { - if n == name { - return true - } - } - return false + return slices.Contains(names, name) } // matchLabels checks if the selector matches the labels. It returns true if the selector matches, diff --git a/app-policy/checker/requestcache.go b/app-policy/checker/requestcache.go index 2599995f5e4..f1bef376644 100644 --- a/app-policy/checker/requestcache.go +++ b/app-policy/checker/requestcache.go @@ -16,6 +16,7 @@ package checker import ( "fmt" + "maps" "regexp" "sync" @@ -116,9 +117,7 @@ func (r *requestCache) initNamespace(name string) *namespace { msg, ok := r.store.NamespaceByID[types.ProtoToNamespaceID(&id)] if ok { ns.Labels = make(map[string]string) - for k, v := range msg.GetLabels() { - ns.Labels[k] = v - } + maps.Copy(ns.Labels, msg.GetLabels()) } return ns } @@ -132,15 +131,11 @@ func (r *requestCache) initPeer(principal string, labels map[string]string) *pee return nil } peer.Labels = make(map[string]string) - for k, v := range labels { - peer.Labels[k] = v - } + maps.Copy(peer.Labels, labels) id := proto.ServiceAccountID{Name: peer.Name, Namespace: peer.Namespace} msg, ok := r.store.ServiceAccountByID[types.ProtoToServiceAccountID(&id)] if ok { - for k, v := range msg.GetLabels() { - peer.Labels[k] = v - } + maps.Copy(peer.Labels, msg.GetLabels()) } return &peer } diff --git a/app-policy/checker/server_test.go b/app-policy/checker/server_test.go index f4a5373eb4e..3384f946b9e 100644 --- a/app-policy/checker/server_test.go +++ b/app-policy/checker/server_test.go @@ -15,7 +15,6 @@ package checker import ( - "context" "testing" authz "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" @@ -29,8 +28,7 @@ import ( func TestCheckNoStore(t *testing.T) { RegisterTestingT(t) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := t.Context() stores := policystore.NewPolicyStoreManager() uut := NewServer(ctx, stores) @@ -44,8 +42,7 @@ func TestCheckNoStore(t *testing.T) { func TestCheckStore(t *testing.T) { RegisterTestingT(t) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := t.Context() stores := policystore.NewPolicyStoreManager() uut := NewServer(ctx, stores) diff --git a/app-policy/cmd/dikastes/dikastes.go b/app-policy/cmd/dikastes/dikastes.go index f21797ab128..72d8e2e4b49 100644 --- a/app-policy/cmd/dikastes/dikastes.go +++ b/app-policy/cmd/dikastes/dikastes.go @@ -73,7 +73,7 @@ func main() { } } -func runServer(arguments map[string]interface{}) { +func runServer(arguments map[string]any) { filePath := arguments["--listen"].(string) dial := arguments["--dial"].(string) _, err := os.Stat(filePath) @@ -160,7 +160,7 @@ func runServer(arguments map[string]interface{}) { gs.GracefulStop() } -func runClient(arguments map[string]interface{}) { +func runClient(arguments map[string]any) { dial := arguments["--dial"].(string) namespace := arguments[""].(string) account := arguments[""].(string) @@ -223,19 +223,17 @@ func (h *httpTerminationHandler) RunHTTPServer(addr string, port string) (*http. } } - httpServerSockAddr := fmt.Sprintf("%s:%s", addr, port) + httpServerSockAddr := net.JoinHostPort(addr, port) httpServerMux := http.NewServeMux() httpServerMux.Handle("/terminate", h) httpServer := &http.Server{Addr: httpServerSockAddr, Handler: httpServerMux} httpServerWg := &sync.WaitGroup{} - httpServerWg.Add(1) - go func() { - defer httpServerWg.Done() + httpServerWg.Go(func() { log.Infof("starting HTTP server on %v", httpServer.Addr) if err := httpServer.ListenAndServe(); err != http.ErrServerClosed { log.Fatalf("HTTP server closed unexpectedly: %v", err) } - }() + }) return httpServer, httpServerWg, nil } diff --git a/app-policy/deps.txt b/app-policy/deps.txt index e51a98ac8c8..96f4ae1b1c4 100644 --- a/app-policy/deps.txt +++ b/app-policy/deps.txt @@ -2,21 +2,24 @@ This file contains the list of modules that this package depends on in order to trigger CI on changes -go 1.25.1 +go 1.25.7 github.com/beorn7/perks v1.0.1 github.com/blang/semver/v4 v4.0.0 github.com/cespare/xxhash/v2 v2.3.0 -github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 github.com/coreos/go-semver v0.3.1 -github.com/coreos/go-systemd/v22 v22.5.0 +github.com/coreos/go-systemd/v22 v22.6.0 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 -github.com/emicklei/go-restful/v3 v3.11.0 +github.com/emicklei/go-restful v2.15.0+incompatible +github.com/emicklei/go-restful/v3 v3.12.2 github.com/envoyproxy/go-control-plane v0.13.4 -github.com/envoyproxy/go-control-plane/envoy v1.32.4 +github.com/envoyproxy/go-control-plane/envoy v1.35.0 github.com/envoyproxy/protoc-gen-validate v1.2.1 -github.com/fxamacker/cbor/v2 v2.7.0 +github.com/fxamacker/cbor/v2 v2.9.0 github.com/go-ini/ini v1.67.0 +github.com/go-kit/log v0.2.1 +github.com/go-logfmt/logfmt v0.6.0 github.com/go-logr/logr v1.4.3 github.com/go-openapi/jsonpointer v0.21.0 github.com/go-openapi/jsonreference v0.20.2 @@ -27,11 +30,11 @@ github.com/gogo/protobuf v1.3.2 github.com/golang/protobuf v1.5.4 github.com/golang/snappy v1.0.0 github.com/google/btree v1.1.3 -github.com/google/gnostic-models v0.6.9 +github.com/google/gnostic-models v0.7.0 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 -github.com/grpc-ecosystem/grpc-gateway v1.16.0 -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 github.com/jinzhu/copier v0.4.0 github.com/josharian/intern v1.0.0 github.com/josharian/native v1.1.0 @@ -39,7 +42,6 @@ github.com/json-iterator/go v1.1.12 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 github.com/kelseyhightower/envconfig v1.4.0 github.com/leodido/go-urn v1.4.0 -github.com/lithammer/dedent v1.1.0 github.com/mailru/easyjson v0.7.7 github.com/mattn/go-runewidth v0.0.16 github.com/mdlayher/genetlink v1.3.2 @@ -47,74 +49,75 @@ github.com/mdlayher/netlink v1.7.2 github.com/mdlayher/socket v0.5.1 github.com/mipearson/rfw v0.0.0-20170619235010-6f0a6f3266ba github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd -github.com/modern-go/reflect2 v1.0.2 +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 github.com/olekukonko/tablewriter v0.0.5 -github.com/onsi/gomega v1.38.0 +github.com/onsi/gomega v1.39.1 +github.com/openshift/custom-resource-status v1.1.2 github.com/pkg/errors v0.9.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/projectcalico/calico -github.com/projectcalico/calico/lib/std v0.0.0-00010101000000-000000000000 -github.com/projectcalico/go-json v0.0.0-20161128004156-6219dc7339ba -github.com/projectcalico/go-yaml-wrapper v0.0.0-20191112210931-090425220c54 -github.com/prometheus/client_golang v1.23.0 +github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 -github.com/prometheus/common v0.65.0 -github.com/prometheus/procfs v0.17.0 +github.com/prometheus/common v0.67.2 +github.com/prometheus/procfs v0.19.2 github.com/rivo/uniseg v0.4.7 github.com/sirupsen/logrus v1.9.3 -github.com/spf13/cobra v1.9.1 -github.com/spf13/pflag v1.0.7 +github.com/spf13/cobra v1.10.1 +github.com/spf13/pflag v1.0.10 github.com/stretchr/objx v0.5.2 -github.com/stretchr/testify v1.10.0 +github.com/stretchr/testify v1.11.1 github.com/tchap/go-patricia/v2 v2.3.3 github.com/vishvananda/netlink v1.3.1 github.com/vishvananda/netns v0.0.5 github.com/x448/float16 v0.8.4 -go.etcd.io/etcd/api/v3 v3.6.4 -go.etcd.io/etcd/client/pkg/v3 v3.6.4 -go.etcd.io/etcd/client/v3 v3.6.4 -go.opentelemetry.io/otel v1.35.0 -go.opentelemetry.io/otel/trace v1.35.0 +go.etcd.io/etcd/api/v3 v3.6.5 +go.etcd.io/etcd/client/pkg/v3 v3.6.5 +go.etcd.io/etcd/client/v3 v3.6.5 +go.opentelemetry.io/otel v1.37.0 +go.opentelemetry.io/otel/trace v1.37.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 -go.yaml.in/yaml/v2 v2.4.2 -golang.org/x/crypto v0.41.0 -golang.org/x/net v0.43.0 -golang.org/x/oauth2 v0.30.0 -golang.org/x/sync v0.16.0 -golang.org/x/sys v0.35.0 -golang.org/x/term v0.34.0 -golang.org/x/text v0.28.0 -golang.org/x/time v0.12.0 +go.yaml.in/yaml/v2 v2.4.3 +go.yaml.in/yaml/v3 v3.0.4 +golang.org/x/crypto v0.47.0 +golang.org/x/net v0.49.0 +golang.org/x/oauth2 v0.34.0 +golang.org/x/sync v0.19.0 +golang.org/x/sys v0.40.0 +golang.org/x/term v0.39.0 +golang.org/x/text v0.33.0 +golang.org/x/time v0.14.0 golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 google.golang.org/genproto v0.0.0-20250603155806-513f23925822 -google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 -google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a -google.golang.org/grpc v1.72.2 -google.golang.org/protobuf v1.36.7 +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c +google.golang.org/grpc v1.76.0 +google.golang.org/protobuf v1.36.10 gopkg.in/evanphx/json-patch.v4 v4.12.0 gopkg.in/go-playground/validator.v9 v9.30.2 gopkg.in/inf.v0 v0.9.1 -gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 -k8s.io/api v0.33.5 -k8s.io/apiextensions-apiserver v0.33.5 -k8s.io/apimachinery v0.33.5 -k8s.io/apiserver v0.33.5 -k8s.io/client-go v0.33.5 -k8s.io/component-base v0.33.5 -k8s.io/component-helpers v0.33.5 -k8s.io/controller-manager v0.33.5 +k8s.io/api v0.34.3 +k8s.io/apiextensions-apiserver v0.34.3 +k8s.io/apimachinery v0.34.3 +k8s.io/apiserver v0.34.3 +k8s.io/client-go v0.34.3 +k8s.io/component-base v0.34.3 +k8s.io/component-helpers v0.34.3 +k8s.io/controller-manager v0.34.3 k8s.io/klog v0.2.0 k8s.io/klog/v2 v2.130.1 -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff -k8s.io/kubernetes v1.33.5 -k8s.io/utils v0.0.0-20241210054802-24370beab758 +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b +k8s.io/kubernetes v1.34.3 +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 +kubevirt.io/api v1.8.0-alpha.0 +kubevirt.io/containerized-data-importer-api v1.63.1 +kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 -sigs.k8s.io/knftables v0.0.18 -sigs.k8s.io/network-policy-api v0.1.5 +sigs.k8s.io/knftables v0.0.19 +sigs.k8s.io/network-policy-api v0.1.8-0.20260212153203-412bf65729a5 sigs.k8s.io/randfill v1.0.0 -sigs.k8s.io/structured-merge-diff/v4 v4.6.0 +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 sigs.k8s.io/yaml v1.6.0 diff --git a/app-policy/policystore/ipset.go b/app-policy/policystore/ipset.go index 619ab32811e..cf7e9146436 100644 --- a/app-policy/policystore/ipset.go +++ b/app-policy/policystore/ipset.go @@ -257,7 +257,7 @@ func (n *trieNode) containsIP(ip net.IP, depth uint64) bool { } func (bm *networkBitmap) isEmpty() bool { - for i := 0; i < BitmapSize; i++ { + for i := range BitmapSize { if bm[i] != 0 { // Still has IP addresses bitmapped return false diff --git a/app-policy/policystore/process.go b/app-policy/policystore/process.go index 0e35e0ce9b8..dba1c385c88 100644 --- a/app-policy/policystore/process.go +++ b/app-policy/policystore/process.go @@ -39,7 +39,7 @@ func (store *PolicyStore) ProcessUpdate(subscriptionType string, update *proto.T case *proto.ToDataplane_ActiveProfileRemove: store.processActiveProfileRemove(payload.ActiveProfileRemove) case *proto.ToDataplane_ActivePolicyUpdate: - if !storeStaged && model.PolicyIsStaged(payload.ActivePolicyUpdate.Id.Name) { + if !storeStaged && model.KindIsStaged(payload.ActivePolicyUpdate.Id.Name) { log.WithFields(log.Fields{ "id": payload.ActivePolicyUpdate.Id, }).Debug("Skipping StagedPolicy ActivePolicyUpdate") @@ -49,7 +49,7 @@ func (store *PolicyStore) ProcessUpdate(subscriptionType string, update *proto.T store.processActivePolicyUpdate(payload.ActivePolicyUpdate) case *proto.ToDataplane_ActivePolicyRemove: - if !storeStaged && model.PolicyIsStaged(payload.ActivePolicyRemove.Id.Name) { + if !storeStaged && model.KindIsStaged(payload.ActivePolicyRemove.Id.Name) { log.WithFields(log.Fields{ "id": payload.ActivePolicyRemove.Id, }).Debug("Skipping StagedPolicy ActivePolicyRemove") @@ -198,7 +198,6 @@ func (store *PolicyStore) processWorkloadEndpointUpdate(subscriptionType string, case "per-host-policies": store.Endpoints[types.ProtoToWorkloadEndpointID(update.Id)] = update.Endpoint log.Debugf("%d endpoints received so far", len(store.Endpoints)) - //store.wepUpdates.onWorkloadEndpointUpdate(update, store.IPToIndexes) } } @@ -216,7 +215,6 @@ func (store *PolicyStore) processWorkloadEndpointRemove(subscriptionType string, store.Endpoint = nil case "per-host-policies": delete(store.Endpoints, types.ProtoToWorkloadEndpointID(update.Id)) - //store.wepUpdates.onWorkloadEndpointRemove(update, store.IPToIndexes) } } diff --git a/app-policy/policystore/process_test.go b/app-policy/policystore/process_test.go index 769cef28808..27b3158a4c0 100644 --- a/app-policy/policystore/process_test.go +++ b/app-policy/policystore/process_test.go @@ -23,9 +23,11 @@ import ( "github.com/projectcalico/calico/felix/types" ) -const addr1Ip = "3.4.6.8" -const addr2Ip = "23.8.58.1" -const addr3Ip = "2.2.2.2" +const ( + addr1Ip = "3.4.6.8" + addr2Ip = "23.8.58.1" + addr3Ip = "2.2.2.2" +) var profile1 = &proto.Profile{ InboundRules: []*proto.Rule{ @@ -35,6 +37,7 @@ var profile1 = &proto.Profile{ }, }, } + var profile2 = &proto.Profile{ OutboundRules: []*proto.Rule{ { @@ -43,7 +46,9 @@ var profile2 = &proto.Profile{ }, }, } + var policy1 = &proto.Policy{ + Tier: "test_tier", InboundRules: []*proto.Rule{ { Action: "allow", @@ -51,7 +56,9 @@ var policy1 = &proto.Policy{ }, }, } + var policy2 = &proto.Policy{ + Tier: "test_tier", OutboundRules: []*proto.Rule{ { Action: "allow", @@ -59,14 +66,17 @@ var policy2 = &proto.Policy{ }, }, } + var endpoint1 = &proto.WorkloadEndpoint{ Name: "wep", ProfileIds: []string{"profile1", "profile2"}, } + var serviceAccount1 = &proto.ServiceAccountUpdate{ Id: &proto.ServiceAccountID{Name: "serviceAccount1", Namespace: "test"}, Labels: map[string]string{"k1": "v1", "k2": "v2"}, } + var namespace1 = &proto.NamespaceUpdate{ Id: &proto.NamespaceID{Name: "namespace1"}, Labels: map[string]string{"k1": "v1", "k2": "v2"}, @@ -123,7 +133,6 @@ func TestIPSetUpdateDispatch(t *testing.T) { RegisterTestingT(t) id := "test_id" store := NewPolicyStore() - //inSync := make(chan struct{}) update := &proto.ToDataplane{ Payload: &proto.ToDataplane_IpsetUpdate{IpsetUpdate: &proto.IPSetUpdate{ Id: id, @@ -131,7 +140,9 @@ func TestIPSetUpdateDispatch(t *testing.T) { Members: []string{ addr1Ip, addr2Ip, - }}}} + }, + }}, + } Expect(func() { store.ProcessUpdate("", update, false) }).ToNot(Panic()) } @@ -293,7 +304,7 @@ func TestActiveProfileRemoveDispatch(t *testing.T) { // ActivePolicyUpdate for a new id func TestActivePolicyUpdateNonExist(t *testing.T) { RegisterTestingT(t) - id := proto.PolicyID{Tier: "test_tier", Name: "test_id"} + id := proto.PolicyID{Name: "test_id"} store := NewPolicyStore() update := &proto.ActivePolicyUpdate{ Id: &id, @@ -307,7 +318,7 @@ func TestActivePolicyUpdateNonExist(t *testing.T) { // ActivePolicyUpdate for an existing id func TestActivePolicyUpdateExist(t *testing.T) { RegisterTestingT(t) - id := types.PolicyID{Tier: "test_tier", Name: "test_id"} + id := types.PolicyID{Name: "test_id"} store := NewPolicyStore() store.PolicyByID[id] = policy2 protoID := types.PolicyIDToProto(id) @@ -322,7 +333,7 @@ func TestActivePolicyUpdateExist(t *testing.T) { // processUpdate handles ActivePolicyDispatch func TestActivePolicyUpdateDispatch(t *testing.T) { RegisterTestingT(t) - id := proto.PolicyID{Tier: "test_tier", Name: "test_id"} + id := proto.PolicyID{Name: "test_id"} store := NewPolicyStore() update := &proto.ToDataplane{Payload: &proto.ToDataplane_ActivePolicyUpdate{ ActivePolicyUpdate: &proto.ActivePolicyUpdate{ @@ -336,7 +347,7 @@ func TestActivePolicyUpdateDispatch(t *testing.T) { // ActivePolicyRemove with unknown id is handled func TestActivePolicyRemoveNonExist(t *testing.T) { RegisterTestingT(t) - id := proto.PolicyID{Tier: "test_tier", Name: "test_id"} + id := proto.PolicyID{Name: "test_id"} store := NewPolicyStore() update := &proto.ActivePolicyRemove{Id: &id} store.processActivePolicyRemove(update) @@ -348,7 +359,7 @@ func TestActivePolicyRemoveNonExist(t *testing.T) { func TestActivePolicyRemoveExist(t *testing.T) { RegisterTestingT(t) - id := types.PolicyID{Tier: "test_tier", Name: "test_id"} + id := types.PolicyID{Name: "test_id"} store := NewPolicyStore() store.PolicyByID[id] = policy1 @@ -361,7 +372,7 @@ func TestActivePolicyRemoveExist(t *testing.T) { // processUpdate handles ActivePolicyRemove func TestActivePolicyRemoveDispatch(t *testing.T) { RegisterTestingT(t) - id := proto.PolicyID{Tier: "test_tier", Name: "test_id"} + id := proto.PolicyID{Name: "test_id"} store := NewPolicyStore() update := &proto.ToDataplane{Payload: &proto.ToDataplane_ActivePolicyRemove{ ActivePolicyRemove: &proto.ActivePolicyRemove{Id: &id}, @@ -425,7 +436,8 @@ func TestServiceAccountRemoveDispatch(t *testing.T) { id := types.ProtoToServiceAccountID(serviceAccount1.GetId()) store.ServiceAccountByID[id] = serviceAccount1 remove := &proto.ToDataplane{Payload: &proto.ToDataplane_ServiceAccountRemove{ - ServiceAccountRemove: &proto.ServiceAccountRemove{Id: serviceAccount1.Id}}} + ServiceAccountRemove: &proto.ServiceAccountRemove{Id: serviceAccount1.Id}, + }} Expect(func() { store.ProcessUpdate("", remove, false) }).ToNot(Panic()) Expect(store.ServiceAccountByID).To(Equal(map[types.ServiceAccountID]*proto.ServiceAccountUpdate{})) } @@ -446,7 +458,8 @@ func TestNamespaceRemoveDispatch(t *testing.T) { id := types.ProtoToNamespaceID(namespace1.GetId()) store.NamespaceByID[id] = namespace1 remove := &proto.ToDataplane{Payload: &proto.ToDataplane_NamespaceRemove{ - NamespaceRemove: &proto.NamespaceRemove{Id: namespace1.Id}}} + NamespaceRemove: &proto.NamespaceRemove{Id: namespace1.Id}, + }} Expect(func() { store.ProcessUpdate("", remove, false) }).ToNot(Panic()) Expect(store.NamespaceByID).To(Equal(map[types.NamespaceID]*proto.NamespaceUpdate{})) } diff --git a/app-policy/policystore/store.go b/app-policy/policystore/store.go index 841c1448b9d..9f09731c8e8 100644 --- a/app-policy/policystore/store.go +++ b/app-policy/policystore/store.go @@ -15,6 +15,7 @@ package policystore import ( + "maps" "sync" log "github.com/sirupsen/logrus" @@ -128,9 +129,7 @@ func (m *policyStoreManager) GetCurrentEndpoints() map[types.WorkloadEndpointID] defer m.mu.RUnlock() copy := make(map[types.WorkloadEndpointID]*proto.WorkloadEndpoint, len(m.current.Endpoints)) - for k, v := range m.current.Endpoints { - copy[k] = v - } + maps.Copy(copy, m.current.Endpoints) return copy } diff --git a/app-policy/proto/healthz.pb.go b/app-policy/proto/healthz.pb.go index 20db29e1c92..143ce1f840c 100644 --- a/app-policy/proto/healthz.pb.go +++ b/app-policy/proto/healthz.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.6 -// protoc v3.5.0 +// protoc-gen-go v1.36.8 +// protoc v6.32.0 // source: healthz.proto package proto diff --git a/app-policy/proto/healthz_grpc.pb.go b/app-policy/proto/healthz_grpc.pb.go index 34b64796238..a63627a5155 100644 --- a/app-policy/proto/healthz_grpc.pb.go +++ b/app-policy/proto/healthz_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v3.5.0 +// - protoc v6.32.0 // source: healthz.proto package proto diff --git a/app-policy/syncher/syncserver_test.go b/app-policy/syncher/syncserver_test.go index 051152b0c7f..b93d61c82c7 100644 --- a/app-policy/syncher/syncserver_test.go +++ b/app-policy/syncher/syncserver_test.go @@ -34,16 +34,14 @@ import ( func TestSyncRestart(t *testing.T) { RegisterTestingT(t) - sCtx, sCancel := context.WithCancel(context.Background()) - defer sCancel() + sCtx := t.Context() server := newTestSyncServer(sCtx) storeManager := policystore.NewPolicyStoreManager() uut := NewClient(server.GetTarget(), storeManager, uds.GetDialOptions()) - cCtx, cCancel := context.WithCancel(context.Background()) - defer cCancel() + cCtx := t.Context() go uut.Sync(cCtx) if uut.Readiness() { @@ -67,8 +65,7 @@ func TestSyncRestart(t *testing.T) { func TestSyncCancelBeforeInSync(t *testing.T) { RegisterTestingT(t) - sCtx, sCancel := context.WithCancel(context.Background()) - defer sCancel() + sCtx := t.Context() server := newTestSyncServer(sCtx) @@ -90,8 +87,7 @@ func TestSyncCancelBeforeInSync(t *testing.T) { func TestSyncCancelAfterInSync(t *testing.T) { RegisterTestingT(t) - sCtx, sCancel := context.WithCancel(context.Background()) - defer sCancel() + sCtx := t.Context() server := newTestSyncServer(sCtx) diff --git a/calicoctl/Dockerfile b/calicoctl/Dockerfile index 2f211b1f12f..9cfca706597 100644 --- a/calicoctl/Dockerfile +++ b/calicoctl/Dockerfile @@ -33,6 +33,14 @@ LABEL org.opencontainers.image.vendor="Project Calico" LABEL org.opencontainers.image.version="${GIT_VERSION}" LABEL org.opencontainers.image.licenses="Apache-2.0" +LABEL description="calicoctl(1) is a command line tool used to interface with the Calico datastore" +LABEL maintainer="maintainers@tigera.io" +LABEL name="Calico CLI tool" +LABEL release=1 +LABEL summary="calicoctl(1) is a command line tool used to interface with the Calico datastore" +LABEL vendor="Project Calico" +LABEL version="${GIT_VERSION}" + ENV CALICO_CTL_CONTAINER=TRUE COPY --from=source / / diff --git a/calicoctl/Makefile b/calicoctl/Makefile index d8af4f3b551..aa1c0593f50 100644 --- a/calicoctl/Makefile +++ b/calicoctl/Makefile @@ -26,8 +26,6 @@ CALICOCTL_DIR=calicoctl CTL_CONTAINER_CREATED=$(CALICOCTL_DIR)/.calico_ctl.created-$(ARCH) SRC_FILES=$(shell find $(CALICOCTL_DIR) -name '*.go') $(shell find ../libcalico-go -name '*.go') -TEST_CONTAINER_NAME ?= calico/test - .PHONY: clean ## Clean enough that a new release build will be clean clean: @@ -102,7 +100,7 @@ $(CALICO_VERSION_HELPER_BIN): $(CALICO_VERSION_HELPER_SRC) .PHONY: ut ## Run the tests in a container. Useful for CI, Mac dev. ut: bin/calicoctl-linux-$(ARCH) - $(DOCKER_RUN) $(CALICO_BUILD) sh -c 'cd /go/src/$(PACKAGE_NAME) && ginkgo -cover -r $(GINKGO_ARGS) calicoctl/*' + $(DOCKER_RUN) -e CALICO_API_GROUP=$(CALICO_API_GROUP) $(CALICO_BUILD) sh -c 'cd /go/src/$(PACKAGE_NAME) && ginkgo -cover -r $(GINKGO_ARGS) calicoctl/*' ############################################################################### # FVs @@ -113,18 +111,18 @@ fv: bin/calicoctl-linux-$(ARCH) version-helper $(MAKE) run-etcd # We start two API servers in order to test multiple kubeconfig support - $(MAKE) run-kubernetes-master KUBE_APISERVER_PORT=6443 KUBE_MOCK_NODE_MANIFEST=mock-node.yaml - $(MAKE) run-kubernetes-master KUBE_APISERVER_PORT=6444 KUBE_MOCK_NODE_MANIFEST=mock-node-second.yaml + $(MAKE) run-kubernetes-control-plane KUBE_APISERVER_PORT=6443 KUBE_MOCK_NODE_MANIFEST=mock-node.yaml + $(MAKE) run-kubernetes-control-plane KUBE_APISERVER_PORT=6444 KUBE_MOCK_NODE_MANIFEST=mock-node-second.yaml # Ensure anonymous is permitted to be admin. while ! docker exec st-apiserver-6443 kubectl --server=https://127.0.0.1:6443 create clusterrolebinding anonymous-admin --clusterrole=cluster-admin --user=system:anonymous; do sleep 2; done # Run the tests - $(DOCKER_RUN) $(CALICO_BUILD) sh -c 'cd /go/src/$(PACKAGE_NAME) && go test $(GO_TEST_ARGS) ./tests/fv' + $(DOCKER_RUN) -e CALICO_API_GROUP=$(CALICO_API_GROUP) $(CALICO_BUILD) sh -c 'cd /go/src/$(PACKAGE_NAME) && go test $(GO_TEST_ARGS) -count=1 ./tests/fv' # Cleanup $(MAKE) stop-etcd - $(MAKE) stop-kubernetes-master KUBE_APISERVER_PORT=6443 - $(MAKE) stop-kubernetes-master KUBE_APISERVER_PORT=6444 + $(MAKE) stop-kubernetes-control-plane KUBE_APISERVER_PORT=6443 + $(MAKE) stop-kubernetes-control-plane KUBE_APISERVER_PORT=6444 ############################################################################### # STs @@ -138,8 +136,9 @@ ST_OPTIONS?= .PHONY: st ## Run the STs in a container st: bin/calicoctl-linux-$(ARCH) version-helper + $(MAKE) -C ../node test_image $(MAKE) run-etcd - $(MAKE) run-kubernetes-master + $(MAKE) run-kubernetes-control-plane # Use the host, PID and network namespaces from the host. # Privileged is needed since 'calico node' write to /proc (to enable ip_forwarding) # Map the docker socket in so docker can be used from inside the container @@ -152,15 +151,15 @@ st: bin/calicoctl-linux-$(ARCH) version-helper -v $(CURDIR):/code \ -v /var/run/docker.sock:/var/run/docker.sock \ $(TEST_CONTAINER_NAME) \ - sh -c 'nosetests $(ST_TO_RUN) -sv --nologcapture --with-xunit --xunit-file="/code/report/nosetests.xml" --with-timer $(ST_OPTIONS)' + sh -c 'pytest $(ST_TO_RUN) -sv --junit-xml="/code/report/nosetests.xml" $(ST_OPTIONS)' $(MAKE) stop-etcd - $(MAKE) stop-kubernetes-master + $(MAKE) stop-kubernetes-control-plane -## Run a local kubernetes master with API +## Run a local kubernetes control plane. # TODO: Commonize this with the functions in lib.Makefile. # For now, these tests do something special and launch two apiserver on different ports, so it makes sense # to keep them separate. -run-kubernetes-master: stop-kubernetes-master +run-kubernetes-control-plane: stop-kubernetes-control-plane # Run a Kubernetes apiserver using Docker. docker run --net=host --detach \ --name st-apiserver-${KUBE_APISERVER_PORT} \ @@ -212,20 +211,20 @@ run-kubernetes-master: stop-kubernetes-master # Apply CRDs because the tests require them. while ! docker exec -ti st-apiserver-${KUBE_APISERVER_PORT} kubectl \ --server=https://127.0.0.1:${KUBE_APISERVER_PORT} \ - apply -f /go/src/github.com/projectcalico/calico/libcalico-go/config/crd; \ - do echo "Waiting for ClusterInformation CRD to apply successfully..."; sleep 2; done + apply -f /go/src/github.com/projectcalico/calico/$(CALICO_CRD_PATH); \ + do echo "Waiting for Calico CRDs to apply successfully..."; sleep 2; done # Create a namespace in the API for the tests to use. -docker exec -ti st-apiserver-${KUBE_APISERVER_PORT} kubectl \ --server=https://127.0.0.1:${KUBE_APISERVER_PORT} \ create namespace test -## Stop the local kubernetes master -stop-kubernetes-master: +## Stop the local kubernetes control plane. +stop-kubernetes-control-plane: # Delete the cluster role binding. -docker exec st-apiserver-${KUBE_APISERVER_PORT} kubectl delete clusterrolebinding anonymous-admin - # Stop master components. + # Stop control plane components. -docker rm -f st-apiserver-${KUBE_APISERVER_PORT} st-controller-manager-${KUBE_APISERVER_PORT} ############################################################################### diff --git a/calicoctl/calicoctl/calicoctl.go b/calicoctl/calicoctl/calicoctl.go index 78cbac8f199..f05f38e7f34 100644 --- a/calicoctl/calicoctl/calicoctl.go +++ b/calicoctl/calicoctl/calicoctl.go @@ -17,6 +17,7 @@ package main import ( "fmt" "os" + "regexp" "strings" "github.com/docopt/docopt-go" @@ -42,7 +43,6 @@ func main() { name. label Add or update labels of resources. validate Validate a resource by file, directory or stdin without applying it. - convert Convert config files between different API versions. ipam IP address management. node Calico node management. version Display the version of this binary. @@ -67,7 +67,7 @@ Description: // Replace all instances of BINARY_NAME with the name of the binary. doc = strings.ReplaceAll(doc, "", name) - var parser = &docopt.Parser{ + parser := &docopt.Parser{ HelpHandler: docopt.PrintHelpOnly, OptionsFirst: true, SkipHelpFlags: false, @@ -125,8 +125,6 @@ Description: err = commands.Label(args) case "validate": err = commands.Validate(args) - case "convert": - err = commands.Convert(args) case "version": err = commands.Version(args) case "node": @@ -142,8 +140,26 @@ Description: } if err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) + fmt.Fprintf(os.Stderr, "%s\n", massageError(err)) os.Exit(1) } } } + +// massageError takes the given error and tries to clean up its message for +// display. In particular, it removes confusing prefixes about JSON and +// tweaks the unknown field error to be more verbose. +func massageError(err error) string { + msg := err.Error() + + // Our YAML processing functions have intermediate steps that use JSON + // so the errors end up confusingly highlighting problems with JSON. + msg = strings.TrimPrefix(msg, "error unmarshaling JSON: while decoding JSON: json: ") + + unknownFiledRegexp := regexp.MustCompile(`unknown field "([^"]+)"`) + if m := unknownFiledRegexp.FindStringSubmatch(msg); m != nil { + msg = "field in document is not recognized or is in the wrong location: " + m[1] + } + + return msg +} diff --git a/calicoctl/calicoctl/commands/argutils/args.go b/calicoctl/calicoctl/commands/argutils/args.go index 9c112f6841a..f446270a27f 100644 --- a/calicoctl/calicoctl/commands/argutils/args.go +++ b/calicoctl/calicoctl/commands/argutils/args.go @@ -16,7 +16,7 @@ package argutils // ArgStringOrBlank returns the requested argument as a string, or as a blank // string if the argument is not present. -func ArgStringOrBlank(args map[string]interface{}, argName string) string { +func ArgStringOrBlank(args map[string]any, argName string) string { if args[argName] != nil { return args[argName].(string) } @@ -25,7 +25,7 @@ func ArgStringOrBlank(args map[string]interface{}, argName string) string { // ArgStringsOrBlank returns the requested argument as a []string, or as a // []string{""} if the argument is not present. -func ArgStringsOrBlank(args map[string]interface{}, argName string) []string { +func ArgStringsOrBlank(args map[string]any, argName string) []string { val := args[argName].([]string) if len(val) > 0 { return val @@ -35,7 +35,7 @@ func ArgStringsOrBlank(args map[string]interface{}, argName string) []string { // ArgBoolOrFalse returns the requested argument as a boolean, or as false // if the argument is not present. -func ArgBoolOrFalse(args map[string]interface{}, argName string) bool { +func ArgBoolOrFalse(args map[string]any, argName string) bool { if args[argName] != nil { return args[argName].(bool) } diff --git a/calicoctl/calicoctl/commands/cluster/diags.go b/calicoctl/calicoctl/commands/cluster/diags.go index f5b885e7df7..95e4eaecd95 100644 --- a/calicoctl/calicoctl/commands/cluster/diags.go +++ b/calicoctl/calicoctl/commands/cluster/diags.go @@ -18,6 +18,7 @@ import ( "context" "fmt" "os" + "path/filepath" "regexp" "strings" "time" @@ -174,6 +175,7 @@ func collectDiags(opts *diagOpts) error { collectSelectedNodeLogs(kubeClient, dir+"/nodes", dir+"/links", opts) } collectGlobalClusterInformation(dir + "/cluster") + collectUnsupportedAnnotations(tempDir, directoryName) createArchive(tempDir, directoryName, archiveName) return nil @@ -440,6 +442,14 @@ func collectKubernetesResource(dir string) { Info: "Collect k8s baselineadminnetworkpolicies (text)", CmdStr: "kubectl get baselineadminnetworkpolicies.policy.networking.k8s.io -Ao wide", FilePath: fmt.Sprintf("%s/baselineadminnetworkpolicies.txt", dir), + }, common.Cmd{ + Info: "Collect multus network-attachment-definitions (yaml)", + CmdStr: "kubectl get network-attachment-definitions.k8s.cni.cncf.io -Ao yaml", + FilePath: fmt.Sprintf("%s/network-attachment-definitions.yaml", dir), + }, common.Cmd{ + Info: "Collect multus network-attachment-definitions (text)", + CmdStr: "kubectl get network-attachment-definitions.k8s.cni.cncf.io -Ao wide", + FilePath: fmt.Sprintf("%s/network-attachment-definitions.txt", dir), }, common.Cmd{ Info: "Collect k8s validatingwebhookconfigurations (text)", CmdStr: "kubectl get validatingwebhookconfigurations.admissionregistration.k8s.io -o wide", @@ -758,6 +768,46 @@ func collectCalicoNodeDiags(curNodeDir string, nodeName, namespace, podName stri } } +func collectUnsupportedAnnotations(tempDir string, directoryName string) { + // Check for files containing unsupported.operator.tigera.io + var filesWithString []string + fullPath := fmt.Sprintf("%s/%s", tempDir, directoryName) + err := filepath.Walk(fullPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() { + content, err := os.ReadFile(path) + if err != nil { + return err + } + if strings.Contains(string(content), "unsupported.operator.tigera.io") { + relPath, err := filepath.Rel(fullPath, path) + if err != nil { + return err + } + filesWithString = append(filesWithString, relPath) + } + } + return nil + }) + if err != nil { + fmt.Printf("Error checking files for unsupported annotation: %s\n", err) + return + } + + // If files were found containing the string, write them to a file + if len(filesWithString) > 0 { + fmt.Println("\n==== WARNING: Unsupported annotation usage detected in the cluster ====") + content := strings.Join(filesWithString, "\n") + filePath := fmt.Sprintf("%s/%s/files_with_unsupported_annotation.txt", tempDir, directoryName) + err = os.WriteFile(filePath, []byte(content), 0644) + if err != nil { + fmt.Printf("Error writing list of files with unsupported annotation: %s\n", err) + } + } +} + // createArchive attempts to bundle all the diagnostics files into a single compressed archive. func createArchive(tempDir string, directoryName string, archiveName string) { fmt.Println("\n==== Producing a diagnostics bundle. ====") diff --git a/calicoctl/calicoctl/commands/commands_suite_test.go b/calicoctl/calicoctl/commands/commands_suite_test.go index 67d5aa0d4b9..8ad3201401e 100644 --- a/calicoctl/calicoctl/commands/commands_suite_test.go +++ b/calicoctl/calicoctl/commands/commands_suite_test.go @@ -5,13 +5,13 @@ package commands_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" ) func TestCommands(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/commands_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Commands Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/commands_suite.xml" + ginkgo.RunSpecs(t, "Commands Suite", suiteConfig, reporterConfig) } diff --git a/calicoctl/calicoctl/commands/common/common_suite_test.go b/calicoctl/calicoctl/commands/common/common_suite_test.go index 690b5549391..a161632bfff 100644 --- a/calicoctl/calicoctl/commands/common/common_suite_test.go +++ b/calicoctl/calicoctl/commands/common/common_suite_test.go @@ -3,11 +3,11 @@ package common_test import ( "testing" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" ) func TestCommon(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Common Suite") + gomega.RegisterFailHandler(ginkgo.Fail) + ginkgo.RunSpecs(t, "Common Suite") } diff --git a/calicoctl/calicoctl/commands/common/printer.go b/calicoctl/calicoctl/commands/common/printer.go index 6509679605e..9bcf8adb83d 100644 --- a/calicoctl/calicoctl/commands/common/printer.go +++ b/calicoctl/calicoctl/commands/common/printer.go @@ -17,7 +17,9 @@ package common import ( "bytes" "context" + "encoding/json" "fmt" + "io" "os" "reflect" "sort" @@ -25,10 +27,9 @@ import ( "text/tabwriter" "github.com/google/safetext/yamltemplate" - "github.com/projectcalico/go-json/json" - "github.com/projectcalico/go-yaml-wrapper" log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/yaml" "github.com/projectcalico/calico/calicoctl/calicoctl/resourcemgr" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" @@ -45,8 +46,12 @@ type ResourcePrinter interface { type ResourcePrinterJSON struct{} func (r ResourcePrinterJSON) Print(client client.Interface, resources []runtime.Object) error { + return r.FPrint(os.Stdout, client, resources) +} + +func (r ResourcePrinterJSON) FPrint(w io.Writer, client client.Interface, resources []runtime.Object) error { // If the results contain a single entry then extract the only value. - var rs interface{} + var rs any if len(resources) == 1 { rs = resources[0] } else { @@ -55,9 +60,9 @@ func (r ResourcePrinterJSON) Print(client client.Interface, resources []runtime. if output, err := json.MarshalIndent(rs, "", " "); err != nil { return err } else { - fmt.Printf("%s\n", string(output)) + _, err := w.Write(output) + return err } - return nil } // ResourcePrinterYAML implements the ResourcePrinter interface and is used to display @@ -65,8 +70,12 @@ func (r ResourcePrinterJSON) Print(client client.Interface, resources []runtime. type ResourcePrinterYAML struct{} func (r ResourcePrinterYAML) Print(client client.Interface, resources []runtime.Object) error { + return r.FPrint(os.Stdout, client, resources) +} + +func (r ResourcePrinterYAML) FPrint(w io.Writer, client client.Interface, resources []runtime.Object) error { // If the results contain a single entry then extract the only value. - var rs interface{} + var rs any if len(resources) == 1 { rs = resources[0] } else { @@ -75,9 +84,9 @@ func (r ResourcePrinterYAML) Print(client client.Interface, resources []runtime. if output, err := yaml.Marshal(rs); err != nil { return err } else { - fmt.Printf("%s", string(output)) + _, err := w.Write(output) + return err } - return nil } // ResourcePrinterTable implements the ResourcePrinter interface and is used to display @@ -186,14 +195,14 @@ func (r ResourcePrinterTemplate) Print(client client.Interface, resources []runt // join is similar to strings.Join() but takes an arbitrary slice of interfaces and converts // each to its string representation and joins them together with the provided separator // string. -func join(items interface{}, separator string) string { +func join(items any, separator string) string { return joinAndTruncate(items, separator, 0) } // joinAndTruncate is similar to strings.Join() but takes an arbitrary slice of interfaces and converts // each to its string representation, joins them together with the provided separator // string and (if maxLen is >0) truncates the output at the given maximum length. -func joinAndTruncate(items interface{}, separator string, maxLen int) string { +func joinAndTruncate(items any, separator string, maxLen int) string { // Nil types. if items == nil { return "" @@ -216,7 +225,7 @@ func joinAndTruncate(items interface{}, separator string, maxLen int) string { if reflect.TypeOf(items).Kind() != reflect.Slice { // Input wasn't a slice, convert it to one so we can take advantage of shared // buffer/truncation logic... - items = []interface{}{items} + items = []any{items} } slice := reflect.ValueOf(items) diff --git a/calicoctl/calicoctl/commands/common/printer_test.go b/calicoctl/calicoctl/commands/common/printer_test.go index 7b7767ddcab..702d4f58463 100644 --- a/calicoctl/calicoctl/commands/common/printer_test.go +++ b/calicoctl/calicoctl/commands/common/printer_test.go @@ -15,15 +15,16 @@ package common import ( + "bytes" "context" "errors" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "github.com/projectcalico/calico/libcalico-go/lib/clientv3" cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" @@ -34,11 +35,11 @@ var nilSlice []int var nilMap map[string]string var _ = DescribeTable("Testing joinAndTruncate", - func(items interface{}, separator string, maxLength int, expected string) { + func(items any, separator string, maxLength int, expected string) { result := joinAndTruncate(items, separator, maxLength) Expect(result).To(Equal(expected)) }, - Entry("nil interface", interface{}(nil), ",", 0, ""), + Entry("nil interface", any(nil), ",", 0, ""), Entry("nil map", nilMap, ",", 0, ""), Entry("empty map", make(map[string]string), ",", 0, ""), Entry("map with one kv", map[string]string{"a": "b"}, ",", 0, "a=b"), @@ -100,6 +101,67 @@ var _ = Describe("Testing printer config()", func() { }) }) +var _ = Describe("ResourcePrinterYAML tests", func() { + It("should omit zero fields with the omitzero tag", func() { + rp := ResourcePrinterYAML{} + var buf bytes.Buffer + gnp := apiv3.NewGlobalNetworkPolicy() + gnp.Name = "foo" + gnp.Spec.Ingress = []apiv3.Rule{ + { + Action: "Allow", + }, + } + err := rp.FPrint(&buf, nil, []runtime.Object{gnp}) + Expect(err).NotTo(HaveOccurred()) + + // The source/destination fields of apiv3.Rule use omitzero. + Expect(buf.String()).To(MatchYAML( + `apiVersion: projectcalico.org/v3 +kind: GlobalNetworkPolicy +metadata: + name: foo +spec: + ingress: + - action: Allow +`, + )) + }) +}) + +var _ = Describe("ResourcePrinterJSON tests", func() { + It("should omit zero fields with the omitzero tag", func() { + rp := ResourcePrinterJSON{} + var buf bytes.Buffer + gnp := apiv3.NewGlobalNetworkPolicy() + gnp.Name = "foo" + gnp.Spec.Ingress = []apiv3.Rule{ + { + Action: "Allow", + }, + } + err := rp.FPrint(&buf, nil, []runtime.Object{gnp}) + Expect(err).NotTo(HaveOccurred()) + + // The source/destination fields of apiv3.Rule use omitzero. + Expect(buf.String()).To(MatchJSON( + `{ + "apiVersion": "projectcalico.org/v3", + "kind": "GlobalNetworkPolicy", + "metadata": { + "name": "foo" + }, + "spec": { + "ingress":[ + {"action": "Allow"} + ] + } +} +`, + )) + }) +}) + type mockClient struct { clientv3.Interface clientv3.BGPConfigurationInterface diff --git a/calicoctl/calicoctl/commands/common/resources.go b/calicoctl/calicoctl/commands/common/resources.go index f4af09b7175..846c061c254 100644 --- a/calicoctl/calicoctl/commands/common/resources.go +++ b/calicoctl/calicoctl/commands/common/resources.go @@ -19,11 +19,11 @@ import ( "fmt" "os" - "github.com/projectcalico/go-yaml-wrapper" log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/api/meta" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/yaml" "github.com/projectcalico/calico/calicoctl/calicoctl/commands/argutils" "github.com/projectcalico/calico/calicoctl/calicoctl/commands/clientmgr" @@ -51,12 +51,12 @@ const ( // The loaded resources may be a slice containing resources and resource lists, or // may be a single resource or a single resource list. This function handles the // different possible options to convert to a single slice of resources. -func convertToSliceOfResources(loaded interface{}) ([]resourcemgr.ResourceObject, error) { +func convertToSliceOfResources(loaded any) ([]resourcemgr.ResourceObject, error) { res := []resourcemgr.ResourceObject{} log.Infof("Converting resource to slice: %v", loaded) switch r := loaded.(type) { case []runtime.Object: - for i := 0; i < len(r); i++ { + for i := range r { r, err := convertToSliceOfResources(r[i]) if err != nil { return nil, err @@ -124,7 +124,7 @@ type fileError struct { // - Convert the loaded resources into a list of resources (easier to handle) // - Process each resource individually, fanning out to the appropriate methods on // the client interface, collate results and exit on the first error. -func ExecuteConfigCommand(args map[string]interface{}, action action) CommandResults { +func ExecuteConfigCommand(args map[string]any, action action) CommandResults { var resources []resourcemgr.ResourceObject singleKind := false @@ -145,7 +145,7 @@ func ExecuteConfigCommand(args map[string]interface{}, action action) CommandRes // Filename is specified. Use the file iterator to handle the fact that this may be a directory rather than a // single file. For each file load the resources from the file and convert to a single slice of resources for // easier handling. - err := file.Iter(args, func(modifiedArgs map[string]interface{}) error { + err := file.Iter(args, func(modifiedArgs map[string]any) error { modifiedFilename := modifiedArgs["--filename"].(string) r, err := resourcemgr.CreateResourcesFromFile(modifiedFilename) @@ -307,7 +307,7 @@ func ExecuteConfigCommand(args map[string]interface{}, action action) CommandRes // ExecuteResourceAction fans out the specific resource action to the appropriate method // on the ResourceManager for the specific resource. -func ExecuteResourceAction(args map[string]interface{}, client client.Interface, resource resourcemgr.ResourceObject, action action) ([]runtime.Object, error) { +func ExecuteResourceAction(args map[string]any, client client.Interface, resource resourcemgr.ResourceObject, action action) ([]runtime.Object, error) { rm := resourcemgr.GetResourceManager(resource) err := handleNamespace(resource, rm, args) @@ -373,7 +373,7 @@ func tryEnsureInitialized(ctx context.Context, client client.Interface) { // handleNamespace fills in the namespace information in the resource (if required), // and validates the namespace depending on whether or not a namespace should be // provided based on the resource kind. -func handleNamespace(resource resourcemgr.ResourceObject, rm resourcemgr.ResourceManager, args map[string]interface{}) error { +func handleNamespace(resource resourcemgr.ResourceObject, rm resourcemgr.ResourceManager, args map[string]any) error { allNs := argutils.ArgBoolOrFalse(args, "--all-namespaces") cliNs := argutils.ArgStringOrBlank(args, "--namespace") resNs := resource.GetObjectMeta().GetNamespace() diff --git a/calicoctl/calicoctl/commands/common/version_mismatch.go b/calicoctl/calicoctl/commands/common/version_mismatch.go index 8e3080b2f80..3411b58ddf7 100644 --- a/calicoctl/calicoctl/commands/common/version_mismatch.go +++ b/calicoctl/calicoctl/commands/common/version_mismatch.go @@ -28,7 +28,7 @@ import ( "github.com/projectcalico/calico/pkg/buildinfo" ) -func CheckVersionMismatch(configArg, allowMismatchArg interface{}) error { +func CheckVersionMismatch(configArg, allowMismatchArg any) error { if allowMismatch, _ := allowMismatchArg.(bool); allowMismatch { log.Infof("Skip version mismatch checking due to '--allow-version-mismatch' argument") diff --git a/calicoctl/calicoctl/commands/convert.go b/calicoctl/calicoctl/commands/convert.go deleted file mode 100644 index 061bc93741e..00000000000 --- a/calicoctl/calicoctl/commands/convert.go +++ /dev/null @@ -1,298 +0,0 @@ -// Copyright (c) 2017-2024 Tigera, Inc. All rights reserved. - -// 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 commands - -import ( - "fmt" - "strings" - - "github.com/docopt/docopt-go" - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - log "github.com/sirupsen/logrus" - networkingv1 "k8s.io/api/networking/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - - "github.com/projectcalico/calico/calicoctl/calicoctl/commands/argutils" - "github.com/projectcalico/calico/calicoctl/calicoctl/commands/common" - "github.com/projectcalico/calico/calicoctl/calicoctl/commands/constants" - "github.com/projectcalico/calico/calicoctl/calicoctl/commands/resourceloader" - "github.com/projectcalico/calico/calicoctl/calicoctl/util" - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned" - cconversion "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" - "github.com/projectcalico/calico/libcalico-go/lib/names" - "github.com/projectcalico/calico/libcalico-go/lib/upgrade/converters" - validator "github.com/projectcalico/calico/libcalico-go/lib/validator/v3" -) - -func Convert(args []string) error { - doc := constants.DatastoreIntro + `Usage: - convert --filename= - [--output=] [--ignore-validation] [--allow-version-mismatch] - -Examples: - # Convert the contents of policy.yaml to a Calico v3 policy. - convert -f ./policy.yaml -o yaml - - # Convert a policy based on the JSON passed into stdin. - cat policy.json | convert -f - - -Options: - -h --help Show this screen. - -f --filename= Filename to use to create the resource. If set to - "-" loads from stdin. - -o --output= Output format. One of: yaml or json. - [Default: yaml] - --ignore-validation Skip validation on the converted manifest. - --allow-version-mismatch Allow client and cluster versions mismatch. - - -Description: - Convert config files from Calico v1 or Kubernetes to Calico v3 API versions. Both YAML and JSON formats are accepted. - - The default output will be printed to stdout in YAML format. -` - // Replace all instances of BINARY_NAME with the name of the binary. - name, _ := util.NameAndDescription() - doc = strings.ReplaceAll(doc, "", name) - - parsedArgs, err := docopt.ParseArgs(doc, args, "") - if err != nil { - return fmt.Errorf("invalid option: 'calicoctl %s'. Use flag '--help' to read about a specific subcommand", strings.Join(args, " ")) - } - if len(parsedArgs) == 0 { - return nil - } - - // Note: Intentionally not check version mismatch for this command - - var rp common.ResourcePrinter - output := parsedArgs["--output"].(string) - // Only supported output formats are yaml (default) and json. - switch output { - case "yaml", "yml": - rp = common.ResourcePrinterYAML{} - case "json": - rp = common.ResourcePrinterJSON{} - default: - return fmt.Errorf("unrecognized output format '%s'", output) - } - - filename := argutils.ArgStringOrBlank(parsedArgs, "--filename") - - // Load the resource from file and convert to a slice - // of resources for easier handling. - convRes, err := resourceloader.CreateResourcesFromFile(filename) - if err != nil { - return fmt.Errorf("failed to create resources from file: %w", err) - } - - // Unpack list resources (if any) into the slice - convRes, err = unpackResourceLists(convRes) - if err != nil { - return fmt.Errorf("failed to unpack lists: %w", err) - } - - var results []runtime.Object - - for _, convResource := range convRes { - v3Resource, err := convertResource(convResource) - if err != nil { - return fmt.Errorf("failed to convert resource: %w", err) - } - - // Remove any extra metadata the object might have. - rom := v3Resource.(v1.ObjectMetaAccessor).GetObjectMeta() - rom.SetUID("") - rom.SetResourceVersion("") - rom.SetCreationTimestamp(v1.Time{}) - rom.SetDeletionTimestamp(nil) - rom.SetDeletionGracePeriodSeconds(nil) - - ignoreValidation := argutils.ArgBoolOrFalse(parsedArgs, "--ignore-validation") - if !ignoreValidation { - if err := validator.Validate(v3Resource); err != nil { - return fmt.Errorf("converted manifest resource(s) failed validation: %s"+ - "Re-run the command with '--ignore-validation' flag to see the converted output", err) - } - } - - results = append(results, v3Resource) - } - - log.Infof("results: %+v", results) - - if len(results) > 1 { - results, err = createV1List(results) - if err != nil { - return fmt.Errorf("failed to create v1.List: %w", err) - } - } - - err = rp.Print(nil, results) - if err != nil { - return fmt.Errorf("failed to print results: %w", err) - } - - return nil -} - -// convertResource converts a k8s or a calico v1 resource into a calico v3 resource. -func convertResource(convResource unversioned.Resource) (converters.Resource, error) { - var res converters.Resource - - if strings.EqualFold(convResource.GetTypeMetadata().APIVersion, resourceloader.VersionK8sNetworkingV1) { - // Convert K8s resource to v3 (currently only NetworkPolicy is supported) - var err error - - res, err = convertK8sResource(convResource) - if err != nil { - return nil, err - } - } else { - // Get the type converter for the v1 resource. - convRes, err := getTypeConverter(convResource.GetTypeMetadata().Kind) - if err != nil { - return nil, err - } - - // Convert v1 API resource to v1 backend KVPair. - kvp, err := convRes.APIV1ToBackendV1(convResource) - if err != nil { - return nil, err - } - - // Convert v1 backend KVPair to v3 API resource. - res, err = convRes.BackendV1ToAPIV3(kvp) - if err != nil { - return nil, err - } - } - - return res, nil -} - -// Convert K8s resource to v3 (currently only NetworkPolicy is supported) -func convertK8sResource(convResource unversioned.Resource) (converters.Resource, error) { - var res converters.Resource - - k8sResKind := convResource.GetTypeMetadata().Kind - - switch strings.ToLower(k8sResKind) { - case "networkpolicy": - k8sNetworkPolicy, ok := convResource.(*resourceloader.K8sNetworkPolicy) - if !ok { - return nil, fmt.Errorf("failed to convert resource to K8sNetworkPolicy") - } - - np := networkingv1.NetworkPolicy{ - TypeMeta: k8sNetworkPolicy.TypeMeta, - ObjectMeta: k8sNetworkPolicy.ObjectMeta, - Spec: k8sNetworkPolicy.Spec, - } - c := cconversion.NewConverter() - - kvp, err := c.K8sNetworkPolicyToCalico(&np) - if err != nil { - return nil, fmt.Errorf("failed to convert k8s resource: %w", err) - } - - k8snp, ok := kvp.Value.(*apiv3.NetworkPolicy) - if !ok { - return nil, fmt.Errorf("failed to convert kvp to apiv3.NetworkPolicy") - } - - // Trim K8sNetworkPolicyNamePrefix from the policy name (the K8sNetworkPolicyToCalico - // function adds it for when it is used for coexisting calico/k8s policies). - k8snp.Name = strings.TrimPrefix(k8snp.Name, names.K8sNetworkPolicyNamePrefix) - res = k8snp - default: - return nil, fmt.Errorf("conversion for the k8s resource type '%s' is not supported", k8sResKind) - } - - return res, nil -} - -// getTypeConverter returns a type specific converter for a given v1 resource. -func getTypeConverter(resKind string) (converters.Converter, error) { - switch strings.ToLower(resKind) { - case "node": - return converters.Node{}, nil - case "hostendpoint": - return converters.HostEndpoint{}, nil - case "workloadendpoint": - return converters.WorkloadEndpoint{}, nil - case "profile": - return converters.Profile{}, nil - case "policy": - return converters.Policy{}, nil - case "tier": - return converters.Tier{}, nil - case "ippool": - return converters.IPPool{}, nil - case "bgppeer": - return converters.BGPPeer{}, nil - - default: - return nil, fmt.Errorf("conversion for the resource type '%s' is not supported", resKind) - } -} - -func unpackResourceLists(convRes []unversioned.Resource) ([]unversioned.Resource, error) { - var unpackedConvRes []unversioned.Resource - for _, convResource := range convRes { - if strings.EqualFold(convResource.GetTypeMetadata().Kind, resourceloader.KindK8sListV1) && strings.EqualFold(convResource.GetTypeMetadata().APIVersion, resourceloader.VersionK8sListV1) { - k8sNPList, ok := convResource.(*resourceloader.K8sNetworkPolicyList) - if !ok { - return nil, fmt.Errorf("failed to convert resource to K8sNetworkPolicyList") - } - - for _, item := range k8sNPList.Items { - // Append the items from the list to unpackedConvRes - i := item - unpackedConvRes = append(unpackedConvRes, &i) - } - } else { - unpackedConvRes = append(unpackedConvRes, convResource) - } - } - - return unpackedConvRes, nil -} - -func createV1List(results []runtime.Object) ([]runtime.Object, error) { - list := v1.List{ - TypeMeta: v1.TypeMeta{ - Kind: resourceloader.KindK8sListV1, - APIVersion: resourceloader.VersionK8sListV1, - }, - } - - for _, item := range results { - var rawExt runtime.RawExtension - - err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&item, &rawExt, nil) - if err != nil { - return nil, fmt.Errorf("failed to convert runtime.Object to runtime.RawExtension: %w", err) - } - - list.Items = append(list.Items, rawExt) - } - - var obj []runtime.Object - obj = append(obj, &list) - - return obj, nil -} diff --git a/calicoctl/calicoctl/commands/datastore/migrate/export.go b/calicoctl/calicoctl/commands/datastore/migrate/export.go index bd45f56ce41..7fda38c6d60 100644 --- a/calicoctl/calicoctl/commands/datastore/migrate/export.go +++ b/calicoctl/calicoctl/commands/datastore/migrate/export.go @@ -34,9 +34,9 @@ import ( "github.com/projectcalico/calico/calicoctl/calicoctl/commands/common" "github.com/projectcalico/calico/calicoctl/calicoctl/commands/constants" "github.com/projectcalico/calico/calicoctl/calicoctl/util" + "github.com/projectcalico/calico/kube-controllers/pkg/converter" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" - "github.com/projectcalico/calico/libcalico-go/lib/names" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" ) var title = cases.Title(language.English) @@ -120,11 +120,11 @@ Description: doc = strings.ReplaceAll(doc, "", name) // Replace with the list of resources that will be exported. - resourceList := "" + var resourceList strings.Builder for _, r := range allV3Resources { - resourceList += fmt.Sprintf(" - %s\n", resourceDisplayMap[r]) + resourceList.WriteString(fmt.Sprintf(" - %s\n", resourceDisplayMap[r])) } - doc = strings.Replace(doc, "", resourceList, 1) + doc = strings.Replace(doc, "", resourceList.String(), 1) parsedArgs, err := docopt.ParseArgs(doc, args, "") if err != nil { @@ -170,7 +170,7 @@ Description: etcdToKddNodeMap := make(map[string]string) // Loop through all the resource types to retrieve every resource available by the v3 API. for _, r := range allV3Resources { - mockArgs := map[string]interface{}{ + mockArgs := map[string]any{ "": r, "": []string{}, "--config": cf, @@ -186,14 +186,14 @@ Description: results := common.ExecuteConfigCommand(mockArgs, common.ActionGetOrList) if len(results.ResErrs) > 0 { - var errStr string + var errStr strings.Builder for i, err := range results.ResErrs { - errStr += err.Error() + errStr.WriteString(err.Error()) if (i + 1) != len(results.ResErrs) { - errStr += "\n" + errStr.WriteString("\n") } } - return errors.New(errStr) + return errors.New(errStr.String()) } for i, resource := range results.Resources { @@ -224,7 +224,7 @@ Description: if !ok { return fmt.Errorf("unable to convert Calico network policy for inspection") } - if !strings.HasPrefix(metaObj.GetObjectMeta().GetName(), names.K8sNetworkPolicyNamePrefix) { + if !strings.HasPrefix(metaObj.GetObjectMeta().GetName(), converter.KubernetesNetworkPolicyEtcdPrefix) { filtered = append(filtered, obj) } } @@ -236,38 +236,10 @@ Description: results.Resources[i] = resource } - // Skip exporting Kubernetes admin network policies. - if r == "globalnetworkpolicies" { - objs, err := meta.ExtractList(resource) - if err != nil { - return fmt.Errorf("error extracting global network policies for inspection before exporting: %s", err) - } - - filtered := []runtime.Object{} - for _, obj := range objs { - metaObj, ok := obj.(v1.ObjectMetaAccessor) - if !ok { - return fmt.Errorf("unable to convert Calico global network policy for inspection") - } - if strings.HasPrefix(metaObj.GetObjectMeta().GetName(), names.K8sAdminNetworkPolicyNamePrefix) || - strings.HasPrefix(metaObj.GetObjectMeta().GetName(), names.K8sBaselineAdminNetworkPolicyNamePrefix) { - continue - } - filtered = append(filtered, obj) - - } - - err = meta.SetList(resource, filtered) - if err != nil { - return fmt.Errorf("unable to remove Kubernetes admin network policies for export: %s", err) - } - results.Resources[i] = resource - } - // Nodes need to also be modified to move the Orchestrator reference to the name field. if r == "nodes" { err := meta.EachListItem(resource, func(obj runtime.Object) error { - node, ok := obj.(*libapiv3.Node) + node, ok := obj.(*internalapi.Node) if !ok { return fmt.Errorf("failed to convert resource to Node object for migration processing: %+v", obj) } @@ -302,8 +274,8 @@ Description: return fmt.Errorf("failed to convert resource to FelixConfiguration object for migration processing: %+v", obj) } - if strings.HasPrefix(felixConfig.GetObjectMeta().GetName(), "node.") { - etcdNodeName := strings.TrimPrefix(felixConfig.GetObjectMeta().GetName(), "node.") + if after, ok0 := strings.CutPrefix(felixConfig.GetObjectMeta().GetName(), "node."); ok0 { + etcdNodeName := after if nodename, ok := etcdToKddNodeMap[etcdNodeName]; ok { felixConfig.GetObjectMeta().SetName(fmt.Sprintf("node.%s", nodename)) } @@ -328,8 +300,8 @@ Description: return fmt.Errorf("failed to convert resource to BGPConfiguration object for migration processing: %+v", obj) } - if strings.HasPrefix(bgpConfig.GetObjectMeta().GetName(), "node.") { - etcdNodeName := strings.TrimPrefix(bgpConfig.GetObjectMeta().GetName(), "node.") + if after, ok0 := strings.CutPrefix(bgpConfig.GetObjectMeta().GetName(), "node."); ok0 { + etcdNodeName := after if nodename, ok := etcdToKddNodeMap[etcdNodeName]; ok { bgpConfig.GetObjectMeta().SetName(fmt.Sprintf("node.%s", nodename)) } @@ -354,7 +326,7 @@ Description: // Denote separation between the v3 resources and the cluster info resource which requires separate handling on import. fmt.Print("===\n") - mockArgs := map[string]interface{}{ + mockArgs := map[string]any{ "": "clusterinfos", "": "default", "--config": cf, @@ -378,14 +350,14 @@ Description: } if len(results.ResErrs) > 0 { - var errStr string + var errStr strings.Builder for i, err := range results.ResErrs { - errStr += err.Error() + errStr.WriteString(err.Error()) if (i + 1) != len(results.ResErrs) { - errStr += "\n" + errStr.WriteString("\n") } } - return errors.New(errStr) + return errors.New(errStr.String()) } // Denote separation between resources stored in YAML and the JSON IPAM resources. diff --git a/calicoctl/calicoctl/commands/datastore/migrate/export_test.go b/calicoctl/calicoctl/commands/datastore/migrate/export_test.go index b42c60b528a..7fc52fe469a 100644 --- a/calicoctl/calicoctl/commands/datastore/migrate/export_test.go +++ b/calicoctl/calicoctl/commands/datastore/migrate/export_test.go @@ -17,7 +17,7 @@ package migrate import ( "strings" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -79,22 +79,24 @@ var _ = Describe("Etcd to KDD Migration Export handling", func() { allPlurals.Discard("profiles") // WEPs are backed by Pods in KDD. allPlurals.Discard("workloadendpoints") + // LiveMigrations are backed by KubeVirt VirtualMachineInstanceMigration in KDD. + allPlurals.Discard("livemigrations") // ClusterInformation is generated fresh in the new cluster. allPlurals.Discard("clusterinformations") // Not supported in KDD (OpenStack only). allPlurals.Discard("caliconodestatuses") // Handled by IPAM migration code. allPlurals.Discard("ipamconfigs") + allPlurals.Discard("ipamconfigurations") allPlurals.Discard("blockaffinities") - allPlurals.Iter(func(resource string) error { + for resource := range allPlurals.All() { if strings.HasPrefix(resource, "kubernetes") { // "kubernetes"-prefixed resources are backed by Kubernetes API // objects, not Calico objects. - return set.RemoveItem + allPlurals.Discard(resource) } - return nil - }) + } Expect(allV3Resources).To(ConsistOf(allPlurals.Slice())) }) diff --git a/calicoctl/calicoctl/commands/datastore/migrate/import.go b/calicoctl/calicoctl/commands/datastore/migrate/import.go index 999847db910..665bd5b307b 100644 --- a/calicoctl/calicoctl/commands/datastore/migrate/import.go +++ b/calicoctl/calicoctl/commands/datastore/migrate/import.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2024 Tigera, Inc. All rights reserved. +// Copyright (c) 2020-2025 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,25 +24,25 @@ import ( "github.com/docopt/docopt-go" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - yaml "github.com/projectcalico/go-yaml-wrapper" log "github.com/sirupsen/logrus" "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/yaml" "github.com/projectcalico/calico/calicoctl/calicoctl/commands/clientmgr" "github.com/projectcalico/calico/calicoctl/calicoctl/commands/common" "github.com/projectcalico/calico/calicoctl/calicoctl/commands/constants" "github.com/projectcalico/calico/calicoctl/calicoctl/util" + "github.com/projectcalico/calico/kube-controllers/pkg/converter" lcconfig "github.com/projectcalico/calico/libcalico-go/config" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" calicoErrors "github.com/projectcalico/calico/libcalico-go/lib/errors" - "github.com/projectcalico/calico/libcalico-go/lib/names" "github.com/projectcalico/calico/libcalico-go/lib/options" ) @@ -202,7 +202,7 @@ func splitImportFile(filename string) ([]byte, []byte, []byte, error) { return split[0], split[1], split[2], nil } -func checkCalicoResourcesNotExist(args map[string]interface{}, c client.Interface) error { +func checkCalicoResourcesNotExist(args map[string]any, c client.Interface) error { // Loop through all the v3 resources to see if anything is returned extendedV3Resources := append(allV3Resources, "clusterinfo") for _, r := range extendedV3Resources { @@ -212,7 +212,7 @@ func checkCalicoResourcesNotExist(args map[string]interface{}, c client.Interfac } // Create mocked args in order to retrieve Get resources. - mockArgs := map[string]interface{}{ + mockArgs := map[string]any{ "": r, "": []string{}, "--config": args["--config"].(string), @@ -246,25 +246,7 @@ func checkCalicoResourcesNotExist(args map[string]interface{}, c client.Interfac } // Make sure that the network policy is a K8s network policy - if !strings.HasPrefix(metaObj.GetObjectMeta().GetName(), names.K8sNetworkPolicyNamePrefix) { - return fmt.Errorf("found existing Calico %s resource", results.SingleKind) - } - } - case "globalnetworkpolicies": - // For globalnetworkpolicies, having K8s admin network policies should not throw an error - objs, err := meta.ExtractList(resource) - if err != nil { - return fmt.Errorf("error extracting global network policies for inspection: %s", err) - } - - for _, obj := range objs { - metaObj, ok := obj.(v1.ObjectMetaAccessor) - if !ok { - return fmt.Errorf("unable to convert Calico global network policy for inspection") - } - - // Make sure that the global network policy is a K8s admin network policy - if !strings.HasPrefix(metaObj.GetObjectMeta().GetName(), names.K8sAdminNetworkPolicyNamePrefix) { + if !strings.HasPrefix(metaObj.GetObjectMeta().GetName(), converter.KubernetesNetworkPolicyEtcdPrefix) { return fmt.Errorf("found existing Calico %s resource", results.SingleKind) } } @@ -348,7 +330,7 @@ func updateV3Resources(cfg *apiconfig.CalicoAPIConfig, data []byte) error { return fmt.Errorf("error while writing to temporary v3 migration config file: %s", err) } - mockArgs := map[string]interface{}{ + mockArgs := map[string]any{ "--config": tempConfigFile.Name(), "--filename": tempfile.Name(), "apply": true, @@ -411,7 +393,7 @@ func importCRDs(cfg *apiconfig.CalicoAPIConfig) error { return nil } -func applyV3(args map[string]interface{}) error { +func applyV3(args map[string]any) error { results := common.ExecuteConfigCommand(args, common.ActionApply) log.Infof("results: %+v", results) @@ -433,7 +415,7 @@ func applyV3(args map[string]interface{}) error { case calicoErrors.ErrorResourceDoesNotExist: // Check that the error is for a Node if key, ok := e.Identifier.(model.ResourceKey); ok { - if key.Kind == libapiv3.KindNode { + if key.Kind == internalapi.KindNode { fmt.Printf("[WARNING] Attempted to import node %v from etcd that references a nonexistent Kubernetes node. Skipping that node. Non-Kubernetes nodes are not supported in the Kubernetes datastore and will be skipped.", e.Identifier) continue } diff --git a/calicoctl/calicoctl/commands/datastore/migrate/migrate_suite_test.go b/calicoctl/calicoctl/commands/datastore/migrate/migrate_suite_test.go index d649ca94b7a..9f0e8da0b81 100644 --- a/calicoctl/calicoctl/commands/datastore/migrate/migrate_suite_test.go +++ b/calicoctl/calicoctl/commands/datastore/migrate/migrate_suite_test.go @@ -17,13 +17,13 @@ package migrate import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" ) func TestCommands(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/migrate_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Migrate Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/migrate_suite.xml" + ginkgo.RunSpecs(t, "Migrate Suite", suiteConfig, reporterConfig) } diff --git a/calicoctl/calicoctl/commands/datastore/migrate/migrateipam.go b/calicoctl/calicoctl/commands/datastore/migrate/migrateipam.go index 6f4fba88b5b..c1c68266012 100644 --- a/calicoctl/calicoctl/commands/datastore/migrate/migrateipam.go +++ b/calicoctl/calicoctl/commands/datastore/migrate/migrateipam.go @@ -133,18 +133,18 @@ func (m *migrateIPAM) PullFromDatastore() error { if m.nodeMap != nil { for i, allocationAttribute := range block.Attributes { // Update the node name if it has a corresponding Kubernetes node name - if nodeName, ok := m.nodeMap[allocationAttribute.AttrSecondary["node"]]; ok { - block.Attributes[i].AttrSecondary["node"] = nodeName + if nodeName, ok := m.nodeMap[allocationAttribute.ActiveOwnerAttrs["node"]]; ok { + block.Attributes[i].ActiveOwnerAttrs["node"] = nodeName } // Update the handle ID for any tunnel addresses - if allocationAttribute.AttrPrimary != nil { + if allocationAttribute.HandleID != nil { for _, handlePrefix := range ipamHandlePrefixes { - if strings.HasPrefix(*allocationAttribute.AttrPrimary, handlePrefix) { - etcdNodeName := strings.TrimPrefix(*allocationAttribute.AttrPrimary, handlePrefix) + if after, ok0 := strings.CutPrefix(*allocationAttribute.HandleID, handlePrefix); ok0 { + etcdNodeName := after if nodeName, ok := m.nodeMap[etcdNodeName]; ok { handleID := fmt.Sprintf("%s%s", handlePrefix, nodeName) - block.Attributes[i].AttrPrimary = &handleID + block.Attributes[i].HandleID = &handleID } } } @@ -205,8 +205,8 @@ func (m *migrateIPAM) PullFromDatastore() error { return fmt.Errorf("unable to convert %+v to an IPAMHandleKey", item.Key) } for _, handlePrefix := range ipamHandlePrefixes { - if strings.HasPrefix(key.HandleID, handlePrefix) { - etcdNodeName := strings.TrimPrefix(key.HandleID, handlePrefix) + if after, ok0 := strings.CutPrefix(key.HandleID, handlePrefix); ok0 { + etcdNodeName := after if nodeName, ok := m.nodeMap[etcdNodeName]; ok { key.HandleID = fmt.Sprintf("%s%s", handlePrefix, nodeName) } diff --git a/calicoctl/calicoctl/commands/datastore/migrate/migrateipam_test.go b/calicoctl/calicoctl/commands/datastore/migrate/migrateipam_test.go index b6b73b45008..f856d8f3895 100644 --- a/calicoctl/calicoctl/commands/datastore/migrate/migrateipam_test.go +++ b/calicoctl/calicoctl/commands/datastore/migrate/migrateipam_test.go @@ -18,7 +18,7 @@ import ( "context" "fmt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" @@ -51,8 +51,8 @@ var _ = Describe("IPAM migration handling", func() { Affinity: &blockAffinityField, Attributes: []model.AllocationAttribute{ { - AttrPrimary: &ipipTunnelHandle, - AttrSecondary: map[string]string{ + HandleID: &ipipTunnelHandle, + ActiveOwnerAttrs: map[string]string{ "node": nodeName, "type": "ipipTunnelAddress", }, @@ -108,8 +108,8 @@ var _ = Describe("IPAM migration handling", func() { Expect(migrateIPAM.IPAMBlocks).To(HaveLen(1)) Expect(*migrateIPAM.IPAMBlocks[0].Value.Affinity).To(Equal(fmt.Sprintf("host:%s", newNodeName))) Expect(migrateIPAM.IPAMBlocks[0].Value.Attributes).To(HaveLen(1)) - Expect(*migrateIPAM.IPAMBlocks[0].Value.Attributes[0].AttrPrimary).To(Equal(fmt.Sprintf("ipip-tunnel-addr-%s", newNodeName))) - Expect(migrateIPAM.IPAMBlocks[0].Value.Attributes[0].AttrSecondary["node"]).To(Equal(newNodeName)) + Expect(*migrateIPAM.IPAMBlocks[0].Value.Attributes[0].HandleID).To(Equal(fmt.Sprintf("ipip-tunnel-addr-%s", newNodeName))) + Expect(migrateIPAM.IPAMBlocks[0].Value.Attributes[0].ActiveOwnerAttrs["node"]).To(Equal(newNodeName)) // Check that the block affinity attributes were changed correctly newAffinityKey := model.BlockAffinityKey{ @@ -154,8 +154,8 @@ var _ = Describe("IPAM migration handling", func() { Expect(migrateIPAM.IPAMBlocks).To(HaveLen(1)) Expect(*migrateIPAM.IPAMBlocks[0].Value.Affinity).To(Equal(fmt.Sprintf("host:%s", nodeName))) Expect(migrateIPAM.IPAMBlocks[0].Value.Attributes).To(HaveLen(1)) - Expect(*migrateIPAM.IPAMBlocks[0].Value.Attributes[0].AttrPrimary).To(Equal(fmt.Sprintf("ipip-tunnel-addr-%s", nodeName))) - Expect(migrateIPAM.IPAMBlocks[0].Value.Attributes[0].AttrSecondary["node"]).To(Equal(nodeName)) + Expect(*migrateIPAM.IPAMBlocks[0].Value.Attributes[0].HandleID).To(Equal(fmt.Sprintf("ipip-tunnel-addr-%s", nodeName))) + Expect(migrateIPAM.IPAMBlocks[0].Value.Attributes[0].ActiveOwnerAttrs["node"]).To(Equal(nodeName)) // Check that the block affinity attributes were not changed newAffinityKeyPath, err := model.KeyToDefaultPath(affinity1.Key) @@ -248,6 +248,11 @@ func (c *MockIPAMClient) HostEndpoints() client.HostEndpointInterface { return nil } +func (c *MockIPAMClient) LiveMigrations() client.LiveMigrationInterface { + // DO NOTHING + return nil +} + func (c *MockIPAMClient) WorkloadEndpoints() client.WorkloadEndpointInterface { // DO NOTHING return nil @@ -293,7 +298,7 @@ func (c *MockIPAMClient) CalicoNodeStatus() client.CalicoNodeStatusInterface { return nil } -func (c *MockIPAMClient) IPAMConfig() client.IPAMConfigInterface { +func (c *MockIPAMClient) IPAMConfiguration() client.IPAMConfigurationInterface { // DO NOTHING return nil } diff --git a/calicoctl/calicoctl/commands/delete.go b/calicoctl/calicoctl/commands/delete.go index ab7f82fc6fb..355b0597640 100644 --- a/calicoctl/calicoctl/commands/delete.go +++ b/calicoctl/calicoctl/commands/delete.go @@ -131,15 +131,15 @@ Description: } if len(results.ResErrs) > 0 { - var errStr string + var errStr strings.Builder for _, err := range results.ResErrs { if results.SingleKind != "" { - errStr += fmt.Sprintf("Failed to delete '%s' resource: %v\n", results.SingleKind, err) + errStr.WriteString(fmt.Sprintf("Failed to delete '%s' resource: %v\n", results.SingleKind, err)) } else { - errStr += fmt.Sprintf("Failed to delete resource: %v\n", err) + errStr.WriteString(fmt.Sprintf("Failed to delete resource: %v\n", err)) } } - return errors.New(errStr) + return errors.New(errStr.String()) } return nil diff --git a/calicoctl/calicoctl/commands/file/file_suite_test.go b/calicoctl/calicoctl/commands/file/file_suite_test.go index af7e09beb96..60550ffd70b 100644 --- a/calicoctl/calicoctl/commands/file/file_suite_test.go +++ b/calicoctl/calicoctl/commands/file/file_suite_test.go @@ -17,9 +17,8 @@ package file_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestCommands(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../report/file_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "File Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../report/file_suite.xml" + ginkgo.RunSpecs(t, "File Suite", suiteConfig, reporterConfig) } diff --git a/calicoctl/calicoctl/commands/file/iter.go b/calicoctl/calicoctl/commands/file/iter.go index 4f38d05a24c..f72d85daed9 100644 --- a/calicoctl/calicoctl/commands/file/iter.go +++ b/calicoctl/calicoctl/commands/file/iter.go @@ -15,6 +15,7 @@ package file import ( + "maps" "os" "path/filepath" "strings" @@ -26,7 +27,7 @@ import ( // - invokes the callback for each manifest files in the directory if the filename in the parsed arguments is a // directory (updating the arguments to include the specific file) // - otherwise just invoke the callback with the unmodified arguments. -func Iter(parsedArgs map[string]interface{}, cb func(map[string]interface{}) error) error { +func Iter(parsedArgs map[string]any, cb func(map[string]any) error) error { // File name is specified. f, ok := parsedArgs["--filename"].(string) if !ok { @@ -84,11 +85,9 @@ func isDir(f string) bool { } // newParsedArgs returns an updated set of arguments which include the specified filename. -func newParsedArgs(original map[string]interface{}, newFilename string) map[string]interface{} { - out := make(map[string]interface{}) - for k, v := range original { - out[k] = v - } +func newParsedArgs(original map[string]any, newFilename string) map[string]any { + out := make(map[string]any) + maps.Copy(out, original) out["--filename"] = newFilename return out } diff --git a/calicoctl/calicoctl/commands/file/iter_test.go b/calicoctl/calicoctl/commands/file/iter_test.go index 2501a6595e7..2d577841943 100644 --- a/calicoctl/calicoctl/commands/file/iter_test.go +++ b/calicoctl/calicoctl/commands/file/iter_test.go @@ -19,7 +19,7 @@ import ( "os" "strings" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/calicoctl/calicoctl/commands/file" @@ -31,7 +31,7 @@ var _ = Describe("File and directory iteration", func() { var dname string var dfname1, dfname2, dfname3 string var df2name1, df2name2, df2name3 string - var invocations []map[string]interface{} + var invocations []map[string]any var testError error BeforeEach(func() { @@ -116,7 +116,7 @@ var _ = Describe("File and directory iteration", func() { }) runTest := func(filename string, recursive bool, expected []string) error { - args := map[string]interface{}{ + args := map[string]any{ "--abc": 1, "--def": "hello", } @@ -127,7 +127,7 @@ var _ = Describe("File and directory iteration", func() { args["--recursive"] = true } - err := file.Iter(args, func(updated map[string]interface{}) error { + err := file.Iter(args, func(updated map[string]any) error { invocations = append(invocations, updated) return testError }) diff --git a/calicoctl/calicoctl/commands/get.go b/calicoctl/calicoctl/commands/get.go index 75bdc121809..5617582324e 100644 --- a/calicoctl/calicoctl/commands/get.go +++ b/calicoctl/calicoctl/commands/get.go @@ -200,14 +200,14 @@ Description: } if len(results.ResErrs) > 0 { - var errStr string + var errStr strings.Builder for i, err := range results.ResErrs { - errStr += err.Error() + errStr.WriteString(err.Error()) if (i + 1) != len(results.ResErrs) { - errStr += "\n" + errStr.WriteString("\n") } } - return errors.New(errStr) + return errors.New(errStr.String()) } return nil diff --git a/calicoctl/calicoctl/commands/ipam/check.go b/calicoctl/calicoctl/commands/ipam/check.go index ab717be0c5b..0e5f716b15b 100644 --- a/calicoctl/calicoctl/commands/ipam/check.go +++ b/calicoctl/calicoctl/commands/ipam/check.go @@ -35,7 +35,7 @@ import ( "github.com/projectcalico/calico/calicoctl/calicoctl/commands/constants" "github.com/projectcalico/calico/calicoctl/calicoctl/util" "github.com/projectcalico/calico/kube-controllers/pkg/controllers/loadbalancer" - apiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" @@ -144,7 +144,8 @@ func NewIPAMChecker(k8sClient kubernetes.Interface, showAllIPs bool, showProblemIPs bool, outFile string, - version string) *IPAMChecker { + version string, +) *IPAMChecker { return &IPAMChecker{ allocations: map[string][]*Allocation{}, allocationsByNode: map[string][]*Allocation{}, @@ -453,16 +454,15 @@ func (c *IPAMChecker) checkIPAM(ctx context.Context) error { var missingHandles []string { fmt.Printf("Scanning for IPs with missing handle...\n") - c.inUseHandles.Iter(func(handleID string) error { + for handleID := range c.inUseHandles.All() { if _, ok := handles[handleID]; ok { - return nil + continue } if c.showProblemIPs { fmt.Printf(" %s is in use in a block but doesn't exist.\n", handleID) } missingHandles = append(missingHandles, handleID) - return nil - }) + } fmt.Printf("Found %d handles mentioned in blocks with no matching handle resource.\n", len(missingHandles)) } @@ -475,7 +475,7 @@ func (c *IPAMChecker) checkIPAM(ctx context.Context) error { return nil } -func getWEPIPs(w apiv3.WorkloadEndpoint) ([]string, error) { +func getWEPIPs(w internalapi.WorkloadEndpoint) ([]string, error) { var ips []string for _, a := range w.Spec.IPNetworks { ip, err := normaliseIP(a) @@ -514,7 +514,7 @@ func (c *IPAMChecker) printReport() { LeakedHandles: c.leakedHandles, } bytes, _ := json.MarshalIndent(r, "", " ") - _ = os.WriteFile(c.outFile, bytes, 0777) + _ = os.WriteFile(c.outFile, bytes, 0o777) } // recordAllocation takes a block and ordinal within that block and updates @@ -536,25 +536,25 @@ func (c *IPAMChecker) recordAllocation(b *model.AllocationBlock, ord int) { attrIdx := *b.Allocations[ord] if len(b.Attributes) > attrIdx { attrs := b.Attributes[attrIdx] - if attrs.AttrPrimary != nil && *attrs.AttrPrimary == ipam.WindowsReservedHandle { + if attrs.HandleID != nil && *attrs.HandleID == ipam.WindowsReservedHandle { c.recordInUseIP(ip, b, "Reserved for Windows") - } else if attrs.AttrPrimary != nil { - alloc.Handle = *attrs.AttrPrimary + } else if attrs.HandleID != nil { + alloc.Handle = *attrs.HandleID c.recordInUseHandle(alloc.Handle) } - if n := attrs.AttrSecondary["node"]; n != "" { + if n := attrs.ActiveOwnerAttrs["node"]; n != "" { node = n } - if p := attrs.AttrSecondary["pod"]; p != "" { + if p := attrs.ActiveOwnerAttrs["pod"]; p != "" { alloc.Pod = p } - if n := attrs.AttrSecondary["namespace"]; n != "" { + if n := attrs.ActiveOwnerAttrs["namespace"]; n != "" { alloc.Namespace = n } - if t := attrs.AttrSecondary["type"]; t != "" { + if t := attrs.ActiveOwnerAttrs["type"]; t != "" { alloc.Type = t } - if t := attrs.AttrSecondary["timestamp"]; t != "" { + if t := attrs.ActiveOwnerAttrs["timestamp"]; t != "" { alloc.CreationTimestamp = t } } @@ -585,7 +585,7 @@ func (c *IPAMChecker) recordAllocation(b *model.AllocationBlock, ord int) { } // recordInUseIP records that the given IP is currently being used by the given resource (i.e., pod, node, etc). -func (c *IPAMChecker) recordInUseIP(ip string, referrer interface{}, friendlyName string) { +func (c *IPAMChecker) recordInUseIP(ip string, referrer any, friendlyName string) { if c.showAllIPs { fmt.Printf(" %s belongs to %s\n", ip, friendlyName) } @@ -606,7 +606,7 @@ func (c *IPAMChecker) recordInUseHandle(handle string) { c.inUseHandles.Add(handle) } -func getNodeIPs(n apiv3.Node) ([]string, error) { +func getNodeIPs(n internalapi.Node) ([]string, error) { var ips []string if n.Spec.IPv4VXLANTunnelAddr != "" { ip, err := normaliseIP(n.Spec.IPv4VXLANTunnelAddr) @@ -690,22 +690,22 @@ type HandleInfo struct { func formatAttrs(attribute model.AllocationAttribute) string { primary := "" - if attribute.AttrPrimary != nil { - primary = *attribute.AttrPrimary + if attribute.HandleID != nil { + primary = *attribute.HandleID } var keys []string - for k := range attribute.AttrSecondary { + for k := range attribute.ActiveOwnerAttrs { keys = append(keys, k) } sort.Strings(keys) var kvs []string for _, k := range keys { - kvs = append(kvs, fmt.Sprintf("%s=%s", k, attribute.AttrSecondary[k])) + kvs = append(kvs, fmt.Sprintf("%s=%s", k, attribute.ActiveOwnerAttrs[k])) } return fmt.Sprintf("Main:%s Extra:%s", primary, strings.Join(kvs, ",")) } type ownerRecord struct { FriendlyName string - Resource interface{} + Resource any } diff --git a/calicoctl/calicoctl/commands/ipam/show.go b/calicoctl/calicoctl/commands/ipam/show.go index 0e6a1e3b50d..fbf11b4d22a 100644 --- a/calicoctl/calicoctl/commands/ipam/show.go +++ b/calicoctl/calicoctl/commands/ipam/show.go @@ -55,7 +55,7 @@ func getBorrowedIPs(ctx context.Context, ippoolClient clientv3.IPPoolInterface, return nil, 0, err } - // For really old IP allocations, AttrSecondary[model.IPAMBlockAttributeNode] used to be not set. + // For really old IP allocations, ActiveOwnerAttrs[model.IPAMBlockAttributeNode] used to be not set. // Count such IP addresses, and, if any, warn customer as we are unable to classify those as // borrowed or not. unclassifiedIPs := 0 @@ -87,16 +87,16 @@ func getBorrowedIPs(ctx context.Context, ippoolClient clientv3.IPPoolInterface, } } - if borrowingNode, ok := attributes.AttrSecondary[model.IPAMBlockAttributeNode]; ok { + if borrowingNode, ok := attributes.ActiveOwnerAttrs[model.IPAMBlockAttributeNode]; ok { if blockOwner != borrowingNode { bIP := borrowedIP{block: b.CIDR.IPNet.String(), blockOwner: blockOwner, borrowingNode: borrowingNode} bIP.addr = b.OrdinalToIP(i).String() - if _, ok := attributes.AttrSecondary[model.IPAMBlockAttributePod]; ok { - bIP.allocatedTo = fmt.Sprintf("%s/%s", attributes.AttrSecondary[model.IPAMBlockAttributeNamespace], - attributes.AttrSecondary[model.IPAMBlockAttributePod]) + if _, ok := attributes.ActiveOwnerAttrs[model.IPAMBlockAttributePod]; ok { + bIP.allocatedTo = fmt.Sprintf("%s/%s", attributes.ActiveOwnerAttrs[model.IPAMBlockAttributeNamespace], + attributes.ActiveOwnerAttrs[model.IPAMBlockAttributePod]) bIP.allocationType = model.IPAMBlockAttributePod - } else if _, ok := attributes.AttrSecondary[model.IPAMBlockAttributeType]; ok { - bIP.allocationType = attributes.AttrSecondary[model.IPAMBlockAttributeType] + } else if _, ok := attributes.ActiveOwnerAttrs[model.IPAMBlockAttributeType]; ok { + bIP.allocationType = attributes.ActiveOwnerAttrs[model.IPAMBlockAttributeType] } details = append(details, &bIP) } @@ -143,9 +143,9 @@ func showBorrowedDetails(ctx context.Context, ippoolClient clientv3.IPPoolInterf return nil } -func showIP(ctx context.Context, ipamClient ipam.Interface, passedIP interface{}) error { +func showIP(ctx context.Context, ipamClient ipam.Interface, passedIP any) error { ip := argutils.ValidateIP(passedIP.(string)) - attr, _, err := ipamClient.GetAssignmentAttributes(ctx, ip) + allocAttr, err := ipamClient.GetAssignmentAttributes(ctx, ip) if err != nil { if _, ok := err.(cerrors.ErrorResourceDoesNotExist); ok { // IP address is not assigned. The detailed error message here is either @@ -160,6 +160,7 @@ func showIP(ctx context.Context, ipamClient ipam.Interface, passedIP interface{} // IP address is assigned. fmt.Printf("IP %s is in use\n", ip) + attr := allocAttr.ActiveOwnerAttrs if len(attr) != 0 { fmt.Println("Attributes:") for k, v := range attr { @@ -221,7 +222,7 @@ func showConfiguration(ctx context.Context, ipamClient ipam.Interface) error { table := tablewriter.NewWriter(os.Stdout) table.SetHeader([]string{"PROPERTY", "VALUE"}) - genRow := func(name string, value interface{}) []string { + genRow := func(name string, value any) []string { return []string{ name, fmt.Sprintf("%#v", value), diff --git a/calicoctl/calicoctl/commands/ipam/split.go b/calicoctl/calicoctl/commands/ipam/split.go index 28c3a3dd4f4..18f6a6ea9a8 100644 --- a/calicoctl/calicoctl/commands/ipam/split.go +++ b/calicoctl/calicoctl/commands/ipam/split.go @@ -231,7 +231,7 @@ func splitCIDR(oldCIDR string, parts int) ([]string, error) { // Create the child CIDRs splitCIDRs := make([]string, parts) - for i := 0; i < parts; i++ { + for i := range parts { splitCIDR := cnet.IPNet{ IPNet: net.IPNet{ IP: maskedIP.IP, diff --git a/calicoctl/calicoctl/commands/node/node_suite_test.go b/calicoctl/calicoctl/commands/node/node_suite_test.go index bc96505bf6c..053078ca5be 100644 --- a/calicoctl/calicoctl/commands/node/node_suite_test.go +++ b/calicoctl/calicoctl/commands/node/node_suite_test.go @@ -5,9 +5,8 @@ package node_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -17,7 +16,8 @@ func init() { } func TestCommands(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../report/node_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Node Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../report/node_suite.xml" + ginkgo.RunSpecs(t, "Node Suite", suiteConfig, reporterConfig) } diff --git a/calicoctl/calicoctl/commands/node/run.go b/calicoctl/calicoctl/commands/node/run.go index 5a93dbb725c..76ff92809dc 100644 --- a/calicoctl/calicoctl/commands/node/run.go +++ b/calicoctl/calicoctl/commands/node/run.go @@ -371,11 +371,12 @@ Description: // unable to find image message. runCmd := exec.Command(cmd[0], cmd[1:]...) if output, err := runCmd.CombinedOutput(); err != nil { - errStr := fmt.Sprintf("Error executing command: %v\n", err) - for _, line := range strings.Split(string(output), "/n") { - errStr += fmt.Sprintf(" | %s/n", line) + var errStr strings.Builder + errStr.WriteString(fmt.Sprintf("Error executing command: %v\n", err)) + for line := range strings.SplitSeq(string(output), "/n") { + errStr.WriteString(fmt.Sprintf(" | %s/n", line)) } - return errors.New(errStr) + return errors.New(errStr.String()) } // Create the command to follow the docker logs for the calico/node @@ -493,10 +494,10 @@ func validateIpAutodetectionMethod(method string, version int) error { // Auto-detection method is "first-found", no additional validation // required. return nil - } else if strings.HasPrefix(method, AUTODETECTION_METHOD_CAN_REACH) { + } else if after, ok := strings.CutPrefix(method, AUTODETECTION_METHOD_CAN_REACH); ok { // Auto-detection method is "can-reach", validate that the address // resolves to at least one IP address of the required version. - addrStr := strings.TrimPrefix(method, AUTODETECTION_METHOD_CAN_REACH) + addrStr := after ips, err := gonet.LookupIP(addrStr) if err != nil { return fmt.Errorf("error executing command: cannot resolve address specified for IP autodetection: %s", addrStr) @@ -509,27 +510,27 @@ func validateIpAutodetectionMethod(method string, version int) error { } } return fmt.Errorf("error executing command: address for IP autodetection does not resolve to an IPv%d address: %s", version, addrStr) - } else if strings.HasPrefix(method, AUTODETECTION_METHOD_INTERFACE) { + } else if after, ok := strings.CutPrefix(method, AUTODETECTION_METHOD_INTERFACE); ok { // Auto-detection method is "interface", validate that the interface // regex is a valid golang regex. - ifStr := strings.TrimPrefix(method, AUTODETECTION_METHOD_INTERFACE) + ifStr := after // Regexes are provided in a string separated by "," - ifRegexes := strings.Split(ifStr, ",") - for _, ifRegex := range ifRegexes { + ifRegexes := strings.SplitSeq(ifStr, ",") + for ifRegex := range ifRegexes { if _, err := regexp.Compile(ifStr); err != nil { return fmt.Errorf("error executing command: invalid interface regex specified for IP autodetection: %s", ifRegex) } } return nil - } else if strings.HasPrefix(method, AUTODETECTION_METHOD_SKIP_INTERFACE) { + } else if after, ok := strings.CutPrefix(method, AUTODETECTION_METHOD_SKIP_INTERFACE); ok { // Auto-detection method is "skip-interface", validate that the // interface regexes used are valid golang regexes. - ifStr := strings.TrimPrefix(method, AUTODETECTION_METHOD_SKIP_INTERFACE) + ifStr := after // Regexes are provided in a string separated by "," - ifRegexes := strings.Split(ifStr, ",") - for _, ifRegex := range ifRegexes { + ifRegexes := strings.SplitSeq(ifStr, ",") + for ifRegex := range ifRegexes { if _, err := regexp.Compile(ifRegex); err != nil { return fmt.Errorf("error executing command: invalid interface regex specified for IP autodetection: %s", ifRegex) } diff --git a/calicoctl/calicoctl/commands/node/status_test.go b/calicoctl/calicoctl/commands/node/status_test.go index 28d3fa83b3c..f9f38588a9f 100644 --- a/calicoctl/calicoctl/commands/node/status_test.go +++ b/calicoctl/calicoctl/commands/node/status_test.go @@ -19,8 +19,7 @@ import ( "net" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/calicoctl/calicoctl/commands/patch.go b/calicoctl/calicoctl/commands/patch.go index c52410980ac..be7b56a7835 100644 --- a/calicoctl/calicoctl/commands/patch.go +++ b/calicoctl/calicoctl/commands/patch.go @@ -108,11 +108,11 @@ Description: } if len(results.ResErrs) > 0 { - var errStr string + var errStr strings.Builder for _, err := range results.ResErrs { - errStr += fmt.Sprintf("Failed to patch '%s' resource: %v\n", results.SingleKind, err) + errStr.WriteString(fmt.Sprintf("Failed to patch '%s' resource: %v\n", results.SingleKind, err)) } - return errors.New(errStr) + return errors.New(errStr.String()) } return nil diff --git a/calicoctl/calicoctl/commands/resourceloader/resourceloader.go b/calicoctl/calicoctl/commands/resourceloader/resourceloader.go index fa4ee3fb991..f0dcc0b48ac 100644 --- a/calicoctl/calicoctl/commands/resourceloader/resourceloader.go +++ b/calicoctl/calicoctl/commands/resourceloader/resourceloader.go @@ -20,9 +20,9 @@ import ( "os" "reflect" - "github.com/projectcalico/go-yaml-wrapper" log "github.com/sirupsen/logrus" networkingv1 "k8s.io/api/networking/v1" + "sigs.k8s.io/yaml" yamlsep "github.com/projectcalico/calico/calicoctl/calicoctl/util/yaml" apiv1 "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" diff --git a/calicoctl/calicoctl/resourcemgr/globalnetworkpolicy.go b/calicoctl/calicoctl/resourcemgr/globalnetworkpolicy.go index 6831d301600..acc95335fb7 100644 --- a/calicoctl/calicoctl/resourcemgr/globalnetworkpolicy.go +++ b/calicoctl/calicoctl/resourcemgr/globalnetworkpolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2024 Tigera, Inc. All rights reserved. +// Copyright (c) 2017-2025 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,14 +16,11 @@ package resourcemgr import ( "context" - "strings" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" - cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" - "github.com/projectcalico/calico/libcalico-go/lib/names" "github.com/projectcalico/calico/libcalico-go/lib/options" ) @@ -43,35 +40,14 @@ func init() { }, func(ctx context.Context, client client.Interface, resource ResourceObject) (ResourceObject, error) { r := resource.(*api.GlobalNetworkPolicy) - if policyIsANP(r) { - return nil, cerrors.ErrorOperationNotSupported{ - Operation: "create or apply", - Identifier: resource, - Reason: "kubernetes admin network policies must be managed through the kubernetes API", - } - } return client.GlobalNetworkPolicies().Create(ctx, r, options.SetOptions{}) }, func(ctx context.Context, client client.Interface, resource ResourceObject) (ResourceObject, error) { r := resource.(*api.GlobalNetworkPolicy) - if policyIsANP(r) { - return nil, cerrors.ErrorOperationNotSupported{ - Operation: "create or apply", - Identifier: resource, - Reason: "kubernetes admin network policies must be managed through the kubernetes API", - } - } return client.GlobalNetworkPolicies().Update(ctx, r, options.SetOptions{}) }, func(ctx context.Context, client client.Interface, resource ResourceObject) (ResourceObject, error) { r := resource.(*api.GlobalNetworkPolicy) - if policyIsANP(r) { - return nil, cerrors.ErrorOperationNotSupported{ - Operation: "create or apply", - Identifier: resource, - Reason: "kubernetes admin network policies must be managed through the kubernetes API", - } - } return client.GlobalNetworkPolicies().Delete(ctx, r.Name, options.DeleteOptions{ResourceVersion: r.ResourceVersion}) }, func(ctx context.Context, client client.Interface, resource ResourceObject) (ResourceObject, error) { @@ -85,11 +61,6 @@ func init() { ) } -func policyIsANP(r *api.GlobalNetworkPolicy) bool { - return strings.HasPrefix(r.Name, names.K8sAdminNetworkPolicyNamePrefix) || - strings.HasPrefix(r.Name, names.K8sBaselineAdminNetworkPolicyNamePrefix) -} - // newGlobalNetworkPolicyList creates a new (zeroed) GlobalNetworkPolicyList struct with the TypeMetadata initialised to the current // version. func newGlobalNetworkPolicyList() *api.GlobalNetworkPolicyList { diff --git a/calicoctl/calicoctl/resourcemgr/kubecontrollersconfig_test.go b/calicoctl/calicoctl/resourcemgr/kubecontrollersconfig_test.go index f6cf13748fe..599cf715f59 100644 --- a/calicoctl/calicoctl/resourcemgr/kubecontrollersconfig_test.go +++ b/calicoctl/calicoctl/resourcemgr/kubecontrollersconfig_test.go @@ -17,14 +17,13 @@ package resourcemgr_test import ( "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) var _ = Describe("KubeControllersConfig tests", func() { - It("Should return full spec as is", func() { text := `apiVersion: projectcalico.org/v3 kind: KubeControllersConfiguration @@ -77,15 +76,6 @@ spec: // Status Expect(kcc.Status.EnvironmentVars).To(BeNil()) - Expect(kcc.Status.RunningConfig.Controllers.Node).To(BeNil()) - Expect(kcc.Status.RunningConfig.Controllers.Policy).To(BeNil()) - Expect(kcc.Status.RunningConfig.Controllers.WorkloadEndpoint).To(BeNil()) - Expect(kcc.Status.RunningConfig.Controllers.ServiceAccount).To(BeNil()) - Expect(kcc.Status.RunningConfig.Controllers.Namespace).To(BeNil()) - Expect(kcc.Status.RunningConfig.Controllers.LoadBalancer).To(BeNil()) - Expect(kcc.Status.RunningConfig.LogSeverityScreen).To(Equal("")) - Expect(kcc.Status.RunningConfig.HealthChecks).To(Equal("")) - Expect(kcc.Status.RunningConfig.EtcdV3CompactionPeriod).To(BeNil()) + Expect(kcc.Status.RunningConfig).To(BeNil()) }) - }) diff --git a/calicoctl/calicoctl/resourcemgr/networkpolicy.go b/calicoctl/calicoctl/resourcemgr/networkpolicy.go index e23277cd4ea..716107f4679 100644 --- a/calicoctl/calicoctl/resourcemgr/networkpolicy.go +++ b/calicoctl/calicoctl/resourcemgr/networkpolicy.go @@ -21,9 +21,9 @@ import ( api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/projectcalico/calico/kube-controllers/pkg/converter" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" - "github.com/projectcalico/calico/libcalico-go/lib/names" "github.com/projectcalico/calico/libcalico-go/lib/options" ) @@ -45,7 +45,9 @@ func init() { }, func(ctx context.Context, client client.Interface, resource ResourceObject) (ResourceObject, error) { r := resource.(*api.NetworkPolicy) - if strings.HasPrefix(r.Name, names.K8sNetworkPolicyNamePrefix) { + if strings.HasPrefix(r.Name, converter.KubernetesNetworkPolicyEtcdPrefix) { + // In etcd mode, we use a name prefix to multiplex K8s NetworkPolicies and Calico NetworkPolicies, so + // cannot allow Calico network policies to be named with that prefix. return nil, cerrors.ErrorOperationNotSupported{ Operation: "create or apply", Identifier: resource, @@ -56,7 +58,9 @@ func init() { }, func(ctx context.Context, client client.Interface, resource ResourceObject) (ResourceObject, error) { r := resource.(*api.NetworkPolicy) - if strings.HasPrefix(r.Name, names.K8sNetworkPolicyNamePrefix) { + if strings.HasPrefix(r.Name, converter.KubernetesNetworkPolicyEtcdPrefix) { + // In etcd mode, we use a name prefix to multiplex K8s NetworkPolicies and Calico NetworkPolicies, so + // cannot allow Calico network policies to be named with that prefix. return nil, cerrors.ErrorOperationNotSupported{ Operation: "apply or replace", Identifier: resource, @@ -67,7 +71,9 @@ func init() { }, func(ctx context.Context, client client.Interface, resource ResourceObject) (ResourceObject, error) { r := resource.(*api.NetworkPolicy) - if strings.HasPrefix(r.Name, names.K8sNetworkPolicyNamePrefix) { + if strings.HasPrefix(r.Name, converter.KubernetesNetworkPolicyEtcdPrefix) { + // In etcd mode, we use a name prefix to multiplex K8s NetworkPolicies and Calico NetworkPolicies, so + // cannot allow Calico network policies to be named with that prefix. return nil, cerrors.ErrorOperationNotSupported{ Operation: "delete", Identifier: resource, diff --git a/calicoctl/calicoctl/resourcemgr/node.go b/calicoctl/calicoctl/resourcemgr/node.go index 0786ebce820..42b01b767e2 100644 --- a/calicoctl/calicoctl/resourcemgr/node.go +++ b/calicoctl/calicoctl/resourcemgr/node.go @@ -17,15 +17,15 @@ package resourcemgr import ( "context" - api "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/options" ) func init() { registerResource( - api.NewNode(), - api.NewNodeList(), + internalapi.NewNode(), + internalapi.NewNodeList(), false, []string{"node", "nodes", "no", "nos"}, []string{"NAME"}, @@ -37,23 +37,23 @@ func init() { "IPV6": "{{if .Spec.BGP}}{{if .Spec.BGP.IPv6Address}}{{.Spec.BGP.IPv6Address}}{{end}}{{end}}", }, func(ctx context.Context, client client.Interface, resource ResourceObject) (ResourceObject, error) { - r := resource.(*api.Node) + r := resource.(*internalapi.Node) return client.Nodes().Create(ctx, r, options.SetOptions{}) }, func(ctx context.Context, client client.Interface, resource ResourceObject) (ResourceObject, error) { - r := resource.(*api.Node) + r := resource.(*internalapi.Node) return client.Nodes().Update(ctx, r, options.SetOptions{}) }, func(ctx context.Context, client client.Interface, resource ResourceObject) (ResourceObject, error) { - r := resource.(*api.Node) + r := resource.(*internalapi.Node) return client.Nodes().Delete(ctx, r.Name, options.DeleteOptions{ResourceVersion: r.ResourceVersion}) }, func(ctx context.Context, client client.Interface, resource ResourceObject) (ResourceObject, error) { - r := resource.(*api.Node) + r := resource.(*internalapi.Node) return client.Nodes().Get(ctx, r.Name, options.GetOptions{ResourceVersion: r.ResourceVersion}) }, func(ctx context.Context, client client.Interface, resource ResourceObject) (ResourceListObject, error) { - r := resource.(*api.Node) + r := resource.(*internalapi.Node) return client.Nodes().List(ctx, options.ListOptions{ResourceVersion: r.ResourceVersion, Name: r.Name}) }, ) diff --git a/calicoctl/calicoctl/resourcemgr/resourcemgr.go b/calicoctl/calicoctl/resourcemgr/resourcemgr.go index 95020b425cb..eccc10af578 100644 --- a/calicoctl/calicoctl/resourcemgr/resourcemgr.go +++ b/calicoctl/calicoctl/resourcemgr/resourcemgr.go @@ -26,13 +26,13 @@ import ( "time" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - yaml "github.com/projectcalico/go-yaml-wrapper" log "github.com/sirupsen/logrus" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/strategicpatch" + "sigs.k8s.io/yaml" "github.com/projectcalico/calico/calicoctl/calicoctl/commands/argutils" yamlsep "github.com/projectcalico/calico/calicoctl/calicoctl/util/yaml" @@ -249,7 +249,7 @@ func (rh resourceHelper) Update(ctx context.Context, client client.Interface, re // If the resourceVersion is not specified then we do a Get to get // the latest resourceVersion and then do an Update with it. // We retry only if we get an update conflict. - for i := 0; i < 5; i++ { + for range 5 { // Get the resource to get the resourceVersion. ro, err := rh.get(ctx, client, resource) if err != nil { @@ -348,7 +348,7 @@ func GetResourceManager(resource runtime.Object) ResourceManager { // This function also inserts resource name, namespace if specified. // Example "calicoctl get bgppeer peer123" will return // a BGPPeer resource with name field populated to "peer123". -func GetResourcesFromArgs(args map[string]interface{}) ([]ResourceObject, error) { +func GetResourcesFromArgs(args map[string]any) ([]ResourceObject, error) { kind := args[""].(string) argname := "" diff --git a/calicoctl/calicoctl/resourcemgr/resourcemgr_suite_test.go b/calicoctl/calicoctl/resourcemgr/resourcemgr_suite_test.go index 6c6e10b728a..c5356ce817c 100644 --- a/calicoctl/calicoctl/resourcemgr/resourcemgr_suite_test.go +++ b/calicoctl/calicoctl/resourcemgr/resourcemgr_suite_test.go @@ -5,13 +5,13 @@ package resourcemgr_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" ) func TestResourcemgr(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/resourcemgr_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Resourcemgr Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/resourcemgr_suite.xml" + ginkgo.RunSpecs(t, "Resourcemgr Suite", suiteConfig, reporterConfig) } diff --git a/calicoctl/calicoctl/resourcemgr/resourcemgr_test.go b/calicoctl/calicoctl/resourcemgr/resourcemgr_test.go index 6211397d9c2..7b6aff3edaa 100644 --- a/calicoctl/calicoctl/resourcemgr/resourcemgr_test.go +++ b/calicoctl/calicoctl/resourcemgr/resourcemgr_test.go @@ -19,7 +19,7 @@ import ( "os" "strings" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "k8s.io/apimachinery/pkg/runtime" diff --git a/calicoctl/calicoctl/resourcemgr/workloadendpoint.go b/calicoctl/calicoctl/resourcemgr/workloadendpoint.go index 138e4075ad0..62e3f3af591 100644 --- a/calicoctl/calicoctl/resourcemgr/workloadendpoint.go +++ b/calicoctl/calicoctl/resourcemgr/workloadendpoint.go @@ -17,15 +17,15 @@ package resourcemgr import ( "context" - api "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/options" ) func init() { registerResource( - api.NewWorkloadEndpoint(), - api.NewWorkloadEndpointList(), + internalapi.NewWorkloadEndpoint(), + internalapi.NewWorkloadEndpointList(), true, []string{"workloadendpoint", "workloadendpoints", "wep", "weps"}, []string{"WORKLOAD", "NODE", "NETWORKS", "INTERFACE"}, @@ -43,23 +43,23 @@ func init() { "INTERFACE": "{{.Spec.InterfaceName}}", }, func(ctx context.Context, client client.Interface, resource ResourceObject) (ResourceObject, error) { - r := resource.(*api.WorkloadEndpoint) + r := resource.(*internalapi.WorkloadEndpoint) return client.WorkloadEndpoints().Create(ctx, r, options.SetOptions{}) }, func(ctx context.Context, client client.Interface, resource ResourceObject) (ResourceObject, error) { - r := resource.(*api.WorkloadEndpoint) + r := resource.(*internalapi.WorkloadEndpoint) return client.WorkloadEndpoints().Update(ctx, r, options.SetOptions{}) }, func(ctx context.Context, client client.Interface, resource ResourceObject) (ResourceObject, error) { - r := resource.(*api.WorkloadEndpoint) + r := resource.(*internalapi.WorkloadEndpoint) return client.WorkloadEndpoints().Delete(ctx, r.Namespace, r.Name, options.DeleteOptions{ResourceVersion: r.ResourceVersion}) }, func(ctx context.Context, client client.Interface, resource ResourceObject) (ResourceObject, error) { - r := resource.(*api.WorkloadEndpoint) + r := resource.(*internalapi.WorkloadEndpoint) return client.WorkloadEndpoints().Get(ctx, r.Namespace, r.Name, options.GetOptions{ResourceVersion: r.ResourceVersion}) }, func(ctx context.Context, client client.Interface, resource ResourceObject) (ResourceListObject, error) { - r := resource.(*api.WorkloadEndpoint) + r := resource.(*internalapi.WorkloadEndpoint) return client.WorkloadEndpoints().List(ctx, options.ListOptions{ResourceVersion: r.ResourceVersion, Namespace: r.Namespace, Name: r.Name}) }, ) diff --git a/calicoctl/calicoctl/util/docstring.go b/calicoctl/calicoctl/util/docstring.go index 7e6090f3395..d388b9b1733 100644 --- a/calicoctl/calicoctl/util/docstring.go +++ b/calicoctl/calicoctl/util/docstring.go @@ -28,9 +28,9 @@ func NameAndDescription() (string, string) { func Resources() string { kinds := resourcemgr.ValidResources() sort.Strings(kinds) - resourceList := "" + var resourceList strings.Builder for _, r := range kinds { - resourceList += fmt.Sprintf(" - %s\n", strings.ToLower(r)) + resourceList.WriteString(fmt.Sprintf(" - %s\n", strings.ToLower(r))) } - return resourceList + return resourceList.String() } diff --git a/calicoctl/calicoctl/util/yaml/separator_test.go b/calicoctl/calicoctl/util/yaml/separator_test.go index 42602f911bc..41e1df2f46c 100644 --- a/calicoctl/calicoctl/util/yaml/separator_test.go +++ b/calicoctl/calicoctl/util/yaml/separator_test.go @@ -18,7 +18,7 @@ import ( "bytes" "strings" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/calicoctl/calicoctl/util/yaml/yaml_suite_test.go b/calicoctl/calicoctl/util/yaml/yaml_suite_test.go index 2ffc3d93fb0..8b8418b9ba6 100644 --- a/calicoctl/calicoctl/util/yaml/yaml_suite_test.go +++ b/calicoctl/calicoctl/util/yaml/yaml_suite_test.go @@ -5,13 +5,13 @@ package yaml_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" ) func TestYaml(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../report/yaml_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Yaml Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../report/yaml_suite.xml" + ginkgo.RunSpecs(t, "Yaml Suite", suiteConfig, reporterConfig) } diff --git a/calicoctl/deps.txt b/calicoctl/deps.txt index 4084ebae043..fd3ef590a95 100644 --- a/calicoctl/deps.txt +++ b/calicoctl/deps.txt @@ -2,13 +2,16 @@ This file contains the list of modules that this package depends on in order to trigger CI on changes -go 1.25.1 +go 1.25.7 github.com/coreos/go-semver v0.3.1 -github.com/coreos/go-systemd/v22 v22.5.0 +github.com/coreos/go-systemd/v22 v22.6.0 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 -github.com/emicklei/go-restful/v3 v3.11.0 -github.com/fxamacker/cbor/v2 v2.7.0 +github.com/emicklei/go-restful v2.15.0+incompatible +github.com/emicklei/go-restful/v3 v3.12.2 +github.com/fxamacker/cbor/v2 v2.9.0 +github.com/go-kit/log v0.2.1 +github.com/go-logfmt/logfmt v0.6.0 github.com/go-logr/logr v1.4.3 github.com/go-openapi/jsonpointer v0.21.0 github.com/go-openapi/jsonreference v0.20.2 @@ -17,12 +20,12 @@ github.com/go-playground/locales v0.14.1 github.com/go-playground/universal-translator v0.18.1 github.com/gogo/protobuf v1.3.2 github.com/golang/protobuf v1.5.4 -github.com/google/gnostic-models v0.6.9 +github.com/google/gnostic-models v0.7.0 github.com/google/go-cmp v0.7.0 github.com/google/safetext v0.0.0-20240722112252-5a72de7e7962 github.com/google/uuid v1.6.0 -github.com/grpc-ecosystem/grpc-gateway v1.16.0 -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 github.com/jinzhu/copier v0.4.0 github.com/josharian/intern v1.0.0 github.com/json-iterator/go v1.1.12 @@ -32,60 +35,62 @@ github.com/mailru/easyjson v0.7.7 github.com/mattn/go-runewidth v0.0.16 github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd -github.com/modern-go/reflect2 v1.0.2 +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 github.com/olekukonko/tablewriter v0.0.5 -github.com/onsi/gomega v1.38.0 +github.com/onsi/gomega v1.39.1 +github.com/openshift/custom-resource-status v1.1.2 github.com/pborman/uuid v1.2.1 github.com/pkg/errors v0.9.1 +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/projectcalico/calico -github.com/projectcalico/calico/lib/std v0.0.0-00010101000000-000000000000 -github.com/projectcalico/go-json v0.0.0-20161128004156-6219dc7339ba -github.com/projectcalico/go-yaml-wrapper v0.0.0-20191112210931-090425220c54 github.com/rivo/uniseg v0.4.7 -github.com/shirou/gopsutil/v4 v4.25.7 +github.com/shirou/gopsutil/v4 v4.25.9 github.com/sirupsen/logrus v1.9.3 -github.com/spf13/pflag v1.0.7 +github.com/spf13/pflag v1.0.10 github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae github.com/tklauser/go-sysconf v0.3.15 github.com/tklauser/numcpus v0.10.0 github.com/x448/float16 v0.8.4 -go.etcd.io/etcd/api/v3 v3.6.4 -go.etcd.io/etcd/client/pkg/v3 v3.6.4 -go.etcd.io/etcd/client/v3 v3.6.4 +go.etcd.io/etcd/api/v3 v3.6.5 +go.etcd.io/etcd/client/pkg/v3 v3.6.5 +go.etcd.io/etcd/client/v3 v3.6.5 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 -go.yaml.in/yaml/v2 v2.4.2 -golang.org/x/crypto v0.41.0 -golang.org/x/net v0.43.0 -golang.org/x/oauth2 v0.30.0 -golang.org/x/sync v0.16.0 -golang.org/x/sys v0.35.0 -golang.org/x/term v0.34.0 -golang.org/x/text v0.28.0 -golang.org/x/time v0.12.0 +go.yaml.in/yaml/v2 v2.4.3 +go.yaml.in/yaml/v3 v3.0.4 +golang.org/x/crypto v0.47.0 +golang.org/x/net v0.49.0 +golang.org/x/oauth2 v0.34.0 +golang.org/x/sync v0.19.0 +golang.org/x/sys v0.40.0 +golang.org/x/term v0.39.0 +golang.org/x/text v0.33.0 +golang.org/x/time v0.14.0 golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 google.golang.org/genproto v0.0.0-20250603155806-513f23925822 -google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 -google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a -google.golang.org/grpc v1.72.2 -google.golang.org/protobuf v1.36.7 +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c +google.golang.org/grpc v1.76.0 +google.golang.org/protobuf v1.36.10 gopkg.in/evanphx/json-patch.v4 v4.12.0 gopkg.in/go-playground/validator.v9 v9.30.2 gopkg.in/inf.v0 v0.9.1 -gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 -k8s.io/api v0.33.5 -k8s.io/apiextensions-apiserver v0.33.5 -k8s.io/apimachinery v0.33.5 -k8s.io/client-go v0.33.5 +k8s.io/api v0.34.3 +k8s.io/apiextensions-apiserver v0.34.3 +k8s.io/apimachinery v0.34.3 +k8s.io/client-go v0.34.3 k8s.io/klog v0.2.0 k8s.io/klog/v2 v2.130.1 -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff -k8s.io/utils v0.0.0-20241210054802-24370beab758 +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 +kubevirt.io/api v1.8.0-alpha.0 +kubevirt.io/containerized-data-importer-api v1.63.1 +kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 -sigs.k8s.io/network-policy-api v0.1.5 +sigs.k8s.io/network-policy-api v0.1.8-0.20260212153203-412bf65729a5 sigs.k8s.io/randfill v1.0.0 -sigs.k8s.io/structured-merge-diff/v4 v4.6.0 +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 sigs.k8s.io/yaml v1.6.0 diff --git a/calicoctl/test-data/convert/input/k8s/k8s-networkpolicy-invalid.yaml b/calicoctl/test-data/convert/input/k8s/k8s-networkpolicy-invalid.yaml deleted file mode 100644 index 39287ef36ba..00000000000 --- a/calicoctl/test-data/convert/input/k8s/k8s-networkpolicy-invalid.yaml +++ /dev/null @@ -1,34 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: test-network-policy - namespace: default -spec: - podSelector: - matchLabels: - role: db - policyTypes: - - Ingress - - Egress - ingress: - - from: - - ipBlock: - cidr: 172.17.0.0/16 - except: - - 172.17.1.0/24 - - namespaceSelector: - matchLabels: - project: myproject - - podSelector: - matchLabels: - role: frontend - ports: - - protocol: TCP - port: -50:-1 # invalid port, conversion will fail - egress: - - to: - - ipBlock: - cidr: 10.0.0.0/24 - ports: - - protocol: TCP - port: 5978 diff --git a/calicoctl/test-data/convert/input/k8s/k8s-networkpolicy-multiple-invalid.yaml b/calicoctl/test-data/convert/input/k8s/k8s-networkpolicy-multiple-invalid.yaml deleted file mode 100644 index be95fa683c9..00000000000 --- a/calicoctl/test-data/convert/input/k8s/k8s-networkpolicy-multiple-invalid.yaml +++ /dev/null @@ -1,54 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: test-network-policy1 - namespace: default -spec: - podSelector: - matchLabels: - role: db - policyTypes: - - Ingress - - Egress - ingress: - - from: - - ipBlock: - cidr: 172.17.0.0/16 - except: - - 172.17.1.0/24 - - namespaceSelector: - matchLabels: - project: myproject - - podSelector: - matchLabels: - role: frontend - ports: - - protocol: TCP - port: -23:-2 - egress: - - to: - - ipBlock: - cidr: 10.0.0.0/24 - ports: - - protocol: TCP - port: 5978 ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: default-deny-ingress -spec: - podSelector: {} - policyTypes: - - Ingress ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: allow-all-egress -spec: - podSelector: {} - egress: - - {} - policyTypes: - - Egress diff --git a/calicoctl/test-data/convert/input/k8s/k8s-networkpolicy-multiple-list.yaml b/calicoctl/test-data/convert/input/k8s/k8s-networkpolicy-multiple-list.yaml deleted file mode 100644 index ac6a3c50138..00000000000 --- a/calicoctl/test-data/convert/input/k8s/k8s-networkpolicy-multiple-list.yaml +++ /dev/null @@ -1,60 +0,0 @@ -apiVersion: v1 -items: - - apiVersion: networking.k8s.io/v1 - kind: NetworkPolicy - metadata: - name: allow-all-egress - namespace: default - spec: - egress: - - {} - podSelector: {} - policyTypes: - - Egress - - apiVersion: networking.k8s.io/v1 - kind: NetworkPolicy - metadata: - name: default-deny-ingress - namespace: default - spec: - podSelector: {} - policyTypes: - - Ingress - - apiVersion: networking.k8s.io/v1 - kind: NetworkPolicy - metadata: - name: test-network-policy1 - namespace: test-ns - spec: - egress: - - ports: - - port: 5978 - protocol: TCP - to: - - ipBlock: - cidr: 10.0.0.0/24 - ingress: - - from: - - ipBlock: - cidr: 172.17.0.0/16 - except: - - 172.17.1.0/24 - - namespaceSelector: - matchLabels: - project: myproject - - podSelector: - matchLabels: - role: frontend - ports: - - port: 6379 - protocol: TCP - podSelector: - matchLabels: - role: db - policyTypes: - - Ingress - - Egress -kind: List -metadata: - resourceVersion: "" - selfLink: "" diff --git a/calicoctl/test-data/convert/input/k8s/k8s-networkpolicy-multiple.yaml b/calicoctl/test-data/convert/input/k8s/k8s-networkpolicy-multiple.yaml deleted file mode 100644 index 3225800a2a0..00000000000 --- a/calicoctl/test-data/convert/input/k8s/k8s-networkpolicy-multiple.yaml +++ /dev/null @@ -1,54 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: test-network-policy1 - namespace: test-ns -spec: - podSelector: - matchLabels: - role: db - policyTypes: - - Ingress - - Egress - ingress: - - from: - - ipBlock: - cidr: 172.17.0.0/16 - except: - - 172.17.1.0/24 - - namespaceSelector: - matchLabels: - project: myproject - - podSelector: - matchLabels: - role: frontend - ports: - - protocol: TCP - port: 6379 - egress: - - to: - - ipBlock: - cidr: 10.0.0.0/24 - ports: - - protocol: TCP - port: 5978 ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: default-deny-ingress -spec: - podSelector: {} - policyTypes: - - Ingress ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: allow-all-egress -spec: - podSelector: {} - egress: - - {} - policyTypes: - - Egress diff --git a/calicoctl/test-data/convert/input/k8s/k8s-networkpolicy.yaml b/calicoctl/test-data/convert/input/k8s/k8s-networkpolicy.yaml deleted file mode 100644 index d4e47bbc3c7..00000000000 --- a/calicoctl/test-data/convert/input/k8s/k8s-networkpolicy.yaml +++ /dev/null @@ -1,34 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: test-network-policy - namespace: default -spec: - podSelector: - matchLabels: - role: db - policyTypes: - - Ingress - - Egress - ingress: - - from: - - ipBlock: - cidr: 172.17.0.0/16 - except: - - 172.17.1.0/24 - - namespaceSelector: - matchLabels: - project: myproject - - podSelector: - matchLabels: - role: frontend - ports: - - protocol: TCP - port: 6379 - egress: - - to: - - ipBlock: - cidr: 10.0.0.0/24 - ports: - - protocol: TCP - port: 5978 diff --git a/calicoctl/test-data/convert/input/v1/bgppeer-global.yaml b/calicoctl/test-data/convert/input/v1/bgppeer-global.yaml deleted file mode 100644 index c9f076b952a..00000000000 --- a/calicoctl/test-data/convert/input/v1/bgppeer-global.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -kind: bgpPeer -metadata: - scope: global - peerIP: 192.20.30.40 -spec: - asNumber: 64567 diff --git a/calicoctl/test-data/convert/input/v1/bgppeer-node.yaml b/calicoctl/test-data/convert/input/v1/bgppeer-node.yaml deleted file mode 100644 index bef3002d397..00000000000 --- a/calicoctl/test-data/convert/input/v1/bgppeer-node.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -kind: bgpPeer -metadata: - scope: node - node: Node1 - peerIP: aa:bb::ff -spec: - asNumber: 64514 diff --git a/calicoctl/test-data/convert/input/v1/bgppeer-node2.yaml b/calicoctl/test-data/convert/input/v1/bgppeer-node2.yaml deleted file mode 100644 index 2e0eb589b1f..00000000000 --- a/calicoctl/test-data/convert/input/v1/bgppeer-node2.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -kind: bgpPeer -metadata: - scope: node - node: Node2 - peerIP: 1.2.3.4 -spec: - asNumber: 6455 diff --git a/calicoctl/test-data/convert/input/v1/migration/README.md b/calicoctl/test-data/convert/input/v1/migration/README.md deleted file mode 100644 index 30f6cb008c9..00000000000 --- a/calicoctl/test-data/convert/input/v1/migration/README.md +++ /dev/null @@ -1,2 +0,0 @@ -The test data here is to be used when testing out the offline migration from -v1 to v3. diff --git a/calicoctl/test-data/convert/input/v1/migration/bgppeer.yaml b/calicoctl/test-data/convert/input/v1/migration/bgppeer.yaml deleted file mode 100644 index 1d90424f3de..00000000000 --- a/calicoctl/test-data/convert/input/v1/migration/bgppeer.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -kind: bgpPeer -metadata: - scope: node - node: rack1-host1 - peerIP: 192.168.1.1 -spec: - asNumber: 63400 diff --git a/calicoctl/test-data/convert/input/v1/migration/hostendpoint.yaml b/calicoctl/test-data/convert/input/v1/migration/hostendpoint.yaml deleted file mode 100644 index f0a09ac2ac6..00000000000 --- a/calicoctl/test-data/convert/input/v1/migration/hostendpoint.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: hostEndpoint -metadata: - name: eth0 - node: myhost - labels: - type: production -spec: - interfaceName: eth0 - expectedIPs: - - 192.168.0.1 - - 192.168.0.2 - profiles: - - profile1 - - profile2 diff --git a/calicoctl/test-data/convert/input/v1/migration/ippool.yaml b/calicoctl/test-data/convert/input/v1/migration/ippool.yaml deleted file mode 100644 index 1e63241c111..00000000000 --- a/calicoctl/test-data/convert/input/v1/migration/ippool.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: v1 -kind: ipPool -metadata: - cidr: 10.1.0.0/16 -spec: - ipip: - enabled: true - mode: cross-subnet - nat-outgoing: true - disabled: false diff --git a/calicoctl/test-data/convert/input/v1/migration/node.yaml b/calicoctl/test-data/convert/input/v1/migration/node.yaml deleted file mode 100644 index 91542f80b8a..00000000000 --- a/calicoctl/test-data/convert/input/v1/migration/node.yaml +++ /dev/null @@ -1,10 +0,0 @@ -## This won't work with KDD -apiVersion: v1 -kind: node -metadata: - name: node-hostname -spec: - bgp: - asNumber: 64512 - ipv4Address: 10.244.0.1/24 - ipv6Address: 2001:db8:85a3::8a2e:370:7334/120 diff --git a/calicoctl/test-data/convert/input/v1/migration/policy.yaml b/calicoctl/test-data/convert/input/v1/migration/policy.yaml deleted file mode 100644 index c97bdc55a63..00000000000 --- a/calicoctl/test-data/convert/input/v1/migration/policy.yaml +++ /dev/null @@ -1,77 +0,0 @@ -## This won't work with KDD -apiVersion: v1 -kind: policy -metadata: - name: allow-tcp-6379 - annotations: - aname: avalue -spec: - order: 1234 - selector: role == 'database' && !has(demo) - types: - - ingress - - egress - ingress: - - action: allow - protocol: tcp - notProtocol: udplite - source: - selector: role == 'frontend' && thing not in {'three', 'four'} - notSelector: role != 'something' && thing in {'one', 'two'} - destination: - ports: - - 6379 - - action: allow - protocol: tcp - source: - notSelector: role != 'something' && thing in {'one', 'two'} - - action: deny - protocol: tcp - destination: - ports: - - 22 - - 443 - notPorts: - - 80 - - action: allow - source: - nets: - - "172.18.18.200/32" - - "172.18.19.0/24" - - action: allow - source: - net: "172.18.18.100/32" - - action: deny - source: - notNet: "172.19.19.100/32" - - action: deny - source: - notNets: - - "172.18.0.0/16" - egress: - - action: allow - protocol: icmp - icmp: - type: 25 - code: 25 - ---- -apiVersion: v1 -kind: policy -metadata: - name: allow-tcp-555-donottrack -spec: - order: 1230 - selector: role == 'database' - types: - - ingress - ingress: - - action: allow - protocol: tcp - source: - selector: role == 'cache' - destination: - ports: - - 555 - doNotTrack: true - preDNAT: false diff --git a/calicoctl/test-data/convert/input/v1/migration/profile.yaml b/calicoctl/test-data/convert/input/v1/migration/profile.yaml deleted file mode 100644 index 43032eb7b22..00000000000 --- a/calicoctl/test-data/convert/input/v1/migration/profile.yaml +++ /dev/null @@ -1,58 +0,0 @@ -## This won't work with KDD -apiVersion: v1 -kind: profile -metadata: - name: profile1 - labels: - profile: profile1 - tags: - - atag - - btag -spec: - ingress: - - action: deny - protocol: udp - source: - tag: ctag - notTag: dtag - net: 172.20.0.0/16 - notNet: 172.20.5.0/24 - destination: - tag: atag - notPorts: - - 22 - - 443 - - 21 - - 8080 - - action: deny - protocol: tcp - source: - nets: - - 10.0.20.0/24 - notNets: - - 10.0.20.64/25 - destination: - tag: atag - notPorts: - - 22 - - 443 - - 21 - - 8080 - - action: allow - protocol: tcp - source: - selector: profile != 'profile1' && has(role) - ports: - - 1234 - - 4567 - - 8:9 - egress: - - action: allow - destination: - notSelector: profile == 'system' - - action: allow - source: - selector: something in {'a', 'b'} - - action: allow - destination: - selector: something not in {'a', 'b'} diff --git a/calicoctl/test-data/convert/input/v1/migration/workloadendpoint.yaml b/calicoctl/test-data/convert/input/v1/migration/workloadendpoint.yaml deleted file mode 100644 index 0b44d8c4e07..00000000000 --- a/calicoctl/test-data/convert/input/v1/migration/workloadendpoint.yaml +++ /dev/null @@ -1,18 +0,0 @@ -## This won't work with KDD -apiVersion: v1 -kind: workloadEndpoint -metadata: - name: eth0 - workload: default.frontend-5gs43 - orchestrator: k8s - node: rack1-host1 - labels: - app: frontend - calico/k8s_ns: default -spec: - interfaceName: cali0ef24ba - mac: ca:fe:1d:52:bb:e9 - ipNetworks: - - 192.168.0.0/32 - profiles: - - profile1 diff --git a/calicoctl/test-data/convert/input/v1/multi-resource.yaml b/calicoctl/test-data/convert/input/v1/multi-resource.yaml deleted file mode 100644 index 014c52902d5..00000000000 --- a/calicoctl/test-data/convert/input/v1/multi-resource.yaml +++ /dev/null @@ -1,39 +0,0 @@ -- apiVersion: v1 - kind: bgpPeer - metadata: - node: Node1 - peerIP: aa:bb::ff - scope: node - spec: - asNumber: 64514 -- apiVersion: v1 - kind: bgpPeer - metadata: - node: Node2 - peerIP: 1.2.3.4 - scope: node - spec: - asNumber: 6455 ---- -apiVersion: v1 -kind: bgpPeer -metadata: - scope: global - peerIP: 192.20.30.40 -spec: - asNumber: 64567 - ---- -- apiVersion: v1 - kind: ipPool - metadata: - cidr: 192.168.0.0/16 - spec: - ipip: - enabled: true - -- apiVersion: v1 - kind: ipPool - metadata: - cidr: 2001::00/120 - spec: diff --git a/calicoctl/test-data/convert/input/v1/node.yaml b/calicoctl/test-data/convert/input/v1/node.yaml deleted file mode 100644 index 124a03a5688..00000000000 --- a/calicoctl/test-data/convert/input/v1/node.yaml +++ /dev/null @@ -1,13 +0,0 @@ -- apiVersion: v1 - kind: node - metadata: - name: node1 -- apiVersion: v1 - kind: node - metadata: - name: node2 - spec: - bgp: - asNumber: 12345 - ipv4Address: 1.2.3.4/24 - ipv6Address: aa::ff diff --git a/calicoctl/test-data/convert/input/v1/test3.yaml b/calicoctl/test-data/convert/input/v1/test3.yaml deleted file mode 100644 index 9e0f5c61dea..00000000000 --- a/calicoctl/test-data/convert/input/v1/test3.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: v1 -kind: policy -metadata: - name: policy1 -spec: - order: 100 - ingress: - - action: deny - source: - selector: type=='application' - selector: type=='database' diff --git a/calicoctl/test-data/convert/output/k8s/k8s-networkpolicy-multiple-list.json b/calicoctl/test-data/convert/output/k8s/k8s-networkpolicy-multiple-list.json deleted file mode 100644 index 957dfa010ba..00000000000 --- a/calicoctl/test-data/convert/output/k8s/k8s-networkpolicy-multiple-list.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "kind": "List", - "apiVersion": "v1", - "metadata": {}, - "items": [ - { - "kind": "NetworkPolicy", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "allow-all-egress", - "namespace": "default", - "creationTimestamp": null - }, - "spec": { - "order": 1000, - "egress": [ - { - "action": "Allow", - "source": {}, - "destination": {} - } - ], - "selector": "projectcalico.org/orchestrator == 'k8s'", - "types": [ - "Egress" - ] - } - }, - { - "kind": "NetworkPolicy", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "default-deny-ingress", - "namespace": "default", - "creationTimestamp": null - }, - "spec": { - "order": 1000, - "selector": "projectcalico.org/orchestrator == 'k8s'", - "types": [ - "Ingress" - ] - } - }, - { - "kind": "NetworkPolicy", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "test-network-policy1", - "namespace": "test-ns", - "creationTimestamp": null - }, - "spec": { - "order": 1000, - "ingress": [ - { - "action": "Allow", - "protocol": "TCP", - "source": { - "nets": [ - "172.17.0.0/16" - ], - "notNets": [ - "172.17.1.0/24" - ] - }, - "destination": { - "ports": [ - 6379 - ] - } - }, - { - "action": "Allow", - "protocol": "TCP", - "source": { - "selector": "projectcalico.org/orchestrator == 'k8s'", - "namespaceSelector": "project == 'myproject'" - }, - "destination": { - "ports": [ - 6379 - ] - } - }, - { - "action": "Allow", - "protocol": "TCP", - "source": { - "selector": "projectcalico.org/orchestrator == 'k8s' \u0026\u0026 role == 'frontend'" - }, - "destination": { - "ports": [ - 6379 - ] - } - } - ], - "egress": [ - { - "action": "Allow", - "protocol": "TCP", - "source": {}, - "destination": { - "nets": [ - "10.0.0.0/24" - ], - "ports": [ - 5978 - ] - } - } - ], - "selector": "projectcalico.org/orchestrator == 'k8s' \u0026\u0026 role == 'db'", - "types": [ - "Ingress", - "Egress" - ] - } - } - ] -} diff --git a/calicoctl/test-data/convert/output/k8s/k8s-networkpolicy-multiple-list.yaml b/calicoctl/test-data/convert/output/k8s/k8s-networkpolicy-multiple-list.yaml deleted file mode 100644 index 296f5721a68..00000000000 --- a/calicoctl/test-data/convert/output/k8s/k8s-networkpolicy-multiple-list.yaml +++ /dev/null @@ -1,77 +0,0 @@ -apiVersion: v1 -items: -- apiVersion: projectcalico.org/v3 - kind: NetworkPolicy - metadata: - creationTimestamp: null - name: allow-all-egress - namespace: default - spec: - egress: - - action: Allow - destination: {} - source: {} - order: 1000 - selector: projectcalico.org/orchestrator == 'k8s' - types: - - Egress -- apiVersion: projectcalico.org/v3 - kind: NetworkPolicy - metadata: - creationTimestamp: null - name: default-deny-ingress - namespace: default - spec: - order: 1000 - selector: projectcalico.org/orchestrator == 'k8s' - types: - - Ingress -- apiVersion: projectcalico.org/v3 - kind: NetworkPolicy - metadata: - creationTimestamp: null - name: test-network-policy1 - namespace: test-ns - spec: - egress: - - action: Allow - destination: - nets: - - 10.0.0.0/24 - ports: - - 5978 - protocol: TCP - source: {} - ingress: - - action: Allow - destination: - ports: - - 6379 - protocol: TCP - source: - nets: - - 172.17.0.0/16 - notNets: - - 172.17.1.0/24 - - action: Allow - destination: - ports: - - 6379 - protocol: TCP - source: - namespaceSelector: project == 'myproject' - selector: projectcalico.org/orchestrator == 'k8s' - - action: Allow - destination: - ports: - - 6379 - protocol: TCP - source: - selector: projectcalico.org/orchestrator == 'k8s' && role == 'frontend' - order: 1000 - selector: projectcalico.org/orchestrator == 'k8s' && role == 'db' - types: - - Ingress - - Egress -kind: List -metadata: {} diff --git a/calicoctl/test-data/convert/output/k8s/k8s-networkpolicy-multiple.json b/calicoctl/test-data/convert/output/k8s/k8s-networkpolicy-multiple.json deleted file mode 100644 index 93c9ebe3cf1..00000000000 --- a/calicoctl/test-data/convert/output/k8s/k8s-networkpolicy-multiple.json +++ /dev/null @@ -1,120 +0,0 @@ -{ - "kind": "List", - "apiVersion": "v1", - "metadata": {}, - "items": [ - { - "kind": "NetworkPolicy", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "test-network-policy1", - "namespace": "test-ns", - "creationTimestamp": null - }, - "spec": { - "order": 1000, - "ingress": [ - { - "action": "Allow", - "protocol": "TCP", - "source": { - "nets": [ - "172.17.0.0/16" - ], - "notNets": [ - "172.17.1.0/24" - ] - }, - "destination": { - "ports": [ - 6379 - ] - } - }, - { - "action": "Allow", - "protocol": "TCP", - "source": { - "selector": "projectcalico.org/orchestrator == 'k8s'", - "namespaceSelector": "project == 'myproject'" - }, - "destination": { - "ports": [ - 6379 - ] - } - }, - { - "action": "Allow", - "protocol": "TCP", - "source": { - "selector": "projectcalico.org/orchestrator == 'k8s' \u0026\u0026 role == 'frontend'" - }, - "destination": { - "ports": [ - 6379 - ] - } - } - ], - "egress": [ - { - "action": "Allow", - "protocol": "TCP", - "source": {}, - "destination": { - "nets": [ - "10.0.0.0/24" - ], - "ports": [ - 5978 - ] - } - } - ], - "selector": "projectcalico.org/orchestrator == 'k8s' \u0026\u0026 role == 'db'", - "types": [ - "Ingress", - "Egress" - ] - } - }, - { - "kind": "NetworkPolicy", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "default-deny-ingress", - "creationTimestamp": null - }, - "spec": { - "order": 1000, - "selector": "projectcalico.org/orchestrator == 'k8s'", - "types": [ - "Ingress" - ] - } - }, - { - "kind": "NetworkPolicy", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "allow-all-egress", - "creationTimestamp": null - }, - "spec": { - "order": 1000, - "egress": [ - { - "action": "Allow", - "source": {}, - "destination": {} - } - ], - "selector": "projectcalico.org/orchestrator == 'k8s'", - "types": [ - "Egress" - ] - } - } - ] -} diff --git a/calicoctl/test-data/convert/output/k8s/k8s-networkpolicy-multiple.yaml b/calicoctl/test-data/convert/output/k8s/k8s-networkpolicy-multiple.yaml deleted file mode 100644 index d7183c77b2b..00000000000 --- a/calicoctl/test-data/convert/output/k8s/k8s-networkpolicy-multiple.yaml +++ /dev/null @@ -1,75 +0,0 @@ -apiVersion: v1 -items: -- apiVersion: projectcalico.org/v3 - kind: NetworkPolicy - metadata: - creationTimestamp: null - name: test-network-policy1 - namespace: test-ns - spec: - egress: - - action: Allow - destination: - nets: - - 10.0.0.0/24 - ports: - - 5978 - protocol: TCP - source: {} - ingress: - - action: Allow - destination: - ports: - - 6379 - protocol: TCP - source: - nets: - - 172.17.0.0/16 - notNets: - - 172.17.1.0/24 - - action: Allow - destination: - ports: - - 6379 - protocol: TCP - source: - namespaceSelector: project == 'myproject' - selector: projectcalico.org/orchestrator == 'k8s' - - action: Allow - destination: - ports: - - 6379 - protocol: TCP - source: - selector: projectcalico.org/orchestrator == 'k8s' && role == 'frontend' - order: 1000 - selector: projectcalico.org/orchestrator == 'k8s' && role == 'db' - types: - - Ingress - - Egress -- apiVersion: projectcalico.org/v3 - kind: NetworkPolicy - metadata: - creationTimestamp: null - name: default-deny-ingress - spec: - order: 1000 - selector: projectcalico.org/orchestrator == 'k8s' - types: - - Ingress -- apiVersion: projectcalico.org/v3 - kind: NetworkPolicy - metadata: - creationTimestamp: null - name: allow-all-egress - spec: - egress: - - action: Allow - destination: {} - source: {} - order: 1000 - selector: projectcalico.org/orchestrator == 'k8s' - types: - - Egress -kind: List -metadata: {} diff --git a/calicoctl/test-data/convert/output/k8s/k8s-networkpolicy.json b/calicoctl/test-data/convert/output/k8s/k8s-networkpolicy.json deleted file mode 100644 index 39044f3813b..00000000000 --- a/calicoctl/test-data/convert/output/k8s/k8s-networkpolicy.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "kind": "NetworkPolicy", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "test-network-policy", - "namespace": "default", - "creationTimestamp": null - }, - "spec": { - "order": 1000, - "ingress": [ - { - "action": "Allow", - "protocol": "TCP", - "source": { - "nets": [ - "172.17.0.0/16" - ], - "notNets": [ - "172.17.1.0/24" - ] - }, - "destination": { - "ports": [ - 6379 - ] - } - }, - { - "action": "Allow", - "protocol": "TCP", - "source": { - "selector": "projectcalico.org/orchestrator == 'k8s'", - "namespaceSelector": "project == 'myproject'" - }, - "destination": { - "ports": [ - 6379 - ] - } - }, - { - "action": "Allow", - "protocol": "TCP", - "source": { - "selector": "projectcalico.org/orchestrator == 'k8s' \u0026\u0026 role == 'frontend'" - }, - "destination": { - "ports": [ - 6379 - ] - } - } - ], - "egress": [ - { - "action": "Allow", - "protocol": "TCP", - "source": {}, - "destination": { - "nets": [ - "10.0.0.0/24" - ], - "ports": [ - 5978 - ] - } - } - ], - "selector": "projectcalico.org/orchestrator == 'k8s' \u0026\u0026 role == 'db'", - "types": [ - "Ingress", - "Egress" - ] - } -} diff --git a/calicoctl/test-data/convert/output/k8s/k8s-networkpolicy.yaml b/calicoctl/test-data/convert/output/k8s/k8s-networkpolicy.yaml deleted file mode 100644 index a38dc298f4b..00000000000 --- a/calicoctl/test-data/convert/output/k8s/k8s-networkpolicy.yaml +++ /dev/null @@ -1,47 +0,0 @@ -apiVersion: projectcalico.org/v3 -kind: NetworkPolicy -metadata: - creationTimestamp: null - name: test-network-policy - namespace: default -spec: - egress: - - action: Allow - destination: - nets: - - 10.0.0.0/24 - ports: - - 5978 - protocol: TCP - source: {} - ingress: - - action: Allow - destination: - ports: - - 6379 - protocol: TCP - source: - nets: - - 172.17.0.0/16 - notNets: - - 172.17.1.0/24 - - action: Allow - destination: - ports: - - 6379 - protocol: TCP - source: - namespaceSelector: project == 'myproject' - selector: projectcalico.org/orchestrator == 'k8s' - - action: Allow - destination: - ports: - - 6379 - protocol: TCP - source: - selector: projectcalico.org/orchestrator == 'k8s' && role == 'frontend' - order: 1000 - selector: projectcalico.org/orchestrator == 'k8s' && role == 'db' - types: - - Ingress - - Egress diff --git a/calicoctl/test-data/convert/output/v1/bgppeer-global.json b/calicoctl/test-data/convert/output/v1/bgppeer-global.json deleted file mode 100644 index f40c0fb4cbe..00000000000 --- a/calicoctl/test-data/convert/output/v1/bgppeer-global.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "kind": "BGPPeer", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "192-20-30-40", - "creationTimestamp": null - }, - "spec": { - "peerIP": "192.20.30.40", - "asNumber": 64567 - } -} diff --git a/calicoctl/test-data/convert/output/v1/bgppeer-global.yaml b/calicoctl/test-data/convert/output/v1/bgppeer-global.yaml deleted file mode 100644 index 1a00e1f017a..00000000000 --- a/calicoctl/test-data/convert/output/v1/bgppeer-global.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: projectcalico.org/v3 -kind: BGPPeer -metadata: - creationTimestamp: null - name: 192-20-30-40 -spec: - asNumber: 64567 - peerIP: 192.20.30.40 diff --git a/calicoctl/test-data/convert/output/v1/bgppeer-node.json b/calicoctl/test-data/convert/output/v1/bgppeer-node.json deleted file mode 100644 index b098b6e0ffd..00000000000 --- a/calicoctl/test-data/convert/output/v1/bgppeer-node.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "kind": "BGPPeer", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "node1.00aa-00bb-0000-0000-0000-0000-0000-00ff", - "creationTimestamp": null - }, - "spec": { - "node": "node1", - "peerIP": "aa:bb::ff", - "asNumber": 64514 - } -} diff --git a/calicoctl/test-data/convert/output/v1/bgppeer-node.yaml b/calicoctl/test-data/convert/output/v1/bgppeer-node.yaml deleted file mode 100644 index 3e580c16201..00000000000 --- a/calicoctl/test-data/convert/output/v1/bgppeer-node.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: projectcalico.org/v3 -kind: BGPPeer -metadata: - creationTimestamp: null - name: node1.00aa-00bb-0000-0000-0000-0000-0000-00ff -spec: - asNumber: 64514 - node: node1 - peerIP: aa:bb::ff diff --git a/calicoctl/test-data/convert/output/v1/bgppeer-node2.json b/calicoctl/test-data/convert/output/v1/bgppeer-node2.json deleted file mode 100644 index 9af46646288..00000000000 --- a/calicoctl/test-data/convert/output/v1/bgppeer-node2.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "kind": "BGPPeer", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "node2.1-2-3-4", - "creationTimestamp": null - }, - "spec": { - "node": "node2", - "peerIP": "1.2.3.4", - "asNumber": 6455 - } -} diff --git a/calicoctl/test-data/convert/output/v1/bgppeer-node2.yaml b/calicoctl/test-data/convert/output/v1/bgppeer-node2.yaml deleted file mode 100644 index 92b9f61f71f..00000000000 --- a/calicoctl/test-data/convert/output/v1/bgppeer-node2.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: projectcalico.org/v3 -kind: BGPPeer -metadata: - creationTimestamp: null - name: node2.1-2-3-4 -spec: - asNumber: 6455 - node: node2 - peerIP: 1.2.3.4 diff --git a/calicoctl/test-data/convert/output/v1/migration/bgppeer.json b/calicoctl/test-data/convert/output/v1/migration/bgppeer.json deleted file mode 100644 index c47af3003ed..00000000000 --- a/calicoctl/test-data/convert/output/v1/migration/bgppeer.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "kind": "BGPPeer", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "rack1-host1.192-168-1-1", - "creationTimestamp": null - }, - "spec": { - "node": "rack1-host1", - "peerIP": "192.168.1.1", - "asNumber": 63400 - } -} diff --git a/calicoctl/test-data/convert/output/v1/migration/bgppeer.yaml b/calicoctl/test-data/convert/output/v1/migration/bgppeer.yaml deleted file mode 100644 index 8bb8d4362d6..00000000000 --- a/calicoctl/test-data/convert/output/v1/migration/bgppeer.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: projectcalico.org/v3 -kind: BGPPeer -metadata: - creationTimestamp: null - name: rack1-host1.192-168-1-1 -spec: - asNumber: 63400 - node: rack1-host1 - peerIP: 192.168.1.1 diff --git a/calicoctl/test-data/convert/output/v1/migration/hostendpoint.json b/calicoctl/test-data/convert/output/v1/migration/hostendpoint.json deleted file mode 100644 index 3f1b46481df..00000000000 --- a/calicoctl/test-data/convert/output/v1/migration/hostendpoint.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "kind": "HostEndpoint", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "myhost.eth0", - "creationTimestamp": null, - "labels": { - "type": "production" - } - }, - "spec": { - "node": "myhost", - "interfaceName": "eth0", - "expectedIPs": [ - "192.168.0.1", - "192.168.0.2" - ], - "profiles": [ - "profile1", - "profile2" - ] - } -} diff --git a/calicoctl/test-data/convert/output/v1/migration/hostendpoint.yaml b/calicoctl/test-data/convert/output/v1/migration/hostendpoint.yaml deleted file mode 100644 index 34f4898c6dd..00000000000 --- a/calicoctl/test-data/convert/output/v1/migration/hostendpoint.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: projectcalico.org/v3 -kind: HostEndpoint -metadata: - creationTimestamp: null - labels: - type: production - name: myhost.eth0 -spec: - expectedIPs: - - 192.168.0.1 - - 192.168.0.2 - interfaceName: eth0 - node: myhost - profiles: - - profile1 - - profile2 diff --git a/calicoctl/test-data/convert/output/v1/migration/ippool.json b/calicoctl/test-data/convert/output/v1/migration/ippool.json deleted file mode 100644 index ccf878716bf..00000000000 --- a/calicoctl/test-data/convert/output/v1/migration/ippool.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "kind": "IPPool", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "10-1-0-0-16", - "creationTimestamp": null - }, - "spec": { - "cidr": "10.1.0.0/16", - "vxlanMode": "Never", - "ipipMode": "CrossSubnet", - "natOutgoing": true, - "blockSize": 26, - "nodeSelector": "all()", - "allowedUses": [ - "Workload", - "Tunnel" - ], - "assignmentMode": "Automatic" - } -} diff --git a/calicoctl/test-data/convert/output/v1/migration/ippool.yaml b/calicoctl/test-data/convert/output/v1/migration/ippool.yaml deleted file mode 100644 index 1bbd7d134f1..00000000000 --- a/calicoctl/test-data/convert/output/v1/migration/ippool.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: projectcalico.org/v3 -kind: IPPool -metadata: - creationTimestamp: null - name: 10-1-0-0-16 -spec: - allowedUses: - - Workload - - Tunnel - assignmentMode: Automatic - blockSize: 26 - cidr: 10.1.0.0/16 - ipipMode: CrossSubnet - natOutgoing: true - nodeSelector: all() - vxlanMode: Never diff --git a/calicoctl/test-data/convert/output/v1/migration/node.json b/calicoctl/test-data/convert/output/v1/migration/node.json deleted file mode 100644 index 605f00900e8..00000000000 --- a/calicoctl/test-data/convert/output/v1/migration/node.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "kind": "Node", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "node-hostname", - "creationTimestamp": null - }, - "spec": { - "bgp": { - "asNumber": 64512, - "ipv4Address": "10.244.0.1/24", - "ipv6Address": "2001:db8:85a3::8a2e:370:7334/120" - } - }, - "status": {} -} diff --git a/calicoctl/test-data/convert/output/v1/migration/node.yaml b/calicoctl/test-data/convert/output/v1/migration/node.yaml deleted file mode 100644 index c2f7de01348..00000000000 --- a/calicoctl/test-data/convert/output/v1/migration/node.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: projectcalico.org/v3 -kind: Node -metadata: - creationTimestamp: null - name: node-hostname -spec: - bgp: - asNumber: 64512 - ipv4Address: 10.244.0.1/24 - ipv6Address: 2001:db8:85a3::8a2e:370:7334/120 -status: {} diff --git a/calicoctl/test-data/convert/output/v1/migration/policy.json b/calicoctl/test-data/convert/output/v1/migration/policy.json deleted file mode 100644 index e3af08b62fa..00000000000 --- a/calicoctl/test-data/convert/output/v1/migration/policy.json +++ /dev/null @@ -1,152 +0,0 @@ -{ - "kind": "List", - "apiVersion": "v1", - "metadata": {}, - "items": [ - { - "kind": "GlobalNetworkPolicy", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "default.allow-tcp-6379", - "creationTimestamp": null, - "labels": { - "projectcalico.org/tier": "default" - }, - "annotations": { - "aname": "avalue" - } - }, - "spec": { - "tier": "default", - "order": 1234, - "ingress": [ - { - "action": "Allow", - "protocol": "TCP", - "notProtocol": "UDPLite", - "source": { - "selector": "role == 'frontend' \u0026\u0026 thing not in {'three', 'four'}", - "notSelector": "role != 'something' \u0026\u0026 thing in {'one', 'two'}" - }, - "destination": { - "ports": [ - 6379 - ] - } - }, - { - "action": "Allow", - "protocol": "TCP", - "source": { - "notSelector": "role != 'something' \u0026\u0026 thing in {'one', 'two'}" - }, - "destination": {} - }, - { - "action": "Deny", - "protocol": "TCP", - "source": {}, - "destination": { - "ports": [ - 22, - 443 - ], - "notPorts": [ - 80 - ] - } - }, - { - "action": "Allow", - "source": { - "nets": [ - "172.18.18.200/32", - "172.18.19.0/24" - ] - }, - "destination": {} - }, - { - "action": "Allow", - "source": { - "nets": [ - "172.18.18.100/32" - ] - }, - "destination": {} - }, - { - "action": "Deny", - "source": { - "notNets": [ - "172.19.19.100/32" - ] - }, - "destination": {} - }, - { - "action": "Deny", - "source": { - "notNets": [ - "172.18.0.0/16" - ] - }, - "destination": {} - } - ], - "egress": [ - { - "action": "Allow", - "protocol": "ICMP", - "icmp": { - "type": 25, - "code": 25 - }, - "source": {}, - "destination": {} - } - ], - "selector": "role == 'database' \u0026\u0026 !has(demo)", - "types": [ - "Ingress", - "Egress" - ] - } - }, - { - "kind": "GlobalNetworkPolicy", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "default.allow-tcp-555-donottrack", - "creationTimestamp": null, - "labels": { - "projectcalico.org/tier": "default" - } - }, - "spec": { - "tier": "default", - "order": 1230, - "ingress": [ - { - "action": "Allow", - "protocol": "TCP", - "source": { - "selector": "role == 'cache'" - }, - "destination": { - "ports": [ - 555 - ] - } - } - ], - "selector": "role == 'database'", - "types": [ - "Ingress" - ], - "doNotTrack": true, - "applyOnForward": true - } - } - ] -} diff --git a/calicoctl/test-data/convert/output/v1/migration/policy.yaml b/calicoctl/test-data/convert/output/v1/migration/policy.yaml deleted file mode 100644 index 0f7597e662a..00000000000 --- a/calicoctl/test-data/convert/output/v1/migration/policy.yaml +++ /dev/null @@ -1,96 +0,0 @@ -apiVersion: v1 -items: -- apiVersion: projectcalico.org/v3 - kind: GlobalNetworkPolicy - metadata: - annotations: - aname: avalue - creationTimestamp: null - labels: - projectcalico.org/tier: default - name: default.allow-tcp-6379 - spec: - egress: - - action: Allow - destination: {} - icmp: - code: 25 - type: 25 - protocol: ICMP - source: {} - ingress: - - action: Allow - destination: - ports: - - 6379 - notProtocol: UDPLite - protocol: TCP - source: - notSelector: role != 'something' && thing in {'one', 'two'} - selector: role == 'frontend' && thing not in {'three', 'four'} - - action: Allow - destination: {} - protocol: TCP - source: - notSelector: role != 'something' && thing in {'one', 'two'} - - action: Deny - destination: - notPorts: - - 80 - ports: - - 22 - - 443 - protocol: TCP - source: {} - - action: Allow - destination: {} - source: - nets: - - 172.18.18.200/32 - - 172.18.19.0/24 - - action: Allow - destination: {} - source: - nets: - - 172.18.18.100/32 - - action: Deny - destination: {} - source: - notNets: - - 172.19.19.100/32 - - action: Deny - destination: {} - source: - notNets: - - 172.18.0.0/16 - order: 1234 - selector: role == 'database' && !has(demo) - tier: default - types: - - Ingress - - Egress -- apiVersion: projectcalico.org/v3 - kind: GlobalNetworkPolicy - metadata: - creationTimestamp: null - labels: - projectcalico.org/tier: default - name: default.allow-tcp-555-donottrack - spec: - applyOnForward: true - doNotTrack: true - ingress: - - action: Allow - destination: - ports: - - 555 - protocol: TCP - source: - selector: role == 'cache' - order: 1230 - selector: role == 'database' - tier: default - types: - - Ingress -kind: List -metadata: {} diff --git a/calicoctl/test-data/convert/output/v1/migration/profile.json b/calicoctl/test-data/convert/output/v1/migration/profile.json deleted file mode 100644 index d4ebe78d58f..00000000000 --- a/calicoctl/test-data/convert/output/v1/migration/profile.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "kind": "Profile", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "profile1", - "creationTimestamp": null - }, - "spec": { - "ingress": [ - { - "action": "Deny", - "protocol": "UDP", - "source": { - "nets": [ - "172.20.0.0/16" - ], - "selector": "ctag == ''", - "notNets": [ - "172.20.5.0/24" - ], - "notSelector": "dtag == ''" - }, - "destination": { - "selector": "atag == ''", - "notPorts": [ - 22, - 443, - 21, - 8080 - ] - } - }, - { - "action": "Deny", - "protocol": "TCP", - "source": { - "nets": [ - "10.0.20.0/24" - ], - "notNets": [ - "10.0.20.0/25" - ] - }, - "destination": { - "selector": "atag == ''", - "notPorts": [ - 22, - 443, - 21, - 8080 - ] - } - }, - { - "action": "Allow", - "protocol": "TCP", - "source": { - "selector": "profile != 'profile1' \u0026\u0026 has(role)", - "ports": [ - 1234, - 4567, - "8:9" - ] - }, - "destination": {} - } - ], - "egress": [ - { - "action": "Allow", - "source": {}, - "destination": { - "notSelector": "profile == 'system'" - } - }, - { - "action": "Allow", - "source": { - "selector": "something in {'a', 'b'}" - }, - "destination": {} - }, - { - "action": "Allow", - "source": {}, - "destination": { - "selector": "something not in {'a', 'b'}" - } - } - ], - "labelsToApply": { - "atag": "", - "btag": "", - "profile": "profile1" - } - } -} diff --git a/calicoctl/test-data/convert/output/v1/migration/profile.yaml b/calicoctl/test-data/convert/output/v1/migration/profile.yaml deleted file mode 100644 index 5829ccaae01..00000000000 --- a/calicoctl/test-data/convert/output/v1/migration/profile.yaml +++ /dev/null @@ -1,63 +0,0 @@ -apiVersion: projectcalico.org/v3 -kind: Profile -metadata: - creationTimestamp: null - name: profile1 -spec: - egress: - - action: Allow - destination: - notSelector: profile == 'system' - source: {} - - action: Allow - destination: {} - source: - selector: something in {'a', 'b'} - - action: Allow - destination: - selector: something not in {'a', 'b'} - source: {} - ingress: - - action: Deny - destination: - notPorts: - - 22 - - 443 - - 21 - - 8080 - selector: atag == '' - protocol: UDP - source: - nets: - - 172.20.0.0/16 - notNets: - - 172.20.5.0/24 - notSelector: dtag == '' - selector: ctag == '' - - action: Deny - destination: - notPorts: - - 22 - - 443 - - 21 - - 8080 - selector: atag == '' - protocol: TCP - source: - nets: - - 10.0.20.0/24 - notNets: - - 10.0.20.0/25 - - action: Allow - destination: {} - protocol: TCP - source: - ports: - - 1234 - - 4567 - - "8:9" - selector: profile != 'profile1' && has(role) - labelsToApply: - atag: "" - btag: "" - profile: profile1 diff --git a/calicoctl/test-data/convert/output/v1/migration/workloadendpoint.json b/calicoctl/test-data/convert/output/v1/migration/workloadendpoint.json deleted file mode 100644 index a14d0efc221..00000000000 --- a/calicoctl/test-data/convert/output/v1/migration/workloadendpoint.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "kind": "WorkloadEndpoint", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "rack1--host1-k8s-frontend--5gs43-eth0", - "namespace": "default", - "creationTimestamp": null, - "labels": { - "app": "frontend", - "projectcalico.org/namespace": "default" - } - }, - "spec": { - "orchestrator": "k8s", - "node": "rack1-host1", - "pod": "frontend-5gs43", - "endpoint": "eth0", - "ipNetworks": [ - "192.168.0.0/32" - ], - "profiles": [ - "profile1" - ], - "interfaceName": "cali0ef24ba", - "mac": "ca:fe:1d:52:bb:e9" - } -} diff --git a/calicoctl/test-data/convert/output/v1/migration/workloadendpoint.yaml b/calicoctl/test-data/convert/output/v1/migration/workloadendpoint.yaml deleted file mode 100644 index eae0f4f98bc..00000000000 --- a/calicoctl/test-data/convert/output/v1/migration/workloadendpoint.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: projectcalico.org/v3 -kind: WorkloadEndpoint -metadata: - creationTimestamp: null - labels: - app: frontend - projectcalico.org/namespace: default - name: rack1--host1-k8s-frontend--5gs43-eth0 - namespace: default -spec: - endpoint: eth0 - interfaceName: cali0ef24ba - ipNetworks: - - 192.168.0.0/32 - mac: ca:fe:1d:52:bb:e9 - node: rack1-host1 - orchestrator: k8s - pod: frontend-5gs43 - profiles: - - profile1 diff --git a/calicoctl/test-data/convert/output/v1/multi-resource.json b/calicoctl/test-data/convert/output/v1/multi-resource.json deleted file mode 100644 index 258b06d44b1..00000000000 --- a/calicoctl/test-data/convert/output/v1/multi-resource.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "kind": "List", - "apiVersion": "v1", - "metadata": {}, - "items": [ - { - "kind": "BGPPeer", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "node1.00aa-00bb-0000-0000-0000-0000-0000-00ff", - "creationTimestamp": null - }, - "spec": { - "node": "node1", - "peerIP": "aa:bb::ff", - "asNumber": 64514 - } - }, - { - "kind": "BGPPeer", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "node2.1-2-3-4", - "creationTimestamp": null - }, - "spec": { - "node": "node2", - "peerIP": "1.2.3.4", - "asNumber": 6455 - } - }, - { - "kind": "BGPPeer", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "192-20-30-40", - "creationTimestamp": null - }, - "spec": { - "peerIP": "192.20.30.40", - "asNumber": 64567 - } - }, - { - "kind": "IPPool", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "192-168-0-0-16", - "creationTimestamp": null - }, - "spec": { - "cidr": "192.168.0.0/16", - "vxlanMode": "Never", - "ipipMode": "Always", - "blockSize": 26, - "nodeSelector": "all()", - "allowedUses": [ - "Workload", - "Tunnel" - ], - "assignmentMode": "Automatic" - } - }, - { - "kind": "IPPool", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "2001---120", - "creationTimestamp": null - }, - "spec": { - "cidr": "2001::/120", - "vxlanMode": "Never", - "ipipMode": "Never", - "blockSize": 122, - "nodeSelector": "all()", - "allowedUses": [ - "Workload", - "Tunnel" - ], - "assignmentMode": "Automatic" - } - } - ] -} diff --git a/calicoctl/test-data/convert/output/v1/multi-resource.yaml b/calicoctl/test-data/convert/output/v1/multi-resource.yaml deleted file mode 100644 index 52b81b20ea6..00000000000 --- a/calicoctl/test-data/convert/output/v1/multi-resource.yaml +++ /dev/null @@ -1,60 +0,0 @@ -apiVersion: v1 -items: -- apiVersion: projectcalico.org/v3 - kind: BGPPeer - metadata: - creationTimestamp: null - name: node1.00aa-00bb-0000-0000-0000-0000-0000-00ff - spec: - asNumber: 64514 - node: node1 - peerIP: aa:bb::ff -- apiVersion: projectcalico.org/v3 - kind: BGPPeer - metadata: - creationTimestamp: null - name: node2.1-2-3-4 - spec: - asNumber: 6455 - node: node2 - peerIP: 1.2.3.4 -- apiVersion: projectcalico.org/v3 - kind: BGPPeer - metadata: - creationTimestamp: null - name: 192-20-30-40 - spec: - asNumber: 64567 - peerIP: 192.20.30.40 -- apiVersion: projectcalico.org/v3 - kind: IPPool - metadata: - creationTimestamp: null - name: 192-168-0-0-16 - spec: - allowedUses: - - Workload - - Tunnel - assignmentMode: Automatic - blockSize: 26 - cidr: 192.168.0.0/16 - ipipMode: Always - nodeSelector: all() - vxlanMode: Never -- apiVersion: projectcalico.org/v3 - kind: IPPool - metadata: - creationTimestamp: null - name: 2001---120 - spec: - allowedUses: - - Workload - - Tunnel - assignmentMode: Automatic - blockSize: 122 - cidr: 2001::/120 - ipipMode: Never - nodeSelector: all() - vxlanMode: Never -kind: List -metadata: {} diff --git a/calicoctl/test-data/convert/output/v1/node.json b/calicoctl/test-data/convert/output/v1/node.json deleted file mode 100644 index af417e4c7bb..00000000000 --- a/calicoctl/test-data/convert/output/v1/node.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "kind": "List", - "apiVersion": "v1", - "metadata": {}, - "items": [ - { - "kind": "Node", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "node1", - "creationTimestamp": null - }, - "spec": {}, - "status": {} - }, - { - "kind": "Node", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "node2", - "creationTimestamp": null - }, - "spec": { - "bgp": { - "asNumber": 12345, - "ipv4Address": "1.2.3.4/24", - "ipv6Address": "aa::ff/128" - } - }, - "status": {} - } - ] -} diff --git a/calicoctl/test-data/convert/output/v1/node.yaml b/calicoctl/test-data/convert/output/v1/node.yaml deleted file mode 100644 index bf5e21cc2de..00000000000 --- a/calicoctl/test-data/convert/output/v1/node.yaml +++ /dev/null @@ -1,22 +0,0 @@ -apiVersion: v1 -items: -- apiVersion: projectcalico.org/v3 - kind: Node - metadata: - creationTimestamp: null - name: node1 - spec: {} - status: {} -- apiVersion: projectcalico.org/v3 - kind: Node - metadata: - creationTimestamp: null - name: node2 - spec: - bgp: - asNumber: 12345 - ipv4Address: 1.2.3.4/24 - ipv6Address: aa::ff/128 - status: {} -kind: List -metadata: {} diff --git a/calicoctl/test-data/convert/output/v1/test3.json b/calicoctl/test-data/convert/output/v1/test3.json deleted file mode 100644 index 3e0e0208ac1..00000000000 --- a/calicoctl/test-data/convert/output/v1/test3.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "kind": "GlobalNetworkPolicy", - "apiVersion": "projectcalico.org/v3", - "metadata": { - "name": "default.policy1", - "creationTimestamp": null, - "labels": { - "projectcalico.org/tier": "default" - } - }, - "spec": { - "tier": "default", - "order": 100, - "ingress": [ - { - "action": "Deny", - "source": { - "selector": "type=='application'" - }, - "destination": {} - } - ], - "selector": "type=='database'", - "types": [ - "Ingress" - ] - } -} diff --git a/calicoctl/test-data/convert/output/v1/test3.yaml b/calicoctl/test-data/convert/output/v1/test3.yaml deleted file mode 100644 index 9a9a2b2355e..00000000000 --- a/calicoctl/test-data/convert/output/v1/test3.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: projectcalico.org/v3 -kind: GlobalNetworkPolicy -metadata: - creationTimestamp: null - labels: - projectcalico.org/tier: default - name: default.policy1 -spec: - ingress: - - action: Deny - destination: {} - source: - selector: type=='application' - order: 100 - selector: type=='database' - tier: default - types: - - Ingress diff --git a/calicoctl/tests/fv/ipam_test.go b/calicoctl/tests/fv/ipam_test.go index 6cd8d8de3cd..cdafc700510 100644 --- a/calicoctl/tests/fv/ipam_test.go +++ b/calicoctl/tests/fv/ipam_test.go @@ -31,7 +31,7 @@ import ( ipamcmd "github.com/projectcalico/calico/calicoctl/calicoctl/commands/ipam" . "github.com/projectcalico/calico/calicoctl/tests/fv/utils" - libapi "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" @@ -71,7 +71,7 @@ func TestIPAM(t *testing.T) { // Set Calico version in ClusterInformation out, err := SetCalicoVersion(kdd) - Expect(err).ToNot(HaveOccurred()) + Expect(err).ToNot(HaveOccurred(), out) Expect(out).To(ContainSubstring("Calico version set to")) // ipam show with specific unallocated IP. @@ -116,7 +116,7 @@ func TestIPAM(t *testing.T) { var allocatedIP string r, err := regexp.Compile(`(10\.65\.[0-9]+\.)([0-9]+)/26`) Expect(err).NotTo(HaveOccurred()) - for _, line := range strings.Split(out, "\n") { + for line := range strings.SplitSeq(out, "\n") { sm := r.FindStringSubmatch(line) if len(sm) > 0 { ordinalBase, err := strconv.Atoi(sm[2]) @@ -338,7 +338,7 @@ func createNodeForLocalhost(t *testing.T, ctx context.Context, client clientv3.I } } else { t.Log("Creating etcd Node") - node := libapi.NewNode() + node := internalapi.NewNode() node.Name = nodeName Expect(err).NotTo(HaveOccurred()) _, err = client.Nodes().Create(ctx, node, options.SetOptions{}) diff --git a/calicoctl/tests/fv/migrate_ipam_test.go b/calicoctl/tests/fv/migrate_ipam_test.go index f74609a6d2c..c0188418cd4 100644 --- a/calicoctl/tests/fv/migrate_ipam_test.go +++ b/calicoctl/tests/fv/migrate_ipam_test.go @@ -27,7 +27,7 @@ import ( . "github.com/projectcalico/calico/calicoctl/tests/fv/utils" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/ipam" "github.com/projectcalico/calico/libcalico-go/lib/logutils" @@ -75,9 +75,9 @@ func TestDatastoreMigrationIPAM(t *testing.T) { }() // Create a Node resource for this host. - node := libapiv3.NewNode() + node := internalapi.NewNode() node.Name = "node4" - node.Spec.OrchRefs = []libapiv3.OrchRef{ + node.Spec.OrchRefs = []internalapi.OrchRef{ { NodeName: "node4", Orchestrator: "k8s", @@ -182,7 +182,7 @@ func TestDatastoreMigrationIPAM(t *testing.T) { var allocatedIP string r, err := regexp.Compile(`(10\.65\.[0-9]+\.)([0-9]+)/26`) Expect(err).NotTo(HaveOccurred()) - for _, line := range strings.Split(out, "\n") { + for line := range strings.SplitSeq(out, "\n") { sm := r.FindStringSubmatch(line) if len(sm) > 0 { ordinalBase, err := strconv.Atoi(sm[2]) diff --git a/calicoctl/tests/fv/utils/calicoctl.go b/calicoctl/tests/fv/utils/calicoctl.go index 957018ae837..b77e8be8301 100644 --- a/calicoctl/tests/fv/utils/calicoctl.go +++ b/calicoctl/tests/fv/utils/calicoctl.go @@ -25,8 +25,10 @@ import ( log "github.com/sirupsen/logrus" ) -var calicoctl = "/go/src/github.com/projectcalico/calico/calicoctl/bin/calicoctl-linux-" + runtime.GOARCH -var version_helper = "/go/src/github.com/projectcalico/calico/calicoctl/tests/fv/helper/bin/calico_version_helper" +var ( + calicoctl = "/go/src/github.com/projectcalico/calico/calicoctl/bin/calicoctl-linux-" + runtime.GOARCH + version_helper = "/go/src/github.com/projectcalico/calico/calicoctl/tests/fv/helper/bin/calico_version_helper" +) func getEnv(kdd bool) []string { env := []string{"ETCD_ENDPOINTS=http://127.0.0.1:2379"} @@ -40,6 +42,7 @@ func getEnv(kdd bool) []string { } } env = append(env, "K8S_INSECURE_SKIP_TLS_VERIFY=true") + env = append(env, fmt.Sprintf("CALICO_API_GROUP=%s", os.Getenv("CALICO_API_GROUP"))) return env } diff --git a/calicoctl/tests/fv/utils/datastore.go b/calicoctl/tests/fv/utils/datastore.go index f296158dd84..d86dc43c131 100644 --- a/calicoctl/tests/fv/utils/datastore.go +++ b/calicoctl/tests/fv/utils/datastore.go @@ -15,7 +15,9 @@ package utils import ( + "os" "testing" + //nolint:staticcheck // Ignore ST1001: should not use dot imports . "github.com/onsi/gomega" @@ -52,6 +54,7 @@ func RunDatastoreTest(t *testing.T, testFn func(t *testing.T, kdd bool, client c config := apiconfig.NewCalicoAPIConfig() config.Spec.DatastoreType = apiconfig.Kubernetes config.Spec.Kubeconfig = "/go/src/github.com/projectcalico/calico/calicoctl/test-data/kubeconfig.yaml" + config.Spec.CalicoAPIGroup = os.Getenv("CALICO_API_GROUP") client, err := clientv3.New(*config) Expect(err).NotTo(HaveOccurred()) defer func() { diff --git a/calicoctl/tests/st/calicoctl/test_convert.py b/calicoctl/tests/st/calicoctl/test_convert.py deleted file mode 100644 index 49c97939c76..00000000000 --- a/calicoctl/tests/st/calicoctl/test_convert.py +++ /dev/null @@ -1,105 +0,0 @@ -# Copyright (c) 2015-2024 Tigera, Inc. All rights reserved. -# -# 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. -import json -import logging -import copy -import os - -from nose_parameterized import parameterized - -from tests.st.test_base import TestBase -from tests.st.utils.utils import calicoctl -from tests.st.utils.data import * - -logging.basicConfig(level=logging.DEBUG, format="%(message)s") -logger = logging.getLogger(__name__) - -# TODO: applySuccess param relates to 'calicoctl apply' not working correctly -# with a resource of "Kind: List". Once that is fixed, we should be able to -# remove this arg (and consistently check for 'calicoctl apply' success). -convert_files = [ - ("test-data/convert/input/v1/bgppeer-global.yaml", True,), - ("test-data/convert/input/v1/bgppeer-node.yaml", True,), - ("test-data/convert/input/v1/bgppeer-node2.yaml", True,), - ("test-data/convert/input/v1/multi-resource.yaml", False,), - ("test-data/convert/input/v1/node.yaml", False,), - ("test-data/convert/input/v1/test3.yaml", True,), - ("test-data/convert/input/v1/migration/bgppeer.yaml", True,), - ("test-data/convert/input/v1/migration/hostendpoint.yaml", True,), - ("test-data/convert/input/v1/migration/ippool.yaml", True,), - ("test-data/convert/input/v1/migration/node.yaml", True,), - ("test-data/convert/input/v1/migration/policy.yaml", False,), - ("test-data/convert/input/v1/migration/profile.yaml", True,), - ("test-data/convert/input/v1/migration/workloadendpoint.yaml", True,), - ("test-data/convert/input/k8s/k8s-networkpolicy.yaml", True,), - ("test-data/convert/input/k8s/k8s-networkpolicy-invalid.yaml", True,), - ("test-data/convert/input/k8s/k8s-networkpolicy-multiple.yaml", False,), - ("test-data/convert/input/k8s/k8s-networkpolicy-multiple-list.yaml", False,), - ("test-data/convert/input/k8s/k8s-networkpolicy-multiple-invalid.yaml", False,), - ] - -class TestCalicoctlConvert(TestBase): - """ - Test calicoctl convert - """ - - def setUp(self): - super(TestCalicoctlConvert, self).setUp() - - def _test_convert(self, filename, applySuccess, format="yaml"): - """ - Test convert successfully - """ - # Convert the file - rc = calicoctl("convert -o %s -f %s" % (format, filename)) - - # Get expected conversion output filename (file in output/ dir with - # yaml or json extension) - output_filename = (filename.replace("input", "output").split(".")[0] - + "." + format) - - # If expected conversion output exists then assert that the - # conversion is successful, otherwise assert an error occurs - if os.path.isfile(output_filename): - rc.assert_no_error() - with open(output_filename, 'r') as f: - expected_output = f.read().rstrip() - self.assertEqual(rc.output, expected_output) - - # With the converted data to a temp file - with open("/tmp/converted", 'w') as f: - f.write(rc.output) - - # Load the converted data - rc = calicoctl("apply -f /tmp/converted") - if applySuccess: - rc.assert_no_error() - else: - rc.assert_error() - else: - rc.assert_error() - - @parameterized.expand(convert_files) - def test_convert_yaml(self, filename, applySuccess): - """ - Test convert with yaml output. - """ - self._test_convert(filename, applySuccess, format="yaml") - - @parameterized.expand(convert_files) - def test_convert_json(self, filename, applySuccess): - """ - Test convert with json output. - """ - self._test_convert(filename, applySuccess, format="json") diff --git a/calicoctl/tests/st/calicoctl/test_crud.py b/calicoctl/tests/st/calicoctl/test_crud.py index 2654eda2973..bd6e9af3c07 100644 --- a/calicoctl/tests/st/calicoctl/test_crud.py +++ b/calicoctl/tests/st/calicoctl/test_crud.py @@ -15,8 +15,6 @@ import copy import os -from nose_parameterized import parameterized - from tests.st.test_base import TestBase from tests.st.utils.utils import log_and_run, calicoctl, \ API_VERSION, name, ERROR_CONFLICT, NOT_FOUND, NOT_NAMESPACED, \ @@ -596,36 +594,47 @@ def test_file_single_list(self): rc = calicoctl("get workloadendpoints -o yaml") rc.assert_empty_list("WorkloadEndpoint") - @parameterized.expand([ - (ippool_name1_rev1_v4,), - (ipresv_name1_rev1_v4,), - (profile_name1_rev1,), - (globalnetworkpolicy_name1_rev1,), - (stagedglobalnetworkpolicy_name1_rev1,), - (globalnetworkset_name1_rev1,), - (globalnetworkset_name1_rev1_large,), - (hostendpoint_name1_rev1,), - (bgppeer_name1_rev1_v4,), - (node_name1_rev1,), - (tier_name1_rev1,), - ]) + def test_non_namespaced_ippool(self): + self._test_non_namespaced(ippool_name1_rev1_v4) - def test_non_namespaced(self, data): - """ - Test namespace is handled as expected for each non-namespaced resource type. - """ - return self._test_non_namespaced(data) + def test_non_namespaced_ipresv(self): + self._test_non_namespaced(ipresv_name1_rev1_v4) - @parameterized.expand([ - (globalnetworkpolicy_tiered_name2_rev1,), - (stagedglobalnetworkpolicy_tiered_name2_rev1,), - ]) - def test_non_namespaced_tiered(self, data): - """ - Test namespace is handled as expected for each non-namespaced resource type. - """ + def test_non_namespaced_profile(self): + self._test_non_namespaced(profile_name1_rev1) + + def test_non_namespaced_globalnetworkpolicy(self): + self._test_non_namespaced(globalnetworkpolicy_name1_rev1) + + def test_non_namespaced_stagedglobalnetworkpolicy(self): + self._test_non_namespaced(stagedglobalnetworkpolicy_name1_rev1) + + def test_non_namespaced_globalnetworkset(self): + self._test_non_namespaced(globalnetworkset_name1_rev1) + + def test_non_namespaced_globalnetworkset_large(self): + self._test_non_namespaced(globalnetworkset_name1_rev1_large) + + def test_non_namespaced_hostendpoint(self): + self._test_non_namespaced(hostendpoint_name1_rev1) + + def test_non_namespaced_bgppeer(self): + self._test_non_namespaced(bgppeer_name1_rev1_v4) + + def test_non_namespaced_node(self): + self._test_non_namespaced(node_name1_rev1) + + def test_non_namespaced_tier(self): + self._test_non_namespaced(tier_name1_rev1) + + def test_non_namespaced_tiered_globalnetworkpolicy(self): + self._create_admin_tier() + self._test_non_namespaced(globalnetworkpolicy_tiered_name2_rev1) + self._delete_admin_tier() + + def test_non_namespaced_tiered_stagedglobalnetworkpolicy(self): self._create_admin_tier() - self._test_non_namespaced(data) + self._test_non_namespaced(stagedglobalnetworkpolicy_tiered_name2_rev1) self._delete_admin_tier() def _create_admin_tier(self): @@ -695,11 +704,13 @@ def test_ip_reservation_truncation(self): rc.assert_no_error() rc.assert_output_contains("10.0.0.0/28,10.0.1.0/28,10.0.2.0/28,10.0.3.0/28,10.0.4.0/28,10.0.5.0/28,10.0....") - @parameterized.expand([ - (globalnetworkset_name1_rev1_large,), - (networkset_name1_rev1_large,), - ]) - def test_nets_truncation(self, data): + def test_nets_truncation_globalnetworkset(self): + self._test_nets_truncation(globalnetworkset_name1_rev1_large) + + def test_nets_truncation_networkset(self): + self._test_nets_truncation(networkset_name1_rev1_large) + + def _test_nets_truncation(self, data): """ Test that the list of nets is truncated if it's too long. """ @@ -715,11 +726,13 @@ def test_nets_truncation(self, data): rc.assert_no_error() rc.assert_output_contains("10.0.0.0/28,10.0.1.0/28,10.0.2.0/28,10.0.3.0/28,10.0.4.0/28,10.0.5.0/28,10.0....") - @parameterized.expand([ - (globalnetworkset_name1_rev1,), - (networkset_name1_rev1,), - ]) - def test_nets_no_truncation(self, data): + def test_nets_no_truncation_globalnetworkset(self): + self._test_nets_no_truncation(globalnetworkset_name1_rev1) + + def test_nets_no_truncation_networkset(self): + self._test_nets_no_truncation(networkset_name1_rev1) + + def _test_nets_no_truncation(self, data): """ Test that the list of nets is shown in full if not too long. """ @@ -730,28 +743,26 @@ def test_nets_no_truncation(self, data): rc.assert_no_error() rc.assert_output_contains("10.0.0.1,11.0.0.0/16,feed:beef::1,dead:beef::96") - @parameterized.expand([ - (networkpolicy_name1_rev1,), - (stagednetworkpolicy_name1_rev1,), - (networkset_name1_rev1,), - (workloadendpoint_name1_rev1,), - ]) - def test_namespaced(self, data): - """ - Tests namespace is handled as expected for each namespaced resource type. - """ - self._test_namespaced(data) + def test_namespaced_networkpolicy(self): + self._test_namespaced(networkpolicy_name1_rev1) - @parameterized.expand([ - (networkpolicy_tiered_name2_rev1,), - (stagednetworkpolicy_tiered_name2_rev1,), - ]) - def test_namespaced_tiered(self, data): - """ - Tests namespace is handled as expected for each namespaced resource type. - """ + def test_namespaced_stagednetworkpolicy(self): + self._test_namespaced(stagednetworkpolicy_name1_rev1) + + def test_namespaced_networkset(self): + self._test_namespaced(networkset_name1_rev1) + + def test_namespaced_workloadendpoint(self): + self._test_namespaced(workloadendpoint_name1_rev1) + + def test_namespaced_tiered_networkpolicy(self): + self._create_admin_tier() + self._test_namespaced(networkpolicy_tiered_name2_rev1) + self._delete_admin_tier() + + def test_namespaced_tiered_stagednetworkpolicy(self): self._create_admin_tier() - self._test_namespaced(data) + self._test_namespaced(stagednetworkpolicy_tiered_name2_rev1) self._delete_admin_tier() def _test_namespaced(self, data): @@ -867,56 +878,6 @@ def _test_namespaced(self, data): rc = calicoctl("delete", data2) rc.assert_no_error() - @parameterized.expand([ - (globalnetworkpolicy_os_name1_rev1, False), - (stagedglobalnetworkpolicy_os_name1_rev1, False), - (networkpolicy_os_name1_rev1, True), - (stagednetworkpolicy_os_name1_rev1, True), - ]) - def test_os_compat(self, data, is_namespaced): - """ - Test policy CRUD commands with calico (open source) style manifests. - """ - # Clone the data so that we can modify the metadata parms. - data1 = copy.deepcopy(data) - - # Create the policy without a tier present in the name or spec. - rc = calicoctl("create", data=data1) - rc.assert_no_error() - - # On get, we expect name to be the same as the name the policy was created with as - # well as have the tier field and value present in the spec. - # First we check with the name without tier in the name. - if is_namespaced: - rc = calicoctl("get %s %s --namespace default -o yaml" % (data['kind'], data['metadata']['name'])) - else: - rc = calicoctl("get %s %s -o yaml" % (data['kind'], data['metadata']['name'])) - data1['metadata']['name'] = data1['metadata']['name'] - data1['spec']['tier'] = 'default' - data1 = add_tier_label(data1) - rc.assert_data(data1) - - # Then we check with the tiered policy name. - if is_namespaced: - rc = calicoctl("get %s %s --namespace default -o yaml" % (data['kind'], data1['metadata']['name'])) - else: - rc = calicoctl("get %s %s -o yaml" % (data['kind'], data1['metadata']['name'])) - rc.assert_data(data1) - - # Deleting without a tiered policy name will delete the correct policy - if is_namespaced: - rc = calicoctl("delete %s %s --namespace default" % (data['kind'], data['metadata']['name'])) - else: - rc = calicoctl("delete %s %s" % (data['kind'], data['metadata']['name'])) - rc.assert_no_error() - - # And we re-check to make sure the deleted policy is not present. - if is_namespaced: - rc = calicoctl("get %s %s --namespace default -o yaml" % (data['kind'], data['metadata']['name'])) - else: - rc = calicoctl("get %s %s -o yaml" % (data['kind'], data['metadata']['name'])) - rc.assert_error(NOT_FOUND) - def test_bgpconfig(self): """ Test CRUD commands behave as expected on the BGP configuration resource: @@ -1034,7 +995,7 @@ def test_clusterinfo(self): rc = calicoctl("get clusterinfo %s -o yaml" % name(clusterinfo_name1_rev1)) rc.assert_no_error() # Check the GUID is populated. - self.assertRegexpMatches(rc.decoded["spec"]["clusterGUID"], "^[a-f0-9]{32}$") + self.assertRegex(rc.decoded["spec"]["clusterGUID"], r"^[a-f0-9]{32}$") # The GUID is unpredictable so tweak our test data to match it. ci = copy.deepcopy(clusterinfo_name1_rev1) ci["spec"]["clusterGUID"] = rc.decoded["spec"]["clusterGUID"] @@ -1083,11 +1044,13 @@ def test_kubecontrollersconfig(self): rc = calicoctl("delete kubecontrollersconfig %s" % name(rev2)) rc.assert_no_error() - @parameterized.expand([ - ('create', 'replace'), - ('apply', 'apply'), - ]) - def test_metadata_unchanged(self, create_cmd, update_cmd): + def test_metadata_unchanged_create_replace(self): + self._test_metadata_unchanged('create', 'replace') + + def test_metadata_unchanged_apply(self): + self._test_metadata_unchanged('apply', 'apply') + + def _test_metadata_unchanged(self, create_cmd, update_cmd): """ Test that the metadata fields other than labels and annotations cannot be changed in create and update operations by applying a resource twice. @@ -1164,7 +1127,6 @@ def test_export_flag(self): rc.assert_no_error() rev1 = rc.decoded self.assertNotIn('uid', rev1['metadata']) - self.assertIsNone(rev1['metadata']['creationTimestamp']) self.assertNotIn('namespace', rev1['metadata']) self.assertNotIn('resourceVersion', rev1['metadata']) self.assertEqual(rev1['metadata']['name'], rev0['metadata']['name']) @@ -1221,7 +1183,6 @@ def test_export_flag(self): rc.assert_no_error() rev1 = rc.decoded self.assertNotIn('uid', rev1['metadata']) - self.assertIsNone(rev1['metadata']['creationTimestamp']) self.assertNotIn('namespace', rev1['metadata']) self.assertNotIn('resourceVersion', rev1['metadata']) self.assertEqual(rev1['metadata']['name'], rev0['metadata']['name']) @@ -1294,21 +1255,23 @@ def test_tier_list_order(self): rc.assert_no_error() tierList = rc.decoded - # Validate the tiers are ordered correctly. Default should have a value of 1M and should be placed last. - # adminnetworkpolicy has a value of 1K, and should be second one. + # Validate the tiers are ordered correctly. self.assertEqual(tierList['items'][0]['metadata']['name'], name(tier_name2_rev1)) - self.assertEqual(tierList['items'][1]['metadata']['name'], 'adminnetworkpolicy') + self.assertEqual(tierList['items'][1]['metadata']['name'], 'kube-admin') self.assertEqual(tierList['items'][2]['metadata']['name'], name(tier_name1_rev1)) self.assertEqual(tierList['items'][3]['metadata']['name'], 'default') + self.assertEqual(tierList['items'][4]['metadata']['name'], 'kube-baseline') # Delete the resources rc = calicoctl("delete", data=resources) - @parameterized.expand([ - ('replace'), - ('apply'), - ]) - def test_disallow_update_old_resource_version(self, update_cmd): + def test_disallow_update_old_resource_version_replace(self): + self._test_disallow_update_old_resource_version('replace') + + def test_disallow_update_old_resource_version_apply(self): + self._test_disallow_update_old_resource_version('apply') + + def _test_disallow_update_old_resource_version(self, update_cmd): """ Test that we disallow updates on resources with old resource versions. """ @@ -1607,7 +1570,7 @@ def test_validate_rejects_datastore_args(self): rc.assert_error() rc.assert_output_contains("Usage:") - # Test --namespace argument rejection + # Test --namespace argument rejection rc = calicoctl("validate --namespace=test -f /tmp/test.yaml", no_config=True) rc.assert_error() rc.assert_output_contains("Usage:") @@ -1659,7 +1622,7 @@ def test_validate_multiple_resources_with_calico_validation_failure(self): valid_ippool = ippool_name1_rev1_v4 invalid_networkpolicy = { 'apiVersion': API_VERSION, - 'kind': 'NetworkPolicy', + 'kind': 'NetworkPolicy', 'metadata': { 'name': 'invalid-policy', 'namespace': 'test' @@ -2433,7 +2396,7 @@ class InvalidData(TestBase): 'node': 'node1', 'peerIP': '192.168.0.250', 'scope': 'node'} - }, 'cannot unmarshal number into Go value of type string'), + }, 'cannot unmarshal number into Go struct field BGPPeerSpec.spec.asNumber of type string'), ("bgpPeer-invalidIP", { 'apiVersion': API_VERSION, 'kind': 'BGPPeer', @@ -2531,7 +2494,7 @@ class InvalidData(TestBase): 'source': {}}], 'order': 100000, 'selector': ""} - }, 'cannot unmarshal number 65536 into Go value of type uint16'), + }, 'cannot unmarshal number 65536'), # https://github.com/projectcalico/libcalico-go/issues/248 ("policy-invalidHighPortinRange", { 'apiVersion': API_VERSION, @@ -2715,7 +2678,7 @@ class InvalidData(TestBase): 'metadata': {'name': 'invalid-ipip-1'}, 'spec': {'disabled': 'True', # disabled value must be a bool 'cidr': "10.0.1.0/24"} - }, "cannot parse string 'True' into field IPPoolSpec.disabled of type bool"), + }, "cannot unmarshal string into Go struct field IPPoolSpec.spec.disabled of type bool"), ("pool-invalidIpIp2", { 'apiVersion': API_VERSION, 'kind': 'IPPool', @@ -2723,7 +2686,7 @@ class InvalidData(TestBase): 'spec': { 'disabled': 'Maybe', 'cidr': "10.0.1.0/24"} - }, "cannot parse string 'Maybe' into field IPPoolSpec.disabled of type bool"), + }, "cannot unmarshal string into Go struct field IPPoolSpec.spec.disabled of type bool"), ("profile-ICMPtype", { 'apiVersion': API_VERSION, 'kind': 'Profile', @@ -2814,18 +2777,13 @@ def check_only_default_profile_returned(self, testdata): '- apiVersion: %s\n' ' kind: %s\n' ' metadata:\n' - ' creationTimestamp: null\n' ' name: projectcalico-default-allow\n' ' resourceVersion: "1"\n' ' spec:\n' ' egress:\n' ' - action: Allow\n' - ' destination: {}\n' - ' source: {}\n' ' ingress:\n' ' - action: Allow\n' - ' destination: {}\n' - ' source: {}\n' 'kind: %sList\n' 'metadata:\n' ' resourceVersion: ' % (API_VERSION, API_VERSION, testdata['kind'], testdata['kind']) @@ -2834,9 +2792,89 @@ def check_only_default_profile_returned(self, testdata): def setUp(self): super(InvalidData, self).setUp() - @parameterized.expand(testdata) - def test_invalid_profiles_rejected(self, name, testdata, error): + @classmethod + def _find_testdata(cls, test_name): + for entry in cls.testdata: + if entry[0] == test_name: + return entry + raise ValueError("Unknown testdata: %s" % test_name) + + def test_invalid_bgppeer_asnum(self): + self._test_invalid_profiles_rejected(*self._find_testdata("bgpPeer-invalidASnum")) + + def test_invalid_bgppeer_ip(self): + self._test_invalid_profiles_rejected(*self._find_testdata("bgpPeer-invalidIP")) + + def test_invalid_bgppeer_apiversion(self): + self._test_invalid_profiles_rejected(*self._find_testdata("bgpPeer-apiversion")) + + def test_invalid_bgppeer_ipv6(self): + self._test_invalid_profiles_rejected(*self._find_testdata("bgpPeer-invalidIpv6")) + + def test_invalid_bgppeer_nodename(self): + self._test_invalid_profiles_rejected(*self._find_testdata("bgpPeer-invalidnodename")) + + def test_invalid_bgppeer_unrecognised_field(self): + self._test_invalid_profiles_rejected(*self._find_testdata("bgpPeer-unrecognisedfield")) + def test_invalid_hostendpoint_interface(self): + self._test_invalid_profiles_rejected(*self._find_testdata("hostEndpoint-invalidInterface")) + + def test_invalid_policy_high_port_in_list(self): + self._test_invalid_profiles_rejected(*self._find_testdata("policy-invalidHighPortinList")) + + def test_invalid_policy_high_port_in_range(self): + self._test_invalid_profiles_rejected(*self._find_testdata("policy-invalidHighPortinRange")) + + def test_invalid_policy_low_port_in_range(self): + self._test_invalid_profiles_rejected(*self._find_testdata("policy-invalidLowPortinRange")) + + def test_invalid_policy_low_port_in_list(self): + self._test_invalid_profiles_rejected(*self._find_testdata("policy-invalidLowPortinList")) + + def test_invalid_policy_reversed_range(self): + self._test_invalid_profiles_rejected(*self._find_testdata("policy-invalidReversedRange")) + + def test_invalid_policy_action(self): + self._test_invalid_profiles_rejected(*self._find_testdata("policy-invalidAction")) + + def test_invalid_policy_networkpolicy_name_rejected(self): + self._test_invalid_profiles_rejected(*self._find_testdata("policy-NetworkPolicyNameRejected")) + + def test_invalid_pool_net1(self): + self._test_invalid_profiles_rejected(*self._find_testdata("pool-invalidNet1")) + + def test_invalid_pool_net2(self): + self._test_invalid_profiles_rejected(*self._find_testdata("pool-invalidNet2")) + + def test_invalid_pool_net3(self): + self._test_invalid_profiles_rejected(*self._find_testdata("pool-invalidNet3")) + + def test_invalid_pool_net4(self): + self._test_invalid_profiles_rejected(*self._find_testdata("pool-invalidNet4")) + + def test_invalid_pool_net6(self): + self._test_invalid_profiles_rejected(*self._find_testdata("pool-invalidNet6")) + + def test_invalid_pool_net7(self): + self._test_invalid_profiles_rejected(*self._find_testdata("pool-invalidNet7")) + + def test_invalid_pool_net8(self): + self._test_invalid_profiles_rejected(*self._find_testdata("pool-invalidNet8")) + + def test_invalid_pool_ipip1(self): + self._test_invalid_profiles_rejected(*self._find_testdata("pool-invalidIpIp1")) + + def test_invalid_pool_ipip2(self): + self._test_invalid_profiles_rejected(*self._find_testdata("pool-invalidIpIp2")) + + def test_invalid_profile_icmp_type(self): + self._test_invalid_profiles_rejected(*self._find_testdata("profile-ICMPtype")) + + def test_invalid_profile_icmp_code(self): + self._test_invalid_profiles_rejected(*self._find_testdata("profile-ICMPcode")) + + def _test_invalid_profiles_rejected(self, name, testdata, error): log_and_run("cat << EOF > %s\n%s" % ("/tmp/testfile.yaml", testdata)) ctl = calicoctl("create", testdata) @@ -2850,9 +2888,10 @@ def test_invalid_profiles_rejected(self, name, testdata, error): # Assert that we saw the correct error being reported ctl.assert_error(error) - @parameterized.expand(compound_test_data) - def test_invalid_compound_profiles_rejected(self, name, testdata, errors): + def test_invalid_compound_config(self): + self._test_invalid_compound_profiles_rejected(*self.compound_test_data[0]) + def _test_invalid_compound_profiles_rejected(self, name, testdata, errors): log_and_run("cat << EOF > %s\n%s" % ("/tmp/testfile.yaml", testdata)) ctl = calicoctl("create", testdata) @@ -2863,7 +2902,6 @@ def test_invalid_compound_profiles_rejected(self, name, testdata, errors): else: self.check_no_data_in_store(data) - # Assert that we saw the correct error being reported for value in errors.values(): ctl.assert_error(value) diff --git a/calicoctl/tests/st/calicoctl/test_ipam_split.py b/calicoctl/tests/st/calicoctl/test_ipam_split.py index 9546e9f784f..81ff6365be2 100644 --- a/calicoctl/tests/st/calicoctl/test_ipam_split.py +++ b/calicoctl/tests/st/calicoctl/test_ipam_split.py @@ -15,8 +15,6 @@ import copy import os -from nose_parameterized import parameterized - from tests.st.test_base import TestBase from tests.st.utils.utils import log_and_run, calicoctl, \ API_VERSION, name, namespace, ERROR_CONFLICT, NOT_FOUND, NOT_NAMESPACED, \ diff --git a/calicoctl/tests/st/calicoctl/test_migrate.py b/calicoctl/tests/st/calicoctl/test_migrate.py index c6f4bde3ef3..4dde6e89d21 100644 --- a/calicoctl/tests/st/calicoctl/test_migrate.py +++ b/calicoctl/tests/st/calicoctl/test_migrate.py @@ -15,8 +15,6 @@ import copy import os -from nose_parameterized import parameterized - from tests.st.test_base import TestBase from tests.st.utils.utils import log_and_run, calicoctl, \ API_VERSION, name, namespace, ERROR_CONFLICT, NOT_FOUND, NOT_NAMESPACED, \ diff --git a/calicoctl/tests/st/calicoctl/test_upgrade.py b/calicoctl/tests/st/calicoctl/test_upgrade.py deleted file mode 100644 index dd3f389d482..00000000000 --- a/calicoctl/tests/st/calicoctl/test_upgrade.py +++ /dev/null @@ -1,122 +0,0 @@ -# Copyright (c) 2015-2017 Tigera, Inc. All rights reserved. -# -# 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. - -import logging -import os -import random - -import yaml -import json -from nose_parameterized import parameterized - -from tests.st.utils.utils import calicoctl, \ - name, wipe_etcd, get_ip, clean_calico_data -from tests.st.utils.v1_data import data - -ETCD_SCHEME = os.environ.get("ETCD_SCHEME", "http") -ETCD_CA = os.environ.get("ETCD_CA_CERT_FILE", "") -ETCD_CERT = os.environ.get("ETCD_CERT_FILE", "") -ETCD_KEY = os.environ.get("ETCD_KEY_FILE", "") -ETCD_HOSTNAME_SSL = "etcd-authority-ssl" - -logging.basicConfig(level=logging.DEBUG, format="%(message)s") -logger = logging.getLogger(__name__) - -tests = [ - ("bgppeer_long_node_name", False), - ("bgppeer_dotted_asn", False), - ("hep_bad_label", True, "a qualified name must consist of alphanumeric characters"), - ("hep_tame", False), - ("hep_mixed_ip", False), - ("hep_label_too_long", True, "name part must be no more than 63 characters"), - ("hep_long_fields", False), - ("hep_name_too_long", True, "name is too long by 11 bytes"), - ("ippool_mixed", False), - ("ippool_v4_small", False), - ("ippool_v4_large", False), - ("node_long_name", False), - ("node_tame", False), - ("policy_long_name", False), - ("policy_big", False), - ("policy_tame", False), - ("profile_big", False), - ("profile_tame", False), - ("wep_bad_workload_id", True, "field must not begin with a '-'"), - ("wep_lots_ips", False), - ("wep_similar_name", True, - "workload was not added through the Calico CNI plugin and cannot be converted"), - ("wep_similar_name_2", False), - ("do_not_track", False), - ("prednat_policy", False), - - # profile_long_labels Fails validation after conversion, but error is not clear. - # TODO: Add some error text once new validator lands and gives this test a sane error message - ("profile_long_labels", True), -] -random.shuffle(tests) - -def _test_converter(testname, fail_expected, error_text=None, format="yaml"): - """ - Convert a v1 object to v3, then apply the result and read it back. - """ - # Let's start every test afresh - wipe_etcd(get_ip()) - testdata = data[testname] - - # Convert data to V3 API using the tool under test - rc = calicoctl("convert -o %s" % format, data=testdata, format=format) - if not fail_expected: - logger.debug("Trying to convert manifest from V1 to V3") - rc.assert_no_error() - if format == "yaml": - parsed_output = yaml.safe_load(rc.output) - else: - parsed_output = json.loads(rc.output) - # Get the converted data and clean it up (remove fields we don't care about) - converted_data = clean_calico_data(parsed_output) - original_resource = rc - - # Apply the converted data - rc = calicoctl("create", data=original_resource.output, format=format) - logger.debug("Trying to create resource using converted manifest") - rc.assert_no_error() - rc = calicoctl("get %s %s -o yaml" % (converted_data['kind'], name(converted_data))) - - # Comparison here needs to be against cleaned versions of data to remove Creation Timestamp - logger.debug("Comparing 'get'ted output with original converted yaml") - cleaned_output = yaml.safe_dump( - clean_calico_data( - yaml.safe_load(rc.output), - extra_keys_to_remove=['projectcalico.org/orchestrator'] - ) - ) - original_resource.assert_data(cleaned_output, format=format) - else: - rc.assert_error(error_text) - -@parameterized(tests) -def test_converter_yaml(testname, fail_expected, error_text=None): - """ - Convert a v1 object to v3, then apply the result and read it back. - """ - test_converter_yaml.__name__ = "yaml_" +testname - _test_converter(testname, fail_expected, error_text=error_text, format="yaml") - -@parameterized(tests) -def test_converter_json(testname, fail_expected, error_text=None): - """ - Convert a v1 object to v3, then apply the result and read it back. - """ - test_converter_json.__name__ = "json_" + testname - _test_converter(testname, fail_expected, error_text=error_text, format="json") diff --git a/calicoctl/tests/st/utils/data.py b/calicoctl/tests/st/utils/data.py index a14e1f6fd33..bff3088ff6f 100644 --- a/calicoctl/tests/st/utils/data.py +++ b/calicoctl/tests/st/utils/data.py @@ -26,11 +26,11 @@ # resource). import netaddr -from utils import API_VERSION +from .utils import API_VERSION # Large list of CIDRs for testing truncation of certain fields. many_nets = [] -for i in xrange(10000): +for i in range(10000): many_nets.append("10.%s.%s.0/28" % (i >> 8, i % 256)) @@ -506,51 +506,6 @@ } } -networkpolicy_os_name1_rev1 = { - 'apiVersion': API_VERSION, - 'kind': 'NetworkPolicy', - 'metadata': { - 'name': 'os-policy-mypolicy1', - 'namespace': 'default' - }, - 'spec': { - 'order': 100, - 'selector': "type=='database'", - 'types': ['Ingress', 'Egress'], - 'egress': [ - { - 'action': 'Allow', - 'source': { - 'selector': "type=='application'"}, - }, - ], - 'ingress': [ - { - 'ipVersion': 4, - 'action': 'Deny', - 'destination': { - 'notNets': ['10.3.0.0/16'], - 'notPorts': ['110:1050'], - 'notSelector': "type=='apples'", - 'nets': ['10.2.0.0/16'], - 'ports': ['100:200'], - 'selector': "type=='application'", - }, - 'protocol': 'TCP', - 'source': { - 'notNets': ['10.1.0.0/16'], - 'notPorts': [1050], - 'notSelector': "type=='database'", - 'nets': ['10.0.0.0/16'], - 'ports': [1234, '10:1024'], - 'selector': "type=='application'", - 'namespaceSelector': 'has(role)', - } - } - ], - } -} - # # Staged Network Policy # @@ -703,52 +658,6 @@ } } -stagednetworkpolicy_os_name1_rev1 = { - 'apiVersion': API_VERSION, - 'kind': 'StagedNetworkPolicy', - 'metadata': { - 'name': 'os-policy-mypolicy1', - 'namespace': 'default' - }, - 'spec': { - 'stagedAction': 'Set', - 'order': 100, - 'selector': "type=='database'", - 'types': ['Ingress', 'Egress'], - 'egress': [ - { - 'action': 'Allow', - 'source': { - 'selector': "type=='application'"}, - }, - ], - 'ingress': [ - { - 'ipVersion': 4, - 'action': 'Deny', - 'destination': { - 'notNets': ['10.3.0.0/16'], - 'notPorts': ['110:1050'], - 'notSelector': "type=='apples'", - 'nets': ['10.2.0.0/16'], - 'ports': ['100:200'], - 'selector': "type=='application'", - }, - 'protocol': 'TCP', - 'source': { - 'notNets': ['10.1.0.0/16'], - 'notPorts': [1050], - 'notSelector': "type=='database'", - 'nets': ['10.0.0.0/16'], - 'ports': [1234, '10:1024'], - 'selector': "type=='application'", - 'namespaceSelector': 'has(role)', - } - } - ], - } -} - # # Staged Kubernetes Network Policy # @@ -923,50 +832,6 @@ } } -globalnetworkpolicy_os_name1_rev1 = { - 'apiVersion': API_VERSION, - 'kind': 'GlobalNetworkPolicy', - 'metadata': { - 'name': 'os-policy-mypolicy1', - }, - 'spec': { - 'order': 100, - 'selector': "type=='database'", - 'types': ['Ingress', 'Egress'], - 'egress': [ - { - 'action': 'Allow', - 'source': { - 'selector': "type=='application'"}, - }, - ], - 'ingress': [ - { - 'ipVersion': 4, - 'action': 'Deny', - 'destination': { - 'notNets': ['10.3.0.0/16'], - 'notPorts': ['110:1050'], - 'notSelector': "type=='apples'", - 'nets': ['10.2.0.0/16'], - 'ports': ['100:200'], - 'selector': "type=='application'", - }, - 'protocol': 'TCP', - 'source': { - 'notNets': ['10.1.0.0/16'], - 'notPorts': [1050], - 'notSelector': "type=='database'", - 'nets': ['10.0.0.0/16'], - 'ports': [1234, '10:1024'], - 'selector': "type=='application'", - 'namespaceSelector': 'has(role)', - } - } - ], - } -} - networkpolicy_name3_rev1 = { 'apiVersion': API_VERSION, 'kind': 'NetworkPolicy', @@ -1121,51 +986,6 @@ } } -stagedglobalnetworkpolicy_os_name1_rev1 = { - 'apiVersion': API_VERSION, - 'kind': 'StagedGlobalNetworkPolicy', - 'metadata': { - 'name': 'os-policy-mystagedpolicy1', - }, - 'spec': { - 'stagedAction': "Set", - 'order': 100, - 'selector': "type=='database'", - 'types': ['Ingress', 'Egress'], - 'egress': [ - { - 'action': 'Allow', - 'source': { - 'selector': "type=='application'"}, - }, - ], - 'ingress': [ - { - 'ipVersion': 4, - 'action': 'Deny', - 'destination': { - 'notNets': ['10.3.0.0/16'], - 'notPorts': ['110:1050'], - 'notSelector': "type=='apples'", - 'nets': ['10.2.0.0/16'], - 'ports': ['100:200'], - 'selector': "type=='application'", - }, - 'protocol': 'TCP', - 'source': { - 'notNets': ['10.1.0.0/16'], - 'notPorts': [1050], - 'notSelector': "type=='database'", - 'nets': ['10.0.0.0/16'], - 'ports': [1234, '10:1024'], - 'selector': "type=='application'", - 'namespaceSelector': 'has(role)', - } - } - ], - } -} - # # Global network sets @@ -1246,7 +1066,7 @@ # - Kubernetes' gRPC API has a 4MB message size limit. # - etcdv3 has a 1MB value size limit. many_nets = [] -for i in xrange(10000): +for i in range(10000): many_nets.append("10.%s.%s.0/28" % (i >> 8, i % 256)) networkset_name1_rev1_large = { 'apiVersion': API_VERSION, @@ -1626,9 +1446,7 @@ 'ipipMTU': 1521, 'ipsetsRefreshInterval': '44s', 'iptablesFilterAllowAction': 'Return', - 'iptablesLockFilePath': '/run/fun', 'iptablesLockProbeInterval': '500ms', - 'iptablesLockTimeout': '22s', 'iptablesMangleAllowAction': 'Accept', 'iptablesMarkMask': 0xff0000, 'iptablesPostWriteCheckInterval': '12s', @@ -1701,9 +1519,7 @@ 'ipipMTU': 1521, 'ipsetsRefreshInterval': '44s', 'iptablesFilterAllowAction': 'Return', - 'iptablesLockFilePath': '/run/fun', 'iptablesLockProbeInterval': '500ms', - 'iptablesLockTimeout': '22s', 'iptablesMangleAllowAction': 'Accept', 'iptablesMarkMask': 0xff0000, 'iptablesPostWriteCheckInterval': '12s', @@ -1747,9 +1563,7 @@ 'ipipMTU': 1521, 'ipsetsRefreshInterval': '44s', 'iptablesFilterAllowAction': 'Return', - 'iptablesLockFilePath': '/run/fun', 'iptablesLockProbeInterval': '500ms', - 'iptablesLockTimeout': '22s', 'iptablesMangleAllowAction': 'Accept', 'iptablesMarkMask': 0xff0000, 'iptablesPostWriteCheckInterval': '12s', diff --git a/calicoctl/tests/st/utils/utils.py b/calicoctl/tests/st/utils/utils.py index 0d2cacfd339..266a2d5f42a 100644 --- a/calicoctl/tests/st/utils/utils.py +++ b/calicoctl/tests/st/utils/utils.py @@ -21,8 +21,6 @@ from subprocess import CalledProcessError from subprocess import check_output, STDOUT -import termios - import json import logging from pprint import pformat @@ -97,7 +95,7 @@ def assert_data(self, data, format="yaml", text=None): cleaned = clean_calico_data(self.decoded) print(self.decoded) - assert cmp(cleaned, data) == 0, \ + assert cleaned == data, \ "Items are not the same. Difference is:\n %s" % \ pformat(DeepDiff(cleaned, data), indent=2) @@ -281,7 +279,7 @@ def calicoctl(command, data=None, load_as_stdin=False, format="yaml", only_stdou output = log_and_run(full_cmd, stderr=(None if only_stdout else STDOUT)) return CalicoctlOutput(full_cmd, output) except CalledProcessError as e: - return CalicoctlOutput(full_cmd, e.output, error=e.returncode) + return CalicoctlOutput(full_cmd, e.output.decode(), error=e.returncode) def clean_calico_data(data, extra_keys_to_remove=None): @@ -314,7 +312,7 @@ def clean_elem(elem, extra_keys): if extra_keys is not None: for extra_key in extra_keys: del_keys.append(extra_key) - for k, v in elem.iteritems(): + for k, v in elem.items(): clean_elem(v, extra_keys) if v is None or v == {}: del_keys.append(k) @@ -372,7 +370,7 @@ def decode_json_yaml(value): def find_and_format_creation_timestamp(decoded): if decoded: if 'items' in decoded: - for i in xrange(len(decoded['items'])): + for i in range(len(decoded['items'])): decoded['items'][i] = format_creation_timestamp(decoded['items'][i]) else: decoded = format_creation_timestamp(decoded) @@ -443,12 +441,6 @@ def get_ip(v6=False): return ip -# Some of the commands we execute like to mess with the TTY configuration, -# which can break the output formatting. As a workaround, save off the -# terminal settings and restore them after each command. -_term_settings = termios.tcgetattr(sys.stdin.fileno()) - - def log_and_run(command, raise_exception_on_failure=True, stderr=STDOUT): def log_output(results): if results is None: @@ -460,21 +452,15 @@ def log_output(results): try: logger.info("%s", command) - try: - results = check_output(command, shell=True, stderr=stderr).rstrip() - finally: - # Restore terminal settings in case the command we ran manipulated - # them. Note: under concurrent access, this is still not a perfect - # solution since another thread's child process may break the - # settings again before we log below. - termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, _term_settings) + results = check_output(command, shell=True, stderr=stderr).rstrip() + results = results.decode() log_output(results) return results except CalledProcessError as e: # Wrap the original exception with one that gives a better error # message (including command output). logger.info(" # Return code: %s", e.returncode) - log_output(e.output) + log_output(e.output.decode()) if raise_exception_on_failure: raise e @@ -522,13 +508,11 @@ def wipe_etcd(ip): if ETCD_SCHEME == "https": # Etcd is running with SSL/TLS, require key/certificates etcd_container_name = "calico-etcd-ssl" - tls_vars = ("ETCDCTL_CACERT=/etc/calico/certs/ca.pem " + - "ETCDCTL_CERT=/etc/calico/certs/client.pem " + - "ETCDCTL_KEY=/etc/calico/certs/client-key.pem ") + tls_vars = ("-e ETCDCTL_CACERT=/etc/calico/certs/ca.pem " + + "-e ETCDCTL_CERT=/etc/calico/certs/client.pem " + + "-e ETCDCTL_KEY=/etc/calico/certs/client-key.pem ") - check_output("docker exec " + etcd_container_name + " sh -c '" + tls_vars + - "ETCDCTL_API=3 etcdctl del --prefix /calico" + - "'", shell=True) + check_output("docker exec " + tls_vars + etcd_container_name + " etcdctl del --prefix /calico", shell=True) def make_list(kind, items): @@ -614,4 +598,4 @@ def set_cluster_version(calico_version="", kdd=False): output = log_and_run(full_cmd, stderr=STDOUT) return CalicoctlOutput(full_cmd, output) except CalledProcessError as e: - return CalicoctlOutput(full_cmd, e.output, error=e.returncode) + return CalicoctlOutput(full_cmd, e.output.decode(), error=e.returncode) diff --git a/calicoctl/tests/st/utils/v1_data.py b/calicoctl/tests/st/utils/v1_data.py deleted file mode 100644 index 601f56313d9..00000000000 --- a/calicoctl/tests/st/utils/v1_data.py +++ /dev/null @@ -1,486 +0,0 @@ -# Copyright (c) 2017-2024 Tigera, Inc. All rights reserved. -# -# 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. - -data = {} -data['bgppeer_long_node_name'] = { - 'apiVersion': 'v1', - 'kind': 'bgpPeer', - 'metadata': { - 'scope': 'node', - 'node': '.123Im_a_Little.Teapot-Short_And-Stout.Heres-My-Handle_Heres_My.Spout.Im_Also.A.Very.LongNodeNameTryingTo-CatchOut_UpgradeCode75', - 'peerIP': '192.168.255.255', - }, - 'spec': { - 'asNumber': "4294967294", - }, -} -data['bgppeer_dotted_asn'] = { - 'apiVersion': 'v1', - 'kind': 'bgpPeer', - 'metadata': { - 'scope': 'global', - 'peerIP': '2006::2:1', - }, - 'spec': { - 'asNumber': "1.10", - }, -} -data['hep_tame'] = { - 'apiVersion': 'v1', - 'kind': 'hostEndpoint', - 'metadata': {'labels': {'type': 'production'}, - 'name': 'eth0', - 'node': 'myhost'}, - 'spec': {'expectedIPs': ['192.168.0.1', '192.168.0.2'], - 'interfaceName': 'eth0', - 'profiles': ['profile1', 'profile2']} -} -data['hep_long_fields'] = { - 'apiVersion': 'v1', - 'kind': 'hostEndpoint', - 'metadata': {'labels': { - '8roper.evil/02.key_name.which.is-also_very.long...1234567890.p': 'frontendFrontEnd.0123456789-_-23wdffrontendFrontEnd.0124679-_-0', - 'calico/k8s_ns': 'default', - 'type': 'type-endFrontEnd.0123456789-_-23wdffrontendFrontEnd.0124679-_-0'}, - 'name': '.123Im_a_LongInterfaceNameTryingToCatchOutUpgradeCode75', - 'node': '.123Im_a_Little.Teapot-Short_And-Stout.Heres-My-Handle_Heres_My.Spout.Im_Also.A.Very.LongNodeNameTryingTo-CatchOut_UpgradeCode75'}, - 'spec': {'expectedIPs': ['fd00:1::321'], - 'interfaceName': 'eth0', - 'profiles': ['profile1', 'profile2']} -} -data['hep_label_too_long'] = { - 'apiVersion': 'v1', - 'kind': 'hostEndpoint', - 'metadata': {'labels': { - '8roper.evil/02.key_name.which.is-also_very.long...1234567890..proper.evil-02.key_name.which.is-also_very.long...1234567890..proper.evil-02.Key_Name.which.is-also_very.long...1234567890..proper.evil-02.key_name.which.is-also_very.long...1234567890..proper.evil-02.Key_name.which.is-also_very.long...1234567890..proper.evil-02.key_name.which.is-also_very.long...1234567890..proper.evil-02.key_Name.which.is-also_very.long...1234567890..proper.evil-02.key_name.which.is-also_very.long...1234567890..proper.evil-02.9': 'frontendFrontEnd.0123456789-_-23wdffrontendFrontEnd.0124679-_-0', - 'calico/k8s_ns': 'default', - 'type': 'type-endFrontEnd.0123456789-_-23wdffrontendFrontEnd.0124679-_-0'}, - 'name': '.123Im_a_Little.Teapot-Short_And-Stout.Heres-My-Handle_Her.Im_AlsoAVeryLongInterfaceNameTryingToCatchOutUpgradeCode75', - 'node': '.123Im_a_Little.Teapot-Short_And-Stout.Heres-My-Handle_Heres_My.Spout.Im_Also.A.Very.LongNodeNameTryingTo-CatchOut_UpgradeCode75'}, - 'spec': {'expectedIPs': ['fd00:1::321'], - 'interfaceName': 'eth0', - 'profiles': ['profile1', 'profile2']} -} -data['hep_bad_label'] = { - 'apiVersion': 'v1', - 'kind': 'hostEndpoint', - 'metadata': {'labels': { - '8roper/evil-02/key_name.which/is-also_very.long...1234567890//proper/evil-02/key_name.which/is-also_very.long...1234567890//proper/evil-02/Key_Name.which/is-also_very.long...1234567890//proper/evil-02/key_name.which/is-also_very.long...1234567890//proper/evil-02/Key_name.which/is-also_very.long...1234567890//proper/evil-02/key_name.which/is-also_very.long...1234567890//proper/evil-02/key_Name.which/is-also_very.long...1234567890//proper/evil-02/key_name.which/is-also_very.long...1234567890//proper/evil-02/9': 'frontendFrontEnd.0123456789-_-23wdffrontendFrontEnd.0124679-_-0', - 'calico/k8s_ns': 'default', - 'type': 'type-endFrontEnd.0123456789-_-23wdffrontendFrontEnd.0124679-_-0'}, - 'name': '.123Im_a_Little.Teapot-Short_And-Stout.Heres-My-Handle_Her.Im_AlsoAVeryLongInterfaceNameTryingToCatchOutUpgradeCode75', - 'node': '.123Im_a_Little.Teapot-Short_And-Stout.Heres-My-Handle_Heres_My.Spout.Im_Also.A.Very.LongNodeNameTryingTo-CatchOut_UpgradeCode75'}, - 'spec': {'expectedIPs': ['fd00:1::321'], - 'interfaceName': 'eth0', - 'profiles': ['profile1', 'profile2']} -} -data['hep_name_too_long'] = { - 'apiVersion': 'v1', - 'kind': 'hostEndpoint', - 'metadata': {'labels': { - 'calico/k8s_ns': 'default', - 'type': 'type-endFrontEnd.0123456789-_-23wdffrontendFrontEnd.0124679-_-0'}, - 'name': '.123Im_a_Little.Teapot-Short_And-Stout.Heres-My-Handle_Heres_My.Spout.Im_AlsoAVeryLongInterfaceNameTryingToCatchOutUpgradeCode75', - 'node': '.123Im_a_Little.Teapot-Short_And-Stout.Heres-My-Handle_Heres_My.Spout.Im_Also.A.Very.LongNodeNameTryingTo-CatchOut_UpgradeCode75'}, - 'spec': {'expectedIPs': ['fd00:1::321'], - 'interfaceName': 'eth0', - 'profiles': ['profile1', 'profile2']} -} -data['hep_mixed_ip'] = { - 'apiVersion': 'v1', - 'kind': 'hostEndpoint', - 'metadata': {'labels': {'type': 'production'}, - 'name': 'eth0', - 'node': 'myotherhost'}, - 'spec': {'expectedIPs': ['192.168.0.1', - '192.168.0.2', - 'fd00:ca:fe:1d:52:bb:e9:80'], - 'interfaceName': 'eth0', - 'profiles': ['profile1', 'profile2']} -} -data['ippool_v4_small'] = { - 'apiVersion': 'v1', - 'kind': 'ipPool', - 'metadata': {'cidr': '10.1.0.0/26'}, - 'spec': {'disabled': False, - 'ipip': {'enabled': True, 'mode': 'cross-subnet'}, - 'nat-outgoing': True} -} -data['ippool_v4_large'] = { - 'apiVersion': 'v1', - 'kind': 'ipPool', - 'metadata': {'cidr': '10.0.0.0/8'}, - 'spec': {'disabled': False, - 'ipip': {'enabled': True, 'mode': 'always'}, - 'nat-outgoing': True} -} -data['ippool_mixed'] = { - 'apiVersion': 'v1', - 'kind': 'ipPool', - 'metadata': {'cidr': '2006::/64'}, - 'spec': {'disabled': False, - 'ipip': {'enabled': False, 'mode': 'always'}, - 'nat-outgoing': False} -} -data['node_long_name'] = { - 'apiVersion': 'v1', - 'kind': 'node', - 'metadata': { - 'name': '-Mary_had-A-Little___Lamb--Whose---Fleece-Was-White.As.Snow...She-Also-Had_an-Evil-NodeName_in_order_to.break.upgrade-code201600'}, - 'spec': {'bgp': {'asNumber': '7.20', - 'ipv4Address': '10.244.0.1/24', - 'ipv6Address': '2001:db8:85a3::8a2e:370:7334/120'}} -} -data['node_tame'] = { - 'apiVersion': 'v1', - 'kind': 'node', - 'metadata': {'name': 'node-hostname'}, - 'spec': {'bgp': {'asNumber': 64512, - 'ipv4Address': '10.244.0.1/24', - 'ipv6Address': '2001:db8:85a3::8a2e:370:7334/120'}} -} -data['policy_tame'] = { - 'apiVersion': 'v1', - 'kind': 'policy', - 'metadata': { - 'name': 'default.allow-tcp-6379' - }, - 'spec': { - 'egress': [{'action': 'allow'}], - 'ingress': [{'action': 'allow', - 'destination': {'ports': [6379]}, - 'protocol': 'tcp', - 'source': {'selector': "role == 'frontend'"}}], - 'selector': "role == 'database'", - 'types': ['ingress', 'egress'] - } -} -data['policy_long_name'] = { - 'apiVersion': 'v1', - 'kind': 'policy', - 'metadata': { - 'name': 'Mary_had-A-Little___Lamb--Whose---Fleece-Was-White.As.Snow...She-Also-Had_an-Evil-PolicyName_in_order_to.break.upgrade-code2016' - }, - 'spec': { - 'egress': [{'action': 'allow'}], - 'ingress': [{'action': 'allow', - 'destination': {'ports': [6379]}, - 'protocol': 'tcp', - 'source': {'nets': ['192.168.0.1/32']}}], - 'selector': "role == 'database'", - 'types': ['ingress', 'egress']} -} -data['profile_tame'] = { - 'apiVersion': 'v1', - 'kind': 'profile', - 'metadata': {'labels': {'profile': 'profile1'}, 'name': 'profile1'}, - 'spec': {'egress': [{'action': 'allow'}], - 'ingress': [{'action': 'deny', - 'source': {'nets': ['10.0.20.0/24']}}, - {'action': 'allow', - 'source': {'selector': "profile == 'profile1'"}}]} -} -data['profile_long_labels'] = { - 'apiVersion': 'v1', - 'kind': 'profile', - 'metadata': {'labels': { - '8roper/evil-02/key_name.which/is-also_very.long...1234567890//proper/evil-02/key_name.which/is-also_very.long...1234567890//proper/evil-02/Key_Name.which/is-also_very.long...1234567890//proper/evil-02/key_name.which/is-also_very.long...1234567890//proper/evil-02/Key_name.which/is-also_very.long...1234567890//proper/evil-02/key_name.which/is-also_very.long...1234567890//proper/evil-02/key_Name.which/is-also_very.long...1234567890//proper/evil-02/key_name.which/is-also_very.long...1234567890//proper/evil-02/9': 'frontendFrontEnd.0123456789-_-23wdffrontendFrontEnd.0124679-_-0'}, - 'name': '-Mary_had-A-Little___Lamb--Whose---Fleece-Was-White.As.Snow...She-Also-Had_an-Evil-ProfileName_in_order_to.break.upgradeprofile1'}, - 'spec': {'egress': [{'action': 'allow'}], - 'ingress': [{'action': 'deny', - 'source': {'nets': ['10.0.20.0/24', - '192.168.0.0/32', - '192.168.1.255/32', - '192.168.2.254/32', - '192.168.3.253/32', - '192.168.4.252/32', - '192.168.5.251/32', - '192.168.6.250/32', - '192.168.7.249/32', - '192.168.8.248/32', - '192.168.9.247/32', - '192.168.10.246/32', - '192.168.11.245/32', - '192.168.12.244/32', - '192.168.13.243/32', - '192.168.14.242/32', - '192.168.15.241/32', - '192.168.16.240/32', - '192.168.17.239/32', - '192.168.18.238/32', - '192.168.100.0/32', - '192.168.101.255/32', - '192.168.102.254/32', - '192.168.103.253/32', - '192.168.104.252/32', - '192.168.105.251/32', - '192.168.106.250/32', - '192.168.107.249/32', - '192.168.108.248/32', - '192.168.109.247/32', - '192.168.110.246/32', - '192.168.111.245/32', - '192.168.112.244/32', - '192.168.113.243/32', - '192.168.114.242/32', - '192.168.115.241/32', - '192.168.116.240/32', - '192.168.117.239/32', - '192.168.118.238/32', - '192.168.200.0/32', - '192.168.201.255/32', - '192.168.202.254/32', - '192.168.203.253/32', - '192.168.204.252/32', - '192.168.205.251/32', - '192.168.206.250/32', - '192.168.207.249/32', - '192.168.208.248/32', - '192.168.209.247/32', - '192.168.210.246/32', - '192.168.211.245/32', - '192.168.212.244/32', - '192.168.213.243/32', - '192.168.214.242/32', - '192.168.215.241/32', - '192.168.216.240/32', - '192.168.217.239/32', - '192.168.218.238/32', - '47.0.0.0/8']}}, - {'action': 'allow', - 'source': {'selector': "profile == 'profile1'"}}]} -} -data['policy_big'] = { - 'apiVersion': 'v1', - 'kind': 'policy', - 'metadata': { - 'annotations': {'aname': 'avalue'}, - 'name': 'default.allow-tcp-6379' - }, - 'spec': {'egress': [{'action': 'allow', - 'icmp': {'code': 25, 'type': 25}, - 'protocol': 'icmp'}], - 'ingress': [{'action': 'allow', - 'destination': {'ports': [6379]}, - 'notProtocol': 'udplite', - 'protocol': 'tcp', - 'source': { - 'notSelector': "role != 'something' && thing in {'one', 'two'}", - 'selector': "role == 'frontend' && thing not in {'three', 'four'}"}}, - {'action': 'allow', - 'protocol': 'tcp', - 'source': { - 'notSelector': "role != 'something' && thing in {'one', 'two'}"}}, - {'action': 'deny', - 'destination': {'notPorts': [80], - 'ports': [22, 443]}, - 'protocol': 'tcp'}, - {'action': 'allow', - 'source': {'nets': ['172.18.18.200/32', - '172.18.19.0/24']}}, - {'action': 'allow', - 'source': {'net': '172.18.18.100/32'}}, - {'action': 'deny', - 'source': {'notNet': '172.19.19.100/32'}}, - {'action': 'deny', - 'source': {'notNets': ['172.18.0.0/16']}}], - 'order': 1234, - 'selector': "role == 'database' && !has(demo)", - 'types': ['ingress', 'egress']} -} -data['profile_big'] = { - 'apiVersion': 'v1', - 'kind': 'profile', - 'metadata': {'labels': {'profile': 'profile1'}, - 'name': 'profile1', - 'tags': ['atag', 'btag']}, - 'spec': {'egress': [{'action': 'allow', - 'destination': {'notSelector': "profile == 'system'"}}, - {'action': 'allow', - 'source': {'selector': "something in {'a', 'b'}"}}, - {'action': 'allow', - 'destination': {'selector': "something not in {'a', 'b'}"}}], - 'ingress': [{'action': 'deny', - 'destination': {'notPorts': [22, 443, 21, 8080], - 'tag': 'atag'}, - 'protocol': 'udp', - 'source': {'net': '172.20.0.0/16', - 'notNet': '172.20.5.0/24', - 'notTag': 'dtag', - 'tag': 'ctag'}}, - {'action': 'deny', - 'destination': {'notPorts': [22, 443, 21, 8080], - 'tag': 'atag'}, - 'protocol': 'tcp', - 'source': {'nets': ['10.0.21.128/25'], - 'notNets': ['10.0.20.0/24']}}, - {'action': 'deny', - 'protocol': 'tcp', - 'source': {'notNets': ['10.0.21.128/25']}}, - {'action': 'allow', - 'protocol': 'tcp', - 'source': {'ports': [1234, 4567, 489], - 'selector': "profile != 'profile1' && has(role)"}}]} -} -data['wep_lots_ips'] = { - 'apiVersion': 'v1', - 'kind': 'workloadEndpoint', - 'metadata': {'labels': {'app': 'frontend', 'calico/k8s_ns': 'default'}, - 'name': 'eth0', - 'node': 'rack1-host1', - 'orchestrator': 'k8s', - 'workload': 'default.frontend-5gs43'}, - 'spec': {'interfaceName': 'cali0ef24ba', - 'ipNetworks': ['192.168.0.0/32', - '192.168.1.255/32', - '192.168.2.254/32', - '192.168.3.253/32', - '192.168.4.252/32', - '192.168.5.251/32', - '192.168.6.250/32', - '192.168.7.249/32', - '192.168.8.248/32', - '192.168.9.247/32', - '192.168.10.246/32', - '192.168.11.245/32', - '192.168.12.244/32', - '192.168.13.243/32', - '192.168.14.242/32', - '192.168.15.241/32', - '192.168.16.240/32', - '192.168.17.239/32', - '192.168.18.238/32', - '192.168.100.0/32', - '192.168.101.255/32', - '192.168.102.254/32', - '192.168.103.253/32', - '192.168.104.252/32', - '192.168.105.251/32', - '192.168.106.250/32', - '192.168.107.249/32', - '192.168.108.248/32', - '192.168.109.247/32', - '192.168.110.246/32', - '192.168.111.245/32', - '192.168.112.244/32', - '192.168.113.243/32', - '192.168.114.242/32', - '192.168.115.241/32', - '192.168.116.240/32', - '192.168.117.239/32', - '192.168.118.238/32', - '192.168.200.0/32', - '192.168.201.255/32', - '192.168.202.254/32', - '192.168.203.253/32', - '192.168.204.252/32', - '192.168.205.251/32', - '192.168.206.250/32', - '192.168.207.249/32', - '192.168.208.248/32', - '192.168.209.247/32', - '192.168.210.246/32', - '192.168.211.245/32', - '192.168.212.244/32', - '192.168.213.243/32', - '192.168.214.242/32', - '192.168.215.241/32', - '192.168.216.240/32', - '192.168.217.239/32', - '192.168.218.238/32'], - 'mac': 'ca:fe:1d:52:bb:e9', - 'profiles': ['profile1']} -} -data['wep_similar_name'] = { - 'apiVersion': 'v1', - 'kind': 'workloadEndpoint', - 'metadata': {'labels': {'app': 'frontend', 'calico/k8s_ns': 'default'}, - 'name': 'eth0', - 'node': 'rack1-host1', - 'orchestrator': 'k8s', - 'workload': 'default/frontend-5gs43'}, - 'spec': {'interfaceName': 'cali0ef24ba', - 'ipNetworks': ['192.168.0.0/32', - '192.168.1.255/32', - '192.168.2.254/32', - '192.168.3.253/32', - '192.168.4.252/32', - '192.168.5.251/32', - '192.168.6.250/32', - '192.168.7.249/32', - '192.168.8.248/32', - '192.168.9.247/32', - '192.168.10.246/32', - '192.168.11.245/32', - '192.168.12.244/32', - '192.168.13.243/32', - '192.168.14.242/32', - '192.168.15.241/32', - '192.168.16.240/32', - '192.168.17.239/32', - '192.168.18.238/32'], - 'mac': 'fe:ed:ca:fe:00:00', - 'profiles': ['profile1']} -} -data['wep_bad_workload_id'] = { - 'apiVersion': 'v1', - 'kind': 'workloadEndpoint', - 'metadata': {'labels': { - 'calico/k8s_ns': 'default'}, - 'name': '.123Im_a_Little.Teapot-Short_And-Stout.Heres-My-Handle_Heres_My.Spout.Im_AlsoAVeryLongInterfaceNameTryingToCatchOutUpgradeCode75', - 'node': '.123Im_a_Little.Teapot-Short_And-Stout.Heres-My-Handle_Heres_My.Spout.Im_Also.A.Very.LongNodeNameTryingTo-CatchOut_UpgradeCode75', - 'orchestrator': 'k8s', - 'workload': 'default.-_.123Im_a_Little.Teapot-Short_And-Stout.Heres-My-Handle_Heres_My_AlsoAVeryLongWorkload-NameTryingToCatchOutUpgradeCode5'}, - 'spec': {'interfaceName': 'cali0ef24ba', - 'ipNetworks': ['192.168.255.255/32', 'fd::1:40'], - 'mac': 'ca:fe:1d:52:bb:e9', - 'profiles': ['profile1']} -} -data['wep_similar_name_2'] = { - 'apiVersion': 'v1', - 'kind': 'workloadEndpoint', - 'metadata': {'labels': {'app': 'frontend', 'calico/k8s_ns': 'default'}, - 'name': 'eth0', - 'node': 'rack1-host1', - 'orchestrator': 'k8s', - 'workload': 'default.frontend.5gs43'}, - 'spec': {'interfaceName': 'cali0ef24ba', - 'ipNetworks': ['fd00:ca:fe:1d:52:bb:e9:80'], - 'mac': 'ca:fe:1d:52:bb:e9', - 'profiles': ['profile1']} -} -data['do_not_track'] = { - 'apiVersion': 'v1', - 'kind': 'policy', - 'metadata': {'name': 'allow-tcp-555-donottrack'}, - 'spec': {'doNotTrack': True, - 'ingress': [{'action': 'allow', - 'destination': {'ports': [555]}, - 'protocol': 'tcp', - 'source': {'selector': "role == 'cache'"}}], - 'order': 1230, - 'selector': "role == 'database'", - 'types': ['ingress']} -} -data['prednat_policy'] = { - 'apiVersion': 'v1', - 'kind': 'policy', - 'metadata': {'name': 'default.allow-cluster-internal-ingress'}, - 'spec': {'ingress': [{'action': 'allow', - 'source': {'nets': ['10.240.0.0/16', - '192.168.0.0/16']}}], - 'order': 10, - 'preDNAT': True, - 'selector': 'has(host-endpoint)'} -} diff --git a/charts/calico/templates/calico-kube-controllers-rbac.yaml b/charts/calico/templates/calico-kube-controllers-rbac.yaml index 0e2b3f058e4..f0f4f7a83a0 100644 --- a/charts/calico/templates/calico-kube-controllers-rbac.yaml +++ b/charts/calico/templates/calico-kube-controllers-rbac.yaml @@ -36,6 +36,28 @@ rules: verbs: - watch - list + # Needed for policy name migrator. + - apiGroups: ["crd.projectcalico.org"] + resources: + - networkpolicies + - stagednetworkpolicies + - globalnetworkpolicies + - stagedglobalnetworkpolicies + verbs: + - watch + - list + - get + - create + - update + - delete + # Needed for policy name migrator to check calico/node daemonset status. + - apiGroups: ["apps"] + resources: + - daemonsets + verbs: + - get + resourceNames: + - calico-node --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 @@ -154,6 +176,34 @@ rules: - update # watch for changes - watch + - apiGroups: ["crd.projectcalico.org"] + resources: + - kubecontrollersconfigurations/status + verbs: + - get + - update + # Needed for policy name migrator. + - apiGroups: ["crd.projectcalico.org"] + resources: + - networkpolicies + - stagednetworkpolicies + - globalnetworkpolicies + - stagedglobalnetworkpolicies + verbs: + - watch + - list + - get + - create + - update + - delete + # Needed for policy name migrator to check calico/node daemonset status. + - apiGroups: ["apps"] + resources: + - daemonsets + verbs: + - get + resourceNames: + - calico-node --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 diff --git a/charts/calico/templates/calico-node-rbac.yaml b/charts/calico/templates/calico-node-rbac.yaml index 01a776ed971..afb5981f635 100644 --- a/charts/calico/templates/calico-node-rbac.yaml +++ b/charts/calico/templates/calico-node-rbac.yaml @@ -68,11 +68,10 @@ rules: verbs: - watch - list - # Watch for changes to Kubernetes (Baseline)AdminNetworkPolicies. + # Watch for changes to Kubernetes ClusterNetworkPolicies. - apiGroups: ["policy.networking.k8s.io"] resources: - - adminnetworkpolicies - - baselineadminnetworkpolicies + - clusternetworkpolicies verbs: - watch - list @@ -192,6 +191,14 @@ rules: verbs: - get {{- end }} + # For monitoring KubeVirt live migration. + - apiGroups: ["kubevirt.io"] + resources: + - virtualmachineinstancemigrations + verbs: + - get + - list + - watch {{- end }} --- diff --git a/charts/crd.projectcalico.org.v1/Chart.yaml b/charts/crd.projectcalico.org.v1/Chart.yaml new file mode 100644 index 00000000000..3eaa3b6b154 --- /dev/null +++ b/charts/crd.projectcalico.org.v1/Chart.yaml @@ -0,0 +1,12 @@ +apiVersion: v2 +name: crd.projectcalico.org.v1 +description: Installs crd.projectcalico.org and operator.tigera.io Custom Resource Definitions (CRDs) +home: https://docs.tigera.io/calico +icon: https://docs.tigera.io/img/calico-logo.png +sources: + - https://github.com/projectcalico/calico/tree/master/charts/crd.projectcalico.org.v1 + - https://github.com/tigera/operator + - https://github.com/projectcalico/calico + +# version will be overridden at chart compilation time via args, but still must exist in chart.yaml +version: v0.0 diff --git a/charts/crd.projectcalico.org.v1/README.md b/charts/crd.projectcalico.org.v1/README.md new file mode 100644 index 00000000000..fbfeb5b3030 --- /dev/null +++ b/charts/crd.projectcalico.org.v1/README.md @@ -0,0 +1,6 @@ +# Calico CRDs + +This chart contains the following Calico Custom Resource Definitions (CRDs), required for the tigera-operator helm chart to function properly: + +- crd.projectalico.org/v1 API group +- operator.tigera.io/v1 API group diff --git a/charts/tigera-operator/crds/calico b/charts/crd.projectcalico.org.v1/templates/calico similarity index 100% rename from charts/tigera-operator/crds/calico rename to charts/crd.projectcalico.org.v1/templates/calico diff --git a/charts/crd.projectcalico.org.v1/templates/operator.tigera.io_apiservers.yaml b/charts/crd.projectcalico.org.v1/templates/operator.tigera.io_apiservers.yaml new file mode 100644 index 00000000000..bd818850547 --- /dev/null +++ b/charts/crd.projectcalico.org.v1/templates/operator.tigera.io_apiservers.yaml @@ -0,0 +1,2829 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: apiservers.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: APIServer + listKind: APIServerList + plural: apiservers + singular: apiserver + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: |- + APIServer installs the Tigera API server and related resources. At most one instance + of this resource is supported. It must be named "default" or "tigera-secure". + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Specification of the desired state for the Tigera API server. + properties: + apiServerDeployment: + description: |- + APIServerDeployment configures the calico-apiserver Deployment. If + used in conjunction with ControlPlaneNodeSelector or ControlPlaneTolerations, then these overrides + take precedence. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the specification of the API server Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the API server Deployment. + If omitted, the API server Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the API server Deployment + pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the API server Deployment's PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the API server pods. + If specified, this overrides any affinity that may be set on the API server Deployment. + If omitted, the API server Deployment will use its default value for affinity. + WARNING: Please note that this field will override the default API server Deployment affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of API server containers. + If specified, this overrides the specified API server Deployment containers. + If omitted, the API server Deployment will use its default values for its containers. + items: + description: + APIServerDeploymentContainer is an + API server Deployment container. + properties: + name: + description: |- + Name is an enum which identifies the API server Deployment container by name. + Supported values are: calico-apiserver, tigera-queryserver, calico-l7-admission-controller + enum: + - calico-apiserver + - tigera-queryserver + - calico-l7-admission-controller + type: string + ports: + description: |- + Ports allows customization of container's ports. + If specified, this overrides the named APIServer Deployment container's ports. + If omitted, the API server Deployment will use its default value for this container's port. + items: + properties: + containerPort: + description: |- + Number of port to expose on the pod's IP address. + This must be a valid port number, 0 < x < 65536. + format: int32 + type: integer + name: + description: |- + Name is an enum which identifies the API server Deployment Container port by name. + Supported values are: apiserver, queryserver, l7admctrl + enum: + - apiserver + - queryserver + - l7admctrl + type: string + required: + - containerPort + - name + type: object + type: array + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named API server Deployment container's resources. + If omitted, the API server Deployment will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + initContainers: + description: |- + InitContainers is a list of API server init containers. + If specified, this overrides the specified API server Deployment init containers. + If omitted, the API server Deployment will use its default values for its init containers. + items: + description: + APIServerDeploymentInitContainer is + an API server Deployment init container. + properties: + name: + description: |- + Name is an enum which identifies the API server Deployment init container by name. + Supported values are: calico-apiserver-certs-key-cert-provisioner + enum: + - calico-apiserver-certs-key-cert-provisioner + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named API server Deployment init container's resources. + If omitted, the API server Deployment will use its default value for this init container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the API server pod's scheduling constraints. + If specified, each of the key/value pairs are added to the API server Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If used in conjunction with ControlPlaneNodeSelector, that nodeSelector is set on the API server Deployment + and each of this field's key/value pairs are added to the API server Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the API server Deployment will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default API server Deployment nodeSelector. + type: object + priorityClassName: + description: + PriorityClassName allows to specify a + PriorityClass resource to be used. + type: string + tolerations: + description: |- + Tolerations is the API server pod's tolerations. + If specified, this overrides any tolerations that may be set on the API server Deployment. + If omitted, the API server Deployment will use its default value for tolerations. + WARNING: Please note that this field will override the default API server Deployment tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + type: object + type: object + type: object + calicoWebhooksDeployment: + description: + CalicoWebhooksDeployment configures the calico-webhooks + Deployment. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the calico-webhooks + Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-webhooks Deployment. + If omitted, the calico-webhooks Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-webhooks Deployment + pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the calico-webhooks Deployment's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-webhooks pods. + If specified, this overrides any affinity that may be set on the calico-webhooks Deployment. + If omitted, the calico-webhooks Deployment will use its default value for affinity. + WARNING: Please note that this field will override the default calico-webhooks Deployment affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-webhooks containers. + If specified, this overrides the specified calico-webhooks Deployment containers. + If omitted, the calico-webhooks Deployment will use its default values for its containers. + items: + description: + CalicoWebhooksDeploymentContainer is + a calico-webhooks Deployment container. + properties: + name: + description: |- + Name is an enum which identifies the calico-webhooks Deployment container by name. + Supported values are: calico-webhooks + enum: + - calico-webhooks + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-webhooks Deployment container's resources. + If omitted, the calico-webhooks Deployment will use its default value for this container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-webhooks pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-webhooks Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If used in conjunction with ControlPlaneNodeSelector, that nodeSelector is set on the calico-webhooks Deployment + and each of this field's key/value pairs are added to the calico-webhooks Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-webhooks Deployment will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-webhooks Deployment nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-webhooks pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-webhooks Deployment. + If omitted, the calico-webhooks Deployment will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-webhooks Deployment tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + logging: + properties: + apiServer: + properties: + logSeverity: + default: Info + description: LogSeverity defines log level for APIServer container. + enum: + - Fatal + - Error + - Warn + - Info + - Debug + - Trace + type: string + type: object + queryServer: + properties: + logSeverity: + default: Info + description: + LogSeverity defines log level for QueryServer + container. + enum: + - Fatal + - Error + - Warn + - Info + - Debug + - Trace + type: string + type: object + type: object + type: object + status: + description: Most recently observed status for the Tigera API server. + properties: + conditions: + description: |- + Conditions represents the latest observed set of conditions for the component. A component may be one or more of + Ready, Progressing, Degraded or other customer types. + items: + description: + Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + state: + description: State provides user-readable status. + type: string + type: object + type: object + x-kubernetes-validations: + - message: resource name must be 'default' or 'tigera-secure' + rule: self.metadata.name == 'default' || self.metadata.name == 'tigera-secure' + served: true + storage: true + subresources: + status: {} diff --git a/charts/tigera-operator/crds/operator.tigera.io_gatewayapis.yaml b/charts/crd.projectcalico.org.v1/templates/operator.tigera.io_gatewayapis.yaml similarity index 98% rename from charts/tigera-operator/crds/operator.tigera.io_gatewayapis.yaml rename to charts/crd.projectcalico.org.v1/templates/operator.tigera.io_gatewayapis.yaml index c34e99344f2..7a8c71cf2ea 100644 --- a/charts/tigera-operator/crds/operator.tigera.io_gatewayapis.yaml +++ b/charts/crd.projectcalico.org.v1/templates/operator.tigera.io_gatewayapis.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.3 + controller-gen.kubebuilder.io/version: v0.18.0 name: gatewayapis.operator.tigera.io spec: group: operator.tigera.io @@ -458,7 +458,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -473,7 +472,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -647,7 +645,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -662,7 +659,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -760,8 +756,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -838,7 +834,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -853,7 +848,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -1027,7 +1021,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -1042,7 +1035,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -1146,7 +1138,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -1654,7 +1646,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -1669,7 +1660,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -1848,7 +1838,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -1863,7 +1852,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -1964,8 +1952,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -2046,7 +2034,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -2061,7 +2048,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -2240,7 +2226,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -2255,7 +2240,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -2361,7 +2345,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -2595,7 +2579,6 @@ spec: - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. If this value is nil, the behavior is equivalent to the Honor policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string nodeTaintsPolicy: description: |- @@ -2605,7 +2588,6 @@ spec: has a toleration, are included. - Ignore: node taints are ignored. All nodes are included. If this value is nil, the behavior is equivalent to the Ignore policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string topologyKey: description: |- @@ -3068,7 +3050,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -3083,7 +3064,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -3262,7 +3242,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -3277,7 +3256,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -3378,8 +3356,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -3460,7 +3438,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -3475,7 +3452,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -3654,7 +3630,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -3669,7 +3644,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -3775,7 +3749,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -4010,7 +3984,6 @@ spec: - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. If this value is nil, the behavior is equivalent to the Honor policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string nodeTaintsPolicy: description: |- @@ -4020,7 +3993,6 @@ spec: has a toleration, are included. - Ignore: node taints are ignored. All nodes are included. If this value is nil, the behavior is equivalent to the Ignore policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string topologyKey: description: |- @@ -4518,7 +4490,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -4533,7 +4504,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -4707,7 +4677,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -4722,7 +4691,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -4820,8 +4788,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -4898,7 +4866,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -4913,7 +4880,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -5087,7 +5053,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -5102,7 +5067,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -5206,7 +5170,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -5438,7 +5402,6 @@ spec: - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. If this value is nil, the behavior is equivalent to the Honor policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string nodeTaintsPolicy: description: |- @@ -5448,7 +5411,6 @@ spec: has a toleration, are included. - Ignore: node taints are ignored. All nodes are included. If this value is nil, the behavior is equivalent to the Ignore policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string topologyKey: description: |- @@ -5496,5 +5458,8 @@ spec: type: object type: object type: object + x-kubernetes-validations: + - message: resource name must be 'default' + rule: self.metadata.name == 'default' || self.metadata.name == 'tigera-secure' served: true storage: true diff --git a/charts/tigera-operator/crds/operator.tigera.io_goldmanes.yaml b/charts/crd.projectcalico.org.v1/templates/operator.tigera.io_goldmanes.yaml similarity index 98% rename from charts/tigera-operator/crds/operator.tigera.io_goldmanes.yaml rename to charts/crd.projectcalico.org.v1/templates/operator.tigera.io_goldmanes.yaml index 2f603c917c2..58f329f3c18 100644 --- a/charts/tigera-operator/crds/operator.tigera.io_goldmanes.yaml +++ b/charts/crd.projectcalico.org.v1/templates/operator.tigera.io_goldmanes.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.3 + controller-gen.kubebuilder.io/version: v0.18.0 name: goldmanes.operator.tigera.io spec: group: operator.tigera.io @@ -465,7 +465,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -480,7 +479,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -654,7 +652,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -669,7 +666,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -767,8 +763,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -845,7 +841,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -860,7 +855,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -1034,7 +1028,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -1049,7 +1042,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -1154,7 +1146,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -1399,7 +1391,6 @@ spec: - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. If this value is nil, the behavior is equivalent to the Honor policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string nodeTaintsPolicy: description: |- @@ -1409,7 +1400,6 @@ spec: has a toleration, are included. - Ignore: node taints are ignored. All nodes are included. If this value is nil, the behavior is equivalent to the Ignore policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string topologyKey: description: |- @@ -1521,6 +1511,9 @@ spec: type: array type: object type: object + x-kubernetes-validations: + - message: resource name must be 'default' + rule: self.metadata.name == 'default' served: true storage: true subresources: diff --git a/charts/tigera-operator/crds/operator.tigera.io_imagesets.yaml b/charts/crd.projectcalico.org.v1/templates/operator.tigera.io_imagesets.yaml similarity index 98% rename from charts/tigera-operator/crds/operator.tigera.io_imagesets.yaml rename to charts/crd.projectcalico.org.v1/templates/operator.tigera.io_imagesets.yaml index d0361cc75ef..552c7ab87ac 100644 --- a/charts/tigera-operator/crds/operator.tigera.io_imagesets.yaml +++ b/charts/crd.projectcalico.org.v1/templates/operator.tigera.io_imagesets.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.3 + controller-gen.kubebuilder.io/version: v0.18.0 name: imagesets.operator.tigera.io spec: group: operator.tigera.io diff --git a/charts/tigera-operator/crds/operator.tigera.io_installations.yaml b/charts/crd.projectcalico.org.v1/templates/operator.tigera.io_installations.yaml similarity index 98% rename from charts/tigera-operator/crds/operator.tigera.io_installations.yaml rename to charts/crd.projectcalico.org.v1/templates/operator.tigera.io_installations.yaml index 20924252846..c51e8453af2 100644 --- a/charts/tigera-operator/crds/operator.tigera.io_installations.yaml +++ b/charts/crd.projectcalico.org.v1/templates/operator.tigera.io_installations.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.3 + controller-gen.kubebuilder.io/version: v0.18.0 name: installations.operator.tigera.io spec: group: operator.tigera.io @@ -447,7 +447,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -462,7 +461,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -636,7 +634,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -651,7 +648,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -749,8 +745,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -827,7 +823,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -842,7 +837,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -1016,7 +1010,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -1031,7 +1024,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -1145,7 +1137,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -1926,7 +1918,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -1941,7 +1932,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -2115,7 +2105,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -2130,7 +2119,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -2228,8 +2216,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -2306,7 +2294,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -2321,7 +2308,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -2495,7 +2481,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -2510,7 +2495,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -2623,7 +2607,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -2772,7 +2756,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -3274,7 +3258,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -3289,7 +3272,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -3463,7 +3445,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -3478,7 +3459,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -3576,8 +3556,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -3654,7 +3634,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -3669,7 +3648,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -3843,7 +3821,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -3858,7 +3835,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -3956,22 +3932,26 @@ spec: name: description: |- Name is an enum which identifies the calico-node-windows DaemonSet container by name. - Supported values are: calico-node-windows + Supported values are: node, felix, confd + calico-node-windows is allowed because it was previously allowed. enum: - calico-node-windows + - node + - felix + - confd type: string resources: description: |- Resources allows customization of limits and requests for compute resources such as cpu and memory. - If specified, this overrides the named calico-node-windows DaemonSet container's resources. - If omitted, the calico-node-windows DaemonSet will use its default value for this container's resources. + If specified, this overrides the named DaemonSet container's resources. + If omitted, the DaemonSet will use its default value for this container's resources. If used in conjunction with the deprecated ComponentResources, then this value takes precedence. properties: claims: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -4059,7 +4039,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -4561,7 +4541,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -4576,7 +4555,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -4750,7 +4728,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -4765,7 +4742,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -4863,8 +4839,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -4941,7 +4917,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -4956,7 +4931,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -5130,7 +5104,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -5145,7 +5118,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -5258,7 +5230,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -5504,6 +5476,9 @@ spec: - Node - Typha - KubeControllers + - NodeWindows + - FelixWindows + - ConfdWindows type: string resourceRequirements: description: @@ -5514,7 +5489,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -6010,7 +5985,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -6025,7 +5999,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -6199,7 +6172,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -6214,7 +6186,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -6312,8 +6283,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -6390,7 +6361,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -6405,7 +6375,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -6579,7 +6548,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -6594,7 +6562,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -6708,7 +6675,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -6968,7 +6935,7 @@ spec: pod is available (Ready for at least minReadySeconds) the old DaemonSet pod on that node is marked deleted. If the old pod becomes unavailable for any reason (Ready transitions to false, is evicted, or is drained) an updated - pod is immediatedly created on that node without considering surge limits. + pod is immediately created on that node without considering surge limits. Allowing surge implies the possibility that the resources consumed by the daemonset on any given node can double if the readiness check fails, and so resource intensive daemonsets should take into account that they may @@ -7002,9 +6969,10 @@ spec: type: string type: object nonPrivileged: - description: - NonPrivileged configures Calico to be run in non-privileged - containers as non-root users where possible. + description: |- + Deprecated. NonPrivileged is deprecated and will be removed from the API in a future release. + Enabling this field is not supported and will cause errors. + NonPrivileged configures Calico to be run in non-privileged containers as non-root users where possible. type: string proxy: description: |- @@ -7721,7 +7689,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -7736,7 +7703,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -7910,7 +7876,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -7925,7 +7890,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -8023,8 +7987,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -8101,7 +8065,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -8116,7 +8079,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -8290,7 +8252,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -8305,7 +8266,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -8418,7 +8378,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -8502,7 +8462,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -8750,7 +8710,6 @@ spec: - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. If this value is nil, the behavior is equivalent to the Honor policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string nodeTaintsPolicy: description: |- @@ -8760,7 +8719,6 @@ spec: has a toleration, are included. - Ignore: node taints are ignored. All nodes are included. If this value is nil, the behavior is equivalent to the Ignore policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string topologyKey: description: |- @@ -9288,7 +9246,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -9303,7 +9260,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -9481,7 +9437,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -9496,7 +9451,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -9595,8 +9549,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -9677,7 +9631,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -9692,7 +9645,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -9870,7 +9822,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -9885,7 +9836,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -10000,7 +9950,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -10794,7 +10744,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -10809,7 +10758,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -10987,7 +10935,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -11002,7 +10949,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -11101,8 +11047,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -11183,7 +11129,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -11198,7 +11143,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -11376,7 +11320,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -11391,7 +11334,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -11505,7 +11447,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -11654,7 +11596,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -12165,7 +12107,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -12180,7 +12121,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -12358,7 +12298,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -12373,7 +12312,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -12472,8 +12410,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -12554,7 +12492,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -12569,7 +12506,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -12747,7 +12683,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -12762,7 +12697,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -12861,22 +12795,26 @@ spec: name: description: |- Name is an enum which identifies the calico-node-windows DaemonSet container by name. - Supported values are: calico-node-windows + Supported values are: node, felix, confd + calico-node-windows is allowed because it was previously allowed. enum: - calico-node-windows + - node + - felix + - confd type: string resources: description: |- Resources allows customization of limits and requests for compute resources such as cpu and memory. - If specified, this overrides the named calico-node-windows DaemonSet container's resources. - If omitted, the calico-node-windows DaemonSet will use its default value for this container's resources. + If specified, this overrides the named DaemonSet container's resources. + If omitted, the DaemonSet will use its default value for this container's resources. If used in conjunction with the deprecated ComponentResources, then this value takes precedence. properties: claims: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -12964,7 +12902,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -13475,7 +13413,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -13490,7 +13427,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -13668,7 +13604,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -13683,7 +13618,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -13782,8 +13716,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -13864,7 +13798,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -13879,7 +13812,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -14057,7 +13989,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -14072,7 +14003,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -14186,7 +14116,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -14434,6 +14364,9 @@ spec: - Node - Typha - KubeControllers + - NodeWindows + - FelixWindows + - ConfdWindows type: string resourceRequirements: description: @@ -14445,7 +14378,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -14954,7 +14887,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -14969,7 +14901,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -15147,7 +15078,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -15162,7 +15092,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -15261,8 +15190,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -15343,7 +15272,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -15358,7 +15286,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -15536,7 +15463,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -15551,7 +15477,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -15666,7 +15591,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -15928,7 +15853,7 @@ spec: pod is available (Ready for at least minReadySeconds) the old DaemonSet pod on that node is marked deleted. If the old pod becomes unavailable for any reason (Ready transitions to false, is evicted, or is drained) an updated - pod is immediatedly created on that node without considering surge limits. + pod is immediately created on that node without considering surge limits. Allowing surge implies the possibility that the resources consumed by the daemonset on any given node can double if the readiness check fails, and so resource intensive daemonsets should take into account that they may @@ -15962,9 +15887,10 @@ spec: type: string type: object nonPrivileged: - description: - NonPrivileged configures Calico to be run in non-privileged - containers as non-root users where possible. + description: |- + Deprecated. NonPrivileged is deprecated and will be removed from the API in a future release. + Enabling this field is not supported and will cause errors. + NonPrivileged configures Calico to be run in non-privileged containers as non-root users where possible. type: string proxy: description: |- @@ -16690,7 +16616,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -16705,7 +16630,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -16883,7 +16807,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -16898,7 +16821,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -16997,8 +16919,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -17079,7 +17001,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -17094,7 +17015,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -17272,7 +17192,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -17287,7 +17206,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -17401,7 +17319,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -17485,7 +17403,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -17735,7 +17653,6 @@ spec: - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. If this value is nil, the behavior is equivalent to the Honor policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string nodeTaintsPolicy: description: |- @@ -17745,7 +17662,6 @@ spec: has a toleration, are included. - Ignore: node taints are ignored. All nodes are included. If this value is nil, the behavior is equivalent to the Ignore policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string topologyKey: description: |- @@ -17923,6 +17839,11 @@ spec: type: string type: object type: object + x-kubernetes-validations: + - message: resource name must be 'default', 'tigera-secure', or 'overlay' + rule: + self.metadata.name == 'default' || self.metadata.name == 'tigera-secure' + || self.metadata.name == 'overlay' served: true storage: true subresources: diff --git a/charts/crd.projectcalico.org.v1/templates/operator.tigera.io_istios.yaml b/charts/crd.projectcalico.org.v1/templates/operator.tigera.io_istios.yaml new file mode 100644 index 00000000000..d9313ad8335 --- /dev/null +++ b/charts/crd.projectcalico.org.v1/templates/operator.tigera.io_istios.yaml @@ -0,0 +1,3441 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: istios.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: Istio + listKind: IstioList + plural: istios + singular: istio + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: Istio is the Schema for the istios API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: IstioSpec defines the desired state of Istio + properties: + dscpMark: + description: |- + DSCPMark define the value of the DSCP mark done by Felix and recognised by Istio CNI for Transparent + NetworkPolicies. + pattern: ^.* + type: integer + x-kubernetes-int-or-string: true + istioCNI: + description: + IstioCNIDaemonset defines the resource requirements for + the Istio CNI plugin. + properties: + spec: + description: + Spec allows users to specify custom fields for the + Istio CNI Daemonset. + properties: + template: + description: + Template allows users to specify custom fields + for the Istio CNI Daemonset. + properties: + spec: + description: + Spec allows users to specify custom fields + for the Istio CNI Daemonset. + properties: + affinity: + description: + Affinity specifies the affinity for the + deployment. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + nodeSelector: + additionalProperties: + type: string + description: + NodeSelector specifies the node affinity + for the deployment. + type: object + resources: + description: + Resources specifies the compute resources + required for the deployment. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references one entry + in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + tolerations: + description: + Tolerations specifies the tolerations + for the deployment. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + istiod: + description: + IstiodDeployment defines the resource requirements and + node selector for the Istio deployment. + properties: + spec: + description: + Spec allows users to specify custom fields for the + Istiod Deployment. + properties: + template: + description: + Template allows users to specify custom fields + for the Istiod Deployment. + properties: + spec: + description: + Spec allows users to specify custom fields + for the Istiod Deployment. + properties: + affinity: + description: + Affinity specifies the affinity for the + deployment. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + nodeSelector: + additionalProperties: + type: string + description: + NodeSelector specifies the node affinity + for the deployment. + type: object + resources: + description: + Resources specifies the compute resources + required for the deployment. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references one entry + in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + tolerations: + description: + Tolerations specifies the tolerations + for the deployment. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + ztunnel: + description: + ZTunnelDaemonset defines the resource requirements for + the ZTunnelDaemonset component. + properties: + spec: + description: + Spec allows users to specify custom fields for the + ZTunnel Daemonset. + properties: + template: + description: + Template allows users to specify custom fields + for the ZTunnel Daemonset. + properties: + spec: + description: + Spec allows users to specify custom fields + for the ZTunnel Daemonset. + properties: + affinity: + description: + Affinity specifies the affinity for the + deployment. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + nodeSelector: + additionalProperties: + type: string + description: + NodeSelector specifies the node affinity + for the deployment. + type: object + resources: + description: + Resources specifies the compute resources + required for the deployment. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references one entry + in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + tolerations: + description: + Tolerations specifies the tolerations + for the deployment. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + type: object + status: + description: IstioStatus defines the observed state of Istio + properties: + conditions: + description: |- + Conditions represents the latest observed set of conditions for the component. A component may be one or more of + Ready, Progressing, Degraded or other customer types. + items: + description: + Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + x-kubernetes-validations: + - message: resource name must be 'default' + rule: self.metadata.name == 'default' + served: true + storage: true + subresources: + status: {} diff --git a/charts/tigera-operator/crds/operator.tigera.io_managementclusterconnections.yaml b/charts/crd.projectcalico.org.v1/templates/operator.tigera.io_managementclusterconnections.yaml similarity index 98% rename from charts/tigera-operator/crds/operator.tigera.io_managementclusterconnections.yaml rename to charts/crd.projectcalico.org.v1/templates/operator.tigera.io_managementclusterconnections.yaml index d09cd236cdd..65899a8adb5 100644 --- a/charts/tigera-operator/crds/operator.tigera.io_managementclusterconnections.yaml +++ b/charts/crd.projectcalico.org.v1/templates/operator.tigera.io_managementclusterconnections.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.3 + controller-gen.kubebuilder.io/version: v0.18.0 name: managementclusterconnections.operator.tigera.io spec: group: operator.tigera.io @@ -83,7 +83,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -165,7 +165,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -347,6 +347,9 @@ spec: type: array type: object type: object + x-kubernetes-validations: + - message: resource name must be 'default' or 'tigera-secure' + rule: self.metadata.name == 'default' || self.metadata.name == 'tigera-secure' served: true storage: true subresources: diff --git a/charts/tigera-operator/crds/operator.tigera.io_tigerastatuses.yaml b/charts/crd.projectcalico.org.v1/templates/operator.tigera.io_tigerastatuses.yaml similarity index 98% rename from charts/tigera-operator/crds/operator.tigera.io_tigerastatuses.yaml rename to charts/crd.projectcalico.org.v1/templates/operator.tigera.io_tigerastatuses.yaml index e6fd0ba160c..282b0a1e6c7 100644 --- a/charts/tigera-operator/crds/operator.tigera.io_tigerastatuses.yaml +++ b/charts/crd.projectcalico.org.v1/templates/operator.tigera.io_tigerastatuses.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.3 + controller-gen.kubebuilder.io/version: v0.18.0 name: tigerastatuses.operator.tigera.io spec: group: operator.tigera.io diff --git a/charts/tigera-operator/crds/operator.tigera.io_whiskers.yaml b/charts/crd.projectcalico.org.v1/templates/operator.tigera.io_whiskers.yaml similarity index 98% rename from charts/tigera-operator/crds/operator.tigera.io_whiskers.yaml rename to charts/crd.projectcalico.org.v1/templates/operator.tigera.io_whiskers.yaml index 4162f3664a5..fb66ddc289c 100644 --- a/charts/tigera-operator/crds/operator.tigera.io_whiskers.yaml +++ b/charts/crd.projectcalico.org.v1/templates/operator.tigera.io_whiskers.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.3 + controller-gen.kubebuilder.io/version: v0.18.0 name: whiskers.operator.tigera.io spec: group: operator.tigera.io @@ -471,7 +471,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -486,7 +485,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -660,7 +658,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -675,7 +672,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -773,8 +769,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -851,7 +847,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -866,7 +861,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -1040,7 +1034,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -1055,7 +1048,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -1161,7 +1153,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -1406,7 +1398,6 @@ spec: - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. If this value is nil, the behavior is equivalent to the Honor policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string nodeTaintsPolicy: description: |- @@ -1416,7 +1407,6 @@ spec: has a toleration, are included. - Ignore: node taints are ignored. All nodes are included. If this value is nil, the behavior is equivalent to the Ignore policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string topologyKey: description: |- @@ -1528,6 +1518,9 @@ spec: type: array type: object type: object + x-kubernetes-validations: + - message: resource name must be 'default' + rule: self.metadata.name == 'default' served: true storage: true subresources: diff --git a/node/tests/st/__init__.py b/charts/crd.projectcalico.org.v1/values.yaml similarity index 100% rename from node/tests/st/__init__.py rename to charts/crd.projectcalico.org.v1/values.yaml diff --git a/charts/deps.txt b/charts/deps.txt index dda84015f8b..557db9f10b5 100644 --- a/charts/deps.txt +++ b/charts/deps.txt @@ -2,5 +2,5 @@ This file contains the list of modules that this package depends on in order to trigger CI on changes -go 1.25.1 +go 1.25.7 github.com/projectcalico/calico diff --git a/charts/projectcalico.org.v3/Chart.yaml b/charts/projectcalico.org.v3/Chart.yaml new file mode 100644 index 00000000000..ad8ed54b81f --- /dev/null +++ b/charts/projectcalico.org.v3/Chart.yaml @@ -0,0 +1,12 @@ +apiVersion: v2 +name: projectcalico.org.v3 +description: Installs projectcalico.org/v3 and operator.tigera.io Custom Resource Definitions (CRDs) +home: https://docs.tigera.io/calico +icon: https://docs.tigera.io/img/calico-logo.png +sources: + - https://github.com/projectcalico/calico/tree/master/charts/projectcalico.org.v3 + - https://github.com/tigera/operator + - https://github.com/projectcalico/calico + +# version will be overridden at chart compilation time via args, but still must exist in chart.yaml +version: v0.0 diff --git a/charts/projectcalico.org.v3/README.md b/charts/projectcalico.org.v3/README.md new file mode 100644 index 00000000000..0984e503cff --- /dev/null +++ b/charts/projectcalico.org.v3/README.md @@ -0,0 +1,6 @@ +# Calico CRDs + +This chart contains the following Calico Custom Resource Definitions (CRDs), required for the tigera-operator helm chart to function properly: + +- projectalico.org/v3 API group +- operator.tigera.io/v1 API group diff --git a/charts/projectcalico.org.v3/templates/admission b/charts/projectcalico.org.v3/templates/admission new file mode 120000 index 00000000000..c68f34de828 --- /dev/null +++ b/charts/projectcalico.org.v3/templates/admission @@ -0,0 +1 @@ +../../../api/admission \ No newline at end of file diff --git a/charts/projectcalico.org.v3/templates/calico b/charts/projectcalico.org.v3/templates/calico new file mode 120000 index 00000000000..5fa63c2cbee --- /dev/null +++ b/charts/projectcalico.org.v3/templates/calico @@ -0,0 +1 @@ +../../../api/config/crd/ \ No newline at end of file diff --git a/charts/projectcalico.org.v3/templates/operator.tigera.io_apiservers.yaml b/charts/projectcalico.org.v3/templates/operator.tigera.io_apiservers.yaml new file mode 100644 index 00000000000..bd818850547 --- /dev/null +++ b/charts/projectcalico.org.v3/templates/operator.tigera.io_apiservers.yaml @@ -0,0 +1,2829 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: apiservers.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: APIServer + listKind: APIServerList + plural: apiservers + singular: apiserver + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: |- + APIServer installs the Tigera API server and related resources. At most one instance + of this resource is supported. It must be named "default" or "tigera-secure". + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Specification of the desired state for the Tigera API server. + properties: + apiServerDeployment: + description: |- + APIServerDeployment configures the calico-apiserver Deployment. If + used in conjunction with ControlPlaneNodeSelector or ControlPlaneTolerations, then these overrides + take precedence. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the specification of the API server Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the API server Deployment. + If omitted, the API server Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the API server Deployment + pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the API server Deployment's PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the API server pods. + If specified, this overrides any affinity that may be set on the API server Deployment. + If omitted, the API server Deployment will use its default value for affinity. + WARNING: Please note that this field will override the default API server Deployment affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of API server containers. + If specified, this overrides the specified API server Deployment containers. + If omitted, the API server Deployment will use its default values for its containers. + items: + description: + APIServerDeploymentContainer is an + API server Deployment container. + properties: + name: + description: |- + Name is an enum which identifies the API server Deployment container by name. + Supported values are: calico-apiserver, tigera-queryserver, calico-l7-admission-controller + enum: + - calico-apiserver + - tigera-queryserver + - calico-l7-admission-controller + type: string + ports: + description: |- + Ports allows customization of container's ports. + If specified, this overrides the named APIServer Deployment container's ports. + If omitted, the API server Deployment will use its default value for this container's port. + items: + properties: + containerPort: + description: |- + Number of port to expose on the pod's IP address. + This must be a valid port number, 0 < x < 65536. + format: int32 + type: integer + name: + description: |- + Name is an enum which identifies the API server Deployment Container port by name. + Supported values are: apiserver, queryserver, l7admctrl + enum: + - apiserver + - queryserver + - l7admctrl + type: string + required: + - containerPort + - name + type: object + type: array + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named API server Deployment container's resources. + If omitted, the API server Deployment will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + initContainers: + description: |- + InitContainers is a list of API server init containers. + If specified, this overrides the specified API server Deployment init containers. + If omitted, the API server Deployment will use its default values for its init containers. + items: + description: + APIServerDeploymentInitContainer is + an API server Deployment init container. + properties: + name: + description: |- + Name is an enum which identifies the API server Deployment init container by name. + Supported values are: calico-apiserver-certs-key-cert-provisioner + enum: + - calico-apiserver-certs-key-cert-provisioner + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named API server Deployment init container's resources. + If omitted, the API server Deployment will use its default value for this init container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the API server pod's scheduling constraints. + If specified, each of the key/value pairs are added to the API server Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If used in conjunction with ControlPlaneNodeSelector, that nodeSelector is set on the API server Deployment + and each of this field's key/value pairs are added to the API server Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the API server Deployment will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default API server Deployment nodeSelector. + type: object + priorityClassName: + description: + PriorityClassName allows to specify a + PriorityClass resource to be used. + type: string + tolerations: + description: |- + Tolerations is the API server pod's tolerations. + If specified, this overrides any tolerations that may be set on the API server Deployment. + If omitted, the API server Deployment will use its default value for tolerations. + WARNING: Please note that this field will override the default API server Deployment tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + type: object + type: object + type: object + calicoWebhooksDeployment: + description: + CalicoWebhooksDeployment configures the calico-webhooks + Deployment. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the calico-webhooks + Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-webhooks Deployment. + If omitted, the calico-webhooks Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-webhooks Deployment + pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the calico-webhooks Deployment's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-webhooks pods. + If specified, this overrides any affinity that may be set on the calico-webhooks Deployment. + If omitted, the calico-webhooks Deployment will use its default value for affinity. + WARNING: Please note that this field will override the default calico-webhooks Deployment affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-webhooks containers. + If specified, this overrides the specified calico-webhooks Deployment containers. + If omitted, the calico-webhooks Deployment will use its default values for its containers. + items: + description: + CalicoWebhooksDeploymentContainer is + a calico-webhooks Deployment container. + properties: + name: + description: |- + Name is an enum which identifies the calico-webhooks Deployment container by name. + Supported values are: calico-webhooks + enum: + - calico-webhooks + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-webhooks Deployment container's resources. + If omitted, the calico-webhooks Deployment will use its default value for this container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-webhooks pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-webhooks Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If used in conjunction with ControlPlaneNodeSelector, that nodeSelector is set on the calico-webhooks Deployment + and each of this field's key/value pairs are added to the calico-webhooks Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-webhooks Deployment will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-webhooks Deployment nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-webhooks pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-webhooks Deployment. + If omitted, the calico-webhooks Deployment will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-webhooks Deployment tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + logging: + properties: + apiServer: + properties: + logSeverity: + default: Info + description: LogSeverity defines log level for APIServer container. + enum: + - Fatal + - Error + - Warn + - Info + - Debug + - Trace + type: string + type: object + queryServer: + properties: + logSeverity: + default: Info + description: + LogSeverity defines log level for QueryServer + container. + enum: + - Fatal + - Error + - Warn + - Info + - Debug + - Trace + type: string + type: object + type: object + type: object + status: + description: Most recently observed status for the Tigera API server. + properties: + conditions: + description: |- + Conditions represents the latest observed set of conditions for the component. A component may be one or more of + Ready, Progressing, Degraded or other customer types. + items: + description: + Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + state: + description: State provides user-readable status. + type: string + type: object + type: object + x-kubernetes-validations: + - message: resource name must be 'default' or 'tigera-secure' + rule: self.metadata.name == 'default' || self.metadata.name == 'tigera-secure' + served: true + storage: true + subresources: + status: {} diff --git a/charts/projectcalico.org.v3/templates/operator.tigera.io_gatewayapis.yaml b/charts/projectcalico.org.v3/templates/operator.tigera.io_gatewayapis.yaml new file mode 100644 index 00000000000..7a8c71cf2ea --- /dev/null +++ b/charts/projectcalico.org.v3/templates/operator.tigera.io_gatewayapis.yaml @@ -0,0 +1,5465 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: gatewayapis.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: GatewayAPI + listKind: GatewayAPIList + plural: gatewayapis + singular: gatewayapi + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: + GatewayAPISpec has fields that can be used to customize our + GatewayAPI support. + properties: + crdManagement: + description: |- + Configures how to manage and update Gateway API CRDs. The default behaviour - which is + used when this field is not set, or is set to "PreferExisting" - is that the Tigera + operator will create the Gateway API CRDs if they do not already exist, but will not + overwrite any existing Gateway API CRDs. This setting may be preferable if the customer + is using other implementations of the Gateway API concurrently with the Gateway API + support in Calico Enterprise. It is then the customer's responsibility to ensure that + CRDs are installed that meet the needs of all the Gateway API implementations in their + cluster. + Alternatively, if this field is set to "Reconcile", the Tigera operator will keep the + cluster's Gateway API CRDs aligned with those that it would install on a cluster that + does not yet have any version of those CRDs. + enum: + - Reconcile + - PreferExisting + type: string + envoyGatewayConfigRef: + description: |- + Reference to a custom EnvoyGateway YAML to use as the base EnvoyGateway configuration for + the gateway controller. When specified, must identify a ConfigMap resource with an + "envoy-gateway.yaml" key whose value is the desired EnvoyGateway YAML (i.e. following the + same pattern as the default `envoy-gateway-config` ConfigMap). + When not specified, the Tigera operator uses the `envoy-gateway-config` from the Envoy + Gateway helm chart as its base. + Starting from that base, the Tigera operator copies and modifies the EnvoyGateway + resource as follows: + 1. If not already specified, it sets the ControllerName to + "gateway.envoyproxy.io/gatewayclass-controller". + 2. It configures the `tigera/envoy-gateway` and `tigera/envoy-ratelimit` images that will + be used (according to the current Calico version, private registry and image set + settings) and any pull secrets that are needed to pull those images. + 3. It enables use of the Backend API. + The resulting EnvoyGateway is provisioned as the `envoy-gateway-config` ConfigMap (which + the gateway controller then uses as its config). + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + gatewayCertgenJob: + description: Allows customization of the gateway certgen job. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into the + job's top-level metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + GatewayCertgenJobSpec allows customization of the + gateway certgen job spec. + properties: + template: + description: + GatewayCertgenJobPodTemplate allows customization + of the gateway certgen job's pod template. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into the + job's pod template. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + GatewayCertgenJobPodSpec allows customization + of the gateway certgen job's pod spec. + properties: + affinity: + description: + If non-nil, Affinity sets the affinity + field of the job's pod template. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + items: + description: |- + GatewayCertgenJobContainer allows customization of the gateway certgen job's resource + requirements. + properties: + name: + enum: + - envoy-gateway-certgen + type: string + resources: + description: |- + If non-nil, Resources sets the ResourceRequirements of the job's "envoy-gateway-certgen" + container. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: + If non-nil, NodeSelector sets the node + selector for where job pods may be scheduled. + type: object + tolerations: + description: + If non-nil, Tolerations sets the tolerations + field of the job's pod template. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + gatewayClasses: + description: |- + Configures the GatewayClasses that will be available; please see GatewayClassSpec for + more detail. If GatewayClasses is nil, the Tigera operator defaults to provisioning a + single GatewayClass named "tigera-gateway-class", without any of the detailed + customizations that are allowed within GatewayClassSpec. + items: + properties: + envoyProxyRef: + description: |- + Reference to a custom EnvoyProxy resource to use as the base EnvoyProxy configuration for + this GatewayClass. When specified, must identify an EnvoyProxy resource. + When not specified, the Tigera operator uses an empty EnvoyProxy resource as its base. + Starting from that base, the Tigera operator copies and modifies the EnvoyProxy resource + as follows, in the order described: + 1. It configures the `tigera/envoy-proxy` image that will be used (according to the + current Calico version, private registry and image set settings) and any pull secrets + that are needed to pull that image. + 2. It applies customizations as specified by the following `GatewayKind`, + `GatewayDeployment`, `GatewayDaemonSet` and `GatewayService` fields. + The resulting EnvoyProxy is provisioned in the `tigera-gateway` namespace, together with + a GatewayClass that references it. + If a custom EnvoyProxy resource is specified and uses `EnvoyDaemonSet` instead of the + default `EnvoyDeployment`, deployment-related customizations will be applied within + `EnvoyDaemonSet` instead of within `EnvoyDeployment`. + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + gatewayDaemonSet: + description: |- + Allows customization of Gateways when deployed as Kubernetes DaemonSets, for Gateways in + this GatewayClass. + properties: + spec: + description: + GatewayDeploymentSpec allows customization + of the spec of gateway daemonsets. + properties: + template: + description: + GatewayDeploymentPodTemplate allows customization + of the pod template of gateway daemonsets. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into each + daemonset's pod template. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + GatewayDaemonSetPodSpec allows customization + of the pod spec of gateway daemonsets. + properties: + affinity: + description: + If non-nil, Affinity sets the affinity + field of the daemonset's pod template. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by + node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by + node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated + with matching the corresponding + nodeSelectorTerm, in the range + 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of + node selector terms. The terms + are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by + node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by + node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of + the matched WeightedPodAffinityTerm + fields are added per-node to find + the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity + scheduling rules (e.g. avoid putting this + pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of + the matched WeightedPodAffinityTerm + fields are added per-node to find + the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + items: + description: |- + GatewayDaemonSetContainer allows customization of the resource requirements of gateway + daemonsets. + properties: + name: + enum: + - envoy + type: string + resources: + description: |- + If non-nil, Resources sets the ResourceRequirements of the daemonset's "envoy" + container. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + If non-nil, NodeSelector sets the node selector for where daemonset pods may be + scheduled. + type: object + tolerations: + description: + If non-nil, Tolerations sets the + tolerations field of the daemonset's pod template. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + If non-nil, TopologySpreadConstraints sets the topology spread constraints of the + daemonset's pod template. TopologySpreadConstraints describes how a group of pods ought + to spread across topology domains. Scheduler will schedule pods in a way which abides by + the constraints. All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given + topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a + list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + type: object + type: object + type: object + gatewayDeployment: + description: |- + Allows customization of Gateways when deployed as Kubernetes Deployments, for Gateways in + this GatewayClass. + properties: + spec: + description: + GatewayDeploymentSpec allows customization + of the spec of gateway deployments. + properties: + replicas: + description: + If non-nil, Replicas sets the number of + replicas for the deployment. + format: int32 + type: integer + strategy: + description: + The deployment strategy to use to replace + existing pods with new ones. + properties: + rollingUpdate: + description: + Spec to control the desired behavior + of rolling update. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: object + template: + description: + GatewayDeploymentPodTemplate allows customization + of the pod template of gateway deployments. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into each + deployment's pod template. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + GatewayDeploymentPodSpec allows customization + of the pod spec of gateway deployments. + properties: + affinity: + description: + If non-nil, Affinity sets the affinity + field of the deployment's pod template. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by + node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by + node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated + with matching the corresponding + nodeSelectorTerm, in the range + 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of + node selector terms. The terms + are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by + node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by + node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of + the matched WeightedPodAffinityTerm + fields are added per-node to find + the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity + scheduling rules (e.g. avoid putting this + pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of + the matched WeightedPodAffinityTerm + fields are added per-node to find + the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + items: + description: |- + GatewayDeploymentContainer allows customization of the resource requirements of gateway + deployments. + properties: + name: + enum: + - envoy + type: string + resources: + description: |- + If non-nil, Resources sets the ResourceRequirements of the deployment's "envoy" + container. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + If non-nil, NodeSelector sets the node selector for where deployment pods may be + scheduled. + type: object + tolerations: + description: + If non-nil, Tolerations sets the + tolerations field of the deployment's pod + template. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + If non-nil, TopologySpreadConstraints sets the topology spread constraints of the + deployment's pod template. TopologySpreadConstraints describes how a group of pods ought + to spread across topology domains. Scheduler will schedule pods in a way which abides by + the constraints. All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given + topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a + list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + type: object + type: object + type: object + gatewayKind: + description: |- + Specifies whether Gateways in this class are deployed as Deployments (default) or as + DaemonSets. It is an error for GatewayKind to specify a choice that is incompatible with + the custom EnvoyProxy, when EnvoyProxyRef is also specified. + enum: + - Deployment + - DaemonSet + type: string + gatewayService: + description: + Allows customization of gateway services, for Gateways + in this GatewayClass. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into the + each Gateway Service's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: |- + GatewayServiceSpec allows customization of the services that front gateway deployments. + The LoadBalancer fields allow customization of the corresponding fields in the Kubernetes + ServiceSpec. These can be used for some cloud-independent control of the external load balancer + that is provisioned for each Gateway. For finer-grained cloud-specific control please use + the Metadata.Annotations field in GatewayService. + properties: + allocateLoadBalancerNodePorts: + type: boolean + loadBalancerClass: + type: string + loadBalancerIP: + type: string + loadBalancerSourceRanges: + items: + type: string + type: array + type: object + type: object + name: + description: The name of this GatewayClass. + type: string + required: + - name + type: object + type: array + gatewayControllerDeployment: + description: Allows customization of the gateway controller deployment. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into the + deployment's top-level metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + GatewayControllerDeploymentSpec allows customization + of the gateway controller deployment spec. + properties: + minReadySeconds: + description: + If non-nil, MinReadySeconds sets the minReadySeconds + field for the deployment. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + replicas: + description: + If non-nil, Replicas sets the number of replicas + for the deployment. + format: int32 + type: integer + template: + description: |- + GatewayControllerDeploymentPodTemplate allows customization of the gateway controller deployment + pod template. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into the + deployment's pod template. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: |- + GatewayControllerDeploymentPodSpec allows customization of the gateway controller deployment pod + spec. + properties: + affinity: + description: + If non-nil, Affinity sets the affinity + field of the deployment's pod template. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + items: + description: |- + GatewayControllerDeploymentContainer allows customization of the gateway controller's resource + requirements. + properties: + name: + enum: + - envoy-gateway + type: string + resources: + description: |- + If non-nil, Resources sets the ResourceRequirements of the controller's "envoy-gateway" + container. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + If non-nil, NodeSelector sets the node selector for where deployment pods may be + scheduled. + type: object + tolerations: + description: + If non-nil, Tolerations sets the tolerations + field of the deployment's pod template. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + If non-nil, TopologySpreadConstraints sets the topology spread constraints of the + deployment's pod template. TopologySpreadConstraints describes how a group of pods ought + to spread across topology domains. Scheduler will schedule pods in a way which abides by + the constraints. All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + type: object + type: object + type: object + type: object + type: object + x-kubernetes-validations: + - message: resource name must be 'default' + rule: self.metadata.name == 'default' || self.metadata.name == 'tigera-secure' + served: true + storage: true diff --git a/charts/projectcalico.org.v3/templates/operator.tigera.io_goldmanes.yaml b/charts/projectcalico.org.v3/templates/operator.tigera.io_goldmanes.yaml new file mode 100644 index 00000000000..58f329f3c18 --- /dev/null +++ b/charts/projectcalico.org.v3/templates/operator.tigera.io_goldmanes.yaml @@ -0,0 +1,1520 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: goldmanes.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: Goldmane + listKind: GoldmaneList + plural: goldmanes + singular: goldmane + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + goldmaneDeployment: + description: + GoldmaneDeployment is the configuration for the goldmane + Deployment. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the specification of the goldmane Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the goldmane Deployment. + If omitted, the goldmane Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + strategy: + description: + The deployment strategy to use to replace existing + pods with new ones. + properties: + rollingUpdate: + description: |- + Rolling update config params. Present only if DeploymentStrategyType = + RollingUpdate. + to be. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: object + template: + description: + Template describes the goldmane Deployment pod + that will be created. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the goldmane Deployment's PodSpec. + properties: + affinity: + description: + Affinity is a group of affinity scheduling + rules for the goldmane pods. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of goldmane containers. + If specified, this overrides the specified EGW Deployment containers. + If omitted, the goldmane Deployment will use its default values for its containers. + items: + properties: + name: + enum: + - goldmane + type: string + resources: + description: + ResourceRequirements describes + the compute resource requirements. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: + NodeSelector gives more control over + the nodes where the goldmane pods will run on. + type: object + priorityClassName: + description: + PriorityClassName allows to specify a + PriorityClass resource to be used. + type: string + terminationGracePeriodSeconds: + description: + TerminationGracePeriodSeconds defines + the termination grace period of the goldmane pods + in seconds. + format: int64 + minimum: 0 + type: integer + tolerations: + description: |- + Tolerations is the goldmane pod's tolerations. + If specified, this overrides any tolerations that may be set on the goldmane Deployment. + If omitted, the goldmane Deployment will use its default value for tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + type: object + type: object + type: object + type: object + status: + description: GoldmaneStatus defines the observed state of Goldmane + properties: + conditions: + description: |- + Conditions represents the latest observed set of conditions for the component. A component may be one or more of + Ready, Progressing, Degraded or other customer types. + items: + description: + Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + x-kubernetes-validations: + - message: resource name must be 'default' + rule: self.metadata.name == 'default' + served: true + storage: true + subresources: + status: {} diff --git a/charts/projectcalico.org.v3/templates/operator.tigera.io_imagesets.yaml b/charts/projectcalico.org.v3/templates/operator.tigera.io_imagesets.yaml new file mode 100644 index 00000000000..552c7ab87ac --- /dev/null +++ b/charts/projectcalico.org.v3/templates/operator.tigera.io_imagesets.yaml @@ -0,0 +1,78 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: imagesets.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: ImageSet + listKind: ImageSetList + plural: imagesets + singular: imageset + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: |- + ImageSet is used to specify image digests for the images that the operator deploys. + The name of the ImageSet is expected to be in the format `-`. + The `variant` used is `enterprise` if the InstallationSpec Variant is + `TigeraSecureEnterprise` otherwise it is `calico`. + The `release` must match the version of the variant that the operator is built to deploy, + this version can be obtained by passing the `--version` flag to the operator binary. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: ImageSetSpec defines the desired state of ImageSet. + properties: + images: + description: |- + Images is the list of images to use digests. All images that the operator will deploy + must be specified. + items: + properties: + digest: + description: |- + Digest is the image identifier that will be used for the Image. + The field should not include a leading `@` and must be prefixed with `sha256:`. + type: string + image: + description: |- + Image is an image that the operator deploys and instead of using the built in tag + the operator will use the Digest for the image identifier. + The value should be the *original* image name without registry or tag or digest. + For the image `docker.io/calico/node:v3.17.1` it should be represented as `calico/node` + The "Installation" spec allows defining custom image registries, paths or prefixes. + Even for custom images such as example.com/custompath/customprefix-calico-node:v3.17.1, + this value should still be `calico/node`. + type: string + required: + - digest + - image + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/projectcalico.org.v3/templates/operator.tigera.io_installations.yaml b/charts/projectcalico.org.v3/templates/operator.tigera.io_installations.yaml new file mode 100644 index 00000000000..c51e8453af2 --- /dev/null +++ b/charts/projectcalico.org.v3/templates/operator.tigera.io_installations.yaml @@ -0,0 +1,17850 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: installations.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: Installation + listKind: InstallationList + plural: installations + singular: installation + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: |- + Installation configures an installation of Calico or Calico Enterprise. At most one instance + of this resource is supported. It must be named "default". The Installation API installs core networking + and network policy components, and provides general install-time configuration. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: + Specification of the desired state for the Calico or Calico + Enterprise installation. + properties: + azure: + description: Azure is used to configure azure provider specific options. + properties: + policyMode: + default: Default + description: |- + PolicyMode determines whether the "control-plane" label is applied to namespaces. It offers two options: Default and Manual. + The Default option adds the "control-plane" label to the required namespaces. + The Manual option does not apply the "control-plane" label to any namespace. + Default: Default + enum: + - Default + - Manual + type: string + type: object + calicoKubeControllersDeployment: + description: |- + CalicoKubeControllersDeployment configures the calico-kube-controllers Deployment. If used in + conjunction with the deprecated ComponentResources, then these overrides take precedence. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the calico-kube-controllers + Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-kube-controllers Deployment. + If omitted, the calico-kube-controllers Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-kube-controllers + Deployment pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the calico-kube-controllers Deployment's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-kube-controllers pods. + If specified, this overrides any affinity that may be set on the calico-kube-controllers Deployment. + If omitted, the calico-kube-controllers Deployment will use its default value for affinity. + WARNING: Please note that this field will override the default calico-kube-controllers Deployment affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-kube-controllers containers. + If specified, this overrides the specified calico-kube-controllers Deployment containers. + If omitted, the calico-kube-controllers Deployment will use its default values for its containers. + items: + description: + CalicoKubeControllersDeploymentContainer + is a calico-kube-controllers Deployment container. + properties: + name: + description: |- + Name is an enum which identifies the calico-kube-controllers Deployment container by name. + Supported values are: calico-kube-controllers, es-calico-kube-controllers + enum: + - calico-kube-controllers + - es-calico-kube-controllers + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-kube-controllers Deployment container's resources. + If omitted, the calico-kube-controllers Deployment will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-kube-controllers pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-kube-controllers Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If used in conjunction with ControlPlaneNodeSelector, that nodeSelector is set on the calico-kube-controllers Deployment + and each of this field's key/value pairs are added to the calico-kube-controllers Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-kube-controllers Deployment will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-kube-controllers Deployment nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-kube-controllers pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-kube-controllers Deployment. + If omitted, the calico-kube-controllers Deployment will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-kube-controllers Deployment tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + calicoNetwork: + description: + CalicoNetwork specifies networking configuration options + for Calico. + properties: + bgp: + description: + BGP configures whether or not to enable Calico's + BGP capabilities. + enum: + - Enabled + - Disabled + type: string + bpfNetworkBootstrap: + description: |- + BPFNetworkBootstrap manages the initial networking setup required to configure the BPF dataplane. + When enabled, the operator tries to bootstraps access to the Kubernetes API Server + by using the Kubernetes service and its associated endpoints. + This field should be enabled only if linuxDataplane is set to "BPF". + If another dataplane is selected, this field must be omitted or explicitly set to Disabled. + When disabled and linuxDataplane is BPF, you must manually provide the Kubernetes API Server + information via the "kubernetes-service-endpoint" ConfigMap. It is invalid to use both the ConfigMap + and have this field set to true at the same time. + Default: Disabled + enum: + - Disabled + - Enabled + type: string + containerIPForwarding: + description: |- + ContainerIPForwarding configures whether ip forwarding will be enabled for containers in the CNI configuration. + Default: Disabled + enum: + - Enabled + - Disabled + type: string + hostPorts: + description: |- + HostPorts configures whether or not Calico will support Kubernetes HostPorts. Valid only when using the Calico CNI plugin. + Default: Enabled + enum: + - Enabled + - Disabled + type: string + ipPools: + description: |- + IPPools contains a list of IP pools to manage. If nil, a single IPv4 IP pool + will be created by the operator. If an empty list is provided, the operator will not create any IP pools and will instead + wait for IP pools to be created out-of-band. + IP pools in this list will be reconciled by the operator and should not be modified out-of-band. + items: + properties: + allowedUses: + description: |- + AllowedUse controls what the IP pool will be used for. If not specified or empty, defaults to + ["Tunnel", "Workload"] for back-compatibility + items: + type: string + type: array + assignmentMode: + description: + AssignmentMode determines if IP addresses from + this pool should be assigned automatically or on request + only + type: string + blockSize: + description: |- + BlockSize specifies the CIDR prefex length to use when allocating per-node IP blocks from + the main IP pool CIDR. + Default: 26 (IPv4), 122 (IPv6) + format: int32 + type: integer + cidr: + description: + CIDR contains the address range for the IP + Pool in classless inter-domain routing format. + type: string + disableBGPExport: + default: false + description: |- + DisableBGPExport specifies whether routes from this IP pool's CIDR are exported over BGP. + Default: false + type: boolean + disableNewAllocations: + description: |- + DisableNewAllocations specifies whether or not new IP allocations are allowed from this pool. + This is useful when you want to prevent new pods from receiving IP addresses from this pool, without + impacting any existing pods that have already been assigned addresses from this pool. + type: boolean + encapsulation: + description: |- + Encapsulation specifies the encapsulation type that will be used with + the IP Pool. + Default: IPIP + enum: + - IPIPCrossSubnet + - IPIP + - VXLAN + - VXLANCrossSubnet + - None + type: string + name: + description: + Name is the name of the IP pool. If omitted, + this will be generated. + type: string + natOutgoing: + description: |- + NATOutgoing specifies if NAT will be enabled or disabled for outgoing traffic. + Default: Enabled + enum: + - Enabled + - Disabled + type: string + nodeSelector: + description: |- + NodeSelector specifies the node selector that will be set for the IP Pool. + Default: 'all()' + type: string + required: + - cidr + type: object + maxItems: 25 + type: array + kubeProxyManagement: + description: |- + KubeProxyManagement controls whether the operator manages the kube-proxy DaemonSet. + When enabled, the operator will manage the DaemonSet by patching it: + it disables kube-proxy if the dataplane is BPF, or enables it otherwise. + Default: Disabled + enum: + - Disabled + - Enabled + type: string + linuxDataplane: + description: |- + LinuxDataplane is used to select the dataplane used for Linux nodes. In particular, it + causes the operator to add required mounts and environment variables for the particular dataplane. + If not specified, iptables mode is used. + Default: Iptables + enum: + - Iptables + - BPF + - VPP + - Nftables + type: string + linuxPolicySetupTimeoutSeconds: + description: |- + LinuxPolicySetupTimeoutSeconds delays new pods from running containers + until their policy has been programmed in the dataplane. + The specified delay defines the maximum amount of time + that the Calico CNI plugin will wait for policy to be programmed. + Only applies to pods created on Linux nodes. + * A value of 0 disables pod startup delays. + Default: 0 + format: int32 + type: integer + mtu: + description: |- + MTU specifies the maximum transmission unit to use on the pod network. + If not specified, Calico will perform MTU auto-detection based on the cluster network. + format: int32 + type: integer + multiInterfaceMode: + description: |- + MultiInterfaceMode configures what will configure multiple interface per pod. Only valid for Calico Enterprise installations + using the Calico CNI plugin. + Default: None + enum: + - None + - Multus + type: string + nodeAddressAutodetectionV4: + description: |- + NodeAddressAutodetectionV4 specifies an approach to automatically detect node IPv4 addresses. If not specified, + will use default auto-detection settings to acquire an IPv4 address for each node. + properties: + canReach: + description: |- + CanReach enables IP auto-detection based on which source address on the node is used to reach the + specified IP or domain. + type: string + cidrs: + description: |- + CIDRS enables IP auto-detection based on which addresses on the nodes are within + one of the provided CIDRs. + items: + type: string + type: array + firstFound: + description: |- + FirstFound uses default interface matching parameters to select an interface, performing best-effort + filtering based on well-known interface names. + type: boolean + interface: + description: + Interface enables IP auto-detection based on + interfaces that match the given regex. + type: string + kubernetes: + description: + Kubernetes configures Calico to detect node addresses + based on the Kubernetes API. + enum: + - NodeInternalIP + type: string + skipInterface: + description: |- + SkipInterface enables IP auto-detection based on interfaces that do not match + the given regex. + type: string + type: object + nodeAddressAutodetectionV6: + description: |- + NodeAddressAutodetectionV6 specifies an approach to automatically detect node IPv6 addresses. If not specified, + IPv6 addresses will not be auto-detected. + properties: + canReach: + description: |- + CanReach enables IP auto-detection based on which source address on the node is used to reach the + specified IP or domain. + type: string + cidrs: + description: |- + CIDRS enables IP auto-detection based on which addresses on the nodes are within + one of the provided CIDRs. + items: + type: string + type: array + firstFound: + description: |- + FirstFound uses default interface matching parameters to select an interface, performing best-effort + filtering based on well-known interface names. + type: boolean + interface: + description: + Interface enables IP auto-detection based on + interfaces that match the given regex. + type: string + kubernetes: + description: + Kubernetes configures Calico to detect node addresses + based on the Kubernetes API. + enum: + - NodeInternalIP + type: string + skipInterface: + description: |- + SkipInterface enables IP auto-detection based on interfaces that do not match + the given regex. + type: string + type: object + sysctl: + description: Sysctl configures sysctl parameters for tuning plugin + items: + properties: + key: + enum: + - net.ipv4.tcp_keepalive_intvl + - net.ipv4.tcp_keepalive_probes + - net.ipv4.tcp_keepalive_time + type: string + value: + type: string + required: + - key + - value + type: object + type: array + windowsDataplane: + description: |- + WindowsDataplane is used to select the dataplane used for Windows nodes. In particular, it + causes the operator to add required mounts and environment variables for the particular dataplane. + If not specified, it is disabled and the operator will not render the Calico Windows nodes daemonset. + Default: Disabled + enum: + - HNS + - Disabled + type: string + type: object + calicoNodeDaemonSet: + description: |- + CalicoNodeDaemonSet configures the calico-node DaemonSet. If used in + conjunction with the deprecated ComponentResources, then these overrides take precedence. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the DaemonSet. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the specification of the calico-node DaemonSet. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-node DaemonSet. + If omitted, the calico-node DaemonSet will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-node DaemonSet + pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the calico-node DaemonSet's PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-node pods. + If specified, this overrides any affinity that may be set on the calico-node DaemonSet. + If omitted, the calico-node DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default calico-node DaemonSet affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-node containers. + If specified, this overrides the specified calico-node DaemonSet containers. + If omitted, the calico-node DaemonSet will use its default values for its containers. + items: + description: + CalicoNodeDaemonSetContainer is a calico-node + DaemonSet container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node DaemonSet container by name. + Supported values are: calico-node + enum: + - calico-node + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-node DaemonSet container's resources. + If omitted, the calico-node DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + dnsConfig: + description: + DNSConfig allows customization of the + DNS configuration for the calico-node pods. + properties: + nameservers: + description: |- + A list of DNS name server IP addresses. + This will be appended to the base nameservers generated from DNSPolicy. + Duplicated nameservers will be removed. + items: + type: string + type: array + x-kubernetes-list-type: atomic + options: + description: |- + A list of DNS resolver options. + This will be merged with the base options generated from DNSPolicy. + Duplicated entries will be removed. Resolution options given in Options + will override those that appear in the base DNSPolicy. + items: + description: + PodDNSConfigOption defines DNS + resolver options of a pod. + properties: + name: + description: |- + Name is this DNS resolver option's name. + Required. + type: string + value: + description: + Value is this DNS resolver + option's value. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + searches: + description: |- + A list of DNS search domains for host-name lookup. + This will be appended to the base search paths generated from DNSPolicy. + Duplicated search paths will be removed. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + dnsPolicy: + description: + DNSPolicy is the DNS policy for the calico-node + pods. + enum: + - "" + - Default + - ClusterFirst + - ClusterFirstWithHostNet + - None + type: string + initContainers: + description: |- + InitContainers is a list of calico-node init containers. + If specified, this overrides the specified calico-node DaemonSet init containers. + If omitted, the calico-node DaemonSet will use its default values for its init containers. + items: + description: + CalicoNodeDaemonSetInitContainer is + a calico-node DaemonSet init container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node DaemonSet init container by name. + Supported values are: install-cni, hostpath-init, flexvol-driver, ebpf-bootstrap, node-certs-key-cert-provisioner, calico-node-prometheus-server-tls-key-cert-provisioner, mount-bpffs (deprecated, replaced by ebpf-bootstrap) + enum: + - install-cni + - hostpath-init + - flexvol-driver + - ebpf-bootstrap + - node-certs-key-cert-provisioner + - calico-node-prometheus-server-tls-key-cert-provisioner + - mount-bpffs + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-node DaemonSet init container's resources. + If omitted, the calico-node DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-node pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-node DaemonSet nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-node DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-node DaemonSet nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-node pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-node DaemonSet. + If omitted, the calico-node DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-node DaemonSet tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + calicoNodeWindowsDaemonSet: + description: + CalicoNodeWindowsDaemonSet configures the calico-node-windows + DaemonSet. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the DaemonSet. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the calico-node-windows + DaemonSet. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-node-windows DaemonSet. + If omitted, the calico-node-windows DaemonSet will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-node-windows DaemonSet + pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the calico-node-windows DaemonSet's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-node-windows pods. + If specified, this overrides any affinity that may be set on the calico-node-windows DaemonSet. + If omitted, the calico-node-windows DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default calico-node-windows DaemonSet affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-node-windows containers. + If specified, this overrides the specified calico-node-windows DaemonSet containers. + If omitted, the calico-node-windows DaemonSet will use its default values for its containers. + items: + description: + CalicoNodeWindowsDaemonSetContainer + is a calico-node-windows DaemonSet container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node-windows DaemonSet container by name. + Supported values are: node, felix, confd + calico-node-windows is allowed because it was previously allowed. + enum: + - calico-node-windows + - node + - felix + - confd + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named DaemonSet container's resources. + If omitted, the DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + initContainers: + description: |- + InitContainers is a list of calico-node-windows init containers. + If specified, this overrides the specified calico-node-windows DaemonSet init containers. + If omitted, the calico-node-windows DaemonSet will use its default values for its init containers. + items: + description: + CalicoNodeWindowsDaemonSetInitContainer + is a calico-node-windows DaemonSet init container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node-windows DaemonSet init container by name. + Supported values are: install-cni;hostpath-init, flexvol-driver, node-certs-key-cert-provisioner, calico-node-windows-prometheus-server-tls-key-cert-provisioner + enum: + - install-cni + - hostpath-init + - flexvol-driver + - node-certs-key-cert-provisioner + - calico-node-windows-prometheus-server-tls-key-cert-provisioner + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-node-windows DaemonSet init container's resources. + If omitted, the calico-node-windows DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-node-windows pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-node-windows DaemonSet nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-node-windows DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-node-windows DaemonSet nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-node-windows pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-node-windows DaemonSet. + If omitted, the calico-node-windows DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-node-windows DaemonSet tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + calicoWindowsUpgradeDaemonSet: + description: |- + Deprecated. The CalicoWindowsUpgradeDaemonSet is deprecated and will be removed from the API in the future. + CalicoWindowsUpgradeDaemonSet configures the calico-windows-upgrade DaemonSet. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the calico-windows-upgrade + DaemonSet. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-windows-upgrade DaemonSet. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-windows-upgrade + DaemonSet pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the calico-windows-upgrade DaemonSet's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-windows-upgrade pods. + If specified, this overrides any affinity that may be set on the calico-windows-upgrade DaemonSet. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default calico-windows-upgrade DaemonSet affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-windows-upgrade containers. + If specified, this overrides the specified calico-windows-upgrade DaemonSet containers. + If omitted, the calico-windows-upgrade DaemonSet will use its default values for its containers. + items: + description: + CalicoWindowsUpgradeDaemonSetContainer + is a calico-windows-upgrade DaemonSet container. + properties: + name: + description: + Name is an enum which identifies + the calico-windows-upgrade DaemonSet container + by name. + enum: + - calico-windows-upgrade + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-windows-upgrade DaemonSet container's resources. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for this container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-windows-upgrade pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-windows-upgrade DaemonSet nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-windows-upgrade DaemonSet nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-windows-upgrade pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-windows-upgrade DaemonSet. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-windows-upgrade DaemonSet tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + certificateManagement: + description: |- + CertificateManagement configures pods to submit a CertificateSigningRequest to the certificates.k8s.io/v1 API in order + to obtain TLS certificates. This feature requires that you bring your own CSR signing and approval process, otherwise + pods will be stuck during initialization. + properties: + caCert: + description: + Certificate of the authority that signs the CertificateSigningRequests + in PEM format. + format: byte + type: string + keyAlgorithm: + description: |- + Specify the algorithm used by pods to generate a key pair that is associated with the X.509 certificate request. + Default: RSAWithSize2048 + enum: + - "" + - RSAWithSize2048 + - RSAWithSize4096 + - RSAWithSize8192 + - ECDSAWithCurve256 + - ECDSAWithCurve384 + - ECDSAWithCurve521 + type: string + signatureAlgorithm: + description: |- + Specify the algorithm used for the signature of the X.509 certificate request. + Default: SHA256WithRSA + enum: + - "" + - SHA256WithRSA + - SHA384WithRSA + - SHA512WithRSA + - ECDSAWithSHA256 + - ECDSAWithSHA384 + - ECDSAWithSHA512 + type: string + signerName: + description: |- + When a CSR is issued to the certificates.k8s.io API, the signerName is added to the request in order to accommodate for clusters + with multiple signers. + Must be formatted as: `/`. + type: string + required: + - caCert + - signerName + type: object + cni: + description: CNI specifies the CNI that will be used by this installation. + properties: + binDir: + description: |- + BinDir is the path to the CNI binaries directory. + If you have changed the installation directory for CNI binaries in the container runtime configuration, + please ensure that this field points to the same directory as specified in the container runtime settings. + Default directory depends on the KubernetesProvider. + * For KubernetesProvider GKE, this field defaults to "/home/kubernetes/bin". + * For KubernetesProvider OpenShift, this field defaults to "/var/lib/cni/bin". + * Otherwise, this field defaults to "/opt/cni/bin". + type: string + confDir: + description: |- + ConfDir is the path to the CNI config directory. + If you have changed the installation directory for CNI configuration in the container runtime configuration, + please ensure that this field points to the same directory as specified in the container runtime settings. + Default directory depends on the KubernetesProvider. + * For KubernetesProvider GKE, this field defaults to "/etc/cni/net.d". + * For KubernetesProvider OpenShift, this field defaults to "/var/run/multus/cni/net.d". + * Otherwise, this field defaults to "/etc/cni/net.d". + type: string + ipam: + description: |- + IPAM specifies the pod IP address management that will be used in the Calico or + Calico Enterprise installation. + properties: + type: + description: |- + Specifies the IPAM plugin that will be used in the Calico or Calico Enterprise installation. + * For CNI Plugin Calico, this field defaults to Calico. + * For CNI Plugin GKE, this field defaults to HostLocal. + * For CNI Plugin AzureVNET, this field defaults to AzureVNET. + * For CNI Plugin AmazonVPC, this field defaults to AmazonVPC. + The IPAM plugin is installed and configured only if the CNI plugin is set to Calico, + for all other values of the CNI plugin the plugin binaries and CNI config is a dependency + that is expected to be installed separately. + Default: Calico + enum: + - Calico + - HostLocal + - AmazonVPC + - AzureVNET + type: string + required: + - type + type: object + type: + description: |- + Specifies the CNI plugin that will be used in the Calico or Calico Enterprise installation. + * For KubernetesProvider GKE, this field defaults to GKE. + * For KubernetesProvider AKS, this field defaults to AzureVNET. + * For KubernetesProvider EKS, this field defaults to AmazonVPC. + * If aws-node daemonset exists in kube-system when the Installation resource is created, this field defaults to AmazonVPC. + * For all other cases this field defaults to Calico. + For the value Calico, the CNI plugin binaries and CNI config will be installed as part of deployment, + for all other values the CNI plugin binaries and CNI config is a dependency that is expected + to be installed separately. + Default: Calico + enum: + - Calico + - GKE + - AmazonVPC + - AzureVNET + type: string + required: + - type + type: object + componentResources: + description: |- + Deprecated. Please use CalicoNodeDaemonSet, TyphaDeployment, and KubeControllersDeployment. + ComponentResources can be used to customize the resource requirements for each component. + Node, Typha, and KubeControllers are supported for installations. + items: + description: |- + Deprecated. Please use component resource config fields in Installation.Spec instead. + The ComponentResource struct associates a ResourceRequirements with a component by name + properties: + componentName: + description: ComponentName is an enum which identifies the component + enum: + - Node + - Typha + - KubeControllers + - NodeWindows + - FelixWindows + - ConfdWindows + type: string + resourceRequirements: + description: + ResourceRequirements allows customization of limits + and requests for compute resources such as cpu and memory. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - componentName + - resourceRequirements + type: object + type: array + controlPlaneNodeSelector: + additionalProperties: + type: string + description: |- + ControlPlaneNodeSelector is used to select control plane nodes on which to run Calico + components. This is globally applied to all resources created by the operator excluding daemonsets. + type: object + controlPlaneReplicas: + description: |- + ControlPlaneReplicas defines how many replicas of the control plane core components will be deployed. + This field applies to all control plane components that support High Availability. Defaults to 2. + format: int32 + type: integer + controlPlaneTolerations: + description: |- + ControlPlaneTolerations specify tolerations which are then globally applied to all resources + created by the operator. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + csiNodeDriverDaemonSet: + description: + CSINodeDriverDaemonSet configures the csi-node-driver + DaemonSet. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the DaemonSet. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the csi-node-driver + DaemonSet. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the csi-node-driver DaemonSet. + If omitted, the csi-node-driver DaemonSet will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the csi-node-driver DaemonSet + pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the csi-node-driver DaemonSet's PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the csi-node-driver pods. + If specified, this overrides any affinity that may be set on the csi-node-driver DaemonSet. + If omitted, the csi-node-driver DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default csi-node-driver DaemonSet affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of csi-node-driver containers. + If specified, this overrides the specified csi-node-driver DaemonSet containers. + If omitted, the csi-node-driver DaemonSet will use its default values for its containers. + items: + description: + CSINodeDriverDaemonSetContainer is + a csi-node-driver DaemonSet container. + properties: + name: + description: |- + Name is an enum which identifies the csi-node-driver DaemonSet container by name. + Supported values are: calico-csi, csi-node-driver-registrar. + enum: + - calico-csi + - csi-node-driver-registrar + - csi-node-driver + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named csi-node-driver DaemonSet container's resources. + If omitted, the csi-node-driver DaemonSet will use its default value for this container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the csi-node-driver pod's scheduling constraints. + If specified, each of the key/value pairs are added to the csi-node-driver DaemonSet nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the csi-node-driver DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default csi-node-driver DaemonSet nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the csi-node-driver pod's tolerations. + If specified, this overrides any tolerations that may be set on the csi-node-driver DaemonSet. + If omitted, the csi-node-driver DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default csi-node-driver DaemonSet tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + fipsMode: + description: |- + FIPSMode uses images and features only that are using FIPS 140-2 validated cryptographic modules and standards. + Only supported for Variant=Calico. + Default: Disabled + enum: + - Enabled + - Disabled + type: string + flexVolumePath: + description: |- + FlexVolumePath optionally specifies a custom path for FlexVolume. If not specified, FlexVolume will be + enabled by default. If set to 'None', FlexVolume will be disabled. The default is based on the + kubernetesProvider. + type: string + imagePath: + description: |- + ImagePath allows for the path part of an image to be specified. If specified + then the specified value will be used as the image path for each image. If not specified + or empty, the default for each image will be used. + A special case value, UseDefault, is supported to explicitly specify the default + image path will be used for each image. + Image format: + `/:` + This option allows configuring the `` portion of the above format. + type: string + imagePrefix: + description: |- + ImagePrefix allows for the prefix part of an image to be specified. If specified + then the given value will be used as a prefix on each image. If not specified + or empty, no prefix will be used. + A special case value, UseDefault, is supported to explicitly specify the default + image prefix will be used for each image. + Image format: + `/:` + This option allows configuring the `` portion of the above format. + type: string + imagePullSecrets: + description: |- + ImagePullSecrets is an array of references to container registry pull secrets to use. These are + applied to all images to be pulled. + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + kubeletVolumePluginPath: + description: |- + KubeletVolumePluginPath optionally specifies enablement of Calico CSI plugin. If not specified, + CSI will be enabled by default. If set to 'None', CSI will be disabled. + Default: /var/lib/kubelet + type: string + kubernetesProvider: + description: |- + KubernetesProvider specifies a particular provider of the Kubernetes platform and enables provider-specific configuration. + If the specified value is empty, the Operator will attempt to automatically determine the current provider. + If the specified value is not empty, the Operator will still attempt auto-detection, but + will additionally compare the auto-detected value to the specified value to confirm they match. + enum: + - "" + - EKS + - GKE + - AKS + - OpenShift + - DockerEnterprise + - RKE2 + - TKG + - Kind + type: string + logging: + description: Logging Configuration for Components + properties: + cni: + description: Customized logging specification for calico-cni plugin + properties: + logFileMaxAgeDays: + description: "Default: 30 (days)" + format: int32 + type: integer + logFileMaxCount: + description: "Default: 10" + format: int32 + type: integer + logFileMaxSize: + anyOf: + - type: integer + - type: string + description: "Default: 100Mi" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + logSeverity: + description: "Default: Info" + enum: + - Error + - Warning + - Info + - Debug + type: string + type: object + type: object + nodeMetricsPort: + description: |- + NodeMetricsPort specifies which port calico/node serves prometheus metrics on. By default, metrics are not enabled. + If specified, this overrides any FelixConfiguration resources which may exist. If omitted, then + prometheus metrics may still be configured through FelixConfiguration. + format: int32 + type: integer + nodeUpdateStrategy: + description: |- + NodeUpdateStrategy can be used to customize the desired update strategy, such as the MaxUnavailable + field. + properties: + rollingUpdate: + description: + Rolling update config params. Present only if type + = "RollingUpdate". + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of nodes with an existing available DaemonSet pod that + can have an updated DaemonSet pod during during an update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up to a minimum of 1. + Default value is 0. + Example: when this is set to 30%, at most 30% of the total number of nodes + that should be running the daemon pod (i.e. status.desiredNumberScheduled) + can have their a new pod created before the old pod is marked as deleted. + The update starts by launching new pods on 30% of nodes. Once an updated + pod is available (Ready for at least minReadySeconds) the old DaemonSet pod + on that node is marked deleted. If the old pod becomes unavailable for any + reason (Ready transitions to false, is evicted, or is drained) an updated + pod is immediately created on that node without considering surge limits. + Allowing surge implies the possibility that the resources consumed by the + daemonset on any given node can double if the readiness check fails, and + so resource intensive daemonsets should take into account that they may + cause evictions during disruption. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of DaemonSet pods that can be unavailable during the + update. Value can be an absolute number (ex: 5) or a percentage of total + number of DaemonSet pods at the start of the update (ex: 10%). Absolute + number is calculated from percentage by rounding up. + This cannot be 0 if MaxSurge is 0 + Default value is 1. + Example: when this is set to 30%, at most 30% of the total number of nodes + that should be running the daemon pod (i.e. status.desiredNumberScheduled) + can have their pods stopped for an update at any given time. The update + starts by stopping at most 30% of those DaemonSet pods and then brings + up new DaemonSet pods in their place. Once the new pods are available, + it then proceeds onto other DaemonSet pods, thus ensuring that at least + 70% of original number of DaemonSet pods are available at all times during + the update. + x-kubernetes-int-or-string: true + type: object + type: + description: + Type of daemon set update. Can be "RollingUpdate" + or "OnDelete". Default is RollingUpdate. + type: string + type: object + nonPrivileged: + description: |- + Deprecated. NonPrivileged is deprecated and will be removed from the API in a future release. + Enabling this field is not supported and will cause errors. + NonPrivileged configures Calico to be run in non-privileged containers as non-root users where possible. + type: string + proxy: + description: |- + Proxy is used to configure the HTTP(S) proxy settings that will be applied to Tigera containers that connect + to destinations outside the cluster. It is expected that NO_PROXY is configured such that destinations within + the cluster (including the API server) are exempt from proxying. + properties: + httpProxy: + description: |- + HTTPProxy defines the value of the HTTP_PROXY environment variable that will be set on Tigera containers that connect to + destinations outside the cluster. + type: string + httpsProxy: + description: |- + HTTPSProxy defines the value of the HTTPS_PROXY environment variable that will be set on Tigera containers that connect to + destinations outside the cluster. + type: string + noProxy: + description: |- + NoProxy defines the value of the NO_PROXY environment variable that will be set on Tigera containers that connect to + destinations outside the cluster. This value must be set such that destinations within the scope of the cluster, including + the Kubernetes API server, are exempt from being proxied. + type: string + type: object + registry: + description: |- + Registry is the default Docker registry used for component Docker images. + If specified then the given value must end with a slash character (`/`) and all images will be pulled from this registry. + If not specified then the default registries will be used. A special case value, UseDefault, is + supported to explicitly specify the default registries will be used. + Image format: + `/:` + This option allows configuring the `` portion of the above format. + type: string + serviceCIDRs: + description: + Kubernetes Service CIDRs. Specifying this is required + when using Calico for Windows. + items: + type: string + type: array + tlsCipherSuites: + description: + TLSCipherSuites defines the cipher suite list that the + TLS protocol should use during secure communication. + items: + properties: + name: + description: This should be a valid TLS cipher suite name. + enum: + - TLS_AES_256_GCM_SHA384 + - TLS_CHACHA20_POLY1305_SHA256 + - TLS_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 + - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + - TLS_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA + - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA + - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + type: string + type: object + type: array + typhaAffinity: + description: |- + Deprecated. Please use Installation.Spec.TyphaDeployment instead. + TyphaAffinity allows configuration of node affinity characteristics for Typha pods. + properties: + nodeAffinity: + description: + NodeAffinity describes node affinity scheduling rules + for typha. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated with the + corresponding weight. + properties: + matchExpressions: + description: + A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with matching the corresponding + nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + WARNING: Please note that if the affinity requirements specified by this field are not met at + scheduling time, the pod will NOT be scheduled onto the node. + There is no fallback to another affinity rules with this setting. + This may cause networking disruption or even catastrophic failure! + PreferredDuringSchedulingIgnoredDuringExecution should be used for affinity + unless there is a specific well understood reason to use RequiredDuringSchedulingIgnoredDuringExecution and + you can guarantee that the RequiredDuringSchedulingIgnoredDuringExecution will always have sufficient nodes to satisfy the requirement. + NOTE: RequiredDuringSchedulingIgnoredDuringExecution is set by default for AKS nodes, + to avoid scheduling Typhas on virtual-nodes. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + type: object + typhaDeployment: + description: |- + TyphaDeployment configures the typha Deployment. If used in conjunction with the deprecated + ComponentResources or TyphaAffinity, then these overrides take precedence. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the specification of the typha Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the typha Deployment. + If omitted, the typha Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + strategy: + description: + The deployment strategy to use to replace existing + pods with new ones. + properties: + rollingUpdate: + description: |- + Rolling update config params. Present only if DeploymentStrategyType = + RollingUpdate. + to be. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: object + template: + description: + Template describes the typha Deployment pod that + will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the typha Deployment's PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the typha pods. + If specified, this overrides any affinity that may be set on the typha Deployment. + If omitted, the typha Deployment will use its default value for affinity. + If used in conjunction with the deprecated TyphaAffinity, then this value takes precedence. + WARNING: Please note that this field will override the default calico-typha Deployment affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of typha containers. + If specified, this overrides the specified typha Deployment containers. + If omitted, the typha Deployment will use its default values for its containers. + items: + description: + TyphaDeploymentContainer is a typha + Deployment container. + properties: + name: + description: |- + Name is an enum which identifies the typha Deployment container by name. + Supported values are: calico-typha + enum: + - calico-typha + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named typha Deployment container's resources. + If omitted, the typha Deployment will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + initContainers: + description: |- + InitContainers is a list of typha init containers. + If specified, this overrides the specified typha Deployment init containers. + If omitted, the typha Deployment will use its default values for its init containers. + items: + description: + TyphaDeploymentInitContainer is a typha + Deployment init container. + properties: + name: + description: |- + Name is an enum which identifies the typha Deployment init container by name. + Supported values are: typha-certs-key-cert-provisioner + enum: + - typha-certs-key-cert-provisioner + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named typha Deployment init container's resources. + If omitted, the typha Deployment will use its default value for this init container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-typha pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-typha Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-typha Deployment will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-typha Deployment nodeSelector. + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + If this value is nil, the default grace period will be used instead. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + Defaults to 30 seconds. + format: int64 + type: integer + tolerations: + description: |- + Tolerations is the typha pod's tolerations. + If specified, this overrides any tolerations that may be set on the typha Deployment. + If omitted, the typha Deployment will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-typha Deployment tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + type: object + type: object + type: object + typhaMetricsPort: + description: + TyphaMetricsPort specifies which port calico/typha serves + prometheus metrics on. By default, metrics are not enabled. + format: int32 + type: integer + variant: + description: |- + Variant is the product to install - one of Calico or TigeraSecureEnterprise + Default: Calico + enum: + - Calico + - TigeraSecureEnterprise + type: string + windowsNodes: + description: Windows Configuration + properties: + cniBinDir: + description: |- + CNIBinDir is the path to the CNI binaries directory on Windows, it must match what is used as 'bin_dir' under + [plugins] + [plugins."io.containerd.grpc.v1.cri"] + [plugins."io.containerd.grpc.v1.cri".cni] + on the containerd 'config.toml' file on the Windows nodes. + type: string + cniConfigDir: + description: |- + CNIConfigDir is the path to the CNI configuration directory on Windows, it must match what is used as 'conf_dir' under + [plugins] + [plugins."io.containerd.grpc.v1.cri"] + [plugins."io.containerd.grpc.v1.cri".cni] + on the containerd 'config.toml' file on the Windows nodes. + type: string + cniLogDir: + description: + CNILogDir is the path to the Calico CNI logs directory + on Windows. + type: string + vxlanAdapter: + description: + VXLANAdapter is the Network Adapter used for VXLAN, + leave blank for primary NIC + type: string + vxlanMACPrefix: + description: + VXLANMACPrefix is the prefix used when generating + MAC addresses for virtual NICs + pattern: ^[0-9A-Fa-f]{2}-[0-9A-Fa-f]{2}$ + type: string + type: object + type: object + status: + description: + Most recently observed state for the Calico or Calico Enterprise + installation. + properties: + calicoVersion: + description: |- + CalicoVersion shows the current running version of calico. + CalicoVersion along with Variant is needed to know the exact + version deployed. + type: string + computed: + description: + Computed is the final installation including overlaid + resources. + properties: + azure: + description: + Azure is used to configure azure provider specific + options. + properties: + policyMode: + default: Default + description: |- + PolicyMode determines whether the "control-plane" label is applied to namespaces. It offers two options: Default and Manual. + The Default option adds the "control-plane" label to the required namespaces. + The Manual option does not apply the "control-plane" label to any namespace. + Default: Default + enum: + - Default + - Manual + type: string + type: object + calicoKubeControllersDeployment: + description: |- + CalicoKubeControllersDeployment configures the calico-kube-controllers Deployment. If used in + conjunction with the deprecated ComponentResources, then these overrides take precedence. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the calico-kube-controllers + Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-kube-controllers Deployment. + If omitted, the calico-kube-controllers Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-kube-controllers + Deployment pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the calico-kube-controllers Deployment's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-kube-controllers pods. + If specified, this overrides any affinity that may be set on the calico-kube-controllers Deployment. + If omitted, the calico-kube-controllers Deployment will use its default value for affinity. + WARNING: Please note that this field will override the default calico-kube-controllers Deployment affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-kube-controllers containers. + If specified, this overrides the specified calico-kube-controllers Deployment containers. + If omitted, the calico-kube-controllers Deployment will use its default values for its containers. + items: + description: + CalicoKubeControllersDeploymentContainer + is a calico-kube-controllers Deployment container. + properties: + name: + description: |- + Name is an enum which identifies the calico-kube-controllers Deployment container by name. + Supported values are: calico-kube-controllers, es-calico-kube-controllers + enum: + - calico-kube-controllers + - es-calico-kube-controllers + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-kube-controllers Deployment container's resources. + If omitted, the calico-kube-controllers Deployment will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-kube-controllers pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-kube-controllers Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If used in conjunction with ControlPlaneNodeSelector, that nodeSelector is set on the calico-kube-controllers Deployment + and each of this field's key/value pairs are added to the calico-kube-controllers Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-kube-controllers Deployment will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-kube-controllers Deployment nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-kube-controllers pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-kube-controllers Deployment. + If omitted, the calico-kube-controllers Deployment will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-kube-controllers Deployment tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + calicoNetwork: + description: + CalicoNetwork specifies networking configuration + options for Calico. + properties: + bgp: + description: + BGP configures whether or not to enable Calico's + BGP capabilities. + enum: + - Enabled + - Disabled + type: string + bpfNetworkBootstrap: + description: |- + BPFNetworkBootstrap manages the initial networking setup required to configure the BPF dataplane. + When enabled, the operator tries to bootstraps access to the Kubernetes API Server + by using the Kubernetes service and its associated endpoints. + This field should be enabled only if linuxDataplane is set to "BPF". + If another dataplane is selected, this field must be omitted or explicitly set to Disabled. + When disabled and linuxDataplane is BPF, you must manually provide the Kubernetes API Server + information via the "kubernetes-service-endpoint" ConfigMap. It is invalid to use both the ConfigMap + and have this field set to true at the same time. + Default: Disabled + enum: + - Disabled + - Enabled + type: string + containerIPForwarding: + description: |- + ContainerIPForwarding configures whether ip forwarding will be enabled for containers in the CNI configuration. + Default: Disabled + enum: + - Enabled + - Disabled + type: string + hostPorts: + description: |- + HostPorts configures whether or not Calico will support Kubernetes HostPorts. Valid only when using the Calico CNI plugin. + Default: Enabled + enum: + - Enabled + - Disabled + type: string + ipPools: + description: |- + IPPools contains a list of IP pools to manage. If nil, a single IPv4 IP pool + will be created by the operator. If an empty list is provided, the operator will not create any IP pools and will instead + wait for IP pools to be created out-of-band. + IP pools in this list will be reconciled by the operator and should not be modified out-of-band. + items: + properties: + allowedUses: + description: |- + AllowedUse controls what the IP pool will be used for. If not specified or empty, defaults to + ["Tunnel", "Workload"] for back-compatibility + items: + type: string + type: array + assignmentMode: + description: + AssignmentMode determines if IP addresses + from this pool should be assigned automatically or + on request only + type: string + blockSize: + description: |- + BlockSize specifies the CIDR prefex length to use when allocating per-node IP blocks from + the main IP pool CIDR. + Default: 26 (IPv4), 122 (IPv6) + format: int32 + type: integer + cidr: + description: + CIDR contains the address range for the + IP Pool in classless inter-domain routing format. + type: string + disableBGPExport: + default: false + description: |- + DisableBGPExport specifies whether routes from this IP pool's CIDR are exported over BGP. + Default: false + type: boolean + disableNewAllocations: + description: |- + DisableNewAllocations specifies whether or not new IP allocations are allowed from this pool. + This is useful when you want to prevent new pods from receiving IP addresses from this pool, without + impacting any existing pods that have already been assigned addresses from this pool. + type: boolean + encapsulation: + description: |- + Encapsulation specifies the encapsulation type that will be used with + the IP Pool. + Default: IPIP + enum: + - IPIPCrossSubnet + - IPIP + - VXLAN + - VXLANCrossSubnet + - None + type: string + name: + description: + Name is the name of the IP pool. If omitted, + this will be generated. + type: string + natOutgoing: + description: |- + NATOutgoing specifies if NAT will be enabled or disabled for outgoing traffic. + Default: Enabled + enum: + - Enabled + - Disabled + type: string + nodeSelector: + description: |- + NodeSelector specifies the node selector that will be set for the IP Pool. + Default: 'all()' + type: string + required: + - cidr + type: object + maxItems: 25 + type: array + kubeProxyManagement: + description: |- + KubeProxyManagement controls whether the operator manages the kube-proxy DaemonSet. + When enabled, the operator will manage the DaemonSet by patching it: + it disables kube-proxy if the dataplane is BPF, or enables it otherwise. + Default: Disabled + enum: + - Disabled + - Enabled + type: string + linuxDataplane: + description: |- + LinuxDataplane is used to select the dataplane used for Linux nodes. In particular, it + causes the operator to add required mounts and environment variables for the particular dataplane. + If not specified, iptables mode is used. + Default: Iptables + enum: + - Iptables + - BPF + - VPP + - Nftables + type: string + linuxPolicySetupTimeoutSeconds: + description: |- + LinuxPolicySetupTimeoutSeconds delays new pods from running containers + until their policy has been programmed in the dataplane. + The specified delay defines the maximum amount of time + that the Calico CNI plugin will wait for policy to be programmed. + Only applies to pods created on Linux nodes. + * A value of 0 disables pod startup delays. + Default: 0 + format: int32 + type: integer + mtu: + description: |- + MTU specifies the maximum transmission unit to use on the pod network. + If not specified, Calico will perform MTU auto-detection based on the cluster network. + format: int32 + type: integer + multiInterfaceMode: + description: |- + MultiInterfaceMode configures what will configure multiple interface per pod. Only valid for Calico Enterprise installations + using the Calico CNI plugin. + Default: None + enum: + - None + - Multus + type: string + nodeAddressAutodetectionV4: + description: |- + NodeAddressAutodetectionV4 specifies an approach to automatically detect node IPv4 addresses. If not specified, + will use default auto-detection settings to acquire an IPv4 address for each node. + properties: + canReach: + description: |- + CanReach enables IP auto-detection based on which source address on the node is used to reach the + specified IP or domain. + type: string + cidrs: + description: |- + CIDRS enables IP auto-detection based on which addresses on the nodes are within + one of the provided CIDRs. + items: + type: string + type: array + firstFound: + description: |- + FirstFound uses default interface matching parameters to select an interface, performing best-effort + filtering based on well-known interface names. + type: boolean + interface: + description: + Interface enables IP auto-detection based + on interfaces that match the given regex. + type: string + kubernetes: + description: + Kubernetes configures Calico to detect node + addresses based on the Kubernetes API. + enum: + - NodeInternalIP + type: string + skipInterface: + description: |- + SkipInterface enables IP auto-detection based on interfaces that do not match + the given regex. + type: string + type: object + nodeAddressAutodetectionV6: + description: |- + NodeAddressAutodetectionV6 specifies an approach to automatically detect node IPv6 addresses. If not specified, + IPv6 addresses will not be auto-detected. + properties: + canReach: + description: |- + CanReach enables IP auto-detection based on which source address on the node is used to reach the + specified IP or domain. + type: string + cidrs: + description: |- + CIDRS enables IP auto-detection based on which addresses on the nodes are within + one of the provided CIDRs. + items: + type: string + type: array + firstFound: + description: |- + FirstFound uses default interface matching parameters to select an interface, performing best-effort + filtering based on well-known interface names. + type: boolean + interface: + description: + Interface enables IP auto-detection based + on interfaces that match the given regex. + type: string + kubernetes: + description: + Kubernetes configures Calico to detect node + addresses based on the Kubernetes API. + enum: + - NodeInternalIP + type: string + skipInterface: + description: |- + SkipInterface enables IP auto-detection based on interfaces that do not match + the given regex. + type: string + type: object + sysctl: + description: + Sysctl configures sysctl parameters for tuning + plugin + items: + properties: + key: + enum: + - net.ipv4.tcp_keepalive_intvl + - net.ipv4.tcp_keepalive_probes + - net.ipv4.tcp_keepalive_time + type: string + value: + type: string + required: + - key + - value + type: object + type: array + windowsDataplane: + description: |- + WindowsDataplane is used to select the dataplane used for Windows nodes. In particular, it + causes the operator to add required mounts and environment variables for the particular dataplane. + If not specified, it is disabled and the operator will not render the Calico Windows nodes daemonset. + Default: Disabled + enum: + - HNS + - Disabled + type: string + type: object + calicoNodeDaemonSet: + description: |- + CalicoNodeDaemonSet configures the calico-node DaemonSet. If used in + conjunction with the deprecated ComponentResources, then these overrides take precedence. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the DaemonSet. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the calico-node + DaemonSet. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-node DaemonSet. + If omitted, the calico-node DaemonSet will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-node DaemonSet + pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the calico-node DaemonSet's PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-node pods. + If specified, this overrides any affinity that may be set on the calico-node DaemonSet. + If omitted, the calico-node DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default calico-node DaemonSet affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-node containers. + If specified, this overrides the specified calico-node DaemonSet containers. + If omitted, the calico-node DaemonSet will use its default values for its containers. + items: + description: + CalicoNodeDaemonSetContainer is + a calico-node DaemonSet container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node DaemonSet container by name. + Supported values are: calico-node + enum: + - calico-node + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-node DaemonSet container's resources. + If omitted, the calico-node DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + dnsConfig: + description: + DNSConfig allows customization of + the DNS configuration for the calico-node pods. + properties: + nameservers: + description: |- + A list of DNS name server IP addresses. + This will be appended to the base nameservers generated from DNSPolicy. + Duplicated nameservers will be removed. + items: + type: string + type: array + x-kubernetes-list-type: atomic + options: + description: |- + A list of DNS resolver options. + This will be merged with the base options generated from DNSPolicy. + Duplicated entries will be removed. Resolution options given in Options + will override those that appear in the base DNSPolicy. + items: + description: + PodDNSConfigOption defines + DNS resolver options of a pod. + properties: + name: + description: |- + Name is this DNS resolver option's name. + Required. + type: string + value: + description: + Value is this DNS resolver + option's value. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + searches: + description: |- + A list of DNS search domains for host-name lookup. + This will be appended to the base search paths generated from DNSPolicy. + Duplicated search paths will be removed. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + dnsPolicy: + description: + DNSPolicy is the DNS policy for the + calico-node pods. + enum: + - "" + - Default + - ClusterFirst + - ClusterFirstWithHostNet + - None + type: string + initContainers: + description: |- + InitContainers is a list of calico-node init containers. + If specified, this overrides the specified calico-node DaemonSet init containers. + If omitted, the calico-node DaemonSet will use its default values for its init containers. + items: + description: + CalicoNodeDaemonSetInitContainer + is a calico-node DaemonSet init container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node DaemonSet init container by name. + Supported values are: install-cni, hostpath-init, flexvol-driver, ebpf-bootstrap, node-certs-key-cert-provisioner, calico-node-prometheus-server-tls-key-cert-provisioner, mount-bpffs (deprecated, replaced by ebpf-bootstrap) + enum: + - install-cni + - hostpath-init + - flexvol-driver + - ebpf-bootstrap + - node-certs-key-cert-provisioner + - calico-node-prometheus-server-tls-key-cert-provisioner + - mount-bpffs + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-node DaemonSet init container's resources. + If omitted, the calico-node DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-node pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-node DaemonSet nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-node DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-node DaemonSet nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-node pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-node DaemonSet. + If omitted, the calico-node DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-node DaemonSet tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + calicoNodeWindowsDaemonSet: + description: + CalicoNodeWindowsDaemonSet configures the calico-node-windows + DaemonSet. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the DaemonSet. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the calico-node-windows + DaemonSet. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-node-windows DaemonSet. + If omitted, the calico-node-windows DaemonSet will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-node-windows + DaemonSet pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the calico-node-windows DaemonSet's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-node-windows pods. + If specified, this overrides any affinity that may be set on the calico-node-windows DaemonSet. + If omitted, the calico-node-windows DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default calico-node-windows DaemonSet affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-node-windows containers. + If specified, this overrides the specified calico-node-windows DaemonSet containers. + If omitted, the calico-node-windows DaemonSet will use its default values for its containers. + items: + description: + CalicoNodeWindowsDaemonSetContainer + is a calico-node-windows DaemonSet container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node-windows DaemonSet container by name. + Supported values are: node, felix, confd + calico-node-windows is allowed because it was previously allowed. + enum: + - calico-node-windows + - node + - felix + - confd + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named DaemonSet container's resources. + If omitted, the DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + initContainers: + description: |- + InitContainers is a list of calico-node-windows init containers. + If specified, this overrides the specified calico-node-windows DaemonSet init containers. + If omitted, the calico-node-windows DaemonSet will use its default values for its init containers. + items: + description: + CalicoNodeWindowsDaemonSetInitContainer + is a calico-node-windows DaemonSet init container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node-windows DaemonSet init container by name. + Supported values are: install-cni;hostpath-init, flexvol-driver, node-certs-key-cert-provisioner, calico-node-windows-prometheus-server-tls-key-cert-provisioner + enum: + - install-cni + - hostpath-init + - flexvol-driver + - node-certs-key-cert-provisioner + - calico-node-windows-prometheus-server-tls-key-cert-provisioner + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-node-windows DaemonSet init container's resources. + If omitted, the calico-node-windows DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-node-windows pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-node-windows DaemonSet nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-node-windows DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-node-windows DaemonSet nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-node-windows pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-node-windows DaemonSet. + If omitted, the calico-node-windows DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-node-windows DaemonSet tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + calicoWindowsUpgradeDaemonSet: + description: |- + Deprecated. The CalicoWindowsUpgradeDaemonSet is deprecated and will be removed from the API in the future. + CalicoWindowsUpgradeDaemonSet configures the calico-windows-upgrade DaemonSet. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the calico-windows-upgrade + DaemonSet. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-windows-upgrade DaemonSet. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-windows-upgrade + DaemonSet pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the calico-windows-upgrade DaemonSet's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-windows-upgrade pods. + If specified, this overrides any affinity that may be set on the calico-windows-upgrade DaemonSet. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default calico-windows-upgrade DaemonSet affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-windows-upgrade containers. + If specified, this overrides the specified calico-windows-upgrade DaemonSet containers. + If omitted, the calico-windows-upgrade DaemonSet will use its default values for its containers. + items: + description: + CalicoWindowsUpgradeDaemonSetContainer + is a calico-windows-upgrade DaemonSet container. + properties: + name: + description: + Name is an enum which identifies + the calico-windows-upgrade DaemonSet container + by name. + enum: + - calico-windows-upgrade + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-windows-upgrade DaemonSet container's resources. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for this container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-windows-upgrade pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-windows-upgrade DaemonSet nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-windows-upgrade DaemonSet nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-windows-upgrade pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-windows-upgrade DaemonSet. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-windows-upgrade DaemonSet tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + certificateManagement: + description: |- + CertificateManagement configures pods to submit a CertificateSigningRequest to the certificates.k8s.io/v1 API in order + to obtain TLS certificates. This feature requires that you bring your own CSR signing and approval process, otherwise + pods will be stuck during initialization. + properties: + caCert: + description: + Certificate of the authority that signs the CertificateSigningRequests + in PEM format. + format: byte + type: string + keyAlgorithm: + description: |- + Specify the algorithm used by pods to generate a key pair that is associated with the X.509 certificate request. + Default: RSAWithSize2048 + enum: + - "" + - RSAWithSize2048 + - RSAWithSize4096 + - RSAWithSize8192 + - ECDSAWithCurve256 + - ECDSAWithCurve384 + - ECDSAWithCurve521 + type: string + signatureAlgorithm: + description: |- + Specify the algorithm used for the signature of the X.509 certificate request. + Default: SHA256WithRSA + enum: + - "" + - SHA256WithRSA + - SHA384WithRSA + - SHA512WithRSA + - ECDSAWithSHA256 + - ECDSAWithSHA384 + - ECDSAWithSHA512 + type: string + signerName: + description: |- + When a CSR is issued to the certificates.k8s.io API, the signerName is added to the request in order to accommodate for clusters + with multiple signers. + Must be formatted as: `/`. + type: string + required: + - caCert + - signerName + type: object + cni: + description: CNI specifies the CNI that will be used by this installation. + properties: + binDir: + description: |- + BinDir is the path to the CNI binaries directory. + If you have changed the installation directory for CNI binaries in the container runtime configuration, + please ensure that this field points to the same directory as specified in the container runtime settings. + Default directory depends on the KubernetesProvider. + * For KubernetesProvider GKE, this field defaults to "/home/kubernetes/bin". + * For KubernetesProvider OpenShift, this field defaults to "/var/lib/cni/bin". + * Otherwise, this field defaults to "/opt/cni/bin". + type: string + confDir: + description: |- + ConfDir is the path to the CNI config directory. + If you have changed the installation directory for CNI configuration in the container runtime configuration, + please ensure that this field points to the same directory as specified in the container runtime settings. + Default directory depends on the KubernetesProvider. + * For KubernetesProvider GKE, this field defaults to "/etc/cni/net.d". + * For KubernetesProvider OpenShift, this field defaults to "/var/run/multus/cni/net.d". + * Otherwise, this field defaults to "/etc/cni/net.d". + type: string + ipam: + description: |- + IPAM specifies the pod IP address management that will be used in the Calico or + Calico Enterprise installation. + properties: + type: + description: |- + Specifies the IPAM plugin that will be used in the Calico or Calico Enterprise installation. + * For CNI Plugin Calico, this field defaults to Calico. + * For CNI Plugin GKE, this field defaults to HostLocal. + * For CNI Plugin AzureVNET, this field defaults to AzureVNET. + * For CNI Plugin AmazonVPC, this field defaults to AmazonVPC. + The IPAM plugin is installed and configured only if the CNI plugin is set to Calico, + for all other values of the CNI plugin the plugin binaries and CNI config is a dependency + that is expected to be installed separately. + Default: Calico + enum: + - Calico + - HostLocal + - AmazonVPC + - AzureVNET + type: string + required: + - type + type: object + type: + description: |- + Specifies the CNI plugin that will be used in the Calico or Calico Enterprise installation. + * For KubernetesProvider GKE, this field defaults to GKE. + * For KubernetesProvider AKS, this field defaults to AzureVNET. + * For KubernetesProvider EKS, this field defaults to AmazonVPC. + * If aws-node daemonset exists in kube-system when the Installation resource is created, this field defaults to AmazonVPC. + * For all other cases this field defaults to Calico. + For the value Calico, the CNI plugin binaries and CNI config will be installed as part of deployment, + for all other values the CNI plugin binaries and CNI config is a dependency that is expected + to be installed separately. + Default: Calico + enum: + - Calico + - GKE + - AmazonVPC + - AzureVNET + type: string + required: + - type + type: object + componentResources: + description: |- + Deprecated. Please use CalicoNodeDaemonSet, TyphaDeployment, and KubeControllersDeployment. + ComponentResources can be used to customize the resource requirements for each component. + Node, Typha, and KubeControllers are supported for installations. + items: + description: |- + Deprecated. Please use component resource config fields in Installation.Spec instead. + The ComponentResource struct associates a ResourceRequirements with a component by name + properties: + componentName: + description: + ComponentName is an enum which identifies the + component + enum: + - Node + - Typha + - KubeControllers + - NodeWindows + - FelixWindows + - ConfdWindows + type: string + resourceRequirements: + description: + ResourceRequirements allows customization of + limits and requests for compute resources such as cpu + and memory. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references one entry in + PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - componentName + - resourceRequirements + type: object + type: array + controlPlaneNodeSelector: + additionalProperties: + type: string + description: |- + ControlPlaneNodeSelector is used to select control plane nodes on which to run Calico + components. This is globally applied to all resources created by the operator excluding daemonsets. + type: object + controlPlaneReplicas: + description: |- + ControlPlaneReplicas defines how many replicas of the control plane core components will be deployed. + This field applies to all control plane components that support High Availability. Defaults to 2. + format: int32 + type: integer + controlPlaneTolerations: + description: |- + ControlPlaneTolerations specify tolerations which are then globally applied to all resources + created by the operator. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + csiNodeDriverDaemonSet: + description: + CSINodeDriverDaemonSet configures the csi-node-driver + DaemonSet. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the DaemonSet. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the csi-node-driver + DaemonSet. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the csi-node-driver DaemonSet. + If omitted, the csi-node-driver DaemonSet will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the csi-node-driver DaemonSet + pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the csi-node-driver DaemonSet's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the csi-node-driver pods. + If specified, this overrides any affinity that may be set on the csi-node-driver DaemonSet. + If omitted, the csi-node-driver DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default csi-node-driver DaemonSet affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of csi-node-driver containers. + If specified, this overrides the specified csi-node-driver DaemonSet containers. + If omitted, the csi-node-driver DaemonSet will use its default values for its containers. + items: + description: + CSINodeDriverDaemonSetContainer + is a csi-node-driver DaemonSet container. + properties: + name: + description: |- + Name is an enum which identifies the csi-node-driver DaemonSet container by name. + Supported values are: calico-csi, csi-node-driver-registrar. + enum: + - calico-csi + - csi-node-driver-registrar + - csi-node-driver + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named csi-node-driver DaemonSet container's resources. + If omitted, the csi-node-driver DaemonSet will use its default value for this container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the csi-node-driver pod's scheduling constraints. + If specified, each of the key/value pairs are added to the csi-node-driver DaemonSet nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the csi-node-driver DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default csi-node-driver DaemonSet nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the csi-node-driver pod's tolerations. + If specified, this overrides any tolerations that may be set on the csi-node-driver DaemonSet. + If omitted, the csi-node-driver DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default csi-node-driver DaemonSet tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + fipsMode: + description: |- + FIPSMode uses images and features only that are using FIPS 140-2 validated cryptographic modules and standards. + Only supported for Variant=Calico. + Default: Disabled + enum: + - Enabled + - Disabled + type: string + flexVolumePath: + description: |- + FlexVolumePath optionally specifies a custom path for FlexVolume. If not specified, FlexVolume will be + enabled by default. If set to 'None', FlexVolume will be disabled. The default is based on the + kubernetesProvider. + type: string + imagePath: + description: |- + ImagePath allows for the path part of an image to be specified. If specified + then the specified value will be used as the image path for each image. If not specified + or empty, the default for each image will be used. + A special case value, UseDefault, is supported to explicitly specify the default + image path will be used for each image. + Image format: + `/:` + This option allows configuring the `` portion of the above format. + type: string + imagePrefix: + description: |- + ImagePrefix allows for the prefix part of an image to be specified. If specified + then the given value will be used as a prefix on each image. If not specified + or empty, no prefix will be used. + A special case value, UseDefault, is supported to explicitly specify the default + image prefix will be used for each image. + Image format: + `/:` + This option allows configuring the `` portion of the above format. + type: string + imagePullSecrets: + description: |- + ImagePullSecrets is an array of references to container registry pull secrets to use. These are + applied to all images to be pulled. + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + kubeletVolumePluginPath: + description: |- + KubeletVolumePluginPath optionally specifies enablement of Calico CSI plugin. If not specified, + CSI will be enabled by default. If set to 'None', CSI will be disabled. + Default: /var/lib/kubelet + type: string + kubernetesProvider: + description: |- + KubernetesProvider specifies a particular provider of the Kubernetes platform and enables provider-specific configuration. + If the specified value is empty, the Operator will attempt to automatically determine the current provider. + If the specified value is not empty, the Operator will still attempt auto-detection, but + will additionally compare the auto-detected value to the specified value to confirm they match. + enum: + - "" + - EKS + - GKE + - AKS + - OpenShift + - DockerEnterprise + - RKE2 + - TKG + - Kind + type: string + logging: + description: Logging Configuration for Components + properties: + cni: + description: + Customized logging specification for calico-cni + plugin + properties: + logFileMaxAgeDays: + description: "Default: 30 (days)" + format: int32 + type: integer + logFileMaxCount: + description: "Default: 10" + format: int32 + type: integer + logFileMaxSize: + anyOf: + - type: integer + - type: string + description: "Default: 100Mi" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + logSeverity: + description: "Default: Info" + enum: + - Error + - Warning + - Info + - Debug + type: string + type: object + type: object + nodeMetricsPort: + description: |- + NodeMetricsPort specifies which port calico/node serves prometheus metrics on. By default, metrics are not enabled. + If specified, this overrides any FelixConfiguration resources which may exist. If omitted, then + prometheus metrics may still be configured through FelixConfiguration. + format: int32 + type: integer + nodeUpdateStrategy: + description: |- + NodeUpdateStrategy can be used to customize the desired update strategy, such as the MaxUnavailable + field. + properties: + rollingUpdate: + description: + Rolling update config params. Present only if + type = "RollingUpdate". + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of nodes with an existing available DaemonSet pod that + can have an updated DaemonSet pod during during an update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up to a minimum of 1. + Default value is 0. + Example: when this is set to 30%, at most 30% of the total number of nodes + that should be running the daemon pod (i.e. status.desiredNumberScheduled) + can have their a new pod created before the old pod is marked as deleted. + The update starts by launching new pods on 30% of nodes. Once an updated + pod is available (Ready for at least minReadySeconds) the old DaemonSet pod + on that node is marked deleted. If the old pod becomes unavailable for any + reason (Ready transitions to false, is evicted, or is drained) an updated + pod is immediately created on that node without considering surge limits. + Allowing surge implies the possibility that the resources consumed by the + daemonset on any given node can double if the readiness check fails, and + so resource intensive daemonsets should take into account that they may + cause evictions during disruption. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of DaemonSet pods that can be unavailable during the + update. Value can be an absolute number (ex: 5) or a percentage of total + number of DaemonSet pods at the start of the update (ex: 10%). Absolute + number is calculated from percentage by rounding up. + This cannot be 0 if MaxSurge is 0 + Default value is 1. + Example: when this is set to 30%, at most 30% of the total number of nodes + that should be running the daemon pod (i.e. status.desiredNumberScheduled) + can have their pods stopped for an update at any given time. The update + starts by stopping at most 30% of those DaemonSet pods and then brings + up new DaemonSet pods in their place. Once the new pods are available, + it then proceeds onto other DaemonSet pods, thus ensuring that at least + 70% of original number of DaemonSet pods are available at all times during + the update. + x-kubernetes-int-or-string: true + type: object + type: + description: + Type of daemon set update. Can be "RollingUpdate" + or "OnDelete". Default is RollingUpdate. + type: string + type: object + nonPrivileged: + description: |- + Deprecated. NonPrivileged is deprecated and will be removed from the API in a future release. + Enabling this field is not supported and will cause errors. + NonPrivileged configures Calico to be run in non-privileged containers as non-root users where possible. + type: string + proxy: + description: |- + Proxy is used to configure the HTTP(S) proxy settings that will be applied to Tigera containers that connect + to destinations outside the cluster. It is expected that NO_PROXY is configured such that destinations within + the cluster (including the API server) are exempt from proxying. + properties: + httpProxy: + description: |- + HTTPProxy defines the value of the HTTP_PROXY environment variable that will be set on Tigera containers that connect to + destinations outside the cluster. + type: string + httpsProxy: + description: |- + HTTPSProxy defines the value of the HTTPS_PROXY environment variable that will be set on Tigera containers that connect to + destinations outside the cluster. + type: string + noProxy: + description: |- + NoProxy defines the value of the NO_PROXY environment variable that will be set on Tigera containers that connect to + destinations outside the cluster. This value must be set such that destinations within the scope of the cluster, including + the Kubernetes API server, are exempt from being proxied. + type: string + type: object + registry: + description: |- + Registry is the default Docker registry used for component Docker images. + If specified then the given value must end with a slash character (`/`) and all images will be pulled from this registry. + If not specified then the default registries will be used. A special case value, UseDefault, is + supported to explicitly specify the default registries will be used. + Image format: + `/:` + This option allows configuring the `` portion of the above format. + type: string + serviceCIDRs: + description: + Kubernetes Service CIDRs. Specifying this is required + when using Calico for Windows. + items: + type: string + type: array + tlsCipherSuites: + description: + TLSCipherSuites defines the cipher suite list that + the TLS protocol should use during secure communication. + items: + properties: + name: + description: This should be a valid TLS cipher suite name. + enum: + - TLS_AES_256_GCM_SHA384 + - TLS_CHACHA20_POLY1305_SHA256 + - TLS_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 + - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + - TLS_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA + - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA + - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + type: string + type: object + type: array + typhaAffinity: + description: |- + Deprecated. Please use Installation.Spec.TyphaDeployment instead. + TyphaAffinity allows configuration of node affinity characteristics for Typha pods. + properties: + nodeAffinity: + description: + NodeAffinity describes node affinity scheduling + rules for typha. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + WARNING: Please note that if the affinity requirements specified by this field are not met at + scheduling time, the pod will NOT be scheduled onto the node. + There is no fallback to another affinity rules with this setting. + This may cause networking disruption or even catastrophic failure! + PreferredDuringSchedulingIgnoredDuringExecution should be used for affinity + unless there is a specific well understood reason to use RequiredDuringSchedulingIgnoredDuringExecution and + you can guarantee that the RequiredDuringSchedulingIgnoredDuringExecution will always have sufficient nodes to satisfy the requirement. + NOTE: RequiredDuringSchedulingIgnoredDuringExecution is set by default for AKS nodes, + to avoid scheduling Typhas on virtual-nodes. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + type: object + typhaDeployment: + description: |- + TyphaDeployment configures the typha Deployment. If used in conjunction with the deprecated + ComponentResources or TyphaAffinity, then these overrides take precedence. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the specification of the typha Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the typha Deployment. + If omitted, the typha Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + strategy: + description: + The deployment strategy to use to replace + existing pods with new ones. + properties: + rollingUpdate: + description: |- + Rolling update config params. Present only if DeploymentStrategyType = + RollingUpdate. + to be. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: object + template: + description: + Template describes the typha Deployment pod + that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the typha Deployment's PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the typha pods. + If specified, this overrides any affinity that may be set on the typha Deployment. + If omitted, the typha Deployment will use its default value for affinity. + If used in conjunction with the deprecated TyphaAffinity, then this value takes precedence. + WARNING: Please note that this field will override the default calico-typha Deployment affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of typha containers. + If specified, this overrides the specified typha Deployment containers. + If omitted, the typha Deployment will use its default values for its containers. + items: + description: + TyphaDeploymentContainer is a typha + Deployment container. + properties: + name: + description: |- + Name is an enum which identifies the typha Deployment container by name. + Supported values are: calico-typha + enum: + - calico-typha + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named typha Deployment container's resources. + If omitted, the typha Deployment will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + initContainers: + description: |- + InitContainers is a list of typha init containers. + If specified, this overrides the specified typha Deployment init containers. + If omitted, the typha Deployment will use its default values for its init containers. + items: + description: + TyphaDeploymentInitContainer is + a typha Deployment init container. + properties: + name: + description: |- + Name is an enum which identifies the typha Deployment init container by name. + Supported values are: typha-certs-key-cert-provisioner + enum: + - typha-certs-key-cert-provisioner + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named typha Deployment init container's resources. + If omitted, the typha Deployment will use its default value for this init container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-typha pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-typha Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-typha Deployment will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-typha Deployment nodeSelector. + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + If this value is nil, the default grace period will be used instead. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + Defaults to 30 seconds. + format: int64 + type: integer + tolerations: + description: |- + Tolerations is the typha pod's tolerations. + If specified, this overrides any tolerations that may be set on the typha Deployment. + If omitted, the typha Deployment will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-typha Deployment tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given + topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + type: object + type: object + type: object + typhaMetricsPort: + description: + TyphaMetricsPort specifies which port calico/typha + serves prometheus metrics on. By default, metrics are not enabled. + format: int32 + type: integer + variant: + description: |- + Variant is the product to install - one of Calico or TigeraSecureEnterprise + Default: Calico + enum: + - Calico + - TigeraSecureEnterprise + type: string + windowsNodes: + description: Windows Configuration + properties: + cniBinDir: + description: |- + CNIBinDir is the path to the CNI binaries directory on Windows, it must match what is used as 'bin_dir' under + [plugins] + [plugins."io.containerd.grpc.v1.cri"] + [plugins."io.containerd.grpc.v1.cri".cni] + on the containerd 'config.toml' file on the Windows nodes. + type: string + cniConfigDir: + description: |- + CNIConfigDir is the path to the CNI configuration directory on Windows, it must match what is used as 'conf_dir' under + [plugins] + [plugins."io.containerd.grpc.v1.cri"] + [plugins."io.containerd.grpc.v1.cri".cni] + on the containerd 'config.toml' file on the Windows nodes. + type: string + cniLogDir: + description: + CNILogDir is the path to the Calico CNI logs + directory on Windows. + type: string + vxlanAdapter: + description: + VXLANAdapter is the Network Adapter used for + VXLAN, leave blank for primary NIC + type: string + vxlanMACPrefix: + description: + VXLANMACPrefix is the prefix used when generating + MAC addresses for virtual NICs + pattern: ^[0-9A-Fa-f]{2}-[0-9A-Fa-f]{2}$ + type: string + type: object + type: object + conditions: + description: |- + Conditions represents the latest observed set of conditions for the component. A component may be one or more of + Ready, Progressing, Degraded or other customer types. + items: + description: + Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + imageSet: + description: |- + ImageSet is the name of the ImageSet being used, if there is an ImageSet + that is being used. If an ImageSet is not being used then this will not be set. + type: string + mtu: + description: |- + MTU is the most recently observed value for pod network MTU. This may be an explicitly + configured value, or based on Calico's native auto-detetion. + format: int32 + type: integer + variant: + description: + Variant is the most recently observed installed variant + - one of Calico or TigeraSecureEnterprise + enum: + - Calico + - TigeraSecureEnterprise + type: string + type: object + type: object + x-kubernetes-validations: + - message: resource name must be 'default', 'tigera-secure', or 'overlay' + rule: + self.metadata.name == 'default' || self.metadata.name == 'tigera-secure' + || self.metadata.name == 'overlay' + served: true + storage: true + subresources: + status: {} diff --git a/charts/projectcalico.org.v3/templates/operator.tigera.io_istios.yaml b/charts/projectcalico.org.v3/templates/operator.tigera.io_istios.yaml new file mode 100644 index 00000000000..d9313ad8335 --- /dev/null +++ b/charts/projectcalico.org.v3/templates/operator.tigera.io_istios.yaml @@ -0,0 +1,3441 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: istios.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: Istio + listKind: IstioList + plural: istios + singular: istio + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: Istio is the Schema for the istios API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: IstioSpec defines the desired state of Istio + properties: + dscpMark: + description: |- + DSCPMark define the value of the DSCP mark done by Felix and recognised by Istio CNI for Transparent + NetworkPolicies. + pattern: ^.* + type: integer + x-kubernetes-int-or-string: true + istioCNI: + description: + IstioCNIDaemonset defines the resource requirements for + the Istio CNI plugin. + properties: + spec: + description: + Spec allows users to specify custom fields for the + Istio CNI Daemonset. + properties: + template: + description: + Template allows users to specify custom fields + for the Istio CNI Daemonset. + properties: + spec: + description: + Spec allows users to specify custom fields + for the Istio CNI Daemonset. + properties: + affinity: + description: + Affinity specifies the affinity for the + deployment. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + nodeSelector: + additionalProperties: + type: string + description: + NodeSelector specifies the node affinity + for the deployment. + type: object + resources: + description: + Resources specifies the compute resources + required for the deployment. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references one entry + in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + tolerations: + description: + Tolerations specifies the tolerations + for the deployment. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + istiod: + description: + IstiodDeployment defines the resource requirements and + node selector for the Istio deployment. + properties: + spec: + description: + Spec allows users to specify custom fields for the + Istiod Deployment. + properties: + template: + description: + Template allows users to specify custom fields + for the Istiod Deployment. + properties: + spec: + description: + Spec allows users to specify custom fields + for the Istiod Deployment. + properties: + affinity: + description: + Affinity specifies the affinity for the + deployment. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + nodeSelector: + additionalProperties: + type: string + description: + NodeSelector specifies the node affinity + for the deployment. + type: object + resources: + description: + Resources specifies the compute resources + required for the deployment. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references one entry + in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + tolerations: + description: + Tolerations specifies the tolerations + for the deployment. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + ztunnel: + description: + ZTunnelDaemonset defines the resource requirements for + the ZTunnelDaemonset component. + properties: + spec: + description: + Spec allows users to specify custom fields for the + ZTunnel Daemonset. + properties: + template: + description: + Template allows users to specify custom fields + for the ZTunnel Daemonset. + properties: + spec: + description: + Spec allows users to specify custom fields + for the ZTunnel Daemonset. + properties: + affinity: + description: + Affinity specifies the affinity for the + deployment. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + nodeSelector: + additionalProperties: + type: string + description: + NodeSelector specifies the node affinity + for the deployment. + type: object + resources: + description: + Resources specifies the compute resources + required for the deployment. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references one entry + in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + tolerations: + description: + Tolerations specifies the tolerations + for the deployment. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + type: object + status: + description: IstioStatus defines the observed state of Istio + properties: + conditions: + description: |- + Conditions represents the latest observed set of conditions for the component. A component may be one or more of + Ready, Progressing, Degraded or other customer types. + items: + description: + Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + x-kubernetes-validations: + - message: resource name must be 'default' + rule: self.metadata.name == 'default' + served: true + storage: true + subresources: + status: {} diff --git a/charts/projectcalico.org.v3/templates/operator.tigera.io_managementclusterconnections.yaml b/charts/projectcalico.org.v3/templates/operator.tigera.io_managementclusterconnections.yaml new file mode 100644 index 00000000000..65899a8adb5 --- /dev/null +++ b/charts/projectcalico.org.v3/templates/operator.tigera.io_managementclusterconnections.yaml @@ -0,0 +1,356 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: managementclusterconnections.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: ManagementClusterConnection + listKind: ManagementClusterConnectionList + plural: managementclusterconnections + singular: managementclusterconnection + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: |- + ManagementClusterConnection represents a link between a managed cluster and a management cluster. At most one + instance of this resource is supported. It must be named "tigera-secure". + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: + ManagementClusterConnectionSpec defines the desired state + of ManagementClusterConnection + properties: + guardianDeployment: + description: GuardianDeployment configures the guardian Deployment. + properties: + spec: + description: Spec is the specification of the guardian Deployment. + properties: + template: + description: + Template describes the guardian Deployment pod + that will be created. + properties: + spec: + description: Spec is the guardian Deployment's PodSpec. + properties: + containers: + description: |- + Containers is a list of guardian containers. + If specified, this overrides the specified guardian Deployment containers. + If omitted, the guardian Deployment will use its default values for its containers. + items: + description: + GuardianDeploymentContainer is a guardian + Deployment container. + properties: + name: + description: |- + Name is an enum which identifies the guardian Deployment container by name. + Supported values are: tigera-guardian + enum: + - tigera-guardian + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named guardian Deployment container's resources. + If omitted, the guardian Deployment will use its default value for this container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + initContainers: + description: |- + InitContainers is a list of guardian init containers. + If specified, this overrides the specified guardian Deployment init containers. + If omitted, the guardian Deployment will use its default values for its init containers. + items: + description: + GuardianDeploymentInitContainer is + a guardian Deployment init container. + properties: + name: + description: + Name is an enum which identifies + the guardian Deployment init container by + name. + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named guardian Deployment init container's resources. + If omitted, the guardian Deployment will use its default value for this init container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + type: object + type: object + type: object + type: object + impersonation: + description: |- + Impersonation configures the RBAC impersonation permissions for the guardian deployment. This field is not + applicable to installation variant Calico as no impersonation is ever used. Otherwise, if this field is left nil, + a default set of permissions will be applied. + WARNING: If this field is specified, it completely replaces the default permissions. + For example, providing an empty `impersonation: {}` block will result in guardian + having NO impersonation permissions. Similarly, if you specify `users` but omit `groups`, + guardian will lose its default permissions to impersonate groups. + properties: + groups: + description: |- + Groups is a list of group names that can be impersonated. An empty list infers all groups can be impersonated, + a null values means none. + items: + type: string + type: array + serviceAccounts: + description: |- + ServiceAccounts is a list of service account names that can be impersonated. An empty list infers all service accounts can + be impersonated, a null values means none. + items: + type: string + type: array + users: + description: |- + Users is a list of users that can be impersonated. An empty list infers all users can be impersonated, a null + value means none. + items: + type: string + type: array + type: object + managementClusterAddr: + description: |- + Specify where the managed cluster can reach the management cluster. Ex.: "10.128.0.10:30449". A managed cluster + should be able to access this address. This field is used by managed clusters only. + type: string + tls: + description: + TLS provides options for configuring how Managed Clusters + can establish an mTLS connection with the Management Cluster. + properties: + ca: + description: |- + CA indicates which verification method the tunnel client should use to verify the tunnel server's identity. + When left blank or set to 'Tigera', the tunnel client will expect a self-signed cert to be included in the certificate bundle + and will expect the cert to have a Common Name (CN) of 'voltron'. + When set to 'Public', the tunnel client will use its installed system certs and will use the managementClusterAddr to verify the tunnel server's identity. + Default: Tigera + enum: + - Tigera + - Public + type: string + type: object + type: object + status: + description: + ManagementClusterConnectionStatus defines the observed state + of ManagementClusterConnection + properties: + conditions: + description: |- + Conditions represents the latest observed set of conditions for the component. A component may be one or more of + Ready, Progressing, Degraded or other customer types. + items: + description: + Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + x-kubernetes-validations: + - message: resource name must be 'default' or 'tigera-secure' + rule: self.metadata.name == 'default' || self.metadata.name == 'tigera-secure' + served: true + storage: true + subresources: + status: {} diff --git a/charts/projectcalico.org.v3/templates/operator.tigera.io_tigerastatuses.yaml b/charts/projectcalico.org.v3/templates/operator.tigera.io_tigerastatuses.yaml new file mode 100644 index 00000000000..282b0a1e6c7 --- /dev/null +++ b/charts/projectcalico.org.v3/templates/operator.tigera.io_tigerastatuses.yaml @@ -0,0 +1,116 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: tigerastatuses.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: TigeraStatus + listKind: TigeraStatusList + plural: tigerastatuses + singular: tigerastatus + scope: Cluster + versions: + - additionalPrinterColumns: + - description: Whether the component running and stable. + jsonPath: .status.conditions[?(@.type=='Available')].status + name: Available + type: string + - description: Whether the component is processing changes. + jsonPath: .status.conditions[?(@.type=='Progressing')].status + name: Progressing + type: string + - description: Whether the component is degraded. + jsonPath: .status.conditions[?(@.type=='Degraded')].status + name: Degraded + type: string + - description: The time the component's Available status last changed. + jsonPath: .status.conditions[?(@.type=='Available')].lastTransitionTime + name: Since + type: date + name: v1 + schema: + openAPIV3Schema: + description: + TigeraStatus represents the most recently observed status for + Calico or a Calico Enterprise functional area. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: TigeraStatusSpec defines the desired state of TigeraStatus + type: object + status: + description: TigeraStatusStatus defines the observed state of TigeraStatus + properties: + conditions: + description: |- + Conditions represents the latest observed set of conditions for this component. A component may be one or more of + Available, Progressing, or Degraded. + items: + description: + TigeraStatusCondition represents a condition attached + to a particular component. + properties: + lastTransitionTime: + description: + The timestamp representing the start time for the + current status. + format: date-time + type: string + message: + description: + Optionally, a detailed message providing additional + context. + type: string + observedGeneration: + description: |- + observedGeneration represents the generation that the condition was set based upon. + For instance, if generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + type: integer + reason: + description: A brief reason explaining the condition. + type: string + status: + description: + The status of the condition. May be True, False, + or Unknown. + type: string + type: + description: + The type of condition. May be Available, Progressing, + or Degraded. + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + required: + - conditions + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/tigera-operator/crds/operator.tigera.io_apiservers.yaml b/charts/projectcalico.org.v3/templates/operator.tigera.io_whiskers.yaml similarity index 87% rename from charts/tigera-operator/crds/operator.tigera.io_apiservers.yaml rename to charts/projectcalico.org.v3/templates/operator.tigera.io_whiskers.yaml index a4c8d7169b0..fb66ddc289c 100644 --- a/charts/tigera-operator/crds/operator.tigera.io_apiservers.yaml +++ b/charts/projectcalico.org.v3/templates/operator.tigera.io_whiskers.yaml @@ -2,23 +2,20 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.3 - name: apiservers.operator.tigera.io + controller-gen.kubebuilder.io/version: v0.18.0 + name: whiskers.operator.tigera.io spec: group: operator.tigera.io names: - kind: APIServer - listKind: APIServerList - plural: apiservers - singular: apiserver + kind: Whisker + listKind: WhiskerList + plural: whiskers + singular: whisker scope: Cluster versions: - name: v1 schema: openAPIV3Schema: - description: |- - APIServer installs the Tigera API server and related resources. At most one instance - of this resource is supported. It must be named "default" or "tigera-secure". properties: apiVersion: description: |- @@ -38,13 +35,17 @@ spec: metadata: type: object spec: - description: Specification of the desired state for the Tigera API server. properties: - apiServerDeployment: + notifications: description: |- - APIServerDeployment configures the calico-apiserver Deployment. If - used in conjunction with ControlPlaneNodeSelector or ControlPlaneTolerations, then these overrides - take precedence. + Default: Enabled + This setting enables calls to an external API to retrieve notification banner text in the Whisker UI. + Allowed values are Enabled or Disabled. Defaults to Enabled. + type: string + whiskerDeployment: + description: + WhiskerDeployment is the configuration for the whisker + Deployment. properties: metadata: description: @@ -69,27 +70,73 @@ spec: type: object type: object spec: - description: Spec is the specification of the API server Deployment. + description: Spec is the specification of the whisker Deployment. properties: minReadySeconds: description: |- MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should be ready without any of its container crashing, for it to be considered available. - If specified, this overrides any minReadySeconds value that may be set on the API server Deployment. - If omitted, the API server Deployment will use its default value for minReadySeconds. + If specified, this overrides any minReadySeconds value that may be set on the whisker Deployment. + If omitted, the whisker Deployment will use its default value for minReadySeconds. format: int32 maximum: 2147483647 minimum: 0 type: integer + strategy: + description: + The deployment strategy to use to replace existing + pods with new ones. + properties: + rollingUpdate: + description: |- + Rolling update config params. Present only if DeploymentStrategyType = + RollingUpdate. + to be. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: object template: description: - Template describes the API server Deployment - pod that will be created. + Template describes the whisker Deployment pod + that will be created. properties: metadata: - description: |- - Metadata is a subset of a Kubernetes object's metadata that is added to - the pod's metadata. + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the pod's metadata. properties: annotations: additionalProperties: @@ -109,14 +156,12 @@ spec: type: object type: object spec: - description: Spec is the API server Deployment's PodSpec. + description: Spec is the whisker Deployment's PodSpec. properties: affinity: - description: |- - Affinity is a group of affinity scheduling rules for the API server pods. - If specified, this overrides any affinity that may be set on the API server Deployment. - If omitted, the API server Deployment will use its default value for affinity. - WARNING: Please note that this field will override the default API server Deployment affinity. + description: + Affinity is a group of affinity scheduling + rules for the whisker pods. properties: nodeAffinity: description: @@ -426,7 +471,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -441,7 +485,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -615,7 +658,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -630,7 +672,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -728,8 +769,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -806,7 +847,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -821,7 +861,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -995,7 +1034,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -1010,7 +1048,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -1097,145 +1134,26 @@ spec: type: object containers: description: |- - Containers is a list of API server containers. - If specified, this overrides the specified API server Deployment containers. - If omitted, the API server Deployment will use its default values for its containers. + Containers is a list of whisker containers. + If specified, this overrides the specified EGW Deployment containers. + If omitted, the whisker Deployment will use its default values for its containers. items: - description: - APIServerDeploymentContainer is an - API server Deployment container. properties: name: - description: |- - Name is an enum which identifies the API server Deployment container by name. - Supported values are: calico-apiserver, tigera-queryserver, calico-l7-admission-controller enum: - - calico-apiserver - - tigera-queryserver - - calico-l7-admission-controller + - whisker + - whisker-backend type: string - ports: - description: |- - Ports allows customization of container's ports. - If specified, this overrides the named APIServer Deployment container's ports. - If omitted, the API server Deployment will use its default value for this container's port. - items: - properties: - containerPort: - description: |- - Number of port to expose on the pod's IP address. - This must be a valid port number, 0 < x < 65536. - format: int32 - type: integer - name: - description: |- - Name is an enum which identifies the API server Deployment Container port by name. - Supported values are: apiserver, queryserver, l7admctrl - enum: - - apiserver - - queryserver - - l7admctrl - type: string - required: - - containerPort - - name - type: object - type: array resources: - description: |- - Resources allows customization of limits and requests for compute resources such as cpu and memory. - If specified, this overrides the named API server Deployment container's resources. - If omitted, the API server Deployment will use its default value for this container's resources. - If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + description: + ResourceRequirements describes + the compute resource requirements. properties: claims: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - This field is immutable. It can only be set for containers. - items: - description: - ResourceClaim references - one entry in PodSpec.ResourceClaims. - properties: - name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. - type: string - request: - description: |- - Request is the name chosen for a request in the referenced claim. - If empty, everything from the claim is made available, otherwise - only the result of this request. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - type: object - required: - - name - type: object - type: array - initContainers: - description: |- - InitContainers is a list of API server init containers. - If specified, this overrides the specified API server Deployment init containers. - If omitted, the API server Deployment will use its default values for its init containers. - items: - description: - APIServerDeploymentInitContainer is - an API server Deployment init container. - properties: - name: - description: |- - Name is an enum which identifies the API server Deployment init container by name. - Supported values are: calico-apiserver-certs-key-cert-provisioner - enum: - - calico-apiserver-certs-key-cert-provisioner - type: string - resources: - description: |- - Resources allows customization of limits and requests for compute resources such as cpu and memory. - If specified, this overrides the named API server Deployment init container's resources. - If omitted, the API server Deployment will use its default value for this init container's resources. - properties: - claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -1294,27 +1212,28 @@ spec: nodeSelector: additionalProperties: type: string - description: |- - NodeSelector is the API server pod's scheduling constraints. - If specified, each of the key/value pairs are added to the API server Deployment nodeSelector provided - the key does not already exist in the object's nodeSelector. - If used in conjunction with ControlPlaneNodeSelector, that nodeSelector is set on the API server Deployment - and each of this field's key/value pairs are added to the API server Deployment nodeSelector provided - the key does not already exist in the object's nodeSelector. - If omitted, the API server Deployment will use its default value for nodeSelector. - WARNING: Please note that this field will modify the default API server Deployment nodeSelector. + description: + NodeSelector gives more control over + the nodes where the whisker pods will run on. type: object priorityClassName: description: PriorityClassName allows to specify a PriorityClass resource to be used. type: string + terminationGracePeriodSeconds: + description: + TerminationGracePeriodSeconds defines + the termination grace period of the whisker pods + in seconds. + format: int64 + minimum: 0 + type: integer tolerations: description: |- - Tolerations is the API server pod's tolerations. - If specified, this overrides any tolerations that may be set on the API server Deployment. - If omitted, the API server Deployment will use its default value for tolerations. - WARNING: Please note that this field will override the default API server Deployment tolerations. + Tolerations is the whisker pod's tolerations. + If specified, this overrides any tolerations that may be set on the whisker Deployment. + If omitted, the whisker Deployment will use its default value for tolerations. items: description: |- The pod this Toleration is attached to tolerates any taint that matches @@ -1479,7 +1398,6 @@ spec: - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. If this value is nil, the behavior is equivalent to the Honor policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string nodeTaintsPolicy: description: |- @@ -1489,7 +1407,6 @@ spec: has a toleration, are included. - Ignore: node taints are ignored. All nodes are included. If this value is nil, the behavior is equivalent to the Ignore policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string topologyKey: description: |- @@ -1535,42 +1452,9 @@ spec: type: object type: object type: object - logging: - properties: - apiServer: - properties: - logSeverity: - default: Info - description: LogSeverity defines log level for APIServer container. - enum: - - Fatal - - Error - - Warn - - Info - - Debug - - Trace - type: string - type: object - queryServer: - properties: - logSeverity: - default: Info - description: - LogSeverity defines log level for QueryServer - container. - enum: - - Fatal - - Error - - Warn - - Info - - Debug - - Trace - type: string - type: object - type: object type: object status: - description: Most recently observed status for the Tigera API server. + description: WhiskerStatus defines the observed state of Whisker properties: conditions: description: |- @@ -1632,11 +1516,11 @@ spec: - type type: object type: array - state: - description: State provides user-readable status. - type: string type: object type: object + x-kubernetes-validations: + - message: resource name must be 'default' + rule: self.metadata.name == 'default' served: true storage: true subresources: diff --git a/node/tests/st/bgp/__init__.py b/charts/projectcalico.org.v3/values.yaml similarity index 100% rename from node/tests/st/bgp/__init__.py rename to charts/projectcalico.org.v3/values.yaml diff --git a/charts/test/helm_suite_test.go b/charts/test/helm_suite_test.go index 2a192795a2b..8a52b0dcccb 100644 --- a/charts/test/helm_suite_test.go +++ b/charts/test/helm_suite_test.go @@ -4,9 +4,8 @@ import ( "os/exec" "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -16,14 +15,14 @@ func init() { } func TestHelm(t *testing.T) { - // testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/helm_suite.xml") + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/helm_suite.xml" _, err := exec.LookPath("helm") if err != nil { t.Skip("skipping exec tests since 'helm' is not installed") } - RunSpecsWithDefaultAndCustomReporters(t, "Helm Suite", []Reporter{junitReporter}) + ginkgo.RunSpecs(t, "Helm Suite", suiteConfig, reporterConfig) } diff --git a/charts/tigera-operator/README.md b/charts/tigera-operator/README.md index 01f80279836..1114c4ecd6d 100644 --- a/charts/tigera-operator/README.md +++ b/charts/tigera-operator/README.md @@ -106,7 +106,7 @@ imagePullSecrets: {} # Configures general installation parameters for Calico. Schema is based # on the operator.tigera.io/Installation API documented -# here: https://docs.tigera.io/calico/latest/reference/installation/api#operator.tigera.io/v1.InstallationSpec +# here: https://docs.tigera.io/calico/latest/reference/installation/api#installationspec installation: enabled: true kubernetesProvider: "" @@ -118,6 +118,10 @@ installation: # Example: --set installation.imagePullSecrets[0].name=my-existing-secret imagePullSecrets: [] + # Configure the kubelet volume plugin path used by the CSI driver. + # Set to "None" to disable the CSI driver. If this field is left unset, /var/lib/kubelet is used and CSI is enabled. + kubeletVolumePluginPath: "None" + # Configures general installation parameters for Calico. Schema is based # on the operator.tigera.io/Installation API documented # here: https://docs.tigera.io/calico/latest/reference/installation/api#operator.tigera.io/v1.APIServerSpec @@ -166,9 +170,6 @@ tigeraOperator: calicoctl: image: quay.io/calico/ctl -# Configuration for the Calico CSI plugin - setting to None will disable the plugin, default: /var/lib/kubelet -kubeletVolumePluginPath: None - # Optionally configure the host and port used to access the Kubernetes API server. kubernetesServiceEndpoint: host: "" diff --git a/charts/tigera-operator/templates/tigera-operator/02-role-tigera-operator.yaml b/charts/tigera-operator/templates/tigera-operator/02-role-tigera-operator.yaml index 1fd2d554d5d..f81de5941dd 100644 --- a/charts/tigera-operator/templates/tigera-operator/02-role-tigera-operator.yaml +++ b/charts/tigera-operator/templates/tigera-operator/02-role-tigera-operator.yaml @@ -6,8 +6,10 @@ metadata: labels: {{- include "tigera-operator.labels" (dict "context" .) | nindent 4 }} rules: - # The tigera/operator installs CustomResourceDefinitions necessary for itself + # The tigera/operator needs the following permissions when configured with the --bootstrapCRDs=true + # argument. When specified, the tigera/operator installs CustomResourceDefinitions necessary for itself # and Calico more broadly to function. + # You can remove this permission block if your operator deployment does not use this option. - apiGroups: - apiextensions.k8s.io resources: @@ -17,7 +19,7 @@ rules: - list - watch - create - # We only allow update access to our own CRDs. + # Allow updating the crd.projectcalico.org CRDs. - apiGroups: - apiextensions.k8s.io resources: @@ -25,11 +27,6 @@ rules: verbs: - update resourceNames: - - apiservers.operator.tigera.io - - gatewayapis.operator.tigera.io - - imagesets.operator.tigera.io - - installations.operator.tigera.io - - tigerastatuses.operator.tigera.io - bgpconfigurations.crd.projectcalico.org - bgpfilters.crd.projectcalico.org - bgppeers.crd.projectcalico.org @@ -52,10 +49,54 @@ rules: - stagedkubernetesnetworkpolicies.crd.projectcalico.org - networksets.crd.projectcalico.org - tiers.crd.projectcalico.org + # Allow updating the projectcalico.org/v3 CRDs. + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - update + resourceNames: + - bgpconfigurations.projectcalico.org + - bgpfilters.projectcalico.org + - bgppeers.projectcalico.org + - blockaffinities.projectcalico.org + - caliconodestatuses.projectcalico.org + - clusterinformations.projectcalico.org + - felixconfigurations.projectcalico.org + - globalnetworkpolicies.projectcalico.org + - stagedglobalnetworkpolicies.projectcalico.org + - globalnetworksets.projectcalico.org + - hostendpoints.projectcalico.org + - ipamblocks.projectcalico.org + - ipamconfigurations.projectcalico.org + - ipamhandles.projectcalico.org + - ippools.projectcalico.org + - ipreservations.projectcalico.org + - kubecontrollersconfigurations.projectcalico.org + - networkpolicies.projectcalico.org + - stagednetworkpolicies.projectcalico.org + - stagedkubernetesnetworkpolicies.projectcalico.org + - networksets.projectcalico.org + - tiers.projectcalico.org + # Allow updating the operator.tigera.io CRDs. + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - update + resourceNames: + - apiservers.operator.tigera.io + - gatewayapis.operator.tigera.io + - imagesets.operator.tigera.io + - installations.operator.tigera.io + - tigerastatuses.operator.tigera.io - whiskers.operator.tigera.io - goldmanes.operator.tigera.io - managementclusterconnections.operator.tigera.io - # We need update and delete access for ANP/BANP CRDs to set owner refs when assuming control of pre-existing CRDs, for example on OCP. + # We need update and delete access for the ClusterNetworkPolicy CRD to set owner references + # when assuming control of pre-existing CRDs, for example on OCP. - apiGroups: - apiextensions.k8s.io resources: @@ -64,8 +105,7 @@ rules: - update - delete resourceNames: - - adminnetworkpolicies.policy.networking.k8s.io - - baselineadminnetworkpolicies.policy.networking.k8s.io + - clusternetworkpolicies.policy.networking.k8s.io - apiGroups: - "" resources: @@ -227,6 +267,7 @@ rules: - watch - apiGroups: - crd.projectcalico.org + - projectcalico.org resources: - felixconfigurations - ippools @@ -238,6 +279,7 @@ rules: - watch - apiGroups: - crd.projectcalico.org + - projectcalico.org resources: - kubecontrollersconfigurations - bgpconfigurations @@ -311,10 +353,19 @@ rules: resources: - mutatingwebhookconfigurations verbs: + - create - delete - get - list - watch + - apiGroups: + - admissionregistration.k8s.io + resources: + - mutatingwebhookconfigurations + resourceNames: + - envoy-gateway-topology-injector.tigera-gateway + verbs: + - update # Needed for operator lock - apiGroups: - coordination.k8s.io @@ -346,6 +397,18 @@ rules: verbs: - list - watch + # The operator needs permissions to create and update validating webhook configuration. + - apiGroups: + - admissionregistration.k8s.io + resources: + - validatingwebhookconfigurations + verbs: + - create + - update + - delete + - get + - list + - watch {{- if eq (lower .Values.installation.kubernetesProvider) "openshift" }} # When running in OpenShift, we need to update networking config. - apiGroups: @@ -393,6 +456,7 @@ rules: # Need these permissions for the calicoctl init container. - apiGroups: - crd.projectcalico.org + - projectcalico.org resources: - bgpconfigurations - bgppeers @@ -408,6 +472,7 @@ rules: - create - apiGroups: - crd.projectcalico.org + - projectcalico.org resources: - ipamblocks verbs: @@ -500,6 +565,8 @@ rules: - tcproutes.gateway.networking.k8s.io - tlsroutes.gateway.networking.k8s.io - udproutes.gateway.networking.k8s.io + - xbackendtrafficpolicies.gateway.networking.x-k8s.io + - xlistenersets.gateway.networking.x-k8s.io - backends.gateway.envoyproxy.io - backendtrafficpolicies.gateway.envoyproxy.io - clienttrafficpolicies.gateway.envoyproxy.io @@ -549,3 +616,13 @@ rules: - update resourceNames: - tigera-gateway-api-gateway-helm-certgen + # For monitoring KubeVirt live migration. The operator does not do this itself, but needs to + # be able to assign this RBAC to Typha and calico-node. + - apiGroups: + - kubevirt.io + resources: + - virtualmachineinstancemigrations + verbs: + - get + - list + - watch diff --git a/charts/tigera-operator/templates/tigera-operator/02-tigera-operator.yaml b/charts/tigera-operator/templates/tigera-operator/02-tigera-operator.yaml index f74672e774c..6077aaa75ac 100644 --- a/charts/tigera-operator/templates/tigera-operator/02-tigera-operator.yaml +++ b/charts/tigera-operator/templates/tigera-operator/02-tigera-operator.yaml @@ -43,8 +43,12 @@ spec: # resources to terminate when being uninstalled. terminationGracePeriodSeconds: 60 hostNetwork: true + {{- if .Values.dnsPolicy }} + dnsPolicy: {{ .Values.dnsPolicy }} + {{- else }} # This must be set when hostNetwork is true or else the cluster services won't resolve dnsPolicy: ClusterFirstWithHostNet + {{- end }} {{- if .Values.dnsConfig }} dnsConfig: {{- toYaml .Values.dnsConfig | nindent 8 }} @@ -77,6 +81,9 @@ spec: value: "tigera-operator" - name: TIGERA_OPERATOR_INIT_IMAGE_VERSION value: {{.Values.tigeraOperator.version}} + {{- with .Values.tigeraOperator.env }} + {{- toYaml . | nindent 12 }} + {{- end }} envFrom: - configMapRef: name: kubernetes-services-endpoint diff --git a/charts/tigera-operator/values.yaml b/charts/tigera-operator/values.yaml index 74a1472031d..78cd8829564 100644 --- a/charts/tigera-operator/values.yaml +++ b/charts/tigera-operator/values.yaml @@ -5,9 +5,23 @@ # Example: --set-file imagePullSecrets.gcr=./pull-secret.json imagePullSecrets: {} +# Configures general installation parameters for Calico. Schema is based +# on the operator.tigera.io/Installation API documented +# here: https://docs.tigera.io/calico/latest/reference/installation/api#installationspec installation: enabled: true - kubernetesProvider: "" + # Uncomment to enable prometheus metrics reporting for node and typha. + # nodeMetricsPort: 9090 + # typhaMetricsPort: 9091 + # Path to the kubelet volume plugin directory used by the Calico CSI driver. + # Set to "None" to disable the CSI driver. If unset, defaults to /var/lib/kubelet (CSI enabled). + kubeletVolumePluginPath: "None" + + # --- General Image Settings --- + # registry: + # imagePath: + # imagePrefix: + # imagePullSecrets are configured on all images deployed by the tigera-operator. # secrets specified here must exist in the tigera-operator namespace; they won't be created by the operator or helm. # imagePullSecrets are a slice of LocalObjectReferences, which is the same format they appear as on deployments. @@ -15,8 +29,58 @@ installation: # Example: --set installation.imagePullSecrets[0].name=my-existing-secret imagePullSecrets: [] - # Disable CSI driver by default. - kubeletVolumePluginPath: "None" + # Valid options: EKS, GKE, AKS, RKE2, OpenShift, DockerEnterprise, TKG, Kind. + # If empty the operator will attempt to automatically determine the current provider. + kubernetesProvider: "" + + # cni: + # # "Calico" (default), "AmazonVPC", "GKE", "AzureVNET", "HostLocal" + # type: "Calico" + # ipam: + # # "Calico" (default), "HostLocal", "AmazonVPC", "AzureVNET" + # type: "Calico" + + # calicoNetwork: + # # "Enabled" (default) or "Disabled" + # bgp: "Enabled" + # + # # One of: Iptables, BPF, VPP, Nftables + # linuxDataplane: "Iptables" + # + # # Enable HostPorts (default: Enabled) + # hostPorts: "Enabled" + # + # # Default: auto-detected based on node interface (typically around 1450) + # mtu: 1450 + # + # ipPools: + # - cidr: "192.168.0.0/16" + # encapsulation: "IPIP" # IPIP, VXLAN, IPIPCrossSubnet, VXLANCrossSubnet, None + # natOutgoing: "Enabled" + # nodeSelector: "all()" + # blockSize: 26 + + # Number of replicas for Typha/Control Plane (default: 2) + controlPlaneReplicas: 2 + + controlPlaneNodeSelector: {} + + controlPlaneTolerations: [] + + # Run Calico in non-privileged mode (Rootless) + nonPrivileged: "Disabled" + + # Enable FIPS compliance mode (Requires FIPS compliant cluster/images) + # Only supported for Variant=Calico. + # fipsMode: "Disabled" + + # Configure log severity (Info, Debug, Warning, Error, Fatal) + # logging: + # cni: + # logSeverity: Info + # logFileMaxSize: 100Mi + # logFileMaxAgeDays: 30 # Days + # logFileMaxCount: 10 # apiServer configures the Calico API server, needed for interacting with # the projectcalico.org/v3 suite of APIs. diff --git a/cni-plugin/.semaphore/run-win-fv.sh b/cni-plugin/.semaphore/run-win-fv.sh deleted file mode 100755 index 19609022b11..00000000000 --- a/cni-plugin/.semaphore/run-win-fv.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -x - -: ${BACKEND:?Error: BACKEND is not set} - -FV_DIR="$HOME/$SEMAPHORE_GIT_DIR/process/testing/winfv-cni-plugin/aso" -pushd ${FV_DIR} - -# Prepare local files -cp $HOME/$SEMAPHORE_GIT_DIR/cni-plugin/bin/windows/*.exe ./windows - -# Run FV. -BACKEND=$BACKEND make run-fv | tee run-fv.log - -# Get results and logs -ls -ltr ./report -mkdir /home/semaphore/fv.log -cp run-fv.log /home/semaphore/fv.log -cp ./report/*.log /home/semaphore/fv.log - - -# Stop for debug -echo "Check for pause file..." -while [ -f /home/semaphore/pause-for-debug ]; -do - echo "#" - sleep 30 -done - -# Print relevant snippets from logs -log_regexps='(? /dev/null && \ -for log_file in /home/semaphore/fv.log/*.log; do - prefix="[$(basename ${log_file})]" - cat ${log_file} | iconv -f UTF-16 -t UTF-8 | sed 's/\r$//g' | grep --line-buffered --perl ${log_regexps} -B 2 -A 15 | sed 's/.*/'"${prefix}"' &/g' -done; - -# Search for the file indicates that the Windows node has completed the FV process -if [ ! -f ./report/done-marker ]; -then - echo "Windows node failed to complete the FV process." - exit 1 -fi - -# Search for error code file -if [ -f ./report/error-codes ]; -then - echo "Windows FV returned error(s)." - exit 1 -fi - -echo "Run Windows FV is done." diff --git a/cni-plugin/Dockerfile b/cni-plugin/Dockerfile index 17b0b9e5d06..ab676b56d3b 100644 --- a/cni-plugin/Dockerfile +++ b/cni-plugin/Dockerfile @@ -33,6 +33,14 @@ LABEL org.opencontainers.image.vendor="Project Calico" LABEL org.opencontainers.image.version="${GIT_VERSION}" LABEL org.opencontainers.image.licenses="Apache-2.0" +LABEL description="Calico Networking for CNI includes a CNI networking plugin and CNI IPAM plugin" +LABEL maintainer="maintainers@tigera.io" +LABEL name="Calico Networking for CNI" +LABEL release=1 +LABEL summary="Calico Networking for CNI includes a CNI networking plugin and CNI IPAM plugin" +LABEL vendor="Project Calico" +LABEL version="${GIT_VERSION}" + ENV PATH=/opt/cni/bin:$PATH COPY --from=source / / diff --git a/cni-plugin/Makefile b/cni-plugin/Makefile index bd1edd023db..e963d6a27f1 100644 --- a/cni-plugin/Makefile +++ b/cni-plugin/Makefile @@ -5,7 +5,7 @@ PACKAGE_NAME = github.com/projectcalico/calico/cni-plugin # Name of the images. # e.g., /: CNI_PLUGIN_IMAGE ?=cni -WINDOWS_IMAGE ?=cni-windows +WINDOWS_IMAGE ?=$(CNI_PLUGIN_IMAGE)-windows BUILD_IMAGES ?=$(CNI_PLUGIN_IMAGE) ############################################################################### @@ -201,10 +201,40 @@ build-win-cni-bins: $(WINDOWS_BIN)/flannel.exe ############################################################################### # Unit Tests ############################################################################### + +fv: + @echo "No FV tests for CNI plugin" + + +## Install KubeVirt CRDs for testing (without operator/controllers) +.PHONY: install-kubevirt-crds +install-kubevirt-crds: + @echo "Installing KubeVirt CRDs for testing..." + @while ! docker exec calico-local-apiserver kubectl \ + apply -f /go/src/github.com/projectcalico/calico/cni-plugin/tests/kubevirt-test-crds.yaml; \ + do echo "Waiting to install KubeVirt CRDs..."; \ + sleep 1; \ + done + @echo "KubeVirt CRDs installed successfully" + +## Remove KubeVirt CRDs +.PHONY: remove-kubevirt-crds +remove-kubevirt-crds: + @echo "Removing KubeVirt CRDs..." + @docker exec calico-local-apiserver kubectl \ + delete -f /go/src/github.com/projectcalico/calico/cni-plugin/tests/kubevirt-test-crds.yaml 2>/dev/null || \ + echo "KubeVirt CRDs already removed or not present" + + ## Run the unit tests. +## Mainline tests run without KubeVirt CRDs to ensure non-KubeVirt code paths don't depend on them. +## KubeVirt-specific tests run separately with CRDs installed. ut: run-k8s-controller-manager $(BIN)/install $(BIN)/host-local $(BIN)/calico-ipam $(BIN)/calico $(MAKE) ut-datastore DATASTORE_TYPE=etcdv3 - $(MAKE) ut-datastore DATASTORE_TYPE=kubernetes + $(MAKE) remove-kubevirt-crds + $(MAKE) ut-datastore DATASTORE_TYPE=kubernetes GINKGO_ARGS='--label-filter=!KubeVirt $(GINKGO_ARGS)' + $(MAKE) install-kubevirt-crds + $(MAKE) ut-datastore DATASTORE_TYPE=kubernetes GINKGO_ARGS='--label-filter=KubeVirt $(GINKGO_ARGS)' ut-datastore: # The tests need to run as root @@ -218,12 +248,13 @@ ut-datastore: -e CNI_SPEC_VERSION=$(CNI_SPEC_VERSION) \ -e DATASTORE_TYPE=$(DATASTORE_TYPE) \ -e ETCD_ENDPOINTS=http://$(LOCAL_IP_ENV):2379 \ + -e CALICO_API_GROUP=$(CALICO_API_GROUP) \ -e KUBECONFIG=/home/user/certs/kubeconfig \ -v $(CURDIR)/../:/go/src/github.com/projectcalico/calico:rw \ -v $(CERTS_PATH):/home/user/certs \ $(CALICO_BUILD) sh -c '$(GIT_CONFIG_SSH) \ cd /go/src/$(PACKAGE_NAME) && \ - ginkgo -cover -r -skipPackage pkg/install,containernetworking-plugins,flannel-cni-plugin $(GINKGO_ARGS)' + ginkgo -cover -r -skip-package pkg/install,containernetworking-plugins,flannel-cni-plugin $(GINKGO_ARGS)' ut-etcd: run-k8s-controller-manager build $(BIN)/host-local $(MAKE) ut-datastore DATASTORE_TYPE=etcdv3 @@ -318,4 +349,4 @@ release-publish-latest: release-prereqs ## Run the unit tests, watching for changes. test-watch: $(BIN)/install run-etcd run-k8s-apiserver # The tests need to run as root - CGO_ENABLED=0 ETCD_IP=127.0.0.1 PLUGIN=calico GOPATH=$(GOPATH) $(shell which ginkgo) watch -skipPackage pkg/install + CGO_ENABLED=0 ETCD_IP=127.0.0.1 PLUGIN=calico GOPATH=$(GOPATH) ginkgo watch -skip-package pkg/install diff --git a/cni-plugin/deps.txt b/cni-plugin/deps.txt index 9cf0bb90b2c..4ddf44cc138 100644 --- a/cni-plugin/deps.txt +++ b/cni-plugin/deps.txt @@ -2,102 +2,111 @@ This file contains the list of modules that this package depends on in order to trigger CI on changes -go 1.25.1 +go 1.25.7 +github.com/DeRuina/timberjack v1.3.9 +github.com/Masterminds/semver/v3 v3.4.0 github.com/alexflint/go-filemutex v1.3.0 github.com/beorn7/perks v1.0.1 github.com/cespare/xxhash/v2 v2.3.0 -github.com/containernetworking/cni v1.2.3 -github.com/containernetworking/plugins v1.6.2 +github.com/containernetworking/cni v1.3.0 +github.com/containernetworking/plugins v1.9.0 github.com/coreos/go-iptables v0.8.0 github.com/coreos/go-semver v0.3.1 -github.com/coreos/go-systemd/v22 v22.5.0 +github.com/coreos/go-systemd/v22 v22.6.0 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc -github.com/emicklei/go-restful/v3 v3.11.0 +github.com/emicklei/go-restful v2.15.0+incompatible +github.com/emicklei/go-restful/v3 v3.12.2 github.com/fsnotify/fsnotify v1.9.0 -github.com/fxamacker/cbor/v2 v2.7.0 +github.com/fxamacker/cbor/v2 v2.9.0 +github.com/go-kit/log v0.2.1 +github.com/go-logfmt/logfmt v0.6.0 github.com/go-logr/logr v1.4.3 github.com/go-openapi/jsonpointer v0.21.0 github.com/go-openapi/jsonreference v0.20.2 github.com/go-openapi/swag v0.23.0 github.com/go-playground/locales v0.14.1 github.com/go-playground/universal-translator v0.18.1 -github.com/gofrs/flock v0.12.1 +github.com/gofrs/flock v0.13.0 github.com/gogo/protobuf v1.3.2 github.com/golang/protobuf v1.5.4 -github.com/google/gnostic-models v0.6.9 +github.com/google/gnostic-models v0.7.0 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 -github.com/grpc-ecosystem/grpc-gateway v1.16.0 -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 github.com/jinzhu/copier v0.4.0 github.com/josharian/intern v1.0.0 github.com/json-iterator/go v1.1.12 github.com/juju/errors v1.0.0 github.com/kelseyhightower/envconfig v1.4.0 +github.com/klauspost/compress v1.18.0 github.com/leodido/go-urn v1.4.0 github.com/mailru/easyjson v0.7.7 github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd -github.com/modern-go/reflect2 v1.0.2 +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 github.com/natefinch/atomic v1.0.1 github.com/nmrshll/go-cp v0.0.0-20180115193924-61436d3b7cfa -github.com/nxadm/tail v1.4.8 github.com/onsi/ginkgo v1.16.5 -github.com/onsi/gomega v1.38.0 +github.com/onsi/ginkgo/v2 v2.28.1 +github.com/onsi/gomega v1.39.1 +github.com/openshift/custom-resource-status v1.1.2 github.com/pkg/errors v0.9.1 +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/projectcalico/calico -github.com/projectcalico/calico/lib/std v0.0.0-00010101000000-000000000000 -github.com/projectcalico/go-json v0.0.0-20161128004156-6219dc7339ba -github.com/projectcalico/go-yaml-wrapper v0.0.0-20191112210931-090425220c54 -github.com/prometheus/client_golang v1.23.0 +github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 -github.com/prometheus/common v0.65.0 -github.com/prometheus/procfs v0.17.0 +github.com/prometheus/common v0.67.2 +github.com/prometheus/procfs v0.19.2 github.com/safchain/ethtool v0.6.2 github.com/sirupsen/logrus v1.9.3 -github.com/spf13/pflag v1.0.7 +github.com/spf13/pflag v1.0.10 github.com/vishvananda/netlink v1.3.1 github.com/vishvananda/netns v0.0.5 github.com/x448/float16 v0.8.4 -go.etcd.io/etcd/api/v3 v3.6.4 -go.etcd.io/etcd/client/pkg/v3 v3.6.4 -go.etcd.io/etcd/client/v3 v3.6.4 +go.etcd.io/etcd/api/v3 v3.6.5 +go.etcd.io/etcd/client/pkg/v3 v3.6.5 +go.etcd.io/etcd/client/v3 v3.6.5 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 -go.yaml.in/yaml/v2 v2.4.2 -golang.org/x/crypto v0.41.0 -golang.org/x/net v0.43.0 -golang.org/x/oauth2 v0.30.0 -golang.org/x/sync v0.16.0 -golang.org/x/sys v0.35.0 -golang.org/x/term v0.34.0 -golang.org/x/text v0.28.0 -golang.org/x/time v0.12.0 +go.yaml.in/yaml/v2 v2.4.3 +go.yaml.in/yaml/v3 v3.0.4 +golang.org/x/crypto v0.47.0 +golang.org/x/mod v0.32.0 +golang.org/x/net v0.49.0 +golang.org/x/oauth2 v0.34.0 +golang.org/x/sync v0.19.0 +golang.org/x/sys v0.40.0 +golang.org/x/term v0.39.0 +golang.org/x/text v0.33.0 +golang.org/x/time v0.14.0 +golang.org/x/tools v0.41.0 golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 google.golang.org/genproto v0.0.0-20250603155806-513f23925822 -google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 -google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a -google.golang.org/grpc v1.72.2 -google.golang.org/protobuf v1.36.7 +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c +google.golang.org/grpc v1.76.0 +google.golang.org/protobuf v1.36.10 gopkg.in/evanphx/json-patch.v4 v4.12.0 gopkg.in/go-playground/validator.v9 v9.30.2 gopkg.in/inf.v0 v0.9.1 -gopkg.in/natefinch/lumberjack.v2 v2.2.1 -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 -gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 -k8s.io/api v0.33.5 -k8s.io/apimachinery v0.33.5 -k8s.io/client-go v0.33.5 +k8s.io/api v0.34.3 +k8s.io/apiextensions-apiserver v0.34.3 +k8s.io/apimachinery v0.34.3 +k8s.io/client-go v0.34.3 k8s.io/klog v0.2.0 k8s.io/klog/v2 v2.130.1 -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff -k8s.io/utils v0.0.0-20241210054802-24370beab758 +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 +kubevirt.io/api v1.8.0-alpha.0 +kubevirt.io/containerized-data-importer-api v1.63.1 +kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 -sigs.k8s.io/knftables v0.0.18 -sigs.k8s.io/network-policy-api v0.1.5 +sigs.k8s.io/knftables v0.0.19 +sigs.k8s.io/network-policy-api v0.1.8-0.20260212153203-412bf65729a5 sigs.k8s.io/randfill v1.0.0 -sigs.k8s.io/structured-merge-diff/v4 v4.6.0 +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 sigs.k8s.io/yaml v1.6.0 diff --git a/cni-plugin/internal/pkg/azure/azure.go b/cni-plugin/internal/pkg/azure/azure.go index 2c1f0c29119..b67aec55041 100644 --- a/cni-plugin/internal/pkg/azure/azure.go +++ b/cni-plugin/internal/pkg/azure/azure.go @@ -16,14 +16,14 @@ func MutateConfigAdd(args *skel.CmdArgs, network AzureNetwork) error { logrus.Info("No Azure subnets defined - don't mutate config (add)") return nil } - var stdinData map[string]interface{} + var stdinData map[string]any var err error if err = json.Unmarshal(args.StdinData, &stdinData); err != nil { return err } // For now, we only support a single subnet. The data model supports multiple though. - stdinData["ipam"].(map[string]interface{})["subnet"] = network.Subnets[0] + stdinData["ipam"].(map[string]any)["subnet"] = network.Subnets[0] // Pack it back into the provided args. args.StdinData, err = json.Marshal(stdinData) @@ -47,7 +47,7 @@ func MutateConfigDel(args *skel.CmdArgs, network AzureNetwork, endpoint AzureEnd return nil } - var stdinData map[string]interface{} + var stdinData map[string]any var err error if err = json.Unmarshal(args.StdinData, &stdinData); err != nil { return err @@ -57,10 +57,10 @@ func MutateConfigDel(args *skel.CmdArgs, network AzureNetwork, endpoint AzureEnd // The azure-vnet-ipam plugin is not receptive to CIDR notation, so strip the prefix length // if it is present. splits := strings.Split(endpoint.Addresses[0], "/") - stdinData["ipam"].(map[string]interface{})["ipAddress"] = splits[0] + stdinData["ipam"].(map[string]any)["ipAddress"] = splits[0] // For now, we only support a single subnet. The data model supports multiple though. - stdinData["ipam"].(map[string]interface{})["subnet"] = network.Subnets[0] + stdinData["ipam"].(map[string]any)["subnet"] = network.Subnets[0] // Pack it back into the provided args. args.StdinData, err = json.Marshal(stdinData) diff --git a/cni-plugin/internal/pkg/azure/azure_suite_test.go b/cni-plugin/internal/pkg/azure/azure_suite_test.go index 9db907d5fb8..8fcb6c2658a 100644 --- a/cni-plugin/internal/pkg/azure/azure_suite_test.go +++ b/cni-plugin/internal/pkg/azure/azure_suite_test.go @@ -17,9 +17,8 @@ package azure import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestIpam(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../report/azure_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Azure Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../report/azure_suite.xml" + ginkgo.RunSpecs(t, "Azure Suite", suiteConfig, reporterConfig) } diff --git a/cni-plugin/internal/pkg/azure/azure_test.go b/cni-plugin/internal/pkg/azure/azure_test.go index 5b1950240ea..de6698defe8 100644 --- a/cni-plugin/internal/pkg/azure/azure_test.go +++ b/cni-plugin/internal/pkg/azure/azure_test.go @@ -18,7 +18,7 @@ import ( "os" "github.com/containernetworking/cni/pkg/skel" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/cni-plugin/internal/pkg/testutils/utils.go b/cni-plugin/internal/pkg/testutils/utils.go index 1eb427d0588..82f01cb3085 100644 --- a/cni-plugin/internal/pkg/testutils/utils.go +++ b/cni-plugin/internal/pkg/testutils/utils.go @@ -26,7 +26,7 @@ import ( "k8s.io/client-go/kubernetes" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapi "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/errors" @@ -143,7 +143,7 @@ func AddNode(c client.Interface, kc *kubernetes.Clientset, host string) error { log.WithField("node", host).WithError(err).Info("Node created") } else { // Otherwise, create it in Calico. - n := libapi.NewNode() + n := internalapi.NewNode() n.Name = host _, err = c.Nodes().Create(context.Background(), n, options.SetOptions{}) if err != nil { @@ -161,7 +161,7 @@ func DeleteNode(c client.Interface, kc *kubernetes.Clientset, host string) error log.WithError(err).WithField("node", host).Debug("Kubernetes node deleted") } else { // Otherwise, delete it in Calico. - n := libapi.NewNode() + n := internalapi.NewNode() n.Name = host _, err = c.Nodes().Delete(context.Background(), host, options.DeleteOptions{}) log.WithError(err).WithField("node", host).Debug("Calico node deleted") diff --git a/cni-plugin/internal/pkg/testutils/utils_linux.go b/cni-plugin/internal/pkg/testutils/utils_linux.go index e79b5af01aa..e64a1f62e2f 100644 --- a/cni-plugin/internal/pkg/testutils/utils_linux.go +++ b/cni-plugin/internal/pkg/testutils/utils_linux.go @@ -35,7 +35,7 @@ import ( "github.com/google/uuid" je "github.com/juju/errors" "github.com/mcuadros/go-version" - "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega/gexec" log "github.com/sirupsen/logrus" "github.com/vishvananda/netlink" diff --git a/cni-plugin/internal/pkg/testutils/utils_windows.go b/cni-plugin/internal/pkg/testutils/utils_windows.go index 4c86a12b01f..09385981b51 100644 --- a/cni-plugin/internal/pkg/testutils/utils_windows.go +++ b/cni-plugin/internal/pkg/testutils/utils_windows.go @@ -444,7 +444,7 @@ func NetworkPod( "Namespace": netns, }) d := windows.NewWindowsDataplane(conf, logger) - _, _, err = d.DoNetworking(ctx, calicoClient, args, result, "", nil, nil, nil) + _, _, err = d.DoNetworking(ctx, calicoClient, args, result, "", nil, nil, nil, false) return err } diff --git a/cni-plugin/internal/pkg/utils/network_linux.go b/cni-plugin/internal/pkg/utils/network_linux.go index 191b96ca179..5f1efd84695 100644 --- a/cni-plugin/internal/pkg/utils/network_linux.go +++ b/cni-plugin/internal/pkg/utils/network_linux.go @@ -22,11 +22,11 @@ import ( "github.com/sirupsen/logrus" "github.com/projectcalico/calico/cni-plugin/pkg/types" - api "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" calicoclient "github.com/projectcalico/calico/libcalico-go/lib/clientv3" ) -func updateHostLocalIPAMDataForOS(subnet string, ipamData map[string]interface{}) error { +func updateHostLocalIPAMDataForOS(subnet string, ipamData map[string]any) error { return nil } @@ -41,7 +41,7 @@ func RegisterDeletedWep(containerID string) error { func CheckForSpuriousDockerAdd(args *skel.CmdArgs, conf types.NetConf, epIDs WEPIdentifiers, - endpoint *api.WorkloadEndpoint, + endpoint *internalapi.WorkloadEndpoint, logger *logrus.Entry) (*cniv1.Result, error) { return nil, nil } diff --git a/cni-plugin/internal/pkg/utils/network_windows.go b/cni-plugin/internal/pkg/utils/network_windows.go index dab2eb7323b..ab935d0d432 100644 --- a/cni-plugin/internal/pkg/utils/network_windows.go +++ b/cni-plugin/internal/pkg/utils/network_windows.go @@ -27,7 +27,7 @@ import ( "github.com/projectcalico/calico/cni-plugin/internal/pkg/utils/cri" "github.com/projectcalico/calico/cni-plugin/pkg/dataplane/windows" "github.com/projectcalico/calico/cni-plugin/pkg/types" - api "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" calicoclient "github.com/projectcalico/calico/libcalico-go/lib/clientv3" ) @@ -201,7 +201,7 @@ func RegisterDeletedWep(containerID string) error { func CheckForSpuriousDockerAdd(args *skel.CmdArgs, conf types.NetConf, epIDs WEPIdentifiers, - endpoint *api.WorkloadEndpoint, + endpoint *internalapi.WorkloadEndpoint, logger *logrus.Entry) (*cniv1.Result, error) { var err error var result *cniv1.Result diff --git a/cni-plugin/internal/pkg/utils/utils.go b/cni-plugin/internal/pkg/utils/utils.go index 7d31c2654ef..b088454dd96 100644 --- a/cni-plugin/internal/pkg/utils/utils.go +++ b/cni-plugin/internal/pkg/utils/utils.go @@ -27,17 +27,17 @@ import ( "strconv" "strings" + "github.com/DeRuina/timberjack" "github.com/containernetworking/cni/pkg/skel" cnitypes "github.com/containernetworking/cni/pkg/types" cniv1 "github.com/containernetworking/cni/pkg/types/100" "github.com/containernetworking/plugins/pkg/ipam" "github.com/sirupsen/logrus" - "gopkg.in/natefinch/lumberjack.v2" "github.com/projectcalico/calico/cni-plugin/internal/pkg/azure" "github.com/projectcalico/calico/cni-plugin/pkg/types" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - api "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/names" cnet "github.com/projectcalico/calico/libcalico-go/lib/net" @@ -124,7 +124,7 @@ func MTUFromFile(filename string, conf types.NetConf) (int, error) { // CreateOrUpdate creates the WorkloadEndpoint if ResourceVersion is not specified, // or Update if it's specified. -func CreateOrUpdate(ctx context.Context, client client.Interface, wep *api.WorkloadEndpoint) (*api.WorkloadEndpoint, error) { +func CreateOrUpdate(ctx context.Context, client client.Interface, wep *internalapi.WorkloadEndpoint) (*internalapi.WorkloadEndpoint, error) { if wep.ResourceVersion != "" { return client.WorkloadEndpoints().Update(ctx, wep, options.SetOptions{}) } @@ -202,8 +202,10 @@ func AddIPAM(conf types.NetConf, args *skel.CmdArgs, logger *logrus.Entry) (*cni // and is the logical counterpart to AddIPAM. func DeleteIPAM(conf types.NetConf, args *skel.CmdArgs, logger *logrus.Entry) error { logger.Info("Calico CNI releasing IP address") - logger.WithFields(logrus.Fields{"paths": os.Getenv("CNI_PATH"), - "type": conf.IPAM.Type}).Debug("Looking for IPAM plugin in paths") + logger.WithFields(logrus.Fields{ + "paths": os.Getenv("CNI_PATH"), + "type": conf.IPAM.Type, + }).Debug("Looking for IPAM plugin in paths") var ae *azure.AzureEndpoint switch conf.IPAM.Type { @@ -213,14 +215,16 @@ func DeleteIPAM(conf types.NetConf, args *skel.CmdArgs, logger *logrus.Entry) er // It just needs a valid CIDR, but it doesn't have to be the CIDR associated with the host. dummyPodCidrv4 := "0.0.0.0/0" dummyPodCidrv6 := "::/0" - var stdinData map[string]interface{} + var stdinData map[string]any err := json.Unmarshal(args.StdinData, &stdinData) if err != nil { return err } - logger.WithFields(logrus.Fields{"podCidrv4": dummyPodCidrv4, - "podCidrv6": dummyPodCidrv6}).Info("Using dummy podCidrs to release the IPs") + logger.WithFields(logrus.Fields{ + "podCidrv4": dummyPodCidrv4, + "podCidrv6": dummyPodCidrv6, + }).Info("Using dummy podCidrs to release the IPs") getDummyPodCIDR := func() (string, string, error) { return dummyPodCidrv4, dummyPodCidrv6, nil } @@ -297,8 +301,8 @@ func DeleteIPAM(conf types.NetConf, args *skel.CmdArgs, logger *logrus.Entry) er // } // ... // } -func ReplaceHostLocalIPAMPodCIDRs(logger *logrus.Entry, stdinData map[string]interface{}, getPodCIDRs func() (string, string, error)) error { - ipamData, ok := stdinData["ipam"].(map[string]interface{}) +func ReplaceHostLocalIPAMPodCIDRs(logger *logrus.Entry, stdinData map[string]any, getPodCIDRs func() (string, string, error)) error { + ipamData, ok := stdinData["ipam"].(map[string]any) if !ok { return fmt.Errorf("failed to parse host-local IPAM data; was expecting a dict, not: %v", stdinData["ipam"]) } @@ -310,13 +314,13 @@ func ReplaceHostLocalIPAMPodCIDRs(logger *logrus.Entry, stdinData map[string]int // Newer versions store one or more subnets in the "ranges" list: untypedRanges := ipamData["ranges"] if untypedRanges != nil { - rangeSets, ok := untypedRanges.([]interface{}) + rangeSets, ok := untypedRanges.([]any) if !ok { return fmt.Errorf("failed to parse host-local IPAM ranges section; was expecting a list, not: %v", ipamData["ranges"]) } for _, urs := range rangeSets { - rs, ok := urs.([]interface{}) + rs, ok := urs.([]any) if !ok { return fmt.Errorf("failed to parse host-local IPAM range set; was expecting a list, not: %v", rs) } @@ -331,9 +335,9 @@ func ReplaceHostLocalIPAMPodCIDRs(logger *logrus.Entry, stdinData map[string]int return nil } -func replaceHostLocalIPAMPodCIDR(logger *logrus.Entry, rawIpamData interface{}, getPodCidrs func() (string, string, error)) error { +func replaceHostLocalIPAMPodCIDR(logger *logrus.Entry, rawIpamData any, getPodCidrs func() (string, string, error)) error { logrus.WithField("ipamData", rawIpamData).Debug("Examining IPAM data for usePodCidr") - ipamData, ok := rawIpamData.(map[string]interface{}) + ipamData, ok := rawIpamData.(map[string]any) if !ok { return fmt.Errorf("failed to parse host-local IPAM data; was expecting a dict, not: %v", rawIpamData) } @@ -379,7 +383,7 @@ func replaceHostLocalIPAMPodCIDR(logger *logrus.Entry, rawIpamData interface{}, } // This function will update host-local IPAM data based on input from cni.conf -func UpdateHostLocalIPAMDataForWindows(subnet string, ipamData map[string]interface{}) error { +func UpdateHostLocalIPAMDataForWindows(subnet string, ipamData map[string]any) error { if len(subnet) == 0 { return nil } @@ -413,7 +417,6 @@ func UpdateHostLocalIPAMDataForWindows(subnet string, ipamData map[string]interf } func getIPRanges(ip net.IP, ipnet *net.IPNet) (string, string) { - ip = ip.To4() // Mask the address ip.Mask(ipnet.Mask) @@ -442,7 +445,6 @@ func validateStartRange(startRange net.IP, expStartRange net.IP) (net.IP, error) return expStartRange, nil } return startRange, nil - } func validateEndRange(endRange net.IP, expEndRange net.IP) (net.IP, error) { @@ -485,7 +487,6 @@ func validateRangeOrSetDefault(rangeData string, expRange string, ipnet *net.IPN } // return default range return expRangeIP.String(), nil - } // ValidateNetworkName checks that the network name meets felix's expectations @@ -539,7 +540,7 @@ func AddIgnoreUnknownArgs() error { // CreateResultFromEndpoint takes a WorkloadEndpoint, extracts IP information // and populates that into a CNI Result. -func CreateResultFromEndpoint(wep *api.WorkloadEndpoint) (*cniv1.Result, error) { +func CreateResultFromEndpoint(wep *internalapi.WorkloadEndpoint) (*cniv1.Result, error) { result := &cniv1.Result{} for _, v := range wep.Spec.IPNetworks { parsedIPConfig := cniv1.IPConfig{} @@ -559,7 +560,7 @@ func CreateResultFromEndpoint(wep *api.WorkloadEndpoint) (*cniv1.Result, error) // PopulateEndpointNets takes a WorkloadEndpoint and a CNI Result, extracts IP address and mask // and populates that information into the WorkloadEndpoint. -func PopulateEndpointNets(wep *api.WorkloadEndpoint, result *cniv1.Result) error { +func PopulateEndpointNets(wep *internalapi.WorkloadEndpoint, result *cniv1.Result) error { var copyIpNet net.IPNet if len(result.IPs) == 0 { return errors.New("IPAM plugin did not return any IP addresses") @@ -701,6 +702,23 @@ func CreateClient(conf types.NetConf) (client.Interface, error) { return nil, err } } + if conf.CalicoAPIGroup != "" { + if err := os.Setenv("CALICO_API_GROUP", string(conf.CalicoAPIGroup)); err != nil { + return nil, err + } + } + + if conf.QPS != 0 { + if err := os.Setenv("K8S_CLIENT_QPS", fmt.Sprintf("%d", conf.QPS)); err != nil { + return nil, err + } + } + + if conf.Burst != 0 { + if err := os.Setenv("K8S_CLIENT_BURST", fmt.Sprintf("%d", conf.Burst)); err != nil { + return nil, err + } + } // Load the client config from the current environment. clientConfig, err := apiconfig.LoadClientConfig("") @@ -748,17 +766,19 @@ func ConfigureLogging(conf types.NetConf) { // Set the log output to write to a log file if specified. if conf.LogFilePath != "" { // Create the path for the log file if it does not exist - err := os.MkdirAll(filepath.Dir(conf.LogFilePath), 0755) + err := os.MkdirAll(filepath.Dir(conf.LogFilePath), 0o755) if err != nil { logrus.WithError(err).Errorf("Failed to create path for CNI log file: %v", filepath.Dir(conf.LogFilePath)) } // Create file logger with log file rotation. - fileLogger := &lumberjack.Logger{ - Filename: conf.LogFilePath, - MaxSize: 100, - MaxAge: 30, - MaxBackups: 10, + fileLogger := &timberjack.Logger{ + Filename: conf.LogFilePath, + FileMode: 0o644, + Compression: "zstd", + MaxSize: 100, + MaxAge: 30, + MaxBackups: 10, } // Set the max size if exists. Defaults to 100 MB. diff --git a/cni-plugin/internal/pkg/utils/utils_test.go b/cni-plugin/internal/pkg/utils/utils_test.go index 37c01973c56..f80b491c6b3 100644 --- a/cni-plugin/internal/pkg/utils/utils_test.go +++ b/cni-plugin/internal/pkg/utils/utils_test.go @@ -17,8 +17,7 @@ package utils_test import ( "os" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/cni-plugin/internal/pkg/utils" @@ -26,16 +25,16 @@ import ( ) var _ = Describe("utils", func() { - table.DescribeTable("Mesos Labels", func(raw, sanitized string) { + DescribeTable("Mesos Labels", func(raw, sanitized string) { result := utils.SanitizeMesosLabel(raw) Expect(result).To(Equal(sanitized)) }, - table.Entry("valid", "k", "k"), - table.Entry("dashes", "-my-val", "my-val"), - table.Entry("double periods", "$my..val", "my.val"), - table.Entry("special chars", "m$y.val", "m-y.val"), - table.Entry("slashes", "//my/val/", "my.val"), - table.Entry("mix of special chars", + Entry("valid", "k", "k"), + Entry("dashes", "-my-val", "my-val"), + Entry("double periods", "$my..val", "my.val"), + Entry("special chars", "m$y.val", "m-y.val"), + Entry("slashes", "//my/val/", "my.val"), + Entry("mix of special chars", "some_val-with.lots*of^weird#characters", "some_val-with.lots-of-weird-characters"), ) }) diff --git a/cni-plugin/internal/pkg/utils/winpol/windows_policy.go b/cni-plugin/internal/pkg/utils/winpol/windows_policy.go index 327f0ef1c1a..048266eed4f 100644 --- a/cni-plugin/internal/pkg/utils/winpol/windows_policy.go +++ b/cni-plugin/internal/pkg/utils/winpol/windows_policy.go @@ -46,7 +46,7 @@ func CalculateEndpointPolicies( found := false for _, inPol := range inputPols { // Decode the raw policy as a dict so we can inspect it without losing any fields. - decoded := map[string]interface{}{} + decoded := map[string]any{} err := json.Unmarshal(inPol, &decoded) if err != nil { logger.WithError(err).Error("GetHNSEndpointPolicies() returned bad JSON") @@ -71,7 +71,7 @@ func CalculateEndpointPolicies( continue } - excList, _ := decoded["ExceptionList"].([]interface{}) + excList, _ := decoded["ExceptionList"].([]any) excList = appendCIDRs(excList, extraNATExceptions) decoded["ExceptionList"] = excList outPol, err = json.Marshal(decoded) @@ -100,7 +100,7 @@ func CalculateEndpointPolicies( } if !found && natOutgoing && len(extraNATExceptions) > 0 { exceptions := appendCIDRs(nil, extraNATExceptions) - dict := map[string]interface{}{ + dict := map[string]any{ "Type": "OutBoundNAT", "ExceptionList": exceptions, } @@ -148,7 +148,7 @@ func CalculateEndpointPolicies( // []byte(`{"ExceptionList":["10.96.0.0/12","192.168.0.0/16"]}`), // ), // } -func convertToHcnEndpointPolicy(policy map[string]interface{}) (hcn.EndpointPolicy, error) { +func convertToHcnEndpointPolicy(policy map[string]any) (hcn.EndpointPolicy, error) { hcnPolicy := hcn.EndpointPolicy{} // Get v2 policy type. @@ -169,7 +169,7 @@ func convertToHcnEndpointPolicy(policy map[string]interface{}) (hcn.EndpointPoli return hcnPolicy, nil } -func appendCIDRs(excList []interface{}, extraNATExceptions []*net.IPNet) []interface{} { +func appendCIDRs(excList []any, extraNATExceptions []*net.IPNet) []any { for _, cidr := range extraNATExceptions { maskedCIDR := &net.IPNet{ IP: cidr.IP.Mask(cidr.Mask), diff --git a/cni-plugin/pkg/dataplane/dataplane.go b/cni-plugin/pkg/dataplane/dataplane.go index 8bca929068e..084b15ad483 100644 --- a/cni-plugin/pkg/dataplane/dataplane.go +++ b/cni-plugin/pkg/dataplane/dataplane.go @@ -26,7 +26,7 @@ import ( "github.com/projectcalico/calico/cni-plugin/pkg/dataplane/grpc" "github.com/projectcalico/calico/cni-plugin/pkg/types" - api "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" calicoclient "github.com/projectcalico/calico/libcalico-go/lib/clientv3" ) @@ -38,8 +38,9 @@ type Dataplane interface { result *cniv1.Result, desiredVethName string, routes []*net.IPNet, - endpoint *api.WorkloadEndpoint, + endpoint *internalapi.WorkloadEndpoint, annotations map[string]string, + skipHostSideRoutes bool, ) (hostVethName, contVethMAC string, err error) CleanUpNamespace(args *skel.CmdArgs) error diff --git a/cni-plugin/pkg/dataplane/grpc/grpc_dataplane.go b/cni-plugin/pkg/dataplane/grpc/grpc_dataplane.go index e30e33a5f2b..b418fb827aa 100644 --- a/cni-plugin/pkg/dataplane/grpc/grpc_dataplane.go +++ b/cni-plugin/pkg/dataplane/grpc/grpc_dataplane.go @@ -30,7 +30,7 @@ import ( "github.com/projectcalico/calico/cni-plugin/pkg/dataplane/grpc/proto" "github.com/projectcalico/calico/cni-plugin/pkg/types" - api "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" calicoclient "github.com/projectcalico/calico/libcalico-go/lib/clientv3" ) @@ -80,8 +80,9 @@ func (d *grpcDataplane) DoNetworking( result *cniv1.Result, desiredVethName string, routes []*net.IPNet, - endpoint *api.WorkloadEndpoint, + endpoint *internalapi.WorkloadEndpoint, annotations map[string]string, + skipHostSideRoutes bool, ) (ifName, contTapMAC string, err error) { d.logger.Infof("Connecting to GRPC backend server at %s", d.socket) conn, err := grpc.NewClient(d.socket, grpc.WithTransportCredentials(insecure.NewCredentials())) diff --git a/cni-plugin/pkg/dataplane/grpc/proto/cnibackend.pb.go b/cni-plugin/pkg/dataplane/grpc/proto/cnibackend.pb.go index eed75661bda..f8c4aa20a0b 100644 --- a/cni-plugin/pkg/dataplane/grpc/proto/cnibackend.pb.go +++ b/cni-plugin/pkg/dataplane/grpc/proto/cnibackend.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 -// protoc v6.32.0 +// protoc-gen-go v1.36.11 +// protoc v6.33.4 // source: cnibackend.proto package proto diff --git a/cni-plugin/pkg/dataplane/grpc/proto/cnibackend_grpc.pb.go b/cni-plugin/pkg/dataplane/grpc/proto/cnibackend_grpc.pb.go index 30717bb6e33..bd5a2e65809 100644 --- a/cni-plugin/pkg/dataplane/grpc/proto/cnibackend_grpc.pb.go +++ b/cni-plugin/pkg/dataplane/grpc/proto/cnibackend_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.5.1 -// - protoc v6.32.0 +// - protoc-gen-go-grpc v1.6.0 +// - protoc v6.33.4 // source: cnibackend.proto package proto @@ -77,10 +77,10 @@ type CniDataplaneServer interface { type UnimplementedCniDataplaneServer struct{} func (UnimplementedCniDataplaneServer) Add(context.Context, *AddRequest) (*AddReply, error) { - return nil, status.Errorf(codes.Unimplemented, "method Add not implemented") + return nil, status.Error(codes.Unimplemented, "method Add not implemented") } func (UnimplementedCniDataplaneServer) Del(context.Context, *DelRequest) (*DelReply, error) { - return nil, status.Errorf(codes.Unimplemented, "method Del not implemented") + return nil, status.Error(codes.Unimplemented, "method Del not implemented") } func (UnimplementedCniDataplaneServer) mustEmbedUnimplementedCniDataplaneServer() {} func (UnimplementedCniDataplaneServer) testEmbeddedByValue() {} @@ -93,7 +93,7 @@ type UnsafeCniDataplaneServer interface { } func RegisterCniDataplaneServer(s grpc.ServiceRegistrar, srv CniDataplaneServer) { - // If the following call pancis, it indicates UnimplementedCniDataplaneServer was + // If the following call panics, it indicates UnimplementedCniDataplaneServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. diff --git a/cni-plugin/pkg/dataplane/grpc/test_server_linux.go b/cni-plugin/pkg/dataplane/grpc/test_server_linux.go index 3757c08511c..24d8442960d 100644 --- a/cni-plugin/pkg/dataplane/grpc/test_server_linux.go +++ b/cni-plugin/pkg/dataplane/grpc/test_server_linux.go @@ -35,7 +35,7 @@ type TestServer struct { retval bool contMac string - Received chan interface{} + Received chan any lis net.Listener grpcServer *grpc.Server @@ -111,7 +111,7 @@ func StartTestServer(socket string, retval bool, contMac string) (s *TestServer, s = &TestServer{ retval: retval, contMac: contMac, - Received: make(chan interface{}, 1), + Received: make(chan any, 1), grpcServer: grpc.NewServer(), } diff --git a/cni-plugin/pkg/dataplane/linux/dataplane_linux.go b/cni-plugin/pkg/dataplane/linux/dataplane_linux.go index 7f51613a8f9..c7d74dd49fc 100644 --- a/cni-plugin/pkg/dataplane/linux/dataplane_linux.go +++ b/cni-plugin/pkg/dataplane/linux/dataplane_linux.go @@ -20,6 +20,7 @@ import ( "io" "net" "os" + "slices" "syscall" "time" @@ -31,20 +32,26 @@ import ( "github.com/vishvananda/netlink" "github.com/projectcalico/calico/cni-plugin/pkg/types" - api "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" calicoclient "github.com/projectcalico/calico/libcalico-go/lib/clientv3" + cnet "github.com/projectcalico/calico/libcalico-go/lib/net" "github.com/projectcalico/calico/libcalico-go/lib/netlinkutils" ) -type linuxDataplane struct { +var ( + hostSideMAC = net.HardwareAddr{0xee, 0xee, 0xee, 0xee, 0xee, 0xee} + hostSideMACAsIPv6LL = cnet.MustMACToIPv6LinkLocal(hostSideMAC) +) + +type LinuxDataplane struct { allowIPForwarding bool mtu int queues int logger *logrus.Entry } -func NewLinuxDataplane(conf types.NetConf, logger *logrus.Entry) *linuxDataplane { - return &linuxDataplane{ +func NewLinuxDataplane(conf types.NetConf, logger *logrus.Entry) *LinuxDataplane { + return &LinuxDataplane{ allowIPForwarding: conf.ContainerSettings.AllowIPForwarding, mtu: conf.MTU, queues: conf.NumQueues, @@ -55,15 +62,16 @@ func NewLinuxDataplane(conf types.NetConf, logger *logrus.Entry) *linuxDataplane // DoNetworking sets up the network for the container's netns. It creates the // veth pair with one end in the host network namespace and the other in the // container's network namespace. It also sets up addresses and routes. -func (d *linuxDataplane) DoNetworking( +func (d *LinuxDataplane) DoNetworking( ctx context.Context, calicoClient calicoclient.Interface, args *skel.CmdArgs, result *cniv1.Result, desiredVethName string, routes []*net.IPNet, - endpoint *api.WorkloadEndpoint, + endpoint *internalapi.WorkloadEndpoint, annotations map[string]string, + skipHostSideRoutes bool, ) (hostVethName, contVethMAC string, err error) { hostVethName = desiredVethName d.logger.Infof("Setting the host side veth name to %s", hostVethName) @@ -92,10 +100,15 @@ func (d *linuxDataplane) DoNetworking( return "", "", fmt.Errorf("failed to lookup %q: %v", hostVethName, err) } - // Add the routes to host veth in the host namespace. - err = SetupRoutes(hostNlHandle, hostVeth, result) - if err != nil { - return "", "", fmt.Errorf("error adding host side routes for interface: %s, error: %s", hostVeth.Attrs().Name, err) + // Add the routes to host veth in the host namespace unless explicitly skipped + // (e.g., for KubeVirt migration target pods where Felix will program the route) + if !skipHostSideRoutes { + err = SetupRoutes(hostNlHandle, hostVeth, result) + if err != nil { + return "", "", fmt.Errorf("error adding host side routes for interface: %s, error: %s", hostVeth.Attrs().Name, err) + } + } else { + d.logger.Info("Skipping host-side route setup (skipHostSideRoutes=true)") } return hostVethName, contVethMAC, err @@ -105,7 +118,7 @@ func (d *linuxDataplane) DoNetworking( // // Note: this method is also used by the Felix FV test-workload to create // a simulated workload. -func (d *linuxDataplane) DoWorkloadNetnsSetUp( +func (d *LinuxDataplane) DoWorkloadNetnsSetUp( hostNlHandle *netlink.Handle, netnsPath string, ipAddrs []*cniv1.IPConfig, @@ -134,6 +147,7 @@ func (d *linuxDataplane) DoWorkloadNetnsSetUp( PeerNamespace: netlink.NsFd(int(hostNS.Fd())), } + var expectedHostSideIPv6Addr net.IP if err := netlink.LinkAdd(veth); err != nil { d.logger.Errorf("Error adding veth %+v: %s", veth, err) return err @@ -145,13 +159,32 @@ func (d *linuxDataplane) DoWorkloadNetnsSetUp( return err } - if mac, err := net.ParseMAC("EE:EE:EE:EE:EE:EE"); err != nil { - d.logger.Infof("failed to parse MAC Address: %v. Using kernel generated MAC.", err) - } else { - // Set the MAC address on the host side interface so the kernel does not - // have to generate a persistent address which fails some times. - if err = hostNlHandle.LinkSetHardwareAddr(hostVeth, mac); err != nil { + macUpdateTimeout := time.After(5 * time.Second) + for { + // We create the veth with random MAC address because the kernel sometimes + // fails to honor a MAC that we set on the LinkAdd request. In addition, + // LinkSetHardwareAddr sometimes fails silently so we retry. + if err = hostNlHandle.LinkSetHardwareAddr(hostVeth, hostSideMAC); err != nil { d.logger.Warnf("failed to Set MAC of %q: %v. Using kernel generated MAC.", hostVethName, err) + break + } + + // Read back the MAC to check that the set actually worked. + hostVeth, err = hostNlHandle.LinkByName(hostVethName) + if err != nil { + err = fmt.Errorf("failed to lookup %q: %v", hostVethName, err) + return err + } + if slices.Equal(hostVeth.Attrs().HardwareAddr, hostSideMAC) { + d.logger.Info("Read back correct host-side MAC.") + expectedHostSideIPv6Addr = hostSideMACAsIPv6LL + break + } + d.logger.Warnf("Host-side veth MAC %s does not match expected %s. Retrying...", hostVeth.Attrs().HardwareAddr, hostSideMAC) + select { + case <-macUpdateTimeout: + return fmt.Errorf("timed out waiting for host veth %q to accept MAC %s", hostVethName, hostSideMAC.String()) + case <-time.After(100 * time.Millisecond): } } @@ -256,7 +289,7 @@ func (d *linuxDataplane) DoWorkloadNetnsSetUp( d.logger.WithField("route", r).Debug("Skipping non-IPv4 route") continue } - d.logger.WithField("route", r).Debug("Adding IPv4 route") + d.logger.WithField("route", r).Info("Adding IPv4 route (inside container)") if err = ip.AddRoute(r, gw, contVeth); err != nil { return fmt.Errorf("failed to add IPv4 route for %v via %v: %v", r, gw, err) } @@ -283,7 +316,11 @@ func (d *linuxDataplane) DoWorkloadNetnsSetUp( // after these sysctls var err error var addresses []netlink.Addr - for i := 0; i < 10; i++ { + for i := range 10 { + if i > 0 { + time.Sleep(50 * time.Millisecond) + d.logger.Info("Retry lookup of host-side IPv6 link local address...") + } // No need to add a dummy next hop route as the host veth device will already have an IPv6 // link local address that can be used as a next hop. // Just fetch the address of the host end of the veth and use it as the next hop. @@ -297,16 +334,41 @@ func (d *linuxDataplane) DoWorkloadNetnsSetUp( // support IPv6. Since a IPv6 address has been allocated that can't be used, // return an error. err = fmt.Errorf("failed to get IPv6 addresses for host side of the veth pair") + } else { + if expectedHostSideIPv6Addr != nil && !expectedHostSideIPv6Addr.Equal(addresses[0].IP) { + // [Shaun] I think we hit this case if the kernel hasn't finished processing + // the MAC update above when we bring the device up. It uses the original + // random MAC to generate the IPv6 link local address. To work around that, + // toggle the device down/up, which refreshes the calculation of the address. + d.logger.Warnf("Host-side veth received unexpected link-local IP; toggling it down/up to reset the IP.") + if err = hostNlHandle.LinkSetDown(hostVeth); err != nil { + return fmt.Errorf("failed to set %q down: %w", hostVethName, err) + } + // Defensive: try to set the MAC again in case it silently failed/got reverted somehow. + if err = hostNlHandle.LinkSetHardwareAddr(hostVeth, hostSideMAC); err != nil { + // Surprising to hit this because we must have succeeded above. + d.logger.Warnf("Failed to set the host-side MAC (while trying to reset the link local IP).") + } + // Let's give the kernel time to settle this time. + time.Sleep(50 * time.Millisecond) + if err = hostNlHandle.LinkSetUp(hostVeth); err != nil { + return fmt.Errorf("failed to set %q down: %w", hostVethName, err) + } + // Leave an error behind so that we retry the loop. + err = fmt.Errorf("host-side veth got wrong link-local IP address: %s", addresses[0].IP) + } } - if err == nil { - break + + if err != nil { + continue } - d.logger.Infof("No IPv6 set on interface, retrying..") - time.Sleep(50 * time.Millisecond) + d.logger.Infof("Host-side veth link-local IP found: %s", addresses[0].IP.String()) + break } if err != nil { + d.logger.Errorf("Gave up waiting for host-side link local address: %s.", err) return err } @@ -317,7 +379,7 @@ func (d *linuxDataplane) DoWorkloadNetnsSetUp( d.logger.WithField("route", r).Debug("Skipping non-IPv6 route") continue } - d.logger.WithField("route", r).Debug("Adding IPv6 route") + d.logger.WithField("route", r).Info("Adding IPv6 route (inside container)") if err = ip.AddRoute(r, hostIPv6Addr, contVeth); err != nil { return fmt.Errorf("failed to add IPv6 route for %v via %v: %v", r, hostIPv6Addr, err) } @@ -427,7 +489,7 @@ func SetupRoutes(hostNlHandle *netlink.Handle, hostVeth netlink.Link, result *cn } // configureSysctls configures necessary sysctls required for the host side of the veth pair for IPv4 and/or IPv6. -func (d *linuxDataplane) configureSysctls(hostVethName string, hasIPv4, hasIPv6 bool) error { +func (d *LinuxDataplane) configureSysctls(hostVethName string, hasIPv4, hasIPv6 bool) error { var err error if hasIPv4 { @@ -496,7 +558,7 @@ func (d *linuxDataplane) configureSysctls(hostVethName string, hasIPv4, hasIPv6 } // configureContainerSysctls configures necessary sysctls required inside the container netns. -func (d *linuxDataplane) configureContainerSysctls(hasIPv4, hasIPv6 bool) error { +func (d *LinuxDataplane) configureContainerSysctls(hasIPv4, hasIPv6 bool) error { // If an IPv4 address is assigned, then configure IPv4 sysctls. if hasIPv4 { if d.allowIPForwarding { @@ -545,7 +607,7 @@ func writeProcSys(path, value string) error { return err } -func (d *linuxDataplane) CleanUpNamespace(args *skel.CmdArgs) error { +func (d *LinuxDataplane) CleanUpNamespace(args *skel.CmdArgs) error { // Only try to delete the device if a namespace was passed in. logCtx := d.logger.WithFields(logrus.Fields{ "netns": args.Netns, diff --git a/cni-plugin/pkg/dataplane/windows/dataplane_windows.go b/cni-plugin/pkg/dataplane/windows/dataplane_windows.go index 81bfc17327d..74d0e4162d4 100644 --- a/cni-plugin/pkg/dataplane/windows/dataplane_windows.go +++ b/cni-plugin/pkg/dataplane/windows/dataplane_windows.go @@ -38,7 +38,7 @@ import ( "github.com/projectcalico/calico/cni-plugin/internal/pkg/utils/cri" "github.com/projectcalico/calico/cni-plugin/internal/pkg/utils/winpol" "github.com/projectcalico/calico/cni-plugin/pkg/types" - api "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" calicoclient "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/options" "github.com/projectcalico/calico/libcalico-go/lib/winutils" @@ -138,8 +138,9 @@ func (d *windowsDataplane) DoNetworking( result *cniv1.Result, desiredVethName string, routes []*net.IPNet, - endpoint *api.WorkloadEndpoint, + endpoint *internalapi.WorkloadEndpoint, annotations map[string]string, + skipHostSideRoutes bool, ) (hostVethName, contVethMAC string, err error) { hostVethName = desiredVethName if len(routes) > 0 { diff --git a/cni-plugin/pkg/install/install.go b/cni-plugin/pkg/install/install.go index 61e1b426a07..c5bd9084aaf 100644 --- a/cni-plugin/pkg/install/install.go +++ b/cni-plugin/pkg/install/install.go @@ -19,8 +19,10 @@ import ( "encoding/json" "fmt" "io" + "net" "os" "runtime" + "slices" "strconv" "strings" "time" @@ -61,15 +63,8 @@ type config struct { ShouldSleep bool `envconfig:"SLEEP" default:"true"` ServiceAccountToken []byte -} -func (c config) skipBinary(binary string) bool { - for _, name := range c.SkipCNIBinaries { - if name == binary { - return true - } - } - return false + CalicoAPIGroup string `envconfig:"CALICO_API_GROUP" default:"crd.projectcalico.org/v1"` } func getEnv(env, def string) string { @@ -202,7 +197,7 @@ func Install(version string) error { if binary.Name() == "install" || binary.Name() == "install.exe" { continue } - if c.skipBinary(binary.Name()) { + if slices.Contains(c.SkipCNIBinaries, binary.Name()) { continue } if fileExists(target) && !c.UpdateCNIBinaries { @@ -300,7 +295,7 @@ func Install(version string) error { } func isValidJSON(s string) error { - var js map[string]interface{} + var js map[string]any return json.Unmarshal([]byte(s), &js) } @@ -355,6 +350,8 @@ func writeCNIConfig(c config) { netconf = strings.ReplaceAll(netconf, "__SERVICEACCOUNT_TOKEN__", string(c.ServiceAccountToken)) + netconf = strings.ReplaceAll(netconf, "__CALICO_API_GROUP__", string(c.CalicoAPIGroup)) + // Replace etcd datastore variables. hostSecretsDir := c.CNINetDir + "/calico-tls" if fileExists(winutils.GetHostPath("/host/etc/cni/net.d/calico-tls/etcd-cert")) { @@ -472,38 +469,53 @@ func copyFileAndPermissions(src, dst string) (err error) { } func writeKubeconfig(kubecfg *rest.Config) { + clientset, err := cni.BuildClientSet() + if err != nil { + logrus.WithError(err).Fatal("Unable to create client for generating CNI token") + } + tr := cni.NewTokenRefresher(clientset, cni.NamespaceOfUsedServiceAccount(), cni.CNIServiceAccountName()) + tu, err := tr.UpdateToken() + if err != nil { + logrus.WithError(err).Fatal("Unable to create token for CNI kubeconfig") + } + + data := calculateKubeconfig(kubecfg, tu.Token) + + if err := os.WriteFile(winutils.GetHostPath("/host/etc/cni/net.d/calico-kubeconfig"), []byte(data), 0o600); err != nil { + logrus.Fatal(err) + } +} + +func calculateKubeconfig(kubecfg *rest.Config, token string) string { data := `# Kubeconfig file for Calico CNI plugin. apiVersion: v1 kind: Config clusters: - name: local cluster: - server: __KUBERNETES_SERVICE_PROTOCOL__://[__KUBERNETES_SERVICE_HOST__]:__KUBERNETES_SERVICE_PORT__ + server: __KUBERNETES_SERVICE_PROTOCOL__://__KUBERNETES_SERVICE_ENDPOINT__ __TLS_CFG__ users: - name: calico user: - token: TOKEN + token: __TOKEN__ contexts: - name: calico-context context: cluster: local user: calico -current-context: calico-context` - - clientset, err := cni.BuildClientSet() - if err != nil { - logrus.WithError(err).Fatal("Unable to create client for generating CNI token") - } - tr := cni.NewTokenRefresher(clientset, cni.NamespaceOfUsedServiceAccount(), cni.CNIServiceAccountName()) - tu, err := tr.UpdateToken() - if err != nil { - logrus.WithError(err).Fatal("Unable to create token for CNI kubeconfig") - } - data = strings.Replace(data, "TOKEN", tu.Token, 1) - data = strings.ReplaceAll(data, "__KUBERNETES_SERVICE_PROTOCOL__", getEnv("KUBERNETES_SERVICE_PROTOCOL", "https")) - data = strings.ReplaceAll(data, "__KUBERNETES_SERVICE_HOST__", getEnv("KUBERNETES_SERVICE_HOST", "")) - data = strings.ReplaceAll(data, "__KUBERNETES_SERVICE_PORT__", getEnv("KUBERNETES_SERVICE_PORT", "")) +current-context: calico-context +` + data = strings.ReplaceAll(data, "__TOKEN__", token) + k8sProto := getEnv("KUBERNETES_SERVICE_PROTOCOL", "https") + if k8sProto == "" { + k8sProto = "https" + } + data = strings.ReplaceAll(data, "__KUBERNETES_SERVICE_PROTOCOL__", k8sProto) + k8sHost := getEnv("KUBERNETES_SERVICE_HOST", "") + k8sPort := getEnv("KUBERNETES_SERVICE_PORT", "") + k8sEndpoint := net.JoinHostPort(k8sHost, k8sPort) + data = strings.ReplaceAll(data, "__KUBERNETES_SERVICE_ENDPOINT__", k8sEndpoint) skipTLSVerify := os.Getenv("SKIP_TLS_VERIFY") if skipTLSVerify == "true" { @@ -512,10 +524,7 @@ current-context: calico-context` ca := "certificate-authority-data: " + base64.StdEncoding.EncodeToString(kubecfg.CAData) data = strings.ReplaceAll(data, "__TLS_CFG__", ca) } - - if err := os.WriteFile(winutils.GetHostPath("/host/etc/cni/net.d/calico-kubeconfig"), []byte(data), 0o600); err != nil { - logrus.Fatal(err) - } + return data } // destinationUptoDate compares the given files and returns diff --git a/cni-plugin/pkg/install/install_linux.go b/cni-plugin/pkg/install/install_linux.go index 92a75055c40..d2c796f7d47 100644 --- a/cni-plugin/pkg/install/install_linux.go +++ b/cni-plugin/pkg/install/install_linux.go @@ -31,7 +31,8 @@ func defaultNetConf() string { "ipam": {"type": "calico-ipam"}, "policy": {"type": "k8s"}, "kubernetes": {"kubeconfig": "__KUBECONFIG_FILEPATH__"}, - "require_mtu_file": __REQUIRE_MTU_FILE__ + "require_mtu_file": __REQUIRE_MTU_FILE__, + "calico_api_group": "__CALICO_API_GROUP__" }, { "type": "portmap", diff --git a/cni-plugin/pkg/install/install_suite_test.go b/cni-plugin/pkg/install/install_suite_test.go index a6722e98b80..f999e07eeeb 100644 --- a/cni-plugin/pkg/install/install_suite_test.go +++ b/cni-plugin/pkg/install/install_suite_test.go @@ -5,9 +5,8 @@ package install import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -17,7 +16,8 @@ func init() { } func TestInstall(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/install_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Install Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/install_suite.xml" + ginkgo.RunSpecs(t, "Install Suite", suiteConfig, reporterConfig) } diff --git a/cni-plugin/pkg/install/install_test.go b/cni-plugin/pkg/install/install_test.go index f4aa116fa5e..e95f5226184 100644 --- a/cni-plugin/pkg/install/install_test.go +++ b/cni-plugin/pkg/install/install_test.go @@ -3,18 +3,21 @@ package install import ( "context" + "encoding/base64" "fmt" "math/rand" "os" "os/exec" "strings" + "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd/api" ) @@ -33,7 +36,8 @@ var expectedDefaultConfig string = `{ "ipam": {"type": "calico-ipam"}, "policy": {"type": "k8s"}, "kubernetes": {"kubeconfig": "/etc/cni/net.d/calico-kubeconfig"}, - "require_mtu_file": false + "require_mtu_file": false, + "calico_api_group": "crd.projectcalico.org/v1" }, { "type": "portmap", @@ -167,15 +171,15 @@ var _ = Describe("CNI installation tests", func() { Expect(err).NotTo(HaveOccurred()) // Make subdirectories for where we expect binaries and config to be installed. - err = os.MkdirAll(tempDir+"/bin", 0755) + err = os.MkdirAll(tempDir+"/bin", 0o755) if err != nil { Fail("Failed to create directory tmp/bin") } - err = os.MkdirAll(tempDir+"/net.d", 0755) + err = os.MkdirAll(tempDir+"/net.d", 0o755) if err != nil { Fail("Failed to create directory tmp/net.d") } - err = os.MkdirAll(tempDir+"/serviceaccount", 0755) + err = os.MkdirAll(tempDir+"/serviceaccount", 0o755) if err != nil { Fail("Failed to create directory tmp/serviceaccount") } @@ -183,7 +187,7 @@ var _ = Describe("CNI installation tests", func() { // Create token file for the Kubernetes client. k8sSecret := []byte("my-secret-key") tokenFile := fmt.Sprintf("%s/serviceaccount/token", tempDir) - err = os.WriteFile(tokenFile, k8sSecret, 0755) + err = os.WriteFile(tokenFile, k8sSecret, 0o755) if err != nil { Fail(fmt.Sprintf("Failed to write k8s secret file: %v", err)) } @@ -212,15 +216,15 @@ yvQ/7I8lUKV1hMeCWc2k/x146B/gEgyDl1zUNnJZ/hrKmXqjQy3dkj4HzBePHYND PuB/TL+u2y+iQUyXxLy3 -----END CERTIFICATE-----`) caFile := fmt.Sprintf("%s/serviceaccount/ca.crt", tempDir) - err = os.WriteFile(caFile, k8sCA, 0755) + err = os.WriteFile(caFile, k8sCA, 0o755) if err != nil { Fail(fmt.Sprintf("Failed to write k8s CA file for test: %v", err)) } // Create namespace file for token refresh k8sNamespace := []byte("kube-system") - var namespaceFile = fmt.Sprintf("%s/serviceaccount/namespace", tempDir) - err = os.WriteFile(namespaceFile, k8sNamespace, 0755) + namespaceFile := fmt.Sprintf("%s/serviceaccount/namespace", tempDir) + err = os.WriteFile(namespaceFile, k8sNamespace, 0o755) if err != nil { Fail(fmt.Sprintf("Failed to write k8s namespace file: %v", err)) } @@ -323,7 +327,7 @@ PuB/TL+u2y+iQUyXxLy3 // Write the alternate configuration to disk so it can be picked up by // the CNI container. altConfigFile := tempDir + "/net.d/alternate-config" - err := os.WriteFile(altConfigFile, []byte(expectedAlternateConfig), 0755) + err := os.WriteFile(altConfigFile, []byte(expectedAlternateConfig), 0o755) Expect(err).NotTo(HaveOccurred()) err = runCniContainer( tempDir, true, @@ -363,12 +367,12 @@ PuB/TL+u2y+iQUyXxLy3 Context("copying /calico-secrets", func() { var err error BeforeEach(func() { - err = os.MkdirAll(tempDir+"/certs", 0755) + err = os.MkdirAll(tempDir+"/certs", 0o755) Expect(err).NotTo(HaveOccurred()) }) It("Should not crash or copy when having a hidden file", func() { - err = os.WriteFile(tempDir+"/certs/.hidden", []byte("doesn't matter"), 0644) + err = os.WriteFile(tempDir+"/certs/.hidden", []byte("doesn't matter"), 0o644) Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Failed to write hidden file: %v", err)) err = runCniContainer(tempDir, true, "-v", tempDir+"/certs:/calico-secrets") Expect(err).NotTo(HaveOccurred()) @@ -376,7 +380,7 @@ PuB/TL+u2y+iQUyXxLy3 Expect(err).To(HaveOccurred()) }) It("Should copy a non-hidden file", func() { - err = os.WriteFile(tempDir+"/certs/etcd-cert", []byte("doesn't matter"), 0644) + err = os.WriteFile(tempDir+"/certs/etcd-cert", []byte("doesn't matter"), 0o644) Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Failed to write file: %v", err)) err = runCniContainer(tempDir, true, "-v", tempDir+"/certs:/calico-secrets", "-e", "CNI_NETWORK_CONFIG={\"etcd_cert\": \"__ETCD_CERT_FILE__\"}") Expect(err).NotTo(HaveOccurred()) @@ -422,9 +426,9 @@ var _ = Describe("file comparison tests", func() { It("should compare two equal files", func() { // Write two identical files. - err := os.WriteFile(tempDir+"/srcFile", []byte("doesn't matter"), 0644) + err := os.WriteFile(tempDir+"/srcFile", []byte("doesn't matter"), 0o644) Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Failed to write file: %v", err)) - err = os.WriteFile(tempDir+"/dstFile", []byte("doesn't matter"), 0644) + err = os.WriteFile(tempDir+"/dstFile", []byte("doesn't matter"), 0o644) Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Failed to write file: %v", err)) // Assert that they are equal. @@ -435,9 +439,9 @@ var _ = Describe("file comparison tests", func() { It("should compare two unequal files", func() { // Write two files with different contents. - err := os.WriteFile(tempDir+"/srcFile", []byte("doesn't matter"), 0644) + err := os.WriteFile(tempDir+"/srcFile", []byte("doesn't matter"), 0o644) Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Failed to write file: %v", err)) - err = os.WriteFile(tempDir+"/dstFile", []byte("it does matter"), 0644) + err = os.WriteFile(tempDir+"/dstFile", []byte("it does matter"), 0o644) Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Failed to write file: %v", err)) // Assert that they are not equal. @@ -448,9 +452,9 @@ var _ = Describe("file comparison tests", func() { It("should compare two unequal files of the same size", func() { // Write two files with different contents, but same total size. - err := os.WriteFile(tempDir+"/srcFile", []byte("foobar"), 0644) + err := os.WriteFile(tempDir+"/srcFile", []byte("foobar"), 0o644) Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Failed to write file: %v", err)) - err = os.WriteFile(tempDir+"/dstFile", []byte("barfoo"), 0644) + err = os.WriteFile(tempDir+"/dstFile", []byte("barfoo"), 0o644) Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Failed to write file: %v", err)) // Assert that they are not equal. @@ -461,13 +465,13 @@ var _ = Describe("file comparison tests", func() { It("should compare two files with differing file modes", func() { // Write two identical files. - err := os.WriteFile(tempDir+"/srcFile", []byte("doesn't matter"), 0644) + err := os.WriteFile(tempDir+"/srcFile", []byte("doesn't matter"), 0o644) Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Failed to write file: %v", err)) - err = os.WriteFile(tempDir+"/dstFile", []byte("doesn't matter"), 0644) + err = os.WriteFile(tempDir+"/dstFile", []byte("doesn't matter"), 0o644) Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Failed to write file: %v", err)) // For whatever reason, we need to explicitly chmod the file to get the permissions to change. - Expect(os.Chmod(tempDir+"/dstFile", 0777)).NotTo(HaveOccurred()) + Expect(os.Chmod(tempDir+"/dstFile", 0o777)).NotTo(HaveOccurred()) // Assert that they are not equal. match, err := destinationUptoDate(tempDir+"/srcFile", tempDir+"/dstFile") @@ -476,12 +480,12 @@ var _ = Describe("file comparison tests", func() { }) It("should compare a big file with a small file", func() { - err := os.WriteFile(tempDir+"/srcFile", bigFile, 0644) + err := os.WriteFile(tempDir+"/srcFile", bigFile, 0o644) Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Failed to write file: %v", err)) // Here we use the first 10 bytes from "bigFile" to be extra tricky, to make sure // we spot if the files partially match. - err = os.WriteFile(tempDir+"/dstFile", bigFile[:10], 0644) + err = os.WriteFile(tempDir+"/dstFile", bigFile[:10], 0o644) Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Failed to write file: %v", err)) // Assert that they are not equal. @@ -493,9 +497,9 @@ var _ = Describe("file comparison tests", func() { It("should compare a small file with a big file", func() { // Here we use the first 10 bytes from "bigFile" to be extra tricky, to make sure // we spot if the files partially match. - err := os.WriteFile(tempDir+"/srcFile", bigFile[:10], 0644) + err := os.WriteFile(tempDir+"/srcFile", bigFile[:10], 0o644) Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Failed to write file: %v", err)) - err = os.WriteFile(tempDir+"/dstFile", bigFile, 0644) + err = os.WriteFile(tempDir+"/dstFile", bigFile, 0o644) Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Failed to write file: %v", err)) // Assert that they are not equal. @@ -507,9 +511,9 @@ var _ = Describe("file comparison tests", func() { It("should compare two files larger than the buffer size, that differ slightly", func() { // Grab a slightly different number of bytes, ensuring that both are large enough // to require a second loop iteration. - err := os.WriteFile(tempDir+"/srcFile", bigFile[:128002], 0644) + err := os.WriteFile(tempDir+"/srcFile", bigFile[:128002], 0o644) Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Failed to write file: %v", err)) - err = os.WriteFile(tempDir+"/dstFile", bigFile[:128003], 0644) + err = os.WriteFile(tempDir+"/dstFile", bigFile[:128003], 0o644) Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Failed to write file: %v", err)) // Assert that they are not equal. @@ -519,9 +523,9 @@ var _ = Describe("file comparison tests", func() { }) It("should compare two identical files larger than the buffer size", func() { - err := os.WriteFile(tempDir+"/srcFile", bigFile, 0644) + err := os.WriteFile(tempDir+"/srcFile", bigFile, 0o644) Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Failed to write file: %v", err)) - err = os.WriteFile(tempDir+"/dstFile", bigFile, 0644) + err = os.WriteFile(tempDir+"/dstFile", bigFile, 0o644) Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Failed to write file: %v", err)) // Assert that they are equal. @@ -538,3 +542,56 @@ func expectFileContents(filename, expected string) { "actual file (%s) differed from expected contents.\nActual: (%s)\nExpected: (%s)", filename, string(actual), string(expected))) } + +func TestCalculateKubeconfig_IPv4_NoBrackets(t *testing.T) { + // Ensure env vars are restored after the test using t.Setenv + t.Setenv("KUBERNETES_SERVICE_PROTOCOL", "") + t.Setenv("KUBERNETES_SERVICE_HOST", "127.0.0.1") + t.Setenv("KUBERNETES_SERVICE_PORT", "6443") + + cfg := &rest.Config{TLSClientConfig: rest.TLSClientConfig{CAData: []byte("my-ca-data")}} + out := calculateKubeconfig(cfg, "my-token") + + if !strings.Contains(out, "server: https://127.0.0.1:6443") { + t.Fatalf("expected IPv4 host to not be wrapped in brackets; got:\n%s", out) + } + + expectedCA := base64.StdEncoding.EncodeToString(cfg.CAData) + if !strings.Contains(out, "certificate-authority-data: "+expectedCA) { + t.Fatalf("expected certificate-authority-data to be present; got:\n%s", out) + } + + if !strings.Contains(out, "token: my-token") { + t.Fatalf("expected token to be present; got:\n%s", out) + } +} + +func TestCalculateKubeconfig_IPv6_Wrapped(t *testing.T) { + t.Setenv("KUBERNETES_SERVICE_PROTOCOL", "https") + t.Setenv("KUBERNETES_SERVICE_HOST", "2001:db8::1") + t.Setenv("KUBERNETES_SERVICE_PORT", "6443") + + cfg := &rest.Config{TLSClientConfig: rest.TLSClientConfig{CAData: []byte("my-ca-data")}} + out := calculateKubeconfig(cfg, "tok") + + if !strings.Contains(out, "server: https://[2001:db8::1]:6443") { + t.Fatalf("expected IPv6 host to be wrapped in brackets; got:\n%s", out) + } +} + +func TestCalculateKubeconfig_SkipTLSVerify(t *testing.T) { + t.Setenv("SKIP_TLS_VERIFY", "true") + t.Setenv("KUBERNETES_SERVICE_PROTOCOL", "https") + t.Setenv("KUBERNETES_SERVICE_HOST", "127.0.0.1") + t.Setenv("KUBERNETES_SERVICE_PORT", "6443") + + cfg := &rest.Config{TLSClientConfig: rest.TLSClientConfig{CAData: []byte("my-ca-data")}} + out := calculateKubeconfig(cfg, "tok") + + if !strings.Contains(out, "insecure-skip-tls-verify: true") { + t.Fatalf("expected insecure-skip-tls-verify: true to be present; got:\n%s", out) + } + if strings.Contains(out, "certificate-authority-data:") { + t.Fatalf("did not expect certificate-authority-data when SKIP_TLS_VERIFY=true; got:\n%s", out) + } +} diff --git a/cni-plugin/pkg/ipamplugin/ipam_plugin.go b/cni-plugin/pkg/ipamplugin/ipam_plugin.go index f7010b767f5..6047d4d704c 100644 --- a/cni-plugin/pkg/ipamplugin/ipam_plugin.go +++ b/cni-plugin/pkg/ipamplugin/ipam_plugin.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015-2024 Tigera, Inc. All rights reserved. +// Copyright (c) 2015-2026 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import ( "path" "path/filepath" "runtime" + "strings" "time" "github.com/containernetworking/cni/pkg/skel" @@ -44,8 +45,11 @@ import ( client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/errors" "github.com/projectcalico/calico/libcalico-go/lib/ipam" + "github.com/projectcalico/calico/libcalico-go/lib/ipam/vmipam" + "github.com/projectcalico/calico/libcalico-go/lib/kubevirt" "github.com/projectcalico/calico/libcalico-go/lib/logutils" cnet "github.com/projectcalico/calico/libcalico-go/lib/net" + "github.com/projectcalico/calico/libcalico-go/lib/options" ) func Main(version string) { @@ -146,14 +150,48 @@ func cmdAdd(args *skel.CmdArgs) error { return fmt.Errorf("error constructing WorkloadEndpoint name: %s", err) } - handleID := utils.GetHandleID(conf.Name, args.ContainerID, epIDs.WEPName) - logger := logrus.WithFields(logrus.Fields{ "Workload": epIDs.WEPName, "ContainerID": epIDs.ContainerID, - "HandleID": handleID, }) + // Always detect VMI info so we can reject migration targets when persistence is disabled. + vmiInfo, err := getVMIInfoForPod(conf, epIDs, logger) + if err != nil { + return fmt.Errorf("failed to get VMI info: %w", err) + } + + // Combined flag: VMI pod with persistence enabled. + ipPersistenceEnabledForVM := vmiInfo != nil && getKubeVirtVMAddressPersistence(calicoClient) + + // Reject migration target pods when persistence is disabled. + if vmiInfo != nil && vmiInfo.IsMigrationTarget() { + if !ipPersistenceEnabledForVM { + logger.Error("Live migration target pod rejected: KubeVirtVMAddressPersistence is disabled") + return fmt.Errorf("live migration target pod is not allowed when KubeVirtVMAddressPersistence is disabled") + } + } + + var handleID string + if ipPersistenceEnabledForVM { + // Use VM-based handle ID for IP stability across pod recreations/migrations. + // Handle ID is based on namespace/vmName which remains stable across VMI recreation. + handleID = vmipam.CreateVMHandleID(conf.Name, vmiInfo.GetNamespace(), vmiInfo.GetName()) + logger.WithFields(logrus.Fields{ + "pod": epIDs.Pod, + "namespace": epIDs.Namespace, + "vmiName": vmiInfo.GetName(), + "vmiUID": vmiInfo.GetVMIUID(), + "isMigrationTarget": vmiInfo.IsMigrationTarget(), + "handleID": handleID, + }).Info("Detected KubeVirt virt-launcher pod, using VM-based handle ID") + } else { + // Default handle ID based on container ID + handleID = utils.GetHandleID(conf.Name, args.ContainerID, epIDs.WEPName) + } + + logger = logger.WithField("HandleID", handleID) + ipamArgs := ipamArgs{} if err = cnitypes.LoadArgs(args.Args, &ipamArgs); err != nil { return err @@ -168,6 +206,32 @@ func cmdAdd(args *skel.CmdArgs) error { attrs[ipam.AttributePod] = epIDs.Pod attrs[ipam.AttributeNamespace] = epIDs.Namespace } + // Add VMI attributes if this is a virt-launcher pod with persistence enabled + if ipPersistenceEnabledForVM { + attrs[ipam.AttributeVMIName] = vmiInfo.GetName() + attrs[ipam.AttributeVMIUID] = vmiInfo.GetVMIUID() + if vmUID := vmiInfo.GetVMUID(); vmUID != "" { + attrs[ipam.AttributeVMUID] = vmUID + } + + isMigrationTarget := vmiInfo.IsMigrationTarget() + if isMigrationTarget { + attrs[ipam.AttributeVMIMUID] = vmiInfo.GetVMIMigrationUID() + } + + // Check if IPs already exist for this VM handle and reuse them. + // - Source pod: reuses IPs from a previous pod with the same VM (pod recreation). + // If no IPs exist, falls through to normal AutoAssign. + // - Migration target: IPs must exist (allocated by the source pod). + reuseExistingIPs, err := handleVirtLauncherPod(calicoClient, handleID, attrs, conf, logger, isMigrationTarget) + if err != nil { + return err + } + if reuseExistingIPs { + return nil + } + // Source pod with no existing IPs - fall through to normal allocation + } r := &cniv1.Result{} if ipamArgs.IP != nil { @@ -179,6 +243,12 @@ func cmdAdd(args *skel.CmdArgs) error { Hostname: nodename, Attrs: attrs, } + + // For VMI pods with persistence enabled, limit the IPs per handle so that parallel calls for the same VM don't over-allocate (instead one will fail and then the retry will go through the IP persistence path). + if ipPersistenceEnabledForVM { + assignArgs.MaxAllocToHandlePerIPVersion = 1 + } + logger.WithField("assignArgs", assignArgs).Info("Assigning provided IP") assignIPWithLock := func() error { unlock := acquireIPAMLockBestEffort(conf.IPAMLockFile) @@ -289,6 +359,12 @@ func cmdAdd(args *skel.CmdArgs) error { Namespace: namespaceObj, } + // For VMI pods with persistence enabled, set MaxAllocToHandlePerIPVersion=1 to ensure only one IP per IP version per VMI. + // The IPAM library will automatically handle IP reuse if the handle already has an allocation. + if ipPersistenceEnabledForVM { + assignArgs.MaxAllocToHandlePerIPVersion = 1 + } + if runtime.GOOS == "windows" { rsvdAttrWindows := &ipam.HostReservedAttr{ StartOfBlock: 3, @@ -390,6 +466,16 @@ func cmdAdd(args *skel.CmdArgs) error { logger.WithFields(logrus.Fields{"result.IPs": r.IPs}).Debug("IPAM Result") } + // Set routes in the result - one route per IP for normal pods. + // For migration target pods, handleVirtLauncherPod() returns early with empty routes, + // so we only reach here for normal pods or migration source pods. + for _, ipConfig := range r.IPs { + r.Routes = append(r.Routes, &cnitypes.Route{ + Dst: ipConfig.Address, + }) + } + logger.WithField("routes", r.Routes).Debug("Added routes to result") + // Print result to stdout, in the format defined by the requested cniVersion. return cnitypes.PrintResult(r, conf.CNIVersion) } @@ -461,6 +547,79 @@ func acquireIPAMLockBestEffort(path string) unlockFn { } } +// getVMIInfoForPod retrieves KubeVirt VirtualMachineInstance (VMI) information for a given pod. +// Returns (vmiInfo, nil) if the pod is a valid virt-launcher pod. +// Returns (nil, nil) if the pod is not a virt-launcher pod. +// Returns (nil, error) if there was an error retrieving or validating VMI information. +func getVMIInfoForPod(conf types.NetConf, epIDs *utils.WEPIdentifiers, logger *logrus.Entry) (*kubevirt.PodVMIInfo, error) { + // Only check for VMI info in Kubernetes orchestrator + if epIDs.Orchestrator != "k8s" || epIDs.Pod == "" || epIDs.Namespace == "" { + return nil, nil + } + + // Quick pre-filter: skip API server queries for pods that are clearly not virt-launcher pods. + // KubeVirt hardcodes the "virt-launcher-" prefix in pod GenerateName when creating virt-launcher pods. + // This avoids unnecessary API server queries for normal (non-VM) pods on the CNI hot path. + if !strings.HasPrefix(epIDs.Pod, "virt-launcher-") { + return nil, nil + } + + // Create Kubernetes client + k8sClient, err := k8s.NewK8sClient(conf, logger) + if err != nil { + logger.WithError(err).Error("Failed to create Kubernetes client for VMI detection") + return nil, err + } + + // Get the pod + pod, err := k8sClient.CoreV1().Pods(epIDs.Namespace).Get(context.Background(), epIDs.Pod, metav1.GetOptions{}) + if err != nil { + logger.WithError(err).Error("Failed to get pod for VMI detection") + return nil, err + } + + // Check ownerReferences before creating the KubeVirt client. + // This avoids errors when KubeVirt is not installed but a pod happens to have the virt-launcher- prefix. + if kubevirt.FindVMIOwnerRef(pod) == nil { + return nil, nil + } + + // Create KubeVirt client for VMI verification + virtClient, err := k8s.NewKubeVirtClient(conf, logger) + if err != nil { + logger.WithError(err).Error("Failed to create KubeVirt client for VMI detection") + return nil, err + } + + // Get and verify VMI info (queries VMI resource for verification) + vmiInfo, err := kubevirt.GetPodVMIInfo(pod, virtClient) + if err != nil { + logger.WithError(err).Error("Invalid virt-launcher pod configuration") + return nil, err + } + + return vmiInfo, nil +} + +// getKubeVirtVMAddressPersistence retrieves the KubeVirtVMAddressPersistence setting from IPAMConfig. +// Returns true if VM address persistence is enabled (the default), false if explicitly disabled. +func getKubeVirtVMAddressPersistence(calicoClient client.Interface) bool { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + ipamConfig, err := calicoClient.IPAMConfiguration().Get(ctx, "default", options.GetOptions{}) + if err != nil { + logrus.WithError(err).Debug("Could not retrieve IPAMConfig, defaulting to VMAddressPersistenceEnabled") + return true + } + + if ipamConfig.Spec.KubeVirtVMAddressPersistence == nil { + return true + } + + return *ipamConfig.Spec.KubeVirtVMAddressPersistence != v3.VMAddressPersistenceDisabled +} + func cmdDel(args *skel.CmdArgs) error { conf := types.NetConf{} if err := json.Unmarshal(args.StdinData, &conf); err != nil { @@ -487,17 +646,46 @@ func cmdDel(args *skel.CmdArgs) error { return fmt.Errorf("error constructing WorkloadEndpoint name: %s", err) } - handleID := utils.GetHandleID(conf.Name, args.ContainerID, epIDs.WEPName) logger := logrus.WithFields(logrus.Fields{ "Workload": epIDs.WEPName, "ContainerID": epIDs.ContainerID, - "HandleID": handleID, }) + // Always detect VMI info for logging, but only use VM-based handle when persistence is enabled. + vmiInfo, err := getVMIInfoForPod(conf, epIDs, logger) + if err != nil { + return fmt.Errorf("failed to get VMI info: %w", err) + } + + // Combined flag: VMI pod with persistence enabled. + ipPersistenceEnabledForVM := vmiInfo != nil && getKubeVirtVMAddressPersistence(calicoClient) + + // Use VM-based handle ID only when both vmiInfo is present and persistence is enabled. + var handleID string + if ipPersistenceEnabledForVM { + // Use VM-based handle ID + // Handle ID is based on namespace/vmName which remains stable across VMI recreation + handleID = vmipam.CreateVMHandleID(conf.Name, vmiInfo.GetNamespace(), vmiInfo.GetName()) + + // VMI deletion status is already available from embedded VMIResource + logger.WithFields(logrus.Fields{ + "pod": epIDs.Pod, + "namespace": epIDs.Namespace, + "vmiName": vmiInfo.GetName(), + "vmiUID": vmiInfo.GetVMIUID(), + "vmDeletionInProgress": vmiInfo.IsVMObjectDeletionInProgress(), + "vmiDeletionInProgress": vmiInfo.IsVMIObjectDeletionInProgress(), + "hasVMOwner": vmiInfo.VMOwner != nil, + "handleID": handleID, + }).Info("Detected KubeVirt virt-launcher pod deletion") + } else { + // Default handle ID based on container ID + handleID = utils.GetHandleID(conf.Name, args.ContainerID, epIDs.WEPName) + } + + logger = logger.WithField("HandleID", handleID) + logger.Info("Releasing address using handleID") - ctx := context.Background() - ctx, cancel := context.WithTimeout(ctx, 90*time.Second) - defer cancel() // Acquire a best-effort host-wide lock to prevent multiple copies of the CNI plugin trying to assign/delete // concurrently. ReleaseXXX is concurrency safe already but serialising the CNI plugins means that @@ -506,6 +694,153 @@ func cmdDel(args *skel.CmdArgs) error { unlock := acquireIPAMLockBestEffort(conf.IPAMLockFile) defer unlock() + ctx := context.Background() + ctx, cancel := context.WithTimeout(ctx, 90*time.Second) + defer cancel() + + // For VMI pods with persistence enabled, handle deletion based on VM/VMI deletion status. + // IP persistence is only maintained while the VM (or standalone VMI) is alive. + // - If VMI is owned by a VM: release when VM has DeletionTimestamp + // - If VMI is standalone (no VM owner): release when VMI has DeletionTimestamp + if ipPersistenceEnabledForVM { + shouldRelease := false + if vmiInfo.VMOwner != nil { + // VMI is owned by a VM - check VM deletion status + shouldRelease = vmiInfo.IsVMObjectDeletionInProgress() + } else { + // Standalone VMI (no VM owner) - check VMI deletion status + shouldRelease = vmiInfo.IsVMIObjectDeletionInProgress() + } + logger.WithFields(logrus.Fields{ + "shouldRelease": shouldRelease, + "hasVMOwner": vmiInfo.VMOwner != nil, + }).Info("Processing VMI pod deletion") + + // Get IPs allocated to this handle so we can clear their attributes + ips, err := calicoClient.IPAM().IPsByHandle(ctx, handleID) + if err != nil { + logger.WithError(err).Warn("Failed to get IPs by handle") + return err + } + + // Build expected owner for verification + expectedOwner := &ipam.AttributeOwner{ + Namespace: epIDs.Namespace, + Name: epIDs.Pod, + } + + // First pass: Clear attributes that match this pod + for _, ip := range ips { + logger.WithField("ip", ip).Info("Attempting to clear pod attributes") + + // Get current AllocationAttribute + attr, err := calicoClient.IPAM().GetAssignmentAttributes(ctx, ip) + if err != nil { + logger.WithError(err).WithField("ip", ip).Warn("Failed to get assignment attributes, skipping attribute cleanup") + continue + } + + // Check which attribute owner matches this pod + activeMatches := expectedOwner.Matches(attr.ActiveOwnerAttrs) + alternateMatches := expectedOwner.Matches(attr.AlternateOwnerAttrs) + + // Prepare updates and preconditions based on which owner type matches + var updates *ipam.OwnerAttributeUpdates + var preconditions *ipam.OwnerAttributePreconditions + var ownerType string + + if activeMatches { + ownerType = "ActiveOwnerAttrs" + updates = &ipam.OwnerAttributeUpdates{ + ClearActiveOwner: true, + } + preconditions = &ipam.OwnerAttributePreconditions{ + ExpectedActiveOwner: expectedOwner, + } + } else if alternateMatches { + ownerType = "AlternateOwnerAttrs" + updates = &ipam.OwnerAttributeUpdates{ + ClearAlternateOwner: true, + } + preconditions = &ipam.OwnerAttributePreconditions{ + ExpectedAlternateOwner: expectedOwner, + } + } else { + logger.WithField("ip", ip).Debug("No matching attributes found for this pod, nothing to clear") + continue + } + + // Clear the matching owner attributes. + // Note: There is a potential race condition between GetAssignmentAttributes and SetOwnerAttributes. + // If Felix performs a SwapAttributes operation upon migration completion during this window, + // the block attributes may have changed, causing SetOwnerAttributes to fail with a precondition + // mismatch error. This is expected behavior - kubelet will retry the CNI DEL operation, and + // on the subsequent attempt, GetAssignmentAttributes will read the updated attributes and + // SetOwnerAttributes will succeed. This race condition should be extremely rare because it only + // gets triggered when a migration target pod gets deleted around the same time migration is completing, + // and the window is tiny (between GetAssignmentAttributes and SetOwnerAttributes). + err = calicoClient.IPAM().SetOwnerAttributes(ctx, ip, handleID, updates, preconditions) + if err != nil { + logger.WithError(err).WithFields(logrus.Fields{ + "ip": ip, + "ownerType": ownerType, + }).Error("Failed to clear owner attributes") + return err + } + logger.WithFields(logrus.Fields{ + "ip": ip, + "ownerType": ownerType, + }).Info("Successfully cleared owner attributes") + } + + // Second pass: Check if any owner attributes remain after clearing this pod's attributes + anyOwnerAttributesRemain := false + for _, ip := range ips { + attr, err := calicoClient.IPAM().GetAssignmentAttributes(ctx, ip) + if err != nil { + if _, ok := err.(errors.ErrorResourceDoesNotExist); ok { + // IP was already released by someone else, treat as no attributes remaining. + logger.WithField("ip", ip).Info("IP already released during cleanup, skipping") + continue + } + logger.WithError(err).WithField("ip", ip).Error("Failed to get assignment attributes for post-cleanup check") + return fmt.Errorf("failed to verify owner attributes after cleanup for IP %s: %w", ip, err) + } + + if len(attr.ActiveOwnerAttrs) > 0 || len(attr.AlternateOwnerAttrs) > 0 { + anyOwnerAttributesRemain = true + logger.WithFields(logrus.Fields{ + "ip": ip, + "hasActiveOwner": len(attr.ActiveOwnerAttrs) > 0, + "hasAlternateOwner": len(attr.AlternateOwnerAttrs) > 0, + }).Debug("Owner attributes still exist for this IP") + break // No need to check remaining IPs + } + } + + // Only release the handle if the VM/VMI is being deleted AND all IPs have empty owner attributes + if shouldRelease && !anyOwnerAttributesRemain { + logger.Info("VM/VMI deletion in progress and all owner attributes empty - releasing IP by handle") + if err := calicoClient.IPAM().ReleaseByHandle(ctx, handleID); err != nil { + if _, ok := err.(errors.ErrorResourceDoesNotExist); !ok { + logger.WithError(err).Error("Failed to release address") + return err + } + logger.Warn("Asked to release address but it doesn't exist. Ignoring") + } else { + logger.Info("Released address using handleID") + } + } else { + logger.WithFields(logrus.Fields{ + "shouldRelease": shouldRelease, + "anyOwnerAttributesRemain": anyOwnerAttributesRemain, + }).Info("Completed attribute cleanup - IP remains allocated to VM handle") + } + + return nil + } + + // For non-VM pods, use the standard release logic if err := calicoClient.IPAM().ReleaseByHandle(ctx, handleID); err != nil { if _, ok := err.(errors.ErrorResourceDoesNotExist); !ok { logger.WithError(err).Error("Failed to release address") @@ -556,3 +891,97 @@ func getNamespace(conf types.NetConf, namespace string, logger *logrus.Entry) (* return ns, nil } + +// handleVirtLauncherPod handles IP allocation for KubeVirt virt-launcher pods by checking +// if IPs are already allocated to the VM handle and reusing them. +// +// For source pods (isMigrationTarget=false): +// - If existing IPs found: reuses them and sets ActiveOwnerAttrs. Returns (true, nil). +// - If no existing IPs: returns (false, nil) so the caller proceeds with normal AutoAssign. +// +// For migration target pods (isMigrationTarget=true): +// - Existing IPs must already be allocated by the source pod. +// - Sets AlternateOwnerAttrs (unconditionally, to handle back-to-back migrations). +// - Returns empty routes so CNI skips host-side route programming. +// - Returns (true, nil) on success, (false, error) if IPs are missing. +func handleVirtLauncherPod(calicoClient client.Interface, handleID string, attrs map[string]string, conf types.NetConf, logger *logrus.Entry, isMigrationTarget bool) (bool, error) { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + existingIPs, err := calicoClient.IPAM().IPsByHandle(ctx, handleID) + if err != nil { + if _, ok := err.(errors.ErrorResourceDoesNotExist); !ok { + // Communication or unexpected error - don't fall through to new allocation. + logger.WithError(err).Error("Failed to get existing IPs for VM handle") + return false, fmt.Errorf("failed to get existing IPs for VM handle %s: %w", handleID, err) + } + } + if len(existingIPs) == 0 { + if isMigrationTarget { + // Migration target requires existing IPs allocated by the source pod. + logger.Error("Migration target pod but VM handle has no allocated IPs") + return false, fmt.Errorf("migration target pod but no IP allocated to VM handle %s", handleID) + } + // Source pod with no existing IPs - fall through to normal AutoAssign. + logger.WithError(err).Info("No existing IPs for VM handle, proceeding with new allocation") + return false, nil + } + + logger.WithFields(logrus.Fields{ + "ipCount": len(existingIPs), + "isMigrationTarget": isMigrationTarget, + }).Info("Found existing IPs for VM handle, reusing them") + + // Build result IPs and update owner attributes for each IP + r := &cniv1.Result{} + for _, ip := range existingIPs { + var mask net.IPMask + if ip.To4() != nil { + mask = net.CIDRMask(32, 32) + } else { + mask = net.CIDRMask(128, 128) + } + r.IPs = append(r.IPs, &cniv1.IPConfig{ + Address: net.IPNet{IP: ip.IP, Mask: mask}, + }) + + // Source pod: set ActiveOwnerAttrs; Migration target: set AlternateOwnerAttrs. + // No preconditions are used here because the attributes may already be non-empty: + // - ActiveOwnerAttrs: a previous virt-launcher pod may be stuck in deletion while a new one starts. + // - AlternateOwnerAttrs: a previous migration may have failed, leaving stale alternate attributes. + // In both cases, the new pod must overwrite the existing attributes. + var updates *ipam.OwnerAttributeUpdates + if isMigrationTarget { + updates = &ipam.OwnerAttributeUpdates{ + AlternateOwnerAttrs: attrs, + } + } else { + updates = &ipam.OwnerAttributeUpdates{ + ActiveOwnerAttrs: attrs, + } + } + err = calicoClient.IPAM().SetOwnerAttributes(ctx, ip, handleID, updates, nil) + if err != nil { + logger.WithError(err).Errorf("Failed to set owner attributes for IP %s", ip) + return false, fmt.Errorf("failed to set owner attributes for IP %s: %w", ip, err) + } + } + + // Set routes based on pod role: + // - Source pod: one route per IP so CNI programs host-side routes + // - Migration target: empty routes to skip route programming (source pod keeps the route + // until migration completes, then Felix updates it) + if isMigrationTarget { + r.Routes = []*cnitypes.Route{} + logger.Info("Migration target pod: returning existing IPs with empty routes") + } else { + for _, ipConfig := range r.IPs { + r.Routes = append(r.Routes, &cnitypes.Route{ + Dst: ipConfig.Address, + }) + } + logger.Info("Source pod: returning existing IPs with routes") + } + + return true, cnitypes.PrintResult(r, conf.CNIVersion) +} diff --git a/cni-plugin/pkg/k8s/k8s.go b/cni-plugin/pkg/k8s/k8s.go index 4cab612c6ef..36081fe2413 100644 --- a/cni-plugin/pkg/k8s/k8s.go +++ b/cni-plugin/pkg/k8s/k8s.go @@ -32,6 +32,7 @@ import ( "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "github.com/projectcalico/calico/cni-plugin/internal/pkg/utils" @@ -39,12 +40,13 @@ import ( "github.com/projectcalico/calico/cni-plugin/pkg/dataplane" "github.com/projectcalico/calico/cni-plugin/pkg/types" "github.com/projectcalico/calico/cni-plugin/pkg/wait" - libapi "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" k8sconversion "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" k8sresources "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/resources" calicoclient "github.com/projectcalico/calico/libcalico-go/lib/clientv3" cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" libipam "github.com/projectcalico/calico/libcalico-go/lib/ipam" + "github.com/projectcalico/calico/libcalico-go/lib/kubevirt" cnet "github.com/projectcalico/calico/libcalico-go/lib/net" "github.com/projectcalico/calico/libcalico-go/lib/options" "github.com/projectcalico/calico/libcalico-go/lib/winutils" @@ -53,7 +55,7 @@ import ( // CmdAddK8s performs the "ADD" operation on a kubernetes pod // Having kubernetes code in its own file avoids polluting the mainline code. It's expected that the kubernetes case will // more special casing than the mainline code. -func CmdAddK8s(ctx context.Context, args *skel.CmdArgs, conf types.NetConf, epIDs utils.WEPIdentifiers, calicoClient calicoclient.Interface, endpoint *libapi.WorkloadEndpoint) (*cniv1.Result, error) { +func CmdAddK8s(ctx context.Context, args *skel.CmdArgs, conf types.NetConf, epIDs utils.WEPIdentifiers, calicoClient calicoclient.Interface, endpoint *internalapi.WorkloadEndpoint) (*cniv1.Result, error) { var err error var result *cniv1.Result @@ -102,7 +104,7 @@ func CmdAddK8s(ctx context.Context, args *skel.CmdArgs, conf types.NetConf, epID // // We unpack the JSON data as an untyped map rather than using a typed struct because we want to // round-trip any fields that we don't know about. - var stdinData map[string]interface{} + var stdinData map[string]any if err := json.Unmarshal(args.StdinData, &stdinData); err != nil { return nil, err } @@ -138,15 +140,15 @@ func CmdAddK8s(ctx context.Context, args *skel.CmdArgs, conf types.NetConf, epID logger.Debug("Updated stdin data") // Extract any custom routes from the IPAM configuration. - ipamData := stdinData["ipam"].(map[string]interface{}) + ipamData := stdinData["ipam"].(map[string]any) untypedRoutes := ipamData["routes"] - hlRoutes, ok := untypedRoutes.([]interface{}) + hlRoutes, ok := untypedRoutes.([]any) if untypedRoutes != nil && !ok { return nil, fmt.Errorf( "failed to parse host-local IPAM routes section; expecting list, not: %v", stdinData["ipam"]) } for _, route := range hlRoutes { - route := route.(map[string]interface{}) + route := route.(map[string]any) untypedDst, ok := route["dst"] if !ok { logger.Debug("Ignoring host-ipam route with no dst") @@ -184,7 +186,7 @@ func CmdAddK8s(ctx context.Context, args *skel.CmdArgs, conf types.NetConf, epID labels := make(map[string]string) annot := make(map[string]string) - var ports []libapi.WorkloadEndpointPort + var ports []internalapi.WorkloadEndpointPort var profiles []string var generateName string var serviceAccount string @@ -234,7 +236,7 @@ func CmdAddK8s(ctx context.Context, args *skel.CmdArgs, conf types.NetConf, epID } if len(v4pools) != 0 || len(v6pools) != 0 || len(ipFamilies) != 0 { - var stdinData map[string]interface{} + var stdinData map[string]any if err := json.Unmarshal(args.StdinData, &stdinData); err != nil { return nil, err } @@ -246,10 +248,10 @@ func CmdAddK8s(ctx context.Context, args *skel.CmdArgs, conf types.NetConf, epID return nil, err } - if _, ok := stdinData["ipam"].(map[string]interface{}); !ok { + if _, ok := stdinData["ipam"].(map[string]any); !ok { return nil, errors.New("data on stdin was of unexpected type") } - stdinData["ipam"].(map[string]interface{})["ipv4_pools"] = v4PoolSlice + stdinData["ipam"].(map[string]any)["ipv4_pools"] = v4PoolSlice logger.WithField("ipv4_pools", v4pools).Debug("Setting IPv4 Pools") } if len(v6pools) > 0 { @@ -258,10 +260,10 @@ func CmdAddK8s(ctx context.Context, args *skel.CmdArgs, conf types.NetConf, epID return nil, err } - if _, ok := stdinData["ipam"].(map[string]interface{}); !ok { + if _, ok := stdinData["ipam"].(map[string]any); !ok { return nil, errors.New("data on stdin was of unexpected type") } - stdinData["ipam"].(map[string]interface{})["ipv6_pools"] = v6PoolSlice + stdinData["ipam"].(map[string]any)["ipv6_pools"] = v6PoolSlice logger.WithField("ipv6_pools", v6pools).Debug("Setting IPv6 Pools") } @@ -285,12 +287,12 @@ func CmdAddK8s(ctx context.Context, args *skel.CmdArgs, conf types.NetConf, epID } } - if _, ok := stdinData["ipam"].(map[string]interface{}); !ok { + if _, ok := stdinData["ipam"].(map[string]any); !ok { return nil, errors.New("data on stdin was of unexpected type") } - stdinData["ipam"].(map[string]interface{})["assign_ipv4"] = &assignV4 - stdinData["ipam"].(map[string]interface{})["assign_ipv6"] = &assignV6 + stdinData["ipam"].(map[string]any)["assign_ipv4"] = &assignV4 + stdinData["ipam"].(map[string]any)["assign_ipv6"] = &assignV6 logger.WithField("assign_ipv4", assignV4).Debug("Setting assignV4") logger.WithField("assign_ipv6", assignV6).Debug("Setting assignV6") } @@ -386,7 +388,7 @@ func CmdAddK8s(ctx context.Context, args *skel.CmdArgs, conf types.NetConf, epID // Configure the endpoint (creating if required). if endpoint == nil { logger.Debug("Initializing new WorkloadEndpoint resource") - endpoint = libapi.NewWorkloadEndpoint() + endpoint = internalapi.NewWorkloadEndpoint() } endpoint.Name = epIDs.WEPName endpoint.Namespace = epIDs.Namespace @@ -424,10 +426,18 @@ func CmdAddK8s(ctx context.Context, args *skel.CmdArgs, conf types.NetConf, epID utils.ReleaseIPAllocation(logger, conf, args) } + // Check if IPAM returned empty routes (migration target pod case) + // calico-ipam returns empty routes for migration target pods to skip host-side route programming + skipHostSideRoutes := false + if conf.IPAM.Type == "calico-ipam" && len(result.Routes) == 0 { + skipHostSideRoutes = true + logger.Info("IPAM returned empty routes - skipping host-side route programming") + } + // Whether the endpoint existed or not, the veth needs (re)creating. desiredVethName := k8sconversion.NewConverter().VethNameForWorkload(epIDs.Namespace, epIDs.Pod) hostVethName, contVethMac, err := d.DoNetworking( - ctx, calicoClient, args, result, desiredVethName, routes, endpoint, annot) + ctx, calicoClient, args, result, desiredVethName, routes, endpoint, annot, skipHostSideRoutes) if err != nil { logger.WithError(err).Error("Error setting up networking") releaseIPAM() @@ -504,12 +514,12 @@ func CmdAddK8s(ctx context.Context, args *skel.CmdArgs, conf types.NetConf, epID for _, ip := range ips { if strings.Contains(ip, ":") { - endpoint.Spec.IPNATs = append(endpoint.Spec.IPNATs, libapi.IPNAT{ + endpoint.Spec.IPNATs = append(endpoint.Spec.IPNATs, internalapi.IPNAT{ InternalIP: podnetV6.IP.String(), ExternalIP: ip, }) } else { - endpoint.Spec.IPNATs = append(endpoint.Spec.IPNATs, libapi.IPNAT{ + endpoint.Spec.IPNATs = append(endpoint.Spec.IPNATs, internalapi.IPNAT{ InternalIP: podnetV4.IP.String(), ExternalIP: ip, }) @@ -523,7 +533,7 @@ func CmdAddK8s(ctx context.Context, args *skel.CmdArgs, conf types.NetConf, epID // Pod resource. (In Enterprise) Felix also modifies the pod through a patch and setting this avoids patching the // same fields as Felix so that we can't clobber Felix's updates. ctxPatchCNI := k8sresources.ContextWithPatchMode(ctx, k8sresources.PatchModeCNI) - var endpointOut *libapi.WorkloadEndpoint + var endpointOut *internalapi.WorkloadEndpoint if endpointOut, err = utils.CreateOrUpdate(ctxPatchCNI, calicoClient, endpoint); err != nil { logger.WithError(err).Error("Error creating/updating endpoint in datastore.") releaseIPAM() @@ -907,7 +917,9 @@ func parseIPAddrs(ipAddrsStr string, logger *logrus.Entry) ([]string, error) { return ips, nil } -func NewK8sClient(conf types.NetConf, logger *logrus.Entry) (*kubernetes.Clientset, error) { +// getK8sRestConfig creates a Kubernetes REST config from the CNI network configuration. +// This helper function builds the config with kubeconfig file and overrides from the network config. +func getK8sRestConfig(conf types.NetConf) (*rest.Config, error) { // Some config can be passed in a kubeconfig file kubeconfig := conf.Kubernetes.Kubeconfig @@ -943,9 +955,14 @@ func NewK8sClient(conf types.NetConf, logger *logrus.Entry) (*kubernetes.Clients } // Use the kubernetes client code to load the kubeconfig file and combine it with the overrides. - config, err := winutils.NewNonInteractiveDeferredLoadingClientConfig( + return winutils.NewNonInteractiveDeferredLoadingClientConfig( &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig}, configOverrides) +} + +func NewK8sClient(conf types.NetConf, logger *logrus.Entry) (*kubernetes.Clientset, error) { + // Get the Kubernetes REST config + config, err := getK8sRestConfig(conf) if err != nil { return nil, err } @@ -954,6 +971,19 @@ func NewK8sClient(conf types.NetConf, logger *logrus.Entry) (*kubernetes.Clients return kubernetes.NewForConfig(config) } +// NewKubeVirtClient creates a KubeVirt client from the CNI network configuration. +// Note: This will return an error if KubeVirt is not installed in the cluster. +func NewKubeVirtClient(conf types.NetConf, logger *logrus.Entry) (kubevirt.VirtClientInterface, error) { + // Get the Kubernetes REST config + config, err := getK8sRestConfig(conf) + if err != nil { + return nil, err + } + + // Create KubeVirt client + return kubevirt.NewVirtClient(config) +} + func getK8sNSInfo(client *kubernetes.Clientset, podNamespace string) (annotations map[string]string, err error) { ns, err := client.CoreV1().Namespaces().Get(context.Background(), podNamespace, metav1.GetOptions{}) logrus.Debugf("namespace info %+v", ns) @@ -963,7 +993,7 @@ func getK8sNSInfo(client *kubernetes.Clientset, podNamespace string) (annotation return ns.Annotations, nil } -func getK8sPodInfo(client *kubernetes.Clientset, podName, podNamespace string) (labels map[string]string, annotations map[string]string, ports []libapi.WorkloadEndpointPort, profiles []string, generateName, serviceAccount string, err error) { +func getK8sPodInfo(client *kubernetes.Clientset, podName, podNamespace string) (labels map[string]string, annotations map[string]string, ports []internalapi.WorkloadEndpointPort, profiles []string, generateName, serviceAccount string, err error) { pod, err := client.CoreV1().Pods(string(podNamespace)).Get(context.Background(), podName, metav1.GetOptions{}) logrus.Debugf("pod info %+v", pod) if err != nil { @@ -977,11 +1007,11 @@ func getK8sPodInfo(client *kubernetes.Clientset, podName, podNamespace string) ( } kvp := kvps[0] - ports = kvp.Value.(*libapi.WorkloadEndpoint).Spec.Ports - labels = kvp.Value.(*libapi.WorkloadEndpoint).Labels - profiles = kvp.Value.(*libapi.WorkloadEndpoint).Spec.Profiles - generateName = kvp.Value.(*libapi.WorkloadEndpoint).GenerateName - serviceAccount = kvp.Value.(*libapi.WorkloadEndpoint).Spec.ServiceAccountName + ports = kvp.Value.(*internalapi.WorkloadEndpoint).Spec.Ports + labels = kvp.Value.(*internalapi.WorkloadEndpoint).Labels + profiles = kvp.Value.(*internalapi.WorkloadEndpoint).Spec.Profiles + generateName = kvp.Value.(*internalapi.WorkloadEndpoint).GenerateName + serviceAccount = kvp.Value.(*internalapi.WorkloadEndpoint).Spec.ServiceAccountName return labels, pod.Annotations, ports, profiles, generateName, serviceAccount, nil } diff --git a/cni-plugin/pkg/plugin/plugin.go b/cni-plugin/pkg/plugin/plugin.go index bee463f3e60..e5ebebceb42 100644 --- a/cni-plugin/pkg/plugin/plugin.go +++ b/cni-plugin/pkg/plugin/plugin.go @@ -26,6 +26,7 @@ import ( "os" "runtime" "runtime/debug" + "slices" "time" "github.com/containernetworking/cni/pkg/skel" @@ -44,7 +45,7 @@ import ( "github.com/projectcalico/calico/cni-plugin/pkg/dataplane" "github.com/projectcalico/calico/cni-plugin/pkg/k8s" "github.com/projectcalico/calico/cni-plugin/pkg/types" - libapi "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/resources" "github.com/projectcalico/calico/libcalico-go/lib/clientv3" cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" @@ -298,7 +299,7 @@ func cmdAdd(args *skel.CmdArgs) (err error) { logger.Debugf("Retrieved list of endpoints: %v", endpoints) - var endpoint *libapi.WorkloadEndpoint + var endpoint *internalapi.WorkloadEndpoint // If the prefix list returns 1 or more items, we go through the items and try to see if the name matches the WEP // identifiers we have. The identifiers we use for this match at this point are: @@ -386,12 +387,9 @@ func cmdAdd(args *skel.CmdArgs) (err error) { // Just update the profile on the endpoint. The profile will be created if needed during the // profile processing step. foundProfile := false - for _, p := range endpoint.Spec.Profiles { - if p == profileID { - logger.Infof("Calico CNI endpoint already has profile: %s\n", profileID) - foundProfile = true - break - } + if slices.Contains(endpoint.Spec.Profiles, profileID) { + logger.Infof("Calico CNI endpoint already has profile: %s\n", profileID) + foundProfile = true } if !foundProfile { logger.Infof("Calico CNI appending profile: %s\n", profileID) @@ -409,8 +407,10 @@ func cmdAdd(args *skel.CmdArgs) (err error) { // 3) Create the veth, configuring it on both the host and container namespace. // 1) Run the IPAM plugin and make sure there's an IP address returned. - logger.WithFields(logrus.Fields{"paths": os.Getenv("CNI_PATH"), - "type": conf.IPAM.Type}).Debug("Looking for IPAM plugin in paths") + logger.WithFields(logrus.Fields{ + "paths": os.Getenv("CNI_PATH"), + "type": conf.IPAM.Type, + }).Debug("Looking for IPAM plugin in paths") var ipamResult cnitypes.Result ipamResult, err = ipam.ExecAdd(conf.IPAM.Type, args.StdinData) logger.WithField("IPAM result", ipamResult).Info("Got result from IPAM plugin") @@ -450,7 +450,7 @@ func cmdAdd(args *skel.CmdArgs) (err error) { } // 2) Create the endpoint object - endpoint = libapi.NewWorkloadEndpoint() + endpoint = internalapi.NewWorkloadEndpoint() endpoint.Name = wepIDs.WEPName endpoint.Namespace = wepIDs.Namespace endpoint.Spec.Endpoint = wepIDs.Endpoint @@ -481,7 +481,7 @@ func cmdAdd(args *skel.CmdArgs) (err error) { var hostVethName, contVethMac string desiredVethName := "cali" + args.ContainerID[:min(11, len(args.ContainerID))] hostVethName, contVethMac, err = d.DoNetworking( - ctx, calicoClient, args, result, desiredVethName, utils.DefaultRoutes, endpoint, map[string]string{}) + ctx, calicoClient, args, result, desiredVethName, utils.DefaultRoutes, endpoint, map[string]string{}, false) if err != nil { // Cleanup IP allocation and return the error. utils.ReleaseIPAllocation(logger, conf, args) diff --git a/cni-plugin/pkg/types/types.go b/cni-plugin/pkg/types/types.go index 393945b7e1c..2cc2d5962b7 100644 --- a/cni-plugin/pkg/types/types.go +++ b/cni-plugin/pkg/types/types.go @@ -18,6 +18,8 @@ import ( "net" "github.com/containernetworking/cni/pkg/types" + + "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/resources" ) // Policy is a struct to hold policy config (which currently happens to also contain some K8s config) @@ -44,7 +46,7 @@ type Kubernetes struct { } type Args struct { - Mesos Mesos `json:"org.apache.mesos,omitempty"` + Mesos Mesos `json:"org.apache.mesos"` } type Mesos struct { @@ -58,7 +60,7 @@ type NetworkInfo struct { Key string `json:"key"` Value string `json:"value"` } `json:"labels,omitempty"` - } `json:"labels,omitempty"` + } `json:"labels"` } // NetConf stores the common network config for Calico CNI plugin @@ -77,32 +79,34 @@ type NetConf struct { AssignIpv6 *string `json:"assign_ipv6"` IPv4Pools []string `json:"ipv4_pools,omitempty"` IPv6Pools []string `json:"ipv6_pools,omitempty"` - } `json:"ipam,omitempty"` - Args Args `json:"args"` - MTU int `json:"mtu"` - NumQueues int `json:"num_queues"` - Nodename string `json:"nodename"` - NodenameFile string `json:"nodename_file"` - IPAMLockFile string `json:"ipam_lock_file"` - NodenameFileOptional bool `json:"nodename_file_optional"` - DatastoreType string `json:"datastore_type"` - EtcdEndpoints string `json:"etcd_endpoints"` - EtcdDiscoverySrv string `json:"etcd_discovery_srv"` - LogLevel string `json:"log_level"` - LogFilePath string `json:"log_file_path"` - LogFileMaxSize int `json:"log_file_max_size"` - LogFileMaxAge int `json:"log_file_max_age"` - LogFileMaxCount int `json:"log_file_max_count"` - Policy Policy `json:"policy"` - Kubernetes Kubernetes `json:"kubernetes"` - FeatureControl FeatureControl `json:"feature_control"` - EtcdScheme string `json:"etcd_scheme"` - EtcdKeyFile string `json:"etcd_key_file"` - EtcdCertFile string `json:"etcd_cert_file"` - EtcdCaCertFile string `json:"etcd_ca_cert_file"` - ContainerSettings ContainerSettings `json:"container_settings,omitempty"` - IncludeDefaultRoutes bool `json:"include_default_routes,omitempty"` - DataplaneOptions map[string]interface{} `json:"dataplane_options,omitempty"` + } `json:"ipam"` + QPS int `json:"qps,omitempty"` + Burst int `json:"burst,omitempty"` + Args Args `json:"args"` + MTU int `json:"mtu"` + NumQueues int `json:"num_queues"` + Nodename string `json:"nodename"` + NodenameFile string `json:"nodename_file"` + IPAMLockFile string `json:"ipam_lock_file"` + NodenameFileOptional bool `json:"nodename_file_optional"` + DatastoreType string `json:"datastore_type"` + EtcdEndpoints string `json:"etcd_endpoints"` + EtcdDiscoverySrv string `json:"etcd_discovery_srv"` + LogLevel string `json:"log_level"` + LogFilePath string `json:"log_file_path"` + LogFileMaxSize int `json:"log_file_max_size"` + LogFileMaxAge int `json:"log_file_max_age"` + LogFileMaxCount int `json:"log_file_max_count"` + Policy Policy `json:"policy"` + Kubernetes Kubernetes `json:"kubernetes"` + FeatureControl FeatureControl `json:"feature_control"` + EtcdScheme string `json:"etcd_scheme"` + EtcdKeyFile string `json:"etcd_key_file"` + EtcdCertFile string `json:"etcd_cert_file"` + EtcdCaCertFile string `json:"etcd_ca_cert_file"` + ContainerSettings ContainerSettings `json:"container_settings"` + IncludeDefaultRoutes bool `json:"include_default_routes,omitempty"` + DataplaneOptions map[string]any `json:"dataplane_options,omitempty"` // Windows-specific configuration. // WindowsPodDeletionTimestampTimeout defines number of seconds before a pod deletion timestamp timeout and @@ -147,6 +151,10 @@ type NetConf struct { // RequireMTUFile specifies whether mtu file is required to execute the cni-plugin RequireMTUFile bool `json:"require_mtu_file,omitempty"` + + // CalicoAPIGroup specifies the API group to use when connecting to the Kubernetes API server. + // If not specified, the default value of "crd.projectcalico.org" is used. + CalicoAPIGroup resources.BackingAPIGroup `json:"calico_api_group,omitempty"` } // Runtime Config is provided by kubernetes diff --git a/cni-plugin/pkg/upgrade/migrate.go b/cni-plugin/pkg/upgrade/migrate.go index 80e4b0d3a0b..ab41a5eeda8 100644 --- a/cni-plugin/pkg/upgrade/migrate.go +++ b/cni-plugin/pkg/upgrade/migrate.go @@ -29,7 +29,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/projectcalico/calico/cni-plugin/internal/pkg/utils" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" @@ -43,12 +43,10 @@ const ( ipAllocPath = "/var/lib/cni/networks/k8s-pod-network" ) -var ( - binariesToDisable = []string{ - "/host/opt/cni/bin/calico", - "/host/opt/cni/bin/host-local", - } -) +var binariesToDisable = []string{ + "/host/opt/cni/bin/calico", + "/host/opt/cni/bin/host-local", +} type accessor interface { Backend() bapi.Client @@ -116,13 +114,13 @@ func Migrate(ctxt context.Context, c client.Interface, nodename string) error { // Implemented retry on conflict, because we get stuck if the upgrade-ipam // container fails here and retries assigning an IP which is already assigned - for i := uint(0); i < 5; i++ { + for range uint(5) { node, err := c.Nodes().Get(ctxt, nodename, options.GetOptions{}) if err != nil { return fmt.Errorf("failed to get calico node resource: %s", err) } if node.Spec.BGP == nil { - node.Spec.BGP = &libapiv3.NodeBGPSpec{} + node.Spec.BGP = &internalapi.NodeBGPSpec{} } node.Spec.BGP.IPv4IPIPTunnelAddr = tunIp.String() if _, err = c.Nodes().Update(ctxt, node, options.SetOptions{}); err != nil { @@ -176,7 +174,7 @@ func Migrate(ctxt context.Context, c client.Interface, nodename string) error { // Disable cni by setting DatastoreReady to false. log.Info("setting datastore readiness to false") var clusterInfo *apiv3.ClusterInformation - for i := uint(0); i < 5; i++ { + for range uint(5) { clusterInfo, err = c.ClusterInformation().Get(ctxt, "default", options.GetOptions{}) if err != nil { return fmt.Errorf("failed to fetch cluster information: %s", err) diff --git a/cni-plugin/pkg/wait/wait.go b/cni-plugin/pkg/wait/wait.go index e4ecb624e5e..5cb8f978ec3 100644 --- a/cni-plugin/pkg/wait/wait.go +++ b/cni-plugin/pkg/wait/wait.go @@ -26,13 +26,13 @@ import ( "github.com/fsnotify/fsnotify" "github.com/sirupsen/logrus" - libapi "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/names" ) // ForEndpointReadyWithTimeout blocks until a status file for the given endpoint // is seen in the provided directory. Unblocks with an error after exceeding timeout. -func ForEndpointReadyWithTimeout(policyDir string, endpoint *libapi.WorkloadEndpoint, timeout time.Duration) error { +func ForEndpointReadyWithTimeout(policyDir string, endpoint *internalapi.WorkloadEndpoint, timeout time.Duration) error { if endpoint == nil { logrus.Panic("Endpoint is nil") } diff --git a/cni-plugin/pkg/wait/wait_suite_test.go b/cni-plugin/pkg/wait/wait_suite_test.go index a946066b505..97e3e138918 100644 --- a/cni-plugin/pkg/wait/wait_suite_test.go +++ b/cni-plugin/pkg/wait/wait_suite_test.go @@ -16,9 +16,8 @@ package wait import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -28,7 +27,8 @@ func init() { } func TestWait(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/status_wait_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Status Wait Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/status_wait_suite.xml" + ginkgo.RunSpecs(t, "Status Wait Suite", suiteConfig, reporterConfig) } diff --git a/cni-plugin/pkg/wait/wait_test.go b/cni-plugin/pkg/wait/wait_test.go index dcb75bbca8e..3a53a8e4652 100644 --- a/cni-plugin/pkg/wait/wait_test.go +++ b/cni-plugin/pkg/wait/wait_test.go @@ -19,11 +19,11 @@ import ( "path/filepath" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - v3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/names" ) @@ -33,18 +33,18 @@ var _ = Describe("k8s-wait", func() { BeforeEach(func() {}) AfterEach(func() {}) - pollAndReturn := func(exit chan error, dummyEndpoint *v3.WorkloadEndpoint, timeout time.Duration) { + pollAndReturn := func(exit chan error, dummyEndpoint *internalapi.WorkloadEndpoint, timeout time.Duration) { defer close(exit) err := ForEndpointReadyWithTimeout("/tmp", dummyEndpoint, timeout) exit <- err } It("should wait for the given timeout and return error when no file is found", func() { - dummyEndpoint := &v3.WorkloadEndpoint{ + dummyEndpoint := &internalapi.WorkloadEndpoint{ ObjectMeta: v1.ObjectMeta{ Namespace: "name-space", }, - Spec: v3.WorkloadEndpointSpec{ + Spec: internalapi.WorkloadEndpointSpec{ Pod: "podname", Orchestrator: "k8s", Node: "node-1", @@ -63,11 +63,11 @@ var _ = Describe("k8s-wait", func() { }) It("should return immediately after the right file is created", func() { - dummyEndpoint := &v3.WorkloadEndpoint{ + dummyEndpoint := &internalapi.WorkloadEndpoint{ ObjectMeta: v1.ObjectMeta{ Namespace: "name-space", }, - Spec: v3.WorkloadEndpointSpec{ + Spec: internalapi.WorkloadEndpointSpec{ Pod: "podname", Orchestrator: "k8s", Node: "node-1", diff --git a/cni-plugin/tests/calico_cni_ipam_test.go b/cni-plugin/tests/calico_cni_ipam_test.go index 193c1be3c2b..f2c0b67d302 100644 --- a/cni-plugin/tests/calico_cni_ipam_test.go +++ b/cni-plugin/tests/calico_cni_ipam_test.go @@ -8,16 +8,16 @@ import ( "os" "github.com/containernetworking/cni/pkg/types" + cniv1 "github.com/containernetworking/cni/pkg/types/100" "github.com/google/uuid" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" crclient "sigs.k8s.io/controller-runtime/pkg/client" "github.com/projectcalico/calico/cni-plugin/internal/pkg/testutils" apiconfig "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/rawcrdclient" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" @@ -28,12 +28,45 @@ import ( "github.com/projectcalico/calico/libcalico-go/lib/options" ) -var plugin = "calico-ipam" -var defaultIPv4Pool = "192.168.0.0/16" +var ( + plugin = "calico-ipam" + defaultIPv4Pool = "192.168.0.0/16" +) + +// verifyRoutesPopulatedInResult verifies that routes are correctly populated in the IPAM result. +// For normal pods (expectNonEmpty=true), it checks that routes exist for all assigned IPs. +// For migration target pods (expectNonEmpty=false), it checks that routes are empty. +func verifyRoutesPopulatedInResult(result *cniv1.Result, expectNonEmpty bool) { + if expectNonEmpty { + // Normal pods should have routes for all assigned IPs + Expect(result.Routes).NotTo(BeEmpty(), "Routes should be present for assigned IPs") + + // Verify that each assigned IP has a corresponding route whose destination + // network contains the IP. We match by IP containment rather than exact string + // equality because older CNI spec versions (< 0.3.0) normalise the IP mask to + // /32 (or /128) during result conversion while routes keep the block-level mask. + for _, ip := range result.IPs { + found := false + for _, route := range result.Routes { + if route.Dst.Contains(ip.Address.IP) { + found = true + break + } + } + Expect(found).To(BeTrue(), + fmt.Sprintf("Route should exist for IP %s", ip.Address.String())) + } + } else { + // Migration target pods should have empty routes + Expect(result.Routes).To(BeEmpty(), "Routes should be empty for migration target pod") + } +} var _ = Describe("Calico IPAM Tests", func() { cniVersion := os.Getenv("CNI_SPEC_VERSION") - calicoClient, err := client.NewFromEnv() + config, err := apiconfig.LoadClientConfigFromEnvironment() + Expect(err).NotTo(HaveOccurred()) + calicoClient, err := client.New(*config) Expect(err).NotTo(HaveOccurred()) k8sClient := getKubernetesClient() @@ -62,33 +95,35 @@ var _ = Describe("Calico IPAM Tests", func() { Expect(err).NotTo(HaveOccurred()) }) - Describe("Run IPAM plugin", func() { - DescribeTable("Request different numbers of IP addresses", - func(expectedIPv4, expectedIPv6 bool, netconf string) { - result, _, _ := testutils.RunIPAMPlugin(netconf, "ADD", "", cid, cniVersion) - var ip4Mask, ip6Mask string + DescribeTable("Request different numbers of IP addresses", + func(expectedIPv4, expectedIPv6 bool, netconf string) { + result, _, _ := testutils.RunIPAMPlugin(netconf, "ADD", "", cid, cniVersion) + var ip4Mask, ip6Mask string - for _, ip := range result.IPs { - if ip.Address.IP.To4() != nil { - ip4Mask = ip.Address.Mask.String() - } else if ip.Address.IP.To16() != nil { - ip6Mask = ip.Address.Mask.String() - } + for _, ip := range result.IPs { + if ip.Address.IP.To4() != nil { + ip4Mask = ip.Address.Mask.String() + } else if ip.Address.IP.To16() != nil { + ip6Mask = ip.Address.Mask.String() } + } - if expectedIPv4 { - Expect(ip4Mask).Should(Equal("ffffffc0")) - } + if expectedIPv4 { + Expect(ip4Mask).Should(Equal("ffffffc0")) + } - if expectedIPv6 { - Expect(ip6Mask).Should(Equal("ffffffffffffffffffffffffffffffc0")) - } + if expectedIPv6 { + Expect(ip6Mask).Should(Equal("ffffffffffffffffffffffffffffffc0")) + } - _, _, exitCode := testutils.RunIPAMPlugin(netconf, "DEL", "", cid, cniVersion) - Expect(exitCode).Should(Equal(0)) - }, + // Verify that routes are present for each assigned IP + verifyRoutesPopulatedInResult(result, true) + + _, _, exitCode := testutils.RunIPAMPlugin(netconf, "DEL", "", cid, cniVersion) + Expect(exitCode).Should(Equal(0)) + }, - Entry("IPAM with no configuration", true, false, fmt.Sprintf(` + Entry("IPAM with no configuration", true, false, fmt.Sprintf(` { "cniVersion": "%s", "name": "net1", @@ -101,9 +136,10 @@ var _ = Describe("Calico IPAM Tests", func() { "datastore_type": "%s", "ipam": { "type": "%s" - } - }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin)), - Entry("IPAM with IPv4 (explicit)", true, false, fmt.Sprintf(` + }, + "calico_api_group": "%s" + }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin, k8s.BackendAPIGroup(&config.Spec))), + Entry("IPAM with IPv4 (explicit)", true, false, fmt.Sprintf(` { "cniVersion": "%s", "name": "net1", @@ -116,9 +152,10 @@ var _ = Describe("Calico IPAM Tests", func() { "ipam": { "type": "%s", "assign_ipv4": "true" - } - }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin)), - Entry("IPAM with IPv6 only", false, true, fmt.Sprintf(` + }, + "calico_api_group": "%s" + }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin, k8s.BackendAPIGroup(&config.Spec))), + Entry("IPAM with IPv6 only", false, true, fmt.Sprintf(` { "cniVersion": "%s", "name": "net1", @@ -132,9 +169,10 @@ var _ = Describe("Calico IPAM Tests", func() { "type": "%s", "assign_ipv4": "false", "assign_ipv6": "true" - } - }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin)), - Entry("IPAM with IPv4 and IPv6", true, true, fmt.Sprintf(` + }, + "calico_api_group": "%s" + }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin, k8s.BackendAPIGroup(&config.Spec))), + Entry("IPAM with IPv4 and IPv6", true, true, fmt.Sprintf(` { "cniVersion": "%s", "name": "net1", @@ -149,10 +187,11 @@ var _ = Describe("Calico IPAM Tests", func() { "type": "%s", "assign_ipv4": "true", "assign_ipv6": "true" - } - }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin)), - ) - }) + }, + "calico_api_group": "%s" + }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin, k8s.BackendAPIGroup(&config.Spec)), + ), + ) Describe("Run IPAM plugin - No partial assignments in dual stack", func() { Context("With no IPv4 pool", func() { @@ -176,8 +215,9 @@ var _ = Describe("Calico IPAM Tests", func() { "type": "%s", "assign_ipv4": "true", "assign_ipv6": "true" - } - }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin) + }, + "calico_api_group": "%s" + }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin, k8s.BackendAPIGroup(&config.Spec)) _, _, _ = testutils.RunIPAMPlugin(netconf, "ADD", "", cid, cniVersion) // Attempt to assign both an IPv4 and IPv6 address @@ -212,8 +252,9 @@ var _ = Describe("Calico IPAM Tests", func() { "type": "%s", "assign_ipv4": "true", "assign_ipv6": "true" - } - }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin) + }, + "calico_api_group": "%s" + }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin, k8s.BackendAPIGroup(&config.Spec)) _, _, _ = testutils.RunIPAMPlugin(netconf, "ADD", "", cid, cniVersion) // Attempt to assign both an IPv4 and IPv6 address @@ -245,8 +286,9 @@ var _ = Describe("Calico IPAM Tests", func() { "type": "%s", "assign_ipv4": "true", "ipv4_pools": [ "192.168.0.0/16" ] - } - }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin) + }, + "calico_api_group": "%s" + }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin, k8s.BackendAPIGroup(&config.Spec)) result, _, _ := testutils.RunIPAMPlugin(netconf, "ADD", "", cid, cniVersion) Expect(len(result.IPs)).To(Equal(1)) Expect(result.IPs[0].Address.IP.String()).Should(HavePrefix("192.168.")) @@ -270,8 +312,9 @@ var _ = Describe("Calico IPAM Tests", func() { "type": "%s", "assign_ipv4": "true", "ipv4_pools": [ "192.169.1.0/24", "192.168.0.0/16" ] - } - }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin) + }, + "calico_api_group": "%s" + }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin, k8s.BackendAPIGroup(&config.Spec)) result, _, _ := testutils.RunIPAMPlugin(netconf, "ADD", "", cid, cniVersion) Expect(result.IPs[0].Address.IP.String()).Should(Or(HavePrefix("192.168."), HavePrefix("192.169.1"))) }) @@ -292,8 +335,9 @@ var _ = Describe("Calico IPAM Tests", func() { "ipam": { "type": "%s", "assign_ipv4": "true" - } - }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin) + }, + "calico_api_group": "%s" + }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin, k8s.BackendAPIGroup(&config.Spec)) // Get an allocation result, _, _ := testutils.RunIPAMPlugin(netconf, "ADD", "", cid, cniVersion) @@ -320,7 +364,6 @@ var _ = Describe("Calico IPAM Tests", func() { pool.Spec.Disabled = false _, err = calicoClient.IPPools().Update(context.Background(), pool, options.SetOptions{}) Expect(err).ToNot(HaveOccurred()) - }) }) @@ -341,8 +384,9 @@ var _ = Describe("Calico IPAM Tests", func() { "type": "%s", "assign_ipv4": "true", "ipv4_pools": [ "192.168.0.0/16", "192.169.1.0/24" ] - } - }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin) + }, + "calico_api_group": "%s" + }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin, k8s.BackendAPIGroup(&config.Spec)) _, err, _ := testutils.RunIPAMPlugin(netconf, "ADD", "", cid, cniVersion) Expect(err.Msg).Should(ContainSubstring("192.169.1.0/24) does not exist")) }) @@ -363,13 +407,13 @@ var _ = Describe("Calico IPAM Tests", func() { "type": "%s", "assign_ipv4": "true", "ipv4_pools": [ "192.168.0.0/16", "192.169.1.0/24" ] - } - }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin) + }, + "calico_api_group": "%s" + }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin, k8s.BackendAPIGroup(&config.Spec)) _, err, _ := testutils.RunIPAMPlugin(netconf, "ADD", "", cid, cniVersion) Expect(err.Msg).Should(ContainSubstring("192.169.1.0/24) does not exist")) }) }) - }) Describe("Requesting an explicit IP address", func() { @@ -385,8 +429,9 @@ var _ = Describe("Calico IPAM Tests", func() { "datastore_type": "%s", "ipam": { "type": "%s" - } - }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin) + }, + "calico_api_group": "%s" + }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin, k8s.BackendAPIGroup(&config.Spec)) Context("Pass explicit IP address", func() { It("Return the expected IP", func() { result, _, _ := testutils.RunIPAMPlugin(netconf, "ADD", "IP=192.168.123.123", cid, cniVersion) @@ -425,8 +470,9 @@ var _ = Describe("Calico IPAM Tests", func() { "datastore_type": "%s", "ipam": { "type": "%s" - } - }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin) + }, + "calico_api_group": "%s" + }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin, k8s.BackendAPIGroup(&config.Spec)) It("should exit successfully even if no address exists", func() { _, _, exitCode := testutils.RunIPAMPlugin(netconf, "DEL", "IP=192.168.123.123", cid, cniVersion) @@ -495,6 +541,7 @@ var _ = Describe("Calico IPAM Tests", func() { crdClient crclient.Client host string netconf string + useV3 bool ) BeforeEach(func() { @@ -510,7 +557,8 @@ var _ = Describe("Calico IPAM Tests", func() { Expect(err).NotTo(HaveOccurred()) kcfg, _, err := k8s.CreateKubernetesClientset(&cfg.Spec) Expect(err).NotTo(HaveOccurred()) - crdClient, err = rawcrdclient.New(kcfg) + useV3 = k8s.BackendAPIGroup(&config.Spec) == "projectcalico.org/v3" + crdClient, err = rawcrdclient.New(kcfg, useV3) Expect(err).NotTo(HaveOccurred()) // Determine host for BlockAffinity and ensure there is a usable netconf. @@ -531,8 +579,9 @@ var _ = Describe("Calico IPAM Tests", func() { "log_level": "debug", "ipam": { "type": "%s" - } - }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin) + }, + "calico_api_group": "%s" + }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), plugin, k8s.BackendAPIGroup(&config.Spec)) }) It("upgrades unlabeled block affinities on first call and not on subsequent calls", func() { @@ -540,18 +589,37 @@ var _ = Describe("Calico IPAM Tests", func() { // 1) Create an unlabeled BlockAffinity for this host. Expect(ipamtestutils.CreateUnlabeledBlockAffinity(ctx, crdClient, host, "10.11.0.0/26")).To(Succeed()) - // Sanity check: ensure the BA has no labels. - var list libapiv3.BlockAffinityList - Expect(crdClient.List(ctx, &list)).To(Succeed()) - var found1 *libapiv3.BlockAffinity - for i := range list.Items { - if list.Items[i].Spec.Node == host && list.Items[i].Spec.CIDR == "10.11.0.0/26" { - found1 = &list.Items[i] - break + getLabels := func(cidr string) map[string]string { + if useV3 { + var list apiv3.BlockAffinityList + Expect(crdClient.List(ctx, &list)).To(Succeed()) + var found1 *apiv3.BlockAffinity + for i := range list.Items { + if list.Items[i].Spec.Node == host && list.Items[i].Spec.CIDR == cidr { + found1 = &list.Items[i] + break + } + } + ExpectWithOffset(1, found1).NotTo(BeNil()) + return found1.Labels + } else { + var list internalapi.BlockAffinityList + Expect(crdClient.List(ctx, &list)).To(Succeed()) + var found1 *internalapi.BlockAffinity + for i := range list.Items { + if list.Items[i].Spec.Node == host && list.Items[i].Spec.CIDR == cidr { + found1 = &list.Items[i] + break + } + } + ExpectWithOffset(1, found1).NotTo(BeNil()) + return found1.Labels } } - Expect(found1).NotTo(BeNil()) - Expect(found1.Labels).To(HaveLen(0)) + + // Sanity check: ensure the BA has no labels. + foundLabels := getLabels("10.11.0.0/26") + Expect(foundLabels).To(HaveLen(0)) // 2) Run the IPAM plugin with explicit IP to trigger maybeUpgradeIPAM. cid1 := uuid.NewString() @@ -564,33 +632,16 @@ var _ = Describe("Calico IPAM Tests", func() { Expect(statErr).NotTo(HaveOccurred()) // 4) Verify the previously unlabeled BA is now labeled. - list = libapiv3.BlockAffinityList{} - Expect(crdClient.List(ctx, &list)).To(Succeed()) - found1 = nil - for i := range list.Items { - if list.Items[i].Spec.Node == host && list.Items[i].Spec.CIDR == "10.11.0.0/26" { - found1 = &list.Items[i] - break - } - } - Expect(found1).NotTo(BeNil()) - Expect(found1.Labels).To(HaveKey(apiv3.LabelAffinityType)) - Expect(found1.Labels).To(HaveKey(apiv3.LabelIPVersion)) - Expect(found1.Labels).To(HaveKey(apiv3.LabelHostnameHash)) + foundLabels = getLabels("10.11.0.0/26") + Expect(foundLabels).To(HaveKey(apiv3.LabelAffinityType)) + Expect(foundLabels).To(HaveKey(apiv3.LabelIPVersion)) + Expect(foundLabels).To(HaveKey(apiv3.LabelHostnameHash)) // 5) Create a second unlabeled BA for this host. Expect(ipamtestutils.CreateUnlabeledBlockAffinity(ctx, crdClient, host, "10.11.0.64/26")).To(Succeed()) - list = libapiv3.BlockAffinityList{} - Expect(crdClient.List(ctx, &list)).To(Succeed()) - var found2 *libapiv3.BlockAffinity - for i := range list.Items { - if list.Items[i].Spec.Node == host && list.Items[i].Spec.CIDR == "10.11.0.64/26" { - found2 = &list.Items[i] - break - } - } - Expect(found2).NotTo(BeNil()) - Expect(found2.Labels).To(HaveLen(0)) + + labels2 := getLabels("10.11.0.64/26") + Expect(labels2).To(HaveLen(0)) // 6) Run the plugin again; since touchfile exists, no upgrade should occur. cid2 := uuid.NewString() @@ -599,17 +650,8 @@ var _ = Describe("Calico IPAM Tests", func() { Expect(result2.IPs).To(HaveLen(1)) // Re-list and ensure the second BA remains unlabeled. - list = libapiv3.BlockAffinityList{} - Expect(crdClient.List(ctx, &list)).To(Succeed()) - found2 = nil - for i := range list.Items { - if list.Items[i].Spec.Node == host && list.Items[i].Spec.CIDR == "10.11.0.64/26" { - found2 = &list.Items[i] - break - } - } - Expect(found2).NotTo(BeNil()) - Expect(found2.Labels).To(HaveLen(0)) + labels2 = getLabels("10.11.0.64/26") + Expect(labels2).To(HaveLen(0)) // Cleanup: release explicit IPs. _, _, exit = testutils.RunIPAMPlugin(netconf, "DEL", "IP=192.168.123.123", cid1, os.Getenv("CNI_SPEC_VERSION")) diff --git a/cni-plugin/tests/calico_cni_k8s_test.go b/cni-plugin/tests/calico_cni_k8s_test.go index 1904c163ae1..1f85f6877c9 100644 --- a/cni-plugin/tests/calico_cni_k8s_test.go +++ b/cni-plugin/tests/calico_cni_k8s_test.go @@ -22,7 +22,7 @@ import ( "github.com/containernetworking/plugins/pkg/ns" cnitestutils "github.com/containernetworking/plugins/pkg/testutils" "github.com/mcuadros/go-version" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -36,7 +36,9 @@ import ( "github.com/projectcalico/calico/cni-plugin/internal/pkg/testutils" "github.com/projectcalico/calico/cni-plugin/internal/pkg/utils" "github.com/projectcalico/calico/cni-plugin/pkg/types" - libapi "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + apiconfig "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" + "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s" k8sconversion "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/ipam" @@ -162,7 +164,9 @@ func getKubernetesClient() *kubernetes.Clientset { var _ = Describe("Kubernetes CNI tests", func() { ctx := context.Background() - calicoClient, err := client.NewFromEnv() + config, err := apiconfig.LoadClientConfigFromEnvironment() + Expect(err).NotTo(HaveOccurred()) + calicoClient, err := client.New(*config) Expect(err).NotTo(HaveOccurred()) k8sClient := getKubernetesClient() @@ -212,8 +216,9 @@ var _ = Describe("Kubernetes CNI tests", func() { "policy": {"type": "k8s"}, "nodename_file_optional": true, "log_level":"debug", - "nodename": "%s" - }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), testNodeName) + "nodename": "%s", + "calico_api_group": "%s" + }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), testNodeName, k8s.BackendAPIGroup(&config.Spec)) }) It("successfully networks the namespace", func() { @@ -279,7 +284,7 @@ var _ = Describe("Kubernetes CNI tests", func() { "projectcalico.org/serviceaccount": "default", })) - Expect(endpoints.Items[0].Spec).Should(Equal(libapi.WorkloadEndpointSpec{ + Expect(endpoints.Items[0].Spec).Should(Equal(internalapi.WorkloadEndpointSpec{ Pod: testPodName, InterfaceName: interfaceName, IPNetworks: []string{result.IPs[0].Address.String()}, @@ -442,7 +447,7 @@ var _ = Describe("Kubernetes CNI tests", func() { "projectcalico.org/orchestrator": api.OrchestratorKubernetes, "projectcalico.org/serviceaccount": "default", })) - Expect(endpoints.Items[0].Spec).Should(Equal(libapi.WorkloadEndpointSpec{ + Expect(endpoints.Items[0].Spec).Should(Equal(internalapi.WorkloadEndpointSpec{ Pod: testPodName, InterfaceName: interfaceName, IPNetworks: []string{result.IPs[0].Address.String()}, @@ -454,7 +459,7 @@ var _ = Describe("Kubernetes CNI tests", func() { Workload: "", ContainerID: containerID, Orchestrator: api.OrchestratorKubernetes, - Ports: []libapi.WorkloadEndpointPort{{ + Ports: []internalapi.WorkloadEndpointPort{{ Name: "anamedport", Protocol: numorstring.ProtocolFromString("TCP"), Port: 555, @@ -538,7 +543,8 @@ var _ = Describe("Kubernetes CNI tests", func() { "policy": {"type": "k8s"}, "nodename_file_optional": true, "log_level":"debug", - "nodename": "%s" + "nodename": "%s", + "calico_api_group": "%s" }` It("should create pods with the right MTU", func() { @@ -548,13 +554,13 @@ var _ = Describe("Kubernetes CNI tests", func() { err = os.MkdirAll("/var/lib/calico", os.ModePerm) Expect(err).NotTo(HaveOccurred()) - err = os.WriteFile(utils.MTUFilePath, []byte("3000"), 0644) + err = os.WriteFile(utils.MTUFilePath, []byte("3000"), 0o644) Expect(err).NotTo(HaveOccurred()) defer func() { _ = os.Remove(utils.MTUFilePath) }() // Create a K8s pod/container name1 := fmt.Sprintf("mtutest%d", rand.Uint32()) - mtuNetconf1 := fmt.Sprintf(mtuNetconfTemplate, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), testNodeName) + mtuNetconf1 := fmt.Sprintf(mtuNetconfTemplate, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), testNodeName, k8s.BackendAPIGroup(&config.Spec)) ensurePodCreated(clientset, testutils.K8S_TEST_NS, &v1.Pod{ ObjectMeta: metav1.ObjectMeta{Name: name1}, @@ -598,7 +604,8 @@ var _ = Describe("Kubernetes CNI tests", func() { "policy": {"type": "k8s"}, "nodename_file_optional": true, "log_level":"debug", - "nodename": "%s" + "nodename": "%s", + "calico_api_group": "%s" }` It("creates pods with the new mtu", func() { @@ -606,7 +613,7 @@ var _ = Describe("Kubernetes CNI tests", func() { // Create a K8s pod/container with non-default MTU name1 := fmt.Sprintf("mtutest%d", rand.Uint32()) - mtuNetconf1 := fmt.Sprintf(mtuNetconfTemplate, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), 3000, testNodeName) + mtuNetconf1 := fmt.Sprintf(mtuNetconfTemplate, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), 3000, testNodeName, k8s.BackendAPIGroup(&config.Spec)) ensurePodCreated(clientset, testutils.K8S_TEST_NS, &v1.Pod{ ObjectMeta: metav1.ObjectMeta{Name: name1}, @@ -626,7 +633,7 @@ var _ = Describe("Kubernetes CNI tests", func() { // Create another K8s pod/container with a different non-default MTU name2 := fmt.Sprintf("mtutest2%d", rand.Uint32()) - mtuNetconf2 := fmt.Sprintf(mtuNetconfTemplate, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), 4000, testNodeName) + mtuNetconf2 := fmt.Sprintf(mtuNetconfTemplate, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), 4000, testNodeName, k8s.BackendAPIGroup(&config.Spec)) ensurePodCreated(clientset, testutils.K8S_TEST_NS, &v1.Pod{ ObjectMeta: metav1.ObjectMeta{Name: name2}, @@ -678,7 +685,8 @@ var _ = Describe("Kubernetes CNI tests", func() { }, "policy": {"type": "k8s"}, "log_level":"debug", - "nodename": "%s" + "nodename": "%s", + "calico_api_group": "%s" }`, expectedV4Routes: []string{ regexp.QuoteMeta("default via 169.254.1.1 dev eth0"), @@ -721,7 +729,8 @@ var _ = Describe("Kubernetes CNI tests", func() { }, "policy": {"type": "k8s"}, "log_level":"debug", - "nodename": "%s" + "nodename": "%s", + "calico_api_group": "%s" }`, expectedV4Routes: []string{ regexp.QuoteMeta("default via 169.254.1.1 dev eth0"), @@ -769,7 +778,8 @@ var _ = Describe("Kubernetes CNI tests", func() { }, "policy": {"type": "k8s"}, "log_level":"debug", - "nodename": "%s" + "nodename": "%s", + "calico_api_group": "%s" }`, expectedV4Routes: []string{ regexp.QuoteMeta("default via 169.254.1.1 dev eth0"), @@ -827,11 +837,12 @@ var _ = Describe("Kubernetes CNI tests", func() { ] }, "kubernetes": { - "kubeconfig": "/home/user/certs/kubeconfig" + "kubeconfig": "/home/user/certs/kubeconfig" }, "policy": {"type": "k8s"}, "log_level":"debug", - "nodename": "%s" + "nodename": "%s", + "calico_api_group": "%s" }`, expectedV4Routes: []string{ regexp.QuoteMeta("10.123.0.0/16 via 169.254.1.1 dev eth0"), @@ -893,7 +904,8 @@ var _ = Describe("Kubernetes CNI tests", func() { }, "policy": {"type": "k8s"}, "log_level":"debug", - "nodename": "%s" + "nodename": "%s", + "calico_api_group": "%s" }`, expectedV4Routes: []string{ regexp.QuoteMeta("default via 169.254.1.1 dev eth0"), @@ -914,14 +926,13 @@ var _ = Describe("Kubernetes CNI tests", func() { // Run tests with PodCIDR for _, c := range hostLocalIPAMConfigs { - c := c // Make sure we get a fresh variable on each loop. // The dual-stack requires PodCIDRs if strings.Contains(c.config, "usePodCidrIPv6") { continue } Context("Using host-local IPAM with one PodCIDR ("+c.description+"): request an IP then release it, and then request it again", func() { It("should successfully assign IP both times and successfully release it in the middle", func() { - netconfHostLocalIPAM := fmt.Sprintf(c.config, c.cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), c.nodename) + netconfHostLocalIPAM := fmt.Sprintf(c.config, c.cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), c.nodename, k8s.BackendAPIGroup(&config.Spec)) clientset := getKubernetesClient() @@ -1029,10 +1040,9 @@ var _ = Describe("Kubernetes CNI tests", func() { // Run tests with PodCIDRs defining a dual-stack deployment for _, c := range hostLocalIPAMConfigs { - c := c // Make sure we get a fresh variable on each loop. Context("Using host-local IPAM with two PodCIDRs ("+c.description+"): request an IP then release it, and then request it again", func() { It("should successfully assign IP both times and successfully release it in the middle", func() { - netconfHostLocalIPAM := fmt.Sprintf(c.config, c.cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), c.nodename) + netconfHostLocalIPAM := fmt.Sprintf(c.config, c.cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), c.nodename, k8s.BackendAPIGroup(&config.Spec)) clientset := getKubernetesClient() @@ -1162,6 +1172,7 @@ var _ = Describe("Kubernetes CNI tests", func() { NodenameFileOptional: true, LogLevel: "debug", Nodename: testNodeName, + CalicoAPIGroup: k8s.BackendAPIGroup(&config.Spec), } nc.IPAM.Type = "calico-ipam" ncb, err := json.Marshal(nc) @@ -1406,6 +1417,7 @@ var _ = Describe("Kubernetes CNI tests", func() { NodenameFileOptional: true, LogLevel: "debug", Nodename: testNodeName, + CalicoAPIGroup: k8s.BackendAPIGroup(&config.Spec), } nc.IPAM.Type = "calico-ipam" ncb, err := json.Marshal(nc) @@ -1493,6 +1505,7 @@ var _ = Describe("Kubernetes CNI tests", func() { NodenameFileOptional: true, LogLevel: "debug", Nodename: testNodeName, + CalicoAPIGroup: k8s.BackendAPIGroup(&config.Spec), } nc.IPAM.Type = "calico-ipam" ncb, err := json.Marshal(nc) @@ -1598,6 +1611,7 @@ var _ = Describe("Kubernetes CNI tests", func() { NodenameFileOptional: true, LogLevel: "debug", Nodename: testNodeName, + CalicoAPIGroup: k8s.BackendAPIGroup(&config.Spec), } netconf.IPAM.Type = "calico-ipam" @@ -1678,6 +1692,7 @@ var _ = Describe("Kubernetes CNI tests", func() { LogLevel: "debug", FeatureControl: types.FeatureControl{FloatingIPs: true}, Nodename: testNodeName, + CalicoAPIGroup: k8s.BackendAPIGroup(&config.Spec), } netconf.IPAM.Type = "calico-ipam" @@ -1737,7 +1752,7 @@ var _ = Describe("Kubernetes CNI tests", func() { // Assert that the endpoint contains the appropriate DNAT podIP := contAddresses[0].IP Expect(endpoints.Items[0].Spec.IPNATs).Should(HaveLen(1)) - Expect(endpoints.Items[0].Spec.IPNATs).Should(Equal([]libapi.IPNAT{{InternalIP: podIP.String(), ExternalIP: "1.1.1.1"}})) + Expect(endpoints.Items[0].Spec.IPNATs).Should(Equal([]internalapi.IPNAT{{InternalIP: podIP.String(), ExternalIP: "1.1.1.1"}})) // Delete the container. _, err = testutils.DeleteContainer(string(confBytes), contNs.Path(), testPodName, testutils.K8S_TEST_NS) @@ -1789,6 +1804,7 @@ var _ = Describe("Kubernetes CNI tests", func() { LogLevel: "debug", FeatureControl: types.FeatureControl{IPAddrsNoIpam: true}, Nodename: testNodeName, + CalicoAPIGroup: k8s.BackendAPIGroup(&config.Spec), } nc.IPAM.Type = "calico-ipam" Expect(nc.CNIVersion).NotTo(BeEmpty()) @@ -1861,7 +1877,7 @@ var _ = Describe("Kubernetes CNI tests", func() { "projectcalico.org/serviceaccount": "default", })) - Expect(endpoints.Items[0].Spec).Should(Equal(libapi.WorkloadEndpointSpec{ + Expect(endpoints.Items[0].Spec).Should(Equal(internalapi.WorkloadEndpointSpec{ Pod: testPodName, InterfaceName: interfaceName, IPNetworks: []string{assignIP.String() + "/32"}, @@ -1978,8 +1994,9 @@ var _ = Describe("Kubernetes CNI tests", func() { }, "policy": {"type": "k8s"}, "log_level":"debug", - "nodename": "%s" - }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), testNodeName) + "nodename": "%s", + "calico_api_group": "%s" + }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), testNodeName, k8s.BackendAPIGroup(&config.Spec)) assignIP := net.IPv4(20, 0, 0, 111).To4() @@ -2046,7 +2063,7 @@ var _ = Describe("Kubernetes CNI tests", func() { "projectcalico.org/serviceaccount": "default", })) - Expect(endpoints.Items[0].Spec).Should(Equal(libapi.WorkloadEndpointSpec{ + Expect(endpoints.Items[0].Spec).Should(Equal(internalapi.WorkloadEndpointSpec{ Pod: testPodName, InterfaceName: interfaceName, IPNetworks: []string{assignIP.String() + "/32"}, @@ -2112,8 +2129,9 @@ var _ = Describe("Kubernetes CNI tests", func() { }, "policy": {"type": "k8s"}, "log_level":"debug", - "nodename": "%s" - }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), testNodeName) + "nodename": "%s", + "calico_api_group": "%s" + }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), testNodeName, k8s.BackendAPIGroup(&config.Spec)) // Now create a K8s pod (without any pod IP annotations). ensurePodCreated(clientset, testutils.K8S_TEST_NS, &v1.Pod{ @@ -2133,7 +2151,7 @@ var _ = Describe("Kubernetes CNI tests", func() { }) defer ensurePodDeleted(clientset, testutils.K8S_TEST_NS, testPodName) - containerID, _, contVeth, contAddresses, _, netNS, err := testutils.CreateContainer(netconfCalicoIPAM, testPodName, testutils.K8S_TEST_NS, "") + containerID, result, contVeth, contAddresses, _, netNS, err := testutils.CreateContainer(netconfCalicoIPAM, testPodName, testutils.K8S_TEST_NS, "") Expect(err).NotTo(HaveOccurred()) mac := contVeth.Attrs().HardwareAddr @@ -2143,6 +2161,11 @@ var _ = Describe("Kubernetes CNI tests", func() { podIPv6 := contAddresses[1].IP Expect(podIPv6.To16()).NotTo(BeNil()) + // Verify that the IPAM plugin populates routes for both IPv4 and IPv6 allocated IPs. + Expect(result.IPs).Should(HaveLen(2)) + Expect(result.Routes).Should(HaveLen(2)) + verifyRoutesPopulatedInResult(result, true) + ids := names.WorkloadEndpointIdentifiers{ Node: testNodeName, Orchestrator: api.OrchestratorKubernetes, @@ -2175,7 +2198,7 @@ var _ = Describe("Kubernetes CNI tests", func() { "projectcalico.org/serviceaccount": "default", })) - Expect(endpoints.Items[0].Spec).Should(Equal(libapi.WorkloadEndpointSpec{ + Expect(endpoints.Items[0].Spec).Should(Equal(internalapi.WorkloadEndpointSpec{ Pod: testPodName, InterfaceName: interfaceName, ServiceAccountName: "default", @@ -2185,7 +2208,7 @@ var _ = Describe("Kubernetes CNI tests", func() { Node: testNodeName, Endpoint: "eth0", Workload: "", - IPNATs: []libapi.IPNAT{ + IPNATs: []internalapi.IPNAT{ { InternalIP: podIPv4.String(), ExternalIP: "1.1.1.1", @@ -2234,6 +2257,7 @@ var _ = Describe("Kubernetes CNI tests", func() { NodenameFileOptional: true, LogLevel: "debug", Nodename: testNodeName, + CalicoAPIGroup: k8s.BackendAPIGroup(&config.Spec), } nc.IPAM.Type = "calico-ipam" ncb, err := json.Marshal(nc) @@ -2474,7 +2498,7 @@ var _ = Describe("Kubernetes CNI tests", func() { var netconf string var clientset *kubernetes.Clientset var workloadName, containerID string - var endpointSpec libapi.WorkloadEndpointSpec + var endpointSpec internalapi.WorkloadEndpointSpec var contNs ns.NetNS var result *cniv1.Result @@ -2505,6 +2529,7 @@ var _ = Describe("Kubernetes CNI tests", func() { NodenameFileOptional: true, LogLevel: "debug", Nodename: testNodeName, + CalicoAPIGroup: k8s.BackendAPIGroup(&config.Spec), } nc.IPAM.Type = "calico-ipam" ncb, err := json.Marshal(nc) @@ -2531,6 +2556,12 @@ var _ = Describe("Kubernetes CNI tests", func() { containerID, result, _, _, _, contNs, err = testutils.CreateContainer(netconf, testPodName, testutils.K8S_TEST_NS, "") Expect(err).ShouldNot(HaveOccurred()) + // Verify that IPAM populates routes in the result (one route per allocated IP). + Expect(result.IPs).Should(HaveLen(1)) + Expect(result.Routes).Should(HaveLen(1)) + Expect(result.Routes[0].Dst.IP.Equal(result.IPs[0].Address.IP)).Should(BeTrue(), + "Route Dst IP should match the allocated IP") + // The endpoint is created in etcd endpoints, err := calicoClient.WorkloadEndpoints().List(ctx, options.ListOptions{}) Expect(err).ShouldNot(HaveOccurred()) @@ -2578,6 +2609,10 @@ var _ = Describe("Kubernetes CNI tests", func() { resultSecondAdd.IPs = nil result.IPs = nil + // Routes are derived from the allocated IPs, so they will differ too. + resultSecondAdd.Routes = nil + result.Routes = nil + // The MAC address will be different, since we create a new veth. Expect(len(resultSecondAdd.Interfaces)).Should(Equal(len(result.Interfaces))) for i := range resultSecondAdd.Interfaces { @@ -2650,8 +2685,9 @@ var _ = Describe("Kubernetes CNI tests", func() { "kubeconfig": "/home/user/certs/kubeconfig" }, "policy": {"type": "k8s"}, - "nodename": "%s" - }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), testNodeName) + "nodename": "%s", + "calico_api_group": "%s" + }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), testNodeName, k8s.BackendAPIGroup(&config.Spec)) }) It("should successfully execute both ADDs but for second ADD will return the same result as the first time but it won't network the container", func() { // Create a new ipPool. @@ -2843,6 +2879,7 @@ var _ = Describe("Kubernetes CNI tests", func() { NodenameFileOptional: true, LogLevel: "debug", Nodename: testNodeName, + CalicoAPIGroup: k8s.BackendAPIGroup(&config.Spec), } nc.IPAM.Type = "calico-ipam" ncb, err := json.Marshal(nc) @@ -2940,7 +2977,7 @@ var _ = Describe("Kubernetes CNI tests", func() { "projectcalico.org/serviceaccount": saName, "projectcalico.org/orchestrator": api.OrchestratorKubernetes, })) - Expect(endpoints.Items[0].Spec).Should(Equal(libapi.WorkloadEndpointSpec{ + Expect(endpoints.Items[0].Spec).Should(Equal(internalapi.WorkloadEndpointSpec{ Pod: testPodName, InterfaceName: interfaceName, IPNetworks: []string{result.IPs[0].Address.String()}, @@ -2952,7 +2989,7 @@ var _ = Describe("Kubernetes CNI tests", func() { Workload: "", ContainerID: containerID, Orchestrator: api.OrchestratorKubernetes, - Ports: []libapi.WorkloadEndpointPort{{ + Ports: []internalapi.WorkloadEndpointPort{{ Name: "anamedport", Protocol: numorstring.ProtocolFromString("TCP"), Port: 555, @@ -3008,6 +3045,7 @@ var _ = Describe("Kubernetes CNI tests", func() { NodenameFileOptional: true, LogLevel: "debug", Nodename: testNodeName, + CalicoAPIGroup: k8s.BackendAPIGroup(&config.Spec), } nc.IPAM.Type = "calico-ipam" ncb, err := json.Marshal(nc) @@ -3091,7 +3129,7 @@ var _ = Describe("Kubernetes CNI tests", func() { Expect(endpoints.Items[0].GenerateName).Should(Equal(generateName)) // Let's just check that the Spec is good too. - Expect(endpoints.Items[0].Spec).Should(Equal(libapi.WorkloadEndpointSpec{ + Expect(endpoints.Items[0].Spec).Should(Equal(internalapi.WorkloadEndpointSpec{ Pod: testPodName, InterfaceName: interfaceName, ServiceAccountName: "default", @@ -3103,7 +3141,7 @@ var _ = Describe("Kubernetes CNI tests", func() { Workload: "", ContainerID: containerID, Orchestrator: api.OrchestratorKubernetes, - Ports: []libapi.WorkloadEndpointPort{{ + Ports: []internalapi.WorkloadEndpointPort{{ Name: "anamedport", Protocol: numorstring.ProtocolFromString("TCP"), Port: 555, @@ -3139,8 +3177,9 @@ var _ = Describe("Kubernetes CNI tests", func() { "kubeconfig": "/home/user/certs/kubeconfig" }, "policy": {"type": "k8s"}, - "nodename": "%s" - }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), testNodeName) + "nodename": "%s", + "calico_api_group": "%s" + }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), testNodeName, k8s.BackendAPIGroup(&config.Spec)) }) It("should fail container creation", func() { // Create a new ipPool. @@ -3201,8 +3240,9 @@ var _ = Describe("Kubernetes CNI tests", func() { "kubeconfig": "/home/user/certs/kubeconfig" }, "policy": {"type": "k8s"}, - "nodename": "%s" - }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), testEndpoint, testNodeName) + "nodename": "%s", + "calico_api_group": "%s" + }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), testEndpoint, testNodeName, k8s.BackendAPIGroup(&config.Spec)) }) AfterEach(func() { @@ -3242,7 +3282,7 @@ var _ = Describe("Kubernetes CNI tests", func() { }) Describe("testConnection tests", func() { - It("successfully connects to the datastore", func(done Done) { + It("successfully connects to the datastore", func() { netconf := fmt.Sprintf(` { "cniVersion": "%s", @@ -3260,8 +3300,9 @@ var _ = Describe("Kubernetes CNI tests", func() { "policy": {"type": "k8s"}, "nodename_file_optional": true, "log_level":"debug", - "nodename": "%s" - }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), testNodeName) + "nodename": "%s", + "calico_api_group": "%s" + }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), testNodeName, k8s.BackendAPIGroup(&config.Spec)) pluginPath := fmt.Sprintf("%s/%s", os.Getenv("BIN"), os.Getenv("PLUGIN")) c := exec.Command(pluginPath, "-t") stdin, err := c.StdinPipe() @@ -3274,10 +3315,9 @@ var _ = Describe("Kubernetes CNI tests", func() { _, err = c.CombinedOutput() Expect(err).ToNot(HaveOccurred()) - close(done) - }, 10) + }) - It("reports it cannot connect to the datastore", func(done Done) { + It("reports it cannot connect to the datastore", func() { // wrong port(s). netconf := fmt.Sprintf(` { @@ -3297,8 +3337,9 @@ var _ = Describe("Kubernetes CNI tests", func() { "policy": {"type": "k8s"}, "nodename_file_optional": true, "log_level":"debug", - "nodename": "%s" - }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), testNodeName) + "nodename": "%s", + "calico_api_group": "%s" + }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), testNodeName, k8s.BackendAPIGroup(&config.Spec)) pluginPath := fmt.Sprintf("%s/%s", os.Getenv("BIN"), os.Getenv("PLUGIN")) c := exec.Command(pluginPath, "-t") stdin, err := c.StdinPipe() @@ -3311,8 +3352,7 @@ var _ = Describe("Kubernetes CNI tests", func() { _, err = c.CombinedOutput() Expect(err).To(HaveOccurred()) - close(done) - }, 10) + }) }) Describe("using hwAddr annotations to assign a fixed MAC address to a container veth", func() { @@ -3336,8 +3376,9 @@ var _ = Describe("Kubernetes CNI tests", func() { "policy": {"type": "k8s"}, "nodename_file_optional": true, "log_level":"debug", - "nodename": "%s" - }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), testNodeName) + "nodename": "%s", + "calico_api_group": "%s" + }`, cniVersion, os.Getenv("ETCD_IP"), os.Getenv("DATASTORE_TYPE"), testNodeName, k8s.BackendAPIGroup(&config.Spec)) name = generateName("test-pod") }) @@ -3404,10 +3445,10 @@ var _ = Describe("Kubernetes CNI tests", func() { var netconf string var ipPool4CIDR *net.IPNet var ipPool6CIDR *net.IPNet - var ipPool4 = "50.80.0.0/16" - var ipPool6 = "fd80:50::/96" + ipPool4 := "50.80.0.0/16" + ipPool6 := "fd80:50::/96" var clientset *kubernetes.Clientset - var testNS = testutils.K8S_TEST_NS + testNS := testutils.K8S_TEST_NS BeforeEach(func() { if version.Compare(cniVersion, "0.3.0", "<") { @@ -3425,6 +3466,7 @@ var _ = Describe("Kubernetes CNI tests", func() { NodenameFileOptional: true, LogLevel: "debug", Nodename: testNodeName, + CalicoAPIGroup: k8s.BackendAPIGroup(&config.Spec), } nc.IPAM.Type = "calico-ipam" ncb, err := json.Marshal(nc) @@ -3446,7 +3488,6 @@ var _ = Describe("Kubernetes CNI tests", func() { // Create a new IP Pool. testutils.MustCreateNewIPPool(calicoClient, ipPool4, false, false, true) testutils.MustCreateNewIPPool(calicoClient, ipPool6, false, false, true) - }) AfterEach(func() { @@ -3496,6 +3537,11 @@ var _ = Describe("Kubernetes CNI tests", func() { Expect(len(interfaces)).Should(Equal(2)) Expect(len(result.IPs)).Should(Equal(1)) + // Verify that the IPAM plugin populates routes for the allocated IPv4 IP. + Expect(result.Routes).Should(HaveLen(1)) + Expect(result.Routes[0].Dst.IP.Equal(result.IPs[0].Address.IP)).Should(BeTrue(), + "Route Dst IP should match the allocated IP") + for _, ip := range result.IPs { interfaceIndex := ip.Interface Expect(interfaceIndex).ShouldNot(BeNil()) @@ -3555,6 +3601,10 @@ var _ = Describe("Kubernetes CNI tests", func() { Expect(len(interfaces)).Should(Equal(2)) Expect(len(result.IPs)).Should(Equal(2)) + // Verify that the IPAM plugin populates routes for both IPv4 and IPv6 allocated IPs. + Expect(result.Routes).Should(HaveLen(2)) + verifyRoutesPopulatedInResult(result, true) + for _, ip := range result.IPs { interfaceIndex := ip.Interface Expect(interfaceIndex).ShouldNot(BeNil()) diff --git a/cni-plugin/tests/calico_cni_suite_test.go b/cni-plugin/tests/calico_cni_suite_test.go index f5400f9d3e3..bdd971c4fc9 100644 --- a/cni-plugin/tests/calico_cni_suite_test.go +++ b/cni-plugin/tests/calico_cni_suite_test.go @@ -5,9 +5,8 @@ package main_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -17,7 +16,8 @@ func init() { } func TestCalicoCni(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/cni_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "CNI suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/cni_suite.xml" + ginkgo.RunSpecs(t, "CNI suite", suiteConfig, reporterConfig) } diff --git a/cni-plugin/tests/calico_cni_test.go b/cni-plugin/tests/calico_cni_test.go index 4feae03c1d3..7cce0e21aed 100644 --- a/cni-plugin/tests/calico_cni_test.go +++ b/cni-plugin/tests/calico_cni_test.go @@ -16,7 +16,7 @@ import ( cniv1 "github.com/containernetworking/cni/pkg/types/100" "github.com/containernetworking/plugins/pkg/ns" "github.com/mcuadros/go-version" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/vishvananda/netlink" @@ -26,7 +26,7 @@ import ( grpc_dataplane "github.com/projectcalico/calico/cni-plugin/pkg/dataplane/grpc" "github.com/projectcalico/calico/cni-plugin/pkg/dataplane/grpc/proto" "github.com/projectcalico/calico/cni-plugin/pkg/dataplane/linux" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/names" "github.com/projectcalico/calico/libcalico-go/lib/netlinkutils" @@ -45,7 +45,7 @@ var _ = Describe("CalicoCni", func() { testutils.WipeDatastore() // Create the node for these tests. The IPAM code requires a corresponding Calico node to exist. var err error - n := libapiv3.NewNode() + n := internalapi.NewNode() n.Name, err = names.Hostname() Expect(err).NotTo(HaveOccurred()) _, err = calicoClient.Nodes().Create(context.Background(), n, options.SetOptions{}) @@ -121,7 +121,7 @@ var _ = Describe("CalicoCni", func() { mac := contVeth.Attrs().HardwareAddr - Expect(endpoints.Items[0].Spec).Should(Equal(libapiv3.WorkloadEndpointSpec{ + Expect(endpoints.Items[0].Spec).Should(Equal(internalapi.WorkloadEndpointSpec{ InterfaceName: fmt.Sprintf("cali%s", containerID), IPNetworks: []string{result.IPs[0].Address.String()}, MAC: mac.String(), @@ -379,7 +379,7 @@ var _ = Describe("CalicoCni", func() { }) Context("With a gRPC dataplane", func() { - It("communicates with the dataplane", func(done Done) { + It("communicates with the dataplane", func() { var contNs ns.NetNS var grpcBackend *grpc_dataplane.TestServer var exitCode int @@ -456,8 +456,7 @@ var _ = Describe("CalicoCni", func() { if err != nil && !strings.Contains(err.Error(), "no such file or directory") { Expect(err).NotTo(HaveOccurred()) } - close(done) - }, 30.0) + }) }) Context("deprecate hostname for nodename", func() { @@ -713,7 +712,7 @@ var _ = Describe("CalicoCni", func() { var containerID string var workloadName string - var endpointSpec libapiv3.WorkloadEndpointSpec + var endpointSpec internalapi.WorkloadEndpointSpec var contNs ns.NetNS var result *cniv1.Result @@ -735,6 +734,12 @@ var _ = Describe("CalicoCni", func() { containerID, result, _, _, _, contNs, err = testutils.CreateContainerWithId(netconf, "", testutils.TEST_DEFAULT_NS, "", "badbeef") Expect(err).ShouldNot(HaveOccurred()) + // Verify that IPAM populates routes in the result (one route per allocated IP). + Expect(result.IPs).Should(HaveLen(1)) + Expect(result.Routes).Should(HaveLen(1)) + Expect(result.Routes[0].Dst.IP.Equal(result.IPs[0].Address.IP)).Should(BeTrue(), + "Route Dst IP should match the allocated IP") + // The endpoint is created in etcd endpoints, err := calicoClient.WorkloadEndpoints().List(ctx, options.ListOptions{Namespace: "default"}) Expect(err).ShouldNot(HaveOccurred()) @@ -775,6 +780,10 @@ var _ = Describe("CalicoCni", func() { // Try to create the same container (so CNI receives the ADD for the same endpoint again) resultSecondAdd, _, _, _, err := testutils.RunCNIPluginWithId(netconf, "", testutils.TEST_DEFAULT_NS, "", containerID, "eth0", contNs) Expect(err).ShouldNot(HaveOccurred()) + // Routes are generated by the IPAM plugin on first ADD but not on the no-op + // second ADD (which skips IPAM). Nullify before comparing. + resultSecondAdd.Routes = nil + result.Routes = nil Expect(resultSecondAdd).Should(Equal(result)) // The endpoint is created in etcd @@ -792,6 +801,10 @@ var _ = Describe("CalicoCni", func() { tweaked := strings.Replace(netconf, "net1", "net2", 1) resultSecondAdd, _, _, _, err := testutils.RunCNIPluginWithId(tweaked, "", "", "", containerID, "", contNs) Expect(err).ShouldNot(HaveOccurred()) + // Routes are generated by the IPAM plugin on first ADD but not on the no-op + // second ADD (which skips IPAM). Nullify before comparing. + resultSecondAdd.Routes = nil + result.Routes = nil Expect(resultSecondAdd).Should(Equal(result)) // The endpoint is created in etcd @@ -874,7 +887,7 @@ var _ = Describe("CalicoCni", func() { Describe("testConnection tests", func() { - It("successfully connects to the datastore", func(done Done) { + It("successfully connects to the datastore", func() { netconf := fmt.Sprintf(` { "cniVersion": "%s", @@ -901,10 +914,9 @@ var _ = Describe("CalicoCni", func() { _, err = c.CombinedOutput() Expect(err).ToNot(HaveOccurred()) - close(done) - }, 10) + }) - It("reports it cannot connect to the datastore", func(done Done) { + It("reports it cannot connect to the datastore", func() { // wrong port. netconf := fmt.Sprintf(` { @@ -932,8 +944,7 @@ var _ = Describe("CalicoCni", func() { _, err = c.CombinedOutput() Expect(err).To(HaveOccurred()) - close(done) - }, 10) + }) }) diff --git a/cni-plugin/tests/kubevirt-test-crds.yaml b/cni-plugin/tests/kubevirt-test-crds.yaml new file mode 100644 index 00000000000..a7f4957f56b --- /dev/null +++ b/cni-plugin/tests/kubevirt-test-crds.yaml @@ -0,0 +1,93 @@ +# Minimal KubeVirt CRDs for testing +# This file contains only the CRD definitions needed for CNI IPAM tests +# It does not include any controllers or operators +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: virtualmachines.kubevirt.io +spec: + group: kubevirt.io + names: + kind: VirtualMachine + listKind: VirtualMachineList + plural: virtualmachines + singular: virtualmachine + shortNames: + - vm + - vms + scope: Namespaced + versions: + - name: v1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + x-kubernetes-preserve-unknown-fields: true + status: + type: object + x-kubernetes-preserve-unknown-fields: true +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: virtualmachineinstances.kubevirt.io +spec: + group: kubevirt.io + names: + kind: VirtualMachineInstance + listKind: VirtualMachineInstanceList + plural: virtualmachineinstances + singular: virtualmachineinstance + shortNames: + - vmi + - vmis + scope: Namespaced + versions: + - name: v1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + x-kubernetes-preserve-unknown-fields: true + status: + type: object + x-kubernetes-preserve-unknown-fields: true +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: virtualmachineinstancemigrations.kubevirt.io +spec: + group: kubevirt.io + names: + kind: VirtualMachineInstanceMigration + listKind: VirtualMachineInstanceMigrationList + plural: virtualmachineinstancemigrations + singular: virtualmachineinstancemigration + shortNames: + - vmim + - vmims + scope: Namespaced + versions: + - name: v1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + x-kubernetes-preserve-unknown-fields: true + status: + type: object + x-kubernetes-preserve-unknown-fields: true diff --git a/cni-plugin/tests/kubevirt_ipam_test.go b/cni-plugin/tests/kubevirt_ipam_test.go new file mode 100644 index 00000000000..88632ccaa5a --- /dev/null +++ b/cni-plugin/tests/kubevirt_ipam_test.go @@ -0,0 +1,1184 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +package main_test + +import ( + "context" + "fmt" + "net" + "os" + "sort" + + "github.com/google/uuid" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + k8stypes "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" + + "github.com/projectcalico/calico/cni-plugin/internal/pkg/testutils" + client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" + "github.com/projectcalico/calico/libcalico-go/lib/errors" + "github.com/projectcalico/calico/libcalico-go/lib/ipam" + "github.com/projectcalico/calico/libcalico-go/lib/ipam/vmipam" + "github.com/projectcalico/calico/libcalico-go/lib/names" + cnet "github.com/projectcalico/calico/libcalico-go/lib/net" + "github.com/projectcalico/calico/libcalico-go/lib/options" +) + +// GetCNIArgsForPod constructs a CNI_ARGS string matching the format kubelet uses in production. +// Kubelet always prepends IgnoreUnknown=1 so that CNI/IPAM plugins using cnitypes.LoadArgs +// with cnitypes.CommonArgs will ignore fields they don't define (e.g., K8S_POD_NAME). +// Without IgnoreUnknown=1, LoadArgs would fail on any field not present in the plugin's args struct. +func GetCNIArgsForPod(podName, namespace, containerID string) string { + return fmt.Sprintf("IgnoreUnknown=1;K8S_POD_NAME=%s;K8S_POD_NAMESPACE=%s;K8S_POD_INFRA_CONTAINER_ID=%s", + podName, namespace, containerID) +} + +// verifyOwnerAttributes checks the IPAM owner attributes for all IPs allocated to a VM handle. +func verifyOwnerAttributes( + calicoClient client.Interface, + networkName string, + namespace string, + vmName string, + expectedActivePodName string, + expectedVMIUID string, + expectedVMUID string, + expectedAlternatePodName string, + expectedVMIMUID string, +) { + ctx := context.Background() + handleID := vmipam.CreateVMHandleID(networkName, namespace, vmName) + + ips, err := calicoClient.IPAM().IPsByHandle(ctx, handleID) + Expect(err).NotTo(HaveOccurred(), "Failed to get IPs by handle %s", handleID) + Expect(ips).NotTo(BeEmpty(), "No IPs found for handle %s", handleID) + + for _, ip := range ips { + allocAttr, err := calicoClient.IPAM().GetAssignmentAttributes(ctx, cnet.IP{IP: ip.IP}) + Expect(err).NotTo(HaveOccurred(), "Failed to get assignment attributes for IP %s", ip) + Expect(allocAttr).NotTo(BeNil(), "No allocation attributes for IP %s", ip) + + fmt.Printf("[TEST] Verifying attributes for IP %s (handle: %s)\n", ip, handleID) + + // Verify ActiveOwnerAttrs + if expectedActivePodName != "" { + Expect(allocAttr.ActiveOwnerAttrs).NotTo(BeNil(), "ActiveOwnerAttrs should not be nil") + Expect(allocAttr.ActiveOwnerAttrs[ipam.AttributePod]).To(Equal(expectedActivePodName), + "ActiveOwnerAttrs pod name mismatch") + Expect(allocAttr.ActiveOwnerAttrs[ipam.AttributeNamespace]).To(Equal(namespace), + "ActiveOwnerAttrs namespace mismatch") + Expect(allocAttr.ActiveOwnerAttrs[ipam.AttributeVMIName]).To(Equal(vmName), + "ActiveOwnerAttrs VMI name mismatch") + Expect(allocAttr.ActiveOwnerAttrs[ipam.AttributeVMIUID]).To(Equal(expectedVMIUID), + "ActiveOwnerAttrs VMI UID mismatch") + if expectedVMUID != "" { + Expect(allocAttr.ActiveOwnerAttrs[ipam.AttributeVMUID]).To(Equal(expectedVMUID), + "ActiveOwnerAttrs VM UID mismatch") + } + } else { + // Active owner should be cleared (nil or empty) + if allocAttr.ActiveOwnerAttrs != nil { + Expect(allocAttr.ActiveOwnerAttrs).To(BeEmpty(), + "ActiveOwnerAttrs should be cleared for IP %s", ip) + } + } + + // Verify AlternateOwnerAttrs + if expectedAlternatePodName != "" { + Expect(allocAttr.AlternateOwnerAttrs).NotTo(BeNil(), "AlternateOwnerAttrs should not be nil for migration target") + Expect(allocAttr.AlternateOwnerAttrs[ipam.AttributePod]).To(Equal(expectedAlternatePodName), + "AlternateOwnerAttrs pod name mismatch") + Expect(allocAttr.AlternateOwnerAttrs[ipam.AttributeNamespace]).To(Equal(namespace), + "AlternateOwnerAttrs namespace mismatch") + Expect(allocAttr.AlternateOwnerAttrs[ipam.AttributeVMIName]).To(Equal(vmName), + "AlternateOwnerAttrs VMI name mismatch") + Expect(allocAttr.AlternateOwnerAttrs[ipam.AttributeVMIUID]).To(Equal(expectedVMIUID), + "AlternateOwnerAttrs VMI UID mismatch") + if expectedVMIMUID != "" { + Expect(allocAttr.AlternateOwnerAttrs[ipam.AttributeVMIMUID]).To(Equal(expectedVMIMUID), + "AlternateOwnerAttrs VMIM UID mismatch") + } + if expectedVMUID != "" { + Expect(allocAttr.AlternateOwnerAttrs[ipam.AttributeVMUID]).To(Equal(expectedVMUID), + "AlternateOwnerAttrs VM UID mismatch") + } + } else { + // No migration target expected - AlternateOwnerAttrs should be nil or empty + if allocAttr.AlternateOwnerAttrs != nil { + Expect(allocAttr.AlternateOwnerAttrs).To(BeEmpty(), + "AlternateOwnerAttrs should be empty when no migration target is expected") + } + } + + fmt.Printf("[TEST] Owner attributes verified for IP %s\n", ip) + } +} + +// getIPsForVMHandle returns the sorted list of IP strings allocated to a VM handle. +// Useful for comparing IPs across operations to verify persistence. +func getIPsForVMHandle(calicoClient client.Interface, networkName, namespace, vmName string) []string { + ctx := context.Background() + handleID := vmipam.CreateVMHandleID(networkName, namespace, vmName) + + ips, err := calicoClient.IPAM().IPsByHandle(ctx, handleID) + Expect(err).NotTo(HaveOccurred(), "Failed to get IPs by handle %s", handleID) + + result := make([]string, len(ips)) + for i, ip := range ips { + result[i] = ip.String() + } + sort.Strings(result) + return result +} + +// verifyOwnerAttributesCleared verifies that both ActiveOwnerAttrs and AlternateOwnerAttrs +// are nil or empty for all IPs allocated to a VM handle. IPs must still be allocated. +func verifyOwnerAttributesCleared(calicoClient client.Interface, networkName, namespace, vmName string) { + ctx := context.Background() + handleID := vmipam.CreateVMHandleID(networkName, namespace, vmName) + + ips, err := calicoClient.IPAM().IPsByHandle(ctx, handleID) + Expect(err).NotTo(HaveOccurred(), "Failed to get IPs by handle %s", handleID) + Expect(ips).NotTo(BeEmpty(), "IPs should still be allocated to handle %s", handleID) + + for _, ip := range ips { + allocAttr, err := calicoClient.IPAM().GetAssignmentAttributes(ctx, cnet.IP{IP: ip.IP}) + Expect(err).NotTo(HaveOccurred()) + + if allocAttr.ActiveOwnerAttrs != nil { + Expect(allocAttr.ActiveOwnerAttrs).To(BeEmpty(), + "ActiveOwnerAttrs should be cleared for IP %s", ip) + } + if allocAttr.AlternateOwnerAttrs != nil { + Expect(allocAttr.AlternateOwnerAttrs).To(BeEmpty(), + "AlternateOwnerAttrs should be cleared for IP %s", ip) + } + } + fmt.Printf("[TEST] Verified owner attributes cleared for handle %s (%d IPs)\n", handleID, len(ips)) +} + +// verifyHandleReleased verifies that the VM handle has been released (no IPs allocated). +func verifyHandleReleased(calicoClient client.Interface, networkName, namespace, vmName string) { + ctx := context.Background() + handleID := vmipam.CreateVMHandleID(networkName, namespace, vmName) + + ips, err := calicoClient.IPAM().IPsByHandle(ctx, handleID) + // After ReleaseByHandle, either IPsByHandle returns an error (handle not found) + // or returns an empty list. Both indicate the handle has been released. + if err == nil { + Expect(ips).To(BeEmpty(), "Handle %s should have no IPs after release", handleID) + } + fmt.Printf("[TEST] Verified handle %s is released (err=%v, ipCount=%d)\n", handleID, err, len(ips)) +} + +// verifyHandleNotReleased verifies that the VM handle still has IPs allocated matching expectedIPs. +func verifyHandleNotReleased(calicoClient client.Interface, networkName, namespace, vmName string, expectedIPs []string) { + currentIPs := getIPsForVMHandle(calicoClient, networkName, namespace, vmName) + Expect(currentIPs).To(Equal(expectedIPs), "IPs should still be allocated to handle") + fmt.Printf("[TEST] Verified handle still has IPs: %v\n", currentIPs) +} + +// getDualStackNetconf returns a netconf JSON string that requests both IPv4 and IPv6 addresses. +func getDualStackNetconf(cniVersion string) string { + return fmt.Sprintf(`{ + "cniVersion": "%s", + "name": "net1", + "type": "calico", + "etcd_endpoints": "http://%s:2379", + "kubernetes": { + "kubeconfig": "/home/user/certs/kubeconfig", + "k8s_api_root": "https://127.0.0.1:6443" + }, + "datastore_type": "kubernetes", + "log_level": "debug", + "ipam": { + "type": "calico-ipam", + "assign_ipv4": "true", + "assign_ipv6": "true" + } + }`, cniVersion, os.Getenv("ETCD_IP")) +} + +// updateIPAMKubeVirtIPPersistence updates the KubeVirtVMAddressPersistence setting in IPAMConfig +func updateIPAMKubeVirtIPPersistence(calicoClient client.Interface, persistence *apiv3.VMAddressPersistence) { + ctx := context.Background() + ipamConfig, err := calicoClient.IPAMConfiguration().Get(ctx, "default", options.GetOptions{}) + + if err != nil { + // Check if error is specifically because the resource doesn't exist + if _, ok := err.(errors.ErrorResourceDoesNotExist); !ok { + // It's a different error, fail the test + Expect(err).NotTo(HaveOccurred()) + } + + // IPAMConfig doesn't exist, create it + fmt.Printf("[TEST] Creating IPAMConfig with KubeVirtVMAddressPersistence = %v\n", persistence) + newConfig := &apiv3.IPAMConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + }, + Spec: apiv3.IPAMConfigurationSpec{ + StrictAffinity: false, + AutoAllocateBlocks: true, + MaxBlocksPerHost: 0, + KubeVirtVMAddressPersistence: persistence, + }, + } + _, err = calicoClient.IPAMConfiguration().Create(ctx, newConfig, options.SetOptions{}) + Expect(err).NotTo(HaveOccurred()) + fmt.Printf("[TEST] IPAMConfig created successfully\n") + } else { + // IPAMConfig exists, update it + fmt.Printf("[TEST] Updating existing IPAMConfig with KubeVirtVMAddressPersistence = %v\n", persistence) + ipamConfig.Spec.KubeVirtVMAddressPersistence = persistence + _, err = calicoClient.IPAMConfiguration().Update(ctx, ipamConfig, options.SetOptions{}) + Expect(err).NotTo(HaveOccurred()) + fmt.Printf("[TEST] IPAMConfig updated successfully\n") + } +} + +// KubeVirt VM-based handle ID tests +// These tests verify that virt-launcher pods use VM-based handle IDs for IP persistence. +// The Makefile installs minimal KubeVirt CRDs (without operators) before running tests. +// Tests require KubeVirt CRDs to be installed and will fail if they are missing. +var _ = Describe("KubeVirt VM-based handle ID", Label("KubeVirt"), func() { + // Skip these tests if not running against Kubernetes datastore + // since we need to create CRD resources + if os.Getenv("DATASTORE_TYPE") != "kubernetes" { + return + } + + cniVersion := os.Getenv("CNI_SPEC_VERSION") + calicoClient, err := client.NewFromEnv() + Expect(err).NotTo(HaveOccurred()) + + var k8sClient *kubernetes.Clientset + var testNs string + var vmName string + var podName string + var cid string + var virtResourceManager *KubeVirtResourceManager + var sourcePodName string + var sourceCID string + var sourceCNIArgs string + var netconf string + var originalIPs []string + + // initialiseTestInfra sets up the test infrastructure including datastore, IP pools, k8s client, node, and namespace + initialiseTestInfra := func() { + testutils.WipeDatastore() + testutils.MustCreateNewIPPool(calicoClient, "192.168.0.0/16", false, false, true) + testutils.MustCreateNewIPPool(calicoClient, "fd80:24e2:f998:72d6::/64", false, false, true) + + // Create a unique container ID for each test + cid = uuid.NewString() + + // Get Kubernetes client first (needed for AddNode) + config, err := clientcmd.BuildConfigFromFlags("", "/home/user/certs/kubeconfig") + Expect(err).NotTo(HaveOccurred()) + k8sClient, err = kubernetes.NewForConfig(config) + Expect(err).NotTo(HaveOccurred()) + + // Create the node for these tests. The IPAM code requires a corresponding Calico node to exist. + var name string + name, err = names.Hostname() + Expect(err).NotTo(HaveOccurred()) + err = testutils.AddNode(calicoClient, k8sClient, name) + Expect(err).NotTo(HaveOccurred()) + + // Create a test namespace + testNs = "test-kubevirt-" + uuid.NewString()[:8] + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: testNs, + }, + } + _, err = k8sClient.CoreV1().Namespaces().Create(context.Background(), ns, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + vmName = "test-vm" + podName = "virt-launcher-" + vmName + "-abcde" + + // Initialize KubeVirt resource manager + virtResourceManager, err = NewKubeVirtResourceManager(k8sClient, testNs, vmName) + Expect(err).NotTo(HaveOccurred()) + } + + // setupSourceVirtLauncherPod creates the VMI (and optionally VM), source virt-launcher pod, + // allocates dual-stack IPs, and verifies owner attributes. + // If standalone is true, creates a standalone VMI without a VM owner. + // If standalone is false, creates a VM and a VMI owned by that VM. + setupSourceVirtLauncherPod := func(standalone bool) { + if standalone { + fmt.Println("[TEST] Setting up standalone VMI (no VM owner)") + virtResourceManager.CreateStandaloneVMI() + } else { + fmt.Println("[TEST] Setting up VM-owned VMI") + virtResourceManager.CreateVM() + virtResourceManager.CreateVMI(false, "") + } + + sourcePodName = "virt-launcher-" + vmName + "-source" + sourceCID = uuid.NewString() + virtResourceManager.CreateVirtLauncherPod(sourcePodName, "") + + netconf = getDualStackNetconf(cniVersion) + sourceCNIArgs = GetCNIArgsForPod(sourcePodName, testNs, sourceCID) + + result, errOut, exitCode := testutils.RunIPAMPlugin(netconf, "ADD", sourceCNIArgs, sourceCID, cniVersion) + Expect(exitCode).To(Equal(0), fmt.Sprintf("Source IPAM ADD failed: %v", errOut)) + Expect(result.IPs).To(HaveLen(2), "Expected dual-stack: one IPv4 and one IPv6") + verifyRoutesPopulatedInResult(result, true) + + originalIPs = getIPsForVMHandle(calicoClient, "net1", testNs, vmName) + Expect(originalIPs).To(HaveLen(2)) + // Verify one IPv4 and one IPv6 + hasIPv4 := net.ParseIP(originalIPs[0]).To4() != nil || net.ParseIP(originalIPs[1]).To4() != nil + hasIPv6 := net.ParseIP(originalIPs[0]).To4() == nil || net.ParseIP(originalIPs[1]).To4() == nil + Expect(hasIPv4).To(BeTrue(), "Expected one IPv4 address in original IPs") + Expect(hasIPv6).To(BeTrue(), "Expected one IPv6 address in original IPs") + fmt.Printf("[TEST] Original IPs: %v\n", originalIPs) + + // Verify source pod is active owner, no alternate + verifyOwnerAttributes(calicoClient, "net1", testNs, vmName, + sourcePodName, virtResourceManager.Resources.VMIUID, virtResourceManager.Resources.VMUID, + "", "") + } + + // setupMigrationTarget creates VMIM and target pod for migration testing. + // Assumes VM, VMI, and source pod with IPs are already created via setupSourceVirtLauncherPod(false). + setupMigrationTarget := func() string { + fmt.Printf("\n[TEST] ===== Setting up migration target =====\n") + + // Create VMIM + migrationUID := virtResourceManager.CreateVMIM("test-migration") + + // Create target pod with migration label + podName = "virt-launcher-" + vmName + "-target" + virtResourceManager.CreateVirtLauncherPod(podName, migrationUID) + + fmt.Println("[TEST] Migration target setup completed - target pod created") + return migrationUID + } + + BeforeEach(func() { + initialiseTestInfra() + }) + + AfterEach(func() { + // Clean up test namespace + if k8sClient != nil && testNs != "" { + err := k8sClient.CoreV1().Namespaces().Delete(context.Background(), testNs, metav1.DeleteOptions{}) + if err != nil { + fmt.Printf("Warning: failed to delete test namespace %s: %v\n", testNs, err) + } + } + + // Delete the node + name, err := names.Hostname() + Expect(err).NotTo(HaveOccurred()) + err = testutils.DeleteNode(calicoClient, k8sClient, name) + Expect(err).NotTo(HaveOccurred()) + }) + + Context("IP Persistence and Release", func() { + Context("on pod recreation", func() { + It("should retain IPs when virt-launcher pod is deleted and recreated with same VMI", func() { + fmt.Println("\n[TEST] ===== Running test: IP persistence on pod recreation =====") + setupSourceVirtLauncherPod(false) + + // Step 1: IPAM DEL for source pod - clears owner attrs but IPs stay allocated + _, _, exitCode := testutils.RunIPAMPlugin(netconf, "DEL", sourceCNIArgs, sourceCID, cniVersion) + Expect(exitCode).To(Equal(0)) + + // Verify IPs still allocated but owner attributes cleared + currentIPs := getIPsForVMHandle(calicoClient, "net1", testNs, vmName) + Expect(currentIPs).To(Equal(originalIPs), "IPs should persist after pod deletion") + verifyOwnerAttributesCleared(calicoClient, "net1", testNs, vmName) + + // Step 2: Delete source pod from k8s, create pod2 with same VMI + err := k8sClient.CoreV1().Pods(testNs).Delete(context.Background(), sourcePodName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + + pod2Name := "virt-launcher-" + vmName + "-pod2" + cid2 := uuid.NewString() + virtResourceManager.CreateVirtLauncherPod(pod2Name, "") + + // Step 3: IPAM ADD for pod2 - should get same IPs + cniArgs2 := GetCNIArgsForPod(pod2Name, testNs, cid2) + result2, errOut, exitCode := testutils.RunIPAMPlugin(netconf, "ADD", cniArgs2, cid2, cniVersion) + Expect(exitCode).To(Equal(0), fmt.Sprintf("IPAM ADD for pod2 failed: %v", errOut)) + Expect(result2.IPs).To(HaveLen(2), "Expected dual-stack: one IPv4 and one IPv6") + verifyRoutesPopulatedInResult(result2, true) + + // Verify same IPs were reused + newIPs := getIPsForVMHandle(calicoClient, "net1", testNs, vmName) + Expect(newIPs).To(Equal(originalIPs), "IPs should be the same after pod recreation") + fmt.Printf("[TEST] Recreated pod IPs: %v (same as original)\n", newIPs) + + // Verify pod2 is now active owner + verifyOwnerAttributes(calicoClient, "net1", testNs, vmName, + pod2Name, virtResourceManager.Resources.VMIUID, virtResourceManager.Resources.VMUID, + "", "") + + // Clean up + _, _, exitCode = testutils.RunIPAMPlugin(netconf, "DEL", cniArgs2, cid2, cniVersion) + Expect(exitCode).To(Equal(0)) + }) + }) + + Context("on live migration", func() { + It("should retain IPs during live migration and clean up owners on pod deletion", func() { + fmt.Println("\n[TEST] ===== Running test: IP persistence on live migration =====") + setupSourceVirtLauncherPod(false) + + // Step 1: Add migration target pod, verify IPs are persistent, + // verify owner attributes (source=active, target=alternate), and verify empty routes + setupMigrationTarget() + targetPodName := podName + targetCID := uuid.NewString() + targetCNIArgs := GetCNIArgsForPod(targetPodName, testNs, targetCID) + result, errOut, exitCode := testutils.RunIPAMPlugin(netconf, "ADD", targetCNIArgs, targetCID, cniVersion) + Expect(exitCode).To(Equal(0), fmt.Sprintf("Target IPAM ADD failed: %v", errOut)) + Expect(result.IPs).To(HaveLen(2), "Migration target should receive both existing IPs") + + currentIPs := getIPsForVMHandle(calicoClient, "net1", testNs, vmName) + Expect(currentIPs).To(Equal(originalIPs), "IPs should persist during migration") + + verifyOwnerAttributes(calicoClient, "net1", testNs, vmName, + sourcePodName, virtResourceManager.Resources.VMIUID, virtResourceManager.Resources.VMUID, + targetPodName, virtResourceManager.Resources.VMIMUID) + + verifyRoutesPopulatedInResult(result, false) + + // Step 2: Delete source pod, verify IPs persist, + // verify active owner cleared and alternate (target) remains + _, _, exitCode = testutils.RunIPAMPlugin(netconf, "DEL", sourceCNIArgs, sourceCID, cniVersion) + Expect(exitCode).To(Equal(0)) + + currentIPs = getIPsForVMHandle(calicoClient, "net1", testNs, vmName) + Expect(currentIPs).To(Equal(originalIPs), "IPs should persist after source pod deletion") + + verifyOwnerAttributes(calicoClient, "net1", testNs, vmName, + "", // active owner cleared + virtResourceManager.Resources.VMIUID, virtResourceManager.Resources.VMUID, + targetPodName, virtResourceManager.Resources.VMIMUID) + + // Step 3: Delete target pod, verify IPs persist, + // verify both owners cleared + _, _, exitCode = testutils.RunIPAMPlugin(netconf, "DEL", targetCNIArgs, targetCID, cniVersion) + Expect(exitCode).To(Equal(0)) + + currentIPs = getIPsForVMHandle(calicoClient, "net1", testNs, vmName) + Expect(currentIPs).To(Equal(originalIPs), "IPs should persist after both pod deletions") + verifyOwnerAttributesCleared(calicoClient, "net1", testNs, vmName) + }) + }) + + Context("on VMI recreation", func() { + It("should retain IPs when VMI is deleted and recreated", func() { + fmt.Println("\n[TEST] ===== Running test: IP persistence on VMI recreation =====") + setupSourceVirtLauncherPod(false) + oldVMIUID := virtResourceManager.Resources.VMIUID + + // Step 1: Delete VMI (simulating VMI recycling by VM controller) + // Note: source pod is NOT deleted - it becomes orphaned + fmt.Println("[TEST] Deleting VMI to simulate VMI recreation...") + virtResourceManager.DeleteVMI() + + // Step 2: Create new VMI (same name, new UID - as VM controller would do) + virtResourceManager.CreateVMI(false, "") + newVMIUID := virtResourceManager.Resources.VMIUID + Expect(newVMIUID).NotTo(Equal(oldVMIUID), "New VMI should have a different UID") + fmt.Printf("[TEST] New VMI created with UID: %s (old: %s)\n", newVMIUID, oldVMIUID) + + // IPs should still be allocated to the handle (handle is based on namespace+name, not UID) + currentIPs := getIPsForVMHandle(calicoClient, "net1", testNs, vmName) + Expect(currentIPs).To(Equal(originalIPs), "IPs should persist across VMI recreation") + + // Step 3: Create new source pod for the new VMI + sourcePod2Name := "virt-launcher-" + vmName + "-src2" + sourceCID2 := uuid.NewString() + virtResourceManager.CreateVirtLauncherPod(sourcePod2Name, "") + + // Step 4: IPAM ADD for new source pod - should get same IPs + cniArgs2 := GetCNIArgsForPod(sourcePod2Name, testNs, sourceCID2) + result, errOut, exitCode := testutils.RunIPAMPlugin(netconf, "ADD", cniArgs2, sourceCID2, cniVersion) + Expect(exitCode).To(Equal(0), fmt.Sprintf("Source pod2 IPAM ADD failed: %v", errOut)) + Expect(result.IPs).To(HaveLen(2)) + verifyRoutesPopulatedInResult(result, true) + + // Verify same IPs + newIPs := getIPsForVMHandle(calicoClient, "net1", testNs, vmName) + Expect(newIPs).To(Equal(originalIPs), "IPs should be the same after VMI recreation") + fmt.Printf("[TEST] New source pod IPs: %v (same as original)\n", newIPs) + + // Verify new source pod is active owner with new VMI UID + verifyOwnerAttributes(calicoClient, "net1", testNs, vmName, + sourcePod2Name, newVMIUID, virtResourceManager.Resources.VMUID, + "", "") + + // Clean up + _, _, exitCode = testutils.RunIPAMPlugin(netconf, "DEL", cniArgs2, sourceCID2, cniVersion) + Expect(exitCode).To(Equal(0)) + }) + }) + + Context("IP release", func() { + It("should not release handle when source pod is deleted without VM deletion", func() { + fmt.Println("\n[TEST] ===== Running test: IP release - no release on pod deletion without VM deletion =====") + setupSourceVirtLauncherPod(false) + + // IPAM DEL for source pod - clears owner attrs but VM is not being deleted + _, _, exitCode := testutils.RunIPAMPlugin(netconf, "DEL", sourceCNIArgs, sourceCID, cniVersion) + Expect(exitCode).To(Equal(0)) + + // Handle and IPs should still exist (VM not deleting → no release) + verifyHandleNotReleased(calicoClient, "net1", testNs, vmName, originalIPs) + verifyOwnerAttributesCleared(calicoClient, "net1", testNs, vmName) + }) + + It("should not release handle when VMI has deletion timestamp", func() { + fmt.Println("\n[TEST] ===== Running test: IP release - no release on VMI deletion =====") + setupSourceVirtLauncherPod(false) + + // Set deletion timestamp on VMI (not VM) + virtResourceManager.SetVMIDeletionTimestamp() + + // IPAM DEL for source pod + _, _, exitCode := testutils.RunIPAMPlugin(netconf, "DEL", sourceCNIArgs, sourceCID, cniVersion) + Expect(exitCode).To(Equal(0)) + + // Handle and IPs should still exist (only VM deletion triggers release, not VMI) + verifyHandleNotReleased(calicoClient, "net1", testNs, vmName, originalIPs) + verifyOwnerAttributesCleared(calicoClient, "net1", testNs, vmName) + }) + + It("should release handle when VM has deletion timestamp and pod is deleted", func() { + fmt.Println("\n[TEST] ===== Running test: IP release - release on VM deletion =====") + setupSourceVirtLauncherPod(false) + + // Set deletion timestamp on VM + virtResourceManager.SetVMDeletionTimestamp() + + // IPAM DEL for source pod - VM is deleting and all owners cleared → release + _, _, exitCode := testutils.RunIPAMPlugin(netconf, "DEL", sourceCNIArgs, sourceCID, cniVersion) + Expect(exitCode).To(Equal(0)) + + // Handle and IPs should be released + verifyHandleReleased(calicoClient, "net1", testNs, vmName) + }) + + It("should release handle when both owners are deleted with VM deleting (source first)", func() { + fmt.Println("\n[TEST] ===== Running test: IP release - both owners deleted, source first =====") + setupSourceVirtLauncherPod(false) + + // Step 1: Add migration target pod + setupMigrationTarget() + targetPodName := podName + targetCID := uuid.NewString() + targetCNIArgs := GetCNIArgsForPod(targetPodName, testNs, targetCID) + result, errOut, exitCode := testutils.RunIPAMPlugin(netconf, "ADD", targetCNIArgs, targetCID, cniVersion) + Expect(exitCode).To(Equal(0), fmt.Sprintf("Target IPAM ADD failed: %v", errOut)) + Expect(result.IPs).To(HaveLen(2)) + + // Step 2: Verify two owners (source=active, target=alternate) + verifyOwnerAttributes(calicoClient, "net1", testNs, vmName, + sourcePodName, virtResourceManager.Resources.VMIUID, virtResourceManager.Resources.VMUID, + targetPodName, virtResourceManager.Resources.VMIMUID) + + // Step 3: Set VM deletion timestamp + virtResourceManager.SetVMDeletionTimestamp() + + // Step 4: Delete source pod - clears active owner, but alternate (target) remains → not released + _, _, exitCode = testutils.RunIPAMPlugin(netconf, "DEL", sourceCNIArgs, sourceCID, cniVersion) + Expect(exitCode).To(Equal(0)) + verifyHandleNotReleased(calicoClient, "net1", testNs, vmName, originalIPs) + + // Step 5: Delete target pod - clears alternate owner, all owners empty, VM deleting → released + _, _, exitCode = testutils.RunIPAMPlugin(netconf, "DEL", targetCNIArgs, targetCID, cniVersion) + Expect(exitCode).To(Equal(0)) + verifyHandleReleased(calicoClient, "net1", testNs, vmName) + }) + + It("should release handle when both owners are deleted with VM deleting (target first)", func() { + fmt.Println("\n[TEST] ===== Running test: IP release - both owners deleted, target first =====") + setupSourceVirtLauncherPod(false) + + // Step 1: Add migration target pod + setupMigrationTarget() + targetPodName := podName + targetCID := uuid.NewString() + targetCNIArgs := GetCNIArgsForPod(targetPodName, testNs, targetCID) + result, errOut, exitCode := testutils.RunIPAMPlugin(netconf, "ADD", targetCNIArgs, targetCID, cniVersion) + Expect(exitCode).To(Equal(0), fmt.Sprintf("Target IPAM ADD failed: %v", errOut)) + Expect(result.IPs).To(HaveLen(2)) + + // Step 2: Verify two owners (source=active, target=alternate) + verifyOwnerAttributes(calicoClient, "net1", testNs, vmName, + sourcePodName, virtResourceManager.Resources.VMIUID, virtResourceManager.Resources.VMUID, + targetPodName, virtResourceManager.Resources.VMIMUID) + + // Step 3: Set VM deletion timestamp + virtResourceManager.SetVMDeletionTimestamp() + + // Step 4: Delete target pod - clears alternate owner, but active (source) remains → not released + _, _, exitCode = testutils.RunIPAMPlugin(netconf, "DEL", targetCNIArgs, targetCID, cniVersion) + Expect(exitCode).To(Equal(0)) + verifyHandleNotReleased(calicoClient, "net1", testNs, vmName, originalIPs) + + // Step 5: Delete source pod - clears active owner, all owners empty, VM deleting → released + _, _, exitCode = testutils.RunIPAMPlugin(netconf, "DEL", sourceCNIArgs, sourceCID, cniVersion) + Expect(exitCode).To(Equal(0)) + verifyHandleReleased(calicoClient, "net1", testNs, vmName) + }) + + It("should release handle when standalone VMI has deletion timestamp and pod is deleted", func() { + fmt.Println("\n[TEST] ===== Running test: IP release - standalone VMI with deletion timestamp =====") + setupSourceVirtLauncherPod(true) + + // Set VMI deletion timestamp (no VM to delete, VMI itself is being deleted) + virtResourceManager.SetVMIDeletionTimestamp() + + // IPAM DEL - standalone VMI is deleting and all owners will be cleared → release + _, _, exitCode := testutils.RunIPAMPlugin(netconf, "DEL", sourceCNIArgs, sourceCID, cniVersion) + Expect(exitCode).To(Equal(0)) + + // Verify handle is released + verifyHandleReleased(calicoClient, "net1", testNs, vmName) + }) + }) + }) + + Context("KubeVirt object verification", func() { + It("should fail IPAM ADD when pod references a VMI that does not exist", func() { + fmt.Println("\n[TEST] ===== Running test: should fail IPAM ADD when pod references a non-existent VMI =====") + + // Create a virt-launcher pod with ownerReference pointing to a VMI that does not exist. + // We do NOT call CreateVM/CreateVMI, so the VMI object is absent from the cluster. + controllerTrue := true + fakeVMIUID := "non-existent-vmi-uid-" + uuid.NewString() + fakePodName := "virt-launcher-" + vmName + "-orphan" + + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: fakePodName, + Namespace: testNs, + UID: k8stypes.UID("pod-" + uuid.NewString()), + Labels: map[string]string{ + "kubevirt.io/domain": vmName, + }, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "kubevirt.io/v1", + Kind: "VirtualMachineInstance", + Name: vmName, + UID: k8stypes.UID(fakeVMIUID), + Controller: &controllerTrue, + }, + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "compute", + Image: "registry.k8s.io/pause:3.1", + }, + }, + }, + } + + _, err := k8sClient.CoreV1().Pods(testNs).Create(context.Background(), pod, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + // Wait for the pod to be retrievable + Eventually(func() error { + _, err := k8sClient.CoreV1().Pods(testNs).Get(context.Background(), fakePodName, metav1.GetOptions{}) + return err + }, "5s", "100ms").Should(BeNil()) + + netconf := getDualStackNetconf(cniVersion) + fakeCID := uuid.NewString() + cniArgs := GetCNIArgsForPod(fakePodName, testNs, fakeCID) + + // IPAM ADD should fail because the referenced VMI does not exist + _, errOut, exitCode := testutils.RunIPAMPlugin(netconf, "ADD", cniArgs, fakeCID, cniVersion) + Expect(exitCode).NotTo(Equal(0), "IPAM ADD should fail when the referenced VMI does not exist") + Expect(errOut.Msg).To(ContainSubstring("failed to get VMI info"), + "Error should indicate VMI lookup failure") + + fmt.Printf("[TEST] IPAM ADD correctly failed: exit=%d, err=%s\n", exitCode, errOut.Msg) + }) + }) + + Context("KubeVirt VM persistence disabled", func() { + AfterEach(func() { + // Clean up IPAMConfig setting + updateIPAMKubeVirtIPPersistence(calicoClient, nil) + }) + + It("should fail if migration target but KubeVirtVMAddressPersistence is disabled", func() { + fmt.Println("\n[TEST] ===== Running test: should fail if migration target but KubeVirtVMAddressPersistence is disabled =====") + setupSourceVirtLauncherPod(false) + + // Set IPAMConfig to disable VM address persistence. + updateIPAMKubeVirtIPPersistence(calicoClient, vmAddressPersistencePtr(apiv3.VMAddressPersistenceDisabled)) + + // Set up migration target (VMIM + target pod) + setupMigrationTarget() + + netconf := getDualStackNetconf(cniVersion) + cniArgs := GetCNIArgsForPod(podName, testNs, cid) + + // Run IPAM ADD - should fail because migration targets are rejected + // when persistence is disabled (VMI detection still happens to catch this). + _, errOut, exitCode := testutils.RunIPAMPlugin(netconf, "ADD", cniArgs, cid, cniVersion) + Expect(exitCode).NotTo(Equal(0), "IPAM ADD should fail when persistence is disabled") + Expect(errOut.Msg).To(ContainSubstring("not allowed when KubeVirtVMAddressPersistence is disabled")) + }) + + It("should treat virt-launcher source pod as a normal pod when persistence is disabled", func() { + fmt.Println("\n[TEST] ===== Running test: should treat virt-launcher source pod as a normal pod when persistence is disabled =====") + + // Disable persistence BEFORE creating the source pod + updateIPAMKubeVirtIPPersistence(calicoClient, vmAddressPersistencePtr(apiv3.VMAddressPersistenceDisabled)) + + // Create VM and VMI resources + virtResourceManager.CreateVM() + virtResourceManager.CreateVMI(false, "") + + netconf := getDualStackNetconf(cniVersion) + ctx := context.Background() + vmHandleID := vmipam.CreateVMHandleID("net1", testNs, vmName) + + // 1. Create first pod, save its IPs, verify no VM-based handle or owner attributes + firstPod := "virt-launcher-" + vmName + "-disabled-1" + firstCID := uuid.NewString() + virtResourceManager.CreateVirtLauncherPod(firstPod, "") + + firstCNIArgs := GetCNIArgsForPod(firstPod, testNs, firstCID) + result1, _, exitCode := testutils.RunIPAMPlugin(netconf, "ADD", firstCNIArgs, firstCID, cniVersion) + Expect(exitCode).To(Equal(0), "IPAM ADD should succeed for first pod") + Expect(result1.IPs).NotTo(BeEmpty(), "First pod should have allocated IPs") + + firstPodIPs := make([]string, len(result1.IPs)) + for i, ip := range result1.IPs { + firstPodIPs[i] = ip.Address.IP.String() + } + fmt.Printf("[TEST] First pod IPs: %v\n", firstPodIPs) + + // No VM-based handle should exist + ips, err := calicoClient.IPAM().IPsByHandle(ctx, vmHandleID) + if err == nil { + Expect(ips).To(BeEmpty(), "VM-based handle should not have any IPs when persistence is disabled") + } + fmt.Printf("[TEST] Confirmed no IPs on VM handle %s (err=%v)\n", vmHandleID, err) + + // 2. Create second pod with same VMI owner, verify it gets a different IP + secondPod := "virt-launcher-" + vmName + "-disabled-2" + secondCID := uuid.NewString() + virtResourceManager.CreateVirtLauncherPod(secondPod, "") + + secondCNIArgs := GetCNIArgsForPod(secondPod, testNs, secondCID) + result2, _, exitCode := testutils.RunIPAMPlugin(netconf, "ADD", secondCNIArgs, secondCID, cniVersion) + Expect(exitCode).To(Equal(0), "IPAM ADD should succeed for second pod") + Expect(result2.IPs).NotTo(BeEmpty(), "Second pod should have allocated IPs") + + secondPodIPs := make([]string, len(result2.IPs)) + for i, ip := range result2.IPs { + secondPodIPs[i] = ip.Address.IP.String() + } + fmt.Printf("[TEST] Second pod IPs: %v\n", secondPodIPs) + + // Second pod should have different IPs from first (no VMI-based reuse) + for _, secondIP := range secondPodIPs { + Expect(firstPodIPs).NotTo(ContainElement(secondIP), + fmt.Sprintf("Second pod IP %s should differ from first pod IPs %v", secondIP, firstPodIPs)) + } + + // 3. Delete first pod, verify its IPs are released + _, _, exitCode = testutils.RunIPAMPlugin(netconf, "DEL", firstCNIArgs, firstCID, cniVersion) + Expect(exitCode).To(Equal(0), "IPAM DEL should succeed for first pod") + + firstHandleID := fmt.Sprintf("net1.%s", firstCID) + ips, err = calicoClient.IPAM().IPsByHandle(ctx, firstHandleID) + if err == nil { + Expect(ips).To(BeEmpty(), "First pod's handle should have no IPs after DEL") + } + fmt.Printf("[TEST] Confirmed first pod IPs released (handle=%s, err=%v)\n", firstHandleID, err) + + // 4. Delete second pod, verify its IPs are released + _, _, exitCode = testutils.RunIPAMPlugin(netconf, "DEL", secondCNIArgs, secondCID, cniVersion) + Expect(exitCode).To(Equal(0), "IPAM DEL should succeed for second pod") + + secondHandleID := fmt.Sprintf("net1.%s", secondCID) + ips, err = calicoClient.IPAM().IPsByHandle(ctx, secondHandleID) + if err == nil { + Expect(ips).To(BeEmpty(), "Second pod's handle should have no IPs after DEL") + } + fmt.Printf("[TEST] Confirmed second pod IPs released (handle=%s, err=%v)\n", secondHandleID, err) + }) + }) +}) + +// Helper function to create VMAddressPersistence pointer +func vmAddressPersistencePtr(v apiv3.VMAddressPersistence) *apiv3.VMAddressPersistence { + return &v +} + +// KubeVirtTestResources stores information about created KubeVirt resources +type KubeVirtTestResources struct { + VMUID string + VMIUID string + VMIMUID string + SourcePodName string + TargetPodName string +} + +// KubeVirtResourceManager encapsulates resources and methods for KubeVirt testing +type KubeVirtResourceManager struct { + dynamicClient dynamic.Interface + k8sClient kubernetes.Interface + testNs string + vmName string + Resources KubeVirtTestResources +} + +// NewKubeVirtResourceManager creates a new KubeVirt resource manager +func NewKubeVirtResourceManager(k8sClient kubernetes.Interface, testNs, vmName string) (*KubeVirtResourceManager, error) { + config, err := clientcmd.BuildConfigFromFlags("", "/home/user/certs/kubeconfig") + if err != nil { + return nil, err + } + + dynamicClient, err := dynamic.NewForConfig(config) + if err != nil { + return nil, err + } + + return &KubeVirtResourceManager{ + dynamicClient: dynamicClient, + k8sClient: k8sClient, + testNs: testNs, + vmName: vmName, + Resources: KubeVirtTestResources{}, + }, nil +} + +// CreateVM creates a VirtualMachine resource +func (h *KubeVirtResourceManager) CreateVM() { + gvr := schema.GroupVersionResource{ + Group: "kubevirt.io", + Version: "v1", + Resource: "virtualmachines", + } + + vm := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "kubevirt.io/v1", + "kind": "VirtualMachine", + "metadata": map[string]interface{}{ + "name": h.vmName, + "namespace": h.testNs, + }, + "spec": map[string]interface{}{ + "running": true, + }, + }, + } + + createdVM, err := h.dynamicClient.Resource(gvr).Namespace(h.testNs).Create(context.Background(), vm, metav1.CreateOptions{}) + if err != nil { + Skip(fmt.Sprintf("Skipping KubeVirt tests - CRDs not installed: %v", err)) + } + + h.Resources.VMUID = string(createdVM.GetUID()) + fmt.Printf("[TEST] VM created successfully: %s/%s (actual UID from K8s: %s)\n", h.testNs, h.vmName, h.Resources.VMUID) +} + +// CreateVMI creates a VirtualMachineInstance resource +func (h *KubeVirtResourceManager) CreateVMI(withMigration bool, migrationUID string) { + controllerTrue := true + blockOwnerDeletion := true + + vmiObj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "kubevirt.io/v1", + "kind": "VirtualMachineInstance", + "metadata": map[string]interface{}{ + "name": h.vmName, + "namespace": h.testNs, + "ownerReferences": []interface{}{ + map[string]interface{}{ + "apiVersion": "kubevirt.io/v1", + "kind": "VirtualMachine", + "name": h.vmName, + "uid": h.Resources.VMUID, + "controller": controllerTrue, + "blockOwnerDeletion": blockOwnerDeletion, + }, + }, + }, + "spec": map[string]interface{}{}, + "status": map[string]interface{}{ + "activePods": map[string]interface{}{ + "pod-" + uuid.NewString(): "node1", + }, + }, + }, + } + + // Add migration state if requested + if withMigration { + status := vmiObj.Object["status"].(map[string]interface{}) + status["migrationState"] = map[string]interface{}{ + "migrationUID": migrationUID, + "sourcePod": "virt-launcher-source", + "targetPod": "virt-launcher-target", + } + } + + gvr := schema.GroupVersionResource{ + Group: "kubevirt.io", + Version: "v1", + Resource: "virtualmachineinstances", + } + + createdVMI, err := h.dynamicClient.Resource(gvr).Namespace(h.testNs).Create(context.Background(), vmiObj, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + h.Resources.VMIUID = string(createdVMI.GetUID()) + fmt.Printf("[TEST] VMI created successfully: %s/%s (actual UID from K8s: %s, withMigration: %v)\n", h.testNs, h.vmName, h.Resources.VMIUID, withMigration) + + // Wait for VMI to be retrievable + fmt.Printf("[TEST] Waiting for VMI to be retrievable...\n") + Eventually(func() error { + _, err := h.dynamicClient.Resource(gvr).Namespace(h.testNs).Get(context.Background(), h.vmName, metav1.GetOptions{}) + return err + }, "5s", "100ms").Should(BeNil()) + fmt.Printf("[TEST] VMI is now retrievable: %s/%s with UID: %s\n", h.testNs, h.vmName, h.Resources.VMIUID) +} + +// CreateStandaloneVMI creates a VirtualMachineInstance resource without a VM owner. +// This simulates a standalone VMI that was created directly (not via a VirtualMachine object). +func (h *KubeVirtResourceManager) CreateStandaloneVMI() { + vmiObj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "kubevirt.io/v1", + "kind": "VirtualMachineInstance", + "metadata": map[string]interface{}{ + "name": h.vmName, + "namespace": h.testNs, + }, + "spec": map[string]interface{}{}, + "status": map[string]interface{}{ + "activePods": map[string]interface{}{ + "pod-" + uuid.NewString(): "node1", + }, + }, + }, + } + + gvr := schema.GroupVersionResource{ + Group: "kubevirt.io", + Version: "v1", + Resource: "virtualmachineinstances", + } + + createdVMI, err := h.dynamicClient.Resource(gvr).Namespace(h.testNs).Create(context.Background(), vmiObj, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + h.Resources.VMIUID = string(createdVMI.GetUID()) + fmt.Printf("[TEST] Standalone VMI created successfully: %s/%s (actual UID from K8s: %s, no VM owner)\n", h.testNs, h.vmName, h.Resources.VMIUID) + + // Wait for VMI to be retrievable + fmt.Printf("[TEST] Waiting for standalone VMI to be retrievable...\n") + Eventually(func() error { + _, err := h.dynamicClient.Resource(gvr).Namespace(h.testNs).Get(context.Background(), h.vmName, metav1.GetOptions{}) + return err + }, "5s", "100ms").Should(BeNil()) + fmt.Printf("[TEST] Standalone VMI is now retrievable: %s/%s with UID: %s\n", h.testNs, h.vmName, h.Resources.VMIUID) +} + +// CreateVirtLauncherPod creates a virt-launcher pod +func (h *KubeVirtResourceManager) CreateVirtLauncherPod(podName string, migrationUID string) { + controllerTrue := true + + podLabels := map[string]string{ + "kubevirt.io/domain": h.vmName, + } + + // Add migration label if migrationUID is provided + if migrationUID != "" { + podLabels["kubevirt.io/migrationJobUID"] = migrationUID + } + + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: podName, + Namespace: h.testNs, + UID: k8stypes.UID("pod-" + uuid.NewString()), + Labels: podLabels, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "kubevirt.io/v1", + Kind: "VirtualMachineInstance", + Name: h.vmName, + UID: k8stypes.UID(h.Resources.VMIUID), + Controller: &controllerTrue, + }, + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "compute", + Image: "registry.k8s.io/pause:3.1", + }, + }, + }, + } + + _, err := h.k8sClient.CoreV1().Pods(h.testNs).Create(context.Background(), pod, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + fmt.Printf("[TEST] Virt-launcher pod created successfully: %s/%s (migrationUID: %s, VMI UID: %s)\n", h.testNs, podName, migrationUID, h.Resources.VMIUID) + + // Wait for pod to be retrievable + Eventually(func() error { + _, err := h.k8sClient.CoreV1().Pods(h.testNs).Get(context.Background(), podName, metav1.GetOptions{}) + return err + }, "5s", "100ms").Should(BeNil()) + fmt.Printf("[TEST] Virt-launcher pod is now retrievable: %s/%s\n", h.testNs, podName) + + // Track pod name in resources + if migrationUID != "" { + h.Resources.TargetPodName = podName + } else { + h.Resources.SourcePodName = podName + } +} + +// DeleteVMI deletes the VirtualMachineInstance resource and waits for it to be gone +func (h *KubeVirtResourceManager) DeleteVMI() { + gvr := schema.GroupVersionResource{ + Group: "kubevirt.io", + Version: "v1", + Resource: "virtualmachineinstances", + } + + err := h.dynamicClient.Resource(gvr).Namespace(h.testNs).Delete(context.Background(), h.vmName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + + // Wait for VMI to be deleted + Eventually(func() bool { + _, err := h.dynamicClient.Resource(gvr).Namespace(h.testNs).Get(context.Background(), h.vmName, metav1.GetOptions{}) + return err != nil + }, "10s", "200ms").Should(BeTrue(), "VMI should be deleted") + + fmt.Printf("[TEST] VMI deleted: %s/%s (old UID: %s)\n", h.testNs, h.vmName, h.Resources.VMIUID) + h.Resources.VMIUID = "" +} + +// CreateVMIM creates a VirtualMachineInstanceMigration resource +func (h *KubeVirtResourceManager) CreateVMIM(name string) string { + vmim := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "kubevirt.io/v1", + "kind": "VirtualMachineInstanceMigration", + "metadata": map[string]interface{}{ + "name": name, + "namespace": h.testNs, + }, + "spec": map[string]interface{}{ + "vmiName": h.vmName, + }, + }, + } + + gvr := schema.GroupVersionResource{ + Group: "kubevirt.io", + Version: "v1", + Resource: "virtualmachineinstancemigrations", + } + + createdVMIM, err := h.dynamicClient.Resource(gvr).Namespace(h.testNs).Create(context.Background(), vmim, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + h.Resources.VMIMUID = string(createdVMIM.GetUID()) + fmt.Printf("[TEST] VMIM created successfully: %s/%s (actual UID from K8s: %s)\n", h.testNs, name, h.Resources.VMIMUID) + + // Wait for VMIM to be retrievable + fmt.Printf("[TEST] Waiting for VMIM to be retrievable...\n") + Eventually(func() error { + _, err := h.dynamicClient.Resource(gvr).Namespace(h.testNs).Get(context.Background(), name, metav1.GetOptions{}) + return err + }, "5s", "100ms").Should(BeNil()) + fmt.Printf("[TEST] VMIM is now retrievable: %s/%s with UID: %s\n", h.testNs, name, h.Resources.VMIMUID) + + return h.Resources.VMIMUID +} + +// SetVMDeletionTimestamp sets a deletion timestamp on the VM object by adding a finalizer +// then initiating deletion. The finalizer prevents actual removal while DeletionTimestamp is set. +func (h *KubeVirtResourceManager) SetVMDeletionTimestamp() { + gvr := schema.GroupVersionResource{ + Group: "kubevirt.io", + Version: "v1", + Resource: "virtualmachines", + } + + // Add a finalizer to prevent actual deletion + patch := []byte(`[{"op": "add", "path": "/metadata/finalizers", "value": ["test.calico.org/prevent-deletion"]}]`) + _, err := h.dynamicClient.Resource(gvr).Namespace(h.testNs).Patch( + context.Background(), h.vmName, k8stypes.JSONPatchType, patch, metav1.PatchOptions{}) + Expect(err).NotTo(HaveOccurred(), "Failed to add finalizer to VM") + + // Delete the VM - sets DeletionTimestamp but finalizer prevents actual removal + err = h.dynamicClient.Resource(gvr).Namespace(h.testNs).Delete( + context.Background(), h.vmName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred(), "Failed to initiate VM deletion") + + // Verify VM still exists with DeletionTimestamp set + vm, err := h.dynamicClient.Resource(gvr).Namespace(h.testNs).Get( + context.Background(), h.vmName, metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred(), "VM should still exist (protected by finalizer)") + Expect(vm.GetDeletionTimestamp()).NotTo(BeNil(), "VM should have DeletionTimestamp set") + + fmt.Printf("[TEST] VM %s/%s now has DeletionTimestamp set\n", h.testNs, h.vmName) +} + +// SetVMIDeletionTimestamp sets a deletion timestamp on the VMI object by adding a finalizer +// then initiating deletion. The finalizer prevents actual removal while DeletionTimestamp is set. +func (h *KubeVirtResourceManager) SetVMIDeletionTimestamp() { + gvr := schema.GroupVersionResource{ + Group: "kubevirt.io", + Version: "v1", + Resource: "virtualmachineinstances", + } + + // Add a finalizer to prevent actual deletion + patch := []byte(`[{"op": "add", "path": "/metadata/finalizers", "value": ["test.calico.org/prevent-deletion"]}]`) + _, err := h.dynamicClient.Resource(gvr).Namespace(h.testNs).Patch( + context.Background(), h.vmName, k8stypes.JSONPatchType, patch, metav1.PatchOptions{}) + Expect(err).NotTo(HaveOccurred(), "Failed to add finalizer to VMI") + + // Delete the VMI - sets DeletionTimestamp but finalizer prevents actual removal + err = h.dynamicClient.Resource(gvr).Namespace(h.testNs).Delete( + context.Background(), h.vmName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred(), "Failed to initiate VMI deletion") + + // Verify VMI still exists with DeletionTimestamp set + vmi, err := h.dynamicClient.Resource(gvr).Namespace(h.testNs).Get( + context.Background(), h.vmName, metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred(), "VMI should still exist (protected by finalizer)") + Expect(vmi.GetDeletionTimestamp()).NotTo(BeNil(), "VMI should have DeletionTimestamp set") + + fmt.Printf("[TEST] VMI %s/%s now has DeletionTimestamp set\n", h.testNs, h.vmName) +} diff --git a/cni-plugin/win_tests/calico_cni_k8s_windows_test.go b/cni-plugin/win_tests/calico_cni_k8s_windows_test.go index 71e227b9ff5..d459de43f86 100755 --- a/cni-plugin/win_tests/calico_cni_k8s_windows_test.go +++ b/cni-plugin/win_tests/calico_cni_k8s_windows_test.go @@ -26,7 +26,7 @@ import ( "github.com/Microsoft/hcsshim" cniv1 "github.com/containernetworking/cni/pkg/types/100" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -41,7 +41,7 @@ import ( "github.com/projectcalico/calico/cni-plugin/pkg/dataplane/windows" "github.com/projectcalico/calico/cni-plugin/pkg/k8s" "github.com/projectcalico/calico/cni-plugin/pkg/types" - libapi "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" k8sconversion "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/names" @@ -119,7 +119,7 @@ var _ = Describe("Kubernetes CNI tests", func() { if os.Getenv("DATASTORE_TYPE") != "kubernetes" { // Since we're not running the startup script, we need to create a Calico Node, as required by our // IPAM plugin. - caliNode := libapi.NewNode() + caliNode := internalapi.NewNode() caliNode.Name = hostname caliNode, err := calicoClient.Nodes().Create(context.Background(), caliNode, options.SetOptions{}) Expect(err).NotTo(HaveOccurred(), "Failed to create Calico Node resource") @@ -283,7 +283,7 @@ var _ = Describe("Kubernetes CNI tests", func() { Expect(err).ShouldNot(HaveOccurred()) Expect(hostEP.GatewayAddress).Should(Equal("10.254.112.1")) Expect(hostEP.IPAddress.String()).Should(Equal("10.254.112.2")) - Expect(hostEP.VirtualNetwork).Should(Equal(hnsNetwork.Id)) + Expect(strings.EqualFold(hostEP.VirtualNetwork, hnsNetwork.Id)).To(BeTrue()) Expect(hostEP.VirtualNetworkName).Should(Equal(hnsNetwork.Name)) containerEP, err := hcsshim.GetHNSEndpointByName(containerID + "_calico-fv") @@ -367,7 +367,7 @@ var _ = Describe("Kubernetes CNI tests", func() { Expect(endpoints.Items[0].Spec.Endpoint).Should(Equal("eth0")) Expect(endpoints.Items[0].Spec.ContainerID).Should(Equal(containerID)) Expect(endpoints.Items[0].Spec.Orchestrator).Should(Equal(api.OrchestratorKubernetes)) - Expect(endpoints.Items[0].Spec.Ports).Should(Equal([]libapi.WorkloadEndpointPort{{ + Expect(endpoints.Items[0].Spec.Ports).Should(Equal([]internalapi.WorkloadEndpointPort{{ Name: "anamedport", Protocol: numorstring.ProtocolFromString("TCP"), Port: 555, @@ -390,7 +390,7 @@ var _ = Describe("Kubernetes CNI tests", func() { Expect(err).ShouldNot(HaveOccurred()) Expect(hostEP.GatewayAddress).Should(Equal("10.254.112.1")) Expect(hostEP.IPAddress.String()).Should(Equal("10.254.112.2")) - Expect(hostEP.VirtualNetwork).Should(Equal(hnsNetwork.Id)) + Expect(strings.EqualFold(hostEP.VirtualNetwork, hnsNetwork.Id)).To(BeTrue()) Expect(hostEP.VirtualNetworkName).Should(Equal(hnsNetwork.Name)) log.Infof("Creating container") @@ -458,7 +458,7 @@ var _ = Describe("Kubernetes CNI tests", func() { Expect(err).ShouldNot(HaveOccurred()) Expect(hostEP.GatewayAddress).Should(Equal("10.254.112.1")) Expect(hostEP.IPAddress.String()).Should(Equal("10.254.112.2")) - Expect(hostEP.VirtualNetwork).Should(Equal(hnsNetwork.Id)) + Expect(strings.EqualFold(hostEP.VirtualNetwork, hnsNetwork.Id)).To(BeTrue()) Expect(hostEP.VirtualNetworkName).Should(Equal(hnsNetwork.Name)) containerEP, err := hcsshim.GetHNSEndpointByName(containerID + "_calico-fv") @@ -538,7 +538,7 @@ var _ = Describe("Kubernetes CNI tests", func() { Expect(err).ShouldNot(HaveOccurred()) Expect(hostEP.GatewayAddress).Should(Equal("10.254.112.1")) Expect(hostEP.IPAddress.String()).Should(Equal("10.254.112.2")) - Expect(hostEP.VirtualNetwork).Should(Equal(hnsNetwork.Id)) + Expect(strings.EqualFold(hostEP.VirtualNetwork, hnsNetwork.Id)).To(BeTrue()) Expect(hostEP.VirtualNetworkName).Should(Equal(hnsNetwork.Name)) containerEP, err := hcsshim.GetHNSEndpointByName(containerID + "_calico-fv") @@ -641,7 +641,7 @@ var _ = Describe("Kubernetes CNI tests", func() { Expect(err).ShouldNot(HaveOccurred()) Expect(hostEP.GatewayAddress).Should(Equal("10.254.112.1")) Expect(hostEP.IPAddress.String()).Should(Equal("10.254.112.2")) - Expect(hostEP.VirtualNetwork).Should(Equal(hnsNetwork.Id)) + Expect(strings.EqualFold(hostEP.VirtualNetwork, hnsNetwork.Id)).To(BeTrue()) Expect(hostEP.VirtualNetworkName).Should(Equal(hnsNetwork.Name)) containerEP, err := hcsshim.GetHNSEndpointByName(containerID + "_calico-fv") @@ -699,7 +699,7 @@ var _ = Describe("Kubernetes CNI tests", func() { Expect(err).ShouldNot(HaveOccurred()) Expect(hostEP.GatewayAddress).Should(Equal("20.0.0.1")) Expect(hostEP.IPAddress.String()).Should(Equal("20.0.0.2")) - Expect(hostEP.VirtualNetwork).Should(Equal(hnsNetwork.Id)) + Expect(strings.EqualFold(hostEP.VirtualNetwork, hnsNetwork.Id)).To(BeTrue()) Expect(hostEP.VirtualNetworkName).Should(Equal(hnsNetwork.Name)) containerEP, err = hcsshim.GetHNSEndpointByName(containerID + "_calico-fv") @@ -812,7 +812,7 @@ var _ = Describe("Kubernetes CNI tests", func() { Expect(err).ShouldNot(HaveOccurred()) Expect(hostEP.GatewayAddress).Should(Equal("10.254.112.1")) Expect(hostEP.IPAddress.String()).Should(Equal("10.254.112.2")) - Expect(hostEP.VirtualNetwork).Should(Equal(hnsNetwork.Id)) + Expect(strings.EqualFold(hostEP.VirtualNetwork, hnsNetwork.Id)).To(BeTrue()) Expect(hostEP.VirtualNetworkName).Should(Equal(hnsNetwork.Name)) containerEP, err := hcsshim.GetHNSEndpointByName(containerID + "_calico-fv") @@ -841,7 +841,7 @@ var _ = Describe("Kubernetes CNI tests", func() { Expect(err).ShouldNot(HaveOccurred()) Expect(hostEP.GatewayAddress).Should(Equal("10.254.112.1")) Expect(hostEP.IPAddress.String()).Should(Equal("10.254.112.2")) - Expect(hostEP.VirtualNetwork).Should(Equal(hnsNetwork.Id)) + Expect(strings.EqualFold(hostEP.VirtualNetwork, hnsNetwork.Id)).To(BeTrue()) Expect(hostEP.VirtualNetworkName).Should(Equal(hnsNetwork.Name)) containerEP, err = hcsshim.GetHNSEndpointByName(containerID + "_calico-fv") @@ -1025,7 +1025,7 @@ var _ = Describe("Kubernetes CNI tests", func() { Expect(err).ShouldNot(HaveOccurred()) Expect(hostEP.GatewayAddress).Should(Equal("10.254.112.1")) Expect(hostEP.IPAddress.String()).Should(Equal("10.254.112.2")) - Expect(hostEP.VirtualNetwork).Should(Equal(hnsNetwork.Id)) + Expect(strings.EqualFold(hostEP.VirtualNetwork, hnsNetwork.Id)).To(BeTrue()) Expect(hostEP.VirtualNetworkName).Should(Equal(hnsNetwork.Name)) containerEP, err := hcsshim.GetHNSEndpointByName(containerID + "_calico-fv") @@ -1045,7 +1045,7 @@ var _ = Describe("Kubernetes CNI tests", func() { var nc types.NetConf var netconf string var workloadName, containerID, name string - var endpointSpec libapi.WorkloadEndpointSpec + var endpointSpec internalapi.WorkloadEndpointSpec var result *cniv1.Result checkIPAMReservation := func() { @@ -1872,7 +1872,7 @@ var _ = Describe("Kubernetes CNI tests", func() { hostEP, err := hcsshim.GetHNSEndpointByName("calico-fv_ep") Expect(err).ShouldNot(HaveOccurred()) Expect(hostEP.IPAddress.String()).Should(Equal("10.254.112.2")) - Expect(hostEP.VirtualNetwork).Should(Equal(hnsNetwork.Id)) + Expect(strings.EqualFold(hostEP.VirtualNetwork, hnsNetwork.Id)).To(BeTrue()) Expect(hostEP.VirtualNetworkName).Should(Equal(hnsNetwork.Name)) Expect(hostEP.MacAddress).Should(Equal(macAddr)) diff --git a/cni-plugin/win_tests/calico_cni_windows_suite_test.go b/cni-plugin/win_tests/calico_cni_windows_suite_test.go index 1f9c4e95095..51343fba96a 100644 --- a/cni-plugin/win_tests/calico_cni_windows_suite_test.go +++ b/cni-plugin/win_tests/calico_cni_windows_suite_test.go @@ -18,9 +18,8 @@ import ( "os" "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -30,12 +29,13 @@ func init() { } func TestCalicoCni(t *testing.T) { - RegisterFailHandler(Fail) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() reportPath := os.Getenv("REPORT") if reportPath == "" { // Default the report path if not specified. reportPath = "../report/windows_suite.xml" } - junitReporter := reporters.NewJUnitReporter(reportPath) - RunSpecsWithDefaultAndCustomReporters(t, "CNI suite (Windows)", []Reporter{junitReporter}) + reporterConfig.JUnitReport = reportPath + ginkgo.RunSpecs(t, "CNI suite (Windows)", suiteConfig, reporterConfig) } diff --git a/confd/Makefile b/confd/Makefile index de891b4f849..ae1f290ae6c 100644 --- a/confd/Makefile +++ b/confd/Makefile @@ -56,6 +56,8 @@ test-kdd: bin/confd-$(ARCH) bin/kubectl bin/bird bin/bird6 bin/calico-node bin/c -v $(CURDIR)/bin:/calico/bin/ \ -v $(CURDIR)/etc/calico:/etc/calico/ \ -v $(CURDIR)/../:/go/src/github.com/projectcalico/calico:rw \ + -e CALICO_CRD_PATH=$(CALICO_CRD_PATH) \ + -e CALICO_API_GROUP=$(CALICO_API_GROUP) \ -e GOPATH=/go \ -e LOCAL_USER_ID=$(LOCAL_USER_ID) \ -e FELIX_TYPHAADDR=127.0.0.1:5473 \ @@ -98,7 +100,7 @@ test-etcd: bin/confd-$(ARCH) bin/etcdctl bin/bird bin/bird6 bin/calico-node bin/ .PHONY: ut ## Run the fast set of unit tests in a container. ut: - $(DOCKER_RUN) $(CALICO_BUILD) sh -c 'cd /go/src/$(PACKAGE_NAME) && ginkgo -r .' + $(DOCKER_RUN) $(CALICO_BUILD) sh -c 'cd /go/src/$(PACKAGE_NAME) && ginkgo -r -race .' bin/kubectl: diff --git a/confd/deps.txt b/confd/deps.txt index 8d247029a0e..f5bd0edd7fd 100644 --- a/confd/deps.txt +++ b/confd/deps.txt @@ -2,16 +2,19 @@ This file contains the list of modules that this package depends on in order to trigger CI on changes -go 1.25.1 +go 1.25.7 github.com/BurntSushi/toml v1.5.0 github.com/beorn7/perks v1.0.1 github.com/cespare/xxhash/v2 v2.3.0 github.com/coreos/go-semver v0.3.1 -github.com/coreos/go-systemd/v22 v22.5.0 +github.com/coreos/go-systemd/v22 v22.6.0 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc -github.com/emicklei/go-restful/v3 v3.11.0 +github.com/emicklei/go-restful v2.15.0+incompatible +github.com/emicklei/go-restful/v3 v3.12.2 github.com/fsnotify/fsnotify v1.9.0 -github.com/fxamacker/cbor/v2 v2.7.0 +github.com/fxamacker/cbor/v2 v2.9.0 +github.com/go-kit/log v0.2.1 +github.com/go-logfmt/logfmt v0.6.0 github.com/go-logr/logr v1.4.3 github.com/go-openapi/jsonpointer v0.21.0 github.com/go-openapi/jsonreference v0.20.2 @@ -21,11 +24,10 @@ github.com/go-playground/universal-translator v0.18.1 github.com/gogo/protobuf v1.3.2 github.com/golang/protobuf v1.5.4 github.com/golang/snappy v1.0.0 -github.com/google/gnostic-models v0.6.9 -github.com/google/go-cmp v0.7.0 +github.com/google/gnostic-models v0.7.0 github.com/google/uuid v1.6.0 -github.com/grpc-ecosystem/grpc-gateway v1.16.0 -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 github.com/jinzhu/copier v0.4.0 github.com/josharian/intern v1.0.0 github.com/json-iterator/go v1.1.12 @@ -34,55 +36,58 @@ github.com/kelseyhightower/memkv v0.1.1 github.com/leodido/go-urn v1.4.0 github.com/mailru/easyjson v0.7.7 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd -github.com/modern-go/reflect2 v1.0.2 +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 +github.com/openshift/custom-resource-status v1.1.2 github.com/pkg/errors v0.9.1 +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/projectcalico/calico -github.com/projectcalico/calico/lib/std v0.0.0-00010101000000-000000000000 -github.com/projectcalico/go-json v0.0.0-20161128004156-6219dc7339ba -github.com/projectcalico/go-yaml-wrapper v0.0.0-20191112210931-090425220c54 -github.com/prometheus/client_golang v1.23.0 +github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 -github.com/prometheus/common v0.65.0 -github.com/prometheus/procfs v0.17.0 +github.com/prometheus/common v0.67.2 +github.com/prometheus/procfs v0.19.2 github.com/sirupsen/logrus v1.9.3 -github.com/spf13/pflag v1.0.7 +github.com/spf13/pflag v1.0.10 github.com/x448/float16 v0.8.4 -go.etcd.io/etcd/api/v3 v3.6.4 -go.etcd.io/etcd/client/pkg/v3 v3.6.4 -go.etcd.io/etcd/client/v3 v3.6.4 +go.etcd.io/etcd/api/v3 v3.6.5 +go.etcd.io/etcd/client/pkg/v3 v3.6.5 +go.etcd.io/etcd/client/v3 v3.6.5 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 -go.yaml.in/yaml/v2 v2.4.2 -golang.org/x/crypto v0.41.0 -golang.org/x/net v0.43.0 -golang.org/x/oauth2 v0.30.0 -golang.org/x/sync v0.16.0 -golang.org/x/sys v0.35.0 -golang.org/x/term v0.34.0 -golang.org/x/text v0.28.0 -golang.org/x/time v0.12.0 +go.yaml.in/yaml/v2 v2.4.3 +go.yaml.in/yaml/v3 v3.0.4 +golang.org/x/crypto v0.47.0 +golang.org/x/net v0.49.0 +golang.org/x/oauth2 v0.34.0 +golang.org/x/sync v0.19.0 +golang.org/x/sys v0.40.0 +golang.org/x/term v0.39.0 +golang.org/x/text v0.33.0 +golang.org/x/time v0.14.0 golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 google.golang.org/genproto v0.0.0-20250603155806-513f23925822 -google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 -google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a -google.golang.org/grpc v1.72.2 -google.golang.org/protobuf v1.36.7 +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c +google.golang.org/grpc v1.76.0 +google.golang.org/protobuf v1.36.10 gopkg.in/evanphx/json-patch.v4 v4.12.0 gopkg.in/go-playground/validator.v9 v9.30.2 gopkg.in/inf.v0 v0.9.1 -gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 -k8s.io/api v0.33.5 -k8s.io/apimachinery v0.33.5 -k8s.io/client-go v0.33.5 +k8s.io/api v0.34.3 +k8s.io/apiextensions-apiserver v0.34.3 +k8s.io/apimachinery v0.34.3 +k8s.io/client-go v0.34.3 k8s.io/klog v0.2.0 k8s.io/klog/v2 v2.130.1 -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff -k8s.io/utils v0.0.0-20241210054802-24370beab758 +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 +kubevirt.io/api v1.8.0-alpha.0 +kubevirt.io/containerized-data-importer-api v1.63.1 +kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 -sigs.k8s.io/network-policy-api v0.1.5 +sigs.k8s.io/network-policy-api v0.1.8-0.20260212153203-412bf65729a5 sigs.k8s.io/randfill v1.0.0 -sigs.k8s.io/structured-merge-diff/v4 v4.6.0 +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 sigs.k8s.io/yaml v1.6.0 diff --git a/confd/etc/calico/confd/templates/bird.cfg.template b/confd/etc/calico/confd/templates/bird.cfg.template index ca3404636e8..71462078a3f 100644 --- a/confd/etc/calico/confd/templates/bird.cfg.template +++ b/confd/etc/calico/confd/templates/bird.cfg.template @@ -1,92 +1,25 @@ +{{- $config := getBGPConfig 4 . -}} + function apply_communities () { - {{- $prefix_advertisements_key := ""}} - {{- $node_prefix_advertisements_key := printf "/bgp/v1/host/%s/prefix_advertisements/ip_v4" (getenv "NODENAME")}} - {{- if exists $node_prefix_advertisements_key}} - {{- $prefix_advertisements_key = $node_prefix_advertisements_key}} - {{- else if exists "/bgp/v1/global/prefix_advertisements/ip_v4"}} - {{- $prefix_advertisements_key = "/bgp/v1/global/prefix_advertisements/ip_v4"}} - {{- end}} - {{- if ne "" $prefix_advertisements_key}} - {{- range gets $prefix_advertisements_key}} - {{- $arr:= jsonArray .Value}} - {{- range $data := $arr}} - if ( net ~ {{$data.cidr}} ) then { - {{- range $dt := $data.communities }} - {{- $i := split $dt ":"}} - {{- $length := len $i}} - {{- if eq $length 2}} - bgp_community.add(({{index $i 0}}, {{index $i 1}})); - {{- else}} - bgp_large_community.add(({{index $i 0}}, {{index $i 1}}, {{index $i 2}})); - {{- end}} - {{- end}} - } - {{- end}} +{{- range $config.Communities}} + if ( net ~ {{.CIDR}} ) then { + {{- range .AddStatements}} + {{.}} {{- end}} - {{- end}} + } +{{- end}} } # Generated by confd include "bird_aggr.cfg"; include "bird_ipam.cfg"; -{{- $node_ip_key := printf "/bgp/v1/host/%s/ip_addr_v4" (getenv "NODENAME")}}{{$node_ip := getv $node_ip_key}} -{{- $router_id := getenv "CALICO_ROUTER_ID" ""}} - -{{- $node_name := getenv "NODENAME"}} +router id {{$config.RouterID}}; -router id {{if eq "hash" ($router_id) -}} - {{hashToIPv4 $node_name}}; -{{- else -}} - {{if ne "" ($router_id)}}{{$router_id}}{{else}}{{$node_ip}}{{end}}; -{{- end}} - -{{- $listen_address := ""}} -{{- $bind_mode := ""}} -{{- $host_bind_mode_key := printf "/bgp/v1/host/%s/bind_mode" (getenv "NODENAME")}} -{{- if exists $host_bind_mode_key }} -{{- $bind_mode = getv $host_bind_mode_key}} -{{- else if exists "/bgp/v1/global/bind_mode" }} -{{- $bind_mode = getv "/bgp/v1/global/bind_mode"}} -{{- end}} -{{- if and (eq $bind_mode "NodeIP") (ne $node_ip "")}} -{{- $listen_address = print " address " $node_ip}} -{{- end}} - -{{- $listen_port := ""}} -{{- $node_listen_port_key := printf "/bgp/v1/host/%s/listen_port" (getenv "NODENAME")}} -{{- if exists $node_listen_port_key}} -# Set node listen_port -{{- $listen_port = print " port " (getv $node_listen_port_key)}} -{{- else if exists "/bgp/v1/global/listen_port" }} -# Set global listen_port -{{- $listen_port = print " port " (getv "/bgp/v1/global/listen_port")}} -{{- end}} - -{{- if or (ne $listen_address "") (ne $listen_port "")}} -listen bgp{{$listen_address}}{{$listen_port}}; -{{- end}} - -{{- define "LOGGING"}} -{{- $node_logging_key := printf "/bgp/v1/host/%s/loglevel" (getenv "NODENAME")}} -{{- if exists $node_logging_key}} -{{- $logging := getv $node_logging_key}} -{{- if eq $logging "debug"}} - debug all; -{{- else if ne $logging "none"}} - debug { states }; -{{- end}} -{{- else if exists "/bgp/v1/global/loglevel"}} -{{- $logging := getv "/bgp/v1/global/loglevel"}} -{{- if eq $logging "debug"}} - debug all; -{{- else if ne $logging "none"}} - debug { states }; -{{- end}} -{{- else}} - debug { states }; -{{- end}} +{{- if or $config.ListenAddress $config.ListenPort}} +# BGP listen configuration +listen bgp{{if $config.ListenAddress}} address {{$config.ListenAddress}}{{end}}{{if $config.ListenPort}} port {{$config.ListenPort}}{{end}}; {{- end}} # Configure synchronization between routing tables and kernel. @@ -107,22 +40,17 @@ protocol kernel { # Watch interface up/down events. protocol device { -{{- template "LOGGING"}} +{{- if $config.DebugMode}} + debug {{$config.DebugMode}}; +{{- end}} scan time 2; # Scan interfaces every 2 seconds } -{{- $ignored_interfaces := ""}} -{{- $node_ignored_interfaces_key := printf "/bgp/v1/host/%s/ignored_interfaces" (getenv "NODENAME")}} -{{- if exists $node_ignored_interfaces_key }}{{$ignored_interfaces = getv $node_ignored_interfaces_key}} -{{- else if exists "/bgp/v1/global/ignored_interfaces" }}{{$ignored_interfaces = getv "/bgp/v1/global/ignored_interfaces"}} -{{- end}} - protocol direct { -{{- template "LOGGING"}} -{{- if ne "" $ignored_interfaces}}{{$ifaces := split $ignored_interfaces ","}} - interface {{range $ifaces}}-"{{.}}", {{end}}-"cali*", -"kube-ipvs*", "*"; -{{- else}} - interface -"cali*", -"kube-ipvs*", "*"; # Exclude cali* and kube-ipvs* but +{{- if $config.DebugMode}} + debug {{$config.DebugMode}}; +{{- end}} + interface {{$config.DirectInterfaces}};{{if eq $config.DirectInterfaces `-"cali*", -"kube-ipvs*", "*"`}} # Exclude cali* and kube-ipvs* but # include everything else. In # IPVS-mode, kube-proxy creates a # kube-ipvs0 interface. We exclude @@ -130,19 +58,18 @@ protocol direct { # gets an address for every in use # cluster IP. We use static routes # for when we legitimately want to - # export cluster IPs. -{{- end}} + # export cluster IPs.{{end}} } -{{if eq "" ($node_ip)}}# IPv4 disabled on this node. -{{else}}{{$node_as_key := printf "/bgp/v1/host/%s/as_num" (getenv "NODENAME")}} +{{if eq "" $config.NodeIP}}# IPv4 disabled on this node. +{{else}} # Template for all BGP clients template bgp bgp_template { -{{- $as_key := or (and (exists $node_as_key) $node_as_key) "/bgp/v1/global/as_num"}} -{{- $node_as_num := getv $as_key}} -{{- template "LOGGING"}} +{{- if $config.DebugMode}} + debug {{$config.DebugMode}}; +{{- end}} description "Connection to BGP peer"; - local as {{$node_as_num}}; + local as {{$config.ASNumber}}; gateway recursive; # This should be the default, but just in case. add paths on; graceful restart; # See comment in kernel section about graceful restart. @@ -156,426 +83,68 @@ template bgp bgp_template { {{ $line }} {{- end }} -# ------------- Node-to-node mesh ------------- -{{- $node_cid_key := printf "/bgp/v1/host/%s/rr_cluster_id" (getenv "NODENAME")}} -{{- $node_cluster_id := getv $node_cid_key}} -{{- if ne "" ($node_cluster_id)}} -# This node ({{getenv "NODENAME"}}) is configured as a route reflector with cluster ID {{$node_cluster_id}}; -# ignore node-to-node mesh setting. -{{- else}} -{{if (json (getv "/bgp/v1/global/node_mesh")).enabled}} -{{range $host := lsdir "/bgp/v1/host"}} -{{$onode_as_key := printf "/bgp/v1/host/%s/as_num" .}} -{{$onode_ip_key := printf "/bgp/v1/host/%s/ip_addr_v4" .}}{{if exists $onode_ip_key}}{{$onode_ip := getv $onode_ip_key}} -{{- $listen_port := ""}} -{{- $onode_listen_port_key := printf "/bgp/v1/host/%s/listen_port" .}} -{{- if exists $onode_listen_port_key}} -{{- $listen_port = getv $onode_listen_port_key}} -{{- else if exists "/bgp/v1/global/listen_port"}} -{{- $listen_port = getv "/bgp/v1/global/listen_port"}} -{{- end}} -{{- $onode_cid_key := printf "/bgp/v1/host/%s/rr_cluster_id" .}} -{{- $onode_cluster_id := getv $onode_cid_key}} -{{$nums := split $onode_ip "."}}{{$id := join $nums "_"}} -# For peer {{$onode_ip_key}} -{{if eq $onode_ip ($node_ip) }}# Skipping ourselves ({{$node_ip}}) -{{- else if ne "" ($onode_cluster_id)}}# Skipping {{$onode_ip}} ({{.}}) as it is a route reflector with cluster ID {{$onode_cluster_id}} -{{else if ne "" $onode_ip}}protocol bgp Mesh_{{$id}} from bgp_template { - neighbor {{$onode_ip}} {{if ne "" $listen_port}}port {{$listen_port}} {{end}}as {{if exists $onode_as_key}}{{getv $onode_as_key}}{{else}}{{getv "/bgp/v1/global/as_num"}}{{end}}; - source address {{$node_ip}}; # The local address we use for the TCP connection - import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. - export filter { - calico_export_to_bgp_peers(true); - reject; - }; # Only want to export routes for workloads. - {{- /* - Make the peering unidirectional. This avoids a race where - - peer A opens a connection and begins a graceful restart - - before the restart completes, peer B opens its connection - - peer A sees the new connection and aborts the graceful restart, causing a route flap. - */ -}} - {{if gt $onode_ip $node_ip}} - passive on; # Mesh is unidirectional, peer will connect to us. - {{- end}} - {{- if exists "/bgp/v1/global/node_mesh_restart_time"}}{{$node_mesh_restart_time := getv "/bgp/v1/global/node_mesh_restart_time"}} - {{- if ne ($node_mesh_restart_time) ""}} - graceful restart time {{$node_mesh_restart_time}}; - {{- end}}{{end}} - {{- if exists "/bgp/v1/global/node_mesh_password"}}{{$node_mesh_password := getv "/bgp/v1/global/node_mesh_password"}} - {{- if ne ($node_mesh_password) ""}} - password "{{$node_mesh_password}}"; - {{- end}}{{end}} -}{{end}}{{end}}{{end}} -{{else}} -# Node-to-node mesh disabled -{{end}} -{{- end}} - - -# ------------- Global peers ------------- -{{if ls "/bgp/v1/global/peer_v4"}} -{{range gets "/bgp/v1/global/peer_v4/*"}}{{$data := json .Value}} -{{$nums := split $data.ip "."}}{{$id := join $nums "_"}} -{{- if $data.local_bgp_peer }} -# Skipping local bgp peer ({{$data.ip}}) -{{- else}} -{{- if $data.port}} -{{- $id = printf "%s_port_%.0f" $id $data.port}} -{{- end}} -# For peer {{.Key}} -{{- if eq $data.ip ($node_ip) }} -# Skipping ourselves ({{$node_ip}}) -{{- else}} -protocol bgp Global_{{$id}} from bgp_template { -{{- if $data.ttl_security }} - ttl security on; - multihop {{ $data.ttl_security }}; -{{- else }} +{{- if $config.Peers}} +# BGP Protocol Configurations +{{- range $config.Peers}} +protocol bgp {{.Name}} from bgp_template { +{{- if eq .TTLSecurity "off"}} ttl security off; multihop; -{{- end }} -{{- if ne ($data.local_as_num) "0"}} - local as {{$data.local_as_num}}; -{{- end }} - neighbor {{$data.ip}} {{if $data.port }}port {{ $data.port }} {{end}}as {{$data.as_num}}; -{{- if eq $data.source_addr "UseNodeIP"}} - source address {{$node_ip}}; # The local address we use for the TCP connection -{{- end}} - import filter { - {{- range $filter := $data.filters }} - {{- $filterKey := printf "/resources/v3/projectcalico.org/bgpfilters/%s" $filter }} - {{- if exists $filterKey }} - {{- $filterVal := json (getv $filterKey) }} - {{- if $filterVal.spec.importV4 }} - {{ bgpFilterFunctionName $filter "import" "4" }}(); - {{- end }} - {{- end }} - {{- end }} - accept; # Prior to introduction of BGP Filters we used "import all" so use default accept behaviour on import - }; - export filter { - {{- range $filter := $data.filters }} - {{- $filterKey := printf "/resources/v3/projectcalico.org/bgpfilters/%s" $filter }} - {{- if exists $filterKey }} - {{- $filterVal := json (getv $filterKey) }} - {{- if $filterVal.spec.exportV4 }} - {{ bgpFilterFunctionName $filter "export" "4" }}(); - {{- end }} - {{- end }} - {{- end }} - calico_export_to_bgp_peers({{eq $data.as_num $node_as_num}}); - reject;{{/* Prior to introduction of BGP Filters anything not explicitly exported through calico_export_to_bgp_peers() - was rejected so use default reject behaviour on export */}} - }; # Only want to export routes for workloads. -{{- if and ($data.calico_node) (gt $data.ip $node_ip)}} - passive on; # Peering is unidirectional, peer will connect to us. -{{- end}} -{{- if ne $data.restart_time ""}} - graceful restart time {{$data.restart_time}}; -{{- end}} -{{- if ne $data.keepalive_time ""}} - keepalive time {{$data.keepalive_time}}; -{{- end}} -{{- if and (eq $data.as_num $node_as_num) (ne "" ($node_cluster_id)) (ne $data.rr_cluster_id ($node_cluster_id))}} - rr client; - rr cluster id {{$node_cluster_id}}; -{{- end}} -{{- if $data.password}} - password "{{$data.password}}"; +{{- else if .TTLSecurity}} + ttl security {{.TTLSecurity}}; {{- end}} -{{- if and (ne $data.as_num $node_as_num) ($data.keep_next_hop)}} - next hop keep; -{{- end}} -{{- if eq $data.next_hop_mode "Self"}} - next hop self; -{{- end}} -{{- if eq $data.next_hop_mode "Keep"}} - next hop keep; +{{- if .LocalASNumber}} + local as {{.LocalASNumber}}; {{- end}} -{{- if $data.num_allow_local_as}} - allow local as {{$data.num_allow_local_as}}; -{{- end}} -{{- if $data.passive_mode}} - passive on; -{{- end}} -} -{{- end}} -{{- end}} -{{end}} -{{else}}# No global peers configured.{{end}} - -{{if ls "/bgp/v1/global/peer_v4"}} -{{$first := true}} -{{range gets "/bgp/v1/global/peer_v4/*"}}{{$data := json .Value}} -{{$nums := split $data.ip "."}}{{$id := join $nums "_"}} -{{- if not $data.local_bgp_peer }} -{{/* Skipping global bgp peer */}} -{{- else}} -{{- if $first}} -# ------------- Global local bgp peers ------------- -{{$first = false}} {{/* Update the flag so it is printed only once */}} -{{- end}} -{{- if $data.port}} -{{- $id = printf "%s_port_%.0f" $id $data.port}} -{{- end}} -# For peer {{.Key}} -protocol bgp Local_Workload_{{$id}} from bgp_template { -{{- if $data.ttl_security }} - ttl security on; - multihop {{ $data.ttl_security }}; -{{- else }} - ttl security off; - multihop; -{{- end }} -{{- if ne ($data.local_as_num) "0"}} - local as {{$data.local_as_num}}; -{{- end }} - neighbor {{$data.ip}} {{if $data.port }}port {{ $data.port }} {{end}}as {{$data.as_num}}; -{{- if eq $data.source_addr "UseNodeIP"}} - source address {{$node_ip}}; # The local address we use for the TCP connection + neighbor {{.IP}}{{if .Port}} port {{.Port}}{{end}} as {{.ASNumber}}; +{{- if .SourceAddr}} + source address {{.SourceAddr}}; # The local address we use for the TCP connection {{- end}} +{{- if .ImportFilter}} import filter { - {{- range $filter := $data.filters }} - {{- $filterKey := printf "/resources/v3/projectcalico.org/bgpfilters/%s" $filter }} - {{- if exists $filterKey }} - {{- $filterVal := json (getv $filterKey) }} - {{- if $filterVal.spec.importV4 }} - {{ bgpFilterFunctionName $filter "import" "4" }}(); - {{- end }} - {{- end }} - {{- end }} - accept; # Prior to introduction of BGP Filters we used "import all" so use default accept behaviour on import + {{.ImportFilter}} }; - export filter { - {{- range $filter := $data.filters }} - {{- $filterKey := printf "/resources/v3/projectcalico.org/bgpfilters/%s" $filter }} - {{- if exists $filterKey }} - {{- $filterVal := json (getv $filterKey) }} - {{- if $filterVal.spec.exportV4 }} - {{ bgpFilterFunctionName $filter "export" "4" }}(); - {{- end }} - {{- end }} - {{- end }} - calico_export_to_bgp_peers({{eq $data.as_num $node_as_num}}); - reject;{{/* Prior to introduction of BGP Filters anything not explicitly exported through calico_export_to_bgp_peers() - was rejected so use default reject behaviour on export */}} - }; # Only want to export routes for workloads. -{{- if and ($data.calico_node) (gt $data.ip $node_ip)}} - passive on; # Peering is unidirectional, peer will connect to us. -{{- end}} -{{- if ne $data.restart_time ""}} - graceful restart time {{$data.restart_time}}; -{{- end}} -{{- if ne $data.keepalive_time ""}} - keepalive time {{$data.keepalive_time}}; -{{- end}} -{{- if and (eq $data.as_num $node_as_num) (ne "" ($node_cluster_id)) (ne $data.rr_cluster_id ($node_cluster_id))}} - rr client; - rr cluster id {{$node_cluster_id}}; -{{- end}} -{{- if $data.password}} - password "{{$data.password}}"; -{{- end}} -{{- if and (ne $data.as_num $node_as_num) ($data.keep_next_hop)}} - next hop keep; -{{- end}} -{{- if eq $data.next_hop_mode "Self"}} - next hop self; -{{- end}} -{{- if eq $data.next_hop_mode "Keep"}} - next hop keep; -{{- end}} -{{- if $data.num_allow_local_as}} - allow local as {{$data.num_allow_local_as}}; -{{- end}} -{{- if $data.passive_mode}} - passive on; -{{- end}} -} -{{- end}} -{{end}} -{{end}}{{/* End of local bgp peer check */}} - - -# ------------- Node-specific peers ------------- -{{$node_peers_key := printf "/bgp/v1/host/%s/peer_v4" (getenv "NODENAME")}} -{{if ls $node_peers_key}} -{{range gets (printf "%s/*" $node_peers_key)}}{{$data := json .Value}} -{{$nums := split $data.ip "."}}{{$id := join $nums "_"}} -{{- if $data.local_bgp_peer }} -# Skipping local bgp peer ({{$data.ip}}) -{{- else}} -{{- if $data.port}} -{{- $id = printf "%s_port_%.0f" $id $data.port}} -{{- end}} -# For peer {{.Key}} -{{- if eq $data.ip ($node_ip) }} -# Skipping ourselves ({{$node_ip}}) {{- else}} -protocol bgp Node_{{$id}} from bgp_template { -{{- if $data.ttl_security }} - ttl security on; - multihop {{ $data.ttl_security }}; -{{- else }} - ttl security off; - multihop; -{{- end }} -{{- if ne ($data.local_as_num) "0"}} - local as {{$data.local_as_num}}; -{{- end }} - neighbor {{$data.ip}} {{if $data.port }}port {{ $data.port }} {{end}}as {{$data.as_num}}; -{{- if eq $data.source_addr "UseNodeIP"}} - source address {{$node_ip}}; # The local address we use for the TCP connection + import all; # Import all routes, since we don't know what the upstream + # topology is and therefore have to trust the ToR/RR. {{- end}} - import filter { - {{- range $filter := $data.filters }} - {{- $filterKey := printf "/resources/v3/projectcalico.org/bgpfilters/%s" $filter }} - {{- if exists $filterKey }} - {{- $filterVal := json (getv $filterKey) }} - {{- if $filterVal.spec.importV4 }} - {{ bgpFilterFunctionName $filter "import" "4" }}(); - {{- end }} - {{- end }} - {{- end }} - accept; # Prior to introduction of BGP Filters we used "import all" so use default accept behaviour on import - }; +{{- if .ExportFilter}} export filter { - {{- range $filter := $data.filters }} - {{- $filterKey := printf "/resources/v3/projectcalico.org/bgpfilters/%s" $filter }} - {{- if exists $filterKey }} - {{- $filterVal := json (getv $filterKey) }} - {{- if $filterVal.spec.exportV4 }} - {{ bgpFilterFunctionName $filter "export" "4" }}(); - {{- end }} - {{- end }} - {{- end }} - calico_export_to_bgp_peers({{eq $data.as_num $node_as_num}}); - reject;{{/* Prior to introduction of BGP Filters anything not explicitly exported through calico_export_to_bgp_peers() - was rejected so use default reject behaviour on export */}} + {{.ExportFilter}} }; # Only want to export routes for workloads. -{{- if ne $data.restart_time ""}} - graceful restart time {{$data.restart_time}}; -{{- end}} -{{- if ne $data.keepalive_time ""}} - keepalive time {{$data.keepalive_time}}; -{{- end}} -{{- if and (eq $data.as_num $node_as_num) (ne "" ($node_cluster_id)) (ne $data.rr_cluster_id ($node_cluster_id))}} - rr client; - rr cluster id {{$node_cluster_id}}; -{{- end}} -{{- if $data.password}} - password "{{$data.password}}"; -{{- end}} -{{- if and (ne $data.as_num $node_as_num) ($data.keep_next_hop)}} - next hop keep; -{{- end}} -{{- if eq $data.next_hop_mode "Self"}} - next hop self; -{{- end}} -{{- if eq $data.next_hop_mode "Keep"}} - next hop keep; -{{- end}} -{{- if $data.num_allow_local_as}} - allow local as {{$data.num_allow_local_as}}; -{{- end}} -{{- if $data.passive_mode}} - passive on; -{{- end}} -} -{{- end}} -{{- end}} -{{end}} -{{else}}# No node-specific peers configured.{{end}} - -{{$node_peers_key := printf "/bgp/v1/host/%s/peer_v4" (getenv "NODENAME")}} -{{if ls $node_peers_key}} -{{$first := true}} -{{range gets (printf "%s/*" $node_peers_key)}}{{$data := json .Value}} -{{$nums := split $data.ip "."}}{{$id := join $nums "_"}} -{{- if not $data.local_bgp_peer }} -{{/* Skipping non local bgp peer */}} {{- else}} -{{- if $first}} -# ------------- Node-specific local BGP peers ------------- -{{$first = false}} {{/* Update the flag so it is printed only once */}} + export all; {{- end}} -{{- if $data.port}} -{{- $id = printf "%s_port_%.0f" $id $data.port}} +{{- if .Passive}} + passive on; {{- end}} -# For peer {{.Key}} -protocol bgp Local_Workload_{{$id}} from bgp_template { -{{- if $data.ttl_security }} - ttl security on; - multihop {{ $data.ttl_security }}; -{{- else }} - ttl security off; - multihop; -{{- end }} -{{- if ne ($data.local_as_num) "0"}} - local as {{$data.local_as_num}}; -{{- end }} - neighbor {{$data.ip}} {{if $data.port }}port {{ $data.port }} {{end}}as {{$data.as_num}}; -{{- if eq $data.source_addr "UseNodeIP"}} - source address {{$node_ip}}; # The local address we use for the TCP connection +{{- if .GracefulRestart}} + graceful restart time {{.GracefulRestart}}; {{- end}} - import filter { - {{- range $filter := $data.filters }} - {{- $filterKey := printf "/resources/v3/projectcalico.org/bgpfilters/%s" $filter }} - {{- if exists $filterKey }} - {{- $filterVal := json (getv $filterKey) }} - {{- if $filterVal.spec.importV4 }} - {{ bgpFilterFunctionName $filter "import" "4" }}(); - {{- end }} - {{- end }} - {{- end }} - accept; # Prior to introduction of BGP Filters we used "import all" so use default accept behaviour on import - }; - export filter { - {{- range $filter := $data.filters }} - {{- $filterKey := printf "/resources/v3/projectcalico.org/bgpfilters/%s" $filter }} - {{- if exists $filterKey }} - {{- $filterVal := json (getv $filterKey) }} - {{- if $filterVal.spec.exportV4 }} - {{ bgpFilterFunctionName $filter "export" "4" }}(); - {{- end }} - {{- end }} - {{- end }} - calico_export_to_bgp_peers({{eq $data.as_num $node_as_num}}); - reject;{{/* Prior to introduction of BGP Filters anything not explicitly exported through calico_export_to_bgp_peers() - was rejected so use default reject behaviour on export */}} - }; # Only want to export routes for workloads. -{{- if ne $data.restart_time ""}} - graceful restart time {{$data.restart_time}}; +{{- if .KeepaliveTime}} + keepalive time {{.KeepaliveTime}}; {{- end}} -{{- if ne $data.keepalive_time ""}} - keepalive time {{$data.keepalive_time}}; -{{- end}} -{{- if and (eq $data.as_num $node_as_num) (ne "" ($node_cluster_id)) (ne $data.rr_cluster_id ($node_cluster_id))}} +{{- if and .RouteReflector .RRClusterID}} rr client; - rr cluster id {{$node_cluster_id}}; + rr cluster id {{.RRClusterID}}; {{- end}} -{{- if $data.password}} - password "{{$data.password}}"; +{{- if .Password}} + password "{{.Password}}"; {{- end}} -{{- if and (ne $data.as_num $node_as_num) ($data.keep_next_hop)}} +{{- if .NextHopKeep}} next hop keep; {{- end}} -{{- if eq $data.next_hop_mode "Self"}} +{{- if .NextHopSelf}} next hop self; {{- end}} -{{- if eq $data.next_hop_mode "Keep"}} - next hop keep; -{{- end}} -{{- if $data.num_allow_local_as}} - allow local as {{$data.num_allow_local_as}}; -{{- end}} -{{- if $data.passive_mode}} - passive on; +{{- if .NumAllowLocalAs}} + allow local as {{.NumAllowLocalAs}}; {{- end}} } + +{{- end}} +{{- else}} +# No BGP peers configured for this node {{- end}} -{{end}} -{{end}}{{/* End of local bgp peer check */}} {{end}}{{/* End of IPv4 enable check */}} diff --git a/confd/etc/calico/confd/templates/bird6.cfg.template b/confd/etc/calico/confd/templates/bird6.cfg.template index a6bd3c60a33..76d5d63a255 100644 --- a/confd/etc/calico/confd/templates/bird6.cfg.template +++ b/confd/etc/calico/confd/templates/bird6.cfg.template @@ -1,93 +1,25 @@ +{{- $config := getBGPConfig 6 . -}} + function apply_communities () { - {{- $prefix_advertisements_key := ""}} - {{- $node_prefix_advertisements_key := printf "/bgp/v1/host/%s/prefix_advertisements/ip_v6" (getenv "NODENAME")}} - {{- if exists $node_prefix_advertisements_key}} - {{- $prefix_advertisements_key = $node_prefix_advertisements_key}} - {{- else if exists "/bgp/v1/global/prefix_advertisements/ip_v6"}} - {{- $prefix_advertisements_key = "/bgp/v1/global/prefix_advertisements/ip_v6"}} - {{- end}} - {{- if ne "" $prefix_advertisements_key}} - {{- range gets $prefix_advertisements_key}} - {{- $arr:= jsonArray .Value}} - {{- range $data := $arr}} - if ( net ~ {{$data.cidr}} ) then { - {{- range $dt := $data.communities }} - {{- $i := split $dt ":"}} - {{- $length := len $i}} - {{- if eq $length 2}} - bgp_community.add(({{index $i 0}}, {{index $i 1}})); - {{- else}} - bgp_large_community.add(({{index $i 0}}, {{index $i 1}}, {{index $i 2}})); - {{- end}} - {{- end}} - } - {{- end}} +{{- range $config.Communities}} + if ( net ~ {{.CIDR}} ) then { + {{- range .AddStatements}} + {{.}} {{- end}} - {{- end}} + } +{{- end}} } # Generated by confd include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -{{- $node_ip_key := printf "/bgp/v1/host/%s/ip_addr_v4" (getenv "NODENAME")}}{{$node_ip := getv $node_ip_key}} -{{- $node_ip6_key := printf "/bgp/v1/host/%s/ip_addr_v6" (getenv "NODENAME")}}{{$node_ip6 := getv $node_ip6_key}} -{{- $router_id := getenv "CALICO_ROUTER_ID" ""}} - -{{- $node_name := getenv "NODENAME"}} +router id {{$config.RouterID}}; -router id {{if eq "hash" ($router_id) -}} - {{hashToIPv4 $node_name}}; # Use IP address generated by nodename's hash -{{- else -}} - {{if ne "" ($router_id)}}{{$router_id}}{{else}}{{$node_ip}}{{end}}; # Use IPv4 address since router id is 4 octets, even in MP-BGP -{{- end}} - -{{- $listen_address := ""}} -{{- $bind_mode := ""}} -{{- $host_bind_mode_key := printf "/bgp/v1/host/%s/bind_mode" (getenv "NODENAME")}} -{{- if exists $host_bind_mode_key }} -{{- $bind_mode = getv $host_bind_mode_key}} -{{- else if exists "/bgp/v1/global/bind_mode" }} -{{- $bind_mode = getv "/bgp/v1/global/bind_mode"}} -{{- end}} -{{- if and (eq $bind_mode "NodeIP") (ne $node_ip6 "")}} -{{- $listen_address = print " address " $node_ip6}} -{{- end}} - -{{- $listen_port := ""}} -{{- $node_listen_port_key := printf "/bgp/v1/host/%s/listen_port" (getenv "NODENAME")}} -{{- if exists $node_listen_port_key}} -# Set node listen_port -{{- $listen_port = print " port " (getv $node_listen_port_key)}} -{{- else if exists "/bgp/v1/global/listen_port" }} -# Set global listen_port -{{- $listen_port = print " port " (getv "/bgp/v1/global/listen_port")}} -{{- end}} - -{{- if or (ne $listen_address "") (ne $listen_port "")}} -listen bgp{{$listen_address}}{{$listen_port}}; -{{- end}} - -{{- define "LOGGING"}} -{{- $node_logging_key := printf "/bgp/v1/host/%s/loglevel" (getenv "NODENAME")}} -{{- if exists $node_logging_key}} -{{- $logging := getv $node_logging_key}} -{{- if eq $logging "debug"}} - debug all; -{{- else if ne $logging "none"}} - debug { states }; -{{- end}} -{{- else if exists "/bgp/v1/global/loglevel"}} -{{- $logging := getv "/bgp/v1/global/loglevel"}} -{{- if eq $logging "debug"}} - debug all; -{{- else if ne $logging "none"}} - debug { states }; -{{- end}} -{{- else}} - debug { states }; -{{- end}} +{{- if or $config.ListenAddress $config.ListenPort}} +# BGP listen configuration +listen bgp{{if $config.ListenAddress}} address {{$config.ListenAddress}}{{end}}{{if $config.ListenPort}} port {{$config.ListenPort}}{{end}}; {{- end}} # Configure synchronization between routing tables and kernel. @@ -108,22 +40,17 @@ protocol kernel { # Watch interface up/down events. protocol device { -{{- template "LOGGING"}} +{{- if $config.DebugMode}} + debug {{$config.DebugMode}}; +{{- end}} scan time 2; # Scan interfaces every 2 seconds } -{{- $ignored_interfaces := ""}} -{{- $node_ignored_interfaces_key := printf "/bgp/v1/host/%s/ignored_interfaces" (getenv "NODENAME")}} -{{- if exists $node_ignored_interfaces_key }}{{$ignored_interfaces = getv $node_ignored_interfaces_key}} -{{- else if exists "/bgp/v1/global/ignored_interfaces" }}{{$ignored_interfaces = getv "/bgp/v1/global/ignored_interfaces"}} -{{- end}} - protocol direct { -{{- template "LOGGING"}} -{{- if ne "" $ignored_interfaces}}{{$ifaces := split $ignored_interfaces ","}} - interface {{range $ifaces}}-"{{.}}", {{end}}-"cali*", -"kube-ipvs*", "*"; -{{- else}} - interface -"cali*", -"kube-ipvs*", "*"; # Exclude cali* and kube-ipvs* but +{{- if $config.DebugMode}} + debug {{$config.DebugMode}}; +{{- end}} + interface {{$config.DirectInterfaces}};{{if eq $config.DirectInterfaces `-"cali*", -"kube-ipvs*", "*"`}} # Exclude cali* and kube-ipvs* but # include everything else. In # IPVS-mode, kube-proxy creates a # kube-ipvs0 interface. We exclude @@ -131,19 +58,17 @@ protocol direct { # gets an address for every in use # cluster IP. We use static routes # for when we legitimately want to - # export cluster IPs. -{{- end}} + # export cluster IPs.{{end}} } -{{if eq "" ($node_ip6)}}# IPv6 disabled on this node. -{{else}}{{$node_as_key := printf "/bgp/v1/host/%s/as_num" (getenv "NODENAME")}} -# Template for all BGP clients +{{if eq "" ($config.NodeIPv6)}}# IPv6 disabled on this node. +{{else}}# Template for all BGP clients template bgp bgp_template { -{{- $as_key := or (and (exists $node_as_key) $node_as_key) "/bgp/v1/global/as_num"}} -{{- $node_as_num := getv $as_key}} -{{- template "LOGGING"}} +{{- if $config.DebugMode}} + debug {{$config.DebugMode}}; +{{- end}} description "Connection to BGP peer"; - local as {{$node_as_num}}; + local as {{$config.ASNumber}}; gateway recursive; # This should be the default, but just in case. add paths on; graceful restart; # See comment in kernel section about graceful restart. @@ -157,425 +82,68 @@ template bgp bgp_template { {{ $line }} {{- end }} -# ------------- Node-to-node mesh ------------- -{{- $node_cid_key := printf "/bgp/v1/host/%s/rr_cluster_id" (getenv "NODENAME")}} -{{- $node_cluster_id := getv $node_cid_key}} -{{- if ne "" ($node_cluster_id)}} -# This node ({{getenv "NODENAME"}}) is configured as a route reflector with cluster ID {{$node_cluster_id}}; -# ignore node-to-node mesh setting. -{{- else}} -{{if (json (getv "/bgp/v1/global/node_mesh")).enabled}} -{{range $host := lsdir "/bgp/v1/host"}} -{{$onode_as_key := printf "/bgp/v1/host/%s/as_num" .}} -{{$onode_ip_key := printf "/bgp/v1/host/%s/ip_addr_v6" .}}{{if exists $onode_ip_key}}{{$onode_ip := getv $onode_ip_key}} -{{- $listen_port := ""}} -{{- $onode_listen_port_key := printf "/bgp/v1/host/%s/listen_port" .}} -{{- if exists $onode_listen_port_key}} -{{- $listen_port = getv $onode_listen_port_key}} -{{- else if exists "/bgp/v1/global/listen_port"}} -{{- $listen_port = getv "/bgp/v1/global/listen_port"}} -{{- end}} -{{- $onode_cid_key := printf "/bgp/v1/host/%s/rr_cluster_id" .}} -{{- $onode_cluster_id := getv $onode_cid_key}} -{{$nums := split $onode_ip ":"}}{{$id := join $nums "_"}} -# For peer {{$onode_ip_key}} -{{if eq $onode_ip ($node_ip6) }}# Skipping ourselves ({{$node_ip6}}) -{{- else if ne "" ($onode_cluster_id)}}# Skipping {{$onode_ip}} ({{.}}) as it is a route reflector with cluster ID {{$onode_cluster_id}} -{{else if eq "" $onode_ip}}# No IPv6 address configured for this node -{{else}}protocol bgp Mesh_{{$id}} from bgp_template { - neighbor {{$onode_ip}} {{if ne "" $listen_port}}port {{$listen_port}} {{end}}as {{if exists $onode_as_key}}{{getv $onode_as_key}}{{else}}{{getv "/bgp/v1/global/as_num"}}{{end}}; - source address {{$node_ip6}}; # The local address we use for the TCP connection - import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. - export filter { - calico_export_to_bgp_peers(true); - reject; - }; # Only want to export routes for workloads. - {{- /* - Make the peering unidirectional. This avoids a race where - - peer A opens a connection and begins a graceful restart - - before the restart completes, peer B opens its connection - - peer A sees the new connection and aborts the graceful restart, causing a route flap. - */ -}} - {{if gt $onode_ip $node_ip6 }} - passive on; # Mesh is unidirectional, peer will connect to us. - {{- end}} - {{- if exists "/bgp/v1/global/node_mesh_restart_time"}}{{$node_mesh_restart_time := getv "/bgp/v1/global/node_mesh_restart_time"}} - {{- if ne ($node_mesh_restart_time) ""}} - graceful restart time {{$node_mesh_restart_time}}; - {{- end}}{{end}} - {{- if exists "/bgp/v1/global/node_mesh_password"}}{{$node_mesh_password := getv "/bgp/v1/global/node_mesh_password"}} - {{- if ne ($node_mesh_password) ""}} - password "{{$node_mesh_password}}"; - {{- end}}{{end}} -}{{end}}{{end}}{{end}} -{{else}} -# Node-to-node mesh disabled -{{end}} -{{- end}} - - -# ------------- Global peers ------------- -{{if ls "/bgp/v1/global/peer_v6"}} -{{range gets "/bgp/v1/global/peer_v6/*"}}{{$data := json .Value}} -{{$nums := split $data.ip ":"}}{{$id := join $nums "_"}} -{{- if $data.local_bgp_peer }} -# Skipping local bgp peer ({{$data.ip}}) -{{- else}} -{{- if $data.port}} -{{- $id = printf "%s_port_%.0f" $id $data.port}} -{{- end}} -# For peer {{.Key}} -{{- if eq $data.ip ($node_ip6) }} -# Skipping ourselves ({{$node_ip6}}) -{{- else}} -protocol bgp Global_{{$id}} from bgp_template { -{{- if $data.ttl_security }} - ttl security on; - multihop {{ $data.ttl_security }}; -{{- else }} +{{- if $config.Peers}} +# BGP Protocol Configurations +{{- range $config.Peers}} +protocol bgp {{.Name}} from bgp_template { +{{- if eq .TTLSecurity "off"}} ttl security off; multihop; -{{- end }} -{{- if ne ($data.local_as_num) "0"}} - local as {{$data.local_as_num}}; -{{- end }} - neighbor {{$data.ip}} {{if $data.port }}port {{ $data.port }} {{end}}as {{$data.as_num}}; -{{- if eq $data.source_addr "UseNodeIP"}} - source address {{$node_ip6}}; # The local address we use for the TCP connection +{{- else if .TTLSecurity}} + ttl security {{.TTLSecurity}}; {{- end}} - import filter { - {{- range $filter := $data.filters }} - {{- $filterKey := printf "/resources/v3/projectcalico.org/bgpfilters/%s" $filter }} - {{- if exists $filterKey }} - {{- $filterVal := json (getv $filterKey) }} - {{- if $filterVal.spec.importV6 }} - {{ bgpFilterFunctionName $filter "import" "6" }}(); - {{- end }} - {{- end }} - {{- end }} - accept; # Prior to introduction of BGP Filters we used "import all" so use default accept behaviour on import - }; - export filter { - {{- range $filter := $data.filters }} - {{- $filterKey := printf "/resources/v3/projectcalico.org/bgpfilters/%s" $filter }} - {{- if exists $filterKey }} - {{- $filterVal := json (getv $filterKey) }} - {{- if $filterVal.spec.exportV6 }} - {{ bgpFilterFunctionName $filter "export" "6" }}(); - {{- end }} - {{- end }} - {{- end }} - calico_export_to_bgp_peers({{eq $data.as_num $node_as_num}}); - reject;{{/* Prior to introduction of BGP Filters anything not explicitly exported through calico_export_to_bgp_peers() - was rejected so use default reject behaviour on export */}} - }; # Only want to export routes for workloads. -{{- if and ($data.calico_node) (gt $data.ip $node_ip6)}} - passive on; # Peering is unidirectional, peer will connect to us. +{{- if .LocalASNumber}} + local as {{.LocalASNumber}}; {{- end}} -{{- if ne $data.restart_time ""}} - graceful restart time {{$data.restart_time}}; -{{- end}} -{{- if ne $data.keepalive_time ""}} - keepalive time {{$data.keepalive_time}}; -{{- end}} -{{- if and (eq $data.as_num $node_as_num) (ne "" ($node_cluster_id)) (ne $data.rr_cluster_id ($node_cluster_id))}} - rr client; - rr cluster id {{$node_cluster_id}}; -{{- end}} -{{- if $data.password}} - password "{{$data.password}}"; -{{- end}} -{{- if and (ne $data.as_num $node_as_num) ($data.keep_next_hop)}} - next hop keep; -{{- end}} -{{- if eq $data.next_hop_mode "Self"}} - next hop self; -{{- end}} -{{- if eq $data.next_hop_mode "Keep"}} - next hop keep; -{{- end}} -{{- if $data.num_allow_local_as}} - allow local as {{$data.num_allow_local_as}}; -{{- end}} -{{- if $data.passive_mode}} - passive on; -{{- end}} -} -{{- end}} -{{- end}} -{{end}} -{{else}}# No global peers configured.{{end}} - -{{if ls "/bgp/v1/global/peer_v6"}} -{{$first := true}} -{{range gets "/bgp/v1/global/peer_v6/*"}}{{$data := json .Value}} -{{$nums := split $data.ip ":"}}{{$id := join $nums "_"}} -{{- if not $data.local_bgp_peer }} -{{/* Skipping global bgp peer */}} -{{- else}} -{{- if $first}} -# ------------- Global local bgp peers ------------- -{{$first = false}} {{/* Update the flag so it is printed only once */}} -{{- end}} -{{- if $data.port}} -{{- $id = printf "%s_port_%.0f" $id $data.port}} -{{- end}} -# For peer {{.Key}} -protocol bgp Local_Workload_{{$id}} from bgp_template { -{{- if $data.ttl_security }} - ttl security on; - multihop {{ $data.ttl_security }}; -{{- else }} - ttl security off; - multihop; -{{- end }} -{{- if ne ($data.local_as_num) "0"}} - local as {{$data.local_as_num}}; -{{- end }} - neighbor {{$data.ip}} {{if $data.port }}port {{ $data.port }} {{end}}as {{$data.as_num}}; -{{- if eq $data.source_addr "UseNodeIP"}} - source address {{$node_ip6}}; # The local address we use for the TCP connection + neighbor {{.IP}}{{if .Port}} port {{.Port}}{{end}} as {{.ASNumber}}; +{{- if .SourceAddr}} + source address {{.SourceAddr}}; # The local address we use for the TCP connection {{- end}} +{{- if .ImportFilter}} import filter { - {{- range $filter := $data.filters }} - {{- $filterKey := printf "/resources/v3/projectcalico.org/bgpfilters/%s" $filter }} - {{- if exists $filterKey }} - {{- $filterVal := json (getv $filterKey) }} - {{- if $filterVal.spec.importV6 }} - {{ bgpFilterFunctionName $filter "import" "6" }}(); - {{- end }} - {{- end }} - {{- end }} - accept; # Prior to introduction of BGP Filters we used "import all" so use default accept behaviour on import + {{.ImportFilter}} }; - export filter { - {{- range $filter := $data.filters }} - {{- $filterKey := printf "/resources/v3/projectcalico.org/bgpfilters/%s" $filter }} - {{- if exists $filterKey }} - {{- $filterVal := json (getv $filterKey) }} - {{- if $filterVal.spec.exportV6 }} - {{ bgpFilterFunctionName $filter "export" "6" }}(); - {{- end }} - {{- end }} - {{- end }} - calico_export_to_bgp_peers({{eq $data.as_num $node_as_num}}); - reject;{{/* Prior to introduction of BGP Filters anything not explicitly exported through calico_export_to_bgp_peers() - was rejected so use default reject behaviour on export */}} - }; # Only want to export routes for workloads. -{{- if and ($data.calico_node) (gt $data.ip $node_ip6)}} - passive on; # Peering is unidirectional, peer will connect to us. -{{- end}} -{{- if ne $data.restart_time ""}} - graceful restart time {{$data.restart_time}}; -{{- end}} -{{- if ne $data.keepalive_time ""}} - keepalive time {{$data.keepalive_time}}; -{{- end}} -{{- if and (eq $data.as_num $node_as_num) (ne "" ($node_cluster_id)) (ne $data.rr_cluster_id ($node_cluster_id))}} - rr client; - rr cluster id {{$node_cluster_id}}; -{{- end}} -{{- if $data.password}} - password "{{$data.password}}"; -{{- end}} -{{- if and (ne $data.as_num $node_as_num) ($data.keep_next_hop)}} - next hop keep; -{{- end}} -{{- if eq $data.next_hop_mode "Self"}} - next hop self; -{{- end}} -{{- if eq $data.next_hop_mode "Keep"}} - next hop keep; -{{- end}} -{{- if $data.num_allow_local_as}} - allow local as {{$data.num_allow_local_as}}; -{{- end}} -{{- if $data.passive_mode}} - passive on; -{{- end}} -} -{{- end}} -{{end}} -{{end}}{{/* End of local bgp peer check */}} - -# ------------- Node-specific peers ------------- -{{$node_peers_key := printf "/bgp/v1/host/%s/peer_v6" (getenv "NODENAME")}} -{{if ls $node_peers_key}} -{{range gets (printf "%s/*" $node_peers_key)}}{{$data := json .Value}} -{{- if $data.local_bgp_peer }} -# Skipping local bgp peer ({{$data.ip}}) {{- else}} -{{$nums := split $data.ip ":"}}{{$id := join $nums "_"}} -{{- if $data.port}} -{{- $id = printf "%s_port_%.0f" $id $data.port}} -{{- end}} -# For peer {{.Key}} -{{- if eq $data.ip ($node_ip6) }} -# Skipping ourselves ({{$node_ip6}}) -{{- else}} -protocol bgp Node_{{$id}} from bgp_template { -{{- if $data.ttl_security }} - ttl security on; - multihop {{ $data.ttl_security }}; -{{- else }} - ttl security off; - multihop; -{{- end }} -{{- if ne ($data.local_as_num) "0"}} - local as {{$data.local_as_num}}; -{{- end }} - neighbor {{$data.ip}} {{if $data.port }}port {{ $data.port }} {{end}}as {{$data.as_num}}; -{{- if eq $data.source_addr "UseNodeIP"}} - source address {{$node_ip6}}; # The local address we use for the TCP connection + import all; # Import all routes, since we don't know what the upstream + # topology is and therefore have to trust the ToR/RR. {{- end}} - import filter { - {{- range $filter := $data.filters }} - {{- $filterKey := printf "/resources/v3/projectcalico.org/bgpfilters/%s" $filter }} - {{- if exists $filterKey }} - {{- $filterVal := json (getv $filterKey) }} - {{- if $filterVal.spec.importV6 }} - {{ bgpFilterFunctionName $filter "import" "6" }}(); - {{- end }} - {{- end }} - {{- end }} - accept; # Prior to introduction of BGP Filters we used "import all" so use default accept behaviour on import - }; +{{- if .ExportFilter}} export filter { - {{- range $filter := $data.filters }} - {{- $filterKey := printf "/resources/v3/projectcalico.org/bgpfilters/%s" $filter }} - {{- if exists $filterKey }} - {{- $filterVal := json (getv $filterKey) }} - {{- if $filterVal.spec.exportV6 }} - {{ bgpFilterFunctionName $filter "export" "6" }}(); - {{- end }} - {{- end }} - {{- end }} - calico_export_to_bgp_peers({{eq $data.as_num $node_as_num}}); - reject;{{/* Prior to introduction of BGP Filters anything not explicitly exported through calico_export_to_bgp_peers() - was rejected so use default reject behaviour on export */}} + {{.ExportFilter}} }; # Only want to export routes for workloads. -{{- if ne $data.restart_time ""}} - graceful restart time {{$data.restart_time}}; -{{- end}} -{{- if ne $data.keepalive_time ""}} - keepalive time {{$data.keepalive_time}}; -{{- end}} -{{- if and (eq $data.as_num $node_as_num) (ne "" ($node_cluster_id)) (ne $data.rr_cluster_id ($node_cluster_id))}} - rr client; - rr cluster id {{$node_cluster_id}}; -{{- end}} -{{- if $data.password}} - password "{{$data.password}}"; -{{- end}} -{{- if and (ne $data.as_num $node_as_num) ($data.keep_next_hop)}} - next hop keep; -{{- end}} -{{- if eq $data.next_hop_mode "Self"}} - next hop self; -{{- end}} -{{- if eq $data.next_hop_mode "Keep"}} - next hop keep; -{{- end}} -{{- if $data.num_allow_local_as}} - allow local as {{$data.num_allow_local_as}}; -{{- end}} -{{- if $data.passive_mode}} - passive on; -{{- end}} -} -{{- end}} -{{- end}} -{{end}} -{{else}}# No node-specific peers configured.{{end}} - -{{$node_peers_key := printf "/bgp/v1/host/%s/peer_v6" (getenv "NODENAME")}} -{{if ls $node_peers_key}} -{{$first := true}} -{{range gets (printf "%s/*" $node_peers_key)}}{{$data := json .Value}} -{{$nums := split $data.ip ":"}}{{$id := join $nums "_"}} -{{- if not $data.local_bgp_peer }} -{{/* Skipping non local bgp peer */}} {{- else}} -{{- if $first}} -# ------------- Node-specific local BGP peers ------------- -{{$first = false}} {{/* Update the flag so it is printed only once */}} + export all; {{- end}} -{{- if $data.port}} -{{- $id = printf "%s_port_%.0f" $id $data.port}} -{{- end}} -# For peer {{.Key}} -protocol bgp Local_Workload_{{$id}} from bgp_template { -{{- if $data.ttl_security }} - ttl security on; - multihop {{ $data.ttl_security }}; -{{- else }} - ttl security off; - multihop; -{{- end }} -{{- if ne ($data.local_as_num) "0"}} - local as {{$data.local_as_num}}; -{{- end }} - neighbor {{$data.ip}} {{if $data.port }}port {{ $data.port }} {{end}}as {{$data.as_num}}; -{{- if eq $data.source_addr "UseNodeIP"}} - source address {{$node_ip6}}; # The local address we use for the TCP connection +{{- if .Passive}} + passive on; {{- end}} - import filter { - {{- range $filter := $data.filters }} - {{- $filterKey := printf "/resources/v3/projectcalico.org/bgpfilters/%s" $filter }} - {{- if exists $filterKey }} - {{- $filterVal := json (getv $filterKey) }} - {{- if $filterVal.spec.importV6 }} - {{ bgpFilterFunctionName $filter "import" "6" }}(); - {{- end }} - {{- end }} - {{- end }} - accept; # Prior to introduction of BGP Filters we used "import all" so use default accept behaviour on import - }; - export filter { - {{- range $filter := $data.filters }} - {{- $filterKey := printf "/resources/v3/projectcalico.org/bgpfilters/%s" $filter }} - {{- if exists $filterKey }} - {{- $filterVal := json (getv $filterKey) }} - {{- if $filterVal.spec.exportV6 }} - {{ bgpFilterFunctionName $filter "export" "6" }}(); - {{- end }} - {{- end }} - {{- end }} - calico_export_to_bgp_peers({{eq $data.as_num $node_as_num}}); - reject;{{/* Prior to introduction of BGP Filters anything not explicitly exported through calico_export_to_bgp_peers() - was rejected so use default reject behaviour on export */}} - }; # Only want to export routes for workloads. -{{- if ne $data.restart_time ""}} - graceful restart time {{$data.restart_time}}; +{{- if .GracefulRestart}} + graceful restart time {{.GracefulRestart}}; {{- end}} -{{- if ne $data.keepalive_time ""}} - keepalive time {{$data.keepalive_time}}; +{{- if .KeepaliveTime}} + keepalive time {{.KeepaliveTime}}; {{- end}} -{{- if and (eq $data.as_num $node_as_num) (ne "" ($node_cluster_id)) (ne $data.rr_cluster_id ($node_cluster_id))}} +{{- if and .RouteReflector .RRClusterID}} rr client; - rr cluster id {{$node_cluster_id}}; + rr cluster id {{.RRClusterID}}; {{- end}} -{{- if $data.password}} - password "{{$data.password}}"; +{{- if .Password}} + password "{{.Password}}"; {{- end}} -{{- if and (ne $data.as_num $node_as_num) ($data.keep_next_hop)}} +{{- if .NextHopKeep}} next hop keep; {{- end}} -{{- if eq $data.next_hop_mode "Self"}} +{{- if .NextHopSelf}} next hop self; {{- end}} -{{- if eq $data.next_hop_mode "Keep"}} - next hop keep; -{{- end}} -{{- if $data.num_allow_local_as}} - allow local as {{$data.num_allow_local_as}}; -{{- end}} -{{- if $data.passive_mode}} - passive on; +{{- if .NumAllowLocalAs}} + allow local as {{.NumAllowLocalAs}}; {{- end}} } + +{{- end}} +{{- else}} +# No BGP peers configured for this node {{- end}} -{{end}} -{{end}}{{/* End of local bgp peer check */}} -{{end}} + +{{end}}{{/* End of IPv6 enable check */}} diff --git a/confd/etc/calico/confd/templates/bird6_aggr.cfg.template b/confd/etc/calico/confd/templates/bird6_aggr.cfg.template index d299b0a551e..179c5475d0a 100644 --- a/confd/etc/calico/confd/templates/bird6_aggr.cfg.template +++ b/confd/etc/calico/confd/templates/bird6_aggr.cfg.template @@ -1,7 +1,9 @@ # Generated by confd +{{- $config := getBGPConfig 6 . }} + {{$route_added := false}} protocol static { -{{- $block_key := printf "/ipam/v2/host/%s/ipv6/block" (getenv "NODENAME")}} +{{- $block_key := printf "/ipam/v2/host/%s/ipv6/block" $config.NodeName}} {{- if ls $block_key}} {{- $route_added = true}} # IP blocks for this host. @@ -37,7 +39,7 @@ protocol static { {{- end}} {{- end}} {{- end}} -{{- $node_peers_key := printf "/bgp/v1/host/%s/peer_v6" (getenv "NODENAME")}} +{{- $node_peers_key := printf "/bgp/v1/host/%s/peer_v6" $config.NodeName}} {{- if ls $node_peers_key}} {{- $run_once := true}} {{- range gets (printf "%s/*" $node_peers_key)}}{{$data := json .Value}} diff --git a/confd/etc/calico/confd/templates/bird6_ipam.cfg.template b/confd/etc/calico/confd/templates/bird6_ipam.cfg.template index fc65d01d3c2..d4940dbd8e1 100644 --- a/confd/etc/calico/confd/templates/bird6_ipam.cfg.template +++ b/confd/etc/calico/confd/templates/bird6_ipam.cfg.template @@ -1,12 +1,5 @@ # Generated by confd -function reject_disabled_pools () -{ -{{range ls "/v1/ipam/v6/pool"}}{{$data := json (getv (printf "/v1/ipam/v6/pool/%s" .))}} -{{- if $data.disableBGPExport}} - if ( net ~ {{$data.cidr}} ) then { reject; } -{{- end}} -{{- end}} -} +{{- $config := getBGPConfig 6 . }} function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. @@ -29,9 +22,11 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + +{{- range $line := $config.BGPExportFilterForDisabledIPPools }} +{{ $line }} +{{- end}} + if (internal_peer) then { reject_tunnel_routes(); } @@ -48,7 +43,7 @@ function calico_export_to_bgp_peers(bool internal_peer) { if ( net ~ {{$cidr}} ) then { accept; } {{- end}} {{- end}} -{{- $rr_cluster_id_key := printf "/bgp/v1/host/%s/rr_cluster_id" (getenv "NODENAME")}} +{{- $rr_cluster_id_key := printf "/bgp/v1/host/%s/rr_cluster_id" $config.NodeName}} {{- $lb_ips := "/bgp/v1/global/svc_loadbalancer_ips"}} {{- if exists $rr_cluster_id_key}}{{$rr_cluster_id := getv $rr_cluster_id_key}} {{- if and (not (eq $rr_cluster_id "")) (exists $lb_ips)}} @@ -62,14 +57,9 @@ function calico_export_to_bgp_peers(bool internal_peer) { {{- end}} {{- end}} {{- end}} -{{range ls "/v1/ipam/v6/pool"}}{{$data := json (getv (printf "/v1/ipam/v6/pool/%s" .))}} -{{- if $data.disableBGPExport}} - # Skip {{$data.cidr}} as BGP export is disabled for it -{{- else}} - if ( net ~ {{$data.cidr}} ) then { - accept; - } -{{- end}} + +{{- range $line := $config.BGPExportFilterForEnabledIPPools }} +{{ $line }} {{- end}} } @@ -85,13 +75,9 @@ filter calico_kernel_programming { {{- end}} {{- end}} -{{range ls "/v1/ipam/v6/pool"}}{{$data := json (getv (printf "/v1/ipam/v6/pool/%s" .))}} -{{- if $data.vxlan_mode}} - if ( net ~ {{$data.cidr}} ) then { - # Don't program VXLAN routes into the kernel - these are handled by Felix. - reject; - } -{{- end}}{{/* End of '$data.vxlan_mode' */}} -{{- end}}{{/* End of 'range ls...' */}} + +{{- range $line := $config.KernelFilterForIPPools }} +{{ $line }} +{{- end}} accept; {{- /* Destination is not in any ipPool, accept */}} } diff --git a/confd/etc/calico/confd/templates/bird_aggr.cfg.template b/confd/etc/calico/confd/templates/bird_aggr.cfg.template index 73862ee861e..8d44bd91007 100644 --- a/confd/etc/calico/confd/templates/bird_aggr.cfg.template +++ b/confd/etc/calico/confd/templates/bird_aggr.cfg.template @@ -1,7 +1,9 @@ # Generated by confd +{{- $config := getBGPConfig 4 . }} + {{$route_added := false}} protocol static { -{{- $block_key := printf "/ipam/v2/host/%s/ipv4/block" (getenv "NODENAME")}} +{{- $block_key := printf "/ipam/v2/host/%s/ipv4/block" $config.NodeName}} {{- if ls $block_key}} {{- $route_added = true}} # IP blocks for this host. @@ -37,7 +39,7 @@ protocol static { {{- end}} {{- end}} {{- end}} -{{- $node_peers_key := printf "/bgp/v1/host/%s/peer_v4" (getenv "NODENAME")}} +{{- $node_peers_key := printf "/bgp/v1/host/%s/peer_v4" $config.NodeName}} {{- if ls $node_peers_key}} {{- $run_once := true}} {{- range gets (printf "%s/*" $node_peers_key)}}{{$data := json .Value}} diff --git a/confd/etc/calico/confd/templates/bird_ipam.cfg.template b/confd/etc/calico/confd/templates/bird_ipam.cfg.template index 803c46e995f..97e58891f2b 100644 --- a/confd/etc/calico/confd/templates/bird_ipam.cfg.template +++ b/confd/etc/calico/confd/templates/bird_ipam.cfg.template @@ -1,12 +1,5 @@ # Generated by confd -function reject_disabled_pools () -{ -{{range ls "/v1/ipam/v4/pool"}}{{$data := json (getv (printf "/v1/ipam/v4/pool/%s" .))}} -{{- if $data.disableBGPExport}} - if ( net ~ {{$data.cidr}} ) then { reject; } -{{- end}} -{{- end}} -} +{{- $config := getBGPConfig 4 . }} function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. @@ -29,9 +22,11 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + +{{- range $line := $config.BGPExportFilterForDisabledIPPools }} +{{ $line }} +{{- end}} + if (internal_peer) then { reject_tunnel_routes(); } @@ -48,7 +43,7 @@ function calico_export_to_bgp_peers(bool internal_peer) { if ( net ~ {{$cidr}} ) then { accept; } {{- end}} {{- end}} -{{- $rr_cluster_id_key := printf "/bgp/v1/host/%s/rr_cluster_id" (getenv "NODENAME")}} +{{- $rr_cluster_id_key := printf "/bgp/v1/host/%s/rr_cluster_id" $config.NodeName}} {{- $lb_ips := "/bgp/v1/global/svc_loadbalancer_ips"}} {{- if exists $rr_cluster_id_key}}{{$rr_cluster_id := getv $rr_cluster_id_key}} {{- if and (not (eq $rr_cluster_id "")) (exists $lb_ips)}} @@ -62,18 +57,11 @@ function calico_export_to_bgp_peers(bool internal_peer) { {{- end}} {{- end}} {{- end}} -{{range ls "/v1/ipam/v4/pool"}}{{$data := json (getv (printf "/v1/ipam/v4/pool/%s" .))}} -{{- if $data.disableBGPExport}} - # Skip {{$data.cidr}} as BGP export is disabled for it -{{- else}} - if ( net ~ {{$data.cidr}} ) then { - accept; - } -{{- end}} +{{- range $line := $config.BGPExportFilterForEnabledIPPools }} +{{ $line }} {{- end}} } -{{$network_key := printf "/bgp/v1/host/%s/network_v4" (getenv "NODENAME")}} filter calico_kernel_programming { {{- $reject_key := "/rejectcidrs"}} {{- if ls $reject_key}} @@ -86,27 +74,9 @@ filter calico_kernel_programming { {{- end}} {{- end}} -{{- if exists $network_key}}{{$network := getv $network_key}} -{{range ls "/v1/ipam/v4/pool"}}{{$data := json (getv (printf "/v1/ipam/v4/pool/%s" .))}} - if ( net ~ {{$data.cidr}} ) then { -{{- if $data.vxlan_mode}} - # Don't program VXLAN routes into the kernel - these are handled by Felix. - reject; - } -{{- else if $data.ipip_mode}}{{if eq $data.ipip_mode "cross-subnet"}} - if defined(bgp_next_hop) && ( bgp_next_hop ~ {{$network}} ) then - krt_tunnel = ""; {{- /* Destination in ipPool, mode is cross sub-net, route from-host on subnet, do not use IPIP */}} - else - krt_tunnel = "{{$data.ipip}}"; {{- /* Destination in ipPool, mode is cross sub-net, route from-host off subnet, set the tunnel (if IPIP not enabled, value will be "") */}} - accept; - } {{- else}} - krt_tunnel = "{{$data.ipip}}"; {{- /* Destination in ipPool, mode not cross sub-net, set the tunnel (if IPIP not enabled, value will be "") */}} - accept; - } {{- end}} {{- else}} - krt_tunnel = "{{$data.ipip}}"; {{- /* Destination in ipPool, mode field is not present, set the tunnel (if IPIP not enabled, value will be "") */}} - accept; - } {{- end}} -{{end}} -{{- end}}{{/* End of 'exists $network_key' */}} + +{{- range $line := $config.KernelFilterForIPPools }} +{{ $line }} +{{- end}} accept; {{- /* Destination is not in any ipPool, accept */}} } diff --git a/confd/pkg/backends/calico/bgp_processor.go b/confd/pkg/backends/calico/bgp_processor.go new file mode 100644 index 00000000000..3022db45089 --- /dev/null +++ b/confd/pkg/backends/calico/bgp_processor.go @@ -0,0 +1,913 @@ +// Copyright (c) 2025-2026 Tigera, Inc. All rights reserved. +// +// 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 calico + +import ( + "encoding/json" + "fmt" + "os" + "slices" + "sort" + "strings" + "sync" + + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + log "github.com/sirupsen/logrus" + + "github.com/projectcalico/calico/confd/pkg/backends/types" + "github.com/projectcalico/calico/confd/pkg/resource/template" + "github.com/projectcalico/calico/libcalico-go/lib/backend/encap" + "github.com/projectcalico/calico/libcalico-go/lib/backend/model" +) + +// NodeName gets the node name from environment +var NodeName = os.Getenv("NODENAME") + +// BGP configuration cache +type bgpConfigCache struct { + config *types.BirdBGPConfig + revision uint64 +} + +// configCache is indexed by IP version (4 or 6) +var configCache map[int]*bgpConfigCache + +// configCacheMutex protects concurrent access to configCache +var configCacheMutex sync.RWMutex + +func init() { + configCache = make(map[int]*bgpConfigCache) +} + +// GetBirdBGPConfig processes raw datastore data into a clean BGP configuration structure +// ipVersion should be 4 for IPv4 or 6 for IPv6 +func (c *client) GetBirdBGPConfig(ipVersion int) (*types.BirdBGPConfig, error) { + logc := log.WithField("ipVersion", ipVersion) + currentRevision := c.GetCurrentRevision() + + configCacheMutex.RLock() + if cached, ok := configCache[ipVersion]; ok && cached.revision == currentRevision { + configCacheMutex.RUnlock() + logc.Debug("BGP config cache hit, returning cached configuration") + return cached.config, nil + } + configCacheMutex.RUnlock() + + logc.Debug("BGP config cache miss or expired, processing new configuration") + + config := &types.BirdBGPConfig{ + NodeName: NodeName, + Peers: make([]types.BirdBGPPeer, 0), + Filters: make(map[string]string), + Communities: make([]types.CommunityRule, 0), + } + + // Get basic node configuration + if err := c.populateNodeConfig(config, ipVersion); err != nil { + logc.WithError(err).Warn("Failed to populate node configuration") + return nil, err + } + logc.Debugf("Populated node configuration: node=%s, ip=%s, ipv6=%s, as=%s", config.NodeName, config.NodeIP, config.NodeIPv6, config.ASNumber) + + // Process all peer types + if err := c.processPeers(config, ipVersion); err != nil { + logc.WithError(err).Warn("Failed to process BGP peers") + return nil, err + } + logc.Debugf("Processed BGP peers: found %d peers", len(config.Peers)) + + // Sort peers by name for consistent output ordering + sort.Slice(config.Peers, func(i, j int) bool { + return config.Peers[i].Name < config.Peers[j].Name + }) + + // Process community rules + if err := c.processCommunityRules(config, ipVersion); err != nil { + logc.WithError(err).Warn("Failed to process community rules") + return nil, err + } + logc.Debugf("Processed community rules: found %d rules", len(config.Communities)) + + // Process ippools. + if err := c.processIPPools(config, ipVersion); err != nil { + logc.WithError(err).Warn("Failed to process ippools") + return nil, err + } + logc.WithFields(log.Fields{ + "numOfFiltersForProgrammingKernel": len(config.KernelFilterForIPPools), + "numOfRejectedFiltersForBGPExport": len(config.BGPExportFilterForDisabledIPPools), + "numOfAcceptedFiltersForBGPExport": len(config.BGPExportFilterForEnabledIPPools), + }).Debug("Processed ippools") + + // Update cache with write lock + configCacheMutex.Lock() + configCache[ipVersion] = &bgpConfigCache{ + config: config, + revision: currentRevision, + } + configCacheMutex.Unlock() + logc.Debug("Updated BGP config cache") + + return config, nil +} + +// populateNodeConfig fills in basic node configuration +func (c *client) populateNodeConfig(config *types.BirdBGPConfig, ipVersion int) error { + // Get node IPv4 address + nodeIPv4Key := fmt.Sprintf("/calico/bgp/v1/host/%s/ip_addr_v4", NodeName) + if nodeIP, err := c.GetValue(nodeIPv4Key); err == nil { + config.NodeIP = nodeIP + } else { + return fmt.Errorf("failed to get node IPv4 address from %s: %w", nodeIPv4Key, err) + } + + // Get node IPv6 address (optional - not all nodes have IPv6) + nodeIPv6Key := fmt.Sprintf("/calico/bgp/v1/host/%s/ip_addr_v6", NodeName) + if nodeIPv6, err := c.GetValue(nodeIPv6Key); err == nil { + config.NodeIPv6 = nodeIPv6 + } + + // Get AS number (try node-specific first, then global). Return error if both fail. + asNum, err := c.getNodeOrGlobalValue(NodeName, "as_num") + if err != nil { + return fmt.Errorf("failed to get AS number: %w", err) + } + config.ASNumber = asNum + + // Get logging configuration. If not found, logLevel will be empty string (uses default). + logLevel, err := c.getNodeOrGlobalValue(NodeName, "loglevel") + if err == nil { + config.LogLevel = logLevel + } + + // Compute debug mode based on log level. + switch logLevel { + case "none": + // DebugMode stays empty (no debug output) + case "debug": + config.DebugMode = "all" + default: + // Default behavior for empty string or any other log level + config.DebugMode = "{ states }" + } + + // Handle router ID logic + routerID := os.Getenv("CALICO_ROUTER_ID") + if routerID == "hash" { + // Use IP address generated by nodename's hash. + hashedID, err := template.HashToIPv4(config.NodeName) + if err != nil { + return fmt.Errorf("failed to hash node name to IPv4: %w", err) + } + config.RouterID = hashedID + } else if routerID != "" { + config.RouterID = routerID + } else { + // Default router ID to node's IPv4 address. We do this even in the BIRD config for + // IPv6 because router ID has to be 4 octets (even in MP-BGP). + config.RouterID = config.NodeIP + } + + // Process bind mode and listen address + bindMode, err := c.getNodeOrGlobalValue(NodeName, "bind_mode") + // Set listen address if bind mode is NodeIP and we have a node IP + if err == nil && bindMode == "NodeIP" { + if ipVersion == 6 && config.NodeIPv6 != "" { + config.ListenAddress = config.NodeIPv6 + } else if ipVersion == 4 && config.NodeIP != "" { + config.ListenAddress = config.NodeIP + } + } + + // Process listen port (node-specific takes precedence over global) + port, err := c.getNodeOrGlobalValue(NodeName, "listen_port") + if err == nil { + config.ListenPort = port + } + + // Process ignored interfaces and build complete interface string + ignoredInterfaces, err := c.getNodeOrGlobalValue(NodeName, "ignored_interfaces") + + // Build the complete interface pattern string + if err == nil && ignoredInterfaces != "" { + // Parse comma-separated list and build pattern + ifaceList := strings.Split(ignoredInterfaces, ",") + var patterns []string + for _, iface := range ifaceList { + patterns = append(patterns, fmt.Sprintf(`-"%s"`, iface)) + } + // Add standard exclusions and wildcard + patterns = append(patterns, `-"cali*"`, `-"kube-ipvs*"`, `"*"`) + config.DirectInterfaces = strings.Join(patterns, ", ") + } else { + // Default pattern with explanatory comment + config.DirectInterfaces = `-"cali*", -"kube-ipvs*", "*"` + } + + return nil +} + +// processPeers processes all BGP peers (mesh, global, and node-specific) +func (c *client) processPeers(config *types.BirdBGPConfig, ipVersion int) error { + // Get node's route reflector cluster ID + nodeClusterID, _ := c.GetValue(fmt.Sprintf("/calico/bgp/v1/host/%s/rr_cluster_id", NodeName)) + + // Process node-to-node mesh peers + if err := c.processMeshPeers(config, nodeClusterID, ipVersion); err != nil { + return fmt.Errorf("failed to process mesh peers: %w", err) + } + + // Process global peers (remote and local BGP peers) + if err := c.processGlobalPeers(config, nodeClusterID, ipVersion); err != nil { + return fmt.Errorf("failed to process global peers: %w", err) + } + + // Process node-specific peers (remote and local BGP peers) + if err := c.processNodePeers(config, nodeClusterID, ipVersion); err != nil { + return fmt.Errorf("failed to process node-specific peers: %w", err) + } + + return nil +} + +// processMeshPeers processes node-to-node mesh BGP peers +func (c *client) processMeshPeers(config *types.BirdBGPConfig, nodeClusterID string, ipVersion int) error { + logc := log.WithField("ipVersion", ipVersion) + + // If this node is a route reflector, skip mesh processing + if nodeClusterID != "" { + logc.Infof("Node %s is a route reflector with cluster ID %s, skipping mesh", NodeName, nodeClusterID) + return nil + } + + // Skip mesh processing if not enabled + meshConfigValue, err := c.GetValue("/calico/bgp/v1/global/node_mesh") + if err != nil { + return nil // No mesh configuration + } + + var meshConfig struct { + Enabled bool `json:"enabled"` + } + if err := json.Unmarshal([]byte(meshConfigValue), &meshConfig); err != nil { + logc.WithError(err).Debug("Failed to unmarshal mesh config") + return err + } + + logc.Debugf("Parsed mesh config: %+v", meshConfig) + + if !meshConfig.Enabled { + logc.Debug("Node-to-node mesh disabled") + return nil + } + + // Get global mesh settings + meshPassword, _ := c.GetValue("/calico/bgp/v1/global/node_mesh_password") + meshRestartTime, _ := c.GetValue("/calico/bgp/v1/global/node_mesh_restart_time") + + // Determine which IP address field to use + ipAddrSuffix := "ip_addr_v4" + if ipVersion == 6 { + ipAddrSuffix = "ip_addr_v6" + } + + // Get the current node's IP for this version + currentNodeIP := config.NodeIP + if ipVersion == 6 { + currentNodeIP = config.NodeIPv6 + } + + // Get all host IP addresses + hostIPsMap, err := c.GetValues([]string{"/calico/bgp/v1/host"}) + if err != nil { + return err + } + + // Extract unique hosts and their IPs from the keys + hostsMap := make(map[string]string) + for key, value := range hostIPsMap { + // Keys are like /calico/bgp/v1/host//ip_addr_v4 + // Only process keys that match the current IP version suffix + if strings.HasSuffix(key, ipAddrSuffix) { + parts := strings.Split(key, "/") + if len(parts) >= 6 { + hostsMap[parts[5]] = value + } + } + } + + for host, peerIP := range hostsMap { + if peerIP == "" { + continue + } + + // Skip ourselves + if peerIP == currentNodeIP { + continue + } + + // Check if peer is a route reflector + peerClusterIDKey := fmt.Sprintf("/calico/bgp/v1/host/%s/rr_cluster_id", host) + peerClusterID, _ := c.GetValue(peerClusterIDKey) + if peerClusterID != "" { + logc.Debugf("Skipping peer %s as it is a route reflector", peerIP) + continue + } + + // Get peer's AS number + peerASKey := fmt.Sprintf("/calico/bgp/v1/host/%s/as_num", host) + peerAS, err := c.GetValue(peerASKey) + if err != nil { + peerAS = config.ASNumber // Use global AS + } + + // Get peer's listen port + peerListenPortKey := fmt.Sprintf("/calico/bgp/v1/host/%s/listen_port", host) + peerListenPort, err := c.GetValue(peerListenPortKey) + if err != nil { + peerListenPort, _ = c.GetValue("/calico/bgp/v1/global/listen_port") + } + + // Create mesh peer name based on IP version + var peerName string + if ipVersion == 4 { + peerName = fmt.Sprintf("Mesh_%s", strings.ReplaceAll(peerIP, ".", "_")) + } else { + peerName = fmt.Sprintf("Mesh_%s", strings.ReplaceAll(peerIP, ":", "_")) + } + + // Mesh peers are iBGP (same AS), so pass true (peer has same AS) to calico_export_to_bgp_peers + exportFilter := "calico_export_to_bgp_peers(true);\n reject;" + + peer := types.BirdBGPPeer{ + Name: peerName, + IP: peerIP, + Port: peerListenPort, + ASNumber: peerAS, + Type: "mesh", + SourceAddr: currentNodeIP, + ImportFilter: "", // Empty means "import all;" in template + ExportFilter: exportFilter, + Password: meshPassword, + GracefulRestart: meshRestartTime, + } + + // Make mesh unidirectional to avoid race conditions + if peerIP > currentNodeIP { + peer.Passive = true + } + + config.Peers = append(config.Peers, peer) + } + + return nil +} + +// processGlobalPeers processes global BGP peers (remote and local) +func (c *client) processGlobalPeers(config *types.BirdBGPConfig, nodeClusterID string, ipVersion int) error { + peerPath := "/calico/bgp/v1/global/peer_v4" + if ipVersion == 6 { + peerPath = "/calico/bgp/v1/global/peer_v6" + } + return c.processPeersFromPath(peerPath, "Global", config, nodeClusterID, ipVersion) +} + +// processNodePeers processes node-specific BGP peers (both remote and local) +func (c *client) processNodePeers(config *types.BirdBGPConfig, nodeClusterID string, ipVersion int) error { + peerPath := fmt.Sprintf("/calico/bgp/v1/host/%s/peer_v4", NodeName) + if ipVersion == 6 { + peerPath = fmt.Sprintf("/calico/bgp/v1/host/%s/peer_v6", NodeName) + } + return c.processPeersFromPath(peerPath, "Node", config, nodeClusterID, ipVersion) +} + +// processPeersFromPath is a helper that processes both remote and local BGP peers from a given datastore path +func (c *client) processPeersFromPath(peerPath, peerType string, config *types.BirdBGPConfig, nodeClusterID string, ipVersion int) error { + logc := log.WithFields(map[string]any{ + "ipVersion": ipVersion, + "peerType": peerType, + "path": peerPath, + }) + + kvPairs, err := c.GetValues([]string{peerPath}) + if err != nil { + logc.WithError(err).Debug("No peers found or error retrieving them") + return nil + } + + logc.Debugf("Found %d peer entries", len(kvPairs)) + + // Unmarshal all peers once and separate into remote and local + var remotePeers, localPeers []bgpPeer + for key, value := range kvPairs { + var peerData bgpPeer + if err := json.Unmarshal([]byte(value), &peerData); err != nil { + logc.WithError(err).Warnf("Failed to unmarshal peer data for key %s", key) + continue + } + + if peerData.LocalBGPPeer { + localPeers = append(localPeers, peerData) + } else { + remotePeers = append(remotePeers, peerData) + } + } + + // Process remote peers first + for _, peerData := range remotePeers { + peer := c.buildPeerFromData(&peerData, peerType, config, nodeClusterID, ipVersion) + if peer != nil { + config.Peers = append(config.Peers, *peer) + logc.Debugf("Added %s peer: %s", peerType, peer.Name) + } + } + + // Then process local BGP peers + for _, peerData := range localPeers { + peer := c.buildPeerFromData(&peerData, "Local_Workload", config, nodeClusterID, ipVersion) + if peer != nil { + config.Peers = append(config.Peers, *peer) + logc.Debugf("Added Local_Workload peer: %s", peer.Name) + } + } + + return nil +} + +// buildPeerFromData constructs a BirdBGPPeer from bgpPeer data +func (c *client) buildPeerFromData(peer *bgpPeer, prefix string, config *types.BirdBGPConfig, nodeClusterID string, ipVersion int) *types.BirdBGPPeer { + logc := log.WithField("ipVersion", ipVersion) + + peerIP := peer.PeerIP.String() + if peerIP == "" || peerIP == "" { + logc.Debugf("buildPeerFromData: no IP found in peer data, peerIP=%s", peerIP) + return nil + } + + // Skip ourselves - check appropriate IP based on version + currentNodeIP := config.NodeIP + if ipVersion == 6 { + currentNodeIP = config.NodeIPv6 + } + if peerIP == currentNodeIP { + logc.Debugf("buildPeerFromData: skipping ourselves (peerIP=%s, currentNodeIP=%s)", peerIP, currentNodeIP) + return nil + } + + logc.Debugf("buildPeerFromData: building peer for IP=%s, prefix=%s", peerIP, prefix) + + // Generate peer name based on IP version + var peerName string + if ipVersion == 4 { + peerName = fmt.Sprintf("%s_%s", prefix, strings.ReplaceAll(peerIP, ".", "_")) + } else { + peerName = fmt.Sprintf("%s_%s", prefix, strings.ReplaceAll(peerIP, ":", "_")) + } + if peer.Port > 0 { + peerName = fmt.Sprintf("%s_port_%d", peerName, peer.Port) + } + + result := &types.BirdBGPPeer{ + Name: peerName, + IP: peerIP, + Type: strings.ToLower(prefix), + } + + // Basic fields + if peer.Port > 0 { + result.Port = fmt.Sprintf("%d", peer.Port) + } + result.ASNumber = peer.ASNum.String() + if peer.LocalASNum != 0 { + result.LocalASNumber = peer.LocalASNum.String() + } + + // TTL security + if peer.TTLSecurity > 0 { + result.TTLSecurity = fmt.Sprintf("on;\n multihop %d", peer.TTLSecurity) + } else { + result.TTLSecurity = "off" + } + + // Source address - use appropriate node IP based on version + if peer.SourceAddr == "UseNodeIP" { + result.SourceAddr = currentNodeIP + } + + // Filters - build inline filter blocks + result.ImportFilter = c.buildImportFilter(peer.Filters, ipVersion) + // Use effective node AS number (local_as_num if set, otherwise node AS) + effectiveNodeAS := config.ASNumber + if result.LocalASNumber != "" { + effectiveNodeAS = result.LocalASNumber + } + result.ExportFilter = c.buildExportFilter(peer.Filters, result.ASNumber, effectiveNodeAS, ipVersion) + + // Optional fields + if peer.Password != nil { + result.Password = *peer.Password + } + if peer.RestartTime != "" { + result.GracefulRestart = peer.RestartTime + } + if peer.KeepaliveTime != "" { + result.KeepaliveTime = peer.KeepaliveTime + } + result.Passive = peer.PassiveMode + if peer.NumAllowLocalAS > 0 { + result.NumAllowLocalAs = fmt.Sprintf("%d", peer.NumAllowLocalAS) + } + + // Next hop mode + switch peer.NextHopMode { + case "Self": + result.NextHopSelf = true + case "Keep": + result.NextHopKeep = true + } + // Legacy keep_next_hop field - only apply for eBGP peers + if peer.KeepNextHop && result.ASNumber != effectiveNodeAS { + result.NextHopKeep = true + } + + // Route reflector handling + // If this node is a route reflector (has a cluster ID) and the peer is iBGP + // and the peer does not have a cluster ID (or has a different one), + // then the peer is a route reflector client. + if result.ASNumber == effectiveNodeAS && nodeClusterID != "" { + if peer.RRClusterID == "" || peer.RRClusterID != nodeClusterID { + result.RouteReflector = true + result.RRClusterID = nodeClusterID + } + } + + // Passive mode handling + // If the peer is a mesh, global, or local workload peer and passive is not set explicitly, + // set passive to true if the peer IP is lexically greater than the current node IP. + if (result.Type == "mesh" || result.Type == "global" || result.Type == "local_workload") && !result.Passive { + if peer.CalicoNode && peerIP > currentNodeIP { + result.Passive = true + } + } + + return result +} + +// truncateBGPFilterName truncates a BGP filter name to fit BIRD's symbol length limit +// Uses the same truncation logic as template_funcs.go to ensure filter function +// definitions and calls use identical names. +// BIRD has a 64 character limit for symbols. The format is 'bgp__importFilterV4' +// Prefix 'bgp_' = 4 chars, Suffix '_importFilterV4' or '_exportFilterV4' = 15 chars +// Total overhead = 4 + 15 = 19 chars, Available for name = 64 - 19 = 45 chars +func truncateBGPFilterName(name string) string { + const maxBIRDSymLen = 64 + // Calculate max length for the filter name part + // Format: 'bgp__importFilterV4' or 'bgp__exportFilterV4' + prefixAndSuffix := "bgp__importFilterV4" // 19 chars (same for export) + maxNameLength := maxBIRDSymLen - len(prefixAndSuffix) + + // Use the shared truncation function from template package + truncated, err := template.TruncateAndHashName(name, maxNameLength) + if err != nil { + // If truncation fails, return original name (shouldn't happen in practice) + log.WithError(err).Warnf("Failed to truncate filter name %s, using original", name) + return name + } + return truncated +} + +// buildImportFilter builds the import filter block +func (c *client) buildImportFilter(filters []string, ipVersion int) string { + var filterLines []string + + // Determine filter suffix based on IP version + filterSuffix := "V4" + if ipVersion == 6 { + filterSuffix = "V6" + } + + // Process BGP filters + for _, filterName := range filters { + filterKey := fmt.Sprintf("/calico/resources/v3/projectcalico.org/bgpfilters/%s", filterName) + if filterValue, err := c.GetValue(filterKey); err == nil { + var filter v3.BGPFilter + if json.Unmarshal([]byte(filterValue), &filter) == nil { + // Check if import rules exist based on IP version + if (ipVersion == 4 && len(filter.Spec.ImportV4) > 0) || (ipVersion == 6 && len(filter.Spec.ImportV6) > 0) { + truncatedName := truncateBGPFilterName(filterName) + filterLines = append(filterLines, fmt.Sprintf("'bgp_%s_importFilter%s'();", truncatedName, filterSuffix)) + } + } + } + } + + filterLines = append(filterLines, "accept; # Prior to introduction of BGP Filters we used \"import all\" so use default accept behaviour on import") + return strings.Join(filterLines, "\n ") +} + +// buildExportFilter builds the export filter block +func (c *client) buildExportFilter(filters []string, peerAS, nodeAS string, ipVersion int) string { + var filterLines []string + + // Determine filter suffix based on IP version + filterSuffix := "V4" + if ipVersion == 6 { + filterSuffix = "V6" + } + + // Process BGP filters + for _, filterName := range filters { + filterKey := fmt.Sprintf("/calico/resources/v3/projectcalico.org/bgpfilters/%s", filterName) + if filterValue, err := c.GetValue(filterKey); err == nil { + var filter v3.BGPFilter + if json.Unmarshal([]byte(filterValue), &filter) == nil { + // Check if export rules exist based on IP version + if (ipVersion == 4 && len(filter.Spec.ExportV4) > 0) || (ipVersion == 6 && len(filter.Spec.ExportV6) > 0) { + truncatedName := truncateBGPFilterName(filterName) + filterLines = append(filterLines, fmt.Sprintf("'bgp_%s_exportFilter%s'();", truncatedName, filterSuffix)) + } + } + } + } + + // Call calico_export_to_bgp_peers + sameAS := peerAS == nodeAS + filterLines = append(filterLines, fmt.Sprintf("calico_export_to_bgp_peers(%v);", sameAS)) + filterLines = append(filterLines, "reject;") + + return strings.Join(filterLines, "\n ") +} + +// getNodeOrGlobalValue attempts to get a value from a node-specific key first, +// then falls back to the global key. Returns the value and any error. +func (c *client) getNodeOrGlobalValue(nodeName, keySuffix string) (string, error) { + nodeKey := fmt.Sprintf("/calico/bgp/v1/host/%s/%s", nodeName, keySuffix) + if val, err := c.GetValue(nodeKey); err == nil { + return val, nil + } + globalKey := fmt.Sprintf("/calico/bgp/v1/global/%s", keySuffix) + return c.GetValue(globalKey) +} + +// processCommunityRules processes BGP community advertisements +func (c *client) processCommunityRules(config *types.BirdBGPConfig, ipVersion int) error { + logc := log.WithField("ipVersion", ipVersion) + + // Determine path suffix based on IP version + ipSuffix := "ip_v4" + if ipVersion == 6 { + ipSuffix = "ip_v6" + } + + // Try node-specific first, then fall back to global + nodeKey := fmt.Sprintf("/calico/bgp/v1/host/%s/prefix_advertisements/%s", NodeName, ipSuffix) + globalKey := fmt.Sprintf("/calico/bgp/v1/global/prefix_advertisements/%s", ipSuffix) + + var communitiesKey string + if _, err := c.GetValue(nodeKey); err == nil { + communitiesKey = nodeKey + } else if _, err := c.GetValue(globalKey); err == nil { + communitiesKey = globalKey + } + + if communitiesKey == "" { + return nil + } + + kvPairs, err := c.GetValues([]string{communitiesKey}) + if err != nil { + return err + } + + for _, value := range kvPairs { + var advertisements []v3.PrefixAdvertisement + if err := json.Unmarshal([]byte(value), &advertisements); err != nil { + logc.WithError(err).Warn("Failed to parse community advertisements") + continue + } + + for _, adv := range advertisements { + // Skip advertisements without a CIDR + if adv.CIDR == "" { + continue + } + + rule := types.CommunityRule{ + CIDR: adv.CIDR, + AddStatements: make([]string, 0, len(adv.Communities)), + } + + // Pre-format BIRD community add statements + for _, commStr := range adv.Communities { + parts := strings.Split(commStr, ":") + if len(parts) == 2 { + // Standard community + rule.AddStatements = append(rule.AddStatements, + fmt.Sprintf("bgp_community.add((%s, %s));", parts[0], parts[1])) + } else if len(parts) == 3 { + // Large community + rule.AddStatements = append(rule.AddStatements, + fmt.Sprintf("bgp_large_community.add((%s, %s, %s));", parts[0], parts[1], parts[2])) + } + } + + if len(rule.AddStatements) > 0 { + config.Communities = append(config.Communities, rule) + } + } + } + + return nil +} + +func (c *client) processIPPools(config *types.BirdBGPConfig, ipVersion int) error { + poolKey := fmt.Sprintf("/calico/v1/ipam/v%d/pool", ipVersion) + logCtx := log.WithFields(map[string]any{ + "ipVersion": ipVersion, + "path": poolKey, + }) + + kvPairs, err := c.GetValues([]string{poolKey}) + if err != nil { + logCtx.WithError(err).Debug("No ippool found or error retrieving them") + return nil + } + + // In IPv6, we only need to include statements with "reject" action, since we include a default "accept" at the end. + var filterActionForKernel string + if ipVersion == 6 { + filterActionForKernel = "reject" + } + + localSubnet, localSubnetErr := c.localSubnet(ipVersion) + if localSubnetErr != nil { + logCtx.WithError(localSubnetErr).Debug("Failed to get local host subnet") + } + + for key, value := range kvPairs { + var ippool model.IPPool + if err := json.Unmarshal([]byte(value), &ippool); err != nil { + logCtx.WithError(err).Warnf("Failed to unmarshal ippool data for key %s", key) + continue + } + + // Generate statements for rejecting disabled ippools in the filter for exporting routes to other peers. + statement := c.processIPPool(&ippool, false, "reject", "", ipVersion) + if len(statement) != 0 { + config.BGPExportFilterForDisabledIPPools = append(config.BGPExportFilterForDisabledIPPools, statement) + } + + // Generate statements for accepting enabled ippools in the filter for exporting routes to other peers. + statement = c.processIPPool(&ippool, false, "accept", "", ipVersion) + if len(statement) != 0 { + config.BGPExportFilterForEnabledIPPools = append(config.BGPExportFilterForEnabledIPPools, statement) + } + + if ipVersion == 6 || ipVersion == 4 && localSubnetErr == nil { + // Generate statements for kernel programming filter. + statement = c.processIPPool(&ippool, true, filterActionForKernel, localSubnet, ipVersion) + if len(statement) != 0 { + config.KernelFilterForIPPools = append(config.KernelFilterForIPPools, statement) + } + } + } + + // Sort statements. + slices.Sort(config.KernelFilterForIPPools) + slices.Sort(config.BGPExportFilterForDisabledIPPools) + slices.Sort(config.BGPExportFilterForEnabledIPPools) + + return nil +} + +// This function generates BIRD statements for an IPPool to be used as BIRD filters based on the following input: +// - ippool: IPPool resource. +// - forProgrammingKernel: Whether the generated statements are intended for programming routes to kernel or exporting to +// other BGP Peers. As an example, we need to set "krt_tunnel" for programming IPIP and no-encap IPv4 routes. +// - filterAction: specified action to filter generated statements. For exporting pools to BGP peers, we need to +// first reject disabled ippools, and then accept the rest at the end after all other filters. Allowed values are +// "accept", "reject", and "" (no filtering). +// - localSubnet: the subnet of local node, which is needed by IPv4 IPIP pool in cross subnet mode. +// - version: the statement ip family. +// +// As an example, For the following sample IPPool resource: +// +// apiVersion: projectcalico.org/v3 +// kind: IPPool +// metadata: +// +// name: my.ippool-1 +// +// spec: +// +// cidr: 10.1.0.0/16 +// ipipMode: Always +// +// this function generates the following statement for programming routes to kernel: +// +// if (net ~ 10.10.0.0/16) then { krt_tunnel="tunl0"; accept; } +// +// and the following statement for exporting to BGP peers: +// +// if (net ~ 10.10.0.0/16) then { accept; } +func (c *client) processIPPool( + ippool *model.IPPool, + forProgrammingKernel bool, + filterAction string, + localSubnet string, + ipVersion int, +) string { + cidr := ippool.CIDR.String() + var action, comment, extraStatement string + switch { + case ippool.DisableBGPExport && !forProgrammingKernel: + // IPPool's BGP export is disabled, and filter is for exporting to other peers. + action = "reject" + comment = "BGP export is disabled." + case ippool.VXLANMode == encap.Always || ippool.VXLANMode == encap.CrossSubnet: + // VXLAN encapsulation is always handled by Felix. + if forProgrammingKernel { + // Felix always handles programming VXLAN IPPools. + action = "reject" + comment = "VXLAN routes are handled by Felix." + } else { + action = "accept" + } + case ippool.IPIPMode == encap.Always || ippool.IPIPMode == encap.CrossSubnet, // IPIP Encapsulation. + ippool.IPIPMode == encap.Never || ippool.VXLANMode == encap.Never: // No-encapsulation. + // IPIP encapsulation or No-Encap. + if forProgrammingKernel && ipVersion == 4 { + // For IPv4 IPIP and no-encap routes, we need to set `krt_tunnel` variable which is needed by + // our fork of BIRD. + extraStatement = extraStatementForKernelProgrammingIPIPNoEncap(ippool.IPIPMode, localSubnet) + } + action = "accept" + default: + log.WithFields(log.Fields{ + "ippool": ippool.CIDR, + "ipVersion": ipVersion, + }).Error("Invalid ippool") + return "" + } + + // Filter statements based on provided filterAction. + if len(filterAction) != 0 && filterAction != action { + return "" + } + return emitFilterStatementForIPPools(cidr, extraStatement, action, comment) +} + +func (c *client) localSubnet(ipVersion int) (string, error) { + key := fmt.Sprintf("/calico/bgp/v1/host/%s/network_v%d", NodeName, ipVersion) + subnet, err := c.GetValue(key) + if err != nil { + return "", fmt.Errorf("failed to get local host subnet: %w", err) + } + return subnet, nil +} + +func extraStatementForKernelProgrammingIPIPNoEncap(ipipMode encap.Mode, localSubnet string) string { + switch v3.EncapMode(ipipMode) { + case v3.Always: + return `krt_tunnel="tunl0";` + case v3.CrossSubnet: + format := `if (defined(bgp_next_hop)&&(bgp_next_hop ~ %s)) then krt_tunnel=""; else krt_tunnel="tunl0";` + return fmt.Sprintf(format, localSubnet) + case v3.Never: + // No-encap case. + return `krt_tunnel="";` + default: + return `` + } +} + +func emitFilterStatementForIPPools(cidr, extraStatement, action, comment string) (statement string) { + // Check mandatory inputs. + if len(cidr) == 0 || len(action) == 0 { + return + } + if len(extraStatement) != 0 { + statement = fmt.Sprintf(" if (net ~ %s) then { %s %s; }", cidr, extraStatement, action) + } else { + statement = fmt.Sprintf(" if (net ~ %s) then { %s; }", cidr, action) + } + if len(comment) != 0 { + statement = fmt.Sprintf("%s %s", statement, formatComment(comment)) + } + return +} + +func formatComment(comment string) string { + return fmt.Sprintf("# %s", comment) +} diff --git a/confd/pkg/backends/calico/bgp_processor_test.go b/confd/pkg/backends/calico/bgp_processor_test.go new file mode 100644 index 00000000000..b89557a2e82 --- /dev/null +++ b/confd/pkg/backends/calico/bgp_processor_test.go @@ -0,0 +1,2320 @@ +// Copyright (c) 2025-2026 Tigera, Inc. All rights reserved. +// +// 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 calico + +import ( + "context" + "encoding/json" + "fmt" + "os" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/projectcalico/calico/confd/pkg/backends/types" + "github.com/projectcalico/calico/confd/pkg/resource/template" +) + +// newTestClient creates a client suitable for testing peer processing functions. +// It initializes the cache and peeringCache with the provided data and ensures +// waitForSync won't block. +func newTestClient(cache, peeringCache map[string]string) *client { + c := &client{ + cache: cache, + peeringCache: peeringCache, + } + // Ensure waitForSync doesn't block - it's a zero-value WaitGroup which is already "done" + return c +} + +func TestBuildImportFilter_DefaultAccept(t *testing.T) { + c := &client{} + + // Test with no filter - should return default accept + result := c.buildImportFilter(nil, 4) + assert.Contains(t, result, "accept;") + assert.Contains(t, result, "# Prior to introduction of BGP Filters") +} + +func TestBuildImportFilter_EmptyFilters(t *testing.T) { + c := &client{} + + // Test with empty filters array - should return default accept + result := c.buildImportFilter([]string{}, 4) + assert.Contains(t, result, "accept;") +} + +func TestBuildImportFilter_IPv4vsIPv6(t *testing.T) { + c := &client{} + + // Test that IPv4 and IPv6 return appropriate default + resultV4 := c.buildImportFilter(nil, 4) + resultV6 := c.buildImportFilter(nil, 6) + + // Both should have accept by default + assert.Contains(t, resultV4, "accept;") + assert.Contains(t, resultV6, "accept;") +} + +func TestBuildExportFilter_SameAS(t *testing.T) { + c := &client{} + + // Same AS should result in reject (via calico_export_to_bgp_peers(true)) + result := c.buildExportFilter(nil, "64512", "64512", 4) + assert.Contains(t, result, "calico_export_to_bgp_peers(true)") + assert.Contains(t, result, "reject;") +} + +func TestBuildExportFilter_DifferentAS_NoFilter(t *testing.T) { + c := &client{} + + // Different AS with no filter should use default export filter + result := c.buildExportFilter(nil, "65000", "64512", 4) + assert.Contains(t, result, "calico_export_to_bgp_peers(false)") +} + +func TestBuildExportFilter_IPv4vsIPv6(t *testing.T) { + c := &client{} + + // Test that both IPv4 and IPv6 work + resultV4 := c.buildExportFilter(nil, "65000", "64512", 4) + resultV6 := c.buildExportFilter(nil, "65000", "64512", 6) + + // Both should have export filter + assert.Contains(t, resultV4, "calico_export_to_bgp_peers") + assert.Contains(t, resultV6, "calico_export_to_bgp_peers") +} + +func TestBuildExportFilter_EmptyFilters(t *testing.T) { + c := &client{} + + // Test with empty filters array + result := c.buildExportFilter([]string{}, "65000", "64512", 4) + assert.Contains(t, result, "calico_export_to_bgp_peers") +} + +func TestRouterIDGeneration_Hash(t *testing.T) { + NodeName = "test-node-hash" + require.NoError(t, os.Setenv("CALICO_ROUTER_ID", "hash")) + defer func() { _ = os.Unsetenv("CALICO_ROUTER_ID") }() + + config := &types.BirdBGPConfig{ + NodeName: NodeName, + NodeIP: "10.0.0.2", + } + + // Test IPv4 - no comment + routerID := os.Getenv("CALICO_ROUTER_ID") + if routerID == "hash" { + hashedID, err := template.HashToIPv4(config.NodeName) + require.NoError(t, err) + config.RouterID = hashedID + } + + assert.NotEmpty(t, config.RouterID) + assert.NotEqual(t, "10.0.0.2", config.RouterID) + assert.Regexp(t, `^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$`, config.RouterID) +} + +func TestRouterIDGeneration_Explicit(t *testing.T) { + require.NoError(t, os.Setenv("CALICO_ROUTER_ID", "192.168.1.1")) + defer func() { _ = os.Unsetenv("CALICO_ROUTER_ID") }() + + config := &types.BirdBGPConfig{ + NodeIP: "10.0.0.1", + } + + routerID := os.Getenv("CALICO_ROUTER_ID") + if routerID != "" && routerID != "hash" { + config.RouterID = routerID + } + + assert.Equal(t, "192.168.1.1", config.RouterID) +} + +func TestRouterIDGeneration_FromNodeIP(t *testing.T) { + _ = os.Unsetenv("CALICO_ROUTER_ID") + + config := &types.BirdBGPConfig{ + NodeIP: "10.0.0.1", + } + + routerID := os.Getenv("CALICO_ROUTER_ID") + if routerID == "" && config.NodeIP != "" { + config.RouterID = config.NodeIP + } + + assert.Equal(t, "10.0.0.1", config.RouterID) +} + +func TestTTLSecurityFormatting(t *testing.T) { + tests := []struct { + name string + enabled bool + hops float64 + expected string + }{ + {name: "TTL security off", enabled: false, expected: "off;\n multihop"}, + {name: "TTL security on with 1 hop", enabled: true, hops: 1, expected: "on;\n multihop 1"}, + {name: "TTL security on with 64 hops", enabled: true, hops: 64, expected: "on;\n multihop 64"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var result string + if tt.enabled { + result = fmt.Sprintf("on;\n multihop %.0f", tt.hops) + } else { + result = "off;\n multihop" + } + + assert.Equal(t, tt.expected, result) + }) + } +} + +// ============================================================================= +// populateNodeConfig Tests +// ============================================================================= + +func TestPopulateNodeConfig_BasicIPv4(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + // Clear CALICO_ROUTER_ID env var for this test + _ = os.Unsetenv("CALICO_ROUTER_ID") + + cache := map[string]string{ + "/calico/bgp/v1/host/test-node/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/global/as_num": "64512", + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{ + NodeName: NodeName, + } + + err := c.populateNodeConfig(config, 4) + require.NoError(t, err) + + assert.Equal(t, "10.0.0.1", config.NodeIP) + assert.Equal(t, "10.0.0.1", config.RouterID) // Default router ID is IPv4 + assert.Equal(t, "64512", config.ASNumber) +} + +func TestPopulateNodeConfig_BasicIPv6(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + _ = os.Unsetenv("CALICO_ROUTER_ID") + + cache := map[string]string{ + "/calico/bgp/v1/host/test-node/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/host/test-node/ip_addr_v6": "fd00::1", + "/calico/bgp/v1/global/as_num": "64512", + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{ + NodeName: NodeName, + } + + err := c.populateNodeConfig(config, 6) + require.NoError(t, err) + + assert.Equal(t, "10.0.0.1", config.NodeIP) + assert.Equal(t, "fd00::1", config.NodeIPv6) + assert.Equal(t, "10.0.0.1", config.RouterID) // Router ID is still IPv4 + assert.Equal(t, "64512", config.ASNumber) + // IPv6 should have a comment explaining router ID is IPv4 +} + +func TestPopulateNodeConfig_NodeSpecificAS(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + _ = os.Unsetenv("CALICO_ROUTER_ID") + + cache := map[string]string{ + "/calico/bgp/v1/host/test-node/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/host/test-node/as_num": "65001", // Node-specific AS + "/calico/bgp/v1/global/as_num": "64512", // Global AS (should be ignored) + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{ + NodeName: NodeName, + } + + err := c.populateNodeConfig(config, 4) + require.NoError(t, err) + + // Node-specific AS should take precedence + assert.Equal(t, "65001", config.ASNumber) +} + +func TestPopulateNodeConfig_RouterIDHash(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node-hash" + defer func() { NodeName = originalNodeName }() + + require.NoError(t, os.Setenv("CALICO_ROUTER_ID", "hash")) + defer func() { _ = os.Unsetenv("CALICO_ROUTER_ID") }() + + cache := map[string]string{ + "/calico/bgp/v1/host/test-node-hash/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/global/as_num": "64512", + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{ + NodeName: NodeName, + } + + err := c.populateNodeConfig(config, 4) + require.NoError(t, err) + + // Router ID should be hash-generated, not the node IP + assert.NotEqual(t, "10.0.0.1", config.RouterID) + assert.Regexp(t, `^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$`, config.RouterID) +} + +func TestPopulateNodeConfig_RouterIDHashIPv6(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node-hash" + defer func() { NodeName = originalNodeName }() + + require.NoError(t, os.Setenv("CALICO_ROUTER_ID", "hash")) + defer func() { _ = os.Unsetenv("CALICO_ROUTER_ID") }() + + cache := map[string]string{ + "/calico/bgp/v1/host/test-node-hash/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/host/test-node-hash/ip_addr_v6": "fd00::1", + "/calico/bgp/v1/global/as_num": "64512", + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{ + NodeName: NodeName, + } + + err := c.populateNodeConfig(config, 6) + require.NoError(t, err) + + // Router ID should be hash-generated + assert.NotEqual(t, "10.0.0.1", config.RouterID) +} + +func TestPopulateNodeConfig_ExplicitRouterID(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + require.NoError(t, os.Setenv("CALICO_ROUTER_ID", "192.168.1.1")) + defer func() { _ = os.Unsetenv("CALICO_ROUTER_ID") }() + + cache := map[string]string{ + "/calico/bgp/v1/host/test-node/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/global/as_num": "64512", + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{ + NodeName: NodeName, + } + + err := c.populateNodeConfig(config, 4) + require.NoError(t, err) + + // Router ID should be the explicit value + assert.Equal(t, "192.168.1.1", config.RouterID) +} + +func TestPopulateNodeConfig_LogLevelDebug(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + _ = os.Unsetenv("CALICO_ROUTER_ID") + + cache := map[string]string{ + "/calico/bgp/v1/host/test-node/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/global/loglevel": "debug", + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{ + NodeName: NodeName, + } + + err := c.populateNodeConfig(config, 4) + require.NoError(t, err) + + assert.Equal(t, "debug", config.LogLevel) + assert.Equal(t, "all", config.DebugMode) +} + +func TestPopulateNodeConfig_LogLevelInfo(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + _ = os.Unsetenv("CALICO_ROUTER_ID") + + cache := map[string]string{ + "/calico/bgp/v1/host/test-node/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/global/loglevel": "info", + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{ + NodeName: NodeName, + } + + err := c.populateNodeConfig(config, 4) + require.NoError(t, err) + + assert.Equal(t, "info", config.LogLevel) + assert.Equal(t, "{ states }", config.DebugMode) +} + +func TestPopulateNodeConfig_LogLevelNone(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + _ = os.Unsetenv("CALICO_ROUTER_ID") + + cache := map[string]string{ + "/calico/bgp/v1/host/test-node/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/global/loglevel": "none", + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{ + NodeName: NodeName, + } + + err := c.populateNodeConfig(config, 4) + require.NoError(t, err) + + assert.Equal(t, "none", config.LogLevel) + assert.Empty(t, config.DebugMode) // No debug output for "none" +} + +func TestPopulateNodeConfig_NodeSpecificLogLevel(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + _ = os.Unsetenv("CALICO_ROUTER_ID") + + cache := map[string]string{ + "/calico/bgp/v1/host/test-node/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/global/loglevel": "info", // Global + "/calico/bgp/v1/host/test-node/loglevel": "debug", // Node-specific (should win) + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{ + NodeName: NodeName, + } + + err := c.populateNodeConfig(config, 4) + require.NoError(t, err) + + assert.Equal(t, "debug", config.LogLevel) + assert.Equal(t, "all", config.DebugMode) +} + +func TestPopulateNodeConfig_BindModeNodeIP_IPv4(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + _ = os.Unsetenv("CALICO_ROUTER_ID") + + cache := map[string]string{ + "/calico/bgp/v1/host/test-node/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/global/bind_mode": "NodeIP", + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{ + NodeName: NodeName, + } + + err := c.populateNodeConfig(config, 4) + require.NoError(t, err) + + assert.Equal(t, "10.0.0.1", config.ListenAddress) +} + +func TestPopulateNodeConfig_BindModeNodeIP_IPv6(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + _ = os.Unsetenv("CALICO_ROUTER_ID") + + cache := map[string]string{ + "/calico/bgp/v1/host/test-node/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/host/test-node/ip_addr_v6": "fd00::1", + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/global/bind_mode": "NodeIP", + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{ + NodeName: NodeName, + } + + err := c.populateNodeConfig(config, 6) + require.NoError(t, err) + + // IPv6 should use IPv6 address for listen + assert.Equal(t, "fd00::1", config.ListenAddress) +} + +func TestPopulateNodeConfig_ListenPort(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + _ = os.Unsetenv("CALICO_ROUTER_ID") + + cache := map[string]string{ + "/calico/bgp/v1/host/test-node/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/global/listen_port": "1790", + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{ + NodeName: NodeName, + } + + err := c.populateNodeConfig(config, 4) + require.NoError(t, err) + + assert.Equal(t, "1790", config.ListenPort) +} + +func TestPopulateNodeConfig_NodeSpecificListenPort(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + _ = os.Unsetenv("CALICO_ROUTER_ID") + + cache := map[string]string{ + "/calico/bgp/v1/host/test-node/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/global/listen_port": "1790", // Global + "/calico/bgp/v1/host/test-node/listen_port": "1791", // Node-specific (should win) + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{ + NodeName: NodeName, + } + + err := c.populateNodeConfig(config, 4) + require.NoError(t, err) + + assert.Equal(t, "1791", config.ListenPort) +} + +func TestPopulateNodeConfig_IgnoredInterfaces_Default(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + _ = os.Unsetenv("CALICO_ROUTER_ID") + + cache := map[string]string{ + "/calico/bgp/v1/host/test-node/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/global/as_num": "64512", + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{ + NodeName: NodeName, + } + + err := c.populateNodeConfig(config, 4) + require.NoError(t, err) + + // Default pattern + assert.Equal(t, `-"cali*", -"kube-ipvs*", "*"`, config.DirectInterfaces) +} + +func TestPopulateNodeConfig_IgnoredInterfaces_Custom(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + _ = os.Unsetenv("CALICO_ROUTER_ID") + + cache := map[string]string{ + "/calico/bgp/v1/host/test-node/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/global/ignored_interfaces": "eth0,docker*", + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{ + NodeName: NodeName, + } + + err := c.populateNodeConfig(config, 4) + require.NoError(t, err) + + // Custom interfaces plus standard exclusions + assert.Contains(t, config.DirectInterfaces, `-"eth0"`) + assert.Contains(t, config.DirectInterfaces, `-"docker*"`) + assert.Contains(t, config.DirectInterfaces, `-"cali*"`) + assert.Contains(t, config.DirectInterfaces, `-"kube-ipvs*"`) + assert.Contains(t, config.DirectInterfaces, `"*"`) +} + +func TestPopulateNodeConfig_NodeSpecificIgnoredInterfaces(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + _ = os.Unsetenv("CALICO_ROUTER_ID") + + cache := map[string]string{ + "/calico/bgp/v1/host/test-node/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/global/ignored_interfaces": "global-if", // Global + "/calico/bgp/v1/host/test-node/ignored_interfaces": "node-if", // Node-specific (should win) + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{ + NodeName: NodeName, + } + + err := c.populateNodeConfig(config, 4) + require.NoError(t, err) + + // Node-specific interface should be present + assert.Contains(t, config.DirectInterfaces, `-"node-if"`) + // Global interface should NOT be present + assert.NotContains(t, config.DirectInterfaces, `-"global-if"`) +} + +func TestPopulateNodeConfig_NodeSpecificBindMode(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + _ = os.Unsetenv("CALICO_ROUTER_ID") + + cache := map[string]string{ + "/calico/bgp/v1/host/test-node/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/host/test-node/bind_mode": "NodeIP", // Node-specific + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{ + NodeName: NodeName, + } + + err := c.populateNodeConfig(config, 4) + require.NoError(t, err) + + assert.Equal(t, "10.0.0.1", config.ListenAddress) +} + +func TestPopulateNodeConfig_NoBindMode(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + _ = os.Unsetenv("CALICO_ROUTER_ID") + + cache := map[string]string{ + "/calico/bgp/v1/host/test-node/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/global/as_num": "64512", + // No bind_mode set + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{ + NodeName: NodeName, + } + + err := c.populateNodeConfig(config, 4) + require.NoError(t, err) + + // ListenAddress should be empty when bind_mode is not NodeIP + assert.Empty(t, config.ListenAddress) +} + +func TestPopulateNodeConfig_DefaultLogLevel(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + _ = os.Unsetenv("CALICO_ROUTER_ID") + + cache := map[string]string{ + "/calico/bgp/v1/host/test-node/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/global/as_num": "64512", + // No loglevel set + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{ + NodeName: NodeName, + } + + err := c.populateNodeConfig(config, 4) + require.NoError(t, err) + + // Default debug mode when no log level is set + assert.Equal(t, "{ states }", config.DebugMode) +} + +// ============================================================================= +// processCommunityRules Tests +// ============================================================================= + +func TestProcessCommunityRules_StandardCommunity(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + // Community advertisements with standard 2-part community + advertisements := []map[string]any{ + { + "cidr": "10.0.0.0/8", + "communities": []any{"65000:100"}, + }, + } + advJSON, _ := json.Marshal(advertisements) + + cache := map[string]string{ + "/calico/bgp/v1/global/prefix_advertisements/ip_v4": string(advJSON), + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{} + + err := c.processCommunityRules(config, 4) + require.NoError(t, err) + + require.Len(t, config.Communities, 1) + assert.Equal(t, "10.0.0.0/8", config.Communities[0].CIDR) + require.Len(t, config.Communities[0].AddStatements, 1) + assert.Equal(t, "bgp_community.add((65000, 100));", config.Communities[0].AddStatements[0]) +} + +func TestProcessCommunityRules_LargeCommunity(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + // Community advertisements with large 3-part community + advertisements := []map[string]any{ + { + "cidr": "172.16.0.0/12", + "communities": []any{"65000:100:200"}, + }, + } + advJSON, _ := json.Marshal(advertisements) + + cache := map[string]string{ + "/calico/bgp/v1/global/prefix_advertisements/ip_v4": string(advJSON), + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{} + + err := c.processCommunityRules(config, 4) + require.NoError(t, err) + + require.Len(t, config.Communities, 1) + assert.Equal(t, "172.16.0.0/12", config.Communities[0].CIDR) + require.Len(t, config.Communities[0].AddStatements, 1) + assert.Equal(t, "bgp_large_community.add((65000, 100, 200));", config.Communities[0].AddStatements[0]) +} + +func TestProcessCommunityRules_MultipleCommunities(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + // Multiple communities for a single CIDR + advertisements := []map[string]any{ + { + "cidr": "10.0.0.0/8", + "communities": []any{"65000:100", "65000:200", "65001:50:100"}, + }, + } + advJSON, _ := json.Marshal(advertisements) + + cache := map[string]string{ + "/calico/bgp/v1/global/prefix_advertisements/ip_v4": string(advJSON), + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{} + + err := c.processCommunityRules(config, 4) + require.NoError(t, err) + + require.Len(t, config.Communities, 1) + require.Len(t, config.Communities[0].AddStatements, 3) + assert.Equal(t, "bgp_community.add((65000, 100));", config.Communities[0].AddStatements[0]) + assert.Equal(t, "bgp_community.add((65000, 200));", config.Communities[0].AddStatements[1]) + assert.Equal(t, "bgp_large_community.add((65001, 50, 100));", config.Communities[0].AddStatements[2]) +} + +func TestProcessCommunityRules_MultipleCIDRs(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + // Multiple advertisements with different CIDRs + advertisements := []map[string]any{ + { + "cidr": "10.0.0.0/8", + "communities": []any{"65000:100"}, + }, + { + "cidr": "192.168.0.0/16", + "communities": []any{"65000:200"}, + }, + } + advJSON, _ := json.Marshal(advertisements) + + cache := map[string]string{ + "/calico/bgp/v1/global/prefix_advertisements/ip_v4": string(advJSON), + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{} + + err := c.processCommunityRules(config, 4) + require.NoError(t, err) + + require.Len(t, config.Communities, 2) + assert.Equal(t, "10.0.0.0/8", config.Communities[0].CIDR) + assert.Equal(t, "192.168.0.0/16", config.Communities[1].CIDR) +} + +func TestProcessCommunityRules_IPv6(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + // IPv6 prefix advertisement + advertisements := []map[string]any{ + { + "cidr": "fd00::/8", + "communities": []any{"65000:100"}, + }, + } + advJSON, _ := json.Marshal(advertisements) + + cache := map[string]string{ + "/calico/bgp/v1/global/prefix_advertisements/ip_v6": string(advJSON), + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{} + + err := c.processCommunityRules(config, 6) + require.NoError(t, err) + + require.Len(t, config.Communities, 1) + assert.Equal(t, "fd00::/8", config.Communities[0].CIDR) +} + +func TestProcessCommunityRules_NodeSpecific(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + // Node-specific should take precedence over global + nodeAdv := []map[string]any{ + { + "cidr": "10.0.0.0/8", + "communities": []any{"65001:100"}, // Node-specific + }, + } + nodeAdvJSON, _ := json.Marshal(nodeAdv) + + globalAdv := []map[string]any{ + { + "cidr": "172.16.0.0/12", + "communities": []any{"65000:200"}, // Global (should be ignored) + }, + } + globalAdvJSON, _ := json.Marshal(globalAdv) + + cache := map[string]string{ + "/calico/bgp/v1/host/test-node/prefix_advertisements/ip_v4": string(nodeAdvJSON), + "/calico/bgp/v1/global/prefix_advertisements/ip_v4": string(globalAdvJSON), + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{} + + err := c.processCommunityRules(config, 4) + require.NoError(t, err) + + require.Len(t, config.Communities, 1) + // Should use node-specific, not global + assert.Equal(t, "10.0.0.0/8", config.Communities[0].CIDR) + assert.Contains(t, config.Communities[0].AddStatements[0], "65001") +} + +func TestProcessCommunityRules_GlobalFallback(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + // Only global defined, no node-specific + globalAdv := []map[string]any{ + { + "cidr": "172.16.0.0/12", + "communities": []any{"65000:200"}, + }, + } + globalAdvJSON, _ := json.Marshal(globalAdv) + + cache := map[string]string{ + "/calico/bgp/v1/global/prefix_advertisements/ip_v4": string(globalAdvJSON), + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{} + + err := c.processCommunityRules(config, 4) + require.NoError(t, err) + + require.Len(t, config.Communities, 1) + assert.Equal(t, "172.16.0.0/12", config.Communities[0].CIDR) +} + +func TestProcessCommunityRules_NoCommunities(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + // No prefix_advertisements defined + cache := map[string]string{} + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{} + + err := c.processCommunityRules(config, 4) + require.NoError(t, err) + + // Should be empty, no error + assert.Len(t, config.Communities, 0) +} + +func TestProcessCommunityRules_EmptyCommunities(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + // Advertisement with empty communities array + advertisements := []map[string]any{ + { + "cidr": "10.0.0.0/8", + "communities": []any{}, + }, + } + advJSON, _ := json.Marshal(advertisements) + + cache := map[string]string{ + "/calico/bgp/v1/global/prefix_advertisements/ip_v4": string(advJSON), + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{} + + err := c.processCommunityRules(config, 4) + require.NoError(t, err) + + // Should be empty since no add statements were generated + assert.Len(t, config.Communities, 0) +} + +func TestProcessCommunityRules_InvalidCommunityFormat(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + // Community with invalid format (not 2 or 3 parts) + advertisements := []map[string]any{ + { + "cidr": "10.0.0.0/8", + "communities": []any{ + "65000", // Invalid: only 1 part + "65000:100", // Valid: 2 parts + "a:b:c:d", // Invalid: 4 parts + }, + }, + } + advJSON, _ := json.Marshal(advertisements) + + cache := map[string]string{ + "/calico/bgp/v1/global/prefix_advertisements/ip_v4": string(advJSON), + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{} + + err := c.processCommunityRules(config, 4) + require.NoError(t, err) + + // Should only have the valid community + require.Len(t, config.Communities, 1) + require.Len(t, config.Communities[0].AddStatements, 1) + assert.Contains(t, config.Communities[0].AddStatements[0], "65000, 100") +} + +func TestProcessCommunityRules_MissingCIDR(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + // Advertisement without CIDR field + advertisements := []map[string]any{ + { + "communities": []any{"65000:100"}, + }, + } + advJSON, _ := json.Marshal(advertisements) + + cache := map[string]string{ + "/calico/bgp/v1/global/prefix_advertisements/ip_v4": string(advJSON), + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{} + + err := c.processCommunityRules(config, 4) + require.NoError(t, err) + + // Should be empty since CIDR is missing + assert.Len(t, config.Communities, 0) +} + +func TestProcessCommunityRules_MissingCommunities(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + // Advertisement without communities field + advertisements := []map[string]any{ + { + "cidr": "10.0.0.0/8", + }, + } + advJSON, _ := json.Marshal(advertisements) + + cache := map[string]string{ + "/calico/bgp/v1/global/prefix_advertisements/ip_v4": string(advJSON), + } + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{} + + err := c.processCommunityRules(config, 4) + require.NoError(t, err) + + // Should be empty since communities are missing + assert.Len(t, config.Communities, 0) +} + +// ============================================================================= +// Peer Processing Tests +// ============================================================================= + +func TestProcessMeshPeers_BasicMesh(t *testing.T) { + // Set up global NodeName + originalNodeName := NodeName + NodeName = "node-1" + defer func() { NodeName = originalNodeName }() + + // Create mesh configuration + meshConfig := map[string]any{ + "enabled": true, + } + meshConfigJSON, _ := json.Marshal(meshConfig) + + cache := map[string]string{ + "/calico/bgp/v1/global/node_mesh": string(meshConfigJSON), + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/host/node-1/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/host/node-2/ip_addr_v4": "10.0.0.2", + "/calico/bgp/v1/host/node-3/ip_addr_v4": "10.0.0.3", + "/calico/bgp/v1/host/node-2/as_num": "64512", + "/calico/bgp/v1/host/node-3/as_num": "64512", + } + + c := newTestClient(cache, nil) + + config := &types.BirdBGPConfig{ + NodeIP: "10.0.0.1", + ASNumber: "64512", + } + + err := c.processMeshPeers(config, "", 4) + require.NoError(t, err) + + // Should have 2 mesh peers (node-2 and node-3, excluding ourselves) + assert.Len(t, config.Peers, 2) + + // Verify peer names and IPs + peerIPs := make(map[string]bool) + for _, peer := range config.Peers { + peerIPs[peer.IP] = true + assert.Equal(t, "mesh", peer.Type) + assert.Contains(t, peer.Name, "Mesh_") + assert.Equal(t, "10.0.0.1", peer.SourceAddr) + } + assert.True(t, peerIPs["10.0.0.2"]) + assert.True(t, peerIPs["10.0.0.3"]) +} + +func TestProcessMeshPeers_IPv6(t *testing.T) { + originalNodeName := NodeName + NodeName = "node-1" + defer func() { NodeName = originalNodeName }() + + meshConfig := map[string]any{ + "enabled": true, + } + meshConfigJSON, _ := json.Marshal(meshConfig) + + cache := map[string]string{ + "/calico/bgp/v1/global/node_mesh": string(meshConfigJSON), + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/host/node-1/ip_addr_v6": "fd00::1", + "/calico/bgp/v1/host/node-2/ip_addr_v6": "fd00::2", + } + + c := newTestClient(cache, nil) + + config := &types.BirdBGPConfig{ + NodeIPv6: "fd00::1", + ASNumber: "64512", + } + + err := c.processMeshPeers(config, "", 6) + require.NoError(t, err) + + assert.Len(t, config.Peers, 1) + assert.Equal(t, "fd00::2", config.Peers[0].IP) + assert.Contains(t, config.Peers[0].Name, "Mesh_fd00__2") + assert.Equal(t, "fd00::1", config.Peers[0].SourceAddr) +} + +func TestProcessMeshPeers_MeshDisabled(t *testing.T) { + originalNodeName := NodeName + NodeName = "node-1" + defer func() { NodeName = originalNodeName }() + + meshConfig := map[string]any{ + "enabled": false, + } + meshConfigJSON, _ := json.Marshal(meshConfig) + + cache := map[string]string{ + "/calico/bgp/v1/global/node_mesh": string(meshConfigJSON), + "/calico/bgp/v1/host/node-1/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/host/node-2/ip_addr_v4": "10.0.0.2", + } + + c := newTestClient(cache, nil) + + config := &types.BirdBGPConfig{ + NodeIP: "10.0.0.1", + ASNumber: "64512", + } + + err := c.processMeshPeers(config, "", 4) + require.NoError(t, err) + + // No peers should be added when mesh is disabled + assert.Len(t, config.Peers, 0) +} + +func TestProcessMeshPeers_RouteReflector(t *testing.T) { + originalNodeName := NodeName + NodeName = "node-1" + defer func() { NodeName = originalNodeName }() + + meshConfig := map[string]any{ + "enabled": true, + } + meshConfigJSON, _ := json.Marshal(meshConfig) + + cache := map[string]string{ + "/calico/bgp/v1/global/node_mesh": string(meshConfigJSON), + "/calico/bgp/v1/host/node-1/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/host/node-2/ip_addr_v4": "10.0.0.2", + } + + c := newTestClient(cache, nil) + + config := &types.BirdBGPConfig{ + NodeIP: "10.0.0.1", + ASNumber: "64512", + } + + // Node is a route reflector - should skip mesh + err := c.processMeshPeers(config, "rr-cluster-1", 4) + require.NoError(t, err) + + assert.Len(t, config.Peers, 0) +} + +func TestProcessMeshPeers_SkipRouteReflectorPeers(t *testing.T) { + originalNodeName := NodeName + NodeName = "node-1" + defer func() { NodeName = originalNodeName }() + + meshConfig := map[string]any{ + "enabled": true, + } + meshConfigJSON, _ := json.Marshal(meshConfig) + + cache := map[string]string{ + "/calico/bgp/v1/global/node_mesh": string(meshConfigJSON), + "/calico/bgp/v1/host/node-1/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/host/node-2/ip_addr_v4": "10.0.0.2", + "/calico/bgp/v1/host/node-2/rr_cluster_id": "rr-cluster", + "/calico/bgp/v1/host/node-3/ip_addr_v4": "10.0.0.3", + } + + c := newTestClient(cache, nil) + + config := &types.BirdBGPConfig{ + NodeIP: "10.0.0.1", + ASNumber: "64512", + } + + err := c.processMeshPeers(config, "", 4) + require.NoError(t, err) + + // Should only have node-3, node-2 is a route reflector + assert.Len(t, config.Peers, 1) + assert.Equal(t, "10.0.0.3", config.Peers[0].IP) +} + +func TestProcessMeshPeers_PassiveMode(t *testing.T) { + originalNodeName := NodeName + NodeName = "node-1" + defer func() { NodeName = originalNodeName }() + + meshConfig := map[string]any{ + "enabled": true, + } + meshConfigJSON, _ := json.Marshal(meshConfig) + + // String comparison: "10.0.0.2" > "10.0.0.1" and "10.0.0.10" > "10.0.0.1" + // because string comparison is lexicographical, not numerical + cache := map[string]string{ + "/calico/bgp/v1/global/node_mesh": string(meshConfigJSON), + "/calico/bgp/v1/host/node-1/ip_addr_v4": "10.0.0.5", // Our IP + "/calico/bgp/v1/host/node-2/ip_addr_v4": "10.0.0.2", // "10.0.0.2" < "10.0.0.5" (string) - NOT passive + "/calico/bgp/v1/host/node-3/ip_addr_v4": "10.0.0.8", // "10.0.0.8" > "10.0.0.5" (string) - PASSIVE + } + + c := newTestClient(cache, nil) + + config := &types.BirdBGPConfig{ + NodeIP: "10.0.0.5", + ASNumber: "64512", + } + + err := c.processMeshPeers(config, "", 4) + require.NoError(t, err) + + assert.Len(t, config.Peers, 2) + + for _, peer := range config.Peers { + switch peer.IP { + case "10.0.0.2": + // "10.0.0.2" < "10.0.0.5" (string comparison), so should NOT be passive + assert.False(t, peer.Passive, "10.0.0.2 < 10.0.0.5 in string comparison") + case "10.0.0.8": + // "10.0.0.8" > "10.0.0.5" (string comparison), so should be passive + assert.True(t, peer.Passive, "10.0.0.8 > 10.0.0.5 in string comparison") + } + } +} + +func TestProcessMeshPeers_WithPasswordAndRestartTime(t *testing.T) { + originalNodeName := NodeName + NodeName = "node-1" + defer func() { NodeName = originalNodeName }() + + meshConfig := map[string]any{ + "enabled": true, + } + meshConfigJSON, _ := json.Marshal(meshConfig) + + cache := map[string]string{ + "/calico/bgp/v1/global/node_mesh": string(meshConfigJSON), + "/calico/bgp/v1/global/node_mesh_password": "secret123", + "/calico/bgp/v1/global/node_mesh_restart_time": "120", + "/calico/bgp/v1/host/node-1/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/host/node-2/ip_addr_v4": "10.0.0.2", + } + + c := newTestClient(cache, nil) + + config := &types.BirdBGPConfig{ + NodeIP: "10.0.0.1", + ASNumber: "64512", + } + + err := c.processMeshPeers(config, "", 4) + require.NoError(t, err) + + require.Len(t, config.Peers, 1) + assert.Equal(t, "secret123", config.Peers[0].Password) + assert.Equal(t, "120", config.Peers[0].GracefulRestart) +} + +func TestProcessGlobalPeers_BasicPeer(t *testing.T) { + originalNodeName := NodeName + NodeName = "node-1" + defer func() { NodeName = originalNodeName }() + + peerData := map[string]any{ + "ip": "192.168.1.100", + "as_num": "65000", + } + peerDataJSON, _ := json.Marshal(peerData) + + cache := map[string]string{ + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/global/peer_v4/192.168.1.100": string(peerDataJSON), + "/calico/bgp/v1/host/node-1/ip_addr_v4": "10.0.0.1", + } + + c := newTestClient(cache, nil) + + config := &types.BirdBGPConfig{ + NodeIP: "10.0.0.1", + ASNumber: "64512", + } + + err := c.processGlobalPeers(config, "", 4) + require.NoError(t, err) + + require.Len(t, config.Peers, 1) + assert.Equal(t, "192.168.1.100", config.Peers[0].IP) + assert.Equal(t, "65000", config.Peers[0].ASNumber) + assert.Contains(t, config.Peers[0].Name, "Global_") +} + +func TestProcessGlobalPeers_IPv6Peer(t *testing.T) { + originalNodeName := NodeName + NodeName = "node-1" + defer func() { NodeName = originalNodeName }() + + peerData := map[string]any{ + "ip": "2001:db8::100", + "as_num": "65000", + "source_addr": "UseNodeIP", // Required to set SourceAddr + } + peerDataJSON, _ := json.Marshal(peerData) + + cache := map[string]string{ + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/global/peer_v6/2001:db8::100": string(peerDataJSON), + "/calico/bgp/v1/host/node-1/ip_addr_v6": "fd00::1", + } + + c := newTestClient(cache, nil) + + config := &types.BirdBGPConfig{ + NodeIPv6: "fd00::1", + ASNumber: "64512", + } + + err := c.processGlobalPeers(config, "", 6) + require.NoError(t, err) + + require.Len(t, config.Peers, 1) + assert.Equal(t, "2001:db8::100", config.Peers[0].IP) + assert.Contains(t, config.Peers[0].Name, "Global_2001_db8__100") + assert.Equal(t, "fd00::1", config.Peers[0].SourceAddr) +} + +func TestProcessGlobalPeers_WithPassword(t *testing.T) { + originalNodeName := NodeName + NodeName = "node-1" + defer func() { NodeName = originalNodeName }() + + peerData := map[string]any{ + "ip": "192.168.1.100", + "as_num": "65000", + "password": "bgp-secret", + } + peerDataJSON, _ := json.Marshal(peerData) + + cache := map[string]string{ + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/global/peer_v4/192.168.1.100": string(peerDataJSON), + "/calico/bgp/v1/host/node-1/ip_addr_v4": "10.0.0.1", + } + + c := newTestClient(cache, nil) + + config := &types.BirdBGPConfig{ + NodeIP: "10.0.0.1", + ASNumber: "64512", + } + + err := c.processGlobalPeers(config, "", 4) + require.NoError(t, err) + + require.Len(t, config.Peers, 1) + assert.Equal(t, "bgp-secret", config.Peers[0].Password) +} + +func TestProcessGlobalPeers_WithTTLSecurity(t *testing.T) { + originalNodeName := NodeName + NodeName = "node-1" + defer func() { NodeName = originalNodeName }() + + peerData := map[string]any{ + "ip": "192.168.1.100", + "as_num": "65000", + "ttl_security": float64(64), + } + peerDataJSON, _ := json.Marshal(peerData) + + cache := map[string]string{ + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/global/peer_v4/192.168.1.100": string(peerDataJSON), + "/calico/bgp/v1/host/node-1/ip_addr_v4": "10.0.0.1", + } + + c := newTestClient(cache, nil) + + config := &types.BirdBGPConfig{ + NodeIP: "10.0.0.1", + ASNumber: "64512", + } + + err := c.processGlobalPeers(config, "", 4) + require.NoError(t, err) + + require.Len(t, config.Peers, 1) + assert.Contains(t, config.Peers[0].TTLSecurity, "on") + assert.Contains(t, config.Peers[0].TTLSecurity, "multihop 64") +} + +func TestProcessNodePeers_BasicPeer(t *testing.T) { + originalNodeName := NodeName + NodeName = "node-1" + defer func() { NodeName = originalNodeName }() + + peerData := map[string]any{ + "ip": "172.16.0.100", + "as_num": "65001", + } + peerDataJSON, _ := json.Marshal(peerData) + + cache := map[string]string{ + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/host/node-1/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/host/node-1/peer_v4/172.16.0.100": string(peerDataJSON), + } + + c := newTestClient(cache, nil) + + config := &types.BirdBGPConfig{ + NodeIP: "10.0.0.1", + ASNumber: "64512", + } + + err := c.processNodePeers(config, "", 4) + require.NoError(t, err) + + require.Len(t, config.Peers, 1) + assert.Equal(t, "172.16.0.100", config.Peers[0].IP) + assert.Equal(t, "65001", config.Peers[0].ASNumber) + assert.Contains(t, config.Peers[0].Name, "Node_") +} + +func TestProcessNodePeers_IPv6Peer(t *testing.T) { + originalNodeName := NodeName + NodeName = "node-1" + defer func() { NodeName = originalNodeName }() + + peerData := map[string]any{ + "ip": "fe80::100", + "as_num": "65001", + } + peerDataJSON, _ := json.Marshal(peerData) + + cache := map[string]string{ + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/host/node-1/ip_addr_v6": "fd00::1", + "/calico/bgp/v1/host/node-1/peer_v6/fe80::100": string(peerDataJSON), + } + + c := newTestClient(cache, nil) + + config := &types.BirdBGPConfig{ + NodeIPv6: "fd00::1", + ASNumber: "64512", + } + + err := c.processNodePeers(config, "", 6) + require.NoError(t, err) + + require.Len(t, config.Peers, 1) + assert.Equal(t, "fe80::100", config.Peers[0].IP) + assert.Contains(t, config.Peers[0].Name, "Node_fe80__100") +} + +func TestProcessNodePeers_LocalBGPPeer(t *testing.T) { + originalNodeName := NodeName + NodeName = "node-1" + defer func() { NodeName = originalNodeName }() + + // Local BGP peers use the regular peer_v4 path with local_bgp_peer: true + peerData := map[string]any{ + "ip": "192.168.1.50", // Local workload IP + "as_num": "64512", + "local_bgp_peer": true, // This marks it as a local BGP peer + } + peerDataJSON, _ := json.Marshal(peerData) + + cache := map[string]string{ + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/host/node-1/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/host/node-1/peer_v4/192.168.1.50": string(peerDataJSON), + } + + c := newTestClient(cache, nil) + + config := &types.BirdBGPConfig{ + NodeIP: "10.0.0.1", + ASNumber: "64512", + } + + err := c.processNodePeers(config, "", 4) + require.NoError(t, err) + + require.Len(t, config.Peers, 1) + assert.Equal(t, "local_workload", config.Peers[0].Type) +} + +func TestProcessPeers_CombinedMeshGlobalNode(t *testing.T) { + originalNodeName := NodeName + NodeName = "node-1" + defer func() { NodeName = originalNodeName }() + + meshConfig := map[string]any{ + "enabled": true, + } + meshConfigJSON, _ := json.Marshal(meshConfig) + + globalPeerData := map[string]any{ + "ip": "192.168.1.100", + "as_num": "65000", + } + globalPeerJSON, _ := json.Marshal(globalPeerData) + + nodePeerData := map[string]any{ + "ip": "172.16.0.100", + "as_num": "65001", + } + nodePeerJSON, _ := json.Marshal(nodePeerData) + + cache := map[string]string{ + "/calico/bgp/v1/global/node_mesh": string(meshConfigJSON), + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/host/node-1/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/host/node-2/ip_addr_v4": "10.0.0.2", + "/calico/bgp/v1/global/peer_v4/192.168.1.100": string(globalPeerJSON), + "/calico/bgp/v1/host/node-1/peer_v4/172.16.0.100": string(nodePeerJSON), + } + + c := newTestClient(cache, nil) + + config := &types.BirdBGPConfig{ + NodeIP: "10.0.0.1", + ASNumber: "64512", + } + + // Process all peer types + err := c.processPeers(config, 4) + require.NoError(t, err) + + // Should have: 1 mesh peer (node-2), 1 global peer, 1 node peer + assert.Len(t, config.Peers, 3) + + // Verify peer types + peerTypes := make(map[string]int) + for _, peer := range config.Peers { + peerTypes[peer.Type]++ + } + assert.Equal(t, 1, peerTypes["mesh"]) + assert.Equal(t, 1, peerTypes["global"]) + assert.Equal(t, 1, peerTypes["node"]) +} + +func TestProcessPeers_NextHopModes(t *testing.T) { + originalNodeName := NodeName + NodeName = "node-1" + defer func() { NodeName = originalNodeName }() + + tests := []struct { + name string + nextHopMode string + keepNextHop bool + peerAS string + nodeAS string + expectSelf bool + expectKeep bool + }{ + { + name: "NextHopSelf mode", + nextHopMode: "Self", + peerAS: "65000", + nodeAS: "64512", + expectSelf: true, + }, + { + name: "NextHopKeep mode", + nextHopMode: "Keep", + peerAS: "65000", + nodeAS: "64512", + expectKeep: true, + }, + { + name: "Legacy keep_next_hop for eBGP", + keepNextHop: true, + peerAS: "65000", + nodeAS: "64512", + expectKeep: true, + }, + { + name: "Legacy keep_next_hop ignored for iBGP", + keepNextHop: true, + peerAS: "64512", + nodeAS: "64512", + expectKeep: false, // Should be ignored for iBGP + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + peerData := map[string]any{ + "ip": "192.168.1.100", + "as_num": tt.peerAS, + } + if tt.nextHopMode != "" { + peerData["next_hop_mode"] = tt.nextHopMode + } + if tt.keepNextHop { + peerData["keep_next_hop"] = true + } + peerDataJSON, _ := json.Marshal(peerData) + + cache := map[string]string{ + "/calico/bgp/v1/global/as_num": tt.nodeAS, + "/calico/bgp/v1/global/peer_v4/192.168.1.100": string(peerDataJSON), + "/calico/bgp/v1/host/node-1/ip_addr_v4": "10.0.0.1", + } + + c := newTestClient(cache, nil) + + config := &types.BirdBGPConfig{ + NodeIP: "10.0.0.1", + ASNumber: tt.nodeAS, + } + + err := c.processGlobalPeers(config, "", 4) + require.NoError(t, err) + + require.Len(t, config.Peers, 1) + assert.Equal(t, tt.expectSelf, config.Peers[0].NextHopSelf, "NextHopSelf mismatch") + assert.Equal(t, tt.expectKeep, config.Peers[0].NextHopKeep, "NextHopKeep mismatch") + }) + } +} + +func TestProcessPeers_RouteReflectorClient(t *testing.T) { + originalNodeName := NodeName + NodeName = "node-1" + defer func() { NodeName = originalNodeName }() + + peerData := map[string]any{ + "ip": "192.168.1.100", + "as_num": "64512", // Same AS for iBGP + "rr_cluster_id": "cluster-2", // Different cluster ID + } + peerDataJSON, _ := json.Marshal(peerData) + + cache := map[string]string{ + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/global/peer_v4/192.168.1.100": string(peerDataJSON), + "/calico/bgp/v1/host/node-1/ip_addr_v4": "10.0.0.1", + } + + c := newTestClient(cache, nil) + + config := &types.BirdBGPConfig{ + NodeIP: "10.0.0.1", + ASNumber: "64512", + } + + // Process with this node having a cluster ID (making it a route reflector) + err := c.processGlobalPeers(config, "cluster-1", 4) + require.NoError(t, err) + + require.Len(t, config.Peers, 1) + assert.True(t, config.Peers[0].RouteReflector, "Peer should be marked as route reflector client") + assert.Equal(t, "cluster-1", config.Peers[0].RRClusterID) +} + +func TestProcessPeers_LocalAS(t *testing.T) { + originalNodeName := NodeName + NodeName = "node-1" + defer func() { NodeName = originalNodeName }() + + peerData := map[string]any{ + "ip": "192.168.1.100", + "as_num": "65000", + "local_as_num": "64000", + "num_allow_local_as": float64(2), // Correct field name + } + peerDataJSON, _ := json.Marshal(peerData) + + cache := map[string]string{ + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/global/peer_v4/192.168.1.100": string(peerDataJSON), + "/calico/bgp/v1/host/node-1/ip_addr_v4": "10.0.0.1", + } + + c := newTestClient(cache, nil) + + config := &types.BirdBGPConfig{ + NodeIP: "10.0.0.1", + ASNumber: "64512", + } + + err := c.processGlobalPeers(config, "", 4) + require.NoError(t, err) + + require.Len(t, config.Peers, 1) + assert.Equal(t, "64000", config.Peers[0].LocalASNumber) + assert.Equal(t, "2", config.Peers[0].NumAllowLocalAs) +} + +// ============================================================================= +// BGP Filter Tests +// ============================================================================= + +func TestBuildImportFilter_WithBGPFilter(t *testing.T) { + // Create a BGP filter resource with import rules + bgpFilter := map[string]any{ + "spec": map[string]any{ + "importV4": []any{ + map[string]any{ + "action": "Accept", + "matchOperator": "In", + "cidr": "10.0.0.0/8", + }, + }, + }, + } + bgpFilterJSON, _ := json.Marshal(bgpFilter) + + cache := map[string]string{ + "/calico/resources/v3/projectcalico.org/bgpfilters/my-filter": string(bgpFilterJSON), + } + + c := newTestClient(cache, nil) + + result := c.buildImportFilter([]string{"my-filter"}, 4) + + // Should include the filter function call + assert.Contains(t, result, "'bgp_my-filter_importFilterV4'();") + // Should still have default accept + assert.Contains(t, result, "accept;") +} + +func TestBuildImportFilter_WithMultipleBGPFilters(t *testing.T) { + filter1 := map[string]any{ + "spec": map[string]any{ + "importV4": []any{ + map[string]any{"action": "Accept"}, + }, + }, + } + filter1JSON, _ := json.Marshal(filter1) + + filter2 := map[string]any{ + "spec": map[string]any{ + "importV4": []any{ + map[string]any{"action": "Reject"}, + }, + }, + } + filter2JSON, _ := json.Marshal(filter2) + + cache := map[string]string{ + "/calico/resources/v3/projectcalico.org/bgpfilters/filter1": string(filter1JSON), + "/calico/resources/v3/projectcalico.org/bgpfilters/filter2": string(filter2JSON), + } + + c := newTestClient(cache, nil) + + result := c.buildImportFilter([]string{"filter1", "filter2"}, 4) + + // Should include both filter function calls + assert.Contains(t, result, "'bgp_filter1_importFilterV4'();") + assert.Contains(t, result, "'bgp_filter2_importFilterV4'();") +} + +func TestBuildImportFilter_IPv6Filter(t *testing.T) { + bgpFilter := map[string]any{ + "spec": map[string]any{ + "importV6": []any{ + map[string]any{ + "action": "Accept", + "cidr": "fd00::/8", + }, + }, + }, + } + bgpFilterJSON, _ := json.Marshal(bgpFilter) + + cache := map[string]string{ + "/calico/resources/v3/projectcalico.org/bgpfilters/ipv6-filter": string(bgpFilterJSON), + } + + c := newTestClient(cache, nil) + + result := c.buildImportFilter([]string{"ipv6-filter"}, 6) + + // Should use V6 suffix + assert.Contains(t, result, "'bgp_ipv6-filter_importFilterV6'();") +} + +func TestBuildImportFilter_FilterWithNoImportRules(t *testing.T) { + // Filter has export rules but no import rules + bgpFilter := map[string]any{ + "spec": map[string]any{ + "exportV4": []any{ + map[string]any{"action": "Accept"}, + }, + }, + } + bgpFilterJSON, _ := json.Marshal(bgpFilter) + + cache := map[string]string{ + "/calico/resources/v3/projectcalico.org/bgpfilters/export-only": string(bgpFilterJSON), + } + + c := newTestClient(cache, nil) + + result := c.buildImportFilter([]string{"export-only"}, 4) + + // Should NOT include the filter call since there are no import rules + assert.NotContains(t, result, "'bgp_export-only_importFilterV4'();") + // Should still have default accept + assert.Contains(t, result, "accept;") +} + +func TestBuildImportFilter_FilterNotFound(t *testing.T) { + cache := map[string]string{ + // No filters in cache + } + + c := newTestClient(cache, nil) + + result := c.buildImportFilter([]string{"nonexistent-filter"}, 4) + + // Should NOT include the filter call since filter doesn't exist + assert.NotContains(t, result, "'bgp_nonexistent-filter_importFilterV4'();") + // Should still have default accept + assert.Contains(t, result, "accept;") +} + +func TestBuildExportFilter_WithBGPFilter(t *testing.T) { + bgpFilter := map[string]any{ + "spec": map[string]any{ + "exportV4": []any{ + map[string]any{ + "action": "Accept", + "cidr": "10.0.0.0/8", + }, + }, + }, + } + bgpFilterJSON, _ := json.Marshal(bgpFilter) + + cache := map[string]string{ + "/calico/resources/v3/projectcalico.org/bgpfilters/my-export-filter": string(bgpFilterJSON), + } + + c := newTestClient(cache, nil) + + result := c.buildExportFilter([]string{"my-export-filter"}, "65000", "64512", 4) + + // Should include the filter function call + assert.Contains(t, result, "'bgp_my-export-filter_exportFilterV4'();") + // Should still have calico_export_to_bgp_peers + assert.Contains(t, result, "calico_export_to_bgp_peers(false);") + assert.Contains(t, result, "reject;") +} + +func TestBuildExportFilter_IPv6Filter(t *testing.T) { + bgpFilter := map[string]any{ + "spec": map[string]any{ + "exportV6": []any{ + map[string]any{ + "action": "Accept", + }, + }, + }, + } + bgpFilterJSON, _ := json.Marshal(bgpFilter) + + cache := map[string]string{ + "/calico/resources/v3/projectcalico.org/bgpfilters/ipv6-export": string(bgpFilterJSON), + } + + c := newTestClient(cache, nil) + + result := c.buildExportFilter([]string{"ipv6-export"}, "65000", "64512", 6) + + // Should use V6 suffix + assert.Contains(t, result, "'bgp_ipv6-export_exportFilterV6'();") +} + +func TestBuildExportFilter_FilterWithNoExportRules(t *testing.T) { + // Filter has import rules but no export rules + bgpFilter := map[string]any{ + "spec": map[string]any{ + "importV4": []any{ + map[string]any{"action": "Accept"}, + }, + }, + } + bgpFilterJSON, _ := json.Marshal(bgpFilter) + + cache := map[string]string{ + "/calico/resources/v3/projectcalico.org/bgpfilters/import-only": string(bgpFilterJSON), + } + + c := newTestClient(cache, nil) + + result := c.buildExportFilter([]string{"import-only"}, "65000", "64512", 4) + + // Should NOT include the filter call since there are no export rules + assert.NotContains(t, result, "'bgp_import-only_exportFilterV4'();") + // Should still have calico_export_to_bgp_peers + assert.Contains(t, result, "calico_export_to_bgp_peers") +} + +func TestProcessGlobalPeers_WithBGPFilter(t *testing.T) { + originalNodeName := NodeName + NodeName = "node-1" + defer func() { NodeName = originalNodeName }() + + // Create BGP filter resource with both import and export rules + bgpFilter := map[string]any{ + "spec": map[string]any{ + "importV4": []any{ + map[string]any{ + "action": "Accept", + "cidr": "10.0.0.0/8", + }, + }, + "exportV4": []any{ + map[string]any{ + "action": "Reject", + "cidr": "192.168.0.0/16", + }, + }, + }, + } + bgpFilterJSON, _ := json.Marshal(bgpFilter) + + peerData := map[string]any{ + "ip": "192.168.1.100", + "as_num": "65000", + "filters": []any{"test-filter"}, + } + peerDataJSON, _ := json.Marshal(peerData) + + cache := map[string]string{ + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/global/peer_v4/192.168.1.100": string(peerDataJSON), + "/calico/bgp/v1/host/node-1/ip_addr_v4": "10.0.0.1", + "/calico/resources/v3/projectcalico.org/bgpfilters/test-filter": string(bgpFilterJSON), + } + + c := newTestClient(cache, nil) + + config := &types.BirdBGPConfig{ + NodeIP: "10.0.0.1", + ASNumber: "64512", + } + + err := c.processGlobalPeers(config, "", 4) + require.NoError(t, err) + + require.Len(t, config.Peers, 1) + + // Verify import filter includes the BGP filter function call + assert.Contains(t, config.Peers[0].ImportFilter, "'bgp_test-filter_importFilterV4'();") + assert.Contains(t, config.Peers[0].ImportFilter, "accept;") + + // Verify export filter includes the BGP filter function call + assert.Contains(t, config.Peers[0].ExportFilter, "'bgp_test-filter_exportFilterV4'();") + assert.Contains(t, config.Peers[0].ExportFilter, "calico_export_to_bgp_peers") +} + +func TestProcessGlobalPeers_WithBGPFilter_IPv6(t *testing.T) { + originalNodeName := NodeName + NodeName = "node-1" + defer func() { NodeName = originalNodeName }() + + // Create BGP filter resource with IPv6 rules + bgpFilter := map[string]any{ + "spec": map[string]any{ + "importV6": []any{ + map[string]any{ + "action": "Accept", + "cidr": "fd00::/8", + }, + }, + "exportV6": []any{ + map[string]any{ + "action": "Accept", + }, + }, + }, + } + bgpFilterJSON, _ := json.Marshal(bgpFilter) + + peerData := map[string]any{ + "ip": "2001:db8::100", + "as_num": "65000", + "filters": []any{"ipv6-bgp-filter"}, + } + peerDataJSON, _ := json.Marshal(peerData) + + cache := map[string]string{ + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/global/peer_v6/2001:db8::100": string(peerDataJSON), + "/calico/bgp/v1/host/node-1/ip_addr_v6": "fd00::1", + "/calico/resources/v3/projectcalico.org/bgpfilters/ipv6-bgp-filter": string(bgpFilterJSON), + } + + c := newTestClient(cache, nil) + + config := &types.BirdBGPConfig{ + NodeIPv6: "fd00::1", + ASNumber: "64512", + } + + err := c.processGlobalPeers(config, "", 6) + require.NoError(t, err) + + require.Len(t, config.Peers, 1) + + // Verify import filter uses V6 suffix + assert.Contains(t, config.Peers[0].ImportFilter, "'bgp_ipv6-bgp-filter_importFilterV6'();") + + // Verify export filter uses V6 suffix + assert.Contains(t, config.Peers[0].ExportFilter, "'bgp_ipv6-bgp-filter_exportFilterV6'();") +} + +func TestProcessGlobalPeers_WithLongFilterName(t *testing.T) { + originalNodeName := NodeName + NodeName = "node-1" + defer func() { NodeName = originalNodeName }() + + // Create a BGP filter with a very long name that needs truncation + longFilterName := "this-is-a-very-long-bgp-filter-name-that-exceeds-bird-symbol-limit" + + bgpFilter := map[string]any{ + "spec": map[string]any{ + "importV4": []any{ + map[string]any{"action": "Accept"}, + }, + }, + } + bgpFilterJSON, _ := json.Marshal(bgpFilter) + + peerData := map[string]any{ + "ip": "192.168.1.100", + "as_num": "65000", + "filters": []any{longFilterName}, + } + peerDataJSON, _ := json.Marshal(peerData) + + cache := map[string]string{ + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/global/peer_v4/192.168.1.100": string(peerDataJSON), + "/calico/bgp/v1/host/node-1/ip_addr_v4": "10.0.0.1", + "/calico/resources/v3/projectcalico.org/bgpfilters/" + longFilterName: string(bgpFilterJSON), + } + + c := newTestClient(cache, nil) + + config := &types.BirdBGPConfig{ + NodeIP: "10.0.0.1", + ASNumber: "64512", + } + + err := c.processGlobalPeers(config, "", 4) + require.NoError(t, err) + + require.Len(t, config.Peers, 1) + + // The filter name should be truncated - check that it doesn't exceed BIRD's limit + // Full function name format: 'bgp__importFilterV4' + // The filter function call should exist and be truncated + assert.Contains(t, config.Peers[0].ImportFilter, "'bgp_") + assert.Contains(t, config.Peers[0].ImportFilter, "_importFilterV4'();") + + // Verify the name was truncated (should contain hash suffix) + // The truncated name should NOT be the full original name + assert.NotContains(t, config.Peers[0].ImportFilter, longFilterName) +} + +func TestProcessNodePeers_WithBGPFilter(t *testing.T) { + originalNodeName := NodeName + NodeName = "node-1" + defer func() { NodeName = originalNodeName }() + + // Create BGP filter resource + bgpFilter := map[string]any{ + "spec": map[string]any{ + "importV4": []any{ + map[string]any{"action": "Accept"}, + }, + "exportV4": []any{ + map[string]any{"action": "Accept"}, + }, + }, + } + bgpFilterJSON, _ := json.Marshal(bgpFilter) + + peerData := map[string]any{ + "ip": "172.16.0.100", + "as_num": "65001", + "filters": []any{"node-peer-filter"}, + } + peerDataJSON, _ := json.Marshal(peerData) + + cache := map[string]string{ + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/host/node-1/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/host/node-1/peer_v4/172.16.0.100": string(peerDataJSON), + "/calico/resources/v3/projectcalico.org/bgpfilters/node-peer-filter": string(bgpFilterJSON), + } + + c := newTestClient(cache, nil) + + config := &types.BirdBGPConfig{ + NodeIP: "10.0.0.1", + ASNumber: "64512", + } + + err := c.processNodePeers(config, "", 4) + require.NoError(t, err) + + require.Len(t, config.Peers, 1) + + // Verify both import and export filters have the BGP filter function calls + assert.Contains(t, config.Peers[0].ImportFilter, "'bgp_node-peer-filter_importFilterV4'();") + assert.Contains(t, config.Peers[0].ExportFilter, "'bgp_node-peer-filter_exportFilterV4'();") +} + +func TestTruncateBGPFilterName(t *testing.T) { + // Test the truncation function directly + tests := []struct { + name string + filterName string + shouldHash bool + }{ + { + name: "Short name - no truncation", + filterName: "my-filter", + shouldHash: false, + }, + { + name: "Long name - should truncate and hash", + filterName: "this-is-a-very-long-filter-name-that-will-exceed-the-bird-symbol-length-limit", + shouldHash: true, + }, + { + name: "Exactly at limit", + filterName: "exactly-45-characters-for-a-filter-name-xxxxx", // 45 chars is the max + shouldHash: false, + }, + { + name: "One over limit", + filterName: "exactly-46-characters-for-a-filter-name-xxxxxx", // 46 chars triggers truncation + shouldHash: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := truncateBGPFilterName(tt.filterName) + + if tt.shouldHash { + // Should be truncated with hash - original name shouldn't be fully present + assert.NotEqual(t, tt.filterName, result) + // Should contain a hash suffix (8 hex chars) + assert.LessOrEqual(t, len(result), 45, "Truncated name should be at most 45 chars") + } else { + // Should be unchanged + assert.Equal(t, tt.filterName, result) + } + }) + } +} + +// ============================================================================= +// Race Condition Tests +// ============================================================================= + +// TestConfigCache_ConcurrentReadWrite tests the race condition in the global +// configCache map when multiple goroutines call GetBirdBGPConfig concurrently. +// +// This test simulates the real-world scenario where bird.cfg.template (IPv4) +// and bird6.cfg.template (IPv6) are rendered simultaneously by different +// goroutines in processor.go's monitorPrefix function. Each template calls +// GetBirdBGPConfig with its respective IP version. +// +// We now run confd UT with -race, so this test will fail if the configCache map +// is accessed without proper synchronization. +func TestConfigCache_ConcurrentReadWrite(t *testing.T) { + originalNodeName := NodeName + NodeName = "test-node" + defer func() { NodeName = originalNodeName }() + + _ = os.Unsetenv("CALICO_ROUTER_ID") + + // Clear the global configCache to start fresh + configCacheMutex.Lock() + configCache = make(map[int]*bgpConfigCache) + configCacheMutex.Unlock() + + // Enable mesh for more realistic scenario + meshConfig := map[string]any{ + "enabled": true, + } + meshConfigJSON, err := json.Marshal(meshConfig) + require.NoError(t, err) + + // Set up cache with basic configuration needed for GetBirdBGPConfig + cache := map[string]string{ + "/calico/bgp/v1/host/test-node/ip_addr_v4": "10.0.0.1", + "/calico/bgp/v1/host/test-node/ip_addr_v6": "fd00::1", + "/calico/bgp/v1/global/as_num": "64512", + "/calico/bgp/v1/global/loglevel": "info", + "/calico/bgp/v1/global/node_mesh": string(meshConfigJSON), + "/calico/bgp/v1/host/peer-node-1/ip_addr_v4": "10.0.0.2", + "/calico/bgp/v1/host/peer-node-2/ip_addr_v6": "fd00::2", + } + + c := newTestClient(cache, nil) + + const numGoroutines = 10 + const testDuration = 10 * time.Second + + // Create a context with 30-second timeout + ctx, cancel := context.WithTimeout(context.Background(), testDuration) + defer cancel() + + var wg sync.WaitGroup + + // Simulate concurrent template rendering - this mimics what happens + // in processor.go when monitorPrefix spawns goroutines for each template + for i := range numGoroutines { + wg.Add(1) + go func(id int) { + defer wg.Done() + + // Alternate between IPv4 and IPv6 to simulate bird.cfg.template + // and bird6.cfg.template rendering concurrently + ipVersion := 4 + if id%2 == 0 { + ipVersion = 6 + } + + iterationCount := 0 + for { + select { + case <-ctx.Done(): + // Context timeout reached, exit goroutine + t.Logf("Goroutine %d (IPv%d) completed %d iterations", id, ipVersion, iterationCount) + return + default: + // This is what the template actually calls via getBGPConfig template function + _, err := c.GetBirdBGPConfig(ipVersion) + if err != nil { + // Some errors are expected if cache is cleared + continue + } + + // Periodically clear the cache to force re-computation and increase + // the likelihood of hitting the race condition + if iterationCount%5 == 0 { + configCacheMutex.Lock() + delete(configCache, ipVersion) + configCacheMutex.Unlock() + } + + iterationCount++ + } + } + }(i) + } + + wg.Wait() +} diff --git a/confd/pkg/backends/calico/client.go b/confd/pkg/backends/calico/client.go index 08cb1a79374..fa4f810f7ae 100644 --- a/confd/pkg/backends/calico/client.go +++ b/confd/pkg/backends/calico/client.go @@ -17,6 +17,7 @@ import ( "context" "encoding/json" "fmt" + "maps" "math" "net" "os" @@ -35,7 +36,7 @@ import ( logutils "github.com/projectcalico/calico/confd/pkg/log" "github.com/projectcalico/calico/confd/pkg/resource/template" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/backend/syncersv1/bgpsyncer" @@ -73,7 +74,7 @@ var ( largeCommunity = regexp.MustCompile(`^(\d+):(\d+):(\d+)$`) ) -var sensitiveValues = map[string]interface{}{ +var sensitiveValues = map[string]any{ "/calico/bgp/v1/global/node_mesh_password": nil, } @@ -164,7 +165,7 @@ func NewCalicoClient(confdConfig *config.Config) (*client, error) { // This channel, for the syncer calling OnUpdates and OnStatusUpdated, has 0 // capacity so that the caller blocks in the same way as it did before when its // calls were processed synchronously. - syncerC: make(chan interface{}), + syncerC: make(chan any), // This channel holds a trigger for existing BGP peerings to be recomputed. We only // ever need 1 pending trigger, hence capacity 1. recheckPeerConfig() does a @@ -172,9 +173,7 @@ func NewCalicoClient(confdConfig *config.Config) (*client, error) { // pending. recheckC: make(chan struct{}, 1), } - for k, v := range globalDefaults { - c.cache[k] = v - } + maps.Copy(c.cache, globalDefaults) // Create secret watcher. Must do this before the syncer, because updates from // the syncer can trigger calling c.secretWatcher.MarkStale(). @@ -378,7 +377,7 @@ type client struct { localBGPPeerWatcher *LocalBGPPeerWatcher // Channels used to decouple update and status processing. - syncerC chan interface{} + syncerC chan any recheckC chan struct{} // Cached value of the default BGP configuration for node to node mesh BGP password lookup. @@ -1027,11 +1026,11 @@ func (c *client) onUpdates(updates []api.Update, needUpdatePeersV1 bool) { } // It's a v3 resource - we care about some of these. - if v3key.Kind == libapiv3.KindNode { + if v3key.Kind == internalapi.KindNode { // Convert to v1 key/value pairs. log.Debugf("Node: %#v", u.Value) if u.Value != nil { - log.Debugf("BGPSpec: %#v", u.Value.(*libapiv3.Node).Spec.BGP) + log.Debugf("BGPSpec: %#v", u.Value.(*internalapi.Node).Spec.BGP) } kvps, err := c.nodeV1Processor.Process(&u.KVPair) if err != nil { @@ -1092,7 +1091,7 @@ func (c *client) onUpdates(updates []api.Update, needUpdatePeersV1 bool) { } } else { // This was a create or update - update node labels. - v3res, ok := u.Value.(*libapiv3.Node) + v3res, ok := u.Value.(*internalapi.Node) if !ok { log.Warning("Bad value for Node resource") continue @@ -1257,7 +1256,7 @@ func (c *client) updateBGPConfigCache(resName string, v3res *apiv3.BGPConfigurat } } -func getBGPConfigKey(v1KeyName string, key interface{}) model.Key { +func getBGPConfigKey(v1KeyName string, key any) model.Key { switch k := key.(type) { case model.NodeBGPConfigKey: k.Name = v1KeyName @@ -1284,7 +1283,7 @@ func getKVPair(key model.Key, value ...string) *model.KVPair { } } -func (c *client) getPrefixAdvertisementsKVPair(v3res *apiv3.BGPConfiguration, key interface{}) { +func (c *client) getPrefixAdvertisementsKVPair(v3res *apiv3.BGPConfiguration, key any) { ipv4Key := getBGPConfigKey("prefix_advertisements/ip_v4", key) ipv6Key := getBGPConfigKey("prefix_advertisements/ip_v6", key) @@ -1341,7 +1340,7 @@ func (c *client) getPrefixAdvertisementsKVPair(v3res *apiv3.BGPConfiguration, ke } } -func (c *client) getListenPortKVPair(v3res *apiv3.BGPConfiguration, key interface{}, updatePeersV1 *bool, updateReasons *[]string) { +func (c *client) getListenPortKVPair(v3res *apiv3.BGPConfiguration, key any, updatePeersV1 *bool, updateReasons *[]string) { listenPortKey := getBGPConfigKey("listen_port", key) if v3res != nil && v3res.Spec.ListenPort != 0 { @@ -1366,7 +1365,7 @@ func (c *client) getListenPortKVPair(v3res *apiv3.BGPConfiguration, key interfac *updatePeersV1 = true } -func (c *client) getBindModeKVPair(v3res *apiv3.BGPConfiguration, key interface{}, updatePeersV1 *bool, updateReasons *[]string) { +func (c *client) getBindModeKVPair(v3res *apiv3.BGPConfiguration, key any, updatePeersV1 *bool, updateReasons *[]string) { bindMode := getBGPConfigKey("bind_mode", key) if v3res != nil && v3res.Spec.BindMode != nil { *updateReasons = append(*updateReasons, "bindMode updated.") @@ -1378,7 +1377,7 @@ func (c *client) getBindModeKVPair(v3res *apiv3.BGPConfiguration, key interface{ *updatePeersV1 = true } -func (c *client) getASNumberKVPair(v3res *apiv3.BGPConfiguration, key interface{}, updatePeersV1 *bool, updateReasons *[]string) { +func (c *client) getASNumberKVPair(v3res *apiv3.BGPConfiguration, key any, updatePeersV1 *bool, updateReasons *[]string) { asNumberKey := getBGPConfigKey("as_num", key) if v3res != nil && v3res.Spec.ASNumber != nil { *updateReasons = append(*updateReasons, "AS number updated.") @@ -1390,7 +1389,7 @@ func (c *client) getASNumberKVPair(v3res *apiv3.BGPConfiguration, key interface{ *updatePeersV1 = true } -func (c *client) getServiceExternalIPsKVPair(v3res *apiv3.BGPConfiguration, key interface{}, svcAdvertisement *bool) { +func (c *client) getServiceExternalIPsKVPair(v3res *apiv3.BGPConfiguration, key any, svcAdvertisement *bool) { svcExternalIPKey := getBGPConfigKey("svc_external_ips", key) if v3res != nil && v3res.Spec.ServiceExternalIPs != nil && len(v3res.Spec.ServiceExternalIPs) != 0 { @@ -1411,7 +1410,7 @@ func (c *client) getServiceExternalIPsKVPair(v3res *apiv3.BGPConfiguration, key *svcAdvertisement = true } -func (c *client) getServiceLoadBalancerIPsKVPair(v3res *apiv3.BGPConfiguration, key interface{}, svcAdvertisement *bool) { +func (c *client) getServiceLoadBalancerIPsKVPair(v3res *apiv3.BGPConfiguration, key any, svcAdvertisement *bool) { svcLoadBalancerIPKey := getBGPConfigKey("svc_loadbalancer_ips", key) if v3res != nil && v3res.Spec.ServiceLoadBalancerIPs != nil && len(v3res.Spec.ServiceLoadBalancerIPs) != 0 { @@ -1430,7 +1429,7 @@ func (c *client) getServiceLoadBalancerIPsKVPair(v3res *apiv3.BGPConfiguration, *svcAdvertisement = true } -func (c *client) getServiceClusterIPsKVPair(v3res *apiv3.BGPConfiguration, key interface{}, svcAdvertisement *bool) { +func (c *client) getServiceClusterIPsKVPair(v3res *apiv3.BGPConfiguration, key any, svcAdvertisement *bool) { svcInternalIPKey := getBGPConfigKey("svc_cluster_ips", key) if len(os.Getenv(envAdvertiseClusterIPs)) != 0 { @@ -1458,7 +1457,7 @@ func (c *client) getServiceClusterIPsKVPair(v3res *apiv3.BGPConfiguration, key i } } -func (c *client) getNodeToNodeMeshKVPair(v3res *apiv3.BGPConfiguration, key interface{}) { +func (c *client) getNodeToNodeMeshKVPair(v3res *apiv3.BGPConfiguration, key any) { meshKey := getBGPConfigKey("node_mesh", key) if v3res != nil && v3res.Spec.NodeToNodeMeshEnabled != nil { @@ -1473,7 +1472,7 @@ func (c *client) getNodeToNodeMeshKVPair(v3res *apiv3.BGPConfiguration, key inte } } -func (c *client) getLogSeverityKVPair(v3res *apiv3.BGPConfiguration, key interface{}) { +func (c *client) getLogSeverityKVPair(v3res *apiv3.BGPConfiguration, key any) { logLevelKey := getBGPConfigKey("loglevel", key) if v3res != nil && v3res.Spec.LogSeverityScreen != "" { @@ -1491,7 +1490,7 @@ func (c *client) getLogSeverityKVPair(v3res *apiv3.BGPConfiguration, key interfa } } -func (c *client) getNodeMeshRestartTimeKVPair(v3res *apiv3.BGPConfiguration, key interface{}) { +func (c *client) getNodeMeshRestartTimeKVPair(v3res *apiv3.BGPConfiguration, key any) { meshRestartKey := getBGPConfigKey("node_mesh_restart_time", key) if v3res != nil && v3res.Spec.NodeMeshMaxRestartTime != nil { @@ -1502,7 +1501,7 @@ func (c *client) getNodeMeshRestartTimeKVPair(v3res *apiv3.BGPConfiguration, key } } -func (c *client) getNodeMeshPasswordKVPair(v3res *apiv3.BGPConfiguration, key interface{}) { +func (c *client) getNodeMeshPasswordKVPair(v3res *apiv3.BGPConfiguration, key any) { meshPasswordKey := getBGPConfigKey("node_mesh_password", key) if c.secretWatcher != nil && v3res != nil && v3res.Spec.NodeMeshPassword != nil && v3res.Spec.NodeMeshPassword.SecretKeyRef != nil { @@ -1522,7 +1521,7 @@ func (c *client) getNodeMeshPasswordKVPair(v3res *apiv3.BGPConfiguration, key in } } -func (c *client) getIgnoredInterfacesKVPair(v3res *apiv3.BGPConfiguration, key interface{}) { +func (c *client) getIgnoredInterfacesKVPair(v3res *apiv3.BGPConfiguration, key any) { ignoredIfacesKey := getBGPConfigKey("ignored_interfaces", key) if v3res != nil && v3res.Spec.IgnoredInterfaces != nil { c.updateCache(api.UpdateTypeKVUpdated, getKVPair(ignoredIfacesKey, strings.Join(v3res.Spec.IgnoredInterfaces, ","))) @@ -1817,6 +1816,18 @@ func (c *client) ParseFailed(rawKey string, rawValue string) { log.Errorf("Unable to parse datastore entry Key=%s; Value=%s", rawKey, rawValue) } +// GetValue gets a single value from the cache +func (c *client) GetValue(key string) (string, error) { + values, err := c.GetValues([]string{key}) + if err != nil { + return "", err + } + if value, exists := values[key]; exists { + return value, nil + } + return "", fmt.Errorf("key not found: %s", key) +} + // GetValues is called from confd to obtain the cached data for the required set of prefixes. // We simply populate the values from our caches, only returning values which have the // requested set of prefixes. diff --git a/confd/pkg/backends/calico/ippools_test.go b/confd/pkg/backends/calico/ippools_test.go new file mode 100644 index 00000000000..828b3c8d305 --- /dev/null +++ b/confd/pkg/backends/calico/ippools_test.go @@ -0,0 +1,306 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. +// +// 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 calico + +import ( + "encoding/json" + "fmt" + "os" + "reflect" + "slices" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/projectcalico/calico/confd/pkg/backends/types" + "github.com/projectcalico/calico/libcalico-go/lib/backend/encap" + "github.com/projectcalico/calico/libcalico-go/lib/backend/model" + "github.com/projectcalico/calico/libcalico-go/lib/net" +) + +type ippoolTestCase struct { + cidr string + exportDisabled bool + ipipMode encap.Mode + vxlanMode encap.Mode +} + +var ( + poolsTestsV4 []ippoolTestCase = []ippoolTestCase{ + // IPv4 IPIP Encapsulation cases. + {cidr: "10.10.0.0/16", exportDisabled: false, ipipMode: encap.Always}, + {cidr: "10.11.0.0/16", exportDisabled: true, ipipMode: encap.Always}, + {cidr: "10.12.0.0/16", exportDisabled: false, ipipMode: encap.CrossSubnet}, + {cidr: "10.13.0.0/16", exportDisabled: true, ipipMode: encap.CrossSubnet}, + // IPv4 No-Encapsulation case. + {cidr: "10.14.0.0/16", exportDisabled: false}, + {cidr: "10.15.0.0/16", exportDisabled: true}, + // IPv4 VXLAN Encapsulation cases. + {cidr: "10.16.0.0/16", exportDisabled: false, vxlanMode: encap.Always}, + {cidr: "10.17.0.0/16", exportDisabled: true, vxlanMode: encap.Always}, + {cidr: "10.18.0.0/16", exportDisabled: false, vxlanMode: encap.CrossSubnet}, + {cidr: "10.19.0.0/16", exportDisabled: true, vxlanMode: encap.CrossSubnet}, + } + + poolsTestsV6 []ippoolTestCase = []ippoolTestCase{ + // IPv6 IPIP Encapsulation cases. + {cidr: "dead:beef:10::/64", exportDisabled: false, ipipMode: encap.Always}, + {cidr: "dead:beef:11::/64", exportDisabled: true, ipipMode: encap.Always}, + {cidr: "dead:beef:12::/64", exportDisabled: false, ipipMode: encap.CrossSubnet}, + {cidr: "dead:beef:13::/64", exportDisabled: true, ipipMode: encap.CrossSubnet}, + // IPv6 No-Encapsulation case. + {cidr: "dead:beef:14::/64", exportDisabled: false}, + {cidr: "dead:beef:15::/64", exportDisabled: true}, + // IPv6 VXLAN Encapsulation cases. + {cidr: "dead:beef:16::/64", exportDisabled: false, vxlanMode: encap.Always}, + {cidr: "dead:beef:17::/64", exportDisabled: true, vxlanMode: encap.Always}, + {cidr: "dead:beef:18::/64", exportDisabled: false, vxlanMode: encap.CrossSubnet}, + {cidr: "dead:beef:19::/64", exportDisabled: true, vxlanMode: encap.CrossSubnet}, + } +) + +func Test_processIPPoolsV4(t *testing.T) { + forKernelStatements := []string{ + // IPv4 IPIP Encapsulation cases. + ` if (net ~ 10.10.0.0/16) then { krt_tunnel="tunl0"; accept; }`, + ` if (net ~ 10.11.0.0/16) then { krt_tunnel="tunl0"; accept; }`, + ` if (net ~ 10.12.0.0/16) then { if (defined(bgp_next_hop)&&(bgp_next_hop ~ 1.1.1.0/24)) then krt_tunnel=""; else krt_tunnel="tunl0"; accept; }`, + ` if (net ~ 10.13.0.0/16) then { if (defined(bgp_next_hop)&&(bgp_next_hop ~ 1.1.1.0/24)) then krt_tunnel=""; else krt_tunnel="tunl0"; accept; }`, + // IPv4 No-Encapsulation case. + ` if (net ~ 10.14.0.0/16) then { krt_tunnel=""; accept; }`, + ` if (net ~ 10.15.0.0/16) then { krt_tunnel=""; accept; }`, + // IPv4 VXLAN Encapsulation cases. + ` if (net ~ 10.16.0.0/16) then { reject; } # VXLAN routes are handled by Felix.`, + ` if (net ~ 10.17.0.0/16) then { reject; } # VXLAN routes are handled by Felix.`, + ` if (net ~ 10.18.0.0/16) then { reject; } # VXLAN routes are handled by Felix.`, + ` if (net ~ 10.19.0.0/16) then { reject; } # VXLAN routes are handled by Felix.`, + } + slices.Sort(forKernelStatements) + + forExportStatements := []string{ + // IPv4 IPIP Encapsulation cases. + ` if (net ~ 10.10.0.0/16) then { accept; }`, + ` if (net ~ 10.11.0.0/16) then { reject; } # BGP export is disabled.`, + ` if (net ~ 10.12.0.0/16) then { accept; }`, + ` if (net ~ 10.13.0.0/16) then { reject; } # BGP export is disabled.`, + // IPv4 No-Encapsulation case. + ` if (net ~ 10.14.0.0/16) then { accept; }`, + ` if (net ~ 10.15.0.0/16) then { reject; } # BGP export is disabled.`, + // IPv4 VXLAN Encapsulation cases. + ` if (net ~ 10.16.0.0/16) then { accept; }`, + ` if (net ~ 10.17.0.0/16) then { reject; } # BGP export is disabled.`, + ` if (net ~ 10.18.0.0/16) then { accept; }`, + ` if (net ~ 10.19.0.0/16) then { reject; } # BGP export is disabled.`, + } + slices.Sort(forExportStatements) + + originalNodeName := NodeName + NodeName = "test-node-ippools" + defer func() { + NodeName = originalNodeName + _ = os.Unsetenv("CALICO_ROUTER_ID") + }() + + cache := ippoolTestCasesToKVPairs(t, poolsTestsV4, 4) + key := fmt.Sprintf("/calico/bgp/v1/host/%s/network_v4", NodeName) + cache[key] = "1.1.1.0/24" + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{ + NodeName: NodeName, + } + + err := c.processIPPools(config, 4) + require.NoError(t, err) + + if !reflect.DeepEqual(config.KernelFilterForIPPools, forKernelStatements) { + t.Errorf("Generated BIRD config differs from expectation:\n Generated=%#v,\n Expected=%#v", + config.KernelFilterForIPPools, forKernelStatements) + } + + expected := filterExpectedStatements(forExportStatements, "reject") + if !reflect.DeepEqual(config.BGPExportFilterForDisabledIPPools, expected) { + t.Errorf("Generated BIRD config differs from expectation:\n Generated=%#v,\n Expected=%#v", + config.BGPExportFilterForDisabledIPPools, expected) + } + + expected = filterExpectedStatements(forExportStatements, "accept") + if !reflect.DeepEqual(config.BGPExportFilterForEnabledIPPools, expected) { + t.Errorf("Generated BIRD config differs from expectation:\n Generated=%#v,\n Expected=%#v", + config.BGPExportFilterForEnabledIPPools, expected) + } +} + +func Test_processIPPoolsV4_NoLocalSubnet(t *testing.T) { + forExportStatements := []string{ + // IPv4 IPIP Encapsulation cases. + ` if (net ~ 10.10.0.0/16) then { accept; }`, + ` if (net ~ 10.11.0.0/16) then { reject; } # BGP export is disabled.`, + ` if (net ~ 10.12.0.0/16) then { accept; }`, + ` if (net ~ 10.13.0.0/16) then { reject; } # BGP export is disabled.`, + // IPv4 No-Encapsulation case. + ` if (net ~ 10.14.0.0/16) then { accept; }`, + ` if (net ~ 10.15.0.0/16) then { reject; } # BGP export is disabled.`, + // IPv4 VXLAN Encapsulation cases. + ` if (net ~ 10.16.0.0/16) then { accept; }`, + ` if (net ~ 10.17.0.0/16) then { reject; } # BGP export is disabled.`, + ` if (net ~ 10.18.0.0/16) then { accept; }`, + ` if (net ~ 10.19.0.0/16) then { reject; } # BGP export is disabled.`, + } + slices.Sort(forExportStatements) + + originalNodeName := NodeName + NodeName = "test-node-ippools" + defer func() { + NodeName = originalNodeName + _ = os.Unsetenv("CALICO_ROUTER_ID") + }() + + cache := ippoolTestCasesToKVPairs(t, poolsTestsV4, 4) + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{ + NodeName: NodeName, + } + + err := c.processIPPools(config, 4) + require.NoError(t, err) + + if config.KernelFilterForIPPools != nil { + t.Errorf("Expected BIRD filter for programming kernel to be nil") + } + + expected := filterExpectedStatements(forExportStatements, "reject") + if !reflect.DeepEqual(config.BGPExportFilterForDisabledIPPools, expected) { + t.Errorf("Generated BIRD config differs from expectation:\n Generated=%#v,\n Expected=%#v", + config.BGPExportFilterForDisabledIPPools, expected) + } + + expected = filterExpectedStatements(forExportStatements, "accept") + if !reflect.DeepEqual(config.BGPExportFilterForEnabledIPPools, expected) { + t.Errorf("Generated BIRD config differs from expectation:\n Generated=%#v,\n Expected=%#v", + config.BGPExportFilterForEnabledIPPools, expected) + } +} + +func Test_processIPPoolsV6(t *testing.T) { + forKernelStatements := []string{ + // IPv6 IPIP Encapsulation cases. + ` if (net ~ dead:beef:10::/64) then { accept; }`, + ` if (net ~ dead:beef:11::/64) then { accept; }`, + ` if (net ~ dead:beef:12::/64) then { accept; }`, + ` if (net ~ dead:beef:13::/64) then { accept; }`, + // IPv6 No-Encapsulation case. + ` if (net ~ dead:beef:14::/64) then { accept; }`, + ` if (net ~ dead:beef:15::/64) then { accept; }`, + // IPv6 VXLAN Encapsulation cases. + ` if (net ~ dead:beef:16::/64) then { reject; } # VXLAN routes are handled by Felix.`, + ` if (net ~ dead:beef:17::/64) then { reject; } # VXLAN routes are handled by Felix.`, + ` if (net ~ dead:beef:18::/64) then { reject; } # VXLAN routes are handled by Felix.`, + ` if (net ~ dead:beef:19::/64) then { reject; } # VXLAN routes are handled by Felix.`, + } + slices.Sort(forKernelStatements) + + forExportStatements := []string{ + // IPv6 IPIP Encapsulation cases. + ` if (net ~ dead:beef:10::/64) then { accept; }`, + ` if (net ~ dead:beef:11::/64) then { reject; } # BGP export is disabled.`, + ` if (net ~ dead:beef:12::/64) then { accept; }`, + ` if (net ~ dead:beef:13::/64) then { reject; } # BGP export is disabled.`, + // IPv6 No-Encapsulation case. + ` if (net ~ dead:beef:14::/64) then { accept; }`, + ` if (net ~ dead:beef:15::/64) then { reject; } # BGP export is disabled.`, + // IPv6 VXLAN Encapsulation cases. + ` if (net ~ dead:beef:16::/64) then { accept; }`, + ` if (net ~ dead:beef:17::/64) then { reject; } # BGP export is disabled.`, + ` if (net ~ dead:beef:18::/64) then { accept; }`, + ` if (net ~ dead:beef:19::/64) then { reject; } # BGP export is disabled.`, + } + slices.Sort(forExportStatements) + + originalNodeName := NodeName + NodeName = "test-node-ippools" + defer func() { + NodeName = originalNodeName + _ = os.Unsetenv("CALICO_ROUTER_ID") + }() + + cache := ippoolTestCasesToKVPairs(t, poolsTestsV6, 6) + + c := newTestClient(cache, nil) + config := &types.BirdBGPConfig{ + NodeName: NodeName, + } + + err := c.processIPPools(config, 6) + require.NoError(t, err) + + expected := filterExpectedStatements(forKernelStatements, "reject") + if !reflect.DeepEqual(config.KernelFilterForIPPools, expected) { + t.Errorf("Generated BIRD config differs from expectation:\n Generated=%#v,\n Expected=%#v", + config.KernelFilterForIPPools, expected) + } + + expected = filterExpectedStatements(forExportStatements, "reject") + if !reflect.DeepEqual(config.BGPExportFilterForDisabledIPPools, expected) { + t.Errorf("Generated BIRD config differs from expectation:\n Generated=%#v,\n Expected=%#v", + config.BGPExportFilterForDisabledIPPools, expected) + } + + expected = filterExpectedStatements(forExportStatements, "accept") + if !reflect.DeepEqual(config.BGPExportFilterForEnabledIPPools, expected) { + t.Errorf("Generated BIRD config differs from expectation:\n Generated=%#v,\n Expected=%#v", + config.BGPExportFilterForEnabledIPPools, expected) + } +} + +func ippoolTestCasesToKVPairs(t *testing.T, tcs []ippoolTestCase, ipVersion int) map[string]string { + cache := map[string]string{} + for _, tc := range tcs { + ippool := ippoolForTestCase(tc) + jsonIPPool, err := json.Marshal(*ippool) + if err != nil { + t.Errorf("Error formatting IPPool into JSON: %s", err) + } + + name := strings.Replace(ippool.CIDR.String(), "/", "-", 1) + key := fmt.Sprintf("/calico/v1/ipam/v%d/pool/%s", ipVersion, name) + cache[key] = string(jsonIPPool) + + } + return cache +} + +func ippoolForTestCase(tc ippoolTestCase) *model.IPPool { + ippool := model.IPPool{} + ippool.CIDR = net.MustParseCIDR(tc.cidr) + ippool.IPIPMode = tc.ipipMode + ippool.VXLANMode = tc.vxlanMode + ippool.DisableBGPExport = tc.exportDisabled + return &ippool +} + +func filterExpectedStatements(statements []string, filterAction string) (filtered []string) { + if len(filterAction) == 0 { + return statements + } + for _, s := range statements { + if strings.Contains(s, fmt.Sprintf("%s; }", filterAction)) { + filtered = append(filtered, s) + } + } + return +} diff --git a/confd/pkg/backends/calico/node_labels.go b/confd/pkg/backends/calico/node_labels.go index 47940eaf7b5..22a08253750 100644 --- a/confd/pkg/backends/calico/node_labels.go +++ b/confd/pkg/backends/calico/node_labels.go @@ -1,6 +1,7 @@ package calico import ( + "maps" "reflect" "sync" @@ -32,9 +33,7 @@ func (m *nodeLabelManager) labelsForNode(n string) (map[string]string, bool) { // Make a copy of the labels map. labels := make(map[string]string, len(l)) - for k, v := range l { - labels[k] = v - } + maps.Copy(labels, l) return labels, ok } diff --git a/confd/pkg/backends/calico/routes.go b/confd/pkg/backends/calico/routes.go index 94816444dce..1c3f4fba17d 100644 --- a/confd/pkg/backends/calico/routes.go +++ b/confd/pkg/backends/calico/routes.go @@ -17,6 +17,7 @@ import ( "fmt" "net" "os" + "slices" "strings" "sync" "time" @@ -34,9 +35,22 @@ import ( ) const ( - envAdvertiseClusterIPs = "CALICO_ADVERTISE_CLUSTER_IPS" + envAdvertiseClusterIPs = "CALICO_ADVERTISE_CLUSTER_IPS" + endpointSliceServiceIndex = "svcKey" ) +func endpointSliceServiceIndexFunc(obj interface{}) ([]string, error) { + ep, ok := obj.(*discoveryv1.EndpointSlice) + if !ok { + return nil, nil + } + svcName, ok := ep.Labels[discoveryv1.LabelServiceName] + if !ok || svcName == "" { + return nil, nil + } + return []string{ep.Namespace + "/" + svcName}, nil +} + // routeGenerator defines the data fields // necessary for monitoring the services/endpoints resources for // valid service ips to advertise @@ -106,7 +120,7 @@ func NewRouteGenerator(c *client) (rg *routeGenerator, err error) { ObjectType: &discoveryv1.EndpointSlice{}, ResyncPeriod: 0, Handler: epHandler, - Indexers: cache.Indexers{}, + Indexers: cache.Indexers{endpointSliceServiceIndex: endpointSliceServiceIndexFunc}, }) return @@ -158,7 +172,6 @@ func (rg *routeGenerator) getServiceForEndpoints(ep *discoveryv1.EndpointSlice) // construct a dummy svc using the service name from endpointslice to get the key svc := &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: svcName, Namespace: ep.Namespace}} key, err := cache.MetaNamespaceKeyFunc(svc) - if err != nil { log.WithField("ep", ep.Name).WithError(err).Warn("getServiceForEndpoints: error on retrieving key for endpoint, passing") return nil, "" @@ -177,24 +190,27 @@ func (rg *routeGenerator) getServiceForEndpoints(ep *discoveryv1.EndpointSlice) // getEndpointsForService retrieves the corresponding ep for the given svc func (rg *routeGenerator) getEndpointsForService(svc *v1.Service) ([]*discoveryv1.EndpointSlice, string) { + key, err := cache.MetaNamespaceKeyFunc(svc) + if err != nil { + log.WithField("svc", svc.Name).WithError(err).Warn("getEndpointsForService: error on retrieving key for service, passing") + return nil, "" + } + + objs, err := rg.epIndexer.(cache.Indexer).ByIndex(endpointSliceServiceIndex, key) + if err != nil { + log.WithField("key", key).WithError(err).Error("getEndpointsForService: error reading endpointslice index") + return nil, key + } + var eps []*discoveryv1.EndpointSlice - for _, obj := range rg.epIndexer.List() { + for _, obj := range objs { ep, ok := obj.(*discoveryv1.EndpointSlice) if !ok { log.Warn("getEndpointsForService: failed to assert type to endpointslice, passing") continue } - if svcName, ok := ep.Labels[discoveryv1.LabelServiceName]; ok && svcName == svc.Name && ep.Namespace == svc.Namespace { - eps = append(eps, ep) - } - } - - key, err := cache.MetaNamespaceKeyFunc(svc) - if err != nil { - log.WithField("svc", svc.Name).WithError(err).Warn("getEndpointsForService: error on retrieving key for service, passing") - return nil, "" + eps = append(eps, ep) } - return eps, key } @@ -378,6 +394,23 @@ func (rg *routeGenerator) isAllowedLoadBalancerIP(loadBalancerIP string) bool { return false } +// hasAnySingleLoadBalancerIP checks whether the service has any LoadBalancer IP +// that matches a single-IP (/32 or /128) entry in serviceLoadBalancerIPs. +// It checks both svc.Spec.LoadBalancerIP (deprecated but still used) and the +// actual assigned IPs from svc.Status.LoadBalancer.Ingress, since IPs assigned +// from a Calico IPPool only appear in status, not spec. +func (rg *routeGenerator) hasAnySingleLoadBalancerIP(svc *v1.Service) bool { + if rg.isSingleLoadBalancerIP(svc.Spec.LoadBalancerIP) { + return true + } + for _, ingress := range svc.Status.LoadBalancer.Ingress { + if rg.isSingleLoadBalancerIP(ingress.IP) { + return true + } + } + return false +} + // isSingleLoadBalancerIP determines if the given IP is in the list of // allowed LoadBalancer CIDRs given in the default bgpconfiguration // and is a single IP entry (/32 for IPV4 or /128 for IPV6) @@ -445,12 +478,7 @@ func addFullIPLength(items []string) []string { // contains returns true if items contains the target. func contains(items []string, target string) bool { - for _, item := range items { - if item == target { - return true - } - } - return false + return slices.Contains(items, target) } // advertiseThisService returns true if this service should be advertised on this node, @@ -479,20 +507,17 @@ func (rg *routeGenerator) advertiseThisService(svc *v1.Service, eps []*discovery // we need to announce single IPs for services of type externalTrafficPolicy Cluster. // There are 2 cases inside this type: // - LoadBalancer with a single IP. - // - Any one of externalIPs in service of type LoadBalancer or NodePort with a single IP. + // - Any one of the externalIPs are a single IP. if svc.Spec.ExternalTrafficPolicy == v1.ServiceExternalTrafficPolicyTypeCluster { - if svc.Spec.Type == v1.ServiceTypeLoadBalancer && rg.isSingleLoadBalancerIP(svc.Spec.LoadBalancerIP) { + if svc.Spec.Type == v1.ServiceTypeLoadBalancer && rg.hasAnySingleLoadBalancerIP(svc) { logc.Debug("Advertising load balancer of type cluster because of single IP definition") return true } - if svc.Spec.Type == v1.ServiceTypeLoadBalancer || svc.Spec.Type == v1.ServiceTypeNodePort { - for _, extIP := range svc.Spec.ExternalIPs { - if rg.isSingleExternalIP(extIP) { - logc.Debug("Advertising external IP of type cluster because of single IP definition") - return true - } - } + // Advertise if there is a fully qualified (i.e., /32 or /128) external IP defined. + if slices.ContainsFunc(svc.Spec.ExternalIPs, rg.isSingleExternalIP) { + logc.Debug("Advertising external IP of type cluster because of single IP definition") + return true } } @@ -503,30 +528,31 @@ func (rg *routeGenerator) advertiseThisService(svc *v1.Service, eps []*discovery return false } - isIPv6 := func(ip string) bool { return strings.Contains(ip, ":") } - - svcIsIPv6 := isIPv6(svc.Spec.ClusterIP) + // Build a lookup table of IP families supported by the Service. + // Example: ["IPv4"], ["IPv6"], or ["IPv4","IPv6"] for dual-stack. + svcIPFamilies := make(map[string]struct{}) + for _, fam := range svc.Spec.IPFamilies { + svcIPFamilies[string(fam)] = struct{}{} + } - // Endpoint-based advertisement logic for both Cluster and Local services. - // For Cluster services: advertise if any endpoints exist (when aggregation is disabled). - // For Local services: advertise only if local endpoints exist. for _, ep := range eps { + // We only consider EndpointSlices whose addressType matches one of the Service’s families. + epFamily := string(ep.AddressType) + // Skip EndpointSlices with incompatible address families. + if _, ok := svcIPFamilies[epFamily]; !ok { + continue + } for _, subset := range ep.Endpoints { // not interested in subset.NotReadyAddresses - for _, address := range subset.Addresses { - if isIPv6(address) != svcIsIPv6 { - continue - } - if svc.Spec.ExternalTrafficPolicy != v1.ServiceExternalTrafficPolicyTypeLocal { - // For Cluster services, advertise if we have any endpoints - logc.Debugf("Advertising cluster service") + if svc.Spec.ExternalTrafficPolicy != v1.ServiceExternalTrafficPolicyTypeLocal { + // For Cluster services, advertise if we have any endpoints + logc.Debugf("Advertising cluster service") + return true + } else { + // For Local services, only advertise if we have local endpoints + if subset.NodeName != nil && *subset.NodeName == rg.nodeName { + logc.Debugf("Advertising local service") return true - } else { - // For Local services, only advertise if we have local endpoints - if subset.NodeName != nil && *subset.NodeName == rg.nodeName { - logc.Debugf("Advertising local service") - return true - } } } } @@ -537,7 +563,7 @@ func (rg *routeGenerator) advertiseThisService(svc *v1.Service, eps []*discovery // unsetRouteForSvc removes the route from the svcClusterRouteMap // but checks to see if it wasn't already deleted by its sibling resource -func (rg *routeGenerator) unsetRouteForSvc(obj interface{}) { +func (rg *routeGenerator) unsetRouteForSvc(obj any) { // generate key key, err := cache.MetaNamespaceKeyFunc(obj) if err != nil { @@ -607,7 +633,7 @@ func (rg *routeGenerator) withdrawRoutesForKey(key string, routes []string) { } // onSvcAdd is called when a k8s service is created -func (rg *routeGenerator) onSvcAdd(obj interface{}) { +func (rg *routeGenerator) onSvcAdd(obj any) { svc, ok := obj.(*v1.Service) if !ok { log.Warn("onSvcAdd: failed to assert type to service, passing") @@ -617,7 +643,7 @@ func (rg *routeGenerator) onSvcAdd(obj interface{}) { } // onSvcUpdate is called when a k8s service is updated -func (rg *routeGenerator) onSvcUpdate(_, obj interface{}) { +func (rg *routeGenerator) onSvcUpdate(_, obj any) { svc, ok := obj.(*v1.Service) if !ok { log.Warn("onSvcUpdate: failed to assert type to service, passing") @@ -627,12 +653,12 @@ func (rg *routeGenerator) onSvcUpdate(_, obj interface{}) { } // onSvcUpdate is called when a k8s service is deleted -func (rg *routeGenerator) onSvcDelete(obj interface{}) { +func (rg *routeGenerator) onSvcDelete(obj any) { rg.unsetRouteForSvc(obj) } // onEPAdd is called when a k8s endpoint is created -func (rg *routeGenerator) onEPAdd(obj interface{}) { +func (rg *routeGenerator) onEPAdd(obj any) { ep, ok := obj.(*discoveryv1.EndpointSlice) if !ok { log.Warn("onEPAdd: failed to assert type to endpoint, passing") @@ -642,7 +668,7 @@ func (rg *routeGenerator) onEPAdd(obj interface{}) { } // onEPUpdate is called when a k8s endpoint is updated -func (rg *routeGenerator) onEPUpdate(_, obj interface{}) { +func (rg *routeGenerator) onEPUpdate(_, obj any) { ep, ok := obj.(*discoveryv1.EndpointSlice) if !ok { log.Warn("onEPUpdate: failed to assert type to endpoints, passing") @@ -652,7 +678,7 @@ func (rg *routeGenerator) onEPUpdate(_, obj interface{}) { } // onEPDelete is called when a k8s endpoint is deleted -func (rg *routeGenerator) onEPDelete(obj interface{}) { +func (rg *routeGenerator) onEPDelete(obj any) { rg.unsetRouteForSvc(obj) } diff --git a/confd/pkg/backends/calico/routes_suite_test.go b/confd/pkg/backends/calico/routes_suite_test.go index 316b578fc8d..234e80c7ae0 100644 --- a/confd/pkg/backends/calico/routes_suite_test.go +++ b/confd/pkg/backends/calico/routes_suite_test.go @@ -3,16 +3,16 @@ package calico import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestCalico(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../report/calico_backend_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Calico Backend Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../report/calico_backend_suite.xml" + ginkgo.RunSpecs(t, "Calico Backend Suite", suiteConfig, reporterConfig) } diff --git a/confd/pkg/backends/calico/routes_test.go b/confd/pkg/backends/calico/routes_test.go index 93c11e769a4..4de5b2757b3 100644 --- a/confd/pkg/backends/calico/routes_test.go +++ b/confd/pkg/backends/calico/routes_test.go @@ -6,7 +6,7 @@ import ( "strings" "sync" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" v1 "k8s.io/api/core/v1" @@ -50,10 +50,12 @@ func buildSimpleService() (svc *v1.Service, ep *discoveryv1.EndpointSlice) { ClusterIPs: []string{"127.0.0.1", "::1"}, ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeLocal, ExternalIPs: []string{externalIP1, externalIP2}, + IPFamilies: []v1.IPFamily{v1.IPv4Protocol}, }, } ep = &discoveryv1.EndpointSlice{ - ObjectMeta: meta, + AddressType: discoveryv1.AddressType(v1.IPv4Protocol), + ObjectMeta: meta, } return } @@ -68,10 +70,12 @@ func buildSimpleService2() (svc *v1.Service, ep *discoveryv1.EndpointSlice) { ClusterIPs: []string{"127.0.0.5", "::5"}, ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeLocal, ExternalIPs: []string{externalIP1, externalIP2}, + IPFamilies: []v1.IPFamily{v1.IPv4Protocol}, }, } ep = &discoveryv1.EndpointSlice{ - ObjectMeta: meta, + AddressType: discoveryv1.AddressType(v1.IPv4Protocol), + ObjectMeta: meta, } return } @@ -86,6 +90,7 @@ func buildSimpleService3() (svc *v1.Service, ep *discoveryv1.EndpointSlice) { ClusterIPs: []string{"127.0.0.10", "::a"}, ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeLocal, LoadBalancerIP: loadBalancerIP1, + IPFamilies: []v1.IPFamily{v1.IPv4Protocol}, }, Status: v1.ServiceStatus{ LoadBalancer: v1.LoadBalancerStatus{ @@ -94,7 +99,8 @@ func buildSimpleService3() (svc *v1.Service, ep *discoveryv1.EndpointSlice) { }, } ep = &discoveryv1.EndpointSlice{ - ObjectMeta: meta, + AddressType: discoveryv1.AddressType(v1.IPv4Protocol), + ObjectMeta: meta, } return } @@ -109,10 +115,12 @@ func buildSimpleService4() (svc *v1.Service, ep *discoveryv1.EndpointSlice) { ClusterIPs: []string{"127.0.0.11", "::b"}, ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeLocal, ExternalIPs: []string{externalIP3}, + IPFamilies: []v1.IPFamily{v1.IPv4Protocol}, }, } ep = &discoveryv1.EndpointSlice{ - ObjectMeta: meta, + AddressType: discoveryv1.AddressType(v1.IPv4Protocol), + ObjectMeta: meta, } return } @@ -139,7 +147,7 @@ var _ = Describe("RouteGenerator", func() { rg = &routeGenerator{ nodeName: "foobar", svcIndexer: cache.NewIndexer(cache.MetaNamespaceKeyFunc, nil), - epIndexer: cache.NewIndexer(cache.MetaNamespaceKeyFunc, nil), + epIndexer: cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{endpointSliceServiceIndex: endpointSliceServiceIndexFunc}), svcRouteMap: make(map[string]map[string]bool), routeAdvertisementRefCount: make(map[string]int), client: &client{ @@ -317,6 +325,7 @@ var _ = Describe("RouteGenerator", func() { // Add the endpoint back with an IPv6 address. The service's cluster IPs // should remain non-advertised. + ep.AddressType = discoveryv1.AddressType(v1.IPv6Protocol) ep.Endpoints = []discoveryv1.Endpoint{{ Addresses: []string{"fd5f:1234::3"}, NodeName: &rg.nodeName, @@ -336,6 +345,7 @@ var _ = Describe("RouteGenerator", func() { // Add the endpoint again with an IPv4 address. The service's cluster IPs // should now be advertised. + ep.AddressType = discoveryv1.AddressType(v1.IPv4Protocol) ep.Endpoints = []discoveryv1.Endpoint{{ Addresses: []string{"10.96.0.45"}, NodeName: &rg.nodeName, @@ -697,6 +707,68 @@ var _ = Describe("RouteGenerator", func() { Expect(rg.client.programmedRouteRefCount).NotTo(HaveKey(key)) }) + // This test reproduces the CI-1944 bug: when serviceLoadBalancerIPs has a /32 entry + // and the LB IP is assigned from an IPPool (so spec.loadBalancerIP is empty, IP only in + // status.loadBalancer.ingress), services with externalTrafficPolicy=Cluster should still + // be advertised per-service, since /32s are excluded from global aggregation. + It("should handle /32 routes for LoadBalancerIPs assigned from IPPool (status only, no spec.loadBalancerIP)", func() { + // Use a fresh LB IP that is not used by any other test service. + lbIP := "192.168.1.50" + key := "/calico/staticroutes/" + lbIP + "-32" + + // Build a LoadBalancer service with Cluster traffic policy and NO spec.loadBalancerIP. + // The IP appears only in status.loadBalancer.ingress, as is typical for IPPool allocation. + lbSvcMeta := metav1.ObjectMeta{Namespace: "foo", Name: "lb-cluster", Labels: map[string]string{"kubernetes.io/service-name": "lb-cluster"}} + lbSvc := &v1.Service{ + ObjectMeta: lbSvcMeta, + Spec: v1.ServiceSpec{ + Type: v1.ServiceTypeLoadBalancer, + ClusterIP: "127.0.0.20", + ClusterIPs: []string{"127.0.0.20"}, + ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + IPFamilies: []v1.IPFamily{v1.IPv4Protocol}, + // LoadBalancerIP intentionally NOT set. + }, + Status: v1.ServiceStatus{ + LoadBalancer: v1.LoadBalancerStatus{ + Ingress: []v1.LoadBalancerIngress{{IP: lbIP}}, + }, + }, + } + lbEp := &discoveryv1.EndpointSlice{ + AddressType: discoveryv1.AddressType(v1.IPv4Protocol), + ObjectMeta: lbSvcMeta, + } + addEndpointSubset(lbEp, rg.nodeName, "1.1.1.1") + + // Configure a /32 serviceLoadBalancerIPs entry matching the service's LB IP. + lbIPRange := fmt.Sprintf("%s/32", lbIP) + By("onLoadBalancerIPsUpdate to include /32 route") + rg.client.onLoadBalancerIPsUpdate([]string{lbIPRange}) + + // No routes should be advertised yet (no service registered). + rg.resyncKnownRoutes() + Expect(rg.client.cache[key]).To(Equal("")) + + // Add the service and endpoint. + err := rg.epIndexer.Add(lbEp) + Expect(err).NotTo(HaveOccurred()) + err = rg.svcIndexer.Add(lbSvc) + Expect(err).NotTo(HaveOccurred()) + + // The /32 route should now be advertised from the per-service path. + By("Resyncing routes after adding service") + rg.resyncKnownRoutes() + Expect(rg.client.cache[key]).To(Equal(lbIP + "/32")) + Expect(rg.client.programmedRouteRefCount[key]).To(Equal(1)) + + // Remove BGPConfiguration and verify route is withdrawn. + rg.client.onLoadBalancerIPsUpdate([]string{}) + rg.resyncKnownRoutes() + Expect(rg.client.cache).NotTo(HaveKey(key)) + Expect(rg.client.programmedRouteRefCount).NotTo(HaveKey(key)) + }) + // This test simulates a situation where BGPConfiguration has a /32 route that exactly matches // externalIP of a LoadBalancer service with ExternalTrafficPolicy set to Local. The route should only be advertised // when the Service is created, and not when the BGPConfiguration is created. @@ -822,6 +894,81 @@ var _ = Describe("Service Load Balancer Aggregation", func() { Expect(result).To(BeFalse()) }) + It("should advertise Cluster services with /32 LB IP from status.loadBalancer.ingress when aggregation is enabled", func() { + // This reproduces the CI-1944 bug: when serviceLoadBalancerIPs has a /32 entry + // and the LB IP is assigned via IPPool (so svc.Spec.LoadBalancerIP is empty, + // IP only in svc.Status.LoadBalancer.Ingress), the service should still be + // advertised per-service since /32s are excluded from global aggregation. + _, lbNet, _ := net.ParseCIDR("192.168.1.0/32") + mockClient.loadBalancerIPs = []string{"192.168.1.0/32"} + mockClient.loadBalancerIPNets = []*net.IPNet{lbNet} + + svc := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "test-svc", Namespace: "default"}, + Spec: v1.ServiceSpec{ + Type: v1.ServiceTypeLoadBalancer, + ClusterIP: "10.0.0.1", + ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + IPFamilies: []v1.IPFamily{v1.IPv4Protocol}, + // Note: LoadBalancerIP is NOT set (empty), as is typical when + // IPs are assigned from a Calico IPPool. + }, + Status: v1.ServiceStatus{ + LoadBalancer: v1.LoadBalancerStatus{ + Ingress: []v1.LoadBalancerIngress{{IP: "192.168.1.0"}}, + }, + }, + } + ep := &discoveryv1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{Name: "test-svc", Namespace: "default"}, + AddressType: discoveryv1.AddressType(v1.IPv4Protocol), + Endpoints: []discoveryv1.Endpoint{ + { + Addresses: []string{"10.0.0.2"}, + NodeName: &rg.nodeName, + }, + }, + } + + result := rg.advertiseThisService(svc, []*discoveryv1.EndpointSlice{ep}) + Expect(result).To(BeTrue()) + }) + + It("should advertise Cluster services with /128 LB IP from status.loadBalancer.ingress when aggregation is enabled", func() { + // IPv6 equivalent of the CI-1944 bug. + _, lbNet, _ := net.ParseCIDR("fd00::1/128") + mockClient.loadBalancerIPs = []string{"fd00::1/128"} + mockClient.loadBalancerIPNets = []*net.IPNet{lbNet} + + svc := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "test-svc", Namespace: "default"}, + Spec: v1.ServiceSpec{ + Type: v1.ServiceTypeLoadBalancer, + ClusterIP: "fd00::1", + ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + IPFamilies: []v1.IPFamily{v1.IPv6Protocol}, + }, + Status: v1.ServiceStatus{ + LoadBalancer: v1.LoadBalancerStatus{ + Ingress: []v1.LoadBalancerIngress{{IP: "fd00::1"}}, + }, + }, + } + ep := &discoveryv1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{Name: "test-svc", Namespace: "default"}, + AddressType: discoveryv1.AddressType(v1.IPv6Protocol), + Endpoints: []discoveryv1.Endpoint{ + { + Addresses: []string{"fd00::2"}, + NodeName: &rg.nodeName, + }, + }, + } + + result := rg.advertiseThisService(svc, []*discoveryv1.EndpointSlice{ep}) + Expect(result).To(BeTrue()) + }) + It("should still advertise Local services when aggregation is enabled", func() { svc := &v1.Service{ ObjectMeta: metav1.ObjectMeta{Name: "test-svc", Namespace: "default"}, @@ -829,10 +976,12 @@ var _ = Describe("Service Load Balancer Aggregation", func() { Type: v1.ServiceTypeLoadBalancer, ClusterIP: "10.0.0.1", ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeLocal, + IPFamilies: []v1.IPFamily{v1.IPv4Protocol}, }, } ep := &discoveryv1.EndpointSlice{ - ObjectMeta: metav1.ObjectMeta{Name: "test-svc", Namespace: "default"}, + ObjectMeta: metav1.ObjectMeta{Name: "test-svc", Namespace: "default"}, + AddressType: discoveryv1.AddressType(v1.IPv4Protocol), Endpoints: []discoveryv1.Endpoint{ { Addresses: []string{"10.0.0.2"}, @@ -858,10 +1007,12 @@ var _ = Describe("Service Load Balancer Aggregation", func() { Type: v1.ServiceTypeLoadBalancer, ClusterIP: "10.0.0.1", ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + IPFamilies: []v1.IPFamily{v1.IPv4Protocol}, }, } ep := &discoveryv1.EndpointSlice{ - ObjectMeta: metav1.ObjectMeta{Name: "test-svc", Namespace: "default", Labels: map[string]string{"kubernetes.io/service-name": "test-svc"}}, + ObjectMeta: metav1.ObjectMeta{Name: "test-svc", Namespace: "default", Labels: map[string]string{"kubernetes.io/service-name": "test-svc"}}, + AddressType: discoveryv1.AddressType(v1.IPv4Protocol), Endpoints: []discoveryv1.Endpoint{ { Addresses: []string{"10.0.0.2"}, @@ -880,11 +1031,13 @@ var _ = Describe("Service Load Balancer Aggregation", func() { Type: v1.ServiceTypeLoadBalancer, ClusterIP: "10.0.0.1", ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + IPFamilies: []v1.IPFamily{v1.IPv4Protocol}, }, } ep := &discoveryv1.EndpointSlice{ - ObjectMeta: metav1.ObjectMeta{Name: "test-svc", Namespace: "default", Labels: map[string]string{"kubernetes.io/service-name": "test-svc"}}, - Endpoints: []discoveryv1.Endpoint{}, + ObjectMeta: metav1.ObjectMeta{Name: "test-svc", Namespace: "default", Labels: map[string]string{"kubernetes.io/service-name": "test-svc"}}, + AddressType: discoveryv1.AddressType(v1.IPv4Protocol), + Endpoints: []discoveryv1.Endpoint{}, } result := rg.advertiseThisService(svc, []*discoveryv1.EndpointSlice{ep}) @@ -904,10 +1057,12 @@ var _ = Describe("Service Load Balancer Aggregation", func() { Type: v1.ServiceTypeLoadBalancer, ClusterIP: "10.0.0.1", // IPv4 ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + IPFamilies: []v1.IPFamily{v1.IPv4Protocol}, }, } ep := &discoveryv1.EndpointSlice{ - ObjectMeta: metav1.ObjectMeta{Name: "test-svc", Namespace: "default", Labels: map[string]string{"kubernetes.io/service-name": "test-svc"}}, + ObjectMeta: metav1.ObjectMeta{Name: "test-svc", Namespace: "default", Labels: map[string]string{"kubernetes.io/service-name": "test-svc"}}, + AddressType: discoveryv1.AddressType(v1.IPv6Protocol), Endpoints: []discoveryv1.Endpoint{ { Addresses: []string{"2001:db8::1"}, // IPv6 @@ -926,6 +1081,65 @@ var _ = Describe("Service Load Balancer Aggregation", func() { Type: v1.ServiceTypeLoadBalancer, ClusterIP: "2001:db8::1", // IPv6 ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + IPFamilies: []v1.IPFamily{v1.IPv6Protocol}, + }, + } + ep := &discoveryv1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-svc", + Namespace: "default", + Labels: map[string]string{"kubernetes.io/service-name": "test-svc"}, + }, + AddressType: discoveryv1.AddressType(v1.IPv6Protocol), + Endpoints: []discoveryv1.Endpoint{ + { + Addresses: []string{"2001:db8::2"}, // IPv6 + }, + }, + } + + result := rg.advertiseThisService(svc, []*discoveryv1.EndpointSlice{ep}) + Expect(result).To(BeTrue()) + }) + + It("should advertise dual-stuck service when get IPv4 endpointSlice", func() { + svc := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "test-svc", Namespace: "default"}, + Spec: v1.ServiceSpec{ + Type: v1.ServiceTypeLoadBalancer, + ClusterIP: "2001:db8::1", + ClusterIPs: []string{"2001:db8::1", "1.1.1.1"}, + ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + IPFamilies: []v1.IPFamily{v1.IPv6Protocol, v1.IPv4Protocol}, + }, + } + ep := &discoveryv1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-svc", + Namespace: "default", + Labels: map[string]string{"kubernetes.io/service-name": "test-svc"}, + }, + AddressType: discoveryv1.AddressType(v1.IPv4Protocol), + Endpoints: []discoveryv1.Endpoint{ + { + Addresses: []string{"10.10.10.10"}, // IPv6 + }, + }, + } + + result := rg.advertiseThisService(svc, []*discoveryv1.EndpointSlice{ep}) + Expect(result).To(BeTrue()) + }) + + It("should advertise dual-stuck service when get IPv6 endpointSlice", func() { + svc := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "test-svc", Namespace: "default"}, + Spec: v1.ServiceSpec{ + Type: v1.ServiceTypeLoadBalancer, + ClusterIP: "2001:db8::1", + ClusterIPs: []string{"2001:db8::1", "1.1.1.1"}, + ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + IPFamilies: []v1.IPFamily{v1.IPv6Protocol, v1.IPv4Protocol}, }, } ep := &discoveryv1.EndpointSlice{ @@ -934,6 +1148,7 @@ var _ = Describe("Service Load Balancer Aggregation", func() { Namespace: "default", Labels: map[string]string{"kubernetes.io/service-name": "test-svc"}, }, + AddressType: discoveryv1.AddressType(v1.IPv6Protocol), Endpoints: []discoveryv1.Endpoint{ { Addresses: []string{"2001:db8::2"}, // IPv6 diff --git a/confd/pkg/backends/calico/secret_watcher.go b/confd/pkg/backends/calico/secret_watcher.go index bb0864fcfc6..9b41b25886a 100644 --- a/confd/pkg/backends/calico/secret_watcher.go +++ b/confd/pkg/backends/calico/secret_watcher.go @@ -174,19 +174,19 @@ func (sw *secretWatcher) SweepStale() { } } -func (sw *secretWatcher) OnAdd(obj interface{}, isInInitialList bool) { +func (sw *secretWatcher) OnAdd(obj any, isInInitialList bool) { log.Debug("Secret added") sw.updateSecret(obj.(*v1.Secret)) sw.client.recheckPeerConfig("secret added") } -func (sw *secretWatcher) OnUpdate(oldObj, newObj interface{}) { +func (sw *secretWatcher) OnUpdate(oldObj, newObj any) { log.Debug("Secret updated") sw.updateSecret(newObj.(*v1.Secret)) sw.client.recheckPeerConfig("secret updated") } -func (sw *secretWatcher) OnDelete(obj interface{}) { +func (sw *secretWatcher) OnDelete(obj any) { log.Debug("Secret deleted") sw.deleteSecret(obj.(*v1.Secret)) sw.client.recheckPeerConfig("secret deleted") diff --git a/confd/pkg/backends/client.go b/confd/pkg/backends/client.go index bc7170f9f0d..e3add6429e0 100644 --- a/confd/pkg/backends/client.go +++ b/confd/pkg/backends/client.go @@ -1,5 +1,9 @@ package backends +import ( + "github.com/projectcalico/calico/confd/pkg/backends/types" +) + // The StoreClient interface is implemented by objects that can retrieve // key/value pairs from a backend store. type StoreClient interface { @@ -7,4 +11,5 @@ type StoreClient interface { GetValues(keys []string) (map[string]string, error) WatchPrefix(prefix string, keys []string, waitIndex uint64, stopChan chan bool) (string, error) GetCurrentRevision() uint64 + GetBirdBGPConfig(ipVersion int) (*types.BirdBGPConfig, error) } diff --git a/confd/pkg/backends/types/bird_bgp_config.go b/confd/pkg/backends/types/bird_bgp_config.go new file mode 100644 index 00000000000..572704b499e --- /dev/null +++ b/confd/pkg/backends/types/bird_bgp_config.go @@ -0,0 +1,66 @@ +// Copyright (c) 2025-2026 Tigera, Inc. All rights reserved. +// +// 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 types + +// BirdBGPConfig represents the processed BGP configuration for templates +type BirdBGPConfig struct { + NodeName string + NodeIP string + NodeIPv6 string + ASNumber string + RouterID string + Peers []BirdBGPPeer + Filters map[string]string + Communities []CommunityRule + LogLevel string + DebugMode string // "all", "{ states }", or "" (none) + ListenAddress string + ListenPort string + DirectInterfaces string // Complete interface pattern string for protocol direct + + BGPExportFilterForDisabledIPPools []string + BGPExportFilterForEnabledIPPools []string + KernelFilterForIPPools []string +} + +// BirdBGPPeer represents a processed BGP peer configuration +type BirdBGPPeer struct { + Name string + IP string + Port string + ASNumber string + LocalASNumber string + Type string // "mesh", "global", "nodeLocal", "globalLocal" + ImportFilter string + ExportFilter string + Password string + TTLSecurity string // "on" with multihop value, or "off" + RouteReflector bool + RRClusterID string + SourceAddr string + NextHopSelf bool + NextHopKeep bool + AddPaths string + Passive bool + GracefulRestart string // restart time value + KeepaliveTime string + NumAllowLocalAs string +} + +// CommunityRule represents BGP community application rules +type CommunityRule struct { + CIDR string + AddStatements []string // Pre-formatted BIRD add statements +} diff --git a/confd/pkg/resource/template/processor.go b/confd/pkg/resource/template/processor.go index 24928674b52..cd7336a0429 100644 --- a/confd/pkg/resource/template/processor.go +++ b/confd/pkg/resource/template/processor.go @@ -88,7 +88,6 @@ func (p *watchProcessor) Process() { // Start the individual watchers for each template. for _, t := range ts { - t := t p.wg.Add(1) go p.monitorPrefix(t) } diff --git a/confd/pkg/resource/template/resource.go b/confd/pkg/resource/template/resource.go index 31d17b17166..c5eef41456f 100644 --- a/confd/pkg/resource/template/resource.go +++ b/confd/pkg/resource/template/resource.go @@ -50,7 +50,7 @@ type TemplateResource struct { StageFile *os.File Uid int ExpandedKeys []string - funcMap map[string]interface{} + funcMap map[string]any keepStageFile bool noop bool store memkv.Store @@ -86,6 +86,9 @@ func NewTemplateResource(path string, config Config) (*TemplateResource, error) tr.syncOnly = config.SyncOnly addFuncs(tr.funcMap, tr.store.FuncMap) + // Add Calico-specific functions. + addCalicoFuncs(tr.funcMap) + if runtime.GOOS == "windows" { // On Windows HPC containers, $PATH does not contain the directory with 'powershell.exe'. Add it so that the powershell command works. _, err = exec.LookPath("powershell.exe") @@ -178,7 +181,7 @@ func (t *TemplateResource) createStageFile() error { return err } - if err = tmpl.Execute(temp, nil); err != nil { + if err = tmpl.Execute(temp, t.storeClient); err != nil { // The key error to return is the failure to execute. // to preserve that error ignore the errors in close and clean temp.Close() // nolint:errcheck diff --git a/confd/pkg/resource/template/template_funcs.go b/confd/pkg/resource/template/template_funcs.go index d254af60d55..0e742c45ca9 100644 --- a/confd/pkg/resource/template/template_funcs.go +++ b/confd/pkg/resource/template/template_funcs.go @@ -1,32 +1,31 @@ package template import ( - "crypto/sha256" "encoding/base64" "encoding/json" "errors" "fmt" + "maps" "net" - "os" "path" "sort" - "strconv" "strings" "time" "github.com/kelseyhightower/memkv" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + + "github.com/projectcalico/calico/confd/pkg/backends" ) -func newFuncMap() map[string]interface{} { - m := make(map[string]interface{}) +func newFuncMap() map[string]any { + m := make(map[string]any) m["base"] = path.Base m["split"] = strings.Split m["json"] = UnmarshalJsonObject m["jsonArray"] = UnmarshalJsonArray m["dir"] = path.Dir m["map"] = CreateMap - m["getenv"] = Getenv m["join"] = strings.Join m["datetime"] = time.Now m["toUpper"] = strings.ToUpper @@ -39,15 +38,27 @@ func newFuncMap() map[string]interface{} { m["fileExists"] = isFileExist m["base64Encode"] = Base64Encode m["base64Decode"] = Base64Decode - m["hashToIPv4"] = hashToIPv4 - m["bgpFilterFunctionName"] = BGPFilterFunctionName m["bgpFilterBIRDFuncs"] = BGPFilterBIRDFuncs return m } -func addFuncs(out, in map[string]interface{}) { - for name, fn := range in { - out[name] = fn +func addFuncs(out, in map[string]any) { + maps.Copy(out, in) +} + +// addCalicoFuncs adds Calico-specific template functions +func addCalicoFuncs(funcMap map[string]any) { + // Add getBGPConfig function that takes the ipVersion and client as parameters + funcMap["getBGPConfig"] = func(ipVersion int, client any) (any, error) { + if storeClient, ok := client.(backends.StoreClient); ok { + config, err := storeClient.GetBirdBGPConfig(ipVersion) + if err != nil { + // Return error to fail template execution and prevent broken config + return nil, err + } + return config, nil + } + return nil, errors.New("client does not support GetBirdBGPConfig") } } @@ -183,7 +194,7 @@ func BGPFilterFunctionName(filterName, direction, version string) (string, error } pieces := []string{"bgp_", "", "_", normalizedDirection, "FilterV", version} maxBIRDSymLen := 64 - resizedName, err := truncateAndHashName(filterName, maxBIRDSymLen-len(strings.Join(pieces, ""))) + resizedName, err := TruncateAndHashName(filterName, maxBIRDSymLen-len(strings.Join(pieces, ""))) if err != nil { return "", err } @@ -389,77 +400,13 @@ func BGPFilterBIRDFuncs(pairs memkv.KVPairs, version int) ([]string, error) { return lines, nil } -// The maximum length of a k8s resource (253 bytes) is longer than the maximum length of BIRD symbols (64 chars). -// This function provides a way to map the k8s resource name to a BIRD symbol name that accounts -// for the length difference in a way that minimizes the chance of collisions -func truncateAndHashName(name string, maxLen int) (string, error) { - if len(name) <= maxLen { - return name, nil - } - // SHA256 outputs a hash 64 chars long but we'll use only the first 16 - hashCharsToUse := 16 - // Account for underscore we insert between truncated name and hash string - hashStrSize := hashCharsToUse + 1 - if maxLen <= hashStrSize { - return "", fmt.Errorf("max truncated string length must be greater than the minimum size of %d", - hashStrSize) - } - hash := sha256.New() - _, err := hash.Write([]byte(name)) - if err != nil { - return "", err - } - truncationLen := maxLen - hashStrSize - hashStr := fmt.Sprintf("%X", hash.Sum(nil)) - truncatedName := fmt.Sprintf("%s_%s", name[:truncationLen], hashStr[:hashCharsToUse]) - return truncatedName, nil -} - -// hashToIPv4 hashes the given string and -// formats the resulting 4 bytes as an IPv4 address. -func hashToIPv4(nodeName string) string { - hash := sha256.New() - _, err := hash.Write([]byte(nodeName)) - if err != nil { - return "" - } - hashBytes := hash.Sum(nil) - ip := hashBytes[:4] - //BGP doesn't allow router IDs in special IP ranges (e.g., 224.x.x.x) - ip0Value := int(ip[0]) - if ip0Value > 223 { - ip0Value = ip0Value - 32 - } - routerId := strconv.Itoa(ip0Value) + "." + - strconv.Itoa(int(ip[1])) + "." + - strconv.Itoa(int(ip[2])) + "." + - strconv.Itoa(int(ip[3])) - return routerId -} - -// Getenv retrieves the value of the environment variable named by the key. -// It returns the value, which will the default value if the variable is not present. -// If no default value was given - returns "". -func Getenv(key string, v ...string) string { - defaultValue := "" - if len(v) > 0 { - defaultValue = v[0] - } - - value := os.Getenv(key) - if value == "" { - return defaultValue - } - return value -} - // CreateMap creates a key-value map of string -> interface{} // The i'th is the key and the i+1 is the value -func CreateMap(values ...interface{}) (map[string]interface{}, error) { +func CreateMap(values ...any) (map[string]any, error) { if len(values)%2 != 0 { return nil, errors.New("invalid map call") } - dict := make(map[string]interface{}, len(values)/2) + dict := make(map[string]any, len(values)/2) for i := 0; i < len(values); i += 2 { key, ok := values[i].(string) if !ok { @@ -470,14 +417,14 @@ func CreateMap(values ...interface{}) (map[string]interface{}, error) { return dict, nil } -func UnmarshalJsonObject(data string) (map[string]interface{}, error) { - var ret map[string]interface{} +func UnmarshalJsonObject(data string) (map[string]any, error) { + var ret map[string]any err := json.Unmarshal([]byte(data), &ret) return ret, err } -func UnmarshalJsonArray(data string) ([]interface{}, error) { - var ret []interface{} +func UnmarshalJsonArray(data string) ([]any, error) { + var ret []any err := json.Unmarshal([]byte(data), &ret) return ret, err } diff --git a/confd/pkg/resource/template/template_funcs_test.go b/confd/pkg/resource/template/template_funcs_test.go index 67b39e06e5d..a783b010129 100644 --- a/confd/pkg/resource/template/template_funcs_test.go +++ b/confd/pkg/resource/template/template_funcs_test.go @@ -12,18 +12,24 @@ import ( func Test_hashToIPv4_invalid_range(t *testing.T) { expectedRouterId := "207.94.5.27" nodeName := "Testrobin123" - actualRouterId := hashToIPv4(nodeName) //invalid router_id 239.94.5.27 + actualRouterId, err := HashToIPv4(nodeName) //invalid router_id 239.94.5.27 + if err != nil { + t.Fatalf(`HashToIPv4(%s) returned unexpected error: %v`, nodeName, err) + } if expectedRouterId != actualRouterId { - t.Errorf(`hashToIPv4(%s) = %s, want %s`, nodeName, actualRouterId, expectedRouterId) + t.Errorf(`HashToIPv4(%s) = %s, want %s`, nodeName, actualRouterId, expectedRouterId) } } func Test_hashToIPv4_valid_range(t *testing.T) { expectedRouterId := "109.174.215.226" nodeName := "nodeTest" - actualRouterId := hashToIPv4(nodeName) //invalid router_id 239.94.5.27 + actualRouterId, err := HashToIPv4(nodeName) //invalid router_id 239.94.5.27 + if err != nil { + t.Fatalf(`HashToIPv4(%s) returned unexpected error: %v`, nodeName, err) + } if expectedRouterId != actualRouterId { - t.Errorf(`hashToIPv4(%s) = %s, want %s`, nodeName, actualRouterId, expectedRouterId) + t.Errorf(`HashToIPv4(%s) = %s, want %s`, nodeName, actualRouterId, expectedRouterId) } } @@ -188,14 +194,20 @@ func Test_BGPFilterBIRDFuncs(t *testing.T) { func Test_ValidateHashToIpv4Method(t *testing.T) { expectedRouterId := "207.94.5.27" nodeName := "Testrobin123" - actualRouterId := hashToIPv4(nodeName) + actualRouterId, err := HashToIPv4(nodeName) + if err != nil { + t.Fatalf("HashToIPv4(%s) returned unexpected error: %v", nodeName, err) + } if expectedRouterId != actualRouterId { t.Errorf("Expected %s to equal %s", expectedRouterId, actualRouterId) } expectedRouterId = "109.174.215.226" nodeName = "nodeTest" - actualRouterId = hashToIPv4(nodeName) + actualRouterId, err = HashToIPv4(nodeName) + if err != nil { + t.Fatalf("HashToIPv4(%s) returned unexpected error: %v", nodeName, err) + } if expectedRouterId != actualRouterId { t.Errorf("Expected %s to equal %s", expectedRouterId, actualRouterId) } diff --git a/confd/pkg/resource/template/template_suite_test.go b/confd/pkg/resource/template/template_suite_test.go index 831cbd7d464..53c4b6915f5 100644 --- a/confd/pkg/resource/template/template_suite_test.go +++ b/confd/pkg/resource/template/template_suite_test.go @@ -3,16 +3,16 @@ package template import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestTemplate(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../report/template_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Template Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../report/template_suite.xml" + ginkgo.RunSpecs(t, "Template Suite", suiteConfig, reporterConfig) } diff --git a/confd/pkg/resource/template/util.go b/confd/pkg/resource/template/util.go index 4f8a91a3dfe..2832b209e3b 100644 --- a/confd/pkg/resource/template/util.go +++ b/confd/pkg/resource/template/util.go @@ -1,10 +1,12 @@ package template import ( + "crypto/sha256" "fmt" "os" "path" "path/filepath" + "strconv" "strings" log "github.com/sirupsen/logrus" @@ -87,3 +89,51 @@ func recursiveFindFiles(root string, pattern string) ([]string, error) { } return files, filepath.Walk(root, findfile) } + +// TruncateAndHashName truncates a name to maxLen characters, appending a hash suffix if truncation is needed. +// The maximum length of a k8s resource (253 bytes) is longer than the maximum length of BIRD symbols (64 chars). +// This function provides a way to map the k8s resource name to a BIRD symbol name that accounts +// for the length difference in a way that minimizes the chance of collisions. +func TruncateAndHashName(name string, maxLen int) (string, error) { + if len(name) <= maxLen { + return name, nil + } + // SHA256 outputs a hash 64 chars long but we'll use only the first 16 + hashCharsToUse := 16 + // Account for underscore we insert between truncated name and hash string + hashStrSize := hashCharsToUse + 1 + if maxLen <= hashStrSize { + return "", fmt.Errorf("max truncated string length must be greater than the minimum size of %d", + hashStrSize) + } + hash := sha256.New() + _, err := hash.Write([]byte(name)) + if err != nil { + return "", err + } + truncationLen := maxLen - hashStrSize + hashStr := fmt.Sprintf("%X", hash.Sum(nil)) + truncatedName := fmt.Sprintf("%s_%s", name[:truncationLen], hashStr[:hashCharsToUse]) + return truncatedName, nil +} + +// HashToIPv4 hashes the given string and formats the resulting 4 bytes as an IPv4 address. +func HashToIPv4(nodeName string) (string, error) { + hash := sha256.New() + _, err := hash.Write([]byte(nodeName)) + if err != nil { + return "", err + } + hashBytes := hash.Sum(nil) + ip := hashBytes[:4] + // BGP doesn't allow router IDs in special IP ranges (e.g., 224.x.x.x) + ip0Value := int(ip[0]) + if ip0Value > 223 { + ip0Value = ip0Value - 32 + } + routerId := strconv.Itoa(ip0Value) + "." + + strconv.Itoa(int(ip[1])) + "." + + strconv.Itoa(int(ip[2])) + "." + + strconv.Itoa(int(ip[3])) + return routerId, nil +} diff --git a/confd/tests/compiled_templates/bgpfilter/export_only/explicit_peer/bird.cfg b/confd/tests/compiled_templates/bgpfilter/export_only/explicit_peer/bird.cfg index 06dd4ae4e31..566e4c9d823 100644 --- a/confd/tests/compiled_templates/bgpfilter/export_only/explicit_peer/bird.cfg +++ b/confd/tests/compiled_templates/bgpfilter/export_only/explicit_peer/bird.cfg @@ -72,23 +72,7 @@ function 'bgp_export-only-filter-2_exportFilterV4'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Node_10_192_0_3 from bgp_template { ttl security off; multihop; @@ -103,9 +87,6 @@ protocol bgp Node_10_192_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.4 protocol bgp Node_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -122,4 +103,3 @@ protocol bgp Node_10_192_0_4 from bgp_template { } - diff --git a/confd/tests/compiled_templates/bgpfilter/export_only/explicit_peer/bird6.cfg b/confd/tests/compiled_templates/bgpfilter/export_only/explicit_peer/bird6.cfg index ccee4c81cab..15c29057b00 100644 --- a/confd/tests/compiled_templates/bgpfilter/export_only/explicit_peer/bird6.cfg +++ b/confd/tests/compiled_templates/bgpfilter/export_only/explicit_peer/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -72,23 +71,7 @@ function 'bgp_export-only-filter-2_exportFilterV6'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))&&((defined(ifname))&&(ifname ~ "*.calico"))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v6/2001::103 +# BGP Protocol Configurations protocol bgp Node_2001__103 from bgp_template { ttl security off; multihop; @@ -103,9 +86,6 @@ protocol bgp Node_2001__103 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v6/2001::104 protocol bgp Node_2001__104 from bgp_template { ttl security off; multihop; @@ -122,4 +102,3 @@ protocol bgp Node_2001__104 from bgp_template { } - diff --git a/confd/tests/compiled_templates/bgpfilter/export_only/explicit_peer/bird6_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/export_only/explicit_peer/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/bgpfilter/export_only/explicit_peer/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/export_only/explicit_peer/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/export_only/explicit_peer/bird_aggr.cfg b/confd/tests/compiled_templates/bgpfilter/export_only/explicit_peer/bird_aggr.cfg index 5a623513074..9d86b3b0205 100644 --- a/confd/tests/compiled_templates/bgpfilter/export_only/explicit_peer/bird_aggr.cfg +++ b/confd/tests/compiled_templates/bgpfilter/export_only/explicit_peer/bird_aggr.cfg @@ -8,7 +8,6 @@ protocol static { route 192.168.221.64/26 blackhole; } - # Aggregation of routes on this host; export the block, nothing beneath it. function calico_aggr () { diff --git a/confd/tests/compiled_templates/bgpfilter/export_only/explicit_peer/bird_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/export_only/explicit_peer/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/bgpfilter/export_only/explicit_peer/bird_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/export_only/explicit_peer/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/export_only/global_peer/bird.cfg b/confd/tests/compiled_templates/bgpfilter/export_only/global_peer/bird.cfg index 0b2caa61286..c151a44e2e7 100644 --- a/confd/tests/compiled_templates/bgpfilter/export_only/global_peer/bird.cfg +++ b/confd/tests/compiled_templates/bgpfilter/export_only/global_peer/bird.cfg @@ -65,22 +65,7 @@ function 'bgp_export-only-filter_exportFilterV4'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))) then { accept; } if (((defined(ifname))&&(ifname ~ "*.calico"))) then { reject; } } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v4/10.192.0.2 -# Skipping ourselves (10.192.0.2) - - -# For peer /bgp/v1/global/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Global_10_192_0_3 from bgp_template { ttl security off; multihop; @@ -95,9 +80,6 @@ protocol bgp Global_10_192_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v4/10.192.0.4 protocol bgp Global_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -114,9 +96,3 @@ protocol bgp Global_10_192_0_4 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/export_only/global_peer/bird6.cfg b/confd/tests/compiled_templates/bgpfilter/export_only/global_peer/bird6.cfg index b82eb03e4d0..2e9d4c963a0 100644 --- a/confd/tests/compiled_templates/bgpfilter/export_only/global_peer/bird6.cfg +++ b/confd/tests/compiled_templates/bgpfilter/export_only/global_peer/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -65,22 +64,7 @@ function 'bgp_export-only-filter_exportFilterV6'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v6/2001::102 -# Skipping ourselves (2001::102) - - -# For peer /bgp/v1/global/peer_v6/2001::103 +# BGP Protocol Configurations protocol bgp Global_2001__103 from bgp_template { ttl security off; multihop; @@ -95,9 +79,6 @@ protocol bgp Global_2001__103 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v6/2001::104 protocol bgp Global_2001__104 from bgp_template { ttl security off; multihop; @@ -114,9 +95,3 @@ protocol bgp Global_2001__104 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/export_only/global_peer/bird6_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/export_only/global_peer/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/bgpfilter/export_only/global_peer/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/export_only/global_peer/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/export_only/global_peer/bird_aggr.cfg b/confd/tests/compiled_templates/bgpfilter/export_only/global_peer/bird_aggr.cfg index 5a623513074..9d86b3b0205 100644 --- a/confd/tests/compiled_templates/bgpfilter/export_only/global_peer/bird_aggr.cfg +++ b/confd/tests/compiled_templates/bgpfilter/export_only/global_peer/bird_aggr.cfg @@ -8,7 +8,6 @@ protocol static { route 192.168.221.64/26 blackhole; } - # Aggregation of routes on this host; export the block, nothing beneath it. function calico_aggr () { diff --git a/confd/tests/compiled_templates/bgpfilter/export_only/global_peer/bird_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/export_only/global_peer/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/bgpfilter/export_only/global_peer/bird_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/export_only/global_peer/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/filter_deletion/step1/bird.cfg b/confd/tests/compiled_templates/bgpfilter/filter_deletion/step1/bird.cfg index 35af6e7f673..c55ef602cc2 100644 --- a/confd/tests/compiled_templates/bgpfilter/filter_deletion/step1/bird.cfg +++ b/confd/tests/compiled_templates/bgpfilter/filter_deletion/step1/bird.cfg @@ -71,22 +71,7 @@ function 'bgp_test-filter_exportFilterV4'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v4/10.192.0.2 -# Skipping ourselves (10.192.0.2) - - -# For peer /bgp/v1/global/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Global_10_192_0_3 from bgp_template { ttl security off; multihop; @@ -102,9 +87,6 @@ protocol bgp Global_10_192_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v4/10.192.0.4 protocol bgp Global_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -122,9 +104,3 @@ protocol bgp Global_10_192_0_4 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/filter_deletion/step1/bird6.cfg b/confd/tests/compiled_templates/bgpfilter/filter_deletion/step1/bird6.cfg index 0a7501fecdc..48b80f2acbd 100644 --- a/confd/tests/compiled_templates/bgpfilter/filter_deletion/step1/bird6.cfg +++ b/confd/tests/compiled_templates/bgpfilter/filter_deletion/step1/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -71,22 +70,7 @@ function 'bgp_test-filter_exportFilterV6'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v6/2001::102 -# Skipping ourselves (2001::102) - - -# For peer /bgp/v1/global/peer_v6/2001::103 +# BGP Protocol Configurations protocol bgp Global_2001__103 from bgp_template { ttl security off; multihop; @@ -102,9 +86,6 @@ protocol bgp Global_2001__103 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v6/2001::104 protocol bgp Global_2001__104 from bgp_template { ttl security off; multihop; @@ -122,9 +103,3 @@ protocol bgp Global_2001__104 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/filter_deletion/step1/bird6_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/filter_deletion/step1/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/bgpfilter/filter_deletion/step1/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/filter_deletion/step1/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/filter_deletion/step1/bird_aggr.cfg b/confd/tests/compiled_templates/bgpfilter/filter_deletion/step1/bird_aggr.cfg index 5a623513074..9d86b3b0205 100644 --- a/confd/tests/compiled_templates/bgpfilter/filter_deletion/step1/bird_aggr.cfg +++ b/confd/tests/compiled_templates/bgpfilter/filter_deletion/step1/bird_aggr.cfg @@ -8,7 +8,6 @@ protocol static { route 192.168.221.64/26 blackhole; } - # Aggregation of routes on this host; export the block, nothing beneath it. function calico_aggr () { diff --git a/confd/tests/compiled_templates/bgpfilter/filter_deletion/step1/bird_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/filter_deletion/step1/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/bgpfilter/filter_deletion/step1/bird_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/filter_deletion/step1/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/filter_deletion/step2/bird.cfg b/confd/tests/compiled_templates/bgpfilter/filter_deletion/step2/bird.cfg index 51d0a184beb..04635d5ba69 100644 --- a/confd/tests/compiled_templates/bgpfilter/filter_deletion/step2/bird.cfg +++ b/confd/tests/compiled_templates/bgpfilter/filter_deletion/step2/bird.cfg @@ -59,22 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v4/10.192.0.2 -# Skipping ourselves (10.192.0.2) - - -# For peer /bgp/v1/global/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Global_10_192_0_3 from bgp_template { ttl security off; multihop; @@ -88,9 +73,6 @@ protocol bgp Global_10_192_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v4/10.192.0.4 protocol bgp Global_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -106,9 +88,3 @@ protocol bgp Global_10_192_0_4 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/filter_deletion/step2/bird6.cfg b/confd/tests/compiled_templates/bgpfilter/filter_deletion/step2/bird6.cfg index 6d0b72fe8b1..efb00b46f3c 100644 --- a/confd/tests/compiled_templates/bgpfilter/filter_deletion/step2/bird6.cfg +++ b/confd/tests/compiled_templates/bgpfilter/filter_deletion/step2/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,22 +58,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v6/2001::102 -# Skipping ourselves (2001::102) - - -# For peer /bgp/v1/global/peer_v6/2001::103 +# BGP Protocol Configurations protocol bgp Global_2001__103 from bgp_template { ttl security off; multihop; @@ -88,9 +72,6 @@ protocol bgp Global_2001__103 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v6/2001::104 protocol bgp Global_2001__104 from bgp_template { ttl security off; multihop; @@ -106,9 +87,3 @@ protocol bgp Global_2001__104 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/filter_deletion/step2/bird6_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/filter_deletion/step2/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/bgpfilter/filter_deletion/step2/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/filter_deletion/step2/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/filter_deletion/step2/bird_aggr.cfg b/confd/tests/compiled_templates/bgpfilter/filter_deletion/step2/bird_aggr.cfg index 5a623513074..9d86b3b0205 100644 --- a/confd/tests/compiled_templates/bgpfilter/filter_deletion/step2/bird_aggr.cfg +++ b/confd/tests/compiled_templates/bgpfilter/filter_deletion/step2/bird_aggr.cfg @@ -8,7 +8,6 @@ protocol static { route 192.168.221.64/26 blackhole; } - # Aggregation of routes on this host; export the block, nothing beneath it. function calico_aggr () { diff --git a/confd/tests/compiled_templates/bgpfilter/filter_deletion/step2/bird_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/filter_deletion/step2/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/bgpfilter/filter_deletion/step2/bird_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/filter_deletion/step2/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/filter_names/bird.cfg b/confd/tests/compiled_templates/bgpfilter/filter_names/bird.cfg index a69c7043b22..001c217b28b 100644 --- a/confd/tests/compiled_templates/bgpfilter/filter_names/bird.cfg +++ b/confd/tests/compiled_templates/bgpfilter/filter_names/bird.cfg @@ -85,22 +85,7 @@ function 'bgp_greater-than-64-characters.s_4C5DB3273E544641_exportFilterV4'() { if ((net ~ 77.4.0.0/16)) then { accept; } if ((net ~ 77.5.0.0/16)) then { reject; } } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v4/10.192.0.2 -# Skipping ourselves (10.192.0.2) - - -# For peer /bgp/v1/global/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Global_10_192_0_3 from bgp_template { ttl security off; multihop; @@ -120,9 +105,6 @@ protocol bgp Global_10_192_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v4/10.192.0.4 protocol bgp Global_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -144,9 +126,3 @@ protocol bgp Global_10_192_0_4 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/filter_names/bird6.cfg b/confd/tests/compiled_templates/bgpfilter/filter_names/bird6.cfg index 070d8c74b6f..c408ba27b5f 100644 --- a/confd/tests/compiled_templates/bgpfilter/filter_names/bird6.cfg +++ b/confd/tests/compiled_templates/bgpfilter/filter_names/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -85,22 +84,7 @@ function 'bgp_greater-than-64-characters.s_4C5DB3273E544641_exportFilterV6'() { if ((net ~ 9000:4::0/64)) then { accept; } if ((net ~ 9000:5::0/64)) then { reject; } } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v6/2001::102 -# Skipping ourselves (2001::102) - - -# For peer /bgp/v1/global/peer_v6/2001::103 +# BGP Protocol Configurations protocol bgp Global_2001__103 from bgp_template { ttl security off; multihop; @@ -120,9 +104,6 @@ protocol bgp Global_2001__103 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v6/2001::104 protocol bgp Global_2001__104 from bgp_template { ttl security off; multihop; @@ -144,9 +125,3 @@ protocol bgp Global_2001__104 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/filter_names/bird6_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/filter_names/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/bgpfilter/filter_names/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/filter_names/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/filter_names/bird_aggr.cfg b/confd/tests/compiled_templates/bgpfilter/filter_names/bird_aggr.cfg index 5a623513074..9d86b3b0205 100644 --- a/confd/tests/compiled_templates/bgpfilter/filter_names/bird_aggr.cfg +++ b/confd/tests/compiled_templates/bgpfilter/filter_names/bird_aggr.cfg @@ -8,7 +8,6 @@ protocol static { route 192.168.221.64/26 blackhole; } - # Aggregation of routes on this host; export the block, nothing beneath it. function calico_aggr () { diff --git a/confd/tests/compiled_templates/bgpfilter/filter_names/bird_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/filter_names/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/bgpfilter/filter_names/bird_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/filter_names/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/import_only/explicit_peer/bird.cfg b/confd/tests/compiled_templates/bgpfilter/import_only/explicit_peer/bird.cfg index 60edaaba366..5392bce8030 100644 --- a/confd/tests/compiled_templates/bgpfilter/import_only/explicit_peer/bird.cfg +++ b/confd/tests/compiled_templates/bgpfilter/import_only/explicit_peer/bird.cfg @@ -72,23 +72,7 @@ function 'bgp_import-only-filter-2_importFilterV4'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Node_10_192_0_3 from bgp_template { ttl security off; multihop; @@ -103,9 +87,6 @@ protocol bgp Node_10_192_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.4 protocol bgp Node_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -122,4 +103,3 @@ protocol bgp Node_10_192_0_4 from bgp_template { } - diff --git a/confd/tests/compiled_templates/bgpfilter/import_only/explicit_peer/bird6.cfg b/confd/tests/compiled_templates/bgpfilter/import_only/explicit_peer/bird6.cfg index 329c0848c6c..74d1a0c87b4 100644 --- a/confd/tests/compiled_templates/bgpfilter/import_only/explicit_peer/bird6.cfg +++ b/confd/tests/compiled_templates/bgpfilter/import_only/explicit_peer/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -72,23 +71,7 @@ function 'bgp_import-only-filter-2_importFilterV6'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))&&((defined(ifname))&&(ifname ~ "cali*"))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v6/2001::103 +# BGP Protocol Configurations protocol bgp Node_2001__103 from bgp_template { ttl security off; multihop; @@ -103,9 +86,6 @@ protocol bgp Node_2001__103 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v6/2001::104 protocol bgp Node_2001__104 from bgp_template { ttl security off; multihop; @@ -122,4 +102,3 @@ protocol bgp Node_2001__104 from bgp_template { } - diff --git a/confd/tests/compiled_templates/bgpfilter/import_only/explicit_peer/bird6_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/import_only/explicit_peer/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/bgpfilter/import_only/explicit_peer/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/import_only/explicit_peer/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/import_only/explicit_peer/bird_aggr.cfg b/confd/tests/compiled_templates/bgpfilter/import_only/explicit_peer/bird_aggr.cfg index 5a623513074..9d86b3b0205 100644 --- a/confd/tests/compiled_templates/bgpfilter/import_only/explicit_peer/bird_aggr.cfg +++ b/confd/tests/compiled_templates/bgpfilter/import_only/explicit_peer/bird_aggr.cfg @@ -8,7 +8,6 @@ protocol static { route 192.168.221.64/26 blackhole; } - # Aggregation of routes on this host; export the block, nothing beneath it. function calico_aggr () { diff --git a/confd/tests/compiled_templates/bgpfilter/import_only/explicit_peer/bird_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/import_only/explicit_peer/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/bgpfilter/import_only/explicit_peer/bird_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/import_only/explicit_peer/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/import_only/global_peer/bird.cfg b/confd/tests/compiled_templates/bgpfilter/import_only/global_peer/bird.cfg index dcd4d1bbdb0..87b5b0a175a 100644 --- a/confd/tests/compiled_templates/bgpfilter/import_only/global_peer/bird.cfg +++ b/confd/tests/compiled_templates/bgpfilter/import_only/global_peer/bird.cfg @@ -65,22 +65,7 @@ function 'bgp_import-only-filter_importFilterV4'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))) then { accept; } if (((defined(ifname))&&(ifname ~ "*"))) then { reject; } } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v4/10.192.0.2 -# Skipping ourselves (10.192.0.2) - - -# For peer /bgp/v1/global/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Global_10_192_0_3 from bgp_template { ttl security off; multihop; @@ -95,9 +80,6 @@ protocol bgp Global_10_192_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v4/10.192.0.4 protocol bgp Global_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -114,9 +96,3 @@ protocol bgp Global_10_192_0_4 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/import_only/global_peer/bird6.cfg b/confd/tests/compiled_templates/bgpfilter/import_only/global_peer/bird6.cfg index 4d90b813c2f..f2910bde96b 100644 --- a/confd/tests/compiled_templates/bgpfilter/import_only/global_peer/bird6.cfg +++ b/confd/tests/compiled_templates/bgpfilter/import_only/global_peer/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -65,22 +64,7 @@ function 'bgp_import-only-filter_importFilterV6'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v6/2001::102 -# Skipping ourselves (2001::102) - - -# For peer /bgp/v1/global/peer_v6/2001::103 +# BGP Protocol Configurations protocol bgp Global_2001__103 from bgp_template { ttl security off; multihop; @@ -95,9 +79,6 @@ protocol bgp Global_2001__103 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v6/2001::104 protocol bgp Global_2001__104 from bgp_template { ttl security off; multihop; @@ -114,9 +95,3 @@ protocol bgp Global_2001__104 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/import_only/global_peer/bird6_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/import_only/global_peer/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/bgpfilter/import_only/global_peer/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/import_only/global_peer/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/import_only/global_peer/bird_aggr.cfg b/confd/tests/compiled_templates/bgpfilter/import_only/global_peer/bird_aggr.cfg index 5a623513074..9d86b3b0205 100644 --- a/confd/tests/compiled_templates/bgpfilter/import_only/global_peer/bird_aggr.cfg +++ b/confd/tests/compiled_templates/bgpfilter/import_only/global_peer/bird_aggr.cfg @@ -8,7 +8,6 @@ protocol static { route 192.168.221.64/26 blackhole; } - # Aggregation of routes on this host; export the block, nothing beneath it. function calico_aggr () { diff --git a/confd/tests/compiled_templates/bgpfilter/import_only/global_peer/bird_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/import_only/global_peer/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/bgpfilter/import_only/global_peer/bird_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/import_only/global_peer/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/match_interface/bird.cfg b/confd/tests/compiled_templates/bgpfilter/match_interface/bird.cfg index 9001564eb42..24e2ac9349f 100644 --- a/confd/tests/compiled_templates/bgpfilter/match_interface/bird.cfg +++ b/confd/tests/compiled_templates/bgpfilter/match_interface/bird.cfg @@ -71,22 +71,7 @@ function 'bgp_test-filter-match-interface_exportFilterV4'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v4/10.192.0.2 -# Skipping ourselves (10.192.0.2) - - -# For peer /bgp/v1/global/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Global_10_192_0_3 from bgp_template { ttl security off; multihop; @@ -102,9 +87,6 @@ protocol bgp Global_10_192_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v4/10.192.0.4 protocol bgp Global_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -122,9 +104,3 @@ protocol bgp Global_10_192_0_4 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/match_interface/bird6.cfg b/confd/tests/compiled_templates/bgpfilter/match_interface/bird6.cfg index 717b893cb6c..36310de54ec 100644 --- a/confd/tests/compiled_templates/bgpfilter/match_interface/bird6.cfg +++ b/confd/tests/compiled_templates/bgpfilter/match_interface/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -71,22 +70,7 @@ function 'bgp_test-filter-match-interface_exportFilterV6'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v6/2001::102 -# Skipping ourselves (2001::102) - - -# For peer /bgp/v1/global/peer_v6/2001::103 +# BGP Protocol Configurations protocol bgp Global_2001__103 from bgp_template { ttl security off; multihop; @@ -102,9 +86,6 @@ protocol bgp Global_2001__103 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v6/2001::104 protocol bgp Global_2001__104 from bgp_template { ttl security off; multihop; @@ -122,9 +103,3 @@ protocol bgp Global_2001__104 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/match_interface/bird6_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/match_interface/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/bgpfilter/match_interface/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/match_interface/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/match_interface/bird_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/match_interface/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/bgpfilter/match_interface/bird_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/match_interface/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/match_operators/bird.cfg b/confd/tests/compiled_templates/bgpfilter/match_operators/bird.cfg index 0af0a6cc3f8..eed2daf0b43 100644 --- a/confd/tests/compiled_templates/bgpfilter/match_operators/bird.cfg +++ b/confd/tests/compiled_templates/bgpfilter/match_operators/bird.cfg @@ -71,22 +71,7 @@ function 'bgp_test-filter-match-operators_exportFilterV4'() { if ((net = 77.2.0.0/16)) then { accept; } if ((net != 77.3.0.0/16)) then { reject; } } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v4/10.192.0.2 -# Skipping ourselves (10.192.0.2) - - -# For peer /bgp/v1/global/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Global_10_192_0_3 from bgp_template { ttl security off; multihop; @@ -102,9 +87,6 @@ protocol bgp Global_10_192_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v4/10.192.0.4 protocol bgp Global_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -122,9 +104,3 @@ protocol bgp Global_10_192_0_4 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/match_operators/bird6.cfg b/confd/tests/compiled_templates/bgpfilter/match_operators/bird6.cfg index 7907545c23d..8d6c924251c 100644 --- a/confd/tests/compiled_templates/bgpfilter/match_operators/bird6.cfg +++ b/confd/tests/compiled_templates/bgpfilter/match_operators/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -71,22 +70,7 @@ function 'bgp_test-filter-match-operators_exportFilterV6'() { if ((net = 9000:2::0/64)) then { accept; } if ((net != 9000:3::0/64)) then { reject; } } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v6/2001::102 -# Skipping ourselves (2001::102) - - -# For peer /bgp/v1/global/peer_v6/2001::103 +# BGP Protocol Configurations protocol bgp Global_2001__103 from bgp_template { ttl security off; multihop; @@ -102,9 +86,6 @@ protocol bgp Global_2001__103 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v6/2001::104 protocol bgp Global_2001__104 from bgp_template { ttl security off; multihop; @@ -122,9 +103,3 @@ protocol bgp Global_2001__104 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/match_operators/bird6_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/match_operators/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/bgpfilter/match_operators/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/match_operators/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/match_operators/bird_aggr.cfg b/confd/tests/compiled_templates/bgpfilter/match_operators/bird_aggr.cfg index 5a623513074..9d86b3b0205 100644 --- a/confd/tests/compiled_templates/bgpfilter/match_operators/bird_aggr.cfg +++ b/confd/tests/compiled_templates/bgpfilter/match_operators/bird_aggr.cfg @@ -8,7 +8,6 @@ protocol static { route 192.168.221.64/26 blackhole; } - # Aggregation of routes on this host; export the block, nothing beneath it. function calico_aggr () { diff --git a/confd/tests/compiled_templates/bgpfilter/match_operators/bird_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/match_operators/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/bgpfilter/match_operators/bird_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/match_operators/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/match_source/bird.cfg b/confd/tests/compiled_templates/bgpfilter/match_source/bird.cfg index e23e7d7f0c0..f41560457cb 100644 --- a/confd/tests/compiled_templates/bgpfilter/match_source/bird.cfg +++ b/confd/tests/compiled_templates/bgpfilter/match_source/bird.cfg @@ -67,22 +67,7 @@ function 'bgp_test-filter-match-source_exportFilterV4'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v4/10.192.0.2 -# Skipping ourselves (10.192.0.2) - - -# For peer /bgp/v1/global/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Global_10_192_0_3 from bgp_template { ttl security off; multihop; @@ -98,9 +83,6 @@ protocol bgp Global_10_192_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v4/10.192.0.4 protocol bgp Global_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -118,9 +100,3 @@ protocol bgp Global_10_192_0_4 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/match_source/bird6.cfg b/confd/tests/compiled_templates/bgpfilter/match_source/bird6.cfg index ed9f1e62fbd..babf81190c8 100644 --- a/confd/tests/compiled_templates/bgpfilter/match_source/bird6.cfg +++ b/confd/tests/compiled_templates/bgpfilter/match_source/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -67,22 +66,7 @@ function 'bgp_test-filter-match-source_exportFilterV6'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v6/2001::102 -# Skipping ourselves (2001::102) - - -# For peer /bgp/v1/global/peer_v6/2001::103 +# BGP Protocol Configurations protocol bgp Global_2001__103 from bgp_template { ttl security off; multihop; @@ -98,9 +82,6 @@ protocol bgp Global_2001__103 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v6/2001::104 protocol bgp Global_2001__104 from bgp_template { ttl security off; multihop; @@ -118,9 +99,3 @@ protocol bgp Global_2001__104 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/match_source/bird6_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/match_source/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/bgpfilter/match_source/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/match_source/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/match_source/bird_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/match_source/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/bgpfilter/match_source/bird_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/match_source/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/multi_filter/explicit_peer/bird.cfg b/confd/tests/compiled_templates/bgpfilter/multi_filter/explicit_peer/bird.cfg index 98a03c493f4..f7202f04c6e 100644 --- a/confd/tests/compiled_templates/bgpfilter/multi_filter/explicit_peer/bird.cfg +++ b/confd/tests/compiled_templates/bgpfilter/multi_filter/explicit_peer/bird.cfg @@ -84,18 +84,7 @@ function 'bgp_test-filter-2_exportFilterV4'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Global_10_192_0_3 from bgp_template { ttl security off; multihop; @@ -112,11 +101,8 @@ protocol bgp Global_10_192_0_3 from bgp_template { calico_export_to_bgp_peers(false); reject; }; # Only want to export routes for workloads. - passive on; # Peering is unidirectional, peer will connect to us. + passive on; } - - -# For peer /bgp/v1/global/peer_v4/10.192.0.4 protocol bgp Global_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -133,13 +119,5 @@ protocol bgp Global_10_192_0_4 from bgp_template { calico_export_to_bgp_peers(false); reject; }; # Only want to export routes for workloads. - passive on; # Peering is unidirectional, peer will connect to us. + passive on; } - - - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/multi_filter/explicit_peer/bird6.cfg b/confd/tests/compiled_templates/bgpfilter/multi_filter/explicit_peer/bird6.cfg index ebafcb62ae0..b936204b558 100644 --- a/confd/tests/compiled_templates/bgpfilter/multi_filter/explicit_peer/bird6.cfg +++ b/confd/tests/compiled_templates/bgpfilter/multi_filter/explicit_peer/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -84,18 +83,7 @@ function 'bgp_test-filter-2_exportFilterV6'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))&&((defined(ifname))&&(ifname ~ "*.calico"))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v6/2001::103 +# BGP Protocol Configurations protocol bgp Global_2001__103 from bgp_template { ttl security off; multihop; @@ -112,11 +100,8 @@ protocol bgp Global_2001__103 from bgp_template { calico_export_to_bgp_peers(false); reject; }; # Only want to export routes for workloads. - passive on; # Peering is unidirectional, peer will connect to us. + passive on; } - - -# For peer /bgp/v1/global/peer_v6/2001::104 protocol bgp Global_2001__104 from bgp_template { ttl security off; multihop; @@ -133,13 +118,7 @@ protocol bgp Global_2001__104 from bgp_template { calico_export_to_bgp_peers(false); reject; }; # Only want to export routes for workloads. - passive on; # Peering is unidirectional, peer will connect to us. + passive on; } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/multi_filter/explicit_peer/bird6_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/multi_filter/explicit_peer/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/bgpfilter/multi_filter/explicit_peer/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/multi_filter/explicit_peer/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/multi_filter/explicit_peer/bird_aggr.cfg b/confd/tests/compiled_templates/bgpfilter/multi_filter/explicit_peer/bird_aggr.cfg index 5a623513074..9d86b3b0205 100644 --- a/confd/tests/compiled_templates/bgpfilter/multi_filter/explicit_peer/bird_aggr.cfg +++ b/confd/tests/compiled_templates/bgpfilter/multi_filter/explicit_peer/bird_aggr.cfg @@ -8,7 +8,6 @@ protocol static { route 192.168.221.64/26 blackhole; } - # Aggregation of routes on this host; export the block, nothing beneath it. function calico_aggr () { diff --git a/confd/tests/compiled_templates/bgpfilter/multi_filter/explicit_peer/bird_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/multi_filter/explicit_peer/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/bgpfilter/multi_filter/explicit_peer/bird_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/multi_filter/explicit_peer/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/multi_filter/global_peer/bird.cfg b/confd/tests/compiled_templates/bgpfilter/multi_filter/global_peer/bird.cfg index 3ace0c5fbb6..07cdf6bba32 100644 --- a/confd/tests/compiled_templates/bgpfilter/multi_filter/global_peer/bird.cfg +++ b/confd/tests/compiled_templates/bgpfilter/multi_filter/global_peer/bird.cfg @@ -84,22 +84,7 @@ function 'bgp_test-filter-2_exportFilterV4'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v4/10.192.0.2 -# Skipping ourselves (10.192.0.2) - - -# For peer /bgp/v1/global/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Global_10_192_0_3 from bgp_template { ttl security off; multihop; @@ -117,9 +102,6 @@ protocol bgp Global_10_192_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v4/10.192.0.4 protocol bgp Global_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -139,9 +121,3 @@ protocol bgp Global_10_192_0_4 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/multi_filter/global_peer/bird6.cfg b/confd/tests/compiled_templates/bgpfilter/multi_filter/global_peer/bird6.cfg index 737623b9542..1456bd0051e 100644 --- a/confd/tests/compiled_templates/bgpfilter/multi_filter/global_peer/bird6.cfg +++ b/confd/tests/compiled_templates/bgpfilter/multi_filter/global_peer/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -84,22 +83,7 @@ function 'bgp_test-filter-2_exportFilterV6'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v6/2001::102 -# Skipping ourselves (2001::102) - - -# For peer /bgp/v1/global/peer_v6/2001::103 +# BGP Protocol Configurations protocol bgp Global_2001__103 from bgp_template { ttl security off; multihop; @@ -117,9 +101,6 @@ protocol bgp Global_2001__103 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v6/2001::104 protocol bgp Global_2001__104 from bgp_template { ttl security off; multihop; @@ -139,9 +120,3 @@ protocol bgp Global_2001__104 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/multi_filter/global_peer/bird6_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/multi_filter/global_peer/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/bgpfilter/multi_filter/global_peer/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/multi_filter/global_peer/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/multi_filter/global_peer/bird_aggr.cfg b/confd/tests/compiled_templates/bgpfilter/multi_filter/global_peer/bird_aggr.cfg index 5a623513074..9d86b3b0205 100644 --- a/confd/tests/compiled_templates/bgpfilter/multi_filter/global_peer/bird_aggr.cfg +++ b/confd/tests/compiled_templates/bgpfilter/multi_filter/global_peer/bird_aggr.cfg @@ -8,7 +8,6 @@ protocol static { route 192.168.221.64/26 blackhole; } - # Aggregation of routes on this host; export the block, nothing beneath it. function calico_aggr () { diff --git a/confd/tests/compiled_templates/bgpfilter/multi_filter/global_peer/bird_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/multi_filter/global_peer/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/bgpfilter/multi_filter/global_peer/bird_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/multi_filter/global_peer/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/node_mesh/bird.cfg b/confd/tests/compiled_templates/bgpfilter/node_mesh/bird.cfg index 91b911c1b9a..fd60b6fc442 100644 --- a/confd/tests/compiled_templates/bgpfilter/node_mesh/bird.cfg +++ b/confd/tests/compiled_templates/bgpfilter/node_mesh/bird.cfg @@ -71,19 +71,7 @@ function 'bgp_test-filter_exportFilterV4'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v4 -# Skipping ourselves (10.192.0.2) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v4 +# BGP Protocol Configurations protocol bgp Mesh_10_192_0_3 from bgp_template { neighbor 10.192.0.3 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -93,12 +81,8 @@ protocol bgp Mesh_10_192_0_3 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v4 protocol bgp Mesh_10_192_0_4 from bgp_template { neighbor 10.192.0.4 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -108,16 +92,5 @@ protocol bgp Mesh_10_192_0_4 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/node_mesh/bird6.cfg b/confd/tests/compiled_templates/bgpfilter/node_mesh/bird6.cfg index 7e89d29df1c..c1e96fe5bed 100644 --- a/confd/tests/compiled_templates/bgpfilter/node_mesh/bird6.cfg +++ b/confd/tests/compiled_templates/bgpfilter/node_mesh/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -71,53 +70,28 @@ function 'bgp_test-filter_exportFilterV6'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))&&((defined(ifname))&&(ifname ~ "some*iface"))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v6 -# Skipping ourselves (2001::102) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v6 +# BGP Protocol Configurations protocol bgp Mesh_2001__103 from bgp_template { neighbor 2001::103 as 64512; source address 2001::102; # The local address we use for the TCP connection import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. + # topology is and therefore have to trust the ToR/RR. export filter { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v6 protocol bgp Mesh_2001__104 from bgp_template { neighbor 2001::104 as 64512; source address 2001::102; # The local address we use for the TCP connection import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. + # topology is and therefore have to trust the ToR/RR. export filter { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/node_mesh/bird6_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/node_mesh/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/bgpfilter/node_mesh/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/node_mesh/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/node_mesh/bird_aggr.cfg b/confd/tests/compiled_templates/bgpfilter/node_mesh/bird_aggr.cfg index 5a623513074..9d86b3b0205 100644 --- a/confd/tests/compiled_templates/bgpfilter/node_mesh/bird_aggr.cfg +++ b/confd/tests/compiled_templates/bgpfilter/node_mesh/bird_aggr.cfg @@ -8,7 +8,6 @@ protocol static { route 192.168.221.64/26 blackhole; } - # Aggregation of routes on this host; export the block, nothing beneath it. function calico_aggr () { diff --git a/confd/tests/compiled_templates/bgpfilter/node_mesh/bird_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/node_mesh/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/bgpfilter/node_mesh/bird_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/node_mesh/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/single_filter/explicit_peer/bird.cfg b/confd/tests/compiled_templates/bgpfilter/single_filter/explicit_peer/bird.cfg index 946e4a1cb78..efa7115b6f1 100644 --- a/confd/tests/compiled_templates/bgpfilter/single_filter/explicit_peer/bird.cfg +++ b/confd/tests/compiled_templates/bgpfilter/single_filter/explicit_peer/bird.cfg @@ -84,23 +84,7 @@ function 'bgp_test-filter-2_exportFilterV4'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))&&((defined(ifname))&&(ifname ~ "eth0"))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Node_10_192_0_3 from bgp_template { ttl security off; multihop; @@ -116,9 +100,6 @@ protocol bgp Node_10_192_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.4 protocol bgp Node_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -136,4 +117,3 @@ protocol bgp Node_10_192_0_4 from bgp_template { } - diff --git a/confd/tests/compiled_templates/bgpfilter/single_filter/explicit_peer/bird6.cfg b/confd/tests/compiled_templates/bgpfilter/single_filter/explicit_peer/bird6.cfg index a4ae6735ddf..3ba314fbb85 100644 --- a/confd/tests/compiled_templates/bgpfilter/single_filter/explicit_peer/bird6.cfg +++ b/confd/tests/compiled_templates/bgpfilter/single_filter/explicit_peer/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -84,23 +83,7 @@ function 'bgp_test-filter-2_exportFilterV6'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v6/2001::103 +# BGP Protocol Configurations protocol bgp Node_2001__103 from bgp_template { ttl security off; multihop; @@ -116,9 +99,6 @@ protocol bgp Node_2001__103 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v6/2001::104 protocol bgp Node_2001__104 from bgp_template { ttl security off; multihop; @@ -136,4 +116,3 @@ protocol bgp Node_2001__104 from bgp_template { } - diff --git a/confd/tests/compiled_templates/bgpfilter/single_filter/explicit_peer/bird6_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/single_filter/explicit_peer/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/bgpfilter/single_filter/explicit_peer/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/single_filter/explicit_peer/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/single_filter/explicit_peer/bird_aggr.cfg b/confd/tests/compiled_templates/bgpfilter/single_filter/explicit_peer/bird_aggr.cfg index 5a623513074..9d86b3b0205 100644 --- a/confd/tests/compiled_templates/bgpfilter/single_filter/explicit_peer/bird_aggr.cfg +++ b/confd/tests/compiled_templates/bgpfilter/single_filter/explicit_peer/bird_aggr.cfg @@ -8,7 +8,6 @@ protocol static { route 192.168.221.64/26 blackhole; } - # Aggregation of routes on this host; export the block, nothing beneath it. function calico_aggr () { diff --git a/confd/tests/compiled_templates/bgpfilter/single_filter/explicit_peer/bird_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/single_filter/explicit_peer/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/bgpfilter/single_filter/explicit_peer/bird_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/single_filter/explicit_peer/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/single_filter/global_peer/bird.cfg b/confd/tests/compiled_templates/bgpfilter/single_filter/global_peer/bird.cfg index a6e538ec88b..e50210aed5c 100644 --- a/confd/tests/compiled_templates/bgpfilter/single_filter/global_peer/bird.cfg +++ b/confd/tests/compiled_templates/bgpfilter/single_filter/global_peer/bird.cfg @@ -71,22 +71,7 @@ function 'bgp_test-filter_exportFilterV4'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))&&((defined(ifname))&&(ifname ~ "eth0"))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v4/10.192.0.2 -# Skipping ourselves (10.192.0.2) - - -# For peer /bgp/v1/global/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Global_10_192_0_3 from bgp_template { ttl security off; multihop; @@ -102,9 +87,6 @@ protocol bgp Global_10_192_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v4/10.192.0.4 protocol bgp Global_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -122,9 +104,3 @@ protocol bgp Global_10_192_0_4 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/single_filter/global_peer/bird6.cfg b/confd/tests/compiled_templates/bgpfilter/single_filter/global_peer/bird6.cfg index a54d68701cf..be94806aed6 100644 --- a/confd/tests/compiled_templates/bgpfilter/single_filter/global_peer/bird6.cfg +++ b/confd/tests/compiled_templates/bgpfilter/single_filter/global_peer/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -71,22 +70,7 @@ function 'bgp_test-filter_exportFilterV6'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v6/2001::102 -# Skipping ourselves (2001::102) - - -# For peer /bgp/v1/global/peer_v6/2001::103 +# BGP Protocol Configurations protocol bgp Global_2001__103 from bgp_template { ttl security off; multihop; @@ -102,9 +86,6 @@ protocol bgp Global_2001__103 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v6/2001::104 protocol bgp Global_2001__104 from bgp_template { ttl security off; multihop; @@ -122,9 +103,3 @@ protocol bgp Global_2001__104 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/single_filter/global_peer/bird6_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/single_filter/global_peer/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/bgpfilter/single_filter/global_peer/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/single_filter/global_peer/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/single_filter/global_peer/bird_aggr.cfg b/confd/tests/compiled_templates/bgpfilter/single_filter/global_peer/bird_aggr.cfg index 5a623513074..9d86b3b0205 100644 --- a/confd/tests/compiled_templates/bgpfilter/single_filter/global_peer/bird_aggr.cfg +++ b/confd/tests/compiled_templates/bgpfilter/single_filter/global_peer/bird_aggr.cfg @@ -8,7 +8,6 @@ protocol static { route 192.168.221.64/26 blackhole; } - # Aggregation of routes on this host; export the block, nothing beneath it. function calico_aggr () { diff --git a/confd/tests/compiled_templates/bgpfilter/single_filter/global_peer/bird_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/single_filter/global_peer/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/bgpfilter/single_filter/global_peer/bird_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/single_filter/global_peer/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/v4_only/explicit_peer/bird.cfg b/confd/tests/compiled_templates/bgpfilter/v4_only/explicit_peer/bird.cfg index d1560614350..724f94b9d78 100644 --- a/confd/tests/compiled_templates/bgpfilter/v4_only/explicit_peer/bird.cfg +++ b/confd/tests/compiled_templates/bgpfilter/v4_only/explicit_peer/bird.cfg @@ -84,23 +84,7 @@ function 'bgp_test-filter-2_exportFilterV4'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Node_10_192_0_3 from bgp_template { ttl security off; multihop; @@ -116,9 +100,6 @@ protocol bgp Node_10_192_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.4 protocol bgp Node_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -136,4 +117,3 @@ protocol bgp Node_10_192_0_4 from bgp_template { } - diff --git a/confd/tests/compiled_templates/bgpfilter/v4_only/explicit_peer/bird6.cfg b/confd/tests/compiled_templates/bgpfilter/v4_only/explicit_peer/bird6.cfg index 49c7c7dd53d..be0a9a9c185 100644 --- a/confd/tests/compiled_templates/bgpfilter/v4_only/explicit_peer/bird6.cfg +++ b/confd/tests/compiled_templates/bgpfilter/v4_only/explicit_peer/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,23 +58,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v6/2001::103 +# BGP Protocol Configurations protocol bgp Node_2001__103 from bgp_template { ttl security off; multihop; @@ -89,9 +72,6 @@ protocol bgp Node_2001__103 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v6/2001::104 protocol bgp Node_2001__104 from bgp_template { ttl security off; multihop; @@ -107,4 +87,3 @@ protocol bgp Node_2001__104 from bgp_template { } - diff --git a/confd/tests/compiled_templates/bgpfilter/v4_only/explicit_peer/bird6_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/v4_only/explicit_peer/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/bgpfilter/v4_only/explicit_peer/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/v4_only/explicit_peer/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/v4_only/explicit_peer/bird_aggr.cfg b/confd/tests/compiled_templates/bgpfilter/v4_only/explicit_peer/bird_aggr.cfg index 5a623513074..9d86b3b0205 100644 --- a/confd/tests/compiled_templates/bgpfilter/v4_only/explicit_peer/bird_aggr.cfg +++ b/confd/tests/compiled_templates/bgpfilter/v4_only/explicit_peer/bird_aggr.cfg @@ -8,7 +8,6 @@ protocol static { route 192.168.221.64/26 blackhole; } - # Aggregation of routes on this host; export the block, nothing beneath it. function calico_aggr () { diff --git a/confd/tests/compiled_templates/bgpfilter/v4_only/explicit_peer/bird_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/v4_only/explicit_peer/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/bgpfilter/v4_only/explicit_peer/bird_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/v4_only/explicit_peer/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/v4_only/global_peer/bird.cfg b/confd/tests/compiled_templates/bgpfilter/v4_only/global_peer/bird.cfg index 35af6e7f673..c55ef602cc2 100644 --- a/confd/tests/compiled_templates/bgpfilter/v4_only/global_peer/bird.cfg +++ b/confd/tests/compiled_templates/bgpfilter/v4_only/global_peer/bird.cfg @@ -71,22 +71,7 @@ function 'bgp_test-filter_exportFilterV4'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v4/10.192.0.2 -# Skipping ourselves (10.192.0.2) - - -# For peer /bgp/v1/global/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Global_10_192_0_3 from bgp_template { ttl security off; multihop; @@ -102,9 +87,6 @@ protocol bgp Global_10_192_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v4/10.192.0.4 protocol bgp Global_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -122,9 +104,3 @@ protocol bgp Global_10_192_0_4 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/v4_only/global_peer/bird6.cfg b/confd/tests/compiled_templates/bgpfilter/v4_only/global_peer/bird6.cfg index 6d0b72fe8b1..efb00b46f3c 100644 --- a/confd/tests/compiled_templates/bgpfilter/v4_only/global_peer/bird6.cfg +++ b/confd/tests/compiled_templates/bgpfilter/v4_only/global_peer/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,22 +58,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v6/2001::102 -# Skipping ourselves (2001::102) - - -# For peer /bgp/v1/global/peer_v6/2001::103 +# BGP Protocol Configurations protocol bgp Global_2001__103 from bgp_template { ttl security off; multihop; @@ -88,9 +72,6 @@ protocol bgp Global_2001__103 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v6/2001::104 protocol bgp Global_2001__104 from bgp_template { ttl security off; multihop; @@ -106,9 +87,3 @@ protocol bgp Global_2001__104 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/v4_only/global_peer/bird6_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/v4_only/global_peer/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/bgpfilter/v4_only/global_peer/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/v4_only/global_peer/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/v4_only/global_peer/bird_aggr.cfg b/confd/tests/compiled_templates/bgpfilter/v4_only/global_peer/bird_aggr.cfg index 5a623513074..9d86b3b0205 100644 --- a/confd/tests/compiled_templates/bgpfilter/v4_only/global_peer/bird_aggr.cfg +++ b/confd/tests/compiled_templates/bgpfilter/v4_only/global_peer/bird_aggr.cfg @@ -8,7 +8,6 @@ protocol static { route 192.168.221.64/26 blackhole; } - # Aggregation of routes on this host; export the block, nothing beneath it. function calico_aggr () { diff --git a/confd/tests/compiled_templates/bgpfilter/v4_only/global_peer/bird_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/v4_only/global_peer/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/bgpfilter/v4_only/global_peer/bird_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/v4_only/global_peer/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/v6_only/explicit_peer/bird.cfg b/confd/tests/compiled_templates/bgpfilter/v6_only/explicit_peer/bird.cfg index 88314269717..81aaaea6c27 100644 --- a/confd/tests/compiled_templates/bgpfilter/v6_only/explicit_peer/bird.cfg +++ b/confd/tests/compiled_templates/bgpfilter/v6_only/explicit_peer/bird.cfg @@ -59,23 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Node_10_192_0_3 from bgp_template { ttl security off; multihop; @@ -89,9 +73,6 @@ protocol bgp Node_10_192_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.4 protocol bgp Node_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -107,4 +88,3 @@ protocol bgp Node_10_192_0_4 from bgp_template { } - diff --git a/confd/tests/compiled_templates/bgpfilter/v6_only/explicit_peer/bird6.cfg b/confd/tests/compiled_templates/bgpfilter/v6_only/explicit_peer/bird6.cfg index e68ab51cb98..874cf97136a 100644 --- a/confd/tests/compiled_templates/bgpfilter/v6_only/explicit_peer/bird6.cfg +++ b/confd/tests/compiled_templates/bgpfilter/v6_only/explicit_peer/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -84,23 +83,7 @@ function 'bgp_test-filter-2_exportFilterV6'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v6/2001::103 +# BGP Protocol Configurations protocol bgp Node_2001__103 from bgp_template { ttl security off; multihop; @@ -116,9 +99,6 @@ protocol bgp Node_2001__103 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v6/2001::104 protocol bgp Node_2001__104 from bgp_template { ttl security off; multihop; @@ -136,4 +116,3 @@ protocol bgp Node_2001__104 from bgp_template { } - diff --git a/confd/tests/compiled_templates/bgpfilter/v6_only/explicit_peer/bird6_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/v6_only/explicit_peer/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/bgpfilter/v6_only/explicit_peer/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/v6_only/explicit_peer/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/v6_only/explicit_peer/bird_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/v6_only/explicit_peer/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/bgpfilter/v6_only/explicit_peer/bird_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/v6_only/explicit_peer/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/v6_only/global_peer/bird.cfg b/confd/tests/compiled_templates/bgpfilter/v6_only/global_peer/bird.cfg index 51d0a184beb..04635d5ba69 100644 --- a/confd/tests/compiled_templates/bgpfilter/v6_only/global_peer/bird.cfg +++ b/confd/tests/compiled_templates/bgpfilter/v6_only/global_peer/bird.cfg @@ -59,22 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v4/10.192.0.2 -# Skipping ourselves (10.192.0.2) - - -# For peer /bgp/v1/global/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Global_10_192_0_3 from bgp_template { ttl security off; multihop; @@ -88,9 +73,6 @@ protocol bgp Global_10_192_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v4/10.192.0.4 protocol bgp Global_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -106,9 +88,3 @@ protocol bgp Global_10_192_0_4 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/v6_only/global_peer/bird6.cfg b/confd/tests/compiled_templates/bgpfilter/v6_only/global_peer/bird6.cfg index 1f344b90fc2..b9329fd6a75 100644 --- a/confd/tests/compiled_templates/bgpfilter/v6_only/global_peer/bird6.cfg +++ b/confd/tests/compiled_templates/bgpfilter/v6_only/global_peer/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -71,22 +70,7 @@ function 'bgp_test-filter_exportFilterV6'() { if (((defined(source))&&(source ~ [ RTS_BGP ]))) then { accept; } reject; } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v6/2001::102 -# Skipping ourselves (2001::102) - - -# For peer /bgp/v1/global/peer_v6/2001::103 +# BGP Protocol Configurations protocol bgp Global_2001__103 from bgp_template { ttl security off; multihop; @@ -102,9 +86,6 @@ protocol bgp Global_2001__103 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v6/2001::104 protocol bgp Global_2001__104 from bgp_template { ttl security off; multihop; @@ -122,9 +103,3 @@ protocol bgp Global_2001__104 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/bgpfilter/v6_only/global_peer/bird6_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/v6_only/global_peer/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/bgpfilter/v6_only/global_peer/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/v6_only/global_peer/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/bgpfilter/v6_only/global_peer/bird_ipam.cfg b/confd/tests/compiled_templates/bgpfilter/v6_only/global_peer/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/bgpfilter/v6_only/global_peer/bird_ipam.cfg +++ b/confd/tests/compiled_templates/bgpfilter/v6_only/global_peer/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/global-external/bird.cfg b/confd/tests/compiled_templates/explicit_peering/global-external/bird.cfg index 4e56628ffcf..0353d15dc08 100644 --- a/confd/tests/compiled_templates/explicit_peering/global-external/bird.cfg +++ b/confd/tests/compiled_templates/explicit_peering/global-external/bird.cfg @@ -7,7 +7,7 @@ include "bird_aggr.cfg"; include "bird_ipam.cfg"; router id 10.192.0.2; -# Set global listen_port +# BGP listen configuration listen bgp port 150; # Configure synchronization between routing tables and kernel. @@ -61,18 +61,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v4/10.192.0.3-150 +# BGP Protocol Configurations protocol bgp Global_10_192_0_3_port_150 from bgp_template { ttl security off; multihop; @@ -84,11 +73,8 @@ protocol bgp Global_10_192_0_3_port_150 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Peering is unidirectional, peer will connect to us. + passive on; } - - -# For peer /bgp/v1/global/peer_v4/10.192.0.4 protocol bgp Global_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -102,9 +88,6 @@ protocol bgp Global_10_192_0_4 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v4/10.192.0.4-166 protocol bgp Global_10_192_0_4_port_166 from bgp_template { ttl security off; multihop; @@ -118,11 +101,3 @@ protocol bgp Global_10_192_0_4_port_166 from bgp_template { reject; }; # Only want to export routes for workloads. } - - - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/explicit_peering/global-external/bird6.cfg b/confd/tests/compiled_templates/explicit_peering/global-external/bird6.cfg index 69cd8d013d2..d8fc709da3e 100644 --- a/confd/tests/compiled_templates/explicit_peering/global-external/bird6.cfg +++ b/confd/tests/compiled_templates/explicit_peering/global-external/bird6.cfg @@ -5,8 +5,9 @@ function apply_communities () # Generated by confd include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP -# Set global listen_port + +router id 10.192.0.2; +# BGP listen configuration listen bgp port 150; # Configure synchronization between routing tables and kernel. diff --git a/confd/tests/compiled_templates/explicit_peering/global-external/bird6_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/global-external/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/explicit_peering/global-external/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/global-external/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/global-external/bird_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/global-external/bird_ipam.cfg index 8c4453f41a1..9793102a805 100644 --- a/confd/tests/compiled_templates/explicit_peering/global-external/bird_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/global-external/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = "tunl0"; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel="tunl0"; accept; } accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/global-ipv6/bird.cfg b/confd/tests/compiled_templates/explicit_peering/global-ipv6/bird.cfg index 47c6ec2b81b..a1bf0c77497 100644 --- a/confd/tests/compiled_templates/explicit_peering/global-ipv6/bird.cfg +++ b/confd/tests/compiled_templates/explicit_peering/global-ipv6/bird.cfg @@ -59,18 +59,6 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured +# No BGP peers configured for this node -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. diff --git a/confd/tests/compiled_templates/explicit_peering/global-ipv6/bird6.cfg b/confd/tests/compiled_templates/explicit_peering/global-ipv6/bird6.cfg index 75b262f32d3..efd94428c07 100644 --- a/confd/tests/compiled_templates/explicit_peering/global-ipv6/bird6.cfg +++ b/confd/tests/compiled_templates/explicit_peering/global-ipv6/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug all; @@ -59,18 +58,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v6/2001::102 +# BGP Protocol Configurations protocol bgp Global_2001__102 from bgp_template { ttl security off; multihop; @@ -83,9 +71,6 @@ protocol bgp Global_2001__102 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v6/2001::104 protocol bgp Global_2001__104 from bgp_template { ttl security off; multihop; @@ -98,13 +83,7 @@ protocol bgp Global_2001__104 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Peering is unidirectional, peer will connect to us. + passive on; } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/explicit_peering/global-ipv6/bird6_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/global-ipv6/bird6_ipam.cfg index 6fd518a50a9..94ba3ce5199 100644 --- a/confd/tests/compiled_templates/explicit_peering/global-ipv6/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/global-ipv6/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,22 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 2002::/64 ) then { - accept; - } + if (net ~ 2002::/64) then { accept; } } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/global-ipv6/bird_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/global-ipv6/bird_ipam.cfg index 8c4453f41a1..9793102a805 100644 --- a/confd/tests/compiled_templates/explicit_peering/global-ipv6/bird_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/global-ipv6/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = "tunl0"; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel="tunl0"; accept; } accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/global/bird.cfg b/confd/tests/compiled_templates/explicit_peering/global/bird.cfg index d53f6cee487..96298ee5087 100644 --- a/confd/tests/compiled_templates/explicit_peering/global/bird.cfg +++ b/confd/tests/compiled_templates/explicit_peering/global/bird.cfg @@ -7,7 +7,7 @@ include "bird_aggr.cfg"; include "bird_ipam.cfg"; router id 10.192.0.2; -# Set global listen_port +# BGP listen configuration listen bgp address 10.192.0.2 port 150; # Configure synchronization between routing tables and kernel. @@ -61,18 +61,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v4/10.192.0.1-150 +# BGP Protocol Configurations protocol bgp Global_10_192_0_1_port_150 from bgp_template { ttl security off; multihop; @@ -86,9 +75,6 @@ protocol bgp Global_10_192_0_1_port_150 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v4/10.192.0.1-166 protocol bgp Global_10_192_0_1_port_166 from bgp_template { ttl security off; multihop; @@ -102,9 +88,6 @@ protocol bgp Global_10_192_0_1_port_166 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v4/10.192.0.3-150 protocol bgp Global_10_192_0_3_port_150 from bgp_template { ttl security off; multihop; @@ -116,13 +99,5 @@ protocol bgp Global_10_192_0_3_port_150 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Peering is unidirectional, peer will connect to us. + passive on; } - - - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/explicit_peering/global/bird6.cfg b/confd/tests/compiled_templates/explicit_peering/global/bird6.cfg index 69cd8d013d2..d8fc709da3e 100644 --- a/confd/tests/compiled_templates/explicit_peering/global/bird6.cfg +++ b/confd/tests/compiled_templates/explicit_peering/global/bird6.cfg @@ -5,8 +5,9 @@ function apply_communities () # Generated by confd include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP -# Set global listen_port + +router id 10.192.0.2; +# BGP listen configuration listen bgp port 150; # Configure synchronization between routing tables and kernel. diff --git a/confd/tests/compiled_templates/explicit_peering/global/bird6_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/global/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/explicit_peering/global/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/global/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/global/bird_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/global/bird_ipam.cfg index 8c4453f41a1..9793102a805 100644 --- a/confd/tests/compiled_templates/explicit_peering/global/bird_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/global/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = "tunl0"; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel="tunl0"; accept; } accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/keepnexthop-global/bird.cfg b/confd/tests/compiled_templates/explicit_peering/keepnexthop-global/bird.cfg index 11d87399ad9..112bd867963 100644 --- a/confd/tests/compiled_templates/explicit_peering/keepnexthop-global/bird.cfg +++ b/confd/tests/compiled_templates/explicit_peering/keepnexthop-global/bird.cfg @@ -59,17 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- -# This node (kube-master) is configured as a route reflector with cluster ID 10.0.0.1; -# ignore node-to-node mesh setting. - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v4/172.19.4.87 +# BGP Protocol Configurations protocol bgp Global_172_19_4_87 from bgp_template { ttl security off; multihop; @@ -86,9 +76,3 @@ protocol bgp Global_172_19_4_87 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/explicit_peering/keepnexthop-global/bird6.cfg b/confd/tests/compiled_templates/explicit_peering/keepnexthop-global/bird6.cfg index a02a2886a4b..0ddcc956719 100644 --- a/confd/tests/compiled_templates/explicit_peering/keepnexthop-global/bird6.cfg +++ b/confd/tests/compiled_templates/explicit_peering/keepnexthop-global/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,17 +58,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- -# This node (kube-master) is configured as a route reflector with cluster ID 10.0.0.1; -# ignore node-to-node mesh setting. - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v6/ac13::57-50 +# BGP Protocol Configurations protocol bgp Global_ac13__57_port_50 from bgp_template { ttl security off; multihop; @@ -86,9 +75,3 @@ protocol bgp Global_ac13__57_port_50 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/explicit_peering/keepnexthop-global/bird6_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/keepnexthop-global/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/explicit_peering/keepnexthop-global/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/keepnexthop-global/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/keepnexthop-global/bird_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/keepnexthop-global/bird_ipam.cfg index 8c4453f41a1..9793102a805 100644 --- a/confd/tests/compiled_templates/explicit_peering/keepnexthop-global/bird_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/keepnexthop-global/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = "tunl0"; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel="tunl0"; accept; } accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/keepnexthop/bird.cfg b/confd/tests/compiled_templates/explicit_peering/keepnexthop/bird.cfg index 76817b8acf6..bf57ab53226 100644 --- a/confd/tests/compiled_templates/explicit_peering/keepnexthop/bird.cfg +++ b/confd/tests/compiled_templates/explicit_peering/keepnexthop/bird.cfg @@ -59,26 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- -# This node (kube-master) is configured as a route reflector with cluster ID 10.0.0.1; -# ignore node-to-node mesh setting. - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.2 -# Skipping ourselves (10.192.0.2) - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Node_10_192_0_3 from bgp_template { ttl security off; multihop; @@ -94,9 +75,6 @@ protocol bgp Node_10_192_0_3 from bgp_template { rr client; rr cluster id 10.0.0.1; } - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.4 protocol bgp Node_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -112,9 +90,6 @@ protocol bgp Node_10_192_0_4 from bgp_template { rr client; rr cluster id 10.0.0.1; } - - -# For peer /bgp/v1/host/kube-master/peer_v4/172.19.4.87 protocol bgp Node_172_19_4_87 from bgp_template { ttl security off; multihop; @@ -131,4 +106,3 @@ protocol bgp Node_172_19_4_87 from bgp_template { } - diff --git a/confd/tests/compiled_templates/explicit_peering/keepnexthop/bird6.cfg b/confd/tests/compiled_templates/explicit_peering/keepnexthop/bird6.cfg index a43c357b002..658e12d2b6d 100644 --- a/confd/tests/compiled_templates/explicit_peering/keepnexthop/bird6.cfg +++ b/confd/tests/compiled_templates/explicit_peering/keepnexthop/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,22 +58,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- -# This node (kube-master) is configured as a route reflector with cluster ID 10.0.0.1; -# ignore node-to-node mesh setting. - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v6/ac13::57-50 +# BGP Protocol Configurations protocol bgp Node_ac13__57_port_50 from bgp_template { ttl security off; multihop; @@ -91,8 +75,3 @@ protocol bgp Node_ac13__57_port_50 from bgp_template { } -# For peer /bgp/v1/host/kube-master/peer_v6/fe0a::2 -# Skipping ourselves (fe0a::2) - - - diff --git a/confd/tests/compiled_templates/explicit_peering/keepnexthop/bird6_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/keepnexthop/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/explicit_peering/keepnexthop/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/keepnexthop/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/keepnexthop/bird_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/keepnexthop/bird_ipam.cfg index 8c4453f41a1..9793102a805 100644 --- a/confd/tests/compiled_templates/explicit_peering/keepnexthop/bird_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/keepnexthop/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = "tunl0"; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel="tunl0"; accept; } accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/local-as-global-ipv6/bird.cfg b/confd/tests/compiled_templates/explicit_peering/local-as-global-ipv6/bird.cfg index 47c6ec2b81b..a1bf0c77497 100644 --- a/confd/tests/compiled_templates/explicit_peering/local-as-global-ipv6/bird.cfg +++ b/confd/tests/compiled_templates/explicit_peering/local-as-global-ipv6/bird.cfg @@ -59,18 +59,6 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured +# No BGP peers configured for this node -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. diff --git a/confd/tests/compiled_templates/explicit_peering/local-as-global-ipv6/bird6.cfg b/confd/tests/compiled_templates/explicit_peering/local-as-global-ipv6/bird6.cfg index d277789a263..12a4a14d5fe 100644 --- a/confd/tests/compiled_templates/explicit_peering/local-as-global-ipv6/bird6.cfg +++ b/confd/tests/compiled_templates/explicit_peering/local-as-global-ipv6/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug all; @@ -59,21 +58,11 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v6/2001::102 +# BGP Protocol Configurations protocol bgp Global_2001__102 from bgp_template { ttl security off; multihop; + local as 64567; neighbor 2001::102 as 64567; import filter { accept; # Prior to introduction of BGP Filters we used "import all" so use default accept behaviour on import @@ -83,9 +72,6 @@ protocol bgp Global_2001__102 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v6/2001::104 protocol bgp Global_2001__104 from bgp_template { ttl security off; multihop; @@ -96,29 +82,8 @@ protocol bgp Global_2001__104 from bgp_template { accept; # Prior to introduction of BGP Filters we used "import all" so use default accept behaviour on import }; export filter { - calico_export_to_bgp_peers(true); + calico_export_to_bgp_peers(false); reject; }; # Only want to export routes for workloads. - passive on; # Peering is unidirectional, peer will connect to us. + passive on; } - - - - - - - - - - - - - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - - - - diff --git a/confd/tests/compiled_templates/explicit_peering/local-as-global-ipv6/bird6_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/local-as-global-ipv6/bird6_ipam.cfg index 6fd518a50a9..94ba3ce5199 100644 --- a/confd/tests/compiled_templates/explicit_peering/local-as-global-ipv6/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/local-as-global-ipv6/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,22 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 2002::/64 ) then { - accept; - } + if (net ~ 2002::/64) then { accept; } } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/local-as-global-ipv6/bird_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/local-as-global-ipv6/bird_ipam.cfg index 8c4453f41a1..9793102a805 100644 --- a/confd/tests/compiled_templates/explicit_peering/local-as-global-ipv6/bird_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/local-as-global-ipv6/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = "tunl0"; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel="tunl0"; accept; } accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/local-as-global/bird.cfg b/confd/tests/compiled_templates/explicit_peering/local-as-global/bird.cfg index 67ba07e4393..2585f933f1a 100644 --- a/confd/tests/compiled_templates/explicit_peering/local-as-global/bird.cfg +++ b/confd/tests/compiled_templates/explicit_peering/local-as-global/bird.cfg @@ -7,7 +7,7 @@ include "bird_aggr.cfg"; include "bird_ipam.cfg"; router id 10.192.0.2; -# Set global listen_port +# BGP listen configuration listen bgp port 150; # Configure synchronization between routing tables and kernel. @@ -61,22 +61,11 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v4/10.192.0.1-150 +# BGP Protocol Configurations protocol bgp Global_10_192_0_1_port_150 from bgp_template { ttl security off; multihop; - local as 65001; + local as 64567; neighbor 10.192.0.1 port 150 as 64567; source address 10.192.0.2; # The local address we use for the TCP connection import filter { @@ -88,13 +77,10 @@ protocol bgp Global_10_192_0_1_port_150 from bgp_template { }; # Only want to export routes for workloads. allow local as 1; } - - -# For peer /bgp/v1/global/peer_v4/10.192.0.1-166 protocol bgp Global_10_192_0_1_port_166 from bgp_template { ttl security off; multihop; - local as 65002; + local as 64567; neighbor 10.192.0.1 port 166 as 64567; source address 10.192.0.2; # The local address we use for the TCP connection import filter { @@ -105,27 +91,17 @@ protocol bgp Global_10_192_0_1_port_166 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v4/10.192.0.3-150 protocol bgp Global_10_192_0_3_port_150 from bgp_template { ttl security off; multihop; + local as 64515; neighbor 10.192.0.3 port 150 as 64567; import filter { accept; # Prior to introduction of BGP Filters we used "import all" so use default accept behaviour on import }; export filter { - calico_export_to_bgp_peers(true); + calico_export_to_bgp_peers(false); reject; }; # Only want to export routes for workloads. - passive on; # Peering is unidirectional, peer will connect to us. + passive on; } - - - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/explicit_peering/local-as-global/bird6.cfg b/confd/tests/compiled_templates/explicit_peering/local-as-global/bird6.cfg index 69cd8d013d2..d8fc709da3e 100644 --- a/confd/tests/compiled_templates/explicit_peering/local-as-global/bird6.cfg +++ b/confd/tests/compiled_templates/explicit_peering/local-as-global/bird6.cfg @@ -5,8 +5,9 @@ function apply_communities () # Generated by confd include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP -# Set global listen_port + +router id 10.192.0.2; +# BGP listen configuration listen bgp port 150; # Configure synchronization between routing tables and kernel. diff --git a/confd/tests/compiled_templates/explicit_peering/local-as-global/bird6_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/local-as-global/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/explicit_peering/local-as-global/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/local-as-global/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/local-as-global/bird_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/local-as-global/bird_ipam.cfg index 8c4453f41a1..9793102a805 100644 --- a/confd/tests/compiled_templates/explicit_peering/local-as-global/bird_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/local-as-global/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = "tunl0"; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel="tunl0"; accept; } accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/local-as-ipv6/bird.cfg b/confd/tests/compiled_templates/explicit_peering/local-as-ipv6/bird.cfg index 3b0dcb4b994..ec2e394c035 100644 --- a/confd/tests/compiled_templates/explicit_peering/local-as-ipv6/bird.cfg +++ b/confd/tests/compiled_templates/explicit_peering/local-as-ipv6/bird.cfg @@ -59,23 +59,6 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - - +# No BGP peers configured for this node diff --git a/confd/tests/compiled_templates/explicit_peering/local-as-ipv6/bird6.cfg b/confd/tests/compiled_templates/explicit_peering/local-as-ipv6/bird6.cfg index 90186b5d29e..f0b8d6b70e6 100644 --- a/confd/tests/compiled_templates/explicit_peering/local-as-ipv6/bird6.cfg +++ b/confd/tests/compiled_templates/explicit_peering/local-as-ipv6/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,23 +58,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v6/2001::101 +# BGP Protocol Configurations protocol bgp Node_2001__101 from bgp_template { ttl security off; multihop; @@ -90,13 +73,10 @@ protocol bgp Node_2001__101 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v6/2001::102 protocol bgp Node_2001__102 from bgp_template { ttl security off; multihop; - local as 65002; + local as 64512; neighbor 2001::102 as 64512; source address 2001::103; # The local address we use for the TCP connection import filter { @@ -108,4 +88,3 @@ protocol bgp Node_2001__102 from bgp_template { }; # Only want to export routes for workloads. allow local as 1; } - diff --git a/confd/tests/compiled_templates/explicit_peering/local-as-ipv6/bird6_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/local-as-ipv6/bird6_ipam.cfg index 6fd518a50a9..94ba3ce5199 100644 --- a/confd/tests/compiled_templates/explicit_peering/local-as-ipv6/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/local-as-ipv6/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,22 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 2002::/64 ) then { - accept; - } + if (net ~ 2002::/64) then { accept; } } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/local-as-ipv6/bird_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/local-as-ipv6/bird_ipam.cfg index 8c4453f41a1..9793102a805 100644 --- a/confd/tests/compiled_templates/explicit_peering/local-as-ipv6/bird_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/local-as-ipv6/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = "tunl0"; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel="tunl0"; accept; } accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/local-as/bird.cfg b/confd/tests/compiled_templates/explicit_peering/local-as/bird.cfg index 7f65162c6ff..eceea0a42c8 100644 --- a/confd/tests/compiled_templates/explicit_peering/local-as/bird.cfg +++ b/confd/tests/compiled_templates/explicit_peering/local-as/bird.cfg @@ -59,27 +59,11 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Node_10_192_0_3 from bgp_template { ttl security off; multihop; - local as 65002; + local as 64512; neighbor 10.192.0.3 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection import filter { @@ -91,9 +75,6 @@ protocol bgp Node_10_192_0_3 from bgp_template { }; # Only want to export routes for workloads. allow local as 1; } - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.4 protocol bgp Node_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -110,4 +91,3 @@ protocol bgp Node_10_192_0_4 from bgp_template { } - diff --git a/confd/tests/compiled_templates/explicit_peering/local-as/bird6.cfg b/confd/tests/compiled_templates/explicit_peering/local-as/bird6.cfg index 626928faa0c..79551934020 100644 --- a/confd/tests/compiled_templates/explicit_peering/local-as/bird6.cfg +++ b/confd/tests/compiled_templates/explicit_peering/local-as/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -44,3 +44,4 @@ protocol direct { } # IPv6 disabled on this node. + diff --git a/confd/tests/compiled_templates/explicit_peering/local-as/bird6_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/local-as/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/explicit_peering/local-as/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/local-as/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/local-as/bird_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/local-as/bird_ipam.cfg index 8c4453f41a1..9793102a805 100644 --- a/confd/tests/compiled_templates/explicit_peering/local-as/bird_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/local-as/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = "tunl0"; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel="tunl0"; accept; } accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/local_bgp_peer/bird.cfg b/confd/tests/compiled_templates/explicit_peering/local_bgp_peer/bird.cfg index fd596e3f939..bfbb401167f 100644 --- a/confd/tests/compiled_templates/explicit_peering/local_bgp_peer/bird.cfg +++ b/confd/tests/compiled_templates/explicit_peering/local_bgp_peer/bird.cfg @@ -62,28 +62,7 @@ template bgp bgp_template { function 'bgp_import-only-filter_importFilterV4'() { if ((net ~ 44.0.0.0/16)) then { accept; } } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# Skipping local bgp peer (192.168.162.134) - - - - - - - -# ------------- Global local bgp peers ------------- - -# For peer /bgp/v1/global/peer_v4/192.168.162.134 +# BGP Protocol Configurations protocol bgp Local_Workload_192_168_162_134 from bgp_template { ttl security off; multihop; @@ -99,27 +78,6 @@ protocol bgp Local_Workload_192_168_162_134 from bgp_template { }; # Only want to export routes for workloads. passive on; } - - - - -# ------------- Node-specific peers ------------- - - - - -# Skipping local bgp peer (192.168.162.136) - - - - - - - - -# ------------- Node-specific local BGP peers ------------- - -# For peer /bgp/v1/host/kube-master/peer_v4/192.168.162.136 protocol bgp Local_Workload_192_168_162_136 from bgp_template { ttl security off; multihop; @@ -137,5 +95,3 @@ protocol bgp Local_Workload_192_168_162_136 from bgp_template { } - - diff --git a/confd/tests/compiled_templates/explicit_peering/local_bgp_peer/bird6.cfg b/confd/tests/compiled_templates/explicit_peering/local_bgp_peer/bird6.cfg index c23746fa576..a9b763a8410 100644 --- a/confd/tests/compiled_templates/explicit_peering/local_bgp_peer/bird6.cfg +++ b/confd/tests/compiled_templates/explicit_peering/local_bgp_peer/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -62,28 +61,7 @@ template bgp bgp_template { function 'bgp_import-only-filter_importFilterV6'() { if ((net ~ 5000::0/64)) then { accept; } } - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# Skipping local bgp peer (fd00:10:244:0:586d:4461:e980:a284) - - - - - - - -# ------------- Global local bgp peers ------------- - -# For peer /bgp/v1/global/peer_v6/fd00:10:244:0:586d:4461:e980:a284 +# BGP Protocol Configurations protocol bgp Local_Workload_fd00_10_244_0_586d_4461_e980_a284 from bgp_template { ttl security off; multihop; @@ -99,25 +77,6 @@ protocol bgp Local_Workload_fd00_10_244_0_586d_4461_e980_a284 from bgp_template }; # Only want to export routes for workloads. passive on; } - - - -# ------------- Node-specific peers ------------- - - - -# Skipping local bgp peer (fd00:10:244:0:586d:4461:e980:a286) - - - - - - - - -# ------------- Node-specific local BGP peers ------------- - -# For peer /bgp/v1/host/kube-master/peer_v6/fd00:10:244:0:586d:4461:e980:a286 protocol bgp Local_Workload_fd00_10_244_0_586d_4461_e980_a286 from bgp_template { ttl security off; multihop; @@ -135,4 +94,3 @@ protocol bgp Local_Workload_fd00_10_244_0_586d_4461_e980_a286 from bgp_template } - diff --git a/confd/tests/compiled_templates/explicit_peering/local_bgp_peer/bird6_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/local_bgp_peer/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/explicit_peering/local_bgp_peer/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/local_bgp_peer/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/local_bgp_peer/bird_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/local_bgp_peer/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/explicit_peering/local_bgp_peer/bird_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/local_bgp_peer/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/route_reflector/bird.cfg b/confd/tests/compiled_templates/explicit_peering/route_reflector/bird.cfg index ce4f7de0989..ce6d6654c85 100644 --- a/confd/tests/compiled_templates/explicit_peering/route_reflector/bird.cfg +++ b/confd/tests/compiled_templates/explicit_peering/route_reflector/bird.cfg @@ -59,22 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- -# This node (kube-master) is configured as a route reflector with cluster ID 10.0.0.1; -# ignore node-to-node mesh setting. - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Node_10_192_0_3 from bgp_template { ttl security off; multihop; @@ -90,9 +75,6 @@ protocol bgp Node_10_192_0_3 from bgp_template { rr client; rr cluster id 10.0.0.1; } - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.4 protocol bgp Node_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -108,9 +90,6 @@ protocol bgp Node_10_192_0_4 from bgp_template { rr client; rr cluster id 10.0.0.1; } - - -# For peer /bgp/v1/host/kube-master/peer_v4/172.19.4.87 protocol bgp Node_172_19_4_87 from bgp_template { ttl security off; multihop; @@ -126,4 +105,3 @@ protocol bgp Node_172_19_4_87 from bgp_template { } - diff --git a/confd/tests/compiled_templates/explicit_peering/route_reflector/bird6.cfg b/confd/tests/compiled_templates/explicit_peering/route_reflector/bird6.cfg index 9caddeeeb60..e075e7d8b4d 100644 --- a/confd/tests/compiled_templates/explicit_peering/route_reflector/bird6.cfg +++ b/confd/tests/compiled_templates/explicit_peering/route_reflector/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,22 +58,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- -# This node (kube-master) is configured as a route reflector with cluster ID 10.0.0.1; -# ignore node-to-node mesh setting. - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v6/ac13::57-50 +# BGP Protocol Configurations protocol bgp Node_ac13__57_port_50 from bgp_template { ttl security off; multihop; @@ -88,9 +72,6 @@ protocol bgp Node_ac13__57_port_50 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v6/fe0a::3 protocol bgp Node_fe0a__3 from bgp_template { ttl security off; multihop; @@ -106,9 +87,6 @@ protocol bgp Node_fe0a__3 from bgp_template { rr client; rr cluster id 10.0.0.1; } - - -# For peer /bgp/v1/host/kube-master/peer_v6/fe0a::4 protocol bgp Node_fe0a__4 from bgp_template { ttl security off; multihop; @@ -126,4 +104,3 @@ protocol bgp Node_fe0a__4 from bgp_template { } - diff --git a/confd/tests/compiled_templates/explicit_peering/route_reflector/bird6_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/route_reflector/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/explicit_peering/route_reflector/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/route_reflector/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/route_reflector/bird_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/route_reflector/bird_ipam.cfg index 8c4453f41a1..9793102a805 100644 --- a/confd/tests/compiled_templates/explicit_peering/route_reflector/bird_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/route_reflector/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = "tunl0"; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel="tunl0"; accept; } accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/route_reflector_v6_by_ip/bird.cfg b/confd/tests/compiled_templates/explicit_peering/route_reflector_v6_by_ip/bird.cfg index f70d574d5a1..b73b3a9988e 100644 --- a/confd/tests/compiled_templates/explicit_peering/route_reflector_v6_by_ip/bird.cfg +++ b/confd/tests/compiled_templates/explicit_peering/route_reflector_v6_by_ip/bird.cfg @@ -59,22 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- -# This node (kube-master) is configured as a route reflector with cluster ID 10.0.0.1; -# ignore node-to-node mesh setting. - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v4/172.19.4.87 +# BGP Protocol Configurations protocol bgp Node_172_19_4_87 from bgp_template { ttl security off; multihop; @@ -90,4 +75,3 @@ protocol bgp Node_172_19_4_87 from bgp_template { } - diff --git a/confd/tests/compiled_templates/explicit_peering/route_reflector_v6_by_ip/bird6.cfg b/confd/tests/compiled_templates/explicit_peering/route_reflector_v6_by_ip/bird6.cfg index ee50d29dda8..ac6b83874f9 100644 --- a/confd/tests/compiled_templates/explicit_peering/route_reflector_v6_by_ip/bird6.cfg +++ b/confd/tests/compiled_templates/explicit_peering/route_reflector_v6_by_ip/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,28 +58,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- -# This node (kube-master) is configured as a route reflector with cluster ID 10.0.0.1; -# ignore node-to-node mesh setting. - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v6/fe0a::2 -# Skipping ourselves (fe0a::2) - - - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v6/ac13::57-50 +# BGP Protocol Configurations protocol bgp Node_ac13__57_port_50 from bgp_template { ttl security off; multihop; @@ -94,9 +72,6 @@ protocol bgp Node_ac13__57_port_50 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v6/fe0a::3 protocol bgp Node_fe0a__3 from bgp_template { ttl security off; multihop; @@ -112,9 +87,6 @@ protocol bgp Node_fe0a__3 from bgp_template { rr client; rr cluster id 10.0.0.1; } - - -# For peer /bgp/v1/host/kube-master/peer_v6/fe0a::4 protocol bgp Node_fe0a__4 from bgp_template { ttl security off; multihop; @@ -130,6 +102,3 @@ protocol bgp Node_fe0a__4 from bgp_template { rr client; rr cluster id 10.0.0.1; } - - - diff --git a/confd/tests/compiled_templates/explicit_peering/route_reflector_v6_by_ip/bird6_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/route_reflector_v6_by_ip/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/explicit_peering/route_reflector_v6_by_ip/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/route_reflector_v6_by_ip/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/route_reflector_v6_by_ip/bird_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/route_reflector_v6_by_ip/bird_ipam.cfg index 8c4453f41a1..9793102a805 100644 --- a/confd/tests/compiled_templates/explicit_peering/route_reflector_v6_by_ip/bird_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/route_reflector_v6_by_ip/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = "tunl0"; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel="tunl0"; accept; } accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/selectors/bird.cfg b/confd/tests/compiled_templates/explicit_peering/selectors/bird.cfg index 87bfec9ed03..ef4fbeb9aec 100644 --- a/confd/tests/compiled_templates/explicit_peering/selectors/bird.cfg +++ b/confd/tests/compiled_templates/explicit_peering/selectors/bird.cfg @@ -59,23 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.3-500 +# BGP Protocol Configurations protocol bgp Node_10_192_0_3_port_500 from bgp_template { ttl security off; multihop; @@ -89,9 +73,6 @@ protocol bgp Node_10_192_0_3_port_500 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.4 protocol bgp Node_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -107,4 +88,3 @@ protocol bgp Node_10_192_0_4 from bgp_template { } - diff --git a/confd/tests/compiled_templates/explicit_peering/selectors/bird6.cfg b/confd/tests/compiled_templates/explicit_peering/selectors/bird6.cfg index 4afa9eb0563..db6313ad243 100644 --- a/confd/tests/compiled_templates/explicit_peering/selectors/bird6.cfg +++ b/confd/tests/compiled_templates/explicit_peering/selectors/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,23 +58,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v6/fd5f::3-500 +# BGP Protocol Configurations protocol bgp Node_fd5f__3_port_500 from bgp_template { ttl security off; multihop; @@ -89,9 +72,6 @@ protocol bgp Node_fd5f__3_port_500 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v6/fd5f::4 protocol bgp Node_fd5f__4 from bgp_template { ttl security off; multihop; @@ -107,4 +87,3 @@ protocol bgp Node_fd5f__4 from bgp_template { } - diff --git a/confd/tests/compiled_templates/explicit_peering/selectors/bird6_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/selectors/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/explicit_peering/selectors/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/selectors/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/selectors/bird_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/selectors/bird_ipam.cfg index 8c4453f41a1..9793102a805 100644 --- a/confd/tests/compiled_templates/explicit_peering/selectors/bird_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/selectors/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = "tunl0"; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel="tunl0"; accept; } accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/selectors/step2/bird.cfg b/confd/tests/compiled_templates/explicit_peering/selectors/step2/bird.cfg index 93cd1272626..593fb031b42 100644 --- a/confd/tests/compiled_templates/explicit_peering/selectors/step2/bird.cfg +++ b/confd/tests/compiled_templates/explicit_peering/selectors/step2/bird.cfg @@ -59,23 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Node_10_192_0_3 from bgp_template { ttl security off; multihop; @@ -89,9 +73,6 @@ protocol bgp Node_10_192_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.4 protocol bgp Node_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -107,4 +88,3 @@ protocol bgp Node_10_192_0_4 from bgp_template { } - diff --git a/confd/tests/compiled_templates/explicit_peering/selectors/step2/bird6.cfg b/confd/tests/compiled_templates/explicit_peering/selectors/step2/bird6.cfg index a712c03929e..8e9d72065a6 100644 --- a/confd/tests/compiled_templates/explicit_peering/selectors/step2/bird6.cfg +++ b/confd/tests/compiled_templates/explicit_peering/selectors/step2/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,23 +58,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v6/fd5f::3 +# BGP Protocol Configurations protocol bgp Node_fd5f__3 from bgp_template { ttl security off; multihop; @@ -89,9 +72,6 @@ protocol bgp Node_fd5f__3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v6/fd5f::4 protocol bgp Node_fd5f__4 from bgp_template { ttl security off; multihop; @@ -107,4 +87,3 @@ protocol bgp Node_fd5f__4 from bgp_template { } - diff --git a/confd/tests/compiled_templates/explicit_peering/selectors/step2/bird6_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/selectors/step2/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/explicit_peering/selectors/step2/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/selectors/step2/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/selectors/step2/bird_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/selectors/step2/bird_ipam.cfg index 8c4453f41a1..9793102a805 100644 --- a/confd/tests/compiled_templates/explicit_peering/selectors/step2/bird_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/selectors/step2/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = "tunl0"; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel="tunl0"; accept; } accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/specific_node/bird.cfg b/confd/tests/compiled_templates/explicit_peering/specific_node/bird.cfg index dafd0fc7d83..e1c2c18b234 100644 --- a/confd/tests/compiled_templates/explicit_peering/specific_node/bird.cfg +++ b/confd/tests/compiled_templates/explicit_peering/specific_node/bird.cfg @@ -59,23 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Node_10_192_0_3 from bgp_template { ttl security off; multihop; @@ -89,9 +73,6 @@ protocol bgp Node_10_192_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.4 protocol bgp Node_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -107,4 +88,3 @@ protocol bgp Node_10_192_0_4 from bgp_template { } - diff --git a/confd/tests/compiled_templates/explicit_peering/specific_node/bird6.cfg b/confd/tests/compiled_templates/explicit_peering/specific_node/bird6.cfg index 626928faa0c..79551934020 100644 --- a/confd/tests/compiled_templates/explicit_peering/specific_node/bird6.cfg +++ b/confd/tests/compiled_templates/explicit_peering/specific_node/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -44,3 +44,4 @@ protocol direct { } # IPv6 disabled on this node. + diff --git a/confd/tests/compiled_templates/explicit_peering/specific_node/bird6_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/specific_node/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/explicit_peering/specific_node/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/specific_node/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/explicit_peering/specific_node/bird_ipam.cfg b/confd/tests/compiled_templates/explicit_peering/specific_node/bird_ipam.cfg index 8c4453f41a1..9793102a805 100644 --- a/confd/tests/compiled_templates/explicit_peering/specific_node/bird_ipam.cfg +++ b/confd/tests/compiled_templates/explicit_peering/specific_node/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = "tunl0"; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel="tunl0"; accept; } accept; } diff --git a/confd/tests/compiled_templates/ignored_interfaces/bird.cfg b/confd/tests/compiled_templates/ignored_interfaces/bird.cfg index 5b2bd2ce801..36af3877917 100644 --- a/confd/tests/compiled_templates/ignored_interfaces/bird.cfg +++ b/confd/tests/compiled_templates/ignored_interfaces/bird.cfg @@ -51,19 +51,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v4 -# Skipping ourselves (10.192.0.2) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v4 +# BGP Protocol Configurations protocol bgp Mesh_10_192_0_3 from bgp_template { neighbor 10.192.0.3 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -73,12 +61,8 @@ protocol bgp Mesh_10_192_0_3 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v4 protocol bgp Mesh_10_192_0_4 from bgp_template { neighbor 10.192.0.4 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -88,16 +72,5 @@ protocol bgp Mesh_10_192_0_4 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/ignored_interfaces/bird6.cfg b/confd/tests/compiled_templates/ignored_interfaces/bird6.cfg index af62009da03..5a87108a0e7 100644 --- a/confd/tests/compiled_templates/ignored_interfaces/bird6.cfg +++ b/confd/tests/compiled_templates/ignored_interfaces/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -35,7 +35,6 @@ protocol direct { interface -"iface-1", -"iface-2", -"cali*", -"kube-ipvs*", "*"; } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -51,52 +50,27 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v6 -# Skipping ourselves (2001::103) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v6 +# BGP Protocol Configurations protocol bgp Mesh_2001__102 from bgp_template { neighbor 2001::102 as 64512; source address 2001::103; # The local address we use for the TCP connection import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. + # topology is and therefore have to trust the ToR/RR. export filter { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v6 protocol bgp Mesh_2001__104 from bgp_template { neighbor 2001::104 as 64512; source address 2001::103; # The local address we use for the TCP connection import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. + # topology is and therefore have to trust the ToR/RR. export filter { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/ignored_interfaces/bird6_ipam.cfg b/confd/tests/compiled_templates/ignored_interfaces/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/ignored_interfaces/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/ignored_interfaces/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/ignored_interfaces/bird_aggr.cfg b/confd/tests/compiled_templates/ignored_interfaces/bird_aggr.cfg index 5a623513074..9d86b3b0205 100644 --- a/confd/tests/compiled_templates/ignored_interfaces/bird_aggr.cfg +++ b/confd/tests/compiled_templates/ignored_interfaces/bird_aggr.cfg @@ -8,7 +8,6 @@ protocol static { route 192.168.221.64/26 blackhole; } - # Aggregation of routes on this host; export the block, nothing beneath it. function calico_aggr () { diff --git a/confd/tests/compiled_templates/ignored_interfaces/bird_ipam.cfg b/confd/tests/compiled_templates/ignored_interfaces/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/ignored_interfaces/bird_ipam.cfg +++ b/confd/tests/compiled_templates/ignored_interfaces/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/mesh/bgp-export/bird.cfg b/confd/tests/compiled_templates/mesh/bgp-export/bird.cfg index 1bd9d150777..78e57248c29 100644 --- a/confd/tests/compiled_templates/mesh/bgp-export/bird.cfg +++ b/confd/tests/compiled_templates/mesh/bgp-export/bird.cfg @@ -59,19 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v4 -# Skipping ourselves (10.192.0.2) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v4 +# BGP Protocol Configurations protocol bgp Mesh_10_192_0_3 from bgp_template { neighbor 10.192.0.3 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -81,12 +69,8 @@ protocol bgp Mesh_10_192_0_3 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v4 protocol bgp Mesh_10_192_0_4 from bgp_template { neighbor 10.192.0.4 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -96,16 +80,7 @@ protocol bgp Mesh_10_192_0_4 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/bgp-export/bird6.cfg b/confd/tests/compiled_templates/mesh/bgp-export/bird6.cfg index b40cd73235a..79551934020 100644 --- a/confd/tests/compiled_templates/mesh/bgp-export/bird6.cfg +++ b/confd/tests/compiled_templates/mesh/bgp-export/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { diff --git a/confd/tests/compiled_templates/mesh/bgp-export/bird6_ipam.cfg b/confd/tests/compiled_templates/mesh/bgp-export/bird6_ipam.cfg index 2706563a32a..ffd817c9c3c 100644 --- a/confd/tests/compiled_templates/mesh/bgp-export/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/bgp-export/bird6_ipam.cfg @@ -1,10 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - - if ( net ~ 2002:102::/64 ) then { reject; } -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -26,26 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (net ~ 2002:102::/64) then { reject; } # BGP export is disabled. + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 2002:101::/64 ) then { - accept; - } - # Skip 2002:102::/64 as BGP export is disabled for it - if ( net ~ 2002:103::/64 ) then { - accept; - } + if (net ~ 2002:101::/64) then { accept; } + if (net ~ 2002:103::/64) then { accept; } } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/mesh/bgp-export/bird_ipam.cfg b/confd/tests/compiled_templates/mesh/bgp-export/bird_ipam.cfg index 6f9ad064e34..2f662cdab79 100644 --- a/confd/tests/compiled_templates/mesh/bgp-export/bird_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/bgp-export/bird_ipam.cfg @@ -1,10 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - - if ( net ~ 192.168.2.0/24 ) then { reject; } -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -26,42 +20,22 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (net ~ 192.168.2.0/24) then { reject; } # BGP export is disabled. + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.1.0/24 ) then { - accept; - } - # Skip 192.168.2.0/24 as BGP export is disabled for it - if ( net ~ 192.168.3.0/24 ) then { - accept; - } + if (net ~ 192.168.1.0/24) then { accept; } + if (net ~ 192.168.3.0/24) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.1.0/24 ) then { - krt_tunnel = ""; - accept; - } - - if ( net ~ 192.168.2.0/24 ) then { - krt_tunnel = ""; - accept; - } - - if ( net ~ 192.168.3.0/24 ) then { - krt_tunnel = ""; - accept; - } - + if (net ~ 192.168.1.0/24) then { krt_tunnel=""; accept; } + if (net ~ 192.168.2.0/24) then { krt_tunnel=""; accept; } + if (net ~ 192.168.3.0/24) then { krt_tunnel=""; accept; } accept; } diff --git a/confd/tests/compiled_templates/mesh/communities/bird.cfg b/confd/tests/compiled_templates/mesh/communities/bird.cfg index 91495ea3872..736a0c8698d 100644 --- a/confd/tests/compiled_templates/mesh/communities/bird.cfg +++ b/confd/tests/compiled_templates/mesh/communities/bird.cfg @@ -12,7 +12,7 @@ include "bird_aggr.cfg"; include "bird_ipam.cfg"; router id 10.192.0.2; -# Set global listen_port +# BGP listen configuration listen bgp port 177; # Configure synchronization between routing tables and kernel. @@ -66,19 +66,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v4 -# Skipping ourselves (10.192.0.2) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v4 +# BGP Protocol Configurations protocol bgp Mesh_10_192_0_3 from bgp_template { neighbor 10.192.0.3 port 177 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -88,12 +76,8 @@ protocol bgp Mesh_10_192_0_3 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v4 protocol bgp Mesh_10_192_0_4 from bgp_template { neighbor 10.192.0.4 port 177 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -103,16 +87,7 @@ protocol bgp Mesh_10_192_0_4 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/communities/bird6.cfg b/confd/tests/compiled_templates/mesh/communities/bird6.cfg index 42eef60e2c8..3f691ca970e 100644 --- a/confd/tests/compiled_templates/mesh/communities/bird6.cfg +++ b/confd/tests/compiled_templates/mesh/communities/bird6.cfg @@ -8,8 +8,9 @@ function apply_communities () # Generated by confd include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP -# Set global listen_port + +router id 10.192.0.2; +# BGP listen configuration listen bgp port 177; # Configure synchronization between routing tables and kernel. diff --git a/confd/tests/compiled_templates/mesh/communities/bird6_ipam.cfg b/confd/tests/compiled_templates/mesh/communities/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/mesh/communities/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/communities/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/mesh/communities/bird_ipam.cfg b/confd/tests/compiled_templates/mesh/communities/bird_ipam.cfg index 6bf2288cb65..cf88b7a78ec 100644 --- a/confd/tests/compiled_templates/mesh/communities/bird_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/communities/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = ""; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel=""; accept; } accept; } diff --git a/confd/tests/compiled_templates/mesh/communities/step2/bird.cfg b/confd/tests/compiled_templates/mesh/communities/step2/bird.cfg index 8e1d61d05ed..c40910916fd 100644 --- a/confd/tests/compiled_templates/mesh/communities/step2/bird.cfg +++ b/confd/tests/compiled_templates/mesh/communities/step2/bird.cfg @@ -11,7 +11,7 @@ include "bird_aggr.cfg"; include "bird_ipam.cfg"; router id 10.192.0.2; -# Set node listen_port +# BGP listen configuration listen bgp port 180; # Configure synchronization between routing tables and kernel. @@ -65,19 +65,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v4 -# Skipping ourselves (10.192.0.2) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v4 +# BGP Protocol Configurations protocol bgp Mesh_10_192_0_3 from bgp_template { neighbor 10.192.0.3 port 177 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -87,12 +75,8 @@ protocol bgp Mesh_10_192_0_3 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v4 protocol bgp Mesh_10_192_0_4 from bgp_template { neighbor 10.192.0.4 port 177 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -102,16 +86,7 @@ protocol bgp Mesh_10_192_0_4 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/communities/step2/bird6.cfg b/confd/tests/compiled_templates/mesh/communities/step2/bird6.cfg index 4094a7fe0bd..34ecd5163ba 100644 --- a/confd/tests/compiled_templates/mesh/communities/step2/bird6.cfg +++ b/confd/tests/compiled_templates/mesh/communities/step2/bird6.cfg @@ -9,8 +9,8 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP -# Set node listen_port +router id 10.192.0.2; +# BGP listen configuration listen bgp port 180; # Configure synchronization between routing tables and kernel. diff --git a/confd/tests/compiled_templates/mesh/communities/step2/bird6_ipam.cfg b/confd/tests/compiled_templates/mesh/communities/step2/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/mesh/communities/step2/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/communities/step2/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/mesh/communities/step2/bird_ipam.cfg b/confd/tests/compiled_templates/mesh/communities/step2/bird_ipam.cfg index 6bf2288cb65..cf88b7a78ec 100644 --- a/confd/tests/compiled_templates/mesh/communities/step2/bird_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/communities/step2/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = ""; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel=""; accept; } accept; } diff --git a/confd/tests/compiled_templates/mesh/hash/bird.cfg b/confd/tests/compiled_templates/mesh/hash/bird.cfg index 71d73be28f4..2d00152df59 100644 --- a/confd/tests/compiled_templates/mesh/hash/bird.cfg +++ b/confd/tests/compiled_templates/mesh/hash/bird.cfg @@ -59,19 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v4 -# Skipping ourselves (10.192.0.2) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v4 +# BGP Protocol Configurations protocol bgp Mesh_10_192_0_3 from bgp_template { neighbor 10.192.0.3 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -81,12 +69,8 @@ protocol bgp Mesh_10_192_0_3 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v4 protocol bgp Mesh_10_192_0_4 from bgp_template { neighbor 10.192.0.4 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -96,16 +80,5 @@ protocol bgp Mesh_10_192_0_4 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/hash/bird6.cfg b/confd/tests/compiled_templates/mesh/hash/bird6.cfg index a9375ed10fb..5fbc57952d7 100644 --- a/confd/tests/compiled_templates/mesh/hash/bird6.cfg +++ b/confd/tests/compiled_templates/mesh/hash/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 25.79.212.8; # Use IP address generated by nodename's hash +router id 25.79.212.8; # Configure synchronization between routing tables and kernel. protocol kernel { diff --git a/confd/tests/compiled_templates/mesh/hash/bird6_ipam.cfg b/confd/tests/compiled_templates/mesh/hash/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/mesh/hash/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/hash/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/mesh/hash/bird_aggr.cfg b/confd/tests/compiled_templates/mesh/hash/bird_aggr.cfg index 5a623513074..9d86b3b0205 100644 --- a/confd/tests/compiled_templates/mesh/hash/bird_aggr.cfg +++ b/confd/tests/compiled_templates/mesh/hash/bird_aggr.cfg @@ -8,7 +8,6 @@ protocol static { route 192.168.221.64/26 blackhole; } - # Aggregation of routes on this host; export the block, nothing beneath it. function calico_aggr () { diff --git a/confd/tests/compiled_templates/mesh/hash/bird_ipam.cfg b/confd/tests/compiled_templates/mesh/hash/bird_ipam.cfg index 6bf2288cb65..cf88b7a78ec 100644 --- a/confd/tests/compiled_templates/mesh/hash/bird_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/hash/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = ""; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel=""; accept; } accept; } diff --git a/confd/tests/compiled_templates/mesh/ipip-always/bird.cfg b/confd/tests/compiled_templates/mesh/ipip-always/bird.cfg index 1bd9d150777..78e57248c29 100644 --- a/confd/tests/compiled_templates/mesh/ipip-always/bird.cfg +++ b/confd/tests/compiled_templates/mesh/ipip-always/bird.cfg @@ -59,19 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v4 -# Skipping ourselves (10.192.0.2) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v4 +# BGP Protocol Configurations protocol bgp Mesh_10_192_0_3 from bgp_template { neighbor 10.192.0.3 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -81,12 +69,8 @@ protocol bgp Mesh_10_192_0_3 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v4 protocol bgp Mesh_10_192_0_4 from bgp_template { neighbor 10.192.0.4 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -96,16 +80,7 @@ protocol bgp Mesh_10_192_0_4 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/ipip-always/bird6.cfg b/confd/tests/compiled_templates/mesh/ipip-always/bird6.cfg index 626928faa0c..79551934020 100644 --- a/confd/tests/compiled_templates/mesh/ipip-always/bird6.cfg +++ b/confd/tests/compiled_templates/mesh/ipip-always/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -44,3 +44,4 @@ protocol direct { } # IPv6 disabled on this node. + diff --git a/confd/tests/compiled_templates/mesh/ipip-always/bird6_ipam.cfg b/confd/tests/compiled_templates/mesh/ipip-always/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/mesh/ipip-always/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/ipip-always/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/mesh/ipip-always/bird_ipam.cfg b/confd/tests/compiled_templates/mesh/ipip-always/bird_ipam.cfg index 8c4453f41a1..9793102a805 100644 --- a/confd/tests/compiled_templates/mesh/ipip-always/bird_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/ipip-always/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = "tunl0"; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel="tunl0"; accept; } accept; } diff --git a/confd/tests/compiled_templates/mesh/ipip-cross-subnet/bird.cfg b/confd/tests/compiled_templates/mesh/ipip-cross-subnet/bird.cfg index c9de101dcb0..01307b00df4 100644 --- a/confd/tests/compiled_templates/mesh/ipip-cross-subnet/bird.cfg +++ b/confd/tests/compiled_templates/mesh/ipip-cross-subnet/bird.cfg @@ -59,19 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v4 -# Skipping ourselves (10.192.0.2) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v4 +# BGP Protocol Configurations protocol bgp Mesh_10_192_0_3 from bgp_template { neighbor 10.192.0.3 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -81,12 +69,8 @@ protocol bgp Mesh_10_192_0_3 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v4 protocol bgp Mesh_10_192_0_6 from bgp_template { neighbor 10.192.0.6 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -96,16 +80,7 @@ protocol bgp Mesh_10_192_0_6 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/ipip-cross-subnet/bird6.cfg b/confd/tests/compiled_templates/mesh/ipip-cross-subnet/bird6.cfg index 626928faa0c..79551934020 100644 --- a/confd/tests/compiled_templates/mesh/ipip-cross-subnet/bird6.cfg +++ b/confd/tests/compiled_templates/mesh/ipip-cross-subnet/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -44,3 +44,4 @@ protocol direct { } # IPv6 disabled on this node. + diff --git a/confd/tests/compiled_templates/mesh/ipip-cross-subnet/bird6_ipam.cfg b/confd/tests/compiled_templates/mesh/ipip-cross-subnet/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/mesh/ipip-cross-subnet/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/ipip-cross-subnet/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/mesh/ipip-cross-subnet/bird_ipam.cfg b/confd/tests/compiled_templates/mesh/ipip-cross-subnet/bird_ipam.cfg index 57300f8a0a5..102c6373120 100644 --- a/confd/tests/compiled_templates/mesh/ipip-cross-subnet/bird_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/ipip-cross-subnet/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,31 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - if defined(bgp_next_hop) && ( bgp_next_hop ~ 10.192.0.0/16 ) then - krt_tunnel = ""; - else - krt_tunnel = "tunl0"; - accept; - } - + if (net ~ 192.168.0.0/16) then { if (defined(bgp_next_hop)&&(bgp_next_hop ~ 10.192.0.0/16)) then krt_tunnel=""; else krt_tunnel="tunl0"; accept; } accept; } diff --git a/confd/tests/compiled_templates/mesh/ipip-off/bird.cfg b/confd/tests/compiled_templates/mesh/ipip-off/bird.cfg index 1bd9d150777..78e57248c29 100644 --- a/confd/tests/compiled_templates/mesh/ipip-off/bird.cfg +++ b/confd/tests/compiled_templates/mesh/ipip-off/bird.cfg @@ -59,19 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v4 -# Skipping ourselves (10.192.0.2) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v4 +# BGP Protocol Configurations protocol bgp Mesh_10_192_0_3 from bgp_template { neighbor 10.192.0.3 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -81,12 +69,8 @@ protocol bgp Mesh_10_192_0_3 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v4 protocol bgp Mesh_10_192_0_4 from bgp_template { neighbor 10.192.0.4 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -96,16 +80,7 @@ protocol bgp Mesh_10_192_0_4 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/ipip-off/bird6.cfg b/confd/tests/compiled_templates/mesh/ipip-off/bird6.cfg index 2926b229262..22377411dc6 100644 --- a/confd/tests/compiled_templates/mesh/ipip-off/bird6.cfg +++ b/confd/tests/compiled_templates/mesh/ipip-off/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,52 +58,27 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v6 -# Skipping ourselves (2001::103) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v6 +# BGP Protocol Configurations protocol bgp Mesh_2001__102 from bgp_template { neighbor 2001::102 as 64512; source address 2001::103; # The local address we use for the TCP connection import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. + # topology is and therefore have to trust the ToR/RR. export filter { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v6 protocol bgp Mesh_2001__104 from bgp_template { neighbor 2001::104 as 64512; source address 2001::103; # The local address we use for the TCP connection import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. + # topology is and therefore have to trust the ToR/RR. export filter { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/ipip-off/bird6_ipam.cfg b/confd/tests/compiled_templates/mesh/ipip-off/bird6_ipam.cfg index 6fd518a50a9..94ba3ce5199 100644 --- a/confd/tests/compiled_templates/mesh/ipip-off/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/ipip-off/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,22 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 2002::/64 ) then { - accept; - } + if (net ~ 2002::/64) then { accept; } } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/mesh/ipip-off/bird_ipam.cfg b/confd/tests/compiled_templates/mesh/ipip-off/bird_ipam.cfg index 6bf2288cb65..cf88b7a78ec 100644 --- a/confd/tests/compiled_templates/mesh/ipip-off/bird_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/ipip-off/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = ""; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel=""; accept; } accept; } diff --git a/confd/tests/compiled_templates/mesh/password/step1/bird.cfg b/confd/tests/compiled_templates/mesh/password/step1/bird.cfg index 1bd9d150777..78e57248c29 100644 --- a/confd/tests/compiled_templates/mesh/password/step1/bird.cfg +++ b/confd/tests/compiled_templates/mesh/password/step1/bird.cfg @@ -59,19 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v4 -# Skipping ourselves (10.192.0.2) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v4 +# BGP Protocol Configurations protocol bgp Mesh_10_192_0_3 from bgp_template { neighbor 10.192.0.3 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -81,12 +69,8 @@ protocol bgp Mesh_10_192_0_3 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v4 protocol bgp Mesh_10_192_0_4 from bgp_template { neighbor 10.192.0.4 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -96,16 +80,7 @@ protocol bgp Mesh_10_192_0_4 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/password/step1/bird6.cfg b/confd/tests/compiled_templates/mesh/password/step1/bird6.cfg index 2926b229262..22377411dc6 100644 --- a/confd/tests/compiled_templates/mesh/password/step1/bird6.cfg +++ b/confd/tests/compiled_templates/mesh/password/step1/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,52 +58,27 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v6 -# Skipping ourselves (2001::103) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v6 +# BGP Protocol Configurations protocol bgp Mesh_2001__102 from bgp_template { neighbor 2001::102 as 64512; source address 2001::103; # The local address we use for the TCP connection import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. + # topology is and therefore have to trust the ToR/RR. export filter { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v6 protocol bgp Mesh_2001__104 from bgp_template { neighbor 2001::104 as 64512; source address 2001::103; # The local address we use for the TCP connection import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. + # topology is and therefore have to trust the ToR/RR. export filter { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/password/step1/bird6_ipam.cfg b/confd/tests/compiled_templates/mesh/password/step1/bird6_ipam.cfg index 6fd518a50a9..94ba3ce5199 100644 --- a/confd/tests/compiled_templates/mesh/password/step1/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/password/step1/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,22 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 2002::/64 ) then { - accept; - } + if (net ~ 2002::/64) then { accept; } } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/mesh/password/step1/bird_ipam.cfg b/confd/tests/compiled_templates/mesh/password/step1/bird_ipam.cfg index 6bf2288cb65..cf88b7a78ec 100644 --- a/confd/tests/compiled_templates/mesh/password/step1/bird_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/password/step1/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = ""; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel=""; accept; } accept; } diff --git a/confd/tests/compiled_templates/mesh/password/step2/bird.cfg b/confd/tests/compiled_templates/mesh/password/step2/bird.cfg index e4e84f20144..d2c172cc676 100644 --- a/confd/tests/compiled_templates/mesh/password/step2/bird.cfg +++ b/confd/tests/compiled_templates/mesh/password/step2/bird.cfg @@ -59,19 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v4 -# Skipping ourselves (10.192.0.2) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v4 +# BGP Protocol Configurations protocol bgp Mesh_10_192_0_3 from bgp_template { neighbor 10.192.0.3 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -81,13 +69,9 @@ protocol bgp Mesh_10_192_0_3 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; password "password-a"; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v4 protocol bgp Mesh_10_192_0_4 from bgp_template { neighbor 10.192.0.4 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -97,17 +81,6 @@ protocol bgp Mesh_10_192_0_4 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; password "password-a"; } - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/password/step2/bird6.cfg b/confd/tests/compiled_templates/mesh/password/step2/bird6.cfg index 5edf0d476d2..ca4cf3d9c6c 100644 --- a/confd/tests/compiled_templates/mesh/password/step2/bird6.cfg +++ b/confd/tests/compiled_templates/mesh/password/step2/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,54 +58,29 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v6 -# Skipping ourselves (2001::103) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v6 +# BGP Protocol Configurations protocol bgp Mesh_2001__102 from bgp_template { neighbor 2001::102 as 64512; source address 2001::103; # The local address we use for the TCP connection import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. + # topology is and therefore have to trust the ToR/RR. export filter { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. password "password-a"; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v6 protocol bgp Mesh_2001__104 from bgp_template { neighbor 2001::104 as 64512; source address 2001::103; # The local address we use for the TCP connection import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. + # topology is and therefore have to trust the ToR/RR. export filter { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; password "password-a"; } - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/password/step2/bird6_ipam.cfg b/confd/tests/compiled_templates/mesh/password/step2/bird6_ipam.cfg index 6fd518a50a9..94ba3ce5199 100644 --- a/confd/tests/compiled_templates/mesh/password/step2/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/password/step2/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,22 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 2002::/64 ) then { - accept; - } + if (net ~ 2002::/64) then { accept; } } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/mesh/password/step2/bird_ipam.cfg b/confd/tests/compiled_templates/mesh/password/step2/bird_ipam.cfg index 6bf2288cb65..cf88b7a78ec 100644 --- a/confd/tests/compiled_templates/mesh/password/step2/bird_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/password/step2/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = ""; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel=""; accept; } accept; } diff --git a/confd/tests/compiled_templates/mesh/password/step3/bird.cfg b/confd/tests/compiled_templates/mesh/password/step3/bird.cfg index 3e9cf2a12cf..e0d8c3e0b87 100644 --- a/confd/tests/compiled_templates/mesh/password/step3/bird.cfg +++ b/confd/tests/compiled_templates/mesh/password/step3/bird.cfg @@ -59,19 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v4 -# Skipping ourselves (10.192.0.2) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v4 +# BGP Protocol Configurations protocol bgp Mesh_10_192_0_3 from bgp_template { neighbor 10.192.0.3 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -81,13 +69,9 @@ protocol bgp Mesh_10_192_0_3 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; password "new-password-a"; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v4 protocol bgp Mesh_10_192_0_4 from bgp_template { neighbor 10.192.0.4 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -97,17 +81,6 @@ protocol bgp Mesh_10_192_0_4 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; password "new-password-a"; } - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/password/step3/bird6.cfg b/confd/tests/compiled_templates/mesh/password/step3/bird6.cfg index bb3b369b56d..2c69dbf5c86 100644 --- a/confd/tests/compiled_templates/mesh/password/step3/bird6.cfg +++ b/confd/tests/compiled_templates/mesh/password/step3/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,54 +58,29 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v6 -# Skipping ourselves (2001::103) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v6 +# BGP Protocol Configurations protocol bgp Mesh_2001__102 from bgp_template { neighbor 2001::102 as 64512; source address 2001::103; # The local address we use for the TCP connection import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. + # topology is and therefore have to trust the ToR/RR. export filter { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. password "new-password-a"; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v6 protocol bgp Mesh_2001__104 from bgp_template { neighbor 2001::104 as 64512; source address 2001::103; # The local address we use for the TCP connection import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. + # topology is and therefore have to trust the ToR/RR. export filter { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; password "new-password-a"; } - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/password/step3/bird6_ipam.cfg b/confd/tests/compiled_templates/mesh/password/step3/bird6_ipam.cfg index 6fd518a50a9..94ba3ce5199 100644 --- a/confd/tests/compiled_templates/mesh/password/step3/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/password/step3/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,22 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 2002::/64 ) then { - accept; - } + if (net ~ 2002::/64) then { accept; } } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/mesh/password/step3/bird_ipam.cfg b/confd/tests/compiled_templates/mesh/password/step3/bird_ipam.cfg index 6bf2288cb65..cf88b7a78ec 100644 --- a/confd/tests/compiled_templates/mesh/password/step3/bird_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/password/step3/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = ""; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel=""; accept; } accept; } diff --git a/confd/tests/compiled_templates/mesh/restart-time/bird.cfg b/confd/tests/compiled_templates/mesh/restart-time/bird.cfg index 0fc2fb78eac..af5881a7f44 100644 --- a/confd/tests/compiled_templates/mesh/restart-time/bird.cfg +++ b/confd/tests/compiled_templates/mesh/restart-time/bird.cfg @@ -59,19 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v4 -# Skipping ourselves (10.192.0.2) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v4 +# BGP Protocol Configurations protocol bgp Mesh_10_192_0_3 from bgp_template { neighbor 10.192.0.3 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -81,13 +69,9 @@ protocol bgp Mesh_10_192_0_3 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; graceful restart time 10; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v4 protocol bgp Mesh_10_192_0_4 from bgp_template { neighbor 10.192.0.4 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -97,17 +81,8 @@ protocol bgp Mesh_10_192_0_4 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; graceful restart time 10; } - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/restart-time/bird6.cfg b/confd/tests/compiled_templates/mesh/restart-time/bird6.cfg index cf85bb99261..dc6061077ef 100644 --- a/confd/tests/compiled_templates/mesh/restart-time/bird6.cfg +++ b/confd/tests/compiled_templates/mesh/restart-time/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,54 +58,29 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v6 -# Skipping ourselves (2001::103) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v6 +# BGP Protocol Configurations protocol bgp Mesh_2001__102 from bgp_template { neighbor 2001::102 as 64512; source address 2001::103; # The local address we use for the TCP connection import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. + # topology is and therefore have to trust the ToR/RR. export filter { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. graceful restart time 10; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v6 protocol bgp Mesh_2001__104 from bgp_template { neighbor 2001::104 as 64512; source address 2001::103; # The local address we use for the TCP connection import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. + # topology is and therefore have to trust the ToR/RR. export filter { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; graceful restart time 10; } - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/restart-time/bird6_ipam.cfg b/confd/tests/compiled_templates/mesh/restart-time/bird6_ipam.cfg index 6fd518a50a9..94ba3ce5199 100644 --- a/confd/tests/compiled_templates/mesh/restart-time/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/restart-time/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,22 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 2002::/64 ) then { - accept; - } + if (net ~ 2002::/64) then { accept; } } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/mesh/restart-time/bird_ipam.cfg b/confd/tests/compiled_templates/mesh/restart-time/bird_ipam.cfg index 6bf2288cb65..cf88b7a78ec 100644 --- a/confd/tests/compiled_templates/mesh/restart-time/bird_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/restart-time/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = ""; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel=""; accept; } accept; } diff --git a/confd/tests/compiled_templates/mesh/route-reflector-mesh-enabled/bird.cfg b/confd/tests/compiled_templates/mesh/route-reflector-mesh-enabled/bird.cfg index d9619af45f6..e0c7c224e84 100644 --- a/confd/tests/compiled_templates/mesh/route-reflector-mesh-enabled/bird.cfg +++ b/confd/tests/compiled_templates/mesh/route-reflector-mesh-enabled/bird.cfg @@ -59,19 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v4 -# Skipping ourselves (10.192.0.2) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v4 +# BGP Protocol Configurations protocol bgp Mesh_10_192_0_3 from bgp_template { neighbor 10.192.0.3 as 64532; source address 10.192.0.2; # The local address we use for the TCP connection @@ -81,27 +69,8 @@ protocol bgp Mesh_10_192_0_3 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v4 -# Skipping 10.192.0.4 (kube-node-2) as it is a route reflector with cluster ID 10.0.0.1 - - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.4 protocol bgp Node_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -117,4 +86,3 @@ protocol bgp Node_10_192_0_4 from bgp_template { } - diff --git a/confd/tests/compiled_templates/mesh/route-reflector-mesh-enabled/bird6.cfg b/confd/tests/compiled_templates/mesh/route-reflector-mesh-enabled/bird6.cfg index 178ede5bfd7..b65b0351298 100644 --- a/confd/tests/compiled_templates/mesh/route-reflector-mesh-enabled/bird6.cfg +++ b/confd/tests/compiled_templates/mesh/route-reflector-mesh-enabled/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,49 +58,18 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v6 -# Skipping ourselves (fe0a::2) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v6 +# BGP Protocol Configurations protocol bgp Mesh_fe0a__4 from bgp_template { neighbor fe0a::4 as 64532; source address fe0a::2; # The local address we use for the TCP connection import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. + # topology is and therefore have to trust the ToR/RR. export filter { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v6 -# Skipping fe0a::6 (kube-node-2) as it is a route reflector with cluster ID 10.0.0.1 - - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v6/fe0a::6 protocol bgp Node_fe0a__6 from bgp_template { ttl security off; multihop; @@ -117,4 +85,3 @@ protocol bgp Node_fe0a__6 from bgp_template { } - diff --git a/confd/tests/compiled_templates/mesh/route-reflector-mesh-enabled/bird6_ipam.cfg b/confd/tests/compiled_templates/mesh/route-reflector-mesh-enabled/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/mesh/route-reflector-mesh-enabled/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/route-reflector-mesh-enabled/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/mesh/route-reflector-mesh-enabled/bird_ipam.cfg b/confd/tests/compiled_templates/mesh/route-reflector-mesh-enabled/bird_ipam.cfg index 8c4453f41a1..9793102a805 100644 --- a/confd/tests/compiled_templates/mesh/route-reflector-mesh-enabled/bird_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/route-reflector-mesh-enabled/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = "tunl0"; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel="tunl0"; accept; } accept; } diff --git a/confd/tests/compiled_templates/mesh/static-routes-exclude-node/bird.cfg b/confd/tests/compiled_templates/mesh/static-routes-exclude-node/bird.cfg index 1bd9d150777..78e57248c29 100644 --- a/confd/tests/compiled_templates/mesh/static-routes-exclude-node/bird.cfg +++ b/confd/tests/compiled_templates/mesh/static-routes-exclude-node/bird.cfg @@ -59,19 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v4 -# Skipping ourselves (10.192.0.2) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v4 +# BGP Protocol Configurations protocol bgp Mesh_10_192_0_3 from bgp_template { neighbor 10.192.0.3 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -81,12 +69,8 @@ protocol bgp Mesh_10_192_0_3 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v4 protocol bgp Mesh_10_192_0_4 from bgp_template { neighbor 10.192.0.4 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -96,16 +80,7 @@ protocol bgp Mesh_10_192_0_4 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/static-routes-exclude-node/bird6.cfg b/confd/tests/compiled_templates/mesh/static-routes-exclude-node/bird6.cfg index 6d07a3dc308..38cdf2e0762 100644 --- a/confd/tests/compiled_templates/mesh/static-routes-exclude-node/bird6.cfg +++ b/confd/tests/compiled_templates/mesh/static-routes-exclude-node/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,53 +58,28 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v6 -# Skipping ourselves (fdf5:10::2) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v6 +# BGP Protocol Configurations protocol bgp Mesh_fdf5_10__3 from bgp_template { neighbor fdf5:10::3 as 64512; source address fdf5:10::2; # The local address we use for the TCP connection import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. + # topology is and therefore have to trust the ToR/RR. export filter { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v6 protocol bgp Mesh_fdf5_10__4 from bgp_template { neighbor fdf5:10::4 as 64512; source address fdf5:10::2; # The local address we use for the TCP connection import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. + # topology is and therefore have to trust the ToR/RR. export filter { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/static-routes-exclude-node/bird6_ipam.cfg b/confd/tests/compiled_templates/mesh/static-routes-exclude-node/bird6_ipam.cfg index 1a5c1e90049..8aa26c5b6ca 100644 --- a/confd/tests/compiled_templates/mesh/static-routes-exclude-node/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/static-routes-exclude-node/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,22 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { # Don't program static routes into kernel. if ( net ~ fd00:96::/112 ) then { reject; } - accept; } diff --git a/confd/tests/compiled_templates/mesh/static-routes-exclude-node/bird_ipam.cfg b/confd/tests/compiled_templates/mesh/static-routes-exclude-node/bird_ipam.cfg index 025d4909edb..9f19a9dae7a 100644 --- a/confd/tests/compiled_templates/mesh/static-routes-exclude-node/bird_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/static-routes-exclude-node/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,14 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } @@ -45,11 +35,6 @@ filter calico_kernel_programming { # Don't program static routes into kernel. if ( net ~ 10.101.0.0/16 ) then { reject; } - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = "tunl0"; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel="tunl0"; accept; } accept; } diff --git a/confd/tests/compiled_templates/mesh/static-routes-exclude-node/step2/bird.cfg b/confd/tests/compiled_templates/mesh/static-routes-exclude-node/step2/bird.cfg index 1bd9d150777..78e57248c29 100644 --- a/confd/tests/compiled_templates/mesh/static-routes-exclude-node/step2/bird.cfg +++ b/confd/tests/compiled_templates/mesh/static-routes-exclude-node/step2/bird.cfg @@ -59,19 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v4 -# Skipping ourselves (10.192.0.2) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v4 +# BGP Protocol Configurations protocol bgp Mesh_10_192_0_3 from bgp_template { neighbor 10.192.0.3 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -81,12 +69,8 @@ protocol bgp Mesh_10_192_0_3 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v4 protocol bgp Mesh_10_192_0_4 from bgp_template { neighbor 10.192.0.4 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -96,16 +80,7 @@ protocol bgp Mesh_10_192_0_4 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/static-routes-exclude-node/step2/bird6.cfg b/confd/tests/compiled_templates/mesh/static-routes-exclude-node/step2/bird6.cfg index 6d07a3dc308..38cdf2e0762 100644 --- a/confd/tests/compiled_templates/mesh/static-routes-exclude-node/step2/bird6.cfg +++ b/confd/tests/compiled_templates/mesh/static-routes-exclude-node/step2/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,53 +58,28 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v6 -# Skipping ourselves (fdf5:10::2) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v6 +# BGP Protocol Configurations protocol bgp Mesh_fdf5_10__3 from bgp_template { neighbor fdf5:10::3 as 64512; source address fdf5:10::2; # The local address we use for the TCP connection import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. + # topology is and therefore have to trust the ToR/RR. export filter { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v6 protocol bgp Mesh_fdf5_10__4 from bgp_template { neighbor fdf5:10::4 as 64512; source address fdf5:10::2; # The local address we use for the TCP connection import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. + # topology is and therefore have to trust the ToR/RR. export filter { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/static-routes-exclude-node/step2/bird6_ipam.cfg b/confd/tests/compiled_templates/mesh/static-routes-exclude-node/step2/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/mesh/static-routes-exclude-node/step2/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/static-routes-exclude-node/step2/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/mesh/static-routes-exclude-node/step2/bird_ipam.cfg b/confd/tests/compiled_templates/mesh/static-routes-exclude-node/step2/bird_ipam.cfg index 8c4453f41a1..9793102a805 100644 --- a/confd/tests/compiled_templates/mesh/static-routes-exclude-node/step2/bird_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/static-routes-exclude-node/step2/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = "tunl0"; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel="tunl0"; accept; } accept; } diff --git a/confd/tests/compiled_templates/mesh/static-routes-no-ipv4-address/bird6.cfg b/confd/tests/compiled_templates/mesh/static-routes-no-ipv4-address/bird6.cfg index 76f74006ad1..2d05dab7ed7 100644 --- a/confd/tests/compiled_templates/mesh/static-routes-no-ipv4-address/bird6.cfg +++ b/confd/tests/compiled_templates/mesh/static-routes-no-ipv4-address/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.10.10.10; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.10.10.10; # Configure synchronization between routing tables and kernel. protocol kernel { diff --git a/confd/tests/compiled_templates/mesh/static-routes-no-ipv4-address/bird6_ipam.cfg b/confd/tests/compiled_templates/mesh/static-routes-no-ipv4-address/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/mesh/static-routes-no-ipv4-address/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/static-routes-no-ipv4-address/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/mesh/static-routes-no-ipv4-address/bird_ipam.cfg b/confd/tests/compiled_templates/mesh/static-routes-no-ipv4-address/bird_ipam.cfg index ddc693f28ff..c4ed32970a3 100644 --- a/confd/tests/compiled_templates/mesh/static-routes-no-ipv4-address/bird_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/static-routes-no-ipv4-address/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,9 +20,7 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } @@ -38,10 +31,7 @@ function calico_export_to_bgp_peers(bool internal_peer) { # Export static routes. if ( net ~ 10.101.0.0/16 ) then { accept; } if ( net ~ 10.101.0.101/32 ) then { accept; } - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } diff --git a/confd/tests/compiled_templates/mesh/static-routes/bird.cfg b/confd/tests/compiled_templates/mesh/static-routes/bird.cfg index 1bd9d150777..78e57248c29 100644 --- a/confd/tests/compiled_templates/mesh/static-routes/bird.cfg +++ b/confd/tests/compiled_templates/mesh/static-routes/bird.cfg @@ -59,19 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v4 -# Skipping ourselves (10.192.0.2) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v4 +# BGP Protocol Configurations protocol bgp Mesh_10_192_0_3 from bgp_template { neighbor 10.192.0.3 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -81,12 +69,8 @@ protocol bgp Mesh_10_192_0_3 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v4 protocol bgp Mesh_10_192_0_4 from bgp_template { neighbor 10.192.0.4 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -96,16 +80,7 @@ protocol bgp Mesh_10_192_0_4 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/static-routes/bird6.cfg b/confd/tests/compiled_templates/mesh/static-routes/bird6.cfg index 6d07a3dc308..38cdf2e0762 100644 --- a/confd/tests/compiled_templates/mesh/static-routes/bird6.cfg +++ b/confd/tests/compiled_templates/mesh/static-routes/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,53 +58,28 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v6 -# Skipping ourselves (fdf5:10::2) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v6 +# BGP Protocol Configurations protocol bgp Mesh_fdf5_10__3 from bgp_template { neighbor fdf5:10::3 as 64512; source address fdf5:10::2; # The local address we use for the TCP connection import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. + # topology is and therefore have to trust the ToR/RR. export filter { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v6 protocol bgp Mesh_fdf5_10__4 from bgp_template { neighbor fdf5:10::4 as 64512; source address fdf5:10::2; # The local address we use for the TCP connection import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. + # topology is and therefore have to trust the ToR/RR. export filter { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/static-routes/bird6_ipam.cfg b/confd/tests/compiled_templates/mesh/static-routes/bird6_ipam.cfg index 7e615a4af05..0de187af88d 100644 --- a/confd/tests/compiled_templates/mesh/static-routes/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/static-routes/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,9 +20,7 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } @@ -38,13 +31,11 @@ function calico_export_to_bgp_peers(bool internal_peer) { # Export static routes. if ( net ~ fd00:96::/112 ) then { accept; } if ( net ~ fd00:96::28/128 ) then { accept; } - } filter calico_kernel_programming { # Don't program static routes into kernel. if ( net ~ fd00:96::/112 ) then { reject; } - accept; } diff --git a/confd/tests/compiled_templates/mesh/static-routes/bird_ipam.cfg b/confd/tests/compiled_templates/mesh/static-routes/bird_ipam.cfg index 7462b303165..bedb7840336 100644 --- a/confd/tests/compiled_templates/mesh/static-routes/bird_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/static-routes/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,9 +20,7 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } @@ -39,10 +32,7 @@ function calico_export_to_bgp_peers(bool internal_peer) { if ( net ~ 10.101.0.0/16 ) then { accept; } if ( net ~ 10.101.0.101/32 ) then { accept; } if ( net ~ 80.15.0.0/24 ) then { accept; } - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } @@ -51,11 +41,6 @@ filter calico_kernel_programming { # Don't program static routes into kernel. if ( net ~ 10.101.0.0/16 ) then { reject; } if ( net ~ 80.15.0.0/24 ) then { reject; } - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = "tunl0"; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel="tunl0"; accept; } accept; } diff --git a/confd/tests/compiled_templates/mesh/static-routes/step2/bird.cfg b/confd/tests/compiled_templates/mesh/static-routes/step2/bird.cfg index 1bd9d150777..78e57248c29 100644 --- a/confd/tests/compiled_templates/mesh/static-routes/step2/bird.cfg +++ b/confd/tests/compiled_templates/mesh/static-routes/step2/bird.cfg @@ -59,19 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v4 -# Skipping ourselves (10.192.0.2) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v4 +# BGP Protocol Configurations protocol bgp Mesh_10_192_0_3 from bgp_template { neighbor 10.192.0.3 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -81,12 +69,8 @@ protocol bgp Mesh_10_192_0_3 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v4 protocol bgp Mesh_10_192_0_4 from bgp_template { neighbor 10.192.0.4 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -96,16 +80,7 @@ protocol bgp Mesh_10_192_0_4 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/static-routes/step2/bird6.cfg b/confd/tests/compiled_templates/mesh/static-routes/step2/bird6.cfg index 6d07a3dc308..38cdf2e0762 100644 --- a/confd/tests/compiled_templates/mesh/static-routes/step2/bird6.cfg +++ b/confd/tests/compiled_templates/mesh/static-routes/step2/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,53 +58,28 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v6 -# Skipping ourselves (fdf5:10::2) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v6 +# BGP Protocol Configurations protocol bgp Mesh_fdf5_10__3 from bgp_template { neighbor fdf5:10::3 as 64512; source address fdf5:10::2; # The local address we use for the TCP connection import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. + # topology is and therefore have to trust the ToR/RR. export filter { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v6 protocol bgp Mesh_fdf5_10__4 from bgp_template { neighbor fdf5:10::4 as 64512; source address fdf5:10::2; # The local address we use for the TCP connection import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. + # topology is and therefore have to trust the ToR/RR. export filter { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/static-routes/step2/bird6_ipam.cfg b/confd/tests/compiled_templates/mesh/static-routes/step2/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/mesh/static-routes/step2/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/static-routes/step2/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/mesh/static-routes/step2/bird_ipam.cfg b/confd/tests/compiled_templates/mesh/static-routes/step2/bird_ipam.cfg index 8c4453f41a1..9793102a805 100644 --- a/confd/tests/compiled_templates/mesh/static-routes/step2/bird_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/static-routes/step2/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - krt_tunnel = "tunl0"; - accept; - } - + if (net ~ 192.168.0.0/16) then { krt_tunnel="tunl0"; accept; } accept; } diff --git a/confd/tests/compiled_templates/mesh/vxlan-always/bird.cfg b/confd/tests/compiled_templates/mesh/vxlan-always/bird.cfg index 1bd9d150777..78e57248c29 100644 --- a/confd/tests/compiled_templates/mesh/vxlan-always/bird.cfg +++ b/confd/tests/compiled_templates/mesh/vxlan-always/bird.cfg @@ -59,19 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v4 -# Skipping ourselves (10.192.0.2) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v4 +# BGP Protocol Configurations protocol bgp Mesh_10_192_0_3 from bgp_template { neighbor 10.192.0.3 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -81,12 +69,8 @@ protocol bgp Mesh_10_192_0_3 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v4 protocol bgp Mesh_10_192_0_4 from bgp_template { neighbor 10.192.0.4 as 64512; source address 10.192.0.2; # The local address we use for the TCP connection @@ -96,16 +80,7 @@ protocol bgp Mesh_10_192_0_4 from bgp_template { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/vxlan-always/bird6.cfg b/confd/tests/compiled_templates/mesh/vxlan-always/bird6.cfg index 6d07a3dc308..d7102792bf1 100644 --- a/confd/tests/compiled_templates/mesh/vxlan-always/bird6.cfg +++ b/confd/tests/compiled_templates/mesh/vxlan-always/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,53 +58,26 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - - - - - -# For peer /bgp/v1/host/kube-master/ip_addr_v6 -# Skipping ourselves (fdf5:10::2) - - - -# For peer /bgp/v1/host/kube-node-1/ip_addr_v6 +# BGP Protocol Configurations protocol bgp Mesh_fdf5_10__3 from bgp_template { neighbor fdf5:10::3 as 64512; source address fdf5:10::2; # The local address we use for the TCP connection import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. + # topology is and therefore have to trust the ToR/RR. export filter { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# For peer /bgp/v1/host/kube-node-2/ip_addr_v6 protocol bgp Mesh_fdf5_10__4 from bgp_template { neighbor fdf5:10::4 as 64512; source address fdf5:10::2; # The local address we use for the TCP connection import all; # Import all routes, since we don't know what the upstream - # topology is and therefore have to trust the ToR/RR. + # topology is and therefore have to trust the ToR/RR. export filter { calico_export_to_bgp_peers(true); reject; }; # Only want to export routes for workloads. - passive on; # Mesh is unidirectional, peer will connect to us. + passive on; } - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/mesh/vxlan-always/bird6_ipam.cfg b/confd/tests/compiled_templates/mesh/vxlan-always/bird6_ipam.cfg index 1bc8a213447..71344bb0244 100644 --- a/confd/tests/compiled_templates/mesh/vxlan-always/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/vxlan-always/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,26 +20,17 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ dead:beef::/64 ) then { - accept; - } + if (net ~ dead:beef::/64) then { accept; } } filter calico_kernel_programming { - - if ( net ~ dead:beef::/64 ) then { - # Don't program VXLAN routes into the kernel - these are handled by Felix. - reject; - } + if (net ~ dead:beef::/64) then { reject; } # VXLAN routes are handled by Felix. accept; } diff --git a/confd/tests/compiled_templates/mesh/vxlan-always/bird_ipam.cfg b/confd/tests/compiled_templates/mesh/vxlan-always/bird_ipam.cfg index 22bf027b62f..43c9a973206 100644 --- a/confd/tests/compiled_templates/mesh/vxlan-always/bird_ipam.cfg +++ b/confd/tests/compiled_templates/mesh/vxlan-always/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,28 +20,18 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - - if ( net ~ 192.168.0.0/16 ) then { - accept; - } + if (net ~ 192.168.0.0/16) then { accept; } } filter calico_kernel_programming { - - if ( net ~ 192.168.0.0/16 ) then { - # Don't program VXLAN routes into the kernel - these are handled by Felix. - reject; - } - + if (net ~ 192.168.0.0/16) then { reject; } # VXLAN routes are handled by Felix. accept; } diff --git a/confd/tests/compiled_templates/next_hop_mode/global_peers/bird.cfg b/confd/tests/compiled_templates/next_hop_mode/global_peers/bird.cfg index c90733fcbaf..319195b0baa 100644 --- a/confd/tests/compiled_templates/next_hop_mode/global_peers/bird.cfg +++ b/confd/tests/compiled_templates/next_hop_mode/global_peers/bird.cfg @@ -59,18 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v4/10.225.0.4 +# BGP Protocol Configurations protocol bgp Global_10_225_0_4 from bgp_template { ttl security off; multihop; @@ -85,9 +74,6 @@ protocol bgp Global_10_225_0_4 from bgp_template { }; # Only want to export routes for workloads. next hop keep; } - - -# For peer /bgp/v1/global/peer_v4/10.225.0.5 protocol bgp Global_10_225_0_5 from bgp_template { ttl security off; multihop; @@ -104,9 +90,3 @@ protocol bgp Global_10_225_0_5 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/next_hop_mode/global_peers/bird6.cfg b/confd/tests/compiled_templates/next_hop_mode/global_peers/bird6.cfg index 9ff9d6179ed..62dc185bd61 100644 --- a/confd/tests/compiled_templates/next_hop_mode/global_peers/bird6.cfg +++ b/confd/tests/compiled_templates/next_hop_mode/global_peers/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,18 +58,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v6/ffee::10 +# BGP Protocol Configurations protocol bgp Global_ffee__10 from bgp_template { ttl security off; multihop; @@ -85,9 +73,6 @@ protocol bgp Global_ffee__10 from bgp_template { }; # Only want to export routes for workloads. next hop keep; } - - -# For peer /bgp/v1/global/peer_v6/ffee::11 protocol bgp Global_ffee__11 from bgp_template { ttl security off; multihop; @@ -104,9 +89,3 @@ protocol bgp Global_ffee__11 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/next_hop_mode/global_peers/bird6_ipam.cfg b/confd/tests/compiled_templates/next_hop_mode/global_peers/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/next_hop_mode/global_peers/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/next_hop_mode/global_peers/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/next_hop_mode/global_peers/bird_ipam.cfg b/confd/tests/compiled_templates/next_hop_mode/global_peers/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/next_hop_mode/global_peers/bird_ipam.cfg +++ b/confd/tests/compiled_templates/next_hop_mode/global_peers/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/next_hop_mode/route_reflectors/bird.cfg b/confd/tests/compiled_templates/next_hop_mode/route_reflectors/bird.cfg index 836951aefc4..8a1b2ee8e53 100644 --- a/confd/tests/compiled_templates/next_hop_mode/route_reflectors/bird.cfg +++ b/confd/tests/compiled_templates/next_hop_mode/route_reflectors/bird.cfg @@ -59,27 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.2 -# Skipping ourselves (10.192.0.2) - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Node_10_192_0_3 from bgp_template { ttl security off; multihop; @@ -93,9 +73,6 @@ protocol bgp Node_10_192_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.4 protocol bgp Node_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -109,9 +86,6 @@ protocol bgp Node_10_192_0_4 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.225.0.4 protocol bgp Node_10_225_0_4 from bgp_template { ttl security off; multihop; @@ -126,9 +100,6 @@ protocol bgp Node_10_225_0_4 from bgp_template { }; # Only want to export routes for workloads. next hop keep; } - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.225.0.5 protocol bgp Node_10_225_0_5 from bgp_template { ttl security off; multihop; @@ -145,4 +116,3 @@ protocol bgp Node_10_225_0_5 from bgp_template { } - diff --git a/confd/tests/compiled_templates/next_hop_mode/route_reflectors/bird6.cfg b/confd/tests/compiled_templates/next_hop_mode/route_reflectors/bird6.cfg index 483e35eb11d..b89d4889825 100644 --- a/confd/tests/compiled_templates/next_hop_mode/route_reflectors/bird6.cfg +++ b/confd/tests/compiled_templates/next_hop_mode/route_reflectors/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,23 +58,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v6/2001::102 +# BGP Protocol Configurations protocol bgp Node_2001__102 from bgp_template { ttl security off; multihop; @@ -89,13 +72,6 @@ protocol bgp Node_2001__102 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v6/2001::103 -# Skipping ourselves (2001::103) - - -# For peer /bgp/v1/host/kube-master/peer_v6/2001::104 protocol bgp Node_2001__104 from bgp_template { ttl security off; multihop; @@ -109,9 +85,6 @@ protocol bgp Node_2001__104 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v6/ffee::10 protocol bgp Node_ffee__10 from bgp_template { ttl security off; multihop; @@ -126,9 +99,6 @@ protocol bgp Node_ffee__10 from bgp_template { }; # Only want to export routes for workloads. next hop keep; } - - -# For peer /bgp/v1/host/kube-master/peer_v6/ffee::11 protocol bgp Node_ffee__11 from bgp_template { ttl security off; multihop; @@ -145,4 +115,3 @@ protocol bgp Node_ffee__11 from bgp_template { } - diff --git a/confd/tests/compiled_templates/next_hop_mode/route_reflectors/bird6_ipam.cfg b/confd/tests/compiled_templates/next_hop_mode/route_reflectors/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/next_hop_mode/route_reflectors/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/next_hop_mode/route_reflectors/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/next_hop_mode/route_reflectors/bird_ipam.cfg b/confd/tests/compiled_templates/next_hop_mode/route_reflectors/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/next_hop_mode/route_reflectors/bird_ipam.cfg +++ b/confd/tests/compiled_templates/next_hop_mode/route_reflectors/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/password-deadlock/bird.cfg b/confd/tests/compiled_templates/password-deadlock/bird.cfg index b9ade532f9a..3d836246e87 100644 --- a/confd/tests/compiled_templates/password-deadlock/bird.cfg +++ b/confd/tests/compiled_templates/password-deadlock/bird.cfg @@ -59,23 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/node1/peer_v4/10.24.0.2 +# BGP Protocol Configurations protocol bgp Node_10_24_0_2 from bgp_template { ttl security off; multihop; @@ -92,4 +76,3 @@ protocol bgp Node_10_24_0_2 from bgp_template { } - diff --git a/confd/tests/compiled_templates/password-deadlock/bird6.cfg b/confd/tests/compiled_templates/password-deadlock/bird6.cfg index ef5f22bba17..29387e46ff9 100644 --- a/confd/tests/compiled_templates/password-deadlock/bird6.cfg +++ b/confd/tests/compiled_templates/password-deadlock/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.24.0.1; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.24.0.1; # Configure synchronization between routing tables and kernel. protocol kernel { diff --git a/confd/tests/compiled_templates/password-deadlock/bird6_ipam.cfg b/confd/tests/compiled_templates/password-deadlock/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/password-deadlock/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/password-deadlock/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/password-deadlock/bird_ipam.cfg b/confd/tests/compiled_templates/password-deadlock/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/password-deadlock/bird_ipam.cfg +++ b/confd/tests/compiled_templates/password-deadlock/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/password/step1/bird.cfg b/confd/tests/compiled_templates/password/step1/bird.cfg index 58ba8e0f082..17829cca001 100644 --- a/confd/tests/compiled_templates/password/step1/bird.cfg +++ b/confd/tests/compiled_templates/password/step1/bird.cfg @@ -59,23 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/node1/peer_v4/10.24.0.2 +# BGP Protocol Configurations protocol bgp Node_10_24_0_2 from bgp_template { ttl security off; multihop; @@ -89,9 +73,6 @@ protocol bgp Node_10_24_0_2 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/node1/peer_v4/10.24.0.3 protocol bgp Node_10_24_0_3 from bgp_template { ttl security off; multihop; @@ -105,9 +86,6 @@ protocol bgp Node_10_24_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/node1/peer_v4/10.24.10.10 protocol bgp Node_10_24_10_10 from bgp_template { ttl security off; multihop; @@ -123,4 +101,3 @@ protocol bgp Node_10_24_10_10 from bgp_template { } - diff --git a/confd/tests/compiled_templates/password/step1/bird6.cfg b/confd/tests/compiled_templates/password/step1/bird6.cfg index 229637bf7e8..a4efdde987a 100644 --- a/confd/tests/compiled_templates/password/step1/bird6.cfg +++ b/confd/tests/compiled_templates/password/step1/bird6.cfg @@ -5,7 +5,8 @@ function apply_communities () # Generated by confd include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.24.0.1; # Use IPv4 address since router id is 4 octets, even in MP-BGP + +router id 10.24.0.1; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,3 +44,4 @@ protocol direct { } # IPv6 disabled on this node. + diff --git a/confd/tests/compiled_templates/password/step1/bird6_ipam.cfg b/confd/tests/compiled_templates/password/step1/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/password/step1/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/password/step1/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/password/step1/bird_ipam.cfg b/confd/tests/compiled_templates/password/step1/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/password/step1/bird_ipam.cfg +++ b/confd/tests/compiled_templates/password/step1/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/password/step2/bird.cfg b/confd/tests/compiled_templates/password/step2/bird.cfg index b427282c0b7..934bb4bc881 100644 --- a/confd/tests/compiled_templates/password/step2/bird.cfg +++ b/confd/tests/compiled_templates/password/step2/bird.cfg @@ -59,23 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/node1/peer_v4/10.24.0.2 +# BGP Protocol Configurations protocol bgp Node_10_24_0_2 from bgp_template { ttl security off; multihop; @@ -89,9 +73,6 @@ protocol bgp Node_10_24_0_2 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/node1/peer_v4/10.24.0.3 protocol bgp Node_10_24_0_3 from bgp_template { ttl security off; multihop; @@ -106,9 +87,6 @@ protocol bgp Node_10_24_0_3 from bgp_template { }; # Only want to export routes for workloads. password "password-b"; } - - -# For peer /bgp/v1/host/node1/peer_v4/10.24.10.10 protocol bgp Node_10_24_10_10 from bgp_template { ttl security off; multihop; @@ -124,4 +102,3 @@ protocol bgp Node_10_24_10_10 from bgp_template { } - diff --git a/confd/tests/compiled_templates/password/step2/bird6.cfg b/confd/tests/compiled_templates/password/step2/bird6.cfg index 229637bf7e8..a4efdde987a 100644 --- a/confd/tests/compiled_templates/password/step2/bird6.cfg +++ b/confd/tests/compiled_templates/password/step2/bird6.cfg @@ -5,7 +5,8 @@ function apply_communities () # Generated by confd include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.24.0.1; # Use IPv4 address since router id is 4 octets, even in MP-BGP + +router id 10.24.0.1; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,3 +44,4 @@ protocol direct { } # IPv6 disabled on this node. + diff --git a/confd/tests/compiled_templates/password/step2/bird6_ipam.cfg b/confd/tests/compiled_templates/password/step2/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/password/step2/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/password/step2/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/password/step2/bird_ipam.cfg b/confd/tests/compiled_templates/password/step2/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/password/step2/bird_ipam.cfg +++ b/confd/tests/compiled_templates/password/step2/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/password/step3/bird.cfg b/confd/tests/compiled_templates/password/step3/bird.cfg index 9dee450f48c..0d87db9de33 100644 --- a/confd/tests/compiled_templates/password/step3/bird.cfg +++ b/confd/tests/compiled_templates/password/step3/bird.cfg @@ -59,23 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/node1/peer_v4/10.24.0.2 +# BGP Protocol Configurations protocol bgp Node_10_24_0_2 from bgp_template { ttl security off; multihop; @@ -90,9 +74,6 @@ protocol bgp Node_10_24_0_2 from bgp_template { }; # Only want to export routes for workloads. password "password-a"; } - - -# For peer /bgp/v1/host/node1/peer_v4/10.24.0.3 protocol bgp Node_10_24_0_3 from bgp_template { ttl security off; multihop; @@ -107,9 +88,6 @@ protocol bgp Node_10_24_0_3 from bgp_template { }; # Only want to export routes for workloads. password "password-b"; } - - -# For peer /bgp/v1/host/node1/peer_v4/10.24.10.10 protocol bgp Node_10_24_10_10 from bgp_template { ttl security off; multihop; @@ -126,4 +104,3 @@ protocol bgp Node_10_24_10_10 from bgp_template { } - diff --git a/confd/tests/compiled_templates/password/step3/bird6.cfg b/confd/tests/compiled_templates/password/step3/bird6.cfg index 229637bf7e8..a4efdde987a 100644 --- a/confd/tests/compiled_templates/password/step3/bird6.cfg +++ b/confd/tests/compiled_templates/password/step3/bird6.cfg @@ -5,7 +5,8 @@ function apply_communities () # Generated by confd include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.24.0.1; # Use IPv4 address since router id is 4 octets, even in MP-BGP + +router id 10.24.0.1; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,3 +44,4 @@ protocol direct { } # IPv6 disabled on this node. + diff --git a/confd/tests/compiled_templates/password/step3/bird6_ipam.cfg b/confd/tests/compiled_templates/password/step3/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/password/step3/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/password/step3/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/password/step3/bird_ipam.cfg b/confd/tests/compiled_templates/password/step3/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/password/step3/bird_ipam.cfg +++ b/confd/tests/compiled_templates/password/step3/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/password/step4/bird.cfg b/confd/tests/compiled_templates/password/step4/bird.cfg index db2f7531661..40d3fc44115 100644 --- a/confd/tests/compiled_templates/password/step4/bird.cfg +++ b/confd/tests/compiled_templates/password/step4/bird.cfg @@ -59,23 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/node1/peer_v4/10.24.0.2 +# BGP Protocol Configurations protocol bgp Node_10_24_0_2 from bgp_template { ttl security off; multihop; @@ -90,9 +74,6 @@ protocol bgp Node_10_24_0_2 from bgp_template { }; # Only want to export routes for workloads. password "password-a"; } - - -# For peer /bgp/v1/host/node1/peer_v4/10.24.0.3 protocol bgp Node_10_24_0_3 from bgp_template { ttl security off; multihop; @@ -107,9 +88,6 @@ protocol bgp Node_10_24_0_3 from bgp_template { }; # Only want to export routes for workloads. password "password-b"; } - - -# For peer /bgp/v1/host/node1/peer_v4/10.24.10.10 protocol bgp Node_10_24_10_10 from bgp_template { ttl security off; multihop; @@ -125,4 +103,3 @@ protocol bgp Node_10_24_10_10 from bgp_template { } - diff --git a/confd/tests/compiled_templates/password/step4/bird6.cfg b/confd/tests/compiled_templates/password/step4/bird6.cfg index 229637bf7e8..a4efdde987a 100644 --- a/confd/tests/compiled_templates/password/step4/bird6.cfg +++ b/confd/tests/compiled_templates/password/step4/bird6.cfg @@ -5,7 +5,8 @@ function apply_communities () # Generated by confd include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.24.0.1; # Use IPv4 address since router id is 4 octets, even in MP-BGP + +router id 10.24.0.1; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,3 +44,4 @@ protocol direct { } # IPv6 disabled on this node. + diff --git a/confd/tests/compiled_templates/password/step4/bird6_ipam.cfg b/confd/tests/compiled_templates/password/step4/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/password/step4/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/password/step4/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/password/step4/bird_ipam.cfg b/confd/tests/compiled_templates/password/step4/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/password/step4/bird_ipam.cfg +++ b/confd/tests/compiled_templates/password/step4/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/password/step5/bird.cfg b/confd/tests/compiled_templates/password/step5/bird.cfg index 54216440327..448e879ce50 100644 --- a/confd/tests/compiled_templates/password/step5/bird.cfg +++ b/confd/tests/compiled_templates/password/step5/bird.cfg @@ -59,23 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/node1/peer_v4/10.24.0.2 +# BGP Protocol Configurations protocol bgp Node_10_24_0_2 from bgp_template { ttl security off; multihop; @@ -90,9 +74,6 @@ protocol bgp Node_10_24_0_2 from bgp_template { }; # Only want to export routes for workloads. password "new-password-a"; } - - -# For peer /bgp/v1/host/node1/peer_v4/10.24.0.3 protocol bgp Node_10_24_0_3 from bgp_template { ttl security off; multihop; @@ -107,9 +88,6 @@ protocol bgp Node_10_24_0_3 from bgp_template { }; # Only want to export routes for workloads. password "new-password-b"; } - - -# For peer /bgp/v1/host/node1/peer_v4/10.24.10.10 protocol bgp Node_10_24_10_10 from bgp_template { ttl security off; multihop; @@ -125,4 +103,3 @@ protocol bgp Node_10_24_10_10 from bgp_template { } - diff --git a/confd/tests/compiled_templates/password/step5/bird6.cfg b/confd/tests/compiled_templates/password/step5/bird6.cfg index 229637bf7e8..a4efdde987a 100644 --- a/confd/tests/compiled_templates/password/step5/bird6.cfg +++ b/confd/tests/compiled_templates/password/step5/bird6.cfg @@ -5,7 +5,8 @@ function apply_communities () # Generated by confd include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.24.0.1; # Use IPv4 address since router id is 4 octets, even in MP-BGP + +router id 10.24.0.1; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,3 +44,4 @@ protocol direct { } # IPv6 disabled on this node. + diff --git a/confd/tests/compiled_templates/password/step5/bird6_ipam.cfg b/confd/tests/compiled_templates/password/step5/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/password/step5/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/password/step5/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/password/step5/bird_ipam.cfg b/confd/tests/compiled_templates/password/step5/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/password/step5/bird_ipam.cfg +++ b/confd/tests/compiled_templates/password/step5/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/password/step6/bird.cfg b/confd/tests/compiled_templates/password/step6/bird.cfg index 06fc4df51fe..d356b0e7748 100644 --- a/confd/tests/compiled_templates/password/step6/bird.cfg +++ b/confd/tests/compiled_templates/password/step6/bird.cfg @@ -59,23 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/node1/peer_v4/10.24.0.2 +# BGP Protocol Configurations protocol bgp Node_10_24_0_2 from bgp_template { ttl security off; multihop; @@ -89,9 +73,6 @@ protocol bgp Node_10_24_0_2 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/node1/peer_v4/10.24.0.3 protocol bgp Node_10_24_0_3 from bgp_template { ttl security off; multihop; @@ -106,9 +87,6 @@ protocol bgp Node_10_24_0_3 from bgp_template { }; # Only want to export routes for workloads. password "new-password-b"; } - - -# For peer /bgp/v1/host/node1/peer_v4/10.24.10.10 protocol bgp Node_10_24_10_10 from bgp_template { ttl security off; multihop; @@ -124,4 +102,3 @@ protocol bgp Node_10_24_10_10 from bgp_template { } - diff --git a/confd/tests/compiled_templates/password/step6/bird6.cfg b/confd/tests/compiled_templates/password/step6/bird6.cfg index 229637bf7e8..a4efdde987a 100644 --- a/confd/tests/compiled_templates/password/step6/bird6.cfg +++ b/confd/tests/compiled_templates/password/step6/bird6.cfg @@ -5,7 +5,8 @@ function apply_communities () # Generated by confd include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.24.0.1; # Use IPv4 address since router id is 4 octets, even in MP-BGP + +router id 10.24.0.1; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,3 +44,4 @@ protocol direct { } # IPv6 disabled on this node. + diff --git a/confd/tests/compiled_templates/password/step6/bird6_ipam.cfg b/confd/tests/compiled_templates/password/step6/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/password/step6/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/password/step6/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/password/step6/bird_ipam.cfg b/confd/tests/compiled_templates/password/step6/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/password/step6/bird_ipam.cfg +++ b/confd/tests/compiled_templates/password/step6/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/reachable_by/global_peers/bird.cfg b/confd/tests/compiled_templates/reachable_by/global_peers/bird.cfg index 443f020470b..0a368f9b3d9 100644 --- a/confd/tests/compiled_templates/reachable_by/global_peers/bird.cfg +++ b/confd/tests/compiled_templates/reachable_by/global_peers/bird.cfg @@ -59,18 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v4/10.225.0.4 +# BGP Protocol Configurations protocol bgp Global_10_225_0_4 from bgp_template { ttl security off; multihop; @@ -85,9 +74,6 @@ protocol bgp Global_10_225_0_4 from bgp_template { }; # Only want to export routes for workloads. next hop keep; } - - -# For peer /bgp/v1/global/peer_v4/10.225.0.5 protocol bgp Global_10_225_0_5 from bgp_template { ttl security off; multihop; @@ -104,9 +90,3 @@ protocol bgp Global_10_225_0_5 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/reachable_by/global_peers/bird6.cfg b/confd/tests/compiled_templates/reachable_by/global_peers/bird6.cfg index dae946aa317..9a4e73bd5d9 100644 --- a/confd/tests/compiled_templates/reachable_by/global_peers/bird6.cfg +++ b/confd/tests/compiled_templates/reachable_by/global_peers/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,18 +58,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v6/ffee::10 +# BGP Protocol Configurations protocol bgp Global_ffee__10 from bgp_template { ttl security off; multihop; @@ -85,9 +73,6 @@ protocol bgp Global_ffee__10 from bgp_template { }; # Only want to export routes for workloads. next hop keep; } - - -# For peer /bgp/v1/global/peer_v6/ffee::11 protocol bgp Global_ffee__11 from bgp_template { ttl security off; multihop; @@ -104,9 +89,3 @@ protocol bgp Global_ffee__11 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/reachable_by/global_peers/bird6_ipam.cfg b/confd/tests/compiled_templates/reachable_by/global_peers/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/reachable_by/global_peers/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/reachable_by/global_peers/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/reachable_by/global_peers/bird_aggr.cfg b/confd/tests/compiled_templates/reachable_by/global_peers/bird_aggr.cfg index 62b03ecd63c..0de4d8f58d9 100644 --- a/confd/tests/compiled_templates/reachable_by/global_peers/bird_aggr.cfg +++ b/confd/tests/compiled_templates/reachable_by/global_peers/bird_aggr.cfg @@ -11,7 +11,6 @@ protocol static { route 10.225.0.5/32 via 10.224.0.1; } - # Aggregation of routes on this host; export the block, nothing beneath it. function calico_aggr () { diff --git a/confd/tests/compiled_templates/reachable_by/global_peers/bird_ipam.cfg b/confd/tests/compiled_templates/reachable_by/global_peers/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/reachable_by/global_peers/bird_ipam.cfg +++ b/confd/tests/compiled_templates/reachable_by/global_peers/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/reachable_by/route_reflectors/bird.cfg b/confd/tests/compiled_templates/reachable_by/route_reflectors/bird.cfg index 8c03cd5af90..a6293113736 100644 --- a/confd/tests/compiled_templates/reachable_by/route_reflectors/bird.cfg +++ b/confd/tests/compiled_templates/reachable_by/route_reflectors/bird.cfg @@ -59,27 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.2 -# Skipping ourselves (10.192.0.2) - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Node_10_192_0_3 from bgp_template { ttl security off; multihop; @@ -93,9 +73,6 @@ protocol bgp Node_10_192_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.4 protocol bgp Node_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -109,9 +86,6 @@ protocol bgp Node_10_192_0_4 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.225.0.4 protocol bgp Node_10_225_0_4 from bgp_template { ttl security off; multihop; @@ -126,9 +100,6 @@ protocol bgp Node_10_225_0_4 from bgp_template { }; # Only want to export routes for workloads. next hop keep; } - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.225.0.5 protocol bgp Node_10_225_0_5 from bgp_template { ttl security off; multihop; @@ -145,4 +116,3 @@ protocol bgp Node_10_225_0_5 from bgp_template { } - diff --git a/confd/tests/compiled_templates/reachable_by/route_reflectors/bird6.cfg b/confd/tests/compiled_templates/reachable_by/route_reflectors/bird6.cfg index 378076af5c1..0dde3f09e05 100644 --- a/confd/tests/compiled_templates/reachable_by/route_reflectors/bird6.cfg +++ b/confd/tests/compiled_templates/reachable_by/route_reflectors/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,23 +58,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v6/2001::102 +# BGP Protocol Configurations protocol bgp Node_2001__102 from bgp_template { ttl security off; multihop; @@ -89,13 +72,6 @@ protocol bgp Node_2001__102 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v6/2001::103 -# Skipping ourselves (2001::103) - - -# For peer /bgp/v1/host/kube-master/peer_v6/2001::104 protocol bgp Node_2001__104 from bgp_template { ttl security off; multihop; @@ -109,9 +85,6 @@ protocol bgp Node_2001__104 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v6/ffee::10 protocol bgp Node_ffee__10 from bgp_template { ttl security off; multihop; @@ -126,9 +99,6 @@ protocol bgp Node_ffee__10 from bgp_template { }; # Only want to export routes for workloads. next hop keep; } - - -# For peer /bgp/v1/host/kube-master/peer_v6/ffee::11 protocol bgp Node_ffee__11 from bgp_template { ttl security off; multihop; @@ -145,4 +115,3 @@ protocol bgp Node_ffee__11 from bgp_template { } - diff --git a/confd/tests/compiled_templates/reachable_by/route_reflectors/bird6_ipam.cfg b/confd/tests/compiled_templates/reachable_by/route_reflectors/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/reachable_by/route_reflectors/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/reachable_by/route_reflectors/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/reachable_by/route_reflectors/bird_aggr.cfg b/confd/tests/compiled_templates/reachable_by/route_reflectors/bird_aggr.cfg index e868c2cb4dc..608fc9fcb58 100644 --- a/confd/tests/compiled_templates/reachable_by/route_reflectors/bird_aggr.cfg +++ b/confd/tests/compiled_templates/reachable_by/route_reflectors/bird_aggr.cfg @@ -11,7 +11,6 @@ protocol static { route 10.225.0.5/32 via 10.224.0.1; } - # Aggregation of routes on this host; export the block, nothing beneath it. function calico_aggr () { diff --git a/confd/tests/compiled_templates/reachable_by/route_reflectors/bird_ipam.cfg b/confd/tests/compiled_templates/reachable_by/route_reflectors/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/reachable_by/route_reflectors/bird_ipam.cfg +++ b/confd/tests/compiled_templates/reachable_by/route_reflectors/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/reverse_peering/auto/bird.cfg b/confd/tests/compiled_templates/reverse_peering/auto/bird.cfg index 9059cddcd3d..593fb031b42 100644 --- a/confd/tests/compiled_templates/reverse_peering/auto/bird.cfg +++ b/confd/tests/compiled_templates/reverse_peering/auto/bird.cfg @@ -59,27 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.2 -# Skipping ourselves (10.192.0.2) - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Node_10_192_0_3 from bgp_template { ttl security off; multihop; @@ -93,9 +73,6 @@ protocol bgp Node_10_192_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.4 protocol bgp Node_10_192_0_4 from bgp_template { ttl security off; multihop; @@ -110,3 +87,4 @@ protocol bgp Node_10_192_0_4 from bgp_template { }; # Only want to export routes for workloads. } + diff --git a/confd/tests/compiled_templates/reverse_peering/auto/bird6.cfg b/confd/tests/compiled_templates/reverse_peering/auto/bird6.cfg index 4a1a98b83a7..2e75c4d52a3 100644 --- a/confd/tests/compiled_templates/reverse_peering/auto/bird6.cfg +++ b/confd/tests/compiled_templates/reverse_peering/auto/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,23 +58,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v6/2001::102 +# BGP Protocol Configurations protocol bgp Node_2001__102 from bgp_template { ttl security off; multihop; @@ -89,13 +72,6 @@ protocol bgp Node_2001__102 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v6/2001::103 -# Skipping ourselves (2001::103) - - -# For peer /bgp/v1/host/kube-master/peer_v6/2001::104 protocol bgp Node_2001__104 from bgp_template { ttl security off; multihop; @@ -110,3 +86,4 @@ protocol bgp Node_2001__104 from bgp_template { }; # Only want to export routes for workloads. } + diff --git a/confd/tests/compiled_templates/reverse_peering/auto/bird6_ipam.cfg b/confd/tests/compiled_templates/reverse_peering/auto/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/reverse_peering/auto/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/reverse_peering/auto/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/reverse_peering/auto/bird_ipam.cfg b/confd/tests/compiled_templates/reverse_peering/auto/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/reverse_peering/auto/bird_ipam.cfg +++ b/confd/tests/compiled_templates/reverse_peering/auto/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/reverse_peering/manual/bird.cfg b/confd/tests/compiled_templates/reverse_peering/manual/bird.cfg index 48f6d834d86..ec2e394c035 100644 --- a/confd/tests/compiled_templates/reverse_peering/manual/bird.cfg +++ b/confd/tests/compiled_templates/reverse_peering/manual/bird.cfg @@ -59,36 +59,6 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.2 -# Skipping ourselves (10.192.0.2) - - - - - - - - - - - +# No BGP peers configured for this node diff --git a/confd/tests/compiled_templates/reverse_peering/manual/bird6.cfg b/confd/tests/compiled_templates/reverse_peering/manual/bird6.cfg index f753834c997..953f3a2bb91 100644 --- a/confd/tests/compiled_templates/reverse_peering/manual/bird6.cfg +++ b/confd/tests/compiled_templates/reverse_peering/manual/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,34 +58,6 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v6/2001::103 -# Skipping ourselves (2001::103) - - - - - - - - - - +# No BGP peers configured for this node diff --git a/confd/tests/compiled_templates/reverse_peering/manual/bird6_ipam.cfg b/confd/tests/compiled_templates/reverse_peering/manual/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/reverse_peering/manual/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/reverse_peering/manual/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/reverse_peering/manual/bird_ipam.cfg b/confd/tests/compiled_templates/reverse_peering/manual/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/reverse_peering/manual/bird_ipam.cfg +++ b/confd/tests/compiled_templates/reverse_peering/manual/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step1/bird.cfg b/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step1/bird.cfg index 342a128135d..85af77c5bf5 100644 --- a/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step1/bird.cfg +++ b/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step1/bird.cfg @@ -59,23 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/node1/peer_v4/172.17.0.6 +# BGP Protocol Configurations protocol bgp Node_172_17_0_6 from bgp_template { ttl security off; multihop; @@ -91,4 +75,3 @@ protocol bgp Node_172_17_0_6 from bgp_template { } - diff --git a/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step1/bird6.cfg b/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step1/bird6.cfg index 75b0e2192ec..0d55cdb741a 100644 --- a/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step1/bird6.cfg +++ b/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step1/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 172.17.0.5; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 172.17.0.5; # Configure synchronization between routing tables and kernel. protocol kernel { diff --git a/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step1/bird6_ipam.cfg b/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step1/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step1/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step1/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step1/bird_ipam.cfg b/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step1/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step1/bird_ipam.cfg +++ b/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step1/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step2/bird.cfg b/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step2/bird.cfg index 8636da9807d..3e82daf19e3 100644 --- a/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step2/bird.cfg +++ b/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step2/bird.cfg @@ -59,23 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/node1/peer_v4/172.17.0.6 +# BGP Protocol Configurations protocol bgp Node_172_17_0_6 from bgp_template { ttl security off; multihop; @@ -90,4 +74,3 @@ protocol bgp Node_172_17_0_6 from bgp_template { } - diff --git a/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step2/bird6.cfg b/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step2/bird6.cfg index 75b0e2192ec..0d55cdb741a 100644 --- a/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step2/bird6.cfg +++ b/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step2/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 172.17.0.5; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 172.17.0.5; # Configure synchronization between routing tables and kernel. protocol kernel { diff --git a/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step2/bird6_ipam.cfg b/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step2/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step2/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step2/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step2/bird_ipam.cfg b/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step2/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step2/bird_ipam.cfg +++ b/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step2/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step3/bird.cfg b/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step3/bird.cfg index ed5e16f081a..c54519598c1 100644 --- a/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step3/bird.cfg +++ b/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step3/bird.cfg @@ -59,23 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/node1/peer_v4/172.17.0.6 +# BGP Protocol Configurations protocol bgp Node_172_17_0_6 from bgp_template { ttl security off; multihop; @@ -91,4 +75,3 @@ protocol bgp Node_172_17_0_6 from bgp_template { } - diff --git a/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step3/bird6.cfg b/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step3/bird6.cfg index 75b0e2192ec..0d55cdb741a 100644 --- a/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step3/bird6.cfg +++ b/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step3/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 172.17.0.5; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 172.17.0.5; # Configure synchronization between routing tables and kernel. protocol kernel { diff --git a/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step3/bird6_ipam.cfg b/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step3/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step3/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step3/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step3/bird_ipam.cfg b/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step3/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step3/bird_ipam.cfg +++ b/confd/tests/compiled_templates/sourceaddr_gracefulrestart/step3/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/ttl_security/explicit_node/bird.cfg b/confd/tests/compiled_templates/ttl_security/explicit_node/bird.cfg index 9c1a3637ca4..afab4e3d388 100644 --- a/confd/tests/compiled_templates/ttl_security/explicit_node/bird.cfg +++ b/confd/tests/compiled_templates/ttl_security/explicit_node/bird.cfg @@ -59,23 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Node_10_192_0_3 from bgp_template { ttl security on; multihop 1; @@ -89,9 +73,6 @@ protocol bgp Node_10_192_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.4 protocol bgp Node_10_192_0_4 from bgp_template { ttl security on; multihop 2; @@ -107,4 +88,3 @@ protocol bgp Node_10_192_0_4 from bgp_template { } - diff --git a/confd/tests/compiled_templates/ttl_security/explicit_node/bird6.cfg b/confd/tests/compiled_templates/ttl_security/explicit_node/bird6.cfg index fcfc8031ccc..953f3a2bb91 100644 --- a/confd/tests/compiled_templates/ttl_security/explicit_node/bird6.cfg +++ b/confd/tests/compiled_templates/ttl_security/explicit_node/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,18 +58,6 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured +# No BGP peers configured for this node -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. diff --git a/confd/tests/compiled_templates/ttl_security/explicit_node/bird6_ipam.cfg b/confd/tests/compiled_templates/ttl_security/explicit_node/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/ttl_security/explicit_node/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/ttl_security/explicit_node/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/ttl_security/explicit_node/bird_aggr.cfg b/confd/tests/compiled_templates/ttl_security/explicit_node/bird_aggr.cfg index 5a623513074..9d86b3b0205 100644 --- a/confd/tests/compiled_templates/ttl_security/explicit_node/bird_aggr.cfg +++ b/confd/tests/compiled_templates/ttl_security/explicit_node/bird_aggr.cfg @@ -8,7 +8,6 @@ protocol static { route 192.168.221.64/26 blackhole; } - # Aggregation of routes on this host; export the block, nothing beneath it. function calico_aggr () { diff --git a/confd/tests/compiled_templates/ttl_security/explicit_node/bird_ipam.cfg b/confd/tests/compiled_templates/ttl_security/explicit_node/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/ttl_security/explicit_node/bird_ipam.cfg +++ b/confd/tests/compiled_templates/ttl_security/explicit_node/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/ttl_security/global/bird.cfg b/confd/tests/compiled_templates/ttl_security/global/bird.cfg index 1afc7a01fc8..50c323d3fc7 100644 --- a/confd/tests/compiled_templates/ttl_security/global/bird.cfg +++ b/confd/tests/compiled_templates/ttl_security/global/bird.cfg @@ -59,22 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v4/10.192.0.2 -# Skipping ourselves (10.192.0.2) - - -# For peer /bgp/v1/global/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Global_10_192_0_3 from bgp_template { ttl security on; multihop 1; @@ -88,9 +73,6 @@ protocol bgp Global_10_192_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v4/10.192.0.4 protocol bgp Global_10_192_0_4 from bgp_template { ttl security on; multihop 1; @@ -106,9 +88,3 @@ protocol bgp Global_10_192_0_4 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/ttl_security/global/bird6.cfg b/confd/tests/compiled_templates/ttl_security/global/bird6.cfg index 13ae17e2341..4f442493842 100644 --- a/confd/tests/compiled_templates/ttl_security/global/bird6.cfg +++ b/confd/tests/compiled_templates/ttl_security/global/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,18 +58,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- - - - -# For peer /bgp/v1/global/peer_v6/2001::102 +# BGP Protocol Configurations protocol bgp Global_2001__102 from bgp_template { ttl security on; multihop 1; @@ -84,13 +72,6 @@ protocol bgp Global_2001__102 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/global/peer_v6/2001::103 -# Skipping ourselves (2001::103) - - -# For peer /bgp/v1/global/peer_v6/2001::104 protocol bgp Global_2001__104 from bgp_template { ttl security on; multihop 1; @@ -106,9 +87,3 @@ protocol bgp Global_2001__104 from bgp_template { } - - -# ------------- Node-specific peers ------------- - -# No node-specific peers configured. - diff --git a/confd/tests/compiled_templates/ttl_security/global/bird6_ipam.cfg b/confd/tests/compiled_templates/ttl_security/global/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/ttl_security/global/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/ttl_security/global/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/ttl_security/global/bird_aggr.cfg b/confd/tests/compiled_templates/ttl_security/global/bird_aggr.cfg index 5a623513074..9d86b3b0205 100644 --- a/confd/tests/compiled_templates/ttl_security/global/bird_aggr.cfg +++ b/confd/tests/compiled_templates/ttl_security/global/bird_aggr.cfg @@ -8,7 +8,6 @@ protocol static { route 192.168.221.64/26 blackhole; } - # Aggregation of routes on this host; export the block, nothing beneath it. function calico_aggr () { diff --git a/confd/tests/compiled_templates/ttl_security/global/bird_ipam.cfg b/confd/tests/compiled_templates/ttl_security/global/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/ttl_security/global/bird_ipam.cfg +++ b/confd/tests/compiled_templates/ttl_security/global/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/ttl_security/peer_selector/bird.cfg b/confd/tests/compiled_templates/ttl_security/peer_selector/bird.cfg index f39804810d3..82dc91cda01 100644 --- a/confd/tests/compiled_templates/ttl_security/peer_selector/bird.cfg +++ b/confd/tests/compiled_templates/ttl_security/peer_selector/bird.cfg @@ -59,23 +59,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v4 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.3 +# BGP Protocol Configurations protocol bgp Node_10_192_0_3 from bgp_template { ttl security on; multihop 1; @@ -89,9 +73,6 @@ protocol bgp Node_10_192_0_3 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v4/10.192.0.4 protocol bgp Node_10_192_0_4 from bgp_template { ttl security on; multihop 1; @@ -107,4 +88,3 @@ protocol bgp Node_10_192_0_4 from bgp_template { } - diff --git a/confd/tests/compiled_templates/ttl_security/peer_selector/bird6.cfg b/confd/tests/compiled_templates/ttl_security/peer_selector/bird6.cfg index c0b85296ed7..efa2d90c1e9 100644 --- a/confd/tests/compiled_templates/ttl_security/peer_selector/bird6.cfg +++ b/confd/tests/compiled_templates/ttl_security/peer_selector/bird6.cfg @@ -6,7 +6,7 @@ function apply_communities () include "bird6_aggr.cfg"; include "bird6_ipam.cfg"; -router id 10.192.0.2; # Use IPv4 address since router id is 4 octets, even in MP-BGP +router id 10.192.0.2; # Configure synchronization between routing tables and kernel. protocol kernel { @@ -43,7 +43,6 @@ protocol direct { # export cluster IPs. } - # Template for all BGP clients template bgp bgp_template { debug { states }; @@ -59,23 +58,7 @@ template bgp bgp_template { # -------------- BGP Filters ------------------ # No v6 BGPFilters configured - -# ------------- Node-to-node mesh ------------- - -# Node-to-node mesh disabled - - - -# ------------- Global peers ------------- -# No global peers configured. - - -# ------------- Node-specific peers ------------- - - - - -# For peer /bgp/v1/host/kube-master/peer_v6/2001::102 +# BGP Protocol Configurations protocol bgp Node_2001__102 from bgp_template { ttl security on; multihop 1; @@ -89,9 +72,6 @@ protocol bgp Node_2001__102 from bgp_template { reject; }; # Only want to export routes for workloads. } - - -# For peer /bgp/v1/host/kube-master/peer_v6/2001::104 protocol bgp Node_2001__104 from bgp_template { ttl security on; multihop 1; @@ -107,4 +87,3 @@ protocol bgp Node_2001__104 from bgp_template { } - diff --git a/confd/tests/compiled_templates/ttl_security/peer_selector/bird6_ipam.cfg b/confd/tests/compiled_templates/ttl_security/peer_selector/bird6_ipam.cfg index d7daa3928e5..2024928a158 100644 --- a/confd/tests/compiled_templates/ttl_security/peer_selector/bird6_ipam.cfg +++ b/confd/tests/compiled_templates/ttl_security/peer_selector/bird6_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,19 +20,15 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/compiled_templates/ttl_security/peer_selector/bird_aggr.cfg b/confd/tests/compiled_templates/ttl_security/peer_selector/bird_aggr.cfg index 5a623513074..9d86b3b0205 100644 --- a/confd/tests/compiled_templates/ttl_security/peer_selector/bird_aggr.cfg +++ b/confd/tests/compiled_templates/ttl_security/peer_selector/bird_aggr.cfg @@ -8,7 +8,6 @@ protocol static { route 192.168.221.64/26 blackhole; } - # Aggregation of routes on this host; export the block, nothing beneath it. function calico_aggr () { diff --git a/confd/tests/compiled_templates/ttl_security/peer_selector/bird_ipam.cfg b/confd/tests/compiled_templates/ttl_security/peer_selector/bird_ipam.cfg index 284fa44f216..6fe41fd331b 100644 --- a/confd/tests/compiled_templates/ttl_security/peer_selector/bird_ipam.cfg +++ b/confd/tests/compiled_templates/ttl_security/peer_selector/bird_ipam.cfg @@ -1,9 +1,4 @@ # Generated by confd -function reject_disabled_pools () -{ - -} - function reject_tunnel_routes () { # Don't export tunnel routes to other nodes, Felix programs them. # IPIP routes are handled by Bird, and it does not re-advertise them. @@ -25,20 +20,16 @@ function reject_local_routes () { function calico_export_to_bgp_peers(bool internal_peer) { # filter code terminates when it calls `accept;` or `reject;`, - # call reject_disabled_pools() first, then reject_tunnel_routes(), - # then apply_communities() and then calico_aggr() - reject_disabled_pools(); + if (internal_peer) then { reject_tunnel_routes(); } reject_local_routes(); apply_communities(); calico_aggr(); - } filter calico_kernel_programming { - accept; } diff --git a/confd/tests/mock_data/calicoctl/explicit_peering/local-as-global-ipv6/input.yaml b/confd/tests/mock_data/calicoctl/explicit_peering/local-as-global-ipv6/input.yaml index cd0b51ce90d..17174f5fcf0 100644 --- a/confd/tests/mock_data/calicoctl/explicit_peering/local-as-global-ipv6/input.yaml +++ b/confd/tests/mock_data/calicoctl/explicit_peering/local-as-global-ipv6/input.yaml @@ -15,6 +15,7 @@ metadata: spec: peerIP: 2001::102 asNumber: 64567 + localASNumber: 64567 sourceAddress: None --- diff --git a/confd/tests/mock_data/calicoctl/explicit_peering/local-as-global/input.yaml b/confd/tests/mock_data/calicoctl/explicit_peering/local-as-global/input.yaml index d974c6e1711..8749100443f 100644 --- a/confd/tests/mock_data/calicoctl/explicit_peering/local-as-global/input.yaml +++ b/confd/tests/mock_data/calicoctl/explicit_peering/local-as-global/input.yaml @@ -16,6 +16,7 @@ metadata: spec: peerIP: 10.192.0.3 asNumber: 64567 + localASNumber: 64515 sourceAddress: None --- @@ -26,7 +27,7 @@ metadata: spec: peerIP: 10.192.0.1:166 asNumber: 64567 - localASNumber: 65002 + localASNumber: 64567 --- kind: BGPPeer @@ -36,7 +37,7 @@ metadata: spec: peerIP: 10.192.0.1 asNumber: 64567 - localASNumber: 65001 + localASNumber: 64567 numAllowedLocalASNumbers: 1 --- diff --git a/confd/tests/mock_data/calicoctl/explicit_peering/local-as-ipv6/input.yaml b/confd/tests/mock_data/calicoctl/explicit_peering/local-as-ipv6/input.yaml index 473420be34d..f5e32afcaa5 100644 --- a/confd/tests/mock_data/calicoctl/explicit_peering/local-as-ipv6/input.yaml +++ b/confd/tests/mock_data/calicoctl/explicit_peering/local-as-ipv6/input.yaml @@ -13,7 +13,7 @@ metadata: spec: peerIP: 2001::102 asNumber: 64512 - localASNumber: 65002 + localASNumber: 64512 node: kube-master numAllowedLocalASNumbers: 1 diff --git a/confd/tests/mock_data/calicoctl/explicit_peering/local-as/input.yaml b/confd/tests/mock_data/calicoctl/explicit_peering/local-as/input.yaml index e6cf9043ef6..8fe08e5ad0d 100644 --- a/confd/tests/mock_data/calicoctl/explicit_peering/local-as/input.yaml +++ b/confd/tests/mock_data/calicoctl/explicit_peering/local-as/input.yaml @@ -13,7 +13,7 @@ metadata: spec: peerIP: 10.192.0.3 asNumber: 64512 - localASNumber: 65002 + localASNumber: 64512 node: kube-master numAllowedLocalASNumbers: 1 diff --git a/confd/tests/mock_data/kdd/ipam_v3.yaml b/confd/tests/mock_data/kdd/ipam_v3.yaml new file mode 100644 index 00000000000..1b00fa41e9f --- /dev/null +++ b/confd/tests/mock_data/kdd/ipam_v3.yaml @@ -0,0 +1,77 @@ +apiVersion: projectcalico.org/v3 +kind: BlockAffinity +metadata: + name: kube-master-192-168-221-192-26 + namespace: "" +spec: + node: kube-master + cidr: 192.168.221.192/26 + deleted: false + state: "" +--- +apiVersion: projectcalico.org/v3 +kind: BlockAffinity +metadata: + name: kube-master-192-168-221-0-26 + namespace: "" +spec: + state: "pending" + node: kube-master + cidr: 192.168.221.0/26 + deleted: false +--- +apiVersion: projectcalico.org/v3 +kind: BlockAffinity +metadata: + name: kube-master-192-168-221-64-26 + namespace: "" +spec: + state: "confirmed" + node: kube-master + cidr: 192.168.221.64/26 + deleted: false +--- +apiVersion: projectcalico.org/v3 +kind: BlockAffinity +metadata: + name: kube-master-10-0-0-0-30 + namespace: "" +spec: + node: kube-master + cidr: 10.0.0.0/30 + deleted: false + state: "" +--- +apiVersion: projectcalico.org/v3 +kind: BlockAffinity +metadata: + name: kube-master-10-1-0-0-24 + namespace: "" +spec: + node: kube-master + cidr: 10.1.0.0/24 + deleted: false + state: "" +--- +apiVersion: projectcalico.org/v3 +kind: BlockAffinity +metadata: + name: kube-master-10-2-0-1-32 + namespace: "" +spec: + node: kube-master + cidr: 10.2.0.1/32 + deleted: false + state: "" +--- +apiVersion: projectcalico.org/v3 +kind: BlockAffinity +metadata: + name: loadbalancer-192-168-200-1-32 + namespace: "" +spec: + node: loadbalancer + cidr: 192.168.200.1/32 + deleted: false + state: "confirmed" + type: "virtual" diff --git a/confd/tests/test_suite_common.sh b/confd/tests/test_suite_common.sh index aa42fe33d25..9e5dc24bf3e 100755 --- a/confd/tests/test_suite_common.sh +++ b/confd/tests/test_suite_common.sh @@ -1017,6 +1017,7 @@ metadata: spec: cidr: 192.168.0.0/16 ipipMode: Never + vxlanMode: Never natOutgoing: true --- kind: IPPool diff --git a/confd/tests/test_suite_kdd.sh b/confd/tests/test_suite_kdd.sh index 1aa6a1477bb..433617ce18f 100755 --- a/confd/tests/test_suite_kdd.sh +++ b/confd/tests/test_suite_kdd.sh @@ -25,8 +25,7 @@ export LOGPATH=/tests/logs/kdd export DATASTORE_TYPE=kubernetes export KUBECONFIG=/home/user/certs/kubeconfig -# CRDs are pulled in from libcalico. -CRDS=../libcalico-go/config/crd +CRDS=../$CALICO_CRD_PATH # Prepopulate k8s with data that cannot be populated through calicoctl. # All tests use the same set of nodes - for k8s these cannot be created through @@ -37,7 +36,14 @@ for i in $(seq 1 30); do kubectl apply -f $CRDS 1>/dev/null 2>&1 && break || sle echo "Populating k8s with test data that cannot be handled by calicoctl" kubectl apply -f $CRDS kubectl apply -f /tests/mock_data/kdd/nodes.yaml -kubectl apply -f /tests/mock_data/kdd/ipam.yaml + +# Apply the right IPAM data based on the configured CALICO_API_GROUP. +if [ "$CALICO_API_GROUP" = "projectcalico.org/v3" ]; then + kubectl apply -f /tests/mock_data/kdd/ipam_v3.yaml +else + echo "Using legacy API group" + kubectl apply -f /tests/mock_data/kdd/ipam.yaml +fi # Use calicoctl to apply some data - this will require the CRDs to be online. Repeat # until successful. diff --git a/crypto/Makefile b/crypto/Makefile deleted file mode 100644 index d004ed880da..00000000000 --- a/crypto/Makefile +++ /dev/null @@ -1,29 +0,0 @@ -PACKAGE_NAME = github.com/projectcalico/calico/crypto - -include ../metadata.mk -include ../lib.Makefile - -.PHONY: ci -ci: fv - -.PHONY: fv -fv: setup-fv bin/fips-test-build - # Run a server using the FIPS strict mode settings and wait 1s to let it come up. - $(DOCKER_RUN) --name fips-test-build --rm -d $(CALICO_BUILD) ./bin/fips-test-build - sleep 1 - # The server should respond with the value for STRICT_FIPS. - $(DOCKER_RUN) $(CALICO_BUILD) curl -k https://localhost:8083 | grep true - # Run the nmap tool on the server to find out the tls versions and ciphers. - docker run --net=host --rm -it instrumentisto/nmap --script ssl-enum-ciphers -p 8083 127.0.0.1 > tmp/nmap.log - # remove times and dates from file - sed -i '1,4d;22d' tmp/nmap.log - docker rm -f fips-test-build - # If the ciphers are as expected, we get exit code 0. - diff -u fv/expected-nmap.log tmp/nmap.log - -bin/fips-test-build: - $(call build_cgo_boring_binary, $(PACKAGE_NAME)/fv/main, $@) - -setup-fv: - mkdir -p bin tmp - $(DOCKER_RUN) $(CALICO_BUILD) sh -c 'openssl req -batch -new -newkey rsa:2048 -sha256 -days 365 -nodes -x509 -keyout tmp/tls.key -out tmp/tls.crt' diff --git a/crypto/deps.txt b/crypto/deps.txt deleted file mode 100644 index 8ddb6494627..00000000000 --- a/crypto/deps.txt +++ /dev/null @@ -1,8 +0,0 @@ -!!! GENERATED FILE, DO NOT EDIT !!! -This file contains the list of modules that this package depends on -in order to trigger CI on changes - -go 1.25.1 -github.com/projectcalico/calico -github.com/sirupsen/logrus v1.9.3 -golang.org/x/sys v0.35.0 diff --git a/crypto/fv/expected-nmap.log b/crypto/fv/expected-nmap.log deleted file mode 100644 index 81c08c7a4d1..00000000000 --- a/crypto/fv/expected-nmap.log +++ /dev/null @@ -1,17 +0,0 @@ -PORT STATE SERVICE -8083/tcp open us-srv -| ssl-enum-ciphers: -| TLSv1.2: -| ciphers: -| TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (secp256r1) - A -| TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (secp256r1) - A -| compressors: -| NULL -| cipher preference: server -| TLSv1.3: -| ciphers: -| TLS_AKE_WITH_AES_128_GCM_SHA256 (secp256r1) - A -| TLS_AKE_WITH_AES_256_GCM_SHA384 (secp256r1) - A -| cipher preference: server -|_ least strength: A - diff --git a/crypto/fv/main/main.go b/crypto/fv/main/main.go deleted file mode 100644 index a5e5224d6a4..00000000000 --- a/crypto/fv/main/main.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import ( - "fmt" - "net/http" - - "github.com/projectcalico/calico/crypto/pkg/tls" -) - -// This server is for testing purposes only to see if the tls config is affected by our build commands as expected. -func main() { - tlsConfig, err := tls.NewTLSConfig() - if err != nil { - panic(fmt.Errorf("failed to create TLS Config: %w", err)) - } - server := http.Server{ - Addr: ":8083", - Handler: fipsHandler{}, - TLSConfig: tlsConfig, - } - - err = server.ListenAndServeTLS("tmp/tls.crt", "tmp/tls.key") - if err != nil { - panic(err) - } -} - -type fipsHandler struct{} - -func (h fipsHandler) ServeHTTP(w http.ResponseWriter, _ *http.Request) { - w.Write([]byte(fmt.Sprintf("%t", tls.BuiltWithBoringCrypto))) -} diff --git a/crypto/pkg/tls/fipstls.go b/crypto/pkg/tls/fipstls.go deleted file mode 100644 index 22398b2a69e..00000000000 --- a/crypto/pkg/tls/fipstls.go +++ /dev/null @@ -1,10 +0,0 @@ -//go:build fipsstrict - -package tls - -import ( - _ "crypto/tls/fipsonly" -) - -// BuiltWithBoringCrypto if true, strict fips mode is enforced. -const BuiltWithBoringCrypto = true diff --git a/crypto/pkg/tls/nonfipstls.go b/crypto/pkg/tls/nonfipstls.go deleted file mode 100644 index 019f5833174..00000000000 --- a/crypto/pkg/tls/nonfipstls.go +++ /dev/null @@ -1,6 +0,0 @@ -//go:build !fipsstrict - -package tls - -// BuiltWithBoringCrypto if true, strict fips mode is enforced. -const BuiltWithBoringCrypto = false diff --git a/crypto/pkg/tls/tls.go b/crypto/pkg/tls/tls.go index eed4412d9fe..09c36f69913 100644 --- a/crypto/pkg/tls/tls.go +++ b/crypto/pkg/tls/tls.go @@ -21,6 +21,7 @@ import ( "os" "strings" + apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" ) @@ -82,8 +83,8 @@ func ParseTLSCiphers(ciphers string) ([]uint16, error) { var result []uint16 supportedCiphers := supportedCipherMap() - cipherNames := strings.Split(ciphers, ",") - for _, name := range cipherNames { + cipherNames := strings.SplitSeq(ciphers, ",") + for name := range cipherNames { name = strings.TrimSpace(name) cipherValue, ok := supportedCiphers[name] if !ok { @@ -95,17 +96,36 @@ func ParseTLSCiphers(ciphers string) ([]uint16, error) { return result, nil } +// ParseTLSVersion parses TLS version string and returns the corresponding tls version constant +// Accepts: "1.2", "1.3", or empty string (defaults to "1.2") +func ParseTLSVersion(version string) (uint16, error) { + switch version { + case "", "1.2": + return tls.VersionTLS12, nil + case "1.3": + return tls.VersionTLS13, nil + default: + return 0, fmt.Errorf("unsupported TLS version: %s (supported versions: 1.2, 1.3)", version) + } +} + // NewTLSConfig returns a tls.Config with the recommended default settings for Calico components. Based on build flags, // boringCrypto may be used and fips strict mode may be enforced, which can override the parameters defined in this func. func NewTLSConfig() (*tls.Config, error) { - log.WithField("BuiltWithBoringCrypto", BuiltWithBoringCrypto).Debug("creating a TLS config") env := os.Getenv("TLS_CIPHER_SUITES") ciphers, err := ParseTLSCiphers(env) if err != nil { return nil, fmt.Errorf("failed to create TLS Config: %w", err) } + + minVersion, err := ParseTLSVersion(os.Getenv("TLS_MIN_VERSION")) + if err != nil { + log.WithError(err).Warn("Invalid TLS_MIN_VERSION, defaulting to TLS 1.2") + minVersion = tls.VersionTLS12 + } + return &tls.Config{ - MinVersion: tls.VersionTLS12, + MinVersion: minVersion, MaxVersion: tls.VersionTLS13, CipherSuites: ciphers, }, nil @@ -139,3 +159,18 @@ func NewMutualTLSConfig(cert, key, ca string) (*tls.Config, error) { return tlsCfg, nil } + +func StringToTLSClientAuthType(clientAuthType string) (tls.ClientAuthType, error) { + switch clientAuthType { + case string(apiv3.RequireAndVerifyClientCert), "": + return tls.RequireAndVerifyClientCert, nil + case string(apiv3.RequireAnyClientCert): + return tls.RequireAnyClientCert, nil + case string(apiv3.VerifyClientCertIfGiven): + return tls.VerifyClientCertIfGiven, nil + case string(apiv3.NoClientCert): + return tls.NoClientCert, nil + default: + return tls.RequireAndVerifyClientCert, fmt.Errorf("invalid client authentication type: %s. Defaulting to RequireAndVerifyClientCert", clientAuthType) + } +} diff --git a/crypto/pkg/tls/tls_test.go b/crypto/pkg/tls/tls_test.go index 5ae36d787c4..5eb67f622b3 100644 --- a/crypto/pkg/tls/tls_test.go +++ b/crypto/pkg/tls/tls_test.go @@ -43,3 +43,55 @@ func TestTLSCipherParsing(t *testing.T) { Expect(ciphersID).To(Equal(testCase.expectedCiphersID)) } } + +func TestTLSVersionParsing(t *testing.T) { + RegisterTestingT(t) + + tests := []struct { + name string + version string + expected uint16 + expectError bool + }{ + { + name: "empty string defaults to TLS 1.2", + version: "", + expected: tls.VersionTLS12, + expectError: false, + }, + { + name: "explicit TLS 1.2", + version: "1.2", + expected: tls.VersionTLS12, + expectError: false, + }, + { + name: "TLS 1.3", + version: "1.3", + expected: tls.VersionTLS13, + expectError: false, + }, + { + name: "invalid version 1.1", + version: "1.1", + expectError: true, + }, + { + name: "invalid version string", + version: "invalid", + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := ParseTLSVersion(tt.version) + if tt.expectError { + Expect(err).To(HaveOccurred()) + } else { + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(Equal(tt.expected)) + } + }) + } +} diff --git a/e2e/Makefile b/e2e/Makefile index dca61e8ed37..b731e01b11e 100644 --- a/e2e/Makefile +++ b/e2e/Makefile @@ -4,14 +4,14 @@ PACKAGE_NAME=github.com/projectcalico/calico/e2e include ../lib.Makefile SRC_FILES=$(shell find pkg cmd -name '*.go') -build: bin/k8s/e2e.test bin/adminpolicy/e2e.test $(SRC_FILES) +build: bin/k8s/e2e.test bin/clusternetworkpolicy/e2e.test $(SRC_FILES) bin/k8s/e2e.test: $(SRC_FILES) mkdir -p bin $(DOCKER_RUN) $(CALICO_BUILD) go test ./cmd/k8s -c -o $@ -bin/adminpolicy/e2e.test: $(SRC_FILES) +bin/clusternetworkpolicy/e2e.test: $(SRC_FILES) mkdir -p bin - $(DOCKER_RUN) $(CALICO_BUILD) go test ./cmd/adminpolicy -c -o $@ + $(DOCKER_RUN) $(CALICO_BUILD) go test ./cmd/clusternetworkpolicy -c -o $@ ############################################################################### # test images diff --git a/e2e/cmd/adminpolicy/e2e_test.go b/e2e/cmd/clusternetworkpolicy/e2e_test.go similarity index 92% rename from e2e/cmd/adminpolicy/e2e_test.go rename to e2e/cmd/clusternetworkpolicy/e2e_test.go index 8602a0e77d8..37a03498b76 100644 --- a/e2e/cmd/adminpolicy/e2e_test.go +++ b/e2e/cmd/clusternetworkpolicy/e2e_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package main +package conformance_test import ( "testing" @@ -24,7 +24,7 @@ import ( "k8s.io/client-go/tools/clientcmd" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/config" - "sigs.k8s.io/network-policy-api/apis/v1alpha1" + api "sigs.k8s.io/network-policy-api/apis/v1alpha2" "sigs.k8s.io/network-policy-api/conformance/tests" "sigs.k8s.io/network-policy-api/conformance/utils/flags" "sigs.k8s.io/network-policy-api/conformance/utils/suite" @@ -49,7 +49,10 @@ func TestConformance(t *testing.T) { t.Fatalf("error when creating Kubernetes ClientSet: %v", err) } - v1alpha1.Install(c.Scheme()) + err = api.Install(c.Scheme()) + if err != nil { + t.Fatalf("error when installing Network Policy API scheme: %v", err) + } supportedFeatures := suite.ParseSupportedFeatures(*flags.SupportedFeatures) exemptFeatures := suite.ParseSupportedFeatures(*flags.ExemptFeatures) diff --git a/e2e/cmd/k8s/e2e_test.go b/e2e/cmd/k8s/e2e_test.go index f6601aefcaf..a34e7f50bdb 100644 --- a/e2e/cmd/k8s/e2e_test.go +++ b/e2e/cmd/k8s/e2e_test.go @@ -31,7 +31,9 @@ import ( _ "k8s.io/kubernetes/test/e2e/network" _ "github.com/projectcalico/calico/e2e/pkg/tests/apis" + _ "github.com/projectcalico/calico/e2e/pkg/tests/bgp" _ "github.com/projectcalico/calico/e2e/pkg/tests/hostendpoints" + _ "github.com/projectcalico/calico/e2e/pkg/tests/ipam" _ "github.com/projectcalico/calico/e2e/pkg/tests/networking" _ "github.com/projectcalico/calico/e2e/pkg/tests/operator" _ "github.com/projectcalico/calico/e2e/pkg/tests/policy" diff --git a/e2e/deps.txt b/e2e/deps.txt index 43113e739aa..375f7c462cf 100644 --- a/e2e/deps.txt +++ b/e2e/deps.txt @@ -2,137 +2,136 @@ This file contains the list of modules that this package depends on in order to trigger CI on changes -go 1.25.1 -cel.dev/expr v0.20.0 +go 1.25.7 +cel.dev/expr v0.24.0 +github.com/Masterminds/semver/v3 v3.4.0 github.com/antlr4-go/antlr/v4 v4.13.0 -github.com/aws/smithy-go v1.22.5 +github.com/aws/smithy-go v1.23.1 github.com/beorn7/perks v1.0.1 github.com/blang/semver/v4 v4.0.0 -github.com/cenkalti/backoff/v4 v4.3.0 +github.com/cenkalti/backoff/v5 v5.0.2 github.com/cespare/xxhash/v2 v2.3.0 -github.com/containerd/containerd v1.7.24 -github.com/containerd/containerd/api v1.8.0 -github.com/containerd/errdefs v1.0.0 -github.com/containerd/errdefs/pkg v0.3.0 -github.com/containerd/log v0.1.0 -github.com/containerd/ttrpc v1.2.6 -github.com/containerd/typeurl/v2 v2.2.2 -github.com/coreos/go-systemd/v22 v22.5.0 -github.com/cyphar/filepath-securejoin v0.4.1 +github.com/coreos/go-semver v0.3.1 +github.com/coreos/go-systemd/v22 v22.6.0 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/distribution/reference v0.6.0 -github.com/docker/go-units v0.5.0 -github.com/emicklei/go-restful/v3 v3.11.0 -github.com/euank/go-kmsg-parser v2.0.0+incompatible -github.com/evanphx/json-patch v5.9.0+incompatible +github.com/emicklei/go-restful v2.15.0+incompatible +github.com/emicklei/go-restful/v3 v3.12.2 +github.com/evanphx/json-patch v5.9.11+incompatible github.com/evanphx/json-patch/v5 v5.9.11 github.com/felixge/httpsnoop v1.0.4 -github.com/fsnotify/fsnotify v1.9.0 -github.com/fxamacker/cbor/v2 v2.7.0 +github.com/fxamacker/cbor/v2 v2.9.0 +github.com/gabriel-vasile/mimetype v1.4.10 +github.com/go-kit/log v0.2.1 +github.com/go-logfmt/logfmt v0.6.0 github.com/go-logr/logr v1.4.3 github.com/go-logr/stdr v1.2.2 github.com/go-openapi/jsonpointer v0.21.0 github.com/go-openapi/jsonreference v0.20.2 github.com/go-openapi/swag v0.23.0 -github.com/godbus/dbus/v5 v5.1.0 +github.com/go-playground/form v3.1.4+incompatible +github.com/go-playground/locales v0.14.1 +github.com/go-playground/universal-translator v0.18.1 +github.com/go-playground/validator/v10 v10.28.0 github.com/gogo/protobuf v1.3.2 -github.com/google/cadvisor v0.52.1 -github.com/google/cel-go v0.23.2 -github.com/google/gnostic-models v0.6.9 +github.com/golang/protobuf v1.5.4 +github.com/google/cel-go v0.26.0 +github.com/google/gnostic-models v0.7.0 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 -github.com/grpc-ecosystem/grpc-gateway v1.16.0 -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 github.com/jinzhu/copier v0.4.0 github.com/josharian/intern v1.0.0 github.com/json-iterator/go v1.1.12 -github.com/karrick/godirwalk v1.17.0 +github.com/kelseyhightower/envconfig v1.4.0 +github.com/leodido/go-urn v1.4.0 github.com/mailru/easyjson v0.7.7 -github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible github.com/moby/spdystream v0.5.0 -github.com/moby/sys/mountinfo v0.7.2 -github.com/moby/sys/user v0.3.0 -github.com/moby/sys/userns v0.1.0 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd -github.com/modern-go/reflect2 v1.0.2 +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f github.com/onsi/ginkgo v1.16.5 -github.com/onsi/ginkgo/v2 v2.23.4 -github.com/onsi/gomega v1.38.0 -github.com/opencontainers/cgroups v0.0.1 +github.com/onsi/ginkgo/v2 v2.28.1 +github.com/onsi/gomega v1.39.1 github.com/opencontainers/go-digest v1.0.0 -github.com/opencontainers/image-spec v1.1.1 -github.com/opencontainers/runtime-spec v1.2.0 -github.com/opencontainers/selinux v1.11.1 +github.com/openshift/custom-resource-status v1.1.2 github.com/pkg/errors v0.9.1 +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/projectcalico/calico github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.80.1 -github.com/prometheus/client_golang v1.23.0 +github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 -github.com/prometheus/common v0.65.0 -github.com/prometheus/procfs v0.17.0 +github.com/prometheus/common v0.67.2 +github.com/prometheus/procfs v0.19.2 github.com/sirupsen/logrus v1.9.3 -github.com/spf13/cobra v1.9.1 -github.com/spf13/pflag v1.0.7 +github.com/spf13/cobra v1.10.1 +github.com/spf13/pflag v1.0.10 github.com/stoewer/go-strcase v1.3.0 github.com/tigera/operator/api v0.0.0-20250807130954-d4353da6554e github.com/x448/float16 v0.8.4 +go.etcd.io/etcd/api/v3 v3.6.5 +go.etcd.io/etcd/client/pkg/v3 v3.6.5 +go.etcd.io/etcd/client/v3 v3.6.5 go.opentelemetry.io/auto/sdk v1.1.0 -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 -go.opentelemetry.io/otel v1.35.0 -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 -go.opentelemetry.io/otel/metric v1.35.0 -go.opentelemetry.io/otel/sdk v1.35.0 -go.opentelemetry.io/otel/trace v1.35.0 -go.opentelemetry.io/proto/otlp v1.5.0 -go.yaml.in/yaml/v2 v2.4.2 -golang.org/x/crypto v0.41.0 -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 -golang.org/x/net v0.43.0 -golang.org/x/oauth2 v0.30.0 -golang.org/x/sync v0.16.0 -golang.org/x/sys v0.35.0 -golang.org/x/term v0.34.0 -golang.org/x/text v0.28.0 -golang.org/x/time v0.12.0 +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 +go.opentelemetry.io/otel v1.37.0 +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 +go.opentelemetry.io/otel/metric v1.37.0 +go.opentelemetry.io/otel/sdk v1.37.0 +go.opentelemetry.io/otel/trace v1.37.0 +go.opentelemetry.io/proto/otlp v1.7.0 +go.uber.org/multierr v1.11.0 +go.uber.org/zap v1.27.0 +go.yaml.in/yaml/v2 v2.4.3 +go.yaml.in/yaml/v3 v3.0.4 +golang.org/x/crypto v0.47.0 +golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 +golang.org/x/mod v0.32.0 +golang.org/x/net v0.49.0 +golang.org/x/oauth2 v0.34.0 +golang.org/x/sync v0.19.0 +golang.org/x/sys v0.40.0 +golang.org/x/term v0.39.0 +golang.org/x/text v0.33.0 +golang.org/x/time v0.14.0 +golang.org/x/tools v0.41.0 +golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 +golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 google.golang.org/genproto v0.0.0-20250603155806-513f23925822 -google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 -google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a -google.golang.org/grpc v1.72.2 -google.golang.org/protobuf v1.36.7 +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c +google.golang.org/grpc v1.76.0 +google.golang.org/protobuf v1.36.10 gopkg.in/evanphx/json-patch.v4 v4.12.0 +gopkg.in/go-playground/validator.v9 v9.30.2 gopkg.in/inf.v0 v0.9.1 gopkg.in/yaml.v3 v3.0.1 -k8s.io/api v0.33.5 -k8s.io/apiextensions-apiserver v0.33.5 -k8s.io/apimachinery v0.33.5 -k8s.io/apiserver v0.33.5 -k8s.io/client-go v0.33.5 -k8s.io/cloud-provider v0.33.5 -k8s.io/component-base v0.33.5 -k8s.io/component-helpers v0.33.5 -k8s.io/controller-manager v0.33.5 -k8s.io/cri-api v0.33.5 -k8s.io/cri-client v0.33.5 -k8s.io/csi-translation-lib v0.33.5 -k8s.io/dynamic-resource-allocation v0.33.5 +k8s.io/api v0.34.3 +k8s.io/apiextensions-apiserver v0.34.3 +k8s.io/apimachinery v0.34.3 +k8s.io/apiserver v0.34.3 +k8s.io/client-go v0.34.3 +k8s.io/component-base v0.34.3 +k8s.io/component-helpers v0.34.3 +k8s.io/controller-manager v0.34.3 k8s.io/klog v0.2.0 k8s.io/klog/v2 v2.130.1 -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff -k8s.io/kube-scheduler v0.33.5 -k8s.io/kubectl v0.33.5 -k8s.io/kubelet v0.33.5 -k8s.io/kubernetes v1.33.5 -k8s.io/mount-utils v0.33.5 -k8s.io/pod-security-admission v0.33.5 -k8s.io/utils v0.0.0-20241210054802-24370beab758 -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 -sigs.k8s.io/controller-runtime v0.20.4 +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b +k8s.io/kubectl v0.34.3 +k8s.io/kubelet v0.34.3 +k8s.io/kubernetes v1.34.3 +k8s.io/pod-security-admission v0.34.3 +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 +kubevirt.io/api v1.8.0-alpha.0 +kubevirt.io/containerized-data-importer-api v1.63.1 +kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 +sigs.k8s.io/controller-runtime v0.22.3 sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 +sigs.k8s.io/network-policy-api v0.1.8-0.20260212153203-412bf65729a5 sigs.k8s.io/randfill v1.0.0 -sigs.k8s.io/structured-merge-diff/v4 v4.6.0 +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 sigs.k8s.io/yaml v1.6.0 diff --git a/e2e/images/rapidclient/main.go b/e2e/images/rapidclient/main.go index 2d1e23b4875..6e4bba813bd 100644 --- a/e2e/images/rapidclient/main.go +++ b/e2e/images/rapidclient/main.go @@ -57,7 +57,7 @@ func main() { if err != nil { log.Fatalf("Request failed: %v", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() // Read and print the response body, err := io.ReadAll(resp.Body) diff --git a/e2e/pkg/config/config.go b/e2e/pkg/config/config.go index cded50e0675..044dfc0e658 100644 --- a/e2e/pkg/config/config.go +++ b/e2e/pkg/config/config.go @@ -85,7 +85,7 @@ func ExtNodeUsername() string { } func ExtNodeSSHKey() string { - return allConfigOptions[externalNodeIP].actualValue + return allConfigOptions[externalNodeSSHKey].actualValue } func ExtNodeIP() string { diff --git a/e2e/pkg/describe/describe.go b/e2e/pkg/describe/describe.go index 8cfd973846e..2dfe6cf281e 100644 --- a/e2e/pkg/describe/describe.go +++ b/e2e/pkg/describe/describe.go @@ -61,11 +61,27 @@ var features = map[string]bool{ "NetworkPolicy": true, "Tiered-Policy": true, "IPPool": true, + "IPAM": true, "AutoHEPs": true, "Host-Protection": true, "HostPorts": true, "OwnerReferences": true, "MTU": true, + "Maglev": true, + "BGPPeer": true, + "IPIP": true, + "Tiered-RBAC": true, + "Pods": true, + "QoS": true, + "Datapath": true, +} + +// RequiresNoEncap marks tests that require unencapsulated traffic to function. +// This is typically used for tests that verify BGP functionality without IPIP, or other similar tests. +// Such tests must be run on clusters that support unencapsulated traffic, such as bare-metal clusters +// or cloud clusters with appropriate configuration. +func RequiresNoEncap() any { + return framework.WithLabel("NoEncap") } // WithFeature marks tests as verifying a specific feature. @@ -86,7 +102,7 @@ func WithAzure() any { return framework.WithLabel("RunsOnAzure") } -// WithAzure marks tests that must run on AWS. +// WithAWS marks tests that must run on AWS. func WithAWS() any { return framework.WithLabel("RunsOnAWS") } diff --git a/e2e/pkg/tests/apis/owner_references.go b/e2e/pkg/tests/apis/owner_references.go index 14c8c1f379e..4aa99c8ff34 100644 --- a/e2e/pkg/tests/apis/owner_references.go +++ b/e2e/pkg/tests/apis/owner_references.go @@ -16,7 +16,9 @@ import ( "fmt" "time" - . "github.com/onsi/ginkgo/v2" + "github.com/onsi/ginkgo/v2" + + //nolint:staticcheck // Ignore ST1001: should not use dot imports . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "k8s.io/apimachinery/pkg/api/errors" @@ -41,7 +43,7 @@ var _ = describe.CalicoDescribe( ctx context.Context ) - BeforeEach(func() { + ginkgo.BeforeEach(func() { // Ensure a clean starting environment before each test. var err error cli, err = client.New(f.ClientConfig()) @@ -55,7 +57,7 @@ var _ = describe.CalicoDescribe( // This test verifies that the Kubernetes Garbage Collector correctly deletes Calico objects // with OwnerReferences if the referenced owner is deleted. - It("should delete a NetworkSet if its owner has been deleted", func() { + ginkgo.It("should delete a NetworkSet if its owner has been deleted", func() { // Create a NetworkSet that will act as the owner. ns := v3.NewNetworkSet() ns.Name = "parent" diff --git a/e2e/pkg/tests/bgp/bgp_password.go b/e2e/pkg/tests/bgp/bgp_password.go new file mode 100644 index 00000000000..321dc3681d7 --- /dev/null +++ b/e2e/pkg/tests/bgp/bgp_password.go @@ -0,0 +1,349 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. +// +// 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 bgp + +import ( + "context" + "fmt" + + "github.com/onsi/ginkgo/v2" + + //nolint:staticcheck // Ignore ST1001: should not use dot imports + . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + v1 "github.com/tigera/operator/api/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/test/e2e/framework" + "k8s.io/utils/ptr" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/projectcalico/calico/e2e/pkg/describe" + "github.com/projectcalico/calico/e2e/pkg/utils" + "github.com/projectcalico/calico/e2e/pkg/utils/client" + "github.com/projectcalico/calico/e2e/pkg/utils/conncheck" +) + +var _ = describe.CalicoDescribe( + describe.WithTeam(describe.Core), + describe.WithFeature("BGPPeer"), + describe.WithCategory(describe.Networking), + describe.WithSerial(), + "BGP password", + func() { + var cli ctrlclient.Client + var checker conncheck.ConnectionTester + var server1 *conncheck.Server + var client1 *conncheck.Client + var restoreBGPConfig func() + + f := utils.NewDefaultFramework("bgp-password") + + ginkgo.BeforeEach(func() { + checker = conncheck.NewConnectionTester(f) + + var err error + cli, err = client.New(f.ClientConfig()) + Expect(err).NotTo(HaveOccurred(), "failed to create API client") + + // Ensure a clean starting environment before each test. + Expect(utils.CleanDatastore(cli)).ShouldNot(HaveOccurred(), "failed to clean datastore") + + // We need a minimum of two nodes for BGP peering tests. + utils.RequireNodeCount(f, 2) + + // Verify BGP is enabled via the Installation resource. + installation := &v1.Installation{} + err = cli.Get(context.Background(), ctrlclient.ObjectKey{Name: "default"}, installation) + Expect(err).NotTo(HaveOccurred(), "Error querying Installation resource") + Expect(installation.Spec.CalicoNetwork).NotTo(BeNil(), "CalicoNetwork is not configured in the Installation") + Expect(installation.Spec.CalicoNetwork.BGP).NotTo(BeNil(), "BGP is not enabled in the cluster") + Expect(*installation.Spec.CalicoNetwork.BGP).To(Equal(v1.BGPEnabled), "BGP is not enabled in the cluster") + + // Ensure full mesh BGP is functioning before each test. + restoreBGPConfig = ensureInitialBGPConfig(cli) + + // Deploy server and client on different nodes to verify cross-node traffic. + server1 = conncheck.NewServer("server", f.Namespace, + conncheck.WithServerLabels(map[string]string{"role": "server"}), + conncheck.WithServerPodCustomizer(conncheck.AvoidEachOther), + ) + client1 = conncheck.NewClient("client", f.Namespace, + conncheck.WithClientCustomizer(conncheck.AvoidEachOther), + ) + checker.AddServer(server1) + checker.AddClient(client1) + checker.Deploy() + + // Verify initial connectivity via full mesh. + checker.ResetExpectations() + checker.ExpectSuccess(client1, server1.ClusterIPs()...) + checker.Execute() + }) + + ginkgo.AfterEach(func() { + checker.Stop() + restoreBGPConfig() + }) + + // Verifies that BGP peers establish and carry traffic when configured with + // matching passwords via secret references, and that mismatched passwords + // prevent BGP sessions from establishing. + ginkgo.It("should enforce BGP password authentication", func() { + ctx := context.Background() + calicoNS := utils.CalicoNamespace(f) + + // Create a shared secret and RBAC so calico-node can read the password. + ginkgo.By("Creating a BGP password secret and RBAC") + createBGPSecret(f, "bgp-password", calicoNS, "correct-horse") + createSecretRBAC(f, "bgp-password", calicoNS) + + // Create a selector-based BGPPeer that peers all nodes with password auth. + ginkgo.By("Creating a password-protected BGPPeer for all nodes") + peer := &v3.BGPPeer{ + ObjectMeta: metav1.ObjectMeta{Name: "password-peer"}, + Spec: v3.BGPPeerSpec{ + NodeSelector: "all()", + PeerSelector: "all()", + Password: &v3.BGPPassword{ + SecretKeyRef: &corev1.SecretKeySelector{ + Key: "password", + LocalObjectReference: corev1.LocalObjectReference{Name: "bgp-password"}, + }, + }, + }, + } + err := cli.Create(ctx, peer) + Expect(err).NotTo(HaveOccurred(), "failed to create password-protected BGPPeer") + ginkgo.DeferCleanup(func() { + if err := cli.Delete(context.Background(), peer); err != nil && !errors.IsNotFound(err) { + framework.Logf("WARNING: failed to delete BGPPeer %s: %v", peer.Name, err) + } + }) + + // Disable full mesh so only the password-protected peers are active. + disableFullMesh(cli) + + // Verify traffic flows through password-authenticated peers. + checker.ResetExpectations() + checker.ExpectSuccess(client1, server1.ClusterIPs()...) + checker.Execute() + + // --- Phase 2: Password mismatch should prevent peering. --- + + // Delete the working peer. With full mesh still disabled, only the + // per-node peers we create next will be active. + ginkgo.By("Removing the working peer for mismatch test") + err = cli.Delete(ctx, peer) + Expect(err).NotTo(HaveOccurred(), "failed to delete password-protected BGPPeer") + + // Get two nodes for explicit per-node peering with different passwords. + nodes := &corev1.NodeList{} + err = cli.List(ctx, nodes) + Expect(err).NotTo(HaveOccurred(), "failed to list nodes") + Expect(len(nodes.Items)).To(BeNumerically(">=", 2), "need at least 2 nodes") + node0 := nodes.Items[0] + node1 := nodes.Items[1] + node0IP := nodeInternalIP(&node0) + node1IP := nodeInternalIP(&node1) + + // Create two secrets with different passwords. + ginkgo.By("Creating two secrets with different passwords") + createBGPSecret(f, "bgp-secret-0", calicoNS, "alpha") + createBGPSecret(f, "bgp-secret-1", calicoNS, "beta") + createSecretRBAC(f, "bgp-secret-0", calicoNS) + createSecretRBAC(f, "bgp-secret-1", calicoNS) + + // Create explicit per-node peers with mismatched passwords. + ginkgo.By("Creating per-node BGPPeers with mismatched passwords") + createBGPPeerWithPassword(cli, "mismatch-peer-0", node0.Name, node1IP, "bgp-secret-0") + createBGPPeerWithPassword(cli, "mismatch-peer-1", node1.Name, node0IP, "bgp-secret-1") + + // Verify that the BGP sessions do not establish due to password mismatch. + // We use CalicoNodeStatus to check per-peer session state rather than + // exec'ing into calico-node pods, since CalicoNodeStatus is the user-facing + // API for observing BGP state. + ginkgo.By("Verifying BGP sessions stay non-established due to password mismatch") + expectBGPPeerNotEstablished(cli, node0.Name, node1IP) + expectBGPPeerNotEstablished(cli, node1.Name, node0IP) + }) + }, +) + +// createBGPSecret creates a Secret containing a BGP password and registers cleanup. +func createBGPSecret(f *framework.Framework, name, namespace, password string) { + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + StringData: map[string]string{ + "password": password, + }, + } + _, err := f.ClientSet.CoreV1().Secrets(namespace).Create(context.Background(), secret, metav1.CreateOptions{}) + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "failed to create secret %s", name) + + ginkgo.DeferCleanup(func() { + err := f.ClientSet.CoreV1().Secrets(namespace).Delete(context.Background(), name, metav1.DeleteOptions{}) + if err != nil && !errors.IsNotFound(err) { + framework.Logf("WARNING: failed to delete secret %s: %v", name, err) + } + }) +} + +// createSecretRBAC creates a Role and RoleBinding allowing calico-node to read the named secret. +func createSecretRBAC(f *framework.Framework, secretName, namespace string) { + role := &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: namespace, + }, + Rules: []rbacv1.PolicyRule{ + { + Verbs: []string{"watch", "list", "get"}, + APIGroups: []string{""}, + Resources: []string{"secrets"}, + ResourceNames: []string{secretName}, + }, + }, + } + _, err := f.ClientSet.RbacV1().Roles(namespace).Create(context.Background(), role, metav1.CreateOptions{}) + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "failed to create Role for secret %s", secretName) + ginkgo.DeferCleanup(func() { + err := f.ClientSet.RbacV1().Roles(namespace).Delete(context.Background(), secretName, metav1.DeleteOptions{}) + if err != nil && !errors.IsNotFound(err) { + framework.Logf("WARNING: failed to delete Role %s: %v", secretName, err) + } + }) + + binding := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: namespace, + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "calico-node", + Namespace: namespace, + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: secretName, + }, + } + _, err = f.ClientSet.RbacV1().RoleBindings(namespace).Create(context.Background(), binding, metav1.CreateOptions{}) + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "failed to create RoleBinding for secret %s", secretName) + ginkgo.DeferCleanup(func() { + err := f.ClientSet.RbacV1().RoleBindings(namespace).Delete(context.Background(), secretName, metav1.DeleteOptions{}) + if err != nil && !errors.IsNotFound(err) { + framework.Logf("WARNING: failed to delete RoleBinding %s: %v", secretName, err) + } + }) +} + +// createBGPPeerWithPassword creates a per-node BGPPeer with password authentication +// and registers cleanup. +func createBGPPeerWithPassword(cli ctrlclient.Client, name, node, peerIP, secretName string) { + peer := &v3.BGPPeer{ + ObjectMeta: metav1.ObjectMeta{Name: name}, + Spec: v3.BGPPeerSpec{ + Node: node, + PeerIP: peerIP, + ASNumber: 64512, + Password: &v3.BGPPassword{ + SecretKeyRef: &corev1.SecretKeySelector{ + Key: "password", + LocalObjectReference: corev1.LocalObjectReference{Name: secretName}, + }, + }, + }, + } + err := cli.Create(context.Background(), peer) + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "failed to create BGPPeer %s", name) + ginkgo.DeferCleanup(func() { + if err := cli.Delete(context.Background(), peer); err != nil && !errors.IsNotFound(err) { + framework.Logf("WARNING: failed to delete BGPPeer %s: %v", name, err) + } + }) +} + +// expectBGPPeerNotEstablished verifies via CalicoNodeStatus that the BGP session +// from nodeName to peerIP is not Established, and remains so for a sustained period. +func expectBGPPeerNotEstablished(cli ctrlclient.Client, nodeName, peerIP string) { + status := &v3.CalicoNodeStatus{ + ObjectMeta: metav1.ObjectMeta{Name: nodeName}, + Spec: v3.CalicoNodeStatusSpec{ + Node: nodeName, + Classes: []v3.NodeStatusClassType{v3.NodeStatusClassTypeBGP}, + UpdatePeriodSeconds: ptr.To[uint32](1), + }, + } + err := cli.Create(context.Background(), status) + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "failed to create CalicoNodeStatus for node %s", nodeName) + defer func() { + err := cli.Delete(context.Background(), status) + if err != nil && !errors.IsNotFound(err) { + framework.Logf("WARNING: failed to delete CalicoNodeStatus for node %s: %v", nodeName, err) + } + }() + + // Wait for the specific peer to appear in the status report as non-established. + EventuallyWithOffset(1, func() error { + if err := cli.Get(context.Background(), ctrlclient.ObjectKey{Name: nodeName}, status); err != nil { + return fmt.Errorf("failed to get CalicoNodeStatus for node %s: %w", nodeName, err) + } + for _, peer := range status.Status.BGP.PeersV4 { + if peer.PeerIP == peerIP { + if peer.State == v3.BGPSessionStateEstablished { + return fmt.Errorf("peer %s on node %s is unexpectedly Established", peerIP, nodeName) + } + // Found the peer and it's not established — this is the expected state. + return nil + } + } + return fmt.Errorf("peer %s not yet reported in CalicoNodeStatus for node %s (peers: %v)", + peerIP, nodeName, status.Status.BGP.PeersV4) + }, "30s", "1s").Should(Succeed(), "peer %s on node %s should appear as non-established", peerIP, nodeName) + + // Verify the peer stays non-established over a sustained period. + ConsistentlyWithOffset(1, func() error { + if err := cli.Get(context.Background(), ctrlclient.ObjectKey{Name: nodeName}, status); err != nil { + return nil // Transient error, don't fail Consistently + } + for _, peer := range status.Status.BGP.PeersV4 { + if peer.PeerIP == peerIP && peer.State == v3.BGPSessionStateEstablished { + return fmt.Errorf("peer %s on node %s unexpectedly became Established", peerIP, nodeName) + } + } + return nil + }, "10s", "1s").Should(Succeed(), "peer %s on node %s should remain non-established", peerIP, nodeName) +} + +// nodeInternalIP returns the first InternalIP address of a node. +func nodeInternalIP(node *corev1.Node) string { + for _, addr := range node.Status.Addresses { + if addr.Type == corev1.NodeInternalIP { + return addr.Address + } + } + framework.Failf("no InternalIP found for node %s", node.Name) + return "" +} diff --git a/e2e/pkg/tests/bgp/export.go b/e2e/pkg/tests/bgp/export.go new file mode 100644 index 00000000000..46384073689 --- /dev/null +++ b/e2e/pkg/tests/bgp/export.go @@ -0,0 +1,160 @@ +// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// +// 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 bgp + +import ( + "context" + "fmt" + + "github.com/onsi/ginkgo/v2" + + //nolint:staticcheck // Ignore ST1001: should not use dot imports + . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + v1 "github.com/tigera/operator/api/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/projectcalico/calico/e2e/pkg/describe" + "github.com/projectcalico/calico/e2e/pkg/utils" + "github.com/projectcalico/calico/e2e/pkg/utils/client" + "github.com/projectcalico/calico/e2e/pkg/utils/conncheck" +) + +var _ = describe.CalicoDescribe( + describe.WithTeam(describe.Core), + describe.WithFeature("BGPPeer"), + describe.WithCategory(describe.Networking), + "BGP export tests", + func() { + // Define variables common across all tests. + var err error + var cli ctrlclient.Client + var checker conncheck.ConnectionTester + var server1 *conncheck.Server + var client1 *conncheck.Client + var restoreBGPConfig func() + + // Create a new framework for the tests. + f := utils.NewDefaultFramework("bgp-export") + + ginkgo.BeforeEach(func() { + // Create a connection tester for the test. + checker = conncheck.NewConnectionTester(f) + + cli, err = client.New(f.ClientConfig()) + Expect(err).NotTo(HaveOccurred()) + + // Ensure a clean starting environment before each test. + Expect(utils.CleanDatastore(cli)).ShouldNot(HaveOccurred()) + + // We need a minimum of two nodes for BGP peering tests. + utils.RequireNodeCount(f, 2) + + // Make sure the cluster is in BGP mode by querying the Installation resource. The tests in this file + // all require BGP, and all require Calico be installed by the operator. + installation := &v1.Installation{} + err = cli.Get(context.Background(), ctrlclient.ObjectKey{Name: "default"}, installation) + Expect(err).NotTo(HaveOccurred(), "Error querying Installation resource") + Expect(installation.Spec.CalicoNetwork).NotTo(BeNil(), "CalicoNetwork is not configured in the Installation") + Expect(installation.Spec.CalicoNetwork.BGP).NotTo(BeNil(), "BGP is not enabled in the cluster") + Expect(*installation.Spec.CalicoNetwork.BGP).To(Equal(v1.BGPEnabled), "BGP is not enabled in the cluster") + + // Ensure full mesh BGP is functioning before each test. + restoreBGPConfig = ensureInitialBGPConfig(cli) + + // Create an IP pool for the test. + pool := v3.NewIPPool() + pool.Name = "bgp-export-pool" + pool.Spec.CIDR = "172.24.0.0/16" + pool.Spec.NATOutgoing = true + pool.Spec.BlockSize = 26 + pool.Spec.DisableBGPExport = false + err = cli.Create(context.Background(), pool) + Expect(err).NotTo(HaveOccurred(), "Error creating IP pool") + ginkgo.DeferCleanup(func() { + err = cli.Delete(context.Background(), pool) + Expect(err).NotTo(HaveOccurred(), "Error deleting IP pool") + }) + + // Before each test, perform the following steps: + // - Create a server pod and corresponding service in the main namespace for the test. + // - Create a client pod and assert that it can connect to the service. + ginkgo.By(fmt.Sprintf("Creating server pod in namespace %s", f.Namespace.Name)) + + // Use customizers to ensure pods use the test IP pool and avoid landing on the same node. + customtizer := conncheck.CombineCustomizers( + conncheck.UseV4IPPool(pool.Name), + conncheck.AvoidEachOther, + ) + server1 = conncheck.NewServer( + "server", + f.Namespace, + conncheck.WithServerLabels(map[string]string{"role": "server"}), + conncheck.WithServerPodCustomizer(customtizer), + ) + client1 = conncheck.NewClient( + "client", + f.Namespace, + conncheck.WithClientCustomizer(customtizer), + ) + checker.AddServer(server1) + checker.AddClient(client1) + checker.Deploy() + + // Verify initial connectivity. + checker.ResetExpectations() + checker.ExpectSuccess(client1, server1.ClusterIPs()...) + checker.Execute() + }) + + ginkgo.AfterEach(func() { + checker.Stop() + restoreBGPConfig() + }) + + ginkgo.It("should not export pools with export disabled", func() { + // Disable the node to node mesh and replace it with explicit peerings. + disableFullMesh(cli) + ginkgo.By("Creating a BGPPeer to re-enable connectivity, simulating full mesh") + peer := &v3.BGPPeer{ + ObjectMeta: metav1.ObjectMeta{Name: "peer-to-self"}, + Spec: v3.BGPPeerSpec{ + NodeSelector: "all()", + PeerSelector: "all()", + }, + } + err = cli.Create(context.Background(), peer) + Expect(err).NotTo(HaveOccurred(), "Error creating BGPPeer resource") + ginkgo.DeferCleanup(func() { + err := cli.Delete(context.Background(), peer) + Expect(err).NotTo(HaveOccurred(), "Error deleting BGPPeer resource during cleanup") + }) + + // Disable BGP export on the pool. + pool := &v3.IPPool{} + err = cli.Get(context.Background(), ctrlclient.ObjectKey{Name: "bgp-export-pool"}, pool) + Expect(err).NotTo(HaveOccurred(), "Error querying IP pool") + pool.Spec.DisableBGPExport = true + err = cli.Update(context.Background(), pool) + Expect(err).NotTo(HaveOccurred(), "Error updating IP pool") + + // Routing should stop working on the IPv4 pool. If there is an IPv6 pool in the cluster, it will + // continue to work, so only test the IPv4 address. + checker.ResetExpectations() + checker.ExpectFailure(client1, server1.ClusterIPv4()) + checker.Execute() + }) + }) diff --git a/e2e/pkg/tests/bgp/peers.go b/e2e/pkg/tests/bgp/peers.go new file mode 100644 index 00000000000..39fe53fa7e3 --- /dev/null +++ b/e2e/pkg/tests/bgp/peers.go @@ -0,0 +1,185 @@ +// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// +// 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 bgp + +import ( + "context" + "fmt" + + "github.com/onsi/ginkgo/v2" + + //nolint:staticcheck // Ignore ST1001: should not use dot imports + . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + v1 "github.com/tigera/operator/api/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/projectcalico/calico/e2e/pkg/describe" + "github.com/projectcalico/calico/e2e/pkg/utils" + "github.com/projectcalico/calico/e2e/pkg/utils/client" + "github.com/projectcalico/calico/e2e/pkg/utils/conncheck" +) + +var _ = describe.CalicoDescribe( + describe.WithTeam(describe.Core), + describe.WithFeature("BGPPeer"), + describe.WithCategory(describe.Networking), + "BGPPeer", + func() { + // Define variables common across all tests. + var err error + var cli ctrlclient.Client + var checker conncheck.ConnectionTester + var server1 *conncheck.Server + var client1 *conncheck.Client + var restoreBGPConfig func() + + // Create a new framework for the tests. + f := utils.NewDefaultFramework("bgppeer") + + ginkgo.BeforeEach(func() { + // Create a connection tester for the test. + checker = conncheck.NewConnectionTester(f) + + cli, err = client.New(f.ClientConfig()) + Expect(err).NotTo(HaveOccurred()) + + // Ensure a clean starting environment before each test. + Expect(utils.CleanDatastore(cli)).ShouldNot(HaveOccurred()) + + // We need a minimum of two nodes for BGP peering tests. + utils.RequireNodeCount(f, 2) + + // Make sure the cluster is in BGP mode by querying the Installation resource. The tests in this file + // all require BGP, and all require Calico be installed by the operator. + installation := &v1.Installation{} + err = cli.Get(context.Background(), ctrlclient.ObjectKey{Name: "default"}, installation) + Expect(err).NotTo(HaveOccurred(), "Error querying Installation resource") + Expect(installation.Spec.CalicoNetwork).NotTo(BeNil(), "CalicoNetwork is not configured in the Installation") + Expect(installation.Spec.CalicoNetwork.BGP).NotTo(BeNil(), "BGP is not enabled in the cluster") + Expect(*installation.Spec.CalicoNetwork.BGP).To(Equal(v1.BGPEnabled), "BGP is not enabled in the cluster") + + // Ensure full mesh BGP is functioning before each test. + restoreBGPConfig = ensureInitialBGPConfig(cli) + + // Before each test, perform the following steps: + // - Create a server pod and corresponding service in the main namespace for the test. + // - Create a client pod and assert that it can connect to the service. + ginkgo.By(fmt.Sprintf("Creating server pod in namespace %s", f.Namespace.Name)) + server1 = conncheck.NewServer( + "server", + f.Namespace, + conncheck.WithServerLabels(map[string]string{"role": "server"}), + conncheck.WithServerPodCustomizer(conncheck.AvoidEachOther), + ) + client1 = conncheck.NewClient( + "client", + f.Namespace, + conncheck.WithClientCustomizer(conncheck.AvoidEachOther), + ) + checker.AddServer(server1) + checker.AddClient(client1) + checker.Deploy() + + // Verify initial connectivity. + checker.ResetExpectations() + checker.ExpectSuccess(client1, server1.ClusterIPs()...) + checker.Execute() + }) + + ginkgo.AfterEach(func() { + checker.Stop() + restoreBGPConfig() + }) + + ginkgo.It("should support BGP peers", func() { + // Disable full mesh BGP. + disableFullMesh(cli) + + // Verify connectivity is lost. + checker.ResetExpectations() + checker.ExpectFailure(client1, server1.ClusterIPs()...) + checker.Execute() + + // Create a BGPPeer to re-enable connectivity. + ginkgo.By("Creating a BGPPeer to re-enable connectivity, simulating full mesh") + peer := &v3.BGPPeer{ + ObjectMeta: metav1.ObjectMeta{Name: "peer-to-self"}, + Spec: v3.BGPPeerSpec{ + NodeSelector: "all()", + PeerSelector: "all()", + }, + } + err = cli.Create(context.Background(), peer) + Expect(err).NotTo(HaveOccurred(), "Error creating BGPPeer resource") + ginkgo.DeferCleanup(func() { + err := cli.Delete(context.Background(), peer) + if !errors.IsNotFound(err) { + Expect(err).NotTo(HaveOccurred(), "Error deleting BGPPeer resource during cleanup") + } + }) + + // Verify connectivity is restored. + checker.ResetExpectations() + checker.ExpectSuccess(client1, server1.ClusterIPs()...) + checker.Execute() + + // Delete the BGPPeer to disable connectivity again. + ginkgo.By("Deleting the BGPPeer to disable connectivity again") + err = cli.Delete(context.Background(), peer) + Expect(err).NotTo(HaveOccurred(), "Error deleting BGPPeer resource") + + // Verify connectivity is lost again. + checker.ResetExpectations() + checker.ExpectFailure(client1, server1.ClusterIPs()...) + checker.Execute() + + // Create per-node BGPPeers to re-enable connectivity. + ginkgo.By("Creating per-node BGPPeers to re-enable connectivity") + nodes := &corev1.NodeList{} + err = cli.List(context.Background(), nodes) + Expect(err).NotTo(HaveOccurred(), "Error querying nodes in the cluster") + for _, node := range nodes.Items { + for _, node2 := range nodes.Items { + if node.Name == node2.Name { + continue + } + peer := &v3.BGPPeer{ + ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("peer-%s-to-%s", node.Name, node2.Name)}, + Spec: v3.BGPPeerSpec{ + Node: node.Name, + PeerSelector: fmt.Sprintf("kubernetes.io/hostname == '%s'", node2.Name), + }, + } + err = cli.Create(context.Background(), peer) + Expect(err).NotTo(HaveOccurred(), "Error creating per-node BGPPeer resource") + ginkgo.DeferCleanup(func() { + err := cli.Delete(context.Background(), peer) + if !errors.IsNotFound(err) { + Expect(err).NotTo(HaveOccurred(), "Error deleting per-node BGPPeer resource during cleanup") + } + }) + } + } + + // Verify connectivity is restored. + checker.ResetExpectations() + checker.ExpectSuccess(client1, server1.ClusterIPs()...) + checker.Execute() + }) + }) diff --git a/e2e/pkg/tests/bgp/route_reflector.go b/e2e/pkg/tests/bgp/route_reflector.go new file mode 100644 index 00000000000..c434a52cbff --- /dev/null +++ b/e2e/pkg/tests/bgp/route_reflector.go @@ -0,0 +1,303 @@ +// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// +// 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 bgp + +import ( + "context" + "fmt" + "time" + + "github.com/onsi/ginkgo/v2" + + //nolint:staticcheck // Ignore ST1001: should not use dot imports + . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "github.com/projectcalico/api/pkg/lib/numorstring" + v1 "github.com/tigera/operator/api/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/projectcalico/calico/e2e/pkg/describe" + "github.com/projectcalico/calico/e2e/pkg/utils" + "github.com/projectcalico/calico/e2e/pkg/utils/client" + "github.com/projectcalico/calico/e2e/pkg/utils/conncheck" + "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/resources" +) + +var _ = describe.CalicoDescribe( + describe.WithTeam(describe.Core), + describe.WithFeature("BGPPeer"), + describe.WithCategory(describe.Networking), + describe.WithDisruptive(), + "Route reflectors", + func() { + // Define variables common across all tests. + var err error + var cli ctrlclient.Client + var checker conncheck.ConnectionTester + var server1 *conncheck.Server + var client1 *conncheck.Client + var client2 *conncheck.Client + var restoreBGPConfig func() + + // Create a new framework for the tests. + f := utils.NewDefaultFramework("route-reflection") + + ginkgo.BeforeEach(func() { + // Create a connection tester for the test. + checker = conncheck.NewConnectionTester(f) + + cli, err = client.New(f.ClientConfig()) + Expect(err).NotTo(HaveOccurred()) + + // Ensure a clean starting environment before each test. + Expect(utils.CleanDatastore(cli)).ShouldNot(HaveOccurred()) + + // We need a minimum of two nodes for BGP peering tests. + utils.RequireNodeCount(f, 3) + + // Make sure the cluster is in BGP mode by querying the Installation resource. The tests in this file + // all require BGP, and all require Calico be installed by the operator. + installation := &v1.Installation{} + err = cli.Get(context.Background(), ctrlclient.ObjectKey{Name: "default"}, installation) + Expect(err).NotTo(HaveOccurred(), "Error querying Installation resource") + Expect(installation.Spec.CalicoNetwork).NotTo(BeNil(), "CalicoNetwork is not configured in the Installation") + Expect(installation.Spec.CalicoNetwork.BGP).NotTo(BeNil(), "BGP is not enabled in the cluster") + Expect(*installation.Spec.CalicoNetwork.BGP).To(Equal(v1.BGPEnabled), "BGP is not enabled in the cluster") + + // Ensure full mesh BGP is functioning before each test. + restoreBGPConfig = ensureInitialBGPConfig(cli) + + // Before each test, perform the following steps: + // - Create a server pod and corresponding service in the main namespace for the test. + // - Create a client pod and assert that it can connect to the service. + ginkgo.By(fmt.Sprintf("Creating server pod in namespace %s", f.Namespace.Name)) + server1 = conncheck.NewServer( + "server", + f.Namespace, + conncheck.WithServerLabels(map[string]string{"role": "server"}), + conncheck.WithServerPodCustomizer(conncheck.AvoidEachOther), + ) + client1 = conncheck.NewClient( + "client", + f.Namespace, + conncheck.WithClientCustomizer(conncheck.AvoidEachOther), + ) + client2 = conncheck.NewClient( + "client2", + f.Namespace, + conncheck.WithClientCustomizer(conncheck.AvoidEachOther), + ) + checker.AddServer(server1) + checker.AddClient(client1) + checker.AddClient(client2) + checker.Deploy() + + // Verify initial connectivity. + checker.ResetExpectations() + checker.ExpectSuccess(client1, server1.ClusterIPs()...) + checker.ExpectSuccess(client2, server1.ClusterIPs()...) + checker.Execute() + }) + + ginkgo.AfterEach(func() { + checker.Stop() + restoreBGPConfig() + }) + + ginkgo.It("should support in-cluster route reflectors", func() { + // Disable full mesh BGP. + disableFullMesh(cli) + + // Set the AS number for the cluster to a non-default value. We do this to get + // coverage of changing the AS number, but it is not strictly required for this test. + asn, err := numorstring.ASNumberFromString("4294.566") + Expect(err).NotTo(HaveOccurred(), "Error converting AS number from string") + setASNumber(cli, asn) + + // Verify connectivity is lost because the full mesh is disabled. + checker.ResetExpectations() + checker.ExpectFailure(client1, server1.ClusterIPs()...) + checker.ExpectFailure(client2, server1.ClusterIPs()...) + checker.Execute() + + // Select a node to act as a route reflector. Give it a RR cluster ID, as well + // as a label identifying it as a route reflector. + nodes := corev1.NodeList{} + err = cli.List(context.Background(), &nodes) + Expect(err).NotTo(HaveOccurred(), "Error querying nodes in the cluster") + Expect(nodes.Items).NotTo(BeEmpty(), "No nodes found in the cluster") + setNodeAsRouteReflector(cli, &nodes.Items[0], "225.0.0.4") + + // Create BGP peer that causes all non-RR nodes to peer with the RR. + ginkgo.By("Creating a BGPPeer to re-enable peers via the RR") + peer := &v3.BGPPeer{ + ObjectMeta: metav1.ObjectMeta{Name: "peer-to-rrs"}, + Spec: v3.BGPPeerSpec{ + NodeSelector: fmt.Sprintf("!has(%s)", resources.RouteReflectorClusterIDAnnotation), + PeerSelector: fmt.Sprintf("has(%s)", resources.RouteReflectorClusterIDAnnotation), + }, + } + err = cli.Create(context.Background(), peer) + Expect(err).NotTo(HaveOccurred(), "Error creating BGPPeer resource") + ginkgo.DeferCleanup(func() { + err := cli.Delete(context.Background(), peer) + Expect(err).NotTo(HaveOccurred(), "Error deleting BGPPeer resource during cleanup") + }) + + // Verify connectivity is restored. + checker.ResetExpectations() + checker.ExpectSuccess(client1, server1.ClusterIPs()...) + checker.ExpectSuccess(client2, server1.ClusterIPs()...) + checker.Execute() + }) + + ginkgo.It("should support clustered route reflectors", func() { + // Disable full mesh BGP. + disableFullMesh(cli) + + // Set the AS number for the cluster to a non-default value. We do this to get + // coverage of changing the AS number, but it is not strictly required for this test. + setASNumber(cli, 64514) + + // Verify connectivity is lost. + checker.ResetExpectations() + checker.ExpectFailure(client1, server1.ClusterIPs()...) + checker.ExpectFailure(client2, server1.ClusterIPs()...) + checker.Execute() + + // Select two nodes to act as route reflectors. Give them RR cluster IDs, as well + // as a label identifying them as route reflectors. + nodes := corev1.NodeList{} + err = cli.List(context.Background(), &nodes) + Expect(err).NotTo(HaveOccurred(), "Error querying nodes in the cluster") + setNodeAsRouteReflector(cli, &nodes.Items[0], "225.0.0.4") + setNodeAsRouteReflector(cli, &nodes.Items[1], "225.0.0.4") + + // Create BGP peer that causes all non-RR nodes to peer with the RRs. + ginkgo.By("Creating a BGPPeer to re-enable peers via the RRs") + peer := &v3.BGPPeer{ + ObjectMeta: metav1.ObjectMeta{Name: "peer-to-rrs"}, + Spec: v3.BGPPeerSpec{ + NodeSelector: fmt.Sprintf("!has(%s)", resources.RouteReflectorClusterIDAnnotation), + PeerSelector: fmt.Sprintf("has(%s)", resources.RouteReflectorClusterIDAnnotation), + }, + } + err = cli.Create(context.Background(), peer) + Expect(err).NotTo(HaveOccurred(), "Error creating BGPPeer resource") + ginkgo.DeferCleanup(func() { + err := cli.Delete(context.Background(), peer) + Expect(err).NotTo(HaveOccurred(), "Error deleting BGPPeer resource during cleanup") + }) + + // Wait for BGP to converge. + waitForBGPEstablished(cli, nodes.Items...) + + // Verify connectivity is restored. + checker.ResetExpectations() + checker.ExpectSuccess(client1, server1.ClusterIPs()...) + checker.ExpectSuccess(client2, server1.ClusterIPs()...) + checker.Execute() + + // By bringing down one of the nodes acting as a route reflector, we can verify that + // route reflection is functioning correctly. + deleteCalicoNode(cli, &nodes.Items[1]) + + // Verify connectivity is maintained. + checker.ResetExpectations() + checker.ExpectSuccess(client1, server1.ClusterIPs()...) + checker.ExpectSuccess(client2, server1.ClusterIPs()...) + checker.Execute() + + // Wait for BGP to converge again after the node restarts. + waitForBGPEstablished(cli, nodes.Items...) + + // Now, remove one of the RRs and verify connectivity is maintained. + ginkgo.By("Removing one of the route reflectors") + setNodeAsNotRouteReflector(cli, &nodes.Items[1]) + + // Wait for BGP to converge. + waitForBGPEstablished(cli, nodes.Items...) + + // Verify connectivity is maintained. + checker.ResetExpectations() + checker.ExpectSuccess(client1, server1.ClusterIPs()...) + checker.ExpectSuccess(client2, server1.ClusterIPs()...) + checker.Execute() + + // Finally, remove the last RR and verify connectivity is lost. + ginkgo.By("Removing the last route reflector") + setNodeAsNotRouteReflector(cli, &nodes.Items[0]) + + // Verify connectivity is lost. + checker.ResetExpectations() + checker.ExpectFailure(client1, server1.ClusterIPs()...) + checker.ExpectFailure(client2, server1.ClusterIPs()...) + checker.Execute() + }) + + ginkgo.It("should support graceful transition to route reflectors", func() { + // Start a continuous connectivity check in the background. + checkpoint := checker.ExpectContinuously(client1, server1.ICMP(), server1.ClusterIP()) + defer checkpoint.Stop() + checkpoint.ExpectSuccess("at the start of the test") + + // Select a node to act as a route reflector. Give it a RR cluster ID, as well + // as a label identifying it as a route reflector. + nodes := corev1.NodeList{} + err = cli.List(context.Background(), &nodes) + Expect(err).NotTo(HaveOccurred(), "Error querying nodes in the cluster") + Expect(nodes.Items).NotTo(BeEmpty(), "No nodes found in the cluster") + setNodeAsRouteReflector(cli, &nodes.Items[0], "225.0.0.4") + + // Create BGP peer that causes all non-RR nodes to peer with the RR. + ginkgo.By("Creating a BGPPeer to enable peerings to the RR") + peer := &v3.BGPPeer{ + ObjectMeta: metav1.ObjectMeta{Name: "peer-to-rrs"}, + Spec: v3.BGPPeerSpec{ + NodeSelector: fmt.Sprintf("!has(%s)", resources.RouteReflectorClusterIDAnnotation), + PeerSelector: fmt.Sprintf("has(%s)", resources.RouteReflectorClusterIDAnnotation), + }, + } + err = cli.Create(context.Background(), peer) + Expect(err).NotTo(HaveOccurred(), "Error creating BGPPeer resource") + ginkgo.DeferCleanup(func() { + err := cli.Delete(context.Background(), peer) + Expect(err).NotTo(HaveOccurred(), "Error deleting BGPPeer resource during cleanup") + }) + + // Wait until BGP has converged. + waitForBGPEstablished(cli, nodes.Items...) + + // At this point, both full mesh and route reflection are enabled, so connectivity + // should be fine. + checkpoint.ExpectSuccess("after enabling route reflectors") + + // Now disable full mesh BGP - connectivity should be maintained via the RR. + disableFullMesh(cli) + + // Wait until BGP has converged again, plus a little extra time to ensure stability. + waitForBGPEstablished(cli, nodes.Items...) + time.Sleep(5 * time.Second) + + checkpoint.ExpectSuccess("after disabling full mesh") + + // Deleting the route reflector role should cause connectivity to be lost. + setNodeAsNotRouteReflector(cli, &nodes.Items[0]) + + checkpoint.ExpectFailure("after removing route reflector role") + }) + }) diff --git a/e2e/pkg/tests/bgp/utils.go b/e2e/pkg/tests/bgp/utils.go new file mode 100644 index 00000000000..0084c507566 --- /dev/null +++ b/e2e/pkg/tests/bgp/utils.go @@ -0,0 +1,175 @@ +package bgp + +import ( + "context" + "fmt" + + "github.com/onsi/ginkgo/v2" + + //nolint:staticcheck // Ignore ST1001: should not use dot imports + . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "github.com/projectcalico/api/pkg/lib/numorstring" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/resources" +) + +// ensureInitialBGPConfig checks for an existing BGPConfiguration resource and ensures that full mesh BGP is enabled. +// It returns a cleanup function to restore the original state after the test. +func ensureInitialBGPConfig(cli ctrlclient.Client) func() { + // Ensure full mesh BGP is functioning before each test. + initialConfig := &v3.BGPConfiguration{} + err := cli.Get(context.Background(), ctrlclient.ObjectKey{Name: "default"}, initialConfig) + if errors.IsNotFound(err) { + // Not found - simply create a new one, enabling full mesh (the default behavior). Ideally, our product code + // would do this automatically, but we do it here until it does. + ginkgo.By("Creating default BGPConfiguration suitable for tests") + err = cli.Create(context.Background(), &v3.BGPConfiguration{ + ObjectMeta: metav1.ObjectMeta{Name: "default"}, + Spec: v3.BGPConfigurationSpec{ + NodeToNodeMeshEnabled: ptr.To(true), + }, + }) + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Error creating BGPConfiguration resource") + + return func() { + ginkgo.By("Deleting BGPConfiguration created for tests") + err := cli.Delete(context.Background(), &v3.BGPConfiguration{ObjectMeta: metav1.ObjectMeta{Name: "default"}}) + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Error deleting BGPConfiguration resource") + } + } + + ginkgo.By("Ensuring full mesh BGP is enabled in existing BGPConfiguration") + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Error querying BGPConfiguration resource") + ExpectWithOffset(1, initialConfig.Spec.NodeToNodeMeshEnabled).NotTo(BeNil(), "nodeToNodeMeshEnabled is not configured in BGPConfiguration") + ExpectWithOffset(1, *initialConfig.Spec.NodeToNodeMeshEnabled).To(BeTrue(), "nodeToNodeMeshEnabled is not enabled in BGPConfiguration") + + return func() { + ginkgo.By("Restoring initial BGPConfiguration") + // Query the resource again to get the latest resource version. + currentConfig := &v3.BGPConfiguration{} + err := cli.Get(context.Background(), ctrlclient.ObjectKey{Name: "default"}, currentConfig) + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Error querying BGPConfiguration resource for restoration") + initialConfig.ResourceVersion = currentConfig.ResourceVersion + err = cli.Update(context.Background(), initialConfig) + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Error restoring initial BGPConfiguration resource") + } +} + +func disableFullMesh(cli ctrlclient.Client) { + ginkgo.By("Disabling full mesh BGP") + config := &v3.BGPConfiguration{} + err := cli.Get(context.Background(), ctrlclient.ObjectKey{Name: "default"}, config) + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Error querying BGPConfiguration resource") + config.Spec.NodeToNodeMeshEnabled = ptr.To(false) + err = cli.Update(context.Background(), config) + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Error updating BGPConfiguration resource") +} + +func setASNumber(cli ctrlclient.Client, asn numorstring.ASNumber) { + ginkgo.By("Setting AS number in BGPConfiguration") + config := &v3.BGPConfiguration{} + err := cli.Get(context.Background(), ctrlclient.ObjectKey{Name: "default"}, config) + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Error querying BGPConfiguration resource") + config.Spec.ASNumber = ptr.To(numorstring.ASNumber(asn)) + err = cli.Update(context.Background(), config) + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Error updating BGPConfiguration resource") +} + +func setNodeAsRouteReflector(cli ctrlclient.Client, rrNode *corev1.Node, rrClusterID string) { + ginkgo.By(fmt.Sprintf("Using node %s as a route reflector", rrNode.Name)) + prePatch := ctrlclient.MergeFrom(rrNode.DeepCopy()) + + // Adding the cluster ID as an annotation tells Calico to configure the node as a route reflector. + if rrNode.Annotations == nil { + rrNode.Annotations = map[string]string{} + } + + // Adding it as a label allows us to select the node in a BGPPeer. + if rrNode.Labels == nil { + rrNode.Labels = map[string]string{} + } + + rrNode.Labels[resources.RouteReflectorClusterIDAnnotation] = rrClusterID + rrNode.Annotations[resources.RouteReflectorClusterIDAnnotation] = rrClusterID + err := cli.Patch(context.Background(), rrNode, prePatch) + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Error marking node as route reflector") + + ginkgo.DeferCleanup(func() { + setNodeAsNotRouteReflector(cli, rrNode) + }) +} + +func setNodeAsNotRouteReflector(cli ctrlclient.Client, rrNode *corev1.Node) { + ginkgo.By(fmt.Sprintf("Removing route reflector role from node %s", rrNode.Name)) + prePatch := ctrlclient.MergeFrom(rrNode.DeepCopy()) + delete(rrNode.Labels, resources.RouteReflectorClusterIDAnnotation) + delete(rrNode.Annotations, resources.RouteReflectorClusterIDAnnotation) + err := cli.Patch(context.Background(), rrNode, prePatch) + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Error removing route reflector label from node") +} + +func deleteCalicoNode(cli ctrlclient.Client, node *corev1.Node) { + ginkgo.By(fmt.Sprintf("Simulating failure of node %s by deleting calico-node Pod", node.Name)) + + // Get the calico/node Pod on the node. + podList := &corev1.PodList{} + err := cli.List(context.Background(), podList, ctrlclient.MatchingLabels{"k8s-app": "calico-node"}, ctrlclient.MatchingFields{"spec.nodeName": node.Name}) + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Error listing calico-node Pods on node") + ExpectWithOffset(1, len(podList.Items)).To(BeNumerically(">", 0), "No calico-node Pod found on node") + + // Delete the calico/node Pod to simulate failure. + err = cli.Delete(context.Background(), &podList.Items[0]) + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Error deleting calico-node Pod to simulate node failure") +} + +// waitForBGPEstablished waits until the expected number of BGPPeers are established for the given nodes +// using CalicoNodeStatus resources. +func waitForBGPEstablished(cli ctrlclient.Client, nodes ...corev1.Node) { + for _, node := range nodes { + waitForBGPEstablishedForNode(cli, node.Name) + } +} + +func waitForBGPEstablishedForNode(cli ctrlclient.Client, node string) { + // Create a CalicoNodeStatus resource to verify BGPPeer status. + status := &v3.CalicoNodeStatus{ + ObjectMeta: metav1.ObjectMeta{Name: node}, + Spec: v3.CalicoNodeStatusSpec{ + Node: node, + Classes: []v3.NodeStatusClassType{v3.NodeStatusClassTypeBGP}, + + // Set a short update period to speed up the test. + UpdatePeriodSeconds: ptr.To[uint32](1), + }, + } + err := cli.Create(context.Background(), status) + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Error creating CalicoNodeStatus resource") + + // Make sure we clean up the CalicoNodeStatus resource after we're done. + defer func() { + err := cli.Delete(context.Background(), status) + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Error deleting CalicoNodeStatus resource") + }() + + // Expect the CalicoNodeStatus to report all BGPPeers as established. + EventuallyWithOffset(1, func() error { + err := cli.Get(context.Background(), ctrlclient.ObjectKey{Name: node}, status) + if err != nil { + return err + } + if status.Status.BGP.NumberEstablishedV4+status.Status.BGP.NumberEstablishedV6 == 0 { + return fmt.Errorf("no BGPPeers are established yet") + } + if status.Status.BGP.NumberNotEstablishedV4+status.Status.BGP.NumberNotEstablishedV6 > 0 { + return fmt.Errorf("not all BGPPeers are established (not established: v4=%d, v6=%d)", + status.Status.BGP.NumberNotEstablishedV4, status.Status.BGP.NumberNotEstablishedV6) + } + return nil + }, "1m", "1s").ShouldNot(HaveOccurred(), "BGPPeer count did not reach expected value") +} diff --git a/e2e/pkg/tests/hostendpoints/auto_hostendpoints.go b/e2e/pkg/tests/hostendpoints/auto_hostendpoints.go index c348ced5aa2..45e4fee4cf2 100644 --- a/e2e/pkg/tests/hostendpoints/auto_hostendpoints.go +++ b/e2e/pkg/tests/hostendpoints/auto_hostendpoints.go @@ -17,7 +17,9 @@ import ( "reflect" "time" - . "github.com/onsi/ginkgo/v2" + "github.com/onsi/ginkgo/v2" + + //nolint:staticcheck // Ignore ST1001: should not use dot imports . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -82,7 +84,7 @@ var _ = describe.CalicoDescribe(describe.WithTeam(describe.Core), }, } - BeforeEach(func() { + ginkgo.BeforeEach(func() { // The following code tries to get config information from k8s ConfigMap. // A framework clientset is needed to access k8s configmap but it will only be created in the context of BeforeEach or IT. // Current solution is to use BeforeEach because this function is not a test case. @@ -126,7 +128,7 @@ var _ = describe.CalicoDescribe(describe.WithTeam(describe.Core), } }) - AfterEach(func() { + ginkgo.AfterEach(func() { // We've updated the kubecontrollersconfiguration, so we need to restore it to its original state. if !reflect.DeepEqual(originalKCC.Spec.Controllers.Node.HostEndpoint, testKCC.Spec.Controllers.Node.HostEndpoint) { logrus.Info("AfterEach: auto host endpoints not previously enabled so disabling") @@ -141,12 +143,12 @@ var _ = describe.CalicoDescribe(describe.WithTeam(describe.Core), } }) - Context("with policies in place", func() { + ginkgo.Context("with policies in place", func() { var checker conncheck.ConnectionTester var server *conncheck.Server var cliHostNet *conncheck.Client - BeforeEach(func() { + ginkgo.BeforeEach(func() { checker = conncheck.NewConnectionTester(f) // Ensure the host networked server pod is on node 0, and client is on node 1. @@ -171,14 +173,14 @@ var _ = describe.CalicoDescribe(describe.WithTeam(describe.Core), checker.Deploy() }) - AfterEach(func() { + ginkgo.AfterEach(func() { checker.Stop() }) - Context("with ingress policy", func() { + ginkgo.Context("with ingress policy", func() { var denyIngressPolicy *v3.GlobalNetworkPolicy - BeforeEach(func() { + ginkgo.BeforeEach(func() { // Declare a policy that blocks ingress to the server // pod on port 9090. But don't apply the policy yet. denyIngressPolicy = &v3.GlobalNetworkPolicy{ @@ -212,12 +214,12 @@ var _ = describe.CalicoDescribe(describe.WithTeam(describe.Core), } }) - AfterEach(func() { + ginkgo.AfterEach(func() { err := cli.Delete(context.Background(), denyIngressPolicy) Expect(err).NotTo(HaveOccurred()) }) - It("should block one node 1 from entering node 0 on port 9090 with ingress policy", func() { + ginkgo.It("should block one node 1 from entering node 0 on port 9090 with ingress policy", func() { // Without any policy, the test pod should be able to hit the service on both ports. checker.ExpectSuccess(cliHostNet, server.ClusterIP().Port(port9090)) checker.ExpectSuccess(cliHostNet, server.ClusterIP().Port(port9091)) @@ -234,10 +236,10 @@ var _ = describe.CalicoDescribe(describe.WithTeam(describe.Core), }) }) - Context("with egress policy", func() { + ginkgo.Context("with egress policy", func() { var clientPod *conncheck.Client - BeforeEach(func() { + ginkgo.BeforeEach(func() { clientNode := getNodeHostname(nodes.Items[1]) clientPodOnNodeOne := func(pod *v1.Pod) { pod.Spec.NodeSelector = map[string]string{"kubernetes.io/hostname": clientNode} @@ -248,12 +250,12 @@ var _ = describe.CalicoDescribe(describe.WithTeam(describe.Core), checker.Deploy() }) - AfterEach(func() { + ginkgo.AfterEach(func() { err := cli.Delete(context.Background(), denyEgressPolicy) Expect(err).NotTo(HaveOccurred()) }) - It("should block one node 1 from reaching node 0 on port 9091 with egress policy", func() { + ginkgo.It("should block one node 1 from reaching node 0 on port 9091 with egress policy", func() { // Without any policy, the test pod should be able to hit the service on both ports. checker.ExpectSuccess(clientPod, server.ClusterIP().Port(port9090)) checker.ExpectSuccess(clientPod, server.ClusterIP().Port(port9091)) @@ -302,6 +304,11 @@ func updateHostEndpointConfig(client ctrlclient.Client, desiredKCC v3.KubeContro } // Check if the current configuration matches the desired configuration. + if currentKCC.Status.RunningConfig == nil || + currentKCC.Status.RunningConfig.Controllers.Node == nil || + currentKCC.Status.RunningConfig.Controllers.Node.HostEndpoint == nil { + return fmt.Errorf("kubecontrollersconfiguration status is not yet updated") + } if !reflect.DeepEqual(currentKCC.Status.RunningConfig.Controllers.Node.HostEndpoint, desiredKCC.Spec.Controllers.Node.HostEndpoint) { return fmt.Errorf("failed to toggle auto-creation of host endpoints") } @@ -324,22 +331,39 @@ func GetAutoHEPsEnabled(kcc v3.KubeControllersConfiguration) bool { } func WaitForAutoHEPs(client ctrlclient.Client, expect bool) { - Eventually(func() bool { + if expect { + logrus.Info("Waiting for the host endpoints to be created") + } else { logrus.Info("Waiting for the host endpoints to be deleted") - heps := getAllAutoHostEndpoints(client) - if expect { - return len(heps.Items) > 0 + } + EventuallyWithOffset(1, func() error { + heps := &v3.HostEndpointList{} + err := client.List(context.Background(), heps) + if err != nil { + return err + } + if expect && len(heps.Items) == 0 { + return fmt.Errorf("expected host endpoints to be present, but none found") } - return len(heps.Items) == 0 - }, 2*time.Minute, 2*time.Second).Should(BeTrue()) + if !expect && len(heps.Items) > 0 { + return fmt.Errorf("expected no host endpoints, but found %d", len(heps.Items)) + } + return nil + }, 1*time.Minute, 2*time.Second).Should(BeNil()) } // expectAutoHostEndpoint asserts that a specified node has the correct auto // host endpoint created for it. func expectAutoHostEndpoint(client ctrlclient.Client, nodeName string) { - Eventually(func() error { + EventuallyWithOffset(1, func() error { // Get the node and its auto host endpoint - hep := getAutoHostEndpoint(client, nodeName) + // This naming convention is hardcoded in kube-controllers. + hepName := nodeName + "-auto-hep" + hep := &v3.HostEndpoint{} + err := client.Get(context.Background(), types.NamespacedName{Name: hepName}, hep) + if err != nil { + return fmt.Errorf("error getting HEP %s: %v", hepName, err) + } // Auto host endpoints are all-interfaces host endpoints. if hep.Spec.InterfaceName != "*" { @@ -354,22 +378,3 @@ func expectAutoHostEndpoint(client ctrlclient.Client, nodeName string) { return nil }, 30*time.Second, 2*time.Second).Should(BeNil()) } - -// getAutoHostEndpoint gets a host endpoint for the specified node. -func getAutoHostEndpoint(client ctrlclient.Client, nodeName string) *v3.HostEndpoint { - // This naming convention is hardcoded in kube-controllers. - hepName := nodeName + "-auto-hep" - - hep := &v3.HostEndpoint{} - err := client.Get(context.Background(), types.NamespacedName{Name: hepName}, hep) - Expect(err).NotTo(HaveOccurred()) - return hep -} - -// getAllAutoHostEndpoints gets all host endpoints. -func getAllAutoHostEndpoints(client ctrlclient.Client) *v3.HostEndpointList { - heps := &v3.HostEndpointList{} - err := client.List(context.Background(), heps) - Expect(err).NotTo(HaveOccurred()) - return heps -} diff --git a/e2e/pkg/tests/hostendpoints/hostendpoints.go b/e2e/pkg/tests/hostendpoints/hostendpoints.go index 12dd7829166..b117b69862b 100644 --- a/e2e/pkg/tests/hostendpoints/hostendpoints.go +++ b/e2e/pkg/tests/hostendpoints/hostendpoints.go @@ -21,7 +21,9 @@ import ( "fmt" "strings" + //nolint:staticcheck // Ignore ST1001: should not use dot imports . "github.com/onsi/ginkgo/v2" + //nolint:staticcheck // Ignore ST1001: should not use dot imports . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -107,6 +109,7 @@ var _ = describe.CalicoDescribe( var checker conncheck.ConnectionTester var hepServer1 *conncheck.Server var client1 *conncheck.Client + var hep *v3.HostEndpoint BeforeEach(func() { var err error @@ -185,13 +188,13 @@ var _ = describe.CalicoDescribe( Expect(err).NotTo(HaveOccurred()) // Create our hostEndpoint - it is created with the same name as the GNP. - createHostEndpoint(cli, hepPolicyName, hepNodeName, hepPort1, pod.Status.HostIP) + hep = createHostEndpoint(cli, hepPolicyName, hepNodeName, hepPort1, pod.Status.HostIP) }) AfterEach(func() { checker.Stop() - // Clean up any policies we created. + // Clean up any policies / heps we created. err := cli.Delete(context.Background(), defaultAllow) if err != nil && !errors.IsNotFound(err) { Expect(err).NotTo(HaveOccurred()) @@ -200,6 +203,10 @@ var _ = describe.CalicoDescribe( if err != nil && !errors.IsNotFound(err) { Expect(err).NotTo(HaveOccurred()) } + err = cli.Delete(context.Background(), hep) + if err != nil && !errors.IsNotFound(err) { + Expect(err).NotTo(HaveOccurred()) + } }) It("should block all inbound connections by default", func() { @@ -356,7 +363,7 @@ var _ = describe.CalicoDescribe( }) It("should deny connections from specified source addresses in a doNotTrack deny policy (DoS mitigation) [ExternalNode]", func() { - extClient := externalnode.NewExternalNodeClient() + extClient := externalnode.NewClient() if extClient == nil { if describe.IncludesFocus("ExternalNode") { framework.Failf("External node client not available") @@ -480,7 +487,7 @@ var _ = describe.CalicoDescribe( }) }) -func createHostEndpoint(cli ctrlclient.Client, policyName string, nodeName string, port int, ip string) { +func createHostEndpoint(cli ctrlclient.Client, policyName string, nodeName string, port int, ip string) *v3.HostEndpoint { hep := &v3.HostEndpoint{ ObjectMeta: metav1.ObjectMeta{ Name: policyName, @@ -504,4 +511,5 @@ func createHostEndpoint(cli ctrlclient.Client, policyName string, nodeName strin err := cli.Create(context.Background(), hep) Expect(err).NotTo(HaveOccurred()) + return hep } diff --git a/e2e/pkg/tests/ipam/ipam_gc.go b/e2e/pkg/tests/ipam/ipam_gc.go new file mode 100644 index 00000000000..deec1b9f84c --- /dev/null +++ b/e2e/pkg/tests/ipam/ipam_gc.go @@ -0,0 +1,175 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. +// +// 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 ipam + +import ( + "context" + "fmt" + "time" + + //nolint:staticcheck // Ignore ST1001: should not use dot imports + . "github.com/onsi/ginkgo/v2" + //nolint:staticcheck // Ignore ST1001: should not use dot imports + . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/test/e2e/framework" + e2enode "k8s.io/kubernetes/test/e2e/framework/node" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/projectcalico/calico/e2e/pkg/describe" + "github.com/projectcalico/calico/e2e/pkg/utils" + "github.com/projectcalico/calico/e2e/pkg/utils/client" + "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" + "github.com/projectcalico/calico/libcalico-go/lib/clientv3" + cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" + "github.com/projectcalico/calico/libcalico-go/lib/ipam" +) + +var _ = describe.CalicoDescribe( + describe.WithTeam(describe.Core), + describe.WithFeature("IPAM"), + describe.WithSerial(), // Modifies global state, so run serially. + describe.WithCategory(describe.Networking), + "IPAM GC", + func() { + var cli ctrlclient.Client + var lcgc clientv3.Interface + + f := utils.NewDefaultFramework("calico-ipam-gc") + + BeforeEach(func() { + var err error + cli, err = client.New(f.ClientConfig()) + Expect(err).NotTo(HaveOccurred(), "failed to create controller-runtime client") + + // Ensure a clean starting environment before each test. + Expect(utils.CleanDatastore(cli)).ShouldNot(HaveOccurred(), "failed to clean datastore") + + // Verify that the cluster uses Calico IPAM. The Installation resource + // provides a definitive answer when available; IP pools serve as a + // fallback indicator for manifest-based installs. + Expect(utils.UsesCalicoIPAM(cli)).To(BeTrue(), "cluster does not use Calico IPAM; this test requires Calico IPAM") + + // Additionally verify IP pools exist as a sanity check. + poolList := &v3.IPPoolList{} + err = cli.List(context.Background(), poolList) + Expect(err).NotTo(HaveOccurred(), "failed to list IP pools") + Expect(poolList.Items).NotTo(BeEmpty(), "no IP pools found; cluster may not be using Calico IPAM") + + // Build a libcalico-go client for IPAM operations. The controller-runtime client + // does not support IPAM, so we talk directly via the libcalico-go backend. + cfg := apiconfig.NewCalicoAPIConfig() + cfg.Spec.DatastoreType = apiconfig.Kubernetes + cfg.Spec.Kubeconfig = framework.TestContext.KubeConfig + lcgc, err = clientv3.New(*cfg) + Expect(err).NotTo(HaveOccurred(), "failed to create libcalico-go client") + }) + + // Verifies that kube-controllers garbage-collects IP addresses allocated to + // non-existent pods. We create a fake IPAM allocation referencing a pod that + // doesn't exist, shorten the leak grace period so GC runs quickly, and then + // wait for the allocation to be released. + It("should garbage-collect leaked IP addresses", func() { + ctx := context.Background() + + // Shorten the leak grace period so GC detects the leak quickly. + By("Shortening the KubeControllersConfiguration leak grace period to 5s") + kcc := v3.NewKubeControllersConfiguration() + err := cli.Get(ctx, ctrlclient.ObjectKey{Name: "default"}, kcc) + Expect(err).NotTo(HaveOccurred(), "failed to get KubeControllersConfiguration") + logrus.Infof("KubeControllersConfiguration before modification: %+v", kcc.Spec) + + // Save original value for restoration. Initialize the Node controller + // config if it's nil (it's enabled by default but may not be explicit). + if kcc.Spec.Controllers.Node == nil { + kcc.Spec.Controllers.Node = &v3.NodeControllerConfig{} + } + origLeakGracePeriod := kcc.Spec.Controllers.Node.LeakGracePeriod + + kcc.Spec.Controllers.Node.LeakGracePeriod = &metav1.Duration{Duration: 5 * time.Second} + err = cli.Update(ctx, kcc) + Expect(err).NotTo(HaveOccurred(), "failed to update KubeControllersConfiguration leak grace period") + + // Restore the original leak grace period when the test completes. + DeferCleanup(func() { + err := cli.Get(ctx, ctrlclient.ObjectKey{Name: "default"}, kcc) + if err != nil { + framework.Logf("WARNING: failed to get KubeControllersConfiguration for restoration: %v", err) + return + } + kcc.Spec.Controllers.Node.LeakGracePeriod = origLeakGracePeriod + if err := cli.Update(ctx, kcc); err != nil { + framework.Logf("WARNING: failed to restore KubeControllersConfiguration leak grace period: %v", err) + } + }) + + // Pick a real node for the IPAM allocation. Using a real node makes the + // allocation look realistic to the GC code, which asserts that the + // allocation is on a known node. + By("Selecting a schedulable node for the fake allocation") + nodeCtx, nodeCancel := context.WithTimeout(ctx, 30*time.Second) + defer nodeCancel() + nodes, err := e2enode.GetBoundedReadySchedulableNodes(nodeCtx, f.ClientSet, 3) + Expect(err).NotTo(HaveOccurred(), "failed to list schedulable nodes") + Expect(nodes.Items).NotTo(BeEmpty(), "no schedulable nodes found") + n := nodes.Items[0] + + // Allocate an IP address referencing a non-existent pod. This simulates + // a leaked allocation that GC should clean up. + By("Allocating a fake IP address for a non-existent pod") + handle := "e2e-ipam-gc-handle" + args := ipam.AutoAssignArgs{ + Num4: 1, + HandleID: &handle, + Attrs: map[string]string{ipam.AttributePod: "fake-pod", ipam.AttributeNode: n.Name, ipam.AttributeNamespace: "default"}, + IntendedUse: v3.IPPoolAllowedUseWorkload, + Hostname: n.Name, + } + assignCtx, assignCancel := context.WithTimeout(ctx, 30*time.Second) + defer assignCancel() + v4s, _, err := lcgc.IPAM().AutoAssign(assignCtx, args) + Expect(err).NotTo(HaveOccurred(), "failed to auto-assign IP") + Expect(v4s.PartialFulfillmentError()).NotTo(HaveOccurred(), "partial fulfillment error on IP assignment") + Expect(v4s.IPs).To(HaveLen(1), "expected exactly one IP to be assigned") + logrus.Infof("Allocated fake IP: %v", v4s.IPs[0]) + + // Ensure cleanup in case GC doesn't release it. + DeferCleanup(func() { + releaseCtx, releaseCancel := context.WithTimeout(context.Background(), 30*time.Second) + defer releaseCancel() + _ = lcgc.IPAM().ReleaseByHandle(releaseCtx, handle) + }) + + // Wait for kube-controllers IPAM GC to detect the leak and release the allocation. + By("Waiting for the leaked IP to be garbage-collected") + Eventually(func() error { + checkCtx, checkCancel := context.WithTimeout(ctx, 30*time.Second) + defer checkCancel() + ips, err := lcgc.IPAM().IPsByHandle(checkCtx, handle) + if err != nil { + if _, ok := err.(cerrors.ErrorResourceDoesNotExist); ok { + return nil // IP has been released + } + return fmt.Errorf("unexpected error checking handle: %w", err) + } + if len(ips) != 0 { + return fmt.Errorf("IP %v still allocated, waiting for GC", ips) + } + return nil + }, 2*time.Minute, 5*time.Second).Should(Succeed(), "timed out waiting for leaked IP to be garbage-collected") + }) + }) diff --git a/e2e/pkg/tests/networking/hostports.go b/e2e/pkg/tests/networking/hostports.go index 089a3bbddd5..bb40dc00be9 100644 --- a/e2e/pkg/tests/networking/hostports.go +++ b/e2e/pkg/tests/networking/hostports.go @@ -16,7 +16,7 @@ package networking import ( "fmt" - . "github.com/onsi/ginkgo/v2" + "github.com/onsi/ginkgo/v2" v1 "k8s.io/api/core/v1" "k8s.io/kubernetes/test/e2e/framework" @@ -39,7 +39,7 @@ var _ = describe.CalicoDescribe( var server *conncheck.Server var client1 *conncheck.Client - BeforeEach(func() { + ginkgo.BeforeEach(func() { // Define a function to set the HostPort on the server pod. hostPortSetter := func(pod *v1.Pod) { pod.Spec.Containers[0].Ports = []v1.ContainerPort{ @@ -59,12 +59,12 @@ var _ = describe.CalicoDescribe( checker.Deploy() }) - AfterEach(func() { + ginkgo.AfterEach(func() { checker.Stop() }) text := fmt.Sprintf("with host port resources active on :%d", hostport) - Context(text, func() { + ginkgo.Context(text, func() { framework.ConformanceIt("should support Pod HostPorts", func() { checker.ExpectSuccess(client1, server.HostPorts(hostport)...) // Expect success on the correct host port. checker.ExpectFailure(client1, server.HostPorts(hostport+1)...) // Expect failure on an incorrect host port. diff --git a/e2e/pkg/tests/networking/ipip.go b/e2e/pkg/tests/networking/ipip.go new file mode 100644 index 00000000000..b449831fb45 --- /dev/null +++ b/e2e/pkg/tests/networking/ipip.go @@ -0,0 +1,254 @@ +// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// +// 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 networking + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/onsi/ginkgo/v2" + + //nolint:staticcheck // Ignore ST1001: should not use dot imports + . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + v1 "github.com/tigera/operator/api/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/kubernetes/test/e2e/framework" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/projectcalico/calico/e2e/pkg/describe" + "github.com/projectcalico/calico/e2e/pkg/utils" + "github.com/projectcalico/calico/e2e/pkg/utils/client" + "github.com/projectcalico/calico/e2e/pkg/utils/conncheck" + "github.com/projectcalico/calico/e2e/pkg/utils/windows" +) + +var _ = describe.CalicoDescribe( + describe.WithTeam(describe.Core), + describe.WithFeature("IPIP"), + describe.WithCategory(describe.Networking), + describe.RequiresNoEncap(), + "IP-in-IP tests", + func() { + // Define variables common across all tests. + var err error + var cli ctrlclient.Client + var checker conncheck.ConnectionTester + var server1 *conncheck.Server + var client1 *conncheck.Client + var poolName string + + // Create a new framework for the tests. + f := utils.NewDefaultFramework("ipip") + + ginkgo.BeforeEach(func() { + if windows.ClusterIsWindows() { + // These tests exec commands in pods to check routes, which has not been implemented + // for Windows yet. + framework.Failf("IPIP tests are not implemented on Windows") + } + + // Create a connection tester for the test. + checker = conncheck.NewConnectionTester(f) + + cli, err = client.New(f.ClientConfig()) + Expect(err).NotTo(HaveOccurred()) + + // Ensure a clean starting environment before each test. + Expect(utils.CleanDatastore(cli)).ShouldNot(HaveOccurred()) + + // We need a minimum of two nodes for BGP peering tests. + utils.RequireNodeCount(f, 2) + + // Make sure the cluster is in BGP mode by querying the Installation resource. The tests in this file + // all require BGP, and all require Calico be installed by the operator. + installation := &v1.Installation{} + err = cli.Get(context.Background(), ctrlclient.ObjectKey{Name: "default"}, installation) + Expect(err).NotTo(HaveOccurred(), "Error querying Installation resource") + Expect(installation.Spec.CalicoNetwork).NotTo(BeNil(), "CalicoNetwork is not configured in the Installation") + Expect(installation.Spec.CalicoNetwork.BGP).NotTo(BeNil(), "BGP is not enabled in the cluster") + Expect(*installation.Spec.CalicoNetwork.BGP).To(Equal(v1.BGPEnabled), "BGP is not enabled in the cluster") + + // Create an IP pool for the test. + poolName = utils.GenerateRandomName("ipip-pool") + pool := v3.NewIPPool() + pool.Name = poolName + pool.Spec.CIDR = "203.0.113.0/24" + pool.Spec.NATOutgoing = true + pool.Spec.BlockSize = 28 + pool.Spec.DisableBGPExport = false + pool.Spec.IPIPMode = v3.IPIPModeAlways + err = cli.Create(context.Background(), pool) + Expect(err).NotTo(HaveOccurred(), "Error creating IP pool") + ginkgo.DeferCleanup(func() { + err = cli.Delete(context.Background(), pool) + Expect(err).NotTo(HaveOccurred(), "Error deleting IP pool") + }) + + // Before each test, perform the following steps: + // - Create a server pod and corresponding service in the main namespace for the test. + // - Create a client pod and assert that it can connect to the service. + ginkgo.By(fmt.Sprintf("Creating server pod in namespace %s", f.Namespace.Name)) + + // Use customizers to ensure pods use the test IP pool and avoid landing on the same node. + customizer := conncheck.CombineCustomizers( + conncheck.UseV4IPPool(pool.Name), + conncheck.AvoidEachOther, + ) + server1 = conncheck.NewServer( + "server", + f.Namespace, + conncheck.WithServerLabels(map[string]string{"role": "server"}), + conncheck.WithServerPodCustomizer(customizer), + ) + client1 = conncheck.NewClient( + "client", + f.Namespace, + conncheck.WithClientCustomizer(customizer), + ) + checker.AddServer(server1) + checker.AddClient(client1) + checker.Deploy() + + // Verify initial connectivity. + checker.ResetExpectations() + checker.ExpectSuccess(client1, server1.ClusterIPs()...) + checker.Execute() + }) + + ginkgo.AfterEach(func() { + checker.Stop() + }) + + ginkgo.It("should support toggling IPIP on and off", func() { + // Toggle IPIP mode off and on again, verifying connectivity after each step. + ginkgo.By("Disabling IPIP mode on the IP pool") + pool := &v3.IPPool{} + err = cli.Get(context.Background(), ctrlclient.ObjectKey{Name: poolName}, pool) + Expect(err).NotTo(HaveOccurred(), "Error querying IP pool") + pool.Spec.IPIPMode = v3.IPIPModeNever + err = cli.Update(context.Background(), pool) + Expect(err).NotTo(HaveOccurred(), "Error updating IP pool to disable IPIP") + + // Verify connectivity still works. + checker.ResetExpectations() + checker.ExpectSuccess(client1, server1.ClusterIPs()...) + checker.Execute() + + // Eventually, the routes for the test's IP pool should no longer use tunl0. + ginkgo.By("Verifying that node routes no longer use tunl0") + Eventually(func() error { + routes := getNodeRoutes(cli, "203.0.113") + if len(routes) == 0 { + return fmt.Errorf("no routes found for test IP pool") + } + for _, r := range routes { + if strings.Contains(r, "tunl0") { + return fmt.Errorf("route for test IP pool is still using tunl0: %s", r) + } + } + return nil + }, 10*time.Second, 1*time.Second).Should(Succeed(), "Routes for the test IP pool are still using tunl0") + + // CrossSubnet. + ginkgo.By("Setting IPIP mode to CrossSubnet on the IP pool") + err = cli.Get(context.Background(), ctrlclient.ObjectKey{Name: poolName}, pool) + Expect(err).NotTo(HaveOccurred(), "Error querying IP pool") + pool.Spec.IPIPMode = v3.IPIPModeCrossSubnet + err = cli.Update(context.Background(), pool) + Expect(err).NotTo(HaveOccurred(), "Error updating IP pool to set IPIP to CrossSubnet") + + // Verify connectivity still works. + checker.ResetExpectations() + checker.ExpectSuccess(client1, server1.ClusterIPs()...) + checker.Execute() + + // Always. + ginkgo.By("Setting IPIP mode to Always on the IP pool") + err = cli.Get(context.Background(), ctrlclient.ObjectKey{Name: poolName}, pool) + Expect(err).NotTo(HaveOccurred(), "Error querying IP pool") + pool.Spec.IPIPMode = v3.IPIPModeAlways + err = cli.Update(context.Background(), pool) + Expect(err).NotTo(HaveOccurred(), "Error updating IP pool to set IPIP to Always") + + // Routes should now be using tunl0 again. + ginkgo.By("Verifying that node routes are using tunl0") + Eventually(func() error { + routes := getNodeRoutes(cli, "203.0.113") + if len(routes) == 0 { + return fmt.Errorf("no routes found for test IP pool") + } + for _, r := range routes { + if strings.Contains(r, "tunl0") { + // Found a route using tunl0, as expected. + return nil + } + } + return fmt.Errorf("no routes for test IP pool are using tunl0: %v", routes) + }, 10*time.Second, 1*time.Second).Should(Succeed(), "Routes for the test IP pool are not using tunl0") + + // Verify connectivity still works. + checker.ResetExpectations() + checker.ExpectSuccess(client1, server1.ClusterIPs()...) + checker.Execute() + }) + + ginkgo.It("should assign an IPIP address to the tunl0 interface", func() { + ginkgo.By("Verifying that each node has a tunl0 interface with an IPIP address") + pods := corev1.PodList{} + err := cli.List(context.Background(), &pods, ctrlclient.MatchingLabels{"k8s-app": "calico-node"}) + Expect(err).NotTo(HaveOccurred(), "Error querying calico/node pods") + Expect(pods.Items).NotTo(BeEmpty(), "No calico/node pods found") + + // Go through each pod and assert that tunl0 has an IP address assigned and that it matches the + // the tunnel IP address assigned to the node. + for _, p := range pods.Items { + // Query the node hosting this pod to get its IP address. + node := corev1.Node{} + err = cli.Get(context.Background(), ctrlclient.ObjectKey{Name: p.Spec.NodeName}, &node) + Expect(err).NotTo(HaveOccurred(), "Error querying node %s", p.Spec.NodeName) + expectedIP, ok := node.Annotations["projectcalico.org/IPv4IPIPTunnelAddr"] + Expect(ok).To(BeTrue(), "Node %s does not have an IPv4Address annotation", node.Name) + + out, err := conncheck.ExecInPod(&p, "sh", "-c", "ip addr show tunl0") + Expect(err).NotTo(HaveOccurred(), "Error querying tunl0 interface in pod %s", p.Name) + Expect(out).To(ContainSubstring(expectedIP), "tunl0 interface in pod %s does not have the expected IPIP address", p.Name) + } + }) + }) + +// getNodeRoutes execs into a calico/node pod and returns the output of "ip route show", +// filtered to only include lines that contain the specified match string. +func getNodeRoutes(cli ctrlclient.Client, match string) []string { + // Find a calico/node pod to exec into. + pods := corev1.PodList{} + err := cli.List(context.Background(), &pods, ctrlclient.MatchingLabels{"k8s-app": "calico-node"}) + Expect(err).NotTo(HaveOccurred(), "Error querying calico/node pods") + Expect(pods.Items).NotTo(BeEmpty(), "No calico/node pods found") + p := &pods.Items[0] + + out, err := conncheck.ExecInPod(p, "sh", "-c", "ip route show") + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Error querying routes from pod %s", p.Name) + + matches := []string{} + for s := range strings.SplitSeq(out, "\n") { + if strings.Contains(s, match) { + matches = append(matches, s) + } + } + return matches +} diff --git a/e2e/pkg/tests/networking/maglev.go b/e2e/pkg/tests/networking/maglev.go new file mode 100644 index 00000000000..db86c2248b3 --- /dev/null +++ b/e2e/pkg/tests/networking/maglev.go @@ -0,0 +1,640 @@ +/* +Copyright (c) 2025 Tigera, Inc. All rights reserved. + +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 networking + +import ( + "context" + "encoding/json" + "fmt" + "net" + "regexp" + "strings" + "time" + + //nolint:staticcheck // Ignore ST1001: should not use dot imports + . "github.com/onsi/ginkgo/v2" + //nolint:staticcheck // Ignore ST1001: should not use dot imports + . "github.com/onsi/gomega" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/test/e2e/framework" + e2enode "k8s.io/kubernetes/test/e2e/framework/node" + k8snet "k8s.io/utils/net" + "k8s.io/utils/ptr" + + "github.com/projectcalico/calico/e2e/pkg/describe" + "github.com/projectcalico/calico/e2e/pkg/utils" + "github.com/projectcalico/calico/e2e/pkg/utils/conncheck" + "github.com/projectcalico/calico/e2e/pkg/utils/externalnode" + "github.com/projectcalico/calico/e2e/pkg/utils/images" +) + +// DESCRIPTION: This test verifies the operation of Maglev load balancing algorithm. + +// TODO: +// DOCS_URL: +// PRECONDITIONS: Enterprise v3.xx or later; OSS v3.xx or later + +var _ = describe.CalicoDescribe( + describe.WithTeam(describe.Core), + describe.WithFeature("Maglev"), + describe.WithCategory(describe.Networking), + describe.WithExternalNode(), + describe.WithDataplane(describe.BPF), + describe.WithAWS(), + "Maglev load balancing tests", + func() { + var ( + f = utils.NewDefaultFramework("calico-maglev") + maglevTests *MaglevTests + nodeNames []string + extNode *externalnode.Client + ) + + BeforeEach(func() { + // Initialize external node for testing + extNode = externalnode.NewClient() + if extNode == nil { + Skip("External node not available - required for Maglev testing") + } + + // Get available nodes for pod distribution + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + nodes, err := e2enode.GetBoundedReadySchedulableNodes(ctx, f.ClientSet, 10) // Get up to 10 nodes + Expect(err).ShouldNot(HaveOccurred()) + if len(nodes.Items) == 0 { + Fail("No schedulable nodes exist, can't continue test.") + } + + nodesInfo := utils.GetNodesInfo(f, nodes, false) + nodeNames = nodesInfo.GetNames() + nodeIPv4s := nodesInfo.GetIPv4s() + nodeIPv6s := nodesInfo.GetIPv6s() + Expect(len(nodeNames)).Should(BeNumerically(">", 0)) + framework.Logf("Found %d nodes for testing: %v", len(nodeNames), nodeNames) + + // Initialize the test helper + maglevTests = NewMaglevTests(f) + + // Populate the node name to IP mappings for routing (using both IPv4 and IPv6 addresses) + maglevTests.nodeNameToIPv4 = make(map[string]string) + maglevTests.nodeNameToIPv6 = make(map[string]string) + for i, nodeName := range nodeNames { + if i < len(nodeIPv4s) && nodeIPv4s[i] != "" { + maglevTests.nodeNameToIPv4[nodeName] = nodeIPv4s[i] + } + if i < len(nodeIPv6s) && nodeIPv6s[i] != "" { + maglevTests.nodeNameToIPv6[nodeName] = nodeIPv6s[i] + } + } + framework.Logf("Node name to IPv4 mapping: %v", maglevTests.nodeNameToIPv4) + framework.Logf("Node name to IPv6 mapping: %v", maglevTests.nodeNameToIPv6) + }) + + makeMaglevTest := func(isIPv6 bool) func() { + + ipVer := "IPv4" + if isIPv6 { + ipVer = "IPv6" + } + return func() { + if isIPv6 { + if len(maglevTests.nodeNameToIPv6) == 0 { + Skip("IPv6 is not configured, skipping IPv6 Maglev test") + } + } else { + if len(maglevTests.nodeNameToIPv4) == 0 { + Skip("IPv4 is not configured, skipping IPv4 Maglev test") + } + } + // Ensure we have at least 3 nodes for the test + Expect(len(nodeNames)).Should(BeNumerically(">=", 3), "Need at least 3 nodes for this test") + + // Deploy 20 backend pods on node 1 (first node) + maglevTests.DeployBackendPods(20, []string{nodeNames[0]}) + // Deploy service "netexec" backed by the 20 pods + maglevTests.DeployService() + // Add route to external node where packets to service cluster IP go to node 2 + maglevTests.SetupExternalNodeClientRoutingToSpecificNode(extNode, nodeNames[1]) // node 2 (second node) + + // Test random backend selection without Maglev annotation + maglevTests.TestRandomBackendSelection(extNode, isIPv6) + + // Enable Maglev on the same service by adding annotation + maglevTests.EnableMaglev() + + // Set a fixed source port for consistent hashing tests + maglevTests.SetSourcePort(12345) + + // Test Maglev consistent hashing with the annotation using first source port + backendViaNode2Port1 := maglevTests.TestMaglevConsistentHashing(extNode, isIPv6) // test with port 12345 + + // Test Maglev consistent hashing with a different source port to verify different flows can hash to different backends + maglevTests.SetSourcePort(23456) // Change source port + backendViaNode2Port2 := maglevTests.TestMaglevConsistentHashing(extNode, isIPv6) // test with port 23456 + framework.Logf("Maglev hashing with different source ports: %s port 12345->%s, port 23456->%s", + ipVer, backendViaNode2Port1, backendViaNode2Port2) + + // Reset source port for next tests + maglevTests.SetSourcePort(12345) + + // Then: Remove the routes from external node to service cluster IPs via node 2 + maglevTests.RemoveExternalNodeClientRoutes(extNode, nodeNames[1]) + + // Add route to external node where packets to service cluster IP go to node 3 + maglevTests.SetupExternalNodeClientRoutingToSpecificNode(extNode, nodeNames[2]) // node 3 (third node) + + // Test Maglev consistent hashing again via node 3 with first source port + backendViaNode3Port1 := maglevTests.TestMaglevConsistentHashing(extNode, isIPv6) // test via node 3 with port 12345 + + // Test Maglev consistent hashing via node 3 with a different source port + maglevTests.SetSourcePort(23456) // Change source port + backendViaNode3Port2 := maglevTests.TestMaglevConsistentHashing(extNode, isIPv6) // test via node 3 with port 23456 + framework.Logf("Maglev hashing via node 3 with different source ports: %s port 12345->%s, port 23456->%s", + ipVer, backendViaNode3Port1, backendViaNode3Port2) + + // Reset source port + maglevTests.SetSourcePort(12345) + + // Assert that the backend selected via node 3 is the same as that selected via node 2 (for same source port) + Expect(backendViaNode3Port1).Should(Equal(backendViaNode2Port1), + fmt.Sprintf("Expected backend selection to be consistent across nodes: node 2 selected %s, node 3 selected %s", + backendViaNode2Port1, backendViaNode3Port1)) + + // Also verify that the second source port routes to the same backend across nodes + Expect(backendViaNode3Port2).Should(Equal(backendViaNode2Port2), + fmt.Sprintf("Expected backend selection (port 23456) to be consistent across nodes: node 2 selected %s, node 3 selected %s", + backendViaNode2Port2, backendViaNode3Port2)) + + framework.Logf("Maglev cross-node consistency verified for both source ports: %s port 12345->%s, port 23456->%s", + ipVer, backendViaNode2Port1, backendViaNode2Port2) + } + } + + It("test service ip load balancing behavior before and after maglev annotation (IPv4)", makeMaglevTest(false)) + It("test service ip load balancing behavior before and after maglev annotation (IPv6)", makeMaglevTest(true)) + + }) + +type MaglevTests struct { + f *framework.Framework + serviceClusterIPv4 string + serviceClusterIPv6 string + loadBalancerService *v1.Service + maglevConfig *MaglevConfig + nodeNameToIPv4 map[string]string + nodeNameToIPv6 map[string]string + connTester conncheck.ConnectionTester +} + +type MaglevConfig struct { + ServiceName string + ServicePort int32 + SourcePort int // Source port for requests to test consistent hashing + NumberOfRequests int // Number of requests to send to the cluster IP for testing + IntervalSeconds int // Interval between requests in seconds +} + +func NewMaglevTests(f *framework.Framework) *MaglevTests { + return &MaglevTests{ + f: f, + maglevConfig: &MaglevConfig{ + ServiceName: "netexec", + ServicePort: 8080, + NumberOfRequests: 10, // Default number of requests for testing + IntervalSeconds: 1, // Default interval between requests + }, + nodeNameToIPv4: make(map[string]string), + nodeNameToIPv6: make(map[string]string), + } +} + +// IPFamilies returns a list of families compatible with this cluster's configuration - for use in service creation. +func (m *MaglevTests) IPFamilies() []v1.IPFamily { + families := make([]v1.IPFamily, 0) + if len(m.nodeNameToIPv4) > 0 { + framework.Logf("Found IPv4 node IPs; Adding IPv4Protocol to IPFamily list") + families = append(families, v1.IPv4Protocol) + } + + if len(m.nodeNameToIPv6) > 0 { + framework.Logf("Found IPv6 node IPs; Adding IPv6Protocol to IPFamily list") + families = append(families, v1.IPv6Protocol) + } + + return families +} + +// parseBackendResponse parses the JSON response from netexec and returns the backend pod name +func (m *MaglevTests) parseBackendResponse(output string) (string, error) { + // NetexecResponse represents the JSON response from the netexec service + type NetexecResponse struct { + Output string `json:"output"` + } + + var response NetexecResponse + if err := json.Unmarshal([]byte(output), &response); err != nil { + return "", fmt.Errorf("failed to parse JSON response: %w", err) + } + + // Remove the trailing newline from the output + backendName := strings.TrimSpace(response.Output) + + // Verify this matches our expected backend pod naming pattern (backend-pod-0 to backend-pod-19) + backendPodPattern := regexp.MustCompile(`^backend-pod-\d+$`) + if !backendPodPattern.MatchString(backendName) { + return "", fmt.Errorf("response '%s' does not match expected backend pod naming pattern 'backend-pod-N'", backendName) + } + + return backendName, nil +} + +func (m *MaglevTests) DeployBackendPods(numPods int, nodes []string) { + By(fmt.Sprintf("deploying %d backend pods for load balancing across %d nodes using conncheck package", numPods, len(nodes))) + + // Create connection tester + m.connTester = conncheck.NewConnectionTester(m.f) + + // Create individual servers for each backend pod + for i := 1; i <= numPods; i++ { + podName := fmt.Sprintf("backend-pod-%d", i) + // Select node using round-robin: (i-1) % len(nodes) to distribute pods evenly + selectedNode := nodes[(i-1)%len(nodes)] + + // Create server with conncheck, disable automatic service creation + server := conncheck.NewServer(podName, m.f.Namespace, + conncheck.WithServerLabels(map[string]string{ + "app": "netexec", + }), + conncheck.WithPorts(8080), + conncheck.WithAutoCreateService(false), // Don't create individual services + conncheck.WithServerPodCustomizer(func(pod *v1.Pod) { + // Schedule pod on specific node + pod.Spec.NodeName = selectedNode + + // Update container to use netexec image and configuration + pod.Spec.Containers[0].Image = images.Agnhost + pod.Spec.Containers[0].Args = []string{"netexec"} + + // Set security context + pod.Spec.SecurityContext = &v1.PodSecurityContext{ + RunAsNonRoot: ptr.To(true), + RunAsUser: ptr.To(int64(1000)), + SeccompProfile: &v1.SeccompProfile{ + Type: v1.SeccompProfileTypeRuntimeDefault, + }, + } + pod.Spec.Containers[0].SecurityContext = &v1.SecurityContext{ + AllowPrivilegeEscalation: ptr.To(false), + RunAsNonRoot: ptr.To(true), + RunAsUser: ptr.To(int64(1000)), + Capabilities: &v1.Capabilities{ + Drop: []v1.Capability{"ALL"}, + }, + } + }), + ) + + // Add server to connection tester + m.connTester.AddServer(server) + framework.Logf("Added server %s to be deployed on node %s", podName, selectedNode) + } + + // Deploy all servers at once + m.connTester.Deploy() + + // Add cleanup using connTester.Stop() + DeferCleanup(func() { + if m.connTester != nil { + m.connTester.Stop() + } + }) + + framework.Logf("All %d backend pods are now ready using conncheck", numPods) +} + +func (m *MaglevTests) DeployService() { + By("deploying service") + + service := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: m.maglevConfig.ServiceName, + Namespace: m.f.Namespace.Name, + }, + Spec: v1.ServiceSpec{ + Type: v1.ServiceTypeClusterIP, + IPFamilies: m.IPFamilies(), + IPFamilyPolicy: ptr.To(v1.IPFamilyPolicyPreferDualStack), + Selector: map[string]string{ + "app": "netexec", + }, + Ports: []v1.ServicePort{ + { + Port: m.maglevConfig.ServicePort, + Protocol: v1.ProtocolTCP, + }, + }, + }, + } + + createdService, err := m.f.ClientSet.CoreV1().Services(m.f.Namespace.Name).Create(context.TODO(), service, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + m.loadBalancerService = createdService + + // Store both IPv4 and IPv6 cluster IPs + if len(createdService.Spec.ClusterIPs) > 0 { + for _, clusterIP := range createdService.Spec.ClusterIPs { + if k8snet.IsIPv6String(clusterIP) { + m.serviceClusterIPv6 = clusterIP + } else { + m.serviceClusterIPv4 = clusterIP + } + } + } else { + // Fallback to single ClusterIP field + if k8snet.IsIPv6String(createdService.Spec.ClusterIP) { + m.serviceClusterIPv6 = createdService.Spec.ClusterIP + } else { + m.serviceClusterIPv4 = createdService.Spec.ClusterIP + } + } + + DeferCleanup(func() { + _ = m.f.ClientSet.CoreV1().Services(m.f.Namespace.Name).Delete(context.TODO(), createdService.Name, metav1.DeleteOptions{}) + }) + + framework.Logf("Service cluster IPv4: %s", m.serviceClusterIPv4) + framework.Logf("Service cluster IPv6: %s", m.serviceClusterIPv6) + + // Wait for service endpoints to be ready + By("waiting for service endpoints to be ready") + Eventually(func() bool { + endpoints, err := m.f.ClientSet.CoreV1().Endpoints(m.f.Namespace.Name).Get(context.TODO(), m.maglevConfig.ServiceName, metav1.GetOptions{}) + if err != nil { + framework.Logf("Failed to get endpoints for service %s: %v", m.maglevConfig.ServiceName, err) + return false + } + + // Check if we have any ready endpoints + totalEndpoints := 0 + for _, subset := range endpoints.Subsets { + totalEndpoints += len(subset.Addresses) + } + + if totalEndpoints == 0 { + framework.Logf("Service %s has no ready endpoints yet", m.maglevConfig.ServiceName) + return false + } + + framework.Logf("Service %s has %d ready endpoints", m.maglevConfig.ServiceName, totalEndpoints) + return true + }, 60*time.Second, 2*time.Second).Should(BeTrue(), "Service should have ready endpoints") +} + +func (m *MaglevTests) EnableMaglev() { + By("enabling Maglev on the service with annotation") + + // Get the current service + service, err := m.f.ClientSet.CoreV1().Services(m.f.Namespace.Name).Get(context.TODO(), m.maglevConfig.ServiceName, metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) + + // Add the Maglev annotation + if service.Annotations == nil { + service.Annotations = make(map[string]string) + } + service.Annotations["lb.projectcalico.org/external-traffic-strategy"] = "maglev" + + // Update the service + _, err = m.f.ClientSet.CoreV1().Services(m.f.Namespace.Name).Update(context.TODO(), service, metav1.UpdateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + framework.Logf("Added Maglev annotation to service %s", m.maglevConfig.ServiceName) + + // Wait for the annotation to be processed by verifying it's present + By("waiting for Maglev annotation to be applied and processed") + Eventually(func() bool { + // Verify the annotation is still present and has been processed + updatedService, err := m.f.ClientSet.CoreV1().Services(m.f.Namespace.Name).Get(context.TODO(), m.maglevConfig.ServiceName, metav1.GetOptions{}) + if err != nil { + framework.Logf("Failed to get service while checking annotation: %v", err) + return false + } + + // Check if the Maglev annotation is present + if updatedService.Annotations == nil { + return false + } + + annotationValue, exists := updatedService.Annotations["lb.projectcalico.org/external-traffic-strategy"] + if !exists || annotationValue != "maglev" { + framework.Logf("Maglev annotation not found or incorrect value: %s", annotationValue) + return false + } + + framework.Logf("Maglev annotation confirmed on service") + return true + }, 10*time.Second, 1*time.Second).Should(BeTrue(), "Maglev annotation should be applied and processed") +} + +func (m *MaglevTests) SetupExternalNodeClientRoutingToSpecificNode(extNode *externalnode.Client, targetNodeName string) { + By(fmt.Sprintf("setting up routing from external node to service cluster IPs via node %s", targetNodeName)) + + // Set up IPv4 routing if IPv4 cluster IP exists + if m.serviceClusterIPv4 != "" { + targetNodeIPv4, exists := m.nodeNameToIPv4[targetNodeName] + if exists && targetNodeIPv4 != "" { + // Add route from external node to the service cluster IPv4 via the specified Kubernetes node + routeCmd := fmt.Sprintf("sudo ip route add %s/32 via %s", m.serviceClusterIPv4, targetNodeIPv4) + _, err := extNode.Exec("sh", "-c", routeCmd) + Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Failed to add IPv4 route to %s via %s", m.serviceClusterIPv4, targetNodeIPv4)) + + // Add cleanup to remove the IPv4 route + DeferCleanup(func() { + deleteRouteCmd := fmt.Sprintf("sudo ip route del %s/32 via %s", m.serviceClusterIPv4, targetNodeIPv4) + _, err := extNode.Exec("sh", "-c", deleteRouteCmd) + if err != nil { + // Route may have already been removed, log but don't fail + framework.Logf("Note: IPv4 route to %s via %s may have already been removed: %v", m.serviceClusterIPv4, targetNodeIPv4, err) + } + }) + + framework.Logf("Added IPv4 route to service cluster IP %s via node %s (IP: %s)", m.serviceClusterIPv4, targetNodeName, targetNodeIPv4) + } else { + framework.Logf("Warning: No IPv4 address found for node %s, skipping IPv4 route setup", targetNodeName) + } + } + + // Set up IPv6 routing if IPv6 cluster IP exists + if m.serviceClusterIPv6 != "" { + targetNodeIPv6, exists := m.nodeNameToIPv6[targetNodeName] + if exists && targetNodeIPv6 != "" { + // Add route from external node to the service cluster IPv6 via the specified Kubernetes node + routeCmd := fmt.Sprintf("sudo ip -6 route add %s/128 via %s", m.serviceClusterIPv6, targetNodeIPv6) + _, err := extNode.Exec("sh", "-c", routeCmd) + Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Failed to add IPv6 route to %s via %s", m.serviceClusterIPv6, targetNodeIPv6)) + + // Add cleanup to remove the IPv6 route + DeferCleanup(func() { + deleteRouteCmd := fmt.Sprintf("sudo ip -6 route del %s/128 via %s", m.serviceClusterIPv6, targetNodeIPv6) + _, err := extNode.Exec("sh", "-c", deleteRouteCmd) + if err != nil { + // Route may have already been removed, log but don't fail + framework.Logf("Note: IPv6 route to %s via %s may have already been removed: %v", m.serviceClusterIPv6, targetNodeIPv6, err) + } + }) + + framework.Logf("Added IPv6 route to service cluster IP %s via node %s (IP: %s)", m.serviceClusterIPv6, targetNodeName, targetNodeIPv6) + } else { + framework.Logf("Warning: No IPv6 address found for node %s, skipping IPv6 route setup", targetNodeName) + } + } +} + +// RemoveExternalNodeClientRoutes removes the routes to service cluster IPs from the external node +func (m *MaglevTests) RemoveExternalNodeClientRoutes(extNode *externalnode.Client, targetNodeName string) { + By(fmt.Sprintf("removing routes from external node to service cluster IPs via node %s", targetNodeName)) + + // Remove IPv4 routing if IPv4 cluster IP exists + if m.serviceClusterIPv4 != "" { + targetNodeIPv4, exists := m.nodeNameToIPv4[targetNodeName] + if exists && targetNodeIPv4 != "" { + // Remove route from external node to the service cluster IPv4 + deleteRouteCmd := fmt.Sprintf("sudo ip route del %s/32 via %s", m.serviceClusterIPv4, targetNodeIPv4) + _, err := extNode.Exec("sh", "-c", deleteRouteCmd) + if err != nil { + framework.Logf("Warning: Failed to remove IPv4 route (may not exist): %v", err) + } else { + framework.Logf("Removed IPv4 route to service cluster IP %s via node %s (IP: %s)", m.serviceClusterIPv4, targetNodeName, targetNodeIPv4) + } + } else { + framework.Logf("Warning: No IPv4 address found for node %s, skipping IPv4 route removal", targetNodeName) + } + } + + // Remove IPv6 routing if IPv6 cluster IP exists + if m.serviceClusterIPv6 != "" { + targetNodeIPv6, exists := m.nodeNameToIPv6[targetNodeName] + if exists && targetNodeIPv6 != "" { + // Remove route from external node to the service cluster IPv6 + deleteRouteCmd := fmt.Sprintf("sudo ip -6 route del %s/128 via %s", m.serviceClusterIPv6, targetNodeIPv6) + _, err := extNode.Exec("sh", "-c", deleteRouteCmd) + if err != nil { + framework.Logf("Warning: Failed to remove IPv6 route (may not exist): %v", err) + } else { + framework.Logf("Removed IPv6 route to service cluster IP %s via node %s (IP: %s)", m.serviceClusterIPv6, targetNodeName, targetNodeIPv6) + } + } else { + framework.Logf("Warning: No IPv6 address found for node %s, skipping IPv6 route removal", targetNodeName) + } + } +} + +// sendRequestsAndGatherStats sends the configured number of requests to the service and returns backend response counts +func (m *MaglevTests) sendRequestsAndGatherStats(extNode *externalnode.Client, useIPv6 bool, testDescription string) map[string]int { + ipVersion := "IPv4" + clusterIP := m.serviceClusterIPv4 + if useIPv6 { + ipVersion = "IPv6" + clusterIP = m.serviceClusterIPv6 + if clusterIP == "" { + Fail("IPv6 cluster IP is not available - dual-stack service creation failed") + } + } + + By(fmt.Sprintf("testing %s from external node using %s (source port: %d)", testDescription, ipVersion, m.maglevConfig.SourcePort)) + + // Track which backends receive requests and their request counts + uniqueBackends := make(map[string]int) + totalRequests := m.maglevConfig.NumberOfRequests + + for i := range totalRequests { + // Use external node to run rapidclient with netexec endpoint to get hostname + // Use configured source port for consistent testing + var cmd string + ep := net.JoinHostPort(clusterIP, fmt.Sprint(m.maglevConfig.ServicePort)) + cmd = fmt.Sprintf("sudo docker run --rm --net=host %s -url http://%s/shell?cmd=hostname -port %d", + images.RapidClient, ep, m.maglevConfig.SourcePort) + output, err := extNode.Exec("sh", "-c", cmd) + Expect(err).NotTo(HaveOccurred()) + + framework.Logf("Request %d (%s): backend response: %s", i+1, ipVersion, output) + + // Parse the JSON response to get the exact backend pod name + matchedBackend, err := m.parseBackendResponse(output) + Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Request %d (%s) failed to parse response: %v", i+1, ipVersion, err)) + + framework.Logf("Request %d (%s): matched backend: %s", i+1, ipVersion, matchedBackend) + + // Track unique backends + uniqueBackends[matchedBackend]++ + + time.Sleep(time.Duration(m.maglevConfig.IntervalSeconds) * time.Second) + } + + framework.Logf("Completed %d requests (%s), all received valid backend responses", totalRequests, ipVersion) + return uniqueBackends +} + +func (m *MaglevTests) TestRandomBackendSelection(extNode *externalnode.Client, useIPv6 bool) { + ipVersion := "IPv4" + if useIPv6 { + ipVersion = "IPv6" + } + + // Send requests and gather backend response statistics + uniqueBackends := m.sendRequestsAndGatherStats(extNode, useIPv6, "random backend selection") + + // Assert that requests are distributed across at least 3 different backend pods + Expect(len(uniqueBackends)).Should(BeNumerically(">=", 3), + fmt.Sprintf("Expected %s requests to be distributed across at least 3 different backends, but only got %d unique backends: %v", + ipVersion, len(uniqueBackends), uniqueBackends)) + + framework.Logf("Random load balancing verified (%s): %d requests distributed across %d unique backends: %v", + ipVersion, m.maglevConfig.NumberOfRequests, len(uniqueBackends), uniqueBackends) +} + +func (m *MaglevTests) TestMaglevConsistentHashing(extNode *externalnode.Client, useIPv6 bool) string { + ipVersion := "IPv4" + if useIPv6 { + ipVersion = "IPv6" + } + + // Send requests and gather backend response statistics + uniqueBackends := m.sendRequestsAndGatherStats(extNode, useIPv6, "Maglev consistent hashing with annotation") + + // Assert that all requests went to exactly one backend (consistent hashing) + Expect(len(uniqueBackends)).Should(Equal(1), + fmt.Sprintf("Expected all %s requests to go to exactly 1 backend with Maglev annotation, but got %d unique backends: %v", + ipVersion, len(uniqueBackends), uniqueBackends)) + + // Get the first (and only) backend name for return + var backendName string + for name := range uniqueBackends { + backendName = name + break // There should only be one backend due to consistent hashing + } + + framework.Logf("Maglev consistent hashing verified (%s): all %d requests consistently routed to backends: %v", ipVersion, m.maglevConfig.NumberOfRequests, uniqueBackends) + return backendName +} + +// SetSourcePort sets the source port for subsequent requests +func (m *MaglevTests) SetSourcePort(port int) { + framework.Logf("Setting source port to %d for subsequent requests", port) + m.maglevConfig.SourcePort = port +} diff --git a/e2e/pkg/tests/networking/mtu.go b/e2e/pkg/tests/networking/mtu.go index 1f6ea5c4c63..6cc3a3f1984 100644 --- a/e2e/pkg/tests/networking/mtu.go +++ b/e2e/pkg/tests/networking/mtu.go @@ -14,7 +14,9 @@ package networking import ( "fmt" - . "github.com/onsi/ginkgo/v2" + "github.com/onsi/ginkgo/v2" + + //nolint:staticcheck // Ignore ST1001: should not use dot imports . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" "k8s.io/utils/ptr" @@ -39,7 +41,7 @@ var _ = describe.CalicoDescribe( var checker conncheck.ConnectionTester var clientPod *conncheck.Client - BeforeEach(func() { + ginkgo.BeforeEach(func() { var err error cli, err = client.New(f.ClientConfig()) Expect(err).NotTo(HaveOccurred()) @@ -61,7 +63,7 @@ var _ = describe.CalicoDescribe( checker.Deploy() }) - It("should select the correct MTU for the platform", func() { + ginkgo.It("should select the correct MTU for the platform", func() { // Get the MTU within the pod. out, err := conncheck.ExecInPod(clientPod.Pod(), "sh", "-c", "ip link show eth0") Expect(err).NotTo(HaveOccurred()) diff --git a/e2e/pkg/tests/networking/pod_termination.go b/e2e/pkg/tests/networking/pod_termination.go new file mode 100644 index 00000000000..cec88090c7d --- /dev/null +++ b/e2e/pkg/tests/networking/pod_termination.go @@ -0,0 +1,423 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. +// +// 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 networking + +import ( + "context" + "fmt" + "strings" + "time" + + //nolint:staticcheck // Ignore ST1001: should not use dot imports + . "github.com/onsi/ginkgo/v2" + //nolint:staticcheck // Ignore ST1001: should not use dot imports + . "github.com/onsi/gomega" + "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/kubernetes/test/e2e/framework" + e2epod "k8s.io/kubernetes/test/e2e/framework/pod" + "k8s.io/utils/ptr" + + "github.com/projectcalico/calico/e2e/pkg/describe" + "github.com/projectcalico/calico/e2e/pkg/utils" + "github.com/projectcalico/calico/e2e/pkg/utils/images" +) + +var _ = describe.CalicoDescribe( + describe.WithTeam(describe.Core), + describe.WithCategory(describe.Networking), + describe.WithFeature("Pods"), + "Pod Termination Grace Period", + func() { + f := utils.NewDefaultFramework("pod-termination") + + // Verifies that a client pod can still reach a server via wget while the + // client pod is in its termination grace period. This confirms that dataplane + // rules (routes, iptables/BPF entries) remain in place for terminating pods. + // + // Setup: deny-all ingress + targeted allow policies so traffic only flows + // through explicit policy. A client pod traps SIGTERM and attempts wget + // during the termination window. + framework.ConformanceIt("should allow client to reach server while client is terminating", func() { + ctx := context.Background() + ns := f.Namespace.Name + gracePeriod := int64(60) + targetPort := 80 + + // Create a deny-all ingress NetworkPolicy to ensure traffic is blocked + // unless explicitly allowed. + By("Creating deny-all ingress NetworkPolicy") + denyPolicy := &networkingv1.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "deny-all", + Namespace: ns, + }, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{}, + Ingress: []networkingv1.NetworkPolicyIngressRule{}, + }, + } + denyPolicy, err := f.ClientSet.NetworkingV1().NetworkPolicies(ns).Create(ctx, denyPolicy, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred(), "failed to create deny-all NetworkPolicy") + DeferCleanup(func() { + if err := f.ClientSet.NetworkingV1().NetworkPolicies(ns).Delete(ctx, denyPolicy.Name, metav1.DeleteOptions{}); err != nil { + framework.Logf("WARNING: failed to delete deny-all NetworkPolicy: %v", err) + } + }) + + // Create a server pod serving HTTP on port 80. + By("Creating an HTTP server pod") + serverPod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "server", + Namespace: ns, + Labels: map[string]string{"pod-name": "server", "role": "server"}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "server-container", + Image: images.Agnhost, + Args: []string{"serve-hostname", "--http", "--port", fmt.Sprintf("%d", targetPort)}, + Ports: []corev1.ContainerPort{{ContainerPort: int32(targetPort)}}, + }, + }, + RestartPolicy: corev1.RestartPolicyNever, + }, + } + serverPod, err = f.ClientSet.CoreV1().Pods(ns).Create(ctx, serverPod, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred(), "failed to create server pod") + DeferCleanup(func() { + if err := f.ClientSet.CoreV1().Pods(ns).Delete(ctx, serverPod.Name, metav1.DeleteOptions{}); err != nil { + framework.Logf("WARNING: failed to delete server pod: %v", err) + } + }) + + // Create a service for the server pod. + serverSvc := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "server-svc", + Namespace: ns, + }, + Spec: corev1.ServiceSpec{ + Selector: map[string]string{"pod-name": "server"}, + Ports: []corev1.ServicePort{ + {Port: int32(targetPort), TargetPort: intstr.FromInt32(int32(targetPort))}, + }, + }, + } + serverSvc, err = f.ClientSet.CoreV1().Services(ns).Create(ctx, serverSvc, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred(), "failed to create server service") + DeferCleanup(func() { + if err := f.ClientSet.CoreV1().Services(ns).Delete(ctx, serverSvc.Name, metav1.DeleteOptions{}); err != nil { + framework.Logf("WARNING: failed to delete server service: %v", err) + } + }) + + err = e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, serverPod) + Expect(err).NotTo(HaveOccurred(), "server pod did not reach Running state") + + By("Waiting for server pod to have an IP") + Eventually(func() error { + p, err := f.ClientSet.CoreV1().Pods(ns).Get(ctx, serverPod.Name, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to get server pod: %w", err) + } + if p.Status.PodIP == "" { + return fmt.Errorf("server pod has no IP yet") + } + return nil + }, 60*time.Second, 2*time.Second).Should(Succeed(), "timed out waiting for server pod IP") + + // Create an allow policy for ingress to the server on the target port. + By("Creating allow-ingress policy for server") + protocolTCP := corev1.ProtocolTCP + allowServerPolicy := &networkingv1.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "allow-server-ingress", + Namespace: ns, + }, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"pod-name": "server"}, + }, + Ingress: []networkingv1.NetworkPolicyIngressRule{{ + Ports: []networkingv1.NetworkPolicyPort{ + {Protocol: &protocolTCP, Port: &intstr.IntOrString{Type: intstr.Int, IntVal: int32(targetPort)}}, + }, + }}, + }, + } + allowServerPolicy, err = f.ClientSet.NetworkingV1().NetworkPolicies(ns).Create(ctx, allowServerPolicy, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred(), "failed to create allow-server-ingress policy") + DeferCleanup(func() { + if err := f.ClientSet.NetworkingV1().NetworkPolicies(ns).Delete(ctx, allowServerPolicy.Name, metav1.DeleteOptions{}); err != nil { + framework.Logf("WARNING: failed to delete allow-server-ingress policy: %v", err) + } + }) + + // Create an allow policy for egress from the client on the target port + DNS. + By("Creating allow-egress policy for client") + protocolUDP := corev1.ProtocolUDP + allowClientPolicy := &networkingv1.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "allow-client-egress", + Namespace: ns, + }, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"pod-name": "client-pod"}, + }, + Egress: []networkingv1.NetworkPolicyEgressRule{{ + Ports: []networkingv1.NetworkPolicyPort{ + {Protocol: &protocolTCP, Port: &intstr.IntOrString{Type: intstr.Int, IntVal: int32(targetPort)}}, + {Protocol: &protocolUDP, Port: &intstr.IntOrString{Type: intstr.Int, IntVal: 53}}, + }, + }}, + }, + } + allowClientPolicy, err = f.ClientSet.NetworkingV1().NetworkPolicies(ns).Create(ctx, allowClientPolicy, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred(), "failed to create allow-client-egress policy") + DeferCleanup(func() { + if err := f.ClientSet.NetworkingV1().NetworkPolicies(ns).Delete(ctx, allowClientPolicy.Name, metav1.DeleteOptions{}); err != nil { + framework.Logf("WARNING: failed to delete allow-client-egress policy: %v", err) + } + }) + + // Create a client pod that traps SIGTERM and performs wget during the + // termination grace period. The sleep before wget ensures we're well + // into the termination window. + By("Creating client pod that performs wget during termination") + target := fmt.Sprintf("%s.%s:%d", serverSvc.Name, ns, targetPort) + clientPod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "client-pod", + Namespace: ns, + Labels: map[string]string{"pod-name": "client-pod"}, + }, + Spec: corev1.PodSpec{ + TerminationGracePeriodSeconds: &gracePeriod, + Containers: []corev1.Container{ + { + Name: "client-container", + Image: images.Alpine, + Command: []string{"/bin/sh"}, + Args: []string{"-c", fmt.Sprintf(` +_term() { + echo "[$(date)] caught SIGTERM signal" + sleep 5 + for i in $(seq 1 5); do + echo "attempt $i of 5" + wget -T 5 %s -O - && { echo "[$(date)] wget success"; sleep 5; exit 0; } || sleep 1 + done + echo "[$(date)] wget failure"; cat /etc/resolv.conf; exit 1 +} +trap _term TERM +echo "[$(date)] starting container" +while :; do sleep 10; done +`, target)}, + }, + }, + RestartPolicy: corev1.RestartPolicyNever, + }, + } + clientPod, err = f.ClientSet.CoreV1().Pods(ns).Create(ctx, clientPod, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred(), "failed to create client pod") + DeferCleanup(func() { + // The pod may already be deleted by the test. + _ = f.ClientSet.CoreV1().Pods(ns).Delete(ctx, clientPod.Name, metav1.DeleteOptions{}) + }) + + err = e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, clientPod) + Expect(err).NotTo(HaveOccurred(), "client pod did not reach Running state") + + // Trigger pod deletion to start the termination grace period. + By("Deleting client pod to trigger termination") + go func() { + deleteErr := f.ClientSet.CoreV1().Pods(ns).Delete(ctx, clientPod.Name, metav1.DeleteOptions{}) + if deleteErr != nil { + logrus.WithError(deleteErr).Error("failed to delete client pod") + } + }() + + // Wait for the pod logs to contain "wget success", confirming the client + // could still reach the server during its termination grace period. + By("Checking pod logs for 'wget success'") + Eventually(func() error { + logs, err := e2epod.GetPodLogs(ctx, f.ClientSet, ns, clientPod.Name, "client-container") + if err != nil { + return fmt.Errorf("failed to get client pod logs: %w", err) + } + logrus.Infof("Client pod logs:\n%s", logs) + if !strings.Contains(logs, "wget success") { + return fmt.Errorf("client pod logs do not contain 'wget success' yet") + } + return nil + }, 2*time.Duration(gracePeriod)*time.Second, 2*time.Second).Should(Succeed(), + "timed out waiting for client pod to successfully wget server during termination") + }) + + // Verifies that a server pod remains pingable while it is terminating. + // The server pod has a PreStop hook that sleeps, keeping it alive during + // the termination grace period. A client pod pings the server continuously + // during this window. + It("should allow pinging a server pod while it is terminating", func() { + ctx := context.Background() + ns := f.Namespace.Name + gracePeriod := int64(60) + + // Create a server pod with a PreStop hook that sleeps for the grace period, + // keeping the pod alive and its IP routable. + By("Creating server pod with PreStop sleep hook") + serverPod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "server", + Namespace: ns, + Labels: map[string]string{"pod-name": "server"}, + }, + Spec: corev1.PodSpec{ + TerminationGracePeriodSeconds: &gracePeriod, + Containers: []corev1.Container{ + { + Name: "server-container", + Image: images.Agnhost, + Args: []string{"serve-hostname", "--http", "--port", "80"}, + Ports: []corev1.ContainerPort{{ContainerPort: 80}}, + Lifecycle: &corev1.Lifecycle{ + PreStop: &corev1.LifecycleHandler{ + Exec: &corev1.ExecAction{ + Command: []string{"/bin/sh", "-c", fmt.Sprintf("sleep %d", gracePeriod)}, + }, + }, + }, + }, + }, + RestartPolicy: corev1.RestartPolicyNever, + }, + } + serverPod, err := f.ClientSet.CoreV1().Pods(ns).Create(ctx, serverPod, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred(), "failed to create server pod") + DeferCleanup(func() { + // The pod may already be deleted by the test. + _ = f.ClientSet.CoreV1().Pods(ns).Delete(ctx, serverPod.Name, metav1.DeleteOptions{}) + }) + + err = e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, serverPod) + Expect(err).NotTo(HaveOccurred(), "server pod did not reach Running state") + + By("Waiting for server pod to have an IP") + var serverIP string + Eventually(func() error { + p, err := f.ClientSet.CoreV1().Pods(ns).Get(ctx, serverPod.Name, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to get server pod: %w", err) + } + if p.Status.PodIP == "" { + return fmt.Errorf("server pod has no IP yet") + } + serverIP = p.Status.PodIP + return nil + }, 60*time.Second, 2*time.Second).Should(Succeed(), "timed out waiting for server pod IP") + + // Verify that the server is pingable before triggering termination. + By("Verifying server is pingable before termination") + expectPingSuccess(ctx, f, ns, "pre-term-ping", serverIP) + + // Record the time we start deletion so we can calculate how much of the + // grace period remains for the Consistently check. + deleteTime := time.Now() + + By("Deleting server pod to trigger termination") + go func() { + deleteErr := f.ClientSet.CoreV1().Pods(ns).Delete(ctx, serverPod.Name, metav1.DeleteOptions{}) + if deleteErr != nil { + logrus.WithError(deleteErr).Error("failed to delete server pod") + } + }() + + // Continuously ping the server during its termination grace period. + // Use a conservative duration that stays within the grace period window + // minus a margin to avoid racing with actual pod shutdown. + By("Pinging server during termination grace period") + margin := 15 * time.Second + consistentlyDuration := time.Duration(gracePeriod)*time.Second - time.Since(deleteTime) - margin + if consistentlyDuration < 5*time.Second { + consistentlyDuration = 5 * time.Second + } + Consistently(func() error { + return pingFromNewPod(ctx, f, ns, "grace-ping", serverIP) + }, consistentlyDuration, 5*time.Second).Should(Succeed(), + "server pod became unreachable during termination grace period") + }) + }) + +// expectPingSuccess creates a short-lived pod that pings the given IP and +// expects it to succeed. +func expectPingSuccess(ctx context.Context, f *framework.Framework, ns, podName, ip string) { + ExpectWithOffset(1, pingFromNewPod(ctx, f, ns, podName, ip)).To(Succeed(), + "ping to %s from pod %s failed", ip, podName) +} + +// pingFromNewPod creates a pod that pings the given IP, waits for it to complete, +// and returns an error if the ping failed. Each invocation generates a unique pod +// name to avoid conflicts when called repeatedly (e.g., inside Consistently). +func pingFromNewPod(ctx context.Context, f *framework.Framework, ns, podName, ip string) error { + uniqueName := utils.GenerateRandomName(podName) + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: uniqueName, + Namespace: ns, + Labels: map[string]string{"pod-name": podName}, + }, + Spec: corev1.PodSpec{ + RestartPolicy: corev1.RestartPolicyNever, + Containers: []corev1.Container{ + { + Name: "ping", + Image: images.Alpine, + Command: []string{"ping", "-c", "3", "-W", "2", ip}, + SecurityContext: &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{"NET_RAW"}, + }, + }, + }, + }, + }, + } + + pod, err := f.ClientSet.CoreV1().Pods(ns).Create(ctx, pod, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("failed to create ping pod: %w", err) + } + defer func() { + graceSeconds := ptr.To[int64](0) + _ = f.ClientSet.CoreV1().Pods(ns).Delete(ctx, pod.Name, metav1.DeleteOptions{GracePeriodSeconds: graceSeconds}) + _ = e2epod.WaitForPodNotFoundInNamespace(ctx, f.ClientSet, pod.Name, ns, 30*time.Second) + }() + + err = e2epod.WaitForPodSuccessInNamespace(ctx, f.ClientSet, pod.Name, ns) + if err != nil { + logs, logErr := e2epod.GetPodLogs(ctx, f.ClientSet, ns, pod.Name, "ping") + if logErr == nil { + logrus.Infof("Ping pod %s logs:\n%s", pod.Name, logs) + } + return fmt.Errorf("ping to %s failed: %w", ip, err) + } + return nil +} diff --git a/e2e/pkg/tests/networking/qos.go b/e2e/pkg/tests/networking/qos.go new file mode 100644 index 00000000000..9236859f560 --- /dev/null +++ b/e2e/pkg/tests/networking/qos.go @@ -0,0 +1,140 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. +// +// 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 networking + +import ( + "context" + "time" + + //nolint:staticcheck // Ignore ST1001: should not use dot imports + . "github.com/onsi/ginkgo/v2" + //nolint:staticcheck // Ignore ST1001: should not use dot imports + . "github.com/onsi/gomega" + "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + e2enode "k8s.io/kubernetes/test/e2e/framework/node" + + "github.com/projectcalico/calico/e2e/pkg/describe" + "github.com/projectcalico/calico/e2e/pkg/utils" + "github.com/projectcalico/calico/e2e/pkg/utils/iperfcheck" +) + +// Tests for Calico QoS bandwidth limiting. Verifies that ingress and egress +// bandwidth annotations produce the expected throughput limits on real traffic. +var _ = describe.CalicoDescribe( + describe.WithTeam(describe.Core), + describe.WithFeature("QoS"), + describe.WithCategory(describe.Networking), + "QoS Controls", + func() { + f := utils.NewDefaultFramework("calico-qos") + + // Verifies that Calico's QoS bandwidth annotations limit actual throughput. + // An iperf3 server and client are deployed on separate nodes. We first measure + // baseline (unlimited) throughput, then apply ingress and egress bandwidth + // limits via pod annotations and verify the actual throughput matches the + // configured limits within a tolerance. + It("should limit bandwidth with QoS annotations", func() { + ctx := context.Background() + + By("Getting cluster node names") + nodeCtx, nodeCancel := context.WithTimeout(ctx, 30*time.Second) + defer nodeCancel() + nodes, err := e2enode.GetBoundedReadySchedulableNodes(nodeCtx, f.ClientSet, 3) + Expect(err).NotTo(HaveOccurred(), "failed to list schedulable nodes") + nodesInfo := utils.GetNodesInfo(f, nodes, true) + nodeNames := nodesInfo.GetNames() + Expect(len(nodeNames)).To(BeNumerically(">=", 2), "QoS test requires at least 2 nodes") + serverNode := nodeNames[0] + clientNode := nodeNames[1] + + pinToNode := func(nodeName string) func(*corev1.Pod) { + return func(pod *corev1.Pod) { + pod.Spec.NodeName = nodeName + } + } + + tester := iperfcheck.NewIperfTester(f) + defer tester.Stop() + + server := iperfcheck.NewPeer("iperf-server", f.Namespace, iperfcheck.WithPeerCustomizer(pinToNode(serverNode))) + client := iperfcheck.NewPeer("iperf-client", f.Namespace, iperfcheck.WithPeerCustomizer(pinToNode(clientNode))) + tester.AddPeer(server) + tester.AddPeer(client) + + // Deploy server and client pods on separate nodes. + By("Deploying iperf3 server and client pods") + tester.Deploy() + + // Measure baseline throughput without any QoS limit. + By("Running iperf3 to measure baseline throughput") + baseline, err := tester.MeasureBandwidth(client, server, iperfcheck.WithRetries(5, 5*time.Second)) + Expect(err).NotTo(HaveOccurred(), "failed to measure baseline throughput") + logrus.Infof("Baseline throughput (bps): %.0f", baseline.AverageRate) + + // The baseline should be much higher than the 10Mbit limit we'll configure. + Expect(baseline.AverageRate).To(BeNumerically(">=", 10_000_000.0*5), "baseline throughput too low to meaningfully test bandwidth limiting") + + // Replace the client with a pod annotated for 10Mbit ingress bandwidth. + By("Replacing iperf3 client with 10Mbit ingress bandwidth limit") + tester.RemovePeer("iperf-client") + client = iperfcheck.NewPeer("iperf-client", f.Namespace, + iperfcheck.WithPeerCustomizer(func(pod *corev1.Pod) { + pod.Spec.NodeName = clientNode + if pod.Annotations == nil { + pod.Annotations = map[string]string{} + } + pod.Annotations["qos.projectcalico.org/ingressBandwidth"] = "10M" + })) + tester.AddPeer(client) + tester.Deploy() + + // Measure ingress-limited throughput. Use -R (reverse) so the server sends + // data to the client, testing the client's ingress limit. + By("Running iperf3 to measure ingress-limited throughput") + ingressResult, err := tester.MeasureBandwidth(client, server, iperfcheck.WithReverse(), iperfcheck.WithRetries(5, 5*time.Second)) + Expect(err).NotTo(HaveOccurred(), "failed to measure ingress-limited throughput") + logrus.Infof("Ingress-limited throughput (bps): %.0f", ingressResult.AverageRate) + + // Expect the limited rate to be within 50% of the desired 10Mbit rate. + // We use a wide tolerance because kind environments can be noisy. + Expect(ingressResult.AverageRate).To(BeNumerically(">=", 10_000_000.0*0.5), "ingress-limited rate too far below target") + Expect(ingressResult.AverageRate).To(BeNumerically("<=", 10_000_000.0*2.0), "ingress-limited rate too far above target") + + // Replace the client with a pod annotated for 10Mbit egress bandwidth. + By("Replacing iperf3 client with 10Mbit egress bandwidth limit") + tester.RemovePeer("iperf-client") + client = iperfcheck.NewPeer("iperf-client", f.Namespace, + iperfcheck.WithPeerCustomizer(func(pod *corev1.Pod) { + pod.Spec.NodeName = clientNode + if pod.Annotations == nil { + pod.Annotations = map[string]string{} + } + pod.Annotations["qos.projectcalico.org/egressBandwidth"] = "10M" + })) + tester.AddPeer(client) + tester.Deploy() + + // Measure egress-limited throughput. Normal mode (client sends to server) + // tests the client's egress limit. + By("Running iperf3 to measure egress-limited throughput") + egressResult, err := tester.MeasureBandwidth(client, server, iperfcheck.WithRetries(5, 5*time.Second)) + Expect(err).NotTo(HaveOccurred(), "failed to measure egress-limited throughput") + logrus.Infof("Egress-limited throughput (bps): %.0f", egressResult.AverageRate) + + Expect(egressResult.AverageRate).To(BeNumerically(">=", 10_000_000.0*0.5), "egress-limited rate too far below target") + Expect(egressResult.AverageRate).To(BeNumerically("<=", 10_000_000.0*2.0), "egress-limited rate too far above target") + }) + }) diff --git a/e2e/pkg/tests/operator/ippools.go b/e2e/pkg/tests/operator/ippools.go index 5d69c79ef2c..4d9dc499cc6 100644 --- a/e2e/pkg/tests/operator/ippools.go +++ b/e2e/pkg/tests/operator/ippools.go @@ -16,7 +16,9 @@ import ( "fmt" "time" - . "github.com/onsi/ginkgo/v2" + "github.com/onsi/ginkgo/v2" + + //nolint:staticcheck // Ignore ST1001: should not use dot imports . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" operatorv1 "github.com/tigera/operator/api/v1" @@ -44,7 +46,7 @@ var _ = describe.CalicoDescribe( originalInstallation *operatorv1.Installation ) - BeforeEach(func() { + ginkgo.BeforeEach(func() { ctx = context.Background() // Ensure a clean starting environment before each test. @@ -71,7 +73,7 @@ var _ = describe.CalicoDescribe( // Additionally, skip if this cluster is operator managed but doesn't use the Calico CNI plugin, as validation // for non-Calico CNI clusters requires that IP pools use the 'all()' node selector which is incompatible with this test. if errors.IsNotFound(err) || installation.Spec.CNI == nil || installation.Spec.CNI.Type != operatorv1.PluginCalico { - Skip("Skipping IP pool management test.") + ginkgo.Skip("Skipping IP pool management test.") } Expect(err).NotTo(HaveOccurred()) @@ -79,7 +81,7 @@ var _ = describe.CalicoDescribe( originalInstallation = installation.DeepCopy() }) - AfterEach(func() { + ginkgo.AfterEach(func() { // Revert the installation to its original state. This might take an attempt or two // if we hit resource version conflicts. Eventually(func() error { @@ -94,7 +96,7 @@ var _ = describe.CalicoDescribe( // This test verifies that the operator properly creates and deletes IP pools when added / removed // from the Installation spec. - It("should create and delete IP pools", func() { + ginkgo.It("should create and delete IP pools", func() { // Determine a free CIDR. var newCIDR string pools := v3.IPPoolList{} @@ -152,7 +154,7 @@ var _ = describe.CalicoDescribe( return err } if pool.Spec.NodeSelector != "!all()" { - return fmt.Errorf("Operator did not revert the change to the IP pool") + return fmt.Errorf("operator did not revert the change to the IP pool") } return nil }, 20*time.Second, 2*time.Second).ShouldNot(HaveOccurred()) @@ -178,7 +180,7 @@ var _ = describe.CalicoDescribe( return err } if pool.Spec.NodeSelector != "has(dummy-key)" { - return fmt.Errorf("Operator did not apply the change to the IP pool") + return fmt.Errorf("operator did not apply the change to the IP pool") } return nil }).ShouldNot(HaveOccurred()) @@ -201,7 +203,7 @@ var _ = describe.CalicoDescribe( } else if err != nil { return err } - return fmt.Errorf("Pool still exists") + return fmt.Errorf("pool still exists") }).ShouldNot(HaveOccurred()) }) }) diff --git a/e2e/pkg/tests/policy/policy.go b/e2e/pkg/tests/policy/policy.go index 8844adbb8d2..83dc3368169 100644 --- a/e2e/pkg/tests/policy/policy.go +++ b/e2e/pkg/tests/policy/policy.go @@ -20,7 +20,10 @@ import ( "time" "github.com/aws/smithy-go/ptr" + + //nolint:staticcheck // Ignore ST1001: should not use dot imports . "github.com/onsi/ginkgo/v2" + //nolint:staticcheck // Ignore ST1001: should not use dot imports . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/sirupsen/logrus" @@ -38,6 +41,7 @@ var _ = describe.CalicoDescribe( describe.WithTeam(describe.Core), describe.WithFeature("NetworkPolicy"), describe.WithCategory(describe.Policy), + describe.WithWindows(), "Calico NetworkPolicy", func() { // Define variables common across all tests. @@ -90,11 +94,11 @@ var _ = describe.CalicoDescribe( // - Creating a default-deny policy applied to both namespaces using a GlobalNetworkPolicy. // - Isolating both namespaces with ingress and egress policies // - Asserting only same namespace connectivity exists. - framework.ConformanceIt("should correctly isolate namespaces [RunsOnWindows]", func() { + framework.ConformanceIt("should correctly isolate namespaces", func() { nsA := f.Namespace By("Creating a second namespace B") - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) defer cancel() nsB, err := f.CreateNamespace(ctx, f.BaseName+"-b", nil) Expect(err).NotTo(HaveOccurred()) @@ -218,12 +222,13 @@ var _ = describe.CalicoDescribe( framework.ConformanceIt("should support service account selectors", func() { ns := f.Namespace - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() By(fmt.Sprintf("Applying a default-deny policy to namespace %s", ns.Name)) - defaultDeny := newDefaultDenyPolicy(ns.Name) - cli.Create(ctx, defaultDeny) + defaultDeny := newDefaultDenyIngressPolicy(ns.Name) + err = cli.Create(ctx, defaultDeny) + Expect(err).NotTo(HaveOccurred()) defer func() { err := cli.Delete(ctx, defaultDeny) Expect(err).NotTo(HaveOccurred()) @@ -236,7 +241,7 @@ var _ = describe.CalicoDescribe( By("Allowing traffic through a service account selector") sa, err := f.ClientSet.CoreV1().ServiceAccounts(ns.Name).Get(ctx, "default", metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) - sa.ObjectMeta.Labels = map[string]string{"ns-name": ns.Name} + sa.Labels = map[string]string{"ns-name": ns.Name} _, err = f.ClientSet.CoreV1().ServiceAccounts(ns.Name).Update(ctx, sa, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -282,11 +287,13 @@ var _ = describe.CalicoDescribe( framework.ConformanceIt("should support namespace selectors", func() { ns := f.Namespace - ctx := context.Background() + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() By(fmt.Sprintf("Applying a default-deny policy to namespace %s", ns.Name)) - defaultDeny := newDefaultDenyPolicy(ns.Name) - cli.Create(ctx, defaultDeny) + defaultDeny := newDefaultDenyIngressPolicy(ns.Name) + err := cli.Create(ctx, defaultDeny) + Expect(err).NotTo(HaveOccurred()) defer func() { err := cli.Delete(ctx, defaultDeny) Expect(err).NotTo(HaveOccurred()) @@ -314,10 +321,18 @@ var _ = describe.CalicoDescribe( }, }, }, + Egress: []v3.Rule{ + { + Action: "Allow", + Destination: v3.EntityRule{ + NamespaceSelector: fmt.Sprintf("kubernetes.io/metadata.name == '%s'", ns.Name), + }, + }, + }, }, } - err := cli.Create(ctx, np) + err = cli.Create(ctx, np) Expect(err).NotTo(HaveOccurred()) defer func() { err := cli.Delete(ctx, np) @@ -332,7 +347,7 @@ var _ = describe.CalicoDescribe( checker.Execute() }) - framework.ConformanceIt("should support a 'deny egress' policy [RunsOnWindows]", func() { + framework.ConformanceIt("should support a 'deny egress' policy", func() { By("Creating calico egress policy which denies traffic within namespace.") nsName := f.Namespace.Name policyName := "deny-egress" @@ -399,7 +414,7 @@ func newNamespaceIsolationPolicy(name, namespace, ingressSelector, egressSelecto } } -func newDefaultDenyPolicy(namespace string) *v3.NetworkPolicy { +func newDefaultDenyIngressPolicy(namespace string) *v3.NetworkPolicy { return &v3.NetworkPolicy{ TypeMeta: metav1.TypeMeta{ Kind: "NetworkPolicy", @@ -410,6 +425,7 @@ func newDefaultDenyPolicy(namespace string) *v3.NetworkPolicy { Namespace: namespace, }, Spec: v3.NetworkPolicySpec{ + // If the spec.types field is not set, it defaults to "Ingress" only. Order: ptr.Float64(5000), Selector: "all()", }, diff --git a/e2e/pkg/tests/policy/service_policy.go b/e2e/pkg/tests/policy/service_policy.go index 7e1dd3db114..4bd1fd91c7b 100644 --- a/e2e/pkg/tests/policy/service_policy.go +++ b/e2e/pkg/tests/policy/service_policy.go @@ -19,7 +19,9 @@ import ( "fmt" "time" + //nolint:staticcheck // Ignore ST1001: should not use dot imports . "github.com/onsi/ginkgo/v2" + //nolint:staticcheck // Ignore ST1001: should not use dot imports . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" v1 "k8s.io/api/core/v1" @@ -192,7 +194,7 @@ var _ = describe.CalicoDescribe( // these need to be closures because they are evaluated in JustBeforeEach(), see https://github.com/onsi/ginkgo/issues/378 func(getAllowClientPolicy, getAllowServerPolicy func() *v3.NetworkPolicy, createClientService bool) { By("Creating default-deny policy, no client should be able to contact the server.") - defaultDeny := newDefaultDenyPolicy(f.Namespace.Name) + defaultDeny := newDefaultDenyIngressPolicy(f.Namespace.Name) err := cli.Create(context.Background(), defaultDeny) Expect(err).NotTo(HaveOccurred()) defer func() { diff --git a/e2e/pkg/tests/policy/staged_policy.go b/e2e/pkg/tests/policy/staged_policy.go new file mode 100644 index 00000000000..639af90905f --- /dev/null +++ b/e2e/pkg/tests/policy/staged_policy.go @@ -0,0 +1,553 @@ +// Copyright (c) 2025 Tigera, Inc. All rights reserved. +package policy + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + //nolint:staticcheck // Ignore ST1001: should not use dot imports + . "github.com/onsi/ginkgo/v2" + //nolint:staticcheck // Ignore ST1001: should not use dot imports + . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/test/e2e/framework" + "k8s.io/utils/ptr" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/projectcalico/calico/e2e/pkg/describe" + "github.com/projectcalico/calico/e2e/pkg/utils" + "github.com/projectcalico/calico/e2e/pkg/utils/client" + "github.com/projectcalico/calico/e2e/pkg/utils/conncheck" + "github.com/projectcalico/calico/lib/httpmachinery/pkg/apiutil" + whiskerv1 "github.com/projectcalico/calico/whisker-backend/pkg/apis/v1" +) + +// DESCRIPTION: This test verifies the staged network policy feature. +// +// DOCS_URL: https://docs.tigera.io/calico/latest/reference/resources/stagednetworkpolicy +// PRECONDITIONS: +var _ = describe.CalicoDescribe( + describe.WithTeam(describe.Core), + describe.WithCategory(describe.Policy), + "staged network policy", + func() { + var ( + kubectl *utils.Kubectl + cli ctrlclient.Client + err error + customTier string + + f = utils.NewDefaultFramework("staged-policy") + + serverPodNamePrefix = "server-pod" + clientPodNamePrefix = "client-pod" + serverPort = 80 + + checker conncheck.ConnectionTester + ) + + BeforeEach(func() { + cli, err = client.New(f.ClientConfig()) + Expect(err).NotTo(HaveOccurred()) + checker = conncheck.NewConnectionTester(f) + + // These tests rely on Whisker - if Whisker is not installed, short-circuit the tests. + installed, err := utils.WhiskerInstalled(cli) + Expect(err).NotTo(HaveOccurred()) + Expect(installed).To(BeTrue(), "Whisker is not installed in the cluster") + }) + + Context("Test presence in flow logs", func() { + var ( + tierObj *v3.Tier + client1 *conncheck.Client + server *conncheck.Server + + stopCh chan time.Time + url string + ) + + BeforeEach(func() { + customTier = utils.GenerateRandomName("e2e-staged-tier") + tierObj = v3.NewTier() + tierObj.Name = customTier + tierObj.Spec.Order = ptr.To[float64](200) + Expect(cli.Create(context.TODO(), tierObj)).ToNot(HaveOccurred()) + + client1 = conncheck.NewClient(clientPodNamePrefix, f.Namespace) + server = conncheck.NewServer(utils.GenerateRandomName(serverPodNamePrefix), f.Namespace) + checker.AddClient(client1) + checker.AddServer(server) + checker.Deploy() + + // stopCh for kubectl + stopCh = make(chan time.Time, 1) + + // We read flow logs from whisker-backend, we start port forward so we can query the flows + kubectl.PortForward("calico-system", "deployment/whisker", "3002", "", stopCh) + + // Build url to get flows from whisker + url = buildURL(server.Pod().Namespace, server.Pod().Namespace, "-1800") + + // Port forward should be working and whisker-backend should return 200 status + verifyPortForward(url) + }) + + AfterEach(func() { + Expect(cli.Delete(context.TODO(), tierObj)).ShouldNot(HaveOccurred()) + + checker.Stop() + + // Stop the kubectl port forward + stopCh <- time.Now() + close(stopCh) + }) + + Context("StagedKubernetesNetworkPolicy", func() { + var stagedKubernetesNetworkPolicy *v3.StagedKubernetesNetworkPolicy + BeforeEach(func() { + // create policy + podSelector := metav1.LabelSelector{MatchLabels: map[string]string{"pod-name": server.Name()}} + stagedKubernetesNetworkPolicy = CreateStagedKubernetesNetworkPolicyIngressDeny("sknp-deny", server.Pod().Namespace, podSelector) + + Expect(cli.Create(context.TODO(), stagedKubernetesNetworkPolicy)).ShouldNot(HaveOccurred()) + + // create client pod and connect from client to server + checker.ExpectSuccess(client1, server.ClusterIP().Port(serverPort)) + checker.Execute() + }) + + framework.ConformanceIt("Validate name, tier, action", func() { + verifyFlowCount(url, 2) + verifyFlowContainsStagedPolicy( + url, + stagedKubernetesNetworkPolicy.Name, + "default", + whiskerv1.PolicyKindStagedKubernetesNetworkPolicy, + whiskerv1.Action(0), // Action is empty for StagedKubernetesNetworkPolicy + ) + }) + + AfterEach(func() { + Expect(cli.Delete(context.TODO(), stagedKubernetesNetworkPolicy)).ShouldNot(HaveOccurred()) + }) + }) + + Context("StagedNetworkPolicy", func() { + var ( + stagedPolicyName string + stagedPolicy *v3.StagedNetworkPolicy + ) + + BeforeEach(func() { + selector := fmt.Sprintf("pod-name==\"%s\"", server.Name()) + + ingress := []v3.Rule{{Action: v3.Deny}} + stagedPolicyName = "snp-deny-1" + stagedPolicy = CreateStagedNetworkPolicy(stagedPolicyName, customTier, server.Pod().Namespace, 10, selector, ingress, nil) + + Expect(cli.Create(context.TODO(), stagedPolicy)).ShouldNot(HaveOccurred()) + + // Connect to the server's cluster IPs. + checker.ExpectSuccess(client1, server.ClusterIP().Port(serverPort)) + checker.Execute() + }) + + framework.ConformanceIt("Validate name, tier, action", func() { + verifyFlowCount(url, 2) + + verifyFlowContainsStagedPolicy( + url, + stagedPolicyName, + stagedPolicy.Spec.Tier, + whiskerv1.PolicyKindStagedNetworkPolicy, + whiskerv1.ActionDeny, + ) + }) + + AfterEach(func() { + // clean-up policy + Expect(cli.Delete(context.TODO(), stagedPolicy)).ShouldNot(HaveOccurred()) + }) + }) + + Context("StagedGlobalNetworkPolicy", func() { + var ( + stagedGlobalNetworkPolicyName string + stagedGlobalNetworkPolicy *v3.StagedGlobalNetworkPolicy + ) + + BeforeEach(func() { + selector := fmt.Sprintf("pod-name == \"%s\"", server.Name()) + ingress := []v3.Rule{{Action: v3.Deny}} + stagedGlobalNetworkPolicyName = "sgnp-deny-1" + stagedGlobalNetworkPolicy = CreateStagedGlobalNetworkPolicy(stagedGlobalNetworkPolicyName, customTier, 10, selector, ingress, nil) + + Expect(cli.Create(context.TODO(), stagedGlobalNetworkPolicy)).ShouldNot(HaveOccurred()) + + // create client pod and connect from client to server + checker.ExpectSuccess(client1, server.ClusterIP().Port(serverPort)) + checker.Execute() + }) + + framework.ConformanceIt("Validate name, tier, action", func() { + verifyFlowCount(url, 2) + verifyFlowContainsStagedPolicy( + url, + stagedGlobalNetworkPolicyName, + stagedGlobalNetworkPolicy.Spec.Tier, + whiskerv1.PolicyKindStagedGlobalNetworkPolicy, + whiskerv1.ActionDeny, + ) + }) + + AfterEach(func() { + Expect(cli.Delete(context.TODO(), stagedGlobalNetworkPolicy)).ShouldNot(HaveOccurred()) + }) + }) + }) + + Context("enforcing staged-policies", func() { + var ( + tierObj *v3.Tier + server *conncheck.Server + client1 *conncheck.Client + ) + + BeforeEach(func() { + cli, err = client.New(f.ClientConfig()) + Expect(err).ToNot(HaveOccurred()) + + customTier = utils.GenerateRandomName("e2e-staged-tier") + tierObj = v3.NewTier() + tierObj.Name = customTier + tierObj.Spec.Order = ptr.To[float64](200) + Expect(cli.Create(context.TODO(), tierObj)).ToNot(HaveOccurred()) + + // Create server + server = conncheck.NewServer(utils.GenerateRandomName(serverPodNamePrefix), f.Namespace) + client1 = conncheck.NewClient(clientPodNamePrefix, f.Namespace) + checker.AddServer(server) + checker.AddClient(client1) + checker.Deploy() + }) + + AfterEach(func() { + Expect(cli.Delete(context.TODO(), tierObj)).ShouldNot(HaveOccurred()) + checker.Stop() + }) + + Context("StagedKubernetesNetworkPolicy", func() { + It("should enforce a deny policy", func() { + // test connection from client to server - it should NOT fail + checker.ExpectSuccess(client1, server.ClusterIP().Port(serverPort)) + checker.Execute() + + // create policy + podSelector := metav1.LabelSelector{MatchLabels: server.Pod().Labels} + policy := CreateStagedKubernetesNetworkPolicyIngressDeny("service-deny-in", server.Pod().Namespace, podSelector) + Expect(cli.Create(context.TODO(), policy)).ShouldNot(HaveOccurred()) + DeferCleanup(func() { + Expect(cli.Delete(context.TODO(), policy)).ShouldNot(HaveOccurred()) + }) + + // enforce the policy + _, enforced := ConvertStagedKubernetesPolicyToK8SEnforced(policy) + Expect(cli.Create(context.TODO(), enforced)).ShouldNot(HaveOccurred()) + DeferCleanup(func() { + Expect(cli.Delete(context.TODO(), enforced)).ShouldNot(HaveOccurred()) + }) + + // test connection from client to server - it should fail + checker.ResetExpectations() + checker.ExpectFailure(client1, server.ClusterIP().Port(serverPort)) + checker.Execute() + }) + }) + + Context("StagedNetworkPolicy", func() { + It("should enforce a deny policy", func() { + // test connection from client to server - it should NOT fail + checker.ExpectSuccess(client1, server.ClusterIP().Port(serverPort)) + checker.Execute() + + // create policy + ingress := []v3.Rule{{Action: v3.Deny}} + selector := fmt.Sprintf("pod-name==\"%s\"", server.Name()) + order := 200.0 + policy := CreateStagedNetworkPolicy("service-deny-in", customTier, server.Pod().Namespace, order, selector, ingress, nil) + Expect(cli.Create(context.TODO(), policy)).ShouldNot(HaveOccurred()) + + // enforce the policy + _, enforced := ConvertStagedPolicyToEnforced(policy) + Expect(cli.Create(context.TODO(), enforced)).ShouldNot(HaveOccurred()) + + // test connection from client to server - it should fail + checker.ResetExpectations() + checker.ExpectFailure(client1, server.ClusterIP().Port(serverPort)) + checker.Execute() + + // delete policies + Expect(cli.Delete(context.TODO(), policy)).ShouldNot(HaveOccurred()) + Expect(cli.Delete(context.TODO(), enforced)).ShouldNot(HaveOccurred()) + }) + }) + + Context("StagedGlobalNetworkPolicy", func() { + It("should enforce a deny policy", func() { + // test connection from client to server - it should NOT fail + checker.ExpectSuccess(client1, server.ClusterIP().Port(serverPort)) + checker.Execute() + + // create policy + ingress := []v3.Rule{{Action: v3.Deny}} + selector := fmt.Sprintf("pod-name==\"%s\"", server.Name()) + order := 200.0 + policy := CreateStagedGlobalNetworkPolicy("service-deny-in", customTier, order, selector, ingress, nil) + Expect(cli.Create(context.TODO(), policy)).ShouldNot(HaveOccurred()) + + // enforce the policy + _, enforced := ConvertStagedGlobalPolicyToEnforced(policy) + Expect(cli.Create(context.TODO(), enforced)).ShouldNot(HaveOccurred()) + + // test connection from client to server - it should fail + checker.ResetExpectations() + checker.ExpectFailure(client1, server.ClusterIP().Port(serverPort)) + checker.Execute() + + // delete policies + Expect(cli.Delete(context.TODO(), policy)).ShouldNot(HaveOccurred()) + Expect(cli.Delete(context.TODO(), enforced)).ShouldNot(HaveOccurred()) + }) + }) + }) + }) + +func buildURL(sourceNamespace, destinationNamespace, startTime string) string { + baseURL := "http://localhost:3002/flows" + + f := whiskerv1.Filters{ + SourceNamespaces: whiskerv1.FilterMatches[string]{ + {Type: whiskerv1.MatchTypeExact, V: sourceNamespace}, + }, + DestNamespaces: whiskerv1.FilterMatches[string]{ + {Type: whiskerv1.MatchTypeExact, V: destinationNamespace}, + }, + } + filtersJSON, err := json.Marshal(f) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + + // Build query parameters for the URL. + params := url.Values{} + params.Add("filters", string(filtersJSON)) + params.Add("startTimeGte", startTime) + return fmt.Sprintf("%s?%s", baseURL, params.Encode()) +} + +func verifyPortForward(url string) { + // Port forward should be working and whisker-backend should return 200 status + Eventually(func() error { + resp, err := http.Get(url) + if err != nil { + return err + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("http response is not successful %d", resp.StatusCode) + } + + return nil + }, 30*time.Second, 1*time.Second).Should(Not(HaveOccurred())) +} + +func verifyFlowCount(url string, count int) { + var response apiutil.List[whiskerv1.FlowResponse] + + EventuallyWithOffset(1, func() error { + resp, err := http.Get(url) + if err != nil { + return err + } + defer func() { _ = resp.Body.Close() }() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + + if err = json.Unmarshal(body, &response); err != nil { + return err + } + + if len(response.Items) != count { + return fmt.Errorf("number of flow items does not match, expected %d, got %d", count, len(response.Items)) + } + + return nil + }, 90*time.Second, 5*time.Second).Should(Not(HaveOccurred())) +} + +func verifyFlowContainsStagedPolicy(url, name, tier string, kind whiskerv1.PolicyKind, action whiskerv1.Action) { + var response apiutil.List[whiskerv1.FlowResponse] + containsStagedPolicy := false + + resp, err := http.Get(url) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + defer func() { _ = resp.Body.Close() }() + + body, err := io.ReadAll(resp.Body) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + + err = json.Unmarshal(body, &response) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + + ExpectWithOffset(1, kind).NotTo(Equal(""), "BUG: kind should not be empty") + + // Build up an error message to help debug if the policy is not found + var msg strings.Builder + msg.WriteString(fmt.Sprintf("Could not find flow:\nKind:%s Name:%s Tier:%s Action:%s\n\n", kind, name, tier, action)) + msg.WriteString(fmt.Sprintf("Found %d flow items:\n", len(response.Items))) + +responseLoop: + for _, item := range response.Items { + pendingPolicies := item.Policies.Pending + for _, pending := range pendingPolicies { + msg.WriteString(fmt.Sprintf( + " - %s\n", policyHitString( + pending.Kind, + pending.Namespace, + pending.Name, + pending.Tier, + pending.Action, + ))) + + if pending.Name == name && + pending.Tier == tier && + pending.Kind == kind && + pending.Action == action { + containsStagedPolicy = true + break responseLoop + } + + if pending.Trigger != nil { + msg.WriteString(fmt.Sprintf( + " - TriggeredBy(%s)\n", + policyHitString( + pending.Trigger.Kind, + pending.Trigger.Namespace, + pending.Trigger.Name, + pending.Trigger.Tier, + pending.Trigger.Action, + ))) + if pending.Trigger.Name == name && + pending.Trigger.Tier == tier && + pending.Trigger.Kind == kind && + pending.Trigger.Action == action { + containsStagedPolicy = true + break responseLoop + } + } + } + } + + Expect(containsStagedPolicy).Should(BeTrue(), msg.String()) +} + +func policyHitString(kind whiskerv1.PolicyKind, namespace, name, tier string, action whiskerv1.Action) string { + msg := fmt.Sprintf("Kind:%s ", kind) + if namespace != "" { + msg += fmt.Sprintf("Namespace:%s ", namespace) + } + if name != "" { + msg += fmt.Sprintf("Name:%s ", name) + } + if tier != "" { + msg += fmt.Sprintf("Tier:%s ", tier) + } + msg += fmt.Sprintf("Action:%s ", action) + return msg +} + +func CreateStagedNetworkPolicy( + policyName, tier, namespace string, + order float64, + selector string, + ingressRules, egressRules []v3.Rule, +) *v3.StagedNetworkPolicy { + policy := v3.NewStagedNetworkPolicy() + policy.ObjectMeta = metav1.ObjectMeta{Name: policyName, Namespace: namespace} + + var types []v3.PolicyType + if len(ingressRules) > 0 { + types = append(types, v3.PolicyTypeIngress) + } + if len(egressRules) > 0 { + types = append(types, v3.PolicyTypeEgress) + } + + policy.Spec = v3.StagedNetworkPolicySpec{ + Tier: tier, + Order: &order, + Ingress: ingressRules, + Egress: egressRules, + Selector: selector, + Types: types, + } + return policy +} + +// CreateStagedKubernetesNetworkPolicyIngressDeny does not have an explicit Action: passing in an empty +// ingress and engress behaves as DENY for the traffic selected by the PodSelector. +func CreateStagedKubernetesNetworkPolicyIngressDeny( + policyName, namespace string, + podSelector metav1.LabelSelector, +) *v3.StagedKubernetesNetworkPolicy { + policy := v3.NewStagedKubernetesNetworkPolicy() + policy.ObjectMeta = metav1.ObjectMeta{ + Name: policyName, + Namespace: namespace, + } + policy.Spec = v3.StagedKubernetesNetworkPolicySpec{ + PodSelector: podSelector, + Ingress: []networkingv1.NetworkPolicyIngressRule{}, + PolicyTypes: []networkingv1.PolicyType{networkingv1.PolicyTypeIngress}, + } + return policy +} + +func CreateStagedGlobalNetworkPolicy( + policyName, tier string, + order float64, + selector string, + ingressRules, egressRules []v3.Rule, +) *v3.StagedGlobalNetworkPolicy { + policy := v3.NewStagedGlobalNetworkPolicy() + policy.ObjectMeta = metav1.ObjectMeta{Name: policyName} + + var types []v3.PolicyType + if len(ingressRules) > 0 { + types = append(types, v3.PolicyTypeIngress) + } + if len(egressRules) > 0 { + types = append(types, v3.PolicyTypeEgress) + } + + policy.Spec = v3.StagedGlobalNetworkPolicySpec{ + Order: &order, + Tier: tier, + Selector: selector, + Ingress: ingressRules, + Egress: egressRules, + Types: types, + } + return policy +} diff --git a/e2e/pkg/tests/policy/staged_policy_converters.go b/e2e/pkg/tests/policy/staged_policy_converters.go new file mode 100644 index 00000000000..06958427db1 --- /dev/null +++ b/e2e/pkg/tests/policy/staged_policy_converters.go @@ -0,0 +1,64 @@ +// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// +// 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 policy + +import ( + "github.com/jinzhu/copier" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// ConvertStagedPolicyToEnforced converts a StagedNetworkPolicy into a StagedAction, NetworkPolicy pair +func ConvertStagedPolicyToEnforced(staged *v3.StagedNetworkPolicy) (v3.StagedAction, *v3.NetworkPolicy) { + // Convert StagedNetworkPolicy to NetworkPolicy + enforced := v3.NewNetworkPolicy() + _ = copier.Copy(&enforced.ObjectMeta, &staged.ObjectMeta) + _ = copier.Copy(&enforced.Spec, &staged.Spec) + + // Clear fields that should not be copied onto new objects. + enforced.ResourceVersion = "" + enforced.UID = "" + + return staged.Spec.StagedAction, enforced +} + +// ConvertStagedGlobalPolicyToEnforced converts a StagedGlobalNetworkPolicy into a StagedAction, GlobalNetworkPolicy pair +func ConvertStagedGlobalPolicyToEnforced(staged *v3.StagedGlobalNetworkPolicy) (v3.StagedAction, *v3.GlobalNetworkPolicy) { + enforced := v3.NewGlobalNetworkPolicy() + _ = copier.Copy(&enforced.ObjectMeta, &staged.ObjectMeta) + _ = copier.Copy(&enforced.Spec, &staged.Spec) + + // Clear fields that should not be copied onto new objects. + enforced.ResourceVersion = "" + enforced.UID = "" + + return staged.Spec.StagedAction, enforced +} + +// ConvertStagedKubernetesPolicyToK8SEnforced converts a StagedKubernetesNetworkPolicy into a StagedAction, networkingv1 NetworkPolicy pair +func ConvertStagedKubernetesPolicyToK8SEnforced(staged *v3.StagedKubernetesNetworkPolicy) (v3.StagedAction, *networkingv1.NetworkPolicy) { + // Convert StagedKubernetesNetworkPolicy to networkingv1.NetworkPolicy + enforced := networkingv1.NetworkPolicy{} + _ = copier.Copy(&enforced.ObjectMeta, &staged.ObjectMeta) + _ = copier.Copy(&enforced.Spec, &staged.Spec) + enforced.TypeMeta = metav1.TypeMeta{APIVersion: "networking.k8s.io/v1", Kind: "NetworkPolicy"} + + // Clear fields that should not be copied onto new objects. + enforced.ResourceVersion = "" + enforced.UID = "" + + return staged.Spec.StagedAction, &enforced +} diff --git a/e2e/pkg/tests/policy/tiered_rbac.go b/e2e/pkg/tests/policy/tiered_rbac.go new file mode 100644 index 00000000000..d7f01a0dbe5 --- /dev/null +++ b/e2e/pkg/tests/policy/tiered_rbac.go @@ -0,0 +1,583 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. +// +// 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 policy + +import ( + "context" + //nolint:staticcheck // Ignore ST1001: should not use dot imports + . "github.com/onsi/ginkgo/v2" + //nolint:staticcheck // Ignore ST1001: should not use dot imports + . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "github.com/sirupsen/logrus" + rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/rest" + "k8s.io/utils/ptr" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/projectcalico/calico/e2e/pkg/describe" + "github.com/projectcalico/calico/e2e/pkg/utils" + "github.com/projectcalico/calico/e2e/pkg/utils/client" +) + +const ( + // Test tier names. + rbacTestTier = "e2e-rbac-test" + rbacOtherTier = "e2e-rbac-other" + + // Impersonated user names. + rbacTierAdminUser = "e2e-rbac-tier-admin" + rbacNoTierGetUser = "e2e-rbac-no-tier-get" + rbacNoPolicyUser = "e2e-rbac-no-policy-access" + rbacOtherTierUser = "e2e-rbac-other-tier-admin" + rbacReadOnlyUser = "e2e-rbac-read-only" + + // Common prefix for RBAC resources created by these tests. + rbacResourcePrefix = "e2e-tiered-rbac-" +) + +// DESCRIPTION: Verify tiered RBAC correctly enforces tier-based access control +// for policy create, update, and delete operations. +// +// The tiered RBAC implementation requires that users have: +// 1. GET access to the tier (resource: "tiers") +// 2. The required verb on either the specific policy or all policies in the tier +// (resource: "tier.networkpolicies" / "tier.globalnetworkpolicies") +// +// PRECONDITIONS: The tiered RBAC webhook or Calico API server must be installed. Tiers are cluster-scoped, +// so these tests must run serially. +var _ = describe.CalicoDescribe( + describe.WithTeam(describe.Core), + describe.WithCategory(describe.Policy), + describe.WithFeature("Tiered-RBAC"), + describe.WithSerial(), + "Tiered RBAC", + func() { + f := utils.NewDefaultFramework("tiered-rbac") + + var ( + adminCli ctrlclient.Client + ctx context.Context + cancel context.CancelFunc + ) + + // newImpersonatedClient creates a controller-runtime client that impersonates the given user. + newImpersonatedClient := func(username string) ctrlclient.Client { + cfg := rest.CopyConfig(f.ClientConfig()) + cfg.Impersonate = rest.ImpersonationConfig{ + UserName: username, + } + c, err := client.NewAPIClient(cfg) + Expect(err).NotTo(HaveOccurred()) + return c + } + + BeforeEach(func() { + var err error + ctx, cancel = context.WithCancel(context.Background()) + + adminCli, err = client.New(f.ClientConfig()) + Expect(err).NotTo(HaveOccurred()) + + Expect(utils.CleanDatastore(adminCli)).ShouldNot(HaveOccurred()) + + By("Creating test tiers") + for _, t := range []struct { + name string + order float64 + }{ + {rbacTestTier, 500}, + {rbacOtherTier, 501}, + } { + tier := v3.NewTier() + tier.Name = t.name + tier.Spec.Order = ptr.To(t.order) + tier.Labels = map[string]string{utils.TestResourceLabel: "true"} + Expect(adminCli.Create(ctx, tier)).To(Succeed()) + } + + By("Creating RBAC resources for test users") + setup := buildTieredRBACResources() + for i := range setup.roles { + _, err := f.ClientSet.RbacV1().ClusterRoles().Create(ctx, &setup.roles[i], metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + } + for i := range setup.bindings { + _, err := f.ClientSet.RbacV1().ClusterRoleBindings().Create(ctx, &setup.bindings[i], metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + } + }) + + AfterEach(func() { + defer cancel() + var errOccurred bool + + By("Cleaning up RBAC resources") + setup := buildTieredRBACResources() + for _, binding := range setup.bindings { + if err := f.ClientSet.RbacV1().ClusterRoleBindings().Delete(ctx, binding.Name, metav1.DeleteOptions{}); err != nil { + logrus.WithError(err).WithField("name", binding.Name).Error("Failed to delete ClusterRoleBinding") + errOccurred = true + } + } + for _, role := range setup.roles { + if err := f.ClientSet.RbacV1().ClusterRoles().Delete(ctx, role.Name, metav1.DeleteOptions{}); err != nil { + logrus.WithError(err).WithField("name", role.Name).Error("Failed to delete ClusterRole") + errOccurred = true + } + } + + By("Cleaning up test tiers") + for _, name := range []string{rbacTestTier, rbacOtherTier} { + tier := v3.NewTier() + tier.Name = name + if err := adminCli.Delete(ctx, tier); err != nil { + logrus.WithError(err).WithField("name", name).Error("Failed to delete Tier") + errOccurred = true + } + } + + Expect(errOccurred).To(BeFalse(), "errors occurred during teardown") + }) + + Context("NetworkPolicy", func() { + It("should allow creation by a user with full tier RBAC", func() { + cli := newImpersonatedClient(rbacTierAdminUser) + + np := v3.NewNetworkPolicy() + np.Name = "rbac-test-allow-create" + np.Namespace = f.Namespace.Name + np.Spec.Tier = rbacTestTier + np.Spec.Order = ptr.To(100.0) + np.Spec.Selector = "all()" + np.Spec.Ingress = []v3.Rule{{Action: v3.Allow}} + + err := cli.Create(ctx, np) + Expect(err).NotTo(HaveOccurred()) + + Expect(adminCli.Delete(ctx, np)).To(Succeed()) + }) + + It("should deny creation by a user without tier GET access", func() { + cli := newImpersonatedClient(rbacNoTierGetUser) + + np := v3.NewNetworkPolicy() + np.Name = "rbac-test-deny-no-get" + np.Namespace = f.Namespace.Name + np.Spec.Tier = rbacTestTier + np.Spec.Order = ptr.To(100.0) + np.Spec.Selector = "all()" + np.Spec.Ingress = []v3.Rule{{Action: v3.Allow}} + + err := cli.Create(ctx, np) + Expect(err).To(HaveOccurred()) + Expect(apierrors.IsForbidden(err)).To(BeTrue(), "expected forbidden error, got: %v", err) + Expect(err.Error()).To(ContainSubstring("tier")) + }) + + It("should deny creation by a user without tier policy access", func() { + cli := newImpersonatedClient(rbacNoPolicyUser) + + np := v3.NewNetworkPolicy() + np.Name = "rbac-test-deny-no-policy" + np.Namespace = f.Namespace.Name + np.Spec.Tier = rbacTestTier + np.Spec.Order = ptr.To(100.0) + np.Spec.Selector = "all()" + np.Spec.Ingress = []v3.Rule{{Action: v3.Allow}} + + err := cli.Create(ctx, np) + Expect(err).To(HaveOccurred()) + Expect(apierrors.IsForbidden(err)).To(BeTrue(), "expected forbidden error, got: %v", err) + Expect(err.Error()).To(ContainSubstring("tier")) + }) + + It("should allow deletion by a user with full tier RBAC", func() { + By("Creating a policy with the admin client") + np := v3.NewNetworkPolicy() + np.Name = "rbac-test-allow-delete" + np.Namespace = f.Namespace.Name + np.Spec.Tier = rbacTestTier + np.Spec.Order = ptr.To(100.0) + np.Spec.Selector = "all()" + np.Spec.Ingress = []v3.Rule{{Action: v3.Allow}} + Expect(adminCli.Create(ctx, np)).To(Succeed()) + + By("Deleting the policy as the tier admin user") + cli := newImpersonatedClient(rbacTierAdminUser) + Expect(cli.Delete(ctx, np)).To(Succeed()) + }) + + // Verifies that the update verb is checked through the same tier RBAC + // authorization path as create and delete. + It("should allow update by a user with full tier RBAC", func() { + By("Creating a policy with the admin client") + np := v3.NewNetworkPolicy() + np.Name = "rbac-test-allow-update" + np.Namespace = f.Namespace.Name + np.Spec.Tier = rbacTestTier + np.Spec.Order = ptr.To(100.0) + np.Spec.Selector = "all()" + np.Spec.Ingress = []v3.Rule{{Action: v3.Allow}} + Expect(adminCli.Create(ctx, np)).To(Succeed(), "admin failed to create policy for update test") + + By("Updating the policy as the tier admin user") + cli := newImpersonatedClient(rbacTierAdminUser) + Expect(cli.Get(ctx, ctrlclient.ObjectKeyFromObject(np), np)).To( + Succeed(), "tier admin failed to get policy", + ) + np.Spec.Order = ptr.To(200.0) + Expect(cli.Update(ctx, np)).To(Succeed(), "tier admin should be able to update policy") + + Expect(adminCli.Delete(ctx, np)).To(Succeed()) + }) + + // Verifies that update is denied when the user lacks tier GET access, + // even though they have update permission on tier.networkpolicies. + It("should deny update by a user without tier GET access", func() { + By("Creating a policy with the admin client") + np := v3.NewNetworkPolicy() + np.Name = "rbac-test-deny-update" + np.Namespace = f.Namespace.Name + np.Spec.Tier = rbacTestTier + np.Spec.Order = ptr.To(100.0) + np.Spec.Selector = "all()" + np.Spec.Ingress = []v3.Rule{{Action: v3.Allow}} + Expect(adminCli.Create(ctx, np)).To(Succeed(), "admin failed to create policy for update test") + + By("Fetching the policy as admin to get the resource version") + Expect(adminCli.Get(ctx, ctrlclient.ObjectKeyFromObject(np), np)).To( + Succeed(), "admin failed to get policy", + ) + + By("Attempting to update the policy without tier GET access") + np.Spec.Order = ptr.To(200.0) + cli := newImpersonatedClient(rbacNoTierGetUser) + err := cli.Update(ctx, np) + Expect(err).To(HaveOccurred(), "update should be denied without tier GET access") + Expect(apierrors.IsForbidden(err)).To(BeTrue(), "expected forbidden error, got: %v", err) + + Expect(adminCli.Delete(ctx, np)).To(Succeed()) + }) + }) + + Context("GlobalNetworkPolicy", func() { + It("should allow creation by a user with full tier RBAC", func() { + cli := newImpersonatedClient(rbacTierAdminUser) + + gnp := v3.NewGlobalNetworkPolicy() + gnp.Name = "rbac-test-allow-gnp" + gnp.Spec.Tier = rbacTestTier + gnp.Spec.Order = ptr.To(100.0) + gnp.Spec.Selector = "all()" + gnp.Spec.Ingress = []v3.Rule{{Action: v3.Allow}} + + err := cli.Create(ctx, gnp) + Expect(err).NotTo(HaveOccurred()) + + Expect(adminCli.Delete(ctx, gnp)).To(Succeed()) + }) + + It("should deny creation by a user without tier access", func() { + cli := newImpersonatedClient(rbacNoTierGetUser) + + gnp := v3.NewGlobalNetworkPolicy() + gnp.Name = "rbac-test-deny-gnp" + gnp.Spec.Tier = rbacTestTier + gnp.Spec.Order = ptr.To(100.0) + gnp.Spec.Selector = "all()" + gnp.Spec.Ingress = []v3.Rule{{Action: v3.Allow}} + + err := cli.Create(ctx, gnp) + Expect(err).To(HaveOccurred()) + Expect(apierrors.IsForbidden(err)).To(BeTrue(), "expected forbidden error, got: %v", err) + Expect(err.Error()).To(ContainSubstring("tier")) + }) + }) + + Context("tier isolation", func() { + It("should restrict a user to only their permitted tier", func() { + cli := newImpersonatedClient(rbacOtherTierUser) + + By("Creating a policy in the permitted tier should succeed") + gnp := v3.NewGlobalNetworkPolicy() + gnp.Name = "rbac-test-other-allowed" + gnp.Spec.Tier = rbacOtherTier + gnp.Spec.Order = ptr.To(100.0) + gnp.Spec.Selector = "all()" + gnp.Spec.Ingress = []v3.Rule{{Action: v3.Allow}} + + err := cli.Create(ctx, gnp) + Expect(err).NotTo(HaveOccurred()) + Expect(adminCli.Delete(ctx, gnp)).To(Succeed()) + + By("Creating a policy in a non-permitted tier should be denied") + gnp2 := v3.NewGlobalNetworkPolicy() + gnp2.Name = "rbac-test-other-denied" + gnp2.Spec.Tier = rbacTestTier + gnp2.Spec.Order = ptr.To(100.0) + gnp2.Spec.Selector = "all()" + gnp2.Spec.Ingress = []v3.Rule{{Action: v3.Allow}} + + err = cli.Create(ctx, gnp2) + Expect(err).To(HaveOccurred()) + Expect(apierrors.IsForbidden(err)).To(BeTrue(), "expected forbidden error, got: %v", err) + Expect(err.Error()).To(ContainSubstring("tier")) + }) + }) + + // The default tier is a built-in resource that must remain immutable. + // Even an admin cannot update or delete it. Each test includes a + // DeferCleanup safety net that restores the default tier if the + // operation unexpectedly succeeds, so we don't leave the cluster broken. + Context("default tier", func() { + // restoreDefaultTier is a safety net for default tier tests. If the + // tier was modified (ResourceVersion changed) it restores the saved + // spec; if it was deleted it recreates it. + restoreDefaultTier := func(saved *v3.Tier) { + current := v3.NewTier() + current.Name = "default" + if err := adminCli.Get(ctx, ctrlclient.ObjectKeyFromObject(current), current); err != nil { + // Tier was deleted, recreate it. + restore := v3.NewTier() + restore.Name = "default" + restore.Spec = saved.Spec + if err := adminCli.Create(ctx, restore); err != nil { + logrus.WithError(err).Error("CRITICAL: failed to recreate deleted default tier") + } + return + } + if current.ResourceVersion != saved.ResourceVersion { + current.Spec = saved.Spec + if err := adminCli.Update(ctx, current); err != nil { + logrus.WithError(err).Warn("Failed to restore default tier") + } + } + } + + It("should not allow updating the default tier", func() { + tier := v3.NewTier() + tier.Name = "default" + Expect(adminCli.Get(ctx, ctrlclient.ObjectKeyFromObject(tier), tier)).To( + Succeed(), "failed to get default tier", + ) + + savedTier := tier.DeepCopy() + DeferCleanup(func() { restoreDefaultTier(savedTier) }) + + tier.Spec.Order = ptr.To(999.0) + err := adminCli.Update(ctx, tier) + Expect(err).To(HaveOccurred(), "default tier should not be updatable") + }) + + It("should not allow deleting the default tier", func() { + tier := v3.NewTier() + tier.Name = "default" + Expect(adminCli.Get(ctx, ctrlclient.ObjectKeyFromObject(tier), tier)).To( + Succeed(), "failed to get default tier", + ) + + savedTier := tier.DeepCopy() + DeferCleanup(func() { restoreDefaultTier(savedTier) }) + + err := adminCli.Delete(ctx, tier) + Expect(err).To(HaveOccurred(), "default tier should not be deletable") + }) + }) + + // Verifies that a user with read-only (get/list/watch) permissions on + // tier policies can get existing policies but cannot create, update, + // or delete them. + Context("read-only access", func() { + It("should allow reading but deny writing for a read-only user", func() { + By("Creating a policy with the admin client") + np := v3.NewNetworkPolicy() + np.Name = "rbac-test-read-only" + np.Namespace = f.Namespace.Name + np.Spec.Tier = rbacTestTier + np.Spec.Order = ptr.To(100.0) + np.Spec.Selector = "all()" + np.Spec.Ingress = []v3.Rule{{Action: v3.Allow}} + Expect(adminCli.Create(ctx, np)).To(Succeed(), "admin failed to create policy for read-only test") + + cli := newImpersonatedClient(rbacReadOnlyUser) + + By("Verifying the read-only user can get the policy") + readNP := v3.NewNetworkPolicy() + Expect(cli.Get(ctx, ctrlclient.ObjectKeyFromObject(np), readNP)).To( + Succeed(), "read-only user should be able to get policy", + ) + + By("Verifying the read-only user cannot create a policy") + newNP := v3.NewNetworkPolicy() + newNP.Name = "rbac-test-read-only-create" + newNP.Namespace = f.Namespace.Name + newNP.Spec.Tier = rbacTestTier + newNP.Spec.Order = ptr.To(100.0) + newNP.Spec.Selector = "all()" + newNP.Spec.Ingress = []v3.Rule{{Action: v3.Allow}} + err := cli.Create(ctx, newNP) + Expect(err).To(HaveOccurred(), "read-only user should not be able to create policy") + Expect(apierrors.IsForbidden(err)).To(BeTrue(), "expected forbidden error, got: %v", err) + + By("Verifying the read-only user cannot update the policy") + readNP.Spec.Order = ptr.To(200.0) + err = cli.Update(ctx, readNP) + Expect(err).To(HaveOccurred(), "read-only user should not be able to update policy") + Expect(apierrors.IsForbidden(err)).To(BeTrue(), "expected forbidden error, got: %v", err) + + By("Verifying the read-only user cannot delete the policy") + err = cli.Delete(ctx, np) + Expect(err).To(HaveOccurred(), "read-only user should not be able to delete policy") + Expect(apierrors.IsForbidden(err)).To(BeTrue(), "expected forbidden error, got: %v", err) + + Expect(adminCli.Delete(ctx, np)).To(Succeed()) + }) + }) + }, +) + +// tieredRBACSetup holds the RBAC resources needed for the tiered RBAC tests. +type tieredRBACSetup struct { + roles []rbacv1.ClusterRole + bindings []rbacv1.ClusterRoleBinding +} + +// buildTieredRBACResources constructs the ClusterRoles and ClusterRoleBindings needed for the +// tiered RBAC test users. Each user gets a different set of permissions to test different +// authorization outcomes: +// +// - rbacTierAdminUser: full tier access (tier GET + tier policy wildcard) +// - rbacNoTierGetUser: has tier policy access but NO tier GET +// - rbacNoPolicyUser: has tier GET but NO tier policy access +// - rbacOtherTierUser: full access but only for a different tier +// - rbacReadOnlyUser: read-only access (get/list/watch) on tier policies +func buildTieredRBACResources() tieredRBACSetup { + setup := tieredRBACSetup{} + + addRoleAndBinding := func(name, user string, rules []rbacv1.PolicyRule) { + setup.roles = append(setup.roles, rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{Name: rbacResourcePrefix + name}, + Rules: rules, + }) + setup.bindings = append(setup.bindings, rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{Name: rbacResourcePrefix + name}, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: rbacResourcePrefix + name, + }, + Subjects: []rbacv1.Subject{ + { + APIGroup: "rbac.authorization.k8s.io", + Kind: "User", + Name: user, + }, + }, + }) + } + + // baseRules returns the standard API server RBAC rules that all test users need. + baseRules := func() []rbacv1.PolicyRule { + return []rbacv1.PolicyRule{ + { + APIGroups: []string{"projectcalico.org"}, + Resources: []string{"networkpolicies", "globalnetworkpolicies"}, + Verbs: []string{"create", "update", "delete", "get", "list", "watch"}, + }, + } + } + + // Tier admin: has GET on the test tier + wildcard policy access for the test tier. + addRoleAndBinding("tier-admin", rbacTierAdminUser, append(baseRules(), + rbacv1.PolicyRule{ + APIGroups: []string{"projectcalico.org"}, + Resources: []string{"tiers"}, + Verbs: []string{"get"}, + ResourceNames: []string{rbacTestTier}, + }, + rbacv1.PolicyRule{ + APIGroups: []string{"projectcalico.org"}, + Resources: []string{"tier.networkpolicies", "tier.globalnetworkpolicies"}, + Verbs: []string{"create", "update", "delete", "get"}, + ResourceNames: []string{rbacTestTier + ".*"}, + }, + )) + + // No tier GET: has tier policy access but lacks the required GET on the tier resource. + // RBAC should deny because tier GET is required alongside policy access. + addRoleAndBinding("no-tier-get", rbacNoTierGetUser, append(baseRules(), + rbacv1.PolicyRule{ + APIGroups: []string{"projectcalico.org"}, + Resources: []string{"tier.networkpolicies", "tier.globalnetworkpolicies"}, + Verbs: []string{"create", "update", "delete", "get"}, + ResourceNames: []string{rbacTestTier + ".*"}, + }, + )) + + // No policy access: has tier GET but lacks permission on tier.networkpolicies. + // RBAC should deny because policy-level access is required. + addRoleAndBinding("no-policy", rbacNoPolicyUser, append(baseRules(), + rbacv1.PolicyRule{ + APIGroups: []string{"projectcalico.org"}, + Resources: []string{"tiers"}, + Verbs: []string{"get"}, + ResourceNames: []string{rbacTestTier}, + }, + )) + + // Other tier admin: full access but scoped to a different tier (rbacOtherTier). + // Should be able to create in rbacOtherTier but denied in rbacTestTier. + addRoleAndBinding("other-tier", rbacOtherTierUser, append(baseRules(), + rbacv1.PolicyRule{ + APIGroups: []string{"projectcalico.org"}, + Resources: []string{"tiers"}, + Verbs: []string{"get"}, + ResourceNames: []string{rbacOtherTier}, + }, + rbacv1.PolicyRule{ + APIGroups: []string{"projectcalico.org"}, + Resources: []string{"tier.networkpolicies", "tier.globalnetworkpolicies"}, + Verbs: []string{"create", "update", "delete", "get"}, + ResourceNames: []string{rbacOtherTier + ".*"}, + }, + )) + + // Read-only: has tier GET and read-only policy access (get/list/watch). + // Should be able to get/list/watch policies but not create, update, or delete. + addRoleAndBinding("read-only", rbacReadOnlyUser, []rbacv1.PolicyRule{ + { + APIGroups: []string{"projectcalico.org"}, + Resources: []string{"networkpolicies", "globalnetworkpolicies"}, + Verbs: []string{"get", "list", "watch"}, + }, + { + APIGroups: []string{"projectcalico.org"}, + Resources: []string{"tiers"}, + Verbs: []string{"get"}, + ResourceNames: []string{rbacTestTier}, + }, + { + APIGroups: []string{"projectcalico.org"}, + Resources: []string{"tier.networkpolicies", "tier.globalnetworkpolicies"}, + Verbs: []string{"get", "list", "watch"}, + ResourceNames: []string{rbacTestTier + ".*"}, + }, + }) + + return setup +} diff --git a/e2e/pkg/tests/policy/tiers.go b/e2e/pkg/tests/policy/tiers.go index ed728c82a27..5bdc004cc74 100644 --- a/e2e/pkg/tests/policy/tiers.go +++ b/e2e/pkg/tests/policy/tiers.go @@ -4,8 +4,9 @@ package policy import ( "context" - + //nolint:staticcheck // Ignore ST1001: should not use dot imports . "github.com/onsi/ginkgo/v2" + //nolint:staticcheck // Ignore ST1001: should not use dot imports . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" networkingv1 "k8s.io/api/networking/v1" diff --git a/e2e/pkg/utils/autodetection.go b/e2e/pkg/utils/autodetection.go index fe3d4e28fba..36eb84db241 100644 --- a/e2e/pkg/utils/autodetection.go +++ b/e2e/pkg/utils/autodetection.go @@ -16,24 +16,47 @@ package utils import ( "context" + "time" - . "github.com/onsi/gomega" + "github.com/onsi/gomega" v1 "github.com/tigera/operator/api/v1" + appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/kubernetes/test/e2e/framework" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" "github.com/projectcalico/calico/e2e/pkg/utils/client" ) +func CalicoNamespace(f *framework.Framework) string { + // Get calico-node pods. + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + podList, err := f.ClientSet.CoreV1().Pods(metav1.NamespaceAll).List( + ctx, + metav1.ListOptions{ + LabelSelector: labels.SelectorFromSet(map[string]string{"k8s-app": "calico-node"}).String(), + }, + ) + gomega.ExpectWithOffset(1, err).NotTo(gomega.HaveOccurred()) + gomega.ExpectWithOffset(1, len(podList.Items)).To(gomega.BeNumerically(">", 0)) + + pod := podList.Items[0] + return pod.Namespace +} + // ExpectedPodMTU returns the MTU that should be configured on pods, based on the Installation // resource. If no MTU is configured, returns nil. func ExpectedPodMTU(f *framework.Framework) *int32 { // Create a client to the API server. cli, err := client.New(f.ClientConfig()) - Expect(err).NotTo(HaveOccurred()) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) installs := &v1.InstallationList{} err = cli.List(context.TODO(), installs) - Expect(err).NotTo(HaveOccurred()) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) for _, inst := range installs.Items { if inst.Status.MTU > 0 { @@ -42,3 +65,12 @@ func ExpectedPodMTU(f *framework.Framework) *int32 { } return nil } + +func WhiskerInstalled(cli ctrlclient.Client) (bool, error) { + k := ctrlclient.ObjectKey{Name: "whisker", Namespace: "calico-system"} + err := cli.Get(context.TODO(), k, &appsv1.Deployment{}) + if errors.IsNotFound(err) { + return false, nil + } + return true, err +} diff --git a/e2e/pkg/utils/clean.go b/e2e/pkg/utils/clean.go index 798ee3010ad..79b53057c36 100644 --- a/e2e/pkg/utils/clean.go +++ b/e2e/pkg/utils/clean.go @@ -122,7 +122,7 @@ func CleanDatastore(cli client.Client) error { // errorRetry is a local helper for retrying a function on error. func errorRetry(desc string, f func() error) error { var err error - for i := 0; i < 5; i++ { + for range 5 { if err = f(); err != nil { logrus.WithError(err).Infof("Retrying function (%s) after error", desc) continue diff --git a/e2e/pkg/utils/client/calicoctl.go b/e2e/pkg/utils/client/calicoctl.go index 375669ef8ce..cd22e8d4616 100644 --- a/e2e/pkg/utils/client/calicoctl.go +++ b/e2e/pkg/utils/client/calicoctl.go @@ -41,17 +41,35 @@ func (c *calicoctlExecClient) Create(ctx context.Context, obj client.Object, opt return c.base.Create(ctx, obj, opts...) } + // calicoctl requires typemeta to be set for create operations, so set it here. + kind, err := c.kindFromObject(obj) + if err != nil { + return err + } + obj.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{ + Group: "projectcalico.org", + Version: "v3", + Kind: kind, + }) + // Create the stdin input for the calicoctl command. serializer := json.NewSerializer(json.DefaultMetaFactory, c.scheme, c.scheme, false) w := &strings.Builder{} - serializer.Encode(obj, w) + err = serializer.Encode(obj, w) + if err != nil { + return err + } // Create a calicoctl command to create the object. cmd := []string{"exec", "-i", c.name, "--", "calicoctl", "create", "-f", "-"} + logrus.WithFields(logrus.Fields{ + "data": w.String(), + }).Info("Executing calicoctl create command") + // Execute the command in the specified pod. - _, err := kubectl.RunKubectlInput(c.namespace, w.String(), cmd...) + _, err = kubectl.RunKubectlInput(c.namespace, w.String(), cmd...) if err != nil { return err } @@ -232,6 +250,10 @@ func (c *calicoctlExecClient) IsObjectNamespaced(obj runtime.Object) (bool, erro return c.base.IsObjectNamespaced(obj) } +func (c *calicoctlExecClient) Apply(ctx context.Context, obj runtime.ApplyConfiguration, opts ...client.ApplyOption) error { + panic("not implemented") +} + func (c *calicoctlExecClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { panic("not implemented") } diff --git a/e2e/pkg/utils/client/client.go b/e2e/pkg/utils/client/client.go index 88794296b02..c05127522ce 100644 --- a/e2e/pkg/utils/client/client.go +++ b/e2e/pkg/utils/client/client.go @@ -18,6 +18,9 @@ import ( v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/sirupsen/logrus" operatorv1 "github.com/tigera/operator/api/v1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/discovery" "k8s.io/client-go/rest" @@ -85,9 +88,22 @@ func newScheme() (*runtime.Scheme, error) { if err := v3.AddToScheme(scheme); err != nil { return nil, err } + + // Add operator APIs. if err := operatorv1.AddToScheme(scheme); err != nil { return nil, err } + + // Add core k8s APIs. + if err := networkingv1.AddToScheme(scheme); err != nil { + return nil, err + } + if err := appsv1.AddToScheme(scheme); err != nil { + return nil, err + } + if err := corev1.AddToScheme(scheme); err != nil { + return nil, err + } return scheme, nil } diff --git a/e2e/pkg/utils/conncheck/client.go b/e2e/pkg/utils/conncheck/client.go index 53b3104dbc0..4a9fe38b21a 100644 --- a/e2e/pkg/utils/conncheck/client.go +++ b/e2e/pkg/utils/conncheck/client.go @@ -42,11 +42,24 @@ func NewClient(id string, ns *v1.Namespace, opts ...ClientOption) *Client { } type Client struct { - name string - namespace *v1.Namespace - labels map[string]string - pod *v1.Pod - customizer func(pod *v1.Pod) + name string + namespace *v1.Namespace + labels map[string]string + pod *v1.Pod + customizers []func(pod *v1.Pod) +} + +// composedCustomizer returns a single customizer function that applies all +// registered customizers in order, or nil if none are registered. +func (c *Client) composedCustomizer() func(*v1.Pod) { + if len(c.customizers) == 0 { + return nil + } + return func(pod *v1.Pod) { + for _, fn := range c.customizers { + fn(pod) + } + } } func (c *Client) ID() string { @@ -76,7 +89,7 @@ func WithClientLabels(labels map[string]string) ClientOption { func WithClientCustomizer(customizer func(pod *v1.Pod)) ClientOption { return func(c *Client) error { - c.customizer = customizer + c.customizers = append(c.customizers, customizer) return nil } } diff --git a/e2e/pkg/utils/conncheck/conncheck.go b/e2e/pkg/utils/conncheck/conncheck.go index a3ef8df6ef6..a77a760ffdf 100644 --- a/e2e/pkg/utils/conncheck/conncheck.go +++ b/e2e/pkg/utils/conncheck/conncheck.go @@ -18,14 +18,14 @@ import ( "context" "fmt" "maps" - "math/rand" "strings" + "sync" "time" + //nolint:staticcheck // Ignore ST1001: should not use dot imports . "github.com/onsi/ginkgo/v2" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/sirupsen/logrus" - corev1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/kubernetes/test/e2e/framework" @@ -33,6 +33,7 @@ import ( e2epod "k8s.io/kubernetes/test/e2e/framework/pod" e2eoutput "k8s.io/kubernetes/test/e2e/framework/pod/output" + "github.com/projectcalico/calico/e2e/pkg/utils" "github.com/projectcalico/calico/e2e/pkg/utils/client" "github.com/projectcalico/calico/e2e/pkg/utils/images" "github.com/projectcalico/calico/e2e/pkg/utils/remotecluster" @@ -40,9 +41,9 @@ import ( ) const ( - maxNameLength = 63 - randomLength = 5 - maxGeneratedNameLength = maxNameLength - randomLength + roleLabel = "e2e.projectcalico.org/role" + roleClient = "client" + roleServer = "server" ) type connectionTester struct { @@ -54,13 +55,27 @@ type connectionTester struct { } type ConnectionTester interface { + // Methods for setup and teardown. AddClient(client *Client) AddServer(server *Server) Deploy() + Stop() + + // Methods for one-shot execution. ExpectSuccess(client *Client, targets ...Target) ExpectFailure(client *Client, targets ...Target) Execute() ResetExpectations() + + // Methods for continuous execution. + ExpectContinuously(client *Client, targets ...Target) Checkpointer +} + +// Checkpointer provides a way to checkpoint continuous connection tests at specific points in time +// during a test to verify that all connections are as expected up to that point. +type Checkpointer interface { + ExpectSuccess(msg string) + ExpectFailure(msg string) Stop() } @@ -94,7 +109,7 @@ func (c *connectionTester) deploy() error { continue } By(fmt.Sprintf("Deploying client pod %s/%s", client.namespace.Name, client.name)) - pod, err := createClientPod(c.f, client.namespace, client.name, client.labels, client.customizer) + pod, err := createClientPod(c.f, client.namespace, client.name, client.labels, client.composedCustomizer()) if err != nil { return err } @@ -112,8 +127,9 @@ func (c *connectionTester) deploy() error { server.name, server.ports, server.labels, - server.podCustomizer, - server.svcCustomizer, + server.composedPodCustomizer(), + server.composedSvcCustomizer(), + server.autoCreateSvc, ) server.pod = pod server.service = svc @@ -121,10 +137,16 @@ func (c *connectionTester) deploy() error { // Wait for all pods to be running. By("Waiting for all pods in the connection checker to be running") - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) + timeout := 1 * time.Minute + if windows.ClusterIsWindows() { + // Windows images are very large (sometimes 2GB+), so this needs a considerably + // longer timeout in order to allow them to be pulled + timeout = 15 * time.Minute + } + ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() for _, client := range c.clients { - err := e2epod.WaitTimeoutForPodRunningInNamespace(ctx, c.f.ClientSet, client.pod.Name, client.pod.Namespace, 1*time.Minute) + err := e2epod.WaitTimeoutForPodRunningInNamespace(ctx, c.f.ClientSet, client.pod.Name, client.pod.Namespace, timeout) if err != nil { return err } @@ -136,7 +158,7 @@ func (c *connectionTester) deploy() error { client.pod = p } for _, server := range c.servers { - err := e2epod.WaitTimeoutForPodRunningInNamespace(ctx, c.f.ClientSet, server.pod.Name, server.pod.Namespace, 1*time.Minute) + err := e2epod.WaitTimeoutForPodRunningInNamespace(ctx, c.f.ClientSet, server.pod.Name, server.pod.Namespace, timeout) if err != nil { return err } @@ -171,14 +193,17 @@ func (c *connectionTester) stop() error { } for _, server := range c.servers { - By(fmt.Sprintf("Deleting server pod %s and service %s", server.pod.Name, server.service.Name)) + By(fmt.Sprintf("Deleting server pod %s", server.pod.Name)) err := c.f.ClientSet.CoreV1().Pods(server.namespace.Name).Delete(ctx, server.pod.Name, metav1.DeleteOptions{}) if err != nil { return err } - err = c.f.ClientSet.CoreV1().Services(server.namespace.Name).Delete(ctx, server.service.Name, metav1.DeleteOptions{}) - if err != nil { - return err + if server.service != nil { + By(fmt.Sprintf("Deleting server service %s", server.service.Name)) + err = c.f.ClientSet.CoreV1().Services(server.namespace.Name).Delete(ctx, server.service.Name, metav1.DeleteOptions{}) + if err != nil { + return err + } } } @@ -202,7 +227,7 @@ func (c *connectionTester) stop() error { func (c *connectionTester) expectationsTested() error { for _, exp := range c.expectations { if !exp.executed { - return fmt.Errorf("Expected connection %s was not executed. Did you call Execute()?", exp.Description) + return fmt.Errorf("expected connection %s was not executed. Did you call Execute()?", exp.Description) } } return nil @@ -281,6 +306,21 @@ func (c *connectionTester) expectSuccess(client *Client, target Target) { c.expectations[e.String()] = e } +// ExpectContinuously starts continuous connection tests from the given client to the given targets. +// It returns a Checkpointer that can be used to checkpoint and verify the results at specific points in time. +// Callers must call Stop() on the returned Checkpointer when done. +func (c *connectionTester) ExpectContinuously(client *Client, targets ...Target) Checkpointer { + checkpointer := &checkpointer{ + results: make(chan connectionResult, 1000), + done: make(chan struct{}), + client: client, + targets: targets, + tester: c, + } + checkpointer.start() + return checkpointer +} + func (c *connectionTester) ExpectFailure(client *Client, targets ...Target) { for _, target := range targets { c.expectFailure(client, target) @@ -314,11 +354,19 @@ func (c *connectionTester) Execute() { framework.Fail("Execute() called before Deploy()", 1) } By(fmt.Sprintf("Testing %d connections in parallel", len(c.expectations))) + + // Channel to collect results. resultChan := make(chan connectionResult, len(c.expectations)) + // Context to control overall timeout for all connections. After it times out, we'll forcefully + // terminate any remaining connections. This avoids deadlocking the test waiting for results if + // something goes wrong. + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + // Launch all of the connections in parallel. We'll wait for them all to finish at the end and report on success / failure. for _, expectation := range c.expectations { - go c.runConnection(expectation, resultChan) + go c.runConnection(ctx, expectation, resultChan) } // Wait for all of the connections to finish. @@ -333,6 +381,10 @@ func (c *connectionTester) Execute() { } } + // Only close the result channel after all connections have finished to avoid + // connection goroutines attempting to write to a closed channel. + close(resultChan) + // If we had any errors, log out the per-test diags and then fail the test. if failed { logDiagsForNamespace(c.f, c.f.Namespace) @@ -341,7 +393,7 @@ func (c *connectionTester) Execute() { } } -func (c *connectionTester) runConnection(exp *Expectation, results chan<- connectionResult) { +func (c *connectionTester) runConnection(ctx context.Context, exp *Expectation, results chan<- connectionResult) { // Exec into the client pod and try to connect to the server. cmd := c.command(exp.Target) @@ -354,18 +406,19 @@ func (c *connectionTester) runConnection(exp *Expectation, results chan<- connec // First attempt. out, result, err := c.execCommandInPod(exp.Client, cmd) + if err != nil { + logCtx.WithError(err).Warn("Connection attempt returned an error") + } - // Retry up to 10 seconds if we didn't get the expected result. This helps to avoid flakes due to transient issues. + // Retry until the context expires if we didn't get the expected result. This helps to avoid flakes due to transient issues. // We don't want to retry too many times, as that could mask real issues and slow down tests. However, we know that // especially on CPU constrained environments such as CI, there can be a delay between making changes (e.g., applying a NetworkPolicy) and // those changes taking effect. So a short retry loop is helpful. - timeout := time.After(10 * time.Second) -loop: - for result != exp.ExpectedResult { +loop: + for err != nil || result != exp.ExpectedResult { select { - case <-timeout: - // Timed out. + case <-ctx.Done(): break loop default: // Not timed out yet. @@ -378,7 +431,11 @@ loop: }).Warn("Connection attempt did not get expected result. Retrying...") time.Sleep(1 * time.Second) + out, result, err = c.execCommandInPod(exp.Client, cmd) + if err != nil { + logCtx.WithError(err).Warn("Connection attempt returned an error") + } } logCtx.WithFields(logrus.Fields{ @@ -400,7 +457,7 @@ loop: // Connect runs a one-shot connection attempt from the client pod to the given target, and returns the output of the command. func (c *connectionTester) Connect(client *Client, target Target) (string, error) { if c.clients[client.ID()] == nil { - return "", fmt.Errorf("Test bug: client %s not registered with connection tester. AddClient()?", client.ID()) + return "", fmt.Errorf("test bug: client %s not registered with connection tester. AddClient()?", client.ID()) } if c.clients[client.ID()].pod == nil { return "", fmt.Errorf("Client %s has no running pod. Did you Deploy()?", client.ID()) @@ -439,9 +496,7 @@ func (c *connectionTester) command(t Target) string { // Windows. switch t.GetProtocol() { case TCP: - cmd = fmt.Sprintf("$sb={Invoke-WebRequest %s -UseBasicParsing -TimeoutSec 3 -DisableKeepAlive}; "+ - "For ($i=0; $i -lt 5; $i++) { sleep 5; "+ - "try {& $sb} catch { echo failed loop $i ; continue }; exit 0 ; }; exit 1", t.Destination()) + cmd = fmt.Sprintf("Invoke-WebRequest %s -UseBasicParsing -TimeoutSec 5 -DisableKeepAlive -ErrorAction Stop | Out-Null", t.Destination()) case ICMP: cmd = fmt.Sprintf("Test-Connection -Count 5 -ComputerName %s", t.Destination()) default: @@ -501,7 +556,8 @@ func (r *connectionResult) Failed() bool { func buildFailureMessage(results []connectionResult) string { // Builed an error message. - msg := "One or more connection tests failed:\n" + var msg strings.Builder + msg.WriteString("One or more connection tests failed:\n") // Add expected results. for _, res := range results { @@ -511,7 +567,7 @@ func buildFailureMessage(results []connectionResult) string { if exp != actual { status = "ERROR" } - msg += fmt.Sprintf( + msg.WriteString(fmt.Sprintf( "\n%s: %s/%s -> %s (%s, %s, %s); Expected=%s, Actual=%s", status, res.clientPod.Namespace, res.clientPod.Name, @@ -521,10 +577,10 @@ func buildFailureMessage(results []connectionResult) string { res.target.AccessType(), exp, actual, - ) + )) } - msg += "\n" - return msg + msg.WriteString("\n") + return msg.String() } // createClientPod creates a long lived pod that sleeps, and can be used to execute connection tests as a client. @@ -535,12 +591,12 @@ func createClientPod(f *framework.Framework, namespace *v1.Namespace, baseName s nodeselector := map[string]string{} // Randomize pod names to avoid clashes with previous tests. - podName := GenerateRandomName(baseName) + podName := utils.GenerateRandomName(baseName) if windows.ClusterIsWindows() { image = images.WindowsClientImage() command = []string{"powershell.exe"} - args = []string{"Start-Sleep", "600"} + args = []string{"Start-Sleep", "3600"} nodeselector["kubernetes.io/os"] = "windows" } else { image = images.Alpine @@ -553,6 +609,7 @@ func createClientPod(f *framework.Framework, namespace *v1.Namespace, baseName s mergedLabels := make(map[string]string) maps.Copy(mergedLabels, labels) mergedLabels["pod-name"] = baseName + mergedLabels[roleLabel] = roleClient // Create the pod. zero := int64(0) @@ -575,11 +632,11 @@ func createClientPod(f *framework.Framework, namespace *v1.Namespace, baseName s }, }, Tolerations: []v1.Toleration{ - corev1.Toleration{ + v1.Toleration{ Key: "kubernetes.io/arch", - Operator: corev1.TolerationOpEqual, + Operator: v1.TolerationOpEqual, Value: "arm64", - Effect: corev1.TaintEffectNoSchedule, + Effect: v1.TaintEffectNoSchedule, }, }, }, @@ -622,12 +679,6 @@ func logDiagsForConnection(f *framework.Framework, res connectionResult) { logrus.WithError(err).Error("Error getting pod description") } logrus.Infof("[DIAGS] Pod %s/%s describe:\n%s", res.clientPod.Namespace, res.clientPod.Name, podDesc) - - // // Collect/log Calico diags. - // err = calico.LogCalicoDiagsForPodNode(f, res.clientPod.Namespace, res.clientPod.Name) - // if err != nil { - // logrus.WithError(err).Error("Error getting Calico diags") - // } } func logDiagsForNamespace(f *framework.Framework, ns *v1.Namespace) { @@ -694,24 +745,122 @@ func logDiagsForNamespace(f *framework.Framework, ns *v1.Namespace) { e2eoutput.DumpDebugInfo(context.Background(), f.ClientSet, f.Namespace.Name) } -func GenerateRandomName(base string) string { - if len(base) > maxGeneratedNameLength { - base = base[:maxGeneratedNameLength] +// ExecInPod executes a kubectl command in a pod. Returns the response as a string, or an error upon failure. +func ExecInPod(pod *v1.Pod, sh, opt, cmd string) (string, error) { + args := []string{"exec", pod.Name, "--", sh, opt, cmd} + return kubectl.NewKubectlCommand(pod.Namespace, args...). + WithTimeout(time.After(10 * time.Second)). + Exec() +} + +// checkpointer implements the Checkpointer interface. +// It runs continuous connection checks in the background, and allows +// the test to checkpoint and verify the results at specific points in time. +type checkpointer struct { + results chan connectionResult + done chan struct{} + client *Client + targets []Target + routinesDone sync.WaitGroup + expectationInProgress sync.WaitGroup + requestsInProgress sync.WaitGroup + tester *connectionTester +} + +func (c *checkpointer) Stop() { + close(c.done) + c.routinesDone.Wait() + close(c.results) +} + +func (c *checkpointer) ExpectSuccess(reason string) { + c.expect(reason, false) +} + +func (c *checkpointer) ExpectFailure(reason string) { + c.expect(reason, true) +} + +func (c *checkpointer) start() { + // Start goroutines running continuous checks for each target. + for _, target := range c.targets { + c.routinesDone.Add(1) + + go func(t Target) { + for { + select { + case <-c.done: + // Stop the goroutine, and mark it as done. + c.routinesDone.Done() + return + default: + } + + // Block if we are currently performing an expectation check. + c.expectationInProgress.Wait() + + c.requestsInProgress.Add(1) + _, result, err := c.tester.execCommandInPod(c.tester.clients[c.client.ID()].pod, c.tester.command(t)) + if err != nil { + logrus.WithError(err).Warn("Continuous connection attempt returned an error") + } + + c.results <- connectionResult{ + clientPod: c.tester.clients[c.client.ID()].pod, + target: t, + expected: Success, + actual: result, + err: err, + } + c.requestsInProgress.Done() + time.Sleep(10 * time.Millisecond) + } + }(target) } - return fmt.Sprintf("%s-%s", base, randomString(randomLength)) } -func randomString(length int) string { - // Generate a random string of the specified length. - const charset = "abcdefghijklmnopqrstuvwxyz0123456789" - b := make([]byte, length) - for i := range b { - b[i] = charset[rand.Intn(len(charset))] +func (c *checkpointer) expect(reason string, expectFailure bool) { + By(fmt.Sprintf("Checking continuous connection results %s", reason)) + + checkResult := func(res connectionResult) string { + if !expectFailure && res.Failed() { + return fmt.Sprintf("Continuous connection check failure %s\n\n%s", reason, buildFailureMessage([]connectionResult{res})) + } else if expectFailure && !res.Failed() { + return fmt.Sprintf("Continuous connection check unexpectedly succeeded %s\n\n%s", reason, buildFailureMessage([]connectionResult{res})) + } + return "" } - return string(b) -} -// ExecInPod executes a kubectl command in a pod. Returns the response as a string, or an error upon failure. -func ExecInPod(pod *v1.Pod, sh, opt, cmd string) (string, error) { - return kubectl.RunKubectl(pod.Namespace, "exec", pod.Name, "--", sh, opt, cmd) + // Wait for at least one result to be available. + select { + case res := <-c.results: + if msg := checkResult(res); msg != "" { + framework.Fail(msg, 2) + } + case <-time.After(10 * time.Second): + framework.Fail("Timeout waiting for continuous connection results", 2) + } + + // Block new connection attempts, and wait for any in-progress attempts to complete before + // draining the results channel. This ensures we have a consistent view of all connection attempts + // up to this point in time, and prevents possible races where new attempts are started while we are + // checking results. + c.expectationInProgress.Add(1) + defer c.expectationInProgress.Done() + + // Wait for any in-progress requests to complete. + c.requestsInProgress.Wait() + + // Drain the results channel and log any failures. + for { + select { + case res := <-c.results: + if msg := checkResult(res); msg != "" { + framework.Fail(msg, 2) + } + default: + // No more results to process. + return + } + } } diff --git a/e2e/pkg/utils/conncheck/customizers.go b/e2e/pkg/utils/conncheck/customizers.go new file mode 100644 index 00000000000..3df0c8786b9 --- /dev/null +++ b/e2e/pkg/utils/conncheck/customizers.go @@ -0,0 +1,57 @@ +package conncheck + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// CombineCustomizers is a meta customizer that applies multiple Pod customizers +// to the Pod spec. +func CombineCustomizers(customizers ...func(*corev1.Pod)) func(*corev1.Pod) { + return func(pod *corev1.Pod) { + for _, customizer := range customizers { + customizer(pod) + } + } +} + +func UseV4IPPool(poolName string) func(*corev1.Pod) { + return func(pod *corev1.Pod) { + if pod.Annotations == nil { + pod.Annotations = map[string]string{} + } + pod.Annotations["cni.projectcalico.org/ipv4pools"] = fmt.Sprintf(`["%s"]`, poolName) + } +} + +// AvoidEachOther is a Pod customizer that adds PodAntiAffinity rules to avoid +// scheduling the Pod on the same node as other Pods deployed with this customizer. +func AvoidEachOther(pod *corev1.Pod) { + // Include a label which we can use in the anti-affinity rule. + if pod.Labels == nil { + pod.Labels = map[string]string{} + } + pod.Labels["e2e.projectcalico.org/anti-affinity"] = "true" + + // Add the PodAntiAffinity rule to make sure Pods are scheduled on different nodes. + pod.Spec.Affinity = &corev1.Affinity{ + PodAntiAffinity: &corev1.PodAntiAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{ + { + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "e2e.projectcalico.org/anti-affinity", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"true"}, + }, + }, + }, + TopologyKey: "kubernetes.io/hostname", + }, + }, + }, + } +} diff --git a/e2e/pkg/utils/conncheck/k8s.go b/e2e/pkg/utils/conncheck/k8s.go index 14a12b7a33c..b807aa9eb49 100644 --- a/e2e/pkg/utils/conncheck/k8s.go +++ b/e2e/pkg/utils/conncheck/k8s.go @@ -20,10 +20,9 @@ import ( "maps" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/sirupsen/logrus" - corev1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -34,7 +33,7 @@ import ( "github.com/projectcalico/calico/e2e/pkg/utils/windows" ) -func CreateServerPodAndServiceX(f *framework.Framework, namespace *v1.Namespace, podName string, ports []int, labels map[string]string, podCustomizer func(pod *v1.Pod), serviceCustomizer func(svc *v1.Service)) (*v1.Pod, *v1.Service) { +func CreateServerPodAndServiceX(f *framework.Framework, namespace *v1.Namespace, podName string, ports []int, labels map[string]string, podCustomizer func(pod *v1.Pod), serviceCustomizer func(svc *v1.Service), autoCreateSvc bool) (*v1.Pod, *v1.Service) { // Because we have a variable amount of ports, we'll first loop through and generate our Containers for our pod, // and ServicePorts.for our Service. var image string @@ -51,7 +50,18 @@ func CreateServerPodAndServiceX(f *framework.Framework, namespace *v1.Namespace, } for _, port := range ports { args := []string{} - if !windows.ClusterIsWindows() { + env := []v1.EnvVar{} + + if windows.ClusterIsWindows() { + env = []v1.EnvVar{ + { + // porter (the windows server pod) uses the port value from the + // env var name, it doesn't care about the env var value here + Name: fmt.Sprintf("SERVE_PORT_%d", port), + Value: "value-not-used", + }, + } + } else { args = []string{fmt.Sprintf("--port=%d", port)} } @@ -61,6 +71,7 @@ func CreateServerPodAndServiceX(f *framework.Framework, namespace *v1.Namespace, Image: image, ImagePullPolicy: v1.PullIfNotPresent, Args: args, + Env: env, Ports: []v1.ContainerPort{ { ContainerPort: int32(port), @@ -91,8 +102,9 @@ func CreateServerPodAndServiceX(f *framework.Framework, namespace *v1.Namespace, newLabels := make(map[string]string) maps.Copy(newLabels, labels) newLabels["pod-name"] = podName + newLabels[roleLabel] = roleServer - By(fmt.Sprintf("Creating a server pod %s in namespace %s", podName, namespace.Name)) + ginkgo.By(fmt.Sprintf("Creating a server pod %s in namespace %s", podName, namespace.Name)) pod := &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: podName, @@ -103,11 +115,11 @@ func CreateServerPodAndServiceX(f *framework.Framework, namespace *v1.Namespace, RestartPolicy: v1.RestartPolicyNever, NodeSelector: nodeselector, Tolerations: []v1.Toleration{ - corev1.Toleration{ + v1.Toleration{ Key: "kubernetes.io/arch", - Operator: corev1.TolerationOpEqual, + Operator: v1.TolerationOpEqual, Value: "arm64", - Effect: corev1.TaintEffectNoSchedule, + Effect: v1.TaintEffectNoSchedule, }, }, }, @@ -119,11 +131,17 @@ func CreateServerPodAndServiceX(f *framework.Framework, namespace *v1.Namespace, ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() pod, err := f.ClientSet.CoreV1().Pods(namespace.Name).Create(ctx, pod, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - logrus.Infof("Created pod %v", pod.ObjectMeta.Name) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + logrus.Infof("Created pod %v", pod.Name) + + // Only create service if autoCreateSvc is true + if !autoCreateSvc { + // Return the pod with nil service when service creation is disabled + return pod, nil + } svcName := fmt.Sprintf("svc-%s", podName) - By(fmt.Sprintf("Creating a service %s for pod %s in namespace %s", svcName, podName, namespace.Name)) + ginkgo.By(fmt.Sprintf("Creating a service %s for pod %s in namespace %s", svcName, podName, namespace.Name)) v4Svc := &v1.Service{ ObjectMeta: metav1.ObjectMeta{Name: svcName}, Spec: v1.ServiceSpec{ @@ -138,7 +156,7 @@ func CreateServerPodAndServiceX(f *framework.Framework, namespace *v1.Namespace, ctx, cancel = context.WithTimeout(context.Background(), 30*time.Second) defer cancel() v4Svc, err = f.ClientSet.CoreV1().Services(namespace.Name).Create(ctx, v4Svc, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) if !pod.Spec.HostNetwork { // Create an ipv6 service for the pod instead, if the cluster supports v6. @@ -162,14 +180,14 @@ func CreateServerPodAndServiceX(f *framework.Framework, namespace *v1.Namespace, ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - By(fmt.Sprintf("Creating a service %s for pod %s in namespace %s", svcName, podName, namespace.Name)) + ginkgo.By(fmt.Sprintf("Creating a service %s for pod %s in namespace %s", svcName, podName, namespace.Name)) v6Svc, err := f.ClientSet.CoreV1().Services(namespace.Name).Create(ctx, svc, metav1.CreateOptions{}) if err == nil { // IPv6 is supported - return the dual stack service. return pod, v6Svc } else if !kerrors.IsInvalid(err) { // An error other than 422 Invalid is an actual error. - Expect(err).NotTo(HaveOccurred(), "Error creating IPv6 service") + gomega.Expect(err).NotTo(gomega.HaveOccurred(), "Error creating IPv6 service") } else { // If v6 is not enabled on the cluster, we will receive an "Invalid" error type. In this case, // fall through and return the v4 service. diff --git a/e2e/pkg/utils/conncheck/server.go b/e2e/pkg/utils/conncheck/server.go index f881c48163c..549aa582e34 100644 --- a/e2e/pkg/utils/conncheck/server.go +++ b/e2e/pkg/utils/conncheck/server.go @@ -16,6 +16,7 @@ package conncheck import ( "fmt" + "strings" v1 "k8s.io/api/core/v1" "k8s.io/kubernetes/test/e2e/framework" @@ -31,9 +32,10 @@ func NewServer(name string, ns *v1.Namespace, opts ...ServerOption) *Server { framework.Fail(msg, 1) } s := &Server{ - name: name, - namespace: ns, - ports: []int{80}, + name: name, + namespace: ns, + ports: []int{80}, + autoCreateSvc: true, } for _, opt := range opts { _ = opt(s) @@ -42,14 +44,41 @@ func NewServer(name string, ns *v1.Namespace, opts ...ServerOption) *Server { } type Server struct { - name string - namespace *v1.Namespace - ports []int - labels map[string]string - pod *v1.Pod - service *v1.Service - podCustomizer func(*v1.Pod) - svcCustomizer func(*v1.Service) + name string + namespace *v1.Namespace + ports []int + labels map[string]string + pod *v1.Pod + service *v1.Service + podCustomizers []func(*v1.Pod) + svcCustomizers []func(*v1.Service) + autoCreateSvc bool +} + +// composedPodCustomizer returns a single customizer function that applies all +// registered pod customizers in order, or nil if none are registered. +func (s *Server) composedPodCustomizer() func(*v1.Pod) { + if len(s.podCustomizers) == 0 { + return nil + } + return func(pod *v1.Pod) { + for _, c := range s.podCustomizers { + c(pod) + } + } +} + +// composedSvcCustomizer returns a single customizer function that applies all +// registered service customizers in order, or nil if none are registered. +func (s *Server) composedSvcCustomizer() func(*v1.Service) { + if len(s.svcCustomizers) == 0 { + return nil + } + return func(svc *v1.Service) { + for _, c := range s.svcCustomizers { + c(svc) + } + } } func (s *Server) ID() string { @@ -115,6 +144,52 @@ func (s *Server) ClusterIP(opts ...TargetOption) Target { return t } +func (s *Server) ClusterIPv4(opts ...TargetOption) Target { + for _, ip := range s.Service().Spec.ClusterIPs { + if strings.Contains(ip, ":") { + continue + } + t := &target{ + server: s, + targetType: TypeClusterIP, + destination: ip, + protocol: TCP, + } + for _, opt := range opts { + if err := opt(t); err != nil { + framework.ExpectNoError(err) + } + } + return t + } + msg := fmt.Sprintf("No IPv4 ClusterIP found for server %s/%s", s.namespace.Name, s.name) + framework.Fail(msg, 1) + return nil +} + +func (s *Server) ClusterIPv6(opts ...TargetOption) Target { + for _, ip := range s.Service().Spec.ClusterIPs { + if !strings.Contains(ip, ":") { + continue + } + t := &target{ + server: s, + targetType: TypeClusterIP, + destination: ip, + protocol: TCP, + } + for _, opt := range opts { + if err := opt(t); err != nil { + framework.ExpectNoError(err) + } + } + return t + } + msg := fmt.Sprintf("No IPv6 ClusterIP found for server %s/%s", s.namespace.Name, s.name) + framework.Fail(msg, 1) + return nil +} + // HostPorts returns a list of targets that can be used to connect to the pod's host IPs on the given port. // It returns a target for each of the pod's host IPs, at the specified port. func (s *Server) HostPorts(port int) []Target { @@ -191,32 +266,23 @@ func WithServerLabels(labels map[string]string) ServerOption { func WithHostNetworking() ServerOption { return func(c *Server) error { - if c.podCustomizer != nil { - return fmt.Errorf("Customizer already set") - } - c.podCustomizer = func(pod *v1.Pod) { + c.podCustomizers = append(c.podCustomizers, func(pod *v1.Pod) { pod.Spec.HostNetwork = true - } + }) return nil } } func WithServerPodCustomizer(customizer func(*v1.Pod)) ServerOption { return func(c *Server) error { - if c.podCustomizer != nil { - return fmt.Errorf("Pod customizer already set") - } - c.podCustomizer = customizer + c.podCustomizers = append(c.podCustomizers, customizer) return nil } } func WithServerSvcCustomizer(customizer func(*v1.Service)) ServerOption { return func(c *Server) error { - if c.svcCustomizer != nil { - return fmt.Errorf("Service customizer already set") - } - c.svcCustomizer = customizer + c.svcCustomizers = append(c.svcCustomizers, customizer) return nil } } @@ -227,3 +293,10 @@ func WithPorts(ports ...int) ServerOption { return nil } } + +func WithAutoCreateService(autoCreate bool) ServerOption { + return func(c *Server) error { + c.autoCreateSvc = autoCreate + return nil + } +} diff --git a/e2e/pkg/utils/conncheck/target.go b/e2e/pkg/utils/conncheck/target.go index 26def2e9faa..52b6fcde29e 100644 --- a/e2e/pkg/utils/conncheck/target.go +++ b/e2e/pkg/utils/conncheck/target.go @@ -111,7 +111,7 @@ func (t *target) String() string { } if t.http != nil { sha := sha1.New() - sha.Write([]byte(fmt.Sprintf("%s:%s %v", t.http.Method, t.http.Path, t.http.Headers))) + _, _ = fmt.Fprintf(sha, "%s:%s %v", t.http.Method, t.http.Path, t.http.Headers) chunks = append(chunks, base64.URLEncoding.EncodeToString(sha.Sum(nil))[:7]) } return strings.Join(chunks, ":") diff --git a/e2e/pkg/utils/externalnode/client.go b/e2e/pkg/utils/externalnode/client.go index 20b98409b84..9f0175e26fd 100644 --- a/e2e/pkg/utils/externalnode/client.go +++ b/e2e/pkg/utils/externalnode/client.go @@ -10,6 +10,7 @@ import ( "sync" "time" + //nolint:staticcheck // Ignore ST1001: should not use dot imports . "github.com/onsi/gomega" "github.com/sirupsen/logrus" @@ -17,7 +18,7 @@ import ( "github.com/projectcalico/calico/e2e/pkg/utils/images" ) -type externalNodeClient struct { +type Client struct { lock sync.Mutex extIP string @@ -26,14 +27,14 @@ type externalNodeClient struct { intIPs []string } -// NewExternalNodeClient reads external node details from e2ecfg first. When configs are not available, +// NewClient reads external node details from e2ecfg first. When configs are not available, // it will parse information from a predefined list of environment variables. -func NewExternalNodeClient() *externalNodeClient { +func NewClient() *Client { if config.ExtNodeIP() == "" || config.ExtNodeSSHKey() == "" || config.ExtNodeUsername() == "" { logrus.Debug("External node details unavailable") return nil } - client := &externalNodeClient{ + client := &Client{ extIP: config.ExtNodeIP(), extKey: config.ExtNodeSSHKey(), extUser: config.ExtNodeUsername(), @@ -41,15 +42,15 @@ func NewExternalNodeClient() *externalNodeClient { return client } -func NewExternalNodeClientManualConfig(ip, key, user string) *externalNodeClient { - return &externalNodeClient{extIP: ip, extKey: key, extUser: user} +func NewClientManualConfig(ip, key, user string) *Client { + return &Client{extIP: ip, extKey: key, extUser: user} } -func (e *externalNodeClient) IP() string { +func (e *Client) IP() string { return e.IPs()[0] } -func (e *externalNodeClient) IPs() []string { +func (e *Client) IPs() []string { e.lock.Lock() defer e.lock.Unlock() @@ -89,18 +90,18 @@ func (e *externalNodeClient) IPs() []string { return e.intIPs } -func (e *externalNodeClient) MustExec(shell, opt, cmd string) string { +func (e *Client) MustExec(shell, opt, cmd string) string { out, err := e.Exec(shell, opt, cmd) ExpectWithOffset(1, err).NotTo(HaveOccurred(), fmt.Sprintf( "failed to execute command %q %q %q on external node: %s", shell, opt, cmd, err)) return out } -func (e *externalNodeClient) Exec(shell, opt, cmd string) (string, error) { +func (e *Client) Exec(shell, opt, cmd string) (string, error) { return e.ExecTimeout(5, shell, opt, cmd) } -func (e *externalNodeClient) ExecTimeout(timeoutSecs int, shell, opt, cmd string) (string, error) { +func (e *Client) ExecTimeout(timeoutSecs int, shell, opt, cmd string) (string, error) { dest := fmt.Sprintf("%v@%v", e.extUser, e.extIP) command := exec.Command( "timeout", fmt.Sprint(timeoutSecs+10), @@ -123,7 +124,7 @@ func (e *externalNodeClient) ExecTimeout(timeoutSecs int, shell, opt, cmd string return outstr, err } -func (e *externalNodeClient) Get(target string, length int) string { +func (e *Client) Get(target string, length int) string { // This is actually just trying to run something on the external node like: // curl -m2 -w "\n%{time_total}" http://172.16.101.14:32517/length/20 // So 2 special things needed here: @@ -134,11 +135,11 @@ func (e *externalNodeClient) Get(target string, length int) string { return fmt.Sprintf(`curl -m2 -w \"\n%%{time_total}\" http://%v/length/%v`, target, length) } -func (e *externalNodeClient) Post(target string, postdata string) string { +func (e *Client) Post(target string, postdata string) string { return fmt.Sprintf(`curl -m2 -w \"\n%%{time_total}\" -d "%v" -X POST http://%v/post`, postdata, target) } -func (e *externalNodeClient) UDP(target string, postdata string) string { +func (e *Client) UDP(target string, postdata string) string { // If target[0] != '[', this is an ipv4 address. if target[0] != '[' { target = strings.ReplaceAll(target, ":", " ") @@ -154,7 +155,7 @@ func (e *externalNodeClient) UDP(target string, postdata string) string { return fmt.Sprintf(`echo %v | nc -6 -u -w1 %v`, postdata, target) } -func (e *externalNodeClient) TestCanConnect(target string) { +func (e *Client) TestCanConnect(target string) { command := fmt.Sprintf(`curl -s -m2 %v`, target) tryConnect := func() error { _, err := e.Exec("sh", "-c", command) @@ -172,7 +173,7 @@ func (e *externalNodeClient) TestCanConnect(target string) { Expect(err).NotTo(HaveOccurred()) } -func (e *externalNodeClient) TestCannotConnect(target string) { +func (e *Client) TestCannotConnect(target string) { command := fmt.Sprintf(`curl -s -m2 %v`, target) tryConnect := func() error { _, err := e.Exec("sh", "-c", command) @@ -190,15 +191,15 @@ func (e *externalNodeClient) TestCannotConnect(target string) { Expect(err).To(HaveOccurred()) } -func (e *externalNodeClient) SetupIperf() { +func (e *Client) SetupIperf() { shell := "/bin/sh" opt := "-c" - cmd := fmt.Sprintf("sudo docker run --rm -d --network host --name iperf %s", images.Iperf) + cmd := fmt.Sprintf("sudo docker run --rm -d --network host --name iperf %s", images.Agnhost) _, err := e.Exec(shell, opt, cmd) Expect(err).NotTo(HaveOccurred()) } -func (e *externalNodeClient) CleanupIperf() { +func (e *Client) CleanupIperf() { shell := "/bin/sh" opt := "-c" cmd := "sudo docker stop iperf" @@ -206,7 +207,7 @@ func (e *externalNodeClient) CleanupIperf() { Expect(err).NotTo(HaveOccurred()) } -func (e *externalNodeClient) RunIperfCmd(iperfCmd string, timeoutSecs int) string { +func (e *Client) RunIperfCmd(iperfCmd string, timeoutSecs int) string { shell := "/bin/sh" opt := "-c" cmd := fmt.Sprintf("sudo docker exec -t iperf %s", iperfCmd) @@ -216,7 +217,7 @@ func (e *externalNodeClient) RunIperfCmd(iperfCmd string, timeoutSecs int) strin return output } -func (e *externalNodeClient) TestCalicoServiceReady(service string) error { +func (e *Client) TestCalicoServiceReady(service string) error { // Wait for the service to be active. cmd := fmt.Sprintf("systemctl is-active %s.service", service) output, err := e.Exec("/bin/sh", "-c", cmd) diff --git a/e2e/pkg/utils/images/images.go b/e2e/pkg/utils/images/images.go index 8c0ded72b5c..1e576571c21 100644 --- a/e2e/pkg/utils/images/images.go +++ b/e2e/pkg/utils/images/images.go @@ -24,8 +24,9 @@ const ( Alpine = "docker.io/alpine:3" Porter = "calico/porter" TestWebserver = "gcr.io/kubernetes-e2e-test-images/test-webserver:1.0" - Iperf = "registry.k8s.io/e2e-test-images/agnhost:2.47" + Agnhost = "registry.k8s.io/e2e-test-images/agnhost:2.47" RapidClient = "quay.io/tigeradev/rapidclient" + Iperf3 = "docker.io/networkstatic/iperf3:latest" ) // Get client image and powershell command based on windows OS version @@ -35,13 +36,14 @@ func WindowsClientImage() string { framework.Failf("WINDOWS_OS env not specified. Please set env properly") return "" } - if opsys == "1809" || opsys == "1909" || opsys == "1903" || opsys == "2004" || opsys == "20H2" { + switch opsys { + case "1809", "1909", "1903", "2004", "20H2": return "mcr.microsoft.com/windows/servercore:" + opsys - } else if opsys == "2022" { + case "2022": // For 2022, servercore uses "ltsc2022" and does not have the image tag // "2022" (unlike previous Windows versions). return "mcr.microsoft.com/windows/servercore:ltsc2022" - } else { + default: framework.Failf("Windows OS version currently not supported: %s", opsys) } return "" diff --git a/e2e/pkg/utils/installation.go b/e2e/pkg/utils/installation.go new file mode 100644 index 00000000000..7fd7cd3ea44 --- /dev/null +++ b/e2e/pkg/utils/installation.go @@ -0,0 +1,49 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. +// +// 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 ( + "context" + + operatorv1 "github.com/tigera/operator/api/v1" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" +) + +// GetInstallation returns the operator Installation resource if available. +// Returns nil if the Installation resource cannot be retrieved (e.g., on +// manifest-based installs where the operator CRD may not exist). +func GetInstallation(cli ctrlclient.Client) *operatorv1.Installation { + installation := &operatorv1.Installation{} + err := cli.Get(context.Background(), ctrlclient.ObjectKey{Name: "default"}, installation) + if err != nil { + return nil + } + return installation +} + +// UsesCalicoIPAM reports whether the cluster uses Calico IPAM. If the operator +// Installation resource is available, it checks the configured IPAM type. +// Returns true if Calico IPAM is in use or if the IPAM type cannot be determined +// (e.g., on manifest-based installs without an Installation resource). +func UsesCalicoIPAM(cli ctrlclient.Client) bool { + installation := GetInstallation(cli) + if installation != nil && + installation.Spec.CNI != nil && + installation.Spec.CNI.IPAM != nil && + installation.Spec.CNI.IPAM.Type != operatorv1.IPAMPluginCalico { + return false + } + return true +} diff --git a/e2e/pkg/utils/iperfcheck/iperfcheck.go b/e2e/pkg/utils/iperfcheck/iperfcheck.go new file mode 100644 index 00000000000..ec4f2ba1bf7 --- /dev/null +++ b/e2e/pkg/utils/iperfcheck/iperfcheck.go @@ -0,0 +1,420 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. +// +// 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 iperfcheck + +import ( + "context" + "encoding/json" + "fmt" + "maps" + "time" + + "github.com/onsi/ginkgo/v2" + "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/test/e2e/framework" + e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl" + e2epod "k8s.io/kubernetes/test/e2e/framework/pod" + + "github.com/projectcalico/calico/e2e/pkg/utils" + "github.com/projectcalico/calico/e2e/pkg/utils/images" +) + +const ( + roleLabel = "e2e.projectcalico.org/role" + roleIperf = "iperf" + + defaultPort = 5201 + defaultDuration = 10 + defaultOmitSeconds = 5 + defaultRetries = 3 + defaultRetryInterval = 5 * time.Second + defaultExecTimeout = 60 * time.Second +) + +// Peer represents an iperf3 endpoint. Any peer can act as server or client +// for a given measurement. Pods run `sleep infinity` and iperf3 commands +// are executed on demand via kubectl exec. +type Peer struct { + name string + namespace *v1.Namespace + labels map[string]string + pod *v1.Pod + customizer func(*v1.Pod) +} + +// PeerOption configures a Peer. +type PeerOption func(*Peer) + +// WithPeerLabels sets additional labels on the peer pod. +func WithPeerLabels(labels map[string]string) PeerOption { + return func(p *Peer) { + p.labels = labels + } +} + +// WithPeerCustomizer sets a function to customize the pod spec before creation. +func WithPeerCustomizer(fn func(*v1.Pod)) PeerOption { + return func(p *Peer) { + p.customizer = fn + } +} + +// NewPeer creates a new iperf3 peer. +func NewPeer(name string, ns *v1.Namespace, opts ...PeerOption) *Peer { + if ns == nil { + framework.Failf("Namespace is required for peer %s", name) + } + p := &Peer{ + name: name, + namespace: ns, + } + for _, opt := range opts { + opt(p) + } + return p +} + +// Name returns the peer's base name. +func (p *Peer) Name() string { + return p.name +} + +// Pod returns the peer's running pod. Fails the test if the pod is not deployed. +func (p *Peer) Pod() *v1.Pod { + if p.pod == nil { + framework.Failf("No pod is running for peer %s/%s", p.namespace.Name, p.name) + } + return p.pod +} + +// Result holds parsed iperf3 bandwidth measurements. +type Result struct { + // AverageRate is the average throughput in bits/sec from end.sum_received. + AverageRate float64 + // PeakRate is the throughput of the first interval in bits/sec. + PeakRate float64 +} + +// IperfTester manages iperf3 peers and executes bandwidth measurements. +type IperfTester struct { + f *framework.Framework + peers map[string]*Peer +} + +// NewIperfTester creates a new IperfTester. +func NewIperfTester(f *framework.Framework) *IperfTester { + return &IperfTester{ + f: f, + peers: make(map[string]*Peer), + } +} + +// AddPeer registers a peer with the tester. +func (t *IperfTester) AddPeer(peer *Peer) { + if _, ok := t.peers[peer.name]; ok { + framework.Failf("Peer %s already registered", peer.name) + } + t.peers[peer.name] = peer +} + +// RemovePeer deletes a specific peer's pod and deregisters it from the tester. +func (t *IperfTester) RemovePeer(name string) { + peer, ok := t.peers[name] + if !ok { + framework.Failf("Peer %s not registered", name) + } + + if peer.pod != nil { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) + defer cancel() + ginkgo.By(fmt.Sprintf("Deleting iperf peer pod %s", peer.pod.Name)) + err := t.f.ClientSet.CoreV1().Pods(peer.namespace.Name).Delete(ctx, peer.pod.Name, metav1.DeleteOptions{}) + framework.ExpectNoError(err, "failed to delete iperf peer pod %s", peer.pod.Name) + + err = e2epod.WaitForPodNotFoundInNamespace(ctx, t.f.ClientSet, peer.pod.Name, peer.namespace.Name, 1*time.Minute) + framework.ExpectNoError(err, "timed out waiting for peer pod %s deletion", peer.pod.Name) + } + + delete(t.peers, name) +} + +// Deploy creates pods for all registered peers that don't have one yet, then +// waits for them to reach Running state. +func (t *IperfTester) Deploy() { + for _, peer := range t.peers { + if peer.pod != nil { + continue + } + ginkgo.By(fmt.Sprintf("Deploying iperf peer pod %s/%s", peer.namespace.Name, peer.name)) + pod, err := createPeerPod(t.f, peer) + framework.ExpectNoError(err, "failed to create iperf peer pod %s", peer.name) + peer.pod = pod + } + + // Wait for all pods to be running. + ginkgo.By("Waiting for all iperf peer pods to be running") + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + defer cancel() + for _, peer := range t.peers { + err := e2epod.WaitTimeoutForPodRunningInNamespace(ctx, t.f.ClientSet, peer.pod.Name, peer.pod.Namespace, 2*time.Minute) + framework.ExpectNoError(err, "iperf peer pod %s did not reach Running state", peer.pod.Name) + + // Refresh pod to get assigned IP, node name, etc. + p, err := t.f.ClientSet.CoreV1().Pods(peer.namespace.Name).Get(ctx, peer.pod.Name, metav1.GetOptions{}) + framework.ExpectNoError(err, "failed to get iperf peer pod %s", peer.pod.Name) + peer.pod = p + } +} + +// Stop deletes all peer pods and waits for them to be gone. +func (t *IperfTester) Stop() { + ginkgo.By("Tearing down iperf tester") + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + defer cancel() + + for _, peer := range t.peers { + if peer.pod == nil { + continue + } + ginkgo.By(fmt.Sprintf("Deleting iperf peer pod %s", peer.pod.Name)) + err := t.f.ClientSet.CoreV1().Pods(peer.namespace.Name).Delete(ctx, peer.pod.Name, metav1.DeleteOptions{}) + if err != nil { + framework.Logf("WARNING: failed to delete iperf peer pod %s: %v", peer.pod.Name, err) + } + } + + for _, peer := range t.peers { + if peer.pod == nil { + continue + } + ginkgo.By(fmt.Sprintf("Waiting for iperf peer pod %s to be deleted", peer.pod.Name)) + if err := e2epod.WaitForPodNotFoundInNamespace(ctx, t.f.ClientSet, peer.pod.Name, peer.namespace.Name, 1*time.Minute); err != nil { + framework.Logf("WARNING: timed out waiting for peer pod %s deletion: %v", peer.pod.Name, err) + } + } +} + +// measureConfig holds options for a single bandwidth measurement. +type measureConfig struct { + reverse bool + duration int + omitSeconds int + retries int + retryInterval time.Duration + port int +} + +// MeasureOption configures a bandwidth measurement. +type MeasureOption func(*measureConfig) + +// WithReverse enables the -R flag so the server sends data to the client. +// This is used for testing ingress bandwidth limits. +func WithReverse() MeasureOption { + return func(c *measureConfig) { + c.reverse = true + } +} + +// WithDuration sets the iperf3 test duration in seconds (-t flag). +func WithDuration(d int) MeasureOption { + return func(c *measureConfig) { + c.duration = d + } +} + +// WithOmitSeconds sets the number of seconds to omit from the start (-O flag). +func WithOmitSeconds(n int) MeasureOption { + return func(c *measureConfig) { + c.omitSeconds = n + } +} + +// WithRetries sets the number of retry attempts and interval between them. +func WithRetries(n int, interval time.Duration) MeasureOption { + return func(c *measureConfig) { + c.retries = n + c.retryInterval = interval + } +} + +// WithPort overrides the default iperf3 port (5201). +func WithPort(port int) MeasureOption { + return func(c *measureConfig) { + c.port = port + } +} + +// MeasureBandwidth runs an iperf3 test from the client peer to the server peer. +// It starts a one-shot iperf3 server on the server peer, then runs the client. +func (t *IperfTester) MeasureBandwidth(client, server *Peer, opts ...MeasureOption) (*Result, error) { + cfg := &measureConfig{ + duration: defaultDuration, + omitSeconds: defaultOmitSeconds, + retries: defaultRetries, + retryInterval: defaultRetryInterval, + port: defaultPort, + } + for _, opt := range opts { + opt(cfg) + } + + if client.pod == nil { + return nil, fmt.Errorf("client peer %s has no running pod", client.name) + } + if server.pod == nil { + return nil, fmt.Errorf("server peer %s has no running pod", server.name) + } + + serverIP := server.pod.Status.PodIP + if serverIP == "" { + return nil, fmt.Errorf("server peer %s has no pod IP", server.name) + } + + var lastErr error + for attempt := range cfg.retries { + logrus.Infof("iperf3 attempt %d of %d", attempt+1, cfg.retries) + + // Start a one-shot iperf3 server in daemon mode. + serverCmd := fmt.Sprintf("iperf3 -s -1 -D -p %d", cfg.port) + _, err := execInPod(server.pod, defaultExecTimeout, "sh", "-c", serverCmd) + if err != nil { + lastErr = fmt.Errorf("failed to start iperf3 server: %w", err) + logrus.WithError(lastErr).Warn("iperf3 server start failed, retrying") + time.Sleep(cfg.retryInterval) + continue + } + + // Give the daemon a moment to bind. + time.Sleep(500 * time.Millisecond) + + // Run the iperf3 client. + clientCmd := fmt.Sprintf("iperf3 -c %s -p %d -t %d -O %d -J", + serverIP, cfg.port, cfg.duration, cfg.omitSeconds) + if cfg.reverse { + clientCmd += " -R" + } + + // Timeout = duration + omit + buffer. + timeout := time.Duration(cfg.duration+cfg.omitSeconds+10) * time.Second + out, err := execInPod(client.pod, timeout, "sh", "-c", clientCmd) + if err != nil { + lastErr = fmt.Errorf("iperf3 client exec failed: %w", err) + logrus.WithError(lastErr).Warn("iperf3 attempt failed, retrying") + time.Sleep(cfg.retryInterval) + continue + } + + result, err := parseIperf3JSON(out) + if err != nil || result.AverageRate == 0 { + lastErr = fmt.Errorf("iperf3 parse failed: %w", err) + logrus.WithError(lastErr).Warn("iperf3 parse failed, retrying") + time.Sleep(cfg.retryInterval) + continue + } + + return result, nil + } + + return nil, fmt.Errorf("iperf3 failed after %d retries: %w", cfg.retries, lastErr) +} + +// createPeerPod creates an iperf3 peer pod that sleeps until exec'd into. +func createPeerPod(f *framework.Framework, peer *Peer) (*v1.Pod, error) { + podName := utils.GenerateRandomName(peer.name) + + mergedLabels := make(map[string]string) + maps.Copy(mergedLabels, peer.labels) + mergedLabels["pod-name"] = peer.name + mergedLabels[roleLabel] = roleIperf + + zero := int64(0) + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: podName, + Labels: mergedLabels, + }, + Spec: v1.PodSpec{ + RestartPolicy: v1.RestartPolicyNever, + NodeSelector: map[string]string{"kubernetes.io/os": "linux"}, + TerminationGracePeriodSeconds: &zero, + Containers: []v1.Container{ + { + Name: "iperf3", + Image: images.Iperf3, + Command: []string{"/bin/sh", "-c", "sleep infinity"}, + ImagePullPolicy: v1.PullIfNotPresent, + }, + }, + Tolerations: []v1.Toleration{ + { + Key: "kubernetes.io/arch", + Operator: v1.TolerationOpEqual, + Value: "arm64", + Effect: v1.TaintEffectNoSchedule, + }, + }, + }, + } + + if peer.customizer != nil { + peer.customizer(pod) + } + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + return f.ClientSet.CoreV1().Pods(peer.namespace.Name).Create(ctx, pod, metav1.CreateOptions{}) +} + +// execInPod runs a command in a pod with the given timeout. +func execInPod(pod *v1.Pod, timeout time.Duration, command ...string) (string, error) { + args := append([]string{"exec", pod.Name, "-n", pod.Namespace, "--"}, command...) + return e2ekubectl.NewKubectlCommand(pod.Namespace, args...). + WithTimeout(time.After(timeout)). + Exec() +} + +// iperf3Result is a minimal struct for parsing iperf3 JSON output. +type iperf3Result struct { + Intervals []struct { + Sum struct { + BitsPerSecond float64 `json:"bits_per_second"` + } `json:"sum"` + } `json:"intervals"` + End struct { + SumReceived struct { + BitsPerSecond float64 `json:"bits_per_second"` + } `json:"sum_received"` + } `json:"end"` +} + +// parseIperf3JSON parses iperf3 JSON output and returns a Result. +func parseIperf3JSON(output string) (*Result, error) { + var raw iperf3Result + if err := json.Unmarshal([]byte(output), &raw); err != nil { + return nil, fmt.Errorf("failed to parse iperf3 JSON: %w", err) + } + + result := &Result{ + AverageRate: raw.End.SumReceived.BitsPerSecond, + } + if len(raw.Intervals) > 0 { + result.PeakRate = raw.Intervals[0].Sum.BitsPerSecond + } + + logrus.Infof("iperf3 result: rate=%.0f bps, peakRate=%.0f bps", result.AverageRate, result.PeakRate) + return result, nil +} diff --git a/e2e/pkg/utils/kubectl.go b/e2e/pkg/utils/kubectl.go new file mode 100644 index 00000000000..3cd313c6850 --- /dev/null +++ b/e2e/pkg/utils/kubectl.go @@ -0,0 +1,49 @@ +package utils + +import ( + "fmt" + "time" + + "k8s.io/kubernetes/test/e2e/framework/kubectl" +) + +// Kubectl is a wrapper around kubectl commands used in tests. Note that this helper is +// NOT meant to be used for general resource CRUD operations, for that use the client in +// pkg/utils/client. +type Kubectl struct{} + +func (k *Kubectl) Logs(ns, label, user string) (string, error) { + options := []string{"logs"} + if user != "" { + options = append(options, fmt.Sprintf("--as=%v", user)) + } + if label != "" { + options = append(options, fmt.Sprintf("-l %s", label)) + } + + output, err := kubectl.NewKubectlCommand(ns, options...).Exec() + return output, err +} + +func (k *Kubectl) Wait(kind, ns, name, user, condition string, timeout time.Duration) error { + options := []string{"wait", kind, name, "--for", condition, "--timeout", timeout.String()} + if user != "" { + options = append(options, fmt.Sprintf("--as=%v", user)) + } + _, err := kubectl.NewKubectlCommand(ns, options...).Exec() + return err +} + +func (k *Kubectl) PortForward(ns, pod, port, user string, timeOut chan time.Time) { + options := []string{"port-forward", pod, fmt.Sprintf("%s:%s", port, port)} + if user != "" { + options = append(options, fmt.Sprintf("--as=%v", user)) + } + + go func() { + _, err := kubectl.NewKubectlCommand(ns, options...).WithTimeout(timeOut).Exec() + if err != nil { + return + } + }() +} diff --git a/e2e/pkg/utils/names.go b/e2e/pkg/utils/names.go new file mode 100644 index 00000000000..fd25ac83b19 --- /dev/null +++ b/e2e/pkg/utils/names.go @@ -0,0 +1,45 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. +// +// 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 ( + "fmt" + "math/rand" +) + +const ( + maxNameLength = 63 + randomLength = 5 + maxGeneratedNameLength = maxNameLength - randomLength +) + +// GenerateRandomName appends a random suffix to the given base name, +// truncating the base if necessary to fit within Kubernetes' 63-character +// name limit. +func GenerateRandomName(base string) string { + if len(base) > maxGeneratedNameLength { + base = base[:maxGeneratedNameLength] + } + return fmt.Sprintf("%s-%s", base, randomString(randomLength)) +} + +func randomString(length int) string { + const charset = "abcdefghijklmnopqrstuvwxyz0123456789" + b := make([]byte, length) + for i := range b { + b[i] = charset[rand.Intn(len(charset))] + } + return string(b) +} diff --git a/e2e/pkg/utils/nodes.go b/e2e/pkg/utils/nodes.go new file mode 100644 index 00000000000..8fb56ddace5 --- /dev/null +++ b/e2e/pkg/utils/nodes.go @@ -0,0 +1,180 @@ +// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// +// 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 ( + "context" + "net" + "slices" + "time" + + "github.com/onsi/gomega" + "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/test/e2e/framework" +) + +const ( + nodeBgpIpv4IPIPTunnelAddrAnnotation = "projectcalico.org/IPv4IPIPTunnelAddr" + nodeBgpIpv4VXLANTunnelAddrAnnotation = "projectcalico.org/IPv4VXLANTunnelAddr" +) + +func RequireNodeCount(f *framework.Framework, count int) { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + nodes, err := f.ClientSet.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred(), "Error listing nodes") + + // Count the number of ready nodes. + numReady := 0 + for _, node := range nodes.Items { + for _, condition := range node.Status.Conditions { + if condition.Type == corev1.NodeReady && condition.Status == corev1.ConditionTrue { + numReady++ + } + } + } + + gomega.ExpectWithOffset(1, numReady).To(gomega.BeNumerically(">=", count), "Test requires at least %d nodes, found %d", count, numReady) +} + +type NodesInfoGetter interface { + GetNames() []string + GetIPv4s() []string + GetIPv6s() []string + GetCalicoNames() []string + GetTunnelIPs() []string +} + +// nodesInfo implements the NodesInfoGetter interface +type nodesInfo struct { + nodeNames []string + nodeIPv4s []string + nodeIPv6s []string + calicoNodeNames []string + tunnelIPs []string +} + +func (n *nodesInfo) GetNames() []string { + return n.nodeNames +} + +func (n *nodesInfo) GetIPv4s() []string { + return n.nodeIPv4s +} + +func (n *nodesInfo) GetIPv6s() []string { + return n.nodeIPv6s +} + +func (n *nodesInfo) GetCalicoNames() []string { + return n.calicoNodeNames +} + +func (n *nodesInfo) GetTunnelIPs() []string { + return n.tunnelIPs +} + +// GetNodesInfo extracts node information from a Kubernetes NodeList and returns +// a NodesInfoGetter interface that provides access to node details. +func GetNodesInfo(f *framework.Framework, nodes *corev1.NodeList, masterOK bool) NodesInfoGetter { + // By default, Calico node name is host name, e.g. ip-10-0-0-108. + // Kubernetes node name could be different (ip-10-0-0-108.us-west-2.compute.internal) if cloud provider is aws. + var nodeNames, nodeIPv4s, nodeIPv6s, calicoNodeNames, tunnelIPs []string + for _, node := range nodes.Items { + addrs := getNodeAddresses(&node, corev1.NodeInternalIP) + if len(addrs) == 0 { + framework.Failf("node %s failed to report a valid ip address\n", node.Name) + } + + if !masterOK && checkNodeIsMaster(f, addrs) { + logrus.Infof("Skip using master node %s", node.Name) + continue + } + + hostNames := getNodeAddresses(&node, corev1.NodeHostName) + if len(hostNames) == 0 { + framework.Failf("node %s failed to report a valid host name\n", node.Name) + } + + nodeNames = append(nodeNames, node.Name) + + // Separate IPv4 and IPv6 addresses + for _, addr := range addrs { + if net.ParseIP(addr).To4() == nil && net.ParseIP(addr) != nil { + nodeIPv6s = append(nodeIPv6s, addr) + } else { + nodeIPv4s = append(nodeIPv4s, addr) + } + } + + calicoNodeNames = append(calicoNodeNames, hostNames[0]) + tunnelIPs = append(tunnelIPs, getNodeTunnelIP(&node)) + } + return &nodesInfo{ + nodeNames: nodeNames, + nodeIPv4s: nodeIPv4s, + nodeIPv6s: nodeIPv6s, + calicoNodeNames: calicoNodeNames, + tunnelIPs: tunnelIPs, + } +} + +func getNodeTunnelIP(node *corev1.Node) string { + if ip, ok := node.Annotations[nodeBgpIpv4IPIPTunnelAddrAnnotation]; ok { + return ip + } + if ip, ok := node.Annotations[nodeBgpIpv4VXLANTunnelAddrAnnotation]; ok { + return ip + } + return "" +} + +func getNodeAddresses(node *corev1.Node, addressType corev1.NodeAddressType) (ips []string) { + for j := range node.Status.Addresses { + nodeAddress := &node.Status.Addresses[j] + if nodeAddress.Type == addressType { + ips = append(ips, nodeAddress.Address) + } + } + return +} + +func checkNodeIsMaster(f *framework.Framework, ips []string) bool { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + endpnts, err := f.ClientSet.CoreV1().Endpoints("default").Get(ctx, "kubernetes", metav1.GetOptions{}) + if err != nil { + framework.Failf("Get endpoints for service kubernetes failed (%s)", err) + } + if len(endpnts.Subsets) == 0 { + framework.Failf("Endpoint has no subsets, cannot determine node addresses.") + } + + hasIP := func(endpointIP string) bool { + return slices.Contains(ips, endpointIP) + } + + for _, ss := range endpnts.Subsets { + for _, e := range ss.Addresses { + if hasIP(e.IP) { + return true + } + } + } + + return false +} diff --git a/e2e/pkg/utils/platforms.go b/e2e/pkg/utils/platforms.go index 73548715e30..1ca0cc110d5 100644 --- a/e2e/pkg/utils/platforms.go +++ b/e2e/pkg/utils/platforms.go @@ -17,7 +17,7 @@ package utils import ( "context" - . "github.com/onsi/gomega" + "github.com/onsi/gomega" v1 "github.com/tigera/operator/api/v1" "k8s.io/kubernetes/test/e2e/framework" @@ -27,12 +27,12 @@ import ( func IsOpenShift(f *framework.Framework) bool { // Create a client to the API server. cli, err := client.New(f.ClientConfig()) - Expect(err).NotTo(HaveOccurred()) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) // Query Installation object to check if we are running on OpenShift. installs := &v1.InstallationList{} err = cli.List(context.TODO(), installs) - Expect(err).NotTo(HaveOccurred()) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) for _, inst := range installs.Items { if inst.Spec.KubernetesProvider == v1.ProviderOpenShift { diff --git a/e2e/pkg/utils/remotecluster/exec.go b/e2e/pkg/utils/remotecluster/exec.go index 696a8dac4e0..c4dd9a102e1 100644 --- a/e2e/pkg/utils/remotecluster/exec.go +++ b/e2e/pkg/utils/remotecluster/exec.go @@ -17,7 +17,7 @@ package remotecluster import ( "strings" - . "github.com/onsi/gomega" + "github.com/onsi/gomega" "k8s.io/client-go/tools/clientcmd" "k8s.io/kubernetes/test/e2e/framework" @@ -52,7 +52,7 @@ func RemoteFrameworkAwareExec(f *framework.Framework, fn func()) { // Resolve the new host. newKubeconfig, err := clientcmd.LoadFromFile(newKubeconfigPath) - Expect(err).NotTo(HaveOccurred()) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) var newKubeconfigContext string if strings.Contains(newKubeconfig.CurrentContext, "@") { // Some generated kubeconfigs will include a username in the context string. @@ -61,7 +61,7 @@ func RemoteFrameworkAwareExec(f *framework.Framework, fn func()) { newKubeconfigContext = newKubeconfig.CurrentContext } cluster := newKubeconfig.Clusters[newKubeconfigContext] - Expect(cluster).NotTo(BeNil()) + gomega.Expect(cluster).NotTo(gomega.BeNil()) newHost := cluster.Server // Set the new kubeconfig path and host. diff --git a/felix/.gitignore b/felix/.gitignore index 30deeeae8e5..8404257c0da 100644 --- a/felix/.gitignore +++ b/felix/.gitignore @@ -32,6 +32,7 @@ bpf-gpl/ut/*.ll.tmp # IDE files. /.idea/ /.vscode/ +.claude # Patterns to match files from older releases (prevents lots of # untracked files from showing up when switching from a maintenance branch. diff --git a/felix/.semaphore/batches.sh b/felix/.semaphore/batches.sh new file mode 100644 index 00000000000..8a6d4217e21 --- /dev/null +++ b/felix/.semaphore/batches.sh @@ -0,0 +1,111 @@ +# Common definitions for the tests that we run on VMs with a newer +# kernel than Semaphore itself provides. +set -e + +num_fv_batches=${NUM_FV_BATCHES:-8} + +if [[ ${RUN_UT} == "true" ]]; then + export batches=(ut $(seq 1 ${num_fv_batches})) +else + export batches=($(seq 1 ${num_fv_batches})) +fi + +run_batch() { + local remote_exec="$1" + local batch="$2" + local vm_name="$3" + local log_file="$4" + local cmd=() + if [ "$batch" = "ut" ]; then + cmd=( + make + --directory=${CALICO_DIR_NAME}/felix + FOCUS="${UT_FOCUS}" + ut-bpf-no-prereqs + ) + else # Numbered FV batch. + cmd=( + make + --directory=${CALICO_DIR_NAME}/felix + FELIX_FV_NFTABLES="$FELIX_FV_NFTABLES" + FV_EXTRA_REPORT_SUFFIX="$FV_EXTRA_REPORT_SUFFIX" + FELIX_FV_BPFATTACHTYPE="$FELIX_FV_BPFATTACHTYPE" + GINKGO_FOCUS="${FV_FOCUS}" + FV_NUM_BATCHES="$num_fv_batches" + FV_BATCHES_TO_RUN="$batch" + check-wireguard + "$FV_NO_PREREQ_TARGET" + ) + fi + + local cmd_quot="" + local first=true + for part in "${cmd[@]}"; do + if [ "$first" = true ]; then + first=false + else + cmd_quot+=" " + fi + cmd_quot+="$(printf "%q" "$part")" + done + + echo "RUNNER: Starting batch '$batch' on VM '$vm_name'" >> "$log_file" + echo "RUNNER: Command: $cmd_quot" >> "$log_file" + + # We used to have occasional problems with ssh sessions dropping while tests + # were running. Avoid by running the tests in the background with nohup, and + # monitoring the log file separately. + # + # Notes on nohup: + # - We need to redirect stdin/stdout/stderr of the command to avoid holding + # open the ssh session. Redirecting stdin is not strictly necessary here + # because the on-test-vm script already passes '-n' to ssh. + # - Since the command will return immediately after putting the batch in the + # background, we need to monitor the log file separately. We do that with + # a retry loop. + # - nohup swallows the return code of the process so we use a 'bash -c' + # wrapper to capture it in a file. + VM_NAME="$vm_name" ${remote_exec} "nohup bash -c 'echo \$\$ > test.pid; $cmd_quot > test.log; echo \$? > test.rc' < /dev/null >& /dev/null & while [ ! -e test.log ]; do sleep 1; done" + echo "RUNNER: Started batch '$batch' on VM '$vm_name', monitoring log..." >> "$log_file" + + # Subshell to limit scope of trap. + ( + # Enable job control so that background processes get their own process groups. + set -m + + # Monitor the log in the background with a retry loop. + stopped=false + while ! $stopped; do + VM_NAME="$vm_name" ${remote_exec} 'tail -F -n 0 "test.log" 2>/dev/null' >> "$log_file" || true + echo "RUNNER: WARNING: Tail process on VM '$vm_name' ended, restarting..." >> "$log_file" + sleep 1 + done & + tail_pid=$! + # Set a trap to stop the tail when we exit. Note: negative PID means + # "kill the process group", i.e. while loop and its children. + trap 'stopped=true; kill -TERM -$tail_pid || true; wait $tail_pid || true' EXIT + + num_fails=0 + while true; do + rc=$(VM_NAME="$vm_name" ${remote_exec} "$CALICO_DIR_NAME/felix/.semaphore/wait-for-test-completion" || echo "ssh error $?") + # Verify that we got a number; if not, probably an ssh error or similar. + if grep -q '^[0-9]\+$' <<< "$rc"; then + if [ "$rc" -eq 66 ]; then + echo "RUNNER: WARNING: Batch '$batch' on VM '$vm_name' stopped without outputting its RC file (possible VM crash/reboot?)." >> "$log_file" + echo "RUNNER: Fetching serial console output for debugging:" >> "$log_file" + gcloud compute instances get-serial-port-output "$vm_name" --zone=${ZONE} --port=1 >> "$log_file" + fi + echo "RUNNER: Batch '$batch' on VM '$vm_name' completed with rc=$rc" >> "$log_file" + exit "$rc" + else + echo "RUNNER: Failed to read batch RC got '$rc', continuing to monitor log..." >> "$log_file" + sleep 10 + num_fails=$((num_fails + 1)) + if [ "$num_fails" -ge 10 ]; then + echo "RUNNER: Too many failures reading batch RC, aborting batch '$batch' on VM '$vm_name'" >> "$log_file" + exit 1 + fi + fi + done + ) +} diff --git a/felix/.semaphore/cache-test-artifacts b/felix/.semaphore/cache-test-artifacts new file mode 100755 index 00000000000..ed6864fc61a --- /dev/null +++ b/felix/.semaphore/cache-test-artifacts @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# Copyright (c) 2025 Tigera, Inc. All rights reserved. +# +# 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. + +# This script is run locally to push artifacts to our sub-directory in GCS. +# The counterpart to this script is load-test-artifacts, which runs on the test +# VM. It can assume that the artifacts have been downloaded to /tmp. + +set -e +set -x + +artifact push job bin & +docker save -o /tmp/calico-felix-test.tar calico/felix-test:latest-amd64 +docker save -o /tmp/felixtest-typha.tar felix-test/typha:latest-amd64 +cd ../.. + +# Future work: Split UT and FV artifacts so we can be even more selective. +# Excluding Typha binaries because we only need the image. +tar -czf /tmp/working-copy.tgz \ + --exclude=.go-pkg-cache \ + --exclude=calico/typha/bin \ + --exclude=calico/typha/docker-image/bin \ + --exclude=artifacts \ + ${CALICO_DIR_NAME} +gcloud storage cp /tmp/working-copy.tgz /tmp/calico-felix-test.tar /tmp/felixtest-typha.tar "${GCS_WORKFLOW_DIR}/felix/fv-artifacts/" +wait diff --git a/felix/.semaphore/clean-up-vms b/felix/.semaphore/clean-up-vms deleted file mode 100755 index 733b5c8602e..00000000000 --- a/felix/.semaphore/clean-up-vms +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash - -# Copyright (c) 2019 Tigera, Inc. All rights reserved. -# -# 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. - -set -e -set -x - -vm_prefix=$1 -project=unique-caldron-775 -zone=europe-west3-c - -gcloud config set project $project -gcloud auth activate-service-account --key-file=$HOME/secrets/secret.google-service-account-key.json - -while true; do - gcloud --quiet compute instances list \ - "--filter=name~'${vm_prefix}.*'" \ - --zones=${zone} \ - --format='table[no-heading](name)' > instance-list - instances="$(cat instance-list)" - if [ "${instances}" = "" ]; then - echo "All instances deleted" - break - fi - echo "Instances to delete: $instances" - gcloud --quiet compute instances delete ${instances} --zone=${zone} - sleep 1 -done diff --git a/felix/.semaphore/cleanup.yml b/felix/.semaphore/cleanup.yml deleted file mode 100644 index 1bc6a284cb9..00000000000 --- a/felix/.semaphore/cleanup.yml +++ /dev/null @@ -1,34 +0,0 @@ -version: v1.0 -name: Felix -agent: - machine: - type: f1-standard-2 - os_image: ubuntu2204 - -execution_time_limit: - minutes: 10 - -global_job_config: - secrets: - - name: docker-hub - prologue: - commands: - - echo $DOCKERHUB_PASSWORD | docker login --username "$DOCKERHUB_USERNAME" --password-stdin - -blocks: -- name: Clean up GCE resources - dependencies: [] - task: - prologue: - commands: - - checkout - - export GOOGLE_APPLICATION_CREDENTIALS=$HOME/secrets/secret.google-service-account-key.json - - export SHORT_WORKFLOW_ID=$(echo ${SEMAPHORE_WORKFLOW_ID} | sha256sum | cut -c -8) - - export VM_PREFIX=sem-${SEMAPHORE_PROJECT_NAME}-${SHORT_WORKFLOW_ID} - - echo VM_PREFIX=${VM_PREFIX} - jobs: - - name: Clean up GCE instances - commands: - - ./.semaphore/clean-up-vms ${VM_PREFIX} - secrets: - - name: google-service-account-for-gce diff --git a/felix/.semaphore/collect-artifacts b/felix/.semaphore/collect-artifacts deleted file mode 100755 index 2f7e53416bd..00000000000 --- a/felix/.semaphore/collect-artifacts +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env bash - -# Copyright (c) 2020 Tigera, Inc. All rights reserved. -# -# 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. - -# No set -e so that we continue to collect artifacts even if some fail. -set -x - -my_dir="$(dirname "$0")" -repo_dir="$my_dir/.." -skip_binaries=false - -for arg in "$@"; do - if [ "$arg" = "--skip-binaries" ]; then - echo "Skipping collecting binaries." - skip_binaries=true - fi -done - -cd "$repo_dir" || exit 1 - -rm -rf artifacts -mkdir -p artifacts -for file in /tmp/core_felix* ; do - if [ -f "$file" ]; then - echo "Adding core file: $file" - sudo chmod a+r "$file" - out_file="artifacts/$(basename "$file").xz" - xz < "$file" > "$out_file" - fi -done -if [ -s fv/data-races.log ]; then - # Only save the log if it's non-empty. - xz < fv/data-races.log > artifacts/data-races.log.xz -fi -if ! $skip_binaries; then - tar -cJf artifacts/binaries.tar.xz bin/calico-felix-amd64 bin/calico-bpf bin/test-* -fi -exit 0 diff --git a/felix/.semaphore/collect-artifacts-from-vms b/felix/.semaphore/collect-artifacts-from-vms deleted file mode 100755 index af7ad2989d0..00000000000 --- a/felix/.semaphore/collect-artifacts-from-vms +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env bash - -# Copyright (c) 2019 Tigera, Inc. All rights reserved. -# -# 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. - -set -e - -source .semaphore/new-kernel-common.sh - -vm_name_prefix=$1 -project=unique-caldron-775 -zone=${ZONE:-europe-west3-c} -my_dir="$(dirname $0)" -repo_dir="." -artifacts_dir="$repo_dir/artifacts" - -echo "Collecting artifacts..." - -pids=() -log_files=() -for batch in "${batches[@]}"; do - vm_name="$vm_name_prefix$batch" - log_file="$artifacts_dir/collect-artifacts-$batch.log" - log_files+=( $log_file ) - echo "Collecting artifacts from $vm_name. Logging to $log_file." - ( - args="" - # Only collect binaries once since they're large to copy and should be identical. - if [ $batch != 1 ]; then - args="--skip-binaries" - fi - VM_NAME=$vm_name $my_dir/on-test-vm ${REPO_NAME}/.semaphore/collect-artifacts $args || echo "Failed to collect artifacts from ${vm_name}." - timeout 10m gcloud --quiet compute scp "--zone=${ZONE}" "ubuntu@${vm_name}:${REPO_NAME}/artifacts" ./ --recurse || echo "Failed to SCP from ${vm_name}" - ) >& "$log_file" & - pid=$! - pids+=( $pid ) -done - -echo "===== Waiting for diags collection to complete =====" -for (( i=0; i /tmp/daemon.json ; else echo -en {\\n > /tmp/daemon.json ; fi' && \ - gcloud --quiet compute ssh --zone=${zone} "ubuntu@${vm_name}" -- 'cat >> /tmp/daemon.json << EOF - "ipv6": true, - "fixed-cidr-v6": "2001:db8:1::/64" -} -EOF' && \ - gcloud --quiet compute ssh --zone=${zone} "ubuntu@${vm_name}" -- sudo mv /tmp/daemon.json /etc/docker/daemon.json && \ - gcloud --quiet compute ssh --zone=${zone} "ubuntu@${vm_name}" -- sudo systemctl restart docker && \ - set +x && \ - echo "$DOCKERHUB_PASSWORD" | gcloud --quiet compute ssh --zone=${zone} "ubuntu@${vm_name}" -- docker login --username "$DOCKERHUB_USERNAME" --password-stdin && \ - set -x && \ - gcloud --quiet compute scp --zone=${zone} --recurse --compress "$(dirname $(pwd))" "ubuntu@${vm_name}:/home/ubuntu/calico" -} - -function delete-vm() { - gcloud --quiet compute instances delete "${vm_name}" --zone=${zone} -} - -for attempt in $(seq 1 5); do - echo "Trying to create text VM, attempt ${attempt}" - if create-vm; then - echo "Success!" - exit 0 - else - echo "Failed to create VM. Tearing it down." - delete-vm || true - fi -done - -echo "Out of retries" -exit 1 diff --git a/felix/.semaphore/create-test-vms b/felix/.semaphore/create-test-vms deleted file mode 100755 index bf9c7faac66..00000000000 --- a/felix/.semaphore/create-test-vms +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env bash - -# Copyright (c) 2019 Tigera, Inc. All rights reserved. -# -# 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. - -set -e - -source .semaphore/new-kernel-common.sh - -vm_name_prefix=$1 -project=unique-caldron-775 -zone=${ZONE:-europe-west3-c} -my_dir="$(dirname $0)" -repo_dir="." -artifacts_dir="$repo_dir/artifacts" - -pids=() -for batch in "${batches[@]}"; do - vm_name="$vm_name_prefix$batch" - log_file="$artifacts_dir/create-vm-$batch.log" - echo "Creating test VM $vm_name in background. Redirecting log to $log_file." - "${my_dir}/create-test-vm" "$vm_name" >& "$log_file" & - pids+=( $! ) - sleep 1 -done - -for pid in "${pids[@]}"; do - echo "Waiting for create-test-vm process PID=$pid to finish" - if ! wait "$pid"; then - echo "Creating one of the test VMs failed, exiting. Logs should be attached as artifacts." - exit 1 - fi -done - -echo "All test VMs started." diff --git a/felix/.semaphore/fv-epilogue b/felix/.semaphore/fv-epilogue new file mode 100644 index 00000000000..699e0cad9dd --- /dev/null +++ b/felix/.semaphore/fv-epilogue @@ -0,0 +1,4 @@ +#!/bin/bash + +.semaphore/vms/publish-reports "${TEST_REPORT_NAME}" +.semaphore/vms/clean-up-vms ${VM_PREFIX} diff --git a/felix/.semaphore/fv-prologue b/felix/.semaphore/fv-prologue new file mode 100644 index 00000000000..0808782edb1 --- /dev/null +++ b/felix/.semaphore/fv-prologue @@ -0,0 +1,91 @@ +#!/bin/bash + +# Set up access to GCP buckets/gcloud API. +export GOOGLE_APPLICATION_CREDENTIALS=$HOME/secrets/secret.google-service-account-key.json +gcloud auth activate-service-account --key-file="$GOOGLE_APPLICATION_CREDENTIALS" +gcloud config set project unique-caldron-775 + +export COMPONENT=felix +export ZONE=europe-west3-c + +# 4 vCPUs seems to be the minimum for stable tests. 8 vCPUs doesn't seem to +# help performance very much. +# +# With each FV batch being quite small, the tests don't use that much RAM, so +# we can use a high-CPU machine type to save costs. +export VM_MACHINE_TYPE=n4-highcpu-4 + +export NUM_FV_BATCHES=8 + +JOB_TAG="" +TEST_REPORT_NAME="" + +# In BPF runs, only run BPF-SAFE tagged tests +if [[ $FELIX_TEST_GROUP =~ bpf ]]; then + export FV_FOCUS=BPF-SAFE + JOB_TAG+="bpf" + TEST_REPORT_NAME+="BPF/" + FV_NO_PREREQ_TARGET=fv-bpf-no-prereqs +else + FV_NO_PREREQ_TARGET=fv-no-prereqs +fi +export FV_NO_PREREQ_TARGET + +if [[ $FELIX_TEST_GROUP =~ 22.04 ]]; then + export IMAGE_FAMILY=ubuntu-2204-lts + JOB_TAG+="2204" + TEST_REPORT_NAME+="Ubuntu-22.04/" +elif [[ $FELIX_TEST_GROUP =~ 24.04 ]]; then + export IMAGE_FAMILY=ubuntu-2404-lts-amd64 + JOB_TAG+="2404" + TEST_REPORT_NAME+="Ubuntu-24.04/" +elif [[ $FELIX_TEST_GROUP =~ 25.10 ]]; then + export IMAGE_FAMILY=ubuntu-2510-amd64 + JOB_TAG+="2510" + TEST_REPORT_NAME+="Ubuntu-25.10/" +else + echo "Unknown OS version in FELIX_TEST_GROUP: $FELIX_TEST_GROUP" + exit 1 +fi +echo "Calculated: IMAGE_FAMILY=${IMAGE_FAMILY}" + +if [[ $FELIX_TEST_GROUP =~ ipt ]]; then + JOB_TAG+="ipt" + TEST_REPORT_NAME+="iptables" +elif [[ $FELIX_TEST_GROUP =~ nft ]]; then + JOB_TAG+="nft" + TEST_REPORT_NAME+="nftables" + export FELIX_FV_NFTABLES=Enabled +fi + +if [[ $FELIX_TEST_GROUP =~ with-ut ]]; then + echo "Test group includes UTs, setting RUN_UT=true" + export RUN_UT=true +else + export RUN_UT=false +fi + +if [[ $FELIX_TEST_GROUP =~ no-fv ]]; then + echo "UT-only run, setting NUM_FV_BATCHES=0" + export NUM_FV_BATCHES=0 +fi + +ENABLE_JIT_HARDENING=false +if [[ $FELIX_TEST_GROUP =~ jitharden ]]; then + JOB_TAG+="jit" + TEST_REPORT_NAME+="/jitharden=2" + ENABLE_JIT_HARDENING=true +fi +export ENABLE_JIT_HARDENING +export JOB_TAG +export CI_GROUP_LABEL="felix-fv" +export CI_JOB_LABEL="${JOB_TAG}" +export TEST_REPORT_NAME + +# Calculate short IDs for use in VM names (which are limited to 63 chars). +SHORT_WORKFLOW_ID=$(echo "${SEMAPHORE_WORKFLOW_ID}" | sha256sum | cut -c -8) +export SHORT_WORKFLOW_ID +export VM_PREFIX=sem-${SEMAPHORE_PROJECT_NAME}-${SHORT_WORKFLOW_ID}-${JOB_TAG}- +mkdir artifacts + +echo "Calculated: JOB_TAG=${JOB_TAG}; TEST_REPORT_NAME=${TEST_REPORT_NAME}; RUN_UT=${RUN_UT}; VM_PREFIX=${VM_PREFIX}" diff --git a/felix/.semaphore/publish-artifacts b/felix/.semaphore/load-test-artifacts similarity index 61% rename from felix/.semaphore/publish-artifacts rename to felix/.semaphore/load-test-artifacts index 0c52920dca6..98f4d1bfeaf 100755 --- a/felix/.semaphore/publish-artifacts +++ b/felix/.semaphore/load-test-artifacts @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# Copyright (c) 2020 Tigera, Inc. All rights reserved. +# Copyright (c) 2025 Tigera, Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,17 +14,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -# No set -e so that we continue to collect artifacts even if some fail. -set -x +# This script is run on the test VM, to load the component-specific test +# artifacts, which have already been downloaded to /tmp. -# set nullglob so that '*' is not used literally if the artifacts dir is empty -shopt -s nullglob +set -ex -my_dir=$(dirname "$0") -repo_dir=$my_dir/.. - -cd "$repo_dir/artifacts" || exit 1 - -for file in *; do - artifact push job "$file" -done +docker load -i /tmp/calico-felix-test.tar +docker load -i /tmp/felixtest-typha.tar +docker tag calico/felix-test:latest-amd64 felix-test:latest-amd64 diff --git a/felix/.semaphore/new-kernel-common.sh b/felix/.semaphore/new-kernel-common.sh deleted file mode 100644 index 2722e87e780..00000000000 --- a/felix/.semaphore/new-kernel-common.sh +++ /dev/null @@ -1,10 +0,0 @@ -# Common definitions for the tests that we run on VMs with a newer -# kernel than Semaphore itself provides. - -num_fv_batches=${NUM_FV_BATCHES:-8} - -if [[ ${RUN_UT} == "true" ]]; then - batches=(ut $(seq 1 ${num_fv_batches})) -else - batches=($(seq 1 ${num_fv_batches})) -fi diff --git a/felix/.semaphore/run-tests-on-vms b/felix/.semaphore/run-tests-on-vms deleted file mode 100755 index 24643d5beef..00000000000 --- a/felix/.semaphore/run-tests-on-vms +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/env bash - -# Copyright (c) 2019 Tigera, Inc. All rights reserved. -# -# 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. - -set -e - -source .semaphore/new-kernel-common.sh - -log_monitor_regexps=( - "(?& "$log_file" & - pid=$! - pids+=( $pid ) - else - VM_NAME=$vm_name ./.semaphore/on-test-vm make --directory=calico/${REPO_NAME} fv-bpf FELIX_FV_NFTABLES="$FELIX_FV_NFTABLES" FELIX_FV_BPFATTACHTYPE="$FELIX_FV_BPFATTACHTYPE" GINKGO_FOCUS="${FV_FOCUS}" FV_NUM_BATCHES=$num_fv_batches FV_BATCHES_TO_RUN="$batch" >& "$log_file" & - pid=$! - pids+=( $pid ) - fi - - prefix="[batch=${batch} pid=${pid}]" - echo "$prefix Started test batch in background; monitoring its log ($log_file)." - ( - tail -F $log_file | \ - grep --line-buffered --perl "${monitor_pattern}" -B 2 -A 15 | \ - sed 's/.*/'"${prefix}"' &/'; - ) & - mon_pid=$! - monitor_pids+=( $mon_pid ) -done - -final_result=0 - -echo -echo "===== Waiting for background test runners to finish ====" -echo - -num_batches=${#batches[@]} -summary=() -for (( i=0; i/dev/null && echo true || echo false` \ ./run-batches @if [ -e fv/latency.log ]; then \ @@ -383,6 +394,9 @@ fv-no-prereqs: fv-bpf: $(MAKE) fv FELIX_FV_ENABLE_BPF=true +fv-bpf-no-prereqs: + $(MAKE) fv-no-prereqs FELIX_FV_ENABLE_BPF=true + fv-nft: $(MAKE) fv FELIX_FV_NFTABLES=Enabled @@ -467,7 +481,7 @@ stop-grafana: @-docker rm -f k8sfv-grafana sleep 2 -bin/calico-bpf: $(SRC_FILES) +bin/calico-bpf: $(LIBBPF_A) $(SRC_FILES) @echo Building calico-bpf... $(call build_cgo_binary, "$(PACKAGE_NAME)/cmd/calico-bpf", $@) @@ -475,10 +489,6 @@ bin/pktgen: $(SRC_FILES) $(FV_SRC_FILES) @echo Building pktgen... $(call build_binary, "$(PACKAGE_NAME)/fv/pktgen", $@) -bin/iptables-locker: ../go.mod $(shell find iptables -type f -name '*.go' -print) - @echo Building iptables-locker... - $(call build_binary, "$(PACKAGE_NAME)/fv/iptables-locker", $@) - bin/test-workload: ../go.mod fv/cgroup/cgroup.go fv/utils/utils.go fv/connectivity/*.go fv/test-workload/*.go @echo Building test-workload... $(call build_binary, "$(PACKAGE_NAME)/fv/test-workload", $@) @@ -526,14 +536,14 @@ release-build: .release-$(VERSION).created # Developer helper scripts (not used by build or test) ############################################################################### .PHONY: ut-no-cover -ut-no-cover: $(SRC_FILES) +ut-no-cover: $(SRC_FILES) $(LIBBPF_A) $(BPF_APACHE_O_FILES) @echo Running Go UTs without coverage. - $(DOCKER_GO_BUILD) -e $(ACK_GINKGO) ginkgo -r -skipPackage $(UT_PACKAGES_TO_SKIP) $(GINKGO_ARGS) + $(DOCKER_GO_BUILD) ginkgo -r -skip-package $(UT_PACKAGES_TO_SKIP) $(GINKGO_ARGS) .PHONY: ut-watch -ut-watch: $(SRC_FILES) +ut-watch: $(SRC_FILES) $(LIBBPF_A) $(BPF_APACHE_O_FILES) @echo Watching go UTs for changes... - $(DOCKER_GO_BUILD) -e $(ACK_GINKGO) ginkgo watch -r -skipPackage $(UT_PACKAGES_TO_SKIP) $(GINKGO_ARGS) + $(DOCKER_GO_BUILD) ginkgo watch -r -skip-package $(UT_PACKAGES_TO_SKIP) $(GINKGO_ARGS) .PHONY: bin/bpf.test bin/bpf.test: $(GENERATED_FILES) $(shell find bpf/ -name '*.go') @@ -548,32 +558,45 @@ bin/bpf_ut.test: $(GENERATED_FILES) $(shell find bpf/ -name '*.go') bin/bpf_debug.test: $(GENERATED_FILES) $(shell find bpf/ -name '*.go') $(DOCKER_GO_BUILD_CGO) go test ./bpf/ut -c -gcflags="-N -l" -o $@ +.PHONY: ut-bpf-prereqs +ut-bpf-prereqs: $(LIBBPF_A) bin/bpf_ut.test bin/bpf.test build-bpf + .PHONY: ut-bpf -ut-bpf: $(LIBBPF_A) bin/bpf_ut.test bin/bpf.test build-bpf +ut-bpf: ut-bpf-prereqs + $(MAKE) ut-bpf-no-prereqs + +# Set format for gotestsum based on VERBOSE environment variable +ifeq ($(VERBOSE),1) + GOTESTSUM_FORMAT := standard-verbose +else + GOTESTSUM_FORMAT := testname +endif + +ut-bpf-no-prereqs: + mkdir -p report $(DOCKER_RUN) \ --privileged \ -e RUN_AS_ROOT=true \ - -e $(ACK_GINKGO) \ - $(CALICO_BUILD) sh -c ' \ + $(CALICO_BUILD) bash -o pipefail -c ' \ mount bpffs /sys/fs/bpf -t bpf && \ cd /go/src/$(PACKAGE_NAME)/bpf/ && \ - BPF_FORCE_REAL_LIB=true ../bin/bpf.test -test.v -test.run "$(FOCUS)"' - $(DOCKER_RUN) \ + BPF_FORCE_REAL_LIB=true ../bin/bpf.test -test.v=test2json -test.run "$(FOCUS)" |& \ + gotestsum --format $(GOTESTSUM_FORMAT) --junitfile ../report/felix_bpf.xml --raw-command -- go tool test2json -t -p $(PACKAGE_NAME)/bpf' + $(DOCKER_RUN_PRIV_NET) \ --privileged \ - -e $(ACK_GINKGO) \ -e RUN_AS_ROOT=true \ -v `pwd`:/code \ -v `pwd`/bpf-gpl/bin:/usr/lib/calico/bpf \ - $(CALICO_BUILD) sh -c ' \ + $(CALICO_BUILD) bash -o pipefail -c ' \ mount bpffs /sys/fs/bpf -t bpf && \ cd /go/src/$(PACKAGE_NAME)/bpf/ut && \ - ../../bin/bpf_ut.test -test.v -test.run "$(FOCUS)"' + ../../bin/bpf_ut.test -test.v=test2json -test.run "$(FOCUS)" |& \ + gotestsum --format $(GOTESTSUM_FORMAT) --junitfile ../../report/felix_bpf_ut.xml --raw-command -- go tool test2json -t -p $(PACKAGE_NAME)/bpf/ut' .PHONY: bench-bpf bench-bpf: $(LIBBPF_A) bin/bpf_ut.test bin/bpf.test build-bpf $(DOCKER_RUN) \ --privileged \ - -e $(ACK_GINKGO) \ -e RUN_AS_ROOT=true \ -v `pwd`:/code \ -v `pwd`/bpf-gpl/bin:/usr/lib/calico/bpf \ @@ -614,9 +637,9 @@ bin/calico-felix.exe: $(SRC_FILES) docs/calc.pdf: docs/calc.dot cd docs/ && dot -Tpdf calc.dot -o calc.pdf -CONFIG_PARAM_DEPS := cmd/calico-felix-docgen/*.go config/*.go ../api/pkg/apis/projectcalico/v3/felixconfig.go ../libcalico-go/config/*.go ../libcalico-go/config/crd/crd.projectcalico.org_felixconfigurations.yaml +CONFIG_PARAM_DEPS := cmd/calico-felix-docgen/*.go config/*.go ../api/pkg/apis/projectcalico/v3/felixconfig.go ../libcalico-go/config/*.go ../$(CALICO_CRD_PATH)crd.projectcalico.org_felixconfigurations.yaml -../libcalico-go/config/crd/crd.projectcalico.org_felixconfigurations.yaml: ../api/pkg/apis/projectcalico/v3/felixconfig.go +../libcalico-go/config/crd/crd.projectcalico.org_felixconfigurations.yaml ../api/config/crd/crd.projectcalico.org_felixconfigurations.yaml: ../api/pkg/apis/projectcalico/v3/felixconfig.go $(MAKE) -C ../libcalico-go gen-crds docs/config-params.json: $(CONFIG_PARAM_DEPS) diff --git a/felix/README.md b/felix/README.md index bbec189f712..f1ec704ba4f 100644 --- a/felix/README.md +++ b/felix/README.md @@ -42,7 +42,7 @@ your contribution. ## How do I build Felix? -Felix mostly uses Docker for builds. We develop on Ubuntu 16.04 but other +Felix mostly uses Docker for builds. We develop on Ubuntu 24.04 but other Linux distributions should work (there are known `Makefile` issues that prevent building on OS X). To build Felix, you will need: @@ -153,10 +153,6 @@ Ginkgo will re-run tests as files are modified and saved. - **Working Directory:** Set the working directory to: `/{path to the project root}/felix/fv` - - **GO tool arguments:** `-tags=fvtests` - - - Check **[x]**`Use all custom build tags` checkbox. - - **Program arguments:** `-ginkgo.v` - Add **Before Launch** instructions @@ -199,14 +195,10 @@ Ginkgo will re-run tests as files are modified and saved. "request": "launch", "mode": "test", "program": "${workspaceFolder}/felix/fv", - "env": { - "GOFLAGS": "-tags=fvtests" - }, "args": [ "-test.v", "-ginkgo.v" ], - "buildFlags": "-tags=fvtests", "cwd": "${workspaceFolder}/felix/fv", "preLaunchTask": "Build and Prepare Felix", "console": "integratedTerminal" diff --git a/felix/aws/ec2.go b/felix/aws/ec2.go index 57838880d2f..db77e824187 100644 --- a/felix/aws/ec2.go +++ b/felix/aws/ec2.go @@ -213,7 +213,7 @@ func (c *ec2Client) getEC2NetworkInterfaceId(ctx context.Context) (networkInstan } var out *ec2.DescribeInstancesOutput - for i := 0; i < retries; i++ { + for range retries { out, err = c.EC2Svc.DescribeInstances(ctx, input) if err != nil { if retriable(err) { @@ -270,7 +270,7 @@ func (c *ec2Client) setEC2SourceDestinationCheck(ctx context.Context, ec2NetId s } var err error - for i := 0; i < retries; i++ { + for range retries { _, err = c.EC2Svc.ModifyNetworkInterfaceAttribute(ctx, input) if err != nil { if retriable(err) { diff --git a/felix/aws/ec2_suite_test.go b/felix/aws/ec2_suite_test.go index 621d959a88f..62b8542c2e0 100644 --- a/felix/aws/ec2_suite_test.go +++ b/felix/aws/ec2_suite_test.go @@ -17,9 +17,8 @@ package aws_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestUpdateEC2Instance(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/aws_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "AWS Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_aws_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/aws", suiteConfig, reporterConfig) } diff --git a/felix/aws/ec2_test.go b/felix/aws/ec2_test.go index b17078143cf..b2492191640 100644 --- a/felix/aws/ec2_test.go +++ b/felix/aws/ec2_test.go @@ -25,7 +25,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/aws/aws-sdk-go-v2/service/ec2/types" "github.com/aws/smithy-go" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" clock "k8s.io/utils/clock/testing" diff --git a/felix/bpf-apache/bpf.h b/felix/bpf-apache/bpf.h index 81b97994db3..d06240ed4c8 100644 --- a/felix/bpf-apache/bpf.h +++ b/felix/bpf-apache/bpf.h @@ -58,18 +58,6 @@ struct ip4key { __u32 addr; }; -union ip4_bpf_lpm_trie_key { - struct bpf_lpm_trie_key lpm; - struct ip4key ip; -}; - -// helper functions -CALI_BPF_INLINE void ip4val_to_lpm( - union ip4_bpf_lpm_trie_key *ret, __u32 mask, __u32 addr) { - ret->lpm.prefixlen = mask; - ret->ip.addr = addr; -} - CALI_BPF_INLINE __u32 port_to_host(__u32 port) { return be32_to_host(port) >> 16; } diff --git a/felix/bpf-apache/filter.c b/felix/bpf-apache/filter.c index fa6990fd90b..43bc2c7fb5d 100644 --- a/felix/bpf-apache/filter.c +++ b/felix/bpf-apache/filter.c @@ -59,7 +59,7 @@ enum xdp_action prefilter(struct xdp_md* xdp) struct ethhdr * ehdr; struct iphdr * ihdr; struct protoport dport = {0,0}; - union ip4_bpf_lpm_trie_key sip; + struct ip4key sip; // You must be at least 'UDP header' tall to take this ride. if (xdp->data + sizeof(*ehdr) + sizeof(*ihdr) + sizeof(struct udphdr) @@ -87,8 +87,8 @@ enum xdp_action prefilter(struct xdp_md* xdp) } } - ip4val_to_lpm(&sip, 32, ihdr->saddr); - + sip.addr = ihdr->saddr; + sip.mask = 32; // Drop the packet if source IP matches a blocklist entry. if (NULL != bpf_map_lookup_elem(&calico_prefilter_v4, &sip)) { // In blocklist - "thou shall not XDP_PASS!" diff --git a/felix/bpf-apache/filter.h b/felix/bpf-apache/filter.h index 221d000f459..bbac597e308 100644 --- a/felix/bpf-apache/filter.h +++ b/felix/bpf-apache/filter.h @@ -21,7 +21,7 @@ struct protoport { struct { __uint(type, BPF_MAP_TYPE_LPM_TRIE); - __type(key, union ip4_bpf_lpm_trie_key); + __type(key, struct ip4key); __type(value, __u32); __uint(max_entries, 10240); __uint(map_flags, BPF_F_NO_PREALLOC); diff --git a/felix/bpf-apache/sockops.c b/felix/bpf-apache/sockops.c index 3a7d732e79c..a61bb42cc5c 100644 --- a/felix/bpf-apache/sockops.c +++ b/felix/bpf-apache/sockops.c @@ -17,7 +17,7 @@ struct { __uint(type, BPF_MAP_TYPE_LPM_TRIE); - __type(key, union ip4_bpf_lpm_trie_key); + __type(key, struct ip4key); __type(value, __u32); __uint(max_entries, 65535); __uint(map_flags, BPF_F_NO_PREALLOC); @@ -27,7 +27,7 @@ __attribute__((section("calico_sockops_func"))) enum bpf_ret_code calico_sockops(struct bpf_sock_ops *skops) { struct sock_key key = {}; - union ip4_bpf_lpm_trie_key sip, dip; + struct ip4key sip, dip; __u32 sport, dport; switch (skops->op) { @@ -42,9 +42,10 @@ enum bpf_ret_code calico_sockops(struct bpf_sock_ops *skops) return BPF_OK; } - ip4val_to_lpm(&sip, 32, skops->local_ip4); - ip4val_to_lpm(&dip, 32, skops->remote_ip4); - + sip.addr = skops->local_ip4; + dip.addr = skops->remote_ip4; + sip.mask = 32; + dip.mask = 32; // If neither source nor dest are present in the Felix-populated endpoints // map we do nothing because the packet is not related to Felix-managed // traffic. @@ -58,12 +59,12 @@ enum bpf_ret_code calico_sockops(struct bpf_sock_ops *skops) // We use the app's port and ip address as key. The socket attached // to our in-kernel context will be stored as the value automatically. - if (sip.ip.addr == ENVOY_IP && sport == ENVOY_PORT) { + if (sip.addr == ENVOY_IP && sport == ENVOY_PORT) { // If the source is envoy, the app is on the destination side. // We set envoy_side to 1 so the sockmap-attached BPF program // (sk_msg in redir.c) can identify packets going to envoy. key.envoy_side = 1; - key.ip4 = dip.ip.addr; + key.ip4 = dip.addr; key.port = dport; } else { // If the source IP is not envoy we assume it comes from the app (if it @@ -74,7 +75,7 @@ enum bpf_ret_code calico_sockops(struct bpf_sock_ops *skops) // because we get executed before the destination address is rewritten // by iptables so the packet from the app still has the destination // address of some other service. We handle the general case. - key.ip4 = sip.ip.addr; + key.ip4 = sip.addr; key.port = sport; key.envoy_side = 0; } diff --git a/felix/bpf-gpl/Makefile b/felix/bpf-gpl/Makefile index 613663251dc..1ec6a0c0462 100644 --- a/felix/bpf-gpl/Makefile +++ b/felix/bpf-gpl/Makefile @@ -1,5 +1,5 @@ # Project Calico BPF dataplane build scripts. -# Copyright (c) 2020-2022 Tigera, Inc. All rights reserved. +# Copyright (c) 2020-2026 Tigera, Inc. All rights reserved. # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later # Disable implicit rules. @@ -58,6 +58,7 @@ LD := llc UT_C_FILES:=$(shell find ut -name '*.c') UT_OBJS:=$(UT_C_FILES:.c=.o) $(shell ./list-ut-objs) UT_OBJS+=ut/ip_parse_test_v6.o +UT_OBJS+=ut/tcp_rst_v6.o XDP_MAP_HEADERS := jump.h COMMON_MAP_HEADERS := counters.h ifstate.h perf_types.h profiling.h rule_counters.h qos.h ctlb_map.h $(XDP_MAP_HEADERS) @@ -65,7 +66,7 @@ IP_MAP_HEADERS := arp.h conntrack_cleanup.h conntrack_types.h failsafe.h nat_typ IPV4_MAP_HEADERS := $(IP_MAP_HEADERS) ip_v4_fragment.h IPV6_MAP_HEADERS := $(IP_MAP_HEADERS) -MAP_OBJS := $(addprefix bin/, common_map_stub.o ipv4_map_stub.o ipv6_map_stub.o xdp_map_stub.o) +MAP_OBJS := $(addprefix bin/, common_map_stub.o ipv4_map_stub.o ipv6_map_stub.o xdp_map_stub.o common_map_stub_ing.o) # Map stub ojbs cannot have '-emit-llvm' in flags MAP_CFLAGS := $(subst -emit-llvm,,$(CFLAGS)) @@ -96,6 +97,9 @@ xdp_map_stub.c: $(XDP_MAP_HEADERS) bin/common_map_stub.o bin/ipv4_map_stub.o: bin/%.o: %.c %.d | bin $(CC) $(MAP_CFLAGS) -c $< -o $@ +bin/common_map_stub_ing.o: bin/%.o: common_map_stub.c common_map_stub.d | bin + $(CC) $(MAP_CFLAGS) -DCALI_COMPILE_FLAGS=2 -c $< -o $@ + bin/ipv6_map_stub.o: bin/%.o: %.c %.d | bin $(CC) $(MAP_CFLAGS) -DIPVER6 -c $< -o $@ @@ -104,10 +108,12 @@ bin/xdp_map_stub.o: bin/%.o: %.c %.d | bin $(CC) $(MAP_CFLAGS) -DCALI_COMPILE_FLAGS=64 -c $< -o $@ OBJS:=$(shell ./list-objs) -OBJS+=bin/tc_preamble.o +OBJS+=bin/tc_preamble_ingress.o +OBJS+=bin/tc_preamble_egress.o OBJS+=bin/tcx_test.o OBJS+=bin/xdp_preamble.o -OBJS+=bin/policy_default.o +OBJS+=bin/policy_default_ingress.o +OBJS+=bin/policy_default_egress.o OBJS+=$(MAP_OBJS) C_FILES:=tc_preamble.c tc.c connect_balancer.c connect_balancer_v46.c connect_balancer_v6.c xdp_preamble.c xdp.c policy_default.c @@ -144,17 +150,20 @@ ut/%.ll: ut/%.c ut/ut.h ut/icmp6_port_unreachable.ll: CFLAGS += -DIPVER6 -tc_preamble.ll: tc_preamble.c tc_preamble.d - $(CC) $(CFLAGS) -c $< -o $@ - +tc_preamble_ingress.ll: tc_preamble.c tc_preamble.d + $(COMPILE) +tc_preamble_egress.ll: tc_preamble.c tc_preamble.d + $(COMPILE) tcx_test.ll: tcx_test.c tcx_test.d $(CC) $(CFLAGS) -c $< -o $@ xdp_preamble.ll: xdp_preamble.c xdp_preamble.d $(CC) $(CFLAGS) -DCALI_COMPILE_FLAGS=64 -c $< -o $@ -policy_default.ll: policy_default.c policy_default.d - $(CC) $(CFLAGS) -c $< -o $@ +policy_default_ingress.ll: policy_default.c policy_default.d + $(COMPILE) +policy_default_egress.ll: policy_default.c policy_default.d + $(COMPILE) # Production and UT versions of the main binaries. # Combining the targets into one rule causes make to fail to rebuild the .ll files. Not sure why. @@ -186,13 +195,17 @@ xdp_v6.d: xdp.c LINK=$(LD) -march=bpf -filetype=obj -o $@ $< -bin/tc_preamble.o: tc_preamble.ll | bin +bin/tc_preamble_ingress.o: tc_preamble_ingress.ll | bin + $(LINK) +bin/tc_preamble_egress.o: tc_preamble_egress.ll | bin $(LINK) bin/tcx_test.o: tcx_test.ll | bin $(LINK) bin/xdp_preamble.o: xdp_preamble.ll | bin $(LINK) -bin/policy_default.o: policy_default.ll | bin +bin/policy_default_ingress.o: policy_default_ingress.ll | bin + $(LINK) +bin/policy_default_egress.o: policy_default_egress.ll | bin $(LINK) bin/to%.o: to%.ll | bin $(LINK) @@ -208,6 +221,10 @@ ut/ip_parse_test_v6.ll: ut/ip_parse_test.c $(CC) $(UT_CFLAGS) $(CFLAGS) -DIPVER6 -c $< -o $@ ut/ip_parse_test_v6.o: ut/ip_parse_test_v6.ll $(LINK) +ut/tcp_rst_v6.ll: ut/tcp_rst.c + $(CC) $(UT_CFLAGS) $(CFLAGS) -DIPVER6 -c $< -o $@ +ut/tcp_rst_v6.o: ut/tcp_rst_v6.ll + $(LINK) %_v4.ll: %.c %.d calculate-flags $(COMPILE) @@ -302,7 +319,7 @@ COMPILE_DEPS=set -e; rm -f $@; \ ifneq ($(MAKECMDGOALS),clean) ifneq ($(MAKECMDGOALS), libbpf) - include $(shell ls *.d) + include $(wildcard *.d) endif endif diff --git a/felix/bpf-gpl/bpf.h b/felix/bpf-gpl/bpf.h index 2acfa3c173e..7e1c2c3c1d7 100644 --- a/felix/bpf-gpl/bpf.h +++ b/felix/bpf-gpl/bpf.h @@ -55,6 +55,8 @@ #define CALI_TC_LO (1<<8) #define CALI_CT_CLEANUP (1<<9) #define CALI_TC_VXLAN (1<<10) +#define CALI_TC_PREAMBLE (1<<11) +#define CALI_TC_DEF_POLICY (1<<12) #ifndef CALI_DROP_WORKLOAD_TO_HOST #define CALI_DROP_WORKLOAD_TO_HOST false @@ -74,6 +76,7 @@ #define CALI_F_NAT_IF (((CALI_COMPILE_FLAGS) & CALI_TC_NAT_IF) != 0) #define CALI_F_LO (((CALI_COMPILE_FLAGS) & CALI_TC_LO) != 0) #define CALI_F_CT_CLEANUP (((CALI_COMPILE_FLAGS) & CALI_CT_CLEANUP) != 0) +#define CALI_F_DEF_POLICY (((CALI_COMPILE_FLAGS) & CALI_TC_DEF_POLICY) != 0) #define CALI_F_MAIN (CALI_F_HEP && !CALI_F_IPIP && !CALI_F_L3_DEV && !CALI_F_NAT_IF && !CALI_F_LO) @@ -84,6 +87,7 @@ #define CALI_F_FROM_WEP (CALI_F_WEP && CALI_F_EGRESS) #define CALI_F_TO_WEP (CALI_F_WEP && CALI_F_INGRESS) +#define CALI_F_PREAMBLE (((CALI_COMPILE_FLAGS) & CALI_TC_PREAMBLE) != 0) #define CALI_F_TO_HOST ((CALI_F_FROM_HEP || CALI_F_FROM_WEP) != 0) #define CALI_F_FROM_HOST (!CALI_F_TO_HOST) @@ -99,6 +103,23 @@ #define CALI_F_CGROUP (((CALI_COMPILE_FLAGS) & CALI_CGROUP) != 0) #define CALI_F_DSR ((CALI_COMPILE_FLAGS & CALI_TC_DSR) != 0) +#if CALI_F_HEP || CALI_F_PREAMBLE || CALI_F_DEF_POLICY +// For HEPs policy direction (CALI_F_INGRESS) matches attachment direction. +#if CALI_F_INGRESS +#define CALI_HOOK_INGRESS +#else +#define CALI_HOOK_EGRESS +#endif +#else +// For WEPs, the policy direction is opposite to the tc hook that the +// program is attached to. +#if CALI_F_INGRESS +#define CALI_HOOK_EGRESS +#else +#define CALI_HOOK_INGRESS +#endif +#endif + #define CALI_RES_REDIR_BACK 108 /* packet should be sent back the same iface */ #define CALI_RES_REDIR_IFINDEX 109 /* packet should be sent straight to * state->ct_result->ifindex_fwd @@ -107,11 +128,9 @@ #error CALI_RES_ values need to be increased above TC_ACT_VALUE_MAX #endif -#ifndef CALI_FIB_LOOKUP_ENABLED -#define CALI_FIB_LOOKUP_ENABLED true -#endif +#define HAS_MAGLEV (CALI_F_FROM_HEP && CALI_F_MAIN) -#define CALI_FIB_ENABLED (!CALI_F_L3 && CALI_FIB_LOOKUP_ENABLED && (CALI_F_TO_HOST || CALI_F_TO_HEP)) +#define CALI_FIB_ENABLED (CALI_F_TO_HOST || CALI_F_TO_HEP) #define COMPILE_TIME_ASSERT(expr) {typedef char array[(expr) ? 1 : -1];} static CALI_BPF_INLINE void __compile_asserts(void) { @@ -122,7 +141,7 @@ static CALI_BPF_INLINE void __compile_asserts(void) { CALI_COMPILE_FLAGS == 0 || CALI_F_CT_CLEANUP || !!(CALI_COMPILE_FLAGS & CALI_CGROUP) != - !!(CALI_COMPILE_FLAGS & (CALI_TC_HOST_EP | CALI_TC_INGRESS | CALI_TC_IPIP | CALI_TC_DSR | CALI_XDP_PROG)) + !!(CALI_COMPILE_FLAGS & (CALI_TC_HOST_EP | CALI_TC_INGRESS | CALI_TC_IPIP | CALI_TC_DSR | CALI_XDP_PROG | CALI_TC_PREAMBLE | CALI_TC_DEF_POLICY)) ); COMPILE_TIME_ASSERT(!CALI_F_DSR || (CALI_F_DSR && CALI_F_FROM_WEP) || (CALI_F_DSR && CALI_F_HEP)); COMPILE_TIME_ASSERT(CALI_F_TO_HOST || CALI_F_FROM_HOST); @@ -180,7 +199,7 @@ enum calico_skb_mark { CALI_SKB_MARK_BYPASS_FWD = CALI_SKB_MARK_BYPASS | 0x00300000, /* Accepted by XDP untracked policy. */ CALI_SKB_MARK_BYPASS_XDP = CALI_SKB_MARK_BYPASS | 0x00500000, - CALI_SKB_MARK_BYPASS_MASK = CALI_SKB_MARK_SEEN_MASK | 0x02700000, + CALI_SKB_MARK_BYPASS_MASK = CALI_SKB_MARK_SEEN_MASK | 0x02f00000, /* The FALLTHROUGH bit is used by programs that are towards the host namespace to indicate * that the packet is not known in BPF conntrack. We have iptables rules to drop or allow * such packets based on their Linux conntrack state. This allows for us to handle flows that @@ -223,14 +242,14 @@ enum calico_skb_mark { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Winvalid-noreturn" static CALI_BPF_INLINE __attribute__((noreturn)) void bpf_exit(int rc) { - // Need volatile here because we don't use rc after this assembler fragment. - // The BPF assembler rejects an input-only operand so we make r0 an in/out operand. - asm volatile ( \ - "exit" \ - : "=r0" (rc) /*out*/ \ - : "0" (rc) /*in*/ \ - : /*clobber*/ \ + asm volatile ( + "r0 = %[rc_arg]\n" //Explicitly move the value of 'rc' into register R0 to prohibit excessive compiler optimization and make the verifier happy + "exit" + : // No output operands + : [rc_arg] "r" (rc) // Input: rc to a general purpose register + : "r0" // Clobber list: R0 is modified by the assembly code ); + __builtin_unreachable(); // Tell the compiler that this function never returns } #pragma clang diagnostic pop @@ -327,20 +346,6 @@ extern const volatile struct cali_tc_preamble_globals __globals; #define INGRESS_PACKET_RATE_CONFIGURED (GLOBAL_FLAGS & CALI_GLOBALS_INGRESS_PACKET_RATE_CONFIGURED) #define EGRESS_PACKET_RATE_CONFIGURED (GLOBAL_FLAGS & CALI_GLOBALS_EGRESS_PACKET_RATE_CONFIGURED) -#ifdef UNITTEST -#define CALI_PATCH_DEFINE(name, pattern) \ -static CALI_BPF_INLINE __be32 cali_patch_##name() \ -{ \ - __u32 ret; \ - asm("%0 = " #pattern ";" : "=r"(ret) /* output */ : /* no inputs */ : /* no clobber */);\ - return ret; \ -} -#define CALI_PATCH(name) cali_patch_##name() - -CALI_PATCH_DEFINE(__skb_mark, 0x4d424b53) /* be 0x4d424b53 = ASCII(SKBM) */ -#define SKB_MARK CALI_PATCH(__skb_mark) -#endif - #define map_symbol(name, ver) name##ver #define MAP_LOOKUP_FN(fname, name, ver) \ diff --git a/felix/bpf-gpl/calculate-flags b/felix/bpf-gpl/calculate-flags index ea69d05a16e..cc83c6c1bac 100755 --- a/felix/bpf-gpl/calculate-flags +++ b/felix/bpf-gpl/calculate-flags @@ -14,9 +14,6 @@ if [[ "${filename}" =~ .*debug.* ]]; then args+=("-DCALI_LOG_LEVEL=CALI_LOG_LEVEL_DEBUG") elif [[ "${filename}" =~ .*no_log.* ]]; then args+=("-DCALI_LOG_LEVEL=CALI_LOG_LEVEL_OFF") -else - echo "No log level in filename" - exit 1 fi if [[ "${filename}" =~ .*host_drop.* ]]; then @@ -25,12 +22,6 @@ else args+=("-DCALI_DROP_WORKLOAD_TO_HOST=false") fi -if [[ "${filename}" =~ .*fib.* ]]; then - args+=("-DCALI_FIB_LOOKUP_ENABLED=true") -else - args+=("-DCALI_FIB_LOOKUP_ENABLED=false") -fi - if [[ "${filename}" =~ test_.* ]]; then args+=("-DUNITTEST") args+=("-DBPF_CORE_SUPPORTED") @@ -54,6 +45,8 @@ flags=0 ((CALI_TC_LO = 1 << 8)) ((CALI_CT_CLEANUP = 1 << 9)) ((CALI_TC_VXLAN = 1 << 10)) +((CALI_TC_PREAMBLE = 1 << 11)) +((CALI_TC_DEF_POLICY = 1 << 12)) if [[ "${filename}" =~ .*hep.* ]]; then # Host endpoint. @@ -113,6 +106,19 @@ if [[ "${filename}" =~ _dsr.* ]]; then ((flags |= CALI_TC_DSR)) fi +if [[ "${filename}" =~ policy_default.* ]]; then + ((flags = CALI_TC_DEF_POLICY)) + if [[ "${filename}" =~ .*_ingress.* ]]; then + ((flags |= CALI_TC_INGRESS)) + fi +fi + +if [[ "${filename}" =~ _preamble.* ]]; then + ((flags |= CALI_TC_PREAMBLE)) + if [[ "${filename}" =~ _ingress.* ]]; then + ((flags |= CALI_TC_INGRESS)) + fi +fi args+=("-DCALI_COMPILE_FLAGS=${flags}") echo "Flags: ${args[*]}" 1>&2 diff --git a/felix/bpf-gpl/connect_balancer_v46.c b/felix/bpf-gpl/connect_balancer_v46.c index 1f6b297bdbd..cfbcca23357 100644 --- a/felix/bpf-gpl/connect_balancer_v46.c +++ b/felix/bpf-gpl/connect_balancer_v46.c @@ -28,12 +28,6 @@ static CALI_BPF_INLINE bool is_ipv4_as_ipv6(__u32 *addr) { return addr[0] == 0 && addr[1] == 0 && addr[2] == bpf_htonl(0x0000ffff); } -enum cali_ctlb_prog_index { - PROG_INDEX_V6_CONNECT, - PROG_INDEX_V6_SENDMSG, - PROG_INDEX_V6_RECVMSG, -}; - SEC("cgroup/connect6") int calico_connect_v46(struct bpf_sock_addr *ctx) { @@ -55,7 +49,7 @@ int calico_connect_v46(struct bpf_sock_addr *ctx) goto v4; } - bpf_tail_call(ctx, &cali_ctlb_progs, PROG_INDEX_V6_CONNECT); + bpf_tail_call(ctx, &cali_ctlb_conn, 0); goto out; v4: @@ -95,7 +89,7 @@ int calico_sendmsg_v46(struct bpf_sock_addr *ctx) goto v4; } - bpf_tail_call(ctx, &cali_ctlb_progs, PROG_INDEX_V6_SENDMSG); + bpf_tail_call(ctx, &cali_ctlb_send, 0); goto out; v4: @@ -136,7 +130,7 @@ int calico_recvmsg_v46(struct bpf_sock_addr *ctx) goto v4; } - bpf_tail_call(ctx, &cali_ctlb_progs, PROG_INDEX_V6_RECVMSG); + bpf_tail_call(ctx, &cali_ctlb_recv, 0); goto out; diff --git a/felix/bpf-gpl/conntrack.h b/felix/bpf-gpl/conntrack.h index 19b299087eb..f202293c73c 100644 --- a/felix/bpf-gpl/conntrack.h +++ b/felix/bpf-gpl/conntrack.h @@ -336,7 +336,7 @@ static CALI_BPF_INLINE bool skb_icmp_err_unpack(struct cali_tc_ctx *ctx, struct if (ctx->ipheader_len == 20) { if (skb_refresh_validate_ptrs(ctx, ICMP_SIZE + sizeof(struct iphdr) + 8)) { deny_reason(ctx, CALI_REASON_SHORT); - ctx->fwd.res = TC_ACT_SHOT; + ctx->state->fwd.res = TC_ACT_SHOT; CALI_DEBUG("ICMP v4 reply: too short getting hdr"); return false; } @@ -788,7 +788,13 @@ static CALI_BPF_INLINE struct calico_ct_result calico_ct_lookup(struct cali_tc_c !ip_equal(result.tun_ip, ctx->state->tun_ip)) { CALI_CT_DEBUG("tunnel src changed from " IP_FMT " to " IP_FMT "", debug_ip(result.tun_ip), debug_ip(ctx->state->tun_ip)); + ct_result_set_flag(result.rc, CT_RES_TUN_SRC_CHANGED); + + if (result.flags & CALI_CT_FLAG_MAGLEV) { + CALI_DEBUG("overwriting tunnel src for Maglev packet"); + tracking_v->tun_ip = ctx->state->tun_ip; + } } if (tracking_v->a_to_b.approved && tracking_v->b_to_a.approved) { diff --git a/felix/bpf-gpl/conntrack_types.h b/felix/bpf-gpl/conntrack_types.h index df69cbe1519..13027b66860 100644 --- a/felix/bpf-gpl/conntrack_types.h +++ b/felix/bpf-gpl/conntrack_types.h @@ -39,7 +39,9 @@ enum cali_ct_type { #define CALI_CT_FLAG_NP_REMOTE 0x1000 /* marks connections from local host to remote backend of a nodeport */ #define CALI_CT_FLAG_NP_NO_DSR 0x2000 /* marks connections from a client which is excluded from DSR */ #define CALI_CT_FLAG_SKIP_REDIR_PEER 0x4000 /* marks connections from a client which is excluded from redir */ -#define CALI_CT_FLAG_CLUSTER_EXTERNAL 0x8000 /* marks connections with source or destination outside cluster */ +#define CALI_CT_FLAG_SET_DSCP 0x8000 /* marks connections that needs to set DSCP */ +#define CALI_CT_FLAG_MAGLEV 0X10000 /* marks Maglev connections. Allows packets of an existing to arrive via a different tunnel after failover. */ +#define CALI_CT_FLAG_SEND_RESET 0x20000 /* marks connections where we should send a TCP RST on behalf of the workload */ struct calico_ct_leg { __u64 bytes; @@ -67,28 +69,30 @@ struct calico_ct_leg { #define CT_INVALID_IFINDEX 0 struct calico_ct_value { __u64 rst_seen; - __u64 last_seen; // 8 - __u8 type; // 16 + __u64 last_seen; // 8 + __u8 type; // 16 __u8 flags; + __u8 flags3; + __u8 flags4; // Important to use explicit padding, otherwise the compiler can decide // not to zero the padding bytes, which upsets the verifier. Worse than // that, debug logging often prevents such optimisation resulting in // failures when debug logging is compiled out only :-). - __u8 pad0[5]; + __u8 pad0[3]; __u8 flags2; union { // CALI_CT_TYPE_NORMAL and CALI_CT_TYPE_NAT_REV. struct { - struct calico_ct_leg a_to_b; // 24 - struct calico_ct_leg b_to_a; // 48 + struct calico_ct_leg a_to_b; // 24 + struct calico_ct_leg b_to_a; // 48 // CALI_CT_TYPE_NAT_REV - ipv46_addr_t tun_ip; // 72 - ipv46_addr_t orig_ip; // 76 - __u16 orig_port; // 80 - __u16 orig_sport; // 82 - ipv46_addr_t orig_sip; // 84 + ipv46_addr_t tun_ip; // 72 + ipv46_addr_t orig_ip; // 76 + __u16 orig_port; // 80 + __u16 orig_sport; // 82 + ipv46_addr_t orig_sip; // 84 }; // CALI_CT_TYPE_NAT_FWD; key for the CALI_CT_TYPE_NAT_REV entry. @@ -120,12 +124,14 @@ static CALI_BPF_INLINE void __xxx_compile_asserts(void) { #define ct_value_set_flags(v, f) do { \ (v)->flags |= ((f) & 0xff); \ (v)->flags2 |= (((f) >> 8) & 0xff); \ + (v)->flags3 |= (((f) >> 16) & 0xff); \ + (v)->flags4 |= (((f) >> 24) & 0xff); \ } while(0) -#define ct_value_get_flags(v) ({ \ - __u16 ret = (v)->flags | ((v)->flags2 << 8); \ - \ - ret; \ +#define ct_value_get_flags(v) ({ \ + __u32 ret = (v)->flags | ((v)->flags2 << 8) | ((v)->flags3 << 16) | ((v)->flags4 << 24); \ + \ + ret; \ }) struct ct_lookup_ctx { @@ -150,9 +156,9 @@ struct ct_create_ctx { ipv46_addr_t tun_ip; /* is set when the packet arrive through the NP tunnel. * It is also set on the first node when we create the * initial CT entry for the tunneled traffic. */ - __u16 flags; + __u32 flags; __u8 proto; - __u8 __pad; + __u8 __pad[3]; enum cali_ct_type type; bool allow_return; }; @@ -199,6 +205,11 @@ enum calico_ct_result_type { * or for packet that have a conntrack entry that is only approved by the other leg * (indicating that policy on this leg failed to allow the packet). */ CALI_CT_INVALID = 6, + /* CALI_CT_MAGLEV_MID_FLOW_MISS is set (in the Maglev program) for packets which were + * originally CALI_CT_MID_FLOW_MISS, but where the maglev-lookup returned a backend. + * It indicates that a midflow Maglev packet should be treated as a failed-over connection. + * This is similar to handling CALI_CT_NEW. */ + CALI_CT_MAGLEV_MID_FLOW_MISS = 7, }; #define CT_RES_RELATED 0x100 @@ -224,7 +235,8 @@ enum calico_ct_result_type { struct calico_ct_result { __s16 rc; - __u16 flags; + __u16 pad; + __u32 flags; ipv46_addr_t nat_ip; ipv46_addr_t nat_sip; __u16 nat_port; diff --git a/felix/bpf-gpl/counters.h b/felix/bpf-gpl/counters.h index 45528d205a2..6f35a3d3d45 100644 --- a/felix/bpf-gpl/counters.h +++ b/felix/bpf-gpl/counters.h @@ -8,7 +8,7 @@ #include "bpf.h" #include "types.h" -#define MAX_COUNTERS_SIZE 24 +#define MAX_COUNTERS_SIZE 26 typedef __u64 counters_t[MAX_COUNTERS_SIZE]; @@ -21,15 +21,11 @@ struct counters_key { #define COUNTERS_TC_EGRESS 1 #define COUNTERS_XDP 2 -CALI_MAP(cali_counters, 5, +CALI_MAP(cali_counters, 6, BPF_MAP_TYPE_PERCPU_HASH, struct counters_key, counters_t, 20000, 0) -CALI_MAP(cali_counters_scratch, 3, - BPF_MAP_TYPE_PERCPU_ARRAY, - __u32, counters_t, 1, 0) - static CALI_BPF_INLINE counters_t *counters_get(int ifindex) { struct counters_key key = { @@ -60,7 +56,7 @@ static CALI_BPF_INLINE void counter_inc(struct cali_tc_ctx *ctx, int type) static CALI_BPF_INLINE void deny_reason(struct cali_tc_ctx *ctx, int reason) { - ctx->fwd.reason = reason; + ctx->state->fwd.reason = reason; counter_inc(ctx, reason); } diff --git a/felix/bpf-gpl/ctlb_map.h b/felix/bpf-gpl/ctlb_map.h index 1ea480d8bb8..03a66009b1f 100644 --- a/felix/bpf-gpl/ctlb_map.h +++ b/felix/bpf-gpl/ctlb_map.h @@ -2,6 +2,9 @@ // Copyright (c) 2020-2025 Tigera, Inc. All rights reserved. // SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +#ifndef __CALI_CTLB_MAPS_H__ +#define __CALI_CTLB_MAPS_H__ + #include #include #include "bpf.h" @@ -10,7 +13,24 @@ struct { __uint(type, BPF_MAP_TYPE_PROG_ARRAY); __type(key, __u32); __type(value, __u32); - __uint(max_entries, 3); + __uint(max_entries, 1); + __uint(map_flags, 0); +}cali_ctlb_conn SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_PROG_ARRAY); + __type(key, __u32); + __type(value, __u32); + __uint(max_entries, 1); + __uint(map_flags, 0); +}cali_ctlb_send SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_PROG_ARRAY); + __type(key, __u32); + __type(value, __u32); + __uint(max_entries, 1); __uint(map_flags, 0); -}cali_ctlb_progs SEC(".maps"); +}cali_ctlb_recv SEC(".maps"); +#endif diff --git a/felix/bpf-gpl/fib_co_re.h b/felix/bpf-gpl/fib_co_re.h index a2ebbe7a870..fb2d1105424 100644 --- a/felix/bpf-gpl/fib_co_re.h +++ b/felix/bpf-gpl/fib_co_re.h @@ -6,25 +6,51 @@ #define __CALI_FIB_CO_RE_H__ #include "profiling.h" - +#ifndef IPVER6 +#include "ip_v4_fragment.h" +#endif #include +static CALI_BPF_INLINE int make_room_for_l2_header(struct cali_tc_ctx *ctx) +{ + int rc = bpf_skb_change_head(ctx->skb, ETH_HLEN, 0); + if (rc < 0) { + CALI_DEBUG("bpf_skb_change_head failed %d.", rc); + return rc; + } + if (skb_refresh_validate_ptrs(ctx, UDP_SIZE)) { + CALI_DEBUG("Too short"); + return -1; + } +#ifdef IPVER6 + eth_hdr(ctx)->h_proto = bpf_htons(ETH_P_IPV6); +#else + eth_hdr(ctx)->h_proto = bpf_htons(ETH_P_IP); +#endif + return 0; +} + static CALI_BPF_INLINE int try_redirect_to_peer(struct cali_tc_ctx *ctx) { struct cali_tc_state *state = ctx->state; + int rc = 0; bool redirect_peer = GLOBAL_FLAGS & CALI_GLOBALS_REDIRECT_PEER; - if (redirect_peer && ct_result_rc(state->ct_result.rc) == CALI_CT_ESTABLISHED_BYPASS && state->ct_result.ifindex_fwd != CT_INVALID_IFINDEX && !(ctx->state->ct_result.flags & CALI_CT_FLAG_SKIP_REDIR_PEER)) { - int rc = bpf_redirect_peer(state->ct_result.ifindex_fwd, 0); + if (CALI_F_L3_DEV) { + rc = make_room_for_l2_header(ctx); + if (rc < 0) { + return TC_ACT_UNSPEC; + } + } + rc = bpf_redirect_peer(state->ct_result.ifindex_fwd, 0); if (rc == TC_ACT_REDIRECT) { counter_inc(ctx, CALI_REDIRECT_PEER); CALI_DEBUG("Redirect to peer interface (%d) succeeded.", state->ct_result.ifindex_fwd); return rc; } } - return TC_ACT_UNSPEC; } @@ -41,13 +67,36 @@ static CALI_BPF_INLINE void fib_error_log(struct cali_tc_ctx *ctx,int rc) static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) { - int rc = ctx->fwd.res; + int rc = ctx->state->fwd.res; struct cali_tc_state *state = ctx->state; + __u32 fib_flags = 0; if (rc == TC_ACT_SHOT) { goto deny; } + if (!bpf_core_field_exists(((struct bpf_fib_lookup *)0)->mark)) { + if (CALI_F_FROM_WEP && EXT_TO_SVC_MARK && ctx->state->ct_result.flags & CALI_CT_FLAG_EXT_LOCAL) { + /* needs to go via routing in netfilter unless we have access + * to BPF_FIB_LOOKUP_MARK in kernel 6.10+ + */ + goto skip_fib; + } + } else { + fib_flags = BPF_FIB_LOOKUP_MARK; + } + +#ifndef IPVER6 + if ((CALI_F_FROM_HOST || CALI_F_FROM_WEP) && (ctx->state->flags & CALI_ST_FIRST_FRAG)) { + /* Revalidate the access to the packet */ + if (skb_refresh_validate_ptrs(ctx, UDP_SIZE)) { + deny_reason(ctx, CALI_REASON_SHORT); + CALI_DEBUG("Too short"); + goto deny; + } + frags4_record_ct(ctx); + } +#endif if (ctx->state->flags & CALI_ST_SKIP_REDIR_ONCE) { goto skip_fib; } @@ -130,7 +179,7 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) if ((rc = try_redirect_to_peer(ctx)) == TC_ACT_REDIRECT) { goto skip_fib; } - } else if (CALI_F_FROM_WEP && fwd_fib(&ctx->fwd)) { + } else if (CALI_F_FROM_WEP && fwd_fib(&ctx->state->fwd)) { struct cali_rt *dest_rt = cali_rt_lookup(&ctx->state->ip_dst); if (dest_rt == NULL) { CALI_DEBUG("No route for " IP_FMT " to forward from WEP", &ctx->state->ip_dst); @@ -153,6 +202,11 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) .ifindex = ctx->skb->ifindex, .l4_protocol = state->ip_proto, }; + + if (bpf_core_field_exists(((struct bpf_fib_lookup *)0)->mark)) { + fib_params(ctx)->mark = EXT_TO_SVC_MARK; + } + #ifdef IPVER6 ipv6_addr_t_to_be32_4_ip(fib_params(ctx)->ipv6_src, &state->ip_src); ipv6_addr_t_to_be32_4_ip(fib_params(ctx)->ipv6_dst, &state->ip_dst); @@ -161,15 +215,22 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) fib_params(ctx)->ipv4_dst = state->ip_dst; #endif - rc = bpf_fib_lookup(ctx->skb, fib_params(ctx), sizeof(struct bpf_fib_lookup), 0); - state->ct_result.ifindex_fwd = fib_params(ctx)->ifindex; + rc = bpf_fib_lookup(ctx->skb, fib_params(ctx), sizeof(struct bpf_fib_lookup), fib_flags); + switch (rc) { + case 0: + case BPF_FIB_LKUP_RET_NO_NEIGH: + state->ct_result.ifindex_fwd = fib_params(ctx)->ifindex; + break; + default: + goto try_fib_external; + } } if (cali_rt_flags_local_workload(dest_rt->flags)) { if ((rc = try_redirect_to_peer(ctx)) == TC_ACT_REDIRECT) { goto skip_fib; } - } else if (cali_rt_is_vxlan(dest_rt) && !(cali_rt_is_same_subnet(dest_rt))) { + } else if ((cali_rt_is_tunneled(dest_rt) && !cali_rt_is_same_subnet(dest_rt))) { struct bpf_tunnel_key key = { .tunnel_id = OVERLAY_TUNNEL_ID, }; @@ -187,7 +248,7 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) int err = bpf_skb_set_tunnel_key(ctx->skb, &key, size, flags); CALI_DEBUG("bpf_skb_set_tunnel_key %d nh " IP_FMT, err, &dest_rt->next_hop); - ctx->fwd.mark |= CALI_SKB_MARK_TUNNEL_KEY_SET; + ctx->state->fwd.mark |= CALI_SKB_MARK_TUNNEL_KEY_SET; rc = bpf_redirect(state->ct_result.ifindex_fwd, 0); if (rc == TC_ACT_REDIRECT) { @@ -197,7 +258,7 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) goto skip_fib; } } - } else if (CALI_F_VXLAN && CALI_F_TO_HEP) { + } else if (CALI_F_TUNNEL && CALI_F_TO_HEP) { if (!(ctx->skb->mark & CALI_SKB_MARK_SEEN) || !skb_mark_equals(ctx->skb, CALI_SKB_MARK_TUNNEL_KEY_SET, CALI_SKB_MARK_TUNNEL_KEY_SET)) { /* packet to vxlan from the host, needs to set tunnel key. Either @@ -206,12 +267,14 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) */ struct cali_rt *dest_rt = cali_rt_lookup(&ctx->state->ip_dst); if (dest_rt == NULL) { - CALI_DEBUG("No route for " IP_FMT " at vxlan device", &ctx->state->ip_dst); + CALI_DEBUG("No route for " IP_FMT " at tunnel device", &ctx->state->ip_dst); goto deny; } - if (!cali_rt_is_vxlan(dest_rt)) { - CALI_DEBUG("Not a vxlan route for " IP_FMT " at vxlan device", &ctx->state->ip_dst); - goto deny; + if (!cali_rt_is_tunneled(dest_rt)) { + CALI_DEBUG("Not a tunnel route for " IP_FMT " at tunnel device", &ctx->state->ip_dst); + // We dont have a tunnel route, so nothing to do here. + // Better to leave it to the tunnel device to handle it. + goto skip_fib; } struct bpf_tunnel_key key = { @@ -232,7 +295,7 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) int err = bpf_skb_set_tunnel_key(ctx->skb, &key, size, flags); CALI_DEBUG("bpf_skb_set_tunnel_key %d nh " IP_FMT, err, &dest_rt->next_hop); - ctx->fwd.mark |= CALI_SKB_MARK_TUNNEL_KEY_SET; + ctx->state->fwd.mark |= CALI_SKB_MARK_TUNNEL_KEY_SET; } } @@ -244,7 +307,7 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) } // Try a short-circuit FIB lookup. - if (fwd_fib(&ctx->fwd)) { + if (fwd_fib(&ctx->state->fwd)) { /* Revalidate the access to the packet */ if (skb_refresh_validate_ptrs(ctx, UDP_SIZE)) { deny_reason(ctx, CALI_REASON_SHORT); @@ -275,6 +338,12 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) nh_params.nh_family = 2 /* AF_INET */; nh_params.ipv4_nh = state->ip_dst; #endif + if (CALI_F_L3_DEV) { + rc = make_room_for_l2_header(ctx); + if (rc < 0) { + goto cancel_fib; + } + } rc = bpf_redirect_neigh(state->ct_result.ifindex_fwd, &nh_params, sizeof(nh_params), 0); if (rc == TC_ACT_REDIRECT) { counter_inc(ctx, CALI_REDIRECT_NEIGH); @@ -282,15 +351,7 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) state->ct_result.ifindex_fwd); goto no_fib_redirect; } - CALI_DEBUG("Fall through to redirect without fib lookup rc %d", rc); - } - rc = bpf_redirect_neigh(state->ct_result.ifindex_fwd, NULL, 0, 0); - if (rc == TC_ACT_REDIRECT) { - counter_inc(ctx, CALI_REDIRECT_NEIGH); - CALI_DEBUG("Redirect to host dev %d without fib lookup", state->ct_result.ifindex_fwd); - goto no_fib_redirect; } - CALI_DEBUG("Fall through to full FIB lookup rc %d", rc); } *fib_params(ctx) = (struct bpf_fib_lookup) { @@ -304,6 +365,11 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) .l4_protocol = state->ip_proto, }; + if (bpf_core_field_exists(((struct bpf_fib_lookup *)0)->mark)) { + fib_params(ctx)->mark = EXT_TO_SVC_MARK; + CALI_DEBUG("FIB mark=0x%d", fib_params(ctx)->mark); + } + if (state->ip_proto != IPPROTO_ICMP_46) { fib_params(ctx)->sport = bpf_htons(state->sport); fib_params(ctx)->dport = bpf_htons(state->dport); @@ -327,14 +393,16 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) CALI_DEBUG("FIB sport=%d", bpf_ntohs(fib_params(ctx)->sport)); CALI_DEBUG("FIB dport=%d", bpf_ntohs(fib_params(ctx)->dport)); #ifdef IPVER6 + CALI_DEBUG("FIB ipv6_src=" IP_FMT, &fib_params(ctx)->ipv6_src); + CALI_DEBUG("FIB ipv6_dst="IP_FMT, &fib_params(ctx)->ipv6_dst); #else - CALI_DEBUG("FIB ipv4_src=%x", bpf_ntohl(fib_params(ctx)->ipv4_src)); - CALI_DEBUG("FIB ipv4_dst=%x", bpf_ntohl(fib_params(ctx)->ipv4_dst)); + CALI_DEBUG("FIB ipv4_src=" IP_FMT, &fib_params(ctx)->ipv4_src); + CALI_DEBUG("FIB ipv4_dst="IP_FMT, &fib_params(ctx)->ipv4_dst); #endif CALI_DEBUG("Traffic is towards the host namespace, doing Linux FIB lookup"); rc = bpf_fib_lookup(ctx->skb, fib_params(ctx), sizeof(struct bpf_fib_lookup), - ctx->fwd.fib_flags | BPF_FIB_LOOKUP_SKIP_NEIGH); + ctx->state->fwd.fib_flags | BPF_FIB_LOOKUP_SKIP_NEIGH | fib_flags); switch (rc) { case BPF_FIB_LKUP_RET_FRAG_NEEDED: /* We are not asking for an MTU check, but we may still get @@ -366,7 +434,7 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) #endif if (!fib_approve(ctx, fib_params(ctx)->ifindex)) { - ctx->fwd.reason = CALI_REASON_WEP_NOT_READY; + ctx->state->fwd.reason = CALI_REASON_WEP_NOT_READY; goto deny; } @@ -380,6 +448,13 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) #endif CALI_DEBUG("Got Linux FIB hit, redirecting to iface %d.", fib_params(ctx)->ifindex); + + if (CALI_F_L3_DEV) { + rc = make_room_for_l2_header(ctx); + if (rc < 0) { + goto cancel_fib; + } + } rc = bpf_redirect_neigh(fib_params(ctx)->ifindex, &nh_params, sizeof(nh_params), 0); break; default: @@ -417,7 +492,7 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) struct arp_value *arpv = cali_arp_lookup_elem(&arpk); if (!arpv) { - ctx->fwd.reason = CALI_REASON_NATIFACE; + ctx->state->fwd.reason = CALI_REASON_NATIFACE; CALI_DEBUG("ARP lookup failed for " IP_FMT " dev %d", debug_ip(state->ip_dst), iface); goto deny; @@ -426,7 +501,7 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) /* Revalidate the access to the packet */ skb_refresh_start_end(ctx); if (ctx->data_start + sizeof(struct ethhdr) > ctx->data_end) { - ctx->fwd.reason = CALI_REASON_SHORT; + ctx->state->fwd.reason = CALI_REASON_SHORT; CALI_DEBUG("Too short"); goto deny; } @@ -438,7 +513,7 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) rc = bpf_redirect(iface, 0); if (rc != TC_ACT_REDIRECT) { - ctx->fwd.reason = CALI_REASON_NATIFACE; + ctx->state->fwd.reason = CALI_REASON_NATIFACE; CALI_DEBUG("Redirect directly to bpfnatin failed."); goto deny; } @@ -462,16 +537,10 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) /* Packet is towards host namespace, mark it so that downstream * programs know that they're not the first to see the packet. */ - ctx->fwd.mark |= CALI_SKB_MARK_SEEN; + ctx->state->fwd.mark |= CALI_SKB_MARK_SEEN; if (ctx->state->ct_result.flags & CALI_CT_FLAG_EXT_LOCAL) { CALI_DEBUG("To host marked with FLAG_EXT_LOCAL"); - ctx->fwd.mark |= EXT_TO_SVC_MARK; - if (CALI_F_FROM_WEP && EXT_TO_SVC_MARK) { - /* needs to go via normal routing unless we have access - * to BPF_FIB_LOOKUP_MARK in kernel 6.10+ - */ - rc = TC_ACT_UNSPEC; - } + ctx->state->fwd.mark |= EXT_TO_SVC_MARK; } if (CALI_F_NAT_IF) { @@ -479,23 +548,23 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) * bpfnatout - if it gets (S)NATed, a new connection is created * and we know that returning packets must go via bpfnatout again. */ - ctx->fwd.mark |= CALI_SKB_MARK_FROM_NAT_IFACE_OUT; + ctx->state->fwd.mark |= CALI_SKB_MARK_FROM_NAT_IFACE_OUT; CALI_DEBUG("marking CALI_SKB_MARK_FROM_NAT_IFACE_OUT"); } if (ct_result_is_related(state->ct_result.rc)) { CALI_DEBUG("Related traffic, marking with CALI_SKB_MARK_RELATED_RESOLVED"); - ctx->fwd.mark |= CALI_SKB_MARK_RELATED_RESOLVED; + ctx->state->fwd.mark |= CALI_SKB_MARK_RELATED_RESOLVED; } - CALI_DEBUG("Traffic is towards host namespace, marking with 0x%x.", ctx->fwd.mark); + CALI_DEBUG("Traffic is towards host namespace, marking with 0x%x.", ctx->state->fwd.mark); /* FIXME: this ignores the mask that we should be using. * However, if we mask off the bits, then clang spots that it * can do a 16-bit store instead of a 32-bit load/modify/store, * which trips up the validator. */ - skb_set_mark(ctx->skb, ctx->fwd.mark); /* make sure that each pkt has SEEN mark */ + skb_set_mark(ctx->skb, ctx->state->fwd.mark); /* make sure that each pkt has SEEN mark */ } goto allow; @@ -516,7 +585,7 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) if (rc == TC_ACT_SHOT) { CALI_INFO("Final result=DENY (%d). Program execution time: %lluns", - ctx->fwd.reason, prog_end_time-state->prog_start_time); + ctx->state->fwd.reason, prog_end_time-state->prog_start_time); } else { if (CALI_F_VXLAN && CALI_F_TO_HOST) { bpf_skb_change_type(ctx->skb, PACKET_HOST); diff --git a/felix/bpf-gpl/fib_common.h b/felix/bpf-gpl/fib_common.h index beb5b4be5ec..70fd084ca22 100644 --- a/felix/bpf-gpl/fib_common.h +++ b/felix/bpf-gpl/fib_common.h @@ -46,7 +46,7 @@ static CALI_BPF_INLINE bool fib_approve(struct cali_tc_ctx *ctx, __u32 ifindex) return false; } if (iface_is_workload(val->flags) && !iface_is_ready(val->flags)) { - ctx->fwd.mark |= CALI_SKB_MARK_SKIP_FIB; + ctx->state->fwd.mark |= CALI_SKB_MARK_SKIP_FIB; CALI_DEBUG("FIB not approved - connection to unready ep %s (ifindex %d) not confirmed.", val->name, ifindex); CALI_DEBUG("FIB not approved - connection to unready ep %s (flags 0x%x) not confirmed.", diff --git a/felix/bpf-gpl/fib_legacy.h b/felix/bpf-gpl/fib_legacy.h index 4fdebe54d64..848e3164363 100644 --- a/felix/bpf-gpl/fib_legacy.h +++ b/felix/bpf-gpl/fib_legacy.h @@ -9,8 +9,8 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) { - int rc = ctx->fwd.res; - enum calico_reason reason = ctx->fwd.reason; + int rc = ctx->state->fwd.res; + enum calico_reason reason = ctx->state->fwd.reason; struct cali_tc_state *state = ctx->state; if (rc == TC_ACT_SHOT) { @@ -94,7 +94,7 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) } // Try a short-circuit FIB lookup. - if (fwd_fib(&ctx->fwd)) { + if (fwd_fib(&ctx->state->fwd)) { /* Revalidate the access to the packet */ if (skb_refresh_validate_ptrs(ctx, UDP_SIZE)) { deny_reason(ctx, CALI_REASON_SHORT); @@ -152,7 +152,7 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) #endif CALI_DEBUG("Traffic is towards the host namespace, doing Linux FIB lookup"); - rc = bpf_fib_lookup(ctx->skb, fib_params(ctx), sizeof(struct bpf_fib_lookup), ctx->fwd.fib_flags); + rc = bpf_fib_lookup(ctx->skb, fib_params(ctx), sizeof(struct bpf_fib_lookup), ctx->state->fwd.fib_flags); switch (rc) { case 0: CALI_DEBUG("FIB lookup succeeded - with neigh"); @@ -212,7 +212,7 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) struct arp_value *arpv = cali_arp_lookup_elem(&arpk); if (!arpv) { - ctx->fwd.reason = CALI_REASON_NATIFACE; + ctx->state->fwd.reason = CALI_REASON_NATIFACE; CALI_DEBUG("ARP lookup failed for " IP_FMT " dev %d", debug_ip(state->ip_dst), iface); goto deny; @@ -221,7 +221,7 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) /* Revalidate the access to the packet */ skb_refresh_start_end(ctx); if (ctx->data_start + sizeof(struct ethhdr) > ctx->data_end) { - ctx->fwd.reason = CALI_REASON_SHORT; + ctx->state->fwd.reason = CALI_REASON_SHORT; CALI_DEBUG("Too short"); goto deny; } @@ -233,7 +233,7 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) rc = bpf_redirect(iface, 0); if (rc != TC_ACT_REDIRECT) { - ctx->fwd.reason = CALI_REASON_NATIFACE; + ctx->state->fwd.reason = CALI_REASON_NATIFACE; CALI_DEBUG("Redirect directly to bpfnatin failed."); goto deny; } @@ -257,10 +257,10 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) /* Packet is towards host namespace, mark it so that downstream * programs know that they're not the first to see the packet. */ - ctx->fwd.mark |= CALI_SKB_MARK_SEEN; + ctx->state->fwd.mark |= CALI_SKB_MARK_SEEN; if (ctx->state->ct_result.flags & CALI_CT_FLAG_EXT_LOCAL) { CALI_DEBUG("To host marked with FLAG_EXT_LOCAL"); - ctx->fwd.mark |= EXT_TO_SVC_MARK; + ctx->state->fwd.mark |= EXT_TO_SVC_MARK; if (CALI_F_FROM_WEP && EXT_TO_SVC_MARK) { /* needs to go via normal routing unless we have access * to BPF_FIB_LOOKUP_MARK in kernel 6.10+ @@ -274,23 +274,23 @@ static CALI_BPF_INLINE int forward_or_drop(struct cali_tc_ctx *ctx) * bpfnatout - if it gets (S)NATed, a new connection is created * and we know that returning packets must go via bpfnatout again. */ - ctx->fwd.mark |= CALI_SKB_MARK_FROM_NAT_IFACE_OUT; + ctx->state->fwd.mark |= CALI_SKB_MARK_FROM_NAT_IFACE_OUT; CALI_DEBUG("marking CALI_SKB_MARK_FROM_NAT_IFACE_OUT"); } if (ct_result_is_related(state->ct_result.rc)) { CALI_DEBUG("Related traffic, marking with CALI_SKB_MARK_RELATED_RESOLVED"); - ctx->fwd.mark |= CALI_SKB_MARK_RELATED_RESOLVED; + ctx->state->fwd.mark |= CALI_SKB_MARK_RELATED_RESOLVED; } - CALI_DEBUG("Traffic is towards host namespace, marking with 0x%x.", ctx->fwd.mark); + CALI_DEBUG("Traffic is towards host namespace, marking with 0x%x.", ctx->state->fwd.mark); /* FIXME: this ignores the mask that we should be using. * However, if we mask off the bits, then clang spots that it * can do a 16-bit store instead of a 32-bit load/modify/store, * which trips up the validator. */ - skb_set_mark(ctx->skb, ctx->fwd.mark); /* make sure that each pkt has SEEN mark */ + skb_set_mark(ctx->skb, ctx->state->fwd.mark); /* make sure that each pkt has SEEN mark */ } goto allow; diff --git a/felix/bpf-gpl/globals.h b/felix/bpf-gpl/globals.h index 0a317288ab4..85431cafd54 100644 --- a/felix/bpf-gpl/globals.h +++ b/felix/bpf-gpl/globals.h @@ -27,6 +27,7 @@ struct name { \ __u32 log_filter_jmp; \ __u32 jumps[40]; \ __s8 dscp; \ + __u32 maglev_lut_size; \ } DECLARE_TC_GLOBAL_DATA(cali_tc_global_data, ipv6_addr_t); diff --git a/felix/bpf-gpl/ip_addr.h b/felix/bpf-gpl/ip_addr.h index 582c7b8ca62..646bd1ed16b 100644 --- a/felix/bpf-gpl/ip_addr.h +++ b/felix/bpf-gpl/ip_addr.h @@ -111,6 +111,7 @@ typedef ipv6_addr_t ipv46_addr_t; #define NP_SPECIAL_IP 0xffffffff #define ip_equal(a, b) ((a) == (b)) #define ip_lt(a, b) (*(a) < *(b)) +#define ip_link_local(ip) (((ip) & bpf_htonl(0xffff0000)) == bpf_htonl(0xa9fe0000)) typedef ipv4_addr_t ipv46_addr_t; diff --git a/felix/bpf-gpl/ip_v4_fragment.h b/felix/bpf-gpl/ip_v4_fragment.h index a2d72834759..9c1921af399 100644 --- a/felix/bpf-gpl/ip_v4_fragment.h +++ b/felix/bpf-gpl/ip_v4_fragment.h @@ -19,6 +19,10 @@ struct frags4_key { __u16 offset; }; +// BPF imposes a 16,000 byte limit on growing the packet; we want enough +// fragments to cover that, but not enough to upset the verifier by making our +// loop too long. +#define MAX_FRAGS 16 #define MAX_FRAG 1504 /* requires multiple of 8 */ struct frags4_value { @@ -35,7 +39,7 @@ CALI_MAP(cali_v4_frgtmp, 2, __u32, struct frags4_value, 1, 0) -CALI_MAP(cali_v4_frgfwd, 2, BPF_MAP_TYPE_LRU_HASH, struct frags4_fwd_key, __u32, 10000, 0) +CALI_MAP(cali_v4_frgfwd, 3, BPF_MAP_TYPE_LRU_HASH, struct frags4_fwd_key, struct frags4_fwd_value, 10000, 0) struct frags4_fwd_key { ipv4_addr_t src; @@ -45,6 +49,12 @@ struct frags4_fwd_key { __u16 __pad; }; +struct frags4_fwd_value { + __u16 sport; + __u16 dport; + __u32 seen_mark; +}; + static CALI_BPF_INLINE struct frags4_value *frags4_get_scratch() { __u32 key = 0; @@ -109,7 +119,7 @@ static CALI_BPF_INLINE bool frags4_try_assemble(struct cali_tc_ctx *ctx) int i, tot_len = 0; - for (i = 0; i < 10; i++) { + for (i = 0; i < MAX_FRAGS; i++) { struct frags4_value *v = cali_v4_frags_lookup_elem(&k); if (!v) { @@ -146,7 +156,7 @@ static CALI_BPF_INLINE bool frags4_try_assemble(struct cali_tc_ctx *ctx) .iphdr_off = off, }; - bpf_loop(10, frag_to_skb, &f2sctx, 0); + bpf_loop(MAX_FRAGS, frag_to_skb, &f2sctx, 0); if (parse_packet_ip(ctx) != PARSING_OK) { goto out; @@ -202,8 +212,7 @@ static CALI_BPF_INLINE bool frags4_handle(struct cali_tc_ctx *ctx) .src = ip_hdr(ctx)->saddr, .dst = ip_hdr(ctx)->daddr, .id = ip_hdr(ctx)->id, - .offset = 8 * bpf_ntohs(ip_hdr(ctx)->frag_off) & 0x1fff, - + .offset = 8 * (bpf_ntohs(ip_hdr(ctx)->frag_off) & 0x1fff), }; int i; @@ -278,7 +287,11 @@ static CALI_BPF_INLINE void frags4_record_ct(struct cali_tc_ctx *ctx) .id = ip_hdr(ctx)->id, }; - __u32 v = 0; + struct frags4_fwd_value v = { + .sport = ctx->state->sport, + .dport = ctx->state->dport, + .seen_mark = ctx->state->fwd.mark, + }; cali_v4_frgfwd_update_elem(&k, &v, 0); CALI_DEBUG("IP FRAG: created ct from " IP_FMT " to " IP_FMT, @@ -311,10 +324,10 @@ static CALI_BPF_INLINE void frags4_remove_ct(struct cali_tc_ctx *ctx) #endif /* BPF_CORE_SUPPORTED */ } -static CALI_BPF_INLINE bool frags4_lookup_ct(struct cali_tc_ctx *ctx) +static CALI_BPF_INLINE struct frags4_fwd_value *frags4_lookup_ct(struct cali_tc_ctx *ctx) { #ifndef BPF_CORE_SUPPORTED - return false; + return NULL; #else /* We do not really use bpf_loop() here, but we need to check if the kernel * supports it. If it does not, we cannot handle fragments as the @@ -322,7 +335,7 @@ static CALI_BPF_INLINE bool frags4_lookup_ct(struct cali_tc_ctx *ctx) */ if (!bpf_core_enum_value_exists(enum bpf_func_id, BPF_FUNC_loop)) { CALI_DEBUG("IP FRAG: kernel too old, skipping fragment handling"); - return false; + return NULL; } struct frags4_fwd_key k = { @@ -334,7 +347,7 @@ static CALI_BPF_INLINE bool frags4_lookup_ct(struct cali_tc_ctx *ctx) CALI_DEBUG("IP FRAG: lookup ct from " IP_FMT " to " IP_FMT, debug_ip(ctx->state->ip_src), debug_ip(ctx->state->ip_dst)); - return cali_v4_frgfwd_lookup_elem(&k) != NULL; + return cali_v4_frgfwd_lookup_elem(&k); #endif /* BPF_CORE_SUPPORTED */ } diff --git a/felix/bpf-gpl/jenkins_hash.h b/felix/bpf-gpl/jenkins_hash.h new file mode 100644 index 00000000000..44665cc93da --- /dev/null +++ b/felix/bpf-gpl/jenkins_hash.h @@ -0,0 +1,163 @@ + +/* + * Copyright (C) 2006. Bob Jenkins (bob_jenkins@burtleburtle.net) + * + * https://burtleburtle.net/bob/hash/ + * + * These are the credits from Bob's sources: + * + * lookup3.c, by Bob Jenkins, May 2006, Public Domain. + * + * These are functions for producing 32-bit hashes for hash table lookup. + * hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final() + * are externally useful functions. Routines to test the hash are included + * if SELF_TEST is defined. You can use this free for any purpose. It's in + * the public domain. It has no warranty. + */ + +/* + * I've chopped and wrapped Jenkins' work. + * Any bugs belong to me... :-( + * -Alex +*/ + + +#include "bpf.h" + +#define hashsize(n) ((__u32)1<<(n)) +#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) + +/* +------------------------------------------------------------------------------- +mix -- mix 3 32-bit values reversibly. + +This is reversible, so any information in (a,b,c) before mix() is +still in (a,b,c) after mix(). + +If four pairs of (a,b,c) inputs are run through mix(), or through +mix() in reverse, there are at least 32 bits of the output that +are sometimes the same for one pair and different for another pair. +This was tested for: +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. + +Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that +satisfy this are + 4 6 8 16 19 4 + 9 15 3 18 27 15 + 14 9 3 7 17 3 +Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing +for "differ" defined as + with a one-bit base and a two-bit delta. I +used http://burtleburtle.net/bob/hash/avalanche.html to choose +the operations, constants, and arrangements of the variables. + +This does not achieve avalanche. There are input bits of (a,b,c) +that fail to affect some output bits of (a,b,c), especially of a. The +most thoroughly mixed value is c, but it doesn't really even achieve +avalanche in c. + +This allows some parallelism. Read-after-writes are good at doubling +the number of bits affected, so the goal of mixing pulls in the opposite +direction as the goal of parallelism. I did what I could. Rotates +seem to cost as much as shifts on every machine I could lay my hands +on, and rotates are much kinder to the top and bottom bits, so I used +rotates. +------------------------------------------------------------------------------- +*/ +#define mix(a,b,c) \ +{ \ +a -= c; a ^= rot(c, 4); c += b; \ +b -= a; b ^= rot(a, 6); a += c; \ +c -= b; c ^= rot(b, 8); b += a; \ +a -= c; a ^= rot(c,16); c += b; \ +b -= a; b ^= rot(a,19); a += c; \ +c -= b; c ^= rot(b, 4); b += a; \ +} + +/* +------------------------------------------------------------------------------- +final -- final mixing of 3 32-bit values (a,b,c) into c + +Pairs of (a,b,c) values differing in only a few bits will usually +produce values of c that look totally different. This was tested for +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. + +These constants passed: + 14 11 25 16 4 14 24 + 12 14 25 16 4 14 24 +and these came close: + 4 8 15 26 3 22 24 + 10 8 15 26 3 22 24 + 11 8 15 26 3 22 24 +------------------------------------------------------------------------------- +*/ +#define final(a,b,c) \ +{ \ +c ^= b; c -= rot(b,14);\ +a ^= c; a -= rot(c,11);\ +b ^= a; b -= rot(a,25);\ +c ^= b; c -= rot(b,16);\ +a ^= c; a -= rot(c,4); \ +b ^= a; b -= rot(a,14);\ +c ^= b; c -= rot(b,24);\ +} + +/* +-------------------------------------------------------------------- + This works on all machines. To be useful, it requires + -- that the key be an array of __u32's, and + -- that the length be the number of __u32's in the key + + The function hashword() is identical to hashlittle() on little-endian + machines, and identical to hashbig() on big-endian machines, + except that the length has to be measured in __u32s rather than in + bytes. hashlittle() is more complicated than hashword() only because + hashlittle() has to dance around fitting the key bytes into registers. +-------------------------------------------------------------------- +*/ +static CALI_BPF_INLINE __u32 hashword( + const __u32 *k, /* the key, an array of __u32 values */ + size_t length, /* the length of the key, in __u32s */ + __u32 initval /* the previous hash, or an arbitrary value */) { + + __u32 a,b,c; + + /* Set up the internal state */ + a = b = c = 0xdeadbeef + (((__u32)length)<<2) + initval; + /*------------------------------------------------- handle most of the key */ + while (length > 3) { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 3; + k += 3; + } + /*------------------------------------------- handle the last 3 __u32's */ + /* all the case statements fall through */ + switch(length){ + case 3 : c+=k[2]; + case 2 : b+=k[1]; + case 1 : a+=k[0]; + final(a,b,c); + case 0: /* case 0: nothing left to add */ + break; + } + /*------------------------------------------------------ report the result */ + return c; +} diff --git a/felix/bpf-gpl/jump.h b/felix/bpf-gpl/jump.h index 9aa47baa518..ac7ffb2c44b 100644 --- a/felix/bpf-gpl/jump.h +++ b/felix/bpf-gpl/jump.h @@ -7,9 +7,18 @@ #include "types.h" -CALI_MAP(cali_state, 4, +#define STATE_SIZE 512 + +static CALI_BPF_INLINE void __compile_state_asserts(void) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-local-typedef" +COMPILE_TIME_ASSERT(sizeof(struct cali_tc_state) <= STATE_SIZE); +#pragma clang diagnostic pop +} + +CALI_MAP(cali_state, 6, BPF_MAP_TYPE_PERCPU_ARRAY, - __u32, struct cali_tc_state, + __u32, char[STATE_SIZE], 2, 0) static CALI_BPF_INLINE struct cali_tc_state *state_get(void) @@ -47,7 +56,22 @@ CALI_MAP_V1(cali_jump_map, BPF_MAP_TYPE_PROG_ARRAY, __u32, __u32, 400, 0) #else /* CALI_F_XDP */ -#define cali_jump_map map_symbol(cali_progs, 3) +/* + * BPF programs have a type, which depends on where they are attached. + * As of kernel 6.12, jump maps are limited to a single program type and + * it is forbidden to jump from one type of program to a different type of map. + * TCX ingress and egress programs have different types, so this means that we need to split the jump maps for the two directions. + * Note: in our code, we generally use "ingress" and "egress" to refer to the policy direction, + * relative to the endpoint that is being secured, which means that, for workload endpoints, + * "ingress" policy is implemented in the kernel's "egress" tc(x) program(!). + * To avoid (further) confusion, we call the kernel's directions "to host" (ingress) and "from host" (egress). +*/ + +#ifdef CALI_HOOK_INGRESS +#define cali_jump_map map_symbol(cali_progs_ing, 2) +#else +#define cali_jump_map map_symbol(cali_progs_egr, 2) +#endif CALI_MAP_V1(cali_jump_map, BPF_MAP_TYPE_PROG_ARRAY, __u32, __u32, 400, 0) @@ -71,6 +95,8 @@ enum cali_jump_index { PROG_INDEX_ICMP_INNER_NAT, PROG_INDEX_NEW_FLOW, PROG_INDEX_IP_FRAG, + PROG_INDEX_MAGLEV, + PROG_INDEX_TCP_RST, PROG_INDEX_MAIN_DEBUG, PROG_INDEX_POLICY_DEBUG, @@ -81,6 +107,8 @@ enum cali_jump_index { PROG_INDEX_ICMP_INNER_NAT_DEBUG, PROG_INDEX_NEW_FLOW_DEBUG, PROG_INDEX_IP_FRAG_DEBUG, + PROG_INDEX_MAGLEV_DEBUG, + PROG_INDEX_TCP_RST_DEBUG, }; #if CALI_F_XDP @@ -96,7 +124,11 @@ CALI_MAP_V1(cali_jump_prog_map, BPF_MAP_TYPE_PROG_ARRAY, __u32, __u32, 2400, 0) bpf_tail_call((ctx)->xdp, &cali_jump_prog_map, (ctx)->xdp_globals->jumps[PROG_INDEX_POLICY]) #else /* CALI_F_XDP */ -#define cali_jump_prog_map map_symbol(cali_jump, 3) +#ifdef CALI_HOOK_INGRESS +#define cali_jump_prog_map map_symbol(cali_jump_ing, 2) +#else +#define cali_jump_prog_map map_symbol(cali_jump_egr, 2) +#endif CALI_MAP_V1(cali_jump_prog_map, BPF_MAP_TYPE_PROG_ARRAY, __u32, __u32, 240000, 0) diff --git a/felix/bpf-gpl/list-objs b/felix/bpf-gpl/list-objs index 12bdcecdd58..0e6847826ac 100755 --- a/felix/bpf-gpl/list-objs +++ b/felix/bpf-gpl/list-objs @@ -11,11 +11,11 @@ emit_filename() { if [ "${core_support}" = "legacy" ]; then - echo "bin/${from_or_to}_${ep_type}_${host_drop}${fib}${extra}${log_level}.o" - echo "bin/${from_or_to}_${ep_type}_${host_drop}${fib}${extra}${log_level}_v6.o" + echo "bin/${from_or_to}_${ep_type}_${host_drop}${extra}${log_level}.o" + echo "bin/${from_or_to}_${ep_type}_${host_drop}${extra}${log_level}_v6.o" else - echo "bin/${from_or_to}_${ep_type}_${host_drop}${fib}${extra}${log_level}_${core_support}.o" - echo "bin/${from_or_to}_${ep_type}_${host_drop}${fib}${extra}${log_level}_${core_support}_v6.o" + echo "bin/${from_or_to}_${ep_type}_${host_drop}${extra}${log_level}_${core_support}.o" + echo "bin/${from_or_to}_${ep_type}_${host_drop}${extra}${log_level}_${core_support}_v6.o" fi } @@ -30,39 +30,28 @@ for log_level in debug no_log; do echo "bin/connect_balancer_${log_level}_co-re_v6.o" echo "bin/xdp_${log_level}.o" echo "bin/xdp_${log_level}_co-re_v6.o" - + directions="from to" for core_support in co-re legacy; do for host_drop in "" "host_drop_"; do if [ "${host_drop}" = "host_drop_" ]; then # The workload-to-host drop setting only applies to the from-workload hook. ep_types="wep" + directions="from" else ep_types="wep hep ipip l3 nat lo vxlan" + directions="from to" fi - for fib in "" "fib_"; do - for ep_type in $ep_types; do - if [ "${fib}" = "fib_" ] && [ "${ep_type}" = "lo" ]; then - directions="from to" - elif [ "${fib}" = "fib_" ] && [ ${ep_type} = "hep" ]; then - directions="from to" - elif [ "${host_drop}" = "host_drop_" ] || [ "${fib}" = "fib_" ]; then - # The workload-to-host drop setting only applies to the from-workload hook. - # The FIB only applies in the from-endpoint hooks. - directions="from" - else - directions="from to" + for ep_type in $ep_types; do + for from_or_to in $directions; do + extra="dsr_" + if [ "${ep_type}" = "hep" ]; then + emit_filename fi - for from_or_to in $directions; do - extra="dsr_" - if [ "${ep_type}" = "hep" ]; then - emit_filename - fi - if [ "${from_or_to}" = "from" ] && [ "${ep_type}" = "wep" ]; then - emit_filename - fi - extra="" + if [ "${from_or_to}" = "from" ] && [ "${ep_type}" = "wep" ]; then emit_filename - done + fi + extra="" + emit_filename done done done diff --git a/felix/bpf-gpl/list-ut-objs b/felix/bpf-gpl/list-ut-objs index 3a087b206cf..89b8806e6c4 100755 --- a/felix/bpf-gpl/list-ut-objs +++ b/felix/bpf-gpl/list-ut-objs @@ -10,8 +10,8 @@ # WARNING: should be kept in sync with the combinations used in tests, in particular bpf_prog_test.go. emit_filename() { - echo "bin/test_${from_or_to}_${ep_type}_fib_${log_level}${dsr}_co-re.o" - echo "bin/test_${from_or_to}_${ep_type}_fib_${log_level}${dsr}_co-re_v6.o" + echo "bin/test_${from_or_to}_${ep_type}_${log_level}${dsr}_co-re.o" + echo "bin/test_${from_or_to}_${ep_type}_${log_level}${dsr}_co-re_v6.o" } log_level=debug @@ -36,7 +36,7 @@ for ep_type in $ep_types; do done done -echo "bin/test_from_hep_fib_no_log_skb0x0.o" -echo "bin/test_from_wep_fib_no_log.o" +echo "bin/test_from_hep_no_log_skb0x0.o" +echo "bin/test_from_wep_no_log.o" echo "bin/test_xdp_debug_co-re.o" echo "bin/test_xdp_debug_co-re_v6.o" diff --git a/felix/bpf-gpl/log.h b/felix/bpf-gpl/log.h index 9d1ddac4ae8..7bc18ae48fb 100644 --- a/felix/bpf-gpl/log.h +++ b/felix/bpf-gpl/log.h @@ -26,7 +26,7 @@ #ifdef BPF_CORE_SUPPORTED #define bpf_log(__fmt, ...) do { \ - char fmt[] = IPVER_PFX __fmt; \ + __attribute__((section(".rodata.cali_debug"))) static const char fmt[] = IPVER_PFX __fmt; \ bpf_trace_printk(fmt, sizeof(fmt), ## __VA_ARGS__); \ } while (0) #else diff --git a/felix/bpf-gpl/maglev.h b/felix/bpf-gpl/maglev.h new file mode 100644 index 00000000000..a957e901e36 --- /dev/null +++ b/felix/bpf-gpl/maglev.h @@ -0,0 +1,50 @@ +// Project Calico BPF dataplane programs. +// Copyright (c) 2020-2023 Tigera, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +#ifndef __CALI_MAGLEV_H__ +#define __CALI_MAGLEV_H__ + +#include "jenkins_hash.h" + +static CALI_BPF_INLINE struct calico_nat_dest* maglev_select_backend(struct cali_tc_ctx *ctx) +{ + __u32 hash; + ipv46_addr_t *ip_src = &ctx->state->ip_src; + ipv46_addr_t *ip_dst = &ctx->state->ip_dst; + __u16 sport = ctx->state->sport; + __u16 dport = ctx->state->dport; + __u8 ip_proto = ctx->state->ip_proto; + __u32 lut_size = ctx->globals->data.maglev_lut_size; + +#ifdef IPVER6 + const __u32 ip_arr[11] = { + ip_src->a, ip_src->b, ip_src->c, ip_src->d, + ip_dst->a, ip_dst->b, ip_dst->c, ip_dst->d, + (__u32)ip_proto, sport, dport + }; + + hash = hashword(ip_arr, 11, 0xCA71C0); +#else + const __u32 ip_arr[5] = {*ip_src, *ip_dst, sport, dport, (__u32) ip_proto}; + hash = hashword (ip_arr, 5, 0xCA71C0); +#endif /* IPVER6 */ + + CALI_DEBUG("Maglev: hashed packet to %d", hash); + struct cali_maglev_key ch_key = { + .ordinal = (hash % lut_size), + .sid = ctx->state->nat_svc_id, + }; + struct calico_nat_dest *ch_val; + + ch_val = cali_maglev_lookup_elem(&ch_key); + + if (!ch_val) { + CALI_DEBUG("Maglev: no backend found for svc %d, idx %d", ctx->state->nat_svc_id, ch_key.ordinal); + return NULL; + } + + return ch_val; +} + +#endif /* __CALI_MAGLEV_H__ */ diff --git a/felix/bpf-gpl/nat.h b/felix/bpf-gpl/nat.h index 2f550c91429..915e5e99963 100644 --- a/felix/bpf-gpl/nat.h +++ b/felix/bpf-gpl/nat.h @@ -174,27 +174,33 @@ static CALI_BPF_INLINE int vxlan_attempt_decap(struct cali_tc_ctx *ctx) CALI_DEBUG("VXLAN: Invalid VNI"); goto fall_through; } - if (vxlan_vni(ctx) != CALI_VXLAN_VNI) { + if (vxlan_vni(ctx) == OVERLAY_TUNNEL_ID) { if (rt_addr_is_remote_host((ipv46_addr_t *)&ip_hdr(ctx)->saddr)) { /* Not BPF-generated VXLAN packet but it was from a Calico host to this node. */ CALI_DEBUG("VXLAN: non-tunnel calico"); goto auto_allow; } + /* Our VNI, but not from a Calico host. Fall through to policy. */ + CALI_DEBUG("VXLAN with our VNI (0x%x) from non-calico source.", vxlan_vni(ctx)); + /* N.B. we defer dropping to policy */ + goto fall_through; + } else if (vxlan_vni(ctx) == CALI_VXLAN_VNI) { + if (!rt_addr_is_remote_host((ipv46_addr_t *)&ip_hdr(ctx)->saddr)) { + CALI_DEBUG("VXLAN with our VNI (0x%x) from unexpected source.", vxlan_vni(ctx)); + deny_reason(ctx, CALI_REASON_UNAUTH_SOURCE); + goto deny; + } + if (!vxlan_udp_csum_ok(udp_hdr(ctx))) { + /* Our VNI but checksum is incorrect (we always use check=0). */ + CALI_DEBUG("VXLAN with our VNI but incorrect checksum."); + deny_reason(ctx, CALI_REASON_UNAUTH_SOURCE); + goto deny; + } + } else { /* Not our VNI, not from Calico host. Fall through to policy. */ - CALI_DEBUG("VXLAN: Not our VNI"); + CALI_DEBUG("VXLAN: Not our VNI 0x%x", vxlan_vni(ctx)); goto fall_through; } - if (!rt_addr_is_remote_host((ipv46_addr_t *)&ip_hdr(ctx)->saddr)) { - CALI_DEBUG("VXLAN with our VNI from unexpected source."); - deny_reason(ctx, CALI_REASON_UNAUTH_SOURCE); - goto deny; - } - if (!vxlan_udp_csum_ok(udp_hdr(ctx))) { - /* Our VNI but checksum is incorrect (we always use check=0). */ - CALI_DEBUG("VXLAN with our VNI but incorrect checksum."); - deny_reason(ctx, CALI_REASON_UNAUTH_SOURCE); - goto deny; - } /* We update the map straight with the packet data, eth header is * dst:src but the value is src:dst so it flips it automatically @@ -238,7 +244,7 @@ static CALI_BPF_INLINE int vxlan_attempt_decap(struct cali_tc_ctx *ctx) return -2; deny: - ctx->fwd.res = TC_ACT_SHOT; + ctx->state->fwd.res = TC_ACT_SHOT; return -1; } diff --git a/felix/bpf-gpl/nat4.h b/felix/bpf-gpl/nat4.h index a395157acbb..27c53f2df2e 100644 --- a/felix/bpf-gpl/nat4.h +++ b/felix/bpf-gpl/nat4.h @@ -27,10 +27,15 @@ static CALI_BPF_INLINE int vxlan_encap(struct cali_tc_ctx *ctx, __be32 *ip_src, __u32 new_hdrsz = sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(struct vxlanhdr); - ret = bpf_skb_adjust_room(ctx->skb, new_hdrsz, BPF_ADJ_ROOM_MAC, - BPF_F_ADJ_ROOM_ENCAP_L4_UDP | - BPF_F_ADJ_ROOM_ENCAP_L3_IPV4 | - BPF_F_ADJ_ROOM_ENCAP_L2(sizeof(struct ethhdr))); + __u64 flags = BPF_F_ADJ_ROOM_ENCAP_L4_UDP | + BPF_F_ADJ_ROOM_ENCAP_L3_IPV4 | + BPF_F_ADJ_ROOM_ENCAP_L2(sizeof(struct ethhdr)); + + if (ctx->state->ip_proto == IPPROTO_UDP) { + flags |= BPF_F_ADJ_ROOM_FIXED_GSO; + } + + ret = bpf_skb_adjust_room(ctx->skb, new_hdrsz, BPF_ADJ_ROOM_MAC, flags); if (ret) { goto out; @@ -95,7 +100,7 @@ static CALI_BPF_INLINE int vxlan_decap(struct __sk_buff *skb) extra_hdrsz = sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(struct vxlanhdr); - ret = bpf_skb_adjust_room(skb, -extra_hdrsz, BPF_ADJ_ROOM_MAC | BPF_F_ADJ_ROOM_FIXED_GSO, 0); + ret = bpf_skb_adjust_room(skb, -extra_hdrsz, BPF_ADJ_ROOM_MAC, BPF_F_ADJ_ROOM_FIXED_GSO); return ret; } diff --git a/felix/bpf-gpl/nat6.h b/felix/bpf-gpl/nat6.h index 26e75011ade..f34fda112a7 100644 --- a/felix/bpf-gpl/nat6.h +++ b/felix/bpf-gpl/nat6.h @@ -24,10 +24,15 @@ static CALI_BPF_INLINE int vxlan_encap(struct cali_tc_ctx *ctx, ipv6_addr_t *ip_ __u32 new_hdrsz = sizeof(struct ethhdr) + sizeof(struct ipv6hdr) + sizeof(struct udphdr) + sizeof(struct vxlanhdr); - if (bpf_skb_adjust_room(ctx->skb, new_hdrsz, BPF_ADJ_ROOM_MAC, - BPF_F_ADJ_ROOM_ENCAP_L4_UDP | - BPF_F_ADJ_ROOM_ENCAP_L3_IPV6 | - BPF_F_ADJ_ROOM_ENCAP_L2(sizeof(struct ethhdr)))) { + __u64 flags = BPF_F_ADJ_ROOM_ENCAP_L4_UDP | + BPF_F_ADJ_ROOM_ENCAP_L3_IPV6 | + BPF_F_ADJ_ROOM_ENCAP_L2(sizeof(struct ethhdr)); + + if (ctx->state->ip_proto == IPPROTO_UDP) { + flags |= BPF_F_ADJ_ROOM_FIXED_GSO; + } + + if (bpf_skb_adjust_room(ctx->skb, new_hdrsz, BPF_ADJ_ROOM_MAC, flags)) { return -1; } @@ -83,7 +88,7 @@ static CALI_BPF_INLINE int vxlan_decap(struct __sk_buff *skb) extra_hdrsz = sizeof(struct ethhdr) + sizeof(struct ipv6hdr) + sizeof(struct udphdr) + sizeof(struct vxlanhdr); - ret = bpf_skb_adjust_room(skb, -extra_hdrsz, BPF_ADJ_ROOM_MAC | BPF_F_ADJ_ROOM_FIXED_GSO, 0); + ret = bpf_skb_adjust_room(skb, -extra_hdrsz, BPF_ADJ_ROOM_MAC, BPF_F_ADJ_ROOM_FIXED_GSO); return ret; } diff --git a/felix/bpf-gpl/nat_lookup.h b/felix/bpf-gpl/nat_lookup.h index 73425ed6068..71e86b2d6bb 100644 --- a/felix/bpf-gpl/nat_lookup.h +++ b/felix/bpf-gpl/nat_lookup.h @@ -104,6 +104,19 @@ static CALI_BPF_INLINE struct calico_nat_dest* calico_nat_lookup(ipv46_addr_t *i } CALI_DEBUG("NAT: nodeport hit"); } + + /* Since we must use ctx to communicate the svc ID we hit out to the Maglev prog, + * we're gating this code from XDP. + */ + #if !(CALI_F_XDP) && !(CALI_F_CGROUP) + if (HAS_MAGLEV && (nat_lv1_val->flags & NAT_FLG_MAGLEV)) { + CALI_DEBUG("NAT: 1st level hit; id=%d, NAT maglev", nat_lv1_val->id); + *res = NAT_MAGLEV; + ctx->state->nat_svc_id = nat_lv1_val->id; + return NULL; /* Make the lookup fail, but signal, that another lookup should be done for maglev */ + } + #endif + /* With LB source range, we install a drop entry in the NAT FE map * with count equal to all-ones for both ip4/6. If we hit this entry, * packet is dropped. diff --git a/felix/bpf-gpl/nat_types.h b/felix/bpf-gpl/nat_types.h index c2930e0f43a..aab68826d50 100644 --- a/felix/bpf-gpl/nat_types.h +++ b/felix/bpf-gpl/nat_types.h @@ -12,6 +12,7 @@ typedef enum calico_nat_lookup_result { NAT_FE_LOOKUP_DROP, NAT_NO_BACKEND, NAT_EXCLUDE, + NAT_MAGLEV, } nat_lookup_result; @@ -44,11 +45,6 @@ struct __attribute__((__packed__)) calico_nat_key { // This is used as a special ID along with count=0 to drop a packet at nat level1 lookup #define NAT_FE_DROP_COUNT 0xffffffff -union calico_nat_lpm_key { - struct bpf_lpm_trie_key lpm; - struct calico_nat_key key; -}; - struct calico_nat_value { __u32 id; __u32 count; @@ -60,6 +56,7 @@ struct calico_nat_value { #define NAT_FLG_EXTERNAL_LOCAL 0x1 #define NAT_FLG_INTERNAL_LOCAL 0x2 #define NAT_FLG_NAT_EXCLUDE 0x4 +#define NAT_FLG_MAGLEV 0X8 #ifdef IPVER6 CALI_MAP_NAMED(cali_v6_nat_fe, cali_nat_fe, 3, @@ -67,7 +64,7 @@ CALI_MAP_NAMED(cali_v6_nat_fe, cali_nat_fe, 3, CALI_MAP_NAMED(cali_v4_nat_fe, cali_nat_fe, 3, #endif BPF_MAP_TYPE_LPM_TRIE, - union calico_nat_lpm_key, struct calico_nat_value, + struct calico_nat_key, struct calico_nat_value, 64*1024, BPF_F_NO_PREALLOC) @@ -121,4 +118,19 @@ struct vxlanhdr { __be32 flags; __be32 vni; }; + +struct cali_maglev_key { + __u32 sid; + __u32 ordinal; // should always be a value of [0..M-1], where M is a very large prime number. -Alex +}; + +#ifdef IPVER6 +CALI_MAP_NAMED(cali_v6_mglv, cali_maglev,2, +#else +CALI_MAP_NAMED(cali_v4_mglv, cali_maglev,2, +#endif + BPF_MAP_TYPE_HASH, + struct cali_maglev_key, struct calico_nat_dest, + 1009, BPF_F_NO_PREALLOC) + #endif /* __CALI_NAT_TYPES_H__ */ diff --git a/felix/bpf-gpl/parsing.h b/felix/bpf-gpl/parsing.h index 191a3038636..1c8c1d6e6cf 100644 --- a/felix/bpf-gpl/parsing.h +++ b/felix/bpf-gpl/parsing.h @@ -177,6 +177,28 @@ static CALI_BPF_INLINE int tc_state_fill_from_nexthdr(struct cali_tc_ctx *ctx, b } } } + if (ctx->state->dport == WG_PORT) { + /* Wireguard packet. Allow if to/from known Calico host. */ + if (CALI_F_FROM_HEP) { + if (rt_addr_is_remote_host(&ctx->state->ip_src)) { + CALI_DEBUG("Wireguard packet from known Calico host, allow."); + goto allow; + } else { + CALI_DEBUG("Wireguard packet from unknown source, drop."); + deny_reason(ctx, CALI_REASON_UNAUTH_SOURCE); + goto deny; + } + } else if (CALI_F_TO_HEP && !CALI_F_L3_DEV) { + if (rt_addr_is_remote_host(&ctx->state->ip_dst)) { + CALI_DEBUG("Wireguard packet to known Calico host, allow."); + goto allow; + } else { + CALI_DEBUG("Wireguard packet to unknown dest, drop."); + deny_reason(ctx, CALI_REASON_UNAUTH_SOURCE); + goto deny; + } + } + } break; #ifdef IPVER6 case IPPROTO_ICMPV6: diff --git a/felix/bpf-gpl/parsing4.h b/felix/bpf-gpl/parsing4.h index 9b04c9615dd..041a3a7cbed 100644 --- a/felix/bpf-gpl/parsing4.h +++ b/felix/bpf-gpl/parsing4.h @@ -93,6 +93,9 @@ static CALI_BPF_INLINE void tc_state_fill_from_iphdr_v4(struct cali_tc_ctx *ctx) ctx->state->ip_proto = ip_hdr(ctx)->protocol; ctx->state->ip_size = ip_hdr(ctx)->tot_len; ctx->ipheader_len = ctx->state->ihl = ip_hdr(ctx)->ihl * 4; + if (ip_is_first_frag(ip_hdr(ctx))) { + ctx->state->flags |= CALI_ST_FIRST_FRAG; + } CALI_DEBUG("IP ihl=%d bytes", ctx->ipheader_len); } diff --git a/felix/bpf-gpl/policy.h b/felix/bpf-gpl/policy.h index c41489c226e..6fdcffc9a5f 100644 --- a/felix/bpf-gpl/policy.h +++ b/felix/bpf-gpl/policy.h @@ -33,10 +33,6 @@ struct ip_set_key { __u8 pad; } __attribute__((packed)); -union ip_set_lpm_key { - struct bpf_lpm_trie_key lpm; - struct ip_set_key ip; -}; #ifdef IPVER6 CALI_MAP_NAMED(cali_v6_ip_sets, cali_ip_sets,, @@ -44,7 +40,7 @@ CALI_MAP_NAMED(cali_v6_ip_sets, cali_ip_sets,, CALI_MAP_NAMED(cali_v4_ip_sets, cali_ip_sets,, #endif BPF_MAP_TYPE_LPM_TRIE, - union ip_set_lpm_key, + struct ip_set_key, __u32, 1024*1024, BPF_F_NO_PREALLOC) diff --git a/felix/bpf-gpl/qos.h b/felix/bpf-gpl/qos.h index ed5aee44084..d703004f1b5 100644 --- a/felix/bpf-gpl/qos.h +++ b/felix/bpf-gpl/qos.h @@ -110,7 +110,7 @@ static CALI_BPF_INLINE int qos_enforce_packet_rate(struct cali_tc_ctx *ctx) static CALI_BPF_INLINE bool qos_dscp_needs_update(struct cali_tc_ctx *ctx) { - return ((ctx->state->flags & CALI_ST_CLUSTER_EXTERNAL) && EGRESS_DSCP >= 0); + return ((ctx->state->flags & CALI_ST_SET_DSCP) && EGRESS_DSCP >= 0); } static CALI_BPF_INLINE bool qos_dscp_set(struct cali_tc_ctx *ctx) diff --git a/felix/bpf-gpl/reasons.h b/felix/bpf-gpl/reasons.h index d521191f5ea..4949496d21c 100644 --- a/felix/bpf-gpl/reasons.h +++ b/felix/bpf-gpl/reasons.h @@ -33,6 +33,8 @@ enum calico_reason { CALI_REASON_FRAG_REORDER, CALI_REASON_FRAG_UNSUPPORTED, CALI_REASON_DROPPED_BY_QOS, + RESERVED1, + CALI_REASON_MAGLEV_NO_BACKEND, // Not used by counters map CALI_REASON_ACCEPTED_BY_XDP, CALI_REASON_WEP_NOT_READY, diff --git a/felix/bpf-gpl/routes.h b/felix/bpf-gpl/routes.h index 5e394fb1c84..adc6347e5ef 100644 --- a/felix/bpf-gpl/routes.h +++ b/felix/bpf-gpl/routes.h @@ -15,11 +15,6 @@ struct cali_rt_key { ipv46_addr_t addr; // NBO }; -union cali_rt_lpm_key { - struct bpf_lpm_trie_key lpm; - struct cali_rt_key key; -}; - enum cali_rt_flags { CALI_RT_UNKNOWN = 0x00, CALI_RT_IN_POOL = 0x01, @@ -38,12 +33,9 @@ enum cali_rt_flags { struct cali_rt { __u32 flags; /* enum cali_rt_flags */ - union { - // IP encap next hop for remote workload routes. - ipv46_addr_t next_hop; - // Interface index for local workload routes. - __u32 if_index; - }; + // IP encap next hop for remote workload routes. + // or ifindex for local workload routes. + ipv46_addr_t next_hop; }; #ifdef IPVER6 @@ -52,18 +44,18 @@ CALI_MAP_NAMED(cali_v6_routes, cali_routes,, CALI_MAP_NAMED(cali_v4_routes, cali_routes,, #endif BPF_MAP_TYPE_LPM_TRIE, - union cali_rt_lpm_key, struct cali_rt, + struct cali_rt_key, struct cali_rt, 256*1024, BPF_F_NO_PREALLOC) static CALI_BPF_INLINE struct cali_rt *cali_rt_lookup(ipv46_addr_t *addr) { - union cali_rt_lpm_key k; + struct cali_rt_key k; #ifdef IPVER6 - k.key.prefixlen = 128; + k.prefixlen = 128; #else - k.key.prefixlen = 32; + k.prefixlen = 32; #endif - k.key.addr = *addr; + k.addr = *addr; return cali_routes_lookup_elem(&k); } @@ -76,6 +68,7 @@ static CALI_BPF_INLINE enum cali_rt_flags cali_rt_lookup_flags(ipv46_addr_t *add return rt->flags; } +#define CALI_RT_IFINDEX(rt) (*((__u32 *)&(rt)->next_hop)) #define cali_rt_is_local(rt) ((rt)->flags & CALI_RT_LOCAL) #define cali_rt_is_host(rt) ((rt)->flags & CALI_RT_HOST) #define cali_rt_is_workload(rt) ((rt)->flags & CALI_RT_WORKLOAD) diff --git a/felix/bpf-gpl/rpf.h b/felix/bpf-gpl/rpf.h index 430fc226251..318d579cfdc 100644 --- a/felix/bpf-gpl/rpf.h +++ b/felix/bpf-gpl/rpf.h @@ -31,9 +31,9 @@ static CALI_BPF_INLINE int wep_rpf_check(struct cali_tc_ctx *ctx, struct cali_rt CALI_INFO("Workload RPF fail: not a local workload."); return RPF_RES_FAIL; } - if (r->if_index != ctx->skb->ifindex) { + if (CALI_RT_IFINDEX(r) != ctx->skb->ifindex) { CALI_INFO("Workload RPF fail skb iface (%d) != route iface (%d)", - ctx->skb->ifindex, r->if_index); + ctx->skb->ifindex, CALI_RT_IFINDEX(r)); return RPF_RES_FAIL; } @@ -44,9 +44,6 @@ static CALI_BPF_INLINE int hep_rpf_check(struct cali_tc_ctx *ctx) { int ret = RPF_RES_FAIL; bool strict; -#ifdef IPVER6 - bool linkLocal = false; -#endif if (!(GLOBAL_FLAGS & CALI_GLOBALS_RPF_OPTION_ENABLED)) { CALI_DEBUG("Host RPF check disabled"); return RPF_RES_DISABLED; @@ -56,9 +53,6 @@ static CALI_BPF_INLINE int hep_rpf_check(struct cali_tc_ctx *ctx) if (ctx->state->ip_proto == IPPROTO_ICMPV6) { return RPF_RES_LOOSE; } - if (ip_link_local(ctx->state->ip_dst) && ip_link_local(ctx->state->ip_src)) { - linkLocal = true; - } #endif strict = GLOBAL_FLAGS & CALI_GLOBALS_RPF_OPTION_STRICT; @@ -119,13 +113,17 @@ static CALI_BPF_INLINE int hep_rpf_check(struct cali_tc_ctx *ctx) #endif } break; -#ifdef IPVER6 case BPF_FIB_LKUP_RET_NOT_FWDED: - if (linkLocal) { - ret = RPF_RES_STRICT; + if (ip_link_local(ctx->state->ip_src)) { +#ifdef IPVER6 + if (ip_link_local(ctx->state->ip_dst)){ +#else + if (ip_equal(ctx->state->ip_dst, HOST_IP)) { +#endif + ret = RPF_RES_STRICT; + } } break; -#endif } diff --git a/felix/bpf-gpl/tc.c b/felix/bpf-gpl/tc.c index 4f2956996a0..d4d8ef74cf7 100644 --- a/felix/bpf-gpl/tc.c +++ b/felix/bpf-gpl/tc.c @@ -1,5 +1,5 @@ // Project Calico BPF dataplane programs. -// Copyright (c) 2020-2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2020-2026 Tigera, Inc. All rights reserved. // SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later #include @@ -56,9 +56,13 @@ #include "bpf_helpers.h" #include "rule_counters.h" #include "qos.h" +#include "maglev.h" #ifndef IPVER6 #include "ip_v4_fragment.h" +#include "tcp4.h" +#else +#include "tcp6.h" #endif #define HAS_HOST_CONFLICT_PROG CALI_F_TO_HEP @@ -69,13 +73,6 @@ SEC("tc") int calico_tc_main(struct __sk_buff *skb) { -#ifdef UNITTEST - /* UT-only workaround to allow us to run the program with BPF_TEST_PROG_RUN - * and simulate a specific mark - */ - skb->mark = SKB_MARK; -#endif - if (CALI_F_LO && CALI_F_TO_HOST) { /* Do nothing, it is a packet that just looped around. */ return TC_ACT_UNSPEC; @@ -83,29 +80,21 @@ int calico_tc_main(struct __sk_buff *skb) /* Optimisation: if another BPF program has already pre-approved the packet, * skip all processing. */ - if (CALI_F_FROM_HOST && skb->mark == CALI_SKB_MARK_BYPASS && - /* If we are on vxlan and we do not have the key set, we cannot short-cirquit */ - !(CALI_F_VXLAN && - !skb_mark_equals(skb, CALI_SKB_MARK_TUNNEL_KEY_SET, CALI_SKB_MARK_TUNNEL_KEY_SET))) { + if (CALI_F_FROM_HOST && skb_mark_equals(skb, CALI_SKB_MARK_BYPASS, CALI_SKB_MARK_BYPASS) && + /* If we are on tunnel and we do not have the key set, we cannot short-circuit */ + !(CALI_F_TUNNEL && !skb_mark_equals(skb, CALI_SKB_MARK_TUNNEL_KEY_SET, CALI_SKB_MARK_TUNNEL_KEY_SET))) { if (CALI_LOG_LEVEL >= CALI_LOG_LEVEL_DEBUG) { /* This generates a bit more richer output for logging */ DECLARE_TC_CTX(_ctx, .skb = skb, - .fwd = { - .res = TC_ACT_UNSPEC, - .reason = CALI_REASON_UNKNOWN, - }, .ipheader_len = IP_SIZE, ); + _ctx.state->fwd.res = TC_ACT_UNSPEC; + _ctx.state->fwd.reason = CALI_REASON_UNKNOWN; struct cali_tc_ctx *ctx = &_ctx; CALI_DEBUG("New packet at ifindex=%d; mark=%x", skb->ifindex, skb->mark); parse_packet_ip(ctx); - if (CALI_F_WEP && qos_enforce_packet_rate(ctx) == TC_ACT_SHOT) { - CALI_DEBUG("Final result=DENY (%d). Dropped due to packet rate QoS.", CALI_REASON_DROPPED_BY_QOS); - deny_reason(ctx, CALI_REASON_DROPPED_BY_QOS); - return TC_ACT_SHOT; - } CALI_DEBUG("Final result=ALLOW (%d). Bypass mark set.", CALI_REASON_BYPASS); } return TC_ACT_UNSPEC; @@ -151,28 +140,33 @@ int calico_tc_main(struct __sk_buff *skb) * we use to pass data from one program to the next via tail calls. */ DECLARE_TC_CTX(_ctx, .skb = skb, - .fwd = { - .res = TC_ACT_UNSPEC, - .reason = CALI_REASON_UNKNOWN, - }, .ipheader_len = IP_SIZE, ); struct cali_tc_ctx *ctx = &_ctx; __builtin_memset(ctx->state, 0, sizeof(*ctx->state)); + _ctx.state->fwd.res = TC_ACT_UNSPEC; + _ctx.state->fwd.reason = CALI_REASON_UNKNOWN; CALI_DEBUG("New packet at ifindex=%d; mark=%x", skb->ifindex, skb->mark); counter_inc(ctx, COUNTER_TOTAL_PACKETS); + if (CALI_F_WEP && qos_enforce_packet_rate(ctx) == TC_ACT_SHOT) { + CALI_DEBUG("Drop packet due to QoS packet rate limit."); + deny_reason(ctx, CALI_REASON_DROPPED_BY_QOS); + ctx->state->fwd.res = TC_ACT_SHOT; + goto finalize; + } + if (CALI_LOG_LEVEL >= CALI_LOG_LEVEL_INFO || PROFILING) { ctx->state->prog_start_time = bpf_ktime_get_ns(); } /* We only try a FIB lookup and redirect for packets that are towards the host. * For packets that are leaving the host namespace, routing has already been done. */ - fwd_fib_set(&ctx->fwd, CALI_F_TO_HOST); + fwd_fib_set(&ctx->state->fwd, CALI_F_TO_HOST); if (CALI_F_TO_HEP || CALI_F_TO_WEP) { /* We're leaving the host namespace, check for other bypass mark bits. @@ -200,25 +194,25 @@ int calico_tc_main(struct __sk_buff *skb) #endif case PARSING_ALLOW_WITHOUT_ENFORCING_POLICY: // A packet that we automatically let through - fwd_fib_set(&ctx->fwd, false); - ctx->fwd.res = TC_ACT_UNSPEC; + fwd_fib_set(&ctx->state->fwd, false); + ctx->state->fwd.res = TC_ACT_UNSPEC; goto finalize; case PARSING_ERROR: default: // A malformed packet or a packet we don't support CALI_DEBUG("Drop malformed or unsupported packet"); - ctx->fwd.res = TC_ACT_SHOT; + ctx->state->fwd.res = TC_ACT_SHOT; goto finalize; } - if (CALI_F_VXLAN && CALI_F_TO_HEP + if (CALI_F_TUNNEL && CALI_F_TO_HEP && skb_mark_equals(ctx->skb, CALI_SKB_MARK_BYPASS, CALI_SKB_MARK_BYPASS)) { - /* In case we are on VXLAN device, CALI_SKB_MARK_BYPASS is set we only got + /* In case we are on tunnel device, CALI_SKB_MARK_BYPASS is set we only got * here because CALI_SKB_MARK_TUNNEL_KEY_SET wasn't set. This happens when * redirecting on a WEP was disabled, e.g. not to bypass the qdisc. We do * not have the key set, but CALI_SKB_MARK_BYPASS tells us that we do not * need to do more than that. Juset forward the packet. We already parsed - * IP header so we have enough to forward via vxlan. So just got to allow + * IP header so we have enough to forward via tunnel. So just got to allow * and forward it. forward_or_drop() will set the key. */ tc_state_fill_from_iphdr(ctx); @@ -240,7 +234,7 @@ int calico_tc_main(struct __sk_buff *skb) #ifndef IPVER6 deny: - ctx->fwd.res = TC_ACT_SHOT; + ctx->state->fwd.res = TC_ACT_SHOT; goto finalize; #endif } @@ -260,6 +254,10 @@ static CALI_BPF_INLINE int pre_policy_processing(struct cali_tc_ctx *ctx) case PARSING_ERROR: goto deny; case PARSING_ALLOW_WITHOUT_ENFORCING_POLICY: + if (CALI_F_FROM_HEP && (ctx->state->ip_proto == IPPROTO_IPIP || + ctx->state->dport == WG_PORT)) { + fwd_fib_set(&(ctx->state->fwd), false); + } goto allow; } @@ -275,7 +273,7 @@ static CALI_BPF_INLINE int pre_policy_processing(struct cali_tc_ctx *ctx) case -2: /* Non-BPF VXLAN packet from another Calico node. */ CALI_DEBUG("VXLAN packet from known Calico host, allow."); - fwd_fib_set(&(ctx->fwd), false); + fwd_fib_set(&(ctx->state->fwd), false); goto allow; } @@ -294,7 +292,11 @@ static CALI_BPF_INLINE int pre_policy_processing(struct cali_tc_ctx *ctx) #ifndef IPVER6 if ((CALI_F_FROM_HOST || CALI_F_FROM_WEP) && ip_is_frag(ip_hdr(ctx)) && !ip_is_first_frag(ip_hdr(ctx))) { - if (frags4_lookup_ct(ctx)) { + struct frags4_fwd_value *frag_ct_val = frags4_lookup_ct(ctx); + if (frag_ct_val) { + ctx->state->sport = frag_ct_val->sport; + ctx->state->dport = frag_ct_val->dport; + ctx->state->fwd.mark = frag_ct_val->seen_mark; if (ip_is_last_frag(ip_hdr(ctx))) { frags4_remove_ct(ctx); } @@ -317,7 +319,7 @@ static CALI_BPF_INLINE int pre_policy_processing(struct cali_tc_ctx *ctx) finalize: return forward_or_drop(ctx); deny: - ctx->fwd.res = TC_ACT_SHOT; + ctx->state->fwd.res = TC_ACT_SHOT; goto finalize; } @@ -358,16 +360,18 @@ static CALI_BPF_INLINE void calico_tc_process_ct_lookup(struct cali_tc_ctx *ctx) } /* Check if someone is trying to spoof a tunnel packet */ - if (CALI_F_FROM_HEP && ct_result_tun_src_changed(ctx->state->ct_result.rc)) { - CALI_DEBUG("dropping tunnel pkt with changed source node"); + if (CALI_F_FROM_HEP + && ct_result_tun_src_changed(ctx->state->ct_result.rc) + && !(ctx->state->ct_result.flags & CALI_CT_FLAG_MAGLEV)) { + CALI_DEBUG("dropping non-maglev tunnel pkt with changed source node"); goto deny; } if (ctx->state->ct_result.flags & CALI_CT_FLAG_NAT_OUT) { ctx->state->flags |= CALI_ST_NAT_OUTGOING; } - if (ctx->state->ct_result.flags & CALI_CT_FLAG_CLUSTER_EXTERNAL) { - ctx->state->flags |= CALI_ST_CLUSTER_EXTERNAL; + if (ctx->state->ct_result.flags & CALI_CT_FLAG_SET_DSCP) { + ctx->state->flags |= CALI_ST_SET_DSCP; } if (CALI_F_TO_HOST && !CALI_F_NAT_IF && @@ -382,17 +386,23 @@ static CALI_BPF_INLINE void calico_tc_process_ct_lookup(struct cali_tc_ctx *ctx) goto deny; } + // Handle all non-Maglev midflow misses here. + // That's all misses that do not ingress on a HEP. if (ct_result_rc(ctx->state->ct_result.rc) == CALI_CT_MID_FLOW_MISS) { if (CALI_F_TO_HOST) { - /* Mid-flow miss: let iptables handle it in case it's an existing flow - * in the Linux conntrack table. We can't apply policy or DNAT because - * it's too late in the flow. iptables will drop if the flow is not - * known. - */ - CALI_DEBUG("CT mid-flow miss; fall through to iptables."); - ctx->fwd.mark = CALI_SKB_MARK_FALLTHROUGH; - fwd_fib_set(&ctx->fwd, false); - goto finalize; + if (CALI_F_FROM_HEP) { + CALI_DEBUG("CT mid-flow miss; possible Maglev failover packet, will perform a NAT lookup"); + } else { + /* Mid-flow miss: let iptables handle it in case it's an existing flow + * in the Linux conntrack table. We can't apply policy or DNAT because + * it's too late in the flow. iptables will drop if the flow is not + * known. + */ + CALI_DEBUG("CT mid-flow miss; fall through to iptables"); + ctx->state->fwd.mark = CALI_SKB_MARK_FALLTHROUGH; + fwd_fib_set(&ctx->state->fwd, false); + goto finalize; + } } else { if (CALI_F_HEP) { // HEP egress for a mid-flow packet with no BPF or Linux CT state. @@ -422,23 +432,30 @@ static CALI_BPF_INLINE void calico_tc_process_ct_lookup(struct cali_tc_ctx *ctx) // suppress CT state creation and to drop the packet if we find that // there is a HEP present. CALI_DEBUG("CT mid-flow miss to HEP with no Linux conntrack entry: " - "continue but suppressing CT state creation."); + "continue but suppressing CT state creation"); ctx->state->flags |= CALI_ST_SUPPRESS_CT_STATE; ct_result_set_rc(ctx->state->ct_result.rc, CALI_CT_NEW); } else { CALI_DEBUG("CT mid-flow miss away from host with no Linux " - "conntrack entry, drop."); + "conntrack entry, will drop"); goto deny; } } } /* Skip policy if we get conntrack hit */ - if (ct_result_rc(ctx->state->ct_result.rc) != CALI_CT_NEW) { + if (ct_result_rc(ctx->state->ct_result.rc) != CALI_CT_NEW && ct_result_rc(ctx->state->ct_result.rc) != CALI_CT_MID_FLOW_MISS) { if (ctx->state->ct_result.flags & CALI_CT_FLAG_SKIP_FIB) { ctx->state->flags |= CALI_ST_SKIP_FIB; } CALI_DEBUG("CT Hit"); + /* Check for TCP RST injection */ + if (CALI_F_TO_HOST && ctx->state->ct_result.flags & CALI_CT_FLAG_SEND_RESET) { + CALI_DEBUG("Sending TCP RST due to CT state"); + ctx->state->ct_result.ifindex_fwd = CT_INVALID_IFINDEX; + CALI_JUMP_TO(ctx, PROG_INDEX_TCP_RST); + goto deny; + } if (ctx->state->ip_proto == IPPROTO_TCP && ct_result_is_syn(ctx->state->ct_result.rc)) { CALI_DEBUG("Forcing policy on SYN"); @@ -465,6 +482,28 @@ static CALI_BPF_INLINE void calico_tc_process_ct_lookup(struct cali_tc_ctx *ctx) !ip_void(ctx->state->tun_ip), &nat_res); } + if (HAS_MAGLEV && nat_res == NAT_MAGLEV) { + /* Packet to be handled by maglev*/ + CALI_DEBUG("NAT Lookup determined packet to be Maglev-related"); + CALI_JUMP_TO(ctx, PROG_INDEX_MAGLEV); + CALI_DEBUG("Failed to jump to maglev program"); + goto deny; + } else if (ct_result_rc(ctx->state->ct_result.rc) == CALI_CT_MID_FLOW_MISS) { + // A non-Maglev mid-flow miss. We would have handled this above, + // but we required a NAT lookup to check if the packet was Maglev. + // We can now handle this as normal. + + /* Mid-flow miss: let iptables handle it in case it's an existing flow + * in the Linux conntrack table. We can't apply policy or DNAT because + * it's too late in the flow. iptables will drop if the flow is not + * known. + */ + CALI_DEBUG("CT mid-flow miss and not a Maglev failover. Fall through to iptables"); + ctx->state->fwd.mark = CALI_SKB_MARK_FALLTHROUGH; + fwd_fib_set(&ctx->state->fwd, false); + goto finalize; + } + if (nat_res == NAT_FE_LOOKUP_DROP) { CALI_DEBUG("Packet is from an unauthorised source: DROP"); deny_reason(ctx, CALI_REASON_UNAUTH_SOURCE); @@ -562,7 +601,7 @@ static CALI_BPF_INLINE void calico_tc_process_ct_lookup(struct cali_tc_ctx *ctx) } else { if (cali_rt_flags_should_set_dscp(dst_flags)) { CALI_DEBUG("Remote host or outside cluster dest " IP_FMT "", debug_ip(ctx->state->post_nat_ip_dst)); - ctx->state->flags |= CALI_ST_CLUSTER_EXTERNAL; + ctx->state->flags |= CALI_ST_SET_DSCP; } } } @@ -570,11 +609,11 @@ static CALI_BPF_INLINE void calico_tc_process_ct_lookup(struct cali_tc_ctx *ctx) /* If either source or destination is outside cluster, set flag as might need to update DSCP later. */ if ((CALI_F_TO_HEP) && (cali_rt_flags_local_host(src_flags)) && cali_rt_flags_should_set_dscp(dst_flags)) { CALI_DEBUG("Remote host or outside cluster dest " IP_FMT "", debug_ip(ctx->state->post_nat_ip_dst)); - ctx->state->flags |= CALI_ST_CLUSTER_EXTERNAL; + ctx->state->flags |= CALI_ST_SET_DSCP; } if ((CALI_F_FROM_HEP) && cali_rt_flags_should_set_dscp(src_flags)) { CALI_DEBUG("Remote host or outside cluster source " IP_FMT "", debug_ip(ctx->state->ip_src)); - ctx->state->flags |= CALI_ST_CLUSTER_EXTERNAL; + ctx->state->flags |= CALI_ST_SET_DSCP; } /* [SMC] I had to add this revalidation when refactoring the conntrack code to use the context and @@ -774,7 +813,7 @@ static CALI_BPF_INLINE void calico_tc_process_ct_lookup(struct cali_tc_ctx *ctx) return; deny: - ctx->fwd.res = TC_ACT_SHOT; + ctx->state->fwd.res = TC_ACT_SHOT; } enum do_nat_res { @@ -817,6 +856,7 @@ static CALI_BPF_INLINE enum do_nat_res do_nat(struct cali_tc_ctx *ctx, /* fall through */ case CALI_CT_NEW: + case CALI_CT_MAGLEV_MID_FLOW_MISS: /* We may not do a true DNAT here if we are resolving service source port * conflict with host->pod w/o service. See calico_tc_host_ct_conflict(). */ @@ -831,7 +871,7 @@ static CALI_BPF_INLINE enum do_nat_res do_nat(struct cali_tc_ctx *ctx, * if we need encap or not. Must do before MTU check and before * we jump to do the encap. */ - if (ct_ctx_nat /* iff CALI_CT_NEW */) { + if (ct_ctx_nat /* iff CALI_CT_NEW || CALI_CT_MAGLEV_MID_FLOW_MISS */) { struct cali_rt * rt; if (encap_needed) { @@ -904,9 +944,9 @@ static CALI_BPF_INLINE enum do_nat_res do_nat(struct cali_tc_ctx *ctx, } } if (encap_needed) { - if (!(STATE->ip_proto == IPPROTO_TCP && skb_is_gso(ctx->skb)) && - ip_is_dnf(ip_hdr(ctx)) && vxlan_encap_too_big(ctx)) { - CALI_DEBUG("Request packet with DNF set is too big"); + if (!skb_is_gso(ctx->skb) && ip_is_dnf(ip_hdr(ctx)) && vxlan_encap_too_big(ctx)) { + CALI_DEBUG("Return ICMP mtu is too big segs %d size %d", + ctx->skb->gso_segs, ctx->skb->gso_size); goto icmp_too_big; } STATE->ip_src = HOST_IP; @@ -1036,9 +1076,9 @@ static CALI_BPF_INLINE enum do_nat_res do_nat(struct cali_tc_ctx *ctx, goto allow; } - if (!(STATE->ip_proto == IPPROTO_TCP && skb_is_gso(ctx->skb)) && - ip_is_dnf(ip_hdr(ctx)) && vxlan_encap_too_big(ctx)) { - CALI_DEBUG("Return ICMP mtu is too big"); + if (!skb_is_gso(ctx->skb) && ip_is_dnf(ip_hdr(ctx)) && vxlan_encap_too_big(ctx)) { + CALI_DEBUG("Return ICMP mtu is too big segs %d size %d", + ctx->skb->gso_segs, ctx->skb->gso_size); goto icmp_too_big; } } @@ -1216,11 +1256,11 @@ static CALI_BPF_INLINE enum do_nat_res do_nat(struct cali_tc_ctx *ctx, return NAT_ENCAP_ALLOW; } -static CALI_BPF_INLINE struct fwd post_nat(struct cali_tc_ctx *ctx, - enum do_nat_res nat_res, - bool fib, - __u32 seen_mark, - bool is_dnat) +static CALI_BPF_INLINE void post_nat(struct cali_tc_ctx *ctx, + enum do_nat_res nat_res, + bool fib, + __u32 seen_mark, + bool is_dnat) { struct cali_tc_state *state = ctx->state; int rc = TC_ACT_UNSPEC; @@ -1250,7 +1290,7 @@ static CALI_BPF_INLINE struct fwd post_nat(struct cali_tc_ctx *ctx, struct cali_rt *r = cali_rt_lookup(&state->post_nat_ip_dst); if (r && cali_rt_flags_local_workload(r->flags)) { - state->ct_result.ifindex_fwd = r->if_index; + state->ct_result.ifindex_fwd = CALI_RT_IFINDEX(r); CALI_DEBUG("NP local WL " IP_FMT ":%d on HEP", debug_ip(state->post_nat_ip_dst), state->post_nat_dport); ctx->state->flags |= CALI_ST_CT_NP_LOOP; @@ -1269,23 +1309,14 @@ static CALI_BPF_INLINE struct fwd post_nat(struct cali_tc_ctx *ctx, } encap_allow: - { - struct fwd fwd = { - .res = rc, - .mark = seen_mark, - }; - fwd_fib_set(&fwd, fib); - return fwd; - } + ctx->state->fwd.res = rc; + ctx->state->fwd.mark = seen_mark; + fwd_fib_set(&ctx->state->fwd, fib); + return; deny: - { - struct fwd fwd = { - .res = TC_ACT_SHOT, - .reason = ctx->fwd.reason, - }; - return fwd; - } + ctx->state->fwd.res = TC_ACT_SHOT; + return; } SEC("tc") @@ -1295,11 +1326,6 @@ int calico_tc_skb_accepted_entrypoint(struct __sk_buff *skb) * we use to pass data from one program to the next via tail calls. */ DECLARE_TC_CTX(_ctx, .skb = skb, - .fwd = { - .res = TC_ACT_UNSPEC, - .reason = CALI_REASON_UNKNOWN, - .mark = CALI_SKB_MARK_SEEN, - }, ); struct cali_tc_ctx *ctx = &_ctx; bool policy_skipped = ctx->state->flags & CALI_ST_SKIP_POLICY; @@ -1338,20 +1364,10 @@ int calico_tc_skb_accepted_entrypoint(struct __sk_buff *skb) skb_log(ctx, true); } -#ifndef IPVER6 - if ((CALI_F_FROM_HOST || CALI_F_FROM_WEP) && ip_is_first_frag(ip_hdr(ctx))) { - frags4_record_ct(ctx); - } -#endif - - if (CALI_F_WEP && qos_enforce_packet_rate(ctx) == TC_ACT_SHOT) { - deny_reason(ctx, CALI_REASON_DROPPED_BY_QOS); - goto deny; - } if ((CALI_F_FROM_WEP || CALI_F_TO_HEP) && qos_dscp_needs_update(ctx) && !qos_dscp_set(ctx)) { goto deny; } - ctx->fwd = calico_tc_skb_accepted(ctx); + calico_tc_skb_accepted(ctx); return forward_or_drop(ctx); deny: @@ -1378,17 +1394,12 @@ int calico_tc_skb_new_flow_entrypoint(struct __sk_buff *skb) { DECLARE_TC_CTX(_ctx, .skb = skb, - .fwd = { - .res = TC_ACT_UNSPEC, - .reason = CALI_REASON_UNKNOWN, - .mark = CALI_SKB_MARK_SEEN, - }, ); struct cali_tc_ctx *ctx = &_ctx; struct cali_tc_state *state = ctx->state; enum do_nat_res nat_res = NAT_ALLOW; bool is_dnat = false; - __u32 seen_mark = ctx->fwd.mark; + __u32 seen_mark = ctx->state->fwd.mark; bool fib = true; CALI_DEBUG("Entering calico_tc_skb_new_flow"); @@ -1426,11 +1437,14 @@ int calico_tc_skb_new_flow_entrypoint(struct __sk_buff *skb) ct_ctx_nat->tun_ip = state->tun_ip; ct_ctx_nat->type = CALI_CT_TYPE_NORMAL; ct_ctx_nat->allow_return = false; + if (state->ct_result.flags & CALI_CT_FLAG_MAGLEV) { + ct_ctx_nat->flags |= CALI_CT_FLAG_MAGLEV; + } if (state->flags & CALI_ST_NAT_OUTGOING) { ct_ctx_nat->flags |= CALI_CT_FLAG_NAT_OUT; } - if (state->flags & CALI_ST_CLUSTER_EXTERNAL) { - ct_ctx_nat->flags |= CALI_CT_FLAG_CLUSTER_EXTERNAL; + if (state->flags & CALI_ST_SET_DSCP) { + ct_ctx_nat->flags |= CALI_CT_FLAG_SET_DSCP; } if (CALI_F_TO_HOST && state->flags & CALI_ST_SKIP_FIB) { ct_ctx_nat->flags |= CALI_CT_FLAG_SKIP_FIB; @@ -1565,7 +1579,7 @@ int calico_tc_skb_new_flow_entrypoint(struct __sk_buff *skb) allow: do_post_nat: - ctx->fwd = post_nat(ctx, nat_res, fib, seen_mark, is_dnat); + post_nat(ctx, nat_res, fib, seen_mark, is_dnat); return forward_or_drop(ctx); icmp_send_reply: @@ -1577,14 +1591,14 @@ int calico_tc_skb_new_flow_entrypoint(struct __sk_buff *skb) goto do_post_nat; } -static CALI_BPF_INLINE struct fwd calico_tc_skb_accepted(struct cali_tc_ctx *ctx) +static CALI_BPF_INLINE void calico_tc_skb_accepted(struct cali_tc_ctx *ctx) { CALI_DEBUG("Entering calico_tc_skb_accepted"); struct cali_tc_state *state = ctx->state; bool fib = true; int ct_rc = ct_result_rc(state->ct_result.rc); bool ct_related = ct_result_is_related(state->ct_result.rc); - __u32 seen_mark = ctx->fwd.mark; + __u32 seen_mark = ctx->state->fwd.mark; size_t l4_csum_off = 0; #ifndef IPVER6 size_t l3_csum_off = 0; @@ -1603,7 +1617,7 @@ static CALI_BPF_INLINE struct fwd calico_tc_skb_accepted(struct cali_tc_ctx *ctx CALI_DEBUG("ct_related=%d", ct_related); CALI_DEBUG("mark=0x%x", seen_mark); - ctx->fwd.reason = CALI_REASON_UNKNOWN; + ctx->state->fwd.reason = CALI_REASON_UNKNOWN; // Set the dport to 0, to make sure conntrack entries for icmp is proper as we use // dport to hold icmp type and code @@ -1635,7 +1649,7 @@ static CALI_BPF_INLINE struct fwd calico_tc_skb_accepted(struct cali_tc_ctx *ctx } } - if (ct_rc == CALI_CT_NEW) { + if (ct_rc == CALI_CT_NEW || ct_rc == CALI_CT_MAGLEV_MID_FLOW_MISS) { CALI_JUMP_TO(ctx, PROG_INDEX_NEW_FLOW); /* should not reach here */ CALI_DEBUG("jump to new flow failed"); @@ -1791,7 +1805,8 @@ static CALI_BPF_INLINE struct fwd calico_tc_skb_accepted(struct cali_tc_ctx *ctx allow: do_post_nat: - return post_nat(ctx, nat_res, fib, seen_mark, is_dnat); + post_nat(ctx, nat_res, fib, seen_mark, is_dnat); + return; deny: nat_res = NAT_DENY; @@ -1805,10 +1820,6 @@ int calico_tc_skb_icmp_inner_nat(struct __sk_buff *skb) * we use to pass data from one program to the next via tail calls. */ DECLARE_TC_CTX(_ctx, .skb = skb, - .fwd = { - .res = TC_ACT_UNSPEC, - .reason = CALI_REASON_UNKNOWN, - }, ); struct cali_tc_ctx *ctx = &_ctx; @@ -1838,7 +1849,7 @@ int calico_tc_skb_icmp_inner_nat(struct __sk_buff *skb) default: // A malformed packet or a packet we don't support CALI_DEBUG("ICMP: Drop malformed or unsupported packet"); - ctx->fwd.res = TC_ACT_SHOT; + ctx->state->fwd.res = TC_ACT_SHOT; goto deny; } @@ -1894,7 +1905,7 @@ int calico_tc_skb_icmp_inner_nat(struct __sk_buff *skb) case CALI_CT_ESTABLISHED_DNAT: if (CALI_F_FROM_HEP && !ip_void(state->tun_ip) && ct_result_np_node(state->ct_result)) { /* Packet is returning from a NAT tunnel, just forward it. */ - ctx->fwd.mark = CALI_SKB_MARK_BYPASS_FWD; + ctx->state->fwd.mark = CALI_SKB_MARK_BYPASS_FWD; CALI_DEBUG("ICMP related returned from NAT tunnel"); goto allow; } @@ -1904,11 +1915,11 @@ int calico_tc_skb_icmp_inner_nat(struct __sk_buff *skb) bool is_dnat = false; enum do_nat_res nat_res = NAT_ALLOW; - __u32 seen_mark = ctx->fwd.mark; + __u32 seen_mark = ctx->state->fwd.mark; bool fib = true; nat_res = do_nat(ctx, inner_ip_offset, icmp_csum_off, false, ct_rc, NULL, &is_dnat, &seen_mark, true); - ctx->fwd = post_nat(ctx, nat_res, fib, seen_mark, is_dnat); + post_nat(ctx, nat_res, fib, seen_mark, is_dnat); allow: /* We are going to forward the packet now. But all the state is about @@ -1928,7 +1939,7 @@ int calico_tc_skb_icmp_inner_nat(struct __sk_buff *skb) goto deny; } tc_state_fill_from_iphdr(ctx); - fwd_fib_set(&ctx->fwd, true); + fwd_fib_set(&ctx->state->fwd, true); return forward_or_drop(ctx); @@ -1936,6 +1947,39 @@ int calico_tc_skb_icmp_inner_nat(struct __sk_buff *skb) return TC_ACT_SHOT; } +SEC("tc") +int calico_tc_skb_send_tcp_rst(struct __sk_buff *skb) +{ + /* Initialise the context, which is stored on the stack, and the state, which + * we use to pass data from one program to the next via tail calls. */ + DECLARE_TC_CTX(_ctx, + .skb = skb, + ); + struct cali_tc_ctx *ctx = &_ctx; + int ret = 0; +#ifndef IPVER6 + ret = tcp_v4_rst(ctx); +#else + ret = tcp_v6_rst(ctx); +#endif + + CALI_DEBUG("Entering calico_tc_skb_send_tcp_rst"); + if (ret) { + ctx->state->fwd.res = TC_ACT_SHOT; + } else { + fwd_fib_set(&ctx->state->fwd, true); + } + + if (skb_refresh_validate_ptrs(ctx, TCP_SIZE)) { + deny_reason(ctx, CALI_REASON_SHORT); + CALI_DEBUG("Too short"); + return TC_ACT_SHOT; + } + + tc_state_fill_from_iphdr(ctx); + return forward_or_drop(ctx); +} + SEC("tc") int calico_tc_skb_send_icmp_replies(struct __sk_buff *skb) @@ -1946,10 +1990,6 @@ int calico_tc_skb_send_icmp_replies(struct __sk_buff *skb) * we use to pass data from one program to the next via tail calls. */ DECLARE_TC_CTX(_ctx, .skb = skb, - .fwd = { - .res = TC_ACT_UNSPEC, - .reason = CALI_REASON_UNKNOWN, - }, ); struct cali_tc_ctx *ctx = &_ctx; @@ -1964,17 +2004,17 @@ int calico_tc_skb_send_icmp_replies(struct __sk_buff *skb) fib_flags |= BPF_FIB_LOOKUP_OUTPUT; if (CALI_F_FROM_WEP) { /* we know it came from workload, just send it back the same way */ - ctx->fwd.res = CALI_RES_REDIR_BACK; + ctx->state->fwd.res = CALI_RES_REDIR_BACK; } } if (icmp_reply(ctx, ctx->state->icmp_type, ctx->state->icmp_code, ctx->state->icmp_un)) { - ctx->fwd.res = TC_ACT_SHOT; + ctx->state->fwd.res = TC_ACT_SHOT; } else { - ctx->fwd.mark = CALI_SKB_MARK_BYPASS_FWD; + ctx->state->fwd.mark = CALI_SKB_MARK_BYPASS_FWD; - fwd_fib_set(&ctx->fwd, false); - fwd_fib_set_flags(&ctx->fwd, fib_flags); + fwd_fib_set(&ctx->state->fwd, false); + fwd_fib_set_flags(&ctx->state->fwd, fib_flags); } if (skb_refresh_validate_ptrs(ctx, ICMP_SIZE)) { @@ -1999,10 +2039,6 @@ int calico_tc_host_ct_conflict(struct __sk_buff *skb) * we use to pass data from one program to the next via tail calls. */ DECLARE_TC_CTX(_ctx, .skb = skb, - .fwd = { - .res = TC_ACT_UNSPEC, - .reason = CALI_REASON_UNKNOWN, - }, ); struct cali_tc_ctx *ctx = &_ctx; @@ -2141,10 +2177,6 @@ int calico_tc_skb_ipv4_frag(struct __sk_buff *skb) * we use to pass data from one program to the next via tail calls. */ DECLARE_TC_CTX(_ctx, .skb = skb, - .fwd = { - .res = TC_ACT_UNSPEC, - .reason = CALI_REASON_UNKNOWN, - }, ); struct cali_tc_ctx *ctx = &_ctx; @@ -2182,7 +2214,79 @@ int calico_tc_skb_ipv4_frag(struct __sk_buff *skb) return forward_or_drop(ctx); deny: - ctx->fwd.res = TC_ACT_SHOT; + ctx->state->fwd.res = TC_ACT_SHOT; goto finalize; } #endif /* !IPVER6 */ + +#if HAS_MAGLEV +SEC("tc") +int calico_tc_maglev(struct __sk_buff *skb) +{ + DECLARE_TC_CTX(_ctx, + .skb = skb, + ); + struct cali_tc_ctx *ctx = &_ctx; + + CALI_DEBUG("Entering calico_tc_maglev"); + + if (skb_refresh_validate_ptrs(ctx, UDP_SIZE)) { + deny_reason(ctx, CALI_REASON_SHORT); + CALI_DEBUG("Too short"); + goto deny; + } + + if (!(ctx->nat_dest = maglev_select_backend(ctx))) { + CALI_DEBUG("Maglev: select backend failed"); + deny_reason(ctx, CALI_REASON_MAGLEV_NO_BACKEND); + goto deny; + } + + // Ammend CT state now that we know it's Maglev. + ctx->state->ct_result.flags |= CALI_CT_FLAG_MAGLEV; + ctx->state->post_nat_ip_dst = ctx->nat_dest->addr; + ctx->state->post_nat_dport = ctx->nat_dest->port; + /* XXX why do we need both? */ + ctx->state->nat_dest.addr = ctx->nat_dest->addr; + ctx->state->nat_dest.port = ctx->nat_dest->port; + + struct cali_rt *dest_rt = NULL; + dest_rt = cali_rt_lookup(&ctx->state->post_nat_ip_dst); + if (!dest_rt) { + CALI_DEBUG("Maglev: No route for post DNAT dest " IP_FMT "", debug_ip(ctx->state->post_nat_ip_dst)); + goto deny; + } + if (cali_rt_flags_skip_ingress_redirect(dest_rt->flags)) { + ctx->state->flags |= CALI_ST_SKIP_REDIR_PEER; + } + + // CALI_CT_MID_FLOW_MISS implies traffic is TCP. If that changes, + // this condition should be adjusted. + if (ct_result_rc(ctx->state->ct_result.rc) == CALI_CT_MID_FLOW_MISS) { + /* treat it as if it was a new flow */ + CALI_DEBUG("Maglev: mid-flow miss, treating as new flow"); + /* remember that is was a mid-flow miss so that we can handle it in the new flow program */ + ctx->state->ct_result.rc = CALI_CT_MAGLEV_MID_FLOW_MISS; + } + + CALI_DEBUG("Maglev: About to jump to policy program."); + ctx->state->pol_rc = CALI_POL_NO_MATCH; + CALI_JUMP_TO_POLICY(ctx); /* after policy accepts the packet it will jump to allowed program */ + + if (CALI_F_HEP) { + CALI_DEBUG("Maglev: HEP with no policy, allow."); + ctx->state->pol_rc = CALI_POL_ALLOW; + ctx->state->flags |= CALI_ST_SKIP_POLICY; + CALI_JUMP_TO(ctx, PROG_INDEX_ALLOWED); + CALI_DEBUG("jump failed"); + /* should not reach here */ + goto deny; + } else { + CALI_DEBUG("Maglev: jump to policy failed."); + goto deny; + } + +deny: + return TC_ACT_SHOT; +} +#endif /* HAS_MAGLEV */ diff --git a/felix/bpf-gpl/tc.h b/felix/bpf-gpl/tc.h index e8a06ee78f2..e1195f53219 100644 --- a/felix/bpf-gpl/tc.h +++ b/felix/bpf-gpl/tc.h @@ -9,7 +9,7 @@ static CALI_BPF_INLINE int calico_tc(struct __sk_buff *skb); -static CALI_BPF_INLINE struct fwd calico_tc_skb_accepted(struct cali_tc_ctx *ctx); +static CALI_BPF_INLINE void calico_tc_skb_accepted(struct cali_tc_ctx *ctx); static CALI_BPF_INLINE int pre_policy_processing(struct cali_tc_ctx *ctx); static CALI_BPF_INLINE void calico_tc_process_ct_lookup(struct cali_tc_ctx *ctx); diff --git a/felix/bpf-gpl/tc_preamble.c b/felix/bpf-gpl/tc_preamble.c index 0db8ee725d5..f34eb4f2e03 100644 --- a/felix/bpf-gpl/tc_preamble.c +++ b/felix/bpf-gpl/tc_preamble.c @@ -55,6 +55,13 @@ int cali_tc_preamble(struct __sk_buff *skb) /* We do the copy once here so keep the program smaller */ globals->data = *globals_data; + // Clear bypass mark from packet if ingress packet rate QoS is configured + if (globals->data.flags & CALI_GLOBALS_INGRESS_PACKET_RATE_CONFIGURED) { + if (skb->mark == CALI_SKB_MARK_BYPASS) { + skb->mark = CALI_SKB_MARK_SEEN; + } + } + #if EMIT_LOGS CALI_LOG("tc_preamble iface %s", globals->data.iface_name); #endif diff --git a/felix/bpf-gpl/tcp4.h b/felix/bpf-gpl/tcp4.h new file mode 100644 index 00000000000..6b954c54762 --- /dev/null +++ b/felix/bpf-gpl/tcp4.h @@ -0,0 +1,84 @@ +// Project Calico BPF dataplane programs. +// Copyright (c) 2020-2026 Tigera, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +#ifndef __CALI_TCP4_H__ +#define __CALI_TCP4_H__ + +#include +#include + +#include "bpf.h" +#include "log.h" +#include "skb.h" + +static CALI_BPF_INLINE int tcp_v4_rst(struct cali_tc_ctx *ctx) { + if (skb_refresh_validate_ptrs(ctx, TCP_SIZE)) { + deny_reason(ctx, CALI_REASON_SHORT); + CALI_DEBUG("TCP reset : too short"); + return -1; + } + struct iphdr ip_orig = *ip_hdr(ctx); + struct tcphdr th_orig = *tcp_hdr(ctx); + int original_len = ctx->skb->len; + + /* Trim to minimum size */ + __u32 len = skb_iphdr_offset(ctx) + IP_SIZE + TCP_SIZE /* max IP len */; + int err = bpf_skb_change_tail(ctx->skb, len, 0); + if (err) { + CALI_DEBUG("tcp reset reply: bpf_skb_change_tail (len=%d) failed (err=%d)", len, err); + return -1; + } + + /* Revalidate all pointers */ + if (skb_refresh_validate_ptrs(ctx, TCP_SIZE)) { + deny_reason(ctx, CALI_REASON_SHORT); + CALI_DEBUG("TCP reset : too short"); + return -1; + } + ip_hdr(ctx)->version = 4; + ip_hdr(ctx)->ihl = 5; + ip_hdr(ctx)->tos = 0; + ip_hdr(ctx)->ttl = 64; + ip_hdr(ctx)->protocol = IPPROTO_TCP; + ip_hdr(ctx)->saddr = ip_orig.daddr; + ip_hdr(ctx)->daddr = ip_orig.saddr; + ip_hdr(ctx)->check = 0; + ip_hdr(ctx)->tot_len = bpf_htons(len - (CALI_F_L3_DEV ? 0 : ETH_SIZE)); + ctx->ipheader_len = 20; + + struct tcphdr *th = ((void *)ip_hdr(ctx)) + IP_SIZE; + __builtin_memset(th, 0, sizeof(struct tcphdr)); + th->source = th_orig.dest; + th->dest = th_orig.source; + th->rst = 1; + th->doff = sizeof(struct tcphdr) / 4; + th->seq = 0; + + if (th_orig.ack) { + th->seq = th_orig.ack_seq; + } else { + th->ack_seq = bpf_htonl(bpf_ntohl(th_orig.seq) + th_orig.syn + th_orig.fin + + original_len - (th_orig.doff << 2)); + th->ack = 1; + } + th->check = 0; + + __wsum ip_csum = bpf_csum_diff(0, 0, ctx->ip_header, sizeof(struct iphdr), 0); + __wsum tcp_csum = bpf_csum_diff(0, 0, (__u32 *)th, len - sizeof(struct iphdr) - skb_iphdr_offset(ctx), 0); + if (bpf_l3_csum_replace(ctx->skb, + skb_iphdr_offset(ctx) + offsetof(struct iphdr, check), 0, ip_csum, 0)) { + CALI_DEBUG("TCP reset v4 reply: set ip csum failed"); + return -1; + } + + err = bpf_l4_csum_replace(ctx->skb, skb_l4hdr_offset(ctx) + + offsetof(struct tcphdr, check), 0, tcp_csum, BPF_F_PSEUDO_HDR); + if (err) { + CALI_DEBUG("TCP reset v4 reply: set tcp csum failed %d", err); + return -1; + } + return 0; +} + +#endif /* __CALI_TCP4_H__ */ diff --git a/felix/bpf-gpl/tcp6.h b/felix/bpf-gpl/tcp6.h new file mode 100644 index 00000000000..9c41e65fd61 --- /dev/null +++ b/felix/bpf-gpl/tcp6.h @@ -0,0 +1,76 @@ +// Project Calico BPF dataplane programs. +// Copyright (c) 2020-2026 Tigera, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +#ifndef __CALI_TCP6_H__ +#define __CALI_TCP6_H__ + +#include +#include + +#include "bpf.h" +#include "log.h" +#include "skb.h" + +static CALI_BPF_INLINE int tcp_v6_rst(struct cali_tc_ctx *ctx) { + if (skb_refresh_validate_ptrs(ctx, TCP_SIZE)) { + deny_reason(ctx, CALI_REASON_SHORT); + CALI_DEBUG("TCP reset : too short"); + return -1; + } + + ipv6_addr_t orig_src, orig_dst; + ipv6hdr_ip_to_ipv6_addr_t(&orig_src, &ip_hdr(ctx)->saddr); + ipv6hdr_ip_to_ipv6_addr_t(&orig_dst, &ip_hdr(ctx)->daddr); + struct tcphdr th_orig = *tcp_hdr(ctx); + int original_len = ctx->skb->len; + + /* Trim to minimum size */ + __u32 len = skb_iphdr_offset(ctx) + IP_SIZE + TCP_SIZE /* max IP len */; + int err = bpf_skb_change_tail(ctx->skb, len, 0); + if (err) { + CALI_DEBUG("tcp reset reply: bpf_skb_change_tail (len=%d) failed (err=%d)", len, err); + return -1; + } + + /* Revalidate all pointers */ + if (skb_refresh_validate_ptrs(ctx, TCP_SIZE)) { + deny_reason(ctx, CALI_REASON_SHORT); + CALI_DEBUG("TCP reset : too short"); + return -1; + } + ip_hdr(ctx)->version = 6; + ip_hdr(ctx)->hop_limit = 255; + ip_hdr(ctx)->nexthdr = IPPROTO_TCP; + ipv6_addr_t_to_ipv6hdr_ip(&ip_hdr(ctx)->daddr, &orig_src); + ipv6_addr_t_to_ipv6hdr_ip(&ip_hdr(ctx)->saddr, &orig_dst); + ip_hdr(ctx)->payload_len = bpf_htons(TCP_SIZE); + ctx->ipheader_len = IP_SIZE; + + struct tcphdr *th = ((void *)ip_hdr(ctx)) + IP_SIZE; + __builtin_memset(th, 0, TCP_SIZE); + th->source = th_orig.dest; + th->dest = th_orig.source; + th->rst = 1; + th->doff = sizeof(struct tcphdr) / 4; + th->seq = 0; + + if (th_orig.ack) { + th->seq = th_orig.ack_seq; + } else { + th->ack_seq = bpf_htonl(bpf_ntohl(th_orig.seq) + th_orig.syn + th_orig.fin + + original_len - (th_orig.doff << 2)); + th->ack = 1; + } + th->check = 0; + + __wsum tcp_csum = bpf_csum_diff(0, 0, (__u32 *)th, len - sizeof(struct ipv6hdr) - skb_iphdr_offset(ctx), 0); + if (bpf_l4_csum_replace(ctx->skb, skb_l4hdr_offset(ctx) + + offsetof(struct tcphdr, check), 0, tcp_csum, BPF_F_PSEUDO_HDR)) { + CALI_DEBUG("TCP reset v6 reply: set tcp csum failed"); + return -1; + } + return 0; +} + +#endif /* __CALI_TCP6_H__ */ diff --git a/felix/bpf-gpl/types.h b/felix/bpf-gpl/types.h index 8938c59aa1b..08e39be3fd7 100644 --- a/felix/bpf-gpl/types.h +++ b/felix/bpf-gpl/types.h @@ -39,6 +39,14 @@ #define UDP_SIZE (sizeof(struct udphdr)) #define TCP_SIZE (sizeof(struct tcphdr)) +struct fwd { + int res; + __u32 mark; + enum calico_reason reason; + __u32 fib_flags; + bool fib; +}; + #define MAX_RULE_IDS 32 // struct cali_tc_state holds state that is passed between the BPF programs. @@ -105,7 +113,7 @@ struct cali_tc_state { __u64 flags; /* Result of the conntrack lookup. */ - struct calico_ct_result ct_result; /* 28 bytes */ + struct calico_ct_result ct_result; /* 32 bytes */ /* Result of the NAT calculation. Zeroed if there is no DNAT. */ struct calico_nat_dest nat_dest; /* 8 bytes */ @@ -115,9 +123,10 @@ struct cali_tc_state { * appropriate conntrack entry. */ DECLARE_IP_ADDR(ip_src_masq); -#ifndef IPVER6 - __u8 __pad_ipv4[48]; -#endif + + __u32 nat_svc_id; + + struct fwd fwd; }; struct pkt_scratch { @@ -158,19 +167,10 @@ enum cali_state_flags { CALI_ST_SKIP_REDIR_PEER = 0x800, /* CALI_ST_SKIP_REDIR_ONCE skips redirection once for this particular packet */ CALI_ST_SKIP_REDIR_ONCE = 0x1000, - /* CALI_ST_CLUSTER_EXTERNAL is set if the packet is heading toward or originating from - * an endpoint outside the cluster */ - CALI_ST_CLUSTER_EXTERNAL = 0x2000, -}; - -struct fwd { - int res; - __u32 mark; - enum calico_reason reason; -#if CALI_FIB_ENABLED - __u32 fib_flags; - bool fib; -#endif + /* CALI_ST_SET_DSCP is set if we need to update packet's DSCP */ + CALI_ST_SET_DSCP = 0x2000, + /* CALI_ST_FIRST_FRAG is set if this packet is the first fragment of a fragmented IP packet */ + CALI_ST_FIRST_FRAG = 0x4000, }; struct cali_tc_ctx { @@ -194,7 +194,6 @@ struct cali_tc_ctx { const volatile struct cali_xdp_globals *xdp_globals; /* XXX we must split the state between tc/xdp */ #endif struct calico_nat_dest *nat_dest; - struct fwd fwd; void *counters; struct pkt_scratch *scratch; }; diff --git a/felix/bpf-gpl/ut/nat_encap_test.c b/felix/bpf-gpl/ut/nat_encap_test.c index 9789fe75864..d80789934a2 100644 --- a/felix/bpf-gpl/ut/nat_encap_test.c +++ b/felix/bpf-gpl/ut/nat_encap_test.c @@ -20,10 +20,6 @@ static CALI_BPF_INLINE int calico_unittest_entry (struct __sk_buff *skb) globals->data = __globals.v4; DECLARE_TC_CTX(_ctx, .skb = skb, - .fwd = { - .res = TC_ACT_UNSPEC, - .reason = CALI_REASON_UNKNOWN, - }, .ipheader_len = IP_SIZE, ); struct cali_tc_ctx *ctx = &_ctx; diff --git a/felix/bpf-gpl/ut/perf_events.c b/felix/bpf-gpl/ut/perf_events.c index 4dc16e57346..41d8c5e428d 100644 --- a/felix/bpf-gpl/ut/perf_events.c +++ b/felix/bpf-gpl/ut/perf_events.c @@ -38,7 +38,6 @@ static CALI_BPF_INLINE int calico_unittest_entry (struct __sk_buff *skb) struct cali_tc_ctx *ctx = &_ctx; if (skb_refresh_validate_ptrs(ctx, UDP_SIZE)) { - ctx->fwd.reason = CALI_REASON_SHORT; CALI_DEBUG("Too short\n"); return -1; } diff --git a/felix/bpf-gpl/ut/tcp_rst.c b/felix/bpf-gpl/ut/tcp_rst.c new file mode 100644 index 00000000000..b4ef08c1496 --- /dev/null +++ b/felix/bpf-gpl/ut/tcp_rst.c @@ -0,0 +1,69 @@ +// Project Calico BPF dataplane programs. +// Copyright (c) 2020-2026 Tigera, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +#include "ut.h" +#include "bpf.h" +#include "nat.h" +#ifndef IPVER6 +#include "tcp4.h" +#else +#include "tcp6.h" +#endif +#include "parsing.h" +#include "jump.h" + +const volatile struct cali_tc_preamble_globals __globals; + +static CALI_BPF_INLINE int calico_unittest_entry (struct __sk_buff *skb) +{ + volatile struct cali_tc_globals *globals = state_get_globals_tc(); + + if (!globals) { + return TC_ACT_SHOT; + } + + /* Set the globals for the rest of the prog chain. */ +#ifndef IPVER6 + globals->data = __globals.v4; +#else + globals->data = __globals.v6; +#endif + DECLARE_TC_CTX(_ctx, + .skb = skb, + .ipheader_len = IP_SIZE, + ); + struct cali_tc_ctx *ctx = &_ctx; + if (!ctx->counters) { + CALI_DEBUG("Counters map lookup failed: DROP\n"); + return TC_ACT_SHOT; + } + int ret = PARSING_OK; +#ifdef IPVER6 + ret = PARSING_OK_V6; +#endif + if (parse_packet_ip(ctx) != ret) { + return TC_ACT_UNSPEC; + } + + tc_state_fill_from_iphdr(ctx); + + switch (tc_state_fill_from_nexthdr(ctx, true)) { + case PARSING_ERROR: + goto deny; + case PARSING_ALLOW_WITHOUT_ENFORCING_POLICY: + goto allow; + } +#ifndef IPVER6 + return tcp_v4_rst(ctx); +#else + return tcp_v6_rst(ctx); +#endif + +allow: + return TC_ACT_UNSPEC; + +deny: + return TC_ACT_SHOT; +} + diff --git a/felix/bpf-gpl/xdp.c b/felix/bpf-gpl/xdp.c index fe7b9c90b17..4fd4b009d74 100644 --- a/felix/bpf-gpl/xdp.c +++ b/felix/bpf-gpl/xdp.c @@ -42,10 +42,6 @@ int calico_xdp_main(struct xdp_md *xdp) .counters = counters_get(xdp->ingress_ifindex), .xdp_globals = state_get_globals_xdp(), .xdp = xdp, - .fwd = { - .res = XDP_PASS, // TODO: Adjust based on the design - .reason = CALI_REASON_UNKNOWN, - }, .ipheader_len = IP_SIZE, }; struct cali_tc_ctx *ctx = &_ctx; @@ -64,6 +60,8 @@ int calico_xdp_main(struct xdp_md *xdp) return XDP_DROP; } __builtin_memset(ctx->state, 0, sizeof(*ctx->state)); + _ctx.state->fwd.res = TC_ACT_UNSPEC; + _ctx.state->fwd.reason = CALI_REASON_UNKNOWN; ctx->scratch = (void *)(ctx->xdp_globals + 1); /* needs to be set to something, not used, there is space */ ctx->nh = &ctx->scratch->l4; @@ -143,10 +141,6 @@ int calico_xdp_accepted_entrypoint(struct xdp_md *xdp) .xdp = xdp, .xdp_globals = state_get_globals_xdp(), .counters = counters_get(xdp->ingress_ifindex), - .fwd = { - .res = XDP_PASS, - .reason = CALI_REASON_UNKNOWN, - }, .ipheader_len = IP_SIZE, }; struct cali_tc_ctx *ctx = &_ctx; diff --git a/felix/bpf/asm/asm.go b/felix/bpf/asm/asm.go index cc8e761c896..6ec8a7cddbc 100644 --- a/felix/bpf/asm/asm.go +++ b/felix/bpf/asm/asm.go @@ -20,6 +20,7 @@ import ( "encoding/binary" "fmt" "math" + "slices" "sort" "strings" @@ -888,12 +889,7 @@ func (b *Block) nextInsnReachable() bool { if lastOpCode == JumpA /*Unconditional jump*/ || lastOpCode == Exit { // Previous instruction doesn't fall through to this one, need // to check if something else jumps here... - for _, l := range b.insnIdxToLabels[len(b.insns)] { - if b.inUseJumpTargets.Contains(l) { - return true // Previous instruction jumps to this one, we're reachable. - } - } - return false + return slices.ContainsFunc(b.insnIdxToLabels[len(b.insns)], b.inUseJumpTargets.Contains) } return true } diff --git a/felix/bpf/asm/asm_test.go b/felix/bpf/asm/asm_test.go index fa45ad661a1..2a0751e9dfa 100644 --- a/felix/bpf/asm/asm_test.go +++ b/felix/bpf/asm/asm_test.go @@ -165,7 +165,7 @@ func TestJumpBackwardsTooFar(t *testing.T) { // Subtract one because the jump is relative to the instruction // after the jump itself. safeNumInsns := -math.MinInt16 - 1 - for i := 0; i < safeNumInsns; i++ { + for range safeNumInsns { b.NoOp() } b.JumpEq32(R0, R1, "label") @@ -196,7 +196,7 @@ func TestSingleTrampoline(t *testing.T) { // a trampoline. b.JumpEq64(R1, R2, "longB") b.JumpEq64(R1, R2, "longA") - for i := 0; i < TrampolineStrideDefault; i++ { + for range TrampolineStrideDefault { b.NoOp() } @@ -259,7 +259,7 @@ func TestTrampolineLoadImm64(t *testing.T) { // a trampoline. b.JumpEq64(R1, R2, "longB") b.JumpEq64(R1, R2, "longA") - for i := 0; i < TrampolineStrideDefault-3; i++ { + for range TrampolineStrideDefault - 3 { b.NoOp() } b.LoadImm64(R3, 1234) @@ -319,7 +319,7 @@ func TestShortJumpAndTrampoline(t *testing.T) { b.JumpEq64(R1, R2, "shortA") b.JumpEq64(R1, R2, "longA") b.LabelNextInsn("shortA") - for i := 0; i < TrampolineStrideDefault; i++ { + for range TrampolineStrideDefault { b.NoOp() } @@ -376,7 +376,7 @@ func TestDoubleTrampoline(t *testing.T) { // a trampoline. b.JumpEq64(R1, R2, "longB") b.JumpEq64(R1, R2, "longA") - for i := 0; i < TrampolineStrideDefault; i++ { + for range TrampolineStrideDefault { b.NoOp() } @@ -385,7 +385,7 @@ func TestDoubleTrampoline(t *testing.T) { b.JumpEq64(R1, R2, "longA") b.JumpEq64(R1, R3, "longC") - for i := 0; i < TrampolineStrideDefault; i++ { + for range TrampolineStrideDefault { b.NoOp() } diff --git a/felix/bpf/asm/opcode_string.go b/felix/bpf/asm/opcode_string.go index 134d934166d..712c4c7cb0c 100644 --- a/felix/bpf/asm/opcode_string.go +++ b/felix/bpf/asm/opcode_string.go @@ -273,8 +273,9 @@ const _Reg_name = "R0R1R2R3R4R5R6R7R8R9R10" var _Reg_index = [...]uint8{0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 23} func (i Reg) String() string { - if i < 0 || i >= Reg(len(_Reg_index)-1) { + idx := int(i) - 0 + if i < 0 || idx >= len(_Reg_index)-1 { return "Reg(" + strconv.FormatInt(int64(i), 10) + ")" } - return _Reg_name[_Reg_index[i]:_Reg_index[i+1]] + return _Reg_name[_Reg_index[idx]:_Reg_index[idx+1]] } diff --git a/felix/bpf/binary.go b/felix/bpf/binary.go deleted file mode 100644 index 9688b8ac3bd..00000000000 --- a/felix/bpf/binary.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) 2020-2021 Tigera, Inc. All rights reserved. -// -// 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. - -// Copyright (c) 2020 All rights reserved. - -package bpf - -import ( - "bytes" - "crypto/rand" - "encoding/binary" - "os" - - "github.com/sirupsen/logrus" -) - -// Binary is an in memory representation of a BPF binary -type Binary struct { - raw []byte -} - -// BinaryFromFile reads a binary from a file -func BinaryFromFile(ifile string) (*Binary, error) { - raw, err := os.ReadFile(ifile) - if err != nil { - return nil, err - } - - return &Binary{ - raw: raw, - }, nil -} - -// WriteToFile writes the binary to a file -func (b *Binary) WriteToFile(ofile string) error { - err := os.WriteFile(ofile, b.raw, 0600) - if err != nil { - return err - } - - // Append a UUID to the file. We want each attachment point to get its own jump map - // but tc names jump maps by hash of the binary, which means they can clash if we load - // the same binary onto multiple interfaces. - f, err := os.OpenFile(ofile, os.O_APPEND|os.O_WRONLY, 0600) - if err != nil { - return err - } - defer func() { - err := f.Close() - if err != nil { - panic(err) - } - }() - uuid := make([]byte, 16) - _, err = rand.Read(uuid) - if err != nil { - return err - } - _, err = f.Write(uuid) - if err != nil { - return err - } - return nil -} - -// ReplaceAll replaces all non-overlapping instance of orig with replacements. -func (b *Binary) ReplaceAll(orig, replacement []byte) { - b.raw = bytes.ReplaceAll(b.raw, orig, replacement) -} - -func (b *Binary) replaceAllLoadImm32(orig, replacement []byte) { - // immediate load has 2 byte 00 op code as a prefix - ldimm := make([]byte, 6) - copy(ldimm[2:], orig[:4]) - rep := make([]byte, 6) - copy(rep[2:], replacement[:4]) - - b.ReplaceAll(ldimm[:], rep[:]) -} - -// PatchSkbMark replaces SKBM with the expected mark - for tests. -func (b *Binary) PatchSkbMark(mark uint32) { - logrus.WithField("mark", mark).Debug("Patching skb mark") - b.patchU32Placeholder("SKBM", uint32(mark)) -} - -// patchU32Placeholder replaces a placeholder with the given value. -func (b *Binary) patchU32Placeholder(from string, to uint32) { - toBytes := make([]byte, 4) - binary.LittleEndian.PutUint32(toBytes, to) - b.replaceAllLoadImm32([]byte(from), toBytes) -} diff --git a/felix/bpf/bpf.go b/felix/bpf/bpf.go index 8e48fc75856..74d7b6e7328 100644 --- a/felix/bpf/bpf.go +++ b/felix/bpf/bpf.go @@ -31,6 +31,7 @@ import ( "path" "path/filepath" "regexp" + "slices" "strconv" "strings" @@ -345,8 +346,8 @@ func (b *BPFLib) ListCIDRMaps(family IPFamily) ([]string, error) { suffix := fmt.Sprintf("_%s_%s_blacklist", family, cidrMapVersion) for _, m := range maps { name := m.Name() - if strings.HasSuffix(name, suffix) { - ifName := strings.TrimSuffix(name, suffix) + if before, ok := strings.CutSuffix(name, suffix); ok { + ifName := before ifNames = append(ifNames, ifName) } } @@ -1205,10 +1206,8 @@ func (b *BPFLib) GetXDPMode(ifName string) (XDPMode, error) { {"xdpoffload", XDPOffload}, {"xdp", XDPDriver}, // We write "xdpdrv" but read back "xdp" } { - for _, f := range s { - if f == modeMapping.String { - return modeMapping.Mode, nil - } + if slices.Contains(s, modeMapping.String) { + return modeMapping.Mode, nil } } @@ -2398,13 +2397,13 @@ func loadObject(obj *libbpf.Obj, data libbpf.GlobalData, mapsToBePinned ...strin // userspace before the program is loaded. mapName := m.Name() if m.IsMapInternal() { - if strings.HasPrefix(mapName, ".rodata") { + if !strings.HasSuffix(mapName, ".rodata") { continue } if data != nil { if err := data.Set(m); err != nil { - return nil, fmt.Errorf("failed to configure %s: %w", obj.Filename(), err) + return nil, fmt.Errorf("failed to configure %s map %s: %w", obj.Filename(), mapName, err) } } continue diff --git a/felix/bpf/bpf_syscall.go b/felix/bpf/bpf_syscall.go index a8a7d6d1461..9800ebdf483 100644 --- a/felix/bpf/bpf_syscall.go +++ b/felix/bpf/bpf_syscall.go @@ -28,6 +28,7 @@ import ( "github.com/projectcalico/calico/felix/bpf/utils" ) +// #cgo CFLAGS: -I${SRCDIR}/../bpf-gpl/libbpf/src -I${SRCDIR}/../bpf-gpl/libbpf/include/uapi -I${SRCDIR}/../bpf-gpl -Werror // #include "bpf_syscall.h" import "C" @@ -38,8 +39,16 @@ func SyscallSupport() bool { const defaultLogSize = 1024 * 1024 const maxLogSize = 128 * 1024 * 1024 +func LoadBPFProgramFromInsnsWithAttachType(insns asm.Insns, name, license string, progType, attachType uint32) (fd ProgFD, err error) { + return loadBPFProgramFromInsns(insns, name, license, progType, attachType) +} + func LoadBPFProgramFromInsns(insns asm.Insns, name, license string, progType uint32) (fd ProgFD, err error) { - log.Debugf("LoadBPFProgramFromInsns(%v, %q, %v, %v)", insns, name, license, progType) + return loadBPFProgramFromInsns(insns, name, license, progType, 0) +} + +func loadBPFProgramFromInsns(insns asm.Insns, name, license string, progType, attachType uint32) (fd ProgFD, err error) { + log.Debugf("loadBPFProgramFromInsns(%v, %q, %v, %v, %v)", insns, name, license, progType, attachType) utils.IncreaseLockedMemoryQuota() // Occasionally see retryable errors here, retry silently a few times before going into log-collection mode. @@ -47,7 +56,7 @@ func LoadBPFProgramFromInsns(insns asm.Insns, name, license string, progType uin for retries := 10; retries > 0; retries-- { // By default, try to load the program with logging disabled. This has two advantages: better performance // and the fact that the log cannot overflow. - fd, err = tryLoadBPFProgramFromInsns(insns, name, license, 0, progType) + fd, err = tryLoadBPFProgramFromInsns(insns, name, license, 0, progType, attachType) if err == nil { log.WithField("fd", fd).Debug("Loaded program successfully") return fd, nil @@ -61,7 +70,7 @@ func LoadBPFProgramFromInsns(insns asm.Insns, name, license string, progType uin log.WithError(err).Warn("Failed to load BPF program; collecting diagnostics...") var logSize uint = defaultLogSize for { - fd, err2 := tryLoadBPFProgramFromInsns(insns, name, license, logSize, progType) + fd, err2 := tryLoadBPFProgramFromInsns(insns, name, license, logSize, progType, attachType) if err2 == nil { // Unexpected but we'll take it. log.Warn("Retry succeeded.") @@ -81,7 +90,7 @@ func LoadBPFProgramFromInsns(insns asm.Insns, name, license string, progType uin } } -func tryLoadBPFProgramFromInsns(insns asm.Insns, name, license string, logSize uint, progType uint32) (ProgFD, error) { +func tryLoadBPFProgramFromInsns(insns asm.Insns, name, license string, logSize uint, progType, attachType uint32) (ProgFD, error) { log.Debugf("tryLoadBPFProgramFromInsns(..., %s, %v, %v, %v)", name, license, logSize, progType) bpfAttr := C.bpf_attr_alloc() defer C.free(unsafe.Pointer(bpfAttr)) @@ -101,32 +110,33 @@ func tryLoadBPFProgramFromInsns(insns asm.Insns, name, license string, logSize u defer C.free(logBuf) } - C.bpf_attr_setup_load_prog(bpfAttr, cName, - (C.uint)(progType), C.uint(len(insns)), cInsnBytes, cLicense, + fd, err := C.bpf_load_prog(cName, (C.uint)(progType), (C.uint)(attachType), cInsnBytes, C.uint(len(insns)), cLicense, (C.uint)(logLevel), (C.uint)(logSize), logBuf) - fd, _, errno := unix.Syscall(unix.SYS_BPF, unix.BPF_PROG_LOAD, uintptr(unsafe.Pointer(bpfAttr)), C.sizeof_union_bpf_attr) - - if errno != 0 && errno != unix.ENOSPC /* log buffer too small */ { - goLog := strings.TrimSpace(C.GoString((*C.char)(logBuf))) - log.WithError(errno).Debug("BPF_PROG_LOAD failed") - if len(goLog) > 0 { - lines := strings.Split(goLog, "\n") - for _, l := range lines { - log.Error("BPF_PROG_LOAD failed, BPF Verifier output: ", l) + if err != nil { + errno, _ := err.(syscall.Errno) + + if errno != 0 && errno != unix.ENOSPC /* log buffer too small */ { + goLog := strings.TrimSpace(C.GoString((*C.char)(logBuf))) + log.WithError(errno).Debug("BPF_PROG_LOAD failed") + if len(goLog) > 0 { + lines := strings.Split(goLog, "\n") + for _, l := range lines { + log.Error("BPF_PROG_LOAD failed, BPF Verifier output: ", l) + } + if errno == 524 /* Linux ENOTSUPP */ && len(lines) == 1 { + // likely a JIT error, verifier passed + // XXX we could test if it says Processed x instructions, but + // the message may change + return 0, fmt.Errorf("likely a JIT error, bpf_harden may be set: %w", unix.ERANGE) + } + } else if logSize > 0 { + log.Error("BPF_PROG_LOAD failed, verifier log was empty.") } - if errno == 524 /* Linux ENOTSUPP */ && len(lines) == 1 { - // likely a JIT error, verifier passed - // XXX we could test if it says Processed x instructions, but - // the message may change - return 0, fmt.Errorf("likely a JIT error, bpf_harden may be set: %w", unix.ERANGE) - } - } else if logSize > 0 { - log.Error("BPF_PROG_LOAD failed, verifier log was empty.") } - } - if errno != 0 { - return 0, errno + if errno != 0 { + return 0, errno + } } return ProgFD(fd), nil } diff --git a/felix/bpf/bpf_syscall.h b/felix/bpf/bpf_syscall.h index 81dde740461..197f508a7c5 100644 --- a/felix/bpf/bpf_syscall.h +++ b/felix/bpf/bpf_syscall.h @@ -20,6 +20,9 @@ #include #include +#include "bpf.h" +#include "libbpf.h" + union bpf_attr *bpf_attr_alloc() { union bpf_attr *attr = malloc(sizeof(union bpf_attr)); memset(attr, 0, sizeof(union bpf_attr)); @@ -48,55 +51,33 @@ void bpf_attr_setup_obj_pin(union bpf_attr *attr, char *path, __u32 fd, __u32 fl attr->file_flags = flags; } -// bpf_attr_setup_load_prog sets up the bpf_attr union for use with BPF_PROG_LOAD. -// A C function makes this easier because unions aren't easy to access from Go. -void bpf_attr_setup_load_prog(union bpf_attr *attr, - const char *name, - __u32 prog_type, - __u32 insn_count, - void *insns, - char *license, - __u32 log_level, - __u32 log_size, - void *log_buf) +int bpf_load_prog(char *name, __u32 prog_type, __u32 attach_type, + void *insns, __u32 insn_count, + char *license, __u32 log_level, + __u32 log_size, void *log_buf) { - attr->prog_type = prog_type; - attr->insn_cnt = insn_count; - attr->insns = (__u64)(unsigned long)insns; - attr->license = (__u64)(unsigned long)license; - attr->log_level = log_level; - attr->log_size = log_size; - attr->log_buf = (__u64)(unsigned long)log_buf; - attr->kern_version = 0; - - if (log_size > 0) { - ((char *)log_buf)[0] = 0; - } - - if (name) { - int sz = sizeof(attr->prog_name); - - int i; - for (i = 0; i < sz; i++) { - if (name[i] == '\0') { - attr->prog_name[i] = '\0'; - break; - } - /* Kernel only accepts alphanum chars and '_' and '.'. In bpf program - * names. So we sanitize any other characters like the '-' in - * wg-v6.cali. - */ - if (isalnum(name[i]) || name[i] == '_' || name[i] == '.') - attr->prog_name[i] = name[i]; - else { - attr->prog_name[i] = '_'; - } - } + DECLARE_LIBBPF_OPTS(bpf_prog_load_opts, opts, + .log_level = log_level, + .log_size = log_size, + .log_buf = log_buf, + .kern_version = 0, + .expected_attach_type = attach_type, + ); + if (name) { + for (int i = 0; i < BPF_OBJ_NAME_LEN; i++) { + if (name[i] == '\0') { + break; + } + if (isalnum(name[i]) || name[i] == '_' || name[i] == '.') + continue; + name[i] = '_'; + } + } - if (i == sz) { - attr->prog_name[sz - 1] = '\0'; - } - } + int fd = bpf_prog_load(prog_type, name, license, insns, insn_count, &opts); + if (fd < 0) + errno = -fd; + return fd; } // bpf_attr_setup_prog_run sets up the bpf_attr union for use with BPF_PROG_TEST_RUN. diff --git a/felix/bpf/bpf_syscall_stub.go b/felix/bpf/bpf_syscall_stub.go index 1a06e002599..00f9078d646 100644 --- a/felix/bpf/bpf_syscall_stub.go +++ b/felix/bpf/bpf_syscall_stub.go @@ -30,6 +30,10 @@ func LoadBPFProgramFromInsns(insns asm.Insns, name, license string, progType uin panic("BPF syscall stub") } +func LoadBPFProgramFromInsnsWithAttachType(insns asm.Insns, name, license string, progType, attachType uint32) (fd ProgFD, err error) { + panic("BPF syscall stub") +} + func RunBPFProgram(fd ProgFD, dataIn []byte, repeat int) (pr ProgResult, err error) { panic("BPF syscall stub") } diff --git a/felix/bpf/bpf_test.go b/felix/bpf/bpf_test.go index c0b365a50a4..ffacae769fb 100644 --- a/felix/bpf/bpf_test.go +++ b/felix/bpf/bpf_test.go @@ -23,6 +23,7 @@ import ( "net" "os" "os/exec" + "slices" "testing" . "github.com/onsi/gomega" @@ -155,12 +156,7 @@ func TestRemoveCIDRMap(t *testing.T) { } func strSliceContains(s []string, e string) bool { - for _, a := range s { - if a == e { - return true - } - } - return false + return slices.Contains(s, e) } func TestListCIDRMap(t *testing.T) { diff --git a/felix/bpf/bpfmap/bpf_maps.go b/felix/bpf/bpfmap/bpf_maps.go index a23b02c29e2..4e0fd53ff1d 100644 --- a/felix/bpf/bpfmap/bpf_maps.go +++ b/felix/bpf/bpfmap/bpf_maps.go @@ -26,6 +26,7 @@ import ( "github.com/projectcalico/calico/felix/bpf/failsafes" "github.com/projectcalico/calico/felix/bpf/hook" "github.com/projectcalico/calico/felix/bpf/ifstate" + "github.com/projectcalico/calico/felix/bpf/ipfrags" "github.com/projectcalico/calico/felix/bpf/ipsets" "github.com/projectcalico/calico/felix/bpf/jump" "github.com/projectcalico/calico/felix/bpf/maps" @@ -48,20 +49,23 @@ type IPMaps struct { SrMsgMap maps.Map CtNatsMap maps.Map CtCleanupMap maps.Map + MaglevMap maps.Map + IPFragMap maps.Map + IPFragFwdMap maps.Map } type CommonMaps struct { - StateMap maps.Map - IfStateMap maps.Map - RuleCountersMap maps.Map - CountersMap maps.Map - ProgramsMap maps.Map - JumpMap maps.MapWithDeleteIfExists - XDPProgramsMap maps.Map - XDPJumpMap maps.MapWithDeleteIfExists - ProfilingMap maps.Map - CTLBProgramsMap maps.Map - QoSMap maps.MapWithUpdateWithFlags + StateMap maps.Map + IfStateMap maps.Map + RuleCountersMap maps.Map + CountersMap maps.Map + ProgramsMaps []maps.Map + JumpMaps []maps.MapWithDeleteIfExists + XDPProgramsMap maps.Map + XDPJumpMap maps.MapWithDeleteIfExists + ProfilingMap maps.Map + CTLBProgramsMaps []maps.Map + QoSMap maps.MapWithUpdateWithFlags } type Maps struct { @@ -84,19 +88,23 @@ func (m *Maps) Destroy() { } func getCommonMaps() *CommonMaps { - return &CommonMaps{ - StateMap: state.Map(), - IfStateMap: ifstate.Map(), - RuleCountersMap: counters.PolicyMap(), - CountersMap: counters.Map(), - ProgramsMap: hook.NewProgramsMap(), - JumpMap: jump.Map().(maps.MapWithDeleteIfExists), - XDPProgramsMap: hook.NewXDPProgramsMap(), - XDPJumpMap: jump.XDPMap().(maps.MapWithDeleteIfExists), - ProfilingMap: profiling.Map(), - CTLBProgramsMap: nat.ProgramsMap(), - QoSMap: qos.Map().(maps.MapWithUpdateWithFlags), + commonMaps := &CommonMaps{ + StateMap: state.Map(), + IfStateMap: ifstate.Map(), + RuleCountersMap: counters.PolicyMap(), + CountersMap: counters.Map(), + ProgramsMaps: hook.NewProgramsMaps(), + XDPProgramsMap: hook.NewXDPProgramsMap(), + XDPJumpMap: jump.XDPMap().(maps.MapWithDeleteIfExists), + ProfilingMap: profiling.Map(), + CTLBProgramsMaps: nat.ProgramsMaps(), + QoSMap: qos.Map().(maps.MapWithUpdateWithFlags), } + jumpMaps := jump.Maps() + for _, jm := range jumpMaps { + commonMaps.JumpMaps = append(commonMaps.JumpMaps, jm.(maps.MapWithDeleteIfExists)) + } + return commonMaps } func getIPMaps(ipFamily int) *IPMaps { @@ -104,6 +112,9 @@ func getIPMaps(ipFamily int) *IPMaps { if ipFamily == 4 { return v4() } + if v6 == nil { + return nil + } return v6() } @@ -111,6 +122,9 @@ func getIPMaps(ipFamily int) *IPMaps { if ipFamily == 4 { return v4() } + if v6 == nil { + return nil + } return v6() } @@ -126,6 +140,9 @@ func getIPMaps(ipFamily int) *IPMaps { CtCleanupMap: getmapWithExistsCheck(conntrack.CleanupMap, conntrack.CleanupMapV6), SrMsgMap: getmap(nat.SendRecvMsgMap, nat.SendRecvMsgMapV6), CtNatsMap: getmap(nat.AllNATsMsgMap, nat.AllNATsMsgMapV6), + MaglevMap: getmapWithExistsCheck(nat.MaglevMap, nat.MaglevMapV6), + IPFragMap: getmap(ipfrags.Map, nil), + IPFragFwdMap: getmap(ipfrags.FwdMap, nil), } } @@ -140,9 +157,12 @@ func CreateBPFMaps(ipV6Enabled bool) (*Maps, error) { mps := ret.slice() for i, bpfMap := range mps { + if bpfMap == nil { + continue + } err := bpfMap.EnsureExists() if err != nil { - for j := 0; j < i; j++ { + for j := range i { m := mps[j] os.Remove(m.(pinnedMap).Path()) m.(pinnedMap).Close() @@ -168,19 +188,22 @@ func (m *Maps) slice() []maps.Map { } func (c *CommonMaps) slice() []maps.Map { - return []maps.Map{ + mapslice := []maps.Map{ c.StateMap, c.IfStateMap, c.RuleCountersMap, c.CountersMap, - c.ProgramsMap, - c.JumpMap, c.XDPProgramsMap, c.XDPJumpMap, c.ProfilingMap, - c.CTLBProgramsMap, c.QoSMap, } + mapslice = append(mapslice, c.ProgramsMaps...) + mapslice = append(mapslice, c.CTLBProgramsMaps...) + for _, m := range c.JumpMaps { + mapslice = append(mapslice, m) + } + return mapslice } func (i *IPMaps) slice() []maps.Map { @@ -196,6 +219,9 @@ func (i *IPMaps) slice() []maps.Map { i.CtCleanupMap, i.SrMsgMap, i.CtNatsMap, + i.MaglevMap, + i.IPFragMap, + i.IPFragFwdMap, } } diff --git a/felix/bpf/conntrack/cleanup.go b/felix/bpf/conntrack/cleanup.go index 4e27189d607..2bab5934793 100644 --- a/felix/bpf/conntrack/cleanup.go +++ b/felix/bpf/conntrack/cleanup.go @@ -16,6 +16,7 @@ package conntrack import ( "net" + "sync" "time" "github.com/prometheus/client_golang/prometheus" @@ -23,9 +24,10 @@ import ( "golang.org/x/sys/unix" "github.com/projectcalico/calico/felix/bpf/conntrack/timeouts" - v3 "github.com/projectcalico/calico/felix/bpf/conntrack/v3" + v4 "github.com/projectcalico/calico/felix/bpf/conntrack/v4" "github.com/projectcalico/calico/felix/bpf/maps" "github.com/projectcalico/calico/felix/timeshim" + "github.com/projectcalico/calico/libcalico-go/lib/set" ) var ( @@ -54,6 +56,77 @@ func init() { prometheus.MustRegister(conntrackCounterStaleNAT) } +type WorkloadRemoveScannerTCP struct { + mutex sync.Mutex + ips set.Set[string] + removedIPs set.Set[string] + ipCh chan string + done chan struct{} +} + +func NewWorkloadRemoveScannerTCP(ipCh chan string) *WorkloadRemoveScannerTCP { + wrs := &WorkloadRemoveScannerTCP{ + ips: set.New[string](), + removedIPs: set.New[string](), + ipCh: ipCh, + done: make(chan struct{}), + } + go wrs.run() + return wrs +} + +func (w *WorkloadRemoveScannerTCP) run() { + defer close(w.done) + for { + ip, ok := <-w.ipCh + if !ok { + return + } + w.mutex.Lock() + w.ips.Add(ip) + log.Debugf("WorkloadRemoveScanner added %s to IPs to check", ip) + w.mutex.Unlock() + } +} + +func (w *WorkloadRemoveScannerTCP) IterationStart() { + w.mutex.Lock() + defer w.mutex.Unlock() + w.removedIPs = w.ips.Copy() + w.ips = set.New[string]() +} + +// IterationEnd satisfies EntryScannerSynced +func (w *WorkloadRemoveScannerTCP) IterationEnd() { + w.mutex.Lock() + w.removedIPs = set.New[string]() + w.mutex.Unlock() +} + +func (w *WorkloadRemoveScannerTCP) Stop() { + close(w.ipCh) // This breaks the 'for range' loop in run() + <-w.done // Wait for the goroutine to actually finish +} + +func (w *WorkloadRemoveScannerTCP) Check(ctKey KeyInterface, ctVal ValueInterface, get EntryGet) (ScanVerdict, int64) { + srcIP := ctKey.AddrA().String() + dstIP := ctKey.AddrB().String() + if ctKey.Proto() == ProtoTCP && + (w.removedIPs.Contains(srcIP) || w.removedIPs.Contains(dstIP)) { + log.WithField("key", ctKey).Debug("Marking conntrack entry for sending RST due to workload removal") + // We found a conntrack entry that has the workload IP as source or destination. + // Mark it for RST sending. + return ScanVerdictSendRST, ctVal.LastSeen() + } + return ScanVerdictOK, ctVal.LastSeen() +} + +func (w *WorkloadRemoveScannerTCP) NumIPsPending() int { + w.mutex.Lock() + defer w.mutex.Unlock() + return w.ips.Len() +} + type LivenessScanner struct { timeouts timeouts.Timeouts dsr bool @@ -215,17 +288,21 @@ func NewStaleNATScanner(frontendHasBackend NATChecker) *StaleNATScanner { func (sns *StaleNATScanner) Check(k KeyInterface, v ValueInterface, get EntryGet) (ScanVerdict, int64) { debug := log.GetLevel() >= log.DebugLevel + lastSeen := v.LastSeen() + if k.Proto() == ProtoTCP { + // we do not handle TCP for the below reasons. + // sns.natChecker.ConntrackFrontendHasBackend returns false if the service gets deleted + // or there are no backends. For TCP, we can still let the connection flow even if the service + // gets deleted as long as the backend is alive. When the backend is removed, We add a flag to the + // conntrack entry to send a RST, so the connection will be closed properly. + return ScanVerdictOK, lastSeen + } + again: - lastSeen := v.LastSeen() switch v.Type() { case TypeNormal: proto := k.Proto() - if proto != ProtoUDP { - // skip non-NAT entry - break - } - // Check if we have an entry to a service IP:port without it being // NATed. Remove such entry as it was created when the service wasn't // programmed yet and there was a NAT miss. @@ -238,7 +315,7 @@ again: port uint16 ) - if v.Flags()&v3.FlagSrcDstBA != 0 { + if v.Flags()&v4.FlagSrcDstBA != 0 { ip = k.AddrA() port = k.PortA() } else { @@ -248,7 +325,7 @@ again: if sns.natChecker.ConntrackDestIsService(ip, port, proto) { log.WithField("key", k).Debugf("TypeNormal to UDP service IP is stale") - return ScanVerdictDelete, lastSeen + return ScanVerdictDeleteImmediate, lastSeen } case TypeNATReverse: diff --git a/felix/bpf/conntrack/conntrack_suite_test.go b/felix/bpf/conntrack/conntrack_suite_test.go index 3b7aaaf24d2..f04defed43a 100644 --- a/felix/bpf/conntrack/conntrack_suite_test.go +++ b/felix/bpf/conntrack/conntrack_suite_test.go @@ -17,9 +17,8 @@ package conntrack_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestBPFConntrack(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/bpf_conntrack_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "BPF Conntrack Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/felix_bpf_conntrack_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/bpf/conntrack", suiteConfig, reporterConfig) } diff --git a/felix/bpf/conntrack/conntrack_test.go b/felix/bpf/conntrack/conntrack_test.go index 7874764556a..90aae9ea0ef 100644 --- a/felix/bpf/conntrack/conntrack_test.go +++ b/felix/bpf/conntrack/conntrack_test.go @@ -19,8 +19,7 @@ import ( "net" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "golang.org/x/sys/unix" @@ -29,6 +28,7 @@ import ( "github.com/projectcalico/calico/felix/bpf/conntrack/timeouts" v2 "github.com/projectcalico/calico/felix/bpf/conntrack/v2" v3 "github.com/projectcalico/calico/felix/bpf/conntrack/v3" + v4 "github.com/projectcalico/calico/felix/bpf/conntrack/v4" "github.com/projectcalico/calico/felix/bpf/maps" "github.com/projectcalico/calico/felix/bpf/mock" "github.com/projectcalico/calico/felix/timeshim/mocktime" @@ -78,7 +78,7 @@ var _ = Describe("BPF Conntrack LivenessCalculator", func() { Expect(deletedEntries).To(ConsistOf(tc.ExpectedDeletions), "Scan() did not delete the expected entries") }, - entries..., + entries, ) DescribeTable( @@ -94,10 +94,65 @@ var _ = Describe("BPF Conntrack LivenessCalculator", func() { Expect(ctMap.IsEmpty()).To(BeTrue(), "all entries should have been deleted, but map isn't empty") }, - entries..., + entries, ) }) +var _ = Describe("BPF workload remove conntrack scanner", func() { + var wrs *conntrack.WorkloadRemoveScannerTCP + var scanner *conntrack.Scanner + var ctMap, ctCleanupMap *mock.Map + var ipCh chan string + BeforeEach(func() { + ctMap = mock.NewMockMap(conntrack.MapParams) + ctCleanupMap = mock.NewMockMap(conntrack.MapParamsCleanup) + ipCh = make(chan string, 10) + wrs = conntrack.NewWorkloadRemoveScannerTCP(ipCh) + scanner = conntrack.NewScanner(ctMap, conntrack.KeyFromBytes, conntrack.ValueFromBytes, nil, "Disabled", + ctCleanupMap, 4, mock.NewMockBPFCleaner(ctMap, ctCleanupMap), wrs) + }) + It("should mark conntrack entries for removed workloads", func() { + // Insert an entry for a workload IP. + for i := 0; i < 15; i++ { + octetA := byte(1 + (i * 2)) + octetB := byte(2 + (i * 2)) + ipA := net.IPv4(10, 0, 0, octetA) + ipB := net.IPv4(10, 0, 0, octetB) + k := conntrack.NewKey(6, ipA, 1234, ipB, 80) + v := conntrack.NewValueNormal(mocktime.StartKTime, 0, conntrack.Leg{SynSeen: true, AckSeen: true}, conntrack.Leg{SynSeen: true, AckSeen: true}) + err := ctMap.Update(k.AsBytes(), v.AsBytes()) + Expect(err).NotTo(HaveOccurred()) + } + for i := 0; i < 6; i++ { + octetA := byte(1 + (i * 2)) + octetB := byte(14 + (i * 2)) + ipA := net.IPv4(10, 0, 0, octetA) + ipB := net.IPv4(10, 0, 0, octetB) + ipCh <- ipA.String() + ipCh <- ipB.String() + } + Eventually(func() int { return wrs.NumIPsPending() }, "2s", "50ms").Should(Equal(12)) + scanner.Scan() // No IPs removed yet, so no deletions. + for i := 0; i < 15; i++ { + octetA := byte(1 + (i * 2)) + octetB := byte(2 + (i * 2)) + ipA := net.IPv4(10, 0, 0, octetA) + ipB := net.IPv4(10, 0, 0, octetB) + k := conntrack.NewKey(6, ipA, 1234, ipB, 80) + val, err := ctMap.Get(k.AsBytes()) + v := conntrack.ValueFromBytes(val) + Expect(err).NotTo(HaveOccurred(), "expected entry for workload IP to still exist") + if i < 12 { + // These workload IPs were removed, so the entry should be marked for RST + Expect(v.Flags()&v4.FlagSendRST).To(Equal(v4.FlagSendRST), "expected entry for removed workload IP to be marked for RST") + } else { + // These workload IPs were not removed, so the entry should be unmodified. + Expect(v.Flags()&v4.FlagSendRST).To(Equal(uint32(0)), "expected entry for existing workload IP to be unmodified") + } + } + }) +}) + type dummyNATChecker struct { check func(fIP net.IP, fPort uint16, bIP net.IP, bPort uint16, proto uint8) bool } diff --git a/felix/bpf/conntrack/cttestdata/ct_data.go b/felix/bpf/conntrack/cttestdata/ct_data.go index 7ffa16a7582..8e3fb1766b1 100644 --- a/felix/bpf/conntrack/cttestdata/ct_data.go +++ b/felix/bpf/conntrack/cttestdata/ct_data.go @@ -19,7 +19,7 @@ import ( "time" "github.com/projectcalico/calico/felix/bpf/conntrack" - v3 "github.com/projectcalico/calico/felix/bpf/conntrack/v3" + v4 "github.com/projectcalico/calico/felix/bpf/conntrack/v4" "github.com/projectcalico/calico/felix/timeshim/mocktime" ) @@ -142,7 +142,7 @@ func init() { // Note: last seen time on the forward entry should be ignored in // favour of the last-seen time on the reverse entry. tcpFwdKey: conntrack.NewValueNATForward(Now-3*time.Hour, 0, tcpRevKey), - tcpRevKey: conntrack.NewValueNATReverse(Now-59*time.Minute, v3.FlagNATFwdDsr, + tcpRevKey: conntrack.NewValueNATReverse(Now-59*time.Minute, v4.FlagNATFwdDsr, conntrack.Leg{SynSeen: true, AckSeen: true}, conntrack.Leg{SynSeen: false, AckSeen: false}, nil, nil, 5555), }, @@ -154,7 +154,7 @@ func init() { // Note: last seen time on the forward entry should be ignored in // favour of the last-seen time on the reverse entry. tcpFwdKey: conntrack.NewValueNATForward(Now-3*time.Hour, 0, tcpRevKey), - tcpRevKey: conntrack.NewValueNATReverse(Now-2*time.Hour, v3.FlagNATFwdDsr, + tcpRevKey: conntrack.NewValueNATReverse(Now-2*time.Hour, v4.FlagNATFwdDsr, conntrack.Leg{SynSeen: true, AckSeen: true}, conntrack.Leg{SynSeen: false, AckSeen: false}, nil, nil, 5555), }, diff --git a/felix/bpf/conntrack/inforeader.go b/felix/bpf/conntrack/inforeader.go index 708e670e232..23557969240 100644 --- a/felix/bpf/conntrack/inforeader.go +++ b/felix/bpf/conntrack/inforeader.go @@ -21,7 +21,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/projectcalico/calico/felix/bpf/conntrack/timeouts" - v3 "github.com/projectcalico/calico/felix/bpf/conntrack/v3" + v4 "github.com/projectcalico/calico/felix/bpf/conntrack/v4" collector "github.com/projectcalico/calico/felix/collector/types" "github.com/projectcalico/calico/felix/collector/types/tuple" "github.com/projectcalico/calico/felix/timeshim" @@ -122,7 +122,7 @@ func (r *InfoReader) makeConntrackInfo(key KeyInterface, val ValueInterface, dna Bytes: int(data.B2A.Bytes), } - if val.Flags()&v3.FlagSrcDstBA != 0 { + if val.Flags()&v4.FlagSrcDstBA != 0 { ipSrc, ipDst = ipDst, ipSrc portSrc, portDst = portDst, portSrc coutersSrc, coutersDst = coutersDst, coutersSrc diff --git a/felix/bpf/conntrack/inforeader_test.go b/felix/bpf/conntrack/inforeader_test.go index 354221473f2..865036bff99 100644 --- a/felix/bpf/conntrack/inforeader_test.go +++ b/felix/bpf/conntrack/inforeader_test.go @@ -18,13 +18,12 @@ import ( "net" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/bpf/conntrack" "github.com/projectcalico/calico/felix/bpf/conntrack/timeouts" - v3 "github.com/projectcalico/calico/felix/bpf/conntrack/v3" + v4 "github.com/projectcalico/calico/felix/bpf/conntrack/v4" collector "github.com/projectcalico/calico/felix/collector/types" "github.com/projectcalico/calico/felix/collector/types/tuple" "github.com/projectcalico/calico/felix/timeshim/mocktime" @@ -77,7 +76,7 @@ var _ = Describe("BPF Conntrack InfoReader", func() { ), Entry("normal entry - no NAT - swapped legs", conntrack.NewKey(123, backendIP, backendPort, clientIP, clientPort), - conntrack.NewValueNormal(now, v3.FlagSrcDstBA, LegDstSrc, LegSrcDst), + conntrack.NewValueNormal(now, v4.FlagSrcDstBA, LegDstSrc, LegSrcDst), collector.ConntrackInfo{ Tuple: makeTuple(clientIP, backendIP, clientPort, backendPort, 123), }, @@ -92,7 +91,7 @@ var _ = Describe("BPF Conntrack InfoReader", func() { ), Entry("normal entry - no NAT - expired - swapped legs", conntrack.NewKey(123, backendIP, backendPort, clientIP, clientPort), - conntrack.NewValueNormal(now-time.Hour, v3.FlagSrcDstBA, LegDstSrc, LegSrcDst), + conntrack.NewValueNormal(now-time.Hour, v4.FlagSrcDstBA, LegDstSrc, LegSrcDst), collector.ConntrackInfo{ Expired: true, Tuple: makeTuple(clientIP, backendIP, clientPort, backendPort, 123), @@ -110,7 +109,7 @@ var _ = Describe("BPF Conntrack InfoReader", func() { ), Entry("reverse entry - NAT - swapped legs", conntrack.NewKey(123, backendIP, backendPort, clientIP, clientPort), - conntrack.NewValueNATReverse(now, v3.FlagSrcDstBA, LegDstSrc, LegSrcDst, net.IPv4(0, 0, 0, 0), svcIP, svcPort), + conntrack.NewValueNATReverse(now, v4.FlagSrcDstBA, LegDstSrc, LegSrcDst, net.IPv4(0, 0, 0, 0), svcIP, svcPort), collector.ConntrackInfo{ IsDNAT: true, Tuple: makeTuple(clientIP, backendIP, clientPort, backendPort, 123), @@ -129,7 +128,7 @@ var _ = Describe("BPF Conntrack InfoReader", func() { ), Entry("reverse entry - NAT - expired - swapped legs", conntrack.NewKey(123, backendIP, backendPort, clientIP, clientPort), - conntrack.NewValueNATReverse(now-time.Hour, v3.FlagSrcDstBA, LegDstSrc, LegSrcDst, net.IPv4(0, 0, 0, 0), svcIP, svcPort), + conntrack.NewValueNATReverse(now-time.Hour, v4.FlagSrcDstBA, LegDstSrc, LegSrcDst, net.IPv4(0, 0, 0, 0), svcIP, svcPort), collector.ConntrackInfo{ Expired: true, IsDNAT: true, @@ -137,6 +136,13 @@ var _ = Describe("BPF Conntrack InfoReader", func() { PreDNATTuple: makeTuple(clientIP, svcIP, clientPort, svcPort, 123), }, ), + Entry("32-bit flags RW", + conntrack.NewKey(123, clientIP, clientPort, backendIP, backendPort), + conntrack.NewValueNormal(now, 0x10000, LegSrcDst, LegDstSrc), + collector.ConntrackInfo{ + Tuple: makeTuple(clientIP, backendIP, clientPort, backendPort, 123), + }, + ), ) }) diff --git a/felix/bpf/conntrack/map.go b/felix/bpf/conntrack/map.go index af96925d690..327784a0878 100644 --- a/felix/bpf/conntrack/map.go +++ b/felix/bpf/conntrack/map.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows // Copyright (c) 2020-2025 Tigera, Inc. All rights reserved. // @@ -74,20 +73,20 @@ const ( ) // NewValueNormal creates a new Value of type TypeNormal based on the given parameters -func NewValueNormal(lastSeen time.Duration, flags uint16, legA, legB Leg) Value { +func NewValueNormal(lastSeen time.Duration, flags uint32, legA, legB Leg) Value { return curVer.NewValueNormal(lastSeen, flags, legA, legB) } // NewValueNATForward creates a new Value of type TypeNATForward for the given // arguments and the reverse key -func NewValueNATForward(lastSeen time.Duration, flags uint16, revKey Key) Value { +func NewValueNATForward(lastSeen time.Duration, flags uint32, revKey Key) Value { return curVer.NewValueNATForward(lastSeen, flags, revKey) } // NewValueNATReverse creates a new Value of type TypeNATReverse for the given // arguments and reverse parameters func NewValueNATReverse( - lastSeen time.Duration, flags uint16, legA, legB Leg, + lastSeen time.Duration, flags uint32, legA, legB Leg, tunnelIP, origIP net.IP, origPort uint16, ) Value { return curVer.NewValueNATReverse(lastSeen, flags, legA, legB, tunnelIP, origIP, origPort) @@ -95,27 +94,27 @@ func NewValueNATReverse( // NewValueNATReverseSNAT in addition to NewValueNATReverse sets the orig source IP func NewValueNATReverseSNAT( - lastSeen time.Duration, flags uint16, legA, legB Leg, + lastSeen time.Duration, flags uint32, legA, legB Leg, tunnelIP, origIP, origSrcIP net.IP, origPort uint16, ) Value { return curVer.NewValueNATReverseSNAT(lastSeen, flags, legA, legB, tunnelIP, origIP, origSrcIP, origPort) } // NewValueV6Normal creates a new ValueV6 of type TypeNormal based on the given parameters -func NewValueV6Normal(lastSeen time.Duration, flags uint16, legA, legB Leg) ValueV6 { +func NewValueV6Normal(lastSeen time.Duration, flags uint32, legA, legB Leg) ValueV6 { return curVer.NewValueV6Normal(lastSeen, flags, legA, legB) } // NewValueV6NATForward creates a new ValueV6 of type TypeNATForward for the given // arguments and the reverse key -func NewValueV6NATForward(lastSeen time.Duration, flags uint16, revKey KeyV6) ValueV6 { +func NewValueV6NATForward(lastSeen time.Duration, flags uint32, revKey KeyV6) ValueV6 { return curVer.NewValueV6NATForward(lastSeen, flags, revKey) } // NewValueV6NATReverse creates a new ValueV6 of type TypeNATReverse for the given // arguments and reverse parameters func NewValueV6NATReverse( - lastSeen time.Duration, flags uint16, legA, legB Leg, + lastSeen time.Duration, flags uint32, legA, legB Leg, tunnelIP, origIP net.IP, origPort uint16, ) ValueV6 { return curVer.NewValueV6NATReverse(lastSeen, flags, legA, legB, tunnelIP, origIP, origPort) @@ -123,7 +122,7 @@ func NewValueV6NATReverse( // NewValueV6NATReverseSNAT in addition to NewValueV6NATReverse sets the orig source IP func NewValueV6NATReverseSNAT( - lastSeen time.Duration, flags uint16, legA, legB Leg, + lastSeen time.Duration, flags uint32, legA, legB Leg, tunnelIP, origIP, origSrcIP net.IP, origPort uint16, ) ValueV6 { return curVer.NewValueV6NATReverseSNAT(lastSeen, flags, legA, legB, tunnelIP, origIP, origSrcIP, origPort) diff --git a/felix/bpf/conntrack/scanner.go b/felix/bpf/conntrack/scanner.go index 2e73fbef2f9..4509d5d9034 100644 --- a/felix/bpf/conntrack/scanner.go +++ b/felix/bpf/conntrack/scanner.go @@ -18,6 +18,7 @@ import ( "fmt" "net" "os" + "strconv" "strings" "sync" "time" @@ -27,6 +28,7 @@ import ( "github.com/projectcalico/calico/felix/bpf/conntrack/cleanupv1" "github.com/projectcalico/calico/felix/bpf/conntrack/timeouts" + v4 "github.com/projectcalico/calico/felix/bpf/conntrack/v4" "github.com/projectcalico/calico/felix/bpf/maps" "github.com/projectcalico/calico/felix/cachingmap" "github.com/projectcalico/calico/felix/jitter" @@ -54,6 +56,10 @@ var ( Name: "felix_bpf_conntrack_sweep_duration", Help: "Conntrack sweep execution time (ns)", }) + conntrackGaugeMaglevTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: "felix_bpf_conntrack_maglev_entries_total", + Help: "Total number of Maglev entries in conntrack table broken down by IP version, and, whether destination backend is remote (we're acting as a frontend) or local (we're the backend node).", + }, []string{"destination", "ip_family"}) dummyKeyV6 = NewKeyV6(0, net.IPv6zero, 0, net.IPv6zero, 0) dummyKey = NewKey(0, net.IPv4zero, 0, net.IPv4zero, 0) ) @@ -64,6 +70,7 @@ func init() { prometheus.MustRegister(conntrackGaugeCleaned) prometheus.MustRegister(conntrackCounterCleaned) prometheus.MustRegister(conntrackGaugeSweepDuration) + prometheus.MustRegister(conntrackGaugeMaglevTotal) } // ScanVerdict represents the set of values returned by EntryScan @@ -75,9 +82,11 @@ const ( // ScanVerdictDelete means entry should be deleted ScanVerdictDelete ScanVerdictDeleteImmediate // Delete without adding to cleanup map + ScanVerdictSendRST // Send RST for TCP connections. ) const cleanupBatchSize int = 1000 +const rstBatchSize int = 10 // EntryGet is a function prototype provided to EntryScanner in case it needs to // evaluate other entries to make a verdict @@ -116,6 +125,10 @@ type Scanner struct { bpfCleaner Cleaner versionHelper ipVersionHelper revNATKeyToFwdNATInfo map[KeyInterface]cleanupv1.ValueInterface + ipFamily int + + conntrackGaugeMaglevToLocalBackend prometheus.Gauge + conntrackGaugeMaglevToRemoteBackend prometheus.Gauge wg sync.WaitGroup stopCh chan struct{} @@ -142,26 +155,47 @@ func NewScanner(ctMap maps.Map, kfb func([]byte) KeyInterface, vfb func([]byte) autoScale: strings.ToLower(autoScalingMode) == "doubleiffull", configChangedRestartCallback: configChangedRestartCallback, bpfCleaner: bpfCleaner, + ipFamily: ipVersion, // revNATKeyToFwdNATInfo stores the opposite direction of the mapping of the cleanup bpf map. // => :: revNATKeyToFwdNATInfo: make(map[KeyInterface]cleanupv1.ValueInterface), } + switch ipVersion { + case 4: + s.versionHelper = ipv4Helper{} + case 6: + s.versionHelper = ipv6Helper{} + default: + return nil + } + if bpfCleaner != nil { switch ipVersion { case 4: s.ctCleanupMap = cachingmap.New[KeyInterface, cleanupv1.ValueInterface](ctCleanupMap.GetName(), maps.NewTypedMap[KeyInterface, cleanupv1.ValueInterface](ctCleanupMap, kfb, CleanupValueFromBytes)) - s.versionHelper = ipv4Helper{} case 6: s.ctCleanupMap = cachingmap.New[KeyInterface, cleanupv1.ValueInterface](ctCleanupMap.GetName(), maps.NewTypedMap[KeyInterface, cleanupv1.ValueInterface](ctCleanupMap, kfb, CleanupValueV6FromBytes)) - s.versionHelper = ipv6Helper{} default: return nil } } + + var err error + + s.conntrackGaugeMaglevToLocalBackend, err = conntrackGaugeMaglevTotal.GetMetricWithLabelValues("local", strconv.Itoa(s.ipFamily)) + if err != nil { + log.WithError(err).Panic("Couldn't init (local) Maglev conntrack metric gauge") + } + + s.conntrackGaugeMaglevToRemoteBackend, err = conntrackGaugeMaglevTotal.GetMetricWithLabelValues("remote", strconv.Itoa(s.ipFamily)) + if err != nil { + log.WithError(err).Panic("Couldn't get (remote) Maglev conntrack metric gauge") + } + return s } @@ -223,6 +257,11 @@ func (s *Scanner) Scan() { used := 0 cleaned := 0 numExpired := 0 + maglevEntriesToLocal, maglevEntriesToRemote := 0, 0 + + batchK := make([][]byte, 0, rstBatchSize) + batchV := make([][]byte, 0, rstBatchSize) + rstCount := 0 if s.ctCleanupMap != nil { s.ctCleanupMap.Desired().DeleteAll() @@ -231,6 +270,7 @@ func (s *Scanner) Scan() { err := s.ctMap.Iter(func(k, v []byte) maps.IteratorAction { ctKey := s.keyFromBytes(k) ctVal := s.valueFromBytes(v) + ctFlags := ctVal.Flags() used++ conntrackCounterCleaned.Inc() @@ -242,6 +282,16 @@ func (s *Scanner) Scan() { }).Debug("Examining conntrack entry") } + if ctFlags&v4.FlagMaglev != 0 { + if ctFlags&v4.FlagExtLocal != 0 { + log.Debug("Conntrack is local maglev connection. Incrementing maglev entries counter") + maglevEntriesToLocal++ + } else if ctFlags&v4.FlagNATNPFwd != 0 { + log.Debug("Conntrack is remote maglev connection. Incrementing maglev entries counter") + maglevEntriesToRemote++ + } + } + for _, scanner := range s.scanners { verdict, ts := scanner.Check(ctKey, ctVal, s.get) switch verdict { @@ -251,6 +301,35 @@ func (s *Scanner) Scan() { case ScanVerdictDelete, ScanVerdictDeleteImmediate: // Entry should be deleted. numExpired++ + case ScanVerdictSendRST: + if ctVal.Flags()&v4.FlagSendRST != 0 { + // RST already set, no need to update. + continue + } + updatedVal := ctVal.SetFlags(ctFlags | v4.FlagSendRST) + batchK = append(batchK, ctKey.AsBytes()) + batchV = append(batchV, updatedVal.AsBytes()) + rstCount++ + if rstCount == rstBatchSize { + if debug { + log.Debugf("Updating RST flag on %d conntrack entries in batch.", len(batchK)) + } + applied, err := s.ctMap.BatchUpdate(batchK, batchV, 0) + if err != nil { + applied++ + } + rstCount = rstBatchSize - applied + if rstCount == 0 { + batchK = batchK[:0] + batchV = batchV[:0] + } else { + // Some entries in the batch failed to update. Remove the successfully updated entries from the batch and keep the rest for the next iteration. + batchK = batchK[applied:] + batchV = batchV[applied:] + } + + } + continue } if debug { log.Debug("Deleting conntrack entry.") @@ -319,6 +398,24 @@ func (s *Scanner) Scan() { // Run the bpf cleaner to process the remaining entries in the cleanup map. cleaned += s.runBPFCleaner() + for rstCount > 0 { + applied, err := s.ctMap.BatchUpdate(batchK, batchV, 0) + if err != nil { + applied++ + } + batchK = batchK[applied:] + batchV = batchV[applied:] + rstCount -= applied + } + + batchK = nil + batchV = nil + + log.WithField("value", maglevEntriesToLocal).Debug("Setting local maglev conntrack entries gauge") + s.conntrackGaugeMaglevToLocalBackend.Set(float64(maglevEntriesToLocal)) + log.WithField("value", maglevEntriesToRemote).Debug("Setting remote maglev conntrack entries gauge") + s.conntrackGaugeMaglevToRemoteBackend.Set(float64(maglevEntriesToRemote)) + conntrackCounterSweeps.Inc() conntrackGaugeUsed.Set(float64(used)) conntrackGaugeCleaned.Set(float64(cleaned)) @@ -381,7 +478,7 @@ func (s *Scanner) writeNewSizeFile() error { // Write the new map size to disk so that restarts will pick it up. filename := "/var/lib/calico/bpf_ct_map_size" log.Debugf("Writing %d to "+filename, newSize) - if err := os.WriteFile(filename, []byte(fmt.Sprintf("%d", newSize)), 0o644); err != nil { + if err := os.WriteFile(filename, fmt.Appendf(nil, "%d", newSize), 0o644); err != nil { return fmt.Errorf("unable to write to %s: %w", filename, err) } return nil @@ -399,9 +496,7 @@ func (s *Scanner) get(k KeyInterface) (ValueInterface, error) { // Start the periodic scanner func (s *Scanner) Start() { - s.wg.Add(1) - go func() { - defer s.wg.Done() + s.wg.Go(func() { log.Debug("Conntrack scanner thread started") defer log.Debug("Conntrack scanner thread stopped") @@ -419,7 +514,7 @@ func (s *Scanner) Start() { return } } - }() + }) } func (s *Scanner) iterStart() { diff --git a/felix/bpf/conntrack/timeouts/timeouts.go b/felix/bpf/conntrack/timeouts/timeouts.go index 2b2ca340d5f..242fe5f7d8a 100644 --- a/felix/bpf/conntrack/timeouts/timeouts.go +++ b/felix/bpf/conntrack/timeouts/timeouts.go @@ -107,7 +107,7 @@ func GetTimeouts(config map[string]string) Timeouts { fields := make(log.Fields) - tt := reflect.TypeOf(t) + tt := reflect.TypeFor[Timeouts]() for i := 0; i < v.NumField(); i++ { fields[tt.Field(i).Name] = v.Field(i).Interface() diff --git a/felix/bpf/conntrack/v3/map.go b/felix/bpf/conntrack/v3/map.go index 470f1253f78..b6249e71fcf 100644 --- a/felix/bpf/conntrack/v3/map.go +++ b/felix/bpf/conntrack/v3/map.go @@ -210,22 +210,21 @@ const ( TypeNATForward TypeNATReverse - FlagNATOut uint16 = (1 << 0) - FlagNATFwdDsr uint16 = (1 << 1) - FlagNATNPFwd uint16 = (1 << 2) - FlagSkipFIB uint16 = (1 << 3) - FlagReserved4 uint16 = (1 << 4) - FlagReserved5 uint16 = (1 << 5) - FlagExtLocal uint16 = (1 << 6) - FlagViaNATIf uint16 = (1 << 7) - FlagSrcDstBA uint16 = (1 << 8) - FlagHostPSNAT uint16 = (1 << 9) - FlagSvcSelf uint16 = (1 << 10) - FlagNPLoop uint16 = (1 << 11) - FlagNPRemote uint16 = (1 << 12) - FlagNoDSR uint16 = (1 << 13) - FlagNoRedirPeer uint16 = (1 << 14) - FlagClusterExternal uint16 = (1 << 15) + FlagNATOut uint16 = (1 << 0) + FlagNATFwdDsr uint16 = (1 << 1) + FlagNATNPFwd uint16 = (1 << 2) + FlagSkipFIB uint16 = (1 << 3) + FlagReserved4 uint16 = (1 << 4) + FlagReserved5 uint16 = (1 << 5) + FlagExtLocal uint16 = (1 << 6) + FlagViaNATIf uint16 = (1 << 7) + FlagSrcDstBA uint16 = (1 << 8) + FlagHostPSNAT uint16 = (1 << 9) + FlagSvcSelf uint16 = (1 << 10) + FlagNPLoop uint16 = (1 << 11) + FlagNPRemote uint16 = (1 << 12) + FlagNoDSR uint16 = (1 << 13) + FlagNoRedirPeer uint16 = (1 << 14) ) func (e Value) ReverseNATKey() KeyInterface { diff --git a/felix/bpf/conntrack/v3/map6.go b/felix/bpf/conntrack/v3/map6.go index b9eab4065ca..55759946fe1 100644 --- a/felix/bpf/conntrack/v3/map6.go +++ b/felix/bpf/conntrack/v3/map6.go @@ -23,6 +23,7 @@ import ( log "github.com/sirupsen/logrus" "golang.org/x/sys/unix" + v4 "github.com/projectcalico/calico/felix/bpf/conntrack/v4" "github.com/projectcalico/calico/felix/bpf/maps" ) @@ -66,7 +67,9 @@ func (k KeyV6) String() string { } func (k KeyV6) Upgrade() maps.Upgradable { - panic("conntrack map key already at its latest version") + var key6 v4.KeyV6 + copy(key6[:], k[:]) + return key6 } func NewKeyV6(proto uint8, ipA net.IP, portA uint16, ipB net.IP, portB uint16) KeyV6 { @@ -372,7 +375,9 @@ func (e ValueV6) IsForwardDSR() bool { } func (e ValueV6) Upgrade() maps.Upgradable { - panic("conntrack map value already at its latest version") + var val6 v4.ValueV6 + copy(val6[:], e[:]) + return val6 } var MapParamsV6 = maps.MapParameters{ diff --git a/felix/bpf/conntrack/v4/map.go b/felix/bpf/conntrack/v4/map.go index 40de008ad9b..cdba9697f46 100644 --- a/felix/bpf/conntrack/v4/map.go +++ b/felix/bpf/conntrack/v4/map.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2022-2025 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -128,6 +128,8 @@ const ( VoLastSeen int = 8 VoType int = 16 VoFlags int = 17 + VoFlags3 int = 18 + VoFlags4 int = 19 VoFlags2 int = 23 VoRevKey int = 24 VoLegAB int = 24 @@ -146,7 +148,7 @@ type ValueInterface interface { RSTSeen() int64 LastSeen() int64 Type() uint8 - Flags() uint16 + Flags() uint32 OrigIP() net.IP OrigPort() uint16 OrigSPort() uint16 @@ -157,6 +159,7 @@ type ValueInterface interface { Data() EntryData IsForwardDSR() bool String() string + SetFlags(flags uint32) ValueInterface } func (e Value) RSTSeen() int64 { @@ -171,8 +174,8 @@ func (e Value) Type() uint8 { return e[VoType] } -func (e Value) Flags() uint16 { - return uint16(e[VoFlags]) | (uint16(e[VoFlags2]) << 8) +func (e Value) Flags() uint32 { + return (uint32(e[VoFlags]) | uint32(e[VoFlags2])<<8 | uint32(e[VoFlags3])<<16 | uint32(e[VoFlags4])<<24) } // OrigIP returns the original destination IP, valid only if Type() is TypeNormal or TypeNATReverse @@ -201,26 +204,38 @@ func (e Value) OrigSrcIP() net.IP { return e[VoOrigSIP : VoOrigSIP+4] } +// SetFlags sets the flags in the value, replacing any existing flags +func (e Value) SetFlags(flags uint32) ValueInterface { + e[VoFlags] = byte(flags & 0xff) + e[VoFlags2] = byte((flags >> 8) & 0xff) + e[VoFlags3] = byte((flags >> 16) & 0xff) + e[VoFlags4] = byte((flags >> 24) & 0xff) + return e +} + const ( TypeNormal uint8 = iota TypeNATForward TypeNATReverse - FlagNATOut uint16 = (1 << 0) - FlagNATFwdDsr uint16 = (1 << 1) - FlagNATNPFwd uint16 = (1 << 2) - FlagSkipFIB uint16 = (1 << 3) - FlagReserved4 uint16 = (1 << 4) - FlagReserved5 uint16 = (1 << 5) - FlagExtLocal uint16 = (1 << 6) - FlagViaNATIf uint16 = (1 << 7) - FlagSrcDstBA uint16 = (1 << 8) - FlagHostPSNAT uint16 = (1 << 9) - FlagSvcSelf uint16 = (1 << 10) - FlagNPLoop uint16 = (1 << 11) - FlagNPRemote uint16 = (1 << 12) - FlagNoDSR uint16 = (1 << 13) - FlagNoRedirPeer uint16 = (1 << 14) + FlagNATOut uint32 = (1 << 0) + FlagNATFwdDsr uint32 = (1 << 1) + FlagNATNPFwd uint32 = (1 << 2) + FlagSkipFIB uint32 = (1 << 3) + FlagReserved4 uint32 = (1 << 4) + FlagReserved5 uint32 = (1 << 5) + FlagExtLocal uint32 = (1 << 6) + FlagViaNATIf uint32 = (1 << 7) + FlagSrcDstBA uint32 = (1 << 8) + FlagHostPSNAT uint32 = (1 << 9) + FlagSvcSelf uint32 = (1 << 10) + FlagNPLoop uint32 = (1 << 11) + FlagNPRemote uint32 = (1 << 12) + FlagNoDSR uint32 = (1 << 13) + FlagNoRedirPeer uint32 = (1 << 14) + FlagSetDSCP uint32 = (1 << 15) + FlagMaglev uint32 = (1 << 16) + FlagSendRST uint32 = (1 << 17) ) func (e Value) ReverseNATKey() KeyInterface { @@ -253,15 +268,17 @@ func (e *Value) SetNATSport(sport uint16) { binary.LittleEndian.PutUint16(e[VoNATSPort:VoNATSPort+2], sport) } -func initValue(v *Value, lastSeen time.Duration, typ uint8, flags uint16) { +func initValue(v *Value, lastSeen time.Duration, typ uint8, flags uint32) { binary.LittleEndian.PutUint64(v[VoLastSeen:VoLastSeen+8], uint64(lastSeen)) v[VoType] = typ v[VoFlags] = byte(flags & 0xff) v[VoFlags2] = byte((flags >> 8) & 0xff) + v[VoFlags3] = byte((flags >> 16) & 0xff) + v[VoFlags4] = byte((flags >> 24) & 0xff) } // NewValueNormal creates a new Value of type TypeNormal based on the given parameters -func NewValueNormal(lastSeen time.Duration, flags uint16, legA, legB Leg) Value { +func NewValueNormal(lastSeen time.Duration, flags uint32, legA, legB Leg) Value { v := Value{} initValue(&v, lastSeen, TypeNormal, flags) @@ -274,7 +291,7 @@ func NewValueNormal(lastSeen time.Duration, flags uint16, legA, legB Leg) Value // NewValueNATForward creates a new Value of type TypeNATForward for the given // arguments and the reverse key -func NewValueNATForward(lastSeen time.Duration, flags uint16, revKey Key) Value { +func NewValueNATForward(lastSeen time.Duration, flags uint32, revKey Key) Value { v := Value{} initValue(&v, lastSeen, TypeNATForward, flags) @@ -286,7 +303,7 @@ func NewValueNATForward(lastSeen time.Duration, flags uint16, revKey Key) Value // NewValueNATReverse creates a new Value of type TypeNATReverse for the given // arguments and reverse parameters -func NewValueNATReverse(lastSeen time.Duration, flags uint16, legA, legB Leg, +func NewValueNATReverse(lastSeen time.Duration, flags uint32, legA, legB Leg, tunnelIP, origIP net.IP, origPort uint16) Value { v := Value{} @@ -304,7 +321,7 @@ func NewValueNATReverse(lastSeen time.Duration, flags uint16, legA, legB Leg, } // NewValueNATReverseSNAT in addition to NewValueNATReverse sets the orig source IP -func NewValueNATReverseSNAT(lastSeen time.Duration, flags uint16, legA, legB Leg, +func NewValueNATReverseSNAT(lastSeen time.Duration, flags uint32, legA, legB Leg, tunnelIP, origIP, origSrcIP net.IP, origPort uint16) Value { v := NewValueNATReverse(lastSeen, flags, legA, legB, tunnelIP, origIP, origPort) copy(v[VoOrigSIP:VoOrigSIP+4], origIP.To4()) @@ -499,9 +516,15 @@ func (e Value) String() string { flagsStr += " np-remote" } - if flags&FlagNPRemote != 0 { + if flags&FlagNoDSR != 0 { flagsStr += " no-dsr" } + if flags&FlagNoRedirPeer != 0 { + flagsStr += " no-redir-peer" + } + if flags&FlagSetDSCP != 0 { + flagsStr += " dscp" + } } ret := fmt.Sprintf("Entry{Type:%d, LastSeen:%d, Flags:%s ", diff --git a/felix/bpf/conntrack/v4/map6.go b/felix/bpf/conntrack/v4/map6.go index 17d96177fed..3b415867f67 100644 --- a/felix/bpf/conntrack/v4/map6.go +++ b/felix/bpf/conntrack/v4/map6.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2022-2025 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -117,6 +117,8 @@ const ( VoLastSeenV6 int = 8 VoTypeV6 int = 16 VoFlagsV6 int = 17 + VoFlags3V6 int = 18 + VoFlags4V6 int = 19 VoFlags2V6 int = 23 VoRevKeyV6 int = 24 VoLegABV6 int = 24 @@ -143,8 +145,8 @@ func (e ValueV6) Type() uint8 { return e[VoTypeV6] } -func (e ValueV6) Flags() uint16 { - return uint16(e[VoFlagsV6]) | (uint16(e[VoFlags2]) << 8) +func (e ValueV6) Flags() uint32 { + return (uint32(e[VoFlagsV6]) | uint32(e[VoFlags2])<<8 | uint32(e[VoFlags3])<<16 | uint32(e[VoFlags4])<<24) } // OrigIP returns the original destination IP, valid only if Type() is TypeNormal or TypeNATReverse @@ -173,6 +175,15 @@ func (e ValueV6) OrigSrcIP() net.IP { return e[VoOrigSIPV6 : VoOrigSIPV6+16] } +// SetFlags sets the flags in the value, replacing any existing flags +func (e ValueV6) SetFlags(flags uint32) ValueInterface { + e[VoFlags] = byte(flags & 0xff) + e[VoFlags2] = byte((flags >> 8) & 0xff) + e[VoFlags3] = byte((flags >> 16) & 0xff) + e[VoFlags4] = byte((flags >> 24) & 0xff) + return e +} + func (e ValueV6) ReverseNATKey() KeyInterface { var ret KeyV6 @@ -203,15 +214,17 @@ func (e *ValueV6) SetNATSport(sport uint16) { binary.LittleEndian.PutUint16(e[VoNATSPortV6:VoNATSPortV6+2], sport) } -func initValueV6(v *ValueV6, lastSeen time.Duration, typ uint8, flags uint16) { +func initValueV6(v *ValueV6, lastSeen time.Duration, typ uint8, flags uint32) { binary.LittleEndian.PutUint64(v[VoLastSeenV6:VoLastSeenV6+8], uint64(lastSeen)) v[VoTypeV6] = typ v[VoFlagsV6] = byte(flags & 0xff) - v[VoFlags2] = byte((flags >> 8) & 0xff) + v[VoFlags2V6] = byte((flags >> 8) & 0xff) + v[VoFlags3V6] = byte((flags >> 16) & 0xff) + v[VoFlags4V6] = byte((flags >> 24) & 0xff) } // NewValueV6Normal creates a new ValueV6 of type TypeNormal based on the given parameters -func NewValueV6Normal(lastSeen time.Duration, flags uint16, legA, legB Leg) ValueV6 { +func NewValueV6Normal(lastSeen time.Duration, flags uint32, legA, legB Leg) ValueV6 { v := ValueV6{} initValueV6(&v, lastSeen, TypeNormal, flags) @@ -224,19 +237,19 @@ func NewValueV6Normal(lastSeen time.Duration, flags uint16, legA, legB Leg) Valu // NewValueV6NATForward creates a new ValueV6 of type TypeNATForward for the given // arguments and the reverse key -func NewValueV6NATForward(lastSeen time.Duration, flags uint16, revKey KeyV6) ValueV6 { +func NewValueV6NATForward(lastSeen time.Duration, flags uint32, revKey KeyV6) ValueV6 { v := ValueV6{} initValueV6(&v, lastSeen, TypeNATForward, flags) - copy(v[VoRevKeyV6:VoRevKeyV6+KeySize], revKey.AsBytes()) + copy(v[VoRevKeyV6:VoRevKeyV6+KeyV6Size], revKey.AsBytes()) return v } // NewValueV6NATReverse creates a new ValueV6 of type TypeNATReverse for the given // arguments and reverse parameters -func NewValueV6NATReverse(lastSeen time.Duration, flags uint16, legA, legB Leg, +func NewValueV6NATReverse(lastSeen time.Duration, flags uint32, legA, legB Leg, tunnelIP, origIP net.IP, origPort uint16) ValueV6 { v := ValueV6{} @@ -254,7 +267,7 @@ func NewValueV6NATReverse(lastSeen time.Duration, flags uint16, legA, legB Leg, } // NewValueV6NATReverseSNAT in addition to NewValueV6NATReverse sets the orig source IP -func NewValueV6NATReverseSNAT(lastSeen time.Duration, flags uint16, legA, legB Leg, +func NewValueV6NATReverseSNAT(lastSeen time.Duration, flags uint32, legA, legB Leg, tunnelIP, origIP, origSrcIP net.IP, origPort uint16) ValueV6 { v := NewValueV6NATReverse(lastSeen, flags, legA, legB, tunnelIP, origIP, origPort) copy(v[VoOrigSIPV6:VoOrigSIPV6+16], origIP.To4()) @@ -349,6 +362,9 @@ func (e ValueV6) String() string { if flags&FlagNPRemote != 0 { flagsStr += " no-dsr" } + if flags&FlagSetDSCP != 0 { + flagsStr += " dscp" + } } ret := fmt.Sprintf("Entry{Type:%d, LastSeen:%d, Flags:%s ", diff --git a/felix/bpf/consistenthash/consistenthash.go b/felix/bpf/consistenthash/consistenthash.go new file mode 100644 index 00000000000..aedda199c4a --- /dev/null +++ b/felix/bpf/consistenthash/consistenthash.go @@ -0,0 +1,165 @@ +package consistenthash + +import ( + "bytes" + "encoding/binary" + "fmt" + "hash" + "slices" + + "github.com/sirupsen/logrus" + k8sp "k8s.io/kubernetes/pkg/proxy" +) + +// ConsistentHash implements ConsistentHash consistent hashing: +// - For each configured backend, generates a preference-list +// of LUT positions it would like to occupy. +// - Constructs a ConsistentHash backend LUT to be hashed-into with packet 5-tuples. +type ConsistentHash struct { + // m is a prime number. + // Defaults to ConsistentHash.M + m int + h1, h2 hash.Hash + + // Lexicographically orders the backends by name. + backendNames []string + backendsByName map[string]backend +} + +type backend struct { + permutation []int + endpoint k8sp.Endpoint +} + +// New returns a backend-hashing module. +func New(lutSize int, hash1, hash2 hash.Hash) *ConsistentHash { + c := &ConsistentHash{m: lutSize} + c.backendNames = make([]string, 0) + c.backendsByName = make(map[string]backend) + + if hash1 == nil || hash2 == nil { + panic("nil hashing function for ConsistentHash") + } + c.h1 = hash1 + c.h2 = hash2 + + return c +} + +// AddBackend generates and stores a permutation for the given backend name, +// to be factored into the LUT generation. +func (ch *ConsistentHash) AddBackend(kep k8sp.Endpoint) { + var b backend + + if kep == nil { + logrus.Warn("Ignoring AddBackend for nil endpoint") + return + } + + name := kep.String() + if _, exists := ch.backendsByName[name]; exists { + logrus.WithField("backend", name).Info("Will not regenerate permutation for pre-existing backend") + return + } + + permutation, err := ch.permutation(name) + if err != nil { + logrus.WithError(err).WithField("backend", name).Error("Failed to generate permutation for backend") + return + } + + b.endpoint = kep + b.permutation = permutation + + ch.backendsByName[name] = b + ch.backendNames = append(ch.backendNames, name) +} + +// Generate sorts the list of backends and then generates a ConsistentHash LUT. +func (ch *ConsistentHash) Generate() []k8sp.Endpoint { + if len(ch.backendNames) == 0 { + return nil + } + + slices.Sort(ch.backendNames) + logrus.WithField("backends", ch.backendNames).Info("sorted backend names") + + // Next-preference for each backend. + next := make([]int, len(ch.backendNames)) + // The final lookup-table to hash against. + lut := make([]k8sp.Endpoint, ch.m) + + // In total, we go to M iterations of the inner loop. + // Can't rely on the outer-loop condition to break at the right time, + // so we're counting manually. + n := 0 + for { + for i, backend := range ch.backendNames { + prefs := ch.backendsByName[backend].permutation + choice := prefs[next[i]] + for lut[choice] != nil { + next[i]++ + choice = prefs[next[i]] + } + + lut[choice] = ch.backendsByName[backend].endpoint + + // Its *next* preference (after this one). Not sure if necessary to remember this. + // Maybe it makes something easier later on if this info is to-hand? + // If its safe to discard, we can probably rewrite this whole func to be more Go-ful. + next[i]++ + n++ + if n == ch.m { + return lut + } + } + } +} + +// Permutation implements Permutator interface. +func (ch *ConsistentHash) permutation(backendName string) ([]int, error) { + offset, skip, err := ch.offsetAndSKip(backendName) + if err != nil { + return nil, fmt.Errorf("couldn't generate permutation skip/offset for backend '%s': %w", backendName, err) + } + + permutation := make([]int, ch.m) + for j := range ch.m { + permutation[j] = (offset + (j * skip)) % ch.m + } + + return permutation, nil +} + +func (ch *ConsistentHash) offsetAndSKip(s string) (int, int, error) { + offset, err := hashFromString(s, ch.h1, []byte{0}) + if err != nil { + return 0, 0, err + } + + skip, err := hashFromString(s, ch.h2, []byte{0xa}) + if err != nil { + return 0, 0, err + } + return (offset % ch.m), (skip % (ch.m - 1)) + 1, nil +} + +func hashFromString(s string, h hash.Hash, seed []byte) (int, error) { + reinitHash(h, seed) + + h.Write([]byte(s)) + sum := h.Sum(nil) + reader := bytes.NewReader(sum) + var result uint32 + err := binary.Read(reader, binary.NativeEndian, &result) + if err != nil { + return 0, err + } + + return int(result), nil +} + +func reinitHash(h hash.Hash, seed []byte) { + h.Reset() + h.Write(seed) +} diff --git a/felix/bpf/consistenthash/consistenthash_suite_test.go b/felix/bpf/consistenthash/consistenthash_suite_test.go new file mode 100644 index 00000000000..7cd5837000b --- /dev/null +++ b/felix/bpf/consistenthash/consistenthash_suite_test.go @@ -0,0 +1,21 @@ +package consistenthash + +import ( + "testing" + + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + + "github.com/projectcalico/calico/libcalico-go/lib/testutils" +) + +func init() { + testutils.HookLogrusForGinkgo() +} + +func TestConsistentHash(t *testing.T) { + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/felix_bpf_consistenthash_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/bpf/consistenthash", suiteConfig, reporterConfig) +} diff --git a/felix/bpf/consistenthash/consistenthash_test.go b/felix/bpf/consistenthash/consistenthash_test.go new file mode 100644 index 00000000000..735b912012f --- /dev/null +++ b/felix/bpf/consistenthash/consistenthash_test.go @@ -0,0 +1,121 @@ +package consistenthash + +import ( + "fmt" + "hash/fnv" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/sirupsen/logrus" + + types "github.com/projectcalico/calico/felix/bpf/consistenthash/test" +) + +var testM = 71 + +var _ = Describe("BPF ConsistentHash UTs", func() { + newDefaultConsistentHash := func() *ConsistentHash { + return New( + // A big prime would cause this test's execution time to balloon. + testM, + fnv.New32(), fnv.New32(), + ) + } + + addBackend := func(mag *ConsistentHash, ip string, port uint16) types.MockEndpoint { + e := types.MockEndpoint{ + Ip: ip, + Prt: port, + } + permutation, err := mag.permutation(e.String()) + Expect(err).NotTo(HaveOccurred()) + + mag.backendsByName[e.String()] = backend{permutation: permutation, endpoint: e} + mag.backendNames = append(mag.backendNames, e.String()) + + logrus.Infof("Appending backend name %s to backends. Num backends: %d", e.String(), len(mag.backendNames)) + return e + } + + Context("Consistent hashing", func() { + var mag *ConsistentHash + + BeforeEach(func() { + mag = newDefaultConsistentHash() + }) + + It("generate valid permutations", func() { + for _, b := range []string{"backend1", "backend2", "backend3", "sdfskjnksdnf", "sdfkjsdnfksjndf", "i am a backend with a very very very long nameeeeeeeeeeeeeeeeeeeeee"} { + p, err := mag.permutation(b) + Expect(err).NotTo(HaveOccurred()) + dupMap := make(map[int]bool) + for _, idx := range p { + Expect(dupMap).NotTo(HaveKey(idx)) + dupMap[idx] = true + } + } + }) + + It("should create a lookup table", func() { + for _, b := range []string{"backend0", "backend1", "backend2", "backend3", "backend4", "backend5", "backend6", "backend7", "backend8", "backend9"} { + addBackend(mag, b, uint16(8080)) + } + + lut := mag.Generate() + Expect(lut).NotTo(ContainElement("")) + Expect(lut).To(HaveLen(testM)) + }) + + It("should generate the same permutations for the same backend", func() { + otherMag := newDefaultConsistentHash() + for _, b := range []string{"backend1", "backend2"} { + e0 := addBackend(mag, b, uint16(8080)) + e1 := addBackend(otherMag, b, uint16(8080)) + Expect(mag.backendsByName[e0.String()]).To(Equal(otherMag.backendsByName[e1.String()])) + } + }) + + It("evenly distribute LUT spaces for every backend", func() { + backends := []string{"backend0", "backend1", "backend2", "backend3", "backend4", "backend5", "backend6", "backend7", "backend8", "backend9"} + for _, b := range backends { + addBackend(mag, b, uint16(8080)) + } + + lut := mag.Generate() + Expect(lut).NotTo(ContainElement("")) + Expect(lut).To(HaveLen(testM)) + + By("gathering all IPs in the LUT, validating each one, and counting occurrences") + foundBackends := make(map[string]int) + for _, ep := range lut { + be := ep.IP() + foundBackends[be] = foundBackends[be] + 1 + + Expect(backends).To(ContainElement(be)) + } + + By("Checking every backend is present") + for _, b := range backends { + Expect(foundBackends).To(HaveKey(b)) + } + + By("confirming the number of occurrences of each key does not differ by more than 1") + lowest := -1 + highest := -1 + for _, v := range foundBackends { + if lowest == -1 { + lowest = v + } + if v > highest { + highest = v + } + if v < lowest { + lowest = v + } + } + + greaterThanOne := (highest - lowest) > 1 + Expect(greaterThanOne).To(BeFalse(), fmt.Sprintf("Highest backends occurrences: %d, lowest: %d", highest, lowest)) + }) + }) +}) diff --git a/felix/bpf/consistenthash/test/types.go b/felix/bpf/consistenthash/test/types.go new file mode 100644 index 00000000000..5cddff358f9 --- /dev/null +++ b/felix/bpf/consistenthash/test/types.go @@ -0,0 +1,25 @@ +package types + +import ( + "net" + "strconv" + + "k8s.io/apimachinery/pkg/util/sets" +) + +type MockEndpoint struct { + Ip string + Prt uint16 + Local, Ready, Serving, Terminating bool + ZoneHnts, NodeHnts sets.Set[string] +} + +func (ep MockEndpoint) String() string { return net.JoinHostPort(ep.Ip, strconv.Itoa(int(ep.Prt))) } +func (ep MockEndpoint) IP() string { return ep.Ip } +func (ep MockEndpoint) IsLocal() bool { return ep.Local } +func (ep MockEndpoint) Port() int { return int(ep.Prt) } +func (ep MockEndpoint) IsReady() bool { return ep.Ready } +func (ep MockEndpoint) IsServing() bool { return ep.Serving } +func (ep MockEndpoint) IsTerminating() bool { return ep.Terminating } +func (ep MockEndpoint) ZoneHints() sets.Set[string] { return ep.ZoneHnts } +func (ep MockEndpoint) NodeHints() sets.Set[string] { return ep.NodeHnts } diff --git a/felix/bpf/counters/counters.go b/felix/bpf/counters/counters.go index e449d5beaf1..5169799c0f0 100644 --- a/felix/bpf/counters/counters.go +++ b/felix/bpf/counters/counters.go @@ -27,7 +27,7 @@ import ( ) const ( - MaxCounterNumber int = 24 + MaxCounterNumber int = 26 counterMapKeySize int = 8 counterMapValueSize int = 8 ) @@ -83,6 +83,8 @@ const ( DroppedFragReorder DroppedFragUnsupported DroppedQoS + Reserved1 + DroppedMaglevNoBackend ) type Description struct { @@ -205,6 +207,10 @@ var descriptions DescList = DescList{ Counter: DroppedQoS, Category: "Dropped", Caption: "QoS control limit", }, + { + Counter: DroppedMaglevNoBackend, + Category: "Dropped", Caption: "Maglev lookup found no backends for service IP", + }, } func Descriptions() DescList { diff --git a/felix/bpf/counters/map.go b/felix/bpf/counters/map.go index c7877d12434..37bfb9c9c84 100644 --- a/felix/bpf/counters/map.go +++ b/felix/bpf/counters/map.go @@ -29,7 +29,7 @@ var MapParameters = maps.MapParameters{ ValueSize: counterMapValueSize * MaxCounterNumber, MaxEntries: 20000, Name: "cali_counters", - Version: 5, + Version: 6, } func Map() maps.Map { diff --git a/felix/bpf/events/events_suite_test.go b/felix/bpf/events/events_suite_test.go index ec7e132a17c..c09d48278b2 100644 --- a/felix/bpf/events/events_suite_test.go +++ b/felix/bpf/events/events_suite_test.go @@ -17,16 +17,16 @@ package events import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestEvents(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/events_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Events Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/felix_bpf_events_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/bpf/events", suiteConfig, reporterConfig) } diff --git a/felix/bpf/failsafes/failsafes.go b/felix/bpf/failsafes/failsafes.go index 0ee7ac074f7..fdc26749245 100644 --- a/felix/bpf/failsafes/failsafes.go +++ b/felix/bpf/failsafes/failsafes.go @@ -46,7 +46,7 @@ type Manager struct { ipFamily proto.IPVersion } -func (m *Manager) OnUpdate(_ interface{}) { +func (m *Manager) OnUpdate(_ any) { } func NewManager( @@ -149,7 +149,7 @@ func (m *Manager) ResyncFailsafes() error { addPort(p, true) } - unknownKeys.Iter(func(k KeyInterface) error { + for k := range unknownKeys.All() { err := m.failsafesMap.Delete(k.ToSlice()) if err != nil { log.WithError(err).WithField("key", k).Warn("Failed to remove failsafe port from map.") @@ -157,8 +157,7 @@ func (m *Manager) ResyncFailsafes() error { } else { log.WithField("key", k).Debug("Deleted failsafe port.") } - return nil - }) + } m.failsafesInSync = !syncFailed if syncFailed { diff --git a/felix/bpf/filter/filter.go b/felix/bpf/filter/filter.go index 61bafb72004..e1d2d053f7f 100644 --- a/felix/bpf/filter/filter.go +++ b/felix/bpf/filter/filter.go @@ -20,8 +20,8 @@ import ( "encoding/binary" "fmt" - "github.com/google/gopacket/layers" - "github.com/google/gopacket/pcap" + "github.com/gopacket/gopacket/layers" + "github.com/gopacket/gopacket/pcap" "github.com/projectcalico/calico/felix/bpf/asm" "github.com/projectcalico/calico/felix/bpf/maps" @@ -33,35 +33,35 @@ var ( skbCb1 = asm.FieldOffset{Offset: 12*4 + 1*4, Field: "skb->cb[1]"} ) -func New(epType tcdefs.EndpointType, minLen int, expression string, jumpMapFD maps.FD) (asm.Insns, error) { +func New(epType tcdefs.EndpointType, maxData int, expression string, jumpMapFD, stateMapFD maps.FD) (asm.Insns, error) { linkType := layers.LinkTypeEthernet if epType == tcdefs.EpTypeL3Device { linkType = layers.LinkTypeIPv4 } - return newFilter(linkType, minLen, expression, jumpMapFD, false) + return newFilter(linkType, maxData, expression, jumpMapFD, stateMapFD, false) } -func NewStandAlone(linkType layers.LinkType, minLen int, expression string) (asm.Insns, error) { - return newFilter(linkType, minLen, expression, 0, true) +func NewStandAlone(linkType layers.LinkType, maxData int, expression string, stateMapFD maps.FD) (asm.Insns, error) { + return newFilter(linkType, maxData, expression, 0, stateMapFD, true) } func newFilter( linkType layers.LinkType, - minLen int, + maxData int, expression string, - jumpMapFD maps.FD, + jumpMapFD, stateMapFD maps.FD, standAlone bool) (asm.Insns, error) { b := asm.NewBlock(true) - insns, err := pcap.CompileBPFFilter(linkType, minLen, expression) + insns, err := pcap.CompileBPFFilter(linkType, maxData, expression) if err != nil { return nil, fmt.Errorf("pcap compile filter: %w", err) } - programHeader(b, minLen) + programHeader(b, maxData, stateMapFD) err = cBPF2eBPF(b, insns, linkType) if err != nil { @@ -100,32 +100,57 @@ func Printk(b *asm.Block, msg string) { b.Call(asm.HelperTracePrintk) } -func programHeader(b *asm.Block, minLen int) { +func programHeader(b *asm.Block, maxData int, stateMapFD maps.FD) { // Preamble to the policy program. b.LabelNextInsn("start") b.Mov64(asm.R6, asm.R1) // Save R1 (context) in R6. - b.AddComment("Make sure enough data is accessible") - b.Load32(asm.R1, asm.R6, asm.SkbuffOffsetLen) - b.JumpLTImm64(asm.R1, int32(minLen), "exit") - - // Load data pointer to R7 - b.Load32(asm.R7, asm.R6, asm.SkbuffOffsetData) - b.Load32(asm.R8, asm.R6, asm.SkbuffOffsetDataEnd) - b.Mov64(asm.R3, asm.R7) - b.AddImm64(asm.R3, int32(minLen)) - b.JumpLE64(asm.R3, asm.R8, "filter") - - // Pull data if do not have enough + b.AddComment("Load packet bytes to stack buffer") + // Load skb->len to determine how many bytes to load + b.Load32(asm.R4, asm.R6, asm.SkbuffOffsetLen) + + // Calculate min(skb->len, maxData) and store in R4 + b.MovImm64(asm.R1, int32(maxData)) + b.JumpLE64(asm.R4, asm.R1, "use_skb_len") + // If skb->len > maxData, use maxData + b.Mov64(asm.R4, asm.R1) + b.LabelNextInsn("use_skb_len") + // R4 now contains min(skb->len, maxData) + + // Check that we have at least 1 byte to load (verifier requirement) + b.JumpLTImm64(asm.R4, 1, "exit") + + // Save the actual length to load in R9 (callee-saved) before calling helper + b.Mov64(asm.R9, asm.R4) + + b.AddComment("Get scratch buffer from state map") + b.MovImm32(asm.R1, 0) // R1 = 0 -use state as scratch buffer, it is not used until after the filter + b.StoreStack32(asm.R1, -4) // store 0 at stack[-4] as a key to the state map + b.Mov64(asm.R2, asm.R10) // R2 = R10 + b.AddImm64(asm.R2, -4) // R2 = &stack[-4] + b.LoadMapFD(asm.R1, uint32(stateMapFD)) // R1 = 0 (64-bit immediate) + b.Call(asm.HelperMapLookupElem) // Call helper + // Check return value for NULL. + b.JumpEqImm64(asm.R0, 0, "exit") + // Set up R7 to point to the sracth buffer + b.Mov64(asm.R7, asm.R0) + + // Prepare arguments for bpf_skb_load_bytes + // R1 = skb (context) + // R2 = offset (0 - start from beginning) + // R3 = destination buffer b.Mov64(asm.R1, asm.R6) // ctx -> R1 - b.LoadImm64(asm.R2, int64(minLen)) - b.Call(asm.HelperSkbPullData) + b.MovImm64(asm.R2, 0) // offset = 0 (start from beginning) + b.Mov64(asm.R3, asm.R7) // dest = scratch buffer + b.Mov64(asm.R4, asm.R9) // restore length to load + b.Call(asm.HelperSkbLoadBytes) + + // Check if bpf_skb_load_bytes succeeded (returns 0 on success) b.JumpNEImm64(asm.R0, 0, "exit") - b.Load32(asm.R7, asm.R6, asm.SkbuffOffsetData) - b.Load32(asm.R8, asm.R6, asm.SkbuffOffsetDataEnd) - b.Mov64(asm.R3, asm.R7) - b.AddImm64(asm.R3, int32(minLen)) - b.JumpGT64(asm.R3, asm.R8, "exit") + + // Set up R8 to point to the end of actually loaded data (R7 + actual length) + b.Mov64(asm.R8, asm.R7) + b.Add64(asm.R8, asm.R9) b.LabelNextInsn("filter") // Zero R1 (A) and R2 (X) diff --git a/felix/bpf/filter/filter_stub.go b/felix/bpf/filter/filter_stub.go index 890e5648341..459702f9202 100644 --- a/felix/bpf/filter/filter_stub.go +++ b/felix/bpf/filter/filter_stub.go @@ -22,6 +22,6 @@ import ( tcdefs "github.com/projectcalico/calico/felix/bpf/tc/defs" ) -func New(_ tcdefs.EndpointType, _ int, _ string, _ maps.FD) (asm.Insns, error) { +func New(_ tcdefs.EndpointType, _ int, _ string, _, _ maps.FD) (asm.Insns, error) { panic("this is stub only") } diff --git a/felix/bpf/hook/load.go b/felix/bpf/hook/load.go index 3048012f027..9cbd29a0c92 100644 --- a/felix/bpf/hook/load.go +++ b/felix/bpf/hook/load.go @@ -72,7 +72,6 @@ type AttachType struct { Family int Type tcdefs.EndpointType LogLevel string - FIB bool ToHostDrop bool DSR bool } @@ -103,6 +102,10 @@ func (at AttachType) hasIPDefrag() bool { return at.Hook == Ingress } +func (at AttachType) hasMaglev() bool { + return at.Type == tcdefs.EpTypeHost && at.Hook == Ingress +} + type DefPolicy int const ( @@ -146,49 +149,41 @@ func initObjectFiles() { for _, family := range []int{4, 6} { for _, logLevel := range []string{"off", "debug"} { for _, epToHostDrop := range []bool{false, true} { - epToHostDrop := epToHostDrop - for _, fibEnabled := range []bool{false, true} { - fibEnabled := fibEnabled - epTypes := []tcdefs.EndpointType{ - tcdefs.EpTypeWorkload, - tcdefs.EpTypeHost, - tcdefs.EpTypeIPIP, - tcdefs.EpTypeL3Device, - tcdefs.EpTypeNAT, - tcdefs.EpTypeLO, - tcdefs.EpTypeVXLAN, - } - for _, epType := range epTypes { - epType := epType - for _, hook := range []Hook{Ingress, Egress} { - hook := hook - for _, dsr := range []bool{false, true} { - toOrFrom := tcdefs.ToEp - if hook == Ingress { - toOrFrom = tcdefs.FromEp - } - - attachType := AttachType{ - Family: family, - Type: epType, - Hook: hook, - ToHostDrop: epToHostDrop, - FIB: fibEnabled, - DSR: dsr, - LogLevel: logLevel, - } - filename := tcdefs.ProgFilename( - family, - epType, - toOrFrom, - epToHostDrop, - fibEnabled, - dsr, - logLevel, - bpfutils.BTFEnabled, - ) - SetObjectFile(attachType, filename) + epTypes := []tcdefs.EndpointType{ + tcdefs.EpTypeWorkload, + tcdefs.EpTypeHost, + tcdefs.EpTypeIPIP, + tcdefs.EpTypeL3Device, + tcdefs.EpTypeNAT, + tcdefs.EpTypeLO, + tcdefs.EpTypeVXLAN, + } + for _, epType := range epTypes { + for _, hook := range []Hook{Ingress, Egress} { + for _, dsr := range []bool{false, true} { + toOrFrom := tcdefs.ToEp + if hook == Ingress { + toOrFrom = tcdefs.FromEp + } + + attachType := AttachType{ + Family: family, + Type: epType, + Hook: hook, + ToHostDrop: epToHostDrop, + DSR: dsr, + LogLevel: logLevel, } + filename := tcdefs.ProgFilename( + family, + epType, + toOrFrom, + epToHostDrop, + dsr, + logLevel, + bpfutils.BTFEnabled, + ) + SetObjectFile(attachType, filename) } } } diff --git a/felix/bpf/hook/map.go b/felix/bpf/hook/map.go index 983e5fe9a37..49d5ab5055c 100644 --- a/felix/bpf/hook/map.go +++ b/felix/bpf/hook/map.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023 Tigera, Inc. All rights reserved. +// Copyright (c) 2023-2026 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -43,6 +43,8 @@ const ( SubProgIcmpInnerNat SubProgNewFlow SubProgIPFrag + SubProgMaglev + SubProgTCPRst SubProgTCMainDebug SubProgXDPMain = SubProgTCMain @@ -61,6 +63,8 @@ var tcSubProgNames = []string{ "calico_tc_skb_icmp_inner_nat", "calico_tc_skb_new_flow_entrypoint", "calico_tc_skb_ipv4_frag", + "calico_tc_maglev", + "calico_tc_skb_send_tcp_rst", } var xdpSubProgNames = []string{ @@ -71,6 +75,64 @@ var xdpSubProgNames = []string{ "calico_xdp_drop", } +// GetSubProgNames returns the sub-program names for the given hook type. +// This is useful for testing and other scenarios where you need to know +// which sub-programs are defined for a particular hook. +// Returns a copy of the internal array to prevent modifications. +// For XDP hooks, returns XDP program names. For TC hooks (Ingress/Egress), +// returns TC program names. +func GetSubProgNames(hookType Hook) []string { + if hookType == XDP { + return append([]string{}, xdpSubProgNames...) + } + // Both Ingress and Egress use TC programs + return append([]string{}, tcSubProgNames...) +} + +// SubProgInfo holds information about a sub-program that should be loaded. +type SubProgInfo struct { + Index int // Index in the sub-program names array + Name string // Name of the sub-program + SubProg SubProg // SubProg identifier +} + +// GetApplicableSubProgs returns the list of sub-programs that should be loaded +// for the given AttachType. It filters out empty entries and conditionally +// excludes programs based on the AttachType's characteristics. +// The skipIPDefrag parameter allows skipping IP defrag even if the AttachType +// normally supports it (used when loading fails and retrying without IP defrag). +func GetApplicableSubProgs(at AttachType, skipIPDefrag bool) []SubProgInfo { + var result []SubProgInfo + + subs := GetSubProgNames(at.Hook) + + for idx, subprog := range subs { + if subprog == "" { + continue + } + + if SubProg(idx) == SubProgTCHostCtConflict && !at.hasHostConflictProg() { + continue + } + + if SubProg(idx) == SubProgIPFrag && (!at.hasIPDefrag() || skipIPDefrag) { + continue + } + + if SubProg(idx) == SubProgMaglev && !at.hasMaglev() { + continue + } + + result = append(result, SubProgInfo{ + Index: idx, + Name: subprog, + SubProg: SubProg(idx), + }) + } + + return result +} + // Layout maps sub-programs of an object to their location in the ProgramsMap type Layout map[SubProg]int @@ -78,9 +140,7 @@ func MergeLayouts(layouts ...Layout) Layout { ret := make(Layout) for _, l := range layouts { - for k, v := range l { - ret[k] = v - } + maps.Copy(ret, l) } return ret @@ -92,7 +152,8 @@ type ProgramsMap struct { programsLock sync.Mutex programs map[AttachType]*program - nextIdx atomic.Int64 + expectedAttachType string + nextIdx atomic.Int64 } type program struct { @@ -100,19 +161,44 @@ type program struct { layout Layout } -var ProgramsMapParameters = bpfmaps.MapParameters{ +var IngressProgramsMapParameters = bpfmaps.MapParameters{ Type: "prog_array", KeySize: 4, ValueSize: 4, MaxEntries: maxPrograms, - Name: "cali_progs", - Version: 3, + Name: "cali_progs_ing", + Version: 2, } -func NewProgramsMap() bpfmaps.Map { +var EgressProgramsMapParameters = bpfmaps.MapParameters{ + Type: "prog_array", + KeySize: 4, + ValueSize: 4, + MaxEntries: maxPrograms, + Name: "cali_progs_egr", + Version: 2, +} + +func NewProgramsMaps() []bpfmaps.Map { + return []bpfmaps.Map{ + NewIngressProgramsMap(), + NewEgressProgramsMap(), + } +} + +func NewIngressProgramsMap() bpfmaps.Map { + return newProgramsMap(IngressProgramsMapParameters, "ingress") +} + +func NewEgressProgramsMap() bpfmaps.Map { + return newProgramsMap(EgressProgramsMapParameters, "egress") +} + +func newProgramsMap(ProgramsMapParameters bpfmaps.MapParameters, expectedAttachType string) bpfmaps.Map { return &ProgramsMap{ - PinnedMap: bpfmaps.NewPinnedMap(ProgramsMapParameters), - programs: make(map[AttachType]*program), + PinnedMap: bpfmaps.NewPinnedMap(ProgramsMapParameters), + programs: make(map[AttachType]*program), + expectedAttachType: expectedAttachType, } } @@ -130,7 +216,7 @@ func NewXDPProgramsMap() bpfmaps.Map { } } -func (pm *ProgramsMap) LoadObj(at AttachType) (Layout, error) { +func (pm *ProgramsMap) LoadObj(at AttachType, progType string) (Layout, error) { file := ObjectFile(at) if file == "" { return nil, fmt.Errorf("no object for attach type %+v", at) @@ -148,13 +234,13 @@ func (pm *ProgramsMap) LoadObj(at AttachType) (Layout, error) { var err error if pi.layout == nil { - la, err := pm.loadObj(at, path.Join(bpfdefs.ObjectDir, file)) + la, err := pm.loadObj(at, path.Join(bpfdefs.ObjectDir, file), progType) if err != nil && strings.Contains(file, "_co-re") { log.WithError(err).Warn("Failed to load CO-RE object, kernel too old? Falling back to non-CO-RE.") file := strings.ReplaceAll(file, "_co-re", "") // Skip trying the same file again, as it will fail with the same error. SetObjectFile(at, file) - la, err = pm.loadObj(at, path.Join(bpfdefs.ObjectDir, file)) + la, err = pm.loadObj(at, path.Join(bpfdefs.ObjectDir, file), progType) } if err == nil { log.WithField("layout", la).Debugf("Loaded generic object file %s", file) @@ -179,36 +265,88 @@ func (pm *ProgramsMap) getOrCreateProgramInfo(at AttachType) *program { return pi } -func (pm *ProgramsMap) loadObj(at AttachType, file string) (Layout, error) { +func (pm *ProgramsMap) loadObj(at AttachType, file, progAttachType string) (Layout, error) { obj, err := libbpf.OpenObject(file) if err != nil { return nil, fmt.Errorf("file %s: %w", file, err) } + if err := pm.configureMapsAndPrograms(obj, file, progAttachType); err != nil { + return nil, err + } + + if !at.hasIPDefrag() { + // Disable autoload for the IP defrag program + obj.SetProgramAutoload("calico_tc_skb_ipv4_frag", false) + } + skipIPDefrag := false + if err := obj.Load(); err != nil { + // If load fails and this attach type has IP defrag, try loading without the IP defrag program + if at.hasIPDefrag() { + log.WithError(err).Warn("Failed to load object with IP defrag program, retrying without it") + // Close the failed object and reopen + obj.Close() + obj, err = libbpf.OpenObject(file) + if err != nil { + return nil, fmt.Errorf("file %s: %w", file, err) + } + + // Re-configure maps + if err := pm.configureMapsAndPrograms(obj, file, progAttachType); err != nil { + return nil, err + } + + // Disable autoload for the IP defrag program + obj.SetProgramAutoload("calico_tc_skb_ipv4_frag", false) + skipIPDefrag = true + + // Try loading again + if err := obj.Load(); err != nil { + return nil, fmt.Errorf("error loading program: %w", err) + } + log.WithField("attach type", at). + Warn("Object loaded without IP defrag - processing of fragmented packets will not be supported") + } else { + return nil, fmt.Errorf("error loading program: %w", err) + } + } + + layout, err := pm.allocateLayout(at, obj, skipIPDefrag) + log.WithError(err).WithField("layout", layout).Debugf("load generic object file %s", file) + + return layout, err +} + +func (pm *ProgramsMap) configureMapsAndPrograms(obj *libbpf.Obj, file, progAttachType string) error { for m, err := obj.FirstMap(); m != nil && err == nil; m, err = m.NextMap() { mapName := m.Name() - if strings.HasPrefix(mapName, ".rodata") { + if strings.Contains(mapName, ".rodata") { continue } if err := pm.setMapSize(m); err != nil { - return nil, fmt.Errorf("error setting map size %s : %w", mapName, err) + return fmt.Errorf("error setting map size %s : %w", mapName, err) } if err := m.SetPinPath(path.Join(bpfdefs.GlobalPinDir, mapName)); err != nil { - return nil, fmt.Errorf("error pinning map %s: %w", mapName, err) + return fmt.Errorf("error pinning map %s: %w", mapName, err) } log.Debugf("map %s k %d v %d pinned to %s for generic object file %s", mapName, m.KeySize(), m.ValueSize(), path.Join(bpfdefs.GlobalPinDir, mapName), file) } - if err := obj.Load(); err != nil { - return nil, fmt.Errorf("error loading program: %w", err) + if progAttachType == "TCX" { + for prog, err := obj.FirstProgram(); prog != nil && err == nil; prog, err = prog.NextProgram() { + attachType := libbpf.AttachTypeTcxEgress + if pm.expectedAttachType == "ingress" { + attachType = libbpf.AttachTypeTcxIngress + } + if err := obj.SetAttachType(prog.Name(), attachType); err != nil { + return fmt.Errorf("error setting attach type for program %s: %w", prog.Name(), err) + } + } } - layout, err := pm.allocateLayout(at, obj) - log.WithError(err).WithField("layout", layout).Debugf("load generic object file %s", file) - - return layout, err + return nil } func (pm *ProgramsMap) setMapSize(m *libbpf.Map) error { @@ -218,43 +356,31 @@ func (pm *ProgramsMap) setMapSize(m *libbpf.Map) error { return nil } -func (pm *ProgramsMap) allocateLayout(at AttachType, obj *libbpf.Obj) (Layout, error) { +func (pm *ProgramsMap) allocateLayout(at AttachType, obj *libbpf.Obj, skipIPDefrag bool) (Layout, error) { mapName := pm.GetName() l := make(Layout) offset := 0 - subs := tcSubProgNames - if at.Hook == XDP { - subs = xdpSubProgNames - } else if at.LogLevel == "debug" { + // Debug programs for TC hooks use a different offset + if at.Hook != XDP && at.LogLevel == "debug" { offset = int(SubProgTCMainDebug) } - for idx, subprog := range subs { - if subprog == "" { - continue - } - - if SubProg(idx) == SubProgTCHostCtConflict && !at.hasHostConflictProg() { - continue - } - - if SubProg(idx) == SubProgIPFrag && !at.hasIPDefrag() { - continue - } + applicableProgs := GetApplicableSubProgs(at, skipIPDefrag) + for _, progInfo := range applicableProgs { pmIdx := pm.allocIdx() - err := obj.UpdateJumpMap(mapName, subprog, pmIdx) + err := obj.UpdateJumpMap(mapName, progInfo.Name, pmIdx) if err != nil { return nil, fmt.Errorf("error updating programs map with %s/%s at %d: %w", - ObjectFile(at), subprog, pmIdx, err) + ObjectFile(at), progInfo.Name, pmIdx, err) } - log.Debugf("generic file %s prog %s loaded at %d", ObjectFile(at), subprog, pmIdx) + log.Debugf("generic file %s prog %s loaded at %d", ObjectFile(at), progInfo.Name, pmIdx) - i := idx + offset - if SubProg(idx) == SubProgTCPolicy { - i = idx // Debug programs share the same policy + i := progInfo.Index + offset + if progInfo.SubProg == SubProgTCPolicy { + i = progInfo.Index // Debug programs share the same policy } l[SubProg(i)] = pmIdx } diff --git a/felix/bpf/ipfrags/map.go b/felix/bpf/ipfrags/map.go index 38bc5ba15e8..5021ea24157 100644 --- a/felix/bpf/ipfrags/map.go +++ b/felix/bpf/ipfrags/map.go @@ -32,8 +32,10 @@ var MapParams = maps.MapParameters{ } const ( - KeySize = 12 - ValueSize = 2 + 2 + 4 + 1504 + KeySize = 12 + ValueSize = 2 + 2 + 4 + 1504 + KeySizeFwd = 16 + ValueSizeFwd = 8 ) func Map() maps.Map { @@ -52,3 +54,16 @@ var MapParameters = maps.MapParameters{ func MapTmp() maps.Map { return maps.NewPinnedMap(MapParameters) } + +var FwdMapParams = maps.MapParameters{ + Type: "lru_hash", + KeySize: KeySizeFwd, + ValueSize: ValueSizeFwd, + MaxEntries: 10000, + Name: "cali_v4_frgfwd", + Version: 3, +} + +func FwdMap() maps.Map { + return maps.NewPinnedMap(FwdMapParams) +} diff --git a/felix/bpf/ipsets/ipsets.go b/felix/bpf/ipsets/ipsets.go index e300091bc81..f0b72ae66e6 100644 --- a/felix/bpf/ipsets/ipsets.go +++ b/felix/bpf/ipsets/ipsets.go @@ -316,16 +316,17 @@ func (m *bpfIPSets) ApplyUpdates(_ ipsets.UpdateListener) { } } - m.dirtyIPSetIDs.Iter(func(setID uint64) error { + for setID := range m.dirtyIPSetIDs.All() { leaveDirty := false ipSet := m.getExistingIPSet(setID) if ipSet == nil { m.lg.WithField("id", setID).Warn("Couldn't find IP set that was marked as dirty.") m.resyncScheduled = true - return set.RemoveItem + m.dirtyIPSetIDs.Discard(setID) + continue } - ipSet.PendingRemoves.Iter(func(entry IPSetEntryInterface) error { + for entry := range ipSet.PendingRemoves.All() { if debug { m.lg.WithFields(log.Fields{"setID": setID, "entry": entry}).Debug("Removing entry from IP set") } @@ -333,13 +334,13 @@ func (m *bpfIPSets) ApplyUpdates(_ ipsets.UpdateListener) { if err != nil { m.lg.WithFields(log.Fields{"setID": setID, "entry": entry}).WithError(err).Error("Failed to remove IP set entry") leaveDirty = true - return nil + continue } numDels++ - return set.RemoveItem - }) + ipSet.PendingRemoves.Discard(entry) + } - ipSet.PendingAdds.Iter(func(entry IPSetEntryInterface) error { + for entry := range ipSet.PendingAdds.All() { if debug { m.lg.WithFields(log.Fields{"setID": setID, "entry": entry}).Debug("Adding entry to IP set") } @@ -347,16 +348,16 @@ func (m *bpfIPSets) ApplyUpdates(_ ipsets.UpdateListener) { if err != nil { m.lg.WithFields(log.Fields{"setID": setID, "entry": entry}).WithError(err).Error("Failed to add IP set entry") leaveDirty = true - return nil + continue } numAdds++ - return set.RemoveItem - }) + ipSet.PendingAdds.Discard(entry) + } if leaveDirty { m.lg.WithField("setID", setID).Debug("IP set still dirty, queueing resync") m.resyncScheduled = true - return nil + continue } if ipSet.Deleted { @@ -365,8 +366,8 @@ func (m *bpfIPSets) ApplyUpdates(_ ipsets.UpdateListener) { } m.lg.WithField("setID", setID).Debug("IP set is now clean") - return set.RemoveItem - }) + m.dirtyIPSetIDs.Discard(setID) + } duration := time.Since(startTime) if numDels > 0 || numAdds > 0 { @@ -448,10 +449,9 @@ func (m *bpfIPSet) ReplaceMembers(members []string, protoIPSetMemberToBPFEntry f } func (m *bpfIPSet) RemoveAll() { - m.DesiredEntries.Iter(func(entry IPSetEntryInterface) error { + for entry := range m.DesiredEntries.All() { m.RemoveMember(entry) - return nil - }) + } } func (m *bpfIPSet) AddMembers(members []string, protoIPSetMemberToBPFEntry func(uint64, string) IPSetEntryInterface) { diff --git a/felix/bpf/ipsets/map.go b/felix/bpf/ipsets/map.go index 3d271ca75af..67932adf290 100644 --- a/felix/bpf/ipsets/map.go +++ b/felix/bpf/ipsets/map.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows // Copyright (c) 2020 Tigera, Inc. All rights reserved. // @@ -175,11 +174,11 @@ func MapMemIter(m MapMem) func(k, v []byte) { } func (m MapMem) String() string { - var out string + var out strings.Builder for k := range m { - out += k.String() + "\n" + out.WriteString(k.String() + "\n") } - return out + return out.String() } diff --git a/felix/bpf/ipsets/map6.go b/felix/bpf/ipsets/map6.go index 1648c7bc170..93403d98a7d 100644 --- a/felix/bpf/ipsets/map6.go +++ b/felix/bpf/ipsets/map6.go @@ -157,11 +157,11 @@ func MapMemV6Iter(m MapMemV6) func(k, v []byte) { } func (m MapMemV6) String() string { - var out string + var out strings.Builder for k := range m { - out += k.String() + "\n" + out.WriteString(k.String() + "\n") } - return out + return out.String() } diff --git a/felix/bpf/jump/map.go b/felix/bpf/jump/map.go index c2f46ab1062..16130466246 100644 --- a/felix/bpf/jump/map.go +++ b/felix/bpf/jump/map.go @@ -36,17 +36,29 @@ const ( XDPMaxEntries = XDPMaxEntryPoints * MaxSubPrograms ) -var MapParameters = maps.MapParameters{ +var IngressMapParameters = maps.MapParameters{ Type: "prog_array", KeySize: 4, ValueSize: 4, MaxEntries: TCMaxEntries, - Name: "cali_jump", - Version: 3, + Name: "cali_jump_ing", + Version: 2, +} + +var EgressMapParameters = maps.MapParameters{ + Type: "prog_array", + KeySize: 4, + ValueSize: 4, + MaxEntries: TCMaxEntries, + Name: "cali_jump_egr", + Version: 2, } -func Map() maps.Map { - return maps.NewPinnedMap(MapParameters) +func Maps() []maps.Map { + return []maps.Map{ + maps.NewPinnedMap(IngressMapParameters), + maps.NewPinnedMap(EgressMapParameters), + } } var XDPMapParameters = maps.MapParameters{ diff --git a/felix/bpf/legacy/cleanup.go b/felix/bpf/legacy/cleanup.go index 3f0d9cb1727..06d0dc55c19 100644 --- a/felix/bpf/legacy/cleanup.go +++ b/felix/bpf/legacy/cleanup.go @@ -132,12 +132,11 @@ func CleanUpMaps() { log.WithError(err).Error("Error while looking for maps.") } - emptyAutoDirs.Iter(func(p string) error { + for p := range emptyAutoDirs.All() { log.WithField("path", p).Debug("Removing empty dir.") err := os.Remove(p) if err != nil { log.WithError(err).Error("Error while removing empty dir.") } - return nil - }) + } } diff --git a/felix/bpf/libbpf/libbpf.go b/felix/bpf/libbpf/libbpf.go index 25f4a81a82f..7f8e103d19e 100644 --- a/felix/bpf/libbpf/libbpf.go +++ b/felix/bpf/libbpf/libbpf.go @@ -47,6 +47,11 @@ type Map struct { bpfObj *C.struct_bpf_object } +type Program struct { + bpfProg *C.struct_bpf_program + bpfObj *C.struct_bpf_object +} + type QdiskHook string const ( @@ -151,6 +156,14 @@ func (o *Obj) Load() error { return nil } +// SetProgramAutoload sets whether a program should be automatically loaded. +// When set to false, the program will not be loaded when Load() is called. +func (o *Obj) SetProgramAutoload(progName string, autoload bool) { + cProgName := C.CString(progName) + defer C.free(unsafe.Pointer(cProgName)) + C.bpf_set_program_autoload(o.obj, cProgName, C.bool(autoload)) +} + // FirstMap returns first bpf map of the object. // Returns error if the map is nil. func (o *Obj) FirstMap() (*Map, error) { @@ -174,6 +187,35 @@ func (m *Map) NextMap() (*Map, error) { return &Map{bpfMap: bpfMap, bpfObj: m.bpfObj}, nil } +func (o *Obj) FirstProgram() (*Program, error) { + bpfProg, err := C.bpf_object__next_program(o.obj, nil) + if bpfProg == nil || err != nil { + return nil, fmt.Errorf("error getting first program %w", err) + } + return &Program{bpfProg: bpfProg, bpfObj: o.obj}, nil +} + +func (p *Program) NextProgram() (*Program, error) { + { + bpfProg, err := C.bpf_object__next_program(p.bpfObj, p.bpfProg) + if err != nil { + return nil, fmt.Errorf("error getting next program %w", err) + } + if bpfProg == nil { + return nil, nil + } + return &Program{bpfProg: bpfProg, bpfObj: p.bpfObj}, nil + } +} + +func (p *Program) Name() string { + name := C.bpf_program__name(p.bpfProg) + if name == nil { + return "" + } + return C.GoString(name) +} + func (o *Obj) ProgramFD(secname string) (int, error) { cSecName := C.CString(secname) defer C.free(unsafe.Pointer(cSecName)) @@ -565,6 +607,7 @@ const ( AttachTypeTcxIngress uint32 = C.BPF_TCX_INGRESS AttachTypeTcxEgress uint32 = C.BPF_TCX_EGRESS + AttachTypeXDP uint32 = C.BPF_XDP ) func (t *TcGlobalData) Set(m *Map) error { @@ -607,6 +650,7 @@ func (t *TcGlobalData) Set(m *Map) error { &cJumps[0], // it is safe because we hold the reference here until we return. &cJumpsV6[0], C.short(t.DSCP), + C.uint(t.MaglevLUTSize), ) return err diff --git a/felix/bpf/libbpf/libbpf_api.h b/felix/bpf/libbpf/libbpf_api.h index d7046b05bd4..9621d1397bc 100644 --- a/felix/bpf/libbpf/libbpf_api.h +++ b/felix/bpf/libbpf/libbpf_api.h @@ -278,7 +278,8 @@ void bpf_tc_set_globals(struct bpf_map *map, uint log_filter_jmp, uint *jumps, uint *jumps6, - short dscp) + short dscp, + uint maglev_lut_size) { struct cali_tc_global_data v4 = { .tunnel_mtu = tmtu, @@ -294,6 +295,7 @@ void bpf_tc_set_globals(struct bpf_map *map, .overlay_tunnel_id = overlay_tunnel_id, .log_filter_jmp = log_filter_jmp, .dscp = dscp, + .maglev_lut_size = maglev_lut_size, }; strncpy(v4.iface_name, iface_name, sizeof(v4.iface_name)); @@ -515,3 +517,16 @@ int create_bpf_map(enum bpf_map_type type, unsigned int key_size, unsigned int v } return fd; } + +void bpf_set_program_autoload(struct bpf_object *obj, const char *progName, bool autoload) +{ + struct bpf_program *prog = bpf_object__find_program_by_name(obj, progName); + if (prog == NULL) { + errno = ENOENT; + return; + } + int ret = bpf_program__set_autoload(prog, autoload); + if (ret) { + set_errno(ret); + } +} diff --git a/felix/bpf/libbpf/libbpf_common.go b/felix/bpf/libbpf/libbpf_common.go index 80b927c10b4..67f70ef378e 100644 --- a/felix/bpf/libbpf/libbpf_common.go +++ b/felix/bpf/libbpf/libbpf_common.go @@ -47,7 +47,8 @@ type TcGlobalData struct { HostTunnelIPv6 [16]byte JumpsV6 [40]uint32 - DSCP int8 + DSCP int8 + MaglevLUTSize uint32 } type XDPGlobalData struct { diff --git a/felix/bpf/libbpf/libbpf_stub.go b/felix/bpf/libbpf/libbpf_stub.go index 2fced32fb0c..6f13e68cacd 100644 --- a/felix/bpf/libbpf/libbpf_stub.go +++ b/felix/bpf/libbpf/libbpf_stub.go @@ -29,6 +29,9 @@ type Map struct { type Link struct { } +type Program struct { +} + func (m *Map) Name() string { panic("LIBBPF syscall stub") } @@ -57,6 +60,10 @@ func (o *Obj) Load() error { panic("LIBBPF syscall stub") } +func (o *Obj) SetProgramAutoload(progName string, autoload bool) { + panic("LIBBPF syscall stub") +} + func (o *Obj) FirstMap() (*Map, error) { panic("LIBBPF syscall stub") } @@ -73,6 +80,18 @@ func (m *Map) NextMap() (*Map, error) { panic("LIBBPF syscall stub") } +func (p *Program) NextProgram() (*Program, error) { + panic("LIBBPF syscall stub") +} + +func (o *Obj) FirstProgram() (*Program, error) { + panic("LIBBPF syscall stub") +} + +func (p *Program) Name() string { + panic("LIBBPF syscall stub") +} + func QueryClassifier(ifindex, handle, pref int, ingress bool) (int, error) { panic("LIBBPF syscall stub") } @@ -186,6 +205,7 @@ const ( GlobalsEgressPacketRateConfigured uint32 = 12345 AttachTypeTcxIngress uint32 = 12345 AttachTypeTcxEgress uint32 = 12345 + AttachTypeXDP uint32 = 12345 ) func (m *Map) SetSize(size int) error { diff --git a/felix/bpf/maps/maps.go b/felix/bpf/maps/maps.go index e15a68e3fa4..87db6204b96 100644 --- a/felix/bpf/maps/maps.go +++ b/felix/bpf/maps/maps.go @@ -15,6 +15,7 @@ package maps import ( + "errors" "fmt" "os" "path" @@ -105,6 +106,7 @@ type Map interface { // Size returns the maximun number of entries the table can hold. Size() int + BatchUpdate(ks, vs [][]byte, flags uint64) (int, error) } type MapWithExistsCheck interface { @@ -342,7 +344,7 @@ func (b *PinnedMap) Iter(f IterCallback) error { if b.perCPU { valueSize = b.ValueSize * NumPossibleCPUs() } - it, err := NewIterator(b.MapFD(), b.KeySize, valueSize, b.MaxEntries) + it, err := NewIterator(b.MapFD(), b.KeySize, valueSize, b.MaxEntries, isBatchOpsSupported()) if err != nil { return fmt.Errorf("failed to create BPF map iterator: %w", err) } @@ -421,7 +423,7 @@ func (b *PinnedMap) BatchUpdate(ks, vs [][]byte, flags uint64) (int, error) { k := make([]byte, 0, count*len(ks[0])) v := make([]byte, 0, count*len(vs[0])) - for i := 0; i < count; i++ { + for i := range count { k = append(k, ks[i]...) v = append(v, vs[i]...) } @@ -459,7 +461,7 @@ func (b *PinnedMap) BatchDelete(ks [][]byte, flags uint64) (int, error) { k := make([]byte, 0, count*len(ks[0])) - for i := 0; i < count; i++ { + for i := range count { k = append(k, ks[i]...) } @@ -481,7 +483,7 @@ func (b *PinnedMap) updateDeltaEntries() error { numEntriesCopied := 0 mapMem := make(map[string]struct{}) - it, err := NewIterator(b.oldfd, b.KeySize, b.ValueSize, b.oldSize) + it, err := NewIterator(b.oldfd, b.KeySize, b.ValueSize, b.oldSize, isBatchOpsSupported()) if err != nil { return fmt.Errorf("failed to create BPF map iterator: %w", err) } @@ -530,7 +532,7 @@ func (b *PinnedMap) updateDeltaEntries() error { func (b *PinnedMap) copyFromOldMap() error { numEntriesCopied := 0 mapMem := make(map[string]struct{}) - it, err := NewIterator(b.oldfd, b.KeySize, b.ValueSize, b.oldSize) + it, err := NewIterator(b.oldfd, b.KeySize, b.ValueSize, b.oldSize, isBatchOpsSupported()) if err != nil { return fmt.Errorf("failed to create BPF map iterator: %w", err) } @@ -726,6 +728,8 @@ func (b *PinnedMap) EnsureExists() error { objName = "ipv6_map_stub.o" case strings.HasPrefix(b.Name, "xdp_cali_"): objName = "xdp_map_stub.o" + case strings.HasPrefix(b.Name, "cali_progs_ing"): + objName = "common_map_stub_ing.o" case strings.HasPrefix(b.Name, "cali_"): objName = "common_map_stub.o" } @@ -976,15 +980,17 @@ func (m *TypedMap[K, V]) BatchUpdate(ks []K, vs []V) (int, error) { } if b, ok := m.untypedMap.(RawBatchOps); ok { - k := make([]byte, 0, count*b.GetKeySize()) - v := make([]byte, 0, count*b.GetValueSize()) + if isBatchOpsSupported() { + k := make([]byte, 0, count*b.GetKeySize()) + v := make([]byte, 0, count*b.GetValueSize()) - for i := 0; i < count; i++ { - k = append(k, ks[i].AsBytes()...) - v = append(v, vs[i].AsBytes()...) - } + for i := range count { + k = append(k, ks[i].AsBytes()...) + v = append(v, vs[i].AsBytes()...) + } - return b.BatchUpdateRaw(k, v, count, 0) + return b.BatchUpdateRaw(k, v, count, 0) + } } cnt := 0 @@ -1029,13 +1035,15 @@ func (m *TypedMap[K, V]) BatchDelete(ks []K) (int, error) { } if b, ok := m.untypedMap.(RawBatchOps); ok { - k := make([]byte, 0, count*b.GetKeySize()) + if isBatchOpsSupported() { + k := make([]byte, 0, count*b.GetKeySize()) - for i := 0; i < count; i++ { - k = append(k, ks[i].AsBytes()...) - } + for i := range count { + k = append(k, ks[i].AsBytes()...) + } - return b.BatchDeleteRaw(k, count, 0) + return b.BatchDeleteRaw(k, count, 0) + } } cnt := 0 @@ -1072,3 +1080,32 @@ func NewTypedMap[K Key, V Value](m MapWithExistsCheck, kConstructor func([]byte) vConstructor: vConstructor, } } + +// isBatchOpsSupported checks if the kernel supports batch map operations. +// It creates a test LPM_TRIE map and attempts a batch lookup operation. +// Older kernels (< 5.13) return errno 524 (ENOTSUPP) for batch operations, +// which indicates that batch ops are not supported. LPM_TRIE is used as previous +// kernel versions supported batch ops for few other map types and support for +// LPM_TRIE was added in 5.13. +var isBatchOpsSupported = sync.OnceValue(func() bool { + // Check if the kernel supports batch operations. + fd, err := createMap("testMap", unix.BPF_MAP_TYPE_LPM_TRIE, 8, 4, 1, unix.BPF_F_NO_PREALLOC) + if err != nil { + log.WithError(err).Warn("Failed to create test map for batch ops support check") + return false + } + defer func() { + err := unix.Close(int(fd)) + if err != nil { + log.WithError(err).Warn("Failed to close test map fd") + } + }() + err = batchLookup(fd, 8, 4) + // kernel errno 524 indicates that batch ops are not supported. + if err != nil && errors.Is(err, syscall.Errno(524)) { + return false + } + + // Any other error (including ENOENT for empty map) or success means batch ops are supported. + return true +}) diff --git a/felix/bpf/maps/syscall.go b/felix/bpf/maps/syscall.go index 3622404fd8f..b643ab52884 100644 --- a/felix/bpf/maps/syscall.go +++ b/felix/bpf/maps/syscall.go @@ -139,16 +139,11 @@ func checkMapIfDebug(mapFD FD, keySize, valueSize int) error { } func GetMapInfo(fd FD) (*MapInfo, error) { - bpfAttr := C.bpf_maps_attr_alloc() - defer C.free(unsafe.Pointer(bpfAttr)) - var bpfMapInfo *C.struct_bpf_map_info = (*C.struct_bpf_map_info)(C.malloc(C.sizeof_struct_bpf_map_info)) - defer C.free(unsafe.Pointer(bpfMapInfo)) - - C.bpf_maps_attr_setup_get_info(bpfAttr, C.uint(fd), C.sizeof_struct_bpf_map_info, unsafe.Pointer(bpfMapInfo)) - _, _, errno := unix.Syscall(unix.SYS_BPF, unix.BPF_OBJ_GET_INFO_BY_FD, uintptr(unsafe.Pointer(bpfAttr)), C.sizeof_union_bpf_attr) - + var bpfMapInfo C.struct_bpf_map_info + infoLen := C.__u32(unsafe.Sizeof(bpfMapInfo)) + errno := C.bpf_map_get_info_by_fd(C.int(fd), &bpfMapInfo, &infoLen) if errno != 0 { - return nil, errno + return nil, unix.Errno(errno) } return &MapInfo{ Type: int(bpfMapInfo._type), @@ -187,6 +182,7 @@ func DeleteMapEntryIfExists(mapFD FD, k []byte) error { // Batch size established by trial and error; 8-32 seemed to be the sweet spot for the conntrack map. const IteratorNumKeys = 1024 +const IteratorNumKeysSlow = 16 // align64 rounds up the given size to the nearest 8-bytes. func align64(size int) int { @@ -196,7 +192,7 @@ func align64(size int) int { return size + (8 - (size % 8)) } -func NewIterator(mapFD FD, keySize, valueSize, maxEntries int) (*Iterator, error) { +func NewIterator(mapFD FD, keySize, valueSize, maxEntries int, isBatchOpsSupported bool) (*Iterator, error) { err := checkMapIfDebug(mapFD, keySize, valueSize) if err != nil { return nil, err @@ -206,15 +202,16 @@ func NewIterator(mapFD FD, keySize, valueSize, maxEntries int) (*Iterator, error valueStride := align64(valueSize) m := &Iterator{ - mapFD: mapFD, - maxEntries: maxEntries, - keySize: keySize, - valueSize: valueSize, - keyStride: keyStride, - valueStride: valueStride, - keysBufSize: keyStride * IteratorNumKeys, - valuesBufSize: valueStride * IteratorNumKeys, - keysValues: make(chan keysValues), + mapFD: mapFD, + maxEntries: maxEntries, + keySize: keySize, + valueSize: valueSize, + keyStride: keyStride, + valueStride: valueStride, + keysBufSize: keyStride * IteratorNumKeys, + valuesBufSize: valueStride * IteratorNumKeys, + keysValues: make(chan keysValues), + batchLookupSupported: isBatchOpsSupported, } m.cancelCtx, m.cancelCB = context.WithCancel(context.Background()) @@ -241,6 +238,21 @@ func NewIterator(mapFD FD, keySize, valueSize, maxEntries int) (*Iterator, error return m, nil } +func (m *Iterator) slowIter(tokenIn, tokenC unsafe.Pointer) (int, error) { + rc := C.bpf_maps_map_load_multi(C.uint(m.mapFD), tokenIn, C.int(IteratorNumKeysSlow), + C.int(m.keyStride), m.keysBuff, C.int(m.valueStride), m.valuesBuff) + if rc < 0 { + return 0, unix.Errno(-rc) + } + if rc == 0 { + return 0, unix.ENOENT + } + count := int(rc) + offset := (count - 1) * m.keyStride + C.memcpy(tokenC, unsafe.Pointer(uintptr(m.keysBuff)+uintptr(offset)), C.size_t(m.keyStride)) + return count, nil +} + func (m *Iterator) syscallThread() { defer m.wg.Done() @@ -253,8 +265,14 @@ func (m *Iterator) syscallThread() { for { count := IteratorNumKeys - _, err := C.bpf_map_batch_lookup(C.int(m.mapFD), tokenIn, tokenC, m.keysBuff, m.valuesBuff, - (*C.__u32)(unsafe.Pointer(&count)), 0) + var err error + if m.batchLookupSupported { + _, err = C.bpf_map_batch_lookup(C.int(m.mapFD), tokenIn, tokenC, m.keysBuff, m.valuesBuff, + (*C.__u32)(unsafe.Pointer(&count)), 0) + } else { + // Batch ops are not supported. + count, err = m.slowIter(tokenIn, tokenC) + } if err != nil { if errors.Is(err, unix.ENOENT) { if count == 0 { @@ -275,18 +293,24 @@ func (m *Iterator) syscallThread() { } } tokenIn = tokenC - bk := C.GoBytes(m.keysBuff, C.int(m.keysBufSize)) bv := C.GoBytes(m.valuesBuff, C.int(m.valuesBufSize)) + kstride := m.keySize + vstride := m.valueSize + if !m.batchLookupSupported { + kstride = m.keyStride + vstride = m.valueStride + } select { case m.keysValues <- keysValues{ - keys: bk[:count*m.keySize], - values: bv[:count*m.valueSize], + keys: bk[:count*kstride], + values: bv[:count*vstride], count: count, }: case <-m.cancelCtx.Done(): } + } } @@ -319,8 +343,19 @@ func (m *Iterator) Next() (k, v []byte, err error) { m.values = x.values } - k = m.keys[m.entryIdx*m.keySize : (m.entryIdx+1)*m.keySize] - v = m.values[m.entryIdx*m.valueSize : (m.entryIdx+1)*m.valueSize] + kstride := m.keySize + vstride := m.valueSize + if !m.batchLookupSupported { + kstride = m.keyStride + vstride = m.valueStride + } + k = m.keys[m.entryIdx*kstride : (m.entryIdx+1)*kstride] + v = m.values[m.entryIdx*vstride : (m.entryIdx+1)*vstride] + if !m.batchLookupSupported { + // For slow iteration, trim the key and value to their actual sizes. + k = k[:m.keySize] + v = v[:m.valueSize] + } m.entryIdx++ m.numEntriesVisited++ @@ -341,3 +376,32 @@ func (m *Iterator) Close() error { return nil } + +func createMap(name string, mapType, keySize, valueSize, maxEntries, flags uint32) (FD, error) { + cMapName := C.CString(name) + defer C.free(unsafe.Pointer(cMapName)) + + fd, err := C.bpf_create_map(mapType, cMapName, C.__u32(keySize), C.__u32(valueSize), C.__u32(maxEntries), C.__u32(flags)) + if err != nil { + return 0, err + } + return FD(fd), nil +} + +func batchLookup(mapFD FD, keySize, valueSize int) error { + token := make([]byte, keySize) + tokenC := C.CBytes(token) + defer C.free(tokenC) + tokenIn := unsafe.Pointer(nil) + + keyStride := align64(keySize) + valueStride := align64(valueSize) + keysBuff := C.malloc(C.size_t(IteratorNumKeys * keyStride)) + valuesBuff := C.malloc(C.size_t(IteratorNumKeys * valueStride)) + defer C.free(keysBuff) + defer C.free(valuesBuff) + + count := IteratorNumKeys + _, err := C.bpf_map_batch_lookup(C.int(mapFD), tokenIn, tokenC, keysBuff, valuesBuff, (*C.__u32)(unsafe.Pointer(&count)), 0) + return err +} diff --git a/felix/bpf/maps/syscall.h b/felix/bpf/maps/syscall.h index 3df9705b3e3..d33c67b4c51 100644 --- a/felix/bpf/maps/syscall.h +++ b/felix/bpf/maps/syscall.h @@ -146,3 +146,16 @@ void bpf_map_batch_lookup(int fd, void *in_batch, void *out_batch, void *keys, set_errno(bpf_map_lookup_batch(fd, in_batch, out_batch, keys, values, count, &opts)); } + +int bpf_create_map(enum bpf_map_type map_type, + const char *name, + __u32 key_size, + __u32 value_size, + __u32 max_entries, + __u32 map_flags) +{ + DECLARE_LIBBPF_OPTS(bpf_map_create_opts, opts, + .map_flags = map_flags); + + return bpf_map_create(map_type, name, key_size, value_size, max_entries, &opts); +} diff --git a/felix/bpf/maps/syscall_common.go b/felix/bpf/maps/syscall_common.go index ef09d2ec51f..44be172ad3c 100644 --- a/felix/bpf/maps/syscall_common.go +++ b/felix/bpf/maps/syscall_common.go @@ -76,10 +76,11 @@ type Iterator struct { numEntriesVisited int // wg is to sync with the syscall executing thread on close - wg sync.WaitGroup - keysValues chan keysValues - cancelCtx context.Context - cancelCB context.CancelFunc + wg sync.WaitGroup + keysValues chan keysValues + cancelCtx context.Context + cancelCB context.CancelFunc + batchLookupSupported bool } type MapInfo struct { diff --git a/felix/bpf/maps/syscall_stub.go b/felix/bpf/maps/syscall_stub.go index 846b3441355..3e9e128dcdb 100644 --- a/felix/bpf/maps/syscall_stub.go +++ b/felix/bpf/maps/syscall_stub.go @@ -16,7 +16,7 @@ package maps -func NewIterator(mapFD FD, keySize, valueSize, maxEntries int) (*Iterator, error) { +func NewIterator(mapFD FD, keySize, valueSize, maxEntries int, batchLookupSupported bool) (*Iterator, error) { panic("BPF syscall stub") } @@ -70,3 +70,11 @@ func (m *Iterator) Next() (k, v []byte, err error) { func (m *Iterator) Close() error { return nil } + +func createMap(name string, mapType, keySize, valueSize, maxEntries, flags uint32) (FD, error) { + panic("BPF syscall stub") +} + +func batchLookup(mapFD FD, keySize, valueSize int) error { + panic("BPF syscall stub") +} diff --git a/felix/bpf/mock/cleaner.go b/felix/bpf/mock/cleaner.go index fba94e2a38a..fecb48e429e 100644 --- a/felix/bpf/mock/cleaner.go +++ b/felix/bpf/mock/cleaner.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows // Copyright (c) 2025 Tigera, Inc. All rights reserved. // diff --git a/felix/bpf/mock/map.go b/felix/bpf/mock/map.go index 187485d0457..957955f2e7f 100644 --- a/felix/bpf/mock/map.go +++ b/felix/bpf/mock/map.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows // Copyright (c) 2020-2025 Tigera, Inc. All rights reserved. // @@ -19,6 +18,7 @@ package mock import ( "fmt" + maps0 "maps" "sync" "github.com/sirupsen/logrus" @@ -97,9 +97,7 @@ func (m *Map) copyContents() map[string]string { m.Lock() defer m.Unlock() contentsCopy := map[string]string{} - for k, v := range m.Contents { - contentsCopy[k] = v - } + maps0.Copy(contentsCopy, m.Contents) return contentsCopy } @@ -140,6 +138,19 @@ func (m *Map) UpdateWithFlags(k, v []byte, flags int) error { return m.updateUnlocked(k, v) } +func (m *Map) BatchUpdate(k, v [][]byte, flags uint64) (int, error) { + m.Lock() + defer m.Unlock() + count := 0 + for i := range k { + if err := m.updateUnlocked(k[i], v[i]); err != nil { + return count, err + } + count++ + } + return count, nil +} + func (m *Map) Get(k []byte) ([]byte, error) { m.Lock() defer m.Unlock() @@ -263,6 +274,10 @@ func (*DummyMap) Update(k, v []byte) error { return nil } +func (*DummyMap) BatchUpdate(k, v [][]byte, flags uint64) (int, error) { + return 0, nil +} + func (*DummyMap) Get(k []byte) ([]byte, error) { return nil, unix.ENOENT } diff --git a/felix/bpf/nat/connecttime.go b/felix/bpf/nat/connecttime.go index 8673dfea360..e1917399fdc 100644 --- a/felix/bpf/nat/connecttime.go +++ b/felix/bpf/nat/connecttime.go @@ -33,27 +33,47 @@ import ( ) const ( - ProgIndexCTLBConnectV6 = iota - ProgIndexCTLBSendV6 - ProgIndexCTLBRecvV6 + MapIndexCTLBConnectV6 = iota + MapIndexCTLBSendV6 + MapIndexCTLBRecvV6 ) var ctlbProgToIndex = map[string]int{ - "calico_connect_v6": ProgIndexCTLBConnectV6, - "calico_sendmsg_v6": ProgIndexCTLBSendV6, - "calico_recvmsg_v6": ProgIndexCTLBRecvV6, + "calico_connect_v6": MapIndexCTLBConnectV6, + "calico_sendmsg_v6": MapIndexCTLBSendV6, + "calico_recvmsg_v6": MapIndexCTLBRecvV6, } -var ProgramsMapParameters = maps.MapParameters{ +var ConnMapParameters = maps.MapParameters{ Type: "prog_array", KeySize: 4, ValueSize: 4, - MaxEntries: 3, - Name: "cali_ctlb_progs", + MaxEntries: 1, + Name: "cali_ctlb_conn", } -func ProgramsMap() maps.Map { - return maps.NewPinnedMap(ProgramsMapParameters) +var SendMapParameters = maps.MapParameters{ + Type: "prog_array", + KeySize: 4, + ValueSize: 4, + MaxEntries: 1, + Name: "cali_ctlb_send", +} + +var RecvMapParameters = maps.MapParameters{ + Type: "prog_array", + KeySize: 4, + ValueSize: 4, + MaxEntries: 1, + Name: "cali_ctlb_recv", +} + +func ProgramsMaps() []maps.Map { + return []maps.Map{ + maps.NewPinnedMap(ConnMapParameters), + maps.NewPinnedMap(SendMapParameters), + maps.NewPinnedMap(RecvMapParameters), + } } func RemoveConnectTimeLoadBalancer(ipv4Enabled bool, cgroupv2 string) error { @@ -69,18 +89,22 @@ func RemoveConnectTimeLoadBalancer(ipv4Enabled bool, cgroupv2 string) error { pinDir := path.Join(bpfMount, bpfdefs.CtlbPinDir) defer bpf.CleanUpCalicoPins(pinDir) - ctlbProgsMap := ProgramsMap() - if err := ctlbProgsMap.EnsureExists(); err != nil { - return fmt.Errorf("failed to create ctlb jump map: %w", err) + ctlbProgsMap := ProgramsMaps() + for _, index := range ctlbProgToIndex { + if err := ctlbProgsMap[index].EnsureExists(); err != nil { + return fmt.Errorf("failed to create ctlb jump map: %w", err) + } } for _, index := range ctlbProgToIndex { - err := ctlbProgsMap.Delete(jump.Key(index)) + err := ctlbProgsMap[index].Delete(jump.Key(0)) if err != nil && !os.IsNotExist(err) { log.Errorf("failed to delete the ctlb jump map entry: %s", err) } } - defer ctlbProgsMap.Close() - defer os.Remove(ctlbProgsMap.Path()) + for _, index := range ctlbProgToIndex { + ctlbProgsMap[index].Close() + os.Remove(ctlbProgsMap[index].Path()) + } if err := detachCtlbPrograms(ipv4Enabled, pinDir, cgroupv2); err != nil { return err @@ -182,23 +206,21 @@ func attachProgram(name, ipver, bpfMount, cgroupPath string, udpNotSeen time.Dur return nil } -func updateCTLBJumpMap(jumpMap maps.Map, obj *libbpf.Obj) error { - for prog, index := range ctlbProgToIndex { - fd, err := obj.ProgramFD(prog) - if err != nil { - return fmt.Errorf("failed to get prog FD. Program = %s: %w", prog, err) - } +func updateCTLBJumpMap(jumpMap maps.Map, obj *libbpf.Obj, prog string) error { + fd, err := obj.ProgramFD(prog) + if err != nil { + return fmt.Errorf("failed to get prog FD. Program = %s: %w", prog, err) + } - err = maps.UpdateMapEntry(jumpMap.MapFD(), jump.Key(index), jump.Value(uint32(fd))) - if err != nil { - log.WithError(err).Errorf("Failed to update %s map at index %d", prog, index) - return err - } + err = maps.UpdateMapEntry(jumpMap.MapFD(), jump.Key(0), jump.Value(uint32(fd))) + if err != nil { + log.WithError(err).Errorf("Failed to update %s map at index 0", prog) + return err } return nil } -func installCTLB(ipv4Enabled, ipv6Enabled bool, cgroupv2 string, logLevel string, udpNotSeen time.Duration, excludeUDP bool, ctlbProgsMap maps.Map, legacy bool) error { +func installCTLB(ipv4Enabled, ipv6Enabled bool, cgroupv2 string, logLevel string, udpNotSeen time.Duration, excludeUDP bool, ctlbProgsMap []maps.Map, legacy bool) error { bpfMount, err := utils.MaybeMountBPFfs() if err != nil { log.WithError(err).Error("Failed to mount bpffs, unable to do connect-time load balancing") @@ -216,7 +238,7 @@ func installCTLB(ipv4Enabled, ipv6Enabled bool, cgroupv2 string, logLevel string return fmt.Errorf("failed to set-up cgroupv2: %w", err) } - var v4Obj, v46Obj, v6Obj *libbpf.Obj + var v4Obj, v6Obj *libbpf.Obj // Load and attach v4, v46 CTLB program. if ipv4Enabled { @@ -226,7 +248,7 @@ func installCTLB(ipv4Enabled, ipv6Enabled bool, cgroupv2 string, logLevel string } defer v4Obj.Close() - v46Obj, err = loadProgram(logLevel, "46", udpNotSeen, excludeUDP) + v46Obj, err := loadProgram(logLevel, "46", udpNotSeen, excludeUDP) if err != nil { return err } @@ -271,9 +293,11 @@ func installCTLB(ipv4Enabled, ipv6Enabled bool, cgroupv2 string, logLevel string defer v6Obj.Close() // If dual-stack, populate the jump maps with v6 ctlb programs. if ipv4Enabled { - err = updateCTLBJumpMap(ctlbProgsMap, v6Obj) - if err != nil { - return err + for prog, index := range ctlbProgToIndex { + err = updateCTLBJumpMap(ctlbProgsMap[index], v6Obj, prog) + if err != nil { + return err + } } } else { err = attachProgram("connect", "6", pinDir, cgroupPath, udpNotSeen, excludeUDP, v6Obj, legacy) @@ -297,15 +321,15 @@ func installCTLB(ipv4Enabled, ipv6Enabled bool, cgroupv2 string, logLevel string return nil } -func InstallConnectTimeLoadBalancer(ipv4Enabled, ipv6Enabled bool, cgroupv2 string, logLevel string, udpNotSeen time.Duration, excludeUDP bool, ctlbProgramsMap maps.Map) error { +func InstallConnectTimeLoadBalancer(ipv4Enabled, ipv6Enabled bool, cgroupv2 string, logLevel string, udpNotSeen time.Duration, excludeUDP bool, ctlbProgramsMap []maps.Map) error { return installCTLB(ipv4Enabled, ipv6Enabled, cgroupv2, logLevel, udpNotSeen, excludeUDP, ctlbProgramsMap, false) } -func InstallConnectTimeLoadBalancerLegacy(ipv4Enabled, ipv6Enabled bool, cgroupv2 string, logLevel string, udpNotSeen time.Duration, excludeUDP bool, ctlbProgramsMap maps.Map) error { +func InstallConnectTimeLoadBalancerLegacy(ipv4Enabled, ipv6Enabled bool, cgroupv2 string, logLevel string, udpNotSeen time.Duration, excludeUDP bool, ctlbProgramsMap []maps.Map) error { return installCTLB(ipv4Enabled, ipv6Enabled, cgroupv2, logLevel, udpNotSeen, excludeUDP, ctlbProgramsMap, true) } -func ProgFileName(logLevel string, ipver string) string { +func ProgFileName(logLevel, ipver string) string { logLevel = strings.ToLower(logLevel) if logLevel == "off" { logLevel = "no_log" diff --git a/felix/bpf/nat/maps.go b/felix/bpf/nat/maps.go index b9f05b777ac..c1dc0690ebb 100644 --- a/felix/bpf/nat/maps.go +++ b/felix/bpf/nat/maps.go @@ -33,22 +33,26 @@ func init() { maps.SetSize(AffinityMapParameters.VersionedName(), AffinityMapParameters.MaxEntries) maps.SetSize(SendRecvMsgMapParameters.VersionedName(), SendRecvMsgMapParameters.MaxEntries) maps.SetSize(CTNATsMapParameters.VersionedName(), CTNATsMapParameters.MaxEntries) + maps.SetSize(MaglevMapParameters.VersionedName(), MaglevMapParameters.MaxEntries) maps.SetSize(FrontendMapV6Parameters.VersionedName(), FrontendMapV6Parameters.MaxEntries) maps.SetSize(BackendMapV6Parameters.VersionedName(), BackendMapV6Parameters.MaxEntries) maps.SetSize(AffinityMapV6Parameters.VersionedName(), AffinityMapV6Parameters.MaxEntries) maps.SetSize(SendRecvMsgMapV6Parameters.VersionedName(), SendRecvMsgMapV6Parameters.MaxEntries) maps.SetSize(CTNATsMapV6Parameters.VersionedName(), CTNATsMapV6Parameters.MaxEntries) + maps.SetSize(MaglevMapV6Parameters.VersionedName(), MaglevMapV6Parameters.MaxEntries) } -func SetMapSizes(fsize, bsize, asize int) { +func SetMapSizes(fsize, bsize, asize, msize int) { maps.SetSize(FrontendMapParameters.VersionedName(), fsize) maps.SetSize(BackendMapParameters.VersionedName(), bsize) maps.SetSize(AffinityMapParameters.VersionedName(), asize) + maps.SetSize(MaglevMapParameters.VersionedName(), msize) maps.SetSize(FrontendMapV6Parameters.VersionedName(), fsize) maps.SetSize(BackendMapV6Parameters.VersionedName(), bsize) maps.SetSize(AffinityMapV6Parameters.VersionedName(), asize) + maps.SetSize(MaglevMapV6Parameters.VersionedName(), msize) } // struct calico_nat_v4_key { @@ -202,12 +206,14 @@ const ( NATFlgExternalLocal = 0x1 NATFlgInternalLocal = 0x2 NATFlgExclude = 0x4 + NATFlgMaglev = 0x8 ) var flgTostr = map[int]string{ NATFlgExternalLocal: "external-local", NATFlgInternalLocal: "internal-local", NATFlgExclude: "nat-exclude", + NATFlgMaglev: "maglev", } type FrontendValue [frontendValueSize]byte @@ -252,7 +258,7 @@ func (v FrontendValue) FlagsAsString() string { flgs := v.Flags() fstr := "" - for i := 0; i < 32; i++ { + for i := range 32 { flg := uint32(1 << i) if flgs&flg != 0 { fstr += flgTostr[int(flg)] @@ -363,6 +369,127 @@ func BackendValueFromBytes(b []byte) BackendValueInterface { return v } +// struct cali_maglev_key { +// __u32 svc_id; +// __u32 ordinal; // should always be a value of [0..M-1], where M is a very large prime number. -Alex +// }; +const MaglevBackendKeySize = 8 + +type MaglevBackendKey [MaglevBackendKeySize]byte + +func NewMaglevBackendKey(svcID uint32, ordinal uint32) MaglevBackendKey { + var k MaglevBackendKey + binary.LittleEndian.PutUint32(k[0:4], svcID) + binary.LittleEndian.PutUint32(k[4:8], ordinal) + return k +} + +type MaglevBackendKeyInterface interface { + SvcID() uint32 + Ordinal() uint32 + AsBytes() []byte +} + +func NewMaglevBackendKeyIntf(svcID, ordinal uint32) MaglevBackendKeyInterface { + return NewMaglevBackendKey(svcID, uint32(ordinal)) +} + +func (k MaglevBackendKey) SvcID() uint32 { + return binary.LittleEndian.Uint32(k[0:4]) +} + +func (k MaglevBackendKey) Ordinal() uint32 { + return binary.LittleEndian.Uint32(k[4:8]) +} + +func (k MaglevBackendKey) AsBytes() []byte { + return k[:] +} + +func (k MaglevBackendKey) String() string { + svcID := k.SvcID() + ord := k.Ordinal() + return fmt.Sprintf("MaglevBackendKey{SvcID: %d, Ordinal: %d}", svcID, ord) +} + +func MaglevBackendKeyFromBytes(b []byte) MaglevBackendKeyInterface { + var k MaglevBackendKey + copy(k[:], b) + return k +} + +var _ MaglevBackendKeyInterface = MaglevBackendKey{} + +const maglevBackendValueSize = backendValueSize + +var MaglevMapParameters = maps.MapParameters{ + Type: "hash", + KeySize: MaglevBackendKeySize, + ValueSize: maglevBackendValueSize, + MaxEntries: 1009, + Name: "cali_v4_mglv", + Flags: unix.BPF_F_NO_PREALLOC, + Version: 2, +} + +func MaglevMap() maps.MapWithExistsCheck { + return maps.NewPinnedMap(MaglevMapParameters) +} + +type MaglevMapMem map[MaglevBackendKey]BackendValue + +// Equal implements the comparable interface. +func (m MaglevMapMem) Equal(cmp MaglevMapMem) bool { + if len(m) != len(cmp) { + return false + } + + for k, v := range m { + if v2, ok := cmp[k]; !ok || v != v2 { + return false + } + } + + return true +} + +// LoadMaglevMap loads the Maglev NAT map into a go map or returns an error +func LoadMaglevMap(m maps.Map) (MaglevMapMem, error) { + ret := make(MaglevMapMem) + + if err := m.Open(); err != nil { + return nil, err + } + + iterFn := MaglevMapMemIter(ret) + + err := m.Iter(func(k, v []byte) maps.IteratorAction { + iterFn(k, v) + return maps.IterNone + }) + if err != nil { + ret = nil + } + + return ret, err +} + +// MaglevMapMemIter returns maps.MapIter that loads the provided MaglevMapMem +func MaglevMapMemIter(m MaglevMapMem) func(k, v []byte) { + ks := len(MaglevBackendKey{}) + vs := len(BackendValue{}) + + return func(k, v []byte) { + var key MaglevBackendKey + copy(key[:ks], k[:ks]) + + var val BackendValue + copy(val[:vs], v[:vs]) + + m[key] = val + } +} + var FrontendMapParameters = maps.MapParameters{ Type: "lpm_trie", KeySize: frontendKeySize, diff --git a/felix/bpf/nat/maps6.go b/felix/bpf/nat/maps6.go index 1022f17a661..08ed7253503 100644 --- a/felix/bpf/nat/maps6.go +++ b/felix/bpf/nat/maps6.go @@ -66,6 +66,53 @@ const backendKeyV6Size = 8 // }; const backendValueV6Size = 20 +// struct cali_maglev_key { +// __u32 svc_id; +// __u32 ordinal; // should always be a value of [0..M-1], where M is a very large prime number. -Alex +// }; +const maglevBackendKeyV6Size = 8 + +const maglevBackendValueV6Size = backendValueV6Size + +type MaglevBackendKeyV6 [maglevBackendKeyV6Size]byte + +var _ MaglevBackendKeyInterface = MaglevBackendKeyV6{} + +func NewMaglevBackendKeyV6(svcID, ordinal uint32) MaglevBackendKeyV6 { + var k MaglevBackendKeyV6 + binary.LittleEndian.PutUint32(k[0:4], svcID) + binary.LittleEndian.PutUint32(k[4:8], ordinal) + return k +} + +func NewMaglevBackendKeyV6Intf(svcID, ordinal uint32) MaglevBackendKeyInterface { + return NewMaglevBackendKeyV6(svcID, ordinal) +} + +func (k MaglevBackendKeyV6) SvcID() uint32 { + return binary.LittleEndian.Uint32(k[0:4]) +} + +func (k MaglevBackendKeyV6) Ordinal() uint32 { + return binary.LittleEndian.Uint32(k[4:8]) +} + +func (k MaglevBackendKeyV6) AsBytes() []byte { + return k[:] +} + +func (k MaglevBackendKeyV6) String() string { + svcID := k.SvcID() + ord := k.Ordinal() + return fmt.Sprintf("MaglevBackendKeyV6{%d:%d}", svcID, ord) +} + +func MaglevBackendKeyV6FromBytes(b []byte) MaglevBackendKeyInterface { + var k MaglevBackendKeyV6 + copy(k[:], b) + return k +} + // (sizeof(addr) + sizeof(port) + sizeof(proto)) in bits const ZeroCIDRV6PrefixLen = (16 + 2 + 1) * 8 @@ -245,6 +292,73 @@ func BackendMapV6() maps.MapWithExistsCheck { return maps.NewPinnedMap(BackendMapV6Parameters) } +var MaglevMapV6Parameters = maps.MapParameters{ + Type: "hash", + KeySize: maglevBackendKeyV6Size, + ValueSize: maglevBackendValueV6Size, + MaxEntries: 1009, + Name: "cali_v6_mglv", + Flags: unix.BPF_F_NO_PREALLOC, + Version: 2, +} + +func MaglevMapV6() maps.MapWithExistsCheck { + return maps.NewPinnedMap(MaglevMapV6Parameters) +} + +type MaglevMapMemV6 map[MaglevBackendKeyV6]BackendValueV6 + +func (m MaglevMapMemV6) Equal(cmp MaglevMapMemV6) bool { + if len(m) != len(cmp) { + return false + } + + for k, v := range m { + if v2, ok := cmp[k]; !ok || v != v2 { + return false + } + } + + return true +} + +// LoadMaglevMapV6 loads the CH NAT map into a go map or returns an error +func LoadMaglevMapV6(m maps.Map) (MaglevMapMemV6, error) { + ret := make(MaglevMapMemV6) + + if err := m.Open(); err != nil { + return nil, err + } + + iterFn := MaglevMapMemV6Iter(ret) + + err := m.Iter(func(k, v []byte) maps.IteratorAction { + iterFn(k, v) + return maps.IterNone + }) + if err != nil { + ret = nil + } + + return ret, err +} + +// MaglevMapMemV6Iter returns maps.MapIter that loads the provided MaglevMapMemV6 +func MaglevMapMemV6Iter(m MaglevMapMemV6) func(k, v []byte) { + ks := len(MaglevBackendKeyV6{}) + vs := len(BackendValueV6{}) + + return func(k, v []byte) { + var key MaglevBackendKeyV6 + copy(key[:ks], k[:ks]) + + var val BackendValueV6 + copy(val[:vs], v[:vs]) + + m[key] = val + } +} + // NATMapMem represents FrontendMap loaded into memory type MapMemV6 map[FrontendKeyV6]FrontendValueV6 diff --git a/felix/bpf/perf/perf.go b/felix/bpf/perf/perf.go index 4a55305ebc6..5bf7f8fe01c 100644 --- a/felix/bpf/perf/perf.go +++ b/felix/bpf/perf/perf.go @@ -172,7 +172,7 @@ func (p *perf) openPerfRings(cpus, ringSize int) ([]*perfRing, error) { pages := ringSize / pageSize rings := make([]*perfRing, cpus) - for cpu := 0; cpu < cpus; cpu++ { + for cpu := range cpus { var err error rings[cpu], err = newPerfRing(cpu, pages, pageSize, p.watermark, p.watermarkBit) if err != nil { @@ -258,7 +258,7 @@ func (p *perf) poll() error { // Reset quotas p.readyCnt = n - for i := 0; i < n; i++ { + for i := range n { p.ready[i].quota = p.dequeueQuota } @@ -478,7 +478,7 @@ func (p *ePoll) Wait(ready []*perfRing) (int, error) { } // Fill in all now ready rings - for i := 0; i < n; i++ { + for i := range n { ready[i] = p.fd2ring[int(p.events[i].Fd)] } @@ -486,11 +486,11 @@ func (p *ePoll) Wait(ready []*perfRing) (int, error) { } // Option is taken by New -type Option func(interface{}) +type Option func(any) // WithWakeUpBytes sets the wakup to be after reaching a watermark of n bytes. func WithWakeUpBytes(n int) Option { - return func(i interface{}) { + return func(i any) { p := i.(*perf) p.watermark = n p.watermarkBit = true @@ -500,7 +500,7 @@ func WithWakeUpBytes(n int) Option { // WithWakeUpEvents sets the wakup to be after reaching a watermark of n events. // Default is a single event. func WithWakeUpEvents(n int) Option { - return func(i interface{}) { + return func(i any) { p := i.(*perf) p.watermark = n p.watermarkBit = false @@ -510,7 +510,7 @@ func WithWakeUpEvents(n int) Option { // WithDequeueQuota changes how many messages are dequeued from a ring before we // switch to another one. Must be at least 1 func WithDequeueQuota(n int) Option { - return func(i interface{}) { + return func(i any) { if n < 1 { return } diff --git a/felix/bpf/perf/perf_test.go b/felix/bpf/perf/perf_test.go index dccdfdb5e8c..969de31f209 100644 --- a/felix/bpf/perf/perf_test.go +++ b/felix/bpf/perf/perf_test.go @@ -215,7 +215,7 @@ func TestPerfPoll(t *testing.T) { }, } - for i := 0; i < 3; i++ { + for i := range 3 { poller.rings[i] = &perfRing{ ctrl: new(unix.PerfEventMmapPage), ring: make([]byte, 1024), diff --git a/felix/bpf/polprog/pol_prog_builder.go b/felix/bpf/polprog/pol_prog_builder.go index 655cdd04f32..ed88c854ce3 100644 --- a/felix/bpf/polprog/pol_prog_builder.go +++ b/felix/bpf/polprog/pol_prog_builder.go @@ -75,7 +75,8 @@ type Option func(b *Builder) func NewBuilder( ipSetIDProvider ipSetIDProvider, ipsetMapFD, stateMapFD, staticProgsMapFD, policyJumpMapFD maps.FD, - opts ...Option) *Builder { + opts ...Option, +) *Builder { b := &Builder{ ipSetIDProvider: ipSetIDProvider, ipSetMapFD: ipsetMapFD, @@ -172,8 +173,17 @@ type Rule struct { } type Policy struct { - Name string - Rules []Rule + Kind string + Namespace string + Name string + Rules []Rule +} + +func (r Policy) NamespacedName() string { + if r.Namespace != "" { + return fmt.Sprintf("%s/%s", r.Namespace, r.Name) + } + return r.Name } type Tier struct { @@ -532,8 +542,8 @@ func (p *Builder) writeProfiles(profiles []Policy, noProfileMatchID uint64, allo func (p *Builder) writePolicyRules(policy Policy, actionLabels map[string]string, destLeg matchLeg) { for ruleIdx, rule := range policy.Rules { - log.Debugf("Start of rule %d", ruleIdx) - p.b.AddCommentF("Start of rule %s", rule) + log.Debugf("Start of %s rule %d", policy.NamespacedName(), ruleIdx) + p.b.AddCommentF("Start of rule %s %s", policy.NamespacedName(), rule) ipsets := p.printIPSetIDs(rule) if ipsets != "" { p.b.AddCommentF("IPSets %s", p.printIPSetIDs(rule)) @@ -541,17 +551,24 @@ func (p *Builder) writePolicyRules(policy Policy, actionLabels map[string]string p.b.AddCommentF("Rule MatchID: %d", rule.MatchID) action := strings.ToLower(rule.Action) p.writeRule(rule, actionLabels[action], destLeg) - log.Debugf("End of rule %d", ruleIdx) - p.b.AddCommentF("End of rule %s", rule.RuleId) + log.Debugf("End of rule %s %d", policy.NamespacedName(), ruleIdx) + p.b.AddCommentF("End of rule %s %s", policy.NamespacedName(), rule.RuleId) } } func (p *Builder) writePolicy(policy Policy, actionLabels map[string]string, destLeg matchLeg) { - p.b.AddCommentF("Start of policy %s", policy.Name) - log.Debugf("Start of policy %q %d", policy.Name, p.policyID) + // Identifying comment at the start and end of the policy. + cmtID := fmt.Sprintf("%s %s %d", policy.Kind, policy.Name, p.policyID) + if policy.Namespace != "" { + cmtID = fmt.Sprintf("%s %s/%s %d", policy.Kind, policy.Namespace, policy.Name, p.policyID) + } + p.b.AddCommentF("Start of %s", cmtID) + log.Debugf("Start of %s", cmtID) + p.writePolicyRules(policy, actionLabels, destLeg) - log.Debugf("End of policy %q %d", policy.Name, p.policyID) - p.b.AddCommentF("End of policy %s", policy.Name) + + log.Debugf("End of %s", cmtID) + p.b.AddCommentF("End of %s", cmtID) p.policyID++ } @@ -806,6 +823,7 @@ func (p *Builder) writeICMPTypeCodeMatch(negate bool, icmpType, icmpCode uint8) p.b.JumpNEImm64(asm.R1, (int32(icmpCode)<<8)|int32(icmpType), p.endOfRuleLabel()) } } + func (p *Builder) writeCIDRSMatch(negate bool, leg matchLeg, cidrs []string) { if p.policyDebugEnabled { cidrStrings := "{" @@ -974,7 +992,6 @@ func (p *Builder) writeIPSetMatch(negate bool, leg matchLeg, ipSets []string) { // Match if packet matches ANY of the given IP sets. func (p *Builder) writeIPSetOrMatch(leg matchLeg, ipSets []string) { - onMatchLabel := p.freshPerRuleLabel() for _, ipSetID := range ipSets { diff --git a/felix/bpf/polprog/pol_prog_builder_test.go b/felix/bpf/polprog/pol_prog_builder_test.go index 26d53afab35..340b7985a0e 100644 --- a/felix/bpf/polprog/pol_prog_builder_test.go +++ b/felix/bpf/polprog/pol_prog_builder_test.go @@ -168,9 +168,9 @@ func TestProgramSplitting(t *testing.T) { Name: "tier0", } actions := []string{"pass", "deny"} - for i := 0; i < 250; i++ { + for i := range 250 { pol := Policy{} - for j := 0; j < 40; j++ { + for j := range 40 { pol.Rules = append(pol.Rules, Rule{Rule: &proto.Rule{ Action: actions[j%len(actions)], IpVersion: 4, @@ -185,9 +185,9 @@ func TestProgramSplitting(t *testing.T) { tier1 := Tier{ Name: "tier0", } - for i := 0; i < 25; i++ { + for i := range 25 { pol := Policy{} - for j := 0; j < 40; j++ { + for j := range 40 { pol.Rules = append(pol.Rules, Rule{Rule: &proto.Rule{ Action: actions[j%len(actions)], IpVersion: 4, diff --git a/felix/bpf/proxy/endpoint.go b/felix/bpf/proxy/endpoint.go index 53e936b17ac..12a9e0a4d42 100644 --- a/felix/bpf/proxy/endpoint.go +++ b/felix/bpf/proxy/endpoint.go @@ -98,6 +98,13 @@ func EndpointInfoOptZoneHints(b sets.Set[string]) EndpoiontInfoOpt { } } +// EndpointInfoOptNodeHints applies the given set to the endpoint's nodeHints field. +func EndpointInfoOptNodeHints(s sets.Set[string]) EndpoiontInfoOpt { + return func(ep *endpointInfo) { + ep.nodeHints = s + } +} + // NewEndpointInfo creates a new endpointInfo, returning it as a k8s proxy Endpoint. func NewEndpointInfo(ip string, port int, opts ...EndpoiontInfoOpt) k8sp.Endpoint { ep := &endpointInfo{ diff --git a/felix/bpf/proxy/health.go b/felix/bpf/proxy/health.go new file mode 100644 index 00000000000..9532dfb35dc --- /dev/null +++ b/felix/bpf/proxy/health.go @@ -0,0 +1,73 @@ +// Copyright (c) 2017-2025 Tigera, Inc. All rights reserved. +// +// 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. + +// This boilerplate code is based on proxiers in k8s.io/kubernetes/pkg/proxy to +// allow reuse of the rest of the proxy package without change + +package proxy + +import ( + "context" + "fmt" + "time" + + logrus "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" + "k8s.io/client-go/kubernetes" + k8sp "k8s.io/kubernetes/pkg/proxy" + "k8s.io/kubernetes/pkg/proxy/healthcheck" +) + +type Healthcheck interface { + Health() healthcheck.ProxyHealth + QueuedUpdate(v1.IPFamily) + Updated(v1.IPFamily) +} + +func NewHealthCheck(k8s kubernetes.Interface, nodeName string, port int, + minSyncPeriod time.Duration) (Healthcheck, error) { + + nodeMgr := new(k8sp.NodeManager) + node := &v1.Node{ + Spec: v1.NodeSpec{}, + } + + nodeMgr.OnNodeChange(node) + + healthzAddr := fmt.Sprintf(":%d", port) + server := healthcheck.NewProxyHealthServer(healthzAddr, minSyncPeriod, nodeMgr) + + // We cannot wait for the healthz server as we cannot stop it. + go func() { + logrus.Infof("Starting BPF Proxy Healthz server on %s", healthzAddr) + for { + err := server.Run(context.Background()) // context is mosstly ignored inside + if err != nil { + logrus.WithError(err).Error("BPF Proxy Healthz server failed, restarting in 1s") + time.Sleep(time.Second) + } + } + }() + + return server, nil +} + +type alwaysHealthy struct{} + +func (a *alwaysHealthy) Health() healthcheck.ProxyHealth { + return healthcheck.ProxyHealth{Healthy: true} +} + +func (a *alwaysHealthy) QueuedUpdate(v1.IPFamily) {} +func (a *alwaysHealthy) Updated(v1.IPFamily) {} diff --git a/felix/bpf/proxy/health_check_nodeport_test.go b/felix/bpf/proxy/health_check_nodeport_test.go index e8c90f589f4..92942d07401 100644 --- a/felix/bpf/proxy/health_check_nodeport_test.go +++ b/felix/bpf/proxy/health_check_nodeport_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021 Tigera, Inc. All rights reserved. +// Copyright (c) 2017-2025 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,10 +17,11 @@ package proxy_test import ( "encoding/json" "fmt" + "net" "net/http" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" discovery "k8s.io/api/discovery/v1" @@ -33,7 +34,7 @@ import ( ) var _ = Describe("BPF Proxy healthCheckNodeport", func() { - var p proxy.Proxy + var p proxy.ProxyFrontend k8s := fake.NewClientset() testNodeName := "testnode" @@ -43,9 +44,10 @@ var _ = Describe("BPF Proxy healthCheckNodeport", func() { By("creating proxy with fake client and mock syncer", func() { var err error - p, err = proxy.New(k8s, &mockDummySyncer{}, - testNodeName, proxy.WithMinSyncPeriod(200*time.Millisecond)) + p, err = proxy.New(k8s, &mockDummySyncer{}, testNodeName, + proxy.WithMinSyncPeriod(200*time.Millisecond), proxy.WithMaxSyncPeriod(1*time.Second)) Expect(err).NotTo(HaveOccurred()) + p.SetHostIPs([]net.IP{net.ParseIP("127.0.0.1")}) }) }) @@ -126,7 +128,7 @@ var _ = Describe("BPF Proxy healthCheckNodeport", func() { Expect(err).NotTo(HaveOccurred()) Expect(result.StatusCode).Should(Equal(503)) - var status map[string]interface{} + var status map[string]any decoder := json.NewDecoder(result.Body) err = decoder.Decode(&status) @@ -180,7 +182,7 @@ var _ = Describe("BPF Proxy healthCheckNodeport", func() { return fmt.Errorf("Unexpected status code %d; expected 200", result.StatusCode) } - var status map[string]interface{} + var status map[string]any decoder := json.NewDecoder(result.Body) err = decoder.Decode(&status) @@ -239,7 +241,7 @@ var _ = Describe("BPF Proxy healthCheckNodeport", func() { return fmt.Errorf("Unexpected status code %d; expected 200", result.StatusCode) } - var status map[string]interface{} + var status map[string]any decoder := json.NewDecoder(result.Body) err = decoder.Decode(&status) diff --git a/felix/bpf/proxy/kube-proxy.go b/felix/bpf/proxy/kube-proxy.go index c5060abe425..27ea9df4775 100644 --- a/felix/bpf/proxy/kube-proxy.go +++ b/felix/bpf/proxy/kube-proxy.go @@ -26,6 +26,7 @@ import ( "github.com/projectcalico/calico/felix/bpf/maps" "github.com/projectcalico/calico/felix/bpf/routes" "github.com/projectcalico/calico/felix/ip" + "github.com/projectcalico/calico/felix/proto" ) // KubeProxy is a wrapper of Proxy that deals with higher level issue like @@ -34,6 +35,15 @@ type KubeProxy struct { proxy ProxyFrontend syncer DPSyncer + // pendingHostMetadataUpdates contains HostMetadataV4V6Update and HostMetadataV4V6Removes + // that we're batching up to send. Only accessed from the int-dataplane goroutine. + // Keyed by hostname (node name). + pendingHostMetadataUpdates map[string]any + // hostMetadataUpdates is a size-1 channel - allows for one non-blocking write, + // and repeated updates get merged into older unconsumed ones. + hostMetadataUpdates chan map[string]any + inSyncWithIntDataplane bool + ipFamily int hostIPUpdates chan []net.IP stopOnce sync.Once @@ -41,14 +51,16 @@ type KubeProxy struct { exiting chan struct{} wg sync.WaitGroup - k8s kubernetes.Interface - hostname string - frontendMap maps.MapWithExistsCheck - backendMap maps.MapWithExistsCheck - affinityMap maps.Map - ctMap maps.Map - rt *RTCache - opts []Option + k8s kubernetes.Interface + hostname string + frontendMap maps.MapWithExistsCheck + backendMap maps.MapWithExistsCheck + MaglevMap maps.MapWithExistsCheck + maglevLUTSize int + affinityMap maps.Map + ctMap maps.Map + rt *RTCache + opts []Option excludedCIDRs *ip.CIDRTrie @@ -65,11 +77,15 @@ func StartKubeProxy(k8s kubernetes.Interface, hostname string, hostname: hostname, frontendMap: bpfMaps.FrontendMap.(maps.MapWithExistsCheck), backendMap: bpfMaps.BackendMap.(maps.MapWithExistsCheck), + MaglevMap: bpfMaps.MaglevMap.(maps.MapWithExistsCheck), affinityMap: bpfMaps.AffinityMap, ctMap: bpfMaps.CtMap, opts: opts, rt: NewRTCache(), + hostMetadataUpdates: make(chan map[string]any, 1), + pendingHostMetadataUpdates: make(map[string]any), + hostIPUpdates: make(chan []net.IP, 1), exiting: make(chan struct{}), } @@ -101,13 +117,21 @@ func (kp *KubeProxy) Stop() { defer kp.lock.Unlock() close(kp.exiting) + close(kp.hostMetadataUpdates) close(kp.hostIPUpdates) kp.proxy.Stop() kp.wg.Wait() }) } -func (kp *KubeProxy) run(hostIPs []net.IP) error { +func (kp *KubeProxy) setProxyHostMetadata(hostMetadata map[string]*proto.HostMetadataV4V6Update) { + kp.lock.Lock() + defer kp.lock.Unlock() + + kp.proxy.SetHostMetadata(hostMetadata, true) +} + +func (kp *KubeProxy) run(hostIPs []net.IP, hostMetadata map[string]*proto.HostMetadataV4V6Update) error { ips := make([]net.IP, 0, len(hostIPs)) for _, ip := range hostIPs { @@ -131,12 +155,15 @@ func (kp *KubeProxy) run(hostIPs []net.IP) error { withLocalNP = append(withLocalNP, podNPIPV6) } - syncer, err := NewSyncer(kp.ipFamily, withLocalNP, kp.frontendMap, kp.backendMap, kp.affinityMap, - kp.rt, kp.excludedCIDRs) + syncer, err := NewSyncer(kp.ipFamily, withLocalNP, kp.frontendMap, kp.backendMap, kp.MaglevMap, kp.affinityMap, + kp.rt, kp.excludedCIDRs, kp.maglevLUTSize) if err != nil { return errors.WithMessage(err, "new bpf syncer") } + kp.proxy.SetHostIPs(hostIPs) + // Don't bother invoking a resync within SetHostMetadata; we will be syncing a fresh syncer right after. + kp.proxy.SetHostMetadata(hostMetadata, false) kp.proxy.SetSyncer(syncer) log.Infof("kube-proxy v%d node info updated, hostname=%q hostIPs=%+v", kp.ipFamily, kp.hostname, hostIPs) @@ -154,7 +181,7 @@ func (kp *KubeProxy) start() error { withLocalNP = append(withLocalNP, podNPIPV6) } - syncer, err := NewSyncer(kp.ipFamily, withLocalNP, kp.frontendMap, kp.backendMap, kp.affinityMap, kp.rt, kp.excludedCIDRs) + syncer, err := NewSyncer(kp.ipFamily, withLocalNP, kp.frontendMap, kp.backendMap, kp.MaglevMap, kp.affinityMap, kp.rt, kp.excludedCIDRs, kp.maglevLUTSize) if err != nil { return errors.WithMessage(err, "new bpf syncer") } @@ -169,34 +196,49 @@ func (kp *KubeProxy) start() error { kp.syncer = syncer kp.lock.Unlock() - // wait for the initial update + // Wait for the initial update. hostIPs := <-kp.hostIPUpdates - err = kp.run(hostIPs) + hostMetadata := make(map[string]*proto.HostMetadataV4V6Update) + // Block until we go in-sync and get the first batch of hostmetadata + // updates, to avoid a flap after a Felix restart. In practice, this + // recv should happen very soon after receiving the host IPs above. + hostMetadataUpdates := <-kp.hostMetadataUpdates + mergeHostMetadataV4V6Updates(hostMetadata, hostMetadataUpdates) + + err = kp.run(hostIPs, hostMetadata) if err != nil { return err } - kp.wg.Add(1) - go func() { - defer kp.wg.Done() + kp.wg.Go(func() { for { + var ok bool select { - case hostIPs, ok := <-kp.hostIPUpdates: + case hostIPs, ok = <-kp.hostIPUpdates: if !ok { log.Error("kube-proxy: hostIPUpdates closed") return } - err = kp.run(hostIPs) + err = kp.run(hostIPs, hostMetadata) if err != nil { log.Panic("kube-proxy failed to resync after host IPs update") } + + case hostMetadataUpdates, ok = <-kp.hostMetadataUpdates: + if !ok { + log.Error("kube-proxy: hostMetadataUpdates closed") + return + } + mergeHostMetadataV4V6Updates(hostMetadata, hostMetadataUpdates) + kp.setProxyHostMetadata(hostMetadata) + case <-kp.exiting: log.Info("kube-proxy: exiting") return } } - }() + }) return nil } @@ -219,6 +261,92 @@ func (kp *KubeProxy) OnHostIPsUpdate(IPs []net.IP) { log.Debugf("kube-proxy OnHostIPsUpdate: %+v", IPs) } +// OnUpdate implements the manager interface. +// Writes updates to pending updates map - overwrites repeated updates for the same key. +func (kp *KubeProxy) OnUpdate(msg any) { + hostname := "" + switch update := msg.(type) { + case *proto.HostMetadataV4V6Update: + hostname = update.Hostname + log.WithField("msg", update).Debugf("kube-proxy OnUpdate: host metadata update") + case *proto.HostMetadataV4V6Remove: + hostname = update.Hostname + log.WithField("msg", update).Debugf("kube-proxy OnUpdate: host metadata remove") + default: + return + } + + if hostname == "" { + log.WithField("msg", msg).Warn("kube-proxy OnUpdate: got host metadata update with empty hostname") + return + } + + kp.pendingHostMetadataUpdates[hostname] = msg +} + +// CompleteDeferredWork implements the manager interface. +// Avoids blocking the thread by draining & merging older updates on the channel before sending. +func (kp *KubeProxy) CompleteDeferredWork() error { + // If not in-sync with felix, we allow sending an empty update + // to signal to the KP loop that it can start looping. + if len(kp.pendingHostMetadataUpdates) == 0 && kp.inSyncWithIntDataplane { + log.Debug("No pending host metadata updates to process") + return nil + } + + // Drain any pre-existing msg first and merge. + updates := kp.pollHostMetadataV4V6UpdatesNonBlocking() + if updates == nil { + updates = make(map[string]any) + } + + // Overwrite any pre-existing updates for a given key. + // Always send 'Removes' instead of just deleting updates of the same key (since downstream may need to see a remove). + for k, v := range kp.pendingHostMetadataUpdates { + updates[k] = v + log.WithField("nodeName", k).Debug("Queueing new host metadata update") + // ... And clear the pending updates after processing. + delete(kp.pendingHostMetadataUpdates, k) + } + + // Send the merged updates back down the channel. + log.Debug("Queueing new hostmetadata for main loop") + kp.hostMetadataUpdates <- updates + log.Debug("Successfully queued new hostmetadata") + kp.inSyncWithIntDataplane = true + return nil +} + +// pollHostMetadataV4V6UpdatesNonBlocking tries to read a pending host metadata update on the update channel. +// Returns nil immediately, if nothing can be received from the updates channel. +func (kp *KubeProxy) pollHostMetadataV4V6UpdatesNonBlocking() map[string]any { + select { + case upd := <-kp.hostMetadataUpdates: + return upd + default: + return nil + } +} + +// mergeHostMetadataV4V6Updates merges the existing host metadata updates with the latest updates: +// - A 'remove' in latest deletes the corresponding key in 'existing'. +// - An 'update' in latest overwrites the corresponding key in 'existing'. +// - If 'latest' is nil, does nothing. 'existing' must be non-nil. +func mergeHostMetadataV4V6Updates(existing map[string]*proto.HostMetadataV4V6Update, latest map[string]any) { + if latest == nil { + return + } + + for k, v := range latest { + switch update := v.(type) { + case *proto.HostMetadataV4V6Update: + existing[k] = update + case *proto.HostMetadataV4V6Remove: + delete(existing, k) + } + } +} + // OnRouteUpdate should be used to update the internal state of routing tables func (kp *KubeProxy) OnRouteUpdate(k routes.KeyInterface, v routes.ValueInterface) { log.WithFields(log.Fields{"key": k, "value": v}).Debug("kube-proxy: OnRouteUpdate") diff --git a/felix/bpf/proxy/kube-proxy_internal_test.go b/felix/bpf/proxy/kube-proxy_internal_test.go index 0e59e331ad0..ed291098896 100644 --- a/felix/bpf/proxy/kube-proxy_internal_test.go +++ b/felix/bpf/proxy/kube-proxy_internal_test.go @@ -17,6 +17,11 @@ package proxy import ( "net" "testing" + "time" + + . "github.com/onsi/gomega" + + "github.com/projectcalico/calico/felix/proto" ) // The main suite of tests in kube-proxy_test.go use a real syncer, making it @@ -71,3 +76,118 @@ func (s *mockSyncer) ConntrackFrontendHasBackend(ip net.IP, port uint16, backend func (s *mockSyncer) ConntrackDestIsService(ip net.IP, port uint16, proto uint8) bool { return true } + +func TestMergeHostMetadataV4V6Updates(t *testing.T) { + RegisterTestingT(t) + existing := map[string]*proto.HostMetadataV4V6Update{ + "host1": {Hostname: "host1", Ipv4Addr: "1.1.1.1"}, + "host2": {Hostname: "host2", Ipv4Addr: "2.2.2.2"}, + } + latest := map[string]any{ + "host1": &proto.HostMetadataV4V6Remove{Hostname: "host1"}, + "host2": &proto.HostMetadataV4V6Update{Hostname: "host2", Ipv4Addr: "5.5.5.5"}, + "host3": &proto.HostMetadataV4V6Update{Hostname: "host3", Ipv4Addr: "3.3.3.3"}, + } + mergeHostMetadataV4V6Updates(existing, latest) + + Expect(existing).To(HaveLen(2), "Expected host1 to be removed and host3 to be added") + Expect(existing).NotTo(HaveKey("host1")) + Expect(existing).To(HaveKey("host2")) + Expect(existing).To(HaveKey("host3")) + + Expect(existing["host2"].Ipv4Addr).To(Equal("5.5.5.5")) + Expect(existing["host3"].Ipv4Addr).To(Equal("3.3.3.3")) +} + +func TestOnUpdateBatchesHostMetadataUpdates(t *testing.T) { + RegisterTestingT(t) + kp := KubeProxy{ + hostMetadataUpdates: make(chan map[string]any, 1), + pendingHostMetadataUpdates: make(map[string]any), + } + + update := &proto.HostMetadataV4V6Update{ + Hostname: "hn1", + Ipv4Addr: "1.2.3.4", + Labels: map[string]string{"label1": "label1val"}, + } + kp.OnUpdate(update) + + update2 := &proto.HostMetadataV4V6Update{ + Hostname: "hn2", + Ipv4Addr: "2.2.2.2", + Labels: map[string]string{"label2": "label2val"}, + } + kp.OnUpdate(update2) + + Consistently(kp.pollHostMetadataV4V6UpdatesNonBlocking(), 100*time.Millisecond).Should(BeNil(), "No updates should have been sent before CompleteDeferredWork") + + Expect(kp.CompleteDeferredWork()).To(Succeed(), "CompleteDeferredWork should succeed") + // Read the queued update from the channel. + Expect(kp.pollHostMetadataV4V6UpdatesNonBlocking()).To(Equal(map[string]any{ + "hn1": update, + "hn2": update2, + })) +} + +func TestCompleteDeferredWorkSendsEmptyUpdateOnce(t *testing.T) { + RegisterTestingT(t) + kp := KubeProxy{ + hostMetadataUpdates: make(chan map[string]any, 1), + pendingHostMetadataUpdates: make(map[string]any), + } + + // First call with no pending updates should still send an empty map + // to signal the KP loop to start. + Expect(kp.CompleteDeferredWork()).To(Succeed()) + msg := kp.pollHostMetadataV4V6UpdatesNonBlocking() + Expect(msg).NotTo(BeNil(), "First call should send an empty update to unblock the KP loop") + Expect(msg).To(BeEmpty(), "The update should be an empty map") + + // Second call with no pending updates should be a no-op. + Expect(kp.CompleteDeferredWork()).To(Succeed()) + Expect(kp.pollHostMetadataV4V6UpdatesNonBlocking()).To(BeNil(), + "Second call with no pending updates should not send anything") + + // Sending a real update should still work after we're in sync. + kp.OnUpdate(&proto.HostMetadataV4V6Update{Hostname: "host1", Ipv4Addr: "1.1.1.1"}) + Expect(kp.CompleteDeferredWork()).To(Succeed()) + msg = kp.pollHostMetadataV4V6UpdatesNonBlocking() + Expect(msg).To(HaveLen(1)) + Expect(msg).To(HaveKey("host1")) + + // And after that, no-op again with no pending updates. + Expect(kp.CompleteDeferredWork()).To(Succeed()) + Expect(kp.pollHostMetadataV4V6UpdatesNonBlocking()).To(BeNil(), + "Should not send when in sync and no pending updates") +} + +func TestOnUpdateRemoveOverwritesPendingUpdate(t *testing.T) { + RegisterTestingT(t) + kp := KubeProxy{ + hostMetadataUpdates: make(chan map[string]any, 1), + pendingHostMetadataUpdates: make(map[string]any), + } + + update1 := &proto.HostMetadataV4V6Update{ + Hostname: "hn1", + Ipv4Addr: "1.2.3.4", + } + update2 := &proto.HostMetadataV4V6Update{ + Hostname: "hn2", + Ipv4Addr: "1.2.3.4", + } + update1Remove := &proto.HostMetadataV4V6Remove{Hostname: "hn1"} + + // Queue an update, then an immediate remove for the same hostname. + kp.OnUpdate(update1) + kp.OnUpdate(update2) + kp.OnUpdate(update1Remove) + + Expect(kp.CompleteDeferredWork()).To(Succeed(), "CompleteDeferredWork should succeed") + + Expect(kp.pollHostMetadataV4V6UpdatesNonBlocking()).To(Equal(map[string]any{ + "hn1": update1Remove, + "hn2": update2, + })) +} diff --git a/felix/bpf/proxy/kube-proxy_test.go b/felix/bpf/proxy/kube-proxy_test.go index 07f32d7dd03..9f778922856 100644 --- a/felix/bpf/proxy/kube-proxy_test.go +++ b/felix/bpf/proxy/kube-proxy_test.go @@ -15,11 +15,9 @@ package proxy_test import ( - "fmt" "net" - "net/http" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" discoveryv1 "k8s.io/api/discovery/v1" @@ -30,6 +28,7 @@ import ( "github.com/projectcalico/calico/felix/bpf/conntrack" "github.com/projectcalico/calico/felix/bpf/mock" proxy "github.com/projectcalico/calico/felix/bpf/proxy" + "github.com/projectcalico/calico/felix/proto" ) var _ = Describe("BPF kube-proxy", func() { @@ -39,6 +38,7 @@ var _ = Describe("BPF kube-proxy", func() { maps.FrontendMap = newMockNATMap() maps.BackendMap = newMockNATBackendMap() maps.AffinityMap = newMockAffinityMap() + maps.MaglevMap = newMockMaglevMap() maps.CtMap = mock.NewMockMap(conntrack.MapParams) front := maps.FrontendMap.(*mockNATMap) @@ -87,7 +87,10 @@ var _ = Describe("BPF kube-proxy", func() { } k8s := fake.NewClientset(testSvc, testSvcEps) - p, _ = proxy.StartKubeProxy(k8s, "test-node", maps, proxy.WithImmediateSync()) + p, _ = proxy.StartKubeProxy(k8s, "test-node", maps, proxy.WithImmediateSync(), proxy.WithMaglevLUTSize(maglevLUTSize)) + // Unblock start(), which blocks on the initial host metadata update. + p.OnUpdate(&proto.HostMetadataV4V6Update{Hostname: "dummy"}) + Expect(p.CompleteDeferredWork()).To(Succeed()) }) AfterEach(func() { @@ -110,21 +113,9 @@ var _ = Describe("BPF kube-proxy", func() { }).Should(BeTrue()) }) - By("checking that the healthCheckNodePort is accessible", func() { - Eventually(func() error { - result, err := http.Get(fmt.Sprintf("http://localhost:%d", healthCheckNodePort)) - if err != nil { - return err - } - if result.StatusCode != 503 { - return fmt.Errorf("Unexpected status code %d; expected 503", result.StatusCode) - } - return nil - }, "5s", "200ms").Should(Succeed()) - }) + updatedIP := net.IPv4(2, 2, 2, 2) By("checking nodeport has the updated IP and not the initial IP", func() { - updatedIP := net.IPv4(2, 2, 2, 2) p.OnHostIPsUpdate([]net.IP{updatedIP}) Eventually(func() bool { @@ -143,19 +134,6 @@ var _ = Describe("BPF kube-proxy", func() { }).Should(BeTrue()) }) - By("checking that the healthCheckNodePort is still accessible", func() { - Eventually(func() error { - result, err := http.Get(fmt.Sprintf("http://localhost:%d", healthCheckNodePort)) - if err != nil { - return err - } - if result.StatusCode != 503 { - return fmt.Errorf("Unexpected status code %d; expected 503", result.StatusCode) - } - return nil - }, "5s", "200ms").Should(Succeed()) - }) - By("checking nodeport has 2 updated IPs", func() { ip1 := net.IPv4(3, 3, 3, 3) ip2 := net.IPv4(4, 4, 4, 4) diff --git a/felix/bpf/proxy/lb_src_range_test.go b/felix/bpf/proxy/lb_src_range_test.go index 48bb142eabc..49cb81c6e1f 100644 --- a/felix/bpf/proxy/lb_src_range_test.go +++ b/felix/bpf/proxy/lb_src_range_test.go @@ -17,7 +17,7 @@ package proxy_test import ( "net" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/sirupsen/logrus" v1 "k8s.io/api/core/v1" @@ -39,6 +39,7 @@ func init() { func testfn(makeIPs func(ips []net.IP) proxy.K8sServicePortOption) { svcs := newMockNATMap() eps := newMockNATBackendMap() + mglv := newMockMaglevMap() aff := newMockAffinityMap() nodeIPs := []net.IP{net.IPv4(192, 168, 0, 1), net.IPv4(10, 123, 0, 1)} @@ -47,7 +48,7 @@ func testfn(makeIPs func(ips []net.IP) proxy.K8sServicePortOption) { externalIP := makeIPs([]net.IP{net.IPv4(35, 0, 0, 2)}) twoExternalIPs := makeIPs([]net.IP{net.IPv4(35, 0, 0, 2), net.IPv4(45, 0, 1, 2)}) - s, _ := proxy.NewSyncer(4, nodeIPs, svcs, eps, aff, rt, nil) + s, _ := proxy.NewSyncer(4, nodeIPs, svcs, eps, mglv, aff, rt, nil, maglevLUTSize) svcKey := k8sp.ServicePortName{ NamespacedName: types.NamespacedName{ @@ -210,7 +211,7 @@ func testfn(makeIPs func(ips []net.IP) proxy.K8sServicePortOption) { externalIP, proxy.K8sSvcWithLBSourceRangeIPs([]*net.IPNet{&ipnet}), ) - s, _ = proxy.NewSyncer(4, nodeIPs, svcs, eps, aff, rt, nil) + s, _ = proxy.NewSyncer(4, nodeIPs, svcs, eps, mglv, aff, rt, nil, maglevLUTSize) err := s.Apply(state) Expect(err).NotTo(HaveOccurred()) Expect(svcs.m).To(HaveLen(3)) @@ -223,7 +224,7 @@ func testfn(makeIPs func(ips []net.IP) proxy.K8sServicePortOption) { v1.ProtocolTCP, externalIP, ) - s, _ = proxy.NewSyncer(4, nodeIPs, svcs, eps, aff, rt, nil) + s, _ = proxy.NewSyncer(4, nodeIPs, svcs, eps, mglv, aff, rt, nil, maglevLUTSize) err := s.Apply(state) Expect(err).NotTo(HaveOccurred()) Expect(svcs.m).To(HaveLen(2)) @@ -246,6 +247,7 @@ func testfn(makeIPs func(ips []net.IP) proxy.K8sServicePortOption) { func test0000SourceRange() { svcs := newMockNATMap() eps := newMockNATBackendMap() + mglv := newMockMaglevMap() aff := newMockAffinityMap() nodeIPs := []net.IP{net.IPv4(192, 168, 0, 1), net.IPv4(10, 123, 0, 1)} @@ -253,7 +255,7 @@ func test0000SourceRange() { externalIP := net.IPv4(35, 0, 0, 2) - s, _ := proxy.NewSyncer(4, nodeIPs, svcs, eps, aff, rt, nil) + s, _ := proxy.NewSyncer(4, nodeIPs, svcs, eps, mglv, aff, rt, nil, maglevLUTSize) svcKey := k8sp.ServicePortName{ NamespacedName: types.NamespacedName{ diff --git a/felix/bpf/proxy/options.go b/felix/bpf/proxy/options.go index 7a7ad6e2962..d6b632f7d50 100644 --- a/felix/bpf/proxy/options.go +++ b/felix/bpf/proxy/options.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2019 Tigera, Inc. All rights reserved. +// Copyright (c) 2017-2025 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import ( "time" log "github.com/sirupsen/logrus" - "k8s.io/kubernetes/pkg/proxy/healthcheck" "github.com/projectcalico/calico/felix/ip" ) @@ -59,6 +58,14 @@ func WithMinSyncPeriod(min time.Duration) Option { }) } +func WithMaxSyncPeriod(max time.Duration) Option { + return makeOption(func(p *proxy) error { + p.maxDPSyncPeriod = max + log.Infof("proxy.WithMaxSyncPeriod(%s)", max) + return nil + }) +} + // WithImmediateSync triggers sync with dataplane on immediately on every update func WithImmediateSync() Option { return WithMinSyncPeriod(0) @@ -87,6 +94,18 @@ func WithIPFamily(ipFamily int) Option { } } +func WithMaglevLUTSize(size int) Option { + return func(P Proxy) error { + p, ok := P.(*KubeProxy) + if !ok { + return nil + } + + p.maglevLUTSize = size + return nil + } +} + var excludeCIDRsMatch = 1 func WithExcludedCIDRs(cidrs []string) Option { @@ -114,9 +133,11 @@ func WithExcludedCIDRs(cidrs []string) Option { }) } -func WithHealthzServer(hs *healthcheck.ProxyHealthServer) Option { +func WithHealthCheck(hc Healthcheck) Option { return makeOption(func(p *proxy) error { - p.healthzServer = hs + if hc != nil { + p.healthzServer = hc + } return nil }) } diff --git a/felix/bpf/proxy/proxy.go b/felix/bpf/proxy/proxy.go index d4120854829..5d8720ecc61 100644 --- a/felix/bpf/proxy/proxy.go +++ b/felix/bpf/proxy/proxy.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021 Tigera, Inc. All rights reserved. +// Copyright (c) 2017-2025 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -41,8 +41,10 @@ import ( "k8s.io/kubernetes/pkg/proxy/apis" "k8s.io/kubernetes/pkg/proxy/config" "k8s.io/kubernetes/pkg/proxy/healthcheck" + "k8s.io/kubernetes/pkg/proxy/runner" "k8s.io/kubernetes/pkg/proxy/util" - "k8s.io/kubernetes/pkg/util/async" + + "github.com/projectcalico/calico/felix/proto" ) // Proxy watches for updates of Services and Endpoints, maintains their mapping @@ -57,12 +59,15 @@ type Proxy interface { type ProxyFrontend interface { Proxy SetSyncer(DPSyncer) + SetHostIPs([]net.IP) + SetHostMetadata(updates map[string]*proto.HostMetadataV4V6Update, requestResync bool) } // DPSyncerState groups the information passed to the DPSyncer's Apply type DPSyncerState struct { SvcMap k8sp.ServicePortMap EpsMap k8sp.EndpointsMap + Hostname string NodeZone string } @@ -93,35 +98,37 @@ type proxy struct { svcMap k8sp.ServicePortMap epsMap k8sp.EndpointsMap + hostMetadataByHostname map[string]*proto.HostMetadataV4V6Update + dpSyncer DPSyncer syncerLck sync.Mutex // executes periodic the dataplane updates - runner *async.BoundedFrequencyRunner + runner *runner.BoundedFrequencyRunner // ensures that only one invocation runs at any time runnerLck sync.Mutex // sets the minimal distance between to sync to avoid overloading the // dataplane in case of frequent changes minDPSyncPeriod time.Duration + // The interval that syncing is guaranteed to run even without any other triggers. + maxDPSyncPeriod time.Duration + + // The interval to retry syncing. + retryDPSyncPeriod time.Duration + // how often to fully sync with k8s - 0 is never syncPeriod time.Duration // event recorder to update node events recorder events.EventRecorder svcHealthServer healthcheck.ServiceHealthServer - healthzServer healthcheckIntf + healthzServer Healthcheck stopCh chan struct{} stopWg sync.WaitGroup stopOnce sync.Once } -type healthcheckIntf interface { - Health() healthcheck.ProxyHealth - QueuedUpdate(v1.IPFamily) - Updated(v1.IPFamily) -} - type stoppableRunner interface { Run(stopCh <-chan struct{}) } @@ -145,12 +152,19 @@ func New(k8s kubernetes.Interface, dp DPSyncer, hostname string, opts ...Option) svcMap: make(k8sp.ServicePortMap), epsMap: make(k8sp.EndpointsMap), + hostMetadataByHostname: make(map[string]*proto.HostMetadataV4V6Update), + recorder: new(loggerRecorder), - minDPSyncPeriod: 30 * time.Second, // XXX revisit the default + // TODO: revisit these default values. + minDPSyncPeriod: 30 * time.Second, + maxDPSyncPeriod: 1 * time.Hour, + retryDPSyncPeriod: 1 * time.Hour, // XXX might be infinite? stopCh: make(chan struct{}), healthzServer: new(alwaysHealthy), + // N.B. we do not start healthzServer until we have at least some host + // IPs. Dataplane implementation of hostports assumes we have a host IP. } for _, o := range opts { @@ -161,12 +175,11 @@ func New(k8s kubernetes.Interface, dp DPSyncer, hostname string, opts ...Option) // We need to create the runner first as once we start getting updates, they // will kick it - p.runner = async.NewBoundedFrequencyRunner("dp-sync-runner", - p.invokeDPSyncer, p.minDPSyncPeriod, time.Hour /* XXX might be infinite? */, 1) + p.runner = runner.NewBoundedFrequencyRunner("dp-sync-runner", + p.invokeDPSyncer, p.minDPSyncPeriod, p.retryDPSyncPeriod, p.maxDPSyncPeriod) dp.SetTriggerFn(p.runner.Run) ipVersion := p.v1IPFamily() - p.svcHealthServer = healthcheck.NewServiceHealthServer(p.hostname, p.recorder, util.NewNodePortAddresses(ipVersion, []string{"0.0.0.0/0"}), p.healthzServer) p.epsChanges = k8sp.NewEndpointsChangeTracker(ipVersion, p.hostname, nil, nil) p.svcChanges = k8sp.NewServiceChangeTracker(ipVersion, makeServiceInfo, nil) @@ -227,8 +240,7 @@ func (p *proxy) Stop() { p.stopOnce.Do(func() { log.Info("Proxy stopping") // Pass empty update to close all the health checks. - _ = p.svcHealthServer.SyncServices(map[types.NamespacedName]uint16{}) - _ = p.svcHealthServer.SyncEndpoints(map[types.NamespacedName]int{}) + p.stopSvcHealthServer() p.dpSyncer.Stop() close(p.stopCh) p.stopWg.Wait() @@ -237,11 +249,9 @@ func (p *proxy) Stop() { } func (p *proxy) startRoutine(f func()) { - p.stopWg.Add(1) - go func() { - defer p.stopWg.Done() + p.stopWg.Go(func() { f() - }() + }) } func (p *proxy) syncDP() { @@ -249,12 +259,15 @@ func (p *proxy) syncDP() { } func (p *proxy) forceSyncDP() { - p.invokeDPSyncer() + err := p.invokeDPSyncer() + if err != nil { + log.WithError(err).Error("Failed invoking dataplane syncer") + } } -func (p *proxy) invokeDPSyncer() { +func (p *proxy) invokeDPSyncer() error { if !p.isInitialized() { - return + return fmt.Errorf("proxy is not initialized") } p.runnerLck.Lock() @@ -263,11 +276,13 @@ func (p *proxy) invokeDPSyncer() { _ = p.svcMap.Update(p.svcChanges) _ = p.epsMap.Update(p.epsChanges) - if err := p.svcHealthServer.SyncServices(p.svcMap.HealthCheckNodePorts()); err != nil { - log.WithError(err).Error("Error syncing healthcheck services") - } - if err := p.svcHealthServer.SyncEndpoints(p.epsMap.LocalReadyEndpoints()); err != nil { - log.WithError(err).Error("Error syncing healthcheck endpoints") + if p.healthzServer != nil && p.svcHealthServer != nil { + if err := p.svcHealthServer.SyncServices(p.svcMap.HealthCheckNodePorts()); err != nil { + return fmt.Errorf("error syncing healthcheck Services - err: %w", err) + } + if err := p.svcHealthServer.SyncEndpoints(p.epsMap.LocalReadyEndpoints()); err != nil { + return fmt.Errorf("error syncing healthcheck endpoints - err: %w", err) + } } if p.healthzServer != nil { @@ -278,19 +293,21 @@ func (p *proxy) invokeDPSyncer() { err := p.dpSyncer.Apply(DPSyncerState{ SvcMap: p.svcMap, EpsMap: p.epsMap, + Hostname: p.hostname, NodeZone: p.nodeZone, }) p.syncerLck.Unlock() if err != nil { - log.WithError(err).Errorf("applying changes failed") // TODO log the error or panic as the best might be to restart // completely to wipe out the loaded bpf maps + return fmt.Errorf("applying changes failed - err: %w", err) } if p.healthzServer != nil { p.healthzServer.Updated(p.v1IPFamily()) } + return nil } func (p *proxy) OnServiceAdd(svc *v1.Service) { @@ -353,6 +370,53 @@ func (p *proxy) SetSyncer(s DPSyncer) { p.forceSyncDP() } +func (p *proxy) stopSvcHealthServer() { + if p.svcHealthServer != nil { + _ = p.svcHealthServer.SyncServices(map[types.NamespacedName]uint16{}) + _ = p.svcHealthServer.SyncEndpoints(map[types.NamespacedName]int{}) + } +} + +func (p *proxy) SetHostIPs(hostIPs []net.IP) { + p.runnerLck.Lock() + defer p.runnerLck.Unlock() + + p.stopSvcHealthServer() + + ips := make([]string, 0, len(hostIPs)) + for _, ip := range hostIPs { + if ip.To4() != nil && p.ipFamily == 4 { + ips = append(ips, ip.String()+"/32") + } else if ip.To4() == nil && p.ipFamily != 4 { + ips = append(ips, ip.String()+"/128") + } + } + + npa := util.NewNodePortAddresses(p.v1IPFamily(), ips) + log.Infof("NodePortAddresses V%d for health checks: %s", p.ipFamily, npa.String()) + p.svcHealthServer = healthcheck.NewServiceHealthServer(p.hostname, p.recorder, + npa, p.healthzServer) +} + +func (p *proxy) SetHostMetadata(updates map[string]*proto.HostMetadataV4V6Update, requestResync bool) { + p.runnerLck.Lock() + defer p.runnerLck.Unlock() + + // Clear the proxy's map and repopulate. + for k := range p.hostMetadataByHostname { + delete(p.hostMetadataByHostname, k) + } + + for k, v := range updates { + p.hostMetadataByHostname[k] = v + } + + if requestResync { + // Invoke a sync via the runner, so that we can release any locks in this goroutine. + p.syncDP() + } +} + func (p *proxy) IPFamily() discovery.AddressType { if p.ipFamily == 4 { return discovery.AddressTypeIPv4 @@ -386,24 +450,33 @@ func (is *initState) setEpsSynced() { type loggerRecorder struct{} -func (r *loggerRecorder) Eventf(regarding runtime.Object, related runtime.Object, eventtype, reason, action, note string, args ...interface{}) { +func (r *loggerRecorder) Eventf(regarding runtime.Object, related runtime.Object, eventtype, reason, action, note string, args ...any) { } const ( ReapTerminatingUDPAnnotation = "projectcalico.org/udpConntrackCleanup" ReapTerminatingUDPImmediatelly = "TerminatingImmediately" - ExcludeServiceAnnotation = "projectcalico.org/natExcludeService" + ExcludeServiceAnnotation = "projectcalico.org/natExcludeService" + ExternalTrafficStrategyAnnotation = "lb.projectcalico.org/external-traffic-strategy" +) + +var ( + ExternalTrafficStrategyMaglev = "maglev" ) type ServiceAnnotations interface { ReapTerminatingUDP() bool ExcludeService() bool + UseMaglev() bool + TopologyMode() string } type servicePortAnnotations struct { reapTerminatingUDP bool excludeService bool + useMaglev bool + topologyMode string } func (s *servicePortAnnotations) ReapTerminatingUDP() bool { @@ -414,6 +487,14 @@ func (s *servicePortAnnotations) ExcludeService() bool { return s.excludeService } +func (s *servicePortAnnotations) UseMaglev() bool { + return s.useMaglev +} + +func (s *servicePortAnnotations) TopologyMode() string { + return s.topologyMode +} + type servicePort struct { k8sp.ServicePort servicePortAnnotations @@ -435,15 +516,14 @@ func makeServiceInfo(_ *v1.ServicePort, s *v1.Service, baseSvc *k8sp.BaseService } } -out: - return svc -} + if a, ok := s.Annotations[ExternalTrafficStrategyAnnotation]; ok && strings.EqualFold(a, ExternalTrafficStrategyMaglev) { + svc.useMaglev = true + } -type alwaysHealthy struct{} + if v, ok := s.Annotations[v1.AnnotationTopologyMode]; ok { + svc.topologyMode = v + } -func (a *alwaysHealthy) Health() healthcheck.ProxyHealth { - return healthcheck.ProxyHealth{Healthy: true} +out: + return svc } - -func (a *alwaysHealthy) QueuedUpdate(v1.IPFamily) {} -func (a *alwaysHealthy) Updated(v1.IPFamily) {} diff --git a/felix/bpf/proxy/proxy_bench_test.go b/felix/bpf/proxy/proxy_bench_test.go index 2cad81f2bb7..c935535b7de 100644 --- a/felix/bpf/proxy/proxy_bench_test.go +++ b/felix/bpf/proxy/proxy_bench_test.go @@ -47,8 +47,10 @@ func benchmarkProxyUpdates(b *testing.B, svcN, epsN int) { &mock.DummyMap{}, &mock.DummyMap{}, &mock.DummyMap{}, + &mock.DummyMap{}, proxy.NewRTCache(), nil, + maglevLUTSize, ) Expect(err).ShouldNot(HaveOccurred()) @@ -104,7 +106,7 @@ func BenchmarkProxyUpdates(b *testing.B) { func makeSvcs(n int) []runtime.Object { svcs := make([]runtime.Object, n) - for i := 0; i < n; i++ { + for i := range n { ip := net.IPv4(10, byte((i&0xff0000)>>16), byte((i&0xff00)>>8), byte(i&0xff)) svcs[i] = &v1.Service{ TypeMeta: typeMetaV1("Service"), @@ -131,9 +133,9 @@ func makeSvcs(n int) []runtime.Object { func makeEps(sn, ep int) []runtime.Object { eps := make([]runtime.Object, sn) - for i := 0; i < sn; i++ { + for i := range sn { addrs := make([]string, ep) - for a := 0; a < ep; a++ { + for a := range ep { addrs[a] = fmt.Sprintf("10.11.12.%d", a) } ep := &discoveryv1.EndpointSlice{ diff --git a/felix/bpf/proxy/proxy_suite_test.go b/felix/bpf/proxy/proxy_suite_test.go index e7e31a009b1..8e0d85ecd34 100644 --- a/felix/bpf/proxy/proxy_suite_test.go +++ b/felix/bpf/proxy/proxy_suite_test.go @@ -17,11 +17,13 @@ package proxy_test import ( "testing" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" ) func TestProxy(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "BPF Proxy Suite") + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/felix_bpf_proxy_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/bpf/proxy", suiteConfig, reporterConfig) } diff --git a/felix/bpf/proxy/proxy_test.go b/felix/bpf/proxy/proxy_test.go index 7e58ad217e1..cf426f8d0d7 100644 --- a/felix/bpf/proxy/proxy_test.go +++ b/felix/bpf/proxy/proxy_test.go @@ -18,9 +18,10 @@ import ( "fmt" "net" "runtime" + "strings" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" discovery "k8s.io/api/discovery/v1" @@ -33,7 +34,7 @@ import ( "github.com/projectcalico/calico/lib/std/ptr" ) -func log(format string, a ...interface{}) { +func log(format string, a ...any) { fmt.Fprintf(GinkgoWriter, format, a...) } @@ -712,14 +713,14 @@ func (s *mockSyncer) checkState(f func(proxy.DPSyncerState)) { case <-tickC: _, file, line, _ := runtime.Caller(1) - var msg string + var msg strings.Builder for _, f := range fails { - msg += "\n" + f + msg.WriteString("\n" + f) } Fail(fmt.Sprintf( "checkState timed out at File: %s Line: %d, last failed expectations: %s", - file, line, msg, + file, line, msg.String(), )) } } diff --git a/felix/bpf/proxy/service_type_change_test.go b/felix/bpf/proxy/service_type_change_test.go index 6ffee19626b..10db372c6fa 100644 --- a/felix/bpf/proxy/service_type_change_test.go +++ b/felix/bpf/proxy/service_type_change_test.go @@ -18,7 +18,7 @@ import ( "context" "net" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" discoveryv1 "k8s.io/api/discovery/v1" @@ -32,6 +32,7 @@ import ( "github.com/projectcalico/calico/felix/bpf/nat" proxy "github.com/projectcalico/calico/felix/bpf/proxy" "github.com/projectcalico/calico/felix/ip" + felixproto "github.com/projectcalico/calico/felix/proto" ) var _ = Describe("BPF service type change", func() { @@ -87,6 +88,7 @@ var _ = Describe("BPF service type change", func() { bpfMaps := &bpfmap.IPMaps{} bpfMaps.FrontendMap = newMockNATMap() bpfMaps.BackendMap = newMockNATBackendMap() + bpfMaps.MaglevMap = newMockMaglevMap() bpfMaps.AffinityMap = newMockAffinityMap() bpfMaps.CtMap = mock.NewMockMap(conntrack.MapParams) front := bpfMaps.FrontendMap.(*mockNATMap) @@ -99,7 +101,10 @@ var _ = Describe("BPF service type change", func() { var p *proxy.KubeProxy BeforeEach(func() { - p, _ = proxy.StartKubeProxy(k8s, "test-node", bpfMaps, proxy.WithImmediateSync()) + p, _ = proxy.StartKubeProxy(k8s, "test-node", bpfMaps, proxy.WithImmediateSync(), proxy.WithMaglevLUTSize(maglevLUTSize)) + // Unblock start(), which blocks on the initial host metadata update. + p.OnUpdate(&felixproto.HostMetadataV4V6Update{Hostname: "dummy"}) + Expect(p.CompleteDeferredWork()).To(Succeed()) p.OnHostIPsUpdate([]net.IP{initIP}) }) diff --git a/felix/bpf/proxy/syncer.go b/felix/bpf/proxy/syncer.go index 33b58db28d2..e968f45dd09 100644 --- a/felix/bpf/proxy/syncer.go +++ b/felix/bpf/proxy/syncer.go @@ -17,6 +17,7 @@ package proxy import ( "context" "fmt" + "hash/fnv" "net" "reflect" "strings" @@ -29,6 +30,7 @@ import ( k8sp "k8s.io/kubernetes/pkg/proxy" "github.com/projectcalico/calico/felix/bpf" + "github.com/projectcalico/calico/felix/bpf/consistenthash" "github.com/projectcalico/calico/felix/bpf/maps" "github.com/projectcalico/calico/felix/bpf/nat" "github.com/projectcalico/calico/felix/bpf/routes" @@ -114,9 +116,10 @@ type stickyFrontend struct { type Syncer struct { ipFamily int - bpfSvcs *cachingmap.CachingMap[nat.FrontendKeyInterface, nat.FrontendValue] - bpfEps *cachingmap.CachingMap[nat.BackendKey, nat.BackendValueInterface] - bpfAff maps.Map + bpfSvcs *cachingmap.CachingMap[nat.FrontendKeyInterface, nat.FrontendValue] + bpfEps *cachingmap.CachingMap[nat.BackendKey, nat.BackendValueInterface] + bpfMaglevEps *cachingmap.CachingMap[nat.MaglevBackendKeyInterface, nat.BackendValueInterface] + bpfAff maps.Map nextSvcID uint32 @@ -130,6 +133,9 @@ type Syncer struct { newEpsMap k8sp.EndpointsMap prevSvcMap map[svcKey]svcInfo prevEpsMap k8sp.EndpointsMap + + maglevLUTSize int + // active Maps contain all active svcs endpoints at the end of an iteration activeSvcsMap map[ipPortProto]uint32 activeEpsMap map[uint32]map[ipPort]struct{} @@ -156,6 +162,7 @@ type Syncer struct { newFrontendKey func(addr net.IP, port uint16, protocol uint8) nat.FrontendKeyInterface newFrontendKeySrc func(addr net.IP, port uint16, protocol uint8, cidr ip.CIDR) nat.FrontendKeyInterface newBackendValue func(addr net.IP, port uint16) nat.BackendValueInterface + newMaglevKey func(svcID, ordinal uint32) nat.MaglevBackendKeyInterface affinityKeyFromBytes func([]byte) nat.AffinityKeyInterface affinityValueFromBytes func([]byte) nat.AffinityValueInterface @@ -211,9 +218,10 @@ func uniqueIPs(ips []net.IP) []net.IP { // NewSyncer returns a new Syncer func NewSyncer(family int, nodePortIPs []net.IP, - frontendMap maps.MapWithExistsCheck, backendMap maps.MapWithExistsCheck, + frontendMap, backendMap, maglevMap maps.MapWithExistsCheck, affmap maps.Map, rt Routes, excludedCIDRs *ip.CIDRTrie, + maglevLUTSize int, ) (*Syncer, error) { s := &Syncer{ @@ -225,6 +233,7 @@ func NewSyncer(family int, nodePortIPs []net.IP, prevEpsMap: make(k8sp.EndpointsMap), stop: make(chan struct{}), excludedCIDRs: excludedCIDRs, + maglevLUTSize: maglevLUTSize, } switch family { @@ -237,9 +246,14 @@ func NewSyncer(family int, nodePortIPs []net.IP, maps.NewTypedMap[nat.BackendKey, nat.BackendValueInterface]( backendMap, nat.BackendKeyFromBytes, nat.BackendValueFromBytes, )) + s.bpfMaglevEps = cachingmap.New[nat.MaglevBackendKeyInterface, nat.BackendValueInterface](maglevMap.GetName(), + maps.NewTypedMap[nat.MaglevBackendKeyInterface, nat.BackendValueInterface]( + maglevMap, nat.MaglevBackendKeyFromBytes, nat.BackendValueFromBytes, + )) s.newFrontendKey = nat.NewNATKeyIntf s.newFrontendKeySrc = nat.NewNATKeySrcIntf s.newBackendValue = nat.NewNATBackendValueIntf + s.newMaglevKey = nat.NewMaglevBackendKeyIntf s.affinityKeyFromBytes = nat.AffinityKeyIntfFromBytes s.affinityValueFromBytes = nat.AffinityValueIntfFromBytes case 6: @@ -251,9 +265,14 @@ func NewSyncer(family int, nodePortIPs []net.IP, maps.NewTypedMap[nat.BackendKey, nat.BackendValueInterface]( backendMap, nat.BackendKeyFromBytes, nat.BackendValueV6FromBytes, )) + s.bpfMaglevEps = cachingmap.New[nat.MaglevBackendKeyInterface, nat.BackendValueInterface](maglevMap.GetName(), + maps.NewTypedMap[nat.MaglevBackendKeyInterface, nat.BackendValueInterface]( + maglevMap, nat.MaglevBackendKeyV6FromBytes, nat.BackendValueV6FromBytes, + )) s.newFrontendKey = nat.NewNATKeyV6Intf s.newFrontendKeySrc = nat.NewNATKeyV6SrcIntf s.newBackendValue = nat.NewNATBackendValueV6Intf + s.newMaglevKey = nat.NewMaglevBackendKeyV6Intf s.affinityKeyFromBytes = nat.AffinityKeyV6IntfFromBytes s.affinityValueFromBytes = nat.AffinityValueV6IntfFromBytes default: @@ -272,6 +291,11 @@ func (s *Syncer) loadOrigs() error { if err != nil { return err } + + err = s.bpfMaglevEps.LoadCacheFromDataplane() + if err != nil { + return err + } return nil } @@ -358,7 +382,7 @@ func (s *Syncer) startupBuildPrev(state DPSyncerState) error { if count > 0 { s.prevEpsMap[svckey.sname] = make([]k8sp.Endpoint, 0, count) } - for i := 0; i < count; i++ { + for i := range count { epk := nat.NewNATBackendKey(id, uint32(i)) ep, ok := s.bpfEps.Dataplane().Get(epk) if !ok { @@ -395,7 +419,7 @@ func (s *Syncer) startupSync(state DPSyncerState) error { return nil } -func (s *Syncer) applySvc(skey svcKey, sinfo Service, eps []k8sp.Endpoint) error { +func (s *Syncer) applySvc(skey svcKey, sinfo Service, eps []k8sp.Endpoint, maglevEPs []k8sp.Endpoint) error { var id uint32 old, exists := s.prevSvcMap[skey] @@ -404,11 +428,10 @@ func (s *Syncer) applySvc(skey svcKey, sinfo Service, eps []k8sp.Endpoint) error } else { id = s.newSvcID() } - count, local, err := s.updateService(skey, sinfo, id, eps) + count, local, err := s.updateService(skey, sinfo, id, eps, maglevEPs) if err != nil { return err } - s.newSvcMap[skey] = svcInfo{ id: id, count: count, @@ -448,12 +471,13 @@ func (s *Syncer) addActiveEps(id uint32, svc Service, eps []k8sp.Endpoint) { func (s *Syncer) applyExpandedNP(sname k8sp.ServicePortName, sinfo k8sp.ServicePort, eps []k8sp.Endpoint, node ip.Addr, nport int) error { + skey := getSvcKey(sname, getSvcKeyExtra(svcTypeNodePortRemote, node.AsNetIP())) si := serviceInfoFromK8sServicePort(sinfo) si.clusterIP = node.AsNetIP() si.port = nport - if err := s.applySvc(skey, si, eps); err != nil { + if err := s.applySvc(skey, si, eps, nil); err != nil { return fmt.Errorf("apply NodePortRemote for %s node %s", sname, node) } @@ -539,6 +563,14 @@ func (s *Syncer) applyDerived( skey = getSvcKey(sname, getSvcKeyExtra(t, sinfo.ClusterIP())) flags := uint32(0) + if sinfo.UseMaglev() { + switch t { + case svcTypeNodePort, svcTypeNodePortRemote: + log.WithField("service", sname).Warn("Refusing to mark service of type: NodePort with Maglev flag") + default: + flags |= nat.NATFlgMaglev + } + } switch t { case svcTypeNodePort, svcTypeLoadBalancer, svcTypeNodePortRemote: @@ -590,6 +622,7 @@ func (s *Syncer) apply(state DPSyncerState) error { s.newSvcMap = make(map[svcKey]svcInfo, len(state.SvcMap)) s.newEpsMap = make(k8sp.EndpointsMap, len(state.EpsMap)) nodeZone := state.NodeZone + nodeName := state.Hostname var expNPMisses []*expandMiss @@ -597,30 +630,37 @@ func (s *Syncer) apply(state DPSyncerState) error { // let CachingMap calculate deltas... s.bpfSvcs.Desired().DeleteAll() s.bpfEps.Desired().DeleteAll() + s.bpfMaglevEps.Desired().DeleteAll() // insert or update existing services for sname, sinfo := range state.SvcMap { svc := sinfo.(Service) - log.WithField("service", sname).Debug("Applying service") skey := getSvcKey(sname, "") + topologyMode := svc.TopologyMode() + + // Topology Aware Routing has precedence over Traffic Distribution. + eps, topologyAwareApplied := FilterEpsByTopologyAwareRouting(state.EpsMap[sname], topologyMode, nodeZone) + if !topologyAwareApplied { + log.Debugf("Topology Aware Routing not applied for service %s, mode %s. Trying Traffic Distribution...", sname, topologyMode) + // If Traffic Distribution can't be applied, it will return the original endpoints (cluster-wide). + eps = FilterEpsByTrafficDistribution(state.EpsMap[sname], nodeName, nodeZone) + } else { + log.Debugf("Topology Aware Routing applied for service %s, mode %s.", sname, topologyMode) + } - eps := make([]k8sp.Endpoint, 0, len(state.EpsMap[sname])) - for _, ep := range state.EpsMap[sname] { - zoneHints := ep.ZoneHints() - if ep.IsReady() || ep.IsTerminating() { - if ShouldAppendTopologyAwareEndpoint(nodeZone, "", zoneHints) { - eps = append(eps, ep) - } else { - log.Debugf("Topology Aware Hints: for Endpoint: '%s' however Zone: '%s' does not match Zone Hints: '%v'\n", - ep.IP(), - nodeZone, - zoneHints) + var maglevEPs []k8sp.Endpoint + if svc.UseMaglev() { + ch := s.newConsistentHash() + for _, ep := range eps { + if ep.IsReady() { + ch.AddBackend(ep) } } + maglevEPs = ch.Generate() } - err := s.applySvc(skey, svc, eps) + err := s.applySvc(skey, svc, eps, maglevEPs) if err != nil { return err } @@ -637,6 +677,7 @@ func (s *Syncer) apply(state DPSyncerState) error { log.Debugf("LB status IP %s", lbIP) } } + // N.B. we assume that k8s provide us with no duplicities for _, extIP := range svc.ExternalIPs() { extInfo := serviceInfoFromK8sServicePort(svc) @@ -683,7 +724,10 @@ func (s *Syncer) apply(state DPSyncerState) error { if err != nil { return err } - // Update the frontends, after this is done we should be handling packets correctly. + err = s.bpfMaglevEps.ApplyAllChanges() + if err != nil { + return err + } err = s.bpfSvcs.ApplyUpdatesOnly() if err != nil { return err @@ -747,9 +791,8 @@ func (s *Syncer) Apply(state DPSyncerState) error { return s.cleanupSticky() } -func (s *Syncer) updateService(skey svcKey, sinfo Service, id uint32, eps []k8sp.Endpoint) (int, int, error) { +func (s *Syncer) updateService(skey svcKey, sinfo Service, id uint32, eps []k8sp.Endpoint, maglevEPs []k8sp.Endpoint) (int, int, error) { cpEps := make([]k8sp.Endpoint, 0, len(eps)) - cnt := 0 local := 0 @@ -797,6 +840,11 @@ func (s *Syncer) updateService(skey svcKey, sinfo Service, id uint32, eps []k8sp flags |= nat.NATFlgInternalLocal } + if sinfo.UseMaglev() && maglevEPs != nil { + flags |= nat.NATFlgMaglev + s.writeMaglevSvcBackends(id, skey, maglevEPs) + } + if err := s.writeSvc(sinfo, id, cnt, local, flags); err != nil { return 0, 0, err } @@ -816,6 +864,23 @@ func (s *Syncer) updateService(skey svcKey, sinfo Service, id uint32, eps []k8sp return cnt, local, nil } +func (s *Syncer) newConsistentHash() *consistenthash.ConsistentHash { + return consistenthash.New( + s.maglevLUTSize, + fnv.New32(), fnv.New32(), + ) +} + +// writeMaglevBackends takes frontend info, and a pre-populated CH module (backends already programmed). +func (s *Syncer) writeMaglevSvcBackends(sID uint32, skey svcKey, maglevEPs []k8sp.Endpoint) { + for i, b := range maglevEPs { + mKey := s.newMaglevKey(sID, uint32(i)) + mVal := s.newBackendValue(net.ParseIP(b.IP()), uint16(b.Port())) + s.bpfMaglevEps.Desired().Set(mKey, mVal) + } + log.WithFields(log.Fields{"service": skey.sname, "id": sID}).Info("Wrote Maglev service backends to LUT") +} + func (s *Syncer) writeSvcBackend(svcID uint32, idx uint32, ep k8sp.Endpoint) error { if log.GetLevel() >= log.DebugLevel { log.WithFields(log.Fields{ @@ -1442,7 +1507,7 @@ func (info *serviceInfo) InternalPolicyLocal() bool { } // K8sServicePortOption defines options for NewK8sServicePort -type K8sServicePortOption func(interface{}) +type K8sServicePortOption func(any) // NewK8sServicePort creates a new k8s ServicePort func NewK8sServicePort(clusterIP net.IP, port int, proto v1.Protocol, @@ -1511,35 +1576,35 @@ func cidrEqual[T ip.IPOrIPNet](a, b []T) bool { // K8sSvcWithLoadBalancerIPs set LoadBalancerIPStrings func K8sSvcWithLoadBalancerIPs(ips []net.IP) K8sServicePortOption { - return func(s interface{}) { + return func(s any) { s.(*servicePort).ServicePort.(*serviceInfo).loadBalancerVIPs = ips } } // K8sSvcWithLBSourceRangeIPs sets LBSourcePortRangeIPs func K8sSvcWithLBSourceRangeIPs(ips []*net.IPNet) K8sServicePortOption { - return func(s interface{}) { + return func(s any) { s.(*servicePort).ServicePort.(*serviceInfo).loadBalancerSourceRanges = ips } } // K8sSvcWithExternalIPs sets ExternalIPs func K8sSvcWithExternalIPs(ips []net.IP) K8sServicePortOption { - return func(s interface{}) { + return func(s any) { s.(*servicePort).ServicePort.(*serviceInfo).externalIPs = ips } } // K8sSvcWithNodePort sets the nodeport func K8sSvcWithNodePort(np int) K8sServicePortOption { - return func(s interface{}) { + return func(s any) { s.(*servicePort).ServicePort.(*serviceInfo).nodePort = np } } // K8sSvcWithLocalOnly sets OnlyNodeLocalEndpoints=true func K8sSvcWithLocalOnly() K8sServicePortOption { - return func(s interface{}) { + return func(s any) { s.(*servicePort).ServicePort.(*serviceInfo).nodeLocalExternal = true s.(*servicePort).ServicePort.(*serviceInfo).nodeLocalInternal = true } @@ -1547,14 +1612,20 @@ func K8sSvcWithLocalOnly() K8sServicePortOption { // K8sSvcWithStickyClientIP sets ServiceAffinityClientIP to seconds func K8sSvcWithStickyClientIP(seconds int) K8sServicePortOption { - return func(s interface{}) { + return func(s any) { s.(*servicePort).ServicePort.(*serviceInfo).stickyMaxAgeSeconds = seconds s.(*servicePort).ServicePort.(*serviceInfo).sessionAffinityType = v1.ServiceAffinityClientIP } } func K8sSvcWithReapTerminatingUDP() K8sServicePortOption { - return func(s interface{}) { + return func(s any) { s.(*servicePort).reapTerminatingUDP = true } } + +func K8sSvcWithTopologyMode(value string) K8sServicePortOption { + return func(s any) { + s.(*servicePort).topologyMode = value + } +} diff --git a/felix/bpf/proxy/syncer_bench_test.go b/felix/bpf/proxy/syncer_bench_test.go index 039989f0d83..04c98dc6ba7 100644 --- a/felix/bpf/proxy/syncer_bench_test.go +++ b/felix/bpf/proxy/syncer_bench_test.go @@ -43,7 +43,7 @@ func makeSvcEpsPair(svcIdx, epCnt, port int, opts ...K8sServicePortOption) (k8sp ) eps := make([]k8sp.Endpoint, epCnt) - for j := 0; j < epCnt; j++ { + for j := range epCnt { eps[j] = NewEndpointInfo("11.1.1.1", j+1) } @@ -65,7 +65,7 @@ func makeState(svcCnt, epCnt int, opts ...K8sServicePortOption) DPSyncerState { EpsMap: make(k8sp.EndpointsMap, epCnt), } - for i := 0; i < svcCnt; i++ { + for i := range svcCnt { sk := makeSvcKey(i) state.SvcMap[sk], state.EpsMap[sk] = makeSvcEpsPair(i, epCnt, 1234, opts...) } @@ -164,15 +164,17 @@ func runBenchmarkServiceUpdate(b *testing.B, svcCnt, epCnt int, mockMaps bool, o b.StopTimer() state := makeState(svcCnt, epCnt, opts...) - + maglevLUTSize := 31 if mockMaps { syncer, err = NewSyncer(4, []net.IP{net.IPv4(1, 1, 1, 1)}, &mock.DummyMap{}, &mock.DummyMap{}, &mock.DummyMap{}, + &mock.DummyMap{}, NewRTCache(), nil, + maglevLUTSize, ) Expect(err).ShouldNot(HaveOccurred()) } else { @@ -188,8 +190,10 @@ func runBenchmarkServiceUpdate(b *testing.B, svcCnt, epCnt int, mockMaps bool, o &mock.DummyMap{}, &mock.DummyMap{}, &mock.DummyMap{}, + &mock.DummyMap{}, NewRTCache(), nil, + maglevLUTSize, ) Expect(err).ShouldNot(HaveOccurred()) } @@ -232,7 +236,7 @@ func BenchmarkServiceUpdate(b *testing.B) { dynaNodePort := func() K8sServicePortOption { np := 0 - return func(s interface{}) { + return func(s any) { np = (np + 1) % 30000 K8sSvcWithNodePort(30000 + np)(s) } diff --git a/felix/bpf/proxy/syncer_test.go b/felix/bpf/proxy/syncer_test.go index 212db8cd6b1..6c1bf12015b 100644 --- a/felix/bpf/proxy/syncer_test.go +++ b/felix/bpf/proxy/syncer_test.go @@ -20,11 +20,12 @@ import ( "sync" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/sirupsen/logrus" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" k8sp "k8s.io/kubernetes/pkg/proxy" "github.com/projectcalico/calico/felix/bpf" @@ -42,18 +43,24 @@ func init() { logrus.SetLevel(logrus.DebugLevel) } +var ( + maglevLUTSize = 31 +) + var _ = Describe("BPF Syncer", func() { var ( svcs *mockNATMap eps *mockNATBackendMap + mgEps *mockMaglevMap aff *mockAffinityMap ct *mock.Map ctCleanup *mock.Map - s *proxy.Syncer - connScan *conntrack.Scanner - state proxy.DPSyncerState - rt *proxy.RTCache + s *proxy.Syncer + syncerMutex sync.Mutex + connScan *conntrack.Scanner + state proxy.DPSyncerState + rt *proxy.RTCache ) nodeIPs := []net.IP{net.IPv4(192, 168, 0, 1), net.IPv4(10, 123, 0, 1)} @@ -65,16 +72,23 @@ var _ = Describe("BPF Syncer", func() { }, } + applySyncer := func(m *sync.Mutex, s *proxy.Syncer, state proxy.DPSyncerState) error { + m.Lock() + defer m.Unlock() + return s.Apply(state) + } + BeforeEach(func() { svcs = newMockNATMap() eps = newMockNATBackendMap() + mgEps = newMockMaglevMap() aff = newMockAffinityMap() ct = mock.NewMockMap(conntrack.MapParams) ctCleanup = mock.NewMockMap(conntrack.MapParamsCleanup) rt = proxy.NewRTCache() - s, _ = proxy.NewSyncer(4, nodeIPs, svcs, eps, aff, rt, nil) + s, _ = proxy.NewSyncer(4, nodeIPs, svcs, eps, mgEps, aff, rt, nil, maglevLUTSize) ep := proxy.NewEndpointInfo("10.1.0.1", 5555, proxy.EndpointInfoOptIsReady(true)) state = proxy.DPSyncerState{ @@ -231,7 +245,7 @@ var _ = Describe("BPF Syncer", func() { }) Expect(err).NotTo(HaveOccurred()) - Expect(cnt).To(Equal(5)) + Expect(cnt).To(Equal(7)) })) udpSvcKey := k8sp.ServicePortName{ @@ -272,7 +286,7 @@ var _ = Describe("BPF Syncer", func() { }) Expect(err).NotTo(HaveOccurred()) - Expect(cnt).To(Equal(4)) + Expect(cnt).To(Equal(6)) })) By("deleting the udp-service backend", makestep(func() { @@ -351,7 +365,7 @@ var _ = Describe("BPF Syncer", func() { }) Expect(err).NotTo(HaveOccurred()) - Expect(cnt).To(Equal(2)) + Expect(cnt).To(Equal(6)) })) By("not programming eps without a service - non reachables", makestep(func() { @@ -498,7 +512,7 @@ var _ = Describe("BPF Syncer", func() { })) By("resyncing after creating a new syncer with the same result", makestep(func() { - s, _ = proxy.NewSyncer(4, nodeIPs, svcs, eps, aff, rt, nil) + s, _ = proxy.NewSyncer(4, nodeIPs, svcs, eps, mgEps, aff, rt, nil, maglevLUTSize) checkAfterResync() })) @@ -506,7 +520,7 @@ var _ = Describe("BPF Syncer", func() { svcs.m[nat.NewNATKey(net.IPv4(5, 5, 5, 5), 1111, 6)] = nat.NewNATValue(0xdeadbeef, 2, 2, 0) eps.m[nat.NewNATBackendKey(0xdeadbeef, 0)] = nat.NewNATBackendValue(net.IPv4(6, 6, 6, 6), 666) eps.m[nat.NewNATBackendKey(0xdeadbeef, 1)] = nat.NewNATBackendValue(net.IPv4(7, 7, 7, 7), 777) - s, _ = proxy.NewSyncer(4, nodeIPs, svcs, eps, aff, rt, nil) + s, _ = proxy.NewSyncer(4, nodeIPs, svcs, eps, mgEps, aff, rt, nil, maglevLUTSize) checkAfterResync() })) @@ -612,7 +626,7 @@ var _ = Describe("BPF Syncer", func() { val, ok := svcs.m[nat.NewNATKey(net.IPv4(10, 0, 0, 2), 2222, proxy.ProtoV1ToIntPanic(v1.ProtocolTCP))] Expect(ok).To(BeTrue()) count := val.Count() - for i := uint32(0); i < count; i++ { + for i := range count { Expect(eps.m).To(HaveKey(nat.NewNATBackendKey(val.ID(), i))) } @@ -636,7 +650,7 @@ var _ = Describe("BPF Syncer", func() { val, ok = svcs.m[nat.NewNATKey(net.IPv4(10, 0, 0, 2), 2222, proxy.ProtoV1ToIntPanic(v1.ProtocolTCP))] Expect(ok).To(BeTrue()) Expect(val.Count()).To(Equal(uint32(0))) - for i := uint32(0); i < count; i++ { + for i := range count { Expect(eps.m).NotTo(HaveKey(nat.NewNATBackendKey(val.ID(), i))) } })) @@ -654,7 +668,7 @@ var _ = Describe("BPF Syncer", func() { By("inserting non-local eps for a NodePort - no route", makestep(func() { // use the meta node IP for nodeports as well - s, _ = proxy.NewSyncer(4, append(nodeIPs, net.IPv4(255, 255, 255, 255)), svcs, eps, aff, rt, nil) + s, _ = proxy.NewSyncer(4, append(nodeIPs, net.IPv4(255, 255, 255, 255)), svcs, eps, mgEps, aff, rt, nil, maglevLUTSize) state.SvcMap[svcKey2] = proxy.NewK8sServicePort( net.IPv4(10, 0, 0, 2), 2222, @@ -689,7 +703,7 @@ var _ = Describe("BPF Syncer", func() { s.SetTriggerFn(func() { go func() { logrus.Info("Syncer triggered") - err := s.Apply(state) + err := applySyncer(&syncerMutex, s, state) logrus.WithError(err).Info("Syncer result") }() }) @@ -807,7 +821,7 @@ var _ = Describe("BPF Syncer", func() { By("inserting only non-local eps for a NodePort - multiple nodes & pods/node", makestep(func() { // use the meta node IP for nodeports as well - s, _ = proxy.NewSyncer(4, append(nodeIPs, net.IPv4(255, 255, 255, 255)), svcs, eps, aff, rt, nil) + s, _ = proxy.NewSyncer(4, append(nodeIPs, net.IPv4(255, 255, 255, 255)), svcs, eps, mgEps, aff, rt, nil, maglevLUTSize) state.SvcMap[svcKey2] = proxy.NewK8sServicePort( net.IPv4(10, 0, 0, 2), 2222, @@ -836,7 +850,7 @@ var _ = Describe("BPF Syncer", func() { ip.FromString("10.123.0.113").(ip.V4Addr)), ) - err := s.Apply(state) + err := applySyncer(&syncerMutex, s, state) Expect(err).NotTo(HaveOccurred()) checkAfterResync = func() { @@ -887,7 +901,7 @@ var _ = Describe("BPF Syncer", func() { By("restarting Syncer to check if NodePortRemotes are picked up correctly", makestep(func() { // use the meta node IP for nodeports as well - s, _ = proxy.NewSyncer(4, append(nodeIPs, net.IPv4(255, 255, 255, 255)), svcs, eps, aff, rt, nil) + s, _ = proxy.NewSyncer(4, append(nodeIPs, net.IPv4(255, 255, 255, 255)), svcs, eps, mgEps, aff, rt, nil, maglevLUTSize) err := s.Apply(state) Expect(err).NotTo(HaveOccurred()) @@ -1110,7 +1124,7 @@ var _ = Describe("BPF Syncer", func() { }) Expect(err).NotTo(HaveOccurred()) - Expect(cnt).To(Equal(0)) + Expect(cnt).To(Equal(6)) })) By("checking endpointslice terminating status should be included in endpointslice collection for processing", makestep(func() { @@ -1166,7 +1180,7 @@ var _ = Describe("BPF Syncer", func() { // Expect 6x new conntrack entries from 3x pods NAT forward and 3x pods NAT reverse total. Expect(err).NotTo(HaveOccurred()) - Expect(cnt).To(Equal(6)) + Expect(cnt).To(Equal(12)) })) }) @@ -1241,6 +1255,122 @@ var _ = Describe("BPF Syncer", func() { }) }) + + Describe("Topology-aware routing and traffic distribution integration", func() { + var ( + svcKeyTopo = k8sp.ServicePortName{ + NamespacedName: types.NamespacedName{ + Namespace: "default", + Name: "topology-service", + }, + } + nodeName = "node-a" + nodeZone = "zone-1" + ) + + BeforeEach(func() { + // Reset state and maps for each test. + svcs = newMockNATMap() + eps = newMockNATBackendMap() + mgEps = newMockMaglevMap() + aff = newMockAffinityMap() + rt = proxy.NewRTCache() + s, _ = proxy.NewSyncer(4, nodeIPs, svcs, eps, mgEps, aff, rt, nil, maglevLUTSize) + }) + + type testCase struct { + name string + service k8sp.ServicePortMap + endpoints k8sp.EndpointsMap + expectBack []string + } + + DescribeTable("endpoint selection behavior", + func(tc testCase) { + state := proxy.DPSyncerState{ + SvcMap: tc.service, + EpsMap: tc.endpoints, + Hostname: nodeName, + NodeZone: nodeZone, + } + + err := s.Apply(state) + Expect(err).NotTo(HaveOccurred()) + + var backendIPs []string + for _, v := range eps.m { + backendIPs = append(backendIPs, v.Addr().String()) + } + + Expect(backendIPs).To(ConsistOf(tc.expectBack)) + }, + Entry("topology-aware: prefers routing over traffic distribution hints", testCase{ + service: k8sp.ServicePortMap{ + svcKeyTopo: svc("10.0.0.50", 8080).topology("Auto").build(), + }, + endpoints: k8sp.EndpointsMap{ + svcKeyTopo: []k8sp.Endpoint{ + ep("10.50.0.1", 8081).zones(nodeZone).build(), + ep("10.50.0.2", 8082).zones(nodeZone).nodes("node-b").build(), + ep("10.50.0.3", 8083).zones("zone-2").nodes("node-c").build(), + ep("10.50.0.4", 8084).zones("zone-2").nodes(nodeName).build(), + }, + }, + expectBack: []string{"10.50.0.1", "10.50.0.2"}, + }), + Entry("topology-aware:falls back to all endpoints if no hints found", testCase{ + service: k8sp.ServicePortMap{ + svcKeyTopo: svc("10.0.0.50", 8080).topology("Auto").build(), + }, + endpoints: k8sp.EndpointsMap{ + svcKeyTopo: []k8sp.Endpoint{ + ep("10.52.0.3", 8083).zones(nodeZone).nodes("node-c").build(), + ep("10.52.0.4", 8084).build(), + }, + }, + expectBack: []string{"10.52.0.3", "10.52.0.4"}, + }), + Entry("traffic distribution: uses hints when topologyMode is disabled", testCase{ + service: k8sp.ServicePortMap{ + svcKeyTopo: svc("10.0.0.53", 8080).topology("Disabled").build(), + }, + endpoints: k8sp.EndpointsMap{ + svcKeyTopo: []k8sp.Endpoint{ + ep("10.53.0.1", 8081).nodes(nodeName).build(), + ep("10.53.0.2", 8082).zones(nodeZone).build(), + ep("10.53.0.3", 8083).build(), + }, + }, + expectBack: []string{"10.53.0.1"}, + }), + Entry("traffic distribution: falls back to zone-local endpoints when no node-local endpoints exist", testCase{ + service: k8sp.ServicePortMap{ + svcKeyTopo: svc("10.0.0.51", 8080).build(), + }, + endpoints: k8sp.EndpointsMap{ + svcKeyTopo: []k8sp.Endpoint{ + ep("10.51.0.2", 8082).zones(nodeZone).nodes("node-b").build(), + ep("10.51.0.3", 8083).zones("zone-2").nodes("node-c").build(), + ep("10.51.0.4", 8084).build(), + }, + }, + expectBack: []string{"10.51.0.2"}, + }), + Entry("falls back to all endpoints when no endpoints match locality", testCase{ + service: k8sp.ServicePortMap{ + svcKeyTopo: svc("10.0.0.52", 8080).build(), + }, + endpoints: k8sp.EndpointsMap{ + svcKeyTopo: []k8sp.Endpoint{ + ep("10.52.0.1", 8081).zones("zone-2").build(), + ep("10.52.0.2", 8082).nodes("node-c").build(), + ep("10.52.0.3", 8083).build(), + }, + }, + expectBack: []string{"10.52.0.1", "10.52.0.2", "10.52.0.3"}, + }), + ) + }) }) type mockNATMap struct { @@ -1429,6 +1559,99 @@ func (m *mockNATBackendMap) Delete(k []byte) error { return nil } +type mockMaglevMap struct { + mock.DummyMap + sync.Mutex + m map[nat.MaglevBackendKey]nat.BackendValue +} + +func (m *mockMaglevMap) MapFD() maps.FD { + panic("implement me") +} + +func newMockMaglevMap() *mockMaglevMap { + return &mockMaglevMap{ + m: make(map[nat.MaglevBackendKey]nat.BackendValue), + } +} + +func (m *mockMaglevMap) GetName() string { + return "maglev" +} + +func (m *mockMaglevMap) Path() string { + return "/sys/fs/bpf/tc/maglev" +} + +func (m *mockMaglevMap) Iter(iter maps.IterCallback) error { + m.Lock() + defer m.Unlock() + + ks := len(nat.MaglevBackendKey{}) + vs := len(nat.BackendValue{}) + for k, v := range m.m { + action := iter(k[:ks], v[:vs]) + if action == maps.IterDelete { + delete(m.m, k) + } + } + + return nil +} + +func (m *mockMaglevMap) Update(k, v []byte) error { + logrus.WithFields(logrus.Fields{ + "k": k, "v": v, + }).Debug("mockMaglevMap.Update()") + + m.Lock() + defer m.Unlock() + + ks := len(nat.MaglevBackendKey{}) + if len(k) != ks { + return fmt.Errorf("expected key size %d got %d", ks, len(k)) + } + vs := len(nat.BackendValue{}) + if len(v) != vs { + return fmt.Errorf("expected value size %d got %d", vs, len(v)) + } + + var key nat.MaglevBackendKey + copy(key[:ks], k[:ks]) + + var val nat.BackendValue + copy(val[:vs], v[:vs]) + + m.m[key] = val + + return nil +} + +func (m *mockMaglevMap) Get(k []byte) ([]byte, error) { + panic("not implemented") +} + +func (m *mockMaglevMap) Delete(k []byte) error { + logrus.WithFields(logrus.Fields{ + "k": k, + }).Debug("mockMaglevMap.Delete()") + + m.Lock() + defer m.Unlock() + + ks := len(nat.MaglevBackendKey{}) + if len(k) != ks { + return fmt.Errorf("expected key size %d got %d", ks, len(k)) + } + + var key nat.MaglevBackendKey + copy(key[:ks], k[:ks]) + + delete(m.m, key) + + return nil +} + type mockAffinityMap struct { mock.DummyMap sync.Mutex @@ -1539,3 +1762,70 @@ func ctEntriesForSvc(ct maps.Map, proto v1.Protocol, err = ct.Update(revKey.AsBytes(), val.AsBytes()) Expect(err).NotTo(HaveOccurred(), "Test failed to populate ct map with REV") } + +type svcBuilder struct { + ip net.IP + port int + protocol v1.Protocol + opts []proxy.K8sServicePortOption +} + +func svc(ip string, port int) *svcBuilder { + return &svcBuilder{ + ip: net.ParseIP(ip), + port: port, + protocol: v1.ProtocolTCP, + } +} + +func (s *svcBuilder) topology(mode string) *svcBuilder { + s.opts = append(s.opts, proxy.K8sSvcWithTopologyMode(mode)) + return s +} + +func (s *svcBuilder) build() k8sp.ServicePort { + return proxy.NewK8sServicePort( + s.ip, + s.port, + s.protocol, + s.opts..., + ) +} + +type epBuilder struct { + ip string + port int + opts []proxy.EndpoiontInfoOpt +} + +func ep(ip string, port int) *epBuilder { + return &epBuilder{ + ip: ip, + port: port, + opts: []proxy.EndpoiontInfoOpt{ + proxy.EndpointInfoOptIsReady(true), + }, + } +} + +func (e *epBuilder) nodes(names ...string) *epBuilder { + set := sets.Set[string]{} + for _, n := range names { + set[n] = struct{}{} + } + e.opts = append(e.opts, proxy.EndpointInfoOptNodeHints(set)) + return e +} + +func (e *epBuilder) zones(zones ...string) *epBuilder { + set := sets.Set[string]{} + for _, n := range zones { + set[n] = struct{}{} + } + e.opts = append(e.opts, proxy.EndpointInfoOptZoneHints(set)) + return e +} + +func (e *epBuilder) build() k8sp.Endpoint { + return proxy.NewEndpointInfo(e.ip, e.port, e.opts...) +} diff --git a/felix/bpf/proxy/topology.go b/felix/bpf/proxy/topology.go index 2a55743b7e2..dbeeb0b4bce 100644 --- a/felix/bpf/proxy/topology.go +++ b/felix/bpf/proxy/topology.go @@ -15,44 +15,136 @@ package proxy import ( + "strings" + log "github.com/sirupsen/logrus" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/sets" + k8sp "k8s.io/kubernetes/pkg/proxy" ) -//nolint:staticcheck // Ignore SA1019 deprecated until kubernetes/pkg/proxy/types.go fixes sets.String -func ShouldAppendTopologyAwareEndpoint(nodeZone string, hintsAnnotation string, zoneHints sets.Set[string]) bool { +// FilterEpsByTopologyAwareRouting filters a slice of Kubernetes endpoints based on topology-aware routing criteria. +// It returns a subset of endpoints that match the provided node zone if topology-aware routing is enabled, +// along with a boolean indicating whether topology-aware routing was applied. +// +// Topology-aware routing is applied only if the topologyMode is "Auto" (case-insensitive). +// If any endpoint is missing zone hints, or if no endpoints match the node zone, the function falls back +// to returning all endpoints to ensure safe routing behavior. +// See: https://kubernetes.io/docs/concepts/services-networking/topology-aware-routing/ +// +// Parameters: +// +// endpoints - Slice of k8sp.Endpoint objects to filter. +// topologyMode - Service annotation value indicating topology-aware routing mode. +// nodeZone - The zone label of the current node. +// +// Returns: +// +// []k8sp.Endpoint - Endpoints chosen by topology-aware routing, or all endpoints if the logic falls back. +// bool - True if topology-aware routing logic was executed for the service, false if it was not enabled. +func FilterEpsByTopologyAwareRouting(endpoints []k8sp.Endpoint, topologyMode, nodeZone string) ([]k8sp.Endpoint, bool) { + if strings.ToLower(topologyMode) != "auto" { + log.Debugf( + "Skipping topology aware endpoint filtering. Feature is enabled only when '%s' is set to 'Auto'; current value: '%s'", + v1.AnnotationTopologyMode, + topologyMode, + ) + return endpoints, false + } - // In order for an endpoint to be Topology Aware and added to endpoint collection the following must be true - // Service annotation contains: "service.kubernetes.io/topology-aware-hints: auto" - // Node label map contains: "topology.kubernetes.io/zone=ZONE" - // Endpoint slice contains hints forZone=ZONE" + eps := make([]k8sp.Endpoint, 0, len(endpoints)) + for _, ep := range endpoints { + zoneHints := ep.ZoneHints() + if !ep.IsReady() && !ep.IsTerminating() { + log.Debugf("Topology Aware Routing: ignoring Endpoint '%s' since its status is not Ready or Terminating\n", ep.IP()) + continue + } - // If all of the above match then return true such that the endpoint is Topology Aware thus added to the collection; - // If service is annotated but endpoint slice zone hint is not included in the node label entry then do not append; - // Otherwise default to appending the endpoint to the collection as before. + if zoneHints.Len() == 0 { + // One EP does not have a zone hint. We assume that a transition from or to Topology Aware Hints is underway. + // Filtering endpoints for a Service in this state would be dangerous so we falls back to using all endpoints. + // Ref: https://kubernetes.io/docs/concepts/services-networking/topology-aware-routing/#safeguards + log.Debugf("Topology Aware Routing: not applied since Endpoint %s is missing a zone hint", ep.IP()) + return endpoints, true + } - // If hints annotation is not recognized or empty then ignore Topology Aware Hints. - if hintsAnnotation != "Auto" && hintsAnnotation != "auto" { - if hintsAnnotation != "" && hintsAnnotation != "Disabled" && hintsAnnotation != "disabled" { - log.Debugf("Skipping topology aware endpoint filtering since Service has unexpected value '%s' for key '%s'\n", hintsAnnotation, v1.DeprecatedAnnotationTopologyAwareHints) + if zoneHints.Has(nodeZone) { + eps = append(eps, ep) + } else { + log.Debugf("Topology Aware Routing: ignoring Endpoint '%s' since its zone '%s' does not match Zone Hints: '%v'\n", + ep.IP(), + nodeZone, + zoneHints) } + } - return true + if len(eps) == 0 { + // If it's unable to find at least one endpoint with a hint targeting the zone it is running in, + // it falls back to using endpoints from all zones. + // Ref: https://kubernetes.io/docs/concepts/services-networking/topology-aware-routing/#safeguards + log.Debugf("Topology Aware Routing: not applied since no endpoints matched for zone: '%s'\n", nodeZone) + return endpoints, true } - // If node zone is empty then ignore Topology Aware Hints. - if len(nodeZone) == 0 { - log.Debugf("Skipping topology aware endpoint filtering since node zone is empty") - return true + return eps, true +} + +func filterEndpointsByHints(endpoints []k8sp.Endpoint, targetHint string, getHints func(k8sp.Endpoint) sets.Set[string]) []k8sp.Endpoint { + eps := make([]k8sp.Endpoint, 0, len(endpoints)) + for _, ep := range endpoints { + epHints := getHints(ep) + if !ep.IsReady() && !ep.IsTerminating() { + log.Debugf("Traffic Distribution: ignoring Endpoint '%s' since its status is not Ready or Terminating\n", ep.IP()) + continue + } + + if epHints.Has(targetHint) { + eps = append(eps, ep) + } else { + log.Debugf("Traffic Distribution: ignoring Endpoint '%s' since target '%s' does not match Hints: '%v'\n", + ep.IP(), + targetHint, + epHints) + } + } + + return eps +} + +// FilterEpsByTrafficDistribution selects endpoints based on Kubernetes traffic distribution semantics. +// It prioritizes endpoints on the same node as the client, then falls back to endpoints in the same zone, +// and finally to all available endpoints if no node or zone matches are found. +// See: https://kubernetes.io/docs/reference/networking/virtual-ips/#traffic-distribution +// +// Parameters: +// +// endpoints - List of candidate endpoints. +// nodeName - Name of the client node. +// nodeZone - Zone of the client node. +// +// Returns: +// +// A slice of endpoints selected based on the best match with traffic distribution preference. +func FilterEpsByTrafficDistribution(endpoints []k8sp.Endpoint, nodeName, nodeZone string) []k8sp.Endpoint { + // Try to prioritizes sending traffic to endpoints on the same node as the client. + eps := filterEndpointsByHints(endpoints, nodeName, func(ep k8sp.Endpoint) sets.Set[string] { + return ep.NodeHints() + }) + if len(eps) != 0 { + log.Debugf("Traffic Distribution applied for node: '%s' \n", nodeName) + return eps } - // If there are no endpoint zone hints then ignore Topology Aware Hints. - if zoneHints.Len() == 0 { - log.Debugf("Skipping topology aware endpoint filtering since one or more endpoints is missing a zone hint") - return true + // If client's node does not have any available endpoints, then fall back to "same zone" behavior. + eps = filterEndpointsByHints(endpoints, nodeZone, func(ep k8sp.Endpoint) sets.Set[string] { + return ep.ZoneHints() + }) + if len(eps) != 0 { + log.Debugf("Traffic Distribution applied for zone: '%s' \n", nodeZone) + return eps } - // Return whether zone hints contain node label zone. - return zoneHints.Has(nodeZone) + // Fall back to cluster-wide if there are no same-zone endpoints either. + log.Debugf("Traffic Distribution: not applied since no endpoints matched for node: '%s' or zone: '%s'\n", nodeName, nodeZone) + return endpoints } diff --git a/felix/bpf/proxy/topology_test.go b/felix/bpf/proxy/topology_test.go index 9240311a50a..38447d1de59 100644 --- a/felix/bpf/proxy/topology_test.go +++ b/felix/bpf/proxy/topology_test.go @@ -19,73 +19,274 @@ import ( . "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/util/sets" + k8sp "k8s.io/kubernetes/pkg/proxy" "github.com/projectcalico/calico/felix/bpf/proxy" ) -func TestShouldAppendTopologyAwareEndpoint(t *testing.T) { - testCases := []struct { - description string - nodeZone string - hintsAnnotation string - //nolint:staticcheck // Ignore SA1019 deprecated until kubernetes/pkg/proxy/types.go fixes sets.String - zoneHints sets.Set[string] - expect bool - actual bool - }{{ - description: "node zone empty, hints annotation empty, zone hints empty, expect should append topology aware endpoint true", - nodeZone: "", - hintsAnnotation: "", - zoneHints: nil, - expect: true, - }, { - description: "node zone us-west-2a, hints annotation empty, zone hints empty, expect should append topology aware endpoint true", - nodeZone: "us-west-2a", - hintsAnnotation: "", - zoneHints: nil, - expect: true, - }, { - description: "node zone us-west-2a, hints annotation auto, zone hints empty, expect should append topology aware endpoint true", - nodeZone: "us-west-2a", - hintsAnnotation: "auto", - zoneHints: nil, - expect: true, - }, { - description: "node zone us-west-2a, hints annotation disabled, zone hints empty, expect should append topology aware endpoint true", - nodeZone: "us-west-2a", - hintsAnnotation: "disabled", - zoneHints: nil, - expect: true, - }, { - description: "node zone us-west-2a, hints annotation auto, zone hints us-west-2a, expect should append topology aware endpoint true", - nodeZone: "us-west-2a", - hintsAnnotation: "auto", - zoneHints: sets.New[string]("us-west-2a"), - expect: true, - }, { - description: "node zone us-west-2a, hints annotation auto, zone hints us-west-2b, expect should append topology aware endpoint false", - nodeZone: "us-west-2a", - hintsAnnotation: "auto", - zoneHints: sets.New[string]("us-west-2b"), - expect: false, - }, { - description: "node zone us-west-2a, hints annotation disabled, zone hints us-west-2b, expect should append topology aware endpoint true", - nodeZone: "us-west-2a", - hintsAnnotation: "disabled", - zoneHints: sets.New[string]("us-west-2b"), - expect: true, - }, { - description: "node zone us-west-2a, hints annotation dummy, zone hints us-west-2b, expect should append topology aware endpoint true", - nodeZone: "us-west-2a", - hintsAnnotation: "dummy", - zoneHints: sets.New[string]("us-west-2b"), - expect: true, - }} +type fakeEndpoint struct { + ip string + ready bool + terminating bool + zoneHints sets.Set[string] + nodeHints sets.Set[string] +} + +// Implement k8sp.Endpoint interface for testing. +type testEndpoint struct { + fakeEndpoint +} + +func (e testEndpoint) IP() string { return e.ip } +func (e testEndpoint) IsReady() bool { return e.ready } +func (e testEndpoint) IsTerminating() bool { return e.terminating } +func (e testEndpoint) ZoneHints() sets.Set[string] { return e.zoneHints } +func (e testEndpoint) NodeHints() sets.Set[string] { return e.nodeHints } +func (e testEndpoint) IsLocal() bool { panic("unimplemented") } +func (e testEndpoint) IsServing() bool { panic("unimplemented") } +func (e testEndpoint) Port() int { panic("unimplemented") } +func (e testEndpoint) String() string { panic("unimplemented") } + +func TestFilterEpsByTopologyAwareRouting(t *testing.T) { + tests := []struct { + name string + topologyMode string + nodeZone string + endpoints []testEndpoint + wantIPs []string + wantFiltered bool + }{ + { + name: "topologyMode not auto: returns all endpoints, not applied", + topologyMode: "disabled", + nodeZone: "zone-a", + endpoints: []testEndpoint{ + {fakeEndpoint{"10.0.0.1", true, false, sets.New[string]("zone-a"), sets.New[string]()}}, + {fakeEndpoint{"10.0.0.2", true, false, sets.New[string]("zone-b"), sets.New[string]()}}, + }, + wantIPs: []string{"10.0.0.1", "10.0.0.2"}, + wantFiltered: false, + }, + { + name: "endpoint not ready or terminating is skipped", + topologyMode: "auto", + nodeZone: "zone-a", + endpoints: []testEndpoint{ + {fakeEndpoint{"10.0.0.1", false, false, sets.New[string]("zone-a"), sets.New[string]()}}, + {fakeEndpoint{"10.0.0.2", true, false, sets.New[string]("zone-a"), sets.New[string]()}}, + }, + wantIPs: []string{"10.0.0.2"}, + wantFiltered: true, + }, + { + name: "endpoint with no zone hints disables filtering", + topologyMode: "auto", + nodeZone: "zone-a", + endpoints: []testEndpoint{ + {fakeEndpoint{"10.0.0.1", true, false, sets.New[string](), sets.New[string]()}}, + {fakeEndpoint{"10.0.0.2", true, false, sets.New[string]("zone-a"), sets.New[string]()}}, + }, + wantIPs: []string{"10.0.0.1", "10.0.0.2"}, + wantFiltered: true, + }, + { + name: "only endpoints matching nodeZone are returned", + topologyMode: "auto", + nodeZone: "zone-a", + endpoints: []testEndpoint{ + {fakeEndpoint{"10.0.0.1", true, false, sets.New[string]("zone-a"), sets.New[string]()}}, + {fakeEndpoint{"10.0.0.2", true, false, sets.New[string]("zone-b"), sets.New[string]()}}, + }, + wantIPs: []string{"10.0.0.1"}, + wantFiltered: true, + }, + { + name: "no endpoints match nodeZone, fallback to all endpoints", + topologyMode: "auto", + nodeZone: "zone-c", + endpoints: []testEndpoint{ + {fakeEndpoint{"10.0.0.1", true, false, sets.New[string]("zone-a"), sets.New[string]()}}, + {fakeEndpoint{"10.0.0.2", true, false, sets.New[string]("zone-b"), sets.New[string]()}}, + }, + wantIPs: []string{"10.0.0.1", "10.0.0.2"}, + wantFiltered: true, + }, + { + name: "all endpoints not ready or terminating, returns all endpoints", + topologyMode: "auto", + nodeZone: "zone-a", + endpoints: []testEndpoint{ + {fakeEndpoint{"10.0.0.1", false, false, sets.New[string]("zone-a"), sets.New[string]()}}, + {fakeEndpoint{"10.0.0.2", false, false, sets.New[string]("zone-a"), sets.New[string]()}}, + }, + wantIPs: []string{"10.0.0.1", "10.0.0.2"}, + wantFiltered: true, + }, + { + name: "endpoint terminating is included", + topologyMode: "auto", + nodeZone: "zone-a", + endpoints: []testEndpoint{ + {fakeEndpoint{"10.0.0.1", false, true, sets.New[string]("zone-a"), sets.New[string]()}}, + {fakeEndpoint{"10.0.0.2", true, false, sets.New[string]("zone-a"), sets.New[string]()}}, + }, + wantIPs: []string{"10.0.0.1", "10.0.0.2"}, + wantFiltered: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + // Convert []testEndpoint to []k8sp.Endpoint + var eps []k8sp.Endpoint + for _, ep := range tt.endpoints { + eps = append(eps, ep) + } + got, filtered := proxy.FilterEpsByTopologyAwareRouting(eps, tt.topologyMode, tt.nodeZone) + var gotIPs []string + for _, ep := range got { + gotIPs = append(gotIPs, ep.IP()) + } + g.Expect(gotIPs).To(ConsistOf(tt.wantIPs)) + g.Expect(filtered).To(Equal(tt.wantFiltered)) + }) + } +} + +func TestFilterEpsByTrafficDistribution(t *testing.T) { + tests := []struct { + name string + nodeName string + nodeZone string + endpoints []testEndpoint + wantIPs []string + }{ + { + name: "prefer endpoints with node hint, only matching node", + nodeName: "node-1", + nodeZone: "zone-a", + endpoints: []testEndpoint{ + {fakeEndpoint{"10.0.0.1", true, false, sets.New[string]("zone-a"), sets.New[string]("node-1")}}, + {fakeEndpoint{"10.0.0.2", true, false, sets.New[string]("zone-a"), sets.New[string]("node-2")}}, + }, + wantIPs: []string{"10.0.0.1"}, + }, + { + name: "prefer endpoints with zone hint if no node hint matches", + nodeName: "node-3", + nodeZone: "zone-b", + endpoints: []testEndpoint{ + {fakeEndpoint{"10.0.0.1", true, false, sets.New[string]("zone-b"), sets.New[string]("node-1")}}, + {fakeEndpoint{"10.0.0.2", true, false, sets.New[string]("zone-a"), sets.New[string]("node-2")}}, + }, + wantIPs: []string{"10.0.0.1"}, + }, + { + name: "one endpoint with zone hint, one with none, only zone-hinted returned", + nodeName: "node-x", + nodeZone: "zone-a", + endpoints: []testEndpoint{ + {fakeEndpoint{"10.0.0.1", true, false, sets.New[string]("zone-a"), sets.New[string]()}}, + {fakeEndpoint{"10.0.0.2", true, false, sets.New[string](), sets.New[string]()}}, + }, + wantIPs: []string{"10.0.0.1"}, + }, + { + name: "prefer endpoints with zone hint if node hint missing", + nodeName: "node-1", + nodeZone: "zone-a", + endpoints: []testEndpoint{ + {fakeEndpoint{"10.0.0.1", true, false, sets.New[string]("zone-a"), sets.New[string]()}}, // no node hint + {fakeEndpoint{"10.0.0.2", true, false, sets.New[string]("zone-a"), sets.New[string]()}}, // no node hint + }, + wantIPs: []string{"10.0.0.1", "10.0.0.2"}, // fallback to zone + }, + { + name: "fallback to all endpoints if no node or zone hint matches", + nodeName: "node-x", + nodeZone: "zone-x", + endpoints: []testEndpoint{ + {fakeEndpoint{"10.0.0.1", true, false, sets.New[string]("zone-a"), sets.New[string]("node-1")}}, + {fakeEndpoint{"10.0.0.2", true, false, sets.New[string]("zone-b"), sets.New[string]("node-2")}}, + }, + wantIPs: []string{"10.0.0.1", "10.0.0.2"}, + }, + { + name: "one endpoint with node hint, one with none, only node-hinted returned", + nodeName: "node-1", + nodeZone: "zone-a", + endpoints: []testEndpoint{ + {fakeEndpoint{"10.0.0.1", true, false, sets.New[string]("zone-a"), sets.New[string]("node-1")}}, + {fakeEndpoint{"10.0.0.2", true, false, sets.New[string]("zone-a"), sets.New[string]()}}, + }, + wantIPs: []string{"10.0.0.1"}, + }, + { + name: "endpoint with no node or zone hints is ignored for node/zone, fallback to all", + nodeName: "node-x", + nodeZone: "zone-x", + endpoints: []testEndpoint{ + {fakeEndpoint{"10.0.0.1", true, false, sets.New[string](), sets.New[string]()}}, + {fakeEndpoint{"10.0.0.2", true, false, sets.New[string](), sets.New[string]()}}, + }, + wantIPs: []string{"10.0.0.1", "10.0.0.2"}, + }, + { + name: "endpoint not ready or terminating is skipped for node and zone hint", + nodeName: "node-1", + nodeZone: "zone-a", + endpoints: []testEndpoint{ + {fakeEndpoint{"10.0.0.1", false, false, sets.New[string]("zone-a"), sets.New[string]("node-1")}}, + {fakeEndpoint{"10.0.0.2", true, false, sets.New[string]("zone-a"), sets.New[string]("node-1")}}, + }, + wantIPs: []string{"10.0.0.2"}, + }, + { + name: "endpoint terminating is included", + nodeName: "node-1", + nodeZone: "zone-a", + endpoints: []testEndpoint{ + {fakeEndpoint{"10.0.0.1", false, true, sets.New[string]("zone-a"), sets.New[string]("node-1")}}, + {fakeEndpoint{"10.0.0.2", true, false, sets.New[string]("zone-a"), sets.New[string]("node-1")}}, + }, + wantIPs: []string{"10.0.0.1", "10.0.0.2"}, + }, + { + name: "all endpoints not ready or terminating, fallback to all endpoints", + nodeName: "node-1", + nodeZone: "zone-a", + endpoints: []testEndpoint{ + {fakeEndpoint{"10.0.0.1", false, false, sets.New[string]("zone-a"), sets.New[string]("node-1")}}, + {fakeEndpoint{"10.0.0.2", false, false, sets.New[string]("zone-a"), sets.New[string]("node-1")}}, + }, + wantIPs: []string{"10.0.0.1", "10.0.0.2"}, + }, + { + name: "one endpoint with node hint but not ready, one with zone hint and ready", + nodeName: "node-1", + nodeZone: "zone-a", + endpoints: []testEndpoint{ + {fakeEndpoint{"10.0.0.1", false, false, sets.New[string]("zone-a"), sets.New[string]("node-1")}}, // not ready + {fakeEndpoint{"10.0.0.2", true, false, sets.New[string]("zone-a"), sets.New[string]()}}, // ready, zone hint + }, + wantIPs: []string{"10.0.0.2"}, + }, + } - for _, tc := range testCases { - t.Run("ShouldAppendTopologyAwareEndpoint", func(t *testing.T) { - tc.actual = proxy.ShouldAppendTopologyAwareEndpoint(tc.nodeZone, tc.hintsAnnotation, tc.zoneHints) - Expect(tc.actual).To(Equal(tc.expect)) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + var eps []k8sp.Endpoint + for _, ep := range tt.endpoints { + eps = append(eps, ep) + } + got := proxy.FilterEpsByTrafficDistribution(eps, tt.nodeName, tt.nodeZone) + var gotIPs []string + for _, ep := range got { + gotIPs = append(gotIPs, ep.IP()) + } + g.Expect(gotIPs).To(ConsistOf(tt.wantIPs)) }) } } diff --git a/felix/bpf/state/map.go b/felix/bpf/state/map.go index a9cdc0349fa..91b026932e1 100644 --- a/felix/bpf/state/map.go +++ b/felix/bpf/state/map.go @@ -108,8 +108,8 @@ type State struct { RulesHit uint32 RuleIDs [MaxRuleIDs]uint64 Flags uint64 - ConntrackRCFlags uint32 - _ uint32 + ConntrackRCPadding uint32 + ConntrackFlags uint32 ConntrackNATIPPort uint64 ConntrackTunIP uint32 ConntrackIfIndexFwd uint32 @@ -121,10 +121,13 @@ type State struct { SrcAddrMasq1 uint32 SrcAddrMasq2 uint32 SrcAddrMasq3 uint32 - _ [48]byte // ipv6 padding + NATSvcID uint32 + _ uint32 + _ [44]byte // ipv6 padding } -const expectedSize = 488 +const expectedSize = 496 +const entrySize = 512 func (s *State) AsBytes() []byte { bPtr := (*[expectedSize]byte)(unsafe.Pointer(s)) @@ -143,10 +146,10 @@ func StateFromBytes(bytes []byte) State { var MapParameters = maps.MapParameters{ Type: "percpu_array", KeySize: 4, - ValueSize: expectedSize, + ValueSize: entrySize, MaxEntries: 2, Name: "cali_state", - Version: 4, + Version: 6, } func Map() maps.Map { @@ -157,7 +160,7 @@ func MapForTest() maps.Map { return maps.NewPinnedMap(maps.MapParameters{ Type: "array", KeySize: 4, - ValueSize: expectedSize, + ValueSize: entrySize, MaxEntries: 2, Name: "test_state", }) diff --git a/felix/bpf/tc/attach.go b/felix/bpf/tc/attach.go index d882028980f..8562279614a 100644 --- a/felix/bpf/tc/attach.go +++ b/felix/bpf/tc/attach.go @@ -32,6 +32,7 @@ import ( "github.com/projectcalico/calico/felix/bpf/bpfdefs" "github.com/projectcalico/calico/felix/bpf/hook" "github.com/projectcalico/calico/felix/bpf/libbpf" + "github.com/projectcalico/calico/felix/bpf/maps" tcdefs "github.com/projectcalico/calico/felix/bpf/tc/defs" "github.com/projectcalico/calico/felix/dataplane/linux/qos" ) @@ -51,7 +52,6 @@ type AttachPoint struct { HostTunnelIPv6 net.IP IntfIPv4 net.IP IntfIPv6 net.IP - FIB bool ToHostDrop bool DSR bool DSROptoutCIDRs bool @@ -75,6 +75,8 @@ type AttachPoint struct { IngressPacketRateConfigured bool EgressPacketRateConfigured bool DSCP int8 + MaglevLUTSize uint32 + ProgramsMap maps.Map } var ErrDeviceNotFound = errors.New("device not found") @@ -148,7 +150,7 @@ func (ap *AttachPoint) AttachProgram() error { // only need to load and configure the preamble that will pass the // configuration further to the selected set of programs. - binaryToLoad := path.Join(bpfdefs.ObjectDir, "tc_preamble.o") + binaryToLoad := path.Join(bpfdefs.ObjectDir, fmt.Sprintf("tc_preamble_%s.o", ap.Hook)) if ap.AttachType == apiv3.BPFAttachOptionTCX { err := ap.attachTCXProgram(binaryToLoad) if err != nil { @@ -185,9 +187,12 @@ func (ap *AttachPoint) AttachProgram() error { } logCxt.Info("Program attached to tc.") // Remove any tcx program. - err = ap.detachTcxProgram() - if err != nil { - logCxt.Warnf("error removing tcx program from %s", err) + if _, err := os.Stat(ap.ProgPinPath()); err == nil { + logCxt.Info("Removing any existing tcx program") + err = ap.detachTcxProgram() + if err != nil { + logCxt.Warnf("error removing tcx program from %s", err) + } } return nil } @@ -415,17 +420,18 @@ func (ap *AttachPoint) Config() string { func (ap *AttachPoint) Configure() *libbpf.TcGlobalData { globalData := &libbpf.TcGlobalData{ - ExtToSvcMark: ap.ExtToServiceConnmark, - VxlanPort: ap.VXLANPort, - Tmtu: ap.TunnelMTU, - PSNatStart: ap.PSNATStart, - PSNatLen: ap.PSNATEnd, - WgPort: ap.WgPort, - Wg6Port: ap.Wg6Port, - NatIn: ap.NATin, - NatOut: ap.NATout, - LogFilterJmp: uint32(ap.LogFilterIdx), - DSCP: ap.DSCP, + ExtToSvcMark: ap.ExtToServiceConnmark, + VxlanPort: ap.VXLANPort, + Tmtu: ap.TunnelMTU, + PSNatStart: ap.PSNATStart, + PSNatLen: ap.PSNATEnd, + WgPort: ap.WgPort, + Wg6Port: ap.Wg6Port, + NatIn: ap.NATin, + NatOut: ap.NATout, + LogFilterJmp: uint32(ap.LogFilterIdx), + DSCP: ap.DSCP, + MaglevLUTSize: ap.MaglevLUTSize, } if ap.Profiling == "Enabled" { @@ -488,7 +494,7 @@ func (ap *AttachPoint) Configure() *libbpf.TcGlobalData { copy(globalData.HostTunnelIPv4[0:4], ap.HostTunnelIPv4.To4()) copy(globalData.HostTunnelIPv6[:], ap.HostTunnelIPv6.To16()) - for i := 0; i < len(globalData.Jumps); i++ { + for i := range len(globalData.Jumps) { globalData.Jumps[i] = 0xffffffff /* uint32(-1) */ globalData.JumpsV6[i] = 0xffffffff /* uint32(-1) */ } @@ -513,6 +519,8 @@ func (ap *AttachPoint) Configure() *libbpf.TcGlobalData { copy(in, ap.Iface) globalData.IfaceName = string(in) + globalData.OverlayTunnelID = ap.OverlayTunnelID + return globalData } diff --git a/felix/bpf/tc/cleanup.go b/felix/bpf/tc/cleanup.go index 5ea53f7d469..54d7c310fa1 100644 --- a/felix/bpf/tc/cleanup.go +++ b/felix/bpf/tc/cleanup.go @@ -18,6 +18,7 @@ import ( "encoding/json" "os" "os/exec" + "slices" "strings" log "github.com/sirupsen/logrus" @@ -81,12 +82,9 @@ func CleanUpProgramsAndPins() { calicoProgIDs.Add(p.ID) continue } - for _, id := range p.Maps { - if calicoMapIDs.Contains(id) { - log.WithField("id", p.ID).Debug("Found calico program (by reference to calico map)") - calicoProgIDs.Add(p.ID) - break - } + if slices.ContainsFunc(p.Maps, calicoMapIDs.Contains) { + log.WithField("id", p.ID).Debug("Found calico program (by reference to calico map)") + calicoProgIDs.Add(p.ID) } } } diff --git a/felix/bpf/tc/defs/defs.go b/felix/bpf/tc/defs/defs.go index b51a60be9b2..758cc87a75f 100644 --- a/felix/bpf/tc/defs/defs.go +++ b/felix/bpf/tc/defs/defs.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Tigera, Inc. All rights reserved. +// Copyright (c) 2021-2026 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -56,6 +56,8 @@ const ( ProgIndexIcmpInnerNat ProgIndexNewFlow ProgIndexIPFrag + ProgIndexMaglev + ProgIndexTCPRst ProgIndexMainDebug ProgIndexPolicyDebug ProgIndexAllowedDebug @@ -65,6 +67,8 @@ const ( ProgIndexIcmpInnerNatDebug ProgIndexNewFlowDebug ProgIndexIPFragDebug + ProgIndexMaglevDebug + ProgIndexTCPRstDebug ProgIndexEndDebug ProgIndexEnd @@ -89,6 +93,8 @@ var ProgramNames = []string{ "calico_tc_skb_icmp_inner_nat", "calico_tc_skb_new_flow_entrypoint", "calico_tc_skb_ipv4_frag", + "calico_tc_maglev", + "calico_tc_skb_send_tcp_rst", /* ipv4 - debug */ "calico_tc_main", "calico_tc_norm_pol_tail", @@ -99,6 +105,8 @@ var ProgramNames = []string{ "calico_tc_skb_icmp_inner_nat", "calico_tc_skb_new_flow_entrypoint", "calico_tc_skb_ipv4_frag", + "calico_tc_maglev", + "calico_tc_skb_send_tcp_rst", /* ipv6 */ "calico_tc_main", "calico_tc_norm_pol_tail", @@ -109,6 +117,8 @@ var ProgramNames = []string{ "calico_tc_skb_icmp_inner_nat", "calico_tc_skb_new_flow_entrypoint", "", + "calico_tc_maglev", + "calico_tc_skb_send_tcp_rst", /* ipv6 - debug */ "calico_tc_main", "calico_tc_norm_pol_tail", @@ -119,6 +129,8 @@ var ProgramNames = []string{ "calico_tc_skb_icmp_inner_nat", "calico_tc_skb_new_flow_entrypoint", "", + "calico_tc_maglev", + "calico_tc_skb_send_tcp_rst", } type ToOrFromEp string @@ -144,36 +156,17 @@ func SectionName(endpointType EndpointType, fromOrTo ToOrFromEp) string { return fmt.Sprintf("calico_%s_%s_ep", fromOrTo, endpointType) } -func ProgFilename(ipVer int, epType EndpointType, toOrFrom ToOrFromEp, epToHostDrop, fib, dsr bool, logLevel string, btf bool) string { +func ProgFilename(ipVer int, epType EndpointType, toOrFrom ToOrFromEp, epToHostDrop, dsr bool, logLevel string, btf bool) string { if epToHostDrop && (epType != EpTypeWorkload || toOrFrom == ToEp) { // epToHostDrop only makes sense in the from-workload program. logrus.Debug("Ignoring epToHostDrop, doesn't apply to this target") epToHostDrop = false } - // Should match CALI_FIB_LOOKUP_ENABLED in bpf.h - if fib { - toHost := (epType == EpTypeWorkload || epType == EpTypeHost || epType == EpTypeLO || - epType == EpTypeVXLAN) && toOrFrom == FromEp - toHEP := (epType == EpTypeHost || epType == EpTypeLO) && toOrFrom == ToEp - - realFIB := epType != EpTypeL3Device && (toHost || toHEP) - - if !realFIB { - // FIB lookup only makes sense for traffic towards the host. - logrus.Debug("Ignoring fib enabled, doesn't apply to this target") - } - fib = realFIB - } - var hostDropPart string if epType == EpTypeWorkload && epToHostDrop { hostDropPart = "host_drop_" } - fibPart := "" - if fib { - fibPart = "fib_" - } dsrPart := "" if dsr && ((epType == EpTypeWorkload && toOrFrom == FromEp) || (epType == EpTypeHost)) { dsrPart = "dsr_" @@ -208,7 +201,7 @@ func ProgFilename(ipVer int, epType EndpointType, toOrFrom ToOrFromEp, epToHostD corePart += "_v6" } - oFileName := fmt.Sprintf("%v_%v_%s%s%s%v%s.o", - toOrFrom, epTypeShort, hostDropPart, fibPart, dsrPart, logLevel, corePart) + oFileName := fmt.Sprintf("%v_%v_%s%s%v%s.o", + toOrFrom, epTypeShort, hostDropPart, dsrPart, logLevel, corePart) return oFileName } diff --git a/felix/bpf/ut/attach_test.go b/felix/bpf/ut/attach_test.go index d75b0a5aa66..d6df83e004b 100644 --- a/felix/bpf/ut/attach_test.go +++ b/felix/bpf/ut/attach_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package ut +package ut_test import ( "encoding/binary" @@ -29,7 +29,7 @@ import ( "time" . "github.com/onsi/gomega" - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" "github.com/projectcalico/calico/felix/bpf" @@ -60,7 +60,7 @@ func newBPFTestEpMgr( bpfmaps *bpfmap.Maps, workloadIfaceRegex *regexp.Regexp, ) (linux.ManagerWithHEPUpdate, error) { - return linux.NewBPFEndpointManager(nil, config, bpfmaps, true, workloadIfaceRegex, idalloc.New(), idalloc.New(), + return linux.NewBPFEndpointManager(nil, config, bpfmaps, workloadIfaceRegex, idalloc.New(), idalloc.New(), rules.NewRenderer(rules.Config{ BPFEnabled: true, IPIPEnabled: true, @@ -79,7 +79,7 @@ func newBPFTestEpMgr( VXLANPort: 4789, VXLANVNI: 4096, FlowLogsEnabled: config.FlowLogsEnabled, - }), + }, false), generictables.NewNoopTable(), generictables.NewNoopTable(), nil, @@ -90,15 +90,34 @@ func newBPFTestEpMgr( nil, nil, 1500, + nil, + nil, ) } +// countSubPrograms counts the number of sub-programs that would be loaded for a given AttachType. +// This uses the GetApplicableSubProgs API from the hook package. +func countSubPrograms(at hook.AttachType) int { + applicableProgs := hook.GetApplicableSubProgs(at, false) + return len(applicableProgs) +} + +// expectedProgramCount computes the expected total program count for a map of AttachTypes. +func expectedProgramCount(programs map[hook.AttachType]hook.Layout) int { + total := 0 + for at := range programs { + total += countSubPrograms(at) + } + return total +} + func runAttachTest(t *testing.T, ipv6Enabled bool) { bpfmaps, err := bpfmap.CreateBPFMaps(ipv6Enabled) Expect(err).NotTo(HaveOccurred()) commonMaps := bpfmaps.CommonMaps - programs := commonMaps.ProgramsMap.(*hook.ProgramsMap) + programsIng := commonMaps.ProgramsMaps[hook.Ingress].(*hook.ProgramsMap) + programsEg := commonMaps.ProgramsMaps[hook.Egress].(*hook.ProgramsMap) loglevel := "off" bpfEpMgr, err := newBPFTestEpMgr( @@ -115,6 +134,7 @@ func runAttachTest(t *testing.T, ipv6Enabled bool) { BPFExtToServiceConnmark: 0, BPFPolicyDebugEnabled: true, BPFIpv6Enabled: ipv6Enabled, + BPFAttachType: "TCX", }, bpfmaps, regexp.MustCompile("^workloadep[0123]"), @@ -139,44 +159,45 @@ func runAttachTest(t *testing.T, ipv6Enabled bool) { err = bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) - programsCount := 14 - if ipv6Enabled { - programsCount = 27 - } - Expect(programs.Count()).To(Equal(programsCount)) - at := programs.Programs() - Expect(at).To(HaveKey(hook.AttachType{ + // Verify expected programs were loaded based on AttachTypes + atIng := programsIng.Programs() + atEg := programsEg.Programs() + expectedIngCount := expectedProgramCount(atIng) + expectedEgCount := expectedProgramCount(atEg) + Expect(programsIng.Count()).To(Equal(expectedIngCount)) + Expect(programsEg.Count()).To(Equal(expectedEgCount)) + Expect(atIng).To(HaveKey(hook.AttachType{ Hook: hook.Ingress, Family: 4, Type: tcdefs.EpTypeHost, LogLevel: loglevel, - FIB: true, ToHostDrop: false, - DSR: false})) - Expect(at).To(HaveKey(hook.AttachType{ + DSR: false, + })) + Expect(atEg).To(HaveKey(hook.AttachType{ Hook: hook.Egress, Family: 4, Type: tcdefs.EpTypeHost, LogLevel: loglevel, - FIB: true, ToHostDrop: false, - DSR: false})) - Expect(at).NotTo(HaveKey(hook.AttachType{ + DSR: false, + })) + Expect(atIng).NotTo(HaveKey(hook.AttachType{ Hook: hook.Ingress, Family: 6, Type: tcdefs.EpTypeHost, LogLevel: loglevel, - FIB: true, ToHostDrop: false, - DSR: false})) - Expect(at).NotTo(HaveKey(hook.AttachType{ + DSR: false, + })) + Expect(atEg).NotTo(HaveKey(hook.AttachType{ Hook: hook.Egress, Family: 6, Type: tcdefs.EpTypeHost, LogLevel: loglevel, - FIB: true, ToHostDrop: false, - DSR: false})) + DSR: false, + })) ifstateMap := ifstateMapDump(commonMaps.IfStateMap) Expect(ifstateMap).To(HaveKey(ifstate.NewKey(uint32(host1.Attrs().Index)))) @@ -195,56 +216,61 @@ func runAttachTest(t *testing.T, ipv6Enabled bool) { bpfEpMgr.OnUpdate(&proto.HostMetadataV6Update{Hostname: "uthost", Ipv6Addr: "1::4"}) err = bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) - Expect(programs.Count()).To(Equal(52)) - at := programs.Programs() + // Verify expected programs were loaded based on AttachTypes + atIng := programsIng.Programs() + atEg := programsEg.Programs() + expectedIngCount := expectedProgramCount(atIng) + expectedEgCount := expectedProgramCount(atEg) + Expect(programsIng.Count()).To(Equal(expectedIngCount)) + Expect(programsEg.Count()).To(Equal(expectedEgCount)) - Expect(at).To(HaveKey(hook.AttachType{ + Expect(atIng).To(HaveKey(hook.AttachType{ Hook: hook.Ingress, Family: 4, Type: tcdefs.EpTypeHost, LogLevel: loglevel, - FIB: true, ToHostDrop: false, - DSR: false})) - Expect(at).To(HaveKey(hook.AttachType{ + DSR: false, + })) + Expect(atEg).To(HaveKey(hook.AttachType{ Hook: hook.Egress, Family: 4, Type: tcdefs.EpTypeHost, LogLevel: loglevel, - FIB: true, ToHostDrop: false, - DSR: false})) - Expect(at).To(HaveKey(hook.AttachType{ + DSR: false, + })) + Expect(atIng).To(HaveKey(hook.AttachType{ Hook: hook.Ingress, Family: 6, Type: tcdefs.EpTypeHost, LogLevel: loglevel, - FIB: true, ToHostDrop: false, - DSR: false})) - Expect(at).To(HaveKey(hook.AttachType{ + DSR: false, + })) + Expect(atEg).To(HaveKey(hook.AttachType{ Hook: hook.Egress, Family: 6, Type: tcdefs.EpTypeHost, LogLevel: loglevel, - FIB: true, ToHostDrop: false, - DSR: false})) + DSR: false, + })) } bpfEpMgr.OnUpdate(&proto.ActivePolicyUpdate{ - Id: &proto.PolicyID{Tier: "default", Name: "untracked"}, - Policy: &proto.Policy{Untracked: true}, + Id: &proto.PolicyID{Name: "untracked"}, + Policy: &proto.Policy{Tier: "default", Untracked: true}, }) bpfEpMgr.OnHEPUpdate(map[string]*proto.HostEndpoint{ - "hostep1": &proto.HostEndpoint{ + "hostep1": { Name: "hostep1", UntrackedTiers: []*proto.TierInfo{ - &proto.TierInfo{ + { Name: "default", - IngressPolicies: []string{"untracked"}, + IngressPolicies: []*proto.PolicyID{{Name: "untracked", Kind: v3.KindGlobalNetworkPolicy}}, }, }, }, @@ -272,13 +298,14 @@ func runAttachTest(t *testing.T, ipv6Enabled bool) { } - pm := jumpMapDump(commonMaps.JumpMap) - Expect(pm).To(HaveKey(hostep1State.IngressPolicyV4())) - Expect(pm).To(HaveKey(hostep1State.EgressPolicyV4())) + pmIng := jumpMapDump(commonMaps.JumpMaps[hook.Ingress]) + pmEgr := jumpMapDump(commonMaps.JumpMaps[hook.Egress]) + Expect(pmIng).To(HaveKey(hostep1State.IngressPolicyV4())) + Expect(pmEgr).To(HaveKey(hostep1State.EgressPolicyV4())) if ipv6Enabled { - Expect(pm).To(HaveKey(hostep1State.IngressPolicyV6())) - Expect(pm).To(HaveKey(hostep1State.EgressPolicyV6())) + Expect(pmIng).To(HaveKey(hostep1State.IngressPolicyV6())) + Expect(pmEgr).To(HaveKey(hostep1State.EgressPolicyV6())) } @@ -307,10 +334,10 @@ func runAttachTest(t *testing.T, ipv6Enabled bool) { t.Run("remove the untracked (xdp) policy", func(t *testing.T) { bpfEpMgr.OnUpdate(&proto.ActivePolicyRemove{ - Id: &proto.PolicyID{Tier: "default", Name: "untracked"}, + Id: &proto.PolicyID{Name: "untracked"}, }) bpfEpMgr.OnHEPUpdate(map[string]*proto.HostEndpoint{ - "hostep1": &proto.HostEndpoint{ + "hostep1": { Name: "hostep1", }, }) @@ -335,16 +362,21 @@ func runAttachTest(t *testing.T, ipv6Enabled bool) { err := bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) - programCount := 14 - jumpMapLen := 2 + // Verify expected programs are loaded based on AttachTypes + atIng := programsIng.Programs() + atEg := programsEg.Programs() + expectedIngCount := expectedProgramCount(atIng) + expectedEgCount := expectedProgramCount(atEg) + Expect(programsIng.Count()).To(Equal(expectedIngCount)) + Expect(programsEg.Count()).To(Equal(expectedEgCount)) + pmIng := jumpMapDump(commonMaps.JumpMaps[hook.Ingress]) + pmEgr := jumpMapDump(commonMaps.JumpMaps[hook.Egress]) + jumpMapLen := 1 if ipv6Enabled { - programCount = 52 - jumpMapLen = 8 + jumpMapLen = 4 } - Expect(programs.Count()).To(Equal(programCount)) - - pm := jumpMapDump(commonMaps.JumpMap) - Expect(len(pm)).To(Equal(jumpMapLen)) // no policy for hep2 + Expect(len(pmIng)).To(Equal(jumpMapLen)) // no policy for hep2 + Expect(len(pmEgr)).To(Equal(jumpMapLen)) // no policy for hep2 }) workload1 := createVethName("workloadep1") @@ -356,46 +388,46 @@ func runAttachTest(t *testing.T, ipv6Enabled bool) { err = bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) - programsCount := 27 - if ipv6Enabled { - programsCount = 52 - } - Expect(programs.Count()).To(Equal(programsCount)) - - at := programs.Programs() - Expect(at).To(HaveKey(hook.AttachType{ + // Verify expected programs were loaded based on AttachTypes + atIng := programsIng.Programs() + atEg := programsEg.Programs() + expectedIngCount := expectedProgramCount(atIng) + expectedEgCount := expectedProgramCount(atEg) + Expect(programsIng.Count()).To(Equal(expectedIngCount)) + Expect(programsEg.Count()).To(Equal(expectedEgCount)) + Expect(atIng).To(HaveKey(hook.AttachType{ Hook: hook.Ingress, Family: 4, Type: tcdefs.EpTypeWorkload, LogLevel: loglevel, - FIB: true, ToHostDrop: false, - DSR: false})) - Expect(at).To(HaveKey(hook.AttachType{ + DSR: false, + })) + Expect(atEg).To(HaveKey(hook.AttachType{ Hook: hook.Egress, Family: 4, Type: tcdefs.EpTypeWorkload, LogLevel: loglevel, - FIB: true, ToHostDrop: false, - DSR: false})) + DSR: false, + })) if ipv6Enabled { - Expect(at).To(HaveKey(hook.AttachType{ + Expect(atIng).To(HaveKey(hook.AttachType{ Hook: hook.Ingress, Family: 6, Type: tcdefs.EpTypeWorkload, LogLevel: loglevel, - FIB: true, ToHostDrop: false, - DSR: false})) - Expect(at).To(HaveKey(hook.AttachType{ + DSR: false, + })) + Expect(atEg).To(HaveKey(hook.AttachType{ Hook: hook.Egress, Family: 6, Type: tcdefs.EpTypeWorkload, LogLevel: loglevel, - FIB: true, ToHostDrop: false, - DSR: false})) + DSR: false, + })) } ifstateMap := ifstateMapDump(commonMaps.IfStateMap) @@ -404,16 +436,17 @@ func runAttachTest(t *testing.T, ipv6Enabled bool) { Expect(wl1State.EgressPolicyV4()).NotTo(Equal(-1)) Expect(wl1State.XDPPolicyV4()).To(Equal(-1)) - pm := jumpMapDump(commonMaps.JumpMap) - Expect(pm).To(HaveKey(wl1State.IngressPolicyV4())) - Expect(pm).To(HaveKey(wl1State.EgressPolicyV4())) + pmIng := jumpMapDump(commonMaps.JumpMaps[hook.Ingress]) + pmEgr := jumpMapDump(commonMaps.JumpMaps[hook.Egress]) + Expect(pmIng).To(HaveKey(wl1State.IngressPolicyV4())) + Expect(pmEgr).To(HaveKey(wl1State.EgressPolicyV4())) if ipv6Enabled { Expect(wl1State.IngressPolicyV6()).NotTo(Equal(-1)) Expect(wl1State.EgressPolicyV6()).NotTo(Equal(-1)) Expect(wl1State.XDPPolicyV6()).To(Equal(-1)) - Expect(pm).To(HaveKey(wl1State.IngressPolicyV6())) - Expect(pm).To(HaveKey(wl1State.EgressPolicyV6())) + Expect(pmIng).To(HaveKey(wl1State.IngressPolicyV6())) + Expect(pmEgr).To(HaveKey(wl1State.EgressPolicyV6())) } }) @@ -421,22 +454,25 @@ func runAttachTest(t *testing.T, ipv6Enabled bool) { defer deleteLink(workload2) t.Run("create another workload, should not load more than the preable", func(t *testing.T) { + programsIngCount := programsIng.Count() + programsEgCount := programsEg.Count() bpfEpMgr.OnUpdate(linux.NewIfaceStateUpdate("workloadep2", ifacemonitor.StateUp, workload2.Attrs().Index)) bpfEpMgr.OnUpdate(linux.NewIfaceAddrsUpdate("workloadep2", "1.6.6.1")) err := bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) - programsCount := 27 - jumpMapLen := 6 + jumpMapLen := 3 if ipv6Enabled { - programsCount = 52 - jumpMapLen = 16 + jumpMapLen = 8 } - Expect(programs.Count()).To(Equal(programsCount)) + Expect(programsIng.Count()).To(Equal(programsIngCount)) + Expect(programsEg.Count()).To(Equal(programsEgCount)) - pm := jumpMapDump(commonMaps.JumpMap) - Expect(len(pm)).To(Equal((jumpMapLen))) + pmIng := jumpMapDump(commonMaps.JumpMaps[hook.Ingress]) + pmEgr := jumpMapDump(commonMaps.JumpMaps[hook.Egress]) + Expect(len(pmIng)).To(Equal((jumpMapLen))) + Expect(len(pmEgr)).To(Equal((jumpMapLen))) }) t.Run("bring first host ep down, should clean up its policies", func(t *testing.T) { @@ -445,14 +481,15 @@ func runAttachTest(t *testing.T, ipv6Enabled bool) { err := bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) - pm := jumpMapDump(commonMaps.JumpMap) + pmIng := jumpMapDump(commonMaps.JumpMaps[hook.Ingress]) + pmEgr := jumpMapDump(commonMaps.JumpMaps[hook.Egress]) // We remember the state from above - Expect(pm).NotTo(HaveKey(hostep1State.IngressPolicyV4())) - Expect(pm).NotTo(HaveKey(hostep1State.EgressPolicyV4())) + Expect(pmIng).NotTo(HaveKey(hostep1State.IngressPolicyV4())) + Expect(pmEgr).NotTo(HaveKey(hostep1State.EgressPolicyV4())) if ipv6Enabled { - Expect(pm).NotTo(HaveKey(hostep1State.IngressPolicyV6())) - Expect(pm).NotTo(HaveKey(hostep1State.EgressPolicyV6())) + Expect(pmIng).NotTo(HaveKey(hostep1State.IngressPolicyV6())) + Expect(pmEgr).NotTo(HaveKey(hostep1State.EgressPolicyV6())) } xdppm := jumpMapDump(commonMaps.XDPJumpMap) Expect(xdppm).To(HaveLen(0)) @@ -465,9 +502,10 @@ func runAttachTest(t *testing.T, ipv6Enabled bool) { wl1State = ifstateMap[ifstate.NewKey(uint32(workload1.Attrs().Index))] fmt.Printf("wl1State = %+v\n", wl1State) - pm := jumpMapDump(commonMaps.JumpMap) - wl1IngressPol := pm[wl1State.IngressPolicyV4()] - wl1EgressPol := pm[wl1State.EgressPolicyV4()] + pmIng := jumpMapDump(commonMaps.JumpMaps[hook.Ingress]) + pmEgr := jumpMapDump(commonMaps.JumpMaps[hook.Egress]) + wl1IngressPol := pmIng[wl1State.IngressPolicyV4()] + wl1EgressPol := pmEgr[wl1State.EgressPolicyV4()] bpfEpMgr.OnUpdate(&proto.WorkloadEndpointUpdate{ Id: &proto.WorkloadEndpointID{ @@ -478,10 +516,11 @@ func runAttachTest(t *testing.T, ipv6Enabled bool) { Endpoint: &proto.WorkloadEndpoint{Name: "workloadep1"}, }) bpfEpMgr.OnUpdate(&proto.ActivePolicyUpdate{ - Id: &proto.PolicyID{Tier: "default", Name: "wl1-policy"}, + Id: &proto.PolicyID{Name: "wl1-policy"}, Policy: &proto.Policy{ + Tier: "default", Namespace: "default", - InboundRules: []*proto.Rule{&proto.Rule{ + InboundRules: []*proto.Rule{{ Action: "allow", Protocol: &proto.Protocol{NumberOrName: &proto.Protocol_Name{Name: "tcp"}}, DstNet: []string{"1.6.6.6/32"}, @@ -497,9 +536,10 @@ func runAttachTest(t *testing.T, ipv6Enabled bool) { Expect(wl1State2).To(Equal(wl1State)) // ... but the policy programs changed - pm = jumpMapDump(commonMaps.JumpMap) - Expect(wl1IngressPol).NotTo(Equal(pm[wl1State2.IngressPolicyV4()])) - Expect(wl1EgressPol).NotTo(Equal(pm[wl1State2.IngressPolicyV4()])) + pmIng = jumpMapDump(commonMaps.JumpMaps[hook.Ingress]) + pmEgr = jumpMapDump(commonMaps.JumpMaps[hook.Egress]) + Expect(wl1IngressPol).NotTo(Equal(pmIng[wl1State2.IngressPolicyV4()])) + Expect(wl1EgressPol).NotTo(Equal(pmEgr[wl1State2.EgressPolicyV4()])) progs, err := bpf.GetAllProgs() Expect(err).NotTo(HaveOccurred()) @@ -514,10 +554,11 @@ func runAttachTest(t *testing.T, ipv6Enabled bool) { err := bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) - pm := jumpMapDump(commonMaps.JumpMap) + pmIng := jumpMapDump(commonMaps.JumpMaps[hook.Ingress]) + pmEgr := jumpMapDump(commonMaps.JumpMaps[hook.Egress]) // We remember the state from above - Expect(pm).NotTo(HaveKey(wl1State.IngressPolicyV4())) - Expect(pm).NotTo(HaveKey(wl1State.EgressPolicyV4())) + Expect(pmIng).NotTo(HaveKey(wl1State.IngressPolicyV4())) + Expect(pmEgr).NotTo(HaveKey(wl1State.EgressPolicyV4())) }) t.Run("restart", func(t *testing.T) { @@ -527,7 +568,6 @@ func runAttachTest(t *testing.T, ipv6Enabled bool) { bpfEpMgr.OnUpdate(linux.NewIfaceStateUpdate("workloadep3", ifacemonitor.StateUp, workload3.Attrs().Index)) bpfEpMgr.OnUpdate(linux.NewIfaceAddrsUpdate("workloadep3", "1.6.6.8")) err = bpfEpMgr.CompleteDeferredWork() - if err != nil { deleteLink(workload3) } @@ -547,7 +587,11 @@ func runAttachTest(t *testing.T, ipv6Enabled bool) { Expect(attached).To(HaveKey("hostep2")) Expect(attached).NotTo(HaveKey("workloadep3")) - programs.ResetForTesting() // Because we recycle it, restarted Felix would get a fresh copy. + // Capture the program count before restart (includes workload3) + programsCountBeforeRestart := programsIng.Count() + + programsIng.ResetForTesting() // Because we recycle it, restarted Felix would get a fresh copy. + programsEg.ResetForTesting() // Because we recycle it, restarted Felix would get a fresh copy. bpfEpMgr, err = newBPFTestEpMgr( &linux.Config{ @@ -585,33 +629,36 @@ func runAttachTest(t *testing.T, ipv6Enabled bool) { // And they still have the same data so that existing preamble programs // and policies can still point to the right stuff. - oldProgsParams := hook.ProgramsMapParameters - oldProgsParams.PinDir = tmp - oldProgs := maps.NewPinnedMap(oldProgsParams) + oldProgsIngParams := hook.IngressProgramsMapParameters + oldProgsIngParams.PinDir = tmp + oldProgs := maps.NewPinnedMap(oldProgsIngParams) err = oldProgs.Open() Expect(err).NotTo(HaveOccurred()) pm := jumpMapDump(oldProgs) - programsCount := 27 - oldPoliciesCount := 4 - if ipv6Enabled { - programsCount = 52 - oldPoliciesCount = 12 - } - Expect(pm).To(HaveLen(programsCount)) + // Old programs map should still have the programs from before restart + Expect(pm).To(HaveLen(programsCountBeforeRestart)) - oldPoliciesParams := jump.MapParameters + oldPoliciesParams := jump.IngressMapParameters oldPoliciesParams.PinDir = tmp oldPolicies := maps.NewPinnedMap(oldPoliciesParams) err = oldPolicies.Open() Expect(err).NotTo(HaveOccurred()) pm = jumpMapDump(oldPolicies) + oldPoliciesCount := 2 + if ipv6Enabled { + oldPoliciesCount = 6 + } Expect(pm).To(HaveLen(oldPoliciesCount)) // After restat we get new maps which are empty - Expect(programs.Count()).To(Equal(0)) - pm = jumpMapDump(commonMaps.ProgramsMap) + Expect(programsIng.Count()).To(Equal(0)) + pm = jumpMapDump(commonMaps.ProgramsMaps[hook.Ingress]) Expect(pm).To(HaveLen(0)) - pm = jumpMapDump(commonMaps.JumpMap) + pm = jumpMapDump(commonMaps.ProgramsMaps[hook.Egress]) + Expect(pm).To(HaveLen(0)) + pm = jumpMapDump(commonMaps.JumpMaps[hook.Ingress]) + Expect(pm).To(HaveLen(0)) + pm = jumpMapDump(commonMaps.JumpMaps[hook.Egress]) Expect(pm).To(HaveLen(0)) err = bpfEpMgr.CompleteDeferredWork() @@ -630,15 +677,20 @@ func runAttachTest(t *testing.T, ipv6Enabled bool) { err = bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) - Expect(programs.Count()).To(Equal(27)) - pm = jumpMapDump(commonMaps.ProgramsMap) - Expect(pm).To(HaveLen(27)) + // After restart and replaying state, verify expected programs were loaded + atIng := programsIng.Programs() + expectedIngCount := expectedProgramCount(atIng) + Expect(programsIng.Count()).To(Equal(expectedIngCount)) + pm = jumpMapDump(commonMaps.ProgramsMaps[hook.Ingress]) + Expect(pm).To(HaveLen(expectedIngCount)) - pm = jumpMapDump(commonMaps.JumpMap) + pmIng := jumpMapDump(commonMaps.JumpMaps[hook.Ingress]) + pmEgr := jumpMapDump(commonMaps.JumpMaps[hook.Egress]) // We remember the state from above - Expect(pm).To(HaveLen(2)) - Expect(pm).To(HaveKey(wl2State.IngressPolicyV4())) - Expect(pm).To(HaveKey(wl2State.EgressPolicyV4())) + Expect(pmIng).To(HaveLen(1)) + Expect(pmEgr).To(HaveLen(1)) + Expect(pmIng).To(HaveKey(wl2State.IngressPolicyV4())) + Expect(pmEgr).To(HaveKey(wl2State.EgressPolicyV4())) _, err = os.Stat(path.Join(bpfdefs.GlobalPinDir, "old_jumps")) Expect(err).To(HaveOccurred()) @@ -661,7 +713,6 @@ func runAttachTest(t *testing.T, ipv6Enabled bool) { bpfEpMgr.OnUpdate(linux.NewIfaceStateUpdate("workloadep3", ifacemonitor.StateUp, workload3.Attrs().Index)) bpfEpMgr.OnUpdate(linux.NewIfaceAddrsUpdate("workloadep3", "1.6.6.8")) err = bpfEpMgr.CompleteDeferredWork() - if err != nil { deleteLink(workload3) } @@ -673,7 +724,8 @@ func runAttachTest(t *testing.T, ipv6Enabled bool) { deleteLink(workload3) - programs.ResetForTesting() // Because we recycle it, restarted Felix would get a fresh copy. + programsIng.ResetForTesting() // Because we recycle it, restarted Felix would get a fresh copy. + programsEg.ResetForTesting() // Because we recycle it, restarted Felix would get a fresh copy. bpfEpMgr, err = newBPFTestEpMgr( &linux.Config{ @@ -694,8 +746,11 @@ func runAttachTest(t *testing.T, ipv6Enabled bool) { ) Expect(err).NotTo(HaveOccurred()) - pm := jumpMapDump(commonMaps.JumpMap) - Expect(pm).To(HaveLen(0)) + pmIng := jumpMapDump(commonMaps.JumpMaps[hook.Ingress]) + pmEgr := jumpMapDump(commonMaps.JumpMaps[hook.Egress]) + + Expect(pmIng).To(HaveLen(0)) + Expect(pmEgr).To(HaveLen(0)) bpfEpMgr.OnUpdate(&proto.HostMetadataUpdate{Hostname: "uthost", Ipv4Addr: "1.2.3.4"}) bpfEpMgr.OnUpdate(linux.NewIfaceStateUpdate("workloadep2", ifacemonitor.StateUp, workload2.Attrs().Index)) @@ -705,11 +760,13 @@ func runAttachTest(t *testing.T, ipv6Enabled bool) { err = bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) - pm = jumpMapDump(commonMaps.JumpMap) + pmIng = jumpMapDump(commonMaps.JumpMaps[hook.Ingress]) + pmEgr = jumpMapDump(commonMaps.JumpMaps[hook.Egress]) // We remember the state from above - Expect(pm).To(HaveLen(2)) - Expect(pm).To(HaveKey(wl2State.IngressPolicyV4())) - Expect(pm).To(HaveKey(wl2State.EgressPolicyV4())) + Expect(pmIng).To(HaveLen(1)) + Expect(pmEgr).To(HaveLen(1)) + Expect(pmIng).To(HaveKey(wl2State.IngressPolicyV4())) + Expect(pmEgr).To(HaveKey(wl2State.EgressPolicyV4())) }) } @@ -729,7 +786,8 @@ func TestAttachWithMultipleWorkloadUpdate(t *testing.T) { Expect(err).NotTo(HaveOccurred()) commonMaps := bpfmaps.CommonMaps - programs := commonMaps.ProgramsMap.(*hook.ProgramsMap) + programsIng := commonMaps.ProgramsMaps[hook.Ingress].(*hook.ProgramsMap) + programsEg := commonMaps.ProgramsMaps[hook.Egress].(*hook.ProgramsMap) loglevel := "off" bpfEpMgr, err := newBPFTestEpMgr( @@ -802,28 +860,29 @@ func TestAttachWithMultipleWorkloadUpdate(t *testing.T) { Expect(qosVal2.PacketRateTokens()).To(Equal(int16(-1))) Expect(qosVal2.PacketRateLastUpdate()).To(Equal(uint64(0))) - at := programs.Programs() - Expect(at).To(HaveKey(hook.AttachType{ + atIng := programsIng.Programs() + atEg := programsEg.Programs() + Expect(atIng).To(HaveKey(hook.AttachType{ Hook: hook.Ingress, Family: 4, Type: tcdefs.EpTypeWorkload, LogLevel: loglevel, - FIB: true, ToHostDrop: false, - DSR: false})) - Expect(at).To(HaveKey(hook.AttachType{ + DSR: false, + })) + Expect(atEg).To(HaveKey(hook.AttachType{ Hook: hook.Egress, Family: 4, Type: tcdefs.EpTypeWorkload, LogLevel: loglevel, - FIB: true, ToHostDrop: false, - DSR: false})) + DSR: false, + })) // The expectation is that, WorkloadEndpointUpdates must not // result in re-attaching the program. Hence the priority, handle of // the tc filters must be the same. - for i := 0; i < 2; i++ { + for range 2 { bpfEpMgr.OnUpdate(&proto.WorkloadEndpointUpdate{ Id: &proto.WorkloadEndpointID{ OrchestratorId: "k8s", @@ -898,7 +957,7 @@ func TestRepeatedAttach(t *testing.T) { ingressProg, err := tc.ListAttachedPrograms(ap.Iface, ap.Hook.String(), true) Expect(err).NotTo(HaveOccurred()) Expect(len(ingressProg)).To(Equal(1)) - for i := 0; i < 3; i++ { + for i := range 3 { err = ap.AttachProgram() Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("failed to attach preamble : %d", i)) } @@ -969,7 +1028,7 @@ func TestCTLBAttachLegacy(t *testing.T) { Expect(err).NotTo(HaveOccurred()) commonMaps := bpfmaps.CommonMaps - err = nat.InstallConnectTimeLoadBalancerLegacy(v4, v6, "", "debug", 60*time.Second, false, commonMaps.CTLBProgramsMap) + err = nat.InstallConnectTimeLoadBalancerLegacy(v4, v6, "", "debug", 60*time.Second, false, commonMaps.CTLBProgramsMaps) Expect(err).NotTo(HaveOccurred()) checkPinPath := func(pinPath string, mustExist bool) { @@ -1043,7 +1102,7 @@ func TestCTLBAttach(t *testing.T) { Expect(err).NotTo(HaveOccurred()) commonMaps := bpfmaps.CommonMaps - err = nat.InstallConnectTimeLoadBalancer(v4, v6, "", "debug", 60*time.Second, false, commonMaps.CTLBProgramsMap) + err = nat.InstallConnectTimeLoadBalancer(v4, v6, "", "debug", 60*time.Second, false, commonMaps.CTLBProgramsMaps) Expect(err).NotTo(HaveOccurred()) checkPinPath := func(pinPath string, mustExist bool) { @@ -1143,7 +1202,7 @@ func TestAttachInterfaceRecreate(t *testing.T) { }, BPFExtToServiceConnmark: 0, BPFPolicyDebugEnabled: true, - BPFAttachType: apiv3.BPFAttachOptionTCX, + BPFAttachType: v3.BPFAttachOptionTCX, }, bpfmaps, regexp.MustCompile("^workloadep[0123]"), @@ -1196,6 +1255,8 @@ func TestAttachInterfaceRecreate(t *testing.T) { deleteLink(workload0_new) } }() + bpfEpMgr.OnUpdate(linux.NewIfaceStateUpdate("workloadep0", ifacemonitor.StateUp, workload0_new.Attrs().Index)) + bpfEpMgr.OnUpdate(linux.NewIfaceAddrsUpdate("workloadep0", "1.6.6.6")) err = bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) _, err = os.Stat(bpfdefs.TcxPinDir + "/workloadep0_ingress") @@ -1223,21 +1284,23 @@ func TestAttachTcx(t *testing.T) { Expect(err).NotTo(HaveOccurred()) loglevel := "off" - bpfEpMgr, err := newBPFTestEpMgr( - &linux.Config{ - Hostname: "uthost", - BPFLogLevel: loglevel, - BPFDataIfacePattern: regexp.MustCompile("^hostep[12]"), - VXLANMTU: 1000, - VXLANPort: 1234, - BPFNodePortDSREnabled: false, - RulesConfig: rules.Config{ - EndpointToHostAction: "RETURN", - }, - BPFExtToServiceConnmark: 0, - BPFPolicyDebugEnabled: true, - BPFAttachType: apiv3.BPFAttachOptionTCX, + bpfConfig := &linux.Config{ + Hostname: "uthost", + BPFLogLevel: loglevel, + BPFDataIfacePattern: regexp.MustCompile("^hostep[12]"), + VXLANMTU: 1000, + VXLANPort: 1234, + BPFNodePortDSREnabled: false, + RulesConfig: rules.Config{ + EndpointToHostAction: "RETURN", }, + BPFExtToServiceConnmark: 0, + BPFPolicyDebugEnabled: true, + BPFAttachType: v3.BPFAttachOptionTCX, + } + + bpfEpMgr, err := newBPFTestEpMgr( + bpfConfig, bpfmaps, regexp.MustCompile("^workloadep[0123]"), ) @@ -1278,20 +1341,26 @@ func TestAttachTcx(t *testing.T) { tcxProgs, err := tc.ListAttachedTcxPrograms("workloadep0", "ingress") Expect(err).NotTo(HaveOccurred()) Expect(len(tcxProgs)).To(Equal(1)) - // Now attach Tc program. - ap := &tc.AttachPoint{ - AttachPoint: bpf.AttachPoint{ - Iface: "workloadep0", - Hook: hook.Ingress, - }, - HostIPv4: net.IPv4(1, 2, 3, 4), - IntfIPv4: net.IPv4(1, 6, 6, 6), - AttachType: apiv3.BPFAttachOptionTC, - } - _, err = tc.EnsureQdisc("workloadep0") + bpfConfig.BPFAttachType = v3.BPFAttachOptionTC + bpfEpMgr, err = newBPFTestEpMgr( + bpfConfig, + bpfmaps, + regexp.MustCompile("^workloadep[0123]"), + ) Expect(err).NotTo(HaveOccurred()) - err = ap.AttachProgram() + bpfEpMgr.OnUpdate(&proto.HostMetadataUpdate{Hostname: "uthost", Ipv4Addr: "1.2.3.4"}) + bpfEpMgr.OnUpdate(linux.NewIfaceStateUpdate("workloadep0", ifacemonitor.StateUp, workload0.Attrs().Index)) + bpfEpMgr.OnUpdate(linux.NewIfaceAddrsUpdate("workloadep0", "1.6.6.6")) + bpfEpMgr.OnUpdate(&proto.WorkloadEndpointUpdate{ + Id: &proto.WorkloadEndpointID{ + OrchestratorId: "k8s", + WorkloadId: "workloadep0", + EndpointId: "workloadep0", + }, + Endpoint: &proto.WorkloadEndpoint{Name: "workloadep0"}, + }) + err = bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) progs, err = tc.ListAttachedPrograms("workloadep0", hook.Ingress.String(), true) Expect(err).NotTo(HaveOccurred()) @@ -1301,7 +1370,17 @@ func TestAttachTcx(t *testing.T) { tcxProgs, err = tc.ListAttachedTcxPrograms("workloadep0", "ingress") Expect(err).NotTo(HaveOccurred()) Expect(len(tcxProgs)).To(Equal(0)) - // Now attach TCx again + + bpfConfig.BPFAttachType = v3.BPFAttachOptionTCX + bpfEpMgr, err = newBPFTestEpMgr( + bpfConfig, + bpfmaps, + regexp.MustCompile("^workloadep[0123]"), + ) + Expect(err).NotTo(HaveOccurred()) + bpfEpMgr.OnUpdate(&proto.HostMetadataUpdate{Hostname: "uthost", Ipv4Addr: "1.2.3.4"}) + bpfEpMgr.OnUpdate(linux.NewIfaceStateUpdate("workloadep0", ifacemonitor.StateUp, workload0.Attrs().Index)) + bpfEpMgr.OnUpdate(linux.NewIfaceAddrsUpdate("workloadep0", "1.6.6.6")) bpfEpMgr.OnUpdate(&proto.WorkloadEndpointUpdate{ Id: &proto.WorkloadEndpointID{ OrchestratorId: "k8s", @@ -1426,7 +1505,7 @@ func ifstateMapDump(m maps.Map) ifstate.MapMem { func jumpMapDump(m maps.Map) map[int]int { jumpMap := make(map[int]int) - for i := 0; i < 100; i++ { + for i := range 100 { if v, err := m.Get(jump.Key(i) /* a good key for any jump map */); err == nil { jumpMap[i] = int(binary.LittleEndian.Uint32(v)) } @@ -1465,7 +1544,6 @@ func BenchmarkAttachProgram(b *testing.B) { }, Type: tcdefs.EpTypeWorkload, ToOrFrom: tcdefs.FromEp, - FIB: true, HostIPv4: net.IPv4(1, 1, 1, 1), IntfIPv4: net.IPv4(1, 1, 1, 1), } diff --git a/felix/bpf/ut/bpf_prog_test.go b/felix/bpf/ut/bpf_prog_test.go index 11931a7686d..40ae616b3d3 100644 --- a/felix/bpf/ut/bpf_prog_test.go +++ b/felix/bpf/ut/bpf_prog_test.go @@ -29,8 +29,8 @@ import ( "syscall" "testing" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gstruct" "github.com/onsi/gomega/types" @@ -79,8 +79,9 @@ func init() { // Constants that are shared with the UT binaries that we build. const ( - natTunnelMTU = uint16(700) - testVxlanPort = uint16(5665) + natTunnelMTU = uint16(700) + testVxlanPort = uint16(5665) + testMaglevLUTSize = uint32(31) ) var ( @@ -97,6 +98,8 @@ var ( node1ip2 = net.IPv4(10, 10, 2, 1).To4() node1tunIP = net.IPv4(11, 11, 0, 1).To4() node2ip = net.IPv4(10, 10, 0, 2).To4() + node3ip = net.IPv4(10, 10, 0, 3).To4() + node3tunIP = net.IPv4(11, 11, 0, 3).To4() intfIP = net.IPv4(10, 10, 0, 3).To4() node1CIDR = net.IPNet{ IP: node1ip, @@ -106,11 +109,17 @@ var ( IP: node2ip, Mask: net.IPv4Mask(255, 255, 255, 255), } + node3CIDR = net.IPNet{ + IP: node3ip, + Mask: net.IPv4Mask(255, 255, 255, 255), + } node1ipV6 = net.ParseIP("abcd::ffff:0a0a:0001").To16() node1ip2V6 = net.ParseIP("abcd::ffff:0a0a:0201").To16() node1tunIPV6 = net.ParseIP("abcd::ffff:0b0b:0001").To16() node2ipV6 = net.ParseIP("abcd::ffff:0a0a:0002").To16() + node3ipV6 = net.ParseIP("abcd::ffff:0a0a:0004").To16() + node3tunIPV6 = net.ParseIP("abcd::ffff:0b0b:0004").To16() intfIPV6 = net.ParseIP("abcd::ffff:0a0a:0003").To16() node1CIDRV6 = net.IPNet{ IP: node1ipV6, @@ -120,6 +129,10 @@ var ( IP: node2ipV6, Mask: net.CIDRMask(128, 128), } + node3CIDRV6 = net.IPNet{ + IP: node3ipV6, + Mask: net.CIDRMask(128, 128), + } ) // Globals that we use to configure the next test run. @@ -214,6 +227,8 @@ var tcJumpMapIndexes = map[string][]int{ tcdefs.ProgIndexIcmpInnerNat, tcdefs.ProgIndexNewFlow, tcdefs.ProgIndexIPFrag, + tcdefs.ProgIndexMaglev, + tcdefs.ProgIndexTCPRst, }, "IPv4 debug": []int{ tcdefs.ProgIndexMainDebug, @@ -225,6 +240,8 @@ var tcJumpMapIndexes = map[string][]int{ tcdefs.ProgIndexIcmpInnerNatDebug, tcdefs.ProgIndexNewFlowDebug, tcdefs.ProgIndexIPFragDebug, + tcdefs.ProgIndexMaglevDebug, + tcdefs.ProgIndexTCPRstDebug, }, "IPv6": []int{ tcdefs.ProgIndexMain, @@ -235,6 +252,8 @@ var tcJumpMapIndexes = map[string][]int{ tcdefs.ProgIndexHostCtConflict, tcdefs.ProgIndexIcmpInnerNat, tcdefs.ProgIndexNewFlow, + tcdefs.ProgIndexMaglev, + tcdefs.ProgIndexTCPRst, }, "IPv6 debug": []int{ tcdefs.ProgIndexMainDebug, @@ -245,6 +264,8 @@ var tcJumpMapIndexes = map[string][]int{ tcdefs.ProgIndexHostCtConflictDebug, tcdefs.ProgIndexIcmpInnerNatDebug, tcdefs.ProgIndexNewFlowDebug, + tcdefs.ProgIndexMaglevDebug, + tcdefs.ProgIndexTCPRstDebug, }, } @@ -272,8 +293,8 @@ func TestLoadZeroProgram(t *testing.T) { } type testLogger interface { - Log(args ...interface{}) - Logf(format string, args ...interface{}) + Log(args ...any) + Logf(format string, args ...any) } func startBPFLogging() *exec.Cmd { @@ -362,7 +383,7 @@ func setupAndRun(logger testLogger, loglevel, section string, rules *polprog.Rul log.WithField("hostIP", hostIP).Info("Host IP") log.WithField("intfIP", intfIP).Info("Intf IP") } - obj += fmt.Sprintf("fib_%s", loglevel) + obj += loglevel if strings.Contains(section, "_dsr") { obj += "_dsr" @@ -384,41 +405,38 @@ func setupAndRun(logger testLogger, loglevel, section string, rules *polprog.Rul } } + useIngressProgMap := strings.Contains(obj, "from_") if topts.xdp { - o, err := objLoad("../../bpf-gpl/bin/xdp_preamble.o", bpfFsDir, "preamble", topts, false, false) + o, err := objLoad("../../bpf-gpl/bin/xdp_preamble.o", bpfFsDir, "preamble", topts, false, false, false, false) Expect(err).NotTo(HaveOccurred()) defer o.Close() } else { - o, err := objLoad("../../bpf-gpl/bin/tc_preamble.o", bpfFsDir, "preamble", topts, false, false) + fileToLoad := "../../bpf-gpl/bin/tc_preamble_egress.o" + if useIngressProgMap { + fileToLoad = "../../bpf-gpl/bin/tc_preamble_ingress.o" + } + o, err := objLoad(fileToLoad, bpfFsDir, "preamble", topts, false, false, false, useIngressProgMap) Expect(err).NotTo(HaveOccurred()) defer o.Close() } - log.Infof("Patching binary %s", obj+".o") - - bin, err := bpf.BinaryFromFile(obj + ".o") - Expect(err).NotTo(HaveOccurred()) - // XXX for now we both path the mark here and include it in the context as - // well. This needs to be done for as long as we want to run the tests on - // older kernels. - bin.PatchSkbMark(skbMark) - tempObj := tempDir + "bpf.o" - err = bin.WriteToFile(tempObj) - Expect(err).NotTo(HaveOccurred()) - if loglevel == "debug" { ipFamily += " debug" } - var o *libbpf.Obj - - o, err = objLoad(tempObj, bpfFsDir, ipFamily, topts, rules != nil, true) + hasMaglev := strings.Contains(obj, "from_hep") + obj += ".o" + o, err := objLoad(obj, bpfFsDir, ipFamily, topts, rules != nil, true, hasMaglev, useIngressProgMap) Expect(err).NotTo(HaveOccurred()) defer o.Close() if rules != nil { - staticProgMap := progMap - polMap := policyJumpMap + staticProgMap := progMap[hook.Egress] + polMap := policyJumpMap[hook.Egress] + if useIngressProgMap { + staticProgMap = progMap[hook.Ingress] + polMap = policyJumpMap[hook.Ingress] + } popts := []polprog.Option{} stride := jump.TCMaxEntryPoints if topts.xdp { @@ -460,14 +478,16 @@ func setupAndRun(logger testLogger, loglevel, section string, rules *polprog.Rul } Expect(errs).To(BeEmpty()) }() - var progType uint32 + progType := uint32(0) + attachType := uint32(0) if topts.xdp { progType = unix.BPF_PROG_TYPE_XDP + attachType = libbpf.AttachTypeXDP } else { progType = unix.BPF_PROG_TYPE_SCHED_CLS } for i, p := range insns { - polProgFD, err := bpf.LoadBPFProgramFromInsns(p, "calico_policy", "Apache-2.0", progType) + polProgFD, err := bpf.LoadBPFProgramFromInsnsWithAttachType(p, "calico_policy", "Apache-2.0", progType, attachType) Expect(err).NotTo(HaveOccurred(), "failed to load program into the kernel") Expect(polProgFD).NotTo(BeZero()) polProgFDs = append(polProgFDs, polProgFD) @@ -581,13 +601,16 @@ func bpftool(args ...string) ([]byte, error) { var ( mapInitOnce sync.Once - natMap, natBEMap, ctMap, ctCleanupMap, rtMap, ipsMap, testStateMap, affinityMap, arpMap, fsafeMap, ipfragsMap maps.Map - natMapV6, natBEMapV6, ctMapV6, ctCleanupMapV6, rtMapV6, ipsMapV6, affinityMapV6, arpMapV6, fsafeMapV6 maps.Map - stateMap, countersMap, ifstateMap, progMap, progMapXDP, policyJumpMap, policyJumpMapXDP maps.Map - perfMap maps.Map - profilingMap, ipfragsMapTmp, ctlbProgsMap maps.Map - qosMap maps.Map - allMaps []maps.Map + natMap, natBEMap, ctMap, ctCleanupMap, rtMap, ipsMap, testStateMap, affinityMap, arpMap, fsafeMap, ipfragsMap, maglevMap maps.Map + natMapV6, natBEMapV6, ctMapV6, ctCleanupMapV6, rtMapV6, ipsMapV6, affinityMapV6, arpMapV6, fsafeMapV6, maglevMapV6 maps.Map + stateMap, countersMap, ifstateMap, progMapXDP, policyJumpMapXDP maps.Map + policyJumpMap []maps.Map + perfMap maps.Map + profilingMap, ipfragsMapTmp maps.Map + qosMap maps.Map + ctlbProgsMap []maps.Map + progMap []maps.Map + allMaps []maps.Map ) func initMapsOnce() { @@ -616,18 +639,21 @@ func initMapsOnce() { ipfragsMap = ipfrags.Map() ipfragsMapTmp = ipfrags.MapTmp() ifstateMap = ifstate.Map() - policyJumpMap = jump.Map() + policyJumpMap = jump.Maps() policyJumpMapXDP = jump.XDPMap() profilingMap = profiling.Map() - ctlbProgsMap = nat.ProgramsMap() + ctlbProgsMap = nat.ProgramsMaps() + progMap = hook.NewProgramsMaps() qosMap = qos.Map() + maglevMap = nat.MaglevMap() + maglevMapV6 = nat.MaglevMapV6() perfMap = perf.Map("perf_evnt", 512) allMaps = []maps.Map{natMap, natBEMap, natMapV6, natBEMapV6, ctMap, ctMapV6, ctCleanupMap, ctCleanupMapV6, rtMap, rtMapV6, ipsMap, ipsMapV6, stateMap, testStateMap, affinityMap, affinityMapV6, arpMap, arpMapV6, fsafeMap, fsafeMapV6, countersMap, ipfragsMap, ipfragsMapTmp, ifstateMap, profilingMap, - policyJumpMap, policyJumpMapXDP, ctlbProgsMap, qosMap} + policyJumpMap[0], policyJumpMap[1], policyJumpMapXDP, ctlbProgsMap[0], ctlbProgsMap[1], ctlbProgsMap[2], qosMap, maglevMap, maglevMapV6} for _, m := range allMaps { err := m.EnsureExists() if err != nil { @@ -651,7 +677,7 @@ func cleanUpMaps() { defer log.SetLevel(logLevel) for _, m := range allMaps { - if m == stateMap || m == testStateMap || m == progMap || m == countersMap || m == ipfragsMapTmp { + if m == stateMap || m == testStateMap || m == progMap[hook.Ingress] || m == progMap[hook.Egress] || m == countersMap || m == ipfragsMapTmp { continue // Can't clean up array maps } log.WithField("map", m.GetName()).Info("Cleaning") @@ -701,7 +727,7 @@ func ipToU32(ip net.IP) uint32 { return binary.LittleEndian.Uint32([]byte(ip[:])) } -func tcUpdateJumpMap(obj *libbpf.Obj, progs []int, hasPolicyProg, hasHostConflictProg bool) error { +func tcUpdateJumpMap(obj *libbpf.Obj, progs []int, hasPolicyProg, hasHostConflictProg, hasMaglev, useIngressProgMap bool) error { for _, idx := range progs { switch idx { case @@ -717,9 +743,19 @@ func tcUpdateJumpMap(obj *libbpf.Obj, progs []int, hasPolicyProg, hasHostConflic if !hasHostConflictProg { continue } + case + tcdefs.ProgIndexMaglev, + tcdefs.ProgIndexMaglevDebug: + if !hasMaglev { + continue + } + } + pmName := progMap[hook.Egress].GetName() + if useIngressProgMap { + pmName = progMap[hook.Ingress].GetName() } log.WithField("prog", tcdefs.ProgramNames[idx]).WithField("idx", idx).Debug("UpdateJumpMap") - err := obj.UpdateJumpMap(progMap.GetName(), tcdefs.ProgramNames[idx], idx) + err := obj.UpdateJumpMap(pmName, tcdefs.ProgramNames[idx], idx) if err != nil { return fmt.Errorf("error updating %s program: %w", tcdefs.ProgramNames[idx], err) } @@ -728,25 +764,31 @@ func tcUpdateJumpMap(obj *libbpf.Obj, progs []int, hasPolicyProg, hasHostConflic return nil } -func objLoad(fname, bpfFsDir, ipFamily string, topts testOpts, polProg, hasHostConflictProg bool) (*libbpf.Obj, error) { +func objLoad(fname, bpfFsDir, ipFamily string, topts testOpts, polProg, hasHostConflictProg, hasMaglev, useIngressProgMap bool) (*libbpf.Obj, error) { log.WithField("program", fname).Debug("Loading BPF program") forXDP := topts.xdp // XXX we do not need to create both sets of maps, but, well, who cares here ;-) - progMap = hook.NewProgramsMap() - policyJumpMap = jump.Map() + progMap = hook.NewProgramsMaps() + policyJumpMap = jump.Maps() progMapXDP = hook.NewXDPProgramsMap() policyJumpMapXDP = jump.XDPMap() if ipFamily == "preamble" { - _ = unix.Unlink(progMap.Path()) - _ = unix.Unlink(policyJumpMap.Path()) + _ = unix.Unlink(progMap[hook.Ingress].Path()) + _ = unix.Unlink(progMap[hook.Egress].Path()) + _ = unix.Unlink(policyJumpMap[hook.Ingress].Path()) + _ = unix.Unlink(policyJumpMap[hook.Egress].Path()) _ = unix.Unlink(progMapXDP.Path()) _ = unix.Unlink(policyJumpMapXDP.Path()) } - err := progMap.EnsureExists() + err := progMap[hook.Ingress].EnsureExists() Expect(err).NotTo(HaveOccurred()) - err = policyJumpMap.EnsureExists() + err = progMap[hook.Egress].EnsureExists() + Expect(err).NotTo(HaveOccurred()) + err = policyJumpMap[hook.Ingress].EnsureExists() + Expect(err).NotTo(HaveOccurred()) + err = policyJumpMap[hook.Egress].EnsureExists() Expect(err).NotTo(HaveOccurred()) err = progMapXDP.EnsureExists() Expect(err).NotTo(HaveOccurred()) @@ -760,16 +802,19 @@ func objLoad(fname, bpfFsDir, ipFamily string, topts testOpts, polProg, hasHostC for m, err := obj.FirstMap(); m != nil && err == nil; m, err = m.NextMap() { if m.IsMapInternal() { - if strings.HasPrefix(m.Name(), ".rodata") { + if ipFamily != "preamble" { + continue + } + if !strings.HasSuffix(m.Name(), ".rodata") { continue } if forXDP { var globals libbpf.XDPGlobalData - for i := 0; i < 16; i++ { + for i := range 16 { globals.Jumps[i] = uint32(i) } if topts.ipv6 { - for i := 0; i < 16; i++ { + for i := range 16 { globals.JumpsV6[i] = uint32(i) } } @@ -781,13 +826,14 @@ func objLoad(fname, bpfFsDir, ipFamily string, topts testOpts, polProg, hasHostC } else { ifaceLog := topts.progLog + "-" + bpfIfaceName globals := libbpf.TcGlobalData{ - Tmtu: natTunnelMTU, - VxlanPort: testVxlanPort, - PSNatStart: uint16(topts.psnaStart), - PSNatLen: uint16(topts.psnatEnd-topts.psnaStart) + 1, - Flags: libbpf.GlobalsNoDSRCidrs, - LogFilterJmp: 0xffffffff, - IfaceName: setLogPrefix(ifaceLog), + Tmtu: natTunnelMTU, + VxlanPort: testVxlanPort, + PSNatStart: uint16(topts.psnaStart), + PSNatLen: uint16(topts.psnatEnd-topts.psnaStart) + 1, + Flags: libbpf.GlobalsNoDSRCidrs, + LogFilterJmp: 0xffffffff, + IfaceName: setLogPrefix(ifaceLog), + MaglevLUTSize: testMaglevLUTSize, } if topts.flowLogsEnabled { globals.Flags |= libbpf.GlobalsFlowLogsEnabled @@ -814,7 +860,7 @@ func objLoad(fname, bpfFsDir, ipFamily string, topts testOpts, polProg, hasHostC copy(globals.HostIPv6[:], hostIP.To16()) copy(globals.IntfIPv6[:], intfIPV6.To16()) - for i := 0; i < tcdefs.ProgIndexEnd; i++ { + for i := range tcdefs.ProgIndexEnd { globals.JumpsV6[i] = uint32(i) } globals.Flags |= libbpf.GlobalsRPFOptionStrict @@ -824,7 +870,7 @@ func objLoad(fname, bpfFsDir, ipFamily string, topts testOpts, polProg, hasHostC copy(globals.IntfIPv4[0:4], intfIP) copy(globals.HostTunnelIPv4[0:4], node1tunIP.To4()) - for i := 0; i < tcdefs.ProgIndexEnd; i++ { + for i := range tcdefs.ProgIndexEnd { globals.Jumps[i] = uint32(i) } log.WithField("globals", globals).Debugf("configure program") @@ -881,7 +927,10 @@ func objLoad(fname, bpfFsDir, ipFamily string, topts testOpts, polProg, hasHostC polProgPath = path.Join(bpfFsDir, polProgPath) _, err = os.Stat(polProgPath) if err == nil { - m := policyJumpMap + m := policyJumpMap[hook.Egress] + if useIngressProgMap { + m = policyJumpMap[hook.Ingress] + } if forXDP { m = policyJumpMapXDP } @@ -896,11 +945,11 @@ func objLoad(fname, bpfFsDir, ipFamily string, topts testOpts, polProg, hasHostC if !forXDP { log.WithField("ipFamily", ipFamily).Debug("Updating jump map") - err = tcUpdateJumpMap(obj, tcJumpMapIndexes[ipFamily], false, hasHostConflictProg) + err = tcUpdateJumpMap(obj, tcJumpMapIndexes[ipFamily], false, hasHostConflictProg, hasMaglev, useIngressProgMap) if err != nil && !strings.Contains(err.Error(), "error updating calico_tc_host_ct_conflict program") { goto out } - err = tcUpdateJumpMap(obj, tcJumpMapIndexes[ipFamily], false, false) + err = tcUpdateJumpMap(obj, tcJumpMapIndexes[ipFamily], false, false, hasMaglev, useIngressProgMap) } else { if err = xdpUpdateJumpMap(obj, xdpJumpMapIndexes[ipFamily]); err != nil { goto out @@ -928,13 +977,17 @@ func objUTLoad(fname, bpfFsDir, ipFamily string, topts testOpts, polProg, hasHos for m, err := obj.FirstMap(); m != nil && err == nil; m, err = m.NextMap() { if m.IsMapInternal() { + if !strings.HasSuffix(m.Name(), ".rodata") { + continue + } globals := libbpf.TcGlobalData{ - Tmtu: natTunnelMTU, - VxlanPort: testVxlanPort, - PSNatStart: uint16(topts.psnaStart), - PSNatLen: uint16(topts.psnatEnd-topts.psnaStart) + 1, - Flags: libbpf.GlobalsNoDSRCidrs, - IfaceName: setLogPrefix(topts.progLog + "-" + bpfIfaceName), + Tmtu: natTunnelMTU, + VxlanPort: testVxlanPort, + PSNatStart: uint16(topts.psnaStart), + PSNatLen: uint16(topts.psnatEnd-topts.psnaStart) + 1, + Flags: libbpf.GlobalsNoDSRCidrs, + IfaceName: setLogPrefix(topts.progLog + "-" + bpfIfaceName), + MaglevLUTSize: testMaglevLUTSize, } if topts.ipv6 { copy(globals.HostTunnelIPv6[:], node1tunIPV6.To16()) @@ -1379,6 +1432,42 @@ func tcpResponseRaw(in []byte) []byte { return out.Bytes() } +func tcpResponseRawV6(in []byte) []byte { + pkt := gopacket.NewPacket(in, layers.LayerTypeEthernet, gopacket.Default) + ethL := pkt.Layer(layers.LayerTypeEthernet) + ethR := ethL.(*layers.Ethernet) + ethR.SrcMAC, ethR.DstMAC = ethR.DstMAC, ethR.SrcMAC + + ipv6L := pkt.Layer(layers.LayerTypeIPv6) + ipv6R := ipv6L.(*layers.IPv6) + ipv6R.SrcIP, ipv6R.DstIP = ipv6R.DstIP, ipv6R.SrcIP + + tcpL := pkt.Layer(layers.LayerTypeTCP) + tcpR := tcpL.(*layers.TCP) + tcpR.SrcPort, tcpR.DstPort = tcpR.DstPort, tcpR.SrcPort + + if tcpR.SYN { + tcpR.ACK = true + } + + _ = tcpR.SetNetworkLayerForChecksum(ipv6R) + + out := gopacket.NewSerializeBuffer() + err := gopacket.SerializeLayers(out, gopacket.SerializeOptions{ComputeChecksums: true}, + ethR, ipv6R, tcpR, gopacket.Payload(pkt.ApplicationLayer().Payload())) + Expect(err).NotTo(HaveOccurred()) + + return out.Bytes() +} + +func dumpMaglevMap(mgMap maps.Map) { + m, err := nat.LoadMaglevMap(mgMap) + Expect(err).NotTo(HaveOccurred()) + for k, v := range m { + fmt.Printf("%s: %s\n", k, v) + } +} + func dumpNATMap(natMap maps.Map) { nt, err := nat.LoadFrontendMap(natMap) Expect(err).NotTo(HaveOccurred()) @@ -1480,6 +1569,10 @@ func resetRTMapV6(rtMap maps.Map) { resetMap(rtMap) } +func resetQoSMap(qosMap maps.Map) { + resetMap(qosMap) +} + func saveRTMap(rtMap maps.Map) routes.MapMem { rt, err := routes.LoadMap(rtMap) Expect(err).NotTo(HaveOccurred()) @@ -1499,6 +1592,20 @@ func restoreRTMap(rtMap maps.Map, m routes.MapMem) { } } +func restoreARPMap(arpMap maps.Map, a arp.MapMem) { + for k, v := range a { + err := arpMap.Update(k[:], v[:]) + Expect(err).NotTo(HaveOccurred()) + } +} + +func restoreARPMapV6(arpMap maps.Map, a arp.MapMemV6) { + for k, v := range a { + err := arpMap.Update(k[:], v[:]) + Expect(err).NotTo(HaveOccurred()) + } +} + func restoreRTMapV6(rtMap maps.Map, m routes.MapMemV6) { for k, v := range m { err := rtMap.Update(k[:], v[:]) @@ -1561,8 +1668,8 @@ var ipv4Default = &layers.IPv4{ Protocol: layers.IPProtocolUDP, } -var srcIPv6 = net.IP([]byte{0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}) -var dstIPv6 = net.IP([]byte{0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}) +var srcIPv6 = net.IP([]byte{0x20, 0x1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}) +var dstIPv6 = net.IP([]byte{0x20, 0x1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}) var srcV6CIDR = ip.CIDRFromNetIP(srcIPv6).(ip.V6CIDR) var dstV6CIDR = ip.CIDRFromNetIP(dstIPv6).(ip.V6CIDR) @@ -1590,6 +1697,7 @@ func testPacket(family int, eth *layers.Ethernet, l3 gopacket.Layer, l4 gopacket payload: payload, ipv6ext: ipv6ext, } + err := pkt.Generate() if err != nil { return nil, nil, nil, nil, nil, err @@ -1897,6 +2005,69 @@ func testPacketUDPDefaultNPWithPayload(destIP net.IP, payload []byte) (*layers.E return e, ip4.(*layers.IPv4), l4, p, b, err } +func testPacketTCPV4WithPayload(destIP net.IP, srcPort, dstPort uint16, syn bool, payload []byte) (*layers.Ethernet, *layers.IPv4, *layers.TCP, []byte, []byte, error) { + if destIP == nil { + log.Panic("destIP must be set") + } + + ip := *ipv4Default + ip.DstIP = destIP + tcp := &layers.TCP{ + SYN: syn, + SrcPort: layers.TCPPort(srcPort), + DstPort: layers.TCPPort(dstPort), + DataOffset: 5, + // Window: 14600, + } + + // ip.Options = []layers.IPv4Option{{ + // OptionType: 123, + // OptionLength: 6, + // OptionData: []byte{0xde, 0xad, 0xbe, 0xef}, + // }} + // ip.IHL += 2 + + e, ip4, l4, p, b, err := testPacket(4, nil, &ip, tcp, payload, nil) + return e, ip4.(*layers.IPv4), l4.(*layers.TCP), p, b, err +} + +func testPacketTCPV4DefaultNP(destIP net.IP, syn bool) (*layers.Ethernet, *layers.IPv4, *layers.TCP, []byte, []byte, error) { + return testPacketTCPV4WithPayload(destIP, 1234, 5678, syn, nil) +} + +func testPacketTCPV6WithPayload(destIP net.IP, srcPort, dstPort uint16, syn bool, payload []byte) (*layers.Ethernet, *layers.IPv6, *layers.TCP, []byte, []byte, error) { + if destIP == nil { + panic("destIP cannot be nil") + } + + ip := *ipv6Default + ip.NextHeader = layers.IPProtocolTCP + ip.DstIP = destIP + + tcp := &layers.TCP{ + SYN: syn, + SrcPort: layers.TCPPort(srcPort), + DstPort: layers.TCPPort(dstPort), + DataOffset: 5, + // Window: 14600, + } + + hop := &layers.IPv6HopByHop{} + hop.NextHeader = layers.IPProtocolTCP + /* from gopacket ip6_test.go */ + tlv := &layers.IPv6HopByHopOption{} + tlv.OptionType = 0x01 // PadN + tlv.OptionData = []byte{0x00, 0x00, 0x00, 0x00} + hop.Options = append(hop.Options, tlv) + + e, ip6, l4, p, b, err := testPacketV6(nil, &ip, tcp, payload, hop) + return e, ip6, l4.(*layers.TCP), p, b, err +} + +func testPacketTCPV6DefaultNP(destIP net.IP, syn bool) (*layers.Ethernet, *layers.IPv6, *layers.TCP, []byte, []byte, error) { + return testPacketTCPV6WithPayload(destIP, 1234, 5678, syn, nil) +} + func ipv6HopByHopExt() gopacket.SerializableLayer { hop := &layers.IPv6HopByHop{} hop.NextHeader = layers.IPProtocolUDP @@ -1945,6 +2116,8 @@ func resetBPFMaps() { resetMap(fsafeMap) resetMap(natMap) resetMap(natBEMap) + resetMap(qosMap) + resetMap(maglevMap) } func TestMapIterWithDelete(t *testing.T) { @@ -1962,7 +2135,7 @@ func TestMapIterWithDelete(t *testing.T) { err := m.EnsureExists() Expect(err).NotTo(HaveOccurred()) - for i := 0; i < 10; i++ { + for i := range 10 { var k, v [8]byte binary.LittleEndian.PutUint64(k[:], uint64(i)) @@ -1988,7 +2161,7 @@ func TestMapIterWithDelete(t *testing.T) { Expect(cnt).To(Equal(10)) - for i := 0; i < 10; i++ { + for i := range 10 { Expect(out).To(HaveKey(uint64(i))) Expect(out[uint64(i)]).To(Equal(uint64(i * 7))) } @@ -2011,7 +2184,7 @@ func TestMapIterWithDeleteLastOfBatch(t *testing.T) { items := 3*maps.IteratorNumKeys + 5 - for i := 0; i < items; i++ { + for i := range items { var k, v [8]byte binary.LittleEndian.PutUint64(k[:], uint64(i)) @@ -2043,7 +2216,7 @@ func TestMapIterWithDeleteLastOfBatch(t *testing.T) { Expect(len(out)).To(Equal(items)) Expect(cnt).To(Equal(items)) - for i := 0; i < items; i++ { + for i := range items { Expect(out).To(HaveKey(uint64(i))) Expect(out[uint64(i)]).To(Equal(uint64(i * 7))) } @@ -2052,12 +2225,14 @@ func TestMapIterWithDeleteLastOfBatch(t *testing.T) { func TestJumpMap(t *testing.T) { RegisterTestingT(t) - progMap = hook.NewProgramsMap() - err := progMap.EnsureExists() + progMap = hook.NewProgramsMaps() + err := progMap[hook.Ingress].EnsureExists() + Expect(err).NotTo(HaveOccurred()) + err = progMap[hook.Egress].EnsureExists() Expect(err).NotTo(HaveOccurred()) - jumpMapFD := progMap.MapFD() - pg := polprog.NewBuilder(idalloc.New(), ipsMap.MapFD(), stateMap.MapFD(), jumpMapFD, policyJumpMap.MapFD(), + jumpMapFD := progMap[hook.Ingress].MapFD() + pg := polprog.NewBuilder(idalloc.New(), ipsMap.MapFD(), stateMap.MapFD(), jumpMapFD, policyJumpMap[hook.Ingress].MapFD(), polprog.WithAllowDenyJumps(tcdefs.ProgIndexAllowed, tcdefs.ProgIndexDrop)) rules := polprog.Rules{} insns, err := pg.Instructions(rules) diff --git a/felix/bpf/ut/failsafes_test.go b/felix/bpf/ut/failsafes_test.go index 6cb9e037e56..85bf5b4726b 100644 --- a/felix/bpf/ut/failsafes_test.go +++ b/felix/bpf/ut/failsafes_test.go @@ -19,8 +19,8 @@ import ( "net" "testing" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/bpf/failsafes" diff --git a/felix/bpf/ut/filter_test.go b/felix/bpf/ut/filter_test.go index fc502fbb713..74f483dab01 100644 --- a/felix/bpf/ut/filter_test.go +++ b/felix/bpf/ut/filter_test.go @@ -18,7 +18,7 @@ import ( "net" "testing" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" "golang.org/x/sys/unix" @@ -48,7 +48,7 @@ func TestFilter(t *testing.T) { SrcPort: 1234, DstPort: 666, }, - make([]byte, 36), + nil, ) type testCase struct { @@ -70,8 +70,8 @@ func TestFilter(t *testing.T) { {"dst 1.2.3.4 and src 11.22.33.44", false}, {"(host 1.2.3.4 or host 5.6.7.8) and (udp port 666)", true}, {"(host 1.2.3.4 or host 5.6.7.8) and (udp port 1212)", false}, - {"len >= 64", true}, - {"len < 64", false}, + {"len >= 20", true}, + {"len < 20", false}, {"len >= 500", false}, {"portrange 600-700", true}, {"tcp portrange 600-700", false}, @@ -92,7 +92,7 @@ func TestFilter(t *testing.T) { for _, tc := range link.tests { t.Run(link.level+"_"+tc.expression, func(t *testing.T) { - insns, err := filter.NewStandAlone(link.typ, 64, tc.expression) + insns, err := filter.NewStandAlone(link.typ, 64, tc.expression, stateMap.MapFD()) Expect(err).NotTo(HaveOccurred()) fd, err := bpf.LoadBPFProgramFromInsns(insns, "filter", "Apache-2.0", unix.BPF_PROG_TYPE_SCHED_CLS) Expect(err).NotTo(HaveOccurred()) @@ -123,7 +123,7 @@ func TestFilter(t *testing.T) { t.Run(link.level+"_"+neg, func(t *testing.T) { - insns, err := filter.NewStandAlone(layers.LinkTypeEthernet, 64, neg) + insns, err := filter.NewStandAlone(layers.LinkTypeEthernet, 64, neg, stateMap.MapFD()) Expect(err).NotTo(HaveOccurred()) fd, err := bpf.LoadBPFProgramFromInsns(insns, "filter", "Apache-2.0", unix.BPF_PROG_TYPE_SCHED_CLS) Expect(err).NotTo(HaveOccurred()) diff --git a/felix/bpf/ut/flow_log_events_test.go b/felix/bpf/ut/flow_log_events_test.go index a6f57ab4558..dad9e5315f2 100644 --- a/felix/bpf/ut/flow_log_events_test.go +++ b/felix/bpf/ut/flow_log_events_test.go @@ -17,7 +17,7 @@ package ut_test import ( "testing" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/bpf/events" diff --git a/felix/bpf/ut/icmp_port_unreachable_test.go b/felix/bpf/ut/icmp_port_unreachable_test.go index 5b1c56a7f2c..4ab7e158489 100644 --- a/felix/bpf/ut/icmp_port_unreachable_test.go +++ b/felix/bpf/ut/icmp_port_unreachable_test.go @@ -19,8 +19,8 @@ import ( "net" "testing" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/bpf/counters" diff --git a/felix/bpf/ut/icmp_related_test.go b/felix/bpf/ut/icmp_related_test.go index 399d31117e2..c3889b4bb44 100644 --- a/felix/bpf/ut/icmp_related_test.go +++ b/felix/bpf/ut/icmp_related_test.go @@ -19,8 +19,8 @@ import ( "net" "testing" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/bpf/nat" @@ -631,7 +631,7 @@ func TestICMPv6RelatedPlain(t *testing.T) { runBpfTest(t, "calico_from_workload_ep", rulesAllowUDP, func(bpfrun bpfProgRunFn) { res, err := bpfrun(pktBytes) Expect(err).NotTo(HaveOccurred()) - Expect(res.Retval).To(Equal(resTC_ACT_UNSPEC)) + Expect(res.Retval).To(Equal(resTC_ACT_REDIRECT)) }, withIPv6()) expectMark(tcdefs.MarkSeen) @@ -707,7 +707,7 @@ func TestICMPv6RelatedNATPodPod(t *testing.T) { runBpfTest(t, "calico_from_workload_ep", rulesAllowUDP, func(bpfrun bpfProgRunFn) { res, err := bpfrun(pktBytes) Expect(err).NotTo(HaveOccurred()) - Expect(res.Retval).To(Equal(resTC_ACT_UNSPEC)) + Expect(res.Retval).To(Equal(resTC_ACT_REDIRECT)) natPkt = gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) }, withIPv6()) diff --git a/felix/bpf/ut/icmp_test.go b/felix/bpf/ut/icmp_test.go index c2afbac835e..dd1900f79ba 100644 --- a/felix/bpf/ut/icmp_test.go +++ b/felix/bpf/ut/icmp_test.go @@ -18,8 +18,8 @@ import ( "net" "testing" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/bpf/conntrack" diff --git a/felix/bpf/ut/icmp_too_big_test.go b/felix/bpf/ut/icmp_too_big_test.go index f48b9461678..f89cdc19dc1 100644 --- a/felix/bpf/ut/icmp_too_big_test.go +++ b/felix/bpf/ut/icmp_too_big_test.go @@ -20,13 +20,13 @@ import ( "net" "testing" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" "github.com/google/netstack/tcpip/header" + "github.com/gopacket/gopacket" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/bpf/conntrack" - conntrack3 "github.com/projectcalico/calico/felix/bpf/conntrack/v3" + conntrack4 "github.com/projectcalico/calico/felix/bpf/conntrack/v4" "github.com/projectcalico/calico/felix/bpf/nat" "github.com/projectcalico/calico/felix/bpf/routes" "github.com/projectcalico/calico/felix/ip" @@ -269,7 +269,7 @@ func TestICMPTooBigNATNodePort(t *testing.T) { v, ok := ct[conntrack.NewKey(uint8(ipv4.Protocol), ipv4.SrcIP, uint16(udp.SrcPort), natIP.To4(), natPort)] Expect(ok).To(BeTrue()) Expect(v.Type()).To(Equal(conntrack.TypeNATReverse)) - Expect(v.Flags()).To(Equal(conntrack3.FlagNATNPFwd | conntrack3.FlagClusterExternal)) + Expect(v.Flags()).To(Equal(conntrack4.FlagNATNPFwd | conntrack4.FlagSetDSCP)) _, _, _, _, pkt2Bytes, err := testPacket(4, nil, &origIPHeader, udpDefault, make([]byte, 1600)) Expect(err).NotTo(HaveOccurred()) diff --git a/felix/bpf/ut/icmp_ttl_exceeded_test.go b/felix/bpf/ut/icmp_ttl_exceeded_test.go index 0e6d5bf5114..6fc95361dd7 100644 --- a/felix/bpf/ut/icmp_ttl_exceeded_test.go +++ b/felix/bpf/ut/icmp_ttl_exceeded_test.go @@ -19,8 +19,8 @@ import ( "net" "testing" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/bpf/nat" diff --git a/felix/bpf/ut/ip4_defrag_test.go b/felix/bpf/ut/ip4_defrag_test.go index 1f1093b33f7..3526504d5f6 100644 --- a/felix/bpf/ut/ip4_defrag_test.go +++ b/felix/bpf/ut/ip4_defrag_test.go @@ -18,8 +18,8 @@ import ( "fmt" "testing" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" ) @@ -33,7 +33,7 @@ func TestIP4Defrag(t *testing.T) { data := make([]byte, 2000) - for i := 0; i < 1000; i++ { + for i := range 1000 { data[i*2] = byte(uint16(i) >> 8) data[i*2+1] = byte(uint16(i) & 0xff) } @@ -145,7 +145,7 @@ func TestIP4Defrag(t *testing.T) { payloadL := pktR.ApplicationLayer() data := payloadL.Payload() - for i := 0; i < 1000; i++ { + for i := range 1000 { Expect(data[i*2]).To(Equal(byte(uint16(i)>>8)), fmt.Sprintf("wrong at index %d", i*2)) Expect(data[i*2+1]).To(Equal(byte(uint16(i)&0xff)), fmt.Sprintf("wrong at index %d", i*2+1)) } diff --git a/felix/bpf/ut/ip_dec_ttl_test.go b/felix/bpf/ut/ip_dec_ttl_test.go index fe96ff89102..00c174edb09 100644 --- a/felix/bpf/ut/ip_dec_ttl_test.go +++ b/felix/bpf/ut/ip_dec_ttl_test.go @@ -18,8 +18,8 @@ import ( "fmt" "testing" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" ) diff --git a/felix/bpf/ut/ip_parse_test.go b/felix/bpf/ut/ip_parse_test.go index 2d12ee4be3f..c0e0bbeee8f 100644 --- a/felix/bpf/ut/ip_parse_test.go +++ b/felix/bpf/ut/ip_parse_test.go @@ -17,7 +17,7 @@ package ut_test import ( "testing" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" ) diff --git a/felix/bpf/ut/ipv4_opts_test.go b/felix/bpf/ut/ipv4_opts_test.go index 5a7121e2424..29e1e8d6267 100644 --- a/felix/bpf/ut/ipv4_opts_test.go +++ b/felix/bpf/ut/ipv4_opts_test.go @@ -19,8 +19,8 @@ import ( "net" "testing" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/bpf/conntrack" diff --git a/felix/bpf/ut/ipv6_test.go b/felix/bpf/ut/ipv6_test.go index 3c4c0fab93c..1d306b93a99 100644 --- a/felix/bpf/ut/ipv6_test.go +++ b/felix/bpf/ut/ipv6_test.go @@ -18,8 +18,8 @@ import ( "fmt" "testing" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/bpf/polprog" diff --git a/felix/bpf/ut/log_test.go b/felix/bpf/ut/log_test.go index 522d01f834c..7f35c6b72da 100644 --- a/felix/bpf/ut/log_test.go +++ b/felix/bpf/ut/log_test.go @@ -18,7 +18,7 @@ import ( "net" "testing" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/bpf/polprog" diff --git a/felix/bpf/ut/maglev_test.go b/felix/bpf/ut/maglev_test.go new file mode 100644 index 00000000000..47b51d7bf56 --- /dev/null +++ b/felix/bpf/ut/maglev_test.go @@ -0,0 +1,2140 @@ +// Copyright (c) 2019-2021 Tigera, Inc. All rights reserved. +// +// 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 ut_test + +import ( + "fmt" + "hash/fnv" + "net" + "testing" + + "github.com/gopacket/gopacket" + "github.com/gopacket/gopacket/layers" + . "github.com/onsi/gomega" + "github.com/sirupsen/logrus" + + "github.com/projectcalico/calico/felix/bpf/arp" + "github.com/projectcalico/calico/felix/bpf/conntrack" + conntrack4 "github.com/projectcalico/calico/felix/bpf/conntrack/v4" + "github.com/projectcalico/calico/felix/bpf/consistenthash" + chtypes "github.com/projectcalico/calico/felix/bpf/consistenthash/test" + "github.com/projectcalico/calico/felix/bpf/maps" + "github.com/projectcalico/calico/felix/bpf/nat" + "github.com/projectcalico/calico/felix/bpf/polprog" + "github.com/projectcalico/calico/felix/bpf/routes" + tcdefs "github.com/projectcalico/calico/felix/bpf/tc/defs" + "github.com/projectcalico/calico/felix/ip" + "github.com/projectcalico/calico/felix/proto" +) + +// Important note to the delevoper: +// While we program both the maglev map *and* backend map in the real world, +// We neglect to program the backend map in these UTs, as it should not be +// used by the BPF program, and would be an error to do so. +// We would like such errors to be immediately apparent, hence the omission. + +var ( + maglevSvcID = uint32(55555) + maglevTestM = testMaglevLUTSize +) + +func TestMaglevNATServiceIPTCP(t *testing.T) { + RegisterTestingT(t) + + var natMap, mgMap maps.MapWithExistsCheck + var ctMap, rtMap maps.Map + var err error + + natMap = nat.FrontendMap() + err = natMap.EnsureExists() + Expect(err).NotTo(HaveOccurred()) + + mgMap = nat.MaglevMap() + err = mgMap.EnsureExists() + Expect(err).NotTo(HaveOccurred()) + + ctMap = conntrack.Map() + err = ctMap.EnsureExists() + Expect(err).NotTo(HaveOccurred()) + + rtMap = routes.Map() + err = rtMap.EnsureExists() + Expect(err).NotTo(HaveOccurred()) + + resetMaps := func() { + resetMap(natMap) + resetMap(mgMap) + resetCTMap(ctMap) + resetRTMap(rtMap) + resetMap(arpMap) + } + defer resetMaps() + + newNode := func(nodeIP net.IP) { + resetMaps() + hostIP = nodeIP + skbMark = 0 + bpfIfaceName = fmt.Sprintf("MNP-%d", nodeIP[3]) + } + + var rtNode1, rtNode2 routes.MapMem + var ctNode1, ctNode2 conntrack.MapMem + var arpNode1, arpNode2 arp.MapMem + + // First, node 3 will initiate a connection to node 2. + // Then, node 1 will pick up the connection mid-flow. + // Node 2 should begin forwarding back to node 1. + newNode(node3ip) + defer func() { bpfIfaceName = "" }() + + serviceIP := net.IPv4(172, 16, 1, 1) + podIP := net.IPv4(8, 8, 8, 8) + podPort := uint16(666) + + node2WlCIDR := net.IPNet{ + IP: podIP, + Mask: net.IPv4Mask(255, 255, 255, 0), + } + var encapedPkt []byte + + // Prepare a test packet. + // SYN, Src IP 1.1.1.1, Src Port 1234, Dst IP 172.16.1.1, Dst Port 5678. + _, ipv4, tcp, payload, pktBytes, err := testPacketTCPV4DefaultNP(serviceIP, true) + Expect(err).NotTo(HaveOccurred()) + + // Node 3: Flagging frontend map item with the consistent-hash flag. + err = natMap.Update( + nat.NewNATKey(ipv4.DstIP, uint16(tcp.DstPort), uint8(layers.IPProtocolTCP)).AsBytes(), + nat.NewNATValueWithFlags(maglevSvcID, 1, 0, 0, nat.NATFlgMaglev).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + + // Node 3: Build a maglev LUT and program each item to the BPF map. + mglv := consistenthash.New(int(maglevTestM), fnv.New32(), fnv.New32()) + mglv.AddBackend(chtypes.MockEndpoint{ + Ip: podIP.String(), + Prt: podPort, + }) + lut := mglv.Generate() + for ordinal, ep := range lut { + err = mgMap.Update( + nat.NewMaglevBackendKey(maglevSvcID, uint32(ordinal)).AsBytes(), + nat.NewNATBackendValue(net.ParseIP(ep.IP()), uint16(ep.Port())).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + } + + // Node 3: Setup routing. + err = rtMap.Update( + routes.NewKey(ip.CIDRFromIPNet(&node2WlCIDR).(ip.V4CIDR)).AsBytes(), + routes.NewValueWithNextHop(routes.FlagsRemoteWorkload|routes.FlagInIPAMPool, + ip.FromNetIP(node2ip).(ip.V4Addr)).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + err = rtMap.Update( + routes.NewKey(ip.CIDRFromIPNet(&node3CIDR).(ip.V4CIDR)).AsBytes(), + routes.NewValue(routes.FlagsLocalHost).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + err = rtMap.Update( + routes.NewKey(ip.CIDRFromIPNet(&node1CIDR).(ip.V4CIDR)).AsBytes(), + routes.NewValue(routes.FlagsRemoteHost).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + err = rtMap.Update( + routes.NewKey(ip.CIDRFromIPNet(&node2CIDR).(ip.V4CIDR)).AsBytes(), + routes.NewValue(routes.FlagsRemoteHost).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + + // Arriving at node 3 + runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(pktBytes) + Expect(err).NotTo(HaveOccurred()) + // Do comparison on the strings rather than the numbers to give more + // meaning to Gomega failure messages. + Expect(retvalToStr[res.Retval]).To(Equal(retvalToStr[resTC_ACT_REDIRECT])) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + ipv4L := pktR.Layer(layers.LayerTypeIPv4) + Expect(ipv4L).NotTo(BeNil()) + ipv4R := ipv4L.(*layers.IPv4) + Expect(ipv4R.SrcIP.String()).To(Equal(hostIP.String())) + Expect(ipv4R.DstIP.String()).To(Equal(node2ip.String())) + + checkVxlanEncap(pktR, false, ipv4, tcp, payload) + + encapedPkt = res.dataOut + + ct, err := conntrack.LoadMapMem(ctMap) + Expect(err).NotTo(HaveOccurred()) + + dumpCTMap(ctMap) + + ctKey := conntrack.NewKey(uint8(layers.IPProtocolTCP), + ipv4.SrcIP, uint16(tcp.SrcPort), ipv4.DstIP, uint16(tcp.DstPort)) + + Expect(ct).Should(HaveKey(ctKey)) + ctr := ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATForward)) + + ctKey = ctr.ReverseNATKey().(conntrack.Key) + Expect(ct).Should(HaveKey(ctKey)) + ctr = ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATReverse)) + + // Approved for both sides due to forwarding through the tunnel + Expect(ctr.Data().A2B.Approved).To(BeTrue()) + Expect(ctr.Data().B2A.Approved).To(BeTrue()) + + v, ok := ct[conntrack.NewKey(uint8(layers.IPProtocolTCP), ipv4.SrcIP, uint16(tcp.SrcPort), podIP.To4(), podPort)] + Expect(ok).To(BeTrue()) + Expect(v.Type()).To(Equal(conntrack.TypeNATReverse)) + Expect(v.Flags()).To(Equal(conntrack4.FlagNATNPFwd | conntrack4.FlagMaglev)) + }) + expectMark(tcdefs.MarkSeenBypassForward) + + // Leaving node 3. + runBpfTest(t, "calico_to_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(encapedPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(resTC_ACT_UNSPEC)) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + Expect(res.dataOut).To(Equal(encapedPkt)) + }) + + dumpCTMap(ctMap) + + // Now arriving at Node 2. + encapedPktArrivesAtNode2 := make([]byte, len(encapedPkt)) + copy(encapedPktArrivesAtNode2, encapedPkt) + + newNode(node2ip) + + // change the routing - backing workload is local now + err = rtMap.Update( + routes.NewKey(ip.CIDRFromIPNet(&node2WlCIDR).(ip.V4CIDR)).AsBytes(), + routes.NewValue(routes.FlagsLocalWorkload|routes.FlagInIPAMPool).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + // we must know that the encaped packet src ip is from a known host + err = rtMap.Update( + routes.NewKey(ip.CIDRFromIPNet(&node3CIDR).(ip.V4CIDR)).AsBytes(), + routes.NewValue(routes.FlagsRemoteHost).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + err = rtMap.Update( + routes.NewKey(ip.CIDRFromIPNet(&node1CIDR).(ip.V4CIDR)).AsBytes(), + routes.NewValue(routes.FlagsRemoteHost).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + err = rtMap.Update( + routes.NewKey(ip.CIDRFromIPNet(&node2CIDR).(ip.V4CIDR)).AsBytes(), + routes.NewValue(routes.FlagsLocalHost).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + + dumpRTMap(rtMap) + // Node 2: now we are at the node with local workload + err = natMap.Update( + nat.NewNATKey(ipv4.DstIP, uint16(tcp.DstPort), uint8(layers.IPProtocolTCP)).AsBytes(), + nat.NewNATValueWithFlags(maglevSvcID /* id */, 1 /* count */, 1 /* local */, 0, nat.NATFlgMaglev).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + + // Node 2: Build a maglev LUT and program each item to the BPF map. + mglv = consistenthash.New(int(maglevTestM), fnv.New32(), fnv.New32()) + mglv.AddBackend(chtypes.MockEndpoint{ + Ip: podIP.String(), + Prt: podPort, + }) + lut = mglv.Generate() + for ordinal, ep := range lut { + err = mgMap.Update( + nat.NewMaglevBackendKey(maglevSvcID, uint32(ordinal)).AsBytes(), + nat.NewNATBackendValue(net.ParseIP(ep.IP()), uint16(ep.Port())).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + } + + // Packet hits node 2. + var recvPkt []byte + runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(encapedPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(retvalToStr[res.Retval]).To(Equal(retvalToStr[resTC_ACT_REDIRECT])) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + payloadL := pktR.ApplicationLayer() + Expect(payloadL).NotTo(BeNil()) + vxlanL := gopacket.NewPacket(payloadL.Payload(), layers.LayerTypeVXLAN, gopacket.Default) + Expect(vxlanL).NotTo(BeNil()) + fmt.Printf("vxlanL = %+v\n", vxlanL) + + ipv4L := pktR.Layer(layers.LayerTypeIPv4) + ipv4R := ipv4L.(*layers.IPv4) + Expect(ipv4R.SrcIP.String()).To(Equal(ipv4.SrcIP.String())) + Expect(ipv4R.DstIP.String()).To(Equal(podIP.String())) + + tcpL := pktR.Layer(layers.LayerTypeTCP) + Expect(tcpL).NotTo(BeNil()) + tcpR := tcpL.(*layers.TCP) + Expect(tcpR.SrcPort).To(Equal(layers.TCPPort(tcp.SrcPort))) + Expect(tcpR.DstPort).To(Equal(layers.TCPPort(podPort))) + + ct, err := conntrack.LoadMapMem(ctMap) + Expect(err).NotTo(HaveOccurred()) + + ctKey := conntrack.NewKey(uint8(layers.IPProtocolTCP), + ipv4.SrcIP, uint16(tcp.SrcPort), ipv4.DstIP, uint16(tcp.DstPort)) + + Expect(ct).Should(HaveKey(ctKey)) + ctr := ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATForward)) + Expect(ctr.NATSPort()).To(Equal(uint16(0))) + + ctKey = ctr.ReverseNATKey().(conntrack.Key) + Expect(ct).Should(HaveKey(ctKey)) + ctr = ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATReverse)) + + // Approved source side + Expect(ctr.Data().A2B.Approved).To(BeTrue()) + // Dest not approved yet + Expect(ctr.Data().B2A.Approved).NotTo(BeTrue()) + + recvPkt = res.dataOut + + dumpCTMap(ctMap) + ct, err = conntrack.LoadMapMem(ctMap) + Expect(err).NotTo(HaveOccurred()) + + v, ok := ct[conntrack.NewKey(uint8(layers.IPProtocolTCP), ipv4.SrcIP, uint16(tcp.SrcPort), podIP.To4(), podPort)] + Expect(ok).To(BeTrue()) + Expect(v.Type()).To(Equal(conntrack.TypeNATReverse)) + Expect(v.Flags()).To(Equal(conntrack4.FlagExtLocal | conntrack4.FlagMaglev)) + }) + expectMark(tcdefs.MarkSeen) + + dumpARPMap(arpMap) + arpNode2 = saveARPMap(arpMap) + ctNode2 = saveCTMap(ctMap) + Expect(arpNode2).To(HaveLen(1)) + + arpKey := arp.NewKey(node3ip, 1 /* ifindex is always 1 in UT */) + Expect(arpNode2).To(HaveKey(arpKey)) + + macDst := encapedPkt[0:6] + macSrc := encapedPkt[6:12] + Expect(arpNode2[arpKey]).To(Equal(arp.NewValue(macDst, macSrc))) + + // try a spoofed tunnel packet, should be dropped and have no effect + skbMark = 0 + runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { + // modify the only known good src IP, we do not care about csums at this point + spoofedPkt := make([]byte, len(encapedPkt)) + copy(spoofedPkt, encapedPkt) + spoofedPkt[26] = 234 + res, err := bpfrun(spoofedPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(resTC_ACT_SHOT)) + }) + + skbMark = tcdefs.MarkSeen + + // Insert the reverse route for backend for RPF check. + beV4CIDR := ip.CIDRFromNetIP(podIP).(ip.V4CIDR) + bertKey := routes.NewKey(beV4CIDR).AsBytes() + bertVal := routes.NewValueWithIfIndex(routes.FlagsLocalWorkload|routes.FlagInIPAMPool, 1).AsBytes() + err = rtMap.Update(bertKey, bertVal) + Expect(err).NotTo(HaveOccurred()) + + // Arriving at workload at node 2 + runBpfTest(t, "calico_to_workload_ep", rulesDefaultAllow, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(recvPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(retvalToStr[res.Retval]).To(Equal(retvalToStr[resTC_ACT_UNSPEC])) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + Expect(res.dataOut).To(Equal(recvPkt)) + + ctNode2, err = conntrack.LoadMapMem(ctMap) + Expect(err).NotTo(HaveOccurred()) + + ctKey := conntrack.NewKey(uint8(layers.IPProtocolTCP), + ipv4.SrcIP, uint16(tcp.SrcPort), ipv4.DstIP, uint16(tcp.DstPort)) + + Expect(ctNode2).Should(HaveKey(ctKey)) + ctr := ctNode2[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATForward)) + + ctKey = ctr.ReverseNATKey().(conntrack.Key) + Expect(ctNode2).Should(HaveKey(ctKey)) + ctr = ctNode2[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATReverse), + fmt.Sprintf("Expected reverse conntrack entry but got %v", ctr)) + + // Approved source side + Expect(ctr.Data().A2B.Approved).To(BeTrue()) + // Approved destination side as well + Expect(ctr.Data().B2A.Approved).To(BeTrue()) + }) + + skbMark = 0 + + // Response leaving workload at node 2 + runBpfTest(t, "calico_from_workload_ep", rulesDefaultAllow, func(bpfrun bpfProgRunFn) { + respPkt := tcpResponseRaw(recvPkt) + // Change the MAC addresses so that we can observe that the right + // addresses were patched in. + copy(respPkt[:6], []byte{1, 2, 3, 4, 5, 6}) + copy(respPkt[6:12], []byte{6, 5, 4, 3, 2, 1}) + res, err := bpfrun(respPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(retvalToStr[res.Retval]).To(Equal(retvalToStr[resTC_ACT_REDIRECT])) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + ethL := pktR.Layer(layers.LayerTypeEthernet) + Expect(ethL).NotTo(BeNil()) + ethR := ethL.(*layers.Ethernet) + Expect(ethR).To(layersMatchFields(&layers.Ethernet{ + SrcMAC: macDst, + DstMAC: macSrc, + EthernetType: layers.EthernetTypeIPv4, + })) + + ipv4L := pktR.Layer(layers.LayerTypeIPv4) + Expect(ipv4L).NotTo(BeNil()) + ipv4R := ipv4L.(*layers.IPv4) + Expect(ipv4R.SrcIP.String()).To(Equal(hostIP.String())) + Expect(ipv4R.DstIP.String()).To(Equal(node3ip.String())) + + checkVxlan(pktR) + + encapedPkt = res.dataOut + }) + + dumpCTMap(ctMap) + expectMark(tcdefs.MarkSeen) + + // Response leaving node 2 + runBpfTest(t, "calico_to_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(encapedPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(retvalToStr[res.Retval]).To(Equal(retvalToStr[resTC_ACT_UNSPEC])) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + ipv4L := pktR.Layer(layers.LayerTypeIPv4) + Expect(ipv4L).NotTo(BeNil()) + ipv4R := ipv4L.(*layers.IPv4) + // check that the IP is fixed up + Expect(ipv4R.SrcIP.String()).To(Equal(node2ip.String())) + Expect(ipv4R.DstIP.String()).To(Equal(node3ip.String())) + + checkVxlan(pktR) + + encapedPkt = res.dataOut + }) + expectMark(tcdefs.MarkSeen) + + rtNode2 = saveRTMap(rtMap) + ctNode2 = saveCTMap(ctMap) + arpNode2 = saveARPMap(arpMap) + + // Now on node 1, handling a failing-over connection. + newNode(node1ip) + encapedPkt = nil + + // Src IP 1.1.1.1, Src Port 1234, Dst IP 172.16.1.1, Dst Port 5678. + _, ipv4, tcp, payload, pktBytes, err = testPacketTCPV4DefaultNP(serviceIP, false) + Expect(err).NotTo(HaveOccurred()) + + // Node 1: Flagging frontend map item with the consistent-hash flag. + err = natMap.Update( + nat.NewNATKey(ipv4.DstIP, uint16(tcp.DstPort), uint8(layers.IPProtocolTCP)).AsBytes(), + nat.NewNATValueWithFlags(maglevSvcID, 1, 0, 0, nat.NATFlgMaglev).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + + // Node 1: Build a maglev LUT and program each item to the BPF map. + mglv = consistenthash.New(int(maglevTestM), fnv.New32(), fnv.New32()) + mglv.AddBackend(chtypes.MockEndpoint{ + Ip: podIP.String(), + Prt: podPort, + }) + lut = mglv.Generate() + for ordinal, ep := range lut { + err = mgMap.Update( + nat.NewMaglevBackendKey(maglevSvcID, uint32(ordinal)).AsBytes(), + nat.NewNATBackendValue(net.ParseIP(ep.IP()), uint16(ep.Port())).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + } + + // Arriving at node 1 - non-routable -> denied + runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(pktBytes) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(resTC_ACT_SHOT)) + }) + + err = rtMap.Update( + routes.NewKey(ip.CIDRFromIPNet(&node2WlCIDR).(ip.V4CIDR)).AsBytes(), + routes.NewValueWithNextHop(routes.FlagsRemoteWorkload|routes.FlagInIPAMPool, + ip.FromNetIP(node2ip).(ip.V4Addr)).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + err = rtMap.Update( + routes.NewKey(ip.CIDRFromIPNet(&node1CIDR).(ip.V4CIDR)).AsBytes(), + routes.NewValue(routes.FlagsLocalHost).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + err = rtMap.Update( + routes.NewKey(ip.CIDRFromIPNet(&node2CIDR).(ip.V4CIDR)).AsBytes(), + routes.NewValue(routes.FlagsRemoteHost).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + dumpRTMap(rtMap) + + // Arriving at node 1 + runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(pktBytes) + Expect(err).NotTo(HaveOccurred()) + // Do comparison on the strings rather than the numbers to give more + // meaning to Gomega failure messages. + Expect(retvalToStr[res.Retval]).To(Equal(retvalToStr[resTC_ACT_REDIRECT])) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + ipv4L := pktR.Layer(layers.LayerTypeIPv4) + Expect(ipv4L).NotTo(BeNil()) + ipv4R := ipv4L.(*layers.IPv4) + Expect(ipv4R.SrcIP.String()).To(Equal(hostIP.String())) + Expect(ipv4R.DstIP.String()).To(Equal(node2ip.String())) + + checkVxlanEncap(pktR, false, ipv4, tcp, payload) + + encapedPkt = res.dataOut + + ct, err := conntrack.LoadMapMem(ctMap) + Expect(err).NotTo(HaveOccurred()) + + ctKey := conntrack.NewKey(uint8(layers.IPProtocolTCP), + ipv4.SrcIP, uint16(tcp.SrcPort), ipv4.DstIP, uint16(tcp.DstPort)) + + Expect(ct).Should(HaveKey(ctKey)) + ctr := ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATForward)) + + ctKey = ctr.ReverseNATKey().(conntrack.Key) + Expect(ct).Should(HaveKey(ctKey)) + ctr = ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATReverse)) + + // Approved for both sides due to forwarding through the tunnel + Expect(ctr.Data().A2B.Approved).To(BeTrue()) + Expect(ctr.Data().B2A.Approved).To(BeTrue()) + }) + expectMark(tcdefs.MarkSeenBypassForward) + + dumpCTMap(ctMap) + ct, err := conntrack.LoadMapMem(ctMap) + Expect(err).NotTo(HaveOccurred()) + v, ok := ct[conntrack.NewKey(uint8(layers.IPProtocolTCP), ipv4.SrcIP, uint16(tcp.SrcPort), podIP.To4(), podPort)] + Expect(ok).To(BeTrue()) + Expect(v.Type()).To(Equal(conntrack.TypeNATReverse)) + Expect(v.Flags()).To(Equal(conntrack4.FlagNATNPFwd | conntrack4.FlagMaglev)) + + // Leaving node 1 + runBpfTest(t, "calico_to_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(encapedPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(resTC_ACT_UNSPEC)) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + Expect(res.dataOut).To(Equal(encapedPkt)) + }) + + dumpCTMap(ctMap) + ctNode1 = saveCTMap(ctMap) + rtNode1 = saveRTMap(rtMap) + arpNode1 = saveARPMap(arpMap) + + encapedPktArrivesAtNode2 = make([]byte, len(encapedPkt)) + copy(encapedPktArrivesAtNode2, encapedPkt) + + recvPkt = nil + newNode(node2ip) + restoreCTMap(ctMap, ctNode2) + restoreRTMap(rtMap, rtNode2) + restoreARPMap(arpMap, arpNode2) + + dumpRTMap(rtMap) + + // now we are at the node with local workload + err = natMap.Update( + nat.NewNATKey(ipv4.DstIP, uint16(tcp.DstPort), uint8(layers.IPProtocolTCP)).AsBytes(), + nat.NewNATValueWithFlags(maglevSvcID /* id */, 1 /* count */, 1 /* local */, 0, nat.NATFlgMaglev).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + + // Node 2: Build a maglev LUT and program each item to the BPF map. + mglv = consistenthash.New(int(maglevTestM), fnv.New32(), fnv.New32()) + mglv.AddBackend(chtypes.MockEndpoint{ + Ip: podIP.String(), + Prt: podPort, + }) + lut = mglv.Generate() + for ordinal, ep := range lut { + err = mgMap.Update( + nat.NewMaglevBackendKey(maglevSvcID, uint32(ordinal)).AsBytes(), + nat.NewNATBackendValue(net.ParseIP(ep.IP()), uint16(ep.Port())).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + } + + // Arriving at node 2 + runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(encapedPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(retvalToStr[res.Retval]).To(Equal(retvalToStr[resTC_ACT_REDIRECT])) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + payloadL := pktR.ApplicationLayer() + Expect(payloadL).NotTo(BeNil()) + vxlanL := gopacket.NewPacket(payloadL.Payload(), layers.LayerTypeVXLAN, gopacket.Default) + Expect(vxlanL).NotTo(BeNil()) + fmt.Printf("vxlanL = %+v\n", vxlanL) + + ipv4L := pktR.Layer(layers.LayerTypeIPv4) + ipv4R := ipv4L.(*layers.IPv4) + Expect(ipv4R.SrcIP.String()).To(Equal(ipv4.SrcIP.String())) + Expect(ipv4R.DstIP.String()).To(Equal(podIP.String())) + + tcpL := pktR.Layer(layers.LayerTypeTCP) + Expect(tcpL).NotTo(BeNil()) + tcpR := tcpL.(*layers.TCP) + Expect(tcpR.SrcPort).To(Equal(layers.TCPPort(tcp.SrcPort))) + Expect(tcpR.DstPort).To(Equal(layers.TCPPort(podPort))) + + ct, err := conntrack.LoadMapMem(ctMap) + Expect(err).NotTo(HaveOccurred()) + + ctKey := conntrack.NewKey(uint8(layers.IPProtocolTCP), + ipv4.SrcIP, uint16(tcp.SrcPort), ipv4.DstIP, uint16(tcp.DstPort)) + + Expect(ct).Should(HaveKey(ctKey)) + ctr := ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATForward)) + Expect(ctr.NATSPort()).To(Equal(uint16(0))) + + ctKey = ctr.ReverseNATKey().(conntrack.Key) + Expect(ct).Should(HaveKey(ctKey)) + ctr = ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATReverse)) + + // Approved source side + Expect(ctr.Data().A2B.Approved).To(BeTrue()) + Expect(ctr.Data().B2A.Approved).To(BeTrue()) + + recvPkt = res.dataOut + + dumpCTMap(ctMap) + ct, err = conntrack.LoadMapMem(ctMap) + Expect(err).NotTo(HaveOccurred()) + v, ok = ct[conntrack.NewKey(uint8(layers.IPProtocolTCP), ipv4.SrcIP, uint16(tcp.SrcPort), podIP.To4(), podPort)] + Expect(ok).To(BeTrue()) + Expect(v.Type()).To(Equal(conntrack.TypeNATReverse)) + Expect(v.Flags()).To(Equal(conntrack4.FlagExtLocal | conntrack4.FlagMaglev)) + }) + expectMark(tcdefs.MarkSeen) + + dumpARPMap(arpMap) + arpNode2 = saveARPMap(arpMap) + Expect(arpNode2).To(HaveLen(2)) + arpKey = arp.NewKey(node1ip, 1 /* ifindex is always 1 in UT */) + Expect(arpNode2).To(HaveKey(arpKey)) + macDst = encapedPkt[0:6] + macSrc = encapedPkt[6:12] + Expect(arpNode2[arpKey]).To(Equal(arp.NewValue(macDst, macSrc))) + + // try a spoofed tunnel packet, should be dropped and have no effect + skbMark = 0 + runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { + // modify the only known good src IP, we do not care about csums at this point + encapedPkt[26] = 234 + res, err := bpfrun(encapedPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(resTC_ACT_SHOT)) + }) + + skbMark = tcdefs.MarkSeen + + // Insert the reverse route for backend for RPF check. + resetRTMap(rtMap) + beV4CIDR = ip.CIDRFromNetIP(podIP).(ip.V4CIDR) + bertKey = routes.NewKey(beV4CIDR).AsBytes() + bertVal = routes.NewValueWithIfIndex(routes.FlagsLocalWorkload|routes.FlagInIPAMPool, 1).AsBytes() + err = rtMap.Update(bertKey, bertVal) + Expect(err).NotTo(HaveOccurred()) + + // Arriving at workload at node 2 + runBpfTest(t, "calico_to_workload_ep", rulesDefaultAllow, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(recvPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(retvalToStr[res.Retval]).To(Equal(retvalToStr[resTC_ACT_UNSPEC])) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + Expect(res.dataOut).To(Equal(recvPkt)) + + ct, err := conntrack.LoadMapMem(ctMap) + Expect(err).NotTo(HaveOccurred()) + + ctKey := conntrack.NewKey(uint8(layers.IPProtocolTCP), + ipv4.SrcIP, uint16(tcp.SrcPort), ipv4.DstIP, uint16(tcp.DstPort)) + + Expect(ct).Should(HaveKey(ctKey)) + ctr := ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATForward)) + + ctKey = ctr.ReverseNATKey().(conntrack.Key) + Expect(ct).Should(HaveKey(ctKey)) + ctr = ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATReverse), + fmt.Sprintf("Expected reverse conntrack entry but got %v", ctr)) + + // Approved source side + Expect(ctr.Data().A2B.Approved).To(BeTrue()) + // Approved destination side as well + Expect(ctr.Data().B2A.Approved).To(BeTrue()) + }) + + skbMark = 0 + + // Response leaving workload at node 2 + runBpfTest(t, "calico_from_workload_ep", rulesDefaultAllow, func(bpfrun bpfProgRunFn) { + respPkt := tcpResponseRaw(recvPkt) + // Change the MAC addresses so that we can observe that the right + // addresses were patched in. + copy(respPkt[:6], []byte{1, 2, 3, 4, 5, 6}) + copy(respPkt[6:12], []byte{6, 5, 4, 3, 2, 1}) + res, err := bpfrun(respPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(retvalToStr[res.Retval]).To(Equal(retvalToStr[resTC_ACT_REDIRECT])) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + ethL := pktR.Layer(layers.LayerTypeEthernet) + Expect(ethL).NotTo(BeNil()) + ethR := ethL.(*layers.Ethernet) + Expect(ethR).To(layersMatchFields(&layers.Ethernet{ + SrcMAC: macDst, + DstMAC: macSrc, + EthernetType: layers.EthernetTypeIPv4, + })) + + ipv4L := pktR.Layer(layers.LayerTypeIPv4) + Expect(ipv4L).NotTo(BeNil()) + ipv4R := ipv4L.(*layers.IPv4) + Expect(ipv4R.SrcIP.String()).To(Equal(hostIP.String())) + Expect(ipv4R.DstIP.String()).To(Equal(node1ip.String())) + + checkVxlan(pktR) + + encapedPkt = res.dataOut + }) + + dumpCTMap(ctMap) + expectMark(tcdefs.MarkSeen) + + // Response leaving node 2 + runBpfTest(t, "calico_to_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(encapedPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(retvalToStr[res.Retval]).To(Equal(retvalToStr[resTC_ACT_UNSPEC])) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + ipv4L := pktR.Layer(layers.LayerTypeIPv4) + Expect(ipv4L).NotTo(BeNil()) + ipv4R := ipv4L.(*layers.IPv4) + // check that the IP is fixed up + Expect(ipv4R.SrcIP.String()).To(Equal(node2ip.String())) + Expect(ipv4R.DstIP.String()).To(Equal(node1ip.String())) + + checkVxlan(pktR) + + encapedPkt = res.dataOut + }) + + dumpCTMap(ctMap) + ctNode2 = saveCTMap(ctMap) + + // Back at node 1 + newNode(node1ip) + restoreCTMap(ctMap, ctNode1) + restoreARPMap(arpMap, arpNode1) + restoreRTMap(rtMap, rtNode1) + + err = natMap.Update( + nat.NewNATKey(ipv4.DstIP, uint16(tcp.DstPort), uint8(layers.IPProtocolTCP)).AsBytes(), + nat.NewNATValueWithFlags(maglevSvcID, 1, 0, 0, nat.NATFlgMaglev).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + mglv = consistenthash.New(int(maglevTestM), fnv.New32(), fnv.New32()) + mglv.AddBackend(chtypes.MockEndpoint{ + Ip: podIP.String(), + Prt: podPort, + }) + lut = mglv.Generate() + for ordinal, ep := range lut { + err = mgMap.Update( + nat.NewMaglevBackendKey(maglevSvcID, uint32(ordinal)).AsBytes(), + nat.NewNATBackendValue(net.ParseIP(ep.IP()), uint16(ep.Port())).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + } + + runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(encapedPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(retvalToStr[res.Retval]).To(Equal(retvalToStr[resTC_ACT_REDIRECT])) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + ipv4L := pktR.Layer(layers.LayerTypeIPv4) + Expect(ipv4L).NotTo(BeNil()) + ipv4R := ipv4L.(*layers.IPv4) + Expect(ipv4R.DstIP.String()).To(Equal(ipv4.SrcIP.String())) + Expect(ipv4R.SrcIP.String()).To(Equal(ipv4.DstIP.String())) + + tcpL := pktR.Layer(layers.LayerTypeTCP) + Expect(tcpL).NotTo(BeNil()) + tcpR := tcpL.(*layers.TCP) + Expect(tcpR.SrcPort).To(Equal(tcp.DstPort)) + Expect(tcpR.DstPort).To(Equal(tcp.SrcPort)) + + payloadL := pktR.ApplicationLayer() + Expect(payloadL).NotTo(BeNil()) + Expect(payload).To(Equal(payloadL.Payload())) + + recvPkt = res.dataOut + }) + + expectMark(tcdefs.MarkSeenBypassForward) + + skbMark = 0 + + // Another pkt arriving at node 1 - uses existing CT entries. + // Change original packet not to be a SYN. + _, ipv4, tcp, payload, pktBytes, err = testPacketTCPV4DefaultNP(serviceIP, false) + Expect(err).NotTo(HaveOccurred()) + runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(pktBytes) + Expect(err).NotTo(HaveOccurred()) + Expect(retvalToStr[res.Retval]).To(Equal(retvalToStr[resTC_ACT_REDIRECT])) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + ipv4L := pktR.Layer(layers.LayerTypeIPv4) + Expect(ipv4L).NotTo(BeNil()) + ipv4R := ipv4L.(*layers.IPv4) + Expect(ipv4R.SrcIP.String()).To(Equal(hostIP.String())) + Expect(ipv4R.DstIP.String()).To(Equal(node2ip.String())) + + // Should be a VXLAN tunnel packet. + udpL := pktR.Layer(layers.LayerTypeUDP) + Expect(udpL).NotTo(BeNil()) + udpR := udpL.(*layers.UDP) + + Expect(udpR.SrcPort).To(BeEquivalentTo(tcp.SrcPort ^ tcp.DstPort)) + + checkVxlanEncap(pktR, false, ipv4, tcp, payload) + }) + + skbMark = 0 + + hostIP = node2ip + + // change the routing - it is a local workload now! + err = rtMap.Update( + routes.NewKey(ip.CIDRFromIPNet(&node2WlCIDR).(ip.V4CIDR)).AsBytes(), + routes.NewValue(routes.FlagsLocalWorkload|routes.FlagInIPAMPool).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + + // we must know that the encaped packet src ip if from a known host + err = rtMap.Update( + routes.NewKey(ip.CIDRFromIPNet(&node1CIDR).(ip.V4CIDR)).AsBytes(), + routes.NewValue(routes.FlagsRemoteHost).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + err = rtMap.Update( + routes.NewKey(ip.CIDRFromIPNet(&node2CIDR).(ip.V4CIDR)).AsBytes(), + routes.NewValue(routes.FlagsLocalHost).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + + dumpRTMap(rtMap) + dumpCTMap(ctMap) +} + +func TestMaglevNATServiceIPTCPV6(t *testing.T) { + RegisterTestingT(t) + + bpfIfaceName = "MNP-1" + defer func() { bpfIfaceName = "" }() + + serviceIP := net.ParseIP("c471::c000:8080:8080").To16() + Expect(serviceIP).NotTo(BeNil()) + + _, ipv6, tcp, payload, pktBytes, err := testPacketTCPV6DefaultNP(serviceIP, true) + Expect(err).NotTo(HaveOccurred()) + + err = natMapV6.Update( + nat.NewNATKeyV6(ipv6.DstIP, uint16(tcp.DstPort), uint8(layers.IPProtocolTCP)).AsBytes(), + nat.NewNATValueV6WithFlags(maglevSvcID, 1, 0, 0, nat.NATFlgMaglev).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + + natIP := net.ParseIP("abcd::ffff:0808:0808") + natPort := uint16(666) + + mgMap6 := nat.MaglevMapV6() + err = mgMap6.EnsureExists() + Expect(err).NotTo(HaveOccurred()) + + // Build a maglev LUT and program each item to the BPF map. + mglv := consistenthash.New(int(maglevTestM), fnv.New32(), fnv.New32()) + mglv.AddBackend(chtypes.MockEndpoint{ + Ip: natIP.String(), + Prt: natPort, + }) + lut := mglv.Generate() + for ordinal, ep := range lut { + err = mgMap6.Update( + nat.NewMaglevBackendKeyV6(maglevSvcID, uint32(ordinal)).AsBytes(), + nat.NewNATBackendValueV6(net.ParseIP(ep.IP()), uint16(ep.Port())).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + } + + node2wCIDR := net.IPNet{ + IP: natIP, + Mask: net.CIDRMask(128, 128), + } + + resetCTMapV6(ctMapV6) // ensure it is clean + + var encapedPkt []byte + + resetRTMap(rtMapV6) + + hostIP = node1ipV6 + skbMark = 0 + + defer resetRTMapV6(rtMapV6) + Expect(err).NotTo(HaveOccurred()) + err = rtMapV6.Update( + routes.NewKeyV6(ip.CIDRFromIPNet(&node2wCIDR).(ip.V6CIDR)).AsBytes(), + routes.NewValueV6WithNextHop(routes.FlagsRemoteWorkload|routes.FlagInIPAMPool, + ip.FromNetIP(node2ipV6).(ip.V6Addr)).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + err = rtMapV6.Update( + routes.NewKeyV6(ip.CIDRFromIPNet(&node1CIDRV6).(ip.V6CIDR)).AsBytes(), + routes.NewValueV6(routes.FlagsLocalHost).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + err = rtMapV6.Update( + routes.NewKeyV6(ip.CIDRFromIPNet(&node2CIDRV6).(ip.V6CIDR)).AsBytes(), + routes.NewValueV6(routes.FlagsRemoteHost).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + dumpRTMapV6(rtMapV6) + rtNode1 := saveRTMapV6(rtMapV6) + + // Arriving at node 1 + runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(pktBytes) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Or( + Equal(resTC_ACT_REDIRECT), + Equal(resTC_ACT_UNSPEC), + )) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + ipv6L := pktR.Layer(layers.LayerTypeIPv6) + Expect(ipv6L).NotTo(BeNil()) + ipv6R := ipv6L.(*layers.IPv6) + Expect(ipv6R.SrcIP.String()).To(Equal(hostIP.String())) + Expect(ipv6R.DstIP.String()).To(Equal(node2ipV6.String())) + + checkVxlanEncap(pktR, false, ipv6, tcp, payload) + + encapedPkt = res.dataOut + + ct, err := conntrack.LoadMapMemV6(ctMapV6) + Expect(err).NotTo(HaveOccurred()) + + ctKey := conntrack.NewKeyV6(uint8(layers.IPProtocolTCP), + ipv6.SrcIP, uint16(tcp.SrcPort), ipv6.DstIP, uint16(tcp.DstPort)) + + Expect(ct).Should(HaveKey(ctKey)) + ctr := ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATForward)) + + ctKey = ctr.ReverseNATKey().(conntrack.KeyV6) + Expect(ct).Should(HaveKey(ctKey)) + ctr = ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATReverse)) + + // Approved for both sides due to forwarding through the tunnel + Expect(ctr.Data().A2B.Approved).To(BeTrue()) + Expect(ctr.Data().B2A.Approved).To(BeTrue()) + }, withIPv6()) + + dumpCTMapV6(ctMapV6) + ct, err := conntrack.LoadMapMemV6(ctMapV6) + Expect(err).NotTo(HaveOccurred()) + v, ok := ct[conntrack.NewKeyV6(uint8(layers.IPProtocolTCP), ipv6.SrcIP, uint16(tcp.SrcPort), natIP, natPort)] + Expect(ok).To(BeTrue()) + Expect(v.Type()).To(Equal(conntrack.TypeNATReverse)) + Expect(v.Flags()).To(Equal(conntrack4.FlagNATNPFwd | conntrack4.FlagMaglev)) + + expectMark(tcdefs.MarkSeenBypassForward) + + // Leaving node 1 + runBpfTest(t, "calico_to_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(encapedPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(resTC_ACT_UNSPEC)) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + Expect(res.dataOut).To(Equal(encapedPkt)) + }, withIPv6()) + + dumpCTMapV6(ctMapV6) + fromHostCT := saveCTMapV6(ctMapV6) + + encapedPktArrivesAtNode2 := make([]byte, len(encapedPkt)) + copy(encapedPktArrivesAtNode2, encapedPkt) + + resetCTMapV6(ctMapV6) + + var recvPkt []byte + + hostIP = node2ipV6 + + // change the routing - it is a local workload now! + err = rtMapV6.Update( + routes.NewKeyV6(ip.CIDRFromIPNet(&node2wCIDR).(ip.V6CIDR)).AsBytes(), + routes.NewValueV6(routes.FlagsLocalWorkload|routes.FlagInIPAMPool).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + + // we must know that the encaped packet src ip if from a known host + err = rtMapV6.Update( + routes.NewKeyV6(ip.CIDRFromIPNet(&node1CIDRV6).(ip.V6CIDR)).AsBytes(), + routes.NewValueV6(routes.FlagsRemoteHost).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + err = rtMapV6.Update( + routes.NewKeyV6(ip.CIDRFromIPNet(&node2CIDRV6).(ip.V6CIDR)).AsBytes(), + routes.NewValueV6(routes.FlagsLocalHost).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + + dumpRTMapV6(rtMapV6) + + // now we are at the node with local workload + err = natMapV6.Update( + nat.NewNATKeyV6(ipv6.DstIP, uint16(tcp.DstPort), uint8(layers.IPProtocolTCP)).AsBytes(), + nat.NewNATValueV6WithFlags(maglevSvcID /* id */, 1 /* count */, 1 /* local */, 0, nat.NATFlgMaglev).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + + // Arriving at node 2 + bpfIfaceName = "NP-2" + + arpMapN2 := saveARPMapV6(arpMapV6) + Expect(arpMapN2).To(HaveLen(0)) + + skbMark = 0 + runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(encapedPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(retvalToStr[res.Retval]).To(Equal(retvalToStr[resTC_ACT_REDIRECT])) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + payloadL := pktR.ApplicationLayer() + Expect(payloadL).NotTo(BeNil()) + vxlanL := gopacket.NewPacket(payloadL.Payload(), layers.LayerTypeVXLAN, gopacket.Default) + Expect(vxlanL).NotTo(BeNil()) + fmt.Printf("vxlanL = %+v\n", vxlanL) + + ipv6L := pktR.Layer(layers.LayerTypeIPv6) + ipv6R := ipv6L.(*layers.IPv6) + Expect(ipv6R.SrcIP.String()).To(Equal(ipv6.SrcIP.String())) + Expect(ipv6R.DstIP.String()).To(Equal(natIP.String())) + + tcpL := pktR.Layer(layers.LayerTypeTCP) + Expect(tcpL).NotTo(BeNil()) + tcpR := tcpL.(*layers.TCP) + Expect(tcpR.SrcPort).To(Equal(layers.TCPPort(tcp.SrcPort))) + Expect(tcpR.DstPort).To(Equal(layers.TCPPort(natPort))) + + ct, err := conntrack.LoadMapMemV6(ctMapV6) + Expect(err).NotTo(HaveOccurred()) + + ctKey := conntrack.NewKeyV6(uint8(layers.IPProtocolTCP), + ipv6.SrcIP, uint16(tcp.SrcPort), ipv6.DstIP, uint16(tcp.DstPort)) + + Expect(ct).Should(HaveKey(ctKey)) + ctr := ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATForward)) + Expect(ctr.NATSPort()).To(Equal(uint16(0))) + + ctKey = ctr.ReverseNATKey().(conntrack.KeyV6) + Expect(ct).Should(HaveKey(ctKey)) + ctr = ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATReverse)) + + // Approved source side + Expect(ctr.Data().A2B.Approved).To(BeTrue()) + // Dest not approved yet + Expect(ctr.Data().B2A.Approved).NotTo(BeTrue()) + + recvPkt = res.dataOut + }, withIPv6()) + + expectMark(tcdefs.MarkSeen) + + dumpCTMapV6(ctMapV6) + ct, err = conntrack.LoadMapMemV6(ctMapV6) + Expect(err).NotTo(HaveOccurred()) + v, ok = ct[conntrack.NewKeyV6(uint8(layers.IPProtocolTCP), ipv6.SrcIP, uint16(tcp.SrcPort), natIP, natPort)] + Expect(ok).To(BeTrue()) + Expect(v.Type()).To(Equal(conntrack.TypeNATReverse)) + Expect(v.Flags()).To(Equal(conntrack4.FlagExtLocal | conntrack4.FlagMaglev)) + + dumpARPMapV6(arpMapV6) + + arpMapN2 = saveARPMapV6(arpMapV6) + Expect(arpMapN2).To(HaveLen(1)) + arpKey := arp.NewKeyV6(node1ipV6, 1) // ifindex is always 1 in UT + Expect(arpMapN2).To(HaveKey(arpKey)) + macDst := encapedPkt[0:6] + macSrc := encapedPkt[6:12] + Expect(arpMapN2[arpKey]).To(Equal(arp.NewValue(macDst, macSrc))) + + // try a spoofed tunnel packet, should be dropped and have no effect + skbMark = 0 + runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { + // modify the only known good src IP, we do not care about csums at this point + encapedPkt[26] = 234 + res, err := bpfrun(encapedPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(resTC_ACT_SHOT)) + }, withIPv6()) + + skbMark = tcdefs.MarkSeen + + // Insert the reverse route for backend for RPF check. + resetRTMap(rtMapV6) + beV4CIDR := ip.CIDRFromNetIP(natIP).(ip.V6CIDR) + bertKey := routes.NewKeyV6(beV4CIDR).AsBytes() + bertVal := routes.NewValueV6WithIfIndex(routes.FlagsLocalWorkload|routes.FlagInIPAMPool, 1).AsBytes() + err = rtMapV6.Update(bertKey, bertVal) + Expect(err).NotTo(HaveOccurred()) + + // Arriving at workload at node 2 + runBpfTest(t, "calico_to_workload_ep", rulesDefaultAllow, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(recvPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(resTC_ACT_UNSPEC)) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + Expect(res.dataOut).To(Equal(recvPkt)) + + ct, err := conntrack.LoadMapMemV6(ctMapV6) + Expect(err).NotTo(HaveOccurred()) + + ctKey := conntrack.NewKeyV6(uint8(layers.IPProtocolTCP), + ipv6.SrcIP, uint16(tcp.SrcPort), ipv6.DstIP, uint16(tcp.DstPort)) + + Expect(ct).Should(HaveKey(ctKey)) + ctr := ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATForward)) + + ctKey = ctr.ReverseNATKey().(conntrack.KeyV6) + Expect(ct).Should(HaveKey(ctKey)) + ctr = ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATReverse), + fmt.Sprintf("Expected reverse conntrack entry but got %v", ctr)) + + // Approved source side + Expect(ctr.Data().A2B.Approved).To(BeTrue()) + // Approved destination side as well + Expect(ctr.Data().B2A.Approved).To(BeTrue()) + }, withIPv6()) + + skbMark = 0 + + // Response leaving workload at node 2 + runBpfTest(t, "calico_from_workload_ep", rulesDefaultAllow, func(bpfrun bpfProgRunFn) { + // SYN,ACK + respPkt := tcpResponseRawV6(recvPkt) + // Change the MAC addresses so that we can observe that the right + // addresses were patched in. + copy(respPkt[:6], []byte{1, 2, 3, 4, 5, 6}) + copy(respPkt[6:12], []byte{6, 5, 4, 3, 2, 1}) + res, err := bpfrun(respPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Or( + Equal(resTC_ACT_REDIRECT), + Equal(resTC_ACT_UNSPEC), + )) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + ethL := pktR.Layer(layers.LayerTypeEthernet) + Expect(ethL).NotTo(BeNil()) + ethR := ethL.(*layers.Ethernet) + Expect(ethR).To(layersMatchFields(&layers.Ethernet{ + SrcMAC: macDst, + DstMAC: macSrc, + EthernetType: layers.EthernetTypeIPv6, + })) + + ipv6L := pktR.Layer(layers.LayerTypeIPv6) + Expect(ipv6L).NotTo(BeNil()) + ipv6R := ipv6L.(*layers.IPv6) + Expect(ipv6R.SrcIP.String()).To(Equal(hostIP.String())) + Expect(ipv6R.DstIP.String()).To(Equal(node1ipV6.String())) + + checkVxlan(pktR) + + encapedPkt = res.dataOut + }, withIPv6()) + + dumpCTMapV6(ctMapV6) + + expectMark(tcdefs.MarkSeen) + + hostIP = node2ipV6 + + // Response leaving node 2 + runBpfTest(t, "calico_to_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(encapedPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(resTC_ACT_UNSPEC)) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + ipv6L := pktR.Layer(layers.LayerTypeIPv6) + Expect(ipv6L).NotTo(BeNil()) + ipv6R := ipv6L.(*layers.IPv6) + // check that the IP is fixed up + Expect(ipv6R.SrcIP.String()).To(Equal(node2ipV6.String())) + Expect(ipv6R.DstIP.String()).To(Equal(node1ipV6.String())) + + checkVxlan(pktR) + + encapedPkt = res.dataOut + }, withIPv6()) + + dumpCTMapV6(ctMapV6) + resetCTMapV6(ctMapV6) + restoreCTMapV6(ctMapV6, fromHostCT) + dumpCTMapV6(ctMapV6) + + hostIP = node1ipV6 + + // change to routing again to a remote workload + resetRTMap(rtMapV6) + restoreRTMapV6(rtMapV6, rtNode1) + dumpRTMapV6(rtMapV6) + + // Response arriving at node 1 + bpfIfaceName = "NP-1" + skbMark = 0 + + runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(encapedPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(retvalToStr[res.Retval]).To(Equal(retvalToStr[resTC_ACT_REDIRECT])) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + ipv6L := pktR.Layer(layers.LayerTypeIPv6) + Expect(ipv6L).NotTo(BeNil()) + ipv6R := ipv6L.(*layers.IPv6) + Expect(ipv6R.DstIP.String()).To(Equal(ipv6.SrcIP.String())) + Expect(ipv6R.SrcIP.String()).To(Equal(ipv6.DstIP.String())) + + tcpL := pktR.Layer(layers.LayerTypeTCP) + Expect(tcpL).NotTo(BeNil()) + tcpR := tcpL.(*layers.TCP) + Expect(tcpR.SrcPort).To(Equal(tcp.DstPort)) + Expect(tcpR.DstPort).To(Equal(tcp.SrcPort)) + + payloadL := pktR.ApplicationLayer() + Expect(payloadL).NotTo(BeNil()) + Expect(payload).To(Equal(payloadL.Payload())) + + recvPkt = res.dataOut + }, withIPv6()) + + expectMark(tcdefs.MarkSeenBypassForward) + saveMark := skbMark + + dumpCTMapV6(ctMapV6) + + skbMark = 0 + // try a spoofed tunnel packet returning back, should be dropped and have no effect + runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { + // modify the only known good src IP, we do not care about csums at this point + encapedPkt[26] = 235 + res, err := bpfrun(encapedPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(retvalToStr[res.Retval]).To(Equal(retvalToStr[resTC_ACT_SHOT])) + }, withIPv6()) + + skbMark = saveMark + // Response leaving to original source + runBpfTest(t, "calico_to_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(recvPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(retvalToStr[res.Retval]).To(Equal(retvalToStr[resTC_ACT_UNSPEC])) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + ct, err := conntrack.LoadMapMemV6(ctMapV6) + Expect(err).NotTo(HaveOccurred()) + + ctKey := conntrack.NewKeyV6(uint8(layers.IPProtocolTCP), + ipv6.SrcIP, uint16(tcp.SrcPort), ipv6.DstIP, uint16(tcp.DstPort)) + + Expect(ct).Should(HaveKey(ctKey)) + ctr := ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATForward)) + + ctKey = ctr.ReverseNATKey().(conntrack.KeyV6) + Expect(ct).Should(HaveKey(ctKey)) + ctr = ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATReverse)) + + // Approved for both sides due to forwarding through the tunnel + Expect(ctr.Data().A2B.Approved).To(BeTrue()) + Expect(ctr.Data().B2A.Approved).To(BeTrue()) + }, withIPv6()) + + dumpCTMapV6(ctMapV6) + + skbMark = 0 + // Another pkt arriving at node 1 - uses existing CT entries + runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(pktBytes) + Expect(err).NotTo(HaveOccurred()) + Expect(retvalToStr[res.Retval]).To(Or( + Equal(retvalToStr[resTC_ACT_REDIRECT]), + Equal(retvalToStr[resTC_ACT_UNSPEC]), + )) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + ipv6L := pktR.Layer(layers.LayerTypeIPv6) + Expect(ipv6L).NotTo(BeNil()) + ipv6R := ipv6L.(*layers.IPv6) + Expect(ipv6R.SrcIP.String()).To(Equal(hostIP.String())) + Expect(ipv6R.DstIP.String()).To(Equal(node2ipV6.String())) + + checkVxlanEncap(pktR, false, ipv6, tcp, payload) + }, withIPv6()) + + expectMark(tcdefs.MarkSeenBypassForward) + + _, ipv6, tcp, payload, pktBytes, err = testPacketTCPV6DefaultNP(serviceIP, true) + Expect(err).NotTo(HaveOccurred()) + + err = natMapV6.Update( + nat.NewNATKeyV6(ipv6.DstIP, uint16(tcp.DstPort), uint8(layers.IPProtocolTCP)).AsBytes(), + nat.NewNATValueV6WithFlags(maglevSvcID, 1, 0, 0, nat.NATFlgMaglev).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + + mgMap6 = nat.MaglevMapV6() + err = mgMap6.EnsureExists() + Expect(err).NotTo(HaveOccurred()) + + // Build a maglev LUT and program each item to the BPF map. + mglv = consistenthash.New(int(maglevTestM), fnv.New32(), fnv.New32()) + mglv.AddBackend(chtypes.MockEndpoint{ + Ip: natIP.String(), + Prt: natPort, + }) + lut = mglv.Generate() + for ordinal, ep := range lut { + err = mgMap6.Update( + nat.NewMaglevBackendKeyV6(maglevSvcID, uint32(ordinal)).AsBytes(), + nat.NewNATBackendValueV6(net.ParseIP(ep.IP()), uint16(ep.Port())).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + } + + resetCTMapV6(ctMapV6) // ensure it is clean + resetRTMap(rtMapV6) + encapedPkt = nil + + hostIP = node3ipV6 + skbMark = 0 + + // Arriving at node 3 - non-routable -> denied + runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(pktBytes) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(resTC_ACT_SHOT)) + }, withIPv6()) + + defer resetRTMapV6(rtMapV6) + Expect(err).NotTo(HaveOccurred()) + err = rtMapV6.Update( + routes.NewKeyV6(ip.CIDRFromIPNet(&node2wCIDR).(ip.V6CIDR)).AsBytes(), + routes.NewValueV6WithNextHop(routes.FlagsRemoteWorkload|routes.FlagInIPAMPool, + ip.FromNetIP(node2ipV6).(ip.V6Addr)).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + err = rtMapV6.Update( + routes.NewKeyV6(ip.CIDRFromIPNet(&node2CIDRV6).(ip.V6CIDR)).AsBytes(), + routes.NewValueV6(routes.FlagsRemoteHost).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + err = rtMapV6.Update( + routes.NewKeyV6(ip.CIDRFromIPNet(&node3CIDRV6).(ip.V6CIDR)).AsBytes(), + routes.NewValueV6(routes.FlagsLocalHost).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + + dumpRTMapV6(rtMapV6) + rtNode3 := saveRTMapV6(rtMapV6) + + // Arriving at node 3 + runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(pktBytes) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Or( + Equal(resTC_ACT_REDIRECT), + Equal(resTC_ACT_UNSPEC), + )) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + ipv6L := pktR.Layer(layers.LayerTypeIPv6) + Expect(ipv6L).NotTo(BeNil()) + ipv6R := ipv6L.(*layers.IPv6) + Expect(ipv6R.SrcIP.String()).To(Equal(hostIP.String())) + Expect(ipv6R.DstIP.String()).To(Equal(node2ipV6.String())) + + checkVxlanEncap(pktR, false, ipv6, tcp, payload) + + encapedPkt = res.dataOut + + ct, err := conntrack.LoadMapMemV6(ctMapV6) + Expect(err).NotTo(HaveOccurred()) + + ctKey := conntrack.NewKeyV6(uint8(layers.IPProtocolTCP), + ipv6.SrcIP, uint16(tcp.SrcPort), ipv6.DstIP, uint16(tcp.DstPort)) + + Expect(ct).Should(HaveKey(ctKey)) + ctr := ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATForward)) + + ctKey = ctr.ReverseNATKey().(conntrack.KeyV6) + Expect(ct).Should(HaveKey(ctKey)) + ctr = ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATReverse)) + + // Approved for both sides due to forwarding through the tunnel + Expect(ctr.Data().A2B.Approved).To(BeTrue()) + Expect(ctr.Data().B2A.Approved).To(BeTrue()) + }, withIPv6()) + + dumpCTMapV6(ctMapV6) + ct, err = conntrack.LoadMapMemV6(ctMapV6) + Expect(err).NotTo(HaveOccurred()) + v, ok = ct[conntrack.NewKeyV6(uint8(layers.IPProtocolTCP), ipv6.SrcIP, uint16(tcp.SrcPort), natIP, natPort)] + Expect(ok).To(BeTrue()) + Expect(v.Type()).To(Equal(conntrack.TypeNATReverse)) + Expect(v.Flags()).To(Equal(conntrack4.FlagNATNPFwd | conntrack4.FlagMaglev)) + + expectMark(tcdefs.MarkSeenBypassForward) + + // Leaving node 1 + runBpfTest(t, "calico_to_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(encapedPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(resTC_ACT_UNSPEC)) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + Expect(res.dataOut).To(Equal(encapedPkt)) + }, withIPv6()) + + dumpCTMapV6(ctMapV6) + fromHostCT = saveCTMapV6(ctMapV6) + + encapedPktArrivesAtNode2 = make([]byte, len(encapedPkt)) + copy(encapedPktArrivesAtNode2, encapedPkt) + + resetCTMapV6(ctMapV6) + + recvPkt = nil + hostIP = node2ipV6 + + // change the routing - it is a local workload now! + err = rtMapV6.Update( + routes.NewKeyV6(ip.CIDRFromIPNet(&node2wCIDR).(ip.V6CIDR)).AsBytes(), + routes.NewValueV6(routes.FlagsLocalWorkload|routes.FlagInIPAMPool).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + + // we must know that the encaped packet src ip if from a known host + err = rtMapV6.Update( + routes.NewKeyV6(ip.CIDRFromIPNet(&node3CIDRV6).(ip.V6CIDR)).AsBytes(), + routes.NewValueV6(routes.FlagsRemoteHost).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + err = rtMapV6.Update( + routes.NewKeyV6(ip.CIDRFromIPNet(&node2CIDRV6).(ip.V6CIDR)).AsBytes(), + routes.NewValueV6(routes.FlagsLocalHost).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + + dumpRTMapV6(rtMapV6) + + // now we are at the node with local workload + err = natMapV6.Update( + nat.NewNATKeyV6(ipv6.DstIP, uint16(tcp.DstPort), uint8(layers.IPProtocolTCP)).AsBytes(), + nat.NewNATValueV6WithFlags(maglevSvcID /* id */, 1 /* count */, 1 /* local */, 0, nat.NATFlgMaglev).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + + // Arriving at node 2 + bpfIfaceName = "NP-2" + + resetMap(arpMapV6) + restoreARPMapV6(arpMapV6, arpMapN2) + Expect(arpMapN2).To(HaveLen(1)) + + skbMark = 0 + runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(encapedPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(resTC_ACT_REDIRECT)) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + payloadL := pktR.ApplicationLayer() + Expect(payloadL).NotTo(BeNil()) + vxlanL := gopacket.NewPacket(payloadL.Payload(), layers.LayerTypeVXLAN, gopacket.Default) + Expect(vxlanL).NotTo(BeNil()) + fmt.Printf("vxlanL = %+v\n", vxlanL) + + ipv6L := pktR.Layer(layers.LayerTypeIPv6) + ipv6R := ipv6L.(*layers.IPv6) + Expect(ipv6R.SrcIP.String()).To(Equal(ipv6.SrcIP.String())) + Expect(ipv6R.DstIP.String()).To(Equal(natIP.String())) + + tcpL := pktR.Layer(layers.LayerTypeTCP) + Expect(tcpL).NotTo(BeNil()) + tcpR := tcpL.(*layers.TCP) + Expect(tcpR.SrcPort).To(Equal(layers.TCPPort(tcp.SrcPort))) + Expect(tcpR.DstPort).To(Equal(layers.TCPPort(natPort))) + + ct, err := conntrack.LoadMapMemV6(ctMapV6) + Expect(err).NotTo(HaveOccurred()) + + ctKey := conntrack.NewKeyV6(uint8(layers.IPProtocolTCP), + ipv6.SrcIP, uint16(tcp.SrcPort), ipv6.DstIP, uint16(tcp.DstPort)) + + Expect(ct).Should(HaveKey(ctKey)) + ctr := ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATForward)) + Expect(ctr.NATSPort()).To(Equal(uint16(0))) + + ctKey = ctr.ReverseNATKey().(conntrack.KeyV6) + Expect(ct).Should(HaveKey(ctKey)) + ctr = ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATReverse)) + + // Approved source side + Expect(ctr.Data().A2B.Approved).To(BeTrue()) + // Dest not approved yet + Expect(ctr.Data().B2A.Approved).NotTo(BeTrue()) + + recvPkt = res.dataOut + }, withIPv6()) + + expectMark(tcdefs.MarkSeen) + + dumpCTMapV6(ctMapV6) + ct, err = conntrack.LoadMapMemV6(ctMapV6) + Expect(err).NotTo(HaveOccurred()) + v, ok = ct[conntrack.NewKeyV6(uint8(layers.IPProtocolTCP), ipv6.SrcIP, uint16(tcp.SrcPort), natIP, natPort)] + Expect(ok).To(BeTrue()) + Expect(v.Type()).To(Equal(conntrack.TypeNATReverse)) + Expect(v.Flags()).To(Equal(conntrack4.FlagExtLocal | conntrack4.FlagMaglev)) + + dumpARPMapV6(arpMapV6) + + arpMapN2 = saveARPMapV6(arpMapV6) + Expect(arpMapN2).To(HaveLen(2)) + arpKey = arp.NewKeyV6(node3ipV6, 1) // ifindex is always 1 in UT + Expect(arpMapN2).To(HaveKey(arpKey)) + macDst = encapedPkt[0:6] + macSrc = encapedPkt[6:12] + Expect(arpMapN2[arpKey]).To(Equal(arp.NewValue(macDst, macSrc))) + + // try a spoofed tunnel packet, should be dropped and have no effect + skbMark = 0 + runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { + // modify the only known good src IP, we do not care about csums at this point + encapedPkt[26] = 234 + res, err := bpfrun(encapedPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(resTC_ACT_SHOT)) + }, withIPv6()) + + skbMark = tcdefs.MarkSeen + + // Insert the reverse route for backend for RPF check. + resetRTMap(rtMapV6) + beV4CIDR = ip.CIDRFromNetIP(natIP).(ip.V6CIDR) + bertKey = routes.NewKeyV6(beV4CIDR).AsBytes() + bertVal = routes.NewValueV6WithIfIndex(routes.FlagsLocalWorkload|routes.FlagInIPAMPool, 1).AsBytes() + err = rtMapV6.Update(bertKey, bertVal) + Expect(err).NotTo(HaveOccurred()) + + // Arriving at workload at node 2 + runBpfTest(t, "calico_to_workload_ep", rulesDefaultAllow, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(recvPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(resTC_ACT_UNSPEC)) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + Expect(res.dataOut).To(Equal(recvPkt)) + + ct, err := conntrack.LoadMapMemV6(ctMapV6) + Expect(err).NotTo(HaveOccurred()) + + ctKey := conntrack.NewKeyV6(uint8(layers.IPProtocolTCP), + ipv6.SrcIP, uint16(tcp.SrcPort), ipv6.DstIP, uint16(tcp.DstPort)) + + Expect(ct).Should(HaveKey(ctKey)) + ctr := ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATForward)) + + ctKey = ctr.ReverseNATKey().(conntrack.KeyV6) + Expect(ct).Should(HaveKey(ctKey)) + ctr = ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATReverse), + fmt.Sprintf("Expected reverse conntrack entry but got %v", ctr)) + + // Approved source side + Expect(ctr.Data().A2B.Approved).To(BeTrue()) + // Approved destination side as well + Expect(ctr.Data().B2A.Approved).To(BeTrue()) + }, withIPv6()) + + skbMark = 0 + + // Response leaving workload at node 2 + runBpfTest(t, "calico_from_workload_ep", rulesDefaultAllow, func(bpfrun bpfProgRunFn) { + // SYN,ACK + respPkt := tcpResponseRawV6(recvPkt) + // Change the MAC addresses so that we can observe that the right + // addresses were patched in. + copy(respPkt[:6], []byte{1, 2, 3, 4, 5, 6}) + copy(respPkt[6:12], []byte{6, 5, 4, 3, 2, 1}) + res, err := bpfrun(respPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Or( + Equal(resTC_ACT_REDIRECT), + Equal(resTC_ACT_UNSPEC), + )) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + ethL := pktR.Layer(layers.LayerTypeEthernet) + Expect(ethL).NotTo(BeNil()) + ethR := ethL.(*layers.Ethernet) + Expect(ethR).To(layersMatchFields(&layers.Ethernet{ + SrcMAC: macDst, + DstMAC: macSrc, + EthernetType: layers.EthernetTypeIPv6, + })) + + ipv6L := pktR.Layer(layers.LayerTypeIPv6) + Expect(ipv6L).NotTo(BeNil()) + ipv6R := ipv6L.(*layers.IPv6) + Expect(ipv6R.SrcIP.String()).To(Equal(hostIP.String())) + Expect(ipv6R.DstIP.String()).To(Equal(node3ipV6.String())) + + checkVxlan(pktR) + + encapedPkt = res.dataOut + }, withIPv6()) + + dumpCTMapV6(ctMapV6) + + expectMark(tcdefs.MarkSeen) + + // Response leaving node 2 + runBpfTest(t, "calico_to_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(encapedPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(resTC_ACT_UNSPEC)) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + ipv6L := pktR.Layer(layers.LayerTypeIPv6) + Expect(ipv6L).NotTo(BeNil()) + ipv6R := ipv6L.(*layers.IPv6) + // check that the IP is fixed up + Expect(ipv6R.SrcIP.String()).To(Equal(node2ipV6.String())) + Expect(ipv6R.DstIP.String()).To(Equal(node3ipV6.String())) + + checkVxlan(pktR) + + encapedPkt = res.dataOut + }, withIPv6()) + + dumpCTMapV6(ctMapV6) + resetCTMapV6(ctMapV6) + restoreCTMapV6(ctMapV6, fromHostCT) + dumpCTMapV6(ctMapV6) + + hostIP = node3ipV6 + + // change to routing again to a remote workload + resetRTMap(rtMapV6) + restoreRTMapV6(rtMapV6, rtNode3) + dumpRTMapV6(rtMapV6) + + // Response arriving at node 1 + bpfIfaceName = "NP-1" + skbMark = 0 + + runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(encapedPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(retvalToStr[res.Retval]).To(Equal(retvalToStr[resTC_ACT_REDIRECT])) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + ipv6L := pktR.Layer(layers.LayerTypeIPv6) + Expect(ipv6L).NotTo(BeNil()) + ipv6R := ipv6L.(*layers.IPv6) + Expect(ipv6R.DstIP.String()).To(Equal(ipv6.SrcIP.String())) + Expect(ipv6R.SrcIP.String()).To(Equal(ipv6.DstIP.String())) + + tcpL := pktR.Layer(layers.LayerTypeTCP) + Expect(tcpL).NotTo(BeNil()) + tcpR := tcpL.(*layers.TCP) + Expect(tcpR.SrcPort).To(Equal(tcp.DstPort)) + Expect(tcpR.DstPort).To(Equal(tcp.SrcPort)) + + payloadL := pktR.ApplicationLayer() + Expect(payloadL).NotTo(BeNil()) + Expect(payload).To(Equal(payloadL.Payload())) + + recvPkt = res.dataOut + }, withIPv6()) + + expectMark(tcdefs.MarkSeenBypassForward) + saveMark = skbMark + + dumpCTMapV6(ctMapV6) + + skbMark = 0 + // try a spoofed tunnel packet returning back, should be dropped and have no effect + runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { + // modify the only known good src IP, we do not care about csums at this point + encapedPkt[26] = 235 + res, err := bpfrun(encapedPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(retvalToStr[res.Retval]).To(Equal(retvalToStr[resTC_ACT_SHOT])) + }, withIPv6()) + + skbMark = saveMark + // Response leaving to original source + runBpfTest(t, "calico_to_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(recvPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(retvalToStr[res.Retval]).To(Equal(retvalToStr[resTC_ACT_UNSPEC])) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + ct, err := conntrack.LoadMapMemV6(ctMapV6) + Expect(err).NotTo(HaveOccurred()) + + ctKey := conntrack.NewKeyV6(uint8(layers.IPProtocolTCP), + ipv6.SrcIP, uint16(tcp.SrcPort), ipv6.DstIP, uint16(tcp.DstPort)) + + Expect(ct).Should(HaveKey(ctKey)) + ctr := ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATForward)) + + ctKey = ctr.ReverseNATKey().(conntrack.KeyV6) + Expect(ct).Should(HaveKey(ctKey)) + ctr = ct[ctKey] + Expect(ctr.Type()).To(Equal(conntrack.TypeNATReverse)) + + // Approved for both sides due to forwarding through the tunnel + Expect(ctr.Data().A2B.Approved).To(BeTrue()) + Expect(ctr.Data().B2A.Approved).To(BeTrue()) + }, withIPv6()) + + dumpCTMapV6(ctMapV6) + + skbMark = 0 + // Another pkt arriving at node 1 - uses existing CT entries + runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(pktBytes) + Expect(err).NotTo(HaveOccurred()) + Expect(retvalToStr[res.Retval]).To(Or( + Equal(retvalToStr[resTC_ACT_REDIRECT]), + Equal(retvalToStr[resTC_ACT_UNSPEC]), + )) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + ipv6L := pktR.Layer(layers.LayerTypeIPv6) + Expect(ipv6L).NotTo(BeNil()) + ipv6R := ipv6L.(*layers.IPv6) + Expect(ipv6R.SrcIP.String()).To(Equal(hostIP.String())) + Expect(ipv6R.DstIP.String()).To(Equal(node2ipV6.String())) + + checkVxlanEncap(pktR, false, ipv6, tcp, payload) + }, withIPv6()) + + expectMark(tcdefs.MarkSeenBypassForward) +} + +func TestMaglevNATNodePortNoFWD(t *testing.T) { + RegisterTestingT(t) + + defer resetCTMap(ctMap) + + bpfIfaceName = "MNPlo" + defer func() { bpfIfaceName = "" }() + + _, ipv4, l4, payload, pktBytes, err := testPacketUDPDefaultNP(node1ip) + Expect(err).NotTo(HaveOccurred()) + udp := l4.(*layers.UDP) + natMap := nat.FrontendMap() + err = natMap.EnsureExists() + Expect(err).NotTo(HaveOccurred()) + + mgMap := nat.MaglevMap() + err = mgMap.EnsureExists() + Expect(err).NotTo(HaveOccurred()) + + // local workload + err = natMap.Update( + nat.NewNATKey(ipv4.DstIP, uint16(udp.DstPort), uint8(ipv4.Protocol)).AsBytes(), + nat.NewNATValueWithFlags(maglevSvcID /* id */, 1 /* count */, 1 /* local */, 0, nat.NATFlgMaglev).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + + natIP := net.IPv4(8, 8, 8, 8) + natPort := uint16(666) + + // Build a maglev LUT and program each item to the BPF map. + mglv := consistenthash.New(int(maglevTestM), fnv.New32(), fnv.New32()) + mglv.AddBackend(chtypes.MockEndpoint{ + Ip: natIP.String(), + Prt: natPort, + }) + lut := mglv.Generate() + for ordinal, ep := range lut { + err = mgMap.Update( + nat.NewMaglevBackendKey(maglevSvcID, uint32(ordinal)).AsBytes(), + nat.NewNATBackendValue(net.ParseIP(ep.IP()), uint16(ep.Port())).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + } + + ctMap := conntrack.Map() + err = ctMap.EnsureExists() + Expect(err).NotTo(HaveOccurred()) + resetCTMap(ctMap) // ensure it is clean + + var recvPkt []byte + + hostIP = node1ip + skbMark = 0 + + // Setup routing + rtMap := routes.Map() + err = rtMap.EnsureExists() + Expect(err).NotTo(HaveOccurred()) + defer resetRTMap(rtMap) + // backend it is a local workload + resetRTMap(rtMap) + beV4CIDR := ip.CIDRFromNetIP(natIP).(ip.V4CIDR) + bertKey := routes.NewKey(beV4CIDR).AsBytes() + bertVal := routes.NewValueWithIfIndex(routes.FlagsLocalWorkload|routes.FlagInIPAMPool, 1).AsBytes() + err = rtMap.Update(bertKey, bertVal) + Expect(err).NotTo(HaveOccurred()) + dumpRTMap(rtMap) + + // Arriving at node + runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(pktBytes) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(resTC_ACT_REDIRECT)) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + ipv4L := pktR.Layer(layers.LayerTypeIPv4) + ipv4R := ipv4L.(*layers.IPv4) + Expect(ipv4R.SrcIP.String()).To(Equal(ipv4.SrcIP.String())) + Expect(ipv4R.DstIP.String()).To(Equal(natIP.String())) + + udpL := pktR.Layer(layers.LayerTypeUDP) + Expect(udpL).NotTo(BeNil()) + udpR := udpL.(*layers.UDP) + Expect(udpR.SrcPort).To(Equal(layers.UDPPort(udp.SrcPort))) + Expect(udpR.DstPort).To(Equal(layers.UDPPort(natPort))) + + recvPkt = res.dataOut + }) + + dumpCTMap(ctMap) + + hostIP = net.IPv4(0, 0, 0, 0) // workloads do not have it set + + expectMark(tcdefs.MarkSeen) + + ct, err := conntrack.LoadMapMem(ctMap) + Expect(err).NotTo(HaveOccurred()) + v, ok := ct[conntrack.NewKey(uint8(ipv4.Protocol), ipv4.SrcIP, uint16(udp.SrcPort), natIP.To4(), natPort)] + Expect(ok).To(BeTrue()) + Expect(v.Type()).To(Equal(conntrack.TypeNATReverse)) + Expect(v.Flags()).To(Equal(conntrack4.FlagExtLocal | conntrack4.FlagMaglev)) + + // Arriving at workload + runBpfTest(t, "calico_to_workload_ep", rulesDefaultAllow, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(recvPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(resTC_ACT_UNSPEC)) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + Expect(res.dataOut).To(Equal(recvPkt)) + }) + + skbMark = 0 + var respPkt []byte + + // Response leaving workload + runBpfTest(t, "calico_from_workload_ep", rulesDefaultAllow, func(bpfrun bpfProgRunFn) { + respPkt = udpResponseRaw(recvPkt) + res, err := bpfrun(respPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(resTC_ACT_REDIRECT)) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + Expect(res.dataOut).To(Equal(respPkt)) + }) + + expectMark(tcdefs.MarkSeen) + + // Response leaving to original source + runBpfTest(t, "calico_to_host_ep", nil, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(respPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(resTC_ACT_UNSPEC)) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + ipv4L := pktR.Layer(layers.LayerTypeIPv4) + Expect(ipv4L).NotTo(BeNil()) + ipv4R := ipv4L.(*layers.IPv4) + Expect(ipv4R.DstIP.String()).To(Equal(ipv4.SrcIP.String())) + Expect(ipv4R.SrcIP.String()).To(Equal(ipv4.DstIP.String())) + + udpL := pktR.Layer(layers.LayerTypeUDP) + Expect(udpL).NotTo(BeNil()) + udpR := udpL.(*layers.UDP) + Expect(udpR.SrcPort).To(Equal(udp.DstPort)) + Expect(udpR.DstPort).To(Equal(udp.SrcPort)) + + payloadL := pktR.ApplicationLayer() + Expect(payloadL).NotTo(BeNil()) + Expect(payload).To(Equal(payloadL.Payload())) + + }) + + dumpCTMap(ctMap) +} + +// TestNormalSYNRetryForcePolicy does the same test for forcing policy +// as TestNATSYNRetryGoesToSameBackend but without NAT. +func TestMaglevNormalSYNRetryForcePolicy(t *testing.T) { + RegisterTestingT(t) + + defer func() { bpfIfaceName = "" }() + bpfIfaceName = "MSYN1" + + tcpSyn := &layers.TCP{ + SrcPort: 54321, + DstPort: 7890, + SYN: true, + DataOffset: 5, + } + + _, ipv4, _, _, synPkt, err := testPacketV4(nil, nil, tcpSyn, nil) + Expect(err).NotTo(HaveOccurred()) + + // Insert a reverse route for the source workload. + rtKey := routes.NewKey(srcV4CIDR).AsBytes() + rtVal := routes.NewValueWithIfIndex(routes.FlagsLocalWorkload|routes.FlagInIPAMPool, 1).AsBytes() + defer resetRTMap(rtMap) + err = rtMap.Update(rtKey, rtVal) + Expect(err).NotTo(HaveOccurred()) + + skbMark = 0 + runBpfTest(t, "calico_from_workload_ep", rulesDefaultAllow, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(synPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(resTC_ACT_REDIRECT)) + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + }) + + expectMark(tcdefs.MarkSeen) + + bpfIfaceName = "SYN2" + explicitAllow := &polprog.Rules{ + Tiers: []polprog.Tier{{ + Name: "base tier", + Policies: []polprog.Policy{{ + Name: "expAllow", + Rules: []polprog.Rule{{ + Rule: &proto.Rule{ + Action: "Allow", + DstPorts: []*proto.PortRange{{First: 7890, Last: 7890}}, + DstNet: []string{ipv4.DstIP.String()}, + }}}, + }}, + }}, + } + + skbMark = 0 + // Make sure that policy still allows the retry (is enforce correctly) + runBpfTest(t, "calico_from_workload_ep", explicitAllow, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(synPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(resTC_ACT_REDIRECT)) + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + }) + expectMark(tcdefs.MarkSeen) + + bpfIfaceName = "SYN3" + changedToDeny := &polprog.Rules{ + Tiers: []polprog.Tier{{ + Name: "base tier", + Policies: []polprog.Policy{{ + Name: "allow->deny", + Rules: []polprog.Rule{{ + Rule: &proto.Rule{ + Action: "Allow", + NotDstPorts: []*proto.PortRange{{First: 7890, Last: 7890}}, + NotDstNet: []string{ipv4.DstIP.String()}, + }}}, + }}, + }}, + } + + skbMark = 0 + // Make sure that when the policy changes, it is applied correctly to the next SYN + runBpfTest(t, "calico_from_workload_ep", changedToDeny, func(bpfrun bpfProgRunFn) { + res, err := bpfrun(synPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(resTC_ACT_SHOT)) + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + }) +} + +func withLogLevelWarnDo(f func()) { + // Disable debug while filling up maps. + loglevel := logrus.GetLevel() + logrus.SetLevel(logrus.WarnLevel) + defer logrus.SetLevel(loglevel) + f() +} diff --git a/felix/bpf/ut/main_test.go b/felix/bpf/ut/main_test.go index b59dc737155..c50a8f8184a 100644 --- a/felix/bpf/ut/main_test.go +++ b/felix/bpf/ut/main_test.go @@ -17,14 +17,22 @@ package ut_test import ( + "fmt" "os" + "os/exec" "testing" ) func TestMain(m *testing.M) { initMapsOnce() cleanUpMaps() - cmd := startBPFLogging() + cmd := exec.Command("sysctl", "-w", "net.ipv6.conf.all.forwarding=1") + err := cmd.Run() + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to enable IPv6 forwarding: %v\n", err) + os.Exit(1) + } + cmd = startBPFLogging() rc := m.Run() cleanUpMaps() stopBPFLogging(cmd) diff --git a/felix/bpf/ut/malformed_packets_test.go b/felix/bpf/ut/malformed_packets_test.go index 64c64112a1f..c6ef54945c1 100644 --- a/felix/bpf/ut/malformed_packets_test.go +++ b/felix/bpf/ut/malformed_packets_test.go @@ -19,8 +19,8 @@ import ( "net" "testing" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" ) diff --git a/felix/bpf/ut/map_upgrade_test.go b/felix/bpf/ut/map_upgrade_test.go index 71d971b93c5..4b380979370 100644 --- a/felix/bpf/ut/map_upgrade_test.go +++ b/felix/bpf/ut/map_upgrade_test.go @@ -196,7 +196,7 @@ func TestMapUpgradeV3ToV5WithLowerSize(t *testing.T) { err := mockMapv3.EnsureExists() Expect(err).NotTo(HaveOccurred()) - for i := 0; i < 10; i++ { + for i := range 10 { k := v3.NewKey(0x1234 + uint32(i)) v := v3.NewValue(0x4568 + uint32(i)) @@ -251,7 +251,7 @@ func TestMapUpgradeWithDeltaEntries(t *testing.T) { Expect(val).To(Equal(val5.AsBytes())) // update v2 map with new k,v pairs - for i := 0; i < 10; i++ { + for i := range 10 { k = v2.NewKey(0x1234 + uint32(i)) v = v2.NewValue(0x4568 + uint32(i)) err = mockMapv2.Update(k.AsBytes(), v.AsBytes()) @@ -272,7 +272,7 @@ func TestMapUpgradeWithDeltaEntries(t *testing.T) { Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal(val5.AsBytes())) - for i := 0; i < 10; i++ { + for i := range 10 { k5 = v5.NewKey(0x1234 + uint32(i)) val5 = v5.NewValue(0x4568 + uint32(i)) val, err = mockMapv5.Get(k5.AsBytes()) @@ -294,7 +294,7 @@ func TestMapResizeWhileUpgradeInProgress(t *testing.T) { err := mockMapv2.EnsureExists() Expect(err).NotTo(HaveOccurred()) - for i := 0; i < 10; i++ { + for i := range 10 { k := v2.NewKey(0x1234 + uint32(i)) v := v2.NewValue(0x4568 + uint32(i)) err = mockMapv2.Update(k.AsBytes(), v.AsBytes()) @@ -306,7 +306,7 @@ func TestMapResizeWhileUpgradeInProgress(t *testing.T) { err = mockMapv5.EnsureExists() Expect(err).NotTo(HaveOccurred()) - for i := 0; i < 10; i++ { + for i := range 10 { k := v5.NewKey(0x1234 + uint32(i)) v := v5.NewValue(0x4568 + uint32(i)) val, err := mockMapv5.Get(k.AsBytes()) @@ -314,7 +314,7 @@ func TestMapResizeWhileUpgradeInProgress(t *testing.T) { Expect(val).To(Equal(v.AsBytes())) } - for i := 0; i < 5; i++ { + for i := range 5 { k := v5.NewKey(0x1234 + uint32(i)) err := mockMapv5.Delete(k.AsBytes()) Expect(err).NotTo(HaveOccurred()) @@ -324,7 +324,7 @@ func TestMapResizeWhileUpgradeInProgress(t *testing.T) { err = mockMapv5_new.EnsureExists() Expect(err).NotTo(HaveOccurred()) - for i := 0; i < 10; i++ { + for i := range 10 { k := v5.NewKey(0x1234 + uint32(i)) v := v5.NewValue(0x4568 + uint32(i)) val, err := mockMapv5_new.Get(k.AsBytes()) @@ -346,7 +346,7 @@ func TestMapUpgradeWhileResizeInProgress(t *testing.T) { err := mockMapv2_old.EnsureExists() Expect(err).NotTo(HaveOccurred()) - for i := 0; i < 10; i++ { + for i := range 10 { k := v2.NewKey(0x1234 + uint32(i)) v := v2.NewValue(0x4568 + uint32(i)) err = mockMapv2_old.Update(k.AsBytes(), v.AsBytes()) @@ -391,7 +391,7 @@ func TestMapUpgradeWhileResizeInProgress(t *testing.T) { err = mockMapv5.EnsureExists() Expect(err).NotTo(HaveOccurred()) - for i := 0; i < 10; i++ { + for i := range 10 { k := v5.NewKey(0x1234 + uint32(i)) v := v5.NewValue(0x4568 + uint32(i)) val, err := mockMapv5.Get(k.AsBytes()) diff --git a/felix/bpf/ut/maps_test.go b/felix/bpf/ut/maps_test.go index 99a358283b4..5a31811f986 100644 --- a/felix/bpf/ut/maps_test.go +++ b/felix/bpf/ut/maps_test.go @@ -28,6 +28,7 @@ import ( "github.com/projectcalico/calico/felix/bpf/bpfmap" "github.com/projectcalico/calico/felix/bpf/conntrack" + "github.com/projectcalico/calico/felix/bpf/failsafes" bpfmaps "github.com/projectcalico/calico/felix/bpf/maps" ) @@ -96,7 +97,7 @@ func TestMapDownSize(t *testing.T) { RegisterTestingT(t) // Add 10 entries to the old map numEntries := 10 - for i := 0; i < numEntries; i++ { + for i := range numEntries { err := insertNumberedKey(i) Expect(err).NotTo(HaveOccurred()) } @@ -149,7 +150,7 @@ func TestCTDeltaMigration(t *testing.T) { // Add 10 more entries to the old map numEntries := 10 - for i := 0; i < numEntries; i++ { + for i := range numEntries { err := insertNumberedKey(i) Expect(err).NotTo(HaveOccurred()) } @@ -157,7 +158,7 @@ func TestCTDeltaMigration(t *testing.T) { matchKeyVals := func(ctSaved conntrack.MapMem) { Expect(ctSaved).Should(HaveKeyWithValue(k, newVal)) Expect(ctSaved).ShouldNot(HaveKeyWithValue(k, v)) - for i := 0; i < numEntries; i++ { + for i := range numEntries { key := conntrack.NewKey(1, net.ParseIP("10.0.0.1"), uint16(i), net.ParseIP("10.0.0.2"), uint16(i>>16)) val := conntrack.Value{} for j := range val { @@ -264,7 +265,7 @@ func TestDeleteDuringIter(t *testing.T) { func testDelDuringIterN(numEntries int) { cleanUpMaps() - for i := 0; i < numEntries; i++ { + for i := range numEntries { err := insertNumberedKey(i) Expect(err).NotTo(HaveOccurred()) } @@ -394,7 +395,7 @@ func benchMapIteration(b *testing.B, n int) { func setUpConntrackMapEntries(b *testing.B, n int) { b.StopTimer() - for i := 0; i < n; i++ { + for i := range n { var k conntrack.Key var v conntrack.Value binary.LittleEndian.PutUint32(k[:], uint32(i)) @@ -445,7 +446,7 @@ func benchMapIteratorMulti(b *testing.B, n int) { } func doSingleMapIteratorMultiTest(n int) { - iter, err := bpfmaps.NewIterator(ctMap.MapFD(), conntrack.KeySize, conntrack.ValueSize, conntrack.MaxEntries) + iter, err := bpfmaps.NewIterator(ctMap.MapFD(), conntrack.KeySize, conntrack.ValueSize, conntrack.MaxEntries, true) if err != nil { panic(err) } @@ -469,3 +470,37 @@ func doSingleMapIteratorMultiTest(n int) { runtime.KeepAlive(k) runtime.KeepAlive(v) } + +// Test slow iteration of failsafe map. +func TestFailsafeMapIteration(t *testing.T) { + RegisterTestingT(t) + defer cleanUpMaps() + port := []uint16{8080, 8081, 8082, 8083, 8084} + keys := make([]failsafes.Key, 0, len(port)) + for _, p := range port { + k := failsafes.MakeKey(17, p, false, "0.0.0.0", 32) + err := fsafeMap.Update(k.ToSlice(), []byte{1, 2, 3, 4}) + Expect(err).NotTo(HaveOccurred()) + keys = append(keys, k.(failsafes.Key)) + } + + iter, err := bpfmaps.NewIterator(fsafeMap.MapFD(), failsafes.KeySize, failsafes.ValueSize, failsafes.MapParams.MaxEntries, false) + Expect(err).NotTo(HaveOccurred()) + var k []byte + numIterations := 0 + for { + k, _, err = iter.Next() + if err != nil { + if err == bpfmaps.ErrIterationFinished { + break + } + Expect(err).NotTo(HaveOccurred()) + } + numIterations++ + key := failsafes.KeyFromSlice(k) + Expect(keys).To(ContainElement(key.(failsafes.Key)), "Unexpected key found during iteration") + } + + Expect(numIterations).To(Equal(len(port))) + _ = iter.Close() +} diff --git a/felix/bpf/ut/nat_encap_test.go b/felix/bpf/ut/nat_encap_test.go index 63c3f6e21f1..0a616f10717 100644 --- a/felix/bpf/ut/nat_encap_test.go +++ b/felix/bpf/ut/nat_encap_test.go @@ -18,8 +18,8 @@ import ( "fmt" "testing" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" ) diff --git a/felix/bpf/ut/nat_test.go b/felix/bpf/ut/nat_test.go index 4f6ba8ca707..43f1d0dbc1b 100644 --- a/felix/bpf/ut/nat_test.go +++ b/felix/bpf/ut/nat_test.go @@ -19,13 +19,13 @@ import ( "net" "testing" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/bpf/arp" "github.com/projectcalico/calico/felix/bpf/conntrack" - conntrack3 "github.com/projectcalico/calico/felix/bpf/conntrack/v3" + v4 "github.com/projectcalico/calico/felix/bpf/conntrack/v4" "github.com/projectcalico/calico/felix/bpf/counters" "github.com/projectcalico/calico/felix/bpf/nat" "github.com/projectcalico/calico/felix/bpf/polprog" @@ -196,7 +196,7 @@ func TestNATPodPodXNode(t *testing.T) { Expect(ok).To(BeTrue()) // No NATing, service already resolved Expect(v.Type()).To(Equal(conntrack.TypeNormal)) - Expect(v.Flags()).To(Equal(conntrack3.FlagClusterExternal)) + Expect(v.Flags()).To(Equal(v4.FlagSetDSCP)) // Arriving at workload at node 2 expectMark(tcdefs.MarkSeen) @@ -431,7 +431,7 @@ func TestNATNodePort(t *testing.T) { v, ok := ct[conntrack.NewKey(uint8(ipv4.Protocol), ipv4.SrcIP, uint16(udp.SrcPort), natIP.To4(), natPort)] Expect(ok).To(BeTrue()) Expect(v.Type()).To(Equal(conntrack.TypeNATReverse)) - Expect(v.Flags()).To(Equal(conntrack3.FlagNATNPFwd | conntrack3.FlagClusterExternal)) + Expect(v.Flags()).To(Equal(v4.FlagNATNPFwd | v4.FlagSetDSCP)) expectMark(tcdefs.MarkSeenBypassForward) // Leaving node 1 @@ -549,7 +549,7 @@ func TestNATNodePort(t *testing.T) { v, ok = ct[conntrack.NewKey(uint8(ipv4.Protocol), ipv4.SrcIP, uint16(udp.SrcPort), natIP.To4(), natPort)] Expect(ok).To(BeTrue()) Expect(v.Type()).To(Equal(conntrack.TypeNATReverse)) - Expect(v.Flags()).To(Equal(conntrack3.FlagExtLocal | conntrack3.FlagClusterExternal)) + Expect(v.Flags()).To(Equal(v4.FlagExtLocal | v4.FlagSetDSCP)) dumpARPMap(arpMap) @@ -1221,7 +1221,7 @@ func TestNATNodePortNoFWD(t *testing.T) { v, ok := ct[conntrack.NewKey(uint8(ipv4.Protocol), ipv4.SrcIP, uint16(udp.SrcPort), natIP.To4(), natPort)] Expect(ok).To(BeTrue()) Expect(v.Type()).To(Equal(conntrack.TypeNATReverse)) - Expect(v.Flags()).To(Equal(conntrack3.FlagExtLocal | conntrack3.FlagClusterExternal)) + Expect(v.Flags()).To(Equal(v4.FlagExtLocal | v4.FlagSetDSCP)) // Arriving at workload runBpfTest(t, "calico_to_workload_ep", rulesDefaultAllow, func(bpfrun bpfProgRunFn) { @@ -1432,47 +1432,124 @@ func TestNATNodePortMultiNIC(t *testing.T) { skbMark = 0 // Response arriving at node 1 through 10.10.0.x - runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { - // Initially, blocked by the VXLAN source policing. - res, err := bpfrun(respPkt) - Expect(err).NotTo(HaveOccurred()) - Expect(res.Retval).To(Equal(resTC_ACT_SHOT)) + runBpfTest(t, "calico_from_host_ep", + &polprog.Rules{ + ForHostInterface: true, + HostNormalTiers: []polprog.Tier{{ + Name: "base tier", + Policies: []polprog.Policy{{ + Name: "deny vxlan", + Rules: []polprog.Rule{{Rule: &proto.Rule{ + Action: "Deny", + Protocol: &proto.Protocol{NumberOrName: &proto.Protocol_Name{Name: "udp"}}, + DstPorts: []*proto.PortRange{{First: 4789, Last: 4789}}, + }}}, + }}, + }}, + }, + func(bpfrun bpfProgRunFn) { + // Test for allowing/denying VXLAN packets - blocked by the VXLAN source policing, from unknown host + res, err := bpfrun(respPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(resTC_ACT_SHOT)) - // Add the route for the remote node. Should now be allowed... - err = rtMap.Update( - routes.NewKey(ip.FromNetIP(node2ip).AsCIDR().(ip.V4CIDR)).AsBytes(), - routes.NewValueWithNextHop(routes.FlagsRemoteHost, ip.FromNetIP(node2ip).(ip.V4Addr)).AsBytes(), - ) - Expect(err).NotTo(HaveOccurred()) - res, err = bpfrun(respPkt) - Expect(err).NotTo(HaveOccurred()) - Expect(res.Retval).To(Equal(resTC_ACT_REDIRECT)) + // Add the route for the remote node. Should now be allowed... + err = rtMap.Update( + routes.NewKey(ip.FromNetIP(node2ip).AsCIDR().(ip.V4CIDR)).AsBytes(), + routes.NewValueWithNextHop(routes.FlagsRemoteHost, ip.FromNetIP(node2ip).(ip.V4Addr)).AsBytes(), + ) + Expect(err).NotTo(HaveOccurred()) + res, err = bpfrun(respPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(resTC_ACT_REDIRECT)) - pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) - fmt.Printf("pktR = %+v\n", pktR) + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) - ipv4L := pktR.Layer(layers.LayerTypeIPv4) - Expect(ipv4L).NotTo(BeNil()) - ipv4R := ipv4L.(*layers.IPv4) - Expect(ipv4R.DstIP.String()).To(Equal(ipv4.SrcIP.String())) - Expect(ipv4R.SrcIP.String()).To(Equal(ipv4.DstIP.String())) + ipv4L := pktR.Layer(layers.LayerTypeIPv4) + Expect(ipv4L).NotTo(BeNil()) + ipv4R := ipv4L.(*layers.IPv4) + Expect(ipv4R.DstIP.String()).To(Equal(ipv4.SrcIP.String())) + Expect(ipv4R.SrcIP.String()).To(Equal(ipv4.DstIP.String())) - udpL := pktR.Layer(layers.LayerTypeUDP) - Expect(udpL).NotTo(BeNil()) - udpR := udpL.(*layers.UDP) - Expect(udpR.SrcPort).To(Equal(udp.DstPort)) - Expect(udpR.DstPort).To(Equal(udp.SrcPort)) + udpL := pktR.Layer(layers.LayerTypeUDP) + Expect(udpL).NotTo(BeNil()) + udpR := udpL.(*layers.UDP) + Expect(udpR.SrcPort).To(Equal(udp.DstPort)) + Expect(udpR.DstPort).To(Equal(udp.SrcPort)) - payloadL := pktR.ApplicationLayer() - Expect(payloadL).NotTo(BeNil()) - Expect(payload).To(Equal(payloadL.Payload())) + payloadL := pktR.ApplicationLayer() + Expect(payloadL).NotTo(BeNil()) + Expect(payload).To(Equal(payloadL.Payload())) - recvPkt = res.dataOut - }) + recvPkt = res.dataOut + + expectMark(tcdefs.MarkSeenBypassForward) + + // Test for allowing/denying VXLAN packets - blocked by the VXLAN VNI policing, unknown VNI + vxlanPkt := func() []byte { + // 1. Create the Ethernet Layer (Outer) + eth := &layers.Ethernet{ + SrcMAC: net.HardwareAddr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}, + DstMAC: net.HardwareAddr{0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB}, + EthernetType: layers.EthernetTypeIPv4, + } + + // 2. Create the IP Layer (Outer) + ip := &layers.IPv4{ + Version: 4, + TTL: 64, + Protocol: layers.IPProtocolUDP, + SrcIP: node2ip, + DstIP: node1ip, + } + + // 3. Create the UDP Layer (Outer) + // IANA assigned port for VXLAN is 4789 + udp := &layers.UDP{ + SrcPort: layers.UDPPort(testVxlanPort), + DstPort: layers.UDPPort(testVxlanPort), + } + _ = udp.SetNetworkLayerForChecksum(ip) + + // 4. Create the VXLAN Layer + vxlan := &layers.VXLAN{ + VNI: 666, + ValidIDFlag: true, // Required for the VNI to be considered valid + } + + // 5. Create a dummy Payload (Inner Ethernet or Raw Data) + payload := gopacket.Payload([]byte("Hello, VXLAN world!")) + + // 6. Serialize the packet + buf := gopacket.NewSerializeBuffer() + opts := gopacket.SerializeOptions{ + ComputeChecksums: true, + FixLengths: true, + } + + err := gopacket.SerializeLayers(buf, opts, + eth, + ip, + udp, + vxlan, + payload, + ) + Expect(err).NotTo(HaveOccurred()) + + // Output the resulting bytes + return buf.Bytes() + }() + + // Packet get rejected because of unknown VNI + res, err = bpfrun(vxlanPkt) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(resTC_ACT_SHOT)) + }) dumpCTMap(ctMap) - expectMark(tcdefs.MarkSeenBypassForward) + skbMark = tcdefs.MarkSeenBypassForward // Response leaving to original source runBpfTest(t, "calico_to_host_ep", nil, func(bpfrun bpfProgRunFn) { @@ -1798,7 +1875,7 @@ func TestNATSYNRetryGoesToSameBackend(t *testing.T) { skbMark = 0 runBpfTest(t, "calico_from_workload_ep", rulesDefaultAllow, func(bpfrun bpfProgRunFn) { // Part 1: if we resend the same SYN, then it should get conntracked to the same backend. - for attempt := 0; attempt < 10; attempt++ { + for attempt := range 10 { res, err := bpfrun(synPkt) Expect(err).NotTo(HaveOccurred()) Expect(res.Retval).To(Equal(resTC_ACT_REDIRECT)) @@ -1814,7 +1891,7 @@ func TestNATSYNRetryGoesToSameBackend(t *testing.T) { // Part 2: If we vary the source port, we should hit both backends eventually. seenOtherIP := false - for attempt := 0; attempt < 100; attempt++ { + for range 100 { tcpSyn.SrcPort++ _, _, _, _, synPkt, err := testPacketV4(nil, nil, tcpSyn, nil) Expect(err).NotTo(HaveOccurred()) @@ -2125,7 +2202,7 @@ func TestNATNodePortIngressDSR(t *testing.T) { v, ok := ct[conntrack.NewKey(uint8(ipv4.Protocol), ipv4.SrcIP, uint16(udp.SrcPort), natIP.To4(), natPort)] Expect(ok).To(BeTrue()) Expect(v.Type()).To(Equal(conntrack.TypeNATReverse)) - Expect(v.Flags()).To(Equal(conntrack3.FlagNATFwdDsr | conntrack3.FlagNATNPFwd | conntrack3.FlagClusterExternal)) + Expect(v.Flags()).To(Equal(v4.FlagNATFwdDsr | v4.FlagNATNPFwd | v4.FlagSetDSCP)) } func TestNATNodePortDSROptout(t *testing.T) { @@ -2226,7 +2303,7 @@ func TestNATNodePortDSROptout(t *testing.T) { v, ok := ct[conntrack.NewKey(uint8(ipv4.Protocol), ipv4.SrcIP, uint16(udp.SrcPort), natIP.To4(), natPort)] Expect(ok).To(BeTrue()) Expect(v.Type()).To(Equal(conntrack.TypeNATReverse)) - Expect(v.Flags()).To(Equal(conntrack3.FlagNATFwdDsr | conntrack3.FlagNATNPFwd | conntrack3.FlagClusterExternal)) + Expect(v.Flags()).To(Equal(v4.FlagNATFwdDsr | v4.FlagNATNPFwd | v4.FlagSetDSCP)) // N.B. we skip the forward part from node, we just needed to have the right packet. @@ -2328,7 +2405,7 @@ func TestNATNodePortDSROptout(t *testing.T) { v, ok = ct[conntrack.NewKey(uint8(ipv4.Protocol), ipv4.SrcIP, uint16(udp.SrcPort), natIP.To4(), natPort)] Expect(ok).To(BeTrue()) Expect(v.Type()).To(Equal(conntrack.TypeNATReverse)) - Expect(v.Flags()).To(Equal(conntrack3.FlagExtLocal | conntrack3.FlagNoDSR | conntrack3.FlagClusterExternal)) + Expect(v.Flags()).To(Equal(v4.FlagExtLocal | v4.FlagNoDSR | v4.FlagSetDSCP)) skbMark = tcdefs.MarkSeen @@ -2799,7 +2876,7 @@ func TestNATHostRemoteNPLocalPod(t *testing.T) { v, ok := ct[conntrack.NewKey(uint8(ipv4.Protocol), ipv4.SrcIP, uint16(udp.SrcPort), natIP.To4(), natPort)] Expect(ok).To(BeTrue()) Expect(v.Type()).To(Equal(conntrack.TypeNATReverse)) - Expect(v.Flags()).To(Equal(conntrack3.FlagNPLoop)) + Expect(v.Flags()).To(Equal(v4.FlagNPLoop)) // Arriving at workload runBpfTest(t, "calico_to_workload_ep", rulesDefaultAllow, func(bpfrun bpfProgRunFn) { @@ -2846,6 +2923,9 @@ func TestNATHostRemoteNPLocalPod(t *testing.T) { func TestNATPodPodXNodeV6(t *testing.T) { RegisterTestingT(t) + cleanUpMaps() + defer cleanUpMaps() + bpfIfaceName = "NAT1" defer func() { bpfIfaceName = "" }() @@ -2922,7 +3002,7 @@ func TestNATPodPodXNodeV6(t *testing.T) { runBpfTest(t, "calico_from_workload_ep", rulesDefaultAllow, func(bpfrun bpfProgRunFn) { res, err := bpfrun(pktBytes) Expect(err).NotTo(HaveOccurred()) - Expect(res.Retval).To(Equal(resTC_ACT_UNSPEC)) + Expect(res.Retval).To(Equal(resTC_ACT_REDIRECT)) pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) fmt.Printf("pktR = %+v\n", pktR) @@ -2980,7 +3060,7 @@ func TestNATPodPodXNodeV6(t *testing.T) { runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { res, err := bpfrun(natedPkt) Expect(err).NotTo(HaveOccurred()) - Expect(res.Retval).To(Equal(resTC_ACT_UNSPEC)) + Expect(res.Retval).To(Equal(resTC_ACT_REDIRECT)) pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) fmt.Printf("pktR = %+v\n", pktR) @@ -2994,7 +3074,7 @@ func TestNATPodPodXNodeV6(t *testing.T) { Expect(ok).To(BeTrue()) // No NATing, service already resolved Expect(v.Type()).To(Equal(conntrack.TypeNormal)) - Expect(v.Flags()).To(Equal(conntrack3.FlagClusterExternal)) + Expect(v.Flags()).To(Equal(v4.FlagSetDSCP)) // Arriving at workload at node 2 expectMark(tcdefs.MarkSeen) @@ -3219,7 +3299,7 @@ func TestNATNodePortV6(t *testing.T) { v, ok := ct[conntrack.NewKeyV6(uint8(17 /* UDP */), ipv6.SrcIP, uint16(udp.SrcPort), natIP, natPort)] Expect(ok).To(BeTrue()) Expect(v.Type()).To(Equal(conntrack.TypeNATReverse)) - Expect(v.Flags()).To(Equal(conntrack3.FlagNATNPFwd | conntrack3.FlagClusterExternal)) + Expect(v.Flags()).To(Equal(v4.FlagNATNPFwd | v4.FlagSetDSCP)) expectMark(tcdefs.MarkSeenBypassForward) // Leaving node 1 @@ -3284,7 +3364,7 @@ func TestNATNodePortV6(t *testing.T) { runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { res, err := bpfrun(encapedPkt) Expect(err).NotTo(HaveOccurred()) - Expect(res.Retval).To(Equal(resTC_ACT_UNSPEC)) + Expect(res.Retval).To(Equal(resTC_ACT_REDIRECT)) pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) fmt.Printf("pktR = %+v\n", pktR) @@ -3337,7 +3417,7 @@ func TestNATNodePortV6(t *testing.T) { v, ok = ct[conntrack.NewKeyV6(uint8(17 /* UDP */), ipv6.SrcIP, uint16(udp.SrcPort), natIP, natPort)] Expect(ok).To(BeTrue()) Expect(v.Type()).To(Equal(conntrack.TypeNATReverse)) - Expect(v.Flags()).To(Equal(conntrack3.FlagExtLocal | conntrack3.FlagClusterExternal)) + Expect(v.Flags()).To(Equal(v4.FlagExtLocal | v4.FlagSetDSCP)) dumpARPMapV6(arpMapV6) @@ -3487,7 +3567,7 @@ func TestNATNodePortV6(t *testing.T) { runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { res, err := bpfrun(encapedPkt) Expect(err).NotTo(HaveOccurred()) - Expect(res.Retval).To(Equal(resTC_ACT_UNSPEC)) + Expect(res.Retval).To(Equal(resTC_ACT_REDIRECT)) pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) fmt.Printf("pktR = %+v\n", pktR) @@ -3632,7 +3712,7 @@ func TestNATNodePortV6(t *testing.T) { runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { res, err := bpfrun(encapedPktArrivesAtNode2) Expect(err).NotTo(HaveOccurred()) - Expect(res.Retval).To(Equal(resTC_ACT_UNSPEC)) + Expect(res.Retval).To(Equal(resTC_ACT_REDIRECT)) pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) fmt.Printf("pktR = %+v\n", pktR) diff --git a/felix/bpf/ut/perf_events_test.go b/felix/bpf/ut/perf_events_test.go index 227c858b948..88c86af56d6 100644 --- a/felix/bpf/ut/perf_events_test.go +++ b/felix/bpf/ut/perf_events_test.go @@ -20,8 +20,8 @@ import ( "runtime" "testing" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" diff --git a/felix/bpf/ut/pkt_byte_counts_test.go b/felix/bpf/ut/pkt_byte_counts_test.go index 257744fb4ae..43c3ee99c62 100644 --- a/felix/bpf/ut/pkt_byte_counts_test.go +++ b/felix/bpf/ut/pkt_byte_counts_test.go @@ -17,7 +17,7 @@ package ut_test import ( "testing" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/bpf/conntrack" diff --git a/felix/bpf/ut/pol_prog_test.go b/felix/bpf/ut/pol_prog_test.go index 8ad746de36f..3318cc07e6a 100644 --- a/felix/bpf/ut/pol_prog_test.go +++ b/felix/bpf/ut/pol_prog_test.go @@ -26,11 +26,13 @@ import ( "testing" . "github.com/onsi/gomega" + "github.com/pkg/errors" log "github.com/sirupsen/logrus" "golang.org/x/sys/unix" "github.com/projectcalico/calico/felix/bpf" "github.com/projectcalico/calico/felix/bpf/asm" + "github.com/projectcalico/calico/felix/bpf/hook" "github.com/projectcalico/calico/felix/bpf/ipsets" "github.com/projectcalico/calico/felix/bpf/jump" "github.com/projectcalico/calico/felix/bpf/maps" @@ -125,7 +127,7 @@ func TestPolicyLoadKitchenSinkPolicy(t *testing.T) { cleanIPSetMap() - pg := polprog.NewBuilder(alloc, ipsMap.MapFD(), stateMap.MapFD(), policyJumpMap.MapFD(), 0, + pg := polprog.NewBuilder(alloc, ipsMap.MapFD(), stateMap.MapFD(), policyJumpMap[hook.Ingress].MapFD(), 0, polprog.WithAllowDenyJumps(tcdefs.ProgIndexAllowed, tcdefs.ProgIndexDrop)) insns, err := pg.Instructions(polprog.Rules{ Tiers: []polprog.Tier{{ @@ -171,7 +173,7 @@ func TestPolicyLoadGarbageProgram(t *testing.T) { RegisterTestingT(t) var insns asm.Insns - for i := 0; i < 256; i++ { + for i := range 256 { i := uint8(i) insns = append(insns, asm.Insn{Instruction: [8]uint8{i, i, i, i, i, i, i, i}}) } @@ -2328,18 +2330,6 @@ var testExpanders = []testExpander{ return out }, }, - { - Name: "WithLargePrefixedTierAndJITHarden", - Expand: func(p polProgramTest) polProgramTest { - out := p - initExtraTierOnce.Do(initExtraTier) - out.Policy.Tiers = append([]polprog.Tier{extraTier}, out.Policy.Tiers...) - out.SetupCB = func() error { return execSysctl("net.core.bpf_jit_harden", "2") } - out.TearDownCB = func() error { return execSysctl("net.core.bpf_jit_harden", "0") } - out.Options = []polprog.Option{polprog.WithTrampolineStride(14000)} - return out - }, - }, } var extraTier polprog.Tier @@ -2349,11 +2339,11 @@ func initExtraTier() { const numExtraPols = 250 const numRulesPerPol = 50 pols := make([]polprog.Policy, 0, numExtraPols) - for i := 0; i < numExtraPols; i++ { + for i := range numExtraPols { pol := polprog.Policy{ Name: fmt.Sprintf("pol-%d", i), } - for j := 0; j < numRulesPerPol; j++ { + for j := range numRulesPerPol { pol.Rules = append(pol.Rules, noOpRule(i*numRulesPerPol+j)) } pols = append(pols, pol) @@ -2381,7 +2371,7 @@ func noOpRule(n int) polprog.Rule { } if n%1000 == 0 { // Add lots of ports to a handful of rules. - for p := 0; p < 2000; p++ { + for p := range 2000 { ports = append(ports, &proto.PortRange{ First: int32(p*2 + 10000), Last: int32(p*2 + 10001), @@ -2876,6 +2866,7 @@ type testCase interface { } var nextPolProgIdx atomic.Int64 +var jumpStride = asm.TrampolineStrideDefault func runTest(t *testing.T, tp testPolicy, polprogOpts ...polprog.Option) { RegisterTestingT(t) @@ -2902,13 +2893,18 @@ func runTest(t *testing.T, tp testPolicy, polprogOpts ...polprog.Option) { setUpIPSets(tp.IPSets(), realAlloc, ipsMap) } - if policyJumpMap != nil { - _ = policyJumpMap.Close() + for _, polJumpMap := range policyJumpMap { + if polJumpMap != nil { + _ = polJumpMap.Close() + } + } + + policyJumpMap = jump.Maps() + for _, hk := range []hook.Hook{hook.Egress, hook.Ingress} { + _ = unix.Unlink(policyJumpMap[hk].Path()) + err = policyJumpMap[hk].EnsureExists() + Expect(err).NotTo(HaveOccurred()) } - policyJumpMap = jump.Map() - _ = unix.Unlink(policyJumpMap.Path()) - err = policyJumpMap.EnsureExists() - Expect(err).NotTo(HaveOccurred()) allowIdx := tcdefs.ProgIndexAllowed denyIdx := tcdefs.ProgIndexDrop @@ -2933,6 +2929,10 @@ func runTest(t *testing.T, tp testPolicy, polprogOpts ...polprog.Option) { polProgIdx := int(nextPolProgIdx.Add(1)) stride := jump.TCMaxEntryPoints polprogOpts = append(polprogOpts, polprog.WithPolicyMapIndexAndStride(int(polProgIdx), stride)) + +retry: + polprogOpts = append(polprogOpts, polprog.WithTrampolineStride(jumpStride)) + if tp.ForIPv6() { polprogOpts = append(polprogOpts, polprog.WithIPv6()) ipsfd = ipsMapV6.MapFD() @@ -2942,7 +2942,7 @@ func runTest(t *testing.T, tp testPolicy, polprogOpts ...polprog.Option) { ipsfd, testStateMap.MapFD(), staticProgsMap.MapFD(), - policyJumpMap.MapFD(), + policyJumpMap[hook.Ingress].MapFD(), polprogOpts..., ) insns, err := pg.Instructions(tp.Policy()) @@ -2962,10 +2962,14 @@ func runTest(t *testing.T, tp testPolicy, polprogOpts ...polprog.Option) { }() for i, p := range insns { polProgFD, err := bpf.LoadBPFProgramFromInsns(p, "calico_policy", "Apache-2.0", unix.BPF_PROG_TYPE_SCHED_CLS) + if err != nil && errors.Is(err, unix.ERANGE) && jumpStride == asm.TrampolineStrideDefault { + jumpStride = 14000 + goto retry + } Expect(err).NotTo(HaveOccurred(), "failed to load program into the kernel") Expect(polProgFD).NotTo(BeZero()) polProgFDs = append(polProgFDs, polProgFD) - err = policyJumpMap.Update( + err = policyJumpMap[hook.Ingress].Update( jump.Key(polprog.SubProgramJumpIdx(polProgIdx, i, stride)), jump.Value(polProgFD.FD()), ) diff --git a/felix/bpf/ut/policy_verdict_events_test.go b/felix/bpf/ut/policy_verdict_events_test.go index bd18781a28f..0ce723e589c 100644 --- a/felix/bpf/ut/policy_verdict_events_test.go +++ b/felix/bpf/ut/policy_verdict_events_test.go @@ -19,7 +19,7 @@ import ( "net" "testing" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/bpf/events" diff --git a/felix/bpf/ut/precompilation_test.go b/felix/bpf/ut/precompilation_test.go index 9810177d14d..e06e90165f1 100644 --- a/felix/bpf/ut/precompilation_test.go +++ b/felix/bpf/ut/precompilation_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package ut +package ut_test import ( "fmt" @@ -64,7 +64,8 @@ func TestPrecompiledBinariesAreLoadable(t *testing.T) { objects[at.ObjectFile()] = struct{}{} } - objects["tc_preamble.o"] = struct{}{} + objects["tc_preamble_ingress.o"] = struct{}{} + objects["tc_preamble_egress.o"] = struct{}{} objects["xdp_preamble.o"] = struct{}{} objects["conntrack_cleanup_debug_co-re_v4.o"] = struct{}{} objects["conntrack_cleanup_debug_co-re_v6.o"] = struct{}{} diff --git a/felix/bpf/ut/qos_test.go b/felix/bpf/ut/qos_test.go index 4b757a41f3e..e35dfc76794 100644 --- a/felix/bpf/ut/qos_test.go +++ b/felix/bpf/ut/qos_test.go @@ -57,6 +57,8 @@ func TestQoSPacketRate(t *testing.T) { defer resetRTMap(rtMap) // Populate QoS map + resetQoSMap(qosMap) + defer resetQoSMap(qosMap) key1 := qos.NewKey(uint32(ifIndex), 1) key2 := qos.NewKey(uint32(ifIndex), 0) value := qos.NewValue(1, 1, -1, 0) @@ -233,14 +235,14 @@ func TestDSCPv6_WEP(t *testing.T) { for _, tc := range []dscpTestCase{ // Dest outside cluster. - {"calico_from_workload_ep", 0, srcIPv6, externalAddr, resTC_ACT_UNSPEC, 16, 16}, + {"calico_from_workload_ep", 0, srcIPv6, externalAddr, resTC_ACT_REDIRECT, 16, 16}, {"calico_to_workload_ep", tcdefs.MarkSeen, srcIPv6, externalAddr, resTC_ACT_UNSPEC, 20, -1}, // Src outside cluster. {"calico_to_workload_ep", tcdefs.MarkSeen, externalAddr, dstIPv6, resTC_ACT_UNSPEC, 20, -1}, // Src and dest both inside cluster. - {"calico_from_workload_ep", 0, srcIPv6, dstIPv6, resTC_ACT_UNSPEC, 16, -1}, + {"calico_from_workload_ep", 0, srcIPv6, dstIPv6, resTC_ACT_REDIRECT, 16, -1}, } { skbMark = tc.expectedSKBMark runBpfTest(t, tc.progName, rulesDefaultAllow, func(bpfrun bpfProgRunFn) { diff --git a/felix/bpf/ut/snat_test.go b/felix/bpf/ut/snat_test.go index 196ef81240a..a53103ad4a2 100644 --- a/felix/bpf/ut/snat_test.go +++ b/felix/bpf/ut/snat_test.go @@ -19,8 +19,8 @@ import ( "net" "testing" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/bpf/conntrack" diff --git a/felix/bpf/ut/tcp_rst_test.go b/felix/bpf/ut/tcp_rst_test.go new file mode 100644 index 00000000000..ec3d14b0d54 --- /dev/null +++ b/felix/bpf/ut/tcp_rst_test.go @@ -0,0 +1,139 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. +// +// 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 ut_test + +import ( + "fmt" + "testing" + + "github.com/gopacket/gopacket" + "github.com/gopacket/gopacket/layers" + . "github.com/onsi/gomega" +) + +func TestTCPReset(t *testing.T) { + RegisterTestingT(t) + cleanUpMaps() + defer cleanUpMaps() + + tcpSyn := &layers.TCP{ + SrcPort: 54321, + DstPort: 7890, + SYN: false, + Seq: 1000, + DataOffset: 5, + } + + _, ipv4, l4, _, pktBytes, err := testPacketV4(nil, nil, tcpSyn, nil) + Expect(err).NotTo(HaveOccurred()) + tcp, ok := l4.(*layers.TCP) + Expect(ok).To(BeTrue()) + runBpfUnitTest(t, "tcp_rst.c", func(bpfrun bpfProgRunFn) { + res, err := bpfrun(pktBytes) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(0)) + + Expect(res.dataOut).To(HaveLen(54)) // eth(14) + ip(20) + tcp(20) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + checkTcpRst(pktR, ipv4, tcp, false) + }) +} + +func TestTCPResetIPv6(t *testing.T) { + RegisterTestingT(t) + cleanUpMaps() + defer cleanUpMaps() + + tcpSyn := &layers.TCP{ + SrcPort: 54321, + DstPort: 7890, + SYN: false, + Seq: 1000, + DataOffset: 5, + } + + hop := &layers.IPv6HopByHop{} + hop.NextHeader = layers.IPProtocolTCP + + /* from gopacket ip6_test.go */ + tlv := &layers.IPv6HopByHopOption{} + tlv.OptionType = 0x01 //PadN + tlv.OptionData = []byte{0x00, 0x00, 0x00, 0x00} + hop.Options = append(hop.Options, tlv) + + _, ipv6, l4, _, pktBytes, err := testPacketV6(nil, nil, tcpSyn, nil, hop) + Expect(err).NotTo(HaveOccurred()) + tcp, ok := l4.(*layers.TCP) + Expect(ok).To(BeTrue()) + runBpfUnitTest(t, "tcp_rst.c", func(bpfrun bpfProgRunFn) { + res, err := bpfrun(pktBytes) + Expect(err).NotTo(HaveOccurred()) + Expect(res.Retval).To(Equal(0)) + + Expect(res.dataOut).To(HaveLen(74)) // Ethernet (14) + IPv6 (40) + TCP (20) + + pktR := gopacket.NewPacket(res.dataOut, layers.LayerTypeEthernet, gopacket.Default) + fmt.Printf("pktR = %+v\n", pktR) + + ipv6L := pktR.Layer(layers.LayerTypeIPv6) + Expect(ipv6L).NotTo(BeNil()) + ipv6R, ok := ipv6L.(*layers.IPv6) + Expect(ok).To(BeTrue()) + Expect(ipv6R.NextHeader).To(Equal(layers.IPProtocolTCP)) + Expect(ipv6R.SrcIP).To(Equal(ipv6.DstIP)) + Expect(ipv6R.DstIP).To(Equal(ipv6.SrcIP)) + + tcpL := pktR.Layer(layers.LayerTypeTCP) + Expect(tcpL).NotTo(BeNil()) + tcpR, ok := tcpL.(*layers.TCP) + Expect(ok).To(BeTrue()) + Expect(tcpR.RST).To(BeTrue()) + Expect(tcpR.SrcPort).To(Equal(tcp.DstPort)) + Expect(tcpR.DstPort).To(Equal(tcp.SrcPort)) + Expect(tcpR.Seq).To(Equal(uint32(0))) + }, withIPv6()) + +} + +func checkTcpRst(pktR gopacket.Packet, ipv4 *layers.IPv4, tcp *layers.TCP, ack bool) { + ipv4L := pktR.Layer(layers.LayerTypeIPv4) + Expect(ipv4L).NotTo(BeNil()) + ipv4R, ok := ipv4L.(*layers.IPv4) + Expect(ok).To(BeTrue()) + Expect(ipv4R.SrcIP).To(Equal(ipv4.DstIP)) + Expect(ipv4R.DstIP).To(Equal(ipv4.SrcIP)) + Expect(ipv4R.Protocol).To(Equal(layers.IPProtocolTCP)) + Expect(ipv4R.TTL).To(Equal(uint8(64))) + + tcpL := pktR.Layer(layers.LayerTypeTCP) + Expect(tcpL).NotTo(BeNil()) + tcpR, ok := tcpL.(*layers.TCP) + Expect(ok).To(BeTrue()) + Expect(tcpR.RST).To(BeTrue()) + Expect(tcpR.FIN).To(BeFalse()) + Expect(tcpR.SYN).To(BeFalse()) + Expect(tcpR.URG).To(BeFalse()) + Expect(tcpR.ECE).To(BeFalse()) + Expect(tcpR.CWR).To(BeFalse()) + Expect(tcpR.NS).To(BeFalse()) + Expect(tcpR.PSH).To(BeFalse()) + + Expect(tcpR.SrcPort).To(Equal(tcp.DstPort)) + Expect(tcpR.DstPort).To(Equal(tcp.SrcPort)) + Expect(tcpR.Seq).To(Equal(uint32(0))) +} diff --git a/felix/bpf/ut/tcp_test.go b/felix/bpf/ut/tcp_test.go index 40642688b8c..a5ec80ab1a0 100644 --- a/felix/bpf/ut/tcp_test.go +++ b/felix/bpf/ut/tcp_test.go @@ -19,8 +19,8 @@ import ( "net" "testing" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/bpf/conntrack" diff --git a/felix/bpf/ut/to_host_allowed_test.go b/felix/bpf/ut/to_host_allowed_test.go index c9a3a65aaed..f7214a0f440 100644 --- a/felix/bpf/ut/to_host_allowed_test.go +++ b/felix/bpf/ut/to_host_allowed_test.go @@ -18,7 +18,7 @@ import ( "net" "testing" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" "github.com/sirupsen/logrus" diff --git a/felix/bpf/ut/whitelist_test.go b/felix/bpf/ut/whitelist_test.go index 79c53d43296..c6fa63a8221 100644 --- a/felix/bpf/ut/whitelist_test.go +++ b/felix/bpf/ut/whitelist_test.go @@ -17,11 +17,11 @@ package ut_test import ( "testing" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/bpf/conntrack" - v3 "github.com/projectcalico/calico/felix/bpf/conntrack/v3" + v4 "github.com/projectcalico/calico/felix/bpf/conntrack/v4" "github.com/projectcalico/calico/felix/bpf/routes" tcdefs "github.com/projectcalico/calico/felix/bpf/tc/defs" "github.com/projectcalico/calico/felix/ip" @@ -137,7 +137,7 @@ func TestSkipIngressRedirect(t *testing.T) { Expect(ctr.Data().A2B.Approved).To(BeTrue()) // Not approved by WEP yet Expect(ctr.Data().B2A.Approved).NotTo(BeTrue()) - Expect(ctr.Flags() & v3.FlagNoRedirPeer).To(Equal(v3.FlagNoRedirPeer)) + Expect(ctr.Flags() & v4.FlagNoRedirPeer).To(Equal(v4.FlagNoRedirPeer)) }) // Reset route map and add reverse route from local workload with skip ingress redirect flag @@ -169,7 +169,7 @@ func TestSkipIngressRedirect(t *testing.T) { Expect(ctr.Data().A2B.Approved).To(BeTrue()) // Not approved by dst WEP yet Expect(ctr.Data().B2A.Approved).NotTo(BeTrue()) - Expect(ctr.Flags() & v3.FlagNoRedirPeer).To(Equal(v3.FlagNoRedirPeer)) + Expect(ctr.Flags() & v4.FlagNoRedirPeer).To(Equal(v4.FlagNoRedirPeer)) }) } @@ -432,7 +432,7 @@ func TestAllowEnterHostToWorkloadV6(t *testing.T) { runBpfTest(t, "calico_from_host_ep", nil, func(bpfrun bpfProgRunFn) { res, err := bpfrun(pktBytes) Expect(err).NotTo(HaveOccurred()) - Expect(res.Retval).To(Equal(resTC_ACT_UNSPEC)) + Expect(res.Retval).To(Equal(resTC_ACT_REDIRECT)) ct, err := conntrack.LoadMapMemV6(ctMapV6) Expect(err).NotTo(HaveOccurred()) diff --git a/felix/bpf/ut/xdp_test.go b/felix/bpf/ut/xdp_test.go index fa5204e0f49..8ff79edd0c0 100644 --- a/felix/bpf/ut/xdp_test.go +++ b/felix/bpf/ut/xdp_test.go @@ -19,8 +19,8 @@ import ( "net" "testing" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket" + "github.com/gopacket/gopacket/layers" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/bpf/failsafes" diff --git a/felix/calc/active_bpgpeer_calculator.go b/felix/calc/active_bpgpeer_calculator.go index 2e0e60fe083..649c5703d99 100644 --- a/felix/calc/active_bpgpeer_calculator.go +++ b/felix/calc/active_bpgpeer_calculator.go @@ -24,7 +24,7 @@ import ( "github.com/projectcalico/calico/felix/dispatcher" "github.com/projectcalico/calico/felix/labelindex" - libv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" sel "github.com/projectcalico/calico/libcalico-go/lib/selector" @@ -110,13 +110,13 @@ func (abp *ActiveBGPPeerCalculator) OnUpdate(update api.Update) (_ bool) { abp.onPeerInactive(id.Name) delete(abp.allBGPPeersByName, id.Name) } - case libv3.KindNode: + case internalapi.KindNode: nodeName := update.Key.(model.ResourceKey).Name if nodeName != abp.hostname { return } if update.Value != nil { - node := update.Value.(*libv3.Node) + node := update.Value.(*internalapi.Node) abp.onLocalNodeLabelUpdate(node.Labels) } else { // Our node was deleted. We must handle this as if the node diff --git a/felix/calc/active_bpgpeer_calculator_test.go b/felix/calc/active_bpgpeer_calculator_test.go index 5ff578617e3..4441f1aea15 100644 --- a/felix/calc/active_bpgpeer_calculator_test.go +++ b/felix/calc/active_bpgpeer_calculator_test.go @@ -15,13 +15,13 @@ package calc import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/projectcalico/calico/lib/std/uniquelabels" - libv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" ) @@ -105,8 +105,8 @@ var _ = Describe("ActiveBGPPeerCalculator", func() { // Add host update. abp.OnUpdate(api.Update{ KVPair: model.KVPair{ - Key: model.ResourceKey{Kind: libv3.KindNode, Name: hostname}, - Value: &libv3.Node{ + Key: model.ResourceKey{Kind: internalapi.KindNode, Name: hostname}, + Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: hostname, Labels: map[string]string{"host": "my-host"}, @@ -293,8 +293,8 @@ var _ = Describe("ActiveBGPPeerCalculator", func() { It("Should set correct bgp peer data on node labels update", func() { abp.OnUpdate(api.Update{ KVPair: model.KVPair{ - Key: model.ResourceKey{Kind: libv3.KindNode, Name: hostname}, - Value: &libv3.Node{ + Key: model.ResourceKey{Kind: internalapi.KindNode, Name: hostname}, + Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: hostname, Labels: map[string]string{"host": "other-host"}, diff --git a/felix/calc/active_rules_calculator.go b/felix/calc/active_rules_calculator.go index 12d4c4c7d4d..34dbcdf5ac2 100644 --- a/felix/calc/active_rules_calculator.go +++ b/felix/calc/active_rules_calculator.go @@ -16,6 +16,7 @@ package calc import ( "reflect" + "slices" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" @@ -44,6 +45,8 @@ type FelixSender interface { type PolicyMatchListener interface { OnPolicyMatch(policyKey model.PolicyKey, endpointKey model.EndpointKey) OnPolicyMatchStopped(policyKey model.PolicyKey, endpointKey model.EndpointKey) + OnComputedSelectorMatch(cs string, endpointKey model.EndpointKey) + OnComputedSelectorMatchStopped(cs string, endpointKey model.EndpointKey) } // ActiveRulesCalculator calculates the set of policies and profiles (i.e. the rules) that @@ -96,6 +99,8 @@ type ActiveRulesCalculator struct { OnAlive func() } +type computedSelector string + func NewActiveRulesCalculator() *ActiveRulesCalculator { arc := &ActiveRulesCalculator{ // Caches of all known policies/profiles and tiers. @@ -283,16 +288,26 @@ func (arc *ActiveRulesCalculator) OnUpdate(update api.Update) (_ bool) { return } +// AddExtraComputedSelector adds an extra non-policy selector to the label index and gives OnComputedSelectorMatch +// and OnComputedSelectorMatchStopped callbacks when that selector matches/stops matching local endpoints. Allows for +// sharing the expensive selector index. +func (arc *ActiveRulesCalculator) AddExtraComputedSelector(cs string) { + sel, err := selector.Parse(cs) + if err != nil { + log.WithError(err).Panicf("Failed to parse computed selector %#v", cs) + } + arc.labelIndex.UpdateSelector(computedSelector(cs), sel) +} + +func (arc *ActiveRulesCalculator) RemoveExtraComputedSelector(cs string) { + arc.labelIndex.DeleteSelector(computedSelector(cs)) +} + func policyForceProgrammed(policy *model.Policy) bool { if policy == nil { return false } - for _, v := range policy.PerformanceHints { - if v == v3.PerfHintAssumeNeededOnEveryNode { - return true - } - } - return false + return slices.Contains(policy.PerformanceHints, v3.PerfHintAssumeNeededOnEveryNode) } func (arc *ActiveRulesCalculator) updateStats() { @@ -308,13 +323,13 @@ func (arc *ActiveRulesCalculator) OnStatusUpdate(status api.SyncStatus) { if arc.missingProfiles.Len() > 0 { // Log out any profiles that were missing during the resync. We defer // this until now because we may hear about profiles or endpoints first. - arc.missingProfiles.Iter(func(profileID string) error { + for profileID := range arc.missingProfiles.All() { log.WithField("profileID", profileID).Warning( "End of resync: local endpoints refer to missing " + "or invalid profile, profile's rules replaced " + "with drop rules.") - return set.RemoveItem - }) + arc.missingProfiles.Discard(profileID) + } } } } @@ -349,10 +364,19 @@ func (arc *ActiveRulesCalculator) updateEndpointProfileIDs(key model.Key, profil } } -func (arc *ActiveRulesCalculator) onMatchStarted(selID, labelId interface{}) { +func (arc *ActiveRulesCalculator) onMatchStarted(selID, labelId any) { + if cs, ok := selID.(computedSelector); ok { + for _, l := range arc.PolicyMatchListeners { + if labelId, ok := labelId.(model.EndpointKey); ok { + l.OnComputedSelectorMatch(string(cs), labelId) + } + } + return + } polKey := selID.(model.PolicyKey) policyWasActive := arc.policyIDToEndpointKeys.ContainsKey(polKey) arc.policyIDToEndpointKeys.Put(selID, labelId) + if !policyWasActive { // Policy wasn't active before, tell the listener. The policy // must be in allPolicies because we can only match on a policy @@ -364,6 +388,7 @@ func (arc *ActiveRulesCalculator) onMatchStarted(selID, labelId interface{}) { } arc.sendPolicyUpdate(polKey, policy) } + if labelId, ok := labelId.(model.EndpointKey); ok { for _, l := range arc.PolicyMatchListeners { l.OnPolicyMatch(polKey, labelId) @@ -371,7 +396,15 @@ func (arc *ActiveRulesCalculator) onMatchStarted(selID, labelId interface{}) { } } -func (arc *ActiveRulesCalculator) onMatchStopped(selID, labelId interface{}) { +func (arc *ActiveRulesCalculator) onMatchStopped(selID, labelId any) { + if cs, ok := selID.(computedSelector); ok { + for _, l := range arc.PolicyMatchListeners { + if labelId, ok := labelId.(model.EndpointKey); ok { + l.OnComputedSelectorMatchStopped(string(cs), labelId) + } + } + return + } polKey := selID.(model.PolicyKey) arc.policyIDToEndpointKeys.Discard(selID, labelId) if !arc.policyIDToEndpointKeys.ContainsKey(selID) { @@ -388,12 +421,10 @@ func (arc *ActiveRulesCalculator) onMatchStopped(selID, labelId interface{}) { } } -var ( - DummyDropRules = model.ProfileRules{ - InboundRules: []model.Rule{{Action: "deny"}}, - OutboundRules: []model.Rule{{Action: "deny"}}, - } -) +var DummyDropRules = model.ProfileRules{ + InboundRules: []model.Rule{{Action: "deny"}}, + OutboundRules: []model.Rule{{Action: "deny"}}, +} func (arc *ActiveRulesCalculator) sendProfileUpdate(profileID string, rules *model.ProfileRules) { active := arc.profileIDToEndpointKeys.ContainsKey(profileID) diff --git a/felix/calc/active_rules_calculator_test.go b/felix/calc/active_rules_calculator_test.go new file mode 100644 index 00000000000..96c171d203f --- /dev/null +++ b/felix/calc/active_rules_calculator_test.go @@ -0,0 +1,239 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. +// +// 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 calc + +import ( + "testing" + + "github.com/projectcalico/calico/lib/std/uniquelabels" + "github.com/projectcalico/calico/libcalico-go/lib/backend/api" + "github.com/projectcalico/calico/libcalico-go/lib/backend/model" +) + +// testPolicyMatchListener records calls to the PolicyMatchListener interface. +type testPolicyMatchListener struct { + policyMatches []policyMatchEvent + policyMatchStops []policyMatchEvent + computedSelectorMatches []computedSelectorMatchEvent + computedSelectorMatchStops []computedSelectorMatchEvent +} + +type policyMatchEvent struct { + PolicyKey model.PolicyKey + EndpointKey model.EndpointKey +} + +type computedSelectorMatchEvent struct { + Selector string + EndpointKey model.EndpointKey +} + +func (t *testPolicyMatchListener) OnPolicyMatch(policyKey model.PolicyKey, endpointKey model.EndpointKey) { + t.policyMatches = append(t.policyMatches, policyMatchEvent{policyKey, endpointKey}) +} + +func (t *testPolicyMatchListener) OnPolicyMatchStopped(policyKey model.PolicyKey, endpointKey model.EndpointKey) { + t.policyMatchStops = append(t.policyMatchStops, policyMatchEvent{policyKey, endpointKey}) +} + +func (t *testPolicyMatchListener) OnComputedSelectorMatch(cs string, endpointKey model.EndpointKey) { + t.computedSelectorMatches = append(t.computedSelectorMatches, computedSelectorMatchEvent{cs, endpointKey}) +} + +func (t *testPolicyMatchListener) OnComputedSelectorMatchStopped(cs string, endpointKey model.EndpointKey) { + t.computedSelectorMatchStops = append(t.computedSelectorMatchStops, computedSelectorMatchEvent{cs, endpointKey}) +} + +// noopRuleScanner satisfies the ruleScanner interface required by ActiveRulesCalculator. +type noopRuleScanner struct{} + +func (n *noopRuleScanner) OnPolicyActive(model.PolicyKey, *model.Policy) {} +func (n *noopRuleScanner) OnPolicyInactive(model.PolicyKey) {} +func (n *noopRuleScanner) OnProfileActive(model.ProfileRulesKey, *model.ProfileRules) {} +func (n *noopRuleScanner) OnProfileInactive(model.ProfileRulesKey) {} + +func createARC() (*ActiveRulesCalculator, *testPolicyMatchListener) { + arc := NewActiveRulesCalculator() + arc.RuleScanner = &noopRuleScanner{} + listener := &testPolicyMatchListener{} + arc.RegisterPolicyMatchListener(listener) + return arc, listener +} + +func addEndpoint(arc *ActiveRulesCalculator, key model.WorkloadEndpointKey, labels map[string]string) { + arc.OnUpdate(api.Update{ + KVPair: model.KVPair{ + Key: key, + Value: &model.WorkloadEndpoint{ + Labels: uniquelabels.Make(labels), + }, + }, + }) +} + +func deleteEndpoint(arc *ActiveRulesCalculator, key model.WorkloadEndpointKey) { + arc.OnUpdate(api.Update{ + KVPair: model.KVPair{ + Key: key, + Value: nil, + }, + }) +} + +func TestARC_ComputedSelector_MatchOnEndpointAdd(t *testing.T) { + arc, listener := createARC() + + arc.AddExtraComputedSelector("has(foo)") + + epKey := model.WorkloadEndpointKey{ + Hostname: "host1", + OrchestratorID: "orch", + WorkloadID: "wl1", + EndpointID: "ep1", + } + addEndpoint(arc, epKey, map[string]string{"foo": "bar"}) + + if len(listener.computedSelectorMatches) != 1 { + t.Fatalf("expected 1 computed selector match, got %d", len(listener.computedSelectorMatches)) + } + ev := listener.computedSelectorMatches[0] + if ev.Selector != "has(foo)" { + t.Errorf("expected selector %q, got %q", "has(foo)", ev.Selector) + } + if ev.EndpointKey != epKey { + t.Errorf("expected endpoint key %v, got %v", epKey, ev.EndpointKey) + } +} + +func TestARC_ComputedSelector_MatchStoppedOnEndpointRemove(t *testing.T) { + arc, listener := createARC() + + arc.AddExtraComputedSelector("has(foo)") + + epKey := model.WorkloadEndpointKey{ + Hostname: "host1", + OrchestratorID: "orch", + WorkloadID: "wl1", + EndpointID: "ep1", + } + addEndpoint(arc, epKey, map[string]string{"foo": "bar"}) + + deleteEndpoint(arc, epKey) + + if len(listener.computedSelectorMatchStops) != 1 { + t.Fatalf("expected 1 computed selector match stop, got %d", len(listener.computedSelectorMatchStops)) + } + ev := listener.computedSelectorMatchStops[0] + if ev.Selector != "has(foo)" { + t.Errorf("expected selector %q, got %q", "has(foo)", ev.Selector) + } + if ev.EndpointKey != epKey { + t.Errorf("expected endpoint key %v, got %v", epKey, ev.EndpointKey) + } +} + +func TestARC_ComputedSelector_NoMatchForNonMatchingEndpoint(t *testing.T) { + arc, listener := createARC() + + arc.AddExtraComputedSelector("has(foo)") + + epKey := model.WorkloadEndpointKey{ + Hostname: "host1", + OrchestratorID: "orch", + WorkloadID: "wl1", + EndpointID: "ep1", + } + // Endpoint does NOT have the "foo" label. + addEndpoint(arc, epKey, map[string]string{"bar": "baz"}) + + if len(listener.computedSelectorMatches) != 0 { + t.Errorf("expected no computed selector matches, got %d", len(listener.computedSelectorMatches)) + } + if len(listener.computedSelectorMatchStops) != 0 { + t.Errorf("expected no computed selector match stops, got %d", len(listener.computedSelectorMatchStops)) + } +} + +func TestARC_RemoveComputedSelector(t *testing.T) { + arc, listener := createARC() + + arc.AddExtraComputedSelector("has(foo)") + + epKey := model.WorkloadEndpointKey{ + Hostname: "host1", + OrchestratorID: "orch", + WorkloadID: "wl1", + EndpointID: "ep1", + } + addEndpoint(arc, epKey, map[string]string{"foo": "bar"}) + + if len(listener.computedSelectorMatches) != 1 { + t.Fatalf("expected 1 match after adding endpoint, got %d", len(listener.computedSelectorMatches)) + } + + // Remove the computed selector — should fire match-stopped. + arc.RemoveExtraComputedSelector("has(foo)") + + if len(listener.computedSelectorMatchStops) != 1 { + t.Fatalf("expected 1 match stop after removing selector, got %d", len(listener.computedSelectorMatchStops)) + } + + // Reset events. + listener.computedSelectorMatches = nil + listener.computedSelectorMatchStops = nil + + // Add another matching endpoint — no further events since selector is removed. + epKey2 := model.WorkloadEndpointKey{ + Hostname: "host1", + OrchestratorID: "orch", + WorkloadID: "wl2", + EndpointID: "ep2", + } + addEndpoint(arc, epKey2, map[string]string{"foo": "baz"}) + + if len(listener.computedSelectorMatches) != 0 { + t.Errorf("expected no matches after selector removed, got %d", len(listener.computedSelectorMatches)) + } + if len(listener.computedSelectorMatchStops) != 0 { + t.Errorf("expected no match stops after selector removed, got %d", len(listener.computedSelectorMatchStops)) + } +} + +func TestARC_ComputedSelector_DoesNotTriggerPolicyCallbacks(t *testing.T) { + arc, listener := createARC() + + arc.AddExtraComputedSelector("has(foo)") + + epKey := model.WorkloadEndpointKey{ + Hostname: "host1", + OrchestratorID: "orch", + WorkloadID: "wl1", + EndpointID: "ep1", + } + addEndpoint(arc, epKey, map[string]string{"foo": "bar"}) + + // Computed selector match should NOT produce policy callbacks. + if len(listener.policyMatches) != 0 { + t.Errorf("expected no policy matches, got %d", len(listener.policyMatches)) + } + if len(listener.policyMatchStops) != 0 { + t.Errorf("expected no policy match stops, got %d", len(listener.policyMatchStops)) + } + + // policyIDToEndpointKeys should be empty — computed selectors don't create policy entries. + if arc.policyIDToEndpointKeys.Len() != 0 { + t.Errorf("expected policyIDToEndpointKeys to be empty, got len=%d", arc.policyIDToEndpointKeys.Len()) + } +} diff --git a/felix/calc/async_calc_graph.go b/felix/calc/async_calc_graph.go index d7c2c8cc80c..8524036101f 100644 --- a/felix/calc/async_calc_graph.go +++ b/felix/calc/async_calc_graph.go @@ -71,8 +71,8 @@ func init() { type AsyncCalcGraph struct { CalcGraph *CalcGraph - inputEvents chan interface{} - outputChannels []chan<- interface{} + inputEvents chan any + outputChannels []chan<- any eventSequencer *EventSequencer beenInSync bool needToSendInSync bool @@ -95,13 +95,13 @@ const ( func NewAsyncCalcGraph( conf *config.Config, - outputChannels []chan<- interface{}, + outputChannels []chan<- any, healthAggregator *health.HealthAggregator, lookupCache *LookupsCache, ) *AsyncCalcGraph { eventSequencer := NewEventSequencer(conf) g := &AsyncCalcGraph{ - inputEvents: make(chan interface{}, 10), + inputEvents: make(chan any, 10), outputChannels: outputChannels, eventSequencer: eventSequencer, healthAggregator: healthAggregator, @@ -228,7 +228,7 @@ func (acg *AsyncCalcGraph) maybeFlush() { } } -func (acg *AsyncCalcGraph) onEvent(event interface{}) { +func (acg *AsyncCalcGraph) onEvent(event any) { log.Debug("Sending output event on channel(s)") healthTickCount := 0 startTime := time.Now() diff --git a/felix/calc/async_decoupler.go b/felix/calc/async_decoupler.go index 18ccf6b8fa2..5c1f67d4e76 100644 --- a/felix/calc/async_decoupler.go +++ b/felix/calc/async_decoupler.go @@ -20,12 +20,12 @@ import ( func NewSyncerCallbacksDecoupler() *SyncerCallbacksDecoupler { return &SyncerCallbacksDecoupler{ - c: make(chan interface{}), + c: make(chan any), } } type SyncerCallbacksDecoupler struct { - c chan interface{} + c chan any } func (a *SyncerCallbacksDecoupler) OnStatusUpdated(status api.SyncStatus) { diff --git a/felix/calc/calc_graph.go b/felix/calc/calc_graph.go index ff60db64c19..8d98c7fac3f 100644 --- a/felix/calc/calc_graph.go +++ b/felix/calc/calc_graph.go @@ -57,6 +57,7 @@ type rulesUpdateCallbacks interface { type endpointCallbacks interface { OnEndpointTierUpdate(endpointKey model.EndpointKey, endpoint model.Endpoint, + computedData []EndpointComputedData, peerData *EndpointBGPPeer, filteredTiers []TierInfo) } diff --git a/felix/calc/calc_graph_bench_test.go b/felix/calc/calc_graph_bench_test.go index efa9c5cca36..a57c909d55f 100644 --- a/felix/calc/calc_graph_bench_test.go +++ b/felix/calc/calc_graph_bench_test.go @@ -25,6 +25,7 @@ import ( . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "github.com/projectcalico/api/pkg/lib/numorstring" "github.com/sirupsen/logrus" "github.com/projectcalico/calico/felix/config" @@ -97,7 +98,7 @@ func benchInitialSnap( conf.FelixHostname = "localhost" es := NewEventSequencer(conf) numMessages := 0 - es.Callback = func(message interface{}) { + es.Callback = func(message any) { numMessages++ } cg = NewCalculationGraph(es, nil, conf, func() {}) @@ -167,7 +168,7 @@ func sendDeletions(cg *CalcGraph, localDeletes []api.Update) { func makeNetSetAndPolUpdates(num int) []api.Update { updates := make([]api.Update, 0, num*2) - for i := 0; i < num; i++ { + for i := range num { // Make one netset and a matching policy. name := fmt.Sprintf("network-set-%d", i) netset := &model.NetworkSet{ @@ -183,7 +184,7 @@ func makeNetSetAndPolUpdates(num int) []api.Update { }, }) - pol := &model.Policy{} + pol := &model.Policy{Tier: "default"} pol.Selector = "all()" pol.InboundRules = append(pol.InboundRules, model.Rule{ Action: "Allow", @@ -210,7 +211,7 @@ var netSetSizes = []int{ func generateNetSetIPs() (ips []calinet.IPNet) { size := netSetSizes[nextNetSetSizeIdx%len(netSetSizes)] nextNetSetSizeIdx++ - for i := 0; i < size; i++ { + for range size { theIP := net.IPv4(0, 0, 0, 0) binary.BigEndian.PutUint32(theIP, nextNetSetIP) _, n, _ := calinet.ParseCIDROrIP(theIP.String()) @@ -222,7 +223,7 @@ func generateNetSetIPs() (ips []calinet.IPNet) { func makeNamespaceUpdates(num int) []api.Update { updates := make([]api.Update, 0, 2*num) - for i := 0; i < num; i++ { + for i := range num { name := fmt.Sprintf("namespace-%d", i) prof := &v3.Profile{ Spec: v3.ProfileSpec{ @@ -258,10 +259,10 @@ var nextTagPolID int func makeTagPolicies(num int) []api.Update { const rulesPerPol = 5 updates := make([]api.Update, 0, num) - for i := 0; i < num; i++ { - pol := &model.Policy{} + for i := range num { + pol := &model.Policy{Tier: "default"} pol.Selector = fmt.Sprintf("has(%s)", markerLabels[i%len(markerLabels)]) - for j := 0; j < rulesPerPol; j++ { + for j := range rulesPerPol { pol.InboundRules = append(pol.InboundRules, model.Rule{ Action: "Allow", SrcSelector: fmt.Sprintf("has(%s)", markerLabels[(nextTagPolID+j)%len(markerLabels)]), @@ -282,7 +283,7 @@ func makeTagPolicies(num int) []api.Update { func makeEndpointUpdates(num int, host string) []api.Update { updates := make([]api.Update, num) - for n := 0; n < num; n++ { + for n := range num { key := model.WorkloadEndpointKey{ Hostname: host, OrchestratorID: "k8s", @@ -305,7 +306,7 @@ func makeEndpointUpdates(num int, host string) []api.Update { } func makeEndpointDeletes(num int, host string) []api.Update { updates := make([]api.Update, num) - for n := 0; n < num; n++ { + for n := range num { key := model.WorkloadEndpointKey{ Hostname: host, OrchestratorID: "k8s", @@ -392,3 +393,324 @@ func generateLabels() uniquelabels.Map { return uniquelabels.Make(labels) } + +// --- Isolated customer environments benchmark --- +// +// Simulates a multi-tenant SaaS cluster with many identical namespaces, each +// containing uniform pods. Pod and namespace labels are modelled on a real +// SaaS deployment with realistic cardinalities. +// +// Pod labels (15): +// - 5 unique-per-pod (string lengths 20, 20, 10, 20, 8) +// - 4 binary (values "0" or "1") +// - 4 small-set (environment, tier, region, team) +// - 1 marker (identical on every pod) +// - 1 high-cardinality (~5000 distinct values, 10 chars) +// +// Namespace labels (3, applied via Profile): +// - kubernetes.io/metadata.name (= namespace name) +// - namespace-id: unique 20-char identifier +// - production: boolean "true"/"false" + +func BenchmarkIsolatedCustomers1k(b *testing.B) { + benchIsolatedCustomers(b, 1_000, 1, 100) +} + +func BenchmarkIsolatedCustomers10k(b *testing.B) { + benchIsolatedCustomers(b, 10_000, 1, 100) +} + +func BenchmarkIsolatedCustomers100k(b *testing.B) { + benchIsolatedCustomers(b, 100_000, 1, 100) +} + +const ( + nsLabelKey = conversion.NamespaceLabelPrefix + conversion.NameLabel + nsProfilePrefix = conversion.NamespaceProfileNamePrefix + kubeDNSCIDR = "10.96.0.10/32" +) + +func benchIsolatedCustomers(b *testing.B, numCustomers, podsPerNamespace, numLocalEps int) { + RegisterTestingT(b) + defer logrus.SetLevel(logrus.GetLevel()) + logrus.SetLevel(logrus.ErrorLevel) + + nsUpdates := makeCustomerNamespaceUpdates(numCustomers) + polUpdates := makeCustomerPolicies(numCustomers) + remoteEpUpdates, localEpUpdates := makeAllCustomerEndpoints(numCustomers, podsPerNamespace, numLocalEps) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + b.StopTimer() + runtime.GC() + logrus.SetLevel(logrus.ErrorLevel) + + conf := config.New() + conf.FelixHostname = "localhost" + es := NewEventSequencer(conf) + numMessages := 0 + es.Callback = func(message interface{}) { + numMessages++ + } + cg = NewCalculationGraph(es, nil, conf, func() {}) + + logrus.SetLevel(logrus.WarnLevel) + b.StartTimer() + b.ReportAllocs() + startTime := time.Now() + + sendCustomerPolicies(cg, polUpdates) + sendCustomerProfiles(cg, nsUpdates) + sendCustomerRemoteEndpoints(cg, remoteEpUpdates) + sendCustomerLocalEndpoints(cg, localEpUpdates) + cg.AllUpdDispatcher.OnDatamodelStatus(api.InSync) + + cg.Flush() + + Expect(es.pendingEndpointUpdates).To(HaveLen(len(localEpUpdates))) + + b.ReportMetric(float64(len(localEpUpdates)), "LocalEps") + b.ReportMetric(float64(len(es.pendingAddedIPSets)), "IPSets") + b.ReportMetric(float64(len(es.pendingPolicyUpdates)), "Policies") + es.Flush() + + b.ReportMetric(float64(time.Since(startTime).Seconds()), "s") + b.ReportMetric(float64(numMessages), "Msgs") + } + b.StopTimer() + + runtime.GC() + time.Sleep(time.Second) + var m runtime.MemStats + runtime.ReadMemStats(&m) + b.ReportMetric(float64(m.HeapAlloc)/(1024*1024), "HeapAllocMB") +} + +// Broken out for CPU profiling visibility. + +func sendCustomerPolicies(cg *CalcGraph, updates []api.Update) { + cg.AllUpdDispatcher.OnUpdates(updates) +} + +func sendCustomerProfiles(cg *CalcGraph, updates []api.Update) { + cg.AllUpdDispatcher.OnUpdates(updates) +} + +func sendCustomerRemoteEndpoints(cg *CalcGraph, updates []api.Update) { + cg.AllUpdDispatcher.OnUpdates(updates) +} + +func sendCustomerLocalEndpoints(cg *CalcGraph, updates []api.Update) { + cg.AllUpdDispatcher.OnUpdates(updates) +} + +// makeCustomerNamespaceUpdates creates Profile + ProfileRules for each +// customer namespace. Each namespace gets three labels: +// - kubernetes.io/metadata.name (standard) +// - namespace-id: unique 20-char identifier +// - production: boolean "true"/"false" +func makeCustomerNamespaceUpdates(numCustomers int) []api.Update { + updates := make([]api.Update, 0, 2*numCustomers) + + for n := 0; n < numCustomers; n++ { + name := fmt.Sprintf("customer-%d", n) + profName := nsProfilePrefix + name + prof := &v3.Profile{ + Spec: v3.ProfileSpec{ + LabelsToApply: map[string]string{ + nsLabelKey: name, + "namespace-id": deterministicString(n, 20), + "production": fmt.Sprintf("%v", n%2 == 0), + }, + }, + } + updates = append(updates, api.Update{ + KVPair: model.KVPair{ + Key: model.ResourceKey{Kind: v3.KindProfile, Name: profName}, + Value: prof, + }, + }) + updates = append(updates, api.Update{ + KVPair: model.KVPair{ + Key: model.ProfileRulesKey{ + ProfileKey: model.ProfileKey{Name: profName}, + }, + Value: &model.ProfileRules{}, + }, + }) + } + + return updates +} + +// makeCustomerPolicies creates one namespaced isolation policy per customer +// namespace: allows intra-namespace traffic and DNS egress. +func makeCustomerPolicies(numCustomers int) []api.Update { + udp := numorstring.ProtocolFromString("UDP") + port53 := numorstring.SinglePort(53) + _, dnsNet, _ := calinet.ParseCIDROrIP(kubeDNSCIDR) + + updates := make([]api.Update, 0, numCustomers) + + for n := 0; n < numCustomers; n++ { + ns := fmt.Sprintf("customer-%d", n) + nsSelector := fmt.Sprintf("%s == '%s'", nsLabelKey, ns) + + pol := &model.Policy{ + Namespace: ns, + Tier: "default", + Selector: nsSelector, + Types: []string{"ingress", "egress"}, + InboundRules: []model.Rule{ + { + Action: "Allow", + SrcSelector: nsSelector, + }, + }, + OutboundRules: []model.Rule{ + { + Action: "Allow", + DstSelector: nsSelector, + }, + { + Action: "Allow", + Protocol: &udp, + DstNets: []*calinet.IPNet{dnsNet}, + DstPorts: []numorstring.Port{port53}, + }, + }, + } + updates = append(updates, api.Update{ + KVPair: model.KVPair{ + Key: model.PolicyKey{Name: fmt.Sprintf("customer-%d/isolation", n), Namespace: ns}, + Value: pol, + }, + }) + } + + return updates +} + +// Small-set label values for pods. +var ( + saasEnvironments = []string{"dev", "staging", "prod"} + saasPodTiers = []string{"frontend", "backend", "middleware", "data"} + saasRegions = []string{"us-east", "us-west", "eu-west", "ap-south"} + saasTeams = []string{"platform", "payments", "identity", "growth", "infra"} +) + +// makeAllCustomerEndpoints creates uniform pods across customer namespaces. +// numLocalEps pods are placed on "localhost" scattered via stride; the rest +// go to "remotehost". Returns (remote, local) slices. +func makeAllCustomerEndpoints(numCustomers, podsPerNamespace, numLocalEps int) (remote, local []api.Update) { + totalEps := numCustomers * podsPerNamespace + if numLocalEps > totalEps { + numLocalEps = totalEps + } + + stride := 1 + if numLocalEps > 0 { + stride = totalEps / numLocalEps + if stride < 1 { + stride = 1 + } + } + + remote = make([]api.Update, 0, totalEps-numLocalEps) + local = make([]api.Update, 0, numLocalEps) + + localCount := 0 + epIdx := 0 + for n := 0; n < numCustomers; n++ { + ns := fmt.Sprintf("customer-%d", n) + profID := nsProfilePrefix + ns + for p := 0; p < podsPerNamespace; p++ { + podName := fmt.Sprintf("app-%09x-%05x", uint32(n*7+3), uint16(p*13+n*17+5)) + labels := makeCustomerPodLabels(epIdx) + if localCount < numLocalEps && epIdx%stride == 0 { + local = append(local, makeCustomerWEP("localhost", ns, podName, profID, labels)) + localCount++ + } else { + remote = append(remote, makeCustomerWEP("remotehost", ns, podName, profID, labels)) + } + epIdx++ + } + } + + return remote, local +} + +// makeCustomerPodLabels generates the 15 realistic labels for a pod at the +// given global index. +func makeCustomerPodLabels(podIdx int) map[string]string { + labels := map[string]string{ + // 5 unique-per-pod labels with specified string lengths. + "pod-id": deterministicString(podIdx*5, 20), + "instance-id": deterministicString(podIdx*5+1, 20), + "short-id": deterministicString(podIdx*5+2, 10), + "config-hash": deterministicString(podIdx*5+3, 20), + "version-tag": deterministicString(podIdx*5+4, 8), + + // 4 binary labels. + "enabled": fmt.Sprintf("%d", podIdx%2), + "debug": fmt.Sprintf("%d", (podIdx/2)%2), + "canary": fmt.Sprintf("%d", (podIdx/4)%2), + "managed": fmt.Sprintf("%d", (podIdx/8)%2), + + // 4 small-set labels. + "environment": saasEnvironments[podIdx%len(saasEnvironments)], + "tier": saasPodTiers[podIdx%len(saasPodTiers)], + "region": saasRegions[podIdx%len(saasRegions)], + "team": saasTeams[podIdx%len(saasTeams)], + + // 1 marker label (same on every pod). + "managed-by": "saas-controller", + + // 1 high-cardinality label (~5000 distinct values, 10 chars). + "tenant-id": deterministicString(podIdx%5000, 10), + } + + // Round-trip through JSON to make strings unique, matching real + // decoder behaviour. + buf, err := json.Marshal(labels) + if err != nil { + panic(err) + } + labels = nil + if err := json.Unmarshal(buf, &labels); err != nil { + panic(err) + } + + return labels +} + +// deterministicString returns a deterministic hex-like string of the given +// length, seeded by n. +func deterministicString(n, length int) string { + const chars = "abcdef0123456789" + b := make([]byte, length) + v := uint64(n)*2654435761 + 1 + for i := range b { + b[i] = chars[v%uint64(len(chars))] + v = v*6364136223846793005 + 1442695040888963407 + } + return string(b) +} + +func makeCustomerWEP(host, ns, podName, profileID string, labels map[string]string) api.Update { + return api.Update{ + KVPair: model.KVPair{ + Key: model.WorkloadEndpointKey{ + Hostname: host, + OrchestratorID: "k8s", + WorkloadID: fmt.Sprintf("%s/%s", ns, podName), + EndpointID: "eth0", + }, + Value: &model.WorkloadEndpoint{ + Labels: uniquelabels.Make(labels), + IPv4Nets: []calinet.IPNet{getNextIP()}, + ProfileIDs: []string{profileID}, + }, + }, + } +} diff --git a/felix/calc/calc_graph_fv_test.go b/felix/calc/calc_graph_fv_test.go index 7bd0fb20a0b..c79f552a203 100644 --- a/felix/calc/calc_graph_fv_test.go +++ b/felix/calc/calc_graph_fv_test.go @@ -25,7 +25,7 @@ import ( "time" "github.com/davecgh/go-spew/spew" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" googleproto "google.golang.org/protobuf/proto" @@ -146,6 +146,12 @@ var baseTests = []StateList{ localEpsWithProfile, }, + // Test movement of policy1 from the default tier to a non-default tier and back. + { + localEp1WithPolicy, + localEp1WithPolicyAndTier, + }, + // Host endpoint tests. {hostEp1WithPolicy, hostEp2WithPolicy, hostEp1WithIngressPolicy, hostEp1WithEgressPolicy}, @@ -685,7 +691,6 @@ var _ = Describe("Async calculation graph state sequencing tests:", func() { for _, expander := range testExpanders() { expanderDesc, expandedTests := expander(baseTest) for _, test := range expandedTests { - test := test It("should handle: "+baseTest.String()+" "+expanderDesc, func() { // Create the calculation graph. conf := config.New() @@ -693,10 +698,10 @@ var _ = Describe("Async calculation graph state sequencing tests:", func() { conf.BPFEnabled = true conf.SetUseNodeResourceUpdates(test.UsesNodeResources()) conf.RouteSource = test.RouteSource() - outputChan := make(chan interface{}) + outputChan := make(chan any) conf.Encapsulation = config.Encapsulation{VXLANEnabled: true, VXLANEnabledV6: true} lookupsCache := NewLookupsCache() - asyncGraph := NewAsyncCalcGraph(conf, []chan<- interface{}{outputChan}, nil, lookupsCache) + asyncGraph := NewAsyncCalcGraph(conf, []chan<- any{outputChan}, nil, lookupsCache) // And a validation filter, with a channel between it // and the async graph. validator := NewValidationFilter(asyncGraph, conf) @@ -818,10 +823,9 @@ func expectCorrectDataplaneState(mockDataplane *mock.MockDataplane, state State) func stringifyRoutes(routes set.Set[types.RouteUpdate]) []string { out := make([]string, 0, routes.Len()) - routes.Iter(func(item types.RouteUpdate) error { + for item := range routes.All() { out = append(out, fmt.Sprintf("%+v", item)) - return nil - }) + } sort.Strings(out) return out } @@ -950,11 +954,11 @@ var _ = Describe("calc graph with health state", func() { // Create the calculation graph. conf := config.New() conf.FelixHostname = localHostname - outputChan := make(chan interface{}) + outputChan := make(chan any) healthAggregator := health.NewHealthAggregator() lookupsCache := NewLookupsCache() conf.Encapsulation = config.Encapsulation{VXLANEnabled: true, VXLANEnabledV6: true} - asyncGraph := NewAsyncCalcGraph(conf, []chan<- interface{}{outputChan}, healthAggregator, lookupsCache) + asyncGraph := NewAsyncCalcGraph(conf, []chan<- any{outputChan}, healthAggregator, lookupsCache) Expect(asyncGraph).NotTo(BeNil()) }) }) diff --git a/felix/calc/calc_graph_test.go b/felix/calc/calc_graph_test.go index 07ba36734fa..88a82d4debb 100644 --- a/felix/calc/calc_graph_test.go +++ b/felix/calc/calc_graph_test.go @@ -15,8 +15,7 @@ package calc_test import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" @@ -43,11 +42,11 @@ var udpPort = proto.ServicePort{Port: 123, Protocol: "UDP"} var tcpPort = proto.ServicePort{Port: 321, Protocol: "TCP"} var _ = DescribeTable("Calculation graph pass-through tests", - func(key model.Key, input interface{}, expUpdate interface{}, expRemove interface{}) { + func(key model.Key, input any, expUpdate any, expRemove any) { // Create a calculation graph/event buffer combo. eb := NewEventSequencer(nil) - var messageReceived interface{} - eb.Callback = func(message interface{}) { + var messageReceived any + eb.Callback = func(message any) { log.WithField("message", message).Info("Received message") messageReceived = message } @@ -229,14 +228,14 @@ var _ = DescribeTable("Calculation graph pass-through tests", var _ = Describe("Host IP duplicate squashing test", func() { var eb *EventSequencer - var messagesReceived []interface{} + var messagesReceived []any var cg *dispatcher.Dispatcher BeforeEach(func() { // Create a calculation graph/event buffer combo. eb = NewEventSequencer(nil) messagesReceived = nil - eb.Callback = func(message interface{}) { + eb.Callback = func(message any) { log.WithField("message", message).Info("Received message") messagesReceived = append(messagesReceived, message) } diff --git a/felix/calc/calc_suite_test.go b/felix/calc/calc_suite_test.go index def7feaa1bd..1ec0246f76a 100644 --- a/felix/calc/calc_suite_test.go +++ b/felix/calc/calc_suite_test.go @@ -17,9 +17,8 @@ package calc_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestCalculationGraph(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/calc_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Calculation graph Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_calc_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/calc", suiteConfig, reporterConfig) } diff --git a/felix/calc/config_batcher.go b/felix/calc/config_batcher.go index 37be50fa63f..60517479c9b 100644 --- a/felix/calc/config_batcher.go +++ b/felix/calc/config_batcher.go @@ -15,6 +15,8 @@ package calc import ( + "maps" + log "github.com/sirupsen/logrus" "github.com/projectcalico/calico/felix/dispatcher" @@ -113,12 +115,8 @@ func (cb *ConfigBatcher) maybeSendCachedConfig() { cb.globalConfig, cb.hostConfig) globalConfigCopy := make(map[string]string) hostConfigCopy := make(map[string]string) - for k, v := range cb.globalConfig { - globalConfigCopy[k] = v - } - for k, v := range cb.hostConfig { - hostConfigCopy[k] = v - } + maps.Copy(globalConfigCopy, cb.globalConfig) + maps.Copy(hostConfigCopy, cb.hostConfig) if !cb.datastoreReady { cb.callbacks.OnDatastoreNotReady() } diff --git a/felix/calc/config_batcher_test.go b/felix/calc/config_batcher_test.go index d87dba678df..8b9c137cddc 100644 --- a/felix/calc/config_batcher_test.go +++ b/felix/calc/config_batcher_test.go @@ -15,7 +15,7 @@ package calc_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/projectcalico/calico/felix/calc" @@ -32,7 +32,7 @@ var _ = Describe("ConfigBatcher", func() { cb = NewConfigBatcher("myhost", recorder) }) - sendHostUpdate := func(name string, value interface{}) { + sendHostUpdate := func(name string, value any) { cb.OnUpdate(api.Update{ KVPair: model.KVPair{ Key: model.HostConfigKey{Name: name, Hostname: "myhost"}, @@ -40,7 +40,7 @@ var _ = Describe("ConfigBatcher", func() { }, }) } - sendGlobalUpdate := func(name string, value interface{}) { + sendGlobalUpdate := func(name string, value any) { cb.OnUpdate(api.Update{ KVPair: model.KVPair{ Key: model.GlobalConfigKey{Name: name}, @@ -48,7 +48,7 @@ var _ = Describe("ConfigBatcher", func() { }, }) } - sendReady := func(ready interface{}) { + sendReady := func(ready any) { cb.OnUpdate(api.Update{ KVPair: model.KVPair{ Key: model.ReadyFlagKey{}, diff --git a/felix/calc/dataplane_passthru.go b/felix/calc/dataplane_passthru.go index 13f8c6bd7d7..d4aa67e5674 100644 --- a/felix/calc/dataplane_passthru.go +++ b/felix/calc/dataplane_passthru.go @@ -21,7 +21,7 @@ import ( "github.com/projectcalico/calico/felix/dispatcher" "github.com/projectcalico/calico/felix/proto" - libv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/net" @@ -107,7 +107,7 @@ func (h *DataplanePassthru) OnUpdate(update api.Update) (filterOut bool) { } else { h.callbacks.OnServiceUpdate(kubernetesServiceToProto(update.Value.(*kapiv1.Service))) } - } else if key.Kind == libv3.KindNode { + } else if key.Kind == internalapi.KindNode { // Handle node resource to pass-through HostMetadataV6Update/HostMetadataV6Remove messages // with IPv6 node address updates. IPv4 updates are handled above my model.HostIPKey updates. log.WithField("update", update).Debug("Passing-through a Node IPv6 address update") @@ -120,7 +120,7 @@ func (h *DataplanePassthru) OnUpdate(update api.Update) (filterOut bool) { log.WithField("update", update).Debug("Passing-through Node remove") h.callbacks.OnHostMetadataRemove(hostname) } else { - node, _ := update.Value.(*libv3.Node) + node, _ := update.Value.(*internalapi.Node) log.WithField("update", update).Debug("Passing-through Node update") bgpIp4net := &net.IPNet{} // required for print in event sequencer bgpIp6net := &net.IPNet{} // required for print in event sequencer @@ -164,9 +164,9 @@ func (h *DataplanePassthru) OnUpdate(update api.Update) (filterOut bool) { // similar fallback as for IPv4, see how HostIPKey is generated in // libcalico-go/lib/backend/syncersv1/updateprocessors/felixnodeprocessor.go var ip *net.IP - ip, _ = cresources.FindNodeAddress(node, libv3.InternalIP, 6) + ip, _ = cresources.FindNodeAddress(node, internalapi.InternalIP, 6) if ip == nil { - ip, _ = cresources.FindNodeAddress(node, libv3.ExternalIP, 6) + ip, _ = cresources.FindNodeAddress(node, internalapi.ExternalIP, 6) } if ip != nil { oldIP := h.hostIPv6s[hostname] diff --git a/felix/calc/encapsulation_resolver.go b/felix/calc/encapsulation_resolver.go index 5218b97eab2..033b382b6b5 100644 --- a/felix/calc/encapsulation_resolver.go +++ b/felix/calc/encapsulation_resolver.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2022-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -169,8 +169,7 @@ func (c *EncapsulationCalculator) handleModelPool(p model.KVPair) error { c.removePool(poolKey) } else { pool, _ := p.Value.(*model.IPPool) - c.updatePool(poolKey, pool.IPIPMode != encap.Undefined, pool.VXLANMode != encap.Undefined) - + c.updatePool(poolKey, pool.IPIPMode != encap.Never, pool.VXLANMode != encap.Never) } return nil diff --git a/felix/calc/encapsulation_resolver_test.go b/felix/calc/encapsulation_resolver_test.go index a7994e9eac7..b97db30cf40 100644 --- a/felix/calc/encapsulation_resolver_test.go +++ b/felix/calc/encapsulation_resolver_test.go @@ -15,8 +15,7 @@ package calc import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -129,17 +128,17 @@ var _ = Describe("EncapsulationCalculator", func() { true, false, false), Entry("Model pool with no encap", nil, nil, - []model.KVPair{*getModelPool("192.168.1.0/24", encap.Undefined, encap.Undefined)}, + []model.KVPair{*getModelPool("192.168.1.0/24", encap.Never, encap.Never)}, nil, nil, false, false, false), Entry("Model pool with IPIP 'Always'", nil, nil, - []model.KVPair{*getModelPool("192.168.1.0/24", encap.Always, encap.Undefined)}, + []model.KVPair{*getModelPool("192.168.1.0/24", encap.Always, encap.Never)}, nil, nil, true, false, false), Entry("Model pool with VXLAN 'Always'", nil, nil, - []model.KVPair{*getModelPool("192.168.1.0/24", encap.Undefined, encap.Always)}, + []model.KVPair{*getModelPool("192.168.1.0/24", encap.Never, encap.Always)}, nil, nil, false, true, false), Entry("Model pool with IPIP 'CrossSubnet' and VXLAN 'CrossSubnet'", @@ -149,18 +148,18 @@ var _ = Describe("EncapsulationCalculator", func() { true, true, false), Entry("2 Model pools with mixed encaps", nil, nil, - []model.KVPair{*getModelPool("192.168.1.0/24", encap.Undefined, encap.Always), *getModelPool("192.168.2.0/24", encap.CrossSubnet, encap.Undefined)}, + []model.KVPair{*getModelPool("192.168.1.0/24", encap.Never, encap.Always), *getModelPool("192.168.2.0/24", encap.CrossSubnet, encap.Never)}, nil, nil, true, true, false), Entry("2 Model pools with mixed encaps, then remove one pool", nil, nil, - []model.KVPair{*getModelPool("192.168.1.0/24", encap.Undefined, encap.Always), *getModelPool("192.168.2.0/24", encap.CrossSubnet, encap.Undefined)}, + []model.KVPair{*getModelPool("192.168.1.0/24", encap.Never, encap.Always), *getModelPool("192.168.2.0/24", encap.CrossSubnet, encap.Never)}, []string{"192.168.2.0/24"}, nil, false, true, false), Entry("Initialize with initPools, update one Model pool and remove another", nil, nil, - []model.KVPair{*getModelPool("192.168.1.0/24", encap.Always, encap.Undefined)}, + []model.KVPair{*getModelPool("192.168.1.0/24", encap.Always, encap.Never)}, []string{"192.168.2.0/24"}, &model.KVPairList{ KVPairs: []*model.KVPair{ @@ -214,7 +213,7 @@ var _ = Describe("EncapsulationCalculator", func() { Entry("Both IPIP and VXLAN false in FelixConfig with mixed pools", &f, &f, []model.KVPair{*getAPIPool("192.168.1.0/24", apiv3.IPIPModeNever, apiv3.VXLANModeAlways), *getAPIPool("192.168.2.0/24", apiv3.IPIPModeCrossSubnet, apiv3.VXLANModeNever)}, - []model.KVPair{*getModelPool("192.168.3.0/24", encap.Undefined, encap.Always), *getModelPool("192.168.4.0/24", encap.CrossSubnet, encap.Undefined)}, + []model.KVPair{*getModelPool("192.168.3.0/24", encap.Never, encap.Always), *getModelPool("192.168.4.0/24", encap.CrossSubnet, encap.Never)}, false, false, false), Entry("Mixed VXLAN (ipv4 enabled, ipv6 disabled)", &f, &t, @@ -278,14 +277,14 @@ var _ = Describe("EncapsulationResolver", func() { It("should not send encapUpdates when adding pools with IPIP encap", func() { _, cidr, err := net.ParseCIDR("192.168.1.0/24") Expect(err).To(Not(HaveOccurred())) - encapsulationResolver.OnPoolUpdate(addPoolUpdate(*cidr, encap.Always, encap.Undefined)) + encapsulationResolver.OnPoolUpdate(addPoolUpdate(*cidr, encap.Always, encap.Never)) Expect(callbacks.encapUpdates).To(BeNil()) }) It("should not send encapUpdates when adding pools with VXLAN encap", func() { _, cidr, err := net.ParseCIDR("192.168.1.0/24") Expect(err).To(Not(HaveOccurred())) - encapsulationResolver.OnPoolUpdate(addPoolUpdate(*cidr, encap.Undefined, encap.CrossSubnet)) + encapsulationResolver.OnPoolUpdate(addPoolUpdate(*cidr, encap.Never, encap.CrossSubnet)) Expect(callbacks.encapUpdates).To(BeNil()) }) It("should not send encapUpdates when adding and removing pools", func() { @@ -310,7 +309,7 @@ var _ = Describe("EncapsulationResolver", func() { It("should send encapUpdates when adding pools with IPIP encap", func() { _, cidr, err := net.ParseCIDR("192.168.1.0/24") Expect(err).To(Not(HaveOccurred())) - encapsulationResolver.OnPoolUpdate(addPoolUpdate(*cidr, encap.Always, encap.Undefined)) + encapsulationResolver.OnPoolUpdate(addPoolUpdate(*cidr, encap.Always, encap.Never)) Expect(callbacks.encapUpdates).To(Equal( []*proto.Encapsulation{ {IpipEnabled: false, VxlanEnabled: false}, @@ -320,7 +319,7 @@ var _ = Describe("EncapsulationResolver", func() { It("should send encapUpdates when adding pools with VXLAN encap", func() { _, cidr, err := net.ParseCIDR("192.168.1.0/24") Expect(err).To(Not(HaveOccurred())) - encapsulationResolver.OnPoolUpdate(addPoolUpdate(*cidr, encap.Undefined, encap.CrossSubnet)) + encapsulationResolver.OnPoolUpdate(addPoolUpdate(*cidr, encap.Never, encap.CrossSubnet)) Expect(callbacks.encapUpdates).To(Equal( []*proto.Encapsulation{ {IpipEnabled: false, VxlanEnabled: false}, @@ -338,7 +337,7 @@ var _ = Describe("EncapsulationResolver", func() { _, cidr2, err := net.ParseCIDR("192.168.2.0/24") Expect(err).To(Not(HaveOccurred())) - encapsulationResolver.OnPoolUpdate(addPoolUpdate(*cidr2, encap.Undefined, encap.Always)) + encapsulationResolver.OnPoolUpdate(addPoolUpdate(*cidr2, encap.Never, encap.Always)) Expect(callbacks.encapUpdates).To(Equal( []*proto.Encapsulation{ {IpipEnabled: false, VxlanEnabled: false}, @@ -372,7 +371,7 @@ var _ = Describe("EncapsulationResolver", func() { It("should not send encapUpdates when adding pools before inSync, but should right after", func() { _, cidr, err := net.ParseCIDR("192.168.1.0/24") Expect(err).To(Not(HaveOccurred())) - encapsulationResolver.OnPoolUpdate(addPoolUpdate(*cidr, encap.Always, encap.Undefined)) + encapsulationResolver.OnPoolUpdate(addPoolUpdate(*cidr, encap.Always, encap.Never)) Expect(callbacks.encapUpdates).To(BeNil()) encapsulationResolver.OnStatusUpdate(api.InSync) diff --git a/felix/calc/endpoint_lookup_cache.go b/felix/calc/endpoint_lookup_cache.go index c0c277f7d3a..a7790310a41 100644 --- a/felix/calc/endpoint_lookup_cache.go +++ b/felix/calc/endpoint_lookup_cache.go @@ -31,10 +31,9 @@ import ( "github.com/projectcalico/calico/felix/rules" "github.com/projectcalico/calico/felix/stringutils" "github.com/projectcalico/calico/lib/std/uniquelabels" - v3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/names" "github.com/projectcalico/calico/libcalico-go/lib/set" ) @@ -98,8 +97,6 @@ type EndpointData interface { type endpointData interface { EndpointData allIPs() [][16]byte - isMarkedToBeDeleted() bool - setMarkedToBeDeleted(b bool) } type MatchData struct { @@ -152,9 +149,12 @@ type EndpointLookupsCache struct { endpointDeletionTimers map[model.Key]*time.Timer + // Map to track which endpoints are marked for deletion + markedForDeletion map[model.EndpointKey]bool + // Node relationship data. // TODO(rlb): We should just treat this as an endpoint - nodes map[string]v3.NodeSpec + nodes map[string]internalapi.NodeSpec nodeIPToNames map[[16]byte][]string deletionDelay time.Duration } @@ -176,8 +176,9 @@ func NewEndpointLookupsCache(opts ...EndpointLookupsCacheOption) *EndpointLookup remoteEndpointData: map[model.EndpointKey]*RemoteEndpointData{}, endpointDeletionTimers: map[model.Key]*time.Timer{}, + markedForDeletion: map[model.EndpointKey]bool{}, nodeIPToNames: make(map[[16]byte][]string), - nodes: make(map[string]v3.NodeSpec), + nodes: make(map[string]internalapi.NodeSpec), deletionDelay: endpointDataDeletionDelay, } @@ -202,7 +203,7 @@ func (ec *EndpointLookupsCache) RegisterWith( // and corresponding IP address relationship. The difference between this handler and the OnUpdate // handler (below) is this method records tier information for local endpoints while this information // is ignored for remote endpoints. -func (ec *EndpointLookupsCache) OnEndpointTierUpdate(key model.EndpointKey, ep model.Endpoint, peerData *EndpointBGPPeer, filteredTiers []TierInfo) { +func (ec *EndpointLookupsCache) OnEndpointTierUpdate(key model.EndpointKey, ep model.Endpoint, _ []EndpointComputedData, _ *EndpointBGPPeer, filteredTiers []TierInfo) { if ep == nil { log.Debugf("Queueing deletion of local endpoint data %v", key) ec.removeEndpointWithDelay(key) @@ -243,18 +244,12 @@ func (ec *EndpointLookupsCache) CreateLocalEndpointData(key model.EndpointKey, e var hasIngress, hasEgress bool for _, pol := range ti.OrderedPolicies { - namespace, tier, name, err := names.DeconstructPolicyName(pol.Key.Name) - if err != nil { - log.WithError(err).Error("Unable to parse policy name") - continue - } if pol.GovernsIngress() { // Add an ingress tier default action lookup. - rid := NewRuleID(tier, name, namespace, RuleIndexTierDefaultAction, - rules.RuleDirIngress, tierDefaultAction) + rid := NewRuleID(pol.Key.Kind, ti.Name, pol.Key.Name, pol.Key.Namespace, RuleIndexTierDefaultAction, rules.RuleDirIngress, tierDefaultAction) ed.Ingress.PolicyMatches[rid.PolicyID] = policyMatchIdxIngress - if model.PolicyIsStaged(pol.Key.Name) { + if model.KindIsStaged(pol.Key.Kind) { // Increment the match index. We don't do this for non-staged policies because they replace the // subsequent staged policy in the results. policyMatchIdxIngress++ @@ -266,11 +261,10 @@ func (ec *EndpointLookupsCache) CreateLocalEndpointData(key model.EndpointKey, e } if pol.GovernsEgress() { // Add an egress tier default action lookup. - rid := NewRuleID(tier, name, namespace, RuleIndexTierDefaultAction, - rules.RuleDirEgress, tierDefaultAction) + rid := NewRuleID(pol.Key.Kind, ti.Name, pol.Key.Name, pol.Key.Namespace, RuleIndexTierDefaultAction, rules.RuleDirEgress, tierDefaultAction) ed.Egress.PolicyMatches[rid.PolicyID] = policyMatchIdxEgress - if model.PolicyIsStaged(pol.Key.Name) { + if model.KindIsStaged(pol.Key.Kind) { // Increment the match index. We don't do this for non-staged policies because they replace the // subsequent staged policy in the results. policyMatchIdxEgress++ @@ -304,13 +298,12 @@ func (ec *EndpointLookupsCache) CreateLocalEndpointData(key model.EndpointKey, e func CalculateCommonEndpointData(key model.EndpointKey, ep model.Endpoint) CommonEndpointData { generateName, ips := extractEndpointInfo(ep) - commonData := CommonEndpointData{ + return CommonEndpointData{ key: key, labels: ep.GetLabels(), generateName: generateName, ips: ips, } - return commonData } func extractEndpointInfo(ep model.Endpoint) (string, [][16]byte) { @@ -362,11 +355,11 @@ func (ec *EndpointLookupsCache) OnResourceUpdate(update api.Update) (_ bool) { switch k := update.Key.(type) { case model.ResourceKey: switch k.Kind { - case v3.KindNode: + case internalapi.KindNode: if update.Value == nil { ec.removeNode(k.Name) } else { - ec.addOrUpdateNode(k.Name, update.Value.(*v3.Node)) + ec.addOrUpdateNode(k.Name, update.Value.(*internalapi.Node)) } default: log.Tracef("Ignoring update for resource: %s", k) @@ -403,39 +396,39 @@ func (ec *EndpointLookupsCache) addOrUpdateEndpoint(key model.EndpointKey, incom // any IP that shouldn't be discarded. ipsToUpdate := set.New[[16]byte]() for _, ip := range ipsOfIncomingEndpoint { - // If this is an already existing IP, then remove it, + // If this is an already existing IP, then remove it if ipsToRemove.Contains(ip) { ipsToRemove.Discard(ip) } - // capture all incoming IPs as both new and existing ip mappins + // capture all incoming IPs as both new and existing ip mappings // need to be updated with incoming endpoint data ipsToUpdate.Add(ip) } // update endpoint data lookup by key - // if there was a previous endpoint with the same key to be deleted, + // If there was a previous endpoint with the same key to be deleted, // stop the deletion timer and let the entries be updated with incomingEndpointData deletionTimer, isEndpointSetToBePrevDeleted := ec.endpointDeletionTimers[key] if endpointAlreadyExists && isEndpointSetToBePrevDeleted { deletionTimer.Stop() delete(ec.endpointDeletionTimers, key) + // Remove deletion marking since we're updating the endpoint + delete(ec.markedForDeletion, key) } ec.storeEndpoint(key, incomingEndpointData) // update endpoint data lookup by ips - ipsToUpdate.Iter(func(newIP [16]byte) error { + for newIP := range ipsToUpdate.All() { ec.updateIPToEndpointMapping(newIP, incomingEndpointData) - return nil - }) + } - ipsToRemove.Iter( - func(ip [16]byte) error { - ec.removeEndpointDataIpMapping(key, ip) - return set.RemoveItem - }) + for ip := range ipsToRemove.All() { + ec.removeEndpointDataIpMapping(key, ip) + ipsToRemove.Discard(ip) + } ec.reportEndpointCacheMetrics() } @@ -499,7 +492,7 @@ func (ec *EndpointLookupsCache) updateIPToEndpointMapping(ip [16]byte, incomingE isExistingEp := false i := 0 for i < len(existingEpDataForIp) { - if existingEpDataForIp[i].isMarkedToBeDeleted() { + if epKey, ok := existingEpDataForIp[i].Key().(model.EndpointKey); ok && ec.markedForDeletion[epKey] { existingEpDataForIp = removeEndpointDataFromSlice(existingEpDataForIp, i) continue } @@ -529,14 +522,14 @@ func removeEndpointDataFromSlice(s []endpointData, i int) []endpointData { // removeEndpointWithDelay marks all EndpointData referenced by the // (key model.Key) and delegates the removeEndpoint to another // goroutine that will be called after endpointDataTTLAfterMarkedAsRemoved -// has passed. ipToEndpointDeletionTimers is used to track the all the timers +// has passed. ipToEndpointDeletionTimers is used to track all the timers // created for tentatively deleted endpoints as they are accessed by add/update // operations. func (ec *EndpointLookupsCache) removeEndpointWithDelay(key model.EndpointKey) { ec.epMutex.Lock() defer ec.epMutex.Unlock() - ed, endpointExists := ec.lookupEndpoint(key) + _, endpointExists := ec.lookupEndpoint(key) if !endpointExists { // for performance improvement - as time.AfterFunc creates a go routine return @@ -548,13 +541,13 @@ func (ec *EndpointLookupsCache) removeEndpointWithDelay(key model.EndpointKey) { } // mark the endpoint to be deleted and attach a timer to delegate the actual deletion - ed.setMarkedToBeDeleted(true) + ec.markedForDeletion[key] = true endpointDeletionTimer := time.AfterFunc(ec.deletionDelay, func() { ec.removeEndpoint(key) }) ec.endpointDeletionTimers[key] = endpointDeletionTimer } -// removeEndpoint removes all EndpointData markedToBeDeleted from the slice +// removeEndpoint removes all EndpointData that were previously marked for deletion // captures all IPs and removes all correspondoing IP to EndpointData mapping as well. func (ec *EndpointLookupsCache) removeEndpoint(key model.EndpointKey) { ec.epMutex.Lock() @@ -568,20 +561,20 @@ func (ec *EndpointLookupsCache) removeEndpoint(key model.EndpointKey) { // If the endpoint has not been marked for deletion ignore it as it may have been // updated before the deletion timer has been triggered. - if !currentEndpointData.isMarkedToBeDeleted() { + if !ec.markedForDeletion[key] { return } // Collect the IPs of this endpoint as we will need to remove it from the IP mapping. ipsMarkedAsDeleted := set.From(currentEndpointData.allIPs()...) - ipsMarkedAsDeleted.Iter(func(ip [16]byte) error { + for ip := range ipsMarkedAsDeleted.All() { ec.removeEndpointDataIpMapping(key, ip) - return nil - }) + } delete(ec.localEndpointData, key) delete(ec.remoteEndpointData, key) delete(ec.endpointDeletionTimers, key) + delete(ec.markedForDeletion, key) ec.reportEndpointCacheMetrics() } @@ -647,8 +640,8 @@ func (ec *EndpointLookupsCache) GetAllEndpointData() []EndpointData { defer ec.epMutex.RUnlock() allEds := []EndpointData{} - for _, ed := range ec.allEndpoints() { - if ed.isMarkedToBeDeleted() { + for key, ed := range ec.allEndpoints() { + if ec.markedForDeletion[key] { continue } allEds = append(allEds, ed) @@ -656,6 +649,27 @@ func (ec *EndpointLookupsCache) GetAllEndpointData() []EndpointData { return allEds } +// IsEndpointDeleted returns whether the given endpoint is marked for deletion. +func (ec *EndpointLookupsCache) IsEndpointDeleted(ep EndpointData) bool { + ec.epMutex.RLock() + defer ec.epMutex.RUnlock() + + if key, ok := ep.Key().(model.EndpointKey); ok { + return ec.markedForDeletion[key] + } + return false +} + +// MarkEndpointForDeletion marks the given endpoint for deletion. +func (ec *EndpointLookupsCache) MarkEndpointForDeletion(ep EndpointData) { + ec.epMutex.Lock() + defer ec.epMutex.Unlock() + + if key, ok := ep.Key().(model.EndpointKey); ok { + ec.markedForDeletion[key] = true + } +} + // reportEndpointCacheMetrics reports endpoint cache performance metrics to prometheus func (ec *EndpointLookupsCache) reportEndpointCacheMetrics() { gaugeEndpointCacheLength.Set(float64(len(ec.remoteEndpointData) + len(ec.localEndpointData))) @@ -755,15 +769,14 @@ func (ec *EndpointLookupsCache) DumpEndpoints() string { ips := set.New[[16]byte]() deleted := "deleted" - if !endpointData.isMarkedToBeDeleted() { + if !ec.markedForDeletion[key] { ips.AddAll(endpointData.allIPs()) deleted = "" } - ips.Iter(func(ip [16]byte) error { + for ip := range ips.All() { ipStr = append(ipStr, net.IP(ip[:16]).String()) - return nil - }) + } lines = append(lines, endpointName(key)+": "+strings.Join(ipStr, ",")+deleted) } @@ -771,7 +784,7 @@ func (ec *EndpointLookupsCache) DumpEndpoints() string { } // addOrUpdateNode tracks IP to node mappings. -func (ec *EndpointLookupsCache) addOrUpdateNode(name string, node *v3.Node) { +func (ec *EndpointLookupsCache) addOrUpdateNode(name string, node *internalapi.Node) { ec.epMutex.Lock() defer ec.epMutex.Unlock() @@ -801,7 +814,7 @@ func (ec *EndpointLookupsCache) removeNode(name string) { } // handleNode handles the mappings for a node. The supplied operator is used to either add or remove the mappings. -func (ec *EndpointLookupsCache) handleNode(name string, node v3.NodeSpec, nodeOp func(name string, ip [16]byte)) { +func (ec *EndpointLookupsCache) handleNode(name string, node internalapi.NodeSpec, nodeOp func(name string, ip [16]byte)) { if node.BGP != nil && node.BGP.IPv4Address != "" { if nodeIP, ok := IPStringToArray(node.BGP.IPv4Address); ok { nodeOp(name, nodeIP) @@ -857,10 +870,6 @@ type CommonEndpointData struct { // contents of the GenerateName field from the WorkloadEndpoint, which is // used by the collector for determining the aggregation name. generateName string - - // used for deleting an EndpointData, to delegate the actual - // deletion endpointDataDeletionDelay later - markedToBeDeleted bool } func (e *CommonEndpointData) Key() model.Key { @@ -888,14 +897,6 @@ func (e *CommonEndpointData) GenerateName() string { return e.generateName } -func (e *CommonEndpointData) isMarkedToBeDeleted() bool { - return e.markedToBeDeleted -} - -func (e *CommonEndpointData) setMarkedToBeDeleted(b bool) { - e.markedToBeDeleted = b -} - // LocalEndpointData is the cache entry struct for local endpoints. We store // additional information for local endpoints. Namely the locally-active // policy. @@ -908,8 +909,10 @@ type LocalEndpointData struct { Egress *MatchData } -var _ endpointData = &LocalEndpointData{} -var _ endpointData = &RemoteEndpointData{} +var ( + _ endpointData = &LocalEndpointData{} + _ endpointData = &RemoteEndpointData{} +) func (ed *LocalEndpointData) IsLocal() bool { return true diff --git a/felix/calc/endpoint_lookup_cache_test.go b/felix/calc/endpoint_lookup_cache_test.go index 32323e825ad..5d7fc36df19 100644 --- a/felix/calc/endpoint_lookup_cache_test.go +++ b/felix/calc/endpoint_lookup_cache_test.go @@ -17,17 +17,19 @@ package calc_test import ( "net" "regexp" + "testing" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" . "github.com/projectcalico/calico/felix/calc" "github.com/projectcalico/calico/felix/rules" - v3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" + cnet "github.com/projectcalico/calico/libcalico-go/lib/net" ) const ( @@ -115,7 +117,6 @@ var _ = Describe("EndpointLookupsCache tests: endpoints", func() { endpoints = ec.GetAllEndpointData() Expect(len(endpoints)).To(Equal(0)) Expect(endpoints).NotTo(ConsistOf(ed)) - }, Entry("remote WEP1 IPv4", remoteWlEpKey1, &remoteWlEp1DualStack, remoteWlEp1DualStack.IPv4Nets[0].IP), Entry("remote WEP1 IPv6", remoteWlEpKey1, &remoteWlEp1DualStack, remoteWlEp1DualStack.IPv6Nets[0].IP), @@ -188,31 +189,34 @@ var _ = Describe("EndpointLookupsCache tests: endpoints", func() { It("should process local endpoints correctly with no staged policies and one tier per ingress and egress", func() { By("adding a host endpoint with ingress policies in tier1 and egress policies in tier default") - p1k := model.PolicyKey{Name: "tier1.pol1"} + p1k := model.PolicyKey{Name: "tier1.pol1", Kind: v3.KindGlobalNetworkPolicy} p1 := &model.Policy{ + Tier: "tier1", Order: &float1_0, Types: []string{"ingress"}, InboundRules: []model.Rule{{Action: "next-tier"}, {Action: "allow"}, {Action: "deny"}}, } - p1id := PolicyID{Name: "pol1", Tier: "tier1"} + p1id := PolicyID{Name: "tier1.pol1", Kind: v3.KindGlobalNetworkPolicy} p1Metadata := ExtractPolicyMetadata(p1) - p2k := model.PolicyKey{Name: "ns1/default.pol2"} + p2k := model.PolicyKey{Name: "pol2", Namespace: "ns1", Kind: v3.KindNetworkPolicy} p2 := &model.Policy{ + Tier: "default", Namespace: "ns1", Order: &float1_0, Types: []string{"egress"}, } - p2id := PolicyID{Name: "pol2", Tier: "default", Namespace: "ns1"} + p2id := PolicyID{Name: "pol2", Namespace: "ns1", Kind: v3.KindNetworkPolicy} p2Metadata := ExtractPolicyMetadata(p2) - p3k := model.PolicyKey{Name: "ns1/default.pol3"} + p3k := model.PolicyKey{Name: "pol3", Namespace: "ns1", Kind: v3.KindNetworkPolicy} p3 := &model.Policy{ + Tier: "default", Namespace: "ns1", Order: &float2_0, Types: []string{"egress"}, } - p3id := PolicyID{Name: "pol3", Tier: "default", Namespace: "ns1"} + p3id := PolicyID{Name: "pol3", Namespace: "ns1", Kind: v3.KindNetworkPolicy} p3Metadata := ExtractPolicyMetadata(p3) t1 := NewTierInfo("tier1") @@ -248,7 +252,7 @@ var _ = Describe("EndpointLookupsCache tests: endpoints", func() { Expect(ed.IngressMatchData().TierData).To(HaveKey("tier1")) Expect(ed.IngressMatchData().TierData["tier1"]).ToNot(BeNil()) Expect(ed.IngressMatchData().TierData["tier1"].TierDefaultActionRuleID).To(Equal( - NewRuleID("tier1", "pol1", "", RuleIndexTierDefaultAction, rules.RuleDirIngress, rules.RuleActionDeny))) + NewRuleID(v3.KindGlobalNetworkPolicy, "tier1", "tier1.pol1", "", RuleIndexTierDefaultAction, rules.RuleDirIngress, rules.RuleActionDeny))) Expect(ed.IngressMatchData().TierData["tier1"].EndOfTierMatchIndex).To(Equal(0)) By("checking compiled egress data") @@ -263,7 +267,7 @@ var _ = Describe("EndpointLookupsCache tests: endpoints", func() { Expect(ed.EgressMatchData().TierData).To(HaveKey("default")) Expect(ed.EgressMatchData().TierData["default"]).ToNot(BeNil()) Expect(ed.EgressMatchData().TierData["default"].TierDefaultActionRuleID).To(Equal( - NewRuleID("default", "pol3", "ns1", RuleIndexTierDefaultAction, rules.RuleDirEgress, rules.RuleActionDeny))) + NewRuleID(v3.KindNetworkPolicy, "default", "pol3", "ns1", RuleIndexTierDefaultAction, rules.RuleDirEgress, rules.RuleActionDeny))) Expect(ed.EgressMatchData().TierData["default"].EndOfTierMatchIndex).To(Equal(0)) }) @@ -278,38 +282,42 @@ var _ = Describe("EndpointLookupsCache tests: endpoints", func() { } By("adding a workloadendpoint with mixed staged/non-staged policies in tier1") - sp1k := model.PolicyKey{Name: "staged:tier1.pol1"} + sp1k := model.PolicyKey{Name: "pol1", Kind: v3.KindStagedGlobalNetworkPolicy} sp1 := &model.Policy{ + Tier: "tier1", Order: &float1_0, Types: []string{dir}, } - sp1id := PolicyID{Name: "staged:pol1", Tier: "tier1"} + sp1id := PolicyID{Name: "pol1", Kind: v3.KindStagedGlobalNetworkPolicy} sp1Metadata := ExtractPolicyMetadata(sp1) - p1k := model.PolicyKey{Name: "tier1.pol1"} + p1k := model.PolicyKey{Name: "pol1", Kind: v3.KindGlobalNetworkPolicy} p1 := &model.Policy{ + Tier: "tier1", Order: &float1_0, Types: []string{dir}, } - p1id := PolicyID{Name: "pol1", Tier: "tier1"} + p1id := PolicyID{Name: "pol1", Kind: v3.KindGlobalNetworkPolicy} p1Metadata := ExtractPolicyMetadata(p1) - sp2k := model.PolicyKey{Name: "ns1/staged:tier1.pol2"} + sp2k := model.PolicyKey{Name: "pol2", Namespace: "ns1", Kind: v3.KindStagedNetworkPolicy} sp2 := &model.Policy{ + Tier: "tier1", Namespace: "ns1", Order: &float2_0, Types: []string{dir}, } - sp2id := PolicyID{Name: "staged:pol2", Tier: "tier1", Namespace: "ns1"} + sp2id := PolicyID{Name: "pol2", Namespace: "ns1", Kind: v3.KindStagedNetworkPolicy} sp2Metadata := ExtractPolicyMetadata(sp2) - p2k := model.PolicyKey{Name: "ns1/tier1.pol2"} + p2k := model.PolicyKey{Name: "pol2", Namespace: "ns1", Kind: v3.KindNetworkPolicy} p2 := &model.Policy{ + Tier: "tier1", Namespace: "ns1", Order: &float2_0, Types: []string{dir}, } - p2id := PolicyID{Name: "pol2", Tier: "tier1", Namespace: "ns1"} + p2id := PolicyID{Name: "pol2", Namespace: "ns1", Kind: v3.KindNetworkPolicy} p2Metadata := ExtractPolicyMetadata(p2) t1 := NewTierInfo("tier1") @@ -323,20 +331,22 @@ var _ = Describe("EndpointLookupsCache tests: endpoints", func() { } By("and adding staged policies in tier default") - sp3k := model.PolicyKey{Name: "ns2/staged:knp.default.pol3"} + sp3k := model.PolicyKey{Name: "knp.default.pol3", Namespace: "ns2", Kind: v3.KindStagedKubernetesNetworkPolicy} sp3 := &model.Policy{ + Tier: "default", Order: &float1_0, Types: []string{dir}, } - sp3id := PolicyID{Name: "staged:knp.default.pol3", Tier: "default", Namespace: "ns2"} + sp3id := PolicyID{Name: "knp.default.pol3", Namespace: "ns2", Kind: v3.KindStagedKubernetesNetworkPolicy} sp3Metadata := ExtractPolicyMetadata(sp3) - sp4k := model.PolicyKey{Name: "staged:default.pol4"} + sp4k := model.PolicyKey{Name: "pol4", Kind: v3.KindStagedGlobalNetworkPolicy} sp4 := &model.Policy{ + Tier: "default", Order: &float2_0, Types: []string{dir}, } - sp4id := PolicyID{Name: "staged:pol4", Tier: "default"} + sp4id := PolicyID{Name: "pol4", Kind: v3.KindStagedGlobalNetworkPolicy} sp4Metadata := ExtractPolicyMetadata(sp4) td := NewTierInfo("default") @@ -400,7 +410,7 @@ var _ = Describe("EndpointLookupsCache tests: endpoints", func() { // Tier contains enforced policy, so has a real implicit drop rule ID. Expect(data.TierData["tier1"].EndOfTierMatchIndex).To(Equal(2)) Expect(data.TierData["tier1"].TierDefaultActionRuleID).To(Equal( - NewRuleID("tier1", "pol2", "ns1", RuleIndexTierDefaultAction, ruleDir, rules.RuleActionDeny))) + NewRuleID(v3.KindNetworkPolicy, "tier1", "pol2", "ns1", RuleIndexTierDefaultAction, ruleDir, rules.RuleActionDeny))) By("checking compiled match data for default tier") // Staged policy increments the next index. @@ -427,7 +437,7 @@ var _ = Describe("EndpointLookupsCache tests: endpoints", func() { var _ = Describe("EndpointLookupCache tests: Node lookup", func() { var elc *EndpointLookupsCache var updates []api.Update - //localIP, _ := IPStringToArray("127.0.0.1") + // localIP, _ := IPStringToArray("127.0.0.1") nodeIPStr := "100.0.0.0/26" nodeIP, _ := IPStringToArray(nodeIPStr) nodeIP2Str := "100.0.0.2/26" @@ -443,10 +453,10 @@ var _ = Describe("EndpointLookupCache tests: Node lookup", func() { By("adding a node and a service") updates = []api.Update{{ KVPair: model.KVPair{ - Key: model.ResourceKey{Kind: v3.KindNode, Name: "node1"}, - Value: &v3.Node{ - Spec: v3.NodeSpec{ - BGP: &v3.NodeBGPSpec{ + Key: model.ResourceKey{Kind: internalapi.KindNode, Name: "node1"}, + Value: &internalapi.Node{ + Spec: internalapi.NodeSpec{ + BGP: &internalapi.NodeBGPSpec{ IPv4Address: nodeIPStr, }, }, @@ -486,10 +496,10 @@ var _ = Describe("EndpointLookupCache tests: Node lookup", func() { By("updating the node and adding a new node") updates = []api.Update{{ KVPair: model.KVPair{ - Key: model.ResourceKey{Kind: v3.KindNode, Name: "node1"}, - Value: &v3.Node{ - Spec: v3.NodeSpec{ - BGP: &v3.NodeBGPSpec{ + Key: model.ResourceKey{Kind: internalapi.KindNode, Name: "node1"}, + Value: &internalapi.Node{ + Spec: internalapi.NodeSpec{ + BGP: &internalapi.NodeBGPSpec{ IPv4Address: nodeIPStr, }, IPv4VXLANTunnelAddr: nodeIPStr, @@ -500,15 +510,15 @@ var _ = Describe("EndpointLookupCache tests: Node lookup", func() { }, { // 2nd node has duplicate main IP and also has other interface IPs assigned KVPair: model.KVPair{ - Key: model.ResourceKey{Kind: v3.KindNode, Name: "node2"}, - Value: &v3.Node{ - Spec: v3.NodeSpec{ - BGP: &v3.NodeBGPSpec{ + Key: model.ResourceKey{Kind: internalapi.KindNode, Name: "node2"}, + Value: &internalapi.Node{ + Spec: internalapi.NodeSpec{ + BGP: &internalapi.NodeBGPSpec{ IPv4Address: nodeIPStr, IPv4IPIPTunnelAddr: nodeIP2Str, }, IPv4VXLANTunnelAddr: nodeIP3Str, - Wireguard: &v3.NodeWireguardSpec{ + Wireguard: &internalapi.NodeWireguardSpec{ InterfaceIPv4Address: nodeIP4Str, }, }, @@ -547,15 +557,15 @@ var _ = Describe("EndpointLookupCache tests: Node lookup", func() { By("Reconfiguring node 2") elc.OnResourceUpdate(api.Update{ KVPair: model.KVPair{ - Key: model.ResourceKey{Kind: v3.KindNode, Name: "node2"}, - Value: &v3.Node{ - Spec: v3.NodeSpec{ - BGP: &v3.NodeBGPSpec{ + Key: model.ResourceKey{Kind: internalapi.KindNode, Name: "node2"}, + Value: &internalapi.Node{ + Spec: internalapi.NodeSpec{ + BGP: &internalapi.NodeBGPSpec{ IPv4Address: nodeIP2Str, IPv4IPIPTunnelAddr: nodeIP2Str, }, IPv4VXLANTunnelAddr: nodeIP3Str, - Wireguard: &v3.NodeWireguardSpec{ + Wireguard: &internalapi.NodeWireguardSpec{ InterfaceIPv4Address: nodeIP4Str, }, }, @@ -574,10 +584,10 @@ var _ = Describe("EndpointLookupCache tests: Node lookup", func() { By("Reconfiguring node 1 to remove the main IP") elc.OnResourceUpdate(api.Update{ KVPair: model.KVPair{ - Key: model.ResourceKey{Kind: v3.KindNode, Name: "node1"}, - Value: &v3.Node{ - Spec: v3.NodeSpec{ - BGP: &v3.NodeBGPSpec{ + Key: model.ResourceKey{Kind: internalapi.KindNode, Name: "node1"}, + Value: &internalapi.Node{ + Spec: internalapi.NodeSpec{ + BGP: &internalapi.NodeBGPSpec{ IPv4IPIPTunnelAddr: nodeIPStr, }, }, @@ -593,9 +603,9 @@ var _ = Describe("EndpointLookupCache tests: Node lookup", func() { By("Reconfiguring node 1 to remove the remaining IP") elc.OnResourceUpdate(api.Update{ KVPair: model.KVPair{ - Key: model.ResourceKey{Kind: v3.KindNode, Name: "node1"}, - Value: &v3.Node{ - Spec: v3.NodeSpec{}, + Key: model.ResourceKey{Kind: internalapi.KindNode, Name: "node1"}, + Value: &internalapi.Node{ + Spec: internalapi.NodeSpec{}, }, }, UpdateType: api.UpdateTypeKVUpdated, @@ -609,6 +619,70 @@ var _ = Describe("EndpointLookupCache tests: Node lookup", func() { }) }) +func TestIsEndpointDeleted(t *testing.T) { + // Create a cache with a short deletion delay for testing + cache := NewEndpointLookupsCache(WithDeletionDelay(50 * time.Millisecond)) + + // Create a test endpoint key + key := model.WorkloadEndpointKey{ + Hostname: "test-node", + OrchestratorID: "cni", + WorkloadID: "test-workload", + EndpointID: "eth0", + } + + // Create endpoint data using the same pattern as existing tests + ip := net.ParseIP("10.0.0.1") + cidr := net.IPNet{ + IP: ip, + Mask: net.CIDRMask(32, 32), + } + + endpoint := &model.WorkloadEndpoint{ + State: "active", + Name: "test-endpoint", + ProfileIDs: []string{"test-profile"}, + IPv4Nets: []cnet.IPNet{{IPNet: cidr}}, + } + + // Add the endpoint to the cache using the correct API pattern + ed := cache.CreateLocalEndpointData(key, endpoint, newTierInfoSlice()) + cache.OnUpdate(api.Update{ + UpdateType: api.UpdateTypeKVNew, + KVPair: model.KVPair{ + Key: key, + Value: endpoint, + }, + }) + + // Check if the endpoint is deleted (should be false) + if cache.IsEndpointDeleted(ed) { + t.Error("Expected endpoint to not be deleted before deletion") + } + + // Remove the endpoint (this should mark it for deletion) + cache.OnUpdate(api.Update{ + UpdateType: api.UpdateTypeKVDeleted, + KVPair: model.KVPair{ + Key: key, + Value: nil, + }, + }) + + // Check if the endpoint is now marked as deleted (should be true) + if !cache.IsEndpointDeleted(ed) { + t.Error("Expected endpoint to be marked as deleted after deletion") + } + + // Wait for the deletion delay to pass + time.Sleep(100 * time.Millisecond) + + // Check if the endpoint is still marked as deleted (should be false as it's been cleaned up) + if cache.IsEndpointDeleted(ed) { + t.Error("Expected endpoint to not be marked as deleted after cleanup") + } +} + func newTierInfoSlice() []TierInfo { return nil } diff --git a/felix/calc/event_sequencer.go b/felix/calc/event_sequencer.go index 69a5b9260f3..1d78451e5d6 100644 --- a/felix/calc/event_sequencer.go +++ b/felix/calc/event_sequencer.go @@ -32,7 +32,7 @@ import ( "github.com/projectcalico/calico/libcalico-go/lib/set" ) -type EventHandler func(message interface{}) +type EventHandler func(message any) type configInterface interface { UpdateFrom(map[string]string, config.Source) (changed bool, err error) @@ -40,11 +40,19 @@ type configInterface interface { ToConfigUpdate() *proto.ConfigUpdate } +// Struct for additional data that feeds into proto.WorkloadEndpoint but is computed rather +// than stored on resource's database. +type EndpointComputedDataKind string +type EndpointComputedData interface { + ApplyTo(*proto.WorkloadEndpoint) +} + // EndpointUpdate contains information about updates applied to the endpoint. type endpointUpdate struct { - endpoint interface{} - peerData *EndpointBGPPeer - tierInfo []TierInfo + endpoint any + computedData []EndpointComputedData + peerData *EndpointBGPPeer + tierInfo []TierInfo } // EventSequencer buffers and coalesces updates from the calculation graph then flushes them @@ -313,10 +321,12 @@ func ParsedRulesToActivePolicyUpdate(key model.PolicyKey, rules *ParsedRules) *p } return &proto.ActivePolicyUpdate{ Id: &proto.PolicyID{ - Tier: key.Tier, - Name: key.Name, + Name: key.Name, + Namespace: key.Namespace, + Kind: key.Kind, }, Policy: &proto.Policy{ + Tier: rules.Tier, Namespace: rules.Namespace, InboundRules: parsedRulesToProtoRules( rules.InboundRules, @@ -342,16 +352,17 @@ func (buf *EventSequencer) OnPolicyInactive(key model.PolicyKey) { } func (buf *EventSequencer) flushPolicyDeletes() { - buf.pendingPolicyDeletes.Iter(func(item model.PolicyKey) error { + for item := range buf.pendingPolicyDeletes.All() { buf.Callback(&proto.ActivePolicyRemove{ Id: &proto.PolicyID{ - Tier: item.Tier, - Name: item.Name, + Name: item.Name, + Namespace: item.Namespace, + Kind: item.Kind, }, }) buf.sentPolicies.Discard(item) - return set.RemoveItem - }) + buf.pendingPolicyDeletes.Discard(item) + } } func (buf *EventSequencer) OnProfileActive(key model.ProfileRulesKey, rules *ParsedRules) { @@ -389,18 +400,18 @@ func (buf *EventSequencer) OnProfileInactive(key model.ProfileRulesKey) { } func (buf *EventSequencer) flushProfileDeletes() { - buf.pendingProfileDeletes.Iter(func(item model.ProfileRulesKey) error { + for item := range buf.pendingProfileDeletes.All() { buf.Callback(&proto.ActiveProfileRemove{ Id: &proto.ProfileID{ Name: item.Name, }, }) buf.sentProfiles.Discard(item) - return set.RemoveItem - }) + buf.pendingProfileDeletes.Discard(item) + } } -func ModelWorkloadEndpointToProto(ep *model.WorkloadEndpoint, peerData *EndpointBGPPeer, tiers []*proto.TierInfo) *proto.WorkloadEndpoint { +func ModelWorkloadEndpointToProto(ep *model.WorkloadEndpoint, computedData []EndpointComputedData, peerData *EndpointBGPPeer, tiers []*proto.TierInfo) *proto.WorkloadEndpoint { mac := "" if ep.Mac != nil { mac = ep.Mac.String() @@ -443,17 +454,17 @@ func ModelWorkloadEndpointToProto(ep *model.WorkloadEndpoint, peerData *Endpoint var skipRedir *proto.WorkloadBpfSkipRedir // BPF ingress redirect should be skipped for VM workloads and workloads that have ingress BW QoS configured - if isVMWorkload(ep.Labels) || (ep.QoSControls != nil && ep.QoSControls.IngressBandwidth > 0) { + if isVMWorkload(ep.Labels) || (ep.QoSControls != nil && (ep.QoSControls.IngressBandwidth > 0 || ep.QoSControls.IngressPacketRate > 0)) { skipRedir = &proto.WorkloadBpfSkipRedir{Ingress: true} } - if ep.QoSControls != nil && ep.QoSControls.EgressBandwidth > 0 { + if ep.QoSControls != nil && (ep.QoSControls.EgressBandwidth > 0 || ep.QoSControls.EgressPacketRate > 0) { if skipRedir == nil { skipRedir = &proto.WorkloadBpfSkipRedir{} } skipRedir.Egress = true } - return &proto.WorkloadEndpoint{ + wep := &proto.WorkloadEndpoint{ State: ep.State, Name: ep.Name, Mac: mac, @@ -470,6 +481,12 @@ func ModelWorkloadEndpointToProto(ep *model.WorkloadEndpoint, peerData *Endpoint SkipRedir: skipRedir, QosPolicies: qosPolicies, } + + for _, cd := range computedData { + cd.ApplyTo(wep) + } + + return wep } func ModelHostEndpointToProto(ep *model.HostEndpoint, tiers, untrackedTiers, preDNATTiers []*proto.TierInfo, forwardTiers []*proto.TierInfo) *proto.HostEndpoint { @@ -492,8 +509,10 @@ func ModelHostEndpointToProto(ep *model.HostEndpoint, tiers, untrackedTiers, pre } } -func (buf *EventSequencer) OnEndpointTierUpdate(endpointKey model.EndpointKey, +func (buf *EventSequencer) OnEndpointTierUpdate( + endpointKey model.EndpointKey, endpoint model.Endpoint, + computedData []EndpointComputedData, peerData *EndpointBGPPeer, filteredTiers []TierInfo, ) { @@ -508,9 +527,10 @@ func (buf *EventSequencer) OnEndpointTierUpdate(endpointKey model.EndpointKey, // Update. buf.pendingEndpointDeletes.Discard(endpointKey) buf.pendingEndpointUpdates[endpointKey] = endpointUpdate{ - endpoint: endpoint, - peerData: peerData, - tierInfo: filteredTiers, + endpoint: endpoint, + computedData: computedData, + peerData: peerData, + tierInfo: filteredTiers, } } } @@ -530,7 +550,7 @@ func (buf *EventSequencer) flushEndpointTierUpdates() { WorkloadId: key.WorkloadID, EndpointId: key.EndpointID, }, - Endpoint: ModelWorkloadEndpointToProto(wlep, endpointUpdate.peerData, tiers), + Endpoint: ModelWorkloadEndpointToProto(wlep, endpointUpdate.computedData, endpointUpdate.peerData, tiers), }) case model.HostEndpointKey: hep := endpoint.(*model.HostEndpoint) @@ -549,7 +569,7 @@ func (buf *EventSequencer) flushEndpointTierUpdates() { } func (buf *EventSequencer) flushEndpointTierDeletes() { - buf.pendingEndpointDeletes.Iter(func(item model.Key) error { + for item := range buf.pendingEndpointDeletes.All() { switch key := item.(type) { case model.WorkloadEndpointKey: buf.Callback(&proto.WorkloadEndpointRemove{ @@ -567,8 +587,8 @@ func (buf *EventSequencer) flushEndpointTierDeletes() { }) } buf.sentEndpoints.Discard(item) - return set.RemoveItem - }) + buf.pendingEndpointDeletes.Discard(item) + } } func (buf *EventSequencer) OnEncapUpdate(encap config.Encapsulation) { @@ -624,13 +644,13 @@ func (buf *EventSequencer) OnHostIPRemove(hostname string) { } func (buf *EventSequencer) flushHostIPDeletes() { - buf.pendingHostIPDeletes.Iter(func(item string) error { + for item := range buf.pendingHostIPDeletes.All() { buf.Callback(&proto.HostMetadataRemove{ Hostname: item, }) buf.sentHostIPs.Discard(item) - return set.RemoveItem - }) + buf.pendingHostIPDeletes.Discard(item) + } } func (buf *EventSequencer) OnHostIPv6Update(hostname string, ip *net.IP) { @@ -666,13 +686,13 @@ func (buf *EventSequencer) OnHostIPv6Remove(hostname string) { } func (buf *EventSequencer) flushHostIPv6Deletes() { - buf.pendingHostIPv6Deletes.Iter(func(item string) error { + for item := range buf.pendingHostIPv6Deletes.All() { buf.Callback(&proto.HostMetadataV6Remove{ Hostname: item, }) buf.sentHostIPv6s.Discard(item) - return set.RemoveItem - }) + buf.pendingHostIPv6Deletes.Discard(item) + } } func (buf *EventSequencer) OnHostMetadataUpdate(hostname string, ip4 *net.IPNet, ip6 *net.IPNet, asnumber string, labels map[string]string) { @@ -717,13 +737,13 @@ func (buf *EventSequencer) OnHostMetadataRemove(hostname string) { } func (buf *EventSequencer) flushHostDeletes() { - buf.pendingHostMetadataDeletes.Iter(func(item string) error { + for item := range buf.pendingHostMetadataDeletes.All() { buf.Callback(&proto.HostMetadataV4V6Remove{ Hostname: item, }) buf.sentHosts.Discard(item) - return set.RemoveItem - }) + buf.pendingHostMetadataDeletes.Discard(item) + } } func (buf *EventSequencer) OnIPPoolUpdate(key model.IPPoolKey, pool *model.IPPool) { @@ -811,17 +831,17 @@ func (buf *EventSequencer) OnIPPoolRemove(key model.IPPoolKey) { } func (buf *EventSequencer) flushIPPoolDeletes() { - buf.pendingIPPoolDeletes.Iter(func(key ip.CIDR) error { + for key := range buf.pendingIPPoolDeletes.All() { buf.Callback(&proto.IPAMPoolRemove{ Id: cidrToIPPoolID(key), }) buf.sentIPPools.Discard(key) - return set.RemoveItem - }) + buf.pendingIPPoolDeletes.Discard(key) + } } func (buf *EventSequencer) flushHostWireguardDeletes() { - buf.pendingWireguardDeletes.Iter(func(key string) error { + for key := range buf.pendingWireguardDeletes.All() { log.WithField("nodename", key).Debug("Processing pending wireguard delete") if buf.sentWireguard.Contains(key) { log.Debug("Sending IPv4 wireguard endpoint remove") @@ -837,8 +857,8 @@ func (buf *EventSequencer) flushHostWireguardDeletes() { }) buf.sentWireguardV6.Discard(key) } - return set.RemoveItem - }) + buf.pendingWireguardDeletes.Discard(key) + } log.Debug("Done flushing wireguard removes") } @@ -916,7 +936,7 @@ func (buf *EventSequencer) Flush() { } func (buf *EventSequencer) flushRemovedIPSets() { - buf.pendingRemovedIPSets.Iter(func(setID string) (err error) { + for setID := range buf.pendingRemovedIPSets.All() { log.Debugf("Flushing IP set remove: %v", setID) buf.Callback(&proto.IPSetRemove{ Id: setID, @@ -925,8 +945,7 @@ func (buf *EventSequencer) flushRemovedIPSets() { buf.pendingAddedIPSetMembers.DiscardKey(setID) buf.pendingRemovedIPSets.Discard(setID) buf.sentIPSets.Discard(setID) - return - }) + } log.Debugf("Done flushing IP set removes") } @@ -975,13 +994,12 @@ func (buf *EventSequencer) OnServiceAccountRemove(id types.ServiceAccountID) { func (buf *EventSequencer) flushServiceAccounts() { // Order doesn't matter, but send removes first to reduce max occupancy - buf.pendingServiceAccountDeletes.Iter(func(id types.ServiceAccountID) error { + for id := range buf.pendingServiceAccountDeletes.All() { protoID := types.ServiceAccountIDToProto(id) msg := proto.ServiceAccountRemove{Id: protoID} buf.Callback(&msg) buf.sentServiceAccounts.Discard(id) - return nil - }) + } buf.pendingServiceAccountDeletes.Clear() for _, msg := range buf.pendingServiceAccountUpdates { buf.Callback(msg) @@ -1066,13 +1084,12 @@ func (buf *EventSequencer) OnGlobalBGPConfigUpdate(cfg *v3.BGPConfiguration) { func (buf *EventSequencer) flushNamespaces() { // Order doesn't matter, but send removes first to reduce max occupancy - buf.pendingNamespaceDeletes.Iter(func(id types.NamespaceID) error { + for id := range buf.pendingNamespaceDeletes.All() { protoID := types.NamespaceIDToProto(id) msg := proto.NamespaceRemove{Id: protoID} buf.Callback(&msg) buf.sentNamespaces.Discard(id) - return nil - }) + } buf.pendingNamespaceDeletes.Clear() for _, msg := range buf.pendingNamespaceUpdates { buf.Callback(msg) @@ -1101,12 +1118,11 @@ func (buf *EventSequencer) OnVTEPRemove(dst string) { } func (buf *EventSequencer) flushVTEPRemoves() { - buf.pendingVTEPDeletes.Iter(func(node string) error { + for node := range buf.pendingVTEPDeletes.All() { msg := proto.VXLANTunnelEndpointRemove{Node: node} buf.Callback(&msg) buf.sentVTEPs.Discard(node) - return nil - }) + } buf.pendingVTEPDeletes.Clear() log.Debug("Done flushing VTEP removes") } @@ -1150,12 +1166,11 @@ func (buf *EventSequencer) flushRouteAdds() { } func (buf *EventSequencer) flushRouteRemoves() { - buf.pendingRouteDeletes.Iter(func(id routeID) error { + for id := range buf.pendingRouteDeletes.All() { msg := proto.RouteRemove{Dst: id.dst} buf.Callback(&msg) buf.sentRoutes.Discard(id) - return nil - }) + } buf.pendingRouteDeletes.Clear() log.Debug("Done flushing route deletes") } @@ -1190,15 +1205,14 @@ func (buf *EventSequencer) OnServiceRemove(update *proto.ServiceRemove) { func (buf *EventSequencer) flushServices() { // Order doesn't matter, but send removes first to reduce max occupancy - buf.pendingServiceDeletes.Iter(func(id serviceID) error { + for id := range buf.pendingServiceDeletes.All() { msg := &proto.ServiceRemove{ Name: id.Name, Namespace: id.Namespace, } buf.Callback(msg) buf.sentServices.Discard(id) - return nil - }) + } buf.pendingServiceDeletes.Clear() for _, msg := range buf.pendingServiceUpdates { buf.Callback(msg) @@ -1219,11 +1233,16 @@ func cidrToIPPoolID(cidr ip.CIDR) string { } func addPolicyToTierInfo(pol *PolKV, tierInfo *proto.TierInfo, egressAllowed bool) { + id := proto.PolicyID{ + Name: pol.Key.Name, + Namespace: pol.Key.Namespace, + Kind: pol.Key.Kind, + } if pol.GovernsIngress() { - tierInfo.IngressPolicies = append(tierInfo.IngressPolicies, pol.Key.Name) + tierInfo.IngressPolicies = append(tierInfo.IngressPolicies, &id) } if egressAllowed && pol.GovernsEgress() { - tierInfo.EgressPolicies = append(tierInfo.EgressPolicies, pol.Key.Name) + tierInfo.EgressPolicies = append(tierInfo.EgressPolicies, &id) } } diff --git a/felix/calc/event_sequencer_test.go b/felix/calc/event_sequencer_test.go index 14f79468d63..1f49b2a690f 100644 --- a/felix/calc/event_sequencer_test.go +++ b/felix/calc/event_sequencer_test.go @@ -15,8 +15,9 @@ package calc_test import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + "fmt" + + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/api/pkg/lib/numorstring" googleproto "google.golang.org/protobuf/proto" @@ -30,13 +31,11 @@ import ( "github.com/projectcalico/calico/libcalico-go/lib/net" ) -var ( - dscp numorstring.DSCP = numorstring.DSCPFromString("AF43") -) +var dscp numorstring.DSCP = numorstring.DSCPFromString("AF43") var _ = DescribeTable("ModelWorkloadEndpointToProto", func(in model.WorkloadEndpoint, expected *proto.WorkloadEndpoint) { - out := calc.ModelWorkloadEndpointToProto(&in, nil, []*proto.TierInfo{}) + out := calc.ModelWorkloadEndpointToProto(&in, nil, nil, []*proto.TierInfo{}) Expect(out).To(Equal(expected)) }, Entry("workload endpoint with NAT", model.WorkloadEndpoint{ @@ -150,7 +149,7 @@ var _ = DescribeTable("ModelWorkloadEndpointToProto", EgressMaxConnections: 14000000, }, QosPolicies: []*proto.QoSPolicy{ - &proto.QoSPolicy{ + { Dscp: 38, }, }, @@ -158,10 +157,37 @@ var _ = DescribeTable("ModelWorkloadEndpointToProto", }), ) +var _ = Describe("ModelWorkloadEndpointToProto with computed data", func() { + It("should apply computed data to the proto endpoint", func() { + in := model.WorkloadEndpoint{ + State: "up", + Name: "bill", + } + cd := &testApplyToComputedData{Annotation: "test-value"} + out := calc.ModelWorkloadEndpointToProto(&in, []calc.EndpointComputedData{cd}, nil, []*proto.TierInfo{}) + Expect(out.State).To(Equal("up")) + Expect(out.Name).To(Equal("bill")) + Expect(out.Annotations).To(HaveKeyWithValue("computed", "test-value")) + }) +}) + +// testApplyToComputedData implements calc.EndpointComputedData for testing. +type testApplyToComputedData struct { + Annotation string +} + +func (t *testApplyToComputedData) ApplyTo(wep *proto.WorkloadEndpoint) { + if wep.Annotations == nil { + wep.Annotations = map[string]string{} + } + wep.Annotations["computed"] = t.Annotation +} + var _ = Describe("ParsedRulesToActivePolicyUpdate", func() { var ( fullyLoadedParsedRules = calc.ParsedRules{ Namespace: "namespace", + Tier: "default", OutboundRules: []*calc.ParsedRule{ {Action: "Allow"}, }, @@ -174,10 +200,10 @@ var _ = Describe("ParsedRulesToActivePolicyUpdate", func() { } fullyLoadedProtoRules = proto.ActivePolicyUpdate{ Id: &proto.PolicyID{ - Tier: "default", Name: "a-policy", }, Policy: &proto.Policy{ + Tier: "default", Namespace: "namespace", InboundRules: []*proto.Rule{{Action: "Deny"}}, OutboundRules: []*proto.Rule{{Action: "Allow"}}, @@ -201,7 +227,7 @@ var _ = Describe("ParsedRulesToActivePolicyUpdate", func() { }) It("should convert the fully-loaded rule", func() { - protoUpdate := calc.ParsedRulesToActivePolicyUpdate(model.PolicyKey{Tier: "default", Name: "a-policy"}, &fullyLoadedParsedRules) + protoUpdate := calc.ParsedRulesToActivePolicyUpdate(model.PolicyKey{Name: "a-policy"}, &fullyLoadedParsedRules) // Check the rule IDs are filled in but ignore them for comparisons. for _, r := range protoUpdate.Policy.InboundRules { Expect(r.RuleId).ToNot(Equal("")) @@ -211,7 +237,8 @@ var _ = Describe("ParsedRulesToActivePolicyUpdate", func() { Expect(r.RuleId).ToNot(Equal("")) r.RuleId = "" } - Expect(googleproto.Equal(protoUpdate, &fullyLoadedProtoRules)).To(BeTrue()) + msg := fmt.Sprintf("Converted protoUpdate \n\n %s \n\n did not match expected \n\n %s", protoUpdate.String(), fullyLoadedProtoRules.String()) + Expect(googleproto.Equal(protoUpdate, &fullyLoadedProtoRules)).To(BeTrue(), msg) }) }) @@ -242,16 +269,16 @@ var _ = DescribeTable("ModelHostEndpointToProto", }), ProfileIDs: []string{"prof1"}, }, - []*proto.TierInfo{{Name: "a", IngressPolicies: []string{"b", "c"}}}, - []*proto.TierInfo{{Name: "d", IngressPolicies: []string{"e", "f"}}}, - []*proto.TierInfo{{Name: "g", IngressPolicies: []string{"h", "i"}}}, + []*proto.TierInfo{{Name: "a", IngressPolicies: []*proto.PolicyID{{Name: "b"}, {Name: "c"}}}}, + []*proto.TierInfo{{Name: "d", IngressPolicies: []*proto.PolicyID{{Name: "e"}, {Name: "f"}}}}, + []*proto.TierInfo{{Name: "g", IngressPolicies: []*proto.PolicyID{{Name: "h"}, {Name: "i"}}}}, &proto.HostEndpoint{ Name: "eth0", ExpectedIpv4Addrs: []string{"10.28.0.13", "10.28.0.14"}, ExpectedIpv6Addrs: []string{"dead::beef", "dead::bee5"}, - Tiers: []*proto.TierInfo{{Name: "a", IngressPolicies: []string{"b", "c"}}}, - UntrackedTiers: []*proto.TierInfo{{Name: "d", IngressPolicies: []string{"e", "f"}}}, - ForwardTiers: []*proto.TierInfo{{Name: "g", IngressPolicies: []string{"h", "i"}}}, + Tiers: []*proto.TierInfo{{Name: "a", IngressPolicies: []*proto.PolicyID{{Name: "b"}, {Name: "c"}}}}, + UntrackedTiers: []*proto.TierInfo{{Name: "d", IngressPolicies: []*proto.PolicyID{{Name: "e"}, {Name: "f"}}}}, + ForwardTiers: []*proto.TierInfo{{Name: "g", IngressPolicies: []*proto.PolicyID{{Name: "h"}, {Name: "i"}}}}, ProfileIds: []string{"prof1"}, }, ), @@ -265,16 +292,16 @@ var _ = DescribeTable("ModelHostEndpointToProto", }), ProfileIDs: []string{"prof1"}, }, - []*proto.TierInfo{{Name: "a", IngressPolicies: []string{"b"}}}, - []*proto.TierInfo{{Name: "a", EgressPolicies: []string{"c"}}}, - []*proto.TierInfo{{Name: "a", EgressPolicies: []string{"d"}}}, + []*proto.TierInfo{{Name: "a", IngressPolicies: []*proto.PolicyID{{Name: "b"}}}}, + []*proto.TierInfo{{Name: "a", EgressPolicies: []*proto.PolicyID{{Name: "c"}}}}, + []*proto.TierInfo{{Name: "a", EgressPolicies: []*proto.PolicyID{{Name: "d"}}}}, &proto.HostEndpoint{ Name: "eth0", ExpectedIpv4Addrs: []string{"10.28.0.13", "10.28.0.14"}, ExpectedIpv6Addrs: []string{"dead::beef", "dead::bee5"}, - Tiers: []*proto.TierInfo{{Name: "a", IngressPolicies: []string{"b"}}}, - UntrackedTiers: []*proto.TierInfo{{Name: "a", EgressPolicies: []string{"c"}}}, - ForwardTiers: []*proto.TierInfo{{Name: "a", EgressPolicies: []string{"d"}}}, + Tiers: []*proto.TierInfo{{Name: "a", IngressPolicies: []*proto.PolicyID{{Name: "b"}}}}, + UntrackedTiers: []*proto.TierInfo{{Name: "a", EgressPolicies: []*proto.PolicyID{{Name: "c"}}}}, + ForwardTiers: []*proto.TierInfo{{Name: "a", EgressPolicies: []*proto.PolicyID{{Name: "d"}}}}, ProfileIds: []string{"prof1"}, }, ), @@ -291,19 +318,19 @@ var _ = DescribeTable("ModelHostEndpointToProto", DSCP: &dscp, }, }, - []*proto.TierInfo{{Name: "a", IngressPolicies: []string{"b"}}}, - []*proto.TierInfo{{Name: "a", EgressPolicies: []string{"c"}}}, - []*proto.TierInfo{{Name: "a", EgressPolicies: []string{"d"}}}, + []*proto.TierInfo{{Name: "a", IngressPolicies: []*proto.PolicyID{{Name: "b"}}}}, + []*proto.TierInfo{{Name: "a", EgressPolicies: []*proto.PolicyID{{Name: "c"}}}}, + []*proto.TierInfo{{Name: "a", EgressPolicies: []*proto.PolicyID{{Name: "d"}}}}, &proto.HostEndpoint{ Name: "eth0", ExpectedIpv4Addrs: []string{"10.28.0.13", "10.28.0.14"}, ExpectedIpv6Addrs: []string{"dead::beef", "dead::bee5"}, - Tiers: []*proto.TierInfo{{Name: "a", IngressPolicies: []string{"b"}}}, - UntrackedTiers: []*proto.TierInfo{{Name: "a", EgressPolicies: []string{"c"}}}, - ForwardTiers: []*proto.TierInfo{{Name: "a", EgressPolicies: []string{"d"}}}, + Tiers: []*proto.TierInfo{{Name: "a", IngressPolicies: []*proto.PolicyID{{Name: "b"}}}}, + UntrackedTiers: []*proto.TierInfo{{Name: "a", EgressPolicies: []*proto.PolicyID{{Name: "c"}}}}, + ForwardTiers: []*proto.TierInfo{{Name: "a", EgressPolicies: []*proto.PolicyID{{Name: "d"}}}}, ProfileIds: []string{"prof1"}, QosPolicies: []*proto.QoSPolicy{ - &proto.QoSPolicy{ + { Dscp: 38, }, }, @@ -331,11 +358,12 @@ var _ = Describe("ServiceAccount update/remove", func() { Labels: map[string]string{"k1": "v2"}, }) uut.Flush() - Expect(recorder.Messages).To(Equal([]interface{}{ + Expect(recorder.Messages).To(Equal([]any{ &proto.ServiceAccountUpdate{ Id: &proto.ServiceAccountID{Name: "test", Namespace: "test"}, Labels: map[string]string{"k1": "v2"}, - }})) + }, + })) }) It("should coalesce add + remove", func() { @@ -355,7 +383,7 @@ var _ = Describe("ServiceAccount update/remove", func() { Labels: map[string]string{"k1": "v1"}, }) uut.Flush() - Expect(recorder.Messages).To(Equal([]interface{}{&proto.ServiceAccountUpdate{ + Expect(recorder.Messages).To(Equal([]any{&proto.ServiceAccountUpdate{ Id: &proto.ServiceAccountID{Name: "test", Namespace: "test"}, Labels: map[string]string{"k1": "v1"}, }})) @@ -367,16 +395,16 @@ var _ = Describe("ServiceAccount update/remove", func() { Labels: map[string]string{"k1": "v1"}, }) uut.Flush() - Expect(recorder.Messages).To(Equal([]interface{}{&proto.ServiceAccountUpdate{ + Expect(recorder.Messages).To(Equal([]any{&proto.ServiceAccountUpdate{ Id: &proto.ServiceAccountID{Name: "test", Namespace: "test"}, Labels: map[string]string{"k1": "v1"}, }})) // Clear messages - recorder.Messages = make([]interface{}, 0) + recorder.Messages = make([]any, 0) uut.OnServiceAccountRemove(types.ServiceAccountID{Name: "test", Namespace: "test"}) uut.Flush() - Expect(recorder.Messages).To(Equal([]interface{}{&proto.ServiceAccountRemove{ + Expect(recorder.Messages).To(Equal([]any{&proto.ServiceAccountRemove{ Id: &proto.ServiceAccountID{Name: "test", Namespace: "test"}, }})) }) @@ -402,11 +430,12 @@ var _ = Describe("Namespace update/remove", func() { Labels: map[string]string{"k1": "v2"}, }) uut.Flush() - Expect(recorder.Messages).To(Equal([]interface{}{ + Expect(recorder.Messages).To(Equal([]any{ &proto.NamespaceUpdate{ Id: &proto.NamespaceID{Name: "test"}, Labels: map[string]string{"k1": "v2"}, - }})) + }, + })) }) It("should coalesce add + remove", func() { @@ -426,7 +455,7 @@ var _ = Describe("Namespace update/remove", func() { Labels: map[string]string{"k1": "v1"}, }) uut.Flush() - Expect(recorder.Messages).To(Equal([]interface{}{&proto.NamespaceUpdate{ + Expect(recorder.Messages).To(Equal([]any{&proto.NamespaceUpdate{ Id: &proto.NamespaceID{Name: "test"}, Labels: map[string]string{"k1": "v1"}, }})) @@ -438,16 +467,16 @@ var _ = Describe("Namespace update/remove", func() { Labels: map[string]string{"k1": "v1"}, }) uut.Flush() - Expect(recorder.Messages).To(Equal([]interface{}{&proto.NamespaceUpdate{ + Expect(recorder.Messages).To(Equal([]any{&proto.NamespaceUpdate{ Id: &proto.NamespaceID{Name: "test"}, Labels: map[string]string{"k1": "v1"}, }})) // Clear messages - recorder.Messages = make([]interface{}, 0) + recorder.Messages = make([]any, 0) uut.OnNamespaceRemove(types.NamespaceID{Name: "test"}) uut.Flush() - Expect(recorder.Messages).To(Equal([]interface{}{&proto.NamespaceRemove{ + Expect(recorder.Messages).To(Equal([]any{&proto.NamespaceRemove{ Id: &proto.NamespaceID{Name: "test"}, }})) }) @@ -474,10 +503,10 @@ var _ = Describe("IPPool update/remove", func() { }) type dataplaneRecorder struct { - Messages []interface{} + Messages []any } -func (d *dataplaneRecorder) record(message interface{}) { +func (d *dataplaneRecorder) record(message any) { d.Messages = append(d.Messages, message) } diff --git a/felix/calc/iplpm.go b/felix/calc/iplpm.go index eeb9f488e84..d450744b766 100644 --- a/felix/calc/iplpm.go +++ b/felix/calc/iplpm.go @@ -15,7 +15,9 @@ package calc import ( + "slices" "strings" + "unique" "github.com/tchap/go-patricia/v2/patricia" @@ -24,19 +26,17 @@ import ( "github.com/projectcalico/calico/libcalico-go/lib/set" ) -// Node is represented by cidr as KEY and v1 key data stored in keys. +// Node is represented by cidr as KEY and stores all keys for that CIDR. type IPTrieNode struct { cidr ip.CIDR - keys []model.Key + keys []unique.Handle[model.Key] } -// Root of IpTree type IpTrie struct { lpmCache *patricia.Trie existingCidrs set.Set[ip.CIDR] } -// NewIpTrie creates new Patricia trie and Initializes func NewIpTrie() *IpTrie { return &IpTrie{ lpmCache: patricia.NewTrie(), @@ -44,17 +44,44 @@ func NewIpTrie() *IpTrie { } } -// newIPTrieNode Function creates new empty node containing the CIDR and single key. +// getLowestSortingKey returns the key with the lexicographically smallest name from a slice of +// keys. This provides deterministic tie-breaking for network sets with the same prefix length. +func getLowestSortingKey(keys []unique.Handle[model.Key]) model.Key { + if len(keys) == 0 { + return nil + } + if len(keys) == 1 { + return keys[0].Value() + } + + // Linear scan to find the lexicographically lowest key + lowestHandle := keys[0] + lowestKeyStr := lowestHandle.Value().String() + + for i := 1; i < len(keys); i++ { + keyStr := keys[i].Value().String() + if keyStr < lowestKeyStr { + lowestHandle = keys[i] + lowestKeyStr = keyStr + } + } + + return lowestHandle.Value() +} + func newIPTrieNode(cidr ip.CIDR, key model.Key) *IPTrieNode { - return &IPTrieNode{cidr: cidr, keys: []model.Key{key}} + return &IPTrieNode{ + cidr: cidr, + keys: []unique.Handle[model.Key]{unique.Make(key)}, + } } -// GetLongestPrefixCidr finds longest prefix match CIDR for the Given IP and if successful return the last key -// recorded. -func (t *IpTrie) GetLongestPrefixCidr(ipAddr ip.Addr) (model.Key, bool) { +// GetLongestPrefixCidr finds the longest prefix match CIDR for the given IP and if successful returns the +// lexicographically lowest key associated with that CIDR. +func (trie *IpTrie) GetLongestPrefixCidr(ipAddr ip.Addr) (model.Key, bool) { var longestPrefix patricia.Prefix var longestItem patricia.Item - ptrie := t.lpmCache + ptrie := trie.lpmCache err := ptrie.VisitPrefixes(patricia.Prefix(ipAddr.AsBinary()), func(prefix patricia.Prefix, item patricia.Item) error { @@ -64,11 +91,79 @@ func (t *IpTrie) GetLongestPrefixCidr(ipAddr ip.Addr) (model.Key, bool) { } return nil }) + if err != nil || longestItem == nil { return nil, false } + node := longestItem.(*IPTrieNode) - return node.keys[len(node.keys)-1], true + return getLowestSortingKey(node.keys), true +} + +// GetLongestPrefixCidrWithNamespaceIsolation finds the best prefix match with namespace isolation. +// Priority order: +// 1) preferred namespace match +// 2) global match +// 3) any other namespace match +func (trie *IpTrie) GetLongestPrefixCidrWithNamespaceIsolation(ipAddr ip.Addr, preferredNamespace string) (model.Key, bool) { + ptrie := trie.lpmCache + searchPrefix := patricia.Prefix(ipAddr.AsBinary()) + + type bestMatch struct { + keys []unique.Handle[model.Key] + prefix patricia.Prefix + } + + var ( + bestPreferred bestMatch + bestGlobal bestMatch + bestOther bestMatch + ) + + updateBest := func(b *bestMatch, prefix patricia.Prefix, keyHandle unique.Handle[model.Key]) { + switch { + case len(prefix) > len(b.prefix): + b.keys = append(b.keys[:0], keyHandle) + b.prefix = prefix + case len(prefix) == len(b.prefix): + b.keys = append(b.keys, keyHandle) + } + } + + if err := ptrie.VisitPrefixes(searchPrefix, func(prefix patricia.Prefix, item patricia.Item) error { + node := item.(*IPTrieNode) + for _, keyHandle := range node.keys { + key := keyHandle.Value() + nsKey, ok := key.(model.NetworkSetKey) + if !ok { + continue + } + ns := nsKey.GetNamespace() + + switch { + case preferredNamespace != "" && ns == preferredNamespace: + updateBest(&bestPreferred, prefix, keyHandle) + case ns == "": + updateBest(&bestGlobal, prefix, keyHandle) + default: + updateBest(&bestOther, prefix, keyHandle) + } + } + return nil + }); err != nil { + return nil, false + } + + switch { + case len(bestPreferred.keys) > 0: + return getLowestSortingKey(bestPreferred.keys), true + case len(bestGlobal.keys) > 0: + return getLowestSortingKey(bestGlobal.keys), true + case len(bestOther.keys) > 0: + return getLowestSortingKey(bestOther.keys), true + default: + return nil, false + } } // GetKeys return list of keys for the Given CIDR @@ -79,7 +174,12 @@ func (t *IpTrie) GetKeys(cidr ip.CIDR) ([]model.Key, bool) { if val != nil { node := val.(*IPTrieNode) - return node.keys, true + // Convert handles back to keys + keys := make([]model.Key, len(node.keys)) + for i, h := range node.keys { + keys[i] = h.Value() + } + return keys, true } return nil, false @@ -100,14 +200,9 @@ func (t *IpTrie) DeleteKey(cidr ip.CIDR, key model.Key) { t.existingCidrs.Discard(cidr) ptrie.Delete(patricia.Prefix(cidrb)) } else { - ii := 0 - for _, val := range node.keys { - if val != key { - node.keys[ii] = val - ii++ - } - } - node.keys = node.keys[:ii] + node.keys = slices.DeleteFunc(node.keys, func(h unique.Handle[model.Key]) bool { + return h.Value() == key + }) } } @@ -126,16 +221,17 @@ func (t *IpTrie) InsertKey(cidr ip.CIDR, key model.Key) { ptrie.Insert(patricia.Prefix(cidrb), newNode) } else { node := val.(*IPTrieNode) + keyHandle := unique.Make(key) isExistingNetset := false - for i, val := range node.keys { - if key == val { - node.keys[i] = key + for i, existingHandle := range node.keys { + if key == existingHandle.Value() { + node.keys[i] = keyHandle isExistingNetset = true break } } if !isExistingNetset { - node.keys = append(node.keys, key) + node.keys = append(node.keys, keyHandle) } } } @@ -144,16 +240,14 @@ func (t *IpTrie) InsertKey(cidr ip.CIDR, key model.Key) { func (t *IpTrie) DumpCIDRKeys() []string { ec := t.existingCidrs lines := []string{} - ec.Iter(func(cidr ip.CIDR) error { + for cidr := range ec.All() { keyStrings := []string{} keys, _ := t.GetKeys(cidr) for _, key := range keys { keyStrings = append(keyStrings, key.String()) } lines = append(lines, cidr.String()+": "+strings.Join(keyStrings, ",")) - - return nil - }) + } return lines } diff --git a/felix/calc/iplpm_test.go b/felix/calc/iplpm_test.go new file mode 100644 index 00000000000..7d407b35a96 --- /dev/null +++ b/felix/calc/iplpm_test.go @@ -0,0 +1,750 @@ +// Copyright (c) 2019-2025 Tigera, Inc. All rights reserved. + +package calc_test + +import ( + "fmt" + "net" + "strings" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + . "github.com/projectcalico/calico/felix/calc" + "github.com/projectcalico/calico/felix/ip" + "github.com/projectcalico/calico/libcalico-go/lib/backend/api" + "github.com/projectcalico/calico/libcalico-go/lib/backend/model" + calinet "github.com/projectcalico/calico/libcalico-go/lib/net" +) + +var _ = DescribeTable("Check Inserting CIDR and compare with network set names", + func(key model.NetworkSetKey, netset *model.NetworkSet) { + it := NewIpTrie() + + for _, cidr := range netset.Nets { + cidrb := ip.CIDRFromCalicoNet(cidr) + it.InsertKey(cidrb, key) + } + for _, cidr := range netset.Nets { + cidrb := ip.CIDRFromCalicoNet(cidr) + keys, ok := it.GetKeys(cidrb) + for _, ekey := range keys { + Expect(ok).To(Equal(true)) + Expect(ekey).To(Equal(key)) + } + } + }, + Entry("Insert network CIDR and match with ns name", netSet1Key, &netSet1), + Entry("Insert network CIDR and match with ns name", netSet2Key, &netSet2), +) + +var _ = DescribeTable("Insert and Delete CIDRs and compare with network set names", + func(key model.NetworkSetKey, netset *model.NetworkSet, key1 model.NetworkSetKey, netset1 *model.NetworkSet) { + it := NewIpTrie() + + for _, cidr := range netset1.Nets { + cidrb := ip.CIDRFromCalicoNet(cidr) + it.InsertKey(cidrb, key1) + } + for _, cidr := range netset.Nets { + cidrb := ip.CIDRFromCalicoNet(cidr) + it.InsertKey(cidrb, key) + } + for _, cidr := range netset1.Nets { + cidrb := ip.CIDRFromCalicoNet(cidr) + it.DeleteKey(cidrb, key1) + } + for _, cidr := range netset.Nets { + cidrb := ip.CIDRFromCalicoNet(cidr) + keys, ok := it.GetKeys(cidrb) + for _, ekey := range keys { + Expect(ok).To(Equal(true)) + Expect(ekey).To(Equal(key)) + } + } + }, + Entry("Insert network CIDR and match with ns name", netSet1Key, &netSet1, netSet2Key, &netSet2), + Entry("Insert network CIDR and match with ns name", netSet2Key, &netSet2, netSet1Key, &netSet1), +) + +var _ = DescribeTable("Test by finding Longest Prefix Match CIDR's name for given IP Address", + func(key1 model.NetworkSetKey, key2 model.NetworkSetKey, netset1 *model.NetworkSet, netset2 *model.NetworkSet, ipAddr net.IP, res model.NetworkSetKey) { + it := NewIpTrie() + ipaddr := ip.FromNetIP(ipAddr) + + for _, cidr := range netset1.Nets { + cidrb := ip.CIDRFromCalicoNet(cidr) + it.InsertKey(cidrb, key1) + } + for _, cidr := range netset2.Nets { + cidrb := ip.CIDRFromCalicoNet(cidr) + it.InsertKey(cidrb, key2) + } + + key, ok := it.GetLongestPrefixCidr(ipaddr) + Expect(ok).To(Equal(true)) + Expect(key).To(Equal(res)) + }, + Entry("Longest Prefix Match find ns name", netSet1Key, netSet3Key, &netSet1, &netSet3, netset3Ip1a, netSet1Key), + Entry("Longest Prefix Match find ns name", netSet1Key, netSet3Key, &netSet1, &netSet3, netset3Ip1b, netSet3Key), +) + +var _ = Describe("IpTrie Namespace-Aware Functionality", func() { + var it *IpTrie + var ns1Key, ns2Key, globalKey model.NetworkSetKey + var testCIDR ip.CIDR + var testIP ip.Addr + + BeforeEach(func() { + it = NewIpTrie() + + // Create test keys with different namespaces + ns1Key = model.NetworkSetKey{Name: "namespace1/test-netset"} + ns2Key = model.NetworkSetKey{Name: "namespace2/test-netset"} + globalKey = model.NetworkSetKey{Name: "global-netset"} + + // Create test CIDR and IP + testCIDR = ip.MustParseCIDROrIP("10.0.0.0/24") + testIP = ip.FromNetIP(mustParseIP("10.0.0.100").IP) + }) + + Context("when testing namespace-aware insertion and retrieval", func() { + It("should organize keys by namespace correctly", func() { + // Insert keys from different namespaces for the same CIDR + it.InsertKey(testCIDR, globalKey) + it.InsertKey(testCIDR, ns1Key) + it.InsertKey(testCIDR, ns2Key) + + // Test namespace-specific retrieval + key, found := it.GetLongestPrefixCidrWithNamespaceIsolation(testIP, "namespace1") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(ns1Key)) + + key, found = it.GetLongestPrefixCidrWithNamespaceIsolation(testIP, "namespace2") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(ns2Key)) + + // Test fallback to global when namespace not found + key, found = it.GetLongestPrefixCidrWithNamespaceIsolation(testIP, "nonexistent") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(globalKey)) + }) + + It("should use lexicographic tie-breaking for same namespace", func() { + // Insert two keys in the same namespace for the same CIDR + firstKey := model.NetworkSetKey{Name: "namespace1/first-netset"} + secondKey := model.NetworkSetKey{Name: "namespace1/second-netset"} + + it.InsertKey(testCIDR, firstKey) + it.InsertKey(testCIDR, secondKey) + + // Should return the lexicographically smallest key + key, found := it.GetLongestPrefixCidrWithNamespaceIsolation(testIP, "namespace1") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(firstKey)) // "first-netset" < "second-netset" + }) + + It("should return the namespace that matches the target IP", func() { + testIP2 := ip.FromNetIP(mustParseIP("192.168.0.100").IP) + + // Insert keys with overlapping CIDRs in different namespaces + ns1CIDR := ip.MustParseCIDROrIP("10.0.0.0/24") + ns2CIDR := ip.MustParseCIDROrIP("192.168.0.0/24") + it.InsertKey(ns1CIDR, ns1Key) + it.InsertKey(ns2CIDR, ns2Key) + + // Test that the correct namespace is returned based on the IP + key, found := it.GetLongestPrefixCidrWithNamespaceIsolation(testIP, "namespace1") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(ns1Key)) + + key, found = it.GetLongestPrefixCidrWithNamespaceIsolation(testIP2, "namespace2") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(ns2Key)) + }) + + It("should fallback to other namespaces when preferred namespace is empty", func() { + nsKey := model.NetworkSetKey{Name: "namespace1/alpha-netset"} + otherNsKey := model.NetworkSetKey{Name: "namespace2/beta-netset"} + + it.InsertKey(testCIDR, nsKey) + it.InsertKey(testCIDR, otherNsKey) + + key, found := it.GetLongestPrefixCidrWithNamespaceIsolation(testIP, "") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(nsKey)) + }) + }) + + Context("when testing backward compatibility", func() { + It("should work with legacy NetworkSetKey", func() { + legacyKey := netSet1Key // This is a NetworkSetKey from test data + it.InsertKey(testCIDR, legacyKey) + + // Should work with legacy method + key, found := it.GetLongestPrefixCidr(testIP) + Expect(found).To(BeTrue()) + Expect(key).To(Equal(legacyKey)) + + // Should also work with namespace method (treats as global) + key, found = it.GetLongestPrefixCidrWithNamespaceIsolation(testIP, "any-namespace") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(legacyKey)) + }) + + It("should maintain allKeys for backward compatibility", func() { + it.InsertKey(testCIDR, globalKey) + it.InsertKey(testCIDR, ns1Key) + + // GetKeys should return all keys + keys, found := it.GetKeys(testCIDR) + Expect(found).To(BeTrue()) + Expect(keys).To(ContainElement(globalKey)) + Expect(keys).To(ContainElement(ns1Key)) + }) + }) + + Context("when testing deletion", func() { + BeforeEach(func() { + it.InsertKey(testCIDR, globalKey) + it.InsertKey(testCIDR, ns1Key) + it.InsertKey(testCIDR, ns2Key) + }) + + It("should remove keys correctly from namespace buckets", func() { + // Delete namespace1 key + it.DeleteKey(testCIDR, ns1Key) + + // Should no longer find namespace1 key + key, found := it.GetLongestPrefixCidrWithNamespaceIsolation(testIP, "namespace1") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(globalKey)) // Should fallback to global + + // Should still find namespace2 key + key, found = it.GetLongestPrefixCidrWithNamespaceIsolation(testIP, "namespace2") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(ns2Key)) + }) + + It("should remove entire node when no keys remain", func() { + // Delete all keys + it.DeleteKey(testCIDR, globalKey) + it.DeleteKey(testCIDR, ns1Key) + it.DeleteKey(testCIDR, ns2Key) + + // Should not find anything + _, found := it.GetLongestPrefixCidrWithNamespaceIsolation(testIP, "namespace1") + Expect(found).To(BeFalse()) + + _, found = it.GetKeys(testCIDR) + Expect(found).To(BeFalse()) + }) + }) + + Context("when testing edge cases", func() { + It("should handle empty namespace correctly", func() { + emptyNsKey := model.NetworkSetKey{Name: "empty-ns"} + it.InsertKey(testCIDR, emptyNsKey) + + // Should be treated as global + key, found := it.GetLongestPrefixCidrWithNamespaceIsolation(testIP, "any-namespace") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(emptyNsKey)) + }) + + It("should handle non-existent CIDR gracefully", func() { + nonExistentIP := ip.FromNetIP(mustParseIP("192.168.1.1").IP) + + _, found := it.GetLongestPrefixCidrWithNamespaceIsolation(nonExistentIP, "namespace1") + Expect(found).To(BeFalse()) + }) + + It("should handle unknown key types", func() { + unknownKey := model.PolicyKey{Name: "test-policy"} + it.InsertKey(testCIDR, unknownKey) + + // Should not be found since key type is unexpected + _, found := it.GetLongestPrefixCidrWithNamespaceIsolation(testIP, "any-namespace") + Expect(found).To(BeFalse()) + }) + + It("should handle 0.0.0.0/0 correctly", func() { + zeroCIDR := ip.MustParseCIDROrIP("0.0.0.0/0") + key := model.NetworkSetKey{Name: "zero-netset"} + + it.InsertKey(zeroCIDR, key) + + // Test with an arbitrary IP + testIP := ip.FromNetIP(net.ParseIP("1.2.3.4")) + + foundKey, found := it.GetLongestPrefixCidr(testIP) + Expect(found).To(BeTrue(), "Should find key for 0.0.0.0/0") + Expect(foundKey).To(Equal(key)) + }) + }) + + Context("when testing multiple overlapping CIDRs", func() { + var broadCIDR, narrowCIDR ip.CIDR + var testIPInBoth, testIPInNarrowOnly ip.Addr + + BeforeEach(func() { + // Create overlapping CIDRs + broadCIDR = ip.MustParseCIDROrIP("10.0.0.0/16") + narrowCIDR = ip.MustParseCIDROrIP("10.0.1.0/24") + + // IP that matches both CIDRs + testIPInBoth = ip.FromNetIP(mustParseIP("10.0.1.100").IP) + // IP that matches only broad CIDR + testIPInNarrowOnly = ip.FromNetIP(mustParseIP("10.0.2.100").IP) + }) + + It("should prioritize namespace isolation over longest prefix match", func() { + // Insert keys for both CIDRs + broadNs1Key := model.NetworkSetKey{Name: "namespace1/broad"} + narrowNs2Key := model.NetworkSetKey{Name: "namespace2/narrow"} + + it.InsertKey(broadCIDR, broadNs1Key) + it.InsertKey(narrowCIDR, narrowNs2Key) + + // For IP in both: should prefer namespace match over longer prefix + key, found := it.GetLongestPrefixCidrWithNamespaceIsolation(testIPInBoth, "namespace1") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(broadNs1Key)) // Namespace1 key wins due to namespace isolation + + // For IP in narrow only: should prefer namespace2 match when requesting namespace2 + key, found = it.GetLongestPrefixCidrWithNamespaceIsolation(testIPInBoth, "namespace2") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(narrowNs2Key)) // Namespace2 key wins due to namespace isolation + + // For IP in broad only: should return broad CIDR + key, found = it.GetLongestPrefixCidrWithNamespaceIsolation(testIPInNarrowOnly, "namespace1") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(broadNs1Key)) + }) + + It("should follow three-tier priority: namespace > global > other namespace", func() { + // Create a more specific test scenario with all three types of keys + testCIDR := ip.MustParseCIDROrIP("10.0.1.0/24") + testIP := ip.FromNetIP(mustParseIP("10.0.1.100").IP) + + // Insert keys with different priorities - all matching the same CIDR + preferredNsKey := model.NetworkSetKey{Name: "target-namespace/preferred"} + globalKey := model.NetworkSetKey{Name: "global-netset"} + otherNsKey := model.NetworkSetKey{Name: "other-namespace/fallback"} + + it.InsertKey(testCIDR, preferredNsKey) + it.InsertKey(testCIDR, globalKey) + it.InsertKey(testCIDR, otherNsKey) + + // Test 1: When preferred namespace exists, it should win + key, found := it.GetLongestPrefixCidrWithNamespaceIsolation(testIP, "target-namespace") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(preferredNsKey)) + + // Test 2: When preferred namespace doesn't exist, global should win over other namespace + key, found = it.GetLongestPrefixCidrWithNamespaceIsolation(testIP, "nonexistent-namespace") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(globalKey)) + + // Test 3: When neither preferred nor global exists, other namespace should be returned + it2 := NewIpTrie() + it2.InsertKey(testCIDR, otherNsKey) + + key, found = it2.GetLongestPrefixCidrWithNamespaceIsolation(testIP, "nonexistent-namespace") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(otherNsKey)) + }) + + It("should respect priority even with different prefix lengths", func() { + // Create test scenario where global has longer prefix than preferred namespace + broadCIDR := ip.MustParseCIDROrIP("10.0.0.0/16") // Less specific + narrowCIDR := ip.MustParseCIDROrIP("10.0.1.0/24") // More specific + testIP := ip.FromNetIP(mustParseIP("10.0.1.100").IP) // Matches both + + // Preferred namespace has broader CIDR, global has narrower CIDR + preferredNsKey := model.NetworkSetKey{Name: "target-namespace/broad"} + globalKey := model.NetworkSetKey{Name: "global-narrow"} + otherNsKey := model.NetworkSetKey{Name: "other-namespace/narrow"} + + it.InsertKey(broadCIDR, preferredNsKey) + it.InsertKey(narrowCIDR, globalKey) + it.InsertKey(narrowCIDR, otherNsKey) + + // Preferred namespace should still win despite having broader prefix + key, found := it.GetLongestPrefixCidrWithNamespaceIsolation(testIP, "target-namespace") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(preferredNsKey)) // Namespace priority over prefix length + + // When preferred namespace doesn't exist, global should win (longer prefix) + key, found = it.GetLongestPrefixCidrWithNamespaceIsolation(testIP, "nonexistent-namespace") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(globalKey)) // Global over other namespace + + // Test other namespace fallback by removing global + it3 := NewIpTrie() + it3.InsertKey(narrowCIDR, otherNsKey) + + key, found = it3.GetLongestPrefixCidrWithNamespaceIsolation(testIP, "nonexistent-namespace") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(otherNsKey)) // Other namespace as last resort + }) + }) +}) + +// Tests for collector integration with namespace-aware lookups +var _ = Describe("Collector Integration Tests", func() { + var lc *LookupsCache + var nsCache *NetworkSetLookupsCache + var testIP [16]byte + + BeforeEach(func() { + lc = NewLookupsCache() + nsCache = NewNetworkSetLookupsCache() + + ipAddr := mustParseIP("10.0.0.100").IP + copy(testIP[:], ipAddr.To16()) + }) + + Context("when testing collector namespace interface", func() { + It("should expose namespace-aware lookups through LookupsCache", func() { + // Verify that the collector interface has the namespace method + Expect(lc).To(BeAssignableToTypeOf(&LookupsCache{})) + + // Test that the method exists and has the right signature + _, found := lc.GetNetworkSetWithNamespace(testIP, "test-namespace") + Expect(found).To(BeFalse()) // Should be false since no data loaded + + // Verify the method is available for collector usage + Expect(lc.GetNetworkSetWithNamespace).ToNot(BeNil()) + }) + + It("should delegate to optimized NetworkSetLookupsCache implementation", func() { + // Create test data + globalKey := model.NetworkSetKey{Name: "global-netset"} + globalNS := model.NetworkSet{ + Nets: []calinet.IPNet{ + calinet.MustParseNetwork("10.0.0.0/8"), // Broad CIDR + }, + } + + ns1Key := model.NetworkSetKey{Name: "namespace1/specific-netset"} + ns1NS := model.NetworkSet{ + Nets: []calinet.IPNet{ + calinet.MustParseNetwork("10.0.0.0/24"), // More specific than global + }, + } + + // Add to the underlying cache + nsCache.OnUpdate(api.Update{ + KVPair: model.KVPair{Key: globalKey, Value: &globalNS}, + UpdateType: api.UpdateTypeKVNew, + }) + nsCache.OnUpdate(api.Update{ + KVPair: model.KVPair{Key: ns1Key, Value: &ns1NS}, + UpdateType: api.UpdateTypeKVNew, + }) + + // Test direct NetworkSetLookupsCache usage (what collector uses) + ed, found := nsCache.GetNetworkSetFromIPWithNamespace(testIP, "namespace1") + Expect(found).To(BeTrue()) + Expect(ed.Key()).To(Equal(ns1Key)) + + // Test fallback behavior + ed, found = nsCache.GetNetworkSetFromIPWithNamespace(testIP, "nonexistent") + Expect(found).To(BeTrue()) + Expect(ed.Key()).To(Equal(globalKey)) // Should return global as fallback + + // Verify legacy method still works + ed2, found2 := nsCache.GetNetworkSetFromIP(testIP) + Expect(found2).To(BeTrue()) + Expect(ed2.Key()).To(Equal(globalKey)) // Should return longest prefix match + }) + + It("should maintain performance with namespace-aware lookups", func() { + // Add multiple NetworkSets across namespaces + for i := range 10 { + key := model.NetworkSetKey{ + Name: fmt.Sprintf("namespace%d/test-netset", i), + } + ns := model.NetworkSet{ + Nets: []calinet.IPNet{ + calinet.MustParseNetwork(fmt.Sprintf("10.%d.0.0/16", i)), + }, + } + + nsCache.OnUpdate(api.Update{ + KVPair: model.KVPair{Key: key, Value: &ns}, + UpdateType: api.UpdateTypeKVNew, + }) + } + + // Performance test: many lookups should complete quickly + start := time.Now() + for i := range 100 { + namespace := fmt.Sprintf("namespace%d", i%10) + testIPLoop := [16]byte{} + loopIP := mustParseIP(fmt.Sprintf("10.%d.1.1", i%10)).IP + copy(testIPLoop[:], loopIP.To16()) + + _, found := nsCache.GetNetworkSetFromIPWithNamespace(testIPLoop, namespace) + Expect(found).To(BeTrue()) + } + elapsed := time.Since(start) + + // Should complete 100 lookups in reasonable time + Expect(elapsed).To(BeNumerically("<", 100*time.Millisecond)) + }) + }) +}) + +// Tests for tie-breaking functionality - ensuring deterministic behavior when multiple +// network sets match the same IP with equal prefix lengths +var _ = Describe("IpTrie Tie-Breaking Functionality", func() { + var it *IpTrie + var testCIDR ip.CIDR + var testIP ip.Addr + + BeforeEach(func() { + it = NewIpTrie() + testCIDR = ip.MustParseCIDROrIP("10.0.0.0/24") + testIP = ip.FromNetIP(mustParseIP("10.0.0.100").IP) + }) + + Context("when multiple keys have the same prefix length", func() { + It("should return the lexicographically smallest key name", func() { + // Insert keys in reverse alphabetical order to test sorting + zebraKey := model.NetworkSetKey{Name: "zebra-netset"} + betaKey := model.NetworkSetKey{Name: "beta-netset"} + alphaKey := model.NetworkSetKey{Name: "alpha-netset"} + + it.InsertKey(testCIDR, zebraKey) + it.InsertKey(testCIDR, betaKey) + it.InsertKey(testCIDR, alphaKey) + + key, found := it.GetLongestPrefixCidr(testIP) + Expect(found).To(BeTrue()) + Expect(key).To(Equal(alphaKey)) // Should return alpha (lexicographically smallest) + }) + + It("should be deterministic regardless of insertion order", func() { + keys := []model.NetworkSetKey{ + {Name: "gamma-netset"}, + {Name: "alpha-netset"}, + {Name: "delta-netset"}, + {Name: "beta-netset"}, + } + + // Test multiple insertion orders + for i := range 5 { + it := NewIpTrie() + + // Insert in different orders + for j := i; j < len(keys)+i; j++ { + it.InsertKey(testCIDR, keys[j%len(keys)]) + } + + key, found := it.GetLongestPrefixCidr(testIP) + Expect(found).To(BeTrue()) + Expect(key).To(Equal(model.NetworkSetKey{Name: "alpha-netset"})) + } + }) + + It("should handle single key correctly", func() { + singleKey := model.NetworkSetKey{Name: "single-netset"} + it.InsertKey(testCIDR, singleKey) + + key, found := it.GetLongestPrefixCidr(testIP) + Expect(found).To(BeTrue()) + Expect(key).To(Equal(singleKey)) + }) + + It("should handle keys with special characters", func() { + key1 := model.NetworkSetKey{Name: "z-special/netset"} + key2 := model.NetworkSetKey{Name: "a-special_netset"} + key3 := model.NetworkSetKey{Name: "m-special.netset"} + + it.InsertKey(testCIDR, key1) + it.InsertKey(testCIDR, key2) + it.InsertKey(testCIDR, key3) + + key, found := it.GetLongestPrefixCidr(testIP) + Expect(found).To(BeTrue()) + Expect(key).To(Equal(key2)) // "a-special_netset" is lexicographically smallest + }) + }) + + Context("when testing namespace-aware tie-breaking", func() { + It("should break ties within the same namespace", func() { + ns1Key1 := model.NetworkSetKey{Name: "namespace1/zebra-netset"} + ns1Key2 := model.NetworkSetKey{Name: "namespace1/alpha-netset"} + ns1Key3 := model.NetworkSetKey{Name: "namespace1/beta-netset"} + + it.InsertKey(testCIDR, ns1Key1) + it.InsertKey(testCIDR, ns1Key2) + it.InsertKey(testCIDR, ns1Key3) + + key, found := it.GetLongestPrefixCidrWithNamespaceIsolation(testIP, "namespace1") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(ns1Key2)) // namespace1/alpha-netset is lexicographically smallest + }) + + It("should prioritize global over other namespace keys when no preferred namespace match", func() { + ns1Key := model.NetworkSetKey{Name: "namespace2/some-netset"} + ns2Key := model.NetworkSetKey{Name: "namespace1/some-netset"} + globalKey := model.NetworkSetKey{Name: "global-netset"} + + it.InsertKey(testCIDR, ns1Key) + it.InsertKey(testCIDR, ns2Key) + it.InsertKey(testCIDR, globalKey) + + // Request a namespace that doesn't exist, should prefer global over other namespaces + key, found := it.GetLongestPrefixCidrWithNamespaceIsolation(testIP, "nonexistent") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(globalKey)) // Global key should be returned over other namespaces + + // Test fallback to other namespace when no global exists + it2 := NewIpTrie() + it2.InsertKey(testCIDR, ns1Key) + it2.InsertKey(testCIDR, ns2Key) + + key, found = it2.GetLongestPrefixCidrWithNamespaceIsolation(testIP, "nonexistent") + Expect(found).To(BeTrue()) + // Should return one of the namespace keys (lexicographically smallest) + Expect(key).To(Equal(ns2Key)) // namespace1/some-netset is lexicographically smaller + }) + + It("should break ties between global keys", func() { + globalKey1 := model.NetworkSetKey{Name: "zebra-global"} + globalKey2 := model.NetworkSetKey{Name: "alpha-global"} + + it.InsertKey(testCIDR, globalKey1) + it.InsertKey(testCIDR, globalKey2) + + key, found := it.GetLongestPrefixCidrWithNamespaceIsolation(testIP, "any-namespace") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(globalKey2)) // alpha-global is lexicographically smallest + }) + + It("should prefer namespace match over global even if global is lexicographically smaller", func() { + globalKey := model.NetworkSetKey{Name: "alpha-global"} + nsKey := model.NetworkSetKey{Name: "namespace1/zebra-netset"} + + it.InsertKey(testCIDR, globalKey) + it.InsertKey(testCIDR, nsKey) + + key, found := it.GetLongestPrefixCidrWithNamespaceIsolation(testIP, "namespace1") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(nsKey)) // Namespace match takes priority + }) + + It("should handle mixed global and namespace keys with proper tie-breaking", func() { + // Same prefix length, different key types + globalKey1 := model.NetworkSetKey{Name: "zebra-global"} + globalKey2 := model.NetworkSetKey{Name: "alpha-global"} + ns1Key1 := model.NetworkSetKey{Name: "namespace1/zebra-netset"} + ns1Key2 := model.NetworkSetKey{Name: "namespace1/alpha-netset"} + + it.InsertKey(testCIDR, globalKey1) + it.InsertKey(testCIDR, globalKey2) + it.InsertKey(testCIDR, ns1Key1) + it.InsertKey(testCIDR, ns1Key2) + + // When requesting namespace1, should get the lexicographically smallest within namespace1 + key, found := it.GetLongestPrefixCidrWithNamespaceIsolation(testIP, "namespace1") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(ns1Key2)) // namespace1/alpha-netset + + // When requesting non-existent namespace, should get lexicographically smallest global + key, found = it.GetLongestPrefixCidrWithNamespaceIsolation(testIP, "nonexistent") + Expect(found).To(BeTrue()) + Expect(key).To(Equal(globalKey2)) // alpha-global + }) + }) + + Context("when testing with different prefix lengths", func() { + It("should prioritize longer prefix over lexicographic order", func() { + // Longer prefix should win even if name is lexicographically larger + broadKey := model.NetworkSetKey{Name: "alpha-broad"} + narrowKey := model.NetworkSetKey{Name: "zebra-narrow"} + + broadCIDR := ip.MustParseCIDROrIP("10.0.0.0/16") // Less specific + narrowCIDR := ip.MustParseCIDROrIP("10.0.0.0/24") // More specific + + it.InsertKey(broadCIDR, broadKey) + it.InsertKey(narrowCIDR, narrowKey) + + key, found := it.GetLongestPrefixCidr(testIP) + Expect(found).To(BeTrue()) + Expect(key).To(Equal(narrowKey)) // Longer prefix wins despite larger name + }) + + It("should only apply tie-breaking when prefix lengths are equal", func() { + // Different prefix lengths with multiple keys each + broadKey1 := model.NetworkSetKey{Name: "zebra-broad"} + broadKey2 := model.NetworkSetKey{Name: "alpha-broad"} + narrowKey1 := model.NetworkSetKey{Name: "zebra-narrow"} + narrowKey2 := model.NetworkSetKey{Name: "alpha-narrow"} + + broadCIDR := ip.MustParseCIDROrIP("10.0.0.0/16") + narrowCIDR := ip.MustParseCIDROrIP("10.0.0.0/24") + + it.InsertKey(broadCIDR, broadKey1) + it.InsertKey(broadCIDR, broadKey2) + it.InsertKey(narrowCIDR, narrowKey1) + it.InsertKey(narrowCIDR, narrowKey2) + + key, found := it.GetLongestPrefixCidr(testIP) + Expect(found).To(BeTrue()) + // Should get alpha-narrow (lexicographically smallest among the longest prefix matches) + Expect(key).To(Equal(narrowKey2)) + }) + }) + + Context("when testing edge cases", func() { + It("should handle empty trie", func() { + _, found := it.GetLongestPrefixCidr(testIP) + Expect(found).To(BeFalse()) + }) + + It("should handle keys that differ only in casing", func() { + key1 := model.NetworkSetKey{Name: "Alpha-netset"} + key2 := model.NetworkSetKey{Name: "alpha-netset"} + + it.InsertKey(testCIDR, key1) + it.InsertKey(testCIDR, key2) + + key, found := it.GetLongestPrefixCidr(testIP) + Expect(found).To(BeTrue()) + // "Alpha-netset" comes before "alpha-netset" in ASCII ordering + Expect(key).To(Equal(key1)) + }) + + It("should handle numeric suffixes correctly", func() { + key1 := model.NetworkSetKey{Name: "netset-10"} + key2 := model.NetworkSetKey{Name: "netset-2"} + key3 := model.NetworkSetKey{Name: "netset-20"} + + it.InsertKey(testCIDR, key1) + it.InsertKey(testCIDR, key2) + it.InsertKey(testCIDR, key3) + + key, found := it.GetLongestPrefixCidr(testIP) + Expect(found).To(BeTrue()) + // String comparison: "netset-10" < "netset-2" < "netset-20" + Expect(key).To(Equal(key1)) + }) + + It("should handle very long key names", func() { + shortKey := model.NetworkSetKey{Name: "z"} + longKey := model.NetworkSetKey{Name: "a" + strings.Repeat("-very-long-name", 100)} + + it.InsertKey(testCIDR, shortKey) + it.InsertKey(testCIDR, longKey) + + key, found := it.GetLongestPrefixCidr(testIP) + Expect(found).To(BeTrue()) + Expect(key).To(Equal(longKey)) // Long key starting with "a" is lexicographically smaller + }) + }) +}) diff --git a/felix/calc/l3_route_resolver.go b/felix/calc/l3_route_resolver.go index 5f2abf82c49..349d1c00040 100644 --- a/felix/calc/l3_route_resolver.go +++ b/felix/calc/l3_route_resolver.go @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2021 Tigera, Inc. All rights reserved. +// Copyright (c) 2019-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ import ( "github.com/projectcalico/calico/felix/dispatcher" "github.com/projectcalico/calico/felix/ip" "github.com/projectcalico/calico/felix/proto" - apiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/encap" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" @@ -122,7 +122,7 @@ func (i l3rrNodeInfo) Equal(b l3rrNodeInfo) bool { l := len(i.Addresses) for ia, a := range i.Addresses { found := false - for j := 0; j < l; j++ { + for j := range l { if a == b.Addresses[(ia+j)%l] { found = true break @@ -153,9 +153,9 @@ func (i l3rrNodeInfo) AddressesAsCIDRs() []ip.CIDR { addrs[a] = struct{}{} } - // Clean up empty (uninitialized) addresses + // Clean up empty (uninitialized) or nil addresses for a := range addrs { - if a == emptyV4Addr || a == emptyV6Addr { + if a == nil || a == emptyV4Addr || a == emptyV6Addr { delete(addrs, a) } } @@ -284,22 +284,22 @@ func (c *L3RouteResolver) OnBlockUpdate(update api.Update) (_ bool) { // Now scan the old routes, looking for any that are no-longer associated with the block. // Remove no longer active routes from the cache and queue up deletions. - cachedRoutes.Iter(func(r nodenameRoute) error { + for r := range cachedRoutes.All() { c.maybeReportLive() // For each existing route which is no longer present, we need to delete it. // Note: since r.Key() only contains the destination, we need to check equality too in case // the gateway has changed. if newRoute, ok := newRoutes[r.Key()]; ok && newRoute == r { // Exists, and we want it to - nothing to do. - return nil + continue } // Current route is not in new set - we need to withdraw the route, and also // remove it from internal state. deletes.Add(r) logrus.WithField("route", r).Debug("Found stale route") - return set.RemoveItem - }) + cachedRoutes.Discard(r) + } // Now scan the new routes, looking for additions. Cache them and queue up adds. for _, r := range newRoutes { @@ -317,25 +317,22 @@ func (c *L3RouteResolver) OnBlockUpdate(update api.Update) (_ bool) { // At this point we've determined the correct diff to perform based on the block update. Queue up // updates. - deletes.Iter(func(nr nodenameRoute) error { + for nr := range deletes.All() { c.trie.RemoveBlockRoute(nr.dst) c.nodeRoutes.Remove(nr) - return nil - }) - adds.Iter(func(nr nodenameRoute) error { + } + for nr := range adds.All() { c.trie.UpdateBlockRoute(nr.dst, nr.nodeName) c.nodeRoutes.Add(nr) - return nil - }) + } } else { // Block has been deleted. Clean up routes that were contributed by this block. logrus.WithField("update", update).Debug("IPAM block deleted") routes := c.blockToRoutes[key] if routes != nil { - routes.Iter(func(nr nodenameRoute) error { + for nr := range routes.All() { c.trie.RemoveBlockRoute(nr.dst) - return nil - }) + } } delete(c.blockToRoutes, key) } @@ -345,7 +342,7 @@ func (c *L3RouteResolver) OnBlockUpdate(update api.Update) (_ bool) { func (c *L3RouteResolver) OnResourceUpdate(update api.Update) (_ bool) { // We only care about nodes, not other resources. resourceKey := update.Key.(model.ResourceKey) - if resourceKey.Kind != apiv3.KindNode { + if resourceKey.Kind != internalapi.KindNode { return } @@ -361,7 +358,7 @@ func (c *L3RouteResolver) OnResourceUpdate(update api.Update) (_ bool) { // Update our tracking data structures. var nodeInfo *l3rrNodeInfo if update.Value != nil { - node := update.Value.(*apiv3.Node) + node := update.Value.(*internalapi.Node) if node.Spec.BGP != nil && (node.Spec.BGP.IPv4Address != "" || node.Spec.BGP.IPv6Address != "") { bgp := node.Spec.BGP nodeInfo = &l3rrNodeInfo{} @@ -384,13 +381,13 @@ func (c *L3RouteResolver) OnResourceUpdate(update api.Update) (_ bool) { nodeInfo.V6CIDR = ip.CIDRFromCalicoNet(*caliNodeCIDRV6).(ip.V6CIDR) } } else { - ipv4, caliNodeCIDR := cresources.FindNodeAddress(node, apiv3.InternalIP, 4) + ipv4, caliNodeCIDR := cresources.FindNodeAddress(node, internalapi.InternalIP, 4) if ipv4 == nil { - ipv4, caliNodeCIDR = cresources.FindNodeAddress(node, apiv3.ExternalIP, 4) + ipv4, caliNodeCIDR = cresources.FindNodeAddress(node, internalapi.ExternalIP, 4) } - ipv6, caliNodeCIDRV6 := cresources.FindNodeAddress(node, apiv3.InternalIP, 6) + ipv6, caliNodeCIDRV6 := cresources.FindNodeAddress(node, internalapi.InternalIP, 6) if ipv6 == nil { - ipv6, caliNodeCIDRV6 = cresources.FindNodeAddress(node, apiv3.ExternalIP, 6) + ipv6, caliNodeCIDRV6 = cresources.FindNodeAddress(node, internalapi.ExternalIP, 6) } hasIPv4 := (ipv4 != nil && caliNodeCIDR != nil) hasIPv6 := (ipv6 != nil && caliNodeCIDRV6 != nil) @@ -590,7 +587,7 @@ func (c *L3RouteResolver) markAllNodeRoutesDirty(nodeName string) { } func (c *L3RouteResolver) visitAllRoutes(trie *ip.CIDRTrie, v func(route nodenameRoute)) { - trie.Visit(func(cidr ip.CIDR, data interface{}) bool { + trie.Visit(func(cidr ip.CIDR, data any) bool { c.maybeReportLive() // Construct a nodenameRoute to pass to the visiting function. @@ -661,10 +658,10 @@ func (c *L3RouteResolver) poolTypeForPool(pool *model.IPPool) proto.IPPoolType { if pool == nil { return proto.IPPoolType_NONE } - if pool.VXLANMode != encap.Undefined { + if pool.VXLANMode != encap.Never { return proto.IPPoolType_VXLAN } - if pool.IPIPMode != encap.Undefined { + if pool.IPIPMode != encap.Never { return proto.IPPoolType_IPIP } return proto.IPPoolType_NO_ENCAP @@ -712,7 +709,7 @@ func (c *L3RouteResolver) routesFromBlock(update api.Update) map[string]nodename // that it finds. func (c *L3RouteResolver) flush() { var buf []ip.CIDRTrieEntry - c.trie.dirtyCIDRs.Iter(func(cidr ip.CIDR) error { + for cidr := range c.trie.dirtyCIDRs.All() { logCxt := logrus.WithField("cidr", cidr) logCxt.Debug("Flushing dirty route") trie := c.trie.trieForCIDR(cidr) @@ -729,7 +726,8 @@ func (c *L3RouteResolver) flush() { if len(buf) == 0 { // CIDR is not in the trie. Nothing to do. Route removed before it had even been sent? logCxt.Debug("CIDR not in trie, ignoring.") - return set.RemoveItem + c.trie.dirtyCIDRs.Discard(cidr) + continue } // Otherwise, check if the route is removed. @@ -738,7 +736,8 @@ func (c *L3RouteResolver) flush() { logCxt.Debug("CIDR was sent before but now needs to be removed.") c.callbacks.OnRouteRemove(cidr.String()) c.trie.SetRouteSent(cidr, false) - return set.RemoveItem + c.trie.dirtyCIDRs.Discard(cidr) + continue } rt := &proto.RouteUpdate{ @@ -807,8 +806,11 @@ func (c *L3RouteResolver) flush() { rt.Borrowed = true } if ri.Refs[0].RefType == RefTypeWEP { - // This is not a tunnel ref, so must be a workload. + // This is explicitly a workload endpoint. if ri.Refs[0].NodeName == c.myNodeName { + // Flag that there's a live WEP on this IP; this avoids + // confusion in the dataplane if we have borrowed IPs + // (which get flagged as both local and remote!). rt.LocalWorkload = true rt.Types |= proto.RouteType_LOCAL_WORKLOAD } else { @@ -872,8 +874,8 @@ func (c *L3RouteResolver) flush() { c.trie.SetRouteSent(cidr, true) } - return set.RemoveItem - }) + c.trie.dirtyCIDRs.Discard(cidr) + } } // nodeInOurSubnet returns true if the IP of the given node is known and it's in our subnet. @@ -993,7 +995,7 @@ func (r *RouteTrie) UpdatePool(cidr ip.CIDR, poolType proto.IPPoolType, natOutgo func (r *RouteTrie) markChildrenDirty(cidr ip.CIDR) { // TODO: avoid full scan to mark children dirty trie := r.trieForCIDR(cidr) - trie.Visit(func(c ip.CIDR, data interface{}) bool { + trie.Visit(func(c ip.CIDR, data any) bool { r.OnAlive() if cidr.Contains(c.Addr()) { r.MarkCIDRDirty(c) diff --git a/felix/calc/l3_route_resolver_test.go b/felix/calc/l3_route_resolver_test.go index adaaad739a3..2a0c9fb1b42 100644 --- a/felix/calc/l3_route_resolver_test.go +++ b/felix/calc/l3_route_resolver_test.go @@ -15,7 +15,7 @@ package calc import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/ip" @@ -154,6 +154,17 @@ var _ = Describe("L3RouteResolver", func() { } Expect(info.AddressesAsCIDRs()).To(Equal([]ip.CIDR{})) }) + It("should not panic on nil addresses in AddressesAsCIDRs()", func() { + // Test for issue #11384: SIGSEGV on null address + // When ExternalIP is empty, a nil ip.Addr can end up in the Addresses slice. + // The nil check should prevent a panic when AsCIDR() is called. + info := l3rrNodeInfo{ + Addresses: []ip.Addr{nil, ip.FromString("192.168.0.1"), nil}, + } + cidrs := info.AddressesAsCIDRs() + Expect(cidrs).To(HaveLen(1)) + Expect(cidrs[0].String()).To(Equal("192.168.0.1/32")) + }) It("should consider VXLANV6Addr in Equal() method", func() { info1 := l3rrNodeInfo{ VXLANV6Addr: ip.FromString("dead:beef::1"), diff --git a/felix/calc/lookups_cache.go b/felix/calc/lookups_cache.go index b8a2464a51e..f547c14e7f4 100644 --- a/felix/calc/lookups_cache.go +++ b/felix/calc/lookups_cache.go @@ -79,6 +79,23 @@ func (lc *LookupsCache) GetNetworkSet(addr [16]byte) (EndpointData, bool) { return lc.nsCache.GetNetworkSetFromIP(addr) } +// GetNetworkSetWithNamespace returns the NetworkSet information for an address with namespace +// precedence. If preferredNamespace is provided, NetworkSets in that namespace are prioritized. +func (lc *LookupsCache) GetNetworkSetWithNamespace(addr [16]byte, preferredNamespace string) (EndpointData, bool) { + return lc.nsCache.GetNetworkSetFromIPWithNamespace(addr, preferredNamespace) +} + +// IsEndpointDeleted returns whether the given endpoint is marked for deletion. +func (lc *LookupsCache) IsEndpointDeleted(ep EndpointData) bool { + return lc.epCache.IsEndpointDeleted(ep) +} + +// MarkEndpointDeleted marks an endpoint as deleted for testing purposes. +// This should not be called from any mainline code. +func (lc *LookupsCache) MarkEndpointDeleted(ep EndpointData) { + lc.epCache.MarkEndpointForDeletion(ep) +} + // GetRuleIDFromNFLOGPrefix returns the RuleID associated with the supplied NFLOG prefix. func (lc *LookupsCache) GetRuleIDFromNFLOGPrefix(prefix [64]byte) *RuleID { return lc.polCache.GetRuleIDFromNFLOGPrefix(prefix) diff --git a/felix/calc/networkset_lookup_cache.go b/felix/calc/networkset_lookup_cache.go index c1bc1d4e051..fbb807353e6 100644 --- a/felix/calc/networkset_lookup_cache.go +++ b/felix/calc/networkset_lookup_cache.go @@ -104,7 +104,7 @@ func (nc *NetworkSetLookupsCache) RegisterWith(allUpdateDispatcher *dispatcher.D } // OnUpdate is the callback method registered with the AllUpdatesDispatcher for -// the model.NetworkSet type. This method updates the mapping between networkSets +// the NetworkSet type. This method updates the mapping between networkSets // and the corresponding CIDRs that they contain. func (nc *NetworkSetLookupsCache) OnUpdate(nsUpdate api.Update) (_ bool) { switch k := nsUpdate.Key.(type) { @@ -173,30 +173,44 @@ func (nc *NetworkSetLookupsCache) removeNetworkSet(key model.Key) { // We don't know about this networkset. Nothing to do. return } - currentData.cidrs.Iter(func(oldCIDR ip.CIDR) error { + for oldCIDR := range currentData.cidrs.All() { nc.ipTree.DeleteKey(oldCIDR, key) - return nil - }) + } delete(nc.networkSets, key) + nc.reportNetworksetCacheMetrics() } // GetNetworkSetFromIP finds Longest Prefix Match CIDR from given IP ADDR and return last observed // Networkset for that CIDR func (nc *NetworkSetLookupsCache) GetNetworkSetFromIP(addr [16]byte) (ed EndpointData, ok bool) { + return nc.GetNetworkSetFromIPWithNamespace(addr, "") +} + +// GetNetworkSetFromIPWithNamespace finds NetworkSet for the Given IP with namespace precedence. +// It prioritizes NetworkSets in the preferredNamespace, falling back to longest prefix match of +// the global networkSets if none found, then the first lexicographically ordered matching +// NetworkSet if no other matches are found. If no preferred namespace is provided, it prioritizes +// global NetworkSets. +func (nc *NetworkSetLookupsCache) GetNetworkSetFromIPWithNamespace(ipAddr [16]byte, preferredNamespace string) (ed EndpointData, ok bool) { + netIP := net.IP(ipAddr[:]) + addr := ip.FromNetIP(netIP) + nc.nsMutex.RLock() defer nc.nsMutex.RUnlock() - // Find the first cidr that contains the ip address to use for the lookup. - ipAddr := ip.FromNetIP(net.IP(addr[:])) - if key, _ := nc.ipTree.GetLongestPrefixCidr(ipAddr); key != nil { - if ns := nc.networkSets[key]; ns != nil { - // Found a NetworkSet, so set the return variables. - ed = ns - ok = true - } + // Use the namespace isolation lookup from IpTrie for collector use case + key, found := nc.ipTree.GetLongestPrefixCidrWithNamespaceIsolation(addr, preferredNamespace) + if !found { + return nil, false } - return + + // Get the NetworkSet data for the key + if ns := nc.networkSets[key]; ns != nil { + return ns, true + } + + return nil, false } func (nc *NetworkSetLookupsCache) DumpNetworksets() string { @@ -206,10 +220,9 @@ func (nc *NetworkSetLookupsCache) DumpNetworksets() string { lines = append(lines, "-------") for key, ns := range nc.networkSets { cidrStr := []string{} - ns.cidrs.Iter(func(cidr ip.CIDR) error { + for cidr := range ns.cidrs.All() { cidrStr = append(cidrStr, cidr.String()) - return nil - }) + } domainStr := []string{} lines = append(lines, key.(model.NetworkSetKey).Name, diff --git a/felix/calc/networkset_lookup_cache_test.go b/felix/calc/networkset_lookup_cache_test.go index 5585a09bba7..ebd9f9f049b 100644 --- a/felix/calc/networkset_lookup_cache_test.go +++ b/felix/calc/networkset_lookup_cache_test.go @@ -17,8 +17,7 @@ package calc_test import ( "net" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/projectcalico/calico/felix/calc" @@ -113,8 +112,22 @@ var _ = Describe("NetworkSetLookupsCache IP tests", func() { By("verifying networkset2 is in the mapping") // This check validates that netSet2 is found since one subnet is outside the range of netSet1's subnets. - for _, cidr = range netSet2.Nets { - verifyIpToNetworkset(netSet2Key, cidr.IP, true, netSet2Labels) + for _, cidr := range netSet2.Nets { + // For overlapping CIDRs (12.0.0.0/24), lowest-lexicographic-name-wins applies + // netSet1 ("netset-1") comes before netSet2 ("netset-2") lexicographically, so netSet1 wins + // For unique CIDRs (13.1.0.0/24), netSet2 should still be returned + var expectedKey model.Key + var expectedLabels map[string]string + if cidr.String() == "12.0.0.0/24" { + // This overlaps with netSet1, so netSet1 should win due to lexicographic ordering + expectedKey = netSet1Key + expectedLabels = origNetSetLabels + } else { + // This is unique to netSet2 + expectedKey = netSet2Key + expectedLabels = netSet2Labels + } + verifyIpToNetworkset(expectedKey, cidr.IP, true, expectedLabels) } By("deleting networkset2") diff --git a/felix/calc/policy_lookup_cache.go b/felix/calc/policy_lookup_cache.go index ab12a9e2953..d88f22f07af 100644 --- a/felix/calc/policy_lookup_cache.go +++ b/felix/calc/policy_lookup_cache.go @@ -25,8 +25,8 @@ import ( "github.com/projectcalico/calico/felix/idalloc" "github.com/projectcalico/calico/felix/rules" + "github.com/projectcalico/calico/felix/types" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/names" "github.com/projectcalico/calico/libcalico-go/lib/set" ) @@ -35,12 +35,10 @@ type pcRuleID struct { id64 uint64 } -var ( - gaugePolicyCacheLength = prometheus.NewGauge(prometheus.GaugeOpts{ - Name: "felix_collector_lookups_cache_policies", - Help: "Total number of entries currently residing in the endpoints lookup cache.", - }) -) +var gaugePolicyCacheLength = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "felix_collector_lookups_cache_policies", + Help: "Total number of entries currently residing in the endpoints lookup cache.", +}) // PolicyLookupsCache provides an API to lookup policy to NFLOG prefix mapping. // To do this, the PolicyLookupsCache hooks into the calculation graph @@ -55,7 +53,8 @@ type PolicyLookupsCache struct { useIDs bool ids *idalloc.IDAllocator - tierRefs map[string]int + tierRefs map[string]int + keyToTier map[model.PolicyKey]string } func NewPolicyLookupsCache() *PolicyLookupsCache { @@ -64,17 +63,18 @@ func NewPolicyLookupsCache() *PolicyLookupsCache { nflogPrefixesProfile: map[model.ProfileRulesKey]set.Set[string]{}, nflogPrefixHash: map[[64]byte]pcRuleID{}, tierRefs: map[string]int{}, + keyToTier: map[model.PolicyKey]string{}, ids: idalloc.New(), } // Add NFLog mappings for the no-profile match. pc.addNFLogPrefixEntry( rules.CalculateNoMatchProfileNFLOGPrefixStr(rules.RuleDirIngress), - NewRuleID("", "", "", 0, rules.RuleDirIngress, rules.RuleActionDeny), + NewRuleID("", "", "", "", 0, rules.RuleDirIngress, rules.RuleActionDeny), ) pc.addNFLogPrefixEntry( rules.CalculateNoMatchProfileNFLOGPrefixStr(rules.RuleDirEgress), - NewRuleID("", "", "", 0, rules.RuleDirEgress, rules.RuleActionDeny), + NewRuleID("", "", "", "", 0, rules.RuleDirEgress, rules.RuleActionDeny), ) return pc @@ -141,41 +141,44 @@ func (pc *PolicyLookupsCache) deleteNFLogPrefixEntry(prefix string) { // updatePolicyRulesNFLOGPrefixes stores the required prefix to RuleID maps for a policy, deleting any // stale entries if the number of rules or action types have changed. func (pc *PolicyLookupsCache) updatePolicyRulesNFLOGPrefixes(key model.PolicyKey, policy *model.Policy) { + // Track mapping of key to tier, allowing us to manage tier reference counts. If the tier for a policy + // changes, we need to treat that as a removal from the old tier and an addition to the new tier. + oldTier, ok := pc.keyToTier[key] + if ok && oldTier != policy.Tier { + pc.removePolicyRulesNFLOGPrefixes(key) + } + pc.keyToTier[key] = policy.Tier + // If this is the first time we have seen this tier, add the default deny entries for the tier, and the default // pass (for staged-only tiers). - count, ok := pc.tierRefs[key.Tier] + count, ok := pc.tierRefs[policy.Tier] if !ok { pc.addNFLogPrefixEntry( - rules.CalculateEndOfTierDropNFLOGPrefixStr(rules.RuleDirIngress, key.Tier), - NewRuleID(key.Tier, "", "", 0, rules.RuleDirIngress, rules.RuleActionDeny), + rules.CalculateEndOfTierDropNFLOGPrefixStr(rules.RuleDirIngress, policy.Tier), + NewRuleID("", policy.Tier, "", "", 0, rules.RuleDirIngress, rules.RuleActionDeny), ) pc.addNFLogPrefixEntry( - rules.CalculateEndOfTierDropNFLOGPrefixStr(rules.RuleDirEgress, key.Tier), - NewRuleID(key.Tier, "", "", 0, rules.RuleDirEgress, rules.RuleActionDeny), + rules.CalculateEndOfTierDropNFLOGPrefixStr(rules.RuleDirEgress, policy.Tier), + NewRuleID("", policy.Tier, "", "", 0, rules.RuleDirEgress, rules.RuleActionDeny), ) pc.addNFLogPrefixEntry( - rules.CalculateEndOfTierPassNFLOGPrefixStr(rules.RuleDirIngress, key.Tier), - NewRuleID(key.Tier, "", "", 0, rules.RuleDirIngress, rules.RuleActionPass), + rules.CalculateEndOfTierPassNFLOGPrefixStr(rules.RuleDirIngress, policy.Tier), + NewRuleID("", policy.Tier, "", "", 0, rules.RuleDirIngress, rules.RuleActionPass), ) pc.addNFLogPrefixEntry( - rules.CalculateEndOfTierPassNFLOGPrefixStr(rules.RuleDirEgress, key.Tier), - NewRuleID(key.Tier, "", "", 0, rules.RuleDirEgress, rules.RuleActionPass), + rules.CalculateEndOfTierPassNFLOGPrefixStr(rules.RuleDirEgress, policy.Tier), + NewRuleID("", policy.Tier, "", "", 0, rules.RuleDirEgress, rules.RuleActionPass), ) } - pc.tierRefs[key.Tier] = count + 1 - - namespace, tier, name, err := names.DeconstructPolicyName(key.Name) - if err != nil { - log.WithError(err).Error("Unable to parse policy name") - return - } + pc.tierRefs[policy.Tier] = count + 1 oldPrefixes := pc.nflogPrefixesPolicy[key] pc.nflogPrefixesPolicy[key] = pc.updateRulesNFLOGPrefixes( + &types.PolicyID{Name: key.Name, Namespace: key.Namespace, Kind: key.Kind}, + key.Kind, + key.Namespace, key.Name, - namespace, - tier, - name, + policy.Tier, oldPrefixes, policy.InboundRules, policy.OutboundRules, @@ -186,19 +189,28 @@ func (pc *PolicyLookupsCache) updatePolicyRulesNFLOGPrefixes(key model.PolicyKey // removePolicyRulesNFLOGPrefixes removes the prefix to RuleID maps for a policy. func (pc *PolicyLookupsCache) removePolicyRulesNFLOGPrefixes(key model.PolicyKey) { - // If this is the last entry for the tier, remove the default action entries for the tier. - // Increment the reference count so that we don't keep adding tiers. - count := pc.tierRefs[key.Tier] + // Look up the tier for this policy. + tier, ok := pc.keyToTier[key] + if !ok { + // We have never seen this policy. Nothing to do. + log.Warnf("Attempted to remove unknown policy %v from PolicyLookupsCache", key) + return + } + delete(pc.keyToTier, key) + + count := pc.tierRefs[tier] if count == 1 { - delete(pc.tierRefs, key.Tier) + // This is the last entry for the tier, remove the default action entries for the tier. + delete(pc.tierRefs, tier) pc.deleteNFLogPrefixEntry( - rules.CalculateEndOfTierDropNFLOGPrefixStr(rules.RuleDirIngress, key.Tier), + rules.CalculateEndOfTierDropNFLOGPrefixStr(rules.RuleDirIngress, tier), ) pc.deleteNFLogPrefixEntry( - rules.CalculateEndOfTierDropNFLOGPrefixStr(rules.RuleDirEgress, key.Tier), + rules.CalculateEndOfTierDropNFLOGPrefixStr(rules.RuleDirEgress, tier), ) } else { - pc.tierRefs[key.Tier] = count - 1 + // Decrement the reference count. + pc.tierRefs[tier] = count - 1 } oldPrefixes := pc.nflogPrefixesPolicy[key] @@ -213,10 +225,11 @@ func (pc *PolicyLookupsCache) removePolicyRulesNFLOGPrefixes(key model.PolicyKey func (pc *PolicyLookupsCache) updateProfileRulesNFLOGPrefixes(key model.ProfileRulesKey, profile *model.ProfileRules) { oldPrefixes := pc.nflogPrefixesProfile[key] pc.nflogPrefixesProfile[key] = pc.updateRulesNFLOGPrefixes( - key.Name, + &types.ProfileID{Name: key.Name}, "", "", key.Name, + "", oldPrefixes, profile.InboundRules, profile.OutboundRules, @@ -235,7 +248,11 @@ func (pc *PolicyLookupsCache) removeProfileRulesNFLOGPrefixes(key model.ProfileR // settings. This method adds any new rules and removes any obsolete rules. // TODO (rlb): Maybe we should do a lazy clean up of rules? func (pc *PolicyLookupsCache) updateRulesNFLOGPrefixes( - v1Name, namespace, tier, name string, oldPrefixes set.Set[string], ingress []model.Rule, egress []model.Rule, + id types.IDMaker, + kind, namespace, name, tier string, + oldPrefixes set.Set[string], + ingress []model.Rule, + egress []model.Rule, ) set.Set[string] { newPrefixes := set.New[string]() @@ -256,19 +273,19 @@ func (pc *PolicyLookupsCache) updateRulesNFLOGPrefixes( } for ii, rule := range ingress { action := convertAction(rule.Action) - prefix := rules.CalculateNFLOGPrefixStr(action, owner, rules.RuleDirIngress, ii, v1Name) + prefix := rules.CalculateNFLOGPrefixStr(action, owner, rules.RuleDirIngress, ii, id) pc.addNFLogPrefixEntry( prefix, - NewRuleID(tier, name, namespace, ii, rules.RuleDirIngress, action), + NewRuleID(kind, tier, name, namespace, ii, rules.RuleDirIngress, action), ) newPrefixes.Add(prefix) } for ii, rule := range egress { action := convertAction(rule.Action) - prefix := rules.CalculateNFLOGPrefixStr(action, owner, rules.RuleDirEgress, ii, v1Name) + prefix := rules.CalculateNFLOGPrefixStr(action, owner, rules.RuleDirEgress, ii, id) pc.addNFLogPrefixEntry( prefix, - NewRuleID(tier, name, namespace, ii, rules.RuleDirEgress, action), + NewRuleID(kind, tier, name, namespace, ii, rules.RuleDirEgress, action), ) newPrefixes.Add(prefix) } @@ -277,30 +294,29 @@ func (pc *PolicyLookupsCache) updateRulesNFLOGPrefixes( // actually map to the end-of-tier defaultActions associated with that policy since that is how // they will be reported by the collector. The collector will only report these stats if we hit // the end-of-tier pass indicating that the tier contains only staged policies. - if model.PolicyIsStaged(v1Name) { - prefix := rules.CalculateNoMatchPolicyNFLOGPrefixStr(rules.RuleDirIngress, v1Name) + if model.KindIsStaged(kind) { + prefix := rules.CalculateNoMatchPolicyNFLOGPrefixStr(rules.RuleDirIngress, id) pc.addNFLogPrefixEntry( prefix, - NewRuleID(tier, name, namespace, RuleIndexTierDefaultAction, rules.RuleDirIngress, rules.RuleActionDeny), + NewRuleID(kind, tier, name, namespace, RuleIndexTierDefaultAction, rules.RuleDirIngress, rules.RuleActionDeny), ) newPrefixes.Add(prefix) - prefix = rules.CalculateNoMatchPolicyNFLOGPrefixStr(rules.RuleDirEgress, v1Name) + prefix = rules.CalculateNoMatchPolicyNFLOGPrefixStr(rules.RuleDirEgress, id) pc.addNFLogPrefixEntry( prefix, - NewRuleID(tier, name, namespace, RuleIndexTierDefaultAction, rules.RuleDirEgress, rules.RuleActionDeny), + NewRuleID(kind, tier, name, namespace, RuleIndexTierDefaultAction, rules.RuleDirEgress, rules.RuleActionDeny), ) newPrefixes.Add(prefix) } // Delete the stale prefixes. if oldPrefixes != nil { - oldPrefixes.Iter(func(item string) error { + for item := range oldPrefixes.All() { if !newPrefixes.Contains(item) { pc.deleteNFLogPrefixEntry(item) } - return nil - }) + } } return newPrefixes @@ -309,10 +325,9 @@ func (pc *PolicyLookupsCache) updateRulesNFLOGPrefixes( // deleteRulesNFLOGPrefixes deletes the supplied set of prefixes. func (pc *PolicyLookupsCache) deleteRulesNFLOGPrefixes(prefixes set.Set[string]) { if prefixes != nil { - prefixes.Iter(func(item string) error { + for item := range prefixes.All() { pc.deleteNFLogPrefixEntry(item) - return nil - }) + } } } @@ -370,15 +385,17 @@ const ( ) type PolicyID struct { - // The tier name. If this is blank this represents a Profile backed rule. - Tier string + // The kind of policy. + Kind string + + // The namespace. This is only non-blank for a NetworkPolicy type. For Tiers, GlobalNetworkPolicies and the + // no match rules this will be blank. + Namespace string + // The policy or profile name. This has the tier removed from the name. If this is blank, this represents // a "no match" rule. For k8s policies, this will be the full v3 name (knp.default.) - this avoids // name conflicts with Calico policies. Name string - // The namespace. This is only non-blank for a NetworkPolicy type. For Tiers, GlobalNetworkPolicies and the - // no match rules this will be blank. - Namespace string } // RuleID contains the complete identifiers for a particular rule. This is a breakdown of the @@ -394,25 +411,28 @@ type RuleID struct { IndexStr string // The rule action. Action rules.RuleAction + // The tier of the policy. Empty for profiles. + Tier string // Optimization so that the hot path doesn't need to create strings. - dpName string fpName string } -func NewRuleID(tier, policy, namespace string, ruleIndex int, ruleDirection rules.RuleDir, ruleAction rules.RuleAction) *RuleID { +func NewRuleID(kind, tier, name, namespace string, ruleIndex int, ruleDirection rules.RuleDir, ruleAction rules.RuleAction) *RuleID { rid := &RuleID{ + // Note: we use a PolicyID here even for profiles to avoid creating a separate struct. Profile type RuleIDs + // will have empty Kind and Namespace, and empty Tier in the RuleID itself. PolicyID: PolicyID{ - Tier: tier, - Name: policy, + Name: name, Namespace: namespace, + Kind: kind, }, + Tier: tier, Direction: ruleDirection, Index: ruleIndex, IndexStr: strconv.Itoa(ruleIndex), Action: ruleAction, } - rid.setDeniedPacketRuleName() rid.setFlowLogPolicyName() return rid } @@ -426,8 +446,8 @@ func (r *RuleID) Equals(r2 *RuleID) bool { func (r *RuleID) String() string { return fmt.Sprintf( - "Rule(Tier=%s,Name=%s,Namespace=%s,Direction=%s,Index=%s,Action=%s)", - r.TierString(), r.NameString(), r.NamespaceString(), r.DirectionString(), r.IndexStr, r.ActionString(), + "Rule(Tier=%s,Kind=%s,Name=%s,Namespace=%s,Direction=%s,Index=%s,Action=%s)", + r.Tier, r.Kind, r.Name, r.Namespace, r.Direction, r.IndexStr, r.Action, ) } @@ -436,7 +456,7 @@ func (r *RuleID) IsNamespaced() bool { } func (r *RuleID) IsProfile() bool { - return len(r.Tier) == 0 + return len(r.Tier) == 0 || r.Tier == ProfileTierStr } func (r *RuleID) IsEndOfTier() bool { @@ -499,61 +519,53 @@ func (r *RuleID) DirectionString() string { return "" } -func (r *RuleID) setDeniedPacketRuleName() { - if r.Action != rules.RuleActionDeny { - return - } - if !r.IsNamespaced() { - r.dpName = fmt.Sprintf( - "%s|%s|%s|%s", +func (r *RuleID) setFlowLogPolicyName() { + if r.IsProfile() { + // This is a profile rule. + r.fpName = fmt.Sprintf( + // |:| + "%s|pro:%s|%s", r.TierString(), r.NameString(), - r.IndexStr, r.ActionString(), ) return - } - r.dpName = fmt.Sprintf( - "%s|%s/%s|%s|%s", - r.TierString(), - r.Namespace, - r.NameString(), - r.IndexStr, - r.ActionString(), - ) -} - -func (r *RuleID) GetDeniedPacketRuleName() string { - if r == nil { - return "" - } - return r.dpName -} - -func (r *RuleID) setFlowLogPolicyName() { - if !r.IsNamespaced() { + } else if r.Kind == "" { + // This is not a profile rule, nor a known policy kind. This makes it an end-of-tier rule. r.fpName = fmt.Sprintf( - "%s|%s.%s|%s", - r.TierString(), + "%s|eot:%s|%s", r.TierString(), - r.NameString(), + NoMatchNameStr, r.ActionString(), ) - } else if strings.HasPrefix(r.Name, names.K8sNetworkPolicyNamePrefix) || - strings.HasPrefix(r.Name, model.PolicyNamePrefixStaged+names.K8sNetworkPolicyNamePrefix) { + return + } + + // Construct the flow log policy name based on the kind of rule. This varies based on + // whether the policy is namespaced or not. + id := types.PolicyID{ + Kind: r.Kind, + Namespace: r.Namespace, + Name: r.Name, + } + if r.IsNamespaced() { + // Namespaced policy. r.fpName = fmt.Sprintf( - "%s|%s/%s|%s", + // |:/| + "%s|%s:%s/%s|%s", r.TierString(), - r.Namespace, + id.KindShortName(), + r.NamespaceString(), r.NameString(), r.ActionString(), ) } else { + // Non-namespaced policy. r.fpName = fmt.Sprintf( - "%s|%s/%s.%s|%s", - r.TierString(), - r.Namespace, + // |:| + "%s|%s:%s|%s", r.TierString(), + id.KindShortName(), r.NameString(), r.ActionString(), ) diff --git a/felix/calc/policy_lookups_cache_test.go b/felix/calc/policy_lookups_cache_test.go index b8530d9edd8..5cb56bc7cf0 100644 --- a/felix/calc/policy_lookups_cache_test.go +++ b/felix/calc/policy_lookups_cache_test.go @@ -15,9 +15,11 @@ package calc_test import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + "fmt" + + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" . "github.com/projectcalico/calico/felix/calc" "github.com/projectcalico/calico/felix/rules" @@ -28,17 +30,20 @@ var ( // Define a set of policies and profiles - these just contain the bare bones info for the // tests. - // GlobalNetworkPolicy tier-1.policy1, three variations + // GlobalNetworkPolicy policy1, three variations gnp1_t1_0i0e_key = model.PolicyKey{ + Name: "policy-1.2.3", + Kind: v3.KindGlobalNetworkPolicy, + } + gnp1_t1_0i0e = &model.Policy{ Tier: "tier-1", - Name: "tier-1.policy-1.2.3", } - gnp1_t1_0i0e = &model.Policy{} gnp1_t1_1i1e_key = model.PolicyKey{ - Tier: "tier-1", - Name: "tier-1.policy-1.2.3", + Name: "policy-1.2.3", + Kind: v3.KindGlobalNetworkPolicy, } gnp1_t1_1i1e = &model.Policy{ + Tier: "tier-1", InboundRules: []model.Rule{ {Action: "allow"}, }, @@ -46,16 +51,17 @@ var ( {Action: "deny"}, }, } - prefix_gnp1_t1_i0A = toprefix("API0|tier-1.policy-1.2.3") - ruleID_gnp1_t1_i0A = NewRuleID("tier-1", "policy-1.2.3", "", 0, rules.RuleDirIngress, rules.RuleActionAllow) - prefix_gnp1_t1_e0D = toprefix("DPE0|tier-1.policy-1.2.3") - ruleID_gnp1_t1_e0D = NewRuleID("tier-1", "policy-1.2.3", "", 0, rules.RuleDirEgress, rules.RuleActionDeny) + prefix_gnp1_t1_i0A = toprefix("API0|gnp/policy-1.2.3") + ruleID_gnp1_t1_i0A = NewRuleID(v3.KindGlobalNetworkPolicy, "tier-1", "policy-1.2.3", "", 0, rules.RuleDirIngress, rules.RuleActionAllow) + prefix_gnp1_t1_e0D = toprefix("DPE0|gnp/policy-1.2.3") + ruleID_gnp1_t1_e0D = NewRuleID(v3.KindGlobalNetworkPolicy, "tier-1", "policy-1.2.3", "", 0, rules.RuleDirEgress, rules.RuleActionDeny) gnp1_t1_4i2e_key = model.PolicyKey{ - Tier: "tier-1", - Name: "tier-1.policy-1.2.3", + Name: "policy-1.2.3", + Kind: v3.KindGlobalNetworkPolicy, } gnp1_t1_4i2e = &model.Policy{ + Tier: "tier-1", InboundRules: []model.Rule{ {Action: "allow"}, {Action: "deny"}, {Action: "pass"}, {Action: "next-tier"}, }, @@ -63,79 +69,89 @@ var ( {Action: "allow"}, {Action: "allow"}, }, } - //prefix_gnp1_t1_i0A defined above - //ruleID_gnp1_t1_i0A defined above - prefix_gnp1_t1_i1D = toprefix("DPI1|tier-1.policy-1.2.3") - ruleID_gnp1_t1_i1D = NewRuleID("tier-1", "policy-1.2.3", "", 1, rules.RuleDirIngress, rules.RuleActionDeny) - prefix_gnp1_t1_i2N = toprefix("PPI2|tier-1.policy-1.2.3") - ruleID_gnp1_t1_i2N = NewRuleID("tier-1", "policy-1.2.3", "", 2, rules.RuleDirIngress, rules.RuleActionPass) - prefix_gnp1_t1_i3P = toprefix("PPI3|tier-1.policy-1.2.3") - ruleID_gnp1_t1_i3P = NewRuleID("tier-1", "policy-1.2.3", "", 3, rules.RuleDirIngress, rules.RuleActionPass) - prefix_gnp1_t1_e0A = toprefix("APE0|tier-1.policy-1.2.3") - ruleID_gnp1_t1_e0A = NewRuleID("tier-1", "policy-1.2.3", "", 0, rules.RuleDirEgress, rules.RuleActionAllow) - prefix_gnp1_t1_e1A = toprefix("APE1|tier-1.policy-1.2.3") - ruleID_gnp1_t1_e1A = NewRuleID("tier-1", "policy-1.2.3", "", 1, rules.RuleDirEgress, rules.RuleActionAllow) - - // NetworkPolicy namespace-1/tier-1.policy-1 + // prefix_gnp1_t1_i0A defined above + // ruleID_gnp1_t1_i0A defined above + prefix_gnp1_t1_i1D = toprefix("DPI1|gnp/policy-1.2.3") + ruleID_gnp1_t1_i1D = NewRuleID(v3.KindGlobalNetworkPolicy, "tier-1", "policy-1.2.3", "", 1, rules.RuleDirIngress, rules.RuleActionDeny) + prefix_gnp1_t1_i2N = toprefix("PPI2|gnp/policy-1.2.3") + ruleID_gnp1_t1_i2N = NewRuleID(v3.KindGlobalNetworkPolicy, "tier-1", "policy-1.2.3", "", 2, rules.RuleDirIngress, rules.RuleActionPass) + prefix_gnp1_t1_i3P = toprefix("PPI3|gnp/policy-1.2.3") + ruleID_gnp1_t1_i3P = NewRuleID(v3.KindGlobalNetworkPolicy, "tier-1", "policy-1.2.3", "", 3, rules.RuleDirIngress, rules.RuleActionPass) + prefix_gnp1_t1_e0A = toprefix("APE0|gnp/policy-1.2.3") + ruleID_gnp1_t1_e0A = NewRuleID(v3.KindGlobalNetworkPolicy, "tier-1", "policy-1.2.3", "", 0, rules.RuleDirEgress, rules.RuleActionAllow) + prefix_gnp1_t1_e1A = toprefix("APE1|gnp/policy-1.2.3") + ruleID_gnp1_t1_e1A = NewRuleID(v3.KindGlobalNetworkPolicy, "tier-1", "policy-1.2.3", "", 1, rules.RuleDirEgress, rules.RuleActionAllow) + + // NetworkPolicy namespace-1/policy-2 np1_t1_0i1e_key = model.PolicyKey{ - Tier: "tier-1", - Name: "namespace-1/tier-1.policy-2", + Name: "policy-2", + Namespace: "namespace-1", + Kind: v3.KindNetworkPolicy, } np1_t1_0i1e = &model.Policy{ + Tier: "tier-1", OutboundRules: []model.Rule{ {Action: "allow"}, }, } - prefix_np1_t1_e0A = toprefix("APE0|namespace-1/tier-1.policy-2") - ruleID_np1_t1_e0A = NewRuleID("tier-1", "policy-2", "namespace-1", 0, rules.RuleDirEgress, rules.RuleActionAllow) + prefix_np1_t1_e0A = toprefix("APE0|np/namespace-1/policy-2") + ruleID_np1_t1_e0A = NewRuleID(v3.KindNetworkPolicy, "tier-1", "policy-2", "namespace-1", 0, rules.RuleDirEgress, rules.RuleActionAllow) // K8s NetworkPolicy namespace-1/knp.default.policy-1.1 knp1_t1_1i0e_key = model.PolicyKey{ - Tier: "default", - Name: "namespace-1/knp.default.policy-1.1.1.1", + Name: "knp.default.policy-1.1.1.1", + Namespace: "namespace-1", + Kind: model.KindKubernetesNetworkPolicy, } knp1_t1_1i0e = &model.Policy{ + Tier: "default", InboundRules: []model.Rule{ {Action: "deny"}, }, } - prefix_knp1_t1_i0D = toprefix("DPI0|namespace-1/knp.default.policy-1.1.1.1") - ruleID_knp1_t1_i0D = NewRuleID("default", "knp.default.policy-1.1.1.1", "namespace-1", 0, rules.RuleDirIngress, rules.RuleActionDeny) + prefix_knp1_t1_i0D = toprefix("DPI0|knp/namespace-1/knp.default.policy-1.1.1.1") + ruleID_knp1_t1_i0D = NewRuleID(model.KindKubernetesNetworkPolicy, "default", "knp.default.policy-1.1.1.1", "namespace-1", 0, rules.RuleDirIngress, rules.RuleActionDeny) - // K8s AdminNetworkPolicy kanp.adminnetworkpolicy.policy-1.1 - kanp1_t1_1i0e_key = model.PolicyKey{ - Tier: "adminnetworkpolicy", - Name: "kanp.adminnetworkpolicy.policy-1.1.1.1", + // K8s ClusterNetworkPolicy kcnp.kube-admin.policy-1.1 + kcnpAdmin1_t1_1i0e_key = model.PolicyKey{ + Name: "kcnp.kube-admin.policy-1.1.1.1", + Kind: model.KindKubernetesClusterNetworkPolicy, } - kanp1_t1_1i0e = &model.Policy{ + kcnpAdmin1_t1_1i0e = &model.Policy{ + Tier: "kube-admin", InboundRules: []model.Rule{ {Action: "deny"}, }, } - prefix_kanp1_t1_i0D = toprefix("DPI0|kanp.adminnetworkpolicy.policy-1.1.1.1") - ruleID_kanp1_t1_i0D = NewRuleID( - "adminnetworkpolicy", - "kanp.adminnetworkpolicy.policy-1.1.1.1", + + prefix_kcnpAdmin1_t1_i0D = toprefix("DPI0|kcnp/kcnp.kube-admin.policy-1.1.1.1") + ruleID_kcnpAdmin1_t1_i0D = NewRuleID( + model.KindKubernetesClusterNetworkPolicy, + "kube-admin", + "kcnp.kube-admin.policy-1.1.1.1", "", 0, rules.RuleDirIngress, rules.RuleActionDeny, ) - // K8s BaselineAdminNetworkPolicy kbanp.baselineadminnetworkpolicy.policy-1.1 - kbanp1_t1_1i0e_key = model.PolicyKey{ - Tier: "baselineadminnetworkpolicy", - Name: "kbanp.baselineadminnetworkpolicy.policy-1.1.1.1", + // K8s ClusterNetworkPolicy kcnp.kube-baseline.policy-1.1 + kcnpBaseline1_t1_1i0e_key = model.PolicyKey{ + Name: "kcnp.kube-baseline.policy-1.1.1.1", + Kind: model.KindKubernetesClusterNetworkPolicy, } - kbanp1_t1_1i0e = &model.Policy{ + kcnpBaseline1_t1_1i0e = &model.Policy{ + Tier: "kube-baseline", InboundRules: []model.Rule{ {Action: "deny"}, }, } - prefix_kbanp1_t1_i0D = toprefix("DPI0|kbanp.baselineadminnetworkpolicy.policy-1.1.1.1") - ruleID_kbanp1_t1_i0D = NewRuleID( - "baselineadminnetworkpolicy", - "kbanp.baselineadminnetworkpolicy.policy-1.1.1.1", + + prefix_kcnpBaseline1_t1_i0D = toprefix("DPI0|kcnp/kcnp.kube-baseline.policy-1.1.1.1") + ruleID_kcnpBaseline1_t1_i0D = NewRuleID( + model.KindKubernetesClusterNetworkPolicy, + "kube-baseline", + "kcnp.kube-baseline.policy-1.1.1.1", "", 0, rules.RuleDirIngress, @@ -156,32 +172,32 @@ var ( } prefix_prof_i0D = toprefix("DRI0|profile-1") prefix_prof_e0D = toprefix("DRE0|profile-1") - ruleID_prof_i0D = NewRuleID("", "profile-1", "", 0, rules.RuleDirIngress, rules.RuleActionDeny) - ruleID_prof_e0D = NewRuleID("", "profile-1", "", 0, rules.RuleDirEgress, rules.RuleActionDeny) + ruleID_prof_i0D = NewRuleID("", "", "profile-1", "", 0, rules.RuleDirIngress, rules.RuleActionDeny) + ruleID_prof_e0D = NewRuleID("", "", "profile-1", "", 0, rules.RuleDirEgress, rules.RuleActionDeny) // Tier no-matches - prefix_nomatch_t1_i = toprefix("DPI|tier-1") - ruleID_nomatch_t1_i = NewRuleID("tier-1", "", "", 0, rules.RuleDirIngress, rules.RuleActionDeny) - prefix_nomatch_t1_e = toprefix("DPE|tier-1") - ruleID_nomatch_t1_e = NewRuleID("tier-1", "", "", 0, rules.RuleDirEgress, rules.RuleActionDeny) - prefix_nomatch_td_i = toprefix("DPI|default") - ruleID_nomatch_td_i = NewRuleID("default", "", "", 0, rules.RuleDirIngress, rules.RuleActionDeny) - prefix_nomatch_td_e = toprefix("DPE|default") - ruleID_nomatch_td_e = NewRuleID("default", "", "", 0, rules.RuleDirEgress, rules.RuleActionDeny) - prefix_nomatch_tanp_i = toprefix("DPI|adminnetworkpolicy") - ruleID_nomatch_tanp_i = NewRuleID("adminnetworkpolicy", "", "", 0, rules.RuleDirIngress, rules.RuleActionDeny) - prefix_nomatch_tanp_e = toprefix("DPE|adminnetworkpolicy") - ruleID_nomatch_tanp_e = NewRuleID("adminnetworkpolicy", "", "", 0, rules.RuleDirEgress, rules.RuleActionDeny) - prefix_nomatch_tbanp_i = toprefix("DPI|baselineadminnetworkpolicy") - ruleID_nomatch_tbanp_i = NewRuleID("baselineadminnetworkpolicy", "", "", 0, rules.RuleDirIngress, rules.RuleActionDeny) - prefix_nomatch_tbanp_e = toprefix("DPE|baselineadminnetworkpolicy") - ruleID_nomatch_tbanp_e = NewRuleID("baselineadminnetworkpolicy", "", "", 0, rules.RuleDirEgress, rules.RuleActionDeny) + prefix_nomatch_t1_i = toprefix("DPI|tier-1") + ruleID_nomatch_t1_i = NewRuleID("", "tier-1", "", "", 0, rules.RuleDirIngress, rules.RuleActionDeny) + prefix_nomatch_t1_e = toprefix("DPE|tier-1") + ruleID_nomatch_t1_e = NewRuleID("", "tier-1", "", "", 0, rules.RuleDirEgress, rules.RuleActionDeny) + prefix_nomatch_td_i = toprefix("DPI|default") + ruleID_nomatch_td_i = NewRuleID("", "default", "", "", 0, rules.RuleDirIngress, rules.RuleActionDeny) + prefix_nomatch_td_e = toprefix("DPE|default") + ruleID_nomatch_td_e = NewRuleID("", "default", "", "", 0, rules.RuleDirEgress, rules.RuleActionDeny) + prefix_nomatch_tkcnpAdmin_i = toprefix("DPI|kube-admin") + ruleID_nomatch_tkcnpAdmin_i = NewRuleID("", "kube-admin", "", "", 0, rules.RuleDirIngress, rules.RuleActionDeny) + prefix_nomatch_tkcnpAdmin_e = toprefix("DPE|kube-admin") + ruleID_nomatch_tkcnpAdmin_e = NewRuleID("", "kube-admin", "", "", 0, rules.RuleDirEgress, rules.RuleActionDeny) + prefix_nomatch_tkcnpBaseline_i = toprefix("DPI|kube-baseline") + ruleID_nomatch_tkcnpBaseline_i = NewRuleID("", "kube-baseline", "", "", 0, rules.RuleDirIngress, rules.RuleActionDeny) + prefix_nomatch_tkcnpBaseline_e = toprefix("DPE|kube-baseline") + ruleID_nomatch_tkcnpBaseline_e = NewRuleID("", "kube-baseline", "", "", 0, rules.RuleDirEgress, rules.RuleActionDeny) // Profile no-matches prefix_nomatch_prof_i = toprefix("DRI") - ruleID_nomatch_prof_i = NewRuleID("", "", "", 0, rules.RuleDirIngress, rules.RuleActionDeny) + ruleID_nomatch_prof_i = NewRuleID("", "", "", "", 0, rules.RuleDirIngress, rules.RuleActionDeny) prefix_nomatch_prof_e = toprefix("DRE") - ruleID_nomatch_prof_e = NewRuleID("", "", "", 0, rules.RuleDirEgress, rules.RuleActionDeny) + ruleID_nomatch_prof_e = NewRuleID("", "", "", "", 0, rules.RuleDirEgress, rules.RuleActionDeny) ) var _ = Describe("PolicyLookupsCache tests", func() { @@ -205,7 +221,7 @@ var _ = Describe("PolicyLookupsCache tests", func() { c := "Querying prefix " + string(prefix[:]) + "\n" pc.OnPolicyActive(key, pol) rid := pc.GetRuleIDFromNFLOGPrefix(prefix) - Expect(rid).NotTo(BeNil(), c+pc.Dump()) + Expect(rid).NotTo(BeNil(), c+pc.Dump(), fmt.Sprintf("Couldn't find prefix: %s", prefix)) Expect(*rid).To(Equal(*expectedRuleID)) // Send a policy delete and check that the entry is not in the cache @@ -229,12 +245,12 @@ var _ = Describe("PolicyLookupsCache tests", func() { Entry("KNP1 (1i0e) no match default ingress", knp1_t1_1i0e_key, knp1_t1_1i0e, prefix_nomatch_td_i, ruleID_nomatch_td_i), Entry("KNP1 (1i0e) no match default egress", knp1_t1_1i0e_key, knp1_t1_1i0e, prefix_nomatch_td_e, ruleID_nomatch_td_e), Entry("KNP1 (1i0e) i0", knp1_t1_1i0e_key, knp1_t1_1i0e, prefix_knp1_t1_i0D, ruleID_knp1_t1_i0D), - Entry("KANP1 (1i0e) no match default ingress", kanp1_t1_1i0e_key, kanp1_t1_1i0e, prefix_nomatch_tanp_i, ruleID_nomatch_tanp_i), - Entry("KANP1 (1i0e) no match default egress", kanp1_t1_1i0e_key, kanp1_t1_1i0e, prefix_nomatch_tanp_e, ruleID_nomatch_tanp_e), - Entry("KANP1 (1i0e) i0", kanp1_t1_1i0e_key, kanp1_t1_1i0e, prefix_kanp1_t1_i0D, ruleID_kanp1_t1_i0D), - Entry("KBANP1 (1i0e) no match default ingress", kbanp1_t1_1i0e_key, kbanp1_t1_1i0e, prefix_nomatch_tbanp_i, ruleID_nomatch_tbanp_i), - Entry("KBANP1 (1i0e) no match default egress", kbanp1_t1_1i0e_key, kbanp1_t1_1i0e, prefix_nomatch_tbanp_e, ruleID_nomatch_tbanp_e), - Entry("KBANP1 (1i0e) i0", kbanp1_t1_1i0e_key, kbanp1_t1_1i0e, prefix_kbanp1_t1_i0D, ruleID_kbanp1_t1_i0D), + Entry("KCNP1_Admin (1i0e) no match default ingress", kcnpAdmin1_t1_1i0e_key, kcnpAdmin1_t1_1i0e, prefix_nomatch_tkcnpAdmin_i, ruleID_nomatch_tkcnpAdmin_i), + Entry("KCNP1_Admin (1i0e) no match default egress", kcnpAdmin1_t1_1i0e_key, kcnpAdmin1_t1_1i0e, prefix_nomatch_tkcnpAdmin_e, ruleID_nomatch_tkcnpAdmin_e), + Entry("KCNP1_Admin (1i0e) i0", kcnpAdmin1_t1_1i0e_key, kcnpAdmin1_t1_1i0e, prefix_kcnpAdmin1_t1_i0D, ruleID_kcnpAdmin1_t1_i0D), + Entry("KCNP1_Baseline (1i0e) no match default ingress", kcnpBaseline1_t1_1i0e_key, kcnpBaseline1_t1_1i0e, prefix_nomatch_tkcnpBaseline_i, ruleID_nomatch_tkcnpBaseline_i), + Entry("KCNP1_Baseline (1i0e) no match default egress", kcnpBaseline1_t1_1i0e_key, kcnpBaseline1_t1_1i0e, prefix_nomatch_tkcnpBaseline_e, ruleID_nomatch_tkcnpBaseline_e), + Entry("KCNP1_Baseline (1i0e) i0", kcnpBaseline1_t1_1i0e_key, kcnpBaseline1_t1_1i0e, prefix_kcnpBaseline1_t1_i0D, ruleID_kcnpBaseline1_t1_i0D), ) DescribeTable( @@ -376,23 +392,23 @@ var _ = Describe("PolicyLookupsCache tests", func() { var _ = Describe("RuleID tests", func() { DescribeTable( "Check flow log name is set correctly", - func(tier, policy, namespace string, ruleIndex int, ruleDirection rules.RuleDir, ruleAction rules.RuleAction, expectedFPName string) { - rid := NewRuleID(tier, policy, namespace, ruleIndex, ruleDirection, ruleAction) + func(kind, tier, policy, namespace string, ruleIndex int, ruleDirection rules.RuleDir, ruleAction rules.RuleAction, expectedFPName string) { + rid := NewRuleID(kind, tier, policy, namespace, ruleIndex, ruleDirection, ruleAction) Expect(rid).NotTo(BeNil()) Expect(rid.GetFlowLogPolicyName()).To(Equal(expectedFPName)) }, - Entry("Global network policy", "default", "gnp-1", "", 0, rules.RuleDirIngress, rules.RuleActionAllow, "default|default.gnp-1|allow"), - Entry("Global network policy in non default tier", "tier-1", "gnp-2", "", 2, rules.RuleDirEgress, rules.RuleActionPass, "tier-1|tier-1.gnp-2|pass"), - Entry("Namespaced network policy", "default", "np-1", "ns1", 0, rules.RuleDirIngress, rules.RuleActionAllow, "default|ns1/default.np-1|allow"), - Entry("Namespaced network policy in non default tier", "netsec", "np-2", "ns2", 0, rules.RuleDirIngress, rules.RuleActionAllow, "netsec|ns2/netsec.np-2|allow"), - Entry("Kubernetes network policy", "default", "knp.default.allow.all", "test", 0, rules.RuleDirIngress, rules.RuleActionAllow, "default|test/knp.default.allow.all|allow"), - Entry("Profile", "", "kns.ns3", "ns3", 0, rules.RuleDirIngress, rules.RuleActionAllow, "__PROFILE__|ns3/__PROFILE__.kns.ns3|allow"), - - Entry("Staged Global network policy", "default", "staged:gnp-1", "", 0, rules.RuleDirIngress, rules.RuleActionAllow, "default|default.staged:gnp-1|allow"), - Entry("Staged Global network policy in non default tier", "tier-1", "staged:gnp-2", "", 2, rules.RuleDirEgress, rules.RuleActionPass, "tier-1|tier-1.staged:gnp-2|pass"), - Entry("Staged Namespaced network policy", "default", "staged:np.1", "ns1", 0, rules.RuleDirIngress, rules.RuleActionAllow, "default|ns1/default.staged:np.1|allow"), - Entry("Staged Namespaced network policy in non default tier", "netsec", "staged:np-2", "ns2", 0, rules.RuleDirIngress, rules.RuleActionAllow, "netsec|ns2/netsec.staged:np-2|allow"), - Entry("Staged Kubernetes network policy", "default", "staged:knp.default.allow.all", "test", 0, rules.RuleDirIngress, rules.RuleActionAllow, "default|test/staged:knp.default.allow.all|allow"), + Entry("Global network policy", v3.KindGlobalNetworkPolicy, "default", "gnp-1", "", 0, rules.RuleDirIngress, rules.RuleActionAllow, "default|gnp:gnp-1|allow"), + Entry("Global network policy in non default tier", v3.KindGlobalNetworkPolicy, "tier-1", "gnp-2", "", 2, rules.RuleDirEgress, rules.RuleActionPass, "tier-1|gnp:gnp-2|pass"), + Entry("Namespaced network policy", v3.KindNetworkPolicy, "default", "np-1", "ns1", 0, rules.RuleDirIngress, rules.RuleActionAllow, "default|np:ns1/np-1|allow"), + Entry("Namespaced network policy in non default tier", v3.KindNetworkPolicy, "netsec", "np-2", "ns2", 0, rules.RuleDirIngress, rules.RuleActionAllow, "netsec|np:ns2/np-2|allow"), + Entry("Kubernetes network policy", model.KindKubernetesNetworkPolicy, "default", "allow.all", "test", 0, rules.RuleDirIngress, rules.RuleActionAllow, "default|knp:test/allow.all|allow"), + Entry("Profile", "", "", "kns.ns3", "ns3", 0, rules.RuleDirIngress, rules.RuleActionAllow, "__PROFILE__|pro:kns.ns3|allow"), + + Entry("Staged Global network policy", v3.KindStagedGlobalNetworkPolicy, "default", "gnp-1", "", 0, rules.RuleDirIngress, rules.RuleActionAllow, "default|sgnp:gnp-1|allow"), + Entry("Staged Global network policy in non default tier", v3.KindStagedGlobalNetworkPolicy, "tier-1", "gnp-2", "", 2, rules.RuleDirEgress, rules.RuleActionPass, "tier-1|sgnp:gnp-2|pass"), + Entry("Staged Namespaced network policy", v3.KindStagedNetworkPolicy, "default", "np.1", "ns1", 0, rules.RuleDirIngress, rules.RuleActionAllow, "default|snp:ns1/np.1|allow"), + Entry("Staged Namespaced network policy in non default tier", v3.KindStagedNetworkPolicy, "netsec", "np-2", "ns2", 0, rules.RuleDirIngress, rules.RuleActionAllow, "netsec|snp:ns2/np-2|allow"), + Entry("Staged Kubernetes network policy", v3.KindStagedKubernetesNetworkPolicy, "default", "allow.all", "test", 0, rules.RuleDirIngress, rules.RuleActionAllow, "default|sknp:test/allow.all|allow"), ) }) diff --git a/felix/calc/policy_resolver.go b/felix/calc/policy_resolver.go index 3b10639b1f5..72ab223d152 100644 --- a/felix/calc/policy_resolver.go +++ b/felix/calc/policy_resolver.go @@ -15,6 +15,9 @@ package calc import ( + "maps" + "slices" + "github.com/prometheus/client_golang/prometheus" log "github.com/sirupsen/logrus" @@ -56,26 +59,34 @@ type PolicyResolver struct { sortedTierData []*TierInfo endpoints map[model.Key]model.Endpoint // Local WEPs/HEPs only. dirtyEndpoints set.Set[model.EndpointKey] + endpointComputedData map[model.WorkloadEndpointKey]map[EndpointComputedDataKind]EndpointComputedData policySorter *PolicySorter Callbacks []PolicyResolverCallbacks InSync bool endpointBGPPeerData map[model.WorkloadEndpointKey]EndpointBGPPeer + + // Track policy updates as they come in - these will be resolved on flush. + pendingPolicyUpdates set.Set[model.PolicyKey] } type PolicyResolverCallbacks interface { - OnEndpointTierUpdate(endpointKey model.EndpointKey, endpoint model.Endpoint, peerData *EndpointBGPPeer, filteredTiers []TierInfo) + OnEndpointTierUpdate(endpointKey model.EndpointKey, endpoint model.Endpoint, computedData []EndpointComputedData, peerData *EndpointBGPPeer, filteredTiers []TierInfo) } +type EndpointComputedDataUpdater func(model.WorkloadEndpointKey, EndpointComputedDataKind, EndpointComputedData) + func NewPolicyResolver() *PolicyResolver { return &PolicyResolver{ policyIDToEndpointIDs: multidict.New[model.PolicyKey, model.EndpointKey](), endpointIDToPolicyIDs: multidict.New[model.EndpointKey, model.PolicyKey](), allPolicies: map[model.PolicyKey]policyMetadata{}, endpoints: make(map[model.Key]model.Endpoint), + endpointComputedData: make(map[model.WorkloadEndpointKey]map[EndpointComputedDataKind]EndpointComputedData), dirtyEndpoints: set.New[model.EndpointKey](), endpointBGPPeerData: map[model.WorkloadEndpointKey]EndpointBGPPeer{}, policySorter: NewPolicySorter(), Callbacks: []PolicyResolverCallbacks{}, + pendingPolicyUpdates: set.New[model.PolicyKey](), } } @@ -98,6 +109,9 @@ func (pr *PolicyResolver) OnUpdate(update api.Update) (filterOut bool) { pr.endpoints[key] = update.Value.(model.Endpoint) } else { delete(pr.endpoints, key) + if wlKey, ok := key.(model.WorkloadEndpointKey); ok { + delete(pr.endpointComputedData, wlKey) + } } pr.dirtyEndpoints.Add(key) gaugeNumActiveEndpoints.Set(float64(len(pr.endpoints))) @@ -105,6 +119,7 @@ func (pr *PolicyResolver) OnUpdate(update api.Update) (filterOut bool) { log.Debugf("Policy update: %v", key) if update.Value == nil { delete(pr.allPolicies, key) + pr.pendingPolicyUpdates.Discard(key) } else { policy := update.Value.(*model.Policy) pr.allPolicies[key] = ExtractPolicyMetadata(policy) @@ -149,8 +164,8 @@ func (pr *PolicyResolver) OnPolicyMatch(policyKey model.PolicyKey, endpointKey m log.Debugf("Storing policy match %v -> %v", policyKey, endpointKey) // If it's first time the policy become matched, add it to the tier if !pr.policySorter.HasPolicy(policyKey) { - policy := pr.allPolicies[policyKey] - pr.policySorter.UpdatePolicy(policyKey, &policy) + // Add a pending policy update to be resolved on flush. + pr.pendingPolicyUpdates.Add(policyKey) } pr.policyIDToEndpointIDs.Put(policyKey, endpointKey) pr.endpointIDToPolicyIDs.Put(endpointKey, policyKey) @@ -170,25 +185,43 @@ func (pr *PolicyResolver) OnPolicyMatchStopped(policyKey model.PolicyKey, endpoi pr.dirtyEndpoints.Add(endpointKey) } +func (pr *PolicyResolver) OnComputedSelectorMatch(_ string, _ model.EndpointKey) {} +func (pr *PolicyResolver) OnComputedSelectorMatchStopped(_ string, _ model.EndpointKey) {} + func (pr *PolicyResolver) Flush() { if !pr.InSync { log.Debugf("Not in sync, skipping flush") return } + // Resolve any pending policy updates, and clear the set. + pr.pendingPolicyUpdates.Iter(func(polKey model.PolicyKey) error { + policy, ok := pr.allPolicies[polKey] + if !ok { + log.Warnf("PolicyResolver missing policy metadata for %s during flush", polKey) + return nil + } + pr.policySorter.UpdatePolicy(polKey, &policy) + + // Continue iteration, removing item from the dirty set. + return set.RemoveItem + }) + pr.sortedTierData = pr.policySorter.Sorted() - pr.dirtyEndpoints.Iter(pr.sendEndpointUpdate) + for endpointID := range pr.dirtyEndpoints.All() { + pr.sendEndpointUpdate(endpointID) + } pr.dirtyEndpoints.Clear() } -func (pr *PolicyResolver) sendEndpointUpdate(endpointID model.EndpointKey) error { +func (pr *PolicyResolver) sendEndpointUpdate(endpointID model.EndpointKey) { log.Debugf("Sending tier update for endpoint %v", endpointID) endpoint, ok := pr.endpoints[endpointID.(model.Key)] if !ok { log.Debugf("Endpoint is unknown, sending nil update") for _, cb := range pr.Callbacks { - cb.OnEndpointTierUpdate(endpointID, nil, nil, []TierInfo{}) + cb.OnEndpointTierUpdate(endpointID, nil, nil, nil, []TierInfo{}) } - return nil + return } applicableTiers := []TierInfo{} @@ -208,8 +241,7 @@ func (pr *PolicyResolver) sendEndpointUpdate(endpointID model.EndpointKey) error if pr.endpointIDToPolicyIDs.Contains(endpointID, polKV.Key) { log.Debugf("Policy %v matches %v", polKV.Key, endpointID) tierMatches = true - filteredTier.OrderedPolicies = append(filteredTier.OrderedPolicies, - polKV) + filteredTier.OrderedPolicies = append(filteredTier.OrderedPolicies, polKV) } } if tierMatches { @@ -218,6 +250,11 @@ func (pr *PolicyResolver) sendEndpointUpdate(endpointID model.EndpointKey) error } } + var computedData []EndpointComputedData + if key, ok := endpointID.(model.WorkloadEndpointKey); ok { + computedData = slices.Collect(maps.Values(pr.endpointComputedData[key])) + } + log.Debugf("Endpoint tier update: %v -> %v", endpointID, applicableTiers) var peerData *EndpointBGPPeer @@ -229,9 +266,40 @@ func (pr *PolicyResolver) sendEndpointUpdate(endpointID model.EndpointKey) error } for _, cb := range pr.Callbacks { - cb.OnEndpointTierUpdate(endpointID, endpoint, peerData, applicableTiers) + cb.OnEndpointTierUpdate(endpointID, endpoint, computedData, peerData, applicableTiers) } - return nil +} + +func (pr *PolicyResolver) OnEndpointComputedDataUpdate( + key model.WorkloadEndpointKey, + kind EndpointComputedDataKind, + computedData EndpointComputedData, +) { + epComputedData, exists := pr.endpointComputedData[key] + if !exists { + if computedData == nil { + return + } + epComputedData = map[EndpointComputedDataKind]EndpointComputedData{} + pr.endpointComputedData[key] = epComputedData + } + + // We can skip a nil -> nil no-op update, but we can't otherwise compare the passed value + // easily since it may be a pointer type or have unexported fields. + if computedData == nil && epComputedData[kind] == nil { + return + } + + // update + if computedData != nil { + epComputedData[kind] = computedData + } else { + delete(epComputedData, kind) + if len(epComputedData) == 0 { + delete(pr.endpointComputedData, key) + } + } + pr.dirtyEndpoints.Add(key) } func (pr *PolicyResolver) OnEndpointBGPPeerDataUpdate(key model.WorkloadEndpointKey, peerData *EndpointBGPPeer) { diff --git a/felix/calc/policy_resolver_test.go b/felix/calc/policy_resolver_test.go index a5a12c3820f..9348c9779cd 100644 --- a/felix/calc/policy_resolver_test.go +++ b/felix/calc/policy_resolver_test.go @@ -18,12 +18,31 @@ import ( "testing" "github.com/google/go-cmp/cmp" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "github.com/projectcalico/calico/felix/proto" "github.com/projectcalico/calico/lib/std/uniquelabels" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" ) +const ( + testComputedDataKindA EndpointComputedDataKind = "kindA" + testComputedDataKindB EndpointComputedDataKind = "kindB" +) + +type testComputedData struct { + Value string +} + +func (t *testComputedData) ApplyTo(wep *proto.WorkloadEndpoint) { + // Append to Annotations as a simple way to observe the effect. + if wep.Annotations == nil { + wep.Annotations = map[string]string{} + } + wep.Annotations["test-computed"] = t.Value +} + func TestPolicyResolver_OnUpdate(t *testing.T) { pr, recorder := createPolicyResolver() @@ -31,7 +50,7 @@ func TestPolicyResolver_OnUpdate(t *testing.T) { Name: "test-policy", } - policy := model.Policy{} + policy := model.Policy{Tier: "default"} kvp := model.KVPair{ Key: polKey, @@ -71,20 +90,22 @@ func createPolicyResolver() (*PolicyResolver, *policyResolverRecorder) { } type policyResolverUpdate struct { - Key model.Key - Endpoint interface{} - Tiers []TierInfo + Key model.Key + Endpoint any + Tiers []TierInfo + ComputedData []EndpointComputedData } type policyResolverRecorder struct { updates []policyResolverUpdate } -func (p *policyResolverRecorder) OnEndpointTierUpdate(endpointKey model.EndpointKey, endpoint model.Endpoint, peerData *EndpointBGPPeer, filteredTiers []TierInfo) { +func (p *policyResolverRecorder) OnEndpointTierUpdate(endpointKey model.EndpointKey, endpoint model.Endpoint, computedData []EndpointComputedData, peerData *EndpointBGPPeer, filteredTiers []TierInfo) { p.updates = append(p.updates, policyResolverUpdate{ - Key: endpointKey, - Endpoint: endpoint, - Tiers: filteredTiers, + Key: endpointKey, + Endpoint: endpoint, + ComputedData: computedData, + Tiers: filteredTiers, }) } @@ -96,11 +117,11 @@ func TestPolicyResolver_OnPolicyMatch(t *testing.T) { pr, recorder := createPolicyResolver() polKey := model.PolicyKey{ - Tier: "default", Name: "test-policy", + Kind: v3.KindNetworkPolicy, } - pol := ExtractPolicyMetadata(&model.Policy{}) + pol := ExtractPolicyMetadata(&model.Policy{Tier: "default"}) endpointKey := model.WorkloadEndpointKey{ Hostname: "test-workload-ep", @@ -166,8 +187,8 @@ func TestPolicyResolver_OnPolicyMatchStopped(t *testing.T) { pr.OnDatamodelStatus(api.InSync) polKey := model.PolicyKey{ - Tier: "default", Name: "test-policy", + Kind: v3.KindNetworkPolicy, } pol := policyMetadata{} @@ -209,3 +230,151 @@ func TestPolicyResolver_OnPolicyMatchStopped(t *testing.T) { t.Error("Incorrect update:", d) } } + +func TestPolicyResolver_ComputedDataIncludedInFlush(t *testing.T) { + pr, recorder := createPolicyResolver() + pr.OnDatamodelStatus(api.InSync) + + endpointKey := model.WorkloadEndpointKey{Hostname: "host1"} + wep := &model.WorkloadEndpoint{Name: "we1"} + pr.endpoints[endpointKey] = wep + + // Need a policy match to get the endpoint into the flush path. + polKey := model.PolicyKey{Name: "test-policy", Kind: v3.KindNetworkPolicy} + pr.allPolicies[polKey] = ExtractPolicyMetadata(&model.Policy{Tier: "default"}) + pr.OnPolicyMatch(polKey, endpointKey) + + cd := &testComputedData{Value: "hello"} + pr.OnEndpointComputedDataUpdate(endpointKey, testComputedDataKindA, cd) + + pr.Flush() + + if len(recorder.updates) != 1 { + t.Fatalf("expected 1 update, got %d", len(recorder.updates)) + } + if len(recorder.updates[0].ComputedData) != 1 { + t.Fatalf("expected 1 computed data entry, got %d", len(recorder.updates[0].ComputedData)) + } + if recorder.updates[0].ComputedData[0] != cd { + t.Errorf("expected computed data %v, got %v", cd, recorder.updates[0].ComputedData[0]) + } +} + +func TestPolicyResolver_ComputedDataNilRemoves(t *testing.T) { + pr, recorder := createPolicyResolver() + pr.OnDatamodelStatus(api.InSync) + + endpointKey := model.WorkloadEndpointKey{Hostname: "host1"} + wep := &model.WorkloadEndpoint{Name: "we1"} + pr.endpoints[endpointKey] = wep + + polKey := model.PolicyKey{Name: "test-policy", Kind: v3.KindNetworkPolicy} + pr.allPolicies[polKey] = ExtractPolicyMetadata(&model.Policy{Tier: "default"}) + pr.OnPolicyMatch(polKey, endpointKey) + + // Add computed data. + cd := &testComputedData{Value: "hello"} + pr.OnEndpointComputedDataUpdate(endpointKey, testComputedDataKindA, cd) + pr.Flush() + recorder.updates = nil + + // Remove with nil. + pr.OnEndpointComputedDataUpdate(endpointKey, testComputedDataKindA, nil) + pr.Flush() + + if len(recorder.updates) != 1 { + t.Fatalf("expected 1 update after nil removal, got %d", len(recorder.updates)) + } + if len(recorder.updates[0].ComputedData) != 0 { + t.Errorf("expected no computed data after nil update, got %d entries", len(recorder.updates[0].ComputedData)) + } + + // Internal map should be cleaned up. + if _, exists := pr.endpointComputedData[endpointKey]; exists { + t.Error("expected endpointComputedData entry to be cleaned up") + } +} + +func TestPolicyResolver_ComputedDataNilToNilIsNoOp(t *testing.T) { + pr, _ := createPolicyResolver() + pr.OnDatamodelStatus(api.InSync) + + endpointKey := model.WorkloadEndpointKey{Hostname: "host1"} + wep := &model.WorkloadEndpoint{Name: "we1"} + pr.endpoints[endpointKey] = wep + + polKey := model.PolicyKey{Name: "test-policy", Kind: v3.KindNetworkPolicy} + pr.allPolicies[polKey] = ExtractPolicyMetadata(&model.Policy{Tier: "default"}) + pr.OnPolicyMatch(polKey, endpointKey) + pr.Flush() + + // Clear dirty set after initial flush. + pr.dirtyEndpoints.Clear() + + // nil → nil should not dirty the endpoint. + pr.OnEndpointComputedDataUpdate(endpointKey, testComputedDataKindA, nil) + + if pr.dirtyEndpoints.Contains(endpointKey) { + t.Error("nil-to-nil computed data update should not dirty the endpoint") + } +} + +func TestPolicyResolver_ComputedDataMultipleKinds(t *testing.T) { + pr, recorder := createPolicyResolver() + pr.OnDatamodelStatus(api.InSync) + + endpointKey := model.WorkloadEndpointKey{Hostname: "host1"} + wep := &model.WorkloadEndpoint{Name: "we1"} + pr.endpoints[endpointKey] = wep + + polKey := model.PolicyKey{Name: "test-policy", Kind: v3.KindNetworkPolicy} + pr.allPolicies[polKey] = ExtractPolicyMetadata(&model.Policy{Tier: "default"}) + pr.OnPolicyMatch(polKey, endpointKey) + + cdA := &testComputedData{Value: "a"} + cdB := &testComputedData{Value: "b"} + pr.OnEndpointComputedDataUpdate(endpointKey, testComputedDataKindA, cdA) + pr.OnEndpointComputedDataUpdate(endpointKey, testComputedDataKindB, cdB) + + pr.Flush() + + if len(recorder.updates) != 1 { + t.Fatalf("expected 1 update, got %d", len(recorder.updates)) + } + if len(recorder.updates[0].ComputedData) != 2 { + t.Fatalf("expected 2 computed data entries, got %d", len(recorder.updates[0].ComputedData)) + } + // Since map iteration order is non-deterministic, check both are present. + found := map[string]bool{} + for _, cd := range recorder.updates[0].ComputedData { + found[cd.(*testComputedData).Value] = true + } + if !found["a"] || !found["b"] { + t.Errorf("expected both computed data kinds, got %v", found) + } +} + +func TestPolicyResolver_EndpointDeleteClearsComputedData(t *testing.T) { + pr, _ := createPolicyResolver() + pr.OnDatamodelStatus(api.InSync) + + endpointKey := model.WorkloadEndpointKey{Hostname: "host1"} + wep := &model.WorkloadEndpoint{Name: "we1"} + pr.endpoints[endpointKey] = wep + + // Add computed data. + cd := &testComputedData{Value: "hello"} + pr.OnEndpointComputedDataUpdate(endpointKey, testComputedDataKindA, cd) + + // Delete the endpoint via OnUpdate. + pr.OnUpdate(api.Update{ + KVPair: model.KVPair{ + Key: endpointKey, + Value: nil, + }, + }) + + if _, exists := pr.endpointComputedData[endpointKey]; exists { + t.Error("expected endpointComputedData to be cleaned up after endpoint deletion") + } +} diff --git a/felix/calc/policy_sorter.go b/felix/calc/policy_sorter.go index 04fc903b1d4..6983d2fd10f 100644 --- a/felix/calc/policy_sorter.go +++ b/felix/calc/policy_sorter.go @@ -25,6 +25,7 @@ import ( "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" + "github.com/projectcalico/calico/libcalico-go/lib/names" ) type PolicySorter struct { @@ -160,18 +161,25 @@ func TierLess(i, j tierInfoKey) bool { } func (poc *PolicySorter) HasPolicy(key model.PolicyKey) bool { - var tierInfo *TierInfo - var found bool - if tierInfo, found = poc.tiers[key.Tier]; found { - _, found = tierInfo.Policies[key] + for _, tierInfo := range poc.tiers { + if _, ok := tierInfo.Policies[key]; ok { + return true + } } - return found + return false } var polMetaDefaultOrder = math.Inf(1) func ExtractPolicyMetadata(policy *model.Policy) policyMetadata { - m := policyMetadata{} + m := policyMetadata{Tier: policy.Tier} + + if policy.Tier == "" { + // This shouldn't happen - all policies should have a tier assigned by now. + // Log a warning and assign to default tier to be safe. + logrus.WithField("policy", policy).Warn("Policy has no tier assigned") + m.Tier = names.DefaultTierName + } if policy.Order == nil { m.Order = polMetaDefaultOrder } else { @@ -203,6 +211,7 @@ func ExtractPolicyMetadata(policy *model.Policy) policyMetadata { type policyMetadata struct { Order float64 // Set to +Inf for default order. Flags policyMetadataFlags + Tier string } type policyMetadataFlags uint8 @@ -234,8 +243,59 @@ func (m *policyMetadata) ApplyOnForward() bool { return m != nil && m.Flags&policyMetaApplyOnForward != 0 } +func (poc *PolicySorter) tierForPolicy(key model.PolicyKey, meta *policyMetadata) (string, *TierInfo) { + if meta != nil { + return meta.Tier, poc.tiers[meta.Tier] + } + for tierName, tierInfo := range poc.tiers { + if _, ok := tierInfo.Policies[key]; ok { + return tierName, tierInfo + } + } + return "", nil +} + func (poc *PolicySorter) UpdatePolicy(key model.PolicyKey, newPolicy *policyMetadata) (dirty bool) { - tierInfo := poc.tiers[key.Tier] + // Find the old tier info if it exists. If it does, and doesn't match the new tier info, we'll need to + // remove it from the old tier. + _, oldTierInfo := poc.tierForPolicy(key, nil) + + tierName, tierInfo := poc.tierForPolicy(key, newPolicy) + + if tierName == "" { + // Failed to find a tier name for this policy. This should not happen. + logrus.WithFields(logrus.Fields{ + "policyKey": key, + "newPolicy": newPolicy, + }).Warn("Failed to find tier for policy during policy sorter update.") + } + + // If the tier has changed, remove from old tier first. + if oldTierInfo != nil && oldTierInfo != tierInfo { + if logrus.IsLevelEnabled(logrus.DebugLevel) { + logrus.WithFields(logrus.Fields{ + "policyKey": key, + "oldTierName": oldTierInfo.Name, + "newTierName": tierName, + }).Debug("Policy tier changed, removing from old tier") + } + + oldPolicy := oldTierInfo.Policies[key] + oldTiKey := tierInfoKey{ + Name: oldTierInfo.Name, + Order: oldTierInfo.Order, + Valid: oldTierInfo.Valid, + } + oldTierInfo.SortedPolicies.Delete(PolKV{Key: key, Value: &oldPolicy}) + delete(oldTierInfo.Policies, key) + if len(oldTierInfo.Policies) == 0 && !oldTierInfo.Valid { + poc.sortedTiers.Delete(oldTiKey) + delete(poc.tiers, oldTierInfo.Name) + } + dirty = true + } + + // Now add to new tier. var tiKey tierInfoKey var oldPolicy *policyMetadata if tierInfo != nil { @@ -248,11 +308,11 @@ func (poc *PolicySorter) UpdatePolicy(key model.PolicyKey, newPolicy *policyMeta } if newPolicy != nil { if tierInfo == nil { - tierInfo = NewTierInfo(key.Tier) + tierInfo = NewTierInfo(tierName) tiKey.Name = tierInfo.Name tiKey.Valid = tierInfo.Valid tiKey.Order = tierInfo.Order - poc.tiers[key.Tier] = tierInfo + poc.tiers[tierName] = tierInfo poc.sortedTiers.ReplaceOrInsert(tiKey) } if oldPolicy == nil || !oldPolicy.Equals(newPolicy) { @@ -273,7 +333,7 @@ func (poc *PolicySorter) UpdatePolicy(key model.PolicyKey, newPolicy *policyMeta delete(tierInfo.Policies, key) if len(tierInfo.Policies) == 0 && !tierInfo.Valid { poc.sortedTiers.Delete(tiKey) - delete(poc.tiers, key.Tier) + delete(poc.tiers, tierName) } dirty = true } @@ -297,7 +357,18 @@ func (p *PolKV) String() string { orderStr = fmt.Sprint(p.Value.Order) } } - return fmt.Sprintf("%s(%s)", p.Key.Name, orderStr) + + var parts []string + if p.Key.Kind != "" { + parts = append(parts, p.Key.Kind) + } + if p.Key.Namespace != "" { + parts = append(parts, p.Key.Namespace) + } + if p.Key.Name != "" { + parts = append(parts, p.Key.Name) + } + return fmt.Sprintf("%s(%s)", strings.Join(parts, "/"), orderStr) } func (p *PolKV) GovernsIngress() bool { @@ -318,8 +389,12 @@ func PolKVLess(i, j PolKV) bool { // We map the default order to +Inf, which compares equal to itself so, // this "just works". if i.Value.Order == j.Value.Order { - // Order is equal, use name as tie-break. - return i.Key.Name < j.Key.Name + // Order is equal, use namespace/name/kind to break ties. + // We start with the most specific (name) to least specific (kind), as + // it's more intuitive to have policies sorted that way. + iStr := fmt.Sprintf("%s/%s/%s", i.Key.Name, i.Key.Namespace, i.Key.Kind) + jStr := fmt.Sprintf("%s/%s/%s", j.Key.Name, j.Key.Namespace, j.Key.Kind) + return iStr < jStr } return i.Value.Order < j.Value.Order } @@ -359,7 +434,7 @@ func (t TierInfo) String() string { polType = "p" } - //Append ApplyOnForward flag. + // Append ApplyOnForward flag. if pol.Value.ApplyOnForward() { polType = polType + "f" } diff --git a/felix/calc/policy_sorter_test.go b/felix/calc/policy_sorter_test.go index 65214d15946..b108baad121 100644 --- a/felix/calc/policy_sorter_test.go +++ b/felix/calc/policy_sorter_test.go @@ -18,6 +18,8 @@ import ( "reflect" "testing" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" ) @@ -43,17 +45,18 @@ func TestPolKV_String(t *testing.T) { }, { name: "nil policy", - kv: PolKV{Key: model.PolicyKey{Name: "name"}}, - expected: "name(nil policy)"}, + kv: PolKV{Key: model.PolicyKey{Name: "name", Namespace: "ns", Kind: "kind"}}, + expected: "kind/ns/name(nil policy)", + }, { name: "nil order", - kv: PolKV{Key: model.PolicyKey{Name: "name"}, Value: &nilOrder}, - expected: "name(default)", + kv: PolKV{Key: model.PolicyKey{Name: "name", Kind: "kind"}, Value: &nilOrder}, + expected: "kind/name(default)", }, { name: "order set", - kv: PolKV{Key: model.PolicyKey{Name: "name"}, Value: &policyMetadata{Order: 10.5}}, - expected: "name(10.5)", + kv: PolKV{Key: model.PolicyKey{Name: "name", Kind: "kind"}, Value: &policyMetadata{Order: 10.5, Tier: "default"}}, + expected: "kind/name(10.5)", }, } @@ -69,14 +72,14 @@ func TestPolicySorter_HasPolicy(t *testing.T) { poc := NewPolicySorter() polKey := model.PolicyKey{ Name: "test-policy", - Tier: "test-tier", + Kind: v3.KindGlobalNetworkPolicy, } found := poc.HasPolicy(polKey) if found { t.Error("Unexpectedly found policy when it should not be present") } - pol := policyMetadata{} + pol := policyMetadata{Tier: "default"} _ = poc.UpdatePolicy(polKey, &pol) found = poc.HasPolicy(polKey) @@ -90,24 +93,25 @@ func TestPolicySorter_UpdatePolicy(t *testing.T) { polKey := model.PolicyKey{ Name: "test-policy", - Tier: "default", + Kind: v3.KindGlobalNetworkPolicy, } - pol := policyMetadata{} + pol := policyMetadata{Tier: "default"} dirty := poc.UpdatePolicy(polKey, &pol) - if tierInfo, found := poc.tiers[polKey.Tier]; !found { + if tierName, tierInfo := poc.tierForPolicy(polKey, &pol); tierName == "" { t.Error("Adding new policy to tier that does not yet exist - expected tier to be created but it is not found") } else { if !dirty { t.Error("Adding new policy - expected dirty to be true but it was false") } - if _, found = tierInfo.Policies[polKey]; !found { + if _, found := tierInfo.Policies[polKey]; !found { t.Error("Adding new policy - expected policy to be in Policies but it is not") } } newPol := policyMetadata{ + Tier: "default", Order: 7, } @@ -154,8 +158,8 @@ func TestPolicySorter_UpdatePolicy(t *testing.T) { t.Error("Deleting existing policy - expected dirty to be true but it was false") } - if tierInfo, found := poc.tiers[polKey.Tier]; found { - if _, found = tierInfo.Policies[polKey]; found { + if tierName, tierInfo := poc.tierForPolicy(polKey, nil); tierName != "" { + if _, found := tierInfo.Policies[polKey]; found { t.Error("Deleting existing policy - expected policy not to be in Policies but it is") } } @@ -169,11 +173,11 @@ func TestPolicySorter_UpdatePolicy(t *testing.T) { func TestPolicySorter_OnUpdate_Basic(t *testing.T) { poc := NewPolicySorter() - policy := model.Policy{} + policy := model.Policy{Tier: "default"} kvp := model.KVPair{ Key: model.PolicyKey{ Name: "test-policy", - Tier: "default", + Kind: v3.KindGlobalNetworkPolicy, }, Value: &policy, } @@ -204,8 +208,10 @@ func TestPolicySorter_OnUpdate_Basic(t *testing.T) { func TestPolicySorter_OnUpdate_RemoveFromNonExistent(t *testing.T) { ps := NewPolicySorter() - key := model.PolicyKey{Tier: "default", Name: "foo"} - pol := &model.Policy{} + key := model.PolicyKey{Name: "foo", Kind: v3.KindGlobalNetworkPolicy} + pol := &model.Policy{ + Tier: "default", + } ps.OnUpdate(api.Update{ KVPair: model.KVPair{ Key: key, @@ -219,6 +225,7 @@ func TestPolicySorter_OnUpdate_RemoveFromNonExistent(t *testing.T) { expectedPolicies := map[model.PolicyKey]policyMetadata{ key: { + Tier: "default", Order: polMetaDefaultOrder, Flags: policyMetaIngress | policyMetaEgress, }, diff --git a/felix/calc/profile_decoder.go b/felix/calc/profile_decoder.go index cab0eb1e640..1e7611a67a1 100644 --- a/felix/calc/profile_decoder.go +++ b/felix/calc/profile_decoder.go @@ -78,7 +78,7 @@ func (p *ProfileDecoder) OnUpdate(update api.Update) (filterOut bool) { return false } -func (p *ProfileDecoder) classifyProfile(key model.ResourceKey) interface{} { +func (p *ProfileDecoder) classifyProfile(key model.ResourceKey) any { namespace, name, err := p.converter.ProfileNameToServiceAccount(key.Name) if err == nil { return types.ServiceAccountID{Name: name, Namespace: namespace} diff --git a/felix/calc/profile_decoder_test.go b/felix/calc/profile_decoder_test.go index 10a41a45635..687cfa20b32 100644 --- a/felix/calc/profile_decoder_test.go +++ b/felix/calc/profile_decoder_test.go @@ -15,7 +15,7 @@ package calc_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -199,7 +199,7 @@ func (p *passthruCallbackRecorder) OnGlobalBGPConfigUpdate(*v3.BGPConfiguration) } func labelsKV(name string, labels map[string]string) model.KVPair { - var value interface{} + var value any if labels != nil { value = &v3.Profile{ Spec: v3.ProfileSpec{ diff --git a/felix/calc/resources_for_test.go b/felix/calc/resources_for_test.go index bfe0072aeb0..08911b0c6cd 100644 --- a/felix/calc/resources_for_test.go +++ b/felix/calc/resources_for_test.go @@ -355,7 +355,22 @@ var ( order30 = float64(30) ) +// A variation of policy1_order20 but in tier-1 instead of default tier. +var policy1_tier1_order20 = model.Policy{ + Tier: "tier-1", + Order: &order20, + Selector: "a == 'a'", + InboundRules: []model.Rule{ + {SrcSelector: allSelector}, + }, + OutboundRules: []model.Rule{ + {SrcSelector: bEpBSelector}, + }, + Types: []string{"ingress", "egress"}, +} + var policy1_order20 = model.Policy{ + Tier: "default", Order: &order20, Selector: "a == 'a'", InboundRules: []model.Rule{ @@ -368,6 +383,7 @@ var policy1_order20 = model.Policy{ } var policy1_order20_always = model.Policy{ + Tier: "default", Order: &order20, Selector: "a == 'a'", InboundRules: []model.Rule{ @@ -381,6 +397,7 @@ var policy1_order20_always = model.Policy{ } var policy1_order20_ondemand = model.Policy{ + Tier: "default", Order: &order20, Selector: "a == 'a'", InboundRules: []model.Rule{ @@ -397,6 +414,7 @@ var ( protoTCP = numorstring.ProtocolFromStringV1("tcp") protoUDP = numorstring.ProtocolFromStringV1("udp") policy1_order20_with_named_port_tcpport = model.Policy{ + Tier: "default", Order: &order20, Selector: "a == 'a'", InboundRules: []model.Rule{ @@ -413,6 +431,7 @@ var ( ) var policy1_order20_with_named_port_tcpport_negated = model.Policy{ + Tier: "default", Order: &order20, Selector: "a == 'a'", InboundRules: []model.Rule{ @@ -428,6 +447,7 @@ var policy1_order20_with_named_port_tcpport_negated = model.Policy{ } var policy_with_named_port_inherit = model.Policy{ + Tier: "default", Order: &order20, Selector: "a == 'a'", InboundRules: []model.Rule{ @@ -442,6 +462,7 @@ var policy_with_named_port_inherit = model.Policy{ } var policy1_order20_with_selector_and_named_port_tcpport = model.Policy{ + Tier: "default", Order: &order20, Selector: "a == 'a'", InboundRules: []model.Rule{ @@ -458,6 +479,7 @@ var policy1_order20_with_selector_and_named_port_tcpport = model.Policy{ } var policy1_order20_with_selector_and_negated_named_port_tcpport = model.Policy{ + Tier: "default", Order: &order20, Selector: "a == 'a'", InboundRules: []model.Rule{ @@ -472,6 +494,7 @@ var policy1_order20_with_selector_and_negated_named_port_tcpport = model.Policy{ } var policy1_order20_with_selector_and_negated_named_port_tcpport_dest = model.Policy{ + Tier: "default", Order: &order20, Selector: "a == 'a'", InboundRules: []model.Rule{ @@ -486,6 +509,7 @@ var policy1_order20_with_selector_and_negated_named_port_tcpport_dest = model.Po } var policy1_order20_with_selector_and_named_port_udpport = model.Policy{ + Tier: "default", Order: &order20, Selector: "a == 'a'", InboundRules: []model.Rule{ @@ -502,6 +526,7 @@ var policy1_order20_with_selector_and_named_port_udpport = model.Policy{ } var policy1_order20_with_named_port_mismatched_protocol = model.Policy{ + Tier: "default", Order: &order20, Selector: "a == 'a'", InboundRules: []model.Rule{ @@ -520,6 +545,7 @@ var policy1_order20_with_named_port_mismatched_protocol = model.Policy{ } var policy1_order20_with_selector_and_named_port_tcpport2 = model.Policy{ + Tier: "default", Order: &order20, Selector: "a == 'a'", InboundRules: []model.Rule{ @@ -536,6 +562,7 @@ var policy1_order20_with_selector_and_named_port_tcpport2 = model.Policy{ } var policy1_order20_ingress_only = model.Policy{ + Tier: "default", Order: &order20, Selector: "a == 'a'", InboundRules: []model.Rule{ @@ -545,6 +572,7 @@ var policy1_order20_ingress_only = model.Policy{ } var policy1_order20_egress_only = model.Policy{ + Tier: "default", Order: &order20, Selector: "a == 'a'", OutboundRules: []model.Rule{ @@ -554,6 +582,7 @@ var policy1_order20_egress_only = model.Policy{ } var policy1_order20_untracked = model.Policy{ + Tier: "default", Order: &order20, Selector: "a == 'a'", InboundRules: []model.Rule{ @@ -566,6 +595,7 @@ var policy1_order20_untracked = model.Policy{ } var policy1_order20_pre_dnat = model.Policy{ + Tier: "default", Order: &order20, Selector: "a == 'a'", InboundRules: []model.Rule{ @@ -575,6 +605,7 @@ var policy1_order20_pre_dnat = model.Policy{ } var policy1_order20_http_match = model.Policy{ + Tier: "default", Order: &order20, Selector: "a == 'a'", InboundRules: []model.Rule{ @@ -583,6 +614,7 @@ var policy1_order20_http_match = model.Policy{ } var policy1_order20_src_service_account = model.Policy{ + Tier: "default", Order: &order20, Selector: "a == 'a'", InboundRules: []model.Rule{ @@ -591,6 +623,7 @@ var policy1_order20_src_service_account = model.Policy{ } var policy1_order20_dst_service_account = model.Policy{ + Tier: "default", Order: &order20, Selector: "a == 'a'", OutboundRules: []model.Rule{ @@ -910,10 +943,10 @@ var remoteIPAMBlockWithBorrows = model.AllocationBlock{ Unallocated: []int{3, 4, 5, 6, 7}, Attributes: []model.AllocationAttribute{ {}, - {AttrSecondary: map[string]string{ + {ActiveOwnerAttrs: map[string]string{ model.IPAMBlockAttributeNode: remoteHostname, }}, - {AttrSecondary: map[string]string{ + {ActiveOwnerAttrs: map[string]string{ model.IPAMBlockAttributeNode: remoteHostname2, }}, }, @@ -935,10 +968,10 @@ var remoteIPAMBlockWithBorrowsSwitched = model.AllocationBlock{ Unallocated: []int{3, 4, 5, 6, 7}, Attributes: []model.AllocationAttribute{ {}, - {AttrSecondary: map[string]string{ + {ActiveOwnerAttrs: map[string]string{ model.IPAMBlockAttributeNode: remoteHostname2, }}, - {AttrSecondary: map[string]string{ + {ActiveOwnerAttrs: map[string]string{ model.IPAMBlockAttributeNode: remoteHostname, }}, }, @@ -963,12 +996,12 @@ var localIPAMBlockWithBorrows = model.AllocationBlock{ {}, // 10.0.0.1 - assigned locally. - {AttrSecondary: map[string]string{ + {ActiveOwnerAttrs: map[string]string{ model.IPAMBlockAttributeNode: localHostname, }}, // 10.0.0.2 - assigned to remoteHostname. - {AttrSecondary: map[string]string{ + {ActiveOwnerAttrs: map[string]string{ model.IPAMBlockAttributeNode: remoteHostname, }}, }, @@ -1028,9 +1061,10 @@ var endpointSlice2NewIPs2 = discovery.EndpointSlice{ } var ( - servicePolicyKey = model.PolicyKey{Name: "svc-policy"} - servicePolicyKey2 = model.PolicyKey{Name: "svc-policy2"} + servicePolicyKey = model.PolicyKey{Name: "svc-policy", Kind: v3.KindGlobalNetworkPolicy} + servicePolicyKey2 = model.PolicyKey{Name: "svc-policy2", Kind: v3.KindGlobalNetworkPolicy} servicePolicy = model.Policy{ + Tier: "default", Namespace: "default", OutboundRules: []model.Rule{ { @@ -1042,27 +1076,28 @@ var ( Types: []string{"egress"}, Selector: "all()", } -) -var servicePolicyNoPorts = model.Policy{ - Namespace: "default", - InboundRules: []model.Rule{ - { - Action: "Allow", - SrcService: "svc", - SrcServiceNamespace: "default", - Protocol: &protoTCP, - SrcPorts: []numorstring.Port{ - { - MinPort: 80, - MaxPort: 80, + servicePolicyNoPorts = model.Policy{ + Tier: "default", + Namespace: "default", + InboundRules: []model.Rule{ + { + Action: "Allow", + SrcService: "svc", + SrcServiceNamespace: "default", + Protocol: &protoTCP, + SrcPorts: []numorstring.Port{ + { + MinPort: 80, + MaxPort: 80, + }, }, }, }, - }, - Types: []string{"ingress"}, - Selector: "all()", -} + Types: []string{"ingress"}, + Selector: "all()", + } +) func intPtr(i int) *int { return &i diff --git a/felix/calc/rule_convert.go b/felix/calc/rule_convert.go index ac5bd52fe8f..1ad903abea9 100644 --- a/felix/calc/rule_convert.go +++ b/felix/calc/rule_convert.go @@ -17,6 +17,7 @@ package calc import ( "crypto/sha256" "encoding/base64" + "maps" "github.com/projectcalico/api/pkg/lib/numorstring" log "github.com/sirupsen/logrus" @@ -169,9 +170,7 @@ func parsedRuleToProtoRule(in *ParsedRule) *proto.Rule { if in.Metadata != nil { if in.Metadata.Annotations != nil { out.Metadata = &proto.RuleMetadata{Annotations: make(map[string]string)} - for k, v := range in.Metadata.Annotations { - out.Metadata.Annotations[k] = v - } + maps.Copy(out.Metadata.Annotations, in.Metadata.Annotations) } } diff --git a/felix/calc/rule_convert_test.go b/felix/calc/rule_convert_test.go index 488fe5f637b..b5f91eb1da8 100644 --- a/felix/calc/rule_convert_test.go +++ b/felix/calc/rule_convert_test.go @@ -17,8 +17,7 @@ package calc import ( net2 "net" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" diff --git a/felix/calc/rule_scanner.go b/felix/calc/rule_scanner.go index 50b569dbe67..92080133fbc 100644 --- a/felix/calc/rule_scanner.go +++ b/felix/calc/rule_scanner.go @@ -159,12 +159,12 @@ func NewRuleScanner() *RuleScanner { } func (rs *RuleScanner) OnProfileActive(key model.ProfileRulesKey, profile *model.ProfileRules) { - parsedRules := rs.updateRules(key, profile.InboundRules, profile.OutboundRules, false, false, "", "", nil) + parsedRules := rs.updateRules(key, profile.InboundRules, profile.OutboundRules, false, false, "", "", "", nil) rs.RulesUpdateCallbacks.OnProfileActive(key, parsedRules) } func (rs *RuleScanner) OnProfileInactive(key model.ProfileRulesKey) { - rs.updateRules(key, nil, nil, false, false, "", "", nil) + rs.updateRules(key, nil, nil, false, false, "", "", "", nil) rs.RulesUpdateCallbacks.OnProfileInactive(key) } @@ -177,26 +177,28 @@ func (rs *RuleScanner) OnPolicyActive(key model.PolicyKey, policy *model.Policy) policy.PreDNAT, policy.Namespace, selector.Normalise(policy.Selector), + policy.Tier, policy.PerformanceHints, ) rs.RulesUpdateCallbacks.OnPolicyActive(key, parsedRules) } func (rs *RuleScanner) OnPolicyInactive(key model.PolicyKey) { - rs.updateRules(key, nil, nil, false, false, "", "", nil) + rs.updateRules(key, nil, nil, false, false, "", "", "", nil) rs.RulesUpdateCallbacks.OnPolicyInactive(key) } func (rs *RuleScanner) updateRules( - key interface{}, + key any, inbound, outbound []model.Rule, untracked, preDNAT bool, origNamespace string, origSelector string, + tier string, perfHints []apiv3.PolicyPerformanceHint, ) (parsedRules *ParsedRules) { - log.Debugf("Scanning rules (%v in, %v out) for key %v", - len(inbound), len(outbound), key) + log.Debugf("Scanning rules (%v in, %v out) for key %v", len(inbound), len(outbound), key) + // Extract all the new selectors/named ports. currentUIDToIPSet := make(map[string]*IPSetData) parsedInbound := make([]*ParsedRule, len(inbound)) @@ -223,6 +225,7 @@ func (rs *RuleScanner) updateRules( } parsedRules = &ParsedRules{ Namespace: origNamespace, + Tier: tier, InboundRules: parsedInbound, OutboundRules: parsedOutbound, Untracked: untracked, @@ -251,7 +254,7 @@ func (rs *RuleScanner) updateRules( }) // Add the new into the index, triggering events as we discover newly-active IP sets. - addedUids.Iter(func(uid string) error { + for uid := range addedUids.All() { rs.rulesIDToUIDs.Put(key, uid) if !rs.uidsToRulesIDs.ContainsKey(uid) { ipSet := currentUIDToIPSet[uid] @@ -261,11 +264,10 @@ func (rs *RuleScanner) updateRules( rs.OnIPSetActive(ipSet) } rs.uidsToRulesIDs.Put(uid, key) - return nil - }) + } // And remove the old, triggering events as we clean up unused IP sets. - removedUids.Iter(func(uid string) error { + for uid := range removedUids.All() { rs.rulesIDToUIDs.Discard(key, uid) rs.uidsToRulesIDs.Discard(uid, key) if !rs.uidsToRulesIDs.ContainsKey(uid) { @@ -275,8 +277,7 @@ func (rs *RuleScanner) updateRules( log.Debugf("IP set became inactive: %v -> %v", uid, ipSetData) rs.OnIPSetInactive(ipSetData) } - return nil - }) + } return } @@ -300,6 +301,8 @@ type ParsedRules struct { // PreDNAT is true if these rules should be applied before any DNAT. PreDNAT bool + Tier string + OriginalSelector string PerformanceHints []apiv3.PolicyPerformanceHint diff --git a/felix/calc/rule_scanner_test.go b/felix/calc/rule_scanner_test.go index 375ef10b36a..0be4287d678 100644 --- a/felix/calc/rule_scanner_test.go +++ b/felix/calc/rule_scanner_test.go @@ -20,8 +20,7 @@ import ( "sort" "strings" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -200,12 +199,12 @@ var _ = Describe("ParsedRule", func() { // We expect all the fields to have the same name, except for // the selectors, which differ, and LogPrefix, which // is deprecated. - prType := reflect.TypeOf(ParsedRule{}) + prType := reflect.TypeFor[ParsedRule]() numPRFields := prType.NumField() prFields := set.New[string]() // Build a set of ParsedRule fields, minus the IPSetIDs variants. - for i := 0; i < numPRFields; i++ { + for i := range numPRFields { name := prType.Field(i).Name if strings.Contains(name, "IPSetIDs") || strings.Contains(name, "IPPortSetIDs") { continue @@ -220,10 +219,10 @@ var _ = Describe("ParsedRule", func() { // Build a set of model.Rule fields, excluding // those which aren't copied through to the ParsedRule. - mrType := reflect.TypeOf(model.Rule{}) + mrType := reflect.TypeFor[model.Rule]() numMRFields := mrType.NumField() mrFields := set.New[string]() - for i := 0; i < numMRFields; i++ { + for i := range numMRFields { name := mrType.Field(i).Name if strings.Contains(name, "Tag") || strings.Contains(name, "LogPrefix") || @@ -251,10 +250,10 @@ var _ = Describe("ParsedRule", func() { It("should have correct fields relative to proto.Rule", func() { // We expect all the fields to have the same name, except for // ICMP and service account matches, which differ in structure. - prType := reflect.TypeOf(ParsedRule{}) + prType := reflect.TypeFor[ParsedRule]() numPRFields := prType.NumField() prFields := []string{} - for i := 0; i < numPRFields; i++ { + for i := range numPRFields { name := strings.ToLower(prType.Field(i).Name) if strings.Contains(name, "icmptype") || strings.Contains(name, "icmpcode") || @@ -269,10 +268,10 @@ var _ = Describe("ParsedRule", func() { } prFields = append(prFields, name) } - protoType := reflect.TypeOf(proto.Rule{}) + protoType := reflect.TypeFor[proto.Rule]() numMRFields := protoType.NumField() protoFields := []string{} - for i := 0; i < numMRFields; i++ { + for i := range numMRFields { name := strings.ToLower(protoType.Field(i).Name) if strings.Contains(name, "icmp") || strings.Contains(name, "serviceaccount") { diff --git a/felix/calc/services_lookup_cache_test.go b/felix/calc/services_lookup_cache_test.go index d7f3e0e697e..a876f69d708 100644 --- a/felix/calc/services_lookup_cache_test.go +++ b/felix/calc/services_lookup_cache_test.go @@ -20,7 +20,7 @@ import ( "sync" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" kapiv1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" @@ -259,7 +259,7 @@ var _ = Describe("ServiceLookupsCache tests", func() { } // Make a list with 600 operations to be performed. - for i := 0; i < 200; i++ { + for range 200 { operations = append(operations, onResourceUpdateFn, getServiceFromPreDNATDestFn, diff --git a/felix/calc/state_test.go b/felix/calc/state_test.go index 8f119e3488d..7184322fb62 100644 --- a/felix/calc/state_test.go +++ b/felix/calc/state_test.go @@ -16,11 +16,13 @@ package calc_test import ( "fmt" + "maps" "reflect" "github.com/sirupsen/logrus" googleproto "google.golang.org/protobuf/proto" + "github.com/projectcalico/calico/felix/calc" "github.com/projectcalico/calico/felix/dataplane/mock" "github.com/projectcalico/calico/felix/proto" "github.com/projectcalico/calico/felix/types" @@ -49,6 +51,7 @@ type State struct { ExpectedUntrackedEndpointPolicyOrder map[string][]mock.TierInfo ExpectedPreDNATEndpointPolicyOrder map[string][]mock.TierInfo ExpectedHostMetadataV4V6 map[string]*proto.HostMetadataV4V6Update + ExpectedEndpointComputedData map[string]map[calc.EndpointComputedDataKind]calc.EndpointComputedData ExpectedNumberOfALPPolicies int ExpectedNumberOfTiers int ExpectedNumberOfPolicies int @@ -78,6 +81,7 @@ func NewState() State { ExpectedUntrackedEndpointPolicyOrder: make(map[string][]mock.TierInfo), ExpectedPreDNATEndpointPolicyOrder: make(map[string][]mock.TierInfo), ExpectedHostMetadataV4V6: make(map[string]*proto.HostMetadataV4V6Update), + ExpectedEndpointComputedData: make(map[string]map[calc.EndpointComputedDataKind]calc.EndpointComputedData), ExpectedNumberOfPolicies: -1, ExpectedNumberOfTiers: -1, ExpectedEncapsulation: &proto.Encapsulation{}, @@ -91,18 +95,11 @@ func (s State) Copy() State { for k, ips := range s.ExpectedIPSets { cpy.ExpectedIPSets[k] = ips.Copy() } - for k, v := range s.ExpectedEndpointPolicyOrder { - cpy.ExpectedEndpointPolicyOrder[k] = v - } - for k, v := range s.ExpectedUntrackedEndpointPolicyOrder { - cpy.ExpectedUntrackedEndpointPolicyOrder[k] = v - } - for k, v := range s.ExpectedPreDNATEndpointPolicyOrder { - cpy.ExpectedPreDNATEndpointPolicyOrder[k] = v - } - for k, v := range s.ExpectedHostMetadataV4V6 { - cpy.ExpectedHostMetadataV4V6[k] = v - } + maps.Copy(cpy.ExpectedEndpointPolicyOrder, s.ExpectedEndpointPolicyOrder) + maps.Copy(cpy.ExpectedUntrackedEndpointPolicyOrder, s.ExpectedUntrackedEndpointPolicyOrder) + maps.Copy(cpy.ExpectedPreDNATEndpointPolicyOrder, s.ExpectedPreDNATEndpointPolicyOrder) + maps.Copy(cpy.ExpectedHostMetadataV4V6, s.ExpectedHostMetadataV4V6) + maps.Copy(cpy.ExpectedEndpointComputedData, s.ExpectedEndpointComputedData) cpy.ExpectedPolicyIDs = s.ExpectedPolicyIDs.Copy() cpy.ExpectedUntrackedPolicyIDs = s.ExpectedUntrackedPolicyIDs.Copy() @@ -133,16 +130,6 @@ func (s State) withKVUpdates(kvs ...model.KVPair) (newState State) { // Make a set containing the new keys. newKeys := make(map[string]bool) - for i, kv := range kvs { - if k, ok := kv.Key.(model.PolicyKey); ok { - if k.Tier == "" { - k.Tier = "default" - kv.Key = k - kvs[i] = kv - } - } - } - for _, kv := range kvs { newKeys[kvToPath(kv)] = true } @@ -303,8 +290,8 @@ func (s State) Keys() set.Set[string] { return set } -func (s State) KVsCopy() map[string]interface{} { - kvs := make(map[string]interface{}) +func (s State) KVsCopy() map[string]any { + kvs := make(map[string]any) for _, kv := range s.DatastoreState { kvs[kvToPath(kv)] = kv.Value } @@ -314,7 +301,7 @@ func (s State) KVsCopy() map[string]interface{} { func kvToPath(kv model.KVPair) string { path, err := model.KeyToDefaultPath(kv.Key) if err != nil { - logrus.WithField("key", kv.Key).Panic("Unable to convert key to default path") + logrus.WithError(err).WithField("key", kv.Key).Panic("Unable to convert key to default path") } return path } @@ -377,7 +364,7 @@ func (s State) NumALPPolicies() int { return s.ExpectedNumberOfALPPolicies } -func (s State) ActiveKeys(keyTypeExample interface{}) set.Set[model.Key] { +func (s State) ActiveKeys(keyTypeExample any) set.Set[model.Key] { // Need to be a little careful here, the DatastoreState can contain an ordered sequence of updates and deletions // We need to track which keys are actually still live at the end of it. keys := set.New[model.Key]() diff --git a/felix/calc/states_for_test.go b/felix/calc/states_for_test.go index ad3671958db..bab6a52b7fe 100644 --- a/felix/calc/states_for_test.go +++ b/felix/calc/states_for_test.go @@ -24,7 +24,7 @@ import ( "github.com/projectcalico/calico/felix/dataplane/mock" "github.com/projectcalico/calico/felix/proto" "github.com/projectcalico/calico/felix/types" - apiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" . "github.com/projectcalico/calico/libcalico-go/lib/backend/model" ) @@ -56,9 +56,9 @@ var initialisedStore = empty.withKVUpdates( // withPolicy adds a tier and policy containing selectors for all and b=="b" var ( - pol1KVPair = KVPair{Key: PolicyKey{Name: "pol-1", Tier: "default"}, Value: &policy1_order20} - pol1KVPairAlways = KVPair{Key: PolicyKey{Name: "pol-1", Tier: "default"}, Value: &policy1_order20_always} - pol1KVPairOnDemand = KVPair{Key: PolicyKey{Name: "pol-1", Tier: "default"}, Value: &policy1_order20_ondemand} + pol1KVPair = KVPair{Key: PolicyKey{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, Value: &policy1_order20} + pol1KVPairAlways = KVPair{Key: PolicyKey{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, Value: &policy1_order20_always} + pol1KVPairOnDemand = KVPair{Key: PolicyKey{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, Value: &policy1_order20_ondemand} ) var withPolicy = initialisedStore.withKVUpdates( @@ -69,40 +69,40 @@ var withPolicy = initialisedStore.withKVUpdates( var withPolicyAlways = initialisedStore.withKVUpdates( pol1KVPairAlways, ).withActivePolicies( - types.PolicyID{Tier: "default", Name: "pol-1"}, + types.PolicyID{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, ).withIPSet(allSelectorId, []string{}).withIPSet(bEqBSelectorId, []string{}).withName("with always-programmed policy") // withPolicyIngressOnly adds a tier and ingress policy containing selectors for all var withPolicyIngressOnly = initialisedStore.withKVUpdates( - KVPair{Key: PolicyKey{Name: "pol-1", Tier: "default"}, Value: &policy1_order20_ingress_only}, + KVPair{Key: PolicyKey{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, Value: &policy1_order20_ingress_only}, ).withName("with ingress-only policy") // withPolicyEgressOnly adds a tier and egress policy containing selectors for b=="b" var withPolicyEgressOnly = initialisedStore.withKVUpdates( - KVPair{Key: PolicyKey{Name: "pol-1", Tier: "default"}, Value: &policy1_order20_egress_only}, + KVPair{Key: PolicyKey{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, Value: &policy1_order20_egress_only}, ).withName("with egress-only policy") // withUntrackedPolicy adds a tier and policy containing selectors for all and b=="b" var withUntrackedPolicy = initialisedStore.withKVUpdates( - KVPair{Key: PolicyKey{Name: "pol-1", Tier: "default"}, Value: &policy1_order20_untracked}, + KVPair{Key: PolicyKey{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, Value: &policy1_order20_untracked}, ).withName("with untracked policy") // withPreDNATPolicy adds a tier and policy containing selectors for all and a=="a" var withPreDNATPolicy = initialisedStore.withKVUpdates( - KVPair{Key: PolicyKey{Name: "pre-dnat-pol-1", Tier: "default"}, Value: &policy1_order20_pre_dnat}, + KVPair{Key: PolicyKey{Name: "pre-dnat-pol-1", Kind: v3.KindGlobalNetworkPolicy}, Value: &policy1_order20_pre_dnat}, ).withName("with pre-DNAT policy") // withHttpMethodPolicy adds a policy containing http method selector. var withHttpMethodPolicy = initialisedStore.withKVUpdates( - KVPair{Key: PolicyKey{Name: "pol-1"}, Value: &policy1_order20_http_match}, + KVPair{Key: PolicyKey{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, Value: &policy1_order20_http_match}, ).withTotalALPPolicies( 1, ).withName("with http-method policy") // withServiceAccountPolicy adds two policies containing service account selector. var withServiceAccountPolicy = initialisedStore.withKVUpdates( - KVPair{Key: PolicyKey{Name: "pol-1"}, Value: &policy1_order20_src_service_account}, - KVPair{Key: PolicyKey{Name: "pol-2"}, Value: &policy1_order20_dst_service_account}, + KVPair{Key: PolicyKey{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, Value: &policy1_order20_src_service_account}, + KVPair{Key: PolicyKey{Name: "pol-2", Kind: v3.KindGlobalNetworkPolicy}, Value: &policy1_order20_dst_service_account}, ).withTotalALPPolicies( 2, ).withName("with service-account policy") @@ -170,7 +170,7 @@ var localEp1WithPolicy = withPolicy.withKVUpdates( "10.0.0.2/32", "fc00:fe11::2/128", }).withActivePolicies( - types.PolicyID{Tier: "default", Name: "pol-1"}, + types.PolicyID{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, ).withActiveProfiles( types.ProfileID{Name: "prof-1"}, types.ProfileID{Name: "prof-2"}, @@ -178,7 +178,11 @@ var localEp1WithPolicy = withPolicy.withKVUpdates( ).withEndpoint( localWlEp1Id, []mock.TierInfo{ - {Name: "default", IngressPolicyNames: []string{"pol-1"}, EgressPolicyNames: []string{"pol-1"}}, + { + Name: "default", + IngressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + }, }, ).withRoutes( // Routes for the local WEPs. @@ -191,7 +195,7 @@ var localEp1WithPolicy = withPolicy.withKVUpdates( // withPolicyAndTier adds a tier and policy containing selectors for all and b=="b" var withPolicyAndTier = initialisedStore.withKVUpdates( KVPair{Key: TierKey{Name: "tier-1"}, Value: &tier1_order20}, - KVPair{Key: PolicyKey{Tier: "tier-1", Name: "pol-1"}, Value: &policy1_order20}, + KVPair{Key: PolicyKey{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, Value: &policy1_tier1_order20}, ).withName("with policy") // localEp1WithPolicyAndTier adds a local endpoint to the mix. It matches all and b=="b". @@ -208,7 +212,7 @@ var localEp1WithPolicyAndTier = withPolicyAndTier.withKVUpdates( "10.0.0.2/32", "fc00:fe11::2/128", }).withActivePolicies( - types.PolicyID{Tier: "tier-1", Name: "pol-1"}, + types.PolicyID{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, ).withActiveProfiles( types.ProfileID{Name: "prof-1"}, types.ProfileID{Name: "prof-2"}, @@ -217,9 +221,9 @@ var localEp1WithPolicyAndTier = withPolicyAndTier.withKVUpdates( localWlEp1Id, []mock.TierInfo{ { - Name: "tier-1", - IngressPolicyNames: []string{"pol-1"}, - EgressPolicyNames: []string{"pol-1"}, + Name: "tier-1", + IngressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, }, }, ).withRoutes( @@ -242,7 +246,7 @@ var localEp2WithPolicyAndTier = withPolicyAndTier.withKVUpdates( }).withIPSet( bEqBSelectorId, []string{}, ).withActivePolicies( - types.PolicyID{Tier: "tier-1", Name: "pol-1"}, + types.PolicyID{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, ).withActiveProfiles( types.ProfileID{Name: "prof-2"}, types.ProfileID{Name: "prof-3"}, @@ -250,9 +254,9 @@ var localEp2WithPolicyAndTier = withPolicyAndTier.withKVUpdates( localWlEp2Id, []mock.TierInfo{ { - Name: "tier-1", - IngressPolicyNames: []string{"pol-1"}, - EgressPolicyNames: []string{"pol-1"}, + Name: "tier-1", + IngressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, }, }, ).withRoutes( @@ -267,23 +271,36 @@ var localEp2WithPolicyAndTier = withPolicyAndTier.withKVUpdates( // change their orders to check that order trumps name. var commLocalEp1WithOneTierPolicy123 = commercialPolicyOrderState( [3]float64{order10, order20, order30}, - [3]string{"pol-1", "pol-2", "pol-3"}, + [3]types.PolicyID{ + {Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "pol-2", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "pol-3", Kind: v3.KindGlobalNetworkPolicy}, + }, ) var commLocalEp1WithOneTierPolicy321 = commercialPolicyOrderState( [3]float64{order30, order20, order10}, - [3]string{"pol-3", "pol-2", "pol-1"}, + [3]types.PolicyID{ + {Name: "pol-3", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "pol-2", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, + }, ) var commLocalEp1WithOneTierPolicyAlpha = commercialPolicyOrderState( [3]float64{order10, order10, order10}, - [3]string{"pol-1", "pol-2", "pol-3"}, + [3]types.PolicyID{ + {Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "pol-2", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "pol-3", Kind: v3.KindGlobalNetworkPolicy}, + }, ) -func commercialPolicyOrderState(policyOrders [3]float64, expectedOrder [3]string) State { +func commercialPolicyOrderState(policyOrders [3]float64, expectedIDs [3]types.PolicyID) State { policies := [3]Policy{} for i := range policies { policies[i] = Policy{ + Tier: "tier-1", Order: &policyOrders[i], Selector: "a == 'a'", InboundRules: []Rule{{SrcSelector: allSelector}}, @@ -293,9 +310,9 @@ func commercialPolicyOrderState(policyOrders [3]float64, expectedOrder [3]string state := initialisedStore.withKVUpdates( KVPair{Key: localWlEpKey1, Value: &localWlEp1}, KVPair{Key: TierKey{Name: "tier-1"}, Value: &tier1_order20}, - KVPair{Key: PolicyKey{Tier: "tier-1", Name: "pol-1"}, Value: &policies[0]}, - KVPair{Key: PolicyKey{Tier: "tier-1", Name: "pol-2"}, Value: &policies[1]}, - KVPair{Key: PolicyKey{Tier: "tier-1", Name: "pol-3"}, Value: &policies[2]}, + KVPair{Key: PolicyKey{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, Value: &policies[0]}, + KVPair{Key: PolicyKey{Name: "pol-2", Kind: v3.KindGlobalNetworkPolicy}, Value: &policies[1]}, + KVPair{Key: PolicyKey{Name: "pol-3", Kind: v3.KindGlobalNetworkPolicy}, Value: &policies[2]}, ).withIPSet(allSelectorId, []string{ "10.0.0.1/32", // ep1 "fc00:fe11::1/128", @@ -307,9 +324,9 @@ func commercialPolicyOrderState(policyOrders [3]float64, expectedOrder [3]string "10.0.0.2/32", "fc00:fe11::2/128", }).withActivePolicies( - types.PolicyID{Tier: "tier-1", Name: "pol-1"}, - types.PolicyID{Tier: "tier-1", Name: "pol-2"}, - types.PolicyID{Tier: "tier-1", Name: "pol-3"}, + types.PolicyID{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, + types.PolicyID{Name: "pol-2", Kind: v3.KindGlobalNetworkPolicy}, + types.PolicyID{Name: "pol-3", Kind: v3.KindGlobalNetworkPolicy}, ).withActiveProfiles( types.ProfileID{Name: "prof-1"}, types.ProfileID{Name: "prof-2"}, @@ -318,9 +335,9 @@ func commercialPolicyOrderState(policyOrders [3]float64, expectedOrder [3]string localWlEp1Id, []mock.TierInfo{ { - Name: "tier-1", - IngressPolicyNames: expectedOrder[:], - EgressPolicyNames: expectedOrder[:], + Name: "tier-1", + IngressPolicies: expectedIDs[:], + EgressPolicies: expectedIDs[:], }, }, ).withRoutes( @@ -329,7 +346,7 @@ func commercialPolicyOrderState(policyOrders [3]float64, expectedOrder [3]string routelocalWlTenDotTwo, routelocalWlV6ColonOne, routelocalWlV6ColonTwo, - ).withName(fmt.Sprintf("ep1 local, 1 tier, policies %v", expectedOrder[:])) + ).withName(fmt.Sprintf("ep1 local, 1 tier, policies %v", expectedIDs[:])) return state } @@ -369,22 +386,31 @@ func tierOrderState(tierOrders [3]float64, expectedOrder [3]string) State { Order: &tierOrders[i], } } + + // initialize three policies with the same order but different tier names + pol1Tier1 := policy1_order20 + pol1Tier1.Tier = "tier-1" + pol1Tier2 := policy1_order20 + pol1Tier2.Tier = "tier-2" + pol1Tier3 := policy1_order20 + pol1Tier3.Tier = "tier-3" + state := initialisedStore.withKVUpdates( KVPair{Key: localWlEpKey1, Value: &localWlEp1}, KVPair{Key: TierKey{Name: "tier-1"}, Value: &tiers[0]}, - KVPair{Key: PolicyKey{Tier: "tier-1", Name: "tier-1-pol"}, Value: &policy1_order20}, + KVPair{Key: PolicyKey{Name: "tier-1-pol", Kind: v3.KindGlobalNetworkPolicy}, Value: &pol1Tier1}, KVPair{Key: TierKey{Name: "tier-2"}, Value: &tiers[1]}, - KVPair{Key: PolicyKey{Tier: "tier-2", Name: "tier-2-pol"}, Value: &policy1_order20}, + KVPair{Key: PolicyKey{Name: "tier-2-pol", Kind: v3.KindGlobalNetworkPolicy}, Value: &pol1Tier2}, KVPair{Key: TierKey{Name: "tier-3"}, Value: &tiers[2]}, - KVPair{Key: PolicyKey{Tier: "tier-3", Name: "tier-3-pol"}, Value: &policy1_order20}, + KVPair{Key: PolicyKey{Name: "tier-3-pol", Kind: v3.KindGlobalNetworkPolicy}, Value: &pol1Tier3}, ).withIPSet( allSelectorId, ep1IPs, ).withIPSet( bEqBSelectorId, ep1IPs, ).withActivePolicies( - types.PolicyID{Tier: "tier-1", Name: "tier-1-pol"}, - types.PolicyID{Tier: "tier-2", Name: "tier-2-pol"}, - types.PolicyID{Tier: "tier-3", Name: "tier-3-pol"}, + types.PolicyID{Name: "tier-1-pol", Kind: v3.KindGlobalNetworkPolicy}, + types.PolicyID{Name: "tier-2-pol", Kind: v3.KindGlobalNetworkPolicy}, + types.PolicyID{Name: "tier-3-pol", Kind: v3.KindGlobalNetworkPolicy}, ).withActiveProfiles( types.ProfileID{Name: "prof-1"}, types.ProfileID{Name: "prof-2"}, @@ -393,19 +419,19 @@ func tierOrderState(tierOrders [3]float64, expectedOrder [3]string) State { localWlEp1Id, []mock.TierInfo{ { - Name: expectedOrder[0], - IngressPolicyNames: []string{expectedOrder[0] + "-pol"}, - EgressPolicyNames: []string{expectedOrder[0] + "-pol"}, + Name: expectedOrder[0], + IngressPolicies: []types.PolicyID{{Name: expectedOrder[0] + "-pol", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: []types.PolicyID{{Name: expectedOrder[0] + "-pol", Kind: v3.KindGlobalNetworkPolicy}}, }, { - Name: expectedOrder[1], - IngressPolicyNames: []string{expectedOrder[1] + "-pol"}, - EgressPolicyNames: []string{expectedOrder[1] + "-pol"}, + Name: expectedOrder[1], + IngressPolicies: []types.PolicyID{{Name: expectedOrder[1] + "-pol", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: []types.PolicyID{{Name: expectedOrder[1] + "-pol", Kind: v3.KindGlobalNetworkPolicy}}, }, { - Name: expectedOrder[2], - IngressPolicyNames: []string{expectedOrder[2] + "-pol"}, - EgressPolicyNames: []string{expectedOrder[2] + "-pol"}, + Name: expectedOrder[2], + IngressPolicies: []types.PolicyID{{Name: expectedOrder[2] + "-pol", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: []types.PolicyID{{Name: expectedOrder[2] + "-pol", Kind: v3.KindGlobalNetworkPolicy}}, }, }, ).withRoutes( @@ -438,7 +464,7 @@ var localEpsWithPolicyAndTier = withPolicyAndTier.withKVUpdates( "10.0.0.2/32", "fc00:fe11::2/128", }).withActivePolicies( - types.PolicyID{Tier: "tier-1", Name: "pol-1"}, + types.PolicyID{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, ).withActiveProfiles( types.ProfileID{Name: "prof-1"}, types.ProfileID{Name: "prof-2"}, @@ -448,18 +474,18 @@ var localEpsWithPolicyAndTier = withPolicyAndTier.withKVUpdates( localWlEp1Id, []mock.TierInfo{ { - Name: "tier-1", - IngressPolicyNames: []string{"pol-1"}, - EgressPolicyNames: []string{"pol-1"}, + Name: "tier-1", + IngressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, }, }, ).withEndpoint( localWlEp2Id, []mock.TierInfo{ { - Name: "tier-1", - IngressPolicyNames: []string{"pol-1"}, - EgressPolicyNames: []string{"pol-1"}, + Name: "tier-1", + IngressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, }, }, ).withRoutes( @@ -482,7 +508,7 @@ var localEp1WithPolicyOnDemand = localEp1WithPolicy.withKVUpdates( // localEp1WithNamedPortPolicy as above but with named port in the policy. var localEp1WithNamedPortPolicy = localEp1WithPolicy.withKVUpdates( - KVPair{Key: PolicyKey{Tier: "default", Name: "pol-1"}, Value: &policy1_order20_with_selector_and_named_port_tcpport}, + KVPair{Key: PolicyKey{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, Value: &policy1_order20_with_selector_and_named_port_tcpport}, ).withIPSet(namedPortAllTCPID, []string{ "10.0.0.1,tcp:8080", "10.0.0.2,tcp:8080", @@ -493,7 +519,7 @@ var localEp1WithNamedPortPolicy = localEp1WithPolicy.withKVUpdates( // localEp1WithNamedPortPolicy as above but with negated named port in the policy. var localEp1WithNegatedNamedPortPolicy = empty.withKVUpdates( KVPair{Key: localWlEpKey1, Value: &localWlEp1}, - KVPair{Key: PolicyKey{Name: "pol-1", Tier: "default"}, Value: &policy1_order20_with_selector_and_negated_named_port_tcpport}, + KVPair{Key: PolicyKey{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, Value: &policy1_order20_with_selector_and_negated_named_port_tcpport}, ).withIPSet(namedPortAllLessFoobarTCPID, []string{ "10.0.0.1,tcp:8080", "10.0.0.2,tcp:8080", @@ -506,7 +532,7 @@ var localEp1WithNegatedNamedPortPolicy = empty.withKVUpdates( "fc00:fe11::1/128", "fc00:fe11::2/128", }).withActivePolicies( - types.PolicyID{Tier: "default", Name: "pol-1"}, + types.PolicyID{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, ).withActiveProfiles( types.ProfileID{Name: "prof-1"}, types.ProfileID{Name: "prof-2"}, @@ -515,8 +541,8 @@ var localEp1WithNegatedNamedPortPolicy = empty.withKVUpdates( localWlEp1Id, []mock.TierInfo{ { - Name: "default", - IngressPolicyNames: []string{"pol-1"}, + Name: "default", + IngressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, }, }, ).withRoutes( @@ -530,7 +556,7 @@ var localEp1WithNegatedNamedPortPolicy = empty.withKVUpdates( // As above but using the destination fields in the policy instead of source. var localEp1WithNegatedNamedPortPolicyDest = localEp1WithNegatedNamedPortPolicy.withKVUpdates( KVPair{ - Key: PolicyKey{Name: "pol-1", Tier: "default"}, + Key: PolicyKey{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, Value: &policy1_order20_with_selector_and_negated_named_port_tcpport_dest, }, ).withName("ep1 local, negated named port policy in destination fields") @@ -538,7 +564,7 @@ var localEp1WithNegatedNamedPortPolicyDest = localEp1WithNegatedNamedPortPolicy. // A host endpoint with a named port var localHostEp1WithNamedPortPolicy = empty.withKVUpdates( KVPair{Key: hostEpWithNameKey, Value: &hostEpWithNamedPorts}, - KVPair{Key: PolicyKey{Tier: "default", Name: "pol-1"}, Value: &policy1_order20_with_selector_and_named_port_tcpport}, + KVPair{Key: PolicyKey{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, Value: &policy1_order20_with_selector_and_named_port_tcpport}, ).withIPSet(namedPortAllTCPID, []string{ "10.0.0.1,tcp:8080", "10.0.0.2,tcp:8080", @@ -550,24 +576,28 @@ var localHostEp1WithNamedPortPolicy = empty.withKVUpdates( "10.0.0.2/32", "fc00:fe11::2/128", }).withActivePolicies( - types.PolicyID{Tier: "default", Name: "pol-1"}, + types.PolicyID{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, ).withActiveProfiles( types.ProfileID{Name: "prof-1"}, ).withEndpoint( "named", []mock.TierInfo{ - {Name: "default", IngressPolicyNames: []string{"pol-1"}, EgressPolicyNames: []string{"pol-1"}}, + { + Name: "default", + IngressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + }, }, ).withName("Host endpoint, named port policy") // As above but with no selector in the rules. var localEp1WithNamedPortPolicyNoSelector = localEp1WithNamedPortPolicy.withKVUpdates( - KVPair{Key: PolicyKey{Tier: "default", Name: "pol-1"}, Value: &policy1_order20_with_named_port_tcpport}, + KVPair{Key: PolicyKey{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, Value: &policy1_order20_with_named_port_tcpport}, ).withName("ep1 local, named port only") // As above but with negated named port. var localEp1WithNegatedNamedPortPolicyNoSelector = localEp1WithNamedPortPolicy.withKVUpdates( - KVPair{Key: PolicyKey{Tier: "default", Name: "pol-1"}, Value: &policy1_order20_with_named_port_tcpport_negated}, + KVPair{Key: PolicyKey{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, Value: &policy1_order20_with_named_port_tcpport_negated}, ).withName("ep1 local, negated named port only") // localEp1WithIngressPolicy is as above except ingress policy only. @@ -579,7 +609,7 @@ var localEp1WithIngressPolicy = withPolicyIngressOnly.withKVUpdates( "10.0.0.2/32", // ep1 and ep2 "fc00:fe11::2/128", }).withActivePolicies( - types.PolicyID{Tier: "default", Name: "pol-1"}, + types.PolicyID{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, ).withActiveProfiles( types.ProfileID{Name: "prof-1"}, types.ProfileID{Name: "prof-2"}, @@ -587,7 +617,11 @@ var localEp1WithIngressPolicy = withPolicyIngressOnly.withKVUpdates( ).withEndpoint( localWlEp1Id, []mock.TierInfo{ - {Name: "default", IngressPolicyNames: []string{"pol-1"}, EgressPolicyNames: nil}, + { + Name: "default", + IngressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: nil, + }, }, ).withRoutes( // Routes for the local WEPs. @@ -599,7 +633,7 @@ var localEp1WithIngressPolicy = withPolicyIngressOnly.withKVUpdates( // localEp1WithNamedPortPolicy as above but with UDP named port in the policy. var localEp1WithNamedPortPolicyUDP = localEp1WithPolicy.withKVUpdates( - KVPair{Key: PolicyKey{Tier: "default", Name: "pol-1"}, Value: &policy1_order20_with_selector_and_named_port_udpport}, + KVPair{Key: PolicyKey{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, Value: &policy1_order20_with_selector_and_named_port_udpport}, ).withIPSet(namedPortAllUDPID, []string{ "10.0.0.1,udp:9091", "10.0.0.2,udp:9091", @@ -620,7 +654,7 @@ var hostEp1WithPolicy = withPolicy.withKVUpdates( "10.0.0.2/32", "fc00:fe11::2/128", }).withActivePolicies( - types.PolicyID{Tier: "default", Name: "pol-1"}, + types.PolicyID{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, ).withActiveProfiles( types.ProfileID{Name: "prof-1"}, types.ProfileID{Name: "prof-2"}, @@ -628,7 +662,11 @@ var hostEp1WithPolicy = withPolicy.withKVUpdates( ).withEndpoint( hostEpWithNameId, []mock.TierInfo{ - {Name: "default", IngressPolicyNames: []string{"pol-1"}, EgressPolicyNames: []string{"pol-1"}}, + { + Name: "default", + IngressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + }, }, ).withName("host ep1, policy") @@ -640,7 +678,7 @@ var hostEp1WithIngressPolicy = withPolicyIngressOnly.withKVUpdates( "10.0.0.2/32", // ep1 and ep2 "fc00:fe11::2/128", }).withActivePolicies( - types.PolicyID{Tier: "default", Name: "pol-1"}, + types.PolicyID{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, ).withActiveProfiles( types.ProfileID{Name: "prof-1"}, types.ProfileID{Name: "prof-2"}, @@ -648,7 +686,11 @@ var hostEp1WithIngressPolicy = withPolicyIngressOnly.withKVUpdates( ).withEndpoint( hostEpWithNameId, []mock.TierInfo{ - {Name: "default", IngressPolicyNames: []string{"pol-1"}, EgressPolicyNames: nil}, + { + Name: "default", + IngressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: nil, + }, }, ).withName("host ep1, ingress-only policy") @@ -660,7 +702,7 @@ var hostEp1WithEgressPolicy = withPolicyEgressOnly.withKVUpdates( "10.0.0.2/32", "fc00:fe11::2/128", }).withActivePolicies( - types.PolicyID{Tier: "default", Name: "pol-1"}, + types.PolicyID{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, ).withActiveProfiles( types.ProfileID{Name: "prof-1"}, types.ProfileID{Name: "prof-2"}, @@ -668,7 +710,11 @@ var hostEp1WithEgressPolicy = withPolicyEgressOnly.withKVUpdates( ).withEndpoint( hostEpWithNameId, []mock.TierInfo{ - {Name: "default", IngressPolicyNames: nil, EgressPolicyNames: []string{"pol-1"}}, + { + Name: "default", + IngressPolicies: nil, + EgressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + }, }, ).withName("host ep1, egress-only policy") @@ -685,9 +731,9 @@ var hostEp1WithUntrackedPolicy = withUntrackedPolicy.withKVUpdates( "10.0.0.2/32", "fc00:fe11::2/128", }).withActivePolicies( - types.PolicyID{Tier: "default", Name: "pol-1"}, + types.PolicyID{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, ).withUntrackedPolicies( - types.PolicyID{Tier: "default", Name: "pol-1"}, + types.PolicyID{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, ).withActiveProfiles( types.ProfileID{Name: "prof-1"}, types.ProfileID{Name: "prof-2"}, @@ -696,7 +742,11 @@ var hostEp1WithUntrackedPolicy = withUntrackedPolicy.withKVUpdates( hostEpWithNameId, []mock.TierInfo{}, []mock.TierInfo{ - {Name: "default", IngressPolicyNames: []string{"pol-1"}, EgressPolicyNames: []string{"pol-1"}}, + { + Name: "default", + IngressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + }, }, []mock.TierInfo{}, ).withName("host ep1, untracked policy") @@ -709,9 +759,9 @@ var hostEp1WithPreDNATPolicy = withPreDNATPolicy.withKVUpdates( "10.0.0.2/32", // ep1 and ep2 "fc00:fe11::2/128", }).withActivePolicies( - types.PolicyID{Tier: "default", Name: "pre-dnat-pol-1"}, + types.PolicyID{Name: "pre-dnat-pol-1", Kind: v3.KindGlobalNetworkPolicy}, ).withPreDNATPolicies( - types.PolicyID{Tier: "default", Name: "pre-dnat-pol-1"}, + types.PolicyID{Name: "pre-dnat-pol-1", Kind: v3.KindGlobalNetworkPolicy}, ).withActiveProfiles( types.ProfileID{Name: "prof-1"}, types.ProfileID{Name: "prof-2"}, @@ -721,22 +771,34 @@ var hostEp1WithPreDNATPolicy = withPreDNATPolicy.withKVUpdates( []mock.TierInfo{}, []mock.TierInfo{}, []mock.TierInfo{ - {Name: "default", IngressPolicyNames: []string{"pre-dnat-pol-1"}, EgressPolicyNames: nil}, + { + Name: "default", + IngressPolicies: []types.PolicyID{{Name: "pre-dnat-pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: nil, + }, }, ).withName("host ep1, pre-DNAT policy") var hostEp1WithTrackedAndUntrackedPolicy = hostEp1WithUntrackedPolicy.withKVUpdates( - KVPair{Key: PolicyKey{Name: "pol-2", Tier: "default"}, Value: &policy1_order20}, + KVPair{Key: PolicyKey{Name: "pol-2", Kind: v3.KindGlobalNetworkPolicy}, Value: &policy1_order20}, ).withActivePolicies( - types.PolicyID{Tier: "default", Name: "pol-1"}, - types.PolicyID{Tier: "default", Name: "pol-2"}, + types.PolicyID{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, + types.PolicyID{Name: "pol-2", Kind: v3.KindGlobalNetworkPolicy}, ).withEndpointUntracked( hostEpWithNameId, []mock.TierInfo{ - {Name: "default", IngressPolicyNames: []string{"pol-2"}, EgressPolicyNames: []string{"pol-2"}}, + { + Name: "default", + IngressPolicies: []types.PolicyID{{Name: "pol-2", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: []types.PolicyID{{Name: "pol-2", Kind: v3.KindGlobalNetworkPolicy}}, + }, }, []mock.TierInfo{ - {Name: "default", IngressPolicyNames: []string{"pol-1"}, EgressPolicyNames: []string{"pol-1"}}, + { + Name: "default", + IngressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + }, }, []mock.TierInfo{}, ).withName("host ep1, tracked+untracked policy") @@ -749,14 +811,18 @@ var hostEp2WithPolicy = withPolicy.withKVUpdates( "10.0.0.3/32", // ep2 "fc00:fe11::3/128", }).withIPSet(bEqBSelectorId, []string{}).withActivePolicies( - types.PolicyID{Tier: "default", Name: "pol-1"}, + types.PolicyID{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, ).withActiveProfiles( types.ProfileID{Name: "prof-2"}, types.ProfileID{Name: "prof-3"}, ).withEndpoint( hostEpNoNameId, []mock.TierInfo{ - {Name: "default", IngressPolicyNames: []string{"pol-1"}, EgressPolicyNames: []string{"pol-1"}}, + { + Name: "default", + IngressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + }, }, ).withName("host ep2, policy") @@ -764,23 +830,36 @@ var hostEp2WithPolicy = withPolicy.withKVUpdates( // change their orders to check that order trumps name. var localEp1WithOneTierPolicy123 = policyOrderState( [3]float64{order10, order20, order30}, - [3]string{"pol-1", "pol-2", "pol-3"}, + [3]types.PolicyID{ + {Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "pol-2", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "pol-3", Kind: v3.KindGlobalNetworkPolicy}, + }, ) var localEp1WithOneTierPolicy321 = policyOrderState( [3]float64{order30, order20, order10}, - [3]string{"pol-3", "pol-2", "pol-1"}, + [3]types.PolicyID{ + {Name: "pol-3", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "pol-2", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, + }, ) var localEp1WithOneTierPolicyAlpha = policyOrderState( [3]float64{order10, order10, order10}, - [3]string{"pol-1", "pol-2", "pol-3"}, + [3]types.PolicyID{ + {Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "pol-2", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "pol-3", Kind: v3.KindGlobalNetworkPolicy}, + }, ) -func policyOrderState(policyOrders [3]float64, expectedOrder [3]string) State { +func policyOrderState(policyOrders [3]float64, expectedOrder [3]types.PolicyID) State { policies := [3]Policy{} for i := range policies { policies[i] = Policy{ + Tier: "default", Order: &policyOrders[i], Selector: "a == 'a'", InboundRules: []Rule{{SrcSelector: allSelector}}, @@ -789,9 +868,9 @@ func policyOrderState(policyOrders [3]float64, expectedOrder [3]string) State { } state := initialisedStore.withKVUpdates( KVPair{Key: localWlEpKey1, Value: &localWlEp1}, - KVPair{Key: PolicyKey{Name: "pol-1", Tier: "default"}, Value: &policies[0]}, - KVPair{Key: PolicyKey{Name: "pol-2", Tier: "default"}, Value: &policies[1]}, - KVPair{Key: PolicyKey{Name: "pol-3", Tier: "default"}, Value: &policies[2]}, + KVPair{Key: PolicyKey{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, Value: &policies[0]}, + KVPair{Key: PolicyKey{Name: "pol-2", Kind: v3.KindGlobalNetworkPolicy}, Value: &policies[1]}, + KVPair{Key: PolicyKey{Name: "pol-3", Kind: v3.KindGlobalNetworkPolicy}, Value: &policies[2]}, ).withIPSet(allSelectorId, []string{ "10.0.0.1/32", // ep1 "fc00:fe11::1/128", @@ -803,9 +882,9 @@ func policyOrderState(policyOrders [3]float64, expectedOrder [3]string) State { "10.0.0.2/32", "fc00:fe11::2/128", }).withActivePolicies( - types.PolicyID{Tier: "default", Name: "pol-1"}, - types.PolicyID{Tier: "default", Name: "pol-2"}, - types.PolicyID{Tier: "default", Name: "pol-3"}, + types.PolicyID{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, + types.PolicyID{Name: "pol-2", Kind: v3.KindGlobalNetworkPolicy}, + types.PolicyID{Name: "pol-3", Kind: v3.KindGlobalNetworkPolicy}, ).withActiveProfiles( types.ProfileID{Name: "prof-1"}, types.ProfileID{Name: "prof-2"}, @@ -813,7 +892,7 @@ func policyOrderState(policyOrders [3]float64, expectedOrder [3]string) State { ).withEndpoint( localWlEp1Id, []mock.TierInfo{ - {Name: "default", IngressPolicyNames: expectedOrder[:], EgressPolicyNames: expectedOrder[:]}, + {Name: "default", IngressPolicies: expectedOrder[:], EgressPolicies: expectedOrder[:]}, }, ).withRoutes( // Routes for the local WEPs. @@ -837,14 +916,18 @@ var localEp2WithPolicy = withPolicy.withKVUpdates( }).withIPSet( bEqBSelectorId, []string{}, ).withActivePolicies( - types.PolicyID{Tier: "default", Name: "pol-1"}, + types.PolicyID{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, ).withActiveProfiles( types.ProfileID{Name: "prof-2"}, types.ProfileID{Name: "prof-3"}, ).withEndpoint( localWlEp2Id, []mock.TierInfo{ - {Name: "default", IngressPolicyNames: []string{"pol-1"}, EgressPolicyNames: []string{"pol-1"}}, + { + Name: "default", + IngressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + }, }, ).withRoutes( // Routes for the local WEPs. @@ -874,7 +957,7 @@ var localEpsWithPolicy = withPolicy.withKVUpdates( "10.0.0.2/32", "fc00:fe11::2/128", }).withActivePolicies( - types.PolicyID{Tier: "default", Name: "pol-1"}, + types.PolicyID{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, ).withActiveProfiles( types.ProfileID{Name: "prof-1"}, types.ProfileID{Name: "prof-2"}, @@ -883,12 +966,20 @@ var localEpsWithPolicy = withPolicy.withKVUpdates( ).withEndpoint( localWlEp1Id, []mock.TierInfo{ - {Name: "default", IngressPolicyNames: []string{"pol-1"}, EgressPolicyNames: []string{"pol-1"}}, + { + Name: "default", + IngressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + }, }, ).withEndpoint( localWlEp2Id, []mock.TierInfo{ - {Name: "default", IngressPolicyNames: []string{"pol-1"}, EgressPolicyNames: []string{"pol-1"}}, + { + Name: "default", + IngressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: []types.PolicyID{{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}}, + }, }, ).withRoutes( // Routes for the local WEPs. @@ -901,7 +992,7 @@ var localEpsWithPolicy = withPolicy.withKVUpdates( ).withName("2 local, overlapping IPs & a policy") var localEpsWithNamedPortsPolicy = localEpsWithPolicy.withKVUpdates( - KVPair{Key: PolicyKey{Tier: "default", Name: "pol-1"}, Value: &policy1_order20_with_selector_and_named_port_tcpport}, + KVPair{Key: PolicyKey{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, Value: &policy1_order20_with_selector_and_named_port_tcpport}, ).withIPSet( allSelectorId, nil, ).withIPSet(namedPortAllTCPID, []string{ @@ -914,7 +1005,7 @@ var localEpsWithNamedPortsPolicy = localEpsWithPolicy.withKVUpdates( }).withName("2 local, overlapping IPs & a named port policy") var localEpsWithNamedPortsPolicyTCPPort2 = localEpsWithPolicy.withKVUpdates( - KVPair{Key: PolicyKey{Tier: "default", Name: "pol-1"}, Value: &policy1_order20_with_selector_and_named_port_tcpport2}, + KVPair{Key: PolicyKey{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, Value: &policy1_order20_with_selector_and_named_port_tcpport2}, ).withIPSet( allSelectorId, nil, ).withIPSet(namedPortAllTCP2ID, []string{ @@ -933,7 +1024,7 @@ var localEpsWithNamedPortsPolicyTCPPort2 = localEpsWithPolicy.withKVUpdates( // localEpsWithMismatchedNamedPortsPolicy contains a policy that has named port matches where the // rule has a protocol that doesn't match that in the named port definitions in the endpoint. var localEpsWithMismatchedNamedPortsPolicy = localEpsWithPolicy.withKVUpdates( - KVPair{Key: PolicyKey{Tier: "default", Name: "pol-1"}, Value: &policy1_order20_with_named_port_mismatched_protocol}, + KVPair{Key: PolicyKey{Name: "pol-1", Kind: v3.KindGlobalNetworkPolicy}, Value: &policy1_order20_with_named_port_mismatched_protocol}, ).withIPSet( allSelectorId, nil, ).withIPSet( @@ -975,22 +1066,22 @@ var localEpsWithOverlappingIPsAndInheritedLabels = empty.withKVUpdates( // Building on the above, we add a policy to match on the inherited label, which should produce // a named port. var localEpsAndNamedPortPolicyMatchingInheritedLabelOnEP1 = localEpsWithOverlappingIPsAndInheritedLabels.withKVUpdates( - KVPair{Key: PolicyKey{Tier: "default", Name: "inherit-pol"}, Value: &policy_with_named_port_inherit}, + KVPair{Key: PolicyKey{Name: "inherit-pol", Kind: v3.KindGlobalNetworkPolicy}, Value: &policy_with_named_port_inherit}, ).withActivePolicies( - types.PolicyID{Tier: "default", Name: "inherit-pol"}, + types.PolicyID{Name: "inherit-pol", Kind: v3.KindGlobalNetworkPolicy}, ).withEndpoint( localWlEp1Id, []mock.TierInfo{{ - Name: "default", - IngressPolicyNames: []string{"inherit-pol"}, - EgressPolicyNames: []string{"inherit-pol"}, + Name: "default", + IngressPolicies: []types.PolicyID{{Name: "inherit-pol", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: []types.PolicyID{{Name: "inherit-pol", Kind: v3.KindGlobalNetworkPolicy}}, }}, ).withEndpoint( localWlEp2Id, []mock.TierInfo{{ - Name: "default", - IngressPolicyNames: []string{"inherit-pol"}, - EgressPolicyNames: []string{"inherit-pol"}, + Name: "default", + IngressPolicies: []types.PolicyID{{Name: "inherit-pol", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: []types.PolicyID{{Name: "inherit-pol", Kind: v3.KindGlobalNetworkPolicy}}, }}, ).withIPSet(namedPortInheritIPSetID, []string{ "10.0.0.1,tcp:8080", // ep1 @@ -1540,8 +1631,8 @@ var vxlanWithBlockRoutes = []types.RouteUpdate{ } var ( - remoteNodeResKey = ResourceKey{Name: remoteHostname, Kind: apiv3.KindNode} - localNodeResKey = ResourceKey{Name: localHostname, Kind: apiv3.KindNode} + remoteNodeResKey = ResourceKey{Name: remoteHostname, Kind: internalapi.KindNode} + localNodeResKey = ResourceKey{Name: localHostname, Kind: internalapi.KindNode} ) // As vxlanWithBlock but with a host sharing the same IP. No route update because we tie-break on host name. @@ -1575,11 +1666,11 @@ var vxlanWithDupNodeIPRemoved = vxlanWithBlockDupNodeIP.withKVUpdates( // As vxlanWithBlock but with node resources instead of host IPs. var vxlanWithBlockNodeRes = vxlanWithBlock.withKVUpdates( KVPair{Key: remoteHostIPKey, Value: nil}, - KVPair{Key: remoteNodeResKey, Value: &apiv3.Node{ + KVPair{Key: remoteNodeResKey, Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: localHostname, }, - Spec: apiv3.NodeSpec{BGP: &apiv3.NodeBGPSpec{ + Spec: internalapi.NodeSpec{BGP: &internalapi.NodeBGPSpec{ IPv4Address: remoteHostIP.String() + "/24", }}, }}, @@ -1858,19 +1949,19 @@ var vxlanLocalBlockWithBorrowsLocalWEP = vxlanLocalBlockWithBorrows.withKVUpdate var vxlanLocalBlockWithBorrowsNodeRes = vxlanLocalBlockWithBorrows.withKVUpdates( KVPair{Key: localHostIPKey, Value: nil}, KVPair{Key: remoteHostIPKey, Value: nil}, - KVPair{Key: remoteNodeResKey, Value: &apiv3.Node{ + KVPair{Key: remoteNodeResKey, Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: remoteHostname, }, - Spec: apiv3.NodeSpec{BGP: &apiv3.NodeBGPSpec{ + Spec: internalapi.NodeSpec{BGP: &internalapi.NodeBGPSpec{ IPv4Address: remoteHostIPWithPrefix, }}, }}, - KVPair{Key: localNodeResKey, Value: &apiv3.Node{ + KVPair{Key: localNodeResKey, Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: localHostname, }, - Spec: apiv3.NodeSpec{BGP: &apiv3.NodeBGPSpec{ + Spec: internalapi.NodeSpec{BGP: &internalapi.NodeBGPSpec{ IPv4Address: localHostIPWithPrefix, }}, }}, @@ -1924,19 +2015,19 @@ var vxlanLocalBlockWithBorrowsCrossSubnetNodeRes = vxlanLocalBlockWithBorrowsNod // As vxlanLocalBlockWithBorrowsCrossSubnetNodeRes but hosts are in a different pool. var vxlanLocalBlockWithBorrowsDifferentSubnetNodeRes = vxlanLocalBlockWithBorrowsNodeRes.withKVUpdates( KVPair{Key: ipPoolKey, Value: &ipPoolWithVXLANCrossSubnet}, - KVPair{Key: remoteNodeResKey, Value: &apiv3.Node{ + KVPair{Key: remoteNodeResKey, Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: remoteHostname, }, - Spec: apiv3.NodeSpec{BGP: &apiv3.NodeBGPSpec{ + Spec: internalapi.NodeSpec{BGP: &internalapi.NodeBGPSpec{ IPv4Address: remoteHostIP.String(), // Omitting the /32 here to check the v3 validator is used for this resource. }}, }}, - KVPair{Key: localNodeResKey, Value: &apiv3.Node{ + KVPair{Key: localNodeResKey, Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: localHostname, }, - Spec: apiv3.NodeSpec{BGP: &apiv3.NodeBGPSpec{ + Spec: internalapi.NodeSpec{BGP: &internalapi.NodeBGPSpec{ IPv4Address: localHostIP.String() + "/32", }}, }}, @@ -2142,11 +2233,11 @@ var vxlanSlash32NoPool = empty.withKVUpdates( var vxlanV6WithBlock = empty.withKVUpdates( KVPair{Key: v6IPPoolKey, Value: &v6IPPoolWithVXLAN}, KVPair{Key: remotev6IPAMBlockKey, Value: &remotev6IPAMBlock}, - KVPair{Key: remoteNodeResKey, Value: &apiv3.Node{ + KVPair{Key: remoteNodeResKey, Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: remoteHostname, }, - Spec: apiv3.NodeSpec{BGP: &apiv3.NodeBGPSpec{ + Spec: internalapi.NodeSpec{BGP: &internalapi.NodeBGPSpec{ IPv6Address: remoteHostIPv6.String() + "/96", }}, }}, @@ -2199,11 +2290,11 @@ var vxlanV6BlockDelete = vxlanV6WithBlock.withKVUpdates( ) var vxlanV6NodeResIPDelete = vxlanV6WithBlock.withKVUpdates( - KVPair{Key: remoteNodeResKey, Value: &apiv3.Node{ + KVPair{Key: remoteNodeResKey, Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: remoteHostname, }, - Spec: apiv3.NodeSpec{BGP: &apiv3.NodeBGPSpec{}}, + Spec: internalapi.NodeSpec{BGP: &internalapi.NodeBGPSpec{}}, }}, ).withHostMetadataV4V6().withName("VXLAN IPv6 Node Resource IP removed").withRoutes( routeUpdateV6IPPoolVXLAN, @@ -2217,11 +2308,11 @@ var vxlanV6NodeResIPDelete = vxlanV6WithBlock.withKVUpdates( ).withVTEPs() var vxlanV6NodeResBGPDelete = vxlanV6WithBlock.withKVUpdates( - KVPair{Key: remoteNodeResKey, Value: &apiv3.Node{ + KVPair{Key: remoteNodeResKey, Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: remoteHostname, }, - Spec: apiv3.NodeSpec{BGP: nil}, + Spec: internalapi.NodeSpec{BGP: nil}, }}, ).withHostMetadataV4V6( &proto.HostMetadataV4V6Update{ @@ -2275,11 +2366,11 @@ var vxlanV4V6WithBlock = empty.withKVUpdates( KVPair{Key: ipPoolKey, Value: &ipPoolWithVXLAN}, KVPair{Key: remoteIPAMBlockKey, Value: &remoteIPAMBlock}, KVPair{Key: remoteHostVXLANTunnelConfigKey, Value: remoteHostVXLANTunnelIP}, - KVPair{Key: remoteNodeResKey, Value: &apiv3.Node{ + KVPair{Key: remoteNodeResKey, Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: remoteHostname, }, - Spec: apiv3.NodeSpec{BGP: &apiv3.NodeBGPSpec{ + Spec: internalapi.NodeSpec{BGP: &internalapi.NodeBGPSpec{ IPv4Address: remoteHostIP.String() + "/24", IPv6Address: remoteHostIPv6.String() + "/96", }}, @@ -2345,11 +2436,11 @@ var vxlanV4V6BlockV4Delete = vxlanV4V6WithBlock.withKVUpdates( ) var vxlanV4V6NodeResIPv4Delete = vxlanV4V6WithBlock.withKVUpdates( - KVPair{Key: remoteNodeResKey, Value: &apiv3.Node{ + KVPair{Key: remoteNodeResKey, Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: remoteHostname, }, - Spec: apiv3.NodeSpec{BGP: &apiv3.NodeBGPSpec{ + Spec: internalapi.NodeSpec{BGP: &internalapi.NodeBGPSpec{ IPv6Address: remoteHostIPv6.String() + "/96", }}, }}, @@ -2381,11 +2472,11 @@ var vxlanV4V6NodeResIPv4Delete = vxlanV4V6WithBlock.withKVUpdates( ) var vxlanV4V6NodeResIPv6Delete = vxlanV4V6WithBlock.withKVUpdates( - KVPair{Key: remoteNodeResKey, Value: &apiv3.Node{ + KVPair{Key: remoteNodeResKey, Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: remoteHostname, }, - Spec: apiv3.NodeSpec{BGP: &apiv3.NodeBGPSpec{ + Spec: internalapi.NodeSpec{BGP: &internalapi.NodeBGPSpec{ IPv4Address: remoteHostIP.String() + "/24", }}, }}, @@ -2415,11 +2506,11 @@ var vxlanV4V6NodeResIPv6Delete = vxlanV4V6WithBlock.withKVUpdates( ) var vxlanV4V6NodeResBGPDelete = vxlanV4V6WithBlock.withKVUpdates( - KVPair{Key: remoteNodeResKey, Value: &apiv3.Node{ + KVPair{Key: remoteNodeResKey, Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: remoteHostname, }, - Spec: apiv3.NodeSpec{BGP: nil}, + Spec: internalapi.NodeSpec{BGP: nil}, }}, ).withHostMetadataV4V6( &proto.HostMetadataV4V6Update{ @@ -2573,15 +2664,15 @@ var hostInIPPool = vxlanWithBlock.withKVUpdates( // we start from vxlan setup as the test framework expects vxlan enabled var nodesWithMoreIPs = vxlanWithBlock.withKVUpdates( - KVPair{Key: remoteNodeResKey, Value: &apiv3.Node{ + KVPair{Key: remoteNodeResKey, Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: remoteHostname, }, - Spec: apiv3.NodeSpec{ - BGP: &apiv3.NodeBGPSpec{ + Spec: internalapi.NodeSpec{ + BGP: &internalapi.NodeBGPSpec{ IPv4Address: remoteHostIPWithPrefix, }, - Addresses: []apiv3.NodeAddress{ + Addresses: []internalapi.NodeAddress{ { Address: remoteHostIPWithPrefix, }, @@ -2591,15 +2682,15 @@ var nodesWithMoreIPs = vxlanWithBlock.withKVUpdates( }, }, }}, - KVPair{Key: localNodeResKey, Value: &apiv3.Node{ + KVPair{Key: localNodeResKey, Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: localHostname, }, - Spec: apiv3.NodeSpec{ - BGP: &apiv3.NodeBGPSpec{ + Spec: internalapi.NodeSpec{ + BGP: &internalapi.NodeBGPSpec{ IPv4Address: localHostIPWithPrefix, }, - Addresses: []apiv3.NodeAddress{ + Addresses: []internalapi.NodeAddress{ { Address: localHostIPWithPrefix, }, @@ -2644,15 +2735,15 @@ var nodesWithMoreIPsRoutes = append(vxlanWithBlockRoutes[0:len(vxlanWithBlockRou var nodesWithMoreIPsAndDuplicates = nodesWithMoreIPs.withKVUpdates( KVPair{ - Key: remoteNodeResKey, Value: &apiv3.Node{ + Key: remoteNodeResKey, Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: remoteHostname, }, - Spec: apiv3.NodeSpec{ - BGP: &apiv3.NodeBGPSpec{ + Spec: internalapi.NodeSpec{ + BGP: &internalapi.NodeBGPSpec{ IPv4Address: remoteHostIPWithPrefix, }, - Addresses: []apiv3.NodeAddress{ + Addresses: []internalapi.NodeAddress{ { Address: "1.2.3.4", }, @@ -2684,15 +2775,15 @@ var nodesWithMoreIPsAndDuplicates = nodesWithMoreIPs.withKVUpdates( ).withName("routes for nodes with more IPs and duplicates") var nodesWithDifferentAddressTypes = nodesWithMoreIPs.withKVUpdates( - KVPair{Key: localNodeResKey, Value: &apiv3.Node{ + KVPair{Key: localNodeResKey, Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: localHostname, }, - Spec: apiv3.NodeSpec{ - BGP: &apiv3.NodeBGPSpec{ + Spec: internalapi.NodeSpec{ + BGP: &internalapi.NodeBGPSpec{ IPv4Address: localHostIPWithPrefix, }, - Addresses: []apiv3.NodeAddress{ + Addresses: []internalapi.NodeAddress{ { Address: localHostIPWithPrefix, }, @@ -2736,30 +2827,30 @@ var nodesWithMoreIPsRoutesDeletedExtras = append(vxlanWithBlockRoutes[0:len(vxla ) var nodesWithMoreIPsDeleted = vxlanWithBlock.withKVUpdates( - KVPair{Key: remoteNodeResKey, Value: &apiv3.Node{ + KVPair{Key: remoteNodeResKey, Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: remoteHostname, }, - Spec: apiv3.NodeSpec{ - BGP: &apiv3.NodeBGPSpec{ + Spec: internalapi.NodeSpec{ + BGP: &internalapi.NodeBGPSpec{ IPv4Address: remoteHostIPWithPrefix, }, - Addresses: []apiv3.NodeAddress{ + Addresses: []internalapi.NodeAddress{ { Address: remoteHostIPWithPrefix, }, }, }, }}, - KVPair{Key: localNodeResKey, Value: &apiv3.Node{ + KVPair{Key: localNodeResKey, Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: localHostname, }, - Spec: apiv3.NodeSpec{ - BGP: &apiv3.NodeBGPSpec{ + Spec: internalapi.NodeSpec{ + BGP: &internalapi.NodeBGPSpec{ IPv4Address: localHostIPWithPrefix, }, - Addresses: []apiv3.NodeAddress{ + Addresses: []internalapi.NodeAddress{ { Address: localHostIPWithPrefix, }, @@ -2803,11 +2894,14 @@ var endpointSliceActive = endpointSliceAndLocalWorkload.withKVUpdates( ).withName("EndpointSliceActive").withIPSet("svc:Jhwii46PCMT5NlhWsUqZmv7al8TeHFbNQMhoVg", []string{ "10.0.0.1,tcp:80", }).withActivePolicies( - types.PolicyID{Tier: "default", Name: "svc-policy"}, + types.PolicyID{Name: "svc-policy", Kind: v3.KindGlobalNetworkPolicy}, ).withEndpoint( localWlEp1Id, []mock.TierInfo{ - {Name: "default", EgressPolicyNames: []string{"svc-policy"}}, + { + Name: "default", + EgressPolicies: []types.PolicyID{{Name: "svc-policy", Kind: v3.KindGlobalNetworkPolicy}}, + }, }, ) @@ -2853,11 +2947,14 @@ var endpointSliceActiveSpecNoPorts = endpointSliceAndLocalWorkload.withKVUpdates ).withName("EndpointSliceActiveNoPorts").withIPSet("svcnoport:T03S_6hogdrGKrNFBcbKTFsH_uKwDHEo8JddOg", []string{ "10.0.0.1/32", }).withActivePolicies( - types.PolicyID{Tier: "default", Name: "svc-policy"}, + types.PolicyID{Name: "svc-policy", Kind: v3.KindGlobalNetworkPolicy}, ).withEndpoint( localWlEp1Id, []mock.TierInfo{ - {Name: "default", IngressPolicyNames: []string{"svc-policy"}}, + { + Name: "default", + IngressPolicies: []types.PolicyID{{Name: "svc-policy", Kind: v3.KindGlobalNetworkPolicy}}, + }, }, ) @@ -2869,15 +2966,15 @@ var endpointSliceActiveSpecPortsAndNoPorts = endpointSliceActiveSpecNoPorts.with ).withIPSet("svc:Jhwii46PCMT5NlhWsUqZmv7al8TeHFbNQMhoVg", []string{ "10.0.0.1,tcp:80", }).withActivePolicies( - types.PolicyID{Tier: "default", Name: "svc-policy"}, - types.PolicyID{Tier: "default", Name: "svc-policy2"}, + types.PolicyID{Name: "svc-policy", Kind: v3.KindGlobalNetworkPolicy}, + types.PolicyID{Name: "svc-policy2", Kind: v3.KindGlobalNetworkPolicy}, ).withEndpoint( localWlEp1Id, []mock.TierInfo{ { - Name: "default", - IngressPolicyNames: []string{"svc-policy"}, - EgressPolicyNames: []string{"svc-policy2"}, + Name: "default", + IngressPolicies: []types.PolicyID{{Name: "svc-policy", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: []types.PolicyID{{Name: "svc-policy2", Kind: v3.KindGlobalNetworkPolicy}}, }, }, ) @@ -2917,19 +3014,19 @@ var wireguardV4 = empty.withKVUpdates( PublicKey: wgPublicKey1.String(), }, }, - KVPair{Key: remoteNodeResKey, Value: &apiv3.Node{ + KVPair{Key: remoteNodeResKey, Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: localHostname, }, - Spec: apiv3.NodeSpec{ - BGP: &apiv3.NodeBGPSpec{ + Spec: internalapi.NodeSpec{ + BGP: &internalapi.NodeBGPSpec{ IPv4Address: remoteHostIP.String() + "/24", }, - Wireguard: &apiv3.NodeWireguardSpec{ + Wireguard: &internalapi.NodeWireguardSpec{ InterfaceIPv4Address: remoteHost2IP.String(), }, }, - Status: apiv3.NodeStatus{ + Status: internalapi.NodeStatus{ WireguardPublicKey: wgPublicKey1.String(), }, }}, @@ -2969,19 +3066,19 @@ var wireguardV6 = empty.withKVUpdates( PublicKeyV6: wgPublicKey2.String(), }, }, - KVPair{Key: remoteNodeResKey, Value: &apiv3.Node{ + KVPair{Key: remoteNodeResKey, Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: localHostname, }, - Spec: apiv3.NodeSpec{ - BGP: &apiv3.NodeBGPSpec{ + Spec: internalapi.NodeSpec{ + BGP: &internalapi.NodeBGPSpec{ IPv6Address: remoteHostIPv6.String() + "/96", }, - Wireguard: &apiv3.NodeWireguardSpec{ + Wireguard: &internalapi.NodeWireguardSpec{ InterfaceIPv6Address: remoteHost2IPv6.String(), }, }, - Status: apiv3.NodeStatus{ + Status: internalapi.NodeStatus{ WireguardPublicKeyV6: wgPublicKey2.String(), }, }}, @@ -3024,21 +3121,21 @@ var wireguardV4V6 = empty.withKVUpdates( PublicKeyV6: wgPublicKey2.String(), }, }, - KVPair{Key: remoteNodeResKey, Value: &apiv3.Node{ + KVPair{Key: remoteNodeResKey, Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: localHostname, }, - Spec: apiv3.NodeSpec{ - BGP: &apiv3.NodeBGPSpec{ + Spec: internalapi.NodeSpec{ + BGP: &internalapi.NodeBGPSpec{ IPv4Address: remoteHostIP.String() + "/24", IPv6Address: remoteHostIPv6.String() + "/96", }, - Wireguard: &apiv3.NodeWireguardSpec{ + Wireguard: &internalapi.NodeWireguardSpec{ InterfaceIPv4Address: remoteHost2IP.String(), InterfaceIPv6Address: remoteHost2IPv6.String(), }, }, - Status: apiv3.NodeStatus{ + Status: internalapi.NodeStatus{ WireguardPublicKey: wgPublicKey1.String(), WireguardPublicKeyV6: wgPublicKey2.String(), }, @@ -3098,12 +3195,12 @@ func (l StateList) String() string { return "[" + strings.Join(names, ", ") + "]" } -// UsesNodeResources returns true if any of the KVs in this state are apiv3.Node resources. +// UsesNodeResources returns true if any of the KVs in this state are internalapi.Node resources. // Some calculation graph nodes support either the v3 Node or the old model.HostIP object. func (l StateList) UsesNodeResources() bool { for _, s := range l { for _, kv := range s.DatastoreState { - if resourceKey, ok := kv.Key.(ResourceKey); ok && resourceKey.Kind == apiv3.KindNode { + if resourceKey, ok := kv.Key.(ResourceKey); ok && resourceKey.Kind == internalapi.KindNode { return true } } @@ -3138,7 +3235,7 @@ func reverseStateOrder(baseTest StateList) (desc string, mappedTests []StateList desc = "with order of states reversed" palindrome := true mappedTest := StateList{} - for ii := 0; ii < len(baseTest); ii++ { + for ii := range baseTest { mappedTest = append(mappedTest, baseTest[len(baseTest)-ii-1]) if &baseTest[len(baseTest)-1-ii] != &baseTest[ii] { palindrome = false diff --git a/felix/calc/stats_collector_test.go b/felix/calc/stats_collector_test.go index f5bd4204186..079d03c3c5a 100644 --- a/felix/calc/stats_collector_test.go +++ b/felix/calc/stats_collector_test.go @@ -17,7 +17,7 @@ package calc_test import ( "errors" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/projectcalico/calico/felix/calc" diff --git a/felix/calc/validation_filter.go b/felix/calc/validation_filter.go index 5376fcdacf2..0f77795bf28 100644 --- a/felix/calc/validation_filter.go +++ b/felix/calc/validation_filter.go @@ -70,7 +70,7 @@ func (v *ValidationFilter) OnUpdates(updates []api.Update) { } if update.Value != nil { val := reflect.ValueOf(update.Value) - if val.Kind() == reflect.Ptr { + if val.Kind() == reflect.Pointer { elem := val.Elem() if elem.Kind() == reflect.Struct { if err := validatorFunc(elem.Interface()); err != nil { diff --git a/felix/calc/validation_filter_test.go b/felix/calc/validation_filter_test.go index 4abe4c53d17..4e52f68023b 100644 --- a/felix/calc/validation_filter_test.go +++ b/felix/calc/validation_filter_test.go @@ -17,7 +17,7 @@ package calc_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/calc" diff --git a/felix/calc/vxlan_resolver.go b/felix/calc/vxlan_resolver.go index c6b4588b701..fc5e0328b6a 100644 --- a/felix/calc/vxlan_resolver.go +++ b/felix/calc/vxlan_resolver.go @@ -22,7 +22,7 @@ import ( "github.com/projectcalico/calico/felix/dispatcher" "github.com/projectcalico/calico/felix/proto" - apiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" cnet "github.com/projectcalico/calico/libcalico-go/lib/net" @@ -53,7 +53,7 @@ type VXLANResolver struct { // Store node metadata indexed by node name, and routes by the // block that contributed them. The following comprises the full internal data model. - nodeNameToNode map[string]*apiv3.Node + nodeNameToNode map[string]*internalapi.Node nodeNameToVXLANTunnelAddr map[string]string nodeNameToIPv4Addr map[string]string nodeNameToVXLANMac map[string]string @@ -69,7 +69,7 @@ func NewVXLANResolver(hostname string, callbacks vxlanCallbacks, useNodeResource return &VXLANResolver{ hostname: hostname, callbacks: callbacks, - nodeNameToNode: map[string]*apiv3.Node{}, + nodeNameToNode: map[string]*internalapi.Node{}, nodeNameToVXLANTunnelAddr: map[string]string{}, nodeNameToIPv4Addr: map[string]string{}, nodeNameToVXLANMac: map[string]string{}, @@ -94,15 +94,15 @@ func (c *VXLANResolver) RegisterWith(allUpdDispatcher *dispatcher.Dispatcher) { func (c *VXLANResolver) OnResourceUpdate(update api.Update) (_ bool) { resourceKey := update.Key.(model.ResourceKey) - if resourceKey.Kind != apiv3.KindNode { + if resourceKey.Kind != internalapi.KindNode { return } nodeName := update.Key.(model.ResourceKey).Name logCtx := logrus.WithField("node", nodeName).WithField("update", update) logCtx.Debug("OnResourceUpdate triggered") - if update.Value != nil && update.Value.(*apiv3.Node).Spec.BGP != nil { - node := update.Value.(*apiv3.Node) + if update.Value != nil && update.Value.(*internalapi.Node).Spec.BGP != nil { + node := update.Value.(*internalapi.Node) bgp := node.Spec.BGP c.nodeNameToNode[nodeName] = node ipv4, _, err := cnet.ParseCIDROrIP(bgp.IPv4Address) diff --git a/felix/cmd/calico-bpf/commands/ipsets.go b/felix/cmd/calico-bpf/commands/ipsets.go index 5b040178243..5f500572b5c 100644 --- a/felix/cmd/calico-bpf/commands/ipsets.go +++ b/felix/cmd/calico-bpf/commands/ipsets.go @@ -16,6 +16,7 @@ package commands import ( "fmt" + "slices" "sort" "github.com/pkg/errors" @@ -81,9 +82,7 @@ func dumpIPSets() error { setIDs = append(setIDs, k) sort.Strings(v) } - sort.Slice(setIDs, func(i, j int) bool { - return setIDs[i] < setIDs[j] - }) + slices.Sort(setIDs) for _, setID := range setIDs { fmt.Printf("IP set %#x\n", setID) for _, member := range membersBySet[setID] { diff --git a/felix/cmd/calico-bpf/commands/nat.go b/felix/cmd/calico-bpf/commands/nat.go index 4bc0f9a434b..f4e04d1320d 100644 --- a/felix/cmd/calico-bpf/commands/nat.go +++ b/felix/cmd/calico-bpf/commands/nat.go @@ -124,7 +124,7 @@ func dump(cmd *cobra.Command) error { return nil } -type printfFn func(format string, i ...interface{}) +type printfFn func(format string, i ...any) func dumpNice[FK nat.FrontendKeyComparable, BV nat.BackendValueInterface](printf printfFn, natMap map[FK]nat.FrontendValue, back map[nat.BackendKey]BV) { @@ -154,12 +154,8 @@ func dumpNice[FK nat.FrontendKeyComparable, BV nat.BackendValueInterface](printf if !ok { printf("is missing\n") } else { - fmtStr := "%s:%d\n" - // Use "[]" with IPv6 addresses - if bv.Addr().To4() == nil { - fmtStr = "[%s]:%d\n" - } - printf(fmtStr, bv.Addr(), bv.Port()) + ep := net.JoinHostPort(bv.Addr().String(), fmt.Sprint(bv.Port())) + printf("%s\n", ep) } } } diff --git a/felix/cmd/calico-bpf/commands/nat_test.go b/felix/cmd/calico-bpf/commands/nat_test.go index 88b0279e080..c1e4907b1b8 100644 --- a/felix/cmd/calico-bpf/commands/nat_test.go +++ b/felix/cmd/calico-bpf/commands/nat_test.go @@ -35,5 +35,5 @@ func TestNATDump(t *testing.T) { nat2.NewNATBackendKey(108, 0): nat2.NewNATBackendValue(net.IPv4(3, 3, 3, 3), 553), } - dumpNice(func(format string, i ...interface{}) { fmt.Printf(format, i...) }, nat, back) + dumpNice(func(format string, i ...any) { fmt.Printf(format, i...) }, nat, back) } diff --git a/felix/cmd/calico-bpf/commands/profiling.go b/felix/cmd/calico-bpf/commands/profiling.go index 2e6d99c36aa..8afd4eabcde 100644 --- a/felix/cmd/calico-bpf/commands/profiling.go +++ b/felix/cmd/calico-bpf/commands/profiling.go @@ -98,7 +98,7 @@ func e2eLatency(cmd *cobra.Command, args []string) { r := []string{i.Name} hit := false - for kind := 0; kind < 4; kind++ { + for kind := range 4 { k.Kind = kind v, ok := data[k] if !ok { diff --git a/felix/cmd/calico-bpf/commands/routes.go b/felix/cmd/calico-bpf/commands/routes.go index e334d20e49b..02da91347eb 100644 --- a/felix/cmd/calico-bpf/commands/routes.go +++ b/felix/cmd/calico-bpf/commands/routes.go @@ -16,7 +16,10 @@ package commands import ( "fmt" + "os" "sort" + "strconv" + "strings" "github.com/pkg/errors" log "github.com/sirupsen/logrus" @@ -28,6 +31,9 @@ import ( ) func init() { + // Register parent "routes" command and its subcommands. + routesCmd.AddCommand(newAddCmd()) + routesCmd.AddCommand(newDelCmd()) routesCmd.AddCommand(routesDumpCmd) rootCmd.AddCommand(routesCmd) } @@ -36,13 +42,14 @@ var routesDumpCmd = &cobra.Command{ Use: "dump", Short: "dumps routes", Run: func(cmd *cobra.Command, args []string) { + // The existing code used the global 'ipv6' flag; preserve that behavior. if err := dumpRoutes(); err != nil { log.WithError(err).Error("Failed to dump routes map.") } }, } -// routesCmd represents the routes command +// routesCmd represents the routes parent command var routesCmd = &cobra.Command{ Use: "routes", Short: "Manipulates routes", @@ -60,6 +67,7 @@ func dumpRoutes() error { if err := routesMap.Open(); err != nil { return errors.WithMessage(err, "failed to open map") } + defer routesMap.Close() var dests []ip.CIDR valueByDest := map[ip.CIDR]routes.ValueInterface{} @@ -113,7 +121,7 @@ func sortCIDRs(cidrs []ip.CIDR) { sort.Slice(cidrs, func(i, j int) bool { addrA := cidrs[i].Addr().(ip.V4Addr) addrB := cidrs[j].Addr().(ip.V4Addr) - for byteIdx := 0; byteIdx < 4; byteIdx++ { + for byteIdx := range 4 { if addrA[byteIdx] < addrB[byteIdx] { return true } @@ -129,7 +137,7 @@ func sortCIDRsV6(cidrs []ip.CIDR) { sort.Slice(cidrs, func(i, j int) bool { addrA := cidrs[i].Addr().(ip.V6Addr) addrB := cidrs[j].Addr().(ip.V6Addr) - for byteIdx := 0; byteIdx < 16; byteIdx++ { + for byteIdx := range 16 { if addrA[byteIdx] < addrB[byteIdx] { return true } @@ -140,3 +148,255 @@ func sortCIDRsV6(cidrs []ip.CIDR) { return cidrs[i].Prefix() < cidrs[j].Prefix() }) } + +// routeCmd holds the parsed CLI args for add/del subcommands. +type routeCmd struct { + IP string + NextHop string + IfIndex string + + // Typed flags: + WorkloadType string // "local" or "remote" + HostType string // "local" or "remote" + Tunneled bool + + // Parsed values + cidr ip.CIDR + nexth ip.Addr + ifIdx int + isV6 bool + flags routes.Flags + mapObj maps.Map +} + +func newAddCmd() *cobra.Command { + rc := &routeCmd{} + cmd := &cobra.Command{ + Use: "add [--nexthop ] [--ifindex ] [--workload |--host ] [--tunneled]", + Short: "Add an entry to the BPF routes map", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + rc.IP = args[0] + return rc.run("add") + }, + } + + // Flags for add + cmd.Flags().StringVar(&rc.NextHop, "nexthop", "", "Next hop IP for route (remote routes)") + cmd.Flags().StringVar(&rc.IfIndex, "ifindex", "", "Interface index for local routes") + cmd.Flags().StringVar(&rc.WorkloadType, "workload", "", "Mark route as workload: specify 'local' or 'remote'") + cmd.Flags().StringVar(&rc.HostType, "host", "", "Mark route as host: specify 'local' or 'remote'") + cmd.Flags().BoolVar(&rc.Tunneled, "tunneled", false, "Mark route as tunneled") + + return cmd +} + +func newDelCmd() *cobra.Command { + rc := &routeCmd{} + cmd := &cobra.Command{ + Use: "del ", + Short: "Delete an entry from the BPF routes map (by CIDR only)", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + rc.IP = args[0] + // For delete we only accept IP/CIDR and ignore any additional flags. + return rc.run("del") + }, + } + + // Intentionally do NOT add flags for del: delete accepts only the IP/CIDR. + return cmd +} + +func (r *routeCmd) openMapForFamily(isV6 bool) error { + if isV6 { + r.mapObj = routes.MapV6() + } else { + r.mapObj = routes.Map() + } + if r.mapObj == nil { + return fmt.Errorf("failed to create routes map object") + } + if err := r.mapObj.Open(); err != nil { + return fmt.Errorf("failed to open routes map: %w", err) + } + return nil +} + +// parseInputs validates and parses inputs. Behavior varies by op: +// - op == "add": require at least one of --workload or --host and validate flags. +// - op == "del": only parse IP/CIDR and family; ignore flags. +func (r *routeCmd) parseInputs(op string) error { + // Parse CIDR/IP and set address family. + cidr, err := ip.ParseCIDROrIP(r.IP) + if err != nil { + return fmt.Errorf("invalid ip/cidr %q: %w", r.IP, err) + } + r.cidr = cidr + r.isV6 = (cidr.Version() == 6) + + // For delete, we only need the CIDR/family. Ignore flags. + if op == "del" { + return nil + } + + // Next hop parse if provided. + if r.NextHop != "" { + nh := ip.FromString(r.NextHop) + if nh == nil { + return fmt.Errorf("invalid nexthop IP %q", r.NextHop) + } + r.nexth = nh + } + + // IfIndex parse if provided. + if r.IfIndex != "" { + i, err := strconv.Atoi(r.IfIndex) + if err != nil { + return fmt.Errorf("invalid ifindex: %w", err) + } + r.ifIdx = i + } + + normalize := func(s string) string { return strings.ToLower(strings.TrimSpace(s)) } + + if r.WorkloadType != "" { + rt := normalize(r.WorkloadType) + if rt != "local" && rt != "remote" { + return fmt.Errorf("invalid --workload value %q; must be 'local' or 'remote'", r.WorkloadType) + } + r.WorkloadType = rt + } + if r.HostType != "" { + rt := normalize(r.HostType) + if rt != "local" && rt != "remote" { + return fmt.Errorf("invalid --host value %q; must be 'local' or 'remote'", r.HostType) + } + r.HostType = rt + } + + // For add, require at least one type specified. + if op == "add" { + if r.WorkloadType == "" && r.HostType == "" { + return errors.New("for add: one of --workload or --host must be specified (use 'local' or 'remote')") + } + } + + // If either type is 'local', require ifindex. + if r.WorkloadType == "local" || r.HostType == "local" { + if r.ifIdx == 0 { + return errors.New("local routes require --ifindex to be specified and non-zero") + } + } + // If either type is 'remote', require nexthop. + if r.WorkloadType == "remote" || r.HostType == "remote" { + if r.nexth == nil { + return errors.New("remote routes require --nexthop to be specified") + } + } + + // Build routes.Flags + var f routes.Flags + if r.WorkloadType != "" { + f |= routes.FlagWorkload + if r.WorkloadType == "local" { + f |= routes.FlagLocal + } + } + if r.HostType != "" { + f |= routes.FlagHost + if r.HostType == "local" { + f |= routes.FlagLocal + } + } + if r.Tunneled { + f |= routes.FlagTunneled + } + r.flags = f + + return nil +} + +func (r *routeCmd) makeKeyAndValue() (routes.KeyInterface, routes.ValueInterface, error) { + // Select constructors for IPv4/IPv6 + var key routes.KeyInterface + var val routes.ValueInterface + + if r.isV6 { + key = routes.NewKeyV6Intf(r.cidr) + // value constructors for v6 + if r.ifIdx != 0 { + val = routes.NewValueV6IntfWithIfIndex(r.flags, r.ifIdx) + return key, val, nil + } + if r.nexth != nil { + val = routes.NewValueV6IntfWithNextHop(r.flags, r.nexth) + return key, val, nil + } + val = routes.NewValueV6Intf(r.flags) + return key, val, nil + } + + // IPv4 path + key = routes.NewKeyIntf(r.cidr) + if r.ifIdx != 0 { + val = routes.NewValueIntfWithIfIndex(r.flags, r.ifIdx) + return key, val, nil + } + if r.nexth != nil { + val = routes.NewValueIntfWithNextHop(r.flags, r.nexth) + return key, val, nil + } + val = routes.NewValueIntf(r.flags) + return key, val, nil +} + +func (r *routeCmd) makeKey() routes.KeyInterface { + if r.isV6 { + return routes.NewKeyV6Intf(r.cidr) + } + return routes.NewKeyIntf(r.cidr) +} + +func (r *routeCmd) run(op string) error { + // Parse and validate inputs (for del we only parse IP/CIDR). + if err := r.parseInputs(op); err != nil { + return err + } + // Open appropriate map + if err := r.openMapForFamily(r.isV6); err != nil { + return err + } + defer func() { + _ = r.mapObj.Close() + }() + + // Build key (always) and value (for add) + key := r.makeKey() + + switch op { + case "add": + // For add, build value and update. + _, val, err := r.makeKeyAndValue() + if err != nil { + return err + } + if val == nil { + return errors.New("failed to construct route value") + } + if err := r.mapObj.Update(key.AsBytes(), val.AsBytes()); err != nil { + return fmt.Errorf("failed to update routes map: %w", err) + } + fmt.Fprintf(os.Stdout, "added route %s\n", r.cidr.String()) + return nil + case "del", "delete", "remove": + // For delete, only the key (CIDR) is used. + if err := r.mapObj.Delete(key.AsBytes()); err != nil { + return fmt.Errorf("failed to delete route from map: %w", err) + } + fmt.Fprintf(os.Stdout, "deleted route %s\n", r.cidr.String()) + return nil + default: + return fmt.Errorf("unknown op %q (use add|del|dump)", op) + } +} diff --git a/felix/collector/collector.go b/felix/collector/collector.go index 5d066a313da..71bf1bbef2e 100644 --- a/felix/collector/collector.go +++ b/felix/collector/collector.go @@ -107,6 +107,13 @@ type Config struct { DisplayDebugTraceLogs bool BPFConntrackTimeouts bpfconntrack.Timeouts + + PolicyStoreManager policystore.PolicyStoreManager +} + +// namespacedEpKey is an interface for keys that have namespace information. +type namespacedEpKey interface { + GetNamespace() string } // A collector (a StatsManager really) collects StatUpdates from data sources @@ -141,8 +148,12 @@ func newCollector(lc *calc.LookupsCache, cfg *Config) Collector { config: cfg, dumpLog: log.New(), ds: make(chan *proto.DataplaneStats, 1000), - policyStoreManager: policystore.NewPolicyStoreManager(), displayDebugTraceLogs: cfg.DisplayDebugTraceLogs, + policyStoreManager: cfg.PolicyStoreManager, + } + + if c.policyStoreManager == nil { + c.policyStoreManager = policystore.NewPolicyStoreManager() } if apiv3.FlowLogsPolicyEvaluationModeType(cfg.PolicyEvaluationMode) == apiv3.FlowLogsPolicyEvaluationModeContinuous { @@ -273,7 +284,7 @@ func (c *collector) startStatsCollectionAndReporting() { func (c *collector) loopProcessingDataplaneInfoUpdates(dpInfoC <-chan *proto.ToDataplane) { for dpInfo := range dpInfoC { c.policyStoreManager.DoWithLock(func(ps *policystore.PolicyStore) { - log.Debugf("Dataplane payload: %v and sequenceNumber: %d, ", dpInfo.Payload, dpInfo.SequenceNumber) + log.Debugf("Dataplane payload: %+v and sequenceNumber: %d, ", dpInfo.Payload, dpInfo.SequenceNumber) // Get the data and update the endpoints. ps.ProcessUpdate(perHostPolicySubscription, dpInfo, true) }) @@ -299,13 +310,9 @@ func (c *collector) getDataAndUpdateEndpoints(t tuple.Tuple, expired bool, packe return data } - // Get the source endpoint. Set the clientIP to an empty value, as it is not used for to get - // the source endpoint. - srcEp := c.lookupEndpoint([16]byte{}, t.Src) + // Get the source and destination endpoints + srcEp, dstEp := c.findEndpointBestMatch(t) srcEpIsNotLocal := srcEp == nil || !srcEp.IsLocal() - - // Get the destination endpoint. If the source is local then we can use egress domain lookups if required. - dstEp := c.lookupEndpoint(t.Src, t.Dst) dstEpIsNotLocal := dstEp == nil || !dstEp.IsLocal() if !exists { @@ -315,14 +322,17 @@ func (c *collector) getDataAndUpdateEndpoints(t tuple.Tuple, expired bool, packe } // Ignore HEP reporters. - if (srcEp != nil && srcEp.IsLocal() && srcEp.IsHostEndpoint()) || - (dstEp != nil && dstEp.IsLocal() && dstEp.IsHostEndpoint()) { + if (!srcEpIsNotLocal && srcEp.IsHostEndpoint()) || + (!dstEpIsNotLocal && dstEp.IsHostEndpoint()) { return nil } // The entry does not exist. Go ahead and create a new one and add it to the map. data = NewData(t, srcEp, dstEp) c.updateEpStatsCache(t, data) + + // Perform an initial evaluation of pending rule traces. + c.evaluatePendingRuleTraceForLocalEp(data) } else if data.Reported { if !data.UnreportedPacketInfo && !packetinfo { // Data has been reported. If the request has not come from a packet info update (e.g. nflog) and we do not @@ -380,22 +390,61 @@ func endpointChanged(ep1, ep2 calc.EndpointData) bool { return ep1.Key() != ep2.Key() } -func (c *collector) lookupEndpoint(clientIPBytes, ip [16]byte) calc.EndpointData { - // Get the endpoint data for this entry. - if ep, ok := c.luc.GetEndpoint(ip); ok { - return ep +// lookupNetworkSetWithNamespace looks up NetworkSets for the given IP address. +// If canCheckEgressDomains is true, then egress domain lookups will be performed if no +// NetworkSet is found for the IP. If preferredNamespace is set, then it will be used for +// namespace-aware NetworkSet resolution. +func (c *collector) lookupNetworkSetWithNamespace(clientIPBytes, ip [16]byte, canCheckEgressDomains bool, preferredNamespace string) calc.EndpointData { + if !c.config.EnableNetworkSets { + return nil } - // No matching endpoint. If NetworkSets are enabled for flows then check if the IP matches a NetworkSet and - // return that. - if c.config.EnableNetworkSets { - if ep, ok := c.luc.GetNetworkSet(ip); ok { - return ep - } + // Check if the IP matches a NetworkSet + if ep, ok := c.luc.GetNetworkSetWithNamespace(ip, preferredNamespace); ok { + return ep } + return nil } +// findEndpointBestMatch performs endpoint lookups for both source and destination. It first tries +// direct endpoint lookups, and only falls back to NetworkSet lookups if needed, using namespace +// context from the other endpoint when available. +func (c *collector) findEndpointBestMatch(t tuple.Tuple) (srcEp, dstEp calc.EndpointData) { + var ep calc.EndpointData + var srcEpFound, dstEpFound bool + if ep, srcEpFound = c.luc.GetEndpoint(t.Src); srcEpFound { + // Only set srcEp if lookup succeeded (to avoid storing a typed nil). + srcEp = ep + } + + if ep, dstEpFound = c.luc.GetEndpoint(t.Dst); dstEpFound { + // Only set dstEp if lookup succeeded (to avoid storing a typed nil). + dstEp = ep + } + + if (srcEpFound && dstEpFound) || !c.config.EnableNetworkSets { + // If both endpoints were found directly, or if NetworkSets are not enabled, return what we + // found from direct lookups + return srcEp, dstEp + } + + if !srcEpFound { + dstNamespace := getNamespaceFromEp(dstEp) + srcEp = c.lookupNetworkSetWithNamespace([16]byte{}, t.Src, false, dstNamespace) + } + + if !dstEpFound { + srcNamespace := getNamespaceFromEp(srcEp) + // This controls whether egress-domain lookups should be performed when resolving the + // destination NetworkSet. Only local source endpoints can trigger egress-domain resolution. + srcEpLocal := srcEp != nil && srcEp.IsLocal() + dstEp = c.lookupNetworkSetWithNamespace(t.Src, t.Dst, srcEpLocal, srcNamespace) + } + + return srcEp, dstEp +} + // updateEpStatsCache updates/add entry to the epStats cache (map[Tuple]*Data) and update the // prometheus reporting func (c *collector) updateEpStatsCache(t tuple.Tuple, data *Data) { @@ -448,9 +497,6 @@ func (c *collector) applyNflogStatUpdate(data *Data, ruleID *calc.RuleID, matchI c.handleDataEndpointOrRulesChanged(data) data.ReplaceRuleID(ruleID, matchIdx, numPkts, numBytes) } - if ru == RuleMatchSet || ru == RuleMatchIsDifferent { - c.evaluatePendingRuleTraceForLocalEp(data) - } } func (c *collector) handleDataEndpointOrRulesChanged(data *Data) { @@ -581,6 +627,16 @@ func (c *collector) expireMetrics(data *Data) { } } +// tryReportAndExpire tries to report expired data metrics and clean up the connection entry. +func (c *collector) tryReportAndExpire(data *Data) { + if data.Expired && c.reportMetrics(data, false) { + // If the data is expired then attempt to report it now so that we can remove the connection entry. If reported + // the data can be expired and deleted immediately, otherwise it will get exported during ticker processing. + c.expireMetrics(data) + c.deleteDataFromEpStats(data) + } +} + func (c *collector) deleteDataFromEpStats(data *Data) { delete(c.epStats, data.Tuple) @@ -694,6 +750,18 @@ func (c *collector) applyPacketInfo(pktInfo types.PacketInfo) { data.PreDNATPort = originalTuple.L4Dst } + // Skip processing if either endpoint is marked for deletion. + if data.SrcEp != nil && c.luc.IsEndpointDeleted(data.SrcEp) { + log.Debugf("Source endpoint: %s marked for deletion, skipping packet info update", data.SrcEp.GenerateName()) + c.tryReportAndExpire(data) + return + } + if data.DstEp != nil && c.luc.IsEndpointDeleted(data.DstEp) { + log.Debugf("Destination endpoint: %s marked for deletion, skipping packet info update", data.DstEp.GenerateName()) + c.tryReportAndExpire(data) + return + } + // Determine the local endpoint for this update. switch pktInfo.Direction { case rules.RuleDirIngress: @@ -777,12 +845,7 @@ func (c *collector) applyPacketInfo(pktInfo types.PacketInfo) { c.applyNflogStatUpdate(data, ruleID, policyIdx, rule.Hits, rule.Bytes) } - if data.Expired && c.reportMetrics(data, false) { - // If the data is expired then attempt to report it now so that we can remove the connection entry. If reported - // the data can be expired and deleted immediately, otherwise it will get exported during ticker processing. - c.expireMetrics(data) - c.deleteDataFromEpStats(data) - } + c.tryReportAndExpire(data) } // convertDataplaneStatsAndApplyUpdate merges the proto.DataplaneStatistics into the current @@ -816,22 +879,26 @@ func (c *collector) updatePendingRuleTraces() { } func (c *collector) evaluatePendingRuleTraceForLocalEp(data *Data) { - // Convert the tuple to a Dikastes flow, which is used by the rule trace evaluator. flow := TupleAsFlow(data.Tuple) - if data.DstEp != nil && !data.DstEp.IsHostEndpoint() && data.DstEp.IsLocal() { - // Evaluate the pending ingress rule trace for the flow. The policyStoreManager is read-locked. - c.policyStoreManager.DoWithReadLock(func(ps *policystore.PolicyStore) { - c.evaluatePendingRuleTrace(rules.RuleDirIngress, ps, data.DstEp, flow, &data.IngressPendingRuleIDs) - }) + srcEp, dstEp := c.findEndpointBestMatch(data.Tuple) + + // If endpoints have changed compared to what Data currently holds, skip evaluation. + if endpointChanged(data.SrcEp, srcEp) || endpointChanged(data.DstEp, dstEp) { + return } - if data.SrcEp != nil && !data.SrcEp.IsHostEndpoint() && data.SrcEp.IsLocal() { - // Evaluate the pending egress rule trace for the flow. The policyStoreManager is read-locked. - c.policyStoreManager.DoWithReadLock(func(ps *policystore.PolicyStore) { + c.policyStoreManager.DoWithReadLock(func(ps *policystore.PolicyStore) { + // Evaluate ingress if destination is local workload endpoint + if data.DstEp != nil && !data.DstEp.IsHostEndpoint() && data.DstEp.IsLocal() { + c.evaluatePendingRuleTrace(rules.RuleDirIngress, ps, data.DstEp, flow, &data.IngressPendingRuleIDs) + } + + // Evaluate egress if source is local workload endpoint + if data.SrcEp != nil && !data.SrcEp.IsHostEndpoint() && data.SrcEp.IsLocal() { c.evaluatePendingRuleTrace(rules.RuleDirEgress, ps, data.SrcEp, flow, &data.EgressPendingRuleIDs) - }) - } + } + }) } // evaluatePendingRuleTrace evaluates the pending rule trace for the given direction and endpoint, @@ -919,6 +986,21 @@ func (f *MessageOnlyFormatter) Format(entry *log.Entry) ([]byte, error) { return b.Bytes(), nil } +// getNamespaceFromEp extracts the namespace from the endpoint. Returns an empty string if +// the namespace cannot be determined. +func getNamespaceFromEp(ep calc.EndpointData) (namespace string) { + if ep == nil { + return + } + if key := ep.Key(); key != nil { + if nk, ok := key.(namespacedEpKey); ok { + namespace = nk.GetNamespace() + } + } + + return +} + // equal returns true if the rule IDs are equal. The order of the content should also the same for // equal to return true. func equal(a, b []*calc.RuleID) bool { diff --git a/felix/collector/collector_suite_test.go b/felix/collector/collector_suite_test.go index dd0ae960eb9..600709b50cf 100644 --- a/felix/collector/collector_suite_test.go +++ b/felix/collector/collector_suite_test.go @@ -17,16 +17,16 @@ package collector import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestCollector(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/collector_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Collector Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_collector_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/collector", suiteConfig, reporterConfig) } diff --git a/felix/collector/collector_test.go b/felix/collector/collector_test.go index d5829f25349..e9e200a8acf 100644 --- a/felix/collector/collector_test.go +++ b/felix/collector/collector_test.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows // Copyright (c) 2018-2025 Tigera, Inc. All rights reserved. // @@ -19,12 +18,14 @@ package collector import ( "fmt" - "strings" + net2 "net" + "slices" "testing" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" kapiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" @@ -147,13 +148,20 @@ var ( CommonEndpointData: calc.CalculateCommonEndpointData(localWlEPKey1, localWlEp1), Ingress: &calc.MatchData{ PolicyMatches: map[calc.PolicyID]int{ - {Name: "policy1", Tier: "default"}: 0, - {Name: "policy2", Tier: "default"}: 0, + {Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}: 0, + {Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}: 0, }, TierData: map[string]*calc.TierData{ "default": { - TierDefaultActionRuleID: calc.NewRuleID("default", "policy2", "", calc.RuleIndexTierDefaultAction, - rules.RuleDirIngress, rules.RuleActionDeny), + TierDefaultActionRuleID: calc.NewRuleID( + v3.KindGlobalNetworkPolicy, + "default", + "policy2", + "", + calc.RuleIndexTierDefaultAction, + rules.RuleDirIngress, + rules.RuleActionDeny, + ), EndOfTierMatchIndex: 0, }, }, @@ -161,13 +169,20 @@ var ( }, Egress: &calc.MatchData{ PolicyMatches: map[calc.PolicyID]int{ - {Name: "policy1", Tier: "default"}: 0, - {Name: "policy2", Tier: "default"}: 0, + {Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}: 0, + {Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}: 0, }, TierData: map[string]*calc.TierData{ "default": { - TierDefaultActionRuleID: calc.NewRuleID("default", "policy2", "", calc.RuleIndexTierDefaultAction, - rules.RuleDirIngress, rules.RuleActionDeny), + TierDefaultActionRuleID: calc.NewRuleID( + v3.KindGlobalNetworkPolicy, + "default", + "policy2", + "", + calc.RuleIndexTierDefaultAction, + rules.RuleDirIngress, + rules.RuleActionDeny, + ), EndOfTierMatchIndex: 0, }, }, @@ -178,13 +193,20 @@ var ( CommonEndpointData: calc.CalculateCommonEndpointData(localWlEPKey2, localWlEp2), Ingress: &calc.MatchData{ PolicyMatches: map[calc.PolicyID]int{ - {Name: "policy1", Tier: "default"}: 0, - {Name: "policy2", Tier: "default"}: 0, + {Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}: 0, + {Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}: 0, }, TierData: map[string]*calc.TierData{ "default": { - TierDefaultActionRuleID: calc.NewRuleID("default", "policy2", "", calc.RuleIndexTierDefaultAction, - rules.RuleDirIngress, rules.RuleActionDeny), + TierDefaultActionRuleID: calc.NewRuleID( + v3.KindGlobalNetworkPolicy, + "default", + "policy2", + "", + calc.RuleIndexTierDefaultAction, + rules.RuleDirIngress, + rules.RuleActionDeny, + ), EndOfTierMatchIndex: 0, }, }, @@ -192,13 +214,20 @@ var ( }, Egress: &calc.MatchData{ PolicyMatches: map[calc.PolicyID]int{ - {Name: "policy1", Tier: "default"}: 0, - {Name: "policy2", Tier: "default"}: 0, + {Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}: 0, + {Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}: 0, }, TierData: map[string]*calc.TierData{ "default": { - TierDefaultActionRuleID: calc.NewRuleID("default", "policy2", "", calc.RuleIndexTierDefaultAction, - rules.RuleDirIngress, rules.RuleActionDeny), + TierDefaultActionRuleID: calc.NewRuleID( + v3.KindGlobalNetworkPolicy, + "default", + "policy2", + "", + calc.RuleIndexTierDefaultAction, + rules.RuleDirIngress, + rules.RuleActionDeny, + ), EndOfTierMatchIndex: 0, }, }, @@ -244,18 +273,25 @@ var ( } ) +func toprefix(s string) [64]byte { + p := [64]byte{} + copy(p[:], []byte(s)) + return p +} + // Nflog prefix test parameters var ( - defTierAllowIngressNFLOGPrefix = [64]byte{'A', 'P', 'I', '0', '|', 'd', 'e', 'f', 'a', 'u', 'l', 't', '.', 'p', 'o', 'l', 'i', 'c', 'y', '1'} - defTierAllowEgressNFLOGPrefix = [64]byte{'A', 'P', 'E', '0', '|', 'd', 'e', 'f', 'a', 'u', 'l', 't', '.', 'p', 'o', 'l', 'i', 'c', 'y', '1'} - defTierDenyIngressNFLOGPrefix = [64]byte{'D', 'P', 'I', '0', '|', 'd', 'e', 'f', 'a', 'u', 'l', 't', '.', 'p', 'o', 'l', 'i', 'c', 'y', '2'} - defTierDenyEgressNFLOGPrefix = [64]byte{'D', 'P', 'E', '0', '|', 'd', 'e', 'f', 'a', 'u', 'l', 't', '.', 'p', 'o', 'l', 'i', 'c', 'y', '2'} + defTierAllowIngressNFLOGPrefix = toprefix("API0|gnp/policy1") + defTierAllowEgressNFLOGPrefix = toprefix("APE0|gnp/policy1") + defTierDenyIngressNFLOGPrefix = toprefix("DPI0|gnp/policy2") + defTierDenyEgressNFLOGPrefix = toprefix("DPE0|gnp/policy2") defTierPolicy1AllowIngressRuleID = &calc.RuleID{ PolicyID: calc.PolicyID{ - Tier: "default", + Kind: v3.KindGlobalNetworkPolicy, Name: "policy1", Namespace: "", }, + Tier: "default", Index: 0, IndexStr: "0", Action: rules.RuleActionAllow, @@ -263,10 +299,11 @@ var ( } defTierPolicy1AllowEgressRuleID = &calc.RuleID{ PolicyID: calc.PolicyID{ - Tier: "default", + Kind: v3.KindGlobalNetworkPolicy, Name: "policy1", Namespace: "", }, + Tier: "default", Index: 0, IndexStr: "0", Action: rules.RuleActionAllow, @@ -274,10 +311,11 @@ var ( } defTierPolicy2DenyIngressRuleID = &calc.RuleID{ PolicyID: calc.PolicyID{ - Tier: "default", + Kind: v3.KindGlobalNetworkPolicy, Name: "policy2", Namespace: "", }, + Tier: "default", Index: 0, IndexStr: "0", Action: rules.RuleActionDeny, @@ -285,10 +323,11 @@ var ( } defTierPolicy2DenyEgressRuleID = &calc.RuleID{ PolicyID: calc.PolicyID{ - Tier: "default", + Kind: v3.KindGlobalNetworkPolicy, Name: "policy2", Namespace: "", }, + Tier: "default", Index: 0, IndexStr: "0", Action: rules.RuleActionDeny, @@ -296,10 +335,11 @@ var ( } tier1TierPolicy1AllowIngressRuleID = &calc.RuleID{ PolicyID: calc.PolicyID{ - Tier: "tier1", + Kind: v3.KindGlobalNetworkPolicy, Name: "policy11", Namespace: "", }, + Tier: "tier1", Index: 0, IndexStr: "0", Action: rules.RuleActionAllow, @@ -307,10 +347,11 @@ var ( } tier1TierPolicy1DenyEgressRuleID = &calc.RuleID{ PolicyID: calc.PolicyID{ - Tier: "tier1", + Kind: v3.KindGlobalNetworkPolicy, Name: "policy11", Namespace: "", }, + Tier: "tier1", Index: 0, IndexStr: "0", Action: rules.RuleActionDeny, @@ -349,6 +390,7 @@ var egressPktAllowNflogTuple = nfnetlink.NflogPacketTuple{ L4Src: nfnetlink.NflogL4Info{Port: srcPort}, L4Dst: nfnetlink.NflogL4Info{Port: dstPort}, } + var egressPktAllow = map[nfnetlink.NflogPacketTuple]*nfnetlink.NflogPacketAggregate{ egressPktAllowNflogTuple: { Prefixes: []nfnetlink.NflogPrefix{ @@ -394,6 +436,7 @@ var localPktIngressNflogTuple = nfnetlink.NflogPacketTuple{ L4Src: nfnetlink.NflogL4Info{Port: srcPort}, L4Dst: nfnetlink.NflogL4Info{Port: dstPort}, } + var localPktIngress = map[nfnetlink.NflogPacketTuple]*nfnetlink.NflogPacketAggregate{ localPktIngressNflogTuple: { Prefixes: []nfnetlink.NflogPrefix{ @@ -446,10 +489,10 @@ var localPktEgressNflogTuple = nfnetlink.NflogPacketTuple{ L4Src: nfnetlink.NflogL4Info{Port: srcPort}, L4Dst: nfnetlink.NflogL4Info{Port: dstPort}, } + var localPktEgress = map[nfnetlink.NflogPacketTuple]*nfnetlink.NflogPacketAggregate{ localPktEgressNflogTuple: { Prefixes: []nfnetlink.NflogPrefix{ - { Prefix: defTierAllowEgressNFLOGPrefix, Len: 20, @@ -470,6 +513,7 @@ var localPktEgressDeniedPreDNATNflogTuple = nfnetlink.NflogPacketTuple{ L4Src: nfnetlink.NflogL4Info{Port: srcPort}, L4Dst: nfnetlink.NflogL4Info{Port: dstPortDNAT}, } + var localPktEgressDeniedPreDNAT = map[nfnetlink.NflogPacketTuple]*nfnetlink.NflogPacketAggregate{ localPktEgressDeniedPreDNATNflogTuple: { Prefixes: []nfnetlink.NflogPrefix{ @@ -494,6 +538,7 @@ var localPktEgressAllowedPreDNATNflogTuple = nfnetlink.NflogPacketTuple{ L4Src: nfnetlink.NflogL4Info{Port: srcPort}, L4Dst: nfnetlink.NflogL4Info{Port: dstPort}, } + var localPktEgressAllowedPreDNAT = map[nfnetlink.NflogPacketTuple]*nfnetlink.NflogPacketAggregate{ localPktEgressAllowedPreDNATNflogTuple: { Prefixes: []nfnetlink.NflogPrefix{ @@ -570,6 +615,180 @@ var _ = Describe("NFLOG Datasource", func() { }) }) }) + + // Tests for deleted endpoints - RuleHits should be skipped + Describe("NFLOG with deleted endpoints", func() { + // Test data for endpoints marked for deletion + var c *collector + var lm *calc.LookupsCache + var nflogReader *NFLogReader + + conf := &Config{ + AgeTimeout: time.Duration(10) * time.Second, + InitialReportingDelay: time.Duration(5) * time.Second, + ExportingInterval: time.Duration(1) * time.Second, + FlowLogsFlushInterval: time.Duration(100) * time.Second, + DisplayDebugTraceLogs: true, + } + + BeforeEach(func() { + epMap := map[[16]byte]calc.EndpointData{ + localIp1: localEd1, + localIp2: localEd2, + remoteIp1: remoteEd1, + } + nflogMap := map[[64]byte]*calc.RuleID{} + + for _, rid := range []*calc.RuleID{defTierPolicy1AllowEgressRuleID, defTierPolicy1AllowIngressRuleID, defTierPolicy2DenyIngressRuleID, defTierPolicy2DenyEgressRuleID} { + nflogMap[policyIDStrToRuleIDParts(rid)] = rid + } + + lm = newMockLookupsCache(epMap, nflogMap, nil, nil) + nflogReader = NewNFLogReader(lm, 0, 0, 0, false) + Expect(nflogReader.Start()).NotTo(HaveOccurred()) + c = newCollector(lm, conf).(*collector) + c.SetPacketInfoReader(nflogReader) + c.SetConntrackInfoReader(dummyConntrackInfoReader{}) + go func() { + Expect(c.Start()).NotTo(HaveOccurred()) + }() + }) + + AfterEach(func() { + nflogReader.Stop() + }) + + Describe("Test source endpoint marked for deletion", func() { + It("should skip RuleHits processing when source endpoint is marked for deletion", func() { + // Set up normal endpoint map + epMap := map[[16]byte]calc.EndpointData{ + localIp1: localEd1, // src endpoint to be marked for deletion + localIp2: localEd2, // normal dest endpoint + remoteIp1: remoteEd1, + } + nflogMap := map[[64]byte]*calc.RuleID{} + + for _, rid := range []*calc.RuleID{defTierPolicy1AllowEgressRuleID, defTierPolicy1AllowIngressRuleID, defTierPolicy2DenyIngressRuleID, defTierPolicy2DenyEgressRuleID} { + nflogMap[policyIDStrToRuleIDParts(rid)] = rid + } + + // Update the lookups cache with endpoint map + lm.SetMockData(epMap, nflogMap, nil, nil) + + // Mark the source endpoint for deletion + lm.MarkEndpointDeleted(localEd1) + + t := tuple.New(localIp1, localIp2, proto_tcp, srcPort, dstPort) + + // Send NFLOG packet - this should create the tuple but skip RuleHits processing + nflogReader.IngressC <- localPktIngress + Eventually(c.epStats).Should(HaveKey(*t)) + + data := c.epStats[*t] + // Verify that RuleHits were not processed (Path should be empty) + Expect(len(data.IngressRuleTrace.Path())).To(Equal(0), "IngressRuleTrace Path should be empty when source endpoint is marked for deletion") + Expect(len(data.EgressRuleTrace.Path())).To(Equal(0), "EgressRuleTrace Path should be empty when source endpoint is marked for deletion") + }) + }) + + Describe("Test destination endpoint marked for deletion", func() { + It("should skip RuleHits processing when destination endpoint is marked for deletion", func() { + // Set up normal endpoint map + epMap := map[[16]byte]calc.EndpointData{ + localIp1: localEd1, // normal src endpoint + localIp2: localEd2, // dest endpoint to be marked for deletion + remoteIp1: remoteEd1, + } + nflogMap := map[[64]byte]*calc.RuleID{} + + for _, rid := range []*calc.RuleID{defTierPolicy1AllowEgressRuleID, defTierPolicy1AllowIngressRuleID, defTierPolicy2DenyIngressRuleID, defTierPolicy2DenyEgressRuleID} { + nflogMap[policyIDStrToRuleIDParts(rid)] = rid + } + + // Update the lookups cache with endpoint map + lm.SetMockData(epMap, nflogMap, nil, nil) + + // Mark the destination endpoint for deletion + lm.MarkEndpointDeleted(localEd2) + + t := tuple.New(localIp1, localIp2, proto_tcp, srcPort, dstPort) + + // Send NFLOG packet - this should create the tuple but skip RuleHits processing + nflogReader.IngressC <- localPktIngress + Eventually(c.epStats).Should(HaveKey(*t)) + + data := c.epStats[*t] + // Verify that RuleHits were not processed (Path should be empty) + Expect(len(data.IngressRuleTrace.Path())).To(Equal(0), "IngressRuleTrace Path should be empty when destination endpoint is marked for deletion") + Expect(len(data.EgressRuleTrace.Path())).To(Equal(0), "EgressRuleTrace Path should be empty when destination endpoint is marked for deletion") + }) + }) + + Describe("Test remote source endpoint marked for deletion", func() { + It("should skip RuleHits processing when remote source endpoint is marked for deletion", func() { + // Set up normal endpoint map + epMap := map[[16]byte]calc.EndpointData{ + localIp1: localEd1, + localIp2: localEd2, + remoteIp1: remoteEd1, // remote src endpoint to be marked for deletion + } + nflogMap := map[[64]byte]*calc.RuleID{} + + for _, rid := range []*calc.RuleID{defTierPolicy1AllowEgressRuleID, defTierPolicy1AllowIngressRuleID, defTierPolicy2DenyIngressRuleID, defTierPolicy2DenyEgressRuleID} { + nflogMap[policyIDStrToRuleIDParts(rid)] = rid + } + + // Update the lookups cache with endpoint map + lm.SetMockData(epMap, nflogMap, nil, nil) + + // Mark the remote source endpoint for deletion + lm.MarkEndpointDeleted(remoteEd1) + + t := tuple.New(remoteIp1, localIp1, proto_tcp, srcPort, dstPort) + + // Send NFLOG packet - this should create the tuple but skip RuleHits processing + nflogReader.IngressC <- ingressPktAllow + Eventually(c.epStats).Should(HaveKey(*t)) + + data := c.epStats[*t] + // Verify that RuleHits were not processed (Path should be empty) + Expect(len(data.IngressRuleTrace.Path())).To(Equal(0), "IngressRuleTrace Path should be empty when remote source endpoint is marked for deletion") + Expect(len(data.EgressRuleTrace.Path())).To(Equal(0), "EgressRuleTrace Path should be empty when remote source endpoint is marked for deletion") + }) + }) + + // Test to ensure normal functionality is not broken + Describe("Test normal RuleHits processing with active endpoints", func() { + It("should process RuleHits when endpoints are NOT marked for deletion", func() { + // Use normal endpoints (not marked for deletion) by resetting to default state + epMap := map[[16]byte]calc.EndpointData{ + localIp1: localEd1, + localIp2: localEd2, + remoteIp1: remoteEd1, + } + nflogMap := map[[64]byte]*calc.RuleID{} + + for _, rid := range []*calc.RuleID{defTierPolicy1AllowEgressRuleID, defTierPolicy1AllowIngressRuleID, defTierPolicy2DenyIngressRuleID, defTierPolicy2DenyEgressRuleID} { + nflogMap[policyIDStrToRuleIDParts(rid)] = rid + } + + // Update the lookups cache with normal (active) endpoint map + lm.SetMockData(epMap, nflogMap, nil, nil) + + t := tuple.New(localIp1, localIp2, proto_tcp, srcPort, dstPort) + + // Send NFLOG packet - this should create the tuple AND process RuleHits + nflogReader.IngressC <- localPktIngress + Eventually(c.epStats).Should(HaveKey(*t)) + + data := c.epStats[*t] + // Verify that RuleHits were processed (Path should NOT be empty) + Eventually(func() int { + return len(data.IngressRuleTrace.Path()) + }, "500ms", "50ms").Should(BeNumerically(">", 0), "IngressRuleTrace Path should NOT be empty when endpoints are active") + }) + }) + }) }) // Entry remoteIp1:srcPort -> localIp1:dstPort @@ -649,6 +868,7 @@ var podProxyEgressPktAllowNflogTuple = nfnetlink.NflogPacketTuple{ L4Src: nfnetlink.NflogL4Info{Port: srcPort}, L4Dst: nfnetlink.NflogL4Info{Port: dstPort}, } + var podProxyEgressPktAllow = map[nfnetlink.NflogPacketTuple]*nfnetlink.NflogPacketAggregate{ podProxyEgressPktAllowNflogTuple: { Prefixes: []nfnetlink.NflogPrefix{ @@ -670,6 +890,7 @@ var proxyBackendIngressPktAllowNflogTuple = nfnetlink.NflogPacketTuple{ L4Src: nfnetlink.NflogL4Info{Port: proxyPort}, L4Dst: nfnetlink.NflogL4Info{Port: dstPort}, } + var proxyBackendIngressPktAllow = map[nfnetlink.NflogPacketTuple]*nfnetlink.NflogPacketAggregate{ proxyBackendIngressPktAllowNflogTuple: { Prefixes: []nfnetlink.NflogPrefix{ @@ -1318,6 +1539,7 @@ var _ = Describe("Conntrack Datasource", func() { Expect(data.ConntrackBytesCounterReverse()).Should(Equal(*counter.New(localCtEntryWithDNAT.ReplyCounters.Bytes))) }) }) + Describe("Test conntrack TCP Protoinfo State", func() { It("Handle TCP conntrack entries with TCP state TIME_WAIT after NFLOGs gathered", func() { By("handling a conntrack update to start tracking stats for tuple") @@ -1378,6 +1600,7 @@ var _ = Describe("Conntrack Datasource", func() { ciReaderSenderChan <- []clttypes.ConntrackInfo{convertCtEntry(inCtEntryStateTimeWait, 0)} Eventually(c.epStats, "500ms", "100ms").ShouldNot(HaveKey(*t)) }) + It("Handle TCP conntrack entries with TCP state TIME_WAIT before NFLOGs gathered", func() { By("handling a conntrack update to start tracking stats for tuple") t := tuple.New(remoteIp1, localIp1, proto_tcp, srcPort, dstPort) @@ -1493,18 +1716,19 @@ var _ = Describe("Conntrack Datasource", func() { By("creating a matching service for the pre-DNAT cluster IP and port") lm.SetMockData(nil, nil, nil, map[model.ResourceKey]*kapiv1.Service{ - {Kind: model.KindKubernetesService, Name: "svc", Namespace: "default"}: {Spec: kapiv1.ServiceSpec{ - Ports: []kapiv1.ServicePort{{ - Name: "test", - Protocol: kapiv1.ProtocolTCP, - Port: int32(dstPortDNAT), - }}, - ClusterIP: "192.168.0.2", - ClusterIPs: []string{ - "192.168.0.2", + {Kind: model.KindKubernetesService, Name: "svc", Namespace: "default"}: { + Spec: kapiv1.ServiceSpec{ + Ports: []kapiv1.ServicePort{{ + Name: "test", + Protocol: kapiv1.ProtocolTCP, + Port: int32(dstPortDNAT), + }}, + ClusterIP: "192.168.0.2", + ClusterIPs: []string{ + "192.168.0.2", + }, }, }, - }, }) By("handling another nflog update for destination matching on policy - should rematch and expire the entry") @@ -1528,18 +1752,19 @@ var _ = Describe("Conntrack Datasource", func() { By("creating a matching service for the pre-DNAT cluster IP and port") lm.SetMockData(nil, nil, nil, map[model.ResourceKey]*kapiv1.Service{ - {Kind: model.KindKubernetesService, Name: "svc", Namespace: "default"}: {Spec: kapiv1.ServiceSpec{ - Ports: []kapiv1.ServicePort{{ - Name: "test", - Protocol: kapiv1.ProtocolTCP, - Port: int32(dstPortDNAT), - }}, - ClusterIP: "192.168.0.2", - ClusterIPs: []string{ - "192.168.0.2", + {Kind: model.KindKubernetesService, Name: "svc", Namespace: "default"}: { + Spec: kapiv1.ServiceSpec{ + Ports: []kapiv1.ServicePort{{ + Name: "test", + Protocol: kapiv1.ProtocolTCP, + Port: int32(dstPortDNAT), + }}, + ClusterIP: "192.168.0.2", + ClusterIPs: []string{ + "192.168.0.2", + }, }, }, - }, }) By("handling another nflog update for destination matching on policy - should rematch and expire the entry") @@ -1550,22 +1775,9 @@ var _ = Describe("Conntrack Datasource", func() { }) func policyIDStrToRuleIDParts(r *calc.RuleID) [64]byte { - var ( - name string - byt64 [64]byte - ) - - if r.Namespace != "" { - if strings.HasPrefix(r.Name, "knp.default.") { - name = fmt.Sprintf("%s/%s", r.Namespace, r.Name) - } else { - name = fmt.Sprintf("%s/%s.%s", r.Namespace, r.Tier, r.Name) - } - } else { - name = fmt.Sprintf("%s.%s", r.Tier, r.Name) - } - - prefix := rules.CalculateNFLOGPrefixStr(r.Action, rules.RuleOwnerTypePolicy, r.Direction, r.Index, name) + var byt64 [64]byte + id := types.PolicyID{Name: r.Name, Namespace: r.Namespace, Kind: r.Kind} + prefix := rules.CalculateNFLOGPrefixStr(r.Action, rules.RuleOwnerTypePolicy, r.Direction, r.Index, id) copy(byt64[:], []byte(prefix)) return byt64 } @@ -1766,156 +1978,1361 @@ func (mr *mockReporter) Report(u any) error { return nil } -func BenchmarkNflogPktToStat(b *testing.B) { - epMap := map[[16]byte]calc.EndpointData{ - localIp1: localEd1, - localIp2: localEd2, - remoteIp1: remoteEd1, +var _ = Describe("Collector Namespace-Aware NetworkSet Lookups", func() { + var c *collector + var testIP [16]byte + + // Convert IP string to [16]byte format + ipToBytes := func(ipStr string) [16]byte { + ip := net2.ParseIP(ipStr) + var result [16]byte + copy(result[:], ip.To16()) + return result } - nflogMap := map[[64]byte]*calc.RuleID{} - - for _, rid := range []*calc.RuleID{defTierPolicy1AllowEgressRuleID, defTierPolicy1AllowIngressRuleID, defTierPolicy2DenyIngressRuleID, defTierPolicy2DenyEgressRuleID} { - nflogMap[policyIDStrToRuleIDParts(rid)] = rid - } + // Helper function to replace the old lookupEndpointWithNamespace behavior. + // This uses the public findEndpointBestMatch interface by setting up a dummy source endpoint + // to provide the preferredNamespace context. + testLookupEndpoint := func(c *collector, clientIPBytes, ip [16]byte, canCheckEgressDomains bool, preferredNamespace string) calc.EndpointData { + srcIP := clientIPBytes - conf := &Config{ - AgeTimeout: time.Duration(10) * time.Second, - InitialReportingDelay: time.Duration(5) * time.Second, - ExportingInterval: time.Duration(1) * time.Second, - FlowLogsFlushInterval: time.Duration(100) * time.Second, - DisplayDebugTraceLogs: true, - } - lm := newMockLookupsCache(epMap, nflogMap, nil, nil) - nflogReader := NewNFLogReader(lm, 0, 0, 0, false) - c := newCollector(lm, conf).(*collector) - c.SetPacketInfoReader(nflogReader) - c.SetConntrackInfoReader(dummyConntrackInfoReader{}) - b.ResetTimer() - b.ReportAllocs() - for n := 0; n < b.N; n++ { - pktinfo := nflogReader.ConvertNflogPkt(rules.RuleDirIngress, ingressPktAllow[ingressPktAllowNflogTuple]) - c.applyPacketInfo(pktinfo) - } -} + // If we need a namespace context but don't have a source IP, generate a dummy one. + if preferredNamespace != "" && srcIP == [16]byte{} { + srcIP = ipToBytes("192.0.2.1") + } -func BenchmarkApplyStatUpdate(b *testing.B) { - epMap := map[[16]byte]calc.EndpointData{ - localIp1: localEd1, - localIp2: localEd2, - remoteIp1: remoteEd1, - } + if preferredNamespace != "" { + // Create a dummy endpoint in the preferred namespace to simulate the source + epKey := model.WorkloadEndpointKey{ + Hostname: "test-host", + OrchestratorID: "k8s", + WorkloadID: "test-workload-src", + EndpointID: "test-endpoint-src", + } + ep := &model.WorkloadEndpoint{ + Name: "test-endpoint-src", + Labels: uniquelabels.Make(map[string]string{"env": "test"}), + } - nflogMap := map[[64]byte]*calc.RuleID{} - for _, rid := range []*calc.RuleID{defTierPolicy1AllowEgressRuleID, defTierPolicy1AllowIngressRuleID, defTierPolicy2DenyIngressRuleID, defTierPolicy2DenyEgressRuleID} { - nflogMap[policyIDStrToRuleIDParts(rid)] = rid - } + common := calc.CalculateCommonEndpointData(epKey, ep) + var endpoint calc.EndpointData + if canCheckEgressDomains { + endpoint = &calc.LocalEndpointData{ + CommonEndpointData: common, + } + } else { + endpoint = &calc.RemoteEndpointData{ + CommonEndpointData: common, + } + } - conf := &Config{ - AgeTimeout: time.Duration(10) * time.Second, - InitialReportingDelay: time.Duration(5) * time.Second, - ExportingInterval: time.Duration(1) * time.Second, - FlowLogsFlushInterval: time.Duration(100) * time.Second, - DisplayDebugTraceLogs: true, - } - lm := newMockLookupsCache(epMap, nflogMap, nil, nil) - nflogReader := NewNFLogReader(lm, 0, 0, 0, false) - c := newCollector(lm, conf).(*collector) - c.SetPacketInfoReader(nflogReader) - c.SetConntrackInfoReader(dummyConntrackInfoReader{}) - var tuples []tuple.Tuple - MaxSrcPort := 1000 - MaxDstPort := 1000 - for sp := 1; sp < MaxSrcPort; sp++ { - for dp := 1; dp < MaxDstPort; dp++ { - t := tuple.New(localIp1, localIp2, proto_tcp, sp, dp) - tuples = append(tuples, *t) - } - } - var rids []*calc.RuleID - MaxEntries := 10000 - for i := 0; i < MaxEntries; i++ { - rid := defTierPolicy1AllowIngressRuleID - rids = append(rids, rid) - } - b.ResetTimer() - b.ReportAllocs() - for n := 0; n < b.N; n++ { - for i := 0; i < MaxEntries; i++ { - data := NewData(tuples[i], localEd1, remoteEd1) - c.applyNflogStatUpdate(data, rids[i], 0, 1, 2) + // Inject the dummy endpoint into the cache + c.luc.SetMockData(map[[16]byte]calc.EndpointData{srcIP: endpoint}, nil, nil, nil) } + + // Perform the lookup using the public interface + t := tuple.Tuple{Src: srcIP, Dst: ip} + _, dstEp := c.findEndpointBestMatch(t) + return dstEp } -} -type dummyConntrackInfoReader struct { - MockSenderChannel chan []clttypes.ConntrackInfo -} + BeforeEach(func() { + // Test IP that will match our NetworkSets + testIP = ipToBytes("10.1.1.1") + }) -func (d dummyConntrackInfoReader) Start() error { return nil } -func (d dummyConntrackInfoReader) ConntrackInfoChan() <-chan []clttypes.ConntrackInfo { - return d.MockSenderChannel -} + Context("when testing endpoint lookup with NetworkSet fallback", func() { + It("should prioritize more specific NetworkSets correctly", func() { + // Create test NetworkSets + globalNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.0.0.0/8"), // Broad CIDR + }, + Labels: uniquelabels.Make(map[string]string{ + "type": "global", + }), + } -func TestLoopDataplaneInfoUpdates(t *testing.T) { - RegisterTestingT(t) + specificNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.1.0.0/16"), // More specific than global + }, + Labels: uniquelabels.Make(map[string]string{ + "type": "specific", + "env": "test", + }), + } - // Setup helper function to initialize the collector and channel, and register cleanup. - setup := func(t *testing.T) (*collector, chan *proto.ToDataplane) { - dpInfoChan := make(chan *proto.ToDataplane, 10) - c := &collector{ - policyStoreManager: policystore.NewPolicyStoreManager(), - } - // Register cleanup to be automatically called at the end of each test - t.Cleanup(func() { - close(dpInfoChan) + // Create test keys - using NetworkSetKey for global, ResourceKey for namespaced + globalKey := model.NetworkSetKey{Name: "global-netset"} + specificKey := model.NetworkSetKey{Name: "specific-netset"} // Using NetworkSetKey for simplicity + + // Create lookups cache with both NetworkSets + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + globalKey: globalNetworkSet, + specificKey: specificNetworkSet, + } + lm := newMockLookupsCache(nil, nil, nsMap, nil) + + // Create collector with NetworkSets enabled + c = newCollector(lm, &Config{ + EnableNetworkSets: true, + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + // Test: Should return the more specific NetworkSet (longest prefix match) + result := testLookupEndpoint(c, [16]byte{}, testIP, false, "any-namespace") + Expect(result).ToNot(BeNil()) + Expect(result.Key()).To(Equal(specificKey)) + + // Verify it's actually the specific NetworkSet + Expect(result.Labels().String()).To(ContainSubstring("specific")) }) - // Start the loop in a goroutine - go c.loopProcessingDataplaneInfoUpdates(dpInfoChan) + It("should fallback to global NetworkSet when no better match exists", func() { + // Create only global NetworkSet + globalNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.0.0.0/8"), // Covers our test IP + }, + Labels: uniquelabels.Make(map[string]string{ + "type": "global", + }), + } - return c, dpInfoChan - } + globalKey := model.NetworkSetKey{Name: "global-netset"} + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + globalKey: globalNetworkSet, + } + lm := newMockLookupsCache(nil, nil, nsMap, nil) + + c = newCollector(lm, &Config{ + EnableNetworkSets: true, + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + // Test with any namespace - should return global NetworkSet + result := testLookupEndpoint(c, [16]byte{}, testIP, false, "any-namespace") + Expect(result).ToNot(BeNil()) + Expect(result.Key()).To(Equal(globalKey)) + }) - insync := func(dpInfoChan chan *proto.ToDataplane) { - // Ensure that the test channel is closed at the end of each test - dpInfo := proto.ToDataplane{ - Payload: &proto.ToDataplane_InSync{ - InSync: &proto.InSync{}, - }, - } - dpInfoChan <- &dpInfo - } + It("should return nil when NetworkSets are disabled", func() { + // Create NetworkSet data but disable NetworkSets in config + globalNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.0.0.0/8"), + }, + } - t.Run("should process dataplane info updates and update the policy store", func(t *testing.T) { - c, dpInfoChan := setup(t) + globalKey := model.NetworkSetKey{Name: "global-netset"} + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + globalKey: globalNetworkSet, + } + lm := newMockLookupsCache(nil, nil, nsMap, nil) - id := proto.WorkloadEndpointID{ - OrchestratorId: "test-orchestrator", - WorkloadId: "test-workload", - EndpointId: "test-endpoint", - } - dpInfo := proto.ToDataplane{ - Payload: &proto.ToDataplane_WorkloadEndpointUpdate{ - WorkloadEndpointUpdate: &proto.WorkloadEndpointUpdate{ - Id: &id, - Endpoint: &proto.WorkloadEndpoint{ - Name: "test-endpoint", - }, - }, - }, - } - dpInfoChan <- &dpInfo - insync(dpInfoChan) + c = newCollector(lm, &Config{ + EnableNetworkSets: false, // Disabled + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) - Eventually(func() bool { - validation := false - c.policyStoreManager.DoWithReadLock(func(store *policystore.PolicyStore) { - validation = len(store.Endpoints) == 1 && - store.Endpoints[types.ProtoToWorkloadEndpointID(&id)].Name == "test-endpoint" - }) - return validation - }, time.Duration(time.Second*5), time.Millisecond*1000).Should(BeTrue()) + // Should return nil because NetworkSets are disabled + result := testLookupEndpoint(c, [16]byte{}, testIP, false, "namespace1") + Expect(result).To(BeNil()) + }) + + It("should prioritize endpoints over NetworkSets", func() { + // Create test endpoint key and data + testEPKey := model.WorkloadEndpointKey{ + Hostname: "test-host", + OrchestratorID: "k8s", + WorkloadID: "test-workload", + EndpointID: "test-endpoint", + } + + testWlEP := &model.WorkloadEndpoint{ + Labels: uniquelabels.Make(map[string]string{ + "type": "endpoint", + }), + } + + endpoint := &calc.LocalEndpointData{ + CommonEndpointData: calc.CalculateCommonEndpointData(testEPKey, testWlEP), + } + + networkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.0.0.0/8"), + }, + } + + nsKey := model.NetworkSetKey{Name: "netset"} + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + nsKey: networkSet, + } + epMap := map[[16]byte]calc.EndpointData{ + testIP: endpoint, + } + lm := newMockLookupsCache(epMap, nil, nsMap, nil) + + c = newCollector(lm, &Config{ + EnableNetworkSets: true, + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + // Should return endpoint, not NetworkSet + result := testLookupEndpoint(c, [16]byte{}, testIP, false, "namespace1") + Expect(result).ToNot(BeNil()) + Expect(result.Key()).To(Equal(testEPKey)) + }) + + It("should handle no matching NetworkSets gracefully", func() { + // Create NetworkSet that doesn't match our test IP + nonMatchingNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("192.168.0.0/16"), // Different range + }, + } + + nonMatchingKey := model.NetworkSetKey{Name: "non-matching"} + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + nonMatchingKey: nonMatchingNetworkSet, + } + lm := newMockLookupsCache(nil, nil, nsMap, nil) + + c = newCollector(lm, &Config{ + EnableNetworkSets: true, + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + // Should return nil since no NetworkSet matches + result := testLookupEndpoint(c, [16]byte{}, testIP, false, "namespace1") + Expect(result).To(BeNil()) + }) + }) + + Context("when testing namespace optimization benefits", func() { + It("should select the most specific NetworkSet match (narrowest CIDR)", func() { + // This test validates that the collector uses the namespace-aware lookup + // Create multiple overlapping NetworkSets to show specificity + broadNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.0.0.0/8"), // Very broad + }, + Labels: uniquelabels.Make(map[string]string{ + "type": "broad", + }), + } + + mediumNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.1.0.0/16"), // Medium specificity + }, + Labels: uniquelabels.Make(map[string]string{ + "type": "medium", + }), + } + + narrowNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.1.1.0/24"), // Most specific + }, + Labels: uniquelabels.Make(map[string]string{ + "type": "narrow", + }), + } + + broadKey := model.NetworkSetKey{Name: "broad-netset"} + mediumKey := model.NetworkSetKey{Name: "medium-netset"} + narrowKey := model.NetworkSetKey{Name: "narrow-netset"} + + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + broadKey: broadNetworkSet, + mediumKey: mediumNetworkSet, + narrowKey: narrowNetworkSet, + } + lm := newMockLookupsCache(nil, nil, nsMap, nil) + + c = newCollector(lm, &Config{ + EnableNetworkSets: true, + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + // Test that collector picks most specific match (narrowest CIDR) + result := testLookupEndpoint(c, [16]byte{}, testIP, false, "test-namespace") + Expect(result).ToNot(BeNil()) + Expect(result.Key()).To(Equal(narrowKey)) + + // Verify it's the narrow NetworkSet + Expect(result.Labels().String()).To(ContainSubstring("narrow")) + }) + + It("should handle performance efficiently with multiple NetworkSets", func() { + // Create multiple NetworkSets for performance testing + nsMap := make(map[model.NetworkSetKey]*model.NetworkSet) + + for i := range 10 { + networkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet(fmt.Sprintf("10.%d.0.0/16", i)), + }, + Labels: uniquelabels.Make(map[string]string{ + "type": fmt.Sprintf("test-%d", i), + }), + } + + key := model.NetworkSetKey{Name: fmt.Sprintf("test-netset-%d", i)} + nsMap[key] = networkSet + } + + lm := newMockLookupsCache(nil, nil, nsMap, nil) + c = newCollector(lm, &Config{ + EnableNetworkSets: true, + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + // Performance test: multiple namespace lookups should complete quickly + start := time.Now() + for i := range 50 { + namespace := fmt.Sprintf("namespace-%d", i%5) + testIPLoop := ipToBytes(fmt.Sprintf("10.%d.1.1", i%10)) + + result := testLookupEndpoint(c, [16]byte{}, testIPLoop, false, namespace) + // Should find a match for IPs in our test ranges + if i < 10 { + Expect(result).ToNot(BeNil()) + } + } + elapsed := time.Since(start) + + // Should complete 50 lookups in reasonable time (namespace optimization) + Expect(elapsed).To(BeNumerically("<", 25*time.Millisecond)) + }) + }) + + Context("when testing namespace-specific NetworkSets", func() { + It("should prioritize namespace-specific NetworkSets over global ones", func() { + // Create a global NetworkSet + globalNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.0.0.0/8"), // Broad global range + }, + Labels: uniquelabels.Make(map[string]string{ + "type": "global", + "tier": "base", + }), + } + + // Create a namespace-specific NetworkSet that overlaps with global + namespaceNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.1.0.0/16"), // More specific range within global + }, + Labels: uniquelabels.Make(map[string]string{ + "type": "namespace-specific", + "tier": "application", + }), + } + + // Create keys - ResourceKey for namespace-scoped, NetworkSetKey for global + globalKey := model.NetworkSetKey{Name: "global-netset"} + namespaceKey := model.ResourceKey{ + Kind: "NetworkSet", + Name: "app-netset", + Namespace: "production", // This has a namespace + } + + // Create the mock lookup cache + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + globalKey: globalNetworkSet, + // For ResourceKey, we need to convert it to NetworkSetKey for the mock + // The real LookupsCache handles ResourceKey properly, but our mock uses NetworkSetKey + } + + // We need to also include the namespaced NetworkSet in the map + // In the real system, ResourceKeys are internally mapped properly + namespacedNSKey := model.NetworkSetKey{Name: namespaceKey.Name} + nsMap[namespacedNSKey] = namespaceNetworkSet + + lm := newMockLookupsCache(nil, nil, nsMap, nil) + + c = newCollector(lm, &Config{ + EnableNetworkSets: true, + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + // Test with the specific namespace - should prefer namespace-specific NetworkSet + result := testLookupEndpoint(c, [16]byte{}, testIP, false, "production") + Expect(result).ToNot(BeNil()) + + // Should get the namespace-specific NetworkSet (more specific CIDR) + Expect(result.Labels().String()).To(ContainSubstring("namespace-specific")) + }) + + It("should fall back to global NetworkSet when no namespace match exists", func() { + // Test true namespace isolation: when a more specific NetworkSet exists in a different namespace, + // it should NOT be selected, and instead fall back to a global NetworkSet + + // Create global NetworkSet with broader range + globalNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.0.0.0/8"), // Broad range that includes testIP (10.1.1.1) + }, + Labels: uniquelabels.Make(map[string]string{ + "type": "global-fallback", + }), + } + + // Create namespace-specific NetworkSet that DOES contain testIP but is from different namespace + // This should NOT be selected for 'staging' namespace requests due to namespace isolation + productionNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.1.0.0/16"), // More specific range that INCLUDES testIP (10.1.1.1) + }, + Labels: uniquelabels.Make(map[string]string{ + "type": "production-specific", + "namespace": "production", + }), + } + + // Use the correct namespace naming format: namespace/name + globalKey := model.NetworkSetKey{Name: "global-netset"} // Global NetworkSet (no namespace prefix) + productionKey := model.NetworkSetKey{Name: "production/prod-netset"} // Namespaced NetworkSet + + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + globalKey: globalNetworkSet, + productionKey: productionNetworkSet, + } + lc := newMockLookupsCache(nil, nil, nsMap, nil) + + c = newCollector(lc, &Config{ + EnableNetworkSets: true, + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + // Request with 'staging' namespace - different from the 'production' namespace NetworkSet + // testIP (10.1.1.1) matches both: + // - global-netset: 10.0.0.0/8 (broader, global) + // - production/prod-netset: 10.1.0.0/16 (more specific, but wrong namespace) + // Should return global NetworkSet due to namespace isolation + result := testLookupEndpoint(c, [16]byte{}, testIP, false, "staging") + Expect(result).ToNot(BeNil()) + Expect(result.Key()).To(Equal(globalKey)) + Expect(result.Labels().String()).To(ContainSubstring("global-fallback")) + }) + + It("should handle multiple namespaced NetworkSets correctly", func() { + // Create NetworkSets for different namespaces with overlapping CIDRs + productionNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.1.1.0/24"), // Specific production range + }, + Labels: uniquelabels.Make(map[string]string{ + "env": "production", + "tier": "frontend", + }), + } + + stagingNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.1.2.0/24"), // Specific staging range + }, + Labels: uniquelabels.Make(map[string]string{ + "env": "staging", + "tier": "frontend", + }), + } + + developmentNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.1.0.0/16"), // Broader dev range + }, + Labels: uniquelabels.Make(map[string]string{ + "env": "development", + "tier": "all", + }), + } + + prodKey := model.NetworkSetKey{Name: "prod-frontend"} + stagingKey := model.NetworkSetKey{Name: "staging-frontend"} + devKey := model.NetworkSetKey{Name: "dev-all"} + + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + prodKey: productionNetworkSet, + stagingKey: stagingNetworkSet, + devKey: developmentNetworkSet, + } + lm := newMockLookupsCache(nil, nil, nsMap, nil) + + c = newCollector(lm, &Config{ + EnableNetworkSets: true, + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + // Test production namespace with IP in production range + prodIP := ipToBytes("10.1.1.100") + result := testLookupEndpoint(c, [16]byte{}, prodIP, false, "production") + Expect(result).ToNot(BeNil()) + Expect(result.Key()).To(Equal(prodKey)) + Expect(result.Labels().String()).To(ContainSubstring("production")) + + // Test staging namespace with IP in staging range + stagingIP := ipToBytes("10.1.2.100") + result = testLookupEndpoint(c, [16]byte{}, stagingIP, false, "staging") + Expect(result).ToNot(BeNil()) + Expect(result.Key()).To(Equal(stagingKey)) + Expect(result.Labels().String()).To(ContainSubstring("staging")) + + // Test development namespace with IP that matches broader dev range + devIP := ipToBytes("10.1.5.100") // In 10.1.0.0/16 but not in specific /24s + result = testLookupEndpoint(c, [16]byte{}, devIP, false, "development") + Expect(result).ToNot(BeNil()) + Expect(result.Key()).To(Equal(devKey)) + Expect(result.Labels().String()).To(ContainSubstring("development")) + }) + + It("should demonstrate namespace isolation in NetworkSet lookups", func() { + // Create identical CIDR ranges in different namespaces + // This tests that namespace isolation works properly + commonCIDR := utils.MustParseNet("10.1.0.0/16") + + frontendNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{commonCIDR}, + Labels: uniquelabels.Make(map[string]string{ + "app": "frontend", + "tier": "web", + }), + } + + backendNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{commonCIDR}, // Same CIDR, different namespace + Labels: uniquelabels.Make(map[string]string{ + "app": "backend", + "tier": "api", + }), + } + + frontendKey := model.NetworkSetKey{Name: "frontend-netset"} + backendKey := model.NetworkSetKey{Name: "backend-netset"} + + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + frontendKey: frontendNetworkSet, + backendKey: backendNetworkSet, + } + lm := newMockLookupsCache(nil, nil, nsMap, nil) + + c = newCollector(lm, &Config{ + EnableNetworkSets: true, + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + // Same IP, different namespaces should potentially return different NetworkSets + // depending on the namespace-aware logic and CIDR specificity + testIPCommon := ipToBytes("10.1.1.100") + + // Frontend namespace lookup + frontendResult := testLookupEndpoint(c, [16]byte{}, testIPCommon, false, "frontend") + Expect(frontendResult).ToNot(BeNil()) + + // Backend namespace lookup + backendResult := testLookupEndpoint(c, [16]byte{}, testIPCommon, false, "backend") + Expect(backendResult).ToNot(BeNil()) + + // In this case with identical CIDRs, the longest-prefix-match logic will determine + // which NetworkSet is returned, but namespace awareness is being tested + }) + + It("should handle empty namespace gracefully", func() { + // Test with empty/default namespace + defaultNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.1.0.0/16"), + }, + Labels: uniquelabels.Make(map[string]string{ + "scope": "default", + }), + } + + defaultKey := model.NetworkSetKey{Name: "default-netset"} + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + defaultKey: defaultNetworkSet, + } + lm := newMockLookupsCache(nil, nil, nsMap, nil) + + c = newCollector(lm, &Config{ + EnableNetworkSets: true, + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + // Test with empty namespace + result := testLookupEndpoint(c, [16]byte{}, testIP, false, "") + Expect(result).ToNot(BeNil()) + Expect(result.Key()).To(Equal(defaultKey)) + }) + }) + + Context("when testing getEndpointsWithNamespaceContext optimization", func() { + It("should optimize lookups when both endpoints are found directly", func() { + srcIP := ipToBytes("10.1.1.10") + dstIP := ipToBytes("10.1.1.20") + + // Create endpoints for both source and destination + srcEPKey := model.WorkloadEndpointKey{ + Hostname: "src-host", + OrchestratorID: "k8s", + WorkloadID: "src-ns/src-workload", + EndpointID: "src-endpoint", + } + dstEPKey := model.WorkloadEndpointKey{ + Hostname: "dst-host", + OrchestratorID: "k8s", + WorkloadID: "dst-ns/dst-workload", + EndpointID: "dst-endpoint", + } + + srcEP := &calc.LocalEndpointData{ + CommonEndpointData: calc.CalculateCommonEndpointData(srcEPKey, &model.WorkloadEndpoint{}), + } + dstEP := &calc.LocalEndpointData{ + CommonEndpointData: calc.CalculateCommonEndpointData(dstEPKey, &model.WorkloadEndpoint{}), + } + + // Create NetworkSets that could match these IPs (but shouldn't be used) + networkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.1.0.0/16"), // Could match both IPs + }, + Labels: uniquelabels.Make(map[string]string{ + "type": "fallback", + }), + } + + nsKey := model.NetworkSetKey{Name: "fallback-netset"} + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + nsKey: networkSet, + } + epMap := map[[16]byte]calc.EndpointData{ + srcIP: srcEP, + dstIP: dstEP, + } + lm := newMockLookupsCache(epMap, nil, nsMap, nil) + + c = newCollector(lm, &Config{ + EnableNetworkSets: true, + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + t := tuple.Make(srcIP, dstIP, proto_tcp, 8080, 80) + srcResult, dstResult := c.findEndpointBestMatch(t) + + // Both should return direct endpoints (not NetworkSets) + Expect(srcResult).ToNot(BeNil()) + Expect(dstResult).ToNot(BeNil()) + Expect(srcResult.Key()).To(Equal(srcEPKey)) + Expect(dstResult.Key()).To(Equal(dstEPKey)) + }) + + It("should use namespace context from destination when source needs NetworkSet lookup", func() { + srcIP := ipToBytes("10.1.1.10") // No direct endpoint for this + dstIP := ipToBytes("10.1.1.20") + + // Create destination endpoint with namespace + dstEPKey := model.WorkloadEndpointKey{ + Hostname: "dst-host", + OrchestratorID: "k8s", + WorkloadID: "production/dst-workload", // namespace: production + EndpointID: "dst-endpoint", + } + dstEP := &calc.LocalEndpointData{ + CommonEndpointData: calc.CalculateCommonEndpointData(dstEPKey, &model.WorkloadEndpoint{}), + } + + // Create NetworkSets - one generic, one namespace-specific + genericNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.0.0.0/8"), // Broader range + }, + Labels: uniquelabels.Make(map[string]string{ + "type": "generic", + }), + } + productionNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.1.0.0/16"), // More specific for production + }, + Labels: uniquelabels.Make(map[string]string{ + "type": "production-specific", + "namespace": "production", + }), + } + + genericKey := model.NetworkSetKey{Name: "generic-netset"} + productionKey := model.NetworkSetKey{Name: "production-netset"} + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + genericKey: genericNetworkSet, + productionKey: productionNetworkSet, + } + epMap := map[[16]byte]calc.EndpointData{ + dstIP: dstEP, + } + lm := newMockLookupsCache(epMap, nil, nsMap, nil) + + c = newCollector(lm, &Config{ + EnableNetworkSets: true, + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + t := tuple.Make(srcIP, dstIP, proto_tcp, 8080, 80) + srcResult, dstResult := c.findEndpointBestMatch(t) + + // Destination should be direct endpoint + Expect(dstResult).ToNot(BeNil()) + Expect(dstResult.Key()).To(Equal(dstEPKey)) + + // Source should use NetworkSet (preferably production-specific due to namespace context) + Expect(srcResult).ToNot(BeNil()) + // Should get the more specific NetworkSet (production-specific) + Expect(srcResult.Key()).To(Equal(productionKey)) + }) + + It("should use namespace context from source when destination needs NetworkSet lookup", func() { + srcIP := ipToBytes("10.1.1.10") + dstIP := ipToBytes("10.1.1.20") // No direct endpoint for this + + // Create source endpoint with namespace + srcEPKey := model.WorkloadEndpointKey{ + Hostname: "src-host", + OrchestratorID: "k8s", + WorkloadID: "staging/src-workload", // namespace: staging + EndpointID: "src-endpoint", + } + srcEP := &calc.LocalEndpointData{ + CommonEndpointData: calc.CalculateCommonEndpointData(srcEPKey, &model.WorkloadEndpoint{}), + } + + // Create NetworkSets + globalNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.0.0.0/8"), // Broader range + }, + Labels: uniquelabels.Make(map[string]string{ + "type": "global", + }), + } + stagingNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.1.0.0/16"), // More specific for staging + }, + Labels: uniquelabels.Make(map[string]string{ + "type": "staging-specific", + "namespace": "staging", + }), + } + + globalKey := model.NetworkSetKey{Name: "global-netset"} + stagingKey := model.NetworkSetKey{Name: "staging-netset"} + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + globalKey: globalNetworkSet, + stagingKey: stagingNetworkSet, + } + epMap := map[[16]byte]calc.EndpointData{ + srcIP: srcEP, + } + lm := newMockLookupsCache(epMap, nil, nsMap, nil) + + c = newCollector(lm, &Config{ + EnableNetworkSets: true, + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + t := tuple.Make(srcIP, dstIP, proto_tcp, 8080, 80) + srcResult, dstResult := c.findEndpointBestMatch(t) + + // Source should be direct endpoint + Expect(srcResult).ToNot(BeNil()) + Expect(srcResult.Key()).To(Equal(srcEPKey)) + + // Destination should use NetworkSet with namespace context from source + Expect(dstResult).ToNot(BeNil()) + // Should get the staging-specific NetworkSet + Expect(dstResult.Key()).To(Equal(stagingKey)) + }) + + It("should return both NetworkSets when no direct endpoints exist", func() { + srcIP := utils.IpStrTo16Byte("10.1.1.10") // No direct endpoints + dstIP := utils.IpStrTo16Byte("10.1.1.20") + + // Create only NetworkSets + srcNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.1.1.0/24"), // Specific for source + }, + Labels: uniquelabels.Make(map[string]string{ + "type": "src-network", + }), + } + dstNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.1.2.0/24"), // Different range for dest + }, + Labels: uniquelabels.Make(map[string]string{ + "type": "dst-network", + }), + } + fallbackNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.0.0.0/8"), // Fallback for both + }, + Labels: uniquelabels.Make(map[string]string{ + "type": "fallback", + }), + } + + srcKey := model.NetworkSetKey{Name: "src-netset"} + dstKey := model.NetworkSetKey{Name: "dst-netset"} + fallbackKey := model.NetworkSetKey{Name: "fallback-netset"} + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + srcKey: srcNetworkSet, + dstKey: dstNetworkSet, + fallbackKey: fallbackNetworkSet, + } + lm := newMockLookupsCache(nil, nil, nsMap, nil) + + c = newCollector(lm, &Config{ + EnableNetworkSets: true, + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + t := tuple.Make(srcIP, dstIP, proto_tcp, 8080, 80) + srcResult, dstResult := c.findEndpointBestMatch(t) + + // Both should return NetworkSets + Expect(srcResult).ToNot(BeNil()) + Expect(dstResult).ToNot(BeNil()) + + // Due to how NetworkSet lookup works, it may return the first matching NetworkSet + // The actual behavior depends on the order of NetworkSets returned by the lookup cache + // Let's check that we get some NetworkSet match for both + Expect(srcResult.Key()).To(BeAssignableToTypeOf(model.NetworkSetKey{})) + Expect(dstResult.Key()).To(BeAssignableToTypeOf(model.NetworkSetKey{})) + }) + + It("should handle NetworkSets disabled gracefully", func() { + srcIP := utils.IpStrTo16Byte("10.1.1.10") + dstIP := utils.IpStrTo16Byte("10.1.1.20") + + // Create NetworkSets that would match + networkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.1.0.0/16"), + }, + } + + nsKey := model.NetworkSetKey{Name: "test-netset"} + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + nsKey: networkSet, + } + lm := newMockLookupsCache(nil, nil, nsMap, nil) + + c = newCollector(lm, &Config{ + EnableNetworkSets: false, // Disabled + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + t := tuple.Make(srcIP, dstIP, proto_tcp, 8080, 80) + srcResult, dstResult := c.findEndpointBestMatch(t) + + // Both should be nil since NetworkSets are disabled and no direct endpoints exist + Expect(srcResult).To(BeNil()) + Expect(dstResult).To(BeNil()) + }) + + It("should handle mixed endpoint and NetworkSet scenarios efficiently", func() { + srcIP := ipToBytes("10.1.1.10") + dstIP := ipToBytes("10.1.1.20") + + // Source has direct endpoint + srcEPKey := model.WorkloadEndpointKey{ + Hostname: "src-host", + OrchestratorID: "k8s", + WorkloadID: "development/src-workload", + EndpointID: "src-endpoint", + } + srcEP := &calc.LocalEndpointData{ + CommonEndpointData: calc.CalculateCommonEndpointData(srcEPKey, &model.WorkloadEndpoint{}), + } + + // Destination only has NetworkSet + networkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.1.0.0/16"), + }, + Labels: uniquelabels.Make(map[string]string{ + "type": "dev-network", + "namespace": "development", + }), + } + + nsKey := model.NetworkSetKey{Name: "dev-netset"} + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + nsKey: networkSet, + } + epMap := map[[16]byte]calc.EndpointData{ + srcIP: srcEP, + } + lm := newMockLookupsCache(epMap, nil, nsMap, nil) + + c = newCollector(lm, &Config{ + EnableNetworkSets: true, + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + t := tuple.Make(srcIP, dstIP, proto_tcp, 8080, 80) + srcResult, dstResult := c.findEndpointBestMatch(t) + + // Source should be direct endpoint + Expect(srcResult).ToNot(BeNil()) + Expect(srcResult.Key()).To(Equal(srcEPKey)) + + // Destination should be NetworkSet (with namespace context from source) + Expect(dstResult).ToNot(BeNil()) + Expect(dstResult.Key()).To(Equal(nsKey)) + }) + }) + + Context("when testing lookupNetworkSetWithNamespace function", func() { + It("should return nil when NetworkSets are disabled", func() { + testIPLocal := ipToBytes("10.1.1.100") + + networkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.1.0.0/16"), + }, + } + + nsKey := model.NetworkSetKey{Name: "test-netset"} + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + nsKey: networkSet, + } + lm := newMockLookupsCache(nil, nil, nsMap, nil) + + c = newCollector(lm, &Config{ + EnableNetworkSets: false, + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + result := c.lookupNetworkSetWithNamespace([16]byte{}, testIPLocal, false, "test-namespace") + Expect(result).To(BeNil()) + }) + + It("should return NetworkSet when one matches", func() { + testIPLocal := ipToBytes("10.1.1.100") + + networkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.1.0.0/16"), + }, + Labels: uniquelabels.Make(map[string]string{ + "type": "test", + }), + } + + nsKey := model.NetworkSetKey{Name: "test-netset"} + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + nsKey: networkSet, + } + lm := newMockLookupsCache(nil, nil, nsMap, nil) + + c = newCollector(lm, &Config{ + EnableNetworkSets: true, + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + result := c.lookupNetworkSetWithNamespace([16]byte{}, testIPLocal, false, "test-namespace") + Expect(result).ToNot(BeNil()) + Expect(result.Key()).To(Equal(nsKey)) + }) + + It("should return nil when no NetworkSet matches", func() { + testIPLocal := ipToBytes("192.168.1.100") // Different range + + networkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.1.0.0/16"), // Won't match testIPLocal + }, + } + + nsKey := model.NetworkSetKey{Name: "test-netset"} + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + nsKey: networkSet, + } + lm := newMockLookupsCache(nil, nil, nsMap, nil) + + c = newCollector(lm, &Config{ + EnableNetworkSets: true, + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + result := c.lookupNetworkSetWithNamespace([16]byte{}, testIPLocal, false, "test-namespace") + Expect(result).To(BeNil()) + }) + }) + + Context("when testing namespace extraction from endpoints", func() { + It("should extract namespace from WorkloadEndpoint correctly", func() { + // Test the getNamespaceFromEp function indirectly by testing the full lookup flow + srcIP := ipToBytes("10.1.1.10") + dstIP := ipToBytes("10.1.1.20") + + // Create source endpoint with namespace in WorkloadID + srcEPKey := model.WorkloadEndpointKey{ + Hostname: "src-host", + OrchestratorID: "k8s", + WorkloadID: "frontend/src-workload", // namespace: frontend + EndpointID: "src-endpoint", + } + srcEP := &calc.LocalEndpointData{ + CommonEndpointData: calc.CalculateCommonEndpointData(srcEPKey, &model.WorkloadEndpoint{}), + } + + // Create namespace-specific NetworkSet that should be used for destination + frontendNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.1.0.0/16"), + }, + Labels: uniquelabels.Make(map[string]string{ + "type": "frontend-network", + "namespace": "frontend", + }), + } + + nsKey := model.NetworkSetKey{Name: "frontend-netset"} + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + nsKey: frontendNetworkSet, + } + epMap := map[[16]byte]calc.EndpointData{ + srcIP: srcEP, + } + lm := newMockLookupsCache(epMap, nil, nsMap, nil) + + c = newCollector(lm, &Config{ + EnableNetworkSets: true, + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + t := tuple.Make(srcIP, dstIP, proto_tcp, 8080, 80) + srcResult, dstResult := c.findEndpointBestMatch(t) + + // Source should be the direct endpoint + Expect(srcResult).ToNot(BeNil()) + Expect(srcResult.Key()).To(Equal(srcEPKey)) + + // Destination should use the NetworkSet (demonstrating namespace context was used) + Expect(dstResult).ToNot(BeNil()) + Expect(dstResult.Key()).To(Equal(nsKey)) + }) + + It("should handle endpoints with ResourceKey correctly", func() { + srcIP := ipToBytes("10.1.1.10") + dstIP := ipToBytes("10.1.1.20") + + // Create a WorkloadEndpoint that represents a production namespace + srcEPKey := model.WorkloadEndpointKey{ + Hostname: "src-host", + OrchestratorID: "k8s", + WorkloadID: "production/src-workload", // namespace: production + EndpointID: "src-endpoint", + } + srcEP := &calc.LocalEndpointData{ + CommonEndpointData: calc.CalculateCommonEndpointData(srcEPKey, &model.WorkloadEndpoint{}), + } + + // Create destination NetworkSet that should use the namespace from source + productionNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.1.0.0/16"), + }, + Labels: uniquelabels.Make(map[string]string{ + "type": "production-network", + "namespace": "production", + }), + } + + nsKey := model.NetworkSetKey{Name: "production-netset"} + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + nsKey: productionNetworkSet, + } + epMap := map[[16]byte]calc.EndpointData{ + srcIP: srcEP, + } + lm := newMockLookupsCache(epMap, nil, nsMap, nil) + + c = newCollector(lm, &Config{ + EnableNetworkSets: true, + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + t := tuple.Make(srcIP, dstIP, proto_tcp, 8080, 80) + srcResult, dstResult := c.findEndpointBestMatch(t) + + // Source should be the WorkloadEndpoint + Expect(srcResult).ToNot(BeNil()) + Expect(srcResult.Key()).To(Equal(srcEPKey)) + + // Destination should use the NetworkSet (with namespace context from source) + Expect(dstResult).ToNot(BeNil()) + Expect(dstResult.Key()).To(Equal(nsKey)) + }) + + It("should handle endpoints with no extractable namespace gracefully", func() { + srcIP := ipToBytes("10.1.1.10") + dstIP := ipToBytes("10.1.1.20") + + // Create endpoint with WorkloadID that doesn't follow namespace/name pattern + srcEPKey := model.WorkloadEndpointKey{ + Hostname: "src-host", + OrchestratorID: "k8s", + WorkloadID: "invalid-workload-format", // No namespace separator + EndpointID: "src-endpoint", + } + srcEP := &calc.LocalEndpointData{ + CommonEndpointData: calc.CalculateCommonEndpointData(srcEPKey, &model.WorkloadEndpoint{}), + } + + // Create a generic NetworkSet + genericNetworkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.1.0.0/16"), + }, + Labels: uniquelabels.Make(map[string]string{ + "type": "generic", + }), + } + + nsKey := model.NetworkSetKey{Name: "generic-netset"} + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + nsKey: genericNetworkSet, + } + epMap := map[[16]byte]calc.EndpointData{ + srcIP: srcEP, + } + lm := newMockLookupsCache(epMap, nil, nsMap, nil) + + c = newCollector(lm, &Config{ + EnableNetworkSets: true, + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + t := tuple.Make(srcIP, dstIP, proto_tcp, 8080, 80) + srcResult, dstResult := c.findEndpointBestMatch(t) + + // Source should be the direct endpoint + Expect(srcResult).ToNot(BeNil()) + Expect(srcResult.Key()).To(Equal(srcEPKey)) + + // Destination should still get the NetworkSet (with empty namespace context) + Expect(dstResult).ToNot(BeNil()) + Expect(dstResult.Key()).To(Equal(nsKey)) + }) + }) +}) + +// Mock EgressDomainCache for testing +type mockEgressDomainCache struct { + domains map[string]map[[16]byte][]string +} + +func (m *mockEgressDomainCache) GetTopLevelDomainsForIP(clientIP string, ip [16]byte) []string { + if clientDomains, ok := m.domains[clientIP]; ok { + if domains, ok := clientDomains[ip]; ok { + return domains + } + } + return nil +} + +func (m *mockEgressDomainCache) IterWatchedDomainsForIP(clientIP string, ip [16]byte, fn func(domain string) bool) { + if clientDomains, ok := m.domains[clientIP]; ok { + if domains, ok := clientDomains[ip]; ok { + _ = slices.ContainsFunc(domains, fn) + } + } +} + +func BenchmarkNflogPktToStat(b *testing.B) { + epMap := map[[16]byte]calc.EndpointData{ + localIp1: localEd1, + localIp2: localEd2, + remoteIp1: remoteEd1, + } + + nflogMap := map[[64]byte]*calc.RuleID{} + + for _, rid := range []*calc.RuleID{defTierPolicy1AllowEgressRuleID, defTierPolicy1AllowIngressRuleID, defTierPolicy2DenyIngressRuleID, defTierPolicy2DenyEgressRuleID} { + nflogMap[policyIDStrToRuleIDParts(rid)] = rid + } + + conf := &Config{ + AgeTimeout: time.Duration(10) * time.Second, + InitialReportingDelay: time.Duration(5) * time.Second, + ExportingInterval: time.Duration(1) * time.Second, + FlowLogsFlushInterval: time.Duration(100) * time.Second, + DisplayDebugTraceLogs: true, + } + lm := newMockLookupsCache(epMap, nflogMap, nil, nil) + nflogReader := NewNFLogReader(lm, 0, 0, 0, false) + c := newCollector(lm, conf).(*collector) + c.SetPacketInfoReader(nflogReader) + c.SetConntrackInfoReader(dummyConntrackInfoReader{}) + b.ResetTimer() + b.ReportAllocs() + for n := 0; n < b.N; n++ { + pktinfo := nflogReader.ConvertNflogPkt(rules.RuleDirIngress, ingressPktAllow[ingressPktAllowNflogTuple]) + c.applyPacketInfo(pktinfo) + } +} + +func BenchmarkApplyStatUpdate(b *testing.B) { + epMap := map[[16]byte]calc.EndpointData{ + localIp1: localEd1, + localIp2: localEd2, + remoteIp1: remoteEd1, + } + + nflogMap := map[[64]byte]*calc.RuleID{} + for _, rid := range []*calc.RuleID{defTierPolicy1AllowEgressRuleID, defTierPolicy1AllowIngressRuleID, defTierPolicy2DenyIngressRuleID, defTierPolicy2DenyEgressRuleID} { + nflogMap[policyIDStrToRuleIDParts(rid)] = rid + } + + conf := &Config{ + AgeTimeout: time.Duration(10) * time.Second, + InitialReportingDelay: time.Duration(5) * time.Second, + ExportingInterval: time.Duration(1) * time.Second, + FlowLogsFlushInterval: time.Duration(100) * time.Second, + DisplayDebugTraceLogs: true, + } + lm := newMockLookupsCache(epMap, nflogMap, nil, nil) + nflogReader := NewNFLogReader(lm, 0, 0, 0, false) + c := newCollector(lm, conf).(*collector) + c.SetPacketInfoReader(nflogReader) + c.SetConntrackInfoReader(dummyConntrackInfoReader{}) + var tuples []tuple.Tuple + MaxSrcPort := 1000 + MaxDstPort := 1000 + for sp := 1; sp < MaxSrcPort; sp++ { + for dp := 1; dp < MaxDstPort; dp++ { + t := tuple.New(localIp1, localIp2, proto_tcp, sp, dp) + tuples = append(tuples, *t) + } + } + var rids []*calc.RuleID + MaxEntries := 10000 + for range MaxEntries { + rid := defTierPolicy1AllowIngressRuleID + rids = append(rids, rid) + } + b.ResetTimer() + b.ReportAllocs() + for n := 0; n < b.N; n++ { + for i := range MaxEntries { + data := NewData(tuples[i], localEd1, remoteEd1) + c.applyNflogStatUpdate(data, rids[i], 0, 1, 2) + } + } +} + +type dummyConntrackInfoReader struct { + MockSenderChannel chan []clttypes.ConntrackInfo +} + +func (d dummyConntrackInfoReader) Start() error { return nil } +func (d dummyConntrackInfoReader) ConntrackInfoChan() <-chan []clttypes.ConntrackInfo { + return d.MockSenderChannel +} + +func TestLoopDataplaneInfoUpdates(t *testing.T) { + RegisterTestingT(t) + + // Setup helper function to initialize the collector and channel, and register cleanup. + setup := func(t *testing.T) (*collector, chan *proto.ToDataplane) { + dpInfoChan := make(chan *proto.ToDataplane, 10) + c := &collector{ + policyStoreManager: policystore.NewPolicyStoreManager(), + } + // Register cleanup to be automatically called at the end of each test + t.Cleanup(func() { + close(dpInfoChan) + }) + + // Start the loop in a goroutine + go c.loopProcessingDataplaneInfoUpdates(dpInfoChan) + + return c, dpInfoChan + } + + insync := func(dpInfoChan chan *proto.ToDataplane) { + // Ensure that the test channel is closed at the end of each test + dpInfo := proto.ToDataplane{ + Payload: &proto.ToDataplane_InSync{ + InSync: &proto.InSync{}, + }, + } + dpInfoChan <- &dpInfo + } + + t.Run("should process dataplane info updates and update the policy store", func(t *testing.T) { + c, dpInfoChan := setup(t) + + id := proto.WorkloadEndpointID{ + OrchestratorId: "test-orchestrator", + WorkloadId: "test-workload", + EndpointId: "test-endpoint", + } + dpInfo := proto.ToDataplane{ + Payload: &proto.ToDataplane_WorkloadEndpointUpdate{ + WorkloadEndpointUpdate: &proto.WorkloadEndpointUpdate{ + Id: &id, + Endpoint: &proto.WorkloadEndpoint{ + Name: "test-endpoint", + }, + }, + }, + } + dpInfoChan <- &dpInfo + insync(dpInfoChan) + + Eventually(func() bool { + validation := false + c.policyStoreManager.DoWithReadLock(func(store *policystore.PolicyStore) { + validation = len(store.Endpoints) == 1 && + store.Endpoints[types.ProtoToWorkloadEndpointID(&id)].Name == "test-endpoint" + }) + return validation + }, time.Duration(time.Second*5), time.Millisecond*1000).Should(BeTrue()) }) t.Run("should handle multiple dataplane info updates", func(t *testing.T) { @@ -1965,7 +3382,6 @@ func TestLoopDataplaneInfoUpdates(t *testing.T) { }) return validation }, time.Duration(time.Second*5), time.Millisecond*1000).Should(BeTrue()) - }) t.Run("should not panic when the channel is closed", func(t *testing.T) { @@ -1988,249 +3404,386 @@ func TestLoopDataplaneInfoUpdates(t *testing.T) { func TestRunPendingRuleTraceEvaluation(t *testing.T) { RegisterTestingT(t) - data1 := &Data{ - Tuple: tuple.Tuple{ - Src: utils.IpStrTo16Byte("192.168.1.1"), - Dst: utils.IpStrTo16Byte("10.0.0.1"), - Proto: proto_tcp, - L4Src: 12345, - L4Dst: 80, - }, - SrcEp: &calc.LocalEndpointData{ - CommonEndpointData: calc.CalculateCommonEndpointData( - model.WorkloadEndpointKey{ - OrchestratorID: "k8s", - WorkloadID: "default.workload1", - EndpointID: "eth0", - }, - &model.WorkloadEndpoint{}, - ), - }, - DstEp: &calc.LocalEndpointData{ - CommonEndpointData: calc.CalculateCommonEndpointData( - model.WorkloadEndpointKey{ - OrchestratorID: "k8s", - WorkloadID: "default.workload2", - EndpointID: "eth0", - }, - &model.WorkloadEndpoint{}, - ), - }, + // Helper function to convert model workload endpoint key to protobuf endpoint ID + convertWorkloadId := func(key model.WorkloadEndpointKey) types.WorkloadEndpointID { + return types.WorkloadEndpointID{ + OrchestratorId: key.OrchestratorID, + WorkloadId: key.WorkloadID, + EndpointId: key.EndpointID, + } + } + + // Setup test environment + epMap := map[[16]byte]calc.EndpointData{ + localIp1: localEd1, + localIp2: localEd2, + remoteIp1: remoteEd1, } + lm := newMockLookupsCache(epMap, nil, nil, nil) policyStoreManager := policystore.NewPolicyStoreManager() - policyStoreManager.DoWithLock(func(ps *policystore.PolicyStore) { - ps.Endpoints[types.WorkloadEndpointID{ - OrchestratorId: "k8s", - WorkloadId: "default.workload1", - EndpointId: "eth0", - }] = &proto.WorkloadEndpoint{ - State: "active", - Name: "eth0", - Tiers: []*proto.TierInfo{ - { - Name: "default", - EgressPolicies: []string{"policy1"}, - }, - }, - } - ps.Endpoints[types.WorkloadEndpointID{ - OrchestratorId: "k8s", - WorkloadId: "default.workload2", - EndpointId: "eth0", - }] = &proto.WorkloadEndpoint{ - State: "active", - Name: "eth0", - Tiers: []*proto.TierInfo{ - { - Name: "default", - IngressPolicies: []string{"policy1"}, - }, - }, - } - ps.PolicyByID[types.PolicyID{ - Tier: "default", - Name: "policy1", - }] = &proto.Policy{ - InboundRules: []*proto.Rule{ - { - Action: "allow", - }, - }, - OutboundRules: []*proto.Rule{ - { - Action: "allow", - }, - }, + conf := &Config{ + AgeTimeout: time.Duration(10) * time.Second, + InitialReportingDelay: time.Duration(5) * time.Second, + ExportingInterval: time.Duration(1) * time.Second, + FlowLogsFlushInterval: time.Duration(100) * time.Second, + DisplayDebugTraceLogs: true, + PolicyStoreManager: policyStoreManager, + } + c := newCollector(lm, conf).(*collector) + + // Create test flow tuples + // Flow 1: Local-to-local communication (localIp1 -> localIp2) + flowTuple1 := tuple.New(localIp1, localIp2, proto_tcp, 1000, 1000) + + // Flow 2: Local-to-remote communication (localIp2 -> remoteIp1) + flowTuple2 := tuple.New(localIp2, remoteIp1, proto_tcp, 1000, 1000) + + // Setup initial policy configuration + // localWlEp1 has policy1 for both ingress and egress + localWlEp1Proto := calc.ModelWorkloadEndpointToProto(localWlEp1, nil, nil, []*proto.TierInfo{ + { + Name: "default", + IngressPolicies: []*proto.PolicyID{{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: []*proto.PolicyID{{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}}, + }, + }) + + // localWlEp2 initially has policy2 (deny) for both ingress and egress + localWlEp2Proto := calc.ModelWorkloadEndpointToProto(localWlEp2, nil, nil, []*proto.TierInfo{ + { + Name: "default", + IngressPolicies: []*proto.PolicyID{{Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: []*proto.PolicyID{{Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}}, + }, + }) + + // remoteWlEp1 has no policies + remoteWlEp1Proto := calc.ModelWorkloadEndpointToProto(remoteWlEp1, nil, nil, []*proto.TierInfo{}) + + // Initialize policy store with endpoints and policies + policyStoreManager.DoWithLock(func(ps *policystore.PolicyStore) { + // Add endpoint configurations + ps.Endpoints[convertWorkloadId(localWlEPKey1)] = localWlEp1Proto + ps.Endpoints[convertWorkloadId(localWlEPKey2)] = localWlEp2Proto + ps.Endpoints[convertWorkloadId(remoteWlEpKey1)] = remoteWlEp1Proto + + // Add policy definitions + // policy1: Allow all traffic + ps.PolicyByID[types.PolicyID{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}] = &proto.Policy{ + Tier: "default", + InboundRules: []*proto.Rule{{Action: "allow"}}, + OutboundRules: []*proto.Rule{{Action: "allow"}}, } - ps.PolicyByID[types.PolicyID{ - Tier: "tier1", - Name: "policy11", - }] = &proto.Policy{ - InboundRules: []*proto.Rule{ - { - Action: "allow", - }, - }, - OutboundRules: []*proto.Rule{ - { - Action: "deny", - }, - }, + // policy2: Deny all traffic + ps.PolicyByID[types.PolicyID{Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}] = &proto.Policy{ + Tier: "default", + InboundRules: []*proto.Rule{{Action: "deny"}}, + OutboundRules: []*proto.Rule{{Action: "deny"}}, } }) policyStoreManager.OnInSync() - c := &collector{ - epStats: make(map[tuple.Tuple]*Data), - policyStoreManager: policyStoreManager, - displayDebugTraceLogs: false, + + // Simulate packet processing to create flow data + ruleIDIngressPolicy1 := calc.NewRuleID(v3.KindGlobalNetworkPolicy, "default", "policy1", "", 0, rules.RuleDirIngress, rules.RuleActionAllow) + packetInfoIngress1 := clttypes.PacketInfo{ + Tuple: *flowTuple1, + Direction: rules.RuleDirIngress, + RuleHits: []clttypes.RuleHit{{RuleID: ruleIDIngressPolicy1, Hits: 1, Bytes: 100}}, } + c.applyPacketInfo(packetInfoIngress1) - c.epStats[data1.Tuple] = data1 + ruleIDEgressPolicy1 := calc.NewRuleID(v3.KindGlobalNetworkPolicy, "default", "policy1", "", 0, rules.RuleDirEgress, rules.RuleActionAllow) + packetInfoEgress1 := clttypes.PacketInfo{ + Tuple: *flowTuple1, + Direction: rules.RuleDirEgress, + RuleHits: []clttypes.RuleHit{{RuleID: ruleIDEgressPolicy1, Hits: 1, Bytes: 100}}, + } + c.applyPacketInfo(packetInfoEgress1) - // Add a second data entry - data2 := &Data{ - Tuple: tuple.Tuple{ - Src: utils.IpStrTo16Byte("192.168.1.2"), - Dst: utils.IpStrTo16Byte("10.0.0.2"), - Proto: proto_tcp, - L4Src: 12346, - L4Dst: 81, + // Process egress packet for flow 2 (localIp2 -> remoteIp1) + ruleIDEgressPolicy2 := calc.NewRuleID(v3.KindGlobalNetworkPolicy, "default", "policy2", "", 0, rules.RuleDirEgress, rules.RuleActionDeny) + packetInfoEgress2 := clttypes.PacketInfo{ + Tuple: *flowTuple2, + Direction: rules.RuleDirEgress, + RuleHits: []clttypes.RuleHit{{RuleID: ruleIDEgressPolicy2, Hits: 1, Bytes: 100}}, + } + c.applyPacketInfo(packetInfoEgress2) + + // Retrieve flow data from collector + flowData1 := c.epStats[*flowTuple1] + flowData2 := c.epStats[*flowTuple2] + + // Verify initial pending rule trace evaluation + testCases := []struct { + name string + pendingRuleIDs []*calc.RuleID + expectedRuleID *calc.RuleID + expectedLength int + description string + }{ + { + name: "Flow1 Ingress", + pendingRuleIDs: flowData1.IngressPendingRuleIDs, + expectedRuleID: defTierPolicy2DenyIngressRuleID, + expectedLength: 1, + description: "Flow1 destination (localEd2) should have policy2 deny rule for ingress", }, - SrcEp: &calc.LocalEndpointData{ - CommonEndpointData: calc.CalculateCommonEndpointData( - model.WorkloadEndpointKey{ - OrchestratorID: "k8s", - WorkloadID: "default.workload3", - EndpointID: "eth1", - }, - &model.WorkloadEndpoint{}, - ), + { + name: "Flow1 Egress", + pendingRuleIDs: flowData1.EgressPendingRuleIDs, + expectedRuleID: defTierPolicy1AllowEgressRuleID, + expectedLength: 1, + description: "Flow1 source (localEd1) should have policy1 allow rule for egress", }, - DstEp: &calc.LocalEndpointData{ - CommonEndpointData: calc.CalculateCommonEndpointData( - model.WorkloadEndpointKey{ - OrchestratorID: "k8s", - WorkloadID: "default.workload4", - EndpointID: "eth1", - }, - &model.WorkloadEndpoint{}, - ), + { + name: "Flow2 Ingress", + pendingRuleIDs: flowData2.IngressPendingRuleIDs, + expectedRuleID: nil, + expectedLength: 0, + description: "Flow2 destination (remoteEd1) has no policies, so no ingress rules", + }, + { + name: "Flow2 Egress", + pendingRuleIDs: flowData2.EgressPendingRuleIDs, + expectedRuleID: defTierPolicy2DenyEgressRuleID, + expectedLength: 1, + description: "Flow2 source (localEd2) should have policy2 deny rule for egress", }, } - c.policyStoreManager.DoWithLock(func(ps *policystore.PolicyStore) { - ps.Endpoints[types.WorkloadEndpointID{ - OrchestratorId: "k8s", - WorkloadId: "default.workload3", - EndpointId: "eth1", - }] = &proto.WorkloadEndpoint{ - State: "active", - Name: "eth1", - Tiers: []*proto.TierInfo{ - { - Name: "tier1", - EgressPolicies: []string{"policy11"}, - }, - }, - } - ps.Endpoints[types.WorkloadEndpointID{ - OrchestratorId: "k8s", - WorkloadId: "default.workload4", - EndpointId: "eth1", - }] = &proto.WorkloadEndpoint{ - State: "active", - Name: "eth1", - Tiers: []*proto.TierInfo{ - { - Name: "tier1", - IngressPolicies: []string{"policy11"}, - }, - }, - } - }) - c.epStats[data2.Tuple] = data2 - t.Run("updatePendingRuleTraces", func(t *testing.T) { - // Update pending rule traces + // Test initial policy evaluation + for _, tc := range testCases { + t.Run("Initial_"+tc.name, func(t *testing.T) { + Expect(tc.pendingRuleIDs).To(HaveLen(tc.expectedLength), tc.description) + if tc.expectedLength == 1 { + validateRuleID(t, tc.pendingRuleIDs[0], tc.expectedRuleID, tc.name) + } + }) + } + + // Test policy update scenario + t.Run("PolicyUpdate", func(t *testing.T) { + // Change localWlEp2 from policy2 (deny) to policy1 (allow) + updatedLocalWlEp2Proto := calc.ModelWorkloadEndpointToProto(localWlEp2, nil, nil, []*proto.TierInfo{ + {Name: "default", IngressPolicies: []*proto.PolicyID{{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}}, EgressPolicies: []*proto.PolicyID{{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}}}, + }) + + // Update the policy store + c.policyStoreManager.DoWithLock(func(ps *policystore.PolicyStore) { + ps.Endpoints[convertWorkloadId(localWlEPKey2)] = updatedLocalWlEp2Proto + }) + c.policyStoreManager.OnInSync() + + // Trigger pending rule trace update c.updatePendingRuleTraces() - for _, ruleID := range []struct { - iteration int + // Get updated flow data + updatedFlowData1 := c.epStats[*flowTuple1] + updatedFlowData2 := c.epStats[*flowTuple2] + + // Verify updated policy evaluation + updatedTestCases := []struct { + name string pendingRuleIDs []*calc.RuleID expectedRuleID *calc.RuleID + expectedLength int + description string }{ - {0, data1.IngressPendingRuleIDs, defTierPolicy1AllowIngressRuleID}, - {1, data1.EgressPendingRuleIDs, defTierPolicy1AllowEgressRuleID}, - {2, data2.IngressPendingRuleIDs, tier1TierPolicy1AllowIngressRuleID}, - {3, data2.EgressPendingRuleIDs, tier1TierPolicy1DenyEgressRuleID}, - } { - Expect(ruleID.pendingRuleIDs).To(HaveLen(1), "Iteration: %s.Expected PendingRuleIDs to be updated") - Expect(ruleID.pendingRuleIDs[0].Name).To(Equal(ruleID.expectedRuleID.Name), "Iteration: %s.Expected policy name to be: %s", ruleID.iteration, ruleID.expectedRuleID.Name) - Expect(ruleID.pendingRuleIDs[0].Tier).To(Equal(ruleID.expectedRuleID.Tier), "Iteration: %s.Expected tier name to be: %s", ruleID.iteration, ruleID.expectedRuleID.Tier) - Expect(ruleID.pendingRuleIDs[0].Namespace).To(Equal(ruleID.expectedRuleID.Namespace), "Iteration: %s.Expected namespace to be: %s", ruleID.iteration, ruleID.expectedRuleID.Namespace) - Expect(ruleID.pendingRuleIDs[0].Action).To(Equal(ruleID.expectedRuleID.Action), "Iteration: %s.Expected action to be: %s", ruleID.iteration, ruleID.expectedRuleID.Action) - Expect(ruleID.pendingRuleIDs[0].Direction).To(Equal(ruleID.expectedRuleID.Direction), "Iteration: %s.Expected direction to be: %s", ruleID.iteration, ruleID.expectedRuleID.Direction) - Expect(ruleID.pendingRuleIDs[0].Index).To(Equal(ruleID.expectedRuleID.Index), "Iteration: %s.Expected index to be: %s", ruleID.iteration, ruleID.expectedRuleID.Index) + { + name: "Flow1 Ingress After Update", + pendingRuleIDs: updatedFlowData1.IngressPendingRuleIDs, + expectedRuleID: defTierPolicy1AllowIngressRuleID, + expectedLength: 1, + description: "After update, Flow1 destination should have policy1 allow rule for ingress", + }, + { + name: "Flow1 Egress After Update", + pendingRuleIDs: updatedFlowData1.EgressPendingRuleIDs, + expectedRuleID: defTierPolicy1AllowEgressRuleID, + expectedLength: 1, + description: "Flow1 source should still have policy1 allow rule for egress", + }, + { + name: "Flow2 Ingress After Update", + pendingRuleIDs: updatedFlowData2.IngressPendingRuleIDs, + expectedRuleID: nil, + expectedLength: 0, + description: "Flow2 destination (remoteEd1) still has no policies", + }, + { + name: "Flow2 Egress After Update", + pendingRuleIDs: updatedFlowData2.EgressPendingRuleIDs, + expectedRuleID: defTierPolicy1AllowEgressRuleID, + expectedLength: 1, + description: "After update, Flow2 source should have policy1 allow rule for egress", + }, } - // Update the policies - c.policyStoreManager.DoWithLock(func(ps *policystore.PolicyStore) { - ps.Endpoints[types.WorkloadEndpointID{ - OrchestratorId: "k8s", - WorkloadId: "default.workload1", - EndpointId: "eth0", - }] = &proto.WorkloadEndpoint{ - State: "active", - Name: "eth0", - Tiers: []*proto.TierInfo{ - { - Name: "tier1", - EgressPolicies: []string{"policy11"}, - }, + for _, tc := range updatedTestCases { + t.Run(tc.name, func(t *testing.T) { + Expect(tc.pendingRuleIDs).To(HaveLen(tc.expectedLength), tc.description) + if tc.expectedLength == 1 { + validateRuleID(t, tc.pendingRuleIDs[0], tc.expectedRuleID, tc.name) + } + }) + } + }) + + Context("lookupNetworkSetWithNamespace function", func() { + It("should return nil when IP does not match any NetworkSet", func() { + srcIP := utils.IpStrTo16Byte("10.1.1.10") + dstIP := utils.IpStrTo16Byte("192.168.1.10") + + // Create a NetworkSet that doesn't match either IP + networkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("172.16.0.0/16"), }, + Labels: uniquelabels.Make(map[string]string{ + "type": "production-network", + "namespace": "production", + }), } - ps.Endpoints[types.WorkloadEndpointID{ - OrchestratorId: "k8s", - WorkloadId: "default.workload2", - EndpointId: "eth0", - }] = &proto.WorkloadEndpoint{ - State: "active", - Name: "eth0", - Tiers: []*proto.TierInfo{ - { - Name: "tier1", - IngressPolicies: []string{"policy11"}, - }, + + nsKey := model.NetworkSetKey{Name: "production-netset"} + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + nsKey: networkSet, + } + lm := newMockLookupsCache(nil, nil, nsMap, nil) + + c = newCollector(lm, &Config{ + EnableNetworkSets: true, + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + result := c.lookupNetworkSetWithNamespace(srcIP, dstIP, false, "production") + Expect(result).To(BeNil()) + }) + + It("should return NetworkSet endpoint for NetworkSet-based lookups", func() { + srcIP := utils.IpStrTo16Byte("10.1.1.10") + dstIP := utils.IpStrTo16Byte("10.1.1.20") + + // Create a NetworkSet that matches the destination IP + networkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.1.0.0/16"), + }, + Labels: uniquelabels.Make(map[string]string{ + "type": "production-network", + "namespace": "production", + }), + } + + nsKey := model.NetworkSetKey{Name: "production-netset"} + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + nsKey: networkSet, + } + lm := newMockLookupsCache(nil, nil, nsMap, nil) + + c = newCollector(lm, &Config{ + EnableNetworkSets: true, + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + result := c.lookupNetworkSetWithNamespace(srcIP, dstIP, false, "production") + Expect(result).ToNot(BeNil()) + Expect(result.Key()).To(Equal(nsKey)) + }) + + It("should return nil when NetworkSets are disabled", func() { + srcIP := utils.IpStrTo16Byte("10.1.1.10") + dstIP := utils.IpStrTo16Byte("10.1.1.20") + + // Create a NetworkSet that would match if enabled + networkSet := &model.NetworkSet{ + Nets: []net.IPNet{ + utils.MustParseNet("10.1.0.0/16"), }, + Labels: uniquelabels.Make(map[string]string{ + "type": "production-network", + "namespace": "production", + }), + } + + nsKey := model.NetworkSetKey{Name: "production-netset"} + nsMap := map[model.NetworkSetKey]*model.NetworkSet{ + nsKey: networkSet, } + lm := newMockLookupsCache(nil, nil, nsMap, nil) + + c = newCollector(lm, &Config{ + EnableNetworkSets: false, // NetworkSets disabled + ExportingInterval: time.Second, + FlowLogsFlushInterval: time.Second, + }).(*collector) + + result := c.lookupNetworkSetWithNamespace(srcIP, dstIP, false, "production") + Expect(result).To(BeNil()) + }) + }) + + // Test endpoint deletion scenario + t.Run("EndpointDeletion", func(t *testing.T) { + // Remove localEd1 from the lookup cache to simulate endpoint deletion + epMapWithoutLocalEd1 := map[[16]byte]calc.EndpointData{ + localIp2: localEd2, + remoteIp1: remoteEd1, + } + lm = newMockLookupsCache(epMapWithoutLocalEd1, nil, nil, nil) + c.luc = lm + + // Make another policy change to trigger evaluation + localWlEp2Proto := calc.ModelWorkloadEndpointToProto(localWlEp2, nil, nil, []*proto.TierInfo{ + {Name: "default", IngressPolicies: []*proto.PolicyID{{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}}, EgressPolicies: []*proto.PolicyID{{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}}}, }) - // Update pending rule traces again + c.policyStoreManager.DoWithLock(func(ps *policystore.PolicyStore) { + ps.Endpoints[convertWorkloadId(localWlEPKey2)] = localWlEp2Proto + }) + c.policyStoreManager.OnInSync() + + // Store original pending rule IDs before update + originalFlow1IngressRules := append([]*calc.RuleID(nil), c.epStats[*flowTuple1].IngressPendingRuleIDs...) + originalFlow1EgressRules := append([]*calc.RuleID(nil), c.epStats[*flowTuple1].EgressPendingRuleIDs...) + + // Trigger update - should skip flow1 since localEd1 is deleted c.updatePendingRuleTraces() - // The pending rule traces should be updated for data1, but not data2 - for _, ruleID := range []struct { - iteration int - pendingRuleIDs []*calc.RuleID - expectedRuleID *calc.RuleID - }{ - {0, data1.IngressPendingRuleIDs, tier1TierPolicy1AllowIngressRuleID}, - {1, data1.EgressPendingRuleIDs, tier1TierPolicy1DenyEgressRuleID}, - {2, data2.IngressPendingRuleIDs, tier1TierPolicy1AllowIngressRuleID}, - {3, data2.EgressPendingRuleIDs, tier1TierPolicy1DenyEgressRuleID}, - } { - Expect(ruleID.pendingRuleIDs).To(HaveLen(1), "Iteration: %s. Expected PendingRuleIDs to be updated", ruleID.iteration) - Expect(ruleID.pendingRuleIDs[0].Name).To(Equal(ruleID.expectedRuleID.Name), "Iteration: %s.Expected policy name to be: %s", ruleID.iteration, ruleID.expectedRuleID.Name) - Expect(ruleID.pendingRuleIDs[0].Tier).To(Equal(ruleID.expectedRuleID.Tier), "Iteration: %s.Expected tier name to be: %s", ruleID.iteration, ruleID.expectedRuleID.Tier) - Expect(ruleID.pendingRuleIDs[0].Namespace).To(Equal(ruleID.expectedRuleID.Namespace), "Iteration: %s.Expected namespace to be: %s", ruleID.iteration, ruleID.expectedRuleID.Namespace) - Expect(ruleID.pendingRuleIDs[0].Action).To(Equal(ruleID.expectedRuleID.Action), "Iteration: %s.Expected action to be: %s", ruleID.iteration, ruleID.expectedRuleID.Action) - Expect(ruleID.pendingRuleIDs[0].Direction).To(Equal(ruleID.expectedRuleID.Direction), "Iteration: %s.Expected direction to be: %s", ruleID.iteration, ruleID.expectedRuleID.Direction) - Expect(ruleID.pendingRuleIDs[0].Index).To(Equal(ruleID.expectedRuleID.Index), "Iteration: %s.Expected index to be: %s", ruleID.iteration, ruleID.expectedRuleID.Index) + currentFlowData1 := c.epStats[*flowTuple1] + currentFlowData2 := c.epStats[*flowTuple2] + + // Verify that flow1 rules remain unchanged (endpoint deleted, so no update) + Expect(currentFlowData1.IngressPendingRuleIDs).To(Equal(originalFlow1IngressRules), + "Flow1 ingress rules should remain unchanged when source endpoint is deleted") + Expect(currentFlowData1.EgressPendingRuleIDs).To(Equal(originalFlow1EgressRules), + "Flow1 egress rules should remain unchanged when source endpoint is deleted") + + // Verify that flow2 ingress rules remain empty + Expect(currentFlowData2.IngressPendingRuleIDs).To(HaveLen(0), + "Flow2 ingress rules should remain empty as destination endpoint has no policies") + // Verify that flow2 rules are still updated (both endpoints exist) + Expect(currentFlowData2.EgressPendingRuleIDs).To(HaveLen(1), + "Flow2 egress rules should still be updated when both endpoints exist") + if len(currentFlowData2.EgressPendingRuleIDs) == 1 { + validateRuleID(t, currentFlowData2.EgressPendingRuleIDs[0], defTierPolicy1AllowEgressRuleID, "Flow2 Egress After Endpoint Deletion") } }) +} +// Helper function to validate rule ID fields +func validateRuleID(t *testing.T, actual, expected *calc.RuleID, context string) { + Expect(actual.Name).To(Equal(expected.Name), "Policy name mismatch in %s", context) + Expect(actual.Tier).To(Equal(expected.Tier), "Tier name mismatch in %s", context) + Expect(actual.Namespace).To(Equal(expected.Namespace), "Namespace mismatch in %s", context) + Expect(actual.Action).To(Equal(expected.Action), "Action mismatch in %s", context) + Expect(actual.Direction).To(Equal(expected.Direction), "Direction mismatch in %s", context) + Expect(actual.Index).To(Equal(expected.Index), "Index mismatch in %s", context) } func TestEqualFunction(t *testing.T) { @@ -2238,7 +3791,7 @@ func TestEqualFunction(t *testing.T) { t.Run("should return true for equal rule IDs", func(t *testing.T) { ruleID1 := &calc.RuleID{ PolicyID: calc.PolicyID{ - Tier: "default", + Kind: v3.KindGlobalNetworkPolicy, Name: "policy1", Namespace: "", }, @@ -2249,7 +3802,7 @@ func TestEqualFunction(t *testing.T) { } ruleID2 := &calc.RuleID{ PolicyID: calc.PolicyID{ - Tier: "default", + Kind: v3.KindGlobalNetworkPolicy, Name: "policy1", Namespace: "", }, @@ -2260,7 +3813,7 @@ func TestEqualFunction(t *testing.T) { } ruleID3 := &calc.RuleID{ PolicyID: calc.PolicyID{ - Tier: "default", + Kind: v3.KindGlobalNetworkPolicy, Name: "policy2", Namespace: "", }, @@ -2271,7 +3824,7 @@ func TestEqualFunction(t *testing.T) { } ruleID4 := &calc.RuleID{ PolicyID: calc.PolicyID{ - Tier: "default", + Kind: v3.KindGlobalNetworkPolicy, Name: "policy2", Namespace: "", }, @@ -2287,7 +3840,7 @@ func TestEqualFunction(t *testing.T) { t.Run("should return false for rule IDs that contain the same elements but are out of order", func(t *testing.T) { ruleID1 := &calc.RuleID{ PolicyID: calc.PolicyID{ - Tier: "default", + Kind: v3.KindGlobalNetworkPolicy, Name: "policy1", Namespace: "", }, @@ -2298,7 +3851,7 @@ func TestEqualFunction(t *testing.T) { } ruleID2 := &calc.RuleID{ PolicyID: calc.PolicyID{ - Tier: "default", + Kind: v3.KindGlobalNetworkPolicy, Name: "policy1", Namespace: "", }, @@ -2309,7 +3862,7 @@ func TestEqualFunction(t *testing.T) { } ruleID3 := &calc.RuleID{ PolicyID: calc.PolicyID{ - Tier: "default", + Kind: v3.KindGlobalNetworkPolicy, Name: "policy1", Namespace: "", }, @@ -2320,7 +3873,7 @@ func TestEqualFunction(t *testing.T) { } ruleID4 := &calc.RuleID{ PolicyID: calc.PolicyID{ - Tier: "default", + Kind: v3.KindGlobalNetworkPolicy, Name: "policy1", Namespace: "", }, @@ -2336,7 +3889,7 @@ func TestEqualFunction(t *testing.T) { t.Run("should return false for different lengths of rule IDs", func(t *testing.T) { ruleID1 := &calc.RuleID{ PolicyID: calc.PolicyID{ - Tier: "default", + Kind: v3.KindGlobalNetworkPolicy, Name: "policy1", Namespace: "", }, @@ -2347,7 +3900,7 @@ func TestEqualFunction(t *testing.T) { } ruleID2 := &calc.RuleID{ PolicyID: calc.PolicyID{ - Tier: "default", + Kind: v3.KindGlobalNetworkPolicy, Name: "policy1", Namespace: "", }, @@ -2358,7 +3911,7 @@ func TestEqualFunction(t *testing.T) { } ruleID3 := &calc.RuleID{ PolicyID: calc.PolicyID{ - Tier: "default", + Kind: v3.KindGlobalNetworkPolicy, Name: "policy1", Namespace: "", }, diff --git a/felix/collector/dataplane_info_reader.go b/felix/collector/dataplane_info_reader.go index 600e7c0de37..8fe40eb57f2 100644 --- a/felix/collector/dataplane_info_reader.go +++ b/felix/collector/dataplane_info_reader.go @@ -26,7 +26,7 @@ import ( // dataplaneInfoReader reads dataplane information. type dataplaneInfoReader struct { dataplaneInfoC chan *proto.ToDataplane - infoC chan interface{} + infoC chan any seqNo uint64 stopC chan struct{} @@ -35,7 +35,7 @@ type dataplaneInfoReader struct { } // NewDataplaneInfoReader returns a new DataplaneInfoReader -func NewDataplaneInfoReader(c chan interface{}) *dataplaneInfoReader { +func NewDataplaneInfoReader(c chan any) *dataplaneInfoReader { return &dataplaneInfoReader{ stopC: make(chan struct{}), dataplaneInfoC: make(chan *proto.ToDataplane, 1000), @@ -46,11 +46,9 @@ func NewDataplaneInfoReader(c chan interface{}) *dataplaneInfoReader { // Start starts the reader. func (r *dataplaneInfoReader) Start() error { log.Info("Start dataplane info reader") - r.wg.Add(1) - go func() { - defer r.wg.Done() + r.wg.Go(func() { r.run() - }() + }) return nil } diff --git a/felix/collector/dataplane_info_reader_test.go b/felix/collector/dataplane_info_reader_test.go index 79f76810730..3830ee92731 100644 --- a/felix/collector/dataplane_info_reader_test.go +++ b/felix/collector/dataplane_info_reader_test.go @@ -26,7 +26,7 @@ import ( func TestRun(t *testing.T) { g := NewGomegaWithT(t) - infoC := make(chan interface{}) + infoC := make(chan any) dpr := NewDataplaneInfoReader(infoC) // Start the reader diff --git a/felix/collector/flowlog/aggregator.go b/felix/collector/flowlog/aggregator.go index c3b65eab66e..de0dd1f485b 100644 --- a/felix/collector/flowlog/aggregator.go +++ b/felix/collector/flowlog/aggregator.go @@ -46,13 +46,11 @@ const ( MinAggregationLevel = FlowDefault ) -var ( - gaugeFlowStoreCacheSizeLength = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Name: "felix_collector_allowed_flowlog_aggregator_store", - Help: "Total number of FlowEntries with a given action currently residing in the FlowStore cache used by the aggregator.", - }, - []string{"action"}) -) +var gaugeFlowStoreCacheSizeLength = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: "felix_collector_allowed_flowlog_aggregator_store", + Help: "Total number of FlowEntries with a given action currently residing in the FlowStore cache used by the aggregator.", +}, + []string{"action"}) func init() { prometheus.MustRegister(gaugeFlowStoreCacheSizeLength) diff --git a/felix/collector/flowlog/aggregator_test.go b/felix/collector/flowlog/aggregator_test.go index d83bd7dd6f6..485e160edb9 100644 --- a/felix/collector/flowlog/aggregator_test.go +++ b/felix/collector/flowlog/aggregator_test.go @@ -17,8 +17,9 @@ package flowlog import ( "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/calico/felix/calc" "github.com/projectcalico/calico/felix/collector/types/endpoint" @@ -65,6 +66,7 @@ var ( publicIP1Str = "1.0.0.1" publicIP2Str = "2.0.0.2" ingressRule1Allow = calc.NewRuleID( + v3.KindGlobalNetworkPolicy, "default", "policy1", "", @@ -73,6 +75,7 @@ var ( rules.RuleActionAllow, ) egressRule2Deny = calc.NewRuleID( + v3.KindGlobalNetworkPolicy, "default", "policy2", "", @@ -199,14 +202,13 @@ func checkProcessArgs(actual, expected []string, numArgs int) bool { if actualArgSet.Len() != numArgs { return false } - actualArgSet.Iter(func(arg string) error { + for arg := range actualArgSet.All() { for _, e := range expected { if arg == e { count = count + 1 } } - return nil - }) + } return count == numArgs } @@ -254,7 +256,6 @@ var _ = Describe("Flow log aggregator tests", func() { BeforeEach(func() { ca = NewAggregator() - }) It("aggregates the fed metric updates", func() { @@ -336,7 +337,6 @@ var _ = Describe("Flow log aggregator tests", func() { By("by endpoint IP classification as the meta name when meta info is missing") ca = NewAggregator() endpointMeta := calc.RemoteEndpointData{ - CommonEndpointData: calc.CalculateCommonEndpointData( model.WorkloadEndpointKey{ Hostname: "node-01", @@ -488,7 +488,6 @@ var _ = Describe("Flow log aggregator tests", func() { muNoConn1Rule1AllowUpdateWithEndpointMetaCopy := muNoConn1Rule1AllowUpdateWithEndpointMeta // Updating the Workload IDs for src and dst. muNoConn1Rule1AllowUpdateWithEndpointMetaCopy.SrcEp = &calc.RemoteEndpointData{ - CommonEndpointData: calc.CalculateCommonEndpointData( model.WorkloadEndpointKey{ Hostname: "node-01", @@ -504,7 +503,6 @@ var _ = Describe("Flow log aggregator tests", func() { } muNoConn1Rule1AllowUpdateWithEndpointMetaCopy.DstEp = &calc.RemoteEndpointData{ - CommonEndpointData: calc.CalculateCommonEndpointData( model.WorkloadEndpointKey{ Hostname: "node-02", @@ -726,7 +724,6 @@ var _ = Describe("Flow log aggregator tests", func() { // Updating the Workload IDs for src and dst. muNoConn1Rule1AllowUpdateWithEndpointMetaCopy.SrcEp = &calc.RemoteEndpointData{ - CommonEndpointData: calc.CalculateCommonEndpointData( model.WorkloadEndpointKey{ Hostname: "node-01", diff --git a/felix/collector/flowlog/fixtures_test.go b/felix/collector/flowlog/fixtures_test.go index 706981057bb..dddf59b0a8d 100644 --- a/felix/collector/flowlog/fixtures_test.go +++ b/felix/collector/flowlog/fixtures_test.go @@ -15,6 +15,7 @@ package flowlog import ( + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "k8s.io/apimachinery/pkg/types" "k8s.io/kubernetes/pkg/proxy" @@ -50,13 +51,20 @@ var ( Ingress: &calc.MatchData{ PolicyMatches: map[calc.PolicyID]int{ - calc.PolicyID{Name: "policy1", Tier: "default"}: 0, - calc.PolicyID{Name: "policy2", Tier: "default"}: 0, + {Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}: 0, + {Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}: 0, }, TierData: map[string]*calc.TierData{ "default": { - TierDefaultActionRuleID: calc.NewRuleID("default", "policy2", "", calc.RuleIndexTierDefaultAction, - rules.RuleDirIngress, rules.RuleActionDeny), + TierDefaultActionRuleID: calc.NewRuleID( + v3.KindGlobalNetworkPolicy, + "default", + "policy2", + "", + calc.RuleIndexTierDefaultAction, + rules.RuleDirIngress, + rules.RuleActionDeny, + ), EndOfTierMatchIndex: 0, }, }, @@ -64,13 +72,20 @@ var ( }, Egress: &calc.MatchData{ PolicyMatches: map[calc.PolicyID]int{ - calc.PolicyID{Name: "policy1", Tier: "default"}: 0, - calc.PolicyID{Name: "policy2", Tier: "default"}: 0, + {Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}: 0, + {Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}: 0, }, TierData: map[string]*calc.TierData{ "default": { - TierDefaultActionRuleID: calc.NewRuleID("default", "policy2", "", calc.RuleIndexTierDefaultAction, - rules.RuleDirIngress, rules.RuleActionDeny), + TierDefaultActionRuleID: calc.NewRuleID( + v3.KindGlobalNetworkPolicy, + "default", + "policy2", + "", + calc.RuleIndexTierDefaultAction, + rules.RuleDirIngress, + rules.RuleActionDeny, + ), EndOfTierMatchIndex: 0, }, }, @@ -669,7 +684,6 @@ var ( DstEp: nil, UnknownRuleID: &calc.RuleID{ PolicyID: calc.PolicyID{ - Tier: "__UNKNOWN__", Name: "__UNKNOWN__", Namespace: "__UNKNOWN__", }, diff --git a/felix/collector/flowlog/flowlogs_suite_test.go b/felix/collector/flowlog/flowlogs_suite_test.go index 655aaa4a3d0..9d4c1092164 100644 --- a/felix/collector/flowlog/flowlogs_suite_test.go +++ b/felix/collector/flowlog/flowlogs_suite_test.go @@ -17,9 +17,8 @@ package flowlog import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/sirupsen/logrus" "github.com/projectcalico/calico/libcalico-go/lib/logutils" @@ -33,7 +32,8 @@ func init() { } func TestFlowlogs(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/flowlogs_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Flow logs Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/felix_collector_flowlog_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/collector/flowlog", suiteConfig, reporterConfig) } diff --git a/felix/collector/flowlog/reporter.go b/felix/collector/flowlog/reporter.go index 35172dad571..71507cd74dc 100644 --- a/felix/collector/flowlog/reporter.go +++ b/felix/collector/flowlog/reporter.go @@ -148,7 +148,7 @@ func (r *FlowLogReporter) Start() error { return nil } -func (r *FlowLogReporter) Report(u interface{}) error { +func (r *FlowLogReporter) Report(u any) error { mu, ok := u.(metric.Update) if !ok { return fmt.Errorf("invalid metric update") diff --git a/felix/collector/flowlog/reporter_test.go b/felix/collector/flowlog/reporter_test.go index a26fa007a15..ab5909b0d6d 100644 --- a/felix/collector/flowlog/reporter_test.go +++ b/felix/collector/flowlog/reporter_test.go @@ -20,7 +20,7 @@ import ( "sync/atomic" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" "github.com/stretchr/testify/mock" @@ -70,7 +70,7 @@ func (d *testFlowLogReporter) Start() error { return nil } -func (d *testFlowLogReporter) Report(logSlice interface{}) error { +func (d *testFlowLogReporter) Report(logSlice any) error { d.mutex.Lock() defer d.mutex.Unlock() @@ -239,7 +239,7 @@ func (m *mockDispatcher) Start() error { return nil } -func (m *mockDispatcher) Report(logSlice interface{}) error { +func (m *mockDispatcher) Report(logSlice any) error { m.iteration++ log.Infof("Mocked dispatcher was called %d times ", m.iteration) logs := logSlice.([]*FlowLog) diff --git a/felix/collector/flowlog/types.go b/felix/collector/flowlog/types.go index 586ad317d68..1df13acea5c 100644 --- a/felix/collector/flowlog/types.go +++ b/felix/collector/flowlog/types.go @@ -292,7 +292,7 @@ func newPolicySet(ruleIDs []*calc.RuleID, includeStaged bool) FlowPolicySet { continue } if !includeStaged { - if model.PolicyIsStaged(rid.Name) { + if model.KindIsStaged(rid.Kind) { nsp++ continue } diff --git a/felix/collector/flowlog/types_test.go b/felix/collector/flowlog/types_test.go index f2c5151a01e..f293bd7fff9 100644 --- a/felix/collector/flowlog/types_test.go +++ b/felix/collector/flowlog/types_test.go @@ -15,9 +15,9 @@ package flowlog import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/calico/felix/calc" "github.com/projectcalico/calico/felix/collector/types/metric" @@ -74,10 +74,10 @@ func setEgressTraceAndMetrics(mu metric.Update, egress, pendingEgress []*calc.Ru var _ = Describe("FlowPolicySets", func() { var ca *Aggregator - egress1Staged := calc.NewRuleID("tier1", "staged:policy1", "namespace1", 0, rules.RuleDirEgress, rules.RuleActionAllow) - egress2 := calc.NewRuleID("tier2", "policy2", "namespace2", 1, rules.RuleDirEgress, rules.RuleActionAllow) - egress3 := calc.NewRuleID("tier3", "policy3", "namespace3", 3, rules.RuleDirEgress, rules.RuleActionAllow) - egress4 := calc.NewRuleID("tier4", "policy4", "namespace4", 1, rules.RuleDirEgress, rules.RuleActionAllow) + egress1Staged := calc.NewRuleID(v3.KindStagedNetworkPolicy, "tier1", "policy1", "namespace1", 0, rules.RuleDirEgress, rules.RuleActionAllow) + egress2 := calc.NewRuleID(v3.KindNetworkPolicy, "tier2", "policy2", "namespace2", 1, rules.RuleDirEgress, rules.RuleActionAllow) + egress3 := calc.NewRuleID(v3.KindNetworkPolicy, "tier3", "policy3", "namespace3", 3, rules.RuleDirEgress, rules.RuleActionAllow) + egress4 := calc.NewRuleID(v3.KindNetworkPolicy, "tier4", "policy4", "namespace4", 1, rules.RuleDirEgress, rules.RuleActionAllow) BeforeEach(func() { ca = NewAggregator() @@ -85,7 +85,6 @@ var _ = Describe("FlowPolicySets", func() { DescribeTable("splits up FlowStore into multiple FlowLogs for multiple items in the FlowPolicySets", func(mus []*metric.Update, aggregation AggregationKind, expected TraceAndMetrics) { - //ca.AggregateOver(aggregation) ca.IncludePolicies(true) for _, mu := range mus { Expect(ca.FeedUpdate(mu)).NotTo(HaveOccurred()) @@ -95,7 +94,7 @@ var _ = Describe("FlowPolicySets", func() { // Validate Expect(len(flowlogs)).Should(Equal(len(expected.Traces))) - for i := 0; i < len(flowlogs); i++ { + for i := range flowlogs { Expect(flowlogs[i].FlowEnforcedPolicySet).Should(Equal(expected.EnforcedTraces[i])) Expect(flowlogs[i].FlowPendingPolicySet).Should(Equal(expected.PendingTrace)) Expect(flowlogs[i].FlowProcessReportedStats.PacketsOut).Should(Equal(expected.Packets)) @@ -113,16 +112,16 @@ var _ = Describe("FlowPolicySets", func() { FlowDefault, TraceAndMetrics{ Traces: []FlowPolicySet{ - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "2|tier3|namespace3/tier3.policy3|allow|3": emptyValue, "3|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "2|tier4|namespace4/tier4.policy4|allow|1": emptyValue, "3|tier3|namespace3/tier3.policy3|allow|3": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier2|np:namespace2/policy2|allow|1": emptyValue, "2|tier3|np:namespace3/policy3|allow|3": emptyValue, "3|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier2|np:namespace2/policy2|allow|1": emptyValue, "2|tier4|np:namespace4/policy4|allow|1": emptyValue, "3|tier3|np:namespace3/policy3|allow|3": emptyValue}, }, EnforcedTraces: []FlowPolicySet{ - {"0|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "1|tier3|namespace3/tier3.policy3|allow|3": emptyValue, "2|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "1|tier4|namespace4/tier4.policy4|allow|1": emptyValue, "2|tier3|namespace3/tier3.policy3|allow|3": emptyValue}, + {"0|tier2|np:namespace2/policy2|allow|1": emptyValue, "1|tier3|np:namespace3/policy3|allow|3": emptyValue, "2|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier2|np:namespace2/policy2|allow|1": emptyValue, "1|tier4|np:namespace4/policy4|allow|1": emptyValue, "2|tier3|np:namespace3/policy3|allow|3": emptyValue}, }, - PendingTrace: FlowPolicySet{"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue}, + PendingTrace: FlowPolicySet{"0|tier1|snp:namespace1/policy1|allow|0": emptyValue}, Packets: 21, Bytes: 246, }, @@ -138,16 +137,16 @@ var _ = Describe("FlowPolicySets", func() { FlowSourcePort, TraceAndMetrics{ Traces: []FlowPolicySet{ - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "2|tier3|namespace3/tier3.policy3|allow|3": emptyValue, "3|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "2|tier4|namespace4/tier4.policy4|allow|1": emptyValue, "3|tier3|namespace3/tier3.policy3|allow|3": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier2|np:namespace2/policy2|allow|1": emptyValue, "2|tier3|np:namespace3/policy3|allow|3": emptyValue, "3|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier2|np:namespace2/policy2|allow|1": emptyValue, "2|tier4|np:namespace4/policy4|allow|1": emptyValue, "3|tier3|np:namespace3/policy3|allow|3": emptyValue}, }, EnforcedTraces: []FlowPolicySet{ - {"0|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "1|tier3|namespace3/tier3.policy3|allow|3": emptyValue, "2|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "1|tier4|namespace4/tier4.policy4|allow|1": emptyValue, "2|tier3|namespace3/tier3.policy3|allow|3": emptyValue}, + {"0|tier2|np:namespace2/policy2|allow|1": emptyValue, "1|tier3|np:namespace3/policy3|allow|3": emptyValue, "2|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier2|np:namespace2/policy2|allow|1": emptyValue, "1|tier4|np:namespace4/policy4|allow|1": emptyValue, "2|tier3|np:namespace3/policy3|allow|3": emptyValue}, }, - PendingTrace: FlowPolicySet{"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue}, + PendingTrace: FlowPolicySet{"0|tier1|snp:namespace1/policy1|allow|0": emptyValue}, Packets: 21, Bytes: 246, }, @@ -163,16 +162,16 @@ var _ = Describe("FlowPolicySets", func() { FlowPrefixName, TraceAndMetrics{ Traces: []FlowPolicySet{ - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "2|tier3|namespace3/tier3.policy3|allow|3": emptyValue, "3|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "2|tier4|namespace4/tier4.policy4|allow|1": emptyValue, "3|tier3|namespace3/tier3.policy3|allow|3": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier2|np:namespace2/policy2|allow|1": emptyValue, "2|tier3|np:namespace3/policy3|allow|3": emptyValue, "3|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier2|np:namespace2/policy2|allow|1": emptyValue, "2|tier4|np:namespace4/policy4|allow|1": emptyValue, "3|tier3|np:namespace3/policy3|allow|3": emptyValue}, }, EnforcedTraces: []FlowPolicySet{ - {"0|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "1|tier3|namespace3/tier3.policy3|allow|3": emptyValue, "2|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "1|tier4|namespace4/tier4.policy4|allow|1": emptyValue, "2|tier3|namespace3/tier3.policy3|allow|3": emptyValue}, + {"0|tier2|np:namespace2/policy2|allow|1": emptyValue, "1|tier3|np:namespace3/policy3|allow|3": emptyValue, "2|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier2|np:namespace2/policy2|allow|1": emptyValue, "1|tier4|np:namespace4/policy4|allow|1": emptyValue, "2|tier3|np:namespace3/policy3|allow|3": emptyValue}, }, - PendingTrace: FlowPolicySet{"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue}, + PendingTrace: FlowPolicySet{"0|tier1|snp:namespace1/policy1|allow|0": emptyValue}, Packets: 21, Bytes: 246, }, @@ -188,16 +187,16 @@ var _ = Describe("FlowPolicySets", func() { FlowNoDestPorts, TraceAndMetrics{ Traces: []FlowPolicySet{ - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "2|tier3|namespace3/tier3.policy3|allow|3": emptyValue, "3|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "2|tier4|namespace4/tier4.policy4|allow|1": emptyValue, "3|tier3|namespace3/tier3.policy3|allow|3": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier2|np:namespace2/policy2|allow|1": emptyValue, "2|tier3|np:namespace3/policy3|allow|3": emptyValue, "3|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier2|np:namespace2/policy2|allow|1": emptyValue, "2|tier4|np:namespace4/policy4|allow|1": emptyValue, "3|tier3|np:namespace3/policy3|allow|3": emptyValue}, }, EnforcedTraces: []FlowPolicySet{ - {"0|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "1|tier3|namespace3/tier3.policy3|allow|3": emptyValue, "2|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "1|tier4|namespace4/tier4.policy4|allow|1": emptyValue, "2|tier3|namespace3/tier3.policy3|allow|3": emptyValue}, + {"0|tier2|np:namespace2/policy2|allow|1": emptyValue, "1|tier3|np:namespace3/policy3|allow|3": emptyValue, "2|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier2|np:namespace2/policy2|allow|1": emptyValue, "1|tier4|np:namespace4/policy4|allow|1": emptyValue, "2|tier3|np:namespace3/policy3|allow|3": emptyValue}, }, - PendingTrace: FlowPolicySet{"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue}, + PendingTrace: FlowPolicySet{"0|tier1|snp:namespace1/policy1|allow|0": emptyValue}, Packets: 21, Bytes: 246, }, @@ -223,16 +222,16 @@ var _ = Describe("FlowPolicySets", func() { FlowDefault, TraceAndMetrics{ Traces: []FlowPolicySet{ - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "2|tier3|namespace3/tier3.policy3|allow|3": emptyValue, "3|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "2|tier4|namespace4/tier4.policy4|allow|1": emptyValue, "3|tier3|namespace3/tier3.policy3|allow|3": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier2|np:namespace2/policy2|allow|1": emptyValue, "2|tier3|np:namespace3/policy3|allow|3": emptyValue, "3|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier2|np:namespace2/policy2|allow|1": emptyValue, "2|tier4|np:namespace4/policy4|allow|1": emptyValue, "3|tier3|np:namespace3/policy3|allow|3": emptyValue}, }, EnforcedTraces: []FlowPolicySet{ - {"0|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "1|tier3|namespace3/tier3.policy3|allow|3": emptyValue, "2|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "1|tier4|namespace4/tier4.policy4|allow|1": emptyValue, "2|tier3|namespace3/tier3.policy3|allow|3": emptyValue}, + {"0|tier2|np:namespace2/policy2|allow|1": emptyValue, "1|tier3|np:namespace3/policy3|allow|3": emptyValue, "2|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier2|np:namespace2/policy2|allow|1": emptyValue, "1|tier4|np:namespace4/policy4|allow|1": emptyValue, "2|tier3|np:namespace3/policy3|allow|3": emptyValue}, }, - PendingTrace: FlowPolicySet{"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue}, + PendingTrace: FlowPolicySet{"0|tier1|snp:namespace1/policy1|allow|0": emptyValue}, Packets: 93, Bytes: 980, }, @@ -249,16 +248,16 @@ var _ = Describe("FlowPolicySets", func() { FlowDefault, TraceAndMetrics{ Traces: []FlowPolicySet{ - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "2|tier3|namespace3/tier3.policy3|allow|3": emptyValue, "3|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "2|tier4|namespace4/tier4.policy4|allow|1": emptyValue, "3|tier3|namespace3/tier3.policy3|allow|3": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier2|np:namespace2/policy2|allow|1": emptyValue, "2|tier3|np:namespace3/policy3|allow|3": emptyValue, "3|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier2|np:namespace2/policy2|allow|1": emptyValue, "2|tier4|np:namespace4/policy4|allow|1": emptyValue, "3|tier3|np:namespace3/policy3|allow|3": emptyValue}, }, EnforcedTraces: []FlowPolicySet{ - {"0|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "1|tier3|namespace3/tier3.policy3|allow|3": emptyValue, "2|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "1|tier4|namespace4/tier4.policy4|allow|1": emptyValue, "2|tier3|namespace3/tier3.policy3|allow|3": emptyValue}, + {"0|tier2|np:namespace2/policy2|allow|1": emptyValue, "1|tier3|np:namespace3/policy3|allow|3": emptyValue, "2|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier2|np:namespace2/policy2|allow|1": emptyValue, "1|tier4|np:namespace4/policy4|allow|1": emptyValue, "2|tier3|np:namespace3/policy3|allow|3": emptyValue}, }, - PendingTrace: FlowPolicySet{"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue}, + PendingTrace: FlowPolicySet{"0|tier1|snp:namespace1/policy1|allow|0": emptyValue}, Packets: 36, Bytes: 372, }, @@ -275,16 +274,16 @@ var _ = Describe("FlowPolicySets", func() { FlowDefault, TraceAndMetrics{ Traces: []FlowPolicySet{ - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "2|tier3|namespace3/tier3.policy3|allow|3": emptyValue, "3|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "2|tier4|namespace4/tier4.policy4|allow|1": emptyValue, "3|tier3|namespace3/tier3.policy3|allow|3": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier2|np:namespace2/policy2|allow|1": emptyValue, "2|tier3|np:namespace3/policy3|allow|3": emptyValue, "3|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier2|np:namespace2/policy2|allow|1": emptyValue, "2|tier4|np:namespace4/policy4|allow|1": emptyValue, "3|tier3|np:namespace3/policy3|allow|3": emptyValue}, }, EnforcedTraces: []FlowPolicySet{ - {"0|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "1|tier3|namespace3/tier3.policy3|allow|3": emptyValue, "2|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "1|tier4|namespace4/tier4.policy4|allow|1": emptyValue, "2|tier3|namespace3/tier3.policy3|allow|3": emptyValue}, + {"0|tier2|np:namespace2/policy2|allow|1": emptyValue, "1|tier3|np:namespace3/policy3|allow|3": emptyValue, "2|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier2|np:namespace2/policy2|allow|1": emptyValue, "1|tier4|np:namespace4/policy4|allow|1": emptyValue, "2|tier3|np:namespace3/policy3|allow|3": emptyValue}, }, - PendingTrace: FlowPolicySet{"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue}, + PendingTrace: FlowPolicySet{"0|tier1|snp:namespace1/policy1|allow|0": emptyValue}, Packets: 36, Bytes: 372, }, @@ -303,16 +302,16 @@ var _ = Describe("FlowPolicySets", func() { FlowDefault, TraceAndMetrics{ Traces: []FlowPolicySet{ - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "2|tier3|namespace3/tier3.policy3|allow|3": emptyValue, "3|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "2|tier4|namespace4/tier4.policy4|allow|1": emptyValue, "3|tier3|namespace3/tier3.policy3|allow|3": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier2|np:namespace2/policy2|allow|1": emptyValue, "2|tier3|np:namespace3/policy3|allow|3": emptyValue, "3|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier2|np:namespace2/policy2|allow|1": emptyValue, "2|tier4|np:namespace4/policy4|allow|1": emptyValue, "3|tier3|np:namespace3/policy3|allow|3": emptyValue}, }, EnforcedTraces: []FlowPolicySet{ - {"0|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "1|tier3|namespace3/tier3.policy3|allow|3": emptyValue, "2|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "1|tier4|namespace4/tier4.policy4|allow|1": emptyValue, "2|tier3|namespace3/tier3.policy3|allow|3": emptyValue}, + {"0|tier2|np:namespace2/policy2|allow|1": emptyValue, "1|tier3|np:namespace3/policy3|allow|3": emptyValue, "2|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier2|np:namespace2/policy2|allow|1": emptyValue, "1|tier4|np:namespace4/policy4|allow|1": emptyValue, "2|tier3|np:namespace3/policy3|allow|3": emptyValue}, }, - PendingTrace: FlowPolicySet{"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue}, + PendingTrace: FlowPolicySet{"0|tier1|snp:namespace1/policy1|allow|0": emptyValue}, Packets: 48, Bytes: 474, }, @@ -332,16 +331,16 @@ var _ = Describe("FlowPolicySets", func() { FlowDefault, TraceAndMetrics{ Traces: []FlowPolicySet{ - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "2|tier3|namespace3/tier3.policy3|allow|3": emptyValue, "3|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue, "1|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "2|tier4|namespace4/tier4.policy4|allow|1": emptyValue, "3|tier3|namespace3/tier3.policy3|allow|3": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier2|np:namespace2/policy2|allow|1": emptyValue, "2|tier3|np:namespace3/policy3|allow|3": emptyValue, "3|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier1|snp:namespace1/policy1|allow|0": emptyValue, "1|tier2|np:namespace2/policy2|allow|1": emptyValue, "2|tier4|np:namespace4/policy4|allow|1": emptyValue, "3|tier3|np:namespace3/policy3|allow|3": emptyValue}, }, EnforcedTraces: []FlowPolicySet{ - {"0|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "1|tier3|namespace3/tier3.policy3|allow|3": emptyValue, "2|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier4|namespace4/tier4.policy4|allow|1": emptyValue}, - {"0|tier2|namespace2/tier2.policy2|allow|1": emptyValue, "1|tier4|namespace4/tier4.policy4|allow|1": emptyValue, "2|tier3|namespace3/tier3.policy3|allow|3": emptyValue}, + {"0|tier2|np:namespace2/policy2|allow|1": emptyValue, "1|tier3|np:namespace3/policy3|allow|3": emptyValue, "2|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier4|np:namespace4/policy4|allow|1": emptyValue}, + {"0|tier2|np:namespace2/policy2|allow|1": emptyValue, "1|tier4|np:namespace4/policy4|allow|1": emptyValue, "2|tier3|np:namespace3/policy3|allow|3": emptyValue}, }, - PendingTrace: FlowPolicySet{"0|tier1|namespace1/tier1.staged:policy1|allow|0": emptyValue}, + PendingTrace: FlowPolicySet{"0|tier1|snp:namespace1/policy1|allow|0": emptyValue}, Packets: 48, Bytes: 524, }, diff --git a/felix/collector/flowlog/utils_test.go b/felix/collector/flowlog/utils_test.go index 1bcbc4d0916..b0ad649bd4e 100644 --- a/felix/collector/flowlog/utils_test.go +++ b/felix/collector/flowlog/utils_test.go @@ -15,7 +15,7 @@ package flowlog import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/felix/collector/goldmane/client.go b/felix/collector/goldmane/client.go index 8a667d14398..5bedb12b06e 100644 --- a/felix/collector/goldmane/client.go +++ b/felix/collector/goldmane/client.go @@ -272,6 +272,12 @@ func toFlowPolicySet(policies []*proto.PolicyHit) flowlog.FlowPolicySet { if s, err := pol.ToString(); err != nil { logrus.WithError(err).WithField("policy", pol).Error("Failed to convert policy hit to string") } else { + if logrus.IsLevelEnabled(logrus.DebugLevel) { + logrus.WithFields(logrus.Fields{ + "policyHit": pol, + "policy": s, + }).Debug("Converted policy hit to string") + } policySet[s] = struct{}{} } } diff --git a/felix/collector/iptables.go b/felix/collector/iptables.go index 161acf42c3f..488c614c875 100644 --- a/felix/collector/iptables.go +++ b/felix/collector/iptables.go @@ -1,5 +1,4 @@ //go:build linux -// +build linux // Copyright (c) 2018-2025 Tigera, Inc. All rights reserved. // @@ -76,11 +75,9 @@ func (r *NFLogReader) Start() error { return nil } - r.wg.Add(1) - go func() { - defer r.wg.Done() + r.wg.Go(func() { r.run() - }() + }) return nil } @@ -240,11 +237,9 @@ func NewNetLinkConntrackReader(period time.Duration) *NetLinkConntrackReader { } func (r *NetLinkConntrackReader) Start() error { - r.wg.Add(1) - go func() { - defer r.wg.Done() + r.wg.Go(func() { r.run() - }() + }) return nil } diff --git a/felix/collector/stats.go b/felix/collector/stats.go index 1180dc2c962..027a9a44115 100644 --- a/felix/collector/stats.go +++ b/felix/collector/stats.go @@ -133,7 +133,7 @@ func (t *RuleTrace) Path() []*calc.RuleID { return t.verdictIdx } - if model.PolicyIsStaged(r.Name) { + if model.KindIsStaged(r.Kind) { // This is a staged policy. If the rule is an implicitly applied the tier action then we only include it if the end-of-tier // pass action has also been hit. if r.IsTierDefaultActionRule() { @@ -254,7 +254,7 @@ func (t *RuleTrace) addRuleID(rid *calc.RuleID, matchIdx, numPkts, numBytes int) // Set as dirty and increment the match revision number for this tier. t.dirty = true - if !model.PolicyIsStaged(rid.Name) && rid.Action != rules.RuleActionPass { + if !model.KindIsStaged(rid.Kind) && rid.Action != rules.RuleActionPass { // This is a verdict action, so increment counters and set our verdict index. t.pktsCtr.Increase(numPkts) t.bytesCtr.Increase(numBytes) @@ -282,7 +282,7 @@ func (t *RuleTrace) replaceRuleID(rid *calc.RuleID, matchIdx, numPkts, numBytes // Reset the reporting path so that we recalculate it next report. t.rulesToReport = nil - if !model.PolicyIsStaged(rid.Name) && rid.Action != rules.RuleActionPass { + if !model.KindIsStaged(rid.Name) && rid.Action != rules.RuleActionPass { // This is a verdict action, so reset and set counters and set our verdict index. t.pktsCtr.ResetAndSet(numPkts) t.bytesCtr.ResetAndSet(numBytes) @@ -507,7 +507,6 @@ func (d *Data) VerdictFound() bool { // for local flows we require egress or ingress verdicts based on the whether source or destination is local return (!srcIsLocal || d.EgressRuleTrace.FoundVerdict()) && (!dstIsLocal || d.IngressRuleTrace.FoundVerdict()) } - } // Set In Counters' values to packets and bytes. Use the SetConntrackCounters* methods @@ -634,7 +633,6 @@ func (d *Data) MetricUpdateEgressConn(ut metric.UpdateType) metric.Update { }, } return metricUpdate - } // metricUpdateIngressNoConn creates a metric update for Inbound non-connection traffic @@ -661,7 +659,6 @@ func (d *Data) MetricUpdateIngressNoConn(ut metric.UpdateType) metric.Update { }, } return metricUpdate - } // metricUpdateEgressNoConn creates a metric update for Outbound non-connection traffic diff --git a/felix/collector/stats_test.go b/felix/collector/stats_test.go index 07a4fc7270d..ffca238128b 100644 --- a/felix/collector/stats_test.go +++ b/felix/collector/stats_test.go @@ -17,8 +17,9 @@ package collector_test import ( "net" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/calico/felix/calc" "github.com/projectcalico/calico/felix/collector" @@ -33,8 +34,9 @@ var ( IndexStr: "1", PolicyID: calc.PolicyID{ Name: "P1", - Tier: "T1", + Kind: v3.KindGlobalNetworkPolicy, }, + Tier: "T1", Direction: rules.RuleDirIngress, } denyIngressRid0 = &calc.RuleID{ @@ -43,8 +45,9 @@ var ( IndexStr: "2", PolicyID: calc.PolicyID{ Name: "P2", - Tier: "T2", + Kind: v3.KindGlobalNetworkPolicy, }, + Tier: "T2", Direction: rules.RuleDirIngress, } allowIngressRid1 = &calc.RuleID{ @@ -53,8 +56,9 @@ var ( IndexStr: "1", PolicyID: calc.PolicyID{ Name: "P1", - Tier: "T3", + Kind: v3.KindGlobalNetworkPolicy, }, + Tier: "T3", Direction: rules.RuleDirIngress, } denyIngressRid1 = &calc.RuleID{ @@ -63,8 +67,9 @@ var ( IndexStr: "2", PolicyID: calc.PolicyID{ Name: "P2", - Tier: "T4", + Kind: v3.KindGlobalNetworkPolicy, }, + Tier: "T4", Direction: rules.RuleDirIngress, } allowIngressRid2 = &calc.RuleID{ @@ -73,8 +78,9 @@ var ( IndexStr: "1", PolicyID: calc.PolicyID{ Name: "P2", - Tier: "T5", + Kind: v3.KindGlobalNetworkPolicy, }, + Tier: "T5", Direction: rules.RuleDirIngress, } nextTierIngressRid0 = &calc.RuleID{ @@ -83,8 +89,9 @@ var ( IndexStr: "3", PolicyID: calc.PolicyID{ Name: "P1", - Tier: "T6", + Kind: v3.KindGlobalNetworkPolicy, }, + Tier: "T6", Direction: rules.RuleDirIngress, } nextTierIngressRid1 = &calc.RuleID{ @@ -93,8 +100,9 @@ var ( IndexStr: "4", PolicyID: calc.PolicyID{ Name: "P2", - Tier: "T7", + Kind: v3.KindGlobalNetworkPolicy, }, + Tier: "T7", Direction: rules.RuleDirIngress, } allowIngressRid11 = &calc.RuleID{ @@ -103,8 +111,9 @@ var ( IndexStr: "1", PolicyID: calc.PolicyID{ Name: "P1", - Tier: "T8", + Kind: v3.KindGlobalNetworkPolicy, }, + Tier: "T8", Direction: rules.RuleDirIngress, } denyIngressRid21 = &calc.RuleID{ @@ -113,8 +122,9 @@ var ( IndexStr: "1", PolicyID: calc.PolicyID{ Name: "P1", - Tier: "T9", + Kind: v3.KindGlobalNetworkPolicy, }, + Tier: "T9", Direction: rules.RuleDirIngress, } @@ -124,8 +134,8 @@ var ( IndexStr: "2", PolicyID: calc.PolicyID{ Name: "P4", - Tier: "T10", }, + Tier: "T10", Direction: rules.RuleDirEgress, } allowEgressRid2 = &calc.RuleID{ @@ -134,8 +144,9 @@ var ( IndexStr: "3", PolicyID: calc.PolicyID{ Name: "P3", - Tier: "T11", + Kind: v3.KindGlobalNetworkPolicy, }, + Tier: "T11", Direction: rules.RuleDirEgress, } ) @@ -295,7 +306,6 @@ var _ = Describe("Rule Trace", func() { It("should have not have action set", func() { Expect(data.IngressAction()).NotTo(Equal(rules.RuleActionAllow)) Expect(data.IngressAction()).NotTo(Equal(rules.RuleActionDeny)) - //Expect(data.IngressAction()).NotTo(Equal(rules.RuleActionPass)) }) }) Context("Replacing a rule tracepoint that was conflicting", func() { @@ -363,5 +373,4 @@ var _ = Describe("Rule Trace", func() { }) }) }) - }) diff --git a/felix/collector/types/counter/counter_suite_test.go b/felix/collector/types/counter/counter_suite_test.go index 553645fd893..736e64881c0 100644 --- a/felix/collector/types/counter/counter_suite_test.go +++ b/felix/collector/types/counter/counter_suite_test.go @@ -17,9 +17,8 @@ package counter import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/logutils" "github.com/projectcalico/calico/libcalico-go/lib/testutils" @@ -31,7 +30,8 @@ func init() { } func TestCounter(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../report/counter_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Counter Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../report/felix_collector_counter_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/collector/counter", suiteConfig, reporterConfig) } diff --git a/felix/collector/types/counter/counter_test.go b/felix/collector/types/counter/counter_test.go index 82177360022..a3a9bcc5c07 100644 --- a/felix/collector/types/counter/counter_test.go +++ b/felix/collector/types/counter/counter_test.go @@ -15,7 +15,7 @@ package counter import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/felix/collector/types/tuple/tuple_suite_test.go b/felix/collector/types/tuple/tuple_suite_test.go index 9ad1abdfbc3..ef736c8a48e 100644 --- a/felix/collector/types/tuple/tuple_suite_test.go +++ b/felix/collector/types/tuple/tuple_suite_test.go @@ -17,9 +17,8 @@ package tuple import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/logutils" "github.com/projectcalico/calico/libcalico-go/lib/testutils" @@ -31,7 +30,8 @@ func init() { } func TestTuple(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../report/tuple_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Tuple Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../report/felix_collector_tuple_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/collector/tuple", suiteConfig, reporterConfig) } diff --git a/felix/collector/types/tuple/tupleset_test.go b/felix/collector/types/tuple/tupleset_test.go index 850f234d790..e9b3143f60c 100644 --- a/felix/collector/types/tuple/tupleset_test.go +++ b/felix/collector/types/tuple/tupleset_test.go @@ -18,7 +18,7 @@ import ( "fmt" "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/collector/utils" diff --git a/felix/collector/types/types.go b/felix/collector/types/types.go index 054e31da8f5..b26368a21d4 100644 --- a/felix/collector/types/types.go +++ b/felix/collector/types/types.go @@ -18,7 +18,7 @@ import "github.com/projectcalico/calico/felix/rules" type Reporter interface { Start() error - Report(interface{}) error + Report(any) error } type TrafficDirection int diff --git a/felix/config/config_params.go b/felix/config/config_params.go index 996705782e6..1ee046eebfb 100644 --- a/felix/config/config_params.go +++ b/felix/config/config_params.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2020-2026 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,11 +17,13 @@ package config import ( "errors" "fmt" + "maps" "math" "net" "os" "reflect" "regexp" + "slices" "strconv" "strings" "time" @@ -34,6 +36,7 @@ import ( "github.com/projectcalico/calico/felix/proto" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" "github.com/projectcalico/calico/libcalico-go/lib/clientv3" + "github.com/projectcalico/calico/libcalico-go/lib/consistenthash" "github.com/projectcalico/calico/libcalico-go/lib/names" "github.com/projectcalico/calico/libcalico-go/lib/set" ) @@ -53,7 +56,8 @@ var ( StringRegexp = regexp.MustCompile(`^.*$`) IfaceParamRegexp = regexp.MustCompile(`^[a-zA-Z0-9:._+-]{1,15}$`) // Hostname have to be valid ipv4, ipv6 or strings up to 64 characters. - HostAddressRegexp = regexp.MustCompile(`^[a-zA-Z0-9:._+-]{1,64}$`) + HostAddressRegexp = regexp.MustCompile(`^[a-zA-Z0-9:._+-]{1,64}$`) + LogActionRateRegexp = regexp.MustCompile(`^([1-9]\d{0,3}/(?:second|minute|hour|day))?$`) ) // Source of a config value. Values from higher-numbered sources override @@ -180,7 +184,7 @@ type Config struct { WireguardThreadingEnabled bool `config:"bool;false"` // nftables configuration. - NFTablesMode string `config:"oneof(Enabled,Disabled);Disabled"` + NFTablesMode string `config:"oneof(Enabled,Disabled,Auto);Auto"` // BPF configuration. BPFEnabled bool `config:"bool;false"` @@ -201,8 +205,7 @@ type Config struct { BPFDSROptoutCIDRs []string `config:"cidr-list;;"` BPFKubeProxyIptablesCleanupEnabled bool `config:"bool;true"` BPFKubeProxyMinSyncPeriod time.Duration `config:"seconds;1"` - BPFKubeProxyHealtzPort int `config:"int;10256;non-zero"` - BPFKubeProxyEndpointSlicesEnabled bool `config:"bool;true"` + BPFKubeProxyHealthzPort int `config:"int;10256;non-zero"` BPFExtToServiceConnmark int `config:"int;0"` BPFPSNATPorts numorstring.Port `config:"portrange;20000:29999"` BPFMapSizeNATFrontend int `config:"int;65536;non-zero"` @@ -221,7 +224,7 @@ type Config struct { BPFForceTrackPacketsFromIfaces []string `config:"iface-filter-slice;docker+"` BPFDisableGROForIfaces *regexp.Regexp `config:"regexp;"` BPFExcludeCIDRsFromNAT []string `config:"cidr-list;;"` - BPFRedirectToPeer string `config:"oneof(Disabled,Enabled,L2Only);L2Only;non-zero"` + BPFRedirectToPeer string `config:"oneof(Disabled,Enabled,L2Only);Enabled;non-zero"` BPFAttachType string `config:"oneof(TCX,TC);TCX;non-zero"` BPFExportBufferSizeMB int `config:"int;1;non-zero"` BPFProfiling string `config:"oneof(Disabled,Enabled);Disabled;non-zero"` @@ -304,8 +307,6 @@ type Config struct { IPForwarding string `config:"oneof(Enabled,Disabled);Enabled"` IptablesRefreshInterval time.Duration `config:"seconds;180"` IptablesPostWriteCheckIntervalSecs time.Duration `config:"seconds;5"` //nolint:staticcheck // Ignore ST1011 don't use unit-specific suffix - IptablesLockFilePath string `config:"file;/run/xtables.lock"` - IptablesLockTimeoutSecs time.Duration `config:"seconds;0"` //nolint:staticcheck // Ignore ST1011 don't use unit-specific suffix IptablesLockProbeIntervalMillis time.Duration `config:"millis;50"` //nolint:staticcheck // Ignore ST1011 don't use unit-specific suffix FeatureDetectOverride map[string]string `config:"keyvaluelist;;"` FeatureGates map[string]string `config:"keyvaluelist;;"` @@ -331,6 +332,8 @@ type Config struct { IptablesMangleAllowAction string `config:"oneof(ACCEPT,RETURN);ACCEPT;non-zero,die-on-fail"` IptablesFilterDenyAction string `config:"oneof(DROP,REJECT);DROP;non-zero,die-on-fail"` LogPrefix string `config:"string;calico-packet"` + LogActionRateLimit string `config:"log-rate;"` + LogActionRateLimitBurst int `config:"int(0,9999);5"` LogFilePath string `config:"file;/var/log/calico/felix.log;die-on-fail"` @@ -407,6 +410,11 @@ type Config struct { PrometheusProcessMetricsEnabled bool `config:"bool;true"` PrometheusWireGuardMetricsEnabled bool `config:"bool;true"` + PrometheusMetricsCAFile string `config:"string;"` + PrometheusMetricsCertFile string `config:"string;"` + PrometheusMetricsKeyFile string `config:"string;"` + PrometheusMetricsClientAuth string `config:"oneof(RequireAndVerifyClientCert,RequireAnyClientCert,VerifyClientCertIfGiven,NoClientCert);RequireAndVerifyClientCert"` + FailsafeInboundHostPorts []ProtoPort `config:"port-list;tcp:22,udp:68,tcp:179,tcp:2379,tcp:2380,tcp:5473,tcp:6443,tcp:6666,tcp:6667;die-on-fail"` FailsafeOutboundHostPorts []ProtoPort `config:"port-list;udp:53,udp:67,tcp:179,tcp:2379,tcp:2380,tcp:5473,tcp:6443,tcp:6666,tcp:6667;die-on-fail"` @@ -506,6 +514,17 @@ type Config struct { useNodeResourceUpdates bool RequireMTUFile bool `config:"bool;false"` + + // BPFMaglevMaxEndpointsPerService is the maximum number of endpoints + // expected to be part of a single Maglev-enabled service. + // + // Influences the size of the per-service Maglev lookup-tables generated by Felix + // and thus the amount of memory reserved. + BPFMaglevMaxEndpointsPerService int `config:"int(1:3000);100"` + + // BPFMaglevMaxServices is the maximum number of expected Maglev-enabled + // services that Felix will allocate lookup-tables for. + BPFMaglevMaxServices int `config:"int(1:3000);100"` } func (config *Config) FilterAllowAction() string { @@ -564,22 +583,16 @@ func (config *Config) Copy() *Config { // Copy the internal state over as a deep copy. cp.internalOverrides = map[string]string{} - for k, v := range config.internalOverrides { - cp.internalOverrides[k] = v - } + maps.Copy(cp.internalOverrides, config.internalOverrides) cp.sourceToRawConfig = map[Source]map[string]string{} for k, v := range config.sourceToRawConfig { cp.sourceToRawConfig[k] = map[string]string{} - for k2, v2 := range v { - cp.sourceToRawConfig[k][k2] = v2 - } + maps.Copy(cp.sourceToRawConfig[k], v) } cp.rawValues = map[string]string{} - for k, v := range config.rawValues { - cp.rawValues[k] = v - } + maps.Copy(cp.rawValues, config.rawValues) return &cp } @@ -599,9 +612,7 @@ func (config *Config) ToConfigUpdate() *proto.ConfigUpdate { buf.SourceToRawConfig = map[uint32]*proto.RawConfig{} for source, c := range config.sourceToRawConfig { kvs := map[string]string{} - for k, v := range c { - kvs[k] = v - } + maps.Copy(kvs, c) buf.SourceToRawConfig[uint32(source)] = &proto.RawConfig{ Source: source.String(), Config: kvs, @@ -609,9 +620,7 @@ func (config *Config) ToConfigUpdate() *proto.ConfigUpdate { } buf.Config = map[string]string{} - for k, v := range config.rawValues { - buf.Config[k] = v - } + maps.Copy(buf.Config, config.rawValues) return &buf } @@ -622,9 +631,7 @@ func (config *Config) UpdateFromConfigUpdate(configUpdate *proto.ConfigUpdate) ( for sourceInt, c := range configUpdate.GetSourceToRawConfig() { source := Source(sourceInt) config.sourceToRawConfig[source] = map[string]string{} - for k, v := range c.GetConfig() { - config.sourceToRawConfig[source][k] = v - } + maps.Copy(config.sourceToRawConfig[source], c.GetConfig()) } // Note: the ConfigUpdate also carries the rawValues, but we recalculate those by calling resolve(), // which tells us if anything changed as a result. @@ -685,11 +692,9 @@ func (config *Config) OpenstackActive() bool { log.Debug("OpenStack metadata port set to non-default, assuming OpenStack active") return true } - for _, prefix := range config.InterfacePrefixes() { - if prefix == "tap" { - log.Debug("Interface prefix list contains 'tap', assuming OpenStack") - return true - } + if slices.Contains(config.InterfacePrefixes(), "tap") { + log.Debug("Interface prefix list contains 'tap', assuming OpenStack") + return true } log.Debug("No evidence this is an OpenStack deployment; disabling OpenStack special-cases") return false @@ -698,8 +703,8 @@ func (config *Config) OpenstackActive() bool { // KubernetesProvider attempts to parse the kubernetes provider, e.g. AKS out of the ClusterType. // The ClusterType is a string which contains a set of comma-separated values in no particular order. func (config *Config) KubernetesProvider() Provider { - settings := strings.Split(config.ClusterType, ",") - for _, s := range settings { + settings := strings.SplitSeq(config.ClusterType, ",") + for s := range settings { p, err := newProvider(s) if err == nil { log.WithFields(log.Fields{"clusterType": config.ClusterType, "provider": p}).Debug( @@ -713,6 +718,14 @@ func (config *Config) KubernetesProvider() Provider { return ProviderNone } +func (config *Config) BPFLUTSizeMaglev() int { + return int(consistenthash.NextPrimeUint16(config.BPFMaglevMaxEndpointsPerService * consistenthash.MaglevEndpointLUTFactor)) +} + +func (config *Config) BPFMapSizeMaglev() int { + return int(config.BPFLUTSizeMaglev()) * config.BPFMaglevMaxServices +} + func (config *Config) applyDefaults() { for _, param := range knownParams { param.setDefault(config) @@ -768,7 +781,7 @@ func (config *Config) resolve() (changedFields set.Set[string], err error) { log.Infof("Parsing value for %v: %v (from %v)", name, rawValue, source) - var value interface{} + var value any if strings.ToLower(rawValue) == "none" { // Special case: we allow a value of "none" to force the value to // the zero value for a field. The zero value often differs from @@ -818,7 +831,7 @@ func (config *Config) resolve() (changedFields set.Set[string], err error) { } changedFields = set.New[string]() - kind := reflect.TypeOf(Config{}) + kind := reflect.TypeFor[Config]() for ii := 0; ii < kind.NumField(); ii++ { field := kind.Field(ii) tag := field.Tag.Get("config") @@ -858,7 +871,7 @@ func SafeParamsEqual(a any, b any) bool { if len(a) != len(b) { return false } - for i := 0; i < len(a); i++ { + for i := range a { if (a[i] == nil) || (b[i] == nil) { if a[i] == b[i] { continue @@ -1004,7 +1017,7 @@ func Params() map[string]Param { func loadParams() { knownParams = make(map[string]Param) config := Config{} - kind := reflect.TypeOf(config) + kind := reflect.TypeFor[Config]() metaRegexp := regexp.MustCompile(`^([^;(]+)(?:\(([^)]*)\))?;` + `([^;]*)(?:;` + `([^;]*))?$`) @@ -1034,7 +1047,7 @@ func loadParams() { paramMin := math.MinInt paramMax := math.MaxInt if kindParams != "" { - for _, r := range strings.Split(kindParams, ",") { + for r := range strings.SplitSeq(kindParams, ",") { minAndMax := strings.Split(r, ":") paramMin = mustParseOptionalInt(minAndMax[0], math.MinInt, field.Name) if len(minAndMax) == 2 { @@ -1083,6 +1096,11 @@ func loadParams() { Msg: "list contains invalid Linux interface name or regex pattern", Schema: "Comma-delimited list of Linux interface names/regex patterns. Regex patterns must start/end with `/`.", } + case "log-rate": + param = &RegexpParam{ + Regexp: LogActionRateRegexp, + Msg: "invalid log rate limit", + } case "regexp": param = &RegexpPatternParam{ Flags: strings.Split(kindParams, ","), @@ -1221,9 +1239,7 @@ func (config *Config) UseNodeResourceUpdates() bool { func (config *Config) RawValues() map[string]string { cp := map[string]string{} - for k, v := range config.rawValues { - cp[k] = v - } + maps.Copy(cp, config.rawValues) return cp } @@ -1281,7 +1297,7 @@ func New() *Config { type Param interface { GetMetadata() *Metadata - Parse(raw string) (result interface{}, err error) + Parse(raw string) (result any, err error) setDefault(*Config) SchemaDescription() string } diff --git a/felix/config/config_params_test.go b/felix/config/config_params_test.go index 1a434a9f58a..15602ac5a0f 100644 --- a/felix/config/config_params_test.go +++ b/felix/config/config_params_test.go @@ -22,8 +22,7 @@ import ( "strings" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -70,7 +69,6 @@ var _ = Describe("FelixConfig vs ConfigParams parity", func() { "VXLANEnabled": "VXLANEnabled", "IpInIpMtu": "IPIPMTU", "Ipv6Support": "IPv6Support", - "IptablesLockTimeoutSecs": "IptablesLockTimeout", "IptablesLockProbeIntervalMillis": "IptablesLockProbeInterval", "IptablesPostWriteCheckIntervalSecs": "IptablesPostWriteCheckInterval", "NetlinkTimeoutSecs": "NetlinkTimeout", @@ -123,7 +121,7 @@ var _ = Describe("FelixConfig vs ConfigParams parity", func() { }) }) -func fieldsByName(example interface{}) map[string]reflect.StructField { +func fieldsByName(example any) map[string]reflect.StructField { fields := map[string]reflect.StructField{} t := reflect.TypeOf(example) for i := 0; i < t.NumField(); i++ { @@ -198,6 +196,22 @@ var _ = Describe("Config override empty", func() { Expect(err).NotTo(HaveOccurred()) Expect(cp.IptablesBackend).To(Equal("auto")) }) + + It("should have correct default EndpointStatusPathPrefix value", func() { + Expect(cp.EndpointStatusPathPrefix).To(Equal("/var/run/calico")) + }) + + Context("with EndpointStatusPathPrefix=none in config file", func() { + BeforeEach(func() { + changed, err := cp.UpdateFrom(map[string]string{"EndpointStatusPathPrefix": "none"}, config.ConfigFile) + Expect(changed).To(BeTrue()) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should have EndpointStatusPathPrefix empty", func() { + Expect(cp.EndpointStatusPathPrefix).To(Equal("")) + }) + }) }) var ( @@ -206,7 +220,7 @@ var ( ) var _ = DescribeTable("Config parsing", - func(key, value string, expected interface{}, errorExpected ...bool) { + func(key, value string, expected any, errorExpected ...bool) { cfg := config.New() _, err := cfg.UpdateFrom(map[string]string{key: value}, config.EnvironmentVariable) configPtr := reflect.ValueOf(cfg) @@ -290,10 +304,6 @@ var _ = DescribeTable("Config parsing", Entry("IptablesPostWriteCheckIntervalSecs", "IptablesPostWriteCheckIntervalSecs", "1.5", 1500*time.Millisecond), - Entry("IptablesLockFilePath", "IptablesLockFilePath", - "/host/run/xtables.lock", "/host/run/xtables.lock"), - Entry("IptablesLockTimeoutSecs", "IptablesLockTimeoutSecs", - "123", 123*time.Second), Entry("IptablesLockProbeIntervalMillis", "IptablesLockProbeIntervalMillis", "123", 123*time.Millisecond), Entry("IptablesLockProbeIntervalMillis garbage", "IptablesLockProbeIntervalMillis", @@ -529,7 +539,7 @@ var _ = DescribeTable("Config parsing", ) var _ = DescribeTable("OpenStack heuristic tests", - func(clusterType, metadataAddr, metadataPort, ifacePrefixes interface{}, expected bool) { + func(clusterType, metadataAddr, metadataPort, ifacePrefixes any, expected bool) { c := config.New() values := make(map[string]string) if clusterType != nil { @@ -776,6 +786,12 @@ var _ = DescribeTable("Config validation", Entry("excessive RouteTableRanges", map[string]string{ "RouteTableRanges": "1-100000000", }, false), + Entry("excessive RouteTableRanges off-by-one", map[string]string{ + "RouteTableRanges": "1-65535,99999-99999", + }, false), + Entry("RouteTableRanges 32-bit wrap-around", map[string]string{ + "RouteTableRanges": "1-65535,1-2147483647", + }, false), Entry("invalid RouteTableRanges", map[string]string{ "RouteTableRanges": "abcde", }, false), diff --git a/felix/config/config_suite_test.go b/felix/config/config_suite_test.go index 8b6d63fd3ee..b65cd78789f 100644 --- a/felix/config/config_suite_test.go +++ b/felix/config/config_suite_test.go @@ -17,9 +17,8 @@ package config_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestConfig(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/config_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Config Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_config_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/config", suiteConfig, reporterConfig) } diff --git a/felix/config/env_var_loader_test.go b/felix/config/env_var_loader_test.go index 3007b44440c..b33d50853ae 100644 --- a/felix/config/env_var_loader_test.go +++ b/felix/config/env_var_loader_test.go @@ -15,7 +15,7 @@ package config_test import ( - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/config" diff --git a/felix/config/file_loader_test.go b/felix/config/file_loader_test.go index cd5ad80c2f4..c668c0a100d 100644 --- a/felix/config/file_loader_test.go +++ b/felix/config/file_loader_test.go @@ -18,7 +18,7 @@ import ( "path" "runtime" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/config" @@ -36,6 +36,10 @@ FelixHostname=hostname LogSeverityScreen=INFO LogSeveritySys=DEBUG` +const confEmptyString = `[default] +EndpointStatusPathPrefix=none +` + var _ = DescribeTable("File parameter parsing", func(fileContent string, expected map[string]string) { actual, err := config.LoadConfigFileData([]byte(fileContent)) @@ -54,6 +58,9 @@ var _ = DescribeTable("File parameter parsing", "LogSeverityScreen": "INFO", "LogSeveritySys": "DEBUG", }), + Entry("Setting 'none' value", confEmptyString, map[string]string{ + "EndpointStatusPathPrefix": "none", + }), ) var _ = DescribeTable("File load tests", diff --git a/felix/config/metadata.go b/felix/config/metadata.go index 0e7da114878..3b3a8e528c0 100644 --- a/felix/config/metadata.go +++ b/felix/config/metadata.go @@ -217,6 +217,11 @@ func CombinedFieldInfo() ([]*FieldInfo, error) { // String schema tends to have the ranges, which are missing from the YAML. pm.YAMLSchema = pm.StringSchema } + if pm.GoType == "*numorstring.Port" { + // The Port type has its own string encoding. + pm.YAMLSchema = "Port range: either an integer in [0,65535] or a string, representing a range, in format `n:m`" + } + pm.StringSchemaHTML = convertSchemaToHTML(pm.StringSchema) pm.YAMLSchemaHTML = convertSchemaToHTML(pm.YAMLSchema) pm.DescriptionHTML = convertDescriptionToHTML(pm.Description) @@ -371,7 +376,7 @@ func safeIsNil(v any) bool { // The nil interface, no type or value. return true } - if reflect.ValueOf(v).Kind() == reflect.Ptr && reflect.ValueOf(v).IsNil() { + if reflect.ValueOf(v).Kind() == reflect.Pointer && reflect.ValueOf(v).IsNil() { // Typed nil. return true } @@ -646,8 +651,7 @@ type StructInfo struct { func parseStruct() map[string]StructInfo { out := make(map[string]StructInfo) - var spec v3.FelixConfigurationSpec - t := reflect.TypeOf(spec) + t := reflect.TypeFor[v3.FelixConfigurationSpec]() for i := 0; i < t.NumField(); i++ { field := t.Field(i) diff --git a/felix/config/metadata_test.go b/felix/config/metadata_test.go index d8fafa80f91..b326daea9a1 100644 --- a/felix/config/metadata_test.go +++ b/felix/config/metadata_test.go @@ -15,12 +15,12 @@ package config import ( - . "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" ) -var _ = Describe("Docs metadata", func() { - It("should load the metadata", func() { +var _ = ginkgo.Describe("Docs metadata", func() { + ginkgo.It("should load the metadata", func() { params, err := CombinedFieldInfo() gomega.Expect(err).NotTo(gomega.HaveOccurred()) gomega.Expect(params).NotTo(gomega.BeEmpty()) diff --git a/felix/config/param_types.go b/felix/config/param_types.go index 3e9c7ce1c8f..70a1f8a729f 100644 --- a/felix/config/param_types.go +++ b/felix/config/param_types.go @@ -19,12 +19,14 @@ import ( "errors" "fmt" "math" + "math/bits" "net" "net/url" "os" "os/exec" "reflect" "regexp" + "slices" "sort" "strconv" "strings" @@ -53,8 +55,8 @@ type Metadata struct { Name string Type string DefaultString string - Default interface{} - ZeroValue interface{} + Default any + ZeroValue any NonZero bool DieOnParseFailure bool Local bool @@ -86,7 +88,7 @@ func (p *BoolParam) SchemaDescription() string { return boolSchema } -func (p *BoolParam) Parse(raw string) (interface{}, error) { +func (p *BoolParam) Parse(raw string) (any, error) { switch strings.ToLower(raw) { case "true", "1", "yes", "y", "t": return true, nil @@ -100,7 +102,7 @@ type BoolPtrParam struct { Metadata } -func (p *BoolPtrParam) Parse(raw string) (interface{}, error) { +func (p *BoolPtrParam) Parse(raw string) (any, error) { t := true f := false switch strings.ToLower(raw) { @@ -126,7 +128,7 @@ type IntParam struct { Ranges []MinMax } -func (p *IntParam) Parse(raw string) (interface{}, error) { +func (p *IntParam) Parse(raw string) (any, error) { value, err := strconv.ParseInt(raw, 0, 64) if err != nil { err = p.parseFailed(raw, "invalid int") @@ -196,7 +198,7 @@ func intSchema(ranges []MinMax) string { } func formatInt(m int) string { - switch m { + switch int64(m) { case math.MaxInt64: return "2^63-1" case math.MinInt64: @@ -209,7 +211,7 @@ type Int32Param struct { Metadata } -func (p *Int32Param) Parse(raw string) (interface{}, error) { +func (p *Int32Param) Parse(raw string) (any, error) { value, err := strconv.ParseInt(raw, 0, 32) if err != nil { err = p.parseFailed(raw, "invalid 32-bit int") @@ -227,7 +229,7 @@ type FloatParam struct { Metadata } -func (p *FloatParam) Parse(raw string) (result interface{}, err error) { +func (p *FloatParam) Parse(raw string) (result any, err error) { result, err = strconv.ParseFloat(raw, 64) if err != nil { err = p.parseFailed(raw, "invalid float") @@ -246,7 +248,7 @@ type SecondsParam struct { Max int } -func (p *SecondsParam) Parse(raw string) (result interface{}, err error) { +func (p *SecondsParam) Parse(raw string) (result any, err error) { seconds, err := strconv.ParseFloat(raw, 64) if err != nil { err = p.parseFailed(raw, "invalid float") @@ -273,7 +275,7 @@ type MillisParam struct { Metadata } -func (p *MillisParam) Parse(raw string) (result interface{}, err error) { +func (p *MillisParam) Parse(raw string) (result any, err error) { millis, err := strconv.ParseFloat(raw, 64) if err != nil { err = p.parseFailed(raw, "invalid float") @@ -293,7 +295,7 @@ type RegexpParam struct { Msg string } -func (p *RegexpParam) Parse(raw string) (result interface{}, err error) { +func (p *RegexpParam) Parse(raw string) (result any, err error) { if !p.Regexp.MatchString(raw) { err = p.parseFailed(raw, p.Msg) } else { @@ -317,13 +319,11 @@ type RegexpPatternParam struct { } // Parse validates whether the given raw string contains a valid regexp pattern. -func (p *RegexpPatternParam) Parse(raw string) (interface{}, error) { +func (p *RegexpPatternParam) Parse(raw string) (any, error) { var result *regexp.Regexp if raw == "" { - for _, f := range p.Flags { - if f == "nil-on-empty" { - return nil, nil - } + if slices.Contains(p.Flags, "nil-on-empty") { + return nil, nil } } result, compileErr := regexp.Compile(raw) @@ -351,11 +351,11 @@ type RegexpPatternListParam struct { // Parse validates whether the given raw string contains a list of valid values. // Validation is dictated by two regexp patterns: one for valid regular expression // values, another for non-regular expressions. -func (p *RegexpPatternListParam) Parse(raw string) (interface{}, error) { +func (p *RegexpPatternListParam) Parse(raw string) (any, error) { var result []*regexp.Regexp // Split into individual elements, then validate each one and compile to regexp - tokens := strings.Split(raw, p.Delimiter) - for _, t := range tokens { + tokens := strings.SplitSeq(raw, p.Delimiter) + for t := range tokens { if p.RegexpElemRegexp.MatchString(t) { // Need to remove the start and end symbols that wrap the actual regexp // Note: There's a coupling here with the assumed pattern in RegexpElemRegexp @@ -389,7 +389,7 @@ type FileParam struct { Executable bool } -func (p *FileParam) Parse(raw string) (interface{}, error) { +func (p *FileParam) Parse(raw string) (any, error) { // Use GetHostPath to use/resolve the CONTAINER_SANDBOX_MOUNT_POINT env var // if running on Windows HPC. // FIXME: this will no longer be needed when containerd v1.6 is EOL'd @@ -459,7 +459,7 @@ type Ipv4Param struct { Metadata } -func (p *Ipv4Param) Parse(raw string) (result interface{}, err error) { +func (p *Ipv4Param) Parse(raw string) (result any, err error) { res := net.ParseIP(raw) if res == nil { err = p.parseFailed(raw, "invalid IP") @@ -479,7 +479,7 @@ type Ipv6Param struct { Metadata } -func (p *Ipv6Param) Parse(raw string) (result interface{}, err error) { +func (p *Ipv6Param) Parse(raw string) (result any, err error) { res := net.ParseIP(raw) if res == nil { err = p.parseFailed(raw, "invalid IP") @@ -499,9 +499,9 @@ type PortListParam struct { Metadata } -func (p *PortListParam) Parse(raw string) (interface{}, error) { +func (p *PortListParam) Parse(raw string) (any, error) { var result []ProtoPort - for _, portStr := range strings.Split(raw, ",") { + for portStr := range strings.SplitSeq(raw, ",") { portStr = strings.Trim(portStr, " ") if portStr == "" { continue @@ -584,7 +584,7 @@ type PortRangeParam struct { Metadata } -func (p *PortRangeParam) Parse(raw string) (interface{}, error) { +func (p *PortRangeParam) Parse(raw string) (any, error) { portRange, err := numorstring.PortFromString(raw) if err != nil { return nil, p.parseFailed(raw, fmt.Sprintf("%s is not a valid port range", raw)) @@ -603,9 +603,9 @@ type PortRangeListParam struct { Metadata } -func (p *PortRangeListParam) Parse(raw string) (interface{}, error) { +func (p *PortRangeListParam) Parse(raw string) (any, error) { var result []numorstring.Port - for _, rangeStr := range strings.Split(raw, ",") { + for rangeStr := range strings.SplitSeq(raw, ",") { portRange, err := numorstring.PortFromString(rangeStr) if err != nil { return nil, p.parseFailed(raw, fmt.Sprintf("%s is not a valid port range", rangeStr)) @@ -626,7 +626,7 @@ type EndpointListParam struct { Metadata } -func (p *EndpointListParam) Parse(raw string) (result interface{}, err error) { +func (p *EndpointListParam) Parse(raw string) (result any, err error) { value := strings.Split(raw, ",") scheme := "" resultSlice := []string{} @@ -674,7 +674,7 @@ type MarkBitmaskParam struct { Metadata } -func (p *MarkBitmaskParam) Parse(raw string) (interface{}, error) { +func (p *MarkBitmaskParam) Parse(raw string) (any, error) { value, err := strconv.ParseUint(raw, 0, 32) if err != nil { log.Warningf("Failed to parse %#v as an int: %v", raw, err) @@ -683,7 +683,7 @@ func (p *MarkBitmaskParam) Parse(raw string) (interface{}, error) { } result := uint32(value) bitCount := uint32(0) - for i := uint(0); i < 32; i++ { + for i := range uint(32) { bit := (result >> i) & 1 bitCount += bit } @@ -704,7 +704,7 @@ type OneofListParam struct { lowerCaseOptionsToCanonical map[string]string } -func (p *OneofListParam) Parse(raw string) (result interface{}, err error) { +func (p *OneofListParam) Parse(raw string) (result any, err error) { result, ok := p.lowerCaseOptionsToCanonical[strings.ToLower(raw)] if !ok { err = p.parseFailed(raw, "unknown option") @@ -725,7 +725,7 @@ type CIDRListParam struct { Metadata } -func (c *CIDRListParam) Parse(raw string) (result interface{}, err error) { +func (c *CIDRListParam) Parse(raw string) (result any, err error) { log.WithField("CIDRs raw", raw).Info("CIDRList") values := strings.Split(raw, ",") resultSlice := []string{} @@ -754,7 +754,7 @@ type ServerListParam struct { const k8sServicePrefix = "k8s-service:" -func (c *ServerListParam) Parse(raw string) (result interface{}, err error) { +func (c *ServerListParam) Parse(raw string) (result any, err error) { log.WithField("raw", raw).Info("ServerList") values := strings.Split(raw, ",") resultSlice := []ServerPort{} @@ -877,7 +877,7 @@ type RegionParam struct { const regionNamespacePrefix = "openstack-region-" const maxRegionLength int = validation.DNS1123LabelMaxLength - len(regionNamespacePrefix) -func (r *RegionParam) Parse(raw string) (result interface{}, err error) { +func (r *RegionParam) Parse(raw string) (result any, err error) { log.WithField("raw", raw).Info("Region") if len(raw) > maxRegionLength { err = fmt.Errorf("the value of OpenstackRegion must be %v chars or fewer", maxRegionLength) @@ -901,14 +901,14 @@ func (r *RegionParam) SchemaDescription() string { // linux can support route-table indices up to 0xFFFFFFFF // however, using 0xFFFFFFFF tables would require too much computation, so the total number of designated tables is capped at 0xFFFF -const routeTableMaxLinux = 0xffffffff +const routeTableMaxLinux uint32 = 0xffffffff const routeTableRangeMaxTables = 0xffff type RouteTableRangeParam struct { Metadata } -func (p *RouteTableRangeParam) Parse(raw string) (result interface{}, err error) { +func (p *RouteTableRangeParam) Parse(raw string) (result any, err error) { err = p.parseFailed(raw, "must be a range of route table indices within 1-250") m := regexp.MustCompile(`^(\d+)-(\d+)$`).FindStringSubmatch(raw) if m == nil { @@ -940,14 +940,14 @@ type RouteTableRangesParam struct { // reserved linux kernel routing tables (will be ignored if targeted by routetablerange) var routeTablesReservedLinux = []int{253, 254, 255} -func (p *RouteTableRangesParam) Parse(raw string) (result interface{}, err error) { +func (p *RouteTableRangesParam) Parse(raw string) (result any, err error) { match := regexp.MustCompile(`(\d+)-(\d+)`).FindAllStringSubmatch(raw, -1) if match == nil { err = p.parseFailed(raw, "must be a list of route-table ranges") return } - tablesTargeted := 0 + var tablesTargeted uint ranges := make([]idalloc.IndexRange, 0) for _, r := range match { // first match is the whole matching string - we only care about submatches @@ -972,8 +972,12 @@ func (p *RouteTableRangesParam) Parse(raw string) (result interface{}, err error return } - tablesTargeted += max - min - if tablesTargeted > routeTableRangeMaxTables { + // The number of route table IDs in the current range. + rangeLen := uint(max - min + 1) + + // Overflow-safe addition + var carry uint + if tablesTargeted, carry = bits.Add(tablesTargeted, rangeLen, 0); carry != 0 || tablesTargeted > routeTableRangeMaxTables { err = p.parseFailed(raw, "targets too many tables") return } @@ -1006,7 +1010,7 @@ type KeyValueListParam struct { Metadata } -func (p *KeyValueListParam) Parse(raw string) (result interface{}, err error) { +func (p *KeyValueListParam) Parse(raw string) (result any, err error) { result, err = stringutils.ParseKeyValueList(raw) return } @@ -1019,7 +1023,7 @@ type KeyDurationListParam struct { Metadata } -func (p *KeyDurationListParam) Parse(raw string) (result interface{}, err error) { +func (p *KeyDurationListParam) Parse(raw string) (result any, err error) { result, err = stringutils.ParseKeyDurationList(raw) return } @@ -1034,7 +1038,7 @@ type StringSliceParam struct { ValidationRegex *regexp.Regexp } -func (p *StringSliceParam) Parse(raw string) (result interface{}, err error) { +func (p *StringSliceParam) Parse(raw string) (result any, err error) { log.WithField("StringSliceParam raw", raw).Info("StringSliceParam") values := strings.Split(raw, ",") diff --git a/felix/config/param_types_test.go b/felix/config/param_types_test.go index b03e490d51a..62d1e30e581 100644 --- a/felix/config/param_types_test.go +++ b/felix/config/param_types_test.go @@ -17,14 +17,14 @@ package config_test import ( "net" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/config" ) var _ = DescribeTable("Endpoint list parameter parsing", - func(raw string, expected interface{}) { + func(raw string, expected any) { p := config.EndpointListParam{config.Metadata{ Name: "Endpoints", }} @@ -42,7 +42,7 @@ var _ = DescribeTable("Endpoint list parameter parsing", ) var _ = DescribeTable("CIDR list parameter parsing", - func(raw string, expected interface{}, expectSuccess bool) { + func(raw string, expected any, expectSuccess bool) { p := config.CIDRListParam{config.Metadata{ Name: "CIDRs", }} @@ -126,7 +126,7 @@ var _ = DescribeTable("IPv6 list parameter parsing", ) var _ = DescribeTable("String Slice parameter with InterfaceRegex parsing", - func(raw string, expected interface{}, expectSuccess bool) { + func(raw string, expected any, expectSuccess bool) { p := config.StringSliceParam{config.Metadata{ Name: "StringSliceParam", }, config.InterfaceRegex, @@ -150,7 +150,7 @@ var _ = DescribeTable("String Slice parameter with InterfaceRegex parsing", ) var _ = DescribeTable("String Slice parameter with IfaceParamRegexp parsing", - func(raw string, expected interface{}, expectSuccess bool) { + func(raw string, expected any, expectSuccess bool) { p := config.StringSliceParam{config.Metadata{ Name: "StringSliceParam", }, config.IfaceParamRegexp, diff --git a/felix/conntrack/conntrack_suite_test.go b/felix/conntrack/conntrack_suite_test.go index 156f3457ec0..ae3238cb892 100644 --- a/felix/conntrack/conntrack_suite_test.go +++ b/felix/conntrack/conntrack_suite_test.go @@ -17,9 +17,8 @@ package conntrack_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestConntrack(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/conntrack_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Conntrack Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_conntrack_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/conntrack", suiteConfig, reporterConfig) } diff --git a/felix/conntrack/conntrack_test.go b/felix/conntrack/conntrack_test.go index 0e26e5ce243..8d4f4c09035 100644 --- a/felix/conntrack/conntrack_test.go +++ b/felix/conntrack/conntrack_test.go @@ -19,7 +19,7 @@ import ( "io" "net" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/projectcalico/calico/felix/conntrack" diff --git a/felix/daemon/daemon.go b/felix/daemon/daemon.go index a4043e34d46..f02e901a3db 100644 --- a/felix/daemon/daemon.go +++ b/felix/daemon/daemon.go @@ -44,7 +44,7 @@ import ( "github.com/projectcalico/calico/felix/statusrep" "github.com/projectcalico/calico/felix/usagerep" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend" bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s" @@ -380,6 +380,13 @@ configRetry: if err != nil { log.WithError(err).Panic("Bug: failed to override config parameter BPFConntrackCleanupMode") } + if configParams.BPFRedirectToPeer == "L2Only" { + log.Warn("BPFRedirectToPeer 'L2Only' is deprecated and equals 'Enabled' now.") + _, err := configParams.OverrideParam("BPFRedirectToPeer", "Enabled") + if err != nil { + log.WithError(err).Panic("Bug: failed to override config parameter BPFRedirectToPeer") + } + } } } @@ -485,11 +492,11 @@ configRetry: var policySyncServer *policysync.Server var policySyncProcessor *policysync.Processor var policySyncAPIBinder binder.Binder - calcGraphClientChannels := []chan<- interface{}{dpConnector.ToDataplane} + calcGraphClientChannels := []chan<- any{dpConnector.ToDataplane} if configParams.IsLeader() && configParams.PolicySyncPathPrefix != "" { log.WithField("policySyncPathPrefix", configParams.PolicySyncPathPrefix).Info( "Policy sync API enabled. Creating the policy sync server.") - toPolicySync := make(chan interface{}) + toPolicySync := make(chan any) policySyncUIDAllocator := policysync.NewUIDAllocator() policySyncProcessor = policysync.NewProcessor(toPolicySync) policySyncServer = policysync.NewServer( @@ -505,7 +512,7 @@ configRetry: if dpStatsCollector != nil { if apiv3.FlowLogsPolicyEvaluationModeType(configParams.FlowLogsPolicyEvaluationMode) == apiv3.FlowLogsPolicyEvaluationModeContinuous { // Fork the calculation graph for dataplane updates that will be sent to the Collector. - toCollectorDataplaneSync := make(chan interface{}) + toCollectorDataplaneSync := make(chan any) // The DataplaneInfoReader wraps and sends the dataplane updates to the Collector. dpir := collector.NewDataplaneInfoReader(toCollectorDataplaneSync) dpStatsCollector.SetDataplaneInfoReader(dpir) @@ -729,7 +736,7 @@ configRetry: dpConnector.ToDataplane <- configParams.ToConfigUpdate() if configParams.PrometheusMetricsEnabled { - log.Info("Prometheus metrics enabled. Starting server.") + log.Info("Prometheus metrics enabled.") gaugeHost := prometheus.NewGauge(prometheus.GaugeOpts{ Name: "felix_host", Help: "Configured Felix hostname (as a label), typically used in grouping/aggregating stats; the label defaults to the hostname of the host but can be overridden by configuration. The value of the gauge is always set to 1.", @@ -738,10 +745,30 @@ configRetry: gaugeHost.Set(1) prometheus.MustRegister(gaugeHost) dp.ConfigurePrometheusMetrics(configParams) - go metricsserver.ServePrometheusMetricsForever( - configParams.PrometheusMetricsHost, - configParams.PrometheusMetricsPort, - ) + if configParams.PrometheusMetricsKeyFile != "" || configParams.PrometheusMetricsCertFile != "" { + log.Info("Trying to start metrics https server.") + go func() { + err := metricsserver.ServePrometheusMetricsHTTPS( + prometheus.DefaultGatherer, + configParams.PrometheusMetricsHost, + configParams.PrometheusMetricsPort, + configParams.PrometheusMetricsCertFile, + configParams.PrometheusMetricsKeyFile, + configParams.PrometheusMetricsClientAuth, + configParams.PrometheusMetricsCAFile, + ) + if err != nil { + log.Info("Error starting metrics https server.", err) + } + }() + } else { + log.Info("Starting metrics http server.") + go metricsserver.ServePrometheusMetricsHTTP( + prometheus.DefaultGatherer, + configParams.PrometheusMetricsHost, + configParams.PrometheusMetricsPort, + ) + } } // Register signal handlers to dump memory/CPU profiles. @@ -973,7 +1000,7 @@ func loadConfigFromDatastore( } err = getAndMergeConfig( ctx, client, hostConfig, - libapiv3.KindNode, hostname, + internalapi.KindNode, hostname, updateprocessors.NewFelixNodeUpdateProcessor(cfg.Spec.K8sUsePodCIDR), &ready, ) @@ -1046,14 +1073,14 @@ type DataplaneConnector struct { config *config.Config configUpdChan chan<- map[string]string - ToDataplane chan interface{} + ToDataplane chan any InSync chan bool // Input channel for msgs from the dataplane. // Msgs popped off this channel are dispatched to all StatusUpdatesFromDataplaneConsumers. - statusUpdatesFromDataplane chan interface{} - statusUpdatesFromDataplaneDispatcher *dispatcher.BlockingDispatcher[interface{}] - statusUpdatesFromDataplaneConsumers []chan interface{} + statusUpdatesFromDataplane chan any + statusUpdatesFromDataplaneDispatcher *dispatcher.BlockingDispatcher[any] + statusUpdatesFromDataplaneConsumers []chan any failureReportChan chan<- string dataplane dp.DataplaneDriver @@ -1081,15 +1108,15 @@ func newConnector(configParams *config.Config, configUpdChan: configUpdChan, datastore: datastore, datastorev3: datastorev3, - ToDataplane: make(chan interface{}), - statusUpdatesFromDataplane: make(chan interface{}), + ToDataplane: make(chan any), + statusUpdatesFromDataplane: make(chan any), statusUpdatesFromDataplaneConsumers: nil, failureReportChan: failureReportChan, dataplane: dataplane, wireguardStatUpdateFromDataplane: make(chan *proto.WireguardStatusUpdate, 1), } - fromDataplaneDispatcher, err := dispatcher.NewBlockingDispatcher[interface{}](felixConn.statusUpdatesFromDataplane) + fromDataplaneDispatcher, err := dispatcher.NewBlockingDispatcher[any](felixConn.statusUpdatesFromDataplane) if err != nil { log.WithError(err).Panic("Failed to create dispatcher for status updates from dataplane") } @@ -1101,8 +1128,8 @@ func newConnector(configParams *config.Config, // NewFromDataplaneConsumer creates a channel which receives status updates from the dataplane. // Each call creates a new consumer channel, and each consumer is dispatched dataplane msgs in series. // So, it's important that all created chans are continuously drained to avoid deadlocking. -func (fc *DataplaneConnector) NewFromDataplaneConsumer() <-chan interface{} { - fromDataplaneC := make(chan interface{}, 10) +func (fc *DataplaneConnector) NewFromDataplaneConsumer() <-chan any { + fromDataplaneC := make(chan any, 10) fc.statusUpdatesFromDataplaneConsumers = append(fc.statusUpdatesFromDataplaneConsumers, fromDataplaneC) return fromDataplaneC } @@ -1203,7 +1230,7 @@ func (fc *DataplaneConnector) handleProcessStatusUpdate(ctx context.Context, msg func (fc *DataplaneConnector) reconcileWireguardStatUpdate(dpPubKey string, ipVersion proto.IPVersion) error { // In case of a recoverable failure (ErrorResourceUpdateConflict), retry update 3 times. - for iter := 0; iter < 3; iter++ { + for range 3 { // Read node resource from datastore and compare it with the publicKey from dataplane. getCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -1409,7 +1436,7 @@ func (fc *DataplaneConnector) handleConfigUpdate(msg *proto.ConfigUpdate) { oldRawConfig := oldConfigCopy.RawValues() newRawConfig := newConfigCopy.RawValues() restartNeeded := false - changedFields.Iter(func(fieldName string) error { + for fieldName := range changedFields.All() { logCtx := log.WithFields(log.Fields{ "key": fieldName, "oldValue": oldRawConfig[fieldName], @@ -1421,8 +1448,7 @@ func (fc *DataplaneConnector) handleConfigUpdate(msg *proto.ConfigUpdate) { logCtx.Info("Configuration value changed; change DOES require Felix to restart.") restartNeeded = true } - return nil - }) + } if restartNeeded { fc.shutDownProcess(reasonConfigChanged) diff --git a/felix/daemon/daemon_suite_test.go b/felix/daemon/daemon_suite_test.go index a6e755c9eec..bd2cdadc374 100644 --- a/felix/daemon/daemon_suite_test.go +++ b/felix/daemon/daemon_suite_test.go @@ -17,9 +17,8 @@ package daemon_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestConfig(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/daemon_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Daemon Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_daemon_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/daemon", suiteConfig, reporterConfig) } diff --git a/felix/daemon/daemon_test.go b/felix/daemon/daemon_test.go index 26153cc682e..27bb7a9cdcc 100644 --- a/felix/daemon/daemon_test.go +++ b/felix/daemon/daemon_test.go @@ -15,7 +15,7 @@ package daemon import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" discoveryv1 "k8s.io/api/discovery/v1" diff --git a/felix/daemon/debug.go b/felix/daemon/debug.go index 31cbd3cf598..8ee032573f9 100644 --- a/felix/daemon/debug.go +++ b/felix/daemon/debug.go @@ -27,7 +27,7 @@ func panicAfter(delay time.Duration) { func simulateDataRace() { i := 0 - for j := 0; j < 3; j++ { + for range 3 { go func() { for { k := i diff --git a/felix/dataplane/driver.go b/felix/dataplane/driver.go index 1df20f0b5c5..617e5891ebb 100644 --- a/felix/dataplane/driver.go +++ b/felix/dataplane/driver.go @@ -94,11 +94,12 @@ func StartDataplaneDriver( // In BPF mode, the BPF programs use mark bits that are not configurable. Make sure that those // bits are covered by our allowed mask. if allowedMarkBits&tcdefs.MarksMask != tcdefs.MarksMask { - log.WithFields(log.Fields{ - "Name": "felix-iptables", - "MarkMask": allowedMarkBits, - "RequiredBPFBits": tcdefs.MarksMask, - }).Panic("IptablesMarkMask/NftablesMarkMask doesn't cover bits that are used (unconditionally) by eBPF mode.") + log.WithFields( + log.Fields{ + "Name": "felix-iptables", + "MarkMask": allowedMarkBits, + "RequiredBPFBits": tcdefs.MarksMask, + }).Panic("IptablesMarkMask/NftablesMarkMask doesn't cover bits that are used (unconditionally) by eBPF mode.") } allowedMarkBits ^= allowedMarkBits & tcdefs.MarksMask log.WithField("updatedBits", allowedMarkBits).Info( @@ -127,41 +128,45 @@ func StartDataplaneDriver( log.Info("Wireguard enabled, allocating a mark bit") markWireguard, _ = markBitsManager.NextSingleBitMark() if markWireguard == 0 { - log.WithFields(log.Fields{ - "Name": "felix-iptables", - "MarkMask": allowedMarkBits, - }).Panic("Failed to allocate a mark bit for wireguard, not enough mark bits available.") + log.WithFields( + log.Fields{ + "Name": "felix-iptables", + "MarkMask": allowedMarkBits, + }).Panic("Failed to allocate a mark bit for wireguard, not enough mark bits available.") } } if markAccept == 0 || markScratch0 == 0 || markPass == 0 || markScratch1 == 0 { - log.WithFields(log.Fields{ - "Name": "felix-iptables", - "MarkMask": allowedMarkBits, - }).Panic("Not enough mark bits available.") + log.WithFields( + log.Fields{ + "Name": "felix-iptables", + "MarkMask": allowedMarkBits, + }).Panic("Not enough mark bits available.") } // Mark bits for endpoint mark. Currently Felix takes the rest bits from mask available for use. markEndpointMark, allocated := markBitsManager.NextBlockBitsMark(markBitsManager.AvailableMarkBitCount()) if kubeIPVSSupportEnabled { if allocated == 0 { - log.WithFields(log.Fields{ - "Name": "felix-iptables", - "MarkMask": allowedMarkBits, - }).Panic("Not enough mark bits available for endpoint mark.") + log.WithFields( + log.Fields{ + "Name": "felix-iptables", + "MarkMask": allowedMarkBits, + }).Panic("Not enough mark bits available for endpoint mark.") } // Take lowest bit position (position 1) from endpoint mark mask reserved for non-calico endpoint. markEndpointNonCaliEndpoint = uint32(1) << uint(bits.TrailingZeros32(markEndpointMark)) } - log.WithFields(log.Fields{ - "acceptMark": markAccept, - "passMark": markPass, - "dropMark": markDrop, - "scratch0Mark": markScratch0, - "scratch1Mark": markScratch1, - "endpointMark": markEndpointMark, - "endpointMarkNonCali": markEndpointNonCaliEndpoint, - }).Info("Calculated iptables mark bits") + log.WithFields( + log.Fields{ + "acceptMark": markAccept, + "passMark": markPass, + "dropMark": markDrop, + "scratch0Mark": markScratch0, + "scratch1Mark": markScratch1, + "endpointMark": markEndpointMark, + "endpointMarkNonCali": markEndpointNonCaliEndpoint, + }).Info("Calculated iptables mark bits") // Create a routing table manager. There are certain components that should take specific indices in the range // to simplify table tidy-up. @@ -200,9 +205,10 @@ func StartDataplaneDriver( // Code defensively here as k8sClientSet may be nil for certain FV tests e.g. OpenStack felixNode, err := k8sClientSet.CoreV1().Nodes().Get(context.Background(), felixHostname, v1.GetOptions{}) if err != nil { - log.WithFields(log.Fields{ - "FelixHostname": felixHostname, - }).Info("Unable to extract node labels from Felix host") + log.WithFields( + log.Fields{ + "FelixHostname": felixHostname, + }).Info("Unable to extract node labels from Felix host") } felixNodeZone = felixNode.Labels[coreV1.LabelTopologyZone] @@ -219,7 +225,7 @@ func StartDataplaneDriver( }, RulesConfig: rules.Config{ FlowLogsEnabled: configParams.FlowLogsEnabled(), - NFTables: configParams.NFTablesMode == "Enabled", + NFTablesMode: configParams.NFTablesMode, WorkloadIfacePrefixes: configParams.InterfacePrefixes(), IPSetConfigV4: ipsets.NewIPVersionConfig( @@ -274,7 +280,10 @@ func StartDataplaneDriver( WireguardEncryptHostTraffic: configParams.WireguardHostEncryptionEnabled, RouteSource: configParams.RouteSource, - LogPrefix: configParams.LogPrefix, + LogPrefix: configParams.LogPrefix, + LogActionRateLimit: configParams.LogActionRateLimit, + LogActionRateLimitBurst: configParams.LogActionRateLimitBurst, + EndpointToHostAction: configParams.DefaultEndpointToHostAction, FilterAllowAction: configParams.FilterAllowAction(), MangleAllowAction: configParams.MangleAllowAction(), @@ -329,8 +338,6 @@ func StartDataplaneDriver( IPSetsRefreshInterval: configParams.IpsetsRefreshInterval, IptablesPostWriteCheckInterval: configParams.IptablesPostWriteCheckIntervalSecs, IptablesInsertMode: configParams.ChainInsertMode, - IptablesLockFilePath: configParams.IptablesLockFilePath, - IptablesLockTimeout: configParams.IptablesLockTimeoutSecs, IptablesLockProbeInterval: configParams.IptablesLockProbeIntervalMillis, MaxIPSetSize: configParams.MaxIpsetSize, IPv6Enabled: configParams.Ipv6Support, @@ -377,7 +384,7 @@ func StartDataplaneDriver( BPFL3IfacePattern: configParams.BPFL3IfacePattern, BPFCgroupV2: configParams.DebugBPFCgroupV2, KubeProxyMinSyncPeriod: configParams.BPFKubeProxyMinSyncPeriod, - KubeProxyHealtzPort: configParams.BPFKubeProxyHealtzPort, + KubeProxyHealtzPort: configParams.BPFKubeProxyHealthzPort, BPFPSNATPorts: configParams.BPFPSNATPorts, BPFMapSizeRoute: configParams.BPFMapSizeRoute, BPFMapSizeNATFrontend: configParams.BPFMapSizeNATFrontend, @@ -389,6 +396,8 @@ func StartDataplaneDriver( BPFMapSizeConntrackCleanupQueue: configParams.BPFMapSizeConntrackCleanupQueue, BPFMapSizeIPSets: configParams.BPFMapSizeIPSets, BPFMapSizeIfState: configParams.BPFMapSizeIfState, + BPFMapSizeMaglev: configParams.BPFMapSizeMaglev(), + BPFMaglevLUTSize: configParams.BPFLUTSizeMaglev(), BPFEnforceRPF: configParams.BPFEnforceRPF, BPFDisableGROForIfaces: configParams.BPFDisableGROForIfaces, BPFExportBufferSizeMB: configParams.BPFExportBufferSizeMB, diff --git a/felix/dataplane/driver_defs.go b/felix/dataplane/driver_defs.go index c48f26561ea..23d47f2efd0 100644 --- a/felix/dataplane/driver_defs.go +++ b/felix/dataplane/driver_defs.go @@ -15,6 +15,6 @@ package dataplane type DataplaneDriver interface { - SendMessage(msg interface{}) error - RecvMessage() (msg interface{}, err error) + SendMessage(msg any) error + RecvMessage() (msg any, err error) } diff --git a/felix/dataplane/external/ext_dataplane.go b/felix/dataplane/external/ext_dataplane.go index f427cc7ce9c..cee86b9119d 100644 --- a/felix/dataplane/external/ext_dataplane.go +++ b/felix/dataplane/external/ext_dataplane.go @@ -91,7 +91,7 @@ type extDataplaneConn struct { nextSeqNumber uint64 } -func (c *extDataplaneConn) RecvMessage() (msg interface{}, err error) { +func (c *extDataplaneConn) RecvMessage() (msg any, err error) { buf := make([]byte, 8) _, err = io.ReadFull(c.fromDataplane, buf) if err != nil { @@ -133,7 +133,7 @@ func (c *extDataplaneConn) RecvMessage() (msg interface{}, err error) { return } -func (fc *extDataplaneConn) SendMessage(msg interface{}) error { +func (fc *extDataplaneConn) SendMessage(msg any) error { log.Debugf("Writing msg (%v) to felix: %#v", fc.nextSeqNumber, msg) envelope, err := WrapPayloadWithEnvelope(msg, fc.nextSeqNumber) @@ -169,7 +169,7 @@ func (fc *extDataplaneConn) SendMessage(msg interface{}) error { return nil } -func WrapPayloadWithEnvelope(msg interface{}, seqNo uint64) (*proto.ToDataplane, error) { +func WrapPayloadWithEnvelope(msg any, seqNo uint64) (*proto.ToDataplane, error) { // Wrap the payload message in an envelope so that protobuf takes care of deserialising // it as the correct type. envelope := &proto.ToDataplane{ diff --git a/felix/dataplane/external/ext_dataplane_test.go b/felix/dataplane/external/ext_dataplane_test.go new file mode 100644 index 00000000000..d36587c1b3b --- /dev/null +++ b/felix/dataplane/external/ext_dataplane_test.go @@ -0,0 +1,97 @@ +// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// +// 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 extdataplane + +import ( + "reflect" + "testing" + + "google.golang.org/protobuf/reflect/protoregistry" + + "github.com/projectcalico/calico/felix/proto" + "github.com/projectcalico/calico/libcalico-go/lib/logutils" +) + +func TestWrapPayloadWithEnvelopeMainline(t *testing.T) { + logutils.ConfigureLoggingForTestingT(t) + msg := &proto.ConfigUpdate{ + Config: map[string]string{ + "key1": "value1", + }, + } + enveloped, err := WrapPayloadWithEnvelope(msg, 10) + if err != nil { + t.Fatalf("Unexpected error wrapping payload: %v", err) + } + if enveloped.SequenceNumber != 10 { + t.Errorf("Expected SeqNum 10 but got %v", enveloped.SequenceNumber) + } + + out := enveloped.GetConfigUpdate() + if !reflect.DeepEqual(out, msg) { + t.Errorf("Expected %v but got %v", msg, out) + } +} + +func TestAllPayloadTypes(t *testing.T) { + logutils.ConfigureLoggingForTestingT(t) + env := &proto.ToDataplane{} + payload := env.ProtoReflect().Descriptor().Oneofs().ByName("payload") + t.Log("Number of payload types:", payload.Fields().Len()) + for i := 0; i < payload.Fields().Len(); i++ { + field := payload.Fields().Get(i) + msgDesc := field.Message() + msgType, err := protoregistry.GlobalTypes.FindMessageByName(msgDesc.FullName()) + if err != nil { + t.Fatalf("Failed to find message type for %v: %v", msgDesc.FullName(), err) + } + msg := msgType.New().Interface() + enveloped, err := WrapPayloadWithEnvelope(msg, 42) + if err != nil { + t.Errorf("Unexpected error wrapping payload of type %v: %v", msgDesc.FullName(), err) + continue + } + if enveloped.SequenceNumber != 42 { + t.Errorf("Expected SeqNum 42 but got %v for payload type %v", enveloped.SequenceNumber, msgDesc.FullName()) + } + // Use reflection to get the payload back out. + gotField := enveloped.ProtoReflect().WhichOneof(payload) + if gotField == nil { + t.Errorf("Expected payload field to be set for type %v but was nil", msgDesc.FullName()) + continue + } + if gotField.FullName() != field.FullName() { + t.Errorf("Expected payload field %v but got %v", field.FullName(), gotField.FullName()) + continue + } + } +} + +func BenchmarkWrapPayloadWithEnvelope(b *testing.B) { + logutils.ConfigureLoggingForTestingTB(b) + msg := &proto.ConfigUpdate{ + Config: map[string]string{ + "key1": "value1", + }, + } + + b.ReportAllocs() + for b.Loop() { + _, err := WrapPayloadWithEnvelope(msg, 123) + if err != nil { + b.Fatalf("Unexpected error wrapping payload: %v", err) + } + } +} diff --git a/felix/dataplane/inactive/inactive_dataplane.go b/felix/dataplane/inactive/inactive_dataplane.go index 62a193da09d..5520da31e8a 100644 --- a/felix/dataplane/inactive/inactive_dataplane.go +++ b/felix/dataplane/inactive/inactive_dataplane.go @@ -19,12 +19,12 @@ package inactive type InactiveDataplane struct { } -func (i *InactiveDataplane) SendMessage(msg interface{}) error { +func (i *InactiveDataplane) SendMessage(msg any) error { // Do nothing. return nil } -func (i *InactiveDataplane) RecvMessage() (msg interface{}, err error) { +func (i *InactiveDataplane) RecvMessage() (msg any, err error) { // Since this dataplane does nothing, we have no messages communicate. msgChan := make(chan struct{}) return <-msgChan, nil diff --git a/felix/dataplane/ipsets/ipsets_mgr.go b/felix/dataplane/ipsets/ipsets_mgr.go index c7a796d7251..afa45abfd06 100644 --- a/felix/dataplane/ipsets/ipsets_mgr.go +++ b/felix/dataplane/ipsets/ipsets_mgr.go @@ -84,7 +84,7 @@ func (m *IPSetsManager) GetIPSetMembers(setID string) (members set.Set[string], return } -func (m *IPSetsManager) OnUpdate(msg interface{}) { +func (m *IPSetsManager) OnUpdate(msg any) { switch msg := msg.(type) { // IP set-related messages, these are extremely common. case *proto.IPSetDeltaUpdate: diff --git a/felix/dataplane/ipsets/ipsets_mgr_test.go b/felix/dataplane/ipsets/ipsets_mgr_test.go index ee8c6c562bd..c67d25b631e 100644 --- a/felix/dataplane/ipsets/ipsets_mgr_test.go +++ b/felix/dataplane/ipsets/ipsets_mgr_test.go @@ -15,7 +15,7 @@ package ipsets import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/proto" diff --git a/felix/dataplane/ipsets/ipsets_mgr_ut_suite_test.go b/felix/dataplane/ipsets/ipsets_mgr_ut_suite_test.go index 615443cc7cc..ddc3a1a35e5 100644 --- a/felix/dataplane/ipsets/ipsets_mgr_ut_suite_test.go +++ b/felix/dataplane/ipsets/ipsets_mgr_ut_suite_test.go @@ -17,9 +17,8 @@ package ipsets import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestIPSetMgr(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/ipset_mgr_ut_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "IPSet Mgr Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/felix_dataplane_ipsets_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/dataplane/ipsets", suiteConfig, reporterConfig) } diff --git a/felix/dataplane/linux/bpf_ep_mgr.go b/felix/dataplane/linux/bpf_ep_mgr.go index cc94ec23eb5..5e431943e24 100644 --- a/felix/dataplane/linux/bpf_ep_mgr.go +++ b/felix/dataplane/linux/bpf_ep_mgr.go @@ -31,6 +31,7 @@ import ( "reflect" "regexp" "runtime" + "slices" "sort" "strconv" "strings" @@ -177,8 +178,8 @@ type bpfDataplane interface { setRPFilter(iface string, val int) error setRoute(ip.CIDR) delRoute(ip.CIDR) - ruleMatchID(dir rules.RuleDir, action string, owner rules.RuleOwnerType, idx int, name string) polprog.RuleMatchID - loadDefaultPolicies() error + ruleMatchID(dir rules.RuleDir, action string, owner rules.RuleOwnerType, idx int, id types.IDMaker) polprog.RuleMatchID + loadDefaultPolicies(hk hook.Hook) error loadTCLogFilter(ap *tc.AttachPoint) (fileDescriptor, int, error) interfaceByIndex(int) (*net.Interface, error) queryClassifier(string, string) bool @@ -192,6 +193,7 @@ type hasLoadPolicyProgram interface { rules polprog.Rules, staticProgsMap maps.Map, polProgsMap maps.Map, + attachType uint32, opts ...polprog.Option, ) ([]fileDescriptor, []asm.Insns, error) } @@ -297,7 +299,6 @@ type bpfEndpointManager struct { logFilters map[string]string bpfLogLevel string hostname string - fibLookupEnabled bool dataIfaceRegex *regexp.Regexp l3IfaceRegex *regexp.Regexp workloadIfaceRegex *regexp.Regexp @@ -316,11 +317,9 @@ type bpfEndpointManager struct { legacyCleanUp bool hostIfaceTrees bpfIfaceTrees - jumpMapAlloc *jumpMapAlloc - xdpJumpMapAlloc *jumpMapAlloc - policyDefaultObj *libbpf.Obj - policyTcAllowFD bpf.ProgFD - policyTcDenyFD bpf.ProgFD + jumpMapAllocs map[hook.Hook]*jumpMapAlloc + policyTcAllowFDs [2]bpf.ProgFD + policyTcDenyFDs [2]bpf.ProgFD ruleRenderer bpfAllowChainRenderer @@ -336,6 +335,7 @@ type bpfEndpointManager struct { rules polprog.Rules, staticProgsMap maps.Map, polProgsMap maps.Map, + attachType uint32, opts ...polprog.Option, ) ([]fileDescriptor, []asm.Insns, error) updatePolicyProgramFn func(rules polprog.Rules, polDir string, ap attachPoint, ipFamily proto.IPVersion) error @@ -401,7 +401,8 @@ type bpfEndpointManager struct { healthAggregator *health.HealthAggregator updateRateLimitedLog *logutilslc.RateLimitedLogger - QoSMap maps.MapWithUpdateWithFlags + QoSMap maps.MapWithUpdateWithFlags + maglevLUTSize int } type bpfEndpointManagerDataplane struct { @@ -416,6 +417,7 @@ type bpfEndpointManagerDataplane struct { tunnelIP net.IP iptablesFilterTable Table ipSetIDAlloc *idalloc.IDAllocator + workloadRemoveChan chan string } type serviceKey struct { @@ -436,7 +438,6 @@ func NewBPFEndpointManager( dp bpfDataplane, config *Config, bpfmaps *bpfmap.Maps, - fibLookupEnabled bool, workloadIfaceRegex *regexp.Regexp, ipSetIDAllocV4 *idalloc.IDAllocator, ipSetIDAllocV6 *idalloc.IDAllocator, @@ -451,6 +452,7 @@ func NewBPFEndpointManager( healthAggregator *health.HealthAggregator, dataplanefeatures *environment.Features, bpfIfaceMTU int, + workloadRemoveChanV4, workloadRemoveChanV6 chan string, ) (*bpfEndpointManager, error) { if livenessCallback == nil { livenessCallback = func() {} @@ -472,7 +474,6 @@ func NewBPFEndpointManager( bpfLogLevel: config.BPFLogLevel, logFilters: config.BPFLogFilters, hostname: config.Hostname, - fibLookupEnabled: fibLookupEnabled, l3IfaceRegex: config.BPFL3IfacePattern, workloadIfaceRegex: workloadIfaceRegex, epToHostAction: config.RulesConfig.EndpointToHostAction, @@ -494,8 +495,11 @@ func NewBPFEndpointManager( // Note: the allocators only allocate a fraction of the map, the // rest is reserved for sub-programs generated if a single program // would be too large. - jumpMapAlloc: newJumpMapAlloc(jump.TCMaxEntryPoints), - xdpJumpMapAlloc: newJumpMapAlloc(jump.XDPMaxEntryPoints), + jumpMapAllocs: map[hook.Hook]*jumpMapAlloc{ + hook.Ingress: newJumpMapAlloc("ingress", jump.TCMaxEntryPoints), + hook.Egress: newJumpMapAlloc("egress", jump.TCMaxEntryPoints), + hook.XDP: newJumpMapAlloc("xdp", jump.XDPMaxEntryPoints), + }, ruleRenderer: iptablesRuleRenderer, onStillAlive: livenessCallback, lookupsCache: lookupsCache, @@ -519,7 +523,8 @@ func NewBPFEndpointManager( profiling: config.BPFProfiling, bpfAttachType: config.BPFAttachType, - QoSMap: bpfmaps.CommonMaps.QoSMap, + QoSMap: bpfmaps.CommonMaps.QoSMap, + maglevLUTSize: config.BPFMaglevLUTSize, } m.policyTrampolineStride.Store(int32(asm.TrampolineStrideDefault)) @@ -541,6 +546,10 @@ func NewBPFEndpointManager( specialInterfaces = append(specialInterfaces, config.RulesConfig.WireguardInterfaceNameV6) } + if config.RulesConfig.IPIPEnabled || config.RulesConfig.WireguardEnabled || config.RulesConfig.WireguardEnabledV6 { + m.overlayTunnelID = 1 + } + for i, d := range specialInterfaces { specialInterfaces[i] = "^" + regexp.QuoteMeta(d) + "$" } @@ -594,10 +603,10 @@ func NewBPFEndpointManager( m.bpfAttachType = apiv3.BPFAttachOptionTC } } - m.v4 = newBPFEndpointManagerDataplane(proto.IPVersion_IPV4, bpfmaps.V4, iptablesFilterTableV4, ipSetIDAllocV4, m) + m.v4 = newBPFEndpointManagerDataplane(proto.IPVersion_IPV4, bpfmaps.V4, iptablesFilterTableV4, ipSetIDAllocV4, workloadRemoveChanV4, m) if m.ipv6Enabled { - m.v6 = newBPFEndpointManagerDataplane(proto.IPVersion_IPV6, bpfmaps.V6, iptablesFilterTableV6, ipSetIDAllocV6, m) + m.v6 = newBPFEndpointManagerDataplane(proto.IPVersion_IPV6, bpfmaps.V6, iptablesFilterTableV6, ipSetIDAllocV6, workloadRemoveChanV6, m) } if m.hostNetworkedNATMode != hostNetworkedNATDisabled { @@ -690,6 +699,7 @@ func newBPFEndpointManagerDataplane( ipMaps *bpfmap.IPMaps, iptablesFilterTable Table, ipSetIDAlloc *idalloc.IDAllocator, + workloadRemoveChan chan string, epMgr *bpfEndpointManager, ) *bpfEndpointManagerDataplane { return &bpfEndpointManagerDataplane{ @@ -699,6 +709,7 @@ func newBPFEndpointManagerDataplane( IPMaps: ipMaps, iptablesFilterTable: iptablesFilterTable, ipSetIDAlloc: ipSetIDAlloc, + workloadRemoveChan: workloadRemoveChan, } } @@ -717,12 +728,14 @@ func (m *bpfEndpointManager) repinJumpMaps() error { } mps := []maps.Map{ - m.commonMaps.ProgramsMap, - m.commonMaps.JumpMap, m.commonMaps.XDPProgramsMap, m.commonMaps.XDPJumpMap, } + for _, mp := range m.commonMaps.JumpMaps { + mps = append(mps, mp) + } + mps = append(mps, m.commonMaps.ProgramsMaps...) for _, mp := range mps { pin := path.Join(tmp, mp.GetName()) if err := libbpf.ObjPin(int(mp.MapFD()), pin); err != nil { @@ -822,7 +835,7 @@ func (m *bpfEndpointManager) updateHostIP(ipAddr string, ipFamily int) { } } -func (m *bpfEndpointManager) OnUpdate(msg interface{}) { +func (m *bpfEndpointManager) OnUpdate(msg any) { switch msg := msg.(type) { // Updates from the dataplane: @@ -923,7 +936,7 @@ func (d *bpfEndpointManagerDataplane) updateIfaceIP(update *ifaceAddrsUpdate) bo isDirty := false if update.Addrs != nil && update.Addrs.Len() > 0 { logrus.Debugf("Interface %+v received address update %+v", update.Name, update.Addrs) - update.Addrs.Iter(func(item string) error { + for item := range update.Addrs.All() { ip := net.ParseIP(item) if d.ipFamily == proto.IPVersion_IPV6 { if ip.To4() == nil && !ip.IsLinkLocalUnicast() { @@ -932,8 +945,7 @@ func (d *bpfEndpointManagerDataplane) updateIfaceIP(update *ifaceAddrsUpdate) bo } else if ip.To4() != nil { ipAddrs = append(ipAddrs, ip) } - return nil - }) + } sort.Slice(ipAddrs, func(i, j int) bool { return bytes.Compare(ipAddrs[i], ipAddrs[j]) < 0 }) @@ -963,14 +975,8 @@ func (m *bpfEndpointManager) reclaimPolicyIdx(name string, ipFamily int, iface * if err := m.jumpMapDelete(attachHook, idx.policyIdx[attachHook]); err != nil { logrus.WithError(err).Warn("Policy program may leak.") } - if attachHook != hook.XDP { - if err := m.jumpMapAlloc.Put(idx.policyIdx[attachHook], name); err != nil { - logrus.WithError(err).Errorf("Policy family %d, hook %s", ipFamily, attachHook) - } - } else { - if err := m.xdpJumpMapAlloc.Put(idx.policyIdx[attachHook], name); err != nil { - logrus.WithError(err).Error(attachHook.String()) - } + if err := m.jumpMapAllocs[attachHook].Put(idx.policyIdx[attachHook], name); err != nil { + logrus.WithError(err).Errorf("Policy family %d, hook %s", ipFamily, attachHook) } } } @@ -980,7 +986,7 @@ func (m *bpfEndpointManager) reclaimFilterIdx(name string, iface *bpfInterface) if err := m.jumpMapDelete(attachHook, iface.dpState.filterIdx[attachHook]); err != nil { logrus.WithError(err).Warn("Filter program may leak.") } - if err := m.jumpMapAlloc.Put(iface.dpState.filterIdx[attachHook], name); err != nil { + if err := m.jumpMapAllocs[attachHook].Put(iface.dpState.filterIdx[attachHook], name); err != nil { logrus.WithError(err).Errorf("Filter hook %s", attachHook) } iface.dpState.filterIdx[attachHook] = -1 @@ -1299,6 +1305,18 @@ func (m *bpfEndpointManager) onWorkloadEndpointRemove(msg *proto.WorkloadEndpoin }) // Remove policy debug info if any m.removeIfaceAllPolicyDebugInfo(oldWEP.Name) + if m.v4 != nil && m.v4.workloadRemoveChan != nil { + for _, addr := range oldWEP.GetIpv4Nets() { + addr = strings.SplitN(addr, "/", 2)[0] + m.v4.workloadRemoveChan <- addr + } + } + if m.v6 != nil && m.v6.workloadRemoveChan != nil { + for _, addr := range oldWEP.GetIpv6Nets() { + addr = strings.SplitN(addr, "/", 2)[0] + m.v6.workloadRemoveChan <- addr + } + } } // onPolicyUpdate stores the policy in the cache and marks any endpoints using it dirty. @@ -1309,7 +1327,7 @@ func (m *bpfEndpointManager) onPolicyUpdate(msg *proto.ActivePolicyUpdate) { // Note, polID includes the tier name as well as the policy name. m.markEndpointsDirty(m.policiesToWorkloads[polID], "policy") if m.bpfPolicyDebugEnabled { - m.updatePolicyCache(polID.Name, "Policy", m.policies[polID].InboundRules, m.policies[polID].OutboundRules) + m.updatePolicyCache(polID, m.policies[polID].InboundRules, m.policies[polID].OutboundRules) } } @@ -1323,8 +1341,8 @@ func (m *bpfEndpointManager) onPolicyRemove(msg *proto.ActivePolicyRemove) { delete(m.policies, polID) delete(m.policiesToWorkloads, polID) if m.bpfPolicyDebugEnabled { - m.dirtyRules.AddSet(m.polNameToMatchIDs[polID.Name]) - delete(m.polNameToMatchIDs, polID.Name) + m.dirtyRules.AddSet(m.polNameToMatchIDs[polID.ID()]) + delete(m.polNameToMatchIDs, polID.ID()) } } @@ -1335,7 +1353,7 @@ func (m *bpfEndpointManager) onProfileUpdate(msg *proto.ActiveProfileUpdate) { m.profiles[profID] = msg.Profile m.markEndpointsDirty(m.profilesToWorkloads[profID], "profile") if m.bpfPolicyDebugEnabled { - m.updatePolicyCache(profID.Name, "Profile", m.profiles[profID].InboundRules, m.profiles[profID].OutboundRules) + m.updatePolicyCacheProfile(profID, m.profiles[profID].InboundRules, m.profiles[profID].OutboundRules) } } @@ -1348,14 +1366,14 @@ func (m *bpfEndpointManager) onProfileRemove(msg *proto.ActiveProfileRemove) { delete(m.profiles, profID) delete(m.profilesToWorkloads, profID) if m.bpfPolicyDebugEnabled { - m.dirtyRules.AddSet(m.polNameToMatchIDs[profID.Name]) - delete(m.polNameToMatchIDs, profID.Name) + m.dirtyRules.AddSet(m.polNameToMatchIDs[profID.ID()]) + delete(m.polNameToMatchIDs, profID.ID()) } } func (m *bpfEndpointManager) removeDirtyPolicies() { b := make([]byte, 8) - m.dirtyRules.Iter(func(item polprog.RuleMatchID) error { + for item := range m.dirtyRules.All() { binary.LittleEndian.PutUint64(b, item) logrus.WithField("ruleId", item).Debug("deleting entry") err := m.commonMaps.RuleCountersMap.Delete(b) @@ -1363,8 +1381,8 @@ func (m *bpfEndpointManager) removeDirtyPolicies() { logrus.WithField("ruleId", item).Info("error deleting entry") } - return set.RemoveItem - }) + m.dirtyRules.Discard(item) + } } func (m *bpfEndpointManager) markEndpointsDirty(ids set.Set[any], kind string) { @@ -1372,7 +1390,7 @@ func (m *bpfEndpointManager) markEndpointsDirty(ids set.Set[any], kind string) { // Hear about the policy/profile before the endpoint. return } - ids.Iter(func(item any) error { + for item := range ids.All() { switch id := item.(type) { case types.WorkloadEndpointID: m.markExistingWEPDirty(id, kind) @@ -1390,8 +1408,7 @@ func (m *bpfEndpointManager) markEndpointsDirty(ids set.Set[any], kind string) { m.dirtyIfaceNames.AddAll(m.hostIfaceTrees.getPhyDevices(id)) } } - return nil - }) + } } func (m *bpfEndpointManager) markExistingWEPDirty(wlID types.WorkloadEndpointID, mapping string) { @@ -1404,17 +1421,22 @@ func (m *bpfEndpointManager) markExistingWEPDirty(wlID types.WorkloadEndpointID, } } -func jumpMapDeleteEntry(m maps.Map, idx, stride int) error { - for subProg := 0; subProg < jump.MaxSubPrograms; subProg++ { +func jumpMapDeleteSubProgs(m maps.Map, idx, stride int) error { + for subProg := range jump.MaxSubPrograms { if err := m.Delete(jump.Key(polprog.SubProgramJumpIdx(idx, subProg, stride))); err != nil { if maps.IsNotExists(err) { - logrus.WithError(err).WithField("idx", idx).Debug( - "Policy program already gone from map.") + if subProg == 0 { + logrus.WithField("idx", idx).Debug( + "First policy program already gone from map.") + } else { + logrus.WithField("idx", idx).Debug( + "Policy sub-program not present, stopping loop early.") + } return nil - } else { - logrus.WithError(err).Warn("Failed to delete policy program from map; policy program may leak.") - return err } + + logrus.WithError(err).Warn("Failed to delete policy program from map; policy program may leak.") + return err } } @@ -1426,9 +1448,6 @@ func (m *bpfEndpointManager) interfaceByIndex(ifindex int) (*net.Interface, erro } func (m *bpfEndpointManager) syncIfStateMap() { - tcSeenIndexes := set.New[int]() - xdpSeenIndexes := set.New[int]() - m.ifacesLock.Lock() defer m.ifacesLock.Unlock() @@ -1446,19 +1465,25 @@ func (m *bpfEndpointManager) syncIfStateMap() { v.XDPPolicyV6, } { if idx := fn(); idx != -1 { - _ = jumpMapDeleteEntry(m.commonMaps.XDPJumpMap, idx, jump.XDPMaxEntryPoints) + _ = jumpMapDeleteSubProgs(m.commonMaps.XDPJumpMap, idx, jump.XDPMaxEntryPoints) } } for _, fn := range []func() int{ v.IngressPolicyV4, - v.EgressPolicyV4, v.IngressPolicyV6, - v.EgressPolicyV6, v.TcIngressFilter, + } { + if idx := fn(); idx != -1 { + _ = jumpMapDeleteSubProgs(m.commonMaps.JumpMaps[hook.Ingress], idx, jump.TCMaxEntryPoints) + } + } + for _, fn := range []func() int{ + v.EgressPolicyV4, + v.EgressPolicyV6, v.TcEgressFilter, } { if idx := fn(); idx != -1 { - _ = jumpMapDeleteEntry(m.commonMaps.JumpMap, idx, jump.TCMaxEntryPoints) + _ = jumpMapDeleteSubProgs(m.commonMaps.JumpMaps[hook.Egress], idx, jump.TCMaxEntryPoints) } } } else { @@ -1486,22 +1511,11 @@ func (m *bpfEndpointManager) syncIfStateMap() { if idx < 0 { return } - var alloc *jumpMapAlloc - var seenIndexes set.Set[int] - if h == hook.XDP { - alloc = m.xdpJumpMapAlloc - seenIndexes = xdpSeenIndexes - } else { - alloc = m.jumpMapAlloc - seenIndexes = tcSeenIndexes - } - if err := alloc.Assign(idx, netiface.Name); err != nil { + if err := m.jumpMapAllocs[h].Assign(idx, netiface.Name); err != nil { // Conflict with another program; need to alloc a new index. logrus.WithError(err).Error("Start of day resync found invalid jump map index, " + "allocate a fresh one.") idx = -1 - } else { - seenIndexes.Add(idx) } indexMap[h] = idx } @@ -1590,8 +1604,10 @@ func (m *bpfEndpointManager) syncIfaceProperties() error { return nil } -func (m *bpfEndpointManager) loadDefaultPolicies() error { - file := path.Join(bpfdefs.ObjectDir, "policy_default.o") +// loadDefaultPolicies loads the default allow and deny policy programs for the given hook +// and not policy direction. +func (m *bpfEndpointManager) loadDefaultPolicies(hk hook.Hook) error { + file := path.Join(bpfdefs.ObjectDir, fmt.Sprintf("policy_default_%s.o", hk)) obj, err := libbpf.OpenObject(file) if err != nil { return fmt.Errorf("file %s: %w", file, err) @@ -1612,23 +1628,34 @@ func (m *bpfEndpointManager) loadDefaultPolicies() error { } } + if m.bpfAttachType == apiv3.BPFAttachOptionTCX { + for p, err := obj.FirstProgram(); p != nil && err == nil; p, err = p.NextProgram() { + attachType := libbpf.AttachTypeTcxEgress + if hk == hook.Ingress { + attachType = libbpf.AttachTypeTcxIngress + } + if err := obj.SetAttachType(p.Name(), attachType); err != nil { + return fmt.Errorf("error setting attach type for program %s: %w", p.Name(), err) + } + } + } + if err := obj.Load(); err != nil { return fmt.Errorf("default policies: %w", err) } - m.policyDefaultObj = obj - fd, err := obj.ProgramFD("calico_tc_deny") if err != nil { return fmt.Errorf("failed to load default deny policy program: %w", err) } - m.policyTcDenyFD = bpf.ProgFD(fd) + m.policyTcDenyFDs[hk] = bpf.ProgFD(fd) fd, err = obj.ProgramFD("calico_tc_allow") if err != nil { return fmt.Errorf("failed to load default allow policy program: %w", err) } - m.policyTcAllowFD = bpf.ProgFD(fd) + m.policyTcAllowFDs[hk] = bpf.ProgFD(fd) + return nil } @@ -1645,25 +1672,26 @@ func (m *bpfEndpointManager) CompleteDeferredWork() error { logrus.WithError(err).Fatal("Cannot load interface state map - essential for consistent operation.") } - m.initUnknownIfaces.Iter(func(iface string) error { + for iface := range m.initUnknownIfaces.All() { if ai, ok := m.initAttaches[iface]; ok { if err := m.cleanupOldAttach(iface, ai); err != nil { logrus.WithError(err).Warnf("Failed to detach old programs from now unused device '%s'", iface) } else { delete(m.initAttaches, iface) - return set.RemoveItem + m.initUnknownIfaces.Discard(iface) } } - return nil - }) + } // Makes sure that we delete entries for non-existing devices and preserve entries // for those that exists until we can make sure that they did (not) change. m.syncIfStateMap() logrus.Info("BPF Interface state map synced.") - if err := m.dp.loadDefaultPolicies(); err != nil { - logrus.WithError(err).Warn("Failed to load default policies, some programs may default to DENY.") + for _, hk := range []hook.Hook{hook.Ingress, hook.Egress} { + if err := m.dp.loadDefaultPolicies(hk); err != nil { + logrus.WithError(err).Warn("Failed to load default policies, some programs may default to DENY.") + } } logrus.Info("Default BPF policy programs loaded.") @@ -1686,12 +1714,12 @@ func (m *bpfEndpointManager) CompleteDeferredWork() error { if m.hostNetworkedNATMode != hostNetworkedNATDisabled { // Update all existing IPs of dirty services - m.dirtyServices.Iter(func(svc serviceKey) error { + for svc := range m.dirtyServices.All() { for _, ip := range m.services[svc] { m.dp.setRoute(ip) } - return set.RemoveItem - }) + m.dirtyServices.Discard(svc) + } } if err := m.ifStateMap.ApplyAllChanges(); err != nil { @@ -1717,7 +1745,8 @@ func (m *bpfEndpointManager) CompleteDeferredWork() error { var err error if m.v6 != nil { err = m.v6.CtMap.CopyDeltaFromOldMap() - } else { + } + if m.v4 != nil { err = m.v4.CtMap.CopyDeltaFromOldMap() } if err != nil { @@ -1740,10 +1769,9 @@ func (m *bpfEndpointManager) CompleteDeferredWork() error { } m.reportHealth(true, "") } else { - m.dirtyIfaceNames.Iter(func(iface string) error { + for iface := range m.dirtyIfaceNames.All() { m.updateRateLimitedLog.WithField("name", iface).Info("Interface remains dirty.") - return nil - }) + } m.reportHealth(false, "Failed to configure some interfaces.") } @@ -1819,12 +1847,10 @@ func (m *bpfEndpointManager) doApplyPolicyToDataIface(iface, masterIface string, } if m.v6 != nil { - parallelWG.Add(1) - go func() { - defer parallelWG.Done() + parallelWG.Go(func() { ingressAP6, egressAP6, xdpAP6, err6 = m.v6.applyPolicyToDataIface(iface, hepPtr, &state, tcAttachPoint, xdpAttachPoint, xdpMode) - }() + }) } if m.v4 != nil { ingressAP4, egressAP4, xdpAP4, err4 = m.v4.applyPolicyToDataIface(iface, hepPtr, &state, @@ -1834,20 +1860,16 @@ func (m *bpfEndpointManager) doApplyPolicyToDataIface(iface, masterIface string, parallelWG.Wait() // Attach ingress program. - parallelWG.Add(1) - go func() { - defer parallelWG.Done() + parallelWG.Go(func() { ingressAP := mergeAttachPoints(ingressAP4, ingressAP6) if ingressAP != nil { m.loadFilterProgram(ingressAP) ingressErr = m.dp.ensureProgramAttached(ingressAP) } - }() + }) // Attach xdp program. - parallelWG.Add(1) - go func() { - defer parallelWG.Done() + parallelWG.Go(func() { xdpAP := mergeAttachPoints(xdpAP4, xdpAP6) if xdpAP != nil { if hepPtr != nil && len(hepPtr.UntrackedTiers) == 1 { @@ -1856,7 +1878,7 @@ func (m *bpfEndpointManager) doApplyPolicyToDataIface(iface, masterIface string, xdpErr = m.dp.ensureNoProgram(xdpAP) } } - }() + }) // Attach egress program. egressAP := mergeAttachPoints(egressAP4, egressAP6) @@ -1912,16 +1934,16 @@ func (m *bpfEndpointManager) applyProgramsToDirtyDataInterfaces() { var mutex sync.Mutex errs := map[string]error{} var wg sync.WaitGroup - m.dirtyIfaceNames.Iter(func(iface string) error { + for iface := range m.dirtyIfaceNames.All() { if !m.isDataIface(iface) && !m.isL3Iface(iface) { logrus.WithField("iface", iface).Debug( "Ignoring interface that doesn't match the host data/l3 interface regex") if !m.isWorkloadIface(iface) { logrus.WithField("iface", iface).Debug( "Removing interface that doesn't match the host data/l3 interface and is not workload interface") - return set.RemoveItem + m.dirtyIfaceNames.Discard(iface) } - return nil + continue } xdpMode := XDPModeAll attachTc := true @@ -2019,8 +2041,7 @@ func (m *bpfEndpointManager) applyProgramsToDirtyDataInterfaces() { errs[iface] = err mutex.Unlock() }(iface) - return nil - }) + } wg.Wait() // We can hold the lock for the whole iteration below because nothing else @@ -2059,9 +2080,9 @@ func (m *bpfEndpointManager) updateWEPsInDataplane() { maxWorkers := runtime.GOMAXPROCS(0) sem := semaphore.NewWeighted(int64(maxWorkers)) - m.dirtyIfaceNames.Iter(func(ifaceName string) error { + for ifaceName := range m.dirtyIfaceNames.All() { if !m.isWorkloadIface(ifaceName) { - return nil + continue } m.opReporter.RecordOperation("update-workload-iface") @@ -2086,8 +2107,7 @@ func (m *bpfEndpointManager) updateWEPsInDataplane() { errs[ifaceName] = err mutex.Unlock() }(ifaceName) - return nil - }) + } wg.Wait() for ifaceName, err := range errs { @@ -2138,18 +2158,13 @@ func (m *bpfEndpointManager) updateWEPsInDataplane() { } func (m *bpfEndpointManager) allocJumpIndicesForWEP(ifaceName string, idx *bpfInterfaceJumpIndices) error { - var err error - if idx.policyIdx[hook.Ingress] == -1 { - idx.policyIdx[hook.Ingress], err = m.jumpMapAlloc.Get(ifaceName) - if err != nil { - return err - } - } - - if idx.policyIdx[hook.Egress] == -1 { - idx.policyIdx[hook.Egress], err = m.jumpMapAlloc.Get(ifaceName) - if err != nil { - return err + for _, h := range []hook.Hook{hook.Ingress, hook.Egress} { + if idx.policyIdx[h] == -1 { + var err error + idx.policyIdx[h], err = m.jumpMapAllocs[h].Get(ifaceName) + if err != nil { + return err + } } } @@ -2157,25 +2172,21 @@ func (m *bpfEndpointManager) allocJumpIndicesForWEP(ifaceName string, idx *bpfIn } func (m *bpfEndpointManager) allocJumpIndicesForDataIface(ifaceName string, xdpMode XDPMode, idx *bpfInterfaceJumpIndices) error { - var err error if xdpMode != XDPModeOnly { - if idx.policyIdx[hook.Ingress] == -1 { - idx.policyIdx[hook.Ingress], err = m.jumpMapAlloc.Get(ifaceName) - if err != nil { - return err - } - } - - if idx.policyIdx[hook.Egress] == -1 { - idx.policyIdx[hook.Egress], err = m.jumpMapAlloc.Get(ifaceName) - if err != nil { - return err + for _, h := range []hook.Hook{hook.Ingress, hook.Egress} { + if idx.policyIdx[h] == -1 { + var err error + idx.policyIdx[h], err = m.jumpMapAllocs[h].Get(ifaceName) + if err != nil { + return err + } } } } if xdpMode != XDPModeNone && idx.policyIdx[hook.XDP] == -1 { - idx.policyIdx[hook.XDP], err = m.xdpJumpMapAlloc.Get(ifaceName) + var err error + idx.policyIdx[hook.XDP], err = m.jumpMapAllocs[hook.XDP].Get(ifaceName) if err != nil { return err } @@ -2204,16 +2215,12 @@ func (m *bpfEndpointManager) wepStateFillJumps(ap *tc.AttachPoint, state *bpfInt } if ap.LogLevel == "debug" { - if state.filterIdx[hook.Ingress] == -1 { - state.filterIdx[hook.Ingress], err = m.jumpMapAlloc.Get(ap.IfaceName()) - if err != nil { - return err - } - } - if state.filterIdx[hook.Egress] == -1 { - state.filterIdx[hook.Egress], err = m.jumpMapAlloc.Get(ap.IfaceName()) - if err != nil { - return err + for _, h := range []hook.Hook{hook.Ingress, hook.Egress} { + if state.filterIdx[h] == -1 { + state.filterIdx[h], err = m.jumpMapAllocs[h].Get(ap.IfaceName()) + if err != nil { + return err + } } } } else { @@ -2221,7 +2228,7 @@ func (m *bpfEndpointManager) wepStateFillJumps(ap *tc.AttachPoint, state *bpfInt if err := m.jumpMapDelete(attachHook, state.filterIdx[attachHook]); err != nil { logrus.WithError(err).Warn("Filter program may leak.") } - if err := m.jumpMapAlloc.Put(state.filterIdx[attachHook], ap.IfaceName()); err != nil { + if err := m.jumpMapAllocs[attachHook].Put(state.filterIdx[attachHook], ap.IfaceName()); err != nil { logrus.WithError(err).Errorf("Filter hook %s", attachHook) } state.filterIdx[attachHook] = -1 @@ -2248,16 +2255,12 @@ func (m *bpfEndpointManager) dataIfaceStateFillJumps(ap *tc.AttachPoint, xdpMode } if ap.LogLevel == "debug" { - if state.filterIdx[hook.Ingress] == -1 { - state.filterIdx[hook.Ingress], err = m.jumpMapAlloc.Get(ap.IfaceName()) - if err != nil { - return err - } - } - if state.filterIdx[hook.Egress] == -1 { - state.filterIdx[hook.Egress], err = m.jumpMapAlloc.Get(ap.IfaceName()) - if err != nil { - return err + for _, h := range []hook.Hook{hook.Ingress, hook.Egress} { + if state.filterIdx[h] == -1 { + state.filterIdx[h], err = m.jumpMapAllocs[h].Get(ap.IfaceName()) + if err != nil { + return err + } } } } else { @@ -2265,7 +2268,7 @@ func (m *bpfEndpointManager) dataIfaceStateFillJumps(ap *tc.AttachPoint, xdpMode if err := m.jumpMapDelete(attachHook, state.filterIdx[attachHook]); err != nil { logrus.WithError(err).Warn("Filter program may leak.") } - if err := m.jumpMapAlloc.Put(state.filterIdx[attachHook], ap.IfaceName()); err != nil { + if err := m.jumpMapAllocs[attachHook].Put(state.filterIdx[attachHook], ap.IfaceName()); err != nil { logrus.WithError(err).Errorf("Filter hook %s", attachHook) } state.filterIdx[attachHook] = -1 @@ -2372,7 +2375,7 @@ func (m *bpfEndpointManager) doApplyPolicy(ifaceName string) (bpfInterfaceState, // Ingress packet rate is configured ap.IngressPacketRateConfigured = true - qosKey := qos.NewKey(uint32(ifindex), 1) //ingress=1 + qosKey := qos.NewKey(uint32(ifindex), 1) // ingress=1 qosValBytes, err := m.QoSMap.Get(qosKey.AsBytes()) if err != nil && !errors.Is(err, os.ErrNotExist) { logrus.WithField("ifindex", ifindex).WithError(err).Debug("Error retrieving ingress entry from QoS map.") @@ -2477,11 +2480,9 @@ func (m *bpfEndpointManager) doApplyPolicy(ifaceName string) (bpfInterfaceState, } if m.v6 != nil { - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { ingressAP6, egressAP6, err6 = m.v6.applyPolicyToWeps(v6Readiness, ifaceName, &state, wep, ap) - }() + }) } if m.v4 != nil { @@ -2500,15 +2501,13 @@ func (m *bpfEndpointManager) doApplyPolicy(ifaceName string) (bpfInterfaceState, // Attach preamble TC program if attachPreamble { - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { ingressAP := mergeAttachPoints(ingressAP4, ingressAP6) if ingressAP != nil { m.loadFilterProgram(ingressAP) ingressErr = m.dp.ensureProgramAttached(ingressAP) } - }() + }) egressAP := mergeAttachPoints(egressAP4, egressAP6) if egressAP != nil { m.loadFilterProgram(egressAP) @@ -2669,7 +2668,7 @@ func (d *bpfEndpointManagerDataplane) wepApplyPolicyToDirection(readiness ifaceR } attachHook := hook.Ingress - if polDirection == PolDirnEgress { + if polDirection == PolDirnIngress { attachHook = hook.Egress } policyIdx = indices.policyIdx[attachHook] @@ -2782,12 +2781,10 @@ func (d *bpfEndpointManagerDataplane) applyPolicyToWeps( var ingressAP *tc.AttachPoint var ingressErr error - parallelWG.Add(1) - go func() { - defer parallelWG.Done() + parallelWG.Go(func() { ingressAP, ingressErr = d.wepApplyPolicyToDirection(readiness, state, endpoint, PolDirnIngress, &ingressAttachPoint) - }() + }) egressAP, egressErr := d.wepApplyPolicyToDirection(readiness, state, endpoint, PolDirnEgress, &egressAttachPoint) @@ -2814,19 +2811,15 @@ func (d *bpfEndpointManagerDataplane) applyPolicyToDataIface( var ingressErr, egressErr, xdpErr error if xdpMode != XDPModeNone { - parallelWG.Add(1) - go func() { - defer parallelWG.Done() + parallelWG.Go(func() { xdpAP, xdpErr = d.attachXDPProgram(&xdpAttachPoint, ep, state) - }() + }) } if xdpMode != XDPModeOnly { - parallelWG.Add(1) - go func() { - defer parallelWG.Done() + parallelWG.Go(func() { ingressAP, ingressErr = d.attachDataIfaceProgram(ifaceName, ep, PolDirnIngress, state, &ingressAttachPoint) - }() + }) egressAP, egressErr = d.attachDataIfaceProgram(ifaceName, ep, PolDirnEgress, state, &egressAttachPoint) } @@ -3042,6 +3035,7 @@ func (m *bpfEndpointManager) calculateTCAttachPoint(ifaceName string) *tc.Attach AttachPoint: bpf.AttachPoint{ Iface: ifaceName, }, + MaglevLUTSize: uint32(m.maglevLUTSize), } ap.Type = m.getEndpointType(ifaceName) @@ -3055,12 +3049,6 @@ func (m *bpfEndpointManager) calculateTCAttachPoint(ifaceName string) *tc.Attach ap.NATin = uint32(m.natInIdx) ap.NATout = uint32(m.natOutIdx) - ap.RedirectPeer = true - if m.bpfRedirectToPeer == "Disabled" { - ap.RedirectPeer = false - } else if (ap.Type == tcdefs.EpTypeIPIP || ap.Type == tcdefs.EpTypeL3Device) && m.bpfRedirectToPeer == "L2Only" { - ap.RedirectPeer = false - } } else { ap.ExtToServiceConnmark = uint32(m.bpfExtToServiceConnmark) } @@ -3070,7 +3058,6 @@ func (m *bpfEndpointManager) calculateTCAttachPoint(ifaceName string) *tc.Attach } ap.ToHostDrop = (m.epToHostAction == "DROP") - ap.FIB = m.fibLookupEnabled ap.DSR = m.dsrEnabled ap.DSROptoutCIDRs = m.dsrOptoutCidrs ap.LogLevel, ap.LogFilter = m.apLogFilter(ap, ifaceName) @@ -3081,6 +3068,12 @@ func (m *bpfEndpointManager) calculateTCAttachPoint(ifaceName string) *tc.Attach ap.Profiling = m.profiling ap.OverlayTunnelID = m.overlayTunnelID ap.AttachType = m.bpfAttachType + ap.RedirectPeer = true + if m.bpfRedirectToPeer == "Disabled" { + ap.RedirectPeer = false + } else if (ap.Type == tcdefs.EpTypeIPIP || ap.Type == tcdefs.EpTypeL3Device) && m.bpfRedirectToPeer == "L2Only" { + ap.RedirectPeer = false + } switch m.rpfEnforceOption { case "Strict": @@ -3119,6 +3112,11 @@ func (d *bpfEndpointManagerDataplane) configureTCAttachPoint(policyDirection Pol } } + ap.ProgramsMap = d.mgr.commonMaps.ProgramsMaps[hook.Ingress] + if ap.Hook == hook.Egress { + ap.ProgramsMap = d.mgr.commonMaps.ProgramsMaps[hook.Egress] + } + if d.mgr.FlowLogsEnabled() { ap.FlowLogsEnabled = true } @@ -3158,14 +3156,14 @@ func (m *bpfEndpointManager) extractTiers(tiers []*proto.TierInfo, direction Pol Policies: make([]polprog.Policy, len(directionalPols)), } - for i, polName := range directionalPols { - if model.PolicyIsStaged(polName) { - logrus.Debugf("Skipping staged policy %v", polName) + for i, polID := range directionalPols { + if model.KindIsStaged(polID.Kind) { + logrus.Debugf("Skipping staged policy %v", polID) continue } stagedOnly = false - pol := m.policies[types.PolicyID{Tier: tier.Name, Name: polName}] + pol := m.policies[types.ProtoToPolicyID(polID)] if pol == nil { logrus.WithField("tier", tier).Warn("Tier refers to unknown policy!") continue @@ -3177,14 +3175,16 @@ func (m *bpfEndpointManager) extractTiers(tiers []*proto.TierInfo, direction Pol prules = pol.OutboundRules } policy := polprog.Policy{ - Name: polName, - Rules: make([]polprog.Rule, len(prules)), + Name: polID.Name, + Namespace: polID.Namespace, + Kind: polID.Kind, + Rules: make([]polprog.Rule, len(prules)), } for ri, r := range prules { policy.Rules[ri] = polprog.Rule{ Rule: r, - MatchID: m.ruleMatchID(dir, r.Action, rules.RuleOwnerTypePolicy, ri, polName), + MatchID: m.ruleMatchID(dir, r.Action, rules.RuleOwnerTypePolicy, ri, types.ProtoToPolicyID(polID)), } } @@ -3226,7 +3226,7 @@ func (m *bpfEndpointManager) extractProfiles(profileNames []string, direction Po for ri, r := range prules { profile.Rules[ri] = polprog.Rule{ Rule: r, - MatchID: m.ruleMatchID(dir, r.Action, rules.RuleOwnerTypeProfile, ri, profName), + MatchID: m.ruleMatchID(dir, r.Action, rules.RuleOwnerTypeProfile, ri, &types.ProfileID{Name: profName}), } } @@ -3297,9 +3297,9 @@ func (m *bpfEndpointManager) addWEPToIndexes(wlID types.WorkloadEndpointID, wl * m.addProfileToEPMappings(wl.ProfileIds, wlID) } -func (m *bpfEndpointManager) addPolicyToEPMappings(tier string, polNames []string, id interface{}) { - for _, pol := range polNames { - polID := types.PolicyID{Tier: tier, Name: pol} +func (m *bpfEndpointManager) addPolicyToEPMappings(tier string, policies []*proto.PolicyID, id any) { + for _, p := range policies { + polID := types.ProtoToPolicyID(p) if m.policiesToWorkloads[polID] == nil { m.policiesToWorkloads[polID] = set.New[any]() } @@ -3307,7 +3307,7 @@ func (m *bpfEndpointManager) addPolicyToEPMappings(tier string, polNames []strin } } -func (m *bpfEndpointManager) addProfileToEPMappings(profileIds []string, id interface{}) { +func (m *bpfEndpointManager) addProfileToEPMappings(profileIds []string, id any) { for _, profName := range profileIds { profID := types.ProfileID{Name: profName} profSet := m.profilesToWorkloads[profID] @@ -3337,9 +3337,9 @@ func (m *bpfEndpointManager) removeWEPFromIndexes(wlID types.WorkloadEndpointID, }) } -func (m *bpfEndpointManager) removePolicyToEPMappings(tier string, polNames []string, id interface{}) { - for _, pol := range polNames { - polID := types.PolicyID{Tier: tier, Name: pol} +func (m *bpfEndpointManager) removePolicyToEPMappings(tier string, policies []*proto.PolicyID, id any) { + for _, pol := range policies { + polID := types.ProtoToPolicyID(pol) polSet := m.policiesToWorkloads[polID] if polSet == nil { continue @@ -3727,13 +3727,17 @@ func (m *bpfEndpointManager) ensureBPFDevices() error { if m.v4 != nil { m.routeTableV4.RouteUpdate(dataplanedefs.BPFInDev, routetable.Target{ Type: routetable.TargetTypeLinkLocalUnicast, - CIDR: bpfnatGWCIDR, + RouteKey: routetable.RouteKey{ + CIDR: bpfnatGWCIDR, + }, }) } if m.v6 != nil { m.routeTableV6.RouteUpdate(dataplanedefs.BPFInDev, routetable.Target{ Type: routetable.TargetTypeLinkLocalUnicast, - CIDR: bpfnatGWCIDRv6, + RouteKey: routetable.RouteKey{ + CIDR: bpfnatGWCIDRv6, + }, }) } @@ -3747,10 +3751,8 @@ func (m *bpfEndpointManager) ensureQdisc(iface string) (bool, error) { return tc.EnsureQdisc(iface) } -func (m *bpfEndpointManager) loadTCObj(at hook.AttachType) (hook.Layout, error) { - pm := m.commonMaps.ProgramsMap.(*hook.ProgramsMap) - - layout, err := pm.LoadObj(at) +func (m *bpfEndpointManager) loadTCObj(at hook.AttachType, pm *hook.ProgramsMap) (hook.Layout, error) { + layout, err := pm.LoadObj(at, string(m.bpfAttachType)) if err != nil { return nil, err } @@ -3760,7 +3762,7 @@ func (m *bpfEndpointManager) loadTCObj(at hook.AttachType) (hook.Layout, error) } at.LogLevel = "off" - layoutNoDebug, err := pm.LoadObj(at) + layoutNoDebug, err := pm.LoadObj(at, string(m.bpfAttachType)) if err != nil { return nil, err } @@ -3777,7 +3779,6 @@ func (m *bpfEndpointManager) ensureProgramLoaded(ap attachPoint, ipFamily proto. Hook: aptc.HookName(), Type: aptc.Type, LogLevel: aptc.LogLevel, - FIB: aptc.FIB, ToHostDrop: aptc.ToHostDrop, DSR: aptc.DSR, } @@ -3786,24 +3787,25 @@ func (m *bpfEndpointManager) ensureProgramLoaded(ap attachPoint, ipFamily proto. policyIdx := aptc.PolicyIdxV4 ap.Log().Debugf("ensureProgramLoaded %d", ipFamily) if ipFamily == proto.IPVersion_IPV6 { - if aptc.HookLayoutV6, err = m.loadTCObj(at); err != nil { + if aptc.HookLayoutV6, err = m.loadTCObj(at, aptc.ProgramsMap.(*hook.ProgramsMap)); err != nil { return fmt.Errorf("loading generic v%d tc hook program: %w", ipFamily, err) } policyIdx = aptc.PolicyIdxV6 } else { - if aptc.HookLayoutV4, err = m.loadTCObj(at); err != nil { + if aptc.HookLayoutV4, err = m.loadTCObj(at, aptc.ProgramsMap.(*hook.ProgramsMap)); err != nil { return fmt.Errorf("loading generic v%d tc hook program: %w", ipFamily, err) } } + jmpMap := m.commonMaps.JumpMaps[aptc.Hook] // Load default policy before the real policy is created and loaded. switch at.DefaultPolicy() { case hook.DefPolicyAllow: - err = maps.UpdateMapEntry(m.commonMaps.JumpMap.MapFD(), - jump.Key(policyIdx), jump.Value(m.policyTcAllowFD.FD())) + err = maps.UpdateMapEntry(jmpMap.MapFD(), + jump.Key(policyIdx), jump.Value(m.policyTcAllowFDs[aptc.Hook].FD())) case hook.DefPolicyDeny: - err = maps.UpdateMapEntry(m.commonMaps.JumpMap.MapFD(), - jump.Key(policyIdx), jump.Value(m.policyTcDenyFD.FD())) + err = maps.UpdateMapEntry(jmpMap.MapFD(), + jump.Key(policyIdx), jump.Value(m.policyTcDenyFDs[aptc.Hook].FD())) } if err != nil { @@ -3818,11 +3820,11 @@ func (m *bpfEndpointManager) ensureProgramLoaded(ap attachPoint, ipFamily proto. at.Family = int(ipFamily) pm := m.commonMaps.XDPProgramsMap.(*hook.ProgramsMap) if ipFamily == proto.IPVersion_IPV6 { - if apxdp.HookLayoutV6, err = pm.LoadObj(at); err != nil { + if apxdp.HookLayoutV6, err = pm.LoadObj(at, ""); err != nil { return fmt.Errorf("loading generic xdp hook program: %w", err) } } else { - if apxdp.HookLayoutV4, err = pm.LoadObj(at); err != nil { + if apxdp.HookLayoutV4, err = pm.LoadObj(at, ""); err != nil { return fmt.Errorf("loading generic xdp hook program: %w", err) } } @@ -3859,13 +3861,13 @@ func (m *bpfEndpointManager) ensureNoProgram(ap attachPoint) error { } // Clean up QoS map - qosIngressKey := qos.NewKey(uint32(ap.IfaceIndex()), 1) //ingress=1 + qosIngressKey := qos.NewKey(uint32(ap.IfaceIndex()), 1) // ingress=1 qosErr := m.QoSMap.Delete(qosIngressKey.AsBytes()) if qosErr != nil && !errors.Is(qosErr, os.ErrNotExist) { err = qosErr logrus.WithError(err).Warn("QoS map may leak.") } - qosEgressKey := qos.NewKey(uint32(ap.IfaceIndex()), 0) //ingress=0 + qosEgressKey := qos.NewKey(uint32(ap.IfaceIndex()), 0) // ingress=0 qosErr = m.QoSMap.Delete(qosEgressKey.AsBytes()) if qosErr != nil && !errors.Is(qosErr, os.ErrNotExist) { err = qosErr @@ -3956,6 +3958,7 @@ func (m *bpfEndpointManager) updatePolicyProgram(rules polprog.Rules, polDir str opts = append(opts, polprog.WithAllowDenyJumps(allow, deny)) } insns, err := m.doUpdatePolicyProgram( + ap, ap.HookName(), progName, ap.PolicyJmp(ipFamily), @@ -3975,13 +3978,21 @@ func (m *bpfEndpointManager) updatePolicyProgram(rules polprog.Rules, polDir str } func (m *bpfEndpointManager) loadTCLogFilter(ap *tc.AttachPoint) (fileDescriptor, int, error) { - logFilter, err := filter.New(ap.Type, 64, ap.LogFilter, m.commonMaps.ProgramsMap.MapFD()) + programsMapFD := ap.ProgramsMap.MapFD() + logFilter, err := filter.New(ap.Type, 64, ap.LogFilter, programsMapFD, m.commonMaps.StateMap.MapFD()) if err != nil { return nil, 0, err } - fd, err := bpf.LoadBPFProgramFromInsns(logFilter, "calico_log_filter", - "Apache-2.0", uint32(unix.BPF_PROG_TYPE_SCHED_CLS)) + attachType := uint32(0) + if m.bpfAttachType == apiv3.BPFAttachOptionTCX { + attachType = libbpf.AttachTypeTcxIngress + if ap.Hook == hook.Egress { + attachType = libbpf.AttachTypeTcxEgress + } + } + fd, err := bpf.LoadBPFProgramFromInsnsWithAttachType(logFilter, "calico_log_filter", + "Apache-2.0", uint32(unix.BPF_PROG_TYPE_SCHED_CLS), attachType) if err != nil { return nil, 0, fmt.Errorf("failed to load BPF log filter program: %w", err) } @@ -3997,7 +4008,7 @@ func (m *bpfEndpointManager) updateLogFilter(ap attachPoint) error { return err } defer fd.Close() - if err := m.commonMaps.JumpMap.Update(jump.Key(idx), jump.Value(fd.FD())); err != nil { + if err := m.commonMaps.JumpMaps[t.Hook].Update(jump.Key(idx), jump.Value(fd.FD())); err != nil { return fmt.Errorf("failed to update %s policy jump map [%d]=%d: %w", ap.HookName(), idx, fd.FD(), err) } @@ -4024,6 +4035,7 @@ func (m *bpfEndpointManager) loadPolicyProgram( rules polprog.Rules, staticProgsMap maps.Map, polProgsMap maps.Map, + attachType uint32, opts ...polprog.Option, ) ( fd []fileDescriptor, insns []asm.Insns, err error, @@ -4082,7 +4094,7 @@ func (m *bpfEndpointManager) loadPolicyProgram( } subProgName = fmt.Sprintf("%s_%d", subProgName, i) } - progFD, err := bpf.LoadBPFProgramFromInsns(p, subProgName, "Apache-2.0", uint32(progType)) + progFD, err := bpf.LoadBPFProgramFromInsnsWithAttachType(p, subProgName, "Apache-2.0", uint32(progType), attachType) if err != nil { logrus.WithError(err).WithFields(logrus.Fields{ "name": subProgName, @@ -4097,6 +4109,7 @@ func (m *bpfEndpointManager) loadPolicyProgram( } func (m *bpfEndpointManager) doUpdatePolicyProgram( + ap attachPoint, hk hook.Hook, progName string, polJumpMapIdx int, @@ -4104,23 +4117,40 @@ func (m *bpfEndpointManager) doUpdatePolicyProgram( ipFamily proto.IPVersion, opts ...polprog.Option, ) ([]asm.Insns, error) { + logCtx := logrus.WithFields(logrus.Fields{ + "iface": ap.IfaceName(), + "hook": hk, + "progName": progName, + "mapIndex": polJumpMapIdx, + "ipFamily": ipFamily, + }) + logCtx.Debug("Updating policy program...") if m.bpfPolicyDebugEnabled { opts = append(opts, polprog.WithPolicyDebugEnabled()) } - staticProgsMap := m.commonMaps.ProgramsMap - if hk == hook.XDP { - staticProgsMap = m.commonMaps.XDPProgramsMap + staticProgsMap := m.commonMaps.XDPProgramsMap + polProgsMap := m.commonMaps.XDPJumpMap + attachType := uint32(0) + if apTc, ok := ap.(*tc.AttachPoint); ok { + staticProgsMap = apTc.ProgramsMap + polProgsMap = m.commonMaps.JumpMaps[apTc.Hook] + if m.bpfAttachType == apiv3.BPFAttachOptionTCX { + attachType = libbpf.AttachTypeTcxIngress + if apTc.Hook == hook.Egress { + attachType = libbpf.AttachTypeTcxEgress + } + } } // If we have to break a program up into sub-programs to please the // verifier then we store the sub-programs at // polJumpMapIdx + subProgNo * stride. - polProgsMap := m.commonMaps.JumpMap stride := jump.TCMaxEntryPoints if hk == hook.XDP { polProgsMap = m.commonMaps.XDPJumpMap stride = jump.XDPMaxEntryPoints + attachType = libbpf.AttachTypeXDP } opts = append(opts, polprog.WithPolicyMapIndexAndStride(polJumpMapIdx, stride)) @@ -4142,6 +4172,7 @@ func (m *bpfEndpointManager) doUpdatePolicyProgram( rules, staticProgsMap, polProgsMap, + attachType, options..., ) if err != nil { @@ -4152,7 +4183,7 @@ func (m *bpfEndpointManager) doUpdatePolicyProgram( if tmp < int32(tstride) { tstride = int(tmp) } - logrus.Debugf("Reducing trampoline stride to %d and retrying", tstride) + logCtx.Debugf("Reducing trampoline stride to %d and retrying", tstride) continue } else { return nil, fmt.Errorf("reducing trampoline stride below 1000 not practical") @@ -4167,7 +4198,7 @@ func (m *bpfEndpointManager) doUpdatePolicyProgram( for tstride < tstrideOrig { if m.policyTrampolineStride.CompareAndSwap(int32(tstrideOrig), int32(tstride)) { - logrus.Warnf("Reducing policy program trampoline stride to %d", tstride) + logCtx.Warnf("Reducing policy program trampoline stride to %d", tstride) } else { tstrideOrig = int(m.policyTrampolineStride.Load()) } @@ -4177,25 +4208,26 @@ func (m *bpfEndpointManager) doUpdatePolicyProgram( for _, progFD := range progFDs { // Once we've put the programs in the map, we don't need their FDs. if err := progFD.Close(); err != nil { - logrus.WithError(err).Panic("Failed to close program FD.") + logCtx.WithError(err).Panic("Failed to close program FD.") } } }() for i, progFD := range progFDs { subProgIdx := polprog.SubProgramJumpIdx(polJumpMapIdx, i, stride) - logrus.Debugf("Putting sub-program %d at position %d", i, subProgIdx) + logCtx.Debugf("Putting sub-program %d at position %d in map %s", i, subProgIdx, polProgsMap.GetName()) if err := polProgsMap.Update(jump.Key(subProgIdx), jump.Value(progFD.FD())); err != nil { return nil, fmt.Errorf("failed to update %s policy jump map [%d]=%d: %w", hk, subProgIdx, progFD, err) } } for i := len(progFDs); i < jump.MaxSubPrograms; i++ { subProgIdx := polprog.SubProgramJumpIdx(polJumpMapIdx, i, stride) + logCtx.Debugf("Clearing sub-program %d at position %d in map %s", i, subProgIdx, polProgsMap.GetName()) if err := polProgsMap.Delete(jump.Key(subProgIdx)); err != nil { if os.IsNotExist(err) { break } - logrus.WithError(err).Warn("Unexpected error while trying to clean up old policy programs.") + logCtx.WithError(err).Warn("Unexpected error while trying to clean up old policy programs.") } } @@ -4206,15 +4238,15 @@ func (m *bpfEndpointManager) jumpMapDelete(h hook.Hook, idx int) error { if idx < 0 { return nil } - - jumpMap := m.commonMaps.JumpMap - stride := jump.TCMaxEntryPoints - if h == hook.XDP { - jumpMap = m.commonMaps.XDPJumpMap - stride = jump.XDPMaxEntryPoints + logrus.WithFields(logrus.Fields{"hook": h, "index": idx}).Debug("Deleting jump map entry") + jumpMap := m.commonMaps.XDPJumpMap + stride := jump.XDPMaxEntryPoints + if h != hook.XDP { + jumpMap = m.commonMaps.JumpMaps[h] + stride = jump.TCMaxEntryPoints } - return jumpMapDeleteEntry(jumpMap, idx, stride) + return jumpMapDeleteSubProgs(jumpMap, idx, stride) } func (m *bpfEndpointManager) removePolicyProgram(ap attachPoint, ipFamily proto.IPVersion) error { @@ -4230,10 +4262,10 @@ func (m *bpfEndpointManager) removePolicyProgram(ap attachPoint, ipFamily proto. pm = m.commonMaps.XDPJumpMap } else { stride = jump.TCMaxEntryPoints - pm = m.commonMaps.JumpMap + pm = m.commonMaps.JumpMaps[ap.HookName()] } - if err := jumpMapDeleteEntry(pm, idx, stride); err != nil { + if err := jumpMapDeleteSubProgs(pm, idx, stride); err != nil { return fmt.Errorf("removing policy iface %s hook %s: %w", ap.IfaceName(), ap.HookName(), err) } @@ -4381,13 +4413,7 @@ func (m *bpfEndpointManager) onServiceUpdate(update *proto.ServiceUpdate) { // Check which IPs have been removed (no-op if we haven't seen it yet) for _, old := range m.services[key] { - exists := false - for _, svcIP := range ips { - if old == svcIP { - exists = true - break - } - } + exists := slices.Contains(ips, old) if !exists { m.dp.delRoute(old) } @@ -4428,7 +4454,9 @@ var ( func (m *bpfEndpointManager) setRoute(cidr ip.CIDR) { target := routetable.Target{ Type: routetable.TargetTypeGlobalUnicast, - CIDR: cidr, + RouteKey: routetable.RouteKey{ + CIDR: cidr, + }, } if cidr.Version() == 6 { @@ -4450,20 +4478,30 @@ func (m *bpfEndpointManager) setRoute(cidr ip.CIDR) { func (m *bpfEndpointManager) delRoute(cidr ip.CIDR) { if m.v6 != nil && cidr.Version() == 6 { - m.routeTableV6.RouteRemove(dataplanedefs.BPFInDev, cidr) + m.routeTableV6.RouteRemove(dataplanedefs.BPFInDev, routetable.RouteKey{CIDR: cidr}) } if m.v4 != nil && cidr.Version() == 4 { - m.routeTableV4.RouteRemove(dataplanedefs.BPFInDev, cidr) + m.routeTableV4.RouteRemove(dataplanedefs.BPFInDev, routetable.RouteKey{CIDR: cidr}) } logrus.WithFields(logrus.Fields{ "cidr": cidr, }).Debug("delRoute") } +func (m *bpfEndpointManager) updatePolicyCacheProfile(id types.ProfileID, inboundRules, outboundRules []*proto.Rule) { + m.updateCache(id, id.Name, "Profile", inboundRules, outboundRules) +} + // updatePolicyCache modifies entries in the cache, adding new entries and marking old entries dirty. -func (m *bpfEndpointManager) updatePolicyCache(name string, owner string, inboundRules, outboundRules []*proto.Rule) { +func (m *bpfEndpointManager) updatePolicyCache(id types.PolicyID, inboundRules, outboundRules []*proto.Rule) { + // Build a unique string name for the policy + name := id.String() + m.updateCache(id, name, "Policy", inboundRules, outboundRules) +} + +func (m *bpfEndpointManager) updateCache(id types.IDMaker, name, owner string, inboundRules, outboundRules []*proto.Rule) { ruleIds := set.New[polprog.RuleMatchID]() - if val, ok := m.polNameToMatchIDs[name]; ok { + if val, ok := m.polNameToMatchIDs[id.ID()]; ok { // If the policy name exists, it means the policy is updated. There are cases where both inbound, // outbound rules are updated or any one. // Mark all the entries as dirty. @@ -4472,22 +4510,20 @@ func (m *bpfEndpointManager) updatePolicyCache(name string, owner string, inboun // Now iterate through all the rules and if the ruleIds are already in the cache, it means the rule has not // changed as part of the update. Remove the dirty flag and add this entry back as non-dirty. for idx, rule := range inboundRules { - ruleIds.Add(m.addRuleInfo(rule, idx, owner, PolDirnIngress, name)) + ruleIds.Add(m.addRuleInfo(id, rule, idx, owner, PolDirnIngress, name)) } for idx, rule := range outboundRules { - ruleIds.Add(m.addRuleInfo(rule, idx, owner, PolDirnEgress, name)) + ruleIds.Add(m.addRuleInfo(id, rule, idx, owner, PolDirnEgress, name)) } - m.polNameToMatchIDs[name] = ruleIds + m.polNameToMatchIDs[id.ID()] = ruleIds } -func (m *bpfEndpointManager) addRuleInfo(rule *proto.Rule, idx int, - owner string, direction PolDirection, polName string, -) polprog.RuleMatchID { +func (m *bpfEndpointManager) addRuleInfo(id types.IDMaker, rule *proto.Rule, idx int, owner string, direction PolDirection, polName string) polprog.RuleMatchID { ruleOwner := rules.RuleOwnerTypePolicy if owner == "Profile" { ruleOwner = rules.RuleOwnerTypeProfile } - matchID := m.dp.ruleMatchID(direction.RuleDir(), rule.Action, ruleOwner, idx, polName) + matchID := m.dp.ruleMatchID(direction.RuleDir(), rule.Action, ruleOwner, idx, id) m.dirtyRules.Discard(matchID) return matchID @@ -4498,7 +4534,7 @@ func (m *bpfEndpointManager) ruleMatchID( action string, owner rules.RuleOwnerType, idx int, - name string, + id types.IDMaker, ) polprog.RuleMatchID { var a rules.RuleAction switch action { @@ -4516,7 +4552,7 @@ func (m *bpfEndpointManager) ruleMatchID( logrus.WithField("action", action).Panic("Unknown rule action") } - return m.ruleMatchIDFromNFLOGPrefix(rules.CalculateNFLOGPrefixStr(a, owner, dir, idx, name)) + return m.ruleMatchIDFromNFLOGPrefix(rules.CalculateNFLOGPrefixStr(a, owner, dir, idx, id)) } func (m *bpfEndpointManager) getIfaceLink(name string) (netlink.Link, error) { @@ -4823,14 +4859,16 @@ func getLeafNodes(intf *bpfIfaceNode) []string { return leaves } -func newJumpMapAlloc(entryPoints int) *jumpMapAlloc { +func newJumpMapAlloc(name string, entryPoints int) *jumpMapAlloc { a := &jumpMapAlloc{ + name: name, + logCtx: logrus.WithField("jumpMapAllocName", name), max: entryPoints, free: set.New[int](), freeStack: make([]int, entryPoints), inUse: map[int]string{}, } - for i := 0; i < entryPoints; i++ { + for i := range entryPoints { a.free.Add(i) a.freeStack[entryPoints-1-i] = i } @@ -4838,9 +4876,11 @@ func newJumpMapAlloc(entryPoints int) *jumpMapAlloc { } type jumpMapAlloc struct { - lock sync.Mutex - max int + name string + logCtx *logrus.Entry + lock sync.Mutex + max int free set.Set[int] freeStack []int inUse map[int]string @@ -4858,7 +4898,7 @@ func (pa *jumpMapAlloc) Get(owner string) (int, error) { pa.free.Discard(idx) pa.inUse[idx] = owner - logrus.WithFields(logrus.Fields{"owner": owner, "index": idx}).Debug("jumpMapAlloc: Allocated policy map index") + pa.logCtx.WithFields(logrus.Fields{"owner": owner, "index": idx}).Debug("Allocated jump map index") pa.checkFreeLockHeld(idx) return idx, nil } @@ -4908,7 +4948,7 @@ func (pa *jumpMapAlloc) Put(idx int, owner string) error { err := fmt.Errorf("jumpMapAlloc: %q trying to free index %d but it is owned by %q", owner, idx, recordedOwner) return err } - logrus.WithFields(logrus.Fields{"owner": owner, "index": idx}).Debug("jumpMapAlloc: Released policy map index") + pa.logCtx.WithFields(logrus.Fields{"owner": owner, "index": idx}).Debug("Released policy map index") delete(pa.inUse, idx) pa.free.Add(idx) pa.freeStack = append(pa.freeStack, idx) @@ -4918,10 +4958,10 @@ func (pa *jumpMapAlloc) Put(idx int, owner string) error { func (pa *jumpMapAlloc) checkFreeLockHeld(idx int) { if len(pa.freeStack) != pa.free.Len() { - logrus.WithFields(logrus.Fields{ + pa.logCtx.WithFields(logrus.Fields{ "assigning": idx, "set": pa.free, "stack": pa.freeStack, - }).Panic("jumpMapAlloc: Free set and free stack got out of sync") + }).Panic("Free set and free stack got out of sync") } } diff --git a/felix/dataplane/linux/bpf_ep_mgr_test.go b/felix/dataplane/linux/bpf_ep_mgr_test.go index 795f96df2a9..bcaf535f295 100644 --- a/felix/dataplane/linux/bpf_ep_mgr_test.go +++ b/felix/dataplane/linux/bpf_ep_mgr_test.go @@ -21,13 +21,15 @@ import ( "errors" "fmt" "hash/fnv" + maps0 "maps" "net" "regexp" "strconv" "sync" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" "github.com/vishvananda/netlink" "golang.org/x/sys/unix" @@ -122,7 +124,7 @@ func (m *mockDataplane) configureBPFDevices() error { return nil } -func (m *mockDataplane) loadDefaultPolicies() error { +func (m *mockDataplane) loadDefaultPolicies(hk hook.Hook) error { return nil } @@ -296,9 +298,9 @@ func (m *mockDataplane) delRoute(cidr ip.CIDR) { delete(m.routes, cidr) } -func (m *mockDataplane) ruleMatchID(dir rules.RuleDir, action string, owner rules.RuleOwnerType, idx int, name string) polprog.RuleMatchID { +func (m *mockDataplane) ruleMatchID(dir rules.RuleDir, action string, owner rules.RuleOwnerType, idx int, id types.IDMaker) polprog.RuleMatchID { h := fnv.New64a() - h.Write([]byte(action + owner.String() + dir.String() + strconv.Itoa(idx+1) + name)) + h.Write([]byte(action + owner.String() + dir.String() + strconv.Itoa(idx+1) + id.ID())) return h.Sum64() } @@ -327,6 +329,7 @@ func (m *mockProgMapDP) loadPolicyProgram(progName string, rules polprog.Rules, staticProgsMap bpfmaps.Map, polProgsMap bpfmaps.Map, + attachType uint32, opts ...polprog.Option, ) ([]fileDescriptor, []asm.Insns, error) { if m.jitHarden { @@ -367,7 +370,6 @@ var _ = Describe("BPF Endpoint Manager", func() { bpfEpMgr *bpfEndpointManager dp *mockDataplane mockDP bpfDataplane - fibLookupEnabled bool endpointToHostAction string dataIfacePattern string l3IfacePattern string @@ -387,13 +389,13 @@ var _ = Describe("BPF Endpoint Manager", func() { filterTableV6 Table ifStateMap *mock.Map countersMap *mock.Map - jumpMap *mock.Map + jumpMapIng *mock.Map + jumpMapEgr *mock.Map xdpJumpMap *mock.Map qosMap *mock.Map ) BeforeEach(func() { - fibLookupEnabled = true endpointToHostAction = "DROP" dataIfacePattern = "^eth0" workloadIfaceRegex = "cali" @@ -426,20 +428,31 @@ var _ = Describe("BPF Endpoint Manager", func() { commonMaps.CountersMap = countersMap commonMaps.RuleCountersMap = mock.NewMockMap(counters.PolicyMapParameters) - progsParams := bpfmaps.MapParameters{ + progsParamsIng := bpfmaps.MapParameters{ Type: "prog_array", KeySize: 4, ValueSize: 4, MaxEntries: 1000, - Name: "cali_progs", - Version: 3, + Name: "cali_progs_ing", + Version: 2, } - commonMaps.ProgramsMap = mock.NewMockMap(progsParams) - commonMaps.XDPProgramsMap = mock.NewMockMap(progsParams) - jumpMap = mock.NewMockMap(progsParams) - commonMaps.JumpMap = jumpMap - xdpJumpMap = mock.NewMockMap(progsParams) + progsParamsEg := bpfmaps.MapParameters{ + Type: "prog_array", + KeySize: 4, + ValueSize: 4, + MaxEntries: 1000, + Name: "cali_progs_eg", + Version: 2, + } + commonMaps.ProgramsMaps = append(commonMaps.ProgramsMaps, mock.NewMockMap(progsParamsIng)) + commonMaps.ProgramsMaps = append(commonMaps.ProgramsMaps, mock.NewMockMap(progsParamsEg)) + commonMaps.XDPProgramsMap = mock.NewMockMap(progsParamsIng) + jumpMapIng = mock.NewMockMap(progsParamsIng) + jumpMapEgr = mock.NewMockMap(progsParamsEg) + commonMaps.JumpMaps = append(commonMaps.JumpMaps, jumpMapIng) + commonMaps.JumpMaps = append(commonMaps.JumpMaps, jumpMapEgr) + xdpJumpMap = mock.NewMockMap(progsParamsIng) commonMaps.XDPJumpMap = xdpJumpMap maps.V4 = v4Maps @@ -463,7 +476,7 @@ var _ = Describe("BPF Endpoint Manager", func() { VXLANPort: 4789, VXLANVNI: 4096, } - ruleRenderer = rules.NewRenderer(rrConfigNormal) + ruleRenderer = rules.NewRenderer(rrConfigNormal, false) filterTableV4 = newMockTable("filter") filterTableV6 = newMockTable("filter") }) @@ -489,7 +502,6 @@ var _ = Describe("BPF Endpoint Manager", func() { BPFIpv6Enabled: ipv6Enabled, }, maps, - fibLookupEnabled, regexp.MustCompile(workloadIfaceRegex), ipSetIDAllocatorV4, ipSetIDAllocatorV6, @@ -504,6 +516,8 @@ var _ = Describe("BPF Endpoint Manager", func() { nil, environment.NewFeatureDetector(nil).GetFeatures(), 1250, + nil, + nil, ) Expect(err).NotTo(HaveOccurred()) bpfEpMgr.v4.hostIP = net.ParseIP("1.2.3.4") @@ -534,7 +548,7 @@ var _ = Describe("BPF Endpoint Manager", func() { } } - genHEPUpdate := func(heps ...interface{}) func() { + genHEPUpdate := func(heps ...any) func() { return func() { hostIfaceToEp := make(map[string]*proto.HostEndpoint) for i := 0; i < len(heps); i += 2 { @@ -551,8 +565,8 @@ var _ = Describe("BPF Endpoint Manager", func() { genPolicy := func(tier, policy string) func() { return func() { bpfEpMgr.OnUpdate(&proto.ActivePolicyUpdate{ - Id: &proto.PolicyID{Tier: tier, Name: policy}, - Policy: &proto.Policy{}, + Id: &proto.PolicyID{Name: policy, Kind: v3.KindNetworkPolicy}, + Policy: &proto.Policy{Tier: tier}, }) err := bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) @@ -562,7 +576,7 @@ var _ = Describe("BPF Endpoint Manager", func() { genUntracked := func(tier, policy string) func() { return func() { bpfEpMgr.OnUpdate(&proto.ActivePolicyUpdate{ - Id: &proto.PolicyID{Tier: tier, Name: policy}, + Id: &proto.PolicyID{Name: policy, Kind: v3.KindNetworkPolicy}, Policy: &proto.Policy{Untracked: true}, }) err := bpfEpMgr.CompleteDeferredWork() @@ -570,7 +584,7 @@ var _ = Describe("BPF Endpoint Manager", func() { } } - genWLUpdate := func(name string, policies ...string) func() { + genWLUpdate := func(name string, policies ...*proto.PolicyID) func() { return func() { update := &proto.WorkloadEndpointUpdate{ Id: &proto.WorkloadEndpointID{ @@ -635,7 +649,7 @@ var _ = Describe("BPF Endpoint Manager", func() { PreDnatTiers: []*proto.TierInfo{ { Name: "default", - IngressPolicies: []string{"default.mypolicy"}, + IngressPolicies: []*proto.PolicyID{{Name: "default.mypolicy", Kind: v3.KindNetworkPolicy}}, }, }, } @@ -645,8 +659,8 @@ var _ = Describe("BPF Endpoint Manager", func() { Tiers: []*proto.TierInfo{ { Name: "default", - IngressPolicies: []string{"default.mypolicy"}, - EgressPolicies: []string{"default.mypolicy"}, + IngressPolicies: []*proto.PolicyID{{Name: "default.mypolicy", Kind: v3.KindNetworkPolicy}}, + EgressPolicies: []*proto.PolicyID{{Name: "default.mypolicy", Kind: v3.KindNetworkPolicy}}, }, }, } @@ -738,7 +752,7 @@ var _ = Describe("BPF Endpoint Manager", func() { newHEP := googleproto.Clone(hostEp).(*proto.HostEndpoint) newHEP.UntrackedTiers = []*proto.TierInfo{{ Name: "default", - IngressPolicies: []string{"untracked1"}, + IngressPolicies: []*proto.PolicyID{{Name: "untracked1", Kind: v3.KindGlobalNetworkPolicy}}, }} err := dp.createIface("bond0", 10, "bond") Expect(err).NotTo(HaveOccurred()) @@ -802,7 +816,6 @@ var _ = Describe("BPF Endpoint Manager", func() { Expect(dp.programAttached("eth20:ingress")).To(BeTrue()) Expect(dp.programAttached("eth20:egress")).To(BeTrue()) Expect(dp.programAttached("eth20:xdp")).To(BeFalse()) - }) It("should add host ifaces to iface tree", func() { @@ -1246,7 +1259,7 @@ var _ = Describe("BPF Endpoint Manager", func() { newHEP := googleproto.Clone(hostEp).(*proto.HostEndpoint) newHEP.UntrackedTiers = []*proto.TierInfo{{ Name: "default", - IngressPolicies: []string{"untracked1"}, + IngressPolicies: []*proto.PolicyID{{Name: "untracked1", Kind: v3.KindGlobalNetworkPolicy}}, }} genHEPUpdate("bond1", newHEP)() err = bpfEpMgr.CompleteDeferredWork() @@ -1303,7 +1316,7 @@ var _ = Describe("BPF Endpoint Manager", func() { newHEP := googleproto.Clone(hostEp).(*proto.HostEndpoint) newHEP.UntrackedTiers = []*proto.TierInfo{{ Name: "default", - IngressPolicies: []string{"untracked1"}, + IngressPolicies: []*proto.PolicyID{{Name: "untracked1", Kind: v3.KindGlobalNetworkPolicy}}, }} genHEPUpdate("bond0", newHEP)() err := bpfEpMgr.CompleteDeferredWork() @@ -1413,8 +1426,8 @@ var _ = Describe("BPF Endpoint Manager", func() { It("stores host endpoint for eth0", func() { Expect(bpfEpMgr.hostIfaceToEpMap["eth0"]).To(Equal(hostEp)) Expect(bpfEpMgr.policiesToWorkloads[types.PolicyID{ - Tier: "default", Name: "default.mypolicy", + Kind: v3.KindNetworkPolicy, }]).To(HaveKey("eth0")) var eth0I, eth0E, eth0X *polprog.Rules @@ -1438,7 +1451,7 @@ var _ = Describe("BPF Endpoint Manager", func() { newHEP := googleproto.Clone(hostEp).(*proto.HostEndpoint) newHEP.UntrackedTiers = []*proto.TierInfo{{ Name: "default", - IngressPolicies: []string{"untracked1"}, + IngressPolicies: []*proto.PolicyID{{Name: "untracked1", Kind: v3.KindGlobalNetworkPolicy}}, }} genHEPUpdate("eth0", newHEP)() @@ -1463,8 +1476,8 @@ var _ = Describe("BPF Endpoint Manager", func() { It("stores host endpoint for eth0", func() { Expect(bpfEpMgr.hostIfaceToEpMap["eth0"]).To(Equal(hostEp)) Expect(bpfEpMgr.policiesToWorkloads[types.PolicyID{ - Tier: "default", Name: "default.mypolicy", + Kind: v3.KindNetworkPolicy, }]).To(HaveKey("eth0")) }) }) @@ -1482,8 +1495,8 @@ var _ = Describe("BPF Endpoint Manager", func() { It("stores host endpoint for eth0", func() { Expect(bpfEpMgr.hostIfaceToEpMap["eth0"]).To(Equal(hostEp)) Expect(bpfEpMgr.policiesToWorkloads[types.PolicyID{ - Tier: "default", Name: "default.mypolicy", + Kind: v3.KindNetworkPolicy, }]).To(HaveKey("eth0")) }) }) @@ -1501,8 +1514,8 @@ var _ = Describe("BPF Endpoint Manager", func() { It("stores host endpoint for eth0", func() { Expect(bpfEpMgr.hostIfaceToEpMap["eth0"]).To(Equal(hostEp)) Expect(bpfEpMgr.policiesToWorkloads[types.PolicyID{ - Tier: "default", Name: "default.mypolicy", + Kind: v3.KindNetworkPolicy, }]).To(HaveKey("eth0")) }) @@ -1512,8 +1525,8 @@ var _ = Describe("BPF Endpoint Manager", func() { It("clears host endpoint for eth0", func() { Expect(bpfEpMgr.hostIfaceToEpMap).To(BeEmpty()) Expect(bpfEpMgr.policiesToWorkloads[types.PolicyID{ - Tier: "default", Name: "default.mypolicy", + Kind: v3.KindNetworkPolicy, }]).NotTo(HaveKey("eth0")) }) }) @@ -1523,8 +1536,8 @@ var _ = Describe("BPF Endpoint Manager", func() { It("clears host endpoint for eth0", func() { Expect(bpfEpMgr.hostIfaceToEpMap).To(BeEmpty()) Expect(bpfEpMgr.policiesToWorkloads[types.PolicyID{ - Tier: "default", Name: "default.mypolicy", + Kind: v3.KindNetworkPolicy, }]).NotTo(HaveKey("eth0")) }) }) @@ -1532,22 +1545,26 @@ var _ = Describe("BPF Endpoint Manager", func() { }) Describe("polCounters", func() { + allowPolID := types.PolicyID{Name: "allowPol", Kind: v3.KindNetworkPolicy} + It("should update the maps with ruleIds", func() { ingRule := &proto.Rule{Action: "Allow", RuleId: "INGRESSALLOW1234"} egrRule := &proto.Rule{Action: "Allow", RuleId: "EGRESSALLOW12345"} - ingRuleMatchId := bpfEpMgr.dp.ruleMatchID(rules.RuleDirIngress, "Allow", rules.RuleOwnerTypePolicy, 0, "allowPol") - egrRuleMatchId := bpfEpMgr.dp.ruleMatchID(rules.RuleDirEgress, "Allow", rules.RuleOwnerTypePolicy, 0, "allowPol") + ingRuleMatchId := bpfEpMgr.dp.ruleMatchID(rules.RuleDirIngress, "Allow", rules.RuleOwnerTypePolicy, 0, allowPolID) + egrRuleMatchId := bpfEpMgr.dp.ruleMatchID(rules.RuleDirEgress, "Allow", rules.RuleOwnerTypePolicy, 0, allowPolID) k := make([]byte, 8) v := make([]byte, 8) rcMap := bpfEpMgr.commonMaps.RuleCountersMap // create a new policy bpfEpMgr.OnUpdate(&proto.ActivePolicyUpdate{ - Id: &proto.PolicyID{Tier: "default", Name: "allowPol"}, + Id: &proto.PolicyID{Name: "allowPol", Kind: v3.KindNetworkPolicy}, Policy: &proto.Policy{InboundRules: []*proto.Rule{ingRule}, OutboundRules: []*proto.Rule{egrRule}}, }) Expect(bpfEpMgr.polNameToMatchIDs).To(HaveLen(1)) - val := bpfEpMgr.polNameToMatchIDs["allowPol"] + Expect(bpfEpMgr.polNameToMatchIDs).To(HaveKey(allowPolID.ID())) + val := bpfEpMgr.polNameToMatchIDs[allowPolID.ID()] + Expect(val).NotTo(BeNil()) Expect(val.Contains(ingRuleMatchId)).To(BeTrue()) Expect(val.Contains(egrRuleMatchId)).To(BeTrue()) binary.LittleEndian.PutUint64(k, ingRuleMatchId) @@ -1560,13 +1577,14 @@ var _ = Describe("BPF Endpoint Manager", func() { // update the ingress rule of the policy ingDenyRule := &proto.Rule{Action: "Deny", RuleId: "INGRESSDENY12345"} - ingDenyRuleMatchId := bpfEpMgr.dp.ruleMatchID(rules.RuleDirIngress, "Deny", rules.RuleOwnerTypePolicy, 0, "allowPol") + ingDenyRuleMatchId := bpfEpMgr.dp.ruleMatchID(rules.RuleDirIngress, "Deny", rules.RuleOwnerTypePolicy, 0, allowPolID) bpfEpMgr.OnUpdate(&proto.ActivePolicyUpdate{ - Id: &proto.PolicyID{Tier: "default", Name: "allowPol"}, + Id: &proto.PolicyID{Name: "allowPol", Kind: v3.KindNetworkPolicy}, Policy: &proto.Policy{InboundRules: []*proto.Rule{ingDenyRule}, OutboundRules: []*proto.Rule{egrRule}}, }) Expect(bpfEpMgr.polNameToMatchIDs).To(HaveLen(1)) - val = bpfEpMgr.polNameToMatchIDs["allowPol"] + val = bpfEpMgr.polNameToMatchIDs[allowPolID.ID()] + Expect(val).NotTo(BeNil()) Expect(val.Contains(ingDenyRuleMatchId)).To(BeTrue()) Expect(val.Contains(egrRuleMatchId)).To(BeTrue()) Expect(bpfEpMgr.dirtyRules.Contains(ingRuleMatchId)).To(BeTrue()) @@ -1582,7 +1600,7 @@ var _ = Describe("BPF Endpoint Manager", func() { Expect(binary.LittleEndian.Uint64(v)).To(Equal(uint64(10))) // delete the policy - bpfEpMgr.OnUpdate(&proto.ActivePolicyRemove{Id: &proto.PolicyID{Tier: "default", Name: "allowPol"}}) + bpfEpMgr.OnUpdate(&proto.ActivePolicyRemove{Id: &proto.PolicyID{Name: "allowPol", Kind: v3.KindNetworkPolicy}}) Expect(bpfEpMgr.dirtyRules.Contains(egrRuleMatchId)).To(BeTrue()) Expect(bpfEpMgr.dirtyRules.Contains(ingDenyRuleMatchId)).To(BeTrue()) Expect(bpfEpMgr.polNameToMatchIDs).To(HaveLen(0)) @@ -1600,8 +1618,8 @@ var _ = Describe("BPF Endpoint Manager", func() { }) It("should cleanup the bpf map after restart", func() { - ingRuleMatchId := bpfEpMgr.dp.ruleMatchID(rules.RuleDirIngress, "Allow", rules.RuleOwnerTypePolicy, 0, "allowPol") - egrRuleMatchId := bpfEpMgr.dp.ruleMatchID(rules.RuleDirEgress, "Allow", rules.RuleOwnerTypePolicy, 0, "allowPol") + ingRuleMatchId := bpfEpMgr.dp.ruleMatchID(rules.RuleDirIngress, "Allow", rules.RuleOwnerTypePolicy, 0, allowPolID) + egrRuleMatchId := bpfEpMgr.dp.ruleMatchID(rules.RuleDirEgress, "Allow", rules.RuleOwnerTypePolicy, 0, allowPolID) k := make([]byte, 8) v := make([]byte, 8) rcMap := bpfEpMgr.commonMaps.RuleCountersMap @@ -1753,42 +1771,48 @@ var _ = Describe("BPF Endpoint Manager", func() { Describe("ifstate(ipv6 disabled)", func() { It("should clean up jump map entries for missing interfaces", func() { - for i := 0; i < 17; i++ { - _ = jumpMap.Update(jump.Key(i), jump.Value(uint32(1000+i))) - _ = jumpMap.Update(jump.Key(i+jump.TCMaxEntryPoints), jump.Value(uint32(1000+i))) + for i := range 9 { + _ = jumpMapIng.Update(jump.Key(i), jump.Value(uint32(1000+i))) + _ = jumpMapIng.Update(jump.Key(i+jump.TCMaxEntryPoints), jump.Value(uint32(1000+i))) + _ = jumpMapEgr.Update(jump.Key(i), jump.Value(uint32(1000+i))) + _ = jumpMapEgr.Update(jump.Key(i+jump.TCMaxEntryPoints), jump.Value(uint32(1000+i))) } - for i := 0; i < 5; i++ { + for i := range 5 { _ = xdpJumpMap.Update(jump.Key(i), jump.Value(uint32(2000+i))) } _ = ifStateMap.Update( ifstate.NewKey(123).AsBytes(), ifstate.NewValue(ifstate.FlgIPv4Ready, "eth123", - 1, 1, 2, -1, -1, -1, 3, 4).AsBytes(), + 0, 0, 0, -1, -1, -1, 1, 1).AsBytes(), ) _ = ifStateMap.Update( ifstate.NewKey(124).AsBytes(), ifstate.NewValue(0, "eth124", - 2, 5, 6, -1, -1, -1, 7, 8).AsBytes(), + 1, 2, 2, -1, -1, -1, 3, 3).AsBytes(), ) _ = ifStateMap.Update( ifstate.NewKey(125).AsBytes(), ifstate.NewValue(ifstate.FlgWEP|ifstate.FlgIPv4Ready, "eth125", - 3, 9, 10, -1, -1, -1, 11, 12).AsBytes(), + 2, 4, 4, -1, -1, -1, 5, 5).AsBytes(), ) _ = ifStateMap.Update( ifstate.NewKey(126).AsBytes(), ifstate.NewValue(ifstate.FlgWEP, "eth123", - 0, 13, 14, -1, -1, -1, 15, 0).AsBytes(), + 3, 6, 6, -1, -1, -1, 7, 7).AsBytes(), ) err := bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) Expect(ifStateMap.IsEmpty()).To(BeTrue()) - Expect(jumpMap.Contents).To(Equal(map[string]string{ - string(jump.Key(16)): string(jump.Value(uint32(1000 + 16))), - string(jump.Key(16 + jump.TCMaxEntryPoints)): string(jump.Value(uint32(1000 + 16))), + Expect(jumpMapIng.Contents).To(Equal(map[string]string{ + string(jump.Key(8)): string(jump.Value(uint32(1000 + 8))), + string(jump.Key(8 + jump.TCMaxEntryPoints)): string(jump.Value(uint32(1000 + 8))), + })) + Expect(jumpMapEgr.Contents).To(Equal(map[string]string{ + string(jump.Key(8)): string(jump.Value(uint32(1000 + 8))), + string(jump.Key(8 + jump.TCMaxEntryPoints)): string(jump.Value(uint32(1000 + 8))), })) Expect(xdpJumpMap.Contents).To(Equal(map[string]string{ // Key 4 wasn't used above so it should persist. @@ -1820,9 +1844,11 @@ var _ = Describe("BPF Endpoint Manager", func() { bpfEpMgr.bpfLogLevel = "debug" bpfEpMgr.logFilters = map[string]string{"all": "tcp"} - for i := 0; i < 8; i++ { - _ = jumpMap.Update(jump.Key(i), jump.Value(uint32(1000+i))) - _ = jumpMap.Update(jump.Key(i+jump.TCMaxEntryPoints), jump.Value(uint32(1000+i))) + for i := range 4 { + _ = jumpMapIng.Update(jump.Key(i), jump.Value(uint32(1000+i))) + _ = jumpMapIng.Update(jump.Key(i+jump.TCMaxEntryPoints), jump.Value(uint32(1000+i))) + _ = jumpMapEgr.Update(jump.Key(i), jump.Value(uint32(1000+i))) + _ = jumpMapEgr.Update(jump.Key(i+jump.TCMaxEntryPoints), jump.Value(uint32(1000+i))) } for i := 1; i < 2; i++ { _ = xdpJumpMap.Update(jump.Key(i), jump.Value(uint32(2000+i))) @@ -1830,8 +1856,8 @@ var _ = Describe("BPF Endpoint Manager", func() { key123 := ifstate.NewKey(123).AsBytes() value123 := ifstate.NewValue(ifstate.FlgIPv4Ready|ifstate.FlgWEP, "cali12345", - -1, 0, 2, -1, -1, -1, 3, 4) - value124 := ifstate.NewValue(0, "eth124", 2, 5, 6, -1, -1, -1, 7, 1) + -1, 0, 0, -1, -1, -1, 1, 1) + value124 := ifstate.NewValue(0, "eth124", 2, 2, 2, -1, -1, -1, 3, 3) _ = ifStateMap.Update( key123, @@ -1852,7 +1878,7 @@ var _ = Describe("BPF Endpoint Manager", func() { } return nil, errors.New("no such network interface") } - genWLUpdate("cali12345", "pol-a")() + genWLUpdate("cali12345", &proto.PolicyID{Name: "pol-a", Kind: v3.KindNetworkPolicy})() err := bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) @@ -1861,17 +1887,16 @@ var _ = Describe("BPF Endpoint Manager", func() { 123: value123.String(), })) - // Expect clean-up deletions. - jmps := dumpJumpMap(jumpMap) - Expect(jmps).To(HaveLen(8)) - Expect(jmps).To(HaveKey(0)) /* filters reloaded to reflect current expressions */ - Expect(jmps).To(HaveKey(2)) - Expect(jmps).To(HaveKey(3)) - Expect(jmps).To(HaveKey(4)) - Expect(jmps).To(HaveKeyWithValue(10000, 1000)) - Expect(jmps).To(HaveKeyWithValue(10002, 1002)) - Expect(jmps).To(HaveKeyWithValue(10003, 1003)) - Expect(jmps).To(HaveKeyWithValue(10004, 1004)) + checkJumpMap := func(m *mock.Map) { + jmps := dumpJumpMap(m) + Expect(jmps).To(HaveLen(4)) + Expect(jmps).To(HaveKey(0)) /* filters reloaded to reflect current expressions */ + Expect(jmps).To(HaveKey(1)) + Expect(jmps).To(HaveKeyWithValue(10000, 1000)) + Expect(jmps).To(HaveKeyWithValue(10001, 1001)) + } + checkJumpMap(jumpMapIng) + checkJumpMap(jumpMapEgr) Expect(dumpJumpMap(xdpJumpMap)).To(Equal(map[int]int{ 1: 2001, })) @@ -1903,7 +1928,7 @@ var _ = Describe("BPF Endpoint Manager", func() { } return nil, errors.New("no such network interface") } - genWLUpdate("cali12345", "pol-a")() + genWLUpdate("cali12345", &proto.PolicyID{Name: "pol-a", Kind: v3.KindNetworkPolicy})() err := bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) @@ -1911,7 +1936,7 @@ var _ = Describe("BPF Endpoint Manager", func() { // XDP gets cleaned up because it's a WEP, ingress keeps its // ID because it was the first; egress gets reallocated. value123Fixed := ifstate.NewValue(ifstate.FlgIPv4Ready|ifstate.FlgWEP, "cali12345", - -1, 0, 1, -1, -1, -1, -1, -1) + -1, 0, 0, -1, -1, -1, -1, -1) Expect(dumpIfstateMap(ifStateMap)).To(Equal(map[int]string{ 123: value123Fixed.String(), })) @@ -1958,8 +1983,8 @@ var _ = Describe("BPF Endpoint Manager", func() { } return nil, errors.New("no such network interface") } - genWLUpdate("cali12345", "pol-a")() - genWLUpdate("cali56789", "pol-b")() + genWLUpdate("cali12345", &proto.PolicyID{Name: "pol-a", Kind: v3.KindNetworkPolicy})() + genWLUpdate("cali56789", &proto.PolicyID{Name: "pol-b", Kind: v3.KindNetworkPolicy})() err := bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) @@ -1970,16 +1995,17 @@ var _ = Describe("BPF Endpoint Manager", func() { val123 := ifstate.ValueFromBytes([]byte(ifStateMap.Contents[string(key123)])) val124 := ifstate.ValueFromBytes([]byte(ifStateMap.Contents[string(key124)])) - tcIDsSeen := set.New[int]() + tcIDsSeenIngress := set.New[int]() + tcIDsSeenEgress := set.New[int]() for _, v := range []ifstate.Value{val123, val124} { Expect(v.XDPPolicyV4()).To(Equal(-1), "WEPs shouldn't get XDP IDs") Expect(v.IngressPolicyV4()).NotTo(Equal(-1), "WEPs should have ingress pol") - Expect(tcIDsSeen.Contains(v.IngressPolicyV4())).To(BeFalse(), "Saw same jump map ID more than once") - tcIDsSeen.Add(v.IngressPolicyV4()) + Expect(tcIDsSeenIngress.Contains(v.IngressPolicyV4())).To(BeFalse(), "Saw same jump map ID more than once") + tcIDsSeenIngress.Add(v.IngressPolicyV4()) Expect(v.EgressPolicyV4()).NotTo(Equal(-1), "WEPs should have egress pol") - Expect(tcIDsSeen.Contains(v.EgressPolicyV4())).To(BeFalse(), "Saw same jump map ID more than once") - tcIDsSeen.Add(v.EgressPolicyV4()) + Expect(tcIDsSeenEgress.Contains(v.EgressPolicyV4())).To(BeFalse(), "Saw same jump map ID more than once") + tcIDsSeenEgress.Add(v.EgressPolicyV4()) Expect(v.XDPPolicyV6()).To(Equal(-1), "WEPs shouldn't get XDP IPv6 ID") Expect(v.IngressPolicyV6()).To(Equal(-1), "WEPs shouldn't get IPv6 ingress pol") @@ -2037,19 +2063,20 @@ var _ = Describe("BPF Endpoint Manager", func() { val124 := ifstate.ValueFromBytes([]byte(ifStateMap.Contents[string(key124)])) xdpIDsSeen := set.New[int]() - tcIDsSeen := set.New[int]() + tcIDsSeenIngress := set.New[int]() + tcIDsSeenEgress := set.New[int]() for _, v := range []ifstate.Value{val123, val124} { Expect(v.XDPPolicyV4()).NotTo(Equal(-1), "WEPs shouldn't get XDP IDs") Expect(xdpIDsSeen.Contains(v.XDPPolicyV4())).To(BeFalse(), fmt.Sprintf("Saw same jump XDP map ID %d more than once", v.XDPPolicyV4())) xdpIDsSeen.Add(v.XDPPolicyV4()) Expect(v.IngressPolicyV4()).NotTo(Equal(-1), "WEPs should have ingress pol") - Expect(tcIDsSeen.Contains(v.IngressPolicyV4())).To(BeFalse(), "Saw same jump map ID more than once") - tcIDsSeen.Add(v.IngressPolicyV4()) + Expect(tcIDsSeenIngress.Contains(v.IngressPolicyV4())).To(BeFalse(), "Saw same jump map ID more than once") + tcIDsSeenIngress.Add(v.IngressPolicyV4()) Expect(v.EgressPolicyV4()).NotTo(Equal(-1), "WEPs should have egress pol") - Expect(tcIDsSeen.Contains(v.EgressPolicyV4())).To(BeFalse(), "Saw same jump map ID more than once") - tcIDsSeen.Add(v.EgressPolicyV4()) + Expect(tcIDsSeenEgress.Contains(v.EgressPolicyV4())).To(BeFalse(), "Saw same jump map ID more than once") + tcIDsSeenEgress.Add(v.EgressPolicyV4()) Expect(v.XDPPolicyV6()).To(Equal(-1), "WEPs shouldn't get XDP IPv6 ID") Expect(v.IngressPolicyV6()).To(Equal(-1), "WEPs shouldn't get IPv6 ingress pol") @@ -2078,7 +2105,7 @@ var _ = Describe("BPF Endpoint Manager", func() { It("check bpf endpoint count after pod churn", func() { start := bpfEpMgr.getNumEPs() - for i := 0; i < 3; i++ { + for i := range 3 { name := fmt.Sprintf("cali%d", i) genIfaceUpdate(name, ifacemonitor.StateUp, 1000+i)() genWLUpdate(name)() @@ -2217,46 +2244,52 @@ var _ = Describe("BPF Endpoint Manager", func() { bpfEpMgr.bpfLogLevel = "debug" bpfEpMgr.logFilters = map[string]string{"all": "tcp"} - for i := 0; i < 17; i++ { - _ = jumpMap.Update(jump.Key(i), jump.Value(uint32(1000+i))) - _ = jumpMap.Update(jump.Key(i+jump.TCMaxEntryPoints), jump.Value(uint32(1000+i))) + for i := range 13 { + _ = jumpMapIng.Update(jump.Key(i), jump.Value(uint32(1000+i))) + _ = jumpMapIng.Update(jump.Key(i+jump.TCMaxEntryPoints), jump.Value(uint32(1000+i))) + _ = jumpMapEgr.Update(jump.Key(i), jump.Value(uint32(1000+i))) + _ = jumpMapEgr.Update(jump.Key(i+jump.TCMaxEntryPoints), jump.Value(uint32(1000+i))) } - for i := 0; i < 5; i++ { + for i := range 9 { _ = xdpJumpMap.Update(jump.Key(i), jump.Value(uint32(2000+i))) } _ = ifStateMap.Update( ifstate.NewKey(123).AsBytes(), ifstate.NewValue(ifstate.FlgIPv6Ready|ifstate.FlgIPv4Ready, "eth123", - 5, 6, 7, 1, 1, 2, 3, 4).AsBytes(), + 0, 0, 0, 1, 1, 1, 2, 2).AsBytes(), ) _ = ifStateMap.Update( ifstate.NewKey(124).AsBytes(), ifstate.NewValue(0, "eth124", - 8, 9, 10, 2, 5, 6, 7, 8).AsBytes(), + 2, 3, 3, 3, 4, 4, 5, 5).AsBytes(), ) _ = ifStateMap.Update( ifstate.NewKey(125).AsBytes(), ifstate.NewValue(ifstate.FlgWEP|ifstate.FlgIPv6Ready|ifstate.FlgIPv4Ready, "eth125", - 13, 14, 15, 3, 9, 10, 11, 12).AsBytes(), + 4, 6, 6, 5, 7, 7, 8, 8).AsBytes(), ) _ = ifStateMap.Update( ifstate.NewKey(126).AsBytes(), ifstate.NewValue(ifstate.FlgWEP, "eth123", - 16, 17, 18, 0, 13, 14, 15, 0).AsBytes(), + 6, 9, 9, 7, 10, 10, 11, 11).AsBytes(), ) err := bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) Expect(ifStateMap.IsEmpty()).To(BeTrue()) - Expect(jumpMap.Contents).To(Equal(map[string]string{ - string(jump.Key(16)): string(jump.Value(uint32(1000 + 16))), - string(jump.Key(16 + jump.TCMaxEntryPoints)): string(jump.Value(uint32(1000 + 16))), + Expect(jumpMapIng.Contents).To(Equal(map[string]string{ + string(jump.Key(12)): string(jump.Value(uint32(1000 + 12))), + string(jump.Key(12 + jump.TCMaxEntryPoints)): string(jump.Value(uint32(1000 + 12))), + })) + Expect(jumpMapEgr.Contents).To(Equal(map[string]string{ + string(jump.Key(12)): string(jump.Value(uint32(1000 + 12))), + string(jump.Key(12 + jump.TCMaxEntryPoints)): string(jump.Value(uint32(1000 + 12))), })) Expect(xdpJumpMap.Contents).To(Equal(map[string]string{ // Key 4 wasn't used above so it should persist. - string(jump.Key(4)): string(jump.Value(uint32(2000 + 4))), + string(jump.Key(8)): string(jump.Value(uint32(2000 + 8))), })) }) @@ -2284,19 +2317,21 @@ var _ = Describe("BPF Endpoint Manager", func() { bpfEpMgr.bpfLogLevel = "debug" bpfEpMgr.logFilters = map[string]string{"all": "tcp"} - for i := 0; i < 8; i++ { - _ = jumpMap.Update(jump.Key(i), jump.Value(uint32(1000+i))) - _ = jumpMap.Update(jump.Key(i+jump.TCMaxEntryPoints), jump.Value(uint32(1000+i))) + for i := range 6 { + _ = jumpMapIng.Update(jump.Key(i), jump.Value(uint32(1000+i))) + _ = jumpMapIng.Update(jump.Key(i+jump.TCMaxEntryPoints), jump.Value(uint32(1000+i))) + _ = jumpMapEgr.Update(jump.Key(i), jump.Value(uint32(1000+i))) + _ = jumpMapEgr.Update(jump.Key(i+jump.TCMaxEntryPoints), jump.Value(uint32(1000+i))) } for i := 1; i < 2; i++ { _ = xdpJumpMap.Update(jump.Key(i), jump.Value(uint32(2000+i))) } key123 := ifstate.NewKey(123).AsBytes() - value124 := ifstate.NewValue(0, "eth124", 2, 5, 6, -1, 0, 4, 7, 1) + value124 := ifstate.NewValue(0, "eth124", 2, 3, 3, -1, 4, 4, 5, 5) value123 := ifstate.NewValue(ifstate.FlgIPv6Ready|ifstate.FlgWEP|ifstate.FlgIPv4Ready, "cali12345", - -1, 0, 2, -1, 1, 5, 3, 4) + -1, 0, 0, -1, 1, 1, 2, 2) _ = ifStateMap.Update( key123, @@ -2317,7 +2352,7 @@ var _ = Describe("BPF Endpoint Manager", func() { } return nil, errors.New("no such network interface") } - genWLUpdate("cali12345", "pol-a")() + genWLUpdate("cali12345", &proto.PolicyID{Name: "pol-a", Kind: v3.KindNetworkPolicy})() err := bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) @@ -2327,13 +2362,18 @@ var _ = Describe("BPF Endpoint Manager", func() { })) // Expect clean-up deletions. - jmps := dumpJumpMap(jumpMap) - Expect(jmps).To(HaveLen(5)) - Expect(jmps).To(HaveKey(2)) /* filters reloaded to reflect current expressions */ - Expect(jmps).To(HaveKey(3)) - Expect(jmps).To(HaveKey(4)) - Expect(jmps).To(HaveKeyWithValue(10002, 1002)) - Expect(jmps).To(HaveKeyWithValue(10003, 1003)) + checkJumpMap := func(m *mock.Map) { + jmps := dumpJumpMap(m) + Expect(jmps).To(HaveLen(6)) + Expect(jmps).To(HaveKey(0)) /* filters reloaded to reflect current expressions */ + Expect(jmps).To(HaveKey(1)) + Expect(jmps).To(HaveKey(2)) + Expect(jmps).To(HaveKeyWithValue(10001, 1001)) + Expect(jmps).To(HaveKeyWithValue(10002, 1002)) + Expect(jmps).To(HaveKeyWithValue(10000, 1000)) + } + checkJumpMap(jumpMapIng) + checkJumpMap(jumpMapEgr) Expect(dumpJumpMap(xdpJumpMap)).To(Equal(map[int]int{ 1: 2001, })) @@ -2365,7 +2405,7 @@ var _ = Describe("BPF Endpoint Manager", func() { } return nil, errors.New("no such network interface") } - genWLUpdate("cali12345", "pol-a")() + genWLUpdate("cali12345", &proto.PolicyID{Name: "pol-a", Kind: v3.KindNetworkPolicy})() err := bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) @@ -2373,7 +2413,7 @@ var _ = Describe("BPF Endpoint Manager", func() { // XDP gets cleaned up because it's a WEP, ingress keeps its // ID because it was the first; egress gets reallocated. value123Fixed := ifstate.NewValue(ifstate.FlgIPv6Ready|ifstate.FlgIPv4Ready|ifstate.FlgWEP, "cali12345", - -1, 0, 1, -1, 2, 3, -1, -1) + -1, 0, 0, -1, 1, 1, -1, -1) Expect(dumpIfstateMap(ifStateMap)).To(Equal(map[int]string{ 123: value123Fixed.String(), })) @@ -2420,8 +2460,8 @@ var _ = Describe("BPF Endpoint Manager", func() { } return nil, errors.New("no such network interface") } - genWLUpdate("cali12345", "pol-a")() - genWLUpdate("cali56789", "pol-b")() + genWLUpdate("cali12345", &proto.PolicyID{Name: "pol-a", Kind: v3.KindNetworkPolicy})() + genWLUpdate("cali56789", &proto.PolicyID{Name: "pol-b", Kind: v3.KindNetworkPolicy})() err := bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) @@ -2432,23 +2472,25 @@ var _ = Describe("BPF Endpoint Manager", func() { val123 := ifstate.ValueFromBytes([]byte(ifStateMap.Contents[string(key123)])) val124 := ifstate.ValueFromBytes([]byte(ifStateMap.Contents[string(key124)])) - tcIDsSeen := set.New[int]() + tcIDsSeenIngress := set.New[int]() + tcIDsSeenEgress := set.New[int]() for _, v := range []ifstate.Value{val123, val124} { Expect(v.IngressPolicyV6()).NotTo(Equal(-1), "WEPs should have ingress pol") - Expect(tcIDsSeen.Contains(v.IngressPolicyV6())).To(BeFalse(), "Saw same jump map ID more than once") - tcIDsSeen.Add(v.IngressPolicyV6()) + Expect(tcIDsSeenIngress.Contains(v.IngressPolicyV6())).To(BeFalse(), "Saw same jump map ID more than once") + tcIDsSeenIngress.Add(v.IngressPolicyV6()) Expect(v.EgressPolicyV6()).NotTo(Equal(-1), "WEPs should have egress pol") - Expect(tcIDsSeen.Contains(v.EgressPolicyV6())).To(BeFalse(), "Saw same jump map ID more than once") - tcIDsSeen.Add(v.EgressPolicyV6()) + Expect(tcIDsSeenEgress.Contains(v.EgressPolicyV6())).To(BeFalse(), "Saw same jump map ID more than once") + tcIDsSeenEgress.Add(v.EgressPolicyV6()) Expect(v.XDPPolicyV4()).To(Equal(-1), "WEPs shouldn't get XDP IDs") Expect(v.IngressPolicyV4()).NotTo(Equal(-1), "WEPs should have ingress pol") - Expect(tcIDsSeen.Contains(v.IngressPolicyV4())).To(BeFalse(), "Saw same jump map ID more than once") - tcIDsSeen.Add(v.IngressPolicyV4()) + Expect(tcIDsSeenIngress.Contains(v.IngressPolicyV4())).To(BeFalse(), "Saw same jump map ID more than once") + tcIDsSeenIngress.Add(v.IngressPolicyV4()) Expect(v.EgressPolicyV4()).NotTo(Equal(-1), "WEPs should have egress pol") - Expect(tcIDsSeen.Contains(v.EgressPolicyV4())).To(BeFalse(), "Saw same jump map ID more than once") + Expect(tcIDsSeenEgress.Contains(v.EgressPolicyV4())).To(BeFalse(), "Saw same jump map ID more than once") + tcIDsSeenEgress.Add(v.EgressPolicyV4()) Expect(v.TcIngressFilter()).To(Equal(-1), "should be no filters in use") Expect(v.TcEgressFilter()).To(Equal(-1), "should be no filters in use") @@ -2503,27 +2545,28 @@ var _ = Describe("BPF Endpoint Manager", func() { val124 := ifstate.ValueFromBytes([]byte(ifStateMap.Contents[string(key124)])) xdpIDsSeen := set.New[int]() - tcIDsSeen := set.New[int]() + tcIDsSeenIngress := set.New[int]() + tcIDsSeenEgress := set.New[int]() for _, v := range []ifstate.Value{val123, val124} { Expect(v.IngressPolicyV6()).NotTo(Equal(-1), "WEPs should have ingress pol") - Expect(tcIDsSeen.Contains(v.IngressPolicyV6())).To(BeFalse(), "Saw same jump map ID more than once") - tcIDsSeen.Add(v.IngressPolicyV6()) + Expect(tcIDsSeenIngress.Contains(v.IngressPolicyV6())).To(BeFalse(), "Saw same jump map ID more than once") + tcIDsSeenIngress.Add(v.IngressPolicyV6()) Expect(v.EgressPolicyV6()).NotTo(Equal(-1), "WEPs should have egress pol") - Expect(tcIDsSeen.Contains(v.EgressPolicyV6())).To(BeFalse(), "Saw same jump map ID more than once") - tcIDsSeen.Add(v.EgressPolicyV6()) + Expect(tcIDsSeenEgress.Contains(v.EgressPolicyV6())).To(BeFalse(), "Saw same jump map ID more than once") + tcIDsSeenEgress.Add(v.EgressPolicyV6()) Expect(v.XDPPolicyV4()).NotTo(Equal(-1), "WEPs shouldn't get XDP IDs") Expect(xdpIDsSeen.Contains(v.XDPPolicyV4())).To(BeFalse(), fmt.Sprintf("Saw same jump XDP map ID %d more than once", v.XDPPolicyV4())) xdpIDsSeen.Add(v.XDPPolicyV4()) Expect(v.IngressPolicyV4()).NotTo(Equal(-1), "WEPs should have ingress pol") - Expect(tcIDsSeen.Contains(v.IngressPolicyV4())).To(BeFalse(), "Saw same jump map ID more than once") - tcIDsSeen.Add(v.IngressPolicyV4()) + Expect(tcIDsSeenIngress.Contains(v.IngressPolicyV4())).To(BeFalse(), "Saw same jump map ID more than once") + tcIDsSeenIngress.Add(v.IngressPolicyV4()) Expect(v.EgressPolicyV4()).NotTo(Equal(-1), "WEPs should have egress pol") - Expect(tcIDsSeen.Contains(v.EgressPolicyV4())).To(BeFalse(), "Saw same jump map ID more than once") - tcIDsSeen.Add(v.EgressPolicyV4()) + Expect(tcIDsSeenEgress.Contains(v.EgressPolicyV4())).To(BeFalse(), "Saw same jump map ID more than once") + tcIDsSeenEgress.Add(v.EgressPolicyV4()) Expect(v.TcIngressFilter()).To(Equal(-1), "should be no filters in use") Expect(v.TcEgressFilter()).To(Equal(-1), "should be no filters in use") @@ -2671,12 +2714,13 @@ var _ = Describe("BPF Endpoint Manager", func() { It("should clean up WL policies and log filters when iface down", func() { genIfaceUpdate("cali12345", ifacemonitor.StateUp, 15)() genPolicy("default", "mypolicy")() - genWLUpdate("cali12345", "mypolicy")() + genWLUpdate("cali12345", &proto.PolicyID{Name: "mypolicy", Kind: v3.KindNetworkPolicy})() err := bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) - Expect(jumpMap.Contents).To(HaveLen(4)) // 2x policy and 2x filter + Expect(jumpMapIng.Contents).To(HaveLen(2)) // 2x policy and 2x filter + Expect(jumpMapEgr.Contents).To(HaveLen(2)) // 2x policy and 2x filter Expect(xdpJumpMap.Contents).To(HaveLen(0)) genIfaceUpdate("cali12345", ifacemonitor.StateDown, 15)() @@ -2684,7 +2728,7 @@ var _ = Describe("BPF Endpoint Manager", func() { err = bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) - Expect(jumpMap.Contents).To(HaveLen(0)) + Expect(jumpMapIng.Contents).To(HaveLen(0)) Expect(xdpJumpMap.Contents).To(HaveLen(0)) }) @@ -2695,14 +2739,15 @@ var _ = Describe("BPF Endpoint Manager", func() { hostEp := googleproto.Clone(hostEpNorm).(*proto.HostEndpoint) hostEp.UntrackedTiers = []*proto.TierInfo{{ Name: "default", - IngressPolicies: []string{"untracked1"}, + IngressPolicies: []*proto.PolicyID{{Name: "untracked1", Kind: v3.KindNetworkPolicy}}, }} genHEPUpdate("eth0", hostEp)() err := bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) - Expect(jumpMap.Contents).To(HaveLen(4)) // 2x policy and 2x filter + Expect(jumpMapIng.Contents).To(HaveLen(2)) // 2x policy and 2x filter + Expect(jumpMapEgr.Contents).To(HaveLen(2)) // 2x policy and 2x filter Expect(xdpJumpMap.Contents).To(HaveLen(1)) // 1x policy genIfaceUpdate("eth0", ifacemonitor.StateDown, 10)() @@ -2710,7 +2755,7 @@ var _ = Describe("BPF Endpoint Manager", func() { err = bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) - Expect(jumpMap.Contents).To(HaveLen(0)) + Expect(jumpMapIng.Contents).To(HaveLen(0)) Expect(xdpJumpMap.Contents).To(HaveLen(0)) }) @@ -2723,32 +2768,40 @@ var _ = Describe("BPF Endpoint Manager", func() { } genIfaceUpdate("cali12345", ifacemonitor.StateUp, 15)() genPolicy("default", "mypolicy")() - genWLUpdate("cali12345", "mypolicy")() + genWLUpdate("cali12345", &proto.PolicyID{Name: "mypolicy", Kind: v3.KindNetworkPolicy})() err := bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) - Expect(jumpMap.Contents).To(HaveLen(4)) // 2x policy and 2x filter + Expect(jumpMapIng.Contents).To(HaveLen(2)) // 2x policy and 2x filter + Expect(jumpMapEgr.Contents).To(HaveLen(2)) // 2x policy and 2x filter Expect(xdpJumpMap.Contents).To(HaveLen(0)) - jumpCopyContents := make(map[string]string, len(jumpMap.Contents)) - for k, v := range jumpMap.Contents { - jumpCopyContents[k] = v - } + jumpCopyContentsIngress := make(map[string]string, len(jumpMapIng.Contents)) + jumpCopyContentsEgress := make(map[string]string, len(jumpMapEgr.Contents)) + maps0.Copy(jumpCopyContentsIngress, jumpMapIng.Contents) + maps0.Copy(jumpCopyContentsEgress, jumpMapEgr.Contents) genPolicy("default", "anotherpolicy")() - genWLUpdate("cali12345", "anotherpolicy")() + genWLUpdate("cali12345", &proto.PolicyID{Name: "anotherpolicy", Kind: v3.KindNetworkPolicy})() err = bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) - Expect(len(jumpCopyContents)).To(Equal(len(jumpMap.Contents))) - Expect(jumpCopyContents).NotTo(Equal(jumpMap.Contents)) + Expect(len(jumpCopyContentsIngress)).To(Equal(len(jumpMapIng.Contents))) + Expect(jumpCopyContentsIngress).NotTo(Equal(jumpMapIng.Contents)) + Expect(len(jumpCopyContentsEgress)).To(Equal(len(jumpMapEgr.Contents))) + Expect(jumpCopyContentsEgress).NotTo(Equal(jumpMapEgr.Contents)) changes := 0 - for k, v := range jumpCopyContents { - if v != jumpMap.Contents[k] { + for k, v := range jumpCopyContentsIngress { + if v != jumpMapIng.Contents[k] { + changes++ + } + } + for k, v := range jumpCopyContentsEgress { + if v != jumpMapEgr.Contents[k] { changes++ } } @@ -2757,10 +2810,10 @@ var _ = Describe("BPF Endpoint Manager", func() { // restart - jumpCopyContents = make(map[string]string, len(jumpMap.Contents)) - for k, v := range jumpMap.Contents { - jumpCopyContents[k] = v - } + jumpCopyContentsIngress = make(map[string]string, len(jumpMapIng.Contents)) + jumpCopyContentsEgress = make(map[string]string, len(jumpMapEgr.Contents)) + maps0.Copy(jumpCopyContentsIngress, jumpMapIng.Contents) + maps0.Copy(jumpCopyContentsEgress, jumpMapEgr.Contents) dp = newMockDataplane() mockDP = &mockProgMapDP{ @@ -2782,18 +2835,25 @@ var _ = Describe("BPF Endpoint Manager", func() { return nil, errors.New("no such network interface") } genPolicy("default", "anotherpolicy")() - genWLUpdate("cali12345", "anotherpolicy")() + genWLUpdate("cali12345", &proto.PolicyID{Name: "anotherpolicy", Kind: v3.KindNetworkPolicy})() err = bpfEpMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) - Expect(len(jumpCopyContents)).To(Equal(len(jumpMap.Contents))) - Expect(jumpCopyContents).NotTo(Equal(jumpMap.Contents)) + Expect(len(jumpCopyContentsIngress)).To(Equal(len(jumpMapIng.Contents))) + Expect(jumpCopyContentsIngress).NotTo(Equal(jumpMapIng.Contents)) + Expect(len(jumpCopyContentsEgress)).To(Equal(len(jumpMapEgr.Contents))) + Expect(jumpCopyContentsEgress).NotTo(Equal(jumpMapEgr.Contents)) changes = 0 - for k, v := range jumpCopyContents { - if v != jumpMap.Contents[k] { + for k, v := range jumpCopyContentsIngress { + if v != jumpMapIng.Contents[k] { + changes++ + } + } + for k, v := range jumpCopyContentsEgress { + if v != jumpMapEgr.Contents[k] { changes++ } } @@ -2810,11 +2870,11 @@ var _ = Describe("jumpMapAlloc tests", func() { var jma *jumpMapAlloc BeforeEach(func() { - jma = newJumpMapAlloc(5) + jma = newJumpMapAlloc("test", 5) }) It("should give initial values in order", func() { - for i := 0; i < 5; i++ { + for i := range 5 { idx, err := jma.Get("test") Expect(err).NotTo(HaveOccurred()) Expect(idx).To(Equal(i)) diff --git a/felix/dataplane/linux/bpf_route_mgr.go b/felix/dataplane/linux/bpf_route_mgr.go index 2de3244c927..e79f1708ccf 100644 --- a/felix/dataplane/linux/bpf_route_mgr.go +++ b/felix/dataplane/linux/bpf_route_mgr.go @@ -189,7 +189,7 @@ func newBPFRouteManager(config *Config, maps *bpfmap.IPMaps, ipFamily proto.IPVe return m } -func (m *bpfRouteManager) OnUpdate(msg interface{}) { +func (m *bpfRouteManager) OnUpdate(msg any) { switch msg := msg.(type) { // Updates to local IPs. We use these to include host IPs in the map. case *ifaceStateUpdate: @@ -244,10 +244,11 @@ func (m *bpfRouteManager) CompleteDeferredWork() error { } func (m *bpfRouteManager) recalculateRoutesForDirtyCIDRs() { - m.dirtyCIDRs.Iter(func(cidr ip.CIDR) error { + for cidr := range m.dirtyCIDRs.All() { // Ignore IPv4 routes if IPv6 is enabled and vice-versa. if uint8(m.ipFamily) != cidr.Version() { - return set.RemoveItem + m.dirtyCIDRs.Discard(cidr) + continue } dataplaneKey := m.bpfOps.NewKey(cidr) newValue := m.calculateRoute(cidr) @@ -256,21 +257,23 @@ func (m *bpfRouteManager) recalculateRoutesForDirtyCIDRs() { if newValue != nil { if exists && oldValue.Equal(newValue) { // Value is already correct. We're done. - return set.RemoveItem + m.dirtyCIDRs.Discard(cidr) + continue } m.desiredRoutes[dataplaneKey] = newValue m.onRouteUpdateCB(dataplaneKey, newValue) } else { if !exists { // Value is already correct. We're done. - return set.RemoveItem + m.dirtyCIDRs.Discard(cidr) + continue } delete(m.desiredRoutes, dataplaneKey) m.onRouteDeleteCB(dataplaneKey) } m.dirtyRoutes.Add(dataplaneKey) - return set.RemoveItem - }) + m.dirtyCIDRs.Discard(cidr) + } } func (m *bpfRouteManager) calculateRoute(cidr ip.CIDR) routes.ValueInterface { @@ -336,11 +339,8 @@ func (m *bpfRouteManager) calculateRoute(cidr ip.CIDR) routes.ValueInterface { } nodeIP := net.ParseIP(cgRoute.DstNodeIp) route = m.bpfOps.NewValueWithNextHop(flags, ip.FromNetIP(nodeIP)) - } else if rts&proto.RouteType_LOCAL_WORKLOAD == proto.RouteType_LOCAL_WORKLOAD && !cgRoute.Borrowed /* not ours */ { - if !cgRoute.LocalWorkload { - // Just the local IPAM block, not an actual workload. - return nil - } + } else if cgRoute.GetLocalWorkload() { + // Explicitly a local WorkloadEndpoint /32. if wepIDs, ok := m.cidrToWEPIDs[cidr]; ok { bestWepScore := -1 var bestWepID *proto.WorkloadEndpointID @@ -349,8 +349,8 @@ func (m *bpfRouteManager) calculateRoute(cidr ip.CIDR) routes.ValueInterface { "Multiple local workloads with same IP but BPF dataplane only supports single route. " + "Will choose one route.") } - wepIDs.Iter(func(wepID types.WorkloadEndpointID) error { - // Route is a local workload look up its name and interface details. + for wepID := range wepIDs.All() { + // Route is a local workload, look up its name and interface details. wepScore := 0 wep := m.wepIDToWorkload[wepID] ifaceName := wep.Name @@ -368,8 +368,7 @@ func (m *bpfRouteManager) calculateRoute(cidr ip.CIDR) routes.ValueInterface { bestWepID = pWepID bestWepScore = wepScore } - return nil - }) + } } } else if rts&proto.RouteType_REMOTE_HOST == proto.RouteType_REMOTE_HOST { flags |= routes.FlagsRemoteHost @@ -390,6 +389,7 @@ func (m *bpfRouteManager) calculateRoute(cidr ip.CIDR) routes.ValueInterface { // hostname. flags |= routes.FlagsLocalHost } else if rts&proto.RouteType_REMOTE_WORKLOAD == proto.RouteType_REMOTE_WORKLOAD { + // Either a remote IPAM block or a remote workload with a borrowed IP. flags |= routes.FlagsRemoteWorkload if m.wgEnabled { flags |= routes.FlagTunneled @@ -407,6 +407,10 @@ func (m *bpfRouteManager) calculateRoute(cidr ip.CIDR) routes.ValueInterface { } nodeIP := net.ParseIP(cgRoute.DstNodeIp) route = m.bpfOps.NewValueWithNextHop(flags, ip.FromNetIP(nodeIP)) + } else if rts&proto.RouteType_LOCAL_WORKLOAD == proto.RouteType_LOCAL_WORKLOAD { + // Local IPAM block. We don't need to have a map entry for that right + // now. + return nil } if route == nil && flags != 0 { @@ -418,7 +422,7 @@ func (m *bpfRouteManager) calculateRoute(cidr ip.CIDR) routes.ValueInterface { func (m *bpfRouteManager) applyUpdates() (numDels uint, numAdds uint) { debug := log.GetLevel() >= log.DebugLevel - m.dirtyRoutes.Iter(func(key routes.KeyInterface) error { + for key := range m.dirtyRoutes.All() { value, present := m.desiredRoutes[key] if !present { // Delete the key. @@ -430,9 +434,10 @@ func (m *bpfRouteManager) applyUpdates() (numDels uint, numAdds uint) { if err != nil { log.WithFields(log.Fields{"key": key}).Error("Failed to delete from BPF map") m.resyncScheduled = true - return nil + continue } - return set.RemoveItem + m.dirtyRoutes.Discard(key) + continue } // If we get here, we're doing an update. @@ -444,10 +449,10 @@ func (m *bpfRouteManager) applyUpdates() (numDels uint, numAdds uint) { if err != nil { log.WithFields(log.Fields{"key": key}).Error("Failed to update BPF map") m.resyncScheduled = true - return nil + continue } - return set.RemoveItem - }) + m.dirtyRoutes.Discard(key) + } return } @@ -518,12 +523,11 @@ func (m *bpfRouteManager) onIfaceIdxChanged(name string) { if wepIDs == nil { return } - wepIDs.Iter(func(wepID types.WorkloadEndpointID) error { + for wepID := range wepIDs.All() { wep := m.wepIDToWorkload[wepID] cidrs := m.getWorkloadCIDRs(wep) m.markCIDRsDirty(cidrs...) - return nil - }) + } } func (m *bpfRouteManager) onIfaceAddrsUpdate(update *ifaceAddrsUpdate) { @@ -534,25 +538,24 @@ func (m *bpfRouteManager) onIfaceAddrsUpdate(update *ifaceAddrsUpdate) { newCIDRs = set.Empty[ip.CIDR]() } else { newCIDRs = set.New[ip.CIDR]() - update.Addrs.Iter(func(cidrStr string) error { + for cidrStr := range update.Addrs.All() { cidr := ip.MustParseCIDROrIP(cidrStr) if uint8(m.ipFamily) != cidr.Version() { - return nil + continue } if cidr.Addr().AsNetIP().IsGlobalUnicast() { newCIDRs.Add(cidr) } - return nil - }) + } } cidrs := m.localIfaceToCIDRs[update.Name] if cidrs != nil { - cidrs.Iter(func(cidr ip.CIDR) error { + for cidr := range cidrs.All() { if newCIDRs.Contains(cidr) { // No change for this address. newCIDRs.Discard(cidr) - return nil + continue } // Address deleted. changed = true @@ -561,11 +564,11 @@ func (m *bpfRouteManager) onIfaceAddrsUpdate(update *ifaceAddrsUpdate) { delete(m.cidrToLocalIfaces, cidr) } m.markCIDRsDirty(cidr) - return set.RemoveItem - }) + cidrs.Discard(cidr) + } } - newCIDRs.Iter(func(cidr ip.CIDR) error { + for cidr := range newCIDRs.All() { changed = true ifaceNames := m.cidrToLocalIfaces[cidr] if ifaceNames == nil { @@ -579,8 +582,8 @@ func (m *bpfRouteManager) onIfaceAddrsUpdate(update *ifaceAddrsUpdate) { } m.markCIDRsDirty(cidr) cidrs.Add(cidr) - return set.RemoveItem - }) + newCIDRs.Discard(cidr) + } if changed { var newIPs []net.IP @@ -696,13 +699,13 @@ func (m *bpfRouteManager) onBGPConfigUpdate(update *proto.GlobalBGPConfigUpdate) cidrsToDel.Discard(cidr) } // Delete the unused routes. - cidrsToDel.Iter(func(cidr ip.CIDR) error { + for cidr := range cidrsToDel.All() { if _, ok := m.cidrToRoute[cidr]; ok { delete(m.cidrToRoute, cidr) m.dirtyCIDRs.Add(cidr) } - return set.RemoveItem - }) + cidrsToDel.Discard(cidr) + } } func (m *bpfRouteManager) removeWEP(id *types.WorkloadEndpointID) { diff --git a/felix/dataplane/linux/dscp_mgr.go b/felix/dataplane/linux/dscp_mgr.go index 917f6c70a35..8ca191c9c8b 100644 --- a/felix/dataplane/linux/dscp_mgr.go +++ b/felix/dataplane/linux/dscp_mgr.go @@ -69,7 +69,7 @@ func newDSCPManager( } } -func (m *dscpManager) OnUpdate(msg interface{}) { +func (m *dscpManager) OnUpdate(msg any) { switch msg := msg.(type) { case *proto.HostEndpointUpdate: m.handleHEPUpdates(msg.GetId(), msg) diff --git a/felix/dataplane/linux/dscp_mgr_test.go b/felix/dataplane/linux/dscp_mgr_test.go index 0f548b41d32..981b38b1b04 100644 --- a/felix/dataplane/linux/dscp_mgr_test.go +++ b/felix/dataplane/linux/dscp_mgr_test.go @@ -15,7 +15,7 @@ package intdataplane import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/sirupsen/logrus" @@ -27,8 +27,10 @@ import ( "github.com/projectcalico/calico/libcalico-go/lib/set" ) -var _ = Describe("DSCP manager - IPv4", dscpManagerTests(4)) -var _ = Describe("DSCP manager - IPv6", dscpManagerTests(6)) +var ( + _ = Describe("DSCP manager - IPv4", dscpManagerTests(4)) + _ = Describe("DSCP manager - IPv6", dscpManagerTests(6)) +) func dscpManagerTests(ipVersion uint8) func() { return func() { @@ -49,7 +51,7 @@ func dscpManagerTests(ipVersion uint8) func() { MarkScratch1: 0x8, MarkDrop: 0x10, MarkEndpoint: 0x11110000, - }) + }, false) manager = newDSCPManager(ipSets, mangleTable, ruleRenderer, ipVersion, Config{ MaxIPSetSize: 1024, diff --git a/felix/dataplane/linux/endpoint_mgr.go b/felix/dataplane/linux/endpoint_mgr.go index 0475dcafd87..7d1b24320fd 100644 --- a/felix/dataplane/linux/endpoint_mgr.go +++ b/felix/dataplane/linux/endpoint_mgr.go @@ -221,7 +221,7 @@ type endpointManager struct { bpfEndpointManager hepListener } -type EndpointStatusUpdateCallback func(ipVersion uint8, id interface{}, status string, extraInfo interface{}) +type EndpointStatusUpdateCallback func(ipVersion uint8, id any, status string, extraInfo any) type procSysWriter func(path, value string) error @@ -381,7 +381,7 @@ func newEndpointManagerWithShims( return epManager } -func (m *endpointManager) OnUpdate(protoBufMsg interface{}) { +func (m *endpointManager) OnUpdate(protoBufMsg any) { log.WithField("msg", protoBufMsg).Debug("Received message") switch msg := protoBufMsg.(type) { case *proto.WorkloadEndpointUpdate: @@ -519,12 +519,9 @@ wepLoop: continue // Already have an update, skip the scan. } for _, t := range wep.Tiers { - for _, pols := range [][]string{t.IngressPolicies, t.EgressPolicies} { + for _, pols := range [][]*proto.PolicyID{t.IngressPolicies, t.EgressPolicies} { for _, p := range pols { - polID := types.PolicyID{ - Tier: t.Name, - Name: p, - } + polID := types.ProtoToPolicyID(p) if m.dirtyPolicyIDs.Contains(polID) { m.pendingWlEpUpdates[wepID] = wep continue wepLoop @@ -556,12 +553,9 @@ wepLoop: func (m *endpointManager) tiersUseDirtyPolicy(tiers []*proto.TierInfo) bool { for _, t := range tiers { - for _, pols := range [][]string{t.IngressPolicies, t.EgressPolicies} { + for _, pols := range [][]*proto.PolicyID{t.IngressPolicies, t.EgressPolicies} { for _, p := range pols { - polID := types.PolicyID{ - Tier: t.Name, - Name: p, - } + polID := types.ProtoToPolicyID(p) if m.dirtyPolicyIDs.Contains(polID) { return true } @@ -589,7 +583,7 @@ func (m *endpointManager) markEndpointStatusDirtyByIface(ifaceName string) { func (m *endpointManager) updateEndpointStatuses() { log.WithField("dirtyEndpoints", m.epIDsToUpdateStatus).Debug("Reporting endpoint status.") - m.epIDsToUpdateStatus.Iter(func(item interface{}) error { + for item := range m.epIDsToUpdateStatus.All() { switch id := item.(type) { case types.WorkloadEndpointID: status, endpoint := m.calculateWorkloadEndpointStatus(id) @@ -599,8 +593,8 @@ func (m *endpointManager) updateEndpointStatuses() { m.OnEndpointStatusUpdate(m.ipVersion, id, status, nil) } - return set.RemoveItem - }) + m.epIDsToUpdateStatus.Discard(item) + } } func (m *endpointManager) calculateWorkloadEndpointStatus(id types.WorkloadEndpointID) (string, *proto.WorkloadEndpoint) { @@ -831,7 +825,10 @@ func (m *endpointManager) resolveWorkloadEndpoints() { logCxt.Debug("Endpoint up, adding routes") for _, s := range ipStrings { routeTargets = append(routeTargets, routetable.Target{ - CIDR: ip.MustParseCIDROrIP(s), + RouteKey: routetable.RouteKey{ + + CIDR: ip.MustParseCIDROrIP(s), + }, DestMAC: mac, }) } @@ -914,7 +911,7 @@ func (m *endpointManager) resolveWorkloadEndpoints() { } } - m.wlIfaceNamesToReconfigure.Iter(func(ifaceName string) error { + for ifaceName := range m.wlIfaceNamesToReconfigure.All() { err := m.configureInterface(ifaceName) if err != nil { if exists, err := m.interfaceExistsInProcSys(ifaceName); err == nil && !exists { @@ -923,10 +920,10 @@ func (m *endpointManager) resolveWorkloadEndpoints() { } else { log.WithError(err).Warn("Failed to configure interface, will retry") } - return nil + continue } - return set.RemoveItem - }) + m.wlIfaceNamesToReconfigure.Discard(ifaceName) + } } func (m *endpointManager) updateWorkloadEndpointChains( @@ -961,10 +958,10 @@ func (m *endpointManager) groupTieredPolicy(tieredPolicies []*proto.TierInfo, fi for _, tierInfo := range tieredPolicies { var inPols, outPols []*rules.PolicyGroup if filter&includeInbound != 0 { - inPols = m.groupPolicies(tierInfo.Name, tierInfo.IngressPolicies, rules.PolicyDirectionInbound) + inPols = m.groupPolicies(tierInfo.IngressPolicies, rules.PolicyDirectionInbound) } if filter&includeOutbound != 0 { - outPols = m.groupPolicies(tierInfo.Name, tierInfo.EgressPolicies, rules.PolicyDirectionOutbound) + outPols = m.groupPolicies(tierInfo.EgressPolicies, rules.PolicyDirectionOutbound) } tierPolGroups = append(tierPolGroups, rules.TierPolicyGroups{ Name: tierInfo.Name, @@ -993,6 +990,14 @@ func (m *endpointManager) hasSourceSpoofingConfiguration(interfaceName string) b return ok } +func getAddrIpVersion(addr string) uint8 { + ip, _, _ := net.ParseCIDR(addr) + if ip.To4() == nil { + return 6 + } + return 4 +} + func (m *endpointManager) updateRPFSkipChain() { log.Debug("Updating RPF skip chain") chain := &generictables.Chain{ @@ -1001,10 +1006,12 @@ func (m *endpointManager) updateRPFSkipChain() { } for interfaceName, addresses := range m.sourceSpoofingConfig { for _, addr := range addresses { - chain.Rules = append(chain.Rules, generictables.Rule{ - Match: m.newMatch().InInterface(interfaceName).SourceNet(addr), - Action: m.actions.Allow(), - }) + if m.ipVersion == getAddrIpVersion(addr) { + chain.Rules = append(chain.Rules, generictables.Rule{ + Match: m.newMatch().InInterface(interfaceName).SourceNet(addr), + Action: m.actions.Allow(), + }) + } } } m.rawTable.UpdateChain(chain) @@ -1574,34 +1581,28 @@ func (m *endpointManager) GetRawHostEndpoints() map[types.HostEndpointID]*proto. return m.rawHostEndpoints } -func (m *endpointManager) groupPolicies(tierName string, names []string, direction rules.PolicyDirection) []*rules.PolicyGroup { - if len(names) == 0 { +func (m *endpointManager) groupPolicies(policies []*proto.PolicyID, direction rules.PolicyDirection) []*rules.PolicyGroup { + if len(policies) == 0 { return nil } + p0 := types.ProtoToPolicyID(policies[0]) group := &rules.PolicyGroup{ - Tier: tierName, - Direction: direction, - PolicyNames: []string{names[0]}, - Selector: m.activePolicySelectors[types.PolicyID{ - Tier: tierName, - Name: names[0], - }], + Direction: direction, + Policies: []*types.PolicyID{&p0}, + Selector: m.activePolicySelectors[p0], } groups := []*rules.PolicyGroup{group} - for _, name := range names[1:] { - sel := m.activePolicySelectors[types.PolicyID{ - Tier: tierName, - Name: name, - }] + for _, pol := range policies[1:] { + pId := types.ProtoToPolicyID(pol) + sel := m.activePolicySelectors[pId] if sel != group.Selector { group = &rules.PolicyGroup{ - Tier: tierName, Direction: direction, Selector: sel, } groups = append(groups, group) } - group.PolicyNames = append(group.PolicyNames, name) + group.Policies = append(group.Policies, &pId) } return groups } diff --git a/felix/dataplane/linux/endpoint_mgr_test.go b/felix/dataplane/linux/endpoint_mgr_test.go index 5cec4ac5fb5..743ba9e3dd6 100644 --- a/felix/dataplane/linux/endpoint_mgr_test.go +++ b/felix/dataplane/linux/endpoint_mgr_test.go @@ -22,10 +22,10 @@ import ( "sync" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/format" - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" "github.com/vishvananda/netlink" @@ -171,8 +171,8 @@ func wlChainsForIfaces(ipVersion uint8, ifaceTierNames []string, epMarkMapper ru } func tierToPolicyName(tierName string) string { - if strings.HasPrefix(tierName, "tier") { - return "pol" + strings.TrimPrefix(tierName, "tier") + if after, ok := strings.CutPrefix(tierName, "tier"); ok { + return "pol" + after } return "a" } @@ -257,10 +257,10 @@ func chainsForIfaces(ipVersion uint8, ifaceName = nameParts[0] if strings.HasPrefix(nameParts[1], "pol") { tierName = "default" - polName = "/" + nameParts[1] + polName = nameParts[1] } else { tierName = nameParts[1] - polName = "/" + tierToPolicyName(tierName) + polName = tierToPolicyName(tierName) } ifaceKind = "normal" } else { @@ -270,10 +270,10 @@ func chainsForIfaces(ipVersion uint8, ifaceName = nameParts[0] if strings.HasPrefix(nameParts[1], "pol") { tierName = "default" - polName = "/" + nameParts[1] + polName = nameParts[1] } else { tierName = nameParts[1] - polName = "/" + tierToPolicyName(tierName) + polName = tierToPolicyName(tierName) } switch nameParts[2] { case "ingress": @@ -335,9 +335,16 @@ func chainsForIfaces(ipVersion uint8, Action: iptables.ClearMarkAction{Mark: 16}, Comment: []string{"Start of tier " + tierName}, }) + + // Determine the policy chain name. + target := rules.PolicyChainName( + "cali-po-", + &types.PolicyID{Name: polName, Kind: v3.KindGlobalNetworkPolicy}, + false, + ) outRules = append(outRules, generictables.Rule{ Match: iptables.Match().MarkClear(16), - Action: iptables.JumpAction{Target: "cali-po-" + tierName + polName}, + Action: iptables.JumpAction{Target: target}, }) if tableKind == "untracked" { outRules = append(outRules, generictables.Rule{ @@ -446,9 +453,15 @@ func chainsForIfaces(ipVersion uint8, Comment: []string{"Start of tier " + tierName}, }) // For untracked policy, we expect a tier with a policy in it. + // Determine the policy chain name. + target := rules.PolicyChainName( + "cali-pi-", + &types.PolicyID{Name: polName, Kind: v3.KindGlobalNetworkPolicy}, + false, + ) inRules = append(inRules, generictables.Rule{ Match: iptables.Match().MarkClear(16), - Action: iptables.JumpAction{Target: "cali-pi-" + tierName + polName}, + Action: iptables.JumpAction{Target: target}, }) if tableKind == "untracked" { inRules = append(inRules, generictables.Rule{ @@ -709,7 +722,7 @@ func (t *mockRouteTable) SetRoutes(routeClass routetable.RouteClass, ifaceName s t.currentRoutes[ifaceName] = targets } -func (t *mockRouteTable) RouteRemove(routeClass routetable.RouteClass, ifaceName string, cidr ip.CIDR) { +func (t *mockRouteTable) RouteRemove(routeClass routetable.RouteClass, ifaceName string, routeKey routetable.RouteKey) { } func (t *mockRouteTable) RouteUpdate(routeClass routetable.RouteClass, ifaceName string, target routetable.Target) { @@ -737,11 +750,11 @@ func (t *mockRouteTable) checkRoutes(ifaceName string, expected []routetable.Tar } type statusReportRecorder struct { - currentState map[interface{}]string - extraInfo map[interface{}]interface{} + currentState map[any]string + extraInfo map[any]any } -func (r *statusReportRecorder) endpointStatusUpdateCallback(ipVersion uint8, id interface{}, status string, extraInfo interface{}) { +func (r *statusReportRecorder) endpointStatusUpdateCallback(ipVersion uint8, id any, status string, extraInfo any) { log.WithFields(log.Fields{ "ipVersion": ipVersion, "id": id, @@ -827,7 +840,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { }) JustBeforeEach(func() { - renderer := rules.NewRenderer(rrConfigNormal) + renderer := rules.NewRenderer(rrConfigNormal, false) rawTable = newMockTable("raw") mangleTable = newMockTable("mangle") filterTable = newMockTable("filter") @@ -836,7 +849,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { currentRoutes: map[string][]routetable.Target{}, } mockProcSys = &testProcSys{state: map[string]string{}, pathsThatExist: map[string]bool{}} - statusReportRec = &statusReportRecorder{currentState: map[interface{}]string{}, extraInfo: map[interface{}]interface{}{}} + statusReportRec = &statusReportRecorder{currentState: map[any]string{}, extraInfo: map[any]any{}} hepListener = &testHEPListener{} nlDataplane = mocknetlink.New() Expect(err).NotTo(HaveOccurred()) @@ -868,7 +881,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { "1", nil, false, - apiv3.BPFAttachOptionTCX, + v3.BPFAttachOptionTCX, hepListener, common.NewCallbacks(), true, @@ -889,14 +902,20 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { if spec.tierName != "" { parts := strings.Split(spec.tierName, "_") var tierName string - var policies []string + var policies []*proto.PolicyID if len(parts) == 1 { if strings.HasPrefix(parts[0], "pol") { tierName = "default" - policies = []string{parts[0]} + policies = []*proto.PolicyID{{ + Name: parts[0], + Kind: v3.KindGlobalNetworkPolicy, + }} } else { tierName = parts[0] - policies = []string{tierToPolicyName(tierName)} + policies = []*proto.PolicyID{{ + Name: tierToPolicyName(tierName), + Kind: v3.KindGlobalNetworkPolicy, + }} } tiers = append(tiers, &proto.TierInfo{ Name: tierName, @@ -906,10 +925,16 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { } else if len(parts) == 2 && parts[1] == "untracked" { if strings.HasPrefix(parts[0], "pol") { tierName = "default" - policies = []string{parts[0]} + policies = []*proto.PolicyID{{ + Name: parts[0], + Kind: v3.KindGlobalNetworkPolicy, + }} } else { tierName = parts[0] - policies = []string{tierToPolicyName(tierName)} + policies = []*proto.PolicyID{{ + Name: tierToPolicyName(tierName), + Kind: v3.KindGlobalNetworkPolicy, + }} } untrackedTiers = append(untrackedTiers, &proto.TierInfo{ Name: tierName, @@ -919,10 +944,16 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { } else if len(parts) == 2 && parts[1] == "preDNAT" { if strings.HasPrefix(parts[0], "pol") { tierName = "default" - policies = []string{parts[0]} + policies = []*proto.PolicyID{{ + Name: parts[0], + Kind: v3.KindGlobalNetworkPolicy, + }} } else { tierName = parts[0] - policies = []string{tierToPolicyName(tierName)} + policies = []*proto.PolicyID{{ + Name: tierToPolicyName(tierName), + Kind: v3.KindGlobalNetworkPolicy, + }} } preDNATTiers = append(preDNATTiers, &proto.TierInfo{ Name: tierName, @@ -931,16 +962,22 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { } else if len(parts) == 2 && parts[1] == "applyOnForward" { forwardTiers = append(forwardTiers, &proto.TierInfo{ Name: "default", - IngressPolicies: []string{parts[0]}, - EgressPolicies: []string{parts[0]}, + IngressPolicies: []*proto.PolicyID{{Name: parts[0], Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: []*proto.PolicyID{{Name: parts[0], Kind: v3.KindGlobalNetworkPolicy}}, }) } else if len(parts) == 2 && parts[1] == "ingress" { if strings.HasPrefix(parts[0], "pol") { tierName = "default" - policies = []string{parts[0]} + policies = []*proto.PolicyID{{ + Name: parts[0], + Kind: v3.KindGlobalNetworkPolicy, + }} } else { tierName = parts[0] - policies = []string{tierToPolicyName(tierName)} + policies = []*proto.PolicyID{{ + Name: tierToPolicyName(tierName), + Kind: v3.KindGlobalNetworkPolicy, + }} } tiers = append(tiers, &proto.TierInfo{ Name: tierName, @@ -949,10 +986,16 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { } else if len(parts) == 2 && parts[1] == "egress" { if strings.HasPrefix(parts[0], "pol") { tierName = "default" - policies = []string{parts[0]} + policies = []*proto.PolicyID{{ + Name: parts[0], + Kind: v3.KindGlobalNetworkPolicy, + }} } else { tierName = parts[0] - policies = []string{tierToPolicyName(tierName)} + policies = []*proto.PolicyID{{ + Name: tierToPolicyName(tierName), + Kind: v3.KindGlobalNetworkPolicy, + }} } tiers = append(tiers, &proto.TierInfo{ Name: tierName, @@ -1064,7 +1107,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { })) It("should report id1 up", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.HostEndpointID{EndpointId: "id1"}: "up", })) }) @@ -1088,7 +1131,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { })) It("should have expected chains", expectChainsFor(ipVersion, flowlogs, "eth0_tierA")) It("should report id1 up", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.HostEndpointID{EndpointId: "id1"}: "up", })) }) @@ -1107,7 +1150,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { })) It("should have expected chains", expectChainsFor(ipVersion, flowlogs, "eth0_tierA")) It("should report id1 up, but id2 now in error", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.HostEndpointID{EndpointId: "id1"}: "up", types.HostEndpointID{EndpointId: "id2"}: "error", })) @@ -1123,7 +1166,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { JustBeforeEach(removeHostEp("id1")) It("should have expected chains", expectChainsFor(ipVersion, flowlogs, "eth0_tierB")) It("should report id2 up only", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.HostEndpointID{EndpointId: "id2"}: "up", })) }) @@ -1153,7 +1196,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { })) It("should have expected chains", expectChainsFor(ipVersion, flowlogs, "eth0_tierB")) It("should report id0 up, but id1 now in error", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.HostEndpointID{EndpointId: "id0"}: "up", types.HostEndpointID{EndpointId: "id1"}: "error", })) @@ -1169,7 +1212,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { JustBeforeEach(removeHostEp("id1")) It("should have expected chains", expectChainsFor(ipVersion, flowlogs, "eth0_tierB")) It("should report id0 up only", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.HostEndpointID{EndpointId: "id0"}: "up", })) }) @@ -1422,7 +1465,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { })) It("should have expected chains", expectChainsFor(ipVersion, flowlogs, "eth0")) It("should report id1 up", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.HostEndpointID{EndpointId: "id1"}: "up", })) }) @@ -1442,7 +1485,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { It("should have expected chains", expectChainsFor(ipVersion, flowlogs, "eth0")) It("should report id1 up", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.HostEndpointID{EndpointId: "id1"}: "up", })) }) @@ -1454,7 +1497,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { })) It("should have expected chains", expectChainsFor(ipVersion, flowlogs, "eth0", "eth1")) It("should report id1 and id22 up", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.HostEndpointID{EndpointId: "id1"}: "up", types.HostEndpointID{EndpointId: "id22"}: "up", })) @@ -1472,7 +1515,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { // because of alphabetical ordering. "id1" is then // unused, and so reported as in error. It("should report id1 error and id0 up", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.HostEndpointID{EndpointId: "id1"}: "error", types.HostEndpointID{EndpointId: "id0"}: "up", })) @@ -1486,7 +1529,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { })) It("should have expected chains", expectChainsFor(ipVersion, flowlogs, "eth0", "eth1")) It("should report id1 and id22 up", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.HostEndpointID{EndpointId: "id1"}: "up", types.HostEndpointID{EndpointId: "id22"}: "up", })) @@ -1502,7 +1545,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { })) It("should have empty dispatch chains", expectEmptyChains(ipVersion)) It("should report endpoint in error", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.HostEndpointID{EndpointId: "id3"}: "error", })) }) @@ -1515,7 +1558,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { })) It("should have expected chains", expectChainsFor(ipVersion, flowlogs, "eth0")) It("should report id4 up", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.HostEndpointID{EndpointId: "id4"}: "up", })) }) @@ -1528,7 +1571,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { })) It("should have expected chains", expectChainsFor(ipVersion, flowlogs, "eth0")) It("should report id5 up", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.HostEndpointID{EndpointId: "id5"}: "up", })) }) @@ -1542,7 +1585,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { })) It("should have expected chains", expectChainsFor(ipVersion, flowlogs, "eth0")) It("should report id3 up", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.HostEndpointID{EndpointId: "id3"}: "up", })) }) @@ -1556,7 +1599,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { })) It("should have expected chains", expectChainsFor(ipVersion, flowlogs, "eth0")) It("should report id3 up", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.HostEndpointID{EndpointId: "id3"}: "up", })) }) @@ -1570,7 +1613,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { })) It("should have empty dispatch chains", expectEmptyChains(ipVersion)) It("should report id3 error", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.HostEndpointID{EndpointId: "id3"}: "error", })) }) @@ -1584,7 +1627,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { })) It("should have empty dispatch chains", expectEmptyChains(ipVersion)) It("should report id3 error", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.HostEndpointID{EndpointId: "id3"}: "error", })) }) @@ -1597,7 +1640,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { })) It("should have empty dispatch chains", expectEmptyChains(ipVersion)) It("should report id4 error", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.HostEndpointID{EndpointId: "id4"}: "error", })) }) @@ -1610,7 +1653,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { })) It("should have empty dispatch chains", expectEmptyChains(ipVersion)) It("should report id5 error", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.HostEndpointID{EndpointId: "id5"}: "error", })) }) @@ -1624,7 +1667,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { })) It("should have empty dispatch chains", expectEmptyChains(ipVersion)) It("should report id3 error", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.HostEndpointID{EndpointId: "id3"}: "error", })) }) @@ -1643,7 +1686,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { }) It("should have expected chains", expectChainsFor(ipVersion, flowlogs, "eth0")) It("should report id3 up", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.HostEndpointID{EndpointId: "id3"}: "up", })) }) @@ -1692,8 +1735,8 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { BeforeEach(func() { tiers = []*proto.TierInfo{{ Name: "default", - IngressPolicies: []string{"policy1"}, - EgressPolicies: []string{"policy1"}, + IngressPolicies: []*proto.PolicyID{{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: []*proto.PolicyID{{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}}, }} }) @@ -1804,7 +1847,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { BeforeEach(func() { tiers = []*proto.TierInfo{{ Name: "default", - IngressPolicies: []string{"policy1"}, + IngressPolicies: []*proto.PolicyID{{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}}, }} }) @@ -1815,7 +1858,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { BeforeEach(func() { tiers = []*proto.TierInfo{{ Name: "default", - EgressPolicies: []string{"policy1"}, + EgressPolicies: []*proto.PolicyID{{Name: "policy1", Kind: v3.KindGlobalNetworkPolicy}}, }} }) @@ -1827,18 +1870,22 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { It("should set routes", func() { if ipVersion == 6 { routeTable.checkRoutes("cali12345-ab", []routetable.Target{{ - CIDR: ip.MustParseCIDROrIP("2001:db8:2::2/128"), + RouteKey: routetable.RouteKey{ + CIDR: ip.MustParseCIDROrIP("2001:db8:2::2/128"), + }, DestMAC: testutils.MustParseMAC("01:02:03:04:05:06"), }}) } else { routeTable.checkRoutes("cali12345-ab", []routetable.Target{{ - CIDR: ip.MustParseCIDROrIP("10.0.240.0/24"), + RouteKey: routetable.RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.240.0/24"), + }, DestMAC: testutils.MustParseMAC("01:02:03:04:05:06"), }}) } }) It("should report endpoint down", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.ProtoToWorkloadEndpointID(&wlEPID1): "down", })) }) @@ -1857,7 +1904,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { applyUpdates(epMgr) }) It("should report the interface as down", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.ProtoToWorkloadEndpointID(&wlEPID1): "down", })) }) @@ -1878,7 +1925,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { It("should have expected chains", expectWlChainsFor(ipVersion, flowlogs, "cali12345-ab")) It("should report endpoint up", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.ProtoToWorkloadEndpointID(&wlEPID1): "up", })) }) @@ -1933,30 +1980,42 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { if ipVersion == 6 { routeTable.checkRoutes("cali12345-ab", []routetable.Target{ { - CIDR: ip.MustParseCIDROrIP("2001:db8:2::2/128"), + RouteKey: routetable.RouteKey{ + CIDR: ip.MustParseCIDROrIP("2001:db8:2::2/128"), + }, DestMAC: testutils.MustParseMAC("01:02:03:04:05:06"), }, { - CIDR: ip.MustParseCIDROrIP("2001:db8:3::2/128"), + RouteKey: routetable.RouteKey{ + CIDR: ip.MustParseCIDROrIP("2001:db8:3::2/128"), + }, DestMAC: testutils.MustParseMAC("01:02:03:04:05:06"), }, { - CIDR: ip.MustParseCIDROrIP("2001:db8:4::2/128"), + RouteKey: routetable.RouteKey{ + CIDR: ip.MustParseCIDROrIP("2001:db8:4::2/128"), + }, DestMAC: testutils.MustParseMAC("01:02:03:04:05:06"), }, }) } else { routeTable.checkRoutes("cali12345-ab", []routetable.Target{ { - CIDR: ip.MustParseCIDROrIP("10.0.240.0/24"), + RouteKey: routetable.RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.240.0/24"), + }, DestMAC: testutils.MustParseMAC("01:02:03:04:05:06"), }, { - CIDR: ip.MustParseCIDROrIP("172.16.1.3/32"), + RouteKey: routetable.RouteKey{ + CIDR: ip.MustParseCIDROrIP("172.16.1.3/32"), + }, DestMAC: testutils.MustParseMAC("01:02:03:04:05:06"), }, { - CIDR: ip.MustParseCIDROrIP("172.18.1.4/32"), + RouteKey: routetable.RouteKey{ + CIDR: ip.MustParseCIDROrIP("172.18.1.4/32"), + }, DestMAC: testutils.MustParseMAC("01:02:03:04:05:06"), }, }) @@ -2002,14 +2061,18 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { if ipVersion == 6 { routeTable.checkRoutes("cali12345-ab", []routetable.Target{ { - CIDR: ip.MustParseCIDROrIP("2001:db8:2::2/128"), + RouteKey: routetable.RouteKey{ + CIDR: ip.MustParseCIDROrIP("2001:db8:2::2/128"), + }, DestMAC: testutils.MustParseMAC("01:02:03:04:05:06"), }, }) } else { routeTable.checkRoutes("cali12345-ab", []routetable.Target{ { - CIDR: ip.MustParseCIDROrIP("10.0.240.0/24"), + RouteKey: routetable.RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.240.0/24"), + }, DestMAC: testutils.MustParseMAC("01:02:03:04:05:06"), }, }) @@ -2066,7 +2129,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { routeTable.checkRoutes("cali12345-ab", nil) }) It("should report endpoint up", func() { - Expect(statusReportRec.currentState).To(Equal(map[interface{}]string{ + Expect(statusReportRec.currentState).To(Equal(map[any]string{ types.ProtoToWorkloadEndpointID(&wlEPID1): "up", })) }) @@ -2074,12 +2137,16 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { It("should have set routes for new iface", func() { if ipVersion == 6 { routeTable.checkRoutes("cali12345-cd", []routetable.Target{{ - CIDR: ip.MustParseCIDROrIP("2001:db8:2::2/128"), + RouteKey: routetable.RouteKey{ + CIDR: ip.MustParseCIDROrIP("2001:db8:2::2/128"), + }, DestMAC: testutils.MustParseMAC("01:02:03:04:05:06"), }}) } else { routeTable.checkRoutes("cali12345-cd", []routetable.Target{{ - CIDR: ip.MustParseCIDROrIP("10.0.240.0/24"), + RouteKey: routetable.RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.240.0/24"), + }, DestMAC: testutils.MustParseMAC("01:02:03:04:05:06"), }}) } @@ -2111,7 +2178,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { Tiers: []*proto.TierInfo{}, Ipv4Nets: []string{"10.0.240.2/24"}, Ipv6Nets: []string{"2001:db8:2::2/128"}, - AllowSpoofedSourcePrefixes: []string{"8.8.8.8/32"}, + AllowSpoofedSourcePrefixes: []string{"8.8.8.8/32", "2001:feed::1/64"}, }, } interfaceUp = &ifaceStateUpdate{ @@ -2130,15 +2197,24 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { mockProcSys.checkStateContains(map[string]string{ "/proc/sys/net/ipv4/conf/cali23456-cd/rp_filter": "0", }) + rawTable.checkChains([][]*generictables.Chain{hostDispatchEmptyNormal, { + &generictables.Chain{Name: rules.ChainRpfSkip, Rules: []generictables.Rule{ + { + Match: iptables.Match().InInterface("cali23456-cd").SourceNet("8.8.8.8/32"), + Action: iptables.AcceptAction{}, + }, + }}, + }}) + } else { + rawTable.checkChains([][]*generictables.Chain{hostDispatchEmptyNormal, { + &generictables.Chain{Name: rules.ChainRpfSkip, Rules: []generictables.Rule{ + { + Match: iptables.Match().InInterface("cali23456-cd").SourceNet("2001:feed::1/64"), + Action: iptables.AcceptAction{}, + }, + }}, + }}) } - rawTable.checkChains([][]*generictables.Chain{hostDispatchEmptyNormal, { - &generictables.Chain{Name: rules.ChainRpfSkip, Rules: []generictables.Rule{ - { - Match: iptables.Match().InInterface("cali23456-cd").SourceNet("8.8.8.8/32"), - Action: iptables.AcceptAction{}, - }, - }}, - }}) By("Re-enabling rpf check on an existing workload") workloadUpdate.Endpoint.AllowSpoofedSourcePrefixes = []string{} @@ -2154,7 +2230,7 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { }}) By("Enabling IP spoofing on an existing workload") - workloadUpdate.Endpoint.AllowSpoofedSourcePrefixes = []string{"8.8.8.8/32"} + workloadUpdate.Endpoint.AllowSpoofedSourcePrefixes = []string{"8.8.8.8/32", "2001:feed::1/64"} epMgr.OnUpdate(workloadUpdate) applyUpdates(epMgr) if ipVersion == 4 { @@ -2162,14 +2238,25 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { "/proc/sys/net/ipv4/conf/cali23456-cd/rp_filter": "0", }) } - rawTable.checkChains([][]*generictables.Chain{hostDispatchEmptyNormal, { - &generictables.Chain{Name: rules.ChainRpfSkip, Rules: []generictables.Rule{ - { - Match: iptables.Match().InInterface("cali23456-cd").SourceNet("8.8.8.8/32"), - Action: iptables.AcceptAction{}, - }, - }}, - }}) + if ipVersion == 4 { + rawTable.checkChains([][]*generictables.Chain{hostDispatchEmptyNormal, { + &generictables.Chain{Name: rules.ChainRpfSkip, Rules: []generictables.Rule{ + { + Match: iptables.Match().InInterface("cali23456-cd").SourceNet("8.8.8.8/32"), + Action: iptables.AcceptAction{}, + }, + }}, + }}) + } else { + rawTable.checkChains([][]*generictables.Chain{hostDispatchEmptyNormal, { + &generictables.Chain{Name: rules.ChainRpfSkip, Rules: []generictables.Rule{ + { + Match: iptables.Match().InInterface("cali23456-cd").SourceNet("2001:feed::1/64"), + Action: iptables.AcceptAction{}, + }, + }}, + }}) + } By("Removing a workload with IP spoofing configured") epMgr.OnUpdate(&proto.WorkloadEndpointRemove{ @@ -2391,164 +2478,176 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { }) Describe("policy grouping tests", func() { + var ( + // Define expected policy IDs for easier reference. + polA1 = "gnp/polA1" + polA2 = "gnp/polA2" + polB1 = "gnp/polB1" + polB2 = "gnp/polB2" + polC1 = "gnp/polC1" + tier2PolA1 = "gnp/tier2.polA1" + tier2PolA2 = "gnp/tier2.polA2" + tier2PolB1 = "gnp/tier2.polB1" + ) + JustBeforeEach(func() { + // Add some policies to the endpoint manager in the default tier. epMgr.OnUpdate(&proto.ActivePolicyUpdate{ - Id: &proto.PolicyID{Tier: "default", Name: "polA1"}, - Policy: &proto.Policy{OriginalSelector: "has(a)"}, + Id: &proto.PolicyID{Name: "polA1", Kind: v3.KindGlobalNetworkPolicy}, + Policy: &proto.Policy{Tier: "default", OriginalSelector: "has(a)"}, }) epMgr.OnUpdate(&proto.ActivePolicyUpdate{ - Id: &proto.PolicyID{Tier: "default", Name: "polA2"}, - Policy: &proto.Policy{OriginalSelector: "has(a)"}, + Id: &proto.PolicyID{Name: "polA2", Kind: v3.KindGlobalNetworkPolicy}, + Policy: &proto.Policy{Tier: "default", OriginalSelector: "has(a)"}, }) epMgr.OnUpdate(&proto.ActivePolicyUpdate{ - Id: &proto.PolicyID{Tier: "default", Name: "polB1"}, - Policy: &proto.Policy{OriginalSelector: "has(b)"}, + Id: &proto.PolicyID{Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + Policy: &proto.Policy{Tier: "default", OriginalSelector: "has(b)"}, }) epMgr.OnUpdate(&proto.ActivePolicyUpdate{ - Id: &proto.PolicyID{Tier: "default", Name: "polB2"}, - Policy: &proto.Policy{OriginalSelector: "has(b)"}, + Id: &proto.PolicyID{Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, + Policy: &proto.Policy{Tier: "default", OriginalSelector: "has(b)"}, }) epMgr.OnUpdate(&proto.ActivePolicyUpdate{ - Id: &proto.PolicyID{Tier: "default", Name: "polC1"}, - Policy: &proto.Policy{OriginalSelector: "has(c)"}, + Id: &proto.PolicyID{Name: "polC1", Kind: v3.KindGlobalNetworkPolicy}, + Policy: &proto.Policy{Tier: "default", OriginalSelector: "has(c)"}, }) + // Also add policies in another tier. Note that names are prefixed with tier name + // so that they don't clash with the default tier policies. epMgr.OnUpdate(&proto.ActivePolicyUpdate{ - Id: &proto.PolicyID{Tier: "tier2", Name: "polA1"}, - Policy: &proto.Policy{OriginalSelector: "has(a)"}, + Id: &proto.PolicyID{Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + Policy: &proto.Policy{Tier: "tier2", OriginalSelector: "has(a)"}, }) epMgr.OnUpdate(&proto.ActivePolicyUpdate{ - Id: &proto.PolicyID{Tier: "tier2", Name: "polA2"}, - Policy: &proto.Policy{OriginalSelector: "has(a)"}, + Id: &proto.PolicyID{Name: "tier2.polA2", Kind: v3.KindGlobalNetworkPolicy}, + Policy: &proto.Policy{Tier: "tier2", OriginalSelector: "has(a)"}, }) epMgr.OnUpdate(&proto.ActivePolicyUpdate{ - Id: &proto.PolicyID{Tier: "tier2", Name: "polB1"}, - Policy: &proto.Policy{OriginalSelector: "has(b)"}, + Id: &proto.PolicyID{Name: "tier2.polB1", Kind: v3.KindGlobalNetworkPolicy}, + Policy: &proto.Policy{Tier: "tier2", OriginalSelector: "has(b)"}, }) epMgr.OnUpdate(&proto.ActivePolicyUpdate{ - Id: &proto.PolicyID{Tier: "tier2", Name: "polB2"}, - Policy: &proto.Policy{OriginalSelector: "has(b)"}, + Id: &proto.PolicyID{Name: "tier2.polB2", Kind: v3.KindGlobalNetworkPolicy}, + Policy: &proto.Policy{Tier: "tier2", OriginalSelector: "has(b)"}, }) }) It("should 'group' a single policy", func() { Expect(epMgr.groupPolicies( - "default", - []string{"polA1"}, + []*proto.PolicyID{{Name: "polA1", Kind: v3.KindGlobalNetworkPolicy}}, rules.PolicyDirectionInbound, )).To(Equal([]*rules.PolicyGroup{ { - Tier: "default", - Direction: rules.PolicyDirectionInbound, - PolicyNames: []string{"polA1"}, - Selector: "has(a)", + Direction: rules.PolicyDirectionInbound, + Policies: []*types.PolicyID{{Name: "polA1", Kind: v3.KindGlobalNetworkPolicy}}, + Selector: "has(a)", }, })) }) It("should 'group' a pair of policies same selector", func() { Expect(epMgr.groupPolicies( - "default", - []string{"polA1", "polA2"}, + []*proto.PolicyID{{Name: "polA1", Kind: v3.KindGlobalNetworkPolicy}, {Name: "polA2", Kind: v3.KindGlobalNetworkPolicy}}, rules.PolicyDirectionInbound, )).To(Equal([]*rules.PolicyGroup{ { - Tier: "default", - Direction: rules.PolicyDirectionInbound, - PolicyNames: []string{"polA1", "polA2"}, - Selector: "has(a)", + Direction: rules.PolicyDirectionInbound, + Policies: []*types.PolicyID{{Name: "polA1", Kind: v3.KindGlobalNetworkPolicy}, {Name: "polA2", Kind: v3.KindGlobalNetworkPolicy}}, + Selector: "has(a)", }, })) }) It("should 'group' a pair of policies different selector", func() { Expect(epMgr.groupPolicies( - "default", - []string{"polA1", "polB1"}, + []*proto.PolicyID{{Name: "polA1", Kind: v3.KindGlobalNetworkPolicy}, {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}}, rules.PolicyDirectionInbound, )).To(Equal([]*rules.PolicyGroup{ { - Tier: "default", - Direction: rules.PolicyDirectionInbound, - PolicyNames: []string{"polA1"}, - Selector: "has(a)", + Direction: rules.PolicyDirectionInbound, + Policies: []*types.PolicyID{{Name: "polA1", Kind: v3.KindGlobalNetworkPolicy}}, + Selector: "has(a)", }, { - Tier: "default", - Direction: rules.PolicyDirectionInbound, - PolicyNames: []string{"polB1"}, - Selector: "has(b)", + Direction: rules.PolicyDirectionInbound, + Policies: []*types.PolicyID{{Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}}, + Selector: "has(b)", }, })) }) It("should 'group' two pairs", func() { Expect(epMgr.groupPolicies( - "default", - []string{"polA1", "polA2", "polB1", "polB2"}, + []*proto.PolicyID{ + {Name: "polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polA2", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, + }, rules.PolicyDirectionInbound, )).To(Equal([]*rules.PolicyGroup{ { - Tier: "default", - Direction: rules.PolicyDirectionInbound, - PolicyNames: []string{"polA1", "polA2"}, - Selector: "has(a)", + Direction: rules.PolicyDirectionInbound, + Policies: []*types.PolicyID{{Name: "polA1", Kind: v3.KindGlobalNetworkPolicy}, {Name: "polA2", Kind: v3.KindGlobalNetworkPolicy}}, + Selector: "has(a)", }, { - Tier: "default", - Direction: rules.PolicyDirectionInbound, - PolicyNames: []string{"polB1", "polB2"}, - Selector: "has(b)", + Direction: rules.PolicyDirectionInbound, + Policies: []*types.PolicyID{{Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}}, + Selector: "has(b)", }, })) }) It("should 'group' mixed", func() { Expect(epMgr.groupPolicies( - "default", - []string{"polA1", "polB1", "polB2", "polA2"}, + []*proto.PolicyID{ + {Name: "polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polA2", Kind: v3.KindGlobalNetworkPolicy}, + }, rules.PolicyDirectionInbound, )).To(Equal([]*rules.PolicyGroup{ { - Tier: "default", - Direction: rules.PolicyDirectionInbound, - PolicyNames: []string{"polA1"}, - Selector: "has(a)", + Direction: rules.PolicyDirectionInbound, + Policies: []*types.PolicyID{{Name: "polA1", Kind: v3.KindGlobalNetworkPolicy}}, + Selector: "has(a)", }, { - Tier: "default", - Direction: rules.PolicyDirectionInbound, - PolicyNames: []string{"polB1", "polB2"}, - Selector: "has(b)", + Direction: rules.PolicyDirectionInbound, + Policies: []*types.PolicyID{{Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}}, + Selector: "has(b)", }, { - Tier: "default", - Direction: rules.PolicyDirectionInbound, - PolicyNames: []string{"polA2"}, - Selector: "has(a)", + Direction: rules.PolicyDirectionInbound, + Policies: []*types.PolicyID{{Name: "polA2", Kind: v3.KindGlobalNetworkPolicy}}, + Selector: "has(a)", }, })) }) It("should 'group' non-default tier", func() { Expect(epMgr.groupPolicies( - "tier2", - []string{"polA1", "polB1", "polB2", "polA2"}, + []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polB2", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polA2", Kind: v3.KindGlobalNetworkPolicy}, + }, rules.PolicyDirectionInbound, )).To(Equal([]*rules.PolicyGroup{ { - Tier: "tier2", - Direction: rules.PolicyDirectionInbound, - PolicyNames: []string{"polA1"}, - Selector: "has(a)", + Direction: rules.PolicyDirectionInbound, + Policies: []*types.PolicyID{{Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}}, + Selector: "has(a)", }, { - Tier: "tier2", - Direction: rules.PolicyDirectionInbound, - PolicyNames: []string{"polB1", "polB2"}, - Selector: "has(b)", + Direction: rules.PolicyDirectionInbound, + Policies: []*types.PolicyID{{Name: "tier2.polB1", Kind: v3.KindGlobalNetworkPolicy}, {Name: "tier2.polB2", Kind: v3.KindGlobalNetworkPolicy}}, + Selector: "has(b)", }, { - Tier: "tier2", - Direction: rules.PolicyDirectionInbound, - PolicyNames: []string{"polA2"}, - Selector: "has(a)", + Direction: rules.PolicyDirectionInbound, + Policies: []*types.PolicyID{{Name: "tier2.polA2", Kind: v3.KindGlobalNetworkPolicy}}, + Selector: "has(a)", }, })) }) @@ -2581,16 +2680,26 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { It("should get the expected policy group chains (ingress)", func() { ingressNamesEP1, groupsEP1 := extractGroups(table.currentChains, ep1IngressChain) Expect(groupsEP1).To(Equal([][]string{ - {"polA1", "polA2"}, - {"polB1", "polB2"}, - {"tier2/polA1", "tier2/polA2"}, + {polA1, polA2}, + {polB1, polB2}, + {tier2PolA1, tier2PolA2}, })) + namesEP2, groupsEP2 := extractGroups(table.currentChains, ep2IngressChain) Expect(groupsEP2).To(Equal([][]string{ - {"polB1", "polB2"}, - {"polC1"}, - {"tier2/polA1", "tier2/polA2"}, + { + polB1, + polB2, + }, + { + polC1, + }, + { + tier2PolA1, + tier2PolA2, + }, })) + Expect(ingressNamesEP1[1]).NotTo(Equal(""), "Policy B group shouldn't be inlined") Expect(ingressNamesEP1[1]).To(Equal(namesEP2[0]), "EPs should share the policy B group") Expect(namesEP2[1]).To(Equal(""), "Group C should be inlined") @@ -2600,36 +2709,73 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { // Start as with the above test... ingressNamesEP1, groupsEP1 := extractGroups(table.currentChains, ep1IngressChain) Expect(groupsEP1).To(Equal([][]string{ - {"polA1", "polA2"}, - {"polB1", "polB2"}, - {"tier2/polA1", "tier2/polA2"}, + { + polA1, + polA2, + }, + { + polB1, + polB2, + }, + { + tier2PolA1, + tier2PolA2, + }, })) _, groupsEP2 := extractGroups(table.currentChains, ep2IngressChain) Expect(groupsEP2).To(Equal([][]string{ - {"polB1", "polB2"}, - {"polC1"}, - {"tier2/polA1", "tier2/polA2"}, + { + polB1, + polB2, + }, + { + polC1, + }, + { + tier2PolA1, + tier2PolA2, + }, })) // Then move polA2 to the B group... epMgr.OnUpdate(&proto.ActivePolicyUpdate{ - Id: &proto.PolicyID{Tier: "default", Name: "polA2"}, - Policy: &proto.Policy{OriginalSelector: "has(b)"}, // :-O + Id: &proto.PolicyID{Name: "polA2", Kind: v3.KindGlobalNetworkPolicy}, + Policy: &proto.Policy{Tier: "default", OriginalSelector: "has(b)"}, // :-O }) applyUpdates(epMgr) _, groupsEP1Post := extractGroups(table.currentChains, ep1IngressChain) Expect(groupsEP1Post).To(Equal([][]string{ - {"polA1"}, - {"polA2", "polB1", "polB2"}, - {"tier2/polA1", "tier2/polA2"}, + { + polA1, + }, + { + polA2, + polB1, + polB2, + }, + { + tier2PolA1, + tier2PolA2, + }, })) + _, groupsEP2Post := extractGroups(table.currentChains, ep2IngressChain) Expect(groupsEP2Post).To(Equal([][]string{ - {"polB1", "polB2"}, - {"polC1"}, - {"tier2/polA1", "tier2/polA2"}, + { + polB1, + polB2, + }, + { + polC1, + }, + // {"tier2.polA1", "tier2.polA2"}, + { + tier2PolA1, + tier2PolA2, + }, })) + Expect(table.currentChains).NotTo(HaveKey(ingressNamesEP1[0]), "Old polA group should be cleaned up") }) @@ -2664,29 +2810,32 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { applyUpdates(epMgr) _, groupsEP1 := extractGroups(table.currentChains, ep1IngressChain) Expect(groupsEP1).To(Equal([][]string{ - {"polB1", "polB2"}, + // {"polB1", "polB2"}, + { + polB1, + polB2, + }, })) - Expect(table.currentChains).NotTo(HaveKey(polAGroup), - "Policy A group should be cleaned up") - Expect(table.currentChains).To(HaveKey(polBGroup), - "Policy B group chain should still be present, it is shared with the second endpoint") + Expect(table.currentChains).NotTo(HaveKey(polAGroup), "Policy A group should be cleaned up") + Expect(table.currentChains).To(HaveKey(polBGroup), "Policy B group chain should still be present, it is shared with the second endpoint") }) } + defineEgressPolicyGroupingTests := func() { It("should get the expected policy group chains (egress)", func() { namesEP1, groupsEP1 := extractGroups(table.currentChains, ep1EgressChain) Expect(groupsEP1).To(Equal([][]string{ - {"polA1"}, - {"polB1", "polB2"}, - {"tier2/polA1"}, - {"tier2/polB1"}, + {polA1}, + {polB1, polB2}, + {tier2PolA1}, + {tier2PolB1}, })) namesEP2In, _ := extractGroups(table.currentChains, ep2IngressChain) namesEP2, groupsEP2 := extractGroups(table.currentChains, ep2EgressChain) Expect(groupsEP2).To(Equal([][]string{ - {"polB1", "polB2"}, - {"tier2/polA1"}, - {"tier2/polB1"}, + {polB1, polB2}, + {tier2PolA1}, + {tier2PolB1}, })) Expect(namesEP1[0]).To(Equal(""), "Group A should be inlined") Expect(namesEP1[1]).NotTo(Equal(""), "Policy B group shouldn't be inlined") @@ -2713,27 +2862,27 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { Tiers: []*proto.TierInfo{ { Name: "default", - IngressPolicies: []string{ - "polA1", - "polA2", - "polB1", - "polB2", + IngressPolicies: []*proto.PolicyID{ + {Name: "polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polA2", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polA1", - "polB1", - "polB2", + EgressPolicies: []*proto.PolicyID{ + {Name: "polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, }, { Name: "tier2", - IngressPolicies: []string{ - "polA1", - "polA2", + IngressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polA2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polA1", - "polB1", + EgressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polB1", Kind: v3.KindGlobalNetworkPolicy}, }, }, }, @@ -2751,25 +2900,25 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { Tiers: []*proto.TierInfo{ { Name: "default", - IngressPolicies: []string{ - "polB1", - "polB2", - "polC1", + IngressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polC1", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polB1", - "polB2", + EgressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, }, { Name: "tier2", - IngressPolicies: []string{ - "polA1", - "polA2", + IngressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polA2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polA1", - "polB1", + EgressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polB1", Kind: v3.KindGlobalNetworkPolicy}, }, }, }, @@ -2801,19 +2950,19 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { Tiers: []*proto.TierInfo{ { Name: "default", - IngressPolicies: []string{ - "polB1", - "polB2", + IngressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polB1", - "polB2", + EgressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, }, { Name: "tier2", - EgressPolicies: []string{ - "polB1", + EgressPolicies: []*proto.PolicyID{ + {Name: "tier2.polB1", Kind: v3.KindGlobalNetworkPolicy}, }, }, }, @@ -2855,27 +3004,27 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { Tiers: []*proto.TierInfo{ { Name: "default", - IngressPolicies: []string{ - "polA1", - "polA2", - "polB1", - "polB2", + IngressPolicies: []*proto.PolicyID{ + {Name: "polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polA2", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polA1", - "polB1", - "polB2", + EgressPolicies: []*proto.PolicyID{ + {Name: "polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, }, { Name: "tier2", - IngressPolicies: []string{ - "polA1", - "polA2", + IngressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polA2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polA1", - "polB1", + EgressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polB1", Kind: v3.KindGlobalNetworkPolicy}, }, }, }, @@ -2893,25 +3042,25 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { Tiers: []*proto.TierInfo{ { Name: "default", - IngressPolicies: []string{ - "polB1", - "polB2", - "polC1", + IngressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polC1", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polB1", - "polB2", + EgressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, }, { Name: "tier2", - IngressPolicies: []string{ - "polA1", - "polA2", + IngressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polA2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polA1", - "polB1", + EgressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polB1", Kind: v3.KindGlobalNetworkPolicy}, }, }, }, @@ -2943,19 +3092,19 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { Tiers: []*proto.TierInfo{ { Name: "default", - IngressPolicies: []string{ - "polB1", - "polB2", + IngressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polB1", - "polB2", + EgressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, }, { Name: "tier2", - EgressPolicies: []string{ - "polB1", + EgressPolicies: []*proto.PolicyID{ + {Name: "tier2.polB1", Kind: v3.KindGlobalNetworkPolicy}, }, }, }, @@ -2997,27 +3146,27 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { Tiers: []*proto.TierInfo{ { Name: "default", - IngressPolicies: []string{ - "polA1", - "polA2", - "polB1", - "polB2", + IngressPolicies: []*proto.PolicyID{ + {Name: "polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polA2", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polA1", - "polB1", - "polB2", + EgressPolicies: []*proto.PolicyID{ + {Name: "polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, }, { Name: "tier2", - IngressPolicies: []string{ - "polA1", - "polA2", + IngressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polA2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polA1", - "polB1", + EgressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polB1", Kind: v3.KindGlobalNetworkPolicy}, }, }, }, @@ -3033,25 +3182,25 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { Tiers: []*proto.TierInfo{ { Name: "default", - IngressPolicies: []string{ - "polB1", - "polB2", - "polC1", + IngressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polC1", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polB1", - "polB2", + EgressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, }, { Name: "tier2", - IngressPolicies: []string{ - "polA1", - "polA2", + IngressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polA2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polA1", - "polB1", + EgressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polB1", Kind: v3.KindGlobalNetworkPolicy}, }, }, }, @@ -3085,19 +3234,19 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { Tiers: []*proto.TierInfo{ { Name: "default", - IngressPolicies: []string{ - "polB1", - "polB2", + IngressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polB1", - "polB2", + EgressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, }, { Name: "tier2", - EgressPolicies: []string{ - "polB1", + EgressPolicies: []*proto.PolicyID{ + {Name: "tier2.polB1", Kind: v3.KindGlobalNetworkPolicy}, }, }, }, @@ -3145,27 +3294,27 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { Tiers: []*proto.TierInfo{ { Name: "default", - IngressPolicies: []string{ - "polA1", - "polA2", - "polB1", - "polB2", + IngressPolicies: []*proto.PolicyID{ + {Name: "polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polA2", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polA1", - "polB1", - "polB2", + EgressPolicies: []*proto.PolicyID{ + {Name: "polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, }, { Name: "tier2", - IngressPolicies: []string{ - "polA1", - "polA2", + IngressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polA2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polA1", - "polB1", + EgressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polB1", Kind: v3.KindGlobalNetworkPolicy}, }, }, }, @@ -3181,25 +3330,25 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { Tiers: []*proto.TierInfo{ { Name: "default", - IngressPolicies: []string{ - "polB1", - "polB2", - "polC1", + IngressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polC1", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polB1", - "polB2", + EgressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, }, { Name: "tier2", - IngressPolicies: []string{ - "polA1", - "polA2", + IngressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polA2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polA1", - "polB1", + EgressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polB1", Kind: v3.KindGlobalNetworkPolicy}, }, }, }, @@ -3233,19 +3382,19 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { Tiers: []*proto.TierInfo{ { Name: "default", - IngressPolicies: []string{ - "polB1", - "polB2", + IngressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polB1", - "polB2", + EgressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, }, { Name: "tier2", - EgressPolicies: []string{ - "polB1", + EgressPolicies: []*proto.PolicyID{ + {Name: "tier2.polB1", Kind: v3.KindGlobalNetworkPolicy}, }, }, }, @@ -3293,27 +3442,27 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { PreDnatTiers: []*proto.TierInfo{ { Name: "default", - IngressPolicies: []string{ - "polA1", - "polA2", - "polB1", - "polB2", + IngressPolicies: []*proto.PolicyID{ + {Name: "polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polA2", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polA1", - "polB1", - "polB2", + EgressPolicies: []*proto.PolicyID{ + {Name: "polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, }, { Name: "tier2", - IngressPolicies: []string{ - "polA1", - "polA2", + IngressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polA2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polA1", - "polB1", + EgressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polB1", Kind: v3.KindGlobalNetworkPolicy}, }, }, }, @@ -3329,25 +3478,25 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { PreDnatTiers: []*proto.TierInfo{ { Name: "default", - IngressPolicies: []string{ - "polB1", - "polB2", - "polC1", + IngressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polC1", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polB1", - "polB2", + EgressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, }, { Name: "tier2", - IngressPolicies: []string{ - "polA1", - "polA2", + IngressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polA2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polA1", - "polB1", + EgressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polB1", Kind: v3.KindGlobalNetworkPolicy}, }, }, }, @@ -3381,19 +3530,19 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { PreDnatTiers: []*proto.TierInfo{ { Name: "default", - IngressPolicies: []string{ - "polB1", - "polB2", + IngressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polB1", - "polB2", + EgressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, }, { Name: "tier2", - EgressPolicies: []string{ - "polB1", + EgressPolicies: []*proto.PolicyID{ + {Name: "tier2.polB1", Kind: v3.KindGlobalNetworkPolicy}, }, }, }, @@ -3441,27 +3590,27 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { ForwardTiers: []*proto.TierInfo{ { Name: "default", - IngressPolicies: []string{ - "polA1", - "polA2", - "polB1", - "polB2", + IngressPolicies: []*proto.PolicyID{ + {Name: "polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polA2", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polA1", - "polB1", - "polB2", + EgressPolicies: []*proto.PolicyID{ + {Name: "polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, }, { Name: "tier2", - IngressPolicies: []string{ - "polA1", - "polA2", + IngressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polA2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polA1", - "polB1", + EgressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polB1", Kind: v3.KindGlobalNetworkPolicy}, }, }, }, @@ -3477,25 +3626,25 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { ForwardTiers: []*proto.TierInfo{ { Name: "default", - IngressPolicies: []string{ - "polB1", - "polB2", - "polC1", + IngressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polC1", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polB1", - "polB2", + EgressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, }, { Name: "tier2", - IngressPolicies: []string{ - "polA1", - "polA2", + IngressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polA2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polA1", - "polB1", + EgressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polB1", Kind: v3.KindGlobalNetworkPolicy}, }, }, }, @@ -3529,19 +3678,19 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { ForwardTiers: []*proto.TierInfo{ { Name: "default", - IngressPolicies: []string{ - "polB1", - "polB2", + IngressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polB1", - "polB2", + EgressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, }, { Name: "tier2", - EgressPolicies: []string{ - "polB1", + EgressPolicies: []*proto.PolicyID{ + {Name: "tier2.polB1", Kind: v3.KindGlobalNetworkPolicy}, }, }, }, @@ -3589,27 +3738,27 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { UntrackedTiers: []*proto.TierInfo{ { Name: "default", - IngressPolicies: []string{ - "polA1", - "polA2", - "polB1", - "polB2", + IngressPolicies: []*proto.PolicyID{ + {Name: "polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polA2", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polA1", - "polB1", - "polB2", + EgressPolicies: []*proto.PolicyID{ + {Name: "polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, }, { Name: "tier2", - IngressPolicies: []string{ - "polA1", - "polA2", + IngressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polA2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polA1", - "polB1", + EgressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polB1", Kind: v3.KindGlobalNetworkPolicy}, }, }, }, @@ -3625,25 +3774,25 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { UntrackedTiers: []*proto.TierInfo{ { Name: "default", - IngressPolicies: []string{ - "polB1", - "polB2", - "polC1", + IngressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polC1", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polB1", - "polB2", + EgressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, }, { Name: "tier2", - IngressPolicies: []string{ - "polA1", - "polA2", + IngressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polA2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polA1", - "polB1", + EgressPolicies: []*proto.PolicyID{ + {Name: "tier2.polA1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "tier2.polB1", Kind: v3.KindGlobalNetworkPolicy}, }, }, }, @@ -3677,19 +3826,19 @@ func endpointManagerTests(ipVersion uint8, flowlogs bool) func() { UntrackedTiers: []*proto.TierInfo{ { Name: "default", - IngressPolicies: []string{ - "polB1", - "polB2", + IngressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, - EgressPolicies: []string{ - "polB1", - "polB2", + EgressPolicies: []*proto.PolicyID{ + {Name: "polB1", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "polB2", Kind: v3.KindGlobalNetworkPolicy}, }, }, { Name: "tier2", - EgressPolicies: []string{ - "polB1", + EgressPolicies: []*proto.PolicyID{ + {Name: "tier2.polB1", Kind: v3.KindGlobalNetworkPolicy}, }, }, }, @@ -3736,21 +3885,17 @@ func extractGroups(dpChains map[string]*generictables.Chain, epChainName string) strings.HasPrefix(ja.Target, string(rules.PolicyOutboundPfx)) { // Found jump to policy. groupChainNames = append(groupChainNames, "") - groups = append(groups, []string{removeDefaultTierPrefix(removePolChainNamePrefix(ja.Target))}) + groups = append(groups, []string{removePolChainNamePrefix(ja.Target)}) } } } return } -func removeDefaultTierPrefix(name string) string { - return strings.TrimPrefix(name, "default/") -} - func extractPolicyNamesFromJumps(chain *generictables.Chain) (pols []string) { for _, r := range chain.Rules { if ja, ok := r.Action.(iptables.JumpAction); ok { - pols = append(pols, removeDefaultTierPrefix(removePolChainNamePrefix(ja.Target))) + pols = append(pols, removePolChainNamePrefix(ja.Target)) } } return @@ -3830,15 +3975,25 @@ type testHEPListener struct { func (t *testHEPListener) OnHEPUpdate(hostIfaceToEpMap map[string]*proto.HostEndpoint) { log.Infof("OnHEPUpdate: %v", hostIfaceToEpMap) t.state = map[string]string{} + + stringifyPolicies := func(policies []*proto.PolicyID) string { + var policyStrings []string + for _, pol := range policies { + policyStrings = append(policyStrings, pol.Name) + } + return strings.Join(policyStrings, ",") + } + stringify := func(tiers []*proto.TierInfo) string { var tierStrings []string for _, tier := range tiers { tierStrings = append(tierStrings, - "I="+strings.Join(tier.IngressPolicies, ",")+ - ",E="+strings.Join(tier.EgressPolicies, ",")) + "I="+stringifyPolicies(tier.IngressPolicies)+ + ",E="+stringifyPolicies(tier.EgressPolicies)) } return strings.Join(tierStrings, "/") } + for ifaceName, hep := range hostIfaceToEpMap { t.state[ifaceName] = "profiles=" + strings.Join(hep.ProfileIds, ",") + ",normal=" + stringify(hep.Tiers) + diff --git a/felix/dataplane/linux/floating_ip_mgr.go b/felix/dataplane/linux/floating_ip_mgr.go index 41532125162..c48a67f0195 100644 --- a/felix/dataplane/linux/floating_ip_mgr.go +++ b/felix/dataplane/linux/floating_ip_mgr.go @@ -100,7 +100,7 @@ func newFloatingIPManager( } } -func (m *floatingIPManager) OnUpdate(protoBufMsg interface{}) { +func (m *floatingIPManager) OnUpdate(protoBufMsg any) { switch msg := protoBufMsg.(type) { case *proto.WorkloadEndpointUpdate: // We only program NAT mappings if the FloatingIPs feature is globally enabled, or diff --git a/felix/dataplane/linux/floating_ip_mgr_test.go b/felix/dataplane/linux/floating_ip_mgr_test.go index 3372b448417..03320d9be9b 100644 --- a/felix/dataplane/linux/floating_ip_mgr_test.go +++ b/felix/dataplane/linux/floating_ip_mgr_test.go @@ -15,7 +15,7 @@ package intdataplane import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/generictables" @@ -87,7 +87,7 @@ func floatingIPManagerTests(ipVersion uint8) func() { }) JustBeforeEach(func() { - renderer := rules.NewRenderer(rrConfigNormal) + renderer := rules.NewRenderer(rrConfigNormal, false) natTable = newMockTable("nat") fipMgr = newFloatingIPManager(natTable, renderer, ipVersion, true) }) diff --git a/felix/dataplane/linux/hostip_mgr.go b/felix/dataplane/linux/hostip_mgr.go index 3a308848b7f..ff768218623 100644 --- a/felix/dataplane/linux/hostip_mgr.go +++ b/felix/dataplane/linux/hostip_mgr.go @@ -69,16 +69,15 @@ func newHostIPManagerWithShims(wlIfacesPrefixes []string, func (m *hostIPManager) getCurrentMembers() []string { members := []string{} for _, addrs := range m.hostIfaceToAddrs { - addrs.Iter(func(ip string) error { + for ip := range addrs.All() { members = append(members, ip) - return nil - }) + } } return members } -func (m *hostIPManager) OnUpdate(msg interface{}) { +func (m *hostIPManager) OnUpdate(msg any) { switch msg := msg.(type) { case *ifaceAddrsUpdate: log.WithField("update", msg).Info("Interface addrs changed.") diff --git a/felix/dataplane/linux/hostip_mgr_test.go b/felix/dataplane/linux/hostip_mgr_test.go index 1bb05400b46..4928afd1cd6 100644 --- a/felix/dataplane/linux/hostip_mgr_test.go +++ b/felix/dataplane/linux/hostip_mgr_test.go @@ -15,7 +15,7 @@ package intdataplane import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" dpsets "github.com/projectcalico/calico/felix/dataplane/ipsets" diff --git a/felix/dataplane/linux/int_dataplane.go b/felix/dataplane/linux/int_dataplane.go index 0e64989716a..ef5edc80d1c 100644 --- a/felix/dataplane/linux/int_dataplane.go +++ b/felix/dataplane/linux/int_dataplane.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2020-2026 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -36,8 +36,6 @@ import ( "golang.org/x/sys/unix" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" "k8s.io/client-go/kubernetes" - k8shealthcheck "k8s.io/kubernetes/pkg/proxy/healthcheck" - "sigs.k8s.io/knftables" "github.com/projectcalico/calico/felix/bpf" "github.com/projectcalico/calico/felix/bpf/bpfmap" @@ -178,7 +176,6 @@ type Config struct { TableRefreshInterval time.Duration IptablesPostWriteCheckInterval time.Duration IptablesInsertMode string - IptablesLockFilePath string IptablesLockTimeout time.Duration IptablesLockProbeInterval time.Duration XDPRefreshInterval time.Duration @@ -198,11 +195,11 @@ type Config struct { ConfigChangedRestartCallback func() FatalErrorRestartCallback func(error) - PostInSyncCallback func() - HealthAggregator *health.HealthAggregator - WatchdogTimeout time.Duration - RouteTableManager *idalloc.IndexAllocator - bpfProxyHealthzServer *k8shealthcheck.ProxyHealthServer + PostInSyncCallback func() + HealthAggregator *health.HealthAggregator + WatchdogTimeout time.Duration + RouteTableManager *idalloc.IndexAllocator + bpfProxyHealthCheck bpfproxy.Healthcheck DebugSimulateDataplaneHangAfter time.Duration DebugSimulateDataplaneApplyDelay time.Duration @@ -242,6 +239,8 @@ type Config struct { BPFMapSizeNATAffinity int BPFMapSizeIPSets int BPFMapSizeIfState int + BPFMapSizeMaglev int + BPFMaglevLUTSize int BPFIpv6Enabled bool BPFHostConntrackBypass bool BPFEnforceRPF string @@ -281,7 +280,7 @@ type Config struct { KubernetesProvider felixconfig.Provider // For testing purposes - allows unit tests to mock out the creation of the nftables dataplane. - NewNftablesDataplane func(knftables.Family, string) (knftables.Interface, error) + NewNftablesDataplane nftables.NewNftablesDataplaneFn } type UpdateBatchResolver interface { @@ -321,8 +320,8 @@ type UpdateBatchResolver interface { // depend on them. For example, it is important that the datastore layer sends an IP set // create event before it sends a rule that references that IP set. type InternalDataplane struct { - toDataplane chan interface{} - fromDataplane chan interface{} + toDataplane chan any + fromDataplane chan any sendDataplaneInSyncOnce sync.Once mainRouteTables []routetable.SyncerInterface @@ -411,6 +410,16 @@ type InternalDataplane struct { actions generictables.ActionFactory newMatch func() generictables.MatchCriteria + + // nftablesEnabled tracks whether we are using nftables on this node. + nftablesEnabled bool + + // kubeProxyNftablesEnabled tracks whether kube-proxy is running in nftables mode on this node. + kubeProxyNftablesEnabled bool + + // getKubeProxyNftablesEnabled is a function that can be called to re-check whether kube-proxy + // is running in nftables mode. + getKubeProxyNftablesEnabled func() (bool, error) } const ( @@ -431,9 +440,18 @@ func NewIntDataplaneDriver(config Config) *InternalDataplane { } log.WithField("config", config).Info("Creating internal dataplane driver.") + + // Decide whether to use nftables or iptables based on configuration and kube-proxy mode. + detectKubeProxyNftablesMode := nftables.KubeProxyNftablesEnabledFn(config.NewNftablesDataplane) + kubeProxyNftablesEnabled, err := detectKubeProxyNftablesMode() + if err != nil { + log.WithError(err).Panic("Unable to detect kube-proxy nftables mode, shutting down") + } + nftablesEnabled := useNftables(config.RulesConfig.NFTablesMode, kubeProxyNftablesEnabled) + ruleRenderer := config.RuleRendererOverride if ruleRenderer == nil { - ruleRenderer = rules.NewRenderer(config.RulesConfig) + ruleRenderer = rules.NewRenderer(config.RulesConfig, nftablesEnabled) } epMarkMapper := rules.NewEndpointMarkMapper( config.RulesConfig.MarkEndpoint, @@ -442,7 +460,7 @@ func NewIntDataplaneDriver(config Config) *InternalDataplane { // Auto-detect host MTU. hostMTU, err := findHostMTU(config.MTUIfacePattern) if err != nil { - log.WithError(err).Fatal("Unable to detect host MTU, shutting down") + log.WithError(err).Panic("Unable to detect host MTU, shutting down") return nil } ConfigureDefaultMTUs(hostMTU, &config) @@ -464,22 +482,25 @@ func NewIntDataplaneDriver(config Config) *InternalDataplane { // Determine the action set and new match function based on the underlying generictables implementation. actionSet := iptables.Actions() newMatchFn := iptables.Match - if config.RulesConfig.NFTables { + if nftablesEnabled { actionSet = nftables.Actions() newMatchFn = nftables.Match } dp := &InternalDataplane{ - toDataplane: make(chan interface{}, msgPeekLimit), - fromDataplane: make(chan interface{}, 100), - ruleRenderer: ruleRenderer, - ifaceMonitor: ifacemonitor.New(config.IfaceMonitorConfig, featureDetector, config.FatalErrorRestartCallback), - ifaceUpdates: make(chan any, 100), - config: config, - applyThrottle: throttle.New(10), - loopSummarizer: logutils.NewSummarizer("dataplane reconciliation loops"), - actions: actionSet, - newMatch: newMatchFn, + toDataplane: make(chan any, msgPeekLimit), + fromDataplane: make(chan any, 100), + ruleRenderer: ruleRenderer, + ifaceMonitor: ifacemonitor.New(config.IfaceMonitorConfig, featureDetector, config.FatalErrorRestartCallback), + ifaceUpdates: make(chan any, 100), + config: config, + applyThrottle: throttle.New(10), + loopSummarizer: logutils.NewSummarizer("dataplane reconciliation loops"), + actions: actionSet, + newMatch: newMatchFn, + nftablesEnabled: nftablesEnabled, + kubeProxyNftablesEnabled: kubeProxyNftablesEnabled, + getKubeProxyNftablesEnabled: detectKubeProxyNftablesMode, } dp.applyThrottle.Refill() // Allow the first apply() immediately. dp.ifaceMonitor.StateCallback = dp.onIfaceStateChange @@ -494,7 +515,6 @@ func NewIntDataplaneDriver(config Config) *InternalDataplane { InsertMode: config.IptablesInsertMode, RefreshInterval: config.TableRefreshInterval, PostWriteInterval: config.IptablesPostWriteCheckInterval, - LockTimeout: config.IptablesLockTimeout, LockProbeInterval: config.IptablesLockProbeInterval, BackendMode: backendMode, LookPathOverride: config.LookPathOverride, @@ -506,15 +526,25 @@ func NewIntDataplaneDriver(config Config) *InternalDataplane { LookPathOverride: config.LookPathOverride, OnStillAlive: dp.reportHealth, OpRecorder: dp.loopSummarizer, - Disabled: !config.RulesConfig.NFTables, + Disabled: !nftablesEnabled, NewDataplane: config.NewNftablesDataplane, } + var cleanupTables []generictables.Table if config.BPFEnabled && config.BPFKubeProxyIptablesCleanupEnabled { // If BPF-mode is enabled, clean up kube-proxy's rules too. log.Info("BPF enabled, configuring iptables/nftables layer to clean up kube-proxy's rules.") iptablesOptions.ExtraCleanupRegexPattern = rules.KubeProxyInsertRuleRegex iptablesOptions.HistoricChainPrefixes = append(iptablesOptions.HistoricChainPrefixes, rules.KubeProxyChainPrefixes...) + // Delete the ip kube-proxy and ip6 kube-proxy tables in nftables. + nftablesKPOptions := nftablesOptions + nftablesKPOptions.Disabled = true + kubeProxyTableV4NFT := nftables.NewTable("kube-proxy", 4, rules.RuleHashPrefix, featureDetector, nftablesKPOptions, nftablesEnabled) + cleanupTables = append(cleanupTables, kubeProxyTableV4NFT) + if config.IPv6Enabled { + kubeProxyTableV6NFT := nftables.NewTable("kube-proxy", 6, rules.RuleHashPrefix, featureDetector, nftablesKPOptions, nftablesEnabled) + cleanupTables = append(cleanupTables, kubeProxyTableV6NFT) + } } if config.BPFEnabled && !config.BPFPolicyDebugEnabled { @@ -532,40 +562,14 @@ func NewIntDataplaneDriver(config Config) *InternalDataplane { iptablesNATOptions.ExtraCleanupRegexPattern += "|" + rules.HistoricInsertedNATRuleRegex } - dataplaneFeatures := featureDetector.GetFeatures() - var iptablesLock sync.Locker - if config.RulesConfig.NFTables { - iptablesLock = dummyLock{} - } else { - if dataplaneFeatures.RestoreSupportsLock { - log.Debug("Calico implementation of iptables lock disabled (because detected version of " + - "iptables-restore will use its own implementation).") - iptablesLock = dummyLock{} - } else if config.IptablesLockTimeout <= 0 { - log.Debug("Calico implementation of iptables lock disabled (by configuration).") - iptablesLock = dummyLock{} - } else { - // Create the shared iptables lock. This allows us to block other processes from - // manipulating iptables while we make our updates. We use a shared lock because we - // actually do multiple updates in parallel (but to different tables), which is safe. - log.WithField("timeout", config.IptablesLockTimeout).Debug( - "Calico implementation of iptables lock enabled") - iptablesLock = iptables.NewSharedLock( - config.IptablesLockFilePath, - config.IptablesLockTimeout, - config.IptablesLockProbeInterval, - ) - } - } - // iptables and nftables implementations. var mangleTableV4NFT, natTableV4NFT, rawTableV4NFT, filterTableV4NFT generictables.Table var mangleTableV4IPT, natTableV4IPT, rawTableV4IPT, filterTableV4IPT generictables.Table // This is required when nftables mode is configured; but also useful for cleanup in other modes. - nftablesV4RootTable := nftables.NewTable("calico", 4, rules.RuleHashPrefix, featureDetector, nftablesOptions, config.RulesConfig.NFTables) + nftablesV4RootTable := nftables.NewTable("calico", 4, rules.RuleHashPrefix, featureDetector, nftablesOptions, nftablesEnabled) - if config.RulesConfig.NFTables { + if nftablesEnabled { // Create nftables Table implementations. mangleTableV4NFT = nftables.NewTableLayer("mangle", nftablesV4RootTable) natTableV4NFT = nftables.NewTableLayer("nat", nftablesV4RootTable) @@ -574,17 +578,16 @@ func NewIntDataplaneDriver(config Config) *InternalDataplane { } // Create iptables table implementations. - mangleTableV4IPT = iptables.NewTable("mangle", 4, rules.RuleHashPrefix, iptablesLock, featureDetector, iptablesOptions) - natTableV4IPT = iptables.NewTable("nat", 4, rules.RuleHashPrefix, iptablesLock, featureDetector, iptablesNATOptions) - rawTableV4IPT = iptables.NewTable("raw", 4, rules.RuleHashPrefix, iptablesLock, featureDetector, iptablesOptions) - filterTableV4IPT = iptables.NewTable("filter", 4, rules.RuleHashPrefix, iptablesLock, featureDetector, iptablesOptions) + mangleTableV4IPT = iptables.NewTable("mangle", 4, rules.RuleHashPrefix, featureDetector, iptablesOptions) + natTableV4IPT = iptables.NewTable("nat", 4, rules.RuleHashPrefix, featureDetector, iptablesNATOptions) + rawTableV4IPT = iptables.NewTable("raw", 4, rules.RuleHashPrefix, featureDetector, iptablesOptions) + filterTableV4IPT = iptables.NewTable("filter", 4, rules.RuleHashPrefix, featureDetector, iptablesOptions) // Based on configuration, some of the above tables should be active and others not. var mangleTableV4, natTableV4, rawTableV4, filterTableV4 generictables.Table var ipSetsV4 dpsets.IPSetsDataplane - var cleanupTables []generictables.Table var cleanupIPSets []dpsets.IPSetsDataplane - if config.RulesConfig.NFTables { + if nftablesEnabled { // Enable nftables. mangleTableV4 = mangleTableV4NFT natTableV4 = natTableV4NFT @@ -722,6 +725,7 @@ func NewIntDataplaneDriver(config Config) *InternalDataplane { } } + dataplaneFeatures := featureDetector.GetFeatures() if config.RulesConfig.VXLANEnabled { var fdbOpts []vxlanfdb.Option if config.BPFEnabled && bpfutils.BTFEnabled { @@ -830,15 +834,15 @@ func NewIntDataplaneDriver(config Config) *InternalDataplane { var filterTableV6NFT, filterTableV6IPT generictables.Table // Create nftables Table implementations for IPv6. - nftablesV6RootTable := nftables.NewTable("calico", 6, rules.RuleHashPrefix, featureDetector, nftablesOptions, config.RulesConfig.NFTables) + nftablesV6RootTable := nftables.NewTable("calico", 6, rules.RuleHashPrefix, featureDetector, nftablesOptions, nftablesEnabled) filterTableV6NFT = nftables.NewTableLayer("filter", nftablesV6RootTable) // Create iptables Table implementations for IPv6. - filterTableV6IPT = iptables.NewTable("filter", 6, rules.RuleHashPrefix, iptablesLock, featureDetector, iptablesOptions) + filterTableV6IPT = iptables.NewTable("filter", 6, rules.RuleHashPrefix, featureDetector, iptablesOptions) // Select the correct table implementation based on whether we're using nftables or iptables. var filterTableV6 generictables.Table - if config.RulesConfig.NFTables { + if nftablesEnabled { filterTableV6 = filterTableV6NFT } else { filterTableV6 = filterTableV6IPT @@ -855,7 +859,7 @@ func NewIntDataplaneDriver(config Config) *InternalDataplane { rules.IPSetIDThisHostIPs, ipSetsV4, config.MaxIPSetSize)) - dp.RegisterManager(newPolicyManager(rawTableV4, mangleTableV4, filterTableV4, ruleRenderer, 4, config.RulesConfig.NFTables)) + dp.RegisterManager(newPolicyManager(rawTableV4, mangleTableV4, filterTableV4, ruleRenderer, 4, nftablesEnabled)) // Clean up any leftover BPF state. err := bpfnat.RemoveConnectTimeLoadBalancer(true, "") @@ -866,7 +870,7 @@ func NewIntDataplaneDriver(config Config) *InternalDataplane { bpfutils.RemoveBPFSpecialDevices() } else { // In BPF mode we still use iptables for raw egress policy. - dp.RegisterManager(newRawEgressPolicyManager(rawTableV4, ruleRenderer, 4, ipSetsV4.SetFilter, config.RulesConfig.NFTables)) + dp.RegisterManager(newRawEgressPolicyManager(rawTableV4, ruleRenderer, 4, ipSetsV4.SetFilter, nftablesEnabled)) } interfaceRegexes := make([]string, len(config.RulesConfig.WorkloadIfacePrefixes)) @@ -893,7 +897,7 @@ func NewIntDataplaneDriver(config Config) *InternalDataplane { } bpfipsets.SetMapSize(config.BPFMapSizeIPSets) - bpfnat.SetMapSizes(config.BPFMapSizeNATFrontend, config.BPFMapSizeNATBackend, config.BPFMapSizeNATAffinity) + bpfnat.SetMapSizes(config.BPFMapSizeNATFrontend, config.BPFMapSizeNATBackend, config.BPFMapSizeNATAffinity, config.BPFMapSizeMaglev) bpfroutes.SetMapSize(config.BPFMapSizeRoute) bpfconntrack.SetMapSize(bpfMapSizeConntrack) bpfconntrack.SetCleanupMapSize(config.BPFMapSizeConntrackCleanupQueue) @@ -933,15 +937,16 @@ func NewIntDataplaneDriver(config Config) *InternalDataplane { // Important that we create the maps before we load a BPF program with TC since we make sure the map // metadata name is set whereas TC doesn't set that field. var conntrackScannerV4, conntrackScannerV6 *bpfconntrack.Scanner + var workloadRemoveChanV4, workloadRemoveChanV6 chan string var ipSetIDAllocatorV4, ipSetIDAllocatorV6 *idalloc.IDAllocator ipSetIDAllocatorV4 = idalloc.New() // Start IPv4 BPF dataplane components - conntrackScannerV4 = startBPFDataplaneComponents(proto.IPVersion_IPV4, bpfMaps.V4, ipSetIDAllocatorV4, config, ipsetsManager, dp) + conntrackScannerV4, workloadRemoveChanV4 = startBPFDataplaneComponents(proto.IPVersion_IPV4, bpfMaps.V4, ipSetIDAllocatorV4, &config, ipsetsManager, dp) if config.BPFIpv6Enabled { // Start IPv6 BPF dataplane components ipSetIDAllocatorV6 = idalloc.New() - conntrackScannerV6 = startBPFDataplaneComponents(proto.IPVersion_IPV6, bpfMaps.V6, ipSetIDAllocatorV6, config, ipsetsManagerV6, dp) + conntrackScannerV6, workloadRemoveChanV6 = startBPFDataplaneComponents(proto.IPVersion_IPV6, bpfMaps.V6, ipSetIDAllocatorV6, &config, ipsetsManagerV6, dp) } workloadIfaceRegex := regexp.MustCompile(strings.Join(interfaceRegexes, "|")) @@ -958,12 +963,10 @@ func NewIntDataplaneDriver(config Config) *InternalDataplane { // Forwarding into an IPIP tunnel fails silently because IPIP tunnels are L3 devices and support for // L3 devices in BPF is not available yet. Disable the FIB lookup in that case. - fibLookupEnabled := !config.RulesConfig.IPIPEnabled bpfEndpointManager, err = NewBPFEndpointManager( nil, &config, bpfMaps, - fibLookupEnabled, workloadIfaceRegex, ipSetIDAllocatorV4, ipSetIDAllocatorV6, @@ -978,6 +981,8 @@ func NewIntDataplaneDriver(config Config) *InternalDataplane { config.HealthAggregator, dataplaneFeatures, podMTU, + workloadRemoveChanV4, + workloadRemoveChanV6, ) if err != nil { log.WithError(err).Panic("Failed to create BPF endpoint manager.") @@ -1019,7 +1024,7 @@ func NewIntDataplaneDriver(config Config) *InternalDataplane { // Activate the connect-time load balancer. err = bpfnat.InstallConnectTimeLoadBalancer(true, config.BPFIpv6Enabled, - config.BPFCgroupV2, logLevel, config.BPFConntrackTimeouts.UDPTimeout, excludeUDP, bpfMaps.CommonMaps.CTLBProgramsMap) + config.BPFCgroupV2, logLevel, config.BPFConntrackTimeouts.UDPTimeout, excludeUDP, bpfMaps.CommonMaps.CTLBProgramsMaps) if err != nil { log.WithError(err).Panic("BPFConnTimeLBEnabled but failed to attach connect-time load balancer, bailing out.") } @@ -1080,7 +1085,7 @@ func NewIntDataplaneDriver(config Config) *InternalDataplane { } var filterMaps nftables.MapsDataplane - if config.RulesConfig.NFTables { + if nftablesEnabled { filterMaps = filterTableV4.(nftables.MapsDataplane) } @@ -1105,7 +1110,7 @@ func NewIntDataplaneDriver(config Config) *InternalDataplane { bpfEndpointManager, callbacks, config.FloatingIPsEnabled, - config.RulesConfig.NFTables, + nftablesEnabled, linkAddrsManagerV4, ) dp.RegisterManager(epManager) @@ -1174,7 +1179,7 @@ func NewIntDataplaneDriver(config Config) *InternalDataplane { var mangleTableV6NFT, natTableV6NFT, rawTableV6NFT generictables.Table var mangleTableV6IPT, natTableV6IPT, rawTableV6IPT generictables.Table - if config.RulesConfig.NFTables { + if nftablesEnabled { // Define nftables table implementations for IPv6. mangleTableV6NFT = nftables.NewTableLayer("mangle", nftablesV6RootTable) natTableV6NFT = nftables.NewTableLayer("nat", nftablesV6RootTable) @@ -1182,14 +1187,14 @@ func NewIntDataplaneDriver(config Config) *InternalDataplane { } // Define iptables table implementations for IPv6. - mangleTableV6IPT = iptables.NewTable("mangle", 6, rules.RuleHashPrefix, iptablesLock, featureDetector, iptablesOptions) - natTableV6IPT = iptables.NewTable("nat", 6, rules.RuleHashPrefix, iptablesLock, featureDetector, iptablesNATOptions) - rawTableV6IPT = iptables.NewTable("raw", 6, rules.RuleHashPrefix, iptablesLock, featureDetector, iptablesOptions) + mangleTableV6IPT = iptables.NewTable("mangle", 6, rules.RuleHashPrefix, featureDetector, iptablesOptions) + natTableV6IPT = iptables.NewTable("nat", 6, rules.RuleHashPrefix, featureDetector, iptablesNATOptions) + rawTableV6IPT = iptables.NewTable("raw", 6, rules.RuleHashPrefix, featureDetector, iptablesOptions) // Select the correct table implementation based on whether we're using nftables or iptables. var mangleTableV6, natTableV6, rawTableV6 generictables.Table var ipSetsV6 dpsets.IPSetsDataplane - if config.RulesConfig.NFTables { + if nftablesEnabled { // Enable nftables. mangleTableV6 = mangleTableV6NFT natTableV6 = natTableV6NFT @@ -1280,13 +1285,13 @@ func NewIntDataplaneDriver(config Config) *InternalDataplane { rules.IPSetIDThisHostIPs, ipSetsV6, config.MaxIPSetSize)) - dp.RegisterManager(newPolicyManager(rawTableV6, mangleTableV6, filterTableV6, ruleRenderer, 6, config.RulesConfig.NFTables)) + dp.RegisterManager(newPolicyManager(rawTableV6, mangleTableV6, filterTableV6, ruleRenderer, 6, nftablesEnabled)) } else { - dp.RegisterManager(newRawEgressPolicyManager(rawTableV6, ruleRenderer, 6, ipSetsV6.SetFilter, config.RulesConfig.NFTables)) + dp.RegisterManager(newRawEgressPolicyManager(rawTableV6, ruleRenderer, 6, ipSetsV6.SetFilter, nftablesEnabled)) } var filterMapsV6 nftables.MapsDataplane - if config.RulesConfig.NFTables { + if nftablesEnabled { filterMapsV6 = filterTableV6.(nftables.MapsDataplane) } @@ -1311,7 +1316,7 @@ func NewIntDataplaneDriver(config Config) *InternalDataplane { nil, callbacks, config.FloatingIPsEnabled, - config.RulesConfig.NFTables, + nftablesEnabled, linkAddrsManagerV6, )) dp.RegisterManager(newFloatingIPManager(natTableV6, ruleRenderer, 6, config.FloatingIPsEnabled)) @@ -1343,7 +1348,7 @@ func NewIntDataplaneDriver(config Config) *InternalDataplane { dp.RegisterManager(dp.wireguardManagerV6) } - if config.RulesConfig.NFTables { + if nftablesEnabled { // In nftables mode, we use a single underlying table to implement all tables. Only add the base table here // to avoid duplicating Apply() calls. dp.allTables = append(dp.allTables, nftablesV4RootTable) @@ -1404,13 +1409,37 @@ func NewIntDataplaneDriver(config Config) *InternalDataplane { log.Info("Starting BPF event poller") if err := bpfEventPoller.Start(); err != nil { log.WithError(err).Info("Stopping bpf event poller") - bpfEvnt.Close() + err := bpfEvnt.Close() + if err != nil { + log.WithError(err).Info("Error from closing bpf event source.") + } } } return dp } +// useNftables determines whether to use nftables based on the FelixConfig setting and +// kube-proxy mode. +func useNftables(mode string, proxyEnabled bool) bool { + use := false + + switch mode { + case "Auto": + // Detect based on kube-proxy mode. + use = proxyEnabled + case "Enabled": + use = true + } + + log.WithFields(log.Fields{ + "kubeProxyEnabled": proxyEnabled, + "calicoMode": mode, + "useNftables": use, + }).Info("Determined whether or not to use nftables") + return use +} + // findHostMTU auto-detects the smallest host interface MTU. func findHostMTU(matchRegex *regexp.Regexp) (int, error) { // Find all the interfaces on the host. @@ -1467,7 +1496,7 @@ func writeMTUFile(mtu int) error { // Write the smallest MTU to disk so other components can rely on this calculation consistently. filename := "/var/lib/calico/mtu" log.Debugf("Writing %d to "+filename, mtu) - if err := os.WriteFile(filename, []byte(fmt.Sprintf("%d", mtu)), 0o644); err != nil { + if err := os.WriteFile(filename, fmt.Appendf(nil, "%d", mtu), 0o644); err != nil { log.WithError(err).Error("Unable to write to " + filename) return err } @@ -1649,7 +1678,7 @@ type Manager interface { // send updates to the IPSets and generictables.Table objects (which will queue the updates // until the main loop instructs them to act) or (for efficiency) may wait until // a call to CompleteDeferredWork() to flush updates to the dataplane. - OnUpdate(protoBufMsg interface{}) + OnUpdate(protoBufMsg any) // Called before the main loop flushes updates to the dataplane to allow for batched // work to be completed. CompleteDeferredWork() error @@ -1715,6 +1744,7 @@ func (d *InternalDataplane) Start() { go d.loopReportingStatus() go d.ifaceMonitor.MonitorInterfaces() go d.monitorHostMTU() + go d.monitorKubeProxyNftablesMode() } // onIfaceInSync is used as a callback from the interface monitor. We use it to send a message back to @@ -1769,6 +1799,38 @@ func (d *InternalDataplane) checkIPVSConfigOnStateUpdate(state ifacemonitor.Stat } } +// monitorKubeProxyNftablesMode monitors kube-proxy's nftables mode has changed and +// triggers a Felix restart if it has. This is only active if the nftables mode is set to "Auto". +func (d *InternalDataplane) monitorKubeProxyNftablesMode() { + if d.config.RulesConfig.NFTablesMode != "Auto" { + // We can skip this check if nftables is not configured to Auto. + log.Debug("Skipping kube-proxy nftables mode monitoring as NFTablesMode is not set to Auto.") + return + } + if d.getKubeProxyNftablesEnabled == nil { + log.Panic("BUG: kube-proxy nftables mode check function is nil") + } + + // Loop forever, checking kube proxy status at intervals. + t := time.Tick(15 * time.Second) + for range t { + previous := d.kubeProxyNftablesEnabled + current, err := d.getKubeProxyNftablesEnabled() + if err != nil { + log.WithError(err).Warn("Failed to detect kube-proxy nftables mode.") + continue + } + + if previous != current { + log.WithFields(log.Fields{ + "previous": previous, + "current": current, + }).Info("kube-proxy nftables mode changed. Restart felix.") + d.config.ConfigChangedRestartCallback() + } + } +} + // onIfaceAddrsChange is our interface address monitor callback. It gets called // from the monitor's thread. func (d *InternalDataplane) onIfaceAddrsChange(ifaceName string, addrs set.Set[string]) { @@ -1794,12 +1856,12 @@ func NewIfaceAddrsUpdate(name string, ips ...string) any { } } -func (d *InternalDataplane) SendMessage(msg interface{}) error { +func (d *InternalDataplane) SendMessage(msg any) error { d.toDataplane <- msg return nil } -func (d *InternalDataplane) RecvMessage() (interface{}, error) { +func (d *InternalDataplane) RecvMessage() (any, error) { return <-d.fromDataplane, nil } @@ -1847,7 +1909,7 @@ func (d *InternalDataplane) bpfMarkPreestablishedFlowsRules() []generictables.Ru func (d *InternalDataplane) setUpIptablesBPF() { // Wildcard matching varies based on iptables vs nftables. wildcard := iptables.Wildcard - if d.config.RulesConfig.NFTables { + if d.nftablesEnabled { wildcard = nftables.Wildcard } @@ -2191,7 +2253,7 @@ func (d *InternalDataplane) shutdownXDPCompletely() error { maxTries := 10 waitInterval := 100 * time.Millisecond var err error - for i := 0; i < maxTries; i++ { + for i := range maxTries { err = d.xdpState.WipeXDP() if err == nil { d.xdpState = nil @@ -2337,7 +2399,7 @@ func newRefreshTicker(name string, interval time.Duration) <-chan time.Time { // onDatastoreMessage is called when we get a message from the calculation graph // it opportunistically processes a match of messages from its channel. -func (d *InternalDataplane) onDatastoreMessage(msg interface{}) { +func (d *InternalDataplane) onDatastoreMessage(msg any) { d.datastoreBatchSize = 1 // Process the message we received, then opportunistically process any other @@ -2349,7 +2411,7 @@ func (d *InternalDataplane) onDatastoreMessage(msg interface{}) { summaryBatchSize.Observe(float64(d.datastoreBatchSize)) } -func (d *InternalDataplane) processMsgFromCalcGraph(msg interface{}) { +func (d *InternalDataplane) processMsgFromCalcGraph(msg any) { if log.IsLevelEnabled(log.InfoLevel) { log.Infof("Received %T update from calculation graph. msg=%s", msg, proto.MsgStringer{Msg: msg}.String()) } @@ -2445,7 +2507,7 @@ func (d *InternalDataplane) processIfaceAddrsUpdate(ifaceAddrsUpdate *ifaceAddrs } func drainChan[T any](c <-chan T, f func(T)) { - for i := 0; i < msgPeekLimit; i++ { + for range msgPeekLimit { select { case v := <-c: f(v) @@ -2506,7 +2568,7 @@ func (d *InternalDataplane) configureKernel() { } } -func (d *InternalDataplane) recordMsgStat(msg interface{}) { +func (d *InternalDataplane) recordMsgStat(msg any) { typeName := reflect.ValueOf(msg).Elem().Type().Name() countMessages.WithLabelValues(typeName).Inc() } @@ -2753,7 +2815,7 @@ func (d *InternalDataplane) apply() { func (d *InternalDataplane) applyXDPActions() error { var err error = nil - for i := 0; i < 10; i++ { + for range 10 { err = d.xdpState.ResyncIfNeeded(d.ipsetsSourceV4) if err != nil { return err @@ -2802,22 +2864,14 @@ func (d *InternalDataplane) reportHealth() { } } -type dummyLock struct{} - -func (d dummyLock) Lock() { -} - -func (d dummyLock) Unlock() { -} - func startBPFDataplaneComponents( ipFamily proto.IPVersion, maps *bpfmap.IPMaps, ipSetIDAllocator *idalloc.IDAllocator, - config Config, + config *Config, ipSetsMgr *dpsets.IPSetsManager, dp *InternalDataplane, -) *bpfconntrack.Scanner { +) (*bpfconntrack.Scanner, chan string) { ipSetConfig := config.RulesConfig.IPSetConfigV4 ipSetEntry := bpfipsets.IPSetEntryFromBytes ipSetProtoEntry := bpfipsets.ProtoIPSetMemberToBPFEntry @@ -2828,26 +2882,28 @@ func startBPFDataplaneComponents( ctKey := bpfconntrack.KeyFromBytes ctVal := bpfconntrack.ValueFromBytes - if config.bpfProxyHealthzServer == nil { - healthzAddr := fmt.Sprintf(":%d", config.KubeProxyHealtzPort) - config.bpfProxyHealthzServer = k8shealthcheck.NewProxyHealthServer( - healthzAddr, config.KubeProxyMinSyncPeriod) - - // We cannot wait for the healthz server as we cannot stop it. - go func() { - for { - err := config.bpfProxyHealthzServer.Run(context.Background()) // context is mosstly ignored inside - if err != nil { - log.WithError(err).Error("BPF Proxy Healthz server failed, restarting in 1s") - time.Sleep(time.Second) - } - } - }() + if config.bpfProxyHealthCheck == nil && config.KubeProxyHealtzPort != 0 { + var err error + config.bpfProxyHealthCheck, err = bpfproxy.NewHealthCheck( + config.KubeClientSet, + config.Hostname, + config.KubeProxyHealtzPort, + config.KubeProxyMinSyncPeriod, + ) + if err != nil { + log.WithError(err).Error("Failed to initialize BPF kube-proxy health check") + } } bpfproxyOpts := []bpfproxy.Option{ - bpfproxy.WithHealthzServer(config.bpfProxyHealthzServer), bpfproxy.WithMinSyncPeriod(config.KubeProxyMinSyncPeriod), + bpfproxy.WithMaglevLUTSize(config.BPFMaglevLUTSize), + } + + if config.bpfProxyHealthCheck != nil { + bpfproxyOpts = append(bpfproxyOpts, bpfproxy.WithHealthCheck(config.bpfProxyHealthCheck)) + } else { + log.Info("No healthz server configured for BPF kube-proxy.") } if config.BPFNodePortDSREnabled { @@ -2891,7 +2947,7 @@ func startBPFDataplaneComponents( ) dp.RegisterManager(failsafeMgr) - bpfRTMgr := newBPFRouteManager(&config, maps, ipFamily, dp.loopSummarizer) + bpfRTMgr := newBPFRouteManager(config, maps, ipFamily, dp.loopSummarizer) dp.RegisterManager(bpfRTMgr) livenessScanner := bpfconntrack.NewLivenessScanner(config.BPFConntrackTimeouts, config.BPFNodePortDSREnabled) @@ -2905,12 +2961,13 @@ func startBPFDataplaneComponents( log.Errorf("error creating the bpf cleaner %v", err) } + workloadRemoveChan := make(chan string, 1000) conntrackScanner := bpfconntrack.NewScanner(maps.CtMap, ctKey, ctVal, config.ConfigChangedRestartCallback, config.BPFMapSizeConntrackScaling, maps.CtCleanupMap.(bpfmaps.MapWithExistsCheck), int(ipFamily), bpfCleaner, - livenessScanner) + livenessScanner, bpfconntrack.NewWorkloadRemoveScannerTCP(workloadRemoveChan)) // Before we start, scan for all finished / timed out connections to // free up the conntrack table asap as it may take time to sync up the @@ -2928,13 +2985,19 @@ func startBPFDataplaneComponents( log.WithError(err).Panic("Failed to start kube-proxy.") } + // Register KP itself as a manager, in order to collect host metadata. + // Kube-proxy already interfaces with the dataplane via manager callbacks to receive information + // that is already at-hand in those managers. But, when it comes to batching raw host information, + // we might-as-well just funnel it directly from the calc-graph. + dp.RegisterManager(kp) + bpfRTMgr.setHostIPUpdatesCallBack(kp.OnHostIPsUpdate) bpfRTMgr.setRoutesCallBacks(kp.OnRouteUpdate, kp.OnRouteDelete) conntrackScanner.AddUnlocked(bpfconntrack.NewStaleNATScanner(kp)) } else { log.Info("BPF enabled but no Kubernetes client available, unable to run kube-proxy module.") } - return conntrackScanner + return conntrackScanner, workloadRemoveChan } func conntrackMapSizeFromFile() (int, error) { diff --git a/felix/dataplane/linux/int_dataplane_test.go b/felix/dataplane/linux/int_dataplane_test.go index c8633219c06..adbe7bad434 100644 --- a/felix/dataplane/linux/int_dataplane_test.go +++ b/felix/dataplane/linux/int_dataplane_test.go @@ -19,7 +19,7 @@ import ( "net" "regexp" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "sigs.k8s.io/knftables" @@ -43,13 +43,13 @@ var _ = Describe("Constructor test", func() { kubernetesProvider := config.ProviderNone routeSource := "CalicoIPAM" var wireguardEncryptHostTraffic bool - var nftablesDataplane func(knftables.Family, string) (knftables.Interface, error) + var nftablesDataplane func(knftables.Family, string, ...knftables.Option) (knftables.Interface, error) BeforeEach(func() { // For most tests here, mock out the creation of the nftables interface in a way // that simulates "nft" being available. We don't want these tests to depend on the // actual kernel version or presence of nftables. - nftablesDataplane = func(knftables.Family, string) (knftables.Interface, error) { + nftablesDataplane = func(knftables.Family, string, ...knftables.Option) (knftables.Interface, error) { return nil, nil } }) @@ -126,7 +126,7 @@ var _ = Describe("Constructor test", func() { Context("when nft is not available", func() { BeforeEach(func() { - nftablesDataplane = func(knftables.Family, string) (knftables.Interface, error) { + nftablesDataplane = func(knftables.Family, string, ...knftables.Option) (knftables.Interface, error) { return nil, errors.New("could not find nftables binary: file not found") } }) diff --git a/felix/dataplane/linux/intdataplane_ut_suite_test.go b/felix/dataplane/linux/intdataplane_ut_suite_test.go index e905ebbe106..8787c91549b 100644 --- a/felix/dataplane/linux/intdataplane_ut_suite_test.go +++ b/felix/dataplane/linux/intdataplane_ut_suite_test.go @@ -17,9 +17,8 @@ package intdataplane import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestIntdataplane(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/intdataplane_ut_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Intdataplane Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/felix_dataplane_linux_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/dataplane/linux", suiteConfig, reporterConfig) } diff --git a/felix/dataplane/linux/ipip_mgr.go b/felix/dataplane/linux/ipip_mgr.go index e4d258800f3..2a6850e02a7 100644 --- a/felix/dataplane/linux/ipip_mgr.go +++ b/felix/dataplane/linux/ipip_mgr.go @@ -120,7 +120,7 @@ func newIPIPManagerWithShims( return m } -func (m *ipipManager) OnUpdate(protoBufMsg interface{}) { +func (m *ipipManager) OnUpdate(protoBufMsg any) { switch msg := protoBufMsg.(type) { case *proto.HostMetadataUpdate: m.logCtx.WithField("hostname", msg.Hostname).Debug("Host update/create") @@ -166,8 +166,10 @@ func (m *ipipManager) tunnelRoute(cidr ip.CIDR, r *proto.RouteUpdate) *routetabl } return &routetable.Target{ - Type: routetable.TargetTypeOnLink, - CIDR: cidr, + Type: routetable.TargetTypeOnLink, + RouteKey: routetable.RouteKey{ + CIDR: cidr, + }, GW: ip.FromString(remoteAddr), Protocol: m.routeProtocol, MTU: m.dpConfig.IPIPMTU, diff --git a/felix/dataplane/linux/ipip_mgr_test.go b/felix/dataplane/linux/ipip_mgr_test.go index 74b745bf14c..84a99394754 100644 --- a/felix/dataplane/linux/ipip_mgr_test.go +++ b/felix/dataplane/linux/ipip_mgr_test.go @@ -20,7 +20,7 @@ import ( "net" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/sirupsen/logrus" "github.com/vishvananda/netlink" @@ -286,8 +286,10 @@ var _ = Describe("IPIPManager", func() { Expect(rt.currentRoutes[dataplanedefs.IPIPIfaceName]).To(HaveLen(1)) Expect(rt.currentRoutes[dataplanedefs.IPIPIfaceName][0]).To(Equal( routetable.Target{ - Type: "onlink", - CIDR: ip.MustParseCIDROrIP("10.0.1.1/32"), + Type: "onlink", + RouteKey: routetable.RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.1.1/32"), + }, GW: ip.FromString("172.0.2.2"), Protocol: 80, })) diff --git a/felix/dataplane/linux/ipset_hosts_mgr.go b/felix/dataplane/linux/ipset_hosts_mgr.go index 633194398af..7867584e72b 100644 --- a/felix/dataplane/linux/ipset_hosts_mgr.go +++ b/felix/dataplane/linux/ipset_hosts_mgr.go @@ -69,7 +69,7 @@ func newHostsIPSetManager( } } -func (m *hostsIPSetManager) OnUpdate(protoBufMsg interface{}) { +func (m *hostsIPSetManager) OnUpdate(protoBufMsg any) { switch msg := protoBufMsg.(type) { case *proto.HostMetadataV4V6Update: if (m.ipVersion == 4 && msg.Ipv4Addr == "") || (m.ipVersion == 6 && msg.Ipv6Addr == "") { diff --git a/felix/dataplane/linux/ipset_hosts_mgr_test.go b/felix/dataplane/linux/ipset_hosts_mgr_test.go index d462cc23bf1..f15f3f4d335 100644 --- a/felix/dataplane/linux/ipset_hosts_mgr_test.go +++ b/felix/dataplane/linux/ipset_hosts_mgr_test.go @@ -15,7 +15,7 @@ package intdataplane import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/sirupsen/logrus" diff --git a/felix/dataplane/linux/masq_mgr.go b/felix/dataplane/linux/masq_mgr.go index 947bd0a8453..1c760dce69d 100644 --- a/felix/dataplane/linux/masq_mgr.go +++ b/felix/dataplane/linux/masq_mgr.go @@ -82,7 +82,7 @@ func newMasqManager( } } -func (d *masqManager) OnUpdate(msg interface{}) { +func (d *masqManager) OnUpdate(msg any) { var poolID string var newPool *proto.IPAMPool diff --git a/felix/dataplane/linux/masq_mgr_test.go b/felix/dataplane/linux/masq_mgr_test.go index 454871d2ab6..29c571b9b2e 100644 --- a/felix/dataplane/linux/masq_mgr_test.go +++ b/felix/dataplane/linux/masq_mgr_test.go @@ -15,7 +15,7 @@ package intdataplane import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" dpsets "github.com/projectcalico/calico/felix/dataplane/ipsets" @@ -51,7 +51,7 @@ var _ = Describe("Masquerade manager", func() { MarkScratch1: 0x8, MarkDrop: 0x10, MarkEndpoint: 0x11110000, - }) + }, false) masqMgr = newMasqManager(ipSets, natTable, ruleRenderer, 1024, 4) }) diff --git a/felix/dataplane/linux/mock_iptables_for_test.go b/felix/dataplane/linux/mock_iptables_for_test.go index c13710551ce..50f71f18b4c 100644 --- a/felix/dataplane/linux/mock_iptables_for_test.go +++ b/felix/dataplane/linux/mock_iptables_for_test.go @@ -15,6 +15,10 @@ package intdataplane import ( + "fmt" + "strings" + + "github.com/google/go-cmp/cmp" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" @@ -82,22 +86,27 @@ func (t *mockTable) checkChains(expecteds [][]*generictables.Chain) { } func (t *mockTable) checkChainsSameAsBefore() { - log.Debug("Expected chains") + // Build a failure message in case of unexpected chains. + var msg strings.Builder + msg.WriteString("Unexpected chain:\n\n %+v\n\n Expected chains:\n\n") for _, chain := range t.expectedChains { log.WithField("chain", *chain).Debug("") + msg.WriteString(fmt.Sprintf(" %+v\n", *chain)) } - // Compare chains one-by-one to get a nice diff. + // Check each current chain is as expected. for _, chain := range t.currentChains { expected, ok := t.expectedChains[chain.Name] - Expect(ok).To(BeTrue(), "Unexpected chain: %v", chain) - Expect(*chain).To(Equal(*expected), t.Table+" chain incorrect") + ExpectWithOffset(2, ok).To(BeTrue(), fmt.Sprintf(msg.String(), *chain)) + ExpectWithOffset(2, *chain).To(Equal(*expected), cmp.Diff(expected, chain)) } + + // Ensure no expected chains are missing. for _, chain := range t.expectedChains { _, ok := t.currentChains[chain.Name] - Expect(ok).To(BeTrue(), "Missing expected chain: %v", chain) + ExpectWithOffset(2, ok).To(BeTrue(), "Missing expected chain: %v", chain) } // Assert the whole map is as expected. - ExpectWithOffset(1, t.currentChains).To(Equal(t.expectedChains), t.Table+" chains incorrect") + ExpectWithOffset(2, t.currentChains).To(Equal(t.expectedChains), t.Table+" chains incorrect") } diff --git a/felix/dataplane/linux/modprobe_test.go b/felix/dataplane/linux/modprobe_test.go index da92901dc6c..ff2975f5ab5 100644 --- a/felix/dataplane/linux/modprobe_test.go +++ b/felix/dataplane/linux/modprobe_test.go @@ -17,7 +17,7 @@ package intdataplane import ( "errors" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/felix/dataplane/linux/noencap_mgr.go b/felix/dataplane/linux/noencap_mgr.go index e7d1ebde28e..f31f08945a1 100644 --- a/felix/dataplane/linux/noencap_mgr.go +++ b/felix/dataplane/linux/noencap_mgr.go @@ -89,26 +89,26 @@ func newNoEncapManagerWithSims( return m } -func (m *noEncapManager) OnUpdate(protoBufMsg interface{}) { +func (m *noEncapManager) OnUpdate(protoBufMsg any) { switch msg := protoBufMsg.(type) { - case *proto.HostMetadataV4V6Update: - if (m.ipVersion == 4 && msg.Ipv4Addr == "") || (m.ipVersion == 6 && msg.Ipv6Addr == "") { - // Skip since the update is for a mismatched IP version - m.logCtx.WithField("msg", msg).Debug("Skipping mismatched IP version update") - return + case *proto.HostMetadataUpdate: + m.logCtx.WithField("hostname", msg.Hostname).Debug("Host update/create") + if msg.Hostname == m.hostname && m.ipVersion == 4 { + m.routesNeedUpdate(msg.Ipv4Addr) } - + case *proto.HostMetadataRemove: + m.logCtx.WithField("hostname", msg.Hostname).Debug("Host removed") + if msg.Hostname == m.hostname && m.ipVersion == 4 { + m.routesNeedUpdate("") + } + case *proto.HostMetadataV6Update: m.logCtx.WithField("hostname", msg.Hostname).Debug("Host update/create") - if msg.Hostname == m.hostname { - if m.ipVersion == 6 { - m.routesNeedUpdate(msg.Ipv6Addr) - } else { - m.routesNeedUpdate(msg.Ipv4Addr) - } + if msg.Hostname == m.hostname && m.ipVersion == 6 { + m.routesNeedUpdate(msg.Ipv6Addr) } - case *proto.HostMetadataV4V6Remove: + case *proto.HostMetadataV6Remove: m.logCtx.WithField("hostname", msg.Hostname).Debug("Host removed") - if msg.Hostname == m.hostname { + if msg.Hostname == m.hostname && m.ipVersion == 6 { m.routesNeedUpdate("") } default: diff --git a/felix/dataplane/linux/noencap_mgr_test.go b/felix/dataplane/linux/noencap_mgr_test.go index df742a380b7..4b00cfd8c28 100644 --- a/felix/dataplane/linux/noencap_mgr_test.go +++ b/felix/dataplane/linux/noencap_mgr_test.go @@ -15,7 +15,7 @@ package intdataplane import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/vishvananda/netlink" @@ -76,11 +76,11 @@ var _ = Describe("NoEncap Manager", func() { }) It("successfully adds a route to the noEncap interface", func() { - noencapMgr.OnUpdate(&proto.HostMetadataV4V6Update{ + noencapMgr.OnUpdate(&proto.HostMetadataUpdate{ Hostname: "node1", Ipv4Addr: "172.0.0.2", }) - noencapMgr.OnUpdate(&proto.HostMetadataV4V6Update{ + noencapMgr.OnUpdate(&proto.HostMetadataUpdate{ Hostname: "node2", Ipv4Addr: "172.0.2.2", }) @@ -142,27 +142,31 @@ var _ = Describe("NoEncap Manager", func() { Expect(rt.currentRoutes[routetable.InterfaceNone]).To(HaveLen(1)) Expect(rt.currentRoutes[routetable.InterfaceNone][0]).To(Equal( routetable.Target{ - Type: "blackhole", - CIDR: ip.MustParseCIDROrIP("192.168.0.100/26"), + Type: "blackhole", + RouteKey: routetable.RouteKey{ + CIDR: ip.MustParseCIDROrIP("192.168.0.100/26"), + }, Protocol: 80, })) Expect(rt.currentRoutes["eth0"]).To(HaveLen(1)) Expect(rt.currentRoutes["eth0"][0]).To(Equal( routetable.Target{ - Type: "noencap", - CIDR: ip.MustParseCIDROrIP("192.168.0.0/26"), + Type: "noencap", + RouteKey: routetable.RouteKey{ + CIDR: ip.MustParseCIDROrIP("192.168.0.0/26"), + }, GW: ip.FromString("172.0.2.2"), Protocol: 80, })) }) It("successfully adds a IPv6 route to the noEncap interface", func() { - noencapMgrV6.OnUpdate(&proto.HostMetadataV4V6Update{ + noencapMgrV6.OnUpdate(&proto.HostMetadataV6Update{ Hostname: "node1", Ipv6Addr: "fc00:10:96::2", }) - noencapMgrV6.OnUpdate(&proto.HostMetadataV4V6Update{ + noencapMgrV6.OnUpdate(&proto.HostMetadataV6Update{ Hostname: "node2", Ipv6Addr: "fc00:10:10::1", }) @@ -225,16 +229,20 @@ var _ = Describe("NoEncap Manager", func() { Expect(rt.currentRoutes[routetable.InterfaceNone]).To(HaveLen(1)) Expect(rt.currentRoutes[routetable.InterfaceNone][0]).To(Equal( routetable.Target{ - Type: "blackhole", - CIDR: ip.MustParseCIDROrIP("dead:beef::1:30/112"), + Type: "blackhole", + RouteKey: routetable.RouteKey{ + CIDR: ip.MustParseCIDROrIP("dead:beef::1:30/112"), + }, Protocol: 80, })) Expect(rt.currentRoutes["eth0"]).To(HaveLen(1)) Expect(rt.currentRoutes["eth0"][0]).To(Equal( routetable.Target{ - Type: "noencap", - CIDR: ip.MustParseCIDROrIP("dead:beef::2:10/112"), + Type: "noencap", + RouteKey: routetable.RouteKey{ + CIDR: ip.MustParseCIDROrIP("dead:beef::2:10/112"), + }, GW: ip.FromString("fc00:10:10::1"), Protocol: 80, })) diff --git a/felix/dataplane/linux/policy_mgr.go b/felix/dataplane/linux/policy_mgr.go index 1d86b0feeb7..57efbf6e1bd 100644 --- a/felix/dataplane/linux/policy_mgr.go +++ b/felix/dataplane/linux/policy_mgr.go @@ -78,7 +78,7 @@ func newRawEgressPolicyManager(rawTable Table, ruleRenderer policyRenderer, ipVe } } -func (m *policyManager) OnUpdate(msg interface{}) { +func (m *policyManager) OnUpdate(msg any) { switch msg := msg.(type) { case *proto.ActivePolicyUpdate: id := types.ProtoToPolicyID(msg.GetId()) @@ -181,10 +181,9 @@ func (m *policyManager) CompleteDeferredWork() error { merged := set.New[string]() for _, ipSets := range m.neededIPSets { - ipSets.Iter(func(item string) error { + for item := range ipSets.All() { merged.Add(item) - return nil - }) + } } m.ipSetsCallback(merged) return nil diff --git a/felix/dataplane/linux/policy_mgr_test.go b/felix/dataplane/linux/policy_mgr_test.go index 4129382d013..a79702f3240 100644 --- a/felix/dataplane/linux/policy_mgr_test.go +++ b/felix/dataplane/linux/policy_mgr_test.go @@ -17,8 +17,9 @@ package intdataplane import ( "fmt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/calico/felix/generictables" "github.com/projectcalico/calico/felix/ipsets" @@ -36,6 +37,10 @@ func policyManagerTests(ipVersion uint8, flowlogs bool) func() { mangleTable *mockTable filterTable *mockTable ruleRenderer *mockPolRenderer + + // Expected chain names for GNP "pol1". + caliPIPol1ChainName = rules.PolicyChainName("cali-pi-", &types.PolicyID{Name: "pol1", Kind: v3.KindGlobalNetworkPolicy}, false) + caliPOPol1ChainName = rules.PolicyChainName("cali-po-", &types.PolicyID{Name: "pol1", Kind: v3.KindGlobalNetworkPolicy}, false) ) BeforeEach(func() { @@ -54,8 +59,9 @@ func policyManagerTests(ipVersion uint8, flowlogs bool) func() { Describe("after a policy update", func() { BeforeEach(func() { policyMgr.OnUpdate(&proto.ActivePolicyUpdate{ - Id: &proto.PolicyID{Name: "pol1", Tier: "tier1"}, + Id: &proto.PolicyID{Name: "pol1", Kind: v3.KindGlobalNetworkPolicy}, Policy: &proto.Policy{ + Tier: "tier1", InboundRules: []*proto.Rule{ {Action: "deny"}, }, @@ -70,19 +76,19 @@ func policyManagerTests(ipVersion uint8, flowlogs bool) func() { It("should install the in and out chain", func() { filterTable.checkChains([][]*generictables.Chain{{ - {Name: "cali-pi-tier1/pol1"}, - {Name: "cali-po-tier1/pol1"}, + {Name: caliPIPol1ChainName}, + {Name: caliPOPol1ChainName}, }}) mangleTable.checkChains([][]*generictables.Chain{{ - {Name: "cali-pi-tier1/pol1"}, - {Name: "cali-po-tier1/pol1"}, + {Name: caliPIPol1ChainName}, + {Name: caliPOPol1ChainName}, }}) }) Describe("after a policy remove", func() { BeforeEach(func() { policyMgr.OnUpdate(&proto.ActivePolicyRemove{ - Id: &proto.PolicyID{Name: "pol1", Tier: "tier1"}, + Id: &proto.PolicyID{Name: "pol1", Kind: v3.KindGlobalNetworkPolicy}, }) }) @@ -96,8 +102,9 @@ func policyManagerTests(ipVersion uint8, flowlogs bool) func() { Describe("after an untracked policy update", func() { BeforeEach(func() { policyMgr.OnUpdate(&proto.ActivePolicyUpdate{ - Id: &proto.PolicyID{Name: "pol1", Tier: "tier1"}, + Id: &proto.PolicyID{Name: "pol1", Kind: v3.KindGlobalNetworkPolicy}, Policy: &proto.Policy{ + Tier: "tier1", InboundRules: []*proto.Rule{ {Action: "deny"}, }, @@ -113,27 +120,27 @@ func policyManagerTests(ipVersion uint8, flowlogs bool) func() { It("should install the raw chains", func() { rawTable.checkChains([][]*generictables.Chain{{ - {Name: "cali-pi-tier1/pol1"}, - {Name: "cali-po-tier1/pol1"}, + {Name: caliPIPol1ChainName}, + {Name: caliPOPol1ChainName}, }}) }) It("should install to the filter table", func() { filterTable.checkChains([][]*generictables.Chain{{ - {Name: "cali-pi-tier1/pol1"}, - {Name: "cali-po-tier1/pol1"}, + {Name: caliPIPol1ChainName}, + {Name: caliPOPol1ChainName}, }}) }) It("should install to the mangle table", func() { mangleTable.checkChains([][]*generictables.Chain{{ - {Name: "cali-pi-tier1/pol1"}, - {Name: "cali-po-tier1/pol1"}, + {Name: caliPIPol1ChainName}, + {Name: caliPOPol1ChainName}, }}) }) Describe("after a policy remove", func() { BeforeEach(func() { policyMgr.OnUpdate(&proto.ActivePolicyRemove{ - Id: &proto.PolicyID{Name: "pol1", Tier: "tier1"}, + Id: &proto.PolicyID{Name: "pol1", Kind: v3.KindGlobalNetworkPolicy}, }) }) @@ -152,8 +159,9 @@ func policyManagerTests(ipVersion uint8, flowlogs bool) func() { Describe("after a pre-DNAT policy update", func() { BeforeEach(func() { policyMgr.OnUpdate(&proto.ActivePolicyUpdate{ - Id: &proto.PolicyID{Name: "pol1", Tier: "tier1"}, + Id: &proto.PolicyID{Name: "pol1", Kind: v3.KindGlobalNetworkPolicy}, Policy: &proto.Policy{ + Tier: "tier1", InboundRules: []*proto.Rule{ {Action: "deny"}, }, @@ -169,27 +177,27 @@ func policyManagerTests(ipVersion uint8, flowlogs bool) func() { It("should install the raw chains", func() { rawTable.checkChains([][]*generictables.Chain{{ - {Name: "cali-pi-tier1/pol1"}, - {Name: "cali-po-tier1/pol1"}, + {Name: caliPIPol1ChainName}, + {Name: caliPOPol1ChainName}, }}) }) It("should install to the filter table", func() { filterTable.checkChains([][]*generictables.Chain{{ - {Name: "cali-pi-tier1/pol1"}, - {Name: "cali-po-tier1/pol1"}, + {Name: caliPIPol1ChainName}, + {Name: caliPOPol1ChainName}, }}) }) It("should install to the mangle table", func() { mangleTable.checkChains([][]*generictables.Chain{{ - {Name: "cali-pi-tier1/pol1"}, - {Name: "cali-po-tier1/pol1"}, + {Name: caliPIPol1ChainName}, + {Name: caliPOPol1ChainName}, }}) }) Describe("after a policy remove", func() { BeforeEach(func() { policyMgr.OnUpdate(&proto.ActivePolicyRemove{ - Id: &proto.PolicyID{Name: "pol1", Tier: "tier1"}, + Id: &proto.PolicyID{Name: "pol1", Kind: v3.KindGlobalNetworkPolicy}, }) }) @@ -281,7 +289,7 @@ var _ = Describe("Raw egress policy manager", func() { MarkDrop: 0x80, MarkEndpoint: 0xff00, MarkNonCaliEndpoint: 0x0100, - }) + }, false) policyMgr = newRawEgressPolicyManager( rawTable, ruleRenderer, @@ -308,8 +316,9 @@ var _ = Describe("Raw egress policy manager", func() { It("correctly reports needed IP sets", func() { By("defining one untracked policy with an IP set") policyMgr.OnUpdate(&proto.ActivePolicyUpdate{ - Id: &proto.PolicyID{Tier: "default", Name: "pol1"}, + Id: &proto.PolicyID{Name: "pol1", Kind: v3.KindGlobalNetworkPolicy}, Policy: &proto.Policy{ + Tier: "default", Untracked: true, OutboundRules: []*proto.Rule{ { @@ -326,8 +335,9 @@ var _ = Describe("Raw egress policy manager", func() { By("defining another untracked policy with a different IP set") policyMgr.OnUpdate(&proto.ActivePolicyUpdate{ - Id: &proto.PolicyID{Tier: "default", Name: "pol2"}, + Id: &proto.PolicyID{Name: "pol2", Kind: v3.KindGlobalNetworkPolicy}, Policy: &proto.Policy{ + Tier: "default", Untracked: true, OutboundRules: []*proto.Rule{ { @@ -344,8 +354,9 @@ var _ = Describe("Raw egress policy manager", func() { By("defining a non-untracked policy with a third IP set") policyMgr.OnUpdate(&proto.ActivePolicyUpdate{ - Id: &proto.PolicyID{Tier: "default", Name: "pol3"}, + Id: &proto.PolicyID{Name: "pol3", Kind: v3.KindGlobalNetworkPolicy}, Policy: &proto.Policy{ + Tier: "default", OutboundRules: []*proto.Rule{ { Action: "deny", @@ -362,7 +373,7 @@ var _ = Describe("Raw egress policy manager", func() { By("removing the first untracked policy") policyMgr.OnUpdate(&proto.ActivePolicyRemove{ - Id: &proto.PolicyID{Tier: "default", Name: "pol1"}, + Id: &proto.PolicyID{Name: "pol1", Kind: v3.KindGlobalNetworkPolicy}, }) err = policyMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) @@ -371,7 +382,7 @@ var _ = Describe("Raw egress policy manager", func() { By("removing the second untracked policy") policyMgr.OnUpdate(&proto.ActivePolicyRemove{ - Id: &proto.PolicyID{Tier: "default", Name: "pol2"}, + Id: &proto.PolicyID{Name: "pol2", Kind: v3.KindGlobalNetworkPolicy}, }) err = policyMgr.CompleteDeferredWork() Expect(err).NotTo(HaveOccurred()) @@ -381,16 +392,16 @@ var _ = Describe("Raw egress policy manager", func() { }) type ipSetsMatcher struct { - items []interface{} + items []any } -func MatchIPSets(items ...interface{}) *ipSetsMatcher { +func MatchIPSets(items ...any) *ipSetsMatcher { return &ipSetsMatcher{ items: items, } } -func (m *ipSetsMatcher) Match(actual interface{}) (success bool, err error) { +func (m *ipSetsMatcher) Match(actual any) (success bool, err error) { actualSet := actual.(set.Set[string]) actualCopy := actualSet.Copy() for _, expected := range m.items { @@ -400,11 +411,11 @@ func (m *ipSetsMatcher) Match(actual interface{}) (success bool, err error) { return } -func (m *ipSetsMatcher) FailureMessage(actual interface{}) (message string) { +func (m *ipSetsMatcher) FailureMessage(actual any) (message string) { return fmt.Sprintf("Expected %v to match IP set IDs: %v", actual.(set.Set[string]), m.items) } -func (m *ipSetsMatcher) NegatedFailureMessage(actual interface{}) (message string) { +func (m *ipSetsMatcher) NegatedFailureMessage(actual any) (message string) { return fmt.Sprintf("Expected %v not to match IP set IDs: %v", actual.(set.Set[string]), m.items) } diff --git a/felix/dataplane/linux/qos/qos.go b/felix/dataplane/linux/qos/qos.go index 23ebb736f9a..bcf6fb98457 100644 --- a/felix/dataplane/linux/qos/qos.go +++ b/felix/dataplane/linux/qos/qos.go @@ -242,20 +242,12 @@ func TeardownIfb(deviceName string) error { return nil } -func CreateIngressQdisc(tbs *TokenBucketState, workloadDeviceName string) error { +func CreateOrUpdateIngressQdisc(tbs *TokenBucketState, workloadDeviceName string) error { workloadDevice, err := netlink.LinkByName(workloadDeviceName) if err != nil { return fmt.Errorf("get host device %s: %w", workloadDeviceName, err) } - return createTBF(tbs, workloadDevice) -} - -func UpdateIngressQdisc(tbs *TokenBucketState, workloadDeviceName string) error { - workloadDevice, err := netlink.LinkByName(workloadDeviceName) - if err != nil { - return fmt.Errorf("get host device %s: %w", workloadDeviceName, err) - } - return updateTBF(tbs, workloadDevice) + return createOrUpdateTBF(tbs, workloadDevice) } func CreateEgressQdisc(tbs *TokenBucketState, workloadDeviceName string, ifbDeviceName string) error { @@ -293,7 +285,7 @@ func CreateEgressQdisc(tbs *TokenBucketState, workloadDeviceName string, ifbDevi }, } - err = netlink.QdiscAdd(qdisc) + err = netlink.QdiscReplace(qdisc) if err != nil { return fmt.Errorf("create ingress qdisc %+v on dev %s: %w", qdisc, workloadDeviceName, err) } @@ -363,7 +355,7 @@ func CreateEgressQdisc(tbs *TokenBucketState, workloadDeviceName string, ifbDevi } // throttle traffic on ifb device - err = createTBF(tbs, ifbDevice) + err = createOrUpdateTBF(tbs, ifbDevice) if err != nil { return fmt.Errorf("create ifb qdisc on dev %s: %w", ifbDeviceName, err) } @@ -375,7 +367,7 @@ func UpdateEgressQdisc(tbs *TokenBucketState, ifbDeviceName string) error { if err != nil { return fmt.Errorf("get ifb device %s: %w", ifbDeviceName, err) } - return updateTBF(tbs, ifbDevice) + return createOrUpdateTBF(tbs, ifbDevice) } func GetTBFValues(rateBitsPerSec, burstBits, peakrateBitsPerSec uint64, minburstBytes uint32) *TokenBucketState { @@ -454,23 +446,7 @@ func makeTBF(tbs *TokenBucketState, workloadDevice netlink.Link) (*netlink.Tbf, return qdisc, nil } -func createTBF(tbs *TokenBucketState, workloadDevice netlink.Link) error { - // Equivalent to - // tc qdisc add dev link root tbf - - qdisc, err := makeTBF(tbs, workloadDevice) - if err != nil { - return fmt.Errorf("make TBF qdisc %+v from tbs %+v: %w", qdisc, tbs, err) - } - - err = netlink.QdiscAdd(qdisc) - if err != nil { - return fmt.Errorf("add TBF qdisc %+v, limit: %v, rate %v, buffer %v, peakrate %v, minburst %v: %w", qdisc, qdisc.Limit, qdisc.Rate, qdisc.Buffer, qdisc.Peakrate, qdisc.Minburst, err) - } - return nil -} - -func updateTBF(tbs *TokenBucketState, workloadDevice netlink.Link) error { +func createOrUpdateTBF(tbs *TokenBucketState, workloadDevice netlink.Link) error { // Equivalent to // tc qdisc replace dev link root tbf diff --git a/felix/dataplane/linux/qos_controls.go b/felix/dataplane/linux/qos_controls.go index 9305e2a1062..24aac6f78d7 100644 --- a/felix/dataplane/linux/qos_controls.go +++ b/felix/dataplane/linux/qos_controls.go @@ -92,7 +92,7 @@ func (m *endpointManager) maybeUpdateQoSBandwidth(old, new *proto.WorkloadEndpoi if currentIngress == nil && desiredIngress != nil { // Add. - err := qos.CreateIngressQdisc(desiredIngress, newName) + err := qos.CreateOrUpdateIngressQdisc(desiredIngress, newName) if err != nil { errs = append(errs, fmt.Errorf("error adding ingress qdisc to workload %s: %w", newName, err)) } @@ -104,7 +104,7 @@ func (m *endpointManager) maybeUpdateQoSBandwidth(old, new *proto.WorkloadEndpoi } } else if !currentIngress.Equals(desiredIngress) { // Update. - err := qos.UpdateIngressQdisc(desiredIngress, newName) + err := qos.CreateOrUpdateIngressQdisc(desiredIngress, newName) if err != nil { errs = append(errs, fmt.Errorf("error changing ingress qdisc on workload %s: %w", newName, err)) } diff --git a/felix/dataplane/linux/route_mgr.go b/felix/dataplane/linux/route_mgr.go index c7ea2053f4c..39acc49213b 100644 --- a/felix/dataplane/linux/route_mgr.go +++ b/felix/dataplane/linux/route_mgr.go @@ -156,7 +156,7 @@ func isRemoteTunnelRoute(msg *proto.RouteUpdate, ippoolType proto.IPPoolType) bo return false } -func (m *routeManager) OnUpdate(protoBufMsg interface{}) { +func (m *routeManager) OnUpdate(protoBufMsg any) { switch msg := protoBufMsg.(type) { case *proto.RouteUpdate: // Check to make sure that we are dealing with messages of the correct IP version. @@ -402,8 +402,10 @@ func blackholeRoutes(localIPAMBlocks map[string]*proto.RouteUpdate, proto netlin continue } rtt = append(rtt, routetable.Target{ - Type: routetable.TargetTypeBlackhole, - CIDR: cidr, + Type: routetable.TargetTypeBlackhole, + RouteKey: routetable.RouteKey{ + CIDR: cidr, + }, Protocol: proto, }) } @@ -421,8 +423,10 @@ func (m *routeManager) noEncapRoute(cidr ip.CIDR, r *proto.RouteUpdate) *routeta return nil } noEncapRoute := routetable.Target{ - Type: routetable.TargetTypeNoEncap, - CIDR: cidr, + Type: routetable.TargetTypeNoEncap, + RouteKey: routetable.RouteKey{ + CIDR: cidr, + }, GW: ip.FromString(r.DstNodeIp), Protocol: m.routeProtocol, } @@ -454,7 +458,7 @@ func (m *routeManager) detectParentIface() (netlink.Link, error) { for _, addr := range addrs { // Match address with or without subnet mask if addr.IP.String() == parentAddr || addr.IPNet.String() == parentAddr { - m.logCtx.Debugf("Found parent interface: %s", link) + m.logCtx.Debugf("Found parent interface: %+v", link) return link, nil } } diff --git a/felix/dataplane/linux/service_loop_mgr.go b/felix/dataplane/linux/service_loop_mgr.go index d69b23aebf7..70f811fa1fd 100644 --- a/felix/dataplane/linux/service_loop_mgr.go +++ b/felix/dataplane/linux/service_loop_mgr.go @@ -57,7 +57,7 @@ func newServiceLoopManager( } } -func (m *serviceLoopManager) OnUpdate(protoBufMsg interface{}) { +func (m *serviceLoopManager) OnUpdate(protoBufMsg any) { switch msg := protoBufMsg.(type) { case *proto.GlobalBGPConfigUpdate: m.pendingGlobalBGPConfig = msg diff --git a/felix/dataplane/linux/sockmap_state.go b/felix/dataplane/linux/sockmap_state.go index 83d61c7aa74..bf88d619826 100644 --- a/felix/dataplane/linux/sockmap_state.go +++ b/felix/dataplane/linux/sockmap_state.go @@ -96,43 +96,39 @@ func (s *sockmapState) processWorkloadUpdates(desired set.Set[string]) error { toAdd := setDifference[string](desired, current) toDrop := setDifference[string](current, desired) - toDrop.Iter(func(cidr string) error { + for cidr := range toDrop.All() { logCxt := log.WithField("cidr", cidr) ip, mask, err := bpf.MemberToIPMask(cidr) if err != nil { logCxt.WithError(err).Error("failed to convert cidr to ip and mask") - return set.StopIteration + break } if err := s.bpfLib.RemoveItemSockmapEndpointsMap(*ip, mask); err != nil { logCxt.WithError(err).Error("failed to remove item from endpoints map") - return set.StopIteration + break } log.Infof("[SOCKMAP] removed %v", cidr) + } - return nil - }) - - toAdd.Iter(func(cidr string) error { + for cidr := range toAdd.All() { logCxt := log.WithField("cidr", cidr) ip, mask, err := bpf.MemberToIPMask(cidr) if err != nil { logCxt.WithError(err).Error("failed to convert cidr to ip and mask") - return set.StopIteration + break } if err := s.bpfLib.UpdateSockmapEndpoints(*ip, mask); err != nil { logCxt.WithError(err).Error("failed to update item on endpoints map") - return set.StopIteration + break } log.Infof("[SOCKMAP] added %v", cidr) - - return nil - }) + } return nil } diff --git a/felix/dataplane/linux/status_combiner.go b/felix/dataplane/linux/status_combiner.go index c47ca6eb34f..79c1826c294 100644 --- a/felix/dataplane/linux/status_combiner.go +++ b/felix/dataplane/linux/status_combiner.go @@ -25,35 +25,35 @@ import ( // endpointStatusCombiner combines the status reports of endpoints from the IPv4 and IPv6 // endpoint managers. Where conflicts occur, it reports the "worse" status. type endpointStatusCombiner struct { - ipVersionToStatuses map[uint8]map[interface{}]string + ipVersionToStatuses map[uint8]map[any]string dirtyIDs set.Set[any] /* FIXME HEP or WEP ID */ - fromDataplane chan interface{} + fromDataplane chan any activeWlEndpoints map[types.WorkloadEndpointID]*proto.WorkloadEndpoint } -func newEndpointStatusCombiner(fromDataplane chan interface{}, ipv6Enabled bool) *endpointStatusCombiner { +func newEndpointStatusCombiner(fromDataplane chan any, ipv6Enabled bool) *endpointStatusCombiner { e := &endpointStatusCombiner{ - ipVersionToStatuses: map[uint8]map[interface{}]string{}, + ipVersionToStatuses: map[uint8]map[any]string{}, dirtyIDs: set.New[any](), fromDataplane: fromDataplane, activeWlEndpoints: map[types.WorkloadEndpointID]*proto.WorkloadEndpoint{}, } // IPv4 is always enabled. - e.ipVersionToStatuses[4] = map[interface{}]string{} + e.ipVersionToStatuses[4] = map[any]string{} if ipv6Enabled { // If IPv6 is enabled, track the IPv6 state too. We use the presence of this // extra map to trigger merging. - e.ipVersionToStatuses[6] = map[interface{}]string{} + e.ipVersionToStatuses[6] = map[any]string{} } return e } func (e *endpointStatusCombiner) OnEndpointStatusUpdate( ipVersion uint8, - id interface{}, // types.HostEndpointID or types.WorkloadEndpointID + id any, // types.HostEndpointID or types.WorkloadEndpointID status string, - extraInfo interface{}, // WorkloadEndpoint or nil + extraInfo any, // WorkloadEndpoint or nil ) { log.WithFields(log.Fields{ "ipVersion": ipVersion, @@ -79,7 +79,7 @@ func (e *endpointStatusCombiner) OnEndpointStatusUpdate( } func (e *endpointStatusCombiner) Apply() { - e.dirtyIDs.Iter(func(id interface{}) error { + for id := range e.dirtyIDs.All() { statusToReport := "" logCxt := log.WithField("id", id) for ipVer, statuses := range e.ipVersionToStatuses { @@ -132,6 +132,6 @@ func (e *endpointStatusCombiner) Apply() { } } } - return set.RemoveItem - }) + e.dirtyIDs.Discard(id) + } } diff --git a/felix/dataplane/linux/status_combiner_test.go b/felix/dataplane/linux/status_combiner_test.go index 35c63c14388..ed13c1ff8ab 100644 --- a/felix/dataplane/linux/status_combiner_test.go +++ b/felix/dataplane/linux/status_combiner_test.go @@ -15,8 +15,7 @@ package intdataplane import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/proto" @@ -29,7 +28,7 @@ var ( var _ = Describe("StatusCombiner", func() { var ( - fromDataplane chan interface{} + fromDataplane chan any statusCombiner *endpointStatusCombiner ) @@ -43,7 +42,7 @@ var _ = Describe("StatusCombiner", func() { } BeforeEach(func() { - fromDataplane = make(chan interface{}) + fromDataplane = make(chan any) }) Describe("with IPv6 enabled", func() { diff --git a/felix/dataplane/linux/vxlan_mgr.go b/felix/dataplane/linux/vxlan_mgr.go index e722dd09404..1897e304707 100644 --- a/felix/dataplane/linux/vxlan_mgr.go +++ b/felix/dataplane/linux/vxlan_mgr.go @@ -166,7 +166,7 @@ func newVXLANManagerWithShims( return m } -func (m *vxlanManager) OnUpdate(protoBufMsg interface{}) { +func (m *vxlanManager) OnUpdate(protoBufMsg any) { switch msg := protoBufMsg.(type) { case *proto.VXLANTunnelEndpointUpdate: // Check to make sure that we are dealing with messages of the correct IP version. @@ -269,8 +269,10 @@ func (m *vxlanManager) tunnelRoute(cidr ip.CIDR, r *proto.RouteUpdate) *routetab // We treat remote tunnel routes as directly connected. They don't have a gateway of // the VTEP because they ARE the VTEP! return &routetable.Target{ - CIDR: cidr, - MTU: m.mtu, + RouteKey: routetable.RouteKey{ + CIDR: cidr, + }, + MTU: m.mtu, } } @@ -286,9 +288,11 @@ func (m *vxlanManager) tunnelRoute(cidr ip.CIDR, r *proto.RouteUpdate) *routetab } return &routetable.Target{ Type: routetable.TargetTypeVXLAN, - CIDR: cidr, - GW: ip.FromString(vtepAddr), - MTU: m.mtu, + RouteKey: routetable.RouteKey{ + CIDR: cidr, + }, + GW: ip.FromString(vtepAddr), + MTU: m.mtu, } } diff --git a/felix/dataplane/linux/vxlan_mgr_test.go b/felix/dataplane/linux/vxlan_mgr_test.go index 546691cf27f..bbd3fbc4785 100644 --- a/felix/dataplane/linux/vxlan_mgr_test.go +++ b/felix/dataplane/linux/vxlan_mgr_test.go @@ -19,7 +19,7 @@ import ( "net" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/sirupsen/logrus" "github.com/vishvananda/netlink" @@ -432,8 +432,10 @@ var _ = Describe("VXLANManager", func() { Expect(rt.currentRoutes[dataplanedefs.VXLANIfaceNameV4]).To(HaveLen(1)) Expect(rt.currentRoutes[dataplanedefs.VXLANIfaceNameV4][0]).To(Equal( routetable.Target{ - CIDR: ip.MustParseCIDROrIP("10.0.1.1/32"), - MTU: 4444, + RouteKey: routetable.RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.1.1/32"), + }, + MTU: 4444, })) // Delete the route. @@ -466,8 +468,10 @@ var _ = Describe("VXLANManager", func() { Expect(rt.currentRoutes[dataplanedefs.VXLANIfaceNameV6]).To(HaveLen(1)) Expect(rt.currentRoutes[dataplanedefs.VXLANIfaceNameV6][0]).To(Equal( routetable.Target{ - CIDR: ip.MustParseCIDROrIP("fc00:10:244::1/112"), - MTU: 6666, + RouteKey: routetable.RouteKey{ + CIDR: ip.MustParseCIDROrIP("fc00:10:244::1/112"), + }, + MTU: 6666, })) // Delete the route. diff --git a/felix/dataplane/linux/wireguard_mgr.go b/felix/dataplane/linux/wireguard_mgr.go index e6d1932a740..5fdf9718f46 100644 --- a/felix/dataplane/linux/wireguard_mgr.go +++ b/felix/dataplane/linux/wireguard_mgr.go @@ -42,7 +42,7 @@ type wireguardManager struct { ipVersion uint8 } -type WireguardStatusUpdateCallback func(ipVersion uint8, id interface{}, status string) +type WireguardStatusUpdateCallback func(ipVersion uint8, id any, status string) func newWireguardManager( wireguardRouteTable *wireguard.Wireguard, @@ -59,7 +59,7 @@ func newWireguardManager( } } -func (m *wireguardManager) OnUpdate(protoBufMsg interface{}) { +func (m *wireguardManager) OnUpdate(protoBufMsg any) { logCtx := log.WithField("ipVersion", m.ipVersion) logCtx.WithField("msg", protoBufMsg).Debug("Received message") switch msg := protoBufMsg.(type) { diff --git a/felix/dataplane/linux/xdp_state.go b/felix/dataplane/linux/xdp_state.go index 4b2f80c7d9c..14bbfa13ca7 100644 --- a/felix/dataplane/linux/xdp_state.go +++ b/felix/dataplane/linux/xdp_state.go @@ -17,6 +17,7 @@ package intdataplane import ( "fmt" "net" + "slices" "strings" "time" @@ -132,7 +133,7 @@ func membersToSet(members []string) set.Set[string] { return membersSet } -func (x *xdpState) OnUpdate(protoBufMsg interface{}) { +func (x *xdpState) OnUpdate(protoBufMsg any) { log.WithField("msg", protoBufMsg).Debug("Received message") switch msg := protoBufMsg.(type) { case *proto.IPSetDeltaUpdate: @@ -199,7 +200,7 @@ func (x *xdpState) ResyncIfNeeded(ipsSourceV4 ipsetsSource) error { } success := false - for i := 0; i < 10; i++ { + for i := range 10 { if i > 0 { log.Info("Retrying after an XDP update failure...") } @@ -346,15 +347,13 @@ func (i *ipsetIDsToMembers) AddMembers(setID string, members set.Set[string]) { return } if rs, ok := i.pendingReplaces[setID]; ok { - members.Iter(func(member string) error { + for member := range members.All() { rs.Add(member) - return nil - }) + } } else { - members.Iter(func(member string) error { + for member := range members.All() { safeAdd(i.pendingAdds, setID, member) - return nil - }) + } } } @@ -364,15 +363,13 @@ func (i *ipsetIDsToMembers) RemoveMembers(setID string, members set.Set[string]) return } if rs, ok := i.pendingReplaces[setID]; ok { - members.Iter(func(member string) error { + for member := range members.All() { rs.Discard(member) - return nil - }) + } } else { - members.Iter(func(member string) error { + for member := range members.All() { safeAdd(i.pendingDeletions, setID, member) - return nil - }) + } } } @@ -402,25 +399,22 @@ func (i *ipsetIDsToMembers) UpdateCache() { cachedSetIDs.Add(setID) } - cachedSetIDs.Iter(func(setID string) error { + for setID := range cachedSetIDs.All() { if m, ok := i.pendingReplaces[setID]; ok { i.cache[setID] = m } else { if m, ok := i.pendingDeletions[setID]; ok { - m.Iter(func(member string) error { + for member := range m.All() { i.cache[setID].Discard(member) - return nil - }) + } } if m, ok := i.pendingAdds[setID]; ok { - m.Iter(func(member string) error { + for member := range m.All() { i.cache[setID].Add(member) - return nil - }) + } } } - return nil - }) + } // flush everything i.pendingReplaces = make(map[string]set.Set[string]) @@ -513,13 +507,7 @@ func (s *xdpIPState) newXDPResyncState(bpfLib bpf.BPFDataplane, ipsSource ipsets if err != nil { return false, err } - matched := false - for _, id := range mapIDs { - if mapID == id { - matched = true - break - } - } + matched := slices.Contains(mapIDs, mapID) return !matched, nil }() if err != nil { @@ -548,14 +536,14 @@ func (s *xdpIPState) newXDPResyncState(bpfLib bpf.BPFDataplane, ipsSource ipsets for _, data := range s.newCurrentState.IfaceNameToData { for _, setIDs := range data.PoliciesToSetIDs { var opErr error - setIDs.Iter(func(setID string) error { + for setID := range setIDs.All() { if visited.Contains(setID) { - return nil + continue } members, err := s.getIPSetMembers(setID, ipsSource) if err != nil { opErr = err - return set.StopIteration + break } s.logCxt.WithFields(log.Fields{ "setID": setID, @@ -563,8 +551,7 @@ func (s *xdpIPState) newXDPResyncState(bpfLib bpf.BPFDataplane, ipsSource ipsets }).Debug("Information about ipset members.") ipsetMembers[setID] = members visited.Add(setID) - return nil - }) + } if opErr != nil { return nil, opErr } @@ -578,12 +565,7 @@ func (s *xdpIPState) newXDPResyncState(bpfLib bpf.BPFDataplane, ipsSource ipsets } func isValidMode(mode bpf.XDPMode, xdpModes []bpf.XDPMode) bool { - for _, xdpMode := range xdpModes { - if xdpMode == mode { - return true - } - } - return false + return slices.Contains(xdpModes, mode) } func (s *xdpIPState) getIPSetMembers(setID string, ipsSource ipsetsSource) (set.Set[string], error) { @@ -642,7 +624,7 @@ func (s *xdpIPState) tryResync(common *xdpStateCommon, ipsSource ipsetsSource) e // In case of mismatched maps, only the program gets replaced. func (s *xdpIPState) fixupXDPProgramAndMapConsistency(resyncState *xdpResyncState) { ifaces := s.getIfaces(resyncState, giNS|giWX|giIX|giUX|giWM|giCM|giRM) - ifaces.Iter(func(iface string) error { + for iface := range ifaces.All() { shouldHaveXDP := func() bool { if data, ok := s.newCurrentState.IfaceNameToData[iface]; ok { return data.NeedsXDP() @@ -784,8 +766,7 @@ func (s *xdpIPState) fixupXDPProgramAndMapConsistency(resyncState *xdpResyncStat "createMap": s.bpfActions.CreateMap.Contains(iface), "removeMap": s.bpfActions.RemoveMap.Contains(iface), }).Debug("Resync - finished fixing XDP program and map consistency.") - return nil - }) + } } // fixupBlocklistContents ensures that contents of the BPF maps are in @@ -801,7 +782,7 @@ func (s *xdpIPState) fixupXDPProgramAndMapConsistency(resyncState *xdpResyncStat // maps on a member level. func (s *xdpIPState) fixupBlocklistContents(resyncState *xdpResyncState) { ifaces := s.getIfaces(resyncState, giNS) - ifaces.Iter(func(iface string) error { + for iface := range ifaces.All() { createMap := s.bpfActions.CreateMap.Contains(iface) s.logCxt.WithFields(log.Fields{ "iface": iface, @@ -822,8 +803,7 @@ func (s *xdpIPState) fixupBlocklistContents(resyncState *xdpResyncState) { "membersToAdd": s.bpfActions.MembersToAdd[iface], "membersToDrop": s.bpfActions.MembersToDrop[iface], }).Debug("Resync - finished fixing map contents.") - return nil - }) + } for _, m := range []map[string]map[string]uint32{s.bpfActions.AddToMap, s.bpfActions.RemoveFromMap} { for iface := range m { if !ifaces.Contains(iface) { @@ -851,10 +831,9 @@ func (s *xdpIPState) fixupBlocklistContentsExistingMap(resyncState *xdpResyncSta "wantedRefCount": refCount, }).Panic("Resync - set id missing from ip set members in resync state!") } - resyncState.ipsetMembers[setID].Iter(func(member string) error { + for member := range resyncState.ipsetMembers[setID].All() { membersInNS[member] += refCount - return nil - }) + } } for mapKey, actualRefCount := range membersInBpfMap { member := mapKey.ToIPNet().String() @@ -900,10 +879,9 @@ func (s *xdpIPState) getSetIDToRefCountFromNewState(iface string) map[string]uin setIDToRefCount := make(map[string]uint32) if data, ok := s.newCurrentState.IfaceNameToData[iface]; ok { for _, setIDs := range data.PoliciesToSetIDs { - setIDs.Iter(func(setID string) error { + for setID := range setIDs.All() { setIDToRefCount[setID] += 1 - return nil - }) + } } } return setIDToRefCount @@ -930,10 +908,6 @@ const ( func (s *xdpIPState) getIfaces(resyncState *xdpResyncState, flags IfaceFlags) set.Set[string] { ifaces := set.New[string]() - addFromSet := func(item string) error { - ifaces.Add(item) - return nil - } if flags&giNS == giNS { for iface, data := range s.newCurrentState.IfaceNameToData { if data.NeedsXDP() { @@ -942,10 +916,14 @@ func (s *xdpIPState) getIfaces(resyncState *xdpResyncState, flags IfaceFlags) se } } if flags&giIX == giIX { - s.bpfActions.InstallXDP.Iter(addFromSet) + for item := range s.bpfActions.InstallXDP.All() { + ifaces.Add(item) + } } if flags&giUX == giUX { - s.bpfActions.UninstallXDP.Iter(addFromSet) + for item := range s.bpfActions.UninstallXDP.All() { + ifaces.Add(item) + } } if flags&giWX == giWX { for iface := range resyncState.ifacesWithProgs { @@ -953,10 +931,14 @@ func (s *xdpIPState) getIfaces(resyncState *xdpResyncState, flags IfaceFlags) se } } if flags&giCM == giCM { - s.bpfActions.CreateMap.Iter(addFromSet) + for item := range s.bpfActions.CreateMap.All() { + ifaces.Add(item) + } } if flags&giRM == giRM { - s.bpfActions.RemoveMap.Iter(addFromSet) + for item := range s.bpfActions.RemoveMap.All() { + ifaces.Add(item) + } } if flags&giWM == giWM { for iface := range resyncState.ifacesWithMaps { @@ -1070,7 +1052,7 @@ func (s *xdpIPState) processPendingDiffState(epSource endpointsSource) { } // dropped ifaces - pds.IfaceNamesToDrop.Iter(func(ifName string) error { + for ifName := range pds.IfaceNamesToDrop.All() { s.logCxt.WithField("iface", ifName).Debug("Iface is gone.") dropXDP := false @@ -1084,9 +1066,7 @@ func (s *xdpIPState) processPendingDiffState(epSource endpointsSource) { delete(newCs.IfaceNameToData, ifName) processedIfaces.Add(ifName) - - return nil - }) + } // Host Endpoints that changed for ifaceName, newEpID := range pds.IfaceEpIDChange { @@ -1103,7 +1083,7 @@ func (s *xdpIPState) processPendingDiffState(epSource endpointsSource) { // CHANGES IN HOST ENDPOINTS // Host Endpoints that were updated - pds.UpdatedHostEndpoints.Iter(func(hepID types.HostEndpointID) error { + for hepID := range pds.UpdatedHostEndpoints.All() { s.logCxt.WithField("hostEpId", hepID.String()).Debug("Host endpoint has changed.") for ifaceName, data := range cs.IfaceNameToData { if processedIfaces.Contains(ifaceName) { @@ -1122,23 +1102,14 @@ func (s *xdpIPState) processPendingDiffState(epSource endpointsSource) { s.processHostEndpointChange(ifaceName, &data, hepID, rawHep[hepID], changeInMaps) processedIfaces.Add(ifaceName) } - - return nil - }) - - // Host Endpoints that were removed - pds.RemovedHostEndpoints.Iter(func(hepID types.HostEndpointID) error { - // XXX do nothing - return nil - }) + } // CHANGES IN POLICIES // Policies that should be removed - pds.PoliciesToRemove.Iter(func(policyID types.PolicyID) error { + for policyID := range pds.PoliciesToRemove.All() { delete(newCs.XDPEligiblePolicies, policyID) - return nil - }) + } // Policies that should be updated ifacesWithUpdatedPolicies := set.New[string]() @@ -1153,13 +1124,7 @@ func (s *xdpIPState) processPendingDiffState(epSource endpointsSource) { continue } hep := rawHep[data.EpID] - foundPolicyID := false - for _, hepPolicyID := range getPolicyIDs(hep) { - if hepPolicyID == policyID { - foundPolicyID = true - break - } - } + foundPolicyID := slices.Contains(getPolicyIDs(hep), policyID) if !foundPolicyID { s.logCxt.WithFields(log.Fields{ "policyID": policyID, @@ -1182,10 +1147,9 @@ func (s *xdpIPState) processPendingDiffState(epSource endpointsSource) { }).Debug("Considering old set ID of policy.") if oldSetIDs != nil { // it means that the old version of the policy was optimized - oldSetIDs.Iter(func(setID string) error { + for setID := range oldSetIDs.All() { m[setID] -= 1 - return nil - }) + } } if rules != nil { // this means that new policy can be optimized @@ -1203,10 +1167,9 @@ func (s *xdpIPState) processPendingDiffState(epSource endpointsSource) { "policyID": policyID.String(), }).Info("Policy will be optimized.") - newSetIDs.Iter(func(setID string) error { + for setID := range newSetIDs.All() { m[setID] += 1 - return nil - }) + } newCs.IfaceNameToData[ifaceName].PoliciesToSetIDs[policyID] = newSetIDs } else { s.logCxt.WithFields(log.Fields{ @@ -1231,7 +1194,7 @@ func (s *xdpIPState) processPendingDiffState(epSource endpointsSource) { } } - ifacesWithUpdatedPolicies.Iter(func(ifaceName string) error { + for ifaceName := range ifacesWithUpdatedPolicies.All() { oldData := cs.IfaceNameToData[ifaceName] newData := newCs.IfaceNameToData[ifaceName] oldNeedsXDP := oldData.NeedsXDP() @@ -1243,8 +1206,7 @@ func (s *xdpIPState) processPendingDiffState(epSource endpointsSource) { ba.InstallXDP.Add(ifaceName) ba.CreateMap.Add(ifaceName) } - return nil - }) + } // populate map changes for ifaceName, ips := range changeInMaps { @@ -1279,10 +1241,9 @@ func dumpSetToString(s set.Set[string]) string { return "" } strs := make([]string, 0, s.Len()) - s.Iter(func(item string) error { + for item := range s.All() { strs = append(strs, fmt.Sprintf("%v", item)) - return nil - }) + } return strings.Join(strs, ", ") } @@ -1290,10 +1251,9 @@ func (s *xdpIPState) processHostEndpointChange(ifaceName string, oldData *xdpIfa policiesToSetIDs := make(map[types.PolicyID]set.Set[string] /**/) oldSetIDs := make(map[string]int) for _, setIDs := range oldData.PoliciesToSetIDs { - setIDs.Iter(func(setID string) error { + for setID := range setIDs.All() { oldSetIDs[setID] += 1 - return nil - }) + } } newPolicyIDs := getPolicyIDs(newEP) @@ -1318,10 +1278,9 @@ func (s *xdpIPState) processHostEndpointChange(ifaceName string, oldData *xdpIfa rulesSetIDs := getSetIDs(rules) policiesToSetIDs[policyID] = rulesSetIDs - rulesSetIDs.Iter(func(setID string) error { + for setID := range rulesSetIDs.All() { newSetIDs[setID] += 1 - return nil - }) + } } s.logCxt.WithFields(log.Fields{ @@ -1361,12 +1320,8 @@ func getPolicyIDs(hep *proto.HostEndpoint) []types.PolicyID { var policyIDs []types.PolicyID // we handle Untracked policy only for _, tier := range hep.GetUntrackedTiers() { - for _, policyName := range tier.IngressPolicies { - policyID := types.PolicyID{ - Tier: tier.Name, - Name: policyName, - } - + for _, policy := range tier.IngressPolicies { + policyID := types.ProtoToPolicyID(policy) policyIDs = append(policyIDs, policyID) // TODO: For now we only support XDP // optimization of only the first untracked @@ -1540,15 +1495,14 @@ func (s *xdpIPState) cleanupCache() { for setID := range s.ipsetIDsToMembers.pendingDeletions { setIDs.Add(setID) } - setIDs.Iter(func(setID string) error { + for setID := range setIDs.All() { if !s.isSetIDInCurrentState(setID) { delete(s.ipsetIDsToMembers.cache, setID) delete(s.ipsetIDsToMembers.pendingReplaces, setID) delete(s.ipsetIDsToMembers.pendingAdds, setID) delete(s.ipsetIDsToMembers.pendingDeletions, setID) } - return nil - }) + } } func (s *xdpIPState) isSetIDInCurrentState(setID string) bool { @@ -1644,12 +1598,11 @@ func (s *xdpIPState) getMemberChanges() map[string]memberChanges { func setDifference[T comparable](a, b set.Set[T]) set.Set[T] { result := set.New[T]() - a.Iter(func(item T) error { + for item := range a.All() { if !b.Contains(item) { result.Add(item) } - return nil - }) + } return result } @@ -1800,7 +1753,7 @@ func (a *xdpBPFActions) apply(memberCache *xdpMemberCache, ipsetIDsToMembers *ip // had generic xdp enabled. allXDPModes := getXDPModes(true) logCxt.Debug("Processing BPF actions.") - a.UninstallXDP.Iter(func(iface string) error { + for iface := range a.UninstallXDP.All() { var removeErrs []error logCxt.WithField("iface", iface).Debug("Removing XDP programs.") for _, mode := range allXDPModes { @@ -1816,34 +1769,31 @@ func (a *xdpBPFActions) apply(memberCache *xdpMemberCache, ipsetIDsToMembers *ip // Only report an error if _all_ of the mode-specific removals failed. if len(removeErrs) == len(allXDPModes) { opErr = fmt.Errorf("failed to remove XDP program from %s: %v", iface, removeErrs) - return set.StopIteration + break } - return nil - }) + } if opErr != nil { return opErr } - a.RemoveMap.Iter(func(iface string) error { + for iface := range a.RemoveMap.All() { logCxt.WithField("iface", iface).Debug("Removing BPF blocklist map.") if err := memberCache.bpfLib.RemoveCIDRMap(iface, memberCache.GetFamily()); err != nil { opErr = err - return set.StopIteration + break } - return nil - }) + } if opErr != nil { return opErr } - a.CreateMap.Iter(func(iface string) error { + for iface := range a.CreateMap.All() { logCxt.WithField("iface", iface).Debug("Creating a BPF blocklist map.") if _, err := memberCache.bpfLib.NewCIDRMap(iface, memberCache.GetFamily()); err != nil { opErr = err - return set.StopIteration + break } - return nil - }) + } if opErr != nil { return opErr } @@ -1909,7 +1859,7 @@ func (a *xdpBPFActions) apply(memberCache *xdpMemberCache, ipsetIDsToMembers *ip } } - a.InstallXDP.Iter(func(iface string) error { + for iface := range a.InstallXDP.All() { logCxt.WithField("iface", iface).Debug("Loading XDP program.") var loadErrs []error for _, mode := range xdpModes { @@ -1926,10 +1876,9 @@ func (a *xdpBPFActions) apply(memberCache *xdpMemberCache, ipsetIDsToMembers *ip } if loadErrs != nil { opErr = fmt.Errorf("failed to load XDP program from %s: %v", iface, loadErrs) - return set.StopIteration + break } - return nil - }) + } if opErr != nil { return opErr } @@ -2003,10 +1952,9 @@ func convertMembersToMasked(members set.Set[string], setType ipsets.IPSetType) s switch setType { case ipsets.IPSetTypeHashIP: newMembers := set.New[string]() - members.Iter(func(member string) error { + for member := range members.All() { newMembers.Add(member + "/32") - return nil - }) + } return newMembers case ipsets.IPSetTypeHashNet: return members @@ -2281,13 +2229,12 @@ type memberIterSet struct { func (m *memberIterSet) Iter(f func(member string, refCount uint32) error) error { var opErr error - m.members.Iter(func(member string) error { + for member := range m.members.All() { if err := f(member, m.refCount); err != nil { opErr = err - return set.StopIteration + break } - return nil - }) + } return opErr } diff --git a/felix/dataplane/linux/xdp_state_test.go b/felix/dataplane/linux/xdp_state_test.go index c3c627150d9..3c5bc30d64e 100644 --- a/felix/dataplane/linux/xdp_state_test.go +++ b/felix/dataplane/linux/xdp_state_test.go @@ -16,12 +16,13 @@ package intdataplane import ( "fmt" + "maps" "reflect" "strings" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/calico/felix/bpf" "github.com/projectcalico/calico/felix/ipsets" @@ -125,7 +126,7 @@ var knownProtoRuleFields = set.From( ) func testAllProtoRuleFieldsAreKnown() { - t := reflect.TypeOf(proto.Rule{}) + t := reflect.TypeFor[proto.Rule]() for i := 0; i < t.NumField(); i++ { name := t.Field(i).Name // skip protobuf fields @@ -141,22 +142,22 @@ type testCBEvent interface { } type updatePolicyType struct { - policyID string - inRules []*proto.Rule + policyName string + inRules []*proto.Rule } func (up *updatePolicyType) Do(ipState *xdpIPState) { - policyID := types.PolicyID{Tier: "default", Name: up.policyID} - policy := &proto.Policy{InboundRules: up.inRules} + policyID := types.PolicyID{Name: up.policyName, Kind: v3.KindGlobalNetworkPolicy} + policy := &proto.Policy{Tier: "default", InboundRules: up.inRules} ipState.updatePolicy(policyID, policy) } type removePolicyType struct { - policyID string + policyName string } func (rp *removePolicyType) Do(ipState *xdpIPState) { - policyID := types.PolicyID{Tier: "default", Name: rp.policyID} + policyID := types.PolicyID{Name: rp.policyName, Kind: v3.KindGlobalNetworkPolicy} ipState.removePolicy(policyID) } @@ -276,18 +277,18 @@ func stringPtr(str string) *string { return &str } -func updatePolicy(policyID string, rules ...*proto.Rule) testCBEvent { +func updatePolicy(policyName string, rules ...*proto.Rule) testCBEvent { return &updatePolicyType{ - policyID: policyID, - inRules: rules, + policyName: policyName, + inRules: rules, } } var _ testCBEvent = &removePolicyType{} -func removePolicy(policyID string) testCBEvent { +func removePolicy(policyName string) testCBEvent { return &removePolicyType{ - policyID: policyID, + policyName: policyName, } } @@ -376,8 +377,8 @@ type testIfaceData struct { func testStateToRealState(testIfaces map[string]testIfaceData, testEligiblePolicies map[string][][]string, realState *xdpSystemState) { for ifaceName, ifaceData := range testIfaces { policiesToSetIDs := make(map[types.PolicyID]set.Set[string], len(ifaceData.policiesToSets)) - for policyID, setIDs := range ifaceData.policiesToSets { - protoID := types.PolicyID{Tier: "default", Name: policyID} + for policyName, setIDs := range ifaceData.policiesToSets { + protoID := types.PolicyID{Name: policyName, Kind: v3.KindGlobalNetworkPolicy} setIDsSet := set.FromArray(setIDs) policiesToSetIDs[protoID] = setIDsSet } @@ -387,7 +388,7 @@ func testStateToRealState(testIfaces map[string]testIfaceData, testEligiblePolic } } for policyID, testRules := range testEligiblePolicies { - protoID := types.PolicyID{Tier: "default", Name: policyID} + protoID := types.PolicyID{Name: policyID, Kind: v3.KindGlobalNetworkPolicy} rules := make([]xdpRule, 0, len(testRules)) for _, setIDs := range testRules { rules = append(rules, xdpRule{ @@ -437,7 +438,7 @@ var _ = Describe("XDP state", func() { type testStruct struct { currentState map[string]testIfaceData eligiblePolicies map[string][][]string - endpoints map[string][]string + endpoints map[string][]*proto.PolicyID events []testCBEvent actions *bpfActions newCurrentState map[string]testIfaceData @@ -509,8 +510,8 @@ var _ = Describe("XDP state", func() { Entry("XDP program gets installed on an interface", testStruct{ // nothing in current state // no eligible policies - endpoints: map[string][]string{ - "ep": {"policy"}, + endpoints: map[string][]*proto.PolicyID{ + "ep": {{Name: "policy", Kind: v3.KindGlobalNetworkPolicy}}, }, events: []testCBEvent{ updatePolicy("policy", denyRule("ipset")), @@ -538,8 +539,8 @@ var _ = Describe("XDP state", func() { Entry("nothing gets installed on an interface if policy is not optimizable", testStruct{ // nothing in current state // no eligible policies - endpoints: map[string][]string{ - "ep": {"policy"}, + endpoints: map[string][]*proto.PolicyID{ + "ep": {{Name: "policy", Kind: v3.KindGlobalNetworkPolicy}}, }, events: []testCBEvent{ updatePolicy("policy", allowRule("ipset")), @@ -566,8 +567,8 @@ var _ = Describe("XDP state", func() { eligiblePolicies: map[string][][]string{ "policy": {{"ipset"}}, }, - endpoints: map[string][]string{ - "ep": {"policy"}, + endpoints: map[string][]*proto.PolicyID{ + "ep": {{Name: "policy", Kind: v3.KindGlobalNetworkPolicy}}, }, events: []testCBEvent{ updatePolicy("policy", denyRule()), @@ -596,7 +597,7 @@ var _ = Describe("XDP state", func() { eligiblePolicies: map[string][][]string{ "policy": {{"ipset"}}, }, - endpoints: map[string][]string{ + endpoints: map[string][]*proto.PolicyID{ "ep": {}, }, events: []testCBEvent{ @@ -627,7 +628,7 @@ var _ = Describe("XDP state", func() { eligiblePolicies: map[string][][]string{ "policy": {{"ipset"}}, }, - endpoints: map[string][]string{ + endpoints: map[string][]*proto.PolicyID{ "ep": {}, }, events: []testCBEvent{ @@ -650,8 +651,8 @@ var _ = Describe("XDP state", func() { }, }, // no eligible policies - endpoints: map[string][]string{ - "ep": {"policy"}, + endpoints: map[string][]*proto.PolicyID{ + "ep": {{Name: "policy", Kind: v3.KindGlobalNetworkPolicy}}, }, events: []testCBEvent{ updatePolicy("policy", denyRule("ipset")), @@ -683,9 +684,9 @@ var _ = Describe("XDP state", func() { }, }, // no eligible policies - endpoints: map[string][]string{ - "ep": {"policy"}, - "ep2": {"policy2"}, + endpoints: map[string][]*proto.PolicyID{ + "ep": {{Name: "policy", Kind: v3.KindGlobalNetworkPolicy}}, + "ep2": {{Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}}, }, events: []testCBEvent{ updatePolicy("policy2", denyRule("ipset2")), @@ -722,9 +723,9 @@ var _ = Describe("XDP state", func() { eligiblePolicies: map[string][][]string{ "policy2": {{"ipset2"}}, }, - endpoints: map[string][]string{ - "ep": {"policy"}, - "ep2": {"policy2"}, + endpoints: map[string][]*proto.PolicyID{ + "ep": {{Name: "policy", Kind: v3.KindGlobalNetworkPolicy}}, + "ep2": {{Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}}, }, events: []testCBEvent{ updatePolicy("policy2", denyRule("ipset2")), @@ -757,9 +758,9 @@ var _ = Describe("XDP state", func() { "policy": {{"ipset"}}, "policy2": {{"ipset2"}}, }, - endpoints: map[string][]string{ - "ep": {"policy"}, - "ep2": {"policy2"}, + endpoints: map[string][]*proto.PolicyID{ + "ep": {{Name: "policy", Kind: v3.KindGlobalNetworkPolicy}}, + "ep2": {{Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}}, }, events: []testCBEvent{ updateInterface("iface", "ep2"), @@ -798,8 +799,8 @@ var _ = Describe("XDP state", func() { "policy": {{"ipset"}}, "policy2": {{"ipset2"}}, }, - endpoints: map[string][]string{ - "ep": {"policy2"}, + endpoints: map[string][]*proto.PolicyID{ + "ep": {{Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}}, }, events: []testCBEvent{ updateHostEndpoint("ep"), @@ -837,8 +838,8 @@ var _ = Describe("XDP state", func() { eligiblePolicies: map[string][][]string{ "policy": {{"ipset"}}, }, - endpoints: map[string][]string{ - "ep": {"policy"}, + endpoints: map[string][]*proto.PolicyID{ + "ep": {{Name: "policy", Kind: v3.KindGlobalNetworkPolicy}}, }, events: []testCBEvent{ updatePolicy("policy", denyRule("ipset2")), @@ -885,9 +886,9 @@ var _ = Describe("XDP state", func() { "policy": {{"ipset"}}, "policy2": {{"ipset2"}}, }, - endpoints: map[string][]string{ - "ep": {"policy"}, - "ep2": {"policy2"}, + endpoints: map[string][]*proto.PolicyID{ + "ep": {{Name: "policy", Kind: v3.KindGlobalNetworkPolicy}}, + "ep2": {{Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}}, }, events: []testCBEvent{ updateInterface("iface", "ep2"), @@ -933,8 +934,8 @@ var _ = Describe("XDP state", func() { eligiblePolicies: map[string][][]string{ "policy": {{"ipset"}}, }, - endpoints: map[string][]string{ - "ep": {"policy2"}, + endpoints: map[string][]*proto.PolicyID{ + "ep": {{Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}}, }, events: []testCBEvent{ updatePolicy("policy2", denyRule("ipset2")), @@ -982,9 +983,9 @@ var _ = Describe("XDP state", func() { "policy2": {{"ipset2"}}, "policy3": {{"ipset3"}}, }, - endpoints: map[string][]string{ - "ep": {"policy3"}, - "ep2": {"policy2"}, + endpoints: map[string][]*proto.PolicyID{ + "ep": {{Name: "policy3", Kind: v3.KindGlobalNetworkPolicy}}, + "ep2": {{Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}}, }, events: []testCBEvent{ updateHostEndpoint("ep"), @@ -1043,9 +1044,9 @@ var _ = Describe("XDP state", func() { "policy2": {{"ipset2"}}, "policy3": {{"ipset3"}}, }, - endpoints: map[string][]string{ - "ep": {"policy3"}, - "ep2": {"policy2"}, + endpoints: map[string][]*proto.PolicyID{ + "ep": {{Name: "policy3", Kind: v3.KindGlobalNetworkPolicy}}, + "ep2": {{Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}}, }, events: []testCBEvent{ updateHostEndpoint("ep"), @@ -1098,8 +1099,8 @@ var _ = Describe("XDP state", func() { eligiblePolicies: map[string][][]string{ "policy": {{"ipset"}}, }, - endpoints: map[string][]string{ - "ep": {"policy"}, + endpoints: map[string][]*proto.PolicyID{ + "ep": {{Name: "policy", Kind: v3.KindGlobalNetworkPolicy}}, }, events: []testCBEvent{ updatePolicy("policy", denyRule("ipset")), @@ -1129,8 +1130,8 @@ var _ = Describe("XDP state", func() { eligiblePolicies: map[string][][]string{ "policy": {{"ipset"}}, }, - endpoints: map[string][]string{ - "ep": {"policy"}, + endpoints: map[string][]*proto.PolicyID{ + "ep": {{Name: "policy", Kind: v3.KindGlobalNetworkPolicy}}, }, events: []testCBEvent{ updateHostEndpoint("ep"), @@ -1160,8 +1161,8 @@ var _ = Describe("XDP state", func() { eligiblePolicies: map[string][][]string{ "policy": {{"ipset"}}, }, - endpoints: map[string][]string{ - "ep": {"policy"}, + endpoints: map[string][]*proto.PolicyID{ + "ep": {{Name: "policy", Kind: v3.KindGlobalNetworkPolicy}}, }, events: []testCBEvent{ updateInterface("iface", "ep"), @@ -1191,8 +1192,8 @@ var _ = Describe("XDP state", func() { eligiblePolicies: map[string][][]string{ "policy": {{"ipset"}}, }, - endpoints: map[string][]string{ - "ep": {"policy"}, + endpoints: map[string][]*proto.PolicyID{ + "ep": {{Name: "policy", Kind: v3.KindGlobalNetworkPolicy}}, }, events: []testCBEvent{ updatePolicy("policy2", allowRule("ipset2")), @@ -1222,9 +1223,9 @@ var _ = Describe("XDP state", func() { eligiblePolicies: map[string][][]string{ "policy": {{"ipset"}}, }, - endpoints: map[string][]string{ - "ep": {"policy"}, - "ep2": {"policy2"}, + endpoints: map[string][]*proto.PolicyID{ + "ep": {{Name: "policy", Kind: v3.KindGlobalNetworkPolicy}}, + "ep2": {{Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}}, }, events: []testCBEvent{ updateHostEndpoint("ep2"), @@ -1261,9 +1262,9 @@ var _ = Describe("XDP state", func() { "policy": {{"ipset"}}, "policy2": {{"ipset2"}}, }, - endpoints: map[string][]string{ - "ep": {"policy"}, - "ep2": {"policy2"}, + endpoints: map[string][]*proto.PolicyID{ + "ep": {{Name: "policy", Kind: v3.KindGlobalNetworkPolicy}}, + "ep2": {{Name: "policy2", Kind: v3.KindGlobalNetworkPolicy}}, }, events: []testCBEvent{ addMembersIPSet("ipset", "member1"), @@ -1315,8 +1316,8 @@ var _ = Describe("XDP state", func() { "policy": {{"ipset"}}, "policy2": {{"ipset2"}}, }, - endpoints: map[string][]string{ - "ep": {"policy"}, + endpoints: map[string][]*proto.PolicyID{ + "ep": {{Name: "policy", Kind: v3.KindGlobalNetworkPolicy}}, }, events: []testCBEvent{ removeHostEndpoint("ep"), @@ -1371,9 +1372,9 @@ var _ = Describe("XDP state", func() { "policy": {{"ipset"}}, "policy2": {{"ipset2"}}, }, - endpoints: map[string][]string{ - "ep": {"policy"}, - "ep2": {"policy"}, + endpoints: map[string][]*proto.PolicyID{ + "ep": {{Name: "policy", Kind: v3.KindGlobalNetworkPolicy}}, + "ep2": {{Name: "policy", Kind: v3.KindGlobalNetworkPolicy}}, }, events: []testCBEvent{ removePolicy("policy"), @@ -1425,7 +1426,7 @@ var _ = Describe("XDP state", func() { // "policy": [][]string{[]string{"ipset"}}, // "policy2": [][]string{[]string{"ipset2"}}, // }, - // endpoints: map[string][]string{ + // endpoints: map[string][]*proto.PolicyID{ // "ep": []string{"policy", "policy2"}, // }, // events: []testCBEvent{ @@ -1461,7 +1462,7 @@ var _ = Describe("XDP state", func() { // eligiblePolicies: map[string][][]string{ // "policy": [][]string{[]string{"ipset"}}, // }, - // endpoints: map[string][]string{ + // endpoints: map[string][]*proto.PolicyID{ // "ep": []string{"policy", "policy2"}, // }, // events: []testCBEvent{ @@ -1487,7 +1488,7 @@ var _ = Describe("XDP state", func() { // }, // }), Entry("invalid policies", func() testStruct { - modifiedRule := func(field string, value interface{}) *proto.Rule { + modifiedRule := func(field string, value any) *proto.Rule { rule := denyRule("ipset") rulePtrValue := reflect.ValueOf(rule) ruleValue := rulePtrValue.Elem() @@ -1640,7 +1641,7 @@ var _ = Describe("XDP state", func() { } ts := testStruct{ currentState: make(map[string]testIfaceData, len(policyInfos)), - endpoints: make(map[string][]string, len(policyInfos)), + endpoints: make(map[string][]*proto.PolicyID, len(policyInfos)), events: make([]testCBEvent, 0, len(policyInfos)), // no actions expected newCurrentState: make(map[string]testIfaceData, len(policyInfos)), @@ -1652,7 +1653,7 @@ var _ = Describe("XDP state", func() { epID: ep, } ts.currentState[iface] = ifaceData - ts.endpoints[ep] = []string{info.name} + ts.endpoints[ep] = []*proto.PolicyID{{Name: info.name, Kind: v3.KindGlobalNetworkPolicy}} ts.events = append(ts.events, updatePolicy(info.name, info.rule)) ts.newCurrentState[iface] = ifaceData } @@ -2070,26 +2071,26 @@ var _ = Describe("XDP state", func() { hasXDP: true, mapExists: true, mapContents: map[bpf.IPv4Mask]uint32{ - bpf.IPv4Mask{Ip: [4]byte{1, 2, 3, 4}, Mask: 32}: 1, - bpf.IPv4Mask{Ip: [4]byte{2, 3, 4, 5}, Mask: 32}: 1, - bpf.IPv4Mask{Ip: [4]byte{3, 4, 5, 6}, Mask: 32}: 1, + {Ip: [4]byte{1, 2, 3, 4}, Mask: 32}: 1, + {Ip: [4]byte{2, 3, 4, 5}, Mask: 32}: 1, + {Ip: [4]byte{3, 4, 5, 6}, Mask: 32}: 1, }, }, "ifBadMap1": { hasXDP: true, mapExists: true, mapContents: map[bpf.IPv4Mask]uint32{ - bpf.IPv4Mask{Ip: [4]byte{42, 42, 42, 42}, Mask: 32}: 3, - bpf.IPv4Mask{Ip: [4]byte{1, 2, 3, 4}, Mask: 16}: 1, + {Ip: [4]byte{42, 42, 42, 42}, Mask: 32}: 3, + {Ip: [4]byte{1, 2, 3, 4}, Mask: 16}: 1, }, }, "ifBadMap2": { hasXDP: true, mapExists: true, mapContents: map[bpf.IPv4Mask]uint32{ - bpf.IPv4Mask{Ip: [4]byte{1, 2, 3, 4}, Mask: 32}: 3, - bpf.IPv4Mask{Ip: [4]byte{2, 3, 4, 5}, Mask: 32}: 6, - bpf.IPv4Mask{Ip: [4]byte{3, 4, 5, 6}, Mask: 32}: 1, + {Ip: [4]byte{1, 2, 3, 4}, Mask: 32}: 3, + {Ip: [4]byte{2, 3, 4, 5}, Mask: 32}: 6, + {Ip: [4]byte{3, 4, 5, 6}, Mask: 32}: 1, }, }, }, @@ -2719,16 +2720,12 @@ var _ = Describe("XDP state", func() { state.ipV4State.bpfActions.UninstallXDP.AddAll(s.uninstall) state.ipV4State.bpfActions.CreateMap.AddAll(s.create) state.ipV4State.bpfActions.RemoveMap.AddAll(s.remove) - for i, p := range s.withProgs { - resyncState.ifacesWithProgs[i] = p - } - for i, p := range s.withMaps { - resyncState.ifacesWithMaps[i] = p - } + maps.Copy(resyncState.ifacesWithProgs, s.withProgs) + maps.Copy(resyncState.ifacesWithMaps, s.withMaps) for iface, needsXDP := range s.newState { data := xdpIfaceData{} if needsXDP { - policyID := types.PolicyID{Tier: "default", Name: "bar"} + policyID := types.PolicyID{Name: "bar", Kind: v3.KindGlobalNetworkPolicy} endpointID := types.HostEndpointID{EndpointId: "foo"} data.EpID = endpointID data.PoliciesToSetIDs = map[types.PolicyID]set.Set[string]{ diff --git a/felix/dataplane/mock/mock_dataplane.go b/felix/dataplane/mock/mock_dataplane.go index 57ef6251fa7..03a9aee08a3 100644 --- a/felix/dataplane/mock/mock_dataplane.go +++ b/felix/dataplane/mock/mock_dataplane.go @@ -16,10 +16,11 @@ package mock import ( "fmt" + "maps" "reflect" "sync" - "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/v2" //nolint:staticcheck // Ignore ST1001: should not use dot imports . "github.com/onsi/gomega" @@ -106,18 +107,21 @@ func (d *MockDataplane) ActiveUntrackedPolicies() set.Set[types.PolicyID] { return d.activeUntrackedPolicies.Copy() } + func (d *MockDataplane) ActivePreDNATPolicies() set.Set[types.PolicyID] { d.Lock() defer d.Unlock() return d.activePreDNATPolicies.Copy() } + func (d *MockDataplane) ActiveProfiles() set.Set[types.ProfileID] { d.Lock() defer d.Unlock() return d.activeProfiles.Copy() } + func (d *MockDataplane) ActiveVTEPs() set.Set[types.VXLANTunnelEndpointUpdate] { d.Lock() defer d.Unlock() @@ -129,6 +133,7 @@ func (d *MockDataplane) ActiveVTEPs() set.Set[types.VXLANTunnelEndpointUpdate] { return cp } + func (d *MockDataplane) ActiveWireguardEndpoints() set.Set[types.WireguardEndpointUpdate] { d.Lock() defer d.Unlock() @@ -140,6 +145,7 @@ func (d *MockDataplane) ActiveWireguardEndpoints() set.Set[types.WireguardEndpoi return cp } + func (d *MockDataplane) ActiveWireguardV6Endpoints() set.Set[types.WireguardEndpointV6Update] { d.Lock() defer d.Unlock() @@ -151,6 +157,7 @@ func (d *MockDataplane) ActiveWireguardV6Endpoints() set.Set[types.WireguardEndp return cp } + func (d *MockDataplane) ActiveHostMetadataV4V6() map[string]*proto.HostMetadataV4V6Update { d.Lock() defer d.Unlock() @@ -162,6 +169,7 @@ func (d *MockDataplane) ActiveHostMetadataV4V6() map[string]*proto.HostMetadataV return cp } + func (d *MockDataplane) ActiveRoutes() set.Set[types.RouteUpdate] { d.Lock() defer d.Unlock() @@ -208,9 +216,7 @@ func (d *MockDataplane) ServiceAccounts() map[types.ServiceAccountID]*proto.Serv defer d.Unlock() cpy := make(map[types.ServiceAccountID]*proto.ServiceAccountUpdate) - for k, v := range d.serviceAccounts { - cpy[k] = v - } + maps.Copy(cpy, d.serviceAccounts) return cpy } @@ -219,9 +225,7 @@ func (d *MockDataplane) Namespaces() map[types.NamespaceID]*proto.NamespaceUpdat defer d.Unlock() cpy := make(map[types.NamespaceID]*proto.NamespaceUpdate) - for k, v := range d.namespaces { - cpy[k] = v - } + maps.Copy(cpy, d.namespaces) return cpy } @@ -260,9 +264,7 @@ func (d *MockDataplane) Config() map[string]string { return nil } localCopy := map[string]string{} - for k, v := range d.config { - localCopy[k] = v - } + maps.Copy(localCopy, d.config) return localCopy } @@ -289,7 +291,7 @@ func NewMockDataplane() *MockDataplane { return s } -func (d *MockDataplane) OnEvent(event interface{}) { +func (d *MockDataplane) OnEvent(event any) { d.Lock() defer d.Unlock() @@ -298,7 +300,7 @@ func (d *MockDataplane) OnEvent(event interface{}) { evType := reflect.TypeOf(event).String() fmt.Fprintf(ginkgo.GinkgoWriter, " <- Event: %v %v\n", evType, event) Expect(event).NotTo(BeNil()) - Expect(reflect.TypeOf(event).Kind()).To(Equal(reflect.Ptr)) + Expect(reflect.TypeOf(event).Kind()).To(Equal(reflect.Pointer)) // Test wrapping the message for the external dataplane switch event := event.(type) { @@ -386,16 +388,15 @@ func (d *MockDataplane) OnEvent(event interface{}) { var allPolsIDs []types.PolicyID for i, tier := range event.Endpoint.Tiers { tierInfos[i].Name = tier.Name - tierInfos[i].IngressPolicyNames = tier.IngressPolicies - tierInfos[i].EgressPolicyNames = tier.EgressPolicies + tierInfos[i].IngressPolicies = convertPolicyIDs(tier.IngressPolicies) + tierInfos[i].EgressPolicies = convertPolicyIDs(tier.EgressPolicies) // Check that all the policies referenced by the endpoint are already present, which // is one of the guarantees provided by the EventSequencer. - var combinedPolNames []string - combinedPolNames = append(combinedPolNames, tier.IngressPolicies...) - combinedPolNames = append(combinedPolNames, tier.EgressPolicies...) - for _, polName := range combinedPolNames { - polID := types.PolicyID{Tier: tier.Name, Name: polName} + var combinedPolicies []types.PolicyID + combinedPolicies = append(combinedPolicies, convertPolicyIDs(tier.IngressPolicies)...) + combinedPolicies = append(combinedPolicies, convertPolicyIDs(tier.EgressPolicies)...) + for _, polID := range combinedPolicies { allPolsIDs = append(allPolsIDs, polID) Expect(d.activePolicies).To(HaveKey(polID), fmt.Sprintf("Expected policy %v referenced by workload endpoint "+ @@ -429,8 +430,8 @@ func (d *MockDataplane) OnEvent(event interface{}) { tierInfos := make([]TierInfo, len(tiers)) for i, tier := range tiers { tierInfos[i].Name = tier.Name - tierInfos[i].IngressPolicyNames = tier.IngressPolicies - tierInfos[i].EgressPolicyNames = tier.EgressPolicies + tierInfos[i].IngressPolicies = convertPolicyIDs(tier.IngressPolicies) + tierInfos[i].EgressPolicies = convertPolicyIDs(tier.EgressPolicies) } id := hostEpId(types.ProtoToHostEndpointID(event.GetId())) d.endpointToPolicyOrder[id.String()] = tierInfos @@ -439,8 +440,8 @@ func (d *MockDataplane) OnEvent(event interface{}) { uTierInfos := make([]TierInfo, len(uTiers)) for i, tier := range uTiers { uTierInfos[i].Name = tier.Name - uTierInfos[i].IngressPolicyNames = tier.IngressPolicies - uTierInfos[i].EgressPolicyNames = tier.EgressPolicies + uTierInfos[i].IngressPolicies = convertPolicyIDs(tier.IngressPolicies) + uTierInfos[i].EgressPolicies = convertPolicyIDs(tier.EgressPolicies) } d.endpointToUntrackedPolicyOrder[id.String()] = uTierInfos @@ -448,8 +449,8 @@ func (d *MockDataplane) OnEvent(event interface{}) { pTierInfos := make([]TierInfo, len(pTiers)) for i, tier := range pTiers { pTierInfos[i].Name = tier.Name - pTierInfos[i].IngressPolicyNames = tier.IngressPolicies - pTierInfos[i].EgressPolicyNames = tier.EgressPolicies + pTierInfos[i].IngressPolicies = convertPolicyIDs(tier.IngressPolicies) + pTierInfos[i].EgressPolicies = convertPolicyIDs(tier.EgressPolicies) } d.endpointToPreDNATPolicyOrder[id.String()] = pTierInfos case *proto.HostEndpointRemove: @@ -470,20 +471,18 @@ func (d *MockDataplane) OnEvent(event interface{}) { Expect(d.namespaces).To(HaveKey(id)) delete(d.namespaces, id) case *proto.RouteUpdate: - d.activeRoutes.Iter(func(r types.RouteUpdate) error { + for r := range d.activeRoutes.All() { if event.Dst == r.Dst { - return set.RemoveItem + d.activeRoutes.Discard(r) } - return nil - }) + } d.activeRoutes.Add(types.ProtoToRouteUpdate(event)) case *proto.RouteRemove: - d.activeRoutes.Iter(func(r types.RouteUpdate) error { + for r := range d.activeRoutes.All() { if event.Dst == r.Dst { - return set.RemoveItem + d.activeRoutes.Discard(r) } - return nil - }) + } case *proto.VXLANTunnelEndpointUpdate: d.activeVTEPs[event.Node] = types.ProtoToVXLANTunnelEndpointUpdate(event) case *proto.VXLANTunnelEndpointRemove: @@ -518,9 +517,21 @@ func (d *MockDataplane) RawValues() map[string]string { } type TierInfo struct { - Name string - IngressPolicyNames []string - EgressPolicyNames []string + Name string + IngressPolicies []types.PolicyID + EgressPolicies []types.PolicyID +} + +func convertPolicyIDs(in []*proto.PolicyID) []types.PolicyID { + var out []types.PolicyID + for _, pid := range in { + out = append(out, types.PolicyID{ + Kind: pid.Kind, + Name: pid.Name, + Namespace: pid.Namespace, + }) + } + return out } type workloadId types.WorkloadEndpointID diff --git a/felix/dataplane/windows/endpoint_mgr.go b/felix/dataplane/windows/endpoint_mgr.go index 1212b2b6e8e..7412bf67727 100644 --- a/felix/dataplane/windows/endpoint_mgr.go +++ b/felix/dataplane/windows/endpoint_mgr.go @@ -16,6 +16,7 @@ package windataplane import ( "errors" + "fmt" "net" "os" "reflect" @@ -140,7 +141,7 @@ func (m *endpointManager) OnIPSetsUpdate(ipSetId string) { // OnUpdate is called by the main dataplane driver loop during the first phase. It processes // specific types of updates from the datastore. -func (m *endpointManager) OnUpdate(msg interface{}) { +func (m *endpointManager) OnUpdate(msg any) { switch msg := msg.(type) { case *proto.WorkloadEndpointUpdate: log.WithField("workloadEndpointId", msg.Id).Info("Processing WorkloadEndpointUpdate") @@ -151,7 +152,7 @@ func (m *endpointManager) OnUpdate(msg interface{}) { id := types.ProtoToWorkloadEndpointID(msg.GetId()) m.pendingWlEpUpdates[id] = nil case *proto.ActivePolicyUpdate: - if model.PolicyIsStaged(msg.Id.Name) { + if model.KindIsStaged(msg.Id.Kind) { log.WithField("policyID", msg.Id).Debug("Skipping ActivePolicyUpdate with staged policy") return } @@ -256,12 +257,12 @@ func (m *endpointManager) refreshPendingWlEpUpdates(updatedPolicies []string) { continue } - var activePolicyNames []string + var activePolicies []string profilesApply := true if len(workload.Tiers) > 0 { - activePolicyNames = append(activePolicyNames, prependAll(policysets.PolicyNamePrefix, workload.Tiers[0].IngressPolicies)...) - activePolicyNames = append(activePolicyNames, prependAll(policysets.PolicyNamePrefix, workload.Tiers[0].EgressPolicies)...) + activePolicies = append(activePolicies, policyIDsToStrings(policysets.PolicyNamePrefix, workload.Tiers[0].IngressPolicies)...) + activePolicies = append(activePolicies, policyIDsToStrings(policysets.PolicyNamePrefix, workload.Tiers[0].EgressPolicies)...) if len(workload.Tiers[0].IngressPolicies) > 0 && len(workload.Tiers[0].EgressPolicies) > 0 { profilesApply = false @@ -269,11 +270,11 @@ func (m *endpointManager) refreshPendingWlEpUpdates(updatedPolicies []string) { } if profilesApply && len(workload.ProfileIds) > 0 { - activePolicyNames = append(activePolicyNames, prependAll(policysets.ProfileNamePrefix, workload.ProfileIds)...) + activePolicies = append(activePolicies, profileIDsToStrings(policysets.ProfileNamePrefix, workload.ProfileIds)...) } Policies: - for _, policyName := range activePolicyNames { + for _, policyName := range activePolicies { for _, updatedPolicy := range updatedPolicies { if policyName == updatedPolicy { log.WithFields(log.Fields{"policyName": policyName, "endpointId": endpointId}).Info("Endpoint is being marked for policy refresh") @@ -311,10 +312,10 @@ func (m *endpointManager) ProcessPolicyProfileUpdate(policySetId string) { // have already been processed by the various managers and we should now have a complete picture // of the policy/rules to be applied for each pending endpoint. func (m *endpointManager) CompleteDeferredWork() error { - m.pendingIPSetUpdate.Iter(func(id string) error { + for id := range m.pendingIPSetUpdate.All() { m.ProcessIpSetUpdate(id) - return set.RemoveItem - }) + m.pendingIPSetUpdate.Discard(id) + } if m.pendingHostAddrs != nil { log.WithField("update", m.pendingHostAddrs).Debug("Pending host addrs update") @@ -381,14 +382,14 @@ func (m *endpointManager) CompleteDeferredWork() error { if t.Name == names.DefaultTierName { defaultTierIngressAppliesToEP = true } - policyNames := prependAll(policysets.PolicyNamePrefix, t.IngressPolicies) + policyNames := policyIDsToStrings(policysets.PolicyNamePrefix, t.IngressPolicies) ingressRules = append(ingressRules, m.policysetsDataplane.GetPolicySetRules(policyNames, true, endOfTierDrop)) } if len(t.EgressPolicies) > 0 { if t.Name == names.DefaultTierName { defaultTierEgressAppliesToEP = true } - policyNames := prependAll(policysets.PolicyNamePrefix, t.EgressPolicies) + policyNames := policyIDsToStrings(policysets.PolicyNamePrefix, t.EgressPolicies) egressRules = append(egressRules, m.policysetsDataplane.GetPolicySetRules(policyNames, false, endOfTierDrop)) } } @@ -397,12 +398,12 @@ func (m *endpointManager) CompleteDeferredWork() error { // If _no_ policies apply at all, then we fall through to the profiles. Otherwise, there's no way to get // from policies to profiles. if len(ingressRules) == 0 || !defaultTierIngressAppliesToEP { - policyNames := prependAll(policysets.ProfileNamePrefix, workload.ProfileIds) + policyNames := profileIDsToStrings(policysets.ProfileNamePrefix, workload.ProfileIds) ingressRules = append(ingressRules, m.policysetsDataplane.GetPolicySetRules(policyNames, true, true)) } if len(egressRules) == 0 || !defaultTierEgressAppliesToEP { - policyNames := prependAll(policysets.ProfileNamePrefix, workload.ProfileIds) + policyNames := profileIDsToStrings(policysets.ProfileNamePrefix, workload.ProfileIds) egressRules = append(egressRules, m.policysetsDataplane.GetPolicySetRules(policyNames, false, true)) } @@ -589,14 +590,34 @@ func (m *endpointManager) getHnsEndpointId(ip string) (string, error) { return "", ErrUnknownEndpoint } -// prependAll prepends a string to all of the provided input strings -func prependAll(prefix string, in []string) (out []string) { +// profileIDsToStrings converts a list of profile names to their string representation with prefix. +func profileIDsToStrings(prefix string, in []string) (out []string) { for _, s := range in { - out = append(out, prefix+s) + out = append(out, profileIDToString(prefix, s)) } return } +func profileIDToString(prefix string, name string) string { + return fmt.Sprintf("%s%s", prefix, name) +} + +// policyIDsToStrings converts a list of PolicyID to their string representation with prefix. +func policyIDsToStrings(prefix string, in []*proto.PolicyID) []string { + var out []string + for _, id := range in { + out = append(out, policyIDToString(prefix, id)) + } + return out +} + +func policyIDToString(prefix string, id *proto.PolicyID) string { + if id.Namespace != "" { + return fmt.Sprintf("%s%s/%s/%s", prefix, id.Kind, id.Namespace, id.Name) + } + return fmt.Sprintf("%s%s/%s", prefix, id.Kind, id.Name) +} + // loopPollingForInterfaceAddrs periodically checks the IP addresses on the host and sends updates on the channel // when the IPs change. func loopPollingForInterfaceAddrs(c chan []string) { diff --git a/felix/dataplane/windows/flattener.go b/felix/dataplane/windows/flattener.go index 691d7499f04..e575292cdb4 100644 --- a/felix/dataplane/windows/flattener.go +++ b/felix/dataplane/windows/flattener.go @@ -185,7 +185,7 @@ func combinePorts(as string, bs string) (string, error) { func parsePorts(portsStr string) *bitset.BitSet { setOfPorts := bitset.New(2 ^ 16 + 1) - for _, p := range strings.Split(portsStr, ",") { + for p := range strings.SplitSeq(portsStr, ",") { if strings.Contains(p, "-") { // Range parts := strings.Split(p, "-") diff --git a/felix/dataplane/windows/ipsets/ipsets.go b/felix/dataplane/windows/ipsets/ipsets.go index 34dbbb0fc8b..a3aa17e6c90 100644 --- a/felix/dataplane/windows/ipsets/ipsets.go +++ b/felix/dataplane/windows/ipsets/ipsets.go @@ -91,10 +91,9 @@ func (s *IPSets) AddMembers(setID string, newMembers []string) { "setID": setID, "filteredMembers": filteredMembers, }).Debug("Adding new members to IP set") - filteredMembers.Iter(func(m string) error { + for m := range filteredMembers.All() { ipSet.Members.Add(m) - return nil - }) + } s.callbackOnUpdate(setID) } @@ -114,10 +113,9 @@ func (s *IPSets) RemoveMembers(setID string, removedMembers []string) { "filteredMembers": filteredMembers, }).Debug("Removing members from IP set") - filteredMembers.Iter(func(m string) error { + for m := range filteredMembers.All() { ipSet.Members.Discard(m) - return nil - }) + } s.callbackOnUpdate(setID) } @@ -130,10 +128,9 @@ func (s *IPSets) GetIPSetMembers(setID string) []string { return nil } - ipSet.Members.Iter(func(member string) error { + for member := range ipSet.Members.All() { retVal = append(retVal, member) - return nil - }) + } // Note: It is very important that nil is returned if there is no ip in an ipset // so that policy rules related to this ipset won't be populated. diff --git a/felix/dataplane/windows/policy_mgr.go b/felix/dataplane/windows/policy_mgr.go index e739a9b5650..475071b4908 100644 --- a/felix/dataplane/windows/policy_mgr.go +++ b/felix/dataplane/windows/policy_mgr.go @@ -36,28 +36,28 @@ func newPolicyManager(policysets policysets.PolicySetsDataplane) *policyManager // OnUpdate is called by the main dataplane driver loop during the first phase. It processes // specific types of updates from the datastore. -func (m *policyManager) OnUpdate(msg interface{}) { +func (m *policyManager) OnUpdate(msg any) { switch msg := msg.(type) { case *proto.ActivePolicyUpdate: - if model.PolicyIsStaged(msg.Id.Name) { + if model.KindIsStaged(msg.Id.Kind) { log.WithField("policyID", msg.Id).Debug("Skipping ActivePolicyUpdate with staged policy") return } log.WithField("policyID", msg.Id).Info("Processing ActivePolicyUpdate") - m.policysetsDataplane.AddOrReplacePolicySet(policysets.PolicyNamePrefix+msg.Id.Name, msg.Policy) + m.policysetsDataplane.AddOrReplacePolicySet(policyIDToString(policysets.PolicyNamePrefix, msg.Id), msg.Policy) case *proto.ActivePolicyRemove: - if model.PolicyIsStaged(msg.Id.Name) { + if model.KindIsStaged(msg.Id.Kind) { log.WithField("policyID", msg.Id).Debug("Skipping ActivePolicyRemove with staged policy") return } log.WithField("policyID", msg.Id).Info("Processing ActivePolicyRemove") - m.policysetsDataplane.RemovePolicySet(policysets.PolicyNamePrefix + msg.Id.Name) + m.policysetsDataplane.RemovePolicySet(policyIDToString(policysets.PolicyNamePrefix, msg.Id)) case *proto.ActiveProfileUpdate: log.WithField("profileId", msg.Id).Info("Processing ActiveProfileUpdate") - m.policysetsDataplane.AddOrReplacePolicySet(policysets.ProfileNamePrefix+msg.Id.Name, msg.Profile) + m.policysetsDataplane.AddOrReplacePolicySet(profileIDToString(policysets.ProfileNamePrefix, msg.Id.Name), msg.Profile) case *proto.ActiveProfileRemove: log.WithField("profileId", msg.Id).Info("Processing ActiveProfileRemove") - m.policysetsDataplane.RemovePolicySet(policysets.ProfileNamePrefix + msg.Id.Name) + m.policysetsDataplane.RemovePolicySet(profileIDToString(policysets.ProfileNamePrefix, msg.Id.Name)) } } diff --git a/felix/dataplane/windows/policy_mgr_test.go b/felix/dataplane/windows/policy_mgr_test.go index fb06b39afd4..9ac92a99cd4 100644 --- a/felix/dataplane/windows/policy_mgr_test.go +++ b/felix/dataplane/windows/policy_mgr_test.go @@ -18,6 +18,7 @@ import ( "testing" . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/calico/felix/dataplane/windows/hns" "github.com/projectcalico/calico/felix/dataplane/windows/policysets" @@ -38,8 +39,9 @@ func TestPolicyManager(t *testing.T) { // Apply policy update policyMgr.OnUpdate(&proto.ActivePolicyUpdate{ - Id: &proto.PolicyID{Name: "pol1", Tier: "tier1"}, + Id: &proto.PolicyID{Name: "pol1", Kind: v3.KindGlobalNetworkPolicy}, Policy: &proto.Policy{ + Tier: "tier1", InboundRules: []*proto.Rule{ {Action: "deny"}, }, @@ -50,61 +52,61 @@ func TestPolicyManager(t *testing.T) { }) // assertion for ingress rules - Expect(ps.GetPolicySetRules([]string{"policy-pol1"}, true, true)).To(Equal([]*hns.ACLPolicy{ - //policy-pol1 deny rule should be present + Expect(ps.GetPolicySetRules([]string{"policy-GlobalNetworkPolicy/pol1"}, true, true)).To(Equal([]*hns.ACLPolicy{ + // policy-GlobalNetworkPolicy/pol1 deny rule should be present {Type: hns.ACL, Protocol: 256, Action: hns.Block, Direction: hns.In, RuleType: hns.Switch, Priority: 1000}, // Default deny rule. {Type: hns.ACL, Protocol: 256, Action: hns.Block, Direction: hns.In, RuleType: hns.Switch, Priority: 1001}, - }), "unexpected rules returned for ingress rules update for policy-pol1") + }), "unexpected rules returned for ingress rules update for policy-GlobalNetworkPolicy/pol1") // assertion for ingress rules with endOfTierDrop disabled - Expect(ps.GetPolicySetRules([]string{"policy-pol1"}, true, false)).To(Equal([]*hns.ACLPolicy{ - //policy-pol1 deny rule should be present + Expect(ps.GetPolicySetRules([]string{"policy-GlobalNetworkPolicy/pol1"}, true, false)).To(Equal([]*hns.ACLPolicy{ + // policy-GlobalNetworkPolicy/pol1 deny rule should be present {Type: hns.ACL, Protocol: 256, Action: hns.Block, Direction: hns.In, RuleType: hns.Switch, Priority: 1000}, // Default deny rule. {Type: hns.ACL, Protocol: 256, Action: policysets.ActionPass, Direction: hns.In, RuleType: hns.Switch, Priority: 1001}, - }), "unexpected rules returned for ingress rules update for policy-pol1") + }), "unexpected rules returned for ingress rules update for policy-GlobalNetworkPolicy/pol1") // assertion for egress rules - Expect(ps.GetPolicySetRules([]string{"policy-pol1"}, false, true)).To(Equal([]*hns.ACLPolicy{ - //policy-pol1 allow rule should be present + Expect(ps.GetPolicySetRules([]string{"policy-GlobalNetworkPolicy/pol1"}, false, true)).To(Equal([]*hns.ACLPolicy{ + // policy-GlobalNetworkPolicy/pol1 allow rule should be present {Type: hns.ACL, Protocol: 256, Action: hns.Allow, Direction: hns.Out, RuleType: hns.Switch, Priority: 1000}, // Default deny rule. {Type: hns.ACL, Protocol: 256, Action: hns.Block, Direction: hns.Out, RuleType: hns.Switch, Priority: 1001}, - }), "unexpected rules returned for egress rules update for policy-pol1") + }), "unexpected rules returned for egress rules update for policy-GlobalNetworkPolicy/pol1") // assertion for egress rules with endOfTierDrop disabled - Expect(ps.GetPolicySetRules([]string{"policy-pol1"}, false, false)).To(Equal([]*hns.ACLPolicy{ - //policy-pol1 allow rule should be present + Expect(ps.GetPolicySetRules([]string{"policy-GlobalNetworkPolicy/pol1"}, false, false)).To(Equal([]*hns.ACLPolicy{ + // policy-GlobalNetworkPolicy/pol1 allow rule should be present {Type: hns.ACL, Protocol: 256, Action: hns.Allow, Direction: hns.Out, RuleType: hns.Switch, Priority: 1000}, // Default deny rule. {Type: hns.ACL, Protocol: 256, Action: policysets.ActionPass, Direction: hns.Out, RuleType: hns.Switch, Priority: 1001}, - }), "unexpected rules returned for egress rules update for policy-pol1") + }), "unexpected rules returned for egress rules update for policy-GlobalNetworkPolicy/pol1") // remove policy here policyMgr.OnUpdate(&proto.ActivePolicyRemove{ - Id: &proto.PolicyID{Name: "pol1", Tier: "tier1"}, + Id: &proto.PolicyID{Name: "pol1", Kind: v3.KindGlobalNetworkPolicy}, }) // default ingress rule - Expect(ps.GetPolicySetRules([]string{"policy-pol1"}, true, true)).To(Equal([]*hns.ACLPolicy{ + Expect(ps.GetPolicySetRules([]string{"policy-GlobalNetworkPolicy/pol1"}, true, true)).To(Equal([]*hns.ACLPolicy{ {Type: hns.ACL, Protocol: 256, Action: hns.Block, Direction: hns.In, RuleType: hns.Switch, Priority: 1001}, - }), "unexpected rules returned after ActivePolicyRemove event for policy-pol1") + }), "unexpected rules returned after ActivePolicyRemove event for policy-GlobalNetworkPolicy/pol1") // default ingress rule with endOfTierDrop disabled - Expect(ps.GetPolicySetRules([]string{"policy-pol1"}, true, false)).To(Equal([]*hns.ACLPolicy{ + Expect(ps.GetPolicySetRules([]string{"policy-GlobalNetworkPolicy/pol1"}, true, false)).To(Equal([]*hns.ACLPolicy{ {Type: hns.ACL, Protocol: 256, Action: policysets.ActionPass, Direction: hns.In, RuleType: hns.Switch, Priority: 1001}, - }), "unexpected rules returned after ActivePolicyRemove event for policy-pol1") + }), "unexpected rules returned after ActivePolicyRemove event for policy-GlobalNetworkPolicy/pol1") // default egress rule - Expect(ps.GetPolicySetRules([]string{"policy-pol1"}, false, true)).To(Equal([]*hns.ACLPolicy{ + Expect(ps.GetPolicySetRules([]string{"policy-GlobalNetworkPolicy/pol1"}, false, true)).To(Equal([]*hns.ACLPolicy{ {Type: hns.ACL, Protocol: 256, Action: hns.Block, Direction: hns.Out, RuleType: hns.Switch, Priority: 1001}, - }), "unexpected rules returned after ActivePolicyRemove event for policy-pol1") + }), "unexpected rules returned after ActivePolicyRemove event for policy-GlobalNetworkPolicy/pol1") // default egress rule with endOfTierDrop disabled - Expect(ps.GetPolicySetRules([]string{"policy-pol1"}, false, false)).To(Equal([]*hns.ACLPolicy{ + Expect(ps.GetPolicySetRules([]string{"policy-GlobalNetworkPolicy/pol1"}, false, false)).To(Equal([]*hns.ACLPolicy{ {Type: hns.ACL, Protocol: 256, Action: policysets.ActionPass, Direction: hns.Out, RuleType: hns.Switch, Priority: 1001}, - }), "unexpected rules returned after ActivePolicyRemove event for policy-pol1") + }), "unexpected rules returned after ActivePolicyRemove event for policy-GlobalNetworkPolicy/pol1") // Apply profile update policyMgr.OnUpdate(&proto.ActiveProfileUpdate{ @@ -121,7 +123,7 @@ func TestPolicyManager(t *testing.T) { // assertion for ingress rules Expect(ps.GetPolicySetRules([]string{"profile-prof1"}, true, true)).To(Equal([]*hns.ACLPolicy{ - //profile-prof1 deny rule should be present + // profile-prof1 deny rule should be present {Type: hns.ACL, Protocol: 256, Action: hns.Block, Direction: hns.In, RuleType: hns.Switch, Priority: 1000}, // Default deny rule. {Type: hns.ACL, Protocol: 256, Action: hns.Block, Direction: hns.In, RuleType: hns.Switch, Priority: 1001}, @@ -129,7 +131,7 @@ func TestPolicyManager(t *testing.T) { // assertion for ingress rules with endOfTierDrop disabled Expect(ps.GetPolicySetRules([]string{"profile-prof1"}, true, false)).To(Equal([]*hns.ACLPolicy{ - //profile-prof1 deny rule should be present + // profile-prof1 deny rule should be present {Type: hns.ACL, Protocol: 256, Action: hns.Block, Direction: hns.In, RuleType: hns.Switch, Priority: 1000}, // Default deny rule. {Type: hns.ACL, Protocol: 256, Action: policysets.ActionPass, Direction: hns.In, RuleType: hns.Switch, Priority: 1001}, @@ -137,7 +139,7 @@ func TestPolicyManager(t *testing.T) { // assertion for egress rules Expect(ps.GetPolicySetRules([]string{"profile-prof1"}, false, true)).To(Equal([]*hns.ACLPolicy{ - //profile-pol1 allow rule should be present + // profile-pol1 allow rule should be present {Type: hns.ACL, Protocol: 256, Action: hns.Allow, Direction: hns.Out, RuleType: hns.Switch, Priority: 1000}, // Default deny rule. {Type: hns.ACL, Protocol: 256, Action: hns.Block, Direction: hns.Out, RuleType: hns.Switch, Priority: 1001}, @@ -145,7 +147,7 @@ func TestPolicyManager(t *testing.T) { // assertion for egress rules Expect(ps.GetPolicySetRules([]string{"profile-prof1"}, false, false)).To(Equal([]*hns.ACLPolicy{ - //profile-pol1 allow rule should be present + // profile-pol1 allow rule should be present {Type: hns.ACL, Protocol: 256, Action: hns.Allow, Direction: hns.Out, RuleType: hns.Switch, Priority: 1000}, // Default deny rule. {Type: hns.ACL, Protocol: 256, Action: policysets.ActionPass, Direction: hns.Out, RuleType: hns.Switch, Priority: 1001}, @@ -165,8 +167,9 @@ func TestPolicyManager(t *testing.T) { // Should skip stagged policy // Apply policy update policyMgr.OnUpdate(&proto.ActivePolicyUpdate{ - Id: &proto.PolicyID{Name: "staged:pol1", Tier: "tier1"}, + Id: &proto.PolicyID{Name: "pol1", Kind: v3.KindStagedGlobalNetworkPolicy}, Policy: &proto.Policy{ + Tier: "tier1", InboundRules: []*proto.Rule{ {Action: "allow"}, }, diff --git a/felix/dataplane/windows/policysets/policyset_defs.go b/felix/dataplane/windows/policysets/policyset_defs.go index 2b1854d5c1d..f2acabd0fc8 100644 --- a/felix/dataplane/windows/policysets/policyset_defs.go +++ b/felix/dataplane/windows/policysets/policyset_defs.go @@ -67,7 +67,7 @@ type PolicySetMetadata struct { // PolicySetsDataplane is a interface for managing a plane of policySet objects type PolicySetsDataplane interface { - AddOrReplacePolicySet(setId string, policy interface{}) + AddOrReplacePolicySet(setId string, policy any) RemovePolicySet(setId string) NewRule(isInbound bool, priority uint16) *hns.ACLPolicy GetPolicySetRules(setIds []string, isInbound, endOfTierDrop bool) (rules []*hns.ACLPolicy) @@ -81,7 +81,7 @@ type policySet struct { PolicySetMetadata // the original policy received from the datastore, which could be // either a Profile or a Policy. - Policy interface{} + Policy any // Each member of the Policy set is a hns ACLRule computed from the // Policy. When this Policy set needs to be applied, this set of // rules is what will be sent to hns for enforcement. diff --git a/felix/dataplane/windows/policysets/policysets.go b/felix/dataplane/windows/policysets/policysets.go index 387844fe298..0a8c8c3cee1 100644 --- a/felix/dataplane/windows/policysets/policysets.go +++ b/felix/dataplane/windows/policysets/policysets.go @@ -61,7 +61,7 @@ func NewPolicySets(hns HNSAPI, ipsets []IPSetCache, reader StaticRulesReader) *P // AddOrReplacePolicySet is responsible for the creation (or replacement) of a Policy set // and it is capable of processing either Profiles or Policies from the datastore. -func (s *PolicySets) AddOrReplacePolicySet(setId string, policy interface{}) { +func (s *PolicySets) AddOrReplacePolicySet(setId string, policy any) { log.WithField("setID", setId).Info("Processing add/replace of Policy set") // Process the policy/profile from the datastore and convert it into @@ -209,12 +209,11 @@ func (s *PolicySets) ProcessIpSetUpdate(ipSetId string) []string { // getPoliciesByIpSetId locates any Policy set(s) which reference the provided IP set func (s *PolicySets) getPoliciesByIpSetId(ipSetId string) (policies []string) { for policySetId, policySet := range s.policySetIdToPolicySet { - policySet.IpSetIds.Iter(func(id string) error { + for id := range policySet.IpSetIds.All() { if id == ipSetId { policies = append(policies, policySetId) } - return nil - }) + } } return } @@ -594,16 +593,11 @@ func protoPortToHCSPort(port *proto.PortRange) string { // This function will create chunks of ports/ports range with chunksize func SplitPortList(ports []*proto.PortRange, chunkSize int) (splits [][]*proto.PortRange) { - if len(ports) == 0 { splits = append(splits, []*proto.PortRange{}) } for i := 0; i < len(ports); i += chunkSize { - last := i + chunkSize - - if last > len(ports) { - last = len(ports) - } + last := min(i+chunkSize, len(ports)) splits = append(splits, ports[i:last]) } @@ -612,16 +606,11 @@ func SplitPortList(ports []*proto.PortRange, chunkSize int) (splits [][]*proto.P // This function will create chunks of IP addresses/Cidr with chunksize func SplitIPList(ipAddrs []string, chunkSize int) (splits [][]string) { - if len(ipAddrs) == 0 { splits = append(splits, []string{}) } for i := 0; i < len(ipAddrs); i += chunkSize { - last := i + chunkSize - - if last > len(ipAddrs) { - last = len(ipAddrs) - } + last := min(i+chunkSize, len(ipAddrs)) splits = append(splits, ipAddrs[i:last]) } diff --git a/felix/dataplane/windows/vxlan_mgr.go b/felix/dataplane/windows/vxlan_mgr.go index e6c8ed4c95f..6e1e4407fcb 100644 --- a/felix/dataplane/windows/vxlan_mgr.go +++ b/felix/dataplane/windows/vxlan_mgr.go @@ -67,7 +67,7 @@ func newVXLANManager(hcn hcnInterface, hostname string, networkName *regexp.Rege } } -func (m *vxlanManager) OnUpdate(protoBufMsg interface{}) { +func (m *vxlanManager) OnUpdate(protoBufMsg any) { switch msg := protoBufMsg.(type) { case *proto.RouteUpdate: if msg.Types&proto.RouteType_REMOTE_WORKLOAD != 0 && msg.IpPoolType == proto.IPPoolType_VXLAN { @@ -206,32 +206,32 @@ func (m *vxlanManager) CompleteDeferredWork() error { } // Remove routes that are no longer needed. - netPolsToRemove.Iter(func(polSetting hcn.RemoteSubnetRoutePolicySetting) error { + for polSetting := range netPolsToRemove.All() { polReq := wrapPolSettings(polSetting) if polReq == nil { - return nil + continue } err = network.RemovePolicy(*polReq) if err != nil { logrus.WithError(err).WithField("request", polSetting).Error("Failed to remove unwanted VXLAN route policy") - return nil + continue } - return set.RemoveItem - }) + netPolsToRemove.Discard(polSetting) + } // Add new routes. - netPolsToAdd.Iter(func(item hcn.RemoteSubnetRoutePolicySetting) error { + for item := range netPolsToAdd.All() { polReq := wrapPolSettings(item) if polReq == nil { - return nil + continue } err = network.AddPolicy(*polReq) if err != nil { logrus.WithError(err).WithField("request", polReq).Error("Failed to add VXLAN route policy") - return nil + continue } - return set.RemoveItem - }) + netPolsToAdd.Discard(item) + } // Wrap up and check for errors. if netPolsToAdd.Len() == 0 && netPolsToRemove.Len() == 0 { diff --git a/felix/dataplane/windows/vxlan_mgr_test.go b/felix/dataplane/windows/vxlan_mgr_test.go index 75904818263..93694ca7fb3 100644 --- a/felix/dataplane/windows/vxlan_mgr_test.go +++ b/felix/dataplane/windows/vxlan_mgr_test.go @@ -19,7 +19,7 @@ import ( "errors" "regexp" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/dataplane/windows/hcn" diff --git a/felix/dataplane/windows/win_dataplane.go b/felix/dataplane/windows/win_dataplane.go index e0b7500d88b..96cb1e040e0 100644 --- a/felix/dataplane/windows/win_dataplane.go +++ b/felix/dataplane/windows/win_dataplane.go @@ -92,9 +92,9 @@ type Config struct { // event before it sends a rule that references that IP set. type WindowsDataplane struct { // the channel which we receive messages from felix - toDataplane chan interface{} + toDataplane chan any // the channel used to send messages from the dataplane to felix - fromDataplane chan interface{} + fromDataplane chan any // ifaceAddrUpdates is a channel used to signal when the host's IPs change. ifaceAddrUpdates chan []string // stores all of the managers which will be processing the various updates from felix. @@ -138,7 +138,7 @@ type Manager interface { // send updates to the IPSets and PolicySets objects (which will queue the updates // until the main loop instructs them to act) or (for efficiency) may wait until // a call to CompleteDeferredWork() to flush updates to the dataplane. - OnUpdate(protoBufMsg interface{}) + OnUpdate(protoBufMsg any) // Called to allow for any batched work to be completed. CompleteDeferredWork() error } @@ -161,8 +161,8 @@ func NewWinDataplaneDriver(hns hns.API, config Config) *WindowsDataplane { config.MaxIPSetSize = math.MaxInt64 dp := &WindowsDataplane{ - toDataplane: make(chan interface{}, msgPeekLimit), - fromDataplane: make(chan interface{}, 100), + toDataplane: make(chan any, msgPeekLimit), + fromDataplane: make(chan any, 100), ifaceAddrUpdates: make(chan []string, 1), config: config, applyThrottle: throttle.New(10), @@ -217,7 +217,7 @@ func (d *WindowsDataplane) Start() { // Called by someone to put a message into our channel so that the loop will pick it up // and process it. -func (d *WindowsDataplane) SendMessage(msg interface{}) error { +func (d *WindowsDataplane) SendMessage(msg any) error { log.Debugf("WindowsDataPlane->SendMessage to felix: %T", msg) d.toDataplane <- msg @@ -226,7 +226,7 @@ func (d *WindowsDataplane) SendMessage(msg interface{}) error { // Called by Felix.go so that it can receive a channel to listen for message being // sent by this dataplane driver. -func (d *WindowsDataplane) RecvMessage() (interface{}, error) { +func (d *WindowsDataplane) RecvMessage() (any, error) { log.Debug("WindowsDataPlane->RecvMessage was invoked") return <-d.fromDataplane, nil @@ -248,7 +248,7 @@ func (d *WindowsDataplane) loopUpdatingDataplane() { datastoreInSync := false // function to pass messages to the managers for processing - processMsgFromCalcGraph := func(msg interface{}) { + processMsgFromCalcGraph := func(msg any) { log.WithField("msg", proto.MsgStringer{Msg: msg}).Infof( "Received %T update from calculation graph", msg) for _, mgr := range d.allManagers { @@ -270,7 +270,7 @@ func (d *WindowsDataplane) loopUpdatingDataplane() { batchSize := 1 processMsgFromCalcGraph(msg) msgLoop1: - for i := 0; i < msgPeekLimit; i++ { + for range msgPeekLimit { select { case msg := <-d.toDataplane: processMsgFromCalcGraph(msg) diff --git a/felix/dataplane/windows/win_dataplane_test.go b/felix/dataplane/windows/win_dataplane_test.go index d66e50e0c3e..b681b4bd097 100644 --- a/felix/dataplane/windows/win_dataplane_test.go +++ b/felix/dataplane/windows/win_dataplane_test.go @@ -15,7 +15,7 @@ package windataplane_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/config" diff --git a/felix/dataplane/windows/windataplane_ut_suite_test.go b/felix/dataplane/windows/windataplane_ut_suite_test.go index fdd9061a107..dde9ef49214 100644 --- a/felix/dataplane/windows/windataplane_ut_suite_test.go +++ b/felix/dataplane/windows/windataplane_ut_suite_test.go @@ -17,9 +17,8 @@ package windataplane_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestWindataplane(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/windataplane_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Windataplane Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/felix_dataplane_windows_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/dataplane/windows", suiteConfig, reporterConfig) } diff --git a/felix/debian/rules b/felix/debian/rules index fee8ec3ce22..5d12f2baaf0 100755 --- a/felix/debian/rules +++ b/felix/debian/rules @@ -28,12 +28,9 @@ override_dh_install: install -m 644 bpf-apache/bin/*.o bpf-gpl/bin/*.o debian/tmp/usr/lib/calico/bpf/ dh_install -# We need to decompress the debug sections in order for dh_dwz to do its thing -# without error. -# See Debian bug https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=931891 override_dh_dwz: - objcopy --decompress-debug-sections debian/calico-felix/usr/bin/calico-felix - dh_dwz + # dwz only slightly compresses the debug sections and is not fully compatible with DWARF5, + # used by Golang 1.25+, so skip this. # Hide the 'useless dependency' warnings, which are just about libc libs anyway override_dh_shlibdeps: diff --git a/felix/deltatracker/delta_tracker.go b/felix/deltatracker/delta_tracker.go index b03446daefa..a071a862894 100644 --- a/felix/deltatracker/delta_tracker.go +++ b/felix/deltatracker/delta_tracker.go @@ -16,6 +16,7 @@ package deltatracker import ( "fmt" + "maps" "reflect" "github.com/sirupsen/logrus" @@ -303,12 +304,8 @@ func (c *DataplaneView[K, V]) ReplaceAllIter(iter func(func(k K, v V)) error) er // state broken because we've removed all the keys that we've seen from oldInDPDesired and // oldInDPNotDesired, and updated c.desiredUpdates to match the new KV's from the iterator. // Fix that up by applying the new keys to the old in-DP maps: - for k, v := range newInDPDesired { - oldInDPDesired[k] = v - } - for k, v := range newInDPNotDesired { - oldInDPNotDesired[k] = v - } + maps.Copy(oldInDPDesired, newInDPDesired) + maps.Copy(oldInDPNotDesired, newInDPNotDesired) return fmt.Errorf("failed to iterate over dataplane state: %w", err) } diff --git a/felix/deltatracker/delta_tracker_test.go b/felix/deltatracker/delta_tracker_test.go index 1327293af2c..2f84c0a708f 100644 --- a/felix/deltatracker/delta_tracker_test.go +++ b/felix/deltatracker/delta_tracker_test.go @@ -452,7 +452,7 @@ func TestDeltaTracker_IterPendingActions(t *testing.T) { Expect(err).NotTo(HaveOccurred()) // Loop to check IterActionNoOp really is a no-op - for i := 0; i < 2; i++ { + for range 2 { Expect(pendingUpdates(dt, IterActionNoOp)).To(Equal(map[string]string{ "2": "B1", "4": "D1", @@ -641,10 +641,7 @@ func TestPendingUpdatesView_IterBatched_PartialApplication(t *testing.T) { } } - applyCount := 2 - if len(cleanKeys) < applyCount { - applyCount = len(cleanKeys) - } + applyCount := min(len(cleanKeys), 2) applyCalls = append(applyCalls, struct { keys []string @@ -715,10 +712,7 @@ func TestPendingDeletesView_IterBatched_PartialApplication(t *testing.T) { } } - deleteCount := 2 - if len(cleanKeys) < deleteCount { - deleteCount = len(cleanKeys) - } + deleteCount := min(len(cleanKeys), 2) deleteCalls = append(deleteCalls, struct { keys []string diff --git a/felix/deps.txt b/felix/deps.txt index a5dedad857b..fba2124b5a9 100644 --- a/felix/deps.txt +++ b/felix/deps.txt @@ -2,42 +2,46 @@ This file contains the list of modules that this package depends on in order to trigger CI on changes -go 1.25.1 -github.com/aws/aws-sdk-go-v2 v1.38.0 -github.com/aws/aws-sdk-go-v2/config v1.31.0 -github.com/aws/aws-sdk-go-v2/credentials v1.18.4 -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.3 -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.3 -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.3 -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 -github.com/aws/aws-sdk-go-v2/service/ec2 v1.242.0 -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.3 -github.com/aws/aws-sdk-go-v2/service/sso v1.28.0 -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.0 -github.com/aws/aws-sdk-go-v2/service/sts v1.37.0 -github.com/aws/smithy-go v1.22.5 +go 1.25.7 +github.com/Masterminds/semver/v3 v3.4.0 +github.com/aws/aws-sdk-go-v2 v1.39.5 +github.com/aws/aws-sdk-go-v2/config v1.31.16 +github.com/aws/aws-sdk-go-v2/credentials v1.18.20 +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.12 +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.12 +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.12 +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 +github.com/aws/aws-sdk-go-v2/service/ec2 v1.259.0 +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.12 +github.com/aws/aws-sdk-go-v2/service/sso v1.30.0 +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.4 +github.com/aws/aws-sdk-go-v2/service/sts v1.39.0 +github.com/aws/smithy-go v1.23.1 github.com/beorn7/perks v1.0.1 -github.com/bits-and-blooms/bitset v1.24.0 +github.com/bits-and-blooms/bitset v1.24.2 github.com/blang/semver/v4 v4.0.0 github.com/cespare/xxhash/v2 v2.3.0 -github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 -github.com/containernetworking/cni v1.2.3 -github.com/containernetworking/plugins v1.6.2 +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 +github.com/containernetworking/cni v1.3.0 +github.com/containernetworking/plugins v1.9.0 github.com/coreos/go-iptables v0.8.0 github.com/coreos/go-semver v0.3.1 -github.com/coreos/go-systemd/v22 v22.5.0 +github.com/coreos/go-systemd/v22 v22.6.0 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/distribution/reference v0.6.0 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 -github.com/emicklei/go-restful/v3 v3.11.0 +github.com/emicklei/go-restful v2.15.0+incompatible +github.com/emicklei/go-restful/v3 v3.12.2 github.com/envoyproxy/go-control-plane v0.13.4 -github.com/envoyproxy/go-control-plane/envoy v1.32.4 +github.com/envoyproxy/go-control-plane/envoy v1.35.0 github.com/envoyproxy/protoc-gen-validate v1.2.1 github.com/fsnotify/fsnotify v1.9.0 -github.com/fxamacker/cbor/v2 v2.7.0 +github.com/fxamacker/cbor/v2 v2.9.0 github.com/gavv/monotime v0.0.0-20190418164738-30dba4353424 github.com/go-ini/ini v1.67.0 +github.com/go-kit/log v0.2.1 +github.com/go-logfmt/logfmt v0.6.0 github.com/go-logr/logr v1.4.3 github.com/go-openapi/jsonpointer v0.21.0 github.com/go-openapi/jsonreference v0.20.2 @@ -49,13 +53,13 @@ github.com/gogo/protobuf v1.3.2 github.com/golang/protobuf v1.5.4 github.com/golang/snappy v1.0.0 github.com/google/btree v1.1.3 -github.com/google/gnostic-models v0.6.9 +github.com/google/gnostic-models v0.7.0 github.com/google/go-cmp v0.7.0 -github.com/google/gopacket v1.1.19 github.com/google/uuid v1.6.0 -github.com/grpc-ecosystem/grpc-gateway v1.16.0 -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 -github.com/ishidawataru/sctp v0.0.0-20250708014235-1989182a9425 +github.com/gopacket/gopacket v1.4.0 +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 +github.com/ishidawataru/sctp v0.0.0-20250829011129-4b890084db30 github.com/jinzhu/copier v0.4.0 github.com/joho/godotenv v1.5.1 github.com/josharian/intern v1.0.0 @@ -73,86 +77,89 @@ github.com/mdlayher/netlink v1.7.2 github.com/mdlayher/socket v0.5.1 github.com/mipearson/rfw v0.0.0-20170619235010-6f0a6f3266ba github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd -github.com/modern-go/reflect2 v1.0.2 +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 -github.com/nxadm/tail v1.4.8 github.com/olekukonko/tablewriter v0.0.5 github.com/onsi/ginkgo v1.16.5 -github.com/onsi/gomega v1.38.0 +github.com/onsi/ginkgo/v2 v2.28.1 +github.com/onsi/gomega v1.39.1 github.com/opencontainers/go-digest v1.0.0 +github.com/openshift/custom-resource-status v1.1.2 github.com/pelletier/go-toml v1.9.5 -github.com/pelletier/go-toml/v2 v2.2.3 +github.com/pelletier/go-toml/v2 v2.2.4 github.com/pkg/errors v0.9.1 +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/projectcalico/calico -github.com/projectcalico/calico/lib/std v0.0.0-00010101000000-000000000000 -github.com/projectcalico/go-json v0.0.0-20161128004156-6219dc7339ba -github.com/projectcalico/go-yaml-wrapper v0.0.0-20191112210931-090425220c54 -github.com/prometheus/client_golang v1.23.0 +github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 -github.com/prometheus/common v0.65.0 -github.com/prometheus/procfs v0.17.0 +github.com/prometheus/common v0.67.2 +github.com/prometheus/procfs v0.19.2 github.com/rivo/uniseg v0.4.7 github.com/safchain/ethtool v0.6.2 -github.com/sagikazarmark/locafero v0.7.0 +github.com/sagikazarmark/locafero v0.11.0 github.com/sirupsen/logrus v1.9.3 -github.com/sourcegraph/conc v0.3.0 -github.com/spf13/afero v1.12.0 -github.com/spf13/cast v1.9.2 -github.com/spf13/cobra v1.9.1 -github.com/spf13/pflag v1.0.7 -github.com/spf13/viper v1.20.1 +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 +github.com/spf13/afero v1.15.0 +github.com/spf13/cast v1.10.0 +github.com/spf13/cobra v1.10.1 +github.com/spf13/pflag v1.0.10 +github.com/spf13/viper v1.21.0 github.com/subosito/gotenv v1.6.0 github.com/tchap/go-patricia/v2 v2.3.3 github.com/vishvananda/netlink v1.3.1 github.com/vishvananda/netns v0.0.5 github.com/x448/float16 v0.8.4 -go.etcd.io/etcd/api/v3 v3.6.4 -go.etcd.io/etcd/client/pkg/v3 v3.6.4 -go.etcd.io/etcd/client/v3 v3.6.4 -go.opentelemetry.io/otel v1.35.0 -go.opentelemetry.io/otel/trace v1.35.0 +go.etcd.io/etcd/api/v3 v3.6.5 +go.etcd.io/etcd/client/pkg/v3 v3.6.5 +go.etcd.io/etcd/client/v3 v3.6.5 +go.opentelemetry.io/otel v1.37.0 +go.opentelemetry.io/otel/trace v1.37.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 -go.yaml.in/yaml/v2 v2.4.2 -golang.org/x/crypto v0.41.0 -golang.org/x/net v0.43.0 -golang.org/x/oauth2 v0.30.0 -golang.org/x/sync v0.16.0 -golang.org/x/sys v0.35.0 -golang.org/x/term v0.34.0 -golang.org/x/text v0.28.0 -golang.org/x/time v0.12.0 +go.yaml.in/yaml/v2 v2.4.3 +go.yaml.in/yaml/v3 v3.0.4 +golang.org/x/crypto v0.47.0 +golang.org/x/mod v0.32.0 +golang.org/x/net v0.49.0 +golang.org/x/oauth2 v0.34.0 +golang.org/x/sync v0.19.0 +golang.org/x/sys v0.40.0 +golang.org/x/term v0.39.0 +golang.org/x/text v0.33.0 +golang.org/x/time v0.14.0 +golang.org/x/tools v0.41.0 golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 google.golang.org/genproto v0.0.0-20250603155806-513f23925822 -google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 -google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a -google.golang.org/grpc v1.72.2 -google.golang.org/protobuf v1.36.7 +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c +google.golang.org/grpc v1.76.0 +google.golang.org/protobuf v1.36.10 gopkg.in/evanphx/json-patch.v4 v4.12.0 gopkg.in/go-playground/validator.v9 v9.30.2 gopkg.in/inf.v0 v0.9.1 -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 -gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 -k8s.io/api v0.33.5 -k8s.io/apiextensions-apiserver v0.33.5 -k8s.io/apimachinery v0.33.5 -k8s.io/apiserver v0.33.5 -k8s.io/client-go v0.33.5 -k8s.io/component-base v0.33.5 -k8s.io/component-helpers v0.33.5 -k8s.io/controller-manager v0.33.5 +k8s.io/api v0.34.3 +k8s.io/apiextensions-apiserver v0.34.3 +k8s.io/apimachinery v0.34.3 +k8s.io/apiserver v0.34.3 +k8s.io/client-go v0.34.3 +k8s.io/component-base v0.34.3 +k8s.io/component-helpers v0.34.3 +k8s.io/controller-manager v0.34.3 k8s.io/klog v0.2.0 k8s.io/klog/v2 v2.130.1 -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff -k8s.io/kubelet v0.33.5 -k8s.io/kubernetes v1.33.5 -k8s.io/utils v0.0.0-20241210054802-24370beab758 -modernc.org/memory v1.10.0 +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b +k8s.io/kubelet v0.34.3 +k8s.io/kubernetes v1.34.3 +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 +kubevirt.io/api v1.8.0-alpha.0 +kubevirt.io/containerized-data-importer-api v1.63.1 +kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 +modernc.org/memory v1.11.0 sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 -sigs.k8s.io/knftables v0.0.18 -sigs.k8s.io/network-policy-api v0.1.5 +sigs.k8s.io/knftables v0.0.19 +sigs.k8s.io/network-policy-api v0.1.8-0.20260212153203-412bf65729a5 sigs.k8s.io/randfill v1.0.0 -sigs.k8s.io/structured-merge-diff/v4 v4.6.0 +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 sigs.k8s.io/yaml v1.6.0 diff --git a/felix/dispatcher/dispatcher.go b/felix/dispatcher/dispatcher.go index bacf8366f89..818e1b18cbf 100644 --- a/felix/dispatcher/dispatcher.go +++ b/felix/dispatcher/dispatcher.go @@ -62,7 +62,7 @@ func NewDispatcher() *Dispatcher { func (d *Dispatcher) Register(keyExample model.Key, receiver UpdateHandler) { keyType := reflect.TypeOf(keyExample) - if keyType.Kind() == reflect.Ptr { + if keyType.Kind() == reflect.Pointer { panic("Register expects a non-pointer") } log.Infof("Registering listener for type %v: %#v", keyType, receiver) diff --git a/felix/docker-image/Dockerfile b/felix/docker-image/Dockerfile index 4ad8cc035ab..ff5648e9839 100644 --- a/felix/docker-image/Dockerfile +++ b/felix/docker-image/Dockerfile @@ -20,8 +20,6 @@ FROM ${BPFTOOL_IMAGE} AS bpftool FROM debian:12-slim AS source -LABEL maintainer="Shaun Crampton " - # Install remaining runtime deps required for felix from the global repository RUN apt-get update && apt-get install -y \ ipset \ @@ -76,5 +74,22 @@ COPY --from=source / / WORKDIR /code +LABEL org.opencontainers.image.description="Calico's per-host daemon" +LABEL org.opencontainers.image.authors="maintainers@tigera.io" +LABEL org.opencontainers.image.source="https://github.com/projectcalico/calico" +LABEL org.opencontainers.image.title="Calico Felix" +LABEL org.opencontainers.image.vendor="Project Calico" +LABEL org.opencontainers.image.version="${GIT_VERSION}" +LABEL org.opencontainers.image.licenses="Apache-2.0" + +# Labels for RedHat OCP certification +LABEL description="Calico's per-host daemon" +LABEL maintainer="maintainers@tigera.io" +LABEL name="Calico Felix" +LABEL release=1 +LABEL summary="Calico's per-host daemon" +LABEL vendor="Project Calico" +LABEL version="${GIT_VERSION}" + # Run felix (via the wrapper script) by default CMD ["/usr/bin/calico-felix-wrapper"] diff --git a/felix/docs/config-params.json b/felix/docs/config-params.json index b615084d0eb..ae9cc25733f 100644 --- a/felix/docs/config-params.json +++ b/felix/docs/config-params.json @@ -726,6 +726,58 @@ { "Name": "Process: Logging", "Fields": [ + { + "Group": "Process: Logging", + "GroupWithSortPrefix": "00 Process: Logging", + "NameConfigFile": "LogActionRateLimit", + "NameEnvVar": "FELIX_LogActionRateLimit", + "NameYAML": "logActionRateLimit", + "NameGoAPI": "LogActionRateLimit", + "StringSchema": "String matching regex `^([1-9]\\d{0,3}/(?:second|minute|hour|day))?$`", + "StringSchemaHTML": "String matching regex ^([1-9]\\d{0,3}/(?:second|minute|hour|day))?$", + "StringDefault": "", + "ParsedDefault": "", + "ParsedDefaultJSON": "\"\"", + "ParsedType": "string", + "YAMLType": "string", + "YAMLSchema": "String matching the regular expression `^[1-9]\\d{0,3}/(?:second|minute|hour|day)$`.", + "YAMLEnumValues": null, + "YAMLSchemaHTML": "String matching the regular expression ^[1-9]\\d{0,3}/(?:second|minute|hour|day)$.", + "YAMLDefault": "", + "Required": false, + "OnParseFailure": "ReplaceWithDefault", + "AllowedConfigSources": "All", + "Description": "Sets the rate of hitting a Log action. The value must be in the format \"N/unit\",\nwhere N is a number and unit is one of: second, minute, hour, or day. For example: \"10/second\" or \"100/hour\".", + "DescriptionHTML": "

Sets the rate of hitting a Log action. The value must be in the format \"N/unit\",\nwhere N is a number and unit is one of: second, minute, hour, or day. For example: \"10/second\" or \"100/hour\".

", + "UserEditable": true, + "GoType": "*string" + }, + { + "Group": "Process: Logging", + "GroupWithSortPrefix": "00 Process: Logging", + "NameConfigFile": "LogActionRateLimitBurst", + "NameEnvVar": "FELIX_LogActionRateLimitBurst", + "NameYAML": "logActionRateLimitBurst", + "NameGoAPI": "LogActionRateLimitBurst", + "StringSchema": "Integer: [0,2^63-1], [9999,2^63-1]", + "StringSchemaHTML": "Integer: [0,263-1], [9999,263-1]", + "StringDefault": "5", + "ParsedDefault": "5", + "ParsedDefaultJSON": "5", + "ParsedType": "int", + "YAMLType": "integer", + "YAMLSchema": "Integer: [0,2^63-1], [9999,2^63-1]", + "YAMLEnumValues": null, + "YAMLSchemaHTML": "Integer: [0,263-1], [9999,263-1]", + "YAMLDefault": "5", + "Required": false, + "OnParseFailure": "ReplaceWithDefault", + "AllowedConfigSources": "All", + "Description": "Sets the rate limit burst of hitting a Log action when LogActionRateLimit is enabled.", + "DescriptionHTML": "

Sets the rate limit burst of hitting a Log action when LogActionRateLimit is enabled.

", + "UserEditable": true, + "GoType": "*int" + }, { "Group": "Process: Logging", "GroupWithSortPrefix": "00 Process: Logging", @@ -792,15 +844,15 @@ "ParsedDefaultJSON": "\"calico-packet\"", "ParsedType": "string", "YAMLType": "string", - "YAMLSchema": "String.", + "YAMLSchema": "String matching the regular expression `^([a-zA-Z0-9%: /_-])*$`.", "YAMLEnumValues": null, - "YAMLSchemaHTML": "String.", + "YAMLSchemaHTML": "String matching the regular expression ^([a-zA-Z0-9%: /_-])*$.", "YAMLDefault": "calico-packet", "Required": false, "OnParseFailure": "ReplaceWithDefault", "AllowedConfigSources": "All", - "Description": "The log prefix that Felix uses when rendering LOG rules.", - "DescriptionHTML": "

The log prefix that Felix uses when rendering LOG rules.

", + "Description": "The log prefix that Felix uses when rendering LOG rules. It is possible to use the following specifiers\nto include extra information in the log prefix.\n- %t: Tier name.\n- %k: Kind (short names).\n- %n: Policy or profile name.\n- %p: Policy or profile name (namespace/name for namespaced kinds or just name for non namespaced kinds).\nCalico includes \": \" characters at the end of the generated log prefix.\nNote that iptables shows up to 29 characters for the log prefix and nftables up to 127 characters. Extra characters are truncated.", + "DescriptionHTML": "

The log prefix that Felix uses when rendering LOG rules. It is possible to use the following specifiers\nto include extra information in the log prefix.\n- %t: Tier name.\n- %k: Kind (short names).\n- %n: Policy or profile name.\n- %p: Policy or profile name (namespace/name for namespaced kinds or just name for non namespaced kinds).\nCalico includes \": \" characters at the end of the generated log prefix.\nNote that iptables shows up to 29 characters for the log prefix and nftables up to 127 characters. Extra characters are truncated.

", "UserEditable": true, "GoType": "string" }, @@ -934,6 +986,84 @@ "UserEditable": true, "GoType": "*bool" }, + { + "Group": "Process: Prometheus metrics", + "GroupWithSortPrefix": "00 Process: Prometheus metrics", + "NameConfigFile": "PrometheusMetricsCAFile", + "NameEnvVar": "FELIX_PrometheusMetricsCAFile", + "NameYAML": "prometheusMetricsCAFile", + "NameGoAPI": "PrometheusMetricsCAFile", + "StringSchema": "String", + "StringSchemaHTML": "String", + "StringDefault": "", + "ParsedDefault": "", + "ParsedDefaultJSON": "\"\"", + "ParsedType": "string", + "YAMLType": "string", + "YAMLSchema": "String.", + "YAMLEnumValues": null, + "YAMLSchemaHTML": "String.", + "YAMLDefault": "", + "Required": false, + "OnParseFailure": "ReplaceWithDefault", + "AllowedConfigSources": "All", + "Description": "Defines the absolute path to the TLS CA certificate file used for securing the /metrics endpoint.\nThis certificate must be valid and accessible by the calico-node process.", + "DescriptionHTML": "

Defines the absolute path to the TLS CA certificate file used for securing the /metrics endpoint.\nThis certificate must be valid and accessible by the calico-node process.

", + "UserEditable": true, + "GoType": "*string" + }, + { + "Group": "Process: Prometheus metrics", + "GroupWithSortPrefix": "00 Process: Prometheus metrics", + "NameConfigFile": "PrometheusMetricsCertFile", + "NameEnvVar": "FELIX_PrometheusMetricsCertFile", + "NameYAML": "prometheusMetricsCertFile", + "NameGoAPI": "PrometheusMetricsCertFile", + "StringSchema": "String", + "StringSchemaHTML": "String", + "StringDefault": "", + "ParsedDefault": "", + "ParsedDefaultJSON": "\"\"", + "ParsedType": "string", + "YAMLType": "string", + "YAMLSchema": "String.", + "YAMLEnumValues": null, + "YAMLSchemaHTML": "String.", + "YAMLDefault": "", + "Required": false, + "OnParseFailure": "ReplaceWithDefault", + "AllowedConfigSources": "All", + "Description": "Defines the absolute path to the TLS certificate file used for securing the /metrics endpoint.\nThis certificate must be valid and accessible by the calico-node process.", + "DescriptionHTML": "

Defines the absolute path to the TLS certificate file used for securing the /metrics endpoint.\nThis certificate must be valid and accessible by the calico-node process.

", + "UserEditable": true, + "GoType": "*string" + }, + { + "Group": "Process: Prometheus metrics", + "GroupWithSortPrefix": "00 Process: Prometheus metrics", + "NameConfigFile": "PrometheusMetricsClientAuth", + "NameEnvVar": "FELIX_PrometheusMetricsClientAuth", + "NameYAML": "prometheusMetricsClientAuth", + "NameGoAPI": "PrometheusMetricsClientAuth", + "StringSchema": "One of: `NoClientCert`, `RequireAndVerifyClientCert`, `RequireAnyClientCert`, `VerifyClientCertIfGiven` (case insensitive)", + "StringSchemaHTML": "One of: NoClientCert, RequireAndVerifyClientCert, RequireAnyClientCert, VerifyClientCertIfGiven (case insensitive)", + "StringDefault": "RequireAndVerifyClientCert", + "ParsedDefault": "RequireAndVerifyClientCert", + "ParsedDefaultJSON": "\"RequireAndVerifyClientCert\"", + "ParsedType": "string", + "YAMLType": "string", + "YAMLSchema": "", + "YAMLEnumValues": null, + "YAMLSchemaHTML": "", + "YAMLDefault": "RequireAndVerifyClientCert", + "Required": false, + "OnParseFailure": "ReplaceWithDefault", + "AllowedConfigSources": "All", + "Description": "Specifies the client authentication type for the /metrics endpoint.\nThis determines how the server validates client certificates. Default is \"RequireAndVerifyClientCert\".", + "DescriptionHTML": "

Specifies the client authentication type for the /metrics endpoint.\nThis determines how the server validates client certificates. Default is \"RequireAndVerifyClientCert\".

", + "UserEditable": true, + "GoType": "*v3.PrometheusMetricsClientAuthType" + }, { "Group": "Process: Prometheus metrics", "GroupWithSortPrefix": "00 Process: Prometheus metrics", @@ -986,6 +1116,32 @@ "UserEditable": true, "GoType": "string" }, + { + "Group": "Process: Prometheus metrics", + "GroupWithSortPrefix": "00 Process: Prometheus metrics", + "NameConfigFile": "PrometheusMetricsKeyFile", + "NameEnvVar": "FELIX_PrometheusMetricsKeyFile", + "NameYAML": "prometheusMetricsKeyFile", + "NameGoAPI": "PrometheusMetricsKeyFile", + "StringSchema": "String", + "StringSchemaHTML": "String", + "StringDefault": "", + "ParsedDefault": "", + "ParsedDefaultJSON": "\"\"", + "ParsedType": "string", + "YAMLType": "string", + "YAMLSchema": "String.", + "YAMLEnumValues": null, + "YAMLSchemaHTML": "String.", + "YAMLDefault": "", + "Required": false, + "OnParseFailure": "ReplaceWithDefault", + "AllowedConfigSources": "All", + "Description": "Defines the absolute path to the private key file corresponding to the TLS certificate\nused for securing the /metrics endpoint. The private key must be valid and accessible by the calico-node process.", + "DescriptionHTML": "

Defines the absolute path to the private key file corresponding to the TLS certificate\nused for securing the /metrics endpoint. The private key must be valid and accessible by the calico-node process.

", + "UserEditable": true, + "GoType": "*string" + }, { "Group": "Process: Prometheus metrics", "GroupWithSortPrefix": "00 Process: Prometheus metrics", @@ -1723,9 +1879,9 @@ "ParsedDefaultJSON": "0", "ParsedType": "numorstring.Port", "YAMLType": "integer or string", - "YAMLSchema": "String.", + "YAMLSchema": "Port range: either an integer in [0,65535] or a string, representing a range, in format `n:m`", "YAMLEnumValues": null, - "YAMLSchemaHTML": "String.", + "YAMLSchemaHTML": "Port range: either an integer in [0,65535] or a string, representing a range, in format n:m", "YAMLDefault": "0", "Required": false, "OnParseFailure": "ReplaceWithDefault", @@ -1742,11 +1898,11 @@ "NameEnvVar": "FELIX_NFTablesMode", "NameYAML": "nftablesMode", "NameGoAPI": "NFTablesMode", - "StringSchema": "One of: `Disabled`, `Enabled` (case insensitive)", - "StringSchemaHTML": "One of: Disabled, Enabled (case insensitive)", - "StringDefault": "Disabled", - "ParsedDefault": "Disabled", - "ParsedDefaultJSON": "\"Disabled\"", + "StringSchema": "One of: `Auto`, `Disabled`, `Enabled` (case insensitive)", + "StringSchemaHTML": "One of: Auto, Disabled, Enabled (case insensitive)", + "StringDefault": "Auto", + "ParsedDefault": "Auto", + "ParsedDefaultJSON": "\"Auto\"", "ParsedType": "string", "YAMLType": "string", "YAMLSchema": "One of: `\"Auto\"`, `\"Disabled\"`, `\"Enabled\"`.", @@ -1756,7 +1912,7 @@ "`\"Enabled\"`" ], "YAMLSchemaHTML": "One of: \"Auto\", \"Disabled\", \"Enabled\".", - "YAMLDefault": "Disabled", + "YAMLDefault": "Auto", "Required": false, "OnParseFailure": "ReplaceWithDefault", "AllowedConfigSources": "All", @@ -2187,14 +2343,14 @@ "ParsedDefaultJSON": "\"auto\"", "ParsedType": "string", "YAMLType": "string", - "YAMLSchema": "One of: `Auto`, `Legacy`, `NFT`.", + "YAMLSchema": "One of: `\"Auto\"`, `\"Legacy\"`, `\"NFT\"`.", "YAMLEnumValues": [ - "Auto", - "Legacy", - "NFT" + "`\"Auto\"`", + "`\"Legacy\"`", + "`\"NFT\"`" ], - "YAMLSchemaHTML": "One of: Auto, Legacy, NFT.", - "YAMLDefault": "Auto", + "YAMLSchemaHTML": "One of: \"Auto\", \"Legacy\", \"NFT\".", + "YAMLDefault": "auto", "Required": false, "OnParseFailure": "ReplaceWithDefault", "AllowedConfigSources": "All", @@ -2261,32 +2417,6 @@ "UserEditable": true, "GoType": "string" }, - { - "Group": "Dataplane: iptables", - "GroupWithSortPrefix": "20 Dataplane: iptables", - "NameConfigFile": "IptablesLockFilePath", - "NameEnvVar": "FELIX_IptablesLockFilePath", - "NameYAML": "iptablesLockFilePath", - "NameGoAPI": "IptablesLockFilePath", - "StringSchema": "Path to file", - "StringSchemaHTML": "Path to file", - "StringDefault": "/run/xtables.lock", - "ParsedDefault": "/run/xtables.lock", - "ParsedDefaultJSON": "\"/run/xtables.lock\"", - "ParsedType": "string", - "YAMLType": "string", - "YAMLSchema": "String.", - "YAMLEnumValues": null, - "YAMLSchemaHTML": "String.", - "YAMLDefault": "/run/xtables.lock", - "Required": false, - "OnParseFailure": "ReplaceWithDefault", - "AllowedConfigSources": "All", - "Description": "The location of the iptables lock file. You may need to change this\nif the lock file is not in its standard location (for example if you have mapped it into Felix's\ncontainer at a different path).", - "DescriptionHTML": "

The location of the iptables lock file. You may need to change this\nif the lock file is not in its standard location (for example if you have mapped it into Felix's\ncontainer at a different path).

", - "UserEditable": true, - "GoType": "string" - }, { "Group": "Dataplane: iptables", "GroupWithSortPrefix": "20 Dataplane: iptables", @@ -2308,34 +2438,8 @@ "Required": false, "OnParseFailure": "ReplaceWithDefault", "AllowedConfigSources": "All", - "Description": "When IptablesLockTimeout is enabled: the time that Felix will wait between\nattempts to acquire the iptables lock if it is not available. Lower values make Felix more\nresponsive when the lock is contended, but use more CPU.", - "DescriptionHTML": "

When IptablesLockTimeout is enabled: the time that Felix will wait between\nattempts to acquire the iptables lock if it is not available. Lower values make Felix more\nresponsive when the lock is contended, but use more CPU.

", - "UserEditable": true, - "GoType": "*v1.Duration" - }, - { - "Group": "Dataplane: iptables", - "GroupWithSortPrefix": "20 Dataplane: iptables", - "NameConfigFile": "IptablesLockTimeoutSecs", - "NameEnvVar": "FELIX_IptablesLockTimeoutSecs", - "NameYAML": "iptablesLockTimeout", - "NameGoAPI": "IptablesLockTimeout", - "StringSchema": "Seconds (floating point)", - "StringSchemaHTML": "Seconds (floating point)", - "StringDefault": "0", - "ParsedDefault": "0s", - "ParsedDefaultJSON": "0", - "ParsedType": "time.Duration", - "YAMLType": "string", - "YAMLSchema": "Duration string, for example `1m30s123ms` or `1h5m`.", - "YAMLEnumValues": null, - "YAMLSchemaHTML": "Duration string, for example 1m30s123ms or 1h5m.", - "YAMLDefault": "0s", - "Required": false, - "OnParseFailure": "ReplaceWithDefault", - "AllowedConfigSources": "All", - "Description": "The time that Felix itself will wait for the iptables lock (rather than delegating the\nlock handling to the `iptables` command).\n\nDeprecated: `iptables-restore` v1.8+ always takes the lock, so enabling this feature results in deadlock.", - "DescriptionHTML": "

The time that Felix itself will wait for the iptables lock (rather than delegating the\nlock handling to the iptables command).

\n

Deprecated: iptables-restore v1.8+ always takes the lock, so enabling this feature results in deadlock.

", + "Description": "Configures the interval between attempts to claim\nthe xtables lock. Shorter intervals are more responsive but use more CPU.", + "DescriptionHTML": "

Configures the interval between attempts to claim\nthe xtables lock. Shorter intervals are more responsive but use more CPU.

", "UserEditable": true, "GoType": "*v1.Duration" }, @@ -3246,36 +3350,10 @@ { "Group": "Dataplane: eBPF", "GroupWithSortPrefix": "22 Dataplane: eBPF", - "NameConfigFile": "BPFKubeProxyEndpointSlicesEnabled", - "NameEnvVar": "FELIX_BPFKubeProxyEndpointSlicesEnabled", - "NameYAML": "bpfKubeProxyEndpointSlicesEnabled", - "NameGoAPI": "BPFKubeProxyEndpointSlicesEnabled", - "StringSchema": "Boolean: `true`, `1`, `yes`, `y`, `t` accepted as True; `false`, `0`, `no`, `n`, `f` accepted (case insensitively) as False.", - "StringSchemaHTML": "Boolean: true, 1, yes, y, t accepted as True; false, 0, no, n, f accepted (case insensitively) as False.", - "StringDefault": "true", - "ParsedDefault": "true", - "ParsedDefaultJSON": "true", - "ParsedType": "bool", - "YAMLType": "boolean", - "YAMLSchema": "Boolean.", - "YAMLEnumValues": null, - "YAMLSchemaHTML": "Boolean.", - "YAMLDefault": "true", - "Required": false, - "OnParseFailure": "ReplaceWithDefault", - "AllowedConfigSources": "All", - "Description": "Deprecated and has no effect. BPF\nkube-proxy always accepts endpoint slices. This option will be removed in\nthe next release.", - "DescriptionHTML": "

Deprecated and has no effect. BPF\nkube-proxy always accepts endpoint slices. This option will be removed in\nthe next release.

", - "UserEditable": true, - "GoType": "*bool" - }, - { - "Group": "Dataplane: eBPF", - "GroupWithSortPrefix": "22 Dataplane: eBPF", - "NameConfigFile": "BPFKubeProxyHealtzPort", - "NameEnvVar": "FELIX_BPFKubeProxyHealtzPort", - "NameYAML": "bpfKubeProxyHealtzPort", - "NameGoAPI": "BPFKubeProxyHealtzPort", + "NameConfigFile": "BPFKubeProxyHealthzPort", + "NameEnvVar": "FELIX_BPFKubeProxyHealthzPort", + "NameYAML": "bpfKubeProxyHealthzPort", + "NameGoAPI": "BPFKubeProxyHealthzPort", "StringSchema": "Integer", "StringSchemaHTML": "Integer", "StringDefault": "10256", @@ -3429,6 +3507,58 @@ "UserEditable": true, "GoType": "string" }, + { + "Group": "Dataplane: eBPF", + "GroupWithSortPrefix": "22 Dataplane: eBPF", + "NameConfigFile": "BPFMaglevMaxEndpointsPerService", + "NameEnvVar": "FELIX_BPFMaglevMaxEndpointsPerService", + "NameYAML": "bpfMaglevMaxEndpointsPerService", + "NameGoAPI": "BPFMaglevMaxEndpointsPerService", + "StringSchema": "Integer: [1,3000]", + "StringSchemaHTML": "Integer: [1,3000]", + "StringDefault": "100", + "ParsedDefault": "100", + "ParsedDefaultJSON": "100", + "ParsedType": "int", + "YAMLType": "integer", + "YAMLSchema": "Integer: [1,3000]", + "YAMLEnumValues": null, + "YAMLSchemaHTML": "Integer: [1,3000]", + "YAMLDefault": "100", + "Required": false, + "OnParseFailure": "ReplaceWithDefault", + "AllowedConfigSources": "All", + "Description": "The maximum number of endpoints\nexpected to be part of a single Maglev-enabled service.\n\nInfluences the size of the per-service Maglev lookup-tables generated by Felix\nand thus the amount of memory reserved.", + "DescriptionHTML": "

The maximum number of endpoints\nexpected to be part of a single Maglev-enabled service.

\n

Influences the size of the per-service Maglev lookup-tables generated by Felix\nand thus the amount of memory reserved.

", + "UserEditable": true, + "GoType": "*int" + }, + { + "Group": "Dataplane: eBPF", + "GroupWithSortPrefix": "22 Dataplane: eBPF", + "NameConfigFile": "BPFMaglevMaxServices", + "NameEnvVar": "FELIX_BPFMaglevMaxServices", + "NameYAML": "bpfMaglevMaxServices", + "NameGoAPI": "BPFMaglevMaxServices", + "StringSchema": "Integer: [1,3000]", + "StringSchemaHTML": "Integer: [1,3000]", + "StringDefault": "100", + "ParsedDefault": "100", + "ParsedDefaultJSON": "100", + "ParsedType": "int", + "YAMLType": "integer", + "YAMLSchema": "Integer: [1,3000]", + "YAMLEnumValues": null, + "YAMLSchemaHTML": "Integer: [1,3000]", + "YAMLDefault": "100", + "Required": false, + "OnParseFailure": "ReplaceWithDefault", + "AllowedConfigSources": "All", + "Description": "The maximum number of expected Maglev-enabled\nservices that Felix will allocate lookup-tables for.", + "DescriptionHTML": "

The maximum number of expected Maglev-enabled\nservices that Felix will allocate lookup-tables for.

", + "UserEditable": true, + "GoType": "*int" + }, { "Group": "Dataplane: eBPF", "GroupWithSortPrefix": "22 Dataplane: eBPF", @@ -3706,9 +3836,9 @@ "ParsedDefaultJSON": "\"20000:29999\"", "ParsedType": "numorstring.Port", "YAMLType": "integer or string", - "YAMLSchema": "String.", + "YAMLSchema": "Port range: either an integer in [0,65535] or a string, representing a range, in format `n:m`", "YAMLEnumValues": null, - "YAMLSchemaHTML": "String.", + "YAMLSchemaHTML": "Port range: either an integer in [0,65535] or a string, representing a range, in format n:m", "YAMLDefault": "20000:29999", "Required": false, "OnParseFailure": "ReplaceWithDefault", @@ -3782,24 +3912,23 @@ "NameGoAPI": "BPFRedirectToPeer", "StringSchema": "One of: `Disabled`, `Enabled`, `L2Only` (case insensitive)", "StringSchemaHTML": "One of: Disabled, Enabled, L2Only (case insensitive)", - "StringDefault": "L2Only", - "ParsedDefault": "L2Only", - "ParsedDefaultJSON": "\"L2Only\"", + "StringDefault": "Enabled", + "ParsedDefault": "Enabled", + "ParsedDefaultJSON": "\"Enabled\"", "ParsedType": "string", "YAMLType": "string", - "YAMLSchema": "One of: `\"Disabled\"`, `\"Enabled\"`, `\"L2Only\"`.", + "YAMLSchema": "One of: `\"Disabled\"`, `\"Enabled\"`.", "YAMLEnumValues": [ "`\"Disabled\"`", - "`\"Enabled\"`", - "`\"L2Only\"`" + "`\"Enabled\"`" ], - "YAMLSchemaHTML": "One of: \"Disabled\", \"Enabled\", \"L2Only\".", - "YAMLDefault": "L2Only", + "YAMLSchemaHTML": "One of: \"Disabled\", \"Enabled\".", + "YAMLDefault": "Enabled", "Required": true, "OnParseFailure": "ReplaceWithDefault", "AllowedConfigSources": "All", - "Description": "Controls which whether it is allowed to forward straight to the\npeer side of the workload devices. It is allowed for any host L2 devices by default\n(L2Only), but it breaks TCP dump on the host side of workload device as it bypasses\nit on ingress. Value of Enabled also allows redirection from L3 host devices like\nIPIP tunnel or Wireguard directly to the peer side of the workload's device. This\nmakes redirection faster, however, it breaks tools like tcpdump on the peer side.\nUse Enabled with caution.", - "DescriptionHTML": "

Controls which whether it is allowed to forward straight to the\npeer side of the workload devices. It is allowed for any host L2 devices by default\n(L2Only), but it breaks TCP dump on the host side of workload device as it bypasses\nit on ingress. Value of Enabled also allows redirection from L3 host devices like\nIPIP tunnel or Wireguard directly to the peer side of the workload's device. This\nmakes redirection faster, however, it breaks tools like tcpdump on the peer side.\nUse Enabled with caution.

", + "Description": "Controls whether traffic may be forwarded directly to the peer side of a workload’s device.\nNote that the legacy \"L2Only\" option is now deprecated and if set it is treated like \"Enabled.\nSetting this option to \"Enabled\" allows direct redirection (including from L3 host devices such as IPIP tunnels or WireGuard),\nwhich can improve redirection performance but causes the redirected packets to bypass the host‑side ingress path.\nAs a result, packet‑capture tools on the host side of the workload device (for example, tcpdump) will not see that traffic.", + "DescriptionHTML": "

Controls whether traffic may be forwarded directly to the peer side of a workload’s device.\nNote that the legacy \"L2Only\" option is now deprecated and if set it is treated like \"Enabled.\nSetting this option to \"Enabled\" allows direct redirection (including from L3 host devices such as IPIP tunnels or WireGuard),\nwhich can improve redirection performance but causes the redirected packets to bypass the host‑side ingress path.\nAs a result, packet‑capture tools on the host side of the workload device (for example, tcpdump) will not see that traffic.

", "UserEditable": true, "GoType": "string" } diff --git a/felix/docs/config-params.md b/felix/docs/config-params.md index 882812ada77..8239bc2aaaa 100644 --- a/felix/docs/config-params.md +++ b/felix/docs/config-params.md @@ -387,6 +387,33 @@ subcomponents, see Felix's logs. ## Process: Logging +### `LogActionRateLimit` (config file) / `logActionRateLimit` (YAML) + +Sets the rate of hitting a Log action. The value must be in the format "N/unit", +where N is a number and unit is one of: second, minute, hour, or day. For example: "10/second" or "100/hour". + +| Detail | | +| --- | --- | +| Environment variable | `FELIX_LogActionRateLimit` | +| Encoding (env var/config file) | String matching regex ^([1-9]\d{0,3}/(?:second\|minute\|hour\|day))?$ | +| Default value (above encoding) | none | +| `FelixConfiguration` field | `logActionRateLimit` (YAML) `LogActionRateLimit` (Go API) | +| `FelixConfiguration` schema | String matching the regular expression ^[1-9]\d{0,3}/(?:second\|minute\|hour\|day)$. | +| Default value (YAML) | none | + +### `LogActionRateLimitBurst` (config file) / `logActionRateLimitBurst` (YAML) + +Sets the rate limit burst of hitting a Log action when LogActionRateLimit is enabled. + +| Detail | | +| --- | --- | +| Environment variable | `FELIX_LogActionRateLimitBurst` | +| Encoding (env var/config file) | Integer: [0,263-1], [9999,263-1] | +| Default value (above encoding) | `5` | +| `FelixConfiguration` field | `logActionRateLimitBurst` (YAML) `LogActionRateLimitBurst` (Go API) | +| `FelixConfiguration` schema | Integer: [0,263-1], [9999,263-1] | +| Default value (YAML) | `5` | + ### `LogDebugFilenameRegex` (config file) / `logDebugFilenameRegex` (YAML) Controls which source code files have their Debug log output included in the logs. @@ -418,7 +445,14 @@ The full path to the Felix log. Set to none to disable file logging. ### `LogPrefix` (config file) / `logPrefix` (YAML) -The log prefix that Felix uses when rendering LOG rules. +The log prefix that Felix uses when rendering LOG rules. It is possible to use the following specifiers +to include extra information in the log prefix. +- %t: Tier name. +- %k: Kind (short names). +- %n: Policy or profile name. +- %p: Policy or profile name (namespace/name for namespaced kinds or just name for non namespaced kinds). +Calico includes ": " characters at the end of the generated log prefix. +Note that iptables shows up to 29 characters for the log prefix and nftables up to 127 characters. Extra characters are truncated. | Detail | | | --- | --- | @@ -426,7 +460,7 @@ The log prefix that Felix uses when rendering LOG rules. | Encoding (env var/config file) | String | | Default value (above encoding) | `calico-packet` | | `FelixConfiguration` field | `logPrefix` (YAML) `LogPrefix` (Go API) | -| `FelixConfiguration` schema | String. | +| `FelixConfiguration` schema | String matching the regular expression ^([a-zA-Z0-9%: /_-])*$. | | Default value (YAML) | `calico-packet` | ### `LogSeverityFile` (config file) / `logSeverityFile` (YAML) @@ -484,6 +518,48 @@ set to false. This reduces the number of metrics reported, reducing Prometheus l | `FelixConfiguration` schema | Boolean. | | Default value (YAML) | `true` | +### `PrometheusMetricsCAFile` (config file) / `prometheusMetricsCAFile` (YAML) + +Defines the absolute path to the TLS CA certificate file used for securing the /metrics endpoint. +This certificate must be valid and accessible by the calico-node process. + +| Detail | | +| --- | --- | +| Environment variable | `FELIX_PrometheusMetricsCAFile` | +| Encoding (env var/config file) | String | +| Default value (above encoding) | none | +| `FelixConfiguration` field | `prometheusMetricsCAFile` (YAML) `PrometheusMetricsCAFile` (Go API) | +| `FelixConfiguration` schema | String. | +| Default value (YAML) | none | + +### `PrometheusMetricsCertFile` (config file) / `prometheusMetricsCertFile` (YAML) + +Defines the absolute path to the TLS certificate file used for securing the /metrics endpoint. +This certificate must be valid and accessible by the calico-node process. + +| Detail | | +| --- | --- | +| Environment variable | `FELIX_PrometheusMetricsCertFile` | +| Encoding (env var/config file) | String | +| Default value (above encoding) | none | +| `FelixConfiguration` field | `prometheusMetricsCertFile` (YAML) `PrometheusMetricsCertFile` (Go API) | +| `FelixConfiguration` schema | String. | +| Default value (YAML) | none | + +### `PrometheusMetricsClientAuth` (config file) / `prometheusMetricsClientAuth` (YAML) + +Specifies the client authentication type for the /metrics endpoint. +This determines how the server validates client certificates. Default is "RequireAndVerifyClientCert". + +| Detail | | +| --- | --- | +| Environment variable | `FELIX_PrometheusMetricsClientAuth` | +| Encoding (env var/config file) | One of: NoClientCert, RequireAndVerifyClientCert, RequireAnyClientCert, VerifyClientCertIfGiven (case insensitive) | +| Default value (above encoding) | `RequireAndVerifyClientCert` | +| `FelixConfiguration` field | `prometheusMetricsClientAuth` (YAML) `PrometheusMetricsClientAuth` (Go API) | +| `FelixConfiguration` schema | `string` | +| Default value (YAML) | `RequireAndVerifyClientCert` | + ### `PrometheusMetricsEnabled` (config file) / `prometheusMetricsEnabled` (YAML) Enables the Prometheus metrics server in Felix if set to true. @@ -510,6 +586,20 @@ The host that the Prometheus metrics server should bind to. | `FelixConfiguration` schema | String. | | Default value (YAML) | none | +### `PrometheusMetricsKeyFile` (config file) / `prometheusMetricsKeyFile` (YAML) + +Defines the absolute path to the private key file corresponding to the TLS certificate +used for securing the /metrics endpoint. The private key must be valid and accessible by the calico-node process. + +| Detail | | +| --- | --- | +| Environment variable | `FELIX_PrometheusMetricsKeyFile` | +| Encoding (env var/config file) | String | +| Default value (above encoding) | none | +| `FelixConfiguration` field | `prometheusMetricsKeyFile` (YAML) `PrometheusMetricsKeyFile` (Go API) | +| `FelixConfiguration` schema | String. | +| Default value (YAML) | none | + ### `PrometheusMetricsPort` (config file) / `prometheusMetricsPort` (YAML) The TCP port that the Prometheus metrics server should bind to. @@ -932,7 +1022,7 @@ network stack is used. | Encoding (env var/config file) | Port range: either a single number in [0,65535] or a range of numbers n:m | | Default value (above encoding) | none | | `FelixConfiguration` field | `natPortRange` (YAML) `NATPortRange` (Go API) | -| `FelixConfiguration` schema | String. | +| `FelixConfiguration` schema | Port range: either an integer in [0,65535] or a string, representing a range, in format n:m | | Default value (YAML) | `0` | ### `NFTablesMode` (config file) / `nftablesMode` (YAML) @@ -942,11 +1032,11 @@ Configures nftables support in Felix. | Detail | | | --- | --- | | Environment variable | `FELIX_NFTablesMode` | -| Encoding (env var/config file) | One of: Disabled, Enabled (case insensitive) | -| Default value (above encoding) | `Disabled` | +| Encoding (env var/config file) | One of: Auto, Disabled, Enabled (case insensitive) | +| Default value (above encoding) | `Auto` | | `FelixConfiguration` field | `nftablesMode` (YAML) `NFTablesMode` (Go API) | | `FelixConfiguration` schema | One of: "Auto", "Disabled", "Enabled". | -| Default value (YAML) | `Disabled` | +| Default value (YAML) | `Auto` | ### `NetlinkTimeoutSecs` (config file) / `netlinkTimeout` (YAML) @@ -1180,8 +1270,8 @@ should be cleaned up to avoid confusing interactions. | Encoding (env var/config file) | One of: auto, legacy, nft (case insensitive) | | Default value (above encoding) | `auto` | | `FelixConfiguration` field | `iptablesBackend` (YAML) `IptablesBackend` (Go API) | -| `FelixConfiguration` schema | One of: Auto, Legacy, NFT. | -| Default value (YAML) | `Auto` | +| `FelixConfiguration` schema | One of: "Auto", "Legacy", "NFT". | +| Default value (YAML) | `auto` | ### `IptablesFilterAllowAction` (config file) / `iptablesFilterAllowAction` (YAML) @@ -1214,26 +1304,10 @@ with an iptables "DROP" action. If you want to use "REJECT" action instead you c | Default value (YAML) | `Drop` | | Notes | Required, Felix will exit if the value is invalid. | -### `IptablesLockFilePath` (config file) / `iptablesLockFilePath` (YAML) - -The location of the iptables lock file. You may need to change this -if the lock file is not in its standard location (for example if you have mapped it into Felix's -container at a different path). - -| Detail | | -| --- | --- | -| Environment variable | `FELIX_IptablesLockFilePath` | -| Encoding (env var/config file) | Path to file | -| Default value (above encoding) | `/run/xtables.lock` | -| `FelixConfiguration` field | `iptablesLockFilePath` (YAML) `IptablesLockFilePath` (Go API) | -| `FelixConfiguration` schema | String. | -| Default value (YAML) | `/run/xtables.lock` | - ### `IptablesLockProbeIntervalMillis` (config file) / `iptablesLockProbeInterval` (YAML) -When IptablesLockTimeout is enabled: the time that Felix will wait between -attempts to acquire the iptables lock if it is not available. Lower values make Felix more -responsive when the lock is contended, but use more CPU. +Configures the interval between attempts to claim +the xtables lock. Shorter intervals are more responsive but use more CPU. | Detail | | | --- | --- | @@ -1244,22 +1318,6 @@ responsive when the lock is contended, but use more CPU. | `FelixConfiguration` schema | Duration string, for example 1m30s123ms or 1h5m. | | Default value (YAML) | `50ms` | -### `IptablesLockTimeoutSecs` (config file) / `iptablesLockTimeout` (YAML) - -The time that Felix itself will wait for the iptables lock (rather than delegating the -lock handling to the `iptables` command). - -Deprecated: `iptables-restore` v1.8+ always takes the lock, so enabling this feature results in deadlock. - -| Detail | | -| --- | --- | -| Environment variable | `FELIX_IptablesLockTimeoutSecs` | -| Encoding (env var/config file) | Seconds (floating point) | -| Default value (above encoding) | `0` (0s) | -| `FelixConfiguration` field | `iptablesLockTimeout` (YAML) `IptablesLockTimeout` (Go API) | -| `FelixConfiguration` schema | Duration string, for example 1m30s123ms or 1h5m. | -| Default value (YAML) | `0s` | - ### `IptablesMangleAllowAction` (config file) / `iptablesMangleAllowAction` (YAML) Controls what happens to traffic that is accepted by a Felix policy chain in the @@ -1787,32 +1845,17 @@ Felix will not modify the JIT hardening setting. | Default value (YAML) | `Auto` | | Notes | Required. | -### `BPFKubeProxyEndpointSlicesEnabled` (config file) / `bpfKubeProxyEndpointSlicesEnabled` (YAML) - -Deprecated and has no effect. BPF -kube-proxy always accepts endpoint slices. This option will be removed in -the next release. - -| Detail | | -| --- | --- | -| Environment variable | `FELIX_BPFKubeProxyEndpointSlicesEnabled` | -| Encoding (env var/config file) | Boolean: true, 1, yes, y, t accepted as True; false, 0, no, n, f accepted (case insensitively) as False. | -| Default value (above encoding) | `true` | -| `FelixConfiguration` field | `bpfKubeProxyEndpointSlicesEnabled` (YAML) `BPFKubeProxyEndpointSlicesEnabled` (Go API) | -| `FelixConfiguration` schema | Boolean. | -| Default value (YAML) | `true` | - -### `BPFKubeProxyHealtzPort` (config file) / `bpfKubeProxyHealtzPort` (YAML) +### `BPFKubeProxyHealthzPort` (config file) / `bpfKubeProxyHealthzPort` (YAML) In BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. The health check server is used by external load balancers to determine if this node should receive traffic. | Detail | | | --- | --- | -| Environment variable | `FELIX_BPFKubeProxyHealtzPort` | +| Environment variable | `FELIX_BPFKubeProxyHealthzPort` | | Encoding (env var/config file) | Integer | | Default value (above encoding) | `10256` | -| `FelixConfiguration` field | `bpfKubeProxyHealtzPort` (YAML) `BPFKubeProxyHealtzPort` (Go API) | +| `FelixConfiguration` field | `bpfKubeProxyHealthzPort` (YAML) `BPFKubeProxyHealthzPort` (Go API) | | `FelixConfiguration` schema | Integer | | Default value (YAML) | `10256` | | Notes | Required. | @@ -1896,6 +1939,37 @@ Controls the log level of the BPF programs when in BPF dataplane mode. One of "O | Default value (YAML) | `Off` | | Notes | Required. | +### `BPFMaglevMaxEndpointsPerService` (config file) / `bpfMaglevMaxEndpointsPerService` (YAML) + +The maximum number of endpoints +expected to be part of a single Maglev-enabled service. + +Influences the size of the per-service Maglev lookup-tables generated by Felix +and thus the amount of memory reserved. + +| Detail | | +| --- | --- | +| Environment variable | `FELIX_BPFMaglevMaxEndpointsPerService` | +| Encoding (env var/config file) | Integer: [1,3000] | +| Default value (above encoding) | `100` | +| `FelixConfiguration` field | `bpfMaglevMaxEndpointsPerService` (YAML) `BPFMaglevMaxEndpointsPerService` (Go API) | +| `FelixConfiguration` schema | Integer: [1,3000] | +| Default value (YAML) | `100` | + +### `BPFMaglevMaxServices` (config file) / `bpfMaglevMaxServices` (YAML) + +The maximum number of expected Maglev-enabled +services that Felix will allocate lookup-tables for. + +| Detail | | +| --- | --- | +| Environment variable | `FELIX_BPFMaglevMaxServices` | +| Encoding (env var/config file) | Integer: [1,3000] | +| Default value (above encoding) | `100` | +| `FelixConfiguration` field | `bpfMaglevMaxServices` (YAML) `BPFMaglevMaxServices` (Go API) | +| `FelixConfiguration` schema | Integer: [1,3000] | +| Default value (YAML) | `100` | + ### `BPFMapSizeConntrack` (config file) / `bpfMapSizeConntrack` (YAML) Sets the size for the conntrack map. This map must be large enough to hold @@ -2068,7 +2142,7 @@ inclusive. | Encoding (env var/config file) | Port range: either a single number in [0,65535] or a range of numbers n:m | | Default value (above encoding) | `20000:29999` | | `FelixConfiguration` field | `bpfPSNATPorts` (YAML) `BPFPSNATPorts` (Go API) | -| `FelixConfiguration` schema | String. | +| `FelixConfiguration` schema | Port range: either an integer in [0,65535] or a string, representing a range, in format n:m | | Default value (YAML) | `20000:29999` | ### `BPFPolicyDebugEnabled` (config file) / `bpfPolicyDebugEnabled` (YAML) @@ -2102,22 +2176,20 @@ Disabled or Enabled. ### `BPFRedirectToPeer` (config file) / `bpfRedirectToPeer` (YAML) -Controls which whether it is allowed to forward straight to the -peer side of the workload devices. It is allowed for any host L2 devices by default -(L2Only), but it breaks TCP dump on the host side of workload device as it bypasses -it on ingress. Value of Enabled also allows redirection from L3 host devices like -IPIP tunnel or Wireguard directly to the peer side of the workload's device. This -makes redirection faster, however, it breaks tools like tcpdump on the peer side. -Use Enabled with caution. +Controls whether traffic may be forwarded directly to the peer side of a workload’s device. +Note that the legacy "L2Only" option is now deprecated and if set it is treated like "Enabled. +Setting this option to "Enabled" allows direct redirection (including from L3 host devices such as IPIP tunnels or WireGuard), +which can improve redirection performance but causes the redirected packets to bypass the host‑side ingress path. +As a result, packet‑capture tools on the host side of the workload device (for example, tcpdump) will not see that traffic. | Detail | | | --- | --- | | Environment variable | `FELIX_BPFRedirectToPeer` | | Encoding (env var/config file) | One of: Disabled, Enabled, L2Only (case insensitive) | -| Default value (above encoding) | `L2Only` | +| Default value (above encoding) | `Enabled` | | `FelixConfiguration` field | `bpfRedirectToPeer` (YAML) `BPFRedirectToPeer` (Go API) | -| `FelixConfiguration` schema | One of: "Disabled", "Enabled", "L2Only". | -| Default value (YAML) | `L2Only` | +| `FelixConfiguration` schema | One of: "Disabled", "Enabled". | +| Default value (YAML) | `Enabled` | | Notes | Required. | ## Dataplane: Windows diff --git a/felix/environment/feature_detect_linux.go b/felix/environment/feature_detect_linux.go index df1ff9ff274..88fa404b6e0 100644 --- a/felix/environment/feature_detect_linux.go +++ b/felix/environment/feature_detect_linux.go @@ -315,7 +315,7 @@ func (d *FeatureDetector) netlinkSupportsStrict() (bool, error) { func countRulesInIptableOutput(in []byte) int { count := 0 - for _, x := range bytes.Split(in, []byte("\n")) { + for x := range bytes.SplitSeq(in, []byte("\n")) { if len(x) >= 1 && x[0] == '-' { count++ } diff --git a/felix/environment/feature_detect_test.go b/felix/environment/feature_detect_test.go index 9004ee3ec62..94a346eff3b 100644 --- a/felix/environment/feature_detect_test.go +++ b/felix/environment/feature_detect_test.go @@ -22,7 +22,7 @@ import ( "strings" "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" "golang.org/x/sys/unix" @@ -192,7 +192,6 @@ func TestFeatureDetection(t *testing.T) { }, }, } { - tst := tst t.Run("iptables version "+tst.iptablesVersion+" kernel "+tst.kernelVersion, func(t *testing.T) { RegisterTestingT(t) dataplane := testutils.NewMockDataplane("filter", map[string][]string{}, "legacy") @@ -277,7 +276,6 @@ func TestFeatureDetectionOverride(t *testing.T) { }, }, } { - tst := tst t.Run("iptables version "+tst.iptablesVersion+" kernel "+tst.kernelVersion, func(t *testing.T) { RegisterTestingT(t) dataplane := testutils.NewMockDataplane("filter", map[string][]string{}, "legacy") @@ -449,7 +447,6 @@ func TestIptablesBackendDetection(t *testing.T) { "legacy", }, } { - tst := tst t.Run("DetectingBackend, testing "+tst.name, func(t *testing.T) { RegisterTestingT(t) Expect(DetectBackend(testutils.LookPathAll, tst.cmdF.NewCmd, tst.spec)).To(Equal(tst.expectedBackend)) @@ -528,10 +525,10 @@ func (d *ipOutputCmd) Output() ([]byte, error) { out := []byte{} for i := 0; i < d.outKube; i++ { - out = append(out, []byte(fmt.Sprintf("KUBE-IPTABLES-HINT - [0:0] %d\n", i))...) + out = append(out, fmt.Appendf(nil, "KUBE-IPTABLES-HINT - [0:0] %d\n", i)...) } for i := 0; i < d.out-d.outKube; i++ { - out = append(out, []byte(fmt.Sprintf("-Output line %d\n", i))...) + out = append(out, fmt.Appendf(nil, "-Output line %d\n", i)...) } return out, nil diff --git a/felix/environment/versionparse.go b/felix/environment/versionparse.go index 9a388dabdfd..9bf687703ae 100644 --- a/felix/environment/versionparse.go +++ b/felix/environment/versionparse.go @@ -65,11 +65,8 @@ func MustParseVersion(v string) *Version { func (v *Version) Compare(other *Version) int { vlen := len(v.versionSlice) olen := len(other.versionSlice) - compLen := vlen - if compLen > olen { - compLen = olen - } - for index := 0; index < compLen; index++ { + compLen := min(vlen, olen) + for index := range compLen { if v.versionSlice[index] == other.versionSlice[index] { continue } diff --git a/felix/felix_suite_test.go b/felix/felix_suite_test.go index 29795151a01..b7a5d42ab4d 100644 --- a/felix/felix_suite_test.go +++ b/felix/felix_suite_test.go @@ -3,11 +3,19 @@ package felix_test import ( "testing" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + + "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) +func init() { + testutils.HookLogrusForGinkgo() +} + func TestFelix(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Felix Suite") + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "./report/felix_suite.xml" + ginkgo.RunSpecs(t, "UT: felix", suiteConfig, reporterConfig) } diff --git a/felix/fv/.gitignore b/felix/fv/.gitignore index 9ed3b07cefe..4a1887ace43 100644 --- a/felix/fv/.gitignore +++ b/felix/fv/.gitignore @@ -1 +1,2 @@ *.test +win-fv.exe diff --git a/felix/fv/apply_on_forward_test.go b/felix/fv/apply_on_forward_test.go index 93c393b12c6..a22c6bbb351 100644 --- a/felix/fv/apply_on_forward_test.go +++ b/felix/fv/apply_on_forward_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -23,7 +21,7 @@ import ( "strings" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -64,41 +62,16 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ apply on forward tests; wit for ii := range w { wIP := fmt.Sprintf("10.65.%d.2", ii) wName := fmt.Sprintf("w%d", ii) + infrastructure.AssignIP(wName, wIP, tc.Felixes[ii].Hostname, client) w[ii] = workload.Run(tc.Felixes[ii], wName, "default", wIP, "8055", "tcp") w[ii].ConfigureInInfra(infra) hostW[ii] = workload.Run(tc.Felixes[ii], fmt.Sprintf("host%d", ii), "", tc.Felixes[ii].IP, "8055", "tcp") } - cc = &connectivity.Checker{} - }) - - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - if NFTMode() { - logNFTDiags(felix) - } else { - felix.Exec("iptables-save", "-c") - felix.Exec("ipset", "list") - } - felix.Exec("ip", "r") - felix.Exec("ip", "a") - } - } + ensureRoutesProgrammed(tc.Felixes) - for _, wl := range w { - wl.Stop() - } - for _, wl := range hostW { - wl.Stop() - } - - tc.Stop() - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() + cc = &connectivity.Checker{} }) itShouldHaveWorkloadToWorkloadAndHostConnectivity := func() { diff --git a/felix/fv/aws_test.go b/felix/fv/aws_test.go index fb964ed4f5e..b9997210c4c 100644 --- a/felix/fv/aws_test.go +++ b/felix/fv/aws_test.go @@ -12,15 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( "fmt" "regexp" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/fv/infrastructure" @@ -57,16 +55,8 @@ var _ = infrastructure.DatastoreDescribe("AWS-ec2-srcdstcheck", []apiconfig.Data tc.Felixes[0].TriggerDelayedStart() }) - AfterEach(func() { - tc.Stop() - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) - getHTTPStatus := func(url string) (string, error) { - op, err := tc.Felixes[0].Container.ExecOutput("wget", "-S", "-T", "2", "-O", "-", "-o", "/dev/stdout", url) + op, err := tc.Felixes[0].ExecOutput("wget", "-S", "-T", "2", "-O", "-", "-o", "/dev/stdout", url) // Return output even when the error is set. // this is useful when wget sets err, in case of bad health status. return op, err diff --git a/felix/fv/bpf_attach_test.go b/felix/fv/bpf_attach_test.go index 817fe286527..4955f7be09c 100644 --- a/felix/fv/bpf_attach_test.go +++ b/felix/fv/bpf_attach_test.go @@ -12,15 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( "encoding/json" "os" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/bpf/ifstate" @@ -61,15 +59,6 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Felix bpf reattach object", Expect(err).NotTo(HaveOccurred()) }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - - tc.Stop() - infra.Stop() - }) - It("should clean up programs when BPFDataIfacePattern changes", func() { By("Starting Felix") felix.TriggerDelayedStart() diff --git a/felix/fv/bpf_counters_test.go b/felix/fv/bpf_counters_test.go index be1890e6a08..fe9210cf479 100644 --- a/felix/fv/bpf_counters_test.go +++ b/felix/fv/bpf_counters_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -22,7 +20,7 @@ import ( "strconv" "strings" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" @@ -37,7 +35,6 @@ import ( ) var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Felix bpf test counters", []apiconfig.DatastoreType{apiconfig.EtcdV3}, func(getInfra infrastructure.InfraFactory) { - if !BPFMode() { return } @@ -54,7 +51,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Felix bpf test counters", [ opts := infrastructure.DefaultTopologyOptions() opts.ExtraEnvVars["FELIX_BPFPolicyDebugEnabled"] = "true" tc, calicoClient = infrastructure.StartNNodeTopology(1, opts, infra) - for i := 0; i < 2; i++ { + for i := range 2 { wIP := fmt.Sprintf("10.65.0.%d", i+2) w[i] = workload.Run(tc.Felixes[0], fmt.Sprintf("w%d", i), "default", wIP, "8055", "tcp") w[i].WorkloadEndpoint.Labels = map[string]string{"name": w[i].Name} @@ -63,21 +60,6 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Felix bpf test counters", [ ensureBPFProgramsAttached(tc.Felixes[0]) }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - for _, felix := range tc.Felixes { - felix.Exec("calico-bpf", "counters", "dump") - } - } - - for i := 0; i < 2; i++ { - w[i].Stop() - } - tc.Stop() - infra.Stop() - }) - createPolicy := func(policy *api.GlobalNetworkPolicy) *api.GlobalNetworkPolicy { log.WithField("policy", dumpResource(policy)).Info("Creating policy") policy, err := calicoClient.GlobalNetworkPolicies().Create(utils.Ctx, policy, utils.NoOptions) @@ -99,25 +81,28 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Felix bpf test counters", [ pol.Namespace = "default" pol.Name = "drop-workload0-to-workload1" pol.Spec.Selector = "all()" - pol.Spec.Ingress = []api.Rule{{ - Action: "Deny", - Source: api.EntityRule{ - Nets: []string{fmt.Sprintf("%s/32", w[0].IP)}, + pol.Spec.Ingress = []api.Rule{ + { + Action: "Deny", + Source: api.EntityRule{ + Nets: []string{fmt.Sprintf("%s/32", w[0].IP)}, + }, + Destination: api.EntityRule{ + Nets: []string{fmt.Sprintf("%s/32", w[1].IP)}, + }, }, - Destination: api.EntityRule{ - Nets: []string{fmt.Sprintf("%s/32", w[1].IP)}, - }}, { Action: api.Allow, - }} + }, + } pol.Spec.Egress = []api.Rule{{Action: api.Allow}} pol = createPolicy(pol) - bpfWaitForPolicy(tc.Felixes[0], w[1].InterfaceName, "ingress", "default.drop-workload0-to-workload1") + bpfWaitForGlobalNetworkPolicy(tc.Felixes[0], w[1].InterfaceName, "ingress", "drop-workload0-to-workload1") By("generating packets and checking the counter") numberOfpackets := 10 - for i := 0; i < numberOfpackets; i++ { + for i := range numberOfpackets { _, err := w[0].RunCmd("pktgen", w[0].IP, w[1].IP, "udp", "--port-dst", "8055", "--ip-id", strconv.Itoa(i+1)) Expect(err).NotTo(HaveOccurred()) _, err = w[0].RunCmd("pktgen", w[0].IP, tc.Felixes[0].IP, "udp", "--port-dst", "8055") @@ -129,7 +114,6 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Felix bpf test counters", [ }) It("should update rule counters", func() { - pol := api.NewGlobalNetworkPolicy() pol.Namespace = "fv" pol.Name = "policy-test" @@ -139,22 +123,22 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Felix bpf test counters", [ pol = createPolicy(pol) Eventually(func() bool { - return bpfCheckIfPolicyProgrammed(tc.Felixes[0], w[0].InterfaceName, "ingress", "default.policy-test", "deny", true) + return bpfCheckIfGlobalNetworkPolicyProgrammed(tc.Felixes[0], w[0].InterfaceName, "ingress", "policy-test", "deny", true) }, "2s", "200ms").Should(BeTrue()) Eventually(func() bool { - return bpfCheckIfPolicyProgrammed(tc.Felixes[0], w[0].InterfaceName, "egress", "default.policy-test", "deny", true) + return bpfCheckIfGlobalNetworkPolicyProgrammed(tc.Felixes[0], w[0].InterfaceName, "egress", "policy-test", "deny", true) }, "2s", "200ms").Should(BeTrue()) Eventually(func() bool { - return bpfCheckIfPolicyProgrammed(tc.Felixes[0], w[1].InterfaceName, "ingress", "default.policy-test", "deny", true) + return bpfCheckIfGlobalNetworkPolicyProgrammed(tc.Felixes[0], w[1].InterfaceName, "ingress", "policy-test", "deny", true) }, "2s", "200ms").Should(BeTrue()) Eventually(func() bool { - return bpfCheckIfPolicyProgrammed(tc.Felixes[0], w[1].InterfaceName, "egress", "default.policy-test", "deny", true) + return bpfCheckIfGlobalNetworkPolicyProgrammed(tc.Felixes[0], w[1].InterfaceName, "egress", "policy-test", "deny", true) }, "2s", "200ms").Should(BeTrue()) - for i := 0; i < 10; i++ { + for range 10 { _, err := w[1].RunCmd("pktgen", w[1].IP, w[0].IP, "udp", "--port-src", "8055", "--port-dst", "8055") Expect(err).NotTo(HaveOccurred()) } @@ -164,29 +148,29 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Felix bpf test counters", [ Expect(v).To(Equal(uint64(10))) } - checkRuleCounters(tc.Felixes[0], w[1].InterfaceName, "egress", "default.policy-test", 10) + checkRuleCounters(tc.Felixes[0], w[1].InterfaceName, "egress", "policy-test", 10) pol.Spec.Ingress = []api.Rule{{Action: "Allow"}} pol.Spec.Egress = []api.Rule{{Action: "Allow"}} pol = updatePolicy(pol) Eventually(func() bool { - return bpfCheckIfPolicyProgrammed(tc.Felixes[0], w[0].InterfaceName, "ingress", "default.policy-test", "allow", true) + return bpfCheckIfGlobalNetworkPolicyProgrammed(tc.Felixes[0], w[0].InterfaceName, "ingress", "policy-test", "allow", true) }, "2s", "200ms").Should(BeTrue()) Eventually(func() bool { - return bpfCheckIfPolicyProgrammed(tc.Felixes[0], w[0].InterfaceName, "egress", "default.policy-test", "allow", true) + return bpfCheckIfGlobalNetworkPolicyProgrammed(tc.Felixes[0], w[0].InterfaceName, "egress", "policy-test", "allow", true) }, "2s", "200ms").Should(BeTrue()) Eventually(func() bool { - return bpfCheckIfPolicyProgrammed(tc.Felixes[0], w[1].InterfaceName, "ingress", "default.policy-test", "allow", true) + return bpfCheckIfGlobalNetworkPolicyProgrammed(tc.Felixes[0], w[1].InterfaceName, "ingress", "policy-test", "allow", true) }, "2s", "200ms").Should(BeTrue()) Eventually(func() bool { - return bpfCheckIfPolicyProgrammed(tc.Felixes[0], w[1].InterfaceName, "egress", "default.policy-test", "allow", true) + return bpfCheckIfGlobalNetworkPolicyProgrammed(tc.Felixes[0], w[1].InterfaceName, "egress", "policy-test", "allow", true) }, "2s", "200ms").Should(BeTrue()) - for i := 0; i < 10; i++ { + for range 10 { _, err := w[1].RunCmd("pktgen", w[1].IP, w[0].IP, "udp", "--port-src", "8055", "--port-dst", "8055") Expect(err).NotTo(HaveOccurred()) } @@ -199,26 +183,26 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Felix bpf test counters", [ Expect(v).To(Equal(uint64(1))) } - checkRuleCounters(tc.Felixes[0], w[1].InterfaceName, "egress", "default.policy-test", 1) - checkRuleCounters(tc.Felixes[0], w[0].InterfaceName, "ingress", "default.policy-test", 1) + checkRuleCounters(tc.Felixes[0], w[1].InterfaceName, "egress", "policy-test", 1) + checkRuleCounters(tc.Felixes[0], w[0].InterfaceName, "ingress", "policy-test", 1) _, err := calicoClient.GlobalNetworkPolicies().Delete(context.Background(), "policy-test", options2.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) Eventually(func() bool { - return bpfCheckIfPolicyProgrammed(tc.Felixes[0], w[0].InterfaceName, "ingress", "default.policy-test", "allow", true) + return bpfCheckIfGlobalNetworkPolicyProgrammed(tc.Felixes[0], w[0].InterfaceName, "ingress", "policy-test", "allow", true) }, "2s", "200ms").ShouldNot(BeTrue()) Eventually(func() bool { - return bpfCheckIfPolicyProgrammed(tc.Felixes[0], w[0].InterfaceName, "egress", "default.policy-test", "allow", true) + return bpfCheckIfGlobalNetworkPolicyProgrammed(tc.Felixes[0], w[0].InterfaceName, "egress", "policy-test", "allow", true) }, "2s", "200ms").ShouldNot(BeTrue()) Eventually(func() bool { - return bpfCheckIfPolicyProgrammed(tc.Felixes[0], w[1].InterfaceName, "ingress", "default.policy-test", "allow", true) + return bpfCheckIfGlobalNetworkPolicyProgrammed(tc.Felixes[0], w[1].InterfaceName, "ingress", "policy-test", "allow", true) }, "2s", "200ms").ShouldNot(BeTrue()) Eventually(func() bool { - return bpfCheckIfPolicyProgrammed(tc.Felixes[0], w[1].InterfaceName, "egress", "default.policy-test", "allow", true) + return bpfCheckIfGlobalNetworkPolicyProgrammed(tc.Felixes[0], w[1].InterfaceName, "egress", "policy-test", "allow", true) }, "2s", "200ms").ShouldNot(BeTrue()) Eventually(func() int { @@ -242,7 +226,7 @@ func checkRuleCounters(felix *infrastructure.Felix, ifName, hook, polName string startOfPol := -1 for idx, str := range strOut { - if strings.Contains(str, fmt.Sprintf("Start of policy %s", polName)) { + if strings.Contains(str, fmt.Sprintf("Start of GlobalNetworkPolicy %s", polName)) { startOfPol = idx break } diff --git a/felix/fv/bpf_dual_stack_test.go b/felix/fv/bpf_dual_stack_test.go index 1cefe62738f..212f43a28a2 100644 --- a/felix/fv/bpf_dual_stack_test.go +++ b/felix/fv/bpf_dual_stack_test.go @@ -12,19 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( "context" "fmt" + "maps" "net" "regexp" "strconv" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" @@ -41,15 +40,14 @@ import ( "github.com/projectcalico/calico/felix/fv/workload" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" - "github.com/projectcalico/calico/libcalico-go/lib/ipam" - cnet "github.com/projectcalico/calico/libcalico-go/lib/net" - options2 "github.com/projectcalico/calico/libcalico-go/lib/options" ) var ( _ = describeBPFDualStackTests(false, true) _ = describeBPFDualStackTests(true, true) _ = describeBPFDualStackTests(false, false) + + _ = describeBPFDualStackProxyHealthTests() ) func describeBPFDualStackTests(ctlbEnabled, ipv6Dataplane bool) bool { @@ -70,11 +68,11 @@ func describeBPFDualStackTests(ctlbEnabled, ipv6Dataplane bool) bool { ) felixIP := func(f int) string { - return tc.Felixes[f].Container.IP + return tc.Felixes[f].IP } felixIP6 := func(f int) string { - return tc.Felixes[f].Container.IPv6 + return tc.Felixes[f].IPv6 } BeforeEach(func() { @@ -92,9 +90,7 @@ func describeBPFDualStackTests(ctlbEnabled, ipv6Dataplane bool) bool { opts.EnableIPv6 = true opts.NATOutgoingEnabled = true opts.AutoHEPsEnabled = false - // Simulate BIRD noEncap routes since IPIP is not supported with IPv6. opts.IPIPMode = api.IPIPModeNever - opts.SimulateBIRDRoutes = true opts.DelayFelixStart = true if ipv6Dataplane { @@ -124,42 +120,25 @@ func describeBPFDualStackTests(ctlbEnabled, ipv6Dataplane bool) bool { } wIP := fmt.Sprintf("10.65.%d.%d", ii, wi+2) + wIPv6 := fmt.Sprintf("dead:beef::%d:%d", ii, wi+2) wName := fmt.Sprintf("w%d%d", ii, wi) + infrastructure.AssignIP(wName, wIP, tc.Felixes[ii].Hostname, calicoClient) + infrastructure.AssignIP(wName, wIPv6, tc.Felixes[ii].Hostname, calicoClient) + w := workload.New(tc.Felixes[ii], wName, "default", - wIP, strconv.Itoa(port), "tcp", workload.WithIPv6Address(net.ParseIP(fmt.Sprintf("dead:beef::%d:%d", ii, wi+2)).String())) + wIP, strconv.Itoa(port), "tcp", workload.WithIPv6Address(wIPv6)) labels["name"] = w.Name labels["workload"] = "regular" w.WorkloadEndpoint.Labels = labels if run { - err := w.Start() + err := w.Start(infra) Expect(err).NotTo(HaveOccurred()) w.ConfigureInInfra(infra) } - if opts.UseIPPools { - // Assign the workload's IP in IPAM, this will trigger calculation of routes. - err := calicoClient.IPAM().AssignIP(context.Background(), ipam.AssignIPArgs{ - IP: cnet.MustParseIP(w.IP), - HandleID: &w.Name, - Attrs: map[string]string{ - ipam.AttributeNode: tc.Felixes[ii].Hostname, - }, - Hostname: tc.Felixes[ii].Hostname, - }) - Expect(err).NotTo(HaveOccurred()) - err = calicoClient.IPAM().AssignIP(context.Background(), ipam.AssignIPArgs{ - IP: cnet.MustParseIP(w.IP6), - HandleID: &w.Name, - Attrs: map[string]string{ - ipam.AttributeNode: tc.Felixes[ii].Hostname, - }, - Hostname: tc.Felixes[ii].Hostname, - }) - } - return w } @@ -205,43 +184,10 @@ func describeBPFDualStackTests(ctlbEnabled, ipv6Dataplane bool) bool { currBpfepsV6 []nat.BackendMapMemV6 ) - currBpfsvcsV6, currBpfepsV6 = dumpNATmapsV6(tc.Felixes) - currBpfsvcs, currBpfeps = dumpNATmaps(tc.Felixes) + currBpfsvcsV6, currBpfepsV6, _ = dumpNATmapsV6(tc.Felixes) + currBpfsvcs, currBpfeps, _ = dumpNATmaps(tc.Felixes) for i, felix := range tc.Felixes { - if NFTMode() { - logNFTDiags(felix) - } else { - felix.Exec("ip6tables-save", "-c") - felix.Exec("iptables-save", "-c") - } - - felix.Exec("conntrack", "-L") - felix.Exec("ip", "-6", "link") - felix.Exec("ip", "-6", "addr") - felix.Exec("ip", "-6", "rule") - felix.Exec("ip", "-6", "route") - felix.Exec("ip", "-6", "neigh") - felix.Exec("calico-bpf", "-6", "ipsets", "dump") - felix.Exec("calico-bpf", "-6", "routes", "dump") - felix.Exec("calico-bpf", "-6", "nat", "dump") - felix.Exec("calico-bpf", "-6", "nat", "aff") - felix.Exec("calico-bpf", "-6", "conntrack", "dump") - felix.Exec("calico-bpf", "-6", "arp", "dump") - felix.Exec("ip", "link") - felix.Exec("ip", "addr") - felix.Exec("ip", "rule") - felix.Exec("ip", "route") - felix.Exec("ip", "neigh") - felix.Exec("arp") - felix.Exec("calico-bpf", "ipsets", "dump") - felix.Exec("calico-bpf", "routes", "dump") - felix.Exec("calico-bpf", "nat", "dump") - felix.Exec("calico-bpf", "nat", "aff") - felix.Exec("calico-bpf", "conntrack", "dump") - felix.Exec("calico-bpf", "arp", "dump") - felix.Exec("calico-bpf", "counters", "dump") - felix.Exec("calico-bpf", "ifstate", "dump") log.Infof("[%d]FrontendMapV6: %+v", i, currBpfsvcsV6[i]) log.Infof("[%d]NATBackendV6: %+v", i, currBpfepsV6[i]) log.Infof("[%d]SendRecvMapV6: %+v", i, dumpSendRecvMapV6(felix)) @@ -252,22 +198,6 @@ func describeBPFDualStackTests(ctlbEnabled, ipv6Dataplane bool) bool { } }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - - for i := 0; i < 2; i++ { - for j := 0; j < 2; j++ { - w[i][j].Stop() - } - } - _, err := calicoClient.GlobalNetworkPolicies().Delete(context.Background(), "policy-1", options2.DeleteOptions{}) - Expect(err).NotTo(HaveOccurred()) - tc.Stop() - infra.Stop() - }) - if !ipv6Dataplane { JustBeforeEach(func() { tc.TriggerDelayedStart() @@ -275,6 +205,7 @@ func describeBPFDualStackTests(ctlbEnabled, ipv6Dataplane bool) bool { ensureRightIFStateFlags(tc.Felixes[1], ifstate.FlgIPv4Ready, ifstate.FlgHEP, nil) }) It("should drop ipv6 packets at workload interface and allow ipv6 packets at host interface when in IPv4 only mode", func() { + cc.ResetExpectations() // IPv4 connectivity must work. cc.Expect(Some, hostW[0], w[0][0]) cc.Expect(Some, hostW[0], hostW[1]) @@ -304,13 +235,14 @@ func describeBPFDualStackTests(ctlbEnabled, ipv6Dataplane bool) bool { k8sClient = infra.(*infrastructure.K8sDatastoreInfra).K8sClient _ = k8sClient testSvc = k8sServiceForDualStack("test-svc", clusterIPs, w[0][0], 80, 8055, int32(npPort), "tcp") - testSvcNamespace = testSvc.ObjectMeta.Namespace + testSvcNamespace = testSvc.Namespace _, err := k8sClient.CoreV1().Services(testSvcNamespace).Create(context.Background(), testSvc, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - Eventually(k8sGetEpsForServiceFunc(k8sClient, testSvc), "10s").Should(HaveLen(1), + Eventually(checkSvcEndpoints(k8sClient, testSvc), "10s").Should(Equal(2), "Service endpoints didn't get created? Is controller-manager happy?") }) It("Should connect to w[0][0] from all other workloads with IPv4 and IPv6", func() { + cc.ResetExpectations() cc.ExpectSome(w[0][1], w[0][0]) cc.ExpectSome(w[1][0], w[0][0]) cc.ExpectSome(w[1][1], w[0][0]) @@ -322,6 +254,7 @@ func describeBPFDualStackTests(ctlbEnabled, ipv6Dataplane bool) bool { }) It("Should connect to w[0][0] via clusterIP (IPv4 and IPv6)", func() { + cc.ResetExpectations() port := uint16(testSvc.Spec.Ports[0].Port) cc.ExpectSome(w[1][0], TargetIP(clusterIPs[0]), port) cc.ExpectSome(w[1][0], TargetIP(clusterIPs[1]), port) @@ -332,6 +265,7 @@ func describeBPFDualStackTests(ctlbEnabled, ipv6Dataplane bool) bool { }) It("Should connect to w[0][0] via nodePort (IPv4 and IPv6)", func() { + cc.ResetExpectations() cc.ExpectSome(w[1][0], TargetIP(felixIP(0)), npPort) cc.ExpectSome(w[0][1], TargetIP(felixIP(0)), npPort) @@ -351,7 +285,7 @@ func describeBPFDualStackTests(ctlbEnabled, ipv6Dataplane bool) bool { node, err := k8sClient.CoreV1().Nodes().Get(context.Background(), tc.Felixes[0].Hostname, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) - delete(node.ObjectMeta.Annotations, "projectcalico.org/IPv6Address") + delete(node.Annotations, "projectcalico.org/IPv6Address") _, err = k8sClient.CoreV1().Nodes().Update(context.Background(), node, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -365,6 +299,7 @@ func describeBPFDualStackTests(ctlbEnabled, ipv6Dataplane bool) bool { ensureRightIFStateFlags(tc.Felixes[0], ifstate.FlgIPv4Ready, ifstate.FlgHEP, nil) ensureRightIFStateFlags(tc.Felixes[1], ifstate.FlgIPv4Ready|ifstate.FlgIPv6Ready, ifstate.FlgHEP, nil) + cc.ResetExpectations() cc.ExpectSome(w[0][1], w[0][0]) cc.ExpectSome(w[1][0], w[0][0]) cc.ExpectSome(w[1][1], w[0][0]) @@ -375,18 +310,17 @@ func describeBPFDualStackTests(ctlbEnabled, ipv6Dataplane bool) bool { cc.Expect(Some, hostW[0], hostW[1], ExpectWithIPVersion(6)) cc.CheckConnectivity() - cc.ResetExpectations() // Since we allow the IPv6 packets through IPv4 programs, a stale neighbor entry might get created // when trying to reach w[0][0] from workloads in felix-1. This will impact subsequent // tests. This does not seem to be a problem with ubuntu 22+ but is on ubuntu 20. // Hence cleaning up the neighbor entry. - tc.Felixes[0].Exec("ip", "-6", "neigh", "del", w[0][0].IP6, "dev", w[0][0].InterfaceName) + _ = tc.Felixes[0].ExecMayFail("ip", "-6", "neigh", "del", w[0][0].IP6, "dev", w[0][0].InterfaceName) // Add the node IPv6 address node, err = k8sClient.CoreV1().Nodes().Get(context.Background(), tc.Felixes[0].Hostname, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) - node.ObjectMeta.Annotations["projectcalico.org/IPv6Address"] = fmt.Sprintf("%s/%s", felixIP6(0), tc.Felixes[0].IPv6Prefix) + node.Annotations["projectcalico.org/IPv6Address"] = fmt.Sprintf("%s/%s", felixIP6(0), tc.Felixes[0].IPv6Prefix) _, err = k8sClient.CoreV1().Nodes().Update(context.Background(), node, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -397,6 +331,7 @@ func describeBPFDualStackTests(ctlbEnabled, ipv6Dataplane bool) bool { Expect(err).NotTo(HaveOccurred()) ensureRightIFStateFlags(tc.Felixes[0], ifstate.FlgIPv4Ready|ifstate.FlgIPv6Ready, ifstate.FlgHEP, nil) + cc.ResetExpectations() cc.ExpectSome(w[0][1], w[0][0]) cc.ExpectSome(w[1][0], w[0][0]) cc.ExpectSome(w[1][1], w[0][0]) @@ -409,7 +344,7 @@ func describeBPFDualStackTests(ctlbEnabled, ipv6Dataplane bool) bool { It("should be able to ping external client from w[0][0]", func() { tc.TriggerDelayedStart() - externalClient := infrastructure.RunExtClient("ext-client") + externalClient := infrastructure.RunExtClient(infra, "ext-client") _ = externalClient ensureRightIFStateFlags(tc.Felixes[0], ifstate.FlgIPv4Ready|ifstate.FlgIPv6Ready, ifstate.FlgHEP, nil) ensureRightIFStateFlags(tc.Felixes[1], ifstate.FlgIPv4Ready|ifstate.FlgIPv6Ready, ifstate.FlgHEP, nil) @@ -420,14 +355,12 @@ func describeBPFDualStackTests(ctlbEnabled, ipv6Dataplane bool) bool { felixIP6(0), externalClient.IPv6) tcpdump.AddMatcher("ICMP", regexp.MustCompile(matcher)) - tcpdump.Start() - defer tcpdump.Stop() + tcpdump.Start(infra) _, err := w[0][0].ExecCombinedOutput("ping6", "-c", "2", externalClient.IPv6) Expect(err).NotTo(HaveOccurred()) Eventually(func() int { return tcpdump.MatchCount("ICMP") }). Should(BeNumerically(">", 0), matcher) - externalClient.Stop() }) It("should have both IPv4 and IPv6 routes for a dual stack UDP service", func() { @@ -436,10 +369,10 @@ func describeBPFDualStackTests(ctlbEnabled, ipv6Dataplane bool) bool { k8sClient = infra.(*infrastructure.K8sDatastoreInfra).K8sClient _ = k8sClient testSvc = k8sServiceForDualStack("test-svc", clusterIPs, w[0][0], 80, 8055, int32(npPort), "udp") - testSvcNamespace = testSvc.ObjectMeta.Namespace + testSvcNamespace = testSvc.Namespace _, err := k8sClient.CoreV1().Services(testSvcNamespace).Create(context.Background(), testSvc, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - Eventually(k8sGetEpsForServiceFunc(k8sClient, testSvc), "10s").Should(HaveLen(1), + Eventually(checkSvcEndpoints(k8sClient, testSvc), "10s").Should(Equal(2), "Service endpoints didn't get created? Is controller-manager happy?") Eventually(func() bool { return checkServiceRoute(tc.Felixes[0], testSvc.Spec.ClusterIPs[0]) @@ -466,12 +399,13 @@ func describeBPFDualStackTests(ctlbEnabled, ipv6Dataplane bool) bool { for _, f := range tc.Felixes { felixReady := func() int { - return healthStatus("["+f.IPv6+"]", "9099", "readiness") + return healthStatus(f.IPv6, "9099", "readiness") } Eventually(felixReady, "10s", "330ms").Should(BeGood()) Consistently(felixReady, "10s", "1s").Should(BeGood()) } + cc.ResetExpectations() cc.Expect(None, w[0][1], w[0][0]) cc.Expect(None, w[1][0], w[0][0]) cc.Expect(None, w[1][1], w[0][0]) @@ -507,6 +441,7 @@ func describeBPFDualStackTests(ctlbEnabled, ipv6Dataplane bool) bool { Consistently(felixReady, "10s", "1s").Should(BeGood()) } + cc.ResetExpectations() cc.Expect(None, w[0][1], w[0][0], ExpectWithIPVersion(6)) cc.Expect(None, w[1][0], w[0][0], ExpectWithIPVersion(6)) cc.Expect(None, w[1][1], w[0][0], ExpectWithIPVersion(6)) @@ -521,38 +456,138 @@ func describeBPFDualStackTests(ctlbEnabled, ipv6Dataplane bool) bool { }) } +func describeBPFDualStackProxyHealthTests() bool { + if !BPFMode() { + return true + } + desc := "_BPF_ _BPF-SAFE_ BPF dual stack kube-proxy health checking tests" + return infrastructure.DatastoreDescribe(desc, []apiconfig.DatastoreType{apiconfig.Kubernetes}, func(getInfra infrastructure.InfraFactory) { + var ( + infra infrastructure.DatastoreInfra + tc infrastructure.TopologyContainers + k8sClient *kubernetes.Clientset + ) + + BeforeEach(func() { + iOpts := []infrastructure.CreateOption{ + infrastructure.K8sWithDualStack(), + infrastructure.K8sWithAPIServerBindAddress("::"), + infrastructure.K8sWithServiceClusterIPRange("dead:beef::abcd:0:0:0/112,10.101.0.0/16"), + } + infra = getInfra(iOpts...) + opts := infrastructure.DefaultTopologyOptions() + opts.EnableIPv6 = true + opts.IPIPMode = api.IPIPModeNever + opts.NATOutgoingEnabled = true + opts.BPFProxyHealthzPort = 10256 + opts.IPIPMode = api.IPIPModeNever + + tc, _ = infrastructure.StartNNodeTopology(2, opts, infra) + k8sClient = infra.(*infrastructure.K8sDatastoreInfra).K8sClient + }) + + AfterEach(func() { + tc.Stop() + infra.Stop() + }) + + It("should have kube-proxy health check working over both IPv4 and IPv6", func() { + felix := tc.Felixes[0] + + felixReady := func(ip string) int { + return healthStatus(ip, "10256", "healthz") + } + + Eventually(func() int { return felixReady(felix.IP) }, "10s", "330ms").Should(BeGood()) + Eventually(func() int { return felixReady(felix.IPv6) }, "10s", "330ms").Should(BeGood()) + }) + + It("should have nodeport health probe working over both IPv4 and IPv6", func() { + // Create a workload on node 0 + w0 := workload.Run( + tc.Felixes[0], + "w0", + "default", + "10.65.0.2", + "8055", + "tcp", + workload.WithIPv6Address("dead:beef::0:2"), + ) + w0.WorkloadEndpoint.Labels = map[string]string{"name": w0.Name, "app": "test"} + w0.ConfigureInInfra(infra) + + // Create a NodePort service with ExternalTrafficPolicy=Local + // This will automatically get a HealthCheckNodePort allocated + clusterIPs := []string{"10.101.0.20", "dead:beef::abcd:0:0:20"} + testSvc := k8sServiceForDualStack("test-np-health", clusterIPs, w0, 80, 8055, 30080, "tcp") + testSvc.Spec.Type = v1.ServiceTypeLoadBalancer + testSvc.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyTypeLocal + healthCheckNodePort := int32(30081) + testSvc.Spec.HealthCheckNodePort = healthCheckNodePort + _, err := k8sClient.CoreV1().Services("default").Create(context.Background(), testSvc, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + Eventually(checkSvcEndpoints(k8sClient, testSvc), "10s").Should(Equal(2), + "Service endpoints didn't get created? Is controller-manager happy?") + + // Check health probe on node 0 (has local endpoint) - should return 200 + Eventually(func() int { + return healthStatus(tc.Felixes[0].IP, strconv.Itoa(int(healthCheckNodePort)), "") + }, "10s", "330ms").Should(Equal(200)) + + Eventually(func() int { + return healthStatus(tc.Felixes[0].IPv6, strconv.Itoa(int(healthCheckNodePort)), "") + }, "10s", "330ms").Should(Equal(200)) + + // Check health probe on node 1 (no local endpoint) - should return 503 + Eventually(func() int { + return healthStatus(tc.Felixes[1].IP, strconv.Itoa(int(healthCheckNodePort)), "") + }, "10s", "330ms").Should(Equal(503)) + + Eventually(func() int { + return healthStatus(tc.Felixes[1].IPv6, strconv.Itoa(int(healthCheckNodePort)), "") + }, "10s", "330ms").Should(Equal(503)) + + // Clean up + w0.Stop() + err = k8sClient.CoreV1().Services("default").Delete(context.Background(), "test-np-health", metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + }) + }) +} + func removeIPv4Address(k8sClient *kubernetes.Clientset, felix *infrastructure.Felix) { node, err := k8sClient.CoreV1().Nodes().Get(context.Background(), felix.Hostname, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) - delete(node.ObjectMeta.Annotations, "projectcalico.org/IPv4Address") + delete(node.Annotations, "projectcalico.org/IPv4Address") _, err = k8sClient.CoreV1().Nodes().Update(context.Background(), node, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) node, err = k8sClient.CoreV1().Nodes().Get(context.Background(), felix.Hostname, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) - node.Status.Addresses = []v1.NodeAddress{{Type: v1.NodeInternalIP, Address: felix.Container.IPv6}} + node.Status.Addresses = []v1.NodeAddress{{Type: v1.NodeInternalIP, Address: felix.IPv6}} _, err = k8sClient.CoreV1().Nodes().UpdateStatus(context.Background(), node, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) - felix.Exec("ip", "addr", "del", felix.Container.IP, "dev", "eth0") + felix.Exec("ip", "addr", "del", felix.IP, "dev", "eth0") } func removeIPv6Address(k8sClient *kubernetes.Clientset, felix *infrastructure.Felix) { node, err := k8sClient.CoreV1().Nodes().Get(context.Background(), felix.Hostname, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) - delete(node.ObjectMeta.Annotations, "projectcalico.org/IPv6Address") + delete(node.Annotations, "projectcalico.org/IPv6Address") _, err = k8sClient.CoreV1().Nodes().Update(context.Background(), node, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) node, err = k8sClient.CoreV1().Nodes().Get(context.Background(), felix.Hostname, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) - node.Status.Addresses = []v1.NodeAddress{{Type: v1.NodeInternalIP, Address: felix.Container.IP}} + node.Status.Addresses = []v1.NodeAddress{{Type: v1.NodeInternalIP, Address: felix.IP}} _, err = k8sClient.CoreV1().Nodes().UpdateStatus(context.Background(), node, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) - felix.Exec("ip", "-6", "addr", "del", felix.Container.IPv6+"/64", "dev", "eth0") + felix.Exec("ip", "-6", "addr", "del", felix.IPv6+"/64", "dev", "eth0") } func ensureRightIFStateFlags(felix *infrastructure.Felix, ready uint32, hostIfType uint32, additionalInterfaces map[string]uint32) { @@ -560,11 +595,7 @@ func ensureRightIFStateFlags(felix *infrastructure.Felix, ready uint32, hostIfTy "eth0": hostIfType | ready, } - if additionalInterfaces != nil { - for k, v := range additionalInterfaces { - expectedIfacesToFlags[k] = v - } - } + maps.Copy(expectedIfacesToFlags, additionalInterfaces) for _, w := range felix.Workloads { if w.Runs() { @@ -586,10 +617,7 @@ func ensureRightIFStateFlags(felix *infrastructure.Felix, ready uint32, hostIfTy numIfaces++ } } - if numIfaces != len(expectedIfacesToFlags) { - return false - } - return true + return numIfaces == len(expectedIfacesToFlags) }, "1m", "1s").Should(BeTrue()) } diff --git a/felix/fv/bpf_map_resize_test.go b/felix/fv/bpf_map_resize_test.go index e175454e8a5..39af70e1283 100644 --- a/felix/fv/bpf_map_resize_test.go +++ b/felix/fv/bpf_map_resize_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -26,7 +24,7 @@ import ( "strings" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -63,22 +61,11 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Felix bpf test configurable BeforeEach(func() { infra = getInfra() opts := infrastructure.DefaultTopologyOptions() - tc, client = infrastructure.StartNNodeTopology(1, opts, infra) + tc, client = infrastructure.StartSingleNodeTopology(opts, infra) infra.AddDefaultAllow() }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - for _, wl := range w { - wl.Stop() - } - tc.Stop() - infra.Stop() - }) - It("should copy data from old map to new map", func() { srcIP := net.IPv4(123, 123, 123, 123) dstIP := net.IPv4(121, 121, 121, 121) @@ -203,17 +190,6 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Felix bpf conntrack table d } }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - for _, wl := range w { - wl.Stop() - } - tc.Stop() - infra.Stop() - }) - It("should resize ct map when it is full", func() { // make sure that connctivity is already established cc := &connectivity.Checker{} @@ -241,7 +217,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Felix bpf conntrack table d dstIP := net.IPv4(121, 121, 121, 121) val := formatBytesWithPrefix(conntrack.NewValueNormal(now, 0, leg, leg).AsBytes()) - c := tc.Felixes[0].WatchStdoutFor(regexp.MustCompile(".*Overriding bpfMapSizeConntrack \\(10000\\) with map size growth \\(20000\\)")) + c := tc.Felixes[0].WatchStdoutFor(regexp.MustCompile(`.*Overriding bpfMapSizeConntrack \(10000\) with map size growth \(20000\)`)) line := "" // Program 10k tcp ct entries into map. This is done in batches of 2k. @@ -304,8 +280,8 @@ func getMapSize(felix *infrastructure.Felix, m maps.Map) (int, error) { return int(output["max_entries"].(float64)), nil } -func showBpfMap(felix *infrastructure.Felix, m maps.Map) (map[string]interface{}, error) { - var data map[string]interface{} +func showBpfMap(felix *infrastructure.Felix, m maps.Map) (map[string]any, error) { + var data map[string]any cmd, err := maps.ShowMapCmd(m) if err != nil { return nil, err diff --git a/felix/fv/bpf_map_upgrade_test.go b/felix/fv/bpf_map_upgrade_test.go index 0e5f73af71f..92237d90c85 100644 --- a/felix/fv/bpf_map_upgrade_test.go +++ b/felix/fv/bpf_map_upgrade_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -22,7 +20,7 @@ import ( "os" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/bpf/conntrack" @@ -51,15 +49,6 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Felix bpf test conntrack ma tc, _ = infrastructure.StartNNodeTopology(1, opts, infra) }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - - tc.Stop() - infra.Stop() - }) - It("should upgrade conntrack entries from v2 to v3", func() { // create conntrack v2 map tc.Felixes[0].Exec("calico-bpf", "conntrack", "create", "--ver=2") diff --git a/felix/fv/bpf_multi_home_test.go b/felix/fv/bpf_multi_home_test.go index 9f323f27c27..faee30fc302 100644 --- a/felix/fv/bpf_multi_home_test.go +++ b/felix/fv/bpf_multi_home_test.go @@ -12,25 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( "context" - "fmt" + "net" "regexp" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/projectcalico/calico/felix/bpf/nat" "github.com/projectcalico/calico/felix/fv/infrastructure" "github.com/projectcalico/calico/felix/fv/workload" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" - "github.com/projectcalico/calico/libcalico-go/lib/ipam" - cnet "github.com/projectcalico/calico/libcalico-go/lib/net" ) var ( @@ -41,68 +38,40 @@ func describeBPFMultiHomedTests() bool { if !BPFMode() { return true } - desc := fmt.Sprintf("_BPF_ _BPF-SAFE_ BPF multi-homed tests") + desc := "_BPF_ _BPF-SAFE_ BPF multi-homed tests" return infrastructure.DatastoreDescribe(desc, []apiconfig.DatastoreType{apiconfig.Kubernetes}, func(getInfra infrastructure.InfraFactory) { var ( - infra infrastructure.DatastoreInfra - tc infrastructure.TopologyContainers - calicoClient client.Interface - Felix *infrastructure.Felix - w *workload.Workload + infra infrastructure.DatastoreInfra + tc infrastructure.TopologyContainers + calicoClient client.Interface + Felix *infrastructure.Felix + w, eth20, eth30 *workload.Workload ) BeforeEach(func() { infra = getInfra() opts := infrastructure.DefaultTopologyOptions() - opts.IPIPMode = api.IPIPModeNever - opts.SimulateBIRDRoutes = true opts.FelixLogSeverity = "Debug" opts.ExtraEnvVars["FELIX_BPFLogLevel"] = "Debug" + opts.ExtraEnvVars["FELIX_BPFExtToServiceConnmark"] = "0x80" tc, calicoClient = infrastructure.StartNNodeTopology(2, opts, infra) Felix = tc.Felixes[0] + infrastructure.AssignIP("workload", "10.65.0.2", Felix.Hostname, calicoClient) w = workload.New(Felix, "workload", "default", "10.65.0.2", "8055", "tcp") - err := w.Start() + err := w.Start(infra) Expect(err).NotTo(HaveOccurred()) w.ConfigureInInfra(infra) - err = calicoClient.IPAM().AssignIP(context.Background(), ipam.AssignIPArgs{ - IP: cnet.MustParseIP(w.IP), - HandleID: &w.Name, - Attrs: map[string]string{ - ipam.AttributeNode: Felix.Hostname, - }, - Hostname: Felix.Hostname, - }) - Expect(err).NotTo(HaveOccurred()) ensureBPFProgramsAttached(tc.Felixes[0]) - }) - - AfterEach(func() { - tc.Stop() - infra.Stop() - }) - - JustAfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - Felix.Exec("conntrack", "-L", "-f", "ipv6") - Felix.Exec("ip6tables-save", "-c") - Felix.Exec("ip", "link") - Felix.Exec("ip", "addr") - Felix.Exec("ip", "rule") - Felix.Exec("ip", "route", "show", "table", "all") - Felix.Exec("calico-bpf", "routes", "dump") - } - }) - It("should allow asymmetric routing", func() { By("setting up node's fake external iface") // We name the iface eth20 since such ifaces are // treated by felix as external to the node // // Using a test-workload creates the namespaces and the // interfaces to emulate the host NICs - eth20 := &workload.Workload{ + eth20 = &workload.Workload{ Name: "eth20", C: Felix.Container, IP: "192.168.20.1", @@ -111,10 +80,10 @@ func describeBPFMultiHomedTests() bool { InterfaceName: "eth20", MTU: 1500, // Need to match host MTU or felix will restart. } - err := eth20.Start() + err = eth20.Start(infra) Expect(err).NotTo(HaveOccurred()) - eth30 := &workload.Workload{ + eth30 = &workload.Workload{ Name: "eth30", C: Felix.Container, IP: "192.168.30.1", @@ -123,7 +92,7 @@ func describeBPFMultiHomedTests() bool { InterfaceName: "eth30", MTU: 1500, // Need to match host MTU or felix will restart. } - err = eth30.Start() + err = eth30.Start(infra) Expect(err).NotTo(HaveOccurred()) // assign address to eth20 and add route to the .20 network @@ -138,16 +107,9 @@ func describeBPFMultiHomedTests() bool { return Felix.ExecMayFail("sysctl", "-w", "net.ipv4.conf.eth20.rp_filter=2") }, "5s", "300ms").Should(Succeed()) - Felix.Exec("ip", "addr", "add", "192.168.20.20/24", "dev", "lo") - Felix.Exec("ip", "addr", "add", "192.168.30.30/24", "dev", "eth30") - Felix.Exec("bash", "-c", "echo 200 container_route >> /etc/iproute2/rt_tables") - Felix.Exec("ip", "route", "add", "10.65.1.0/24", "dev", "eth20", - "table", "container_route") - Felix.Exec("ip", "rule", "add", "from", w.IP, "table", "container_route") - Felix.Exec("ip", "route", "flush", "cache") - Felix.Exec("ip", "neigh", "add", "10.65.1.3", "lladdr", "ee:ee:ee:ee:ee:ee", "dev", "eth20") - - Felix.Exec("ip", "route", "add", "192.168.30.1/32", "dev", "eth30") + Eventually(func() error { + return Felix.ExecMayFail("sysctl", "-w", "net.ipv4.conf.eth30.rp_filter=2") + }, "5s", "300ms").Should(Succeed()) _, err = eth20.RunCmd("ip", "route", "add", "blackhole", "10.65.1.0/32") Expect(err).NotTo(HaveOccurred()) @@ -156,40 +118,38 @@ func describeBPFMultiHomedTests() bool { Expect(err).NotTo(HaveOccurred()) _, err = eth30.RunCmd("ip", "route", "add", "10.65.0.0/24", "via", "192.168.30.30", "dev", "eth0") Expect(err).NotTo(HaveOccurred()) - Felix.Exec("sysctl", "-w", "net.ipv4.conf.eth30.rp_filter=2") - _, err = w.RunCmd("bash", "-c", "echo 200 bh_route >> /etc/iproute2/rt_tables") - Expect(err).NotTo(HaveOccurred()) - _, err = w.RunCmd("ip", "rule", "add", "from", "10.65.1.3", "table", "bh_route", "priority", "1") - Expect(err).NotTo(HaveOccurred()) _, err = w.RunCmd("ip", "rule", "del", "from", "all", "lookup", "local", "priority", "0") Expect(err).NotTo(HaveOccurred()) _, err = w.RunCmd("ip", "rule", "add", "from", "all", "lookup", "local", "priority", "2") Expect(err).NotTo(HaveOccurred()) _, err = w.RunCmd("ip", "route", "add", "blackhole", w.IP+"/32") Expect(err).NotTo(HaveOccurred()) + }) - err = calicoClient.IPAM().AssignIP(context.Background(), ipam.AssignIPArgs{ - IP: cnet.MustParseIP("10.65.1.3"), - HandleID: &w.Name, - Attrs: map[string]string{ - ipam.AttributeNode: tc.Felixes[1].Hostname, - }, - Hostname: tc.Felixes[1].Hostname, - }) - Expect(err).NotTo(HaveOccurred()) + It("should allow asymmetric routing", func() { + var err error + + Felix.Exec("ip", "addr", "add", "192.168.20.20/24", "dev", "lo") + Felix.Exec("ip", "addr", "add", "192.168.30.30/24", "dev", "eth30") + Felix.Exec("bash", "-c", "echo 200 container_route >> /etc/iproute2/rt_tables") + Felix.Exec("ip", "route", "add", "10.65.1.0/24", "dev", "eth20", + "table", "container_route") + Felix.Exec("ip", "rule", "add", "from", w.IP, "table", "container_route") + Felix.Exec("ip", "route", "flush", "cache") + Felix.Exec("ip", "neigh", "add", "10.65.1.3", "lladdr", "ee:ee:ee:ee:ee:ee", "dev", "eth20") + + Felix.Exec("ip", "route", "add", "192.168.30.1/32", "dev", "eth30") dump20 := Felix.AttachTCPDump("eth20") dump20.SetLogEnabled(true) dump20.AddMatcher("eth20-egress", regexp.MustCompile("10.65.0.2.30444 > 10.65.1.3.30444: UDP")) - dump20.Start("-v", "udp", "and", "dst", "host", "10.65.1.3") - defer dump20.Stop() + dump20.Start(infra, "-v", "udp", "and", "dst", "host", "10.65.1.3") dump30 := Felix.AttachTCPDump("eth30") dump30.SetLogEnabled(true) dump30.AddMatcher("eth30-ingress", regexp.MustCompile("10.65.1.3.30444 > 10.65.0.2.30444: UDP")) - dump30.Start("-v", "udp", "and", "dst", "host", "10.65.0.2") - defer dump30.Stop() + dump30.Start(infra, "-v", "udp", "and", "dst", "host", "10.65.0.2") By("Sending packet from the workload via eth20") _, err = w.RunCmd("pktgen", w.IP, "10.65.1.3", "udp", "--ip-id", "1", @@ -214,5 +174,65 @@ func describeBPFMultiHomedTests() bool { Eventually(dump20.MatchCountFn("eth20-egress"), "5s", "330ms").Should(BeNumerically("==", 2)) Eventually(dump30.MatchCountFn("eth30-ingress"), "5s", "330ms").Should(BeNumerically("==", 2)) }) + + It("should route external traffic marked with connmark correctly", func() { + var err error + + clusterIP := "10.101.123.1" + testSvc := k8sService("test-service", clusterIP, w, 30444, 444, 30444, "udp") + k8sClient := infra.(*infrastructure.K8sDatastoreInfra).K8sClient + testSvcNamespace := testSvc.Namespace + _, err = k8sClient.CoreV1().Services(testSvcNamespace).Create(context.Background(), testSvc, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + Eventually(checkSvcEndpoints(k8sClient, testSvc), "10s").Should(Equal(1), + "Service endpoints didn't get created. Is controller-manager happy?") + + _, err = eth30.RunCmd("ip", "route", "add", clusterIP+"/32", "via", "192.168.30.30", "dev", "eth0") + Expect(err).NotTo(HaveOccurred()) + + Felix.Exec("ip", "addr", "add", "192.168.20.20/24", "dev", "eth20") + Felix.Exec("ip", "addr", "add", "192.168.30.30/24", "dev", "eth30") + + Felix.Exec("bash", "-c", "echo 200 mark_route >> /etc/iproute2/rt_tables") + Felix.Exec("ip", "route", "add", "10.65.1.0/24", "dev", "eth20", + "table", "mark_route") + Felix.Exec("ip", "rule", "add", "fwmark", "0x80/0x80", "table", "mark_route") + Felix.Exec("ip", "route", "flush", "cache") + Felix.Exec("ip", "neigh", "add", "10.65.1.3", "lladdr", "ee:ee:ee:ee:ee:ee", "dev", "eth20") + + dump20 := Felix.AttachTCPDump("eth20") + dump20.SetLogEnabled(true) + dump20.AddMatcher("eth20-egress", regexp.MustCompile(clusterIP+".30444 > 10.65.1.3.30444: UDP")) + dump20.Start(infra, "-v", "udp", "and", "src", "host", clusterIP) + + dump30 := Felix.AttachTCPDump("eth30") + dump30.SetLogEnabled(true) + dump30.AddMatcher("eth30-ingress", regexp.MustCompile("10.65.1.3.30444 > "+clusterIP+".30444: UDP")) + dump30.Start(infra, "-v", "udp", "and", "dst", "host", clusterIP) + + ip := testSvc.Spec.ClusterIP + natK := nat.NewNATKey(net.ParseIP(ip), 30444, 17) + + Eventually(func() bool { + natmaps, _, _ := dumpNATMapsAny(4, Felix) + if _, ok := natmaps[natK]; !ok { + return false + } + return true + }, "5s").Should(BeTrue(), "service NAT key didn't show up") + + By("Sending request via eth30") + _, err = eth30.RunCmd("pktgen", "10.65.1.3", clusterIP, "udp", "--ip-id", "1", + "--port-src", "30444", "--port-dst", "30444") + Expect(err).NotTo(HaveOccurred()) + + By("Sending reply from the workload via eth20") + _, err = w.RunCmd("pktgen", w.IP, "10.65.1.3", "udp", "--ip-id", "2", + "--port-src", "444", "--port-dst", "30444") + Expect(err).NotTo(HaveOccurred()) + + Eventually(dump30.MatchCountFn("eth30-ingress"), "5s", "330ms").Should(BeNumerically("==", 1)) + Eventually(dump20.MatchCountFn("eth20-egress"), "5s", "330ms").Should(BeNumerically("==", 1)) + }) }) } diff --git a/felix/fv/bpf_pol_scale_test.go b/felix/fv/bpf_pol_scale_test.go index fc1166a9ecc..127af8fc88b 100644 --- a/felix/fv/bpf_pol_scale_test.go +++ b/felix/fv/bpf_pol_scale_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -21,27 +19,26 @@ import ( "fmt" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/sirupsen/logrus" "github.com/projectcalico/calico/felix/fv/connectivity" - "github.com/projectcalico/calico/felix/fv/containers" "github.com/projectcalico/calico/felix/fv/infrastructure" "github.com/projectcalico/calico/felix/fv/workload" + "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/options" ) -var _ = Context("_BPF-SAFE_ BPF policy scale tests", func() { +var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ BPF policy scale tests", []apiconfig.DatastoreType{apiconfig.EtcdV3}, func(getInfra infrastructure.InfraFactory) { if !BPFMode() { // Non-BPF run. return } var ( - etcd *containers.Container tc infrastructure.TopologyContainers felixPID int client client.Interface @@ -51,13 +48,14 @@ var _ = Context("_BPF-SAFE_ BPF policy scale tests", func() { ) BeforeEach(func() { + infra = getInfra() topologyOptions := infrastructure.DefaultTopologyOptions() topologyOptions.FelixLogSeverity = "Info" topologyOptions.EnableIPv6 = false topologyOptions.ExtraEnvVars["FELIX_BPFLogLevel"] = "off" topologyOptions.ExtraEnvVars["FELIX_BPFMapSizeIPSets"] = "10000000" logrus.SetLevel(logrus.InfoLevel) - tc, etcd, client, infra = infrastructure.StartSingleNodeEtcdTopology(topologyOptions) + tc, client = infrastructure.StartSingleNodeTopology(topologyOptions, infra) felixPID = tc.Felixes[0].GetFelixPID() _ = felixPID w[0] = workload.Run(tc.Felixes[0], "w0", "default", "10.65.0.2", "8085", "tcp") @@ -65,19 +63,6 @@ var _ = Context("_BPF-SAFE_ BPF policy scale tests", func() { cc = &connectivity.Checker{} }) - AfterEach(func() { - for _, wl := range w { - wl.Stop() - } - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - etcd.Exec("etcdctl", "get", "/", "--prefix", "--keys-only") - } - etcd.Stop() - infra.Stop() - }) - addW0NetSet := func(numSets int) { ns := api.NewGlobalNetworkSet() ns.Name = "netset-extra" @@ -171,17 +156,17 @@ var _ = Context("_BPF-SAFE_ BPF policy scale tests", func() { // Remove one EP. w[0].RemoveFromInfra(infra) // After removing workload, we get a dummy "drop all" policy program. - Eventually(tc.Felixes[0].BPFNumPolProgramsTotalByEntryPointFn(w0PolIdxIngress), "60s", "1s").Should(Equal(1), + Eventually(tc.Felixes[0].BPFNumPolProgramsTotalByEntryPointFn(w0PolIdxIngress, "ingress"), "60s", "1s").Should(Equal(1), "w[0] ingress policy programs not cleaned up after removing ep?") - Eventually(tc.Felixes[0].BPFNumPolProgramsTotalByEntryPointFn(w0PolIdxEgress), "60s", "1s").Should(Equal(1), + Eventually(tc.Felixes[0].BPFNumPolProgramsTotalByEntryPointFn(w0PolIdxEgress, "egress"), "60s", "1s").Should(Equal(1), "w[0] egress policy programs not cleaned up after removing ep?") // Stop it, should get full cleanup now. w[0].Stop() w[0] = nil // Prevent second call to Stop in AfterEach. - Eventually(tc.Felixes[0].BPFNumPolProgramsTotalByEntryPointFn(w0PolIdxIngress), "60s", "1s").Should(Equal(0), + Eventually(tc.Felixes[0].BPFNumPolProgramsTotalByEntryPointFn(w0PolIdxIngress, "ingress"), "60s", "1s").Should(Equal(0), "w[0] ingress policy programs not cleaned up?") - Eventually(tc.Felixes[0].BPFNumPolProgramsTotalByEntryPointFn(w0PolIdxEgress), "60s", "1s").Should(Equal(0), + Eventually(tc.Felixes[0].BPFNumPolProgramsTotalByEntryPointFn(w0PolIdxEgress, "egress"), "60s", "1s").Should(Equal(0), "w[0] egress policy programs not cleaned up?") }) @@ -193,16 +178,16 @@ var _ = Context("_BPF-SAFE_ BPF policy scale tests", func() { w[0] = nil }() // After stopping workload, interface is gone and programs get cleaned up. - Eventually(tc.Felixes[0].BPFNumPolProgramsTotalByEntryPointFn(w0PolIdxIngress), "60s", "1s").Should(Equal(0), + Eventually(tc.Felixes[0].BPFNumPolProgramsTotalByEntryPointFn(w0PolIdxIngress, "ingress"), "60s", "1s").Should(Equal(0), "w[0] ingress policy programs not cleaned up?") - Eventually(tc.Felixes[0].BPFNumPolProgramsTotalByEntryPointFn(w0PolIdxEgress), "60s", "1s").Should(Equal(0), + Eventually(tc.Felixes[0].BPFNumPolProgramsTotalByEntryPointFn(w0PolIdxEgress, "egress"), "60s", "1s").Should(Equal(0), "w[0] egress policy programs not cleaned up?") // Remove should have no further effect. w[0].RemoveFromInfra(infra) - Consistently(tc.Felixes[0].BPFNumPolProgramsTotalByEntryPointFn(w0PolIdxIngress), "10s", "1s").Should(Equal(0), + Consistently(tc.Felixes[0].BPFNumPolProgramsTotalByEntryPointFn(w0PolIdxIngress, "ingress"), "10s", "1s").Should(Equal(0), "w[0] ingress policy programs came back?") - Consistently(tc.Felixes[0].BPFNumPolProgramsTotalByEntryPointFn(w0PolIdxEgress), "10s", "1s").Should(Equal(0), + Consistently(tc.Felixes[0].BPFNumPolProgramsTotalByEntryPointFn(w0PolIdxEgress, "egress"), "10s", "1s").Should(Equal(0), "w[0] egress policy programs came back?") }) }) diff --git a/felix/fv/bpf_policy_dump_test.go b/felix/fv/bpf_policy_dump_test.go index bbc06486b71..aac84def72a 100644 --- a/felix/fv/bpf_policy_dump_test.go +++ b/felix/fv/bpf_policy_dump_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -22,7 +20,7 @@ import ( "regexp" "strings" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -36,7 +34,6 @@ import ( ) var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Felix bpf test policy dump", []apiconfig.DatastoreType{apiconfig.EtcdV3}, func(getInfra infrastructure.InfraFactory) { - if os.Getenv("FELIX_FV_ENABLE_BPF") != "true" { // Non-BPF run. return @@ -54,7 +51,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Felix bpf test policy dump" opts := infrastructure.DefaultTopologyOptions() opts.ExtraEnvVars["FELIX_BPFPolicyDebugEnabled"] = "true" tc, calicoClient = infrastructure.StartNNodeTopology(1, opts, infra) - for i := 0; i < 2; i++ { + for i := range 2 { wIP := fmt.Sprintf("10.65.0.%d", i+2) w[i] = workload.Run(tc.Felixes[0], fmt.Sprintf("w%d", i), "default", wIP, "8055", "tcp") w[i].WorkloadEndpoint.Labels = map[string]string{"name": w[i].Name} @@ -64,16 +61,9 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Felix bpf test policy dump" }) AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { + if CurrentSpecReport().Failed() { tc.Felixes[0].Exec("calico-bpf", "policy", "dump", w[0].InterfaceName, "all") - infra.DumpErrorData() } - - for i := 0; i < 2; i++ { - w[i].Stop() - } - tc.Stop() - infra.Stop() }) createPolicy := func(policy *api.GlobalNetworkPolicy) *api.GlobalNetworkPolicy { @@ -97,6 +87,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Felix bpf test policy dump" protoTCP := numorstring.ProtocolFromString(numorstring.ProtocolTCP) protoUDP := numorstring.ProtocolFromString(numorstring.ProtocolUDP) sportRange, err := numorstring.PortFromRange(100, 105) + Expect(err).NotTo(HaveOccurred()) dportRange, err := numorstring.PortFromRange(200, 205) Expect(err).NotTo(HaveOccurred()) @@ -120,16 +111,16 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Felix bpf test policy dump" Expect(err).NotTo(HaveOccurred()) return out }, "10s", "200ms").Should(And( - ContainSubstring("Start of policy default.policy-tcp"), + ContainSubstring("Start of GlobalNetworkPolicy policy-tcp"), ContainSubstring("IPSets src_ip_set_ids"), )) outStr := out - Expect(outStr).To(ContainSubstring("Start of rule action:\"allow\"")) + Expect(outStr).To(ContainSubstring("Start of rule policy-tcp action:\"allow\"")) Expect(outStr).To(ContainSubstring("IPSets src_ip_set_ids:")) re := regexp.MustCompile("0x[0-9a-fA-F]+") ipSetFound := false - for _, tmp := range strings.Split(outStr, "\n") { + for tmp := range strings.SplitSeq(outStr, "\n") { if strings.Contains(tmp, "IPSets src_ip_set_ids:") { log.WithField("line", tmp).Info("Examining line for IPSet ID") ipsetStr := re.FindAllString(tmp, -1) @@ -153,7 +144,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Felix bpf test policy dump" outStr = string(out) Expect(outStr).To(ContainSubstring(ifaceStr)) Expect(outStr).To(ContainSubstring("Hook: tc egress")) - Expect(outStr).To(ContainSubstring("Start of policy default.policy-tcp")) + Expect(outStr).To(ContainSubstring("Start of GlobalNetworkPolicy policy-tcp")) Expect(outStr).To(ContainSubstring("Load packet metadata saved by previous program")) Expect(outStr).To(ContainSubstring("Save state pointer in register R9")) Expect(outStr).To(ContainSubstring("If protocol != tcp, skip to next rule")) @@ -168,12 +159,12 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Felix bpf test policy dump" out, err = tc.Felixes[0].ExecOutput("calico-bpf", "policy", "dump", w[0].InterfaceName, "egress") Expect(err).NotTo(HaveOccurred()) return out - }, "5s", "200ms").Should(ContainSubstring("Start of policy default.policy-tcp")) + }, "5s", "200ms").Should(ContainSubstring("Start of GlobalNetworkPolicy policy-tcp")) outStr = string(out) - Expect(outStr).To(ContainSubstring("Start of rule action:\"deny\"")) + Expect(outStr).To(ContainSubstring("Start of rule policy-tcp action:\"deny\"")) ipSetFound = false - for _, tmp := range strings.Split(outStr, "\n") { + for tmp := range strings.SplitSeq(outStr, "\n") { if strings.Contains(tmp, "IPSets not_dst_ip_set_ids:") { log.WithField("line", tmp).Info("Examining line for IPSet ID") ipsetStr := re.FindAllString(tmp, -1) @@ -197,7 +188,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Felix bpf test policy dump" outStr = string(out) Expect(outStr).To(ContainSubstring(ifaceStr)) Expect(outStr).To(ContainSubstring("Hook: tc ingress")) - Expect(outStr).To(ContainSubstring("Start of policy default.policy-tcp")) + Expect(outStr).To(ContainSubstring("Start of GlobalNetworkPolicy policy-tcp")) Expect(outStr).To(ContainSubstring("Load packet metadata saved by previous program")) Expect(outStr).To(ContainSubstring("Save state pointer in register R9")) Expect(outStr).To(ContainSubstring("If protocol == tcp, skip to next rule")) @@ -258,7 +249,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Felix bpf test policy dump" outStr := string(out) Expect(outStr).To(ContainSubstring(ifaceStr)) Expect(outStr).To(ContainSubstring("Hook: tc egress")) - Expect(outStr).To(ContainSubstring("Start of policy default.policy-icmp")) + Expect(outStr).To(ContainSubstring("Start of GlobalNetworkPolicy policy-icmp")) Expect(outStr).To(ContainSubstring("Load packet metadata saved by previous program")) Expect(outStr).To(ContainSubstring("Save state pointer in register R9")) Expect(outStr).To(ContainSubstring("If protocol != icmp, skip to next rule")) @@ -276,7 +267,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Felix bpf test policy dump" outStr = string(out) Expect(outStr).To(ContainSubstring(ifaceStr)) Expect(outStr).To(ContainSubstring("Hook: tc ingress")) - Expect(outStr).To(ContainSubstring("Start of policy default.policy-icmp")) + Expect(outStr).To(ContainSubstring("Start of GlobalNetworkPolicy policy-icmp")) Expect(outStr).To(ContainSubstring("Load packet metadata saved by previous program")) Expect(outStr).To(ContainSubstring("Save state pointer in register R9")) Expect(outStr).To(ContainSubstring("If protocol == icmp, skip to next rule")) diff --git a/felix/fv/bpf_test.go b/felix/fv/bpf_test.go index 50d7ee73e50..96fee09f179 100644 --- a/felix/fv/bpf_test.go +++ b/felix/fv/bpf_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -21,18 +19,17 @@ import ( "encoding/json" "fmt" "net" - "net/http" "os" "path" "regexp" + "slices" "sort" "strconv" "strings" "sync" "time" - "github.com/davecgh/go-spew/spew" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -55,15 +52,15 @@ import ( . "github.com/projectcalico/calico/felix/fv/connectivity" "github.com/projectcalico/calico/felix/fv/containers" "github.com/projectcalico/calico/felix/fv/infrastructure" + "github.com/projectcalico/calico/felix/fv/tcpdump" "github.com/projectcalico/calico/felix/fv/utils" "github.com/projectcalico/calico/felix/fv/workload" "github.com/projectcalico/calico/felix/proto" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapi "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" - "github.com/projectcalico/calico/libcalico-go/lib/ipam" - cnet "github.com/projectcalico/calico/libcalico-go/lib/net" options2 "github.com/projectcalico/calico/libcalico-go/lib/options" + "github.com/projectcalico/calico/libcalico-go/lib/set" ) // We run with and without connection-time load balancing for a couple of reasons: @@ -101,7 +98,7 @@ var ( // with debug disabled and that can lead to verifier issues. var _ = describeBPFTests(withProto("tcp"), withConnTimeLoadBalancingEnabled(), - withBPFLogLevel("info")) + withBPFLogLevel("off")) type bpfTestOptions struct { connTimeEnabled bool @@ -301,15 +298,14 @@ func describeBPFTests(opts ...bpfTestOpt) bool { ) return infrastructure.DatastoreDescribe(desc, []apiconfig.DatastoreType{apiconfig.Kubernetes}, func(getInfra infrastructure.InfraFactory) { var ( - infra infrastructure.DatastoreInfra - tc infrastructure.TopologyContainers - calicoClient client.Interface - cc *Checker - externalClient *containers.Container - deadWorkload *workload.Workload - options infrastructure.TopologyOptions - numericProto uint8 - felixPanicExpected bool + infra infrastructure.DatastoreInfra + tc infrastructure.TopologyContainers + calicoClient client.Interface + cc *Checker + externalClient *containers.Container + deadWorkload *workload.Workload + options infrastructure.TopologyOptions + numericProto uint8 ) containerIP := func(c *containers.Container) string { @@ -340,8 +336,6 @@ func describeBPFTests(opts ...bpfTestOpt) bool { } BeforeEach(func() { - felixPanicExpected = false - iOpts := []infrastructure.CreateOption{} if testOpts.ipv6 { iOpts = append(iOpts, @@ -371,15 +365,16 @@ func describeBPFTests(opts ...bpfTestOpt) bool { options.AutoHEPsEnabled = true // override IPIP being enabled by default options.IPIPMode = api.IPIPModeNever - options.SimulateBIRDRoutes = false switch testOpts.tunnel { case "none": - // Enable adding simulated routes. - options.SimulateBIRDRoutes = true + // Felix must program unencap routes. case "ipip": - // IPIP is not supported in IPv6. We need to mimic routes in FVs. + options.IPIPStrategy = infrastructure.NewDefaultTunnelStrategy(options.IPPoolCIDR, options.IPv6PoolCIDR) options.IPIPMode = api.IPIPModeAlways - options.SimulateBIRDRoutes = true + if testOpts.ipv6 { + options.SimulateBIRDRoutes = true + options.ExtraEnvVars["FELIX_ProgramClusterRoutes"] = "Disabled" + } case "vxlan": options.VXLANMode = api.VXLANModeAlways options.VXLANStrategy = infrastructure.NewDefaultTunnelStrategy(options.IPPoolCIDR, options.IPv6PoolCIDR) @@ -407,6 +402,8 @@ func describeBPFTests(opts ...bpfTestOpt) bool { options.ExtraEnvVars["FELIX_BPFLogLevel"] = fmt.Sprint(testOpts.bpfLogLevel) options.ExtraEnvVars["FELIX_BPFConntrackLogLevel"] = fmt.Sprint(testOpts.bpfLogLevel) options.ExtraEnvVars["FELIX_BPFProfiling"] = "Enabled" + options.ExtraEnvVars["FELIX_PrometheusMetricsEnabled"] = "true" + options.ExtraEnvVars["FELIX_PrometheusMetricsHost"] = "0.0.0.0" if testOpts.dsr { options.ExtraEnvVars["FELIX_BPFExternalServiceMode"] = "dsr" } @@ -416,15 +413,6 @@ func describeBPFTests(opts ...bpfTestOpt) bool { options.ExtraEnvVars["FELIX_BPFExtToServiceConnmark"] = "0x80" options.ExtraEnvVars["FELIX_HEALTHENABLED"] = "true" options.ExtraEnvVars["FELIX_BPFDSROptoutCIDRs"] = "245.245.0.0/16,beaf::dead/64" - if testOpts.tunnel == "wireguard" { - options.ExtraEnvVars["FELIX_BPFREDIRECTTOPEERFROML3DEVICE"] = "true" - } - if testOpts.tunnel == "ipip" && testOpts.protocol != "udp" { - // Some tests run with tcpdump in udp case. We make sure that - // the redirection does not affect connectivity, but we exclude - // those tests. It is too coarse, but the simplest. - options.ExtraEnvVars["FELIX_BPFREDIRECTTOPEERFROML3DEVICE"] = "true" - } if !testOpts.ipv6 { options.ExtraEnvVars["FELIX_HEALTHHOST"] = "0.0.0.0" } else { @@ -432,8 +420,8 @@ func describeBPFTests(opts ...bpfTestOpt) bool { options.ExtraEnvVars["FELIX_HEALTHHOST"] = "::" } - if false && testOpts.protocol == "tcp" { - filters := map[string]string{"all": "tcp"} + if testOpts.protocol == "tcp" { + filters := map[string]string{"all": "tcp or (udp port 4789)"} tcpResetTimeout := api.BPFConntrackTimeout("5s") felixConfig := api.NewFelixConfiguration() felixConfig.SetName("default") @@ -462,10 +450,12 @@ func describeBPFTests(opts ...bpfTestOpt) bool { options.ExtraEnvVars["FELIX_BPFConnectTimeLoadBalancing"] = string(api.BPFConnectTimeLBTCP) } } + options.ExtraEnvVars["FELIX_BPFMaglevMaxServices"] = "50" + options.ExtraEnvVars["FELIX_BPFMaglevMaxEndpointsPerService"] = "20" }) JustAfterEach(func() { - if CurrentGinkgoTestDescription().Failed { + if CurrentSpecReport().Failed() { var ( currBpfsvcs []nat.MapMem currBpfeps []nat.BackendMapMem @@ -474,9 +464,9 @@ func describeBPFTests(opts ...bpfTestOpt) bool { ) if testOpts.ipv6 { - currBpfsvcsV6, currBpfepsV6 = dumpNATmapsV6(tc.Felixes) + currBpfsvcsV6, currBpfepsV6, _ = dumpNATmapsV6(tc.Felixes) } else { - currBpfsvcs, currBpfeps = dumpNATmaps(tc.Felixes) + currBpfsvcs, currBpfeps, _ = dumpNATmaps(tc.Felixes) } for i, felix := range tc.Felixes { @@ -534,22 +524,6 @@ func describeBPFTests(opts ...bpfTestOpt) bool { } }) - AfterEach(func() { - log.Info("AfterEach starting") - for _, f := range tc.Felixes { - if !felixPanicExpected { - _ = f.ExecMayFail("calico-bpf", "connect-time", "clean") - } - f.Stop() - } - externalClient.Stop() - log.Info("AfterEach done") - }) - - AfterEach(func() { - infra.Stop() - }) - createPolicy := func(policy *api.GlobalNetworkPolicy) *api.GlobalNetworkPolicy { log.WithField("policy", dumpResource(policy)).Info("Creating policy") policy, err := calicoClient.GlobalNetworkPolicies().Create(utils.Ctx, policy, utils.NoOptions) @@ -569,7 +543,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { var ( hostW *workload.Workload w [2]*workload.Workload - wepCopy [2]*libapi.WorkloadEndpoint + wepCopy [2]*internalapi.WorkloadEndpoint ) if !testOpts.connTimeEnabled { @@ -584,7 +558,6 @@ func describeBPFTests(opts ...bpfTestOpt) bool { JustBeforeEach(func() { tc, calicoClient = infrastructure.StartNNodeTopology(1, options, infra) - hostW = workload.Run( tc.Felixes[0], "host", @@ -594,7 +567,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { testOpts.protocol) // Start a couple of workloads so we can check workload-to-workload and workload-to-host. - for i := 0; i < 2; i++ { + for i := range 2 { wIP := fmt.Sprintf("10.65.0.%d", i+2) if testOpts.ipv6 { wIP = fmt.Sprintf("dead:beef::%d", i+2) @@ -625,16 +598,16 @@ func describeBPFTests(opts ...bpfTestOpt) bool { pol = createPolicy(pol) Eventually(func() bool { - return bpfCheckIfPolicyProgrammed(tc.Felixes[0], w[0].InterfaceName, "ingress", "default.policy-1", "allow", true) + return bpfCheckIfGlobalNetworkPolicyProgrammed(tc.Felixes[0], w[0].InterfaceName, "ingress", "policy-1", "allow", true) }, "5s", "200ms").Should(BeTrue()) Eventually(func() bool { - return bpfCheckIfPolicyProgrammed(tc.Felixes[0], w[0].InterfaceName, "egress", "default.policy-1", "allow", true) + return bpfCheckIfGlobalNetworkPolicyProgrammed(tc.Felixes[0], w[0].InterfaceName, "egress", "policy-1", "allow", true) }, "5s", "200ms").Should(BeTrue()) Eventually(func() bool { - return bpfCheckIfPolicyProgrammed(tc.Felixes[0], w[1].InterfaceName, "ingress", "default.policy-1", "allow", true) + return bpfCheckIfGlobalNetworkPolicyProgrammed(tc.Felixes[0], w[1].InterfaceName, "ingress", "policy-1", "allow", true) }, "5s", "200ms").Should(BeTrue()) Eventually(func() bool { - return bpfCheckIfPolicyProgrammed(tc.Felixes[0], w[1].InterfaceName, "egress", "default.policy-1", "allow", true) + return bpfCheckIfGlobalNetworkPolicyProgrammed(tc.Felixes[0], w[1].InterfaceName, "egress", "policy-1", "allow", true) }, "5s", "200ms").Should(BeTrue()) }) @@ -643,11 +616,10 @@ func describeBPFTests(opts ...bpfTestOpt) bool { BeforeEach(func() { // Disable core dumps, we know we're about to cause a panic. options.FelixCoreDumpsEnabled = false - felixPanicExpected = true }) It("0xffff000 not covering BPF bits should panic", func() { - felixPanicExpected = true + tc.Felixes[0].PanicExpected = true panicC := tc.Felixes[0].WatchStdoutFor(regexp.MustCompile("PANIC.*IptablesMarkMask/NftablesMarkMask doesn't cover bits that are used")) fc, err := calicoClient.FelixConfigurations().Get(context.Background(), "default", options2.GetOptions{}) @@ -670,6 +642,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { }) It("0xfff00000 only covering BPF bits should panic", func() { + tc.Felixes[0].PanicExpected = true panicC := tc.Felixes[0].WatchStdoutFor(regexp.MustCompile("PANIC.*Not enough mark bits available")) fc, err := calicoClient.FelixConfigurations().Get(context.Background(), "default", options2.GetOptions{}) @@ -695,14 +668,13 @@ func describeBPFTests(opts ...bpfTestOpt) bool { if testOpts.bpfLogLevel == "debug" && testOpts.protocol == "udp" && !testOpts.ipv6 { It("udp should have connectivity after a service is recreated", func() { - clusterIP := "10.101.0.111" + clusterIP := "10.101.123.1" tcpdump := w[0].AttachTCPDump() tcpdump.SetLogEnabled(true) tcpdump.AddMatcher("udp-be", regexp.MustCompile(fmt.Sprintf("%s\\.12345 > %s\\.8055: \\[udp sum ok\\] UDP", w[1].IP, w[0].IP))) - tcpdump.Start("-vvv", "udp") - defer tcpdump.Stop() + tcpdump.Start(infra, "-vvv", "udp") // Just to create the wrong normal entry to the service _, err := w[1].RunCmd("pktgen", w[1].IP, clusterIP, "udp", @@ -727,7 +699,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { Should(BeNumerically("==", 0)) testSvc := k8sService("svc-no-backends", clusterIP, w[0], 80, 8055, 0, testOpts.protocol) - testSvcNamespace := testSvc.ObjectMeta.Namespace + testSvcNamespace := testSvc.Namespace k8sClient := infra.(*infrastructure.K8sDatastoreInfra).K8sClient _, err = k8sClient.CoreV1().Services(testSvcNamespace).Create(context.Background(), testSvc, metav1.CreateOptions{}) @@ -738,7 +710,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { natK := nat.NewNATKey(net.ParseIP(ip), port, 17) Eventually(func() bool { - natmaps, _ := dumpNATMapsAny(4, tc.Felixes[0]) + natmaps, _, _ := dumpNATMapsAny(4, tc.Felixes[0]) if _, ok := natmaps[natK]; !ok { return false } @@ -808,6 +780,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { BeforeEach(func() { options.ExtraEnvVars["FELIX_DefaultEndpointToHostAction"] = "ACCEPT" }) + It("should allow traffic from workload to workload and to/from host", func() { cc.ExpectSome(w[0], w[1]) cc.ExpectSome(w[1], w[0]) @@ -1185,7 +1158,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { } Eventually(func(g Gomega) { - natmap, natbe := dumpNATMapsAny(family, tc.Felixes[0]) + natmap, natbe, _ := dumpNATMapsAny(family, tc.Felixes[0]) g.Expect(natmap).To(HaveKey(natK)) g.Expect(natmap[natK].Count()).To(Equal(uint32(3))) svc := natmap[natK] @@ -1204,7 +1177,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { Expect(err).NotTo(HaveOccurred()) Eventually(func(g Gomega) { - natmap, natbe := dumpNATMapsAny(family, tc.Felixes[0]) + natmap, natbe, _ := dumpNATMapsAny(family, tc.Felixes[0]) g.Expect(natmap).To(HaveKey(natK)) g.Expect(natmap[natK].Count()).To(Equal(uint32(2))) svc := natmap[natK] @@ -1219,6 +1192,83 @@ func describeBPFTests(opts ...bpfTestOpt) bool { By("Waiting for dp to get setup up") ensureBPFProgramsAttached(tc.Felixes[0], "bpfout.cali") + progIDs := set.New[int]() + mapIDs := set.New[int]() + + // Get the program IDs of the preamble programs that we attach + // as part of this test. There can be other preamble programs + // from previous tests and we want to ignore those when checking that programs are cleaned up after disabling BPF. + getPreambleProgramIDs := func() set.Set[int] { + var bpfnetTCX []struct { + TC []struct { + Name string `json:"name"` + ID int `json:"prog_id"` + } `json:"tc"` + } + + var bpfnet []struct { + TC []struct { + Name string `json:"name"` + ID int `json:"id"` + } `json:"tc"` + } + out, err := tc.Felixes[0].ExecOutput("bpftool", "net", "show", "-j") + Expect(err).NotTo(HaveOccurred()) + preambleIDs := set.New[int]() + if BPFAttachType() == "tc" { + err = json.Unmarshal([]byte(out), &bpfnet) + Expect(err).NotTo(HaveOccurred()) + for _, entry := range bpfnet { + for _, prog := range entry.TC { + if strings.Contains(prog.Name, "cali_tc_pream") { + preambleIDs.Add(prog.ID) + } + } + } + } else { + err = json.Unmarshal([]byte(out), &bpfnetTCX) + Expect(err).NotTo(HaveOccurred()) + for _, entry := range bpfnetTCX { + for _, prog := range entry.TC { + if strings.Contains(prog.Name, "cali_tc_pream") { + preambleIDs.Add(prog.ID) + } + } + } + } + return preambleIDs + } + + var preambleIDsBefore set.Set[int] + Eventually(func() int { + preambleIDsBefore = getPreambleProgramIDs() + return preambleIDsBefore.Len() + }, "15s", "1s").Should(Equal(10)) // 10 = 2 (ingress+egress) * 5 interfaces (bpfout, lo, eth0, caliXXX x2) + + type bpfProgs []struct { + ID int `json:"id"` + Name string `json:"name"` + MapIDs []int `json:"map_ids"` + } + programs := bpfProgs{} + out, err := tc.Felixes[0].ExecOutput("bpftool", "prog", "show", "-j") + Expect(err).NotTo(HaveOccurred()) + err = json.Unmarshal([]byte(out), &programs) + Expect(err).NotTo(HaveOccurred()) + + // Get the program and map IDs of all program that are currently attached. + for _, prog := range programs { + if strings.Contains(prog.Name, "cali_tc_pream") && !preambleIDsBefore.Contains(prog.ID) { + continue + } + progIDs.Add(prog.ID) + mapIDs.AddAll(prog.MapIDs) + } + + // check for cgroups + out, err = tc.Felixes[0].ExecOutput("bpftool", "cgroup", "show", "/run/calico/cgroup") + Expect(err).NotTo(HaveOccurred()) + Expect(out).To(ContainSubstring("calico_connect")) By("Changing env and restarting felix") @@ -1227,21 +1277,46 @@ func describeBPFTests(opts ...bpfTestOpt) bool { By("Checking that all programs got cleaned up") - Eventually(func() string { - out, _ := tc.Felixes[0].ExecOutput("bpftool", "-jp", "prog", "show") - return out - }, "15s", "1s").ShouldNot( - Or(ContainSubstring("cali_"), ContainSubstring("calico_"), ContainSubstring("xdp_cali_"))) + // Check that the preamble programs we attached got cleaned up. + Eventually(func() int { + return getPreambleProgramIDs().Len() + }, "15s", "1s").Should(Equal(0)) - // N.B. calico_failsafe map is created in iptables mode by - // bpf.NewFailsafeMap() It has calico_ prefix. All other bpf - // maps have only cali_ prefix. - Eventually(func() string { - out, _ := tc.Felixes[0].ExecOutput("bpftool", "-jp", "map", "show") - return out - }, "15s", "1s").ShouldNot(Or(ContainSubstring("cali_"), ContainSubstring("xdp_cali_"))) + programs = bpfProgs{} + out, err = tc.Felixes[0].ExecOutput("bpftool", "prog", "show", "-j") + Expect(err).NotTo(HaveOccurred()) + err = json.Unmarshal([]byte(out), &programs) + Expect(err).NotTo(HaveOccurred()) + mapIDsAfter := set.New[int]() + progIDsAfter := set.New[int]() + for _, prog := range programs { + progIDsAfter.Add(prog.ID) + } + + for _, prog := range progIDs.Slice() { + Expect(progIDsAfter).NotTo(ContainElement(prog)) + } + + var bpfMaps []struct { + ID int `json:"id"` + } + out, err = tc.Felixes[0].ExecOutput("bpftool", "map", "show", "-j") + Expect(err).NotTo(HaveOccurred()) + err = json.Unmarshal([]byte(out), &bpfMaps) + Expect(err).NotTo(HaveOccurred()) + + for _, m := range bpfMaps { + mapIDsAfter.Add(m.ID) + } + for _, id := range mapIDs.Slice() { + Expect(mapIDsAfter).NotTo(ContainElement(id)) + } - out, _ := tc.Felixes[0].ExecCombinedOutput("ip", "link", "show", "dev", "bpfin.cali") + out, err = tc.Felixes[0].ExecOutput("bpftool", "cgroup", "show", "/run/calico/cgroup") + Expect(err).NotTo(HaveOccurred()) + Expect(out).NotTo(ContainSubstring("calico_connect")) + + out, _ = tc.Felixes[0].ExecCombinedOutput("ip", "link", "show", "dev", "bpfin.cali") Expect(out).To(Equal("Device \"bpfin.cali\" does not exist.\n")) out, _ = tc.Felixes[0].ExecCombinedOutput("ip", "link", "show", "dev", "bpfout.cali") Expect(out).To(Equal("Device \"bpfout.cali\" does not exist.\n")) @@ -1269,6 +1344,9 @@ func describeBPFTests(opts ...bpfTestOpt) bool { } wName := fmt.Sprintf("w%d%d", ii, wi) + if options.UseIPPools { + infrastructure.AssignIP(wName, wIP, tc.Felixes[ii].Hostname, calicoClient) + } w := workload.New(tc.Felixes[ii], wName, "default", wIP, strconv.Itoa(port), testOpts.protocol) @@ -1277,23 +1355,10 @@ func describeBPFTests(opts ...bpfTestOpt) bool { w.WorkloadEndpoint.Labels = labels if run { - err := w.Start() + err := w.Start(infra) Expect(err).NotTo(HaveOccurred()) w.ConfigureInInfra(infra) } - if options.UseIPPools { - // Assign the workload's IP in IPAM, this will trigger calculation of routes. - err := calicoClient.IPAM().AssignIP(context.Background(), ipam.AssignIPArgs{ - IP: cnet.MustParseIP(wIP), - HandleID: &w.Name, - Attrs: map[string]string{ - ipam.AttributeNode: tc.Felixes[ii].Hostname, - }, - Hostname: tc.Felixes[ii].Hostname, - }) - Expect(err).NotTo(HaveOccurred()) - } - return w } @@ -1328,11 +1393,9 @@ func describeBPFTests(opts ...bpfTestOpt) bool { // We will use this container to model an external client trying to connect into // workloads on a host. Create a route in the container for the workload CIDR. // TODO: Copied from another test - externalClient = infrastructure.RunExtClientWithOpts("ext-client", - infrastructure.ExtClientOpts{ - IPv6Enabled: testOpts.ipv6, - }, - ) + externalClient = infrastructure.RunExtClientWithOpts(infra, "ext-client", infrastructure.ExtClientOpts{ + IPv6Enabled: testOpts.ipv6, + }) _ = externalClient err := infra.AddDefaultDeny() @@ -1344,14 +1407,6 @@ func describeBPFTests(opts ...bpfTestOpt) bool { return healthStatus(containerIP(f.Container), "9099", "readiness") } Eventually(felixReady, "10s", "500ms").Should(BeGood()) - Eventually(func() int { - resp, err := http.Get("http://" + containerIP(f.Container) + ":10256" + "/healthz") - if err != nil { - log.WithError(err).WithField("resp", resp).Warn("HTTP GET failed") - return -1 - } - return resp.StatusCode - }, "3s", "500ms").Should(BeGood()) } } } @@ -1396,7 +1451,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { wlIP = "dead:beef::1:5" } dpOnlyWorkload := workload.New(tc.Felixes[1], "w-dp", "default", wlIP, "8057", testOpts.protocol) - err = dpOnlyWorkload.Start() + err = dpOnlyWorkload.Start(infra) Expect(err).NotTo(HaveOccurred()) tc.Felixes[1].Exec("ip", "route", "add", dpOnlyWorkload.IP, "dev", dpOnlyWorkload.InterfaceName, "scope", "link") @@ -1409,8 +1464,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { pattern = fmt.Sprintf(`IP6 .* %s\.8057: UDP`, dpOnlyWorkload.IP) } tcpdump.AddMatcher("UDP-8057", regexp.MustCompile(pattern)) - tcpdump.Start() - defer tcpdump.Stop() + tcpdump.Start(infra) // Send packets in the background. var wg sync.WaitGroup @@ -1654,8 +1708,9 @@ func describeBPFTests(opts ...bpfTestOpt) bool { pol = createPolicy(pol) }) - bpfWaitForPolicy(tc.Felixes[0], "eth0", "egress", "default.host-0-1") + bpfWaitForGlobalNetworkPolicy(tc.Felixes[0], "eth0", "egress", "host-0-1") }) + It("should handle NAT outgoing", func() { By("SNATting outgoing traffic with the flag set") cc.ExpectSNAT(w[0][0], felixIP(0), hostW[1]) @@ -1701,13 +1756,26 @@ func describeBPFTests(opts ...bpfTestOpt) bool { }) It("connectivity from all workloads via workload 0's main IP", func() { + var tcpd *tcpdump.TCPDump + + if testOpts.tunnel == "vxlan" { + tcpd = tc.Felixes[0].AttachTCPDump("eth0") + tcpd.SetLogEnabled(true) + tcpd.AddMatcher("eth0-vxlan", regexp.MustCompile("VXLAN,.* vni 4096")) + tcpd.Start(infra, "-vvv", "udp", "port", "4789") + } + cc.ExpectSome(w[0][1], w[0][0]) cc.ExpectSome(w[1][0], w[0][0]) cc.ExpectSome(w[1][1], w[0][0]) cc.CheckConnectivity(conntrackChecks(tc.Felixes)...) + + if testOpts.tunnel == "vxlan" { + Eventually(func() int { return tcpd.MatchCount("eth0-vxlan") }).Should(BeNumerically(">", 0)) + } }) - _ = !testOpts.ipv6 && !testOpts.dsr && + _ = !testOpts.ipv6 && !testOpts.dsr && testOpts.protocol == "udp" && testOpts.udpUnConnected && !testOpts.connTimeEnabled && It("should handle fragmented UDP", func() { if testOpts.tunnel == "vxlan" && !utils.UbuntuReleaseGreater("22.04") { Skip("Ubuntu too old to handle frag on vxlan dev properly") @@ -1726,30 +1794,38 @@ func describeBPFTests(opts ...bpfTestOpt) bool { tcpdump1.SetLogEnabled(true) tcpdump1.AddMatcher("udp-frags", regexp.MustCompile( fmt.Sprintf("%s.* > %s.*", w[1][0].IP, w[0][0].IP))) - tcpdump1.Start("-vvv", "src", "host", w[1][0].IP, "and", "dst", "host", w[0][0].IP) - defer tcpdump1.Stop() + tcpdump1.Start(infra, "-vvv", "src", "host", w[1][0].IP, "and", "dst", "host", w[0][0].IP) tcpdump0 := w[0][0].AttachTCPDump() tcpdump0.SetLogEnabled(true) tcpdump0.AddMatcher("udp-pod-frags", regexp.MustCompile( fmt.Sprintf("%s.* > %s.*", w[1][0].IP, w[0][0].IP))) - tcpdump0.Start("-vvv", "src", "host", w[1][0].IP, "and", "dst", "host", w[0][0].IP) - defer tcpdump1.Stop() + tcpdump0.Start(infra, "-vvv", "src", "host", w[1][0].IP, "and", "dst", "host", w[0][0].IP) // Give tcpdump some time to start up! time.Sleep(time.Second) // Send a packet with large payload without the DNF flag + // 16,000 bytes is the typical limit on the size of a + // single skb, which in turn is the limit on the size + // that a BPF program can grow a packet. _, err := w[1][0].RunCmd("pktgen", w[1][0].IP, w[0][0].IP, "udp", - "--port-src", "30444", "--port-dst", "30444", "--ip-dnf=n", "--payload-size=1600", "--udp-sock") + "--port-src", "30444", "--port-dst", "30444", "--ip-dnf=n", "--payload-size=16000", "--udp-sock") Expect(err).NotTo(HaveOccurred()) // We should see two fragments on the host interface - Eventually(func() int { return tcpdump1.MatchCount("udp-frags") }).Should(Equal(2)) - // We should see a reassembled packet at the destination workload. - // If ebpf program did not reassemble the packet, we would still - // see two fragments! - Eventually(func() int { return tcpdump0.MatchCount("udp-pod-frags") }).Should(Equal(2)) + Eventually(func() int { return tcpdump1.MatchCount("udp-frags") }).Should(Equal(12)) + // We should see the fragments reach the workload. We reassemble them in the middle but they + // get fragmented again. + Eventually(func() int { return tcpdump0.MatchCount("udp-pod-frags") }).Should(Equal(12)) + // Send another set of fragmented packets with the same source and destination ports. This + // will result in the first fragment hitting the conntrack and bypass mark set. We should + // still see the fragments reach the destination. + By("Sending another set of fragmented packets") + _, err = w[1][0].RunCmd("pktgen", w[1][0].IP, w[0][0].IP, "udp", + "--port-src", "30444", "--port-dst", "30444", "--ip-dnf=n", "--payload-size=16000", "--udp-sock") + Expect(err).NotTo(HaveOccurred()) + Eventually(func() int { return tcpdump1.MatchCount("udp-frags") }).Should(Equal(24)) }) if (testOpts.protocol == "tcp" || (testOpts.protocol == "udp" && !testOpts.udpUnConnected)) && @@ -1768,7 +1844,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { clusterIP1 = "dead:beef::abcd:0:0:111" } testSvc := k8sService("svc-no-backends", clusterIP1, w[0][0], 80, 1234, 0, testOpts.protocol) - testSvcNamespace := testSvc.ObjectMeta.Namespace + testSvcNamespace := testSvc.Namespace testSvc.Spec.Selector = map[string]string{"somelabel": "somevalue"} _, err := k8sClient.CoreV1().Services(testSvcNamespace).Create(context.Background(), testSvc, metav1.CreateOptions{}) @@ -1785,7 +1861,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { } Eventually(func() bool { - natmaps, _ := dumpNATMapsAny(family, tc.Felixes[0]) + natmaps, _, _ := dumpNATMapsAny(family, tc.Felixes[0]) if _, ok := natmaps[natK]; !ok { return false } @@ -1803,8 +1879,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { pattern = fmt.Sprintf(`IP %s.\d+ > %s\.80`, w[0][0].IP, testSvc.Spec.ClusterIP) } tcpdump.AddMatcher("no-backend", regexp.MustCompile(pattern)) - tcpdump.Start() - defer tcpdump.Stop() + tcpdump.Start(infra) By("testing connectivity") @@ -1819,7 +1894,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { // Test doesn't use services so ignore the runs with those turned on. if testOpts.protocol == "tcp" && !testOpts.connTimeEnabled && !testOpts.dsr { - It("should not be able to spoof TCP", func() { + spoofSetup := func() { if testOpts.ipv6 { // XXX the routing needs to be different and may not // apply to ipv6 @@ -1842,10 +1917,16 @@ func describeBPFTests(opts ...bpfTestOpt) bool { // Check that the route manipulation succeeded. cc.CheckConnectivity() cc.ResetExpectations() + } - // PHASE 1: basic single-shot connectivity checks to check that the test infra - // is basically doing what we want. I.e. if felix and the workload disagree on - // interface then new connections get dropped. + // Basic single-shot connectivity checks to check that the test infra + // is basically doing what we want. I.e. if felix and the workload disagree on + // interface then new connections get dropped. + It("should not be able to spoof new TCP connections", func() { + spoofSetup() + if testOpts.ipv6 { + return + } // Switch routes to use the spoofed interface, should fail. By("Workload using spoof0, felix expecting eth0, should fail") @@ -1868,9 +1949,16 @@ func describeBPFTests(opts ...bpfTestOpt) bool { cc.Expect(Some, w[0][0], w[1][0]) cc.CheckConnectivity() cc.ResetExpectations() + }) + + // Keep a connection up and move it from one interface to the other using the pod's + // routes. To the host this looks like one workload is spoofing the other. + It("should not be able to spoof existing TCP connections", func() { + spoofSetup() + if testOpts.ipv6 { + return + } - // PHASE 2: keep a connection up and move it from one interface to the other using the pod's - // routes. To the host this looks like one workload is spoofing the other. By("Starting permanent connection") pc := w[0][0].StartPersistentConnection(w[1][0].IP, 8055, workload.PersistentConnectionOpts{ MonitorConnectivity: true, @@ -1921,6 +2009,319 @@ func describeBPFTests(opts ...bpfTestOpt) bool { }) } + Describe("Test advertised IP's with maglev enabled", func() { + if testOpts.connTimeEnabled { + // FIXME externalClient also does conntime balancing + return + } + + var ( + testSvc *v1.Service + testSvcNamespace string + port uint16 + proto uint8 + ) + if numNodes < 3 { + panic("need 3 nodes") + } + + tgtPort := 8055 + externalIP := extIP + testSvcName := "test-maglev-service" + + familyInt := 4 + if family == "ipv6" { + familyInt = 6 + } + + felixWithMaglevBackend := 0 + initialIngressFelix := 1 + failoverIngressFelix := 2 + + newConntrackKey := func(srcIP net.IP, srcPort uint16, dstIP net.IP, dstPort uint16, family string) conntrack.KeyInterface { + var key conntrack.KeyInterface + // cmp := bytes.Compare(srcIP, dstIP) + // srcLTDst := cmp < 0 || (cmp == 0 && srcPort < dstPort) + + ipA, ipB := srcIP, dstIP + portA, portB := uint16(srcPort), dstPort + // if !srcLTDst { + // ipB, ipA = srcIP, dstIP + // portB, portA = uint16(srcPort), port + // } + switch family { + case "ipv4": + key = conntrack.NewKey(proto, ipA, portA, ipB, portB) + case "ipv6": + key = conntrack.NewKeyV6(proto, ipA, portA, ipB, portB) + } + return key + } + checkConntrackExists := func(f *infrastructure.Felix, ctK conntrack.KeyInterface) (conntrack.ValueInterface, bool) { + ctMap := dumpCTMapsAny(familyInt, f) + log.Infof("Dumping CT map for felix %s, searching for key: %s", f.Name, ctK.String()) + + for k, v := range ctMap { + log.Infof("key: %s\n\tval: %s", k.String(), v.String()) + } + v, ok := ctMap[ctK] + return v, ok + } + checkConntrackExistsAnyDirection := func(f *infrastructure.Felix, ipA net.IP, portA uint16, ipB net.IP, portB uint16, family string) (conntrack.ValueInterface, bool) { + keyAB := newConntrackKey(ipA, portA, ipB, portB, family) + keyBA := newConntrackKey(ipB, portB, ipA, portA, family) + + val, exists := checkConntrackExists(f, keyAB) + if !exists { + val, exists = checkConntrackExists(f, keyBA) + } + + return val, exists + } + maglevMapAnySearch := func(val nat.BackendValueInterface, family string, felix *infrastructure.Felix) nat.BackendValueInterface { + Expect(family).To(Or(Equal("ipv4"), Equal("ipv6"))) + + switch family { + case "ipv4": + vType := nat.BackendValue{} + Expect(val).To(BeAssignableToTypeOf(vType)) + kvs := dumpMaglevMap(felix) + valParsed, _ := val.(nat.BackendValue) + + for _, v := range kvs { + if v.Addr().Equal(valParsed.Addr()) && v.Port() == valParsed.Port() { + return v + } + } + + case "ipv6": + vType := nat.BackendValueV6{} + Expect(val).To(BeAssignableToTypeOf(vType)) + kvs := dumpMaglevMapV6(felix) + valParsed, _ := val.(nat.BackendValueV6) + + for _, v := range kvs { + if v.Addr().Equal(valParsed.Addr()) && v.Port() == valParsed.Port() { + return v + } + } + } + return nil + } + maglevMapAnySearchFunc := func(val nat.BackendValueInterface, family string, felix *infrastructure.Felix) func() nat.BackendValueInterface { + return func() nat.BackendValueInterface { + return maglevMapAnySearch(val, family, felix) + } + } + + probeMaglevConntrackMetric := func(metricName string, felixes ...*infrastructure.Felix) []int { + counts := make([]int, 0) + for _, f := range felixes { + ctCount, err := f.PromMetric(metricName).Int() + if err != nil { + log.WithError(err).WithField("felix", f.Name).Warn("Error while probing Felix metric. Skipping this felix") + continue + } + counts = append(counts, ctCount) + } + return counts + } + + BeforeEach(func() { + switch testOpts.protocol { + case "udp": + proto = 17 + case "tcp": + proto = 6 + case "sctp": + proto = 132 + default: + log.WithField("protocol", testOpts.protocol).Panic("unknown test protocol") + } + log.WithFields(log.Fields{"number": proto, "name": testOpts.protocol}).Info("parsed protocol") + + pTCP := numorstring.ProtocolFromString("tcp") + promPinhole := api.Rule{ + Action: "Allow", + Protocol: &pTCP, + Destination: api.EntityRule{ + Ports: []numorstring.Port{ + {MinPort: 9091, MaxPort: 9091}, + }, + Nets: []string{}, + }, + } + + // Create policy allowing ingress from external client + allowIngressFromExtClient := api.NewGlobalNetworkPolicy() + allowIngressFromExtClient.Namespace = "fv" + allowIngressFromExtClient.Name = "policy-ext-client" + allowIngressFromExtClient.Spec.Ingress = []api.Rule{ + promPinhole, + { + Action: "Allow", + Source: api.EntityRule{ + Nets: []string{ + containerIP(externalClient) + "/" + ipMask(), + }, + }, + }, + } + + allowIngressFromExtClientSelector := "all()" + allowIngressFromExtClient.Spec.Selector = allowIngressFromExtClientSelector + allowIngressFromExtClient = createPolicy(allowIngressFromExtClient) + + // Create service with maglev annotation + testSvc = k8sServiceWithExtIP(testSvcName, clusterIP, w[felixWithMaglevBackend][0], 80, tgtPort, 0, + testOpts.protocol, []string{externalIP}) + testSvc.Annotations = map[string]string{ + "lb.projectcalico.org/external-traffic-strategy": "maglev", + } + + testSvcNamespace = testSvc.Namespace + _, err := k8sClient.CoreV1().Services(testSvcNamespace).Create(context.Background(), testSvc, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + Eventually(checkSvcEndpoints(k8sClient, testSvc), "10s").Should(Equal(1), + "Service endpoint didn't get created. Is controller-manager happy?") + Expect(k8sGetEpsForService(k8sClient, testSvc)[0].Endpoints[0].Addresses).Should(HaveLen(1), + "Service endpoint didn't have the expected number of addresses.") + + Expect(testSvc.Spec.ExternalIPs).To(HaveLen(1)) + Expect(testSvc.Spec.ExternalIPs[0]).To(Equal(externalIP)) + Expect(testSvc.Spec.Ports).To(HaveLen(1)) + port = uint16(testSvc.Spec.Ports[0].Port) + + conntrackFlushWorkloadEntries(tc.Felixes) + + eps := k8sGetEpsForService(k8sClient, testSvc) + Expect(eps).NotTo(HaveLen(0), "Expected endpoints for the service") + Expect(eps[0].Endpoints).NotTo(HaveLen(0), "Endpointslice had no endpoints") + Expect(eps[0].Endpoints[0].Addresses).NotTo(BeEmpty(), "No addresses in endpointslice item") + Expect(net.ParseIP(eps[0].Endpoints[0].Addresses[0])).NotTo(BeNil(), "Endpoint address was not parseable as an IP") + + var testMaglevMapVal nat.BackendValueInterface + switch family { + case "ipv4": + testMaglevMapVal = nat.NewNATBackendValue(net.ParseIP(eps[0].Endpoints[0].Addresses[0]), uint16(tgtPort)) + case "ipv6": + testMaglevMapVal = nat.NewNATBackendValueV6(net.ParseIP(eps[0].Endpoints[0].Addresses[0]), uint16(tgtPort)) + default: + log.Panicf("Unexpected IP family %s", family) + } + + log.Info("Waiting for Maglev map to converge...") + Eventually(maglevMapAnySearchFunc(testMaglevMapVal, family, tc.Felixes[0]), "10s").ShouldNot(BeNil(), "A maglev map entry never showed up (Felix[0]). Looked for backend: %v", testMaglevMapVal) + Eventually(maglevMapAnySearchFunc(testMaglevMapVal, family, tc.Felixes[1]), "10s").ShouldNot(BeNil(), "A maglev map entry never showed up (Felix[1]). Looked for backend: %v", testMaglevMapVal) + Eventually(maglevMapAnySearchFunc(testMaglevMapVal, family, tc.Felixes[2]), "10s").ShouldNot(BeNil(), "A maglev map entry never showed up (Felix[2]). Looked for backend: %v", testMaglevMapVal) + + Expect(maglevMapAnySearch(testMaglevMapVal, family, tc.Felixes[1]).Addr().String()).Should(Equal(w[0][0].IP)) + + // Configure routes on external client and Felix nodes. + // Use Felix[1] as a middlebox initially. + ipRoute := []string{"ip"} + if testOpts.ipv6 { + ipRoute = append(ipRoute, "-6") + } + + cmdCleanRt := append(ipRoute, "route", "del", clusterIP) + _ = externalClient.ExecMayFail(strings.Join(cmdCleanRt, "")) + cmdCleanRt = append(ipRoute, "route", "del", externalIP) + _ = externalClient.ExecMayFail(strings.Join(cmdCleanRt, "")) + + cmdCIP := append(ipRoute, "route", "add", clusterIP, "via", felixIP(initialIngressFelix)) + externalClient.Exec(cmdCIP...) + cmdEIP := append(ipRoute, "route", "add", externalIP, "via", felixIP(initialIngressFelix)) + externalClient.Exec(cmdEIP...) + }) + + It("should have connectivity from external client to maglev backend via cluster IP and external IP", func() { + probeMaglevLocalConntrackMetricFunc := func(felixes ...*infrastructure.Felix) func() []int { + return func() []int { + return probeMaglevConntrackMetric(fmt.Sprintf("felix_bpf_conntrack_maglev_entries_total{destination=\"local\",ip_family=\"%d\"}", familyInt), felixes...) + } + } + probeMaglevRemoteConntrackMetricFunc := func(felixes ...*infrastructure.Felix) func() []int { + return func() []int { + return probeMaglevConntrackMetric(fmt.Sprintf("felix_bpf_conntrack_maglev_entries_total{destination=\"remote\",ip_family=\"%d\"}", familyInt), felixes...) + } + } + + Eventually(probeMaglevLocalConntrackMetricFunc(tc.Felixes...), "10s", "1s").Should(Equal([]int{0, 0, 0}), "Expected maglev local-conntrack metric to start at 0 for all Felixes") + Eventually(probeMaglevRemoteConntrackMetricFunc(tc.Felixes...), "10s", "1s").Should(Equal([]int{0, 0, 0}), "Expected maglev remote-conntrack metric to start at 0 for all Felixes") + + cc.ExpectSome(externalClient, TargetIP(clusterIP), port) + cc.ExpectSome(externalClient, TargetIP(externalIP), port) + cc.CheckConnectivity() + + // There is a 10-second interval between iterations of Felix's conntrack scanner (where we export the maglev conntrack metrics). + // This means we must be very pessimistic about timeouts when searching for the prom values we're after. + Eventually(probeMaglevRemoteConntrackMetricFunc(tc.Felixes[initialIngressFelix]), "12s", "1s").Should(Equal([]int{2}), "Expected maglev-ingress felix to increment the remote-conntracks metric") + Eventually(probeMaglevLocalConntrackMetricFunc(tc.Felixes[felixWithMaglevBackend]), "12s", "1s").Should(Equal([]int{2}), "Expected felix with maglev backend to increment the local-conntracks metric") + Consistently(probeMaglevLocalConntrackMetricFunc(tc.Felixes[initialIngressFelix])).Should(Equal([]int{0}), "Expected ingress-felix to only have remote maglev conntracks, but saw metric for local maglev conntracks go up") + Consistently(probeMaglevRemoteConntrackMetricFunc(tc.Felixes[felixWithMaglevBackend])).Should(Equal([]int{0}), "Expected backing felix to only have local maglev conntracks, but saw metric for remote maglev conntracks go up") + Consistently(probeMaglevLocalConntrackMetricFunc(tc.Felixes[failoverIngressFelix])).Should(Equal([]int{0}), "No failover occurred, but an unrelated Felix's local maglev prom metrics went up") + Consistently(probeMaglevRemoteConntrackMetricFunc(tc.Felixes[failoverIngressFelix])).Should(Equal([]int{0}), "No failover occurred, but an unrelated Felix's remote maglev prom metrics went up") + }) + + testFailover := func(serviceIP string) { + By("making a connection over a loadbalancer and then switching off routing to it") + pc := &PersistentConnection{ + Runtime: externalClient, + RuntimeName: externalClient.Name, + IP: serviceIP, + Port: int(port), + SourcePort: 50000, + Protocol: testOpts.protocol, + MonitorConnectivity: true, + ProbeLoopFileTimeout: 15 * time.Second, + } + err := pc.Start() + Expect(err).NotTo(HaveOccurred()) + defer pc.Stop() + + Eventually(pc.PongCount, "5s", "100ms").Should(BeNumerically(">", 0), "Connection failed") + + backingPodIPAddr := net.ParseIP(w[0][0].IP) + clientIPAddr := net.ParseIP(containerIP(externalClient)) + + ctVal, ctExists := checkConntrackExistsAnyDirection(tc.Felixes[1], clientIPAddr, uint16(pc.SourcePort), backingPodIPAddr, uint16(tgtPort), family) + Expect(ctExists).To(BeTrue(), "No conntrack (src->dst / dst->src) existed for the connection on Felix[1]") + Expect(ctVal.OrigIP().String()).To(Equal(serviceIP), "Unexpected OrigIP on loadbalancer Felix service connection") + + ctVal, ctExists = checkConntrackExistsAnyDirection(tc.Felixes[2], clientIPAddr, uint16(pc.SourcePort), backingPodIPAddr, uint16(tgtPort), family) + Expect(ctExists).To(BeFalse(), "Conntrack existed for the connection on Felix[2] before Felix[2] should have handled the connection: %v", ctVal) + + // Traffic is flowing over LB 1. Change ExtClient's serviceIP route to go via LB 2. + ipRoute := []string{"ip"} + if testOpts.ipv6 { + ipRoute = append(ipRoute, "-6") + } + ipRouteReplace := append(ipRoute, "route", "replace", serviceIP, "via", felixIP(2)) + externalClient.Exec(ipRouteReplace...) + + lastPongCount := pc.PongCount() + + checkCTExistsFn := func() bool { + _, ctExists = checkConntrackExistsAnyDirection(tc.Felixes[2], clientIPAddr, uint16(pc.SourcePort), backingPodIPAddr, uint16(tgtPort), family) + return ctExists + } + Eventually(checkCTExistsFn, "10s").Should(BeTrue(), "Conntrack didn't exist on Felix[2] for failover traffic. Did the failover actually occur?") + + // Check the backing node updated conntrack tun_ip to the new loadbalancer node. + ctVal, ctExists = checkConntrackExistsAnyDirection(tc.Felixes[0], clientIPAddr, uint16(pc.SourcePort), backingPodIPAddr, uint16(tgtPort), family) + Expect(ctExists).To(BeTrue(), "Conntrack didn't exist on backing Felix[0].") + Expect(ctVal.Data().TunIP.String()).To(Equal(felixIP(2)), "Backing node did not update its conntrack tun_ip to the new loadbalancer IP") + + // Connection should persist after the changeover. + Eventually(pc.PongCount, "5s", "100ms").Should(BeNumerically(">", lastPongCount), "Connection is no longer ponging after route failover") + } + + It("should maintain connections to a cluster IP across loadbalancer failover using maglev", func() { testFailover(clusterIP) }) + It("should maintain connections to an external IP across loadbalancer failover using maglev", func() { testFailover(externalIP) }) + }) + Describe("Test Load balancer service with external IP", func() { if testOpts.connTimeEnabled { // FIXME externalClient also does conntime balancing @@ -1983,7 +2384,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { testOpts.protocol, externalIP, srcIPRange) By("Deleting first service") - err := k8sClient.CoreV1().Services(testSvc.ObjectMeta.Namespace).Delete(context.Background(), testSvcName, metav1.DeleteOptions{}) + err := k8sClient.CoreV1().Services(testSvc.Namespace).Delete(context.Background(), testSvcName, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) By("Sleeping") @@ -2056,6 +2457,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { } pol = updatePolicy(pol) }) + It("should not have connectivity from external client, and return connection refused", func() { icmpProto := "icmp" if testOpts.ipv6 { @@ -2065,15 +2467,14 @@ func describeBPFTests(opts ...bpfTestOpt) bool { tcpdump := externalClient.AttachTCPDump("any") tcpdump.SetLogEnabled(true) if testOpts.ipv6 { - tcpdump.AddMatcher("unreach", regexp.MustCompile("destination unreachable")) - tcpdump.AddMatcher("bad csum", regexp.MustCompile("bad icmp6 cksum")) + tcpdump.AddMatcher("unreach", regexp.MustCompile(`destination unreachable`)) + tcpdump.AddMatcher("bad csum", regexp.MustCompile(`bad icmp6 cksum`)) } else { - tcpdump.AddMatcher("unreach", regexp.MustCompile("port \\d+ unreachable")) - tcpdump.AddMatcher("bad csum", regexp.MustCompile("wrong icmp cksum")) + tcpdump.AddMatcher("unreach", regexp.MustCompile(`port \d+ unreachable`)) + tcpdump.AddMatcher("bad csum", regexp.MustCompile(`wrong icmp cksum`)) } - tcpdump.Start("-vv", testOpts.protocol, "port", strconv.Itoa(int(port)), "or", icmpProto) - defer tcpdump.Stop() + tcpdump.Start(infra, "-vv", testOpts.protocol, "port", strconv.Itoa(int(port)), "or", icmpProto) cc.Expect(None, externalClient, TargetIP(ip[0]), ExpectWithPorts(port), @@ -2172,11 +2573,11 @@ func describeBPFTests(opts ...bpfTestOpt) bool { // Create a service of type clusterIP BeforeEach(func() { testSvc = k8sService(testSvcName, clusterIP, w[0][0], 80, tgtPort, 0, testOpts.protocol) - testSvcNamespace = testSvc.ObjectMeta.Namespace + testSvcNamespace = testSvc.Namespace _, err := k8sClient.CoreV1().Services(testSvcNamespace).Create(context.Background(), testSvc, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - Eventually(k8sGetEpsForServiceFunc(k8sClient, testSvc), "10s").Should(HaveLen(1), - "Service endpoints didn't get created? Is controller-manager happy?") + Eventually(checkSvcEndpoints(k8sClient, testSvc), "10s").Should(Equal(1), + "Service endpoints didn't get created. Is controller-manager happy?") tc.Felixes[1].Exec("ip", "route", "add", "local", extIP, "dev", "eth0") tc.Felixes[0].Exec("ip", "route", "add", "local", extIP, "dev", "eth0") }) @@ -2483,11 +2884,11 @@ func describeBPFTests(opts ...bpfTestOpt) bool { BeforeEach(func() { testSvc = k8sService(testSvcName, clusterIP, w[0][0], 80, tgtPort, 0, testOpts.protocol) - testSvcNamespace = testSvc.ObjectMeta.Namespace + testSvcNamespace = testSvc.Namespace _, err := k8sClient.CoreV1().Services(testSvcNamespace).Create(context.Background(), testSvc, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - Eventually(k8sGetEpsForServiceFunc(k8sClient, testSvc), "10s").Should(HaveLen(1), - "Service endpoints didn't get created? Is controller-manager happy?") + Eventually(checkSvcEndpoints(k8sClient, testSvc), "10s").Should(Equal(1), + "Service endpoints didn't get created. Is controller-manager happy?") }) It("should have connectivity from all workloads via a service to workload 0", func() { @@ -2559,10 +2960,10 @@ func describeBPFTests(opts ...bpfTestOpt) bool { It("should have connectivity from workload via a service IP to a host-process listening on that IP", func() { By("Setting up a dummy service " + excludeSvcIP) svc := k8sService("dummy-service", excludeSvcIP, w[0][0] /* unimportant */, 8066, 8077, 0, testOpts.protocol) - svc.ObjectMeta.Annotations = map[string]string{ + svc.Annotations = map[string]string{ proxy.ExcludeServiceAnnotation: "true", } - _, err := k8sClient.CoreV1().Services(testSvc.ObjectMeta.Namespace). + _, err := k8sClient.CoreV1().Services(testSvc.Namespace). Create(context.Background(), svc, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -2673,7 +3074,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { if time.Since(startTime) > 5*time.Second { Fail("NAT maps failed to converge") } - natBeforeUpdate, natBackBeforeUpdate = dumpNATmapsAny(family, tc.Felixes) + natBeforeUpdate, natBackBeforeUpdate, _ = dumpNATmapsAny(family, tc.Felixes) for i, m := range natBeforeUpdate { if natV, ok := m[oldK]; !ok { goto retry @@ -2705,13 +3106,14 @@ func describeBPFTests(opts ...bpfTestOpt) bool { svc, err := k8sClient.CoreV1(). Services(testSvcNamespace). Get(context.Background(), testSvcName, metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) - testSvcUpdated.ObjectMeta.ResourceVersion = svc.ObjectMeta.ResourceVersion + testSvcUpdated.ResourceVersion = svc.ResourceVersion _, err = k8sClient.CoreV1().Services(testSvcNamespace).Update(context.Background(), testSvcUpdated, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) - Eventually(k8sGetEpsForServiceFunc(k8sClient, testSvc), "10s").Should(HaveLen(1), - "Service endpoints didn't get created? Is controller-manager happy?") + Eventually(checkSvcEndpoints(k8sClient, testSvc), "10s").Should(Equal(1), + "Service endpoints didn't get created. Is controller-manager happy?") }) It("should have connectivity from all workloads via the new port", func() { @@ -2751,7 +3153,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { natK = nat.NewNATKey(ipv4, portNew, numericProto) } - natmaps, natbacks := dumpNATmapsAny(family, tc.Felixes) + natmaps, natbacks, _ := dumpNATmapsAny(family, tc.Felixes) for i := range tc.Felixes { Expect(natmaps[i]).To(HaveKey(natK)) @@ -2793,7 +3195,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { var prevBpfsvcs []map[nat.FrontendKeyInterface]nat.FrontendValue Eventually(func() bool { - prevBpfsvcs, _ = dumpNATmapsAny(family, tc.Felixes) + prevBpfsvcs, _, _ = dumpNATmapsAny(family, tc.Felixes) for _, m := range prevBpfsvcs { if _, ok := m[natK]; !ok { return false @@ -2806,7 +3208,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { Services(testSvcNamespace). Delete(context.Background(), testSvcName, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) - Eventually(k8sGetEpsForServiceFunc(k8sClient, testSvc), "10s").Should(HaveLen(0)) + Eventually(checkSvcEndpoints(k8sClient, testSvc), "10s").Should(Equal(0)) cc.ExpectNone(w[0][1], TargetIP(ip), port) cc.ExpectNone(w[1][0], TargetIP(ip), port) @@ -2819,7 +3221,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { bckID := natV.ID() Eventually(func() bool { - svcs, eps := dumpNATMapsAny(family, f) + svcs, eps, _ := dumpNATMapsAny(family, f) if _, ok := svcs[natK]; ok { return false @@ -2850,7 +3252,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { BeforeEach(func() { testSvc = k8sService(testSvcName, clusterIP, w[0][0], 80, 8055, 0, testOpts.protocol) - testSvcNamespace = testSvc.ObjectMeta.Namespace + testSvcNamespace = testSvc.Namespace // select all pods with port 8055 testSvc.Spec.Selector = map[string]string{"port": "8055"} if setAffinity { @@ -2858,7 +3260,8 @@ func describeBPFTests(opts ...bpfTestOpt) bool { } _, err := k8sClient.CoreV1().Services(testSvcNamespace).Create(context.Background(), testSvc, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - Eventually(k8sGetEpsForServiceFunc(k8sClient, testSvc), "10s").Should(HaveLen(1), + // We have 3 backends all listening on port 8055. + Eventually(checkSvcEndpoints(k8sClient, testSvc), "10s").Should(Equal(3), "Service endpoints didn't get created? Is controller-manager happy?") }) @@ -2910,7 +3313,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { } Eventually(func() bool { - m, be := dumpNATMapsAny(family, tc.Felixes[0]) + m, be, _ := dumpNATMapsAny(family, tc.Felixes[0]) v, ok := m[natFtKey] if !ok || v.Count() == 0 { @@ -3033,13 +3436,13 @@ func describeBPFTests(opts ...bpfTestOpt) bool { By("Setting up the service", func() { testSvc = k8sService(testSvcName, clusterIP, w[0][0], 80, 8055, 0, testOpts.protocol) - testSvcNamespace = testSvc.ObjectMeta.Namespace + testSvcNamespace = testSvc.Namespace // select all pods with port 8055 testSvc.Spec.Selector = map[string]string{"port": "8055"} testSvc.Spec.SessionAffinity = "ClientIP" _, err := k8sClient.CoreV1().Services(testSvcNamespace).Create(context.Background(), testSvc, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - Eventually(k8sGetEpsForServiceFunc(k8sClient, testSvc), "10s").Should(HaveLen(1), + Eventually(checkSvcEndpoints(k8sClient, testSvc), "10s").Should(Equal(3), "Service endpoints didn't get created? Is controller-manager happy?") }) @@ -3058,7 +3461,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { family = 4 } Eventually(func() bool { - m, be := dumpNATMapsAny(family, tc.Felixes[0]) + m, be, _ := dumpNATMapsAny(family, tc.Felixes[0]) v, ok := m[natFtKey] if !ok || v.Count() == 0 { return false @@ -3117,14 +3520,10 @@ func describeBPFTests(opts ...bpfTestOpt) bool { cc.CheckConnectivity() }) - ifUDPnoCTLB := func(desc string, body func()) { - if testOpts.protocol != "udp" || testOpts.connTimeEnabled { + It("should have connectivity after a backend is replaced by a new one", func() { + if testOpts.protocol == "udp" && testOpts.connTimeEnabled { return } - It(desc, body) - } - - ifUDPnoCTLB("should have connectivity after a backend is replaced by a new one", func() { var ( testSvc *v1.Service testSvcNamespace string @@ -3134,10 +3533,10 @@ func describeBPFTests(opts ...bpfTestOpt) bool { By("Setting up the service", func() { testSvc = k8sService(testSvcName, clusterIP, w[0][0], 80, 8055, 0, testOpts.protocol) - testSvcNamespace = testSvc.ObjectMeta.Namespace + testSvcNamespace = testSvc.Namespace _, err := k8sClient.CoreV1().Services(testSvcNamespace).Create(context.Background(), testSvc, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - Eventually(k8sGetEpsForServiceFunc(k8sClient, testSvc), "10s").Should(HaveLen(1), + Eventually(checkSvcEndpoints(k8sClient, testSvc), "10s").Should(Equal(1), "Service endpoints didn't get created? Is controller-manager happy?") }) @@ -3160,19 +3559,23 @@ func describeBPFTests(opts ...bpfTestOpt) bool { natFtKey = nat.NewNATKey(net.ParseIP(ip), port, numericProto) family = 4 } - Eventually(func() bool { - m, be := dumpNATMapsAny(family, tc.Felixes[1]) - - v, ok := m[natFtKey] - if !ok || v.Count() == 0 { - return false - } + // Check all felixes. In the shared-cgroup FV environment, + // felix-0's CTLB program intercepts connects from all + // containers, so its NAT maps must be synced too. + for _, f := range tc.Felixes { + Eventually(func() bool { + m, be, _ := dumpNATMapsAny(family, f) - beKey := nat.NewNATBackendKey(v.ID(), 0) + v, ok := m[natFtKey] + if !ok || v.Count() == 0 { + return false + } - _, ok = be[beKey] - return ok - }, 5*time.Second).Should(BeTrue()) + beKey := nat.NewNATBackendKey(v.ID(), 0) + _, ok = be[beKey] + return ok + }, 5*time.Second).Should(BeTrue()) + } }) By("Making sure that backend is ready") @@ -3186,7 +3589,9 @@ func describeBPFTests(opts ...bpfTestOpt) bool { Timeout: 60 * time.Second, }, ) - defer pc.Stop() + if testOpts.protocol != "tcp" { + defer pc.Stop() + } By("Testing connectivity") prevCount := pc.PongCount() @@ -3197,15 +3602,54 @@ func describeBPFTests(opts ...bpfTestOpt) bool { testSvc2 := k8sService(testSvcName, clusterIP, w[1][0], 80, 8055, 0, testOpts.protocol) k8sUpdateService(k8sClient, testSvcNamespace, testSvcName, testSvc, testSvc2) - By("Stoping the original backend to make sure it is not reachable") + var tcpd *tcpdump.TCPDump + if testOpts.protocol == "tcp" { + iface := w[1][1].InterfaceName + srcIP := clusterIP + tcpdHost := tc.Felixes[1] + if testOpts.connTimeEnabled { + iface = "eth0" + switch testOpts.tunnel { + case "vxlan": + iface = "vxlan.calico" + case "wireguard": + iface = "wireguard.cali" + if testOpts.ipv6 { + iface = "wireguard.cali-v6" + } + case "ipip": + iface = "tunl0" + } + srcIP = w[0][0].IP + tcpdHost = tc.Felixes[0] + } + tcpd = tcpdHost.AttachTCPDump(iface) + tcpd.SetLogEnabled(true) + + ipRegex := "IP" + if testOpts.ipv6 { + ipRegex = "IP6" + } + tcpd.AddMatcher("tcp-rst", + regexp.MustCompile(fmt.Sprintf(`%s %s\.\d+ > %s\.\d+: Flags \[[^\]]*R[^\]]*\]`, ipRegex, srcIP, w[1][1].IP))) + tcpd.Start(infra) + } + + By("Stopping the original backend to make sure it is not reachable") w[0][0].Stop() By("removing the old workload from infra") w[0][0].RemoveFromInfra(infra) By("Testing connectivity continues") - prevCount = pc.PongCount() - Eventually(pc.PongCount, "15s").Should(BeNumerically(">", prevCount), - "Expected to see pong responses on the connection but didn't receive any") + if testOpts.protocol == "tcp" { + Eventually(func() int { return tcpd.MatchCount("tcp-rst") }, "25s").ShouldNot(BeZero(), + "Expected to see TCP RSTs on the connection after backend change") + Expect(pc.IsConnectionReset()).To(BeTrue()) + } else { + prevCount = pc.PongCount() + Eventually(pc.PongCount, "15s").Should(BeNumerically(">", prevCount), + "Expected to see pong responses on the connection but didn't receive any") + } }) }) @@ -3239,10 +3683,10 @@ func describeBPFTests(opts ...bpfTestOpt) bool { internalLocal := v1.ServiceInternalTrafficPolicyLocal testSvc.Spec.InternalTrafficPolicy = &internalLocal } - testSvcNamespace = testSvc.ObjectMeta.Namespace + testSvcNamespace = testSvc.Namespace _, err := k8sClient.CoreV1().Services(testSvcNamespace).Create(context.Background(), testSvc, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - Eventually(k8sGetEpsForServiceFunc(k8sClient, testSvc), "10s").Should(HaveLen(1), + Eventually(checkSvcEndpoints(k8sClient, testSvc), "10s").Should(Equal(1), "Service endpoints didn't get created? Is controller-manager happy?") }) @@ -3343,21 +3787,21 @@ func describeBPFTests(opts ...bpfTestOpt) bool { } for _, felix := range tc.Felixes { - fe, _ := dumpNATMapsAny(family, felix) + fe, _, _ := dumpNATMapsAny(family, felix) for key := range fe { Expect(key.Addr().String()).To(BeElementOf(ipOK)) } } // RemoteNodeport on node 0 - fe, _ := dumpNATMapsAny(family, tc.Felixes[0]) + fe, _, _ := dumpNATMapsAny(family, tc.Felixes[0]) Expect(fe).To(HaveKey(feKey)) be := fe[feKey] Expect(be.Count()).To(Equal(uint32(1))) Expect(be.LocalCount()).To(Equal(uint32(1))) // RemoteNodeport on node 1 - fe, _ = dumpNATMapsAny(family, tc.Felixes[1]) + fe, _, _ = dumpNATMapsAny(family, tc.Felixes[1]) Expect(fe).To(HaveKey(feKey)) be = fe[feKey] Expect(be.Count()).To(Equal(uint32(1))) @@ -3757,9 +4201,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { if intLocal { It("workload should have connectivity to self via local and not remote node", func() { w00Expects := []ExpectationOption{ExpectWithPorts(npPort)} - hostW0SrcIP := ExpectWithSrcIPs("0.0.0.0") - - hostW0SrcIP = ExpectWithSrcIPs(felixIP(0)) + hostW0SrcIP := ExpectWithSrcIPs(felixIP(0)) if testOpts.ipv6 { hostW0SrcIP = ExpectWithSrcIPs(felixIP(0)) switch testOpts.tunnel { @@ -3876,8 +4318,8 @@ func describeBPFTests(opts ...bpfTestOpt) bool { arpRegexp := regexp.MustCompile(fmt.Sprintf(".*%s : (.*) -> (.*)", felixIP(1))) - lines := strings.Split(out, "\n") - for _, l := range lines { + lines := strings.SplitSeq(out, "\n") + for l := range lines { if strings.Contains(l, felixIP(1)) { MACs := arpRegexp.FindStringSubmatch(l) Expect(MACs).To(HaveLen(3)) @@ -3897,8 +4339,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { tcpdump := tc.Felixes[0].AttachTCPDump("eth0") tcpdump.SetLogEnabled(true) tcpdump.AddMatcher("MACs", regexp.MustCompile(fmt.Sprintf("%s > %s", srcMAC, dstMAC))) - tcpdump.Start("-e", "udp", "and", "src", felixIP(0), "and", "port", "4789") - defer tcpdump.Stop() + tcpdump.Start(infra, "-e", "udp", "and", "src", felixIP(0), "and", "port", "4789") cc.ExpectSome(externalClient, TargetIP(felixIP(1)), npPort) cc.CheckConnectivity() @@ -4008,8 +4449,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { } tcpdump := tc.Felixes[0].AttachTCPDump("eth0") tcpdump.SetLogEnabled(true) - tcpdump.Start("tcp", "port", "8055") - defer tcpdump.Stop() + tcpdump.Start(infra, "tcp", "port", "8055") err := pc.Start() Expect(err).NotTo(HaveOccurred()) @@ -4122,7 +4562,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { InterfaceName: "eth20", MTU: 1500, // Need to match host MTU or felix will restart. } - err := eth20.Start() + err := eth20.Start(infra) Expect(err).NotTo(HaveOccurred()) // assign address to eth20 and add route to the .20 network @@ -4264,7 +4704,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { remoteWL.IP6 = remoteWLIP } - err := remoteWL.Start() + err := remoteWL.Start(infra) Expect(err).NotTo(HaveOccurred()) clusterIP := "10.101.0.211" @@ -4273,10 +4713,10 @@ func describeBPFTests(opts ...bpfTestOpt) bool { } svcHostNP := k8sService("test-host-np", clusterIP, hostW[0], 81, 8055, int32(hostNP), testOpts.protocol) - testSvcNamespace := svcHostNP.ObjectMeta.Namespace + testSvcNamespace := svcHostNP.Namespace _, err = k8sClient.CoreV1().Services(testSvcNamespace).Create(context.Background(), svcHostNP, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - Eventually(k8sGetEpsForServiceFunc(k8sClient, svcHostNP), "10s").Should(HaveLen(1), + Eventually(checkSvcEndpoints(k8sClient, svcHostNP), "10s").Should(Equal(1), "Service endpoints didn't get created? Is controller-manager happy?") if testOpts.ipv6 { @@ -4322,8 +4762,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { tcpdump := w[0][0].AttachTCPDump() tcpdump.SetLogEnabled(true) tcpdump.AddMatcher("mtu-1300", regexp.MustCompile("mtu 1300")) - tcpdump.Start("-vvv", "icmp", "or", "icmp6") - defer tcpdump.Stop() + tcpdump.Start(infra, "-vvv", "icmp", "or", "icmp6") ipRouteFlushCache := []string{"ip", "route", "flush", "cache"} if testOpts.ipv6 { @@ -4358,8 +4797,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { tcpdump.SetLogEnabled(true) tcpdump.AddMatcher("mtu-1300", regexp.MustCompile("mtu 1300")) // we also need to watch for the ICMP forwarded to the host with the backend via VXLAN - tcpdump.Start("-vvv", "icmp", "or", "icmp6", "or", "udp", "port", "4789") - defer tcpdump.Stop() + tcpdump.Start(infra, "-vvv", "icmp", "or", "icmp6", "or", "udp", "port", "4789") ipRouteFlushCache := []string{"ip", "route", "flush", "cache"} if testOpts.ipv6 { @@ -4455,10 +4893,10 @@ func describeBPFTests(opts ...bpfTestOpt) bool { k8sClient := infra.(*infrastructure.K8sDatastoreInfra).K8sClient testSvc = k8sService(testSvcName, clusterIP, tgtWorkload, 80, tgtPort, int32(npPort), testOpts.protocol) - testSvcNamespace = testSvc.ObjectMeta.Namespace + testSvcNamespace = testSvc.Namespace _, err := k8sClient.CoreV1().Services(testSvcNamespace).Create(context.Background(), testSvc, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - Eventually(k8sGetEpsForServiceFunc(k8sClient, testSvc), "10s").Should(HaveLen(1), + Eventually(checkSvcEndpoints(k8sClient, testSvc), "10s").Should(Equal(1), "Service endpoints didn't get created? Is controller-manager happy?") // Sync with all felixes because some fwd tests with "none" @@ -4479,7 +4917,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { family = 4 } - m, be := dumpNATMapsAny(family, flx) + m, be, _ := dumpNATMapsAny(family, flx) v, ok := m[natFtKey] if !ok || v.Count() == 0 { return false @@ -4531,13 +4969,12 @@ func describeBPFTests(opts ...bpfTestOpt) bool { felixIP(1), containerIP(externalClient), felixIP(1)) } tcpdump.AddMatcher("ICMP", regexp.MustCompile(matcher)) - tcpdump.Start(testOpts.protocol, "port", strconv.Itoa(int(npPort)), "or", icmpProto) - defer tcpdump.Stop() + tcpdump.Start(infra, testOpts.protocol, "port", strconv.Itoa(int(npPort)), "or", icmpProto) cc.ExpectNone(externalClient, TargetIP(felixIP(1)), npPort) cc.CheckConnectivity() - Eventually(func() int { return tcpdump.MatchCount("ICMP") }). + Eventually(func() int { return tcpdump.MatchCount("ICMP") }, 10*time.Second, 200*time.Millisecond). Should(BeNumerically(">", 0), matcher) }) }) @@ -4569,12 +5006,11 @@ func describeBPFTests(opts ...bpfTestOpt) bool { felixIP(1), containerIP(externalClient), felixIP(1), npPort) } tcpdump.AddMatcher("ICMP", regexp.MustCompile(matcher)) - tcpdump.Start(testOpts.protocol, "port", strconv.Itoa(int(npPort)), "or", icmpProto) - defer tcpdump.Stop() + tcpdump.Start(infra, testOpts.protocol, "port", strconv.Itoa(int(npPort)), "or", icmpProto) cc.ExpectNone(externalClient, TargetIP(felixIP(1)), npPort) cc.CheckConnectivity() - Eventually(func() int { return tcpdump.MatchCount("ICMP") }). + Eventually(func() int { return tcpdump.MatchCount("ICMP") }, 10*time.Second, 200*time.Millisecond). Should(BeNumerically(">", 0), matcher) }) } @@ -4593,12 +5029,11 @@ func describeBPFTests(opts ...bpfTestOpt) bool { tgtWorkload.IP, w[1][1].IP, tgtWorkload.IP, tgtPort) } tcpdump.AddMatcher("ICMP", regexp.MustCompile(matcher)) - tcpdump.Start(testOpts.protocol, "port", strconv.Itoa(tgtPort), "or", icmpProto) - defer tcpdump.Stop() + tcpdump.Start(infra, testOpts.protocol, "port", strconv.Itoa(tgtPort), "or", icmpProto) cc.ExpectNone(w[1][1], TargetIP(tgtWorkload.IP), uint16(tgtPort)) cc.CheckConnectivity() - Eventually(func() int { return tcpdump.MatchCount("ICMP") }). + Eventually(func() int { return tcpdump.MatchCount("ICMP") }, 10*time.Second, 200*time.Millisecond). Should(BeNumerically(">", 0), matcher) }) @@ -4617,7 +5052,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { tgtWorkload.IP, w[1][1].IP, w[0][0].IP, tgtPort) } tcpdump.AddMatcher("ICMP", regexp.MustCompile(matcher)) - tcpdump.Start(testOpts.protocol, "port", strconv.Itoa(tgtPort), "or", icmpProto) + tcpdump.Start(infra, testOpts.protocol, "port", strconv.Itoa(tgtPort), "or", icmpProto) } else { if testOpts.ipv6 { matcher = fmt.Sprintf("IP6 %s > %s: ICMP6, destination unreachable, unreachable port, %s udp port %d", @@ -4627,9 +5062,8 @@ func describeBPFTests(opts ...bpfTestOpt) bool { tgtWorkload.IP, w[1][1].IP, felixIP(1), npPort) } tcpdump.AddMatcher("ICMP", regexp.MustCompile(matcher)) - tcpdump.Start(testOpts.protocol, "port", strconv.Itoa(int(npPort)), "or", icmpProto) + tcpdump.Start(infra, testOpts.protocol, "port", strconv.Itoa(int(npPort)), "or", icmpProto) } - defer tcpdump.Stop() cc.ExpectNone(w[1][1], TargetIP(felixIP(1)), npPort) cc.CheckConnectivity() @@ -4638,7 +5072,6 @@ func describeBPFTests(opts ...bpfTestOpt) bool { }) }) }) - }) It("should have connectivity when DNAT redirects to-host traffic to a local pod.", func() { @@ -4649,18 +5082,16 @@ func describeBPFTests(opts ...bpfTestOpt) bool { hostIP0 := TargetIP(felixIP(0)) hostPort := uint16(8080) + target := net.JoinHostPort(w[0][0].IP, "8055") + var ( - target string tool string nftFamily string ) - if testOpts.ipv6 { - target = fmt.Sprintf("[%s]:8055", w[0][0].IP) tool = "ip6tables" nftFamily = "ip6" } else { - target = fmt.Sprintf("%s:8055", w[0][0].IP) tool = "iptables" nftFamily = "ip" } @@ -4725,11 +5156,11 @@ func describeBPFTests(opts ...bpfTestOpt) bool { It("should have connectivity from host-networked pods via service to host-networked backend", func() { By("Setting up the service") testSvc := k8sService("host-svc", clusterIP, hostW[0], 80, 8055, 0, testOpts.protocol) - testSvcNamespace := testSvc.ObjectMeta.Namespace + testSvcNamespace := testSvc.Namespace k8sClient := infra.(*infrastructure.K8sDatastoreInfra).K8sClient _, err := k8sClient.CoreV1().Services(testSvcNamespace).Create(context.Background(), testSvc, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - Eventually(k8sGetEpsForServiceFunc(k8sClient, testSvc), "10s").Should(HaveLen(1), + Eventually(checkSvcEndpoints(k8sClient, testSvc), "10s").Should(Equal(1), "Service endpoints didn't get created? Is controller-manager happy?") By("Testing connectivity") @@ -4761,6 +5192,13 @@ func describeBPFTests(opts ...bpfTestOpt) bool { pol = createPolicy(pol) pc = nil + if NFTMode() && testOpts.ipv6 && !testOpts.dsr && testOpts.tunnel == "none" && testOpts.connTimeEnabled { + // In NFT mode, we add the kube-proxy tables. + tc.Felixes[0].Exec("nft", "add", "table", "ip", "kube-proxy") + tc.Felixes[0].Exec("nft", "add", "chain", "ip", "kube-proxy", "KUBE-TEST", "{ type filter hook forward priority 0 ; }") + tc.Felixes[0].Exec("nft", "add", "table", "ip6", "kube-proxy") + tc.Felixes[0].Exec("nft", "add", "chain", "ip6", "kube-proxy", "KUBE-TEST", "{ type filter hook forward priority 0 ; }") + } }) AfterEach(func() { @@ -4789,6 +5227,12 @@ func describeBPFTests(opts ...bpfTestOpt) bool { // Wait for BPF to be active. ensureAllNodesBPFProgramsAttached(tc.Felixes) + if NFTMode() && testOpts.ipv6 && !testOpts.dsr && testOpts.tunnel == "none" && testOpts.connTimeEnabled { + Eventually(func() string { + out, _ := tc.Felixes[0].ExecOutput("nft", "list", "tables") + return out + }, "15s", "1s").ShouldNot(ContainSubstring("kube-proxy")) + } } expectPongs := func() { @@ -4840,6 +5284,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { // To mimic 3rd party CNI, we do not install IPPools and set the source to // learn routes to WorkloadIPs as IPAM/CNI is not going to provide either. options.UseIPPools = false + options.SimulateBIRDRoutes = true options.ExtraEnvVars["FELIX_ROUTESOURCE"] = "WorkloadIPs" setupCluster() }) @@ -4851,7 +5296,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { Skip("NFT does not support third-party rules") } - c := infrastructure.RunExtClient("ext-workload") + c := infrastructure.RunExtClient(infra, "ext-workload") extWorkload = &workload.Workload{ C: c, Name: "ext-workload", @@ -4860,7 +5305,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { IP: containerIP(c), } - err := extWorkload.Start() + err := extWorkload.Start(infra) // FIXME Expect(err).NotTo(HaveOccurred()) tool := "iptables" @@ -4923,14 +5368,12 @@ func describeBPFTests(opts ...bpfTestOpt) bool { pol.Spec.Selector = "all()" pol = createPolicy(pol) - }) if testOpts.protocol == "udp" || testOpts.tunnel == "ipip" || testOpts.ipv6 { return } It("should allow traffic from workload to this host device", func() { - var ( test30 *workload.Workload test30IP string @@ -4958,7 +5401,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { InterfaceName: "test30", MTU: 1500, // Need to match host MTU or felix will restart. } - err := test30.Start() + err := test30.Start(infra) Expect(err).NotTo(HaveOccurred()) // assign address to test30 and add route to the .30 network if testOpts.ipv6 { @@ -5029,8 +5472,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { matcher := fmt.Sprintf("%s %s\\.30444 > %s\\.30444: UDP", ipVer, w[1][0].IP, w[0][0].IP) tcpdump.AddMatcher("UDP-30444", regexp.MustCompile(matcher)) - tcpdump.Start(testOpts.protocol, "port", "30444", "or", "port", "30445") - defer tcpdump.Stop() + tcpdump.Start(infra, testOpts.protocol, "port", "30444", "or", "port", "30445") // send a packet from the correct workload to create a conntrack entry _, err := w[1][0].RunCmd("pktgen", w[1][0].IP, w[0][0].IP, "udp", @@ -5141,7 +5583,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { InterfaceName: "eth20", MTU: 1500, // Need to match host MTU or felix will restart. } - err := eth20.Start() + err := eth20.Start(infra) Expect(err).NotTo(HaveOccurred()) // assign address to eth20 and add route to the .20 network @@ -5174,7 +5616,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { InterfaceName: "eth30", MTU: 1500, // Need to match host MTU or felix will restart. } - err = eth30.Start() + err = eth30.Start(infra) Expect(err).NotTo(HaveOccurred()) // assign address to eth30 and add route to the .30 network @@ -5223,8 +5665,7 @@ func describeBPFTests(opts ...bpfTestOpt) bool { tcpdump.SetLogEnabled(true) matcher := fmt.Sprintf("%s %s\\.30446 > %s\\.30446: UDP", ipVer, fakeWorkloadIP, w[1][1].IP) tcpdump.AddMatcher("UDP-30446", regexp.MustCompile(matcher)) - tcpdump.Start() - defer tcpdump.Stop() + tcpdump.Start(infra) _, err := eth20.RunCmd("pktgen", fakeWorkloadIP, w[1][1].IP, "udp", "--port-src", "30446", "--port-dst", "30446") @@ -5298,14 +5739,16 @@ func typeMetaV1(kind string) metav1.TypeMeta { func objectMetaV1(name string) metav1.ObjectMeta { return metav1.ObjectMeta{ - Name: name, - Namespace: "default", + Name: name, + Namespace: "default", + Annotations: make(map[string]string), } } -func dumpNATmaps(felixes []*infrastructure.Felix) ([]nat.MapMem, []nat.BackendMapMem) { +func dumpNATmaps(felixes []*infrastructure.Felix) ([]nat.MapMem, []nat.BackendMapMem, []nat.MaglevMapMem) { bpfsvcs := make([]nat.MapMem, len(felixes)) bpfeps := make([]nat.BackendMapMem, len(felixes)) + bpfcheps := make([]nat.MaglevMapMem, len(felixes)) // Felixes are independent, we can dump the maps concurrently var wg sync.WaitGroup @@ -5315,20 +5758,23 @@ func dumpNATmaps(felixes []*infrastructure.Felix) ([]nat.MapMem, []nat.BackendMa go func(i int) { defer wg.Done() defer GinkgoRecover() - bpfsvcs[i], bpfeps[i] = dumpNATMaps(felixes[i]) + bpfsvcs[i], bpfeps[i], bpfcheps[i] = dumpNATMaps(felixes[i]) }(i) } wg.Wait() - return bpfsvcs, bpfeps + return bpfsvcs, bpfeps, bpfcheps } func dumpNATmapsAny(family int, felixes []*infrastructure.Felix) ( - []map[nat.FrontendKeyInterface]nat.FrontendValue, []map[nat.BackendKey]nat.BackendValueInterface, + []map[nat.FrontendKeyInterface]nat.FrontendValue, + []map[nat.BackendKey]nat.BackendValueInterface, + []map[nat.MaglevBackendKeyInterface]nat.BackendValueInterface, ) { bpfsvcs := make([]map[nat.FrontendKeyInterface]nat.FrontendValue, len(felixes)) bpfeps := make([]map[nat.BackendKey]nat.BackendValueInterface, len(felixes)) + bpfcheps := make([]map[nat.MaglevBackendKeyInterface]nat.BackendValueInterface, len(felixes)) // Felixes are independent, we can dump the maps concurrently var wg sync.WaitGroup @@ -5338,18 +5784,19 @@ func dumpNATmapsAny(family int, felixes []*infrastructure.Felix) ( go func(i int) { defer wg.Done() defer GinkgoRecover() - bpfsvcs[i], bpfeps[i] = dumpNATMapsAny(family, felixes[i]) + bpfsvcs[i], bpfeps[i], bpfcheps[i] = dumpNATMapsAny(family, felixes[i]) }(i) } wg.Wait() - return bpfsvcs, bpfeps + return bpfsvcs, bpfeps, bpfcheps } -func dumpNATmapsV6(felixes []*infrastructure.Felix) ([]nat.MapMemV6, []nat.BackendMapMemV6) { +func dumpNATmapsV6(felixes []*infrastructure.Felix) ([]nat.MapMemV6, []nat.BackendMapMemV6, []nat.MaglevMapMemV6) { bpfsvcs := make([]nat.MapMemV6, len(felixes)) bpfeps := make([]nat.BackendMapMemV6, len(felixes)) + cheps := make([]nat.MaglevMapMemV6, len(felixes)) // Felixes are independent, we can dump the maps concurrently var wg sync.WaitGroup @@ -5359,49 +5806,57 @@ func dumpNATmapsV6(felixes []*infrastructure.Felix) ([]nat.MapMemV6, []nat.Backe go func(i int) { defer wg.Done() defer GinkgoRecover() - bpfsvcs[i], bpfeps[i] = dumpNATMapsV6(felixes[i]) + bpfsvcs[i], bpfeps[i], cheps[i] = dumpNATMapsV6(felixes[i]) }(i) } wg.Wait() - return bpfsvcs, bpfeps + return bpfsvcs, bpfeps, cheps } -func dumpNATMaps(felix *infrastructure.Felix) (nat.MapMem, nat.BackendMapMem) { - return dumpNATMap(felix), dumpEPMap(felix) +func dumpNATMaps(felix *infrastructure.Felix) (nat.MapMem, nat.BackendMapMem, nat.MaglevMapMem) { + return dumpNATMap(felix), dumpEPMap(felix), dumpMaglevMap(felix) } -func dumpNATMapsV6(felix *infrastructure.Felix) (nat.MapMemV6, nat.BackendMapMemV6) { - return dumpNATMapV6(felix), dumpEPMapV6(felix) +func dumpNATMapsV6(felix *infrastructure.Felix) (nat.MapMemV6, nat.BackendMapMemV6, nat.MaglevMapMemV6) { + return dumpNATMapV6(felix), dumpEPMapV6(felix), dumpMaglevMapV6(felix) } func dumpNATMapsAny(family int, felix *infrastructure.Felix) ( map[nat.FrontendKeyInterface]nat.FrontendValue, map[nat.BackendKey]nat.BackendValueInterface, + map[nat.MaglevBackendKeyInterface]nat.BackendValueInterface, ) { f := make(map[nat.FrontendKeyInterface]nat.FrontendValue) b := make(map[nat.BackendKey]nat.BackendValueInterface) + m := make(map[nat.MaglevBackendKeyInterface]nat.BackendValueInterface) if family == 6 { - f6, b6 := dumpNATMapsV6(felix) + f6, b6, m6 := dumpNATMapsV6(felix) for k, v := range f6 { f[k] = v } for k, v := range b6 { b[k] = v } + for k, v := range m6 { + m[k] = v + } } else { - f4, b4 := dumpNATMaps(felix) + f4, b4, m4 := dumpNATMaps(felix) for k, v := range f4 { f[k] = v } for k, v := range b4 { b[k] = v } + for k, v := range m4 { + m[k] = v + } } - return f, b + return f, b, m } func dumpCTMapsAny(family int, felix *infrastructure.Felix) map[conntrack.KeyInterface]conntrack.ValueInterface { @@ -5454,6 +5909,20 @@ func dumpEPMap(felix *infrastructure.Felix) nat.BackendMapMem { return m } +func dumpMaglevMap(felix *infrastructure.Felix) nat.MaglevMapMem { + bm := nat.MaglevMap() + m := make(nat.MaglevMapMem) + dumpBPFMap(felix, bm, nat.MaglevMapMemIter(m)) + return m +} + +func dumpMaglevMapV6(felix *infrastructure.Felix) nat.MaglevMapMemV6 { + bm := nat.MaglevMapV6() + m := make(nat.MaglevMapMemV6) + dumpBPFMap(felix, bm, nat.MaglevMapMemV6Iter(m)) + return m +} + func dumpNATMapV6(felix *infrastructure.Felix) nat.MapMemV6 { bm := nat.FrontendMapV6() m := make(nat.MapMemV6) @@ -5609,9 +6078,10 @@ func k8sService(name, clusterIP string, w *workload.Workload, port, svcType = v1.ServiceTypeNodePort } + meta := objectMetaV1(name) return &v1.Service{ TypeMeta: typeMetaV1("Service"), - ObjectMeta: objectMetaV1(name), + ObjectMeta: meta, Spec: v1.ServiceSpec{ ClusterIP: clusterIP, Type: svcType, @@ -5698,32 +6168,47 @@ func k8sServiceWithExtIP(name, clusterIP string, w *workload.Workload, port, } } -func k8sGetEpsForService(k8s kubernetes.Interface, svc *v1.Service) []v1.EndpointSubset { - ep, _ := k8s.CoreV1(). - Endpoints(svc.ObjectMeta.Namespace). - Get(context.Background(), svc.ObjectMeta.Name, metav1.GetOptions{}) - log.WithField("endpoints", - spew.Sprint(ep)).Infof("Got endpoints for %s", svc.ObjectMeta.Name) - return ep.Subsets +func k8sGetEpsForService(k8s kubernetes.Interface, svc *v1.Service) []discovery.EndpointSlice { + eps, err := k8s.DiscoveryV1(). + EndpointSlices(svc.Namespace). + List(context.Background(), metav1.ListOptions{ + LabelSelector: "kubernetes.io/service-name=" + svc.Name, + }) + Expect(err).NotTo(HaveOccurred()) + return eps.Items } -func k8sGetEpsForServiceFunc(k8s kubernetes.Interface, svc *v1.Service) func() []v1.EndpointSubset { - return func() []v1.EndpointSubset { +func k8sGetEpsForServiceFunc(k8s kubernetes.Interface, svc *v1.Service) func() []discovery.EndpointSlice { + return func() []discovery.EndpointSlice { return k8sGetEpsForService(k8s, svc) } } +func checkSvcEndpoints(k8s kubernetes.Interface, svc *v1.Service) func() int { + return func() int { + epslices := k8sGetEpsForService(k8s, svc) + totalEps := 0 + for _, eps := range epslices { + if eps.Endpoints == nil { + continue + } + totalEps += len(eps.Endpoints) + } + return totalEps + } +} + func k8sUpdateService(k8sClient kubernetes.Interface, nameSpace, svcName string, oldsvc, newsvc *v1.Service) { svc, err := k8sClient.CoreV1(). Services(nameSpace). Get(context.Background(), svcName, metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) log.WithField("origSvc", svc).Info("Read original service before updating it") - newsvc.ObjectMeta.ResourceVersion = svc.ObjectMeta.ResourceVersion + newsvc.ResourceVersion = svc.ResourceVersion _, err = k8sClient.CoreV1().Services(nameSpace).Update(context.Background(), newsvc, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) - Eventually(k8sGetEpsForServiceFunc(k8sClient, oldsvc), "10s").Should(HaveLen(1), + Eventually(checkSvcEndpoints(k8sClient, oldsvc), "10s").Should(Equal(1), "Service endpoints didn't get created? Is controller-manager happy?") - updatedSvc, err := k8sClient.CoreV1().Services(nameSpace).Get(context.Background(), svcName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) log.WithField("updatedSvc", updatedSvc).Info("Read back updated Service") @@ -5744,10 +6229,10 @@ func k8sCreateLBServiceWithEndPoints(k8sClient kubernetes.Interface, name, clust testSvc = k8sLBService(name, clusterIP, "nobackend", port, tgtPort, protocol, externalIPs, srcRange) epslen = 0 } - testSvcNamespace = testSvc.ObjectMeta.Namespace + testSvcNamespace = testSvc.Namespace _, err := k8sClient.CoreV1().Services(testSvcNamespace).Create(context.Background(), testSvc, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - Eventually(k8sGetEpsForServiceFunc(k8sClient, testSvc), "10s").Should(HaveLen(epslen), + Eventually(checkSvcEndpoints(k8sClient, testSvc), "10s").Should(Equal(epslen), "Service endpoints didn't get created? Is controller-manager happy?") return testSvc } @@ -5832,14 +6317,14 @@ func conntrackFlushWorkloadEntries(felixes []*infrastructure.Felix) func() { } } -func conntrackChecks(felixes []*infrastructure.Felix) []interface{} { +func conntrackChecks(felixes []*infrastructure.Felix) []any { if felixes[0].ExpectedIPIPTunnelAddr != "" || felixes[0].ExpectedWireguardTunnelAddr != "" || felixes[0].ExpectedWireguardV6TunnelAddr != "" { return nil } - return []interface{}{ + return []any{ CheckWithInit(conntrackFlushWorkloadEntries(felixes)), CheckWithFinalTest(conntrackCheck(felixes)), CheckWithBeforeRetry(conntrackFlushWorkloadEntries(felixes)), @@ -5905,23 +6390,17 @@ func checkServiceRoute(felix *infrastructure.Felix, ip string) bool { lines := strings.Split(out, "\n") rtRE := regexp.MustCompile(ip + " .* dev bpfin.cali") - for _, l := range lines { - if rtRE.MatchString(l) { - return true - } - } - - return false + return slices.ContainsFunc(lines, rtRE.MatchString) } -func checkIfPolicyOrRuleProgrammed(felix *infrastructure.Felix, iface, hook, polName, action string, isWorkload, isPolicy bool, ipFamily proto.IPVersion) bool { +func checkIfPolicyOrRuleProgrammed(felix *infrastructure.Felix, iface, hook, polName, action string, isWorkload bool, polType string, ipFamily proto.IPVersion) bool { startStr := "" endStr := "" - if isPolicy { - startStr = fmt.Sprintf("Start of policy %s", polName) - endStr = fmt.Sprintf("End of policy %s", polName) + if polType != "" { + startStr = fmt.Sprintf("Start of %s %s", polType, polName) + endStr = fmt.Sprintf("End of %s %s", polType, polName) } - actionStr := fmt.Sprintf("Start of rule action:\"%s\"", action) + actionStr := fmt.Sprintf("Start of rule %s action:\"%s\"", polName, action) var policyDbg bpf.PolicyDebugInfo out, err := felix.ExecOutput("cat", bpf.PolicyDebugJSONFileName(iface, hook, ipFamily)) if err != nil { @@ -5969,15 +6448,20 @@ func checkIfPolicyOrRuleProgrammed(felix *infrastructure.Felix, iface, hook, pol } func bpfCheckIfRuleProgrammed(felix *infrastructure.Felix, iface, hook, polName, action string, isWorkload bool) bool { - return checkIfPolicyOrRuleProgrammed(felix, iface, hook, polName, action, isWorkload, false, proto.IPVersion_IPV4) + return checkIfPolicyOrRuleProgrammed(felix, iface, hook, polName, action, isWorkload, "", proto.IPVersion_IPV4) +} + +func bpfCheckIfNetworkPolicyProgrammed(felix *infrastructure.Felix, iface, hook, polNS, polName, action string, isWorkload bool) bool { + namespacedName := fmt.Sprintf("%s/%s", polNS, polName) + return checkIfPolicyOrRuleProgrammed(felix, iface, hook, namespacedName, action, isWorkload, "NetworkPolicy", proto.IPVersion_IPV4) } -func bpfCheckIfPolicyProgrammed(felix *infrastructure.Felix, iface, hook, polName, action string, isWorkload bool) bool { - return checkIfPolicyOrRuleProgrammed(felix, iface, hook, polName, action, isWorkload, true, proto.IPVersion_IPV4) +func bpfCheckIfGlobalNetworkPolicyProgrammed(felix *infrastructure.Felix, iface, hook, polName, action string, isWorkload bool) bool { + return checkIfPolicyOrRuleProgrammed(felix, iface, hook, polName, action, isWorkload, "GlobalNetworkPolicy", proto.IPVersion_IPV4) } -func bpfCheckIfPolicyProgrammedV6(felix *infrastructure.Felix, iface, hook, polName, action string, isWorkload bool) bool { - return checkIfPolicyOrRuleProgrammed(felix, iface, hook, polName, action, isWorkload, true, proto.IPVersion_IPV6) +func bpfCheckIfGlobalNetworkPolicyProgrammedV6(felix *infrastructure.Felix, iface, hook, polName, action string, isWorkload bool) bool { + return checkIfPolicyOrRuleProgrammed(felix, iface, hook, polName, action, isWorkload, "GlobalNetworkPolicy", proto.IPVersion_IPV6) } func bpfDumpPolicy(felix *infrastructure.Felix, iface, hook string) string { @@ -5995,10 +6479,22 @@ func bpfDumpPolicy(felix *infrastructure.Felix, iface, hook string) string { return out } -func bpfWaitForPolicy(felix *infrastructure.Felix, iface, hook, policy string) string { - search := fmt.Sprintf("Start of policy %s", policy) +// bpfWaitForGlobalNetworkPolicy waits for the given global network policy to appear in BPF policy. +func bpfWaitForGlobalNetworkPolicy(felix *infrastructure.Felix, iface, hook, policyName string) string { + search := fmt.Sprintf("Start of GlobalNetworkPolicy %s", policyName) + return bpfWaitForPolicy(felix, iface, hook, search) +} + +// bpfWaitForNetworkPolicy waits for the given network policy in the given namespace to appear in BPF policy. +func bpfWaitForNetworkPolicy(felix *infrastructure.Felix, iface, hook, ns, policyName string) string { + search := fmt.Sprintf("Start of NetworkPolicy %s/%s", ns, policyName) + return bpfWaitForPolicy(felix, iface, hook, search) +} + +// bpfWaitForNetworkPolicy waits for the given search string to appear in BPF policy. +func bpfWaitForPolicy(felix *infrastructure.Felix, iface, hook, search string) string { out := "" - EventuallyWithOffset(1, func() string { + EventuallyWithOffset(2, func() string { out = bpfDumpPolicy(felix, iface, hook) return out }, "5s", "200ms").Should(ContainSubstring(search)) diff --git a/felix/fv/config_update_test.go b/felix/fv/config_update_test.go index 48751a646ab..a9d2d219b5d 100644 --- a/felix/fv/config_update_test.go +++ b/felix/fv/config_update_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -21,15 +19,14 @@ import ( "errors" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/projectcalico/calico/felix/fv/containers" "github.com/projectcalico/calico/felix/fv/infrastructure" "github.com/projectcalico/calico/felix/fv/metrics" - "github.com/projectcalico/calico/felix/fv/workload" + "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/options" ) @@ -40,43 +37,22 @@ const ( kubeProxyModeIptables = "iptables" ) -var _ = Context("Config update tests, after starting felix", func() { +var _ = infrastructure.DatastoreDescribe("Config update tests, after starting felix", []apiconfig.DatastoreType{apiconfig.EtcdV3}, func(getInfra infrastructure.InfraFactory) { var ( - etcd *containers.Container tc infrastructure.TopologyContainers felixPID int client client.Interface infra infrastructure.DatastoreInfra - w [3]*workload.Workload cfgChangeTime time.Time ) BeforeEach(func() { - if NFTMode() { - Skip("TODO: Implement for NFT") - } - tc, etcd, client, infra = infrastructure.StartSingleNodeEtcdTopology(infrastructure.DefaultTopologyOptions()) + infra = getInfra() + tc, client = infrastructure.StartSingleNodeTopology(infrastructure.DefaultTopologyOptions(), infra) + _ = infra felixPID = tc.Felixes[0].GetSinglePID("calico-felix") }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - tc.Felixes[0].Exec("iptables-save", "-c") - tc.Felixes[0].Exec("ip", "r") - } - - for ii := range w { - w[ii].Stop() - } - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - etcd.Exec("etcdctl", "get", "/", "--prefix", "--keys-only") - } - etcd.Stop() - infra.Stop() - }) - shouldStayUp := func() { // Felix has a 2s timer before it restarts so need to monitor for > 2s. // We use ContainElement because Felix regularly forks off subprocesses and those @@ -236,6 +212,46 @@ var _ = Context("Config update tests, after starting felix", func() { It("should exit after a delay", shouldExitAfterADelay) }) }) + + Context("after switching kube-proxy mode to nftables that should trigger a restart", func() { + shouldExitAfterADelay := func() { + // Felix checks every 15s, so wait with enough buffer. + Eventually(tc.Felixes[0].GetFelixPIDs, "30s", "100ms").ShouldNot(ContainElement(felixPID)) + + // Update felix pid after restart. + felixPID = tc.Felixes[0].GetSinglePID("calico-felix") + } + + BeforeEach(func() { + if NFTMode() { + Skip("Skipping auto-detection tests when felix is already explicitly in nftables mode") + } + + cfgChangeTime = time.Now() + + // Create nftables rules in the "kube-proxy" table, which should trigger felix restart. + tc.Felixes[0].Exec("nft", "add", "table", "ip", "kube-proxy") + tc.Felixes[0].Exec("nft", "add", "chain", "ip", "kube-proxy", "KUBE-TEST", "{ type filter hook forward priority 0 ; }") + }) + + It("should exit after a delay", shouldExitAfterADelay) + + Context("after removing nftables rules that should trigger a restart", func() { + BeforeEach(func() { + // Wait felix in sync again. + shouldExitAfterADelay() + waitForFelixInSync(tc.Felixes[0]) + + // Track the current time and then make the config change back to iptables mode. + cfgChangeTime = time.Now() + + // Remove the nftables rules in the "kube-proxy" table, which should trigger felix restart. + tc.Felixes[0].Exec("nft", "delete", "table", "ip", "kube-proxy") + }) + + It("should exit after a delay", shouldExitAfterADelay) + }) + }) }) func waitForFelixInSync(felix *infrastructure.Felix) { diff --git a/felix/fv/connectivity/conncheck.go b/felix/fv/connectivity/conncheck.go index ffc013797c4..07526f23058 100644 --- a/felix/fv/connectivity/conncheck.go +++ b/felix/fv/connectivity/conncheck.go @@ -22,13 +22,14 @@ import ( "io" "os/exec" "regexp" + "slices" "strconv" "strings" "sync" "time" "github.com/google/uuid" - "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/v2" log "github.com/sirupsen/logrus" //nolint:staticcheck // Ignore ST1001: should not use dot imports @@ -298,26 +299,26 @@ func (c *Checker) ExpectedConnectivityPretty() []string { var defaultConnectivityTimeout = 10 * time.Second -func (c *Checker) CheckConnectivityOffset(offset int, opts ...interface{}) { +func (c *Checker) CheckConnectivityOffset(offset int, opts ...any) { c.CheckConnectivityWithTimeoutOffset(offset+2, defaultConnectivityTimeout, opts...) } -func (c *Checker) CheckConnectivity(opts ...interface{}) { +func (c *Checker) CheckConnectivity(opts ...any) { c.CheckConnectivityWithTimeoutOffset(2, defaultConnectivityTimeout, opts...) } -func (c *Checker) CheckConnectivityPacketLoss(opts ...interface{}) { +func (c *Checker) CheckConnectivityPacketLoss(opts ...any) { // Timeout is not used for packet loss test because there is no retry. c.CheckConnectivityWithTimeoutOffset(2, 0*time.Second, opts...) } -func (c *Checker) CheckConnectivityWithTimeout(timeout time.Duration, opts ...interface{}) { +func (c *Checker) CheckConnectivityWithTimeout(timeout time.Duration, opts ...any) { Expect(timeout).To(BeNumerically(">", 100*time.Millisecond), "Very low timeout, did you mean to multiply by time.?") c.CheckConnectivityWithTimeoutOffset(2, timeout, opts...) } -func (c *Checker) CheckConnectivityWithTimeoutOffset(callerSkip int, timeout time.Duration, opts ...interface{}) { +func (c *Checker) CheckConnectivityWithTimeoutOffset(callerSkip int, timeout time.Duration, opts ...any) { log.Info("Starting connectivity check...") for _, o := range opts { switch v := o.(type) { @@ -497,19 +498,19 @@ type ConnectionSource interface { SourceIPs() []string } -func (m *Matcher) Match(actual interface{}) (success bool, err error) { +func (m *Matcher) Match(actual any) (success bool, err error) { actual.(ConnectionSource).PreRetryCleanup(m.IP, m.Port, m.Protocol) success = actual.(ConnectionSource).CanConnectTo(m.IP, m.Port, m.Protocol) != nil return } -func (m *Matcher) FailureMessage(actual interface{}) (message string) { +func (m *Matcher) FailureMessage(actual any) (message string) { src := actual.(ConnectionSource) message = fmt.Sprintf("Expected %v\n\t%+v\nto have connectivity to %v\n\t%v:%v\nbut it does not", src.SourceName(), src, m.TargetName, m.IP, m.Port) return } -func (m *Matcher) NegatedFailureMessage(actual interface{}) (message string) { +func (m *Matcher) NegatedFailureMessage(actual any) (message string) { src := actual.(ConnectionSource) message = fmt.Sprintf("Expected %v\n\t%+v\nnot to have connectivity to %v\n\t%v:%v\nbut it does", src.SourceName(), src, m.TargetName, m.IP, m.Port) return @@ -632,13 +633,7 @@ func (e Expectation) Matches(response *Result, checkSNAT bool) bool { } if checkSNAT { - match := false - for _, src := range e.ExpSrcIPs { - if src == response.LastResponse.SourceIP() { - match = true - break - } - } + match := slices.Contains(e.ExpSrcIPs, response.LastResponse.SourceIP()) if !match { return false } @@ -945,17 +940,19 @@ type Runtime interface { type PersistentConnection struct { sync.Mutex - RuntimeName string - Runtime Runtime - Name string - Protocol string - IP string - Port int - SourcePort int - MonitorConnectivity bool - NamespacePath string - Timeout time.Duration - Sleep time.Duration + RuntimeName string + Runtime Runtime + Name string + Protocol string + IP string + Port int + SourcePort int + MonitorConnectivity bool + NamespacePath string + Timeout time.Duration + Sleep time.Duration + ProbeLoopFileTimeout time.Duration + connectionReset bool loopFile string runCmd *exec.Cmd @@ -1043,6 +1040,7 @@ func (pc *PersistentConnection) Start() error { line, err := stdoutReader.ReadString('\n') if err != nil { log.WithError(err).Info("End of permanent connection stdout") + pc.connectionReset = true return } line = strings.TrimSpace(string(line)) @@ -1063,6 +1061,7 @@ func (pc *PersistentConnection) Start() error { line, err := stderrReader.ReadString('\n') if err != nil { log.WithError(err).Info("End of permanent connection stderr") + pc.connectionReset = true return } line = strings.TrimSpace(string(line)) @@ -1073,24 +1072,28 @@ func (pc *PersistentConnection) Start() error { return fmt.Errorf("failed to start a permanent connection: %v", err) } - loopFileGone := false - for range 5 { + timeout := 5 * time.Second + if pc.ProbeLoopFileTimeout > 0 { + timeout = pc.ProbeLoopFileTimeout + } + + timedOut := time.After(timeout) + for { err = pc.Runtime.ExecMayFail("stat", loopFile) if err != nil { - loopFileGone = true break } - time.Sleep(time.Second) + select { + case <-timedOut: + return fmt.Errorf("failed to wait for test-connection to be ready, the loop file did not disappear") + case <-time.After(time.Second): + } } pc.loopFile = loopFile pc.runCmd = runCmd pc.Name = n - if !loopFileGone { - return fmt.Errorf("failed to wait for test-connection to be ready, the loop file did not disappear") - } - return nil } @@ -1110,3 +1113,7 @@ func (pc *PersistentConnection) PongCount() int { log.WithField("name", pc.Name).Infof("pong count %d", pc.pongCount) return pc.pongCount } + +func (pc *PersistentConnection) IsConnectionReset() bool { + return pc.connectionReset +} diff --git a/felix/fv/containers/containers.go b/felix/fv/containers/containers.go index d7e745f8219..280376fdde3 100644 --- a/felix/fv/containers/containers.go +++ b/felix/fv/containers/containers.go @@ -24,13 +24,14 @@ import ( "os" "os/exec" "regexp" + "slices" "sort" "strconv" "strings" "sync" "time" - "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/v2" //nolint:staticcheck // Ignore ST1001: should not use dot imports . "github.com/onsi/gomega" @@ -40,6 +41,7 @@ import ( "github.com/projectcalico/calico/felix/fv/tcpdump" "github.com/projectcalico/calico/felix/fv/utils" "github.com/projectcalico/calico/libcalico-go/lib/set" + "github.com/projectcalico/calico/libcalico-go/lib/testutils/stacktrace" ) type Container struct { @@ -63,6 +65,7 @@ type Container struct { logFinished sync.WaitGroup dropAllLogs bool ignoreEmptyLines bool + logLimitBytes int } type watch struct { @@ -192,6 +195,7 @@ type RunOpts struct { SameNamespace *Container StopTimeoutSecs int StopSignal string + LogLimitBytes int } func NextContainerIndex() int { @@ -214,6 +218,7 @@ func RunWithFixedName(name string, opts RunOpts, args ...string) (c *Container) c = &Container{ Name: name, ignoreEmptyLines: opts.IgnoreEmptyLines, + logLimitBytes: opts.LogLimitBytes, } // Prep command to run the container. @@ -257,6 +262,7 @@ func RunWithFixedName(name string, opts RunOpts, args ...string) (c *Container) // Merge container's output into our own logging. c.logFinished.Add(2) + go c.copyOutputToLog("stdout", stdout, &c.logFinished, &c.stdoutWatches, nil) go c.copyOutputToLog("stderr", stderr, &c.logFinished, &c.stderrWatches, nil) @@ -337,10 +343,21 @@ func (c *Container) Start() { // is stopped. func (c *Container) Remove() { c.runCmd = utils.Command("docker", "rm", "-f", c.Name) + log.WithField("container", c).Info("Removing... container.") + // Do the deletion in the background so we don't hold things up. err := c.runCmd.Start() + cmd := c.runCmd Expect(err).NotTo(HaveOccurred()) - log.WithField("container", c).Info("Removed container.") + // Make sure we wait on the deletion process. + go func() { + err := cmd.Wait() + if err != nil { + log.WithError(err).Infof("Error from docker rm -f %s.", c.Name) + } else { + log.Infof("Container removed: %s.", c.Name) + } + }() } func (c *Container) copyOutputToLog(streamName string, stream io.Reader, done *sync.WaitGroup, watches *[]*watch, extraWriter io.Writer) { @@ -371,6 +388,7 @@ func (c *Container) copyOutputToLog(streamName string, stream io.Reader, done *s Expect(err).NotTo(HaveOccurred(), "Failed to write to data race log (close).") }() + bytesSeen := 0 for scanner.Scan() { line := scanner.Text() if extraWriter != nil { @@ -388,6 +406,19 @@ func (c *Container) copyOutputToLog(streamName string, stream io.Reader, done *s c.mutex.Lock() droppingLogs := c.dropAllLogs c.mutex.Unlock() + + // Or because we hit the limit. + wasDropping := c.logLimitBytes > 0 && bytesSeen > c.logLimitBytes + bytesSeen += len(line) + nowDropping := c.logLimitBytes > 0 && bytesSeen > c.logLimitBytes + if nowDropping { + if !wasDropping { + // We just hit the limit. + fmt.Fprintf(ginkgo.GinkgoWriter, "%v[%v] %v\n", c.Name, streamName, "...truncated...") + } + droppingLogs = true + } + if !droppingLogs { fmt.Fprintf(ginkgo.GinkgoWriter, "%v[%v] %v\n", c.Name, streamName, line) } @@ -395,7 +426,7 @@ func (c *Container) copyOutputToLog(streamName string, stream io.Reader, done *s // Capture data race warnings and log to file. if strings.Contains(line, "WARNING: DATA RACE") { _, err := fmt.Fprintf(dataRaceFile, "Detected data race (in %s) while running test: %s\n", - c.Name, ginkgo.CurrentGinkgoTestDescription().FullTestText) + c.Name, ginkgo.CurrentSpecReport().FullText()) Expect(err).NotTo(HaveOccurred(), "Failed to write to data race log.") foundDataRace = true } @@ -509,7 +540,7 @@ func (c *Container) GetPIDs(processName string) []int { return nil } var pids []int - for _, line := range strings.Split(out, "\n") { + for line := range strings.SplitSeq(out, "\n") { if line == "" { continue } @@ -534,7 +565,7 @@ func (c *Container) GetProcInfo(processName string) []ProcInfo { return nil } var pids []ProcInfo - for _, line := range strings.Split(out, "\n") { + for line := range strings.SplitSeq(out, "\n") { log.WithField("line", line).Debug("Parsing ps line") matches := psRegexp.FindStringSubmatch(line) if len(matches) == 0 { @@ -651,25 +682,37 @@ func (c *Container) FileExists(path string) bool { } func (c *Container) Exec(cmd ...string) { - log.WithField("container", c.Name).WithField("command", cmd).Info("Running command") + log.WithField("container", c.Name).WithFields(log.Fields{"command": cmd, "stack": miniStackTrace()}).Info("Exec: Running command") arg := []string{"exec", c.Name} arg = append(arg, cmd...) utils.Run("docker", arg...) } func (c *Container) ExecWithInput(input []byte, cmd ...string) { - log.WithField("container", c.Name).WithField("command", cmd).Info("Running command") + log.WithField("container", c.Name).WithFields(log.Fields{"command": cmd, "stack": miniStackTrace()}).Info("ExecWithInput: Running command") arg := []string{"exec", "-i", c.Name} arg = append(arg, cmd...) utils.RunWithInput(input, "docker", arg...) } func (c *Container) ExecMayFail(cmd ...string) error { + log.WithField("container", c.Name).WithFields(log.Fields{"command": cmd, "stack": miniStackTrace()}).Info("ExecMayFail: Running command") arg := []string{"exec", c.Name} arg = append(arg, cmd...) return utils.RunMayFail("docker", arg...) } +func (c *Container) ExecBestEffort(cmd ...string) { + err := c.ExecMayFail(cmd...) + if err != nil { + log.WithError(err).Errorf("Command (%s) failed, ignoring.", strings.Join(cmd, " ")) + } +} + +func miniStackTrace() string { + return stacktrace.MiniStackStrace("/containers/") +} + func (c *Container) ExecOutput(args ...string) (string, error) { arg := []string{"exec", c.Name} arg = append(arg, args...) @@ -759,7 +802,7 @@ func (c *Container) IPSetSizes() map[string]int { currentName := "" membersSeen := false log.WithField("ipsets", ipsetsOutput).Info("IP sets state") - for _, line := range strings.Split(ipsetsOutput, "\n") { + for line := range strings.SplitSeq(ipsetsOutput, "\n") { log.WithField("line", line).Debug("Parsing line") if strings.HasPrefix(line, "Name:") { currentName = strings.Split(line, " ")[1] @@ -809,19 +852,19 @@ func (c *Container) NumNFTSetMembers(ipVersion int, setName string) int { } type nftResp struct { - Nftables []map[string]interface{} `json:"nftables"` + Nftables []map[string]any `json:"nftables"` } var resp nftResp Expect(json.Unmarshal([]byte(out), &resp)).NotTo(HaveOccurred(), fmt.Sprintf("Failed to unmarshal JSON: %s", out)) for _, obj := range resp.Nftables { if obj["set"] != nil { - setObj, ok := obj["set"].(map[string]interface{}) + setObj, ok := obj["set"].(map[string]any) Expect(ok).To(BeTrue(), fmt.Sprintf("Failed to parse set: %v", obj)) if _, ok := setObj["elem"]; !ok { // No elements. return 0 } - elems, ok := setObj["elem"].([]interface{}) + elems, ok := setObj["elem"].([]any) Expect(ok).To(BeTrue(), fmt.Sprintf("Failed to parse elem: %v", setObj)) return len(elems) } @@ -854,7 +897,7 @@ func (c *Container) nftablesSetNamesForVersion(ver string) []string { out, err := c.ExecOutput("nft", "list", "sets", ver) Expect(err).NotTo(HaveOccurred(), out) var names []string - for _, line := range strings.Split(out, "\n") { + for line := range strings.SplitSeq(out, "\n") { line = strings.TrimSpace(line) if strings.HasPrefix(line, "set ") { // set { @@ -981,24 +1024,12 @@ func (c *Container) BPFNATDump(ipv6 bool) map[string][]string { // BPFNATHasBackendForService returns true is the given service has the given backend programmed in NAT tables func (c *Container) BPFNATHasBackendForService(svcIP string, svcPort, proto int, ip string, port int) bool { front := fmt.Sprintf("%s port %d proto %d", svcIP, svcPort, proto) - fmtStr := "%s:%d" - ipv6 := false - ipAddr := net.ParseIP(ip) - if ipAddr.To4() == nil { - fmtStr = "[%s]:%d" - ipv6 = true - } - back := fmt.Sprintf(fmtStr, ip, port) + back := net.JoinHostPort(ip, fmt.Sprint(port)) + ipv6 := net.ParseIP(ip).To4() == nil nat := c.BPFNATDump(ipv6) if natBack, ok := nat[front]; ok { - found := false - for _, b := range natBack { - if b == back { - found = true - break - } - } + found := slices.Contains(natBack, back) if !found { return false } diff --git a/felix/fv/debug_port_test.go b/felix/fv/debug_port_test.go index e518aa1307d..9500f8b982a 100644 --- a/felix/fv/debug_port_test.go +++ b/felix/fv/debug_port_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -21,7 +19,7 @@ import ( "net/http" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/fv/infrastructure" @@ -79,13 +77,5 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Debug port tests", []apicon Expect(get("debug/pprof/heap")).NotTo(HaveOccurred()) Expect(get("metrics")).To(HaveOccurred(), "Metrics on the debug port?") }) - - AfterEach(func() { - tc.Stop() - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) }) }) diff --git a/felix/fv/donottrack_test.go b/felix/fv/donottrack_test.go index 7a0db0236fa..215c37532c1 100644 --- a/felix/fv/donottrack_test.go +++ b/felix/fv/donottrack_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -22,7 +20,7 @@ import ( "strings" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -88,34 +86,11 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ do-not-track policy tests; // We will use this container to model an external client trying to connect into // workloads on a host. Create a route in the container for the workload CIDR. - externalClient = infrastructure.RunExtClient("ext-client") + externalClient = infrastructure.RunExtClient(infra, "ext-client") err = infra.AddDefaultDeny() Expect(err).To(BeNil()) }) - JustAfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - if NFTMode() { - logNFTDiags(felix) - } else { - felix.Exec("iptables-save", "-c") - felix.Exec("ip6tables-save", "-c") - } - felix.Exec("ip", "r") - felix.Exec("calico-bpf", "policy", "dump", "eth0", "all", "--asm") - felix.Exec("calico-bpf", "-6", "policy", "dump", "eth0", "all", "--asm") - felix.Exec("calico-bpf", "counters", "dump") - } - } - }) - - AfterEach(func() { - tc.Stop() - infra.Stop() - externalClient.Stop() - }) - expectFullConnectivity := func(opts ...ExpectationOption) { cc.ResetExpectations() cc.Expect(Some, tc.Felixes[0], hostW[1].Port(8055), opts...) @@ -328,27 +303,27 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ do-not-track policy tests; Expect(err).NotTo(HaveOccurred()) lines := strings.Split(strings.Trim(defaultRoute, "\n "), "\n") Expect(lines).To(HaveLen(1)) - defaultRouteArgs := strings.Split(strings.Replace(lines[0], "eth0", "bond0", -1), " ") + defaultRouteArgs := strings.Split(strings.ReplaceAll(lines[0], "eth0", "bond0"), " ") // Assuming the subnet route will be "proto kernel" and that will be the only such route. subnetRoute, err := felix.ExecOutput("ip", "route", "show", "proto", "kernel") Expect(err).NotTo(HaveOccurred()) lines = strings.Split(strings.Trim(subnetRoute, "\n "), "\n") Expect(lines).To(HaveLen(1), "expected only one proto kernel route, has docker's routing set-up changed?") - subnetArgs := strings.Split(strings.Replace(lines[0], "eth0", "bond0", -1), " ") + subnetArgs := strings.Split(strings.ReplaceAll(lines[0], "eth0", "bond0"), " ") - //Move IPv6 + // Move IPv6 defaultRoute6, err := felix.ExecOutput("ip", "-6", "route", "show", "default") Expect(err).NotTo(HaveOccurred()) lines = strings.Split(strings.Trim(defaultRoute6, "\n "), "\n") Expect(lines).To(HaveLen(1)) - defaultRoute6Args := strings.Split(strings.Replace(lines[0], "eth0", "bond0", -1), " ") + defaultRoute6Args := strings.Split(strings.ReplaceAll(lines[0], "eth0", "bond0"), " ") // Assuming the subnet route will be "proto kernel" and that will be the only such route. subnetRoute6, err := felix.ExecOutput("ip", "-6", "route", "show", "proto", "kernel") Expect(err).NotTo(HaveOccurred()) lines = strings.Split(strings.Trim(subnetRoute6, "\n "), "\n") - subnet6Args := strings.Split(strings.Replace(lines[0], "eth0", "bond0", -1), " ") + subnet6Args := strings.Split(strings.ReplaceAll(lines[0], "eth0", "bond0"), " ") felix.Exec("ip", "addr", "del", felix.IP, "dev", "eth0") ip6WithSubnet := felix.IPv6 + "/" + felix.GetIPv6Prefix() @@ -375,11 +350,11 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ do-not-track policy tests; for _, felix := range tc.Felixes { Eventually(func() bool { - return bpfCheckIfPolicyProgrammed(felix, "bond0", "egress", "default.allow-egress", "allow", false) + return bpfCheckIfGlobalNetworkPolicyProgrammed(felix, "bond0", "egress", "allow-egress", "allow", false) }, "5s", "200ms").Should(BeTrue()) Eventually(func() bool { - return bpfCheckIfPolicyProgrammedV6(felix, "bond0", "egress", "default.allow-egress", "allow", false) + return bpfCheckIfGlobalNetworkPolicyProgrammedV6(felix, "bond0", "egress", "allow-egress", "allow", false) }, "5s", "200ms").Should(BeTrue()) } @@ -391,13 +366,12 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ do-not-track policy tests; It("should implement untracked policy correctly", func() { testDonotTrackPolicy("bond0") }) - }) - }) func createHostEndpoint(f *infrastructure.Felix, iface string, - expectedIPs []string, client client.Interface, ctx context.Context) { + expectedIPs []string, client client.Interface, ctx context.Context, +) { hep := api.NewHostEndpoint() hep.Name = iface + "-" + f.Name hep.Labels = map[string]string{ diff --git a/felix/fv/dscp_test.go b/felix/fv/dscp_test.go index 34b982584e7..f9fed36ef06 100644 --- a/felix/fv/dscp_test.go +++ b/felix/fv/dscp_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -22,7 +20,7 @@ import ( "strings" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -33,7 +31,7 @@ import ( "github.com/projectcalico/calico/felix/fv/utils" "github.com/projectcalico/calico/felix/fv/workload" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - api "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/options" ) @@ -68,9 +66,11 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ dscp tests", []apiconfig.Da infra.AddDefaultAllow() // Create workload on host 1 (Felix0). + infrastructure.AssignIP("ep1-1", "10.65.0.0", tc.Felixes[0].Hostname, client) ep1_1 = workload.Run(tc.Felixes[0], "ep1-1", "default", "10.65.0.0", wepPortStr, "tcp") ep1_1.ConfigureInInfra(infra) + infrastructure.AssignIP("ep2-1", "10.65.0.1", tc.Felixes[0].Hostname, client) ep2_1 = workload.Run(tc.Felixes[0], "ep2-1", "default", "10.65.0.1", wepPortStr, "tcp") ep2_1.ConfigureInInfra(infra) @@ -78,15 +78,19 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ dscp tests", []apiconfig.Da hostw.ConfigureInInfra(infra) // Create workload on host 2 (Felix1) + infrastructure.AssignIP("ep1-2", "10.65.1.0", tc.Felixes[1].Hostname, client) + infrastructure.AssignIP("ep1-2", "dead:beef::1:0", tc.Felixes[1].Hostname, client) ep1_2Opts := workload.WithIPv6Address("dead:beef::1:0") ep1_2 = workload.Run(tc.Felixes[1], "ep1-2", "default", "10.65.1.0", wepPortStr, "tcp", ep1_2Opts) ep1_2.ConfigureInInfra(infra) + infrastructure.AssignIP("ep2-2", "10.65.1.1", tc.Felixes[1].Hostname, client) + infrastructure.AssignIP("ep2-2", "dead:beef::1:1", tc.Felixes[1].Hostname, client) ep2_2Opts := workload.WithIPv6Address("dead:beef::1:1") ep2_2 = workload.Run(tc.Felixes[1], "ep2-2", "default", "10.65.1.1", wepPortStr, "tcp", ep2_2Opts) ep2_2.ConfigureInInfra(infra) - cc = &connectivity.Checker{} + ensureRoutesProgrammed(tc.Felixes) if BPFMode() { ensureAllNodesBPFProgramsAttached(tc.Felixes) @@ -97,7 +101,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ dscp tests", []apiconfig.Da extClientOpts := infrastructure.ExtClientOpts{ Image: utils.Config.FelixImage, } - extClient = infrastructure.RunExtClientWithOpts("ext-client1", extClientOpts) + extClient = infrastructure.RunExtClientWithOpts(infra, "ext-client1", extClientOpts) extWorkload = &workload.Workload{ C: extClient, Name: "ext-workload", @@ -106,8 +110,10 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ dscp tests", []apiconfig.Da IP: extClient.IP, IP6: extClient.IPv6, } - err := extWorkload.Start() + err := extWorkload.Start(infra) Expect(err).NotTo(HaveOccurred()) + + cc = &connectivity.Checker{} }) AfterEach(func() { @@ -146,7 +152,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ dscp tests", []apiconfig.Da Skip("Skipping for BPF dataplane.") } - detecIptablesRule := func(felix *infrastructure.Felix, ipVersion uint8) { + detectIptablesRule := func(felix *infrastructure.Felix, ipVersion uint8) { binary := "iptables-save" if ipVersion == 6 { binary = "ip6tables-save" @@ -186,8 +192,8 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ dscp tests", []apiconfig.Da detectNftablesRule(tc.Felixes[0], 4) detectNftablesRule(tc.Felixes[0], 6) } else { - detecIptablesRule(tc.Felixes[0], 4) - detecIptablesRule(tc.Felixes[0], 6) + detectIptablesRule(tc.Felixes[0], 4) + detectIptablesRule(tc.Felixes[0], 6) } }) @@ -251,12 +257,12 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ dscp tests", []apiconfig.Da Expect(err).NotTo(HaveOccurred()) By("setting the initial DSCP values") - ep1_1.WorkloadEndpoint.Spec.QoSControls = &api.QoSControls{ + ep1_1.WorkloadEndpoint.Spec.QoSControls = &internalapi.QoSControls{ DSCP: &dscp20, } ep1_1.UpdateInInfra(infra) - ep2_2.WorkloadEndpoint.Spec.QoSControls = &api.QoSControls{ + ep2_2.WorkloadEndpoint.Spec.QoSControls = &internalapi.QoSControls{ DSCP: &dscp40, } ep2_2.UpdateInInfra(infra) @@ -276,12 +282,12 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ dscp tests", []apiconfig.Da cc.CheckConnectivity() By("updating DSCP values on some workloads") - ep2_1.WorkloadEndpoint.Spec.QoSControls = &api.QoSControls{ + ep2_1.WorkloadEndpoint.Spec.QoSControls = &internalapi.QoSControls{ DSCP: &dscp0, } ep2_1.UpdateInInfra(infra) - ep1_2.WorkloadEndpoint.Spec.QoSControls = &api.QoSControls{ + ep1_2.WorkloadEndpoint.Spec.QoSControls = &internalapi.QoSControls{ DSCP: &dscp40, } ep1_2.UpdateInInfra(infra) @@ -301,12 +307,12 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ dscp tests", []apiconfig.Da cc.CheckConnectivity() By("updating DSCP values on other workloads") - ep1_1.WorkloadEndpoint.Spec.QoSControls = &api.QoSControls{ + ep1_1.WorkloadEndpoint.Spec.QoSControls = &internalapi.QoSControls{ DSCP: &dscp32, } ep1_1.UpdateInInfra(infra) - ep1_2.WorkloadEndpoint.Spec.QoSControls = &api.QoSControls{ + ep1_2.WorkloadEndpoint.Spec.QoSControls = &internalapi.QoSControls{ DSCP: &dscp20, } ep1_2.UpdateInInfra(infra) @@ -326,12 +332,12 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ dscp tests", []apiconfig.Da cc.CheckConnectivity() By("reverting the DSCP values") - ep1_1.WorkloadEndpoint.Spec.QoSControls = &api.QoSControls{ + ep1_1.WorkloadEndpoint.Spec.QoSControls = &internalapi.QoSControls{ DSCP: &dscp20, } ep1_1.UpdateInInfra(infra) - ep1_2.WorkloadEndpoint.Spec.QoSControls = &api.QoSControls{ + ep1_2.WorkloadEndpoint.Spec.QoSControls = &internalapi.QoSControls{ DSCP: &dscp40, } ep1_2.UpdateInInfra(infra) @@ -379,10 +385,10 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ dscp tests", []apiconfig.Da cc.CheckConnectivity() By("resetting DSCP value on some workloads") - ep2_1.WorkloadEndpoint.Spec.QoSControls = &api.QoSControls{} + ep2_1.WorkloadEndpoint.Spec.QoSControls = &internalapi.QoSControls{} ep2_1.UpdateInInfra(infra) - ep1_2.WorkloadEndpoint.Spec.QoSControls = &api.QoSControls{} + ep1_2.WorkloadEndpoint.Spec.QoSControls = &internalapi.QoSControls{} ep1_2.UpdateInInfra(infra) if !BPFMode() { @@ -429,7 +435,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ dscp tests", []apiconfig.Da for dscpStr, dscpVal := range numorstring.AllDSCPValues { // Use a workload on host2 since it's configured as dual stack. dscp := numorstring.DSCPFromString(dscpStr) - ep1_2.WorkloadEndpoint.Spec.QoSControls = &api.QoSControls{ + ep1_2.WorkloadEndpoint.Spec.QoSControls = &internalapi.QoSControls{ DSCP: &dscp, } ep1_2.UpdateInInfra(infra) @@ -475,12 +481,12 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ dscp tests", []apiconfig.Da verifyQoSPolicies(tc.Felixes[1], nil, nil) } - ep1_1.WorkloadEndpoint.Spec.QoSControls = &api.QoSControls{ + ep1_1.WorkloadEndpoint.Spec.QoSControls = &internalapi.QoSControls{ DSCP: &dscpAF11, } ep1_1.UpdateInInfra(infra) - ep2_2.WorkloadEndpoint.Spec.QoSControls = &api.QoSControls{ + ep2_2.WorkloadEndpoint.Spec.QoSControls = &internalapi.QoSControls{ DSCP: &dscpEF, } ep2_2.UpdateInInfra(infra) diff --git a/felix/fv/ep_to_host_action_test.go b/felix/fv/ep_to_host_action_test.go index 77e035af73a..eb8150ff386 100644 --- a/felix/fv/ep_to_host_action_test.go +++ b/felix/fv/ep_to_host_action_test.go @@ -12,16 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( "context" "fmt" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -61,6 +58,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ endpoint-to-host-action tes for ii := range w { wIP := fmt.Sprintf("10.65.%d.2", ii) wName := fmt.Sprintf("w%d", ii) + infrastructure.AssignIP(wName, wIP, tc.Felixes[ii].Hostname, client) w[ii] = workload.Run(tc.Felixes[ii], wName, "default", wIP, "8055", "tcp") w[ii].ConfigureInInfra(infra) @@ -70,39 +68,15 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ endpoint-to-host-action tes cc = &connectivity.Checker{} }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - felix.Exec("iptables-save", "-c") - felix.Exec("ipset", "list") - felix.Exec("ip", "r") - felix.Exec("ip", "a") - } - } - - for _, wl := range w { - wl.Stop() - } - for _, wl := range hostW { - wl.Stop() - } - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) - - entry := func(chainPolicy string, epToHostPol string, hep string, hepPolicy api.Action, expectedConn connectivity.Expected) table.TableEntry { - return table.Entry( + entry := func(chainPolicy string, epToHostPol string, hep string, hepPolicy api.Action, expectedConn connectivity.Expected) TableEntry { + return Entry( fmt.Sprintf("INPUT=%s, ep-to-host=%s, HEP=%s, HEP-pol=%s, expected connectivity=%v", chainPolicy, epToHostPol, hep, hepPolicy, expectedConn), chainPolicy, epToHostPol, hep, hepPolicy, expectedConn, ) } - table.DescribeTable("endpoint to host tests", + DescribeTable("endpoint to host tests", func(chainPolicy string, epToHostPol string, hepIface string, hepPolicy api.Action, expectedConn connectivity.Expected) { // Set the chain policy. for _, f := range tc.Felixes { diff --git a/felix/fv/etcd_restart_test.go b/felix/fv/etcd_restart_test.go index 94eccf0a248..5e83c8a7241 100644 --- a/felix/fv/etcd_restart_test.go +++ b/felix/fv/etcd_restart_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -24,7 +22,7 @@ import ( "syscall" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/sirupsen/logrus" @@ -36,11 +34,12 @@ import ( "github.com/projectcalico/calico/felix/fv/metrics" "github.com/projectcalico/calico/felix/fv/utils" "github.com/projectcalico/calico/felix/fv/workload" + "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/netlinkutils" ) -var _ = Context("etcd connection interruption", func() { +var _ = infrastructure.DatastoreDescribe("etcd connection interruption", []apiconfig.DatastoreType{apiconfig.EtcdV3}, func(getInfra infrastructure.InfraFactory) { var ( etcd *containers.Container tc infrastructure.TopologyContainers @@ -54,7 +53,9 @@ var _ = Context("etcd connection interruption", func() { if NFTMode() { Skip("Test is dataplane independent, skip for nftables") } - tc, etcd, client, infra = infrastructure.StartNNodeEtcdTopology(2, infrastructure.DefaultTopologyOptions()) + infra = getInfra() + tc, client = infrastructure.StartNNodeTopology(2, infrastructure.DefaultTopologyOptions(), infra) + etcd = infra.(*infrastructure.EtcdDatastoreInfra).EtcdContainer infrastructure.CreateDefaultProfile(client, "default", map[string]string{"default": ""}, "") // Wait until the tunl0 device appears; it is created when felix inserts the ipip module // into the kernel. @@ -80,6 +81,7 @@ var _ = Context("etcd connection interruption", func() { for ii := range w { wIP := fmt.Sprintf("10.65.%d.2", ii) wName := fmt.Sprintf("w%d", ii) + infrastructure.AssignIP(wName, wIP, tc.Felixes[ii].Hostname, client) w[ii] = workload.Run(tc.Felixes[ii], wName, "default", wIP, "8055", "tcp") w[ii].Configure(client) } @@ -87,27 +89,6 @@ var _ = Context("etcd connection interruption", func() { cc = &connectivity.Checker{} }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - felix.Exec("iptables-save", "-c") - felix.Exec("ipset", "list") - felix.Exec("ip", "r") - } - } - - for _, wl := range w { - wl.Stop() - } - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - etcd.Exec("etcdctl", "get", "/", "--prefix", "--keys-only") - } - etcd.Stop() - infra.Stop() - }) - It("shouldn't use excessive CPU when etcd is stopped", func() { By("having initial workload to workload connectivity", func() { cc.ExpectSome(w[0], w[1]) @@ -148,7 +129,7 @@ var _ = Context("etcd connection interruption", func() { Expect(err).NotTo(HaveOccurred()) logrus.WithField("output", out).WithError(err).Info("Conntrack entries") found := false - for _, line := range strings.Split(out, "\n") { + for line := range strings.SplitSeq(out, "\n") { matches := portRegexp.FindStringSubmatch(line) if len(matches) < 2 { continue diff --git a/felix/fv/ethtool_test.go b/felix/fv/ethtool_test.go index eb9c0108e21..1f69b599a18 100644 --- a/felix/fv/ethtool_test.go +++ b/felix/fv/ethtool_test.go @@ -12,15 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( "context" "os" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -56,11 +54,6 @@ var _ = infrastructure.DatastoreDescribe( tc, calicoClient = infrastructure.StartNNodeTopology(1, options, infra) }) - AfterEach(func() { - tc.Stop() - infra.Stop() - }) - Context("With Felix configuration set GRO disabled on eth0", func() { It("should detected by the ethtool in Felix to assert update made successfully ", func() { diff --git a/felix/fv/flow_logs_goldmane_staged_test.go b/felix/fv/flow_logs_goldmane_staged_test.go index 3ef12bf9d48..fe6e71a2081 100644 --- a/felix/fv/flow_logs_goldmane_staged_test.go +++ b/felix/fv/flow_logs_goldmane_staged_test.go @@ -1,6 +1,3 @@ -//go:build fvtests -// +build fvtests - // Copyright (c) 2018-2025 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,7 +22,7 @@ import ( "strings" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -114,28 +111,36 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag infra.AddDefaultAllow() // Create workload on host 1. + infrastructure.AssignIP("ep1-1", "10.65.0.0", tc.Felixes[0].Hostname, client) ep1_1 = workload.Run(tc.Felixes[0], "ep1-1", "default", "10.65.0.0", wepPortStr, "tcp") ep1_1.ConfigureInInfra(infra) + infrastructure.AssignIP("ep2-1", "10.65.1.0", tc.Felixes[1].Hostname, client) ep2_1 = workload.Run(tc.Felixes[1], "ep2-1", "default", "10.65.1.0", wepPortStr, "tcp") ep2_1.ConfigureInInfra(infra) + infrastructure.AssignIP("ep2-2", "10.65.1.1", tc.Felixes[1].Hostname, client) ep2_2 = workload.Run(tc.Felixes[1], "ep2-2", "default", "10.65.1.1", wepPortStr, "tcp") ep2_2.ConfigureInInfra(infra) + infrastructure.AssignIP("ep2-3", "10.65.1.2", tc.Felixes[1].Hostname, client) ep2_3 = workload.Run(tc.Felixes[1], "ep2-3", "default", "10.65.1.2", wepPortStr, "tcp") ep2_3.ConfigureInInfra(infra) + ensureRoutesProgrammed(tc.Felixes) + // Create tiers tier1 and tier2 tier := api.NewTier() tier.Name = "tier1" tier.Spec.Order = &float1_0 _, err := client.Tiers().Create(utils.Ctx, tier, utils.NoOptions) + Expect(err).NotTo(HaveOccurred()) tier = api.NewTier() tier.Name = "tier2" tier.Spec.Order = &float2_0 _, err = client.Tiers().Create(utils.Ctx, tier, utils.NoOptions) + Expect(err).NotTo(HaveOccurred()) // Allow all traffic to/from ep1-1 gnp := api.NewGlobalNetworkPolicy() @@ -229,7 +234,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag Expect(err).NotTo(HaveOccurred()) // (s)knp3.1->sknp3.9 egress: (N2-1) - for i := 0; i < 9; i++ { + for i := range 9 { sknp := api.NewStagedKubernetesNetworkPolicy() sknp.Name = fmt.Sprintf("knp3-%d", i+1) sknp.Namespace = "default" @@ -284,7 +289,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag svcName := "test-service" k8sClient := infra.(*infrastructure.K8sDatastoreInfra).K8sClient tSvc := k8sService(svcName, clusterIP, ep2_1, svcPort, wepPort, 0, "tcp") - tSvcNamespace := tSvc.ObjectMeta.Namespace + tSvcNamespace := tSvc.Namespace _, err = k8sClient.CoreV1().Services(tSvcNamespace).Create(context.Background(), tSvc, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -292,23 +297,27 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag Expect(ep2_1.IP).NotTo(Equal("")) getEpsFunc := k8sGetEpsForServiceFunc(k8sClient, tSvc) epCorrectFn := func() error { - eps := getEpsFunc() + epslices := getEpsFunc() + if len(epslices) != 1 { + return fmt.Errorf("Wrong number of endpoints: %#v", epslices) + } + eps := epslices[0].Endpoints if len(eps) != 1 { - return fmt.Errorf("Wrong number of endpoints: %#v", eps) + return fmt.Errorf("Wrong number of endpoint addresses: %#v", epslices[0]) } addrs := eps[0].Addresses if len(addrs) != 1 { return fmt.Errorf("Wrong number of addresses: %#v", eps[0]) } - if addrs[0].IP != ep2_1.IP { - return fmt.Errorf("Unexpected IP: %s != %s", addrs[0].IP, ep2_1.IP) + if addrs[0] != ep2_1.IP { + return fmt.Errorf("Unexpected IP: %s != %s", addrs[0], ep2_1.IP) } - ports := eps[0].Ports + ports := epslices[0].Ports if len(ports) != 1 { return fmt.Errorf("Wrong number of ports: %#v", eps[0]) } - if ports[0].Port != int32(wepPort) { - return fmt.Errorf("Wrong port %d != svcPort", ports[0].Port) + if *ports[0].Port != int32(wepPort) { + return fmt.Errorf("Wrong port %d != svcPort", *ports[0].Port) } return nil } @@ -316,8 +325,8 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag if !bpfEnabled { // Wait for felix to see and program some expected nflog entries, and for the cluster IP to appear. - Eventually(getRuleFunc(tc.Felixes[0], "APE0|default.ep1-1-allow-all"), "10s", "1s").ShouldNot(HaveOccurred()) - Eventually(getRuleFunc(tc.Felixes[1], "APE0|default.ep1-1-allow-all"), "10s", "1s").Should(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[0], "APE0|gnp/default.ep1-1-allow-all"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[1], "APE0|gnp/default.ep1-1-allow-all"), "10s", "1s").Should(HaveOccurred()) // When policies are programmed, make sure no staged policy is programmed. Staged policies must be skipped. Consistently(getRuleFunc(tc.Felixes[0], "staged"), "5s", "1s").Should(HaveOccurred()) Consistently(getRuleFunc(tc.Felixes[1], "staged"), "5s", "1s").Should(HaveOccurred()) @@ -333,10 +342,9 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag Eventually(checkNat, "10s", "1s").Should(BeTrue(), "Expected NAT to be programmed") - bpfWaitForPolicy(tc.Felixes[0], ep1_1.InterfaceName, - "ingress", "default.ep1-1-allow-all") - bpfWaitForPolicy(tc.Felixes[1], ep2_2.InterfaceName, - "ingress", "default/tier1.np1-1") + bpfWaitForGlobalNetworkPolicy(tc.Felixes[0], ep1_1.InterfaceName, "ingress", "default.ep1-1-allow-all") + bpfWaitForNetworkPolicy(tc.Felixes[1], ep2_2.InterfaceName, "ingress", "default", "tier1.np1-1") + // When policies are programmed, make sure no staged policy is programmed. Staged policies must be skipped. Consistently(bpfDumpPolicy(tc.Felixes[0], ep1_1.InterfaceName, "ingress"), "5s", "1s").ShouldNot(ContainSubstring("staged")) Consistently(bpfDumpPolicy(tc.Felixes[0], ep1_1.InterfaceName, "egress"), "5s", "1s").ShouldNot(ContainSubstring("staged")) @@ -453,10 +461,10 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag Reporter: "src", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|default|default.ep1-1-allow-all|allow|0": {}, + "0|default|gnp:default.ep1-1-allow-all|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|default|default.ep1-1-allow-all|allow|0": {}, + "0|default|gnp:default.ep1-1-allow-all|allow|0": {}, }, }, ) @@ -473,10 +481,10 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag Reporter: "src", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|default|default.ep1-1-allow-all|allow|0": {}, + "0|default|gnp:default.ep1-1-allow-all|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|default|default.ep1-1-allow-all|allow|0": {}, + "0|default|gnp:default.ep1-1-allow-all|allow|0": {}, }, }, ) @@ -493,10 +501,10 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag Reporter: "src", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|default|default.ep1-1-allow-all|allow|0": {}, + "0|default|gnp:default.ep1-1-allow-all|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|default|default.ep1-1-allow-all|allow|0": {}, + "0|default|gnp:default.ep1-1-allow-all|allow|0": {}, }, }, ) @@ -513,10 +521,10 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag Reporter: "dst", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|default|default.ep1-1-allow-all|allow|0": {}, + "0|default|gnp:default.ep1-1-allow-all|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|default|default.ep1-1-allow-all|allow|0": {}, + "0|default|gnp:default.ep1-1-allow-all|allow|0": {}, }, }, ) @@ -542,10 +550,10 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag Reporter: "dst", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier2|default/tier2.np2-4|deny|0": {}, + "0|tier2|np:default/tier2.np2-4|deny|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier2|default/tier2.staged:np2-3|deny|1": {}, + "0|tier2|snp:default/tier2.np2-3|deny|1": {}, }, }, ) @@ -562,10 +570,10 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag Reporter: "src", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier2|tier2.gnp2-2|deny|0": {}, + "0|tier2|gnp:tier2.gnp2-2|deny|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier2|tier2.gnp2-2|deny|0": {}, + "0|tier2|gnp:tier2.gnp2-2|deny|0": {}, }, }, ) @@ -582,10 +590,10 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag Reporter: "src", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|deny|1": {}, + "0|tier1|np:default/tier1.np1-1|deny|1": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|deny|1": {}, + "0|tier1|np:default/tier1.np1-1|deny|1": {}, }, }, ) @@ -602,12 +610,12 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag Reporter: "dst", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|1": {}, - "1|default|default/default.np3-3|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|1": {}, + "1|default|np:default/default.np3-3|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|1": {}, - "1|tier2|default/tier2.staged:np2-3|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|1": {}, + "1|tier2|snp:default/tier2.np2-3|allow|0": {}, }, }, ) @@ -624,10 +632,10 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag Reporter: "dst", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|allow|0": {}, }, }, ) @@ -644,12 +652,12 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag Reporter: "src", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|__PROFILE__|pro:kns.default|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|tier2|default/tier2.staged:np2-1|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|tier2|snp:default/tier2.np2-1|allow|0": {}, }, }, ) @@ -661,38 +669,6 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag return nil }, "30s", "3s").ShouldNot(HaveOccurred()) }) - - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - logNFTDiags(felix) - felix.Exec("iptables-save", "-c") - felix.Exec("ipset", "list") - felix.Exec("ip", "r") - felix.Exec("ip", "a") - } - if bpfEnabled { - tc.Felixes[0].Exec("calico-bpf", "policy", "dump", ep1_1.InterfaceName, "all", "--asm") - tc.Felixes[1].Exec("calico-bpf", "policy", "dump", ep2_2.InterfaceName, "all", "--asm") - } - } - - ep1_1.Stop() - ep2_1.Stop() - ep2_2.Stop() - ep2_3.Stop() - for _, felix := range tc.Felixes { - if bpfEnabled { - felix.Exec("calico-bpf", "connect-time", "clean") - } - felix.Stop() - } - - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) }) // Felix1 Felix2 @@ -757,9 +733,11 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ aggregation of flow log wit infra.AddDefaultAllow() // Create workload on host 1. + infrastructure.AssignIP("ep1-1", "10.65.0.0", tc.Felixes[0].Hostname, client) ep1_1 = workload.Run(tc.Felixes[0], "ep1-1", "default", "10.65.0.0", wepPortStr, "tcp") ep1_1.ConfigureInInfra(infra) + infrastructure.AssignIP("ep2-1", "10.65.1.0", tc.Felixes[1].Hostname, client) ep2_1 = workload.Run(tc.Felixes[1], "ep2-1", "default", "10.65.1.0", wepPortStr, "tcp") ep2_1.ConfigureInInfra(infra) @@ -768,11 +746,13 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ aggregation of flow log wit tier.Name = "tier1" tier.Spec.Order = &float1_0 _, err := client.Tiers().Create(utils.Ctx, tier, utils.NoOptions) + Expect(err).NotTo(HaveOccurred()) tier = api.NewTier() tier.Name = "tier2" tier.Spec.Order = &float2_0 _, err = client.Tiers().Create(utils.Ctx, tier, utils.NoOptions) + Expect(err).NotTo(HaveOccurred()) // np1-1 egress/ingress pass np := api.NewNetworkPolicy() @@ -803,22 +783,18 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ aggregation of flow log wit Expect(err).NotTo(HaveOccurred()) if !bpfEnabled { - Eventually(getRuleFunc(tc.Felixes[0], "PPI0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) - Eventually(getRuleFunc(tc.Felixes[0], "PPE0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) - Eventually(getRuleFunc(tc.Felixes[1], "PPI0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) - Eventually(getRuleFunc(tc.Felixes[1], "PPE0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[0], "PPI0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[0], "PPE0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[1], "PPI0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[1], "PPE0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) // When policies are programmed, make sure no staged policy is programmed. Staged policies must be skipped. Consistently(getRuleFunc(tc.Felixes[0], "staged"), "5s", "1s").Should(HaveOccurred()) Consistently(getRuleFunc(tc.Felixes[1], "staged"), "5s", "1s").Should(HaveOccurred()) } else { - bpfWaitForPolicy(tc.Felixes[0], ep1_1.InterfaceName, - "egress", "default/tier1.np1-1") - bpfWaitForPolicy(tc.Felixes[0], ep1_1.InterfaceName, - "ingress", "default/tier1.np1-1") - bpfWaitForPolicy(tc.Felixes[1], ep2_1.InterfaceName, - "egress", "default/tier1.np1-1") - bpfWaitForPolicy(tc.Felixes[1], ep2_1.InterfaceName, - "ingress", "default/tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[0], ep1_1.InterfaceName, "egress", "default", "tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[0], ep1_1.InterfaceName, "ingress", "default", "tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[1], ep2_1.InterfaceName, "egress", "default", "tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[1], ep2_1.InterfaceName, "ingress", "default", "tier1.np1-1") // When policies are programmed, make sure no staged policy is programmed. Staged policies must be skipped. Consistently(bpfDumpPolicy(tc.Felixes[0], ep1_1.InterfaceName, "ingress"), "5s", "1s").ShouldNot(ContainSubstring("staged")) Consistently(bpfDumpPolicy(tc.Felixes[0], ep1_1.InterfaceName, "egress"), "5s", "1s").ShouldNot(ContainSubstring("staged")) @@ -840,22 +816,18 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ aggregation of flow log wit if !bpfEnabled { // Wait for felix to see and program some expected nflog entries, and for the cluster IP to appear. - Eventually(getRuleFunc(tc.Felixes[0], "PPI0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) - Eventually(getRuleFunc(tc.Felixes[0], "PPE0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) - Eventually(getRuleFunc(tc.Felixes[1], "PPI0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) - Eventually(getRuleFunc(tc.Felixes[1], "PPE0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[0], "PPI0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[0], "PPE0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[1], "PPI0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[1], "PPE0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) // When policies are programmed, make sure no staged policy is programmed. Staged policies must be skipped. Consistently(getRuleFunc(tc.Felixes[0], "staged"), "5s", "1s").Should(HaveOccurred()) Consistently(getRuleFunc(tc.Felixes[1], "staged"), "5s", "1s").Should(HaveOccurred()) } else { - bpfWaitForPolicy(tc.Felixes[0], ep1_1.InterfaceName, - "egress", "default/tier1.np1-1") - bpfWaitForPolicy(tc.Felixes[0], ep1_1.InterfaceName, - "ingress", "default/tier1.np1-1") - bpfWaitForPolicy(tc.Felixes[1], ep2_1.InterfaceName, - "egress", "default/tier1.np1-1") - bpfWaitForPolicy(tc.Felixes[1], ep2_1.InterfaceName, - "ingress", "default/tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[0], ep1_1.InterfaceName, "egress", "default", "tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[0], ep1_1.InterfaceName, "ingress", "default", "tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[1], ep2_1.InterfaceName, "egress", "default", "tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[1], ep2_1.InterfaceName, "ingress", "default", "tier1.np1-1") // When policies are programmed, make sure no staged policy is programmed. Staged policies must be skipped. Consistently(bpfDumpPolicy(tc.Felixes[0], ep1_1.InterfaceName, "ingress"), "5s", "1s").ShouldNot(ContainSubstring("staged")) Consistently(bpfDumpPolicy(tc.Felixes[0], ep1_1.InterfaceName, "egress"), "5s", "1s").ShouldNot(ContainSubstring("staged")) @@ -874,22 +846,18 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ aggregation of flow log wit if !bpfEnabled { // Wait for felix to see and program some expected nflog entries, and for the cluster IP to appear. - Eventually(getRuleFunc(tc.Felixes[0], "PPI0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) - Eventually(getRuleFunc(tc.Felixes[0], "PPE0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) - Eventually(getRuleFunc(tc.Felixes[1], "PPI0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) - Eventually(getRuleFunc(tc.Felixes[1], "PPE0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[0], "PPI0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[0], "PPE0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[1], "PPI0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[1], "PPE0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) // When policies are programmed, make sure no staged policy is programmed. Staged policies must be skipped. Consistently(getRuleFunc(tc.Felixes[0], "staged"), "5s", "1s").Should(HaveOccurred()) Consistently(getRuleFunc(tc.Felixes[1], "staged"), "5s", "1s").Should(HaveOccurred()) } else { - bpfWaitForPolicy(tc.Felixes[0], ep1_1.InterfaceName, - "egress", "default/tier1.np1-1") - bpfWaitForPolicy(tc.Felixes[0], ep1_1.InterfaceName, - "ingress", "default/tier1.np1-1") - bpfWaitForPolicy(tc.Felixes[1], ep2_1.InterfaceName, - "egress", "default/tier1.np1-1") - bpfWaitForPolicy(tc.Felixes[1], ep2_1.InterfaceName, - "ingress", "default/tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[0], ep1_1.InterfaceName, "egress", "default", "tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[0], ep1_1.InterfaceName, "ingress", "default", "tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[1], ep2_1.InterfaceName, "egress", "default", "tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[1], ep2_1.InterfaceName, "ingress", "default", "tier1.np1-1") // When policies are programmed, make sure no staged policy is programmed. Staged policies must be skipped. Consistently(bpfDumpPolicy(tc.Felixes[0], ep1_1.InterfaceName, "ingress"), "5s", "1s").ShouldNot(ContainSubstring("staged")) Consistently(bpfDumpPolicy(tc.Felixes[0], ep1_1.InterfaceName, "egress"), "5s", "1s").ShouldNot(ContainSubstring("staged")) @@ -910,22 +878,19 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ aggregation of flow log wit if !bpfEnabled { // Wait for felix to see and program some expected nflog entries, and for the cluster IP to appear. - Eventually(getRuleFunc(tc.Felixes[0], "PPI0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) - Eventually(getRuleFunc(tc.Felixes[0], "PPE0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) - Eventually(getRuleFunc(tc.Felixes[1], "PPI0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) - Eventually(getRuleFunc(tc.Felixes[1], "PPE0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[0], "PPI0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[0], "PPE0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[1], "PPI0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[1], "PPE0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) // When policies are programmed, make sure no staged policy is programmed. Staged policies must be skipped. Consistently(getRuleFunc(tc.Felixes[0], "staged"), "5s", "1s").Should(HaveOccurred()) Consistently(getRuleFunc(tc.Felixes[1], "staged"), "5s", "1s").Should(HaveOccurred()) } else { - bpfWaitForPolicy(tc.Felixes[0], ep1_1.InterfaceName, - "egress", "default/tier1.np1-1") - bpfWaitForPolicy(tc.Felixes[0], ep1_1.InterfaceName, - "ingress", "default/tier1.np1-1") - bpfWaitForPolicy(tc.Felixes[1], ep2_1.InterfaceName, - "egress", "default/tier1.np1-1") - bpfWaitForPolicy(tc.Felixes[1], ep2_1.InterfaceName, - "ingress", "default/tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[0], ep1_1.InterfaceName, "egress", "default", "tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[0], ep1_1.InterfaceName, "ingress", "default", "tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[1], ep2_1.InterfaceName, "egress", "default", "tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[1], ep2_1.InterfaceName, "ingress", "default", "tier1.np1-1") + // When policies are programmed, make sure no staged policy is programmed. Staged policies must be skipped. Consistently(bpfDumpPolicy(tc.Felixes[0], ep1_1.InterfaceName, "ingress"), "5s", "1s").ShouldNot(ContainSubstring("staged")) Consistently(bpfDumpPolicy(tc.Felixes[0], ep1_1.InterfaceName, "egress"), "5s", "1s").ShouldNot(ContainSubstring("staged")) @@ -1003,8 +968,8 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ aggregation of flow log wit Reporter: "src", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|__PROFILE__|pro:kns.default|allow|0": {}, }, }, ) @@ -1029,8 +994,8 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ aggregation of flow log wit Reporter: "dst", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|__PROFILE__|pro:kns.default|allow|0": {}, }, }, ) @@ -1117,12 +1082,12 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ aggregation of flow log wit Reporter: "src", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|__PROFILE__|pro:kns.default|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|tier2|default/tier2.staged:np2-1|deny|-1": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|tier2|snp:default/tier2.np2-1|deny|-1": {}, }, }, ) @@ -1137,13 +1102,13 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ aggregation of flow log wit Reporter: "src", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|__PROFILE__|pro:kns.default|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|tier2|default/tier2.staged:np2-1|pass|-1": {}, - "2|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|tier2|snp:default/tier2.np2-1|pass|-1": {}, + "2|__PROFILE__|pro:kns.default|allow|0": {}, }, }, ) @@ -1168,12 +1133,12 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ aggregation of flow log wit Reporter: "dst", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|__PROFILE__|pro:kns.default|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|tier2|default/tier2.staged:np2-1|deny|-1": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|tier2|snp:default/tier2.np2-1|deny|-1": {}, }, }, ) @@ -1188,13 +1153,13 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ aggregation of flow log wit Reporter: "dst", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|__PROFILE__|pro:kns.default|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|tier2|default/tier2.staged:np2-1|pass|-1": {}, - "2|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|tier2|snp:default/tier2.np2-1|pass|-1": {}, + "2|__PROFILE__|pro:kns.default|allow|0": {}, }, }, ) @@ -1280,8 +1245,8 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ aggregation of flow log wit Reporter: "src", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|__PROFILE__|pro:kns.default|allow|0": {}, }, }, ) @@ -1306,8 +1271,8 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ aggregation of flow log wit Reporter: "dst", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|__PROFILE__|pro:kns.default|allow|0": {}, }, }, ) @@ -1319,36 +1284,6 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ aggregation of flow log wit return nil }, "30s", "3s").ShouldNot(HaveOccurred()) }) - - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - logNFTDiags(felix) - felix.Exec("iptables-save", "-c") - felix.Exec("ipset", "list") - felix.Exec("ip", "r") - felix.Exec("ip", "a") - } - if bpfEnabled { - tc.Felixes[0].Exec("calico-bpf", "policy", "dump", ep1_1.InterfaceName, "all", "--asm") - tc.Felixes[1].Exec("calico-bpf", "policy", "dump", ep2_1.InterfaceName, "all", "--asm") - } - } - - ep1_1.Stop() - ep2_1.Stop() - for _, felix := range tc.Felixes { - if bpfEnabled { - felix.Exec("calico-bpf", "connect-time", "clean") - } - felix.Stop() - } - - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) }) // Felix1 Felix2 @@ -1409,9 +1344,11 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag infra.AddDefaultAllow() // Create workload on host 1. + infrastructure.AssignIP("ep1-1", "10.65.0.0", tc.Felixes[0].Hostname, client) ep1_1 = workload.Run(tc.Felixes[0], "ep1-1", "default", "10.65.0.0", wepPortStr, "tcp") ep1_1.ConfigureInInfra(infra) + infrastructure.AssignIP("ep2-1", "10.65.1.0", tc.Felixes[1].Hostname, client) ep2_1 = workload.Run(tc.Felixes[1], "ep2-1", "default", "10.65.1.0", wepPortStr, "tcp") ep2_1.ConfigureInInfra(infra) @@ -1420,11 +1357,13 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag tier.Name = "tier1" tier.Spec.Order = &float1_0 _, err := client.Tiers().Create(utils.Ctx, tier, utils.NoOptions) + Expect(err).NotTo(HaveOccurred()) tier = api.NewTier() tier.Name = "tier2" tier.Spec.Order = &float2_0 _, err = client.Tiers().Create(utils.Ctx, tier, utils.NoOptions) + Expect(err).NotTo(HaveOccurred()) // np1-1 egress/ingress pass np := api.NewNetworkPolicy() @@ -1455,22 +1394,19 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag Expect(err).NotTo(HaveOccurred()) if !bpfEnabled { - Eventually(getRuleFunc(tc.Felixes[0], "PPI0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) - Eventually(getRuleFunc(tc.Felixes[0], "PPE0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) - Eventually(getRuleFunc(tc.Felixes[1], "PPI0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) - Eventually(getRuleFunc(tc.Felixes[1], "PPE0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[0], "PPI0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[0], "PPE0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[1], "PPI0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[1], "PPE0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) // When policies are programmed, make sure no staged policy is programmed. Staged policies must be skipped. Consistently(getRuleFunc(tc.Felixes[0], "staged"), "5s", "1s").Should(HaveOccurred()) Consistently(getRuleFunc(tc.Felixes[1], "staged"), "5s", "1s").Should(HaveOccurred()) } else { - bpfWaitForPolicy(tc.Felixes[0], ep1_1.InterfaceName, - "egress", "default/tier1.np1-1") - bpfWaitForPolicy(tc.Felixes[0], ep1_1.InterfaceName, - "ingress", "default/tier1.np1-1") - bpfWaitForPolicy(tc.Felixes[1], ep2_1.InterfaceName, - "egress", "default/tier1.np1-1") - bpfWaitForPolicy(tc.Felixes[1], ep2_1.InterfaceName, - "ingress", "default/tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[0], ep1_1.InterfaceName, "egress", "default", "tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[0], ep1_1.InterfaceName, "ingress", "default", "tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[1], ep2_1.InterfaceName, "egress", "default", "tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[1], ep2_1.InterfaceName, "ingress", "default", "tier1.np1-1") + // When policies are programmed, make sure no staged policy is programmed. Staged policies must be skipped. Consistently(bpfDumpPolicy(tc.Felixes[0], ep1_1.InterfaceName, "ingress"), "5s", "1s").ShouldNot(ContainSubstring("staged")) Consistently(bpfDumpPolicy(tc.Felixes[0], ep1_1.InterfaceName, "egress"), "5s", "1s").ShouldNot(ContainSubstring("staged")) @@ -1564,22 +1500,19 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag if !bpfEnabled { // Wait for felix to see and program some expected nflog entries, and for the cluster IP to appear. - Eventually(getRuleFunc(tc.Felixes[0], "PPI0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) - Eventually(getRuleFunc(tc.Felixes[0], "PPE0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) - Eventually(getRuleFunc(tc.Felixes[1], "PPI0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) - Eventually(getRuleFunc(tc.Felixes[1], "PPE0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[0], "PPI0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[0], "PPE0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[1], "PPI0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[1], "PPE0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) // When policies are programmed, make sure no staged policy is programmed. Staged policies must be skipped. Consistently(getRuleFunc(tc.Felixes[0], "staged"), "5s", "1s").Should(HaveOccurred()) Consistently(getRuleFunc(tc.Felixes[1], "staged"), "5s", "1s").Should(HaveOccurred()) } else { - bpfWaitForPolicy(tc.Felixes[0], ep1_1.InterfaceName, - "egress", "default/tier1.np1-1") - bpfWaitForPolicy(tc.Felixes[0], ep1_1.InterfaceName, - "ingress", "default/tier1.np1-1") - bpfWaitForPolicy(tc.Felixes[1], ep2_1.InterfaceName, - "egress", "default/tier1.np1-1") - bpfWaitForPolicy(tc.Felixes[1], ep2_1.InterfaceName, - "ingress", "default/tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[0], ep1_1.InterfaceName, "egress", "default", "tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[0], ep1_1.InterfaceName, "ingress", "default", "tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[1], ep2_1.InterfaceName, "egress", "default", "tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[1], ep2_1.InterfaceName, "ingress", "default", "tier1.np1-1") + // When policies are programmed, make sure no staged policy is programmed. Staged policies must be skipped. Consistently(bpfDumpPolicy(tc.Felixes[0], ep1_1.InterfaceName, "ingress"), "5s", "1s").ShouldNot(ContainSubstring("staged")) Consistently(bpfDumpPolicy(tc.Felixes[0], ep1_1.InterfaceName, "egress"), "5s", "1s").ShouldNot(ContainSubstring("staged")) @@ -1626,22 +1559,18 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag if !bpfEnabled { // Wait for felix to see and program some expected nflog entries, and for the cluster IP to appear. - Eventually(getRuleFunc(tc.Felixes[0], "PPI0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) - Eventually(getRuleFunc(tc.Felixes[0], "PPE0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) - Eventually(getRuleFunc(tc.Felixes[1], "PPI0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) - Eventually(getRuleFunc(tc.Felixes[1], "PPE0|default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[0], "PPI0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[0], "PPE0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[1], "PPI0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFunc(tc.Felixes[1], "PPE0|np/default/tier1.np1-1"), "10s", "1s").ShouldNot(HaveOccurred()) // When policies are programmed, make sure no staged policy is programmed. Staged policies must be skipped. Consistently(getRuleFunc(tc.Felixes[0], "staged"), "5s", "1s").Should(HaveOccurred()) Consistently(getRuleFunc(tc.Felixes[1], "staged"), "5s", "1s").Should(HaveOccurred()) } else { - bpfWaitForPolicy(tc.Felixes[0], ep1_1.InterfaceName, - "egress", "default/tier1.np1-1") - bpfWaitForPolicy(tc.Felixes[0], ep1_1.InterfaceName, - "ingress", "default/tier1.np1-1") - bpfWaitForPolicy(tc.Felixes[1], ep2_1.InterfaceName, - "egress", "default/tier1.np1-1") - bpfWaitForPolicy(tc.Felixes[1], ep2_1.InterfaceName, - "ingress", "default/tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[0], ep1_1.InterfaceName, "egress", "default", "tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[0], ep1_1.InterfaceName, "ingress", "default", "tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[1], ep2_1.InterfaceName, "egress", "default", "tier1.np1-1") + bpfWaitForNetworkPolicy(tc.Felixes[1], ep2_1.InterfaceName, "ingress", "default", "tier1.np1-1") // When policies are programmed, make sure no staged policy is programmed. Staged policies must be skipped. Consistently(bpfDumpPolicy(tc.Felixes[0], ep1_1.InterfaceName, "ingress"), "5s", "1s").ShouldNot(ContainSubstring("staged")) Consistently(bpfDumpPolicy(tc.Felixes[0], ep1_1.InterfaceName, "egress"), "5s", "1s").ShouldNot(ContainSubstring("staged")) @@ -1721,12 +1650,12 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag Reporter: "src", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|__PROFILE__|pro:kns.default|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|tier2|default/tier2.staged:np2-1|deny|-1": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|tier2|snp:default/tier2.np2-1|deny|-1": {}, }, }, ) @@ -1742,12 +1671,12 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag Reporter: "src", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|__PROFILE__|pro:kns.default|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|tier2|default/tier2.staged:np2-1|allow|2": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|tier2|snp:default/tier2.np2-1|allow|2": {}, }, }, ) @@ -1772,12 +1701,12 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag Reporter: "dst", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|__PROFILE__|pro:kns.default|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|tier2|default/tier2.staged:np2-1|deny|-1": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|tier2|snp:default/tier2.np2-1|deny|-1": {}, }, }, ) @@ -1793,12 +1722,12 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag Reporter: "dst", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|__PROFILE__|pro:kns.default|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|tier2|default/tier2.staged:np2-1|allow|1": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|tier2|snp:default/tier2.np2-1|allow|1": {}, }, }, ) @@ -1880,12 +1809,12 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag Reporter: "src", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|__PROFILE__|pro:kns.default|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|tier2|default/tier2.staged:np2-1|deny|-1": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|tier2|snp:default/tier2.np2-1|deny|-1": {}, }, }, ) @@ -1900,13 +1829,13 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag Reporter: "src", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|__PROFILE__|pro:kns.default|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|tier2|default/tier2.staged:np2-1|pass|0": {}, - "2|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|tier2|snp:default/tier2.np2-1|pass|0": {}, + "2|__PROFILE__|pro:kns.default|allow|0": {}, }, }, ) @@ -1931,12 +1860,12 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag Reporter: "dst", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|__PROFILE__|pro:kns.default|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|tier2|default/tier2.staged:np2-1|deny|-1": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|tier2|snp:default/tier2.np2-1|deny|-1": {}, }, }, ) @@ -1951,13 +1880,13 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag Reporter: "dst", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|__PROFILE__|pro:kns.default|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|tier2|default/tier2.staged:np2-1|pass|0": {}, - "2|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|tier2|snp:default/tier2.np2-1|pass|0": {}, + "2|__PROFILE__|pro:kns.default|allow|0": {}, }, }, ) @@ -1971,33 +1900,12 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log with stag }) AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - logNFTDiags(felix) - felix.Exec("iptables-save", "-c") - felix.Exec("ipset", "list") - felix.Exec("ip", "r") - felix.Exec("ip", "a") - } - if bpfEnabled { - tc.Felixes[0].Exec("calico-bpf", "policy", "dump", ep1_1.InterfaceName, "all", "--asm") - tc.Felixes[1].Exec("calico-bpf", "policy", "dump", ep2_1.InterfaceName, "all", "--asm") - } - } - - ep1_1.Stop() - ep2_1.Stop() for _, felix := range tc.Felixes { if bpfEnabled { + // FIXME felix.Exec("calico-bpf", "connect-time", "clean") } - felix.Stop() - } - - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() } - infra.Stop() }) }) diff --git a/felix/fv/flow_logs_goldmane_test.go b/felix/fv/flow_logs_goldmane_test.go index d08502a59e2..44d86974c19 100644 --- a/felix/fv/flow_logs_goldmane_test.go +++ b/felix/fv/flow_logs_goldmane_test.go @@ -1,6 +1,3 @@ -//go:build fvtests -// +build fvtests - // Copyright (c) 2025 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,15 +17,15 @@ package fv_test import ( "context" "fmt" - "net" "os" "strconv" "strings" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/projectcalico/calico/felix/collector/flowlog" "github.com/projectcalico/calico/felix/collector/local" @@ -119,6 +116,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log tests", [ for ii := range wlHost1 { wIP := fmt.Sprintf("10.65.0.%d", ii) wName := fmt.Sprintf("wl-host1-%d", ii) + infrastructure.AssignIP(wName, wIP, tc.Felixes[0].Hostname, client) wlHost1[ii] = workload.Run(tc.Felixes[0], wName, "default", wIP, "8055", "tcp") wlHost1[ii].WorkloadEndpoint.GenerateName = "wl-host1-" wlHost1[ii].ConfigureInInfra(infra) @@ -128,6 +126,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log tests", [ for ii := range wlHost2 { wIP := fmt.Sprintf("10.65.1.%d", ii) wName := fmt.Sprintf("wl-host2-%d", ii) + infrastructure.AssignIP(wName, wIP, tc.Felixes[1].Hostname, client) wlHost2[ii] = workload.Run(tc.Felixes[1], wName, "default", wIP, "8055", "tcp") wlHost2[ii].WorkloadEndpoint.GenerateName = "wl-host2-" wlHost2[ii].ConfigureInInfra(infra) @@ -219,47 +218,51 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log tests", [ Eventually(hostEndpointProgrammed, "30s", "1s").Should(BeTrue(), "Expected HostEndpoint iptables rules to appear") if !BPFMode() { - rulesProgrammed := func() bool { + rulesProgrammed := func() error { out0, err := tc.Felixes[0].ExecOutput("iptables-save", "-t", "filter") - Expect(err).NotTo(HaveOccurred()) + if err != nil { + return err + } out1, err := tc.Felixes[1].ExecOutput("iptables-save", "-t", "filter") - Expect(err).NotTo(HaveOccurred()) + if err != nil { + return err + } if strings.Count(out0, "ARE0|default") == 0 { - return false + return fmt.Errorf("ARE0|default rule not found on felix 0") } - if strings.Count(out1, "default.gnp-1") == 0 { - return false + if strings.Count(out1, "gnp-1") == 0 { + return fmt.Errorf("gnp-1 rule not found on felix 1") } - return true + return nil } if NFTMode() { - rulesProgrammed = func() bool { + rulesProgrammed = func() error { out0, err := tc.Felixes[0].ExecOutput("nft", "list", "ruleset") Expect(err).NotTo(HaveOccurred()) out1, err := tc.Felixes[1].ExecOutput("nft", "list", "ruleset") Expect(err).NotTo(HaveOccurred()) if strings.Count(out0, "ARE0|default") == 0 { - return false + return fmt.Errorf("ARE0|default rule not found on felix 0") } - if strings.Count(out1, "default.gnp-1") == 0 { - return false + if strings.Count(out1, "gnp-1") == 0 { + return fmt.Errorf("gnp-1 rule not found on felix 1") } - return true + return nil } } - Eventually(rulesProgrammed, "10s", "1s").Should(BeTrue(), + Eventually(rulesProgrammed, "10s", "1s").ShouldNot(HaveOccurred(), "Expected iptables rules to appear on the correct felix instances") } else { Eventually(func() bool { - return bpfCheckIfPolicyProgrammed(tc.Felixes[1], "eth0", "egress", "default.gnp-1", "allow", false) + return bpfCheckIfGlobalNetworkPolicyProgrammed(tc.Felixes[1], "eth0", "egress", "gnp-1", "allow", false) }, "5s", "200ms").Should(BeTrue()) Eventually(func() bool { - return bpfCheckIfPolicyProgrammed(tc.Felixes[1], "eth0", "ingress", "default.gnp-1", "allow", false) + return bpfCheckIfGlobalNetworkPolicyProgrammed(tc.Felixes[1], "eth0", "ingress", "gnp-1", "allow", false) }, "5s", "200ms").Should(BeTrue()) Eventually(func() bool { - return bpfCheckIfPolicyProgrammed(tc.Felixes[1], wlHost2[1].InterfaceName, "ingress", "default/default.np-1", "deny", true) + return bpfCheckIfNetworkPolicyProgrammed(tc.Felixes[1], wlHost2[1].InterfaceName, "ingress", "default", "default.np-1", "deny", true) }, "5s", "200ms").Should(BeTrue()) Eventually(func() bool { @@ -373,7 +376,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log tests", [ Reporter: "src", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|__PROFILE__|__PROFILE__.default|allow|0": {}, + "0|__PROFILE__|pro:default|allow|0": {}, }, }) @@ -396,7 +399,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log tests", [ Reporter: "src", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|__PROFILE__|__PROFILE__.default|allow|0": {}, + "0|__PROFILE__|pro:default|allow|0": {}, }, }) @@ -420,7 +423,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log tests", [ Reporter: "dst", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|__PROFILE__|__PROFILE__.default|allow|0": {}, + "0|__PROFILE__|pro:default|allow|0": {}, }, }) @@ -435,7 +438,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log tests", [ Reporter: "dst", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|default|default/default.np-1|deny|0": {}, + "0|default|np:default/default.np-1|deny|0": {}, }, }) @@ -458,7 +461,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log tests", [ Reporter: "src", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|__PROFILE__|__PROFILE__.default|allow|0": {}, + "0|__PROFILE__|pro:default|allow|0": {}, }, }) @@ -473,58 +476,6 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log tests", [ It("should get expected flow logs", func() { checkFlowLogs() }) - - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - logNFTDiags(felix) - felix.Exec("iptables-save", "-c") - felix.Exec("ipset", "list") - felix.Exec("ip", "r") - felix.Exec("ip", "a") - } - if bpfEnabled { - for _, felix := range tc.Felixes { - felix.Exec("calico-bpf", "ipsets", "dump") - felix.Exec("calico-bpf", "routes", "dump") - felix.Exec("calico-bpf", "nat", "dump") - felix.Exec("calico-bpf", "nat", "aff") - felix.Exec("calico-bpf", "conntrack", "dump") - felix.Exec("calico-bpf", "arp", "dump") - felix.Exec("calico-bpf", "counters", "dump") - felix.Exec("calico-bpf", "ifstate", "dump") - felix.Exec("calico-bpf", "policy", "dump", "eth0", "all") - } - for _, w := range wlHost1 { - tc.Felixes[0].Exec("calico-bpf", "policy", "dump", w.InterfaceName, "all") - } - for _, w := range wlHost1 { - tc.Felixes[1].Exec("calico-bpf", "policy", "dump", w.InterfaceName, "all") - } - } - } - - for _, wl := range wlHost1 { - wl.Stop() - } - for _, wl := range wlHost2 { - wl.Stop() - } - for _, wl := range hostW { - wl.Stop() - } - for _, felix := range tc.Felixes { - if bpfEnabled { - felix.Exec("calico-bpf", "connect-time", "clean") - } - felix.Stop() - } - - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) }) var _ = infrastructure.DatastoreDescribe("goldmane flow log ipv6 tests", []apiconfig.DatastoreType{apiconfig.Kubernetes}, func(getInfra infrastructure.InfraFactory) { @@ -550,8 +501,7 @@ var _ = infrastructure.DatastoreDescribe("goldmane flow log ipv6 tests", []apico opts.FlowLogSource = infrastructure.FlowLogSourceLocalSocket opts.EnableIPv6 = true - opts.IPIPMode = api.IPIPModeNever - opts.SimulateBIRDRoutes = true + opts.IPIPMode = api.IPIPModeAlways opts.NATOutgoingEnabled = true opts.AutoHEPsEnabled = false opts.ExtraEnvVars["FELIX_FLOWLOGSFLUSHINTERVAL"] = "2" @@ -561,33 +511,33 @@ var _ = infrastructure.DatastoreDescribe("goldmane flow log ipv6 tests", []apico tc, client = infrastructure.StartNNodeTopology(2, opts, infra) - addWorkload := func(run bool, ii, wi, port int, labels map[string]string) *workload.Workload { + addWorkload := func(hostname string, ii, wi, port int, labels map[string]string) *workload.Workload { if labels == nil { labels = make(map[string]string) } wIP := fmt.Sprintf("10.65.%d.%d", ii, wi+2) + wIPv6 := fmt.Sprintf("dead:beef::%d:%d", ii, wi+2) wName := fmt.Sprintf("w%d%d", ii, wi) + infrastructure.AssignIP(wName, wIP, hostname, client) + infrastructure.AssignIP(wName, wIPv6, hostname, client) w := workload.New(tc.Felixes[ii], wName, "default", - wIP, strconv.Itoa(port), "tcp", workload.WithIPv6Address(net.ParseIP(fmt.Sprintf("dead:beef::%d:%d", ii, wi+2)).String())) + wIP, strconv.Itoa(port), "tcp", workload.WithIPv6Address(wIPv6)) labels["name"] = w.Name labels["workload"] = "regular" - w.WorkloadEndpoint.Labels = labels - if run { - err := w.Start() - Expect(err).NotTo(HaveOccurred()) - w.ConfigureInInfra(infra) - } + err := w.Start(infra) + Expect(err).NotTo(HaveOccurred()) + w.ConfigureInInfra(infra) return w } for ii := range tc.Felixes { // Two workloads on each host so we can check the same host and other host cases. - w[ii][0] = addWorkload(true, ii, 0, 8055, map[string]string{"port": "8055"}) - w[ii][1] = addWorkload(true, ii, 1, 8056, nil) + w[ii][0] = addWorkload(tc.Felixes[ii].Hostname, ii, 0, 8055, map[string]string{"port": "8055"}) + w[ii][1] = addWorkload(tc.Felixes[ii].Hostname, ii, 1, 8056, nil) } err = infra.AddDefaultDeny() @@ -645,10 +595,10 @@ var _ = infrastructure.DatastoreDescribe("goldmane flow log ipv6 tests", []apico out, err = tc.Felixes[0].ExecOutput("iptables-save", "-t", "filter") } Expect(err).NotTo(HaveOccurred()) - if strings.Count(out, "default.gnp-1") == 0 { + if strings.Count(out, "gnp-1") == 0 { return false } - if strings.Count(out, "default.gnp-2") == 0 { + if strings.Count(out, "gnp-2") == 0 { return false } return true @@ -657,19 +607,19 @@ var _ = infrastructure.DatastoreDescribe("goldmane flow log ipv6 tests", []apico "Expected iptables rules to appear on the correct felix instances") } else { Eventually(func() bool { - return bpfCheckIfPolicyProgrammed(tc.Felixes[0], w[0][0].InterfaceName, "egress", "default.gnp-1", "allow", true) + return bpfCheckIfGlobalNetworkPolicyProgrammed(tc.Felixes[0], w[0][0].InterfaceName, "egress", "gnp-1", "allow", true) }, "15s", "200ms").Should(BeTrue()) Eventually(func() bool { - return bpfCheckIfPolicyProgrammed(tc.Felixes[0], w[0][0].InterfaceName, "ingress", "default.gnp-1", "allow", true) + return bpfCheckIfGlobalNetworkPolicyProgrammed(tc.Felixes[0], w[0][0].InterfaceName, "ingress", "gnp-1", "allow", true) }, "5s", "200ms").Should(BeTrue()) Eventually(func() bool { - return bpfCheckIfPolicyProgrammed(tc.Felixes[0], w[0][1].InterfaceName, "egress", "default.gnp-2", "deny", true) + return bpfCheckIfGlobalNetworkPolicyProgrammed(tc.Felixes[0], w[0][1].InterfaceName, "egress", "gnp-2", "deny", true) }, "5s", "200ms").Should(BeTrue()) Eventually(func() bool { - return bpfCheckIfPolicyProgrammed(tc.Felixes[0], w[0][1].InterfaceName, "ingress", "default.gnp-2", "deny", true) + return bpfCheckIfGlobalNetworkPolicyProgrammed(tc.Felixes[0], w[0][1].InterfaceName, "ingress", "gnp-2", "deny", true) }, "5s", "200ms").Should(BeTrue()) } @@ -681,22 +631,6 @@ var _ = infrastructure.DatastoreDescribe("goldmane flow log ipv6 tests", []apico cc.CheckConnectivity() }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - logNFTDiags(felix) - felix.Exec("ip6tables-save", "-c") - felix.Exec("ipset", "list") - felix.Exec("ip", "-6", "r") - felix.Exec("ip", "a") - felix.Exec("iptables-save", "-c") - felix.Exec("ip", "r") - } - } - tc.Stop() - infra.Stop() - }) - It("Should report the ipv6 flow logs", func() { var flows []flowlog.FlowLog var err error @@ -745,6 +679,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane local server tests wlHost1 [2]*workload.Workload wlHost2 [2]*workload.Workload cc *connectivity.Checker + client client.Interface ) BeforeEach(func() { @@ -760,7 +695,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane local server tests opts.ExtraEnvVars["FELIX_FLOWLOGSLOCALREPORTER"] = "Enabled" numNodes := 2 - tc, _ = infrastructure.StartNNodeTopology(numNodes, opts, infra) + tc, client = infrastructure.StartNNodeTopology(numNodes, opts, infra) // Install a default profile that allows all ingress and egress, in the absence of any Policy. infra.AddDefaultAllow() @@ -769,6 +704,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane local server tests for ii := range wlHost1 { wIP := fmt.Sprintf("10.65.0.%d", ii) wName := fmt.Sprintf("wl-host1-%d", ii) + infrastructure.AssignIP(wName, wIP, tc.Felixes[0].Hostname, client) wlHost1[ii] = workload.Run(tc.Felixes[0], wName, "default", wIP, "8055", "tcp") wlHost1[ii].WorkloadEndpoint.GenerateName = "wl-host1-" wlHost1[ii].ConfigureInInfra(infra) @@ -778,6 +714,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane local server tests for ii := range wlHost2 { wIP := fmt.Sprintf("10.65.1.%d", ii) wName := fmt.Sprintf("wl-host2-%d", ii) + infrastructure.AssignIP(wName, wIP, tc.Felixes[1].Hostname, client) wlHost2[ii] = workload.Run(tc.Felixes[1], wName, "default", wIP, "8055", "tcp") wlHost2[ii].WorkloadEndpoint.GenerateName = "wl-host2-" wlHost2[ii].ConfigureInInfra(infra) @@ -856,52 +793,467 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane local server tests }) AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - logNFTDiags(felix) - felix.Exec("iptables-save", "-c") - felix.Exec("ipset", "list") - felix.Exec("ip", "r") - felix.Exec("ip", "a") - } + // FIXME + + for _, felix := range tc.Felixes { if bpfEnabled { - for _, felix := range tc.Felixes { - felix.Exec("calico-bpf", "ipsets", "dump") - felix.Exec("calico-bpf", "routes", "dump") - felix.Exec("calico-bpf", "nat", "dump") - felix.Exec("calico-bpf", "nat", "aff") - felix.Exec("calico-bpf", "conntrack", "dump") - felix.Exec("calico-bpf", "arp", "dump") - felix.Exec("calico-bpf", "counters", "dump") - felix.Exec("calico-bpf", "ifstate", "dump") - felix.Exec("calico-bpf", "policy", "dump", "eth0", "all") - } - for _, w := range wlHost1 { - tc.Felixes[0].Exec("calico-bpf", "policy", "dump", w.InterfaceName, "all") - } - for _, w := range wlHost1 { - tc.Felixes[1].Exec("calico-bpf", "policy", "dump", w.InterfaceName, "all") + felix.Exec("calico-bpf", "connect-time", "clean") + } + } + }) +}) + +var _ = infrastructure.DatastoreDescribe("flow log with deleted service pod test", []apiconfig.DatastoreType{apiconfig.Kubernetes}, func(getInfra infrastructure.InfraFactory) { + const ( + wepPort = 8055 + svcPort = 8066 + ) + wepPortStr := fmt.Sprintf("%d", wepPort) + svcPortStr := fmt.Sprintf("%d", svcPort) + clusterIP := "10.101.0.10" + + var ( + infra infrastructure.DatastoreInfra + opts infrastructure.TopologyOptions + tc infrastructure.TopologyContainers + client client.Interface + ep1_1, ep2_1 *workload.Workload + cc *connectivity.Checker + ) + + bpfEnabled := os.Getenv("FELIX_FV_ENABLE_BPF") == "true" + + BeforeEach(func() { + infra = getInfra() + opts = infrastructure.DefaultTopologyOptions() + opts.FlowLogSource = infrastructure.FlowLogSourceLocalSocket + opts.IPIPMode = api.IPIPModeNever + opts.NATOutgoingEnabled = true + opts.ExtraEnvVars["FELIX_FLOWLOGSFLUSHINTERVAL"] = "25" + opts.ExtraEnvVars["FELIX_FLOWLOGSENABLEHOSTENDPOINT"] = "true" + opts.ExtraEnvVars["FELIX_FLOWLOGSFILEINCLUDELABELS"] = "true" + opts.ExtraEnvVars["FELIX_FLOWLOGSFILEINCLUDEPOLICIES"] = "true" + opts.ExtraEnvVars["FELIX_FLOWLOGSCOLLECTORDEBUGTRACE"] = "true" + opts.ExtraEnvVars["FELIX_FLOWLOGSFILEENABLED"] = "true" + opts.ExtraEnvVars["FELIX_FLOWLOGSFILEINCLUDESERVICE"] = "true" + opts.ExtraEnvVars["FELIX_FLOWLOGSGOLDMANESERVER"] = local.SocketAddress + + // Start felix instances. + tc, client = infrastructure.StartNNodeTopology(2, opts, infra) + + if bpfEnabled { + ensureBPFProgramsAttached(tc.Felixes[0]) + ensureBPFProgramsAttached(tc.Felixes[1]) + } + + // Install a default profile that allows all ingress and egress, in the absence of any Policy. + infra.AddDefaultAllow() + + // Create workload on host 1. + infrastructure.AssignIP("ep1-1", "10.65.0.0", tc.Felixes[0].Hostname, client) + ep1_1 = workload.Run(tc.Felixes[0], "ep1-1", "default", "10.65.0.0", wepPortStr, "tcp") + ep1_1.ConfigureInInfra(infra) + + infrastructure.AssignIP("ep2-1", "10.65.1.0", tc.Felixes[1].Hostname, client) + ep2_1 = workload.Run(tc.Felixes[1], "ep2-1", "default", "10.65.1.0", wepPortStr, "tcp") + ep2_1.ConfigureInInfra(infra) + + ensureRoutesProgrammed(tc.Felixes) + + // Create a service that maps to ep2_1. Rather than checking connectivity to the endpoint we'll go via + // the service to test the destination service name handling. + svcName := "test-service" + k8sClient := infra.(*infrastructure.K8sDatastoreInfra).K8sClient + tSvc := k8sService(svcName, clusterIP, ep2_1, svcPort, wepPort, 0, "tcp") + tSvcNamespace := tSvc.Namespace + _, err := k8sClient.CoreV1().Services(tSvcNamespace).Create(context.Background(), tSvc, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + // Wait for the endpoints to be updated and for the address to be ready. + Expect(ep2_1.IP).NotTo(Equal("")) + getEpsFunc := k8sGetEpsForServiceFunc(k8sClient, tSvc) + epCorrectFn := func() error { + eps := getEpsFunc() + if len(eps) != 1 { + return fmt.Errorf("Wrong number of endpointslices: %#v", eps) + } + if len(eps[0].Endpoints) != 1 { + return fmt.Errorf("Wrong number of endpoints: %#v", eps[0]) + } + endpoints := eps[0].Endpoints + addrs := endpoints[0].Addresses + if len(addrs) != 1 { + return fmt.Errorf("Wrong number of addresses: %#v", eps[0]) + } + if addrs[0] != ep2_1.IP { + return fmt.Errorf("Unexpected IP: %s != %s", addrs[0], ep2_1.IP) + } + ports := eps[0].Ports + if len(ports) != 1 { + return fmt.Errorf("Wrong number of ports: %#v", eps[0]) + } + if *ports[0].Port != int32(wepPort) { + return fmt.Errorf("Wrong port %d != svcPort", *ports[0].Port) + } + return nil + } + Eventually(epCorrectFn, "10s").ShouldNot(HaveOccurred()) + + // Create a policy that allows ep1-1 to communicate with test-service using label matching + gnpServiceAllow := api.NewGlobalNetworkPolicy() + gnpServiceAllow.Name = "ep1-1-allow-test-service" + gnpServiceAllow.Spec.Order = &float2_0 + gnpServiceAllow.Spec.Tier = "default" + gnpServiceAllow.Spec.Selector = ep1_1.NameSelector() + gnpServiceAllow.Spec.Types = []api.PolicyType{api.PolicyTypeEgress} + gnpServiceAllow.Spec.Egress = []api.Rule{ + { + Action: api.Allow, + Destination: api.EntityRule{ + Services: &api.ServiceMatch{ + Namespace: "default", + Name: svcName, + }, + }, + }, + } + _, err = client.GlobalNetworkPolicies().Create(utils.Ctx, gnpServiceAllow, utils.NoOptions) + Expect(err).NotTo(HaveOccurred()) + + if !bpfEnabled { + // Wait for felix to see and program some expected nflog entries, and for the cluster IP to appear. + Eventually(getRuleFunc(tc.Felixes[0], "APE0|gnp/ep1-1-allow-test-service"), "10s", "1s").ShouldNot(HaveOccurred()) + } else { + checkNat := func() bool { + for _, f := range tc.Felixes { + if !f.BPFNATHasBackendForService(clusterIP, svcPort, 6, ep2_1.IP, wepPort) { + return false + } } + return true } + + Eventually(checkNat, "10s", "1s").Should(BeTrue(), "Expected NAT to be programmed") + + bpfWaitForGlobalNetworkPolicy(tc.Felixes[0], ep1_1.InterfaceName, "egress", "ep1-1-allow-test-service") } - for _, wl := range wlHost1 { - wl.Stop() + if !bpfEnabled { + // Mimic the kube-proxy service iptable clusterIP rule. + for _, f := range tc.Felixes { + f.Exec("iptables", "-t", "nat", "-A", "PREROUTING", + "-p", "tcp", + "-d", clusterIP, + "-m", "tcp", "--dport", svcPortStr, + "-j", "DNAT", "--to-destination", + ep2_1.IP+":"+wepPortStr) + } } - for _, wl := range wlHost2 { - wl.Stop() + }) + + It("should get expected flow logs", func() { + // Describe the connectivity that we now expect. + // For ep1_1 -> ep2_1 we use the service cluster IP to test service info in the flow log + cc = &connectivity.Checker{} + cc.ExpectSome(ep1_1, connectivity.TargetIP(clusterIP), uint16(svcPort)) // allowed by np1-1 + + // Do 3 rounds of connectivity checking. + cc.CheckConnectivity() + cc.CheckConnectivity() + cc.CheckConnectivity() + + flowlogs.WaitForConntrackScan(bpfEnabled) + + // Verify we have allowed flow logs before deleting the backing pod + Eventually(func() error { + flows, err := tc.Felixes[0].FlowLogs() + if err != nil { + return err + } + foundAllowed := false + for _, fl := range flows { + if fl.Action == "allow" && fl.DstService.PortNum == int(svcPort) { + foundAllowed = true + break + } + } + if !foundAllowed { + return fmt.Errorf("no allowed flow log found for service port %d", svcPort) + } + return nil + }, "20s", "1s").ShouldNot(HaveOccurred()) + + // Verify that the workload endpoint for ep2_1 exists + Eventually(func() error { + wlList, _ := client.WorkloadEndpoints().List(utils.Ctx, options.ListOptions{}) + for _, wl := range wlList.Items { + if strings.Contains(wl.Name, "ep2--1") { + return nil + } + } + return fmt.Errorf("workload endpoint default/%s still exists", ep2_1.Name) + }, "10s", "1s").ShouldNot(HaveOccurred()) + + // Delete the backing pod (ep2_1) to test flow logs when service has no endpoints + ep2_1.RemoveFromInfra(infra) + + // Wait a moment for the endpoint deletion to propagate + Eventually(func() error { + wlList, _ := client.WorkloadEndpoints().List(utils.Ctx, options.ListOptions{}) + for _, wl := range wlList.Items { + if strings.Contains(wl.Name, "ep2--1") { + return fmt.Errorf("workload endpoint default/%s still exists", ep2_1.Name) + } + } + return nil + }, "10s", "1s").ShouldNot(HaveOccurred()) + + // Now expect connectivity to fail since there's no backing pod + cc = &connectivity.Checker{} + cc.ExpectNone(ep1_1, connectivity.TargetIP(clusterIP), uint16(svcPort)) + + // Do more rounds of connectivity checking - these should fail + cc.CheckConnectivity() + cc.CheckConnectivity() + cc.CheckConnectivity() + + flowlogs.WaitForConntrackScan(bpfEnabled) + + // Verify we get denied or failed flow logs after deleting the backing pod + Consistently(func() error { + var flows []flowlog.FlowLog + var err error + if flows, err = tc.Felixes[0].FlowLogs(); err != nil { + return err + } + for _, fl := range flows { + // After pod deletion, should not see denied flows for the service port + if fl.DstService.PortNum == int(svcPort) && fl.Action == "deny" { + return fmt.Errorf("found denied flow log for service port %d", svcPort) + } + } + return nil + }, "1m", "1s").ShouldNot(HaveOccurred()) + + // Delete conntrack state so that we don't keep seeing 0-metric copies of the logs. This will allow the flows + // to expire quickly. + for ii := range tc.Felixes { + tc.Felixes[ii].Exec("conntrack", "-F") } - for _, felix := range tc.Felixes { + }) + + AfterEach(func() { + if CurrentGinkgoTestDescription().Failed { if bpfEnabled { - felix.Exec("calico-bpf", "connect-time", "clean") + tc.Felixes[0].Exec("calico-bpf", "policy", "dump", ep1_1.InterfaceName, "all", "--asm") } - felix.Stop() } + }) +}) - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() +var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ goldmane flow log networkset precedence tests", []apiconfig.DatastoreType{apiconfig.EtcdV3}, func(getInfra infrastructure.InfraFactory) { + bpfEnabled := os.Getenv("FELIX_FV_ENABLE_BPF") == "true" + + var ( + infra infrastructure.DatastoreInfra + tc infrastructure.TopologyContainers + opts infrastructure.TopologyOptions + client client.Interface + swl1, swl2, swl3, swl4 *workload.Workload + dwl1, dwl2 *workload.Workload + cc *connectivity.Checker + ) + + BeforeEach(func() { + infra = getInfra() + opts.FelixLogSeverity = "Debug" + opts = infrastructure.DefaultTopologyOptions() + opts.IPIPMode = api.IPIPModeNever + opts.FlowLogSource = infrastructure.FlowLogSourceLocalSocket + + opts.ExtraEnvVars["FELIX_FLOWLOGSCOLLECTORDEBUGTRACE"] = "true" + opts.ExtraEnvVars["FELIX_FLOWLOGSFLUSHINTERVAL"] = "2" + opts.ExtraEnvVars["FELIX_FLOWLOGSGOLDMANESERVER"] = local.SocketAddress + opts.ExtraEnvVars["FELIX_FLOWLOGSENABLENETWORKSETS"] = "true" + }) + + JustBeforeEach(func() { + var err error + numNodes := 2 + tc, client = infrastructure.StartNNodeTopology(numNodes, opts, infra) + + if BPFMode() { + ensureBPFProgramsAttached(tc.Felixes[0]) + ensureBPFProgramsAttached(tc.Felixes[1]) + } + + infra.AddDefaultAllow() + + // Source workloads on Node 0 + // swl1 in ns1 + infrastructure.AssignIP("swl1", "10.65.0.2", tc.Felixes[0].Hostname, client) + swl1 = workload.Run(tc.Felixes[0], "swl1", "ns1", "10.65.0.2", "8055", "tcp") + swl1.WorkloadEndpoint.GenerateName = "swl1-" + swl1.WorkloadEndpoint.Namespace = "ns1" + swl1.ConfigureInInfra(infra) + + // swl2 in ns2 + infrastructure.AssignIP("swl2", "10.65.0.3", tc.Felixes[0].Hostname, client) + swl2 = workload.Run(tc.Felixes[0], "swl2", "ns2", "10.65.0.3", "8055", "tcp") + swl2.WorkloadEndpoint.GenerateName = "swl2-" + swl2.WorkloadEndpoint.Namespace = "ns2" + swl2.ConfigureInInfra(infra) + + // swl3 in ns3 + infrastructure.AssignIP("swl3", "10.65.0.4", tc.Felixes[0].Hostname, client) + swl3 = workload.Run(tc.Felixes[0], "swl3", "ns3", "10.65.0.4", "8055", "tcp") + swl3.WorkloadEndpoint.GenerateName = "swl3-" + swl3.WorkloadEndpoint.Namespace = "ns3" + swl3.ConfigureInInfra(infra) + + // swl4 in ns3 + infrastructure.AssignIP("swl4", "10.65.0.5", tc.Felixes[0].Hostname, client) + swl4 = workload.Run(tc.Felixes[0], "swl4", "ns3", "10.65.0.5", "8055", "tcp") + swl4.WorkloadEndpoint.GenerateName = "swl4-" + swl4.WorkloadEndpoint.Namespace = "ns3" + swl4.ConfigureInInfra(infra) + + // Destination workloads on Node 1 (Host Networked to simulate external/non-WEP IPs) + + // dwl1 + infrastructure.AssignIP("dwl1", "10.65.1.2", tc.Felixes[1].Hostname, client) + dwl1 = workload.New(tc.Felixes[1], "dwl1", "", "10.65.1.2", "8055", "tcp", workload.WithHostNetworked()) + // Add IP before starting workload so it can bind + err = tc.Felixes[1].ExecMayFail("ip", "addr", "add", "10.65.1.2/32", "dev", "lo") + Expect(err).NotTo(HaveOccurred()) + Expect(dwl1.Start(tc.Felixes[1])).NotTo(HaveOccurred()) + + // dwl2 + infrastructure.AssignIP("dwl2", "10.65.1.3", tc.Felixes[1].Hostname, client) + dwl2 = workload.New(tc.Felixes[1], "dwl2", "", "10.65.1.3", "8055", "tcp", workload.WithHostNetworked()) + // Add IP before starting workload so it can bind + err = tc.Felixes[1].ExecMayFail("ip", "addr", "add", "10.65.1.3/32", "dev", "lo") + Expect(err).NotTo(HaveOccurred()) + Expect(dwl2.Start(tc.Felixes[1])).NotTo(HaveOccurred()) + + // Add a policy to allow all traffic + policy := api.NewGlobalNetworkPolicy() + policy.Name = "allow-all" + order := float64(20) + policy.Spec.Order = &order + policy.Spec.Selector = "all()" + policy.Spec.Ingress = []api.Rule{{Action: api.Allow}} + policy.Spec.Egress = []api.Rule{{Action: api.Allow}} + _, err = client.GlobalNetworkPolicies().Create(utils.Ctx, policy, utils.NoOptions) + Expect(err).NotTo(HaveOccurred()) + + if !BPFMode() { + Eventually(getRuleFuncTable(tc.Felixes[0], "API0|gnp/allow-all", "filter"), "10s", "1s").ShouldNot(HaveOccurred()) + Eventually(getRuleFuncTable(tc.Felixes[0], "APE0|gnp/allow-all", "filter"), "10s", "1s").ShouldNot(HaveOccurred()) + } else { + bpfWaitForPolicy(tc.Felixes[0], swl1.InterfaceName, "ingress", "allow-all") + bpfWaitForPolicy(tc.Felixes[0], swl1.InterfaceName, "egress", "allow-all") + } + + // NetworkSets + // netset-1 in ns1 matches dwl1 + netset1 := api.NewNetworkSet() + netset1.Name = "netset-1" + netset1.Namespace = "ns1" + netset1.Spec.Nets = []string{dwl1.IP + "/32"} + _, err = client.NetworkSets().Create(utils.Ctx, netset1, utils.NoOptions) + Expect(err).NotTo(HaveOccurred()) + + // netset-2 in ns2 matches dwl1 + netset2 := api.NewNetworkSet() + netset2.Name = "netset-2" + netset2.Namespace = "ns2" + netset2.Spec.Nets = []string{dwl1.IP + "/32"} + _, err = client.NetworkSets().Create(utils.Ctx, netset2, utils.NoOptions) + Expect(err).NotTo(HaveOccurred()) + + // gns-1 (global) matches dwl1 + gnetset := api.NewGlobalNetworkSet() + gnetset.Name = "gns-1" + gnetset.Spec.Nets = []string{dwl1.IP + "/32"} + _, err = client.GlobalNetworkSets().Create(utils.Ctx, gnetset, utils.NoOptions) + Expect(err).NotTo(HaveOccurred()) + + // netset-4 in ns4 matches dwl2 + netset4 := api.NewNetworkSet() + netset4.Name = "netset-4" + netset4.Namespace = "ns4" + netset4.Spec.Nets = []string{dwl2.IP + "/32"} + _, err = client.NetworkSets().Create(utils.Ctx, netset4, utils.NoOptions) + Expect(err).NotTo(HaveOccurred()) + + if BPFMode() { + ensureAllNodesBPFProgramsAttached(tc.Felixes) } - infra.Stop() + }) + + It("should report correct network sets based on namespace precedence", func() { + // Connectivity check + cc = &connectivity.Checker{} + cc.ExpectSome(swl1, dwl1) + cc.ExpectSome(swl2, dwl1) + cc.ExpectSome(swl3, dwl1) + cc.ExpectSome(swl4, dwl2) + cc.CheckConnectivity() + + flowlogs.WaitForConntrackScan(bpfEnabled) + + for ii := range tc.Felixes { + tc.Felixes[ii].Exec("conntrack", "-F") + } + + Eventually(func() error { + wepPort := 8055 + flowTester := flowlogs.NewFlowTester(flowlogs.FlowTesterOptions{ + ExpectLabels: true, + ExpectEnforcedPolicies: true, + MatchEnforcedPolicies: true, + MatchLabels: false, + Includes: []flowlogs.IncludeFilter{flowlogs.IncludeByDestPort(wepPort)}, + }) + + err := flowTester.PopulateFromFlowLogs(tc.Felixes[0]) + if err != nil { + return fmt.Errorf("error populating flow logs from Felix[0]: %s", err) + } + + aggrTuple := tuple.Make(flowlog.EmptyIP, flowlog.EmptyIP, 6, flowlogs.SourcePortIsNotIncluded, wepPort) + + type checkArgs struct { + desc string + srcNS string + srcAggName string + dstNS string + dstAggName string + } + check := func(args checkArgs) { + flowTester.CheckFlow(flowlog.FlowLog{ + FlowMeta: flowlog.FlowMeta{ + Tuple: aggrTuple, + SrcMeta: endpoint.Metadata{Type: "wep", Namespace: args.srcNS, Name: flowlog.FieldNotIncluded, AggregatedName: args.srcAggName}, + DstMeta: endpoint.Metadata{Type: "ns", Namespace: args.dstNS, Name: flowlog.FieldNotIncluded, AggregatedName: args.dstAggName}, + DstService: flowlog.FlowService{Namespace: flowlog.FieldNotIncluded, Name: flowlog.FieldNotIncluded, PortName: flowlog.FieldNotIncluded, PortNum: 0}, + Action: "allow", Reporter: "src", + }, + FlowEnforcedPolicySet: flowlog.FlowPolicySet{"0|default|gnp:allow-all|allow|0": {}}, + }) + } + + check(checkArgs{desc: "ns1 -> netset-1", srcNS: "ns1", srcAggName: "swl1-*", dstNS: "ns1", dstAggName: "netset-1"}) + check(checkArgs{desc: "ns2 -> netset-2", srcNS: "ns2", srcAggName: "swl2-*", dstNS: "ns2", dstAggName: "netset-2"}) + check(checkArgs{desc: "ns3 -> gns-1", srcNS: "ns3", srcAggName: "swl3-*", dstNS: flowlog.FieldNotIncluded, dstAggName: "gns-1"}) + check(checkArgs{desc: "ns3 -> netset-4", srcNS: "ns3", srcAggName: "swl4-*", dstNS: "ns4", dstAggName: "netset-4"}) + + if err := flowTester.Finish(); err != nil { + return fmt.Errorf("Flows incorrect on Felix[0]:\n%v", err) + } + return nil + }, "30s", "3s").ShouldNot(HaveOccurred()) }) }) @@ -921,3 +1273,27 @@ func countNodesWithNodeIP(c client.Interface) int { return count } + +func getRuleFuncTable(felix *infrastructure.Felix, chain string, table string) func() error { + return func() error { + if NFTMode() { + out, err := felix.ExecOutput("nft", "list", "ruleset") + if err != nil { + return err + } + if strings.Contains(out, chain) { + return nil + } + return fmt.Errorf("chain %s not found in nft ruleset", chain) + } + + out, err := felix.ExecOutput("iptables-save", "-t", table) + if err != nil { + return err + } + if strings.Contains(out, chain) { + return nil + } + return fmt.Errorf("chain %s not found in table %s", chain, table) + } +} diff --git a/felix/fv/forward_drop_test.go b/felix/fv/forward_drop_test.go index 704119cba45..e68574c5741 100644 --- a/felix/fv/forward_drop_test.go +++ b/felix/fv/forward_drop_test.go @@ -12,14 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( "fmt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" "github.com/projectcalico/calico/felix/fv/connectivity" "github.com/projectcalico/calico/felix/fv/infrastructure" @@ -68,18 +66,6 @@ var _ = infrastructure.DatastoreDescribe("Base FORWARD behaviour", []apiconfig.D cc = &connectivity.Checker{} }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - tc.Felixes[0].Exec("iptables-save", "-c") - tc.Felixes[0].Exec("ipset", "list") - tc.Felixes[0].Exec("ip", "r") - tc.Felixes[0].Exec("ip", "a") - } - tc.Stop() - infra.Stop() - }) - It("should not forward because of FORWARD DROP policy", func() { cc.ExpectNone(w[0], w[1]) cc.ExpectNone(w[1], w[0]) diff --git a/felix/fv/fv_infra_test.go b/felix/fv/fv_infra_test.go index 281f22411bb..c7c4adf2f06 100644 --- a/felix/fv/fv_infra_test.go +++ b/felix/fv/fv_infra_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -23,7 +21,7 @@ import ( "strings" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" @@ -72,18 +70,6 @@ func describeConnCheckTests(protocol string) bool { cc.Protocol = protocol }) - AfterEach(func() { - for _, wl := range hostW { - wl.Stop() - } - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) - It("should have host-to-host on right port only", func() { cc.ExpectSome(tc.Felixes[0], hostW[1]) if !strings.HasPrefix(protocol, "ip") { @@ -124,12 +110,12 @@ func describeConnCheckTests(protocol string) bool { tcpdF := tc.Felixes[0].AttachTCPDump("eth0") tcpdF.SetLogEnabled(true) tcpdF.AddMatcher("UDP", regexp.MustCompile(`.*UDP.*`)) - tcpdF.Start() + tcpdF.Start(infra) tcpdW := hostW[1].AttachTCPDump() tcpdW.SetLogEnabled(true) tcpdW.AddMatcher("UDP", regexp.MustCompile(`.*UDP.*`)) - tcpdW.Start() + tcpdW.Start(infra) cc.ExpectLoss(tc.Felixes[0], hostW[1], 2*time.Second, 20, -1) cc.CheckConnectivityPacketLoss() @@ -161,14 +147,6 @@ var _ = infrastructure.DatastoreDescribe("Container self tests", tc, _ = infrastructure.StartNNodeTopology(1, options, infra) }) - AfterEach(func() { - tc.Stop() - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) - It("should only report that existing files actually exist", func() { Expect(tc.Felixes[0].FileExists("/usr/local/bin/calico-felix")).To(BeTrue()) Expect(tc.Felixes[0].FileExists("/garbage")).To(BeFalse()) diff --git a/felix/fv/fv_suite_test.go b/felix/fv/fv_suite_test.go index 8a4aa0c57ea..7b7969cbd24 100644 --- a/felix/fv/fv_suite_test.go +++ b/felix/fv/fv_suite_test.go @@ -12,21 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( "bytes" "fmt" + "hash/fnv" "os" "runtime/metrics" + "strconv" "testing" "time" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + . "github.com/onsi/ginkgo/v2" + "github.com/onsi/ginkgo/v2/reporters" + "github.com/onsi/ginkgo/v2/types" + "github.com/onsi/gomega" "github.com/onsi/gomega/format" "github.com/prometheus/procfs" "golang.org/x/sys/unix" @@ -38,7 +39,10 @@ import ( "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) -var realStdout = os.Stdout +var ( + reportPath string + realStdout = os.Stdout +) func init() { testutils.HookLogrusForGinkgo() @@ -48,32 +52,55 @@ func init() { } func TestFv(t *testing.T) { - RegisterFailHandler(Fail) - reportName := "fv_suite" + gomega.RegisterFailHandler(Fail) + err := configureManualSharding() + if err != nil { + t.Fatalf("Failed to configure manual sharding: %v", err) + } + // OS_RELEASE is set by run-batches. On ubuntu, it looks like 24.04. + osRel := os.Getenv("OS_RELEASE") + extraSuffix := os.Getenv("EXTRA_REPORT_SUFFIX") + descSuffix := osRel + fileSuffix := osRel + if BPFMode() { + fileSuffix += "_bpf" + descSuffix += " BPF" + } else { + descSuffix += " non-BPF" + } if NFTMode() { - reportName = "fv_nft_suite" + fileSuffix += "_nft" + descSuffix += " (nftables)" + } else { + fileSuffix += "_ipt" + descSuffix += " (iptables)" } - junitReporter := reporters.NewJUnitReporter(fmt.Sprintf("../report/%s.xml", reportName)) - RunSpecsWithDefaultAndCustomReporters(t, "FV Suite", []Reporter{junitReporter}) + if extraSuffix != "" { + descSuffix += " " + extraSuffix + } + suiteConfig, reporterConfig := GinkgoConfiguration() + reportPath = fmt.Sprintf("../report/felix_fv_%s.xml", fileSuffix) + reporterConfig.JUnitReport = "" + RunSpecs(t, "FV: Felix "+descSuffix, suiteConfig, reporterConfig) } var _ = BeforeEach(func() { - _, _ = fmt.Fprintf(realStdout, "\nFV-TEST-START: %s", CurrentGinkgoTestDescription().FullTestText) + _, _ = fmt.Fprintf(realStdout, "\nFV-TEST-START: %s", CurrentSpecReport().FullText()) }) var _ = JustAfterEach(func() { - if CurrentGinkgoTestDescription().Failed { + if CurrentSpecReport().Failed() { _, _ = fmt.Fprintf(realStdout, "\n") } }) var _ = AfterEach(func() { defer connectivity.UnactivatedCheckers.Clear() - if CurrentGinkgoTestDescription().Failed { + if CurrentSpecReport().Failed() { // If the test has already failed, ignore any connectivity checker leak. return } - Expect(connectivity.UnactivatedCheckers.Len()).To(BeZero(), + gomega.Expect(connectivity.UnactivatedCheckers.Len()).To(gomega.BeZero(), "Test bug: ConnectivityChecker was created but not activated.") }) @@ -87,7 +114,7 @@ var _ = BeforeSuite(func() { var err error // Using Prometheus' library because we already import it indirectly. procFS, err = procfs.NewFS("/proc") - Expect(err).NotTo(HaveOccurred()) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) go func() { for { select { @@ -102,6 +129,42 @@ var _ = BeforeSuite(func() { }() }) +func configureManualSharding() error { + currentBatch, err := strconv.Atoi(os.Getenv("FV_BATCH")) + if err != nil || currentBatch < 1 { + return fmt.Errorf("invalid FV_BATCH value: %s", os.Getenv("FV_BATCH")) + } + totalBatches, err := strconv.Atoi(os.Getenv("FV_NUM_BATCHES")) + if err != nil || totalBatches < 1 { + return fmt.Errorf("invalid FV_NUM_BATCHES value: %s", os.Getenv("FV_NUM_BATCHES")) + } + + if totalBatches < currentBatch { + return fmt.Errorf("totalBatches (%d) cannot be less than currentBatch (%d)", totalBatches, currentBatch) + } + + fmt.Printf("[SHARD-INIT] Manual Sharding Active: Running Batch %d of %d\n", currentBatch, totalBatches) + + BeforeEach(func() { + specReport := CurrentSpecReport() + hash := hashString(specReport.FullText()) + assignedBatch := (hash % uint32(totalBatches)) + 1 + if int(assignedBatch) != currentBatch { + Skip(fmt.Sprintf("️[SHARD-SKIP] Test assigned to batch %d (Current: %d)", assignedBatch, currentBatch)) + } else { + fmt.Printf("️[SHARD-RUN] Batch %d executing: %s\n", currentBatch, specReport.LeafNodeText) + } + }) + + return nil +} + +func hashString(s string) uint32 { + h := fnv.New32a() + h.Write([]byte(s)) + return h.Sum32() +} + func logStats() { p := message.NewPrinter(language.English) @@ -182,3 +245,36 @@ var _ = AfterSuite(func() { infrastructure.RemoveTLSCredentials() close(stopMonitorC) }) + +var _ = ReportAfterSuite("Clean Report Generator", func(report Report) { + cleanSpecs := []SpecReport{} + skippedOrPendingCount := 0 + + for _, spec := range report.SpecReports { + // Filter skipped or pending tests + if spec.State == types.SpecStateSkipped || spec.State == types.SpecStatePending { + skippedOrPendingCount++ + continue + } + + // Strip logs for passed tests to save space + if spec.State == types.SpecStatePassed { + spec.CapturedGinkgoWriterOutput = "" + spec.CapturedStdOutErr = "" + } + + cleanSpecs = append(cleanSpecs, spec) + } + + // Update the report object with the filtered list + report.SpecReports = cleanSpecs + fmt.Printf("\n[REPORT-CLEANER] Removed %d skipped or pending specs. Saving report with %d specs.\n", skippedOrPendingCount, len(cleanSpecs)) + + if reportPath != "" { + if err := reporters.GenerateJUnitReport(report, reportPath); err != nil { + fmt.Printf("[REPORT-CLEANER] Error generating JUnit report: %v\n", err) + } else { + fmt.Printf("[REPORT-CLEANER] JUnit report saved to: %s\n", reportPath) + } + } +}) diff --git a/felix/fv/health_test.go b/felix/fv/health_test.go index 1f78743bd85..cdcf333df19 100644 --- a/felix/fv/health_test.go +++ b/felix/fv/health_test.go @@ -1,5 +1,3 @@ -//go:build fvtests - // Copyright (c) 2017-2019,2021 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); @@ -39,9 +37,10 @@ import ( "fmt" "io" "math/rand" + gonet "net" "net/http" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/types" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -51,13 +50,14 @@ import ( "github.com/projectcalico/calico/felix/fv/infrastructure" "github.com/projectcalico/calico/felix/fv/utils" "github.com/projectcalico/calico/felix/fv/workload" + "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" "github.com/projectcalico/calico/libcalico-go/lib/health" "github.com/projectcalico/calico/libcalico-go/lib/net" "github.com/projectcalico/calico/libcalico-go/lib/options" ) -var _ = Describe("_HEALTH_ _BPF-SAFE_ health tests", func() { - var k8sInfra *infrastructure.K8sDatastoreInfra +var _ = infrastructure.DatastoreDescribe("_HEALTH_ _BPF-SAFE_ health tests", []apiconfig.DatastoreType{apiconfig.Kubernetes}, func(getInfra infrastructure.InfraFactory) { + var infra infrastructure.DatastoreInfra var felix *infrastructure.Felix felixReady := func() int { @@ -69,19 +69,12 @@ var _ = Describe("_HEALTH_ _BPF-SAFE_ health tests", func() { } BeforeEach(func() { - var err error - k8sInfra, err = infrastructure.GetK8sDatastoreInfra(infrastructure.K8SInfraLocalCluster) - Expect(err).NotTo(HaveOccurred()) + infra = getInfra() // Avoid cross-talk between tests. felix = nil }) - AfterEach(func() { - felix.Stop() - k8sInfra.Stop() - }) - // describeCommonFelixTests creates specs for Felix tests that are common between the // two scenarios below (with and without Typha). describeCommonFelixTests := func() { @@ -106,9 +99,8 @@ var _ = Describe("_HEALTH_ _BPF-SAFE_ health tests", func() { podIP := "10.0.0.1" pod := workload.New(felix, testPodName, "default", podIP, "12345", "tcp") - pod.Start() - - pod.ConfigureInInfra(k8sInfra) + Expect(pod.Start(infra)).To(Succeed()) + pod.ConfigureInInfra(infra) } Describe("after removing iptables-restore/nft", func() { @@ -190,6 +182,7 @@ var _ = Describe("_HEALTH_ _BPF-SAFE_ health tests", func() { utils.Config.TyphaImage, "calico-typha")...) Expect(typhaContainer).NotTo(BeNil()) + infra.AddCleanup(typhaContainer.Stop) typhaReady = healthStatusFn(typhaContainer.IP, "9098", "readiness") typhaLiveness = healthStatusFn(typhaContainer.IP, "9098", "liveness") } @@ -211,7 +204,7 @@ var _ = Describe("_HEALTH_ _BPF-SAFE_ health tests", func() { envVars["FELIX_HealthTimeoutOverrides"] = "CalculationGraph=" + params.calcGraphTimeout } felix = infrastructure.RunFelix( - k8sInfra, 0, infrastructure.TopologyOptions{ + infra, 0, infrastructure.TopologyOptions{ EnableIPv6: false, ExtraEnvVars: envVars, DelayFelixStart: true, @@ -220,7 +213,7 @@ var _ = Describe("_HEALTH_ _BPF-SAFE_ health tests", func() { if BPFMode() { // In BPF mode, felix needs the Node to be configured. ipPoolCIDR := net.MustParseCIDR("10.70.0.0/24") - k8sInfra.AddNode(felix, &ipPoolCIDR.IPNet, nil, 0, true) + infra.AddNode(felix, &ipPoolCIDR.IPNet, nil, 0, true) } felix.TriggerDelayedStart() } @@ -232,24 +225,24 @@ var _ = Describe("_HEALTH_ _BPF-SAFE_ health tests", func() { } It("should run healthchecks on localhost by default", func() { - startFelix("", k8sInfra.GetDockerArgs, felixParams{dataplaneTimeout: "20s"}) + startFelix("", infra.GetDockerArgs, felixParams{dataplaneTimeout: "20s"}) Eventually(checkHealthInternally, "10s", "100ms").ShouldNot(HaveOccurred()) }) It("should run support running healthchecks on '127.0.0.1'", func() { - startFelix("", k8sInfra.GetDockerArgs, felixParams{dataplaneTimeout: "20", healthHost: "127.0.0.1"}) + startFelix("", infra.GetDockerArgs, felixParams{dataplaneTimeout: "20", healthHost: "127.0.0.1"}) Eventually(checkHealthInternally, "10s", "100ms").ShouldNot(HaveOccurred()) }) It("should support running healthchecks on 'localhost'", func() { - startFelix("", k8sInfra.GetDockerArgs, felixParams{dataplaneTimeout: "20", healthHost: "localhost"}) + startFelix("", infra.GetDockerArgs, felixParams{dataplaneTimeout: "20", healthHost: "localhost"}) Eventually(checkHealthInternally, "10s", "100ms").ShouldNot(HaveOccurred()) }) }) Describe("with Felix running (no Typha)", func() { BeforeEach(func() { - startFelix("", k8sInfra.GetDockerArgs, felixParams{dataplaneTimeout: "20", healthHost: "0.0.0.0"}) + startFelix("", infra.GetDockerArgs, felixParams{dataplaneTimeout: "20", healthHost: "0.0.0.0"}) }) describeCommonFelixTests() @@ -257,7 +250,7 @@ var _ = Describe("_HEALTH_ _BPF-SAFE_ health tests", func() { Describe("with Felix (no Typha) and Felix calc graph set to hang (10s calc graph timeout)", func() { BeforeEach(func() { - startFelix("", k8sInfra.GetDockerArgs, felixParams{calcGraphTimeout: "10s", calcGraphHangTime: "5", healthHost: "0.0.0.0"}) + startFelix("", infra.GetDockerArgs, felixParams{calcGraphTimeout: "10s", calcGraphHangTime: "5", healthHost: "0.0.0.0"}) waitForMainLoop(felix) }) @@ -270,7 +263,7 @@ var _ = Describe("_HEALTH_ _BPF-SAFE_ health tests", func() { Describe("with Felix (no Typha) and Felix dataplane set to hang (default 90s timeout)", func() { BeforeEach(func() { - startFelix("", k8sInfra.GetDockerArgs, felixParams{dataplaneHangTime: "5", healthHost: "0.0.0.0"}) + startFelix("", infra.GetDockerArgs, felixParams{dataplaneHangTime: "5", healthHost: "0.0.0.0"}) waitForMainLoop(felix) }) @@ -284,7 +277,7 @@ var _ = Describe("_HEALTH_ _BPF-SAFE_ health tests", func() { Describe("with Felix (no Typha) and Felix dataplane set to hang (20s timeout)", func() { BeforeEach(func() { - startFelix("", k8sInfra.GetDockerArgs, felixParams{dataplaneTimeout: "20", dataplaneHangTime: "5", healthHost: "0.0.0.0"}) + startFelix("", infra.GetDockerArgs, felixParams{dataplaneTimeout: "20", dataplaneHangTime: "5", healthHost: "0.0.0.0"}) waitForMainLoop(felix) }) @@ -297,12 +290,8 @@ var _ = Describe("_HEALTH_ _BPF-SAFE_ health tests", func() { Describe("with Felix and Typha running", func() { BeforeEach(func() { - startTypha(k8sInfra.GetDockerArgs) - startFelix(typhaContainer.IP+":5473", k8sInfra.GetDockerArgs, felixParams{dataplaneTimeout: "20", healthHost: "0.0.0.0"}) - }) - - AfterEach(func() { - typhaContainer.Stop() + startTypha(infra.GetDockerArgs) + startFelix(typhaContainer.IP+":5473", infra.GetDockerArgs, felixParams{dataplaneTimeout: "20", healthHost: "0.0.0.0"}) }) describeCommonFelixTests() @@ -321,14 +310,10 @@ var _ = Describe("_HEALTH_ _BPF-SAFE_ health tests", func() { Describe("with Felix unable to connect to Typha at first (20s timeout)", func() { BeforeEach(func() { // We have to start Typha first so we can pass its IP to Felix. - startTypha(k8sInfra.GetDockerArgs) + startTypha(infra.GetDockerArgs) // Start felix with the wrong Typha port so it won't be able to connect initially. Then, we'll add a // NAT rule to steer the traffic to the right port below. - startFelix(typhaContainer.IP+":5474" /*wrong port!*/, k8sInfra.GetDockerArgs, felixParams{dataplaneTimeout: "20", healthHost: "0.0.0.0"}) - }) - - AfterEach(func() { - typhaContainer.Stop() + startFelix(typhaContainer.IP+":5474" /*wrong port!*/, infra.GetDockerArgs, felixParams{dataplaneTimeout: "20", healthHost: "0.0.0.0"}) }) It("should report not ready until it connects to Typha, then report ready", func() { @@ -345,7 +330,7 @@ var _ = Describe("_HEALTH_ _BPF-SAFE_ health tests", func() { Describe("with typha connected to bad API endpoint", func() { BeforeEach(func() { - startTypha(k8sInfra.GetBadEndpointDockerArgs) + startTypha(infra.GetBadEndpointDockerArgs) }) It("typha should not report ready", func() { @@ -362,7 +347,7 @@ var _ = Describe("_HEALTH_ _BPF-SAFE_ health tests", func() { BeforeEach(func() { var err error - info, err = k8sInfra.GetCalicoClient().ClusterInformation().Get( + info, err = infra.GetCalicoClient().ClusterInformation().Get( context.Background(), "default", options.GetOptions{}, @@ -371,13 +356,13 @@ var _ = Describe("_HEALTH_ _BPF-SAFE_ health tests", func() { log.Infof("info = %#v", info) notReady := false info.Spec.DatastoreReady = ¬Ready - info, err = k8sInfra.GetCalicoClient().ClusterInformation().Update( + info, err = infra.GetCalicoClient().ClusterInformation().Update( context.Background(), info, options.SetOptions{}, ) Expect(err).NotTo(HaveOccurred()) - startFelix("", k8sInfra.GetDockerArgs, felixParams{dataplaneTimeout: "20", healthHost: "0.0.0.0"}) + startFelix("", infra.GetDockerArgs, felixParams{dataplaneTimeout: "20", healthHost: "0.0.0.0"}) }) AfterEach(func() { @@ -385,7 +370,7 @@ var _ = Describe("_HEALTH_ _BPF-SAFE_ health tests", func() { ready := true info.Spec.DatastoreReady = &ready var err error - info, err = k8sInfra.GetCalicoClient().ClusterInformation().Update( + info, err = infra.GetCalicoClient().ClusterInformation().Update( context.Background(), info, options.SetOptions{}, @@ -407,11 +392,8 @@ var _ = Describe("_HEALTH_ _BPF-SAFE_ health tests", func() { Describe("with Felix connected to bad typha port", func() { BeforeEach(func() { - startTypha(k8sInfra.GetDockerArgs) - startFelix(typhaContainer.IP+":5474", k8sInfra.GetDockerArgs, felixParams{dataplaneTimeout: "20", healthHost: "0.0.0.0"}) - }) - AfterEach(func() { - typhaContainer.Stop() + startTypha(infra.GetDockerArgs) + startFelix(typhaContainer.IP+":5474", infra.GetDockerArgs, felixParams{dataplaneTimeout: "20", healthHost: "0.0.0.0"}) }) It("should become unready, then die", func() { Eventually(felixReady, "5s", "1s").ShouldNot(BeGood()) @@ -429,8 +411,9 @@ func healthStatusFn(ip, port, endpoint string) func() int { } } -func healthStatus(ip, port, endpoint string) int { - resp, err := http.Get("http://" + ip + ":" + port + "/" + endpoint) +func healthStatus(ip, port, path string) int { + ep := gonet.JoinHostPort(ip, port) + resp, err := http.Get("http://" + ep + "/" + path) if err != nil { log.WithError(err).WithField("resp", resp).Warn("HTTP GET failed") return statusErr diff --git a/felix/fv/host_port_test.go b/felix/fv/host_port_test.go index 67a197841b2..13e29e24a15 100644 --- a/felix/fv/host_port_test.go +++ b/felix/fv/host_port_test.go @@ -1,5 +1,3 @@ -//go:build fvtests - // Copyright (c) 2017-2018,2021 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +17,7 @@ package fv_test import ( "os" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -41,7 +39,7 @@ func MetricsPortReachable(felix *infrastructure.Felix, bpf bool) bool { // Delete existing conntrack state for the metrics port. felix.Exec("conntrack", "-L") felix.Exec("conntrack", "-L", "-p", "tcp", "--dport", metrics.PortString()) - felix.ExecMayFail("conntrack", "-D", "-p", "tcp", "--orig-port-dst", metrics.PortString()) + _ = felix.ExecMayFail("conntrack", "-D", "-p", "tcp", "--orig-port-dst", metrics.PortString()) // Now try to get a metric. m, err := metrics.GetFelixMetric(felix.IP, "felix_active_local_endpoints") @@ -95,21 +93,6 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ host-port tests", []apiconf } }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - if NFTMode() { - logNFTDiags(tc.Felixes[0]) - } else { - tc.Felixes[0].Exec("iptables-save", "-c") - } - tc.Felixes[0].Exec("ip", "r") - tc.Felixes[0].Exec("ip", "a") - } - tc.Stop() - infra.Stop() - }) - It("with no endpoints or policy, port should be reachable", func() { Eventually(metricsPortReachable, "10s", "1s").Should(BeTrue()) }) diff --git a/felix/fv/hostendpoints_test.go b/felix/fv/hostendpoints_test.go index e9c18ec528d..2608acc44e4 100644 --- a/felix/fv/hostendpoints_test.go +++ b/felix/fv/hostendpoints_test.go @@ -12,15 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( "fmt" "os" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -70,6 +68,7 @@ func describeHostEndpointTests(getInfra infrastructure.InfraFactory, allInterfac for ii := range w { wIP := fmt.Sprintf("10.65.%d.2", ii) wName := fmt.Sprintf("w%d", ii) + infrastructure.AssignIP(wName, wIP, tc.Felixes[ii].Hostname, client) w[ii] = workload.Run(tc.Felixes[ii], wName, "default", wIP, "8055", "tcp") w[ii].ConfigureInInfra(infra) @@ -83,33 +82,6 @@ func describeHostEndpointTests(getInfra infrastructure.InfraFactory, allInterfac cc254 = &connectivity.Checker{Protocol: "ip4:254"} }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - if NFTMode() { - logNFTDiags(felix) - } - felix.Exec("iptables-save", "-c") - felix.Exec("ipset", "list") - felix.Exec("ip", "r") - felix.Exec("ip", "a") - } - } - - for _, wl := range w { - wl.Stop() - } - for _, wl := range hostW { - wl.Stop() - } - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) - expectHostToHostTraffic := func() { cc.ExpectSome(tc.Felixes[0], hostW[1]) cc.ExpectSome(tc.Felixes[1], hostW[0]) @@ -610,6 +582,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ with IP forwarding disabled for ii := range w { wIP := fmt.Sprintf("10.65.%d.2", ii) wName := fmt.Sprintf("w%d", ii) + infrastructure.AssignIP(wName, wIP, tc.Felixes[ii].Hostname, client) w[ii] = workload.Run(tc.Felixes[ii], wName, "default", wIP, "8055", "tcp") w[ii].ConfigureInInfra(infra) @@ -627,34 +600,6 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ with IP forwarding disabled cc = &connectivity.Checker{} }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - if NFTMode() { - logNFTDiags(felix) - } - felix.Exec("iptables-save", "-c") - felix.Exec("ipset", "list") - felix.Exec("ip", "r") - felix.Exec("ip", "a") - felix.Exec("sysctl", "-a") - } - } - - for _, wl := range w { - wl.Stop() - } - for _, wl := range hostW { - wl.Stop() - } - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) - if BPFMode() { It("should force IPForward to Enabled", func() { // Our RPF check fails in BPF mode if IP forwarding is disabled diff --git a/felix/fv/infrastructure/cleanup.go b/felix/fv/infrastructure/cleanup.go new file mode 100644 index 00000000000..e21001d3625 --- /dev/null +++ b/felix/fv/infrastructure/cleanup.go @@ -0,0 +1,94 @@ +// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// +// 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 infrastructure + +import ( + "sync" + + "github.com/sirupsen/logrus" + + "github.com/projectcalico/calico/libcalico-go/lib/testutils/stacktrace" +) + +// cleanupStack is a reusable reverse-order cleanup registry. +// It is thread-safe and does not suppress panics from registered functions. +type cleanupStack struct { + mu sync.Mutex + fns []annotatedFunc +} + +type annotatedFunc struct { + caller string + f func() +} + +func (c *cleanupStack) Add(f func()) { + if f == nil { + return + } + c.mu.Lock() + c.fns = append(c.fns, annotatedFunc{ + f: f, + caller: miniStackStrace(), + }) + c.mu.Unlock() +} + +func miniStackStrace() string { + return stacktrace.MiniStackStrace("/infrastructure/cleanup.go") +} + +// Run executes registered functions in reverse order and clears the stack. +// Panics from cleanup functions are allowed to propagate to the caller. +func (c *cleanupStack) Run() { + logrus.Info("Running cleanup stack...") + c.mu.Lock() + fns := c.fns + c.fns = nil + c.mu.Unlock() + + runCleanupStack(fns) +} + +func runCleanupStack(fs []annotatedFunc) { + if len(fs) == 0 { + return + } + + // We want all cleanup functions to run in reverse order, even if there's + // a panic. We also want the panic to propagate so that it causes the test + // to fail. By deferring the first function and then recursing, we get + // both of those properties. + defer func() { + logCtx := logrus.WithField("registeredAt", fs[0].caller) + logCtx.Info("Running cleanup func.") + // We don't want to recover a panic from runCleanupStack so we call the + // func via callFuncLogPanic; this limits the scope of recover call. + callFuncLogPanic(logCtx, fs[0].f) + logCtx.Info("Cleanup func succeeded.") + }() + runCleanupStack(fs[1:]) +} + +func callFuncLogPanic(logCtx *logrus.Entry, theFunc func()) { + defer func() { + if x := recover(); x != nil { + logCtx.WithField("panic", x).Warn("Cleanup func panicked.") + panic(x) + } + }() + + theFunc() +} diff --git a/felix/fv/infrastructure/datastore_describe.go b/felix/fv/infrastructure/datastore_describe.go index 4a2cf61e315..f78bc99dae8 100644 --- a/felix/fv/infrastructure/datastore_describe.go +++ b/felix/fv/infrastructure/datastore_describe.go @@ -18,8 +18,9 @@ import ( "os" "strings" - "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" + "github.com/sirupsen/logrus" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" "github.com/projectcalico/calico/libcalico-go/lib/set" @@ -39,27 +40,51 @@ func DatastoreDescribe(description string, datastores []apiconfig.DatastoreType, ginkgo.Describe(fmt.Sprintf("%s (%s backend)", description, ds), func() { var coreFilesAtStart set.Set[string] + var currentInfra []DatastoreInfra ginkgo.BeforeEach(func() { coreFilesAtStart = readCoreFiles() + currentInfra = nil }) + // Pick the base factory for this datastore, then wrap it to record the created infra. + var baseFactory InfraFactory switch ds { case apiconfig.EtcdV3: - body(createEtcdDatastoreInfra) + baseFactory = createEtcdDatastoreInfra case apiconfig.Kubernetes: - body(createK8sDatastoreInfra) + baseFactory = createK8sDatastoreInfra default: panic(fmt.Errorf("unknown DatastoreType, %s", ds)) } + wrappedFactory := func(opts ...CreateOption) DatastoreInfra { + inf := baseFactory(opts...) + currentInfra = append(currentInfra, inf) + return inf + } + body(wrappedFactory) ginkgo.AfterEach(func() { + // Always stop the infra after each test (collects diags on failure and cleans up). + logrus.WithField("test", ginkgo.CurrentSpecReport().FullText).Info("DatastoreDescribe AfterEach: stopping infrastructure.") + if len(currentInfra) > 0 { + for i := len(currentInfra) - 1; i >= 0; i-- { + if currentInfra[i] != nil { + currentInfra[i].Stop() + } + } + currentInfra = nil + } + }) + + ginkgo.AfterEach(func() { + // Then, perform the core file check. + logrus.WithField("test", ginkgo.CurrentSpecReport().FullText).Info("DatastoreDescribe AfterEach: checking for core files.") afterCoreFiles := readCoreFiles() - coreFilesAtStart.Iter(func(item string) error { + for item := range coreFilesAtStart.All() { afterCoreFiles.Discard(item) - return nil - }) + } if afterCoreFiles.Len() != 0 { - if ginkgo.CurrentGinkgoTestDescription().Failed { + if ginkgo.CurrentSpecReport().Failed() { ginkgo.Fail(fmt.Sprintf("Test FAILED and new core files were detected during tear-down: %v. "+ "Felix must have panicked during the test.", afterCoreFiles.Slice())) return diff --git a/felix/fv/infrastructure/diagnostics.go b/felix/fv/infrastructure/diagnostics.go new file mode 100644 index 00000000000..005d8923fd2 --- /dev/null +++ b/felix/fv/infrastructure/diagnostics.go @@ -0,0 +1,68 @@ +// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// +// 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 infrastructure + +// dumpFelixDiags collects a standard set of diagnostics from a Felix node. +// It is intentionally conservative and conditioned on test modes so it +// can be used universally from DumpErrorData() on failures. +func dumpFelixDiags(f *Felix) { + // Core Linux networking dumps. + f.ExecBestEffort("ip", "link") + f.ExecBestEffort("ip", "addr") + f.ExecBestEffort("ip", "rule", "list") + f.ExecBestEffort("ip", "route", "show", "table", "all") + f.ExecBestEffort("ip", "route", "show", "cached") + f.ExecBestEffort("conntrack", "-L") + + // Table dumps (iptables or nftables) depending on mode. + if NFTMode() { + f.ExecBestEffort("nft", "list", "ruleset") + } else { + f.ExecBestEffort("iptables-save", "-c") + } + + // IPv6-specific diagnostics if IPv6 is enabled for this node. + if f.TopologyOptions.EnableIPv6 { + f.ExecBestEffort("ip", "-6", "link") + f.ExecBestEffort("ip", "-6", "addr") + f.ExecBestEffort("ip", "-6", "rule") + f.ExecBestEffort("ip", "-6", "route", "show", "table", "all") + f.ExecBestEffort("ip", "-6", "route", "show", "cached") + f.ExecBestEffort("ip", "-6", "neigh") + f.ExecBestEffort("conntrack", "-L", "-f", "ipv6") + if !NFTMode() { + f.ExecBestEffort("ip6tables-save", "-c") + } + } + + // BPF-specific diagnostics when running in BPF mode. + if BPFMode() { + // Data-plane maps & state. + f.ExecBestEffort("calico-bpf", "ipsets", "dump") + f.ExecBestEffort("calico-bpf", "routes", "dump") + f.ExecBestEffort("calico-bpf", "nat", "dump") + f.ExecBestEffort("calico-bpf", "conntrack", "dump") + f.ExecBestEffort("calico-bpf", "arp", "dump") + f.ExecBestEffort("calico-bpf", "counters", "dump") + f.ExecBestEffort("calico-bpf", "ifstate", "dump") + // Policy attached to host (best-effort on eth0). + f.ExecBestEffort("calico-bpf", "policy", "dump", "eth0", "all") + if f.TopologyOptions.EnableIPv6 { + // IPv6 route and policy dumps where supported. + f.ExecBestEffort("calico-bpf", "-6", "routes", "dump") + f.ExecBestEffort("calico-bpf", "policy", "-6", "dump", "eth0", "all") + } + } +} diff --git a/felix/fv/infrastructure/ext_client.go b/felix/fv/infrastructure/ext_client.go index 8fa302a2b76..b175e24cc41 100644 --- a/felix/fv/infrastructure/ext_client.go +++ b/felix/fv/infrastructure/ext_client.go @@ -29,11 +29,11 @@ type ExtClientOpts struct { Image string } -func RunExtClient(namePrefix string) *containers.Container { - return RunExtClientWithOpts(namePrefix, ExtClientOpts{}) +func RunExtClient(infra CleanupProvider, namePrefix string) *containers.Container { + return RunExtClientWithOpts(infra, namePrefix, ExtClientOpts{}) } -func RunExtClientWithOpts(namePrefix string, opts ExtClientOpts) *containers.Container { +func RunExtClientWithOpts(infra CleanupProvider, namePrefix string, opts ExtClientOpts) *containers.Container { wd, err := os.Getwd() gomega.Expect(err).NotTo(gomega.HaveOccurred(), "failed to get working directory") @@ -51,6 +51,7 @@ func RunExtClientWithOpts(namePrefix string, opts ExtClientOpts) *containers.Con "-v", wd+"/../bin:/usr/local/bin", // Map in the test-connectivity binary etc. image, "/bin/sh", "-c", "sleep 1000") + infra.AddCleanup(c.Stop) if opts.IPv6Enabled { c.Exec("sysctl", "-w", "net.ipv6.conf.all.disable_ipv6=0") diff --git a/felix/fv/infrastructure/felix.go b/felix/fv/infrastructure/felix.go index 6c9478383b3..bb1ff7054d4 100644 --- a/felix/fv/infrastructure/felix.go +++ b/felix/fv/infrastructure/felix.go @@ -19,6 +19,8 @@ import ( "context" "fmt" "io" + "maps" + "net" "net/http" "os" "path" @@ -29,7 +31,7 @@ import ( "sync/atomic" "time" - "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/v2" //nolint:staticcheck // Ignore ST1001: should not use dot imports . "github.com/onsi/gomega" @@ -78,6 +80,10 @@ type Felix struct { // get assigned to the IPv6 Wireguard tunnel. Filled in by SetExpectedWireguardV6TunnelAddr(). ExpectedWireguardV6TunnelAddr string + // PanicExpected If set to true by the test, disables some diags collection + // on Stop() + PanicExpected bool + // IP of the Typha that this Felix is using (if any). TyphaIP string @@ -92,6 +98,8 @@ type Felix struct { uniqueName string flowServer *local.FlowServer + + infra DatastoreInfra } type workload interface { @@ -241,18 +249,14 @@ func RunFelix(infra DatastoreInfra, id int, options TopologyOptions) *Felix { envVars["DELAY_FELIX_START"] = "true" } - for k, v := range options.ExtraEnvVars { - envVars[k] = v - } + maps.Copy(envVars, options.ExtraEnvVars) for k, v := range envVars { args = append(args, "-e", fmt.Sprintf("%s=%s", k, v)) } // Add in the volumes. - for k, v := range options.ExtraVolumes { - volumes[k] = v - } + maps.Copy(volumes, options.ExtraVolumes) for k, v := range volumes { args = append(args, "-v", fmt.Sprintf("%s:%s", k, v)) } @@ -311,26 +315,37 @@ func RunFelix(infra DatastoreInfra, id int, options TopologyOptions) *Felix { "-P", "FORWARD", "DROP") } - return &Felix{ + f := &Felix{ Container: c, startupDelayed: options.DelayFelixStart, uniqueName: uniqueName, TopologyOptions: options, flowServer: flowServer, + infra: infra, } + // Register this Felix for teardown and diagnostics via infra. + infra.AddCleanup(f.Stop) + infra.RegisterFelix(f) + return f } func (f *Felix) Stop() { if f == nil { return } + if BPFMode() && !f.PanicExpected { + err := f.ExecMayFail("calico-bpf", "connect-time", "clean") + if err != nil { + logrus.WithError(err).Warn("Failed to clean up BPF connect-time state") + } + } if CreateCgroupV2 { _ = f.ExecMayFail("rmdir", path.Join("/run/calico/cgroup/", f.Name)) } f.FlowServerStop() f.Container.Stop() - if ginkgo.CurrentGinkgoTestDescription().Failed { + if ginkgo.CurrentSpecReport().Failed() { Expect(f.DataRaces()).To(BeEmpty(), "Test FAILED and data races were detected in the logs at teardown.") } else { Expect(f.DataRaces()).To(BeEmpty(), "Test PASSED but data races were detected in the logs at teardown.") @@ -406,9 +421,17 @@ func (f *Felix) Ready() (bool, error) { healthAddr = f.TopologyOptions.ExtraEnvVars["FELIX_HEALTHHOST"] } - resp, err := http.Get("http://" + healthAddr + ":9099/readiness") + url := "http://" + net.JoinHostPort(healthAddr, "9099") + "/readiness" + ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) + defer cancel() + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { - logrus.WithError(err).Debug("HTTP GET for readiness failed") + logrus.WithError(err).Error("Forming HTTP request for readiness failed") + return false, err + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + logrus.WithError(err).Warn("HTTP GET for readiness failed") return false, err } ok := resp.StatusCode == http.StatusOK @@ -490,6 +513,10 @@ func (f *Felix) FlowServerReset() { } } +func (f *Felix) AddCleanup(fn func()) { + f.infra.AddCleanup(fn) +} + func (f *Felix) FlowServerAddress() string { if f.flowServer != nil { return f.flowServer.Address() @@ -516,6 +543,7 @@ func (f *Felix) FlowLogsFromLocalSocket() ([]flowlog.FlowLog, error) { if len(flows) == 0 { return nil, fmt.Errorf("no flow log received yet") } + var flogs []flowlog.FlowLog for _, f := range flows { flogs = append(flogs, goldmane.ConvertGoldmaneToFlowlog(types.FlowToProto(f))) @@ -575,8 +603,8 @@ func (f *Felix) BPFIfState(family int) map[string]BPFIfState { states := make(map[string]BPFIfState) - lines := strings.Split(out, "\n") - for _, line := range lines { + lines := strings.SplitSeq(out, "\n") + for line := range lines { match := bpfIfStateRegexp.FindStringSubmatch(line) if len(match) == 0 { continue @@ -635,7 +663,7 @@ func (f *Felix) BPFNumContiguousPolProgramsFn(iface string, ingressOrEgress stri func (f *Felix) BPFNumPolProgramsByName(iface string, ingressOrEgress string, family int) (contiguous, total int) { entryPointIdx := f.BPFPolEntryPointIdx(iface, ingressOrEgress, family) - return f.BPFNumPolProgramsByEntryPoint(entryPointIdx) + return f.BPFNumPolProgramsByEntryPoint(entryPointIdx, ingressOrEgress) } func (f *Felix) BPFPolEntryPointIdx(iface string, ingressOrEgress string, family int) int { @@ -655,20 +683,25 @@ func (f *Felix) BPFPolEntryPointIdx(iface string, ingressOrEgress string, family return entryPointIdx } -func (f *Felix) BPFNumPolProgramsTotalByEntryPointFn(entryPointIdx int) func() (total int) { +func (f *Felix) BPFNumPolProgramsTotalByEntryPointFn(entryPointIdx int, ingressOrEgress string) func() (total int) { return func() (total int) { - _, total = f.BPFNumPolProgramsByEntryPoint(entryPointIdx) + _, total = f.BPFNumPolProgramsByEntryPoint(entryPointIdx, ingressOrEgress) return } } -func (f *Felix) BPFNumPolProgramsByEntryPoint(entryPointIdx int) (contiguous, total int) { +func (f *Felix) BPFNumPolProgramsByEntryPoint(entryPointIdx int, ingressOrEgress string) (contiguous, total int) { gapSeen := false - for i := 0; i < jump.MaxSubPrograms; i++ { + jmpMapName := jump.EgressMapParameters.VersionedName() + if ingressOrEgress == "egress" { + jmpMapName = jump.IngressMapParameters.VersionedName() + } + pinnedMap := "/sys/fs/bpf/tc/globals/" + jmpMapName + for i := range jump.MaxSubPrograms { k := polprog.SubProgramJumpIdx(entryPointIdx, i, jump.TCMaxEntryPoints) out, err := f.ExecOutput( "bpftool", "map", "lookup", - "pinned", "/sys/fs/bpf/tc/globals/cali_jump3", + "pinned", pinnedMap, "key", fmt.Sprintf("%d", k&0xff), fmt.Sprintf("%d", (k>>8)&0xff), @@ -694,8 +727,8 @@ func (f *Felix) IPTablesChains(table string) map[string][]string { out := map[string][]string{} raw, err := f.ExecOutput("iptables-save", "-t", table) Expect(err).NotTo(HaveOccurred()) - lines := strings.Split(raw, "\n") - for _, line := range lines { + lines := strings.SplitSeq(raw, "\n") + for line := range lines { if strings.HasPrefix(line, "#") { // Line is a comment, ignore. continue diff --git a/felix/fv/infrastructure/infra_etcd.go b/felix/fv/infrastructure/infra_etcd.go index 828c27f34e8..02fae0c7619 100644 --- a/felix/fv/infrastructure/infra_etcd.go +++ b/felix/fv/infrastructure/infra_etcd.go @@ -21,59 +21,95 @@ import ( "net" "os" + "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" "github.com/projectcalico/calico/felix/fv/containers" "github.com/projectcalico/calico/felix/fv/utils" - libapi "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/options" ) type EtcdDatastoreInfra struct { - etcdContainer *containers.Container + EtcdContainer *containers.Container bpfLog *containers.Container client client.Interface Endpoint string BadEndpoint string + + cleanups cleanupStack + felixes []*Felix + + bpfLogByteLimit int } func createEtcdDatastoreInfra(opts ...CreateOption) DatastoreInfra { - infra, err := GetEtcdDatastoreInfra() + infra, err := GetEtcdDatastoreInfra(opts...) gomega.Expect(err).NotTo(gomega.HaveOccurred()) return infra } -func GetEtcdDatastoreInfra() (*EtcdDatastoreInfra, error) { +func GetEtcdDatastoreInfra(opts ...CreateOption) (_ *EtcdDatastoreInfra, err error) { eds := &EtcdDatastoreInfra{} + defer func() { + if err != nil { + log.Warn("Failed to get etcd datastore infra, running cleanups.") + eds.Stop() + } + }() + + for _, opt := range opts { + opt(eds) + } // Start etcd. - eds.etcdContainer = RunEtcd() - if eds.etcdContainer == nil { + eds.EtcdContainer = RunEtcd() + if eds.EtcdContainer == nil { return nil, errors.New("failed to create etcd container") } + // Ensure etcd is stopped via cleanup stack. + eds.AddCleanup(func() { + if eds.EtcdContainer != nil { + eds.EtcdContainer.StopLogs() + eds.EtcdContainer.Stop() + } + }) // In BPF mode, start BPF logging. if os.Getenv("FELIX_FV_ENABLE_BPF") == "true" { - eds.bpfLog = RunBPFLog() + eds.bpfLog = RunBPFLog(eds, eds.bpfLogByteLimit) } - eds.Endpoint = fmt.Sprintf("https://%s:6443", eds.etcdContainer.IP) - eds.BadEndpoint = fmt.Sprintf("https://%s:1234", eds.etcdContainer.IP) + // Ensure client is closed via cleanup stack (if it was created). + eds.AddCleanup(func() { + if eds.client != nil { + if err := eds.client.Close(); err != nil { + log.WithError(err).Warn("Client Close() returned an error. Ignoring.") + } + } + }) + + eds.Endpoint = fmt.Sprintf("https://%s:6443", eds.EtcdContainer.IP) + eds.BadEndpoint = fmt.Sprintf("https://%s:1234", eds.EtcdContainer.IP) return eds, nil } +func (eds *EtcdDatastoreInfra) setBPFLogByteLimit(limit int) { + eds.bpfLogByteLimit = limit +} + func (eds *EtcdDatastoreInfra) GetDockerArgs() []string { return []string{ "-e", "CALICO_DATASTORE_TYPE=etcdv3", "-e", "FELIX_DATASTORETYPE=etcdv3", "-e", "TYPHA_DATASTORETYPE=etcdv3", - "-e", "TYPHA_ETCDENDPOINTS=http://" + eds.etcdContainer.IP + ":2379", - "-e", "CALICO_ETCD_ENDPOINTS=http://" + eds.etcdContainer.IP + ":2379", + "-e", "TYPHA_ETCDENDPOINTS=http://" + eds.EtcdContainer.IP + ":2379", + "-e", "CALICO_ETCD_ENDPOINTS=http://" + eds.EtcdContainer.IP + ":2379", } } @@ -82,18 +118,22 @@ func (eds *EtcdDatastoreInfra) GetBadEndpointDockerArgs() []string { "-e", "CALICO_DATASTORE_TYPE=etcdv3", "-e", "FELIX_DATASTORETYPE=etcdv3", "-e", "TYPHA_DATASTORETYPE=etcdv3", - "-e", "TYPHA_ETCDENDPOINTS=http://" + eds.etcdContainer.IP + ":2379", - "-e", "CALICO_ETCD_ENDPOINTS=http://" + eds.etcdContainer.IP + ":1234", + "-e", "TYPHA_ETCDENDPOINTS=http://" + eds.EtcdContainer.IP + ":2379", + "-e", "CALICO_ETCD_ENDPOINTS=http://" + eds.EtcdContainer.IP + ":1234", } } func (eds *EtcdDatastoreInfra) GetCalicoClient() client.Interface { if eds.client == nil { - eds.client = utils.GetEtcdClient(eds.etcdContainer.IP) + eds.client = utils.GetEtcdClient(eds.EtcdContainer.IP) } return eds.client } +func (eds *EtcdDatastoreInfra) UseProjectCalicoV3API() bool { + return false +} + func (eds *EtcdDatastoreInfra) GetClusterGUID() string { ci, err := eds.GetCalicoClient().ClusterInformation().Get( context.Background(), @@ -137,7 +177,7 @@ func (eds *EtcdDatastoreInfra) RemoveNodeAddresses(felix *Felix) { if err != nil { panic(err) } - node.Spec.Addresses = []libapi.NodeAddress{} + node.Spec.Addresses = []internalapi.NodeAddress{} _, err = eds.GetCalicoClient().Nodes().Update(utils.Ctx, node, utils.NoOptions) if err != nil { panic(err) @@ -145,12 +185,12 @@ func (eds *EtcdDatastoreInfra) RemoveNodeAddresses(felix *Felix) { } func (eds *EtcdDatastoreInfra) AddNode(felix *Felix, v4CIDR *net.IPNet, v6CIDR *net.IPNet, idx int, needBGP bool) { - felixNode := libapi.NewNode() + felixNode := internalapi.NewNode() felixNode.Name = felix.Hostname felixNode.Spec.IPv4VXLANTunnelAddr = felix.ExpectedVXLANTunnelAddr felixNode.Spec.IPv6VXLANTunnelAddr = felix.ExpectedVXLANV6TunnelAddr if needBGP { - felixNode.Spec.BGP = &libapi.NodeBGPSpec{ + felixNode.Spec.BGP = &internalapi.NodeBGPSpec{ IPv4Address: fmt.Sprintf("%s/%s", felix.IP, felix.IPPrefix), IPv4IPIPTunnelAddr: felix.ExpectedIPIPTunnelAddr, } @@ -158,10 +198,10 @@ func (eds *EtcdDatastoreInfra) AddNode(felix *Felix, v4CIDR *net.IPNet, v6CIDR * felixNode.Spec.BGP.IPv6Address = fmt.Sprintf("%s/%s", felix.IPv6, felix.IPv6Prefix) } } - nodeAddress := libapi.NodeAddress{Address: felix.IP, Type: libapi.InternalIP} + nodeAddress := internalapi.NodeAddress{Address: felix.IP, Type: internalapi.InternalIP} felixNode.Spec.Addresses = append(felixNode.Spec.Addresses, nodeAddress) if len(felix.IPv6) > 0 { - nodeAddressV6 := libapi.NodeAddress{Address: felix.IPv6, Type: libapi.InternalIP} + nodeAddressV6 := internalapi.NodeAddress{Address: felix.IPv6, Type: internalapi.InternalIP} felixNode.Spec.Addresses = append(felixNode.Spec.Addresses, nodeAddressV6) } gomega.Eventually(func() error { @@ -173,7 +213,7 @@ func (eds *EtcdDatastoreInfra) AddNode(felix *Felix, v4CIDR *net.IPNet, v6CIDR * }, "10s", "500ms").ShouldNot(gomega.HaveOccurred()) } -func (eds *EtcdDatastoreInfra) AddWorkload(wep *libapi.WorkloadEndpoint) (*libapi.WorkloadEndpoint, error) { +func (eds *EtcdDatastoreInfra) AddWorkload(wep *internalapi.WorkloadEndpoint) (*internalapi.WorkloadEndpoint, error) { return eds.GetCalicoClient().WorkloadEndpoints().Create(utils.Ctx, wep, utils.NoOptions) } @@ -182,7 +222,7 @@ func (eds *EtcdDatastoreInfra) RemoveWorkload(ns string, name string) error { return err } -func (eds *EtcdDatastoreInfra) UpdateWorkload(wep *libapi.WorkloadEndpoint) (*libapi.WorkloadEndpoint, error) { +func (eds *EtcdDatastoreInfra) UpdateWorkload(wep *internalapi.WorkloadEndpoint) (*internalapi.WorkloadEndpoint, error) { return eds.GetCalicoClient().WorkloadEndpoints().Update(utils.Ctx, wep, options.SetOptions{}) } @@ -195,7 +235,7 @@ func (eds *EtcdDatastoreInfra) AddAllowToDatastore(selector string) error { policy.Spec.Egress = []api.Rule{{ Action: api.Allow, Destination: api.EntityRule{ - Nets: []string{eds.etcdContainer.IP + "/32"}, + Nets: []string{eds.EtcdContainer.IP + "/32"}, }, }} _, err := eds.GetCalicoClient().GlobalNetworkPolicies().Create(utils.Ctx, policy, utils.NoOptions) @@ -218,14 +258,35 @@ func (eds *EtcdDatastoreInfra) AddDefaultDeny() error { } func (eds *EtcdDatastoreInfra) DumpErrorData() { - eds.etcdContainer.Exec("etcdctl", "get", "/", "--prefix", "--keys-only") + // Per-Felix diagnostics first for context. + for _, f := range eds.felixes { + if f != nil { + dumpFelixDiags(f) + } + } + // Etcd datastore contents (keys only) for quick overview. + eds.EtcdContainer.Exec("etcdctl", "get", "/", "--prefix", "--keys-only") } func (eds *EtcdDatastoreInfra) Stop() { - eds.bpfLog.StopLogs() - eds.etcdContainer.StopLogs() - eds.bpfLog.Stop() - err := eds.client.Close() - log.WithError(err).Warn("Client Close() returned an error. Ignoring.") - eds.etcdContainer.Stop() + // Collect diagnostics first, before tearing anything down. + log.Info("Stopping etcd infra.") + if ginkgo.CurrentSpecReport().Failed() { + // Queue up the diags dump so that the cleanupStack will handle any + // panic from it. + eds.AddCleanup(eds.DumpErrorData) + } + // Run registered teardowns (reverse order). Do not suppress panics. + eds.cleanups.Run() +} + +func (eds *EtcdDatastoreInfra) AddCleanup(f func()) { + eds.cleanups.Add(f) +} + +func (eds *EtcdDatastoreInfra) RegisterFelix(f *Felix) { + if f == nil { + return + } + eds.felixes = append(eds.felixes, f) } diff --git a/felix/fv/infrastructure/infra_k8s.go b/felix/fv/infrastructure/infra_k8s.go index 04def099390..f4d31f0c0fe 100644 --- a/felix/fv/infrastructure/infra_k8s.go +++ b/felix/fv/infrastructure/infra_k8s.go @@ -27,7 +27,7 @@ import ( "time" "github.com/davecgh/go-spew/spew" - "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" @@ -40,8 +40,9 @@ import ( "github.com/projectcalico/calico/felix/fv/containers" "github.com/projectcalico/calico/felix/fv/utils" + "github.com/projectcalico/calico/felix/ip" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapi "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" @@ -50,6 +51,8 @@ import ( "github.com/projectcalico/calico/libcalico-go/lib/options" ) +const DefaultBPFLogByteLimit = 64 * 1024 * 1024 + type K8sDatastoreInfra struct { etcdContainer *containers.Container bpfLog *containers.Container @@ -76,6 +79,10 @@ type K8sDatastoreInfra struct { serviceClusterIPRange string apiServerBindIP string ipMask string + + cleanups cleanupStack + felixes []*Felix + bpfLogByteLimit int } var ( @@ -124,25 +131,19 @@ func TearDownK8sInfra(kds *K8sDatastoreInfra) { } if kds.etcdContainer != nil { - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { kds.etcdContainer.Stop() - }() + }) } if kds.k8sApiContainer != nil { - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { kds.k8sApiContainer.Stop() - }() + }) } if kds.k8sControllerManager != nil { - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { kds.k8sControllerManager.Stop() - }() + }) } wg.Wait() log.Info("TearDownK8sInfra done") @@ -175,6 +176,7 @@ func GetK8sDatastoreInfra(index K8sInfraIndex, opts ...CreateOption) (*K8sDatast resetAll := temp.ipv6 != kds.ipv6 || temp.dualStack != kds.dualStack if !resetAll { + kds.bpfLogByteLimit = temp.bpfLogByteLimit kds.EnsureReady() kds.PerTestSetup(index) return kds, nil @@ -193,20 +195,35 @@ func GetK8sDatastoreInfra(index K8sInfraIndex, opts ...CreateOption) (*K8sDatast return K8sInfra[index], err } +func (kds *K8sDatastoreInfra) setBPFLogByteLimit(limit int) { + kds.bpfLogByteLimit = limit +} + func (kds *K8sDatastoreInfra) PerTestSetup(index K8sInfraIndex) { if os.Getenv("FELIX_FV_ENABLE_BPF") == "true" && index == K8SInfraLocalCluster { - kds.bpfLog = RunBPFLog() + kds.bpfLog = RunBPFLog(kds, kds.bpfLogByteLimit) } - K8sInfra[index].runningTest = ginkgo.CurrentGinkgoTestDescription().FullTestText + K8sInfra[index].runningTest = ginkgo.CurrentSpecReport().FullText() } -func RunBPFLog() *containers.Container { - return containers.Run("bpf-log", +type CleanupProvider interface { + AddCleanup(func()) +} + +func RunBPFLog(cp CleanupProvider, byteLimit int) *containers.Container { + if byteLimit == 0 { + byteLimit = DefaultBPFLogByteLimit + } + c := containers.Run("bpf-log", containers.RunOpts{ AutoRemove: true, IgnoreEmptyLines: true, + LogLimitBytes: byteLimit, }, "--privileged", utils.Config.FelixImage, "/usr/bin/bpftool", "prog", "tracelog") + cp.AddCleanup(c.Stop) + cp.AddCleanup(c.StopLogs) + return c } func (kds *K8sDatastoreInfra) runK8sApiserver() { @@ -215,9 +232,11 @@ func (kds *K8sDatastoreInfra) runK8sApiserver() { if err != nil { panic(err) } + crdPath := os.Getenv("CALICO_CRD_PATH") + args := []string{ "-v", os.Getenv("CERTS_PATH") + ":/home/user/certs", // Mount in location of certificates. - "-v", pwd + "/../../libcalico-go/config/crd:/crds", // Mount in location of CRDs. + "-v", fmt.Sprintf("%s/../../%s:/crds", pwd, crdPath), // Mount in location of CRDs. utils.Config.K8sImage, "kube-apiserver", "--v=0", @@ -291,14 +310,20 @@ func (kds *K8sDatastoreInfra) runK8sControllerManager() { kds.k8sControllerManager = c } -func setupK8sDatastoreInfra(opts ...CreateOption) (*K8sDatastoreInfra, error) { +func setupK8sDatastoreInfra(opts ...CreateOption) (kds *K8sDatastoreInfra, err error) { log.Info("Starting Kubernetes infrastructure") log.Info("Starting etcd") - kds := &K8sDatastoreInfra{ + kds = &K8sDatastoreInfra{ serviceClusterIPRange: "10.101.0.0/16", ipMask: "/32", } + defer func() { + if err != nil { + log.Warn("setupK8sDatastoreInfra about to fail, tearing down the infra.") + TearDownK8sInfra(kds) + } + }() for _, o := range opts { o(kds) @@ -325,7 +350,6 @@ func setupK8sDatastoreInfra(opts ...CreateOption) (*K8sDatastoreInfra, error) { kds.runK8sApiserver() if kds.k8sApiContainer == nil { - TearDownK8sInfra(kds) return nil, errors.New("failed to create k8s API server container") } @@ -345,7 +369,6 @@ func setupK8sDatastoreInfra(opts ...CreateOption) (*K8sDatastoreInfra, error) { } if time.Since(start) > 120*time.Second { log.WithError(err).Error("Failed to create k8s client.") - TearDownK8sInfra(kds) return nil, err } time.Sleep(100 * time.Millisecond) @@ -376,7 +399,6 @@ func setupK8sDatastoreInfra(opts ...CreateOption) (*K8sDatastoreInfra, error) { } if time.Since(start) > 90*time.Second { log.WithError(err).Error("Failed to install role binding") - TearDownK8sInfra(kds) return nil, err } time.Sleep(100 * time.Millisecond) @@ -391,7 +413,6 @@ func setupK8sDatastoreInfra(opts ...CreateOption) (*K8sDatastoreInfra, error) { } if time.Since(start) > 15*time.Second { log.WithError(err).Error("Failed to list namespaces.") - TearDownK8sInfra(kds) return nil, err } time.Sleep(500 * time.Millisecond) @@ -401,16 +422,14 @@ func setupK8sDatastoreInfra(opts ...CreateOption) (*K8sDatastoreInfra, error) { log.Info("Starting controller manager.") kds.runK8sControllerManager() if kds.k8sControllerManager == nil { - TearDownK8sInfra(kds) return nil, errors.New("failed to create k8s controller manager container") } log.Info("Started controller manager.") // Apply CRDs (mounted as docker volume) - err := kds.k8sApiContainer.ExecMayFail("kubectl", "--kubeconfig=/home/user/certs/kubeconfig", "apply", "-f", "/crds/") + err = kds.k8sApiContainer.ExecMayFail("kubectl", "--kubeconfig=/home/user/certs/kubeconfig", "apply", "-f", "/crds/") if err != nil { - TearDownK8sInfra(kds) return nil, err } @@ -419,9 +438,10 @@ func setupK8sDatastoreInfra(opts ...CreateOption) (*K8sDatastoreInfra, error) { kds.BadEndpoint = fmt.Sprintf("https://%s:1234", kds.containerGetIPForURL(kds.k8sApiContainer)) start = time.Now() + groupVersion := os.Getenv("CALICO_API_GROUP") for { var resp *http.Response - resp, err = insecureHTTPClient.Get(kds.Endpoint + "/apis/crd.projectcalico.org/v1/felixconfigurations") + resp, err = insecureHTTPClient.Get(fmt.Sprintf("%s/apis/%s/felixconfigurations", kds.Endpoint, groupVersion)) if resp.StatusCode != 200 { err = fmt.Errorf("bad status (%v) for CRD GET request", resp.StatusCode) } @@ -434,7 +454,6 @@ func setupK8sDatastoreInfra(opts ...CreateOption) (*K8sDatastoreInfra, error) { } if time.Since(start) > 120*time.Second { log.WithError(err).Error("API server is not responding to requests") - TearDownK8sInfra(kds) return nil, err } time.Sleep(100 * time.Millisecond) @@ -445,21 +464,49 @@ func setupK8sDatastoreInfra(opts ...CreateOption) (*K8sDatastoreInfra, error) { kds.CertFileName = "/tmp/" + kds.k8sApiContainer.Name + ".crt" start = time.Now() for { + // Make sure any retry is clean. + _ = os.Remove(kds.CertFileName) + cmd := utils.Command("docker", "cp", kds.k8sApiContainer.Name+":/home/user/certs/kubernetes.pem", kds.CertFileName, ) err = cmd.Run() - if err == nil { - break + if err != nil { + if time.Since(start) > 120*time.Second { + log.WithError(err).Error("Failed to get API server cert") + return nil, err + } + time.Sleep(100 * time.Millisecond) + continue } - if time.Since(start) > 120*time.Second { - log.WithError(err).Error("Failed to get API server cert") - TearDownK8sInfra(kds) - return nil, err + // Sometimes the very first felix container that we start fails to + // mount this file. Double check that it is there and we can read it. + if _, err := os.Stat(kds.CertFileName); err != nil { + log.WithError(err).Error("Failed to stat API server cert that we just copied?!") + if time.Since(start) > 120*time.Second { + return nil, err + } + time.Sleep(100 * time.Millisecond) + continue } - time.Sleep(100 * time.Millisecond) + if f, err := os.Open(kds.CertFileName); err != nil { + log.WithError(err).Error("Failed to open API server cert that we just copied?!") + if time.Since(start) > 120*time.Second { + return nil, err + } + time.Sleep(100 * time.Millisecond) + continue + } else { + err := f.Sync() + if err != nil { + log.WithError(err).Error("Failed to sync API server cert that we just copied?!") + } + _ = f.Close() + } + break } + log.Info("Got API server cert.") start = time.Now() for { @@ -470,6 +517,7 @@ func setupK8sDatastoreInfra(opts ...CreateOption) (*K8sDatastoreInfra, error) { K8sAPIEndpoint: kds.Endpoint, K8sInsecureSkipTLSVerify: true, K8sClientQPS: 100, + CalicoAPIGroup: os.Getenv("CALICO_API_GROUP"), }, }, }) @@ -487,7 +535,6 @@ func setupK8sDatastoreInfra(opts ...CreateOption) (*K8sDatastoreInfra, error) { } if time.Since(start) > 120*time.Second && err != nil { log.WithError(err).Error("Failed to initialise calico client") - TearDownK8sInfra(kds) return nil, err } time.Sleep(100 * time.Millisecond) @@ -502,7 +549,6 @@ func setupK8sDatastoreInfra(opts ...CreateOption) (*K8sDatastoreInfra, error) { } if time.Since(start) > 20*time.Second { log.WithError(err).Error("Failed to get default service account.") - TearDownK8sInfra(kds) return nil, err } time.Sleep(100 * time.Millisecond) @@ -548,7 +594,26 @@ func (kds *K8sDatastoreInfra) Stop() { kds.needsCleanup = true kds.runningTest = "" - kds.bpfLog.Stop() + // We do run the per-test cleanup stack, this tears down the resources that + // the test created. + if ginkgo.CurrentSpecReport().Failed() { + // Queue up the diags dump so that the cleanupStack will handle any + // panic from it. + kds.AddCleanup(kds.DumpErrorData) + } + // Run registered teardowns (reverse order). Do not suppress panics. + defer kds.cleanups.Run() +} + +func (kds *K8sDatastoreInfra) AddCleanup(f func()) { + kds.cleanups.Add(f) +} + +func (kds *K8sDatastoreInfra) RegisterFelix(f *Felix) { + if f == nil { + return + } + kds.felixes = append(kds.felixes, f) } type cleanupFunc func(clientset *kubernetes.Clientset, calicoClient client.Interface) @@ -570,6 +635,7 @@ func (kds *K8sDatastoreInfra) CleanUp() { cleanupAllTiers, cleanupAllHostEndpoints, cleanupAllNetworkSets, + cleanupAllGlobalNetworkSets, cleanupAllFelixConfigurations, cleanupAllServices, } @@ -611,7 +677,6 @@ func (kds *K8sDatastoreInfra) GetDockerArgs() []string { "-e", "K8S_API_ENDPOINT=" + kds.Endpoint, "-e", "KUBERNETES_MASTER=" + kds.Endpoint, "-e", "K8S_INSECURE_SKIP_TLS_VERIFY=true", - "-v", kds.CertFileName + ":/tmp/apiserver.crt", } } @@ -622,7 +687,6 @@ func (kds *K8sDatastoreInfra) GetBadEndpointDockerArgs() []string { "-e", "TYPHA_DATASTORETYPE=kubernetes", "-e", "K8S_API_ENDPOINT=" + kds.BadEndpoint, "-e", "K8S_INSECURE_SKIP_TLS_VERIFY=true", - "-v", kds.CertFileName + ":/tmp/apiserver.crt", } } @@ -630,6 +694,10 @@ func (kds *K8sDatastoreInfra) GetCalicoClient() client.Interface { return kds.calicoClient } +func (kds *K8sDatastoreInfra) UseProjectCalicoV3API() bool { + return os.Getenv("CALICO_API_GROUP") == "projectcalico.org/v3" +} + func (kds *K8sDatastoreInfra) GetClusterGUID() string { ci, err := kds.GetCalicoClient().ClusterInformation().Get( context.Background(), @@ -708,7 +776,10 @@ func (kds *K8sDatastoreInfra) AddNode(felix *Felix, v4CIDR *net.IPNet, v6CIDR *n } if len(felix.IPv6) > 0 && v6CIDR != nil { nodeIn.Annotations["projectcalico.org/IPv6Address"] = fmt.Sprintf("%s/%s", felix.IPv6, felix.IPv6Prefix) - nodeIn.Spec.PodCIDRs = append(nodeIn.Spec.PodCIDRs, fmt.Sprintf("%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%d:0/96", v6CIDR.IP[0], v6CIDR.IP[1], v6CIDR.IP[2], v6CIDR.IP[3], v6CIDR.IP[4], v6CIDR.IP[5], v6CIDR.IP[6], v6CIDR.IP[7], v6CIDR.IP[8], v6CIDR.IP[9], v6CIDR.IP[10], v6CIDR.IP[11], idx)) + v6CIDR := fmt.Sprintf("%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%04x::/96", v6CIDR.IP[0], v6CIDR.IP[1], v6CIDR.IP[2], v6CIDR.IP[3], v6CIDR.IP[4], v6CIDR.IP[5], v6CIDR.IP[6], v6CIDR.IP[7], v6CIDR.IP[8], v6CIDR.IP[9], idx) + // Put the CIDR into canonical format, as required by k8s validation. + v6CIDR = ip.MustParseCIDROrIP(v6CIDR).String() + nodeIn.Spec.PodCIDRs = append(nodeIn.Spec.PodCIDRs, v6CIDR) nodeIn.Status.Addresses = append(nodeIn.Status.Addresses, v1.NodeAddress{ Address: felix.IPv6, Type: v1.NodeInternalIP, @@ -783,7 +854,7 @@ func (kds *K8sDatastoreInfra) RemoveWorkload(ns, name string) error { return err } -func (kds *K8sDatastoreInfra) AddWorkload(wep *libapi.WorkloadEndpoint) (*libapi.WorkloadEndpoint, error) { +func (kds *K8sDatastoreInfra) AddWorkload(wep *internalapi.WorkloadEndpoint) (*internalapi.WorkloadEndpoint, error) { desiredStatus := getPodStatusFromWep(wep) podIn := &v1.Pod{ ObjectMeta: metav1.ObjectMeta{Name: wep.Spec.Workload, Namespace: wep.Namespace}, @@ -828,7 +899,7 @@ func (kds *K8sDatastoreInfra) AddWorkload(wep *libapi.WorkloadEndpoint) (*libapi return kds.calicoClient.WorkloadEndpoints().Get(context.Background(), wep.Namespace, name, options.GetOptions{}) } -func (kds *K8sDatastoreInfra) UpdateWorkload(wep *libapi.WorkloadEndpoint) (*libapi.WorkloadEndpoint, error) { +func (kds *K8sDatastoreInfra) UpdateWorkload(wep *internalapi.WorkloadEndpoint) (*internalapi.WorkloadEndpoint, error) { log.WithField("wep", wep).Debug("Updating Pod for workload (labels, annotations and status only)") podIn, err := kds.K8sClient.CoreV1().Pods(wep.Namespace).Get(context.Background(), wep.Spec.Workload, metav1.GetOptions{}) if err != nil { @@ -875,14 +946,20 @@ func (kds *K8sDatastoreInfra) AddDefaultDeny() error { policy := api.NewNetworkPolicy() policy.Name = "deny-all" policy.Namespace = "default" - policy.Spec.Ingress = []api.Rule{{Action: api.Deny}} - policy.Spec.Egress = []api.Rule{{Action: api.Deny}} policy.Spec.Selector = "all()" + policy.Spec.Types = []api.PolicyType{api.PolicyTypeIngress, api.PolicyTypeEgress} _, err := kds.calicoClient.NetworkPolicies().Create(utils.Ctx, policy, utils.NoOptions) return err } func (kds *K8sDatastoreInfra) DumpErrorData() { + // Per-Felix diagnostics first for context. + for _, f := range kds.felixes { + if f != nil { + dumpFelixDiags(f) + } + } + nsList, err := kds.K8sClient.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{}) if err == nil { log.Info("DIAGS: Kubernetes Namespaces:") @@ -1155,7 +1232,7 @@ func cleanupAllTiers(clientset *kubernetes.Clientset, client client.Interface) { } log.WithField("count", len(tiers.Items)).Info("Tiers present") for _, tier := range tiers.Items { - if tier.Name == names.DefaultTierName || tier.Name == names.AdminNetworkPolicyTierName { + if names.TierIsStatic(tier.Name) { continue } @@ -1176,12 +1253,29 @@ func cleanupAllNetworkSets(clientset *kubernetes.Clientset, client client.Interf } log.WithField("count", len(ns.Items)).Info("networksets present") for _, n := range ns.Items { - _, err = client.HostEndpoints().Delete(ctx, n.Name, options.DeleteOptions{}) + _, err = client.NetworkSets().Delete(ctx, n.Namespace, n.Name, options.DeleteOptions{}) + if err != nil { + panic(err) + } + } + log.Info("Cleaned up networksets") +} + +func cleanupAllGlobalNetworkSets(clientset *kubernetes.Clientset, client client.Interface) { + log.Info("Cleaning up global network sets") + ctx := context.Background() + gns, err := client.GlobalNetworkSets().List(ctx, options.ListOptions{}) + if err != nil { + panic(err) + } + log.WithField("count", len(gns.Items)).Info("global networksets present") + for _, gn := range gns.Items { + _, err = client.GlobalNetworkSets().Delete(ctx, gn.Name, options.DeleteOptions{}) if err != nil { panic(err) } } - log.Info("Cleaned up host networksets") + log.Info("Cleaned up global network sets") } func cleanupAllHostEndpoints(clientset *kubernetes.Clientset, client client.Interface) { @@ -1241,17 +1335,17 @@ func cleanupAllServices(clientset *kubernetes.Clientset, calicoClient client.Int panic(err) } } - endpointsInterface := coreV1.Endpoints(ns.Name) - endpoints, err := endpointsInterface.List(context.Background(), metav1.ListOptions{}) + endpointSliceInterface := clientset.DiscoveryV1().EndpointSlices(ns.Name) + endpointSlices, err := endpointSliceInterface.List(context.Background(), metav1.ListOptions{}) if err != nil { panic(err) } - for _, ep := range endpoints.Items { - if ep.Name == "kubernetes" { + for _, ep := range endpointSlices.Items { + if ep.Labels["kubernetes.io/service-name"] == "kubernetes" { // Skip cleaning up the Kubernetes API service. continue } - err := endpointsInterface.Delete(context.Background(), ep.Name, metav1.DeleteOptions{}) + err := endpointSliceInterface.Delete(context.Background(), ep.Name, metav1.DeleteOptions{}) if err != nil && !strings.Contains(err.Error(), "not found") { panic(err) } @@ -1295,10 +1389,11 @@ func K8sWithDualStack() CreateOption { } } -func getPodStatusFromWep(wep *libapi.WorkloadEndpoint) v1.PodStatus { +func getPodStatusFromWep(wep *internalapi.WorkloadEndpoint) v1.PodStatus { podIPs := []v1.PodIP{} for _, ipnet := range wep.Spec.IPNetworks { podIP := strings.Split(ipnet, "/")[0] + podIP = net.ParseIP(podIP).String() // Normalise the IP. podIPs = append(podIPs, v1.PodIP{IP: podIP}) } podStatus := v1.PodStatus{ @@ -1319,7 +1414,7 @@ func getPodStatusFromWep(wep *libapi.WorkloadEndpoint) v1.PodStatus { return podStatus } -func updatePodLabelsAndAnnotations(wep *libapi.WorkloadEndpoint, pod *v1.Pod) *v1.Pod { +func updatePodLabelsAndAnnotations(wep *internalapi.WorkloadEndpoint, pod *v1.Pod) *v1.Pod { if wep.Labels != nil { pod.Labels = wep.Labels } diff --git a/felix/fv/infrastructure/infrastructure.go b/felix/fv/infrastructure/infrastructure.go index 59873ba78f2..e461a816d51 100644 --- a/felix/fv/infrastructure/infrastructure.go +++ b/felix/fv/infrastructure/infrastructure.go @@ -21,13 +21,17 @@ import ( api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/calico/felix/fv/utils" - libapi "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" ) // DatastoreInfra is an interface that is to be used to abstract away // the datastore being used and the functions that are datastore specific type DatastoreInfra interface { + // setBPFLogByteLimit is called before the infra is started by the + // WithBPFLogByteLimit option. + setBPFLogByteLimit(limit int) + // GetDockerArgs returns a string slice of args to be passed to the docker // run command when starting Typha or Felix. It includes // CALICO_DATASTORE_TYPE, FELIX_DATASTORETYPE, an appropriate endpoint, @@ -40,6 +44,11 @@ type DatastoreInfra interface { // GetCalicoClient will return a client.Interface configured to access // the datastore. GetCalicoClient() client.Interface + + // UseProjectCalicoV3API returns true if the datastore should be accessed using the v3 CRD API instead + // of the crd.projectcalico.org API group. + UseProjectCalicoV3API() bool + // GetClusterGUID will return the cluster GUID. GetClusterGUID() string // SetExpectedIPIPTunnelAddr will set the Felix object's @@ -70,14 +79,14 @@ type DatastoreInfra interface { AddNode(felix *Felix, v4CIDR *net.IPNet, v6CIDR *net.IPNet, idx int, needBGP bool) // AddWorkload will take the appropriate steps to create a workload in the // datastore with the passed in wep values. If this succeeds then the - // *libapi.WorkloadEndpoint will be returned, otherwise an error will be + // *internalapi.WorkloadEndpoint will be returned, otherwise an error will be // returned. - AddWorkload(wep *libapi.WorkloadEndpoint) (*libapi.WorkloadEndpoint, error) + AddWorkload(wep *internalapi.WorkloadEndpoint) (*internalapi.WorkloadEndpoint, error) // RemoveWorkload reverses the effect of AddWorkload. RemoveWorkload(ns string, name string) error // UpdateWorkload updates a workload in the infra. On kdd the workload is // removed then added. - UpdateWorkload(wep *libapi.WorkloadEndpoint) (*libapi.WorkloadEndpoint, error) + UpdateWorkload(wep *internalapi.WorkloadEndpoint) (*internalapi.WorkloadEndpoint, error) // AddDefaultAllow will ensure that the datastore is configured so that // the default profile/namespace will allow traffic. Returns the name of the // default profile. @@ -93,6 +102,10 @@ type DatastoreInfra interface { // occurs. DumpErrorData() + // AddCleanup registers a function to be run during Stop(), in reverse order of registration. + AddCleanup(func()) + // RegisterFelix records a Felix node to include in diagnostics. + RegisterFelix(*Felix) // Stop cleans up anything necessary in preparation for the end of the test. Stop() } @@ -113,3 +126,9 @@ func CreateDefaultProfile(c client.Interface, name string, labels map[string]str } type CreateOption func(DatastoreInfra) + +func WithBPFLogByteLimit(limit int) CreateOption { + return func(di DatastoreInfra) { + di.setBPFLogByteLimit(limit) + } +} diff --git a/libcalico-go/lib/backend/model/kubeadminnetworkpolicy.go b/felix/fv/infrastructure/modes.go similarity index 69% rename from libcalico-go/lib/backend/model/kubeadminnetworkpolicy.go rename to felix/fv/infrastructure/modes.go index 913e0dc3efb..3365f822fc9 100644 --- a/libcalico-go/lib/backend/model/kubeadminnetworkpolicy.go +++ b/felix/fv/infrastructure/modes.go @@ -1,5 +1,5 @@ -// Copyright (c) 2024 Tigera, Inc. All rights reserved. - +// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// // 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 @@ -12,9 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -package model +package infrastructure + +import "os" -const ( - KindKubernetesAdminNetworkPolicy = "KubernetesAdminNetworkPolicy" - KindKubernetesBaselineAdminNetworkPolicy = "KubernetesBaselineAdminNetworkPolicy" -) +// NFTMode returns true if FV tests are configured to use nftables backend. +func NFTMode() bool { + return os.Getenv("FELIX_FV_NFTABLES") == "Enabled" +} diff --git a/felix/fv/infrastructure/topology.go b/felix/fv/infrastructure/topology.go index b7654cec190..87eb8b4112f 100644 --- a/felix/fv/infrastructure/topology.go +++ b/felix/fv/infrastructure/topology.go @@ -17,13 +17,14 @@ package infrastructure import ( "context" "fmt" + "maps" "net" "os" "regexp" "sync" "time" - "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/v2" //nolint:staticcheck // Ignore ST1001: should not use dot imports . "github.com/onsi/gomega" @@ -31,9 +32,10 @@ import ( log "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/projectcalico/calico/felix/fv/containers" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/errors" + "github.com/projectcalico/calico/libcalico-go/lib/ipam" + cnet "github.com/projectcalico/calico/libcalico-go/lib/net" "github.com/projectcalico/calico/libcalico-go/lib/options" "github.com/projectcalico/calico/libcalico-go/lib/resources" ) @@ -80,6 +82,7 @@ type TopologyOptions struct { IPv6PoolUsages []api.IPPoolAllowedUse NeedNodeIP bool FlowLogSource int + BPFProxyHealthzPort int // zero means disable } // Calico containers created during topology creation. @@ -121,7 +124,6 @@ func DefaultTopologyOptions() TopologyOptions { TyphaLogSeverity: "info", IPIPMode: api.IPIPModeAlways, IPIPStrategy: NewDefaultTunnelStrategy(DefaultIPPoolCIDR, DefaultIPv6PoolCIDR), - SimulateBIRDRoutes: true, IPPoolCIDR: DefaultIPPoolCIDR, IPv6PoolCIDR: DefaultIPv6PoolCIDR, UseIPPools: true, @@ -178,37 +180,6 @@ func DeleteDefaultIPPool(ctx context.Context, client client.Interface) (*api.IPP return DeleteIPPoolByName(ctx, client, DefaultIPPoolName) } -// StartSingleNodeEtcdTopology starts an etcd container and a single Felix container; it initialises -// the datastore and installs a Node resource for the Felix node. -func StartSingleNodeEtcdTopology(options TopologyOptions) (tc TopologyContainers, etcd *containers.Container, calicoClient client.Interface, infra DatastoreInfra) { - tc, etcd, calicoClient, infra = StartNNodeEtcdTopology(1, options) - return -} - -// StartNNodeEtcdTopology starts an etcd container and a set of Felix hosts. If n > 1, sets -// up IPIP, otherwise this is skipped. -// -// - Configures an IPAM pool for 10.65.0.0/16 (so that Felix programs the all-IPAM blocks IP set) -// but (for simplicity) we don't actually use IPAM to assign IPs. -// - Configures routes between the hosts, giving each host 10.65.x.0/24, where x is the -// index in the returned array. When creating workloads, use IPs from the relevant block. -// - Configures the Tunnel IP for each host as 10.65.x.1. -func StartNNodeEtcdTopology( - n int, - opts TopologyOptions, -) (tc TopologyContainers, etcd *containers.Container, client client.Interface, infra DatastoreInfra) { - log.Infof("Starting a %d-node etcd topology.", n) - - eds, err := GetEtcdDatastoreInfra() - Expect(err).ToNot(HaveOccurred()) - etcd = eds.etcdContainer - infra = eds - - tc, client = StartNNodeTopology(n, opts, eds) - - return -} - // StartSingleNodeTopology starts an etcd container and a single Felix container; it initialises // the datastore and installs a Node resource for the Felix node. func StartSingleNodeTopology( @@ -237,7 +208,7 @@ func StartNNodeTopology( var err error if opts.EnableIPv6 && opts.IPIPMode != api.IPIPModeNever && os.Getenv("FELIX_FV_ENABLE_BPF") == "true" { - log.Errorf("IPIP not supported in BPF with ipv6!") + ginkgo.Fail("IPIP not supported in BPF with ipv6!") return } @@ -260,6 +231,10 @@ func StartNNodeTopology( opts.IPIPMode = api.IPIPModeNever } + if !opts.SimulateBIRDRoutes { + opts.ExtraEnvVars["FELIX_ProgramClusterRoutes"] = "Enabled" + } + // Get client. client = infra.GetCalicoClient() mustInitDatastore(client) @@ -311,6 +286,8 @@ func StartNNodeTopology( typhaIP = tc.Typha.IP } + opts.ExtraEnvVars["FELIX_BPFKUBEPROXYHEALTZPORT"] = fmt.Sprintf("%d", opts.BPFProxyHealthzPort) + tc.Felixes = make([]*Felix, n) var wg sync.WaitGroup @@ -319,12 +296,10 @@ func StartNNodeTopology( // the same copy, while starting Felixes, we could hit a concurrent map read/write // problem. optsPerFelix := make([]TopologyOptions, n) - for i := 0; i < n; i++ { + for i := range n { optsPerFelix[i] = opts optsPerFelix[i].ExtraEnvVars = map[string]string{} - for k, v := range opts.ExtraEnvVars { - optsPerFelix[i].ExtraEnvVars[k] = v - } + maps.Copy(optsPerFelix[i].ExtraEnvVars, opts.ExtraEnvVars) // Different log prefix for each Felix. optsPerFelix[i].ExtraEnvVars["BPF_LOG_PFX"] = fmt.Sprintf("%d-", i) @@ -343,7 +318,7 @@ func StartNNodeTopology( } // Now start the Felixes. - for i := 0; i < n; i++ { + for i := range n { wg.Add(1) go func(i int) { defer wg.Done() @@ -358,7 +333,7 @@ func StartNNodeTopology( _, IPv6CIDR, err := net.ParseCIDR(opts.IPv6PoolCIDR) Expect(err).To(BeNil()) - for i := 0; i < n; i++ { + for i := range n { opts.ExtraEnvVars["BPF_LOG_PFX"] = "" felix := tc.Felixes[i] felix.TyphaIP = typhaIP @@ -387,7 +362,6 @@ func StartNNodeTopology( infra.SetExpectedVXLANTunnelAddr(felix, opts.VXLANStrategy.TunnelAddress(i)) expectedIPs = append(expectedIPs, felix.ExpectedVXLANTunnelAddr) if opts.EnableIPv6 { - expectedIPs = append(expectedIPs, felix.IPv6) infra.SetExpectedVXLANV6TunnelAddr(felix, opts.VXLANStrategy.TunnelAddressV6(i)) expectedIPs = append(expectedIPs, felix.ExpectedVXLANV6TunnelAddr) } @@ -488,6 +462,11 @@ func StartNNodeTopology( } wg.Wait() + if ginkgo.CurrentSpecReport().Failed() { + // If one of our parallel start-up goroutines fails, it will eventually + // fail the test but Ginkgo has no automatic way to abort the main goroutine. + ginkgo.Fail("StartNNodeTopology: failure on background goroutine.") + } success = true return } @@ -534,3 +513,16 @@ func mustInitDatastore(client client.Interface) { return err }).ShouldNot(HaveOccurred(), "mustInitDatastore failed") } + +func AssignIP(workload, addr, hostname string, client client.Interface) { + // Assign the workload's IP in IPAM, this will trigger calculation of routes. + err := client.IPAM().AssignIP(context.Background(), ipam.AssignIPArgs{ + IP: cnet.MustParseIP(addr), + HandleID: &workload, + Attrs: map[string]string{ + ipam.AttributeNode: hostname, + }, + Hostname: hostname, + }) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) +} diff --git a/felix/fv/infrastructure/typha.go b/felix/fv/infrastructure/typha.go index f6eb53d8c50..5d623e69215 100644 --- a/felix/fv/infrastructure/typha.go +++ b/felix/fv/infrastructure/typha.go @@ -77,9 +77,10 @@ func RunTypha(infra DatastoreInfra, options TopologyOptions) *Typha { args..., ) - return &Typha{ - Container: c, - } + t := &Typha{Container: c} + // Register Typha teardown with infra. + infra.AddCleanup(t.Stop) + return t } var CertDir = "" diff --git a/felix/fv/ingress_egress_test.go b/felix/fv/ingress_egress_test.go index 2d47c1abf35..96bec5e3b54 100644 --- a/felix/fv/ingress_egress_test.go +++ b/felix/fv/ingress_egress_test.go @@ -12,28 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( "strconv" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/calico/felix/fv/connectivity" - "github.com/projectcalico/calico/felix/fv/containers" "github.com/projectcalico/calico/felix/fv/infrastructure" "github.com/projectcalico/calico/felix/fv/utils" "github.com/projectcalico/calico/felix/fv/workload" + "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" ) -var _ = Context("_INGRESS-EGRESS_ _BPF-SAFE_ with initialized Felix, etcd datastore, 3 workloads", func() { +var _ = infrastructure.DatastoreDescribe("_INGRESS-EGRESS_ _BPF-SAFE_ with initialized Felix, etcd datastore, 3 workloads", []apiconfig.DatastoreType{apiconfig.EtcdV3}, func(getInfra infrastructure.InfraFactory) { var ( - etcd *containers.Container tc infrastructure.TopologyContainers client client.Interface infra infrastructure.DatastoreInfra @@ -42,8 +39,9 @@ var _ = Context("_INGRESS-EGRESS_ _BPF-SAFE_ with initialized Felix, etcd datast ) BeforeEach(func() { + infra = getInfra() opts := infrastructure.DefaultTopologyOptions() - tc, etcd, client, infra = infrastructure.StartSingleNodeEtcdTopology(opts) + tc, client = infrastructure.StartSingleNodeTopology(opts, infra) infrastructure.CreateDefaultProfile(client, "default", map[string]string{"default": ""}, "default == ''") // Create three workloads, using that profile. @@ -56,28 +54,6 @@ var _ = Context("_INGRESS-EGRESS_ _BPF-SAFE_ with initialized Felix, etcd datast cc = &connectivity.Checker{} }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - if NFTMode() { - logNFTDiags(tc.Felixes[0]) - } else { - tc.Felixes[0].Exec("iptables-save", "-c") - } - tc.Felixes[0].Exec("ip", "r") - } - - for ii := range w { - w[ii].Stop() - } - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - etcd.Exec("etcdctl", "get", "/", "--prefix", "--keys-only") - } - etcd.Stop() - infra.Stop() - }) - It("full connectivity to and from workload 0", func() { cc.ExpectSome(w[1], w[0]) cc.ExpectSome(w[2], w[0]) @@ -218,9 +194,8 @@ var _ = Context("_INGRESS-EGRESS_ _BPF-SAFE_ with initialized Felix, etcd datast }) }) -var _ = Context("_INGRESS-EGRESS_ (iptables-only) with initialized Felix, etcd datastore, 3 workloads", func() { +var _ = infrastructure.DatastoreDescribe("_INGRESS-EGRESS_ (iptables-only) with initialized Felix, etcd datastore, 3 workloads", []apiconfig.DatastoreType{apiconfig.EtcdV3}, func(getInfra infrastructure.InfraFactory) { var ( - etcd *containers.Container tc infrastructure.TopologyContainers client client.Interface infra infrastructure.DatastoreInfra @@ -230,8 +205,9 @@ var _ = Context("_INGRESS-EGRESS_ (iptables-only) with initialized Felix, etcd d ) BeforeEach(func() { + infra = getInfra() opts := infrastructure.DefaultTopologyOptions() - tc, etcd, client, infra = infrastructure.StartSingleNodeEtcdTopology(opts) + tc, client = infrastructure.StartSingleNodeTopology(opts, infra) infrastructure.CreateDefaultProfile(client, "default", map[string]string{"default": ""}, "default == ''") if NFTMode() { @@ -250,28 +226,6 @@ var _ = Context("_INGRESS-EGRESS_ (iptables-only) with initialized Felix, etcd d cc = &connectivity.Checker{} }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - if NFTMode() { - logNFTDiags(tc.Felixes[0]) - } else { - tc.Felixes[0].Exec("iptables-save", "-c") - } - tc.Felixes[0].Exec("ip", "r") - } - - for ii := range w { - w[ii].Stop() - } - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - etcd.Exec("etcdctl", "get", "/", "--prefix", "--keys-only") - } - etcd.Stop() - infra.Stop() - }) - Context("with an ingress policy with no rules", func() { BeforeEach(func() { policy := api.NewNetworkPolicy() @@ -295,7 +249,7 @@ var _ = Context("_INGRESS-EGRESS_ (iptables-only) with initialized Felix, etcd d Eventually(func() string { out, _ := tc.Felixes[0].ExecOutput(listCmd...) return out - }).Should(ContainSubstring("Policy fv/default.policy-1 ingress")) + }).Should(ContainSubstring("NetworkPolicy fv/policy-1 ingress")) }) }) @@ -320,14 +274,13 @@ var _ = Context("_INGRESS-EGRESS_ (iptables-only) with initialized Felix, etcd d Eventually(func() string { out, _ := tc.Felixes[0].ExecOutput(listCmd...) return out - }).Should(ContainSubstring("Policy fv/default.policy-1 egress")) + }).Should(ContainSubstring("NetworkPolicy fv/policy-1 egress")) }) }) }) -var _ = Context("with Typha and Felix-Typha TLS", func() { +var _ = infrastructure.DatastoreDescribe("with Typha and Felix-Typha TLS", []apiconfig.DatastoreType{apiconfig.EtcdV3}, func(getInfra infrastructure.InfraFactory) { var ( - etcd *containers.Container tc infrastructure.TopologyContainers client client.Interface infra infrastructure.DatastoreInfra @@ -336,10 +289,11 @@ var _ = Context("with Typha and Felix-Typha TLS", func() { ) BeforeEach(func() { + infra = getInfra() options := infrastructure.DefaultTopologyOptions() options.WithTypha = true options.WithFelixTyphaTLS = true - tc, etcd, client, infra = infrastructure.StartSingleNodeEtcdTopology(options) + tc, client = infrastructure.StartSingleNodeTopology(options, infra) infrastructure.CreateDefaultProfile(client, "default", map[string]string{"default": ""}, "default == ''") // Create three workloads, using that profile. @@ -352,28 +306,6 @@ var _ = Context("with Typha and Felix-Typha TLS", func() { cc = &connectivity.Checker{} }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - if NFTMode() { - logNFTDiags(tc.Felixes[0]) - } else { - tc.Felixes[0].Exec("iptables-save", "-c") - } - tc.Felixes[0].Exec("ip", "r") - } - - for ii := range w { - w[ii].Stop() - } - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - etcd.Exec("etcdctl", "get", "/", "--prefix", "--keys-only") - } - etcd.Stop() - infra.Stop() - }) - It("full connectivity to and from workload 0", func() { cc.ExpectSome(w[1], w[0]) cc.ExpectSome(w[2], w[0]) diff --git a/felix/fv/ip6tables_test.go b/felix/fv/ip6tables_test.go index b1c0dde3ec2..800a169057c 100644 --- a/felix/fv/ip6tables_test.go +++ b/felix/fv/ip6tables_test.go @@ -12,18 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( - "context" "fmt" "net" "strconv" "strings" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -35,8 +32,6 @@ import ( "github.com/projectcalico/calico/felix/fv/workload" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" - "github.com/projectcalico/calico/libcalico-go/lib/ipam" - cnet "github.com/projectcalico/calico/libcalico-go/lib/net" ) var _ = infrastructure.DatastoreDescribe("IPv6 iptables/nftables tests", []apiconfig.DatastoreType{apiconfig.Kubernetes}, func(getInfra infrastructure.InfraFactory) { @@ -72,38 +67,6 @@ var _ = infrastructure.DatastoreDescribe("IPv6 iptables/nftables tests", []apico } }) - JustAfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - if NFTMode() { - logNFTDiags(felix) - } - for _, table := range []string{"filter", "mangle", "raw", "nat"} { - felix.Exec("ip6tables-save", "-c", "-t", table) - } - felix.Exec("conntrack", "-L") - felix.Exec("ip", "-6", "link") - felix.Exec("ip", "-6", "addr") - felix.Exec("ip", "-6", "rule") - felix.Exec("ip", "-6", "route") - felix.Exec("ip", "-6", "route", "show", "table", "1") - felix.Exec("ip", "-6", "neigh") - } - } - }) - - AfterEach(func() { - log.Info("AfterEach starting") - for _, f := range tc.Felixes { - f.Stop() - } - log.Info("AfterEach done") - }) - - AfterEach(func() { - infra.Stop() - }) - var w [2][2]*workload.Workload setupCluster := func() { @@ -116,6 +79,7 @@ var _ = infrastructure.DatastoreDescribe("IPv6 iptables/nftables tests", []apico wIP := net.ParseIP(fmt.Sprintf("dead:beef::%d:%d", ii, wi+2)).String() wName := fmt.Sprintf("w%d%d", ii, wi) + infrastructure.AssignIP(wName, wIP, tc.Felixes[ii].Hostname, calicoClient) w := workload.New(tc.Felixes[ii], wName, "default", wIP, strconv.Itoa(port), "tcp") @@ -124,23 +88,10 @@ var _ = infrastructure.DatastoreDescribe("IPv6 iptables/nftables tests", []apico w.WorkloadEndpoint.Labels = labels if run { - err := w.Start() + err := w.Start(infra) Expect(err).NotTo(HaveOccurred()) w.ConfigureInInfra(infra) } - if options.UseIPPools { - // Assign the workload's IP in IPAM, this will trigger calculation of routes. - err := calicoClient.IPAM().AssignIP(context.Background(), ipam.AssignIPArgs{ - IP: cnet.MustParseIP(wIP), - HandleID: &w.Name, - Attrs: map[string]string{ - ipam.AttributeNode: tc.Felixes[ii].Hostname, - }, - Hostname: tc.Felixes[ii].Hostname, - }) - Expect(err).NotTo(HaveOccurred()) - } - return w } diff --git a/felix/fv/ipip_test.go b/felix/fv/ipip_test.go deleted file mode 100644 index 0959d2df8ef..00000000000 --- a/felix/fv/ipip_test.go +++ /dev/null @@ -1,493 +0,0 @@ -// Copyright (c) 2020-2025 Tigera, Inc. All rights reserved. -// -// 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. - -//go:build fvtests - -package fv_test - -import ( - "context" - "errors" - "fmt" - "os" - "strings" - "syscall" - "time" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - "github.com/projectcalico/api/pkg/lib/numorstring" - "github.com/sirupsen/logrus" - "github.com/vishvananda/netlink" - - "github.com/projectcalico/calico/felix/dataplane/linux/dataplanedefs" - "github.com/projectcalico/calico/felix/fv/connectivity" - "github.com/projectcalico/calico/felix/fv/containers" - "github.com/projectcalico/calico/felix/fv/infrastructure" - "github.com/projectcalico/calico/felix/fv/utils" - "github.com/projectcalico/calico/felix/fv/workload" - "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" - cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" - "github.com/projectcalico/calico/libcalico-go/lib/netlinkutils" - "github.com/projectcalico/calico/libcalico-go/lib/options" -) - -var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ IPIP topology with BIRD programming routes before adding host IPs to IP sets", []apiconfig.DatastoreType{apiconfig.EtcdV3, apiconfig.Kubernetes}, func(getInfra infrastructure.InfraFactory) { - var ( - bpfEnabled = os.Getenv("FELIX_FV_ENABLE_BPF") == "true" - infra infrastructure.DatastoreInfra - tc infrastructure.TopologyContainers - client client.Interface - w [2]*workload.Workload - hostW [2]*workload.Workload - cc *connectivity.Checker - ) - - BeforeEach(func() { - infra = getInfra() - if (NFTMode() || BPFMode()) && getDataStoreType(infra) == "etcdv3" { - Skip("Skipping NFT / BPF test for etcdv3 backend.") - } - topologyOptions := infrastructure.DefaultTopologyOptions() - topologyOptions.EnableIPv6 = false - tc, client = infrastructure.StartNNodeTopology(2, topologyOptions, infra) - - // Install a default profile that allows all ingress and egress, in the absence of any Policy. - infra.AddDefaultAllow() - - // Wait until the tunl0 device appears; it is created when felix inserts the ipip module - // into the kernel. - Eventually(func() error { - nlHandle, err := netlink.NewHandle(syscall.NETLINK_ROUTE) - if err != nil { - return err - } - defer nlHandle.Close() - links, err := netlinkutils.LinkListRetryEINTR(nlHandle) - if err != nil { - return err - } - for _, link := range links { - if link.Attrs().Name == dataplanedefs.IPIPIfaceName { - return nil - } - } - return errors.New("tunl0 wasn't auto-created") - }).Should(BeNil()) - - // Create workloads, using that profile. One on each "host". - for ii := range w { - wIP := fmt.Sprintf("10.65.%d.2", ii) - wName := fmt.Sprintf("w%d", ii) - w[ii] = workload.Run(tc.Felixes[ii], wName, "default", wIP, "8055", "tcp") - w[ii].ConfigureInInfra(infra) - - hostW[ii] = workload.Run(tc.Felixes[ii], fmt.Sprintf("host%d", ii), "", tc.Felixes[ii].IP, "8055", "tcp") - } - - if bpfEnabled { - ensureAllNodesBPFProgramsAttached(tc.Felixes) - } - - cc = &connectivity.Checker{} - }) - - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - if NFTMode() { - logNFTDiags(felix) - } else { - felix.Exec("iptables-save", "-c") - felix.Exec("ipset", "list") - } - felix.Exec("ip", "r") - felix.Exec("ip", "a") - if BPFMode() { - felix.Exec("calico-bpf", "policy", "dump", "eth0", "all", "--asm") - } - } - } - - for _, wl := range w { - wl.Stop() - } - for _, wl := range hostW { - wl.Stop() - } - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) - - It("should fully randomize MASQUERADE rules", func() { - for _, felix := range tc.Felixes { - if NFTMode() { - Eventually(func() string { - out, _ := felix.ExecOutput("nft", "list", "table", "calico") - return out - }, "10s", "100ms").Should(ContainSubstring("fully-random")) - } else { - Eventually(func() string { - out, _ := felix.ExecOutput("iptables-save", "-c") - return out - }, "10s", "100ms").Should(ContainSubstring("--random-fully")) - } - } - }) - - It("should have correct connectivity", func() { - // checking workload to workload connectivity - cc.ExpectSome(w[0], w[1]) - cc.ExpectSome(w[1], w[0]) - - // checking host to workload connectivity - cc.ExpectSome(tc.Felixes[0], w[1]) - cc.ExpectSome(tc.Felixes[0], w[0]) - - // Checking host to host connectivity - cc.ExpectSome(tc.Felixes[0], hostW[1]) - cc.ExpectSome(tc.Felixes[1], hostW[0]) - - cc.CheckConnectivity() - }) - - Context("with host protection policy in place", func() { - BeforeEach(func() { - // Make sure our new host endpoints don't cut felix off from the datastore. - err := infra.AddAllowToDatastore("host-endpoint=='true'") - Expect(err).NotTo(HaveOccurred()) - - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() - - for _, f := range tc.Felixes { - hep := api.NewHostEndpoint() - hep.Name = "eth0-" + f.Name - hep.Labels = map[string]string{ - "host-endpoint": "true", - } - hep.Spec.Node = f.Hostname - hep.Spec.ExpectedIPs = []string{f.IP} - _, err := client.HostEndpoints().Create(ctx, hep, options.SetOptions{}) - Expect(err).NotTo(HaveOccurred()) - } - }) - - It("should have workload connectivity but not host connectivity", func() { - // Host endpoints (with no policies) block host-host traffic due to default drop. - cc.ExpectNone(tc.Felixes[0], hostW[1]) - cc.ExpectNone(tc.Felixes[1], hostW[0]) - // But the rules to allow IPIP between our hosts let the workload traffic through. - cc.ExpectSome(w[0], w[1]) - cc.ExpectSome(w[1], w[0]) - cc.CheckConnectivity() - }) - }) - - Context("with all-interfaces host protection policy in place", func() { - BeforeEach(func() { - // Make sure our new host endpoints don't cut felix off from the datastore. - err := infra.AddAllowToDatastore("host-endpoint=='true'") - Expect(err).NotTo(HaveOccurred()) - - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() - - // Create host endpoints for each node. - for _, f := range tc.Felixes { - hep := api.NewHostEndpoint() - hep.Name = "all-interfaces-" + f.Name - hep.Labels = map[string]string{ - "host-endpoint": "true", - "hostname": f.Hostname, - } - hep.Spec.Node = f.Hostname - hep.Spec.ExpectedIPs = []string{f.IP} - hep.Spec.InterfaceName = "*" - _, err := client.HostEndpoints().Create(ctx, hep, options.SetOptions{}) - Expect(err).NotTo(HaveOccurred()) - } - }) - - It("should block host-to-host traffic in the absence of policy allowing it", func() { - cc.ExpectNone(tc.Felixes[0], hostW[1]) - cc.ExpectNone(tc.Felixes[1], hostW[0]) - cc.ExpectSome(w[0], w[1]) - cc.ExpectSome(w[1], w[0]) - cc.CheckConnectivity() - }) - - It("should allow host-to-own-pod traffic in the absence of policy allowing it but not host to other-pods", func() { - cc.ExpectSome(tc.Felixes[0], w[0]) - cc.ExpectSome(tc.Felixes[1], w[1]) - cc.ExpectNone(tc.Felixes[0], w[1]) - cc.ExpectNone(tc.Felixes[1], w[0]) - cc.CheckConnectivity() - }) - - It("should allow felixes[0] to reach felixes[1] if ingress and egress policies are in place", func() { - // Create a policy selecting felix[1] that allows egress. - policy := api.NewGlobalNetworkPolicy() - policy.Name = "f0-egress" - policy.Spec.Egress = []api.Rule{{Action: api.Allow}} - policy.Spec.Selector = fmt.Sprintf("hostname == '%s'", tc.Felixes[0].Hostname) - _, err := client.GlobalNetworkPolicies().Create(utils.Ctx, policy, utils.NoOptions) - Expect(err).NotTo(HaveOccurred()) - - // But there is no policy allowing ingress into felix[1]. - cc.ExpectNone(tc.Felixes[0], hostW[1]) - cc.ExpectNone(tc.Felixes[1], hostW[0]) - - // Workload connectivity is unchanged. - cc.ExpectSome(w[0], w[1]) - cc.ExpectSome(w[1], w[0]) - cc.CheckConnectivity() - cc.ResetExpectations() - - // Now add a policy selecting felix[1] that allows ingress. - policy = api.NewGlobalNetworkPolicy() - policy.Name = "f1-ingress" - policy.Spec.Ingress = []api.Rule{{Action: api.Allow}} - policy.Spec.Selector = fmt.Sprintf("hostname == '%s'", tc.Felixes[1].Hostname) - _, err = client.GlobalNetworkPolicies().Create(utils.Ctx, policy, utils.NoOptions) - Expect(err).NotTo(HaveOccurred()) - - // Now testContainers.Felix[0] can reach testContainers.Felix[1]. - cc.ExpectSome(tc.Felixes[0], hostW[1]) - cc.ExpectNone(tc.Felixes[1], hostW[0]) - - // Workload connectivity is unchanged. - cc.ExpectSome(w[0], w[1]) - cc.ExpectSome(w[1], w[0]) - cc.CheckConnectivity() - }) - - Context("with policy allowing port 8055", func() { - BeforeEach(func() { - tcp := numorstring.ProtocolFromString("tcp") - udp := numorstring.ProtocolFromString("udp") - p8055 := numorstring.SinglePort(8055) - policy := api.NewGlobalNetworkPolicy() - policy.Name = "allow-8055" - policy.Spec.Ingress = []api.Rule{ - { - Protocol: &udp, - Destination: api.EntityRule{ - Ports: []numorstring.Port{p8055}, - }, - Action: api.Allow, - }, - { - Protocol: &tcp, - Destination: api.EntityRule{ - Ports: []numorstring.Port{p8055}, - }, - Action: api.Allow, - }, - } - policy.Spec.Egress = []api.Rule{ - { - Protocol: &udp, - Destination: api.EntityRule{ - Ports: []numorstring.Port{p8055}, - }, - Action: api.Allow, - }, - { - Protocol: &tcp, - Destination: api.EntityRule{ - Ports: []numorstring.Port{p8055}, - }, - Action: api.Allow, - }, - } - policy.Spec.Selector = fmt.Sprintf("has(host-endpoint)") - _, err := client.GlobalNetworkPolicies().Create(utils.Ctx, policy, utils.NoOptions) - Expect(err).NotTo(HaveOccurred()) - }) - - // Please take care if adding other connectivity checks into this case, to - // avoid those other checks setting up conntrack state that allows the - // existing case to pass for a different reason. - It("allows host0 to remote Calico-networked workload via service IP", func() { - // Allocate a service IP. - serviceIP := "10.101.0.11" - port := 8055 - tgtPort := 8055 - - createK8sServiceWithoutKubeProxy(createK8sServiceWithoutKubeProxyArgs{ - infra: infra, - felix: tc.Felixes[0], - w: w[1], - svcName: "test-svc", - serviceIP: serviceIP, - targetIP: w[1].IP, - port: port, - tgtPort: tgtPort, - chain: "OUTPUT", - }) - // Expect to connect to the service IP. - cc.ExpectSome(tc.Felixes[0], connectivity.TargetIP(serviceIP), uint16(port)) - cc.CheckConnectivity() - }) - }) - }) - - Context("after removing BGP address from nodes", func() { - // Simulate having a host send IPIP traffic from an unknown source, should get blocked. - BeforeEach(func() { - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() - if bpfEnabled { - infra.RemoveNodeAddresses(tc.Felixes[0]) - } else { - for _, f := range tc.Felixes { - infra.RemoveNodeAddresses(f) - } - } - - listOptions := options.ListOptions{} - if bpfEnabled { - listOptions.Name = tc.Felixes[0].Hostname - } - l, err := client.Nodes().List(ctx, listOptions) - Expect(err).NotTo(HaveOccurred()) - for _, node := range l.Items { - node.Spec.BGP = nil - _, err := client.Nodes().Update(ctx, &node, options.SetOptions{}) - Expect(err).NotTo(HaveOccurred()) - } - - if bpfEnabled { - Eventually(tc.Felixes[1].NumTCBPFProgsEth0, "5s", "200ms").Should(Equal(2)) - } else { - for _, f := range tc.Felixes { - // Removing the BGP config triggers a Felix restart and Felix has a 2s timer during - // a config restart to ensure that it doesn't tight loop. Wait for the ipset to be - // updated as a signal that Felix has restarted. - Eventually(f.IPSetSizeFn("cali40all-hosts-net"), "5s", "200ms").Should(BeZero()) - } - } - }) - - It("should have no workload to workload connectivity", func() { - cc.ExpectNone(w[0], w[1]) - cc.ExpectNone(w[1], w[0]) - cc.CheckConnectivity() - }) - }) - - Context("external nodes configured", func() { - var externalClient *containers.Container - - BeforeEach(func() { - externalClient = infrastructure.RunExtClient("ext-client") - - Eventually(func() error { - err := externalClient.ExecMayFail("ip", "tunnel", "add", "tunl0", "mode", "ipip") - if err != nil && strings.Contains(err.Error(), "SIOCADDTUNNEL: File exists") { - return nil - } - return err - }).Should(Succeed()) - - externalClient.Exec("ip", "link", "set", "tunl0", "up") - externalClient.Exec("ip", "addr", "add", "dev", "tunl0", "10.65.222.1") - externalClient.Exec("ip", "route", "add", "10.65.0.0/24", "via", - tc.Felixes[0].IP, "dev", "tunl0", "onlink") - - tc.Felixes[0].Exec("ip", "route", "add", "10.65.222.1", "via", - externalClient.IP, "dev", "tunl0", "onlink") - }) - - JustAfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - externalClient.Exec("ip", "r") - externalClient.Exec("ip", "l") - externalClient.Exec("ip", "a") - } - }) - AfterEach(func() { - externalClient.Stop() - }) - - It("should allow IPIP to external client iff it is in ExternalNodesCIDRList", func() { - By("testing that ext client ipip does not work if not part of ExternalNodesCIDRList") - - for _, f := range tc.Felixes { - // Make sure that only the internal nodes are present in the ipset - if BPFMode() { - Eventually(f.BPFRoutes, "10s").Should(ContainSubstring(f.IP)) - Consistently(f.BPFRoutes).ShouldNot(ContainSubstring(externalClient.IP)) - } else if NFTMode() { - Eventually(f.NFTSetSizeFn("cali40all-hosts-net"), "5s", "200ms").Should(Equal(2)) - } else { - Eventually(f.IPSetSizeFn("cali40all-hosts-net"), "5s", "200ms").Should(Equal(2)) - } - } - - cc.ExpectNone(externalClient, w[0]) - cc.CheckConnectivity() - - By("changing configuration to include the external client") - - updateConfig := func(addr string) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - c, err := client.FelixConfigurations().Get(ctx, "default", options.GetOptions{}) - if err != nil { - // Create the default config if it doesn't already exist. - if _, ok := err.(cerrors.ErrorResourceDoesNotExist); ok { - c = api.NewFelixConfiguration() - c.Name = "default" - c, err = client.FelixConfigurations().Create(ctx, c, options.SetOptions{}) - Expect(err).NotTo(HaveOccurred()) - } else { - Expect(err).NotTo(HaveOccurred()) - } - } - c.Spec.ExternalNodesCIDRList = &[]string{addr} - logrus.WithFields(logrus.Fields{"felixconfiguration": c, "adding Addr": addr}).Info("Updating FelixConfiguration ") - _, err = client.FelixConfigurations().Update(ctx, c, options.SetOptions{}) - Expect(err).NotTo(HaveOccurred()) - } - - updateConfig(externalClient.IP) - - // Wait for the config to take - for _, f := range tc.Felixes { - if BPFMode() { - Eventually(f.BPFRoutes, "10s").Should(ContainSubstring(externalClient.IP)) - Expect(f.IPSetSize("cali40all-hosts-net")).To(BeZero(), - "BPF mode shouldn't program IP sets") - } else if NFTMode() { - Eventually(f.NFTSetSizeFn("cali40all-hosts-net"), "15s", "200ms").Should(Equal(3)) - } else { - Eventually(f.IPSetSizeFn("cali40all-hosts-net"), "15s", "200ms").Should(Equal(3)) - } - } - - By("testing that the ext client can connect via ipip") - cc.ResetExpectations() - cc.ExpectSome(externalClient, w[0]) - cc.CheckConnectivity() - }) - }) -}) diff --git a/felix/fv/ipsets_test.go b/felix/fv/ipsets_test.go index 81410748a86..e6766ac9b87 100644 --- a/felix/fv/ipsets_test.go +++ b/felix/fv/ipsets_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -21,21 +19,20 @@ import ( "fmt" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/sirupsen/logrus" - "github.com/projectcalico/calico/felix/fv/containers" "github.com/projectcalico/calico/felix/fv/infrastructure" "github.com/projectcalico/calico/felix/fv/workload" + "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/options" ) -var _ = Context("_IPSets_ Tests for IPset rendering", func() { +var _ = infrastructure.DatastoreDescribe("_IPSets_ Tests for IPset rendering", []apiconfig.DatastoreType{apiconfig.EtcdV3}, func(getInfra infrastructure.InfraFactory) { var ( - etcd *containers.Container tc infrastructure.TopologyContainers felixPID int client client.Interface @@ -55,23 +52,13 @@ var _ = Context("_IPSets_ Tests for IPset rendering", func() { } } logrus.SetLevel(logrus.InfoLevel) - tc, etcd, client, infra = infrastructure.StartSingleNodeEtcdTopology(topologyOptions) + infra = getInfra() + tc, client = infrastructure.StartSingleNodeTopology(topologyOptions, infra) felixPID = tc.Felixes[0].GetFelixPID() _ = felixPID w = workload.Run(tc.Felixes[0], "w", "default", "10.65.0.2", "8085", "tcp") }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - etcd.Exec("etcdctl", "get", "/", "--prefix", "--keys-only") - } - - w.Stop() - tc.Stop() - etcd.Stop() - infra.Stop() - }) - It("should handle thousands of IP sets flapping", func() { // This test activates thousands of selectors all at once, simulating // a very large policy set that applies to all pods. @@ -87,7 +74,7 @@ var _ = Context("_IPSets_ Tests for IPset rendering", func() { By("Creating a workload, activating the policies") // Create a workload that uses the policy. - baseNumSets := tc.Felixes[0].Container.NumIPSets() + baseNumSets := tc.Felixes[0].NumIPSets() wep := w.WorkloadEndpoint.DeepCopy() w.ConfigureInInfra(infra) startTime := time.Now() @@ -142,7 +129,7 @@ func createNetworkSetPolicies(c client.Interface, numPols, numRulesPerPol int) { By("Creating network sets") sizes := []int{1, 1, 1, 2, 3, 4, 5, 5, 5, 5, 5, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 100, 200, 1000} numSets := numPols * numRulesPerPol - for i := 0; i < numSets; i++ { + for i := range numSets { ns := api.NewGlobalNetworkSet() ns.Name = fmt.Sprintf("netset-%d", i) ns.Labels = map[string]string{ @@ -155,9 +142,9 @@ func createNetworkSetPolicies(c client.Interface, numPols, numRulesPerPol int) { By("Creating policies with selectors") // Make a policy that activates them - for i := 0; i < numPols; i++ { + for i := range numPols { rules := make([]api.Rule, numRulesPerPol) - for j := 0; j < numRulesPerPol; j++ { + for j := range numRulesPerPol { rules[j] = api.Rule{ Action: "Allow", Source: api.EntityRule{ diff --git a/felix/fv/iptables-locker/iptables-locker.go b/felix/fv/iptables-locker/iptables-locker.go deleted file mode 100644 index 630d17fee00..00000000000 --- a/felix/fv/iptables-locker/iptables-locker.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. -// -// 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 main - -import ( - "time" - - "github.com/docopt/docopt-go" - log "github.com/sirupsen/logrus" - - "github.com/projectcalico/calico/felix/iptables" -) - -const usage = `iptables-locker, test tool for grabbing the iptables lock. - -Usage: - iptables-locker - -` - -func main() { - arguments, err := docopt.ParseArgs(usage, nil, "v0.1") - if err != nil { - println(usage) - log.WithError(err).Fatal("Failed to parse usage") - } - durationStr := arguments[""].(string) - duration, err := time.ParseDuration(durationStr) - if err != nil { - println(usage) - log.WithError(err).Fatal("Failed to parse usage") - } - - iptablesLock := iptables.NewSharedLock( - "/run/xtables.lock", - 1*time.Second, - 50*time.Millisecond, - ) - iptablesLock.Lock() - println("LOCKED") - time.Sleep(duration) - iptablesLock.Unlock() -} diff --git a/felix/fv/iptables_cleanup_test.go b/felix/fv/iptables_cleanup_test.go index febb1e00e25..72f39d55dec 100644 --- a/felix/fv/iptables_cleanup_test.go +++ b/felix/fv/iptables_cleanup_test.go @@ -1,5 +1,3 @@ -//go:build fvtests - // Copyright (c) 2019 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,9 +17,8 @@ package fv_test import ( "os" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - log "github.com/sirupsen/logrus" "github.com/projectcalico/calico/felix/fv/infrastructure" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" @@ -127,19 +124,4 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ iptables cleanup tests", [] }, "5s").ShouldNot(ContainSubstring("cali")) }) }) - - JustAfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - tc.Felixes[0].Exec("iptables-save", "-c") - tc.Felixes[0].Exec("ip", "r") - } - }) - - AfterEach(func() { - log.Info("AfterEach starting") - tc.Felixes[0].Exec("calico-bpf", "connect-time", "clean") - tc.Stop() - infra.Stop() - log.Info("AfterEach done") - }) }) diff --git a/felix/fv/iptables_disruption_test.go b/felix/fv/iptables_disruption_test.go index 84e896a6a18..b13a7703242 100644 --- a/felix/fv/iptables_disruption_test.go +++ b/felix/fv/iptables_disruption_test.go @@ -12,15 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( "fmt" "strings" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/fv/connectivity" @@ -88,22 +86,4 @@ var _ = infrastructure.DatastoreDescribe("iptables disruption tests", []apiconfi Eventually(findRule, "20s", "100ms").Should(Not(BeEmpty())) }) - - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - if NFTMode() { - logNFTDiags(felix) - } else { - _ = felix.ExecMayFail("iptables-save", "-c") - _ = felix.ExecMayFail("ipset", "list") - } - } - } - tc.Stop() - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) }) diff --git a/felix/fv/iptables_force_programming_test.go b/felix/fv/iptables_force_programming_test.go index 62511714131..86da1d7f3c8 100644 --- a/felix/fv/iptables_force_programming_test.go +++ b/felix/fv/iptables_force_programming_test.go @@ -12,14 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( "fmt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -92,8 +90,8 @@ var _ = infrastructure.DatastoreDescribe("iptables force-programming tests", []a Eventually(func() map[string][]string { return tc.Felixes[0].IPTablesChains("filter") }, "10s", "100ms").Should(And( - HaveKey("cali-pi-_l_CMLPBmpkyZIIwB62k"), - HaveKey("cali-po-_l_CMLPBmpkyZIIwB62k"), + HaveKey("cali-pi-gnp/policy-1"), + HaveKey("cali-po-gnp/policy-1"), )) }) diff --git a/felix/fv/iptables_lock_test.go b/felix/fv/iptables_lock_test.go deleted file mode 100644 index cf9926e8db3..00000000000 --- a/felix/fv/iptables_lock_test.go +++ /dev/null @@ -1,144 +0,0 @@ -//go:build fvtests - -// Copyright (c) 2017,2022 Tigera, Inc. All rights reserved. -// -// 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 fv_test - -import ( - "bufio" - "fmt" - "os" - "os/exec" - "path" - "strings" - "time" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - log "github.com/sirupsen/logrus" - - "github.com/projectcalico/calico/felix/fv/utils" -) - -var _ = Describe("with running container", func() { - var containerIdx int - var containerName string - var felixCmd *exec.Cmd - - cmdInContainer := func(cmd ...string) *exec.Cmd { - arg := []string{"exec", containerName} - arg = append(arg, cmd...) - return utils.Command("docker", arg...) - } - - BeforeEach(func() { - containerName = fmt.Sprintf("felix-fv-%d-%d", os.Getpid(), containerIdx) - containerIdx++ - myDir, err := os.Getwd() - Expect(err).NotTo(HaveOccurred()) - log.WithFields(log.Fields{ - "name": containerName, - "myDir": myDir, - }).Info("Starting a Felix container") - // Run a felix container. The tests in this file don't actually rely on Felix - // but the calico/felix-test container has all the iptables dependencies we need to - // check the lock behaviour. Note: we don't map the host's iptables lock into the - // container so the scope of the lock is limited to the container. - wd, err := os.Getwd() - Expect(err).NotTo(HaveOccurred(), "failed to get working directory") - fvBin := os.Getenv("FV_BINARY") - if fvBin == "" { - fvBin = "bin/calico-felix-amd64" - } - felixCmd = utils.Command("docker", "run", - "--rm", - "--name", containerName, - "-v", fmt.Sprintf("%s/..:/codebase", myDir), - "-v", fmt.Sprintf("%s:/usr/local/bin/calico-felix", path.Join(wd, "..", fvBin)), - "--privileged", - utils.Config.FelixImage) - err = felixCmd.Start() - Expect(err).NotTo(HaveOccurred()) - - log.Info("Waiting for container to be listed in docker ps") - start := time.Now() - for { - cmd := utils.Command("docker", "ps") - out, err := cmd.CombinedOutput() - Expect(err).NotTo(HaveOccurred()) - if strings.Contains(string(out), containerName) { - break - } - if time.Since(start) > 60*time.Second { - log.Panic("Timed out waiting for container to be listed.") - } - time.Sleep(1000 * time.Millisecond) - } - }) - AfterEach(func() { - // Send an interrupt to ensure that docker gracefully shuts down the container. - // If we kill the docker process then it detaches the container. - log.Info("Stopping Felix container") - felixCmd.Process.Signal(os.Interrupt) - }) - - Describe("with the lock being held for 2s", func() { - var lockCmd *exec.Cmd - BeforeEach(func() { - // Start the iptables-locker, which is a simple test app that locks - // the iptables lock and then releases it after a timeout. - log.Info("Starting iptables-locker") - lockCmd = cmdInContainer("/codebase/bin/iptables-locker", "2s") - stdErr, err := lockCmd.StderrPipe() - Expect(err).NotTo(HaveOccurred()) - lockCmd.Start() - - // Wait for the iptables-locker to tell us that it actually acquired the - // lock. - log.Info("Waiting for iptables-locker to acquire lock") - scanner := bufio.NewScanner(stdErr) - scanResult := scanner.Scan() - if !scanResult { - log.WithError(scanner.Err()).Warning("Scan failed") - } - Expect(scanResult).To(BeTrue()) - Expect(scanner.Text()).To(Equal("LOCKED")) - Expect(scanner.Err()).NotTo(HaveOccurred()) - log.Info("iptables-locker acquired lock") - }) - - It("iptables should fail to get the lock in 1s", func() { - iptCmd := cmdInContainer("iptables", "-w", "1", "-A", "FORWARD") - out, err := iptCmd.CombinedOutput() - Expect(string(out)).To(ContainSubstring("Stopped waiting")) - Expect(err).To(HaveOccurred()) - }) - - It("iptables should succeed in getting the lock after 3s", func() { - iptCmd := cmdInContainer("iptables", "-w", "3", "-A", "FORWARD") - out, err := iptCmd.CombinedOutput() - log.Infof("iptables output='%s'", out) - Expect(err).NotTo(HaveOccurred()) - }) - - AfterEach(func() { - if lockCmd != nil { - log.Info("waiting for iptables-locker to finish") - err := lockCmd.Wait() - Expect(err).NotTo(HaveOccurred()) - } - }) - }) -}) diff --git a/felix/fv/label_index_metrics_test.go b/felix/fv/label_index_metrics_test.go index 01102fa487c..66cf9a901d9 100644 --- a/felix/fv/label_index_metrics_test.go +++ b/felix/fv/label_index_metrics_test.go @@ -12,15 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( "context" "fmt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -147,12 +145,4 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ label index metrics tests", Expect(metrics.GetFelixMetricInt(tc.Felixes[0].IP, "felix_label_index_num_active_selectors{optimized=\"false\"}")).To(BeNumerically("==", 1)) Expect(metrics.GetFelixMetricInt(tc.Felixes[0].IP, "felix_label_index_num_active_selectors{optimized=\"true\"}")).To(BeNumerically("==", 2)) }) - - AfterEach(func() { - tc.Stop() - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) }) diff --git a/felix/fv/latency_test.go b/felix/fv/latency_test.go index a08de0992f6..9d3fb22aec6 100644 --- a/felix/fv/latency_test.go +++ b/felix/fv/latency_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -23,16 +21,16 @@ import ( "strconv" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" "github.com/projectcalico/calico/felix/fv/connectivity" - "github.com/projectcalico/calico/felix/fv/containers" "github.com/projectcalico/calico/felix/fv/infrastructure" "github.com/projectcalico/calico/felix/fv/utils" "github.com/projectcalico/calico/felix/fv/workload" + "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" ) @@ -50,9 +48,8 @@ func (c latencyConfig) workloadIP(workloadIdx int) string { return fmt.Sprintf("fdc6:3dbc:e983:cbc%x::1", workloadIdx) } -var _ = Context("_BPF-SAFE_ Latency tests with initialized Felix and etcd datastore", func() { +var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Latency tests with initialized Felix and etcd datastore", []apiconfig.DatastoreType{apiconfig.EtcdV3}, func(getInfra infrastructure.InfraFactory) { var ( - etcd *containers.Container tc infrastructure.TopologyContainers client client.Interface infra infrastructure.DatastoreInfra @@ -64,8 +61,8 @@ var _ = Context("_BPF-SAFE_ Latency tests with initialized Felix and etcd datast topologyOptions := infrastructure.DefaultTopologyOptions() topologyOptions.IPIPMode = api.IPIPModeNever topologyOptions.ExtraEnvVars["FELIX_BPFLOGLEVEL"] = "off" // For best perf. - - tc, etcd, client, infra = infrastructure.StartSingleNodeEtcdTopology(topologyOptions) + infra = getInfra() + tc, client = infrastructure.StartSingleNodeTopology(topologyOptions, infra) _ = tc.Felixes[0].GetFelixPID() var err error @@ -78,21 +75,6 @@ var _ = Context("_BPF-SAFE_ Latency tests with initialized Felix and etcd datast if err != nil { log.WithError(err).Error("Close returned error") } - - if CurrentGinkgoTestDescription().Failed { - if NFTMode() { - logNFTDiags(tc.Felixes[0]) - } else { - tc.Felixes[0].Exec("iptables-save", "-c") - } - } - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - etcd.Exec("etcdctl", "get", "/", "--prefix", "--keys-only") - } - etcd.Stop() - infra.Stop() }) describeLatencyTests := func(c latencyConfig) { @@ -119,9 +101,7 @@ var _ = Context("_BPF-SAFE_ Latency tests with initialized Felix and etcd datast BeforeEach(func() { for ii := range w { iiStr := strconv.Itoa(ii) - var ports string - - ports = "3000" + ports := "3000" w[ii] = workload.Run( tc.Felixes[0], "w"+iiStr, @@ -217,12 +197,6 @@ var _ = Context("_BPF-SAFE_ Latency tests with initialized Felix and etcd datast }) }) }) - - AfterEach(func() { - for ii := range w { - w[ii].Stop() - } - }) } Context("IPv4: Network sets tests with initialized Felix and etcd datastore", func() { @@ -236,9 +210,9 @@ var _ = Context("_BPF-SAFE_ Latency tests with initialized Felix and etcd datast }) func generateIPv4s(n int) (result []string) { - for a := 0; a < 256; a++ { - for b := 0; b < 256; b++ { - for c := 0; c < 256; c++ { + for a := range 256 { + for b := range 256 { + for c := range 256 { if n <= 0 { return } @@ -251,9 +225,9 @@ func generateIPv4s(n int) (result []string) { } func generateIPv6s(n int) (result []string) { - for a := 0; a < 256; a++ { - for b := 0; b < 256; b++ { - for c := 0; c < 256; c++ { + for a := range 256 { + for b := range 256 { + for c := range 256 { if n <= 0 { return } @@ -271,15 +245,13 @@ func getTotalBPFIPSetMembers(felix *infrastructure.Felix) int { log.WithError(err).WithField("output", out).Warn("Failed to run bpftool") return -1 } - r := regexp.MustCompile(`Found (\d+) elements`) - m := r.FindStringSubmatch(out) - if m != nil { - count, err := strconv.ParseInt(m[1], 10, 64) - if err != nil { - log.WithError(err).Panic("Failed to parse bpftool output") - } - return int(count) + + // Count occurrences of "key:" at the start of lines in bpftool output. + re := regexp.MustCompile(`(?m)^\s*"key":`) + matches := re.FindAllStringIndex(out, -1) + if matches == nil { + log.WithField("out", out).Warn("bpftool didn't return any 'key:' lines") + return 0 } - log.WithField("out", out).Warn("bpftool didn't return a Found n elements line") - return -1 + return len(matches) } diff --git a/felix/fv/metrics/metrics.go b/felix/fv/metrics/metrics.go index 2c2cd149bb7..d0290c7764f 100644 --- a/felix/fv/metrics/metrics.go +++ b/felix/fv/metrics/metrics.go @@ -106,7 +106,7 @@ func GetRawMetrics(ip string, port int, caFile, certFile, keyFile string) (out s httpClient.Transport = &http.Transport{} } var resp *http.Response - resp, err = httpClient.Get(fmt.Sprintf("%v://%v:%v/metrics", method, ip, port)) + resp, err = httpClient.Get(fmt.Sprintf("%s://%s/metrics", method, net.JoinHostPort(ip, strconv.Itoa(port)))) if err != nil { return } diff --git a/felix/fv/mtu_test.go b/felix/fv/mtu_test.go index b00933d9b97..41432ea7235 100644 --- a/felix/fv/mtu_test.go +++ b/felix/fv/mtu_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -21,7 +19,7 @@ import ( "fmt" "strings" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -65,20 +63,6 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ VXLAN topology before addin return 50 } - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - felix.Exec("ip", "link") - if BPFMode() { - felix.Exec("calico-bpf", "ifstate", "dump") - } - } - infra.DumpErrorData() - } - tc.Stop() - infra.Stop() - }) - for _, ipv6 := range []bool{true, false} { enableIPv6 := ipv6 Describe(fmt.Sprintf("IPv6 enabled: %v", enableIPv6), func() { diff --git a/felix/fv/named_ports_test.go b/felix/fv/named_ports_test.go index 45b7f940845..0733158b33c 100644 --- a/felix/fv/named_ports_test.go +++ b/felix/fv/named_ports_test.go @@ -12,57 +12,52 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( - "context" "encoding/json" "fmt" "os" "strconv" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" log "github.com/sirupsen/logrus" "github.com/projectcalico/calico/felix/fv/connectivity" - "github.com/projectcalico/calico/felix/fv/containers" "github.com/projectcalico/calico/felix/fv/infrastructure" "github.com/projectcalico/calico/felix/fv/utils" "github.com/projectcalico/calico/felix/fv/workload" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/options" ) -var _ = Context("_BPF-SAFE_ TCP: Destination named ports: with initialized Felix, etcd datastore, 3 workloads, allow-all profile", func() { - describeNamedPortTests(false, "tcp") +var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ TCP: Destination named ports: with initialized Felix, etcd datastore, 3 workloads, allow-all profile", []apiconfig.DatastoreType{apiconfig.EtcdV3}, func(getInfra infrastructure.InfraFactory) { + describeNamedPortTests(false, "tcp", getInfra) }) -var _ = Context("_BPF-SAFE_ TCP: Source named ports: with initialized Felix, etcd datastore, 3 workloads, allow-all profile", func() { - describeNamedPortTests(true, "tcp") +var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ TCP: Source named ports: with initialized Felix, etcd datastore, 3 workloads, allow-all profile", []apiconfig.DatastoreType{apiconfig.EtcdV3}, func(getInfra infrastructure.InfraFactory) { + describeNamedPortTests(true, "tcp", getInfra) }) -var _ = Context("_BPF-SAFE_ UDP: Destination named ports: with initialized Felix, etcd datastore, 3 workloads, allow-all profile", func() { - describeNamedPortTests(false, "udp") +var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ UDP: Destination named ports: with initialized Felix, etcd datastore, 3 workloads, allow-all profile", []apiconfig.DatastoreType{apiconfig.EtcdV3}, func(getInfra infrastructure.InfraFactory) { + describeNamedPortTests(false, "udp", getInfra) }) -var _ = Context("_BPF-SAFE_ UDP: Source named ports: with initialized Felix, etcd datastore, 3 workloads, allow-all profile", func() { - describeNamedPortTests(true, "udp") +var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ UDP: Source named ports: with initialized Felix, etcd datastore, 3 workloads, allow-all profile", []apiconfig.DatastoreType{apiconfig.EtcdV3}, func(getInfra infrastructure.InfraFactory) { + describeNamedPortTests(true, "udp", getInfra) }) -var _ = Context("SCTP: Destination named ports: with initialized Felix, etcd datastore, 3 workloads, allow-all profile", func() { - describeNamedPortTests(false, "sctp") +var _ = infrastructure.DatastoreDescribe("SCTP: Destination named ports: with initialized Felix, etcd datastore, 3 workloads, allow-all profile", []apiconfig.DatastoreType{apiconfig.EtcdV3}, func(getInfra infrastructure.InfraFactory) { + describeNamedPortTests(false, "sctp", getInfra) }) -var _ = Context("SCTP: Source named ports: with initialized Felix, etcd datastore, 3 workloads, allow-all profile", func() { - describeNamedPortTests(true, "sctp") +var _ = infrastructure.DatastoreDescribe("SCTP: Source named ports: with initialized Felix, etcd datastore, 3 workloads, allow-all profile", []apiconfig.DatastoreType{apiconfig.EtcdV3}, func(getInfra infrastructure.InfraFactory) { + describeNamedPortTests(true, "sctp", getInfra) }) // describeNamedPortTests describes tests for either source or destination named ports. @@ -75,9 +70,8 @@ var _ = Context("SCTP: Source named ports: with initialized Felix, etcd datastor // // - The policy generation is parameterized to move the match criteria from destination port // to source port (and from ingress/egress to the opposite) if the flag is set. -func describeNamedPortTests(testSourcePorts bool, protocol string) { +func describeNamedPortTests(testSourcePorts bool, protocol string, getInfra infrastructure.InfraFactory) { var ( - etcd *containers.Container tc infrastructure.TopologyContainers client client.Interface infra infrastructure.DatastoreInfra @@ -97,7 +91,8 @@ func describeNamedPortTests(testSourcePorts bool, protocol string) { ) BeforeEach(func() { - tc, etcd, client, infra = infrastructure.StartSingleNodeEtcdTopology(infrastructure.DefaultTopologyOptions()) + infra = getInfra() + tc, client = infrastructure.StartSingleNodeTopology(infrastructure.DefaultTopologyOptions(), infra) infrastructure.CreateDefaultProfile(client, "default", map[string]string{"default": ""}, "default == ''") // Create some workloads, using that profile. for ii := range w { @@ -121,7 +116,7 @@ func describeNamedPortTests(testSourcePorts bool, protocol string) { // Includes some named ports on each workload. Each workload gets its own named port, // which is unique and a shared one. - w[ii].WorkloadEndpoint.Spec.Ports = []libv3.WorkloadEndpointPort{ + w[ii].WorkloadEndpoint.Spec.Ports = []internalapi.WorkloadEndpointPort{ { Port: sharedPort, Name: sharedPortName, @@ -148,87 +143,13 @@ func describeNamedPortTests(testSourcePorts bool, protocol string) { } }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - log.Warn("Test failed, dumping diags...") - utils.Run("docker", "logs", tc.Felixes[0].Name) - - if NFTMode() { - logNFTDiags(tc.Felixes[0]) - } else { - if NFTMode() { - logNFTDiags(tc.Felixes[0]) - } else { - utils.Run("docker", "exec", tc.Felixes[0].Name, "iptables-save", "-c") - utils.Run("docker", "exec", tc.Felixes[0].Name, "ipset", "list") - } - utils.Run("docker", "exec", tc.Felixes[0].Name, "ipset", "list") - utils.Run("docker", "exec", tc.Felixes[0].Name, "ip", "r") - } - - profiles, err := client.Profiles().List(context.Background(), options.ListOptions{}) - if err == nil { - log.Info("DIAGS: Calico Profiles:") - for _, profile := range profiles.Items { - log.Info(profile) - } - } - policies, err := client.NetworkPolicies().List(context.Background(), options.ListOptions{}) - if err == nil { - log.Info("DIAGS: Calico NetworkPolicies:") - for _, policy := range policies.Items { - log.Info(policy) - } - } - gnps, err := client.GlobalNetworkPolicies().List(context.Background(), options.ListOptions{}) - if err == nil { - log.Info("DIAGS: Calico GlobalNetworkPolicies:") - for _, gnp := range gnps.Items { - log.Info(gnp) - } - } - workloads, err := client.WorkloadEndpoints().List(context.Background(), options.ListOptions{}) - if err == nil { - log.Info("DIAGS: Calico WorkloadEndpoints:") - for _, w := range workloads.Items { - log.Info(w) - } - } - nodes, err := client.Nodes().List(context.Background(), options.ListOptions{}) - if err == nil { - log.Info("DIAGS: Calico Nodes:") - for _, n := range nodes.Items { - log.Info(n) - } - } - - tc.Felixes[0].Exec("calico-bpf", "ipsets", "dump") - tc.Felixes[0].Exec("bpftool", "map") - tc.Felixes[0].Exec("bpftool", "prog") - } - - for ii := range w { - w[ii].Stop() - } - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - utils.Run("docker", "exec", etcd.Name, "etcdctl", "get", "/", "--prefix", "--keys-only") - } - etcd.Stop() - infra.Stop() - }) - type ingressEgress int const ( applyAtW0 ingressEgress = iota applyAtOthers ) - bpfEnabled := false - if os.Getenv("FELIX_FV_ENABLE_BPF") == "true" { - bpfEnabled = true - } + bpfEnabled := os.Getenv("FELIX_FV_ENABLE_BPF") == "true" cleanConntrack := func() { if bpfEnabled && protocol == "udp" { @@ -239,6 +160,7 @@ func describeNamedPortTests(testSourcePorts bool, protocol string) { // Baseline test with no named ports policy. Context("with no named port policy", func() { It("should give full connectivity to and from workload 0", func() { + cc.ResetExpectations() // Outbound, w0 should be able to reach all ports on w1 & w2 cc.ExpectSome(w[0], w[1].Port(sharedPort)) cc.ExpectSome(w[0], w[2].Port(sharedPort)) @@ -283,7 +205,7 @@ func describeNamedPortTests(testSourcePorts bool, protocol string) { ports := []numorstring.Port{ numorstring.NamedPort(sharedPortName), } - for i := 0; i < numNumericPorts; i++ { + for i := range numNumericPorts { ports = append(ports, numorstring.SinglePort(3000+uint16(i))) } entRule := api.EntityRule{} @@ -327,6 +249,7 @@ func describeNamedPortTests(testSourcePorts bool, protocol string) { createPolicy(pol) + cc.ResetExpectations() if negated { // Only traffic _not_ going to listed ports is allowed. @@ -484,6 +407,7 @@ func describeNamedPortTests(testSourcePorts bool, protocol string) { // This spec establishes a baseline for the connectivity, then the specs below run // with tweaked versions of the policy. expectBaselineConnectivity := func() { + cc.ResetExpectations() cc.ExpectSome(w[0], w[1].Port(sharedPort)) // Allowed by named port in list. cc.ExpectSome(w[1], w[0].Port(sharedPort)) // Allowed by named port in list. cc.ExpectSome(w[3], w[1].Port(sharedPort)) // Allowed by named port in list. @@ -512,6 +436,7 @@ func describeNamedPortTests(testSourcePorts bool, protocol string) { }) It("should have expected connectivity", func() { + cc.ResetExpectations() cc.ExpectSome(w[3], w[1].Port(sharedPort)) // No change. cc.ExpectSome(w[3], w[0].Port(sharedPort)) // No change. cc.ExpectNone(w[3], w[2].Port(sharedPort)) // Disallowed by negative selector. @@ -539,6 +464,7 @@ func describeNamedPortTests(testSourcePorts bool, protocol string) { }) It("should have expected connectivity", func() { + cc.ResetExpectations() cc.ExpectSome(w[3], w[1].Port(sharedPort)) // No change. cc.ExpectSome(w[3], w[0].Port(sharedPort)) // No change. cc.ExpectNone(w[3], w[2].Port(sharedPort)) // Disallowed by negative selector. @@ -556,6 +482,7 @@ func describeNamedPortTests(testSourcePorts bool, protocol string) { }) expectW2AndW3Blocked := func() { + cc.ResetExpectations() cc.ExpectSome(w[0], w[1].Port(sharedPort)) // No change cc.ExpectSome(w[1], w[0].Port(sharedPort)) // No change @@ -677,6 +604,7 @@ func describeNamedPortTests(testSourcePorts bool, protocol string) { }) It("should give expected connectivity", func() { + cc.ResetExpectations() cc.ExpectSome(w[0], w[1].Port(sharedPort)) // No change. cc.ExpectSome(w[1], w[0].Port(sharedPort)) // No change. cc.ExpectSome(w[3], w[1].Port(sharedPort)) // No change. @@ -711,6 +639,7 @@ func describeNamedPortTests(testSourcePorts bool, protocol string) { }) It("should have expected connectivity", func() { + cc.ResetExpectations() cc.ExpectSome(w[3], w[1].Port(sharedPort)) // No change cc.ExpectSome(w[3], w[0].Port(sharedPort)) // No change cc.ExpectNone(w[2], w[3].Port(sharedPort)) // No change @@ -729,9 +658,8 @@ func describeNamedPortTests(testSourcePorts bool, protocol string) { } // This test reproduces a particular Kubernetes failure scenario seen during FV testing named ports. -var _ = Describe("TCP: named port with a simulated kubernetes nginx and client", func() { +var _ = infrastructure.DatastoreDescribe("TCP: named port with a simulated kubernetes nginx and client", []apiconfig.DatastoreType{apiconfig.EtcdV3}, func(getInfra infrastructure.InfraFactory) { var ( - etcd *containers.Container tc infrastructure.TopologyContainers client client.Interface infra infrastructure.DatastoreInfra @@ -743,7 +671,8 @@ var _ = Describe("TCP: named port with a simulated kubernetes nginx and client", ) BeforeEach(func() { - tc, etcd, client, infra = infrastructure.StartSingleNodeEtcdTopology(infrastructure.DefaultTopologyOptions()) + infra = getInfra() + tc, client = infrastructure.StartSingleNodeTopology(infrastructure.DefaultTopologyOptions(), infra) // Create a namespace profile and write to the datastore. infrastructure.CreateDefaultProfile(client, "kns.test", map[string]string{"name": "test"}, "") // Create nginx workload. @@ -758,7 +687,7 @@ var _ = Describe("TCP: named port with a simulated kubernetes nginx and client", nginx.WorkloadEndpoint.Labels = map[string]string{ "name": "nginx", } - nginx.WorkloadEndpoint.Spec.Ports = []libv3.WorkloadEndpointPort{ + nginx.WorkloadEndpoint.Spec.Ports = []internalapi.WorkloadEndpointPort{ { Port: 80, Name: "http-port", @@ -815,32 +744,8 @@ var _ = Describe("TCP: named port with a simulated kubernetes nginx and client", cc = &connectivity.Checker{} }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - log.Warn("Test failed, dumping diags...") - utils.Run("docker", "logs", tc.Felixes[0].Name) - - if NFTMode() { - logNFTDiags(tc.Felixes[0]) - } else { - utils.Run("docker", "exec", tc.Felixes[0].Name, "iptables-save", "-c") - utils.Run("docker", "exec", tc.Felixes[0].Name, "ipset", "list") - } - utils.Run("docker", "exec", tc.Felixes[0].Name, "ip", "r") - } - - nginx.Stop() - nginxClient.Stop() - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - utils.Run("docker", "exec", etcd.Name, "etcdctl", "get", "/", "--prefix", "--keys-only") - } - etcd.Stop() - infra.Stop() - }) - It("HTTP port policy should open up nginx port", func() { + cc.ResetExpectations() // The profile has a default allow so we should start with connectivity. cc.ExpectSome(nginxClient, nginx.Port(80)) cc.ExpectSome(nginxClient, nginx.Port(81)) @@ -928,7 +833,7 @@ func describeNamedPortHostEndpointTests(getInfra infrastructure.InfraFactory, na }) AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { + if CurrentSpecReport().Failed() { for _, felix := range tc.Felixes { if NFTMode() { logNFTDiags(felix) @@ -940,31 +845,25 @@ func describeNamedPortHostEndpointTests(getInfra infrastructure.InfraFactory, na felix.Exec("ip", "a") } } - - for _, wl := range hostW { - wl.Stop() - } - tc.Stop() - - infra.Stop() + // Cleanup is handled by DatastoreDescribe's AfterEach via infra.Stop(). }) expectNoConnectivity := func() { + cc.ResetExpectations() cc.ExpectNone(tc.Felixes[0], hostW[1].Port(8055)) cc.ExpectNone(tc.Felixes[1], hostW[0].Port(8055)) cc.ExpectNone(tc.Felixes[0], hostW[1].Port(8056)) cc.ExpectNone(tc.Felixes[1], hostW[0].Port(8056)) cc.CheckConnectivityOffset(1) - cc.ResetExpectations() } expectNamedPortOpen := func() { + cc.ResetExpectations() cc.ExpectSome(tc.Felixes[0], hostW[1].Port(8055)) cc.ExpectSome(tc.Felixes[1], hostW[0].Port(8055)) cc.ExpectNone(tc.Felixes[0], hostW[1].Port(8056)) cc.ExpectNone(tc.Felixes[1], hostW[0].Port(8056)) cc.CheckConnectivityOffset(1) - cc.ResetExpectations() } It("should have expected initial connectivity", func() { @@ -1044,9 +943,8 @@ var _ = infrastructure.DatastoreDescribe("named port, all-interfaces host endpoi }) // This test verifies that TCP named ports aren't matched by UDP rules and vice versa. -var _ = Describe("tests with mixed TCP/UDP", func() { +var _ = infrastructure.DatastoreDescribe("tests with mixed TCP/UDP", []apiconfig.DatastoreType{apiconfig.EtcdV3}, func(getInfra infrastructure.InfraFactory) { var ( - etcd *containers.Container tc infrastructure.TopologyContainers client client.Interface infra infrastructure.DatastoreInfra @@ -1059,7 +957,8 @@ var _ = Describe("tests with mixed TCP/UDP", func() { ) BeforeEach(func() { - tc, etcd, client, infra = infrastructure.StartSingleNodeEtcdTopology(infrastructure.DefaultTopologyOptions()) + infra = getInfra() + tc, client = infrastructure.StartSingleNodeTopology(infrastructure.DefaultTopologyOptions(), infra) infrastructure.CreateDefaultProfile(client, "open", map[string]string{"default": ""}, "") createTarget := func(ip, protocol string) *workload.Workload { @@ -1075,7 +974,7 @@ var _ = Describe("tests with mixed TCP/UDP", func() { w.WorkloadEndpoint.Labels = map[string]string{ "name": "nginx", } - w.WorkloadEndpoint.Spec.Ports = []libv3.WorkloadEndpointPort{ + w.WorkloadEndpoint.Spec.Ports = []internalapi.WorkloadEndpointPort{ { Port: 80, Name: "tcp-port", @@ -1140,37 +1039,14 @@ var _ = Describe("tests with mixed TCP/UDP", func() { tcpCC = &connectivity.Checker{Protocol: "tcp"} }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - log.Warn("Test failed, dumping diags...") - utils.Run("docker", "logs", tc.Felixes[0].Name) - if NFTMode() { - logNFTDiags(tc.Felixes[0]) - } else { - utils.Run("docker", "exec", tc.Felixes[0].Name, "iptables-save", "-c") - utils.Run("docker", "exec", tc.Felixes[0].Name, "ipset", "list") - } - utils.Run("docker", "exec", tc.Felixes[0].Name, "ip", "r") - } - - targetTCPWorkload.Stop() - targetUDPWorkload.Stop() - clientWorkload.Stop() - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - utils.Run("docker", "exec", etcd.Name, "etcdctl", "get", "/", "--prefix", "--keys-only") - } - etcd.Stop() - infra.Stop() - }) - It("shouldn't confuse TCP and UDP ports", func() { + tcpCC.ResetExpectations() // The profile has a default allow so we should start with connectivity. tcpCC.ExpectSome(clientWorkload, targetTCPWorkload.Port(80)) tcpCC.ExpectSome(clientWorkload, targetTCPWorkload.Port(81)) tcpCC.CheckConnectivity() + udpCC.ResetExpectations() udpCC.ExpectSome(clientWorkload, targetUDPWorkload.Port(80)) udpCC.ExpectSome(clientWorkload, targetUDPWorkload.Port(81)) udpCC.CheckConnectivity() @@ -1182,6 +1058,7 @@ var _ = Describe("tests with mixed TCP/UDP", func() { tcpCC.ExpectNone(clientWorkload, targetTCPWorkload.Port(80)) tcpCC.ExpectNone(clientWorkload, targetTCPWorkload.Port(81)) tcpCC.CheckConnectivity() + udpCC.ResetExpectations() udpCC.ExpectNone(clientWorkload, targetUDPWorkload.Port(80)) udpCC.ExpectNone(clientWorkload, targetUDPWorkload.Port(81)) diff --git a/felix/fv/nat_outgoing_test.go b/felix/fv/nat_outgoing_test.go index ebd5352549a..3cdfe6b1ab3 100644 --- a/felix/fv/nat_outgoing_test.go +++ b/felix/fv/nat_outgoing_test.go @@ -1,5 +1,3 @@ -//go:build fvtests - // Copyright (c) 2019,2021 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,12 +18,10 @@ import ( "context" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - log "github.com/sirupsen/logrus" - "github.com/projectcalico/calico/felix/fv/containers" "github.com/projectcalico/calico/felix/fv/infrastructure" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" @@ -34,18 +30,15 @@ import ( var _ = infrastructure.DatastoreDescribe("NATOutgoing rule rendering test", []apiconfig.DatastoreType{apiconfig.EtcdV3, apiconfig.Kubernetes}, func(getInfra infrastructure.InfraFactory) { var ( - infra infrastructure.DatastoreInfra - tc infrastructure.TopologyContainers - client client.Interface - dumpedDiags bool - externalClient *containers.Container + infra infrastructure.DatastoreInfra + tc infrastructure.TopologyContainers + client client.Interface ) BeforeEach(func() { var err error infra = getInfra() - dumpedDiags = false opts := infrastructure.DefaultTopologyOptions() opts.IPIPMode = api.IPIPModeNever opts.EnableIPv6 = true @@ -69,43 +62,61 @@ var _ = infrastructure.DatastoreDescribe("NATOutgoing rule rendering test", []ap Expect(err).NotTo(HaveOccurred()) }) - // Utility function to dump diags if the test failed. Should be called in the inner-most - // AfterEach() to dump diags before the test is torn down. Only the first call for a given - // test has any effect. - dumpDiags := func() { - if !CurrentGinkgoTestDescription().Failed || dumpedDiags { - return - } + It("should have expected restriction on the nat outgoing rule", func() { if NFTMode() { - logNFTDiags(tc.Felixes[0]) + Eventually(func() string { + output, _ := tc.Felixes[0].ExecOutput("nft", "list", "chain", "ip", "calico", "nat-cali-nat-outgoing") + return output + }, 5*time.Second, 100*time.Millisecond).Should(MatchRegexp(".* oifname eth\\+")) } else { - iptSave, err := tc.Felixes[0].ExecOutput("iptables-save", "-c") - if err == nil { - log.Info("iptables-save:\n" + iptSave) - } + Eventually(func() string { + output, _ := tc.Felixes[0].ExecOutput("iptables-save", "-t", "nat") + return output + }, 5*time.Second, 100*time.Millisecond).Should(MatchRegexp("-A cali-nat-outgoing .*-o eth\\+ ")) } - dumpedDiags = true - infra.DumpErrorData() - } - - AfterEach(func() { - dumpDiags() - tc.Stop() - infra.Stop() - externalClient.Stop() }) +}) - It("should have expected restriction on the nat outgoing rule", func() { +var _ = infrastructure.DatastoreDescribe("NATPortRange rendering test", []apiconfig.DatastoreType{apiconfig.EtcdV3, apiconfig.Kubernetes}, func(getInfra infrastructure.InfraFactory) { + var ( + infra infrastructure.DatastoreInfra + tc infrastructure.TopologyContainers + client client.Interface + ) + + BeforeEach(func() { + var err error + infra = getInfra() + + opts := infrastructure.DefaultTopologyOptions() + opts.IPIPMode = api.IPIPModeNever + opts.EnableIPv6 = true + + opts.ExtraEnvVars = map[string]string{ + "FELIX_NATPortRange": "32768:65535", + } + tc, client = infrastructure.StartSingleNodeTopology(opts, infra) + + ctx := context.Background() + ippool := api.NewIPPool() + ippool.Name = "nat-pool" + ippool.Spec.CIDR = "10.244.255.0/24" + ippool.Spec.NATOutgoing = true + ippool, err = client.IPPools().Create(ctx, ippool, options.SetOptions{}) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should have expected rendering", func() { if NFTMode() { Eventually(func() string { output, _ := tc.Felixes[0].ExecOutput("nft", "list", "chain", "ip", "calico", "nat-cali-nat-outgoing") return output - }, 5*time.Second, 100*time.Millisecond).Should(MatchRegexp(".* oifname eth\\+")) + }, 5*time.Second, 100*time.Millisecond).Should(ContainSubstring("32768-65535")) } else { Eventually(func() string { output, _ := tc.Felixes[0].ExecOutput("iptables-save", "-t", "nat") return output - }, 5*time.Second, 100*time.Millisecond).Should(MatchRegexp("-A cali-nat-outgoing .*-o eth\\+ ")) + }, 5*time.Second, 100*time.Millisecond).Should(ContainSubstring("32768-65535")) } }) }) diff --git a/felix/fv/netsets_test.go b/felix/fv/netsets_test.go index a8e89707e12..8b680e92c30 100644 --- a/felix/fv/netsets_test.go +++ b/felix/fv/netsets_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -24,16 +22,16 @@ import ( "sync" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" "github.com/projectcalico/calico/felix/fv/connectivity" - "github.com/projectcalico/calico/felix/fv/containers" "github.com/projectcalico/calico/felix/fv/infrastructure" "github.com/projectcalico/calico/felix/fv/utils" "github.com/projectcalico/calico/felix/fv/workload" + "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/net" "github.com/projectcalico/calico/libcalico-go/lib/options" @@ -92,9 +90,8 @@ func (c netsetsConfig) workloadFullLengthCIDR(workloadIdx int, namespaced bool) return addr + "/128" } -var _ = Context("_NET_SETS_ Network sets tests with initialized Felix and etcd datastore", func() { +var _ = infrastructure.DatastoreDescribe("_NET_SETS_ Network sets tests with initialized Felix and etcd datastore", []apiconfig.DatastoreType{apiconfig.EtcdV3}, func(getInfra infrastructure.InfraFactory) { var ( - etcd *containers.Container tc infrastructure.TopologyContainers felixPID int client client.Interface @@ -103,27 +100,11 @@ var _ = Context("_NET_SETS_ Network sets tests with initialized Felix and etcd d BeforeEach(func() { topologyOptions := infrastructure.DefaultTopologyOptions() - tc, etcd, client, infra = infrastructure.StartSingleNodeEtcdTopology(topologyOptions) + infra = getInfra() + tc, client = infrastructure.StartSingleNodeTopology(topologyOptions, infra) felixPID = tc.Felixes[0].GetFelixPID() }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - if NFTMode() { - logNFTDiags(tc.Felixes[0]) - } else { - tc.Felixes[0].Exec("iptables-save", "-c") - } - } - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - etcd.Exec("etcdctl", "get", "/", "--prefix", "--keys-only") - } - etcd.Stop() - infra.Stop() - }) - describeConnTests := func(c netsetsConfig) { var ( w, nw [4]*workload.Workload @@ -148,9 +129,7 @@ var _ = Context("_NET_SETS_ Network sets tests with initialized Felix and etcd d BeforeEach(func() { for ii := range w { iiStr := strconv.Itoa(ii) - var ports string - - ports = "3000" + ports := "3000" w[ii] = workload.Run( tc.Felixes[0], "w"+iiStr, @@ -205,8 +184,8 @@ var _ = Context("_NET_SETS_ Network sets tests with initialized Felix and etcd d }) assertNoConnectivity := func() { - for i := 0; i < len(w); i++ { - for j := 0; j < len(w); j++ { + for i := range len(w) { + for j := range len(w) { if i != j { cc.ExpectNone(w[i], w[j]) cc.ExpectNone(nw[i], nw[j]) @@ -1054,15 +1033,6 @@ var _ = Context("_NET_SETS_ Network sets tests with initialized Felix and etcd d }) }) }) - - AfterEach(func() { - for ii := range w { - w[ii].Stop() - } - for ii := range nw { - nw[ii].Stop() - } - }) } Context("_BPF-SAFE_ IPv4: Network sets tests with initialized Felix and etcd datastore", func() { @@ -1104,7 +1074,7 @@ var _ = Context("_NET_SETS_ Network sets tests with initialized Felix and etcd d nw.Configure(client) // Generate policies and network sets. - for i := 0; i < numPolicies; i++ { + for i := range numPolicies { pol := api.NewGlobalNetworkPolicy() pol.Name = fmt.Sprintf("policy-%d", i) pol.Spec = api.GlobalNetworkPolicySpec{ @@ -1120,7 +1090,7 @@ var _ = Context("_NET_SETS_ Network sets tests with initialized Felix and etcd d } policies = append(policies, pol) } - for i := 0; i < numNetworkSets; i++ { + for i := range numNetworkSets { ns := api.NewGlobalNetworkSet() ns.Name = fmt.Sprintf("netset-%d", i) ns.Labels = map[string]string{ @@ -1140,7 +1110,7 @@ var _ = Context("_NET_SETS_ Network sets tests with initialized Felix and etcd d churnPolicies := func(iterations int) { log.Info("Churning policies...") created := false - for i := 0; i < iterations; i++ { + for range iterations { if created { for _, pol := range policies { log.WithField("policy", pol.Name).Info("Deleting policy") @@ -1170,7 +1140,7 @@ var _ = Context("_NET_SETS_ Network sets tests with initialized Felix and etcd d churnNetworkSets := func(iterations int) { log.Info("Churning network sets...") created := false - for i := 0; i < iterations; i++ { + for range iterations { if created { for _, ns := range networkSets { log.WithField("name", ns.Name).Info("Deleting network set") @@ -1223,7 +1193,7 @@ var _ = Context("_NET_SETS_ Network sets tests with initialized Felix and etcd d func generateNets(n int) []string { var nets []string - for i := 0; i < n; i++ { + for range n { a := rand.Intn(255) b := rand.Intn(255) pr := rand.Intn(16) + 16 diff --git a/felix/fv/nft_test.go b/felix/fv/nft_test.go index 630ae6d4678..0ae323196a3 100644 --- a/felix/fv/nft_test.go +++ b/felix/fv/nft_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( diff --git a/felix/fv/pktgen/pktgen.go b/felix/fv/pktgen/pktgen.go index b3292d521f5..ac0d974c6f8 100644 --- a/felix/fv/pktgen/pktgen.go +++ b/felix/fv/pktgen/pktgen.go @@ -20,8 +20,8 @@ import ( "strconv" "github.com/docopt/docopt-go" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket" + "github.com/gopacket/gopacket/layers" log "github.com/sirupsen/logrus" "golang.org/x/sys/unix" ) @@ -254,6 +254,16 @@ func main() { log.WithError(err).Fatal("failed to create UDP socket") } + if sport != 0 { + laddr := &unix.SockaddrInet4{ + Port: int(sport), // Your specific source port + Addr: [4]byte{0, 0, 0, 0}, + } + if err := unix.Bind(s, laddr); err != nil { + log.WithError(err).Fatal("failed to bind to source port") + } + } + if !ipDNF { err = unix.SetsockoptInt(s, unix.IPPROTO_IP, unix.IP_PMTUDISC, unix.IP_PMTUDISC_DONT) if err != nil { diff --git a/felix/fv/pod_setup_wait_test.go b/felix/fv/pod_setup_wait_test.go index 9769ae62378..48d6f4b92fb 100644 --- a/felix/fv/pod_setup_wait_test.go +++ b/felix/fv/pod_setup_wait_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020 Tigera, Inc. All rights reserved. +// Copyright (c) 2020-2025 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -21,7 +19,7 @@ import ( "path/filepath" "regexp" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/sirupsen/logrus" @@ -32,8 +30,6 @@ import ( ) var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Pod setup status wait", []apiconfig.DatastoreType{apiconfig.EtcdV3, apiconfig.Kubernetes}, func(getInfra infrastructure.InfraFactory) { - const nodeCount = 1 - var ( infra infrastructure.DatastoreInfra topologyOptions infrastructure.TopologyOptions @@ -54,19 +50,11 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Pod setup status wait", []a topologyOptions.FelixLogSeverity = "Debug" topologyOptions.FelixDebugFilenameRegex = "status_file_reporter" - tc, _ = infrastructure.StartNNodeTopology(nodeCount, topologyOptions, infra) + tc, _ = infrastructure.StartSingleNodeTopology(topologyOptions, infra) tc.Felixes[0].Exec("rm", "-rf", "/tmp/endpoint-status") dataplaneInSyncReceivedC = tc.Felixes[0].WatchStdoutFor(regexp.MustCompile("DataplaneInSync received from upstream")) }) - AfterEach(func() { - tc.Stop() - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) - Describe("with the file-reporter writing endpoint status to '/tmp/endpoint-status'", func() { buildStatCmdInFelix := func(felix *infrastructure.Felix, filename string) func() error { return func() error { @@ -85,7 +73,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Pod setup status wait", []a var statCmds [2]func() error for i := range dummyWorkloads { dummyWorkloads[i] = workload.New(tc.Felixes[0], fmt.Sprintf("workload-endpoint-status-tests-%d", i), "default", fmt.Sprintf("10.65.0.%d", 10+i), "8080", "tcp") - err := dummyWorkloads[i].Start() + err := dummyWorkloads[i].Start(infra) Expect(err).NotTo(HaveOccurred()) dummyWorkloads[i].ConfigureInInfra(infra) @@ -130,7 +118,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Pod setup status wait", []a By("creating a workload before Felix starts") wl := workload.New(tc.Felixes[0], "workload-endpoint-status-tests-0", "default", "10.65.0.10", "8080", "tcp") wl.ConfigureInInfra(infra) - err := wl.Start() + err := wl.Start(infra) Expect(err).NotTo(HaveOccurred(), "Couldn't start a test workload") By("determining the filename Felix will look for") diff --git a/felix/fv/policy_grouping_test.go b/felix/fv/policy_grouping_test.go index 9648c4033e0..e658e039a1c 100644 --- a/felix/fv/policy_grouping_test.go +++ b/felix/fv/policy_grouping_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -22,7 +20,7 @@ import ( "strings" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -181,24 +179,6 @@ var _ = infrastructure.DatastoreDescribe("policy grouping tests", []apiconfig.Da } } }) - - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - if NFTMode() { - logNFTDiags(felix) - } else { - _ = felix.ExecMayFail("iptables-save", "-c") - _ = felix.ExecMayFail("ipset", "list") - } - } - } - tc.Stop() - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) }) func floatPtr(f float64) *float64 { diff --git a/felix/fv/policy_perf_hint_test.go b/felix/fv/policy_perf_hint_test.go index 2e6b4f93ecb..bf065b1762d 100644 --- a/felix/fv/policy_perf_hint_test.go +++ b/felix/fv/policy_perf_hint_test.go @@ -12,14 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -99,25 +97,4 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ policy performance hints te "Expected IP set to be cleaned up when policy no longer has AssumeNeededOnEveryNode", ) }) - - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - if NFTMode() { - logNFTDiags(felix) - } else { - _ = felix.ExecMayFail("iptables-save", "-c") - _ = felix.ExecMayFail("ipset", "list") - } - if BPFMode() { - _ = felix.ExecMayFail("calico-bpf", "ipsets", "dump") - } - } - } - tc.Stop() - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) }) diff --git a/felix/fv/policy_test.go b/felix/fv/policy_test.go new file mode 100644 index 00000000000..377c2df49a5 --- /dev/null +++ b/felix/fv/policy_test.go @@ -0,0 +1,145 @@ +// Copyright (c) 2025-2026 Tigera, Inc. All rights reserved. +// +// 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 fv_test + +import ( + "context" + "fmt" + "strings" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + + "github.com/projectcalico/calico/felix/fv/infrastructure" + "github.com/projectcalico/calico/felix/fv/utils" + "github.com/projectcalico/calico/felix/fv/workload" + "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" + client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" + "github.com/projectcalico/calico/libcalico-go/lib/options" +) + +var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ policy tests", []apiconfig.DatastoreType{apiconfig.Kubernetes}, func(getInfra infrastructure.InfraFactory) { + const ( + wepPortStr = "8055" + ) + + var ( + infra infrastructure.DatastoreInfra + tc infrastructure.TopologyContainers + client client.Interface + ep1_1, ep2_1 *workload.Workload // Workloads on Felix0 + ) + + BeforeEach(func() { + infra = getInfra() + + options := infrastructure.DefaultTopologyOptions() + options.IPIPMode = api.IPIPModeNever + tc, client = infrastructure.StartSingleNodeTopology(options, infra) + + // Install a default profile that allows all ingress and egress, in the absence of any Policy. + infra.AddDefaultAllow() + + // Create workload on host 1 (Felix0). + ep1_1 = workload.Run(tc.Felixes[0], "ep1-1", "default", "10.65.0.0", wepPortStr, "tcp") + ep1_1.ConfigureInInfra(infra) + + ep2_1 = workload.Run(tc.Felixes[0], "ep2-1", "default", "10.65.0.1", wepPortStr, "tcp") + ep2_1.ConfigureInInfra(infra) + + if BPFMode() { + ensureAllNodesBPFProgramsAttached(tc.Felixes) + } + }) + + It("LOG action rule should reflect logPrefix correctly", func() { + if BPFMode() { + Skip("Skipping for BPF dataplane.") + } + + // Explicitly configure the MTU. + felixConfig := api.NewFelixConfiguration() // Create a default FelixConfiguration + felixConfig.Name = "default" + felixConfig.Spec.LogPrefix = "aXy9%n%%t %k %p" + LogActionRateLimitBurst := 9_999 + felixConfig.Spec.LogActionRateLimitBurst = &LogActionRateLimitBurst + logActionRateLimit := "9999/day" + felixConfig.Spec.LogActionRateLimit = &logActionRateLimit + _, err := client.FelixConfigurations().Create(context.Background(), felixConfig, options.SetOptions{}) + Expect(err).NotTo(HaveOccurred()) + + // gnp2-4 egress(N1-1) ingress(N1-1) + gnp := api.NewGlobalNetworkPolicy() + gnp.Name = "ep2-4" + gnp.Spec.Order = &float1_0 + gnp.Spec.Tier = "default" + gnp.Spec.Selector = ep1_1.NameSelector() + gnp.Spec.Types = []api.PolicyType{api.PolicyTypeIngress, api.PolicyTypeEgress} + gnp.Spec.Ingress = []api.Rule{ + {Action: api.Log, Source: api.EntityRule{Selector: ep2_1.NameSelector()}}, + } + gnp.Spec.Egress = []api.Rule{ + {Action: api.Allow, Destination: api.EntityRule{Selector: ep2_1.NameSelector()}}, + } + _, err = client.GlobalNetworkPolicies().Create(utils.Ctx, gnp, utils.NoOptions) + Expect(err).NotTo(HaveOccurred()) + + expectedPattern := "aXy9ep2-4%default gnp ep2-4: " + detectIptablesRule := func(felix *infrastructure.Felix, ipVersion uint8) { + binary := "iptables-save" + if ipVersion == 6 { + binary = "ip6tables-save" + } + + logLimitPattern := fmt.Sprintf("-m limit --limit %s --limit-burst %d", + logActionRateLimit, LogActionRateLimitBurst, + ) + getRules := func() bool { + output, err := felix.ExecOutput(binary, "-t", "filter") + Expect(err).NotTo(HaveOccurred()) + return strings.Contains(output, expectedPattern) && strings.Contains(output, logLimitPattern) + } + Eventually(getRules, 5*time.Second, 100*time.Millisecond).Should(BeTrue()) + Consistently(getRules, 3*time.Second, 100*time.Millisecond).Should(BeTrue()) + } + + detectNftablesRule := func(felix *infrastructure.Felix, ipVersion uint8) { + ipFamily := "ip" + if ipVersion == 6 { + ipFamily = "ip6" + } + logLimitPattern := fmt.Sprintf("limit rate %s burst %d packets", + logActionRateLimit, LogActionRateLimitBurst, + ) + getRules := func() bool { + output, err := felix.ExecOutput("nft", "list", "table", ipFamily, "calico") + Expect(err).NotTo(HaveOccurred()) + return strings.Contains(output, expectedPattern) && strings.Contains(output, logLimitPattern) + } + Eventually(getRules, 5*time.Second, 100*time.Millisecond).Should(BeTrue()) + Consistently(getRules, 3*time.Second, 100*time.Millisecond).Should(BeTrue()) + } + + if NFTMode() { + detectNftablesRule(tc.Felixes[0], 4) + detectNftablesRule(tc.Felixes[0], 6) + } else { + detectIptablesRule(tc.Felixes[0], 4) + detectIptablesRule(tc.Felixes[0], 6) + } + }) +}) diff --git a/felix/fv/policysync_test.go b/felix/fv/policysync_test.go index 126e12156d9..60a1e4bc8d9 100644 --- a/felix/fv/policysync_test.go +++ b/felix/fv/policysync_test.go @@ -1,5 +1,3 @@ -//go:build fvtests - // Copyright (c) 2019-2024 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,9 +25,9 @@ import ( "strconv" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -37,12 +35,12 @@ import ( googleproto "google.golang.org/protobuf/proto" "github.com/projectcalico/calico/felix/dataplane/mock" - "github.com/projectcalico/calico/felix/fv/containers" "github.com/projectcalico/calico/felix/fv/infrastructure" "github.com/projectcalico/calico/felix/fv/utils" "github.com/projectcalico/calico/felix/fv/workload" "github.com/projectcalico/calico/felix/proto" "github.com/projectcalico/calico/felix/types" + "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/options" @@ -55,10 +53,8 @@ func init() { resolver.SetDefaultScheme("passthrough") } -var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { - +var _ = infrastructure.DatastoreDescribe("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", []apiconfig.DatastoreType{apiconfig.EtcdV3}, func(getInfra infrastructure.InfraFactory) { var ( - etcd *containers.Container tc infrastructure.TopologyContainers calicoClient client.Interface infra infrastructure.DatastoreInfra @@ -82,7 +78,8 @@ var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { // options.ExtraEnvVars["FELIX_DebugDisableLogDropping"] = "true" // options.FelixLogSeverity = "debug" options.ExtraVolumes[tempDir] = "/var/run/calico/policysync" - tc, etcd, calicoClient, infra = infrastructure.StartSingleNodeEtcdTopology(options) + infra = getInfra() + tc, calicoClient = infrastructure.StartSingleNodeTopology(options, infra) infrastructure.CreateDefaultProfile(calicoClient, "default", map[string]string{"default": ""}, "default == ''") // Create three workloads, using that profile. @@ -98,19 +95,6 @@ var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { hostMgmtCredsPath = filepath.Join(tempDir, binder.CredentialsSubdir) }) - AfterEach(func() { - for ii := range w { - w[ii].Stop() - } - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - etcd.Exec("etcdctl", "get", "/", "--prefix", "--keys-only") - } - etcd.Stop() - infra.Stop() - }) - AfterEach(func() { if tempDir != "" { err := os.RemoveAll(tempDir) @@ -119,10 +103,7 @@ var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { }) Context("with the binder", func() { - - var ( - hostWlSocketPath, containerWlSocketPath [3]string - ) + var hostWlSocketPath, containerWlSocketPath [3]string dirNameForWorkload := func(wl *workload.Workload) string { return filepath.Join(binder.MountSubdir, wl.WorkloadEndpoint.Spec.Pod) @@ -131,7 +112,7 @@ var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { createWorkloadDirectory := func(wl *workload.Workload) (string, string) { dirName := dirNameForWorkload(wl) hostWlDir := filepath.Join(tempDir, dirName) - os.MkdirAll(hostWlDir, 0777) + Expect(os.MkdirAll(hostWlDir, 0o777)).To(Succeed()) return hostWlDir, filepath.Join("/var/run/calico/policysync", dirName) } @@ -145,13 +126,13 @@ var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { credentialFileName := credentials.Uid + binder.CredentialsExtension credsFileTmp := filepath.Join(tempDir, credentialFileName) - err = os.WriteFile(credsFileTmp, attrs, 0777) + err = os.WriteFile(credsFileTmp, attrs, 0o777) if err != nil { return err } // Lazy create the credential's directory - err = os.MkdirAll(hostMgmtCredsPath, 0777) + err = os.MkdirAll(hostMgmtCredsPath, 0o777) if err != nil { return err } @@ -201,7 +182,6 @@ var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { }) Context("with open workload connections", func() { - // Then connect to it. var ( wlConn [3]*grpc.ClientConn @@ -214,7 +194,7 @@ var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { createWorkloadConn := func(i int) (*grpc.ClientConn, proto.PolicySyncClient) { var opts []grpc.DialOption opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) - opts = append(opts, grpc.WithDialer(unixDialer)) + opts = append(opts, grpc.WithContextDialer(unixDialer)) var conn *grpc.ClientConn conn, err = grpc.NewClient(hostWlSocketPath[i], opts...) Expect(err).NotTo(HaveOccurred()) @@ -241,9 +221,7 @@ var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { }) Context("with mock clients syncing", func() { - var ( - mockWlClient [3]*mockWorkloadClient - ) + var mockWlClient [3]*mockWorkloadClient BeforeEach(func() { for i := range w { @@ -283,16 +261,16 @@ var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { Eventually(mockWlClient[2].InSync, "10s").Should(BeTrue()) // Close it and wait for the client to shut down. - wlConn[2].Close() + _ = wlConn[2].Close() Eventually(mockWlClient[2].Done, "10s").Should(BeClosed()) }) doChurn := func(wlIndexes ...int) { - for i := 0; i < 100; i++ { + for i := range 100 { wlIdx := wlIndexes[i%len(wlIndexes)] By(fmt.Sprintf("Churn %d; targeting workload %d", i, wlIdx)) - policy := api.NewGlobalNetworkPolicy() + policy := v3.NewGlobalNetworkPolicy() policy.SetName("policy-0") policy.Spec.Selector = w[wlIdx].NameSelector() @@ -307,7 +285,7 @@ var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { } if wlIdx != 2 { - policyID := types.PolicyID{Name: "default.policy-0", Tier: "default"} + policyID := types.PolicyID{Name: "policy-0", Kind: v3.KindGlobalNetworkPolicy} Eventually(mockWlClient[wlIdx].ActivePolicies, waitTime).Should(Equal(set.From(policyID))) } @@ -335,29 +313,30 @@ var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { Context("after adding a policy that applies to workload 0 only", func() { var ( - policy *api.GlobalNetworkPolicy + policy *v3.GlobalNetworkPolicy policyID types.PolicyID ) BeforeEach(func() { - policy = api.NewGlobalNetworkPolicy() + policy = v3.NewGlobalNetworkPolicy() policy.SetName("policy-0") policy.Spec.Selector = w[0].NameSelector() - policy.Spec.Ingress = []api.Rule{ + policy.Spec.Ingress = []v3.Rule{ { Action: "Allow", - Source: api.EntityRule{ + Source: v3.EntityRule{ Selector: "all()", - ServiceAccounts: &api.ServiceAccountMatch{ + ServiceAccounts: &v3.ServiceAccountMatch{ Selector: "foo == 'bar'", }, }, - HTTP: &api.HTTPMatch{Methods: []string{"GET"}, - Paths: []api.HTTPPath{{Exact: "/path"}}, + HTTP: &v3.HTTPMatch{ + Methods: []string{"GET"}, + Paths: []v3.HTTPPath{{Exact: "/path"}}, }, }, } - policy.Spec.Egress = []api.Rule{ + policy.Spec.Egress = []v3.Rule{ { Action: "Allow", }, @@ -365,18 +344,16 @@ var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { policy, err = calicoClient.GlobalNetworkPolicies().Create(ctx, policy, utils.NoOptions) Expect(err).NotTo(HaveOccurred()) - policyID = types.PolicyID{Name: "default.policy-0", Tier: "default"} + policyID = types.PolicyID{Name: "policy-0", Kind: v3.KindGlobalNetworkPolicy} }) It("should be sent to workload 0 only", func() { - Eventually(mockWlClient[0].ActivePolicies).Should(Equal(set.From( - policyID, - ))) + Eventually(mockWlClient[0].ActivePolicies).Should(Equal(set.From(policyID))) Eventually(mockWlClient[0].EndpointToPolicyOrder).Should(Equal( map[string][]mock.TierInfo{"k8s/fv/fv-pod-0/eth0": {{ - Name: "default", - EgressPolicyNames: []string{"default.policy-0"}, - IngressPolicyNames: []string{"default.policy-0"}, + Name: "default", + EgressPolicies: []types.PolicyID{{Name: "policy-0", Kind: v3.KindGlobalNetworkPolicy}}, + IngressPolicies: []types.PolicyID{{Name: "policy-0", Kind: v3.KindGlobalNetworkPolicy}}, }}})) Consistently(mockWlClient[1].ActivePolicies).Should(Equal(set.New[types.PolicyID]())) @@ -384,9 +361,7 @@ var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { }) It("should be correctly mapped to proto policy", func() { - Eventually(mockWlClient[0].ActivePolicies).Should(Equal(set.From( - policyID, - ))) + Eventually(mockWlClient[0].ActivePolicies).Should(Equal(set.From(policyID))) protoPol := mockWlClient[0].ActivePolicy(policyID) // The rule IDs are fairly random hashes, check they're there but // ignore them for the comparison. @@ -401,6 +376,7 @@ var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { Expect(googleproto.Equal(protoPol, &proto.Policy{ Namespace: "", // Global policy has no namespace + Tier: "default", InboundRules: []*proto.Rule{ { Action: "allow", @@ -411,8 +387,9 @@ var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { SrcServiceAccountMatch: &proto.ServiceAccountMatch{ Selector: "foo == 'bar'", }, - HttpMatch: &proto.HTTPMatch{Methods: []string{"GET"}, - Paths: []*proto.HTTPMatch_PathMatch{{PathMatch: &proto.HTTPMatch_PathMatch_Exact{Exact: "/path"}}}, + HttpMatch: &proto.HTTPMatch{ + Methods: []string{"GET"}, + Paths: []*proto.HTTPMatch_PathMatch{{PathMatch: &proto.HTTPMatch_PathMatch_Exact{Exact: "/path"}}}, }, }, }, @@ -439,9 +416,7 @@ var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { It("should handle a change of selector", func() { // Make sure the initial update makes it through or we might get a // false positive. - Eventually(mockWlClient[0].ActivePolicies).Should(Equal(set.From( - policyID, - ))) + Eventually(mockWlClient[0].ActivePolicies).Should(Equal(set.From(policyID))) By("Sending through an endpoint update and policy remove") policy.Spec.Selector = w[1].NameSelector() @@ -457,9 +432,9 @@ var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { Eventually(mockWlClient[1].ActivePolicies).Should(Equal(set.From(policyID))) Eventually(mockWlClient[1].EndpointToPolicyOrder).Should(Equal( map[string][]mock.TierInfo{"k8s/fv/fv-pod-1/eth0": {{ - Name: "default", - EgressPolicyNames: []string{"default.policy-0"}, - IngressPolicyNames: []string{"default.policy-0"}, + Name: "default", + EgressPolicies: []types.PolicyID{{Name: "policy-0", Kind: v3.KindGlobalNetworkPolicy}}, + IngressPolicies: []types.PolicyID{{Name: "policy-0", Kind: v3.KindGlobalNetworkPolicy}}, }}})) Consistently(mockWlClient[2].ActivePolicies).Should(Equal(set.New[types.PolicyID]())) @@ -473,7 +448,8 @@ var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { defProfID, ))) Eventually(mockWlClient[0].EndpointToProfiles).Should(Equal(map[string][]string{ - "k8s/fv/fv-pod-0/eth0": {"default"}})) + "k8s/fv/fv-pod-0/eth0": {"default"}, + })) // Send in an endpoint update that adds one profile and deletes another. var err error @@ -484,7 +460,8 @@ var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { By("Sending through an endpoint update and policy remove/update") notDefProfID := types.ProfileID{Name: "notdefault"} Eventually(mockWlClient[0].EndpointToProfiles).Should(Equal(map[string][]string{ - "k8s/fv/fv-pod-0/eth0": {"notdefault"}})) + "k8s/fv/fv-pod-0/eth0": {"notdefault"}, + })) Eventually(mockWlClient[0].ActiveProfiles).Should(Equal(set.From(notDefProfID))) Eventually(mockWlClient[2].ActiveProfiles).Should(Equal(set.From(defProfID))) @@ -497,7 +474,7 @@ var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { BeforeEach(func() { log.Info("Adding Service Account Profile") - profile := api.NewProfile() + profile := v3.NewProfile() profile.SetName(conversion.ServiceAccountProfileNamePrefix + "sa-namespace.sa-name") saID.Name = "sa-name" saID.Namespace = "sa-namespace" @@ -529,7 +506,7 @@ var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { BeforeEach(func() { log.Info("Adding Namespace Profile") - profile := api.NewProfile() + profile := v3.NewProfile() profile.SetName(conversion.NamespaceProfileNamePrefix + "ns1") nsID.Name = "ns1" profile.Spec.LabelsToApply = map[string]string{ @@ -603,7 +580,9 @@ var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { // Then create a new mock client with a new connection. newWlConn, newWlClient := createWorkloadConn(0) - defer newWlConn.Close() + defer func() { + _ = newWlConn.Close() + }() client := newMockWorkloadClient("workload-0 second client") client.StartSyncing(ctx, newWlClient) @@ -623,12 +602,14 @@ var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { // Workload should be sent over the API. Eventually(client.EndpointToPolicyOrder).Should(Equal(map[string][]mock.TierInfo{"k8s/fv/fv-pod-0/eth0": {}})) - wlConn[0].Close() + _ = wlConn[0].Close() Eventually(client.Done).Should(BeClosed()) // Then create a new mock client with a new connection. newWlConn, newWlClient := createWorkloadConn(0) - defer newWlConn.Close() + defer func() { + _ = newWlConn.Close() + }() client = newMockWorkloadClient("workload-0 second client") client.StartSyncing(ctx, newWlClient) @@ -654,7 +635,9 @@ var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { // Then create a new mock client with a new connection. newWlConn, newWlClient := createWorkloadConn(0) - defer newWlConn.Close() + defer func() { + _ = newWlConn.Close() + }() client := newMockWorkloadClient("workload-0 second client") client.StartSyncing(ctx, newWlClient) @@ -684,8 +667,11 @@ var _ = Context("_POL-SYNC_ _BPF-SAFE_ policy sync API tests", func() { }) }) -func unixDialer(target string, timeout time.Duration) (net.Conn, error) { - return net.DialTimeout("unix", target, timeout) +func unixDialer(ctx context.Context, target string) (net.Conn, error) { + if deadline, ok := ctx.Deadline(); ok { + return net.DialTimeout("unix", target, time.Until(deadline)) + } + return net.DialTimeout("unix", target, 0) } type mockWorkloadClient struct { diff --git a/felix/fv/pre_dnat_test.go b/felix/fv/pre_dnat_test.go index 377896cadae..c2e0b4b75cc 100644 --- a/felix/fv/pre_dnat_test.go +++ b/felix/fv/pre_dnat_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -21,7 +19,7 @@ import ( "strconv" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -82,27 +80,10 @@ var _ = infrastructure.DatastoreDescribe("pre-dnat with initialized Felix, 2 wor // We will use this container to model an external client trying to connect into // workloads on a host. Create a route in the container for the workload CIDR. - externalClient = infrastructure.RunExtClient("ext-client") + externalClient = infrastructure.RunExtClient(infra, "ext-client") externalClient.Exec("ip", "r", "add", "10.65.0.0/24", "via", tc.Felixes[0].IP) }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - tc.Felixes[0].Exec("iptables-save", "-c") - tc.Felixes[0].Exec("ip", "r") - tc.Felixes[0].Exec("ip", "a") - } - - for ii := range w { - w[ii].Stop() - } - tc.Stop() - - infra.Stop() - externalClient.Stop() - }) - Context("with node port DNATs", func() { BeforeEach(func() { tc.Felixes[0].Exec( diff --git a/felix/fv/prometheus_metrics_test.go b/felix/fv/prometheus_metrics_test.go index 8fc8df80722..22539acbdaa 100644 --- a/felix/fv/prometheus_metrics_test.go +++ b/felix/fv/prometheus_metrics_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -21,7 +19,7 @@ import ( "net/http" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/fv/infrastructure" @@ -88,13 +86,5 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Prometheus metrics tests", Expect(get(debugServer, debugPath)).NotTo(HaveOccurred()) Expect(get(metricsServer, debugPath)).To(HaveOccurred()) }) - - AfterEach(func() { - tc.Stop() - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) }) }) diff --git a/felix/fv/qos_controls_test.go b/felix/fv/qos_controls_test.go index c6e45bf7f69..2a2ad16e338 100644 --- a/felix/fv/qos_controls_test.go +++ b/felix/fv/qos_controls_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -25,7 +23,7 @@ import ( "strings" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/format" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -36,10 +34,8 @@ import ( "github.com/projectcalico/calico/felix/fv/infrastructure" "github.com/projectcalico/calico/felix/fv/workload" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - api "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" - "github.com/projectcalico/calico/libcalico-go/lib/ipam" - cnet "github.com/projectcalico/calico/libcalico-go/lib/net" ) func init() { @@ -171,16 +167,31 @@ var _ = infrastructure.DatastoreDescribe( []apiconfig.DatastoreType{apiconfig.Kubernetes, apiconfig.EtcdV3}, func(getInfra infrastructure.InfraFactory) { type testConf struct { - Encap string + Encap string + BPFLogLevel string } for _, testConfig := range []testConf{ - {Encap: "none"}, - {Encap: "ipip"}, - {Encap: "vxlan"}, + { + Encap: "none", + BPFLogLevel: "Debug", + }, + { + Encap: "none", + BPFLogLevel: "Info", + }, + { + Encap: "ipip", + BPFLogLevel: "Debug", + }, + { + Encap: "vxlan", + BPFLogLevel: "Info", + }, } { encap := testConfig.Encap + bpfLogLevel := testConfig.BPFLogLevel - Describe(fmt.Sprintf("encap='%s'", encap), func() { + Describe(fmt.Sprintf("encap='%s', bpfLogLevel='%s'", encap, bpfLogLevel), func() { var ( infra infrastructure.DatastoreInfra tc infrastructure.TopologyContainers @@ -191,7 +202,7 @@ var _ = infrastructure.DatastoreDescribe( ) BeforeEach(func() { - infra = getInfra() + infra = getInfra(infrastructure.WithBPFLogByteLimit(16 * 1024 * 1024)) topt = infrastructure.DefaultTopologyOptions() switch encap { @@ -207,17 +218,18 @@ var _ = infrastructure.DatastoreDescribe( } topt.VXLANMode = apiv3.VXLANModeNever topt.IPIPMode = apiv3.IPIPModeAlways - topt.SimulateBIRDRoutes = true case "vxlan": topt.IPIPMode = apiv3.IPIPModeNever topt.VXLANMode = apiv3.VXLANModeAlways topt.VXLANStrategy = infrastructure.NewDefaultTunnelStrategy(topt.IPPoolCIDR, topt.IPv6PoolCIDR) - topt.SimulateBIRDRoutes = false } - topt.UseIPPools = true topt.DelayFelixStart = true topt.TriggerDelayedFelixStart = true + topt.FelixLogSeverity = "Debug" + if BPFMode() { + topt.ExtraEnvVars["FELIX_BPFLogLevel"] = bpfLogLevel + } if _, ok := infra.(*infrastructure.EtcdDatastoreInfra); ok && BPFMode() { Skip("Skipping QoS control tests on etcd datastore and BPF mode.") @@ -230,20 +242,9 @@ var _ = infrastructure.DatastoreDescribe( for ii := range w { wIP := fmt.Sprintf("10.65.%d.2", ii) wName := fmt.Sprintf("w%d", ii) + infrastructure.AssignIP(wName, wIP, tc.Felixes[ii].Hostname, calicoClient) w[ii] = workload.Run(tc.Felixes[ii], wName, "default", wIP, "8055", "tcp") w[ii].ConfigureInInfra(infra) - if topt.UseIPPools { - // Assign the workload's IP in IPAM, this will trigger calculation of routes. - err := calicoClient.IPAM().AssignIP(context.Background(), ipam.AssignIPArgs{ - IP: cnet.MustParseIP(wIP), - HandleID: &w[ii].Name, - Attrs: map[string]string{ - ipam.AttributeNode: tc.Felixes[ii].Hostname, - }, - Hostname: tc.Felixes[ii].Hostname, - }) - Expect(err).NotTo(HaveOccurred()) - } } if BPFMode() { @@ -257,19 +258,9 @@ var _ = infrastructure.DatastoreDescribe( }) AfterEach(func() { - for _, felix := range tc.Felixes { - felix.Exec("ip", "route") - if BPFMode() { - felix.Exec("calico-bpf", "counters", "dump") - felix.Exec("calico-bpf", "ifstate", "dump") - } - } - - tc.Stop() - infra.Stop() - if cancel != nil { cancel() + cancel = nil } }) @@ -326,7 +317,14 @@ var _ = infrastructure.DatastoreDescribe( if BPFMode() && BPFAttachType() == "tc" { Skip("Skipping QoS control bandwidth tests on BPF TC attach mode.") } + + By("Removing all limits from workloads") + for i := range len(w) { + w[i].WorkloadEndpoint.Spec.QoSControls = nil + w[i].UpdateInInfra(infra) + } }) + getQdisc := func() string { out, err := tc.Felixes[1].ExecOutput("tc", "qdisc") logrus.Infof("tc qdisc output:\n%v", out) @@ -350,13 +348,12 @@ var _ = infrastructure.DatastoreDescribe( Expect(baselinePeakrate).To(BeNumerically(">=", 100000000.0*10)) By("Setting 10Mbps limit and 100Mbps peakrate for ingress on workload 1") - w[1].WorkloadEndpoint.Spec.QoSControls = &api.QoSControls{ + w[1].WorkloadEndpoint.Spec.QoSControls = &internalapi.QoSControls{ IngressBandwidth: 10000000, IngressBurst: 300000000, IngressPeakrate: 100000000, } w[1].UpdateInInfra(infra) - Eventually(tc.Felixes[1].ExecOutputFn("ip", "r", "get", "10.65.1.2"), "10s").Should(ContainSubstring(w[1].InterfaceName)) By("Waiting for the config to appear in 'tc qdisc'") // ingress config should be present @@ -374,13 +371,12 @@ var _ = infrastructure.DatastoreDescribe( Expect(ingressLimitedPeakrate).To(BeNumerically("<=", 100000000.0*1.2)) By("Setting 10Mbps limit and 100Mbps peakrate for egress on workload 1") - w[1].WorkloadEndpoint.Spec.QoSControls = &api.QoSControls{ + w[1].WorkloadEndpoint.Spec.QoSControls = &internalapi.QoSControls{ EgressBandwidth: 10000000, EgressBurst: 300000000, EgressPeakrate: 100000000, } w[1].UpdateInInfra(infra) - Eventually(tc.Felixes[1].ExecOutputFn("ip", "r", "get", "10.65.1.2"), "10s").Should(ContainSubstring(w[1].InterfaceName)) By("Waiting for the config to appear in 'tc qdisc'") // ingress config should not be present @@ -400,7 +396,6 @@ var _ = infrastructure.DatastoreDescribe( By("Removing all limits from workload 1") w[1].WorkloadEndpoint.Spec.QoSControls = nil w[1].UpdateInInfra(infra) - Eventually(tc.Felixes[1].ExecOutputFn("ip", "r", "get", "10.65.1.2"), "10s").Should(ContainSubstring(w[1].InterfaceName)) By("Waiting for the config to disappear in 'tc qdisc'") // ingress config should not be present @@ -413,7 +408,28 @@ var _ = infrastructure.DatastoreDescribe( Expect(err).NotTo(HaveOccurred()) err = serverCmd.Process.Release() Expect(err).NotTo(HaveOccurred()) + }) + + It("should correctly apply bandwidth limits when a non-default qdisc exists (handle != 0)", func() { + By("Replacing the default noqueue qdisc with a non-default qdisc (handle 8001)") + out, err := tc.Felixes[1].ExecOutput("tc", "qdisc", "replace", "dev", w[1].InterfaceName, "root", "handle", "8001:", "noqueue") + logrus.Infof("tc qdisc replace output:\n%v", out) + Expect(err).NotTo(HaveOccurred()) + By("Waiting for the config to appear in 'tc qdisc'") + Eventually(getQdisc, "10s", "1s").Should(MatchRegexp(`qdisc noqueue 8001: dev ` + regexp.QuoteMeta(w[1].InterfaceName) + ` root refcnt \d+`)) + + By("Setting 10Mbps limit and 100Mbps peakrate for ingress on workload 1") + w[1].WorkloadEndpoint.Spec.QoSControls = &internalapi.QoSControls{ + IngressBandwidth: 10000000, + IngressBurst: 300000000, + IngressPeakrate: 100000000, + } + w[1].UpdateInInfra(infra) + + By("Waiting for the config to appear in 'tc qdisc'") + // ingress config should be present + Eventually(getQdisc, "10s", "1s").Should(MatchRegexp(`qdisc tbf \d+: dev ` + regexp.QuoteMeta(w[1].InterfaceName) + ` root refcnt \d+ rate ` + regexp.QuoteMeta("10Mbit") + `.* peakrate ` + regexp.QuoteMeta("100Mbit"))) }) }) @@ -434,12 +450,11 @@ var _ = infrastructure.DatastoreDescribe( Expect(baselineRate).To(BeNumerically(">=", 100*1e6*0.8)) By("Setting 100 packets/s limit for ingress on workload 0 (iperf2 server)") - w[0].WorkloadEndpoint.Spec.QoSControls = &api.QoSControls{ + w[0].WorkloadEndpoint.Spec.QoSControls = &internalapi.QoSControls{ IngressPacketRate: 100, IngressPacketBurst: 200, } w[0].UpdateInInfra(infra) - Eventually(tc.Felixes[0].ExecOutputFn("ip", "r", "get", "10.65.0.2"), "10s").Should(ContainSubstring(w[0].InterfaceName)) if BPFMode() { By("Waiting for the config to appear in the BPF maps on workload 0") @@ -462,6 +477,7 @@ var _ = infrastructure.DatastoreDescribe( By("Running iperf2 client on workload 1 with packet rate limit for ingress on workload 0") ingressLimitedPeakrate, err := retryIperf2Client(w[1], 5, 5*time.Second, "-c", w[0].IP, "-u", "-l1000", "-b10M", "-t1") + Expect(err).NotTo(HaveOccurred()) logrus.Infof("iperf client peakrate with ingress packet rate limit on client (bps): %v", ingressLimitedPeakrate) // Expect the limited peakrate to be below an estimated desired rate (1000 byte packet * 8 bits/byte * (100 packets/s + 200 packet burst) = 2400000bps), with a 20% margin Expect(ingressLimitedPeakrate).To(BeNumerically(">=", 1000*8*100)) @@ -480,7 +496,6 @@ var _ = infrastructure.DatastoreDescribe( By("Removing all limits from workload 0") w[0].WorkloadEndpoint.Spec.QoSControls = nil w[0].UpdateInInfra(infra) - Eventually(tc.Felixes[0].ExecOutputFn("ip", "r", "get", "10.65.0.2"), "10s").Should(ContainSubstring(w[0].InterfaceName)) if BPFMode() { By("Waiting for the config to disappear in the BPF maps on workload 0") @@ -502,12 +517,11 @@ var _ = infrastructure.DatastoreDescribe( } By("Setting 100kpps limit for egress on workload 1 (iperf2 client)") - w[1].WorkloadEndpoint.Spec.QoSControls = &api.QoSControls{ + w[1].WorkloadEndpoint.Spec.QoSControls = &internalapi.QoSControls{ EgressPacketRate: 100, EgressPacketBurst: 200, } w[1].UpdateInInfra(infra) - Eventually(tc.Felixes[1].ExecOutputFn("ip", "r", "get", "10.65.1.2"), "10s").Should(ContainSubstring(w[1].InterfaceName)) if BPFMode() { By("Waiting for the config to appear in the BPF maps on workload 1") @@ -530,6 +544,7 @@ var _ = infrastructure.DatastoreDescribe( By("Running iperf2 client on workload 1 with packet rate limit for egress on workload 1") egressLimitedPeakrate, err := retryIperf2Client(w[1], 5, 5*time.Second, "-c", w[0].IP, "-u", "-l1000", "-b10M", "-t1") + Expect(err).NotTo(HaveOccurred()) logrus.Infof("iperf client peakrate with egress packet rate limit on client (bps): %v", egressLimitedPeakrate) // Expect the limited peakrate to be below an estimated desired rate (1000 byte packet * 8 bits/byte * (100 packets/s + 200 packet burst) = 2400000bps), with a 20% margin Expect(egressLimitedPeakrate).To(BeNumerically(">=", 1000*8*100)) @@ -548,7 +563,6 @@ var _ = infrastructure.DatastoreDescribe( By("Removing all limits from workload 1") w[1].WorkloadEndpoint.Spec.QoSControls = nil w[1].UpdateInInfra(infra) - Eventually(tc.Felixes[1].ExecOutputFn("ip", "r", "get", "10.65.1.2"), "10s").Should(ContainSubstring(w[1].InterfaceName)) if BPFMode() { By("Waiting for the config to disappear in the BPF maps on workload 1") @@ -614,11 +628,10 @@ var _ = infrastructure.DatastoreDescribe( } By("Setting connection limit for ingress on workload 0") - w[0].WorkloadEndpoint.Spec.QoSControls = &api.QoSControls{ + w[0].WorkloadEndpoint.Spec.QoSControls = &internalapi.QoSControls{ IngressMaxConnections: int64(numConnections), } w[0].UpdateInInfra(infra) - Eventually(tc.Felixes[0].ExecOutputFn("ip", "r", "get", "10.65.0.2"), "10s").Should(ContainSubstring(w[0].InterfaceName)) By("Waiting for the config to appear in 'iptables-save/nft list ruleset' on workload 0") if NFTMode() { @@ -657,7 +670,6 @@ var _ = infrastructure.DatastoreDescribe( By("Removing all limits from workload 0") w[0].WorkloadEndpoint.Spec.QoSControls = nil w[0].UpdateInInfra(infra) - Eventually(tc.Felixes[0].ExecOutputFn("ip", "r", "get", "10.65.0.2"), "10s").Should(ContainSubstring(w[0].InterfaceName)) By("Waiting for the config to disappear in 'iptables-save/nft list ruleset' on workload 0") if NFTMode() { @@ -673,11 +685,10 @@ var _ = infrastructure.DatastoreDescribe( } By("Setting connection limit for egress on workload 1 (clients)") - w[1].WorkloadEndpoint.Spec.QoSControls = &api.QoSControls{ + w[1].WorkloadEndpoint.Spec.QoSControls = &internalapi.QoSControls{ EgressMaxConnections: int64(numConnections), } w[1].UpdateInInfra(infra) - Eventually(tc.Felixes[1].ExecOutputFn("ip", "r", "get", "10.65.1.2"), "10s").Should(ContainSubstring(w[1].InterfaceName)) By("Waiting for the config to appear in 'iptables-save/nft list ruleset' on workload 1") if NFTMode() { @@ -709,7 +720,6 @@ var _ = infrastructure.DatastoreDescribe( By("Removing all limits from workload 1") w[1].WorkloadEndpoint.Spec.QoSControls = nil w[1].UpdateInInfra(infra) - Eventually(tc.Felixes[1].ExecOutputFn("ip", "r", "get", "10.65.1.2"), "10s").Should(ContainSubstring(w[1].InterfaceName)) By("Waiting for the config to disappear in 'iptables-save/nft list ruleset' on workload 1") if NFTMode() { diff --git a/felix/fv/routes_test.go b/felix/fv/routes_test.go index 71776e581c6..93ee4fadc1b 100644 --- a/felix/fv/routes_test.go +++ b/felix/fv/routes_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -23,7 +21,7 @@ import ( "sync" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/sirupsen/logrus" @@ -62,26 +60,6 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ routing table tests", []api cc = &connectivity.Checker{} }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - felix.Exec("ip", "r") - } - } - - for _, wls := range w { - for _, wl := range wls { - wl.Stop() - } - } - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) - Describe("with a workload", func() { BeforeEach(func() { w[0][0] = workload.Run(tc.Felixes[0], @@ -165,7 +143,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ routing table tests", []api Describe("with locally conflicting IPs", func() { BeforeEach(func() { // Start two local workloads with the same IP >:) - for i := 0; i < 2; i++ { + for i := range 2 { w[0][i] = workload.Run(tc.Felixes[0], fmt.Sprintf("w%d", i), "default", "10.65.0.2", "8088", "tcp") w[0][i].ConfigureInInfra(infra) @@ -211,7 +189,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ routing table tests", []api Describe("with local/remote conflicting IPs", func() { BeforeEach(func() { // One local, one remote workload with same IP >:) - for i := 0; i < 2; i++ { + for i := range 2 { w[i][0] = workload.Run(tc.Felixes[i], fmt.Sprintf("w%d", i), "default", "10.65.0.2", "8088", "tcp") w[i][0].ConfigureInInfra(infra) } diff --git a/felix/fv/routing_test.go b/felix/fv/routing_test.go index 42daad662cb..8f6841f5245 100644 --- a/felix/fv/routing_test.go +++ b/felix/fv/routing_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -23,7 +21,7 @@ import ( "strings" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -69,8 +67,9 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ cluster routing using Felix routeSource := testConfig.RouteSource brokenXSum := testConfig.BrokenXSum enableIPv6 := testConfig.EnableIPv6 + description := fmt.Sprintf("with topology set to IPIPMode %s, routeSource %s, brokenXSum: %v, enableIPv6: %v", ipipMode, routeSource, brokenXSum, enableIPv6) - Describe(fmt.Sprintf("with topology set to IPIPMode %s, routeSource %s, brokenXSum: %v, enableIPv6: %v", ipipMode, routeSource, brokenXSum, enableIPv6), func() { + Describe(description, func() { var ( infra infrastructure.DatastoreInfra tc infrastructure.TopologyContainers @@ -82,7 +81,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ cluster routing using Felix hostW6 [3]*workload.Workload cc *connectivity.Checker topologyOptions infrastructure.TopologyOptions - timeout time.Duration = time.Second * 30 + timeout = time.Second * 30 ) BeforeEach(func() { @@ -100,47 +99,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ cluster routing using Felix cc = &connectivity.Checker{} }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - if NFTMode() { - logNFTDiags(felix) - } else { - felix.Exec("iptables-save", "-c") - felix.Exec("ipset", "list") - } - felix.Exec("ip", "r") - if enableIPv6 { - felix.Exec("ip", "-6", "route") - } - felix.Exec("ip", "a") - if BPFMode() { - felix.Exec("calico-bpf", "policy", "dump", "eth0", "all", "--asm") - } - } - } - - for _, wl := range w { - wl.Stop() - } - for _, wl := range w6 { - wl.Stop() - } - for _, wl := range hostW { - wl.Stop() - } - for _, wl := range hostW6 { - wl.Stop() - } - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) - - // Only applicable to IPIP encap + // These tests are only applicable to IPIP encap if ipipMode != api.IPIPModeNever { if brokenXSum { It("should disable checksum offload", func() { @@ -179,6 +138,162 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ cluster routing using Felix } } }) + + It("should configure the ipip device correctly", func() { + // The ipip device should appear with default MTU, etc. FV environment uses MTU 1500, + // which means that we should expect 1480 after subtracting IPIP overhead for IPv4. + mtuStr := "mtu 1480" + for _, felix := range felixes { + Eventually(func() string { + out, _ := felix.ExecOutput("ip", "-d", "link", "show", dataplanedefs.IPIPIfaceName) + return out + }, "60s", "500ms").Should(ContainSubstring(mtuStr)) + } + + // Change the host device's MTU, and expect the IPIP device to be updated. + for _, felix := range felixes { + Eventually(func() error { + _, err := felix.ExecOutput("ip", "link", "set", "eth0", "mtu", "1400") + return err + }, "15s", "100ms").Should(BeNil()) + } + + // MTU should be auto-detected, and updated to the host MTU minus 20 bytes overhead IPIP. + mtuStr = "mtu 1380" + mtuValue := "1380" + for _, felix := range felixes { + // Felix checks host MTU every 30s + Eventually(func() string { + out, _ := felix.ExecOutput("ip", "-d", "link", "show", dataplanedefs.IPIPIfaceName) + return out + }, "60s", "500ms").Should(ContainSubstring(mtuStr)) + + // And expect the MTU file on disk to be updated. + Eventually(func() string { + out, _ := felix.ExecOutput("cat", "/var/lib/calico/mtu") + return out + }, "30s", "100ms").Should(ContainSubstring(mtuValue)) + } + + // Explicitly configure the MTU. + felixConfig := api.NewFelixConfiguration() // Create a default FelixConfiguration + felixConfig.Name = "default" + mtu := 1300 + felixConfig.Spec.IPIPMTU = &mtu + _, err := client.FelixConfigurations().Create(context.Background(), felixConfig, options.SetOptions{}) + Expect(err).NotTo(HaveOccurred()) + + // Expect the settings to be changed on the device. + for _, felix := range felixes { + // Felix checks host MTU every 30s + Eventually(func() string { + out, _ := felix.ExecOutput("ip", "-d", "link", "show", dataplanedefs.IPIPIfaceName) + return out + }, "60s", "500ms").Should(ContainSubstring("mtu 1300")) + } + }) + + Context("external nodes configured", func() { + var externalClient *containers.Container + + BeforeEach(func() { + externalClient = infrastructure.RunExtClient(infra, "ext-client") + + Eventually(func() error { + err := externalClient.ExecMayFail("ip", "tunnel", "add", "tunl0", "mode", "ipip") + if err != nil && strings.Contains(err.Error(), "SIOCADDTUNNEL: File exists") { + return nil + } + return err + }).Should(Succeed()) + + externalClient.Exec("ip", "link", "set", "tunl0", "up") + externalClient.Exec("ip", "addr", "add", "dev", "tunl0", "10.65.222.1") + externalClient.Exec("ip", "route", "add", "10.65.0.0/24", "via", + tc.Felixes[0].IP, "dev", "tunl0", "onlink") + }) + + JustAfterEach(func() { + if CurrentGinkgoTestDescription().Failed { + externalClient.Exec("ip", "r") + externalClient.Exec("ip", "l") + externalClient.Exec("ip", "a") + } + }) + + It("should allow IPIP to external client if it is in ExternalNodesCIDRList", func() { + By("testing that ext client ipip does not work if not part of ExternalNodesCIDRList") + for _, f := range tc.Felixes { + // Make sure that only the internal nodes are present in the ipset + if BPFMode() { + Eventually(f.BPFRoutes, "15s").Should(ContainSubstring(f.IP)) + Consistently(f.BPFRoutes).ShouldNot(ContainSubstring(externalClient.IP)) + } else if NFTMode() { + Eventually(f.NFTSetSizeFn("cali40all-hosts-net"), "15s", "200ms").Should(Equal(len(felixes))) + } else { + Eventually(f.IPSetSizeFn("cali40all-hosts-net"), "15s", "200ms").Should(Equal(len(felixes))) + } + } + + cc.ExpectNone(externalClient, w[0]) + cc.CheckConnectivityWithTimeout(timeout) + + By("changing configuration to include the external client") + + updateConfig := func(addr string) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + c, err := client.FelixConfigurations().Get(ctx, "default", options.GetOptions{}) + if err != nil { + // Create the default config if it doesn't already exist. + if _, ok := err.(cerrors.ErrorResourceDoesNotExist); ok { + c = api.NewFelixConfiguration() + c.Name = "default" + c, err = client.FelixConfigurations().Create(ctx, c, options.SetOptions{}) + Expect(err).NotTo(HaveOccurred()) + } else { + Expect(err).NotTo(HaveOccurred()) + } + } + c.Spec.ExternalNodesCIDRList = &[]string{addr} + logrus.WithFields(logrus.Fields{"felixconfiguration": c, "adding Addr": addr}).Info("Updating FelixConfiguration ") + _, err = client.FelixConfigurations().Update(ctx, c, options.SetOptions{}) + Expect(err).NotTo(HaveOccurred()) + } + + updateConfig(externalClient.IP) + + // Wait for the config to take + for _, f := range tc.Felixes { + if BPFMode() { + Eventually(f.BPFRoutes, "15s").Should(ContainSubstring(externalClient.IP)) + Expect(f.IPSetSize("cali40all-hosts-net")).To(BeZero(), + "BPF mode shouldn't program IP sets") + } else if NFTMode() { + Eventually(f.NFTSetSizeFn("cali40all-hosts-net"), "15s", "200ms").Should(Equal(len(felixes) + 1)) + } else { + Eventually(f.IPSetSizeFn("cali40all-hosts-net"), "15s", "200ms").Should(Equal(len(felixes) + 1)) + } + } + + // Pause felix[0], so it can't touch the dataplane; we want to + // test that felix[0] blocks the traffic. + pid := felixes[0].GetFelixPID() + felixes[0].Exec("kill", "-STOP", fmt.Sprint(pid)) + + tc.Felixes[0].Exec("ip", "route", "add", "10.65.222.1", "via", + externalClient.IP, "dev", dataplanedefs.IPIPIfaceName, "onlink", "proto", "90") + + if BPFMode() { + tc.Felixes[0].Exec("calico-bpf", "routes", "add", "10.65.222.1", "--nexthop", externalClient.IP, "--workload", "remote", "--tunneled") + } + + By("testing that the ext client can connect via ipip") + cc.ResetExpectations() + cc.ExpectSome(externalClient, w[0]) + cc.CheckConnectivityWithTimeout(timeout) + }) + }) } It("should have correct connectivity", func() { @@ -273,14 +388,14 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ cluster routing using Felix Expect(err).NotTo(HaveOccurred()) lines := strings.Split(strings.Trim(defaultRoute, "\n "), "\n") Expect(lines).To(HaveLen(1)) - defaultRouteArgs := strings.Split(strings.Replace(lines[0], "eth0", "bond0", -1), " ") + defaultRouteArgs := strings.Split(strings.ReplaceAll(lines[0], "eth0", "bond0"), " ") // Assuming the subnet route will be "proto kernel" and that will be the only such route. subnetRoute, err := felix.ExecOutput("ip", "route", "show", "proto", "kernel") Expect(err).NotTo(HaveOccurred()) lines = strings.Split(strings.Trim(subnetRoute, "\n "), "\n") Expect(lines).To(HaveLen(1), "expected only one proto kernel route, has docker's routing set-up changed?") - subnetArgs := strings.Split(strings.Replace(lines[0], "eth0", "bond0", -1), " ") + subnetArgs := strings.Split(strings.ReplaceAll(lines[0], "eth0", "bond0"), " ") // Add the bond, replacing eth0. felix.Exec("ip", "addr", "del", felix.IP, "dev", "eth0") @@ -522,7 +637,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ cluster routing using Felix Action: api.Allow, }, } - policy.Spec.Selector = fmt.Sprintf("has(host-endpoint)") + policy.Spec.Selector = "has(host-endpoint)" _, err := client.GlobalNetworkPolicies().Create(utils.Ctx, policy, utils.NoOptions) Expect(err).NotTo(HaveOccurred()) }) @@ -611,6 +726,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ cluster routing using Felix node.Spec.BGP = nil _, err = client.Nodes().Update(ctx, node, options.SetOptions{}) + Expect(err).NotTo(HaveOccurred()) }) It("should have no connectivity from third felix and expected number of IPs in allow list", func() { @@ -718,172 +834,70 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ cluster routing using Felix }) } }) + }) - It("should configure the ipip device correctly", func() { - if ipipMode == api.IPIPModeNever { - Skip("ipip device is not available in no encap routing") - } - // The ipip device should appear with default MTU, etc. FV environment uses MTU 1500, - // which means that we should expect 1480 after subtracting IPIP overhead for IPv4. - mtuStr := "mtu 1480" - for _, felix := range felixes { - Eventually(func() string { - out, _ := felix.ExecOutput("ip", "-d", "link", "show", dataplanedefs.IPIPIfaceName) - return out - }, "60s", "500ms").Should(ContainSubstring(mtuStr)) - } - - // Change the host device's MTU, and expect the IPIP device to be updated. - for _, felix := range felixes { - Eventually(func() error { - _, err := felix.ExecOutput("ip", "link", "set", "eth0", "mtu", "1400") - return err - }, "15s", "100ms").Should(BeNil()) - } - - // MTU should be auto-detected, and updated to the host MTU minus 20 bytes overhead IPIP. - mtuStr = "mtu 1380" - mtuValue := "1380" - for _, felix := range felixes { - // Felix checks host MTU every 30s - Eventually(func() string { - out, _ := felix.ExecOutput("ip", "-d", "link", "show", dataplanedefs.IPIPIfaceName) - return out - }, "60s", "500ms").Should(ContainSubstring(mtuStr)) - - // And expect the MTU file on disk to be updated. - Eventually(func() string { - out, _ := felix.ExecOutput("cat", "/var/lib/calico/mtu") - return out - }, "30s", "100ms").Should(ContainSubstring(mtuValue)) - } + Describe(description+" and borrowed workload IPs", func() { + var ( + infra infrastructure.DatastoreInfra + tc infrastructure.TopologyContainers + felixes []*infrastructure.Felix + client client.Interface + w [3]*workload.Workload + w6 [3]*workload.Workload + cc *connectivity.Checker + topologyOptions infrastructure.TopologyOptions + timeout = time.Second * 30 + ) - // Explicitly configure the MTU. - felixConfig := api.NewFelixConfiguration() // Create a default FelixConfiguration - felixConfig.Name = "default" - mtu := 1300 - felixConfig.Spec.IPIPMTU = &mtu - _, err := client.FelixConfigurations().Create(context.Background(), felixConfig, options.SetOptions{}) - Expect(err).NotTo(HaveOccurred()) + BeforeEach(func() { + infra = getInfra() - // Expect the settings to be changed on the device. - for _, felix := range felixes { - // Felix checks host MTU every 30s - Eventually(func() string { - out, _ := felix.ExecOutput("ip", "-d", "link", "show", dataplanedefs.IPIPIfaceName) - return out - }, "60s", "500ms").Should(ContainSubstring("mtu 1300")) + if (NFTMode() || BPFMode()) && getDataStoreType(infra) == "etcdv3" { + Skip("Skipping NFT / BPF tests for etcdv3 backend.") } - }) - - Context("external nodes configured", func() { - var externalClient *containers.Container - BeforeEach(func() { - externalClient = infrastructure.RunExtClient("ext-client") - - Eventually(func() error { - err := externalClient.ExecMayFail("ip", "tunnel", "add", "tunl0", "mode", "ipip") - if err != nil && strings.Contains(err.Error(), "SIOCADDTUNNEL: File exists") { - return nil - } - return err - }).Should(Succeed()) - - externalClient.Exec("ip", "link", "set", "tunl0", "up") - externalClient.Exec("ip", "addr", "add", "dev", "tunl0", "10.65.222.1") - externalClient.Exec("ip", "route", "add", "10.65.0.0/24", "via", - tc.Felixes[0].IP, "dev", "tunl0", "onlink") - }) - - JustAfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - externalClient.Exec("ip", "r") - externalClient.Exec("ip", "l") - externalClient.Exec("ip", "a") - } - }) + topologyOptions = createIPIPBaseTopologyOptions(ipipMode, enableIPv6, routeSource, brokenXSum) - AfterEach(func() { - externalClient.Stop() - }) + cc = &connectivity.Checker{} - It("should allow IPIP to external client if it is in ExternalNodesCIDRList", func() { - if ipipMode == api.IPIPModeNever { - Skip("external nodes is not applicable to no encap routing") - } + // Deploy the topology. + tc, client = infrastructure.StartNNodeTopology(3, topologyOptions, infra) - By("testing that ext client ipip does not work if not part of ExternalNodesCIDRList") - for _, f := range tc.Felixes { - // Make sure that only the internal nodes are present in the ipset - if BPFMode() { - Eventually(f.BPFRoutes, "15s").Should(ContainSubstring(f.IP)) - Consistently(f.BPFRoutes).ShouldNot(ContainSubstring(externalClient.IP)) - } else if NFTMode() { - Eventually(f.NFTSetSizeFn("cali40all-hosts-net"), "15s", "200ms").Should(Equal(len(felixes))) - } else { - Eventually(f.IPSetSizeFn("cali40all-hosts-net"), "15s", "200ms").Should(Equal(len(felixes))) - } - } + // Assign tunnel addresses in IPAM based on the topology. + // This will assign blocks to particular nodes so that the + // workload IP assignments below will borrow. + assignTunnelAddresses(infra, tc, client) - cc.ExpectNone(externalClient, w[0]) - cc.CheckConnectivityWithTimeout(timeout) + // Offset of +1 means that felix[0]'s workload borrows its IP from + // felix[1]'s block and so on. + w, w6, _, _ = setupWorkloadsWithOffset(infra, tc, topologyOptions, client, enableIPv6, 1) + felixes = tc.Felixes + }) - By("changing configuration to include the external client") - - updateConfig := func(addr string) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - c, err := client.FelixConfigurations().Get(ctx, "default", options.GetOptions{}) - if err != nil { - // Create the default config if it doesn't already exist. - if _, ok := err.(cerrors.ErrorResourceDoesNotExist); ok { - c = api.NewFelixConfiguration() - c.Name = "default" - c, err = client.FelixConfigurations().Create(ctx, c, options.SetOptions{}) - Expect(err).NotTo(HaveOccurred()) - } else { - Expect(err).NotTo(HaveOccurred()) - } - } - c.Spec.ExternalNodesCIDRList = &[]string{addr} - logrus.WithFields(logrus.Fields{"felixconfiguration": c, "adding Addr": addr}).Info("Updating FelixConfiguration ") - _, err = client.FelixConfigurations().Update(ctx, c, options.SetOptions{}) - Expect(err).NotTo(HaveOccurred()) - } + It("should have connectivity", func() { + if !ipipTunnelSupported(ipipMode, routeSource) { + Skip("Skipping due to known issue with tunnel IPs not being programmed in WEP mode") + } - updateConfig(externalClient.IP) + for i := range 3 { + f := felixes[i] + cc.ExpectSome(f, w[i]) // Host to local workload. + cc.ExpectSome(f, w[(i+1)%3]) // Host to next node's workload + cc.ExpectSome(w[i], w[(i+1)%3]) // Local workload to next node's workload. - // Wait for the config to take - for _, f := range tc.Felixes { - if BPFMode() { - Eventually(f.BPFRoutes, "15s").Should(ContainSubstring(externalClient.IP)) - Expect(f.IPSetSize("cali40all-hosts-net")).To(BeZero(), - "BPF mode shouldn't program IP sets") - } else if NFTMode() { - Eventually(f.NFTSetSizeFn("cali40all-hosts-net"), "15s", "200ms").Should(Equal(len(felixes) + 1)) - } else { - Eventually(f.IPSetSizeFn("cali40all-hosts-net"), "15s", "200ms").Should(Equal(len(felixes) + 1)) - } + if enableIPv6 { + cc.ExpectSome(f, w6[i]) + cc.ExpectSome(f, w6[(i+1)%3]) + cc.ExpectSome(w6[i], w6[(i+1)%3]) } - // Pause felix[0], so it can't touch the dataplane; we want to - // test that felix[0] blocks the traffic. - pid := felixes[0].GetFelixPID() - felixes[0].Exec("kill", "-STOP", fmt.Sprint(pid)) - - tc.Felixes[0].Exec("ip", "route", "add", "10.65.222.1", "via", - externalClient.IP, "dev", dataplanedefs.IPIPIfaceName, "onlink", "proto", "90") + } - By("testing that the ext client can connect via ipip") - cc.ResetExpectations() - cc.ExpectSome(externalClient, w[0]) - cc.CheckConnectivityWithTimeout(timeout) - }) + cc.CheckConnectivityWithTimeout(timeout) }) }) - Describe("with a borrowed tunnel IP on one host", func() { + Describe(description+" and a borrowed tunnel IP on one host", func() { var ( infra infrastructure.DatastoreInfra tc infrastructure.TopologyContainers @@ -891,11 +905,9 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ cluster routing using Felix client client.Interface w [3]*workload.Workload w6 [3]*workload.Workload - hostW [3]*workload.Workload - hostW6 [3]*workload.Workload cc *connectivity.Checker topologyOptions infrastructure.TopologyOptions - timeout time.Duration = time.Second * 30 + timeout = time.Second * 30 ) BeforeEach(func() { @@ -913,59 +925,19 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ cluster routing using Felix // Deploy the topology. tc, client = infrastructure.StartNNodeTopology(3, topologyOptions, infra) - w, w6, hostW, hostW6 = setupWorkloads(infra, tc, topologyOptions, client, enableIPv6) + w, w6, _, _ = setupWorkloads(infra, tc, topologyOptions, client, enableIPv6) felixes = tc.Felixes // Assign tunnel addresees in IPAM based on the topology. assignTunnelAddresses(infra, tc, client) }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range felixes { - if NFTMode() { - logNFTDiags(felix) - } else { - felix.Exec("iptables-save", "-c") - felix.Exec("ipset", "list") - } - felix.Exec("ipset", "list") - felix.Exec("ip", "r") - felix.Exec("ip", "a") - felix.Exec("calico-bpf", "routes", "dump") - if enableIPv6 { - felix.Exec("ip", "-6", "route") - felix.Exec("calico-bpf", "-6", "routes", "dump") - } - } - } - - for _, wl := range w { - wl.Stop() - } - for _, wl := range w6 { - wl.Stop() - } - for _, wl := range hostW { - wl.Stop() - } - for _, wl := range hostW6 { - wl.Stop() - } - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) - It("should have host to workload connectivity", func() { if !ipipTunnelSupported(ipipMode, routeSource) { Skip("Skipping due to known issue with tunnel IPs not being programmed in WEP mode") } - for i := 0; i < 3; i++ { + for i := range 3 { f := felixes[i] cc.ExpectSome(f, w[0]) cc.ExpectSome(f, w[1]) @@ -994,7 +966,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ cluster routing using Felix hostW6 [3]*workload.Workload cc *connectivity.Checker topologyOptions infrastructure.TopologyOptions - timeout time.Duration = time.Second * 30 + timeout = time.Second * 30 ) BeforeEach(func() { @@ -1133,7 +1105,7 @@ func createK8sServiceWithoutKubeProxy(args createK8sServiceWithoutKubeProxyArgs) if BPFMode() { k8sClient := args.infra.(*infrastructure.K8sDatastoreInfra).K8sClient testSvc := k8sService(args.svcName, args.serviceIP, args.w, args.port, args.tgtPort, 0, "tcp") - testSvcNamespace := testSvc.ObjectMeta.Namespace + testSvcNamespace := testSvc.Namespace _, err := k8sClient.CoreV1().Services(testSvcNamespace).Create(context.Background(), testSvc, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) Eventually(k8sGetEpsForServiceFunc(k8sClient, testSvc), "10s").Should(HaveLen(1), @@ -1168,10 +1140,8 @@ func createIPIPBaseTopologyOptions( topologyOptions.IPIPMode = ipipMode topologyOptions.IPIPStrategy = infrastructure.NewDefaultTunnelStrategy(topologyOptions.IPPoolCIDR, topologyOptions.IPv6PoolCIDR) topologyOptions.VXLANMode = api.VXLANModeNever - topologyOptions.SimulateBIRDRoutes = false topologyOptions.EnableIPv6 = enableIPv6 topologyOptions.FelixLogSeverity = "Debug" - topologyOptions.ExtraEnvVars["FELIX_ProgramClusterRoutes"] = "Enabled" topologyOptions.ExtraEnvVars["FELIX_ROUTESOURCE"] = routeSource // We force the broken checksum handling on or off so that we're not dependent on kernel version // for these tests. Since we're testing in containers anyway, checksum offload can't really be @@ -1207,5 +1177,34 @@ func allHostsIPSetSize(felixes []*infrastructure.Felix, ipipMode api.IPIPMode) i } func ipipTunnelSupported(ipipMode api.IPIPMode, routeSource string) bool { - return !(ipipMode == api.IPIPModeAlways && routeSource == "WorkloadIPs") + return ipipMode != api.IPIPModeAlways || routeSource != "WorkloadIPs" +} + +func ensureRoutesProgrammed(felixes []*infrastructure.Felix) { + for _, felix := range felixes { + ensureFelixRoutesProgrammed(felix) + } +} + +func ensureFelixRoutesProgrammed(felix *infrastructure.Felix) { + routesExist := func() bool { + cmdv4 := []string{"ip", "route", "show"} + outv4, err := felix.ExecOutput(cmdv4...) + Expect(err).NotTo(HaveOccurred()) + + // Check for the default route protocol used in Felix or Calico vxlan devices. + if strings.Contains(outv4, fmt.Sprintf("proto %v", dataplanedefs.DefaultRouteProto)) || + strings.Contains(outv4, dataplanedefs.VXLANIfaceNameV4) { + return true + } + + cmdv6 := []string{"ip", "-6", "route", "show"} + outv6, err := felix.ExecOutput(cmdv6...) + Expect(err).NotTo(HaveOccurred()) + // Check for the default route protocol used in Felix or Calico vxlan devices. + return strings.Contains(outv6, fmt.Sprintf("proto %v", dataplanedefs.DefaultRouteProto)) || + strings.Contains(outv6, dataplanedefs.VXLANIfaceNameV6) + } + EventuallyWithOffset(2, routesExist, "1m", "1s").Should(BeTrue()) + ConsistentlyWithOffset(2, routesExist, "3s", "1s").Should(BeTrue()) } diff --git a/felix/fv/rpf_test.go b/felix/fv/rpf_test.go index f0caa15fed9..fc18844d91e 100644 --- a/felix/fv/rpf_test.go +++ b/felix/fv/rpf_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -21,7 +19,7 @@ import ( "os" "regexp" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" @@ -105,7 +103,7 @@ var _ = infrastructure.DatastoreDescribe( InterfaceName: "eth20", MTU: 1500, // Need to match host MTU or felix will restart. } - err := external.Start() + err := external.Start(infra) Expect(err).NotTo(HaveOccurred()) // assign address to eth20 and add route to the .20 network @@ -126,32 +124,6 @@ var _ = infrastructure.DatastoreDescribe( }) }) - JustAfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - if NFTMode() { - logNFTDiags(felix) - } else { - felix.Exec("iptables-save", "-c") - } - felix.Exec("ip", "link") - felix.Exec("ip", "addr") - felix.Exec("ip", "rule") - felix.Exec("ip", "route") - } - } - }) - - AfterEach(func() { - log.Info("AfterEach starting") - for _, f := range tc.Felixes { - f.Exec("calico-bpf", "connect-time", "clean") - f.Stop() - } - infra.Stop() - log.Info("AfterEach done") - }) - Context("With BPFEnforceRPF=Disabled", func() { BeforeEach(func() { options.ExtraEnvVars["FELIX_BPFEnforceRPF"] = "Disabled" @@ -164,15 +136,13 @@ var _ = infrastructure.DatastoreDescribe( tcpdumpHEP.SetLogEnabled(true) matcherHEP := fmt.Sprintf("IP %s\\.30446 > %s\\.30446: UDP", fakeWorkloadIP, w.IP) tcpdumpHEP.AddMatcher("UDP-30446", regexp.MustCompile(matcherHEP)) - tcpdumpHEP.Start() - defer tcpdumpHEP.Stop() + tcpdumpHEP.Start(infra) tcpdumpWl := w.AttachTCPDump() tcpdumpWl.SetLogEnabled(true) matcherWl := fmt.Sprintf("IP %s\\.30446 > %s\\.30446: UDP", fakeWorkloadIP, w.IP) tcpdumpWl.AddMatcher("UDP-30446", regexp.MustCompile(matcherWl)) - tcpdumpWl.Start() - defer tcpdumpWl.Stop() + tcpdumpWl.Start(infra) _, err := external.RunCmd("pktgen", fakeWorkloadIP, w.IP, "udp", "--port-src", "30446", "--port-dst", "30446", "--ip-id", "666") @@ -200,15 +170,13 @@ var _ = infrastructure.DatastoreDescribe( tcpdumpHEP.SetLogEnabled(true) matcherHEP := fmt.Sprintf("IP %s\\.30446 > %s\\.30446: UDP", fakeWorkloadIP, w.IP) tcpdumpHEP.AddMatcher("UDP-30446", regexp.MustCompile(matcherHEP)) - tcpdumpHEP.Start() - defer tcpdumpHEP.Stop() + tcpdumpHEP.Start(infra) tcpdumpWl := w.AttachTCPDump() tcpdumpWl.SetLogEnabled(true) matcherWl := fmt.Sprintf("IP %s\\.30446 > %s\\.30446: UDP", fakeWorkloadIP, w.IP) tcpdumpWl.AddMatcher("UDP-30446", regexp.MustCompile(matcherWl)) - tcpdumpWl.Start() - defer tcpdumpWl.Stop() + tcpdumpWl.Start(infra) _, err := external.RunCmd("pktgen", fakeWorkloadIP, w.IP, "udp", "--port-src", "30446", "--port-dst", "30446", "--ip-id", "666") @@ -234,15 +202,13 @@ var _ = infrastructure.DatastoreDescribe( tcpdumpHEP.SetLogEnabled(true) matcherHEP := fmt.Sprintf("IP %s\\.30446 > %s\\.30446: UDP", fakeWorkloadIP, w.IP) tcpdumpHEP.AddMatcher("UDP-30446", regexp.MustCompile(matcherHEP)) - tcpdumpHEP.Start() - defer tcpdumpHEP.Stop() + tcpdumpHEP.Start(infra) tcpdumpWl := w.AttachTCPDump() tcpdumpWl.SetLogEnabled(true) matcherWl := fmt.Sprintf("IP %s\\.30446 > %s\\.30446: UDP", fakeWorkloadIP, w.IP) tcpdumpWl.AddMatcher("UDP-30446", regexp.MustCompile(matcherWl)) - tcpdumpWl.Start() - defer tcpdumpWl.Stop() + tcpdumpWl.Start(infra) _, err := external.RunCmd("pktgen", fakeWorkloadIP, w.IP, "udp", "--port-src", "30446", "--port-dst", "30446", "--ip-id", "666") diff --git a/felix/fv/rule_metadata_test.go b/felix/fv/rule_metadata_test.go index 6f901d33ea1..23765e3c241 100644 --- a/felix/fv/rule_metadata_test.go +++ b/felix/fv/rule_metadata_test.go @@ -12,37 +12,35 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( - "math/rand" + cryptorand "crypto/rand" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/calico/felix/fv/connectivity" - "github.com/projectcalico/calico/felix/fv/containers" "github.com/projectcalico/calico/felix/fv/infrastructure" "github.com/projectcalico/calico/felix/fv/utils" "github.com/projectcalico/calico/felix/fv/workload" + "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" ) -var _ = Describe("Rule Metadata tests", func() { +var _ = infrastructure.DatastoreDescribe("Rule Metadata tests", []apiconfig.DatastoreType{apiconfig.EtcdV3}, func(getInfra infrastructure.InfraFactory) { var ( tc infrastructure.TopologyContainers client client.Interface infra infrastructure.DatastoreInfra - etcd *containers.Container wl0 *workload.Workload wl1 *workload.Workload ) BeforeEach(func() { - tc, etcd, client, infra = infrastructure.StartSingleNodeEtcdTopology(infrastructure.DefaultTopologyOptions()) + infra = getInfra() + tc, client = infrastructure.StartSingleNodeTopology(infrastructure.DefaultTopologyOptions(), infra) wl0 = workload.Run(tc.Felixes[0], "test0", "default", "10.65.0.1", "80", "tcp") wl0.Configure(client) @@ -51,19 +49,6 @@ var _ = Describe("Rule Metadata tests", func() { wl1.Configure(client) }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - logNFTDiags(felix) - } - } - wl0.Stop() - wl1.Stop() - tc.Stop() - etcd.Stop() - infra.Stop() - }) - Context("With a GlobalNetworkPolicy with rule metadata", func() { var gnp *api.GlobalNetworkPolicy @@ -142,7 +127,7 @@ var _ = Describe("Rule Metadata tests", func() { BeforeEach(func() { // build some random bytes to try to break annotation processing rv := make([]byte, 200) - _, err := rand.Read(rv) + _, err := cryptorand.Read(rv) Expect(err).ToNot(HaveOccurred()) // the profile should allow the workloads to communicate p = api.NewProfile() diff --git a/felix/fv/run-batches b/felix/fv/run-batches index 241d7776dfe..a31f66a6150 100755 --- a/felix/fv/run-batches +++ b/felix/fv/run-batches @@ -16,42 +16,29 @@ : ${FV_NUM_BATCHES:=4} : ${FV_BATCHES_TO_RUN:=1 2 3 4} -: ${FV_SLOW_SPEC_THRESH:=90} +: ${FV_SLOW_SPEC_THRESH:="90s"} : ${FV_BINARY:=bin/calico-felix-amd64} -: ${PRIVATE_KEY:=`pwd`/private.key} +: ${PRIVATE_KEY:=$(pwd)/private.key} : ${GINKGO_ARGS:=} : ${GINKGO_FOCUS:=.*} mkdir -p cwlogs -export FV_CWLOGDIR=`pwd`/cwlogs +export FV_CWLOGDIR=$(pwd)/cwlogs +export OS_RELEASE=$(lsb_release -rs 2> /dev/null || echo "unknown") for batch in ${FV_BATCHES_TO_RUN}; do ( - echo "Running FV batch ${batch}" - # List the tests that will be run. - ./fv.test -ginkgo.parallel.node ${batch} \ - -ginkgo.parallel.total ${FV_NUM_BATCHES} \ - -ginkgo.seed 1 \ - -ginkgo.randomizeAllSpecs=true \ - -ginkgo.noisySkippings=false \ - -ginkgo.slowSpecThreshold ${FV_SLOW_SPEC_THRESH} \ - -ginkgo.focus="${GINKGO_FOCUS}" \ - ${GINKGO_ARGS} \ - -ginkgo.v \ - -ginkgo.noColor \ - -ginkgo.dryRun 2>&1 | awk -f test-list-filter.awk - # Now really run them. - ./fv.test -ginkgo.parallel.node ${batch} \ - -ginkgo.parallel.total ${FV_NUM_BATCHES} \ - -ginkgo.seed 1 \ - -ginkgo.randomizeAllSpecs=true \ - -ginkgo.noisySkippings=false \ - -ginkgo.slowSpecThreshold ${FV_SLOW_SPEC_THRESH} \ - -ginkgo.focus="${GINKGO_FOCUS}" \ - ${GINKGO_ARGS} - status=$? - echo "Test batch $batch completed with status $status" - exit $status + echo "Running FV batch ${batch}" + FV_BATCH=${batch} FV_NUM_BATCHES=${FV_NUM_BATCHES} ./fv.test \ + --ginkgo.randomize-all \ + --ginkgo.seed 1 \ + --ginkgo.silence-skips \ + --ginkgo.slow-spec-threshold=${FV_SLOW_SPEC_THRESH} \ + --ginkgo.focus="${GINKGO_FOCUS}" \ + ${GINKGO_ARGS} + status=$? + echo "Test batch $batch completed with status $status" + exit $status ) & pids[${batch}]=$! done @@ -67,6 +54,8 @@ for batch in ${FV_BATCHES_TO_RUN}; do fi done +docker ps + if [ $result -eq 0 ]; then echo "All tests passed" else diff --git a/felix/fv/run-test b/felix/fv/run-test index 0eab182cf83..c5eeb2127b2 100755 --- a/felix/fv/run-test +++ b/felix/fv/run-test @@ -2,4 +2,4 @@ export FV_FELIXIMAGE="calico/felix-test-amd64:latest" export PRIVATE_KEY="`pwd`/private.key" -exec ginkgo "$@" --tags fvtests . +exec ginkgo "$@" . diff --git a/felix/fv/service_loop_prevention_test.go b/felix/fv/service_loop_prevention_test.go index 2fe24992011..68d679676cc 100644 --- a/felix/fv/service_loop_prevention_test.go +++ b/felix/fv/service_loop_prevention_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -22,7 +20,7 @@ import ( "strings" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -57,26 +55,6 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ service loop prevention; wi } }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - if NFTMode() { - logNFTDiags(felix) - } else { - felix.Exec("iptables-save", "-c") - felix.Exec("ipset", "list") - } - felix.Exec("ip", "r") - felix.Exec("ip", "a") - } - } - tc.Stop() - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) - updateBGPConfig := func(deltaFn func(*api.BGPConfiguration)) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() @@ -100,7 +78,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ service loop prevention; wi out, err := felix.ExecOutput(saveCommand, "-t", "filter") Expect(err).NotTo(HaveOccurred()) var cidrBlockLines []string - for _, line := range strings.Split(out, "\n") { + for line := range strings.SplitSeq(out, "\n") { if strings.Contains(line, "-A cali-cidr-block") { cidrBlockLines = append(cidrBlockLines, line) } @@ -112,11 +90,8 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ service loop prevention; wi tryRoutingLoop := func(expectLoop bool, count int) { // Run containers to model a default gateway, and an external client connecting to // services within the cluster via that gateway. - externalGW := infrastructure.RunExtClient("ext-gw") - defer externalGW.Stop() - - externalClient := infrastructure.RunExtClient("ext-client") - defer externalClient.Stop() + externalGW := infrastructure.RunExtClient(infra, "ext-gw") + externalClient := infrastructure.RunExtClient(infra, "ext-client") // Add a service CIDR route in those containers, similar to the routes that they // would have via BGP per our service advertisement feature. (This should really be @@ -131,16 +106,15 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ service loop prevention; wi externalGW.Exec("sysctl", "-w", "net.ipv4.ip_forward=1") // Also tell Felix to route that CIDR to the external gateway. - tc.Felixes[0].ExecMayFail("ip", "r", "d", "10.96.0.0/17") + _ = tc.Felixes[0].ExecMayFail("ip", "r", "d", "10.96.0.0/17") tc.Felixes[0].Exec("ip", "r", "a", "10.96.0.0/17", "via", externalGW.IP) tc.Felixes[0].Exec("iptables", "-P", "FORWARD", "ACCEPT") // Start monitoring all packets, on the Felix, to or from a specific (but // unused) service IP. tcpdumpF := tc.Felixes[0].AttachTCPDump("eth0") - tcpdumpF.AddMatcher("serviceIPPackets", regexp.MustCompile("10\\.96\\.0\\.19")) - tcpdumpF.Start() - defer tcpdumpF.Stop() + tcpdumpF.AddMatcher("serviceIPPackets", regexp.MustCompile(`10\.96\.0\.19`)) + tcpdumpF.Start(infra) // Send a single ping from the external client to the unused service IP. err := externalClient.ExecMayFail("ping", "-c", "1", "-W", "1", "10.96.0.19") diff --git a/felix/fv/service_policy_test.go b/felix/fv/service_policy_test.go index 6630987bba2..a8d0ceffa75 100644 --- a/felix/fv/service_policy_test.go +++ b/felix/fv/service_policy_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2024 Tigera, Inc. All rights reserved. +// Copyright (c) 2021-2025 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,15 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( - "context" "fmt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -34,8 +31,6 @@ import ( "github.com/projectcalico/calico/felix/fv/workload" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" - "github.com/projectcalico/calico/libcalico-go/lib/ipam" - "github.com/projectcalico/calico/libcalico-go/lib/net" "github.com/projectcalico/calico/libcalico-go/lib/options" ) @@ -62,16 +57,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Service network policy test for ii := range w { wIP := fmt.Sprintf("10.65.%d.2", ii) wName := fmt.Sprintf("w%d", ii) - err := client.IPAM().AssignIP(context.Background(), ipam.AssignIPArgs{ - IP: net.MustParseIP(wIP), - HandleID: &wName, - Attrs: map[string]string{ - ipam.AttributeNode: tc.Felixes[ii].Hostname, - }, - Hostname: tc.Felixes[ii].Hostname, - }) - Expect(err).NotTo(HaveOccurred()) - + infrastructure.AssignIP(wName, wIP, tc.Felixes[ii].Hostname, client) w[ii] = workload.Run(tc.Felixes[ii], wName, "default", wIP, "80,81", "tcp") w[ii].ConfigureInInfra(infra) @@ -81,34 +67,6 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ Service network policy test cc = &connectivity.Checker{} }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - if NFTMode() { - logNFTDiags(felix) - } else { - felix.Exec("iptables-save", "-c") - felix.Exec("ipset", "list") - } - felix.Exec("ip", "r") - felix.Exec("ip", "a") - } - } - - for _, wl := range w { - wl.Stop() - } - for _, wl := range hostW { - wl.Stop() - } - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) - It("should allow egress to a service", func() { // Expect basic connectivity to work. cc.ExpectSome(w[0], w[1].Port(80)) diff --git a/felix/fv/sockmap_test.go b/felix/fv/sockmap_test.go index 16e4e0b0ad0..b6a8b991463 100644 --- a/felix/fv/sockmap_test.go +++ b/felix/fv/sockmap_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -27,7 +25,7 @@ import ( "strings" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" @@ -85,7 +83,7 @@ func testIPToHex(ip string) []string { } func getEndpointsMapContents(felix *infrastructure.Felix) [][]string { - output, err := felix.Container.ExecOutput( + output, err := felix.ExecOutput( "bpftool", "--json", "--pretty", @@ -261,7 +259,7 @@ var _ = infrastructure.DatastoreDescribe("[SOCKMAP] with Felix using sockmap", [ AfterEach(func() { defer unlockSockmapTest() host.Stop() - tc.Stop() + // Clean up the sockmap state. We do this by starting // a new felix instance (after stopping the old one), // so it cleans up the whatever state we ended up @@ -275,7 +273,7 @@ var _ = infrastructure.DatastoreDescribe("[SOCKMAP] with Felix using sockmap", [ opts := getSockmapOpts() tc, _ = infrastructure.StartSingleNodeTopology(opts, infra) defer tc.Stop() - output, err := tc.Felixes[0].Container.ExecOutput( + output, err := tc.Felixes[0].ExecOutput( "bpftool", "--json", "--pretty", @@ -286,20 +284,20 @@ var _ = infrastructure.DatastoreDescribe("[SOCKMAP] with Felix using sockmap", [ ) if err != nil { log.WithFields(log.Fields{ - "containerID": tc.Felixes[0].Container.Name, + "containerID": tc.Felixes[0].Name, "output": output, }).WithError(err).Info("Failed to dump the contents of the sock map, skipping cleanup") return } if strings.TrimSpace(output) != "[]" { log.WithFields(log.Fields{ - "containerID": tc.Felixes[0].Container.Name, + "containerID": tc.Felixes[0].Name, "output": output, }).Info("Sock map is not empty, skipping cleanup") return } fullCgroupDir := "/run/calico/cgroup" - output, err = tc.Felixes[0].Container.ExecOutput( + output, err = tc.Felixes[0].ExecOutput( "bpftool", "cgroup", "detach", @@ -311,14 +309,13 @@ var _ = infrastructure.DatastoreDescribe("[SOCKMAP] with Felix using sockmap", [ if err != nil { log.WithFields(log.Fields{ "cgroupdir": fullCgroupDir, - "containerID": tc.Felixes[0].Container.Name, + "containerID": tc.Felixes[0].Name, "output": output, }).WithError(err).Info("Failed to detach sockops program from cgroup, skipping cleanup") return } log.Info("Cleanup finished") }() - infra.Stop() }) It("should put the IP of the host in sockmap endpoints map", func() { diff --git a/felix/fv/spoof_test.go b/felix/fv/spoof_test.go index 28186408186..ff27faf9770 100644 --- a/felix/fv/spoof_test.go +++ b/felix/fv/spoof_test.go @@ -12,15 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( "fmt" "regexp" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -28,48 +26,28 @@ import ( "github.com/projectcalico/calico/felix/fv/containers" "github.com/projectcalico/calico/felix/fv/infrastructure" "github.com/projectcalico/calico/felix/fv/workload" + "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" + client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" ) -var _ = Describe("Spoof tests", func() { +var _ = infrastructure.DatastoreDescribe("Spoof tests", []apiconfig.DatastoreType{apiconfig.EtcdV3}, func(getInfra infrastructure.InfraFactory) { var ( - infra infrastructure.DatastoreInfra - tc infrastructure.TopologyContainers - w [3]*workload.Workload - cc *connectivity.Checker + infra infrastructure.DatastoreInfra + tc infrastructure.TopologyContainers + w [3]*workload.Workload + cc *connectivity.Checker + calicoClient client.Interface ) - teardownInfra := func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - if NFTMode() { - logNFTDiags(felix) - } else { - - felix.Exec("iptables-save", "-c") - felix.Exec("ip6tables-save", "-c") - } - felix.Exec("ipset", "list") - felix.Exec("ip", "r") - felix.Exec("ip", "-6", "r") - felix.Exec("ip", "a") - felix.Exec("ip", "-6", "a") - } - } - for _, wl := range w { - wl.Stop() - } - tc.Stop() - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - } + BeforeEach(func() { + infra = getInfra() + }) spoofTests := func() { It("should drop spoofed traffic", func() { cc = &connectivity.Checker{} // Setup a spoofed workload. Make w[0] spoof w[2] by making it - // use w[2]'s IP to test connections. + // use w[2]'s IP to test connections spoofed := &workload.SpoofedWorkload{ Workload: w[0], SpoofedSourceIP: w[2].IP, @@ -103,21 +81,17 @@ var _ = Describe("Spoof tests", func() { externalClient *containers.Container ) BeforeEach(func() { - externalClient = infrastructure.RunExtClient("ext-client") + externalClient = infrastructure.RunExtClient(infra, "ext-client") err := externalClient.CopyFileIntoContainer("../bin/pktgen", "pktgen") Expect(err).NotTo(HaveOccurred()) }) - AfterEach(func() { - externalClient.Stop() - }) It("should send RST for a stray TCP packet", func() { tcpdump := tc.Felixes[0].AttachTCPDump("eth0") tcpdump.SetLogEnabled(true) pattern := fmt.Sprintf(`IP %s\.1234 > %s\.3434: Flags \[R\], seq 123`, tc.Felixes[0].IP, externalClient.IP) tcpdump.AddMatcher("RST", regexp.MustCompile(pattern)) - tcpdump.Start("tcp", "port", "1234") - defer tcpdump.Stop() + tcpdump.Start(infra, "tcp", "port", "1234") err := externalClient.ExecMayFail("pktgen", externalClient.IP, tc.Felixes[0].IP, "tcp", "--port-src", "3434", "--port-dst", "1234", "--tcp-ack", "--tcp-ack-no=123", "--tcp-seq-no=111") @@ -131,13 +105,10 @@ var _ = Describe("Spoof tests", func() { Context("_BPF-SAFE_ IPv4", func() { BeforeEach(func() { - var err error - infra, err = infrastructure.GetEtcdDatastoreInfra() - Expect(err).NotTo(HaveOccurred()) opts := infrastructure.DefaultTopologyOptions() opts.ExtraEnvVars["FELIX_BPFConnectTimeLoadBalancing"] = string(api.BPFConnectTimeLBDisabled) opts.ExtraEnvVars["FELIX_BPFHostNetworkedNATWithoutCTLB"] = string(api.BPFHostNetworkedNATEnabled) - tc, _ = infrastructure.StartNNodeTopology(3, opts, infra) + tc, calicoClient = infrastructure.StartNNodeTopology(3, opts, infra) // Install a default profile allowing all ingress and egress, // in the absence of policy. infra.AddDefaultAllow() @@ -146,6 +117,7 @@ var _ = Describe("Spoof tests", func() { for ii := range w { wIP := fmt.Sprintf("10.65.%d.2", ii) wName := fmt.Sprintf("w%d", ii) + infrastructure.AssignIP(wName, wIP, tc.Felixes[ii].Hostname, calicoClient) w[ii] = workload.Run(tc.Felixes[ii], wName, "default", wIP, "8055", "tcp") w[ii].ConfigureInInfra(infra) } @@ -155,18 +127,11 @@ var _ = Describe("Spoof tests", func() { } }) - AfterEach(func() { - teardownInfra() - }) - spoofTests() }) Context("IPv6", func() { BeforeEach(func() { - var err error - infra, err = infrastructure.GetEtcdDatastoreInfra() - Expect(err).NotTo(HaveOccurred()) opts := infrastructure.DefaultTopologyOptions() opts.EnableIPv6 = true opts.IPIPMode = api.IPIPModeNever @@ -193,10 +158,6 @@ var _ = Describe("Spoof tests", func() { } }) - AfterEach(func() { - teardownInfra() - }) - spoofTests() }) }) diff --git a/felix/fv/status_report_test.go b/felix/fv/status_report_test.go index 47cb12709a4..f39b2193d6c 100644 --- a/felix/fv/status_report_test.go +++ b/felix/fv/status_report_test.go @@ -12,15 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( "context" "fmt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -62,15 +60,6 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ openstack status-reporting" w.ConfigureInInfra(infra) }) - AfterEach(func() { - w.Stop() - tc.Stop() - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) - Describe("With status-reporting writing to etcd", func() { getStatus := func() string { wlListOpts := model.WorkloadEndpointStatusListOptions{ diff --git a/felix/fv/tcpdump/tcpdump.go b/felix/fv/tcpdump/tcpdump.go index 28600ac9ea2..a2e9451081b 100644 --- a/felix/fv/tcpdump/tcpdump.go +++ b/felix/fv/tcpdump/tcpdump.go @@ -23,7 +23,7 @@ import ( "sync" "time" - "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" "github.com/sirupsen/logrus" @@ -136,7 +136,11 @@ func (t *TCPDump) ResetCount(name string) { logrus.Infof("[%s] Reset count for %s", t.contName, name) } -func (t *TCPDump) Start(expr ...string) { +type CleanupProvider interface { + AddCleanup(func()) +} + +func (t *TCPDump) Start(infra CleanupProvider, expr ...string) { args := append(t.args, expr...) t.cmd = utils.Command(t.exe, args...) var err error @@ -151,24 +155,33 @@ func (t *TCPDump) Start(expr ...string) { err = t.cmd.Start() + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + infra.AddCleanup(t.Stop) + select { case <-t.listeningStarted: case <-time.After(60 * time.Second): ginkgo.Fail("Failed to start tcpdump: it never reported that it was listening") } - gomega.Expect(err).NotTo(gomega.HaveOccurred()) } func (t *TCPDump) Stop() { var err error if t.args[0] == "run" { err = exec.Command("docker", "stop", t.contName).Run() + if err != nil { + logrus.WithError(err).Error("Failed to stop tcpdump container; maybe it failed to start?") + } } else { err = t.cmd.Process.Kill() - } - if err != nil { - logrus.WithError(err).Error("Failed to kill tcpdump; maybe it failed to start?") + if err != nil { + logrus.Errorf("Failed to stop tcpdump: %v", err) + } + err := t.cmd.Wait() + if err != nil { + logrus.WithError(err).Error("Failed to wait for tcpdump to exit") + } } } diff --git a/felix/fv/test-connection/test-connection.go b/felix/fv/test-connection/test-connection.go index a3e27db8b14..e32e79a9297 100644 --- a/felix/fv/test-connection/test-connection.go +++ b/felix/fv/test-connection/test-connection.go @@ -488,6 +488,20 @@ func (tc *testConn) tryLoopFile(loopFile string, logPongs bool, timeout, sleep t continue } else { fmt.Printf("err = %+v\n", err) + // If the initial exchange has completed and the loop file exists, + // we were asked to stop. The connection error is expected during + // shutdown, so exit cleanly. We only check after sentInitial so + // that startup failures still fail loudly (the loop file is + // expected to exist before the first successful exchange). + if ls.sentInitial && ls.loopFile != "" { + if _, statErr := os.Stat(ls.loopFile); statErr == nil { + log.WithError(err).Info("Connection error during shutdown, exiting cleanly") + if rmErr := os.Remove(ls.loopFile); rmErr != nil { + log.WithError(rmErr).Info("Failed to remove loop file during shutdown") + } + break + } + } log.WithError(err).Fatal("Failed to receive") } @@ -639,9 +653,7 @@ func (tc *testConn) tryConnectWithPacketLoss() error { var lastResponse connectivity.Response // Start a reader - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { lastSequence := 0 count := 0 @@ -700,12 +712,10 @@ func (tc *testConn) tryConnectWithPacketLoss() error { count++ } } - }() + }) // start a writer - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { count := 0 for { @@ -742,7 +752,7 @@ func (tc *testConn) tryConnectWithPacketLoss() error { } } - }() + }) // Wait for writer and reader to complete. wg.Wait() diff --git a/felix/fv/test-list-filter.awk b/felix/fv/test-list-filter.awk deleted file mode 100644 index 8e4fdd77207..00000000000 --- a/felix/fv/test-list-filter.awk +++ /dev/null @@ -1,49 +0,0 @@ -BEGIN { - print; - print "The following FV tests will run in this batch"; - print "============================================="; -} - -/msg="Loaded config"/ { - next; -} - -/^Running Suite:/ { - next; -} - -/^==============/ { - next; -} - -/^Random Seed:/ { - next; -} - -/^Parallel test node/ { - next; -} - -/^JUnit report was created/ { - next; -} - -/^Ran [[:digit:]]+ of [[:digit:]]+ Specs/ { - next; -} - -/^SUCCESS!/ { - next; -} - -/^PASS$/ { - next; -} - -/^[•S]+$/ { - next; -} - -{ - print; -} diff --git a/felix/fv/tiered_policy_test.go b/felix/fv/tiered_policy_test.go index 67cfd853e2a..97f55119755 100644 --- a/felix/fv/tiered_policy_test.go +++ b/felix/fv/tiered_policy_test.go @@ -1,6 +1,3 @@ -//go:build fvtests -// +build fvtests - // Copyright (c) 2018-2025 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,7 +20,7 @@ import ( "os" "strings" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" networkingv1 "k8s.io/api/networking/v1" @@ -104,21 +101,28 @@ var _ = infrastructure.DatastoreDescribe("connectivity tests and flow logs with infra.AddDefaultAllow() // Create workload on host 1. + infrastructure.AssignIP("ep1-1", "10.65.0.0", tc.Felixes[0].Hostname, client) ep1_1 = workload.Run(tc.Felixes[0], "ep1-1", "default", "10.65.0.0", wepPortStr, "tcp") ep1_1.ConfigureInInfra(infra) + infrastructure.AssignIP("ep2-1", "10.65.1.0", tc.Felixes[1].Hostname, client) ep2_1 = workload.Run(tc.Felixes[1], "ep2-1", "default", "10.65.1.0", wepPortStr, "tcp") ep2_1.ConfigureInInfra(infra) + infrastructure.AssignIP("ep2-2", "10.65.1.1", tc.Felixes[1].Hostname, client) ep2_2 = workload.Run(tc.Felixes[1], "ep2-2", "default", "10.65.1.1", wepPortStr, "tcp") ep2_2.ConfigureInInfra(infra) + infrastructure.AssignIP("ep2-3", "10.65.1.2", tc.Felixes[1].Hostname, client) ep2_3 = workload.Run(tc.Felixes[1], "ep2-3", "default", "10.65.1.2", wepPortStr, "tcp") ep2_3.ConfigureInInfra(infra) + infrastructure.AssignIP("ep2-3", "10.65.1.3", tc.Felixes[1].Hostname, client) ep2_4 = workload.Run(tc.Felixes[1], "ep2-4", "default", "10.65.1.3", wepPortStr, "tcp") ep2_4.ConfigureInInfra(infra) + ensureRoutesProgrammed(tc.Felixes) + // Create tiers tier1 and tier2 tier := api.NewTier() tier.Name = "tier1" @@ -241,7 +245,7 @@ var _ = infrastructure.DatastoreDescribe("connectivity tests and flow logs with Expect(err).NotTo(HaveOccurred()) // (s)knp3.1->sknp3.9 egress: (N2-1) - for i := 0; i < 9; i++ { + for i := range 9 { sknp := api.NewStagedKubernetesNetworkPolicy() sknp.Name = fmt.Sprintf("knp3-%d", i+1) sknp.Namespace = "default" @@ -295,7 +299,7 @@ var _ = infrastructure.DatastoreDescribe("connectivity tests and flow logs with svcName := "test-service" k8sClient := infra.(*infrastructure.K8sDatastoreInfra).K8sClient tSvc := k8sService(svcName, clusterIP, ep2_1, svcPort, wepPort, 0, "tcp") - tSvcNamespace := tSvc.ObjectMeta.Namespace + tSvcNamespace := tSvc.Namespace _, err = k8sClient.CoreV1().Services(tSvcNamespace).Create(context.Background(), tSvc, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -303,23 +307,27 @@ var _ = infrastructure.DatastoreDescribe("connectivity tests and flow logs with Expect(ep2_1.IP).NotTo(Equal("")) getEpsFunc := k8sGetEpsForServiceFunc(k8sClient, tSvc) epCorrectFn := func() error { - eps := getEpsFunc() + epslices := getEpsFunc() + if len(epslices) != 1 { + return fmt.Errorf("Wrong number of endpoints: %#v", epslices) + } + eps := epslices[0].Endpoints if len(eps) != 1 { - return fmt.Errorf("Wrong number of endpoints: %#v", eps) + return fmt.Errorf("Wrong number of endpoint addresses: %#v", epslices[0]) } addrs := eps[0].Addresses if len(addrs) != 1 { return fmt.Errorf("Wrong number of addresses: %#v", eps[0]) } - if addrs[0].IP != ep2_1.IP { - return fmt.Errorf("Unexpected IP: %s != %s", addrs[0].IP, ep2_1.IP) + if addrs[0] != ep2_1.IP { + return fmt.Errorf("Unexpected IP: %s != %s", addrs[0], ep2_1.IP) } - ports := eps[0].Ports + ports := epslices[0].Ports if len(ports) != 1 { return fmt.Errorf("Wrong number of ports: %#v", eps[0]) } - if ports[0].Port != int32(wepPort) { - return fmt.Errorf("Wrong port %d != svcPort", ports[0].Port) + if *ports[0].Port != int32(wepPort) { + return fmt.Errorf("Wrong port %d != svcPort", *ports[0].Port) } return nil } @@ -343,19 +351,14 @@ var _ = infrastructure.DatastoreDescribe("connectivity tests and flow logs with // Nftables out0, err := tc.Felixes[1].ExecOutput("nft", "list", "ruleset") Expect(err).NotTo(HaveOccurred()) - if strings.Count(out0, "End of tier tier1. Drop if no policies passed packet") == 0 { - return false - } - return true + return strings.Contains(out0, "End of tier tier1. Drop if no policies passed packet") } // Iptables out0, err := tc.Felixes[1].ExecOutput("iptables-save", "-t", "filter") Expect(err).NotTo(HaveOccurred()) - if strings.Count(out0, "End of tier tier1. Drop if no policies passed packet") == 0 { - return false - } - return true + + return strings.Contains(out0, "End of tier tier1. Drop if no policies passed packet") } // BPF @@ -526,10 +529,10 @@ var _ = infrastructure.DatastoreDescribe("connectivity tests and flow logs with Reporter: "src", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|default|default.ep1-1-allow-all|allow|0": {}, + "0|default|gnp:default.ep1-1-allow-all|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|default|default.ep1-1-allow-all|allow|0": {}, + "0|default|gnp:default.ep1-1-allow-all|allow|0": {}, }, }) @@ -544,10 +547,10 @@ var _ = infrastructure.DatastoreDescribe("connectivity tests and flow logs with Reporter: "src", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|default|default.ep1-1-allow-all|allow|0": {}, + "0|default|gnp:default.ep1-1-allow-all|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|default|default.ep1-1-allow-all|allow|0": {}, + "0|default|gnp:default.ep1-1-allow-all|allow|0": {}, }, }) @@ -562,10 +565,10 @@ var _ = infrastructure.DatastoreDescribe("connectivity tests and flow logs with Reporter: "dst", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|default|default.ep1-1-allow-all|allow|0": {}, + "0|default|gnp:default.ep1-1-allow-all|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|default|default.ep1-1-allow-all|allow|0": {}, + "0|default|gnp:default.ep1-1-allow-all|allow|0": {}, }, }) @@ -580,10 +583,10 @@ var _ = infrastructure.DatastoreDescribe("connectivity tests and flow logs with Reporter: "src", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|default|default.ep1-1-allow-all|allow|0": {}, + "0|default|gnp:default.ep1-1-allow-all|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|default|default.ep1-1-allow-all|allow|0": {}, + "0|default|gnp:default.ep1-1-allow-all|allow|0": {}, }, }) @@ -598,10 +601,10 @@ var _ = infrastructure.DatastoreDescribe("connectivity tests and flow logs with Reporter: "src", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|default|default.ep1-1-allow-all|allow|0": {}, + "0|default|gnp:default.ep1-1-allow-all|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|default|default.ep1-1-allow-all|allow|0": {}, + "0|default|gnp:default.ep1-1-allow-all|allow|0": {}, }, }) @@ -617,10 +620,10 @@ var _ = infrastructure.DatastoreDescribe("connectivity tests and flow logs with Reporter: "dst", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|default|default.ep1-1-allow-all|allow|0": {}, + "0|default|gnp:default.ep1-1-allow-all|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|default|default.ep1-1-allow-all|allow|0": {}, + "0|default|gnp:default.ep1-1-allow-all|allow|0": {}, }, }) @@ -645,10 +648,10 @@ var _ = infrastructure.DatastoreDescribe("connectivity tests and flow logs with Reporter: "src", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|deny|1": {}, + "0|tier1|np:default/tier1.np1-1|deny|1": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|deny|1": {}, + "0|tier1|np:default/tier1.np1-1|deny|1": {}, }, }) @@ -663,10 +666,10 @@ var _ = infrastructure.DatastoreDescribe("connectivity tests and flow logs with Reporter: "src", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier2|tier2.gnp2-2|deny|0": {}, + "0|tier2|gnp:tier2.gnp2-2|deny|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier2|tier2.gnp2-2|deny|0": {}, + "0|tier2|gnp:tier2.gnp2-2|deny|0": {}, }, }) @@ -681,12 +684,12 @@ var _ = infrastructure.DatastoreDescribe("connectivity tests and flow logs with Reporter: "src", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|__PROFILE__|pro:kns.default|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|0": {}, - "1|tier2|default/tier2.staged:np2-1|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|0": {}, + "1|tier2|snp:default/tier2.np2-1|allow|0": {}, }, }) @@ -701,10 +704,10 @@ var _ = infrastructure.DatastoreDescribe("connectivity tests and flow logs with Reporter: "dst", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier2|default/tier2.np2-4|deny|0": {}, + "0|tier2|np:default/tier2.np2-4|deny|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier2|default/tier2.staged:np2-3|deny|1": {}, + "0|tier2|snp:default/tier2.np2-3|deny|1": {}, }, }) @@ -719,12 +722,12 @@ var _ = infrastructure.DatastoreDescribe("connectivity tests and flow logs with Reporter: "dst", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|1": {}, - "1|default|default/default.np3-3|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|1": {}, + "1|default|np:default/default.np3-3|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|pass|1": {}, - "1|tier2|default/tier2.staged:np2-3|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|pass|1": {}, + "1|tier2|snp:default/tier2.np2-3|allow|0": {}, }, }) @@ -739,10 +742,10 @@ var _ = infrastructure.DatastoreDescribe("connectivity tests and flow logs with Reporter: "dst", }, FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier1|default/tier1.np1-1|allow|0": {}, + "0|tier1|np:default/tier1.np1-1|allow|0": {}, }, }) @@ -759,12 +762,12 @@ var _ = infrastructure.DatastoreDescribe("connectivity tests and flow logs with }, // Enforced and pending policy sets must be identical. FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|tier1.ep2-4|pass|-1": {}, - "1|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|gnp:tier1.ep2-4|pass|-1": {}, + "1|__PROFILE__|pro:kns.default|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier1|tier1.ep2-4|pass|-1": {}, - "1|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|gnp:tier1.ep2-4|pass|-1": {}, + "1|__PROFILE__|pro:kns.default|allow|0": {}, }, }) @@ -780,12 +783,12 @@ var _ = infrastructure.DatastoreDescribe("connectivity tests and flow logs with }, // Enforced and pending policy sets must be identical. FlowEnforcedPolicySet: flowlog.FlowPolicySet{ - "0|tier1|tier1.ep2-4|pass|-1": {}, - "1|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|gnp:tier1.ep2-4|pass|-1": {}, + "1|__PROFILE__|pro:kns.default|allow|0": {}, }, FlowPendingPolicySet: flowlog.FlowPolicySet{ - "0|tier1|tier1.ep2-4|pass|-1": {}, - "1|__PROFILE__|__PROFILE__.kns.default|allow|0": {}, + "0|tier1|gnp:tier1.ep2-4|pass|-1": {}, + "1|__PROFILE__|pro:kns.default|allow|0": {}, }, }) @@ -814,27 +817,4 @@ var _ = infrastructure.DatastoreDescribe("connectivity tests and flow logs with Eventually(checkFlowLogs, "30s", "3s").ShouldNot(HaveOccurred()) }) }) - - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - felix.Exec("iptables-save", "-c") - felix.Exec("ipset", "list") - felix.Exec("ip", "r") - felix.Exec("ip", "a") - } - } - - ep1_1.Stop() - ep2_1.Stop() - ep2_2.Stop() - ep2_3.Stop() - ep2_4.Stop() - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) }) diff --git a/felix/fv/utils/utils.go b/felix/fv/utils/utils.go index 0523e898abb..fa78bb26f3b 100644 --- a/felix/fv/utils/utils.go +++ b/felix/fv/utils/utils.go @@ -126,17 +126,18 @@ func indent(s string, prefix string) string { } func formatCommand(command string, args []string) string { - out := command + var out strings.Builder + out.WriteString(command) for _, arg := range args { // Only quote if there are actually some interesting characters in there, just to make it easier to read. quoted := fmt.Sprintf("%q", arg) if quoted == `"`+arg+`"` { - out += " " + arg + out.WriteString(" " + arg) } else { - out += " " + quoted + out.WriteString(" " + quoted) } } - return out + return out.String() } func GetCommandOutput(command string, args ...string) (string, error) { diff --git a/felix/fv/vxlan_test.go b/felix/fv/vxlan_test.go index 12cbda126c5..dbdd77e4638 100644 --- a/felix/fv/vxlan_test.go +++ b/felix/fv/vxlan_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -22,7 +20,7 @@ import ( "strings" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -98,45 +96,6 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ VXLAN topology before addin assignTunnelAddresses(infra, tc, client) }) - JustAfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range felixes { - if NFTMode() { - logNFTDiags(felix) - } else { - felix.Exec("iptables-save", "-c") - felix.Exec("ipset", "list") - } - felix.Exec("ipset", "list") - felix.Exec("ip", "r") - felix.Exec("ip", "a") - if enableIPv6 { - felix.Exec("ip", "-6", "route") - } - felix.Exec("ip", "-d", "link") - } - - infra.DumpErrorData() - } - }) - - AfterEach(func() { - for _, wl := range w { - wl.Stop() - } - for _, wl := range w6 { - wl.Stop() - } - for _, wl := range hostW { - wl.Stop() - } - for _, wl := range hostW6 { - wl.Stop() - } - tc.Stop() - infra.Stop() - }) - if brokenXSum { It("should disable checksum offload", func() { Eventually(func() string { @@ -196,7 +155,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ VXLAN topology before addin // Checking host to workload connectivity // Skipping due to known issue with tunnel IPs not being programmed in WEP mode if vxlanTunnelSupported(vxlanMode, routeSource) { - for i := 0; i < 3; i++ { + for i := range 3 { f := felixes[i] cc.ExpectSome(f, w[0]) cc.ExpectSome(f, w[1]) @@ -278,14 +237,14 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ VXLAN topology before addin Expect(err).NotTo(HaveOccurred()) lines := strings.Split(strings.Trim(defaultRoute, "\n "), "\n") Expect(lines).To(HaveLen(1)) - defaultRouteArgs := strings.Split(strings.Replace(lines[0], "eth0", "bond0", -1), " ") + defaultRouteArgs := strings.Split(strings.ReplaceAll(lines[0], "eth0", "bond0"), " ") // Assuming the subnet route will be "proto kernel" and that will be the only such route. subnetRoute, err := felix.ExecOutput("ip", "route", "show", "proto", "kernel") Expect(err).NotTo(HaveOccurred()) lines = strings.Split(strings.Trim(subnetRoute, "\n "), "\n") Expect(lines).To(HaveLen(1), "expected only one proto kernel route, has docker's routing set-up changed?") - subnetArgs := strings.Split(strings.Replace(lines[0], "eth0", "bond0", -1), " ") + subnetArgs := strings.Split(strings.ReplaceAll(lines[0], "eth0", "bond0"), " ") // Add the bond, replacing eth0. felix.Exec("ip", "addr", "del", felix.IP, "dev", "eth0") @@ -526,7 +485,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ VXLAN topology before addin Action: api.Allow, }, } - policy.Spec.Selector = fmt.Sprintf("has(host-endpoint)") + policy.Spec.Selector = "has(host-endpoint)" _, err := client.GlobalNetworkPolicies().Create(utils.Ctx, policy, utils.NoOptions) Expect(err).NotTo(HaveOccurred()) }) @@ -610,6 +569,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ VXLAN topology before addin node.Spec.BGP = nil _, err = client.Nodes().Update(ctx, node, options.SetOptions{}) + Expect(err).NotTo(HaveOccurred()) }) It("should have no connectivity from third felix and expected number of IPs in allow list", func() { @@ -907,8 +867,6 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ VXLAN topology before addin client client.Interface w [3]*workload.Workload w6 [3]*workload.Workload - hostW [3]*workload.Workload - hostW6 [3]*workload.Workload cc *connectivity.Checker topologyOptions infrastructure.TopologyOptions ) @@ -929,7 +887,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ VXLAN topology before addin // Deploy the topology. tc, client = infrastructure.StartNNodeTopology(3, topologyOptions, infra) - w, w6, hostW, hostW6 = setupWorkloads(infra, tc, topologyOptions, client, enableIPv6) + w, w6, _, _ = setupWorkloads(infra, tc, topologyOptions, client, enableIPv6) felixes = tc.Felixes // Assign tunnel addresees in IPAM based on the topology. @@ -955,25 +913,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ VXLAN topology before addin } } } - - for _, wl := range w { - wl.Stop() - } - for _, wl := range w6 { - wl.Stop() - } - for _, wl := range hostW { - wl.Stop() - } - for _, wl := range hostW6 { - wl.Stop() - } - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() + // Topology/workload cleanup is handled by infra.Stop() via DatastoreDescribe. }) It("should have host to workload connectivity", func() { @@ -981,7 +921,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ VXLAN topology before addin Skip("Skipping due to known issue with tunnel IPs not being programmed in WEP mode") } - for i := 0; i < 3; i++ { + for i := range 3 { f := felixes[i] cc.ExpectSome(f, w[0]) cc.ExpectSome(f, w[1]) @@ -1006,8 +946,6 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ VXLAN topology before addin client client.Interface w [3]*workload.Workload w6 [3]*workload.Workload - hostW [3]*workload.Workload - hostW6 [3]*workload.Workload cc *connectivity.Checker topologyOptions infrastructure.TopologyOptions ) @@ -1055,52 +993,13 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ VXLAN topology before addin // Deploy the topology. tc, client = infrastructure.StartNNodeTopology(3, topologyOptions, infra) - w, w6, hostW, hostW6 = setupWorkloads(infra, tc, topologyOptions, client, enableIPv6) + w, w6, _, _ = setupWorkloads(infra, tc, topologyOptions, client, enableIPv6) felixes = tc.Felixes // Assign tunnel addresees in IPAM based on the topology. assignTunnelAddresses(infra, tc, client) }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range felixes { - if NFTMode() { - logNFTDiags(felix) - } else { - felix.Exec("iptables-save", "-c") - felix.Exec("ipset", "list") - } - felix.Exec("ipset", "list") - felix.Exec("ip", "r") - felix.Exec("ip", "a") - if enableIPv6 { - felix.Exec("ip", "-6", "route") - } - felix.Exec("calico-bpf", "routes", "dump") - } - } - - for _, wl := range w { - wl.Stop() - } - for _, wl := range w6 { - wl.Stop() - } - for _, wl := range hostW { - wl.Stop() - } - for _, wl := range hostW6 { - wl.Stop() - } - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) - It("should have correct connectivity", func() { // Workload to workload connectivity. cc.ExpectSome(w[0], w[1]) @@ -1112,7 +1011,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ VXLAN topology before addin // Host to workload connectivity. if vxlanTunnelSupported(vxlanMode, routeSource) { - for i := 0; i < 3; i++ { + for i := range 3 { f := felixes[i] cc.ExpectSome(f, w[0]) cc.ExpectSome(f, w[1]) @@ -1218,32 +1117,14 @@ func setupWorkloadsWithOffset(infra infrastructure.DatastoreInfra, tc infrastruc for ii := range w { wIP := fmt.Sprintf("%d.%d.%d.2", IPv4CIDR.IP[0], IPv4CIDR.IP[1], ii+offset) wName := fmt.Sprintf("w%d", ii) - err := client.IPAM().AssignIP(context.Background(), ipam.AssignIPArgs{ - IP: net.MustParseIP(wIP), - HandleID: &wName, - Attrs: map[string]string{ - ipam.AttributeNode: tc.Felixes[ii].Hostname, - }, - Hostname: tc.Felixes[ii].Hostname, - }) - Expect(err).NotTo(HaveOccurred()) - + infrastructure.AssignIP(wName, wIP, tc.Felixes[ii].Hostname, client) w[ii] = workload.Run(tc.Felixes[ii], wName, "default", wIP, "8055", "tcp") w[ii].ConfigureInInfra(infra) if enableIPv6 { w6IP := fmt.Sprintf("%x%x:%x%x:%x%x:%x%x:%x%x:%x%x:%d:3", IPv6CIDR.IP[0], IPv6CIDR.IP[1], IPv6CIDR.IP[2], IPv6CIDR.IP[3], IPv6CIDR.IP[4], IPv6CIDR.IP[5], IPv6CIDR.IP[6], IPv6CIDR.IP[7], IPv6CIDR.IP[8], IPv6CIDR.IP[9], IPv6CIDR.IP[10], IPv6CIDR.IP[11], ii+offset) w6Name := fmt.Sprintf("w6-%d", ii) - err := client.IPAM().AssignIP(context.Background(), ipam.AssignIPArgs{ - IP: net.MustParseIP(w6IP), - HandleID: &w6Name, - Attrs: map[string]string{ - ipam.AttributeNode: tc.Felixes[ii].Hostname, - }, - Hostname: tc.Felixes[ii].Hostname, - }) - Expect(err).NotTo(HaveOccurred()) - + infrastructure.AssignIP(w6Name, w6IP, tc.Felixes[ii].Hostname, client) w6[ii] = workload.Run(tc.Felixes[ii], w6Name, "default", w6IP, "8055", "tcp") w6[ii].ConfigureInInfra(infra) } @@ -1254,6 +1135,7 @@ func setupWorkloadsWithOffset(infra infrastructure.DatastoreInfra, tc infrastruc } } + ensureRoutesProgrammed(tc.Felixes) if BPFMode() { ensureAllNodesBPFProgramsAttached(tc.Felixes) } @@ -1295,5 +1177,5 @@ func waitForVXLANDevice(tc infrastructure.TopologyContainers, enableIPv6 bool) { } func vxlanTunnelSupported(vxlanMode api.VXLANMode, routeSource string) bool { - return !(vxlanMode == api.VXLANModeAlways && routeSource == "WorkloadIPs") + return vxlanMode != api.VXLANModeAlways || routeSource != "WorkloadIPs" } diff --git a/felix/fv/winfv/policy_test.go b/felix/fv/winfv/policy_test.go index e392729a402..2b44e5cd776 100644 --- a/felix/fv/winfv/policy_test.go +++ b/felix/fv/winfv/policy_test.go @@ -18,10 +18,11 @@ import ( "bytes" "context" "fmt" + "os" "os/exec" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" @@ -123,6 +124,7 @@ func newClient() clientv3.Interface { cfg := apiconfig.NewCalicoAPIConfig() cfg.Spec.DatastoreType = apiconfig.Kubernetes cfg.Spec.Kubeconfig = `c:\k\config` + cfg.Spec.CalicoAPIGroup = os.Getenv("CALICO_API_GROUP") client, err := clientv3.New(*cfg) ExpectWithOffset(1, err).NotTo(HaveOccurred()) mustInitDatastore(client) diff --git a/felix/fv/winfv/win_fv_suite_test.go b/felix/fv/winfv/win_fv_suite_test.go index 1fc3be2e978..6444a9bc383 100644 --- a/felix/fv/winfv/win_fv_suite_test.go +++ b/felix/fv/winfv/win_fv_suite_test.go @@ -17,9 +17,8 @@ package winfv_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestNetworkingWindows(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/fv_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Felix windows Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/felix_fv_winfv_suite.xml" + ginkgo.RunSpecs(t, "FV: felix/fv/winfv", suiteConfig, reporterConfig) } diff --git a/felix/fv/wireguard_test.go b/felix/fv/wireguard_test.go index fa07d688545..86482ab9faa 100644 --- a/felix/fv/wireguard_test.go +++ b/felix/fv/wireguard_test.go @@ -12,15 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( - "bytes" + "bufio" "context" "errors" "fmt" + "io" + "maps" "os" "os/exec" "regexp" @@ -28,7 +28,7 @@ import ( "strings" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/types" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -42,9 +42,8 @@ import ( "github.com/projectcalico/calico/felix/fv/utils" "github.com/projectcalico/calico/felix/fv/workload" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - v3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/clientv3" - "github.com/projectcalico/calico/libcalico-go/lib/ipam" "github.com/projectcalico/calico/libcalico-go/lib/net" "github.com/projectcalico/calico/libcalico-go/lib/options" ) @@ -76,7 +75,6 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ WireGuard-Supported", []api routeEntriesV4 [nodeCount]string routeEntriesV6 [nodeCount]string dmesgCmd *exec.Cmd - dmesgBuf bytes.Buffer dmesgKill func() wgBootstrapEvents chan struct{} @@ -112,12 +110,36 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ WireGuard-Supported", []api // Start a process tailing the dmesg log. ctx, cancel := context.WithCancel(context.Background()) - dmesgCmd = exec.CommandContext(ctx, "sudo", "dmesg", "-wH") - dmesgCmd.Stdout = &dmesgBuf - dmesgCmd.Stderr = &dmesgBuf - err := dmesgCmd.Start() + dmesgCmd = exec.CommandContext(ctx, "sudo", "dmesg", "-WH") + dmesgCmd.WaitDelay = time.Second + dmesgIn, err := dmesgCmd.StdinPipe() + Expect(err).NotTo(HaveOccurred()) + dmesgOut, err := dmesgCmd.StdoutPipe() + Expect(err).NotTo(HaveOccurred()) + dmesgErr, err := dmesgCmd.StderrPipe() + Expect(err).NotTo(HaveOccurred()) + err = dmesgCmd.Start() Expect(err).NotTo(HaveOccurred()) + copyOutputToLog := func(name string, pipe io.ReadCloser) { + scanner := bufio.NewScanner(pipe) + scanner.Buffer(nil, 10*1024*1024) // Increase maximum buffer size (but don't pre-alloc). + for scanner.Scan() { + line := scanner.Text() + line = strings.TrimRight(line, " \n") + _, _ = fmt.Fprintf(GinkgoWriter, "dmesg[%v] %v\n", name, line) + } + err := scanner.Err() + if err != nil && !errors.Is(err, io.EOF) { + log.WithError(err).Errorf("Error reading %v", name) + } + } + go copyOutputToLog("out", dmesgOut) + go copyOutputToLog("err", dmesgErr) dmesgKill = cancel + // close stdin to make sure sudo fails fast if it's asking for + // password or something. + Expect(dmesgIn.Close()).NotTo(HaveOccurred()) + log.Info("Started dmesg log capture") infra = getInfra() @@ -170,7 +192,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ WireGuard-Supported", []api // Swap route entry to match between workloads. routeEntriesV6[0], routeEntriesV6[1] = routeEntriesV6[1], routeEntriesV6[0] } - for i := 0; i < nodeCount; i++ { + for i := range nodeCount { wgBootstrapEvents = topologyContainers.Felixes[i].WatchStdoutFor( regexp.MustCompile(".*(Cleared wireguard public key from datastore|Wireguard public key not set in datastore).+"), ) @@ -191,44 +213,13 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ WireGuard-Supported", []api if dmesgKill != nil { log.Info("Stop dmesg log capture") dmesgKill() - log.Infof("Captured dmesg log:\n%v", dmesgBuf.String()) + _ = dmesgCmd.Wait() + dmesgKill = nil } - if CurrentGinkgoTestDescription().Failed { - for _, felix := range topologyContainers.Felixes { - felix.Exec("ip", "link") - felix.Exec("ip", "addr") - felix.Exec("ip", "rule", "list") - felix.Exec("ip", "route", "show", "table", "all") - felix.Exec("ip", "route", "show", "cached") - felix.Exec("wg") - felix.Exec("wg", "show", "all", "private-key") - if BPFMode() { - felix.Exec("calico-bpf", "policy", "dump", "eth0", "all", "--asm") - felix.Exec("calico-bpf", "policy", "-6", "dump", "eth0", "all", "--asm") - } - } - } - - if wireguardEnabledV4 { - for _, wl := range wlsV4 { - wl.Stop() - } - } - if wireguardEnabledV6 { - for _, wl := range wlsV6 { - wl.Stop() - } - } for _, tcpdump := range tcpdumps { tcpdump.Stop() } - topologyContainers.Stop() - - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() }) Context("with Wireguard enabled", func() { @@ -445,7 +436,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ WireGuard-Supported", []api for _, felix := range topologyContainers.Felixes { if wireguardEnabledV4 { var wgPubKeyOrig string - var node *v3.Node + var node *internalapi.Node var err error // Get the original public-key. @@ -481,7 +472,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ WireGuard-Supported", []api } if wireguardEnabledV6 { var wgPubKeyOrig string - var node *v3.Node + var node *internalapi.Node var err error // Get the original public-key. @@ -713,7 +704,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ WireGuard-Supported", []api tcpdump.AddMatcher("numWorkload1to0PacketsV6", regexp.MustCompile(workload10PacketsPatternV6)) } - tcpdump.Start() + tcpdump.Start(infra) tcpdumps[i] = tcpdump } }) @@ -851,7 +842,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ WireGuard-Supported", []api By("Waiting for the policy to apply") if BPFMode() { for _, felix := range topologyContainers.Felixes { - bpfWaitForPolicy(felix, "eth0", "egress", "default.deny-wg-port") + bpfWaitForGlobalNetworkPolicy(felix, "eth0", "egress", "deny-wg-port") } } @@ -1120,20 +1111,6 @@ var _ = infrastructure.DatastoreDescribe("WireGuard-Unsupported", []apiconfig.Da tc.Felixes[0].TriggerDelayedStart() }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - tc.Felixes[0].Exec("ip", "link") - tc.Felixes[0].Exec("wg") - } - - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() - }) - It("no Wireguard device exists", func() { Eventually(func() error { out, err := tc.Felixes[0].ExecOutput("ip", "link", "show") @@ -1239,7 +1216,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ WireGuard-Supported 3 node }) AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { + if CurrentSpecReport().Failed() { for _, felix := range tc.Felixes { felix.Exec("ip", "addr") felix.Exec("ip", "rule", "list") @@ -1259,7 +1236,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ WireGuard-Supported 3 node tc.Stop() - if CurrentGinkgoTestDescription().Failed { + if CurrentSpecReport().Failed() { infra.DumpErrorData() } infra.Stop() @@ -1285,7 +1262,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ WireGuard-Supported 3 node for i := range []int{0, 1} { Eventually(func() string { return getWireguardRoutingRule(tc.Felixes[i], 4) - }, "10s", "100ms").Should(MatchRegexp(fmt.Sprintf("\\d+:\\s+not from all fwmark 0x\\d+/0x\\d+ lookup \\d+"))) + }, "10s", "100ms").Should(MatchRegexp(`\d+:\s+not from all fwmark 0x\d+/0x\d+ lookup \d+`)) } // 3. by checking, Wireguard route table exist. for i := range []int{0, 1} { @@ -1341,7 +1318,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ WireGuard-Supported 3 node // Check the rule exists. Eventually(func() string { return getWireguardRoutingRule(tc.Felixes[i], 4) - }, "10s", "100ms").Should(MatchRegexp(fmt.Sprintf("\\d+:\\s+not from all fwmark 0x\\d+/0x\\d+ lookup \\d+"))) + }, "10s", "100ms").Should(MatchRegexp(`\d+:\s+not from all fwmark 0x\d+/0x\d+ lookup \d+`)) } for i := range []int{0, 1} { // Check the route entry exists. @@ -1369,7 +1346,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ WireGuard-Supported 3 node inWorkloadPacketsPattern := fmt.Sprintf("IP %s\\.\\d+ > %s\\.\\d+:", wls[2].IP, wls[0].IP) tcpdump.AddMatcher("numInWorkloadPackets", regexp.MustCompile(inWorkloadPacketsPattern)) - tcpdump.Start() + tcpdump.Start(infra) tcpdumps = append(tcpdumps, tcpdump) } }) @@ -1491,7 +1468,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ WireGuard-Supported 3-node } // initialise external client - externalClient = infrastructure.RunExtClient("ext-client") + externalClient = infrastructure.RunExtClient(infra, "ext-client") externalClient.Exec("ip", "route", "add", wlsByHost[0][0].IP, "via", tc.Felixes[0].IP) for i := range tc.Felixes { @@ -1519,7 +1496,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ WireGuard-Supported 3-node nonTunnelPacketsFelix1toFelix0Pattern := fmt.Sprintf("IP %s\\.%s > %s\\.%s: TCP", tc.Felixes[1].IP, defaultWorkloadPort, tc.Felixes[0].IP, defaultWorkloadPort) tcpdump.AddMatcher("numNonTunnelPacketsFelix1toFelix0", regexp.MustCompile(nonTunnelPacketsFelix1toFelix0Pattern)) - tcpdump.Start() + tcpdump.Start(infra) tcpdumps = append(tcpdumps, tcpdump) } @@ -1551,8 +1528,8 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ WireGuard-Supported 3-node Eventually(func() []int { var handshakes []int out, _ := felix.ExecOutput("wg", "show", wireguardInterfaceNameDefault, "latest-handshakes") - peers := strings.Split(out, "\n") - for _, peer := range peers { + peers := strings.SplitSeq(out, "\n") + for peer := range peers { parts := strings.Split(peer, "\t") if len(parts) == 2 { h, _ := strconv.Atoi(parts[1]) @@ -1565,42 +1542,9 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ WireGuard-Supported 3-node }) AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - for _, felix := range tc.Felixes { - felix.Exec("ip", "addr") - felix.Exec("ip", "rule", "list") - felix.Exec("ip", "route", "show", "table", "all") - felix.Exec("ip", "route", "show", "cached") - felix.Exec("wg") - felix.Exec("cat", "/proc/sys/net/ipv4/conf/all/src_valid_mark") - - if NFTMode() { - logNFTDiags(felix) - } else { - felix.Exec("iptables-save", "-c", "-t", "raw") - felix.Exec("iptables", "-L", "-vx") - } - } - } - - for felixIdx, felixWls := range wlsByHost { - for i := range felixWls { - wlsByHost[felixIdx][i].Stop() - } - } - - externalClient.Stop() - for _, tcpdump := range tcpdumps { tcpdump.Stop() } - - tc.Stop() - - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - } - infra.Stop() }) It("should pass basic connectivity scenarios", func() { @@ -1623,7 +1567,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ WireGuard-Supported 3-node for _, felix := range tc.Felixes { Eventually(func() string { return getWireguardRoutingRule(felix, 4) - }, "10s", "100ms").Should(MatchRegexp(fmt.Sprintf("\\d+:\\s+not from all fwmark 0x\\d+/0x\\d+ lookup \\d+"))) + }, "10s", "100ms").Should(MatchRegexp(`\d+:\s+not from all fwmark 0x\d+/0x\d+ lookup \d+`)) } By("Checking the routing table entries exist") @@ -1715,7 +1659,7 @@ var _ = infrastructure.DatastoreDescribe("_BPF-SAFE_ WireGuard-Supported 3-node cc.ResetExpectations() By("checking same node pod-to-pod connectivity") - for felixIdx := 0; felixIdx < nodeCount; felixIdx++ { + for felixIdx := range nodeCount { cc.ExpectSome(wlsByHost[felixIdx][0], wlsByHost[felixIdx][1]) } @@ -1776,8 +1720,6 @@ func wireguardTopologyOptions(routeSource string, ipipEnabled, wireguardIPv4Enab if ipipEnabled && !wireguardIPv6Enabled { topologyOptions.IPIPMode = api.IPIPModeAlways - topologyOptions.SimulateBIRDRoutes = false - topologyOptions.ExtraEnvVars["FELIX_ProgramClusterRoutes"] = "Enabled" } else { topologyOptions.IPIPMode = api.IPIPModeNever topologyOptions.SimulateBIRDRoutes = true @@ -1787,9 +1729,7 @@ func wireguardTopologyOptions(routeSource string, ipipEnabled, wireguardIPv4Enab topologyOptions.ExtraEnvVars["FELIX_IPTABLESMARKMASK"] = "4294934528" // 0xffff8000 for _, envs := range extraEnvs { - for k, v := range envs { - topologyOptions.ExtraEnvVars[k] = v - } + maps.Copy(topologyOptions.ExtraEnvVars, envs) } // Enable Wireguard. @@ -1890,22 +1830,11 @@ func createWorkloadWithAssignedIP( if ip.To4() == nil { mtu = wireguardMTUV6Default } - - wl := workload.Run(felix, wlName, "default", wlIP, defaultWorkloadPort, "tcp", workload.WithMTU(mtu)) - wl.ConfigureInInfra(*infra) - if infraOpts.UseIPPools { - err := (*client).IPAM().AssignIP(utils.Ctx, ipam.AssignIPArgs{ - IP: ip, - HandleID: &wlName, - Attrs: map[string]string{ - ipam.AttributeNode: felix.Hostname, - }, - Hostname: felix.Hostname, - }) - Expect(err).NotTo(HaveOccurred()) + infrastructure.AssignIP(wlName, wlIP, felix.Hostname, *client) } - + wl := workload.Run(felix, wlName, "default", wlIP, defaultWorkloadPort, "tcp", workload.WithMTU(mtu)) + wl.ConfigureInInfra(*infra) return wl } diff --git a/felix/fv/workload/workload.go b/felix/fv/workload/workload.go index b75f2786f47..8993c84dd7b 100644 --- a/felix/fv/workload/workload.go +++ b/felix/fv/workload/workload.go @@ -28,7 +28,7 @@ import ( "sync" "time" - "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/v2" //nolint:staticcheck // Ignore ST1001: should not use dot imports . "github.com/onsi/gomega" @@ -39,7 +39,7 @@ import ( "github.com/projectcalico/calico/felix/fv/infrastructure" "github.com/projectcalico/calico/felix/fv/tcpdump" "github.com/projectcalico/calico/felix/fv/utils" - api "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/options" @@ -57,11 +57,11 @@ type Workload struct { outPipe io.ReadCloser errPipe io.ReadCloser namespacePath string - WorkloadEndpoint *api.WorkloadEndpoint + WorkloadEndpoint *internalapi.WorkloadEndpoint Protocol string // "tcp" or "udp" SpoofInterfaceName string SpoofName string - SpoofWorkloadEndpoint *api.WorkloadEndpoint + SpoofWorkloadEndpoint *internalapi.WorkloadEndpoint MTU int isRunning bool isSpoofing bool @@ -100,32 +100,49 @@ const defaultMTU = 1440 /* wiregueard mtu */ func (w *Workload) Stop() { if w == nil { log.Info("Stop no-op because nil workload") - } else { - log.WithField("workload", w.Name).Info("Stop") - _ = w.C.ExecMayFail("sh", "-c", fmt.Sprintf("kill -9 %s & ip link del %s & ip netns del %s & wait", w.pid, w.InterfaceName, w.NamespaceID())) - // Killing the process inside the container should cause our long-running - // docker exec command to exit. Do the Wait on a background goroutine, - // so we can time it out, just in case. - waitDone := make(chan struct{}) - go func() { - defer close(waitDone) - _, err := w.runCmd.Process.Wait() - if err != nil { - log.WithField("workload", w.Name).Error("Failed to wait for docker exec, attempting to kill it.") - _ = w.runCmd.Process.Kill() - } - }() + return + } + log.WithField("workload", w.Name).Info("Stop") + if !w.isRunning { + log.WithField("workload", w.Name).Info("Workload already stopped") + return + } - select { - case <-waitDone: - log.WithField("workload", w.Name).Info("Workload stopped") - case <-time.After(10 * time.Second): - log.WithField("workload", w.Name).Error("Workload docker exec failed to exit? Killing it.") + cleanupCmds := []string{ + fmt.Sprintf("kill -9 %s", w.pid), + } + if w.InterfaceName != "" { + // Only try to delete the veth if we created one. + cleanupCmds = append(cleanupCmds, fmt.Sprintf("ip link del %s", w.InterfaceName)) + } + if w.NamespaceID() != "" { + cleanupCmds = append(cleanupCmds, fmt.Sprintf("ip netns del %s", w.NamespaceID())) + } + cleanupCmds = append(cleanupCmds, "wait") + + _ = w.C.ExecMayFail("sh", "-c", strings.Join(cleanupCmds, " & ")) + // Killing the process inside the container should cause our long-running + // docker exec command to exit. Do the Wait on a background goroutine, + // so we can time it out, just in case. + waitDone := make(chan struct{}) + go func() { + defer close(waitDone) + _, err := w.runCmd.Process.Wait() + if err != nil { + log.WithField("workload", w.Name).Error("Failed to wait for docker exec, attempting to kill it.") _ = w.runCmd.Process.Kill() } + }() - w.isRunning = false + select { + case <-waitDone: + log.WithField("workload", w.Name).Info("Workload stopped") + case <-time.After(10 * time.Second): + log.WithField("workload", w.Name).Error("Workload docker exec failed to exit? Killing it.") + _ = w.runCmd.Process.Kill() } + + w.isRunning = false } func Run(c *infrastructure.Felix, name, profile, ip, ports, protocol string, opts ...Opt) (w *Workload) { @@ -180,7 +197,7 @@ func New(c *infrastructure.Felix, name, profile, ip, ports, protocol string, opt // Build unique workload name and struct. workloadIdx++ - wep := api.NewWorkloadEndpoint() + wep := internalapi.NewWorkloadEndpoint() wep.Labels = map[string]string{"name": n} wep.Spec.Node = c.Hostname wep.Spec.Orchestrator = "felixfv" @@ -221,10 +238,18 @@ func New(c *infrastructure.Felix, name, profile, ip, ports, protocol string, opt func run(c *infrastructure.Felix, name, profile, ip, ports, protocol string, opts ...Opt) (w *Workload, err error) { w = New(c, name, profile, ip, ports, protocol, opts...) - return w, w.Start() + err = w.Start(c) + if err != nil { + return w, err + } + return w, nil } -func (w *Workload) Start() error { +type CleanupProvider interface { + AddCleanup(func()) +} + +func (w *Workload) Start(cleanupProvider CleanupProvider) error { var err error // Start the workload. @@ -267,14 +292,15 @@ func (w *Workload) Start() error { return fmt.Errorf("runCmd Start failed: %v", err) } + // Make sure we get stopped. + cleanupProvider.AddCleanup(w.Stop) + // Read the workload's namespace path, which it writes to its standard output. stdoutReader := bufio.NewReader(w.outPipe) stderrReader := bufio.NewReader(w.errPipe) var errDone sync.WaitGroup - errDone.Add(1) - go func() { - defer errDone.Done() + errDone.Go(func() { for { line, err := stderrReader.ReadString('\n') if err != nil { @@ -283,7 +309,7 @@ func (w *Workload) Start() error { } _, _ = fmt.Fprintf(ginkgo.GinkgoWriter, "%v[stderr] %v", w.Name, line) } - }() + }) pid, err := stdoutReader.ReadString('\n') if err != nil { @@ -707,8 +733,8 @@ func (w *Workload) ToMatcher(explicitPort ...uint16) *connectivity.Matcher { const nsprefix = "/var/run/netns/" func (w *Workload) netns() string { - if strings.HasPrefix(w.namespacePath, nsprefix) { - return strings.TrimPrefix(w.namespacePath, nsprefix) + if after, ok := strings.CutPrefix(w.namespacePath, nsprefix); ok { + return after } return "" @@ -875,7 +901,7 @@ func (w *Workload) InterfaceIndex() int { func (w *Workload) RenameInterface(from, to string) { var err error sleep := 100 * time.Millisecond - for try := 0; try < 40; try++ { + for range 40 { // Can fail with EBUSY. err = w.C.ExecMayFail("ip", "link", "set", from, "name", to) if err == nil { diff --git a/felix/fv/xdp_test.go b/felix/fv/xdp_test.go index cf850164080..346ddce38dd 100644 --- a/felix/fv/xdp_test.go +++ b/felix/fv/xdp_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build fvtests - package fv_test import ( @@ -22,9 +20,9 @@ import ( "strconv" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/calico/felix/bpf" "github.com/projectcalico/calico/felix/environment" @@ -32,6 +30,8 @@ import ( "github.com/projectcalico/calico/felix/fv/infrastructure" "github.com/projectcalico/calico/felix/fv/utils" "github.com/projectcalico/calico/felix/fv/workload" + "github.com/projectcalico/calico/felix/rules" + "github.com/projectcalico/calico/felix/types" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/options" @@ -97,7 +97,7 @@ func xdpTest(getInfra infrastructure.InfraFactory, proto string) { "8055,8056,1234", proto) - hostEp := api.NewHostEndpoint() + hostEp := v3.NewHostEndpoint() hostEp.Name = fmt.Sprintf("host-endpoint-%d", ii) hostEp.Labels = map[string]string{ "host-endpoint": "true", @@ -114,26 +114,6 @@ func xdpTest(getInfra infrastructure.InfraFactory, proto string) { cc = &connectivity.Checker{Protocol: proto} }) - AfterEach(func() { - if CurrentGinkgoTestDescription().Failed { - infra.DumpErrorData() - for _, felix := range tc.Felixes { - if NFTMode() { - logNFTDiags(felix) - } else { - felix.Exec("iptables-save", "-c") - } - } - } - - for _, wl := range hostW { - wl.Stop() - } - tc.Stop() - - infra.Stop() - }) - clnt, srvr := 0, 1 expectNoConnectivity := func(cc *connectivity.Checker) { @@ -204,19 +184,22 @@ func xdpTest(getInfra infrastructure.InfraFactory, proto string) { }) Context("with XDP blocklist on felix[srvr] blocking felixes[clnt]", func() { + // The expected iptables chain name for the xdpf policy. + xdpfChainName := rules.PolicyChainName("cali-pi-", &types.PolicyID{Name: "xdpf", Kind: v3.KindGlobalNetworkPolicy}, false) + BeforeEach(func() { order := float64(20) // allow everything - allowAllPolicy := api.NewGlobalNetworkPolicy() + allowAllPolicy := v3.NewGlobalNetworkPolicy() allowAllPolicy.Name = "allow-all" allowAllPolicy.Spec.Order = &order allowAllPolicy.Spec.Selector = "all()" - allowAllPolicy.Spec.Ingress = []api.Rule{{ - Action: api.Allow, + allowAllPolicy.Spec.Ingress = []v3.Rule{{ + Action: v3.Allow, }} - allowAllPolicy.Spec.Egress = []api.Rule{{ - Action: api.Allow, + allowAllPolicy.Spec.Egress = []v3.Rule{{ + Action: v3.Allow, }} _, err := client.GlobalNetworkPolicies().Create(utils.Ctx, allowAllPolicy, utils.NoOptions) Expect(err).NotTo(HaveOccurred()) @@ -227,15 +210,15 @@ func xdpTest(getInfra infrastructure.InfraFactory, proto string) { // apply XDP policy to felix[srvr] blocking felixes[clnt] by IP serverSelector := "role=='server'" - xdpPolicy := api.NewGlobalNetworkPolicy() + xdpPolicy := v3.NewGlobalNetworkPolicy() xdpPolicy.Name = "xdpf" // keep name short, so it matches with the iptables chain name xdpPolicy.Spec.Order = &order xdpPolicy.Spec.DoNotTrack = true xdpPolicy.Spec.ApplyOnForward = true xdpPolicy.Spec.Selector = serverSelector - xdpPolicy.Spec.Ingress = []api.Rule{{ - Action: api.Deny, - Source: api.EntityRule{ + xdpPolicy.Spec.Ingress = []v3.Rule{{ + Action: v3.Deny, + Source: v3.EntityRule{ Selector: "xdpblocklist-set=='true'", }, }} @@ -269,7 +252,7 @@ func xdpTest(getInfra infrastructure.InfraFactory, proto string) { applyGlobalNetworkSets := func(name string, ip string, cidrToHexSuffix string, update bool) (hexCIDR []string) { // create GlobalNetworkSet with IP of felixes[clnt] - var srcNS *api.GlobalNetworkSet + var srcNS *v3.GlobalNetworkSet var err error if update { srcNS, err = client.GlobalNetworkSets().Get(utils.Ctx, name, options.GetOptions{}) @@ -279,7 +262,7 @@ func xdpTest(getInfra infrastructure.InfraFactory, proto string) { _, err = client.GlobalNetworkSets().Update(utils.Ctx, srcNS, utils.NoOptions) } else { - srcNS = api.NewGlobalNetworkSet() + srcNS = v3.NewGlobalNetworkSet() srcNS.Name = name srcNS.Spec.Nets = []string{ip} srcNS.Labels = map[string]string{ @@ -319,7 +302,7 @@ func xdpTest(getInfra infrastructure.InfraFactory, proto string) { Expect(doHping()).To(HaveOccurred()) if !BPFMode() && !NFTMode() { - output, err := tc.Felixes[srvr].ExecOutput("iptables", "-t", "raw", "-v", "-n", "-L", "cali-pi-default/default.xdpf") + output, err := tc.Felixes[srvr].ExecOutput("iptables", "-t", "raw", "-v", "-n", "-L", xdpfChainName) // the only rule that refers to a cali40-prefixed ipset should // have 0 packets/bytes because the raw small packets should've been // blocked by XDP @@ -352,7 +335,7 @@ func xdpTest(getInfra infrastructure.InfraFactory, proto string) { Expect(doPing()).To(HaveOccurred()) if !BPFMode() && !NFTMode() { - output, err := tc.Felixes[srvr].ExecOutput("iptables", "-t", "raw", "-v", "-n", "-L", "cali-pi-default/default.xdpf") + output, err := tc.Felixes[srvr].ExecOutput("iptables", "-t", "raw", "-v", "-n", "-L", xdpfChainName) // the only rule that refers to a cali40-prefixed ipset should // have 0 packets/bytes because the icmp packets should've been // blocked by XDP @@ -390,13 +373,12 @@ func xdpTest(getInfra infrastructure.InfraFactory, proto string) { if !BPFMode() { if !NFTMode() { Eventually(func() string { - out, _ := tc.Felixes[srvr].ExecOutput("iptables", "-t", "raw", "-v", "-n", "-L", - "cali-pi-default/default.xdpf") + out, _ := tc.Felixes[srvr].ExecOutput("iptables", "-t", "raw", "-v", "-n", "-L", xdpfChainName) return out }).Should(MatchRegexp(`(?m)^\s+0\s+0.*cali40s:`)) } else { Eventually(func() string { - out, _ := tc.Felixes[srvr].ExecOutput("nft", "list", "chain", "ip", "calico", "raw-cali-pi-default/default.xdpf") + out, _ := tc.Felixes[srvr].ExecOutput("nft", "list", "chain", "ip", "calico", "raw-cali-pi-gnp/xdpf") return out }).Should(MatchRegexp(`packets 0 bytes 0`)) } @@ -508,8 +490,7 @@ func xdpTest(getInfra infrastructure.InfraFactory, proto string) { if !BPFMode() && !NFTMode() { // the only rule that refers to a cali40-prefixed ipset should have 0 packets/bytes Eventually(func() string { - out, _ := tc.Felixes[srvr].ExecOutput("iptables", "-t", "raw", "-v", "-n", "-L", - "cali-pi-default/default.xdpf") + out, _ := tc.Felixes[srvr].ExecOutput("iptables", "-t", "raw", "-v", "-n", "-L", xdpfChainName) return out }).Should(MatchRegexp(`(?m)^\s+0\s+0.*cali40s:`)) } diff --git a/felix/generictables/match_builder.go b/felix/generictables/match_builder.go index 45382cb3102..0763b8c7576 100644 --- a/felix/generictables/match_builder.go +++ b/felix/generictables/match_builder.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2024 Tigera, Inc. All rights reserved. +// Copyright (c) 2016-2026 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -71,6 +71,7 @@ type MatchCriteria interface { NotICMPV6Type(t uint8) MatchCriteria ICMPV6TypeAndCode(t, c uint8) MatchCriteria NotICMPV6TypeAndCode(t, c uint8) MatchCriteria + Limit(r string, b uint16) MatchCriteria // Only supported in nftables. InInterfaceVMAP(mapname string) MatchCriteria diff --git a/felix/hashutils/id.go b/felix/hashutils/id.go deleted file mode 100644 index 6d27b24e2b6..00000000000 --- a/felix/hashutils/id.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2016-2017 Tigera, Inc. All rights reserved. -// -// 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 hashutils - -import ( - "crypto/sha256" - "encoding/base64" - - log "github.com/sirupsen/logrus" -) - -const shortenedPrefix = "_" - -// GetLengthLimitedID returns an ID that consists of the given prefix and, either the given suffix, -// or, if that would exceed the length limit, a cryptographic hash of the suffix, truncated to the -// required length. -func GetLengthLimitedID(fixedPrefix, suffix string, maxLength int) string { - prefixLen := len(fixedPrefix) - suffixLen := len(suffix) - totalLen := prefixLen + suffixLen - if totalLen > maxLength || (totalLen == maxLength && suffix[0:1] == shortenedPrefix) { - // Either it's just too long, or it's exactly the right length but it happens to - // start with the character that we use to denote a shortened string, which could - // result in a clash. Hash the value and truncate... - hasher := sha256.New() - _, err := hasher.Write([]byte(suffix)) - if err != nil { - log.WithError(err).Panic("Failed to write suffix to hash.") - } - hash := base64.RawURLEncoding.EncodeToString(hasher.Sum(nil)) - charsLeftForHash := maxLength - 1 - prefixLen - return fixedPrefix + shortenedPrefix + hash[0:charsLeftForHash] - } - // No need to shorten. - return fixedPrefix + suffix -} diff --git a/felix/hashutils/id_test.go b/felix/hashutils/id_test.go deleted file mode 100644 index 32dd6ed3848..00000000000 --- a/felix/hashutils/id_test.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2016-2017 Tigera, Inc. All rights reserved. -// -// 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 hashutils_test - -import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - - . "github.com/projectcalico/calico/felix/hashutils" -) - -var _ = Describe("Id", func() { - It("should return the suffix if short enough", func() { - Expect(GetLengthLimitedID("felix", "1234", 10)).To(Equal("felix1234")) - }) - It("should return the suffix if exact length without _ prefix", func() { - Expect(GetLengthLimitedID("felix", "123456", 11)).To(Equal("felix123456")) - }) - It("should return the hash if exact length with _ prefix", func() { - Expect(GetLengthLimitedID("felix", "_2345", 10)).To(Equal("felix_kMQI")) - }) - It("should return the hash if too long prefix", func() { - Expect(GetLengthLimitedID("felix", "12345678910", 13)).To(Equal("felix_Y2QCZIS")) - }) -}) diff --git a/felix/idalloc/idalloc.go b/felix/idalloc/idalloc.go index 08697d18d2a..ccd2f5a19ba 100644 --- a/felix/idalloc/idalloc.go +++ b/felix/idalloc/idalloc.go @@ -81,7 +81,7 @@ func (a *IDAllocator) GetOrAlloc(id string) uint64 { if debug { log.WithFields(log.Fields{"id": id}).Debug("No existing IP set ID mapping, allocating one...") } - for n := uint64(0); n < math.MaxUint64; n++ { + for n := range uint64(math.MaxUint64) { candidate := a.TrialHash(id, n) if candidate == 0 { if debug { diff --git a/felix/idalloc/idalloc_suite_test.go b/felix/idalloc/idalloc_suite_test.go index 6d376ac9e6d..f5358753d90 100644 --- a/felix/idalloc/idalloc_suite_test.go +++ b/felix/idalloc/idalloc_suite_test.go @@ -17,9 +17,8 @@ package idalloc_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestCalculationGraph(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/idalloc_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "ID allocation graph Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_idalloc_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/idalloc", suiteConfig, reporterConfig) } diff --git a/felix/idalloc/index_allocator.go b/felix/idalloc/index_allocator.go index d5ecb14b9cf..1387eaf3b85 100644 --- a/felix/idalloc/index_allocator.go +++ b/felix/idalloc/index_allocator.go @@ -102,7 +102,7 @@ func (r *IndexAllocator) ReleaseIndex(index int) { // GrabBlock tries to grab a contiguous block of indices from the stack func (r *IndexAllocator) GrabBlock(len int) (set.Set[int], error) { indices := set.New[int]() - for i := 0; i < len; i++ { + for range len { idx, err := r.GrabIndex() if err != nil { return indices, err diff --git a/felix/idalloc/index_allocator_test.go b/felix/idalloc/index_allocator_test.go index 19aed505b2d..d23a21645c1 100644 --- a/felix/idalloc/index_allocator_test.go +++ b/felix/idalloc/index_allocator_test.go @@ -15,7 +15,7 @@ package idalloc_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/projectcalico/calico/felix/idalloc" diff --git a/felix/ifacemonitor/ifacemonitor_suite_test.go b/felix/ifacemonitor/ifacemonitor_suite_test.go index ac9914661f0..17d9db2cf84 100644 --- a/felix/ifacemonitor/ifacemonitor_suite_test.go +++ b/felix/ifacemonitor/ifacemonitor_suite_test.go @@ -17,9 +17,8 @@ package ifacemonitor_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestIfacemonitor(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/ifacemonitor_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Ifacemonitor Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_ifacemonitor_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/ifacemonitor", suiteConfig, reporterConfig) } diff --git a/felix/ifacemonitor/ifacemonitor_test.go b/felix/ifacemonitor/ifacemonitor_test.go index 75e05cd2c55..f5a923ca92f 100644 --- a/felix/ifacemonitor/ifacemonitor_test.go +++ b/felix/ifacemonitor/ifacemonitor_test.go @@ -23,7 +23,7 @@ import ( "syscall" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" "github.com/vishvananda/netlink" @@ -270,7 +270,7 @@ func (nl *netlinkTest) ListLocalRoutes(link netlink.Link, family int) ([]netlink model, prs := nl.links[name] var routes []netlink.Route if prs { - model.addrs.Iter(func(addr string) error { + for addr := range model.addrs.All() { net, err := netlink.ParseIPNet(addr) if err != nil { panic("Address parsing failed") @@ -290,8 +290,7 @@ func (nl *netlinkTest) ListLocalRoutes(link netlink.Link, family int) ([]netlink }) } } - return nil - }) + } } return routes, nil } @@ -303,7 +302,7 @@ func (nl *netlinkTest) AddrList(link netlink.Link, family int) ([]netlink.Addr, model, prs := nl.links[name] addrs := []netlink.Addr{} if prs { - model.addrs.Iter(func(addr string) error { + for addr := range model.addrs.All() { net, err := netlink.ParseIPNet(addr) if err != nil { panic("Address parsing failed") @@ -321,8 +320,7 @@ func (nl *netlinkTest) AddrList(link netlink.Link, family int) ([]netlink.Addr, }) } } - return nil - }) + } } return addrs, nil } @@ -558,7 +556,7 @@ var _ = Describe("ifacemonitor", func() { } // Repeat for 3 different interfaces (to test regexp of interface excludes) - for index := 0; index < 3; index++ { + for index := range 3 { interfaceName := fmt.Sprintf("kube-ipvs%d", index) netlinkUpdates(interfaceName) } diff --git a/felix/ifacemonitor/update_filter.go b/felix/ifacemonitor/update_filter.go index f731a495312..4b889ae4939 100644 --- a/felix/ifacemonitor/update_filter.go +++ b/felix/ifacemonitor/update_filter.go @@ -69,7 +69,7 @@ func FilterUpdates(ctx context.Context, type timestampedUpd struct { ReadyAt time.Time - Update interface{} // RouteUpdate or LinkUpdate + Update any // RouteUpdate or LinkUpdate } updatesByIfaceIdx := map[int][]timestampedUpd{} diff --git a/felix/ip/ip_addr.go b/felix/ip/ip_addr.go index 87e0fdbf1b4..0734bd52044 100644 --- a/felix/ip/ip_addr.go +++ b/felix/ip/ip_addr.go @@ -90,13 +90,14 @@ func (a V4Addr) String() string { } func (a V4Addr) AsBinary() string { - ipInBinary := fmt.Sprintf("%04b", 4) - for ii := 0; ii < net.IPv4len; ii++ { + var ipInBinary strings.Builder + ipInBinary.WriteString(fmt.Sprintf("%04b", 4)) + for ii := range net.IPv4len { temp := fmt.Sprintf("%08b", a[ii]) - ipInBinary += temp + ipInBinary.WriteString(temp) } - return ipInBinary + return ipInBinary.String() } func (a V4Addr) Add(n int) Addr { @@ -154,13 +155,14 @@ func (a V6Addr) String() string { } func (a V6Addr) AsBinary() string { - ipInBinary := fmt.Sprintf("%04b", 6) - for ii := 0; ii < net.IPv6len; ii++ { + var ipInBinary strings.Builder + ipInBinary.WriteString(fmt.Sprintf("%04b", 6)) + for ii := range net.IPv6len { temp := fmt.Sprintf("%08b", a[ii]) - ipInBinary += temp + ipInBinary.WriteString(temp) } - return ipInBinary + return ipInBinary.String() } func (a V6Addr) Add(n int) Addr { @@ -238,13 +240,14 @@ func (c V4CIDR) String() string { } func (c V4CIDR) AsBinary() string { - ipInBinary := fmt.Sprintf("%04b", 4) - for ii := 0; ii < net.IPv4len; ii++ { + var ipInBinary strings.Builder + ipInBinary.WriteString(fmt.Sprintf("%04b", 4)) + for ii := range net.IPv4len { temp := fmt.Sprintf("%08b", c.addr[ii]) - ipInBinary += temp + ipInBinary.WriteString(temp) } - return ipInBinary[0 : c.prefix+4] + return ipInBinary.String()[0 : c.prefix+4] } func (c V4CIDR) IsSingleAddress() bool { @@ -303,12 +306,13 @@ func (c V6CIDR) String() string { } func (c V6CIDR) AsBinary() string { - ipInBinary := fmt.Sprintf("%04b", 6) - for ii := 0; ii < net.IPv6len; ii++ { + var ipInBinary strings.Builder + ipInBinary.WriteString(fmt.Sprintf("%04b", 6)) + for ii := range net.IPv6len { temp := fmt.Sprintf("%08b", c.addr[ii]) - ipInBinary += temp + ipInBinary.WriteString(temp) } - return ipInBinary[0 : c.prefix+4] + return ipInBinary.String()[0 : c.prefix+4] } func (c V6CIDR) IsSingleAddress() bool { diff --git a/felix/ip/ip_addr_test.go b/felix/ip/ip_addr_test.go index a70a2b8fb50..66f777c8a78 100644 --- a/felix/ip/ip_addr_test.go +++ b/felix/ip/ip_addr_test.go @@ -17,7 +17,7 @@ package ip_test import ( "fmt" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/projectcalico/calico/felix/ip" diff --git a/felix/ip/ip_suite_test.go b/felix/ip/ip_suite_test.go index c31a9473fae..22060ebd34f 100644 --- a/felix/ip/ip_suite_test.go +++ b/felix/ip/ip_suite_test.go @@ -17,17 +17,17 @@ package ip_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestIp(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/ip_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "IP Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_ip_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/ip", suiteConfig, reporterConfig) } func init() { diff --git a/felix/ip/trie.go b/felix/ip/trie.go index a5a152ba699..687c8dd57d8 100644 --- a/felix/ip/trie.go +++ b/felix/ip/trie.go @@ -28,7 +28,7 @@ type CIDRTrie struct { type CIDRNode struct { cidr CIDR children [2]*CIDRNode - data interface{} + data any } func NewCIDRTrie() *CIDRTrie { @@ -94,10 +94,10 @@ func deleteInternal(n *CIDRNode, cidr CIDR) *CIDRNode { type CIDRTrieEntry struct { CIDR CIDR - Data interface{} + Data any } -func (t *CIDRTrie) Get(cidr CIDR) interface{} { +func (t *CIDRTrie) Get(cidr CIDR) any { return t.root.get(cidr) } @@ -111,7 +111,7 @@ func (t *CIDRTrie) LookupPath(buffer []CIDRTrieEntry, cidr CIDR) []CIDRTrieEntry } // LPM does a longest prefix match on the trie -func (t *CIDRTrie) LPM(cidr CIDR) (CIDR, interface{}) { +func (t *CIDRTrie) LPM(cidr CIDR) (CIDR, any) { n := t.root var match *CIDRNode @@ -180,7 +180,7 @@ func (n *CIDRNode) lookupPath(buffer []CIDRTrieEntry, cidr CIDR) []CIDRTrieEntry return child.lookupPath(buffer, cidr) } -func (n *CIDRNode) get(cidr CIDR) interface{} { +func (n *CIDRNode) get(cidr CIDR) any { node := n.getNode(cidr, false) if node == nil { return nil @@ -295,7 +295,7 @@ func (n *CIDRNode) appendTo(s []CIDRTrieEntry) []CIDRTrieEntry { return s } -func (n *CIDRNode) visit(f func(cidr CIDR, data interface{}) bool) bool { +func (n *CIDRNode) visit(f func(cidr CIDR, data any) bool) bool { if n == nil { return true } @@ -317,7 +317,7 @@ func (t *CIDRTrie) ToSlice() []CIDRTrieEntry { return t.root.appendTo(nil) } -func (t *CIDRTrie) Visit(f func(cidr CIDR, data interface{}) bool) { +func (t *CIDRTrie) Visit(f func(cidr CIDR, data any) bool) { t.root.visit(f) } @@ -370,7 +370,7 @@ func (t *CIDRTrie) ClosestDescendants(buf []CIDR, parent CIDR) []CIDR { return buf } -func (t *CIDRTrie) Update(cidr CIDR, value interface{}) { +func (t *CIDRTrie) Update(cidr CIDR, value any) { if value == nil { logrus.Panic("Can't store nil in a CIDRTrie") } @@ -455,24 +455,13 @@ func CommonPrefix(a, b CIDR) CIDR { } func V4CommonPrefix(a, b V4CIDR) V4CIDR { - var result V4CIDR - var maxLen uint8 - if b.prefix < a.prefix { - maxLen = b.prefix - } else { - maxLen = a.prefix - } - a32 := a.addr.AsUint32() b32 := b.addr.AsUint32() xored := a32 ^ b32 // Has a zero bit wherever the two values are the same. commonPrefixLen := uint8(bits.LeadingZeros32(xored)) - if commonPrefixLen > maxLen { - result.prefix = maxLen - } else { - result.prefix = commonPrefixLen - } + var result V4CIDR + result.prefix = min(commonPrefixLen, min(b.prefix, a.prefix)) mask := uint32(0xffffffff) << (32 - result.prefix) commonPrefix32 := mask & a32 @@ -482,15 +471,6 @@ func V4CommonPrefix(a, b V4CIDR) V4CIDR { } func V6CommonPrefix(a, b V6CIDR) V6CIDR { - var result V6CIDR - var maxLen uint8 - - if b.prefix < a.prefix { - maxLen = b.prefix - } else { - maxLen = a.prefix - } - a_h, a_l := a.addr.AsUint64Pair() b_h, b_l := b.addr.AsUint64Pair() @@ -499,17 +479,15 @@ func V6CommonPrefix(a, b V6CIDR) V6CIDR { commonPrefixLen := uint8(bits.LeadingZeros64(xored_h)) + var result V6CIDR + maxLen := min(b.prefix, a.prefix) if xored_h == 0 { // This means a_h == b_h and commonPrefixLen will be > 64. The first // 8 bytes of the result will be equal to a_h (and b_h), last 8 will // be the common prefix of a_l and b_l. commonPrefixLen = 64 + uint8(bits.LeadingZeros64(xored_l)) binary.BigEndian.PutUint64(result.addr[:8], a_h) - if commonPrefixLen > maxLen { - result.prefix = maxLen - } else { - result.prefix = commonPrefixLen - } + result.prefix = min(commonPrefixLen, maxLen) mask := uint64(0xffffffffffffffff) << (128 - result.prefix) commonPrefix64 := mask & a_l binary.BigEndian.PutUint64(result.addr[8:], commonPrefix64) @@ -517,11 +495,7 @@ func V6CommonPrefix(a, b V6CIDR) V6CIDR { // This means commonPrefixLen will be < 64. Just the first 8 bytes of // the result will be filled with the common prefix of a_h and b_h, // last 8 will be 0. - if commonPrefixLen > maxLen { - result.prefix = maxLen - } else { - result.prefix = commonPrefixLen - } + result.prefix = min(commonPrefixLen, maxLen) mask := uint64(0xffffffffffffffff) << (64 - result.prefix) commonPrefix64 := mask & a_h binary.BigEndian.PutUint64(result.addr[:8], commonPrefix64) diff --git a/felix/ip/trie_test.go b/felix/ip/trie_test.go index c3f0b23cee9..eb47c1e767b 100644 --- a/felix/ip/trie_test.go +++ b/felix/ip/trie_test.go @@ -18,8 +18,7 @@ import ( "fmt" "testing" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/ip" @@ -97,7 +96,7 @@ var _ = Describe("CIDRTrie tests", func() { return s } - lpm := func(cidr string, expectedCidr string) interface{} { + lpm := func(cidr string, expectedCidr string) any { cidrIn := ip.MustParseCIDROrIP(cidr) cidrOut, data := trie.LPM(cidrIn) @@ -438,7 +437,7 @@ var _ = Describe("CIDRTrie tests", func() { update(c) } var expSlice []string - expected.Iter(func(cidr string) error { + for cidr := range expected.All() { expSlice = append(expSlice, cidr) path := lookup(cidr) @@ -446,9 +445,7 @@ var _ = Describe("CIDRTrie tests", func() { Expect(expected.Contains(c)).To(BeTrue(), fmt.Sprintf( "Trie returned a path (%v) including a CIDR that wasn't supposed to be in the trie (%v)", path, c)) } - - return nil - }) + } Expect(contents()).To(ConsistOf(expSlice), fmt.Sprintf("Trie had incorrect contents with this sequence of CIDRs: %s", cidrs)) } diff --git a/felix/ipsets/ipsets.go b/felix/ipsets/ipsets.go index 55e37ff3bf4..2f4fc237e5b 100644 --- a/felix/ipsets/ipsets.go +++ b/felix/ipsets/ipsets.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2024 Tigera, Inc. All rights reserved. +// Copyright (c) 2017-2025 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -36,6 +36,7 @@ import ( const ( MaxIPSetDeletionsPerIteration = 1 + MaxRetryAttempt = 10 ) type dataplaneMetadata struct { @@ -44,6 +45,7 @@ type dataplaneMetadata struct { RangeMin int RangeMax int DeleteFailed bool + ListFailed bool } // IPSets manages a whole "plane" of IP sets, i.e. all the IPv4 sets, or all the IPv6 IP sets. @@ -195,10 +197,9 @@ func (s *IPSets) AddOrReplaceIPSet(setMetadata IPSetMetadata, members []string) desiredMembers.Delete(k) } }) - canonMembers.Iter(func(m IPSetMember) error { + for m := range canonMembers.All() { desiredMembers.Add(m) - return nil - }) + } s.updateDirtiness(mainIPSetName) } @@ -252,10 +253,9 @@ func (s *IPSets) AddMembers(setID string, newMembers []string) { return } membersTracker := s.mainSetNameToMembers[setName] - canonMembers.Iter(func(member IPSetMember) error { + for member := range canonMembers.All() { membersTracker.Desired().Add(member) - return nil - }) + } s.updateDirtiness(setName) } @@ -273,10 +273,9 @@ func (s *IPSets) RemoveMembers(setID string, removedMembers []string) { return } membersTracker := s.mainSetNameToMembers[setName] - canonMembers.Iter(func(member IPSetMember) error { + for member := range canonMembers.All() { membersTracker.Desired().Delete(member) - return nil - }) + } s.updateDirtiness(setName) } @@ -353,22 +352,31 @@ func (s *IPSets) ApplyUpdates(listener UpdateListener) { retryDelay *= 2 } - for attempt := 0; attempt < 10; attempt++ { + var resyncErr, updateErr error + for attempt := range MaxRetryAttempt { if attempt > 0 { s.logCxt.Info("Retrying after an ipsets update failure...") } + treatFailureAsTransient := attempt < MaxRetryAttempt/2 if s.fullResyncRequired || s.ipSetsRequiringResync.Len() > 0 { // Compare our in-memory state against the dataplane and queue up // modifications to fix any inconsistencies. s.logCxt.Debug("Resyncing ipsets with dataplane.") s.opReporter.RecordOperation(fmt.Sprint("resync-ipsets-v", s.IPVersionConfig.Family.Version())) - if err := s.tryResync(); err != nil { - s.logCxt.WithError(err).Warning("Failed to resync with dataplane") - backOff() - continue + if resyncErr = s.tryResync(); resyncErr != nil { + s.logCxt.WithError(resyncErr).Warning("Failed to resync with dataplane") + + // After a few attempts, most likely, we are dealing with a persistent failure. + // This could be due to different failures, like userspace and kernel incompatibility. + // The incompatibility failure can be fixed by swapping the one Felix understand (created from + // desired state) and the one (with higher revision) in dataplane. As such, we should stop re-trying + // and instead fall through to tryUpdates() below, which will do the swap. + if treatFailureAsTransient { + backOff() + continue + } } - s.fullResyncRequired = false } // Opportunistically delete some temporary IP sets. It's possible @@ -377,22 +385,26 @@ func (s *IPSets) ApplyUpdates(listener UpdateListener) { s.tryTempIPSetDeletions() dirtyIPSets := s.dirtyIPSetsForUpdate() - if err := s.tryUpdates(dirtyIPSets, listener); err != nil { - if attempt >= 5 { + if updateErr = s.tryUpdates(dirtyIPSets, listener); updateErr != nil { + if treatFailureAsTransient { + // Transient failure, resync the IP sets that we failed to update. + s.logCxt.WithError(updateErr).WithField("attempt", attempt).Warning( + "Failed to update IP sets. Will do partial resync.") + } else { // Persistent failures, try a full resync. - s.logCxt.WithError(err).WithField("attempt", attempt).Warning( + s.logCxt.WithError(updateErr).WithField("attempt", attempt).Warning( "Persistently failed to update IP sets. Will do full resync.") s.QueueResync() - } else { - // More than one failure, resync the IP sets that we failed to update. - s.logCxt.WithError(err).WithField("attempt", attempt).Warning( - "Failed to update IP sets. Will do partial resync.") } countNumIPSetErrors.Inc() + } + + if resyncErr != nil || updateErr != nil { backOff() continue } + s.fullResyncRequired = false success = true break } @@ -429,10 +441,9 @@ func (s *IPSets) tryResync() (err error) { if s.fullResyncRequired { s.setNameToProgrammedMetadata.Dataplane().DeleteAll() } else { - s.ipSetsRequiringResync.Iter(func(name string) error { + for name := range s.ipSetsRequiringResync.All() { s.setNameToProgrammedMetadata.Dataplane().Delete(name) - return nil - }) + } } // Even if we're doing a partial resync, we still list all IP set names. @@ -440,20 +451,18 @@ func (s *IPSets) tryResync() (err error) { // are known to exist. ipSets, err := s.CalicoIPSets() if err != nil { - s.logCxt.WithError(err).Error("Failed to get the list of ipsets") + s.logCxt.WithError(err).Error("Failed to get the list of Calico ipsets") return } if debug { - s.logCxt.Debugf("List of ipsets: %v", ipSets) + s.logCxt.Debugf("List of calico ipsets: %v", ipSets) } ipSetPartOfSync := func(name string) bool { - if s.fullResyncRequired { - return true - } - return s.ipSetsRequiringResync.Contains(name) + return s.fullResyncRequired || s.ipSetsRequiringResync.Contains(name) } + var failedIPSets []string for _, name := range ipSets { if !ipSetPartOfSync(name) { // Skipping this IP set on this pass. @@ -462,16 +471,26 @@ func (s *IPSets) tryResync() (err error) { if debug { s.logCxt.Debugf("Parsing IP set %v.", name) } - err = s.resyncIPSet(name) - if err != nil { - s.logCxt.WithError(err).Errorf("Failed to parse ipset %v", name) - return + if err = s.resyncIPSet(name); err != nil { + // Ignore failures of IP sets not in desired state, as those will be cleaned up later. + if _, desired := s.setNameToProgrammedMetadata.Desired().Get(name); desired { + failedIPSets = append(failedIPSets, name) + s.logCxt.WithError(err).WithField("name", name). + Warn("Failed to parse required Calico-owned ipset that is needed, will try recreating it.") + } else { + s.logCxt.WithError(err).WithField("name", name). + Warn("Failed to parse Calico-owned ipset that is no longer needed, will queue it for deletion.") + } } else { // Successful resync of this IP set, clear any pending partial resync. s.ipSetsRequiringResync.Discard(name) } } + if len(failedIPSets) > 0 { + return fmt.Errorf("failed to parse IPSets %v", strings.Join(failedIPSets, ",")) + } + // Mark any IP sets that we didn't see as empty. for name, members := range s.mainSetNameToMembers { if !ipSetPartOfSync(name) { @@ -551,8 +570,9 @@ func (s *IPSets) resyncIPSet(ipSetName string) error { // // As we stream through the data, we extract the name of the IP set and its members. We // use the IP set's metadata to convert each member to its canonical form for comparison. + meta := dataplaneMetadata{} + debug := log.GetLevel() >= log.DebugLevel err := s.runIPSetList(ipSetName, func(scanner *bufio.Scanner) error { - debug := log.GetLevel() >= log.DebugLevel ipSetName := "" var ipSetType IPSetType for scanner.Scan() { @@ -576,9 +596,7 @@ func (s *IPSets) resyncIPSet(ipSetName string) error { // When we hit the Header line we should know the name, and type of the IP set, which lets // us update the tracker. parts := strings.Split(line, " ") - meta := dataplaneMetadata{ - Type: ipSetType, - } + meta.Type = ipSetType for idx, p := range parts { if p == "maxelem" { if idx+1 >= len(parts) { @@ -609,7 +627,6 @@ func (s *IPSets) resyncIPSet(ipSetName string) error { break } } - s.setNameToProgrammedMetadata.Dataplane().Set(ipSetName, meta) } if strings.HasPrefix(line, "Members:") { // Start of a Members entry, following this, there'll be one member per @@ -681,9 +698,16 @@ func (s *IPSets) resyncIPSet(ipSetName string) error { return scanner.Err() }) if err != nil { - return err + // This can occur if we have version skew with the version of IP set + // used to create the IP set. Mark the metadata as invalid in order + // to trigger the IP set to be recreated. + meta.ListFailed = true } - return nil + if debug { + s.logCxt.WithField("setName", ipSetName).Debugf("Parsed metadata from dataplane %+v", meta) + } + s.setNameToProgrammedMetadata.Dataplane().Set(ipSetName, meta) + return err } func (s *IPSets) runIPSetList(arg string, parsingFunc func(*bufio.Scanner) error) error { @@ -854,14 +878,13 @@ func (s *IPSets) tryUpdates(dirtyIPSets []string, listener UpdateListener) (err func (s *IPSets) dirtyIPSetsForUpdate() []string { var dirtyIPSets []string - s.ipSetsWithDirtyMembers.Iter(func(setName string) error { + for setName := range s.ipSetsWithDirtyMembers.All() { if _, ok := s.setNameToProgrammedMetadata.Desired().Get(setName); !ok { // Skip deletions and IP sets that aren't needed due to the filter. - return nil + continue } dirtyIPSets = append(dirtyIPSets, setName) - return nil - }) + } s.setNameToProgrammedMetadata.PendingUpdates().Iter(func( setName string, v dataplaneMetadata, @@ -907,7 +930,7 @@ func (s *IPSets) writeUpdates(setName string, w io.Writer, listener UpdateListen // writeLine until an error occurs, writeLine writes a line to the output, after an error, // it is a no-op. - writeLine := func(format string, a ...interface{}) { + writeLine := func(format string, a ...any) { if err != nil { return } @@ -1014,7 +1037,8 @@ func (s *IPSets) ApplyDeletions() bool { if numDeletions >= MaxIPSetDeletionsPerIteration { // Deleting IP sets is slow (40ms) and serialised in the kernel. Avoid holding up the main loop // for too long. We'll leave the remaining sets pending deletion and mop them up next time. - log.Debugf("Deleted batch of %d IP sets, rate limiting further IP set deletions.", MaxIPSetDeletionsPerIteration) + log.Debugf("Deleted batch of %d IP sets, rate limiting further IP set deletions.", + MaxIPSetDeletionsPerIteration) // Leave the item in the set, so we'll do another batch of deletions next time around the loop. return deltatracker.IterActionNoOpStopIteration } @@ -1070,7 +1094,8 @@ func (s *IPSets) tryTempIPSetDeletions() { if numDeletions >= MaxIPSetDeletionsPerIteration { // Deleting IP sets is slow (40ms) and serialised in the kernel. Avoid holding up the main loop // for too long. We'll leave the remaining sets pending deletion and mop them up next time. - log.Debugf("Deleted batch of 20 temp IP sets, rate limiting further IP set deletions.") + log.Debugf("Deleted batch of %d IP sets, rate limiting further IP set deletions.", + MaxIPSetDeletionsPerIteration) // Leave the item in the set, so we'll do another batch of deletions next time around the loop. return deltatracker.IterActionNoOpStopIteration } diff --git a/felix/ipsets/ipsets_suite_test.go b/felix/ipsets/ipsets_suite_test.go index c2bd5d46331..66d6b3c01ed 100644 --- a/felix/ipsets/ipsets_suite_test.go +++ b/felix/ipsets/ipsets_suite_test.go @@ -17,9 +17,8 @@ package ipsets_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestIPSets(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/ipsets_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "IP sets Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_ipsets_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/ipsets", suiteConfig, reporterConfig) } diff --git a/felix/ipsets/ipsets_test.go b/felix/ipsets/ipsets_test.go index 68579949656..c04189b00e5 100644 --- a/felix/ipsets/ipsets_test.go +++ b/felix/ipsets/ipsets_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2024 Tigera, Inc. All rights reserved. +// Copyright (c) 2016-2025 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,8 +19,7 @@ import ( "slices" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/ip" @@ -464,9 +463,48 @@ var _ = Describe("IP sets dataplane", func() { }) }) + It("Calico IP sets with unsupported revision and in desired sets should be re-created", func() { + dataplane.IPSetMetadata = map[string]setMetadata{ + v4MainIPSetName: { + Name: v4MainIPSetName, + Family: "inet", + Type: IPSetTypeHashIP, + MaxSize: 1234, + Revision: supportedMockRevision + 1, + }, + v4MainIPSetName2: { + Name: v4MainIPSetName2, + Family: "inet", + Type: IPSetTypeHashIP, + MaxSize: 1234, + Revision: supportedMockRevision + 1, + }, + v4MainIPSetName3: { + Name: v4MainIPSetName3, + Family: "inet", + Type: IPSetTypeHashIP, + MaxSize: 1234, + Revision: supportedMockRevision, + }, + } + dataplane.IPSetMembers[v4MainIPSetName] = set.From("10.0.0.1", "10.0.0.3") + dataplane.IPSetMembers[v4MainIPSetName2] = set.From("10.0.0.1", "10.0.0.4") + dataplane.IPSetMembers[v4MainIPSetName3] = set.From("10.0.0.5", "10.0.0.6") + + ipsets.AddOrReplaceIPSet(meta, []string{"10.0.0.1", "10.0.0.2"}) + ipsets.AddOrReplaceIPSet(meta3, []string{"10.0.0.5", "10.0.0.6"}) + + apply() + dataplane.ExpectMembers(map[string][]string{ + v4MainIPSetName: []string{"10.0.0.1", "10.0.0.2"}, + // v4MainIPSetName2 should be destroyed since it's not in the desired state. + v4MainIPSetName3: []string{"10.0.0.5", "10.0.0.6"}, // This IPSet should not be touched. + }) + }) + Describe("with many left-over IP sets in place", func() { BeforeEach(func() { - for i := 0; i < MaxIPSetDeletionsPerIteration*3; i++ { + for i := range MaxIPSetDeletionsPerIteration * 3 { setName := fmt.Sprintf("cali40s:%d", i) dataplane.IPSetMembers[setName] = set.From("10.0.0.1") } @@ -795,7 +833,7 @@ var _ = Describe("IP sets dataplane", func() { It("should be detected after many transient errors", func() { // Simulate lots of transient failures in a row, followed by success. - dataplane.RestoreOpFailures = slices.Repeat([]string{"write-ip"}, 6) + dataplane.RestoreOpFailures = slices.Repeat([]string{"write-ip"}, (MaxRetryAttempt/2)+1) // Trigger an update to only one IP set. ipsets.AddMembers(ipSetID2, []string{"10.0.0.4"}) apply() diff --git a/felix/ipsets/utils_for_test.go b/felix/ipsets/utils_for_test.go index f5c6b20d543..35fa48cb5b4 100644 --- a/felix/ipsets/utils_for_test.go +++ b/felix/ipsets/utils_for_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2024 Tigera, Inc. All rights reserved. +// Copyright (c) 2016-2025 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ import ( "strings" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" @@ -36,6 +36,10 @@ import ( // This file contains shared test infrastructure for testing the ipsets package. +const ( + supportedMockRevision = 5 +) + var ( errTransientFailure = errors.New("simulated transient failure") errPermanentFailure = errors.New("simulated permanent failure") @@ -454,6 +458,7 @@ type setMetadata struct { Name string Family IPFamily Type IPSetType + Revision int MaxSize int RangeMin int RangeMax int @@ -726,18 +731,26 @@ func (c *listCmd) main() { result = fmt.Errorf("ipset %v does not exists", c.SetName) return } - writef("Name: %s\n", c.SetName) meta, ok := c.Dataplane.IPSetMetadata[c.SetName] if !ok { // Default metadata for IP sets created by tests. meta = setMetadata{ - Name: v4MainIPSetName, - Family: IPFamilyV4, - Type: IPSetTypeHashIP, - MaxSize: 1234, + Name: v4MainIPSetName, + Family: IPFamilyV4, + Type: IPSetTypeHashIP, + Revision: supportedMockRevision, + MaxSize: 1234, } } + + if meta.Revision > supportedMockRevision { + result = fmt.Errorf("revision %v not supported", meta.Revision) + return + } + + writef("Name: %s\n", c.SetName) writef("Type: %s\n", meta.Type) + writef("Revision: %d\n", meta.Revision) switch meta.Type { case IPSetTypeBitmapPort: writef("Header: family %s range %d-%d\n", meta.Family, meta.RangeMin, meta.RangeMax) @@ -748,8 +761,7 @@ func (c *listCmd) main() { } writef("Field: foobar\n") // Dummy field, should get ignored. writef("Members:\n") - members.Iter(func(member string) error { + for member := range members.All() { writef("%s\n", member) - return nil - }) + } } diff --git a/felix/iptables/actions.go b/felix/iptables/actions.go index 55fa73adf46..af2800fc4e5 100644 --- a/felix/iptables/actions.go +++ b/felix/iptables/actions.go @@ -17,6 +17,8 @@ package iptables import ( "fmt" "math" + "net" + "strconv" "github.com/sirupsen/logrus" @@ -274,7 +276,7 @@ func (g DNATAction) ToFragment(features *environment.Features) string { if g.DestPort == 0 { return fmt.Sprintf("--jump DNAT --to-destination %s", g.DestAddr) } else { - return fmt.Sprintf("--jump DNAT --to-destination %s:%d", g.DestAddr, g.DestPort) + return fmt.Sprintf("--jump DNAT --to-destination %s", net.JoinHostPort(g.DestAddr, strconv.Itoa(int(g.DestPort)))) } } diff --git a/felix/iptables/actions_test.go b/felix/iptables/actions_test.go index 28c2e8b2fda..3ae739e0b54 100644 --- a/felix/iptables/actions_test.go +++ b/felix/iptables/actions_test.go @@ -15,7 +15,7 @@ package iptables_test import ( - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/environment" @@ -37,7 +37,9 @@ var _ = DescribeTable("Actions", Entry("SNATAction", environment.Features{}, SNATAction{ToAddr: "10.0.0.1"}, "--jump SNAT --to-source 10.0.0.1"), Entry("SNATAction fully random", environment.Features{SNATFullyRandom: true}, SNATAction{ToAddr: "10.0.0.1"}, "--jump SNAT --to-source 10.0.0.1 --random-fully"), Entry("MasqAction", environment.Features{}, MasqAction{}, "--jump MASQUERADE"), + Entry("MasqAction", environment.Features{}, MasqAction{ToPorts: "32768-65535"}, "--jump MASQUERADE --to-ports 32768-65535"), Entry("MasqAction", environment.Features{MASQFullyRandom: true}, MasqAction{}, "--jump MASQUERADE --random-fully"), + Entry("MasqAction", environment.Features{MASQFullyRandom: true}, MasqAction{ToPorts: "32768-65535"}, "--jump MASQUERADE --to-ports 32768-65535 --random-fully"), Entry("ClearMarkAction", environment.Features{}, ClearMarkAction{Mark: 0x1000}, "--jump MARK --set-mark 0/0x1000"), Entry("SetMarkAction", environment.Features{}, SetMarkAction{Mark: 0x1000}, "--jump MARK --set-mark 0x1000/0x1000"), Entry("SetMaskedMarkAction", environment.Features{}, SetMaskedMarkAction{ diff --git a/felix/iptables/iptables_ut_suite_test.go b/felix/iptables/iptables_ut_suite_test.go index f72447ab2f8..c37c24e042a 100644 --- a/felix/iptables/iptables_ut_suite_test.go +++ b/felix/iptables/iptables_ut_suite_test.go @@ -17,9 +17,8 @@ package iptables import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestIptablesUT(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/iptables_ut_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Iptables Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_iptables_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/iptables", suiteConfig, reporterConfig) } diff --git a/felix/iptables/lock_linux.go b/felix/iptables/lock_linux.go deleted file mode 100644 index 26961050a35..00000000000 --- a/felix/iptables/lock_linux.go +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright (c) 2020-2025 Tigera, Inc. All rights reserved. -// Copyright 2017 The Kubernetes Authors. -// -// 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. - -// This file is based on that extracted from Kubernetes at pkg/util/iptables/iptables_linux.go. - -package iptables - -import ( - "errors" - "fmt" - "io" - "net" - "os" - "sync" - "time" - - "github.com/prometheus/client_golang/prometheus" - log "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" - - cprometheus "github.com/projectcalico/calico/libcalico-go/lib/prometheus" -) - -var ( - summaryLockAcquisitionTime = cprometheus.NewSummary(prometheus.SummaryOpts{ - Name: "felix_iptables_lock_acquire_secs", - Help: "Time in seconds that it took to acquire the iptables lock(s).", - }) - countLockRetries = prometheus.NewCounterVec(prometheus.CounterOpts{ - Name: "felix_iptables_lock_retries", - Help: "Number of times the iptables lock was held by someone else and we had to retry.", - }, []string{"version"}) - countLockRetriesV14 = countLockRetries.WithLabelValues("1.4") - countLockRetriesV16 = countLockRetries.WithLabelValues("1.6") -) - -func init() { - prometheus.MustRegister( - summaryLockAcquisitionTime, - countLockRetries, - ) -} - -func NewSharedLock(lockFilePath string, lockTimeout, lockProbeInterval time.Duration) *SharedLock { - return &SharedLock{ - lockFilePath: lockFilePath, - lockTimeout: lockTimeout, - lockProbeInterval: lockProbeInterval, - GrabIptablesLocks: GrabIptablesLocks, - } -} - -// SharedLock allows for multiple goroutines to share the iptables lock without blocking on each -// other. That is safe because each of our goroutines is accessing a different iptables table, so -// they do not conflict. -type SharedLock struct { - lock sync.Mutex - referenceCount int - - iptablesLockHandle io.Closer - - lockFilePath string - lockTimeout time.Duration - lockProbeInterval time.Duration - - GrabIptablesLocks func(lockFilePath, socketName string, timeout, probeInterval time.Duration) (io.Closer, error) -} - -func (l *SharedLock) Lock() { - l.lock.Lock() - defer l.lock.Unlock() - - if l.referenceCount == 0 { - // The lock isn't currently held. Acquire it. - lockHandle, err := l.GrabIptablesLocks( - l.lockFilePath, - "@xtables", - l.lockTimeout, - l.lockProbeInterval, - ) - if err != nil { - // We give the lock plenty of time so err on the side of assuming a - // programming bug. - log.WithError(err).Panic("Failed to acquire iptables lock") - } - l.iptablesLockHandle = lockHandle - } - l.referenceCount++ -} - -func (l *SharedLock) Unlock() { - l.lock.Lock() - defer l.lock.Unlock() - - l.referenceCount-- - if l.referenceCount < 0 { - log.Panic("Unmatched Unlock()") - } - if l.referenceCount == 0 { - log.Debug("Releasing iptables lock.") - err := l.iptablesLockHandle.Close() - if err != nil { - // We haven't done anything with the file or socket so we shouldn't be - // able to hit any "deferred flush" type errors from the close. Panic - // since we're not sure what's going on. - log.WithError(err).Panic("Error while closing iptables lock.") - } - l.iptablesLockHandle = nil - } -} - -type Locker struct { - Lock16 io.Closer - Lock14 io.Closer -} - -func (l *Locker) Close() error { - var err error - if l.Lock16 != nil { - err = l.Lock16.Close() - if err != nil { - log.WithError(err).Error("Error while closing lock file.") - } - } - if l.Lock14 != nil { - err14 := l.Lock14.Close() - if err14 != nil { - log.WithError(err14).Error("Error while closing lock socket.") - } - if err14 != nil && err == nil { - err = err14 - } - } - return err -} - -var ( - Err14LockTimeout = errors.New("timed out waiting for iptables 1.4 lock") - Err16LockTimeout = errors.New("timed out waiting for iptables 1.6 lock") -) - -func GrabIptablesLocks(lockFilePath, socketName string, timeout, probeInterval time.Duration) (io.Closer, error) { - var err error - var success bool - - l := &Locker{} - defer func(l *Locker) { - // Clean up immediately on failure - if !success { - l.Close() - } - }(l) - - // Grab both 1.6.x and 1.4.x-style locks; we don't know what the - // iptables-restore version is if it doesn't support --wait, so we - // can't assume which lock method it'll use. - - // Roughly duplicate iptables 1.6.x xtables_lock() function. - f, err := os.OpenFile(lockFilePath, os.O_CREATE, 0600) - l.Lock16 = f - if err != nil { - return nil, fmt.Errorf("failed to open iptables lock %s: %v", lockFilePath, err) - } - - startTime := time.Now() - for { - if err := grabIptablesFileLock(f); err == nil { - break - } - if time.Since(startTime) > timeout { - return nil, Err16LockTimeout - } - time.Sleep(probeInterval) - countLockRetriesV16.Inc() - } - - startTime14 := time.Now() - for { - l.Lock14, err = net.ListenUnix("unix", &net.UnixAddr{Name: socketName, Net: "unix"}) - if err == nil { - break - } - if time.Since(startTime14) > timeout { - return nil, Err14LockTimeout - } - time.Sleep(probeInterval) - countLockRetriesV14.Inc() - } - - summaryLockAcquisitionTime.Observe(time.Since(startTime).Seconds()) - - success = true - return l, nil -} - -func grabIptablesFileLock(f *os.File) error { - return unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB) -} diff --git a/felix/iptables/lock_test.go b/felix/iptables/lock_test.go deleted file mode 100644 index f18c26c7ee9..00000000000 --- a/felix/iptables/lock_test.go +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. -// -// 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 iptables_test - -import ( - "io" - "os" - "time" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - - . "github.com/projectcalico/calico/felix/iptables" -) - -var _ = Describe("SharedLock", func() { - var lock *SharedLock - var mockIptablesLock *mockLock - var mockErr error - - mockGrabLock := func(lockFilePath, socketName string, timeout, probeInterval time.Duration) (io.Closer, error) { - return mockIptablesLock, mockErr - } - - BeforeEach(func() { - mockErr = nil - mockIptablesLock = &mockLock{} - lock = NewSharedLock("/foo/bar.lock", time.Second, time.Millisecond) - lock.GrabIptablesLocks = mockGrabLock - }) - - It("should close the lock after final unlock call", func() { - lock.Lock() - Expect(mockIptablesLock.Closed).To(BeFalse()) - lock.Unlock() - Expect(mockIptablesLock.Closed).To(BeTrue()) - }) - It("should allow multiple holders", func() { - lock.Lock() - lock.Lock() - Expect(mockIptablesLock.Closed).To(BeFalse()) - lock.Unlock() - Expect(mockIptablesLock.Closed).To(BeFalse()) - lock.Unlock() - Expect(mockIptablesLock.Closed).To(BeTrue()) - }) - It("should panic on misuse", func() { - Expect(lock.Unlock).To(Panic()) - }) - It("should panic on failure to acquire", func() { - mockErr = Err14LockTimeout - Expect(lock.Lock).To(Panic()) - }) - It("should panic on failure to close", func() { - lock.Lock() - mockIptablesLock.Err = Err14LockTimeout - Expect(lock.Unlock).To(Panic()) - }) -}) - -type mockLock struct { - Closed bool - Err error -} - -func (l *mockLock) Close() error { - Expect(l.Closed).To(BeFalse()) - l.Closed = true - return l.Err -} - -var _ = Describe("GrabIptablesLocks FV", func() { - var fileName string - - BeforeEach(func() { - f, err := os.CreateTemp("", "iptlocktest") - Expect(err).NotTo(HaveOccurred()) - fileName = f.Name() - f.Close() - }) - AfterEach(func() { - os.Remove(fileName) - }) - - It("should block concurrent invocations", func() { - l, err := GrabIptablesLocks(fileName, "@dummytables", 1*time.Second, 50*time.Millisecond) - Expect(err).NotTo(HaveOccurred()) - defer l.Close() - - l2, err := GrabIptablesLocks(fileName, "@dummytables", 100*time.Millisecond, 10*time.Millisecond) - Expect(err).To(Equal(Err16LockTimeout)) - Expect(l2).To(BeNil()) - }) - It("should allow access after being released", func() { - l, err := GrabIptablesLocks(fileName, "@dummytables", 1*time.Second, 50*time.Millisecond) - Expect(err).NotTo(HaveOccurred()) - l.Close() - - l2, err := GrabIptablesLocks(fileName, "@dummytables", 1*time.Second, 50*time.Millisecond) - Expect(err).NotTo(HaveOccurred()) - l2.Close() - }) - It("should block concurrent invocations using only iptables 1.4 version of lock", func() { - l, err := GrabIptablesLocks(fileName, "@dummytables", 1*time.Second, 50*time.Millisecond) - // Sneakily remove the lockfile after it's been locked so that we fall through to - // the v1.4 lock. - os.Remove(fileName) - Expect(err).NotTo(HaveOccurred()) - defer l.Close() - - l2, err := GrabIptablesLocks(fileName, "@dummytables", 100*time.Millisecond, 10*time.Millisecond) - Expect(err).To(Equal(Err14LockTimeout)) - Expect(l2).To(BeNil()) - }) - It("should allow access after being released using only iptables 1.4 version of lock", func() { - l, err := GrabIptablesLocks(fileName, "@dummytables", 1*time.Second, 50*time.Millisecond) - // Sneakily remove the lockfile after it's been locked so that we fall through to - // the v1.4 lock. - os.Remove(fileName) - Expect(err).NotTo(HaveOccurred()) - l.Close() - - l2, err := GrabIptablesLocks(fileName, "@dummytables", 1*time.Second, 50*time.Millisecond) - Expect(err).NotTo(HaveOccurred()) - l2.Close() - }) -}) - -var _ = Describe("locker", func() { - var l *Locker - var lock14 mockCloser - var lock16 mockCloser - - BeforeEach(func() { - lock14 = mockCloser{} - lock16 = mockCloser{} - l = &Locker{ - Lock14: &lock14, - Lock16: &lock16, - } - }) - - It("should return nil with no err", func() { - Expect(l.Close()).NotTo(HaveOccurred()) - }) - It("should return lock16 err", func() { - lock16.Err = Err16LockTimeout - Expect(l.Close()).To(Equal(Err16LockTimeout)) - }) - It("should return lock14 err", func() { - lock14.Err = Err14LockTimeout - Expect(l.Close()).To(Equal(Err14LockTimeout)) - }) -}) - -type mockCloser struct { - Err error -} - -func (c *mockCloser) Close() error { - return c.Err -} diff --git a/felix/iptables/lock_windows.go b/felix/iptables/lock_windows.go deleted file mode 100644 index 3d7dc3c3c73..00000000000 --- a/felix/iptables/lock_windows.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2018-2025 Tigera, Inc. All rights reserved. - -package iptables - -import "sync" - -func NewSharedLock() { - panic("iptables lock is not implemented for windows platform") -} - -// SharedLock allows for multiple goroutines to share the iptables lock without blocking on each -// other. That is safe because each of our goroutines is accessing a different iptables table, so -// they do not conflict. -// This is just a stub placeholder for windows -type SharedLock struct { - lock sync.Mutex -} - -func (l *SharedLock) Lock() { - l.lock.Lock() - defer l.lock.Unlock() - -} - -func (l *SharedLock) Unlock() { - l.lock.Lock() - defer l.lock.Unlock() - -} diff --git a/felix/iptables/match_builder.go b/felix/iptables/match_builder.go index a57011f2a7f..5540521bcff 100644 --- a/felix/iptables/match_builder.go +++ b/felix/iptables/match_builder.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2016-2026 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -325,6 +325,14 @@ func (m matchCriteria) NotICMPV6TypeAndCode(t, c uint8) generictables.MatchCrite return append(m, fmt.Sprintf("-m icmp6 ! --icmpv6-type %d/%d", t, c)) } +// The expected rate must be an integer (0~9999) with /second, /minute, /hour, or /day suffix. +func (m matchCriteria) Limit(rate string, burst uint16) generictables.MatchCriteria { + if burst == 0 { + return append(m, fmt.Sprintf("-m limit --limit %s", rate)) + } + return append(m, fmt.Sprintf("-m limit --limit %s --limit-burst %d", rate, burst)) +} + func (m matchCriteria) InInterfaceVMAP(mapname string) generictables.MatchCriteria { log.Panic("InInterfaceVMAP not supported in iptables") return m diff --git a/felix/iptables/match_builder_test.go b/felix/iptables/match_builder_test.go index 65da1980b4b..c7bee4fceaf 100644 --- a/felix/iptables/match_builder_test.go +++ b/felix/iptables/match_builder_test.go @@ -15,8 +15,7 @@ package iptables_test import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/generictables" @@ -112,4 +111,7 @@ var _ = DescribeTable("MatchBuilder", // IPVS. Entry("IPVSConnection", Match().IPVSConnection(), "-m ipvs --ipvs"), Entry("NotIPVSConnection", Match().NotIPVSConnection(), "-m ipvs ! --ipvs"), + // Limits. + Entry("Limit with rate", Match().Limit("30/second", 0), "-m limit --limit 30/second"), + Entry("Limit with rate and burst", Match().Limit("40/day", 5), "-m limit --limit 40/day --limit-burst 5"), ) diff --git a/felix/iptables/restore_buffer.go b/felix/iptables/restore_buffer.go index c1c432144b9..d26d26f4611 100644 --- a/felix/iptables/restore_buffer.go +++ b/felix/iptables/restore_buffer.go @@ -79,7 +79,7 @@ func (b *RestoreInputBuilder) EndTransaction() { } // writeFormattedLine writes a line to the internal buffer, appending a new line. -func (b *RestoreInputBuilder) writeFormattedLine(format string, args ...interface{}) { +func (b *RestoreInputBuilder) writeFormattedLine(format string, args ...any) { _, err := fmt.Fprintf(&b.buf, format, args...) if err != nil { log.WithError(err).Panic("Failed to write to in-memory buffer") diff --git a/felix/iptables/rules_test.go b/felix/iptables/rules_test.go index 5180458cc02..98d09b277e5 100644 --- a/felix/iptables/rules_test.go +++ b/felix/iptables/rules_test.go @@ -19,9 +19,8 @@ import ( "errors" "io" "strings" - "sync" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/environment" @@ -85,7 +84,6 @@ var _ = Describe("Hash extraction tests", func() { "filter", 4, "cali:", - &sync.Mutex{}, fd, TableOptions{ HistoricChainPrefixes: []string{"felix-", "cali"}, diff --git a/felix/iptables/table.go b/felix/iptables/table.go index 930a4f9f395..32788763c52 100644 --- a/felix/iptables/table.go +++ b/felix/iptables/table.go @@ -24,7 +24,6 @@ import ( "reflect" "regexp" "strings" - "sync" "time" "github.com/prometheus/client_golang/prometheus" @@ -261,9 +260,6 @@ type Table struct { peakIptablesSaveTime time.Duration peakIptablesRestoreTime time.Duration - // calicoXtablesLock, if enabled, our implementation of the xtables lock. - calicoXtablesLock sync.Locker - // lockTimeout is the timeout used for iptables-restore's native xtables lock implementation. lockTimeout time.Duration // lockTimeout is the lock probe interval used for iptables-restore's native xtables lock @@ -302,8 +298,6 @@ type TableOptions struct { RefreshInterval time.Duration PostWriteInterval time.Duration - // LockTimeout is the timeout to use for iptables-restore's native xtables lock. - LockTimeout time.Duration // LockProbeInterval is the probe interval to use for iptables-restore's native xtables lock. LockProbeInterval time.Duration @@ -325,7 +319,6 @@ func NewTable( name string, ipVersion uint8, hashPrefix string, - iptablesWriteLock sync.Locker, featureDetector environment.FeatureDetectorIface, options TableOptions, ) *Table { @@ -436,9 +429,6 @@ func NewTable( refreshInterval: options.RefreshInterval, - calicoXtablesLock: iptablesWriteLock, - - lockTimeout: options.LockTimeout, lockProbeInterval: options.LockProbeInterval, newCmd: newCmd, @@ -1162,7 +1152,7 @@ func (t *Table) applyUpdates() error { // Make a pass over the dirty chains and generate a forward reference for any that we're about to update. // Writing a forward reference ensures that the chain exists and that it is empty. - t.dirtyChains.Iter(func(chainName string) error { + for chainName := range t.dirtyChains.All() { chainNeedsToBeFlushed := false if t.nftablesMode { // iptables-nft-restore 0 && reflect.DeepEqual(currentHashes, previousHashes) { // Chain is already correct, skip it. log.Debug("Chain already correct") - return set.RemoveItem + t.dirtyChains.Discard(chainName) + continue } chainNeedsToBeFlushed = true } else if _, present := t.desiredStateOfChain(chainName); !present { @@ -1191,12 +1182,11 @@ func (t *Table) applyUpdates() error { if chainNeedsToBeFlushed { buf.WriteForwardReference(chainName) } - return nil - }) + } // Make a second pass over the dirty chains. This time, we write out the rule changes. newHashes := map[string][]string{} - t.dirtyChains.Iter(func(chainName string) error { + for chainName := range t.dirtyChains.All() { if chain, ok := t.desiredStateOfChain(chainName); ok { // Chain update or creation. Scan the chain against its previous hashes // and replace/append/delete as appropriate. @@ -1230,8 +1220,7 @@ func (t *Table) applyUpdates() error { buf.WriteLine(line) } } - return nil // Delay clearing the set until we've programmed iptables. - }) + } // Make a copy of our full rules map and keep track of all changes made while processing dirtyInsertAppend. // When we've successfully updated iptables, we'll update our cache of chainToFullRules with this map. @@ -1244,7 +1233,7 @@ func (t *Table) applyUpdates() error { // Now calculate iptables updates for our inserted and appended rules, which are used to hook top-level chains. var deleteRenderingErr error var line string - t.dirtyInsertAppend.Iter(func(chainName string) error { + for chainName := range t.dirtyInsertAppend.All() { previousHashes := t.chainToDataplaneHashes[chainName] newRules := newChainToFullRules[chainName] @@ -1254,16 +1243,16 @@ func (t *Table) applyUpdates() error { if reflect.DeepEqual(newChainHashes, previousHashes) { // Chain is in sync, skip to next one. - return nil + continue } // For simplicity, if we've discovered that we're out-of-sync, remove all our // rules from this chain, then re-insert/re-append them below. - for i := 0; i < len(previousHashes); i++ { + for i := range previousHashes { if previousHashes[i] != "" { line, deleteRenderingErr = t.renderDeleteByValueLine(chainName, i) if deleteRenderingErr != nil { - return set.StopIteration + break } buf.WriteLine(line) } @@ -1320,9 +1309,7 @@ func (t *Table) applyUpdates() error { newHashes[chainName] = newChainHashes newChainToFullRules[chainName] = newRules - - return nil // Delay clearing the set until we've programmed iptables. - }) + } // If rendering a delete by line number reached an unexpected state, error out so applyUpdates() can be retried. if deleteRenderingErr != nil { return deleteRenderingErr @@ -1337,13 +1324,12 @@ func (t *Table) applyUpdates() error { buf.EndTransaction() buf.StartTransaction(t.name) - t.dirtyChains.Iter(func(chainName string) error { + for chainName := range t.dirtyChains.All() { if _, ok := t.desiredStateOfChain(chainName); !ok { // Chain deletion buf.WriteForwardReference(chainName) } - return nil // Delay clearing the set until we've programmed iptables. - }) + } } // Do deletions at the end. This ensures that we don't try to delete any chains that @@ -1351,14 +1337,13 @@ func (t *Table) applyUpdates() error { // above). Note: if a chain is being deleted at the same time as a chain that it refers to // then we'll issue a create+flush instruction in the very first pass, which will sever the // references. - t.dirtyChains.Iter(func(chainName string) error { + for chainName := range t.dirtyChains.All() { if _, ok := t.desiredStateOfChain(chainName); !ok { // Chain deletion buf.WriteLine(fmt.Sprintf("--delete-chain %s", chainName)) newHashes[chainName] = nil } - return nil // Delay clearing the set until we've programmed iptables. - }) + } buf.EndTransaction() @@ -1462,11 +1447,7 @@ func (t *Table) execIptablesRestore(buf *RestoreInputBuilder) error { cmd.SetStdout(&outputBuf) cmd.SetStderr(&errBuf) countNumRestoreCalls.Inc() - // Note: calicoXtablesLock will be a dummy lock if our xtables lock is disabled (i.e. if iptables-restore - // supports the xtables lock itself, or if our implementation is disabled by config. - t.calicoXtablesLock.Lock() err := cmd.Run() - t.calicoXtablesLock.Unlock() if err != nil { // To log out the input, we must convert to string here since, after we return, the buffer can be re-used // (and the logger may convert to string on a background thread). diff --git a/felix/iptables/table_test.go b/felix/iptables/table_test.go index a21721b6dfb..0529b7bc344 100644 --- a/felix/iptables/table_test.go +++ b/felix/iptables/table_test.go @@ -17,7 +17,7 @@ package iptables_test import ( "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" @@ -42,7 +42,6 @@ var _ = Describe("Table with an empty dataplane (legacy)", func() { "INPUT": {}, "OUTPUT": {}, }, "legacy") - iptLock := &mockMutex{} featureDetector := environment.NewFeatureDetector(nil) featureDetector.NewCmd = dataplane.NewCmd featureDetector.GetKernelVersionReader = dataplane.GetKernelVersionReader @@ -50,7 +49,6 @@ var _ = Describe("Table with an empty dataplane (legacy)", func() { "filter", 4, rules.RuleHashPrefix, - iptLock, featureDetector, TableOptions{ HistoricChainPrefixes: rules.AllHistoricChainNamePrefixes, @@ -74,7 +72,6 @@ var _ = Describe("Table with an empty dataplane (legacy)", func() { func describeEmptyDataplaneTests(dataplaneMode string) { var dataplane *testutils.MockDataplane var table *Table - var iptLock *mockMutex var featureDetector *environment.FeatureDetector BeforeEach(func() { dataplane = testutils.NewMockDataplane("filter", map[string][]string{ @@ -82,7 +79,6 @@ func describeEmptyDataplaneTests(dataplaneMode string) { "INPUT": {}, "OUTPUT": {}, }, dataplaneMode) - iptLock = &mockMutex{} featureDetector = environment.NewFeatureDetector(nil) featureDetector.NewCmd = dataplane.NewCmd featureDetector.GetKernelVersionReader = dataplane.GetKernelVersionReader @@ -90,7 +86,6 @@ func describeEmptyDataplaneTests(dataplaneMode string) { "filter", 4, rules.RuleHashPrefix, - iptLock, featureDetector, TableOptions{ HistoricChainPrefixes: rules.AllHistoricChainNamePrefixes, @@ -131,8 +126,6 @@ func describeEmptyDataplaneTests(dataplaneMode string) { "iptables-save", })) } - Expect(iptLock.Held).To(BeFalse()) - Expect(iptLock.WasTaken).To(BeFalse()) }) It("should have a refresh scheduled at start-of-day", func() { @@ -177,7 +170,6 @@ func describeEmptyDataplaneTests(dataplaneMode string) { "filter", 4, rules.RuleHashPrefix, - &mockMutex{}, featureDetector, TableOptions{ HistoricChainPrefixes: rules.AllHistoricChainNamePrefixes, @@ -199,12 +191,6 @@ func describeEmptyDataplaneTests(dataplaneMode string) { }) table.Apply() }) - It("should acquire the iptables lock", func() { - Expect(iptLock.WasTaken).To(BeTrue()) - }) - It("should release the iptables lock", func() { - Expect(iptLock.Held).To(BeFalse()) - }) It("should be in the dataplane", func() { Expect(dataplane.Chains).To(Equal(map[string][]string{ "FORWARD": {`-m comment --comment "cali:hecdSCslEjdBPBPo" --jump DROP`}, @@ -1236,7 +1222,6 @@ func describePostUpdateCheckTests(enableRefresh bool, dataplaneMode string) { "filter", 4, rules.RuleHashPrefix, - &mockMutex{}, featureDetector, options, ) @@ -1493,7 +1478,6 @@ func describeDirtyDataplaneTests(appendMode bool, dataplaneMode string) { "filter", 4, rules.RuleHashPrefix, - &mockMutex{}, featureDetector, TableOptions{ HistoricChainPrefixes: rules.AllHistoricChainNamePrefixes, @@ -1692,19 +1676,19 @@ func describeDirtyDataplaneTests(appendMode bool, dataplaneMode string) { Expect(func() { table.Apply() }).To(Panic()) - }, 1) + }) It("it should do exponential backoff", func() { Expect(func() { table.Apply() }).To(Panic()) Expect(dataplane.CumulativeSleep).To(Equal((100 + 200 + 400) * time.Millisecond)) - }, 1) + }) It("it should retry 3 times", func() { Expect(func() { table.Apply() }).To(Panic()) Expect(dataplane.Cmds).To(HaveLen(5)) - }, 1) + }) }) It("shouldn't touch already-correct chain", func() { @@ -1747,14 +1731,14 @@ func describeDirtyDataplaneTests(appendMode bool, dataplaneMode string) { Expect(func() { table.Apply() }).To(Panic()) - }, 1) + }) It("it should do exponential backoff", func() { Expect(func() { table.Apply() }).To(Panic()) Expect(dataplane.CumulativeSleep).To(Equal( (1 + 2 + 4 + 8 + 16 + 32 + 64 + 128 + 256 + 512) * time.Millisecond)) - }, 1) + }) }) Describe("with a simulated clobber of chains before first write", func() { @@ -1903,13 +1887,11 @@ var _ = Describe("Table with inserts and a non-Calico chain (nft)", func() { func describeInsertAndNonCalicoChainTests(dataplaneMode string) { var dataplane *testutils.MockDataplane var table *Table - var iptLock *mockMutex BeforeEach(func() { dataplane = testutils.NewMockDataplane("filter", map[string][]string{ "FORWARD": {}, "non-calico": {"-m comment \"foo\""}, }, dataplaneMode) - iptLock = &mockMutex{} featureDetector := environment.NewFeatureDetector(nil) featureDetector.NewCmd = dataplane.NewCmd featureDetector.GetKernelVersionReader = dataplane.GetKernelVersionReader @@ -1917,7 +1899,6 @@ func describeInsertAndNonCalicoChainTests(dataplaneMode string) { "filter", 6, rules.RuleHashPrefix, - iptLock, featureDetector, TableOptions{ HistoricChainPrefixes: rules.AllHistoricChainNamePrefixes, @@ -1948,8 +1929,6 @@ func describeInsertAndNonCalicoChainTests(dataplaneMode string) { "FORWARD": {"-m comment --comment \"cali:hecdSCslEjdBPBPo\" --jump DROP"}, } dataplane.ResetCmds() - iptLock.WasTaken = false - iptLock.Held = false table.Apply() }) @@ -1961,9 +1940,6 @@ func describeInsertAndNonCalicoChainTests(dataplaneMode string) { It("should make no changes to the dataplane", func() { Expect(dataplane.CmdNames).To(BeEmpty()) }) - It("should not take the lock", func() { - Expect(iptLock.WasTaken).To(BeFalse()) - }) }) } @@ -1978,13 +1954,11 @@ var _ = Describe("Insert early rules (nft)", func() { func describeInsertEarlyRules(dataplaneMode string) { var dataplane *testutils.MockDataplane var table *Table - var iptLock *mockMutex var featureDetector *environment.FeatureDetector BeforeEach(func() { dataplane = testutils.NewMockDataplane("filter", map[string][]string{ "FORWARD": {"-m comment --comment \"some rule\""}, }, dataplaneMode) - iptLock = &mockMutex{} featureDetector = environment.NewFeatureDetector(nil) featureDetector.NewCmd = dataplane.NewCmd featureDetector.GetKernelVersionReader = dataplane.GetKernelVersionReader @@ -1992,7 +1966,6 @@ func describeInsertEarlyRules(dataplaneMode string) { "filter", 4, rules.RuleHashPrefix, - iptLock, featureDetector, TableOptions{ HistoricChainPrefixes: rules.AllHistoricChainNamePrefixes, @@ -2052,23 +2025,3 @@ func describeInsertEarlyRules(dataplaneMode string) { Expect(res).To(HaveLen(2)) }) } - -type mockMutex struct { - Held bool - WasTaken bool -} - -func (m *mockMutex) Lock() { - if m.Held { - Fail("Mutex already held") - } - m.Held = true - m.WasTaken = true -} - -func (m *mockMutex) Unlock() { - if !m.Held { - Fail("Mutex not held") - } - m.Held = false -} diff --git a/felix/iptables/testutils/utils.go b/felix/iptables/testutils/utils.go index 5a8c07b1530..9b639dfec9c 100644 --- a/felix/iptables/testutils/utils.go +++ b/felix/iptables/testutils/utils.go @@ -24,7 +24,7 @@ import ( "strings" "time" - "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/v2" //nolint:staticcheck // Ignore ST1001: should not use dot imports . "github.com/onsi/gomega" diff --git a/felix/iptree/iptree_test.go b/felix/iptree/iptree_test.go index 8b10a8dc955..fbe7bea20af 100644 --- a/felix/iptree/iptree_test.go +++ b/felix/iptree/iptree_test.go @@ -26,7 +26,6 @@ func TestMainline(t *testing.T) { "10.0.0.16/28", "10.0.0.8/29", "10.0.0.4/30", "10.0.0.3", "10.0.0.0"}, []string{"10.0.0.0/24"}}, } { - test := test t.Run(test.Name, func(t *testing.T) { RegisterTestingT(t) @@ -34,7 +33,7 @@ func TestMainline(t *testing.T) { for _, c := range test.CIDRsToAdd { tree.AddCIDRString(c) } - var cidrsAsIface []interface{} + var cidrsAsIface []any for _, c := range test.CIDRsToExpect { cidrsAsIface = append(cidrsAsIface, c) } @@ -55,7 +54,6 @@ func TestIntersection(t *testing.T) { {"zero-1&128-1", []string{"0.0.0.0/1"}, []string{"128.0.0.0/1"}, []string{}}, {"128-1&zero-0", []string{"128.0.0.0/1"}, []string{"0.0.0.0/0"}, []string{"128.0.0.0/1"}}, } { - test := test t.Run(test.Name, func(t *testing.T) { RegisterTestingT(t) @@ -70,7 +68,7 @@ func TestIntersection(t *testing.T) { intersection := Intersect(treeA, treeB) - var cidrsAsIface []interface{} + var cidrsAsIface []any for _, c := range test.CIDRsToExpect { cidrsAsIface = append(cidrsAsIface, c) } @@ -94,7 +92,6 @@ func TestSubtraction(t *testing.T) { []string{"10.0.0.0/25", "10.0.1.0/25"}, []string{"10.0.0.0/23"}, []string{}}, } { - test := test t.Run(test.Name, func(t *testing.T) { RegisterTestingT(t) @@ -109,7 +106,7 @@ func TestSubtraction(t *testing.T) { intersection := Subtract(treeA, treeB) - var cidrsAsIface []interface{} + var cidrsAsIface []any for _, c := range test.CIDRsToExpect { cidrsAsIface = append(cidrsAsIface, c) } diff --git a/felix/iputils/intersect.go b/felix/iputils/intersect.go index 2bc4a71cacf..4c597e62990 100644 --- a/felix/iputils/intersect.go +++ b/felix/iputils/intersect.go @@ -52,10 +52,10 @@ func IntersectCIDRs(aStrs []string, bStrs []string) (out []string) { } } - intersection.Iter(func(cidr ip.CIDR) error { + for cidr := range intersection.All() { out = append(out, cidr.String()) - return set.RemoveItem - }) + intersection.Discard(cidr) + } // Sort the output for determinism both in testing and in rule generation. sort.Strings(out) diff --git a/felix/iputils/intersect_test.go b/felix/iputils/intersect_test.go index 7941ab59496..8de5c3f52ef 100644 --- a/felix/iputils/intersect_test.go +++ b/felix/iputils/intersect_test.go @@ -45,7 +45,6 @@ func TestIntersectCIDRs(t *testing.T) { []string{"10.0.1.1/32", "10.0.2.1/32"}, }, } { - test := test sort.Strings(test.as) sort.Strings(test.bs) sort.Strings(test.exp) diff --git a/felix/jitter/jitter_suite_test.go b/felix/jitter/jitter_suite_test.go index 57bb7915d2e..56908526321 100644 --- a/felix/jitter/jitter_suite_test.go +++ b/felix/jitter/jitter_suite_test.go @@ -17,9 +17,8 @@ package jitter import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestJitter(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/jitter_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Jitter Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_jitter_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/jitter", suiteConfig, reporterConfig) } diff --git a/felix/jitter/jittered_ticker_test.go b/felix/jitter/jittered_ticker_test.go index 355a4742de0..04443aa7ddd 100644 --- a/felix/jitter/jittered_ticker_test.go +++ b/felix/jitter/jittered_ticker_test.go @@ -18,7 +18,7 @@ import ( "fmt" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/sirupsen/logrus" ) @@ -38,12 +38,12 @@ var _ = Describe("Real 20ms + 10ms Ticker", func() { now := time.Now() duration := now.Sub(startTime) Expect(duration).To(BeNumerically(">=", 20*time.Millisecond)) - }, 1) + }) It("should produce longer and shorter ticks", func() { lastTime := startTime foundLT5 := false foundGT5 := false - for i := 0; i < 40; i++ { + for range 40 { <-ticker.Channel() now := time.Now() duration := time.Since(lastTime) @@ -60,7 +60,7 @@ var _ = Describe("Real 20ms + 10ms Ticker", func() { } Expect(foundLT5).To(BeTrue()) Expect(foundGT5).To(BeTrue()) - }, 1) + }) }) var _ = Describe("Ticker.Stop()", func() { @@ -78,7 +78,7 @@ var _ = Describe("Delay calculation", func() { ticker = NewTicker(20*time.Millisecond, 10*time.Millisecond) ticker.Stop() }) - for i := 0; i < 10; i++ { + for i := range 10 { It(fmt.Sprintf("should tick before max delay, trial: %v", i), func() { duration := ticker.calculateDelay() Expect(duration).To(BeNumerically("<=", 30*time.Millisecond)) @@ -92,7 +92,7 @@ var _ = Describe("Delay calculation", func() { It("should produce longer and shorter ticks", func() { foundLT5 := false foundGT5 := false - for i := 0; i < 40; i++ { + for range 40 { duration := ticker.calculateDelay() logrus.WithField("duration", duration).Debug("Tick") if duration < 25*time.Millisecond { @@ -106,7 +106,7 @@ var _ = Describe("Delay calculation", func() { } Expect(foundLT5).To(BeTrue()) Expect(foundGT5).To(BeTrue()) - }, 1) + }) }) var _ = Describe("Ticker constructor", func() { diff --git a/felix/k8sfv/calc_graph.go b/felix/k8sfv/calc_graph.go index 69b680e93e1..888807b3b11 100644 --- a/felix/k8sfv/calc_graph.go +++ b/felix/k8sfv/calc_graph.go @@ -49,7 +49,6 @@ func rotateLabels(clientset *kubernetes.Clientset, nsPrefix string) error { waiter := sync.WaitGroup{} waiter.Add(len(nsMaturity)) for nsName := range nsMaturity { - nsName := nsName go func() { for _, role := range []string{"1", "2", "3", "4", "5"} { for _, instance := range []string{"1", "2", "3", "4", "5"} { diff --git a/felix/k8sfv/calc_graph_test.go b/felix/k8sfv/calc_graph_test.go index 01eba740114..b49c474e323 100644 --- a/felix/k8sfv/calc_graph_test.go +++ b/felix/k8sfv/calc_graph_test.go @@ -17,7 +17,7 @@ package main import ( "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" "k8s.io/client-go/kubernetes" diff --git a/felix/k8sfv/k8sfv.go b/felix/k8sfv/k8sfv.go index ecac91f55fd..9cd67a7108c 100644 --- a/felix/k8sfv/k8sfv.go +++ b/felix/k8sfv/k8sfv.go @@ -17,10 +17,11 @@ package main import ( "context" "fmt" + "os" "strings" "time" - "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/push" @@ -89,7 +90,7 @@ var ( var _ = ginkgo.JustBeforeEach(func() { log.Info(">>> JustBeforeEach <<<") - testName = ginkgo.CurrentGinkgoTestDescription().FullTestText + testName = ginkgo.CurrentSpecReport().FullText() }) var _ = ginkgo.AfterEach(func() { @@ -103,7 +104,7 @@ var _ = ginkgo.AfterEach(func() { // Store the result of each test in a Prometheus metric. result := float64(1) - if ginkgo.CurrentGinkgoTestDescription().Failed { + if ginkgo.CurrentSpecReport().Failed() { result = 0 } gaugeVecTestResult.WithLabelValues(testName, codeLevel).Set(result) @@ -133,7 +134,6 @@ var _ = ginkgo.AfterSuite(func() { }) func initialize(k8sServerEndpoint string) (clientset *kubernetes.Clientset) { - config, err := clientcmd.NewNonInteractiveClientConfig(*api.NewConfig(), "", &clientcmd.ConfigOverrides{ @@ -161,6 +161,7 @@ func initialize(k8sServerEndpoint string) (clientset *kubernetes.Clientset) { KubeConfig: apiconfig.KubeConfig{ K8sAPIEndpoint: k8sServerEndpoint, K8sInsecureSkipTLSVerify: true, + CalicoAPIGroup: os.Getenv("CALICO_API_GROUP"), }, }, }) @@ -185,14 +186,13 @@ func initialize(k8sServerEndpoint string) (clientset *kubernetes.Clientset) { } func create1000Pods(clientset *kubernetes.Clientset, nsPrefix string) error { - d = NewDeployment(clientset, 49, true) nsName := nsPrefix + "test" // Create 1000 pods. createNamespace(clientset, nsName, nil) log.Info("Creating pods:") - for i := 0; i < 1000; i++ { + for range 1000 { createPod(clientset, d, nsName, podSpec{}) } log.Info("Done") diff --git a/felix/k8sfv/k8sfv_suite_test.go b/felix/k8sfv/k8sfv_suite_test.go index 9d4ea09ce86..eae5a1d783d 100644 --- a/felix/k8sfv/k8sfv_suite_test.go +++ b/felix/k8sfv/k8sfv_suite_test.go @@ -19,9 +19,8 @@ import ( "os" "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" log "github.com/sirupsen/logrus" "github.com/projectcalico/calico/felix/logutils" @@ -42,8 +41,15 @@ func init() { } func TestMain(t *testing.T) { - RegisterFailHandler(Fail) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() // The run-test script runs this file from k8sfv/output. - junitReporter := reporters.NewJUnitReporter("../../report/k8sfv_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Felix/KDD FV tests", []Reporter{junitReporter}) + nameTypha := "" + fileTypha := "" + if os.Getenv("USE_TYPHA") == "true" { + nameTypha = " (typha)" + fileTypha = "typha_" + } + reporterConfig.JUnitReport = "../../report/felix_k8sfv_" + fileTypha + "suite.xml" + ginkgo.RunSpecs(t, "k8sfv tests"+nameTypha, suiteConfig, reporterConfig) } diff --git a/felix/k8sfv/leastsquares_test.go b/felix/k8sfv/leastsquares_test.go index c2694ad8511..e4e1c53ad12 100644 --- a/felix/k8sfv/leastsquares_test.go +++ b/felix/k8sfv/leastsquares_test.go @@ -15,7 +15,7 @@ package main import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/k8sfv/leastsquares" diff --git a/felix/k8sfv/pod_test.go b/felix/k8sfv/pod_test.go index 3237b59f051..bd8e378898b 100644 --- a/felix/k8sfv/pod_test.go +++ b/felix/k8sfv/pod_test.go @@ -18,7 +18,7 @@ import ( "context" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/felix/k8sfv/run-test b/felix/k8sfv/run-test index 54905a68e19..be75bf8d213 100755 --- a/felix/k8sfv/run-test +++ b/felix/k8sfv/run-test @@ -254,10 +254,10 @@ felix_ip=$(get_container_ip ${prefix}-felix) felix_hostname=$(get_container_hostname ${prefix}-felix) # Run the test suite. -run="cd /testcode/k8sfv/output && /testcode/bin/k8sfv.test -ginkgo.v -k8s-api-endpoint ${k8s_api_endpoint} -felix-ip ${felix_ip} -felix-hostname ${felix_hostname} -prometheus-push-url \"${PROMPG_URL}\" -code-level \"${CODE_LEVEL}\"" +run="cd /testcode/k8sfv/output && /testcode/bin/k8sfv.test --ginkgo.v -k8s-api-endpoint ${k8s_api_endpoint} -felix-ip ${felix_ip} -felix-hostname ${felix_hostname} -prometheus-push-url \"${PROMPG_URL}\" -code-level \"${CODE_LEVEL}\"" if test -n "${GINKGO_FOCUS}"; then - docker exec ${prefix}-felix /bin/sh -c \ - "${run} -ginkgo.focus \"${GINKGO_FOCUS}\"" + docker exec -e USE_TYPHA=${USE_TYPHA} ${prefix}-felix /bin/sh -c \ + "${run} --ginkgo.focus=\"${GINKGO_FOCUS}\"" elif ${JUST_A_MINUTE}; then tmp=$(mktemp) # Run all the tests that are _not_ marked as "[slow]", and fail @@ -265,8 +265,8 @@ elif ${JUST_A_MINUTE}; then # test should be marked as "[slow]" if it normally takes longer # than 1 minute - so this allows a buffer of 1 extra minute to # allow for executor slowness. - docker exec ${prefix}-felix /bin/sh -c \ - "${run} -ginkgo.skip \"\\[slow\\]\" -ginkgo.slowSpecThreshold 120" | tee $tmp + docker exec -e USE_TYPHA=${USE_TYPHA} ${prefix}-felix /bin/sh -c \ + "${run} --ginkgo.skip=\"\\[slow\\]\" --ginkgo.slow-spec-threshold=120s" | tee $tmp # Fail if the docker command returned failure cmdStatus=${PIPESTATUS[0]} if [ $cmdStatus -ne 0 ]; then @@ -297,7 +297,7 @@ elif ${JUST_A_MINUTE}; then exit 1 fi else - docker exec ${prefix}-felix /bin/sh -c \ + docker exec -e USE_TYPHA=${USE_TYPHA} ${prefix}-felix /bin/sh -c \ "${run}" fi diff --git a/felix/k8sfv/scale.go b/felix/k8sfv/scale.go index 0bd783cb0e1..79167a2577c 100644 --- a/felix/k8sfv/scale.go +++ b/felix/k8sfv/scale.go @@ -45,7 +45,7 @@ func addEndpoints( numEndpoints int, ) { //last_time = time.Now() - for i := 0; i < numEndpoints; i++ { + for i := range numEndpoints { endpoint_id := fmt.Sprintf("endpoint_%d", i+1) ip := fmt.Sprintf("%d.%d.%d.%d", @@ -79,7 +79,7 @@ func addEndpoints( } func addNamespaces(clientset *kubernetes.Clientset, nsPrefix string) { - for ii := 0; ii < 1000; ii++ { + for ii := range 1000 { nsName := fmt.Sprintf("%s-%03d", nsPrefix, ii%1000) createNamespace(clientset, nsName, nil) } diff --git a/felix/k8sfv/scale_test.go b/felix/k8sfv/scale_test.go index 91508fa8c5a..fa8fa6286a2 100644 --- a/felix/k8sfv/scale_test.go +++ b/felix/k8sfv/scale_test.go @@ -20,7 +20,7 @@ import ( "os/exec" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" "k8s.io/client-go/kubernetes" @@ -81,7 +81,7 @@ var _ = Context("with a k8s clientset", func() { addNamespaces(clientset, nsPrefix) heapInUseMeasurements := []leastsquares.Point{} heapAllocMeasurements := []leastsquares.Point{} - for ii := 0; ii < cycles; ii++ { + for ii := range cycles { // Add 10,000 endpoints. addEndpoints(clientset, nsPrefix, d, 10000) @@ -170,7 +170,7 @@ var _ = Context("with a k8s clientset", func() { It("should handle 10 local endpoints", func() { createNamespace(clientset, nsPrefix+"test", nil) - for ii := 0; ii < 10; ii++ { + for range 10 { createPod(clientset, d, nsPrefix+"test", podSpec{}) } time.Sleep(10 * time.Second) @@ -178,7 +178,7 @@ var _ = Context("with a k8s clientset", func() { It("should handle 100 local endpoints", func() { createNamespace(clientset, nsPrefix+"test", nil) - for ii := 0; ii < 100; ii++ { + for range 100 { createPod(clientset, d, nsPrefix+"test", podSpec{}) } time.Sleep(10 * time.Second) @@ -209,8 +209,8 @@ var _ = Context("with a k8s clientset", func() { // Slow: takes about 15 minutes. It("should add and remove 1000 pods, of which about 100 on local node [slow]", func() { createNamespace(clientset, nsPrefix+"scale", nil) - for cycle := 0; cycle < 10; cycle++ { - for ii := 0; ii < 1000; ii++ { + for range 10 { + for range 1000 { createPod(clientset, d, nsPrefix+"scale", podSpec{}) time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) } diff --git a/felix/labelindex/label_inherit_index_test.go b/felix/labelindex/label_inherit_index_test.go index 8fbc641763b..eb528253f07 100644 --- a/felix/labelindex/label_inherit_index_test.go +++ b/felix/labelindex/label_inherit_index_test.go @@ -15,7 +15,7 @@ package labelindex_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/projectcalico/calico/felix/labelindex" @@ -26,16 +26,16 @@ import ( type update struct { op string - labelId interface{} - selId interface{} + labelId any + selId any } var _ = Describe("Keys", func() { It("should work as a map key", func() { - key1 := model.KeyFromDefaultPath("/calico/v1/policy/tier/tier1/policy/policy1") - key2 := model.KeyFromDefaultPath("calico/v1/policy/tier/tier1/policy/policy1") - key3 := model.KeyFromDefaultPath("/calico/v1/policy/tier/tier1/policy/policy2") - m := make(map[interface{}]bool) + key1 := model.KeyFromDefaultPath("/calico/v1/policy/NetworkPolicy/kube-system/policy1") + key2 := model.KeyFromDefaultPath("calico/v1/policy/NetworkPolicy/kube-system/policy1") + key3 := model.KeyFromDefaultPath("/calico/v1/policy/NetworkPolicy/kube-system/policy2") + m := make(map[any]bool) m[key1] = true Expect(m[key2]).To(BeTrue()) Expect(m[key3]).To(BeFalse()) @@ -52,17 +52,21 @@ var _ = Describe("Index", func() { err error ) - onMatchStart := func(selId, labelId interface{}) { + onMatchStart := func(selId, labelId any) { updates = append(updates, - update{op: "start", + update{ + op: "start", labelId: labelId, - selId: selId}) + selId: selId, + }) } - onMatchStop := func(selId, labelId interface{}) { + onMatchStop := func(selId, labelId any) { updates = append(updates, - update{op: "stop", + update{ + op: "stop", labelId: labelId, - selId: selId}) + selId: selId, + }) } BeforeEach(func() { diff --git a/felix/labelindex/label_inheritance_index.go b/felix/labelindex/label_inheritance_index.go index a7f04d19dd4..039779998b5 100644 --- a/felix/labelindex/label_inheritance_index.go +++ b/felix/labelindex/label_inheritance_index.go @@ -91,16 +91,16 @@ type parentData struct { itemIDs set.Typed[any] } -type MatchCallback func(selId, labelId interface{}) +type MatchCallback func(selId, labelId any) type InheritIndex struct { - itemDataByID map[interface{}]*itemData + itemDataByID map[any]*itemData parentDataByParentID map[string]*parentData - selectorsById map[interface{}]*selector.Selector + selectorsById map[any]*selector.Selector // Current matches. - selIdsByLabelId map[interface{}]set.Typed[any] - labelIdsBySelId map[interface{}]set.Typed[any] + selIdsByLabelId map[any]set.Typed[any] + labelIdsBySelId map[any]set.Typed[any] // Callback functions OnMatchStarted MatchCallback @@ -110,14 +110,14 @@ type InheritIndex struct { } func NewInheritIndex(onMatchStarted, onMatchStopped MatchCallback) *InheritIndex { - itemData := map[interface{}]*itemData{} + itemData := map[any]*itemData{} inheritIDx := InheritIndex{ itemDataByID: itemData, parentDataByParentID: map[string]*parentData{}, - selectorsById: map[interface{}]*selector.Selector{}, + selectorsById: map[any]*selector.Selector{}, - selIdsByLabelId: map[interface{}]set.Typed[any]{}, - labelIdsBySelId: map[interface{}]set.Typed[any]{}, + selIdsByLabelId: map[any]set.Typed[any]{}, + labelIdsBySelId: map[any]set.Typed[any]{}, // Callback functions OnMatchStarted: onMatchStarted, @@ -169,7 +169,7 @@ func (l *InheritIndex) OnUpdate(update api.Update) (_ bool) { return } -func (idx *InheritIndex) UpdateSelector(id interface{}, sel *selector.Selector) { +func (idx *InheritIndex) UpdateSelector(id any, sel *selector.Selector) { if sel == nil { log.WithField("id", id).Panic("Selector should not be nil") panic("Selector should not be nil") @@ -189,20 +189,19 @@ func (idx *InheritIndex) UpdateSelector(id interface{}, sel *selector.Selector) idx.selectorsById[id] = sel } -func (idx *InheritIndex) DeleteSelector(id interface{}) { +func (idx *InheritIndex) DeleteSelector(id any) { log.Infof("Deleting selector %v", id) matchSet := idx.labelIdsBySelId[id] if matchSet != nil { - matchSet.Iter(func(labelId interface{}) error { + for labelId := range matchSet.All() { // This modifies the set we're iterating over, but that's safe in Go. idx.deleteMatch(id, labelId) - return nil - }) + } } delete(idx.selectorsById, id) } -func (idx *InheritIndex) UpdateLabels(id interface{}, labels uniquelabels.Map, parentIDs []string) { +func (idx *InheritIndex) UpdateLabels(id any, labels uniquelabels.Map, parentIDs []string) { log.Debug("Inherit index updating labels for ", id) log.Debug("Num dirty items ", idx.dirtyItemIDs.Len(), " items") @@ -237,7 +236,7 @@ func (idx *InheritIndex) UpdateLabels(id interface{}, labels uniquelabels.Map, p log.Debug("Num ending dirty items ", idx.dirtyItemIDs.Len(), " items") } -func (idx *InheritIndex) DeleteLabels(id interface{}) { +func (idx *InheritIndex) DeleteLabels(id any) { log.Debug("Inherit index deleting labels for ", id) oldItemData := idx.itemDataByID[id] var oldParents []*parentData @@ -271,7 +270,7 @@ func (idx *InheritIndex) discardParentIfEmpty(id string) { } } -func (idx *InheritIndex) onItemParentsUpdate(id interface{}, oldParents, newParents []*parentData) { +func (idx *InheritIndex) onItemParentsUpdate(id any, oldParents, newParents []*parentData) { log.WithFields(log.Fields{ "oldParents": oldParents, "newParents": newParents, @@ -324,17 +323,16 @@ func (idx *InheritIndex) DeleteParentLabels(parentID string) { func (idx *InheritIndex) flushChildren(parentID string) { parentData := idx.parentDataByParentID[parentID] if parentData != nil && parentData.itemIDs != nil { - parentData.itemIDs.Iter(func(itemID interface{}) error { + for itemID := range parentData.itemIDs.All() { log.Debug("Marking child ", itemID, " dirty") idx.dirtyItemIDs.Add(itemID) - return nil - }) + } } idx.flushUpdates() } func (idx *InheritIndex) flushUpdates() { - idx.dirtyItemIDs.Iter(func(itemID interface{}) error { + for itemID := range idx.dirtyItemIDs.All() { log.Debugf("Flushing %#v", itemID) _, ok := idx.itemDataByID[itemID] if !ok { @@ -342,22 +340,21 @@ func (idx *InheritIndex) flushUpdates() { log.Debugf("Flushing delete of item %v", itemID) matchSet := idx.selIdsByLabelId[itemID] if matchSet != nil { - matchSet.Iter(func(selId interface{}) error { + for selId := range matchSet.All() { // This modifies the set we're iterating over, but that's safe in Go. idx.deleteMatch(selId, itemID) - return nil - }) + } } } else { // Item updated/created, re-evaluate labels. log.Debugf("Flushing update of item %v", itemID) idx.scanAllSelectors(itemID) } - return set.RemoveItem - }) + idx.dirtyItemIDs.Discard(itemID) + } } -func (idx *InheritIndex) scanAllLabels(selId interface{}, sel *selector.Selector) { +func (idx *InheritIndex) scanAllLabels(selId any, sel *selector.Selector) { log.Debugf("Scanning all (%v) labels against selector %v", len(idx.itemDataByID), selId) for labelId, labels := range idx.itemDataByID { @@ -365,7 +362,7 @@ func (idx *InheritIndex) scanAllLabels(selId interface{}, sel *selector.Selector } } -func (idx *InheritIndex) scanAllSelectors(labelId interface{}) { +func (idx *InheritIndex) scanAllSelectors(labelId any) { log.Debugf("Scanning all (%v) selectors against labels %v", len(idx.selectorsById), labelId) labels := idx.itemDataByID[labelId] @@ -375,9 +372,9 @@ func (idx *InheritIndex) scanAllSelectors(labelId interface{}) { } func (idx *InheritIndex) updateMatches( - selId interface{}, + selId any, sel *selector.Selector, - labelId interface{}, + labelId any, labels parser.Labels, ) { nowMatches := sel.EvaluateLabels(labels) @@ -388,7 +385,7 @@ func (idx *InheritIndex) updateMatches( } } -func (idx *InheritIndex) storeMatch(selId, labelId interface{}) { +func (idx *InheritIndex) storeMatch(selId, labelId any) { labelIds := idx.labelIdsBySelId[selId] if labelIds == nil { labelIds = set.New[any]() @@ -410,7 +407,7 @@ func (idx *InheritIndex) storeMatch(selId, labelId interface{}) { } } -func (idx *InheritIndex) deleteMatch(selId, labelId interface{}) { +func (idx *InheritIndex) deleteMatch(selId, labelId any) { labelIds := idx.labelIdsBySelId[selId] if labelIds == nil { return diff --git a/felix/labelindex/labelindex_suite_test.go b/felix/labelindex/labelindex_suite_test.go index eba0a822316..081acf9db89 100644 --- a/felix/labelindex/labelindex_suite_test.go +++ b/felix/labelindex/labelindex_suite_test.go @@ -17,9 +17,8 @@ package labelindex_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestLabels(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/labels_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Labels Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_labelindex_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/labelindex", suiteConfig, reporterConfig) } diff --git a/felix/labelindex/labelnamevalueindex/label_name_value_index.go b/felix/labelindex/labelnamevalueindex/label_name_value_index.go index 8ecb95ad998..7f55596eb86 100644 --- a/felix/labelindex/labelnamevalueindex/label_name_value_index.go +++ b/felix/labelindex/labelnamevalueindex/label_name_value_index.go @@ -250,12 +250,11 @@ func (s LabelNameSingleValueStrategy[ItemID]) EstimatedItemsToScan() int { func (s LabelNameSingleValueStrategy[ItemID]) Scan(f func(id ItemID) bool) { // Ideal case, we have one set to scan. - s.idSet.Iter(func(id ItemID) error { + for id := range s.idSet.All() { if !f(id) { - return set.StopIteration + break } - return nil - }) + } } func (s LabelNameSingleValueStrategy[ItemID]) Name() string { @@ -311,13 +310,12 @@ func (s LabelNameStrategy[ItemID]) EstimatedItemsToScan() int { func (s LabelNameStrategy[ItemID]) Scan(f func(id ItemID) bool) { for _, epIDs := range s.values.m { stop := false - epIDs.Iter(func(id ItemID) error { + for id := range epIDs.All() { if !f(id) { stop = true - return set.StopIteration + break } - return nil - }) + } if stop { return } diff --git a/felix/labelindex/labelrestrictionindex/label_restriction_index.go b/felix/labelindex/labelrestrictionindex/label_restriction_index.go index 0431c7dce34..bce5f3ba133 100644 --- a/felix/labelindex/labelrestrictionindex/label_restriction_index.go +++ b/felix/labelindex/labelrestrictionindex/label_restriction_index.go @@ -92,7 +92,7 @@ func (s *LabelRestrictionIndex[SelID]) AddSelector(id SelID, selector *selector. optimized := false debug := logrus.IsLevelEnabled(logrus.DebugLevel) if found { - res := lrs[labelName] + res, _ := lrs.Get(labelName) if !res.PossibleToSatisfy() { // Selector is impossible to satisfy, we don't even need to // add it to the index(!) @@ -158,7 +158,7 @@ func (s *LabelRestrictionIndex[SelID]) DeleteSelector(id SelID) { labelName, found := findMostRestrictedLabel(lrs) optimized := false if found { - res := lrs[labelName] + res, _ := lrs.Get(labelName) if !res.PossibleToSatisfy() { optimized = true } else if res.MustHaveOneOfValues != nil { @@ -187,11 +187,11 @@ func (s *LabelRestrictionIndex[SelID]) DeleteSelector(id SelID) { delete(s.selectorsByID, id) } -func findMostRestrictedLabel(lrs map[uniquestr.Handle]parser.LabelRestriction) (uniquestr.Handle, bool) { +func findMostRestrictedLabel(lrs parser.LabelRestrictions) (uniquestr.Handle, bool) { var zeroHandle uniquestr.Handle var bestLabel uniquestr.Handle var bestScore = -1 - for label, res := range lrs { + for label, res := range lrs.All() { score := scoreLabelRestriction(res) if bestLabel == zeroHandle || score > bestScore || @@ -214,10 +214,7 @@ func scoreLabelRestriction(lr parser.LabelRestriction) int { score += 10 } if lr.MustHaveOneOfValues != nil { - s := 10000 - len(lr.MustHaveOneOfValues) - if s < 100 { - s = 100 - } + s := max(10000-len(lr.MustHaveOneOfValues), 100) score += s } return score @@ -233,27 +230,40 @@ type Labeled interface { AllOwnAndParentLabelHandles() iter.Seq2[uniquestr.Handle, uniquestr.Handle] } -func (s *LabelRestrictionIndex[SelID]) IterPotentialMatches(item Labeled, f func(SelID, *selector.Selector)) { - emit := func(id SelID) error { - f(id, s.selectorsByID[id]) - return nil - } - - for k, v := range item.AllOwnAndParentLabelHandles() { - values, ok := s.labelToValueToIDs[k] - if !ok { - continue +func (s *LabelRestrictionIndex[SelID]) AllPotentialMatches(item Labeled) iter.Seq2[SelID, *selector.Selector] { + return func(yield func(SelID, *selector.Selector) bool) { + emit := func(id SelID) bool { + return yield(id, s.selectorsByID[id]) } - if values.selsMatchingWildcard != nil { - values.selsMatchingWildcard.Iter(emit) + + for k, v := range item.AllOwnAndParentLabelHandles() { + values, ok := s.labelToValueToIDs[k] + if !ok { + continue + } + if values.selsMatchingWildcard != nil { + for id := range values.selsMatchingWildcard.All() { + if !emit(id) { + return + } + } + } + if ids := values.selsMatchingSpecificValues[v]; ids != nil { + for id := range ids.All() { + if !emit(id) { + return + } + } + } } - if ids := values.selsMatchingSpecificValues[v]; ids != nil { - ids.Iter(emit) + + // Finally, emit the unoptimized selectors. + for id := range s.unoptimizedIDs.All() { + if !emit(id) { + return + } } } - - // Finally, emit the unoptimized selectors. - s.unoptimizedIDs.Iter(emit) } func (s *LabelRestrictionIndex[SelID]) updateGauges() { diff --git a/felix/labelindex/labelrestrictionindex/label_restriction_index_test.go b/felix/labelindex/labelrestrictionindex/label_restriction_index_test.go index 88eff01f889..19d9adf3e8d 100644 --- a/felix/labelindex/labelrestrictionindex/label_restriction_index_test.go +++ b/felix/labelindex/labelrestrictionindex/label_restriction_index_test.go @@ -72,11 +72,11 @@ func TestLabelRestrictionIndex(t *testing.T) { t.Log("Checking that the correct selectors are found...") potentialMatches := func(labels map[string]string) []string { var out []string - idx.IterPotentialMatches(labeledAdapter(labels), func(s string, s2 *selector.Selector) { - Expect(out).NotTo(ContainElement(s), "IterPotentialMatches produced duplicate: "+s) + for s, s2 := range idx.AllPotentialMatches(labeledAdapter(labels)) { + Expect(out).NotTo(ContainElement(s), "AllPotentialMatches produced duplicate: "+s) out = append(out, s) Expect(s2).NotTo(BeNil()) - }) + } // Sanity check that all selectors that match the labels are returned. // This is basically a cross-check on the caller's Expect(). for selID, sel := range idx.selectorsByID { @@ -211,7 +211,7 @@ func TestFindMostRestrictedLabel(t *testing.T) { "findMostRestrictedLabel should prefer impossible selector (present and absent)") var manyVals []uniquestr.Handle - for i := 0; i < 15000; i++ { + for i := range 15000 { manyVals = append(manyVals, uniquestr.Make(fmt.Sprint(i))) } Expect(mostRestricted(map[string]parser.LabelRestriction{ @@ -242,7 +242,7 @@ func mostRestricted(m map[string]parser.LabelRestriction) string { lrs[uniquestr.Make(k)] = v } } - handle, found := findMostRestrictedLabel(lrs) + handle, found := findMostRestrictedLabel(parser.MakeLabelRestrictions(lrs)) if found { return handle.Value() } diff --git a/felix/labelindex/named_port_bench_test.go b/felix/labelindex/named_port_bench_test.go index 82611596fcc..54018de892f 100644 --- a/felix/labelindex/named_port_bench_test.go +++ b/felix/labelindex/named_port_bench_test.go @@ -65,7 +65,7 @@ func benchmarkWorkloadUpdates(b *testing.B, numSels int) { lastID = ipSetID lastMember = member } - for i := 0; i < numSels; i++ { + for i := range numSels { sel, err := selector.Parse(fmt.Sprintf(`alpha == "beta" && has(ipset-%d)`, i)) if err != nil { b.Fatal(err) @@ -91,7 +91,7 @@ func sendUpdates(b *testing.B, idx *SelectorAndNamedPortIndex, updates []api.Upd func makeEndpointUpdates(num int) []api.Update { updates := make([]api.Update, num) - for n := 0; n < num; n++ { + for n := range num { key := model.WorkloadEndpointKey{ Hostname: "host", OrchestratorID: "k8s", @@ -156,7 +156,7 @@ func benchmarkParentUpdates(b *testing.B, numSels, numEndpoints int) { idx.OnUpdate(upd) } - for i := 0; i < numSels; i++ { + for i := range numSels { sel, err := selector.Parse(fmt.Sprintf(`projectcalico.org/name == "namespace-%d"`, i)) if err != nil { b.Fatal(err) diff --git a/felix/labelindex/named_port_index.go b/felix/labelindex/named_port_index.go index faa03a06071..25658b825e5 100644 --- a/felix/labelindex/named_port_index.go +++ b/felix/labelindex/named_port_index.go @@ -17,6 +17,7 @@ package labelindex import ( "iter" "math" + "slices" "strings" "time" @@ -91,12 +92,7 @@ func (d *endpointData) RemoveMatchingIPSetID(id string) { } func (d *endpointData) HasParent(parent *npParentData) bool { - for _, p := range d.parents { - if p == parent { - return true - } - } - return false + return slices.Contains(d.parents, parent) } func (d *endpointData) LookupNamedPorts(name string, proto ipsetmember.Protocol) []uint16 { @@ -670,7 +666,7 @@ func (idx *SelectorAndNamedPortIndex) scanEndpointAgainstIPSets( // Iterate over potential new matches and incref any members that // that produces. (This may temporarily over count.) - idx.selectorCandidatesIdx.IterPotentialMatches(epData, func(ipSetID string, _ *selector.Selector) { + for ipSetID := range idx.selectorCandidatesIdx.AllPotentialMatches(epData) { // Make sure we don't appear non-live if there are a lot of IP sets to get through. idx.maybeReportLive() @@ -698,7 +694,7 @@ func (idx *SelectorAndNamedPortIndex) scanEndpointAgainstIPSets( ipSetData.memberToRefCount[newMember] = newRefCount } } - }) + } // Decref all the old matches, emitting events if we drop to zero. for ipSetID, oldMembers := range oldIPSetContributions { @@ -783,7 +779,7 @@ func (idx *SelectorAndNamedPortIndex) UpdateParentLabels(parentID string, rawLab } func (idx *SelectorAndNamedPortIndex) updateParent(parentData *npParentData, applyUpdate, revertUpdate func()) { - parentData.IterEndpointIDs(func(id interface{}) error { + parentData.IterEndpointIDs(func(id any) error { epData, _ := idx.endpointKVIdx.Get(id) // This endpoint matches this parent, calculate its old contribution. (The revert function // is a no-op on the first loop but keeping it here, rather than at the bottom of the loop @@ -843,14 +839,13 @@ func (idx *SelectorAndNamedPortIndex) RecalcCachedContributions(epData *endpoint return nil } contrib := map[string][]ipsetmember.IPSetMember{} - epData.cachedMatchingIPSetIDs.Iter(func(ipSetID string) error { + for ipSetID := range epData.cachedMatchingIPSetIDs.All() { ipSetData := idx.ipSetDataByID[ipSetID] if ipSetData == nil { log.WithField("ipSetID", ipSetID).Panic("Endpoint cachedMatchingIPSetIDs refers to nonexistent IP set.") } contrib[ipSetID] = idx.CalculateEndpointContribution(epData, ipSetData) - return nil - }) + } return contrib } @@ -902,7 +897,7 @@ func (idx *SelectorAndNamedPortIndex) iterEndpointCandidates(ipsetID string, f f bestParentStrategy := labelnamevalueindex.ScanStrategy[string](nil) bestParentEndpointEstimate := math.MaxInt - for k, r := range restrictions { + for k, r := range restrictions.All() { epStrat := idx.endpointKVIdx.StrategyFor(k, r) parentStrat := idx.parentKVIdx.StrategyFor(k, r) epsToScan := epStrat.EstimatedItemsToScan() diff --git a/felix/labelindex/named_port_index_test.go b/felix/labelindex/named_port_index_test.go index 05b11aeb026..49130942aa9 100644 --- a/felix/labelindex/named_port_index_test.go +++ b/felix/labelindex/named_port_index_test.go @@ -23,7 +23,7 @@ import ( "strings" "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" diff --git a/felix/logutils/logutils.go b/felix/logutils/logutils.go index 75f049b3803..6e5186c8db0 100644 --- a/felix/logutils/logutils.go +++ b/felix/logutils/logutils.go @@ -88,10 +88,7 @@ func ConfigureLogging(configParams *config.Config) { logLevelSyslog := logutils.SafeParseLogLevel(configParams.LogSeveritySys) // Work out the most verbose level that is being logged. - mostVerboseLevel := logLevelScreen - if logLevelFile > mostVerboseLevel { - mostVerboseLevel = logLevelFile - } + mostVerboseLevel := max(logLevelFile, logLevelScreen) if logLevelSyslog > mostVerboseLevel { mostVerboseLevel = logLevelScreen } @@ -172,7 +169,7 @@ func getScreenDestination(configParams *config.Config, logLevel log.Level) *logu // TODO(dimitrin): Once logrus is upgraded to 1.2.0+, replace all logutils Trace calls with // calls to logrus Trace. // Tracef prints the debug log if display is true. -func Tracef(display bool, format string, args ...interface{}) { +func Tracef(display bool, format string, args ...any) { if display { log.Debugf(format, args...) } diff --git a/felix/markbits/mark_bits.go b/felix/markbits/mark_bits.go index 673bacea280..17f93a500e0 100644 --- a/felix/markbits/mark_bits.go +++ b/felix/markbits/mark_bits.go @@ -33,7 +33,7 @@ type MarkBitsManager struct { func NewMarkBitsManager(markMask uint32, markName string) *MarkBitsManager { numBitsFound := 0 - for shift := uint(0); shift < 32; shift++ { + for shift := range uint(32) { bit := uint32(1) << shift if markMask&bit > 0 { numBitsFound += 1 @@ -78,7 +78,7 @@ func (mc *MarkBitsManager) AvailableMarkBitCount() int { // It is up to the caller to check the result. func (mc *MarkBitsManager) NextBlockBitsMark(size int) (uint32, int) { mark := uint32(0) - for allocated := 0; allocated < size; allocated++ { + for allocated := range size { if bit, err := mc.NextSingleBitMark(); err != nil { log.WithFields(log.Fields{ "Name": mc.name, @@ -99,7 +99,7 @@ func (mc *MarkBitsManager) NextBlockBitsMark(size int) (uint32, int) { // Return Nth mark bit without allocation. func (mc *MarkBitsManager) nthMark(n int) (uint32, error) { numBitsFound := 0 - for shift := uint(0); shift < 32; shift++ { + for shift := range uint(32) { candidate := uint32(1) << shift if mc.mask&candidate > 0 { if numBitsFound == n { @@ -158,7 +158,7 @@ func (mc *MarkBitsManager) MapMarkToNumber(mark uint32) (int, error) { number := 0 numBitsFound := uint32(0) - for shift := uint(0); shift < 32; shift++ { + for shift := range uint(32) { bit := uint32(1) << shift if mc.mask&bit > 0 { if bit&mark > 0 { diff --git a/felix/markbits/mark_bits_test.go b/felix/markbits/mark_bits_test.go index 0223ad10e0b..2d3ad1c3887 100644 --- a/felix/markbits/mark_bits_test.go +++ b/felix/markbits/mark_bits_test.go @@ -17,7 +17,7 @@ package markbits_test import ( "errors" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/markbits" diff --git a/felix/markbits/markbits_suite_test.go b/felix/markbits/markbits_suite_test.go index f7e600e0105..bc0344485ba 100644 --- a/felix/markbits/markbits_suite_test.go +++ b/felix/markbits/markbits_suite_test.go @@ -17,9 +17,8 @@ package markbits_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestConfig(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/markbits_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "MarkBits Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_markbits_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/markbits", suiteConfig, reporterConfig) } diff --git a/felix/multidict/multidict.go b/felix/multidict/multidict.go index defd40bf57f..fdad835a009 100644 --- a/felix/multidict/multidict.go +++ b/felix/multidict/multidict.go @@ -69,10 +69,9 @@ func (md Multidict[K, V]) Iter(key K, f func(value V)) { if s == nil { return } - s.Iter(func(v V) error { + for v := range s.All() { f(v) - return nil - }) + } } func (md Multidict[K, V]) DiscardKey(k K) { diff --git a/felix/multidict/multidict_suite_test.go b/felix/multidict/multidict_suite_test.go index 7ee67f2d5e0..6792fe350e2 100644 --- a/felix/multidict/multidict_suite_test.go +++ b/felix/multidict/multidict_suite_test.go @@ -17,9 +17,8 @@ package multidict_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestMultidict(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/multidict_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Multidict Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_multidict_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/multidict", suiteConfig, reporterConfig) } diff --git a/felix/multidict/multidict_test.go b/felix/multidict/multidict_test.go index 77cd517b033..3c9e96623b0 100644 --- a/felix/multidict/multidict_test.go +++ b/felix/multidict/multidict_test.go @@ -15,7 +15,7 @@ package multidict_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/projectcalico/calico/felix/multidict" diff --git a/felix/netlinkshim/mocknetlink/netlink.go b/felix/netlinkshim/mocknetlink/netlink.go index e9449ab6455..46d8b412346 100644 --- a/felix/netlinkshim/mocknetlink/netlink.go +++ b/felix/netlinkshim/mocknetlink/netlink.go @@ -17,6 +17,7 @@ package mocknetlink import ( "errors" "fmt" + "maps" "net" "reflect" "strings" @@ -25,7 +26,7 @@ import ( "time" "unsafe" - "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/v2" //nolint:staticcheck // Ignore ST1001: should not use dot imports . "github.com/onsi/gomega" @@ -98,8 +99,8 @@ func init() { } // First check that our struct matches the netlink one... - nlType := reflect.TypeOf(ErrLinkNotFound) - ourType := reflect.TypeOf(myLinkNotFoundError{}) + nlType := reflect.TypeFor[netlink.LinkNotFoundError]() + ourType := reflect.TypeFor[myLinkNotFoundError]() if nlType.NumField() != ourType.NumField() { panic("netlink.LinkNotFoundError structure appears to have changed (different number of fields)") } @@ -375,10 +376,9 @@ func (d *MockNetlinkDataplane) GetDeletedConntrackEntries() []net.IP { defer ginkgo.GinkgoRecover() cpy := make([]net.IP, 0, d.deletedConntrackEntries.Len()) - d.deletedConntrackEntries.Iter(func(addr ip.Addr) error { + for addr := range d.deletedConntrackEntries.All() { cpy = append(cpy, addr.AsNetIP()) - return nil - }) + } return cpy } @@ -951,12 +951,31 @@ func (d *MockNetlinkDataplane) AddNeighs(family int, neighs ...netlink.Neigh) { func addNeighs(family int, neighMap map[NeighKey]*netlink.Neigh, neighs []netlink.Neigh) { for _, neigh := range neighs { - neigh := neigh nk := NeighKeyForFamily(family, neigh.LinkIndex, neigh.HardwareAddr, ip.FromNetIP(neigh.IP)) neighMap[nk] = &neigh } } +// RemoveNeighs allows test code to remove neighbours from the mock dataplane +// without going through the netlink API. +func (d *MockNetlinkDataplane) RemoveNeighs(family int, neighs ...netlink.Neigh) { + err := d.checkNeighFamily(family) + if err != nil { + panic(err) + } + if d.NeighsByFamily[family] == nil { + return + } + removeNeighs(family, d.NeighsByFamily[family], neighs) +} + +func removeNeighs(family int, neighMap map[NeighKey]*netlink.Neigh, neighs []netlink.Neigh) { + for _, neigh := range neighs { + nk := NeighKeyForFamily(family, neigh.LinkIndex, neigh.HardwareAddr, ip.FromNetIP(neigh.IP)) + delete(neighMap, nk) + } +} + // NeighKeyForFamily returns an appropriate NeighKey for the given family. // Different families are keyed in different ways by the kernel. ARP/NDP // entries are keyed on IP address, but FDB entries are keyed on MAC address @@ -1144,7 +1163,7 @@ func KeyForRoute(route *netlink.Route) string { if table == 0 { table = unix.RT_TABLE_MAIN } - key := fmt.Sprintf("%v-%v", table, route.Dst) + key := fmt.Sprintf("%v-%v-%v", table, route.Dst, route.Priority) log.WithField("routeKey", key).Debug("Calculated route key") return key } @@ -1179,9 +1198,7 @@ func (l *MockLink) copy() *MockLink { var wgPeersCopy map[wgtypes.Key]wgtypes.Peer if l.WireguardPeers != nil { wgPeersCopy = map[wgtypes.Key]wgtypes.Peer{} - for k, v := range l.WireguardPeers { - wgPeersCopy[k] = v - } + maps.Copy(wgPeersCopy, l.WireguardPeers) } return &MockLink{ diff --git a/felix/netlinkshim/mocknetlink/wireguard.go b/felix/netlinkshim/mocknetlink/wireguard.go index c2ad7c714b3..538d6f2c0dc 100644 --- a/felix/netlinkshim/mocknetlink/wireguard.go +++ b/felix/netlinkshim/mocknetlink/wireguard.go @@ -17,7 +17,7 @@ package mocknetlink import ( "sort" - "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" "github.com/sirupsen/logrus" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" @@ -237,10 +237,9 @@ func (d *MockWireguard) ConfigureDevice(name string, cfg wgtypes.Config) error { } var allowedIPStr []string - allowedIPs.Iter(func(allowedIP string) error { + for allowedIP := range allowedIPs.All() { allowedIPStr = append(allowedIPStr, allowedIP) - return nil - }) + } sort.Strings(allowedIPStr) peer.AllowedIPs = nil diff --git a/felix/nfnetlink/conntrack_linux.go b/felix/nfnetlink/conntrack_linux.go index d8f61c9b43a..e5ff9eb260d 100644 --- a/felix/nfnetlink/conntrack_linux.go +++ b/felix/nfnetlink/conntrack_linux.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows // Copyright (c) 2016-2025 Tigera, Inc. All rights reserved. // @@ -65,7 +64,7 @@ func conntrackEntryFromNfAttrs(m []byte, family uint8) (CtEntry, error) { native := binary.BigEndian // Start building a Conntrack Entry - for idx := 0; idx < n; idx++ { + for idx := range n { attr := attrs[idx] attrType := int(attr.Attr.Type) & nfnl.NLA_TYPE_MASK isNestedAttr := int(attr.Attr.Type)&syscall.NLA_F_NESTED == syscall.NLA_F_NESTED @@ -135,7 +134,7 @@ func parseConntrackTuple(tuple *CtTuple, value []byte, family uint8) error { tuple.L3ProtoNum = int(family) native := binary.BigEndian - for idx := 0; idx < n; idx++ { + for idx := range n { attr := attrs[idx] attrType := uint16(int(attr.Attr.Type) & nfnl.NLA_TYPE_MASK) isNestedAttr := int(attr.Attr.Type)&syscall.NLA_F_NESTED == syscall.NLA_F_NESTED @@ -172,7 +171,7 @@ func parseTupleIp(tuple *CtTuple, value []byte) error { if err != nil { return err } - for idx := 0; idx < n; idx++ { + for idx := range n { attr := attrs[idx] attrType := uint16(int(attr.Attr.Type) & nfnl.NLA_TYPE_MASK) switch attrType { @@ -194,7 +193,7 @@ func parseTupleProto(tuple *CtTuple, value []byte) error { return err } native := binary.BigEndian - for idx := 0; idx < n; idx++ { + for idx := range n { attr := attrs[idx] attrType := uint16(int(attr.Attr.Type) & nfnl.NLA_TYPE_MASK) switch attrType { @@ -225,7 +224,7 @@ func parseConntrackCounters(value []byte) (CtCounters, error) { return counters, err } native := binary.BigEndian - for idx := 0; idx < n; idx++ { + for idx := range n { attr := attrs[idx] attrType := uint16(int(attr.Attr.Type) & nfnl.NLA_TYPE_MASK) switch attrType { @@ -251,7 +250,7 @@ func parseProtoInfo(cpi *CtProtoInfo, value []byte) error { return err } - for idx := 0; idx < n; idx++ { + for idx := range n { attr := attrs[idx] attrType := uint16(int(attr.Attr.Type) & nfnl.NLA_TYPE_MASK) isNestedAttr := int(attr.Attr.Type)&syscall.NLA_F_NESTED == syscall.NLA_F_NESTED @@ -279,7 +278,7 @@ func parseProtoInfoTCP(cpi *CtProtoInfo, value []byte) error { if err != nil { return err } - for idx := 0; idx < n; idx++ { + for idx := range n { attr := attrs[idx] attrType := uint16(int(attr.Attr.Type) & nfnl.NLA_TYPE_MASK) switch attrType { diff --git a/felix/nfnetlink/conntrack_test.go b/felix/nfnetlink/conntrack_test.go index 7659c92ef8f..7ca6418e3f2 100644 --- a/felix/nfnetlink/conntrack_test.go +++ b/felix/nfnetlink/conntrack_test.go @@ -19,7 +19,7 @@ import ( "syscall" "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/nfnetlink/nfnl" diff --git a/felix/nfnetlink/nflog_linux.go b/felix/nfnetlink/nflog_linux.go index 40c575a24f2..a99512ca6c0 100644 --- a/felix/nfnetlink/nflog_linux.go +++ b/felix/nfnetlink/nflog_linux.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows // Copyright (c) 2016-2025 Tigera, Inc. All rights reserved. // @@ -364,7 +363,7 @@ func parseNflog(m []byte) (NflogPacket, error) { return nflogPacket, err } - for idx := 0; idx < n; idx++ { + for idx := range n { attr := attrs[idx] attrType := int(attr.Attr.Type) & nfnl.NLA_TYPE_MASK native := binary.BigEndian diff --git a/felix/nfnetlink/nflog_test.go b/felix/nfnetlink/nflog_test.go index ad760cadef3..455498b2e88 100644 --- a/felix/nfnetlink/nflog_test.go +++ b/felix/nfnetlink/nflog_test.go @@ -17,26 +17,26 @@ package nfnetlink import ( "net" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" ) -var _ = Describe("ParseNflog", func() { - Describe("Ipv6", func() { +var _ = ginkgo.Describe("ParseNflog", func() { + ginkgo.Describe("Ipv6", func() { data := [...]byte{8, 0, 1, 0, 134, 221, 3, 0, 8, 0, 10, 0, 68, 82, 73, 0, 8, 0, 5, 0, 0, 0, 0, 3, 8, 0, 11, 0, 0, 0, 3, 232, 8, 0, 14, 0, 0, 0, 3, 232, 84, 0, 9, 0, 96, 15, 33, 48, 0, 40, 6, 64, 254, 128, 0, 0, 0, 0, 0, 0, 10, 0, 39, 255, 254, 134, 38, 162, 254, 128, 0, 0, 0, 0, 0, 0, 10, 0, 39, 255, 254, 215, 241, 163, 150, 125, 31, 64, 121, 172, 141, 35, 0, 0, 0, 0, 160, 2, 112, 128, 118, 211, 0, 0, 2, 4, 5, 160, 4, 2, 8, 10, 2, 59, 176, 73, 0, 0, 0, 0, 1, 3, 3, 7} - It("should parse NFLOG packet with IPv6 payload without errors", func() { + ginkgo.It("should parse NFLOG packet with IPv6 payload without errors", func() { nflogPacket, err := parseNflog(data[:]) - Expect(err).To(BeNil()) + gomega.Expect(err).To(gomega.BeNil()) prefix := string(nflogPacket.Prefix.Prefix[:nflogPacket.Prefix.Len]) - Expect(prefix).To(Equal("DRI")) + gomega.Expect(prefix).To(gomega.Equal("DRI")) tuple := nflogPacket.Tuple - Expect(net.IP(tuple.Src[:16]).String()).To(Equal("fe80::a00:27ff:fe86:26a2")) - Expect(net.IP(tuple.Dst[:16]).String()).To(Equal("fe80::a00:27ff:fed7:f1a3")) + gomega.Expect(net.IP(tuple.Src[:16]).String()).To(gomega.Equal("fe80::a00:27ff:fe86:26a2")) + gomega.Expect(net.IP(tuple.Dst[:16]).String()).To(gomega.Equal("fe80::a00:27ff:fed7:f1a3")) // TCP - Expect(tuple.Proto).To(Equal(6)) + gomega.Expect(tuple.Proto).To(gomega.Equal(6)) // Dst port 8000 - Expect(tuple.L4Src.Port).To(Equal(38525)) - Expect(tuple.L4Dst.Port).To(Equal(8000)) + gomega.Expect(tuple.L4Src.Port).To(gomega.Equal(38525)) + gomega.Expect(tuple.L4Dst.Port).To(gomega.Equal(8000)) }) }) }) diff --git a/felix/nfnetlink/nfnetlink_suite_test.go b/felix/nfnetlink/nfnetlink_suite_test.go index 10570e28ea2..11356b5f907 100644 --- a/felix/nfnetlink/nfnetlink_suite_test.go +++ b/felix/nfnetlink/nfnetlink_suite_test.go @@ -17,21 +17,19 @@ package nfnetlink_test import ( "testing" - "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" + "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" - "github.com/projectcalico/calico/libcalico-go/lib/logutils" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) -func TestNfnetlink(t *testing.T) { - gomega.RegisterFailHandler(ginkgo.Fail) - junitReporter := reporters.NewJUnitReporter("../report/ip_suite.xml") - ginkgo.RunSpecsWithDefaultAndCustomReporters(t, "Nfnetlink Suite", []ginkgo.Reporter{junitReporter}) -} - func init() { testutils.HookLogrusForGinkgo() - logutils.ConfigureFormatter("test") +} + +func TestNfnetlink(t *testing.T) { + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_nfnetlink_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/nfnetlink", suiteConfig, reporterConfig) } diff --git a/felix/nfnetlink/nfnl/conntrack_linux.go b/felix/nfnetlink/nfnl/conntrack_linux.go index ffbd1501288..9516968bae5 100644 --- a/felix/nfnetlink/nfnl/conntrack_linux.go +++ b/felix/nfnetlink/nfnl/conntrack_linux.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows // Copyright (c) 2016-2025 Tigera, Inc. All rights reserved. // diff --git a/felix/nfnetlink/nfnl/nflog_linux.go b/felix/nfnetlink/nfnl/nflog_linux.go index 51dced5cd48..ce3e8608937 100644 --- a/felix/nfnetlink/nfnl/nflog_linux.go +++ b/felix/nfnetlink/nfnl/nflog_linux.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows // Copyright (c) 2016-2025 Tigera, Inc. All rights reserved. // diff --git a/felix/nfnetlink/nfnl/nflog_netlink_linux.go b/felix/nfnetlink/nfnl/nflog_netlink_linux.go index d97fa334b2f..b503a9f97b9 100644 --- a/felix/nfnetlink/nfnl/nflog_netlink_linux.go +++ b/felix/nfnetlink/nfnl/nflog_netlink_linux.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows // Copyright (c) 2016-2025 Tigera, Inc. All rights reserved. // diff --git a/felix/nfnetlink/nfnl/nfnetlink_linux.go b/felix/nfnetlink/nfnl/nfnetlink_linux.go index e49a4345a23..f6c6dc21802 100644 --- a/felix/nfnetlink/nfnl/nfnetlink_linux.go +++ b/felix/nfnetlink/nfnl/nfnetlink_linux.go @@ -1,5 +1,4 @@ //go:build !windows -// +build !windows // Copyright (c) 2016-2025 Tigera, Inc. All rights reserved. // diff --git a/felix/nftables/actions.go b/felix/nftables/actions.go index cd135f116ae..33b688ccc15 100644 --- a/felix/nftables/actions.go +++ b/felix/nftables/actions.go @@ -17,6 +17,8 @@ package nftables import ( "fmt" "math" + "net" + "strconv" "strings" "github.com/sirupsen/logrus" @@ -263,7 +265,7 @@ type LogAction struct { } func (g LogAction) ToFragment(features *environment.Features) string { - return fmt.Sprintf(`log prefix %s level info`, g.Prefix) + return fmt.Sprintf(`log prefix "%s: " level info`, g.Prefix) } func (g LogAction) String() string { @@ -292,7 +294,7 @@ func (g DNATAction) ToFragment(features *environment.Features) string { if g.DestPort == 0 { return fmt.Sprintf("dnat to %s", g.DestAddr) } else { - return fmt.Sprintf("dnat to %s:%d", g.DestAddr, g.DestPort) + return fmt.Sprintf("dnat to %s", net.JoinHostPort(g.DestAddr, strconv.Itoa(int(g.DestPort)))) } } @@ -329,7 +331,7 @@ func (g MasqAction) ToFragment(features *environment.Features) string { } if g.ToPorts != "" { // e.g., masquerade to :1024-65535 - return fmt.Sprintf("masquerade to %s"+fullyRand, g.ToPorts) + return fmt.Sprintf("masquerade to :%s"+fullyRand, g.ToPorts) } return "masquerade" + fullyRand } diff --git a/felix/nftables/actions_test.go b/felix/nftables/actions_test.go index c830a9406f6..de7ada44784 100644 --- a/felix/nftables/actions_test.go +++ b/felix/nftables/actions_test.go @@ -15,8 +15,8 @@ package nftables_test import ( - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" + . "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/environment" "github.com/projectcalico/calico/felix/generictables" @@ -25,19 +25,21 @@ import ( var _ = DescribeTable("Actions", func(features environment.Features, action generictables.Action, expRendering string) { - Expect(action.ToFragment(&features)).To(Equal(expRendering)) + gomega.Expect(action.ToFragment(&features)).To(gomega.Equal(expRendering)) }, Entry("GotoAction", environment.Features{}, GotoAction{Target: "cali-abcd"}, "goto cali-abcd"), Entry("JumpAction", environment.Features{}, JumpAction{Target: "cali-abcd"}, "jump cali-abcd"), Entry("ReturnAction", environment.Features{}, ReturnAction{}, "return"), Entry("DropAction", environment.Features{}, DropAction{}, "drop"), Entry("AcceptAction", environment.Features{}, AcceptAction{}, "accept"), - Entry("LogAction", environment.Features{}, LogAction{Prefix: "prefix"}, "log prefix prefix level info"), + Entry("LogAction", environment.Features{}, LogAction{Prefix: "prefix"}, `log prefix "prefix: " level info`), Entry("DNATAction", environment.Features{}, DNATAction{DestAddr: "10.0.0.1", DestPort: 8081}, "dnat to 10.0.0.1:8081"), Entry("SNATAction", environment.Features{}, SNATAction{ToAddr: "10.0.0.1"}, "snat to 10.0.0.1"), Entry("SNATAction fully random", environment.Features{SNATFullyRandom: true}, SNATAction{ToAddr: "10.0.0.1"}, "snat to 10.0.0.1 fully-random"), Entry("MasqAction", environment.Features{}, MasqAction{}, "masquerade"), + Entry("MasqAction", environment.Features{}, MasqAction{ToPorts: "32768-65535"}, "masquerade to :32768-65535"), Entry("MasqAction", environment.Features{MASQFullyRandom: true}, MasqAction{}, "masquerade fully-random"), + Entry("MasqAction", environment.Features{MASQFullyRandom: true}, MasqAction{ToPorts: "32768-65535"}, "masquerade to :32768-65535 fully-random"), Entry("ClearMarkAction", environment.Features{}, ClearMarkAction{Mark: 0x1000}, "meta mark set mark & 0xffffefff"), Entry("SetMarkAction", environment.Features{}, SetMarkAction{Mark: 0x1000}, "meta mark set mark or 0x1000"), Entry("SetMaskedMarkAction", environment.Features{}, SetMaskedMarkAction{Mark: 0x1000, Mask: 0xf000}, "meta mark set mark & 0xffff0fff ^ 0x1000"), diff --git a/felix/nftables/fake_test.go b/felix/nftables/fake_test.go index e1ee400152f..03b77be2733 100644 --- a/felix/nftables/fake_test.go +++ b/felix/nftables/fake_test.go @@ -57,6 +57,9 @@ type fakeNFT struct { // Allow overriding the next ListElements response for one or more sets to be an error. ListElementsErrors map[string]error + + // Track the number of List calls (simulates nft process spawns). + ListCallCount int } func (f *fakeNFT) Reset() { @@ -136,6 +139,8 @@ func (f *fakeNFT) preList() { f.lock.Lock() defer f.lock.Unlock() + f.ListCallCount++ + if f.PreList != nil { logrus.Info("Calling PreList") f.PreList() @@ -175,3 +180,7 @@ func (f *fakeNFT) maybeFailListElements(name string) error { } return nil } + +func (f *fakeNFT) ListCounters(ctx context.Context) ([]*knftables.Counter, error) { + return f.fake.ListCounters(ctx) +} diff --git a/felix/nftables/ipsets.go b/felix/nftables/ipsets.go index f5bcaa182bc..f10f97616cb 100644 --- a/felix/nftables/ipsets.go +++ b/felix/nftables/ipsets.go @@ -184,10 +184,9 @@ func (s *IPSets) AddOrReplaceIPSet(setMetadata ipsets.IPSetMetadata, members []s desiredMembers.Delete(k) } }) - canonMembers.Iter(func(m SetMember) error { + for m := range canonMembers.All() { desiredMembers.Add(m) - return nil - }) + } s.updateDirtiness(mainIPSetName) } @@ -244,10 +243,9 @@ func (s *IPSets) AddMembers(setID string, newMembers []string) { return } membersTracker := s.mainSetNameToMembers[setName] - canonMembers.Iter(func(member SetMember) error { + for member := range canonMembers.All() { membersTracker.Desired().Add(member) - return nil - }) + } s.updateDirtiness(setName) } @@ -265,10 +263,9 @@ func (s *IPSets) RemoveMembers(setID string, removedMembers []string) { return } membersTracker := s.mainSetNameToMembers[setName] - canonMembers.Iter(func(member SetMember) error { + for member := range canonMembers.All() { membersTracker.Desired().Delete(member) - return nil - }) + } s.updateDirtiness(setName) } @@ -336,7 +333,7 @@ func (s *IPSets) ApplyUpdates(listener ipsets.UpdateListener) { retryDelay *= 2 } - for attempt := 0; attempt < 10; attempt++ { + for attempt := range 10 { if attempt > 0 { s.logCxt.Info("Retrying after an nftables set update failure...") } @@ -496,10 +493,9 @@ func (s *IPSets) tryResync() error { memberTracker := s.getOrCreateMemberTracker(setName) numExtrasExpected := memberTracker.PendingDeletions().Len() err = memberTracker.Dataplane().ReplaceFromIter(func(f func(k SetMember)) error { - elemsSet.Iter(func(item SetMember) error { + for item := range elemsSet.All() { f(item) - return nil - }) + } return nil }) if err != nil { @@ -586,14 +582,13 @@ func (s *IPSets) NFTablesSet(name string) *knftables.Set { func (s *IPSets) tryUpdates(listener ipsets.UpdateListener) error { var dirtyIPSets []string - s.ipSetsWithDirtyMembers.Iter(func(setName string) error { + for setName := range s.ipSetsWithDirtyMembers.All() { if _, ok := s.setNameToProgrammedMetadata.Desired().Get(setName); !ok { // Skip deletions and IP sets that aren't needed due to the filter. - return nil + continue } dirtyIPSets = append(dirtyIPSets, setName) - return nil - }) + } s.setNameToProgrammedMetadata.PendingUpdates().Iter(func(setName string, v ipsets.IPSetMetadata) deltatracker.IterAction { if !s.ipSetsWithDirtyMembers.Contains(setName) { diff --git a/felix/nftables/ipsets_test.go b/felix/nftables/ipsets_test.go index 8311569a9e3..5d15312c5c7 100644 --- a/felix/nftables/ipsets_test.go +++ b/felix/nftables/ipsets_test.go @@ -18,8 +18,7 @@ import ( "context" "fmt" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "sigs.k8s.io/knftables" @@ -149,7 +148,7 @@ var _ = Describe("IPSets with empty data plane", func() { // ourselves to in the resync code. tx := f.NewTransaction() tx.Add(&knftables.Table{}) - for i := 0; i < 200; i++ { + for i := range 200 { tx.Add(&knftables.Set{ Name: fmt.Sprintf("set-%d", i), Type: "ipv4_addr", @@ -249,27 +248,27 @@ var _ = DescribeTable("IPSets programming v4", }, Entry( - ipsets.IPSetTypeHashIP, + string(ipsets.IPSetTypeHashIP), ipsets.IPSetMetadata{SetID: "test", Type: ipsets.IPSetTypeHashIP}, []string{"10.0.0.1"}, []*knftables.Element{{Set: "cali40test", Key: []string{"10.0.0.1"}}}, ), Entry( - ipsets.IPSetTypeHashNet, + string(ipsets.IPSetTypeHashNet), ipsets.IPSetMetadata{SetID: "test", Type: ipsets.IPSetTypeHashNet}, []string{"10.0.0.0/24"}, []*knftables.Element{{Set: "cali40test", Key: []string{"10.0.0.0/24"}}}, ), Entry( - ipsets.IPSetTypeHashIPPort, + string(ipsets.IPSetTypeHashIPPort), ipsets.IPSetMetadata{SetID: "test", Type: ipsets.IPSetTypeHashIPPort}, []string{"1.2.3.4,tcp:81"}, []*knftables.Element{{Set: "cali40test", Key: []string{"1.2.3.4", "tcp", "81"}}}, ), Entry( - ipsets.IPSetTypeHashNetNet, + string(ipsets.IPSetTypeHashNetNet), ipsets.IPSetMetadata{SetID: "test", Type: ipsets.IPSetTypeHashNetNet}, []string{"10.0.0.0/32,10.0.0.0/32"}, []*knftables.Element{{Set: "cali40test", Key: []string{"10.0.0.0", "10.0.0.0"}}}, ), Entry( - ipsets.IPSetTypeBitmapPort, + string(ipsets.IPSetTypeBitmapPort), ipsets.IPSetMetadata{SetID: "test", Type: ipsets.IPSetTypeBitmapPort}, []string{"v4,80"}, []*knftables.Element{{Set: "cali40test", Key: []string{"80"}}}, ), @@ -303,27 +302,27 @@ var _ = DescribeTable("IPSets programming v6", }, Entry( - ipsets.IPSetTypeHashIP, + string(ipsets.IPSetTypeHashIP), ipsets.IPSetMetadata{SetID: "test", Type: ipsets.IPSetTypeHashIP}, []string{"2001:db8::1"}, []*knftables.Element{{Set: "cali60test", Key: []string{"2001:db8::1"}}}, ), Entry( - ipsets.IPSetTypeHashNet, + string(ipsets.IPSetTypeHashNet), ipsets.IPSetMetadata{SetID: "test", Type: ipsets.IPSetTypeHashNet}, []string{"2001:db8::/64"}, []*knftables.Element{{Set: "cali60test", Key: []string{"2001:db8::/64"}}}, ), Entry( - ipsets.IPSetTypeHashIPPort, + string(ipsets.IPSetTypeHashIPPort), ipsets.IPSetMetadata{SetID: "test", Type: ipsets.IPSetTypeHashIPPort}, []string{"2001:db8::1,tcp:81"}, []*knftables.Element{{Set: "cali60test", Key: []string{"2001:db8::1", "tcp", "81"}}}, ), Entry( - ipsets.IPSetTypeHashNetNet, + string(ipsets.IPSetTypeHashNetNet), ipsets.IPSetMetadata{SetID: "test", Type: ipsets.IPSetTypeHashNetNet}, []string{"2001:db8::/128,2001:db8::/128"}, []*knftables.Element{{Set: "cali60test", Key: []string{"2001:db8::", "2001:db8::"}}}, ), Entry( - ipsets.IPSetTypeBitmapPort, + string(ipsets.IPSetTypeBitmapPort), ipsets.IPSetMetadata{SetID: "test", Type: ipsets.IPSetTypeBitmapPort}, []string{"v6,80"}, []*knftables.Element{{Set: "cali60test", Key: []string{"80"}}}, ), @@ -336,17 +335,17 @@ var _ = DescribeTable("CanonicalizeMember", Expect(canon.Key()).To(Equal(key)) }, - Entry(ipsets.IPSetTypeHashIP, ipsets.IPSetTypeHashIP, "192.168.0.1/32", "192.168.0.1", []string{"192.168.0.1"}), - Entry(ipsets.IPSetTypeHashIP, ipsets.IPSetTypeHashIP, "fe80::1/128", "fe80::1", []string{"fe80::1"}), - Entry(ipsets.IPSetTypeHashNet, ipsets.IPSetTypeHashNet, "10.0.0.0/24", "10.0.0.0/24", []string{"10.0.0.0/24"}), - Entry(ipsets.IPSetTypeHashNet, ipsets.IPSetTypeHashNet, "2001:db8::/64", "2001:db8::/64", []string{"2001:db8::/64"}), - Entry(ipsets.IPSetTypeHashIPPort, ipsets.IPSetTypeHashIPPort, "192.168.0.1,tcp:80", "192.168.0.1,tcp:80", []string{"192.168.0.1", "tcp", "80"}), - Entry(ipsets.IPSetTypeHashIPPort, ipsets.IPSetTypeHashIPPort, "fe80::1,udp:53", "fe80::1,udp:53", []string{"fe80::1", "udp", "53"}), - Entry(ipsets.IPSetTypeHashNetNet, ipsets.IPSetTypeHashNetNet, "10.0.0.1/32,10.0.0.1/32", "10.0.0.1/32 . 10.0.0.1/32", []string{"10.0.0.1", "10.0.0.1"}), - Entry(ipsets.IPSetTypeHashNetNet, ipsets.IPSetTypeHashNetNet, "2001:db8::1/128,2001:db8::1/128", "2001:db8::1/128 . 2001:db8::1/128", []string{"2001:db8::1", "2001:db8::1"}), - Entry(ipsets.IPSetTypeBitmapPort, ipsets.IPSetTypeBitmapPort, "v4,80", "80", []string{"80"}), - Entry(ipsets.IPSetTypeBitmapPort, ipsets.IPSetTypeBitmapPort, "v6,80", "80", []string{"80"}), - Entry(ipsets.IPSetTypeBitmapPort, ipsets.IPSetTypeBitmapPort, "80", "80", []string{"80"}), + Entry(string(ipsets.IPSetTypeHashIP), ipsets.IPSetTypeHashIP, "192.168.0.1/32", "192.168.0.1", []string{"192.168.0.1"}), + Entry(string(ipsets.IPSetTypeHashIP), ipsets.IPSetTypeHashIP, "fe80::1/128", "fe80::1", []string{"fe80::1"}), + Entry(string(ipsets.IPSetTypeHashNet), ipsets.IPSetTypeHashNet, "10.0.0.0/24", "10.0.0.0/24", []string{"10.0.0.0/24"}), + Entry(string(ipsets.IPSetTypeHashNet), ipsets.IPSetTypeHashNet, "2001:db8::/64", "2001:db8::/64", []string{"2001:db8::/64"}), + Entry(string(ipsets.IPSetTypeHashIPPort), ipsets.IPSetTypeHashIPPort, "192.168.0.1,tcp:80", "192.168.0.1,tcp:80", []string{"192.168.0.1", "tcp", "80"}), + Entry(string(ipsets.IPSetTypeHashIPPort), ipsets.IPSetTypeHashIPPort, "fe80::1,udp:53", "fe80::1,udp:53", []string{"fe80::1", "udp", "53"}), + Entry(string(ipsets.IPSetTypeHashNetNet), ipsets.IPSetTypeHashNetNet, "10.0.0.1/32,10.0.0.1/32", "10.0.0.1/32 . 10.0.0.1/32", []string{"10.0.0.1", "10.0.0.1"}), + Entry(string(ipsets.IPSetTypeHashNetNet), ipsets.IPSetTypeHashNetNet, "2001:db8::1/128,2001:db8::1/128", "2001:db8::1/128 . 2001:db8::1/128", []string{"2001:db8::1", "2001:db8::1"}), + Entry(string(ipsets.IPSetTypeBitmapPort), ipsets.IPSetTypeBitmapPort, "v4,80", "80", []string{"80"}), + Entry(string(ipsets.IPSetTypeBitmapPort), ipsets.IPSetTypeBitmapPort, "v6,80", "80", []string{"80"}), + Entry(string(ipsets.IPSetTypeBitmapPort), ipsets.IPSetTypeBitmapPort, "80", "80", []string{"80"}), ) var _ = DescribeTable("NFTablesSet", @@ -360,27 +359,27 @@ var _ = DescribeTable("NFTablesSet", }, Entry( - ipsets.IPSetTypeHashIP, + string(ipsets.IPSetTypeHashIP), ipsets.IPSetMetadata{SetID: "test", Type: ipsets.IPSetTypeHashIP}, &knftables.Set{Name: "cali40test", Type: "ipv4_addr"}, ), Entry( - ipsets.IPSetTypeHashNet, + string(ipsets.IPSetTypeHashNet), ipsets.IPSetMetadata{SetID: "test", Type: ipsets.IPSetTypeHashNet}, &knftables.Set{Name: "cali40test", Type: "ipv4_addr", Flags: []knftables.SetFlag{knftables.IntervalFlag}}, ), Entry( - ipsets.IPSetTypeHashIPPort, + string(ipsets.IPSetTypeHashIPPort), ipsets.IPSetMetadata{SetID: "test", Type: ipsets.IPSetTypeHashIPPort}, &knftables.Set{Name: "cali40test", Type: "ipv4_addr . inet_proto . inet_service"}, ), Entry( - ipsets.IPSetTypeHashNetNet, + string(ipsets.IPSetTypeHashNetNet), ipsets.IPSetMetadata{SetID: "test", Type: ipsets.IPSetTypeHashNetNet}, &knftables.Set{Name: "cali40test", Type: "ipv4_addr . ipv4_addr"}, ), Entry( - ipsets.IPSetTypeBitmapPort, + string(ipsets.IPSetTypeBitmapPort), ipsets.IPSetMetadata{SetID: "test", Type: ipsets.IPSetTypeBitmapPort}, &knftables.Set{Name: "cali40test", Type: "inet_service"}, ), diff --git a/felix/nftables/maps.go b/felix/nftables/maps.go index f7743a5ff52..a56cfdceaf0 100644 --- a/felix/nftables/maps.go +++ b/felix/nftables/maps.go @@ -169,14 +169,13 @@ func (s *Maps) AddOrReplaceMap(meta MapMetadata, members map[string][]string) { desiredMembers.Delete(k) } }) - canonMembers.Iter(func(m MapMember) error { + for m := range canonMembers.All() { if !desiredMembers.Contains(m) { // Incref any chain referenced by the member. s.maybeIncrefChain(m) desiredMembers.Add(m) } - return nil - }) + } s.updateDirtiness(meta.Name) } @@ -245,11 +244,10 @@ func (s *Maps) AddMembers(mapName string, newMembers map[string][]string) { return } membersTracker := s.mapNameToMembers[mapName] - canonMembers.Iter(func(member MapMember) error { + for member := range canonMembers.All() { s.maybeIncrefChain(member) membersTracker.Desired().Add(member) - return nil - }) + } s.updateDirtiness(mapName) } @@ -266,11 +264,10 @@ func (s *Maps) RemoveMembers(mapName string, removedMembers map[string][]string) return } membersTracker := s.mapNameToMembers[mapName] - canonMembers.Iter(func(member MapMember) error { + for member := range canonMembers.All() { s.maybeDecrefChain(member) membersTracker.Desired().Delete(member) - return nil - }) + } s.updateDirtiness(mapName) } @@ -386,10 +383,9 @@ func (s *Maps) LoadDataplaneState() error { memberTracker := s.getOrCreateMemberTracker(mapName) numExtrasExpected := memberTracker.PendingDeletions().Len() err = memberTracker.Dataplane().ReplaceFromIter(func(f func(k MapMember)) error { - elemsSet.Iter(func(item MapMember) error { + for item := range elemsSet.All() { f(item) - return nil - }) + } return nil }) if err != nil { @@ -541,17 +537,15 @@ func (s *Maps) FinishMapUpdates(updates *MapUpdates) { // dataplane should be in sync. for mapName, members := range updates.MapToAddedMembers { setMap(mapName) - members.Iter(func(member MapMember) error { + for member := range members.All() { s.mapNameToMembers[mapName].Dataplane().Add(member) - return nil - }) + } } for mapName, members := range updates.MapToDeletedMembers { setMap(mapName) - members.Iter(func(member MapMember) error { + for member := range members.All() { s.mapNameToMembers[mapName].Dataplane().Delete(member) - return nil - }) + } } // We need to clear pending deletions now that we have successfully deleted the maps. @@ -570,14 +564,13 @@ func (s *Maps) dirtyMaps() []string { var dirtyMaps []string // Collect any maps with dirty members that need to be updated based on resync with the dataplane. - s.mapsWithDirtyMembers.Iter(func(mapName string) error { + for mapName := range s.mapsWithDirtyMembers.All() { if _, ok := s.mapNameToProgrammedMetadata.Desired().Get(mapName); !ok { // Skip deletions and maps that aren't needed due to the filter. - return nil + continue } dirtyMaps = append(dirtyMaps, mapName) - return nil - }) + } // Any maps that are marked for deletion should have their members cleared out if there are any. // Because of the potential interdependency between maps and chains, we need to: diff --git a/felix/nftables/maps_test.go b/felix/nftables/maps_test.go index 5bd64b8a85c..4ec35889054 100644 --- a/felix/nftables/maps_test.go +++ b/felix/nftables/maps_test.go @@ -19,7 +19,7 @@ import ( "fmt" "strings" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "sigs.k8s.io/knftables" @@ -242,7 +242,7 @@ var _ = Describe("Maps with empty data plane", func() { // ourselves to in the resync code. tx := f.NewTransaction() tx.Add(&knftables.Table{}) - for i := 0; i < 200; i++ { + for i := range 200 { tx.Add(&knftables.Map{ Name: fmt.Sprintf("map-%d", i), Type: "ifname : verdict", diff --git a/felix/nftables/match_builder.go b/felix/nftables/match_builder.go index e206c5d6adf..b46e2a86a45 100644 --- a/felix/nftables/match_builder.go +++ b/felix/nftables/match_builder.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2016-2026 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -534,6 +534,16 @@ func (m nftMatch) NotICMPV6TypeAndCode(t, c uint8) generictables.MatchCriteria { return m } +// The expected rate must be an integer (0~9999) with /second, /minute, /hour, or /day suffix. +func (m nftMatch) Limit(rate string, burst uint16) generictables.MatchCriteria { + if burst > 0 { + m.clauses = append(m.clauses, fmt.Sprintf("limit rate %s burst %d packets", rate, burst)) + } else { + m.clauses = append(m.clauses, fmt.Sprintf("limit rate %s", rate)) + } + return m +} + func (m nftMatch) InInterfaceVMAP(name string) generictables.MatchCriteria { m.clauses = append(m.clauses, fmt.Sprintf("iifname vmap @-%s", LegalizeSetName(name))) return m diff --git a/felix/nftables/match_builder_test.go b/felix/nftables/match_builder_test.go index 513b1ffe75e..a96907ee87e 100644 --- a/felix/nftables/match_builder_test.go +++ b/felix/nftables/match_builder_test.go @@ -15,8 +15,7 @@ package nftables_test import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/generictables" @@ -128,6 +127,10 @@ var _ = DescribeTable("MatchBuilder", Entry("ICMPV6TypeAndCode", nftables.Match().ICMPV6TypeAndCode(123, 5), "icmpv6 type 123 code 5"), Entry("NotICMPV6TypeAndCode", nftables.Match().NotICMPV6TypeAndCode(123, 5), "icmpv6 type != 123 code != 5"), + // Limits. + Entry("Limit with rate", nftables.Match().Limit("10/minute", 0), "limit rate 10/minute"), + Entry("Limit with rate and burst", nftables.Match().Limit("20/hour", 10), "limit rate 20/hour burst 10 packets"), + // VMAPs Entry("InInterfaceVMAP", nftables.Match().InInterfaceVMAP("vmap1234").(nftables.NFTMatchCriteria).SetLayer("filter"), "iifname vmap @filter-vmap1234"), Entry("OutInterfaceVMAP", nftables.Match().OutInterfaceVMAP("vmap1234").(nftables.NFTMatchCriteria).SetLayer("raw"), "oifname vmap @raw-vmap1234"), diff --git a/felix/nftables/matchers_test.go b/felix/nftables/matchers_test.go index 47aaa5507c2..99ec6ec4e23 100644 --- a/felix/nftables/matchers_test.go +++ b/felix/nftables/matchers_test.go @@ -27,7 +27,7 @@ type containRule struct { expected knftables.Rule } -func (c *containRule) Match(actual interface{}) (success bool, err error) { +func (c *containRule) Match(actual any) (success bool, err error) { rules, ok := actual.([]*knftables.Rule) if !ok { return false, fmt.Errorf("Expected []*knftables.Rule, but got: %+v", reflect.TypeOf(actual).String()) @@ -45,12 +45,12 @@ func (c *containRule) Match(actual interface{}) (success bool, err error) { return false, nil } -func (c *containRule) FailureMessage(actual interface{}) (message string) { +func (c *containRule) FailureMessage(actual any) (message string) { j, _ := json.MarshalIndent(actual, "", " ") return fmt.Sprintf("Expected rules to contain %+v.\nRules: %s", c.expected, j) } -func (c *containRule) NegatedFailureMessage(actual interface{}) (message string) { +func (c *containRule) NegatedFailureMessage(actual any) (message string) { j, _ := json.MarshalIndent(actual, "", " ") return fmt.Sprintf("Expected rules to not contain %+v.\nRules: %s", c.expected, j) } @@ -82,7 +82,7 @@ func EqualRules(expected []knftables.Rule) types.GomegaMatcher { } } -func (e *equalRulesMatcher) Match(actual interface{}) (success bool, err error) { +func (e *equalRulesMatcher) Match(actual any) (success bool, err error) { rules, ok := actual.([]*knftables.Rule) if !ok { return false, fmt.Errorf("Expected []*knftables.Rule, but got: %+v", reflect.TypeOf(actual).String()) @@ -106,13 +106,13 @@ func (e *equalRulesMatcher) Match(actual interface{}) (success bool, err error) return true, nil } -func (e *equalRulesMatcher) FailureMessage(actual interface{}) (message string) { +func (e *equalRulesMatcher) FailureMessage(actual any) (message string) { exp, _ := json.MarshalIndent(e.expected, "", " ") act, _ := json.MarshalIndent(actual, "", " ") return fmt.Sprintf("Expected rules to equal %s, but got %s", exp, act) } -func (e *equalRulesMatcher) NegatedFailureMessage(actual interface{}) (message string) { +func (e *equalRulesMatcher) NegatedFailureMessage(actual any) (message string) { exp, _ := json.MarshalIndent(e.expected, "", " ") act, _ := json.MarshalIndent(actual, "", " ") diff --git a/felix/nftables/nftables_ut_suite_test.go b/felix/nftables/nftables_ut_suite_test.go index ac5f531cae0..ec5bb393de4 100644 --- a/felix/nftables/nftables_ut_suite_test.go +++ b/felix/nftables/nftables_ut_suite_test.go @@ -17,9 +17,8 @@ package nftables import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestNftablesUT(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/nftables_ut_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "nftables Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_nftables_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/nftables", suiteConfig, reporterConfig) } diff --git a/felix/nftables/rules_test.go b/felix/nftables/rules_test.go index 399c6fdf481..3258d32304a 100644 --- a/felix/nftables/rules_test.go +++ b/felix/nftables/rules_test.go @@ -17,7 +17,7 @@ package nftables import ( "strings" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "sigs.k8s.io/knftables" diff --git a/felix/nftables/table.go b/felix/nftables/table.go index d9cd823ba8e..c7c9e3c4fe3 100644 --- a/felix/nftables/table.go +++ b/felix/nftables/table.go @@ -255,7 +255,7 @@ type NftablesTable struct { type TableOptions struct { // NewDataplane is an optional function to override the creation of the knftables client, // used for testing. - NewDataplane func(knftables.Family, string) (knftables.Interface, error) + NewDataplane NewNftablesDataplaneFn // Disabled can be set to true when running in iptables mode, triggering a cleanup // of any Calico nftables content. @@ -940,7 +940,7 @@ func (t *NftablesTable) applyUpdates() error { // Make a pass over the dirty chains and generate a forward reference for any that we're about to update. // Writing a forward reference ensures that the chain exists and that it is empty. - t.dirtyChains.Iter(func(chainName string) error { + for chainName := range t.dirtyChains.All() { t.logCxt.WithField("chainName", chainName).Debug("Checking dirty chain") if _, present := t.desiredStateOfChain(chainName); !present { // About to delete this chain, flush it first to sever dependencies. @@ -955,12 +955,11 @@ func (t *NftablesTable) applyUpdates() error { }).Debug("Adding chain") tx.Add(&knftables.Chain{Name: chainName}) } - return nil - }) + } // Make a second pass over the dirty chains. This time, we write out the rule changes. newHashes := map[string][]string{} - t.dirtyChains.Iter(func(chainName string) error { + for chainName := range t.dirtyChains.All() { if chain, ok := t.desiredStateOfChain(chainName); ok { // Chain update or creation. Scan the chain against its previous hashes // and replace/append/delete as appropriate. @@ -1017,8 +1016,7 @@ func (t *NftablesTable) applyUpdates() error { } } } - return nil // Delay clearing the set until we've programmed nftables. - }) + } // Make a copy of our full rules map and keep track of all changes made while processing dirtyBaseChains. // When we've successfully updated nftables, we'll update our cache of chainToFullRules with this map. @@ -1029,7 +1027,7 @@ func (t *NftablesTable) applyUpdates() error { } // Now calculate nftables updates for our inserted and appended rules, which are used to hook top-level chains. - t.dirtyBaseChains.Iter(func(chainName string) error { + for chainName := range t.dirtyBaseChains.All() { previousHashes := t.chainToDataplaneHashes[chainName] newRules := newChainToFullRules[chainName] @@ -1038,7 +1036,7 @@ func (t *NftablesTable) applyUpdates() error { if reflect.DeepEqual(newChainHashes, previousHashes) { // Chain is in sync, skip to next one. - return nil + continue } // For simplicity, if we've discovered that we're out-of-sync, remove all our @@ -1075,9 +1073,8 @@ func (t *NftablesTable) applyUpdates() error { newHashes[chainName] = newChainHashes newChainToFullRules[chainName] = newRules - - return nil // Delay clearing the set until we've programmed nftables. - }) + // We don't delete the items from the set until after programming succeeds. + } // Now that chains + rules are added, we can add map elements. We do this afterwards in case // they reference chains that we've just added. @@ -1088,7 +1085,7 @@ func (t *NftablesTable) applyUpdates() error { // Do deletions at the end. This ensures that we don't try to delete any chains that // are still referenced (because we'll have removed the references in the modify pass // above). - t.dirtyChains.Iter(func(chainName string) error { + for chainName := range t.dirtyChains.All() { if _, ok := t.desiredStateOfChain(chainName); !ok { // Chain deletion t.logCxt.WithFields(logrus.Fields{ @@ -1097,8 +1094,7 @@ func (t *NftablesTable) applyUpdates() error { tx.Delete(&knftables.Chain{Name: chainName}) newHashes[chainName] = nil } - return nil // Delay clearing the set until we've programmed nftables. - }) + } // Delete any maps that may have been referenced by rules above. for _, m := range mapUpdates.MapsToDelete { @@ -1157,7 +1153,14 @@ func (t *NftablesTable) applyUpdates() error { // Invalidate the in-memory dataplane state so that we reload on the next write. This ensures we have the correct handles // in-memory for each of the objects we've just written. nftables requires an object's handle in order to // perform update or delete operations. - t.InvalidateDataplaneCache("post-write") + // + // Skip invalidation for disabled tables that have finished cleanup (no chains left in the dataplane). + // These tables only exist to remove leftover nftables state when switching to iptables mode. Once cleanup + // is confirmed, there's no need to reload state on every apply cycle — doing so would fork nft processes + // on every iteration for no useful work. + if !t.disabled || len(t.chainToDataplaneHashes) != 0 { + t.InvalidateDataplaneCache("post-write") + } return nil } diff --git a/felix/nftables/table_test.go b/felix/nftables/table_test.go index 9179234e23e..7a12dd0e0af 100644 --- a/felix/nftables/table_test.go +++ b/felix/nftables/table_test.go @@ -18,7 +18,7 @@ import ( "context" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "sigs.k8s.io/knftables" @@ -52,7 +52,7 @@ var _ = Describe("Table with an empty dataplane", func() { var featureDetector *environment.FeatureDetector var f *fakeNFT BeforeEach(func() { - newDataplane := func(fam knftables.Family, name string) (knftables.Interface, error) { + newDataplane := func(fam knftables.Family, name string, options ...knftables.Option) (knftables.Interface, error) { f = NewFake(fam, name) return f, nil } @@ -860,7 +860,7 @@ var _ = Describe("Insert early rules", func() { var featureDetector *environment.FeatureDetector var f *fakeNFT BeforeEach(func() { - newDataplane := func(fam knftables.Family, name string) (knftables.Interface, error) { + newDataplane := func(fam knftables.Family, name string, options ...knftables.Option) (knftables.Interface, error) { f = NewFake(fam, name) return f, nil } @@ -922,3 +922,122 @@ var _ = Describe("Insert early rules", func() { Expect(res).To(HaveLen(2)) }) }) + +var _ = Describe("Disabled table cache invalidation", func() { + var table *nftables.NftablesTable + var featureDetector *environment.FeatureDetector + var f *fakeNFT + + BeforeEach(func() { + newDataplane := func(fam knftables.Family, name string, options ...knftables.Option) (knftables.Interface, error) { + f = NewFake(fam, name) + return f, nil + } + featureDetector = environment.NewFeatureDetector(nil) + table = nftables.NewTable( + "calico", + 4, + rules.RuleHashPrefix, + featureDetector, + nftables.TableOptions{ + NewDataplane: newDataplane, + LookPathOverride: testutils.LookPathNoLegacy, + OpRecorder: logutils.NewSummarizer("test loop"), + Disabled: true, + }, + true, + ) + }) + + Context("when there are chains in the dataplane to clean up", func() { + BeforeEach(func() { + // Simulate pre-existing nftables chains left over from a previous nftables mode run. + tx := f.NewTransaction() + tx.Add(&knftables.Table{}) + tx.Add(&knftables.Chain{Name: "cali-foobar"}) + tx.Add(&knftables.Rule{Chain: "cali-foobar", Rule: "counter accept", Comment: ptr("cali:en3LGdDuVUQEgLl8;")}) + Expect(f.Run(context.Background(), tx)).NotTo(HaveOccurred()) + }) + + It("should clean up chains and then skip cache invalidation on subsequent applies", func() { + // First Apply: discovers the chain and cleans it up in one transaction. + // This also deletes the nftables table itself since it's disabled. + table.Apply() + + // Verify the table was deleted (List returns an error when the table doesn't exist). + _, err := f.Fake().List(context.Background(), "chain") + Expect(err).To(HaveOccurred(), "Expected table to be deleted after cleanup") + + // Record list call count after first apply. + listCallsAfterFirstApply := f.ListCallCount + + // Second Apply: cleanup is complete and chainToDataplaneHashes is empty, + // so cache should NOT have been invalidated — no new List calls. + table.Apply() + Expect(f.ListCallCount).To(Equal(listCallsAfterFirstApply), + "Expected no additional List calls after disabled table finished cleanup") + + // Third Apply: should still be a no-op with no List calls. + table.Apply() + Expect(f.ListCallCount).To(Equal(listCallsAfterFirstApply), + "Expected no additional List calls on repeated applies after cleanup") + }) + }) + + Context("when there are no chains in the dataplane", func() { + It("should not invalidate cache after apply (no cleanup needed)", func() { + // First Apply: empty dataplane, nothing to clean up. The first Apply will call + // loadDataplaneState to get in sync initially. + table.Apply() + + // Record the list call count after the first apply completes. + listCallsAfterFirstApply := f.ListCallCount + + // Second Apply: since the table is disabled and has no chains, cache should NOT + // have been invalidated, so no new List calls should be made. + table.Apply() + Expect(f.ListCallCount).To(Equal(listCallsAfterFirstApply), + "Expected no additional List calls because disabled table with no chains should skip cache invalidation") + }) + }) +}) + +var _ = Describe("Enabled table cache invalidation", func() { + var table *nftables.NftablesTable + var featureDetector *environment.FeatureDetector + var f *fakeNFT + + BeforeEach(func() { + newDataplane := func(fam knftables.Family, name string, options ...knftables.Option) (knftables.Interface, error) { + f = NewFake(fam, name) + return f, nil + } + featureDetector = environment.NewFeatureDetector(nil) + table = nftables.NewTable( + "calico", + 4, + rules.RuleHashPrefix, + featureDetector, + nftables.TableOptions{ + NewDataplane: newDataplane, + LookPathOverride: testutils.LookPathNoLegacy, + OpRecorder: logutils.NewSummarizer("test loop"), + }, + true, + ) + }) + + It("should always invalidate cache after apply", func() { + // First Apply: programs base chains. + table.Apply() + + // Record list call count after first apply. + listCallsAfterFirstApply := f.ListCallCount + + // Second Apply: cache should have been invalidated (enabled table always invalidates), + // so List should be called again to reload state. + table.Apply() + Expect(f.ListCallCount).To(BeNumerically(">", listCallsAfterFirstApply), + "Expected List to be called again because enabled table always invalidates cache after apply") + }) +}) diff --git a/felix/nftables/utils.go b/felix/nftables/utils.go new file mode 100644 index 00000000000..7fde4f6e9cf --- /dev/null +++ b/felix/nftables/utils.go @@ -0,0 +1,90 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. +// +// 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 nftables + +import ( + "context" + "time" + + log "github.com/sirupsen/logrus" + "sigs.k8s.io/knftables" +) + +// NewNftablesDataplaneFn is a function type that creates a new nftables dataplane interface. +type NewNftablesDataplaneFn func(knftables.Family, string, ...knftables.Option) (knftables.Interface, error) + +// KubeProxyNftablesEnabledFn returns a function that can be used to check if kube-proxy is running +// in nftables mode. It does this by checking for the presence of the nftables chains that +// kube-proxy creates when running in nftables mode. +func KubeProxyNftablesEnabledFn(newDataplane NewNftablesDataplaneFn) func() (bool, error) { + if newDataplane == nil { + newDataplane = knftables.New + } + + // Create v4 and v6 nftables interfaces. If either succeeds, we can use it to check for + // the presence of the kube-proxy nftables table. + nft, err := newDataplane(knftables.IPv4Family, "kube-proxy") + if err != nil { + // Don't return an error here - some systems may not have nftables support. We handle + // this case in the returned function. + log.WithError(err).Warn("Failed to create nftables interface to check kube-proxy mode.") + } + nftv6, err := newDataplane(knftables.IPv6Family, "kube-proxy") + if err != nil { + // Don't return an error here - some systems may not have nftables support. We handle + // this case in the returned function. + log.WithError(err).Warn("Failed to create IPv6 nftables interface to check kube-proxy mode.") + } + + // Common function to check for the presence of the kube-proxy nftables table for a given family. + checkTable := func(nft knftables.Interface) (bool, error) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + objs, err := nft.List(ctx, "chains") + if err != nil { + if knftables.IsNotFound(err) { + // Table not found, kube-proxy is not in nftables mode. + return false, nil + } + log.WithError(err).Warn("Failed to list nftables to check kube-proxy mode.") + return false, err + } + + // If there are any chains, kube-proxy is in nftables mode. + return len(objs) > 0, nil + } + + // Return a function that checks both IPv4 and IPv6 tables, depending on which are available. + return func() (bool, error) { + if nft != nil { + ipv4Enabled, err := checkTable(nft) + if err != nil { + // Failed to check IPv4 table. + return false, err + } else if ipv4Enabled { + // Kube-proxy is in nftables mode. + return true, nil + } + // kube-proxy not in nftables mode for IPv4, maybe check IPv6. + } + if nftv6 != nil { + // Check IPv6 table. + return checkTable(nftv6) + } + + // Neither IPv4 nor IPv6 nftables interfaces are available, kube-proxy must not be in nftables mode. + return false, nil + } +} diff --git a/felix/policysync/ipset.go b/felix/policysync/ipset.go index aee1f2022a4..6a404f4362b 100644 --- a/felix/policysync/ipset.go +++ b/felix/policysync/ipset.go @@ -67,10 +67,9 @@ func (s *ipSetInfo) deltaUpdate(update *proto.IPSetDeltaUpdate) { func (s *ipSetInfo) getIPSetUpdate() *proto.IPSetUpdate { u := &proto.IPSetUpdate{Id: s.SetID, Type: s.getProtoType()} - s.members.Iter(func(item ipsets.IPSetMember) error { + for item := range s.members.All() { u.Members = append(u.Members, item.String()) - return nil - }) + } return u } diff --git a/felix/policysync/ipset_test.go b/felix/policysync/ipset_test.go index 666d4b7d1f6..d6e267775bb 100644 --- a/felix/policysync/ipset_test.go +++ b/felix/policysync/ipset_test.go @@ -18,7 +18,7 @@ import ( "reflect" "strings" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/policysync" @@ -30,7 +30,7 @@ var _ = Describe("AddIPSetsRule", func() { It("should add all fields that end in IpSetIds", func() { r := &proto.Rule{} var fields []string - rt := reflect.TypeOf(r) + rt := reflect.TypeFor[*proto.Rule]() rv := reflect.Indirect(reflect.ValueOf(r)) for i := 0; i < rv.Type().NumField(); i++ { fn := rt.Elem().Field(i).Name diff --git a/felix/policysync/policysync_suite_test.go b/felix/policysync/policysync_suite_test.go index 278ba3c5fd0..675eeb7d893 100644 --- a/felix/policysync/policysync_suite_test.go +++ b/felix/policysync/policysync_suite_test.go @@ -17,9 +17,8 @@ package policysync_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestPolicysync(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/policysync_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Policysync Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_policysync_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/policysync", suiteConfig, reporterConfig) } diff --git a/felix/policysync/processor.go b/felix/policysync/processor.go index f4e90257c91..3016d941d77 100644 --- a/felix/policysync/processor.go +++ b/felix/policysync/processor.go @@ -32,8 +32,8 @@ import ( const MaxMembersPerMessage = 82200 type Processor struct { - Updates <-chan interface{} - JoinUpdates chan interface{} + Updates <-chan any + JoinUpdates chan any endpointsByID map[types.WorkloadEndpointID]*EndpointInfo policyByID map[types.PolicyID]*policyInfo profileByID map[types.ProfileID]*profileInfo @@ -73,12 +73,12 @@ type LeaveRequest struct { JoinMetadata } -func NewProcessor(updates <-chan interface{}) *Processor { +func NewProcessor(updates <-chan any) *Processor { return &Processor{ // Updates from the calculation graph. Updates: updates, // JoinUpdates from the new servers that have started. - JoinUpdates: make(chan interface{}, 10), + JoinUpdates: make(chan any, 10), endpointsByID: make(map[types.WorkloadEndpointID]*EndpointInfo), policyByID: make(map[types.PolicyID]*policyInfo), profileByID: make(map[types.ProfileID]*profileInfo), @@ -145,7 +145,8 @@ func (p *Processor) handleJoin(joinReq JoinRequest) { if p.receivedInSync { log.WithField("channel", ei.output).Debug("Already in sync with the datastore, sending in-sync message to client") ei.output <- &proto.ToDataplane{ - Payload: &proto.ToDataplane_InSync{InSync: &proto.InSync{}}} + Payload: &proto.ToDataplane_InSync{InSync: &proto.InSync{}}, + } } logCxt.Debug("Done with join") } @@ -177,7 +178,7 @@ func (p *Processor) handleLeave(leaveReq LeaveRequest) { ei.currentJoinUID = 0 } -func (p *Processor) handleDataplane(update interface{}) { +func (p *Processor) handleDataplane(update any) { log.WithFields(log.Fields{"update": update, "type": reflect.TypeOf(update)}).Debug("Dataplane update") switch update := update.(type) { case *proto.InSync: @@ -224,7 +225,8 @@ func (p *Processor) handleInSync(update *proto.InSync) { p.receivedInSync = true for _, ei := range p.updateableEndpoints() { ei.output <- &proto.ToDataplane{ - Payload: &proto.ToDataplane_InSync{InSync: &proto.InSync{}}} + Payload: &proto.ToDataplane_InSync{InSync: &proto.InSync{}}, + } } } @@ -263,7 +265,8 @@ func (p *Processor) maybeSyncEndpoint(ei *EndpointInfo) { p.syncAddedPolicies(ei) p.syncAddedProfiles(ei) ei.output <- &proto.ToDataplane{ - Payload: &proto.ToDataplane_WorkloadEndpointUpdate{WorkloadEndpointUpdate: ei.endpointUpd}} + Payload: &proto.ToDataplane_WorkloadEndpointUpdate{WorkloadEndpointUpdate: ei.endpointUpd}, + } p.syncRemovedPolicies(ei) p.syncRemovedProfiles(ei) doDel() @@ -567,7 +570,7 @@ func (p *Processor) updateableEndpoints() []*EndpointInfo { // referencesIPSet determines whether the endpoint's policies or profiles reference a given IPSet func (p *Processor) referencesIPSet(ei *EndpointInfo, id string) bool { - var found = false + found := false ei.iterateProfiles(func(pid types.ProfileID) bool { pi := p.profileByID[pid] if pi.referencesIPSet(id) { @@ -651,20 +654,18 @@ func (p *Processor) sendIPSetRemove(ei *EndpointInfo, id string) { // Perform the action on every policy on the Endpoint, breaking if the action returns true. func (ei *EndpointInfo) iteratePolicies(action func(id types.PolicyID) (stop bool)) { - var pId types.PolicyID seen := make(map[types.PolicyID]bool) for _, tier := range ei.endpointUpd.GetEndpoint().GetTiers() { - pId.Tier = tier.Name - for _, name := range tier.GetIngressPolicies() { - pId.Name = name + for _, pol := range tier.GetIngressPolicies() { // No need to check seen since we trust Calc graph to only list a policy once per tier. + pId := types.ProtoToPolicyID(pol) seen[pId] = true if action(pId) { return } } - for _, name := range tier.GetEgressPolicies() { - pId.Name = name + for _, pol := range tier.GetEgressPolicies() { + pId := types.ProtoToPolicyID(pol) if !seen[pId] { seen[pId] = true if action(pId) { @@ -721,7 +722,8 @@ func splitIPSetDeltaUpdate(update *proto.IPSetDeltaUpdate) []*proto.ToDataplane var out []*proto.ToDataplane for len(adds)+len(dels) > 0 { msg := &proto.ToDataplane{Payload: &proto.ToDataplane_IpsetDeltaUpdate{ - IpsetDeltaUpdate: &proto.IPSetDeltaUpdate{Id: update.GetId()}}} + IpsetDeltaUpdate: &proto.IPSetDeltaUpdate{Id: update.GetId()}, + }} out = append(out, msg) update := msg.GetIpsetDeltaUpdate() if len(adds) > 0 { @@ -758,10 +760,7 @@ func splitMembers(members []string) [][]string { } for remains > 0 { - numThis := MaxMembersPerMessage - if remains < numThis { - numThis = remains - } + numThis := min(remains, MaxMembersPerMessage) end := first + numThis sliceThis := members[first:end] out = append(out, sliceThis) diff --git a/felix/policysync/processor_test.go b/felix/policysync/processor_test.go index 350af37ffcc..0c2c1b89928 100644 --- a/felix/policysync/processor_test.go +++ b/felix/policysync/processor_test.go @@ -23,7 +23,7 @@ import ( "path" "reflect" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" gomegatypes "github.com/onsi/gomega/types" "google.golang.org/grpc" @@ -38,10 +38,12 @@ import ( "github.com/projectcalico/calico/pod2daemon/binder" ) -const IPSetName = "testset" -const ProfileName = "testpro" -const TierName = "testtier" -const PolicyName = "testpolicy" +const ( + IPSetName = "testset" + ProfileName = "testpro" + tierName = "testtier" + PolicyName = "testpolicy" +) func init() { resolver.SetDefaultScheme("passthrough") @@ -49,7 +51,7 @@ func init() { var _ = Describe("Processor", func() { var uut *policysync.Processor - var updates chan interface{} + var updates chan any var updateServiceAccount func(name, namespace string) var removeServiceAccount func(name, namespace string) var updateNamespace func(name string) @@ -58,7 +60,7 @@ var _ = Describe("Processor", func() { var leave func(jm policysync.JoinMetadata) BeforeEach(func() { - updates = make(chan interface{}) + updates = make(chan any) uut = policysync.NewProcessor(updates) updateServiceAccount = func(name, namespace string) { @@ -103,15 +105,12 @@ var _ = Describe("Processor", func() { }) Context("with Processor started", func() { - BeforeEach(func() { uut.Start() }) Describe("ServiceAccount update/remove", func() { - Context("updates before any join", func() { - BeforeEach(func() { // Add, delete, re-add updateServiceAccount("test_serviceaccount0", "test_namespace0") @@ -133,7 +132,7 @@ var _ = Describe("Processor", func() { BeforeEach(func() { output, _ = join("test", 1) - for i := 0; i < 3; i++ { + for i := range 3 { msg := <-output accounts[i] = types.ProtoToServiceAccountID( msg.GetServiceAccountUpdate().GetId(), @@ -143,11 +142,14 @@ var _ = Describe("Processor", func() { It("should get 3 updates", func() { Expect(accounts).To(ContainElement(types.ServiceAccountID{ - Name: "test_serviceaccount0", Namespace: "test_namespace0"})) + Name: "test_serviceaccount0", Namespace: "test_namespace0", + })) Expect(accounts).To(ContainElement(types.ServiceAccountID{ - Name: "test_serviceaccount0", Namespace: "test_namespace1"})) + Name: "test_serviceaccount0", Namespace: "test_namespace1", + })) Expect(accounts).To(ContainElement(types.ServiceAccountID{ - Name: "test_serviceaccount1", Namespace: "test_namespace0"})) + Name: "test_serviceaccount1", Namespace: "test_namespace0", + })) }) It("should pass updates", func() { @@ -156,7 +158,6 @@ var _ = Describe("Processor", func() { Expect(googleproto.Equal( msg.GetServiceAccountUpdate().GetId(), &proto.ServiceAccountID{Name: "t0", Namespace: "t5"}, )).To(BeTrue()) - }) It("should pass removes", func() { @@ -174,7 +175,7 @@ var _ = Describe("Processor", func() { var output [2]chan *proto.ToDataplane BeforeEach(func() { - for i := 0; i < 2; i++ { + for i := range 2 { w := fmt.Sprintf("test%d", i) d := types.WorkloadEndpointIDToProto(testId(w)) output[i], _ = join(w, uint64(i)) @@ -227,14 +228,11 @@ var _ = Describe("Processor", func() { }, })).To(BeTrue()) }) - }) }) Describe("Namespace update/remove", func() { - Context("updates before any join", func() { - BeforeEach(func() { // Add, delete, re-add updateNamespace("test_namespace0") @@ -256,7 +254,7 @@ var _ = Describe("Processor", func() { BeforeEach(func() { output, _ = join("test", 1) - for i := 0; i < 3; i++ { + for i := range 3 { msg := <-output accounts[i] = types.ProtoToNamespaceID( msg.GetNamespaceUpdate().GetId(), @@ -288,7 +286,7 @@ var _ = Describe("Processor", func() { var output [2]chan *proto.ToDataplane BeforeEach(func() { - for i := 0; i < 2; i++ { + for i := range 2 { w := fmt.Sprintf("test%d", i) d := types.WorkloadEndpointIDToProto(testId(w)) output[i], _ = join(w, uint64(i)) @@ -333,12 +331,10 @@ var _ = Describe("Processor", func() { }, })).To(BeTrue()) }) - }) }) Describe("IP Set updates", func() { - Context("with two joined endpoints, one with active profile", func() { var refdOutput chan *proto.ToDataplane var unrefdOutput chan *proto.ToDataplane @@ -348,7 +344,7 @@ var _ = Describe("Processor", func() { var proUpd *proto.ActiveProfileUpdate var ipSetUpd *proto.IPSetUpdate - BeforeEach(func(done Done) { + BeforeEach(func() { refdId = testId("refd") refdOutput, _ = join("refd", 1) unrefdId = testId("unrefd") @@ -409,22 +405,18 @@ var _ = Describe("Processor", func() { g := <-unrefdOutput Expect(googleproto.Equal(g.GetWorkloadEndpointUpdate(), u)).To(BeTrue()) } - - close(done) }) - It("should send IPSetUpdate to only to ref'd endpoint", func(done Done) { + It("should send IPSetUpdate to only to ref'd endpoint", func() { msg := updateIpSet(IPSetName, 2) updates <- msg g := <-refdOutput Expect(googleproto.Equal(g, &proto.ToDataplane{Payload: &proto.ToDataplane_IpsetUpdate{IpsetUpdate: msg}})).To(BeTrue()) assertInactiveNoUpdate() - close(done) }) - It("should send IPSetDeltaUpdate to ref'd endpoint", func(done Done) { - + It("should send IPSetDeltaUpdate to ref'd endpoint", func() { // Try combinations of adds, removes, and both to ensure the splitting logic // doesn't split these up strangely. @@ -432,7 +424,8 @@ var _ = Describe("Processor", func() { updates <- msg2 g := <-refdOutput Expect(googleproto.Equal(g, &proto.ToDataplane{ - Payload: &proto.ToDataplane_IpsetDeltaUpdate{IpsetDeltaUpdate: msg2}})).To(BeTrue()) + Payload: &proto.ToDataplane_IpsetDeltaUpdate{IpsetDeltaUpdate: msg2}, + })).To(BeTrue()) msg2 = deltaUpdateIpSet(IPSetName, 2, 0) updates <- msg2 @@ -451,11 +444,9 @@ var _ = Describe("Processor", func() { Expect(g.GetIpsetDeltaUpdate().GetRemovedMembers()).To(Equal(msg2.RemovedMembers)) assertInactiveNoUpdate() - - close(done) }) - It("should send IPSetUpdate when endpoint newly refs wep update", func(done Done) { + It("should send IPSetUpdate when endpoint newly refs wep update", func() { wepUpd := &proto.WorkloadEndpointUpdate{ Id: types.WorkloadEndpointIDToProto(unrefdId), Endpoint: &proto.WorkloadEndpoint{ProfileIds: []string{ProfileName}}, @@ -469,11 +460,9 @@ var _ = Describe("Processor", func() { g = <-unrefdOutput Expect(googleproto.Equal(g.GetWorkloadEndpointUpdate(), wepUpd)).To(BeTrue()) - - close(done) }) - It("should send IPSetRemove when endpoint stops ref wep update", func(done Done) { + It("should send IPSetRemove when endpoint stops ref wep update", func() { wepUpd := &proto.WorkloadEndpointUpdate{ Id: types.WorkloadEndpointIDToProto(refdId), Endpoint: &proto.WorkloadEndpoint{ProfileIds: []string{}}, @@ -497,10 +486,9 @@ var _ = Describe("Processor", func() { Expect(googleproto.Equal(g.GetWorkloadEndpointUpdate(), wepUpd)).To(BeTrue()) assertInactiveNoUpdate() - close(done) }) - It("should send IPSetUpdate when endpoint newly refs profile update", func(done Done) { + It("should send IPSetUpdate when endpoint newly refs profile update", func() { newSetName := "new-set" updates <- updateIpSet(newSetName, 6) pu := &proto.ActiveProfileUpdate{ @@ -522,11 +510,9 @@ var _ = Describe("Processor", func() { Expect(googleproto.Equal(g.GetActiveProfileUpdate(), pu)).To(BeTrue()) assertInactiveNoUpdate() - - close(done) }) - It("should send IPSetRemove when endpoint stops ref profile update", func(done Done) { + It("should send IPSetRemove when endpoint stops ref profile update", func() { pu := &proto.ActiveProfileUpdate{ Id: &proto.ProfileID{Name: ProfileName}, Profile: &proto.Profile{ @@ -544,11 +530,9 @@ var _ = Describe("Processor", func() { Expect(googleproto.Equal(g.GetIpsetRemove(), &proto.IPSetRemove{Id: IPSetName})).To(BeTrue()) assertInactiveNoUpdate() - - close(done) }) - It("should send Update & remove profile update changes IPSet", func(done Done) { + It("should send Update & remove profile update changes IPSet", func() { newSetName := "new-set" updates <- updateIpSet(newSetName, 6) pu := &proto.ActiveProfileUpdate{ @@ -574,16 +558,16 @@ var _ = Describe("Processor", func() { Expect(googleproto.Equal(g.GetIpsetRemove(), &proto.IPSetRemove{Id: IPSetName})).To(BeTrue()) assertInactiveNoUpdate() - - close(done) }) - It("should send IPSetUpdate/Remove when endpoint newly refs policy update", func(done Done) { + It("should send IPSetUpdate/Remove when endpoint newly refs policy update", func() { // Create the policy without the ref, and link it to the unref'd WEP. - policyID := &proto.PolicyID{Tier: "tier0", Name: "testpolicy"} + policyID := &proto.PolicyID{Name: "testpolicy"} + tier := "tier0" pu := &proto.ActivePolicyUpdate{ Id: policyID, Policy: &proto.Policy{ + Tier: tier, OutboundRules: []*proto.Rule{ {Action: "allow", SrcIpSetIds: []string{}}, }, @@ -595,8 +579,8 @@ var _ = Describe("Processor", func() { Endpoint: &proto.WorkloadEndpoint{ Tiers: []*proto.TierInfo{ { - Name: policyID.Tier, - IngressPolicies: []string{policyID.Name}, + Name: tier, + IngressPolicies: []*proto.PolicyID{policyID}, }, }, }, @@ -640,15 +624,16 @@ var _ = Describe("Processor", func() { Expect(googleproto.Equal(g.GetActivePolicyUpdate(), pu)).To(BeTrue()) g = <-unrefdOutput Expect(googleproto.Equal(g.GetIpsetRemove(), &proto.IPSetRemove{Id: IPSetName})).To(BeTrue()) - close(done) }) - It("should send IPSetUpdate/Remove when policy changes IPset", func(done Done) { + It("should send IPSetUpdate/Remove when policy changes IPset", func() { // Create policy referencing the existing IPSet and link to the unreferenced WEP - policyID := &proto.PolicyID{Tier: "tier0", Name: "testpolicy"} + policyID := &proto.PolicyID{Name: "testpolicy"} + tier := "tier0" pu := &proto.ActivePolicyUpdate{ Id: policyID, Policy: &proto.Policy{ + Tier: tier, OutboundRules: []*proto.Rule{ {Action: "allow", SrcIpSetIds: []string{IPSetName}}, }, @@ -660,8 +645,8 @@ var _ = Describe("Processor", func() { Endpoint: &proto.WorkloadEndpoint{ Tiers: []*proto.TierInfo{ { - Name: policyID.Tier, - IngressPolicies: []string{policyID.Name}, + Name: tier, + IngressPolicies: []*proto.PolicyID{policyID}, }, }, }, @@ -702,8 +687,6 @@ var _ = Describe("Processor", func() { g = <-unrefdOutput Expect(g.GetIpsetUpdate().GetId()).To(Equal(newSetName)) Expect(g.GetIpsetUpdate().GetMembers()).To(HaveLen(12)) - - close(done) }) }) @@ -747,7 +730,7 @@ var _ = Describe("Processor", func() { var clientCancel func() var syncStream proto.PolicySync_SyncClient - BeforeEach(func(done Done) { + BeforeEach(func() { wepId = testId("default/withsync") opts := getDialOptions() @@ -790,11 +773,9 @@ var _ = Describe("Processor", func() { g, err = syncStream.Recv() Expect(err).ToNot(HaveOccurred()) Expect(googleproto.Equal(g.GetWorkloadEndpointUpdate(), wepUpd)).To(BeTrue()) + }) - close(done) - }, 2) - - It("should split large IPSetUpdate", func(done Done) { + It("should split large IPSetUpdate", func() { msg := updateIpSet(IPSetName, 82250) By("sending a large IPSetUpdate") updates <- msg @@ -808,10 +789,9 @@ var _ = Describe("Processor", func() { out, err = syncStream.Recv() Expect(err).ToNot(HaveOccurred()) Expect(out.GetIpsetDeltaUpdate().GetAddedMembers()).To(HaveLen(50)) - close(done) - }, 5) + }) - It("should split IpSetDeltaUpdates with both large adds and removes", func(done Done) { + It("should split IpSetDeltaUpdates with both large adds and removes", func() { msg2 := deltaUpdateIpSet(IPSetName, 82250, 82250) By("sending a large IPSetDeltaUpdate") updates <- msg2 @@ -833,11 +813,9 @@ var _ = Describe("Processor", func() { Expect(err).ToNot(HaveOccurred()) Expect(out.GetIpsetDeltaUpdate().GetAddedMembers()).To(HaveLen(0)) Expect(out.GetIpsetDeltaUpdate().GetRemovedMembers()).To(HaveLen(82200)) + }) - close(done) - }, 5) - - It("should split IpSetDeltaUpdates with large adds", func(done Done) { + It("should split IpSetDeltaUpdates with large adds", func() { msg2 := deltaUpdateIpSet(IPSetName, 82250, 0) By("sending a large IPSetDeltaUpdate") updates <- msg2 @@ -853,11 +831,9 @@ var _ = Describe("Processor", func() { Expect(err).ToNot(HaveOccurred()) Expect(out.GetIpsetDeltaUpdate().GetAddedMembers()).To(HaveLen(50)) Expect(out.GetIpsetDeltaUpdate().GetRemovedMembers()).To(HaveLen(0)) + }) - close(done) - }, 5) - - It("should split IpSetDeltaUpdates with large removes", func(done Done) { + It("should split IpSetDeltaUpdates with large removes", func() { msg2 := deltaUpdateIpSet(IPSetName, 0, 82250) By("sending a large IPSetDeltaUpdate") updates <- msg2 @@ -873,9 +849,7 @@ var _ = Describe("Processor", func() { Expect(err).ToNot(HaveOccurred()) Expect(out.GetIpsetDeltaUpdate().GetAddedMembers()).To(HaveLen(0)) Expect(out.GetIpsetDeltaUpdate().GetRemovedMembers()).To(HaveLen(82200)) - - close(done) - }, 5) + }) AfterEach(func() { clientCancel() @@ -886,7 +860,6 @@ var _ = Describe("Processor", func() { }) Describe("Profile & Policy updates", func() { - Context("with two joined endpoints", func() { var output [2]chan *proto.ToDataplane var wepID [2]types.WorkloadEndpointID @@ -903,7 +876,7 @@ var _ = Describe("Processor", func() { Expect(googleproto.Equal(g.GetWorkloadEndpointUpdate(), wepu)).To(BeTrue()) } - for i := 0; i < 2; i++ { + for i := range 2 { w := fmt.Sprintf("test%d", i) wepID[i] = testId(w) output[i], _ = join(w, uint64(i)) @@ -911,11 +884,10 @@ var _ = Describe("Processor", func() { // Ensure the joins are completed by sending a workload endpoint for each. assertNoUpdate(i) } - }) Context("with active profile", func() { - var profileID = proto.ProfileID{Name: ProfileName} + profileID := proto.ProfileID{Name: ProfileName} var proUpdate *proto.ActiveProfileUpdate BeforeEach(func() { @@ -925,7 +897,7 @@ var _ = Describe("Processor", func() { updates <- proUpdate }) - It("should add & remove profile when ref'd or not by WEP", func(done Done) { + It("should add & remove profile when ref'd or not by WEP", func() { msg := &proto.WorkloadEndpointUpdate{ Id: types.WorkloadEndpointIDToProto(wepID[0]), Endpoint: &proto.WorkloadEndpoint{ProfileIds: []string{ProfileName}}, @@ -958,11 +930,9 @@ var _ = Describe("Processor", func() { updates <- msg g = <-output[0] Expect(googleproto.Equal(g.GetWorkloadEndpointUpdate(), msg)).To(BeTrue()) - - close(done) }) - It("should add new & remove old when ref changes", func(done Done) { + It("should add new & remove old when ref changes", func() { // Add new profile newName := "new-profile-name" newProfileID := proto.ProfileID{Name: newName} @@ -1005,13 +975,11 @@ var _ = Describe("Processor", func() { updates <- msg2 g = <-output[0] Expect(googleproto.Equal(g.GetWorkloadEndpointUpdate(), msg2)).To(BeTrue()) - - close(done) }) }) Context("with active policy", func() { - var policyID = proto.PolicyID{Tier: TierName, Name: PolicyName} + policyID := proto.PolicyID{Name: PolicyName} var polUpd *proto.ActivePolicyUpdate BeforeEach(func() { @@ -1021,13 +989,13 @@ var _ = Describe("Processor", func() { updates <- polUpd }) - It("should add & remove policy when ref'd or not by WEP", func(done Done) { + It("should add & remove policy when ref'd or not by WEP", func() { msg := &proto.WorkloadEndpointUpdate{ Id: types.WorkloadEndpointIDToProto(wepID[0]), Endpoint: &proto.WorkloadEndpoint{Tiers: []*proto.TierInfo{ { - Name: TierName, - IngressPolicies: []string{PolicyName}, + Name: tierName, + IngressPolicies: []*proto.PolicyID{&policyID}, }, }}, } @@ -1043,7 +1011,7 @@ var _ = Describe("Processor", func() { Id: types.WorkloadEndpointIDToProto(wepID[0]), Endpoint: &proto.WorkloadEndpoint{Tiers: []*proto.TierInfo{ { - Name: TierName, + Name: tierName, }, }}, } @@ -1063,14 +1031,12 @@ var _ = Describe("Processor", func() { updates <- msg g = <-output[0] Expect(googleproto.Equal(g.GetWorkloadEndpointUpdate(), msg)).To(BeTrue()) - - close(done) }) - It("should add new & remove old when ref changes", func(done Done) { + It("should add new & remove old when ref changes", func() { // Add new policy newName := "new-policy-name" - newPolicyID := proto.PolicyID{Tier: TierName, Name: newName} + newPolicyID := proto.PolicyID{Name: newName} msg := &proto.ActivePolicyUpdate{Id: &newPolicyID} updates <- msg @@ -1078,8 +1044,8 @@ var _ = Describe("Processor", func() { Id: types.WorkloadEndpointIDToProto(wepID[0]), Endpoint: &proto.WorkloadEndpoint{Tiers: []*proto.TierInfo{ { - Name: TierName, - EgressPolicies: []string{PolicyName}, + Name: tierName, + EgressPolicies: []*proto.PolicyID{&policyID}, }, }}, } @@ -1095,8 +1061,8 @@ var _ = Describe("Processor", func() { Id: types.WorkloadEndpointIDToProto(wepID[0]), Endpoint: &proto.WorkloadEndpoint{Tiers: []*proto.TierInfo{ { - Name: TierName, - EgressPolicies: []string{newName}, + Name: tierName, + EgressPolicies: []*proto.PolicyID{&newPolicyID}, }, }}, } @@ -1118,16 +1084,13 @@ var _ = Describe("Processor", func() { updates <- msg2 g = <-output[0] Expect(googleproto.Equal(g.GetWorkloadEndpointUpdate(), msg2)).To(BeTrue()) - - close(done) }) - }) }) Context("with profile & wep added before joining", func() { - var profileID = proto.ProfileID{Name: ProfileName} - var wepId = testId("test") + profileID := proto.ProfileID{Name: ProfileName} + wepId := testId("test") var wepUpd *proto.WorkloadEndpointUpdate var proUpdate *proto.ActiveProfileUpdate @@ -1143,7 +1106,7 @@ var _ = Describe("Processor", func() { updates <- wepUpd }) - It("should sync profile & wep when wep joins", func(done Done) { + It("should sync profile & wep when wep joins", func() { output, _ := join("test", 1) g := <-output @@ -1151,11 +1114,9 @@ var _ = Describe("Processor", func() { g = <-output Expect(googleproto.Equal(g.GetWorkloadEndpointUpdate(), wepUpd)).To(BeTrue()) - - close(done) }) - It("should resync profile & wep", func(done Done) { + It("should resync profile & wep", func() { output, jm := join("test", 1) g := <-output Expect(googleproto.Equal(g.GetActiveProfileUpdate(), proUpdate)).To(BeTrue()) @@ -1170,11 +1131,9 @@ var _ = Describe("Processor", func() { Expect(googleproto.Equal(g.GetActiveProfileUpdate(), proUpdate)).To(BeTrue()) g = <-output Expect(googleproto.Equal(g.GetWorkloadEndpointUpdate(), wepUpd)).To(BeTrue()) - - close(done) }) - It("should not resync removed profile", func(done Done) { + It("should not resync removed profile", func() { output, jm := join("test", 1) g := <-output Expect(googleproto.Equal(g.GetActiveProfileUpdate(), proUpdate)).To(BeTrue()) @@ -1194,15 +1153,12 @@ var _ = Describe("Processor", func() { output, _ = join("test", 2) g = <-output Expect(googleproto.Equal(g.GetWorkloadEndpointUpdate(), wepUpd2)).To(BeTrue()) - - close(done) }) - }) Context("with policy & wep added before joining", func() { - var policyID = proto.PolicyID{Tier: TierName, Name: PolicyName} - var wepId = testId("test") + policyID := proto.PolicyID{Name: PolicyName} + wepId := testId("test") var wepUpd *proto.WorkloadEndpointUpdate var polUpd *proto.ActivePolicyUpdate @@ -1215,15 +1171,15 @@ var _ = Describe("Processor", func() { Id: types.WorkloadEndpointIDToProto(wepId), Endpoint: &proto.WorkloadEndpoint{Tiers: []*proto.TierInfo{ { - Name: TierName, - EgressPolicies: []string{PolicyName}, + Name: tierName, + EgressPolicies: []*proto.PolicyID{&policyID}, }, }}, } updates <- wepUpd }) - It("should sync policy & wep when wep joins", func(done Done) { + It("should sync policy & wep when wep joins", func() { output, _ := join("test", 1) g := <-output @@ -1231,11 +1187,9 @@ var _ = Describe("Processor", func() { g = <-output Expect(googleproto.Equal(g.GetWorkloadEndpointUpdate(), wepUpd)).To(BeTrue()) - - close(done) }) - It("should resync policy & wep", func(done Done) { + It("should resync policy & wep", func() { output, jm := join("test", 1) g := <-output Expect(googleproto.Equal(g.GetActivePolicyUpdate(), polUpd)).To(BeTrue()) @@ -1250,11 +1204,9 @@ var _ = Describe("Processor", func() { Expect(googleproto.Equal(g.GetActivePolicyUpdate(), polUpd)).To(BeTrue()) g = <-output Expect(googleproto.Equal(g.GetWorkloadEndpointUpdate(), wepUpd)).To(BeTrue()) - - close(done) }) - It("should not resync removed policy", func(done Done) { + It("should not resync removed policy", func() { output, jm := join("test", 1) g := <-output Expect(googleproto.Equal(g.GetActivePolicyUpdate(), polUpd)).To(BeTrue()) @@ -1273,16 +1225,13 @@ var _ = Describe("Processor", func() { output, _ = join("test", 2) g = <-output Expect(googleproto.Equal(g.GetWorkloadEndpointUpdate(), wepUpd2)).To(BeTrue()) - - close(done) }) }) }) Describe("join / leave processing", func() { - Context("with WEP before any join", func() { - var wepId = testId("test") + wepId := testId("test") var wepUpd *proto.WorkloadEndpointUpdate BeforeEach(func() { @@ -1292,7 +1241,7 @@ var _ = Describe("Processor", func() { updates <- wepUpd }) - It("should close old channel on new join", func(done Done) { + It("should close old channel on new join", func() { oldChan, _ := join("test", 1) g := <-oldChan Expect(googleproto.Equal(g.GetWorkloadEndpointUpdate(), wepUpd)).To(BeTrue()) @@ -1302,11 +1251,9 @@ var _ = Describe("Processor", func() { Expect(googleproto.Equal(g.GetWorkloadEndpointUpdate(), wepUpd)).To(BeTrue()) Expect(oldChan).To(BeClosed()) - - close(done) }) - It("should ignore stale leave requests", func(done Done) { + It("should ignore stale leave requests", func() { oldChan, oldMeta := join("test", 1) g := <-oldChan Expect(googleproto.Equal(g.GetWorkloadEndpointUpdate(), wepUpd)).To(BeTrue()) @@ -1321,11 +1268,9 @@ var _ = Describe("Processor", func() { updates <- wepUpd g = <-newChan Expect(googleproto.Equal(g.GetWorkloadEndpointUpdate(), wepUpd)).To(BeTrue()) - - close(done) }) - It("should close active connection on clean leave", func(done Done) { + It("should close active connection on clean leave", func() { c, m := join("test", 1) g := <-c @@ -1339,8 +1284,6 @@ var _ = Describe("Processor", func() { leave(m) Eventually(c).Should(BeClosed()) - - close(done) }) }) @@ -1352,18 +1295,17 @@ var _ = Describe("Processor", func() { }) Describe("InSync processing", func() { - It("should send InSync on all open outputs", func(done Done) { + It("should send InSync on all open outputs", func() { var c [2]chan *proto.ToDataplane - for i := 0; i < 2; i++ { + for i := range 2 { c[i], _ = join(fmt.Sprintf("test%d", i), uint64(i)) } is := &proto.InSync{} updates <- is - for i := 0; i < 2; i++ { + for i := range 2 { g := <-c[i] Expect(googleproto.Equal(g.GetInSync(), is)).To(BeTrue()) } - close(done) }) }) }) @@ -1383,7 +1325,7 @@ func updateIpSet(id string, num int) *proto.IPSetUpdate { Type: proto.IPSetUpdate_IP_AND_PORT, Members: []string{}, } - for i := 0; i < num; i++ { + for i := range num { msg.Members = append(msg.Members, makeIPAndPort(i)) } return msg @@ -1400,7 +1342,7 @@ func deltaUpdateIpSet(id string, add, del int) *proto.IPSetDeltaUpdate { msg := &proto.IPSetDeltaUpdate{ Id: id, } - for i := 0; i < add; i++ { + for i := range add { msg.AddedMembers = append(msg.AddedMembers, makeIPAndPort(i)) } for i := add; i < add+del; i++ { @@ -1420,7 +1362,8 @@ func makeIPAndPort(i int) string { func getDialOptions() []grpc.DialOption { return []grpc.DialOption{ grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithContextDialer(getDialer("unix"))} + grpc.WithContextDialer(getDialer("unix")), + } } func getDialer(proto string) func(context.Context, string) (net.Conn, error) { @@ -1445,12 +1388,12 @@ func openListener(dir string) net.Listener { return lis } -type testCreds struct { -} +type testCreds struct{} func (t testCreds) ClientHandshake(cxt context.Context, _ string, conn net.Conn) (net.Conn, credentials.AuthInfo, error) { return conn, binder.Credentials{}, errors.New("client handshake unsupported") } + func (t testCreds) ServerHandshake(conn net.Conn) (net.Conn, credentials.AuthInfo, error) { return conn, binder.Credentials{ Uid: "test", @@ -1474,16 +1417,16 @@ func (t testCreds) Clone() credentials.TransportCredentials { func (t testCreds) OverrideServerName(string) error { return nil } -func HavePayload(expected interface{}) gomegatypes.GomegaMatcher { +func HavePayload(expected any) gomegatypes.GomegaMatcher { return &payloadMatcher{equal: Equal(expected)} } type payloadMatcher struct { equal gomegatypes.GomegaMatcher - payload interface{} + payload any } -func (p *payloadMatcher) Match(actual interface{}) (success bool, err error) { +func (p *payloadMatcher) Match(actual any) (success bool, err error) { td, ok := actual.(*proto.ToDataplane) if !ok { return false, fmt.Errorf("HasPayload expects a *proto.ToDataplane") @@ -1492,10 +1435,10 @@ func (p *payloadMatcher) Match(actual interface{}) (success bool, err error) { return p.equal.Match(p.payload) } -func (p *payloadMatcher) FailureMessage(actual interface{}) (message string) { +func (p *payloadMatcher) FailureMessage(actual any) (message string) { return p.equal.FailureMessage(p.payload) } -func (p *payloadMatcher) NegatedFailureMessage(actual interface{}) (message string) { +func (p *payloadMatcher) NegatedFailureMessage(actual any) (message string) { return p.equal.NegatedFailureMessage(p.payload) } diff --git a/felix/policysync/server.go b/felix/policysync/server.go index 76fcb393e9c..06972c73d16 100644 --- a/felix/policysync/server.go +++ b/felix/policysync/server.go @@ -40,12 +40,12 @@ const OutputQueueLen = 100 // credentials present in the gRPC request. type Server struct { proto.UnimplementedPolicySyncServer - JoinUpdates chan<- interface{} + JoinUpdates chan<- any stats chan<- *proto.DataplaneStats nextJoinUID func() uint64 } -func NewServer(joins chan<- interface{}, collector collector.Collector, allocUID func() uint64) *Server { +func NewServer(joins chan<- any, collector collector.Collector, allocUID func() uint64) *Server { var stats chan<- *proto.DataplaneStats if collector != nil { stats = collector.ReportingChannel() diff --git a/felix/policysync/server_test.go b/felix/policysync/server_test.go index 1ff78f7f325..22af4c4e7a7 100644 --- a/felix/policysync/server_test.go +++ b/felix/policysync/server_test.go @@ -19,7 +19,7 @@ import ( "errors" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" @@ -32,10 +32,10 @@ import ( var _ = Describe("Server", func() { var uut *policysync.Server - var joins chan interface{} + var joins chan any BeforeEach(func() { - joins = make(chan interface{}) + joins = make(chan any) uut = policysync.NewServer(joins, nil, policysync.NewUIDAllocator().NextUID) }) @@ -47,7 +47,7 @@ var _ = Describe("Server", func() { var output chan *proto.ToDataplane syncDone := make(chan bool) - BeforeEach(func(done Done) { + BeforeEach(func() { output = make(chan *proto.ToDataplane) stream = &testSyncStream{output: output} go func() { @@ -58,10 +58,9 @@ var _ = Describe("Server", func() { jr := j.(policysync.JoinRequest) Expect(jr.EndpointID.WorkloadId).To(Equal(WorkloadID)) updates = jr.C - close(done) }) - It("should stream messages", func(done Done) { + It("should stream messages", func() { msgs := []*proto.ToDataplane{ {Payload: &proto.ToDataplane_WorkloadEndpointUpdate{}}, {Payload: &proto.ToDataplane_InSync{}}, @@ -71,28 +70,24 @@ var _ = Describe("Server", func() { g := <-output Expect(googleproto.Equal(g, msg)).To(BeTrue()) } - - close(done) }) Context("with unstreamed updates", func() { - BeforeEach(func(done Done) { + BeforeEach(func() { // Queue up 10 messages. This should not block because the updates channel should be buffered. - for i := 0; i < 10; i++ { + for range 10 { updates <- &proto.ToDataplane{} } - close(done) }) Context("after error on stream", func() { - BeforeEach(func(done Done) { + BeforeEach(func() { stream.sendErr = true <-output - close(done) }) - It("should drain updates channel, send leave request and end Sync", func(done Done) { - for i := 0; i < 10; i++ { + It("should drain updates channel, send leave request and end Sync", func() { + for range 10 { updates <- &proto.ToDataplane{} } j := <-joins @@ -100,7 +95,6 @@ var _ = Describe("Server", func() { Expect(lr.EndpointID.WorkloadId).To(Equal(WorkloadID)) close(updates) <-syncDone - close(done) }) }) @@ -110,15 +104,14 @@ var _ = Describe("Server", func() { close(updates) }) - It("send pending updates, leave request and end Sync", func(done Done) { - for i := 0; i < 10; i++ { + It("send pending updates, leave request and end Sync", func() { + for range 10 { <-output } j := <-joins lr := j.(policysync.LeaveRequest) Expect(lr.EndpointID.WorkloadId).To(Equal(WorkloadID)) <-syncDone - close(done) }) }) }) @@ -155,11 +148,11 @@ func (*testSyncStream) Context() context.Context { return &testContext{} } -func (*testSyncStream) SendMsg(m interface{}) error { +func (*testSyncStream) SendMsg(m any) error { panic("not implemented") } -func (*testSyncStream) RecvMsg(m interface{}) error { +func (*testSyncStream) RecvMsg(m any) error { panic("not implemented") } @@ -181,7 +174,7 @@ const WorkloadName = "servertest" const Namespace = "default" const WorkloadID = "default/servertest" -func (*testContext) Value(key interface{}) interface{} { +func (*testContext) Value(key any) any { // Server accesses the peer value only. peer := &peer.Peer{AuthInfo: binder.Credentials{ Uid: "test", diff --git a/felix/proto/felixbackend.pb.go b/felix/proto/felixbackend.pb.go index 8309d3ee8e6..8ee095fc754 100644 --- a/felix/proto/felixbackend.pb.go +++ b/felix/proto/felixbackend.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 -// protoc v6.32.0 +// protoc-gen-go v1.36.11 +// protoc v6.33.4 // source: felixbackend.proto package proto @@ -1991,8 +1991,9 @@ func (x *ActivePolicyRemove) GetId() *PolicyID { type PolicyID struct { state protoimpl.MessageState `protogen:"open.v1"` - Tier string `protobuf:"bytes,1,opt,name=tier,proto3" json:"tier,omitempty"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Namespace string `protobuf:"bytes,3,opt,name=namespace,proto3" json:"namespace,omitempty"` + Kind string `protobuf:"bytes,4,opt,name=kind,proto3" json:"kind,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -2027,16 +2028,23 @@ func (*PolicyID) Descriptor() ([]byte, []int) { return file_felixbackend_proto_rawDescGZIP(), []int{15} } -func (x *PolicyID) GetTier() string { +func (x *PolicyID) GetName() string { if x != nil { - return x.Tier + return x.Name } return "" } -func (x *PolicyID) GetName() string { +func (x *PolicyID) GetNamespace() string { if x != nil { - return x.Name + return x.Namespace + } + return "" +} + +func (x *PolicyID) GetKind() string { + if x != nil { + return x.Kind } return "" } @@ -2052,6 +2060,7 @@ type Policy struct { PreDnat bool `protobuf:"varint,4,opt,name=pre_dnat,json=preDnat,proto3" json:"pre_dnat,omitempty"` OriginalSelector string `protobuf:"bytes,6,opt,name=original_selector,json=originalSelector,proto3" json:"original_selector,omitempty"` PerfHints []string `protobuf:"bytes,7,rep,name=perf_hints,json=perfHints,proto3" json:"perf_hints,omitempty"` + Tier string `protobuf:"bytes,8,opt,name=tier,proto3" json:"tier,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -2135,6 +2144,13 @@ func (x *Policy) GetPerfHints() []string { return nil } +func (x *Policy) GetTier() string { + if x != nil { + return x.Tier + } + return "" +} + type Rule struct { state protoimpl.MessageState `protogen:"open.v1"` Action string `protobuf:"bytes,1,opt,name=action,proto3" json:"action,omitempty"` @@ -3759,8 +3775,8 @@ func (x *HostEndpointRemove) GetId() *HostEndpointID { type TierInfo struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - IngressPolicies []string `protobuf:"bytes,2,rep,name=ingress_policies,json=ingressPolicies,proto3" json:"ingress_policies,omitempty"` - EgressPolicies []string `protobuf:"bytes,3,rep,name=egress_policies,json=egressPolicies,proto3" json:"egress_policies,omitempty"` + IngressPolicies []*PolicyID `protobuf:"bytes,2,rep,name=ingress_policies,json=ingressPolicies,proto3" json:"ingress_policies,omitempty"` + EgressPolicies []*PolicyID `protobuf:"bytes,3,rep,name=egress_policies,json=egressPolicies,proto3" json:"egress_policies,omitempty"` DefaultAction string `protobuf:"bytes,4,opt,name=default_action,json=defaultAction,proto3" json:"default_action,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -3803,14 +3819,14 @@ func (x *TierInfo) GetName() string { return "" } -func (x *TierInfo) GetIngressPolicies() []string { +func (x *TierInfo) GetIngressPolicies() []*PolicyID { if x != nil { return x.IngressPolicies } return nil } -func (x *TierInfo) GetEgressPolicies() []string { +func (x *TierInfo) GetEgressPolicies() []*PolicyID { if x != nil { return x.EgressPolicies } @@ -6506,10 +6522,11 @@ const file_felixbackend_proto_rawDesc = "" + "\x02id\x18\x01 \x01(\v2\x0f.felix.PolicyIDR\x02id\x12%\n" + "\x06policy\x18\x02 \x01(\v2\r.felix.PolicyR\x06policy\"5\n" + "\x12ActivePolicyRemove\x12\x1f\n" + - "\x02id\x18\x01 \x01(\v2\x0f.felix.PolicyIDR\x02id\"2\n" + + "\x02id\x18\x01 \x01(\v2\x0f.felix.PolicyIDR\x02id\"\\\n" + "\bPolicyID\x12\x12\n" + - "\x04tier\x18\x01 \x01(\tR\x04tier\x12\x12\n" + - "\x04name\x18\x02 \x01(\tR\x04name\"\x91\x02\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x1c\n" + + "\tnamespace\x18\x03 \x01(\tR\tnamespace\x12\x12\n" + + "\x04kind\x18\x04 \x01(\tR\x04kindJ\x04\b\x01\x10\x02R\x04tier\"\xa5\x02\n" + "\x06Policy\x12\x1c\n" + "\tnamespace\x18\x05 \x01(\tR\tnamespace\x120\n" + "\rinbound_rules\x18\x01 \x03(\v2\v.felix.RuleR\finboundRules\x122\n" + @@ -6518,7 +6535,8 @@ const file_felixbackend_proto_rawDesc = "" + "\bpre_dnat\x18\x04 \x01(\bR\apreDnat\x12+\n" + "\x11original_selector\x18\x06 \x01(\tR\x10originalSelector\x12\x1d\n" + "\n" + - "perf_hints\x18\a \x03(\tR\tperfHints\"\xaa\x10\n" + + "perf_hints\x18\a \x03(\tR\tperfHints\x12\x12\n" + + "\x04tier\x18\b \x01(\tR\x04tier\"\xaa\x10\n" + "\x04Rule\x12\x16\n" + "\x06action\x18\x01 \x01(\tR\x06action\x12/\n" + "\n" + @@ -6668,11 +6686,11 @@ const file_felixbackend_proto_rawDesc = "" + "\x13expected_ipv6_addrs\x18\x05 \x03(\tR\x11expectedIpv6Addrs\x123\n" + "\fqos_policies\x18\t \x03(\v2\x10.felix.QoSPolicyR\vqosPolicies\";\n" + "\x12HostEndpointRemove\x12%\n" + - "\x02id\x18\x01 \x01(\v2\x15.felix.HostEndpointIDR\x02id\"\x99\x01\n" + + "\x02id\x18\x01 \x01(\v2\x15.felix.HostEndpointIDR\x02id\"\xbb\x01\n" + "\bTierInfo\x12\x12\n" + - "\x04name\x18\x01 \x01(\tR\x04name\x12)\n" + - "\x10ingress_policies\x18\x02 \x03(\tR\x0fingressPolicies\x12'\n" + - "\x0fegress_policies\x18\x03 \x03(\tR\x0eegressPolicies\x12%\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12:\n" + + "\x10ingress_policies\x18\x02 \x03(\v2\x0f.felix.PolicyIDR\x0fingressPolicies\x128\n" + + "\x0fegress_policies\x18\x03 \x03(\v2\x0f.felix.PolicyIDR\x0eegressPolicies\x12%\n" + "\x0edefault_action\x18\x04 \x01(\tR\rdefaultAction\"7\n" + "\aNatInfo\x12\x15\n" + "\x06ext_ip\x18\x01 \x01(\tR\x05extIp\x12\x15\n" + @@ -7103,47 +7121,49 @@ var file_felixbackend_proto_depIdxs = []int32{ 45, // 89: felix.HostEndpoint.forward_tiers:type_name -> felix.TierInfo 38, // 90: felix.HostEndpoint.qos_policies:type_name -> felix.QoSPolicy 41, // 91: felix.HostEndpointRemove.id:type_name -> felix.HostEndpointID - 41, // 92: felix.HostEndpointStatusUpdate.id:type_name -> felix.HostEndpointID - 49, // 93: felix.HostEndpointStatusUpdate.status:type_name -> felix.EndpointStatus - 41, // 94: felix.HostEndpointStatusRemove.id:type_name -> felix.HostEndpointID - 33, // 95: felix.WorkloadEndpointStatusUpdate.id:type_name -> felix.WorkloadEndpointID - 49, // 96: felix.WorkloadEndpointStatusUpdate.status:type_name -> felix.EndpointStatus - 36, // 97: felix.WorkloadEndpointStatusUpdate.endpoint:type_name -> felix.WorkloadEndpoint - 33, // 98: felix.WorkloadEndpointStatusRemove.id:type_name -> felix.WorkloadEndpointID - 0, // 99: felix.WireguardStatusUpdate.ip_version:type_name -> felix.IPVersion - 94, // 100: felix.HostMetadataV4V6Update.labels:type_name -> felix.HostMetadataV4V6Update.LabelsEntry - 63, // 101: felix.IPAMPoolUpdate.pool:type_name -> felix.IPAMPool - 67, // 102: felix.ServiceAccountUpdate.id:type_name -> felix.ServiceAccountID - 95, // 103: felix.ServiceAccountUpdate.labels:type_name -> felix.ServiceAccountUpdate.LabelsEntry - 67, // 104: felix.ServiceAccountRemove.id:type_name -> felix.ServiceAccountID - 70, // 105: felix.NamespaceUpdate.id:type_name -> felix.NamespaceID - 96, // 106: felix.NamespaceUpdate.labels:type_name -> felix.NamespaceUpdate.LabelsEntry - 70, // 107: felix.NamespaceRemove.id:type_name -> felix.NamespaceID - 1, // 108: felix.RouteUpdate.types:type_name -> felix.RouteType - 2, // 109: felix.RouteUpdate.ip_pool_type:type_name -> felix.IPPoolType - 71, // 110: felix.RouteUpdate.tunnel_type:type_name -> felix.TunnelType - 31, // 111: felix.DataplaneStats.protocol:type_name -> felix.Protocol - 78, // 112: felix.DataplaneStats.stats:type_name -> felix.Statistic - 79, // 113: felix.DataplaneStats.rules:type_name -> felix.RuleTrace - 3, // 114: felix.DataplaneStats.action:type_name -> felix.Action - 5, // 115: felix.Statistic.direction:type_name -> felix.Statistic.Direction - 6, // 116: felix.Statistic.relativity:type_name -> felix.Statistic.Relativity - 7, // 117: felix.Statistic.kind:type_name -> felix.Statistic.Kind - 3, // 118: felix.Statistic.action:type_name -> felix.Action - 24, // 119: felix.RuleTrace.policy:type_name -> felix.PolicyID - 20, // 120: felix.RuleTrace.profile:type_name -> felix.ProfileID - 8, // 121: felix.RuleTrace.direction:type_name -> felix.RuleTrace.Direction - 85, // 122: felix.ServiceUpdate.ports:type_name -> felix.ServicePort - 13, // 123: felix.ConfigUpdate.SourceToRawConfigEntry.value:type_name -> felix.RawConfig - 9, // 124: felix.PolicySync.Sync:input_type -> felix.SyncRequest - 77, // 125: felix.PolicySync.Report:input_type -> felix.DataplaneStats - 10, // 126: felix.PolicySync.Sync:output_type -> felix.ToDataplane - 76, // 127: felix.PolicySync.Report:output_type -> felix.ReportResult - 126, // [126:128] is the sub-list for method output_type - 124, // [124:126] is the sub-list for method input_type - 124, // [124:124] is the sub-list for extension type_name - 124, // [124:124] is the sub-list for extension extendee - 0, // [0:124] is the sub-list for field type_name + 24, // 92: felix.TierInfo.ingress_policies:type_name -> felix.PolicyID + 24, // 93: felix.TierInfo.egress_policies:type_name -> felix.PolicyID + 41, // 94: felix.HostEndpointStatusUpdate.id:type_name -> felix.HostEndpointID + 49, // 95: felix.HostEndpointStatusUpdate.status:type_name -> felix.EndpointStatus + 41, // 96: felix.HostEndpointStatusRemove.id:type_name -> felix.HostEndpointID + 33, // 97: felix.WorkloadEndpointStatusUpdate.id:type_name -> felix.WorkloadEndpointID + 49, // 98: felix.WorkloadEndpointStatusUpdate.status:type_name -> felix.EndpointStatus + 36, // 99: felix.WorkloadEndpointStatusUpdate.endpoint:type_name -> felix.WorkloadEndpoint + 33, // 100: felix.WorkloadEndpointStatusRemove.id:type_name -> felix.WorkloadEndpointID + 0, // 101: felix.WireguardStatusUpdate.ip_version:type_name -> felix.IPVersion + 94, // 102: felix.HostMetadataV4V6Update.labels:type_name -> felix.HostMetadataV4V6Update.LabelsEntry + 63, // 103: felix.IPAMPoolUpdate.pool:type_name -> felix.IPAMPool + 67, // 104: felix.ServiceAccountUpdate.id:type_name -> felix.ServiceAccountID + 95, // 105: felix.ServiceAccountUpdate.labels:type_name -> felix.ServiceAccountUpdate.LabelsEntry + 67, // 106: felix.ServiceAccountRemove.id:type_name -> felix.ServiceAccountID + 70, // 107: felix.NamespaceUpdate.id:type_name -> felix.NamespaceID + 96, // 108: felix.NamespaceUpdate.labels:type_name -> felix.NamespaceUpdate.LabelsEntry + 70, // 109: felix.NamespaceRemove.id:type_name -> felix.NamespaceID + 1, // 110: felix.RouteUpdate.types:type_name -> felix.RouteType + 2, // 111: felix.RouteUpdate.ip_pool_type:type_name -> felix.IPPoolType + 71, // 112: felix.RouteUpdate.tunnel_type:type_name -> felix.TunnelType + 31, // 113: felix.DataplaneStats.protocol:type_name -> felix.Protocol + 78, // 114: felix.DataplaneStats.stats:type_name -> felix.Statistic + 79, // 115: felix.DataplaneStats.rules:type_name -> felix.RuleTrace + 3, // 116: felix.DataplaneStats.action:type_name -> felix.Action + 5, // 117: felix.Statistic.direction:type_name -> felix.Statistic.Direction + 6, // 118: felix.Statistic.relativity:type_name -> felix.Statistic.Relativity + 7, // 119: felix.Statistic.kind:type_name -> felix.Statistic.Kind + 3, // 120: felix.Statistic.action:type_name -> felix.Action + 24, // 121: felix.RuleTrace.policy:type_name -> felix.PolicyID + 20, // 122: felix.RuleTrace.profile:type_name -> felix.ProfileID + 8, // 123: felix.RuleTrace.direction:type_name -> felix.RuleTrace.Direction + 85, // 124: felix.ServiceUpdate.ports:type_name -> felix.ServicePort + 13, // 125: felix.ConfigUpdate.SourceToRawConfigEntry.value:type_name -> felix.RawConfig + 9, // 126: felix.PolicySync.Sync:input_type -> felix.SyncRequest + 77, // 127: felix.PolicySync.Report:input_type -> felix.DataplaneStats + 10, // 128: felix.PolicySync.Sync:output_type -> felix.ToDataplane + 76, // 129: felix.PolicySync.Report:output_type -> felix.ReportResult + 128, // [128:130] is the sub-list for method output_type + 126, // [126:128] is the sub-list for method input_type + 126, // [126:126] is the sub-list for extension type_name + 126, // [126:126] is the sub-list for extension extendee + 0, // [0:126] is the sub-list for field type_name } func init() { file_felixbackend_proto_init() } diff --git a/felix/proto/felixbackend.proto b/felix/proto/felixbackend.proto index 003651887fe..fda0342e356 100644 --- a/felix/proto/felixbackend.proto +++ b/felix/proto/felixbackend.proto @@ -236,8 +236,13 @@ message ActivePolicyRemove { } message PolicyID { - string tier = 1; + // The "tier" field has been deprecated and removed. + reserved 1; + reserved "tier"; + string name = 2; + string namespace = 3; + string kind = 4; } message Policy { @@ -253,6 +258,8 @@ message Policy { string original_selector = 6; repeated string perf_hints = 7; + + string tier = 8; } enum IPVersion { @@ -332,7 +339,7 @@ message Rule { RuleMetadata metadata = 123; - // Changed to config option. + // Changed to FelixConfig option. reserved 200; reserved "log_prefix"; @@ -476,8 +483,8 @@ message HostEndpointRemove { message TierInfo { string name = 1; - repeated string ingress_policies = 2; - repeated string egress_policies = 3; + repeated PolicyID ingress_policies = 2; + repeated PolicyID egress_policies = 3; string default_action = 4; } diff --git a/felix/proto/felixbackend_grpc.pb.go b/felix/proto/felixbackend_grpc.pb.go index b783522c18f..7a26439ba5f 100644 --- a/felix/proto/felixbackend_grpc.pb.go +++ b/felix/proto/felixbackend_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.5.1 -// - protoc v6.32.0 +// - protoc-gen-go-grpc v1.6.0 +// - protoc v6.33.4 // source: felixbackend.proto package proto @@ -126,10 +126,10 @@ type PolicySyncServer interface { type UnimplementedPolicySyncServer struct{} func (UnimplementedPolicySyncServer) Sync(*SyncRequest, grpc.ServerStreamingServer[ToDataplane]) error { - return status.Errorf(codes.Unimplemented, "method Sync not implemented") + return status.Error(codes.Unimplemented, "method Sync not implemented") } func (UnimplementedPolicySyncServer) Report(context.Context, *DataplaneStats) (*ReportResult, error) { - return nil, status.Errorf(codes.Unimplemented, "method Report not implemented") + return nil, status.Error(codes.Unimplemented, "method Report not implemented") } func (UnimplementedPolicySyncServer) mustEmbedUnimplementedPolicySyncServer() {} func (UnimplementedPolicySyncServer) testEmbeddedByValue() {} @@ -142,7 +142,7 @@ type UnsafePolicySyncServer interface { } func RegisterPolicySyncServer(s grpc.ServiceRegistrar, srv PolicySyncServer) { - // If the following call pancis, it indicates UnimplementedPolicySyncServer was + // If the following call panics, it indicates UnimplementedPolicySyncServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. diff --git a/felix/proto/msgstringer.go b/felix/proto/msgstringer.go index f4837bdadde..f1bebf96b3d 100644 --- a/felix/proto/msgstringer.go +++ b/felix/proto/msgstringer.go @@ -23,7 +23,7 @@ import ( // msgStringer wraps an API message to customise how we stringify it. For example, it truncates // the lists of members in the (potentially very large) IPSetsUpdate messages. type MsgStringer struct { - Msg interface{} + Msg any } func (m MsgStringer) String() string { diff --git a/felix/ratelimited/runner_test.go b/felix/ratelimited/runner_test.go index 2a742fc9b1c..0e8ca970a06 100644 --- a/felix/ratelimited/runner_test.go +++ b/felix/ratelimited/runner_test.go @@ -26,8 +26,7 @@ import ( func TestRunner(t *testing.T) { RegisterTestingT(t) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + ctx := t.Context() kicks := make(chan struct{}) diff --git a/felix/routerule/route_rule.go b/felix/routerule/route_rule.go index b4c96db74b8..4f2b818afad 100644 --- a/felix/routerule/route_rule.go +++ b/felix/routerule/route_rule.go @@ -96,17 +96,16 @@ func New( } indexOK := true - tableIndexSet.Iter(func(i int) error { + for i := range tableIndexSet.All() { if (i == 0) || int64(i) >= int64(linuxRTTableMax) || i == unix.RT_TABLE_DEFAULT || i == unix.RT_TABLE_LOCAL || i == unix.RT_TABLE_MAIN { indexOK = false - return set.StopIteration + break } - return nil - }) + } if !indexOK { return nil, ErrTableIndexFailed @@ -132,13 +131,12 @@ func New( // Return nil if no active Rule exists. func (r *RouteRules) getActiveRule(rule *Rule, f RulesMatchFunc) *Rule { var active *Rule - r.activeRules.Iter(func(p *Rule) error { + for p := range r.activeRules.All() { if f(p, rule) { active = p - return set.StopIteration + break } - return nil - }) + } return active } @@ -218,10 +216,9 @@ func (r *RouteRules) closeNetlinkHandle() { func (r *RouteRules) PrintCurrentRules() { log.WithField("count", r.activeRules.Len()).Info("summary of active rules") - r.activeRules.Iter(func(p *Rule) error { + for p := range r.activeRules.All() { p.LogCxt().Info("active rule") - return nil - }) + } } func (r *RouteRules) Apply() error { @@ -256,7 +253,6 @@ func (r *RouteRules) Apply() error { toRemove := set.New[*Rule]() for _, nlRule := range nlRules { // Give each loop a fresh copy of nlRule since we would need to use pointer later. - nlRule := nlRule if r.tableIndexSet.Contains(nlRule.Table) { // Table index of the rule is managed by us. // Be careful, do not use &nlRule below as it remain same value through iterations. @@ -272,26 +268,23 @@ func (r *RouteRules) Apply() error { updatesFailed := false - toRemove.Iter(func(rule *Rule) error { + for rule := range toRemove.All() { if err := nl.RuleDel(rule.nlRule); err != nil { rule.LogCxt().WithError(err).Warnf("Failed to remove rule from dataplane.") updatesFailed = true } else { rule.LogCxt().Debugf("Rule removed from dataplane.") } - return nil - }) + } - toAdd.Iter(func(rule *Rule) error { + for rule := range toAdd.All() { if err := nl.RuleAdd(rule.nlRule); err != nil { rule.LogCxt().WithError(err).Warnf("Failed to add rule from dataplane.") updatesFailed = true - return nil } else { rule.LogCxt().Debugf("Rule added to dataplane.") } - return nil - }) + } if updatesFailed { r.closeNetlinkHandle() // Defensive: force a netlink reconnection next time. diff --git a/felix/routerule/route_rule_test.go b/felix/routerule/route_rule_test.go index b01bff505ca..711a60a56f0 100644 --- a/felix/routerule/route_rule_test.go +++ b/felix/routerule/route_rule_test.go @@ -21,7 +21,7 @@ import ( "strings" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" "github.com/vishvananda/netlink" @@ -304,7 +304,7 @@ var _ = Describe("RouteRules", func() { }) It("should panic after all its retries are exhausted", func() { - for i := 0; i < 3; i++ { + for range 3 { Expect(rrs.Apply()).To(Equal(ErrConnectFailed)) } Expect(func() { _ = rrs.Apply() }).To(Panic()) @@ -315,7 +315,6 @@ var _ = Describe("RouteRules", func() { // each case, we make the failure transient so that only the first Apply() should // fail. Then, at most, the second call to Apply() should succeed. for _, failFlags := range failureScenarios { - failFlags := failFlags desc := fmt.Sprintf("with some rules added and failures: %v", failFlags) Context(desc, func() { BeforeEach(func() { diff --git a/felix/routerule/routerule_suite_test.go b/felix/routerule/routerule_suite_test.go index c7f93ba202d..0e31a94fb32 100644 --- a/felix/routerule/routerule_suite_test.go +++ b/felix/routerule/routerule_suite_test.go @@ -17,9 +17,8 @@ package routerule_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestRules(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/routerule_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "RouteRule Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_routerule_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/routerule", suiteConfig, reporterConfig) } diff --git a/felix/routerule/rule_lib.go b/felix/routerule/rule_lib.go index 0adfdd4784b..081a0a2409b 100644 --- a/felix/routerule/rule_lib.go +++ b/felix/routerule/rule_lib.go @@ -47,7 +47,7 @@ func (r *Rule) NetLinkRule() *netlink.Rule { } func (r *Rule) LogCxt() *log.Entry { - var src interface{} + var src any if r.nlRule.Src != nil { src = r.nlRule.Src } diff --git a/felix/routerule/rule_lib_test.go b/felix/routerule/rule_lib_test.go index af3e92c6fab..be466493780 100644 --- a/felix/routerule/rule_lib_test.go +++ b/felix/routerule/rule_lib_test.go @@ -15,7 +15,7 @@ package routerule_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/vishvananda/netlink" "golang.org/x/sys/unix" diff --git a/felix/routetable/bench_test.go b/felix/routetable/bench_test.go index 85835f8ed8c..3455d7ea310 100644 --- a/felix/routetable/bench_test.go +++ b/felix/routetable/bench_test.go @@ -87,10 +87,12 @@ func benchResyncNumRoutes(b *testing.B, numRoutes int) { n := 0 outer: - for i := 0; i < 256; i++ { - for j := 0; j < 256; j++ { + for i := range 256 { + for j := range 256 { rt.RouteUpdate(RouteClassLocalWorkload, ifaceName, Target{ - CIDR: ip.MustParseCIDROrIP(fmt.Sprintf("10.0.%d.%d/32", i, j)), + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP(fmt.Sprintf("10.0.%d.%d/32", i, j)), + }, }) n++ if n == numRoutes { diff --git a/felix/routetable/conntrack_owner_tracker.go b/felix/routetable/conntrack_owner_tracker.go index 06d97fb0857..5366cd4a160 100644 --- a/felix/routetable/conntrack_owner_tracker.go +++ b/felix/routetable/conntrack_owner_tracker.go @@ -205,10 +205,10 @@ func (c *ConntrackCleanupManager) CIDRNeedsEarlyCleanup(cidr ip.CIDR, oldIface i // StartConntrackCleanupAndReset starts all pending conntrack cleanups and // resets the tracker to prepare for the next round of updates. func (c *ConntrackCleanupManager) StartConntrackCleanupAndReset() { - c.addrsToCleanUp.Iter(func(addr ip.Addr) error { + for addr := range c.addrsToCleanUp.All() { c.startDeletion(addr) - return set.RemoveItem - }) + c.addrsToCleanUp.Discard(addr) + } // Reset the tracker; we assume that the RouteTable has now implemented // its planned actions, and it'll tell us if any routes change. diff --git a/felix/routetable/defs.go b/felix/routetable/defs.go index 3151cb43295..c9159ba42c9 100644 --- a/felix/routetable/defs.go +++ b/felix/routetable/defs.go @@ -16,6 +16,7 @@ package routetable import ( "errors" + "fmt" "net" "reflect" @@ -69,9 +70,30 @@ var ( ErrIfaceDown = errors.New("interface down") ) +// RouteKey represents the kernel's FIB key, and is also what we use on the RouteTable API to +// identify each route. The kernel and RouteTable API allow routes to coexist as long as they have +// different keys. +type RouteKey struct { + // Destination CIDR; route matches traffic to this destination. + CIDR ip.CIDR + // TOS is the Type-of-Service field. For example, one app may mark its + // packets as "high importance" and that will take a different route to + // another app. + // + // Kernel uses the TOS=0 route if there isn't a more precise match. + TOS int + // Priority is the routing metric / distance. Given two routes with the + // same CIDR, the kernel prefers the route with the _lower_ priority. + Priority int +} + +func (k RouteKey) String() string { + return fmt.Sprintf("%s(tos=%x metric=%d)", k.CIDR.String(), k.TOS, k.Priority) +} + type Target struct { + RouteKey Type TargetType - CIDR ip.CIDR GW ip.Addr Src ip.Addr DestMAC net.HardwareAddr diff --git a/felix/routetable/dummy_table.go b/felix/routetable/dummy_table.go index 38db593cb75..52091de002a 100644 --- a/felix/routetable/dummy_table.go +++ b/felix/routetable/dummy_table.go @@ -2,7 +2,6 @@ package routetable import ( "github.com/projectcalico/calico/felix/ifacemonitor" - "github.com/projectcalico/calico/felix/ip" ) type DummyTable struct { @@ -24,7 +23,7 @@ func (*DummyTable) Apply() error { func (*DummyTable) SetRoutes(routeClass RouteClass, ifaceName string, targets []Target) { } -func (*DummyTable) RouteRemove(routeClass RouteClass, ifaceName string, cidr ip.CIDR) { +func (*DummyTable) RouteRemove(routeClass RouteClass, ifaceName string, routeKey RouteKey) { } func (*DummyTable) RouteUpdate(routeClass RouteClass, ifaceName string, target Target) { diff --git a/felix/routetable/interface.go b/felix/routetable/interface.go index 1f5c6bc4500..057bdc222d2 100644 --- a/felix/routetable/interface.go +++ b/felix/routetable/interface.go @@ -16,7 +16,6 @@ package routetable import ( "github.com/projectcalico/calico/felix/ifacemonitor" - "github.com/projectcalico/calico/felix/ip" ) // SyncerInterface is the interface used to manage data-sync of route table managers. This includes notification of @@ -31,7 +30,7 @@ type SyncerInterface interface { type Interface interface { SyncerInterface SetRoutes(routeClass RouteClass, ifaceName string, targets []Target) - RouteRemove(routeClass RouteClass, ifaceName string, cidr ip.CIDR) + RouteRemove(routeClass RouteClass, ifaceName string, routeKey RouteKey) RouteUpdate(routeClass RouteClass, ifaceName string, target Target) Index() int QueueResyncIface(ifaceName string) @@ -68,8 +67,8 @@ func (cv *ClassView) SetRoutes(ifaceName string, targets []Target) { cv.routeTable.SetRoutes(cv.class, ifaceName, targets) } -func (cv *ClassView) RouteRemove(ifaceName string, cidr ip.CIDR) { - cv.routeTable.RouteRemove(cv.class, ifaceName, cidr) +func (cv *ClassView) RouteRemove(ifaceName string, routeKey RouteKey) { + cv.routeTable.RouteRemove(cv.class, ifaceName, routeKey) } func (cv *ClassView) RouteUpdate(ifaceName string, target Target) { diff --git a/felix/routetable/ownershippol/iface_ownership_policy.go b/felix/routetable/ownershippol/iface_ownership_policy.go index a9312487612..adcb992bbdb 100644 --- a/felix/routetable/ownershippol/iface_ownership_policy.go +++ b/felix/routetable/ownershippol/iface_ownership_policy.go @@ -15,6 +15,8 @@ package ownershippol import ( + "slices" + "github.com/vishvananda/netlink" ) @@ -37,12 +39,7 @@ func (d *ExclusiveOwnershipPolicy) IfaceIsOurs(ifaceName string) bool { if d.InterfaceNames == nil { return true } - for _, iface := range d.InterfaceNames { - if iface == ifaceName { - return true - } - } - return false + return slices.Contains(d.InterfaceNames, ifaceName) } func (d *ExclusiveOwnershipPolicy) RouteIsOurs(ifaceName string, route *netlink.Route) bool { diff --git a/felix/routetable/ownershippol/main_route_table_policy.go b/felix/routetable/ownershippol/main_route_table_policy.go index a56cf8c5938..45d093ced71 100644 --- a/felix/routetable/ownershippol/main_route_table_policy.go +++ b/felix/routetable/ownershippol/main_route_table_policy.go @@ -15,6 +15,7 @@ package ownershippol import ( + "slices" "strings" "github.com/vishvananda/netlink" @@ -127,10 +128,8 @@ func (d *MainTableOwnershipPolicy) RouteIsOurs(ifaceName string, route *netlink. // This covers: // - VXLAN "same-subnet" routes via the host's main NIC. // - Special no-interface routes such as blackhole/prohibit. - for _, protocol := range d.ExclusiveRouteProtocols { - if route.Protocol == protocol { - return true - } + if slices.Contains(d.ExclusiveRouteProtocols, route.Protocol) { + return true } // "No-interface" special routes can only be ours if they have one @@ -150,24 +149,12 @@ func (d *MainTableOwnershipPolicy) RouteIsOurs(ifaceName string, route *netlink. // more permissive protocol value here because we already know that // this is one of our interfaces so the route is very likely to be // ours. - for _, protocol := range d.AllRouteProtocols { - if route.Protocol == protocol { - return true - } - } - // Calico interface but not our route. - return false + return slices.Contains(d.AllRouteProtocols, route.Protocol) } // Check if this route goes to one of our tunnel/special purpose // interfaces. These are always ours. - for _, iface := range d.CalicoSpecialInterfaces { - if ifaceName == iface { - return true - } - } - - return false + return slices.Contains(d.CalicoSpecialInterfaces, ifaceName) } var _ routetable.OwnershipPolicy = (*MainTableOwnershipPolicy)(nil) diff --git a/felix/routetable/route_table.go b/felix/routetable/route_table.go index 331768963c6..e155be5b3b1 100644 --- a/felix/routetable/route_table.go +++ b/felix/routetable/route_table.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2023 Tigera, Inc. All rights reserved. +// Copyright (c) 2016-2026 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -142,21 +142,22 @@ type RouteTable struct { // Interface update tracking. fullResyncNeeded bool ifacesToRescan set.Set[string] + ifacesToARP set.Set[string] makeARPEntries bool haveMultiPathRoutes bool // ifaceToRoutes and cidrToIfaces are our inputs, updated // eagerly when something in the manager layer tells us to change the // routes. - ifaceToRoutes map[RouteClass]map[string]map[ip.CIDR]Target - cidrToIfaces map[RouteClass]map[ip.CIDR]set.Set[string] + ifaceToRoutes map[RouteClass]map[string]map[RouteKey]Target + cidrToIfaces map[RouteClass]map[RouteKey]set.Set[string] // kernelRoutes tracks the relationship between the route that we want // to program for a given CIDR (i.e. the route selected after conflict // resolution if there are multiple routes) and the route that's actually // in the kernel. - kernelRoutes *deltatracker.DeltaTracker[kernelRouteKey, kernelRoute] - pendingARPs map[string]map[ip.Addr]net.HardwareAddr + kernelRoutes *deltatracker.DeltaTracker[RouteKey, kernelRoute] + permanentARPs map[string]map[ip.Addr]net.HardwareAddr ifaceNameToIndex map[string]int ifaceIndexToName map[int]string @@ -303,17 +304,18 @@ func New( fullResyncNeeded: true, ifacesToRescan: set.New[string](), + ifacesToARP: set.New[string](), ownershipPolicy: ownershipPolicy, - ifaceToRoutes: map[RouteClass]map[string]map[ip.CIDR]Target{}, - cidrToIfaces: map[RouteClass]map[ip.CIDR]set.Set[string]{}, + ifaceToRoutes: map[RouteClass]map[string]map[RouteKey]Target{}, + cidrToIfaces: map[RouteClass]map[RouteKey]set.Set[string]{}, - kernelRoutes: deltatracker.New[kernelRouteKey, kernelRoute]( - deltatracker.WithValuesEqualFn[kernelRouteKey, kernelRoute](func(a, b kernelRoute) bool { + kernelRoutes: deltatracker.New( + deltatracker.WithValuesEqualFn[RouteKey](func(a, b kernelRoute) bool { return a.Equals(b) }), ), - pendingARPs: map[string]map[ip.Addr]net.HardwareAddr{}, + permanentARPs: map[string]map[ip.Addr]net.HardwareAddr{}, ifaceIndexToGraceInfo: map[int]graceInfo{}, ifaceNameToIndex: map[string]int{}, @@ -345,6 +347,39 @@ func New( handlemgr.WithSocketTimeout(netlinkTimeout), ) + // The conntrack tracker (ConntrackCleanupManager) cleans up conntrack state for a workload + // IP - i.e. for flows going to or from that IP, on this node - when Felix stops programming + // a local route for that workload. The latter happens during live migration when the VM + // was originally on this node: a remote BIRD-programmed route appears for the IP, then + // Felix removes the old local route for the IP, which means that conntrack state for that + // IP is cleaned up; yet we want existing connections to the migrating IP to be impacted as + // little as possible. + // + // Note that this isn't related to Priority, so it's irrelevant that the conntrack tracker + // doesn't yet handle that. We need the route table to handle Priority, to get the correct + // interaction between Felix- and BIRD-programmed routes with different priorities, and we + // include Priority in RouteKey because that matches what the kernel does. But Felix itself + // doesn't program multiple routes with the same IP and different priorities; at least, not + // for live migration. + // + // The conntrack issue is about moving an existing flow from one node to another. The + // concerns are: + // + // 1. If TCP loose is disabled (`nf_conntrack_tcp_loose`), a non-SYN packet on the new node + // will not be able to re-establish the conntrack state there. + // + // 2. If the next packet on a migrated flow is in the reverse direction (i.e. from the + // original connection's destination IP), policy might not allow it, whereas it would allow + // a packet in the forwards direction. + // + // 3. Re-evaluating policy for a migrated flow might require retransmitting a packet, and + // for UDP retransmission depends on application layer behaviour, instead of being baked + // into the protocol like with TCP; so that might not happen. + // + // For the current increment of live migration support we have decided to live with these + // issues. In future we may address them by migrating the conntrack state to the new node. + // For now, TCP loose is effectively required for (1), and we hope that (2) and (3) will not + // have significant impacts in practice. if rt.conntrackCleanupEnabled { rt.conntrackTracker = NewConntrackCleanupManager(ipVersion, rt.conntrack) } else { @@ -457,15 +492,16 @@ func (r *RouteTable) SetRoutes(routeClass RouteClass, ifaceName string, targets r.checkTargets(ifaceName, targets...) if r.ifaceToRoutes[routeClass] == nil { - r.ifaceToRoutes[routeClass] = map[string]map[ip.CIDR]Target{} + r.ifaceToRoutes[routeClass] = map[string]map[RouteKey]Target{} } // Figure out what has changed. oldTargetsToCleanUp := r.ifaceToRoutes[routeClass][ifaceName] - newTargets := map[ip.CIDR]Target{} + newTargets := map[RouteKey]Target{} for _, t := range targets { - delete(oldTargetsToCleanUp, t.CIDR) - newTargets[t.CIDR] = t + routeKey := r.normalizeRouteKey(t.RouteKey) + delete(oldTargetsToCleanUp, routeKey) + newTargets[routeKey] = t } // Record the new desired state. @@ -477,18 +513,18 @@ func (r *RouteTable) SetRoutes(routeClass RouteClass, ifaceName string, targets } // Clean up the old CIDRs. - for cidr := range oldTargetsToCleanUp { - r.logCxt.WithField("cidr", cidr).Debug("Cleaning up old route.") + for routeKey := range oldTargetsToCleanUp { + r.logCxt.WithField("routeKey", routeKey).Debug("Cleaning up old route.") // removeOwningIface() calls recalculateDesiredKernelRoute. - r.removeOwningIface(routeClass, ifaceName, cidr) + r.removeOwningIface(routeClass, ifaceName, routeKey) } // Clean out the pending ARP list, then recalculate it below. - delete(r.pendingARPs, ifaceName) - for cidr, target := range newTargets { + delete(r.permanentARPs, ifaceName) + for routeKey, target := range newTargets { // addOwningIface() calls recalculateDesiredKernelRoute. - r.addOwningIface(routeClass, ifaceName, cidr) - r.updatePendingARP(ifaceName, cidr.Addr(), target.DestMAC) + r.addOwningIface(routeClass, ifaceName, routeKey) + r.updatePermanentARP(ifaceName, routeKey.CIDR.Addr(), target.DestMAC) } } @@ -503,99 +539,105 @@ func (r *RouteTable) RouteUpdate(routeClass RouteClass, ifaceName string, target r.checkTargets(ifaceName, target) if r.ifaceToRoutes[routeClass] == nil { - r.ifaceToRoutes[routeClass] = map[string]map[ip.CIDR]Target{} + r.ifaceToRoutes[routeClass] = map[string]map[RouteKey]Target{} } routesByCIDR := r.ifaceToRoutes[routeClass][ifaceName] if routesByCIDR == nil { - routesByCIDR = map[ip.CIDR]Target{} + routesByCIDR = map[RouteKey]Target{} r.ifaceToRoutes[routeClass][ifaceName] = routesByCIDR } - routesByCIDR[target.CIDR] = target - r.addOwningIface(routeClass, ifaceName, target.CIDR) - r.updatePendingARP(ifaceName, target.CIDR.Addr(), target.DestMAC) + routeKey := r.normalizeRouteKey(target.RouteKey) + routesByCIDR[routeKey] = target + r.addOwningIface(routeClass, ifaceName, routeKey) + r.updatePermanentARP(ifaceName, routeKey.CIDR.Addr(), target.DestMAC) } // RouteRemove removes the route with the specified CIDR. These deltas will // be applied to any routes set using SetRoute. -func (r *RouteTable) RouteRemove(routeClass RouteClass, ifaceName string, cidr ip.CIDR) { +func (r *RouteTable) RouteRemove(routeClass RouteClass, ifaceName string, routeKey RouteKey) { if !r.ownershipPolicy.IfaceIsOurs(ifaceName) { r.logCxt.WithField("ifaceName", ifaceName).Error( "Cannot set route for interface not managed by this routetable.") return } - delete(r.ifaceToRoutes[routeClass][ifaceName], cidr) + routeKey = r.normalizeRouteKey(routeKey) + _, exists := r.ifaceToRoutes[routeClass][ifaceName][routeKey] + if !exists { + return + } + delete(r.ifaceToRoutes[routeClass][ifaceName], routeKey) if len(r.ifaceToRoutes[routeClass][ifaceName]) == 0 { delete(r.ifaceToRoutes[routeClass], ifaceName) } - r.removeOwningIface(routeClass, ifaceName, cidr) - r.removePendingARP(ifaceName, cidr.Addr()) + r.removeOwningIface(routeClass, ifaceName, routeKey) + r.removePermanentARP(ifaceName, routeKey.CIDR.Addr()) } -func (r *RouteTable) updatePendingARP(ifaceName string, addr ip.Addr, mac net.HardwareAddr) { +func (r *RouteTable) updatePermanentARP(ifaceName string, addr ip.Addr, mac net.HardwareAddr) { if !r.makeARPEntries { return } if len(mac) == 0 { - r.removePendingARP(ifaceName, addr) + r.removePermanentARP(ifaceName, addr) return } r.logCxt.Debug("Adding pending ARP entry.") - if r.pendingARPs[ifaceName] == nil { - r.pendingARPs[ifaceName] = map[ip.Addr]net.HardwareAddr{} + if r.permanentARPs[ifaceName] == nil { + r.permanentARPs[ifaceName] = map[ip.Addr]net.HardwareAddr{} } - r.pendingARPs[ifaceName][addr] = mac + r.permanentARPs[ifaceName][addr] = mac } -func (r *RouteTable) removePendingARP(ifaceName string, addr ip.Addr) { +func (r *RouteTable) removePermanentARP(ifaceName string, addr ip.Addr) { if !r.makeARPEntries { return } - if pending, ok := r.pendingARPs[ifaceName]; ok { - delete(pending, addr) - if len(pending) == 0 { - delete(r.pendingARPs, ifaceName) + if arps, ok := r.permanentARPs[ifaceName]; ok { + delete(arps, addr) + if len(arps) == 0 { + delete(r.permanentARPs, ifaceName) } } } -func (r *RouteTable) addOwningIface(class RouteClass, ifaceName string, cidr ip.CIDR) { +func (r *RouteTable) addOwningIface(class RouteClass, ifaceName string, routeKey RouteKey) { if r.cidrToIfaces[class] == nil { - r.cidrToIfaces[class] = map[ip.CIDR]set.Set[string]{} + r.cidrToIfaces[class] = map[RouteKey]set.Set[string]{} } - ifaceNames := r.cidrToIfaces[class][cidr] + ifaceNames := r.cidrToIfaces[class][routeKey] if ifaceNames == nil { ifaceNames = set.New[string]() - r.cidrToIfaces[class][cidr] = ifaceNames + r.cidrToIfaces[class][routeKey] = ifaceNames } ifaceNames.Add(ifaceName) - r.recalculateDesiredKernelRoute(cidr) + r.recalculateDesiredKernelRoute(routeKey) } -func (r *RouteTable) removeOwningIface(class RouteClass, ifaceName string, cidr ip.CIDR) { - ifaceNames, ok := r.cidrToIfaces[class][cidr] +func (r *RouteTable) removeOwningIface(class RouteClass, ifaceName string, routeKey RouteKey) { + ifaceNames, ok := r.cidrToIfaces[class][routeKey] if !ok { return } ifaceNames.Discard(ifaceName) if ifaceNames.Len() == 0 { - delete(r.cidrToIfaces[class], cidr) + delete(r.cidrToIfaces[class], routeKey) } - r.recalculateDesiredKernelRoute(cidr) + r.recalculateDesiredKernelRoute(routeKey) } // recheckRouteOwnershipsByIface reruns conflict resolution for all // the interface's routes. func (r *RouteTable) recheckRouteOwnershipsByIface(name string) { - seen := set.New[ip.CIDR]() + seen := set.New[RouteKey]() for _, ifaceToRoutes := range r.ifaceToRoutes { - for cidr := range ifaceToRoutes[name] { - if seen.Contains(cidr) { + for routeKey := range ifaceToRoutes[name] { + if seen.Contains(routeKey) { continue } - r.recalculateDesiredKernelRoute(cidr) - seen.Add(cidr) + r.recalculateDesiredKernelRoute(routeKey) + seen.Add(routeKey) } } } @@ -623,10 +665,9 @@ func (r *RouteTable) ifaceNameForIndex(ifindex int) (string, bool) { return name, ok } -func (r *RouteTable) recalculateDesiredKernelRoute(cidr ip.CIDR) { +func (r *RouteTable) recalculateDesiredKernelRoute(routeKey RouteKey) { defer r.updateGauges() - kernKey := r.routeKeyForCIDR(cidr) - oldDesiredRoute, _ := r.kernelRoutes.Desired().Get(kernKey) + oldDesiredRoute, _ := r.kernelRoutes.Desired().Get(routeKey) var bestTarget Target bestRouteClass := RouteClassMax @@ -635,7 +676,7 @@ func (r *RouteTable) recalculateDesiredKernelRoute(cidr ip.CIDR) { var candidates []string for routeClass, cidrToIface := range r.cidrToIfaces { - ifaces := cidrToIface[cidr] + ifaces := cidrToIface[routeKey] if ifaces == nil { continue } @@ -643,27 +684,28 @@ func (r *RouteTable) recalculateDesiredKernelRoute(cidr ip.CIDR) { // In case of conflicts (more than one route with the same CIDR), pick // one deterministically so that we don't churn the dataplane. - ifaces.Iter(func(ifaceName string) error { + ifacesLoop: + for ifaceName := range ifaces.All() { candidates = append(candidates, ifaceName) ifIndex, ok := r.ifaceIndexForName(ifaceName) if !ok { r.logCxt.WithField("ifaceName", ifaceName).Debug("Skipping route for missing interface.") - return nil + continue } someUp := false - target, ok := r.ifaceToRoutes[routeClass][ifaceName][cidr] + target, ok := r.ifaceToRoutes[routeClass][ifaceName][routeKey] if !ok { log.WithFields(log.Fields{ "ifaceName": ifaceName, - "cidr": cidr, + "routeKey": routeKey, }).Warn("Bug? No route for iface/CIDR (recalculateDesiredKernelRoute called too early?).") - return nil + continue } for _, nh := range target.MultiPath { if ifIndex, ok := r.ifaceIndexForName(nh.IfaceName); !ok { r.logCxt.WithField("ifaceName", nh.IfaceName).Debug("Skipping multi-path route for missing interface.") - return nil + continue ifacesLoop } else { if r.ifaceIndexToState[ifIndex] == ifacemonitor.StateUp { someUp = true @@ -680,11 +722,11 @@ func (r *RouteTable) recalculateDesiredKernelRoute(cidr ip.CIDR) { if ifaceName != InterfaceNone && r.ifaceIndexToState[ifIndex] != ifacemonitor.StateUp { r.logCxt.WithField("ifaceName", ifaceName).Debug("Skipping route for down interface.") - return nil + continue } else if len(target.MultiPath) > 0 && !someUp { // Multi-path routes require at least one interface to be up. r.logCxt.Debug("Skipping multi-path route; all interfaces down.") - return nil + continue } // Main tie-breaker is the RouteClass, which is prioritised @@ -696,25 +738,24 @@ func (r *RouteTable) recalculateDesiredKernelRoute(cidr ip.CIDR) { bestRouteClass = routeClass bestTarget = target } - return nil - }) + } } if bestIfaceIdx == -1 { if len(candidates) == 0 { r.logCxt.WithFields(log.Fields{ - "cidr": cidr, + "routeKey": routeKey, }).Debug("CIDR no longer has any associated routes.") } else { r.logCxt.WithFields(log.Fields{ - "cidr": cidr, + "routeKey": routeKey, "candidates": candidates, }).Debug("No valid route for this CIDR (all candidate routes missing iface index).") } // Clean up the old entries. - r.kernelRoutes.Desired().Delete(kernKey) - r.conntrackTracker.RemoveCIDROwner(cidr) + r.kernelRoutes.Desired().Delete(routeKey) + r.conntrackTracker.RemoveCIDROwner(routeKey.CIDR) return } @@ -753,21 +794,21 @@ func (r *RouteTable) recalculateDesiredKernelRoute(cidr ip.CIDR) { } if log.IsLevelEnabled(log.DebugLevel) && !reflect.DeepEqual(oldDesiredRoute, kernRoute) { r.logCxt.WithFields(log.Fields{ - "dst": kernKey, + "dst": routeKey, "oldRoute": oldDesiredRoute, "newRoute": kernRoute, "iface": bestIface, }).Debug("Preferred kernel route for this dest has changed.") } else if log.IsLevelEnabled(log.DebugLevel) { r.logCxt.WithFields(log.Fields{ - "dst": kernKey, + "dst": routeKey, "route": kernRoute, "iface": bestIface, }).Debug("Preferred kernel route for this dest still the same.") } - r.kernelRoutes.Desired().Set(kernKey, kernRoute) - r.conntrackTracker.UpdateCIDROwner(cidr, bestIfaceIdx, bestRouteClass) + r.kernelRoutes.Desired().Set(routeKey, kernRoute) + r.conntrackTracker.UpdateCIDROwner(routeKey.CIDR, bestIfaceIdx, bestRouteClass) } func (r *RouteTable) QueueResync() { @@ -793,13 +834,13 @@ func (r *RouteTable) ReadRoutesFromKernel(ifaceName string) ([]Target, error) { } var allTargets []Target - r.kernelRoutes.Dataplane().Iter(func(key kernelRouteKey, kernRoute kernelRoute) { + r.kernelRoutes.Dataplane().Iter(func(key RouteKey, kernRoute kernelRoute) { if kernRoute.Ifindex != ifaceIndex { return } target := Target{ - CIDR: key.CIDR, + RouteKey: key, Src: kernRoute.Src, Protocol: kernRoute.Protocol, } @@ -914,6 +955,11 @@ func (r *RouteTable) maybeResyncWithDataplane() error { } if r.fullResyncNeeded { + // Mark to reprogram static ARP for all interfaces. + for ifaceName := range r.permanentARPs { + r.ifacesToARP.Add(ifaceName) + } + return r.doFullResync(nl) } @@ -941,8 +987,8 @@ func (r *RouteTable) doFullResync(nl netlinkshim.Interface) error { routeFilterFlags := netlink.RT_FILTER_TABLE var err error - seenKeys := set.NewSize[kernelRouteKey](r.kernelRoutes.Dataplane().Len()) - for attempt := 0; attempt < routeListFilterAttempts; attempt++ { + seenKeys := set.NewSize[RouteKey](r.kernelRoutes.Dataplane().Len()) + for range routeListFilterAttempts { // Using the Iter version here saves allocating a large slice of netlink.Route, // which we immediately discard. var scratchRoute netlink.Route @@ -957,11 +1003,11 @@ func (r *RouteTable) doFullResync(nl netlinkshim.Interface) error { return true } - kernKey, kernRoute := r.netlinkRouteToKernelRoute(&scratchRoute) - if oldRoute, ok := r.kernelRoutes.Dataplane().Get(kernKey); !ok || oldRoute.Equals(kernRoute) { - r.kernelRoutes.Dataplane().Set(kernKey, kernRoute) + routeKey, kernRoute := r.netlinkRouteToKernelRoute(&scratchRoute) + if oldRoute, ok := r.kernelRoutes.Dataplane().Get(routeKey); !ok || oldRoute.Equals(kernRoute) { + r.kernelRoutes.Dataplane().Set(routeKey, kernRoute) } - seenKeys.Add(kernKey) + seenKeys.Add(routeKey) r.livenessCallback() return true }) @@ -984,10 +1030,10 @@ func (r *RouteTable) doFullResync(nl netlinkshim.Interface) error { return fmt.Errorf("failed to list all routes for resync: %w", err) } - r.kernelRoutes.Dataplane().Iter(func(kernKey kernelRouteKey, kernRoute kernelRoute) { - if !seenKeys.Contains(kernKey) { - r.kernelRoutes.Dataplane().Delete(kernKey) - r.conntrackTracker.OnDataplaneRouteDeleted(kernKey.CIDR, kernRoute.Ifindex) + r.kernelRoutes.Dataplane().Iter(func(routeKey RouteKey, kernRoute kernelRoute) { + if !seenKeys.Contains(routeKey) { + r.kernelRoutes.Dataplane().Delete(routeKey) + r.conntrackTracker.OnDataplaneRouteDeleted(routeKey.CIDR, kernRoute.Ifindex) } r.livenessCallback() }) @@ -1008,15 +1054,15 @@ func (r *RouteTable) resyncIndividualInterfaces(nl netlinkshim.Interface) error return nil } r.opReporter.RecordOperation(fmt.Sprint("partial-resync-routes-v", r.ipVersion)) - r.ifacesToRescan.Iter(func(ifaceName string) error { + for ifaceName := range r.ifacesToRescan.All() { r.livenessCallback() err := r.resyncIface(nl, ifaceName) if err != nil { r.nl.MarkHandleForReopen() - return nil + continue } - return set.RemoveItem - }) + r.ifacesToRescan.Discard(ifaceName) + } return nil } @@ -1043,14 +1089,17 @@ func (r *RouteTable) resyncIface(nl netlinkshim.Interface, ifaceName string) err return nil } + // Mark to reprogram static ARP for this interface. + r.ifacesToARP.Add(ifaceName) + routeFilter := &netlink.Route{ Table: r.tableIndex, LinkIndex: ifIndex, } routeFilterFlags := netlink.RT_FILTER_OIF | netlink.RT_FILTER_TABLE - seenRoutes := set.New[kernelRouteKey]() - for attempt := 0; attempt < routeListFilterAttempts; attempt++ { + seenRoutes := set.New[RouteKey]() + for range routeListFilterAttempts { // Using the Iter version here saves allocating a large slice of netlink.Route, // which we immediately discard. var scratchRoute netlink.Route @@ -1064,11 +1113,11 @@ func (r *RouteTable) resyncIface(nl netlinkshim.Interface, ifaceName string) err return true } - kernKey, kernRoute := r.netlinkRouteToKernelRoute(&scratchRoute) - if oldRoute, ok := r.kernelRoutes.Dataplane().Get(kernKey); !ok || oldRoute.Equals(kernRoute) { - r.kernelRoutes.Dataplane().Set(kernKey, kernRoute) + routeKey, kernRoute := r.netlinkRouteToKernelRoute(&scratchRoute) + if oldRoute, ok := r.kernelRoutes.Dataplane().Get(routeKey); !ok || oldRoute.Equals(kernRoute) { + r.kernelRoutes.Dataplane().Set(routeKey, kernRoute) } - seenRoutes.Add(kernKey) + seenRoutes.Add(routeKey) return true }) if errors.Is(err, unix.EINTR) { @@ -1107,19 +1156,19 @@ func (r *RouteTable) resyncIface(nl netlinkshim.Interface, ifaceName string) err // Look for routes that the tracker says are there but are actually missing. for _, ifaceToRoutes := range r.ifaceToRoutes { - for cidr := range ifaceToRoutes[ifaceName] { - kernKey := r.routeKeyForCIDR(cidr) - if seenRoutes.Contains(kernKey) { + for _, target := range ifaceToRoutes[ifaceName] { + routeKey := r.normalizeRouteKey(target.RouteKey) + if seenRoutes.Contains(routeKey) { // Route still there; handled above. continue } - desKernRoute, ok := r.kernelRoutes.Desired().Get(kernKey) + desKernRoute, ok := r.kernelRoutes.Desired().Get(routeKey) if !ok || desKernRoute.Ifindex != ifIndex { // The interface we're syncing doesn't own this route // so the fact that it's missing is expected. continue } - r.kernelRoutes.Dataplane().Delete(kernKey) + r.kernelRoutes.Dataplane().Delete(routeKey) } } partialResyncTimeSummary.Observe(r.time.Since(startTime).Seconds()) @@ -1226,8 +1275,14 @@ func (r *RouteTable) refreshIfaceStateBestEffort(nl netlinkshim.Interface, iface return nil } -func (r *RouteTable) routeKeyForCIDR(cidr ip.CIDR) kernelRouteKey { - return kernelRouteKey{CIDR: cidr} +// normalizeRouteKey normalizes a RouteKey for the current IP version. For IPv6, +// the kernel treats priority 0 as a sigil meaning "use the default value", +// which is 1024. We normalize here so that routes round trip cleanly. +func (r *RouteTable) normalizeRouteKey(key RouteKey) RouteKey { + if r.ipVersion == 6 && key.Priority == 0 { + key.Priority = 1024 + } + return key } func (r *RouteTable) routeIsOurs(route *netlink.Route) bool { @@ -1263,7 +1318,7 @@ func (r *RouteTable) routeIsOurs(route *netlink.Route) bool { return true } -func (r *RouteTable) netlinkRouteToKernelRoute(route *netlink.Route) (kernKey kernelRouteKey, kernRoute kernelRoute) { +func (r *RouteTable) netlinkRouteToKernelRoute(route *netlink.Route) (routeKey RouteKey, kernRoute kernelRoute) { // Defensive; recent versions of netlink always return a CIDR, but just // in case that gets regressed... cidr := ip.CIDRFromIPNet(route.Dst) @@ -1275,7 +1330,7 @@ func (r *RouteTable) netlinkRouteToKernelRoute(route *netlink.Route) (kernKey ke } } - kernKey = kernelRouteKey{ + routeKey = RouteKey{ CIDR: cidr, Priority: route.Priority, TOS: route.Tos, @@ -1309,7 +1364,7 @@ func (r *RouteTable) netlinkRouteToKernelRoute(route *netlink.Route) (kernKey ke if log.IsLevelEnabled(log.DebugLevel) { r.logCxt.WithFields(log.Fields{ "kernRoute": kernRoute, - "kernKey": kernKey, + "routeKey": routeKey, }).Debug("Loaded route from kernel.") } return @@ -1349,45 +1404,45 @@ func (r *RouteTable) applyUpdates(attempt int) error { } // First clean up any old routes. - deletionErrs := map[kernelRouteKey]error{} - r.kernelRoutes.PendingDeletions().Iter(func(kernKey kernelRouteKey) deltatracker.IterAction { + deletionErrs := map[RouteKey]error{} + r.kernelRoutes.PendingDeletions().Iter(func(routeKey RouteKey) deltatracker.IterAction { r.livenessCallback() - kernRoute, _ := r.kernelRoutes.PendingDeletions().Get(kernKey) + kernRoute, _ := r.kernelRoutes.PendingDeletions().Get(routeKey) if r.ifaceInGracePeriod(kernRoute.Ifindex) { // Don't remove unexpected routes from interfaces created recently. r.logCxt.WithFields(log.Fields{ "route": kernRoute, - "dest": kernKey, + "dest": routeKey, }).Debug("Found unexpected route; ignoring due to grace period.") return deltatracker.IterActionNoOp } - err := r.deleteRoute(nl, kernKey) + err := r.deleteRoute(nl, routeKey) if err != nil { - deletionErrs[kernKey] = err + deletionErrs[routeKey] = err return deltatracker.IterActionNoOp } - r.conntrackTracker.OnDataplaneRouteDeleted(kernKey.CIDR, kernRoute.Ifindex) + r.conntrackTracker.OnDataplaneRouteDeleted(routeKey.CIDR, kernRoute.Ifindex) // Route is gone, clean up the dataplane side of the tracker. - r.logCxt.WithField("route", kernKey).Debug("Deleted route.") + r.logCxt.WithField("route", routeKey).Debug("Deleted route.") return deltatracker.IterActionUpdateDataplane }) // Now do a first pass of the routes that we want to create/update and // trigger any necessary conntrack cleanups for moved routes. - r.kernelRoutes.PendingUpdates().Iter(func(kernKey kernelRouteKey, kernRoute kernelRoute) deltatracker.IterAction { + r.kernelRoutes.PendingUpdates().Iter(func(routeKey RouteKey, kernRoute kernelRoute) deltatracker.IterAction { r.livenessCallback() - cidr := kernKey.CIDR - dataplaneRoute, dataplaneExists := r.kernelRoutes.Dataplane().Get(kernKey) + cidr := routeKey.CIDR + dataplaneRoute, dataplaneExists := r.kernelRoutes.Dataplane().Get(routeKey) if dataplaneExists && r.conntrackTracker.CIDRNeedsEarlyCleanup(cidr, dataplaneRoute.Ifindex) { - err := r.deleteRoute(nl, kernKey) + err := r.deleteRoute(nl, routeKey) if err != nil { - deletionErrs[kernKey] = err + deletionErrs[routeKey] = err return deltatracker.IterActionNoOp } // This will queue the route for conntrack cleanup. - r.conntrackTracker.OnDataplaneRouteDeleted(kernKey.CIDR, dataplaneRoute.Ifindex) + r.conntrackTracker.OnDataplaneRouteDeleted(routeKey.CIDR, dataplaneRoute.Ifindex) } return deltatracker.IterActionNoOp }) @@ -1396,25 +1451,25 @@ func (r *RouteTable) applyUpdates(attempt int) error { // time. r.conntrackTracker.StartConntrackCleanupAndReset() - updateErrs := map[kernelRouteKey]error{} - r.kernelRoutes.PendingUpdates().Iter(func(kernKey kernelRouteKey, kRoute kernelRoute) deltatracker.IterAction { + updateErrs := map[RouteKey]error{} + r.kernelRoutes.PendingUpdates().Iter(func(routeKey RouteKey, kRoute kernelRoute) deltatracker.IterAction { r.livenessCallback() - dst := kernKey.CIDR.ToIPNet() + dst := routeKey.CIDR.ToIPNet() flags := 0 if kRoute.OnLink { flags = unix.RTNH_F_ONLINK } // In case we're moving a route, wait for the cleanup to finish. - r.conntrackTracker.WaitForPendingDeletion(kernKey.CIDR) + r.conntrackTracker.WaitForPendingDeletion(routeKey.CIDR) nlRoute := &netlink.Route{ Family: r.netlinkFamily, Table: r.tableIndex, Dst: &dst, - Tos: kernKey.TOS, - Priority: int(kernKey.Priority), + Tos: routeKey.TOS, + Priority: int(routeKey.Priority), Type: kRoute.Type, Scope: kRoute.Scope, @@ -1434,7 +1489,7 @@ func (r *RouteTable) applyUpdates(attempt int) error { } r.logCxt.WithFields(log.Fields{ "nlRoute": nlRoute, - "ourKey": kernKey, + "ourKey": routeKey, "ourRoute": kRoute, }).Debug("Replacing route") err := nl.RouteReplace(nlRoute) @@ -1460,7 +1515,7 @@ func (r *RouteTable) applyUpdates(attempt int) error { err = fmt.Errorf("%v(%s): %w", kRoute, name, err) } - updateErrs[kernKey] = err + updateErrs[routeKey] = err return deltatracker.IterActionNoOp } @@ -1469,12 +1524,30 @@ func (r *RouteTable) applyUpdates(attempt int) error { }) arpErrs := map[string]error{} - for ifaceName, addrToMAC := range r.pendingARPs { - // Add static ARP entries (for workload endpoints). This may have been - // needed at one point but it no longer seems to be required. Leaving - // it here for two reasons: (1) there may be an obscure scenario where - // it is needed. (2) we have tests that monitor netlink, and they break - // if it is removed because they see the ARP traffic. + for ifaceName, addrToMAC := range r.permanentARPs { + if !r.ifacesToARP.Contains(ifaceName) { + continue + } + // Add static ARP entries (for workload endpoints). As far as we're aware, this + // helps in two ways. + // + // 1. It slightly reduces the TTFP (time to first ping) for a new endpoint, by + // removing the need for the host kernel to send an ARP request to the endpoint and + // to wait for its response. + // + // 2. It supports VMs with a restrictive arp_ignore setting. For example, we are + // aware of OpenStack VMs with arp_ignore set to 2, which means "reply only if the + // target IP address is local address configured on the incoming interface and both + // with the sender's IP address are part from same subnet on this interface" - which + // will never be True for the way that Calico provisions VM IPs. + // + // For (2) it is important that Felix maintains the ARP programming for an interface + // beyond the point when that interface is first configured. In particular, dnsmasq + // overwrites our ARP programming - with an entry that is identical to ours, except + // without the PERMANENT flag - when it receives a DHCP request from an OpenStack VM + // and issues its IP address. Also interface flaps can lose the ARP programming. + // In all such cases, our PERMANENT static ARP programming needs to be reinstated; + // otherwise we lose connectivity to VMs with arp_ignore=2. ifaceIdx, ok := r.ifaceIndexForName(ifaceName) if !ok { // Asked to add ARP entries but the interface isn't known (yet). @@ -1482,6 +1555,7 @@ func (r *RouteTable) applyUpdates(attempt int) error { // datastore stops asking us to add ARP entries for this interface. continue } + ifaceARPDone := true for addr, mac := range addrToMAC { r.livenessCallback() err := r.addStaticARPEntry(nl, addr, mac, ifaceIdx) @@ -1506,12 +1580,13 @@ func (r *RouteTable) applyUpdates(attempt int) error { if err != nil { log.WithError(err).Debug("Failed to add neighbor entry.") arpErrs[fmt.Sprintf("%s/%s", ifaceName, addr)] = err - } else { - delete(addrToMAC, addr) + ifaceARPDone = false } } - if len(addrToMAC) == 0 { - delete(r.pendingARPs, ifaceName) + if ifaceARPDone { + // Don't need to program ARP again for this interface until its state + // changes or we're asked to do a full resync. + r.ifacesToARP.Discard(ifaceName) } } @@ -1563,19 +1638,19 @@ func (r *RouteTable) ifaceInGracePeriod(ifindex int) bool { return r.time.Since(graceInf.FirstSeen) < r.routeCleanupGracePeriod } -func (r *RouteTable) deleteRoute(nl netlinkshim.Interface, kernKey kernelRouteKey) error { +func (r *RouteTable) deleteRoute(nl netlinkshim.Interface, routeKey RouteKey) error { // Template route for deletion. The family, table, TOS, Priority uniquely // identify the route, but we also need to set some fields to their "wildcard" // values (found via code reading the kernel and running "ip route del" under // strace). - dst := kernKey.CIDR.ToIPNet() + dst := routeKey.CIDR.ToIPNet() nlRoute := &netlink.Route{ Family: r.netlinkFamily, Table: r.tableIndex, Dst: &dst, - Tos: kernKey.TOS, - Priority: kernKey.Priority, + Tos: routeKey.TOS, + Priority: routeKey.Priority, Protocol: unix.RTPROT_UNSPEC, // Wildcard (but also zero value). Scope: unix.RT_SCOPE_NOWHERE, // Wildcard. Note: non-zero value! @@ -1583,7 +1658,7 @@ func (r *RouteTable) deleteRoute(nl netlinkshim.Interface, kernKey kernelRouteKe } err := nl.RouteDel(nlRoute) if errors.Is(err, unix.ESRCH) { - r.logCxt.WithField("route", kernKey).Debug("Tried to delete route but it wasn't found.") + r.logCxt.WithField("route", routeKey).Debug("Tried to delete route but it wasn't found.") err = nil // Already gone (we hope). } return err @@ -1721,26 +1796,6 @@ func (r *RouteTable) checkTargets(ifaceName string, targets ...Target) { } } -// kernelRouteKey represents the kernel's FIB key. The kernel allows routes -// to coexist as long as they have different keys. -type kernelRouteKey struct { - // Destination CIDR; route matches traffic to this destination. - CIDR ip.CIDR - // TOS is the Type-of-Service field. For example, one app may mark its - // packets as "high importance" and that will take a different route to - // another app. - // - // Kernel uses the TOS=0 route if there isn't a more precise match. - TOS int - // Priority is the routing metric / distance. Given two routes with the - // same CIDR, the kernel prefers the route with the _lower_ priority. - Priority int -} - -func (k kernelRouteKey) String() string { - return fmt.Sprintf("%s(tos=%x metric=%d)", k.CIDR.String(), k.TOS, k.Priority) -} - // kernelRoute is our low-level representation of the parts of a route that // we care to program. It contains fields that we can easily read back from the // kernel for comparison. In particular, we track the interface index instead diff --git a/felix/routetable/route_table_test.go b/felix/routetable/route_table_test.go index 2ddd8ff665f..6135f6ea65c 100644 --- a/felix/routetable/route_table_test.go +++ b/felix/routetable/route_table_test.go @@ -20,7 +20,7 @@ import ( "syscall" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/vishvananda/netlink" "golang.org/x/sys/unix" @@ -35,6 +35,18 @@ import ( "github.com/projectcalico/calico/felix/timeshim/mocktime" ) +const routePriorityForTest int = 48931 + +// routeKeyStr builds a mock dataplane route key string that includes table, CIDR, and priority. +func routeKeyStr(table int, cidr string, priority int) string { + return fmt.Sprintf("%d-%s-%d", table, cidr, priority) +} + +// mainRouteKey builds a route key string for the main routing table with the standard test priority. +func mainRouteKey(cidr string) string { + return routeKeyStr(254, cidr, routePriorityForTest) +} + var ( FelixRouteProtocol = netlink.RouteProtocol(syscall.RTPROT_BOOT) @@ -85,12 +97,14 @@ var _ = Describe("RouteTable v6", func() { It("should use interface index 1 for no-iface routes", func() { rt.RouteUpdate(RouteClassWireguard, InterfaceNone, Target{ - CIDR: ip.MustParseCIDROrIP("f00f::/128"), + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("f00f::/128"), + }, Type: TargetTypeThrow, }) err := rt.Apply() Expect(err).ToNot(HaveOccurred()) - Expect(dataplane.RouteKeyToRoute["254-f00f::/128"]).To(Equal( + Expect(dataplane.RouteKeyToRoute[routeKeyStr(254, "f00f::/128", 1024)]).To(Equal( netlink.Route{ Family: netlink.FAMILY_V6, LinkIndex: 1, @@ -99,6 +113,7 @@ var _ = Describe("RouteTable v6", func() { Protocol: syscall.RTPROT_BOOT, Scope: netlink.SCOPE_UNIVERSE, Table: unix.RT_TABLE_MAIN, + Priority: 1024, }, )) }) @@ -115,12 +130,16 @@ var _ = Describe("RouteTable v6", func() { Scope: netlink.SCOPE_LINK, Table: unix.RT_TABLE_MAIN, } - rt.SetRoutes(RouteClassLocalWorkload, noopLink.LinkAttrs.Name, []Target{ - {CIDR: ip.MustParseCIDROrIP("10.0.0.4/32"), DestMAC: mac1}, - }) + rt.SetRoutes(RouteClassLocalWorkload, noopLink.LinkAttrs.Name, []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.4/32"), + Priority: routePriorityForTest, + }, + DestMAC: mac1, + }}) dataplane.AddMockRoute(&noopRoute) - // Route that should be deleted. + // Routes that should be deleted. deleteLink := dataplane.AddIface(5, "cali5", true, true) deleteRoute := netlink.Route{ LinkIndex: deleteLink.LinkAttrs.Index, @@ -131,12 +150,49 @@ var _ = Describe("RouteTable v6", func() { Table: unix.RT_TABLE_MAIN, } dataplane.AddMockRoute(&deleteRoute) + deleteRoute2 := netlink.Route{ + LinkIndex: deleteLink.LinkAttrs.Index, + Dst: mustParseCIDR("10.0.0.2/32"), + Type: syscall.RTN_UNICAST, + Protocol: FelixRouteProtocol, + Scope: netlink.SCOPE_LINK, + Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, + } + dataplane.AddMockRoute(&deleteRoute2) err := rt.Apply() Expect(err).ToNot(HaveOccurred()) Expect(dataplane.DeletedRouteKeys).ToNot(HaveKey(mocknetlink.KeyForRoute(&noopRoute))) Expect(dataplane.UpdatedRouteKeys).ToNot(HaveKey(mocknetlink.KeyForRoute(&noopRoute))) Expect(dataplane.DeletedRouteKeys).To(HaveKey(mocknetlink.KeyForRoute(&deleteRoute))) + Expect(dataplane.DeletedRouteKeys).To(HaveKey(mocknetlink.KeyForRoute(&deleteRoute2))) + }) + + It("should normalize IPv6 Priority 0 to 1024 in RouteRemove", func() { + // Add a route with Priority 0 — internally, routeKeyForTarget normalizes this to 1024 for IPv6. + link := dataplane.AddIface(10, "cali10", true, true) + rt.OnIfaceStateChanged(link.LinkAttrs.Name, link.LinkAttrs.Index, ifacemonitor.StateUp) + + rt.RouteUpdate(RouteClassLocalWorkload, link.LinkAttrs.Name, Target{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("fd00::1/128"), + // Priority 0 — will be normalized to 1024. + }, + Type: TargetTypeVXLAN, + }) + err := rt.Apply() + Expect(err).ToNot(HaveOccurred()) + Expect(dataplane.RouteKeyToRoute).To(HaveKey(routeKeyStr(254, "fd00::1/128", 1024))) + + // Now remove using Priority 0 again — RouteRemove should normalize it to 1024. + rt.RouteRemove(RouteClassLocalWorkload, link.LinkAttrs.Name, RouteKey{ + CIDR: ip.MustParseCIDROrIP("fd00::1/128"), + // Priority 0 — should be normalized to 1024 to match the stored route. + }) + err = rt.Apply() + Expect(err).ToNot(HaveOccurred()) + Expect(dataplane.RouteKeyToRoute).NotTo(HaveKey(routeKeyStr(254, "fd00::1/128", 1024))) }) }) @@ -208,6 +264,7 @@ var _ = Describe("RouteTable", func() { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_LINK, Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, } dataplane.AddMockRoute(&cali1Route) cali3Route = netlink.Route{ @@ -218,6 +275,7 @@ var _ = Describe("RouteTable", func() { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_LINK, Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, } dataplane.AddMockRoute(&cali3Route) gatewayRoute = netlink.Route{ @@ -282,11 +340,16 @@ var _ = Describe("RouteTable", func() { Scope: netlink.SCOPE_LINK, Src: net.ParseIP("192.168.0.1"), Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, } dataplane.AddMockRoute(&updateRoute) - rt.SetRoutes(RouteClassLocalWorkload, updateLink.LinkAttrs.Name, []Target{ - {CIDR: ip.MustParseCIDROrIP("10.0.0.5"), DestMAC: mac1}, - }) + rt.SetRoutes(RouteClassLocalWorkload, updateLink.LinkAttrs.Name, []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.5"), + Priority: routePriorityForTest, + }, + DestMAC: mac1, + }}) fixedRoute := updateRoute fixedRoute.Src = nil @@ -331,13 +394,17 @@ var _ = Describe("RouteTable", func() { It("Should add routes with a source address", func() { // Route that needs to be added addLink := dataplane.AddIface(6, "cali6", true, true) - rt.SetRoutes(RouteClassLocalWorkload, addLink.LinkAttrs.Name, []Target{ - {CIDR: ip.MustParseCIDROrIP("10.0.0.6"), DestMAC: mac1}, - }) + rt.SetRoutes(RouteClassLocalWorkload, addLink.LinkAttrs.Name, []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.6"), + Priority: routePriorityForTest, + }, + DestMAC: mac1, + }}) err := rt.Apply() Expect(err).ToNot(HaveOccurred()) cidr := mustParseCIDR("10.0.0.6/32") - Expect(dataplane.RouteKeyToRoute["254-10.0.0.6/32"]).To(Equal(netlink.Route{ + Expect(dataplane.RouteKeyToRoute[mainRouteKey("10.0.0.6/32")]).To(Equal(netlink.Route{ Family: unix.AF_INET, LinkIndex: addLink.LinkAttrs.Index, Dst: cidr, @@ -346,6 +413,7 @@ var _ = Describe("RouteTable", func() { Scope: netlink.SCOPE_LINK, Src: deviceRouteSourceAddress, Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, })) dataplane.ExpectNeighs(unix.AF_INET, netlink.Neigh{ Family: unix.AF_INET, @@ -356,31 +424,89 @@ var _ = Describe("RouteTable", func() { HardwareAddr: mac1, }) }) + Context("after initial route programming", func() { + var cidr *net.IPNet + var linkIndex int + BeforeEach(func() { + // Initial route programming... + addLink := dataplane.AddIface(6, "cali6", true, true) + linkIndex = addLink.LinkAttrs.Index + rt.SetRoutes(RouteClassLocalWorkload, addLink.LinkAttrs.Name, []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.6"), + Priority: routePriorityForTest, + }, + DestMAC: mac1, + }}) + err := rt.Apply() + Expect(err).ToNot(HaveOccurred()) + cidr = mustParseCIDR("10.0.0.6/32") + }) + It("ARP entry should exist", func() { + dataplane.ExpectNeighs(unix.AF_INET, netlink.Neigh{ + Family: unix.AF_INET, + LinkIndex: linkIndex, + State: netlink.NUD_PERMANENT, + Type: unix.RTN_UNICAST, + IP: cidr.IP, + HardwareAddr: mac1, + }) + }) + It("ARP entry should be reestablished by a resync", func() { + dataplane.RemoveNeighs(unix.AF_INET, netlink.Neigh{ + Family: unix.AF_INET, + LinkIndex: linkIndex, + State: netlink.NUD_PERMANENT, + Type: unix.RTN_UNICAST, + IP: cidr.IP, + HardwareAddr: mac1, + }) + rt.QueueResync() + err := rt.Apply() + Expect(err).NotTo(HaveOccurred()) + dataplane.ExpectNeighs(unix.AF_INET, netlink.Neigh{ + Family: unix.AF_INET, + LinkIndex: linkIndex, + State: netlink.NUD_PERMANENT, + Type: unix.RTN_UNICAST, + IP: cidr.IP, + HardwareAddr: mac1, + }) + }) + }) It("Should skip adding an ARP entry if route is deleted via SetRoutes before sync", func() { // Route that needs to be added link := dataplane.AddIface(6, "cali6", true, true) - rt.SetRoutes(RouteClassLocalWorkload, link.LinkAttrs.Name, []Target{ - {CIDR: ip.MustParseCIDROrIP("10.0.0.6"), DestMAC: mac1}, - }) + rt.SetRoutes(RouteClassLocalWorkload, link.LinkAttrs.Name, []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.6"), + Priority: routePriorityForTest, + }, + DestMAC: mac1, + }}) rt.SetRoutes(RouteClassLocalWorkload, link.LinkAttrs.Name, nil) err := rt.Apply() Expect(err).ToNot(HaveOccurred()) - Expect(dataplane.RouteKeyToRoute).NotTo(HaveKey("254-10.0.0.6/32")) + Expect(dataplane.RouteKeyToRoute).NotTo(HaveKey(mainRouteKey("10.0.0.6/32"))) dataplane.ExpectNeighs(unix.AF_INET) }) It("Should skip adding an ARP entry if route is deleted via RouteRemove before sync", func() { // Route that needs to be added link := dataplane.AddIface(6, "cali6", true, true) cidr := ip.MustParseCIDROrIP("10.0.0.6") - rt.SetRoutes(RouteClassLocalWorkload, link.LinkAttrs.Name, []Target{ - {CIDR: cidr, DestMAC: mac1}, - }) - rt.RouteRemove(RouteClassLocalWorkload, link.LinkAttrs.Name, cidr) + rt.SetRoutes(RouteClassLocalWorkload, link.LinkAttrs.Name, []Target{{ + RouteKey: RouteKey{ + CIDR: cidr, + Priority: routePriorityForTest, + }, + DestMAC: mac1, + }}) + rt.RouteRemove(RouteClassLocalWorkload, link.LinkAttrs.Name, RouteKey{CIDR: cidr, Priority: routePriorityForTest}) err := rt.Apply() Expect(err).ToNot(HaveOccurred()) - Expect(dataplane.RouteKeyToRoute).NotTo(HaveKey("254-10.0.0.6/32")) + Expect(dataplane.RouteKeyToRoute).NotTo(HaveKey(mainRouteKey("10.0.0.6/32"))) dataplane.ExpectNeighs(unix.AF_INET) }) It("Should not remove routes with a source address", func() { @@ -396,10 +522,15 @@ var _ = Describe("RouteTable", func() { Scope: netlink.SCOPE_LINK, Src: deviceRouteSourceAddress, Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, } - rt.SetRoutes(RouteClassLocalWorkload, noopLink.LinkAttrs.Name, []Target{ - {CIDR: ip.MustParseCIDROrIP("10.0.0.4/32"), DestMAC: mac1}, - }) + rt.SetRoutes(RouteClassLocalWorkload, noopLink.LinkAttrs.Name, []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.4/32"), + Priority: routePriorityForTest, + }, + DestMAC: mac1, + }}) dataplane.AddMockRoute(&noopRoute) err := rt.Apply() @@ -427,10 +558,15 @@ var _ = Describe("RouteTable", func() { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_LINK, Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, } - rt.SetRoutes(RouteClassLocalWorkload, updateLink.LinkAttrs.Name, []Target{ - {CIDR: ip.MustParseCIDROrIP("10.0.0.5"), DestMAC: mac1}, - }) + rt.SetRoutes(RouteClassLocalWorkload, updateLink.LinkAttrs.Name, []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.5"), + Priority: routePriorityForTest, + }, + DestMAC: mac1, + }}) dataplane.AddMockRoute(&updateRoute) fixedRoute := updateRoute @@ -464,10 +600,15 @@ var _ = Describe("RouteTable", func() { Scope: netlink.SCOPE_LINK, Src: net.ParseIP("192.168.0.2"), Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, } - rt.SetRoutes(RouteClassLocalWorkload, updateLink.LinkAttrs.Name, []Target{ - {CIDR: ip.MustParseCIDROrIP("10.0.0.5"), DestMAC: mac1}, - }) + rt.SetRoutes(RouteClassLocalWorkload, updateLink.LinkAttrs.Name, []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.5"), + Priority: routePriorityForTest, + }, + DestMAC: mac1, + }}) dataplane.AddMockRoute(&updateRoute) fixedRoute := updateRoute @@ -498,10 +639,16 @@ var _ = Describe("RouteTable", func() { Scope: netlink.SCOPE_LINK, Src: net.ParseIP("192.168.0.2"), Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, } - rt.SetRoutes(RouteClassLocalWorkload, noopLink.LinkAttrs.Name, []Target{ - {CIDR: ip.MustParseCIDROrIP("10.0.0.5"), DestMAC: mac1, Src: ip.FromString("192.168.0.2")}, - }) + rt.SetRoutes(RouteClassLocalWorkload, noopLink.LinkAttrs.Name, []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.5"), + Priority: routePriorityForTest, + }, + DestMAC: mac1, + Src: ip.FromString("192.168.0.2"), + }}) dataplane.AddMockRoute(&noopRoute) err := rt.Apply() Expect(err).ToNot(HaveOccurred()) @@ -519,10 +666,16 @@ var _ = Describe("RouteTable", func() { Scope: netlink.SCOPE_LINK, Src: net.ParseIP("192.168.0.2"), Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, } - rt.SetRoutes(RouteClassLocalWorkload, noopLink.LinkAttrs.Name, []Target{ - {CIDR: ip.MustParseCIDROrIP("10.0.0.5"), DestMAC: mac1, Src: ip.FromString("192.168.0.3")}, - }) + rt.SetRoutes(RouteClassLocalWorkload, noopLink.LinkAttrs.Name, []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.5"), + Priority: routePriorityForTest, + }, + DestMAC: mac1, + Src: ip.FromString("192.168.0.3"), + }}) dataplane.AddMockRoute(&noopRoute) err := rt.Apply() Expect(err).ToNot(HaveOccurred()) @@ -564,12 +717,16 @@ var _ = Describe("RouteTable", func() { It("Should add routes with a protocol", func() { // Route that needs to be added addLink := dataplane.AddIface(6, "cali6", true, true) - rt.SetRoutes(RouteClassLocalWorkload, addLink.LinkAttrs.Name, []Target{ - {CIDR: ip.MustParseCIDROrIP("10.0.0.6"), DestMAC: mac1}, - }) + rt.SetRoutes(RouteClassLocalWorkload, addLink.LinkAttrs.Name, []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.6"), + Priority: routePriorityForTest, + }, + DestMAC: mac1, + }}) err := rt.Apply() Expect(err).ToNot(HaveOccurred()) - Expect(dataplane.RouteKeyToRoute["254-10.0.0.6/32"]).To(Equal(netlink.Route{ + Expect(dataplane.RouteKeyToRoute[mainRouteKey("10.0.0.6/32")]).To(Equal(netlink.Route{ Family: unix.AF_INET, LinkIndex: addLink.LinkAttrs.Index, Dst: mustParseCIDR("10.0.0.6/32"), @@ -577,13 +734,17 @@ var _ = Describe("RouteTable", func() { Protocol: deviceRouteProtocol, Scope: netlink.SCOPE_LINK, Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, })) By("Reading back the route") Expect(rt.ReadRoutesFromKernel(addLink.LinkAttrs.Name)).To(ConsistOf( Target{ - Type: TargetTypeLinkLocalUnicast, - CIDR: ip.MustParseCIDROrIP("10.0.0.6"), + Type: TargetTypeLinkLocalUnicast, + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.6"), + Priority: routePriorityForTest, + }, Protocol: deviceRouteProtocol, }), ) @@ -592,25 +753,26 @@ var _ = Describe("RouteTable", func() { // Route that needs to be added addLink := dataplane.AddIface(6, "cali6", true, true) addLink2 := dataplane.AddIface(7, "cali7", true, true) - rt.SetRoutes(RouteClassLocalWorkload, InterfaceNone, []Target{ - { - Type: TargetTypeVXLAN, - CIDR: ip.MustParseCIDROrIP("10.0.0.0/24"), - MultiPath: []NextHop{ - { - IfaceName: addLink.LinkAttrs.Name, - Gw: ip.FromString("10.0.0.6"), - }, - { - IfaceName: addLink2.LinkAttrs.Name, - Gw: ip.FromString("10.0.0.7"), - }, + rt.SetRoutes(RouteClassLocalWorkload, InterfaceNone, []Target{{ + Type: TargetTypeVXLAN, + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.0/24"), + Priority: routePriorityForTest, + }, + MultiPath: []NextHop{ + { + IfaceName: addLink.LinkAttrs.Name, + Gw: ip.FromString("10.0.0.6"), + }, + { + IfaceName: addLink2.LinkAttrs.Name, + Gw: ip.FromString("10.0.0.7"), }, }, - }) + }}) err := rt.Apply() Expect(err).ToNot(HaveOccurred()) - Expect(dataplane.RouteKeyToRoute["254-10.0.0.0/24"]).To(Equal(netlink.Route{ + Expect(dataplane.RouteKeyToRoute[mainRouteKey("10.0.0.0/24")]).To(Equal(netlink.Route{ Family: unix.AF_INET, LinkIndex: 0, Dst: mustParseCIDR("10.0.0.0/24"), @@ -631,13 +793,17 @@ var _ = Describe("RouteTable", func() { Flags: syscall.RTNH_F_ONLINK, }, }, + Priority: routePriorityForTest, })) By("Reading back the route") Expect(rt.ReadRoutesFromKernel(InterfaceNone)).To(ConsistOf( Target{ - Type: TargetTypeVXLAN, - CIDR: ip.MustParseCIDROrIP("10.0.0.0/24"), + Type: TargetTypeVXLAN, + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.0/24"), + Priority: routePriorityForTest, + }, Protocol: deviceRouteProtocol, MultiPath: []NextHop{ { @@ -658,27 +824,28 @@ var _ = Describe("RouteTable", func() { addLink2 := dataplane.AddIface(7, "cali7", false, false) By("Setting routes") - rt.SetRoutes(RouteClassLocalWorkload, InterfaceNone, []Target{ - { - Type: TargetTypeVXLAN, - CIDR: ip.MustParseCIDROrIP("10.0.0.0/24"), - MultiPath: []NextHop{ - { - IfaceName: addLink.LinkAttrs.Name, - Gw: ip.FromString("10.0.0.6"), - }, - { - IfaceName: addLink2.LinkAttrs.Name, - Gw: ip.FromString("10.0.0.7"), - }, + rt.SetRoutes(RouteClassLocalWorkload, InterfaceNone, []Target{{ + Type: TargetTypeVXLAN, + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.0/24"), + Priority: routePriorityForTest, + }, + MultiPath: []NextHop{ + { + IfaceName: addLink.LinkAttrs.Name, + Gw: ip.FromString("10.0.0.6"), + }, + { + IfaceName: addLink2.LinkAttrs.Name, + Gw: ip.FromString("10.0.0.7"), }, }, - }) + }}) By("Apply") err := rt.Apply() Expect(err).ToNot(HaveOccurred()) - Expect(dataplane.RouteKeyToRoute["254-10.0.0.0/24"]).To(BeZero()) + Expect(dataplane.RouteKeyToRoute[mainRouteKey("10.0.0.0/24")]).To(BeZero()) By("Bringing interfaces up") dataplane.SetIface("cali6", true, true) @@ -710,8 +877,9 @@ var _ = Describe("RouteTable", func() { Flags: syscall.RTNH_F_ONLINK, }, }, + Priority: routePriorityForTest, } - Expect(dataplane.RouteKeyToRoute["254-10.0.0.0/24"]).To(Equal(expectedRoute)) + Expect(dataplane.RouteKeyToRoute[mainRouteKey("10.0.0.0/24")]).To(Equal(expectedRoute)) By("Bringing one interface down") dataplane.SetIface("cali6", false, false) @@ -722,7 +890,7 @@ var _ = Describe("RouteTable", func() { Expect(err).ToNot(HaveOccurred()) // It's ok to have one interface down on a multi-path route. // Kernel keeps the route in place. - Expect(dataplane.RouteKeyToRoute["254-10.0.0.0/24"]).To(Equal(expectedRoute), + Expect(dataplane.RouteKeyToRoute[mainRouteKey("10.0.0.0/24")]).To(Equal(expectedRoute), "Route should not be removed when only one interface is down") By("Bringing other interface down") @@ -734,32 +902,33 @@ var _ = Describe("RouteTable", func() { Expect(err).ToNot(HaveOccurred()) // The kernel will remove the route once all interfaces go down // so we do the same to stay in sync. - Expect(dataplane.RouteKeyToRoute["254-10.0.0.0/24"]).To(BeZero(), + Expect(dataplane.RouteKeyToRoute[mainRouteKey("10.0.0.0/24")]).To(BeZero(), "Route should be removed when all interfaces are down") }) It("Should add/remove multi-path routes when interface creted/deleted", func() { By("Setting routes") - rt.SetRoutes(RouteClassLocalWorkload, InterfaceNone, []Target{ - { - Type: TargetTypeVXLAN, - CIDR: ip.MustParseCIDROrIP("10.0.0.0/24"), - MultiPath: []NextHop{ - { - IfaceName: "cali6", - Gw: ip.FromString("10.0.0.6"), - }, - { - IfaceName: "cali7", - Gw: ip.FromString("10.0.0.7"), - }, + rt.SetRoutes(RouteClassLocalWorkload, InterfaceNone, []Target{{ + Type: TargetTypeVXLAN, + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.0/24"), + Priority: routePriorityForTest, + }, + MultiPath: []NextHop{ + { + IfaceName: "cali6", + Gw: ip.FromString("10.0.0.6"), + }, + { + IfaceName: "cali7", + Gw: ip.FromString("10.0.0.7"), }, }, - }) + }}) By("Apply") err := rt.Apply() Expect(err).ToNot(HaveOccurred()) - Expect(dataplane.RouteKeyToRoute["254-10.0.0.0/24"]).To(BeZero()) + Expect(dataplane.RouteKeyToRoute[mainRouteKey("10.0.0.0/24")]).To(BeZero()) By("Creating one interface") addLink := dataplane.AddIface(6, "cali6", false, false) @@ -769,7 +938,7 @@ var _ = Describe("RouteTable", func() { By("Apply") err = rt.Apply() Expect(err).ToNot(HaveOccurred()) - Expect(dataplane.RouteKeyToRoute["254-10.0.0.0/24"]).To(BeZero()) + Expect(dataplane.RouteKeyToRoute[mainRouteKey("10.0.0.0/24")]).To(BeZero()) By("Creating other interface") addLink2 := dataplane.AddIface(7, "cali7", false, false) @@ -800,8 +969,9 @@ var _ = Describe("RouteTable", func() { Flags: syscall.RTNH_F_ONLINK, }, }, + Priority: routePriorityForTest, } - Expect(dataplane.RouteKeyToRoute["254-10.0.0.0/24"]).To(Equal(expectedRoute)) + Expect(dataplane.RouteKeyToRoute[mainRouteKey("10.0.0.0/24")]).To(Equal(expectedRoute)) By("Deleting one interface") dataplane.DelIface("cali6") @@ -812,19 +982,28 @@ var _ = Describe("RouteTable", func() { Expect(err).ToNot(HaveOccurred()) // Can't have a route with a deleted interface. The ifindex becomes // invalid. - Expect(dataplane.RouteKeyToRoute["254-10.0.0.0/24"]).To(BeZero(), + Expect(dataplane.RouteKeyToRoute[mainRouteKey("10.0.0.0/24")]).To(BeZero(), "Route should be removed when one interface deleted") }) It("Should add multiple routes with a protocol", func() { // Route that needs to be added addLink := dataplane.AddIface(6, "cali6", true, true) - rt.SetRoutes(RouteClassLocalWorkload, addLink.LinkAttrs.Name, []Target{ - {CIDR: ip.MustParseCIDROrIP("10.0.0.6"), DestMAC: mac1}, - {CIDR: ip.MustParseCIDROrIP("10.0.0.7"), DestMAC: mac1}, - }) + rt.SetRoutes(RouteClassLocalWorkload, addLink.LinkAttrs.Name, []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.6"), + Priority: routePriorityForTest, + }, + DestMAC: mac1, + }, { + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.7"), + Priority: routePriorityForTest, + }, + DestMAC: mac1, + }}) err := rt.Apply() Expect(err).ToNot(HaveOccurred()) - Expect(dataplane.RouteKeyToRoute["254-10.0.0.6/32"]).To(Equal(netlink.Route{ + Expect(dataplane.RouteKeyToRoute[mainRouteKey("10.0.0.6/32")]).To(Equal(netlink.Route{ Family: unix.AF_INET, LinkIndex: addLink.LinkAttrs.Index, Dst: mustParseCIDR("10.0.0.6/32"), @@ -832,8 +1011,9 @@ var _ = Describe("RouteTable", func() { Protocol: deviceRouteProtocol, Scope: netlink.SCOPE_LINK, Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, })) - Expect(dataplane.RouteKeyToRoute["254-10.0.0.7/32"]).To(Equal(netlink.Route{ + Expect(dataplane.RouteKeyToRoute[mainRouteKey("10.0.0.7/32")]).To(Equal(netlink.Route{ Family: unix.AF_INET, LinkIndex: addLink.LinkAttrs.Index, Dst: mustParseCIDR("10.0.0.7/32"), @@ -841,15 +1021,25 @@ var _ = Describe("RouteTable", func() { Protocol: deviceRouteProtocol, Scope: netlink.SCOPE_LINK, Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, })) }) It("Should add multiple routes with a protocol after persistent failures", func() { // Route that needs to be added addLink := dataplane.AddIface(6, "cali6", true, true) - rt.SetRoutes(RouteClassLocalWorkload, addLink.LinkAttrs.Name, []Target{ - {CIDR: ip.MustParseCIDROrIP("10.0.0.6"), DestMAC: mac1}, - {CIDR: ip.MustParseCIDROrIP("10.0.0.7"), DestMAC: mac1}, - }) + rt.SetRoutes(RouteClassLocalWorkload, addLink.LinkAttrs.Name, []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.6"), + Priority: routePriorityForTest, + }, + DestMAC: mac1, + }, { + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.7"), + Priority: routePriorityForTest, + }, + DestMAC: mac1, + }}) // Persist failures, this will apply the deltas to the cache but will be out of sync with the dataplane. dataplane.FailuresToSimulate = mocknetlink.FailNextRouteAdd | mocknetlink.FailNextRouteReplace dataplane.PersistFailures = true @@ -861,7 +1051,7 @@ var _ = Describe("RouteTable", func() { dataplane.PersistFailures = false err = rt.Apply() Expect(err).NotTo(HaveOccurred()) - Expect(dataplane.RouteKeyToRoute["254-10.0.0.6/32"]).To(Equal(netlink.Route{ + Expect(dataplane.RouteKeyToRoute[mainRouteKey("10.0.0.6/32")]).To(Equal(netlink.Route{ Family: unix.AF_INET, LinkIndex: addLink.LinkAttrs.Index, Dst: mustParseCIDR("10.0.0.6/32"), @@ -869,8 +1059,9 @@ var _ = Describe("RouteTable", func() { Protocol: deviceRouteProtocol, Scope: netlink.SCOPE_LINK, Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, })) - Expect(dataplane.RouteKeyToRoute["254-10.0.0.7/32"]).To(Equal(netlink.Route{ + Expect(dataplane.RouteKeyToRoute[mainRouteKey("10.0.0.7/32")]).To(Equal(netlink.Route{ Family: unix.AF_INET, LinkIndex: addLink.LinkAttrs.Index, Dst: mustParseCIDR("10.0.0.7/32"), @@ -878,6 +1069,7 @@ var _ = Describe("RouteTable", func() { Protocol: deviceRouteProtocol, Scope: netlink.SCOPE_LINK, Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, })) }) It("Should not remove routes with a protocol", func() { @@ -891,10 +1083,15 @@ var _ = Describe("RouteTable", func() { Protocol: deviceRouteProtocol, Scope: netlink.SCOPE_LINK, Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, } - rt.SetRoutes(RouteClassLocalWorkload, noopLink.LinkAttrs.Name, []Target{ - {CIDR: ip.MustParseCIDROrIP("10.0.0.4/32"), DestMAC: mac1}, - }) + rt.SetRoutes(RouteClassLocalWorkload, noopLink.LinkAttrs.Name, []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.4/32"), + Priority: routePriorityForTest, + }, + DestMAC: mac1, + }}) dataplane.AddMockRoute(&noopRoute) err := rt.Apply() @@ -912,10 +1109,15 @@ var _ = Describe("RouteTable", func() { Type: syscall.RTN_UNICAST, Scope: netlink.SCOPE_LINK, Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, } - rt.SetRoutes(RouteClassLocalWorkload, updateLink.LinkAttrs.Name, []Target{ - {CIDR: ip.MustParseCIDROrIP("10.0.0.5"), DestMAC: mac1}, - }) + rt.SetRoutes(RouteClassLocalWorkload, updateLink.LinkAttrs.Name, []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.5"), + Priority: routePriorityForTest, + }, + DestMAC: mac1, + }}) dataplane.AddMockRoute(&updateRoute) fixedRoute := updateRoute @@ -938,10 +1140,15 @@ var _ = Describe("RouteTable", func() { Protocol: 64, Scope: netlink.SCOPE_LINK, Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, } - rt.SetRoutes(RouteClassLocalWorkload, updateLink.LinkAttrs.Name, []Target{ - {CIDR: ip.MustParseCIDROrIP("10.0.0.5"), DestMAC: mac1}, - }) + rt.SetRoutes(RouteClassLocalWorkload, updateLink.LinkAttrs.Name, []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.5"), + Priority: routePriorityForTest, + }, + DestMAC: mac1, + }}) dataplane.AddMockRoute(&updateRoute) fixedRoute := updateRoute @@ -965,9 +1172,13 @@ var _ = Describe("RouteTable", func() { err := rt.Apply() Expect(err).ToNot(HaveOccurred()) // We try to add 10.0.0.1 back in. - rt.SetRoutes(RouteClassLocalWorkload, "cali1", []Target{ - {CIDR: ip.MustParseCIDROrIP("10.0.0.1/32"), DestMAC: mac1}, - }) + rt.SetRoutes(RouteClassLocalWorkload, "cali1", []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.1/32"), + Priority: routePriorityForTest, + }, + DestMAC: mac1, + }}) start := time.Now() err = rt.Apply() Expect(err).ToNot(HaveOccurred()) @@ -979,9 +1190,13 @@ var _ = Describe("RouteTable", func() { err := rt.Apply() Expect(err).ToNot(HaveOccurred()) // We try to add 10.0.0.10, which hasn't been seen before. - rt.SetRoutes(RouteClassLocalWorkload, "cali1", []Target{ - {CIDR: ip.MustParseCIDROrIP("10.0.0.10/32"), DestMAC: mac1}, - }) + rt.SetRoutes(RouteClassLocalWorkload, "cali1", []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.10/32"), + Priority: routePriorityForTest, + }, + DestMAC: mac1, + }}) start := time.Now() err = rt.Apply() Expect(err).ToNot(HaveOccurred()) @@ -1008,7 +1223,10 @@ var _ = Describe("RouteTable", func() { dataplane.FailuresToSimulate = mocknetlink.FailNextRouteAdd | mocknetlink.FailNextRouteReplace dataplane.PersistFailures = true rt.RouteUpdate(RouteClassLocalWorkload, "cali3", Target{ - CIDR: ip.MustParseCIDROrIP("10.20.30.40"), + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.20.30.40"), + Priority: routePriorityForTest, + }, }) err = rt.Apply() Expect(err).To(HaveOccurred()) @@ -1027,6 +1245,7 @@ var _ = Describe("RouteTable", func() { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_LINK, Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, })) }) @@ -1042,6 +1261,7 @@ var _ = Describe("RouteTable", func() { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_LINK, Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, })) }) }) @@ -1049,12 +1269,18 @@ var _ = Describe("RouteTable", func() { Describe("after adding two routes to cali3", func() { JustBeforeEach(func() { rt.RouteUpdate(RouteClassLocalWorkload, "cali3", Target{ - CIDR: ip.MustParseCIDROrIP("10.20.30.40"), + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.20.30.40"), + Priority: routePriorityForTest, + }, }) err := rt.Apply() Expect(err).ToNot(HaveOccurred()) rt.RouteUpdate(RouteClassLocalWorkload, "cali3", Target{ - CIDR: ip.MustParseCIDROrIP("10.0.20.0/24"), + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.20.0/24"), + Priority: routePriorityForTest, + }, }) err = rt.Apply() Expect(err).ToNot(HaveOccurred()) @@ -1069,6 +1295,7 @@ var _ = Describe("RouteTable", func() { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_LINK, Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, })) Expect(dataplane.RouteKeyToRoute).To(ContainElement(netlink.Route{ Family: unix.AF_INET, @@ -1078,17 +1305,27 @@ var _ = Describe("RouteTable", func() { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_LINK, Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, })) }) It("should make no dataplane updates when deleting, creating and updating back to the same target before the next apply", func() { - rt.RouteRemove(RouteClassLocalWorkload, "cali3", ip.MustParseCIDROrIP("10.0.20.0/24")) + rt.RouteRemove(RouteClassLocalWorkload, "cali3", RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.20.0/24"), + Priority: routePriorityForTest, + }) rt.RouteUpdate(RouteClassLocalWorkload, "cali3", Target{ - CIDR: ip.MustParseCIDROrIP("10.0.20.0/24"), - GW: ip.FromString("1.2.3.4"), + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.20.0/24"), + Priority: routePriorityForTest, + }, + GW: ip.FromString("1.2.3.4"), }) rt.RouteUpdate(RouteClassLocalWorkload, "cali3", Target{ - CIDR: ip.MustParseCIDROrIP("10.0.20.0/24"), + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.20.0/24"), + Priority: routePriorityForTest, + }, }) dataplane.ResetDeltas() @@ -1106,6 +1343,7 @@ var _ = Describe("RouteTable", func() { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_LINK, Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, })) Expect(dataplane.RouteKeyToRoute).To(ContainElement(netlink.Route{ Family: unix.AF_INET, @@ -1115,15 +1353,25 @@ var _ = Describe("RouteTable", func() { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_LINK, Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, })) }) It("should make no dataplane updates when deleting and then setting back to the same target before the next apply", func() { - rt.RouteRemove(RouteClassLocalWorkload, "cali3", ip.MustParseCIDROrIP("10.0.20.0/24")) + rt.RouteRemove(RouteClassLocalWorkload, "cali3", RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.20.0/24"), + Priority: routePriorityForTest, + }) rt.SetRoutes(RouteClassLocalWorkload, "cali3", []Target{{ - CIDR: ip.MustParseCIDROrIP("10.0.20.0/24"), + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.20.0/24"), + Priority: routePriorityForTest, + }, }, { - CIDR: ip.MustParseCIDROrIP("10.20.30.40"), + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.20.30.40"), + Priority: routePriorityForTest, + }, }}) dataplane.ResetDeltas() @@ -1142,6 +1390,7 @@ var _ = Describe("RouteTable", func() { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_LINK, Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, })) Expect(dataplane.RouteKeyToRoute).To(ContainElement(netlink.Route{ Family: unix.AF_INET, @@ -1151,18 +1400,25 @@ var _ = Describe("RouteTable", func() { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_LINK, Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, })) }) }) Describe("delete interface", func() { BeforeEach(func() { - rt.SetRoutes(RouteClassLocalWorkload, "cali1", []Target{ - {CIDR: ip.MustParseCIDROrIP("10.0.0.1/32")}, - }) - rt.SetRoutes(RouteClassLocalWorkload, "cali3", []Target{ - {CIDR: ip.MustParseCIDROrIP("10.0.0.3/32")}, - }) + rt.SetRoutes(RouteClassLocalWorkload, "cali1", []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.1/32"), + Priority: routePriorityForTest, + }, + }}) + rt.SetRoutes(RouteClassLocalWorkload, "cali3", []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.3/32"), + Priority: routePriorityForTest, + }, + }}) // Apply the changes. err := rt.Apply() Expect(err).NotTo(HaveOccurred()) @@ -1188,19 +1444,29 @@ var _ = Describe("RouteTable", func() { // each case, we make the failure transient so that only the first Apply() should // fail. Then, at most, the second call to Apply() should succeed. for _, testFailFlags := range mocknetlink.RoutetableFailureScenarios { - testFailFlags := testFailFlags desc := fmt.Sprintf("with some routes added and failures: %v", testFailFlags) Describe(desc, func() { BeforeEach(func() { - rt.SetRoutes(RouteClassLocalWorkload, "cali1", []Target{ - {CIDR: ip.MustParseCIDROrIP("10.0.0.1/32"), DestMAC: mac1}, - }) - rt.SetRoutes(RouteClassLocalWorkload, "cali2", []Target{ - {CIDR: ip.MustParseCIDROrIP("10.0.0.2/32"), DestMAC: mac2}, - }) - rt.SetRoutes(RouteClassLocalWorkload, "cali3", []Target{ - {CIDR: ip.MustParseCIDROrIP("10.0.1.3/32")}, - }) + rt.SetRoutes(RouteClassLocalWorkload, "cali1", []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.1/32"), + Priority: routePriorityForTest, + }, + DestMAC: mac1, + }}) + rt.SetRoutes(RouteClassLocalWorkload, "cali2", []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.2/32"), + Priority: routePriorityForTest, + }, + DestMAC: mac2, + }}) + rt.SetRoutes(RouteClassLocalWorkload, "cali3", []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.1.3/32"), + Priority: routePriorityForTest, + }, + }}) dataplane.FailuresToSimulate = testFailFlags }) JustBeforeEach(func() { @@ -1241,7 +1507,7 @@ var _ = Describe("RouteTable", func() { // resolution determines that no routes are eligible for programming. if testFailFlags != mocknetlink.FailNextLinkByNameNotFound { It("should keep correct route", func() { - Expect(dataplane.RouteKeyToRoute["254-10.0.0.1/32"]).To(Equal(netlink.Route{ + Expect(dataplane.RouteKeyToRoute[mainRouteKey("10.0.0.1/32")]).To(Equal(netlink.Route{ Family: unix.AF_INET, LinkIndex: cali1.LinkAttrs.Index, Dst: &ip1, @@ -1249,13 +1515,14 @@ var _ = Describe("RouteTable", func() { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_LINK, Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, })) - Expect(dataplane.AddedRouteKeys.Contains("254-10.0.0.1/32")).To(BeFalse()) + Expect(dataplane.AddedRouteKeys.Contains(mainRouteKey("10.0.0.1/32"))).To(BeFalse()) }) } It("should add new route", func() { - Expect(dataplane.RouteKeyToRoute).To(HaveKey("254-10.0.0.2/32")) - Expect(dataplane.RouteKeyToRoute["254-10.0.0.2/32"]).To(Equal(netlink.Route{ + Expect(dataplane.RouteKeyToRoute).To(HaveKey(mainRouteKey("10.0.0.2/32"))) + Expect(dataplane.RouteKeyToRoute[mainRouteKey("10.0.0.2/32")]).To(Equal(netlink.Route{ Family: unix.AF_INET, LinkIndex: cali2.LinkAttrs.Index, Dst: &ip2, @@ -1263,11 +1530,12 @@ var _ = Describe("RouteTable", func() { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_LINK, Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, })) }) It("should update changed route", func() { - Expect(dataplane.RouteKeyToRoute).To(HaveKey("254-10.0.1.3/32")) - Expect(dataplane.RouteKeyToRoute["254-10.0.1.3/32"]).To(Equal(netlink.Route{ + Expect(dataplane.RouteKeyToRoute).To(HaveKey(mainRouteKey("10.0.1.3/32"))) + Expect(dataplane.RouteKeyToRoute[mainRouteKey("10.0.1.3/32")]).To(Equal(netlink.Route{ Family: unix.AF_INET, LinkIndex: cali3.LinkAttrs.Index, Dst: &ip13, @@ -1275,8 +1543,9 @@ var _ = Describe("RouteTable", func() { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_LINK, Table: unix.RT_TABLE_MAIN, + Priority: routePriorityForTest, })) - Expect(dataplane.DeletedRouteKeys.Contains("254-10.0.0.3/32")).To(BeTrue()) + Expect(dataplane.DeletedRouteKeys.Contains(mainRouteKey("10.0.0.3/32"))).To(BeTrue()) Eventually(dataplane.GetDeletedConntrackEntries).Should(ContainElement(net.ParseIP("10.0.0.3").To4())) }) It("should have expected number of routes at the end", func() { @@ -1383,7 +1652,6 @@ var _ = Describe("RouteTable", func() { mocknetlink.FailNextRouteListEINTR, mocknetlink.FailNextRouteListWrappedEINTR, } { - failure := failure It(fmt.Sprintf("with a %v failure it should ignore Down updates", failure), func() { // First Apply() with a failure. dataplane.FailuresToSimulate = failure @@ -1428,7 +1696,10 @@ var _ = Describe("RouteTable", func() { // Trigger a per-interface sync. rt.OnIfaceStateChanged("cali1", 2, ifacemonitor.StateUp) rt.RouteUpdate(RouteClassLocalWorkload, "cali1", Target{ - CIDR: ip.MustParseCIDROrIP("10.0.20.0/24"), + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.20.0/24"), + Priority: routePriorityForTest, + }, }) err := rt.Apply() Expect(err).NotTo(HaveOccurred()) @@ -1439,6 +1710,517 @@ var _ = Describe("RouteTable", func() { }) }) +var _ = Describe("RouteTable with multiple priorities", func() { + var dataplane *mocknetlink.MockNetlinkDataplane + var t *mocktime.MockTime + var rt *RouteTable + + const ( + lowPriority = 100 + highPriority = 200 + ) + + BeforeEach(func() { + dataplane = mocknetlink.New() + t = mocktime.New() + t.SetAutoIncrement(11 * time.Second) + rt = New( + &defaultOwnershipPolicy, + 4, + 10*time.Second, + nil, + FelixRouteProtocol, + true, + 0, + logutils.NewSummarizer("test"), + dataplane, + WithRouteCleanupGracePeriod(10*time.Second), + WithTimeShim(t), + WithConntrackShim(dataplane), + WithNetlinkHandleShim(dataplane.NewMockNetlink), + ) + }) + + Describe("with an interface", func() { + var cali1 *mocknetlink.MockLink + BeforeEach(func() { + cali1 = dataplane.AddIface(3, "cali1", true, true) + }) + + It("should program two routes with the same CIDR but different priorities", func() { + rt.SetRoutes(RouteClassLocalWorkload, "cali1", []Target{ + { + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.1/32"), + Priority: lowPriority, + }, + }, + { + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.1/32"), + Priority: highPriority, + }, + }, + }) + err := rt.Apply() + Expect(err).ToNot(HaveOccurred()) + + lowKey := routeKeyStr(254, "10.0.0.1/32", lowPriority) + highKey := routeKeyStr(254, "10.0.0.1/32", highPriority) + + Expect(dataplane.RouteKeyToRoute).To(HaveKey(lowKey)) + Expect(dataplane.RouteKeyToRoute).To(HaveKey(highKey)) + Expect(dataplane.RouteKeyToRoute[lowKey]).To(Equal(netlink.Route{ + Family: unix.AF_INET, + LinkIndex: cali1.LinkAttrs.Index, + Dst: mustParseCIDR("10.0.0.1/32"), + Type: syscall.RTN_UNICAST, + Protocol: FelixRouteProtocol, + Scope: netlink.SCOPE_LINK, + Table: unix.RT_TABLE_MAIN, + Priority: lowPriority, + })) + Expect(dataplane.RouteKeyToRoute[highKey]).To(Equal(netlink.Route{ + Family: unix.AF_INET, + LinkIndex: cali1.LinkAttrs.Index, + Dst: mustParseCIDR("10.0.0.1/32"), + Type: syscall.RTN_UNICAST, + Protocol: FelixRouteProtocol, + Scope: netlink.SCOPE_LINK, + Table: unix.RT_TABLE_MAIN, + Priority: highPriority, + })) + }) + + It("should add two routes with different priorities via RouteUpdate", func() { + rt.RouteUpdate(RouteClassLocalWorkload, "cali1", Target{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.1/32"), + Priority: lowPriority, + }, + }) + rt.RouteUpdate(RouteClassLocalWorkload, "cali1", Target{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.1/32"), + Priority: highPriority, + }, + }) + err := rt.Apply() + Expect(err).ToNot(HaveOccurred()) + + Expect(dataplane.RouteKeyToRoute).To(HaveKey(routeKeyStr(254, "10.0.0.1/32", lowPriority))) + Expect(dataplane.RouteKeyToRoute).To(HaveKey(routeKeyStr(254, "10.0.0.1/32", highPriority))) + }) + + Context("after programming two routes with different priorities", func() { + BeforeEach(func() { + rt.SetRoutes(RouteClassLocalWorkload, "cali1", []Target{ + { + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.1/32"), + Priority: lowPriority, + }, + }, + { + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.1/32"), + Priority: highPriority, + }, + }, + }) + err := rt.Apply() + Expect(err).ToNot(HaveOccurred()) + }) + + It("should remove only the low-priority route when it is removed", func() { + rt.RouteRemove(RouteClassLocalWorkload, "cali1", RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.1/32"), + Priority: lowPriority, + }) + dataplane.ResetDeltas() + err := rt.Apply() + Expect(err).ToNot(HaveOccurred()) + + lowKey := routeKeyStr(254, "10.0.0.1/32", lowPriority) + highKey := routeKeyStr(254, "10.0.0.1/32", highPriority) + + Expect(dataplane.RouteKeyToRoute).NotTo(HaveKey(lowKey)) + Expect(dataplane.RouteKeyToRoute).To(HaveKey(highKey)) + Expect(dataplane.DeletedRouteKeys).To(HaveKey(lowKey)) + }) + + It("should remove only the high-priority route when it is removed", func() { + rt.RouteRemove(RouteClassLocalWorkload, "cali1", RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.1/32"), + Priority: highPriority, + }) + dataplane.ResetDeltas() + err := rt.Apply() + Expect(err).ToNot(HaveOccurred()) + + lowKey := routeKeyStr(254, "10.0.0.1/32", lowPriority) + highKey := routeKeyStr(254, "10.0.0.1/32", highPriority) + + Expect(dataplane.RouteKeyToRoute).To(HaveKey(lowKey)) + Expect(dataplane.RouteKeyToRoute).NotTo(HaveKey(highKey)) + Expect(dataplane.DeletedRouteKeys).To(HaveKey(highKey)) + }) + + It("should remove both routes when SetRoutes is called with nil", func() { + rt.SetRoutes(RouteClassLocalWorkload, "cali1", nil) + dataplane.ResetDeltas() + err := rt.Apply() + Expect(err).ToNot(HaveOccurred()) + + lowKey := routeKeyStr(254, "10.0.0.1/32", lowPriority) + highKey := routeKeyStr(254, "10.0.0.1/32", highPriority) + + Expect(dataplane.RouteKeyToRoute).NotTo(HaveKey(lowKey)) + Expect(dataplane.RouteKeyToRoute).NotTo(HaveKey(highKey)) + Expect(dataplane.DeletedRouteKeys).To(HaveKey(lowKey)) + Expect(dataplane.DeletedRouteKeys).To(HaveKey(highKey)) + }) + + It("should replace both routes when SetRoutes is called with new targets at same priorities", func() { + rt.SetRoutes(RouteClassLocalWorkload, "cali1", []Target{ + { + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.1/32"), + Priority: lowPriority, + }, + GW: ip.FromString("10.0.0.254"), + }, + { + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.1/32"), + Priority: highPriority, + }, + GW: ip.FromString("10.0.0.253"), + }, + }) + dataplane.ResetDeltas() + err := rt.Apply() + Expect(err).ToNot(HaveOccurred()) + + lowKey := routeKeyStr(254, "10.0.0.1/32", lowPriority) + highKey := routeKeyStr(254, "10.0.0.1/32", highPriority) + + Expect(dataplane.RouteKeyToRoute[lowKey].Gw).To(Equal(net.ParseIP("10.0.0.254").To4())) + Expect(dataplane.RouteKeyToRoute[highKey].Gw).To(Equal(net.ParseIP("10.0.0.253").To4())) + }) + + It("should handle removing one priority and keeping the other via SetRoutes", func() { + rt.SetRoutes(RouteClassLocalWorkload, "cali1", []Target{ + { + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.1/32"), + Priority: highPriority, + }, + }, + }) + dataplane.ResetDeltas() + err := rt.Apply() + Expect(err).ToNot(HaveOccurred()) + + lowKey := routeKeyStr(254, "10.0.0.1/32", lowPriority) + highKey := routeKeyStr(254, "10.0.0.1/32", highPriority) + + Expect(dataplane.RouteKeyToRoute).NotTo(HaveKey(lowKey)) + Expect(dataplane.RouteKeyToRoute).To(HaveKey(highKey)) + Expect(dataplane.DeletedRouteKeys).To(HaveKey(lowKey)) + }) + + It("should survive a resync and keep both routes", func() { + rt.QueueResync() + dataplane.ResetDeltas() + err := rt.Apply() + Expect(err).ToNot(HaveOccurred()) + + lowKey := routeKeyStr(254, "10.0.0.1/32", lowPriority) + highKey := routeKeyStr(254, "10.0.0.1/32", highPriority) + + Expect(dataplane.RouteKeyToRoute).To(HaveKey(lowKey)) + Expect(dataplane.RouteKeyToRoute).To(HaveKey(highKey)) + // No changes should be needed. + Expect(dataplane.AddedRouteKeys).To(BeEmpty()) + Expect(dataplane.DeletedRouteKeys).To(BeEmpty()) + }) + + It("should add a third priority for the same CIDR", func() { + const thirdPriority = 300 + rt.RouteUpdate(RouteClassLocalWorkload, "cali1", Target{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.1/32"), + Priority: thirdPriority, + }, + }) + err := rt.Apply() + Expect(err).ToNot(HaveOccurred()) + + Expect(dataplane.RouteKeyToRoute).To(HaveKey(routeKeyStr(254, "10.0.0.1/32", lowPriority))) + Expect(dataplane.RouteKeyToRoute).To(HaveKey(routeKeyStr(254, "10.0.0.1/32", highPriority))) + Expect(dataplane.RouteKeyToRoute).To(HaveKey(routeKeyStr(254, "10.0.0.1/32", thirdPriority))) + }) + }) + + It("should clean up stale kernel routes at different priorities during resync", func() { + // Pre-populate the kernel with two routes at different priorities + // that we don't know about. + staleRouteLow := netlink.Route{ + Family: unix.AF_INET, + LinkIndex: cali1.LinkAttrs.Index, + Dst: mustParseCIDR("10.0.0.1/32"), + Type: syscall.RTN_UNICAST, + Protocol: FelixRouteProtocol, + Scope: netlink.SCOPE_LINK, + Table: unix.RT_TABLE_MAIN, + Priority: lowPriority, + } + staleRouteHigh := netlink.Route{ + Family: unix.AF_INET, + LinkIndex: cali1.LinkAttrs.Index, + Dst: mustParseCIDR("10.0.0.1/32"), + Type: syscall.RTN_UNICAST, + Protocol: FelixRouteProtocol, + Scope: netlink.SCOPE_LINK, + Table: unix.RT_TABLE_MAIN, + Priority: highPriority, + } + dataplane.AddMockRoute(&staleRouteLow) + dataplane.AddMockRoute(&staleRouteHigh) + + err := rt.Apply() + Expect(err).ToNot(HaveOccurred()) + + // Both stale routes should be deleted. + lowKey := routeKeyStr(254, "10.0.0.1/32", lowPriority) + highKey := routeKeyStr(254, "10.0.0.1/32", highPriority) + Expect(dataplane.RouteKeyToRoute).NotTo(HaveKey(lowKey)) + Expect(dataplane.RouteKeyToRoute).NotTo(HaveKey(highKey)) + }) + + It("should clean up stale priority and keep desired priority during resync", func() { + // Pre-populate the kernel with a stale route at one priority. + staleRoute := netlink.Route{ + Family: unix.AF_INET, + LinkIndex: cali1.LinkAttrs.Index, + Dst: mustParseCIDR("10.0.0.1/32"), + Type: syscall.RTN_UNICAST, + Protocol: FelixRouteProtocol, + Scope: netlink.SCOPE_LINK, + Table: unix.RT_TABLE_MAIN, + Priority: lowPriority, + } + dataplane.AddMockRoute(&staleRoute) + + // Tell the routetable we want a route at a different priority. + rt.SetRoutes(RouteClassLocalWorkload, "cali1", []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.1/32"), + Priority: highPriority, + }, + }}) + + err := rt.Apply() + Expect(err).ToNot(HaveOccurred()) + + lowKey := routeKeyStr(254, "10.0.0.1/32", lowPriority) + highKey := routeKeyStr(254, "10.0.0.1/32", highPriority) + + // Stale route should be cleaned up, desired route should be programmed. + Expect(dataplane.RouteKeyToRoute).NotTo(HaveKey(lowKey)) + Expect(dataplane.RouteKeyToRoute).To(HaveKey(highKey)) + }) + + Describe("live migration: source host", func() { + // Simulates the sequence of events when a VM is live-migrated + // AWAY from this host. Felix manages the local workload route + // on cali1; BIRD programs a remote route on eth0 that Felix + // should not touch. + + const ( + normalPriority = 200 // Normal routing priority. + elevatedPriority = 100 // Lower number = higher kernel preference. + birdProto = 12 // RTPROT_BIRD + vmCIDR = "10.0.0.42/32" + ) + + var eth0 *mocknetlink.MockLink + + BeforeEach(func() { + eth0 = dataplane.AddIface(10, "eth0", true, true) + }) + + It("should handle the full migration sequence", func() { + // Step 1: Felix programs a local route for the VM at normal priority. + rt.SetRoutes(RouteClassLocalWorkload, "cali1", []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP(vmCIDR), + Priority: normalPriority, + }, + }}) + err := rt.Apply() + Expect(err).ToNot(HaveOccurred()) + + felixKey := routeKeyStr(254, vmCIDR, normalPriority) + Expect(dataplane.RouteKeyToRoute).To(HaveKey(felixKey)) + + // Step 2: BIRD programs a remote route at elevated priority via eth0. + birdRoute := netlink.Route{ + Family: unix.AF_INET, + LinkIndex: eth0.LinkAttrs.Index, + Dst: mustParseCIDR(vmCIDR), + Type: syscall.RTN_UNICAST, + Protocol: birdProto, + Scope: netlink.SCOPE_UNIVERSE, + Table: unix.RT_TABLE_MAIN, + Priority: elevatedPriority, + } + dataplane.AddMockRoute(&birdRoute) + birdKey := routeKeyStr(254, vmCIDR, elevatedPriority) + + // Resync: Felix should see the BIRD route but leave it alone. + rt.QueueResync() + dataplane.ResetDeltas() + err = rt.Apply() + Expect(err).ToNot(HaveOccurred()) + + Expect(dataplane.RouteKeyToRoute).To(HaveKey(felixKey)) + Expect(dataplane.RouteKeyToRoute).To(HaveKey(birdKey)) + Expect(dataplane.DeletedRouteKeys).NotTo(HaveKey(birdKey)) + + // Step 3: Source cleanup — Felix removes the local route. + rt.RouteRemove(RouteClassLocalWorkload, "cali1", RouteKey{ + CIDR: ip.MustParseCIDROrIP(vmCIDR), + Priority: normalPriority, + }) + dataplane.ResetDeltas() + err = rt.Apply() + Expect(err).ToNot(HaveOccurred()) + + Expect(dataplane.RouteKeyToRoute).NotTo(HaveKey(felixKey)) + Expect(dataplane.RouteKeyToRoute).To(HaveKey(birdKey)) + Expect(dataplane.DeletedRouteKeys).To(HaveKey(felixKey)) + Expect(dataplane.DeletedRouteKeys).NotTo(HaveKey(birdKey)) + + // Step 4: BIRD updates its route to normal priority. + dataplane.RemoveMockRoute(&birdRoute) + birdRouteNormal := netlink.Route{ + Family: unix.AF_INET, + LinkIndex: eth0.LinkAttrs.Index, + Dst: mustParseCIDR(vmCIDR), + Type: syscall.RTN_UNICAST, + Protocol: birdProto, + Scope: netlink.SCOPE_UNIVERSE, + Table: unix.RT_TABLE_MAIN, + Priority: normalPriority, + } + dataplane.AddMockRoute(&birdRouteNormal) + birdNormalKey := routeKeyStr(254, vmCIDR, normalPriority) + + // Resync: Felix should leave BIRD's route at the new priority alone. + rt.QueueResync() + dataplane.ResetDeltas() + err = rt.Apply() + Expect(err).ToNot(HaveOccurred()) + + Expect(dataplane.RouteKeyToRoute).To(HaveKey(birdNormalKey)) + Expect(dataplane.DeletedRouteKeys).NotTo(HaveKey(birdNormalKey)) + }) + }) + + Describe("live migration: destination host", func() { + // Simulates the sequence of events when a VM is live-migrated + // TO this host. BIRD has a remote route for the VM on eth0; + // Felix then programs a local workload route on cali1 with + // elevated priority. + + const ( + normalPriority = 200 // Normal routing priority. + elevatedPriority = 100 // Lower number = higher kernel preference. + birdProto = 12 // RTPROT_BIRD + vmCIDR = "10.0.0.42/32" + ) + + var eth0 *mocknetlink.MockLink + + BeforeEach(func() { + eth0 = dataplane.AddIface(10, "eth0", true, true) + }) + + It("should handle the full migration sequence", func() { + // Step 1: BIRD remote route exists at normal priority. + birdRoute := netlink.Route{ + Family: unix.AF_INET, + LinkIndex: eth0.LinkAttrs.Index, + Dst: mustParseCIDR(vmCIDR), + Type: syscall.RTN_UNICAST, + Protocol: birdProto, + Scope: netlink.SCOPE_UNIVERSE, + Table: unix.RT_TABLE_MAIN, + Priority: normalPriority, + } + dataplane.AddMockRoute(&birdRoute) + birdKey := routeKeyStr(254, vmCIDR, normalPriority) + + // Initial resync: BIRD route should be left alone. + rt.QueueResync() + dataplane.ResetDeltas() + err := rt.Apply() + Expect(err).ToNot(HaveOccurred()) + + Expect(dataplane.RouteKeyToRoute).To(HaveKey(birdKey)) + Expect(dataplane.DeletedRouteKeys).NotTo(HaveKey(birdKey)) + + // Step 2: VM is now live on this host — Felix programs a + // local route with elevated priority. + rt.SetRoutes(RouteClassLocalWorkload, "cali1", []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP(vmCIDR), + Priority: elevatedPriority, + }, + }}) + dataplane.ResetDeltas() + err = rt.Apply() + Expect(err).ToNot(HaveOccurred()) + + felixKey := routeKeyStr(254, vmCIDR, elevatedPriority) + Expect(dataplane.RouteKeyToRoute).To(HaveKey(felixKey)) + Expect(dataplane.RouteKeyToRoute).To(HaveKey(birdKey)) + + // Step 3: Source cleanup — BIRD removes the remote route. + dataplane.RemoveMockRoute(&birdRoute) + + // Resync: Felix route should survive, BIRD route gone. + rt.QueueResync() + dataplane.ResetDeltas() + err = rt.Apply() + Expect(err).ToNot(HaveOccurred()) + + Expect(dataplane.RouteKeyToRoute).To(HaveKey(felixKey)) + Expect(dataplane.RouteKeyToRoute).NotTo(HaveKey(birdKey)) + + // Step 4: Reversion to normal — Felix updates its route + // to normal priority. + rt.SetRoutes(RouteClassLocalWorkload, "cali1", []Target{{ + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP(vmCIDR), + Priority: normalPriority, + }, + }}) + dataplane.ResetDeltas() + err = rt.Apply() + Expect(err).ToNot(HaveOccurred()) + + normalKey := routeKeyStr(254, vmCIDR, normalPriority) + Expect(dataplane.RouteKeyToRoute).To(HaveKey(normalKey)) + Expect(dataplane.RouteKeyToRoute).NotTo(HaveKey(felixKey)) + Expect(dataplane.DeletedRouteKeys).To(HaveKey(felixKey)) + }) + }) + }) +}) + var _ = Describe("RouteTable (main table)", func() { var dataplane *mocknetlink.MockNetlinkDataplane var t *mocktime.MockTime @@ -1619,6 +2401,7 @@ var _ = Describe("RouteTable (table 100)", func() { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_UNIVERSE, Table: 100, + Priority: routePriorityForTest, } dataplane.AddMockRoute(&throwRoute) @@ -1631,6 +2414,7 @@ var _ = Describe("RouteTable (table 100)", func() { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_LINK, Table: 100, + Priority: routePriorityForTest, } }) It("should tidy up non-link routes immediately and wait for the route cleanup delay for interface routes", func() { @@ -1669,7 +2453,10 @@ var _ = Describe("RouteTable (table 100)", func() { Describe("after configuring a throw route", func() { JustBeforeEach(func() { rt.RouteUpdate(RouteClassWireguard, InterfaceNone, Target{ - CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + Priority: routePriorityForTest, + }, Type: TargetTypeThrow, }) err := rt.Apply() @@ -1686,7 +2473,10 @@ var _ = Describe("RouteTable (table 100)", func() { Describe("after configuring a throw route", func() { JustBeforeEach(func() { rt.RouteUpdate(RouteClassWireguard, InterfaceNone, Target{ - CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + Priority: routePriorityForTest, + }, Type: TargetTypeThrow, }) err := rt.Apply() @@ -1695,18 +2485,30 @@ var _ = Describe("RouteTable (table 100)", func() { It("should be able to toggle between throw and local iface routes", func() { // Modify the action associated with a particular destination. - for ii := 0; ii < 3; ii++ { - rt.RouteRemove(RouteClassWireguard, InterfaceNone, ip.MustParseCIDROrIP("10.10.10.10/32")) + for range 3 { + rt.RouteRemove(RouteClassWireguard, InterfaceNone, RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + Priority: routePriorityForTest, + }) rt.RouteUpdate(RouteClassLocalWorkload, "cali", Target{ - CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + Priority: routePriorityForTest, + }, }) err := rt.Apply() Expect(err).ToNot(HaveOccurred()) Expect(dataplane.RouteKeyToRoute).To(ConsistOf(caliRoute, gatewayRoute, caliRouteTable100SameAsThrow)) - rt.RouteRemove(RouteClassLocalWorkload, "cali", ip.MustParseCIDROrIP("10.10.10.10/32")) + rt.RouteRemove(RouteClassLocalWorkload, "cali", RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + Priority: routePriorityForTest, + }) rt.RouteUpdate(RouteClassWireguard, InterfaceNone, Target{ - CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + Priority: routePriorityForTest, + }, Type: TargetTypeThrow, }) err = rt.Apply() @@ -1717,13 +2519,19 @@ var _ = Describe("RouteTable (table 100)", func() { It("should prioritise a workload route over the throw route", func() { rt.RouteUpdate(RouteClassLocalWorkload, "cali", Target{ - CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + Priority: routePriorityForTest, + }, }) err := rt.Apply() Expect(err).ToNot(HaveOccurred()) Expect(dataplane.RouteKeyToRoute).To(ConsistOf(caliRoute, gatewayRoute, caliRouteTable100SameAsThrow)) - rt.RouteRemove(RouteClassLocalWorkload, "cali", ip.MustParseCIDROrIP("10.10.10.10/32")) + rt.RouteRemove(RouteClassLocalWorkload, "cali", RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + Priority: routePriorityForTest, + }) err = rt.Apply() Expect(err).ToNot(HaveOccurred()) Expect(dataplane.RouteKeyToRoute).To(ConsistOf(caliRoute, gatewayRoute, throwRoute)) @@ -1733,7 +2541,10 @@ var _ = Describe("RouteTable (table 100)", func() { Describe("throw route configured in dataplane, actual route is via cali", func() { It("the throw route should be removed and the interface route added", func() { rt.RouteUpdate(RouteClassLocalWorkload, "cali", Target{ - CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + Priority: routePriorityForTest, + }, }) err := rt.Apply() Expect(err).ToNot(HaveOccurred()) @@ -1744,12 +2555,18 @@ var _ = Describe("RouteTable (table 100)", func() { Describe("after configuring an existing throw route and then deleting it", func() { JustBeforeEach(func() { rt.RouteUpdate(RouteClassWireguard, InterfaceNone, Target{ - CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + Priority: routePriorityForTest, + }, Type: TargetTypeThrow, }) err := rt.Apply() Expect(err).ToNot(HaveOccurred()) - rt.RouteRemove(RouteClassWireguard, InterfaceNone, ip.MustParseCIDROrIP("10.10.10.10/32")) + rt.RouteRemove(RouteClassWireguard, InterfaceNone, RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + Priority: routePriorityForTest, + }) err = rt.Apply() Expect(err).ToNot(HaveOccurred()) }) @@ -1764,13 +2581,19 @@ var _ = Describe("RouteTable (table 100)", func() { Describe("after configuring a throw route and then replacing it with a blackhole route", func() { JustBeforeEach(func() { rt.RouteUpdate(RouteClassIPAMBlockDrop, InterfaceNone, Target{ - CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + Priority: routePriorityForTest, + }, Type: TargetTypeThrow, }) err := rt.Apply() Expect(err).ToNot(HaveOccurred()) rt.RouteUpdate(RouteClassIPAMBlockDrop, InterfaceNone, Target{ - CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + Priority: routePriorityForTest, + }, Type: TargetTypeBlackhole, }) err = rt.Apply() @@ -1786,21 +2609,28 @@ var _ = Describe("RouteTable (table 100)", func() { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_UNIVERSE, Table: 100, + Priority: routePriorityForTest, })) - Expect(dataplane.UpdatedRouteKeys.Contains("100-10.10.10.10/32")).To(BeTrue()) + Expect(dataplane.UpdatedRouteKeys.Contains(routeKeyStr(100, "10.10.10.10/32", routePriorityForTest))).To(BeTrue()) }) }) Describe("after configuring a blackhole route and then replacing it with a prohibit route", func() { JustBeforeEach(func() { rt.RouteUpdate(RouteClassIPAMBlockDrop, InterfaceNone, Target{ - CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + Priority: routePriorityForTest, + }, Type: TargetTypeBlackhole, }) err := rt.Apply() Expect(err).ToNot(HaveOccurred()) rt.RouteUpdate(RouteClassIPAMBlockDrop, InterfaceNone, Target{ - CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.10.10.10/32"), + Priority: routePriorityForTest, + }, Type: TargetTypeProhibit, }) err = rt.Apply() @@ -1816,8 +2646,9 @@ var _ = Describe("RouteTable (table 100)", func() { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_UNIVERSE, Table: 100, + Priority: routePriorityForTest, })) - Expect(dataplane.UpdatedRouteKeys.Contains("100-10.10.10.10/32")).To(BeTrue()) + Expect(dataplane.UpdatedRouteKeys.Contains(routeKeyStr(100, "10.10.10.10/32", routePriorityForTest))).To(BeTrue()) }) }) }) @@ -1835,13 +2666,17 @@ var _ = Describe("RouteTable (table 100)", func() { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_LINK, Table: 100, + Priority: routePriorityForTest, } }) It("should create the table as needed", func() { // In "strict" mode, RouteListFiltered returns an error if the routing table doesn't exist. // Check that is handled and that we proceed to create the route (and thus create the routing table). rt.RouteUpdate(RouteClassLocalWorkload, "cali", Target{ - CIDR: ip.MustParseCIDROrIP("10.0.0.1/32"), + RouteKey: RouteKey{ + CIDR: ip.MustParseCIDROrIP("10.0.0.1/32"), + Priority: routePriorityForTest, + }, }) err := rt.Apply() Expect(err).ToNot(HaveOccurred()) diff --git a/felix/routetable/routeclass_string.go b/felix/routetable/routeclass_string.go index baeff8de5f3..a35ae6d89a1 100644 --- a/felix/routetable/routeclass_string.go +++ b/felix/routetable/routeclass_string.go @@ -25,8 +25,9 @@ const _RouteClass_name = "RouteClassLocalWorkloadRouteClassBPFSpecialRouteClassW var _RouteClass_index = [...]uint8{0, 23, 43, 62, 87, 108, 132, 152, 169, 192, 205} func (i RouteClass) String() string { - if i < 0 || i >= RouteClass(len(_RouteClass_index)-1) { + idx := int(i) - 0 + if i < 0 || idx >= len(_RouteClass_index)-1 { return "RouteClass(" + strconv.FormatInt(int64(i), 10) + ")" } - return _RouteClass_name[_RouteClass_index[i]:_RouteClass_index[i+1]] + return _RouteClass_name[_RouteClass_index[idx]:_RouteClass_index[idx+1]] } diff --git a/felix/routetable/routetable_suite_test.go b/felix/routetable/routetable_suite_test.go index e3a8c3c2547..69f6a5247c4 100644 --- a/felix/routetable/routetable_suite_test.go +++ b/felix/routetable/routetable_suite_test.go @@ -17,9 +17,8 @@ package routetable_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -28,8 +27,9 @@ func init() { testutils.HookLogrusForGinkgo() } -func TestRouteTable(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/routetable_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "RouteTable Suite", []Reporter{junitReporter}) +func TestRules(t *testing.T) { + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_routetable_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/routetable", suiteConfig, reporterConfig) } diff --git a/felix/rules/dispatch.go b/felix/rules/dispatch.go index 76cbd6e7a75..ca46ceea3a7 100644 --- a/felix/rules/dispatch.go +++ b/felix/rules/dispatch.go @@ -103,6 +103,7 @@ func (r *DefaultRuleRenderer) WorkloadInterfaceAllowChains( return r.Allow() }, endRules, + "", ) chains = append(chains, toChildChains...) chains = append(chains, toRootChain) @@ -315,6 +316,7 @@ func (r *DefaultRuleRenderer) interfaceNameDispatchChains( return r.GoTo(EndpointChainName(pfx, name, r.maxNameLength)) }, fromEndRules, + "", ) chains = append(chains, fromChildChains...) chains = append(chains, fromRootChain) @@ -333,6 +335,7 @@ func (r *DefaultRuleRenderer) interfaceNameDispatchChains( return r.GoTo(EndpointChainName(pfx, name, r.maxNameLength)) }, toEndRules, + "", ) chains = append(chains, toChildChains...) chains = append(chains, toRootChain) @@ -363,9 +366,18 @@ func (r *DefaultRuleRenderer) endpointMarkDispatchChains( // The workload and host endpoint share the same root chain. We also need to put an non-cali mark rules at the end. // Work out child chains and root rules for workload and host endpoint separately and merge them back together. - for _, names := range [][]string{wlNames, hepNames} { + for index, names := range [][]string{wlNames, hepNames} { if len(names) > 0 { commonPrefix, prefixes, prefixToNames := r.sortAndDivideEndpointNamesToPrefixTree(names) + var infix string + switch index { + case 0: + infix = "wep" + case 1: + infix = "hep" + default: + log.Panicf("no mapping for index %d to infix", index) + } childChains, _, rootRules := r.buildSingleDispatchChains( dispatchSetMarkEndpointChainName, @@ -378,6 +390,7 @@ func (r *DefaultRuleRenderer) endpointMarkDispatchChains( return r.GoTo(EndpointChainName(pfx, name, r.maxNameLength)) }, nil, + "-"+infix, ) chains = append(chains, childChains...) @@ -468,8 +481,9 @@ func (r *DefaultRuleRenderer) buildSingleDispatchChains( getMatchForEndpoint func(name string) generictables.MatchCriteria, getActionForEndpoint func(pfx, name string) generictables.Action, endRules []generictables.Rule, + infix string, ) ([]*generictables.Chain, *generictables.Chain, []generictables.Rule) { - if r.NFTables && (endpointPfx == WorkloadFromEndpointPfx || endpointPfx == WorkloadToEndpointPfx) { + if r.nft && (endpointPfx == WorkloadFromEndpointPfx || endpointPfx == WorkloadToEndpointPfx) { // Currently only supported for nftables workload endpoint dispatch. return r.buildSingleDispatchChainsVMAP( chainName, @@ -486,6 +500,7 @@ func (r *DefaultRuleRenderer) buildSingleDispatchChains( getMatchForEndpoint, getActionForEndpoint, endRules, + infix, ) } @@ -500,6 +515,7 @@ func (r *DefaultRuleRenderer) buildSingleDispatchChainTree( getMatchForEndpoint func(name string) generictables.MatchCriteria, getActionForEndpoint func(pfx, name string) generictables.Action, endRules []generictables.Rule, + infix string, ) ([]*generictables.Chain, *generictables.Chain, []generictables.Rule) { childChains := make([]*generictables.Chain, 0) rootRules := make([]generictables.Rule, 0) @@ -518,7 +534,14 @@ func (r *DefaultRuleRenderer) buildSingleDispatchChainTree( // More than one name, render a prefix match in the root chain... nextChar := prefix[len(commonPrefix):] ifaceMatch := prefix + r.wildcard - childChainName := chainName + "-" + nextChar + // Inject an infix into the child chain name. This fixes the chain + // naming collision that can occur when chains are constructed in + // more than a single pass. Without this, interfaces like "caliX..." + // (Calico workload interfaces) can end up in the same chain as + // interfaces exposed as host endpoints that start with the same + // character "X". In this case rules generated in the previous round + // could be overwritten by the new ones. + childChainName := fmt.Sprintf("%s%s-%s", chainName, infix, nextChar) logCxt := logCxt.WithFields(log.Fields{ "childChainName": childChainName, "ifaceMatch": ifaceMatch, diff --git a/felix/rules/dispatch_test.go b/felix/rules/dispatch_test.go index dfcb1a55894..6f8eb6c5a3b 100644 --- a/felix/rules/dispatch_test.go +++ b/felix/rules/dispatch_test.go @@ -17,8 +17,7 @@ package rules_test import ( "fmt" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/generictables" @@ -66,7 +65,7 @@ var _ = Describe("Dispatch chains", func() { var epMarkMapper EndpointMarkMapper var renderer RuleRenderer BeforeEach(func() { - renderer = NewRenderer(rrConfigNormal) + renderer = NewRenderer(rrConfigNormal, false) epMarkMapper = NewEndpointMarkMapper(rrConfigNormal.MarkEndpoint, rrConfigNormal.MarkNonCaliEndpoint) }) @@ -238,7 +237,7 @@ var _ = Describe("Dispatch chains", func() { }, }, { - Name: "cali-set-endpoint-mark-2", + Name: "cali-set-endpoint-mark-wep-2", Rules: []generictables.Rule{ inboundGotoRule("cali2333", "cali-sm-cali2333"), inboundGotoRule("cali2444", "cali-sm-cali2444"), @@ -248,7 +247,7 @@ var _ = Describe("Dispatch chains", func() { Name: "cali-set-endpoint-mark", Rules: []generictables.Rule{ inboundGotoRule("cali1234", "cali-sm-cali1234"), - inboundGotoRule("cali2+", "cali-set-endpoint-mark-2"), + inboundGotoRule("cali2+", "cali-set-endpoint-mark-wep-2"), smUnknownEndpointDropRule("cali"), smUnknownEndpointDropRule("tap"), smNonCaliSetMarkRule, @@ -354,7 +353,7 @@ var _ = Describe("Dispatch chains", func() { }, }, { - Name: "cali-set-endpoint-mark-1", + Name: "cali-set-endpoint-mark-wep-1", Rules: []generictables.Rule{ inboundGotoRule("cali11", "cali-sm-cali11"), inboundGotoRule("cali12", "cali-sm-cali12"), @@ -362,7 +361,7 @@ var _ = Describe("Dispatch chains", func() { }, }, { - Name: "cali-set-endpoint-mark-2", + Name: "cali-set-endpoint-mark-wep-2", Rules: []generictables.Rule{ inboundGotoRule("cali21", "cali-sm-cali21"), inboundGotoRule("cali22", "cali-sm-cali22"), @@ -371,8 +370,8 @@ var _ = Describe("Dispatch chains", func() { { Name: "cali-set-endpoint-mark", Rules: []generictables.Rule{ - inboundGotoRule("cali1+", "cali-set-endpoint-mark-1"), - inboundGotoRule("cali2+", "cali-set-endpoint-mark-2"), + inboundGotoRule("cali1+", "cali-set-endpoint-mark-wep-1"), + inboundGotoRule("cali2+", "cali-set-endpoint-mark-wep-2"), smUnknownEndpointDropRule("cali"), smUnknownEndpointDropRule("tap"), smNonCaliSetMarkRule, diff --git a/felix/rules/dscp_test.go b/felix/rules/dscp_test.go index eab33021d14..563b739d890 100644 --- a/felix/rules/dscp_test.go +++ b/felix/rules/dscp_test.go @@ -15,7 +15,7 @@ package rules_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/felix/generictables" @@ -36,7 +36,7 @@ var _ = Describe("DSCP", func() { var renderer RuleRenderer BeforeEach(func() { - renderer = NewRenderer(rrConfigNormal) + renderer = NewRenderer(rrConfigNormal, false) }) It("should render empty chain for no policies", func() { diff --git a/felix/rules/endpoints.go b/felix/rules/endpoints.go index 8634ebf81bf..70700a16c21 100644 --- a/felix/rules/endpoints.go +++ b/felix/rules/endpoints.go @@ -15,21 +15,22 @@ package rules import ( + "crypto/sha3" "encoding/base64" "fmt" + "hash" "strconv" "strings" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/sirupsen/logrus" - "golang.org/x/crypto/sha3" "github.com/projectcalico/calico/felix/generictables" - "github.com/projectcalico/calico/felix/hashutils" "github.com/projectcalico/calico/felix/iptables" "github.com/projectcalico/calico/felix/proto" "github.com/projectcalico/calico/felix/types" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" + calicohash "github.com/projectcalico/calico/libcalico-go/lib/hash" ) const ( @@ -359,7 +360,7 @@ func (r *DefaultRuleRenderer) endpointSetMarkChain( } func (r *DefaultRuleRenderer) PolicyGroupToIptablesChains(group *PolicyGroup) []*generictables.Chain { - rules := make([]generictables.Rule, 0, len(group.PolicyNames)*2-1) + rules := make([]generictables.Rule, 0, len(group.Policies)*2-1) polChainPrefix := PolicyInboundPfx if group.Direction == PolicyDirectionOutbound { polChainPrefix = PolicyOutboundPfx @@ -370,9 +371,9 @@ func (r *DefaultRuleRenderer) PolicyGroupToIptablesChains(group *PolicyGroup) [] // fire. const returnStride = 5 count := -1 - for _, polName := range group.PolicyNames { - if model.PolicyIsStaged(polName) { - logrus.Debugf("Skip programming staged policy %v", polName) + for _, pol := range group.Policies { + if model.KindIsStaged(pol.Kind) { + logrus.Debugf("Skip programming staged policy %v", pol) continue } count++ @@ -403,8 +404,8 @@ func (r *DefaultRuleRenderer) PolicyGroupToIptablesChains(group *PolicyGroup) [] chainToJumpTo := PolicyChainName( polChainPrefix, - &types.PolicyID{Tier: group.Tier, Name: polName}, - r.NFTables, + pol, + r.nft, ) rules = append(rules, generictables.Rule{ Match: match, @@ -453,7 +454,7 @@ func (r *DefaultRuleRenderer) endpointIptablesChain( } } - //Add QoS controls for packet rate if applicable + // Add QoS controls for packet rate if applicable if chainType == chainTypeNormal && qosControls != nil { logrus.WithField("qosControls", qosControls).Debug("Rendering QoS controls packet rate rules") markLimitPacketRate := r.MarkScratch0 @@ -461,7 +462,7 @@ func (r *DefaultRuleRenderer) endpointIptablesChain( // Add ingress packet rate limit rules if applicable if qosControls.IngressPacketRate != 0 { logrus.WithFields(logrus.Fields{"IngressPacketRate": qosControls.IngressPacketRate, "IngressPacketBurst": qosControls.IngressPacketBurst, "mark": markLimitPacketRate}).Debug("Rendering ingress packet rate limit rules") - if r.NFTables { + if r.nft { rules = append(rules, generictables.Rule{ Match: r.NewMatch(), @@ -499,7 +500,7 @@ func (r *DefaultRuleRenderer) endpointIptablesChain( // Add egress packet rate limit rules if applicable if qosControls.EgressPacketRate != 0 { logrus.WithFields(logrus.Fields{"EgressPacketRate": qosControls.EgressPacketRate, "EgressPacketBurst": qosControls.EgressPacketBurst, "mark": markLimitPacketRate}).Debug("Rendering egress packet rate limit rules") - if r.NFTables { + if r.nft { rules = append(rules, generictables.Rule{ Match: r.NewMatch(), @@ -541,7 +542,7 @@ func (r *DefaultRuleRenderer) endpointIptablesChain( rules = r.appendConntrackRules(rules, allowAction) } - //Add QoS controls for number of connections if applicable + // Add QoS controls for number of connections if applicable if chainType == chainTypeNormal && qosControls != nil { logrus.WithField("qosControls", qosControls).Debug("Rendering QoS controls number of connection rules") if dir == RuleDirIngress { @@ -636,15 +637,15 @@ func (r *DefaultRuleRenderer) endpointIptablesChain( } if polGroup.ShouldBeInlined() { // Group is too small to have its own chain. - for _, p := range polGroup.PolicyNames { - if model.PolicyIsStaged(p) { + for _, p := range polGroup.Policies { + if model.KindIsStaged(p.Kind) { logrus.Debugf("Skip programming inlined staged policy %v", p) continue } chainsToJumpTo = append(chainsToJumpTo, PolicyChainName( policyPrefix, - &types.PolicyID{Tier: tier.Name, Name: p}, - r.NFTables, + p, + r.nft, )) } } else { @@ -701,9 +702,10 @@ func (r *DefaultRuleRenderer) endpointIptablesChain( rules = append(rules, generictables.Rule{ Match: r.NewMatch().MarkClear(r.MarkPass), Action: r.IptablesFilterDenyAction(), - Comment: []string{fmt.Sprintf("End of tier %s. %s if no policies passed packet", - tier.Name, - r.IptablesFilterDenyAction()), + Comment: []string{ + fmt.Sprintf("End of tier %s. %s if no policies passed packet", + tier.Name, + r.IptablesFilterDenyAction()), }, }) } else if r.FlowLogsEnabled { @@ -737,7 +739,7 @@ func (r *DefaultRuleRenderer) endpointIptablesChain( if chainType == chainTypeNormal { // Then, jump to each profile in turn. for _, profileID := range profileIds { - profChainName := ProfileChainName(profilePrefix, &types.ProfileID{Name: profileID}, r.NFTables) + profChainName := ProfileChainName(profilePrefix, &types.ProfileID{Name: profileID}, r.nft) rules = append(rules, generictables.Rule{Match: r.NewMatch(), Action: r.Jump(profChainName)}, // If policy marked packet as accepted, it returns, setting the @@ -808,7 +810,7 @@ func (r *DefaultRuleRenderer) appendConntrackRules(rules []generictables.Rule, a } func EndpointChainName(prefix string, ifaceName string, maxLen int) string { - return hashutils.GetLengthLimitedID( + return calicohash.GetLengthLimitedID( prefix, ifaceName, maxLen, @@ -822,15 +824,14 @@ const MaxPolicyGroupUIDLength = iptables.MaxChainNameLength - len(PolicyGroupInb // a list of policies. If large enough (currently >1 entry) it will be // programmed into its own chain. type PolicyGroup struct { - // Tier is only used in enterprise. There can be policies with the same - // name in different tiers so we need to disambiguate. - Tier string // Direction matches the policy model direction inbound/outbound. Each // group is either inbound or outbound since the set of active policy // can differ between the directions (a policy may have inbound rules // only, for example). - Direction PolicyDirection - PolicyNames []string + Direction PolicyDirection + + Policies []*types.PolicyID + // Selector is the original selector used by the grouped policies. By // grouping on selector, we ensure that if one policy in a group matches // an endpoint then all policies in that group must match the endpoint. @@ -847,7 +848,7 @@ func (g *PolicyGroup) UniqueID() string { return g.cachedUID } - hash := sha3.New224() + hash := hash.Hash(sha3.New224()) write := func(s string) { _, err := hash.Write([]byte(s)) if err != nil { @@ -858,12 +859,11 @@ func (g *PolicyGroup) UniqueID() string { logrus.WithError(err).Panic("Failed to write to hasher") } } - write(g.Tier) write(g.Selector) write(fmt.Sprint(g.Direction)) - write(strconv.Itoa(len(g.PolicyNames))) - for _, name := range g.PolicyNames { - write(name) + write(strconv.Itoa(len(g.Policies))) + for _, policy := range g.Policies { + write(policy.String()) } hashBytes := hash.Sum(make([]byte, 0, hash.Size())) g.cachedUID = base64.RawURLEncoding.EncodeToString(hashBytes)[:MaxPolicyGroupUIDLength] @@ -879,8 +879,8 @@ func (g *PolicyGroup) ChainName() string { func (g *PolicyGroup) ShouldBeInlined() bool { var count int - for _, name := range g.PolicyNames { - if !model.PolicyIsStaged(name) { + for _, pol := range g.Policies { + if !model.KindIsStaged(pol.Kind) { count++ if count > 1 { return false @@ -891,8 +891,8 @@ func (g *PolicyGroup) ShouldBeInlined() bool { } func (g *PolicyGroup) HasNonStagedPolicies() bool { - for _, n := range g.PolicyNames { - if !model.PolicyIsStaged(n) { + for _, pol := range g.Policies { + if !model.KindIsStaged(pol.Kind) { return true } } diff --git a/felix/rules/endpoints_test.go b/felix/rules/endpoints_test.go index 2d47d0fe440..1bf45bde9c6 100644 --- a/felix/rules/endpoints_test.go +++ b/felix/rules/endpoints_test.go @@ -19,16 +19,17 @@ import ( "strings" "github.com/google/go-cmp/cmp" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/format" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/calico/felix/generictables" "github.com/projectcalico/calico/felix/ipsets" . "github.com/projectcalico/calico/felix/iptables" "github.com/projectcalico/calico/felix/proto" . "github.com/projectcalico/calico/felix/rules" + "github.com/projectcalico/calico/felix/types" ) func init() { @@ -36,10 +37,28 @@ func init() { format.MaxLength = 0 } -var _ = Describe("Endpoints", endpointRulesTests(false)) -var _ = Describe("Endpoints with flowlogs", endpointRulesTests(true)) +var ( + _ = Describe("Endpoints", endpointRulesTests(false, "DROP")) + _ = Describe("Endpoints with flowlogs", endpointRulesTests(true, "DROP")) +) + +var ( + // Expected ID suffixes for policies used in tests. + // These don't exceed the length limit for iptables chain names, so + // they do not get hashed. + gnpAI = "gnp/ai" + gnpBI = "gnp/bi" + gnpAE = "gnp/ae" + gnpBE = "gnp/be" + + gnpC = "gnp/c" + gnpAFI = "gnp/afi" + gnpBFI = "gnp/bfi" + gnpAFE = "gnp/afe" + gnpBFE = "gnp/bfe" +) -func endpointRulesTests(flowLogsEnabled bool) func() { +func endpointRulesTests(flowLogsEnabled bool, dropActionOverride string) func() { return func() { const ( ProtoUDP = 17 @@ -102,31 +121,30 @@ func endpointRulesTests(flowLogsEnabled bool) func() { VXLANVNI: 4096, } - var renderer RuleRenderer - var epMarkMapper EndpointMarkMapper + var ( + renderer RuleRenderer + epMarkMapper EndpointMarkMapper + commonRuleBuilderOpts = []ruleBuilderOpt{ + withFlowLogs(flowLogsEnabled), + withDropActionOverride(dropActionOverride), + withDenyAction(denyAction, denyActionString), + withVXLANPort(VXLANPort), + } + ) Context("with normal config", func() { BeforeEach(func() { - renderer = NewRenderer(rrConfigNormalMangleReturn) + renderer = NewRenderer(rrConfigNormalMangleReturn, false) epMarkMapper = NewEndpointMarkMapper(rrConfigNormalMangleReturn.MarkEndpoint, rrConfigNormalMangleReturn.MarkNonCaliEndpoint) }) It("should render a minimal workload endpoint", func() { - toWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), - ).build() - - fromWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), - withDropIPIP(), - withDropVXLAN(VXLANPort), + toWlRules := newRuleBuilder(commonRuleBuilderOpts...).build() + fromWlOpts := append(commonRuleBuilderOpts, withEgress(), - ).build() + ) + fromWlRules := newRuleBuilder(fromWlOpts...).build() expected := trimSMChain(kubeIPVSEnabled, []*generictables.Chain{ { @@ -152,11 +170,10 @@ func endpointRulesTests(flowLogsEnabled bool) func() { }) It("should render a disabled workload endpoint", func() { - rules := newRuleBuilder( - withDenyAction(denyAction), - withFlowLogs(flowLogsEnabled), + opts := append(commonRuleBuilderOpts, withDisabledEndpoint(), - ).build() + ) + rules := newRuleBuilder(opts...).build() expected := trimSMChain(kubeIPVSEnabled, []*generictables.Chain{ { @@ -182,24 +199,18 @@ func endpointRulesTests(flowLogsEnabled bool) func() { }) It("should render a fully-loaded workload endpoint", func() { - toWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), - withPolicies("ai", "bi"), + toWlRulesOpts := append(commonRuleBuilderOpts, + withPolicies(gnpAI, gnpBI), withProfiles("prof1", "prof2"), - ).build() - - fromWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), - withDropIPIP(), - withDropVXLAN(VXLANPort), + ) + toWlRules := newRuleBuilder(toWlRulesOpts...).build() + + fromWlOpts := append(commonRuleBuilderOpts, withEgress(), - withPolicies("ae", "be"), + withPolicies(gnpAE, gnpBE), withProfiles("prof1", "prof2"), - ).build() + ) + fromWlRules := newRuleBuilder(fromWlOpts...).build() expected := trimSMChain(kubeIPVSEnabled, []*generictables.Chain{ { @@ -215,64 +226,74 @@ func endpointRulesTests(flowLogsEnabled bool) func() { Rules: setEndpointMarkRules(0xd400, 0xff00), }, }) - Expect(renderer.WorkloadEndpointToIptablesChains( + actual := renderer.WorkloadEndpointToIptablesChains( "cali1234", epMarkMapper, true, tiersToSinglePolGroups([]*proto.TierInfo{{ - Name: "default", - IngressPolicies: []string{"ai", "bi"}, - EgressPolicies: []string{"ae", "be"}, + Name: "default", + IngressPolicies: []*proto.PolicyID{ + {Name: "ai", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "bi", Kind: v3.KindGlobalNetworkPolicy}, + }, + EgressPolicies: []*proto.PolicyID{ + {Name: "ae", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "be", Kind: v3.KindGlobalNetworkPolicy}, + }, }}), []string{"prof1", "prof2"}, nil, - )).To(Equal(expected)) + ) + Expect(actual).To(Equal(expected), cmp.Diff(actual, expected)) }) It("should render a workload endpoint with policy groups", func() { polGrpInABC := &PolicyGroup{ - Tier: "default", - Direction: PolicyDirectionInbound, - PolicyNames: []string{"a", "b", "c"}, - Selector: "all()", + Direction: PolicyDirectionInbound, + Policies: []*types.PolicyID{ + {Name: "a"}, + {Name: "b"}, + {Name: "c"}, + }, + Selector: "all()", } polGrpInEF := &PolicyGroup{ - Tier: "default", - Direction: PolicyDirectionInbound, - PolicyNames: []string{"e", "f"}, - Selector: "someLabel == 'bar'", + Direction: PolicyDirectionInbound, + Policies: []*types.PolicyID{ + {Name: "e"}, + {Name: "f"}, + }, + Selector: "someLabel == 'bar'", } polGrpOutAB := &PolicyGroup{ - Tier: "default", - Direction: PolicyDirectionOutbound, - PolicyNames: []string{"a", "b"}, - Selector: "all()", + Direction: PolicyDirectionOutbound, + Policies: []*types.PolicyID{ + {Name: "a"}, + {Name: "b"}, + }, + Selector: "all()", } polGrpOutDE := &PolicyGroup{ - Tier: "default", - Direction: PolicyDirectionOutbound, - PolicyNames: []string{"d", "e"}, - Selector: "someLabel == 'bar'", + Direction: PolicyDirectionOutbound, + Policies: []*types.PolicyID{ + {Name: "d"}, + {Name: "e"}, + }, + Selector: "someLabel == 'bar'", } - toWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), + toWlRulesOpts := append(commonRuleBuilderOpts, withPolicyGroups(polGrpInABC, polGrpInEF), withProfiles("prof1", "prof2"), - ).build() + ) + toWlRules := newRuleBuilder(toWlRulesOpts...).build() - fromWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), + fromWlOpts := append(commonRuleBuilderOpts, withEgress(), withPolicyGroups(polGrpOutAB, polGrpOutDE), withProfiles("prof1", "prof2"), - withDropIPIP(), - withDropVXLAN(VXLANPort), - ).build() + ) + fromWlRules := newRuleBuilder(fromWlOpts...).build() expected := trimSMChain(kubeIPVSEnabled, []*generictables.Chain{ { @@ -312,24 +333,18 @@ func endpointRulesTests(flowLogsEnabled bool) func() { }) It("should render a fully-loaded workload endpoint - one staged policy, one enforced", func() { - toWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), - withPolicies("staged:ai", "bi"), + toWlRulesOpts := append(commonRuleBuilderOpts, + withPolicies("staged:ai", gnpBI), withProfiles("prof1", "prof2"), - ).build() + ) + toWlRules := newRuleBuilder(toWlRulesOpts...).build() - fromWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), + fromWlOpts := append(commonRuleBuilderOpts, withEgress(), - withPolicies("ae", "staged:be"), + withPolicies(gnpAE, "staged:be"), withProfiles("prof1", "prof2"), - withDropIPIP(), - withDropVXLAN(VXLANPort), - ).build() + ) + fromWlRules := newRuleBuilder(fromWlOpts...).build() expected := trimSMChain(kubeIPVSEnabled, []*generictables.Chain{ { @@ -345,39 +360,40 @@ func endpointRulesTests(flowLogsEnabled bool) func() { Rules: setEndpointMarkRules(0xd400, 0xff00), }, }) - Expect(renderer.WorkloadEndpointToIptablesChains( + actual := renderer.WorkloadEndpointToIptablesChains( "cali1234", epMarkMapper, true, tiersToSinglePolGroups([]*proto.TierInfo{{ - Name: "default", - IngressPolicies: []string{"staged:ai", "bi"}, - EgressPolicies: []string{"ae", "staged:be"}, + Name: "default", + IngressPolicies: []*proto.PolicyID{ + {Name: "ai", Kind: v3.KindStagedGlobalNetworkPolicy}, + {Name: "bi", Kind: v3.KindGlobalNetworkPolicy}, + }, + EgressPolicies: []*proto.PolicyID{ + {Name: "ae", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "be", Kind: v3.KindStagedGlobalNetworkPolicy}, + }, }}), []string{"prof1", "prof2"}, nil, - )).To(Equal(expected)) + ) + Expect(actual).To(Equal(expected), cmp.Diff(actual, expected)) }) It("should render a fully-loaded workload endpoint - both staged, end-of-tier action is pass", func() { - toWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), + toWlRulesOpts := append(commonRuleBuilderOpts, withPolicies("staged:ai", "staged:bi"), withProfiles("prof1", "prof2"), - ).build() + ) + toWlRules := newRuleBuilder(toWlRulesOpts...).build() - fromWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), + fromWlOpts := append(commonRuleBuilderOpts, withEgress(), withPolicies("staged:ae", "staged:be"), withProfiles("prof1", "prof2"), - withDropIPIP(), - withDropVXLAN(VXLANPort), - ).build() + ) + fromWlRules := newRuleBuilder(fromWlOpts...).build() expected := trimSMChain(kubeIPVSEnabled, []*generictables.Chain{ { @@ -398,9 +414,15 @@ func endpointRulesTests(flowLogsEnabled bool) func() { epMarkMapper, true, tiersToSinglePolGroups([]*proto.TierInfo{{ - Name: "default", - IngressPolicies: []string{"staged:ai", "staged:bi"}, - EgressPolicies: []string{"staged:ae", "staged:be"}, + Name: "default", + IngressPolicies: []*proto.PolicyID{ + {Name: "ai", Kind: v3.KindStagedGlobalNetworkPolicy}, + {Name: "bi", Kind: v3.KindStagedGlobalNetworkPolicy}, + }, + EgressPolicies: []*proto.PolicyID{ + {Name: "ae", Kind: v3.KindStagedGlobalNetworkPolicy}, + {Name: "be", Kind: v3.KindStagedGlobalNetworkPolicy}, + }, }}), []string{"prof1", "prof2"}, nil, @@ -409,35 +431,33 @@ func endpointRulesTests(flowLogsEnabled bool) func() { It("should render a fully-loaded workload endpoint - staged policy group, end-of-tier pass", func() { polGrpIngress := &PolicyGroup{ - Tier: "default", - Direction: PolicyDirectionInbound, - PolicyNames: []string{"staged:ai", "staged:bi"}, - Selector: "all()", + Direction: PolicyDirectionInbound, + Policies: []*types.PolicyID{ + {Name: "ai", Kind: v3.KindStagedGlobalNetworkPolicy}, + {Name: "bi", Kind: v3.KindStagedGlobalNetworkPolicy}, + }, + Selector: "all()", } polGrpEgress := &PolicyGroup{ - Tier: "default", - Direction: PolicyDirectionOutbound, - PolicyNames: []string{"staged:ae", "staged:be"}, - Selector: "all()", + Direction: PolicyDirectionOutbound, + Policies: []*types.PolicyID{ + {Name: "ae", Kind: v3.KindStagedGlobalNetworkPolicy}, + {Name: "be", Kind: v3.KindStagedGlobalNetworkPolicy}, + }, + Selector: "all()", } - toWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), + toWlRulesOpts := append(commonRuleBuilderOpts, withPolicyGroups(polGrpIngress), withProfiles("prof1", "prof2"), - ).build() + ) + toWlRules := newRuleBuilder(toWlRulesOpts...).build() - fromWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), + fromWlOpts := append(commonRuleBuilderOpts, withEgress(), withPolicyGroups(polGrpEgress), withProfiles("prof1", "prof2"), - withDropIPIP(), - withDropVXLAN(VXLANPort), - ).build() + ) + fromWlRules := newRuleBuilder(fromWlOpts...).build() expected := trimSMChain(kubeIPVSEnabled, []*generictables.Chain{ { @@ -471,26 +491,21 @@ func endpointRulesTests(flowLogsEnabled bool) func() { }) It("should render a fully-loaded workload endpoint with tier DefaultAction is Pass", func() { - toWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), - withPolicies("ai", "bi"), + // Suffixes for policy IDs "ai" and "bi". + toWlRulesOpts := append(commonRuleBuilderOpts, + withPolicies(gnpAI, gnpBI), withProfiles("prof1", "prof2"), withTierPassAction(), - ).build() + ) + toWlRules := newRuleBuilder(toWlRulesOpts...).build() - fromWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), + fromWlOpts := append(commonRuleBuilderOpts, withEgress(), - withPolicies("ae", "be"), + withPolicies(gnpAE, gnpBE), withProfiles("prof1", "prof2"), - withDropIPIP(), - withDropVXLAN(VXLANPort), withTierPassAction(), - ).build() + ) + fromWlRules := newRuleBuilder(fromWlOpts...).build() expected := trimSMChain(kubeIPVSEnabled, []*generictables.Chain{ { @@ -506,73 +521,85 @@ func endpointRulesTests(flowLogsEnabled bool) func() { Rules: setEndpointMarkRules(0xd400, 0xff00), }, }) - Expect(renderer.WorkloadEndpointToIptablesChains( + actual := renderer.WorkloadEndpointToIptablesChains( "cali1234", epMarkMapper, true, tiersToSinglePolGroups([]*proto.TierInfo{{ - Name: "default", - DefaultAction: "Pass", - IngressPolicies: []string{"ai", "bi"}, - EgressPolicies: []string{"ae", "be"}, + Name: "default", + DefaultAction: "Pass", + IngressPolicies: []*proto.PolicyID{ + {Name: "ai", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "bi", Kind: v3.KindGlobalNetworkPolicy}, + }, + EgressPolicies: []*proto.PolicyID{ + {Name: "ae", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "be", Kind: v3.KindGlobalNetworkPolicy}, + }, }}), []string{"prof1", "prof2"}, nil, - )).To(Equal(expected)) + ) + Expect(actual).To(Equal(expected), cmp.Diff(actual, expected)) }) It("should render a host endpoint", func() { actual := renderer.HostEndpointToFilterChains("eth0", tiersToSinglePolGroups([]*proto.TierInfo{{ - Name: "default", - IngressPolicies: []string{"ai", "bi"}, - EgressPolicies: []string{"ae", "be"}, + Name: "default", + IngressPolicies: []*proto.PolicyID{ + {Name: "ai", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "bi", Kind: v3.KindGlobalNetworkPolicy}, + }, + EgressPolicies: []*proto.PolicyID{ + {Name: "ae", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "be", Kind: v3.KindGlobalNetworkPolicy}, + }, }}), tiersToSinglePolGroups([]*proto.TierInfo{{ - Name: "default", - IngressPolicies: []string{"afi", "bfi"}, - EgressPolicies: []string{"afe", "bfe"}, + Name: "default", + IngressPolicies: []*proto.PolicyID{ + {Name: "afi", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "bfi", Kind: v3.KindGlobalNetworkPolicy}, + }, + EgressPolicies: []*proto.PolicyID{ + {Name: "afe", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "bfe", Kind: v3.KindGlobalNetworkPolicy}, + }, }}), epMarkMapper, []string{"prof1", "prof2"}, ) - toHostRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), - withPolicies("ae", "be"), + + toHostOpts := append(commonRuleBuilderOpts, + withEgress(), + withPolicies(gnpAE, gnpBE), withProfiles("prof1", "prof2"), forHostEndpoint(), - withEgress(), - ).build() + ) + toHostRules := newRuleBuilder(toHostOpts...).build() - fromHostRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), - withPolicies("ai", "bi"), + fromHostOpts := append(commonRuleBuilderOpts, + withPolicies(gnpAI, gnpBI), withProfiles("prof1", "prof2"), forHostEndpoint(), - ).build() + ) + fromHostRules := newRuleBuilder(fromHostOpts...).build() - toHostFWRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), - withPolicies("afe", "bfe"), + toHostFWOpts := append(commonRuleBuilderOpts, + withPolicies(gnpAFE, gnpBFE), withForwardPolicies(), withEgress(), forHostEndpoint(), - ).build() + ) + toHostFWRules := newRuleBuilder(toHostFWOpts...).build() - fromHostFWRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), - withPolicies("afi", "bfi"), + fromHostFWOpts := append(commonRuleBuilderOpts, + withPolicies(gnpAFI, gnpBFI), withForwardPolicies(), forHostEndpoint(), - ).build() + ) + fromHostFWRules := newRuleBuilder(fromHostFWOpts...).build() expected := trimSMChain(kubeIPVSEnabled, []*generictables.Chain{ { @@ -600,24 +627,20 @@ func endpointRulesTests(flowLogsEnabled bool) func() { }) It("should render host endpoint raw chains with untracked policies", func() { - toHostRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), - withPolicies("c"), + toHostOpts := append(commonRuleBuilderOpts, + withPolicies(gnpC), forHostEndpoint(), withUntrackedPolicies(), withEgress(), - ).build() + ) + toHostRules := newRuleBuilder(toHostOpts...).build() - fromHostRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), - withPolicies("c"), + fromHostOpts := append(commonRuleBuilderOpts, + withPolicies(gnpC), forHostEndpoint(), withUntrackedPolicies(), - ).build() + ) + fromHostRules := newRuleBuilder(fromHostOpts...).build() expected := []*generictables.Chain{ { @@ -632,53 +655,46 @@ func endpointRulesTests(flowLogsEnabled bool) func() { Expect(renderer.HostEndpointToRawChains("eth0", tiersToSinglePolGroups([]*proto.TierInfo{{ Name: "default", - IngressPolicies: []string{"c"}, - EgressPolicies: []string{"c"}, + IngressPolicies: []*proto.PolicyID{{Name: "c", Kind: v3.KindGlobalNetworkPolicy}}, + EgressPolicies: []*proto.PolicyID{{Name: "c", Kind: v3.KindGlobalNetworkPolicy}}, }}), )).To(Equal(expected)) }) It("should render host endpoint mangle chains with pre-DNAT policies", func() { - fromHostRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), - withPolicies("c"), + fromHostOpts := append(commonRuleBuilderOpts, + withPolicies(gnpC), forHostEndpoint(), withPreDNATPolicies(), - ).build() + ) + fromHostRules := newRuleBuilder(fromHostOpts...).build() expected := []*generictables.Chain{ { Name: "cali-fh-eth0", Rules: fromHostRules, }, } - Expect(renderer.HostEndpointToMangleIngressChains( + actual := renderer.HostEndpointToMangleIngressChains( "eth0", tiersToSinglePolGroups([]*proto.TierInfo{{ Name: "default", - IngressPolicies: []string{"c"}, + IngressPolicies: []*proto.PolicyID{{Name: "c", Kind: v3.KindGlobalNetworkPolicy}}, }}), - )).To(Equal(expected)) + ) + Expect(actual).To(Equal(expected), cmp.Diff(actual, expected)) }) It("should render a workload endpoint with packet rate limiting QoSControls", func() { - toWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), + toWlRulesOpts := append(commonRuleBuilderOpts, withQoSPacketRate(2000, 4000), - ).build() + ) + toWlRules := newRuleBuilder(toWlRulesOpts...).build() - fromWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), + fromWlOpts := append(commonRuleBuilderOpts, withEgress(), - withDropIPIP(), - withDropVXLAN(VXLANPort), withQoSPacketRate(1000, 2000), - ).build() + ) + fromWlRules := newRuleBuilder(fromWlOpts...).build() expected := []*generictables.Chain{ { @@ -709,22 +725,16 @@ func endpointRulesTests(flowLogsEnabled bool) func() { }) It("should render a workload endpoint with connection limiting QoSControls", func() { - toWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), + toWlRulesOpts := append(commonRuleBuilderOpts, withQoSMaxConnections(20), - ).build() + ) + toWlRules := newRuleBuilder(toWlRulesOpts...).build() - fromWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), + fromWlOpts := append(commonRuleBuilderOpts, withEgress(), - withDropIPIP(), - withDropVXLAN(VXLANPort), withQoSMaxConnections(10), - ).build() + ) + fromWlRules := newRuleBuilder(fromWlOpts...).build() expected := trimSMChain(kubeIPVSEnabled, []*generictables.Chain{ { @@ -755,28 +765,22 @@ func endpointRulesTests(flowLogsEnabled bool) func() { Describe("with ctstate=INVALID disabled", func() { BeforeEach(func() { - renderer = NewRenderer(rrConfigConntrackDisabledReturnAction) + renderer = NewRenderer(rrConfigConntrackDisabledReturnAction, false) epMarkMapper = NewEndpointMarkMapper(rrConfigConntrackDisabledReturnAction.MarkEndpoint, rrConfigConntrackDisabledReturnAction.MarkNonCaliEndpoint) }) It("should render a minimal workload endpoint", func() { - toWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), + toWlRulesOpts := append(commonRuleBuilderOpts, withInvalidCTStateDisabled(), - ).build() + ) + toWlRules := newRuleBuilder(toWlRulesOpts...).build() - fromWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), - withInvalidCTStateDisabled(), + fromWlOpts := append(commonRuleBuilderOpts, withEgress(), - withDropIPIP(), - withDropVXLAN(VXLANPort), - ).build() + withInvalidCTStateDisabled(), + ) + fromWlRules := newRuleBuilder(fromWlOpts...).build() expected := trimSMChain(kubeIPVSEnabled, []*generictables.Chain{ { @@ -803,15 +807,13 @@ func endpointRulesTests(flowLogsEnabled bool) func() { }) It("should render host endpoint mangle chains with pre-DNAT policies", func() { - fromHostRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), - withPolicies("c"), + fromHostOpts := append(commonRuleBuilderOpts, + withPolicies(gnpC), forHostEndpoint(), withPreDNATPolicies(), withInvalidCTStateDisabled(), - ).build() + ) + fromHostRules := newRuleBuilder(fromHostOpts...).build() expected := []*generictables.Chain{ { @@ -819,13 +821,14 @@ func endpointRulesTests(flowLogsEnabled bool) func() { Rules: fromHostRules, }, } - Expect(renderer.HostEndpointToMangleIngressChains( + actual := renderer.HostEndpointToMangleIngressChains( "eth0", tiersToSinglePolGroups([]*proto.TierInfo{{ Name: "default", - IngressPolicies: []string{"c"}, + IngressPolicies: []*proto.PolicyID{{Name: "c", Kind: v3.KindGlobalNetworkPolicy}}, }}), - )).To(Equal(expected)) + ) + Expect(actual).To(Equal(expected), cmp.Diff(actual, expected)) }) }) @@ -833,23 +836,16 @@ func endpointRulesTests(flowLogsEnabled bool) func() { Context("VXLAN allowed, IPIP dropped", func() { It("should render a minimal workload endpoint without VXLAN drop encap rule and with IPIP drop encap rule", func() { rrConfigNormalMangleReturn.AllowVXLANPacketsFromWorkloads = true - renderer = NewRenderer(rrConfigNormalMangleReturn) + renderer = NewRenderer(rrConfigNormalMangleReturn, false) epMarkMapper = NewEndpointMarkMapper(rrConfigNormalMangleReturn.MarkEndpoint, rrConfigNormalMangleReturn.MarkNonCaliEndpoint) - toWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), - ).build() - - fromWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), - withDropIPIP(), + toWlRules := newRuleBuilder(commonRuleBuilderOpts...).build() + fromWlOpts := append(commonRuleBuilderOpts, withEgress(), - ).build() + withAllowVXLAN(), + ) + fromWlRules := newRuleBuilder(fromWlOpts...).build() expected := trimSMChain(kubeIPVSEnabled, []*generictables.Chain{ { @@ -878,7 +874,7 @@ func endpointRulesTests(flowLogsEnabled bool) func() { Context("VXLAN dropped, IPIP allowed", func() { It("should render a minimal workload endpoint with VXLAN drop encap rule and without IPIP drop encap rule", func() { rrConfigNormalMangleReturn.AllowIPIPPacketsFromWorkloads = true - renderer = NewRenderer(rrConfigNormalMangleReturn) + renderer = NewRenderer(rrConfigNormalMangleReturn, false) epMarkMapper = NewEndpointMarkMapper(rrConfigNormalMangleReturn.MarkEndpoint, rrConfigNormalMangleReturn.MarkNonCaliEndpoint) @@ -890,19 +886,12 @@ func endpointRulesTests(flowLogsEnabled bool) func() { nil, ) - toWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), - ).build() - - fromWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), - withDropVXLAN(VXLANPort), + toWlRules := newRuleBuilder(commonRuleBuilderOpts...).build() + fromWlOpts := append(commonRuleBuilderOpts, withEgress(), - ).build() + withAllowIPIP(), + ) + fromWlRules := newRuleBuilder(fromWlOpts...).build() expected := trimSMChain(kubeIPVSEnabled, []*generictables.Chain{ { @@ -926,22 +915,17 @@ func endpointRulesTests(flowLogsEnabled bool) func() { It("should render a minimal workload endpoint without both VXLAN and IPIP drop encap rule", func() { rrConfigNormalMangleReturn.AllowVXLANPacketsFromWorkloads = true rrConfigNormalMangleReturn.AllowIPIPPacketsFromWorkloads = true - renderer = NewRenderer(rrConfigNormalMangleReturn) + renderer = NewRenderer(rrConfigNormalMangleReturn, false) epMarkMapper = NewEndpointMarkMapper(rrConfigNormalMangleReturn.MarkEndpoint, rrConfigNormalMangleReturn.MarkNonCaliEndpoint) - toWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), - ).build() - - fromWlRules := newRuleBuilder( - withFlowLogs(flowLogsEnabled), - withDenyAction(denyAction), - withDenyActionString(denyActionString), + toWlRules := newRuleBuilder(commonRuleBuilderOpts...).build() + fromWlOpts := append(commonRuleBuilderOpts, withEgress(), - ).build() + withAllowIPIP(), + withAllowVXLAN(), + ) + fromWlRules := newRuleBuilder(fromWlOpts...).build() expected := trimSMChain(kubeIPVSEnabled, []*generictables.Chain{ { @@ -995,15 +979,15 @@ func tiersToSinglePolGroups(tiers []*proto.TierInfo) (tierGroups []TierPolicyGro DefaultAction: t.DefaultAction, } for _, n := range t.IngressPolicies { + conv := types.ProtoToPolicyID(n) tg.IngressPolicies = append(tg.IngressPolicies, &PolicyGroup{ - Tier: t.Name, - PolicyNames: []string{n}, + Policies: []*types.PolicyID{&conv}, }) } for _, n := range t.EgressPolicies { + conv := types.ProtoToPolicyID(n) tg.EgressPolicies = append(tg.EgressPolicies, &PolicyGroup{ - Tier: t.Name, - PolicyNames: []string{n}, + Policies: []*types.PolicyID{&conv}, }) } tierGroups = append(tierGroups, tg) @@ -1016,60 +1000,51 @@ var _ = Describe("PolicyGroups", func() { It("should make sensible UIDs", func() { pgs := []PolicyGroup{ { - Tier: "default", - Direction: PolicyDirectionInbound, - PolicyNames: nil, - Selector: "all()", + Direction: PolicyDirectionInbound, + Policies: nil, + Selector: "all()", }, { - Tier: "foo", - Direction: PolicyDirectionInbound, - PolicyNames: nil, - Selector: "all()", + Direction: PolicyDirectionInbound, + Policies: nil, + Selector: "all()", }, { - Tier: "default", - Direction: PolicyDirectionOutbound, - PolicyNames: nil, - Selector: "all()", + Direction: PolicyDirectionOutbound, + Policies: nil, + Selector: "all()", }, { - Tier: "default", - Direction: PolicyDirectionInbound, - PolicyNames: []string{"a"}, - Selector: "all()", + Direction: PolicyDirectionInbound, + Policies: []*types.PolicyID{{Name: "a"}}, + Selector: "all()", }, { - Tier: "default", - Direction: PolicyDirectionInbound, - PolicyNames: nil, - Selector: "a == 'b'", + Direction: PolicyDirectionInbound, + Policies: nil, + Selector: "a == 'b'", }, { - Tier: "default", - Direction: PolicyDirectionInbound, - PolicyNames: []string{"a", "b"}, - Selector: "all()", + Direction: PolicyDirectionInbound, + Policies: []*types.PolicyID{{Name: "a"}, {Name: "b"}}, + Selector: "all()", }, { - Tier: "default", - Direction: PolicyDirectionInbound, - PolicyNames: []string{"ab"}, - Selector: "all()", + Direction: PolicyDirectionInbound, + Policies: []*types.PolicyID{{Name: "ab"}}, + Selector: "all()", }, { - Tier: "default", - Direction: PolicyDirectionInbound, - PolicyNames: []string{"aaa", "bbb"}, - Selector: "all()", + Direction: PolicyDirectionInbound, + Policies: []*types.PolicyID{{Name: "aaa"}, {Name: "bbb"}}, + Selector: "all()", }, { - Tier: "default", Direction: PolicyDirectionInbound, // Between this and the entry above, we check that the data // sent to the hasher is delimited somehow. - PolicyNames: []string{"aaab", "bb"}, - Selector: "all()", + Policies: []*types.PolicyID{{Name: "aaab"}, {Name: "bb"}}, + Selector: "all()", }, } @@ -1083,29 +1058,73 @@ var _ = Describe("PolicyGroups", func() { It("should detect staged policies", func() { pg := PolicyGroup{ - Tier: "default", Direction: PolicyDirectionInbound, - PolicyNames: []string{ - "namespace/staged:foo", + Policies: []*types.PolicyID{ + { + Namespace: "namespace", + Name: "foo", + Kind: v3.KindStagedNetworkPolicy, + }, }, Selector: "all()", } Expect(pg.HasNonStagedPolicies()).To(BeFalse()) - pg.PolicyNames = []string{ - "staged:foo", + pg.Policies = []*types.PolicyID{ + { + Name: "bar", + Kind: v3.KindStagedGlobalNetworkPolicy, + }, } Expect(pg.HasNonStagedPolicies()).To(BeFalse()) - pg.PolicyNames = []string{ - "namespace/staged:foo", - "namespace/bar", + pg.Policies = []*types.PolicyID{ + { + Namespace: "namespace", + Name: "foo", + Kind: v3.KindStagedNetworkPolicy, + }, + { + Namespace: "namespace", + Name: "bar", + Kind: v3.KindGlobalNetworkPolicy, + }, } Expect(pg.HasNonStagedPolicies()).To(BeTrue()) }) }) -var _ = table.DescribeTable("PolicyGroup chains", +var ( + // Chain names for policy ID "a". + cali_pi_a = PolicyChainName("cali-pi-", &types.PolicyID{Name: "a", Kind: v3.KindGlobalNetworkPolicy}, false) + cali_po_a = PolicyChainName("cali-po-", &types.PolicyID{Name: "a", Kind: v3.KindGlobalNetworkPolicy}, false) + + // Chain names for policy ID "b". + cali_pi_b = PolicyChainName("cali-pi-", &types.PolicyID{Name: "b", Kind: v3.KindGlobalNetworkPolicy}, false) + cali_po_b = PolicyChainName("cali-po-", &types.PolicyID{Name: "b", Kind: v3.KindGlobalNetworkPolicy}, false) + + // Chain names for policy ID "c". + cali_pi_c = PolicyChainName("cali-pi-", &types.PolicyID{Name: "c", Kind: v3.KindGlobalNetworkPolicy}, false) + cali_po_c = PolicyChainName("cali-po-", &types.PolicyID{Name: "c", Kind: v3.KindGlobalNetworkPolicy}, false) + + // Chain names for policy ID "d". + cali_pi_d = PolicyChainName("cali-pi-", &types.PolicyID{Name: "d", Kind: v3.KindGlobalNetworkPolicy}, false) + cali_po_d = PolicyChainName("cali-po-", &types.PolicyID{Name: "d", Kind: v3.KindGlobalNetworkPolicy}, false) + + // Chain names for policy ID "e". + cali_pi_e = PolicyChainName("cali-pi-", &types.PolicyID{Name: "e", Kind: v3.KindGlobalNetworkPolicy}, false) + cali_po_e = PolicyChainName("cali-po-", &types.PolicyID{Name: "e", Kind: v3.KindGlobalNetworkPolicy}, false) + + // Chain names for policy ID "f". + cali_pi_f = PolicyChainName("cali-pi-", &types.PolicyID{Name: "f", Kind: v3.KindGlobalNetworkPolicy}, false) + cali_po_f = PolicyChainName("cali-po-", &types.PolicyID{Name: "f", Kind: v3.KindGlobalNetworkPolicy}, false) + + cali_po_g = PolicyChainName("cali-po-", &types.PolicyID{Name: "g", Kind: v3.KindGlobalNetworkPolicy}, false) + cali_po_h = PolicyChainName("cali-po-", &types.PolicyID{Name: "h", Kind: v3.KindGlobalNetworkPolicy}, false) + cali_po_i = PolicyChainName("cali-po-", &types.PolicyID{Name: "i", Kind: v3.KindGlobalNetworkPolicy}, false) +) + +var _ = DescribeTable("PolicyGroup chains", func(group PolicyGroup, expectedRules []generictables.Rule) { renderer := NewRenderer(Config{ MarkAccept: 0x8, @@ -1115,91 +1134,112 @@ var _ = table.DescribeTable("PolicyGroup chains", MarkDrop: 0x80, MarkEndpoint: 0xff00, MarkNonCaliEndpoint: 0x0100, - }) + }, false) chains := renderer.PolicyGroupToIptablesChains(&group) Expect(chains).To(HaveLen(1)) Expect(chains[0].Name).ToNot(BeEmpty()) Expect(chains[0].Name).To(Equal(group.ChainName())) - Expect(chains[0].Rules).To(Equal(expectedRules)) + Expect(chains[0].Rules).To(Equal(expectedRules), cmp.Diff(chains[0].Rules, expectedRules)) }, polGroupEntry( PolicyGroup{ - Tier: "default", - Direction: PolicyDirectionInbound, - PolicyNames: []string{"a"}, - Selector: "all()", + Direction: PolicyDirectionInbound, + Policies: []*types.PolicyID{ + {Name: "a", Kind: v3.KindGlobalNetworkPolicy}, + }, + Selector: "all()", }, []generictables.Rule{ - jumpToPolicyGroup("cali-pi-default/a", 0), + jumpToPolicyGroup(cali_pi_a, 0), }, ), polGroupEntry( PolicyGroup{ - Tier: "default", - Direction: PolicyDirectionInbound, - PolicyNames: []string{"a", "b"}, - Selector: "all()", + Direction: PolicyDirectionInbound, + Policies: []*types.PolicyID{ + {Name: "a", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "b", Kind: v3.KindGlobalNetworkPolicy}, + }, + Selector: "all()", }, []generictables.Rule{ - jumpToPolicyGroup("cali-pi-default/a", 0), - jumpToPolicyGroup("cali-pi-default/b", 0x18), + jumpToPolicyGroup(cali_pi_a, 0), + jumpToPolicyGroup(cali_pi_b, 0x18), }, ), polGroupEntry( PolicyGroup{ - Tier: "default", - Direction: PolicyDirectionInbound, - PolicyNames: []string{"a", "b", "c"}, - Selector: "all()", + Direction: PolicyDirectionInbound, + Policies: []*types.PolicyID{ + {Name: "a", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "b", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "c", Kind: v3.KindGlobalNetworkPolicy}, + }, + Selector: "all()", }, []generictables.Rule{ - jumpToPolicyGroup("cali-pi-default/a", 0), - jumpToPolicyGroup("cali-pi-default/b", 0x18), - jumpToPolicyGroup("cali-pi-default/c", 0x18), + jumpToPolicyGroup(cali_pi_a, 0), + jumpToPolicyGroup(cali_pi_b, 0x18), + jumpToPolicyGroup(cali_pi_c, 0x18), }, ), polGroupEntry( PolicyGroup{ - Tier: "default", - Direction: PolicyDirectionInbound, - PolicyNames: []string{"a", "b", "c", "d"}, - Selector: "all()", + Direction: PolicyDirectionInbound, + Policies: []*types.PolicyID{ + {Name: "a", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "b", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "c", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "d", Kind: v3.KindGlobalNetworkPolicy}, + }, + Selector: "all()", }, []generictables.Rule{ - jumpToPolicyGroup("cali-pi-default/a", 0), - jumpToPolicyGroup("cali-pi-default/b", 0x18), - jumpToPolicyGroup("cali-pi-default/c", 0x18), - jumpToPolicyGroup("cali-pi-default/d", 0x18), + jumpToPolicyGroup(cali_pi_a, 0), + jumpToPolicyGroup(cali_pi_b, 0x18), + jumpToPolicyGroup(cali_pi_c, 0x18), + jumpToPolicyGroup(cali_pi_d, 0x18), }, ), polGroupEntry( PolicyGroup{ - Tier: "default", - Direction: PolicyDirectionInbound, - PolicyNames: []string{"a", "b", "c", "d", "e"}, - Selector: "all()", + Direction: PolicyDirectionInbound, + Policies: []*types.PolicyID{ + {Name: "a", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "b", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "c", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "d", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "e", Kind: v3.KindGlobalNetworkPolicy}, + }, + Selector: "all()", }, []generictables.Rule{ - jumpToPolicyGroup("cali-pi-default/a", 0), - jumpToPolicyGroup("cali-pi-default/b", 0x18), - jumpToPolicyGroup("cali-pi-default/c", 0x18), - jumpToPolicyGroup("cali-pi-default/d", 0x18), - jumpToPolicyGroup("cali-pi-default/e", 0x18), + jumpToPolicyGroup(cali_pi_a, 0), + jumpToPolicyGroup(cali_pi_b, 0x18), + jumpToPolicyGroup(cali_pi_c, 0x18), + jumpToPolicyGroup(cali_pi_d, 0x18), + jumpToPolicyGroup(cali_pi_e, 0x18), }, ), polGroupEntry( PolicyGroup{ - Tier: "default", - Direction: PolicyDirectionInbound, - PolicyNames: []string{"a", "b", "c", "d", "e", "f"}, - Selector: "all()", + Direction: PolicyDirectionInbound, + Policies: []*types.PolicyID{ + {Name: "a", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "b", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "c", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "d", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "e", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "f", Kind: v3.KindGlobalNetworkPolicy}, + }, + Selector: "all()", }, []generictables.Rule{ - jumpToPolicyGroup("cali-pi-default/a", 0), - jumpToPolicyGroup("cali-pi-default/b", 0x18), - jumpToPolicyGroup("cali-pi-default/c", 0x18), - jumpToPolicyGroup("cali-pi-default/d", 0x18), - jumpToPolicyGroup("cali-pi-default/e", 0x18), + jumpToPolicyGroup(cali_pi_a, 0), + jumpToPolicyGroup(cali_pi_b, 0x18), + jumpToPolicyGroup(cali_pi_c, 0x18), + jumpToPolicyGroup(cali_pi_d, 0x18), + jumpToPolicyGroup(cali_pi_e, 0x18), { // Only get a return action every 5 rules and only if it's // not the last action. @@ -1207,88 +1247,118 @@ var _ = table.DescribeTable("PolicyGroup chains", Action: ReturnAction{}, Comment: []string{"Return on verdict"}, }, - jumpToPolicyGroup("cali-pi-default/f", 0), + jumpToPolicyGroup(cali_pi_f, 0), }, ), polGroupEntry( PolicyGroup{ - Tier: "default", - Direction: PolicyDirectionOutbound, - PolicyNames: []string{"a", "b", "c", "d", "e", "f", "g"}, - Selector: "all()", + Direction: PolicyDirectionOutbound, + Policies: []*types.PolicyID{ + {Name: "a", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "b", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "c", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "d", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "e", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "f", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "g", Kind: v3.KindGlobalNetworkPolicy}, + }, + Selector: "all()", }, []generictables.Rule{ - jumpToPolicyGroup("cali-po-default/a", 0), - jumpToPolicyGroup("cali-po-default/b", 0x18), - jumpToPolicyGroup("cali-po-default/c", 0x18), - jumpToPolicyGroup("cali-po-default/d", 0x18), - jumpToPolicyGroup("cali-po-default/e", 0x18), + jumpToPolicyGroup(cali_po_a, 0), + jumpToPolicyGroup(cali_po_b, 0x18), + jumpToPolicyGroup(cali_po_c, 0x18), + jumpToPolicyGroup(cali_po_d, 0x18), + jumpToPolicyGroup(cali_po_e, 0x18), { Match: Match().MarkNotClear(0x18), Action: ReturnAction{}, Comment: []string{"Return on verdict"}, }, - jumpToPolicyGroup("cali-po-default/f", 0), - jumpToPolicyGroup("cali-po-default/g", 0x18), + jumpToPolicyGroup(cali_po_f, 0), + jumpToPolicyGroup(cali_po_g, 0x18), }, ), polGroupEntry( PolicyGroup{ - Tier: "default", - Direction: PolicyDirectionOutbound, - PolicyNames: []string{"staged:a", "staged:b", "c", "d", "e", "f", "g", "h", "i"}, - Selector: "all()", + Direction: PolicyDirectionOutbound, + Policies: []*types.PolicyID{ + {Name: "a", Kind: v3.KindStagedGlobalNetworkPolicy}, + {Name: "b", Kind: v3.KindStagedGlobalNetworkPolicy}, + {Name: "c", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "d", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "e", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "f", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "g", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "h", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "i", Kind: v3.KindGlobalNetworkPolicy}, + }, + Selector: "all()", }, []generictables.Rule{ // Match criteria and return rules get skipped until we hit the // first non-staged policy. - jumpToPolicyGroup("cali-po-default/c", 0), - jumpToPolicyGroup("cali-po-default/d", 0x18), - jumpToPolicyGroup("cali-po-default/e", 0x18), - jumpToPolicyGroup("cali-po-default/f", 0x18), - jumpToPolicyGroup("cali-po-default/g", 0x18), + jumpToPolicyGroup(cali_po_c, 0), + jumpToPolicyGroup(cali_po_d, 0x18), + jumpToPolicyGroup(cali_po_e, 0x18), + jumpToPolicyGroup(cali_po_f, 0x18), + jumpToPolicyGroup(cali_po_g, 0x18), { Match: Match().MarkNotClear(0x18), Action: ReturnAction{}, Comment: []string{"Return on verdict"}, }, - jumpToPolicyGroup("cali-po-default/h", 0), - jumpToPolicyGroup("cali-po-default/i", 0x18), + jumpToPolicyGroup(cali_po_h, 0), + jumpToPolicyGroup(cali_po_i, 0x18), }, ), polGroupEntry( PolicyGroup{ - Tier: "default", - Direction: PolicyDirectionOutbound, - PolicyNames: []string{"staged:a", "staged:b", "staged:c", "d", "staged:e", "f", "g"}, - Selector: "all()", + Direction: PolicyDirectionOutbound, + Policies: []*types.PolicyID{ + {Name: "a", Kind: v3.KindStagedGlobalNetworkPolicy}, + {Name: "b", Kind: v3.KindStagedGlobalNetworkPolicy}, + {Name: "c", Kind: v3.KindStagedGlobalNetworkPolicy}, + {Name: "d", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "e", Kind: v3.KindStagedGlobalNetworkPolicy}, + {Name: "f", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "g", Kind: v3.KindGlobalNetworkPolicy}, + }, + Selector: "all()", }, []generictables.Rule{ // Match criteria and return rules get skipped until we hit the // first non-staged policy. - jumpToPolicyGroup("cali-po-default/d", 0), - jumpToPolicyGroup("cali-po-default/f", 0x18), - jumpToPolicyGroup("cali-po-default/g", 0x18), + jumpToPolicyGroup(cali_po_d, 0), + jumpToPolicyGroup(cali_po_f, 0x18), + jumpToPolicyGroup(cali_po_g, 0x18), }, ), polGroupEntry( PolicyGroup{ - Tier: "default", - Direction: PolicyDirectionOutbound, - PolicyNames: []string{"staged:a", "staged:b", "staged:c", "staged:d", "staged:e", "f", "g"}, - Selector: "all()", + Direction: PolicyDirectionOutbound, + Policies: []*types.PolicyID{ + {Name: "a", Kind: v3.KindStagedGlobalNetworkPolicy}, + {Name: "b", Kind: v3.KindStagedGlobalNetworkPolicy}, + {Name: "c", Kind: v3.KindStagedGlobalNetworkPolicy}, + {Name: "d", Kind: v3.KindStagedGlobalNetworkPolicy}, + {Name: "e", Kind: v3.KindStagedGlobalNetworkPolicy}, + {Name: "f", Kind: v3.KindGlobalNetworkPolicy}, + {Name: "g", Kind: v3.KindGlobalNetworkPolicy}, + }, + Selector: "all()", }, []generictables.Rule{ // Match criteria and return rules get skipped until we hit the // first non-staged policy. - jumpToPolicyGroup("cali-po-default/f", 0), - jumpToPolicyGroup("cali-po-default/g", 0x18), + jumpToPolicyGroup(cali_po_f, 0), + jumpToPolicyGroup(cali_po_g, 0x18), }, ), ) -func polGroupEntry(group PolicyGroup, rules []generictables.Rule) table.TableEntry { - return table.Entry(fmt.Sprintf("%v", group), group, rules) +func polGroupEntry(group PolicyGroup, rules []generictables.Rule) TableEntry { + return Entry(fmt.Sprintf("%v", group), group, rules) } func jumpToPolicyGroup(target string, clearMark uint32) generictables.Rule { @@ -1310,28 +1380,28 @@ func withEgress() ruleBuilderOpt { } } -func withDenyAction(action generictables.Action) ruleBuilderOpt { +func withDenyAction(action generictables.Action, actionStr string) ruleBuilderOpt { return func(r *ruleBuilder) { r.denyAction = action + r.denyActionString = actionStr } } -func withDenyActionString(actionStr string) ruleBuilderOpt { +func withAllowIPIP() ruleBuilderOpt { return func(r *ruleBuilder) { - r.denyActionString = actionStr + r.dropIPIP = false } } -func withDropIPIP() ruleBuilderOpt { +func withAllowVXLAN() ruleBuilderOpt { return func(r *ruleBuilder) { - r.dropIPIP = true + r.dropVXLAN = false } } -func withDropVXLAN(vxlanPort int) ruleBuilderOpt { +func withVXLANPort(vxlanPort int) ruleBuilderOpt { return func(r *ruleBuilder) { r.vxlanPort = vxlanPort - r.dropVXLAN = true } } @@ -1409,9 +1479,18 @@ func withDisabledEndpoint() ruleBuilderOpt { } } +func withDropActionOverride(action string) ruleBuilderOpt { + return func(r *ruleBuilder) { + r.dropActionOverride = action + } +} + func forHostEndpoint() ruleBuilderOpt { return func(r *ruleBuilder) { r.forHostEndpoint = true + // IPIP and VXLAN must be allowed to/from a host. + r.dropIPIP = false + r.dropVXLAN = false } } @@ -1445,10 +1524,15 @@ type ruleBuilder struct { qosMaxConn int64 flowLogsEnabled bool + + dropActionOverride string } func newRuleBuilder(opts ...ruleBuilderOpt) *ruleBuilder { - b := &ruleBuilder{} + b := &ruleBuilder{ + dropIPIP: true, + dropVXLAN: true, + } for _, o := range opts { o(b) } @@ -1463,11 +1547,7 @@ func newRuleBuilder(opts ...ruleBuilderOpt) *ruleBuilder { func (b *ruleBuilder) build() []generictables.Rule { var rules []generictables.Rule if b.disabled { - return []generictables.Rule{{ - Match: Match(), - Action: b.denyAction, - Comment: []string{"Endpoint admin disabled"}, - }} + return b.getDropActionOverrideRules(Match(), "Endpoint admin disabled") } // Add rules only if endpoint is not disabled. @@ -1495,12 +1575,12 @@ func (b *ruleBuilder) build() []generictables.Rule { rules = append(rules, clearMarkRule(0x18, "")) // Drop VXLAN traffic originating from workloads, if not allowed. - if b.dropVXLAN { + if b.dropVXLAN && b.direction == "egress" { rules = append(rules, b.dropVXLANTunnel()) } // Drop IPIP traffic originating from workloads, if not allowed. - if b.dropIPIP { + if b.dropIPIP && b.direction == "egress" { rules = append(rules, b.dropIPIPTunnel()) } @@ -1707,9 +1787,9 @@ func (b *ruleBuilder) matchPolicies() []generictables.Rule { continue } endOfTierDrop = true - target := fmt.Sprintf("cali-pi-default/%v", p) + target := fmt.Sprintf("cali-pi-%v", p) if b.egress { - target = fmt.Sprintf("cali-po-default/%v", p) + target = fmt.Sprintf("cali-po-%v", p) } rules = append(rules, generictables.Rule{ Match: Match().MarkClear(0x10), @@ -1735,18 +1815,12 @@ func (b *ruleBuilder) matchPolicies() []generictables.Rule { endOfTierDrop = false } + ruleComment := fmt.Sprintf("End of tier default. %s if no policies passed packet", b.denyActionString) if b.flowLogsEnabled { rules = append(rules, b.nflogAction(endOfTierDrop, false)) } if endOfTierDrop { - rules = append(rules, - generictables.Rule{ - Match: Match().MarkClear(0x10), - Action: b.denyAction, - Comment: []string{fmt.Sprintf("End of tier default. %s if no policies passed packet", - b.denyActionString, - )}, - }) + rules = append(rules, b.getDropActionOverrideRules(Match().MarkClear(0x10), ruleComment)...) } return rules } @@ -1770,16 +1844,42 @@ func (b *ruleBuilder) matchProfiles() []generictables.Rule { }, ) } + + ruleComment := fmt.Sprintf("%s if no profiles matched", b.denyActionString) if b.flowLogsEnabled { rules = append(rules, b.nflogAction(true, true)) } - rules = append(rules, - generictables.Rule{ - Match: Match(), - Action: b.denyAction, - Comment: []string{fmt.Sprintf("%s if no profiles matched", b.denyActionString)}, - }, - ) + + rules = append(rules, b.getDropActionOverrideRules(Match(), ruleComment)...) + + return rules +} + +func (b *ruleBuilder) getDropActionOverrideRules(matchCriteria generictables.MatchCriteria, comment string) []generictables.Rule { + var rules []generictables.Rule + if strings.HasPrefix(b.dropActionOverride, "LOG") { + rules = append(rules, + generictables.Rule{ + Match: matchCriteria, + Action: Actions().Log("calico-drop"), + Comment: []string{comment}, + }) + } + if strings.HasSuffix(b.dropActionOverride, "ACCEPT") { + rules = append(rules, + generictables.Rule{ + Match: matchCriteria, + Action: Actions().Allow(), + Comment: []string{comment}, + }) + } else { + rules = append(rules, + generictables.Rule{ + Match: matchCriteria, + Action: b.denyAction, + Comment: []string{comment}, + }) + } return rules } diff --git a/felix/rules/epmark_test.go b/felix/rules/epmark_test.go index 3217079e33d..d0873d909ac 100644 --- a/felix/rules/epmark_test.go +++ b/felix/rules/epmark_test.go @@ -17,8 +17,7 @@ package rules_test import ( "strings" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/projectcalico/calico/felix/rules" @@ -52,8 +51,8 @@ func init() { func(eps []string, expected []uint32) { result := []uint32{} for _, ep := range eps { - if strings.HasPrefix(ep, "x") { - epmm.ReleaseEndpointMark(strings.TrimPrefix(ep, "x")) + if after, ok := strings.CutPrefix(ep, "x"); ok { + epmm.ReleaseEndpointMark(after) } else { // if error, function return 0 which match ErrMark. mark, _ := epmm.GetEndpointMark(ep) diff --git a/felix/rules/nat.go b/felix/rules/nat.go index 1254b42ea05..ab5ea7b2650 100644 --- a/felix/rules/nat.go +++ b/felix/rules/nat.go @@ -16,6 +16,7 @@ package rules import ( "fmt" + "net" "sort" "strings" @@ -92,7 +93,7 @@ func (r *DefaultRuleRenderer) NATOutgoingChain(natOutgoingActive bool, ipVersion toPorts := fmt.Sprintf("%d-%d", r.NATPortRange.MinPort, r.NATPortRange.MaxPort) portRangeSnatRule := r.Masq(toPorts) if r.NATOutgoingAddress != nil { - toAddress := fmt.Sprintf("%s:%s", r.NATOutgoingAddress.String(), toPorts) + toAddress := net.JoinHostPort(r.NATOutgoingAddress.String(), toPorts) portRangeSnatRule = r.SNAT(toAddress) } rules = []generictables.Rule{ diff --git a/felix/rules/nat_test.go b/felix/rules/nat_test.go index 8e39606e3bb..72f0ce59e04 100644 --- a/felix/rules/nat_test.go +++ b/felix/rules/nat_test.go @@ -18,7 +18,7 @@ import ( "fmt" "net" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -44,7 +44,7 @@ var _ = Describe("NAT", func() { var renderer RuleRenderer BeforeEach(func() { - renderer = NewRenderer(rrConfigNormal) + renderer = NewRenderer(rrConfigNormal, false) }) It("should render rules when active", func() { @@ -63,7 +63,7 @@ var _ = Describe("NAT", func() { It("should render rules when active with all hosts NAT exclusion", func() { localConfig := rrConfigNormal localConfig.NATOutgoingExclusions = "IPPoolsAndHostIPs" - renderer = NewRenderer(localConfig) + renderer = NewRenderer(localConfig, false) Expect(renderer.NATOutgoingChain(true, 4)).To(Equal(&generictables.Chain{ Name: "cali-nat-outgoing", @@ -82,7 +82,7 @@ var _ = Describe("NAT", func() { snatAddress := "192.168.0.1" localConfig := rrConfigNormal localConfig.NATOutgoingAddress = net.ParseIP(snatAddress) - renderer = NewRenderer(localConfig) + renderer = NewRenderer(localConfig, false) Expect(renderer.NATOutgoingChain(true, 4)).To(Equal(&generictables.Chain{ Name: "cali-nat-outgoing", @@ -100,7 +100,7 @@ var _ = Describe("NAT", func() { // copy struct localConfig := rrConfigNormal localConfig.NATPortRange, _ = numorstring.PortFromRange(99, 100) - renderer = NewRenderer(localConfig) + renderer = NewRenderer(localConfig, false) Expect(renderer.NATOutgoingChain(true, 4)).To(Equal(&generictables.Chain{ Name: "cali-nat-outgoing", @@ -143,7 +143,7 @@ var _ = Describe("NAT", func() { localConfig := rrConfigNormal localConfig.NATPortRange, _ = numorstring.PortFromRange(99, 100) localConfig.IptablesNATOutgoingInterfaceFilter = "cali-123" - renderer = NewRenderer(localConfig) + renderer = NewRenderer(localConfig, false) Expect(renderer.NATOutgoingChain(true, 4)).To(Equal(&generictables.Chain{ Name: "cali-nat-outgoing", @@ -192,7 +192,7 @@ var _ = Describe("NAT", func() { localConfig := rrConfigNormal localConfig.NATPortRange, _ = numorstring.PortFromRange(99, 100) localConfig.NATOutgoingAddress = net.ParseIP(snatAddress) - renderer = NewRenderer(localConfig) + renderer = NewRenderer(localConfig, false) expectedAddress := fmt.Sprintf("%s:%s", snatAddress, "99-100") diff --git a/felix/rules/nflogprefix.go b/felix/rules/nflogprefix.go index 6df7b25a568..d0998327aeb 100644 --- a/felix/rules/nflogprefix.go +++ b/felix/rules/nflogprefix.go @@ -4,7 +4,8 @@ package rules import ( "fmt" - "github.com/projectcalico/calico/felix/hashutils" + "github.com/projectcalico/calico/felix/types" + "github.com/projectcalico/calico/libcalico-go/lib/hash" ) const ( @@ -25,14 +26,13 @@ const ( // O: the owner type: (P)olicy or p(R)ofile // D: the rule direction: (I)ngress or (E)gress // III: the rule index (not a fixed number of digits) -// Name: the policy or profile name (from the v1 model value - this may include both the -// namespace and tier depending on the resource type) +// Name: the policy or profile ID string, including one or more of name, namespace, and kind. // // If the total length of the prefix is greater than NFLOGPrefixMaxLength, then the first 10 chars // and the last 10 chars are left unchanged and the remainder is filled in with a hash of the original prefix // up to the max length. This allows for a reasonable stab at matching a hashed prefix with the profile or policy. -func CalculateNFLOGPrefixStr(action RuleAction, owner RuleOwnerType, dir RuleDir, idx int, name string) string { - return maybeHash(fmt.Sprintf("%c%c%c%d|%s", action, owner, dir, idx, name)) +func CalculateNFLOGPrefixStr(action RuleAction, owner RuleOwnerType, dir RuleDir, idx int, id types.IDMaker) string { + return maybeHash(fmt.Sprintf("%c%c%c%d|%s", action, owner, dir, idx, id.ID())) } // CalculateEndOfTierDropNFLOGPrefixStr calculates NFLOG prefix string to use for the no-policy-match @@ -87,12 +87,9 @@ func CalculateNoMatchProfileNFLOGPrefixStr(dir RuleDir) string { // // O: the owner type: Always (P)olicy // D: the rule direction: (I)ngress or (E)gress -// Policy: the policy name -// -// This is the same format as CalculateEndOfTierDropNFLOGPrefixStr but since the policy name always includes the -// tier as a prefix, it is not possible to have log prefix clashes between tiers and policies. -func CalculateNoMatchPolicyNFLOGPrefixStr(dir RuleDir, name string) string { - return maybeHash(fmt.Sprintf("%c%c%c|%s", RuleActionDeny, RuleOwnerTypePolicy, dir, name)) +// Policy: the policy ID, consisting of the policy name, namespace, and kind. +func CalculateNoMatchPolicyNFLOGPrefixStr(dir RuleDir, id types.IDMaker) string { + return maybeHash(fmt.Sprintf("%c%c%c|%s", RuleActionDeny, RuleOwnerTypePolicy, dir, id.ID())) } func maybeHash(prefix string) string { @@ -102,7 +99,7 @@ func maybeHash(prefix string) string { if len(prefix) >= NFLOGPrefixMaxLengthWoTerm { fixedPrefix := prefix[:hashedPrefixPrefixLen] fixedSuffix := prefix[len(prefix)-hashedPrefixSuffixLen:] - hash := hashutils.GetLengthLimitedID("", prefix, hashLen) + hash := hash.GetLengthLimitedID("", prefix, hashLen) prefix = fixedPrefix + hash + "_" + fixedSuffix } return prefix diff --git a/felix/rules/nflogprefix_test.go b/felix/rules/nflogprefix_test.go index fa177bf0bb7..d952956e03e 100644 --- a/felix/rules/nflogprefix_test.go +++ b/felix/rules/nflogprefix_test.go @@ -15,17 +15,28 @@ package rules_test import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + "fmt" + + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" . "github.com/projectcalico/calico/felix/rules" + "github.com/projectcalico/calico/felix/types" +) + +var ( + // 62 characters with XXX0|gnp/ prefixed. + chars62 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + + // 63 characters with XXX0|gnp/ prefixed. + chars63 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0" ) var _ = Describe("NFLOG prefix construction tests", func() { DescribeTable( "CalculateNFLOGPrefixStr should return correct result", - func(action RuleAction, owner RuleOwnerType, dir RuleDir, idx int, name string, expected string, expectHash bool) { + func(action RuleAction, owner RuleOwnerType, dir RuleDir, idx int, name types.IDMaker, expected string, expectHash bool) { actual := CalculateNFLOGPrefixStr(action, owner, dir, idx, name) Expect(actual).To(Equal(expected), actual) if expectHash { @@ -37,38 +48,39 @@ var _ = Describe("NFLOG prefix construction tests", func() { Entry( "Short NP name - will not hash", RuleActionAllow, RuleOwnerTypePolicy, RuleDirIngress, 0, - "namespace1/default.policy", - "API0|namespace1/default.policy", false, + types.PolicyID{Name: "default.policy", Namespace: "namespace1", Kind: v3.KindNetworkPolicy}, + "API0|np/namespace1/default.policy", + false, ), Entry( "Short profile name - will not hash", RuleActionPass, RuleOwnerTypeProfile, RuleDirEgress, 999, - "short.profile.name", + types.ProfileID{Name: "short.profile.name"}, "PRE999|short.profile.name", false, ), Entry( "Policy name makes raw prefix 62 bytes - will not hash", RuleActionDeny, RuleOwnerTypePolicy, RuleDirEgress, 88, - "01234567890123456789012345678901234567890123456789012345", - "DPE88|01234567890123456789012345678901234567890123456789012345", false, + types.PolicyID{Name: chars62, Kind: v3.KindGlobalNetworkPolicy}, + fmt.Sprintf("DPE88|gnp/%s", chars62), false, ), Entry( "Policy name makes raw prefix 63 bytes - will hash", RuleActionDeny, RuleOwnerTypePolicy, RuleDirIngress, 88, - "012345678901234567890123456789012345678901234567890123456", - "DPI88|0123_S7GBfc7R7_4bzrXpbzglqoJrRG_UxIP0CuYjRWshz_7890123456", true, + types.PolicyID{Name: chars63, Kind: v3.KindGlobalNetworkPolicy}, + "DPI88|gnp/_12IXaBPahNM7a7dZhI8zg6PStmHsgneYaax8LVSIq_RSTUVWXYZ0", true, ), Entry( "Very long GNP name - will hash", RuleActionDeny, RuleOwnerTypePolicy, RuleDirEgress, 1, - "quite-a-long-namespace/quite-a-long-tier-name.quite-a-long-policy-name", - "DPE1|quite_nnW4FYptgISH4G3jdI6KJWVcEx19s0BDp2On0wVAY_olicy-name", true, + types.PolicyID{Name: "this-is-still-quite-a-long-tier-name-even-though-its-not-required.quite-a-long-policy-name", Kind: v3.KindGlobalNetworkPolicy}, + "DPE1|gnp/t_FSGxA9eGa6t_A5Rj2agPvl4aq8-eohq0An6xEW4n7_olicy-name", true, ), Entry( "A similar (but different) very long GNP name - will hash", RuleActionDeny, RuleOwnerTypePolicy, RuleDirEgress, 2, - "quite-a-long-namespace/quite-a-long-tier-name.quite-a-long-policy-name2", - "DPE2|quite_1G46uquk9ypeSpt4I-AIn1FSwWxCefDd8GEcuoxHP_licy-name2", true, + types.PolicyID{Name: "this-is-still-quite-a-long-tier-name-even-though-its-not-required.quite-a-long-policy-name2", Kind: v3.KindGlobalNetworkPolicy}, + "DPE2|gnp/t_G4MaamO0BQNQg8Ibyy-r9qg2HIK8-7EaPOVzsy5Nv_licy-name2", true, ), ) diff --git a/felix/rules/policy.go b/felix/rules/policy.go index 49c1723ad25..e363d5f090a 100644 --- a/felix/rules/policy.go +++ b/felix/rules/policy.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2016-2026 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,75 +16,92 @@ package rules import ( "fmt" + "regexp" "strings" "github.com/sirupsen/logrus" googleproto "google.golang.org/protobuf/proto" "github.com/projectcalico/calico/felix/generictables" - "github.com/projectcalico/calico/felix/hashutils" "github.com/projectcalico/calico/felix/ipsets" "github.com/projectcalico/calico/felix/iptables" "github.com/projectcalico/calico/felix/nftables" "github.com/projectcalico/calico/felix/proto" "github.com/projectcalico/calico/felix/types" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" + "github.com/projectcalico/calico/libcalico-go/lib/hash" ) // ruleRenderer defined in rules_defs.go. func (r *DefaultRuleRenderer) PolicyToIptablesChains(policyID *types.PolicyID, policy *proto.Policy, ipVersion uint8) []*generictables.Chain { - if model.PolicyIsStaged(policyID.Name) { + if model.KindIsStaged(policyID.Kind) { logrus.Debugf("Skip programming staged policy %v", policyID.Name) return nil } + + // Build an appropriate comment for the policy. + var commentIngress, commentEgress string + if policyID.Namespace == "" { + commentIngress = fmt.Sprintf("%s %s ingress", policyID.Kind, policyID.Name) + commentEgress = fmt.Sprintf("%s %s egress", policyID.Kind, policyID.Name) + } else { + commentIngress = fmt.Sprintf("%s %s/%s ingress", policyID.Kind, policyID.Namespace, policyID.Name) + commentEgress = fmt.Sprintf("%s %s/%s egress", policyID.Kind, policyID.Namespace, policyID.Name) + } + inbound := generictables.Chain{ - Name: PolicyChainName(PolicyInboundPfx, policyID, r.NFTables), - // Note that the policy name includes the tier, so it does not need to be separately specified. + Name: PolicyChainName(PolicyInboundPfx, policyID, r.nft), Rules: r.ProtoRulesToIptablesRules( policy.InboundRules, ipVersion, RuleOwnerTypePolicy, RuleDirIngress, - policyID.Name, + policyID, + policy.Tier, policy.Untracked, - fmt.Sprintf("Policy %s ingress", policyID.Name), + commentIngress, ), } outbound := generictables.Chain{ - Name: PolicyChainName(PolicyOutboundPfx, policyID, r.NFTables), + Name: PolicyChainName(PolicyOutboundPfx, policyID, r.nft), // Note that the policy name also includes the tier, so it does not need to be separately specified. Rules: r.ProtoRulesToIptablesRules( policy.OutboundRules, ipVersion, RuleOwnerTypePolicy, RuleDirEgress, - policyID.Name, + policyID, + policy.Tier, policy.Untracked, - fmt.Sprintf("Policy %s egress", policyID.Name), + commentEgress, ), } return []*generictables.Chain{&inbound, &outbound} } func (r *DefaultRuleRenderer) ProfileToIptablesChains(profileID *types.ProfileID, profile *proto.Profile, ipVersion uint8) (inbound, outbound *generictables.Chain) { + // Profiles are not related to any tier. + tier := "" inbound = &generictables.Chain{ - Name: ProfileChainName(ProfileInboundPfx, profileID, r.NFTables), + Name: ProfileChainName(ProfileInboundPfx, profileID, r.nft), Rules: r.ProtoRulesToIptablesRules( profile.InboundRules, ipVersion, RuleOwnerTypeProfile, RuleDirIngress, - profileID.Name, + profileID, + tier, false, fmt.Sprintf("Profile %s ingress", profileID.Name), ), } outbound = &generictables.Chain{ - Name: ProfileChainName(ProfileOutboundPfx, profileID, r.NFTables), + Name: ProfileChainName(ProfileOutboundPfx, profileID, r.nft), Rules: r.ProtoRulesToIptablesRules( profile.OutboundRules, ipVersion, RuleOwnerTypeProfile, RuleDirEgress, - profileID.Name, + profileID, + tier, false, fmt.Sprintf("Profile %s egress", profileID.Name), ), @@ -97,14 +114,15 @@ func (r *DefaultRuleRenderer) ProtoRulesToIptablesRules( ipVersion uint8, owner RuleOwnerType, dir RuleDir, - name string, + id types.IDMaker, + tier string, untracked bool, chainComments ...string, ) []generictables.Rule { var rules []generictables.Rule for ii, protoRule := range protoRules { // TODO (Matt): Need rule hash when that's cleaned up. - rules = append(rules, r.ProtoRuleToIptablesRules(protoRule, ipVersion, owner, dir, ii, name, untracked)...) + rules = append(rules, r.ProtoRuleToIptablesRules(protoRule, ipVersion, owner, dir, ii, id, tier, untracked)...) } // Strip off any return rules at the end of the chain. No matter their @@ -216,7 +234,9 @@ func (r *DefaultRuleRenderer) ProtoRuleToIptablesRules( ipVersion uint8, owner RuleOwnerType, dir RuleDir, - idx int, name string, + idx int, + id types.IDMaker, + tier string, untracked bool, ) []generictables.Rule { ruleCopy := FilterRuleToIPVersion(ipVersion, pRule) @@ -353,7 +373,7 @@ func (r *DefaultRuleRenderer) ProtoRuleToIptablesRules( } rs := matchBlockBuilder.Rules - rules := r.CombineMatchAndActionsForProtoRule(ruleCopy, match, owner, dir, idx, name, untracked) + rules := r.CombineMatchAndActionsForProtoRule(ruleCopy, match, owner, dir, idx, id, tier, untracked) rs = append(rs, rules...) // Render rule annotations as comments on each rule. for i := range rs { @@ -591,7 +611,8 @@ func (r *DefaultRuleRenderer) CombineMatchAndActionsForProtoRule( owner RuleOwnerType, dir RuleDir, idx int, - name string, + id types.IDMaker, + tier string, untracked bool, ) []generictables.Rule { var rules []generictables.Rule @@ -599,13 +620,13 @@ func (r *DefaultRuleRenderer) CombineMatchAndActionsForProtoRule( if pRule.Action == "log" { // This rule should log (and possibly do something else too). - logPrefix := r.LogPrefix - if logPrefix == "" { - logPrefix = "calico-packet" + logMatch := r.NewMatch() + if len(r.LogActionRateLimit) != 0 { + logMatch = logMatch.Limit(r.LogActionRateLimit, uint16(r.LogActionRateLimitBurst)) } rules = append(rules, generictables.Rule{ - Match: r.NewMatch(), - Action: r.Log(logPrefix), + Match: logMatch, + Action: r.Log(r.generateLogPrefix(id, tier)), }) } @@ -625,7 +646,7 @@ func (r *DefaultRuleRenderer) CombineMatchAndActionsForProtoRule( Match: r.NewMatch(), Action: r.Nflog( nflogGroup, - CalculateNFLOGPrefixStr(RuleActionAllow, owner, dir, idx, name), + CalculateNFLOGPrefixStr(RuleActionAllow, owner, dir, idx, id), 0, ), }) @@ -644,7 +665,7 @@ func (r *DefaultRuleRenderer) CombineMatchAndActionsForProtoRule( Match: r.NewMatch(), Action: r.Nflog( nflogGroup, - CalculateNFLOGPrefixStr(RuleActionPass, owner, dir, idx, name), + CalculateNFLOGPrefixStr(RuleActionPass, owner, dir, idx, id), 0, ), }) @@ -662,7 +683,7 @@ func (r *DefaultRuleRenderer) CombineMatchAndActionsForProtoRule( Match: r.NewMatch(), Action: r.Nflog( nflogGroup, - CalculateNFLOGPrefixStr(RuleActionDeny, owner, dir, idx, name), + CalculateNFLOGPrefixStr(RuleActionDeny, owner, dir, idx, id), 0, ), }) @@ -700,6 +721,61 @@ func (r *DefaultRuleRenderer) CombineMatchAndActionsForProtoRule( return finalRules } +var logPrefixRE = regexp.MustCompile("%[tknp]") + +// generateLogPrefix returns a log prefix string with known specifiers replaced by their corresponding values. +// If no known specifiers are present, the log prefix is returned as-is. +// Supported specifiers in the log prefix format string: +// +// %t - Tier name +// %k - Kind (short names like gnp for GlobalNetworkPolicies) +// %n - Policy or profile name. +// %p - Policy or profile name including namespace: +// - namespace/name for namespaced kinds. +// - name for non namespaced kinds. +func (r *DefaultRuleRenderer) generateLogPrefix(id types.IDMaker, tier string) string { + logPrefix := "calico-packet" + if len(r.LogPrefix) != 0 { + logPrefix = r.LogPrefix + } + + if !strings.Contains(logPrefix, "%") { + return logPrefix + } + + var kind, name, namespace string + switch v := id.(type) { + case *types.PolicyID: + kind = v.KindShortName() + name = v.Name + namespace = v.Namespace + case *types.ProfileID: + kind = "pro" + name = v.Name + default: + kind = "unknown" + name = "unknown" + } + + return logPrefixRE.ReplaceAllStringFunc(logPrefix, func(specifier string) string { + switch specifier { + case "%k": + return kind + case "%p": + if len(namespace) != 0 { + return fmt.Sprintf("%s/%s", namespace, name) + } + return name + case "%n": + return name + case "%t": + return tier + default: + return specifier + } + }) +} + func appendProtocolMatch(match generictables.MatchCriteria, protocol *proto.Protocol, logCxt *logrus.Entry) generictables.MatchCriteria { if protocol == nil { return match @@ -951,9 +1027,9 @@ func PolicyChainName(prefix PolicyChainNamePrefix, polID *types.PolicyID, nft bo if nft { maxLen = nftables.MaxChainNameLength } - return hashutils.GetLengthLimitedID( + return hash.GetLengthLimitedID( string(prefix), - polID.Tier+"/"+polID.Name, + polID.ID(), maxLen, ) } @@ -963,7 +1039,7 @@ func ProfileChainName(prefix ProfileChainNamePrefix, profID *types.ProfileID, nf if nft { maxLen = nftables.MaxChainNameLength } - return hashutils.GetLengthLimitedID( + return hash.GetLengthLimitedID( string(prefix), profID.Name, maxLen, diff --git a/felix/rules/policy_test.go b/felix/rules/policy_test.go index 786a839081a..7c309661090 100644 --- a/felix/rules/policy_test.go +++ b/felix/rules/policy_test.go @@ -15,9 +15,11 @@ package rules_test import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + "fmt" + + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/calico/felix/environment" "github.com/projectcalico/calico/felix/generictables" @@ -28,6 +30,11 @@ import ( "github.com/projectcalico/calico/felix/types" ) +var ( + cali_pi_default_foo = "cali-pi-gnp/default.foo" + cali_po_default_foo = "cali-po-gnp/default.foo" +) + var ruleTestData = []TableEntry{ Entry("Empty rule", 4, &proto.Rule{}, ""), @@ -196,6 +203,11 @@ var filteredRuleTestData = []TableEntry{ &proto.Rule{NotDstNet: []string{"::/0"}}), } +var ( + fooPolicyID = &types.PolicyID{Name: "default.foo", Kind: v3.KindGlobalNetworkPolicy} + defaultTier = "default" +) + var _ = Describe("Protobuf rule to iptables rule conversion", func() { rrConfigNormal := Config{ IPIPEnabled: true, @@ -208,16 +220,15 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { MarkScratch1: 0x400, MarkDrop: 0x800, MarkEndpoint: 0xff000, - LogPrefix: "calico-packet", } DescribeTable( "Allow rules should be correctly rendered", func(ipVer int, in *proto.Rule, expMatch string) { rrConfigNormal.FlowLogsEnabled = false - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) rules := renderer.ProtoRuleToIptablesRules(in, uint8(ipVer), - RuleOwnerTypePolicy, RuleDirIngress, 0, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) // For allow, should be one match rule that sets the mark, then one that reads the // mark and returns. Expect(len(rules)).To(Equal(2)) @@ -231,19 +242,19 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { // Explicit allow should be treated the same as empty. in.Action = "allow" rules2 := renderer.ProtoRuleToIptablesRules(in, uint8(ipVer), - RuleOwnerTypePolicy, RuleDirIngress, 0, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) Expect(rules2).To(Equal(rules)) }, - ruleTestData..., + ruleTestData, ) DescribeTable( "Allow rules should be correctly rendered with flowlogs enabled", func(ipVer int, in *proto.Rule, expMatch string) { rrConfigNormal.FlowLogsEnabled = true - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) rules := renderer.ProtoRuleToIptablesRules(in, uint8(ipVer), - RuleOwnerTypePolicy, RuleDirIngress, 0, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) // For allow, should be one match rule that sets the mark, then one that reads the // mark and returns. Expect(len(rules)).To(Equal(3)) @@ -253,7 +264,7 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { Match: iptables.Match().MarkSingleBitSet(0x80), Action: iptables.NflogAction{ Group: 1, - Prefix: "API0|default.foo", + Prefix: "API0|gnp/default.foo", }, })) Expect(rules[2]).To(Equal(generictables.Rule{ @@ -264,10 +275,10 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { // Explicit allow should be treated the same as empty. in.Action = "allow" rules2 := renderer.ProtoRuleToIptablesRules(in, uint8(ipVer), - RuleOwnerTypePolicy, RuleDirIngress, 0, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) Expect(rules2).To(Equal(rules)) }, - ruleTestData..., + ruleTestData, ) DescribeTable( @@ -276,10 +287,10 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { for _, action := range []string{"next-tier", "pass"} { By("Rendering for action " + action) rrConfigNormal.FlowLogsEnabled = false - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) in.Action = action rules := renderer.ProtoRuleToIptablesRules(in, uint8(ipVer), - RuleOwnerTypePolicy, RuleDirIngress, 0, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) // For pass, should be one match rule that sets the mark, then one // that reads the mark and returns. Expect(len(rules)).To(Equal(2)) @@ -291,7 +302,7 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { })) } }, - ruleTestData..., + ruleTestData, ) DescribeTable( @@ -300,10 +311,10 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { for _, action := range []string{"next-tier", "pass"} { By("Rendering for action " + action) rrConfigNormal.FlowLogsEnabled = true - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) in.Action = action rules := renderer.ProtoRuleToIptablesRules(in, uint8(ipVer), - RuleOwnerTypePolicy, RuleDirIngress, 0, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) // For pass, should be one match rule that sets the mark, then one // that reads the mark and returns. Expect(len(rules)).To(Equal(3)) @@ -313,7 +324,7 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { Match: iptables.Match().MarkSingleBitSet(0x100), Action: iptables.NflogAction{ Group: 1, - Prefix: "PPI0|default.foo", + Prefix: "PPI0|gnp/default.foo", }, })) Expect(rules[2]).To(Equal(generictables.Rule{ @@ -322,18 +333,18 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { })) } }, - ruleTestData..., + ruleTestData, ) DescribeTable( "Log rules should be correctly rendered", func(ipVer int, in *proto.Rule, expMatch string) { rrConfigNormal.FlowLogsEnabled = false - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) logRule := in logRule.Action = "log" rules := renderer.ProtoRuleToIptablesRules(logRule, uint8(ipVer), - RuleOwnerTypePolicy, RuleDirIngress, 0, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) // For deny, should be one match rule that just does the DROP. Expect(len(rules)).To(Equal(1)) Expect(rules[0].Match.Render()).To(Equal(expMatch)) @@ -341,12 +352,12 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { // Enabling flow log must not have any effect rrConfigNormal.FlowLogsEnabled = true - renderer = NewRenderer(rrConfigNormal) + renderer = NewRenderer(rrConfigNormal, false) rules2 := renderer.ProtoRuleToIptablesRules(logRule, uint8(ipVer), - RuleOwnerTypePolicy, RuleDirIngress, 0, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) Expect(rules2).To(Equal(rules)) }, - ruleTestData..., + ruleTestData, ) DescribeTable( @@ -355,35 +366,124 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { rrConfigNormal.FlowLogsEnabled = false rrConfigPrefix := rrConfigNormal rrConfigPrefix.LogPrefix = "foobar" - renderer := NewRenderer(rrConfigPrefix) + renderer := NewRenderer(rrConfigPrefix, false) logRule := in logRule.Action = "log" rules := renderer.ProtoRuleToIptablesRules(logRule, uint8(ipVer), - RuleOwnerTypePolicy, RuleDirIngress, 0, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) // For deny, should be one match rule that just does the DROP. Expect(len(rules)).To(Equal(1)) Expect(rules[0].Match.Render()).To(Equal(expMatch)) - Expect(rules[0].Action).To(Equal(iptables.LogAction{Prefix: "foobar"})) + Expect(rules[0].Action).To(Equal(iptables.LogAction{Prefix: rrConfigPrefix.LogPrefix})) + + // Enabling flow log must not have any effect + rrConfigPrefix.FlowLogsEnabled = true + renderer = NewRenderer(rrConfigPrefix, false) + rules2 := renderer.ProtoRuleToIptablesRules(logRule, uint8(ipVer), + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) + Expect(rules2).To(Equal(rules)) + }, + ruleTestData, + ) + + DescribeTable( + "Log rules should be correctly rendered with specifiers in prefix", + func(ipVer int, in *proto.Rule, expMatch string) { + rrConfigNormal.FlowLogsEnabled = false + rrConfigPrefix := rrConfigNormal + rrConfigPrefix.LogPrefix = "foobar %t %k %p %n - %n %p %k %t" + renderer := NewRenderer(rrConfigPrefix, false) + logRule := in + logRule.Action = "log" + rules := renderer.ProtoRuleToIptablesRules(logRule, uint8(ipVer), + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) + Expect(len(rules)).To(Equal(1)) + Expect(rules[0].Match.Render()).To(Equal(expMatch)) + logPrefix := fmt.Sprintf("foobar %s %s %s %s - %s %s %s %s", + defaultTier, fooPolicyID.KindShortName(), fooPolicyID.Name, fooPolicyID.Name, + fooPolicyID.Name, fooPolicyID.Name, fooPolicyID.KindShortName(), defaultTier, + ) + Expect(rules[0].Action).To(Equal(iptables.LogAction{Prefix: logPrefix})) + + // Should generate expected logPrefix for a Calico NetworkPolicy + cnpPolicyID := &types.PolicyID{Name: "foo", Kind: v3.KindNetworkPolicy, Namespace: "app"} + tier := "admin" + rrConfigPrefix.LogPrefix = "calico-packet %k:%p:%t:%n" + renderer = NewRenderer(rrConfigPrefix, false) + rules = renderer.ProtoRuleToIptablesRules(logRule, uint8(ipVer), + RuleOwnerTypePolicy, RuleDirIngress, 0, cnpPolicyID, tier, false) + Expect(len(rules)).To(Equal(1)) + Expect(rules[0].Match.Render()).To(Equal(expMatch)) + logPrefix = fmt.Sprintf("calico-packet %s:%s:%s:%s", + cnpPolicyID.KindShortName(), fmt.Sprintf("%s/%s", cnpPolicyID.Namespace, cnpPolicyID.Name), + tier, cnpPolicyID.Name, + ) + Expect(rules[0].Action).To(Equal(iptables.LogAction{Prefix: logPrefix})) + + // Should generate expected logPrefix for a Profile + profileID := &types.ProfileID{Name: "profile1"} + rrConfigPrefix.LogPrefix = "calico-packet %p:%k:%%y%t%%n" + renderer = NewRenderer(rrConfigPrefix, false) + tier = "" // Profiles are not related to any tier. + rules = renderer.ProtoRuleToIptablesRules(logRule, uint8(ipVer), + RuleOwnerTypePolicy, RuleDirIngress, 0, profileID, tier, false) + Expect(len(rules)).To(Equal(1)) + Expect(rules[0].Match.Render()).To(Equal(expMatch)) + logPrefix = fmt.Sprintf("calico-packet %s:%s:%%%%y%s%%%s", profileID.Name, "pro", tier, "profile1") + Expect(rules[0].Action).To(Equal(iptables.LogAction{Prefix: logPrefix})) + }, + ruleTestData, + ) + + DescribeTable( + "Log rules should be correctly rendered with rate limits", + func(ipVer int, in *proto.Rule, expMatch string) { + rrConfigNormal.FlowLogsEnabled = false + rrConfigPrefix := rrConfigNormal + rrConfigPrefix.LogActionRateLimit = "50/minute" + rrConfigPrefix.LogPrefix = "foobar" + renderer := NewRenderer(rrConfigPrefix, false) + logRule := in + logRule.Action = "log" + rules := renderer.ProtoRuleToIptablesRules(logRule, uint8(ipVer), + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) + Expect(len(rules)).To(Equal(1)) + Expect(rules[0].Match.Render()).To(ContainSubstring(expMatch)) + logRateLimitMatch := fmt.Sprintf("-m limit --limit %s", rrConfigPrefix.LogActionRateLimit) + Expect(rules[0].Match.Render()).To(ContainSubstring(logRateLimitMatch)) + Expect(rules[0].Action).To(Equal(iptables.LogAction{Prefix: rrConfigPrefix.LogPrefix})) + + // With both rate and burst. + rrConfigPrefix.LogActionRateLimitBurst = 90 + renderer = NewRenderer(rrConfigPrefix, false) + rules = renderer.ProtoRuleToIptablesRules(logRule, uint8(ipVer), + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) + Expect(len(rules)).To(Equal(1)) + Expect(rules[0].Match.Render()).To(ContainSubstring(expMatch)) + logRateLimitMatch = fmt.Sprintf("-m limit --limit %s --limit-burst %d", + rrConfigPrefix.LogActionRateLimit, rrConfigPrefix.LogActionRateLimitBurst) + Expect(rules[0].Match.Render()).To(ContainSubstring(logRateLimitMatch)) + Expect(rules[0].Action).To(Equal(iptables.LogAction{Prefix: rrConfigPrefix.LogPrefix})) // Enabling flow log must not have any effect rrConfigPrefix.FlowLogsEnabled = true - renderer = NewRenderer(rrConfigPrefix) + renderer = NewRenderer(rrConfigPrefix, false) rules2 := renderer.ProtoRuleToIptablesRules(logRule, uint8(ipVer), - RuleOwnerTypePolicy, RuleDirIngress, 0, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) Expect(rules2).To(Equal(rules)) }, - ruleTestData..., + ruleTestData, ) DescribeTable( "Deny (DROP) rules should be correctly rendered", func(ipVer int, in *proto.Rule, expMatch string) { rrConfigNormal.FlowLogsEnabled = false - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) denyRule := in denyRule.Action = "deny" rules := renderer.ProtoRuleToIptablesRules(denyRule, uint8(ipVer), - RuleOwnerTypePolicy, RuleDirIngress, 0, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) // For deny, should be one match rule that just does the DROP. Expect(len(rules)).To(Equal(2)) Expect(rules[0].Match.Render()).To(Equal(expMatch)) @@ -393,18 +493,18 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { Action: iptables.DropAction{}, })) }, - ruleTestData..., + ruleTestData, ) DescribeTable( "Deny (DROP) rules should be correctly rendered with flowlogs enabled", func(ipVer int, in *proto.Rule, expMatch string) { rrConfigNormal.FlowLogsEnabled = true - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) denyRule := in denyRule.Action = "deny" rules := renderer.ProtoRuleToIptablesRules(denyRule, uint8(ipVer), - RuleOwnerTypePolicy, RuleDirIngress, 0, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) // For deny, should be one match rule that just does the DROP. Expect(len(rules)).To(Equal(3)) Expect(rules[0].Match.Render()).To(Equal(expMatch)) @@ -413,7 +513,7 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { Match: iptables.Match().MarkSingleBitSet(0x800), Action: iptables.NflogAction{ Group: 1, - Prefix: "DPI0|default.foo", + Prefix: "DPI0|gnp/default.foo", }, })) Expect(rules[2]).To(Equal(generictables.Rule{ @@ -421,21 +521,22 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { Action: iptables.DropAction{}, })) }, - ruleTestData..., + ruleTestData, ) DescribeTable( "Inbound deny rules should be correctly rendered within a policy", func(ipVer int, in *proto.Rule, expMatch string) { rrConfigNormal.FlowLogsEnabled = false - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) denyRule := in denyRule.Action = "deny" policyID := &types.PolicyID{ - Tier: "default", Name: "default.foo", + Kind: v3.KindGlobalNetworkPolicy, } policy := &proto.Policy{ + Tier: defaultTier, Namespace: "", InboundRules: []*proto.Rule{denyRule}, OutboundRules: []*proto.Rule{}, @@ -444,15 +545,15 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { } chains := renderer.PolicyToIptablesChains(policyID, policy, uint8(ipVer)) - Expect(chains[0].Name).To(Equal("cali-pi-default/default.foo")) - Expect(chains[1].Name).To(Equal("cali-po-default/default.foo")) + Expect(chains[0].Name).To(Equal(cali_pi_default_foo)) + Expect(chains[1].Name).To(Equal(cali_po_default_foo)) inbound := chains[0].Rules outbound := chains[1].Rules Expect(inbound).To(HaveLen(2)) Expect(outbound).To(ConsistOf( generictables.Rule{ - Comment: []string{"Policy default.foo egress"}, + Comment: []string{"GlobalNetworkPolicy default.foo egress"}, })) Expect(inbound[0].Match.Render()).To(Equal(expMatch)) Expect(inbound[0].Action).To(Equal(iptables.SetMarkAction{Mark: 0x800})) @@ -461,21 +562,22 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { Action: iptables.DropAction{}, })) }, - ruleTestData..., + ruleTestData, ) DescribeTable( "Inbound deny rules should be correctly rendered within a policy with flowlogs enabled", func(ipVer int, in *proto.Rule, expMatch string) { rrConfigNormal.FlowLogsEnabled = true - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) denyRule := in denyRule.Action = "deny" policyID := &types.PolicyID{ - Tier: "default", Name: "default.foo", + Kind: v3.KindGlobalNetworkPolicy, } policy := &proto.Policy{ + Tier: defaultTier, Namespace: "", InboundRules: []*proto.Rule{denyRule}, OutboundRules: []*proto.Rule{}, @@ -484,15 +586,15 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { } chains := renderer.PolicyToIptablesChains(policyID, policy, uint8(ipVer)) - Expect(chains[0].Name).To(Equal("cali-pi-default/default.foo")) - Expect(chains[1].Name).To(Equal("cali-po-default/default.foo")) + Expect(chains[0].Name).To(Equal(cali_pi_default_foo)) + Expect(chains[1].Name).To(Equal(cali_po_default_foo)) inbound := chains[0].Rules outbound := chains[1].Rules Expect(inbound).To(HaveLen(3)) Expect(outbound).To(ConsistOf( generictables.Rule{ - Comment: []string{"Policy default.foo egress"}, + Comment: []string{"GlobalNetworkPolicy default.foo egress"}, })) Expect(inbound[0].Match.Render()).To(Equal(expMatch)) Expect(inbound[0].Action).To(Equal(iptables.SetMarkAction{Mark: 0x800})) @@ -500,7 +602,7 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { Match: iptables.Match().MarkSingleBitSet(0x800), Action: iptables.NflogAction{ Group: 1, - Prefix: "DPI0|default.foo", + Prefix: "DPI0|gnp/default.foo", }, })) Expect(inbound[2]).To(Equal(generictables.Rule{ @@ -508,21 +610,22 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { Action: iptables.DropAction{}, })) }, - ruleTestData..., + ruleTestData, ) DescribeTable( "Outbound deny rules should be correctly rendered within a policy", func(ipVer int, in *proto.Rule, expMatch string) { rrConfigNormal.FlowLogsEnabled = false - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) denyRule := in denyRule.Action = "deny" policyID := &types.PolicyID{ - Tier: "default", Name: "default.foo", + Kind: v3.KindGlobalNetworkPolicy, } policy := &proto.Policy{ + Tier: defaultTier, Namespace: "", InboundRules: []*proto.Rule{}, OutboundRules: []*proto.Rule{denyRule}, @@ -531,15 +634,15 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { } chains := renderer.PolicyToIptablesChains(policyID, policy, uint8(ipVer)) - Expect(chains[0].Name).To(Equal("cali-pi-default/default.foo")) - Expect(chains[1].Name).To(Equal("cali-po-default/default.foo")) + Expect(chains[0].Name).To(Equal(cali_pi_default_foo)) + Expect(chains[1].Name).To(Equal(cali_po_default_foo)) inbound := chains[0].Rules outbound := chains[1].Rules Expect(inbound).To(ConsistOf( generictables.Rule{ - Comment: []string{"Policy default.foo ingress"}, + Comment: []string{"GlobalNetworkPolicy default.foo ingress"}, })) Expect(outbound).To(HaveLen(2)) Expect(outbound[0].Match.Render()).To(Equal(expMatch)) @@ -549,22 +652,23 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { Action: iptables.DropAction{}, })) }, - ruleTestData..., + ruleTestData, ) DescribeTable( "Outbound deny rules should be correctly rendered within a policy with flowlogs enabled", func(ipVer int, in *proto.Rule, expMatch string) { rrConfigNormal.FlowLogsEnabled = true - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) denyRule := in denyRule.Action = "deny" policyID := &types.PolicyID{ - Tier: "default", Name: "default.foo", + Kind: v3.KindGlobalNetworkPolicy, } policy := &proto.Policy{ Namespace: "", + Tier: defaultTier, InboundRules: []*proto.Rule{}, OutboundRules: []*proto.Rule{denyRule}, Untracked: false, @@ -572,15 +676,15 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { } chains := renderer.PolicyToIptablesChains(policyID, policy, uint8(ipVer)) - Expect(chains[0].Name).To(Equal("cali-pi-default/default.foo")) - Expect(chains[1].Name).To(Equal("cali-po-default/default.foo")) + Expect(chains[0].Name).To(Equal(cali_pi_default_foo)) + Expect(chains[1].Name).To(Equal(cali_po_default_foo)) inbound := chains[0].Rules outbound := chains[1].Rules Expect(inbound).To(ConsistOf( generictables.Rule{ - Comment: []string{"Policy default.foo ingress"}, + Comment: []string{"GlobalNetworkPolicy default.foo ingress"}, })) Expect(outbound).To(HaveLen(3)) Expect(outbound[0].Match.Render()).To(Equal(expMatch)) @@ -589,7 +693,7 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { Match: iptables.Match().MarkSingleBitSet(0x800), Action: iptables.NflogAction{ Group: 2, - Prefix: "DPE0|default.foo", + Prefix: "DPE0|gnp/default.foo", }, })) Expect(outbound[2]).To(Equal(generictables.Rule{ @@ -597,21 +701,22 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { Action: iptables.DropAction{}, })) }, - ruleTestData..., + ruleTestData, ) DescribeTable( "Inbound deny rules should be correctly rendered within a staged policy", func(ipVer int, in *proto.Rule, expMatch string) { rrConfigNormal.FlowLogsEnabled = false - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) denyRule := in denyRule.Action = "deny" policyID := &types.PolicyID{ - Tier: "default", Name: "staged:default.foo", + Kind: v3.KindStagedGlobalNetworkPolicy, } policy := &proto.Policy{ + Tier: defaultTier, Namespace: "", InboundRules: []*proto.Rule{denyRule}, OutboundRules: []*proto.Rule{}, @@ -625,26 +730,27 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { // Should be same with flowlogs enabled. rrConfigNormal.FlowLogsEnabled = true - renderer = NewRenderer(rrConfigNormal) + renderer = NewRenderer(rrConfigNormal, false) chainsWithFlowLogs := renderer.PolicyToIptablesChains(policyID, policy, uint8(ipVer)) // Staged policies should be skipped. Expect(chainsWithFlowLogs).To(HaveLen(0)) }, - ruleTestData..., + ruleTestData, ) DescribeTable( "Outbound deny rules should be correctly rendered within a staged policy", func(ipVer int, in *proto.Rule, expMatch string) { rrConfigNormal.FlowLogsEnabled = false - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) denyRule := in denyRule.Action = "deny" policyID := &types.PolicyID{ - Tier: "default", Name: "staged:default.foo", + Kind: v3.KindStagedGlobalNetworkPolicy, } policy := &proto.Policy{ + Tier: defaultTier, Namespace: "", InboundRules: []*proto.Rule{}, OutboundRules: []*proto.Rule{denyRule}, @@ -658,25 +764,26 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { // Should be same with flowlogs enabled. rrConfigNormal.FlowLogsEnabled = true - renderer = NewRenderer(rrConfigNormal) + renderer = NewRenderer(rrConfigNormal, false) chainsWithFlowLogs := renderer.PolicyToIptablesChains(policyID, policy, uint8(ipVer)) // Staged policies should be skipped. Expect(chainsWithFlowLogs).To(HaveLen(0)) }, - ruleTestData..., + ruleTestData, ) DescribeTable( "Inbound allow rules should be correctly rendered within a staged policy", func(ipVer int, in *proto.Rule, expMatch string) { rrConfigNormal.FlowLogsEnabled = false - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) denyRule := in policyID := &types.PolicyID{ - Tier: "default", Name: "staged:default.foo", + Kind: v3.KindStagedGlobalNetworkPolicy, } policy := &proto.Policy{ + Tier: defaultTier, Namespace: "", InboundRules: []*proto.Rule{denyRule}, OutboundRules: []*proto.Rule{}, @@ -690,25 +797,26 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { // Should be same with flowlogs enabled. rrConfigNormal.FlowLogsEnabled = true - renderer = NewRenderer(rrConfigNormal) + renderer = NewRenderer(rrConfigNormal, false) chainsWithFlowLogs := renderer.PolicyToIptablesChains(policyID, policy, uint8(ipVer)) // Staged policies should be skipped. Expect(chainsWithFlowLogs).To(HaveLen(0)) }, - ruleTestData..., + ruleTestData, ) DescribeTable( "Outbound allow rules should be correctly rendered within a staged policy", func(ipVer int, in *proto.Rule, expMatch string) { rrConfigNormal.FlowLogsEnabled = false - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) denyRule := in policyID := &types.PolicyID{ - Tier: "default", Name: "staged:default.foo", + Kind: v3.KindStagedGlobalNetworkPolicy, } policy := &proto.Policy{ + Tier: defaultTier, Namespace: "", InboundRules: []*proto.Rule{}, OutboundRules: []*proto.Rule{denyRule}, @@ -722,12 +830,12 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { // Should be same with flowlogs enabled. rrConfigNormal.FlowLogsEnabled = true - renderer = NewRenderer(rrConfigNormal) + renderer = NewRenderer(rrConfigNormal, false) chainsWithFlowLogs := renderer.PolicyToIptablesChains(policyID, policy, uint8(ipVer)) // Staged policies should be skipped. Expect(chainsWithFlowLogs).To(HaveLen(0)) }, - ruleTestData..., + ruleTestData, ) DescribeTable( @@ -736,11 +844,11 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { rrConfigReject := rrConfigNormal rrConfigReject.FilterDenyAction = "REJECT" rrConfigReject.FlowLogsEnabled = false - renderer := NewRenderer(rrConfigReject) + renderer := NewRenderer(rrConfigReject, false) denyRule := in denyRule.Action = "deny" rules := renderer.ProtoRuleToIptablesRules(denyRule, uint8(ipVer), - RuleOwnerTypePolicy, RuleDirIngress, 0, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) // For deny, should be one match rule that just does the REJECT. Expect(len(rules)).To(Equal(2)) Expect(rules[0].Match.Render()).To(Equal(expMatch)) @@ -750,7 +858,7 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { Action: iptables.RejectAction{}, })) }, - ruleTestData..., + ruleTestData, ) DescribeTable( @@ -759,11 +867,11 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { rrConfigReject := rrConfigNormal rrConfigReject.FilterDenyAction = "REJECT" rrConfigReject.FlowLogsEnabled = true - renderer := NewRenderer(rrConfigReject) + renderer := NewRenderer(rrConfigReject, false) denyRule := in denyRule.Action = "deny" rules := renderer.ProtoRuleToIptablesRules(denyRule, uint8(ipVer), - RuleOwnerTypePolicy, RuleDirIngress, 0, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) // For deny, should be one match rule that just does the REJECT. Expect(len(rules)).To(Equal(3)) Expect(rules[0].Match.Render()).To(Equal(expMatch)) @@ -772,7 +880,7 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { Match: iptables.Match().MarkSingleBitSet(0x800), Action: iptables.NflogAction{ Group: 1, - Prefix: "DPI0|default.foo", + Prefix: "DPI0|gnp/default.foo", }, })) Expect(rules[2]).To(Equal(generictables.Rule{ @@ -780,14 +888,14 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { Action: iptables.RejectAction{}, })) }, - ruleTestData..., + ruleTestData, ) const ( clearBothMarksRule = "-A test --jump MARK --set-mark 0x0/0x600" preSetAllBlocksMarkRule = "-A test --jump MARK --set-mark 0x200/0x600" allowIfAllMarkRule = "-A test -m mark --mark 0x200/0x200 --jump MARK --set-mark 0x80/0x80" - nflogAllowRule = "-A test -m mark --mark 0x80/0x80 --jump NFLOG --nflog-group 1 --nflog-prefix API0|default.foo --nflog-range 80" + nflogAllowRule = "-A test -m mark --mark 0x80/0x80 --jump NFLOG --nflog-group 1 --nflog-prefix API0|gnp/default.foo --nflog-range 80" allowIfAllMarkAndTCPRule = "-A test -p tcp -m mark --mark 0x200/0x200 --jump MARK --set-mark 0x80/0x80" allowIfAllMarkAndUDPRule = "-A test -p udp -m mark --mark 0x200/0x200 --jump MARK --set-mark 0x80/0x80" returnRule = "-A test -m mark --mark 0x80/0x80 --jump RETURN" @@ -801,7 +909,7 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { "CIDR split tests", func(numSrc, numNotSrc, numDst, numNotDst int, expected []string) { rrConfigNormal.FlowLogsEnabled = false - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) pRule := proto.Rule{ SrcNet: []string{"10.0.0.0/24", "10.0.1.0/24"}[:numSrc], NotSrcNet: []string{"11.0.0.0/24", "11.0.1.0/24"}[:numNotSrc], @@ -810,7 +918,7 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { Action: "allow", } iptRules := renderer.ProtoRuleToIptablesRules(&pRule, 4, - RuleOwnerTypePolicy, RuleDirIngress, 0, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) rendered := []string{} for _, ir := range iptRules { s := iptables.NewIptablesRenderer("").RenderAppend(&ir, "test", "", &environment.Features{}) @@ -918,7 +1026,7 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { "CIDR split tests with flowlogs enabled", func(numSrc, numNotSrc, numDst, numNotDst int, expected []string) { rrConfigNormal.FlowLogsEnabled = true - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) pRule := proto.Rule{ SrcNet: []string{"10.0.0.0/24", "10.0.1.0/24"}[:numSrc], NotSrcNet: []string{"11.0.0.0/24", "11.0.1.0/24"}[:numNotSrc], @@ -927,7 +1035,7 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { Action: "allow", } iptRules := renderer.ProtoRuleToIptablesRules(&pRule, 4, - RuleOwnerTypePolicy, RuleDirIngress, 0, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) rendered := []string{} for _, ir := range iptRules { s := iptables.NewIptablesRenderer("").RenderAppend(&ir, "test", "", &environment.Features{}) @@ -1053,9 +1161,9 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { "Named port tests", func(pRule *proto.Rule, expected []string) { rrConfigNormal.FlowLogsEnabled = false - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) iptRules := renderer.ProtoRuleToIptablesRules(pRule, 4, - RuleOwnerTypePolicy, RuleDirIngress, 0, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) rendered := []string{} for _, ir := range iptRules { s := iptables.NewIptablesRenderer("").RenderAppend(&ir, "test", "", &environment.Features{}) @@ -1449,9 +1557,9 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { "Named port tests with flowlogs enabled", func(pRule *proto.Rule, expected []string) { rrConfigNormal.FlowLogsEnabled = true - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) iptRules := renderer.ProtoRuleToIptablesRules(pRule, 4, - RuleOwnerTypePolicy, RuleDirIngress, 0, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) rendered := []string{} for _, ir := range iptRules { s := iptables.NewIptablesRenderer("").RenderAppend(&ir, "test", "", &environment.Features{}) @@ -1863,60 +1971,60 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { var renderer *DefaultRuleRenderer BeforeEach(func() { rrConfigNormal.FlowLogsEnabled = false - renderer = NewRenderer(rrConfigNormal).(*DefaultRuleRenderer) + renderer = NewRenderer(rrConfigNormal, false).(*DefaultRuleRenderer) }) It("should skip rules of incorrect IP version", func() { rules := renderer.ProtoRulesToIptablesRules([]*proto.Rule{{IpVersion: 4}}, 6, - RuleOwnerTypePolicy, RuleDirIngress, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, fooPolicyID, defaultTier, false) Expect(rules).To(BeEmpty()) }) It("should skip with mixed source CIDR matches", func() { rules := renderer.ProtoRulesToIptablesRules([]*proto.Rule{{SrcNet: []string{"10.0.0.1"}}}, 6, - RuleOwnerTypePolicy, RuleDirIngress, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, fooPolicyID, defaultTier, false) Expect(rules).To(BeEmpty()) }) It("should skip with mixed source CIDR matches", func() { rules := renderer.ProtoRulesToIptablesRules([]*proto.Rule{{SrcNet: []string{"feed::beef"}}}, 4, - RuleOwnerTypePolicy, RuleDirIngress, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, fooPolicyID, defaultTier, false) Expect(rules).To(BeEmpty()) }) It("should skip with mixed dest CIDR matches", func() { rules := renderer.ProtoRulesToIptablesRules([]*proto.Rule{{DstNet: []string{"10.0.0.1"}}}, 6, - RuleOwnerTypePolicy, RuleDirIngress, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, fooPolicyID, defaultTier, false) Expect(rules).To(BeEmpty()) }) It("should skip with mixed dest CIDR matches", func() { rules := renderer.ProtoRulesToIptablesRules([]*proto.Rule{{DstNet: []string{"feed::beef"}}}, 4, - RuleOwnerTypePolicy, RuleDirIngress, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, fooPolicyID, defaultTier, false) Expect(rules).To(BeEmpty()) }) It("should skip with mixed negated source CIDR matches", func() { rules := renderer.ProtoRulesToIptablesRules([]*proto.Rule{{NotSrcNet: []string{"10.0.0.1"}}}, 6, - RuleOwnerTypePolicy, RuleDirIngress, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, fooPolicyID, defaultTier, false) Expect(rules).To(BeEmpty()) }) It("should skip with mixed negated source CIDR matches", func() { rules := renderer.ProtoRulesToIptablesRules([]*proto.Rule{{NotSrcNet: []string{"feed::beef"}}}, 4, - RuleOwnerTypePolicy, RuleDirIngress, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, fooPolicyID, defaultTier, false) Expect(rules).To(BeEmpty()) }) It("should skip with mixed negated dest CIDR matches", func() { rules := renderer.ProtoRulesToIptablesRules([]*proto.Rule{{NotDstNet: []string{"10.0.0.1"}}}, 6, - RuleOwnerTypePolicy, RuleDirIngress, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, fooPolicyID, defaultTier, false) Expect(rules).To(BeEmpty()) }) It("should skip with mixed negated dest CIDR matches", func() { rules := renderer.ProtoRulesToIptablesRules([]*proto.Rule{{NotDstNet: []string{"feed::beef"}}}, 4, - RuleOwnerTypePolicy, RuleDirIngress, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, fooPolicyID, defaultTier, false) Expect(rules).To(BeEmpty()) }) }) @@ -1925,60 +2033,60 @@ var _ = Describe("Protobuf rule to iptables rule conversion", func() { var renderer *DefaultRuleRenderer BeforeEach(func() { rrConfigNormal.FlowLogsEnabled = true - renderer = NewRenderer(rrConfigNormal).(*DefaultRuleRenderer) + renderer = NewRenderer(rrConfigNormal, false).(*DefaultRuleRenderer) }) It("should skip rules of incorrect IP version", func() { rules := renderer.ProtoRulesToIptablesRules([]*proto.Rule{{IpVersion: 4}}, 6, - RuleOwnerTypePolicy, RuleDirIngress, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, fooPolicyID, defaultTier, false) Expect(rules).To(BeEmpty()) }) It("should skip with mixed source CIDR matches", func() { rules := renderer.ProtoRulesToIptablesRules([]*proto.Rule{{SrcNet: []string{"10.0.0.1"}}}, 6, - RuleOwnerTypePolicy, RuleDirIngress, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, fooPolicyID, defaultTier, false) Expect(rules).To(BeEmpty()) }) It("should skip with mixed source CIDR matches", func() { rules := renderer.ProtoRulesToIptablesRules([]*proto.Rule{{SrcNet: []string{"feed::beef"}}}, 4, - RuleOwnerTypePolicy, RuleDirIngress, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, fooPolicyID, defaultTier, false) Expect(rules).To(BeEmpty()) }) It("should skip with mixed dest CIDR matches", func() { rules := renderer.ProtoRulesToIptablesRules([]*proto.Rule{{DstNet: []string{"10.0.0.1"}}}, 6, - RuleOwnerTypePolicy, RuleDirIngress, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, fooPolicyID, defaultTier, false) Expect(rules).To(BeEmpty()) }) It("should skip with mixed dest CIDR matches", func() { rules := renderer.ProtoRulesToIptablesRules([]*proto.Rule{{DstNet: []string{"feed::beef"}}}, 4, - RuleOwnerTypePolicy, RuleDirIngress, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, fooPolicyID, defaultTier, false) Expect(rules).To(BeEmpty()) }) It("should skip with mixed negated source CIDR matches", func() { rules := renderer.ProtoRulesToIptablesRules([]*proto.Rule{{NotSrcNet: []string{"10.0.0.1"}}}, 6, - RuleOwnerTypePolicy, RuleDirIngress, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, fooPolicyID, defaultTier, false) Expect(rules).To(BeEmpty()) }) It("should skip with mixed negated source CIDR matches", func() { rules := renderer.ProtoRulesToIptablesRules([]*proto.Rule{{NotSrcNet: []string{"feed::beef"}}}, 4, - RuleOwnerTypePolicy, RuleDirIngress, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, fooPolicyID, defaultTier, false) Expect(rules).To(BeEmpty()) }) It("should skip with mixed negated dest CIDR matches", func() { rules := renderer.ProtoRulesToIptablesRules([]*proto.Rule{{NotDstNet: []string{"10.0.0.1"}}}, 6, - RuleOwnerTypePolicy, RuleDirIngress, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, fooPolicyID, defaultTier, false) Expect(rules).To(BeEmpty()) }) It("should skip with mixed negated dest CIDR matches", func() { rules := renderer.ProtoRulesToIptablesRules([]*proto.Rule{{NotDstNet: []string{"feed::beef"}}}, 4, - RuleOwnerTypePolicy, RuleDirIngress, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, fooPolicyID, defaultTier, false) Expect(rules).To(BeEmpty()) }) }) @@ -1996,20 +2104,19 @@ var _ = Describe("Filtered rules (negated catch-all CIDR validation)", func() { MarkScratch1: 0x400, MarkDrop: 0x800, MarkEndpoint: 0xff000, - LogPrefix: "calico-packet", } DescribeTable( "Rules with catch-all negated CIDRs should be filtered out", func(ipVer int, in *proto.Rule) { rrConfigNormal.FlowLogsEnabled = false - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) rules := renderer.ProtoRuleToIptablesRules(in, uint8(ipVer), - RuleOwnerTypePolicy, RuleDirIngress, 0, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) // Rules with catch-all negated CIDRs should be completely filtered out Expect(len(rules)).To(Equal(0)) }, - filteredRuleTestData..., + filteredRuleTestData, ) }) @@ -2187,14 +2294,13 @@ var _ = Describe("rule metadata tests", func() { MarkScratch1: 0x400, MarkDrop: 0x800, MarkEndpoint: 0xff000, - LogPrefix: "calico-packet", } It("IPv4 should include annotations in comments", func() { rrConfigNormal.FlowLogsEnabled = false - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) rs := renderer.ProtoRuleToIptablesRules(rule, uint8(4), - RuleOwnerTypePolicy, RuleDirIngress, 0, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) for _, r := range rs { Expect(r.Comment).To(ContainElement("testkey00=testvalue00")) Expect(r.Comment).To(ContainElement("testkey01=testvalue01")) @@ -2202,9 +2308,9 @@ var _ = Describe("rule metadata tests", func() { // It should be the same with flowlogs enabled rrConfigNormal.FlowLogsEnabled = true - renderer = NewRenderer(rrConfigNormal) + renderer = NewRenderer(rrConfigNormal, false) rs1 := renderer.ProtoRuleToIptablesRules(rule, uint8(4), - RuleOwnerTypePolicy, RuleDirIngress, 0, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) for _, r := range rs1 { Expect(r.Comment).To(ContainElement("testkey00=testvalue00")) Expect(r.Comment).To(ContainElement("testkey01=testvalue01")) @@ -2213,9 +2319,9 @@ var _ = Describe("rule metadata tests", func() { It("IPv6 should include annotations in comments", func() { rrConfigNormal.FlowLogsEnabled = false - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) rs := renderer.ProtoRuleToIptablesRules(rule, uint8(6), - RuleOwnerTypePolicy, RuleDirIngress, 0, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) for _, r := range rs { Expect(r.Comment).To(ContainElement("testkey00=testvalue00")) Expect(r.Comment).To(ContainElement("testkey01=testvalue01")) @@ -2223,9 +2329,9 @@ var _ = Describe("rule metadata tests", func() { // It should be the same with flowlogs enabled rrConfigNormal.FlowLogsEnabled = true - renderer = NewRenderer(rrConfigNormal) + renderer = NewRenderer(rrConfigNormal, false) rs1 := renderer.ProtoRuleToIptablesRules(rule, uint8(6), - RuleOwnerTypePolicy, RuleDirIngress, 0, "default.foo", false) + RuleOwnerTypePolicy, RuleDirIngress, 0, fooPolicyID, defaultTier, false) for _, r := range rs1 { Expect(r.Comment).To(ContainElement("testkey00=testvalue00")) Expect(r.Comment).To(ContainElement("testkey01=testvalue01")) @@ -2234,35 +2340,37 @@ var _ = Describe("rule metadata tests", func() { It("should include a chain name comment with a policy", func() { rrConfigNormal.FlowLogsEnabled = false - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) chains := renderer.PolicyToIptablesChains( &types.PolicyID{ Name: "long-policy-name-that-gets-hashed", + Kind: v3.KindGlobalNetworkPolicy, }, &proto.Policy{ + Tier: defaultTier, InboundRules: []*proto.Rule{{Action: "allow"}}, }, 4, ) Expect(chains).To(ConsistOf( &generictables.Chain{ - Name: "cali-pi-_FJ9yUkNpzshVDh2n7mg", + Name: "cali-pi-_PymyFHQQg3808jiz1OJ", Rules: []generictables.Rule{ { Match: iptables.Match(), Action: iptables.SetMarkAction{Mark: 0x80}, Comment: []string{ - "Policy long-policy-name-that-gets-hashed ingress", + "GlobalNetworkPolicy long-policy-name-that-gets-hashed ingress", }, }, }, }, &generictables.Chain{ - Name: "cali-po-_FJ9yUkNpzshVDh2n7mg", + Name: "cali-po-_PymyFHQQg3808jiz1OJ", Rules: []generictables.Rule{ { Comment: []string{ - "Policy long-policy-name-that-gets-hashed egress", + "GlobalNetworkPolicy long-policy-name-that-gets-hashed egress", }, }, }, @@ -2272,43 +2380,45 @@ var _ = Describe("rule metadata tests", func() { It("should include a chain name comment with a policy and flowlogs enabled", func() { rrConfigNormal.FlowLogsEnabled = true - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) chains := renderer.PolicyToIptablesChains( &types.PolicyID{ Name: "long-policy-name-that-gets-hashed", + Kind: v3.KindGlobalNetworkPolicy, }, &proto.Policy{ + Tier: defaultTier, InboundRules: []*proto.Rule{{Action: "allow"}}, }, 4, ) Expect(chains).To(ConsistOf( &generictables.Chain{ - Name: "cali-pi-_FJ9yUkNpzshVDh2n7mg", + Name: "cali-pi-_PymyFHQQg3808jiz1OJ", Rules: []generictables.Rule{ { Match: iptables.Match(), Action: iptables.SetMarkAction{Mark: 0x80}, Comment: []string{ - "Policy long-policy-name-that-gets-hashed ingress", + "GlobalNetworkPolicy long-policy-name-that-gets-hashed ingress", }, }, { Match: iptables.Match().MarkSingleBitSet(0x80), Action: iptables.NflogAction{ Group: 1, - Prefix: "API0|long-policy-name-that-gets-hashed", + Prefix: "API0|gnp/long-policy-name-that-gets-hashed", }, Comment: nil, }, }, }, &generictables.Chain{ - Name: "cali-po-_FJ9yUkNpzshVDh2n7mg", + Name: "cali-po-_PymyFHQQg3808jiz1OJ", Rules: []generictables.Rule{ { Comment: []string{ - "Policy long-policy-name-that-gets-hashed egress", + "GlobalNetworkPolicy long-policy-name-that-gets-hashed egress", }, }, }, @@ -2318,7 +2428,7 @@ var _ = Describe("rule metadata tests", func() { It("should include a chain name comment with a profile", func() { rrConfigNormal.FlowLogsEnabled = false - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) inbound, outbound := renderer.ProfileToIptablesChains( &types.ProfileID{ Name: "long-policy-name-that-gets-hashed", @@ -2356,7 +2466,7 @@ var _ = Describe("rule metadata tests", func() { It("should include a chain name comment with a profile and flowlogs enabled", func() { rrConfigNormal.FlowLogsEnabled = true - renderer := NewRenderer(rrConfigNormal) + renderer := NewRenderer(rrConfigNormal, false) inbound, outbound := renderer.ProfileToIptablesChains( &types.ProfileID{ Name: "long-policy-name-that-gets-hashed", diff --git a/felix/rules/rule_defs.go b/felix/rules/rule_defs.go index ca4f92cd3ba..6b49b51b97c 100644 --- a/felix/rules/rule_defs.go +++ b/felix/rules/rule_defs.go @@ -319,7 +319,16 @@ type RuleRenderer interface { PolicyToIptablesChains(policyID *types.PolicyID, policy *proto.Policy, ipVersion uint8) []*generictables.Chain ProfileToIptablesChains(profileID *types.ProfileID, policy *proto.Profile, ipVersion uint8) (inbound, outbound *generictables.Chain) - ProtoRuleToIptablesRules(pRule *proto.Rule, ipVersion uint8, owner RuleOwnerType, dir RuleDir, idx int, name string, untracked bool) []generictables.Rule + ProtoRuleToIptablesRules( + pRule *proto.Rule, + ipVersion uint8, + owner RuleOwnerType, + dir RuleDir, + idx int, + id types.IDMaker, + tier string, + untracked bool, + ) []generictables.Rule NATOutgoingChain(active bool, ipVersion uint8) *generictables.Chain @@ -356,6 +365,9 @@ type DefaultRuleRenderer struct { // maxNameLength is the maximum length of a chain name. maxNameLength int + + // nft is true if we are generating NFTables rules. + nft bool } func (r *DefaultRuleRenderer) IptablesFilterDenyAction() generictables.Action { @@ -424,7 +436,10 @@ type Config struct { WireguardEncryptHostTraffic bool RouteSource string - LogPrefix string + LogPrefix string + LogActionRateLimit string + LogActionRateLimitBurst int + EndpointToHostAction string FilterAllowAction string MangleAllowAction string @@ -444,7 +459,7 @@ type Config struct { BPFForceTrackPacketsFromIfaces []string ServiceLoopPrevention string - NFTables bool + NFTablesMode string FlowLogsEnabled bool } @@ -488,7 +503,7 @@ func (c *Config) validate() { } } -func NewRenderer(config Config) RuleRenderer { +func NewRenderer(config Config, nft bool) RuleRenderer { log.WithField("config", config).Info("Creating rule renderer.") config.validate() @@ -498,7 +513,7 @@ func NewRenderer(config Config) RuleRenderer { var drop generictables.Action = iptables.DropAction{} var ret generictables.Action = iptables.ReturnAction{} - if config.NFTables { + if nft { actions = nftables.Actions() reject = nftables.RejectAction{} accept = nftables.AcceptAction{} @@ -507,13 +522,13 @@ func NewRenderer(config Config) RuleRenderer { } newMatchFn := func() generictables.MatchCriteria { - if config.NFTables { + if nft { return nftables.Match() } return iptables.Match() } combineMatches := iptables.Combine - if config.NFTables { + if nft { combineMatches = nftables.Combine } @@ -580,7 +595,7 @@ func NewRenderer(config Config) RuleRenderer { maxNameLength := iptables.MaxChainNameLength wildcard := iptables.Wildcard - if config.NFTables { + if nft { wildcard = nftables.Wildcard maxNameLength = nftables.MaxChainNameLength } @@ -597,5 +612,6 @@ func NewRenderer(config Config) RuleRenderer { wildcard: wildcard, maxNameLength: maxNameLength, CombineMatches: combineMatches, + nft: nft, } } diff --git a/felix/rules/rules_suite_test.go b/felix/rules/rules_suite_test.go index 4236ce48c8c..ef99dc99913 100644 --- a/felix/rules/rules_suite_test.go +++ b/felix/rules/rules_suite_test.go @@ -17,9 +17,8 @@ package rules_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestRules(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/rules_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Rules Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_rules_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/rules", suiteConfig, reporterConfig) } diff --git a/felix/rules/static_test.go b/felix/rules/static_test.go index deb48311a02..3f3c9870800 100644 --- a/felix/rules/static_test.go +++ b/felix/rules/static_test.go @@ -18,7 +18,7 @@ import ( "fmt" "net" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -36,7 +36,7 @@ var _ = Describe("Static", func() { var conf Config JustBeforeEach(func() { // Cast back to the expected type so we can access a finer-grained API for testing. - rr = NewRenderer(conf).(*DefaultRuleRenderer) + rr = NewRenderer(conf, false).(*DefaultRuleRenderer) }) checkManglePostrouting := func(ipVersion uint8, ipvs bool) { @@ -1315,7 +1315,6 @@ var _ = Describe("Static", func() { }) for _, ipVersion := range []uint8{4, 6} { // Capture current value of ipVersion. - ipVersion := ipVersion ipSetThisHost := fmt.Sprintf("cali%d0this-host", ipVersion) portRanges1 := []*proto.PortRange{ diff --git a/felix/serviceindex/serviceindex_suite_test.go b/felix/serviceindex/serviceindex_suite_test.go index 1702ae33a19..416abe9db3b 100644 --- a/felix/serviceindex/serviceindex_suite_test.go +++ b/felix/serviceindex/serviceindex_suite_test.go @@ -17,9 +17,8 @@ package serviceindex_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestLabels(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/serviceindex_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "ServiceIndex Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_serviceindex_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/serviceindex", suiteConfig, reporterConfig) } diff --git a/felix/serviceindex/serviceindex_test.go b/felix/serviceindex/serviceindex_test.go index beae0bbcfc4..a519c0b691b 100644 --- a/felix/serviceindex/serviceindex_test.go +++ b/felix/serviceindex/serviceindex_test.go @@ -15,7 +15,7 @@ package serviceindex_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" discovery "k8s.io/api/discovery/v1" diff --git a/felix/statusrep/status_file_reporter.go b/felix/statusrep/status_file_reporter.go index e57a6334187..77a783813d0 100644 --- a/felix/statusrep/status_file_reporter.go +++ b/felix/statusrep/status_file_reporter.go @@ -43,7 +43,7 @@ const ( // an entry for each workload, when each workload's // policy is programmed for the first time. type EndpointStatusFileReporter struct { - endpointUpdatesC <-chan interface{} + endpointUpdatesC <-chan any endpointStatusDirPrefix string // DeltaTracker for the Workload endpoint status. @@ -90,7 +90,7 @@ type FileReporterOption func(*EndpointStatusFileReporter) // NewEndpointStatusFileReporter creates a new EndpointStatusFileReporter. func NewEndpointStatusFileReporter( - endpointUpdatesC <-chan interface{}, + endpointUpdatesC <-chan any, statusDirPath string, opts ...FileReporterOption, ) *EndpointStatusFileReporter { @@ -247,7 +247,7 @@ func (fr *EndpointStatusFileReporter) resetStoppedTimerOrInit(t *time.Timer, d t // A sub-call of SyncForever, not intended to be called outside the main loop. // Updates delta tracker state to match the received update. // Logs and discards errors generated from converting endpoint updates to endpoint keys. -func (fr *EndpointStatusFileReporter) handleEndpointUpdate(e interface{}) { +func (fr *EndpointStatusFileReporter) handleEndpointUpdate(e any) { switch m := e.(type) { case *proto.WorkloadEndpointStatusUpdate: if m.Id == nil { diff --git a/felix/statusrep/status_file_reporter_test.go b/felix/statusrep/status_file_reporter_test.go index 55f18d9a50d..9349768d8c2 100644 --- a/felix/statusrep/status_file_reporter_test.go +++ b/felix/statusrep/status_file_reporter_test.go @@ -22,7 +22,7 @@ import ( "reflect" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/sirupsen/logrus" @@ -107,7 +107,7 @@ func clearDir(dirPath string) { var _ = Describe("Endpoint Policy Status Reports [file-reporting]", func() { logrus.SetLevel(logrus.DebugLevel) - var endpointUpdatesC chan interface{} + var endpointUpdatesC chan any var ctx context.Context var cancel context.CancelFunc var doneC chan struct{} @@ -141,7 +141,7 @@ var _ = Describe("Endpoint Policy Status Reports [file-reporting]", func() { filename = filepath.Join(statusDir, mapKey) logrus.Infof("get filename %s", filename) - endpointUpdatesC = make(chan interface{}) + endpointUpdatesC = make(chan any) ctx, cancel = context.WithCancel(context.Background()) doneC = make(chan struct{}) }) diff --git a/felix/statusrep/status_reporter.go b/felix/statusrep/status_reporter.go index 86c66887d10..cd9697949e7 100644 --- a/felix/statusrep/status_reporter.go +++ b/felix/statusrep/status_reporter.go @@ -29,7 +29,7 @@ import ( type EndpointStatusReporter struct { hostname string region string - endpointUpdates <-chan interface{} + endpointUpdates <-chan any stop chan bool datastore datastore epStatusIDToStatus map[model.Key]string @@ -49,7 +49,7 @@ type pendingUpdate struct { func NewEndpointStatusReporter(hostname string, region string, - endpointUpdates <-chan interface{}, + endpointUpdates <-chan any, datastore datastore, reportingDelay time.Duration, resyncInterval time.Duration) *EndpointStatusReporter { @@ -75,7 +75,7 @@ func NewEndpointStatusReporter(hostname string, // the tickers to be mocked for UT. func newEndpointStatusReporterWithTickerChans(hostname string, region string, - endpointUpdates <-chan interface{}, + endpointUpdates <-chan any, datastore datastore, resyncTicker stoppable, resyncTickerChan <-chan time.Time, diff --git a/felix/statusrep/status_reporter_test.go b/felix/statusrep/status_reporter_test.go index 30491f06664..ef8a911aad1 100644 --- a/felix/statusrep/status_reporter_test.go +++ b/felix/statusrep/status_reporter_test.go @@ -21,7 +21,7 @@ import ( "sync" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" @@ -137,7 +137,7 @@ var updatedHostEPKey = model.HostEndpointStatusKey{ var _ = Describe("Status", func() { var esr *EndpointStatusReporter - var epUpdates chan interface{} + var epUpdates chan any var datastore *mockDatastore var resyncTicker, rateLimitTicker *mockStoppable var resyncTickerChan, rateLimitTickerChan chan time.Time @@ -150,7 +150,7 @@ var _ = Describe("Status", func() { JustBeforeEach(func() { log.Info("JustBeforeEach called, creating EndpointStatusReporter") - epUpdates = make(chan interface{}) + epUpdates = make(chan any) datastore = newMockDatastore() resyncTicker = &mockStoppable{} rateLimitTicker = &mockStoppable{} @@ -190,7 +190,7 @@ var _ = Describe("Status", func() { defer datastore.mutex.Unlock() return datastore.workloadsListed }).Should(BeTrue()) - }, 1) + }) It("should not coalesce flapping workload EP updates", func() { epUpdates <- &wlEPUpdateUp // tick #1 epUpdates <- &wlEPUpdateUp // no change, ignored @@ -199,15 +199,15 @@ var _ = Describe("Status", func() { epUpdates <- &wlEPUpdateDown // tick #4 rateLimitTickerChan <- time.Now() rateLimitTickerChan <- time.Now() - Eventually(datastore.snapshot).Should(Equal(map[model.Key]interface{}{ + Eventually(datastore.snapshot).Should(Equal(map[model.Key]any{ updatedWlEPKey: wlEPDown, })) rateLimitTickerChan <- time.Now() - Eventually(datastore.snapshot).Should(Equal(map[model.Key]interface{}{ + Eventually(datastore.snapshot).Should(Equal(map[model.Key]any{ updatedWlEPKey: wlEPUp, })) rateLimitTickerChan <- time.Now() - Eventually(datastore.snapshot).Should(Equal(map[model.Key]interface{}{ + Eventually(datastore.snapshot).Should(Equal(map[model.Key]any{ updatedWlEPKey: wlEPDown, })) }) @@ -221,7 +221,7 @@ var _ = Describe("Status", func() { rateLimitTickerChan <- time.Now() Eventually(datastore.snapshot).Should(BeEmpty()) rateLimitTickerChan <- time.Now() - Eventually(datastore.snapshot).Should(Equal(map[model.Key]interface{}{ + Eventually(datastore.snapshot).Should(Equal(map[model.Key]any{ updatedWlEPKey: wlEPUp, })) rateLimitTickerChan <- time.Now() @@ -235,15 +235,15 @@ var _ = Describe("Status", func() { epUpdates <- &hostEPUpdateDown // tick #4 rateLimitTickerChan <- time.Now() rateLimitTickerChan <- time.Now() - Eventually(datastore.snapshot).Should(Equal(map[model.Key]interface{}{ + Eventually(datastore.snapshot).Should(Equal(map[model.Key]any{ updatedHostEPKey: hostEPDown, })) rateLimitTickerChan <- time.Now() - Eventually(datastore.snapshot).Should(Equal(map[model.Key]interface{}{ + Eventually(datastore.snapshot).Should(Equal(map[model.Key]any{ updatedHostEPKey: hostEPUp, })) rateLimitTickerChan <- time.Now() - Eventually(datastore.snapshot).Should(Equal(map[model.Key]interface{}{ + Eventually(datastore.snapshot).Should(Equal(map[model.Key]any{ updatedHostEPKey: hostEPDown, })) }) @@ -257,7 +257,7 @@ var _ = Describe("Status", func() { rateLimitTickerChan <- time.Now() Eventually(datastore.snapshot).Should(BeEmpty()) rateLimitTickerChan <- time.Now() - Eventually(datastore.snapshot).Should(Equal(map[model.Key]interface{}{ + Eventually(datastore.snapshot).Should(Equal(map[model.Key]any{ updatedHostEPKey: hostEPUp, })) rateLimitTickerChan <- time.Now() @@ -278,7 +278,7 @@ var _ = Describe("Status", func() { time.Sleep(20 * time.Millisecond) Expect(datastore.snapshot()).To(BeEmpty()) rateLimitTickerChan <- time.Now() // Triggers successful retry. - Eventually(datastore.snapshot).Should(Equal(map[model.Key]interface{}{ + Eventually(datastore.snapshot).Should(Equal(map[model.Key]any{ updatedWlEPKey: wlEPUp, })) }) @@ -291,7 +291,7 @@ var _ = Describe("Status", func() { It("should report status with that region", func() { epUpdates <- &wlEPUpdateUp rateLimitTickerChan <- time.Now() // Tries first write - Eventually(datastore.snapshot).Should(Equal(map[model.Key]interface{}{ + Eventually(datastore.snapshot).Should(Equal(map[model.Key]any{ updatedWlEPKeyRegion: wlEPUp, })) }) @@ -316,11 +316,11 @@ var _ = Describe("Status", func() { rateLimitTickerChan <- time.Now() rateLimitTickerChan <- time.Now() rateLimitTickerChan <- time.Now() - Eventually(datastore.snapshot).Should(Equal(map[model.Key]interface{}{ + Eventually(datastore.snapshot).Should(Equal(map[model.Key]any{ remoteWlEPKey: wlEPUp, remoteHostEPKey: hostEPDown, })) - }, 1) + }) It("should clean up one endpoint per tick", func() { // Kick off the resync. resyncTickerChan <- time.Now() @@ -335,7 +335,7 @@ var _ = Describe("Status", func() { rateLimitTickerChan <- time.Now() epUpdates <- &proto.DataplaneInSync{} Expect(datastore.numKVs()).To(Equal(2)) - }, 1) + }) It("with concurrent datastore changes, it should handle key not found", func() { // Kick off the resync. @@ -355,7 +355,7 @@ var _ = Describe("Status", func() { rateLimitTickerChan <- time.Now() // But it should only try each delete once. Expect(datastore.NumDeletes()).To(Equal(2)) - }, 1) + }) Describe("with an error on the first 2 List() calls", func() { JustBeforeEach(func() { @@ -410,12 +410,12 @@ var _ = Describe("Status", func() { By("deleting first endpoint immediately after resync") time.Sleep(20 * time.Millisecond) Eventually(datastore.numKVs).Should(Equal(4)) - }, 1) + }) It("should process workload update", func() { epUpdates <- &wlEPUpdateUp rateLimitTickerChan <- time.Now() // Copy to active. rateLimitTickerChan <- time.Now() // Do the write. - Eventually(datastore.snapshot).Should(Equal(map[model.Key]interface{}{ + Eventually(datastore.snapshot).Should(Equal(map[model.Key]any{ localWlEPKey: wlEPUp, localHostEPKey: hostEPDown, remoteWlEPKey: wlEPUp, @@ -430,7 +430,7 @@ var _ = Describe("Status", func() { epUpdates <- &wlEPUpdateDown rateLimitTickerChan <- time.Now() rateLimitTickerChan <- time.Now() - Eventually(datastore.snapshot).Should(Equal(map[model.Key]interface{}{ + Eventually(datastore.snapshot).Should(Equal(map[model.Key]any{ localWlEPKey: wlEPUp, localHostEPKey: hostEPDown, remoteWlEPKey: wlEPUp, @@ -459,11 +459,11 @@ var _ = Describe("Status", func() { rateLimitTickerChan <- time.Now() rateLimitTickerChan <- time.Now() rateLimitTickerChan <- time.Now() - Eventually(datastore.snapshot).Should(Equal(map[model.Key]interface{}{ + Eventually(datastore.snapshot).Should(Equal(map[model.Key]any{ remoteWlEPKey: nil, remoteHostEPKey: nil, })) - }, 1) + }) It("should clean up one endpoint per tick", func() { // Kick off the resync. resyncTickerChan <- time.Now() @@ -473,18 +473,18 @@ var _ = Describe("Status", func() { By("deleting second endpoint after rate limit timer tick") rateLimitTickerChan <- time.Now() Eventually(datastore.numKVs).Should(Equal(2)) - }, 1) + }) }) }) }) var _ = Describe("Non-mocked EndpointStatusReporter", func() { var esr *EndpointStatusReporter - var epUpdates chan interface{} + var epUpdates chan any var datastore *mockDatastore BeforeEach(func() { - epUpdates = make(chan interface{}) + epUpdates = make(chan any) datastore = newMockDatastore() esr = NewEndpointStatusReporter( hostname, @@ -511,7 +511,7 @@ var _ = Describe("Non-mocked EndpointStatusReporter", func() { type mockDatastore struct { mutex sync.Mutex - kvs map[model.Key]interface{} + kvs map[model.Key]any workloadsListed, hostsListed bool ListErrs, ApplyErrs, DeleteErrs []error numDeletes int @@ -519,15 +519,15 @@ type mockDatastore struct { func newMockDatastore() *mockDatastore { return &mockDatastore{ - kvs: make(map[model.Key]interface{}), + kvs: make(map[model.Key]any), } } -func (d *mockDatastore) snapshot() map[model.Key]interface{} { +func (d *mockDatastore) snapshot() map[model.Key]any { d.mutex.Lock() defer d.mutex.Unlock() - snap := make(map[model.Key]interface{}) + snap := make(map[model.Key]any) for k, v := range d.kvs { // Return values rather than pointers for ease of comparison. if v == nil { @@ -557,7 +557,7 @@ func (d *mockDatastore) clear() { d.mutex.Lock() defer d.mutex.Unlock() - d.kvs = make(map[model.Key]interface{}) + d.kvs = make(map[model.Key]any) } func (d *mockDatastore) List(ctx context.Context, list model.ListInterface, revision string) (*model.KVPairList, error) { diff --git a/felix/statusrep/statusrep_suite_test.go b/felix/statusrep/statusrep_suite_test.go index a8f73b80bcf..4f100b03c67 100644 --- a/felix/statusrep/statusrep_suite_test.go +++ b/felix/statusrep/statusrep_suite_test.go @@ -17,9 +17,8 @@ package statusrep_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestStatusrep(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/statusrep_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Statusrep Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_statusrep_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/statusrep", suiteConfig, reporterConfig) } diff --git a/felix/stringutils/common_prefix_test.go b/felix/stringutils/common_prefix_test.go index f8d8bb12565..2cfd555f9ed 100644 --- a/felix/stringutils/common_prefix_test.go +++ b/felix/stringutils/common_prefix_test.go @@ -17,8 +17,7 @@ package stringutils_test import ( "sort" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/projectcalico/calico/felix/stringutils" diff --git a/felix/stringutils/parse_keyvalue_list.go b/felix/stringutils/parse_keyvalue_list.go index fd8c2684db0..8275396160a 100644 --- a/felix/stringutils/parse_keyvalue_list.go +++ b/felix/stringutils/parse_keyvalue_list.go @@ -35,7 +35,7 @@ func ParseKeyValueList(param string) (map[string]string, error) { return res, nil } var invalidItems []string - for _, item := range strings.Split(param, ",") { + for item := range strings.SplitSeq(param, ",") { if item == "" { // Accept empty items (e.g trailing ",") continue diff --git a/felix/stringutils/parse_keyvalue_list_test.go b/felix/stringutils/parse_keyvalue_list_test.go index c2e83979d59..35a2ae84637 100644 --- a/felix/stringutils/parse_keyvalue_list_test.go +++ b/felix/stringutils/parse_keyvalue_list_test.go @@ -17,7 +17,7 @@ package stringutils_test import ( "time" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/projectcalico/calico/felix/stringutils" diff --git a/felix/stringutils/slice_test.go b/felix/stringutils/slice_test.go index 2d0ca16d90e..3eb13b3f1f0 100644 --- a/felix/stringutils/slice_test.go +++ b/felix/stringutils/slice_test.go @@ -3,8 +3,7 @@ package stringutils_test import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/projectcalico/calico/felix/stringutils" diff --git a/felix/stringutils/stringutils_suite_test.go b/felix/stringutils/stringutils_suite_test.go index c5c4dd302be..ed71eb04f0b 100644 --- a/felix/stringutils/stringutils_suite_test.go +++ b/felix/stringutils/stringutils_suite_test.go @@ -17,9 +17,8 @@ package stringutils_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestStringutils(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/stringutils_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Stringutils Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_stringutils_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/stringutils", suiteConfig, reporterConfig) } diff --git a/felix/throttle/throttle_suite_test.go b/felix/throttle/throttle_suite_test.go index 15b2b021ffc..a5e8fd4c8aa 100644 --- a/felix/throttle/throttle_suite_test.go +++ b/felix/throttle/throttle_suite_test.go @@ -17,9 +17,8 @@ package throttle import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestJitter(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/throttle_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Throttle Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_throttle_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/throttle", suiteConfig, reporterConfig) } diff --git a/felix/throttle/throttle_test.go b/felix/throttle/throttle_test.go index 74b5a1e6e79..9f39124b51b 100644 --- a/felix/throttle/throttle_test.go +++ b/felix/throttle/throttle_test.go @@ -15,7 +15,7 @@ package throttle_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/projectcalico/calico/felix/throttle" @@ -31,7 +31,7 @@ var _ = Describe("Throttle with bucket size 3", func() { Expect(throttle.WouldAdmit()).To(BeFalse()) }) It("should leak", func() { - for i := 0; i < 5; i++ { + for range 5 { throttle.Refill() } Expect(throttle.Admit()).To(BeTrue()) @@ -41,7 +41,7 @@ var _ = Describe("Throttle with bucket size 3", func() { Expect(throttle.Admit()).To(BeFalse()) }) It("should not go negative", func() { - for i := 0; i < 5; i++ { + for range 5 { Expect(throttle.Admit()).To(BeFalse()) } throttle.Refill() diff --git a/felix/types/id_maker.go b/felix/types/id_maker.go new file mode 100644 index 00000000000..1898192fafa --- /dev/null +++ b/felix/types/id_maker.go @@ -0,0 +1,9 @@ +// Copyright (c) 2025 Tigera, Inc. All rights reserved. + +package types + +// IDMaker is a simple interface for types that can provide an ID string. Used for functions that accept either a +// PolicyID or ProfileID. +type IDMaker interface { + ID() string +} diff --git a/felix/types/policy_id.go b/felix/types/policy_id.go index 47947366d4b..f6ed841f769 100644 --- a/felix/types/policy_id.go +++ b/felix/types/policy_id.go @@ -17,28 +17,79 @@ package types import ( "fmt" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "github.com/sirupsen/logrus" + "github.com/projectcalico/calico/felix/proto" + "github.com/projectcalico/calico/libcalico-go/lib/backend/model" +) + +// Short string representations of policy kinds. +const ( + ShortKindNetworkPolicy string = "np" + ShortKindGlobalNetworkPolicy string = "gnp" + ShortKindStagedNetworkPolicy string = "snp" + ShortKindStagedGlobalNetworkPolicy string = "sgnp" + ShortKindStagedKubernetesNetworkPolicy string = "sknp" + ShortKindKubernetesNetworkPolicy string = "knp" + ShortKindKubernetesClusterNetworkPolicy string = "kcnp" + ShortKindProfile string = "pro" ) type PolicyID struct { - Tier string - Name string + Name string + Namespace string + Kind string } func (p PolicyID) String() string { - return fmt.Sprintf("{Tier: %s, Name: %s}", p.Tier, p.Name) + return fmt.Sprintf("{Name: %s, Namespace: %s, Kind: %s}", p.Name, p.Namespace, p.Kind) +} + +func (p PolicyID) ID() string { + // Include namespace only if it's set. + if p.Namespace != "" { + return fmt.Sprintf("%s/%s/%s", p.KindShortName(), p.Namespace, p.Name) + } + return fmt.Sprintf("%s/%s", p.KindShortName(), p.Name) +} + +// KindShortName returns a short string for the kind of policy. This is used where space is at a premium, +// e.g. in NFLOG prefixes. If the kind is unrecognized, it returns the full kind string. +func (p PolicyID) KindShortName() string { + switch p.Kind { + case v3.KindNetworkPolicy: + return ShortKindNetworkPolicy + case v3.KindGlobalNetworkPolicy: + return ShortKindGlobalNetworkPolicy + case v3.KindStagedNetworkPolicy: + return ShortKindStagedNetworkPolicy + case v3.KindStagedGlobalNetworkPolicy: + return ShortKindStagedGlobalNetworkPolicy + case v3.KindStagedKubernetesNetworkPolicy: + return ShortKindStagedKubernetesNetworkPolicy + case model.KindKubernetesNetworkPolicy: + return ShortKindKubernetesNetworkPolicy + case model.KindKubernetesClusterNetworkPolicy: + return ShortKindKubernetesClusterNetworkPolicy + default: + logrus.Warnf("Unrecognized policy kind %q when generating short name", p.Kind) + return p.Kind + } } func ProtoToPolicyID(p *proto.PolicyID) PolicyID { return PolicyID{ - Tier: p.GetTier(), - Name: p.GetName(), + Name: p.GetName(), + Namespace: p.GetNamespace(), + Kind: p.GetKind(), } } func PolicyIDToProto(p PolicyID) *proto.PolicyID { return &proto.PolicyID{ - Tier: p.Tier, - Name: p.Name, + Name: p.Name, + Namespace: p.Namespace, + Kind: p.Kind, } } diff --git a/felix/types/policy_id_test.go b/felix/types/policy_id_test.go index 2158fe7af9b..dababd1c2cb 100644 --- a/felix/types/policy_id_test.go +++ b/felix/types/policy_id_test.go @@ -28,8 +28,8 @@ func TestPolicyID_String(t *testing.T) { p PolicyID want string }{ - {"empty", PolicyID{}, "{Tier: , Name: }"}, - {"non-empty", PolicyID{"foo", "bar"}, "{Tier: foo, Name: bar}"}, + {"empty", PolicyID{}, "{Name: , Namespace: , Kind: }"}, + {"non-empty", PolicyID{"foo", "bar", "baz"}, "{Name: foo, Namespace: bar, Kind: baz}"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -47,7 +47,11 @@ func TestProtoToPolicyID(t *testing.T) { want PolicyID }{ {"empty", nil, PolicyID{}}, - {"non-empty", &proto.PolicyID{Tier: "foo", Name: "bar"}, PolicyID{Tier: "foo", Name: "bar"}}, + { + "non-empty", + &proto.PolicyID{Name: "bar", Namespace: "baz", Kind: "foo"}, + PolicyID{Name: "bar", Namespace: "baz", Kind: "foo"}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -65,7 +69,11 @@ func TestPolicyIDToProto(t *testing.T) { want *proto.PolicyID }{ {"empty", PolicyID{}, &proto.PolicyID{}}, - {"non-empty", PolicyID{Tier: "foo", Name: "bar"}, &proto.PolicyID{Tier: "foo", Name: "bar"}}, + { + "non-empty", + PolicyID{Name: "foo", Namespace: "bar", Kind: "bar"}, + &proto.PolicyID{Name: "foo", Namespace: "bar", Kind: "bar"}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/felix/types/profile_id.go b/felix/types/profile_id.go index df822b8aebc..e286ae85052 100644 --- a/felix/types/profile_id.go +++ b/felix/types/profile_id.go @@ -20,6 +20,10 @@ type ProfileID struct { Name string } +func (p ProfileID) ID() string { + return p.Name +} + func ProtoToProfileID(p *proto.ProfileID) ProfileID { return ProfileID{ Name: p.GetName(), diff --git a/felix/usagerep/usagerep.go b/felix/usagerep/usagerep.go index b9351d2e303..52683f64c83 100644 --- a/felix/usagerep/usagerep.go +++ b/felix/usagerep/usagerep.go @@ -161,7 +161,7 @@ func (u *UsageReporter) reportUsage(clusterGUID, clusterType, calicoVersion stri log.WithError(err).Info("Failed to report usage/get deprecation warnings.") return } - jsonResp := map[string]interface{}{} + jsonResp := map[string]any{} if err := json.NewDecoder(resp.Body).Decode(&jsonResp); err != nil { log.WithError(err).Warn( "Failed to decode report server response") diff --git a/felix/usagerep/usagerep_suite_test.go b/felix/usagerep/usagerep_suite_test.go index 20893aafbe2..eef021bf9d1 100644 --- a/felix/usagerep/usagerep_suite_test.go +++ b/felix/usagerep/usagerep_suite_test.go @@ -17,9 +17,8 @@ package usagerep import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestUsagerep(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/usagerep_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Usagerep Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_usagerep_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/usagerep", suiteConfig, reporterConfig) } diff --git a/felix/usagerep/usagerep_test.go b/felix/usagerep/usagerep_test.go index 4c66056e406..e16bdfca40e 100644 --- a/felix/usagerep/usagerep_test.go +++ b/felix/usagerep/usagerep_test.go @@ -22,7 +22,7 @@ import ( "sync" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" @@ -292,7 +292,7 @@ var _ = Describe("UsageReporter with default URL", func() { }) It("should have a random component", func() { firstDelay := u.calculateInitialDelay(1000) - for i := 0; i < 10; i++ { + for range 10 { if u.calculateInitialDelay(1000) != firstDelay { return // Success } @@ -302,7 +302,7 @@ var _ = Describe("UsageReporter with default URL", func() { It("should have an average close to expected value", func() { var total time.Duration // Give it a high but bounded number of iterations to converge. - for i := int64(0); i < 100000; i++ { + for i := range int64(100000) { total += u.calculateInitialDelay(60) if i > 100 { average := time.Duration(int64(total) / (i + 1)) diff --git a/felix/utils/run-coverage b/felix/utils/run-coverage index d2b5e0b4875..a41a956315e 100755 --- a/felix/utils/run-coverage +++ b/felix/utils/run-coverage @@ -17,7 +17,7 @@ set -e # Remove the old profiles. -find . -name "*.coverprofile" -type f -delete +find . -name "coverprofile.out" -type f -delete echo "Calculating packages to cover..." go_dirs=$(find -type f -name '*.go' | \ @@ -39,10 +39,10 @@ if [ "$#" -eq 0 ]; then fi set -x -ginkgo -cover -covermode=count -coverpkg=${go_dirs} -keepGoing -r "$@" +ginkgo -cover -covermode=count -coverpkg=${go_dirs} -keep-going -r "$@" set +x -gocovmerge $(find . -name '*.coverprofile') > combined.coverprofile +gocovmerge $(find . -name 'coverprofile.out') > combined.coverprofile # Print the coverage. We use sed to remove the verbose prefix and trim down # the whitespace. diff --git a/felix/wireguard/bootstrap.go b/felix/wireguard/bootstrap.go index 03710b39ea3..07194ecec60 100644 --- a/felix/wireguard/bootstrap.go +++ b/felix/wireguard/bootstrap.go @@ -29,7 +29,7 @@ import ( "github.com/projectcalico/calico/felix/config" "github.com/projectcalico/calico/felix/netlinkshim" - apiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/clientv3" cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" "github.com/projectcalico/calico/libcalico-go/lib/options" @@ -429,8 +429,8 @@ func getPublicKeyForNode(logCtx *log.Entry, nodeName string, calicoClient client defer expBackoffMgr.Backoff().Stop() var err error - var node *apiv3.Node - for r := 0; r < maxRetries; r++ { + var node *internalapi.Node + for range maxRetries { cxt, cancel := context.WithTimeout(context.Background(), bootstrapK8sClientTimeout) node, err = calicoClient.Nodes().Get(cxt, nodeName, options.GetOptions{}) cancel() @@ -537,7 +537,7 @@ func removeWireguardDevice( // Make a few attempts to delete the wireguard device. var err error var handle netlinkshim.Interface - for r := 0; r < bootstrapMaxRetries; r++ { + for range bootstrapMaxRetries { if handle == nil { if handle, err = getNetlinkHandle(); err != nil { <-expBackoffMgr.Backoff().C() @@ -585,8 +585,8 @@ func removeWireguardPublicKey( // Make a few attempts to remove the public key from the datastore. var err error - var thisNode *apiv3.Node - for r := 0; r < bootstrapMaxRetries; r++ { + var thisNode *internalapi.Node + for range bootstrapMaxRetries { cxt, cancel := context.WithTimeout(context.Background(), bootstrapK8sClientTimeout) thisNode, err = calicoClient.Nodes().Get(cxt, nodeName, options.GetOptions{}) cancel() diff --git a/felix/wireguard/bootstrap_test.go b/felix/wireguard/bootstrap_test.go index 935708a1f05..0efcce1f081 100644 --- a/felix/wireguard/bootstrap_test.go +++ b/felix/wireguard/bootstrap_test.go @@ -19,7 +19,7 @@ import ( "errors" "fmt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/vishvananda/netlink" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" @@ -28,7 +28,7 @@ import ( "github.com/projectcalico/calico/felix/config" mocknetlink "github.com/projectcalico/calico/felix/netlinkshim/mocknetlink" . "github.com/projectcalico/calico/felix/wireguard" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/options" "github.com/projectcalico/calico/typha/pkg/discovery" @@ -82,62 +82,62 @@ var ( node1PrivateKeyV6, _ = wgtypes.GeneratePrivateKey() node2PrivateKey, _ = wgtypes.GeneratePrivateKey() node2PrivateKeyV6, _ = wgtypes.GeneratePrivateKey() - node1V4 = &libapiv3.Node{ + node1V4 = &internalapi.Node{ ObjectMeta: v1.ObjectMeta{ Name: nodeName1, }, - Status: libapiv3.NodeStatus{ + Status: internalapi.NodeStatus{ WireguardPublicKey: node1PrivateKey.PublicKey().String(), }, } - node1V6 = &libapiv3.Node{ + node1V6 = &internalapi.Node{ ObjectMeta: v1.ObjectMeta{ Name: nodeName1, }, - Status: libapiv3.NodeStatus{ + Status: internalapi.NodeStatus{ WireguardPublicKeyV6: node1PrivateKeyV6.PublicKey().String(), }, } - node1V4V6 = &libapiv3.Node{ + node1V4V6 = &internalapi.Node{ ObjectMeta: v1.ObjectMeta{ Name: nodeName1, }, - Status: libapiv3.NodeStatus{ + Status: internalapi.NodeStatus{ WireguardPublicKey: node1PrivateKey.PublicKey().String(), WireguardPublicKeyV6: node1PrivateKeyV6.PublicKey().String(), }, } - node2V4 = &libapiv3.Node{ + node2V4 = &internalapi.Node{ ObjectMeta: v1.ObjectMeta{ Name: nodeName2, }, - Status: libapiv3.NodeStatus{ + Status: internalapi.NodeStatus{ WireguardPublicKey: node2PrivateKey.PublicKey().String(), }, } - node2V6 = &libapiv3.Node{ + node2V6 = &internalapi.Node{ ObjectMeta: v1.ObjectMeta{ Name: nodeName2, }, - Status: libapiv3.NodeStatus{ + Status: internalapi.NodeStatus{ WireguardPublicKeyV6: node2PrivateKeyV6.PublicKey().String(), }, } - node2V4V6 = &libapiv3.Node{ + node2V4V6 = &internalapi.Node{ ObjectMeta: v1.ObjectMeta{ Name: nodeName2, }, - Status: libapiv3.NodeStatus{ + Status: internalapi.NodeStatus{ WireguardPublicKey: node2PrivateKey.PublicKey().String(), WireguardPublicKeyV6: node2PrivateKeyV6.PublicKey().String(), }, } // node3 has neither IPv4 nor IPv6 wireguard config - node3 = &libapiv3.Node{ + node3 = &internalapi.Node{ ObjectMeta: v1.ObjectMeta{ Name: nodeName3, }, - Status: libapiv3.NodeStatus{ + Status: internalapi.NodeStatus{ WireguardPublicKey: "", WireguardPublicKeyV6: "", }, @@ -146,7 +146,7 @@ var ( func newMockClient() *mockClient { return &mockClient{ - nodes: make(map[string]*libapiv3.Node), + nodes: make(map[string]*internalapi.Node), } } @@ -158,14 +158,14 @@ type mockClient struct { numUpdates int numGetErrors int numUpdateErrors int - nodes map[string]*libapiv3.Node + nodes map[string]*internalapi.Node } func (c *mockClient) Nodes() clientv3.NodeInterface { return c } -func (c *mockClient) Get(_ context.Context, name string, _ options.GetOptions) (*libapiv3.Node, error) { +func (c *mockClient) Get(_ context.Context, name string, _ options.GetOptions) (*internalapi.Node, error) { c.numGets++ if c.numGetErrors > 0 { c.numGetErrors-- @@ -178,7 +178,7 @@ func (c *mockClient) Get(_ context.Context, name string, _ options.GetOptions) ( return n.DeepCopy(), nil } -func (c *mockClient) Update(_ context.Context, res *libapiv3.Node, _ options.SetOptions) (*libapiv3.Node, error) { +func (c *mockClient) Update(_ context.Context, res *internalapi.Node, _ options.SetOptions) (*internalapi.Node, error) { c.numUpdates++ if c.numUpdateErrors > 0 { c.numUpdateErrors-- diff --git a/felix/wireguard/metrics_linux.go b/felix/wireguard/metrics_linux.go index a20442a34d3..a8e5761a050 100644 --- a/felix/wireguard/metrics_linux.go +++ b/felix/wireguard/metrics_linux.go @@ -18,6 +18,7 @@ package wireguard import ( "fmt" + "maps" "os" "time" @@ -274,9 +275,7 @@ func (collector *Metrics) defaultLabelValues(publicKey string, extend prometheus l[labelPublicKey] = publicKey } - for k, v := range extend { - l[k] = v - } + maps.Copy(l, extend) return l } diff --git a/felix/wireguard/metrics_test.go b/felix/wireguard/metrics_test.go index f283e8e9b22..aaacaf49569 100644 --- a/felix/wireguard/metrics_test.go +++ b/felix/wireguard/metrics_test.go @@ -20,7 +20,7 @@ import ( "text/template" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/expfmt" @@ -174,7 +174,7 @@ var _ = Describe("wireguard metrics", func() { Expect(err).ToNot(HaveOccurred()) } - data := map[string]interface{}{ + data := map[string]any{ "pubkey": mockPeers[0].peer.PublicKey.String(), "peerkey": mockPeers[1].peer.PublicKey.String(), "endpoint": mockPeers[1].peer.Endpoint.String(), diff --git a/felix/wireguard/wireguard.go b/felix/wireguard/wireguard.go index d5bdb6e0702..3963872d62f 100644 --- a/felix/wireguard/wireguard.go +++ b/felix/wireguard/wireguard.go @@ -83,10 +83,9 @@ func newNodeData() *nodeData { func (n *nodeData) allowedCidrsForWireguard() []net.IPNet { cidrs := make([]net.IPNet, 0, n.cidrs.Len()) - n.cidrs.Iter(func(item ip.CIDR) error { + for item := range n.cidrs.All() { cidrs = append(cidrs, item.ToIPNet()) - return nil - }) + } return cidrs } @@ -465,14 +464,13 @@ func (w *Wireguard) localWorkloadCIDRAdd(cidr ip.CIDR) { if !w.localCIDRsUpdated { contained := false if node, ok := w.nodes[w.hostname]; ok { - node.cidrs.Iter(func(filtered ip.CIDR) error { + for filtered := range node.cidrs.All() { filteredIPNet := filtered.ToIPNet() if filteredIPNet.Contains(cidr.ToIPNet().IP) && filtered.Prefix() >= cidr.Prefix() { contained = true - return set.StopIteration + break } - return nil - }) + } } if !contained { w.localCIDRsUpdated = true @@ -781,22 +779,18 @@ func (w *Wireguard) Apply() (err error) { // Update link address if out of sync. if !w.inSyncInterfaceAddr { w.logCtx.Debug("Ensure wireguard interface address is correct") - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { if errLink = w.ensureLinkAddress(netlinkClient); errLink == nil { w.inSyncInterfaceAddr = true } - }() + }) } // Apply routetable updates. w.logCtx.Debug("Apply routing table updates for wireguard") - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { errRoutes = w.routetable.Apply() - }() + }) // Apply wireguard configuration. wg.Add(1) @@ -920,25 +914,23 @@ func (w *Wireguard) getLocalNodeCIDRUpdates() *nodeUpdateData { // Include all local CIDRs, update the cidrsAdded with any missing. oldFiltered := node.cidrs.Copy() - w.localCIDRs.Iter(func(cidr ip.CIDR) error { + for cidr := range w.localCIDRs.All() { if oldFiltered.Contains(cidr) { oldFiltered.Discard(cidr) } else { nodeUpdate.cidrsAdded.Add(cidr) } - return nil - }) + } // Include all local IPs that are not covered by the local CIDRs, update the cidrsAdded with any missing. - w.localIPs.Iter(func(addr ip.Addr) error { + for addr := range w.localIPs.All() { overlaps := false - w.localCIDRs.Iter(func(itemCIDR ip.CIDR) error { + for itemCIDR := range w.localCIDRs.All() { cidr := itemCIDR.ToIPNet() if cidr.Contains(addr.AsNetIP()) { overlaps = true - return set.StopIteration + break } - return nil - }) + } if !overlaps { ipAsCidr := addr.AsCIDR() if oldFiltered.Contains(ipAsCidr) { @@ -947,13 +939,11 @@ func (w *Wireguard) getLocalNodeCIDRUpdates() *nodeUpdateData { nodeUpdate.cidrsAdded.Add(ipAsCidr) } } - return nil - }) + } // Remove any existing entry that is now no longer required. - oldFiltered.Iter(func(cidr ip.CIDR) error { + for cidr := range oldFiltered.All() { nodeUpdate.cidrsDeleted.Add(cidr) - return nil - }) + } // Return the node update return nodeUpdate @@ -1069,18 +1059,16 @@ func (w *Wireguard) updateCacheFromNodeUpdates() (conflictingKeys set.Set[wgtype updated = true } - update.cidrsDeleted.Iter(func(cidr ip.CIDR) error { + for cidr := range update.cidrsDeleted.All() { logCtx.WithField("cidr", cidr).Debug("Discarding CIDR") node.cidrs.Discard(cidr) updated = true - return nil - }) - update.cidrsAdded.Iter(func(cidr ip.CIDR) error { + } + for cidr := range update.cidrsAdded.All() { logCtx.WithField("cidr", cidr).Debug("Adding CIDR") node.cidrs.Add(cidr) updated = true - return nil - }) + } if updated { // Node configuration updated. Store node data. @@ -1106,12 +1094,11 @@ func (w *Wireguard) updateRouteTableFromNodeUpdates() { // is somewhat defensive as we have the information to decide which route we need to remove - however we have // also had bugs related to state tracking so deleting both is reasonable - routetable ignores the one that is // not programmed. - update.cidrsDeleted.Iter(func(cidr ip.CIDR) error { + for cidr := range update.cidrsDeleted.All() { w.logCtx.WithField("cidr", cidr).Debug("Removing CIDR from routetable interface") - w.routetable.RouteRemove(w.interfaceName, cidr) - w.routetable.RouteRemove(routetable.InterfaceNone, cidr) - return nil - }) + w.routetable.RouteRemove(w.interfaceName, routetable.RouteKey{CIDR: cidr}) + w.routetable.RouteRemove(routetable.InterfaceNone, routetable.RouteKey{CIDR: cidr}) + } } // Now do the adds or updates. The routetable component will take care of routes that don't actually change and @@ -1148,7 +1135,7 @@ func (w *Wireguard) updateRouteTableFromNodeUpdates() { ifaceName = w.interfaceName } - updateSet.Iter(func(cidr ip.CIDR) error { + for cidr := range updateSet.All() { updateLogCtx := logCtx.WithField("cidr", cidr) updateLogCtx.Debug("Updating route for CIDR") if node.routingToWireguard != shouldRouteToWireguard { @@ -1160,15 +1147,14 @@ func (w *Wireguard) updateRouteTableFromNodeUpdates() { // information to decide which route we need to remove - however we have also had bugs related to state // tracking so deleting both is reasonable - routetable ignores the one that is not programmed. updateLogCtx.Debug("Wireguard routing has changed - delete previous route") - w.routetable.RouteRemove(routetable.InterfaceNone, cidr) - w.routetable.RouteRemove(w.interfaceName, cidr) + w.routetable.RouteRemove(routetable.InterfaceNone, routetable.RouteKey{CIDR: cidr}) + w.routetable.RouteRemove(w.interfaceName, routetable.RouteKey{CIDR: cidr}) } w.routetable.RouteUpdate(ifaceName, routetable.Target{ - Type: targetType, - CIDR: cidr, + RouteKey: routetable.RouteKey{CIDR: cidr}, + Type: targetType, }) - return nil - }) + } node.routingToWireguard = shouldRouteToWireguard } } @@ -1207,10 +1193,9 @@ func (w *Wireguard) constructWireguardDeltaFromNodeUpdates(conflictingKeys set.S } else if update.cidrsAdded.Len() > 0 { logCtx.Debug("Peer programmed, no CIDRs deleted and CIDRs added") wgpeer.AllowedIPs = make([]net.IPNet, 0, update.cidrsAdded.Len()) - update.cidrsAdded.Iter(func(cidr ip.CIDR) error { + for cidr := range update.cidrsAdded.All() { wgpeer.AllowedIPs = append(wgpeer.AllowedIPs, cidr.ToIPNet()) - return nil - }) + } updatePeer = true } @@ -1235,21 +1220,21 @@ func (w *Wireguard) constructWireguardDeltaFromNodeUpdates(conflictingKeys set.S } // Finally loop through any conflicting public keys and check each of the nodes is now handled correctly. - conflictingKeys.Iter(func(key wgtypes.Key) error { + for key := range conflictingKeys.All() { logCtx := w.logCtx.WithField("publicKey", key) logCtx.Debug("Processing public key with conflicting nodes") nodenames := w.publicKeyToNodeNames[key] if nodenames == nil { - return nil + continue } - nodenames.Iter(func(nodename string) error { + for nodename := range nodenames.All() { nodeLogCtx := logCtx.WithField("node", nodename) nodeLogCtx.Debug("Processing peer") peer := w.nodes[nodename] if peer == nil || peer.programmedInWireguard == w.shouldProgramWireguardPeer(nodename, peer) { // The peer programming matches the expected value, so nothing to do. nodeLogCtx.Debug("Programming state has not changed") - return nil + continue } else if peer.programmedInWireguard { // The peer is programmed and shouldn't be. Add a delta delete. nodeLogCtx.Debug("Programmed in wireguard, need to delete") @@ -1267,10 +1252,8 @@ func (w *Wireguard) constructWireguardDeltaFromNodeUpdates(conflictingKeys set.S PersistentKeepaliveInterval: &w.config.PersistentKeepAlive, }) } - return nil - }) - return nil - }) + } + } } // Delta updates only include updates to peer config, so if no peer updates, just return nil. @@ -1657,26 +1640,20 @@ func (w *Wireguard) ensureDisabled(netlinkClient netlinkshim.Interface) error { wg := sync.WaitGroup{} if w.routerule != nil { - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { errRule = w.routerule.Apply() - }() + }) } - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { errLink = w.ensureNoLink(netlinkClient) - }() + }) if w.config.RoutingTableIndex > 0 { // Only attempt automatic cleanup of the routing table if it is not the default table. - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { // The routetable configuration will be empty since we will not send updates, so applying this will remove the // old routes if so configured. errRoutes = w.routetable.Apply() - }() + }) wg.Wait() } @@ -1865,10 +1842,10 @@ func getOnlyItemInSet[T comparable](s set.Set[T]) *T { return nil } var i *T - s.Iter(func(item T) error { + for item := range s.All() { i = &item - return set.StopIteration - }) + break + } return i } diff --git a/felix/wireguard/wireguard_suite_test.go b/felix/wireguard/wireguard_suite_test.go index 834f4fb84b5..1ce42a0bea1 100644 --- a/felix/wireguard/wireguard_suite_test.go +++ b/felix/wireguard/wireguard_suite_test.go @@ -17,9 +17,8 @@ package wireguard_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestRules(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/wireguard_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Wireguard Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/felix_wireguard_suite.xml" + ginkgo.RunSpecs(t, "UT: felix/wireguard", suiteConfig, reporterConfig) } diff --git a/felix/wireguard/wireguard_test.go b/felix/wireguard/wireguard_test.go index d54069c2bcc..9ed9bf5ce4e 100644 --- a/felix/wireguard/wireguard_test.go +++ b/felix/wireguard/wireguard_test.go @@ -17,11 +17,12 @@ package wireguard_test import ( "errors" "fmt" + "maps" "net" "syscall" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" "github.com/vishvananda/netlink" @@ -78,13 +79,13 @@ var ( ipnet_4 = cidr_4.ToIPNet() // ipnet_5 = cidr_5.ToIPNet() // ipnet_6 = cidr_6.ToIPNet() - routekey_cidr_local = fmt.Sprintf("%d-%s", tableIndex, cidr_local) - // routekey_1 = fmt.Sprintf("%d-%s", tableIndex, cidr_1) - // routekey_2 = fmt.Sprintf("%d-%s", tableIndex, cidr_2) - // routekey_3 = fmt.Sprintf("%d-%s", tableIndex, cidr_3) - routekey_4 = fmt.Sprintf("%d-%s", tableIndex, cidr_4) - // routekey_5 = fmt.Sprintf("%d-%s", tableIndex, cidr_5) - routekey_6 = fmt.Sprintf("%d-%s", tableIndex, cidr_6) + routekey_cidr_local = fmt.Sprintf("%d-%s-0", tableIndex, cidr_local) + // routekey_1 = fmt.Sprintf("%d-%s-0", tableIndex, cidr_1) + // routekey_2 = fmt.Sprintf("%d-%s-0", tableIndex, cidr_2) + // routekey_3 = fmt.Sprintf("%d-%s-0", tableIndex, cidr_3) + routekey_4 = fmt.Sprintf("%d-%s-0", tableIndex, cidr_4) + // routekey_5 = fmt.Sprintf("%d-%s-0", tableIndex, cidr_5) + routekey_6 = fmt.Sprintf("%d-%s-0", tableIndex, cidr_6) ipv6_int1 = ip.FromString("2001:db8::192:168:0:0") ipv6_int2 = ip.FromString("2001:db8::192:168:10:0") @@ -109,13 +110,13 @@ var ( ipnetV6_4 = cidrV6_4.ToIPNet() // ipnetV6_5 = cidrV6_5.ToIPNet() // ipnetV6_6 = cidrV6_6.ToIPNet() - routekey_cidrV6_local = fmt.Sprintf("%d-%s", tableIndex, cidrV6_local) - // routekeyV6_1 = fmt.Sprintf("%d-%s", tableIndex, cidrV6_1) - // routekeyV6_2 = fmt.Sprintf("%d-%s", tableIndex, cidrV6_2) - // routekeyV6_3 = fmt.Sprintf("%d-%s", tableIndex, cidrV6_3) - routekeyV6_4 = fmt.Sprintf("%d-%s", tableIndex, cidrV6_4) - // routekeyV6_5 = fmt.Sprintf("%d-%s", tableIndex, cidr_5) - routekeyV6_6 = fmt.Sprintf("%d-%s", tableIndex, cidrV6_6) + routekey_cidrV6_local = fmt.Sprintf("%d-%s-1024", tableIndex, cidrV6_local) + // routekeyV6_1 = fmt.Sprintf("%d-%s-1024", tableIndex, cidrV6_1) + // routekeyV6_2 = fmt.Sprintf("%d-%s-1024", tableIndex, cidrV6_2) + // routekeyV6_3 = fmt.Sprintf("%d-%s-1024", tableIndex, cidrV6_3) + routekeyV6_4 = fmt.Sprintf("%d-%s-1024", tableIndex, cidrV6_4) + // routekeyV6_5 = fmt.Sprintf("%d-%s-0", tableIndex, cidr_5) + routekeyV6_6 = fmt.Sprintf("%d-%s-1024", tableIndex, cidrV6_6) ) func mustGeneratePrivateKey() wgtypes.Key { @@ -1224,9 +1225,7 @@ func describeEnableTests(enableV4, enableV6 bool) { // Take a copy of the current peer configuration for one of the tests. wgPeers = make(map[wgtypes.Key]wgtypes.Peer) - for k, p := range link.WireguardPeers { - wgPeers[k] = p - } + maps.Copy(wgPeers, link.WireguardPeers) wg.EndpointWireguardUpdate(peer2, key_peer1, nil) rtDataplane.ResetDeltas() @@ -1238,9 +1237,7 @@ func describeEnableTests(enableV4, enableV6 bool) { // Take a copy of the current peer configuration for one of the tests. wgPeersV6 = make(map[wgtypes.Key]wgtypes.Peer) - for k, p := range linkV6.WireguardPeers { - wgPeersV6[k] = p - } + maps.Copy(wgPeersV6, linkV6.WireguardPeers) wgV6.EndpointWireguardUpdate(peer2, keyV6_peer1, nil) rtDataplaneV6.ResetDeltas() @@ -1391,9 +1388,9 @@ func describeEnableTests(enableV4, enableV6 bool) { if enableV4 { // Update the mock routing table dataplane so that it knows about the wireguard interface. rtDataplane.NameToLink[ifaceName] = link - routekey_1 = fmt.Sprintf("%d-%s", tableIndex, cidr_1) - routekey_2 = fmt.Sprintf("%d-%s", tableIndex, cidr_2) - routekey_3 = fmt.Sprintf("%d-%s", tableIndex, cidr_3) + routekey_1 = fmt.Sprintf("%d-%s-0", tableIndex, cidr_1) + routekey_2 = fmt.Sprintf("%d-%s-0", tableIndex, cidr_2) + routekey_3 = fmt.Sprintf("%d-%s-0", tableIndex, cidr_3) wg.RouteUpdate(hostname, cidr_local) wg.RouteUpdate(peer1, cidr_1) @@ -1406,9 +1403,9 @@ func describeEnableTests(enableV4, enableV6 bool) { if enableV6 { // Update the mock routing table dataplane so that it knows about the wireguard interface. rtDataplaneV6.NameToLink[ifaceNameV6] = linkV6 - routekeyV6_1 = fmt.Sprintf("%d-%s", tableIndex, cidrV6_1) - routekeyV6_2 = fmt.Sprintf("%d-%s", tableIndex, cidrV6_2) - routekeyV6_3 = fmt.Sprintf("%d-%s", tableIndex, cidrV6_3) + routekeyV6_1 = fmt.Sprintf("%d-%s-1024", tableIndex, cidrV6_1) + routekeyV6_2 = fmt.Sprintf("%d-%s-1024", tableIndex, cidrV6_2) + routekeyV6_3 = fmt.Sprintf("%d-%s-1024", tableIndex, cidrV6_3) wgV6.RouteUpdate(hostname, cidrV6_local) wgV6.RouteUpdate(peer1, cidrV6_1) @@ -1523,6 +1520,7 @@ func describeEnableTests(enableV4, enableV6 bool) { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_LINK, Table: tableIndex, + Priority: 1024, })) Expect(rtDataplaneV6.RouteKeyToRoute[routekeyV6_2]).To(Equal(netlink.Route{ Family: unix.AF_INET6, @@ -1532,6 +1530,7 @@ func describeEnableTests(enableV4, enableV6 bool) { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_LINK, Table: tableIndex, + Priority: 1024, })) Expect(rtDataplaneV6.RouteKeyToRoute[routekeyV6_3]).To(Equal(netlink.Route{ Family: unix.AF_INET6, @@ -1541,6 +1540,7 @@ func describeEnableTests(enableV4, enableV6 bool) { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_LINK, Table: tableIndex, + Priority: 1024, })) Expect(rtDataplaneV6.RouteKeyToRoute[routekeyV6_4]).To(Equal(netlink.Route{ Family: unix.AF_INET6, @@ -1550,6 +1550,7 @@ func describeEnableTests(enableV4, enableV6 bool) { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_UNIVERSE, Table: tableIndex, + Priority: 1024, })) } }) @@ -1796,6 +1797,7 @@ func describeEnableTests(enableV4, enableV6 bool) { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_UNIVERSE, Table: tableIndex, + Priority: 1024, })) // Remove the wireguard config for this peer. Should have no further impact. @@ -2005,6 +2007,7 @@ func describeEnableTests(enableV4, enableV6 bool) { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_UNIVERSE, Table: tableIndex, + Priority: 1024, })) Expect(linkV6.WireguardPeers).To(HaveLen(1)) Expect(linkV6.WireguardPeers).To(HaveKey(keyV6_peer1)) @@ -2118,6 +2121,7 @@ func describeEnableTests(enableV4, enableV6 bool) { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_UNIVERSE, Table: tableIndex, + Priority: 1024, })) // Re-add the endpoint. Wireguard config will be added back in. @@ -2143,6 +2147,7 @@ func describeEnableTests(enableV4, enableV6 bool) { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_LINK, Table: tableIndex, + Priority: 1024, })) } }) @@ -2301,6 +2306,7 @@ func describeEnableTests(enableV4, enableV6 bool) { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_UNIVERSE, Table: tableIndex, + Priority: 1024, })) By("Deleting local route") @@ -2328,6 +2334,7 @@ func describeEnableTests(enableV4, enableV6 bool) { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_LINK, Table: tableIndex, + Priority: 1024, })) } }) @@ -2420,6 +2427,7 @@ func describeEnableTests(enableV4, enableV6 bool) { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_UNIVERSE, Table: tableIndex, + Priority: 1024, })) } }) @@ -2507,7 +2515,7 @@ func describeEnableTests(enableV4, enableV6 bool) { It("should reprogram the route to peer3 only", func() { if enableV4 { - routekey_4 := fmt.Sprintf("%d-%s", tableIndex, cidr_4) + routekey_4 := fmt.Sprintf("%d-%s-0", tableIndex, cidr_4) Expect(rtDataplane.UpdatedRouteKeys).To(HaveLen(1)) Expect(rtDataplane.DeletedRouteKeys).To(HaveLen(0)) Expect(rtDataplane.UpdatedRouteKeys).To(HaveKey(routekey_4)) @@ -2522,7 +2530,7 @@ func describeEnableTests(enableV4, enableV6 bool) { })) } if enableV6 { - routekeyV6_4 := fmt.Sprintf("%d-%s", tableIndex, cidrV6_4) + routekeyV6_4 := fmt.Sprintf("%d-%s-1024", tableIndex, cidrV6_4) Expect(rtDataplaneV6.UpdatedRouteKeys).To(HaveLen(1)) Expect(rtDataplaneV6.DeletedRouteKeys).To(HaveLen(0)) Expect(rtDataplaneV6.UpdatedRouteKeys).To(HaveKey(routekeyV6_4)) @@ -2534,6 +2542,7 @@ func describeEnableTests(enableV4, enableV6 bool) { Protocol: FelixRouteProtocol, Scope: netlink.SCOPE_LINK, Table: tableIndex, + Priority: 1024, })) } }) @@ -2754,9 +2763,9 @@ func describeEnableTests(enableV4, enableV6 bool) { // We expect the link to exist. link = wgDataplane.NameToLink[ifaceName] Expect(link).ToNot(BeNil()) - routekey_1 = fmt.Sprintf("%d-%s", tableIndex, cidr_1) - routekey_2 = fmt.Sprintf("%d-%s", tableIndex, cidr_2) - routekey_3 = fmt.Sprintf("%d-%s", tableIndex, cidr_3) + routekey_1 = fmt.Sprintf("%d-%s-0", tableIndex, cidr_1) + routekey_2 = fmt.Sprintf("%d-%s-0", tableIndex, cidr_2) + routekey_3 = fmt.Sprintf("%d-%s-0", tableIndex, cidr_3) // Set the interface to be up wgDataplane.SetIface(ifaceName, true, true) @@ -2810,9 +2819,9 @@ func describeEnableTests(enableV4, enableV6 bool) { // We expect the link to exist. linkV6 = wgDataplaneV6.NameToLink[ifaceNameV6] Expect(linkV6).ToNot(BeNil()) - routekeyV6_1 = fmt.Sprintf("%d-%s", tableIndex, cidrV6_1) - routekeyV6_2 = fmt.Sprintf("%d-%s", tableIndex, cidrV6_2) - routekeyV6_3 = fmt.Sprintf("%d-%s", tableIndex, cidrV6_3) + routekeyV6_1 = fmt.Sprintf("%d-%s-1024", tableIndex, cidrV6_1) + routekeyV6_2 = fmt.Sprintf("%d-%s-1024", tableIndex, cidrV6_2) + routekeyV6_3 = fmt.Sprintf("%d-%s-1024", tableIndex, cidrV6_3) // Set the interface to be up wgDataplaneV6.SetIface(ifaceNameV6, true, true) diff --git a/go.mod b/go.mod index 451878e511c..bf81dd10b28 100644 --- a/go.mod +++ b/go.mod @@ -1,47 +1,49 @@ module github.com/projectcalico/calico -go 1.25.1 +go 1.25.7 require ( - cloud.google.com/go/storage v1.50.0 + cloud.google.com/go/storage v1.57.1 github.com/BurntSushi/toml v1.5.0 + github.com/DeRuina/timberjack v1.3.9 github.com/Masterminds/semver/v3 v3.4.0 github.com/Microsoft/hcsshim v0.13.0 github.com/apparentlymart/go-cidr v1.1.0 github.com/approvals/go-approval-tests v1.6.0 - github.com/aws/aws-sdk-go-v2 v1.38.0 - github.com/aws/aws-sdk-go-v2/config v1.31.0 - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.3 - github.com/aws/aws-sdk-go-v2/service/ec2 v1.242.0 - github.com/aws/smithy-go v1.22.5 - github.com/bits-and-blooms/bitset v1.24.0 + github.com/aws/aws-sdk-go-v2 v1.39.5 + github.com/aws/aws-sdk-go-v2/config v1.31.16 + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.12 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.259.0 + github.com/aws/smithy-go v1.23.1 + github.com/bits-and-blooms/bitset v1.24.2 github.com/buger/jsonparser v1.1.1 github.com/container-storage-interface/spec v1.9.0 - github.com/containernetworking/cni v1.2.3 - github.com/containernetworking/plugins v1.6.2 - github.com/coreos/go-semver v0.3.1 + github.com/containernetworking/cni v1.3.0 + github.com/containernetworking/plugins v1.9.0 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/docker/distribution v2.8.3+incompatible - github.com/docker/docker v28.3.3+incompatible + github.com/docker/docker v28.5.1+incompatible github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 - github.com/envoyproxy/go-control-plane/envoy v1.32.4 + github.com/envoyproxy/go-control-plane/envoy v1.35.0 github.com/fsnotify/fsnotify v1.9.0 github.com/gavv/monotime v0.0.0-20190418164738-30dba4353424 github.com/go-ini/ini v1.67.0 github.com/go-logr/logr v1.4.3 - github.com/gofrs/flock v0.12.1 + github.com/gofrs/flock v0.13.0 github.com/gogo/googleapis v1.4.1 github.com/golang/snappy v1.0.0 github.com/google/btree v1.1.3 github.com/google/go-cmp v0.7.0 + github.com/google/go-containerregistry v0.20.3 github.com/google/go-github/v53 v53.2.0 - github.com/google/gopacket v1.1.19 github.com/google/netstack v0.0.0-20191123085552-55fcc16cd0eb github.com/google/safetext v0.0.0-20240722112252-5a72de7e7962 github.com/google/uuid v1.6.0 - github.com/gruntwork-io/terratest v0.50.0 + github.com/gopacket/gopacket v1.4.0 + github.com/gruntwork-io/terratest v0.52.0 github.com/hashicorp/yamux v0.1.2 - github.com/ishidawataru/sctp v0.0.0-20250708014235-1989182a9425 + github.com/ishidawataru/sctp v0.0.0-20250829011129-4b890084db30 + github.com/jinzhu/copier v0.4.0 github.com/joho/godotenv v1.5.1 github.com/json-iterator/go v1.1.12 github.com/juju/clock v1.1.1 @@ -56,88 +58,98 @@ require ( github.com/natefinch/atomic v1.0.1 github.com/nmrshll/go-cp v0.0.0-20180115193924-61436d3b7cfa github.com/olekukonko/tablewriter v0.0.5 - github.com/onsi/ginkgo v1.16.5 - github.com/onsi/ginkgo/v2 v2.23.4 - github.com/onsi/gomega v1.38.0 + github.com/onsi/ginkgo/v2 v2.28.1 + github.com/onsi/gomega v1.39.1 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.9.1 github.com/projectcalico/api v0.0.0-20220722155641-439a754a988b github.com/projectcalico/calico/lib/httpmachinery v0.0.0-00010101000000-000000000000 github.com/projectcalico/calico/lib/std v0.0.0-00010101000000-000000000000 - github.com/projectcalico/go-json v0.0.0-20161128004156-6219dc7339ba - github.com/projectcalico/go-yaml-wrapper v0.0.0-20191112210931-090425220c54 - github.com/prometheus/client_golang v1.23.0 + github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 - github.com/prometheus/common v0.65.0 - github.com/prometheus/procfs v0.17.0 + github.com/prometheus/common v0.67.2 + github.com/prometheus/procfs v0.19.2 github.com/safchain/ethtool v0.6.2 - github.com/shirou/gopsutil/v4 v4.25.7 + github.com/shirou/gopsutil/v4 v4.25.9 github.com/sirupsen/logrus v1.9.3 github.com/slack-go/slack v0.17.3 github.com/snowzach/rotatefilehook v0.0.0-20220211133110-53752135082d - github.com/spf13/cast v1.9.2 - github.com/spf13/cobra v1.9.1 - github.com/spf13/pflag v1.0.7 - github.com/spf13/viper v1.20.1 - github.com/stretchr/testify v1.10.0 + github.com/spf13/cast v1.10.0 + github.com/spf13/cobra v1.10.1 + github.com/spf13/pflag v1.0.10 + github.com/spf13/viper v1.21.0 + github.com/stretchr/testify v1.11.1 github.com/tchap/go-patricia/v2 v2.3.3 github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae github.com/tigera/operator/api v0.0.0-20250807130954-d4353da6554e + github.com/urfave/cli/v3 v3.3.8 github.com/vishvananda/netlink v1.3.1 - go.etcd.io/etcd/api/v3 v3.6.4 - go.etcd.io/etcd/client/pkg/v3 v3.6.4 - go.etcd.io/etcd/client/v2 v2.305.22 - go.etcd.io/etcd/client/v3 v3.6.4 + go.etcd.io/etcd/api/v3 v3.6.5 + go.etcd.io/etcd/client/pkg/v3 v3.6.5 + go.etcd.io/etcd/client/v2 v2.305.24 + go.etcd.io/etcd/client/v3 v3.6.5 go.yaml.in/yaml/v3 v3.0.4 - golang.org/x/crypto v0.41.0 - golang.org/x/mod v0.27.0 - golang.org/x/net v0.43.0 - golang.org/x/oauth2 v0.30.0 - golang.org/x/sync v0.16.0 - golang.org/x/sys v0.35.0 - golang.org/x/text v0.28.0 - golang.org/x/time v0.12.0 + golang.org/x/mod v0.32.0 + golang.org/x/net v0.49.0 + golang.org/x/oauth2 v0.34.0 + golang.org/x/sync v0.19.0 + golang.org/x/sys v0.40.0 + golang.org/x/text v0.33.0 + golang.org/x/time v0.14.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 - google.golang.org/api v0.236.0 - google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a - google.golang.org/grpc v1.72.2 - google.golang.org/protobuf v1.36.7 + google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c + google.golang.org/grpc v1.76.0 + google.golang.org/protobuf v1.36.10 gopkg.in/go-playground/validator.v9 v9.30.2 - // Replaced with older version below until we can handle the updated permissions it now puts on log files. - gopkg.in/natefinch/lumberjack.v2 v2.2.1 - helm.sh/helm/v3 v3.17.4 - k8s.io/api v0.33.5 - k8s.io/apiextensions-apiserver v0.33.5 - k8s.io/apimachinery v0.33.5 - k8s.io/apiserver v0.33.5 - k8s.io/client-go v0.33.5 - k8s.io/component-base v0.33.5 + gopkg.in/yaml.v3 v3.0.1 + helm.sh/helm/v3 v3.19.0 + k8s.io/api v0.34.3 + k8s.io/apiextensions-apiserver v0.34.3 + k8s.io/apimachinery v0.34.3 + k8s.io/apiserver v0.34.3 + k8s.io/client-go v0.34.3 + k8s.io/component-base v0.34.3 k8s.io/klog/v2 v2.130.1 - k8s.io/kube-aggregator v0.33.5 - k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff - k8s.io/kubernetes v1.33.5 - k8s.io/utils v0.0.0-20241210054802-24370beab758 - modernc.org/memory v1.10.0 - sigs.k8s.io/controller-runtime v0.20.4 - sigs.k8s.io/kind v0.29.0 - sigs.k8s.io/knftables v0.0.18 - sigs.k8s.io/network-policy-api v0.1.5 + k8s.io/kube-aggregator v0.34.3 + k8s.io/kube-openapi v0.31.0 + k8s.io/kubernetes v1.34.3 + k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 + modernc.org/memory v1.11.0 + sigs.k8s.io/controller-runtime v0.22.3 + sigs.k8s.io/kind v0.30.0 + sigs.k8s.io/knftables v0.0.19 + sigs.k8s.io/network-policy-api v0.1.8-0.20260212153203-412bf65729a5 sigs.k8s.io/yaml v1.6.0 ) +require ( + github.com/go-kit/log v0.2.1 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/openshift/custom-resource-status v1.1.2 // indirect + kubevirt.io/containerized-data-importer-api v1.63.1 // indirect + kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 // indirect +) + +require ( + golang.org/x/crypto v0.47.0 // indirect + kubevirt.io/api v1.8.0-alpha.0 + kubevirt.io/client-go v1.8.0-alpha.0 +) + require ( al.essio.dev/pkg/shellescape v1.5.1 // indirect - cel.dev/expr v0.20.0 // indirect - cloud.google.com/go v0.120.0 // indirect - cloud.google.com/go/auth v0.16.1 // indirect + cel.dev/expr v0.24.0 // indirect + cloud.google.com/go v0.121.6 // indirect + cloud.google.com/go/auth v0.16.5 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect - cloud.google.com/go/compute/metadata v0.7.0 // indirect + cloud.google.com/go/compute/metadata v0.8.0 // indirect cloud.google.com/go/iam v1.5.2 // indirect cloud.google.com/go/monitoring v1.24.2 // indirect + cyphar.com/go-pathrs v0.2.1 // indirect filippo.io/edwards25519 v1.1.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.50.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/hnslib v0.1.1 // indirect @@ -147,11 +159,11 @@ require ( github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.18.4 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.18.20 // indirect github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.41 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.3 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.3 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.12 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.12 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24 // indirect github.com/aws/aws-sdk-go-v2/service/acm v1.30.6 // indirect github.com/aws/aws-sdk-go-v2/service/autoscaling v1.51.0 // indirect @@ -160,10 +172,10 @@ require ( github.com/aws/aws-sdk-go-v2/service/ecr v1.36.6 // indirect github.com/aws/aws-sdk-go-v2/service/ecs v1.52.0 // indirect github.com/aws/aws-sdk-go-v2/service/iam v1.38.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5 // indirect github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.5 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.12 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5 // indirect github.com/aws/aws-sdk-go-v2/service/kms v1.37.6 // indirect github.com/aws/aws-sdk-go-v2/service/lambda v1.69.0 // indirect @@ -174,16 +186,16 @@ require ( github.com/aws/aws-sdk-go-v2/service/sns v1.33.6 // indirect github.com/aws/aws-sdk-go-v2/service/sqs v1.37.1 // indirect github.com/aws/aws-sdk-go-v2/service/ssm v1.56.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.28.0 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.37.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.0 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.39.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/boombuler/barcode v1.0.1 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudflare/circl v1.6.1 // indirect - github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect + github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect github.com/containerd/cgroups/v3 v3.0.3 // indirect github.com/containerd/containerd/api v1.8.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect @@ -193,24 +205,25 @@ require ( github.com/containerd/ttrpc v1.2.6 // indirect github.com/containerd/typeurl/v2 v2.2.2 // indirect github.com/coreos/go-iptables v0.8.0 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/coreos/go-semver v0.3.1 // indirect + github.com/coreos/go-systemd/v22 v22.6.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect - github.com/cyphar/filepath-securejoin v0.4.1 // indirect + github.com/cyphar/filepath-securejoin v0.6.0 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/cli v27.5.0+incompatible // indirect github.com/docker/docker-credential-helpers v0.8.2 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/ebitengine/purego v0.8.4 // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/ebitengine/purego v0.9.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/euank/go-kmsg-parser v2.0.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.10 // indirect github.com/go-errors/errors v1.4.2 // indirect - github.com/go-jose/go-jose/v4 v4.0.5 // indirect + github.com/go-jose/go-jose/v4 v4.1.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect @@ -219,7 +232,7 @@ require ( github.com/go-playground/form v3.1.4+incompatible // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.27.0 // indirect + github.com/go-playground/validator/v10 v10.28.0 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect @@ -234,18 +247,17 @@ require ( github.com/gonvenience/wrap v1.1.2 // indirect github.com/gonvenience/ytbx v1.4.4 // indirect github.com/google/cadvisor v0.52.1 // indirect - github.com/google/cel-go v0.23.2 // indirect - github.com/google/gnostic-models v0.6.9 // indirect - github.com/google/go-containerregistry v0.20.3 + github.com/google/cel-go v0.26.0 // indirect + github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-querystring v1.1.0 // indirect - github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect + github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect - github.com/googleapis/gax-go/v2 v2.14.2 // indirect + github.com/googleapis/gax-go/v2 v2.15.0 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/gruntwork-io/go-commons v0.8.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -255,7 +267,6 @@ require ( github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgx/v5 v5.7.1 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/jinzhu/copier v0.4.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/josharian/native v1.1.0 // indirect @@ -285,30 +296,29 @@ require ( github.com/moby/sys/mountinfo v0.7.2 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect - github.com/nxadm/tail v1.4.8 // indirect github.com/opencontainers/cgroups v0.0.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opencontainers/runtime-spec v1.2.0 // indirect - github.com/opencontainers/selinux v1.11.1 // indirect + github.com/opencontainers/selinux v1.13.0 // indirect github.com/pborman/uuid v1.2.1 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/pquerna/otp v1.4.0 // indirect github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.80.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.12.0 // indirect + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect + github.com/spf13/afero v1.15.0 // indirect github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/stretchr/objx v0.5.2 // indirect @@ -317,7 +327,6 @@ require ( github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/numcpus v0.10.0 // indirect github.com/urfave/cli v1.22.16 // indirect - github.com/urfave/cli/v3 v3.3.8 github.com/vbatts/tar-split v0.11.6 // indirect github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 // indirect github.com/vishvananda/netns v0.0.5 // indirect @@ -326,53 +335,49 @@ require ( github.com/zeebo/errs v1.4.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful v0.46.1 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/sdk v1.35.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect - go.opentelemetry.io/proto/otlp v1.5.0 // indirect - go.uber.org/automaxprocs v1.6.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/term v0.34.0 // indirect - golang.org/x/tools v0.35.0 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect + golang.org/x/term v0.39.0 // indirect + golang.org/x/tools v0.41.0 // indirect golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect + google.golang.org/api v0.247.0 // indirect google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/cloud-provider v0.33.5 // indirect - k8s.io/component-helpers v0.33.5 // indirect - k8s.io/controller-manager v0.33.5 // indirect - k8s.io/cri-api v0.33.5 // indirect - k8s.io/cri-client v0.33.5 // indirect - k8s.io/csi-translation-lib v0.33.5 // indirect - k8s.io/dynamic-resource-allocation v0.33.5 // indirect - k8s.io/kms v0.33.5 // indirect - k8s.io/kube-scheduler v0.33.5 // indirect - k8s.io/kubectl v0.33.5 // indirect - k8s.io/kubelet v0.33.5 // indirect - k8s.io/mount-utils v0.33.5 // indirect - k8s.io/pod-security-admission v0.33.5 - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect -) - -require ( + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + k8s.io/cloud-provider v0.34.3 // indirect + k8s.io/component-helpers v0.34.3 // indirect + k8s.io/controller-manager v0.34.3 // indirect + k8s.io/cri-api v0.34.3 // indirect + k8s.io/cri-client v0.34.3 // indirect + k8s.io/csi-translation-lib v0.34.3 // indirect + k8s.io/dynamic-resource-allocation v0.34.3 // indirect + k8s.io/kms v0.34.3 // indirect + k8s.io/kube-scheduler v0.34.3 // indirect + k8s.io/kubectl v0.34.3 // indirect + k8s.io/kubelet v0.34.3 // indirect + k8s.io/mount-utils v0.34.3 // indirect + k8s.io/pod-security-admission v0.34.3 + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect ) replace ( @@ -380,36 +385,39 @@ replace ( github.com/projectcalico/calico/lib/httpmachinery => ./lib/httpmachinery github.com/projectcalico/calico/lib/std => ./lib/std - // Newer versions set the file mode on logs to 0600, which breaks a lot of our tests. - gopkg.in/natefinch/lumberjack.v2 => gopkg.in/natefinch/lumberjack.v2 v2.0.0 - // Need replacements for all the k8s subsidiary projects that are pulled in indirectly because // the kubernets repo pulls them in via a replacement to its own vendored copies, which doesn't work for // transient imports. - k8s.io/api => k8s.io/api v0.33.5 - k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.33.5 - k8s.io/apimachinery => k8s.io/apimachinery v0.33.5 - k8s.io/apiserver => k8s.io/apiserver v0.33.5 - k8s.io/cli-runtime => k8s.io/cli-runtime v0.33.5 - k8s.io/client-go => k8s.io/client-go v0.33.5 - k8s.io/cloud-provider => k8s.io/cloud-provider v0.33.5 - k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.33.5 - k8s.io/code-generator => k8s.io/code-generator v0.33.5 - k8s.io/component-base => k8s.io/component-base v0.33.5 - k8s.io/component-helpers => k8s.io/component-helpers v0.33.5 - k8s.io/controller-manager => k8s.io/controller-manager v0.33.5 - k8s.io/cri-api => k8s.io/cri-api v0.33.5 - k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.33.5 - k8s.io/endpointslice => k8s.io/endpointslice v0.33.5 - k8s.io/externaljwt => k8s.io/externaljwt v0.33.5 - k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.33.5 - k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.33.5 - k8s.io/kube-proxy => k8s.io/kube-proxy v0.33.5 - k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.33.5 - k8s.io/kubectl => k8s.io/kubectl v0.33.5 - k8s.io/kubelet => k8s.io/kubelet v0.33.5 - k8s.io/metrics => k8s.io/metrics v0.33.5 - k8s.io/mount-utils => k8s.io/mount-utils v0.33.5 - k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.33.5 - k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.33.5 + k8s.io/api => k8s.io/api v0.34.3 + k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.34.3 + k8s.io/apimachinery => k8s.io/apimachinery v0.34.3 + k8s.io/apiserver => k8s.io/apiserver v0.34.3 + k8s.io/cli-runtime => k8s.io/cli-runtime v0.34.3 + k8s.io/client-go => k8s.io/client-go v0.34.3 + k8s.io/cloud-provider => k8s.io/cloud-provider v0.34.3 + k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.34.3 + k8s.io/code-generator => k8s.io/code-generator v0.34.3 + k8s.io/component-base => k8s.io/component-base v0.34.3 + k8s.io/component-helpers => k8s.io/component-helpers v0.34.3 + k8s.io/controller-manager => k8s.io/controller-manager v0.34.3 + k8s.io/cri-api => k8s.io/cri-api v0.34.3 + k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.34.3 + k8s.io/endpointslice => k8s.io/endpointslice v0.34.3 + k8s.io/externaljwt => k8s.io/externaljwt v0.34.3 + k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.34.3 + k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.34.3 + + // kubevirt.io/client-go requires a tagged kube-openapi version that doesn't + // exist; pin to the pseudo-version used by the rest of our k8s dependencies. + k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b + k8s.io/kube-proxy => k8s.io/kube-proxy v0.34.3 + k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.34.3 + k8s.io/kubectl => k8s.io/kubectl v0.34.3 + k8s.io/kubelet => k8s.io/kubelet v0.34.3 + k8s.io/metrics => k8s.io/metrics v0.34.3 + k8s.io/mount-utils => k8s.io/mount-utils v0.34.3 + k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.34.3 + k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.34.3 + + kubevirt.io/client-go => github.com/tigera/kubevirt-client-go v1.7.0-tigera1 ) diff --git a/go.sum b/go.sum index b89aa24b345..d84fab98795 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,16 @@ al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho= al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890= -cel.dev/expr v0.20.0 h1:OunBvVCfvpWlt4dN7zg3FM6TDkzOePe1+foGJ9AXeeI= -cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA= -cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q= -cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU= -cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI= +cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c= +cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI= +cloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI= +cloud.google.com/go/auth v0.16.5/go.mod h1:utzRfHMP+Vv0mpOkTRQoWD2q3BatTOoWbA7gCc2dUhQ= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= -cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= -cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= +cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA= +cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw= cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8= cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE= cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= @@ -19,28 +19,32 @@ cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFs cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY= cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM= cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U= -cloud.google.com/go/storage v1.50.0 h1:3TbVkzTooBvnZsk7WaAQfOsNrdoM8QHusXA1cpk6QJs= -cloud.google.com/go/storage v1.50.0/go.mod h1:l7XeiD//vx5lfqE3RavfmU9yvk5Pp0Zhcv482poyafY= +cloud.google.com/go/storage v1.57.1 h1:gzao6odNJ7dR3XXYvAgPK+Iw4fVPPznEPPyNjbaVkq8= +cloud.google.com/go/storage v1.57.1/go.mod h1:329cwlpzALLgJuu8beyJ/uvQznDHpa2U5lGjWednkzg= cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4= cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= +cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8= +cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 h1:f2Qw/Ehhimh5uO1fayV0QIW7DShEQqhtUfhYc+cBPlw= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0/go.mod h1:2bIszWvQRlJVmJLiuLhukLImRjKPcYdzzsx6darK02A= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0 h1:5IT7xOdq17MtcdtL/vtl6mGfzhaq4m4vpollPRmlsBQ= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0/go.mod h1:ZV4VOm0/eHR06JLrXWe09068dHpr3TRpY9Uo7T+anuA= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.50.0 h1:nNMpRpnkWDAaqcpxMJvxa/Ud98gjbYwayJY4/9bdjiU= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.50.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.50.0 h1:ig/FpDD2JofP/NExKQUbn7uOSZzJAQqogfqluZK4ed4= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.50.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0= +github.com/DeRuina/timberjack v1.3.9 h1:6UXZ1I7ExPGTX/1UNYawR58LlOJUHKBPiYC7WQ91eBo= +github.com/DeRuina/timberjack v1.3.9/go.mod h1:RLoeQrwrCGIEF8gO5nV5b/gMD0QIy7bzQhBUgpp1EqE= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0 h1:4LP6hvB4I5ouTbGgWtixJhgED6xdf67twf9PoY96Tbg= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0/go.mod h1:jUZ5LYlw40WMd07qxcQJD5M40aUxrfwqQX1g7zxYnrQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo= github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab h1:UKkYhof1njT1/xq4SEg5z+VpTgjmNeHwPGRQl7takDI= github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab/go.mod h1:3VYc5hodBMJ5+l/7J4xAyMeuM2PNuepvHlGs8yilUCA= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= @@ -57,6 +61,8 @@ github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cq github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/alexflint/go-filemutex v1.3.0 h1:LgE+nTUWnQCyRKbpoceKZsPQbs84LivvgwUymZXdOcM= github.com/alexflint/go-filemutex v1.3.0/go.mod h1:U0+VA/i30mGBlLCrFPGtTe9y6wGQfNAWPBTekHQ+c8A= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= @@ -69,24 +75,24 @@ github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 h1:7Ip0wMmLHLRJdrloD github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/aws/aws-sdk-go-v2 v1.38.0 h1:UCRQ5mlqcFk9HJDIqENSLR3wiG1VTWlyUfLDEvY7RxU= -github.com/aws/aws-sdk-go-v2 v1.38.0/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg= +github.com/aws/aws-sdk-go-v2 v1.39.5 h1:e/SXuia3rkFtapghJROrydtQpfQaaUgd1cUvyO1mp2w= +github.com/aws/aws-sdk-go-v2 v1.39.5/go.mod h1:yWSxrnioGUZ4WVv9TgMrNUeLV3PFESn/v+6T/Su8gnM= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc= -github.com/aws/aws-sdk-go-v2/config v1.31.0 h1:9yH0xiY5fUnVNLRWO0AtayqwU1ndriZdN78LlhruJR4= -github.com/aws/aws-sdk-go-v2/config v1.31.0/go.mod h1:VeV3K72nXnhbe4EuxxhzsDc/ByrCSlZwUnWH52Nde/I= -github.com/aws/aws-sdk-go-v2/credentials v1.18.4 h1:IPd0Algf1b+Qy9BcDp0sCUcIWdCQPSzDoMK3a8pcbUM= -github.com/aws/aws-sdk-go-v2/credentials v1.18.4/go.mod h1:nwg78FjH2qvsRM1EVZlX9WuGUJOL5od+0qvm0adEzHk= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.3 h1:GicIdnekoJsjq9wqnvyi2elW6CGMSYKhdozE7/Svh78= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.3/go.mod h1:R7BIi6WNC5mc1kfRM7XM/VHC3uRWkjc396sfabq4iOo= +github.com/aws/aws-sdk-go-v2/config v1.31.16 h1:E4Tz+tJiPc7kGnXwIfCyUj6xHJNpENlY11oKpRTgsjc= +github.com/aws/aws-sdk-go-v2/config v1.31.16/go.mod h1:2S9hBElpCyGMifv14WxQ7EfPumgoeCPZUpuPX8VtW34= +github.com/aws/aws-sdk-go-v2/credentials v1.18.20 h1:KFndAnHd9NUuzikHjQ8D5CfFVO+bgELkmcGY8yAw98Q= +github.com/aws/aws-sdk-go-v2/credentials v1.18.20/go.mod h1:9mCi28a+fmBHSQ0UM79omkz6JtN+PEsvLrnG36uoUv0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.12 h1:VO3FIM2TDbm0kqp6sFNR0PbioXJb/HzCDW6NtIZpIWE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.12/go.mod h1:6C39gB8kg82tx3r72muZSrNhHia9rjGkX7ORaS2GKNE= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.41 h1:hqcxMc2g/MwwnRMod9n6Bd+t+9Nf7d5qRg7RaXKPd6o= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.41/go.mod h1:d1eH0VrttvPmrCraU68LOyNdu26zFxQFjrVSb5vdhog= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.3 h1:o9RnO+YZ4X+kt5Z7Nvcishlz0nksIt2PIzDglLMP0vA= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.3/go.mod h1:+6aLJzOG1fvMOyzIySYjOFjcguGvVRL68R+uoRencN4= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.3 h1:joyyUFhiTQQmVK6ImzNU9TQSNRNeD9kOklqTzyk5v6s= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.3/go.mod h1:+vNIyZQP3b3B1tSLI0lxvrU9cfM7gpdRXMFfm67ZcPc= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.12 h1:p/9flfXdoAnwJnuW9xHEAFY22R3A6skYkW19JFF9F+8= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.12/go.mod h1:ZTLHakoVCTtW8AaLGSwJ3LXqHD9uQKnOcv1TrpO6u2k= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.12 h1:2lTWFvRcnWFFLzHWmtddu5MTchc5Oj2OOey++99tPZ0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.12/go.mod h1:hI92pK+ho8HVcWMHKHrK3Uml4pfG7wvL86FzO0LVtQQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24 h1:JX70yGKLj25+lMC5Yyh8wBtvB01GDilyRuJvXJ4piD0= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24/go.mod h1:+Ln60j9SUTD0LEwnhEB0Xhg61DHqplBrbZpLgyjoEHg= github.com/aws/aws-sdk-go-v2/service/acm v1.30.6 h1:fDg0RlN30Xf/yYzEUL/WXqhmgFsjVb/I3230oCfyI5w= @@ -97,22 +103,22 @@ github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.44.0 h1:OREVd94+oXW5a+3SS github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.44.0/go.mod h1:Qbr4yfpNqVNl69l/GEDK+8wxLf/vHi0ChoiSDzD7thU= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.37.1 h1:vucMirlM6D+RDU8ncKaSZ/5dGrXNajozVwpmWNPn2gQ= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.37.1/go.mod h1:fceORfs010mNxZbQhfqUjUeHlTwANmIT4mvHamuUaUg= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.242.0 h1:xgbWik/QFlVCvoUbumUPPZI7+0RXiScb8eHSV06CELU= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.242.0/go.mod h1:EeWmteKqZjaMj45MUmPET1SisFI+HkqWIRQoyjMivcc= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.259.0 h1:0BwB+z9JX7fleVvaZaUuzIHvGWiWn2BQLJIW2riEzDQ= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.259.0/go.mod h1:DT0XByGaNaOff3CtLVmj3jKcMeVDfOj5DkLD39UPJY0= github.com/aws/aws-sdk-go-v2/service/ecr v1.36.6 h1:zg+3FGHA0PBs0KM25qE/rOf2o5zsjNa1g/Qq83+SDI0= github.com/aws/aws-sdk-go-v2/service/ecr v1.36.6/go.mod h1:ZSq54Z9SIsOTf1Efwgw1msilSs4XVEfVQiP9nYVnKpM= github.com/aws/aws-sdk-go-v2/service/ecs v1.52.0 h1:7/vgFWplkusJN/m+3QOa+W9FNRqa8ujMPNmdufRaJpg= github.com/aws/aws-sdk-go-v2/service/ecs v1.52.0/go.mod h1:dPTOvmjJQ1T7Q+2+Xs2KSPrMvx+p0rpyV+HsQVnUK4o= github.com/aws/aws-sdk-go-v2/service/iam v1.38.1 h1:hfkzDZHBp9jAT4zcd5mtqckpU4E3Ax0LQaEWWk1VgN8= github.com/aws/aws-sdk-go-v2/service/iam v1.38.1/go.mod h1:u36ahDtZcQHGmVm/r+0L1sfKX4fzLEMdCqiKRKkUMVM= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 h1:xtuxji5CS0JknaXoACOunXOYOQzgfTvGAc9s2QdCJA4= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2/go.mod h1:zxwi0DIR0rcRcgdbl7E2MSOvxDyyXGBlScvBkARFaLQ= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5 h1:gvZOjQKPxFXy1ft3QnEyXmT+IqneM9QAUWlM3r0mfqw= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5/go.mod h1:DLWnfvIcm9IET/mmjdxeXbBKmTCm0ZB8p1za9BVteM8= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.5 h1:3Y457U2eGukmjYjeHG6kanZpDzJADa2m0ADqnuePYVQ= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.5/go.mod h1:CfwEHGkTjYZpkQ/5PvcbEtT7AJlG68KkEvmtwU8z3/U= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.3 h1:ieRzyHXypu5ByllM7Sp4hC5f/1Fy5wqxqY0yB85hC7s= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.3/go.mod h1:O5ROz8jHiOAKAwx179v+7sHMhfobFVi6nZt8DEyiYoM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.12 h1:MM8imH7NZ0ovIVX7D2RxfMDv7Jt9OiUXkcQ+GqywA7M= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.12/go.mod h1:gf4OGwdNkbEsb7elw2Sy76odfhwNktWII3WgvQgQQ6w= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5 h1:P1doBzv5VEg1ONxnJss1Kh5ZG/ewoIE4MQtKKc6Crgg= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5/go.mod h1:NOP+euMW7W3Ukt28tAxPuoWao4rhhqJD3QEBk7oCg7w= github.com/aws/aws-sdk-go-v2/service/kms v1.37.6 h1:CZImQdb1QbU9sGgJ9IswhVkxAcjkkD1eQTMA1KHWk+E= @@ -133,19 +139,19 @@ github.com/aws/aws-sdk-go-v2/service/sqs v1.37.1 h1:39WvSrVq9DD6UHkD+fx5x19P5KpR github.com/aws/aws-sdk-go-v2/service/sqs v1.37.1/go.mod h1:3gwPzC9LER/BTQdQZ3r6dUktb1rSjABF1D3Sr6nS7VU= github.com/aws/aws-sdk-go-v2/service/ssm v1.56.0 h1:mADKqoZaodipGgiZfuAjtlcr4IVBtXPZKVjkzUZCCYM= github.com/aws/aws-sdk-go-v2/service/ssm v1.56.0/go.mod h1:l9qF25TzH95FhcIak6e4vt79KE4I7M2Nf59eMUVjj6c= -github.com/aws/aws-sdk-go-v2/service/sso v1.28.0 h1:Mc/MKBf2m4VynyJkABoVEN+QzkfLqGj0aiJuEe7cMeM= -github.com/aws/aws-sdk-go-v2/service/sso v1.28.0/go.mod h1:iS5OmxEcN4QIPXARGhavH7S8kETNL11kym6jhoS7IUQ= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.0 h1:6csaS/aJmqZQbKhi1EyEMM7yBW653Wy/B9hnBofW+sw= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.0/go.mod h1:59qHWaY5B+Rs7HGTuVGaC32m0rdpQ68N8QCN3khYiqs= -github.com/aws/aws-sdk-go-v2/service/sts v1.37.0 h1:MG9VFW43M4A8BYeAfaJJZWrroinxeTi2r3+SnmLQfSA= -github.com/aws/aws-sdk-go-v2/service/sts v1.37.0/go.mod h1:JdeBDPgpJfuS6rU/hNglmOigKhyEZtBmbraLE4GK1J8= -github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw= -github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.0 h1:xHXvxst78wBpJFgDW07xllOx0IAzbryrSdM4nMVQ4Dw= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.0/go.mod h1:/e8m+AO6HNPPqMyfKRtzZ9+mBF5/x1Wk8QiDva4m07I= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.4 h1:tBw2Qhf0kj4ZwtsVpDiVRU3zKLvjvjgIjHMKirxXg8M= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.4/go.mod h1:Deq4B7sRM6Awq/xyOBlxBdgW8/Z926KYNNaGMW2lrkA= +github.com/aws/aws-sdk-go-v2/service/sts v1.39.0 h1:C+BRMnasSYFcgDw8o9H5hzehKzXyAb9GY5v/8bP9DUY= +github.com/aws/aws-sdk-go-v2/service/sts v1.39.0/go.mod h1:4EjU+4mIx6+JqKQkruye+CaigV7alL3thVPfDd9VlMs= +github.com/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M= +github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bits-and-blooms/bitset v1.24.0 h1:H4x4TuulnokZKvHLfzVRTHJfFfnHEeSYJizujEZvmAM= -github.com/bits-and-blooms/bitset v1.24.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bits-and-blooms/bitset v1.24.2 h1:M7/NzVbsytmtfHbumG+K2bremQPMJuqv1JD3vOaFxp0= +github.com/bits-and-blooms/bitset v1.24.2/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= 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/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= @@ -156,18 +162,29 @@ github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx2 github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= +github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= +github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= +github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs= +github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk= -github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/container-storage-interface/spec v1.9.0 h1:zKtX4STsq31Knz3gciCYCi1SXtO2HJDecIjDVboYavY= github.com/container-storage-interface/spec v1.9.0/go.mod h1:ZfDu+3ZRyeVqxZM0Ds19MVLkN2d1XJ5MAfi1L3VjlT0= github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= @@ -186,23 +203,23 @@ github.com/containerd/ttrpc v1.2.6 h1:zG+Kn5EZ6MUYCS1t2Hmt2J4tMVaLSFEJVOraDQwNPC github.com/containerd/ttrpc v1.2.6/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= github.com/containerd/typeurl/v2 v2.2.2 h1:3jN/k2ysKuPCsln5Qv8bzR9cxal8XjkxPogJfSNO31k= github.com/containerd/typeurl/v2 v2.2.2/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= -github.com/containernetworking/cni v1.2.3 h1:hhOcjNVUQTnzdRJ6alC5XF+wd9mfGIUaj8FuJbEslXM= -github.com/containernetworking/cni v1.2.3/go.mod h1:DuLgF+aPd3DzcTQTtp/Nvl1Kim23oFKdm2okJzBQA5M= -github.com/containernetworking/plugins v1.6.2 h1:pqP8Mq923TLyef5g97XfJ/xpDeVek4yF8A4mzy9Tc4U= -github.com/containernetworking/plugins v1.6.2/go.mod h1:SP5UG3jDO9LtmfbBJdP+nl3A1atOtbj2MBOYsnaxy64= +github.com/containernetworking/cni v1.3.0 h1:v6EpN8RznAZj9765HhXQrtXgX+ECGebEYEmnuFjskwo= +github.com/containernetworking/cni v1.3.0/go.mod h1:Bs8glZjjFfGPHMw6hQu82RUgEPNGEaBb9KS5KtNMnJ4= +github.com/containernetworking/plugins v1.9.0 h1:Mg3SXBdRGkdXyFC4lcwr6u2ZB2SDeL6LC3U+QrEANuQ= +github.com/containernetworking/plugins v1.9.0/go.mod h1:JG3BxoJifxxHBhG3hFyxyhid7JgRVBu/wtooGEvWf1c= github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc= github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= +github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= -github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is= +github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -213,8 +230,8 @@ github.com/docker/cli v27.5.0+incompatible h1:aMphQkcGtpHixwwhAXJT1rrK/detk2JIvD github.com/docker/cli v27.5.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= -github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM= +github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= @@ -225,17 +242,19 @@ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= -github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k= +github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/emicklei/go-restful v2.15.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= +github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= -github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= -github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= +github.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo= +github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= @@ -252,26 +271,44 @@ github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwo github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= -github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0= +github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gavv/monotime v0.0.0-20190418164738-30dba4353424 h1:Vh7rylVZRZCj6W41lRlP17xPk4Nq260H4Xo/DDYmEZk= github.com/gavv/monotime v0.0.0-20190418164738-30dba4353424/go.mod h1:vmp8DIyckQMXOPl0AQVHt+7n5h7Gb7hS6CUydiV8QeA= +github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= +github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= +github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= +github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= +github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE= +github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= -github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= +github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI= +github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -280,11 +317,16 @@ github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= @@ -296,28 +338,33 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= -github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688= +github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= -github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= +github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= +github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= -github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -334,6 +381,9 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= @@ -354,10 +404,10 @@ github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/cadvisor v0.52.1 h1:sC8SZ6jio9ds+P2dk51bgbeYeufxo55n0X3tmrpA9as= github.com/google/cadvisor v0.52.1/go.mod h1:OAhPcx1nOm5YwMh/JhpUOMKyv1YKLRtS9KgzWPndHmA= -github.com/google/cel-go v0.23.2 h1:UdEe3CvQh3Nv+E/j9r1Y//WO0K0cSyD7/y0bzyLIMI4= -github.com/google/cel-go v0.23.2/go.mod h1:52Pb6QsDbC5kvgxvZhiL9QX1oZEkcUF/ZqaPx1J5Wwo= -github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= -github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= +github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI= +github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -365,8 +415,11 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI= @@ -376,14 +429,18 @@ github.com/google/go-github/v53 v53.2.0/go.mod h1:XhFRObz+m/l+UCm9b7KSIC3lT3NWSX github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= -github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/netstack v0.0.0-20191123085552-55fcc16cd0eb h1:/YcrD0GSdU5gtckXHVjSEd0Y6VgboNW7VYyImZS3y6g= github.com/google/netstack v0.0.0-20191123085552-55fcc16cd0eb/go.mod h1:r/rILWg3r1Qy9G1IFMhsqWLq2GjwuYoTuPgG7ckMAjk= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/safetext v0.0.0-20240722112252-5a72de7e7962 h1:+9C/TgFfcCmZBV7Fjb3kQCGlkpFrhtvFDgbdQHB9RaA= @@ -396,26 +453,28 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= -github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0= -github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w= +github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= +github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= +github.com/gopacket/gopacket v1.4.0 h1:cr1OlFpzksCkZHNO0eLjaSSOrMQnpPXg0j6qHIY3y2U= +github.com/gopacket/gopacket v1.4.0/go.mod h1:EpvsxINeehp5qj4YMKMLf2/dekdhKn2IIAO/ZOifS7o= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 h1:FbSCl+KggFl+Ocym490i/EyXF4lPgLoUtcSWquBM0Rs= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/gruntwork-io/go-commons v0.8.0 h1:k/yypwrPqSeYHevLlEDmvmgQzcyTwrlZGRaxEM6G0ro= github.com/gruntwork-io/go-commons v0.8.0/go.mod h1:gtp0yTtIBExIZp7vyIV9I0XQkVwiQZze678hvDXof78= -github.com/gruntwork-io/terratest v0.50.0 h1:AbBJ7IRCpLZ9H4HBrjeoWESITv8nLjN6/f1riMNcAsw= -github.com/gruntwork-io/terratest v0.50.0/go.mod h1:see0lbKvAqz6rvzvN2wyfuFQQG4PWcAb2yHulF6B2q4= +github.com/gruntwork-io/terratest v0.52.0 h1:7+I3FqEImowIajZ9Qyo5ngr7n2AUINJko6x+KzlWNjU= +github.com/gruntwork-io/terratest v0.52.0/go.mod h1:y2Evi+Ac04QpzF3mbRPqrBjipDN7gjqlw6+OZoy2vX4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -426,10 +485,12 @@ github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6 github.com/homeport/dyff v1.6.0 h1:AN+ikld0Fy+qx34YE7655b/bpWuxS6cL9k852pE2GUc= github.com/homeport/dyff v1.6.0/go.mod h1:FlAOFYzeKvxmU5nTrnG+qrlJVWpsFew7pt8L99p5q8k= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/ishidawataru/sctp v0.0.0-20250708014235-1989182a9425 h1:37AZdk/mVlt6NiksUPgo7MCHBVTLUWWD8CIbQdeLU5E= -github.com/ishidawataru/sctp v0.0.0-20250708014235-1989182a9425/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= +github.com/ishidawataru/sctp v0.0.0-20250829011129-4b890084db30 h1:SF8DGX8bGAXMAvxtJvFFy2KIAPwxIEDP3XpzZVhz0i4= +github.com/ishidawataru/sctp v0.0.0-20250829011129-4b890084db30/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -446,12 +507,14 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= -github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= +github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= +github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= +github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/juju/clock v1.1.1 h1:NvgHG9DQmOpBevgt6gzkyimdWBooLXDy1cQn89qJzBI= @@ -488,6 +551,7 @@ github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zt github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -497,6 +561,7 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/libopenstorage/openstorage v1.0.0 h1:GLPam7/0mpdP8ZZtKjbfcXJBTIA/T1O6CBErVEFEyIM= @@ -511,8 +576,13 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= +github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 h1:BXxTozrOU8zgC5dkpn3J6NTRdoP+hjok/e+ACr4Hibk= github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3/go.mod h1:x1uk6vxTiVuNt6S5R2UYgdhpj3oKojXvOXauHZ7dEnI= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -534,6 +604,8 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/ github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= +github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= +github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws= github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc= github.com/mipearson/rfw v0.0.0-20170619235010-6f0a6f3266ba h1:IcYRolrKyyT0xX/j6mHzigZnwEN7NCzaaUBL2E75V7A= @@ -560,13 +632,14 @@ github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7z github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= @@ -579,6 +652,7 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A= github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nmrshll/go-cp v0.0.0-20180115193924-61436d3b7cfa h1:/SRdH7jdcIW6WBtZO1BlpI+6TxqpOiMqLZJ10Uhk0k0= github.com/nmrshll/go-cp v0.0.0-20180115193924-61436d3b7cfa/go.mod h1:/Uh/WFiWYXoTKVsM302U10XnogAldY7up/xErXmt1FA= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -588,14 +662,58 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= -github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= +github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= +github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0= +github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= +github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw= +github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= +github.com/onsi/ginkgo/v2 v2.8.1/go.mod h1:N1/NbDngAFcSLdyZ+/aYTYGSlq9qMCS/cNKGJjy+csc= +github.com/onsi/ginkgo/v2 v2.9.0/go.mod h1:4xkjoL/tZv4SMWeww56BU5kAt19mVB47gTWxmrTcxyk= +github.com/onsi/ginkgo/v2 v2.9.1/go.mod h1:FEcmzVcCHl+4o9bQZVab+4dC9+j+91t2FHSzmGAPfuo= +github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts= +github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= +github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= +github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= +github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= +github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI= +github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= +github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.38.0 h1:c/WX+w8SLAinvuKKQFh77WEucCnPk4j2OTUr7lt7BeY= -github.com/onsi/gomega v1.38.0/go.mod h1:OcXcwId0b9QsE7Y49u+BTrL4IdKOBOKnD6VQNTJEB6o= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= +github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= +github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM= +github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= +github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= +github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= +github.com/onsi/gomega v1.27.1/go.mod h1:aHX5xOykVYzWOV4WqQy0sy8BQptgukenXpCXfadcIAw= +github.com/onsi/gomega v1.27.3/go.mod h1:5vG284IBtfDAmDyrK+eGyZmUgUlmi+Wngqo557cZ6Gw= +github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ= +github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= +github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= +github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= +github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= github.com/opencontainers/cgroups v0.0.1 h1:MXjMkkFpKv6kpuirUa4USFBas573sSAY082B4CiHEVA= github.com/opencontainers/cgroups v0.0.1/go.mod h1:s8lktyhlGUqM7OSRL5P7eAW6Wb+kWPNvt4qvVfzA5vs= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -604,18 +722,22 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.11.1 h1:nHFvthhM0qY8/m+vfhJylliSshm8G1jJ2jDMcgULaH8= -github.com/opencontainers/selinux v1.11.1/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= +github.com/opencontainers/selinux v1.13.0 h1:Zza88GWezyT7RLql12URvoxsbLfjFx988+LGaWfbL84= +github.com/opencontainers/selinux v1.13.0/go.mod h1:XxWTed+A/s5NNq4GmYScVy+9jzXhGBVEOAyucdRUY8s= +github.com/openshift/custom-resource-status v1.1.2 h1:C3DL44LEbvlbItfd8mT5jWrqPfHnSOQoQf/sypqA6A4= +github.com/openshift/custom-resource-status v1.1.2/go.mod h1:DB/Mf2oTeiAmVVX1gN+NEqweonAPY0TKUwADizj8+ZA= +github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= @@ -623,45 +745,42 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg= github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= -github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= -github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= -github.com/projectcalico/go-json v0.0.0-20161128004156-6219dc7339ba h1:aaF2byUCZhzszHsfPEr2M3qcU4ibtD/yk/il2R7T1PU= -github.com/projectcalico/go-json v0.0.0-20161128004156-6219dc7339ba/go.mod h1:q8EdCgBdMQzgiX/uk4GXLWLk+gIHd1a7mWUAamJKDb4= -github.com/projectcalico/go-yaml-wrapper v0.0.0-20191112210931-090425220c54 h1:Jt2Pic9dxgJisekm8q2WV9FaWxUJhhRfwHSP640drww= -github.com/projectcalico/go-yaml-wrapper v0.0.0-20191112210931-090425220c54/go.mod h1:UgC0aTQ2KMDxlX3lU/stndk7DMUBJqzN40yFiILHgxc= github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.80.1 h1:DP+PUNVOc+Bkft8a4QunLzaZ0RspWuD3tBbcPHr2PeE= github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.80.1/go.mod h1:6x4x0t9BP35g4XcjkHE9EB3RxhyfxpdpmZKd/Qyk8+M= -github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= -github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= -github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= -github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= -github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8= +github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/safchain/ethtool v0.6.2 h1:O3ZPFAKEUEfbtE6J/feEe2Ft7dIJ2Sy8t4SdMRiIMHY= github.com/safchain/ethtool v0.6.2/go.mod h1:VS7cn+bP3Px3rIq55xImBiZGHVLNyBh5dqG6dDQy8+I= -github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= -github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= +github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= -github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM= -github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U= +github.com/shirou/gopsutil/v4 v4.25.9 h1:JImNpf6gCVhKgZhtaAHJ0serfFGtlfIlSC08eaKdTrU= +github.com/shirou/gopsutil/v4 v4.25.9/go.mod h1:gxIxoC+7nQRwUl/xNhutXlD8lq+jxTgpIkEf3rADHL8= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -672,19 +791,21 @@ github.com/snowzach/rotatefilehook v0.0.0-20220211133110-53752135082d h1:4660u5v github.com/snowzach/rotatefilehook v0.0.0-20220211133110-53752135082d/go.mod h1:ZLVe3VfhAuMYLYWliGEydMBoRnfib8EFSqkBYu1ck9E= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= -github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= -github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= -github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= -github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= -github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= -github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= -github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= @@ -699,14 +820,16 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tchap/go-patricia/v2 v2.3.3 h1:xfNEsODumaEcCcY3gI0hYPZ/PcpVv5ju6RMAhgwZDDc= @@ -715,6 +838,16 @@ github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae h1:vgGSvdW5Lqg+I1 github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae/go.mod h1:quDq6Se6jlGwiIKia/itDZxqC5rj6/8OdFyMMAwTxCs= github.com/texttheater/golang-levenshtein v1.0.1 h1:+cRNoVrfiwufQPhoMzB6N0Yf/Mqajr6t1lOv8GyGE2U= github.com/texttheater/golang-levenshtein v1.0.1/go.mod h1:PYAKrbF5sAiq9wd+H82hs7gNaen0CplQ9uvm6+enD/8= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/tigera/kubevirt-client-go v1.7.0-tigera1 h1:1d24IpCpdG5pVkh/A8I3dDIfFzPwLIXRBPJ1NsaoawQ= +github.com/tigera/kubevirt-client-go v1.7.0-tigera1/go.mod h1:LL+q8y/jbNYdNho6chmHIzUVww+ILalYYnrXVSaVvhA= github.com/tigera/operator/api v0.0.0-20250807130954-d4353da6554e h1:Ai1gPM5vHN7rxMqIuLI3r1N6Z3N34VWegStGXjy7J18= github.com/tigera/operator/api v0.0.0-20250807130954-d4353da6554e/go.mod h1:VHTGNLahatombm93XGzp3ikt3dCaZVqWEEt6o38ZLZA= github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= @@ -744,95 +877,125 @@ github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= -go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= -go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo= -go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= -go.etcd.io/etcd/client/pkg/v3 v3.6.4 h1:9HBYrjppeOfFjBjaMTRxT3R7xT0GLK8EJMVC4xg6ok0= -go.etcd.io/etcd/client/pkg/v3 v3.6.4/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= -go.etcd.io/etcd/client/v2 v2.305.22 h1:FedDsGxor5iE9muhXm1CgE/TiSVOtgyB5+NYCHPzA2Q= -go.etcd.io/etcd/client/v2 v2.305.22/go.mod h1:VP7+1hEKyfGPuRdDmtT8GjM2HcVCKVlGmxfv3NwmrII= -go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A= -go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo= -go.etcd.io/etcd/pkg/v3 v3.5.21 h1:jUItxeKyrDuVuWhdh0HtjUANwyuzcb7/FAeUfABmQsk= -go.etcd.io/etcd/pkg/v3 v3.5.21/go.mod h1:wpZx8Egv1g4y+N7JAsqi2zoUiBIUWznLjqJbylDjWgU= -go.etcd.io/etcd/raft/v3 v3.5.21 h1:dOmE0mT55dIUsX77TKBLq+RgyumsQuYeiRQnW/ylugk= -go.etcd.io/etcd/raft/v3 v3.5.21/go.mod h1:fmcuY5R2SNkklU4+fKVBQi2biVp5vafMrWUEj4TJ4Cs= -go.etcd.io/etcd/server/v3 v3.5.21 h1:9w0/k12majtgarGmlMVuhwXRI2ob3/d1Ik3X5TKo0yU= -go.etcd.io/etcd/server/v3 v3.5.21/go.mod h1:G1mOzdwuzKT1VRL7SqRchli/qcFrtLBTAQ4lV20sXXo= +go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= +go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= +go.etcd.io/etcd/api/v3 v3.6.5 h1:pMMc42276sgR1j1raO/Qv3QI9Af/AuyQUW6CBAWuntA= +go.etcd.io/etcd/api/v3 v3.6.5/go.mod h1:ob0/oWA/UQQlT1BmaEkWQzI0sJ1M0Et0mMpaABxguOQ= +go.etcd.io/etcd/client/pkg/v3 v3.6.5 h1:Duz9fAzIZFhYWgRjp/FgNq2gO1jId9Yae/rLn3RrBP8= +go.etcd.io/etcd/client/pkg/v3 v3.6.5/go.mod h1:8Wx3eGRPiy0qOFMZT/hfvdos+DjEaPxdIDiCDUv/FQk= +go.etcd.io/etcd/client/v2 v2.305.24 h1:h70g+O0cUBNhiXMJw71topidfHlRUdP9XmgZhOEL0cg= +go.etcd.io/etcd/client/v2 v2.305.24/go.mod h1:EYxkfyrwxUZGPLO+gbFKaM/dtGysz5u/TOe6d9HLoRc= +go.etcd.io/etcd/client/v3 v3.6.5 h1:yRwZNFBx/35VKHTcLDeO7XVLbCBFbPi+XV4OC3QJf2U= +go.etcd.io/etcd/client/v3 v3.6.5/go.mod h1:ZqwG/7TAFZ0BJ0jXRPoJjKQJtbFo/9NIY8uoFFKcCyo= +go.etcd.io/etcd/pkg/v3 v3.6.4 h1:fy8bmXIec1Q35/jRZ0KOes8vuFxbvdN0aAFqmEfJZWA= +go.etcd.io/etcd/pkg/v3 v3.6.4/go.mod h1:kKcYWP8gHuBRcteyv6MXWSN0+bVMnfgqiHueIZnKMtE= +go.etcd.io/etcd/server/v3 v3.6.4 h1:LsCA7CzjVt+8WGrdsnh6RhC0XqCsLkBly3ve5rTxMAU= +go.etcd.io/etcd/server/v3 v3.6.4/go.mod h1:aYCL/h43yiONOv0QIR82kH/2xZ7m+IWYjzRmyQfnCAg= +go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= +go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao= -go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful v0.46.1 h1:6l13V1yADBiPaUz+tExvNHHO2jF9RbxZsmea3POmlmc= go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful v0.46.1/go.mod h1:lqAQ/iU6CDKDWb73W3wNIit+0uL8EIqli8CdBdnThVo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/contrib/propagators/b3 v1.21.1 h1:WPYiUgmw3+b7b3sQ1bFBFAf0q+Di9dvNc3AtYfnT4RQ= go.opentelemetry.io/contrib/propagators/b3 v1.21.1/go.mod h1:EmzokPoSqsYMBVK4nRnhsfm5mbn8J1eDuz/U1UaQaWg= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= -go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= -go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= -go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= -go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= +go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0= +golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -845,24 +1008,58 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -874,54 +1071,134 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= -golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= -golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= +golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -930,8 +1207,10 @@ golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uI golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdIg1ozBNLgPy4SLT84nfcBjr6rhGtXYtrkWLU= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ= -google.golang.org/api v0.236.0 h1:CAiEiDVtO4D/Qja2IA9VzlFrgPnK3XVMmRoJZlSWbc0= -google.golang.org/api v0.236.0/go.mod h1:X1WF9CU2oTc+Jml1tiIxGmWFK/UZezdqEu09gcxZAj4= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/api v0.247.0 h1:tSd/e0QrUlLsrwMKmkbQhYVa109qIintOls2Wh6bngc= +google.golang.org/api v0.247.0/go.mod h1:r1qZOPmxXffXg6xS5uhx16Fa/UFY8QU/K4bfKrnvovM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -939,17 +1218,17 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= -google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 h1:mVXdvnmR3S3BQOqHECm9NGMjYiRtEvDYcqAqedTXY6s= -google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074/go.mod h1:vYFwMYFbmA8vl6Z/krj/h7+U/AqpHknwJX4Uqgfyc7I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a h1:tPE/Kp+x9dMSwUm/uM0JKK0IfdiJkwAbSMSeZBXXJXc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= -google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -959,10 +1238,19 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= -google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= @@ -976,8 +1264,8 @@ gopkg.in/go-playground/validator.v9 v9.30.2 h1:icxYLlYflpazIV3ufMoNB9h9SYMQ37DZ8 gopkg.in/go-playground/validator.v9 v9.30.2/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -987,93 +1275,111 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -helm.sh/helm/v3 v3.17.4 h1:GK+vgn9gKCyoH44+f3B5zpA78iH3AK4ywIInDEmmn/g= -helm.sh/helm/v3 v3.17.4/go.mod h1:+uJKMH/UiMzZQOALR3XUf3BLIoczI2RKKD6bMhPh4G8= +helm.sh/helm/v3 v3.19.0 h1:krVyCGa8fa/wzTZgqw0DUiXuRT5BPdeqE/sQXujQ22k= +helm.sh/helm/v3 v3.19.0/go.mod h1:Lk/SfzN0w3a3C3o+TdAKrLwJ0wcZ//t1/SDXAvfgDdc= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.33.5 h1:YR+uhYj05jdRpcksv8kjSliW+v9hwXxn6Cv10aR8Juw= -k8s.io/api v0.33.5/go.mod h1:2gzShdwXKT5yPGiqrTrn/U/nLZ7ZyT4WuAj3XGDVgVs= -k8s.io/apiextensions-apiserver v0.33.5 h1:93NZh6rmrcamX/tfv/dZrTsMiQX69ufANmDcKPEgSeA= -k8s.io/apiextensions-apiserver v0.33.5/go.mod h1:JIbyQnNlu6nQa7b1vgFi51pmlXOk8mdn0WJwUJnz/7U= -k8s.io/apimachinery v0.33.5 h1:NiT64hln4TQXeYR18/ES39OrNsjGz8NguxsBgp+6QIo= -k8s.io/apimachinery v0.33.5/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/apiserver v0.33.5 h1:X1Gy33r4YkRLRqTjGjofk7X1/EjSLEVSJ/A+1qjoj60= -k8s.io/apiserver v0.33.5/go.mod h1:Q+b5Btbc8x0PqOCeh/xBTesKk+cXQRN+PF2wdrTKDeg= -k8s.io/cli-runtime v0.33.5 h1:wM7DoglOkrJDmddla864mVpueaEDX7/XGAkHGMWQkpc= -k8s.io/cli-runtime v0.33.5/go.mod h1:ZmUR+ybq97SqxSkkqGQdIhzCfk/+ETUhwKQq5EguaCw= -k8s.io/client-go v0.33.5 h1:I8BdmQGxInpkMEnJvV6iG7dqzP3JRlpZZlib3OMFc3o= -k8s.io/client-go v0.33.5/go.mod h1:W8PQP4MxbM4ypgagVE65mUUqK1/ByQkSALF9tzuQ6u0= -k8s.io/cloud-provider v0.33.5 h1:LyU8ZVID3Q8S/aEsPqv5bHCfPeo3NXKrD5iSBqHeV/U= -k8s.io/cloud-provider v0.33.5/go.mod h1:kSY4+DXKXhWmHLyuUDY09zTL9g9MbcOpahoVY6mzW8Q= -k8s.io/cluster-bootstrap v0.33.5 h1:VO0BhAwtAa6PYbBTkpK6mZwkGs8dlbeAN8pXbEovWWw= -k8s.io/cluster-bootstrap v0.33.5/go.mod h1:jPhTDgjG8RfxGkNJQzt3Qffq/KcYGRJI+UbKeJvEFyw= -k8s.io/component-base v0.33.5 h1:4D3kxjEx1pJRy3WHAZsmX3+LCpmd4ftE+2J4v6naTnQ= -k8s.io/component-base v0.33.5/go.mod h1:Zma1YjBVuuGxIbspj1vGR3/5blzo2ARf1v0QTtog1to= -k8s.io/component-helpers v0.33.5 h1:1LDSMzn7YTreVLPaOBJK36ase/FWi2sDpeJJvbEBO2s= -k8s.io/component-helpers v0.33.5/go.mod h1:C3HsDU2lANSLgTTgMJ0TFnG5xZrVrxR3Ss9n7Wrsw4s= -k8s.io/controller-manager v0.33.5 h1:abmssknXnhOhW533583v2SYQObD5RhYiSL7Za1rezGM= -k8s.io/controller-manager v0.33.5/go.mod h1:KuQeAlf4vI2+qj5fwPVLaDlbtrTBA/8L/LqQvI74Ow0= -k8s.io/cri-api v0.33.5 h1:UBYYKZwaFTaAJj1gF6vYy/iSkWk5pIVaoTjjF6ltUCs= -k8s.io/cri-api v0.33.5/go.mod h1:OLQvT45OpIA+tv91ZrpuFIGY+Y2Ho23poS7n115Aocs= -k8s.io/cri-client v0.33.5 h1:RjkIlunp2NxKPQLvD9XlQwjliz71L0jljmIrGGxPZAU= -k8s.io/cri-client v0.33.5/go.mod h1:apcc+zemlSsvajH3a61bARqArBN2YkQueMe820qiVjQ= -k8s.io/csi-translation-lib v0.33.5 h1:CatOlpJtSJ9eliQWvGsdjWBdOFgQvIlyaVXfs1QojTY= -k8s.io/csi-translation-lib v0.33.5/go.mod h1:/7unRR1EEbxjjDcUN3v5Qc3vSPy3vwwFbuTPzk/raCo= -k8s.io/dynamic-resource-allocation v0.33.5 h1:rWNQB06AMTufOODf1H9BMM7Qw0fBKP0KA2mHEuEmkQQ= -k8s.io/dynamic-resource-allocation v0.33.5/go.mod h1:b9RAX4x8WFTMYonx7yZTDqC4++LwIMJNq+CuN5/s9lU= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= +k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.3 h1:uGH1qpDvSiYG4HVFqc6A3L4CKiX+aBWDrrsxHYK0Bdo= +k8s.io/apiserver v0.34.3/go.mod h1:QPnnahMO5C2m3lm6fPW3+JmyQbvHZQ8uudAu/493P2w= +k8s.io/cli-runtime v0.34.3 h1:YRyMhiwX0dT9lmG0AtZDaeG33Nkxgt9OlCTZhRXj9SI= +k8s.io/cli-runtime v0.34.3/go.mod h1:GVwL1L5uaGEgM7eGeKjaTG2j3u134JgG4dAI6jQKhMc= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= +k8s.io/cloud-provider v0.34.3 h1:+ZIj1mYPzrA0vWZMFFustsDCe1iP+xkhq0ZXZBhPW0o= +k8s.io/cloud-provider v0.34.3/go.mod h1:e0XM6MTHG4rPk1Fa7oWnQT9VqKca+jw7wcc+BJeUcn4= +k8s.io/cluster-bootstrap v0.34.3 h1:mLguWldCwTk0GvoWHg6tf6qgpVMDLCSvvtWUoL13RXg= +k8s.io/cluster-bootstrap v0.34.3/go.mod h1:50AJCDVJ8HMmw9W2EN0cHSbTsI9GYtyOM+eyxjVlAwg= +k8s.io/code-generator v0.34.3/go.mod h1:oW73UPYpGLsbRN8Ozkhd6ZzkF8hzFCiYmvEuWZDroI4= +k8s.io/component-base v0.34.3 h1:zsEgw6ELqK0XncCQomgO9DpUIzlrYuZYA0Cgo+JWpVk= +k8s.io/component-base v0.34.3/go.mod h1:5iIlD8wPfWE/xSHTRfbjuvUul2WZbI2nOUK65XL0E/c= +k8s.io/component-helpers v0.34.3 h1:Iws1GQfM89Lxo7IZITGmVdFOW0Bmyd7SVwwIu1/CCkE= +k8s.io/component-helpers v0.34.3/go.mod h1:S8HjjMTrUDVMVPo2EdNYRtQx9uIEIueQYdPMOe9UxJs= +k8s.io/controller-manager v0.34.3 h1:pEW6ExR3FteKkYkKRrLoi0Sy8dcbvUTAReP8OTxK5k0= +k8s.io/controller-manager v0.34.3/go.mod h1:YzXiwiubf6GdSC3ej2XFYhQQBwF5AvJq/3eymdsU9OU= +k8s.io/cri-api v0.34.3 h1:zFdQSHZuQlQXesw9ncjQRUyDpvLng/84Q4qLKd8x2zE= +k8s.io/cri-api v0.34.3/go.mod h1:4qVUjidMg7/Z9YGZpqIDygbkPWkg3mkS1PvOx/kpHTE= +k8s.io/cri-client v0.34.3 h1:P1LMB7Aa52OfIaJ383FfHYJTlokZsLYUBn6Y7IEc9Oc= +k8s.io/cri-client v0.34.3/go.mod h1:EEQXyjPYePB2RSBnrTs+4up14q/6lVKnHvPXUSMYR4A= +k8s.io/csi-translation-lib v0.34.3 h1:WGE/HPz5D3TIqffhYkk6s4KfW1mcSwSH30MzABK47Pg= +k8s.io/csi-translation-lib v0.34.3/go.mod h1:Lx11spUQnRzYFDrTok0/6cQMP3oXHi73+mXWvkRTxbE= +k8s.io/dynamic-resource-allocation v0.34.3 h1:8UGn1CTj1IljJa+r6HxnEDqLvcBZkv5c+Ooa6x1Oy+o= +k8s.io/dynamic-resource-allocation v0.34.3/go.mod h1:eYjQqNaHLfqXT94lbSXEy8ZLaUg1mGJ2JCEtNWM7e7M= +k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kms v0.33.5 h1:u60sDBe4Fz6PLeiNF13sEahjIU428ajiWuIw7NSVBlg= -k8s.io/kms v0.33.5/go.mod h1:C1I8mjFFBNzfUZXYt9FZVJ8MJl7ynFbGgZFbBzkBJ3E= -k8s.io/kube-aggregator v0.33.5 h1:5libMG9e4m9lwhNBT89bBCd9x/rZebMahw5CHq9DE/Q= -k8s.io/kube-aggregator v0.33.5/go.mod h1:mHmmDqxY2ZkInu7eSAXb1ecaKV/U9DqPTQWBV2O84go= -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/kube-scheduler v0.33.5 h1:eqZHF4H4F33XF7yGIK4quk1DxGACexINNuNJlO4M1ZY= -k8s.io/kube-scheduler v0.33.5/go.mod h1:n004Qs3SWbLMJ6PuPtwkS377CqaaKUeYFzqb1+4IaA8= -k8s.io/kubectl v0.33.5 h1:/wj5EjXXrVeSd8+FcZ2sIIP1PlQkq8HWsR9T1Nsl32c= -k8s.io/kubectl v0.33.5/go.mod h1:YrBGE7U+nz7+UatG+aNDocIQtdTyqN528dwFCv6+Kuw= -k8s.io/kubelet v0.33.5 h1:PYV+O8B6ZoQMDaQdYTSCzNdQTLdjAAWTspS2KkNfjLQ= -k8s.io/kubelet v0.33.5/go.mod h1:8SQ/0fgyNwm6zjHktBhPAUlgtji19YMa2lhSlYKUhrA= -k8s.io/kubernetes v1.33.5 h1:uf0/5VVQodYf61FudCbwtWjg5ApQcKKA7mwanTLjxyw= -k8s.io/kubernetes v1.33.5/go.mod h1:nrt8sldmckKz2fCZhgRX3SKfS2e+CzXATPv6ITNkU00= -k8s.io/mount-utils v0.33.5 h1:Dc+t8nfUD+k0kvDyzWqsIxYPd3+KMuVQae2Gr8BAmxs= -k8s.io/mount-utils v0.33.5/go.mod h1:1JR4rKymg8B8bCPo618hpSAdrpO6XLh0Acqok/xVwPE= -k8s.io/pod-security-admission v0.33.5 h1:wThuFFa3ykpClGqtjRKEn/7zaIuinvvqgpzPdHPVwOs= -k8s.io/pod-security-admission v0.33.5/go.mod h1:hlFjlok9N9X94rqbG3/xNU1m4V4hiuANvlIk/lL9hXE= -k8s.io/sample-apiserver v0.33.5 h1:CIP6Mt1KLdwbaYPlEletH3JJ/EbCfKCmTubj0g6MgGs= -k8s.io/sample-apiserver v0.33.5/go.mod h1:1Rvz644e4l3xLWI9H/Vwvyf4iJrS2DF3280qqBZN3OA= -k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= -k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kms v0.34.3 h1:QzBOD0sk1bGQVMcZQAHGjtbP1iKZJUyhC6D0I+BTxIE= +k8s.io/kms v0.34.3/go.mod h1:s1CFkLG7w9eaTYvctOxosx88fl4spqmixnNpys0JAtM= +k8s.io/kube-aggregator v0.34.3 h1:rKsZWTD2As4dKuv+zzdJU0uo5H7bFlAEoSucai4mW6M= +k8s.io/kube-aggregator v0.34.3/go.mod h1:d4D8PV2FK4Qlq6u442FSum1tHPhK9tKdKBfH/A3R0I0= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= +k8s.io/kube-scheduler v0.34.3 h1:ki99I6opxUZNcIO/QIq1Jz5iTVRHPRdDRFxi5A/3Zjw= +k8s.io/kube-scheduler v0.34.3/go.mod h1:ECkAVWCHQjWJLasX6eznbZ/7J6YqDWiZchXpjG/w5ig= +k8s.io/kubectl v0.34.3 h1:vpM6//153gh5gvsYHXWHVJ4l4xmN5QFwTSmlfd8icm8= +k8s.io/kubectl v0.34.3/go.mod h1:zZQHtIZoUqTP1bAnPzq/3W1jfc0NeOeunFgcswrfg1c= +k8s.io/kubelet v0.34.3 h1:8QRev2FmasZ05yCC774qn6ULche72PYM7AQv0CVt9CM= +k8s.io/kubelet v0.34.3/go.mod h1:pMgblr+nVQ02UkyaTcgqzS3AIYVQkjlMFg1Pd5rGC1Q= +k8s.io/kubernetes v1.34.3 h1:0TfljWbhEF5DBks+WFMSrvKfxBLo4vnZuqORjLMiyT4= +k8s.io/kubernetes v1.34.3/go.mod h1:m6pZk6a179pRo2wsTiCPORJ86iOEQmfIzUvtyEF8BwA= +k8s.io/mount-utils v0.34.3 h1:+sk7PVMQhGoNkGnxmxhyjEXpFcTaD6s3a6NXZNhqERc= +k8s.io/mount-utils v0.34.3/go.mod h1:MIjjYlqJ0ziYQg0MO09kc9S96GIcMkhF/ay9MncF0GA= +k8s.io/pod-security-admission v0.34.3 h1:l9qgNG4X+Zl7H9d5KlYeUtRg9TfApTGNFHCy4F6aXJs= +k8s.io/pod-security-admission v0.34.3/go.mod h1:ureLqfSF+1BrkFIbIImze8LfRMVj48Kg8qYiPHLclmE= +k8s.io/sample-apiserver v0.34.3 h1:M5FMFbJLo+5UTaH/T9PGmacxBUe4Qdyp/iGjHgxiGaU= +k8s.io/sample-apiserver v0.34.3/go.mod h1:009MLbuqeP8oeQyIbovwKOb3crLuANZdKJ2H//IGUeQ= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +kubevirt.io/api v1.8.0-alpha.0 h1:5GAhXatWdkwpz8EWTsHtT3NXWN3D1BlVWWR3RnTFrUE= +kubevirt.io/api v1.8.0-alpha.0/go.mod h1:J2HiSOLVhKj2UXoe14V6rpr3ZO/W/QBxcN+QK/S0Xkc= +kubevirt.io/containerized-data-importer-api v1.63.1 h1:g2I9za0QEscRsQjOOK/MM0feywp1x9Gl8IyT6Egtg0g= +kubevirt.io/containerized-data-importer-api v1.63.1/go.mod h1:VGp35wxpLXU18b7cnEpmcThI3AjcZUSfg/Zfql44U4o= +kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 h1:QMrd0nKP0BGbnxTqakhDZAUhGKxPiPiN5gSDqKUmGGc= +kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90/go.mod h1:018lASpFYBsYN6XwmA2TIrPCx6e0gviTd/ZNtSitKgc= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= -modernc.org/memory v1.10.0 h1:fzumd51yQ1DxcOxSO+S6X7+QTuVU+n8/Aj7swYjFfC4= -modernc.org/memory v1.10.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU= -sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2Fol2CS0QHMNs/WI1MOSGzCm1KhM5ec= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/controller-runtime v0.22.3 h1:I7mfqz/a/WdmDCEnXmSPm8/b/yRTy6JsKKENTijTq8Y= +sigs.k8s.io/controller-runtime v0.22.3/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/kind v0.29.0 h1:3TpCsyh908IkXXpcSnsMjWdwdWjIl7o9IMZImZCWFnI= -sigs.k8s.io/kind v0.29.0/go.mod h1:ldWQisw2NYyM6k64o/tkZng/1qQW7OlzcN5a8geJX3o= -sigs.k8s.io/knftables v0.0.18 h1:6Duvmu0s/HwGifKrtl6G3AyAPYlWiZqTgS8bkVMiyaE= -sigs.k8s.io/knftables v0.0.18/go.mod h1:f/5ZLKYEUPUhVjUCg6l80ACdL7CIIyeL0DxfgojGRTk= -sigs.k8s.io/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ= -sigs.k8s.io/kustomize/api v0.19.0/go.mod h1:/BbwnivGVcBh1r+8m3tH1VNxJmHSk1PzP5fkP6lbL1o= -sigs.k8s.io/kustomize/kyaml v0.19.0 h1:RFge5qsO1uHhwJsu3ipV7RNolC7Uozc0jUBC/61XSlA= -sigs.k8s.io/kustomize/kyaml v0.19.0/go.mod h1:FeKD5jEOH+FbZPpqUghBP8mrLjJ3+zD3/rf9NNu1cwY= -sigs.k8s.io/network-policy-api v0.1.5 h1:xyS7VAaM9EfyB428oFk7WjWaCK6B129i+ILUF4C8l6E= -sigs.k8s.io/network-policy-api v0.1.5/go.mod h1:D7Nkr43VLNd7iYryemnj8qf0N/WjBzTZDxYA+g4u1/Y= +sigs.k8s.io/kind v0.30.0 h1:2Xi1KFEfSMm0XDcvKnUt15ZfgRPCT0OnCBbpgh8DztY= +sigs.k8s.io/kind v0.30.0/go.mod h1:FSqriGaoTPruiXWfRnUXNykF8r2t+fHtK0P0m1AbGF8= +sigs.k8s.io/knftables v0.0.19 h1:0orK0+tYhY575F5X9uJGu80t+aVQ/hJj48I3fz3TBk8= +sigs.k8s.io/knftables v0.0.19/go.mod h1:f/5ZLKYEUPUhVjUCg6l80ACdL7CIIyeL0DxfgojGRTk= +sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= +sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM= +sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78= +sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po= +sigs.k8s.io/network-policy-api v0.1.8-0.20260212153203-412bf65729a5 h1:ps2rsHCZp0GDQCit54NO7Zg5QoFc7RYv5m0ukgmtyVE= +sigs.k8s.io/network-policy-api v0.1.8-0.20260212153203-412bf65729a5/go.mod h1:xYIHRc47QPAIiPkl+oXfu5we8jACNlt7WuKc7ZP0sGU= sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= -sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= +sigs.k8s.io/structured-merge-diff/v6 v6.2.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/goldmane/deps.txt b/goldmane/deps.txt index 57ea364c2ee..8ba0a38ee6b 100644 --- a/goldmane/deps.txt +++ b/goldmane/deps.txt @@ -2,78 +2,79 @@ This file contains the list of modules that this package depends on in order to trigger CI on changes -go 1.25.1 +go 1.25.7 github.com/beorn7/perks v1.0.1 github.com/blang/semver/v4 v4.0.0 github.com/cespare/xxhash/v2 v2.3.0 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/distribution/reference v0.6.0 -github.com/emicklei/go-restful/v3 v3.11.0 -github.com/evanphx/json-patch v5.9.0+incompatible +github.com/emicklei/go-restful v2.15.0+incompatible +github.com/emicklei/go-restful/v3 v3.12.2 +github.com/evanphx/json-patch v5.9.11+incompatible github.com/evanphx/json-patch/v5 v5.9.11 -github.com/fxamacker/cbor/v2 v2.7.0 +github.com/fxamacker/cbor/v2 v2.9.0 github.com/go-logr/logr v1.4.3 github.com/go-openapi/jsonpointer v0.21.0 github.com/go-openapi/jsonreference v0.20.2 github.com/go-openapi/swag v0.23.0 github.com/gogo/protobuf v1.3.2 -github.com/google/gnostic-models v0.6.9 -github.com/google/go-cmp v0.7.0 +github.com/google/gnostic-models v0.7.0 github.com/google/uuid v1.6.0 +github.com/jinzhu/copier v0.4.0 github.com/josharian/intern v1.0.0 github.com/json-iterator/go v1.1.12 github.com/kelseyhightower/envconfig v1.4.0 github.com/mailru/easyjson v0.7.7 github.com/mattn/go-runewidth v0.0.16 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd -github.com/modern-go/reflect2 v1.0.2 +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 github.com/olekukonko/tablewriter v0.0.5 github.com/opencontainers/go-digest v1.0.0 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/projectcalico/calico -github.com/projectcalico/calico/lib/std v0.0.0-00010101000000-000000000000 -github.com/prometheus/client_golang v1.23.0 +github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 -github.com/prometheus/common v0.65.0 -github.com/prometheus/procfs v0.17.0 +github.com/prometheus/common v0.67.2 +github.com/prometheus/procfs v0.19.2 github.com/rivo/uniseg v0.4.7 github.com/sirupsen/logrus v1.9.3 -github.com/spf13/pflag v1.0.7 +github.com/spf13/pflag v1.0.10 github.com/stretchr/objx v0.5.2 -github.com/stretchr/testify v1.10.0 +github.com/stretchr/testify v1.11.1 github.com/x448/float16 v0.8.4 -go.opentelemetry.io/otel v1.35.0 -go.opentelemetry.io/otel/trace v1.35.0 -go.yaml.in/yaml/v2 v2.4.2 -golang.org/x/net v0.43.0 -golang.org/x/oauth2 v0.30.0 -golang.org/x/sys v0.35.0 -golang.org/x/term v0.34.0 -golang.org/x/text v0.28.0 -golang.org/x/time v0.12.0 +go.opentelemetry.io/otel v1.37.0 +go.opentelemetry.io/otel/trace v1.37.0 +go.yaml.in/yaml/v2 v2.4.3 +go.yaml.in/yaml/v3 v3.0.4 +golang.org/x/net v0.49.0 +golang.org/x/oauth2 v0.34.0 +golang.org/x/sys v0.40.0 +golang.org/x/term v0.39.0 +golang.org/x/text v0.33.0 +golang.org/x/time v0.14.0 google.golang.org/genproto v0.0.0-20250603155806-513f23925822 -google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a -google.golang.org/grpc v1.72.2 -google.golang.org/protobuf v1.36.7 +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c +google.golang.org/grpc v1.76.0 +google.golang.org/protobuf v1.36.10 gopkg.in/inf.v0 v0.9.1 gopkg.in/yaml.v3 v3.0.1 -k8s.io/api v0.33.5 -k8s.io/apiextensions-apiserver v0.33.5 -k8s.io/apimachinery v0.33.5 -k8s.io/apiserver v0.33.5 -k8s.io/client-go v0.33.5 -k8s.io/component-base v0.33.5 -k8s.io/component-helpers v0.33.5 -k8s.io/controller-manager v0.33.5 +k8s.io/api v0.34.3 +k8s.io/apiextensions-apiserver v0.34.3 +k8s.io/apimachinery v0.34.3 +k8s.io/apiserver v0.34.3 +k8s.io/client-go v0.34.3 +k8s.io/component-base v0.34.3 +k8s.io/component-helpers v0.34.3 +k8s.io/controller-manager v0.34.3 k8s.io/klog v0.2.0 k8s.io/klog/v2 v2.130.1 -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff -k8s.io/kubelet v0.33.5 -k8s.io/kubernetes v1.33.5 -k8s.io/utils v0.0.0-20241210054802-24370beab758 -sigs.k8s.io/controller-runtime v0.20.4 +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b +k8s.io/kubelet v0.34.3 +k8s.io/kubernetes v1.34.3 +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 +sigs.k8s.io/controller-runtime v0.22.3 sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 sigs.k8s.io/randfill v1.0.0 -sigs.k8s.io/structured-merge-diff/v4 v4.6.0 +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 sigs.k8s.io/yaml v1.6.0 diff --git a/goldmane/pkg/goldmane/goldmane.go b/goldmane/pkg/goldmane/goldmane.go index f12eb2433f6..a3d03d68105 100644 --- a/goldmane/pkg/goldmane/goldmane.go +++ b/goldmane/pkg/goldmane/goldmane.go @@ -108,28 +108,6 @@ func init() { prometheus.MustRegister(numDroppedFlows) } -// listRequest is an internal helper used to synchronously request matching flows from the aggregator. -type listRequest struct { - respCh chan *listResponse - req *proto.FlowListRequest -} - -type listResponse struct { - results *proto.FlowListResult - err error -} - -// filterHintsRequest is an internal helper used to synchronously request filter hints from the aggregator. -type filterHintsRequest struct { - respCh chan *filterHintsResponse - req *proto.FilterHintsRequest -} - -type filterHintsResponse struct { - results *proto.FilterHintsResult - err error -} - // sinkRequest is an internal helper used to set the sink for the aggregator, which can by modified at runtime. type sinkRequest struct { sink storage.Sink @@ -350,37 +328,6 @@ func (a *Goldmane) Stream(req *proto.FlowStreamRequest) (stream.Stream, error) { return s, nil } -// List returns a list of flows that match the given request. It uses a channel to -// synchronously request the flows from the aggregator. -func (a *Goldmane) List(req *proto.FlowListRequest) (*proto.FlowListResult, error) { - respCh := make(chan *listResponse) - defer close(respCh) - a.listRequests <- listRequest{respCh, req} - resp := <-respCh - return resp.results, resp.err -} - -func (a *Goldmane) Hints(req *proto.FilterHintsRequest) (*proto.FilterHintsResult, error) { - logrus.WithField("req", req).Debug("Received hints request") - - respCh := make(chan *filterHintsResponse) - defer close(respCh) - a.filterHintsRequests <- filterHintsRequest{respCh, req} - resp := <-respCh - - return resp.results, resp.err -} - -func (a *Goldmane) validateListRequest(req *proto.FlowListRequest) error { - if err := a.validateTimeRange(req.StartTimeGte, req.StartTimeLt); err != nil { - return err - } - if len(req.SortBy) > 1 { - return fmt.Errorf("at most one sort order is supported") - } - return nil -} - func (a *Goldmane) validateTimeRange(startTimeGt, startTimeLt int64) error { if startTimeGt >= startTimeLt { return fmt.Errorf("startTimeGt (%d) must be less than startTimeLt (%d)", startTimeGt, startTimeLt) @@ -441,75 +388,6 @@ func (a *Goldmane) normalizeTimeRange(gt, lt int64) (int64, int64) { return gt, lt } -func (a *Goldmane) queryFlows(req *proto.FlowListRequest) *listResponse { - logrus.WithFields(logrus.Fields{"req": req}).Debug("Received flow request") - - // Sanitize the time range, resolving any relative time values. - req.StartTimeGte, req.StartTimeLt = a.normalizeTimeRange(req.StartTimeGte, req.StartTimeLt) - - // Validate the request. - if err := a.validateListRequest(req); err != nil { - return &listResponse{nil, err} - } - - flowsToReturn, meta, err := a.flowStore.List(req) - if err != nil { - logrus.WithError(err).Warn("Error listing flows") - return &listResponse{nil, err} - } - - return &listResponse{&proto.FlowListResult{ - Meta: &proto.ListMetadata{ - TotalPages: int64(meta.TotalPages), - TotalResults: int64(meta.TotalResults), - }, - Flows: a.flowsToResult(flowsToReturn), - }, nil} -} - -func (a *Goldmane) queryFilterHints(req *proto.FilterHintsRequest) *filterHintsResponse { - logrus.WithFields(logrus.Fields{"req": req}).Debug("Received filter hints request.") - - // Sanitize the time range, resolving any relative time values. - req.StartTimeGte, req.StartTimeLt = a.normalizeTimeRange(req.StartTimeGte, req.StartTimeLt) - - // Validate the request. - if err := a.validateTimeRange(req.StartTimeGte, req.StartTimeLt); err != nil { - return &filterHintsResponse{nil, err} - } - - values, meta, err := a.flowStore.FilterHints(req) - if err != nil { - logrus.WithError(err).Warn("Error listing filter hints") - return &filterHintsResponse{nil, err} - } - - var hints []*proto.FilterHint - for _, value := range values { - hints = append(hints, &proto.FilterHint{Value: value}) - } - - return &filterHintsResponse{&proto.FilterHintsResult{ - Meta: &proto.ListMetadata{ - TotalPages: int64(meta.TotalPages), - TotalResults: int64(meta.TotalResults), - }, - Hints: hints, - }, nil} -} - -// flowsToResult converts a list of internal Flow objects to a list of proto.FlowResult objects. -func (a *Goldmane) flowsToResult(flows []*types.Flow) []*proto.FlowResult { - var flowsToReturn []*proto.FlowResult - for _, flow := range flows { - flowsToReturn = append(flowsToReturn, &proto.FlowResult{ - Flow: types.FlowToProto(flow), - Id: a.flowStore.ID(*flow.Key), - }) - } - return flowsToReturn -} - func (a *Goldmane) Stop() { close(a.done) } diff --git a/goldmane/pkg/goldmane/goldmane_benchmark_test.go b/goldmane/pkg/goldmane/goldmane_benchmark_test.go new file mode 100644 index 00000000000..f7676e866b9 --- /dev/null +++ b/goldmane/pkg/goldmane/goldmane_benchmark_test.go @@ -0,0 +1,180 @@ +// Copyright (c) 2025 Tigera, Inc. All rights reserved. + +// 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 goldmane_test + +import ( + "math/rand/v2" + "os" + "runtime" + "strings" + "testing" + + "github.com/sirupsen/logrus" + + "github.com/projectcalico/calico/goldmane/pkg/goldmane" + "github.com/projectcalico/calico/goldmane/pkg/testutils" + "github.com/projectcalico/calico/goldmane/pkg/types" + "github.com/projectcalico/calico/lib/std/time" +) + +// Performance thresholds for the benchmarks. +// These constants define the upper limits for each key metric. +const ( + maxNsPerOp = 13000.0 // Max allowed nanoseconds per operation (13 microseconds) - for reference only, check benchmark output + maxNsPerOpParallel = 35000.0 // Max allowed nanoseconds per operation (35 microseconds) - for reference only, check benchmark output + maxHeapAllocMB = 5.0 // Max allowed total heap allocation in MB + maxHeapAllocMBParallel = 8.0 // Max allowed total heap allocation in MB + maxBytesPerOp = 10000.0 // Max allowed bytes allocated per op +) + +var flows []*types.Flow + +func init() { + now := time.Now() + for range 1000 { + randomNumber := rand.IntN(1000) + flowStartTime := now.Add(time.Duration(randomNumber) * time.Millisecond) + flows = append(flows, + types.ProtoToFlow( + testutils.NewRandomFlow(flowStartTime.Unix()), + ), + ) + } +} + +func setupBenchmark(b *testing.B) func() { + // Set up logrus to use b.Logf via custom writer + writer := &logrusWriter{b} + logrus.SetOutput(writer) + logrus.SetLevel(logrus.WarnLevel) + + return func() { + logrus.SetOutput(os.Stderr) + } +} + +type logrusWriter struct { + b *testing.B +} + +func (w *logrusWriter) Write(p []byte) (int, error) { + w.b.Logf("%s", strings.TrimSuffix(string(p), "\n")) + return len(p), nil +} + +// BenchmarkGoldmaneReceive benchmarks the basic Receive function performance with a single flow. +func BenchmarkGoldmaneReceive(b *testing.B) { + cleanup := setupBenchmark(b) + defer cleanup() + + // Setup Goldmane + gm := goldmane.NewGoldmane() + now := time.Now().Unix() + <-gm.Run(now) + defer gm.Stop() + + flowCount := len(flows) + idx := rand.IntN(flowCount) + + b.ResetTimer() + b.ReportAllocs() + + // Clean up memory before measurement begins + runtime.GC() + + for b.Loop() { + gm.Receive(flows[idx]) + + idx++ + if idx == flowCount { + idx = 0 + } + } + + // Cleanup and give GC a moment to settle before measuring memory + runtime.GC() + + // Collect memory stats + var m runtime.MemStats + runtime.ReadMemStats(&m) + heapAllocMB := float64(m.HeapAlloc) / (1024 * 1024) + b.ReportMetric(heapAllocMB, "HeapAllocMB") + + // Log benchmark expectations + b.Logf("\t=== Benchmark Thresholds (Max allowed values) ===") + b.Logf("\t\tMax allowed ns/op :\t\t%.1f", maxNsPerOp) + b.Logf("\t\tMax allowed HeapAllocMB :\t%.3f", maxHeapAllocMB) + b.Logf("\t\tMax allowed B/op (Bytes/op):\t%.1f", maxBytesPerOp) + b.Logf("\t\tNote: ns/op is measured by Go's benchmark framework (shown in output above)") + + // Fail the test if memory thresholds are exceeded + if heapAllocMB > maxHeapAllocMB { + b.Fatalf("HeapAllocMB too high: got %.2f MB, max %.2f MB", heapAllocMB, maxHeapAllocMB) + } +} + +// BenchmarkGoldmaneReceiveParallel benchmarks parallel Receive calls to test concurrency performance. +func BenchmarkGoldmaneReceiveParallel(b *testing.B) { + cleanup := setupBenchmark(b) + defer cleanup() + + // Setup Goldmane + gm := goldmane.NewGoldmane() + now := time.Now().Unix() + <-gm.Run(now) + defer gm.Stop() + + flowCount := len(flows) + + b.ResetTimer() + b.ReportAllocs() + + // Clean up memory before measurement begins + runtime.GC() + + b.RunParallel(func(pb *testing.PB) { + idx := rand.IntN(flowCount) + + for pb.Next() { + gm.Receive(flows[idx]) + + idx++ + if idx == flowCount { + idx = 0 + } + } + }) + + // Cleanup and give GC a moment to settle before measuring memory + runtime.GC() + + // Collect memory stats + var m runtime.MemStats + runtime.ReadMemStats(&m) + heapAllocMB := float64(m.HeapAlloc) / (1024 * 1024) + b.ReportMetric(heapAllocMB, "HeapAllocMB") + + // Log benchmark expectations + b.Logf("\t=== Benchmark Thresholds (Max allowed values) ===") + b.Logf("\t\tMax allowed ns/op (Parallel Exec.) :\t\t%.1f", maxNsPerOpParallel) + b.Logf("\t\tMax allowed HeapAllocMB :\t%.3f", maxHeapAllocMBParallel) + b.Logf("\t\tMax allowed B/op (Bytes/op):\t%.1f", maxBytesPerOp) + b.Logf("\t\tNote: ns/op is measured by Go's benchmark framework (shown in output above)") + + // Fail the test if memory thresholds are exceeded + if heapAllocMB > maxHeapAllocMBParallel { + b.Fatalf("HeapAllocMB too high: got %.2f MB, max %.2f MB", heapAllocMB, maxHeapAllocMBParallel) + } +} diff --git a/goldmane/pkg/goldmane/goldmane_filter_hints.go b/goldmane/pkg/goldmane/goldmane_filter_hints.go new file mode 100644 index 00000000000..617578f0f98 --- /dev/null +++ b/goldmane/pkg/goldmane/goldmane_filter_hints.go @@ -0,0 +1,74 @@ +// Copyright (c) 2025 Tigera, Inc. All rights reserved. + +// 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 goldmane + +import ( + "github.com/sirupsen/logrus" + + "github.com/projectcalico/calico/goldmane/proto" +) + +// filterHintsRequest is an internal helper used to synchronously request filter hints from the aggregator. +type filterHintsRequest struct { + respCh chan *filterHintsResponse + req *proto.FilterHintsRequest +} + +type filterHintsResponse struct { + results *proto.FilterHintsResult + err error +} + +func (a *Goldmane) Hints(req *proto.FilterHintsRequest) (*proto.FilterHintsResult, error) { + logrus.WithField("req", req).Debug("Received hints request") + + respCh := make(chan *filterHintsResponse) + defer close(respCh) + a.filterHintsRequests <- filterHintsRequest{respCh, req} + resp := <-respCh + + return resp.results, resp.err +} + +func (a *Goldmane) queryFilterHints(req *proto.FilterHintsRequest) *filterHintsResponse { + logrus.WithFields(logrus.Fields{"req": req}).Debug("Received filter hints request.") + + // Sanitize the time range, resolving any relative time values. + req.StartTimeGte, req.StartTimeLt = a.normalizeTimeRange(req.StartTimeGte, req.StartTimeLt) + + // Validate the request. + if err := a.validateTimeRange(req.StartTimeGte, req.StartTimeLt); err != nil { + return &filterHintsResponse{nil, err} + } + + values, meta, err := a.flowStore.FilterHints(req) + if err != nil { + logrus.WithError(err).Warn("Error listing filter hints") + return &filterHintsResponse{nil, err} + } + + var hints []*proto.FilterHint + for _, value := range values { + hints = append(hints, &proto.FilterHint{Value: value}) + } + + return &filterHintsResponse{&proto.FilterHintsResult{ + Meta: &proto.ListMetadata{ + TotalPages: int64(meta.TotalPages), + TotalResults: int64(meta.TotalResults), + }, + Hints: hints, + }, nil} +} diff --git a/goldmane/pkg/goldmane/goldmane_filter_hints_test.go b/goldmane/pkg/goldmane/goldmane_filter_hints_test.go new file mode 100644 index 00000000000..d5831ce0e98 --- /dev/null +++ b/goldmane/pkg/goldmane/goldmane_filter_hints_test.go @@ -0,0 +1,310 @@ +// Copyright (c) 2025 Tigera, Inc. All rights reserved. + +// 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 goldmane_test + +import ( + "fmt" + "testing" + + . "github.com/onsi/gomega" + "github.com/stretchr/testify/require" + + "github.com/projectcalico/calico/goldmane/pkg/goldmane" + "github.com/projectcalico/calico/goldmane/pkg/testutils" + "github.com/projectcalico/calico/goldmane/pkg/types" + "github.com/projectcalico/calico/goldmane/proto" + "github.com/projectcalico/calico/lib/std/time" +) + +func TestFilterHints(t *testing.T) { + type tc struct { + name string + req *proto.FilterHintsRequest + numResp int + check func([]*proto.FilterHint) error + } + + tests := []tc{ + { + name: "SourceName, no filters", + req: &proto.FilterHintsRequest{Type: proto.FilterType_FilterTypeSourceName}, + numResp: 10, + check: func(hints []*proto.FilterHint) error { + for i, hint := range hints { + if hint.Value != fmt.Sprintf("source-%d", i) { + return fmt.Errorf("Expected SourceName to be source-%d, got %s", i, hint.Value) + } + } + return nil + }, + }, + + { + name: "SourceName, with SourceName filter", + req: &proto.FilterHintsRequest{ + Type: proto.FilterType_FilterTypeSourceName, + Filter: &proto.Filter{SourceNames: []*proto.StringMatch{{Value: "source-1"}}}, + }, + numResp: 1, + }, + + { + name: "Tier, no filters", + req: &proto.FilterHintsRequest{ + Type: proto.FilterType_FilterTypePolicyTier, + }, + numResp: 12, + }, + { + name: "Policy name, no filters", + req: &proto.FilterHintsRequest{ + Type: proto.FilterType_FilterTypePolicyName, + }, + numResp: 12, + }, + { + name: "Namespace hints with filters kind & tier", + req: &proto.FilterHintsRequest{ + Type: proto.FilterType_FilterTypeSourceNamespace, + Filter: &proto.Filter{ + Policies: []*proto.PolicyMatch{ + { + Kind: proto.PolicyKind_CalicoNetworkPolicy, + Tier: "tier-5", + }, + }, + }, + }, + numResp: 1, + check: func(hints []*proto.FilterHint) error { + for _, h := range hints { + if h.Value != "source-ns-5" { + return fmt.Errorf("expected namespace source-ns-5, got %s", h.Value) + } + } + return nil + }, + }, + { + name: "PolicyName hints with filters namespace & kind", + req: &proto.FilterHintsRequest{ + Type: proto.FilterType_FilterTypePolicyName, + Filter: &proto.Filter{ + SourceNamespaces: []*proto.StringMatch{{Value: "source-ns-4"}}, + Policies: []*proto.PolicyMatch{{Kind: proto.PolicyKind_CalicoNetworkPolicy}}, + }, + }, + numResp: 2, + }, + { + name: "PolicyTier hints with filter policy name", + req: &proto.FilterHintsRequest{ + Type: proto.FilterType_FilterTypePolicyTier, + Filter: &proto.Filter{ + Policies: []*proto.PolicyMatch{ + {Name: "name-4"}, + {Name: "name-5"}, + }, + }, + }, + numResp: 4, + check: func(hints []*proto.FilterHint) error { + if hints[0].Value != "tier-4" { + return fmt.Errorf("expected tier tier-4, got %s", hints[0].Value) + } + if hints[1].Value != "tier-5" { + return fmt.Errorf("expected tier tier-5, got %s", hints[0].Value) + } + return nil + }, + }, + { + name: "PolicyKind hints with filters namespace & tier", + req: &proto.FilterHintsRequest{ + Type: proto.FilterType_FilterTypePolicyKind, + Filter: &proto.Filter{ + SourceNamespaces: []*proto.StringMatch{{Value: "source-ns-2"}}, + Policies: []*proto.PolicyMatch{{ + Tier: "tier-2", + Namespace: "policy-namespace-2", + }}, + }, + }, + numResp: 1, + check: func(hints []*proto.FilterHint) error { + // In this test all policies use only CalicoNetworkPolicy kind. + if hints[0].Value != "CalicoNetworkPolicy" { + return fmt.Errorf("expected kind CalicoNetworkPolicy, got %s", hints[0].Value) + } + return nil + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Create a clock and rollover controller. + c := newClock(initialNow) + roller := &rolloverController{ + ch: make(chan time.Time), + aggregationWindowSecs: 1, + clock: c, + } + opts := []goldmane.Option{ + goldmane.WithRolloverTime(1 * time.Second), + goldmane.WithRolloverFunc(roller.After), + goldmane.WithNowFunc(c.Now), + } + defer setupTest(t, opts...)() + <-gm.Run(c.Now().Unix()) + + // Create 10 flows, with a mix of fields to filter on. + for i := range 10 { + // Start with a base flow. + fl := testutils.NewRandomFlow(c.Now().Unix() - 1) + + // Configure fields to filter on. + fl.Key.SourceName = fmt.Sprintf("source-%d", i) + fl.Key.SourceNamespace = fmt.Sprintf("source-ns-%d", i) + fl.Key.DestName = fmt.Sprintf("dest-%d", i) + fl.Key.DestNamespace = fmt.Sprintf("dest-ns-%d", i) + fl.Key.Proto = "tcp" + fl.Key.DestPort = int64(i) + enforcedPolicyKind := proto.PolicyKind_CalicoNetworkPolicy + pendingPolicyKind := proto.PolicyKind_CalicoNetworkPolicy + if i > 5 { + pendingPolicyKind = proto.PolicyKind_Profile + enforcedPolicyKind = proto.PolicyKind_Profile + } + + fl.Key.Policies = &proto.PolicyTrace{ + EnforcedPolicies: []*proto.PolicyHit{ + { + Name: fmt.Sprintf("name-%d", i), + Tier: fmt.Sprintf("tier-%d", i), + Namespace: fmt.Sprintf("policy-namespace-%d", i), + Kind: enforcedPolicyKind, + }, + }, + PendingPolicies: []*proto.PolicyHit{ + { + Name: fmt.Sprintf("name-pending-%d", i), + Kind: pendingPolicyKind, + Tier: fmt.Sprintf("tier-pending-%d", i), + Namespace: fmt.Sprintf("ns-pending-%d", i), + }, + }, + } + + // Send it to goldmane. + gm.Receive(types.ProtoToFlow(fl)) + } + + // Wait for all flows to be received. + Eventually(func() bool { + results, _ := gm.List(&proto.FlowListRequest{}) + return len(results.Flows) == 10 + }, waitTimeout, retryTime, "Didn't receive all flows").Should(BeTrue()) + + // Query for hints using the query from the testcase. + results, err := gm.Hints(tc.req) + require.NoError(t, err) + + // Verify the hints. + require.Len(t, results.Hints, tc.numResp, "Expected %d hints, got %d: %+v", tc.numResp, len(results.Hints), results.Hints) + + if tc.check != nil { + require.NoError(t, tc.check(results.Hints), fmt.Sprintf("Hints check failed on hints: %+v", results.Hints)) + } + }) + } + + // Run some tests against EndOfTier flows. + eotTests := []tc{ + { + name: "EndOfTier, Tier, no filters", + req: &proto.FilterHintsRequest{ + Type: proto.FilterType_FilterTypePolicyTier, + }, + numResp: 10, + }, + } + + for _, tc := range eotTests { + t.Run(tc.name, func(t *testing.T) { + // Create a clock and rollover controller. + c := newClock(initialNow) + roller := &rolloverController{ + ch: make(chan time.Time), + aggregationWindowSecs: 1, + clock: c, + } + opts := []goldmane.Option{ + goldmane.WithRolloverTime(1 * time.Second), + goldmane.WithRolloverFunc(roller.After), + goldmane.WithNowFunc(c.Now), + } + defer setupTest(t, opts...)() + <-gm.Run(c.Now().Unix()) + + // Create 10 flows, with a mix of fields to filter on. + for i := range 10 { + // Start with a base flow. + fl := testutils.NewRandomFlow(c.Now().Unix() - 1) + + // Configure fields to filter on. + fl.Key.SourceName = fmt.Sprintf("source-%d", i) + fl.Key.SourceNamespace = fmt.Sprintf("source-ns-%d", i) + fl.Key.DestName = fmt.Sprintf("dest-%d", i) + fl.Key.DestNamespace = fmt.Sprintf("dest-ns-%d", i) + fl.Key.Proto = "tcp" + fl.Key.DestPort = int64(i) + enforcedPolicyKind := proto.PolicyKind_CalicoNetworkPolicy + fl.Key.Policies = &proto.PolicyTrace{ + EnforcedPolicies: []*proto.PolicyHit{ + { + Trigger: &proto.PolicyHit{ + Name: fmt.Sprintf("name-%d", i), + Kind: enforcedPolicyKind, + Tier: fmt.Sprintf("tier-%d", i), + Namespace: fmt.Sprintf("ns-%d", i), + }, + }, + }, + } + + // Send it to goldmane. + gm.Receive(types.ProtoToFlow(fl)) + } + + // Wait for all flows to be received. + Eventually(func() bool { + results, _ := gm.List(&proto.FlowListRequest{}) + return len(results.Flows) == 10 + }, waitTimeout, retryTime, "Didn't receive all flows").Should(BeTrue()) + + // Query for hints using the query from the testcase. + results, err := gm.Hints(tc.req) + require.NoError(t, err) + + // Verify the hints. + require.Len(t, results.Hints, tc.numResp, "Expected %d hints, got %d: %+v", tc.numResp, len(results.Hints), results.Hints) + + if tc.check != nil { + require.NoError(t, tc.check(results.Hints), fmt.Sprintf("Hints check failed on hints: %+v", results.Hints)) + } + }) + } +} diff --git a/goldmane/pkg/goldmane/goldmane_filters.go b/goldmane/pkg/goldmane/goldmane_filters.go new file mode 100644 index 00000000000..a93f8f896c8 --- /dev/null +++ b/goldmane/pkg/goldmane/goldmane_filters.go @@ -0,0 +1,93 @@ +// Copyright (c) 2025 Tigera, Inc. All rights reserved. + +// 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 goldmane + +import ( + "fmt" + + "github.com/sirupsen/logrus" + + "github.com/projectcalico/calico/goldmane/pkg/types" + "github.com/projectcalico/calico/goldmane/proto" +) + +// listRequest is an internal helper used to synchronously request matching flows from the aggregator. +type listRequest struct { + respCh chan *listResponse + req *proto.FlowListRequest +} + +type listResponse struct { + results *proto.FlowListResult + err error +} + +// List returns a list of flows that match the given request. It uses a channel to +// synchronously request the flows from the aggregator. +func (a *Goldmane) List(req *proto.FlowListRequest) (*proto.FlowListResult, error) { + respCh := make(chan *listResponse) + defer close(respCh) + a.listRequests <- listRequest{respCh, req} + resp := <-respCh + return resp.results, resp.err +} + +func (a *Goldmane) queryFlows(req *proto.FlowListRequest) *listResponse { + logrus.WithFields(logrus.Fields{"req": req}).Debug("Received flow request") + + // Sanitize the time range, resolving any relative time values. + req.StartTimeGte, req.StartTimeLt = a.normalizeTimeRange(req.StartTimeGte, req.StartTimeLt) + + // Validate the request. + if err := a.validateListRequest(req); err != nil { + return &listResponse{nil, err} + } + + flowsToReturn, meta, err := a.flowStore.List(req) + if err != nil { + logrus.WithError(err).Warn("Error listing flows") + return &listResponse{nil, err} + } + + return &listResponse{&proto.FlowListResult{ + Meta: &proto.ListMetadata{ + TotalPages: int64(meta.TotalPages), + TotalResults: int64(meta.TotalResults), + }, + Flows: a.flowsToResult(flowsToReturn), + }, nil} +} + +// flowsToResult converts a list of internal Flow objects to a list of proto.FlowResult objects. +func (a *Goldmane) flowsToResult(flows []*types.Flow) []*proto.FlowResult { + var flowsToReturn []*proto.FlowResult + for _, flow := range flows { + flowsToReturn = append(flowsToReturn, &proto.FlowResult{ + Flow: types.FlowToProto(flow), + Id: a.flowStore.ID(*flow.Key), + }) + } + return flowsToReturn +} + +func (a *Goldmane) validateListRequest(req *proto.FlowListRequest) error { + if err := a.validateTimeRange(req.StartTimeGte, req.StartTimeLt); err != nil { + return err + } + if len(req.SortBy) > 1 { + return fmt.Errorf("at most one sort order is supported") + } + return nil +} diff --git a/goldmane/pkg/goldmane/goldmane_filters_test.go b/goldmane/pkg/goldmane/goldmane_filters_test.go new file mode 100644 index 00000000000..8b9a66b827a --- /dev/null +++ b/goldmane/pkg/goldmane/goldmane_filters_test.go @@ -0,0 +1,744 @@ +// Copyright (c) 2025 Tigera, Inc. All rights reserved. + +// 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 goldmane_test + +import ( + "fmt" + "testing" + + . "github.com/onsi/gomega" + + "github.com/projectcalico/calico/goldmane/pkg/goldmane" + "github.com/projectcalico/calico/goldmane/pkg/testutils" + "github.com/projectcalico/calico/goldmane/pkg/types" + "github.com/projectcalico/calico/goldmane/proto" + "github.com/projectcalico/calico/lib/std/time" +) + +func TestFilter(t *testing.T) { + type tc struct { + name string + req *proto.FlowListRequest + numFlows int + check func([]*proto.FlowResult) error + } + + tests := []tc{ + { + name: "SourceName, no sort", + req: &proto.FlowListRequest{Filter: &proto.Filter{SourceNames: []*proto.StringMatch{{Value: "source-1"}}}}, + numFlows: 1, + check: func(flows []*proto.FlowResult) error { + for _, fl := range flows { + if fl.Flow.Key.SourceName != "source-1" { + return fmt.Errorf("Expected SourceName to be source-1, got %s", fl.Flow.Key.SourceName) + } + } + + return nil + }, + }, + + { + name: "SourceName, sort by SourceName", + req: &proto.FlowListRequest{ + Filter: &proto.Filter{SourceNames: []*proto.StringMatch{{Value: "source-1"}}}, + SortBy: []*proto.SortOption{{SortBy: proto.SortBy_SourceName}}, + }, + numFlows: 1, + check: func(flows []*proto.FlowResult) error { + for _, fl := range flows { + if fl.Flow.Key.SourceName != "source-1" { + return fmt.Errorf("Expected SourceName to be source-1, got %s", fl.Flow.Key.SourceName) + } + } + + return nil + }, + }, + + { + name: "SourceNamespace, no sort", + req: &proto.FlowListRequest{ + Filter: &proto.Filter{SourceNamespaces: []*proto.StringMatch{{Value: "source-ns-1"}}}, + }, + numFlows: 1, + check: func(flows []*proto.FlowResult) error { + for _, fl := range flows { + if fl.Flow.Key.SourceNamespace != "source-ns-1" { + return fmt.Errorf("Expected SourceNamespace to be source-ns-1, got %s", fl.Flow.Key.SourceNamespace) + } + } + + return nil + }, + }, + + { + name: "Multiple SourceNamespaces, no sort", + req: &proto.FlowListRequest{ + Filter: &proto.Filter{ + SourceNamespaces: []*proto.StringMatch{{Value: "source-ns-1"}, {Value: "source-ns-2"}}, + }, + }, + numFlows: 2, + }, + + { + name: "DestName, no sort", + req: &proto.FlowListRequest{Filter: &proto.Filter{DestNames: []*proto.StringMatch{{Value: "dest-2"}}}}, + numFlows: 1, + check: func(flows []*proto.FlowResult) error { + for _, fl := range flows { + if fl.Flow.Key.DestName != "dest-2" { + return fmt.Errorf("Expected DestName to be dest-2, got %s", fl.Flow.Key.DestName) + } + } + + return nil + }, + }, + + { + name: "DestName, no sort, no match", + req: &proto.FlowListRequest{Filter: &proto.Filter{DestNames: []*proto.StringMatch{{Value: "dest-100"}}}}, + numFlows: 0, + }, + + { + name: "Port, no sort", + req: &proto.FlowListRequest{Filter: &proto.Filter{DestPorts: []*proto.PortMatch{{Port: 5}}}}, + numFlows: 1, + check: func(flows []*proto.FlowResult) error { + for _, fl := range flows { + if fl.Flow.Key.DestPort != 5 { + return fmt.Errorf("Expected DestPort to be 5, got %d", fl.Flow.Key.DestPort) + } + } + + return nil + }, + }, + + { + name: "Multiple ports, no sort", + req: &proto.FlowListRequest{ + Filter: &proto.Filter{ + DestPorts: []*proto.PortMatch{{Port: 5}, {Port: 6}}, + }, + }, + numFlows: 2, + }, + + { + name: "multiple enforced policy Tiers (OR operator), match", + req: &proto.FlowListRequest{ + Filter: &proto.Filter{ + Policies: []*proto.PolicyMatch{ + {Tier: "tier-5"}, + {Tier: "tier-4"}, + {Tier: "tier-3"}, + }, + }, + }, + numFlows: 3, + check: func(flows []*proto.FlowResult) error { + // Tiers we expect across all flows. + expected := map[string]bool{ + "tier-5": false, + "tier-4": false, + "tier-3": false, + } + + for _, fl := range flows { + if len(fl.Flow.Key.Policies.EnforcedPolicies) == 0 { + return fmt.Errorf("flow has no enforced policies") + } + + tier := fl.Flow.Key.Policies.EnforcedPolicies[0].Tier + + // Flow must belong to one of the expected tiers. + if _, ok := expected[tier]; !ok { + return fmt.Errorf("unexpected tier %q in flow", tier) + } + + // Mark this expected tier as seen. + expected[tier] = true + } + + // Ensure all three tiers were present exactly once. + for tier, found := range expected { + if !found { + return fmt.Errorf("expected tier %s to appear in some flow, but it did not", tier) + } + } + + return nil + }, + }, + + { + name: "multiple pending policy Tiers (OR operator), match", + req: &proto.FlowListRequest{ + Filter: &proto.Filter{ + Policies: []*proto.PolicyMatch{ + {Tier: "pending-tier-5"}, + {Tier: "pending-tier-4"}, + {Tier: "pending-tier-3"}, + }, + }, + }, + numFlows: 3, + check: func(flows []*proto.FlowResult) error { + expected := map[string]bool{ + "pending-tier-5": false, + "pending-tier-4": false, + "pending-tier-3": false, + } + + for _, fl := range flows { + if len(fl.Flow.Key.Policies.PendingPolicies) == 0 { + return fmt.Errorf("flow has no enforced policies") + } + + tier := fl.Flow.Key.Policies.PendingPolicies[0].Tier + + if _, ok := expected[tier]; !ok { + return fmt.Errorf("unexpected tier %q in flow", tier) + } + + expected[tier] = true + } + + for tier, found := range expected { + if !found { + return fmt.Errorf("expected tier %s to appear in some flow, but it did not", tier) + } + } + + return nil + }, + }, + + { + name: "Multiple Tiers", + req: &proto.FlowListRequest{ + Filter: &proto.Filter{ + Policies: []*proto.PolicyMatch{{Tier: "tier-5"}, {Tier: "tier-6"}}, + }, + }, + numFlows: 2, + }, + + { + name: "Full policy match", + req: &proto.FlowListRequest{ + Filter: &proto.Filter{ + Policies: []*proto.PolicyMatch{ + { + Tier: "tier-4", + Name: "name-4", + Namespace: "ns-4", + Action: proto.Action_Allow, + Kind: proto.PolicyKind_CalicoNetworkPolicy, + }, + }, + }, + }, + numFlows: 1, + check: func(results []*proto.FlowResult) error { + foundPolicy := results[0].Flow.Key.Policies.EnforcedPolicies[0] + if foundPolicy.Tier != "tier-4" || foundPolicy.Name != "name-4" || foundPolicy.Namespace != "ns-4" || + foundPolicy.Action != proto.Action_Allow || foundPolicy.Kind != proto.PolicyKind_CalicoNetworkPolicy { + return fmt.Errorf("Policy does not match expected values") + } + return nil + }, + }, + + { + name: "Full Profile enforced policy match", + req: &proto.FlowListRequest{ + Filter: &proto.Filter{ + Policies: []*proto.PolicyMatch{ + { + Tier: "pending-tier-5", + Name: "pending-name-5", + Namespace: "pending-ns-5", + Action: proto.Action_Deny, + Kind: proto.PolicyKind_Profile, + }, + }, + }, + }, + numFlows: 1, + check: func(results []*proto.FlowResult) error { + foundPolicy := results[0].Flow.Key.Policies.PendingPolicies[0] + if foundPolicy.Tier != "pending-tier-5" || foundPolicy.Name != "pending-name-5" || foundPolicy.Namespace != "pending-ns-5" || + foundPolicy.Action != proto.Action_Deny || foundPolicy.Kind != proto.PolicyKind_Profile { + return fmt.Errorf("Policy does not match expected values") + } + return nil + }, + }, + + { + name: "match on policy Kind, no match", + req: &proto.FlowListRequest{ + Filter: &proto.Filter{ + Policies: []*proto.PolicyMatch{ + { + Kind: proto.PolicyKind_GlobalNetworkPolicy, + }, + }, + }, + }, + numFlows: 0, + }, + + { + name: "No Policies filter: match on policy Kind=Profile", + req: &proto.FlowListRequest{ + Filter: &proto.Filter{ + Policies: []*proto.PolicyMatch{ + { + Kind: proto.PolicyKind_Profile, + }, + }, + }, + }, + numFlows: 5, + check: func(results []*proto.FlowResult) error { + foundPolicy := results[0].Flow.Key.Policies.PendingPolicies[0] + if foundPolicy.Kind != proto.PolicyKind_Profile { + return fmt.Errorf("Policy does not match expected values") + } + return nil + }, + }, + + { + name: "match on multiple enforced policy Kinds (OR Operator), match", + req: &proto.FlowListRequest{ + Filter: &proto.Filter{ + Policies: []*proto.PolicyMatch{ + {Kind: proto.PolicyKind_CalicoNetworkPolicy}, + {Kind: proto.PolicyKind_NetworkPolicy}, + }, + }, + }, + numFlows: 10, + check: func(flows []*proto.FlowResult) error { + expected := map[proto.PolicyKind]bool{ + proto.PolicyKind_CalicoNetworkPolicy: false, + proto.PolicyKind_NetworkPolicy: false, + } + + for _, fl := range flows { + if len(fl.Flow.Key.Policies.EnforcedPolicies) == 0 { + return fmt.Errorf("flow has no enforced policies") + } + + kind := fl.Flow.Key.Policies.EnforcedPolicies[0].Kind + + if _, ok := expected[kind]; !ok { + return fmt.Errorf("unexpected kind %q in flow", kind) + } + + expected[kind] = true + } + + for kind, found := range expected { + if !found { + return fmt.Errorf("expected kind %s to appear in some flow, but it did not", kind) + } + } + + return nil + }, + }, + + { + name: "match on multiple pending policy Kinds (OR Operator), match", + req: &proto.FlowListRequest{ + Filter: &proto.Filter{ + Policies: []*proto.PolicyMatch{ + {Kind: proto.PolicyKind_StagedNetworkPolicy}, + {Kind: proto.PolicyKind_Profile}, + }, + }, + }, + numFlows: 10, + check: func(flows []*proto.FlowResult) error { + expected := map[proto.PolicyKind]bool{ + proto.PolicyKind_StagedNetworkPolicy: false, + proto.PolicyKind_Profile: false, + } + + for _, fl := range flows { + pending := fl.Flow.Key.Policies.PendingPolicies + if len(pending) == 0 { + return fmt.Errorf("flow has no pending policies") + } + + for _, p := range pending { + kind := p.Kind + + // Mark this expected kind as observed. + expected[kind] = true + } + } + + // Ensure all expected kinds appeared in at least one flow. + for kind, found := range expected { + if !found { + return fmt.Errorf("expected kind %s to appear in some flow, but it did not", kind) + } + } + + return nil + }, + }, + + { + name: "match on pending policy", + req: &proto.FlowListRequest{ + Filter: &proto.Filter{ + Policies: []*proto.PolicyMatch{ + { + Namespace: "pending-ns-5", + }, + }, + }, + }, + numFlows: 1, + }, + + { + name: "fuzzy match on destination namespace", + req: &proto.FlowListRequest{ + Filter: &proto.Filter{ + DestNamespaces: []*proto.StringMatch{ + { + // This should match all of the flow's destination namespaces. + Value: "dest", + Type: proto.MatchType_Fuzzy, + }, + }, + }, + }, + numFlows: 10, + }, + + { + name: "fuzzy match on destination namespace, no match", + req: &proto.FlowListRequest{ + Filter: &proto.Filter{ + DestNamespaces: []*proto.StringMatch{ + { + Value: "nomatch", + Type: proto.MatchType_Fuzzy, + }, + }, + }, + }, + numFlows: 0, + }, + + { + name: "Reporter filter - Src", + req: &proto.FlowListRequest{ + Filter: &proto.Filter{ + Reporter: proto.Reporter_Src, + }, + }, + numFlows: 5, // Assuming half of the 10 flows have Reporter_Src + check: func(flows []*proto.FlowResult) error { + for _, fl := range flows { + if fl.Flow.Key.Reporter != proto.Reporter_Src { + return fmt.Errorf("Expected Reporter to be Src, got %s", fl.Flow.Key.Reporter) + } + } + + return nil + }, + }, + + { + name: "Reporter filter - Dst", + req: &proto.FlowListRequest{ + Filter: &proto.Filter{ + Reporter: proto.Reporter_Dst, + }, + }, + numFlows: 5, // Assuming half of the 10 flows have Reporter_Dst + check: func(flows []*proto.FlowResult) error { + for _, fl := range flows { + if fl.Flow.Key.Reporter != proto.Reporter_Dst { + return fmt.Errorf("Expected Reporter to be Dst, got %s", fl.Flow.Key.Reporter) + } + } + + return nil + }, + }, + + { + name: "Actions filter - Allow", + req: &proto.FlowListRequest{ + Filter: &proto.Filter{ + Actions: []proto.Action{proto.Action_Allow}, + }, + }, + numFlows: 5, + check: func(flows []*proto.FlowResult) error { + for _, fl := range flows { + if fl.Flow.Key.Action != proto.Action_Allow { + return fmt.Errorf("Expected flow with Allow action") + } + } + + return nil + }, + }, + + { + name: "Actions filter - Deny", + req: &proto.FlowListRequest{ + Filter: &proto.Filter{ + Actions: []proto.Action{proto.Action_Deny}, + }, + }, + numFlows: 5, + check: func(flows []*proto.FlowResult) error { + for _, fl := range flows { + if fl.Flow.Key.Action != proto.Action_Deny { + return fmt.Errorf("Expected flow with Deny action") + } + } + + return nil + }, + }, + + { + name: "PendingActions filter - Allow", + req: &proto.FlowListRequest{ + Filter: &proto.Filter{ + PendingActions: []proto.Action{proto.Action_Allow}, + }, + }, + numFlows: 5, + check: func(flows []*proto.FlowResult) error { + for _, fl := range flows { + hasPendingAllow := false + for _, p := range fl.Flow.Key.Policies.PendingPolicies { + if p.Action == proto.Action_Allow { + hasPendingAllow = true + break + } + } + if !hasPendingAllow { + return fmt.Errorf("Expected at least one pending policy with Allow action") + } + } + + return nil + }, + }, + + { + name: "PendingActions filter - Deny", + req: &proto.FlowListRequest{ + Filter: &proto.Filter{ + PendingActions: []proto.Action{proto.Action_Deny}, + }, + }, + numFlows: 5, // All flows have pending policy with Action_Allow + check: func(flows []*proto.FlowResult) error { + for _, fl := range flows { + hasPendingDeny := false + for _, p := range fl.Flow.Key.Policies.PendingPolicies { + if p.Action == proto.Action_Deny { + hasPendingDeny = true + break + } + } + if !hasPendingDeny { + return fmt.Errorf("Expected at least one pending policy with Deny action") + } + } + + return nil + }, + }, + + { + name: "PendingActions - Deny AND Actions - Deny", + req: &proto.FlowListRequest{ + Filter: &proto.Filter{ + PendingActions: []proto.Action{proto.Action_Deny}, + Actions: []proto.Action{proto.Action_Deny}, + }, + }, + numFlows: 5, + check: func(flows []*proto.FlowResult) error { + for _, fl := range flows { + hasPendingDeny := false + for _, p := range fl.Flow.Key.Policies.PendingPolicies { + if p.Action == proto.Action_Deny { + hasPendingDeny = true + break + } + } + if !hasPendingDeny || fl.Flow.Key.Action != proto.Action_Deny { + return fmt.Errorf("Expected flow with action=Deny and pending_action=Deny") + } + } + + return nil + }, + }, + + { + name: "PendingActions - Allow AND Actions - Allow", + req: &proto.FlowListRequest{ + Filter: &proto.Filter{ + PendingActions: []proto.Action{proto.Action_Allow}, + Actions: []proto.Action{proto.Action_Allow}, + }, + }, + numFlows: 5, + check: func(flows []*proto.FlowResult) error { + for _, fl := range flows { + hasPendingAllow := false + for _, p := range fl.Flow.Key.Policies.PendingPolicies { + if p.Action == proto.Action_Allow { + hasPendingAllow = true + break + } + } + if !hasPendingAllow || fl.Flow.Key.Action != proto.Action_Allow { + return fmt.Errorf("Expected flow with action=Allow and pending_action=Allow") + } + } + + return nil + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Create a clock and rollover controller. + c := newClock(initialNow) + roller := &rolloverController{ + ch: make(chan time.Time), + aggregationWindowSecs: 1, + clock: c, + } + opts := []goldmane.Option{ + goldmane.WithRolloverTime(1 * time.Second), + goldmane.WithRolloverFunc(roller.After), + goldmane.WithNowFunc(c.Now), + } + defer setupTest(t, opts...)() + <-gm.Run(c.Now().Unix()) + + // Create 10 flows, with a mix of fields to filter on. + for i := range 10 { + // Start with a base flow. + fl := testutils.NewRandomFlow(c.Now().Unix() - 1) + + // Configure fields to filter on. + fl.Key.SourceName = fmt.Sprintf("source-%d", i) + fl.Key.SourceNamespace = fmt.Sprintf("source-ns-%d", i) + fl.Key.DestName = fmt.Sprintf("dest-%d", i) + fl.Key.DestNamespace = fmt.Sprintf("dest-ns-%d", i) + fl.Key.Proto = "tcp" + fl.Key.DestPort = int64(i) + fl.Key.Reporter = proto.Reporter_Src + fl.Key.Action = proto.Action_Allow + policyAction := proto.Action_Allow + enforcedPolicyKind := proto.PolicyKind_CalicoNetworkPolicy + pendingPolicyKind := proto.PolicyKind_StagedNetworkPolicy + if i >= 5 { + fl.Key.Reporter = proto.Reporter_Dst + policyAction = proto.Action_Deny + fl.Key.Action = proto.Action_Deny + enforcedPolicyKind = proto.PolicyKind_NetworkPolicy + pendingPolicyKind = proto.PolicyKind_Profile + } + + fl.Key.Policies = &proto.PolicyTrace{ + EnforcedPolicies: []*proto.PolicyHit{ + { + Tier: fmt.Sprintf("tier-%d", i), + Name: fmt.Sprintf("name-%d", i), + Namespace: fmt.Sprintf("ns-%d", i), + Action: policyAction, + Kind: enforcedPolicyKind, + }, + }, + + PendingPolicies: []*proto.PolicyHit{ + { + Tier: fmt.Sprintf("pending-tier-%d", i), + Name: fmt.Sprintf("pending-name-%d", i), + Namespace: fmt.Sprintf("pending-ns-%d", i), + Action: policyAction, + Kind: pendingPolicyKind, + }, + }, + } + + // Send it to goldmane. + gm.Receive(types.ProtoToFlow(fl)) + } + + // Query for flows using the query from the testcase. + var flows []*proto.FlowResult + if tc.numFlows == 0 { + Consistently(func() int { + var results *proto.FlowListResult + results, _ = gm.List(tc.req) + flows = results.Flows + return len(flows) + }, 1*time.Second, retryTime).Should(Equal(0)) + return + } else { + var err error + Eventually(func() error { + var results *proto.FlowListResult + results, err = gm.List(tc.req) + flows = results.Flows + if err != nil { + return err + } + if len(flows) >= tc.numFlows { + return nil + } + return fmt.Errorf("Expected %d flows, got %d", tc.numFlows, len(flows)) + }, waitTimeout, retryTime, "Didn't receive flows").ShouldNot(HaveOccurred()) + + Expect(len(flows)).To(Equal(tc.numFlows), "Expected %d flows, got %d", tc.numFlows, len(flows)) + + if tc.check != nil { + Expect(tc.check(flows)).To(BeNil()) + } + } + }) + } +} diff --git a/goldmane/pkg/goldmane/goldmane_test.go b/goldmane/pkg/goldmane/goldmane_test.go index 8111c5db95a..8fe4b0a46d3 100644 --- a/goldmane/pkg/goldmane/goldmane_test.go +++ b/goldmane/pkg/goldmane/goldmane_test.go @@ -68,11 +68,12 @@ func setupTest(t *testing.T, opts ...goldmane.Option) func() { func ExpectFlowsEqual(t *testing.T, expected, actual *proto.Flow, additionalMsg ...string) { if !googleproto.Equal(expected, actual) { - msg := fmt.Sprintf("\nExpected:\n\t%v\nActual:\n\t%v", expected, actual) + var msg strings.Builder + msg.WriteString(fmt.Sprintf("\nExpected:\n\t%v\nActual:\n\t%v", expected, actual)) for _, m := range additionalMsg { - msg += "\n" + m + msg.WriteString("\n" + m) } - t.Error(msg) + t.Error(msg.String()) } } @@ -1366,13 +1367,29 @@ func TestStreams(t *testing.T) { // Start Goldmane. <-gm.Run(c.Now().Unix()) - // Create many flows. + // Create many flows, tracking how many unique flows we create. + keys := make(map[types.FlowKey]struct{}) for range 5000 { // Ingest some new flow data. fl := testutils.NewRandomFlow(c.Now().Unix() - 5) + keys[*types.ProtoToFlowKey(fl.Key)] = struct{}{} gm.Receive(types.ProtoToFlow(fl)) } + // Wait for flows to be received. Depending on test environment speed, this may take a little while. + // We just want to ensure that the flows are present before we start streaming. If we don't, then + // we may miss them when we start the stream. + Eventually(func() error { + results, err := gm.List(&proto.FlowListRequest{}) + if err != nil { + return err + } + if len(results.Flows) < len(keys) { + return fmt.Errorf("Expected at least %d flows, got %d", len(keys), len(results.Flows)) + } + return nil + }, 3*waitTimeout, retryTime).Should(BeNil(), "Goldmane took too long to receive flows") + // Start a stream, and cancel it immediately after receiving the first flow in order to // "catch it in the act" of iterating flows. for range 10 { @@ -1407,12 +1424,28 @@ func TestStreams(t *testing.T) { <-gm.Run(c.Now().Unix()) // Create many flows. + keys := make(map[types.FlowKey]struct{}) for range 5000 { // Ingest some new flow data. fl := testutils.NewRandomFlow(c.Now().Unix() - 5) + keys[*types.ProtoToFlowKey(fl.Key)] = struct{}{} gm.Receive(types.ProtoToFlow(fl)) } + // Wait for flows to be received. Depending on test environment speed, this may take a little while. + // We just want to ensure that the flows are present before we start streaming. If we don't, then + // we may miss them when we start the stream. + Eventually(func() error { + results, err := gm.List(&proto.FlowListRequest{}) + if err != nil { + return err + } + if len(results.Flows) < len(keys) { + return fmt.Errorf("Expected at least %d flows, got %d", len(keys), len(results.Flows)) + } + return nil + }, 3*waitTimeout, retryTime).Should(BeNil(), "Goldmane took too long to receive flows") + // Start 10 concurrent streams that will act at the same time. var streams []stream.Stream for range 10 { @@ -1500,498 +1533,6 @@ func TestSortOrder(t *testing.T) { } } -func TestFilter(t *testing.T) { - type tc struct { - name string - req *proto.FlowListRequest - numFlows int - check func(*proto.FlowResult) error - } - - tests := []tc{ - { - name: "SourceName, no sort", - req: &proto.FlowListRequest{Filter: &proto.Filter{SourceNames: []*proto.StringMatch{{Value: "source-1"}}}}, - numFlows: 1, - check: func(fl *proto.FlowResult) error { - if fl.Flow.Key.SourceName != "source-1" { - return fmt.Errorf("Expected SourceName to be source-1, got %s", fl.Flow.Key.SourceName) - } - return nil - }, - }, - - { - name: "SourceName, sort by SourceName", - req: &proto.FlowListRequest{ - Filter: &proto.Filter{SourceNames: []*proto.StringMatch{{Value: "source-1"}}}, - SortBy: []*proto.SortOption{{SortBy: proto.SortBy_SourceName}}, - }, - numFlows: 1, - check: func(fl *proto.FlowResult) error { - if fl.Flow.Key.SourceName != "source-1" { - return fmt.Errorf("Expected SourceName to be source-1, got %s", fl.Flow.Key.SourceName) - } - return nil - }, - }, - - { - name: "SourceNamespace, no sort", - req: &proto.FlowListRequest{ - Filter: &proto.Filter{SourceNamespaces: []*proto.StringMatch{{Value: "source-ns-1"}}}, - }, - numFlows: 1, - check: func(fl *proto.FlowResult) error { - if fl.Flow.Key.SourceNamespace != "source-ns-1" { - return fmt.Errorf("Expected SourceNamespace to be source-ns-1, got %s", fl.Flow.Key.SourceNamespace) - } - return nil - }, - }, - - { - name: "Multiple SourceNamespaces, no sort", - req: &proto.FlowListRequest{ - Filter: &proto.Filter{ - SourceNamespaces: []*proto.StringMatch{{Value: "source-ns-1"}, {Value: "source-ns-2"}}, - }, - }, - numFlows: 2, - }, - - { - name: "DestName, no sort", - req: &proto.FlowListRequest{Filter: &proto.Filter{DestNames: []*proto.StringMatch{{Value: "dest-2"}}}}, - numFlows: 1, - check: func(fl *proto.FlowResult) error { - if fl.Flow.Key.DestName != "dest-2" { - return fmt.Errorf("Expected DestName to be dest-2, got %s", fl.Flow.Key.DestName) - } - return nil - }, - }, - - { - name: "DestName, no sort, no match", - req: &proto.FlowListRequest{Filter: &proto.Filter{DestNames: []*proto.StringMatch{{Value: "dest-100"}}}}, - numFlows: 0, - }, - - { - name: "Port, no sort", - req: &proto.FlowListRequest{Filter: &proto.Filter{DestPorts: []*proto.PortMatch{{Port: 5}}}}, - numFlows: 1, - check: func(fl *proto.FlowResult) error { - if fl.Flow.Key.DestPort != 5 { - return fmt.Errorf("Expected DestPort to be 5, got %d", fl.Flow.Key.DestPort) - } - return nil - }, - }, - - { - name: "Multiple ports, no sort", - req: &proto.FlowListRequest{ - Filter: &proto.Filter{ - DestPorts: []*proto.PortMatch{{Port: 5}, {Port: 6}}, - }, - }, - numFlows: 2, - }, - - { - name: "Tier", - req: &proto.FlowListRequest{ - Filter: &proto.Filter{ - Policies: []*proto.PolicyMatch{{Tier: "tier-5"}}, - }, - }, - numFlows: 1, - check: func(fl *proto.FlowResult) error { - if fl.Flow.Key.Policies.EnforcedPolicies[0].Tier != "tier-5" { - return fmt.Errorf("Expected Tier to be tier-5, got %s", fl.Flow.Key.Policies.EnforcedPolicies[0].Tier) - } - return nil - }, - }, - - { - name: "Multiple Tiers", - req: &proto.FlowListRequest{ - Filter: &proto.Filter{ - Policies: []*proto.PolicyMatch{{Tier: "tier-5"}, {Tier: "tier-6"}}, - }, - }, - numFlows: 2, - }, - - { - name: "Full policy match", - req: &proto.FlowListRequest{ - Filter: &proto.Filter{ - Policies: []*proto.PolicyMatch{ - { - Tier: "tier-5", - Name: "name-5", - Namespace: "ns-5", - Action: proto.Action_Allow, - Kind: proto.PolicyKind_CalicoNetworkPolicy, - }, - }, - }, - }, - numFlows: 1, - }, - - { - name: "match on policy Kind, no match", - req: &proto.FlowListRequest{ - Filter: &proto.Filter{ - Policies: []*proto.PolicyMatch{ - { - Kind: proto.PolicyKind_GlobalNetworkPolicy, - }, - }, - }, - }, - numFlows: 0, - }, - - { - name: "match on policy Kind, match", - req: &proto.FlowListRequest{ - Filter: &proto.Filter{ - Policies: []*proto.PolicyMatch{ - { - Kind: proto.PolicyKind_CalicoNetworkPolicy, - }, - }, - }, - }, - numFlows: 10, - }, - - { - name: "match on pending policy", - req: &proto.FlowListRequest{ - Filter: &proto.Filter{ - Policies: []*proto.PolicyMatch{ - { - Namespace: "pending-ns-5", - }, - }, - }, - }, - numFlows: 1, - }, - - { - name: "fuzzy match on destination namespace", - req: &proto.FlowListRequest{ - Filter: &proto.Filter{ - DestNamespaces: []*proto.StringMatch{ - { - // This should match all of the flow's destination namespaces. - Value: "dest", - Type: proto.MatchType_Fuzzy, - }, - }, - }, - }, - numFlows: 10, - }, - - { - name: "fuzzy match on destination namespace, no match", - req: &proto.FlowListRequest{ - Filter: &proto.Filter{ - DestNamespaces: []*proto.StringMatch{ - { - Value: "nomatch", - Type: proto.MatchType_Fuzzy, - }, - }, - }, - }, - numFlows: 0, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - // Create a clock and rollover controller. - c := newClock(initialNow) - roller := &rolloverController{ - ch: make(chan time.Time), - aggregationWindowSecs: 1, - clock: c, - } - opts := []goldmane.Option{ - goldmane.WithRolloverTime(1 * time.Second), - goldmane.WithRolloverFunc(roller.After), - goldmane.WithNowFunc(c.Now), - } - defer setupTest(t, opts...)() - <-gm.Run(c.Now().Unix()) - - // Create 10 flows, with a mix of fields to filter on. - for i := range 10 { - // Start with a base flow. - fl := testutils.NewRandomFlow(c.Now().Unix() - 1) - - // Configure fields to filter on. - fl.Key.SourceName = fmt.Sprintf("source-%d", i) - fl.Key.SourceNamespace = fmt.Sprintf("source-ns-%d", i) - fl.Key.DestName = fmt.Sprintf("dest-%d", i) - fl.Key.DestNamespace = fmt.Sprintf("dest-ns-%d", i) - fl.Key.Proto = "tcp" - fl.Key.DestPort = int64(i) - fl.Key.Policies = &proto.PolicyTrace{ - EnforcedPolicies: []*proto.PolicyHit{ - { - Tier: fmt.Sprintf("tier-%d", i), - Name: fmt.Sprintf("name-%d", i), - Namespace: fmt.Sprintf("ns-%d", i), - Action: proto.Action_Allow, - Kind: proto.PolicyKind_CalicoNetworkPolicy, - }, - }, - PendingPolicies: []*proto.PolicyHit{ - { - Tier: fmt.Sprintf("pending-tier-%d", i), - Name: fmt.Sprintf("pending-name-%d", i), - Namespace: fmt.Sprintf("pending-ns-%d", i), - Action: proto.Action_Allow, - Kind: proto.PolicyKind_CalicoNetworkPolicy, - }, - }, - } - - // Send it to goldmane. - gm.Receive(types.ProtoToFlow(fl)) - } - - // Query for flows using the query from the testcase. - var flows []*proto.FlowResult - if tc.numFlows == 0 { - Consistently(func() int { - var results *proto.FlowListResult - results, _ = gm.List(tc.req) - flows = results.Flows - return len(flows) - }, 1*time.Second, retryTime).Should(Equal(0)) - return - } else { - var err error - Eventually(func() error { - var results *proto.FlowListResult - results, err = gm.List(tc.req) - flows = results.Flows - if err != nil { - return err - } - if len(flows) >= tc.numFlows { - return nil - } - return fmt.Errorf("Expected %d flows, got %d", tc.numFlows, len(flows)) - }, waitTimeout, retryTime, "Didn't receive flows").ShouldNot(HaveOccurred()) - - Expect(len(flows)).To(Equal(tc.numFlows), "Expected %d flows, got %d", tc.numFlows, len(flows)) - - if tc.check != nil { - for _, fl := range flows { - Expect(tc.check(fl)).To(BeNil()) - } - } - } - }) - } -} - -func TestFilterHints(t *testing.T) { - type tc struct { - name string - req *proto.FilterHintsRequest - numResp int - check func([]*proto.FilterHint) error - } - - tests := []tc{ - { - name: "SourceName, no filters", - req: &proto.FilterHintsRequest{Type: proto.FilterType_FilterTypeSourceName}, - numResp: 10, - check: func(hints []*proto.FilterHint) error { - for i, hint := range hints { - if hint.Value != fmt.Sprintf("source-%d", i) { - return fmt.Errorf("Expected SourceName to be source-%d, got %s", i, hint.Value) - } - } - return nil - }, - }, - - { - name: "SourceName, with SourceName filter", - req: &proto.FilterHintsRequest{ - Type: proto.FilterType_FilterTypeSourceName, - Filter: &proto.Filter{SourceNames: []*proto.StringMatch{{Value: "source-1"}}}, - }, - numResp: 1, - }, - - { - name: "Tier, no filters", - req: &proto.FilterHintsRequest{ - Type: proto.FilterType_FilterTypePolicyTier, - }, - numResp: 10, - }, - { - name: "Policy name, no filters", - req: &proto.FilterHintsRequest{ - Type: proto.FilterType_FilterTypePolicyName, - }, - numResp: 10, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - // Create a clock and rollover controller. - c := newClock(initialNow) - roller := &rolloverController{ - ch: make(chan time.Time), - aggregationWindowSecs: 1, - clock: c, - } - opts := []goldmane.Option{ - goldmane.WithRolloverTime(1 * time.Second), - goldmane.WithRolloverFunc(roller.After), - goldmane.WithNowFunc(c.Now), - } - defer setupTest(t, opts...)() - <-gm.Run(c.Now().Unix()) - - // Create 10 flows, with a mix of fields to filter on. - for i := range 10 { - // Start with a base flow. - fl := testutils.NewRandomFlow(c.Now().Unix() - 1) - - // Configure fields to filter on. - fl.Key.SourceName = fmt.Sprintf("source-%d", i) - fl.Key.SourceNamespace = fmt.Sprintf("source-ns-%d", i) - fl.Key.DestName = fmt.Sprintf("dest-%d", i) - fl.Key.DestNamespace = fmt.Sprintf("dest-ns-%d", i) - fl.Key.Proto = "tcp" - fl.Key.DestPort = int64(i) - fl.Key.Policies = &proto.PolicyTrace{ - EnforcedPolicies: []*proto.PolicyHit{ - { - Name: fmt.Sprintf("name-%d", i), - Tier: fmt.Sprintf("tier-%d", i), - }, - }, - } - - // Send it to goldmane. - gm.Receive(types.ProtoToFlow(fl)) - } - - // Wait for all flows to be received. - Eventually(func() bool { - results, _ := gm.List(&proto.FlowListRequest{}) - return len(results.Flows) == 10 - }, waitTimeout, retryTime, "Didn't receive all flows").Should(BeTrue()) - - // Query for hints using the query from the testcase. - results, err := gm.Hints(tc.req) - require.NoError(t, err) - - // Verify the hints. - require.Len(t, results.Hints, tc.numResp, "Expected %d hints, got %d: %+v", tc.numResp, len(results.Hints), results.Hints) - - if tc.check != nil { - require.NoError(t, tc.check(results.Hints), fmt.Sprintf("Hints check failed on hints: %+v", results.Hints)) - } - }) - } - - // Run some tests against EndOfTier flows. - eotTests := []tc{ - { - name: "EndOfTier, Tier, no filters", - req: &proto.FilterHintsRequest{ - Type: proto.FilterType_FilterTypePolicyTier, - }, - numResp: 10, - }, - } - - for _, tc := range eotTests { - t.Run(tc.name, func(t *testing.T) { - // Create a clock and rollover controller. - c := newClock(initialNow) - roller := &rolloverController{ - ch: make(chan time.Time), - aggregationWindowSecs: 1, - clock: c, - } - opts := []goldmane.Option{ - goldmane.WithRolloverTime(1 * time.Second), - goldmane.WithRolloverFunc(roller.After), - goldmane.WithNowFunc(c.Now), - } - defer setupTest(t, opts...)() - <-gm.Run(c.Now().Unix()) - - // Create 10 flows, with a mix of fields to filter on. - for i := range 10 { - // Start with a base flow. - fl := testutils.NewRandomFlow(c.Now().Unix() - 1) - - // Configure fields to filter on. - fl.Key.SourceName = fmt.Sprintf("source-%d", i) - fl.Key.SourceNamespace = fmt.Sprintf("source-ns-%d", i) - fl.Key.DestName = fmt.Sprintf("dest-%d", i) - fl.Key.DestNamespace = fmt.Sprintf("dest-ns-%d", i) - fl.Key.Proto = "tcp" - fl.Key.DestPort = int64(i) - fl.Key.Policies = &proto.PolicyTrace{ - EnforcedPolicies: []*proto.PolicyHit{ - { - Trigger: &proto.PolicyHit{ - Tier: fmt.Sprintf("tier-%d", i), - }, - }, - }, - } - - // Send it to goldmane. - gm.Receive(types.ProtoToFlow(fl)) - } - - // Wait for all flows to be received. - Eventually(func() bool { - results, _ := gm.List(&proto.FlowListRequest{}) - return len(results.Flows) == 10 - }, waitTimeout, retryTime, "Didn't receive all flows").Should(BeTrue()) - - // Query for hints using the query from the testcase. - results, err := gm.Hints(tc.req) - require.NoError(t, err) - - // Verify the hints. - require.Len(t, results.Hints, tc.numResp, "Expected %d hints, got %d: %+v", tc.numResp, len(results.Hints), results.Hints) - - if tc.check != nil { - require.NoError(t, tc.check(results.Hints), fmt.Sprintf("Hints check failed on hints: %+v", results.Hints)) - } - }) - } -} - func TestStatistics(t *testing.T) { var roller *rolloverController diff --git a/goldmane/pkg/storage/bucket.go b/goldmane/pkg/storage/bucket.go index 05802d78049..cdbd9f7d508 100644 --- a/goldmane/pkg/storage/bucket.go +++ b/goldmane/pkg/storage/bucket.go @@ -121,10 +121,9 @@ func (b *AggregationBucket) Reset(start, end int64) { b.Flows = set.New[*DiachronicFlow]() } else { // Otherwise, use the existing set but clear it. - b.Flows.Iter(func(item *DiachronicFlow) error { + for item := range b.Flows.All() { b.Flows.Discard(item) - return nil - }) + } } } @@ -145,12 +144,11 @@ func (b *AggregationBucket) Iter(fn func(FlowBuilder) bool) { return } - b.Flows.Iter(func(d *DiachronicFlow) error { + for d := range b.Flows.All() { if fn(NewDeferredFlowBuilder(d, b.StartTime, b.EndTime)) { - return set.StopIteration + break } - return nil - }) + } } func (b *AggregationBucket) QueryStatistics(q *proto.StatisticsRequest) map[StatisticsKey]*counts { diff --git a/goldmane/pkg/storage/bucket_ring.go b/goldmane/pkg/storage/bucket_ring.go index f11c873effe..165209234c9 100644 --- a/goldmane/pkg/storage/bucket_ring.go +++ b/goldmane/pkg/storage/bucket_ring.go @@ -190,6 +190,12 @@ func extractPolicyFieldsFromFlowKey(getField func(*proto.PolicyHit) string) func policyTrace := types.FlowLogPolicyToProto(key.Policies()) for _, policyList := range [][]*proto.PolicyHit{policyTrace.EnforcedPolicies, policyTrace.PendingPolicies} { for _, p := range policyList { + // Skip Profiles in hints, as these aren't a real kind in Kubernetes clusters - these are + // logically equivalent to "default allow" or "no policies matched". + if p.Kind == proto.PolicyKind_Profile { + continue + } + val := getField(p) if p.Trigger != nil { // EndOfTier policies store the tier in the trigger. @@ -228,6 +234,18 @@ func (r *BucketRing) FilterHints(req *proto.FilterHintsRequest) ([]string, *type return p.Name }, ) + case proto.FilterType_FilterTypePolicyKind: + valueFunc = extractPolicyFieldsFromFlowKey( + func(p *proto.PolicyHit) string { + return p.Kind.String() + }, + ) + case proto.FilterType_FilterTypePolicyNamespace: + valueFunc = extractPolicyFieldsFromFlowKey( + func(p *proto.PolicyHit) string { + return p.Namespace + }, + ) default: return nil, nil, fmt.Errorf("unsupported filter type '%s'", req.Type.String()) } @@ -281,10 +299,9 @@ func (r *BucketRing) Rollover(sink Sink) int64 { // Capture the flows from the bucket before we clear it. flows := set.New[*DiachronicFlow]() if r.buckets[r.headIndex].Flows != nil { - r.buckets[r.headIndex].Flows.Iter(func(d *DiachronicFlow) error { + for d := range r.buckets[r.headIndex].Flows.All() { flows.Add(d) - return nil - }) + } } // Clear data from the bucket that is now the head. The start time of the new bucket @@ -293,7 +310,7 @@ func (r *BucketRing) Rollover(sink Sink) int64 { // Update DiachronicFlows. We need to remove any windows from the DiachronicFlows that have expired. // Find the oldest bucket's start time and remove any data from the DiachronicFlows that is older than that. - flows.Iter(func(d *DiachronicFlow) error { + for d := range flows.All() { // Rollover the DiachronicFlow. This will remove any expired data from it. d.Rollover(r.BeginningOfHistory()) @@ -308,8 +325,7 @@ func (r *BucketRing) Rollover(sink Sink) int64 { } delete(r.diachronics, d.Key) } - return nil - }) + } // Emit flows to the sink. if sink != nil { @@ -381,10 +397,9 @@ func (r *BucketRing) FlowSet(startGt, startLt int64) set.Set[*DiachronicFlow] { if (startGt == 0 || b.StartTime >= startGt) && (startLt == 0 || b.StartTime <= startLt) { - b.Flows.Iter(func(d *DiachronicFlow) error { + for d := range b.Flows.All() { flows.Add(d) - return nil - }) + } } } return flows @@ -565,7 +580,7 @@ func (r *BucketRing) maybeBuildFlowCollection(startIndex, endIndex int) *FlowCol }) // Use the DiachronicFlow data to build the aggregated flows. - keys.Iter(func(d *DiachronicFlow) error { + for d := range keys.All() { if logrus.IsLevelEnabled(logrus.DebugLevel) { logrus.WithFields(d.Key.Fields()).WithFields(logrus.Fields{ "start": startTime, @@ -575,8 +590,7 @@ func (r *BucketRing) maybeBuildFlowCollection(startIndex, endIndex int) *FlowCol if f := d.Aggregate(startTime, endTime); f != nil { flows.Flows = append(flows.Flows, *f) } - return nil - }) + } // The next bucket that will trigger an emission is the one after the end of the current window. // Log this for debugging purposes. @@ -781,13 +795,12 @@ func (r *BucketRing) IterFlows(start, end int64, f func(d *DiachronicFlow, s, e stopBucketIteration := false // Iterate through all of the flows in the bucket. - b.Flows.Iter(func(d *DiachronicFlow) error { + for d := range b.Flows.All() { if err := f(d, b.StartTime, b.EndTime); errors.Is(err, ErrStopBucketIteration) { stopBucketIteration = true - return set.StopIteration + break } - return nil - }) + } if stopBucketIteration { // If the inner function indicates to stop iterating, return a StopBucketIteration error diff --git a/goldmane/pkg/storage/ring_index.go b/goldmane/pkg/storage/ring_index.go index 355cf5d0ec8..d446adaec0f 100644 --- a/goldmane/pkg/storage/ring_index.go +++ b/goldmane/pkg/storage/ring_index.go @@ -53,7 +53,7 @@ func (a *RingIndex) List(opts IndexFindOpts) ([]*types.Flow, types.ListMeta) { // Aggregate the relevant DiachronicFlows across the time range. flowsByKey := map[types.FlowKey]*types.Flow{} - keys.Iter(func(d *DiachronicFlow) error { + for d := range keys.All() { logCtx := logrus.WithField("id", d.ID) if logrus.IsLevelEnabled(logrus.DebugLevel) { // Unpacking the key is a bit expensive, so only do it in debug mode. @@ -68,8 +68,7 @@ func (a *RingIndex) List(opts IndexFindOpts) ([]*types.Flow, types.ListMeta) { flowsByKey[*flow.Key] = flow } } - return nil - }) + } // Convert the map to a slice. flows := []*types.Flow{} @@ -134,7 +133,7 @@ func (a *RingIndex) FilterValueSet(valueFunc func(*types.FlowKey) []string, opts // Aggregate the relevant DiachronicFlows across the time range. var values []string seen := set.New[string]() - keys.Iter(func(d *DiachronicFlow) error { + for d := range keys.All() { if logrus.IsLevelEnabled(logrus.DebugLevel) { logrus.WithFields(d.Key.Fields()). WithFields(logrus.Fields{"filter": opts.filter}). @@ -156,8 +155,7 @@ func (a *RingIndex) FilterValueSet(valueFunc func(*types.FlowKey) []string, opts } } } - return nil - }) + } // Sort the values alphanumerically. sort.Strings(values) diff --git a/goldmane/pkg/types/filters.go b/goldmane/pkg/types/filters.go index fae7a4582c4..d5186442b27 100644 --- a/goldmane/pkg/types/filters.go +++ b/goldmane/pkg/types/filters.go @@ -70,6 +70,8 @@ func Matches(filter *proto.Filter, key *FlowKey) bool { &stringComparison{filter: filter.DestNamespaces, genVals: namespaces(key.DestNamespace)}, &stringComparison{filter: filter.Protocols, genVals: func() []string { return []string{key.Proto()} }}, &actionMatch{filter: filter.Actions, key: key}, + &pendingActionMatch{filter: filter.PendingActions, key: key}, + &reporterMatch{filter: filter.Reporter, key: key}, &portComparison{filter: filter.DestPorts, key: key}, &policyComparison{filter: filter.Policies, key: key}, } @@ -100,6 +102,40 @@ func (a *actionMatch) matches() bool { return slices.Contains(a.filter, a.key.Action()) } +type pendingActionMatch struct { + filter []proto.Action + key *FlowKey +} + +func (a *pendingActionMatch) matches() bool { + if len(a.filter) == 0 { + return true + } + + policyTrace := FlowLogPolicyToProto(a.key.Policies()) + + for _, hit := range policyTrace.PendingPolicies { + if slices.Contains(a.filter, hit.Action) { + return true + } + } + + return false +} + +type reporterMatch struct { + filter proto.Reporter + key *FlowKey +} + +func (r *reporterMatch) matches() bool { + if r.filter == proto.Reporter_ReporterUnspecified { + // No filter value specified, so this comparison matches. + return true + } + return r.filter == r.key.Reporter() +} + type portComparison struct { filter []*proto.PortMatch key *FlowKey diff --git a/goldmane/proto/api.pb.go b/goldmane/proto/api.pb.go index 24ab4f215f1..1c33c8c8043 100644 --- a/goldmane/proto/api.pb.go +++ b/goldmane/proto/api.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 -// protoc v6.32.0 +// protoc-gen-go v1.36.11 +// protoc v6.33.4 // source: api.proto package proto @@ -33,6 +33,8 @@ const ( FilterType_FilterTypeSourceNamespace FilterType = 4 FilterType_FilterTypePolicyTier FilterType = 5 FilterType_FilterTypePolicyName FilterType = 6 + FilterType_FilterTypePolicyKind FilterType = 7 + FilterType_FilterTypePolicyNamespace FilterType = 8 ) // Enum value maps for FilterType. @@ -45,6 +47,8 @@ var ( 4: "FilterTypeSourceNamespace", 5: "FilterTypePolicyTier", 6: "FilterTypePolicyName", + 7: "FilterTypePolicyKind", + 8: "FilterTypePolicyNamespace", } FilterType_value = map[string]int32{ "FilterTypeUnspecified": 0, @@ -54,6 +58,8 @@ var ( "FilterTypeSourceNamespace": 4, "FilterTypePolicyTier": 5, "FilterTypePolicyName": 6, + "FilterTypePolicyKind": 7, + "FilterTypePolicyNamespace": 8, } ) @@ -196,9 +202,8 @@ const ( PolicyKind_StagedGlobalNetworkPolicy PolicyKind = 4 PolicyKind_StagedKubernetesNetworkPolicy PolicyKind = 5 // Native Kubernetes types. - PolicyKind_NetworkPolicy PolicyKind = 6 - PolicyKind_AdminNetworkPolicy PolicyKind = 7 - PolicyKind_BaselineAdminNetworkPolicy PolicyKind = 8 + PolicyKind_NetworkPolicy PolicyKind = 6 + PolicyKind_ClusterNetworkPolicy PolicyKind = 11 // Calico Profiles. PolicyKind_Profile PolicyKind = 9 PolicyKind_EndOfTier PolicyKind = 10 @@ -214,8 +219,7 @@ var ( 4: "StagedGlobalNetworkPolicy", 5: "StagedKubernetesNetworkPolicy", 6: "NetworkPolicy", - 7: "AdminNetworkPolicy", - 8: "BaselineAdminNetworkPolicy", + 11: "ClusterNetworkPolicy", 9: "Profile", 10: "EndOfTier", } @@ -227,8 +231,7 @@ var ( "StagedGlobalNetworkPolicy": 4, "StagedKubernetesNetworkPolicy": 5, "NetworkPolicy": 6, - "AdminNetworkPolicy": 7, - "BaselineAdminNetworkPolicy": 8, + "ClusterNetworkPolicy": 11, "Profile": 9, "EndOfTier": 10, } @@ -1157,9 +1160,13 @@ type Filter struct { // Actions filters on the action field. Combined using logical OR. Actions []Action `protobuf:"varint,7,rep,packed,name=actions,proto3,enum=goldmane.Action" json:"actions,omitempty"` // Policies matches on policy fields. Combined using logical OR. - Policies []*PolicyMatch `protobuf:"bytes,8,rep,name=policies,proto3" json:"policies,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Policies []*PolicyMatch `protobuf:"bytes,8,rep,name=policies,proto3" json:"policies,omitempty"` + // Reporter filters on the reporter field. + Reporter Reporter `protobuf:"varint,9,opt,name=reporter,proto3,enum=goldmane.Reporter" json:"reporter,omitempty"` + // Pending/Staged Actions filters on the action field. Combined using logical OR. + PendingActions []Action `protobuf:"varint,10,rep,packed,name=pending_actions,json=pendingActions,proto3,enum=goldmane.Action" json:"pending_actions,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Filter) Reset() { @@ -1248,6 +1255,20 @@ func (x *Filter) GetPolicies() []*PolicyMatch { return nil } +func (x *Filter) GetReporter() Reporter { + if x != nil { + return x.Reporter + } + return Reporter_ReporterUnspecified +} + +func (x *Filter) GetPendingActions() []Action { + if x != nil { + return x.PendingActions + } + return nil +} + type StringMatch struct { state protoimpl.MessageState `protogen:"open.v1"` Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` @@ -2346,7 +2367,7 @@ const file_api_proto_rawDesc = "" + "\n" + "FlowResult\x12\x0e\n" + "\x02id\x18\x01 \x01(\x03R\x02id\x12\"\n" + - "\x04flow\x18\x02 \x01(\v2\x0e.goldmane.FlowR\x04flow\"\xc4\x03\n" + + "\x04flow\x18\x02 \x01(\v2\x0e.goldmane.FlowR\x04flow\"\xaf\x04\n" + "\x06Filter\x128\n" + "\fsource_names\x18\x01 \x03(\v2\x15.goldmane.StringMatchR\vsourceNames\x12B\n" + "\x11source_namespaces\x18\x02 \x03(\v2\x15.goldmane.StringMatchR\x10sourceNamespaces\x124\n" + @@ -2357,7 +2378,10 @@ const file_api_proto_rawDesc = "" + "\n" + "dest_ports\x18\x06 \x03(\v2\x13.goldmane.PortMatchR\tdestPorts\x12*\n" + "\aactions\x18\a \x03(\x0e2\x10.goldmane.ActionR\aactions\x121\n" + - "\bpolicies\x18\b \x03(\v2\x15.goldmane.PolicyMatchR\bpolicies\"L\n" + + "\bpolicies\x18\b \x03(\v2\x15.goldmane.PolicyMatchR\bpolicies\x12.\n" + + "\breporter\x18\t \x01(\x0e2\x12.goldmane.ReporterR\breporter\x129\n" + + "\x0fpending_actions\x18\n" + + " \x03(\x0e2\x10.goldmane.ActionR\x0ependingActions\"L\n" + "\vStringMatch\x12\x14\n" + "\x05value\x18\x01 \x01(\tR\x05value\x12'\n" + "\x04type\x18\x02 \x01(\x0e2\x13.goldmane.MatchTypeR\x04type\"\x1f\n" + @@ -2450,7 +2474,7 @@ const file_api_proto_rawDesc = "" + "\n" + "passed_out\x18\n" + " \x03(\x03R\tpassedOut\x12\f\n" + - "\x01x\x18\v \x03(\x03R\x01x*\xc9\x01\n" + + "\x01x\x18\v \x03(\x03R\x01x*\x82\x02\n" + "\n" + "FilterType\x12\x19\n" + "\x15FilterTypeUnspecified\x10\x00\x12\x16\n" + @@ -2459,7 +2483,9 @@ const file_api_proto_rawDesc = "" + "\x17FilterTypeDestNamespace\x10\x03\x12\x1d\n" + "\x19FilterTypeSourceNamespace\x10\x04\x12\x18\n" + "\x14FilterTypePolicyTier\x10\x05\x12\x18\n" + - "\x14FilterTypePolicyName\x10\x06*>\n" + + "\x14FilterTypePolicyName\x10\x06\x12\x18\n" + + "\x14FilterTypePolicyKind\x10\a\x12\x1d\n" + + "\x19FilterTypePolicyNamespace\x10\b*>\n" + "\x06Action\x12\x15\n" + "\x11ActionUnspecified\x10\x00\x12\t\n" + "\x05Allow\x10\x01\x12\b\n" + @@ -2467,7 +2493,7 @@ const file_api_proto_rawDesc = "" + "\x04Pass\x10\x03*!\n" + "\tMatchType\x12\t\n" + "\x05Exact\x10\x00\x12\t\n" + - "\x05Fuzzy\x10\x01*\x95\x02\n" + + "\x05Fuzzy\x10\x01*\x83\x02\n" + "\n" + "PolicyKind\x12\x13\n" + "\x0fKindUnspecified\x10\x00\x12\x17\n" + @@ -2476,12 +2502,11 @@ const file_api_proto_rawDesc = "" + "\x13StagedNetworkPolicy\x10\x03\x12\x1d\n" + "\x19StagedGlobalNetworkPolicy\x10\x04\x12!\n" + "\x1dStagedKubernetesNetworkPolicy\x10\x05\x12\x11\n" + - "\rNetworkPolicy\x10\x06\x12\x16\n" + - "\x12AdminNetworkPolicy\x10\a\x12\x1e\n" + - "\x1aBaselineAdminNetworkPolicy\x10\b\x12\v\n" + + "\rNetworkPolicy\x10\x06\x12\x18\n" + + "\x14ClusterNetworkPolicy\x10\v\x12\v\n" + "\aProfile\x10\t\x12\r\n" + "\tEndOfTier\x10\n" + - "*v\n" + + "\"\x04\b\a\x10\a\"\x04\b\b\x10\b*v\n" + "\x06SortBy\x12\b\n" + "\x04Time\x10\x00\x12\f\n" + "\bDestName\x10\x01\x12\x11\n" + @@ -2593,44 +2618,46 @@ var file_api_proto_depIdxs = []int32{ 20, // 15: goldmane.Filter.dest_ports:type_name -> goldmane.PortMatch 1, // 16: goldmane.Filter.actions:type_name -> goldmane.Action 22, // 17: goldmane.Filter.policies:type_name -> goldmane.PolicyMatch - 2, // 18: goldmane.StringMatch.type:type_name -> goldmane.MatchType - 4, // 19: goldmane.SortOption.sort_by:type_name -> goldmane.SortBy - 3, // 20: goldmane.PolicyMatch.kind:type_name -> goldmane.PolicyKind - 1, // 21: goldmane.PolicyMatch.action:type_name -> goldmane.Action - 26, // 22: goldmane.FlowUpdate.flow:type_name -> goldmane.Flow - 5, // 23: goldmane.FlowKey.source_type:type_name -> goldmane.EndpointType - 5, // 24: goldmane.FlowKey.dest_type:type_name -> goldmane.EndpointType - 6, // 25: goldmane.FlowKey.reporter:type_name -> goldmane.Reporter - 1, // 26: goldmane.FlowKey.action:type_name -> goldmane.Action - 27, // 27: goldmane.FlowKey.policies:type_name -> goldmane.PolicyTrace - 25, // 28: goldmane.Flow.Key:type_name -> goldmane.FlowKey - 28, // 29: goldmane.PolicyTrace.enforced_policies:type_name -> goldmane.PolicyHit - 28, // 30: goldmane.PolicyTrace.pending_policies:type_name -> goldmane.PolicyHit - 3, // 31: goldmane.PolicyHit.kind:type_name -> goldmane.PolicyKind - 1, // 32: goldmane.PolicyHit.action:type_name -> goldmane.Action - 28, // 33: goldmane.PolicyHit.trigger:type_name -> goldmane.PolicyHit - 7, // 34: goldmane.StatisticsRequest.type:type_name -> goldmane.StatisticType - 8, // 35: goldmane.StatisticsRequest.group_by:type_name -> goldmane.StatisticsGroupBy - 22, // 36: goldmane.StatisticsRequest.policy_match:type_name -> goldmane.PolicyMatch - 28, // 37: goldmane.StatisticsResult.policy:type_name -> goldmane.PolicyHit - 9, // 38: goldmane.StatisticsResult.direction:type_name -> goldmane.RuleDirection - 8, // 39: goldmane.StatisticsResult.group_by:type_name -> goldmane.StatisticsGroupBy - 7, // 40: goldmane.StatisticsResult.type:type_name -> goldmane.StatisticType - 10, // 41: goldmane.Flows.List:input_type -> goldmane.FlowListRequest - 12, // 42: goldmane.Flows.Stream:input_type -> goldmane.FlowStreamRequest - 13, // 43: goldmane.Flows.FilterHints:input_type -> goldmane.FilterHintsRequest - 24, // 44: goldmane.FlowCollector.Connect:input_type -> goldmane.FlowUpdate - 29, // 45: goldmane.Statistics.List:input_type -> goldmane.StatisticsRequest - 11, // 46: goldmane.Flows.List:output_type -> goldmane.FlowListResult - 17, // 47: goldmane.Flows.Stream:output_type -> goldmane.FlowResult - 14, // 48: goldmane.Flows.FilterHints:output_type -> goldmane.FilterHintsResult - 23, // 49: goldmane.FlowCollector.Connect:output_type -> goldmane.FlowReceipt - 30, // 50: goldmane.Statistics.List:output_type -> goldmane.StatisticsResult - 46, // [46:51] is the sub-list for method output_type - 41, // [41:46] is the sub-list for method input_type - 41, // [41:41] is the sub-list for extension type_name - 41, // [41:41] is the sub-list for extension extendee - 0, // [0:41] is the sub-list for field type_name + 6, // 18: goldmane.Filter.reporter:type_name -> goldmane.Reporter + 1, // 19: goldmane.Filter.pending_actions:type_name -> goldmane.Action + 2, // 20: goldmane.StringMatch.type:type_name -> goldmane.MatchType + 4, // 21: goldmane.SortOption.sort_by:type_name -> goldmane.SortBy + 3, // 22: goldmane.PolicyMatch.kind:type_name -> goldmane.PolicyKind + 1, // 23: goldmane.PolicyMatch.action:type_name -> goldmane.Action + 26, // 24: goldmane.FlowUpdate.flow:type_name -> goldmane.Flow + 5, // 25: goldmane.FlowKey.source_type:type_name -> goldmane.EndpointType + 5, // 26: goldmane.FlowKey.dest_type:type_name -> goldmane.EndpointType + 6, // 27: goldmane.FlowKey.reporter:type_name -> goldmane.Reporter + 1, // 28: goldmane.FlowKey.action:type_name -> goldmane.Action + 27, // 29: goldmane.FlowKey.policies:type_name -> goldmane.PolicyTrace + 25, // 30: goldmane.Flow.Key:type_name -> goldmane.FlowKey + 28, // 31: goldmane.PolicyTrace.enforced_policies:type_name -> goldmane.PolicyHit + 28, // 32: goldmane.PolicyTrace.pending_policies:type_name -> goldmane.PolicyHit + 3, // 33: goldmane.PolicyHit.kind:type_name -> goldmane.PolicyKind + 1, // 34: goldmane.PolicyHit.action:type_name -> goldmane.Action + 28, // 35: goldmane.PolicyHit.trigger:type_name -> goldmane.PolicyHit + 7, // 36: goldmane.StatisticsRequest.type:type_name -> goldmane.StatisticType + 8, // 37: goldmane.StatisticsRequest.group_by:type_name -> goldmane.StatisticsGroupBy + 22, // 38: goldmane.StatisticsRequest.policy_match:type_name -> goldmane.PolicyMatch + 28, // 39: goldmane.StatisticsResult.policy:type_name -> goldmane.PolicyHit + 9, // 40: goldmane.StatisticsResult.direction:type_name -> goldmane.RuleDirection + 8, // 41: goldmane.StatisticsResult.group_by:type_name -> goldmane.StatisticsGroupBy + 7, // 42: goldmane.StatisticsResult.type:type_name -> goldmane.StatisticType + 10, // 43: goldmane.Flows.List:input_type -> goldmane.FlowListRequest + 12, // 44: goldmane.Flows.Stream:input_type -> goldmane.FlowStreamRequest + 13, // 45: goldmane.Flows.FilterHints:input_type -> goldmane.FilterHintsRequest + 24, // 46: goldmane.FlowCollector.Connect:input_type -> goldmane.FlowUpdate + 29, // 47: goldmane.Statistics.List:input_type -> goldmane.StatisticsRequest + 11, // 48: goldmane.Flows.List:output_type -> goldmane.FlowListResult + 17, // 49: goldmane.Flows.Stream:output_type -> goldmane.FlowResult + 14, // 50: goldmane.Flows.FilterHints:output_type -> goldmane.FilterHintsResult + 23, // 51: goldmane.FlowCollector.Connect:output_type -> goldmane.FlowReceipt + 30, // 52: goldmane.Statistics.List:output_type -> goldmane.StatisticsResult + 48, // [48:53] is the sub-list for method output_type + 43, // [43:48] is the sub-list for method input_type + 43, // [43:43] is the sub-list for extension type_name + 43, // [43:43] is the sub-list for extension extendee + 0, // [0:43] is the sub-list for field type_name } func init() { file_api_proto_init() } diff --git a/goldmane/proto/api.proto b/goldmane/proto/api.proto index a583268fb83..7aaea5c6d03 100644 --- a/goldmane/proto/api.proto +++ b/goldmane/proto/api.proto @@ -155,6 +155,8 @@ enum FilterType { FilterTypeSourceNamespace = 4; FilterTypePolicyTier = 5; FilterTypePolicyName = 6; + FilterTypePolicyKind = 7; + FilterTypePolicyNamespace = 8; } // FlowResult wraps a Flow object with additional metadata. @@ -200,6 +202,12 @@ message Filter { // Policies matches on policy fields. Combined using logical OR. repeated PolicyMatch policies = 8; + + // Reporter filters on the reporter field. + Reporter reporter = 9; + + // Pending/Staged Actions filters on the action field. Combined using logical OR. + repeated Action pending_actions = 10; } enum MatchType { @@ -247,8 +255,10 @@ enum PolicyKind { // Native Kubernetes types. NetworkPolicy = 6; - AdminNetworkPolicy = 7; - BaselineAdminNetworkPolicy = 8; + // AdminNetworkPolicy (7) and BaselineAdminNetworkPolicy (8) are no longer supported. + reserved 7; + reserved 8; + ClusterNetworkPolicy = 11; // Calico Profiles. Profile = 9; diff --git a/goldmane/proto/api_grpc.pb.go b/goldmane/proto/api_grpc.pb.go index c41ddc0e7dd..f03a42b5c38 100644 --- a/goldmane/proto/api_grpc.pb.go +++ b/goldmane/proto/api_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.5.1 -// - protoc v6.32.0 +// - protoc-gen-go-grpc v1.6.0 +// - protoc v6.33.4 // source: api.proto package proto @@ -121,13 +121,13 @@ type FlowsServer interface { type UnimplementedFlowsServer struct{} func (UnimplementedFlowsServer) List(context.Context, *FlowListRequest) (*FlowListResult, error) { - return nil, status.Errorf(codes.Unimplemented, "method List not implemented") + return nil, status.Error(codes.Unimplemented, "method List not implemented") } func (UnimplementedFlowsServer) Stream(*FlowStreamRequest, grpc.ServerStreamingServer[FlowResult]) error { - return status.Errorf(codes.Unimplemented, "method Stream not implemented") + return status.Error(codes.Unimplemented, "method Stream not implemented") } func (UnimplementedFlowsServer) FilterHints(context.Context, *FilterHintsRequest) (*FilterHintsResult, error) { - return nil, status.Errorf(codes.Unimplemented, "method FilterHints not implemented") + return nil, status.Error(codes.Unimplemented, "method FilterHints not implemented") } func (UnimplementedFlowsServer) mustEmbedUnimplementedFlowsServer() {} func (UnimplementedFlowsServer) testEmbeddedByValue() {} @@ -140,7 +140,7 @@ type UnsafeFlowsServer interface { } func RegisterFlowsServer(s grpc.ServiceRegistrar, srv FlowsServer) { - // If the following call pancis, it indicates UnimplementedFlowsServer was + // If the following call panics, it indicates UnimplementedFlowsServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. @@ -287,7 +287,7 @@ type FlowCollectorServer interface { type UnimplementedFlowCollectorServer struct{} func (UnimplementedFlowCollectorServer) Connect(grpc.BidiStreamingServer[FlowUpdate, FlowReceipt]) error { - return status.Errorf(codes.Unimplemented, "method Connect not implemented") + return status.Error(codes.Unimplemented, "method Connect not implemented") } func (UnimplementedFlowCollectorServer) mustEmbedUnimplementedFlowCollectorServer() {} func (UnimplementedFlowCollectorServer) testEmbeddedByValue() {} @@ -300,7 +300,7 @@ type UnsafeFlowCollectorServer interface { } func RegisterFlowCollectorServer(s grpc.ServiceRegistrar, srv FlowCollectorServer) { - // If the following call pancis, it indicates UnimplementedFlowCollectorServer was + // If the following call panics, it indicates UnimplementedFlowCollectorServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. @@ -399,7 +399,7 @@ type StatisticsServer interface { type UnimplementedStatisticsServer struct{} func (UnimplementedStatisticsServer) List(*StatisticsRequest, grpc.ServerStreamingServer[StatisticsResult]) error { - return status.Errorf(codes.Unimplemented, "method List not implemented") + return status.Error(codes.Unimplemented, "method List not implemented") } func (UnimplementedStatisticsServer) mustEmbedUnimplementedStatisticsServer() {} func (UnimplementedStatisticsServer) testEmbeddedByValue() {} @@ -412,7 +412,7 @@ type UnsafeStatisticsServer interface { } func RegisterStatisticsServer(s grpc.ServiceRegistrar, srv StatisticsServer) { - // If the following call pancis, it indicates UnimplementedStatisticsServer was + // If the following call panics, it indicates UnimplementedStatisticsServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. diff --git a/goldmane/proto/methods.go b/goldmane/proto/methods.go index ab992792aa7..70eada698e4 100644 --- a/goldmane/proto/methods.go +++ b/goldmane/proto/methods.go @@ -21,6 +21,8 @@ import ( "github.com/sirupsen/logrus" "k8s.io/kubernetes/pkg/apis/core/validation" + + "github.com/projectcalico/calico/felix/types" ) // NewFlow returns a new proto.Flow object with all fields initialized and non-nil. @@ -36,7 +38,7 @@ func NewFlow() *Flow { // TODO: This is a temporary solution - we should be pushing the structured PolicyHit representation // further down the stack, rather than converting between string / struct. func (h *PolicyHit) ToString() (string, error) { - // Format is: "||||" + // Format is: "||:[/]||" tmpl := "%d|%s|%s|%s|%d" if err := h.Validate(); err != nil { @@ -76,11 +78,18 @@ func (h *PolicyHit) Validate() error { switch h.Kind { case PolicyKind_GlobalNetworkPolicy, PolicyKind_StagedGlobalNetworkPolicy, - PolicyKind_AdminNetworkPolicy, - PolicyKind_BaselineAdminNetworkPolicy: + PolicyKind_ClusterNetworkPolicy: if h.Namespace != "" { return fmt.Errorf("unexpected namespace for global policy") } + case PolicyKind_CalicoNetworkPolicy, + PolicyKind_NetworkPolicy, + PolicyKind_StagedKubernetesNetworkPolicy, + PolicyKind_StagedNetworkPolicy: + // Namespaced policies - require a namespace. + if h.Namespace == "" { + return fmt.Errorf("missing namespace for namespaced policy") + } case PolicyKind_EndOfTier: if h.Trigger == nil { return fmt.Errorf("EndOfTier hit missing trigger") @@ -101,41 +110,46 @@ func (h *PolicyHit) fields() logrus.Fields { } } +// makeNamePart generates the name part of the policy hit string based on the policy kind. +// The name part has the format ":[/]". func makeNamePart(h *PolicyHit) (string, error) { logrus.WithFields(h.fields()).Debug("Generating name part from policy hit") - var namePart string + var shortKind string switch h.Kind { case PolicyKind_GlobalNetworkPolicy: - namePart = fmt.Sprintf("%s.%s", h.Tier, h.Name) + shortKind = types.ShortKindGlobalNetworkPolicy case PolicyKind_CalicoNetworkPolicy: - namePart = fmt.Sprintf("%s/%s.%s", h.Namespace, h.Tier, h.Name) + shortKind = types.ShortKindNetworkPolicy case PolicyKind_NetworkPolicy: - namePart = fmt.Sprintf("%s/knp.default.%s", h.Namespace, h.Name) + shortKind = types.ShortKindKubernetesNetworkPolicy case PolicyKind_StagedKubernetesNetworkPolicy: - namePart = fmt.Sprintf("%s/staged:knp.default.%s", h.Namespace, h.Name) + shortKind = types.ShortKindStagedKubernetesNetworkPolicy case PolicyKind_StagedGlobalNetworkPolicy: - namePart = fmt.Sprintf("%s.staged:%s", h.Tier, h.Name) + shortKind = types.ShortKindStagedGlobalNetworkPolicy case PolicyKind_StagedNetworkPolicy: - namePart = fmt.Sprintf("%s/%s.staged:%s", h.Namespace, h.Tier, h.Name) - case PolicyKind_AdminNetworkPolicy: - namePart = fmt.Sprintf("kanp.adminnetworkpolicy.%s", h.Name) - case PolicyKind_BaselineAdminNetworkPolicy: - namePart = fmt.Sprintf("kbanp.baselineadminnetworkpolicy.%s", h.Name) + shortKind = types.ShortKindStagedNetworkPolicy + case PolicyKind_ClusterNetworkPolicy: + shortKind = types.ShortKindKubernetesClusterNetworkPolicy case PolicyKind_Profile: - // Profile names are __PROFILE__.name. The name part may include indicators of the kind of - // profile - e.g., __PROFILE__.kns.default, __PROFILE__.ksa.svcacct. - namePart = fmt.Sprintf("__PROFILE__.%s", h.Name) + shortKind = types.ShortKindProfile default: logrus.WithFields(h.fields()).Error("Unexpected policy kind") return "", fmt.Errorf("unexpected policy kind: %v", h.Kind) } + + var namePart string + if h.Namespace == "" { + // Global policies and profiles don't have a namespace. + namePart = fmt.Sprintf("%s:%s", shortKind, h.Name) + } else { + // Namespaced policies. + namePart = fmt.Sprintf("%s:%s/%s", shortKind, h.Namespace, h.Name) + } logrus.WithFields(h.fields()).WithField("namePart", namePart).Debug("Generated name part") return namePart, nil } -// HitFromString parses a policy hit label string into a PolicyHit struct. -// TODO: This is a temporary solution - we should be pushing the structured PolicyHit representation -// further down the stack, rather than converting between string / struct. +// HitFromString parses a policy hit label string as generated by Felix string into a PolicyHit struct. func HitFromString(s string) (*PolicyHit, error) { parts := strings.Split(s, "|") if len(parts) != 5 { @@ -172,69 +186,39 @@ func HitFromString(s string) (*PolicyHit, error) { var kind PolicyKind var name string var ns string - nameParts := strings.Split(namePart, "/") - if len(nameParts) == 1 { - // No namespace, must be a global policy. - // Name format is "(staged:)tier.name". - n := nameParts[0] - if strings.Contains(n, "staged:") { - kind = PolicyKind_StagedGlobalNetworkPolicy - n = strings.Replace(n, "staged:", "", 1) - } else if strings.HasPrefix(n, "kanp.") { - kind = PolicyKind_AdminNetworkPolicy - n = strings.TrimPrefix(n, "kanp.") - } else if strings.HasPrefix(n, "kbanp.") { - kind = PolicyKind_BaselineAdminNetworkPolicy - n = strings.TrimPrefix(n, "kbanp.") - } else if strings.HasPrefix(n, "__PROFILE__.") { - kind = PolicyKind_Profile - } else { - kind = PolicyKind_GlobalNetworkPolicy - } - - // At this point, n is "tier.name". The name may of dots in it, so - // we need to recombine the parts except for the tier. - name = strings.Join(strings.Split(n, ".")[1:], ".") + // Parse the name part to determine the kind. + switch strings.Split(namePart, ":")[0] { + case types.ShortKindGlobalNetworkPolicy: + kind = PolicyKind_GlobalNetworkPolicy + case types.ShortKindStagedNetworkPolicy: + kind = PolicyKind_StagedNetworkPolicy + case types.ShortKindNetworkPolicy: + kind = PolicyKind_CalicoNetworkPolicy + case types.ShortKindKubernetesNetworkPolicy: + kind = PolicyKind_NetworkPolicy + case types.ShortKindStagedKubernetesNetworkPolicy: + kind = PolicyKind_StagedKubernetesNetworkPolicy + case types.ShortKindStagedGlobalNetworkPolicy: + kind = PolicyKind_StagedGlobalNetworkPolicy + case types.ShortKindKubernetesClusterNetworkPolicy: + kind = PolicyKind_ClusterNetworkPolicy + case types.ShortKindProfile: + kind = PolicyKind_Profile + default: + return nil, fmt.Errorf("unexpected policy kind in name part: %s", namePart) + } - // Verify the "tier" part of "tier.name" matches the tier. - if strings.Split(n, ".")[0] != tier { - return nil, fmt.Errorf("tier does not match: %s != %s", strings.Split(n, ".")[0], tier) - } + // Determine the name and namespace (if applicable). + namespacedName := strings.SplitN(namePart, ":", 2)[1] + nameParts := strings.Split(namespacedName, "/") + if len(nameParts) == 1 { + // No namespace, must be a global policy. + name = nameParts[0] } else if len(nameParts) == 2 { // Namespaced. - // Name format for Calico policies is "tier.(staged:)name". - // Name format for K8s policies is "(staged:)knp.default.name". - n := nameParts[1] ns = nameParts[0] - if strings.HasPrefix(n, "staged:knp.") { - // StagedKubernetesNetworkPolicy. - kind = PolicyKind_StagedKubernetesNetworkPolicy - n = strings.TrimPrefix(n, "staged:knp.") - } else if strings.HasPrefix(n, "knp.") { - // KubernetesNetworkPolicy. - kind = PolicyKind_NetworkPolicy - n = strings.TrimPrefix(n, "knp.") - } else { - // This is either a Calico NetworkPolicy or Calico StagedNetworkPolicy. - if strings.Contains(n, "staged:") { - kind = PolicyKind_StagedNetworkPolicy - n = strings.Replace(n, "staged:", "", 1) - } else { - // Calico NetworkPolicy. - // Name format is already "tier.name". - kind = PolicyKind_CalicoNetworkPolicy - } - } - - // At this point, n is "tier.name". The name may of dots in it, so - // we need to recombine the parts except for the tier. - name = strings.Join(strings.Split(n, ".")[1:], ".") - - // Verify the "tier" part of "tier.name" matches the tier. - if strings.Split(n, ".")[0] != tier { - return nil, fmt.Errorf("tier does not match: %s != %s", strings.Split(n, ".")[0], tier) - } + name = nameParts[1] } // Verify the name is valid. @@ -294,6 +278,6 @@ func HitFromString(s string) (*PolicyHit, error) { RuleIndex: ruleIdx, Kind: kind, } - logrus.WithFields(hit.fields()).Debug("Parsed policy hit") + logrus.WithField("orig", s).WithFields(hit.fields()).Debug("Parsed policy hit") return hit, nil } diff --git a/goldmane/proto/methods_test.go b/goldmane/proto/methods_test.go index 7ee0d0d37e0..d352698ff3f 100644 --- a/goldmane/proto/methods_test.go +++ b/goldmane/proto/methods_test.go @@ -11,58 +11,58 @@ func TestConversion(t *testing.T) { // Ensure that the strings are the same before and after the conversion. tests := []string{ // GNP - "0|tier|tier.name|allow|0", + "0|tier|gnp:name|allow|0", // Staged GNP - "1|tier|tier.staged:name|deny|2", - "1|tier|tier.staged:name.with.dots|deny|2", + "1|tier|sgnp:name|deny|2", + "1|tier|sgnp:name.with.dots|deny|2", - // GNP in baseline / anp namespaces. - "0|adminnetworkpolicy|adminnetworkpolicy.name|deny|1", - "1|baselineadminnetworkpolicy|baselineadminnetworkpolicy.name|deny|2", + // GNP in ClusterNetworkPolicy namespaces. + "0|kube-admin|kcnp:name|deny|1", + "1|kube-baseline|kcnp:name|deny|2", // Namespaced Calico NP - "0|tier|namespace/tier.name|allow|1", - "0|tier1|default/tier1.np1-1|pass|0", + "0|tier|np:namespace/name|allow|1", + "0|tier|np:namespace/tier.name|allow|1", + "0|tier1|np:default/tier1.np1-1|pass|0", // Namespaced KNP - "0|default|namespace/knp.default.name|allow|2", + "0|default|knp:namespace/name|allow|2", // Staged poliicies have a different format depending on the Kind. // For Calico policies: |/.staged: // For kubernetes policies: default|/staged:knp.default. // // Calico Namespaced StagedNetworkPolicy. - "1|tier|namespace/tier.staged:name|deny|0", - "1|tier|namespace/tier.staged:name.with.dots|deny|0", + "1|tier|snp:namespace/name|deny|0", + "1|tier|snp:namespace/name.with.dots|deny|0", // StagedKubernetesNetworkPolicy. - "1|default|namespace/staged:knp.default.name|deny|3", - "1|default|namespace/staged:knp.default.name.with.dots|deny|3", + "1|default|sknp:namespace/name|deny|3", + "1|default|sknp:namespace/name.with.dots|deny|3", - // AdminNetworkPolicy - "3|adminnetworkpolicy|kanp.adminnetworkpolicy.name|pass|4", - "3|adminnetworkpolicy|kanp.adminnetworkpolicy.name.with.dots|pass|4", - "2|adminnetworkpolicy|kanp.adminnetworkpolicy.name.with.dots|pass|1", + // ClusterNetworkPolicy - Admin Tier + "3|kube-admin|kcnp:name|pass|4", + "3|kube-admin|kcnp:name.with.dots|pass|4", + "2|kube-admin|kcnp:name.with.dots|pass|1", - // BaslineAdminNetworkPolicy - "0|baselineadminnetworkpolicy|kbanp.baselineadminnetworkpolicy.name|pass|4", - "3|baselineadminnetworkpolicy|kbanp.baselineadminnetworkpolicy.name.with.dots|pass|4", - "2|baselineadminnetworkpolicy|kbanp.baselineadminnetworkpolicy.name.with.dots|pass|1", + // ClusterNetworkPolicy - Baseline Tier + "3|kube-baseline|kcnp:name|pass|4", + "3|kube-baseline|kcnp:name.with.dots|pass|4", + "2|kube-baseline|kcnp:name.with.dots|pass|1", // Profile rules. - "1|__PROFILE__|__PROFILE__.kns.default|allow|0", - "2|__PROFILE__|__PROFILE__.kns.default|allow|1", - "1|__PROFILE__|__PROFILE__.ksa.svcacct|allow|0", + "1|__PROFILE__|pro:kns.default|allow|0", + "2|__PROFILE__|pro:kns.default|allow|1", + "1|__PROFILE__|pro:ksa.svcacct|allow|0", // End of tier rules - indicated by -1. - "0|tier1|ns1/tier1.policy1|deny|-1", - "1|tier1|ns1/tier1.policy1|allow|-1", - "0|adminnetworkpolicy|kanp.adminnetworkpolicy.policy1|allow|-1", - "2|adminnetworkpolicy|adminnetworkpolicy.policy1|allow|-1", - - "0|tier2|default/tier2.staged:np2-1|deny|-1", - "1|tier2|default/tier2.staged:np2-1|deny|-1", + "0|tier1|np:ns1/tier1.policy1|deny|-1", + "1|tier1|np:ns1/tier1.policy1|allow|-1", + "0|kube-admin|kcnp:policy1|allow|-1", + "2|kube-admin|gnp:kube-admin.policy1|allow|-1", + "0|tier2|snp:default/np2-1|deny|-1", + "1|tier2|snp:default/np2-1|deny|-1", } for _, strVal := range tests { @@ -84,35 +84,31 @@ func TestConversion(t *testing.T) { func TestInvalidStrings(t *testing.T) { tests := []string{ // Invalid integer values. - "1|tier|staged:tier.name|deny|notInt", - "notint|baselineadminnetworkpolicy|baselineadminnetworkpolicy.name|deny|2", - "notint|baselineadminnetworkpolicy|kbanp.baselineadminnetworkpolicy.name|pass|4", - "notint|tier1|ns1/tier1.policy1|deny|-1", + "1|tier|sgnp:tier.name|deny|notInt", + "notint|kube-baseline|kcnp:name|deny|2", + "notint|kube-baseline|kcnp:name|pass|4", + "notint|tier1|np:ns1/tier1.policy1|deny|-1", // An extra section. - "0|tier|tier.name|allow|0|extra", + "0|tier|gnp:tier.name|allow|0|extra", // Invalid characters. - "1|tier|invalid-ch@aracter|deny|2", - "1|_|namespace/staged:tier.name|deny|0", - "0|@dminnetworkpolicy|kanp.adminnetworkpolicy.policy1|allow|-1", + "1|tier|gnp:invalid-ch@aracter|deny|2", + "1|_|snp:namespace/tier.name|deny|0", + "0|@kube-baseline|kcnp:policy1|allow|-1", // Bad action field. - "0|adminnetworkpolicy|adminnetworkpolicy.name|badaction|1", + "0|kube-admin|kcnp:name|badaction|1", - // Tier fields do not match. - "0|tier|namespace/knp.default.name|allow|2", - "0|adminnetworkpolicy|kanp.foobar.policy1|allow|-1", - "1|tier|tier2.name|allow|0", - "0||namespace/tier.name|allow|1", + // Tier field is missing. + "0||np:namespace/name|allow|1", // Missing a name section. - "0|tier1|default/|pass|0", - "1|tier|namespace/knp.default|deny|3", + "0|tier1|np:namespace/|pass|0", // Profile rules. - "1|___PROFILE__|__PROFILE__.kns.default|allow|0", - "1|__PROFILE__|__PROFILE__.PSA.svcacct|allow|0", + "1|___PROFILE__|pro:kns.default|allow|0", + "1|__PROFILE__|pro:PSA.svcacct|allow|0", } for _, strVal := range tests { @@ -133,7 +129,7 @@ func TestToString(t *testing.T) { tests := []testCase{ { name: "Valid base case", - strVal: "0|tier|tier.name|allow|0", + strVal: "0|tier|gnp:name|allow|0", hit: &PolicyHit{ Kind: PolicyKind_GlobalNetworkPolicy, Tier: "tier", diff --git a/guardian/deps.txt b/guardian/deps.txt index 1573bf3aad0..56797982e11 100644 --- a/guardian/deps.txt +++ b/guardian/deps.txt @@ -2,37 +2,50 @@ This file contains the list of modules that this package depends on in order to trigger CI on changes -go 1.25.1 +go 1.25.7 github.com/beorn7/perks v1.0.1 github.com/cespare/xxhash/v2 v2.3.0 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc +github.com/fxamacker/cbor/v2 v2.9.0 github.com/go-logr/logr v1.4.3 +github.com/gogo/protobuf v1.3.2 github.com/google/go-cmp v0.7.0 github.com/hashicorp/yamux v0.1.2 +github.com/jinzhu/copier v0.4.0 +github.com/json-iterator/go v1.1.12 github.com/kelseyhightower/envconfig v1.4.0 +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 -github.com/onsi/gomega v1.38.0 +github.com/onsi/gomega v1.39.1 github.com/pkg/errors v0.9.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/projectcalico/calico -github.com/projectcalico/calico/lib/std v0.0.0-00010101000000-000000000000 -github.com/prometheus/client_golang v1.23.0 +github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 -github.com/prometheus/common v0.65.0 -github.com/prometheus/procfs v0.17.0 +github.com/prometheus/common v0.67.2 +github.com/prometheus/procfs v0.19.2 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/objx v0.5.2 -github.com/stretchr/testify v1.10.0 -golang.org/x/net v0.43.0 -golang.org/x/oauth2 v0.30.0 -golang.org/x/sys v0.35.0 -golang.org/x/text v0.28.0 -golang.org/x/time v0.12.0 -google.golang.org/protobuf v1.36.7 +github.com/stretchr/testify v1.11.1 +github.com/x448/float16 v0.8.4 +go.yaml.in/yaml/v2 v2.4.3 +go.yaml.in/yaml/v3 v3.0.4 +golang.org/x/net v0.49.0 +golang.org/x/oauth2 v0.34.0 +golang.org/x/sys v0.40.0 +golang.org/x/text v0.33.0 +golang.org/x/time v0.14.0 +google.golang.org/protobuf v1.36.10 +gopkg.in/inf.v0 v0.9.1 gopkg.in/yaml.v3 v3.0.1 -k8s.io/api v0.33.5 -k8s.io/apimachinery v0.33.5 -k8s.io/client-go v0.33.5 +k8s.io/api v0.34.3 +k8s.io/apimachinery v0.34.3 +k8s.io/client-go v0.34.3 k8s.io/klog v0.2.0 k8s.io/klog/v2 v2.130.1 -k8s.io/utils v0.0.0-20241210054802-24370beab758 +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 +sigs.k8s.io/randfill v1.0.0 +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 +sigs.k8s.io/yaml v1.6.0 diff --git a/guardian/pkg/asyncutil/async.go b/guardian/pkg/asyncutil/async.go index 113ea690fca..304555c5288 100644 --- a/guardian/pkg/asyncutil/async.go +++ b/guardian/pkg/asyncutil/async.go @@ -243,9 +243,7 @@ func (executor *commandExecutor[C, R]) execBacklog(shutdownCtx context.Context) } func (executor *commandExecutor[C, R]) executeCommand(ctx context.Context, req Command[C, R]) { - executor.inflightCmds.Add(1) - go func() { - defer executor.inflightCmds.Done() + executor.inflightCmds.Go(func() { result, err := executor.command(ctx, req.Get()) if err != nil { logrus.WithError(err).Debug("Error executing command") @@ -260,7 +258,7 @@ func (executor *commandExecutor[C, R]) executeCommand(ctx context.Context, req C } req.Return(result) - }() + }) } func (executor *commandExecutor[C, R]) Send(params C) <-chan Result[R] { diff --git a/guardian/pkg/conn/forward_test.go b/guardian/pkg/conn/forward_test.go index 8f3dda7bd36..665547a4a10 100644 --- a/guardian/pkg/conn/forward_test.go +++ b/guardian/pkg/conn/forward_test.go @@ -31,19 +31,15 @@ func TestForwardConnections(t *testing.T) { defer func() { _ = lst2.Close() }() var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { dst1, err = lst1.Accept() Expect(err).ShouldNot(HaveOccurred()) - }() + }) - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { dst2, err = lst2.Accept() Expect(err).ShouldNot(HaveOccurred()) - }() + }) t.Log("Connecting to the localhost listeners") src1, err := net.Dial("tcp", lst1.Addr().String()) @@ -54,11 +50,9 @@ func TestForwardConnections(t *testing.T) { wg.Wait() - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { conn.Forward(dst1, src2) - }() + }) request := "request" response := "response" diff --git a/guardian/pkg/daemon/daemon.go b/guardian/pkg/daemon/daemon.go index 3291f281f83..b8f690c5e52 100644 --- a/guardian/pkg/daemon/daemon.go +++ b/guardian/pkg/daemon/daemon.go @@ -78,25 +78,21 @@ func Run(ctx context.Context, cfg config.Config, proxyTargets []server.Target) { }() var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { // Allow requests to come down from the management cluster. if err := srv.ListenAndServeManagementCluster(); err != nil { logrus.WithError(err).Warn("Serving the tunnel exited.") } - }() + }) // Allow requests from the cluster to be sent up to the management cluster. if cfg.Listen { - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { if err := srv.ListenAndServeCluster(); err != nil { logrus.WithError(err).Warn("proxy tunnel exited with an error") } - }() + }) } if err := srv.WaitForShutdown(); err != nil { diff --git a/guardian/pkg/server/server.go b/guardian/pkg/server/server.go index b7d70c62813..4b8c784d4c6 100644 --- a/guardian/pkg/server/server.go +++ b/guardian/pkg/server/server.go @@ -132,7 +132,7 @@ func (srv *server) ListenAndServeCluster() error { return fmt.Errorf("failed to connect to tunnel: %w", err) } - listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", srv.listenHost, srv.listenPort)) + listener, err := net.Listen("tcp", net.JoinHostPort(srv.listenHost, srv.listenPort)) if err != nil { return fmt.Errorf("failed to listen on %s:%s: %w", srv.listenHost, srv.listenPort, err) } diff --git a/hack/Makefile b/hack/Makefile index 1f2b07e4661..c2fe82ae47b 100644 --- a/hack/Makefile +++ b/hack/Makefile @@ -13,13 +13,10 @@ include ../lib.Makefile BINDIR?=bin -# Create a list of files upon which the generated file depends, skip the generated file itself -APIS_SRCS := $(filter-out ./lib/apis/v3/zz_generated.deepcopy.go, $(wildcard ./lib/apis/v3/*.go)) - .PHONY: clean clean: rm -rf $(BINDIR) - find . -name '*.coverprofile' -type f -delete + find . -name 'coverprofile.out' -type f -delete ############################################################################### # Building the binary @@ -47,9 +44,16 @@ fv: st: @echo "No STs available" +############################################################################### +# Shell script tests +############################################################################### +.PHONY: shell-test +shell-test: + bash cherry-pick-pull_test.sh + ############################################################################### # CI ############################################################################### .PHONY: ci ## Run what CI runs -ci: clean static-checks test +ci: clean static-checks test shell-test diff --git a/hack/check-ginkgo-v2.sh b/hack/check-ginkgo-v2.sh new file mode 100755 index 00000000000..4ab03a45f8d --- /dev/null +++ b/hack/check-ginkgo-v2.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# Copyright (c) 2026 Tigera, Inc. All rights reserved. +# +# 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. + +# This script checks that no Go files import ginkgo v1 +# (github.com/onsi/ginkgo). Only ginkgo v2 (github.com/onsi/ginkgo/v2) +# is allowed. + +problems="$(grep -r -I --include='*.go' '"github.com/onsi/ginkgo"' \ + --exclude-dir='vendor' \ + --exclude-dir='containernetworking-plugins' \ + . | + grep -v 'ginkgo/v2')" + +if [ "$problems" ]; then + echo "Some files import ginkgo v1 (github.com/onsi/ginkgo)." + echo "Only ginkgo v2 (github.com/onsi/ginkgo/v2) is allowed." + echo + printf "%s" "$problems" + echo + echo + exit 1 +fi diff --git a/hack/cherry-pick-pull b/hack/cherry-pick-pull new file mode 100755 index 00000000000..6725db12c35 --- /dev/null +++ b/hack/cherry-pick-pull @@ -0,0 +1,470 @@ +#!/usr/bin/env bash + +# Copyright 2015 The Kubernetes Authors. +# +# 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. + +# Checkout a PR from GitHub. (Yes, this is sitting in a Git tree. How +# meta.) Assumes you care about pulls from remote "upstream" and +# checks them out to a branch named: +# automated-cherry-pick-of--- +# +# For example: +# cherry_pick_pull.sh release-v3.12 3320 + +set -o errexit +set -o nounset +set -o pipefail + +function init-vars() { + THIS_SCRIPT=$(basename "$0") + + if [[ "$#" -lt 2 ]]; then + echo "${0} ...: cherry pick one or more PRs onto and propose pull request" + echo + echo " Checks out and handles the cherry-pick of (possibly multiple) for you." + echo " Examples:" + echo " ${THIS_SCRIPT} upstream/release-3.14 12345 # Cherry-picks PR 12345 onto upstream/release-3.14 and proposes that as a PR." + echo " ${THIS_SCRIPT} upstream/release-3.14 12345 56789 # Cherry-picks PR 12345, then 56789 and proposes the combination as a single PR." + echo " CHERRY_PICK=1 SRC_UPSTREAM_REMOTE=open-source DST_UPSTREAM_REMOTE=upstream ${THIS_SCRIPT} upstream/release-3.14 12345" + echo " # ^- Uses git cherry-pick to pick PR 12345 as a single commit from the 'open-source' remote to the" + echo " # 'upstream' remote's release-3.14 branch, and proposes that as a PR." + echo + echo " Environment variables:" + echo + echo " Set SRC_UPSTREAM_REMOTE and DST_UPSTREAM_REMOTE (default for both: upstream) and FORK_REMOTE (default: origin)" + echo " To override the default source and destination repos, and the repo to push the PR to." + echo + echo " Set CHERRY_PICK=1 to use 'git cherry-pick' to pick the PR as a single commit (instead of the default, which" + echo " uses 'git am' to preserve the PR's intermediate commits). Requires that the PR has already been merged." + echo + echo " Set SQUASH_COMMITS=1 to apply the PR as a single commit using 'git apply'. This doesn't handle conflicts as" + echo " well as 'git cherry-pick' (watch out for .rej files!), but it doesn't require the source PR to have been merged." + echo + echo " Set DRY_RUN=1 to skip git push and creating PR. When DRY_RUN is set the script will leave you in a branch" + echo " containing the commits you cherry-picked." + echo + echo " Set REGENERATE_DOCS=1 to regenerate documentation for the target branch after picking the specified commits." + echo " This is useful when picking commits containing changes to API documentation." + exit 2 + fi + + REPO_ROOT="$(git rev-parse --show-toplevel)" + declare -rg REPO_ROOT + cd "${REPO_ROOT}" + + STARTINGBRANCH=$(git symbolic-ref --short HEAD) + declare -rg STARTINGBRANCH + declare -rg REBASEMAGIC="${REPO_ROOT}/.git/rebase-apply" + DRY_RUN=${DRY_RUN:-""} + REGENERATE_DOCS=${REGENERATE_DOCS:-""} + SRC_UPSTREAM_REMOTE=${SRC_UPSTREAM_REMOTE:-upstream} + DST_UPSTREAM_REMOTE=${DST_UPSTREAM_REMOTE:-upstream} + FORK_REMOTE=${FORK_REMOTE:-origin} + SRC_MAIN_REPO_ORG=${SRC_MAIN_REPO_ORG:-$(git remote get-url "$SRC_UPSTREAM_REMOTE" | awk '{gsub(/http[s]:\/\/|git@|ssh:\/\//,"")}1' | awk -F'[@:./]' 'NR==1{print $3}')} + SRC_MAIN_REPO_NAME=${SRC_MAIN_REPO_NAME:-$(git remote get-url "$SRC_UPSTREAM_REMOTE" | awk '{gsub(/http[s]:\/\/|git@|ssh:\/\//,"")}1' | awk -F'[@:./]' 'NR==1{print $4}')} + if [[ -z ${SRC_MAIN_REPO_ORG:-} || -z ${SRC_MAIN_REPO_NAME:-} ]]; then + echo "Can't parse $SRC_UPSTREAM_REMOTE URL to set SRC_MAIN_REPO_ORG or SRC_MAIN_REPO_NAME." + exit 1 + fi + DST_MAIN_REPO_ORG=${DST_MAIN_REPO_ORG:-$(git remote get-url "$DST_UPSTREAM_REMOTE" | awk '{gsub(/http[s]:\/\/|git@|ssh:\/\//,"")}1' | awk -F'[@:./]' 'NR==1{print $3}')} + DST_MAIN_REPO_NAME=${DST_MAIN_REPO_NAME:-$(git remote get-url "$DST_UPSTREAM_REMOTE" | awk '{gsub(/http[s]:\/\/|git@|ssh:\/\//,"")}1' | awk -F'[@:./]' 'NR==1{print $4}')} + if [[ -z ${DST_MAIN_REPO_ORG:-} || -z ${DST_MAIN_REPO_NAME:-} ]]; then + echo "Can't parse $DST_UPSTREAM_REMOTE URL to set DST_MAIN_REPO_ORG or DST_MAIN_REPO_NAME." + exit 1 + fi + + CHERRY_PICK=${CHERRY_PICK:-""} + SQUASH_COMMITS=${SQUASH_COMMITS:-""} + + FAIL_ON_CONFLICT=${FAIL_ON_CONFLICT:-""} + AUTO_CREATE_PR=${AUTO_CREATE_PR:-""} + + if [[ -z ${GITHUB_USER:-} ]]; then + echo "Please export GITHUB_USER= (or GH organization, if that's where your fork lives)" + exit 1 + fi + + if ! which gh > /dev/null; then + echo "Can't find 'gh' tool in PATH, please install: https://cli.github.com/" + exit 1 + fi + + if git_status=$(git status --porcelain --untracked=no 2>/dev/null) && [[ -n "${git_status}" ]]; then + echo "!!! Dirty tree. Clean up and try again." + exit 1 + fi + + if [[ -e "${REBASEMAGIC}" ]]; then + echo "!!! 'git rebase' or 'git am' in progress. Clean up and try again." + exit 1 + fi + + declare -rg BRANCH="$1" + shift 1 + declare -rg PULLS=( "$@" ) + + function join { local IFS="$1"; shift; echo "$*"; } + declare -rg PULLDASH=$(join - "${PULLS[@]/#/#}") # Generates something like "#12345-#56789" + + # Pre-compute markdown links for each PR. + declare -ag PULLLINK + for pull in "${PULLS[@]}"; do + PULLLINK+=( "${SRC_MAIN_REPO_ORG}/${SRC_MAIN_REPO_NAME}#${pull}" ) + done + + echo "+++ Updating remotes..." + git remote update "${SRC_UPSTREAM_REMOTE}" "${DST_UPSTREAM_REMOTE}" "${FORK_REMOTE}" + + if ! git log -n1 --format=%H "${BRANCH}" >/dev/null 2>&1; then + echo "!!! '${BRANCH}' not found. The second argument should be something like ${DST_UPSTREAM_REMOTE}/release-0.21." + echo " (In particular, it needs to be a valid, existing remote branch that I can 'git checkout'.)" + exit 1 + fi + + declare -rg NEWBRANCHREQ="auto-pick-of-${PULLDASH}" # "Required" portion for tools. + declare -rg NEWBRANCH="$(echo "${NEWBRANCHREQ}-${BRANCH}" | sed 's/\//-/g')" + declare -rg NEWBRANCHUNIQ="${NEWBRANCH}-$(date +%s)" # Unique branch name to avoid collisions. +} + +cleanbranch="" +pr_body_file="" +gitamcleanup=false +gitcherrycleanup=false +function return_to_kansas { + if [[ "${gitamcleanup}" == "true" ]]; then + echo + echo "+++ Aborting in-progress git am." + git am --abort >/dev/null 2>&1 || true + fi + if [[ "${gitcherrycleanup}" == "true" ]]; then + echo + echo "+++ Aborting in-progress git cherry-pick." + git cherry-pick --abort >/dev/null 2>&1 || true + fi + + # return to the starting branch and delete the PR text file + if [[ -z "${DRY_RUN:-}" ]]; then + if [[ -n "${STARTINGBRANCH:-}" ]]; then + echo + echo "+++ Returning you to the ${STARTINGBRANCH} branch and cleaning up." + git checkout -f "${STARTINGBRANCH}" >/dev/null 2>&1 || true + if [[ -n "${cleanbranch}" ]]; then + git branch -D "${cleanbranch}" >/dev/null 2>&1 || true + fi + if [[ -n "${pr_body_file}" ]]; then + rm "${pr_body_file}" + fi + fi + fi +} +trap return_to_kansas EXIT + +# Pure text-processing function: builds the PR title, body, and labels from +# pre-fetched PR metadata. Makes NO gh/git calls so it can be unit-tested +# with canned input. +# +# Inputs (must be set before calling): +# rel - release branch basename (e.g. "release-v3.14") +# PULL_TITLES[] - array of PR titles +# PULL_BODIES[] - array of PR bodies +# PULL_LABELS[] - array of PR labels (one element per PR, labels +# within each element separated by newlines) +# PULLLINK[] - array of PR links (e.g. "org/repo#123") +# SRC_MAIN_REPO_ORG, SRC_MAIN_REPO_NAME +# DST_MAIN_REPO_ORG, DST_MAIN_REPO_NAME +# EXTRA_LABELS - (optional) extra comma-separated labels to append +# +# Outputs (set after calling): +# NEW_PR_TITLE - generated PR title (without [relshort] prefix) +# NEW_PR_BODY - generated PR body +# NEW_PR_LABELS - generated comma-separated labels +function build-pr-description() { + local new_labels="" + local new_body="" + local new_header=$'**Cherry-pick history**\n' + local new_title="" + local idx=0 + + if [[ "${#PULL_TITLES[@]}" -gt 1 ]]; then + new_header+="- Pick onto **$rel**:"$'\n' + fi + for idx in "${!PULL_TITLES[@]}"; do + local pull_title="${PULL_TITLES[$idx]}" + local pull_body="${PULL_BODIES[$idx]}" + local pull_labels="${PULL_LABELS[$idx]}" + new_labels+="${pull_labels}"$'\n' + local stripped_title=$(echo "${pull_title}" | sed 's/^\[.*\] //') # Remove any previous branch tag prefix [...] + + if [ "$SRC_MAIN_REPO_ORG" != "$DST_MAIN_REPO_ORG" ] || [ "$SRC_MAIN_REPO_NAME" != "$DST_MAIN_REPO_NAME" ]; then + # Prefix bare PR/issue numbers with source repo to ensure correct GitHub links + # when picking between repos. Only match PR tags that are not already prefixed with org/repo. + pull_body=$(echo "$pull_body" | sed "s/\([^a-zA-Z0-9_.-]\|^\)#\([0-9]\+\)/\1${SRC_MAIN_REPO_ORG}\/${SRC_MAIN_REPO_NAME}#\2/g") + fi + + # Remove sections that are not relevant to cherry-picked PRs. + for section in "Todos" "Reminder for the reviewer"; do + pull_body=$(echo "$pull_body" | awk ' + /^## '"$section"'/ { skip=1; next } + /^#/ && skip { skip=0 } + !skip + ') + done + + if [[ "${#PULL_TITLES[@]}" -gt 1 ]]; then + new_header+=" - ${PULLLINK[idx]}"$'\n' + else + new_header+="- Pick onto **$rel**: ${PULLLINK[idx]}"$'\n' + fi + + if [[ -n "${new_title}" ]]; then + new_title+="; " + fi + new_title+="${stripped_title}" + if [[ "${#PULL_TITLES[@]}" -gt 1 ]]; then + # Picking multiple PRs, so use a separator and link to each PR. + new_body+=$'\n---\n' + new_body+="**$pull_title** (${PULLLINK[idx]})"$'\n' + else + # If the body already starts with a cherry-pick history header, strip + # it (the old bullets remain in place and stack beneath ours). + # Otherwise add a blank line to separate the new header from the body. + local first_line + first_line=$(echo "${pull_body}" | head -n1) + if [[ "${first_line}" == "## Cherry-pick history" || "${first_line}" == "**Cherry-pick history**" ]]; then + pull_body=$(echo "${pull_body}" | tail -n +2) + else + new_header+=$'\n' + fi + fi + new_body+="${pull_body}" + done + + new_labels=$(echo -e "${new_labels}" | sort -u | grep '.' | grep -v 'cherry-pick-candidate' | paste -sd,) + if [[ -n "${EXTRA_LABELS:-}" ]]; then + if [[ -n "${new_labels}" ]]; then + new_labels+=",${EXTRA_LABELS}" + else + new_labels="${EXTRA_LABELS}" + fi + fi + + NEW_PR_TITLE="${new_title}" + NEW_PR_BODY="${new_header}${new_body}"$'\n'"${META_BLOCK:-}" + NEW_PR_LABELS="${new_labels}" +} + +function make-a-pr() { + echo + echo "+++ Creating a pull request on GitHub at ${GITHUB_USER}:${NEWBRANCH}" + + local rel="$(basename "${BRANCH}")" + + # Shorten release-calient-v3.xx to v3.xx. + local relshort="${rel#release-}" + relshort="${relshort#calient-}" + + # Fetch PR metadata from GitHub. + local -a PULL_TITLES=() + local -a PULL_BODIES=() + local -a PULL_LABELS=() + for pullnumber in "${PULLS[@]}"; do + local pull_json=$(gh pr view -R "${SRC_MAIN_REPO_ORG}/${SRC_MAIN_REPO_NAME}" "${pullnumber}" --json title,body,labels) + PULL_TITLES+=( "$(echo "${pull_json}" | jq -r '.title')" ) + PULL_BODIES+=( "$(echo "${pull_json}" | jq -r '.body')" ) + PULL_LABELS+=( "$(echo "${pull_json}" | jq -r '.labels[].name' | paste -sd$'\n')" ) + done + + # Build the PR description (pure text processing, no API calls). + build-pr-description + + pr_body_file="$(mktemp -t prtext.XXXX)" # cleaned in return_to_kansas + printf '%s\n' "${NEW_PR_BODY}" >"${pr_body_file}" + + if [[ -n "${DRY_RUN}" ]]; then + echo "!!! Skipping PR creation. PR text would have been:" + echo + cat "${pr_body_file}" + else + head_ref="${GITHUB_USER}:${NEWBRANCH}" + + # If AUTO_CREATE_PR is set and true, there is no fork involved. + if [[ -n "${AUTO_CREATE_PR}" ]]; then + head_ref="${NEWBRANCH}" + fi + + echo "+++ Creating PR into ${DST_MAIN_REPO_ORG}/${DST_MAIN_REPO_NAME}. Running: gh pr create -t \"[${relshort}] ${NEW_PR_TITLE}\" -F \"${pr_body_file}\" --repo \"${DST_MAIN_REPO_ORG}/${DST_MAIN_REPO_NAME}\" -H \"${head_ref}\" -B \"${rel}\" -l \"${NEW_PR_LABELS}\"" + gh pr create \ + -t "[${relshort}] ${NEW_PR_TITLE}" \ + -F "${pr_body_file}" \ + --repo "${DST_MAIN_REPO_ORG}/${DST_MAIN_REPO_NAME}" \ + -H "${head_ref}" \ + -B "${rel}" \ + -l "${NEW_PR_LABELS}" + fi +} + +function create-branch-and-pr() { + echo "+++ Creating local branch ${NEWBRANCHUNIQ}" + git checkout -b "${NEWBRANCHUNIQ}" "${BRANCH}" + cleanbranch="${NEWBRANCHUNIQ}" + + for pull in "${PULLS[@]}"; do + if [[ -n "${CHERRY_PICK}" ]]; then + echo "+++ Cherry-picking PR ${pull} from ${SRC_MAIN_REPO_ORG}/${SRC_MAIN_REPO_NAME}" + local merge_commit=$(gh pr view --repo ${SRC_MAIN_REPO_ORG}/${SRC_MAIN_REPO_NAME} ${pull} --json mergeCommit -q .mergeCommit.oid) + if [[ -z "${merge_commit}" || "${merge_commit}" == "null" ]]; then + echo "!!! PR ${pull} has bad/missing merge commit (perhaps it's not merged yet?), cannot cherry-pick." + exit 1 + fi + apply_cmd="git cherry-pick -x --mainline=1 ${merge_commit}" + add_cmd_msg="git add / git cherry-pick --continue" + add_cmd="true" # No-op, cherry-pick commits for us + gitcherrycleanup=true + elif [[ -n "${SQUASH_COMMITS}" ]]; then + echo "+++ Downloading patch to /tmp/${pull}.patch (in case you need to do this again)" + gh pr diff --repo ${SRC_MAIN_REPO_ORG}/${SRC_MAIN_REPO_NAME} ${pull} > /tmp/${pull}.patch + apply_cmd="git apply -p1 -3 \"/tmp/${pull}.patch\"" + add_cmd_msg="git add" + add_cmd="git add" + else + echo "+++ Downloading patch to /tmp/${pull}.patch (in case you need to do this again)" + gh pr diff --repo ${SRC_MAIN_REPO_ORG}/${SRC_MAIN_REPO_NAME} ${pull} --patch > /tmp/${pull}.patch + apply_cmd="git am --empty=keep -3 \"/tmp/${pull}.patch\"" + add_cmd_msg="git add / git am --continue" + add_cmd="true" # No-op, am commits for us + gitamcleanup=true + fi + + echo + echo "+++ About to attempt cherry pick of PR using:" + echo " $ ${apply_cmd}" + echo + + if (eval "${apply_cmd}"); then + # If patch is applied cleanly, add files, they will be committed later + eval "$add_cmd" + else + if [[ -n "${FAIL_ON_CONFLICT}" ]]; then + echo "Conflicts detected and FAIL_ON_CONFLICT is set, aborting." + exit 1 + fi + + # If there are conflicts, prompt user to resolve them in another window and continue + conflicts=false + while unmerged=$(git status --porcelain | grep ^U) && [[ -n ${unmerged} ]] \ + || [[ -e "${REBASEMAGIC}" ]]; do + conflicts=true # <-- We should have detected conflicts once + echo + echo "+++ Conflicts detected:" + echo + (git status --porcelain | grep ^U) || echo "!!! None. Did you '${add_cmd_msg}'?" + echo + echo "+++ Please resolve the conflicts in another window (and remember to '${add_cmd_msg}')" + read -p "+++ Proceed (anything but 'y' aborts the cherry-pick)? [y/n] " -r + echo + if ! [[ "${REPLY}" =~ ^[yY]$ ]]; then + echo "Aborting." >&2 + exit 1 + fi + done + + if [[ "${conflicts}" != "true" ]]; then + echo "!!! git cherry-pick / am / git apply failed, likely because of an in-progress 'git am' or 'git rebase'" + exit 1 + fi + fi + + # When squashing, commit patch changes with the same message as the merge commit from the original PR + if [[ -n "${SQUASH_COMMITS}" ]]; then + merge_commit_hash=$(gh pr view --repo "${SRC_MAIN_REPO_ORG}/${SRC_MAIN_REPO_NAME}" ${pull} --json mergeCommit -q .mergeCommit.oid) + if [[ -z "${merge_commit_hash}" || "${merge_commit_hash}" == "null" ]]; then + echo "!!! PR ${pull} has bad/missing merge commit (perhaps it's not merged yet?), using generic commit message." + git commit -m "Pick ${SRC_MAIN_REPO_ORG}/${SRC_MAIN_REPO_NAME}#${pull}" + else + git commit -C "${merge_commit_hash}" + fi + fi + + # remove the patch file from /tmp + rm -f "/tmp/${pull}.patch" + done + gitamcleanup=false + + # Re-generate docs (if needed) + if [[ -n "${REGENERATE_DOCS}" ]]; then + echo + echo "Regenerating docs..." + if ! hack/generate-docs.sh; then + echo + echo "hack/generate-docs.sh FAILED to complete." + exit 1 + fi + fi + + if [[ -n "${DRY_RUN}" ]]; then + # Output the PR text to the console. + make-a-pr + + echo "!!! Skipping git push and PR creation because you set DRY_RUN." + echo "To return to the branch you were in when you invoked this script:" + echo + echo " git checkout ${STARTINGBRANCH}" + echo + echo "To delete this branch:" + echo + echo " git branch -D ${NEWBRANCHUNIQ}" + exit 0 + fi + + if git remote -v | grep ^"${FORK_REMOTE}" | grep \<"${DST_MAIN_REPO_ORG}"/"${DST_MAIN_REPO_NAME}".git; then + echo "!!! You have ${FORK_REMOTE} configured as your ${DST_MAIN_REPO_ORG}/${DST_MAIN_REPO_NAME}.git" + echo "This isn't normal. Leaving you with push instructions:" + echo + echo "+++ First manually push the branch this script created:" + echo + echo " git push REMOTE ${NEWBRANCHUNIQ}:${NEWBRANCH}" + echo + echo "where REMOTE is your personal fork (maybe ${DST_UPSTREAM_REMOTE}? Consider swapping those.)." + echo "OR consider setting SRC_UPSTREAM_REMOTE, DST_UPSTREAM_REMOTE, and FORK_REMOTE to different values." + echo + make-a-pr + cleanbranch="" + exit 0 + fi + + echo + echo "+++ I'm about to do the following to push to GitHub (and I'm assuming ${FORK_REMOTE} is your personal fork):" + echo + echo " git push ${FORK_REMOTE} ${NEWBRANCHUNIQ}:${NEWBRANCH}" + echo + if [[ -n "${AUTO_CREATE_PR}" ]]; then + echo "AUTO_CREATE_PR is set, proceeding without prompt." + else + read -p "+++ Proceed (anything but 'y' aborts the cherry-pick)? [y/n] " -r + if ! [[ "${REPLY}" =~ ^[yY]$ ]]; then + echo "Aborting." >&2 + exit 1 + fi + fi + + git push "${FORK_REMOTE}" -f "${NEWBRANCHUNIQ}:${NEWBRANCH}" + make-a-pr +} + +# Check if the script is being executed directly (not sourced). +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + # This script is being executed directly, should call its functions. + init-vars "$@" + create-branch-and-pr +fi diff --git a/hack/cherry-pick-pull_test.sh b/hack/cherry-pick-pull_test.sh new file mode 100644 index 00000000000..923d8843fad --- /dev/null +++ b/hack/cherry-pick-pull_test.sh @@ -0,0 +1,325 @@ +#!/usr/bin/env bash + +# Unit tests for build-pr-description() in hack/cherry-pick-pull. +# Run with: bash hack/cherry-pick-pull_test.sh + +set -o errexit +set -o nounset +set -o pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/cherry-pick-pull" + +PASS=0 +FAIL=0 + +# assert_equals ACTUAL EXPECTED [MESSAGE] +assert_equals() { + local actual="$1" expected="$2" msg="${3:-}" + if [[ "$actual" == "$expected" ]]; then + return 0 + fi + echo "FAIL${msg:+: $msg}" + echo " expected: $(printf '%q' "$expected")" + echo " actual: $(printf '%q' "$actual")" + FAIL=$((FAIL + 1)) + return 1 +} + +run_test() { + local name="$1" + # Reset outputs and defaults + NEW_PR_TITLE="" + NEW_PR_BODY="" + NEW_PR_LABELS="" + EXTRA_LABELS="" + META_BLOCK="" + SRC_MAIN_REPO_ORG="projectcalico" + SRC_MAIN_REPO_NAME="calico" + DST_MAIN_REPO_ORG="projectcalico" + DST_MAIN_REPO_NAME="calico" + echo -n " ${name}... " +} + +pass() { + echo "ok" + PASS=$((PASS + 1)) +} + +############################################################################### +# Test 1: Fresh single pick — no cherry-pick history in source body +############################################################################### +run_test "fresh single pick" + +rel="release-v3.14" +PULL_TITLES=( "Fix widget rendering" ) +PULL_BODIES=( "This PR fixes the widget rendering bug. + +## Description +Some details here." ) +PULL_LABELS=( "bug +docs-not-required" ) +PULLLINK=( "projectcalico/calico#100" ) + +build-pr-description + +expected_body="**Cherry-pick history** +- Pick onto **release-v3.14**: projectcalico/calico#100 + +This PR fixes the widget rendering bug. + +## Description +Some details here. +" +assert_equals "$NEW_PR_TITLE" "Fix widget rendering" "title" && +assert_equals "$NEW_PR_BODY" "$expected_body" "body" && +assert_equals "$NEW_PR_LABELS" "bug,docs-not-required" "labels" && +pass + +############################################################################### +# Test 2: Re-pick with old ## heading format +############################################################################### +run_test "re-pick (old ## format)" + +rel="release-v3.15" +PULL_TITLES=( "[v3.14] Fix widget rendering" ) +PULL_BODIES=( "## Cherry-pick history +- Pick onto **release-v3.14**: projectcalico/calico#100 + +This PR fixes the widget rendering bug." ) +PULL_LABELS=( "bug" ) +PULLLINK=( "projectcalico/calico#200" ) + +build-pr-description + +expected_body="**Cherry-pick history** +- Pick onto **release-v3.15**: projectcalico/calico#200 +- Pick onto **release-v3.14**: projectcalico/calico#100 + +This PR fixes the widget rendering bug. +" +assert_equals "$NEW_PR_TITLE" "Fix widget rendering" "title" && +assert_equals "$NEW_PR_BODY" "$expected_body" "body" && +pass + +############################################################################### +# Test 3: Re-pick with new **bold** heading format +############################################################################### +run_test "re-pick (new ** format)" + +rel="release-v3.15" +PULL_TITLES=( "[v3.14] Fix widget rendering" ) +PULL_BODIES=( "**Cherry-pick history** +- Pick onto **release-v3.14**: projectcalico/calico#100 + +This PR fixes the widget rendering bug." ) +PULL_LABELS=( "bug" ) +PULLLINK=( "projectcalico/calico#300" ) + +build-pr-description + +expected_body="**Cherry-pick history** +- Pick onto **release-v3.15**: projectcalico/calico#300 +- Pick onto **release-v3.14**: projectcalico/calico#100 + +This PR fixes the widget rendering bug. +" +assert_equals "$NEW_PR_TITLE" "Fix widget rendering" "title" && +assert_equals "$NEW_PR_BODY" "$expected_body" "body" && +pass + +############################################################################### +# Test 4: Re-pick of a re-pick — multiple stacked bullet lines +############################################################################### +run_test "re-pick of a re-pick (stacked bullets)" + +rel="release-v3.16" +PULL_TITLES=( "[v3.15] Fix widget rendering" ) +PULL_BODIES=( "**Cherry-pick history** +- Pick onto **release-v3.15**: projectcalico/calico#300 +- Pick onto **release-v3.14**: projectcalico/calico#100 + +This PR fixes the widget rendering bug." ) +PULL_LABELS=( "bug" ) +PULLLINK=( "projectcalico/calico#400" ) + +build-pr-description + +expected_body="**Cherry-pick history** +- Pick onto **release-v3.16**: projectcalico/calico#400 +- Pick onto **release-v3.15**: projectcalico/calico#300 +- Pick onto **release-v3.14**: projectcalico/calico#100 + +This PR fixes the widget rendering bug. +" +assert_equals "$NEW_PR_TITLE" "Fix widget rendering" "title" && +assert_equals "$NEW_PR_BODY" "$expected_body" "body" && +pass + +############################################################################### +# Test 4b: Re-pick of a multi-PR pick — indented sub-bullets +############################################################################### +run_test "re-pick of multi-PR pick (indented bullets)" + +rel="release-v3.15" +PULL_TITLES=( "[v3.14] Fix widget rendering; Update docs for widget" ) +PULL_BODIES=( "**Cherry-pick history** +- Pick onto **release-v3.14**: + - projectcalico/calico#100 + - projectcalico/calico#101 + +The actual PR body starts here." ) +PULL_LABELS=( "bug" ) +PULLLINK=( "projectcalico/calico#500" ) + +build-pr-description + +expected_body="**Cherry-pick history** +- Pick onto **release-v3.15**: projectcalico/calico#500 +- Pick onto **release-v3.14**: + - projectcalico/calico#100 + - projectcalico/calico#101 + +The actual PR body starts here. +" +assert_equals "$NEW_PR_TITLE" "Fix widget rendering; Update docs for widget" "title" && +assert_equals "$NEW_PR_BODY" "$expected_body" "body" && +pass + +############################################################################### +# Test 5: Multi-PR pick +############################################################################### +run_test "multi-PR pick" + +rel="release-v3.14" +PULL_TITLES=( "Fix widget rendering" "Update docs for widget" ) +PULL_BODIES=( "Widget fix body." "Docs update body." ) +PULL_LABELS=( "bug" "docs-completed" ) +PULLLINK=( "projectcalico/calico#100" "projectcalico/calico#101" ) + +build-pr-description + +expected_body="**Cherry-pick history** +- Pick onto **release-v3.14**: + - projectcalico/calico#100 + - projectcalico/calico#101 + +--- +**Fix widget rendering** (projectcalico/calico#100) +Widget fix body. +--- +**Update docs for widget** (projectcalico/calico#101) +Docs update body. +" +assert_equals "$NEW_PR_TITLE" "Fix widget rendering; Update docs for widget" "title" && +assert_equals "$NEW_PR_BODY" "$expected_body" "body" && +assert_equals "$NEW_PR_LABELS" "bug,docs-completed" "labels" && +pass + +############################################################################### +# Test 6: Cross-repo pick — bare #123 refs get prefixed +############################################################################### +run_test "cross-repo pick (bare # refs prefixed)" + +rel="release-v3.14" +SRC_MAIN_REPO_ORG="tigera" +SRC_MAIN_REPO_NAME="calico-private" +PULL_TITLES=( "Fix issue" ) +PULL_BODIES=( "Fixes #42 and relates to #99. +Already prefixed: tigera/calico-private#50 should stay." ) +PULL_LABELS=( "bug" ) +PULLLINK=( "tigera/calico-private#500" ) + +build-pr-description + +expected_body="**Cherry-pick history** +- Pick onto **release-v3.14**: tigera/calico-private#500 + +Fixes tigera/calico-private#42 and relates to tigera/calico-private#99. +Already prefixed: tigera/calico-private#50 should stay. +" +assert_equals "$NEW_PR_TITLE" "Fix issue" "title" && +assert_equals "$NEW_PR_BODY" "$expected_body" "body" && +pass + +############################################################################### +# Test 7: EXTRA_LABELS and cherry-pick-candidate filtering +############################################################################### +run_test "EXTRA_LABELS and cherry-pick-candidate filtered" + +rel="release-v3.14" +EXTRA_LABELS="extra-label" +PULL_TITLES=( "Some fix" ) +PULL_BODIES=( "Body text." ) +PULL_LABELS=( "bug +cherry-pick-candidate" ) +PULLLINK=( "projectcalico/calico#100" ) + +build-pr-description + +assert_equals "$NEW_PR_LABELS" "bug,extra-label" "labels" && +pass + +############################################################################### +# Test 8: Sections (Todos, Reminder for the reviewer) are stripped +############################################################################### +run_test "irrelevant sections stripped" + +rel="release-v3.14" +PULL_TITLES=( "A change" ) +PULL_BODIES=( "Main description. + +## Todos +- [ ] something to do + +## Reminder for the reviewer +Check the thing. + +## Release note +Important note." ) +PULL_LABELS=( "enhancement" ) +PULLLINK=( "projectcalico/calico#100" ) + +build-pr-description + +expected_body="**Cherry-pick history** +- Pick onto **release-v3.14**: projectcalico/calico#100 + +Main description. + +## Release note +Important note. +" +assert_equals "$NEW_PR_BODY" "$expected_body" "body" && +pass + +############################################################################### +# Test 9: META_BLOCK is included +############################################################################### +run_test "META_BLOCK included" + +rel="release-v3.14" +META_BLOCK="" +PULL_TITLES=( "A change" ) +PULL_BODIES=( "Body." ) +PULL_LABELS=( "enhancement" ) +PULLLINK=( "projectcalico/calico#100" ) + +build-pr-description + +expected_body="**Cherry-pick history** +- Pick onto **release-v3.14**: projectcalico/calico#100 + +Body. +" +assert_equals "$NEW_PR_BODY" "$expected_body" "body" && +pass + +############################################################################### +# Summary +############################################################################### +echo +echo "Results: ${PASS} passed, ${FAIL} failed" +if [[ "${FAIL}" -gt 0 ]]; then + exit 1 +fi diff --git a/hack/cmd/deps/deps.go b/hack/cmd/deps/deps.go index edb1269a8fa..824401ce501 100644 --- a/hack/cmd/deps/deps.go +++ b/hack/cmd/deps/deps.go @@ -1,12 +1,15 @@ package main import ( + "bufio" "bytes" "encoding/json" "flag" "fmt" + "io/fs" "os" "os/exec" + "path/filepath" "regexp" "runtime" "sort" @@ -23,25 +26,24 @@ import ( func printUsageAndExit() { _, _ = fmt.Fprint(os.Stderr, `CI Dependency helper tool. -Usage: +Usage: deps [options] modules # Print go modules that package depends on deps [options] local-dirs # Print in-repo go package dirs that # package depends on. - deps local-dirs-main-only # Print in-repo go package dirs that + deps local-dirs-main-only # Print in-repo go package dirs that # package depends on. (Main packages only.) deps [options] local-dirs-main-only # Print in-repo go package dirs that # package depends on. (Main packages only.) deps [options] test-exclusions # Print glob patterns to match *_test.go - # files in dependency dirs outside the + # files in dependency dirs outside the # package itself. deps [options] sem-change-in(-pretty) [,...] # Print a SemaphoreCI - # conditions DSL change_in() clause for + # conditions DSL change_in() clause for # , including non-test deps from # any clauses. - deps [options] replace-sem-change-in # Replace all ${CHANGE_IN(...)} - # placeholders in a file + deps [options] generate-semaphore-yamls # Generate Semaphore pipeline YAMLs Options: @@ -52,13 +54,15 @@ The test-exclusions and sem-change-in sub-commands are intended to be used with packages at the top-level of the repo. Test exclusions are based on whether a dependency is within the package. -The change_in() clause always depends on the whole package directory itself. +The change_in() clause always depends on the whole package directory itself. Some non-Go dependencies are hard-coded in the tool. For example, it knows that node depends on felix/bpf-*. `) os.Exit(1) } +const mainBranchName = "master" + var ( pretty = flag.Bool("pretty", false, "Pretty-print the output (only applies to sem-change-in).") logLevel = flag.String("loglevel", "warn", "Logrus log level (debug, info, warn, error, fatal, panic).") @@ -79,13 +83,20 @@ func main() { logutils.ConfigureFormatter("deps") args := flag.Args() - if len(args) != 2 { - logrus.Warnf("Incorrect number of arguments: %v", args) + if len(args) == 0 { + logrus.Warnf("Missing sub-command") printUsageAndExit() } cmd := args[0] - pkg := args[1] + var pkg string + if cmd != "generate-semaphore-yamls" { + if len(args) != 2 { + logrus.Warnf("Incorrect number of arguments for %s: %v", cmd, args) + printUsageAndExit() + } + pkg = args[1] + } switch cmd { case "modules": @@ -98,8 +109,8 @@ func main() { printTestExclusions(pkg) case "sem-change-in": printSemChangeIn(pkg, *pretty) - case "replace-sem-change-in": - replaceSemChangeInPlaceholders(pkg) + case "generate-semaphore-yamls": + generateSemaphoreYamls() default: printUsageAndExit() } @@ -121,17 +132,20 @@ var nonGoDeps = map[string][]string{ // BPF programs. "/felix/bpf-apache", "/felix/bpf-gpl", + // Root-level Makefile is used to build operator and other images, + // used by the STs. + "/Makefile", + }, + + "e2e": { + // Root-level Makefile is used to build the operator and other images. + "/Makefile", }, // Whisker is not a go project so we list the whole thing. "whisker": { "/whisker", }, - - // Process is not a go project so we list the whole thing. - "process": { - "/process", - }, } var defaultExclusions = []string{ @@ -145,91 +159,120 @@ var defaultExclusions = []string{ "/**/*.md", } -func replaceSemChangeInPlaceholders(yamlFile string) { - fileContents, err := os.ReadFile(yamlFile) - if err != nil { - logrus.Fatalf("Failed to read file %s: %s", yamlFile, err) - } - fileStat, err := os.Stat(yamlFile) - if err != nil { - logrus.Fatalf("Failed to stat file %s: %s", yamlFile, err) - } +// extraPrereqRegexps captures some out-of-band knowledge that helps to weed +// out false dependencies. Before emitting a dependency on the key, +// it checks whether any files in the package match the given regexp. If they +// don't the dependency is skipped. +var extraPrereqRegexps = map[string]*regexp.Regexp{ + // Every project that imports the libcalico-go client would depend on the + // IPAM package due to transitive import. Only list the IPAM package as a + // dependency if it's actually used. + "/libcalico-go/lib/ipam": regexp.MustCompile(`\.IPAM\(\)`), +} - changeInPattern := regexp.MustCompile(`\$\{CHANGE_IN\(([^)]+)\)}`) - replacements := map[string][]byte{} - for _, groups := range changeInPattern.FindAllSubmatch(fileContents, -1) { - pkg := string(groups[1]) - if _, ok := replacements[pkg]; ok { - continue // already calculated. - } - replacements[pkg] = nil +// calculateDeps calculates the file-level dependencies of the input package +// specs. Each package spec is a comma-delimited list of directories relative +// to the root of the repo. The first entry in the list is the "primary" +// package for which we include all Go files, including test files and all +// their dependencies. The subsequent items are "secondary" build-only +// dependencies, for which we include non-test files only. +func calculateDeps(packages set.Set[string]) map[string]*Deps { + deps := map[string]*Deps{} + for pkg := range packages.All() { + deps[pkg] = nil } var lock sync.Mutex var eg errgroup.Group eg.SetLimit(runtime.NumCPU()) - for pkg := range replacements { + for pkg := range deps { eg.Go(func() error { - repl, err := calculateChangeIn(pkg, false) + repl, err := calculateSemDeps(pkg) if err != nil { return fmt.Errorf("failed to calculate change_in for package %s: %w", pkg, err) } lock.Lock() - replacements[pkg] = []byte(repl) + deps[pkg] = repl lock.Unlock() return nil }) } if err := eg.Wait(); err != nil { - logrus.Fatalln("Failed to calculate change_in replacements:", err) + logrus.Fatalln("Failed to calculate change_in dependencies:", err) } + return deps +} - newContents := changeInPattern.ReplaceAllFunc(fileContents, func(match []byte) []byte { - pkg := changeInPattern.FindSubmatch(match)[1] - return replacements[string(pkg)] - }) +func printSemChangeIn(pkg string, pretty bool) { + changeIn, err := calculateChangeIn(pkg, pretty) + if err != nil { + logrus.Fatalf("Failed to calculate change_in for package %s: %v", pkg, err) + } + _, _ = fmt.Println(changeIn) +} - if err := os.WriteFile(yamlFile, newContents, fileStat.Mode()); err != nil { - logrus.Fatalf("Failed to write file %s: %s", yamlFile, err) +func calculateChangeIn(pkg string, pretty bool) (string, error) { + deps, err := calculateSemDeps(pkg) + if err != nil { + return "", err } + return formatChangeIn(deps.Inclusions, deps.Exclusions, pretty, ""), nil } -func printSemChangeIn(pkg string, pretty bool) { - _, _ = fmt.Println(calculateChangeIn(pkg, pretty)) +func formatChangeIn(inclusions set.Set[string], exclusions set.Set[string], pretty bool, defaultBranchStanza string) string { + incl := formatSemList(inclusions) + excl := formatSemList(exclusions) + if pretty { + incl = "\n" + strings.ReplaceAll(incl, ",", ",\n ") + "\n" + excl = "\n" + strings.ReplaceAll(excl, ",", ",\n ") + "\n" + } + out := fmt.Sprintf("change_in(%s, {pipeline_file: 'ignore', exclude: %s%s})", incl, excl, defaultBranchStanza) + return out } -func calculateChangeIn(pkg string, pretty bool) (string, error) { - parts := strings.Split(pkg, ",") - pkg = parts[0] +type Deps struct { + Inclusions set.Set[string] + Exclusions set.Set[string] +} + +func calculateSemDeps(pkgList string) (deps *Deps, err error) { + parts := strings.Split(pkgList, ",") + primaryPkg := parts[0] otherPkgs := parts[1:] if len(otherPkgs) == 0 { - logrus.Infof("Calculating deps for %s package", pkg) + logrus.Infof("Calculating deps for %s package", primaryPkg) } else { - logrus.Infof("Calculating deps for %s package; including secondary deps: %v", pkg, otherPkgs) + logrus.Infof("Calculating deps for %s package; including secondary deps: %v", primaryPkg, otherPkgs) } - localDirs, err := loadLocalDirs(pkg, false) + localDirs, err := loadLocalDirs(primaryPkg, false) if err != nil { - return "", fmt.Errorf("failed to load local dirs: %w", err) + return nil, fmt.Errorf("failed to load local dirs: %w", err) } inclusions := set.New[string]() - inclusions.Add("/" + pkg + "/**") + inclusions.Add("/" + primaryPkg + "/**") inclusions.AddAll(defaultInclusions) - inclusions.AddAll(nonGoDeps[pkg]) + inclusions.AddAll(nonGoDeps[primaryPkg]) for _, dir := range localDirs { - if strings.HasPrefix(dir+"/", "/"+pkg) { + if strings.HasPrefix(dir+"/", "/"+primaryPkg) { continue // covered by the whole-package inclusion. } inclusions.Add(dir + "/*.go") } - exclusions := set.From(calculateTestExclusionGlobs(pkg, localDirs)...) + exclusions := set.From(calculateTestExclusionGlobs(primaryPkg, localDirs)...) exclusions.AddAll(defaultExclusions) // Some jobs depend on secondary packages. For example, the node tests // also run typha, api server, etc. Add inclusions for those. for _, otherPkg := range otherPkgs { + const nonGoPrefix = "non-go:" + if after, ok := strings.CutPrefix(otherPkg, nonGoPrefix); ok { + inclusions.Add(after) + continue + } + // For secondary dependencies, we only list dependencies of "main" // packages. This prevents us from picking up test-only dependencies. // For example, kube-controllers FV has utility packages that depend on @@ -237,7 +280,7 @@ func calculateChangeIn(pkg string, pretty bool) (string, error) { // pick those up unintentionally. otherPkgDirs, err := loadLocalDirs(otherPkg, true) if err != nil { - return "", fmt.Errorf("failed to load local dirs for secondary package %s: %w", otherPkg, err) + return nil, fmt.Errorf("failed to load local dirs for secondary package %s: %w", otherPkg, err) } for _, dir := range otherPkgDirs { inclusions.Add(dir + "/*.go") @@ -246,17 +289,59 @@ func calculateChangeIn(pkg string, pretty bool) (string, error) { inclusions.Add(otherPkg + "/deps.txt") inclusions.Add(otherPkg + "/**/*Dockerfile*") inclusions.AddAll(nonGoDeps[otherPkg]) - exclusions.AddAll(calculateTestExclusionGlobs(pkg, otherPkgDirs)) + exclusions.AddAll(calculateTestExclusionGlobs(primaryPkg, otherPkgDirs)) } - incl := formatSemList(inclusions) - excl := formatSemList(exclusions) - if pretty { - incl = "\n" + strings.ReplaceAll(incl, ",", ",\n ") + "\n" - excl = "\n" + strings.ReplaceAll(excl, ",", ",\n ") + "\n" + return &Deps{inclusions, exclusions}, nil +} + +func filterInclusions(primaryPkg string, inclusions set.Set[string]) set.Typed[string] { + out := set.New[string]() + + conditionalIncludes := map[string]*regexp.Regexp{} + for item := range inclusions.All() { + if r := extraPrereqRegexps[item]; r != nil { + conditionalIncludes[item] = r + } else { + out.Add(item) + } } - out := fmt.Sprintf("change_in(%s, {pipeline_file: 'ignore', exclude: %s})", incl, excl) - return out, nil + + if len(conditionalIncludes) == 0 { + return out + } + + dirFS := os.DirFS(".").(fs.ReadFileFS) + err := fs.WalkDir(dirFS, primaryPkg, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + if !strings.HasSuffix(path, ".go") { + return nil + } + if data, err := dirFS.ReadFile(path); err != nil { + return fmt.Errorf("failed to read file %s: %w", path, err) + } else { + for in, re := range conditionalIncludes { + if re.Match(data) { + out.Add(in) + delete(conditionalIncludes, in) + } + } + } + if len(conditionalIncludes) == 0 { + return fs.SkipAll + } + return nil + }) + if err != nil { + logrus.Fatalf("Failed to filter inclusions for %s: %v", primaryPkg, err) + } + + return out } func formatSemList(s set.Set[string]) string { @@ -326,6 +411,8 @@ func loadLocalDirs(pkg string, mainDepsOnly bool) (out []string, err error) { out = append(out, pkg) } } + + out = filterInclusions(pkg, set.FromArray(out)).Slice() sort.Strings(out) return out, nil } @@ -347,6 +434,9 @@ func printModules(pkg string) { // For ease, do the full cross product. Only takes ~100ms. var mods []string for _, mod := range modules { + if mod.Replace != nil { + mod = *mod.Replace + } for _, pkg := range packageDeps { if strings.HasPrefix(pkg, mod.Path) { if mod.Version != "" { @@ -423,6 +513,7 @@ func findMainPackages(pkg string) ([]string, error) { type module struct { Path string Version string + Replace *module } func loadGoMods() ([]module, error) { @@ -430,9 +521,13 @@ func loadGoMods() ([]module, error) { } func loadGoToolJSON[Item any](args ...string) ([]Item, error) { - out, err := exec.Command("go", args...).Output() + cmd := exec.Command("go", args...) + var stderr bytes.Buffer + cmd.Stderr = &stderr + out, err := cmd.Output() if err != nil { - return nil, err + errOut := stderr.Bytes() + return nil, fmt.Errorf("%w, %s", err, string(errOut)) } var items []Item decoder := json.NewDecoder(bytes.NewReader(out)) @@ -446,3 +541,335 @@ func loadGoToolJSON[Item any](args ...string) ([]Item, error) { } return items, nil } + +type templateData struct { + originalPath string + filename string + content string +} + +func generateSemaphoreYamls() { + logrus.Info("Generating semaphore YAML pipeline files") + semaphoreDir := ".semaphore" + defaultBranchStanza, err := calculateBranchStanza(semaphoreDir) + if err != nil { + logrus.Fatalf("Failed to calculate default branch stanza: %v", err) + } + logrus.Infof("Using default branch stanza: %q", defaultBranchStanza) + + // Validate change_in lines in template blocks include pipeline_file stanza. + if err := validateChangeInClauses(semaphoreDir); err != nil { + logrus.Fatalf("Template validation failed: %v", err) + } + + // Load all the template files + templatesDir := filepath.Join(semaphoreDir, "semaphore.yml.d") + templates, err := readTemplates(templatesDir) + if err != nil { + logrus.Fatalf("Failed to read templates: %v", err) + } + var globalExtraDeps []string + for _, t := range templates { + globalExtraDeps = append(globalExtraDeps, "/"+t.originalPath) + } + blocksDir := filepath.Join(semaphoreDir, "semaphore.yml.d", "blocks") + blocks, err := readTemplates(blocksDir) + if err != nil { + logrus.Fatalf("Failed to read templates: %v", err) + } + // For convenience when writing blocks we add an indent here. + blocks = indentBlocks(blocks) + + // But after that, we can treat all templates equally. + templates = append(templates, blocks...) + sort.Slice(templates, func(i, j int) bool { + // Sort only on the filename, so we make use of the numeric prefixes. + return strings.Compare(templates[i].filename, templates[j].filename) < 0 + }) + + // Next, collect all the CHANGE_IN placeholders and do the heavy-lift + // calculation to figure out the dependencies. + placeholders := extractChangeInPlaceholders(templates) + deps := calculateDeps(placeholders) + + // Build the main file, which is triggered by PRs and uses the calculated + // dependencies. + mainFile := filepath.Join(semaphoreDir, "semaphore.yml") + err = buildSemaphoreYAML(mainFile, templates, globalExtraDeps, deps, false, defaultBranchStanza) + if err != nil { + logrus.Fatalf("Failed to build semaphore YAML: %v", err) + } + + // Build the scheduled file, which builds all our code, but not slow + // third-party builds. + scheduledFile := filepath.Join(semaphoreDir, "semaphore-scheduled-builds.yml") + err = buildSemaphoreYAML(scheduledFile, templates, globalExtraDeps, nil, false, defaultBranchStanza) + if err != nil { + logrus.Fatalf("Failed to build semaphore YAML: %v", err) + } + + // If needed, build the third-party file, which runs weekly. + thirdPartyFile := filepath.Join(semaphoreDir, "semaphore-third-party-builds.yml") + var weeklyTemplates []templateData + foundWeekly := false + for _, t := range templates { + switch t.filename { + case "01-preamble.yml", + "02-global_job_config.yml", + "10-prerequisites.yml", + "09-blocks.yml": + weeklyTemplates = append(weeklyTemplates, t) + default: + if strings.Contains(t.content, "WEEKLY_RUN") { + weeklyTemplates = append(weeklyTemplates, t) + foundWeekly = true + } + } + } + if foundWeekly { + logrus.Infof("Found templates that run weekly, generating %s.", thirdPartyFile) + err = buildSemaphoreYAML(thirdPartyFile, weeklyTemplates, globalExtraDeps, nil, true, defaultBranchStanza) + if err != nil { + logrus.Fatalf("Failed to build semaphore YAML: %v", err) + } + } + + logrus.Info("Semaphore YAML generation complete") +} + +func buildSemaphoreYAML(file string, templates []templateData, globalExtraDeps []string, deps map[string]*Deps, weekly bool, defaultBranchStanza string) error { + var data bytes.Buffer + + data.WriteString( + "# !! WARNING, DO NOT EDIT !! This file is generated from the templates\n" + + "# in /.semaphore/semaphore.yml.d. To update, modify the relevant\n" + + "# template and then run 'make gen-semaphore-yaml'.\n", + ) + + // Force the extraDeps := append(...) call below to allocate a fresh copy + // by capping the len/cap of globalExtraDeps. + globalExtraDeps = globalExtraDeps[:len(globalExtraDeps):len(globalExtraDeps)] + + weeklyRun := "false" + if weekly { + weeklyRun = "true" + } + forceRun := "false" + if deps == nil { + forceRun = "true" + } + for _, t := range templates { + extraDeps := append(globalExtraDeps, "/"+t.originalPath) + changeInPattern := regexp.MustCompile(`\$\{CHANGE_IN\(([^)]+)\)}`) + content := changeInPattern.ReplaceAllStringFunc(t.content, func(match string) string { + pkg := changeInPattern.FindStringSubmatch(match)[1] + if deps == nil { + // Generating a daily/weekly file. + return "true" + } + dep := deps[pkg] + inclusions := dep.Inclusions.Copy() + for _, d := range extraDeps { + inclusions.Add(d) + } + return formatChangeIn(inclusions, dep.Exclusions, false, defaultBranchStanza) + }) + content = strings.ReplaceAll(content, "${FORCE_RUN}", forceRun) + content = strings.ReplaceAll(content, "${WEEKLY_RUN}", weeklyRun) + content = strings.ReplaceAll(content, "${DEFAULT_BRANCH}", defaultBranchStanza) + _, _ = data.WriteString(content) + } + + return os.WriteFile(file, data.Bytes(), 0644) +} + +func indentBlocks(blocks []templateData) []templateData { + for i, block := range blocks { + lines := strings.Split(block.content, "\n") + var indented strings.Builder + for _, line := range lines { + if line == "" { + // Ignore blank lines (and in particular, the empty "line" that + // Split() creates if the content ends with a newline. + continue + } + indented.WriteString(" " + line + "\n") + } + blocks[i].content = indented.String() + } + return blocks +} + +func readTemplates(templatesDir string) ([]templateData, error) { + templateFiles, err := os.ReadDir(templatesDir) + if err != nil { + return nil, fmt.Errorf("failed to read templates directory: %w", err) + } + var templates []templateData + for _, t := range templateFiles { + if t.IsDir() { + continue + } + if !strings.HasSuffix(t.Name(), ".yml") { + continue + } + origPath := filepath.Join(templatesDir, t.Name()) + contentBytes, err := os.ReadFile(origPath) + if err != nil { + return nil, fmt.Errorf("failed to read template file %s: %v", origPath, err) + } + content := string(contentBytes) + // Clean up extra space at end of file; but ensure there's one newline. + content = strings.TrimRight(content, "\n ") + content += "\n" + templates = append(templates, templateData{ + originalPath: origPath, + filename: t.Name(), + content: content, + }) + } + return templates, err +} + +func mustReadFile(path string) []byte { + b, err := os.ReadFile(path) + if err != nil { + logrus.Fatalf("Failed to read %s: %v", path, err) + } + return b +} + +func extractChangeInPlaceholders(templates []templateData) set.Set[string] { + pattern := regexp.MustCompile(`\$\{CHANGE_IN\(([^)]+)\)}`) + out := set.New[string]() + for _, t := range templates { + matches := pattern.FindAllStringSubmatch(t.content, -1) + for _, m := range matches { + out.Add(m[1]) + } + } + return out +} + +func validateChangeInClauses(semaphoreDir string) error { + root := filepath.Join(semaphoreDir, "semaphore.yml.d") + var offending []string + err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() || !strings.HasSuffix(path, ".yml") { + return nil + } + data := mustReadFile(path) + scanner := bufio.NewScanner(bytes.NewReader(data)) + for scanner.Scan() { + l := scanner.Text() + if strings.Contains(l, "change_in(") && !strings.Contains(l, "pipeline_file:") { + offending = append(offending, fmt.Sprintf("%s: %s", path, l)) + } + } + return nil + }) + if err != nil { + return err + } + if len(offending) > 0 { + return fmt.Errorf("all change_in clauses must include pipeline_file: 'ignore' (or 'track'). Offending lines:\n%s", strings.Join(offending, "\n")) + } + return nil +} + +func calculateBranchStanza(semaphoreDir string) (string, error) { + branch, err := calculateDefaultBranch(semaphoreDir) + if err != nil { + return "", err + } + if branch == mainBranchName { + // Default, so no need to specify. + return "", nil + } + return fmt.Sprintf(", default_branch: '%s'", branch), nil +} + +func calculateDefaultBranch(semaphoreDir string) (string, error) { + if branch := os.Getenv("DEFAULT_BRANCH_OVERRIDE"); branch != "" { + // Manual override. + logrus.Infof("Using DEFAULT_BRANCH_OVERRIDE for default branch: %s", branch) + return branch, nil + } + + if branch := os.Getenv("SEMAPHORE_GIT_BRANCH"); branch != "" { + // In CI, this env var is set either to the current branch, if we're + // building on a branch, or to the target branch if we're building + // a PR. + logrus.Infof("Using SEMAPHORE_GIT_BRANCH for default branch: %s", branch) + return branch, nil + } + + // Fallback to git. + out, err := exec.Command("git", "branch", "--show-current").Output() + if err != nil { + return "", fmt.Errorf("git branch --show-current failed: %w", err) + } + branch := strings.TrimSpace(string(out)) + + if branch == mainBranchName { + logrus.Info("On master branch, using that for default branch.") + return branch, nil + } + + // Check for release branch. + releaseBranchPrefix := os.Getenv("RELEASE_BRANCH_PREFIX") + if releaseBranchPrefix == "" { + return "", fmt.Errorf("RELEASE_BRANCH_PREFIX not set") + } + releaseBranchRegexp := regexp.MustCompile(`^` + regexp.QuoteMeta(releaseBranchPrefix) + `-v[\d.-]+$`) + if s := releaseBranchRegexp.FindString(branch); s != "" { + // Explicitly on a release branch, so use that. + logrus.Infof("On release branch %s, using that for default branch.", s) + return s, nil + } + + // If we're not on a release branch, this is likely to be a PR build, + // and the semaphore.yml should have inherited the default from whichever + // branch it was based on. Check there. + logrus.Infof("Branch %q is not a release branch, checking semaphore.yml for default branch.", branch) + detected, err := detectExistingDefaultBranch(filepath.Join(semaphoreDir, "semaphore.yml")) + if err != nil { + return "", fmt.Errorf("detect release branch from semaphore yaml: %w", err) + } + if detected != "" { + logrus.Infof("Found default branch %s in semaphore.yml, using it for default branch.", detected) + return detected, nil + } + + logrus.Info("Found no default branch in semaphore.yml, assuming master.") + return mainBranchName, nil +} + +func detectExistingDefaultBranch(path string) (string, error) { + data, err := os.ReadFile(path) + if err != nil { + if os.IsNotExist(err) { + return "", nil + } + return "", err + } + re := regexp.MustCompile(`default_branch: '([^']+)'`) + matches := re.FindAllStringSubmatch(string(data), -1) + if len(matches) == 0 { + return "", nil + } + branches := set.New[string]() + branch := "" + for _, m := range matches { + branch = m[1] + branches.Add(branch) + } + if branches.Len() > 1 { + return "", fmt.Errorf("detected more than one branch in the current semaphore.yml, bailing out: %v", branches.Slice()) + } + + return branch, nil +} diff --git a/hack/deps.txt b/hack/deps.txt index 8a4bc10db4b..54b1a9a1430 100644 --- a/hack/deps.txt +++ b/hack/deps.txt @@ -2,16 +2,19 @@ This file contains the list of modules that this package depends on in order to trigger CI on changes -go 1.25.1 -github.com/aws/smithy-go v1.22.5 +go 1.25.7 +github.com/aws/smithy-go v1.23.1 github.com/beorn7/perks v1.0.1 github.com/cespare/xxhash/v2 v2.3.0 github.com/coreos/go-semver v0.3.1 -github.com/coreos/go-systemd/v22 v22.5.0 +github.com/coreos/go-systemd/v22 v22.6.0 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc -github.com/emicklei/go-restful/v3 v3.11.0 -github.com/fxamacker/cbor/v2 v2.7.0 +github.com/emicklei/go-restful v2.15.0+incompatible +github.com/emicklei/go-restful/v3 v3.12.2 +github.com/fxamacker/cbor/v2 v2.9.0 github.com/go-ini/ini v1.67.0 +github.com/go-kit/log v0.2.1 +github.com/go-logfmt/logfmt v0.6.0 github.com/go-logr/logr v1.4.3 github.com/go-openapi/jsonpointer v0.21.0 github.com/go-openapi/jsonreference v0.20.2 @@ -20,11 +23,10 @@ github.com/go-playground/locales v0.14.1 github.com/go-playground/universal-translator v0.18.1 github.com/gogo/protobuf v1.3.2 github.com/golang/protobuf v1.5.4 -github.com/google/gnostic-models v0.6.9 -github.com/google/go-cmp v0.7.0 +github.com/google/gnostic-models v0.7.0 github.com/google/uuid v1.6.0 -github.com/grpc-ecosystem/grpc-gateway v1.16.0 -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 github.com/jinzhu/copier v0.4.0 github.com/josharian/intern v1.0.0 github.com/json-iterator/go v1.1.12 @@ -34,58 +36,59 @@ github.com/leodido/go-urn v1.4.0 github.com/mailru/easyjson v0.7.7 github.com/mipearson/rfw v0.0.0-20170619235010-6f0a6f3266ba github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd -github.com/modern-go/reflect2 v1.0.2 +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 +github.com/openshift/custom-resource-status v1.1.2 github.com/pkg/errors v0.9.1 +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/projectcalico/calico -github.com/projectcalico/calico/lib/std v0.0.0-00010101000000-000000000000 -github.com/projectcalico/go-json v0.0.0-20161128004156-6219dc7339ba -github.com/projectcalico/go-yaml-wrapper v0.0.0-20191112210931-090425220c54 -github.com/prometheus/client_golang v1.23.0 +github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 -github.com/prometheus/common v0.65.0 -github.com/prometheus/procfs v0.17.0 +github.com/prometheus/common v0.67.2 +github.com/prometheus/procfs v0.19.2 github.com/sirupsen/logrus v1.9.3 -github.com/spf13/pflag v1.0.7 +github.com/spf13/pflag v1.0.10 github.com/x448/float16 v0.8.4 -go.etcd.io/etcd/api/v3 v3.6.4 -go.etcd.io/etcd/client/pkg/v3 v3.6.4 -go.etcd.io/etcd/client/v3 v3.6.4 +go.etcd.io/etcd/api/v3 v3.6.5 +go.etcd.io/etcd/client/pkg/v3 v3.6.5 +go.etcd.io/etcd/client/v3 v3.6.5 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 -go.yaml.in/yaml/v2 v2.4.2 +go.yaml.in/yaml/v2 v2.4.3 go.yaml.in/yaml/v3 v3.0.4 -golang.org/x/crypto v0.41.0 -golang.org/x/mod v0.27.0 -golang.org/x/net v0.43.0 -golang.org/x/oauth2 v0.30.0 -golang.org/x/sync v0.16.0 -golang.org/x/sys v0.35.0 -golang.org/x/term v0.34.0 -golang.org/x/text v0.28.0 -golang.org/x/time v0.12.0 +golang.org/x/crypto v0.47.0 +golang.org/x/mod v0.32.0 +golang.org/x/net v0.49.0 +golang.org/x/oauth2 v0.34.0 +golang.org/x/sync v0.19.0 +golang.org/x/sys v0.40.0 +golang.org/x/term v0.39.0 +golang.org/x/text v0.33.0 +golang.org/x/time v0.14.0 golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 google.golang.org/genproto v0.0.0-20250603155806-513f23925822 -google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 -google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a -google.golang.org/grpc v1.72.2 -google.golang.org/protobuf v1.36.7 +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c +google.golang.org/grpc v1.76.0 +google.golang.org/protobuf v1.36.10 gopkg.in/evanphx/json-patch.v4 v4.12.0 gopkg.in/go-playground/validator.v9 v9.30.2 gopkg.in/inf.v0 v0.9.1 -gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 -k8s.io/api v0.33.5 -k8s.io/apiextensions-apiserver v0.33.5 -k8s.io/apimachinery v0.33.5 -k8s.io/client-go v0.33.5 +k8s.io/api v0.34.3 +k8s.io/apiextensions-apiserver v0.34.3 +k8s.io/apimachinery v0.34.3 +k8s.io/client-go v0.34.3 k8s.io/klog v0.2.0 k8s.io/klog/v2 v2.130.1 -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff -k8s.io/utils v0.0.0-20241210054802-24370beab758 +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 +kubevirt.io/api v1.8.0-alpha.0 +kubevirt.io/containerized-data-importer-api v1.63.1 +kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 -sigs.k8s.io/network-policy-api v0.1.5 +sigs.k8s.io/network-policy-api v0.1.8-0.20260212153203-412bf65729a5 sigs.k8s.io/randfill v1.0.0 -sigs.k8s.io/structured-merge-diff/v4 v4.6.0 +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 sigs.k8s.io/yaml v1.6.0 diff --git a/hack/git-hooks/pre-commit-in-container b/hack/git-hooks/pre-commit-in-container index 402acb2f24c..967e45c9c45 100755 --- a/hack/git-hooks/pre-commit-in-container +++ b/hack/git-hooks/pre-commit-in-container @@ -139,8 +139,8 @@ for filename in $changed_go_files; do echo " Test suite doesn't call HookLogrusForGinkgo(): $filename" logrus_hook_check_failed=true fi - if ! grep -q 'NewJUnitReporter' "$filename"; then - echo " Test suite doesn't call NewJUnitReporter(): $filename" + if ! grep -q 'RunSpecs' "$filename"; then + echo " Test suite doesn't call RunSpecs(): $filename" junit_hook_check_failed=true fi fi @@ -198,15 +198,16 @@ if $logrus_hook_check_failed; then fi if $junit_hook_check_failed; then echo - echo "One or more test suites don't call NewJUnitReporter()." - echo "If a suite doesn't call NewJUnitReporter() then it won't generate " + echo "One or more test suites don't call RunSpecs()." + echo "If a suite doesn't call RunSpecs() then it won't generate " echo "a junit report during UT runs. Example:" echo - echo 'import "github.com/onsi/ginkgo/reporters"' + echo 'import "github.com/onsi/ginkgo/ginkgo/v2"' echo 'func TestFoo(t *testing.T) {' - echo ' RegisterFailHandler(Fail)' - echo ' junitReporter := reporters.NewJUnitReporter("junit.xml")' - echo ' RunSpecsWithDefaultAndCustomReporters(t, "Foo Suite", []Reporter{junitReporter})' + echo ' gomega.RegisterFailHandler(Fail)' + echo ' suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration()' + echo ' reporterConfig.JUnitReport = "/path/to/foo_suite.xml"' + echo ' ginkgo.RunSpecs(t, "Foo Suite", suiteConfig, reporterConfig)' echo '}' fi if $go_copyright_check_failed; then diff --git a/hack/test/kind/kind-single.config b/hack/test/kind/kind-single.config index 5fea893778c..46d9e3d1e3c 100644 --- a/hack/test/kind/kind-single.config +++ b/hack/test/kind/kind-single.config @@ -7,12 +7,20 @@ networking: disableDefaultCNI: true podSubnet: "192.168.0.0/16" dnsSearch: [] + nodes: # For libcalico-go tests, we only need a control plane node. - role: control-plane extraPortMappings: - containerPort: 8080 hostPort: 8080 + +featureGates: + "MutatingAdmissionPolicy": true + +runtimeConfig: + "admissionregistration.k8s.io/v1beta1": "true" + kubeadmConfigPatches: - | apiVersion: kubeadm.k8s.io/v1beta3 diff --git a/hack/test/kind/kind.config b/hack/test/kind/kind.config index fc72eab8434..18a7ca99791 100644 --- a/hack/test/kind/kind.config +++ b/hack/test/kind/kind.config @@ -6,11 +6,19 @@ networking: podSubnet: "192.168.0.0/16,fd00:10:244::/64" ipFamily: dual dnsSearch: [] + nodes: - role: control-plane - role: worker - role: worker - role: worker + +featureGates: + "MutatingAdmissionPolicy": true + +runtimeConfig: + "admissionregistration.k8s.io/v1beta1": "true" + kubeadmConfigPatches: - | apiVersion: kubeadm.k8s.io/v1beta3 diff --git a/hack/test/spider/main.go b/hack/test/spider/main.go index a57a1e5014d..2ebe2c23cb5 100644 --- a/hack/test/spider/main.go +++ b/hack/test/spider/main.go @@ -172,7 +172,7 @@ func main() { if err != nil { panic(fmt.Sprintf("%s: %s", err, stderr.String())) } - for _, p := range strings.Split(out.String(), "\n") { + for p := range strings.SplitSeq(out.String(), "\n") { fmt.Println(p) } return @@ -180,7 +180,7 @@ func main() { // From the list of files, condense that to a set of packages. changedPackages := map[string]string{} - for _, f := range strings.Split(out.String(), "\n") { + for f := range strings.SplitSeq(out.String(), "\n") { changedPackages[filepath.Dir(f)] = "" } @@ -199,7 +199,7 @@ func main() { // Loop through impacted packages a few times to make sure capture the full // depth of the dependency tree. - for i := 0; i < 10; i++ { + for range 10 { originalSize := len(impactedPackages) for pkg := range impactedPackages { for d := range packageToDeps[pkg] { diff --git a/key-cert-provisioner/deps.txt b/key-cert-provisioner/deps.txt index 54333084381..5839d891144 100644 --- a/key-cert-provisioner/deps.txt +++ b/key-cert-provisioner/deps.txt @@ -2,49 +2,50 @@ This file contains the list of modules that this package depends on in order to trigger CI on changes -go 1.25.1 +go 1.25.7 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc -github.com/emicklei/go-restful/v3 v3.11.0 -github.com/fxamacker/cbor/v2 v2.7.0 +github.com/emicklei/go-restful v2.15.0+incompatible +github.com/emicklei/go-restful/v3 v3.12.2 +github.com/fxamacker/cbor/v2 v2.9.0 github.com/go-logr/logr v1.4.3 github.com/go-openapi/jsonpointer v0.21.0 github.com/go-openapi/jsonreference v0.20.2 github.com/go-openapi/swag v0.23.0 github.com/gogo/protobuf v1.3.2 -github.com/google/gnostic-models v0.6.9 -github.com/google/go-cmp v0.7.0 +github.com/google/gnostic-models v0.7.0 github.com/google/uuid v1.6.0 github.com/josharian/intern v1.0.0 github.com/json-iterator/go v1.1.12 github.com/mailru/easyjson v0.7.7 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd -github.com/modern-go/reflect2 v1.0.2 +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 github.com/pkg/errors v0.9.1 github.com/projectcalico/calico github.com/sirupsen/logrus v1.9.3 -github.com/spf13/pflag v1.0.7 +github.com/spf13/pflag v1.0.10 github.com/x448/float16 v0.8.4 -go.yaml.in/yaml/v2 v2.4.2 -golang.org/x/net v0.43.0 -golang.org/x/oauth2 v0.30.0 -golang.org/x/sys v0.35.0 -golang.org/x/term v0.34.0 -golang.org/x/text v0.28.0 -golang.org/x/time v0.12.0 -google.golang.org/protobuf v1.36.7 +go.yaml.in/yaml/v2 v2.4.3 +go.yaml.in/yaml/v3 v3.0.4 +golang.org/x/net v0.49.0 +golang.org/x/oauth2 v0.34.0 +golang.org/x/sys v0.40.0 +golang.org/x/term v0.39.0 +golang.org/x/text v0.33.0 +golang.org/x/time v0.14.0 +google.golang.org/protobuf v1.36.10 gopkg.in/evanphx/json-patch.v4 v4.12.0 gopkg.in/inf.v0 v0.9.1 gopkg.in/yaml.v3 v3.0.1 -k8s.io/api v0.33.5 -k8s.io/apimachinery v0.33.5 -k8s.io/client-go v0.33.5 +k8s.io/api v0.34.3 +k8s.io/apimachinery v0.34.3 +k8s.io/client-go v0.34.3 k8s.io/klog v0.2.0 k8s.io/klog/v2 v2.130.1 -k8s.io/kube-aggregator v0.33.5 -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff -k8s.io/utils v0.0.0-20241210054802-24370beab758 +k8s.io/kube-aggregator v0.34.3 +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 sigs.k8s.io/randfill v1.0.0 -sigs.k8s.io/structured-merge-diff/v4 v4.6.0 +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 sigs.k8s.io/yaml v1.6.0 diff --git a/key-cert-provisioner/pkg/k8s/certificate_test.go b/key-cert-provisioner/pkg/k8s/certificate_test.go index 9c8e4c3a58f..9e361d95995 100644 --- a/key-cert-provisioner/pkg/k8s/certificate_test.go +++ b/key-cert-provisioner/pkg/k8s/certificate_test.go @@ -19,7 +19,7 @@ import ( "os" "path/filepath" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" certV1 "k8s.io/api/certificates/v1" certV1beta1 "k8s.io/api/certificates/v1beta1" diff --git a/key-cert-provisioner/pkg/k8s/k8s_suite_test.go b/key-cert-provisioner/pkg/k8s/k8s_suite_test.go index 4b9c6914e4f..48ffd0fe2e5 100644 --- a/key-cert-provisioner/pkg/k8s/k8s_suite_test.go +++ b/key-cert-provisioner/pkg/k8s/k8s_suite_test.go @@ -16,11 +16,11 @@ package k8s_test import ( "testing" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" ) func TestTLS(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "K8s Suite") + gomega.RegisterFailHandler(ginkgo.Fail) + ginkgo.RunSpecs(t, "K8s Suite") } diff --git a/key-cert-provisioner/pkg/tls/tls.go b/key-cert-provisioner/pkg/tls/tls.go index 82072f5ad76..065d1a9eccf 100644 --- a/key-cert-provisioner/pkg/tls/tls.go +++ b/key-cert-provisioner/pkg/tls/tls.go @@ -30,7 +30,7 @@ import ( ) type X509CSR struct { - PrivateKey interface{} + PrivateKey any PrivateKeyPEM []byte CSR []byte } @@ -129,7 +129,7 @@ type basicConstraints struct { // Create a private key based on the env variables. // Default: 2048 bit. -func GeneratePrivateKey(algorithm string) (interface{}, []byte, error) { +func GeneratePrivateKey(algorithm string) (any, []byte, error) { switch algorithm { case "RSAWithSize2048": return genRSA(2048) @@ -149,7 +149,7 @@ func GeneratePrivateKey(algorithm string) (interface{}, []byte, error) { } // genECDSA generates a private key. -func genECDSA(curve elliptic.Curve) (interface{}, []byte, error) { +func genECDSA(curve elliptic.Curve) (any, []byte, error) { key, err := ecdsa.GenerateKey(curve, rand.Reader) if err != nil { return nil, nil, err @@ -236,7 +236,7 @@ func asn1BitLength(bitString []byte) int { for i := range bitString { b := bitString[len(bitString)-i-1] - for bit := uint(0); bit < 8; bit++ { + for bit := range uint(8) { if (b>>bit)&1 == 1 { return bitLen } diff --git a/key-cert-provisioner/pkg/tls/tls_suite_test.go b/key-cert-provisioner/pkg/tls/tls_suite_test.go index c866f9f3cc7..19256d77a4b 100644 --- a/key-cert-provisioner/pkg/tls/tls_suite_test.go +++ b/key-cert-provisioner/pkg/tls/tls_suite_test.go @@ -16,11 +16,11 @@ package tls_test import ( "testing" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" ) func TestTLS(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "TLS Suite") + gomega.RegisterFailHandler(ginkgo.Fail) + ginkgo.RunSpecs(t, "TLS Suite") } diff --git a/key-cert-provisioner/pkg/tls/tls_test.go b/key-cert-provisioner/pkg/tls/tls_test.go index 40415f7e1ff..20d671ae3d9 100644 --- a/key-cert-provisioner/pkg/tls/tls_test.go +++ b/key-cert-provisioner/pkg/tls/tls_test.go @@ -21,8 +21,7 @@ import ( "encoding/pem" "net" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/key-cert-provisioner/pkg/cfg" diff --git a/key-cert-provisioner/test-signer/test-signer.go b/key-cert-provisioner/test-signer/test-signer.go index 4db6a4f793b..9e9985705aa 100644 --- a/key-cert-provisioner/test-signer/test-signer.go +++ b/key-cert-provisioner/test-signer/test-signer.go @@ -83,7 +83,7 @@ func main() { log.Fatal("No key found") } - var parsedKey interface{} + var parsedKey any if parsedKey, err = x509.ParsePKCS1PrivateKey(keyDER.Bytes); err != nil { if parsedKey, err = x509.ParsePKCS8PrivateKey(keyDER.Bytes); err != nil { log.Fatal(err) diff --git a/kube-controllers/Dockerfile b/kube-controllers/Dockerfile index 250c24b6160..97fb369b9d4 100644 --- a/kube-controllers/Dockerfile +++ b/kube-controllers/Dockerfile @@ -13,8 +13,9 @@ # limitations under the License. ARG CALICO_BASE +ARG UBI_IMAGE -FROM alpine:3 AS builder +FROM ${UBI_IMAGE} AS builder # Make sure the status and pprof files are owned by our user. RUN mkdir /status /profiles @@ -48,6 +49,14 @@ LABEL org.opencontainers.image.vendor="Project Calico" LABEL org.opencontainers.image.version="${GIT_VERSION}" LABEL org.opencontainers.image.licenses="Apache-2.0" +LABEL description="Calico Kubernetes controllers monitor the Kubernetes API and perform actions based on cluster state" +LABEL maintainer="maintainers@tigera.io" +LABEL name="Calico Kubernetes controllers" +LABEL release=1 +LABEL summary="Calico Kubernetes controllers monitor the Kubernetes API and perform actions based on cluster state" +LABEL vendor="Project Calico" +LABEL version="${GIT_VERSION}" + COPY --from=source / / USER 999 diff --git a/kube-controllers/Makefile b/kube-controllers/Makefile index 985a499bab1..82fc3914a82 100644 --- a/kube-controllers/Makefile +++ b/kube-controllers/Makefile @@ -14,9 +14,6 @@ BUILD_IMAGES ?=$(KUBE_CONTROLLERS_IMAGE) $(FLANNEL_MIGRATION_IMAGE) # Additions to EXTRA_DOCKER_ARGS need to happen before the include since # that variable is evaluated when we declare DOCKER_RUN and siblings. ############################################################################### -MAKE_BRANCH?=$(GO_BUILD_VER) -MAKE_REPO?=https://raw.githubusercontent.com/projectcalico/go-build/$(MAKE_BRANCH) - include ../lib.Makefile SRC_FILES=$(shell find cmd -name '*.go') $(shell find pkg -name '*.go') $(shell find ../libcalico-go -name '*.go') @@ -132,12 +129,13 @@ TEST_BINARIES=$(addsuffix /ut.test,$(WHAT)) ## Run the unit tests in a container. test: ut -ut fv: $(TEST_BINARIES) +ut fv: $(TEST_BINARIES) $(KUBE_CONTROLLER_CONTAINER_CREATED) KUBE_IMAGE=$(CALICO_BUILD) \ ETCD_IMAGE=$(ETCD_IMAGE) \ + CALICO_API_GROUP=$(CALICO_API_GROUP) \ CONTAINER_NAME=$(KUBE_CONTROLLERS_IMAGE):latest-$(ARCH) \ MIGRATION_CONTAINER_NAME=$(FLANNEL_MIGRATION_IMAGE):latest-$(ARCH) \ - CRDS=$(CURDIR)/../libcalico-go/config/crd \ + CRDS=$(REPO_ROOT)/$(CALICO_CRD_PATH) \ CERTS_PATH=$(CERTS_PATH) \ ./run-uts $(WHAT) @@ -146,7 +144,7 @@ ut fv: $(TEST_BINARIES) # Only do this if there are .go files in the path. %/ut.test: $(SRC_FILES) if [ $$(find ./$* -name '*.go' | wc -l) -gt 0 ]; then \ - $(DOCKER_RUN) -e CGO_ENABLED=0 $(CALICO_BUILD) go test ./$* -c --tags fvtests -o $@; \ + $(DOCKER_RUN) -e CGO_ENABLED=0 $(CALICO_BUILD) go test ./$* -c -o $@; \ else \ echo "Skipping $* as it has no .go files in it"; \ fi diff --git a/kube-controllers/cmd/kube-controllers/fv_test.go b/kube-controllers/cmd/kube-controllers/fv_test.go index acf5d1d8a3b..0a02e1b3a18 100644 --- a/kube-controllers/cmd/kube-controllers/fv_test.go +++ b/kube-controllers/cmd/kube-controllers/fv_test.go @@ -18,12 +18,11 @@ import ( "context" "fmt" "net/http" - "os" "os/exec" "strings" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -39,11 +38,14 @@ import ( var _ = Describe("[etcd] kube-controllers health check FV tests", func() { var ( etcd *containers.Container - policyController *containers.Container + kubeControllers *containers.Container apiserver *containers.Container calicoClient client.Interface k8sClient *kubernetes.Clientset controllerManager *containers.Container + err error + kconfigfile string + removeKubeconfig func() ) BeforeEach(func() { @@ -54,21 +56,12 @@ var _ = Describe("[etcd] kube-controllers health check FV tests", func() { // Run apiserver. apiserver = testutils.RunK8sApiserver(etcd.IP) - // Write out a kubeconfig file - kconfigfile, err := os.CreateTemp("", "ginkgo-policycontroller") - Expect(err).NotTo(HaveOccurred()) - defer func() { _ = os.Remove(kconfigfile.Name()) }() - data := testutils.BuildKubeconfig(apiserver.IP) - _, err = kconfigfile.Write([]byte(data)) - Expect(err).NotTo(HaveOccurred()) - - // Make the kubeconfig readable by the container. - Expect(kconfigfile.Chmod(os.ModePerm)).NotTo(HaveOccurred()) + kconfigfile, removeKubeconfig = testutils.BuildKubeconfig(apiserver.IP) // Run the controller. - policyController = testutils.RunPolicyController(apiconfig.EtcdV3, etcd.IP, kconfigfile.Name(), "") + kubeControllers = testutils.RunKubeControllers(apiconfig.EtcdV3, etcd.IP, kconfigfile, "") - k8sClient, err = testutils.GetK8sClient(kconfigfile.Name()) + k8sClient, err = testutils.GetK8sClient(kconfigfile) Expect(err).NotTo(HaveOccurred()) // Wait for the apiserver to be available. @@ -88,9 +81,10 @@ var _ = Describe("[etcd] kube-controllers health check FV tests", func() { AfterEach(func() { _ = calicoClient.Close() controllerManager.Stop() - policyController.Stop() + kubeControllers.Stop() apiserver.Stop() etcd.Stop() + removeKubeconfig() }) It("should initialize the datastore at start-of-day", func() { @@ -109,7 +103,7 @@ var _ = Describe("[etcd] kube-controllers health check FV tests", func() { It("should pass health check", func() { By("Waiting for an initial readiness report") Eventually(func() []byte { - cmd := exec.Command("docker", "exec", policyController.Name, "/usr/bin/check-status", "-r") + cmd := exec.Command("docker", "exec", kubeControllers.Name, "/usr/bin/check-status", "-r") stdoutStderr, _ := cmd.CombinedOutput() return stdoutStderr @@ -117,7 +111,7 @@ var _ = Describe("[etcd] kube-controllers health check FV tests", func() { By("Waiting for the controller to be ready") Eventually(func() string { - cmd := exec.Command("docker", "exec", policyController.Name, "/usr/bin/check-status", "-r") + cmd := exec.Command("docker", "exec", kubeControllers.Name, "/usr/bin/check-status", "-r") stdoutStderr, _ := cmd.CombinedOutput() return strings.TrimSpace(string(stdoutStderr)) @@ -127,7 +121,7 @@ var _ = Describe("[etcd] kube-controllers health check FV tests", func() { It("should fail health check if apiserver is not running", func() { By("Waiting for an initial readiness report") Eventually(func() []byte { - cmd := exec.Command("docker", "exec", policyController.Name, "/usr/bin/check-status", "-r") + cmd := exec.Command("docker", "exec", kubeControllers.Name, "/usr/bin/check-status", "-r") stdoutStderr, _ := cmd.CombinedOutput() return stdoutStderr @@ -138,7 +132,7 @@ var _ = Describe("[etcd] kube-controllers health check FV tests", func() { By("Waiting for the readiness to change") Eventually(func() []byte { - cmd := exec.Command("docker", "exec", policyController.Name, "/usr/bin/check-status", "-r") + cmd := exec.Command("docker", "exec", kubeControllers.Name, "/usr/bin/check-status", "-r") stdoutStderr, _ := cmd.CombinedOutput() return stdoutStderr @@ -148,7 +142,7 @@ var _ = Describe("[etcd] kube-controllers health check FV tests", func() { It("should fail health check if etcd not running", func() { By("Waiting for an initial readiness report") Eventually(func() []byte { - cmd := exec.Command("docker", "exec", policyController.Name, "/usr/bin/check-status", "-r") + cmd := exec.Command("docker", "exec", kubeControllers.Name, "/usr/bin/check-status", "-r") stdoutStderr, _ := cmd.CombinedOutput() return stdoutStderr @@ -159,7 +153,7 @@ var _ = Describe("[etcd] kube-controllers health check FV tests", func() { By("Waiting for the readiness to change") Eventually(func() []byte { - cmd := exec.Command("docker", "exec", policyController.Name, "/usr/bin/check-status", "-r") + cmd := exec.Command("docker", "exec", kubeControllers.Name, "/usr/bin/check-status", "-r") stdoutStderr, _ := cmd.CombinedOutput() return stdoutStderr @@ -170,9 +164,11 @@ var _ = Describe("[etcd] kube-controllers health check FV tests", func() { var _ = Describe("kube-controllers metrics and pprof FV tests", func() { var ( - etcd *containers.Container - kubectrls *containers.Container - apiserver *containers.Container + etcd *containers.Container + kubectrls *containers.Container + apiserver *containers.Container + kconfigfile string + removeKubeconfig func() ) BeforeEach(func() { @@ -183,19 +179,11 @@ var _ = Describe("kube-controllers metrics and pprof FV tests", func() { apiserver = testutils.RunK8sApiserver(etcd.IP) // Write out a kubeconfig file - kconfigfile, err := os.CreateTemp("", "ginkgo-policycontroller") - Expect(err).NotTo(HaveOccurred()) - defer func() { _ = os.Remove(kconfigfile.Name()) }() - data := testutils.BuildKubeconfig(apiserver.IP) - _, err = kconfigfile.Write([]byte(data)) - Expect(err).NotTo(HaveOccurred()) - - // Make the kubeconfig readable by the container. - Expect(kconfigfile.Chmod(os.ModePerm)).NotTo(HaveOccurred()) + kconfigfile, removeKubeconfig = testutils.BuildKubeconfig(apiserver.IP) // Create some clients. - client := testutils.GetCalicoClient(apiconfig.Kubernetes, "", kconfigfile.Name()) - k8sClient, err := testutils.GetK8sClient(kconfigfile.Name()) + client := testutils.GetCalicoClient(apiconfig.Kubernetes, "", kconfigfile) + k8sClient, err := testutils.GetK8sClient(kconfigfile) Expect(err).NotTo(HaveOccurred()) // Wait for the apiserver to be available. @@ -206,15 +194,7 @@ var _ = Describe("kube-controllers metrics and pprof FV tests", func() { // Apply the necessary CRDs. There can sometimes be a delay between starting // the API server and when CRDs are apply-able, so retry here. - apply := func() error { - out, err := apiserver.ExecOutput("kubectl", "apply", "-f", "/crds/") - if err != nil { - return fmt.Errorf("%s: %s", err, out) - } - return nil - } - By("Applying CRDs") - Eventually(apply, 10*time.Second).ShouldNot(HaveOccurred()) + testutils.ApplyCRDs(apiserver) // Enable metrics and pprof ports for these tests. Eventually(func() error { @@ -230,13 +210,14 @@ var _ = Describe("kube-controllers metrics and pprof FV tests", func() { // Run the controller. We don't need to run any controllers for these tests, but // we do need to run something, so just run the node controller. - kubectrls = testutils.RunPolicyController(apiconfig.Kubernetes, etcd.IP, kconfigfile.Name(), "node") + kubectrls = testutils.RunKubeControllers(apiconfig.Kubernetes, etcd.IP, kconfigfile, "node") }) AfterEach(func() { kubectrls.Stop() apiserver.Stop() etcd.Stop() + removeKubeconfig() }) get := func(server, path string) error { @@ -270,11 +251,14 @@ var _ = Describe("kube-controllers metrics and pprof FV tests", func() { var _ = Describe("[kdd] kube-controllers health check FV tests", func() { var ( etcd *containers.Container - policyController *containers.Container + kubeControllers *containers.Container apiserver *containers.Container calicoClient client.Interface k8sClient *kubernetes.Clientset controllerManager *containers.Container + err error + kconfigfile string + removeKubeconfig func() ) BeforeEach(func() { @@ -285,17 +269,10 @@ var _ = Describe("[kdd] kube-controllers health check FV tests", func() { apiserver = testutils.RunK8sApiserver(etcd.IP) // Write out a kubeconfig file - var err error - kconfigfile, err := os.CreateTemp("", "ginkgo-policycontroller") - Expect(err).NotTo(HaveOccurred()) - defer func() { _ = os.Remove(kconfigfile.Name()) }() - data := testutils.BuildKubeconfig(apiserver.IP) - _, err = kconfigfile.Write([]byte(data)) - Expect(err).NotTo(HaveOccurred()) + kconfigfile, removeKubeconfig = testutils.BuildKubeconfig(apiserver.IP) // Make the kubeconfig readable by the container. - Expect(kconfigfile.Chmod(os.ModePerm)).NotTo(HaveOccurred()) - k8sClient, err = testutils.GetK8sClient(kconfigfile.Name()) + k8sClient, err = testutils.GetK8sClient(kconfigfile) Expect(err).NotTo(HaveOccurred()) // Wait for the apiserver to be available. Eventually(func() error { @@ -309,29 +286,24 @@ var _ = Describe("[kdd] kube-controllers health check FV tests", func() { // Apply the necessary CRDs. There can sometimes be a delay between starting // the API server and when CRDs are apply-able, so retry here. - apply := func() error { - out, err := apiserver.ExecOutput("kubectl", "apply", "-f", "/crds/") - if err != nil { - return fmt.Errorf("%s: %s", err, out) - } - return nil - } - By("Applying CRDs") - Eventually(apply, 10*time.Second).ShouldNot(HaveOccurred()) - calicoClient = testutils.GetCalicoClient(apiconfig.Kubernetes, "", kconfigfile.Name()) + testutils.ApplyCRDs(apiserver) + + calicoClient = testutils.GetCalicoClient(apiconfig.Kubernetes, "", kconfigfile) // In KDD mode, we only support the node controller right now. - policyController = testutils.RunPolicyController(apiconfig.Kubernetes, "", kconfigfile.Name(), "node") + kubeControllers = testutils.RunKubeControllers(apiconfig.Kubernetes, "", kconfigfile, "node") // Run controller manager. controllerManager = testutils.RunK8sControllerManager(apiserver.IP) }) + AfterEach(func() { _ = calicoClient.Close() controllerManager.Stop() - policyController.Stop() + kubeControllers.Stop() apiserver.Stop() etcd.Stop() + removeKubeconfig() }) It("should initialize the datastore at start-of-day", func() { @@ -350,7 +322,7 @@ var _ = Describe("[kdd] kube-controllers health check FV tests", func() { It("should pass health check", func() { By("Waiting for an initial readiness report") Eventually(func() []byte { - cmd := exec.Command("docker", "exec", policyController.Name, "/usr/bin/check-status", "-r") + cmd := exec.Command("docker", "exec", kubeControllers.Name, "/usr/bin/check-status", "-r") stdoutStderr, _ := cmd.CombinedOutput() return stdoutStderr @@ -358,7 +330,7 @@ var _ = Describe("[kdd] kube-controllers health check FV tests", func() { By("Waiting for the controller to be ready") Eventually(func() string { - cmd := exec.Command("docker", "exec", policyController.Name, "/usr/bin/check-status", "-r") + cmd := exec.Command("docker", "exec", kubeControllers.Name, "/usr/bin/check-status", "-r") stdoutStderr, _ := cmd.CombinedOutput() return strings.TrimSpace(string(stdoutStderr)) @@ -368,7 +340,7 @@ var _ = Describe("[kdd] kube-controllers health check FV tests", func() { It("should fail health check if apiserver is not running", func() { By("Waiting for an initial readiness report") Eventually(func() []byte { - cmd := exec.Command("docker", "exec", policyController.Name, "/usr/bin/check-status", "-r") + cmd := exec.Command("docker", "exec", kubeControllers.Name, "/usr/bin/check-status", "-r") stdoutStderr, _ := cmd.CombinedOutput() return stdoutStderr @@ -379,7 +351,7 @@ var _ = Describe("[kdd] kube-controllers health check FV tests", func() { By("Waiting for the readiness to change") Eventually(func() []byte { - cmd := exec.Command("docker", "exec", policyController.Name, "/usr/bin/check-status", "-r") + cmd := exec.Command("docker", "exec", kubeControllers.Name, "/usr/bin/check-status", "-r") stdoutStderr, _ := cmd.CombinedOutput() return stdoutStderr diff --git a/kube-controllers/cmd/kube-controllers/kube_controllers_suite_test.go b/kube-controllers/cmd/kube-controllers/kube_controllers_suite_test.go index 230c5b7823e..1242bbb6a61 100644 --- a/kube-controllers/cmd/kube-controllers/kube_controllers_suite_test.go +++ b/kube-controllers/cmd/kube-controllers/kube_controllers_suite_test.go @@ -17,9 +17,8 @@ package main_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/sirupsen/logrus" "github.com/projectcalico/calico/libcalico-go/lib/testutils" @@ -31,7 +30,8 @@ func init() { } func Test(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/main_controller_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Main binary suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/main_controller_suite.xml" + ginkgo.RunSpecs(t, "Main binary suite", suiteConfig, reporterConfig) } diff --git a/kube-controllers/cmd/kube-controllers/main.go b/kube-controllers/cmd/kube-controllers/main.go index 548394fc89d..e46cdc0d9a8 100644 --- a/kube-controllers/cmd/kube-controllers/main.go +++ b/kube-controllers/cmd/kube-controllers/main.go @@ -23,6 +23,8 @@ import ( "strings" "time" + "github.com/projectcalico/api/pkg/client/clientset_generated/clientset" + "github.com/projectcalico/api/pkg/client/informers_generated/externalversions" "github.com/prometheus/client_golang/prometheus/promhttp" log "github.com/sirupsen/logrus" "go.etcd.io/etcd/client/pkg/v3/srv" @@ -38,6 +40,7 @@ import ( "github.com/projectcalico/calico/kube-controllers/pkg/config" "github.com/projectcalico/calico/kube-controllers/pkg/controllers/controller" "github.com/projectcalico/calico/kube-controllers/pkg/controllers/flannelmigration" + "github.com/projectcalico/calico/kube-controllers/pkg/controllers/ippool" "github.com/projectcalico/calico/kube-controllers/pkg/controllers/loadbalancer" "github.com/projectcalico/calico/kube-controllers/pkg/controllers/namespace" "github.com/projectcalico/calico/kube-controllers/pkg/controllers/networkpolicy" @@ -48,6 +51,7 @@ import ( "github.com/projectcalico/calico/kube-controllers/pkg/converter" "github.com/projectcalico/calico/kube-controllers/pkg/status" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" + "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/debugserver" "github.com/projectcalico/calico/libcalico-go/lib/logutils" @@ -103,7 +107,7 @@ func main() { log.SetLevel(logLevel) // Build clients to be used by the controllers. - k8sClientset, calicoClient, err := getClients(cfg.Kubeconfig) + k8sClientset, libcalicoClient, calicoClient, err := getClients(cfg.Kubeconfig) if err != nil { log.WithError(err).Fatal("Failed to start") } @@ -121,7 +125,7 @@ func main() { initCtx, cancelInit := context.WithTimeout(ctx, 60*time.Second) // Loop until context expires or calicoClient is initialized. for { - err := calicoClient.EnsureInitialized(initCtx, "", "k8s") + err := libcalicoClient.EnsureInitialized(initCtx, "", "k8s") if err != nil { log.WithError(err).Info("Failed to initialize datastore") s.SetReady( @@ -152,7 +156,7 @@ func main() { informers: make([]cache.SharedIndexInformer, 0), } - dataFeed := utils.NewDataFeed(calicoClient, cfg.DatastoreType) + dataFeed := utils.NewDataFeed(libcalicoClient, cfg.DatastoreType) var runCfg config.RunConfig // flannelmigration doesn't use the datastore config API @@ -168,7 +172,7 @@ func main() { } log.WithField("flannelConfig", flannelConfig).Info("Loaded Flannel configuration from environment") - flannelMigrationController := flannelmigration.NewFlannelMigrationController(ctx, k8sClientset, calicoClient, flannelConfig) + flannelMigrationController := flannelmigration.NewFlannelMigrationController(ctx, k8sClientset, libcalicoClient, flannelConfig) controllerCtrl.controllers["FlannelMigration"] = flannelMigrationController // Set some global defaults for flannelmigration @@ -181,13 +185,13 @@ func main() { controllerCtrl.restart = make(chan config.RunConfig) } else { log.Info("Getting initial config snapshot from datastore") - cCtrlr := config.NewRunConfigController(ctx, *cfg, calicoClient.KubeControllersConfiguration()) + cCtrlr := config.NewRunConfigController(ctx, *cfg, libcalicoClient.KubeControllersConfiguration()) runCfg = <-cCtrlr.ConfigChan() log.Info("Got initial config snapshot") // any subsequent changes trigger a restart controllerCtrl.restart = cCtrlr.ConfigChan() - controllerCtrl.InitControllers(ctx, runCfg, k8sClientset, calicoClient, dataFeed) + controllerCtrl.InitControllers(ctx, runCfg, k8sClientset, libcalicoClient, calicoClient, dataFeed) } if cfg.DatastoreType == utils.Etcdv3 { @@ -198,7 +202,7 @@ func main() { // Run the health checks on a separate goroutine. if runCfg.HealthEnabled { log.Info("Starting status report routine") - go runHealthChecks(ctx, s, k8sClientset, calicoClient) + go runHealthChecks(ctx, s, k8sClientset, libcalicoClient) } // Set the log level from the merged config. @@ -289,10 +293,7 @@ func runHealthChecks(ctx context.Context, s *status.Status, k8sClientset *kubern // If we encountered errors, retry again with a longer timeout. if !s.GetReadiness() { - timeout = 2 * timeout - if timeout > maxTimeout { - timeout = maxTimeout - } + timeout = min(2*timeout, maxTimeout) log.Infof("Health check is not ready, retrying in 2 seconds with new timeout: %s", timeout) time.Sleep(2 * time.Second) continue @@ -327,17 +328,17 @@ func startCompactor(ctx context.Context, interval time.Duration) { } log.WithField("period", interval).Info("Starting periodic etcdv3 compaction") - etcd3.StartCompactor(ctx, etcdClient, interval) + etcd3.StartCompactorPerEndpoint(etcdClient, interval) break } } // getClients builds and returns Kubernetes and Calico clients. -func getClients(kubeconfig string) (*kubernetes.Clientset, client.Interface, error) { +func getClients(kubeconfig string) (*kubernetes.Clientset, client.Interface, clientset.Interface, error) { // Get Calico client config, err := apiconfig.LoadClientConfigFromEnvironment() if err != nil { - return nil, nil, err + return nil, nil, nil, err } // Increase the client QPS on the Calico client. @@ -345,16 +346,16 @@ func getClients(kubeconfig string) (*kubernetes.Clientset, client.Interface, err // - Secondly, the IPAM GC controller can potentially generate a very large number of requests. config.Spec.K8sClientQPS = 500 - calicoClient, err := client.New(*config) + libcalicoClient, err := client.New(*config) if err != nil { - return nil, nil, fmt.Errorf("failed to build Calico client: %s", err) + return nil, nil, nil, fmt.Errorf("failed to build Calico client: %s", err) } // Now build the Kubernetes client, we support in-cluster config and kubeconfig // as means of configuring the client. k8sconfig, err := winutils.BuildConfigFromFlags("", kubeconfig) if err != nil { - return nil, nil, fmt.Errorf("failed to build kubernetes client config: %s", err) + return nil, nil, nil, fmt.Errorf("failed to build kubernetes client config: %s", err) } // Increase the QPS of the Kubernetes client as well. This is also used heavily by the IPAM GC controller @@ -365,10 +366,17 @@ func getClients(kubeconfig string) (*kubernetes.Clientset, client.Interface, err // Get Kubernetes clientset k8sClientset, err := kubernetes.NewForConfig(k8sconfig) if err != nil { - return nil, nil, fmt.Errorf("failed to build kubernetes client: %s", err) + return nil, nil, nil, fmt.Errorf("failed to build kubernetes client: %s", err) } - return k8sClientset, calicoClient, nil + // Create a clientset for interacting with the projectcalico.org/v3 API. + var v3c clientset.Interface + v3c, err = clientset.NewForConfig(k8sconfig) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to build Calico Kubernetes v3 client: %s", err) + } + + return k8sClientset, libcalicoClient, v3c, nil } // Returns an etcdv3 client based on the environment. The client will be configured to @@ -448,7 +456,14 @@ type controllerControl struct { informers []cache.SharedIndexInformer } -func (cc *controllerControl) InitControllers(ctx context.Context, cfg config.RunConfig, k8sClientset *kubernetes.Clientset, calicoClient client.Interface, dataFeed *utils.DataFeed) { +func (cc *controllerControl) InitControllers( + ctx context.Context, + cfg config.RunConfig, + k8sClientset *kubernetes.Clientset, + calicoClient client.Interface, + v3c clientset.Interface, + dataFeed *utils.DataFeed, +) { // Create a shared informer factory to allow cache sharing between controllers monitoring the // same resource. factory := informers.NewSharedInformerFactory(k8sClientset, 0) @@ -457,6 +472,24 @@ func (cc *controllerControl) InitControllers(ctx context.Context, cfg config.Run serviceInformer := factory.Core().V1().Services().Informer() namespaceInformer := factory.Core().V1().Namespaces().Informer() + if v3c != nil { + // Create informers for Calico resources. + calicoFactory := externalversions.NewSharedInformerFactory(v3c, 5*time.Minute) + poolInformer := calicoFactory.Projectcalico().V3().IPPools().Informer() + blockInformer := calicoFactory.Projectcalico().V3().IPAMBlocks().Informer() + + // Determine if we are running in v3 CRD mode, and enable controllers accordingly. + config, _ := apiconfig.LoadClientConfigFromEnvironment() + v3CRDs := k8s.UsingV3CRDs(&config.Spec) + + if v3CRDs { + // Enable the IPPool controller, which manages graceful IP pool teardown. + poolController := ippool.NewController(ctx, v3c, poolInformer, blockInformer, calicoClient.IPAM()) + cc.controllers["IPPool"] = poolController + cc.registerInformers(poolInformer, blockInformer) + } + } + if cfg.Controllers.WorkloadEndpoint != nil { podController := pod.NewPodController(ctx, k8sClientset, calicoClient, *cfg.Controllers.WorkloadEndpoint, podInformer) cc.controllers["Pod"] = podController @@ -487,6 +520,12 @@ func (cc *controllerControl) InitControllers(ctx context.Context, cfg config.Run cc.registerInformers(serviceInformer, namespaceInformer) } + if cfg.Controllers.Migration != nil && cfg.Controllers.Migration.PolicyNameMigrator == "Enabled" { + // Register the policy name migrator controller. + policyMigrator := networkpolicy.NewMigratorController(ctx, k8sClientset, calicoClient, dataFeed) + cc.controllers["NetworkPolicyMigrator"] = policyMigrator + } + // We don't need the full Pod object. In order to reduce memory usage, add a transform that only // includes the fields we need. if err := podInformer.SetTransform(converter.PodTransformer(cfg.Controllers.WorkloadEndpoint != nil)); err != nil { @@ -525,10 +564,8 @@ func (cc *controllerControl) RunControllers(dataFeed *utils.DataFeed, cfg config go c.Run(cc.stop) } - if cfg.Controllers.Node != nil || cfg.Controllers.LoadBalancer != nil { - // Start dataFeed for controllers that need it - dataFeed.Start() - } + // Start dataFeed for controllers that need it + dataFeed.Start() // Block until we are cancelled, or get a new configuration and need to restart select { diff --git a/kube-controllers/deps.txt b/kube-controllers/deps.txt index 60ae2fa85b5..5844628add2 100644 --- a/kube-controllers/deps.txt +++ b/kube-controllers/deps.txt @@ -2,19 +2,24 @@ This file contains the list of modules that this package depends on in order to trigger CI on changes -go 1.25.1 +go 1.25.7 +github.com/Masterminds/semver/v3 v3.4.0 github.com/beorn7/perks v1.0.1 github.com/blang/semver/v4 v4.0.0 -github.com/cenkalti/backoff/v4 v4.3.0 +github.com/cenkalti/backoff/v5 v5.0.2 github.com/cespare/xxhash/v2 v2.3.0 github.com/coreos/go-semver v0.3.1 -github.com/coreos/go-systemd/v22 v22.5.0 +github.com/coreos/go-systemd/v22 v22.6.0 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc -github.com/emicklei/go-restful/v3 v3.11.0 +github.com/emicklei/go-restful v2.15.0+incompatible +github.com/emicklei/go-restful/v3 v3.12.2 +github.com/evanphx/json-patch v5.9.11+incompatible +github.com/evanphx/json-patch/v5 v5.9.11 github.com/felixge/httpsnoop v1.0.4 -github.com/fsnotify/fsnotify v1.9.0 -github.com/fxamacker/cbor/v2 v2.7.0 +github.com/fxamacker/cbor/v2 v2.9.0 github.com/go-ini/ini v1.67.0 +github.com/go-kit/log v0.2.1 +github.com/go-logfmt/logfmt v0.6.0 github.com/go-logr/logr v1.4.3 github.com/go-logr/stdr v1.2.2 github.com/go-openapi/jsonpointer v0.21.0 @@ -26,11 +31,11 @@ github.com/gogo/protobuf v1.3.2 github.com/golang/protobuf v1.5.4 github.com/golang/snappy v1.0.0 github.com/google/btree v1.1.3 -github.com/google/gnostic-models v0.6.9 +github.com/google/gnostic-models v0.7.0 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 -github.com/grpc-ecosystem/grpc-gateway v1.16.0 -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 github.com/jinzhu/copier v0.4.0 github.com/joho/godotenv v1.5.1 github.com/josharian/intern v1.0.0 @@ -40,7 +45,6 @@ github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 github.com/kelseyhightower/envconfig v1.4.0 github.com/kylelemons/godebug v1.1.0 github.com/leodido/go-urn v1.4.0 -github.com/lithammer/dedent v1.1.0 github.com/mailru/easyjson v0.7.7 github.com/mattn/go-runewidth v0.0.16 github.com/mdlayher/genetlink v1.3.2 @@ -48,82 +52,86 @@ github.com/mdlayher/netlink v1.7.2 github.com/mdlayher/socket v0.5.1 github.com/mipearson/rfw v0.0.0-20170619235010-6f0a6f3266ba github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd -github.com/modern-go/reflect2 v1.0.2 +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 -github.com/nxadm/tail v1.4.8 github.com/olekukonko/tablewriter v0.0.5 github.com/onsi/ginkgo v1.16.5 -github.com/onsi/gomega v1.38.0 +github.com/onsi/ginkgo/v2 v2.28.1 +github.com/onsi/gomega v1.39.1 +github.com/openshift/custom-resource-status v1.1.2 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.9.1 +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/projectcalico/calico -github.com/projectcalico/calico/lib/std v0.0.0-00010101000000-000000000000 -github.com/projectcalico/go-json v0.0.0-20161128004156-6219dc7339ba -github.com/projectcalico/go-yaml-wrapper v0.0.0-20191112210931-090425220c54 -github.com/prometheus/client_golang v1.23.0 +github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 -github.com/prometheus/common v0.65.0 -github.com/prometheus/procfs v0.17.0 +github.com/prometheus/common v0.67.2 +github.com/prometheus/procfs v0.19.2 github.com/rivo/uniseg v0.4.7 github.com/sirupsen/logrus v1.9.3 -github.com/spf13/cobra v1.9.1 -github.com/spf13/pflag v1.0.7 +github.com/spf13/cobra v1.10.1 +github.com/spf13/pflag v1.0.10 github.com/tchap/go-patricia/v2 v2.3.3 github.com/vishvananda/netlink v1.3.1 github.com/vishvananda/netns v0.0.5 github.com/x448/float16 v0.8.4 -go.etcd.io/etcd/api/v3 v3.6.4 -go.etcd.io/etcd/client/pkg/v3 v3.6.4 -go.etcd.io/etcd/client/v3 v3.6.4 +go.etcd.io/etcd/api/v3 v3.6.5 +go.etcd.io/etcd/client/pkg/v3 v3.6.5 +go.etcd.io/etcd/client/v3 v3.6.5 go.opentelemetry.io/auto/sdk v1.1.0 -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 -go.opentelemetry.io/otel v1.35.0 -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 -go.opentelemetry.io/otel/metric v1.35.0 -go.opentelemetry.io/otel/sdk v1.35.0 -go.opentelemetry.io/otel/trace v1.35.0 -go.opentelemetry.io/proto/otlp v1.5.0 +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 +go.opentelemetry.io/otel v1.37.0 +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 +go.opentelemetry.io/otel/metric v1.37.0 +go.opentelemetry.io/otel/sdk v1.37.0 +go.opentelemetry.io/otel/trace v1.37.0 +go.opentelemetry.io/proto/otlp v1.7.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 -go.yaml.in/yaml/v2 v2.4.2 -golang.org/x/crypto v0.41.0 -golang.org/x/net v0.43.0 -golang.org/x/oauth2 v0.30.0 -golang.org/x/sync v0.16.0 -golang.org/x/sys v0.35.0 -golang.org/x/term v0.34.0 -golang.org/x/text v0.28.0 -golang.org/x/time v0.12.0 +go.yaml.in/yaml/v2 v2.4.3 +go.yaml.in/yaml/v3 v3.0.4 +golang.org/x/crypto v0.47.0 +golang.org/x/mod v0.32.0 +golang.org/x/net v0.49.0 +golang.org/x/oauth2 v0.34.0 +golang.org/x/sync v0.19.0 +golang.org/x/sys v0.40.0 +golang.org/x/term v0.39.0 +golang.org/x/text v0.33.0 +golang.org/x/time v0.14.0 +golang.org/x/tools v0.41.0 golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 google.golang.org/genproto v0.0.0-20250603155806-513f23925822 -google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 -google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a -google.golang.org/grpc v1.72.2 -google.golang.org/protobuf v1.36.7 +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c +google.golang.org/grpc v1.76.0 +google.golang.org/protobuf v1.36.10 gopkg.in/evanphx/json-patch.v4 v4.12.0 gopkg.in/go-playground/validator.v9 v9.30.2 gopkg.in/inf.v0 v0.9.1 -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 -gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 -k8s.io/api v0.33.5 -k8s.io/apiextensions-apiserver v0.33.5 -k8s.io/apimachinery v0.33.5 -k8s.io/apiserver v0.33.5 -k8s.io/client-go v0.33.5 -k8s.io/component-base v0.33.5 -k8s.io/component-helpers v0.33.5 -k8s.io/controller-manager v0.33.5 +k8s.io/api v0.34.3 +k8s.io/apiextensions-apiserver v0.34.3 +k8s.io/apimachinery v0.34.3 +k8s.io/apiserver v0.34.3 +k8s.io/client-go v0.34.3 +k8s.io/component-base v0.34.3 +k8s.io/component-helpers v0.34.3 +k8s.io/controller-manager v0.34.3 k8s.io/klog v0.2.0 k8s.io/klog/v2 v2.130.1 -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff -k8s.io/kubernetes v1.33.5 -k8s.io/utils v0.0.0-20241210054802-24370beab758 +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b +k8s.io/kubernetes v1.34.3 +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 +kubevirt.io/api v1.8.0-alpha.0 +kubevirt.io/containerized-data-importer-api v1.63.1 +kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 +sigs.k8s.io/controller-runtime v0.22.3 sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 -sigs.k8s.io/knftables v0.0.18 -sigs.k8s.io/network-policy-api v0.1.5 +sigs.k8s.io/knftables v0.0.19 +sigs.k8s.io/network-policy-api v0.1.8-0.20260212153203-412bf65729a5 sigs.k8s.io/randfill v1.0.0 -sigs.k8s.io/structured-merge-diff/v4 v4.6.0 +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 sigs.k8s.io/yaml v1.6.0 diff --git a/kube-controllers/docker-image/flannel-migration/Dockerfile b/kube-controllers/docker-image/flannel-migration/Dockerfile index c7c89a60b88..0eeee8b2d61 100644 --- a/kube-controllers/docker-image/flannel-migration/Dockerfile +++ b/kube-controllers/docker-image/flannel-migration/Dockerfile @@ -13,8 +13,9 @@ # limitations under the License. ARG CALICO_BASE +ARG UBI_IMAGE -FROM alpine:3 AS builder +FROM ${UBI_IMAGE} AS builder # Make sure the status file is owned by our user. RUN mkdir /status @@ -45,6 +46,14 @@ LABEL org.opencontainers.image.vendor="Project Calico" LABEL org.opencontainers.image.version="${GIT_VERSION}" LABEL org.opencontainers.image.licenses="Apache-2.0" +LABEL description="Calico Flannel migration controller updates a flannel cluster to Calico" +LABEL maintainer="maintainers@tigera.io" +LABEL name="Calico Flannel migration controller" +LABEL release=1 +LABEL summary="Calico Flannel migration controller updates a flannel cluster to Calico" +LABEL vendor="Project Calico" +LABEL version="${GIT_VERSION}" + COPY --from=source / / USER 999 diff --git a/kube-controllers/pkg/cache/cache.go b/kube-controllers/pkg/cache/cache.go index 8913cc2354c..807d119adb1 100644 --- a/kube-controllers/pkg/cache/cache.go +++ b/kube-controllers/pkg/cache/cache.go @@ -30,15 +30,15 @@ import ( type ResourceCache interface { // Set sets the key to the provided value, and generates an update // on the queue the value has changed. - Set(key string, value interface{}) + Set(key string, value any) // Get gets the value associated with the given key. Returns nil // if the key is not present. - Get(key string) (interface{}, bool) + Get(key string) (any, bool) // Prime sets the key to the provided value, but does not generate // and update on the queue ever. - Prime(key string, value interface{}) + Prime(key string, value any) // Delete deletes the value identified by the given key from the cache, and // generates an update on the queue if a value was deleted. @@ -64,7 +64,7 @@ type ResourceCache interface { // Groups together all the arguments to pass in single struct. type ResourceCacheArgs struct { // ListFunc returns a mapping of keys to objects from the Calico datastore. - ListFunc func() (map[string]interface{}, error) + ListFunc func() (map[string]any, error) // ObjectType is the type of object which is to be stored in this cache. ObjectType reflect.Type @@ -95,7 +95,7 @@ type ReconcilerConfig struct { type calicoCache struct { threadSafeCache *cache.Cache workqueue workqueue.TypedRateLimitingInterface[any] - ListFunc func() (map[string]interface{}, error) + ListFunc func() (map[string]any, error) ObjectType reflect.Type log *log.Entry running bool @@ -122,7 +122,7 @@ func NewResourceCache(args ResourceCacheArgs) ResourceCache { } } -func (c *calicoCache) Set(key string, newObj interface{}) { +func (c *calicoCache) Set(key string, newObj any) { if reflect.TypeOf(newObj) != c.ObjectType { c.log.Fatalf("Wrong object type received to store in cache. Expected: %s, Found: %s", c.ObjectType, reflect.TypeOf(newObj)) } @@ -159,7 +159,7 @@ func (c *calicoCache) Clean(key string) { c.threadSafeCache.Delete(key) } -func (c *calicoCache) Get(key string) (interface{}, bool) { +func (c *calicoCache) Get(key string) (any, bool) { obj, found := c.threadSafeCache.Get(key) if found { return obj, true @@ -169,7 +169,7 @@ func (c *calicoCache) Get(key string) (interface{}, bool) { // Prime adds the key and value to the cache but will never generate // an update on the queue. -func (c *calicoCache) Prime(key string, value interface{}) { +func (c *calicoCache) Prime(key string, value any) { c.threadSafeCache.Set(key, value, cache.NoExpiration) } diff --git a/kube-controllers/pkg/cache/cache_suite_test.go b/kube-controllers/pkg/cache/cache_suite_test.go index e0d67bdde8a..34dc9cf6231 100644 --- a/kube-controllers/pkg/cache/cache_suite_test.go +++ b/kube-controllers/pkg/cache/cache_suite_test.go @@ -17,13 +17,13 @@ package cache_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" ) func TestCache(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/cache_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Cache Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/cache_suite.xml" + ginkgo.RunSpecs(t, "Cache Suite", suiteConfig, reporterConfig) } diff --git a/kube-controllers/pkg/cache/cache_test.go b/kube-controllers/pkg/cache/cache_test.go index 5e78bee0ae5..e0ac129e4c5 100644 --- a/kube-controllers/pkg/cache/cache_test.go +++ b/kube-controllers/pkg/cache/cache_test.go @@ -18,7 +18,7 @@ import ( "fmt" "reflect" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/kube-controllers/pkg/cache" @@ -28,8 +28,8 @@ type resource struct { name string } -func listFunc() (map[string]interface{}, error) { - m := make(map[string]interface{}) +func listFunc() (map[string]any, error) { + m := make(map[string]any) for i := 1; i <= 10; i++ { resourceName := fmt.Sprintf("ns%d", i) obj := resource{ @@ -45,7 +45,7 @@ var _ = Describe("Cache", func() { rcargs := cache.ResourceCacheArgs{ ListFunc: listFunc, - ObjectType: reflect.TypeOf(resource{}), + ObjectType: reflect.TypeFor[resource](), } Context("Get operation", func() { diff --git a/kube-controllers/pkg/config/config_fv_test.go b/kube-controllers/pkg/config/config_fv_test.go index a6a7f6314a5..96b83c40509 100644 --- a/kube-controllers/pkg/config/config_fv_test.go +++ b/kube-controllers/pkg/config/config_fv_test.go @@ -16,11 +16,10 @@ package config_test import ( "context" - "os" "regexp" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -41,7 +40,8 @@ var _ = Describe("KubeControllersConfiguration FV tests", func() { c client.Interface k8sClient *kubernetes.Clientset controllerManager *containers.Container - kconfigFile *os.File + kconfigFile string + removeKubeconfig func() ) BeforeEach(func() { @@ -54,16 +54,9 @@ var _ = Describe("KubeControllersConfiguration FV tests", func() { // Write out a kubeconfig file var err error - kconfigFile, err = os.CreateTemp("", "ginkgo-nodecontroller") - Expect(err).NotTo(HaveOccurred()) - data := testutils.BuildKubeconfig(apiserver.IP) - _, err = kconfigFile.Write([]byte(data)) - Expect(err).NotTo(HaveOccurred()) - - // Make the kubeconfig readable by the container. - Expect(kconfigFile.Chmod(os.ModePerm)).NotTo(HaveOccurred()) + kconfigFile, removeKubeconfig = testutils.BuildKubeconfig(apiserver.IP) - k8sClient, err = testutils.GetK8sClient(kconfigFile.Name()) + k8sClient, err = testutils.GetK8sClient(kconfigFile) Expect(err).NotTo(HaveOccurred()) // Wait for the apiserver to be available. @@ -82,16 +75,16 @@ var _ = Describe("KubeControllersConfiguration FV tests", func() { AfterEach(func() { _ = c.Close() - _ = os.Remove(kconfigFile.Name()) controllerManager.Stop() uut.Stop() apiserver.Stop() etcd.Stop() + removeKubeconfig() }) Context("with no KubeControllersConfig at start of day", func() { BeforeEach(func() { - uut = testutils.RunKubeControllerWithEnv(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name(), nil) + uut = testutils.RunKubeControllerWithEnv(apiconfig.EtcdV3, etcd.IP, kconfigFile, nil) }) It("should create default config", func() { @@ -102,6 +95,7 @@ var _ = Describe("KubeControllersConfiguration FV tests", func() { }, time.Second*10, time.Millisecond*500).ShouldNot(BeNil()) // Spot check the status to make sure it's set. + Expect(out.Status.RunningConfig).ToNot(BeNil()) Expect(out.Status.RunningConfig.HealthChecks).To(Equal(v3.Enabled)) }) @@ -123,6 +117,9 @@ var _ = Describe("KubeControllersConfiguration FV tests", func() { if err != nil { return "" } + if out.Status.RunningConfig == nil { + return "" + } return out.Status.RunningConfig.HealthChecks }, time.Second*5).Should(Equal(v3.Enabled)) }) @@ -156,6 +153,9 @@ var _ = Describe("KubeControllersConfiguration FV tests", func() { Eventually(func() bool { out, err = c.KubeControllersConfiguration().Get(context.Background(), "default", options.GetOptions{}) Expect(err).ToNot(HaveOccurred()) + if out.Status.RunningConfig == nil { + return false + } return out.Status.RunningConfig.HealthChecks != "" }, time.Second*10, time.Millisecond*500).Should(BeTrue()) @@ -176,7 +176,7 @@ var _ = Describe("KubeControllersConfiguration FV tests", func() { _, err := c.KubeControllersConfiguration().Create(context.Background(), kcc, options.SetOptions{}) Expect(err).ToNot(HaveOccurred()) - uut = testutils.RunKubeControllerWithEnv(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name(), nil) + uut = testutils.RunKubeControllerWithEnv(apiconfig.EtcdV3, etcd.IP, kconfigFile, nil) }) It("should set status matching config with defaults for unset values", func() { @@ -185,12 +185,16 @@ var _ = Describe("KubeControllersConfiguration FV tests", func() { var err error out, err = c.KubeControllersConfiguration().Get(context.Background(), "default", options.GetOptions{}) Expect(err).ToNot(HaveOccurred()) + if out.Status.RunningConfig == nil { + return nil + } return out.Status.RunningConfig.Controllers.Namespace }, time.Second*10, time.Millisecond*500).ShouldNot(BeNil()) Expect(out.Status.RunningConfig.Controllers.Node).To(BeNil()) Expect(out.Status.RunningConfig.Controllers.Policy).To(BeNil()) Expect(out.Status.RunningConfig.Controllers.WorkloadEndpoint).To(BeNil()) Expect(out.Status.RunningConfig.Controllers.ServiceAccount).To(BeNil()) + Expect(out.Status.RunningConfig.Controllers.LoadBalancer).To(BeNil()) // These fields are defaulted Expect(out.Status.RunningConfig.HealthChecks).To(Equal(v3.Enabled)) @@ -211,7 +215,7 @@ var _ = Describe("KubeControllersConfiguration FV tests", func() { _, err := c.KubeControllersConfiguration().Create(context.Background(), kcc, options.SetOptions{}) Expect(err).ToNot(HaveOccurred()) - uut = testutils.RunKubeControllerWithEnv(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name(), map[string]string{ + uut = testutils.RunKubeControllerWithEnv(apiconfig.EtcdV3, etcd.IP, kconfigFile, map[string]string{ "ENABLED_CONTROLLERS": "node", }) }) @@ -244,5 +248,18 @@ var _ = Describe("KubeControllersConfiguration FV tests", func() { Expect(err).ToNot(HaveOccurred()) Expect(kcc.Status.EnvironmentVars).To(Equal(map[string]string{"ENABLED_CONTROLLERS": "node"})) }) + + It("should not default loadbalancer config if not part of ENABLED_CONTROLLERS", func() { + // Wait until controller is up and has set status + var kcc *v3.KubeControllersConfiguration + Eventually(func() map[string]string { + var err error + kcc, err = c.KubeControllersConfiguration().Get(context.Background(), "default", options.GetOptions{}) + Expect(err).ToNot(HaveOccurred()) + return kcc.Status.EnvironmentVars + }, time.Second*10, time.Millisecond*500).Should(Equal(map[string]string{"ENABLED_CONTROLLERS": "node"})) + + Expect(kcc.Status.RunningConfig.Controllers.LoadBalancer).To(BeNil()) + }) }) }) diff --git a/kube-controllers/pkg/config/config_suite_test.go b/kube-controllers/pkg/config/config_suite_test.go index 78608243901..a37bad91ff5 100644 --- a/kube-controllers/pkg/config/config_suite_test.go +++ b/kube-controllers/pkg/config/config_suite_test.go @@ -17,9 +17,8 @@ package config_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestConfig(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/config_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Config Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/config_suite.xml" + ginkgo.RunSpecs(t, "Config Suite", suiteConfig, reporterConfig) } diff --git a/kube-controllers/pkg/config/config_test.go b/kube-controllers/pkg/config/config_test.go index 1eceda0f6cf..dcd779d19a2 100644 --- a/kube-controllers/pkg/config/config_test.go +++ b/kube-controllers/pkg/config/config_test.go @@ -19,7 +19,7 @@ import ( "os" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" @@ -32,7 +32,6 @@ import ( ) var _ = Describe("Config", func() { - // unsetEnv() function that unsets environment variables // required by kube-controllers controller unsetEnv := func() { @@ -112,7 +111,7 @@ var _ = Describe("Config", func() { cancel() }) - It("should return default RunConfig", func(done Done) { + It("should return default RunConfig", func() { runCfg := <-ctrl.ConfigChan() Expect(runCfg.LogLevelScreen).To(Equal(log.InfoLevel)) Expect(runCfg.HealthEnabled).To(BeTrue()) @@ -148,10 +147,12 @@ var _ = Describe("Config", func() { Expect(rc.LoadBalancer).To(Equal(&config.LoadBalancerControllerConfig{ AssignIPs: v3.AllServices, })) - close(done) + Expect(rc.Migration).To(Equal(&config.MigrationControllerConfig{ + PolicyNameMigrator: "Enabled", + })) }) - It("should write status", func(done Done) { + It("should write status", func() { <-ctrl.ConfigChan() Expect(m.update).ToNot(BeNil()) s := m.update.Status @@ -167,17 +168,20 @@ var _ = Describe("Config", func() { LeakGracePeriod: &v1.Duration{Duration: 15 * time.Minute}, })) Expect(c.Policy).To(Equal(&v3.PolicyControllerConfig{ - ReconcilerPeriod: &v1.Duration{Duration: time.Minute * 5}})) + ReconcilerPeriod: &v1.Duration{Duration: time.Minute * 5}, + })) Expect(c.WorkloadEndpoint).To(Equal(&v3.WorkloadEndpointControllerConfig{ - ReconcilerPeriod: &v1.Duration{Duration: time.Minute * 5}})) + ReconcilerPeriod: &v1.Duration{Duration: time.Minute * 5}, + })) Expect(c.Namespace).To(Equal(&v3.NamespaceControllerConfig{ - ReconcilerPeriod: &v1.Duration{Duration: time.Minute * 5}})) + ReconcilerPeriod: &v1.Duration{Duration: time.Minute * 5}, + })) Expect(c.ServiceAccount).To(Equal(&v3.ServiceAccountControllerConfig{ - ReconcilerPeriod: &v1.Duration{Duration: time.Minute * 5}})) + ReconcilerPeriod: &v1.Duration{Duration: time.Minute * 5}, + })) Expect(c.LoadBalancer).To(Equal(&v3.LoadBalancerControllerConfig{ AssignIPs: v3.AllServices, })) - close(done) }) }) @@ -202,16 +206,23 @@ var _ = Describe("Config", func() { LeakGracePeriod: &v1.Duration{Duration: 20 * time.Minute}, }, Policy: &v3.PolicyControllerConfig{ - ReconcilerPeriod: &v1.Duration{Duration: time.Second * 30}}, + ReconcilerPeriod: &v1.Duration{Duration: time.Second * 30}, + }, WorkloadEndpoint: &v3.WorkloadEndpointControllerConfig{ - ReconcilerPeriod: &v1.Duration{Duration: time.Second * 31}}, + ReconcilerPeriod: &v1.Duration{Duration: time.Second * 31}, + }, Namespace: &v3.NamespaceControllerConfig{ - ReconcilerPeriod: &v1.Duration{Duration: time.Second * 32}}, + ReconcilerPeriod: &v1.Duration{Duration: time.Second * 32}, + }, ServiceAccount: &v3.ServiceAccountControllerConfig{ - ReconcilerPeriod: &v1.Duration{Duration: time.Second * 33}}, + ReconcilerPeriod: &v1.Duration{Duration: time.Second * 33}, + }, LoadBalancer: &v3.LoadBalancerControllerConfig{ AssignIPs: v3.RequestedServicesOnly, }, + Migration: &v3.MigrationControllerConfig{ + PolicyNameMigrator: "Disabled", + }, }, } m = &mockKCC{get: kcc} @@ -223,7 +234,7 @@ var _ = Describe("Config", func() { cancel() }) - It("should return RunConfig matching API", func(done Done) { + It("should return RunConfig matching API", func() { runCfg := <-ctrl.ConfigChan() Expect(runCfg.LogLevelScreen).To(Equal(log.WarnLevel)) Expect(runCfg.HealthEnabled).To(BeFalse()) @@ -258,10 +269,12 @@ var _ = Describe("Config", func() { Expect(rc.LoadBalancer).To(Equal(&config.LoadBalancerControllerConfig{ AssignIPs: v3.RequestedServicesOnly, })) - close(done) + Expect(rc.Migration).To(Equal(&config.MigrationControllerConfig{ + PolicyNameMigrator: "Disabled", + })) }) - It("should write status matching API", func(done Done) { + It("should write status matching API", func() { <-ctrl.ConfigChan() Expect(m.update).ToNot(BeNil()) s := m.update.Status @@ -269,8 +282,8 @@ var _ = Describe("Config", func() { // Since there are no environment variables, the running config // should be exactly the API Spec - Expect(s.RunningConfig).To(Equal(m.get.Spec)) - close(done) + Expect(s.RunningConfig).NotTo(BeNil()) + Expect(*s.RunningConfig).To(Equal(m.get.Spec)) }) }) @@ -290,13 +303,12 @@ var _ = Describe("Config", func() { cancel() }) - It("should create a default KubeControllersConfig", func(done Done) { + It("should create a default KubeControllersConfig", func() { <-ctrl.ConfigChan() Expect(m.create.Spec).To(Equal(config.NewDefaultKubeControllersConfig().Spec)) - close(done) - }, 600) + }) - It("should send new update when API values change", func(done Done) { + It("should send new update when API values change", func() { // initial config <-ctrl.ConfigChan() @@ -318,10 +330,9 @@ var _ = Describe("Config", func() { // get the update <-ctrl.ConfigChan() - close(done) }) - It("should not send new update when Spec is unchanged", func(done Done) { + It("should not send new update when Spec is unchanged", func() { // initial config <-ctrl.ConfigChan() @@ -346,10 +357,9 @@ var _ = Describe("Config", func() { update = true } Expect(update).To(BeFalse()) - close(done) - }, 2) + }) - It("should handle watch closed by remote", func(done Done) { + It("should handle watch closed by remote", func() { // initial config <-ctrl.ConfigChan() @@ -398,16 +408,11 @@ var _ = Describe("Config", func() { // this should trigger an update <-ctrl.ConfigChan() - - close(done) - - }, 3) + }) }) - }) Context("with valid user defined values", func() { - var cfg *config.Config BeforeEach(func() { @@ -452,7 +457,7 @@ var _ = Describe("Config", func() { cancel() }) - It("should return RunConfig matching env", func(done Done) { + It("should return RunConfig matching env", func() { runCfg := <-ctrl.ConfigChan() Expect(runCfg.LogLevelScreen).To(Equal(log.DebugLevel)) Expect(runCfg.HealthEnabled).To(BeFalse()) @@ -472,10 +477,9 @@ var _ = Describe("Config", func() { Expect(rc.Namespace).To(BeNil()) Expect(rc.WorkloadEndpoint).To(BeNil()) Expect(rc.ServiceAccount).To(BeNil()) - close(done) - }, 600) + }) - It("should write status", func(done Done) { + It("should write status", func() { <-ctrl.ConfigChan() Expect(m.update).ToNot(BeNil()) s := m.update.Status @@ -499,11 +503,11 @@ var _ = Describe("Config", func() { LeakGracePeriod: &v1.Duration{Duration: 15 * time.Minute}, })) Expect(c.Policy).To(Equal(&v3.PolicyControllerConfig{ - ReconcilerPeriod: &v1.Duration{Duration: time.Second * 105}})) + ReconcilerPeriod: &v1.Duration{Duration: time.Second * 105}, + })) Expect(c.WorkloadEndpoint).To(BeNil()) Expect(c.Namespace).To(BeNil()) Expect(c.ServiceAccount).To(BeNil()) - close(done) }) }) @@ -530,13 +534,17 @@ var _ = Describe("Config", func() { }, }, Policy: &v3.PolicyControllerConfig{ - ReconcilerPeriod: &v1.Duration{Duration: time.Second * 30}}, + ReconcilerPeriod: &v1.Duration{Duration: time.Second * 30}, + }, WorkloadEndpoint: &v3.WorkloadEndpointControllerConfig{ - ReconcilerPeriod: &v1.Duration{Duration: time.Second * 31}}, + ReconcilerPeriod: &v1.Duration{Duration: time.Second * 31}, + }, Namespace: &v3.NamespaceControllerConfig{ - ReconcilerPeriod: &v1.Duration{Duration: time.Second * 32}}, + ReconcilerPeriod: &v1.Duration{Duration: time.Second * 32}, + }, ServiceAccount: &v3.ServiceAccountControllerConfig{ - ReconcilerPeriod: &v1.Duration{Duration: time.Second * 33}}, + ReconcilerPeriod: &v1.Duration{Duration: time.Second * 33}, + }, }, } m = &mockKCC{get: kcc} @@ -548,7 +556,7 @@ var _ = Describe("Config", func() { cancel() }) - It("should return RunConfig matching API environment", func(done Done) { + It("should return RunConfig matching API environment", func() { runCfg := <-ctrl.ConfigChan() Expect(runCfg.LogLevelScreen).To(Equal(log.DebugLevel)) Expect(runCfg.HealthEnabled).To(BeFalse()) @@ -570,10 +578,9 @@ var _ = Describe("Config", func() { Expect(rc.WorkloadEndpoint).To(BeNil()) Expect(rc.Namespace).To(BeNil()) Expect(rc.ServiceAccount).To(BeNil()) - close(done) }) - It("should write status matching environment", func(done Done) { + It("should write status matching environment", func() { <-ctrl.ConfigChan() Expect(m.update).ToNot(BeNil()) s := m.update.Status @@ -596,14 +603,13 @@ var _ = Describe("Config", func() { HostEndpoint: &v3.AutoHostEndpointConfig{AutoCreate: v3.Enabled, CreateDefaultHostEndpoint: v3.DefaultHostEndpointsEnabled}, })) Expect(c.Policy).To(Equal(&v3.PolicyControllerConfig{ - ReconcilerPeriod: &v1.Duration{Duration: time.Second * 105}})) + ReconcilerPeriod: &v1.Duration{Duration: time.Second * 105}, + })) Expect(c.WorkloadEndpoint).To(BeNil()) Expect(c.Namespace).To(BeNil()) Expect(c.ServiceAccount).To(BeNil()) - close(done) }) }) - }) Context("with invalid user defined values", func() { @@ -629,7 +635,6 @@ var _ = Describe("Config", func() { }) Context("with ENABLED_CONTROLLERS set", func() { - BeforeEach(func() { unsetEnv() err := os.Setenv("ENABLED_CONTROLLERS", "node,namespace,policy,serviceaccount,workloadendpoint") @@ -640,8 +645,7 @@ var _ = Describe("Config", func() { unsetEnv() }) - It("should use reconciler periods from API", func(done Done) { - + It("should use reconciler periods from API", func() { cfg := new(config.Config) err := cfg.Parse() Expect(err).ToNot(HaveOccurred()) @@ -658,13 +662,17 @@ var _ = Describe("Config", func() { HostEndpoint: &v3.AutoHostEndpointConfig{AutoCreate: v3.Enabled}, }, Policy: &v3.PolicyControllerConfig{ - ReconcilerPeriod: &v1.Duration{Duration: time.Second * 30}}, + ReconcilerPeriod: &v1.Duration{Duration: time.Second * 30}, + }, WorkloadEndpoint: &v3.WorkloadEndpointControllerConfig{ - ReconcilerPeriod: &v1.Duration{Duration: time.Second * 31}}, + ReconcilerPeriod: &v1.Duration{Duration: time.Second * 31}, + }, Namespace: &v3.NamespaceControllerConfig{ - ReconcilerPeriod: &v1.Duration{Duration: time.Second * 32}}, + ReconcilerPeriod: &v1.Duration{Duration: time.Second * 32}, + }, ServiceAccount: &v3.ServiceAccountControllerConfig{ - ReconcilerPeriod: &v1.Duration{Duration: time.Second * 33}}, + ReconcilerPeriod: &v1.Duration{Duration: time.Second * 33}, + }, }, } m := &mockKCC{get: kcc} @@ -676,7 +684,6 @@ var _ = Describe("Config", func() { Expect(runCfg.Controllers.WorkloadEndpoint.ReconcilerPeriod).To(Equal(time.Second * 31)) Expect(runCfg.Controllers.Namespace.ReconcilerPeriod).To(Equal(time.Second * 32)) Expect(runCfg.Controllers.ServiceAccount.ReconcilerPeriod).To(Equal(time.Second * 33)) - close(done) }) }) }) @@ -700,6 +707,11 @@ func (m *mockKCC) Update(ctx context.Context, res *v3.KubeControllersConfigurati return res, nil } +func (m *mockKCC) UpdateStatus(ctx context.Context, res *v3.KubeControllersConfiguration, opts options.SetOptions) (*v3.KubeControllersConfiguration, error) { + m.update = res.DeepCopy() + return res, nil +} + func (m *mockKCC) Delete(ctx context.Context, name string, opts options.DeleteOptions) (*v3.KubeControllersConfiguration, error) { panic("implement me") } diff --git a/kube-controllers/pkg/config/runconfig.go b/kube-controllers/pkg/config/runconfig.go index e7fe0923eca..a941ed9da17 100644 --- a/kube-controllers/pkg/config/runconfig.go +++ b/kube-controllers/pkg/config/runconfig.go @@ -60,6 +60,11 @@ type ControllersConfig struct { ServiceAccount *GenericControllerConfig Namespace *GenericControllerConfig LoadBalancer *LoadBalancerControllerConfig + Migration *MigrationControllerConfig +} + +type MigrationControllerConfig struct { + PolicyNameMigrator v3.ControllerMode } type GenericControllerConfig struct { @@ -87,11 +92,11 @@ type AutoHostEndpointConfig struct { } type AutoHostEndpointTemplate struct { - GenerateName string - InterfaceCIDRs []string - InterfaceSelector string - Labels map[string]string - NodeSelector string + GenerateName string + InterfaceCIDRs []string + InterfacePattern string + Labels map[string]string + NodeSelector string } type LoadBalancerControllerConfig struct { @@ -142,6 +147,9 @@ func NewDefaultKubeControllersConfig() *v3.KubeControllersConfiguration { LoadBalancer: &v3.LoadBalancerControllerConfig{ AssignIPs: v3.AllServices, }, + Migration: &v3.MigrationControllerConfig{ + PolicyNameMigrator: v3.ControllerEnabled, + }, }, } @@ -168,6 +176,11 @@ func syncDatastore(ctx context.Context, cfg Config, client clientv3.KubeControll // set to the empty state. var currentSet bool var w watch.Interface + defer func() { + if w != nil { + w.Stop() + } + }() env := make(map[string]string) for _, k := range AllEnvs { @@ -205,7 +218,7 @@ MAINLOOP: // Write the status back to the API datastore, so that end users can inspect the current // running config. snapshot.Status = status - snapshot, err = client.Update(ctx, snapshot, options.SetOptions{}) + snapshot, err = client.UpdateStatus(ctx, snapshot, options.SetOptions{}) if err != nil { log.WithError(err).Warn("unable to perform status update on KubeControllersConfiguration(default)") snapshot = nil @@ -243,7 +256,6 @@ MAINLOOP: time.Sleep(datastoreBackoff) continue MAINLOOP } - defer w.Stop() for e := range w.ResultChan() { switch e.Type { case watch.Error: @@ -270,7 +282,7 @@ MAINLOOP: // our update will trigger a watch update in an infinite loop if !reflect.DeepEqual(snapshot.Status, status) { snapshot.Status = status - snapshot, err = client.Update(ctx, snapshot, options.SetOptions{}) + snapshot, err = client.UpdateStatus(ctx, snapshot, options.SetOptions{}) if err != nil { // this probably means someone else is trying to write to the resource, // so best to just take a breath and start over @@ -332,7 +344,10 @@ func getOrCreateSnapshot(ctx context.Context, kcc clientv3.KubeControllersConfig // mergeConfig takes the environment variables, and resulting config func mergeConfig(envVars map[string]string, envCfg Config, apiCfg v3.KubeControllersConfigurationSpec) (RunConfig, v3.KubeControllersConfigurationStatus) { var rCfg RunConfig - status := v3.KubeControllersConfigurationStatus{EnvironmentVars: map[string]string{}} + status := v3.KubeControllersConfigurationStatus{ + RunningConfig: &v3.KubeControllersConfigurationSpec{}, + EnvironmentVars: map[string]string{}, + } rc := &rCfg.Controllers mergeLogLevel(envVars, &status, &rCfg, apiCfg) @@ -345,6 +360,10 @@ func mergeConfig(envVars map[string]string, envCfg Config, apiCfg v3.KubeControl mergeHealthEnabled(envVars, &status, &rCfg, apiCfg) + mergeLoadBalancer(&status, &rCfg, apiCfg) + + mergeMigrationController(&status, &rCfg, apiCfg) + // Merge prometheus information. if apiCfg.PrometheusMetricsPort != nil { rCfg.PrometheusPort = *apiCfg.PrometheusMetricsPort @@ -387,14 +406,33 @@ func mergeConfig(envVars map[string]string, envCfg Config, apiCfg v3.KubeControl rc.Namespace.NumberOfWorkers = envCfg.ProfileWorkers } - if rc.LoadBalancer != nil { + return rCfg, status +} + +func mergeLoadBalancer(status *v3.KubeControllersConfigurationStatus, rCfg *RunConfig, apiCfg v3.KubeControllersConfigurationSpec) { + if rCfg.Controllers.LoadBalancer != nil { if apiCfg.Controllers.LoadBalancer != nil { - rc.LoadBalancer.AssignIPs = apiCfg.Controllers.LoadBalancer.AssignIPs + rCfg.Controllers.LoadBalancer.AssignIPs = apiCfg.Controllers.LoadBalancer.AssignIPs status.RunningConfig.Controllers.LoadBalancer.AssignIPs = apiCfg.Controllers.LoadBalancer.AssignIPs } } +} - return rCfg, status +func mergeMigrationController(status *v3.KubeControllersConfigurationStatus, rCfg *RunConfig, apiCfg v3.KubeControllersConfigurationSpec) { + rCfg.Controllers.Migration = &MigrationControllerConfig{ + PolicyNameMigrator: v3.ControllerEnabled, + } + status.RunningConfig.Controllers.Migration = &v3.MigrationControllerConfig{ + PolicyNameMigrator: v3.ControllerEnabled, + } + + // Override from API if set. + if apiCfg.Controllers.Migration != nil { + if apiCfg.Controllers.Migration.PolicyNameMigrator == v3.ControllerDisabled { + rCfg.Controllers.Migration.PolicyNameMigrator = v3.ControllerDisabled + status.RunningConfig.Controllers.Migration.PolicyNameMigrator = v3.ControllerDisabled + } + } } func mergeAutoHostEndpoints(envVars map[string]string, status *v3.KubeControllersConfigurationStatus, rCfg *RunConfig, apiCfg v3.KubeControllersConfigurationSpec) { @@ -428,11 +466,11 @@ func mergeAutoHostEndpoints(envVars map[string]string, status *v3.KubeController var templates []AutoHostEndpointTemplate for _, template := range ac.Node.HostEndpoint.Templates { rcTemplate := AutoHostEndpointTemplate{ - GenerateName: template.GenerateName, - InterfaceCIDRs: template.InterfaceCIDRs, - InterfaceSelector: template.InterfaceSelector, - NodeSelector: template.NodeSelector, - Labels: template.Labels, + GenerateName: template.GenerateName, + InterfaceCIDRs: template.InterfaceCIDRs, + InterfacePattern: template.InterfacePattern, + NodeSelector: template.NodeSelector, + Labels: template.Labels, } templates = append(templates, rcTemplate) @@ -465,11 +503,11 @@ func mergeAutoHostEndpoints(envVars map[string]string, status *v3.KubeController for template := range rc.Node.AutoHostEndpointConfig.Templates { rcTemplate := (rc.Node.AutoHostEndpointConfig.Templates)[template] scTemplate := v3.Template{ - GenerateName: rcTemplate.GenerateName, - InterfaceCIDRs: rcTemplate.InterfaceCIDRs, - InterfaceSelector: rcTemplate.InterfaceSelector, - NodeSelector: rcTemplate.NodeSelector, - Labels: rcTemplate.Labels, + GenerateName: rcTemplate.GenerateName, + InterfaceCIDRs: rcTemplate.InterfaceCIDRs, + InterfacePattern: rcTemplate.InterfacePattern, + NodeSelector: rcTemplate.NodeSelector, + Labels: rcTemplate.Labels, } templates = append(templates, scTemplate) @@ -608,7 +646,7 @@ func mergeEnabledControllers(envVars map[string]string, status *v3.KubeControlle v, p := envVars[EnvEnabledControllers] if p { status.EnvironmentVars[EnvEnabledControllers] = v - for _, controllerType := range strings.Split(v, ",") { + for controllerType := range strings.SplitSeq(v, ",") { switch controllerType { case "workloadendpoint": rc.WorkloadEndpoint = &GenericControllerConfig{} @@ -626,8 +664,12 @@ func mergeEnabledControllers(envVars map[string]string, status *v3.KubeControlle rc.ServiceAccount = &GenericControllerConfig{} sc.ServiceAccount = &v3.ServiceAccountControllerConfig{} case "loadbalancer": - rc.LoadBalancer = &LoadBalancerControllerConfig{} - sc.LoadBalancer = &v3.LoadBalancerControllerConfig{} + rc.LoadBalancer = &LoadBalancerControllerConfig{ + AssignIPs: v3.AllServices, + } + sc.LoadBalancer = &v3.LoadBalancerControllerConfig{ + AssignIPs: v3.AllServices, + } case "flannelmigration": log.WithField(EnvEnabledControllers, v).Fatal("cannot run flannelmigration with other controllers") default: diff --git a/kube-controllers/pkg/controllers/flannelmigration/config_test.go b/kube-controllers/pkg/controllers/flannelmigration/config_test.go index c37e570e73d..0e713287f9b 100644 --- a/kube-controllers/pkg/controllers/flannelmigration/config_test.go +++ b/kube-controllers/pkg/controllers/flannelmigration/config_test.go @@ -17,7 +17,7 @@ package flannelmigration_test import ( "os" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" fm "github.com/projectcalico/calico/kube-controllers/pkg/controllers/flannelmigration" diff --git a/kube-controllers/pkg/controllers/flannelmigration/flannel_migration_fv_test.go b/kube-controllers/pkg/controllers/flannelmigration/flannel_migration_fv_test.go index c4548bbc01d..593e8e2213e 100644 --- a/kube-controllers/pkg/controllers/flannelmigration/flannel_migration_fv_test.go +++ b/kube-controllers/pkg/controllers/flannelmigration/flannel_migration_fv_test.go @@ -18,12 +18,11 @@ import ( "context" "fmt" "net" - "os" "regexp" "time" gocidr "github.com/apparentlymart/go-cidr/cidr" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/sirupsen/logrus" @@ -63,9 +62,10 @@ var _ = Describe("flannel-migration-controller FV test", func() { bc backend.Client k8sClient *kubernetes.Clientset controllerManager *containers.Container - kconfigfile *os.File + kconfigfile string err error flannelCluster *testutils.FlannelCluster + removeKubeconfig func() ) logKubectl := func(args ...string) { @@ -80,7 +80,7 @@ var _ = Describe("flannel-migration-controller FV test", func() { // (container.Run returns after 'docker ps' shows the container, the polling interval is 1 second.) // Add 60 seconds delay before main thread exits, this is to make sure controller is still running // after test case completed and stopped by AfterEach. - migrationController = testutils.RunFlannelMigrationController(kconfigfile.Name(), controllerNodeName, flannelSubnetEnv, 3, 60) + migrationController = testutils.RunFlannelMigrationController(kconfigfile, controllerNodeName, flannelSubnetEnv, 3, 60) } stopController := func() { @@ -94,18 +94,9 @@ var _ = Describe("flannel-migration-controller FV test", func() { // Run apiserver. apiserver = testutils.RunK8sApiserver(etcd.IP) - // Write out a kubeconfig file - kconfigfile, err = os.CreateTemp("", "ginkgo-migrationcontroller") - Expect(err).NotTo(HaveOccurred()) + kconfigfile, removeKubeconfig = testutils.BuildKubeconfig(apiserver.IP) - data := testutils.BuildKubeconfig(apiserver.IP) - _, err = kconfigfile.Write([]byte(data)) - Expect(err).NotTo(HaveOccurred()) - - // Make the kubeconfig readable by the container. - Expect(kconfigfile.Chmod(os.ModePerm)).NotTo(HaveOccurred()) - - k8sClient, err = testutils.GetK8sClient(kconfigfile.Name()) + k8sClient, err = testutils.GetK8sClient(kconfigfile) Expect(err).NotTo(HaveOccurred()) // Wait for the apiserver to be available. @@ -116,20 +107,13 @@ var _ = Describe("flannel-migration-controller FV test", func() { // Apply the necessary CRDs. There can sometimes be a delay between starting // the API server and when CRDs are apply-able, so retry here. - apply := func() error { - out, err := apiserver.ExecOutput("kubectl", "apply", "-f", "/crds/") - if err != nil { - return fmt.Errorf("%s: %s", err, out) - } - return nil - } - Eventually(apply, 10*time.Second).ShouldNot(HaveOccurred()) + testutils.ApplyCRDs(apiserver) // Make a Calico client and backend client. type accessor interface { Backend() backend.Client } - calicoClient = testutils.GetCalicoClient(apiconfig.Kubernetes, "", kconfigfile.Name()) + calicoClient = testutils.GetCalicoClient(apiconfig.Kubernetes, "", kconfigfile) bc = calicoClient.(accessor).Backend() // Run controller manager. @@ -152,10 +136,10 @@ var _ = Describe("flannel-migration-controller FV test", func() { AfterEach(func() { _ = calicoClient.Close() flannelCluster.Reset() - _ = os.Remove(kconfigfile.Name()) controllerManager.Stop() apiserver.Stop() etcd.Stop() + removeKubeconfig() }) Context("Should migrate FV tests", func() { @@ -334,10 +318,10 @@ func validateCalicoIPAM(fc *testutils.FlannelCluster, client client.Interface, b Expect(node.Spec.VXLANTunnelMACAddr).To(Equal(fn.VtepMac)) // Check tunnel ip been correctly assigned. - attr, _, err := client.IPAM().GetAssignmentAttributes(ctx, vtepIP) + allocAttr, err := client.IPAM().GetAssignmentAttributes(ctx, vtepIP) Expect(err).NotTo(HaveOccurred()) - Expect(attr[ipam.AttributeNode]).To(Equal(nodeName)) - Expect(attr[ipam.AttributeType]).To(Equal(ipam.AttributeTypeVXLAN)) + Expect(allocAttr.ActiveOwnerAttrs[ipam.AttributeNode]).To(Equal(nodeName)) + Expect(allocAttr.ActiveOwnerAttrs[ipam.AttributeType]).To(Equal(ipam.AttributeTypeVXLAN)) // Check block affinities been correctly claimed. opts := model.BlockAffinityListOptions{Host: nodeName, AffinityType: string(ipam.AffinityTypeHost), IPVersion: 4} diff --git a/kube-controllers/pkg/controllers/flannelmigration/flannelmigration_suite_test.go b/kube-controllers/pkg/controllers/flannelmigration/flannelmigration_suite_test.go index ce696e42512..74e241a4f63 100644 --- a/kube-controllers/pkg/controllers/flannelmigration/flannelmigration_suite_test.go +++ b/kube-controllers/pkg/controllers/flannelmigration/flannelmigration_suite_test.go @@ -17,9 +17,8 @@ package flannelmigration_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestConfig(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../report/flannelmigration_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "flannelmigration Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/flannelmigration_suite.xml" + ginkgo.RunSpecs(t, "flannelmigration Suite", suiteConfig, reporterConfig) } diff --git a/kube-controllers/pkg/controllers/flannelmigration/ipam_migrator.go b/kube-controllers/pkg/controllers/flannelmigration/ipam_migrator.go index 806096f143c..18c7bba2404 100644 --- a/kube-controllers/pkg/controllers/flannelmigration/ipam_migrator.go +++ b/kube-controllers/pkg/controllers/flannelmigration/ipam_migrator.go @@ -25,7 +25,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" - libapi "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" "github.com/projectcalico/calico/libcalico-go/lib/ipam" @@ -86,16 +86,12 @@ func (m ipamMigrator) InitialiseIPPoolAndFelixConfig() error { } // Based on FlannelSubnetLen, work out the size of ippool. - blockSize := m.config.DefaultIppoolSize - if m.config.FlannelSubnetLen > m.config.DefaultIppoolSize { + blockSize := max(m.config.FlannelSubnetLen, // Flannel subnet is smaller than one Calico IPAM block with default size of /26. - blockSize = m.config.FlannelSubnetLen - } - blockSizeV6 := m.config.DefaultIppoolSizeV6 - if m.config.FlannelIpv6SubnetLen > m.config.DefaultIppoolSizeV6 { + m.config.DefaultIppoolSize) + blockSizeV6 := max(m.config.FlannelIpv6SubnetLen, // Flannel subnet is smaller than one Calico IPAM block with default size of /122. - blockSizeV6 = m.config.FlannelIpv6SubnetLen - } + m.config.DefaultIppoolSizeV6) // Canal creates default ippool and FelixConfigurations with no VXLAN. // In this case, we should not check vxlan settings for existing ippool or FelixConfigurations. @@ -225,8 +221,9 @@ func setupCalicoNodeVxlan(ctx context.Context, c client.Interface, nodeName stri // Assign vtep IP. // Check current status of vtep IP. It could be assigned already if migration controller restarts. assign := true - attr, _, err := c.IPAM().GetAssignmentAttributes(ctx, vtepIP) + allocAttr, err := c.IPAM().GetAssignmentAttributes(ctx, vtepIP) if err == nil { + attr := allocAttr.ActiveOwnerAttrs if attr[ipam.AttributeType] == ipam.AttributeTypeVXLAN && attr[ipam.AttributeNode] == nodeName { // The tunnel address is still valid, do nothing. log.Infof("Calico Node %s vtep IP been assigned already.", nodeName) @@ -276,7 +273,7 @@ func setupCalicoNodeVxlan(ctx context.Context, c client.Interface, nodeName stri log.Infof("Calico Node current value: %+v.", node) - node.Spec.BGP = &libapi.NodeBGPSpec{} + node.Spec.BGP = &internalapi.NodeBGPSpec{} // Set public ip with subnet /32. // The subnet part is required to pass Felix validation. node.Spec.BGP.IPv4Address = fmt.Sprintf("%s/32", publicIP) diff --git a/kube-controllers/pkg/controllers/flannelmigration/migration_controller.go b/kube-controllers/pkg/controllers/flannelmigration/migration_controller.go index 815f33684e2..dd217eb8f23 100644 --- a/kube-controllers/pkg/controllers/flannelmigration/migration_controller.go +++ b/kube-controllers/pkg/controllers/flannelmigration/migration_controller.go @@ -107,7 +107,7 @@ func NewFlannelMigrationController(ctx context.Context, k8sClientset *kubernetes // Setup event handlers handlers := cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { + AddFunc: func(obj any) { mc.processNewNode(obj.(*v1.Node)) }, } diff --git a/felix/hashutils/hashutils_suite_test.go b/kube-controllers/pkg/controllers/ippool/ippool_suite_test.go similarity index 59% rename from felix/hashutils/hashutils_suite_test.go rename to kube-controllers/pkg/controllers/ippool/ippool_suite_test.go index 4336f33023e..e49ad48d7d7 100644 --- a/felix/hashutils/hashutils_suite_test.go +++ b/kube-controllers/pkg/controllers/ippool/ippool_suite_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2017 Tigera, Inc. All rights reserved. +// Copyright (c) 2026 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,24 +12,26 @@ // See the License for the specific language governing permissions and // limitations under the License. -package hashutils_test +package ippool_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + "github.com/sirupsen/logrus" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func init() { testutils.HookLogrusForGinkgo() + logrus.SetLevel(logrus.DebugLevel) } -func TestHashutils(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/hashutils_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Hashutils Suite", []Reporter{junitReporter}) +func Test(t *testing.T) { + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/ippool_controller_suite.xml" + ginkgo.RunSpecs(t, "IPPool controller suite", suiteConfig, reporterConfig) } diff --git a/kube-controllers/pkg/controllers/ippool/pool_controller.go b/kube-controllers/pkg/controllers/ippool/pool_controller.go new file mode 100644 index 00000000000..ceb496d14a9 --- /dev/null +++ b/kube-controllers/pkg/controllers/ippool/pool_controller.go @@ -0,0 +1,477 @@ +// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// +// 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 ippool + +import ( + "context" + "fmt" + "slices" + "strings" + + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "github.com/projectcalico/api/pkg/client/clientset_generated/clientset" + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + uruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/tools/cache" + + "github.com/projectcalico/calico/felix/ip" + "github.com/projectcalico/calico/kube-controllers/pkg/controllers/controller" + "github.com/projectcalico/calico/libcalico-go/lib/ipam" + cnet "github.com/projectcalico/calico/libcalico-go/lib/net" +) + +const ( + IPPoolFinalizer = "projectcalico.org/ippool-finalizer" +) + +// IPPoolController is responsible for watching IPPool and IPAMBlock resources and managing the finalization / deletion +// of IP pools. when a new IP pool is added, it ensures a finalizer is added to it. When an IP pool is deleted, it ensures that all +// associated IPAM blocks are released before allowing the pool to be fully deleted. +type IPPoolController struct { + ctx context.Context + + // For syncing node objects from the k8s API. + poolInformer cache.SharedIndexInformer + blockInformer cache.SharedIndexInformer + + cli clientset.Interface + ipam ipam.Interface +} + +func NewController( + ctx context.Context, + cli clientset.Interface, + poolInformer cache.SharedIndexInformer, + blockInformer cache.SharedIndexInformer, + ipam ipam.Interface, +) controller.Controller { + c := &IPPoolController{ + ctx: ctx, + cli: cli, + poolInformer: poolInformer, + blockInformer: blockInformer, + ipam: ipam, + } + + // Configure events for new IP pools. + poolHandlers := cache.ResourceEventHandlerFuncs{ + DeleteFunc: func(obj any) { + logrus.WithField("name", obj.(*v3.IPPool).Name).Info("Handling pool deletion") + if err := c.Reconcile(obj.(*v3.IPPool)); err != nil { + logrus.WithError(err).Error("Error handling pool deletion") + } + }, + AddFunc: func(obj any) { + logrus.WithField("name", obj.(*v3.IPPool).Name).Info("Handling pool add") + if err := c.Reconcile(obj.(*v3.IPPool)); err != nil { + logrus.WithError(err).Error("Error handling pool add") + } + }, + UpdateFunc: func(oldObj, newObj any) { + logrus.WithField("name", newObj.(*v3.IPPool).Name).Info("Handling pool update") + if err := c.Reconcile(newObj.(*v3.IPPool)); err != nil { + logrus.WithError(err).Error("Error handling pool update") + } + }, + } + if _, err := poolInformer.AddEventHandler(poolHandlers); err != nil { + logrus.WithError(err).Fatal("Failed to register event handler for IPPool") + } + + // Configure handlers for IPAM block updates. We need to trigger a reconcile for any + // deleting IP pools when blocks are deleted, to ensure we can finalize the pool. + blockHandlers := cache.ResourceEventHandlerFuncs{ + DeleteFunc: func(obj any) { + block := obj.(*v3.IPAMBlock) + + // Find any pools that might be associated with this block and trigger a reconcile. + for _, i := range poolInformer.GetIndexer().List() { + pool := i.(*v3.IPPool) + if pool.DeletionTimestamp == nil { + // Pool is not being deleted, skip it. + continue + } + _, poolNet, err := cnet.ParseCIDR(pool.Spec.CIDR) + if err != nil { + logrus.WithError(err).WithField("cidr", pool.Spec.CIDR).Error("Failed to parse CIDR from IPPool") + continue + } + _, blockNet, err := cnet.ParseCIDR(block.Spec.CIDR) + if err != nil { + logrus.WithError(err).WithField("cidr", block.Spec.CIDR).Error("Failed to parse CIDR from IPAMBlock") + continue + } + if poolNet.Contains(blockNet.IP) { + logrus.WithField("name", pool.Name).Debug("Triggering reconcile for finalizing pool due to block deletion") + if err := c.Reconcile(pool); err != nil { + logrus.WithError(err).Error("Error handling pool reconcile due to block deletion") + } + } + } + }, + } + if _, err := blockInformer.AddEventHandler(blockHandlers); err != nil { + logrus.WithError(err).Fatal("Failed to register event handler for IPAMBlock") + } + + return c +} + +// Run starts the node controller. It does start-of-day preparation +// and then launches worker threads. +func (c *IPPoolController) Run(stopCh chan struct{}) { + defer uruntime.HandleCrash() + + logrus.Info("Starting IPPool controller") + + // Wait till k8s cache is synced + logrus.Debug("Waiting to sync with Kubernetes API") + if !cache.WaitForNamedCacheSync("pools", stopCh, c.poolInformer.HasSynced, c.blockInformer.HasSynced) { + logrus.Info("Failed to sync resources, received signal for controller to shut down.") + return + } + + logrus.Debug("Finished syncing with Kubernetes API") + + <-stopCh + logrus.Info("Stopping IPPool controller") +} + +func (c *IPPoolController) Reconcile(p *v3.IPPool) error { + ctx := context.TODO() + logCtx := logrus.WithFields(logrus.Fields{ + "name": p.Name, + "cidr": p.Spec.CIDR, + "hasFinalizer": hasFinalizer(p), + }) + logCtx.Debug("Reconciling IPPool") + + // First, check for overlapping IP pools and update their status accordingly. + if err := c.reconcileConditions(ctx); err != nil { + logCtx.WithError(err).Warn("Failed to reconcile pool overlaps") + } + + // Next, ensure that the finalizer is added / removed as needed. + if err := c.reconcileFinalizer(ctx, logCtx, p); err != nil { + return fmt.Errorf("failed to reconcile finalizer for IPPool: %w", err) + } + return nil +} + +// reconcileConditions checks for various conditions that should be set on each IP pool. +func (c *IPPoolController) reconcileConditions(ctx context.Context) error { + pools := c.poolInformer.GetIndexer().List() + slices.SortFunc(pools, poolSortFunc) + + // Every time an IP pool is added, updated, or deleted, we need to check if it changes the active set of pools. We only + // allow a single IP pool covering a given CIDR to be active at a time, and so we need to ensure that: + // - When a pool is added / updated, if it overlaps with an existing active pool, we should not enable the new pool. + // - When a pool is deleted, if it was the only active pool covering its CIDR, we should enable another overlapping pool if there is one. + // Use a trie to find overlapping pools more efficiently. We can insert each pool into the trie, and if we find an existing pool that + // overlaps with it, we can mark the new pool as disabled. + trie := ip.NewCIDRTrie() + triev6 := ip.NewCIDRTrie() + active := map[string]*v3.IPPool{} + overlapping := map[string]*v3.IPPool{} + for _, p := range pools { + pool := p.(*v3.IPPool) + + cidr, err := ip.CIDRFromString(pool.Spec.CIDR) + if err != nil { + logrus.WithError(err).WithField("cidr", pool.Spec.CIDR).Error("Failed to parse CIDR from IPPool") + continue + } + + // Use the appropriate trie based on the IP address family of this pool. + t := trie + if cidr.Version() == 6 { + t = triev6 + } + + // If the pool is administratively disabled, reflect that in its conditions and skip it for the purposes of + // determining overlaps, since an administratively disabled pool should not block other pools from being active. + if pool.Spec.Disabled { + cond := metav1.Condition{ + Type: v3.IPPoolConditionAllocatable, + Status: metav1.ConditionFalse, + Reason: v3.IPPoolReasonDisabled, + Message: "IPPool.Spec.Disabled is true", + } + if err := updateCondition(ctx, c.cli, pool, cond); err != nil { + logrus.WithError(err).WithField("pool", pool.Name).Error("Failed to update status of IPPool") + } + continue + } + if pool.DeletionTimestamp != nil { + cond := metav1.Condition{ + Type: v3.IPPoolConditionAllocatable, + Status: metav1.ConditionFalse, + Reason: v3.IPPoolReasonTerminating, + Message: "IPPool is being deleted", + } + if err := updateCondition(ctx, c.cli, pool, cond); err != nil { + logrus.WithError(err).WithField("pool", pool.Name).Error("Failed to update status of IPPool") + } + // If the pool is being deleted, we still want to consider it for overlaps. + // This ensures we don't preemptively enable another pool that might overlap with it until this pool + // is fully deleted. + t.Update(cidr, pool) + continue + } + + // Check if this pool is overlapped by any existing active pool in the trie. + if e := t.Get(cidr); e != nil || t.Intersects(cidr) || t.Covers(cidr) { + // This pool overlaps with an existing active pool, so we should disable it. + logrus.WithField("overlap", pool.Name).Debug("Found overlapping pools") + overlapping[pool.Name] = pool + } + + if _, ok := overlapping[pool.Name]; !ok { + // This pool does not overlap with any existing active pools, so we can add it to the active set and insert it into the trie. + logrus.WithField("pool", pool.Name).Debug("Found non-overlapping pool, adding to active set") + active[pool.Name] = pool + t.Update(cidr, pool) + } + } + + // Mark any overlapping pools as disabled. + for _, pool := range overlapping { + // Disable any other overlapping pools by setting a condition on them, which will prevent IPAM from allocating from those pools. + cond := metav1.Condition{ + Type: v3.IPPoolConditionAllocatable, + Status: metav1.ConditionFalse, + Reason: v3.IPPoolReasonCIDROverlap, + Message: "CIDR overlaps another pool; disabled to prevent IP allocation conflicts.", + } + if err := updateCondition(ctx, c.cli, pool, cond); err != nil { + logrus.WithError(err).WithField("pool", pool.Name).Error("Failed to update status of IPPool") + } + } + + // Make sure non-overlapping pools are enabled by removing the disabled condition if it exists. + for _, pool := range active { + cond := metav1.Condition{ + Type: v3.IPPoolConditionAllocatable, + Status: metav1.ConditionTrue, + Reason: v3.IPPoolReasonOK, + Message: "IPPool is available for IP allocation.", + } + if setConditionOnPool(pool, cond) { + logrus.WithField("pool", pool.Name).Infof("Setting condition %s to %s", cond.Type, cond.Status) + if _, err := c.cli.ProjectcalicoV3().IPPools().UpdateStatus(ctx, pool, metav1.UpdateOptions{}); err != nil { + logrus.WithError(err).WithField("otherPool", pool.Name).Error("Failed to update status of IPPool") + } + + // If this pool was previously disabled, we need to ensure the finalizer is correctly set on it since + // it would not have had one applied when it was disabled. + if err := c.reconcileFinalizer(ctx, logrus.WithField("pool", pool.Name), pool); err != nil { + logrus.WithError(err).WithField("otherPool", pool.Name).Error("Failed to reconcile finalizer for IPPool") + } + } + } + + return nil +} + +func poolSortFunc(a, b any) int { + poolA := a.(*v3.IPPool) + poolB := b.(*v3.IPPool) + + aCat := poolSortCategory(poolA) + bCat := poolSortCategory(poolB) + if aCat != bCat { + return aCat - bCat + } + + // Within the same category, sort by creation timestamp (older first). + if poolA.CreationTimestamp.Before(&poolB.CreationTimestamp) { + return -1 + } + if poolB.CreationTimestamp.Before(&poolA.CreationTimestamp) { + return 1 + } + + // If creation timestamps are equal, sort by name to ensure a deterministic order. + return strings.Compare(poolA.Name, poolB.Name) +} + +// poolSortCategory returns the sort priority for a pool: +// - 0: Active pools (Allocatable=True, not being deleted) — sorted first so we prefer to keep existing active pools active. +// - 1: Terminating pools (DeletionTimestamp set) — sorted after active but before disabled pools, so they are +// inserted into the overlap trie before disabled pools are evaluated. This ensures terminating pools continue +// to mask overlapping disabled pools until fully deleted. +// - 2: Disabled pools (Allocatable=False, not being deleted) — sorted after terminating pools. +// - 3: New pools (no Allocatable condition yet) — sorted last so they don't preempt any existing pools. +func poolSortCategory(p *v3.IPPool) int { + if hasCondition(p, v3.IPPoolConditionAllocatable, metav1.ConditionTrue) && p.DeletionTimestamp == nil { + return 0 + } + if p.DeletionTimestamp != nil { + return 1 + } + if hasCondition(p, v3.IPPoolConditionAllocatable, metav1.ConditionFalse) { + return 2 + } + return 3 +} + +// reconcileFinalizer ensures that a finalizer is added to the pool when it is created, and that when the pool is deleted, all associated +// IPAM blocks are released before the finalizer is removed and the pool can be fully deleted. +func (c *IPPoolController) reconcileFinalizer(ctx context.Context, logCtx *logrus.Entry, p *v3.IPPool) error { + var err error + + if p.DeletionTimestamp != nil { + logCtx = logCtx.WithField("deletionTimestamp", p.DeletionTimestamp.String()) + } + + if p.DeletionTimestamp == nil { + if hasCondition(p, v3.IPPoolConditionAllocatable, metav1.ConditionFalse) { + // If this pool is disabled due to CIDR overlaps or other validation issues, we should not add a finalizer to it + // since any IPAM blocks within this CIDR belong to the active pool and we don't want to interfere with the deletion of this + // pool if the user tries to delete it to resolve the overlap. + if hasFinalizer(p) { + logCtx.Info("IPPool is not active, removing finalizer") + p.Finalizers = slices.DeleteFunc(p.Finalizers, func(s string) bool { return s == IPPoolFinalizer }) + if _, err = c.cli.ProjectcalicoV3().IPPools().Update(ctx, p, metav1.UpdateOptions{}); err != nil { + logCtx.WithError(err).Error("Failed to remove finalizer from IPPool") + return err + } + } + return nil + } + + // If the IP pool is not being deleted, add a finalizer to it so we can insert ourselves into the deletion flow. + if !hasFinalizer(p) { + logCtx.Info("Adding finalizer to IPPool") + p.SetFinalizers(append(p.Finalizers, IPPoolFinalizer)) + if _, err = c.cli.ProjectcalicoV3().IPPools().Update(ctx, p, metav1.UpdateOptions{}); err != nil { + logCtx.WithError(err).Error("Failed to add finalizer to IPPool") + return err + } + } + return nil + } + + if !hasFinalizer(p) { + logCtx.Info("IPPool is being deleted, but no finalizer is present, skipping finalization") + return nil + } + + // If the IP pool is being deleted, and we have a finalizer: + // - Release all affinities for this IP pool in order to prevent new allocations. + // - Wait until all IPAM blocks within the pool are released before removing the finalizer. + _, parsedNet, err := cnet.ParseCIDR(p.Spec.CIDR) + if err != nil { + return err + } + logCtx.Info("IPPool is being deleted, releasing affinities") + if err = c.ipam.ReleasePoolAffinities(ctx, *parsedNet); err != nil { + return err + } + + // If there are no IPAM blocks left in this pool, it is safe to remove our finalizer. + if c.blocksInPool(*parsedNet) { + logCtx.Info("IPAM blocks still exist in pool, not removing finalizer") + return nil + } + + logCtx.Info("No IPAM blocks left in pool, removing finalizer") + p.Finalizers = slices.DeleteFunc(p.Finalizers, func(s string) bool { return s == IPPoolFinalizer }) + if _, err := c.cli.ProjectcalicoV3().IPPools().Update(ctx, p, metav1.UpdateOptions{}); err != nil { + logCtx.WithError(err).Error("Failed to remove finalizer from IPPool") + return err + } + return nil +} + +func (c *IPPoolController) blocksInPool(cidr cnet.IPNet) bool { + // Go through all of the IPAM blocks and check if any of them are in this pool. + // TODO: We should be able to optimize this by using better data structures instead of iterating through all blocks. + for _, i := range c.blockInformer.GetIndexer().List() { + block := i.(*v3.IPAMBlock) + _, parsedNet, err := cnet.ParseCIDR(block.Spec.CIDR) + if err != nil { + logrus.WithError(err).WithField("cidr", block.Spec.CIDR).Error("Failed to parse CIDR from IPAMBlock") + continue + } + if cidr.Contains(parsedNet.IP) { + logrus.WithField("cidr", cidr.String()).WithField("block", block.Spec.CIDR).Debug("Found IPAMBlock in pool") + return true + } + } + return false +} + +func hasFinalizer(p *v3.IPPool) bool { + return slices.Contains(p.Finalizers, IPPoolFinalizer) +} + +func hasCondition(p *v3.IPPool, conditionType string, status metav1.ConditionStatus) bool { + if p.Status == nil { + return false + } + for _, c := range p.Status.Conditions { + if c.Type == conditionType && c.Status == status { + return true + } + } + return false +} + +// updateCondition updates the given condition on the IP pool if it has changed, and updates the status of the pool if needed. +func updateCondition(ctx context.Context, cli clientset.Interface, p *v3.IPPool, condition metav1.Condition) error { + if setConditionOnPool(p, condition) { + logrus.WithField("pool", p.Name).Infof("Updating condition %s to %s", condition.Type, condition.Status) + if _, err := cli.ProjectcalicoV3().IPPools().UpdateStatus(ctx, p, metav1.UpdateOptions{}); err != nil { + logrus.WithError(err).WithField("pool", p.Name).Error("Failed to update status of IPPool") + return err + } + } + return nil +} + +// setConditionOnPool sets the given condition on the IP pool, replacing any existing condition of the same type. +// Returns true if the condition was changed and needs to be updated in the API, or false if the condition was already in the desired state. +func setConditionOnPool(p *v3.IPPool, condition metav1.Condition) bool { + if p.Status == nil { + // If there is no status, we need to create one and add the condition to it. + condition.LastTransitionTime = metav1.Now() + p.Status = &v3.IPPoolStatus{ + Conditions: []metav1.Condition{condition}, + } + return true + } + + conditions := p.Status.Conditions + for i, c := range conditions { + if c.Type == condition.Type { + if c.Status == condition.Status && c.Reason == condition.Reason && c.Message == condition.Message { + // No change, return false. + return false + } + + // Update existing condition. + condition.LastTransitionTime = metav1.Now() + p.Status.Conditions[i] = condition + return true + } + } + + // Condition not found, add it. + condition.LastTransitionTime = metav1.Now() + p.Status.Conditions = append(p.Status.Conditions, condition) + return true +} diff --git a/kube-controllers/pkg/controllers/ippool/pool_controller_test.go b/kube-controllers/pkg/controllers/ippool/pool_controller_test.go new file mode 100644 index 00000000000..946a65f008b --- /dev/null +++ b/kube-controllers/pkg/controllers/ippool/pool_controller_test.go @@ -0,0 +1,353 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. +// +// 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 ippool_test + +import ( + "context" + "fmt" + "slices" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/utils/ptr" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/projectcalico/calico/felix/fv/containers" + "github.com/projectcalico/calico/kube-controllers/pkg/controllers/ippool" + "github.com/projectcalico/calico/kube-controllers/tests/testutils" + "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" + "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s" + "github.com/projectcalico/calico/libcalico-go/lib/ipam" +) + +var _ = Describe("IP pool lifecycle FV", func() { + var ( + etcd *containers.Container + kubectrl *containers.Container + apiserver *containers.Container + k8sClient *kubernetes.Clientset + cli ctrlclient.Client + controllerManager *containers.Container + err error + pool *v3.IPPool + ipamcli ipam.Interface + ) + + BeforeEach(func() { + // Run etcd. + etcd = testutils.RunEtcd() + + // Determine if we should use v3 CRDs based on the test config. + var cfg *apiconfig.CalicoAPIConfig + cfg, err = apiconfig.LoadClientConfigFromEnvironment() + Expect(err).NotTo(HaveOccurred()) + useV3CRDs := k8s.UsingV3CRDs(&cfg.Spec) + if !useV3CRDs { + Skip("IP pool controller only runs against v3 CRDs") + } + + // Run apiserver. + apiserver = testutils.RunK8sApiserver(etcd.IP) + kubeconfig, cleanup := testutils.BuildKubeconfig(apiserver.IP) + defer cleanup() + + // Create clients for the test. + ipamcli = testutils.GetCalicoClient(apiconfig.Kubernetes, "", kubeconfig).IPAM() + k8sClient, err = testutils.GetK8sClient(kubeconfig) + Expect(err).NotTo(HaveOccurred()) + + // Register Calico CRD types with the scheme. + Expect(v3.AddToGlobalScheme()).NotTo(HaveOccurred()) + + // Create a client for interacting with CRDs directly. + config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) + Expect(err).NotTo(HaveOccurred()) + cli, err = ctrlclient.New(config, ctrlclient.Options{}) + Expect(err).NotTo(HaveOccurred()) + + // Wait for the apiserver to be available. + Eventually(func() error { + _, err := k8sClient.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{}) + return err + }, 30*time.Second, 1*time.Second).Should(BeNil()) + + // Apply the necessary CRDs if we're running in k8s mode. + testutils.ApplyCRDs(apiserver) + + // Run kube-controllers. + mode := apiconfig.Kubernetes + kubectrl = testutils.RunKubeControllers(mode, etcd.IP, kubeconfig, "") + + // Create default pool for tests. + pool = &v3.IPPool{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pool", + }, + Spec: v3.IPPoolSpec{ + CIDR: "192.168.1.0/24", + }, + } + + By("creating a node for the test", func() { + _, err = k8sClient.CoreV1().Nodes().Create(context.Background(), + &v1.Node{ + TypeMeta: metav1.TypeMeta{Kind: "Node", APIVersion: "v1"}, + ObjectMeta: metav1.ObjectMeta{Name: "test-node"}, + Spec: v1.NodeSpec{}, + }, + metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + AfterEach(func() { + controllerManager.Stop() + kubectrl.Stop() + apiserver.Stop() + etcd.Stop() + }) + + It("should create and delete an IP pool", func() { + // Create an IP pool. + err = cli.Create(context.Background(), pool) + Expect(err).NotTo(HaveOccurred()) + + expectFinalizerPresent(cli, pool.Name) + + // Delete the IP pool. + err = cli.Delete(context.Background(), pool) + Expect(err).NotTo(HaveOccurred()) + + // Expect the IP pool to be removed from the API server. + expectPoolDeleted(cli, pool.Name) + }) + + It("should not remove a pool that is in use", func() { + // Create an IP pool. + err = cli.Create(context.Background(), pool) + Expect(err).NotTo(HaveOccurred()) + expectFinalizerPresent(cli, pool.Name) + + // The pool should be allocatable. + expectPoolAllocatable(cli, pool.Name) + + // Assign an IP address from the pool, putting it "in use". + _, _, err = ipamcli.AutoAssign(context.Background(), ipam.AutoAssignArgs{ + Num4: 1, + HandleID: ptr.To("test-handle"), + IntendedUse: v3.IPPoolAllowedUseWorkload, + Hostname: "test-node", + }) + Expect(err).NotTo(HaveOccurred()) + + // Delete the IP pool. + err = cli.Delete(context.Background(), pool) + Expect(err).NotTo(HaveOccurred()) + + // Expect the IP pool to still exist in the API server. + p := &v3.IPPool{} + EventuallyWithOffset(1, func() error { + return cli.Get(context.Background(), ctrlclient.ObjectKey{Name: pool.Name}, p) + }, 10*time.Second, 1*time.Second).ShouldNot(HaveOccurred(), "IP pool should still exist") + + // Expect a status condition indicating the pool is terminating. + expectPoolNotAllocatable(cli, pool.Name) + + // Release the assigned IP address. + Expect(ipamcli.ReleaseByHandle(context.Background(), "test-handle")).NotTo(HaveOccurred()) + + // Expect the IP pool to be removed from the API server. + expectPoolDeleted(cli, pool.Name) + }) + + It("should mark overlapping IP pools with a status condition", func() { + // Create the first IP pool. + err = cli.Create(context.Background(), pool) + Expect(err).NotTo(HaveOccurred()) + expectFinalizerPresent(cli, pool.Name) + + // Create a second IP pool that overlaps with the first. + overlappingPool := &v3.IPPool{ + ObjectMeta: metav1.ObjectMeta{ + Name: "overlapping-pool", + }, + Spec: v3.IPPoolSpec{ + CIDR: "192.168.0.0/16", + }, + } + err = cli.Create(context.Background(), overlappingPool) + Expect(err).NotTo(HaveOccurred()) + + // Expect the second pool to have a status condition indicating it overlaps with another pool. + expectPoolNotAllocatable(cli, overlappingPool.Name) + + // Create a third pool that also overlaps with both the first and second pools. + anotherOverlappingPool := &v3.IPPool{ + ObjectMeta: metav1.ObjectMeta{ + Name: "another-overlapping-pool", + }, + Spec: v3.IPPoolSpec{ + CIDR: "192.168.0.0/16", + }, + } + err = cli.Create(context.Background(), anotherOverlappingPool) + Expect(err).NotTo(HaveOccurred()) + + // Create a fourth pool that does NOT overlap with the first pool, but does + // overlap with the second pool. We expect this pool to be allowed. + nonOverlappingPool := &v3.IPPool{ + ObjectMeta: metav1.ObjectMeta{ + Name: "non-overlapping-pool", + }, + Spec: v3.IPPoolSpec{ + CIDR: "192.168.2.0/24", + }, + } + err = cli.Create(context.Background(), nonOverlappingPool) + Expect(err).NotTo(HaveOccurred()) + + // Expect the third pool to also have a status condition indicating it overlaps with another pool. + expectPoolNotAllocatable(cli, anotherOverlappingPool.Name) + + // Expect the non-overlapping pool to be allocatable. + expectFinalizerPresent(cli, nonOverlappingPool.Name) + expectPoolAllocatable(cli, nonOverlappingPool.Name) + + // Delete the first pool. + err = cli.Delete(context.Background(), pool) + Expect(err).NotTo(HaveOccurred()) + + // The second pool should still be masked by the non-overlapping pool, even though it was created before the non-overlapping pool, + // because we prioritize pools that are already allocatable. + expectPoolNotAllocatable(cli, overlappingPool.Name) + expectPoolNotAllocatable(cli, anotherOverlappingPool.Name) + + // Delete the non-overlapping pool, which should allow the second pool to become allocatable since it was created before the third pool. + err = cli.Delete(context.Background(), nonOverlappingPool) + Expect(err).NotTo(HaveOccurred()) + + // Expect the second pool to have its status updated to be enabled, but the + // third pool should still be disabled because it overlaps with the second pool. + expectPoolAllocatable(cli, overlappingPool.Name) + expectPoolNotAllocatable(cli, anotherOverlappingPool.Name) + + // Expect a finalizer to be added to the second pool to prevent deletion while it's allocatable, + // but the third pool should not have a finalizer since it's still disabled. + expectFinalizerPresent(cli, overlappingPool.Name) + expectFinalizerMissing(cli, anotherOverlappingPool.Name) + + // Delete the second pool, which should allow the third pool to become allocatable. + err = cli.Delete(context.Background(), overlappingPool) + Expect(err).NotTo(HaveOccurred()) + + // Expect the third pool to have its status updated to be enabled. + expectPoolAllocatable(cli, anotherOverlappingPool.Name) + }) +}) + +func expectPoolAllocatable(cli ctrlclient.Client, poolName string) { + pool := &v3.IPPool{} + EventuallyWithOffset(1, func() error { + err := cli.Get(context.Background(), ctrlclient.ObjectKey{Name: poolName}, pool) + if err != nil { + return err + } + if pool.Status == nil { + return fmt.Errorf("%s status is nil", poolName) + } + for _, condition := range pool.Status.Conditions { + if condition.Type == v3.IPPoolConditionAllocatable && condition.Status == metav1.ConditionTrue { + return nil + } else if condition.Type == v3.IPPoolConditionAllocatable && condition.Status == metav1.ConditionFalse { + return fmt.Errorf("pool %s is not allocatable", poolName) + } + } + return fmt.Errorf("condition not found on pool %s", poolName) + }, 10*time.Second, 1*time.Second).ShouldNot(HaveOccurred(), "IP pool should be allocatable") +} + +func expectPoolNotAllocatable(cli ctrlclient.Client, poolName string) { + pool := &v3.IPPool{} + EventuallyWithOffset(1, func() error { + err := cli.Get(context.Background(), ctrlclient.ObjectKey{Name: poolName}, pool) + if err != nil { + return err + } + if pool.Status == nil { + return fmt.Errorf("%s status is nil", poolName) + } + for _, condition := range pool.Status.Conditions { + if condition.Type == v3.IPPoolConditionAllocatable && condition.Status == metav1.ConditionFalse { + return nil + } else if condition.Type == v3.IPPoolConditionAllocatable && condition.Status == metav1.ConditionTrue { + return fmt.Errorf("pool %s is allocatable", poolName) + } + } + return fmt.Errorf("condition not found on pool %s", poolName) + }, 10*time.Second, 1*time.Second).ShouldNot(HaveOccurred(), "IP pool should be not allocatable") +} + +func expectPoolDeleted(cli ctrlclient.Client, poolName string) { + pool := &v3.IPPool{} + EventuallyWithOffset(1, func() error { + err := cli.Get(context.Background(), ctrlclient.ObjectKey{Name: poolName}, pool) + if errors.IsNotFound(err) { + // Pool has been deleted. + return nil + } else if err != nil { + // Some other error occurred. + return err + } + return fmt.Errorf("pool %s still exists", poolName) + }, 10*time.Second, 1*time.Second).ShouldNot(HaveOccurred(), "IP pool should be deleted") +} + +func expectFinalizerPresent(cli ctrlclient.Client, poolName string) { + pool := &v3.IPPool{} + EventuallyWithOffset(1, func() error { + err := cli.Get(context.Background(), ctrlclient.ObjectKey{Name: poolName}, pool) + if err != nil { + return err + } + if slices.Contains(pool.Finalizers, ippool.IPPoolFinalizer) { + return nil + } + return fmt.Errorf("finalizer not found") + }, 10*time.Second, 1*time.Second).ShouldNot(HaveOccurred(), "finalizer should be added to IP pool") +} + +func expectFinalizerMissing(cli ctrlclient.Client, poolName string) { + pool := &v3.IPPool{} + EventuallyWithOffset(1, func() error { + err := cli.Get(context.Background(), ctrlclient.ObjectKey{Name: poolName}, pool) + if errors.IsNotFound(err) { + // Pool has been deleted, so finalizer is effectively removed. + return nil + } else if err != nil { + // Some other error occurred. + return err + } + if !slices.Contains(pool.Finalizers, ippool.IPPoolFinalizer) { + return nil + } + return fmt.Errorf("finalizer still present") + }, 10*time.Second, 1*time.Second).ShouldNot(HaveOccurred(), "finalizer should be removed from IP pool") +} diff --git a/kube-controllers/pkg/controllers/ippool/pool_controller_unit_test.go b/kube-controllers/pkg/controllers/ippool/pool_controller_unit_test.go new file mode 100644 index 00000000000..e38fa4da40c --- /dev/null +++ b/kube-controllers/pkg/controllers/ippool/pool_controller_unit_test.go @@ -0,0 +1,170 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. +// +// 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 ippool + +import ( + "slices" + "testing" + "time" + + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestPoolSortFunc(t *testing.T) { + now := time.Unix(0, 0) + + makePool := func(name string, createdAt time.Time, condition *metav1.Condition, deleting bool) *v3.IPPool { + p := &v3.IPPool{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + CreationTimestamp: metav1.NewTime(createdAt), + }, + } + if deleting { + ts := metav1.NewTime(now) + p.DeletionTimestamp = &ts + } + if condition != nil { + p.Status = &v3.IPPoolStatus{ + Conditions: []metav1.Condition{*condition}, + } + } + return p + } + + allocatable := &metav1.Condition{ + Type: v3.IPPoolConditionAllocatable, + Status: metav1.ConditionTrue, + Reason: v3.IPPoolReasonOK, + } + disabled := &metav1.Condition{ + Type: v3.IPPoolConditionAllocatable, + Status: metav1.ConditionFalse, + Reason: v3.IPPoolReasonCIDROverlap, + } + + tests := []struct { + name string + pools []*v3.IPPool + expected []string // expected order of pool names after sorting + }{ + { + name: "active pools sort before disabled pools", + pools: []*v3.IPPool{ + makePool("disabled-pool", now.Add(-1*time.Minute), disabled, false), + makePool("active-pool", now, allocatable, false), + }, + expected: []string{"active-pool", "disabled-pool"}, + }, + { + name: "active pools sort before terminating pools", + pools: []*v3.IPPool{ + makePool("terminating-pool", now.Add(-1*time.Minute), allocatable, true), + makePool("active-pool", now, allocatable, false), + }, + expected: []string{"active-pool", "terminating-pool"}, + }, + { + name: "terminating pools sort before disabled pools", + pools: []*v3.IPPool{ + makePool("disabled-pool", now.Add(-2*time.Minute), disabled, false), + makePool("terminating-pool", now.Add(-1*time.Minute), allocatable, true), + }, + expected: []string{"terminating-pool", "disabled-pool"}, + }, + { + name: "terminating pool with Allocatable=False still sorts before disabled pools", + pools: []*v3.IPPool{ + makePool("disabled-pool", now.Add(-2*time.Minute), disabled, false), + makePool("terminating-pool", now.Add(-1*time.Minute), disabled, true), + }, + expected: []string{"terminating-pool", "disabled-pool"}, + }, + { + name: "within same category, older pools sort first", + pools: []*v3.IPPool{ + makePool("newer-pool", now, allocatable, false), + makePool("older-pool", now.Add(-1*time.Minute), allocatable, false), + }, + expected: []string{"older-pool", "newer-pool"}, + }, + { + name: "same category and timestamp sorts by name", + pools: []*v3.IPPool{ + makePool("pool-b", now, allocatable, false), + makePool("pool-a", now, allocatable, false), + }, + expected: []string{"pool-a", "pool-b"}, + }, + { + name: "new pools with no condition sort after active pools", + pools: []*v3.IPPool{ + makePool("new-pool", now.Add(-1*time.Minute), nil, false), + makePool("active-pool", now, allocatable, false), + }, + expected: []string{"active-pool", "new-pool"}, + }, + { + name: "new pools with no condition sort after terminating pools", + pools: []*v3.IPPool{ + makePool("new-pool", now.Add(-1*time.Minute), nil, false), + makePool("terminating-pool", now, allocatable, true), + }, + expected: []string{"terminating-pool", "new-pool"}, + }, + { + name: "new pools with no condition sort after disabled pools", + pools: []*v3.IPPool{ + makePool("new-pool", now.Add(-1*time.Minute), nil, false), + makePool("disabled-pool", now, disabled, false), + }, + expected: []string{"disabled-pool", "new-pool"}, + }, + { + name: "full scenario: active, terminating, disabled, and new pools", + pools: []*v3.IPPool{ + makePool("disabled-2", now.Add(-1*time.Minute), disabled, false), + makePool("active-pool", now.Add(-5*time.Minute), allocatable, false), + makePool("new-pool", now, nil, false), + makePool("disabled-1", now.Add(-2*time.Minute), disabled, false), + makePool("terminating-pool", now.Add(-3*time.Minute), allocatable, true), + }, + expected: []string{"active-pool", "terminating-pool", "disabled-1", "disabled-2", "new-pool"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + slices.SortFunc(tt.pools, func(a, b *v3.IPPool) int { + return poolSortFunc(a, b) + }) + + got := make([]string, len(tt.pools)) + for i, p := range tt.pools { + got[i] = p.Name + } + + if len(got) != len(tt.expected) { + t.Fatalf("expected %v, got %v", tt.expected, got) + } + for i := range got { + if got[i] != tt.expected[i] { + t.Fatalf("expected %v, got %v", tt.expected, got) + } + } + }) + } +} diff --git a/kube-controllers/pkg/controllers/loadbalancer/loadbalancer_controller.go b/kube-controllers/pkg/controllers/loadbalancer/loadbalancer_controller.go index 36f46acb681..b81bbf5f605 100644 --- a/kube-controllers/pkg/controllers/loadbalancer/loadbalancer_controller.go +++ b/kube-controllers/pkg/controllers/loadbalancer/loadbalancer_controller.go @@ -115,9 +115,9 @@ type loadBalancerController struct { dataFeed *utils.DataFeed cfg config.LoadBalancerControllerConfig clientSet kubernetes.Interface - syncerUpdates chan interface{} + syncerUpdates chan any syncStatus bapi.SyncStatus - syncChan chan interface{} + syncChan chan any serviceUpdates chan serviceKey ipPools map[string]api.IPPool serviceInformer cache.SharedIndexInformer @@ -135,8 +135,8 @@ func NewLoadBalancerController(clientset kubernetes.Interface, calicoClient clie cfg: cfg, clientSet: clientset, dataFeed: dataFeed, - syncerUpdates: make(chan interface{}, utils.BatchUpdateSize), - syncChan: make(chan interface{}, 1), + syncerUpdates: make(chan any, utils.BatchUpdateSize), + syncChan: make(chan any, 1), serviceUpdates: make(chan serviceKey, utils.BatchUpdateSize), ipPools: make(map[string]api.IPPool), serviceInformer: serviceInformer, @@ -181,7 +181,7 @@ func (c *loadBalancerController) Run(stopCh chan struct{}) { log.Info("Stopping Service controller") } -func (c *loadBalancerController) onServiceAdd(objNew interface{}) { +func (c *loadBalancerController) onServiceAdd(objNew any) { if svc, ok := objNew.(*v1.Service); ok { svcKey, err := serviceKeyFromService(svc) if err != nil { @@ -191,7 +191,7 @@ func (c *loadBalancerController) onServiceAdd(objNew interface{}) { } } -func (c *loadBalancerController) onServiceUpdate(objNew interface{}, objOld interface{}) { +func (c *loadBalancerController) onServiceUpdate(objNew any, objOld any) { if svc, ok := objNew.(*v1.Service); ok { svcKey, err := serviceKeyFromService(svc) if err != nil { @@ -201,7 +201,7 @@ func (c *loadBalancerController) onServiceUpdate(objNew interface{}, objOld inte } } -func (c *loadBalancerController) onServiceDelete(objNew interface{}) { +func (c *loadBalancerController) onServiceDelete(objNew any) { if svc, ok := objNew.(*v1.Service); ok { svcKey, err := serviceKeyFromService(svc) if err != nil { @@ -262,7 +262,7 @@ func (c *loadBalancerController) acceptScheduledRequests(stopCh <-chan struct{}) } } -func (c *loadBalancerController) handleUpdate(update interface{}) { +func (c *loadBalancerController) handleUpdate(update any) { switch update := update.(type) { case bapi.SyncStatus: c.syncStatus = update @@ -290,17 +290,22 @@ func (c *loadBalancerController) handleUpdate(update interface{}) { } func (c *loadBalancerController) handleBlockUpdate(kvp model.KVPair) { - if kvp.Value == nil { + block, ok := kvp.Value.(*model.AllocationBlock) + if !ok { + log.WithField("key", kvp.Key.String()).Errorf("unexpected type for AllocationBlock value: %T", kvp.Value) + c.allocationTracker.deleteBlock(kvp.Key.String()) + return + } + if block == nil { c.allocationTracker.deleteBlock(kvp.Key.String()) return } - affinity := kvp.Value.(*model.AllocationBlock).Affinity - block := kvp.Value.(*model.AllocationBlock) + affinity := block.Affinity key := kvp.Key.String() - if affinity != nil && *affinity != fmt.Sprintf("%s:%s", ipam.AffinityTypeVirtual, api.VirtualLoadBalancer) { - c.allocationTracker.deleteBlock(kvp.Key.String()) + if affinity == nil || *affinity != fmt.Sprintf("%s:%s", ipam.AffinityTypeVirtual, api.VirtualLoadBalancer) { + c.allocationTracker.deleteBlock(key) return } @@ -308,21 +313,21 @@ func (c *loadBalancerController) handleBlockUpdate(kvp model.KVPair) { for i := range block.Allocations { if block.Allocations[i] != nil { - if _, ok := block.Attributes[*block.Allocations[i]].AttrSecondary[ipam.AttributeNamespace]; !ok { - log.Warnf("no %s attribute found for block with handle %s", ipam.AttributeNamespace, *block.Attributes[*block.Allocations[i]].AttrPrimary) + if _, ok := block.Attributes[*block.Allocations[i]].ActiveOwnerAttrs[ipam.AttributeNamespace]; !ok { + log.Warnf("no %s attribute found for block with handle %s", ipam.AttributeNamespace, *block.Attributes[*block.Allocations[i]].HandleID) continue } - if _, ok := block.Attributes[*block.Allocations[i]].AttrSecondary[ipam.AttributeService]; !ok { - log.Warnf("no %s attribute found for block with handle %s", ipam.AttributeService, *block.Attributes[*block.Allocations[i]].AttrPrimary) + if _, ok := block.Attributes[*block.Allocations[i]].ActiveOwnerAttrs[ipam.AttributeService]; !ok { + log.Warnf("no %s attribute found for block with handle %s", ipam.AttributeService, *block.Attributes[*block.Allocations[i]].HandleID) continue } ip := block.OrdinalToIP(i) svcKey := serviceKey{ - handle: *block.Attributes[*block.Allocations[i]].AttrPrimary, - namespace: block.Attributes[*block.Allocations[i]].AttrSecondary[ipam.AttributeNamespace], - name: block.Attributes[*block.Allocations[i]].AttrSecondary[ipam.AttributeService], + handle: *block.Attributes[*block.Allocations[i]].HandleID, + namespace: block.Attributes[*block.Allocations[i]].ActiveOwnerAttrs[ipam.AttributeNamespace], + name: block.Attributes[*block.Allocations[i]].ActiveOwnerAttrs[ipam.AttributeService], } c.allocationTracker.assignAddressToBlock(key, ip.String(), svcKey) @@ -344,6 +349,13 @@ func (c *loadBalancerController) handleIPPoolUpdate(kvp model.KVPair) { } pool := kvp.Value.(*api.IPPool) + + if pool.DeletionTimestamp != nil { + // Pool is being deleted, remove it from our map. + delete(c.ipPools, kvp.Key.String()) + return + } + if slices.Contains(pool.Spec.AllowedUses, api.IPPoolAllowedUseLoadBalancer) { c.ipPools[kvp.Key.String()] = *pool } else { @@ -408,7 +420,7 @@ func (c *loadBalancerController) syncService(svcKey serviceKey) { if len(c.ipPools) == 0 { if _, ok := c.allocationTracker.ipsByService[svcKey]; ok { // Last LoadBalancer IPPool was deleted, and we have previously assigned IPs to this service. We need to release the IPs now and update the service status - log.Debugf("No ippools with allowedUse LoadBalancer found. Releasing previously assigned IPs for Service %s/%s", svcKey.namespace, svcKey.name) + log.Warnf("No ippools with allowedUse LoadBalancer found. Releasing previously assigned IPs for Service %s/%s", svcKey.namespace, svcKey.name) err := c.releaseIPsByHandle(svcKey) if err != nil { log.WithError(err).Errorf("Error releasing previously assigned IPs for Service %s/%s", svcKey.namespace, svcKey.name) @@ -431,7 +443,18 @@ func (c *loadBalancerController) syncService(svcKey serviceKey) { } } else { // We can skip service sync if there are no ippools defined that can be used for Service LoadBalancer - log.Debugf("No ippools with allowedUse LoadBalancer found. Skipping IP assignment for Service %s/%s", svcKey.namespace, svcKey.name) + svc, err := c.serviceLister.Services(svcKey.namespace).Get(svcKey.name) + if apierrors.IsNotFound(err) { + return + } + if err != nil { + log.WithError(err).Errorf("Error getting service %s/%s", svcKey.namespace, svcKey.name) + return + } + if IsCalicoManagedLoadBalancer(svc, c.cfg.AssignIPs) { + // Only warn the user if the service is managed by Calico and should have IP assigned + log.Warnf("No ippools with allowedUse LoadBalancer found. Skipping IP assignment for Service %s/%s", svcKey.namespace, svcKey.name) + } } return } @@ -491,6 +514,7 @@ func (c *loadBalancerController) syncService(svcKey serviceKey) { } for ip := range c.allocationTracker.ipsByService[svcKey] { if _, ok := lbIPs[ip]; !ok { + log.Infof("Removing IP assignment (%s) for Service %s/%s; no longer in annotations.", ip, svc.Namespace, svc.Name) err = c.releaseIP(svcKey, ip) if err != nil { log.WithError(err).Errorf("Failed to release IP for %s/%s", svc.Namespace, svc.Name) @@ -502,6 +526,7 @@ func (c *loadBalancerController) syncService(svcKey serviceKey) { // If pool annotations are specified, we need to check that the IPs assigned are from the specified pools for ip := range c.allocationTracker.ipsByService[svcKey] { if !poolContains(ip, ipv4pools) && !poolContains(ip, ipv6pools) { + log.Infof("Removing IP assignment (%s) for Service %s/%s: not from specified pools (%v, %v).", ip, svc.Namespace, svc.Name, ipv4pools, ipv6pools) err = c.releaseIP(svcKey, ip) if err != nil { log.WithError(err).Errorf("Failed to release IP for %s/%s", svc.Namespace, svc.Name) @@ -520,7 +545,10 @@ func (c *loadBalancerController) syncService(svcKey serviceKey) { if pool != nil { // We want to release the address if annotation changed and we are no longer requesting IP from manual pool, // if pool is nil we can skip this and the address will be removed during the next IPAM sync - if *pool.Spec.AssignmentMode == api.Manual { + // + // AssignmentMode should never be nil due to defaulting, but we check it just in case. + if pool.Spec.AssignmentMode != nil && *pool.Spec.AssignmentMode == api.Manual { + log.Infof("Removing IP assignment (%s) for Service %s/%s. No annotations but IP is from a 'Manual' IP pool.", ip, svc.Namespace, svc.Name) err = c.releaseIP(svcKey, ip) if err != nil { log.WithError(err).Errorf("Failed to release IP for %s/%s", svc.Namespace, svc.Name) @@ -532,9 +560,12 @@ func (c *loadBalancerController) syncService(svcKey serviceKey) { } if c.needsIPsAssigned(svc, svcKey) { - _, err = c.assignIP(svc) + log.Infof("Service requires an IP assignment %s/%s", svc.Namespace, svc.Name) + ips, err := c.assignIP(svc) if err != nil { log.WithError(err).Errorf("Failed to assign IP for %s/%s", svc.Namespace, svc.Name) + } else { + log.Infof("Assigned IPs %s for Service %s/%s", ips, svc.Namespace, svc.Name) } } @@ -640,6 +671,7 @@ func (c *loadBalancerController) assignIP(svc *v1.Service) ([]string, error) { if loadBalancerIPs != nil { // User requested specific IP, attempt to allocate + log.Infof("Trying to assign requested IPs %v to Service %s/%s", loadBalancerIPs, svc.Namespace, svc.Name) for _, addr := range loadBalancerIPs { if _, exists := c.allocationTracker.ipsByService[*svcKey][addr.String()]; exists { // We must be trying to assign missing address due to an error, @@ -678,15 +710,18 @@ func (c *loadBalancerController) assignIP(svc *v1.Service) ([]string, error) { num6++ } } + log.Infof("Service %s/%s requires %v IPv4 and %v IPv6 addresses.", svc.Namespace, svc.Name, num4, num6) // Check if IP from ipFamily is already assigned, skip it as we're trying to assign only the missing one. // This can happen when error happened during the initial assignment, and now we're trying to assign ip again from the syncIPAM func for ingress := range c.allocationTracker.ipsByService[*svcKey] { if ip := cnet.ParseIP(ingress); ip != nil { if ip.To4() != nil { + log.Infof("Service already has an IPv4 address: %s", ip.String()) num4 = 0 } if ip.To16() != nil { + log.Infof("Service already has an IPv6 address: %s", ip.String()) num6 = 0 } } @@ -730,6 +765,7 @@ func (c *loadBalancerController) assignIP(svc *v1.Service) ([]string, error) { if v4Assignments != nil { for _, assignment := range v4Assignments.IPs { + log.Infof("Service %s/%s now has IP: %s", svcKey.namespace, svcKey.name, assignment.IP.String()) assignedIPs = append(assignedIPs, assignment.IP.String()) c.allocationTracker.assignAddressToService(*svcKey, assignment.IP.String()) } @@ -737,6 +773,7 @@ func (c *loadBalancerController) assignIP(svc *v1.Service) ([]string, error) { if v6assignments != nil { for _, assignment := range v6assignments.IPs { + log.Infof("Service %s/%s now has IP: %s", svcKey.namespace, svcKey.name, assignment.IP.String()) assignedIPs = append(assignedIPs, assignment.IP.String()) c.allocationTracker.assignAddressToService(*svcKey, assignment.IP.String()) } @@ -955,6 +992,11 @@ func poolContains(ipAddr string, cidrs []cnet.IPNet) bool { return false } ip := net.ParseIP(ipAddr) + if ip == nil { + // Invalid IP address, cannot be in any pool + log.Warnf("Invalid IP address encountered in IPAM allocation tracker: %q (treating as not in any pool)", ipAddr) + return false + } for _, cidr := range cidrs { if cidr.Contains(ip) { return true @@ -978,7 +1020,7 @@ func (c *loadBalancerController) poolForIP(ipAddr string) (*api.IPPool, error) { return nil, nil } -func kick(c chan<- interface{}) { +func kick(c chan<- any) { select { case c <- nil: // pass diff --git a/kube-controllers/pkg/controllers/loadbalancer/loadbalancer_controller_fv_test.go b/kube-controllers/pkg/controllers/loadbalancer/loadbalancer_controller_fv_test.go index 16ff1efa4f3..13a8369535c 100644 --- a/kube-controllers/pkg/controllers/loadbalancer/loadbalancer_controller_fv_test.go +++ b/kube-controllers/pkg/controllers/loadbalancer/loadbalancer_controller_fv_test.go @@ -17,10 +17,9 @@ package loadbalancer import ( "context" "fmt" - "os" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" v1 "k8s.io/api/core/v1" @@ -160,19 +159,13 @@ var _ = Describe("Calico loadbalancer controller FV tests (etcd mode)", func() { apiserver = testutils.RunK8sApiserver(etcd.IP) // Write out a kubeconfig file - kconfigfile, err := os.CreateTemp("", "ginkgo-loadbalancercontroller") - Expect(err).NotTo(HaveOccurred()) - defer func() { _ = os.Remove(kconfigfile.Name()) }() - data := testutils.BuildKubeconfig(apiserver.IP) - _, err = kconfigfile.Write([]byte(data)) - Expect(err).NotTo(HaveOccurred()) + kconfigfile, cancel := testutils.BuildKubeconfig(apiserver.IP) + defer cancel() - // Make the kubeconfig readable by the container. - Expect(kconfigfile.Chmod(os.ModePerm)).NotTo(HaveOccurred()) + loadbalancercontroller = testutils.RunLoadBalancerController(apiconfig.EtcdV3, etcd.IP, kconfigfile, "") - loadbalancercontroller = testutils.RunLoadBalancerController(apiconfig.EtcdV3, etcd.IP, kconfigfile.Name(), "") - - k8sClient, err = testutils.GetK8sClient(kconfigfile.Name()) + var err error + k8sClient, err = testutils.GetK8sClient(kconfigfile) Expect(err).NotTo(HaveOccurred()) // Wait for the apiserver to be available. @@ -304,8 +297,8 @@ var _ = Describe("Calico loadbalancer controller FV tests (etcd mode)", func() { Expect(serviceSpecific.Status.LoadBalancer.Ingress[0].IP).Should(Equal(v4poolManualSpecifcIP)) // Update the service type to NodePort, LoadBalancer controller should release the IP - svcPatch := map[string]interface{}{} - spec := map[string]interface{}{} + svcPatch := map[string]any{} + spec := map[string]any{} svcPatch["spec"] = spec spec["type"] = v1.ServiceTypeNodePort patch, err := json.Marshal(svcPatch) @@ -477,8 +470,8 @@ var _ = Describe("Calico loadbalancer controller FV tests (etcd mode)", func() { service, err = k8sClient.CoreV1().Services(testNamespace).Get(context.Background(), serviceIpv4PoolSpecified.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) + Expect(service.Status.LoadBalancer.Ingress).NotTo(BeEmpty(), "saw service.Status.LoadBalancer.Ingress non-empty and then empty again!") Expect(service.Status.LoadBalancer.Ingress[0].IP).Should(Equal(specificIpFromAutomaticPool)) - }) }) }) diff --git a/kube-controllers/pkg/controllers/loadbalancer/loadbalancer_controller_ut_test.go b/kube-controllers/pkg/controllers/loadbalancer/loadbalancer_controller_ut_test.go index 0cf130d54cd..df7f7e5afcf 100644 --- a/kube-controllers/pkg/controllers/loadbalancer/loadbalancer_controller_ut_test.go +++ b/kube-controllers/pkg/controllers/loadbalancer/loadbalancer_controller_ut_test.go @@ -19,7 +19,7 @@ import ( "fmt" "net" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" v1 "k8s.io/api/core/v1" @@ -223,8 +223,8 @@ var _ = Describe("LoadBalancer controller UTs", func() { Unallocated: []int{1, 2, 3}, Attributes: []model.AllocationAttribute{ { - AttrPrimary: &svcKey.handle, - AttrSecondary: map[string]string{ + HandleID: &svcKey.handle, + ActiveOwnerAttrs: map[string]string{ ipam.AttributeService: svc.Name, ipam.AttributeType: string(svc.Spec.Type), ipam.AttributeNamespace: svc.Namespace, @@ -253,16 +253,16 @@ var _ = Describe("LoadBalancer controller UTs", func() { block.Unallocated = []int{2, 3} block.Attributes = []model.AllocationAttribute{ { - AttrPrimary: &svcKey.handle, - AttrSecondary: map[string]string{ + HandleID: &svcKey.handle, + ActiveOwnerAttrs: map[string]string{ ipam.AttributeService: svc.Name, ipam.AttributeType: string(svc.Spec.Type), ipam.AttributeNamespace: svc.Namespace, }, }, { - AttrPrimary: &svcKey.handle, - AttrSecondary: map[string]string{ + HandleID: &svcKey.handle, + ActiveOwnerAttrs: map[string]string{ ipam.AttributeService: svc.Name, ipam.AttributeType: string(svc.Spec.Type), ipam.AttributeNamespace: svc.Namespace, @@ -291,6 +291,27 @@ var _ = Describe("LoadBalancer controller UTs", func() { Expect(c.allocationTracker.servicesByIP).To(BeEmpty()) }) + It("should not panic on handleBlockUpdate with nil *AllocationBlock", func() { + cidr := cnet.MustParseCIDR("10.0.0.4/30") + key := model.BlockKey{CIDR: cidr} + + // A nil *AllocationBlock wrapped in a non-nil interface passes the + // "kvp.Value == nil" check but causes a nil-pointer dereference on + // field access. + var nilBlock *model.AllocationBlock + kvp := model.KVPair{ + Key: key, + Value: nilBlock, // non-nil interface, nil underlying pointer + } + + Expect(func() { + c.handleBlockUpdate(kvp) + }).ToNot(Panic()) + + // The block should have been cleaned up from the tracker. + Expect(c.allocationTracker.ipsByBlock).To(BeEmpty()) + }) + It("should parse calico annotations", func() { ipv4poolName := "ipv4pool" ipv6poolName := "ipv6pool" @@ -517,4 +538,40 @@ var _ = Describe("LoadBalancer controller UTs", func() { Expect(err).NotTo(HaveOccurred()) Expect(cli.IPAMUpgradeCallCount()).To(Equal(2)) }) + + It("should handle invalid IP addresses in allocation tracker without panicking", func() { + svcKey, err := serviceKeyFromService(&svc) + Expect(err).ToNot(HaveOccurred()) + + // Add an invalid IP address to the allocation tracker + c.allocationTracker.assignAddressToService(*svcKey, "invalid-ip-address") + + // Create a service with pool annotations to trigger the problematic code path + svc.Annotations = map[string]string{ + annotationIPv4Pools: "[\"10.0.0.0/24\"]", + } + + // Add a valid IP pool to the controller + ipv4Pool := apiv3.IPPool{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pool", + }, + Spec: apiv3.IPPoolSpec{ + CIDR: "10.0.0.0/24", + AllowedUses: []apiv3.IPPoolAllowedUse{apiv3.IPPoolAllowedUseLoadBalancer}, + }, + } + c.ipPools["test-pool"] = ipv4Pool + + // Create the service in the fake client + _, err = c.clientSet.CoreV1().Services(svc.Namespace).Create(context.Background(), &svc, metav1.CreateOptions{}) + Expect(err).ToNot(HaveOccurred()) + + // This should not panic even with invalid IP in allocation tracker + // The syncService method should handle invalid IPs gracefully + Expect(func() { + c.syncService(*svcKey) + }).ToNot(Panic()) + }) }) diff --git a/kube-controllers/pkg/controllers/loadbalancer/loadbalancer_suite_test.go b/kube-controllers/pkg/controllers/loadbalancer/loadbalancer_suite_test.go index a2f3169a251..5a8a122aa13 100644 --- a/kube-controllers/pkg/controllers/loadbalancer/loadbalancer_suite_test.go +++ b/kube-controllers/pkg/controllers/loadbalancer/loadbalancer_suite_test.go @@ -17,9 +17,8 @@ package loadbalancer import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/sirupsen/logrus" "github.com/projectcalico/calico/libcalico-go/lib/testutils" @@ -31,7 +30,8 @@ func init() { } func Test(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/loadbalancer_controller_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "LoadBalancer controller suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/loadbalancer_controller_suite.xml" + ginkgo.RunSpecs(t, "LoadBalancer controller suite", suiteConfig, reporterConfig) } diff --git a/kube-controllers/pkg/controllers/namespace/namespace_controller.go b/kube-controllers/pkg/controllers/namespace/namespace_controller.go index 999ea2ba9a2..46df5e24102 100644 --- a/kube-controllers/pkg/controllers/namespace/namespace_controller.go +++ b/kube-controllers/pkg/controllers/namespace/namespace_controller.go @@ -55,9 +55,9 @@ func NewNamespaceController(ctx context.Context, k8sClientset *kubernetes.Client // Function returns map of profile_name:object stored by policy controller // in the Calico datastore. Identifies controller written objects by // their naming convention. - listFunc := func() (map[string]interface{}, error) { + listFunc := func() (map[string]any, error) { log.Debugf("Listing profiles from Calico datastore") - filteredProfiles := make(map[string]interface{}) + filteredProfiles := make(map[string]any) // Get all profile objects from Calico datastore. profileList, err := c.Profiles().List(ctx, options.ListOptions{}) @@ -83,7 +83,7 @@ func NewNamespaceController(ctx context.Context, k8sClientset *kubernetes.Client // Create a Cache to store Profiles in. cacheArgs := rcache.ResourceCacheArgs{ ListFunc: listFunc, - ObjectType: reflect.TypeOf(api.Profile{}), + ObjectType: reflect.TypeFor[api.Profile](), LogTypeDesc: "Namespace", } ccache := rcache.NewResourceCache(cacheArgs) @@ -98,7 +98,7 @@ func NewNamespaceController(ctx context.Context, k8sClientset *kubernetes.Client ObjectType: &v1.Namespace{}, ResyncPeriod: 0, Handler: cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { + AddFunc: func(obj any) { log.Debugf("Got ADD event for Namespace: %#v", obj) profile, err := namespaceConverter.Convert(obj) if err != nil { @@ -110,7 +110,7 @@ func NewNamespaceController(ctx context.Context, k8sClientset *kubernetes.Client k := namespaceConverter.GetKey(profile) ccache.Set(k, profile) }, - UpdateFunc: func(oldObj interface{}, newObj interface{}) { + UpdateFunc: func(oldObj any, newObj any) { log.Debugf("Got UPDATE event for Namespace") log.Debugf("Old object: \n%#v\n", oldObj) log.Debugf("New object: \n%#v\n", newObj) @@ -132,7 +132,7 @@ func NewNamespaceController(ctx context.Context, k8sClientset *kubernetes.Client k := namespaceConverter.GetKey(profile) ccache.Set(k, profile) }, - DeleteFunc: func(obj interface{}) { + DeleteFunc: func(obj any) { // Convert the namespace into a Profile. log.Debugf("Got DELETE event for namespace: %#v", obj) profile, err := namespaceConverter.Convert(obj) diff --git a/kube-controllers/pkg/controllers/namespace/namespace_controller_fv_test.go b/kube-controllers/pkg/controllers/namespace/namespace_controller_fv_test.go index 997b5aad1bf..5d53db543ea 100644 --- a/kube-controllers/pkg/controllers/namespace/namespace_controller_fv_test.go +++ b/kube-controllers/pkg/controllers/namespace/namespace_controller_fv_test.go @@ -16,10 +16,9 @@ package namespace_test import ( "context" - "os" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" v1 "k8s.io/api/core/v1" @@ -36,11 +35,13 @@ import ( var _ = Describe("Calico namespace controller FV tests (etcd mode)", func() { var ( etcd *containers.Container - policyController *containers.Container + kubeControllers *containers.Container apiserver *containers.Container calicoClient client.Interface k8sClient *kubernetes.Clientset controllerManager *containers.Container + kconfigfile string + removeKubeconfig func() ) BeforeEach(func() { @@ -52,20 +53,13 @@ var _ = Describe("Calico namespace controller FV tests (etcd mode)", func() { apiserver = testutils.RunK8sApiserver(etcd.IP) // Write out a kubeconfig file - kconfigfile, err := os.CreateTemp("", "ginkgo-policycontroller") - Expect(err).NotTo(HaveOccurred()) - defer func() { _ = os.Remove(kconfigfile.Name()) }() - data := testutils.BuildKubeconfig(apiserver.IP) - _, err = kconfigfile.Write([]byte(data)) - Expect(err).NotTo(HaveOccurred()) - - // Make the kubeconfig readable by the container. - Expect(kconfigfile.Chmod(os.ModePerm)).NotTo(HaveOccurred()) + kconfigfile, removeKubeconfig = testutils.BuildKubeconfig(apiserver.IP) // Run the controller. - policyController = testutils.RunPolicyController(apiconfig.EtcdV3, etcd.IP, kconfigfile.Name(), "") + kubeControllers = testutils.RunKubeControllers(apiconfig.EtcdV3, etcd.IP, kconfigfile, "") - k8sClient, err = testutils.GetK8sClient(kconfigfile.Name()) + var err error + k8sClient, err = testutils.GetK8sClient(kconfigfile) Expect(err).NotTo(HaveOccurred()) // Wait for the apiserver to be available. @@ -85,9 +79,10 @@ var _ = Describe("Calico namespace controller FV tests (etcd mode)", func() { AfterEach(func() { _ = calicoClient.Close() controllerManager.Stop() - policyController.Stop() + kubeControllers.Stop() apiserver.Stop() etcd.Stop() + removeKubeconfig() }) Context("Namespace Profile FV tests", func() { diff --git a/kube-controllers/pkg/controllers/namespace/namespace_suite_test.go b/kube-controllers/pkg/controllers/namespace/namespace_suite_test.go index a94cfe9594e..2a123fc5a24 100644 --- a/kube-controllers/pkg/controllers/namespace/namespace_suite_test.go +++ b/kube-controllers/pkg/controllers/namespace/namespace_suite_test.go @@ -17,9 +17,8 @@ package namespace_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/sirupsen/logrus" "github.com/projectcalico/calico/libcalico-go/lib/testutils" @@ -31,7 +30,8 @@ func init() { } func Test(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/namespace_controller_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Namespace controller suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/namespace_controller_suite.xml" + ginkgo.RunSpecs(t, "Namespace controller suite", suiteConfig, reporterConfig) } diff --git a/kube-controllers/pkg/controllers/networkpolicy/policy_controller.go b/kube-controllers/pkg/controllers/networkpolicy/policy_controller.go index 6da202f89d1..70b633ede70 100644 --- a/kube-controllers/pkg/controllers/networkpolicy/policy_controller.go +++ b/kube-controllers/pkg/controllers/networkpolicy/policy_controller.go @@ -36,7 +36,6 @@ import ( "github.com/projectcalico/calico/kube-controllers/pkg/converter" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/errors" - "github.com/projectcalico/calico/libcalico-go/lib/names" "github.com/projectcalico/calico/libcalico-go/lib/options" ) @@ -59,7 +58,7 @@ func NewPolicyController(ctx context.Context, clientset *kubernetes.Clientset, c // Function returns map of policyName:policy stored by policy controller // in datastore. - listFunc := func() (map[string]interface{}, error) { + listFunc := func() (map[string]any, error) { // Get all policies from datastore calicoPolicies, err := c.NetworkPolicies().List(ctx, options.ListOptions{}) if err != nil { @@ -67,9 +66,9 @@ func NewPolicyController(ctx context.Context, clientset *kubernetes.Clientset, c } // Filter in only objects that are written by policy controller. - m := make(map[string]interface{}) + m := make(map[string]any) for _, policy := range calicoPolicies.Items { - if strings.HasPrefix(policy.Name, names.K8sNetworkPolicyNamePrefix) { + if strings.HasPrefix(policy.Name, converter.KubernetesNetworkPolicyEtcdPrefix) { // Update the network policy's ObjectMeta so that it simply contains the name and namespace. // There is other metadata that we might receive (like resource version) that we don't want to // compare in the cache. @@ -85,7 +84,7 @@ func NewPolicyController(ctx context.Context, clientset *kubernetes.Clientset, c cacheArgs := rcache.ResourceCacheArgs{ ListFunc: listFunc, - ObjectType: reflect.TypeOf(api.NetworkPolicy{}), + ObjectType: reflect.TypeFor[api.NetworkPolicy](), } ccache := rcache.NewResourceCache(cacheArgs) @@ -96,7 +95,7 @@ func NewPolicyController(ctx context.Context, clientset *kubernetes.Clientset, c ObjectType: &networkingv1.NetworkPolicy{}, ResyncPeriod: 0, Handler: cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { + AddFunc: func(obj any) { log.Debugf("Got ADD event for network policy: %#v", obj) policy, err := policyConverter.Convert(obj) if err != nil { @@ -108,7 +107,7 @@ func NewPolicyController(ctx context.Context, clientset *kubernetes.Clientset, c k := policyConverter.GetKey(policy) ccache.Set(k, policy) }, - UpdateFunc: func(oldObj interface{}, newObj interface{}) { + UpdateFunc: func(oldObj any, newObj any) { log.Debugf("Got UPDATE event for NetworkPolicy.") log.Debugf("Old object: \n%#v\n", oldObj) log.Debugf("New object: \n%#v\n", newObj) @@ -122,7 +121,7 @@ func NewPolicyController(ctx context.Context, clientset *kubernetes.Clientset, c k := policyConverter.GetKey(policy) ccache.Set(k, policy) }, - DeleteFunc: func(obj interface{}) { + DeleteFunc: func(obj any) { log.Debugf("Got DELETE event for NetworkPolicy: %#v", obj) policy, err := policyConverter.Convert(obj) if err != nil { diff --git a/kube-controllers/pkg/controllers/networkpolicy/policy_controller_fv_test.go b/kube-controllers/pkg/controllers/networkpolicy/policy_controller_fv_test.go index c4fc7534d4e..3c7cfa24a85 100644 --- a/kube-controllers/pkg/controllers/networkpolicy/policy_controller_fv_test.go +++ b/kube-controllers/pkg/controllers/networkpolicy/policy_controller_fv_test.go @@ -16,10 +16,9 @@ package networkpolicy_test import ( "context" - "os" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" networkingv1 "k8s.io/api/networking/v1" @@ -36,11 +35,12 @@ import ( var _ = Describe("Calico networkpolicy controller FV tests (etcd mode)", func() { var ( etcd *containers.Container - policyController *containers.Container + kubeControllers *containers.Container apiserver *containers.Container calicoClient client.Interface k8sClient *kubernetes.Clientset controllerManager *containers.Container + err error ) BeforeEach(func() { @@ -52,20 +52,13 @@ var _ = Describe("Calico networkpolicy controller FV tests (etcd mode)", func() apiserver = testutils.RunK8sApiserver(etcd.IP) // Write out a kubeconfig file - kconfigfile, err := os.CreateTemp("", "ginkgo-policycontroller") - Expect(err).NotTo(HaveOccurred()) - defer func() { _ = os.Remove(kconfigfile.Name()) }() - data := testutils.BuildKubeconfig(apiserver.IP) - _, err = kconfigfile.Write([]byte(data)) - Expect(err).NotTo(HaveOccurred()) - - // Make the kubeconfig readable by the container. - Expect(kconfigfile.Chmod(os.ModePerm)).NotTo(HaveOccurred()) + kconfigfile, cancel := testutils.BuildKubeconfig(apiserver.IP) + defer cancel() // Run the controller. - policyController = testutils.RunPolicyController(apiconfig.EtcdV3, etcd.IP, kconfigfile.Name(), "") + kubeControllers = testutils.RunKubeControllers(apiconfig.EtcdV3, etcd.IP, kconfigfile, "") - k8sClient, err = testutils.GetK8sClient(kconfigfile.Name()) + k8sClient, err = testutils.GetK8sClient(kconfigfile) Expect(err).NotTo(HaveOccurred()) // Wait for the apiserver to be available. @@ -85,7 +78,7 @@ var _ = Describe("Calico networkpolicy controller FV tests (etcd mode)", func() AfterEach(func() { _ = calicoClient.Close() controllerManager.Stop() - policyController.Stop() + kubeControllers.Stop() apiserver.Stop() etcd.Stop() }) diff --git a/kube-controllers/pkg/controllers/networkpolicy/policy_name_migrator.go b/kube-controllers/pkg/controllers/networkpolicy/policy_name_migrator.go new file mode 100644 index 00000000000..d55b7620732 --- /dev/null +++ b/kube-controllers/pkg/controllers/networkpolicy/policy_name_migrator.go @@ -0,0 +1,380 @@ +package networkpolicy + +import ( + "context" + "os" + "reflect" + "time" + + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/projectcalico/calico/kube-controllers/pkg/controllers/controller" + "github.com/projectcalico/calico/kube-controllers/pkg/controllers/utils" + bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" + "github.com/projectcalico/calico/libcalico-go/lib/backend/model" + "github.com/projectcalico/calico/libcalico-go/lib/clientv3" + liberr "github.com/projectcalico/calico/libcalico-go/lib/errors" + "github.com/projectcalico/calico/libcalico-go/lib/set" + "github.com/projectcalico/calico/libcalico-go/lib/winutils" +) + +// NewMigratorController creates a new controller that migrates network policies +// from v1 names to v3 names in the datastore. +// +// In Calico v3.32, the naming convention for network policies was changed to remove the tier prefixing requirement. +// As part of this, we aligned the v3 object names with the actual datastore (crd.projectcalico.org/v1 and etcdv3) object names +// for newly created policies. However, existing policies created prior to v3.32 retained their original v1 names in the datastore. +// This controller is responsible for migrating those existing policies to use the new v3 naming convention in the datastore for consistency. +// +// Note: This controller only migrates policies in the "default" tier, as policies in other tiers have always had validation that prevents +// the naming mismatch. +// +// The migration process involves: +// 1. Listing all network policies in the datastore. +// 2. Identifying policies where the v1 name (in the datastore Key) differs from the v3 name (in the ObjectMeta). +// 3. For each mismatched policy: +// a. Create a new policy entry in the datastore with the correct v3 name. +// b. Delete the old policy entry with the v1 name. +func NewMigratorController(ctx context.Context, cs kubernetes.Interface, cli clientv3.Interface, feed *utils.DataFeed) controller.Controller { + type accessor interface { + Backend() bapi.Client + } + + // Read the namespace from the service account file to determine the namespace we're running in. + namespace, err := os.ReadFile(winutils.GetHostPath("/var/run/secrets/kubernetes.io/serviceaccount/namespace")) + if err != nil { + logrus.WithError(err).Warn("Failed to read service account namespace file, defaulting to 'calico-system'") + namespace = []byte("calico-system") + } + + c := &policyMigrator{ + ctx: ctx, + cli: cli, + cs: cs, + bc: cli.(accessor).Backend(), + doWork: make(chan struct{}, 1), + pendingWork: set.New[model.ResourceKey](), + kvps: make(map[model.ResourceKey]*model.KVPair), + updates: make(chan bapi.Update, utils.BatchUpdateSize), + statusUpdates: make(chan bapi.SyncStatus), + namespace: string(namespace), + skipRollout: os.Getenv("FV_TEST") == "true", + } + c.RegisterWith(feed) + + return c +} + +type policyMigrator struct { + ctx context.Context + cli clientv3.Interface + cs kubernetes.Interface + bc bapi.Client + status bapi.SyncStatus + + // State + kvps map[model.ResourceKey]*model.KVPair + pendingWork set.Set[model.ResourceKey] + + // Channels + doWork chan struct{} + updates chan bapi.Update + statusUpdates chan bapi.SyncStatus + + // Configuration. + namespace string + + // For FV testing - allows skipping the calico-node rollout wait. + skipRollout bool +} + +func (c *policyMigrator) RegisterWith(f *utils.DataFeed) { + // Register for updates for policy resources. + f.RegisterForNotification(model.ResourceKey{}, c.onUpdate) + + // Register for sync status updates. + f.RegisterForSyncStatus(c.onStatusUpdate) +} + +func (c *policyMigrator) onStatusUpdate(status bapi.SyncStatus) { + c.statusUpdates <- status +} + +func (c *policyMigrator) onUpdate(update bapi.Update) { + switch update.Key.(type) { + case model.ResourceKey: + switch update.KVPair.Key.(model.ResourceKey).Kind { + case v3.KindNetworkPolicy, + v3.KindGlobalNetworkPolicy, + v3.KindStagedNetworkPolicy, + v3.KindStagedGlobalNetworkPolicy: + // We care about these kinds. + // Send the update to the processing channel. + c.updates <- update + } + } +} + +func (c *policyMigrator) kick() { + select { + case c.doWork <- struct{}{}: + // Successfully sent kick. + default: + // Kick already pending. + } +} + +func (c *policyMigrator) Run(stop chan struct{}) { + logrus.Info("Starting policy migration controller") + + // Start a goroutine to handle updates. + go c.run(stop) + + <-stop + logrus.Info("Stopping policy migration controller") +} + +// run is the main loop for the controller, processing updates and status changes and triggering work. +func (c *policyMigrator) run(stop chan struct{}) { + // Wait for calico-node rollout to complete before starting migration. + err := c.waitForCalicoNodeRollout() + if err != nil { + logrus.Errorf("Error waiting for calico-node rollout: %v", err) + } + + for { + select { + case <-stop: + return + case status := <-c.statusUpdates: + c.status = status + logrus.Infof("Syncer status updated: %s", status.String()) + c.kick() + case updates := <-c.updates: + logEntry := logrus.WithFields(logrus.Fields{"controller": "PolicyMigrator"}) + utils.ProcessBatch(c.updates, updates, c.processUpdates, logEntry) + c.kick() + case <-time.After(5 * time.Minute): + // Periodic kick to reprocess pending work. This handles any missed updates or transient errors. + // Ideally, we'd requeue errors immediately, but this is a safety net. + c.kick() + case <-c.doWork: + err := c.processPendingWork() + if err != nil { + logrus.Errorf("Error processing updates: %v", err) + } + } + } +} + +// processUpdates processes incoming updates from the syncer and updates internal state. +func (c *policyMigrator) processUpdates(update bapi.Update) { + // Store updates. + kvp := update.KVPair + key, ok := kvp.Key.(model.ResourceKey) + if !ok { + logrus.Errorf("Received unexpected key type: %T", kvp.Key) + return + } + + logCtx := logrus.WithFields(logrus.Fields{ + "key": key, + "type": update.UpdateType.String(), + }) + logCtx.Debug("Received policy update") + + if kvp.Value == nil { + // Deletion - remove from state. + delete(c.kvps, kvp.Key.(model.ResourceKey)) + c.pendingWork.Discard(key) + } else { + // Addition or update - store in state. + c.kvps[key] = &kvp + + // If the policy needs migration, add to pending work. + if p, ok := kvp.Value.(client.Object); ok { + if needsMigration(p, key) { + logCtx.Debug("Stored policy for potential migration check") + c.pendingWork.Add(key) + } else { + logCtx.Debug("Policy does not need migration, skipping") + c.pendingWork.Discard(key) + } + } + } +} + +// processPendingWork processes policies that need migration. +func (c *policyMigrator) processPendingWork() error { + // Wait for the syncer to be in sync. + if c.status != bapi.InSync { + logrus.Debug("Syncer not in sync yet, waiting...") + return nil + } + + // No-op for now. + for key := range c.pendingWork.All() { + kvp := c.kvps[key] + p := kvp.Value.(client.Object) + k := kvp.Key.(model.ResourceKey) + + logCtx := logrus.WithFields(logrus.Fields{ + "namespace": p.GetNamespace(), + "v3name": p.GetName(), + "kind": p.GetObjectKind().GroupVersionKind().Kind, + }) + logCtx.Debug("Processing policy for potential migration") + + // The name in the Key is the actual v1 ID used in the datastore, whereas the ObjectMeta.Name is the v3 + // object name. If they differ, we need to correct the underlying datastore entry to align with the v3 naming. + if !needsMigration(p, k) { + logCtx.Debug("No migration needed") + c.pendingWork.Discard(key) + continue + } + + logCtx.WithFields(logrus.Fields{ + "v1Name": k.Name, + }).Debug("Migrating policy to new name") + + // Create a new Policy object with the correct v3 name. + newPolicy := p.DeepCopyObject() + newKey := k + newKey.Name = p.GetName() + + // Create the new policy in the datastore. + _, err := c.bc.Create(c.ctx, &model.KVPair{Key: newKey, Value: newPolicy}) + if err != nil { + if _, ok := err.(liberr.ErrorResourceAlreadyExists); !ok { + // If the error is AlreadyExists, it means we already fixed this policy in a previous run, so we can safely ignore it. + // For other errors, log them and continue on to the next piece of work. + logCtx.Errorf("Error creating new policy %s: %v", newKey, err) + continue + } + logCtx.Infof("New policy %s already exists, carry on", newKey) + } + + // Delete the old policy from the datastore. + _, err = c.bc.DeleteKVP(c.ctx, kvp) + if err != nil { + if _, ok := err.(liberr.ErrorResourceDoesNotExist); !ok { + // If the error is NotFound, it means the old policy was already deleted, so we can safely ignore it. + // For other errors, log them and continue on to the next piece of work. + logCtx.Errorf("Error deleting old policy %s: %v", k, err) + continue + } + logCtx.Infof("Old policy %s already deleted, carry on", k) + } + + // Successfully migrated this policy, remove from pending work. + logrus.WithFields(logrus.Fields{ + "namespace": p.GetNamespace(), + "newName": p.GetName(), + "oldName": k.Name, + "kind": p.GetObjectKind().GroupVersionKind().Kind, + }).Info("Successfully migrated storage name for policy") + c.pendingWork.Discard(key) + } + return nil +} + +// waitForCalicoNodeRollout waits for all calico-node pods to be running the version that supports the new policy names. +func (c *policyMigrator) waitForCalicoNodeRollout() error { + if c.skipRollout { + // This is useful for FV tests that don't run calico-node. + logrus.Info("Skipping calico-node rollout wait as per configuration") + return nil + } + + for { + select { + case <-c.ctx.Done(): + return c.ctx.Err() + case <-time.After(5 * time.Second): + // Rate limit checks to once every 5 seconds. + ds, err := c.cs.AppsV1().DaemonSets(c.namespace).Get(c.ctx, "calico-node", metav1.GetOptions{}) + if err != nil { + logrus.Errorf("Error getting calico-node DaemonSet: %v", err) + continue + } + if ds.Status.ObservedGeneration != ds.Generation { + logrus.WithFields(logrus.Fields{ + "observedGeneration": ds.Status.ObservedGeneration, + "generation": ds.Generation, + }).Info("Waiting for calico-node DaemonSet to be observed") + continue + } + if ds.Status.CurrentNumberScheduled != ds.Status.DesiredNumberScheduled { + logrus.WithFields(logrus.Fields{ + "currentNumberScheduled": ds.Status.CurrentNumberScheduled, + "desiredNumberScheduled": ds.Status.DesiredNumberScheduled, + }).Info("Waiting for all calico-node pods to be scheduled") + continue + } + if ds.Status.UpdatedNumberScheduled != ds.Status.DesiredNumberScheduled { + logCtx := logrus.WithFields(logrus.Fields{ + "updatedNumberScheduled": ds.Status.UpdatedNumberScheduled, + "desiredNumberScheduled": ds.Status.DesiredNumberScheduled, + }) + logCtx.Info("Waiting for all calico-node pods to be updated") + continue + } + if ds.Status.NumberUnavailable > 0 { + logCtx := logrus.WithFields(logrus.Fields{ + "numberUnavailable": ds.Status.NumberUnavailable, + }) + logCtx.Info("Waiting for all calico-node pods to be available") + continue + } + if ds.Status.NumberMisscheduled > 0 { + logCtx := logrus.WithFields(logrus.Fields{ + "numberMisscheduled": ds.Status.NumberMisscheduled, + }) + logCtx.Info("Waiting for all calico-node pods to be correctly scheduled") + continue + } + logrus.Info("All calico-node pods are up-to-date") + return nil + } + } +} + +func needsMigration(p client.Object, k model.ResourceKey) bool { + // The name in the Key is the actual v1 ID used in the datastore, whereas the ObjectMeta.Name is the v3 + // object name. If they differ, we need to correct the underlying datastore entry to align with the v3 naming. + // + // Policies in non-default tiers have traditionally had restrictive validation that prevented the mismatch + // this controller is designed to fix. Therefore, we only need to fix policies in the default tier. + return isDefaultTier(p) && k.Name != p.GetName() +} + +func isDefaultTier(p client.Object) bool { + logCtx := logrus.WithFields(logrus.Fields{ + "namespace": p.GetNamespace(), + "name": p.GetName(), + }) + + // Use reflection to get the Tier field from the policy spec. + spec := reflect.ValueOf(p).Elem().FieldByName("Spec") + if !spec.IsValid() { + logCtx.Warn("Spec field not found in object") + return true // Default to true if Spec field is not found. + } + + tierField := spec.FieldByName("Tier") + if !tierField.IsValid() { + logCtx.Warn("Tier field not found in Spec") + return true // Default to true if Tier field is not found. + } + + tier, ok := tierField.Interface().(string) + if !ok { + logCtx.Warn("Tier field is not a string") + return true // Default to true if Tier field is not a string. + } + return tier == "" || tier == "default" +} diff --git a/kube-controllers/pkg/controllers/networkpolicy/policy_name_migrator_test.go b/kube-controllers/pkg/controllers/networkpolicy/policy_name_migrator_test.go new file mode 100644 index 00000000000..e64857af53b --- /dev/null +++ b/kube-controllers/pkg/controllers/networkpolicy/policy_name_migrator_test.go @@ -0,0 +1,632 @@ +// Copyright (c) 2025-2026 Tigera, Inc. All rights reserved. +// +// 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 networkpolicy_test + +import ( + "context" + "encoding/json" + "fmt" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + kerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/projectcalico/calico/felix/fv/containers" + "github.com/projectcalico/calico/kube-controllers/tests/testutils" + "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" + v1scheme "github.com/projectcalico/calico/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme" + bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" + "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s" + "github.com/projectcalico/calico/libcalico-go/lib/backend/model" + client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" + "github.com/projectcalico/calico/libcalico-go/lib/errors" + "github.com/projectcalico/calico/libcalico-go/lib/options" +) + +var _ = Describe("policy name migration tests (etcd mode)", func() { + var ( + etcd *containers.Container + kubectrl *containers.Container + apiserver *containers.Container + cli client.Interface + bcli bapi.Client + k8sClient *kubernetes.Clientset + controllerManager *containers.Container + err error + ) + + // Define an interface to access the backend client from the Calico client. + type accessor interface { + Backend() bapi.Client + } + + BeforeEach(func() { + // Run etcd. + etcd = testutils.RunEtcd() + + // Run apiserver. + apiserver = testutils.RunK8sApiserver(etcd.IP) + kubeconfig, cleanup := testutils.BuildKubeconfig(apiserver.IP) + defer cleanup() + + // Run the controller. + mode := apiconfig.EtcdV3 + kubectrl = testutils.RunKubeControllers(mode, etcd.IP, kubeconfig, "") + + // Create clients for the test. + cli = testutils.GetCalicoClient(mode, etcd.IP, kubeconfig) + k8sClient, err = testutils.GetK8sClient(kubeconfig) + Expect(err).NotTo(HaveOccurred()) + bcli = cli.(accessor).Backend() + + // Wait for the apiserver to be available. + Eventually(func() error { + _, err := k8sClient.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{}) + return err + }, 30*time.Second, 1*time.Second).Should(BeNil()) + }) + + AfterEach(func() { + _ = cli.Close() + controllerManager.Stop() + kubectrl.Stop() + apiserver.Stop() + etcd.Stop() + }) + + It("should update NetworkPolicy names correctly", func() { + var err error + + // Create a policy that was created without a tier prefix in the v3 API, but + // whose backend representation includes the tier prefix. + mismatchedNames := v3.NewNetworkPolicy() + mismatchedNames.Name = "mismatched" // Name was created without tier. + mismatchedNames.Namespace = "default" + mismatchedNames.Spec = v3.NetworkPolicySpec{ + Tier: "default", + Selector: "all()", + Ingress: []v3.Rule{{ + Action: "Allow", + }}, + } + _, err = bcli.Create(context.Background(), &model.KVPair{ + Key: model.ResourceKey{ + Kind: v3.KindNetworkPolicy, + Name: "default.mismatched", // Old generated name format including tier. + Namespace: mismatchedNames.Namespace, + }, + Value: mismatchedNames, + }) + Expect(err).NotTo(HaveOccurred()) + + // Create a policy that was created in a non-default tier, and that + // has a name that matches between the v3 API and backend. + matchingWithPrefix := v3.NewNetworkPolicy() + matchingWithPrefix.Name = "custom-tier.policy-in-non-default-tier" + matchingWithPrefix.Namespace = "default" + matchingWithPrefix.Spec = v3.NetworkPolicySpec{ + Tier: "custom-tier", + Selector: "all()", + Ingress: []v3.Rule{{ + Action: "Allow", + }}, + } + _, err = bcli.Create(context.Background(), &model.KVPair{ + Key: model.ResourceKey{ + Kind: v3.KindNetworkPolicy, + Name: matchingWithPrefix.Name, // Name matches between v3 and backend. + Namespace: matchingWithPrefix.Namespace, + }, + Value: matchingWithPrefix, + }) + Expect(err).NotTo(HaveOccurred()) + + // Create a policy that was created without a tier prefix in the v3 API, and + // whose backend representation also does not include the tier prefix. + matchingNoPrefix := v3.NewNetworkPolicy() + matchingNoPrefix.Name = "matching-name" + matchingNoPrefix.Namespace = "default" + matchingNoPrefix.Spec = v3.NetworkPolicySpec{ + Tier: "default", + Selector: "all()", + Ingress: []v3.Rule{{ + Action: "Allow", + }}, + } + _, err = bcli.Create(context.Background(), &model.KVPair{ + Key: model.ResourceKey{ + Kind: v3.KindNetworkPolicy, + Name: matchingNoPrefix.Name, // Name matches between v3 and backend. + Namespace: matchingNoPrefix.Namespace, + }, + Value: matchingNoPrefix, + }) + Expect(err).NotTo(HaveOccurred()) + + // Check that all policies are accessible via the v3 API with the correct v3 API names. + for _, np := range []*v3.NetworkPolicy{mismatchedNames, matchingWithPrefix, matchingNoPrefix} { + Eventually(func() error { + _, err = cli.NetworkPolicies().Get(context.Background(), np.Namespace, np.Name, options.GetOptions{}) + return err + }, 5*time.Second, 1*time.Second).Should(BeNil(), "NetworkPolicy was not accessible via v3 API: %s", np.Name) + } + + // Check that the v1 backend representation for the mismatched policy has been updated to match + // the v3 API name. + kvp := expectFound(bcli, "mismatched", mismatchedNames.Namespace, v3.KindNetworkPolicy) + Expect(kvp.Value.(*v3.NetworkPolicy).Name).To(Equal("mismatched")) + + // Check that the old mismatched key is no longer present in the backend. + expectNotFound(bcli, "default.mismatched", mismatchedNames.Namespace, v3.KindNetworkPolicy) + + // Check that the other two policies are still present under their original names. + kvp = expectFound(bcli, matchingWithPrefix.Name, matchingWithPrefix.Namespace, v3.KindNetworkPolicy) + Expect(kvp.Value.(*v3.NetworkPolicy).Name).To(Equal(matchingWithPrefix.Name)) + + kvp = expectFound(bcli, matchingNoPrefix.Name, matchingNoPrefix.Namespace, v3.KindNetworkPolicy) + Expect(kvp.Value.(*v3.NetworkPolicy).Name).To(Equal(matchingNoPrefix.Name)) + }) + + It("should update a GlobalNetworkPolicy name correctly", func() { + var err error + + // Create a GlobalNetworkPolicy that was created without a tier prefix in the v3 API, but + // whose backend representation includes the tier prefix. + mismatchedNames := v3.NewGlobalNetworkPolicy() + mismatchedNames.Name = "mismatched" + mismatchedNames.Spec = v3.GlobalNetworkPolicySpec{ + Tier: "default", + Selector: "all()", + } + _, err = bcli.Create(context.Background(), &model.KVPair{ + Key: model.ResourceKey{ + Kind: v3.KindGlobalNetworkPolicy, + Name: "default.mismatched", // Old generated name format including tier. + }, + Value: mismatchedNames, + }) + Expect(err).NotTo(HaveOccurred()) + + // Check that the policy is accessible via the v3 API with the correct v3 API name. + Eventually(func() error { + _, err = cli.GlobalNetworkPolicies().Get(context.Background(), mismatchedNames.Name, options.GetOptions{}) + return err + }, 5*time.Second, 1*time.Second).Should(BeNil(), "GlobalNetworkPolicy was not accessible via v3 API: %s", mismatchedNames.Name) + + // Check that the v1 backend representation for the mismatched policy has been updated to match + // the v3 API name. + kvp := expectFound(bcli, "mismatched", "", v3.KindGlobalNetworkPolicy) + Expect(kvp.Value.(*v3.GlobalNetworkPolicy).Name).To(Equal("mismatched")) + + // Check that the old mismatched key is no longer present in the backend. + expectNotFound(bcli, "default.mismatched", "", v3.KindGlobalNetworkPolicy) + }) + + It("should update a StagedNetworkPolicy name correctly", func() { + var err error + + // Create a StagedNetworkPolicy that was created without a tier prefix in the v3 API, but + // whose backend representation includes the tier prefix. + mismatchedNames := v3.NewStagedNetworkPolicy() + mismatchedNames.Name = "mismatched" + mismatchedNames.Namespace = "default" + mismatchedNames.Spec = v3.StagedNetworkPolicySpec{ + Tier: "default", + Selector: "all()", + } + _, err = bcli.Create(context.Background(), &model.KVPair{ + Key: model.ResourceKey{ + Kind: v3.KindStagedNetworkPolicy, + Name: "default.mismatched", // Old generated name format including tier. + Namespace: mismatchedNames.Namespace, + }, + Value: mismatchedNames, + }) + Expect(err).NotTo(HaveOccurred()) + + // Check that the policy is accessible via the v3 API with the correct v3 API name. + Eventually(func() error { + _, err = cli.StagedNetworkPolicies().Get(context.Background(), mismatchedNames.Namespace, mismatchedNames.Name, options.GetOptions{}) + return err + }, 5*time.Second, 1*time.Second).Should(BeNil(), "StagedNetworkPolicy was not accessible via v3 API: %s", mismatchedNames.Name) + + // Check that the v1 backend representation for the mismatched policy has been updated to match + // the v3 API name. + kvp := expectFound(bcli, "mismatched", mismatchedNames.Namespace, v3.KindStagedNetworkPolicy) + Expect(kvp.Value.(*v3.StagedNetworkPolicy).Name).To(Equal("mismatched")) + + // Check that the old mismatched key is no longer present in the backend. + expectNotFound(bcli, "default.mismatched", mismatchedNames.Namespace, v3.KindStagedNetworkPolicy) + }) + + It("should update a StagedGlobalNetworkPolicy name correctly", func() { + var err error + + // Create a StagedGlobalNetworkPolicy that was created without a tier prefix in the v3 API, but + // whose backend representation includes the tier prefix. + mismatchedNames := v3.NewStagedGlobalNetworkPolicy() + mismatchedNames.Name = "mismatched" + mismatchedNames.Spec = v3.StagedGlobalNetworkPolicySpec{ + Tier: "default", + Selector: "all()", + } + _, err = bcli.Create(context.Background(), &model.KVPair{ + Key: model.ResourceKey{ + Kind: v3.KindStagedGlobalNetworkPolicy, + Name: "default.mismatched", // Old generated name format including tier. + }, + Value: mismatchedNames, + }) + Expect(err).NotTo(HaveOccurred()) + + // Check that the policy is accessible via the v3 API with the correct v3 API name. + Eventually(func() error { + _, err = cli.StagedGlobalNetworkPolicies().Get(context.Background(), mismatchedNames.Name, options.GetOptions{}) + return err + }, 5*time.Second, 1*time.Second).Should(BeNil(), "StagedGlobalNetworkPolicy was not accessible via v3 API: %s", mismatchedNames.Name) + + // Check that the v1 backend representation for the mismatched policy has been updated to match + // the v3 API name. + kvp := expectFound(bcli, "mismatched", "", v3.KindStagedGlobalNetworkPolicy) + Expect(kvp.Value.(*v3.StagedGlobalNetworkPolicy).Name).To(Equal("mismatched")) + + // Check that the old mismatched key is no longer present in the backend. + expectNotFound(bcli, "default.mismatched", "", v3.KindStagedGlobalNetworkPolicy) + }) + + It("should fix if both mismatched and correct keys exist", func() { + var err error + + // Create a policy that was created without a tier prefix in the v3 API, but + // whose backend representation includes the tier prefix. + mismatchedNames := v3.NewNetworkPolicy() + mismatchedNames.Name = "mismatched" + mismatchedNames.Namespace = "default" + mismatchedNames.Spec = v3.NetworkPolicySpec{ + Tier: "default", + Selector: "all()", + } + + // Create the correct key. + _, err = bcli.Create(context.Background(), &model.KVPair{ + Key: model.ResourceKey{ + Kind: v3.KindNetworkPolicy, + Name: "mismatched", // Correct name. + Namespace: mismatchedNames.Namespace, + }, + Value: mismatchedNames, + }) + Expect(err).NotTo(HaveOccurred()) + + // Create the old mismatched key. + _, err = bcli.Create(context.Background(), &model.KVPair{ + Key: model.ResourceKey{ + Kind: v3.KindNetworkPolicy, + Name: "default.mismatched", // Old generated name format including tier. + Namespace: mismatchedNames.Namespace, + }, + Value: mismatchedNames, + }) + Expect(err).NotTo(HaveOccurred()) + + // Check that the policy is accessible via the v3 API with the correct v3 API name. + Eventually(func() error { + _, err = cli.NetworkPolicies().Get(context.Background(), mismatchedNames.Namespace, mismatchedNames.Name, options.GetOptions{}) + return err + }, 5*time.Second, 1*time.Second).Should(BeNil(), "NetworkPolicy was not accessible via v3 API: %s", mismatchedNames.Name) + + // Check that the old mismatched key is no longer present in the backend. + expectNotFound(bcli, "default.mismatched", mismatchedNames.Namespace, v3.KindNetworkPolicy) + + // Check that the correct key is still present. + kvp := expectFound(bcli, "mismatched", mismatchedNames.Namespace, v3.KindNetworkPolicy) + Expect(kvp.Value.(*v3.NetworkPolicy).Name).To(Equal("mismatched")) + }) +}) + +func expectFound(bcli bapi.Client, name, ns, kind string) *model.KVPair { + var kvp *model.KVPair + var err error + EventuallyWithOffset(1, func() error { + kvp, err = bcli.Get(context.Background(), model.ResourceKey{ + Kind: kind, + Name: name, + Namespace: ns, + }, "") + return err + }, 5*time.Second, 1*time.Second).Should(BeNil(), "expected to find key %s/%s for kind %s", ns, name, kind) + return kvp +} + +func expectNotFound(bcli bapi.Client, name, ns, kind string) { + EventuallyWithOffset(1, func() error { + _, err := bcli.Get(context.Background(), model.ResourceKey{ + Kind: kind, + Name: name, + Namespace: ns, + }, "") + if _, ok := err.(errors.ErrorResourceDoesNotExist); ok { + return nil + } + if err != nil { + return err + } + return fmt.Errorf("expected not to find old key for mismatched policy") + }, 5*time.Second, 1*time.Second).Should(BeNil()) +} + +// Kubernetes CRD backend needs separate tests since the v1 API behavior is different such that it does not allow us +// to write objects with mismatched v3 and v1 names. Instead, we'll access the CRD API directly to create the mismatched +// objects. +var _ = Describe("policy name migration tests (kdd mode)", func() { + var ( + etcd *containers.Container + kubectrl *containers.Container + apiserver *containers.Container + k8sClient *kubernetes.Clientset + crdClient ctrlclient.Client + controllerManager *containers.Container + err error + ) + + BeforeEach(func() { + // Run etcd. + etcd = testutils.RunEtcd() + + // Determine if we should use v3 CRDs based on the test config. + var cfg *apiconfig.CalicoAPIConfig + cfg, err = apiconfig.LoadClientConfigFromEnvironment() + Expect(err).NotTo(HaveOccurred()) + useV3CRDs := k8s.UsingV3CRDs(&cfg.Spec) + if useV3CRDs { + Skip("policy name migration does not apply to projectcalico.org/v3 CRDs") + } + + // Run apiserver. + apiserver = testutils.RunK8sApiserver(etcd.IP) + kubeconfig, cleanup := testutils.BuildKubeconfig(apiserver.IP) + defer cleanup() + + // Run the controller. + mode := apiconfig.Kubernetes + kubectrl = testutils.RunKubeControllers(mode, etcd.IP, kubeconfig, "") + + // Create clients for the test. + k8sClient, err = testutils.GetK8sClient(kubeconfig) + Expect(err).NotTo(HaveOccurred()) + + // Register Calico CRD types with the scheme. + v1scheme.AddCalicoResourcesToGlobalScheme() + + // Create a client for interacting with CRDs directly. + config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) + Expect(err).NotTo(HaveOccurred()) + crdClient, err = ctrlclient.New(config, ctrlclient.Options{}) + Expect(err).NotTo(HaveOccurred()) + + // Wait for the apiserver to be available. + Eventually(func() error { + _, err := k8sClient.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{}) + return err + }, 30*time.Second, 1*time.Second).Should(BeNil()) + + // Apply the necessary CRDs if we're running in k8s mode. + testutils.ApplyCRDs(apiserver) + }) + + AfterEach(func() { + controllerManager.Stop() + kubectrl.Stop() + apiserver.Stop() + etcd.Stop() + }) + + It("should update NetworkPolicy names correctly", func() { + // Create a CRD NetworkPolicy that was created without a tier prefix in the v3 API, but + // whose backend representation includes the tier prefix. + mismatchedNames := &v3.NetworkPolicy{} + mismatchedNames.Name = "default.mismatched" + mismatchedNames.Namespace = "default" + mismatchedNames.Spec = v3.NetworkPolicySpec{ + Tier: "default", + Selector: "all()", + } + + // Set the annotation to indicate the v3 API name. + v3meta := &metav1.ObjectMeta{} + v3meta.Name = "mismatched" // Name was created without tier. + v3metaBytes, err := json.Marshal(v3meta) + Expect(err).NotTo(HaveOccurred()) + mismatchedNames.Annotations = map[string]string{"projectcalico.org/metadata": string(v3metaBytes)} + + err = crdClient.Create(context.Background(), mismatchedNames) + Expect(err).NotTo(HaveOccurred()) + + // We should see the CRD re-written with the correct name. + Eventually(func() error { + np := &v3.NetworkPolicy{} + err := crdClient.Get(context.Background(), ctrlclient.ObjectKey{ + Namespace: "default", + Name: "mismatched", + }, np) + return err + }, 5*time.Second, 1*time.Second).Should(BeNil(), "NetworkPolicy was not accessible via CRD API with correct name") + + // Check that the old mismatched key is no longer present in the backend. + Eventually(func() error { + np := &v3.NetworkPolicy{} + err := crdClient.Get(context.Background(), ctrlclient.ObjectKey{ + Namespace: "default", + Name: "default.mismatched", + }, np) + if err != nil { + if kerrors.IsNotFound(err) { + return nil + } + return err + } + return fmt.Errorf("expected not to find old key for mismatched policy") + }, 5*time.Second, 1*time.Second).Should(BeNil(), "expected not to find old key for mismatched policy") + }) + + It("should update a GlobalNetworkPolicy name correctly", func() { + // Same test, but for GNP. + mismatchedNames := &v3.GlobalNetworkPolicy{} + mismatchedNames.Name = "default.mismatched" + mismatchedNames.Spec = v3.GlobalNetworkPolicySpec{ + Tier: "default", + Selector: "all()", + } + + // Set the annotation to indicate the v3 API name. + v3meta := &metav1.ObjectMeta{} + v3meta.Name = "mismatched" // Name was created without tier. + v3metaBytes, err := json.Marshal(v3meta) + Expect(err).NotTo(HaveOccurred()) + mismatchedNames.Annotations = map[string]string{"projectcalico.org/metadata": string(v3metaBytes)} + + err = crdClient.Create(context.Background(), mismatchedNames) + Expect(err).NotTo(HaveOccurred()) + + // We should see the CRD re-written with the correct name. + Eventually(func() error { + gnp := &v3.GlobalNetworkPolicy{} + err := crdClient.Get(context.Background(), ctrlclient.ObjectKey{ + Name: "mismatched", + }, gnp) + return err + }, 5*time.Second, 1*time.Second).Should(BeNil(), "GlobalNetworkPolicy was not accessible via CRD API with correct name") + }) + + It("should update a StagedNetworkPolicy name correctly", func() { + // Same test, but for SNP. + mismatchedNames := &v3.StagedNetworkPolicy{} + mismatchedNames.Name = "default.mismatched" + mismatchedNames.Namespace = "default" + mismatchedNames.Spec = v3.StagedNetworkPolicySpec{ + Tier: "default", + Selector: "all()", + } + + // Set the annotation to indicate the v3 API name. + v3meta := &metav1.ObjectMeta{} + v3meta.Name = "mismatched" // Name was created without tier. + v3metaBytes, err := json.Marshal(v3meta) + Expect(err).NotTo(HaveOccurred()) + mismatchedNames.Annotations = map[string]string{"projectcalico.org/metadata": string(v3metaBytes)} + + err = crdClient.Create(context.Background(), mismatchedNames) + Expect(err).NotTo(HaveOccurred()) + + // We should see the CRD re-written with the correct name. + Eventually(func() error { + snp := &v3.StagedNetworkPolicy{} + err := crdClient.Get(context.Background(), ctrlclient.ObjectKey{ + Namespace: "default", + Name: "mismatched", + }, snp) + return err + }, 5*time.Second, 1*time.Second).Should(BeNil(), "StagedNetworkPolicy was not accessible via CRD API with correct name") + }) + + It("should update a StagedGlobalNetworkPolicy name correctly", func() { + // Same test, but for SGNP. + mismatchedNames := &v3.StagedGlobalNetworkPolicy{} + mismatchedNames.Name = "default.mismatched" + mismatchedNames.Spec = v3.StagedGlobalNetworkPolicySpec{ + Tier: "default", + Selector: "all()", + } + + // Set the annotation to indicate the v3 API name. + v3meta := &metav1.ObjectMeta{} + v3meta.Name = "mismatched" // Name was created without tier. + v3metaBytes, err := json.Marshal(v3meta) + Expect(err).NotTo(HaveOccurred()) + mismatchedNames.Annotations = map[string]string{"projectcalico.org/metadata": string(v3metaBytes)} + + err = crdClient.Create(context.Background(), mismatchedNames) + Expect(err).NotTo(HaveOccurred()) + + // We should see the CRD re-written with the correct name. + Eventually(func() error { + sgnp := &v3.StagedGlobalNetworkPolicy{} + err := crdClient.Get(context.Background(), ctrlclient.ObjectKey{ + Name: "mismatched", + }, sgnp) + return err + }, 5*time.Second, 1*time.Second).Should(BeNil(), "StagedGlobalNetworkPolicy was not accessible via CRD API with correct name") + }) + + It("should fix if both mismatched and correct keys exist", func() { + // Create two CRDs - one with the correct name and one with the old mismatched name. + matchingNames := &v3.NetworkPolicy{} + matchingNames.Name = "policy-name" // Correct name, i.e., "already migrated". + matchingNames.Namespace = "default" + matchingNames.Spec = v3.NetworkPolicySpec{ + Tier: "default", + Selector: "all()", + } + v3meta := &metav1.ObjectMeta{} + v3meta.Name = "policy-name" // Name matches underlying CRD. + v3metaBytes, err := json.Marshal(v3meta) + Expect(err).NotTo(HaveOccurred()) + matchingNames.Annotations = map[string]string{"projectcalico.org/metadata": string(v3metaBytes)} + + err = crdClient.Create(context.Background(), matchingNames) + Expect(err).NotTo(HaveOccurred()) + + // Create the same object, but this time with the old mismatched name in the CRD name. + mismatchedNames := &v3.NetworkPolicy{} + mismatchedNames.Name = "default.mismatched" + mismatchedNames.Namespace = "default" + mismatchedNames.Spec = matchingNames.Spec + mismatchedNames.Annotations = matchingNames.Annotations + + err = crdClient.Create(context.Background(), mismatchedNames) + Expect(err).NotTo(HaveOccurred()) + + // We should see the incorrect CRD removed, leaving only the correctly named one. + Eventually(func() error { + np := &v3.NetworkPolicy{} + err := crdClient.Get(context.Background(), ctrlclient.ObjectKey{ + Namespace: "default", + Name: "default.mismatched", + }, np) + if err != nil { + if kerrors.IsNotFound(err) { + return nil + } + return err + } + return fmt.Errorf("expected not to find old key for mismatched policy") + }, 5*time.Second, 1*time.Second).Should(BeNil(), "expected not to find old key for mismatched policy") + + // Check that the correct key is still present. + Eventually(func() error { + np := &v3.NetworkPolicy{} + err := crdClient.Get(context.Background(), ctrlclient.ObjectKey{ + Namespace: "default", + Name: "policy-name", + }, np) + return err + }, 5*time.Second, 1*time.Second).Should(BeNil(), "NetworkPolicy was not accessible via CRD API with correct name") + }) +}) diff --git a/kube-controllers/pkg/controllers/networkpolicy/policy_suite_test.go b/kube-controllers/pkg/controllers/networkpolicy/policy_suite_test.go index 10646069488..96915343e1f 100644 --- a/kube-controllers/pkg/controllers/networkpolicy/policy_suite_test.go +++ b/kube-controllers/pkg/controllers/networkpolicy/policy_suite_test.go @@ -17,9 +17,8 @@ package networkpolicy_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/sirupsen/logrus" "github.com/projectcalico/calico/libcalico-go/lib/testutils" @@ -31,7 +30,8 @@ func init() { } func Test(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/networkpolicy_controller_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "NetworkPolicy controller suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/networkpolicy_controller_suite.xml" + ginkgo.RunSpecs(t, "NetworkPolicy controller suite", suiteConfig, reporterConfig) } diff --git a/kube-controllers/pkg/controllers/node/auto_hep_fv_test.go b/kube-controllers/pkg/controllers/node/auto_hep_fv_test.go index dd3d7f6a755..01a7e9dbf16 100644 --- a/kube-controllers/pkg/controllers/node/auto_hep_fv_test.go +++ b/kube-controllers/pkg/controllers/node/auto_hep_fv_test.go @@ -16,10 +16,10 @@ package node_test import ( "context" - "os" + "maps" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" v1 "k8s.io/api/core/v1" @@ -29,7 +29,7 @@ import ( "github.com/projectcalico/calico/felix/fv/containers" "github.com/projectcalico/calico/kube-controllers/tests/testutils" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapi "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/options" ) @@ -42,7 +42,8 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { c client.Interface k8sClient *kubernetes.Clientset controllerManager *containers.Container - kconfigFile *os.File + kconfigFile string + removeKubeconfig func() ) const kNodeName = "k8snodename" @@ -100,9 +101,9 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { CreateDefaultHostEndpoint: api.DefaultHostEndpointsDisabled, Templates: []api.Template{ { - GenerateName: "template", - InterfaceSelector: "eth0", - Labels: map[string]string{"template-label": "template-value"}, + GenerateName: "template", + InterfacePattern: "eth0", + Labels: map[string]string{"template-label": "template-value"}, }, }, }, @@ -119,16 +120,9 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { // Write out a kubeconfig file var err error - kconfigFile, err = os.CreateTemp("", "ginkgo-nodecontroller") - Expect(err).NotTo(HaveOccurred()) - data := testutils.BuildKubeconfig(apiserver.IP) - _, err = kconfigFile.Write([]byte(data)) - Expect(err).NotTo(HaveOccurred()) + kconfigFile, removeKubeconfig = testutils.BuildKubeconfig(apiserver.IP) - // Make the kubeconfig readable by the container. - Expect(kconfigFile.Chmod(os.ModePerm)).NotTo(HaveOccurred()) - - k8sClient, err = testutils.GetK8sClient(kconfigFile.Name()) + k8sClient, err = testutils.GetK8sClient(kconfigFile) Expect(err).NotTo(HaveOccurred()) // Wait for the apiserver to be available. @@ -147,11 +141,11 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { AfterEach(func() { _ = c.Close() - _ = os.Remove(kconfigFile.Name()) controllerManager.Stop() nodeController.Stop() apiserver.Stop() etcd.Stop() + removeKubeconfig() }) It("should create and sync hostendpoints for Calico nodes", func() { @@ -159,7 +153,7 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { Expect(err).ToNot(HaveOccurred()) // Run controller with auto HEP enabled - nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name()) + nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile) // Create a kubernetes node with some labels. kn := &v1.Node{ @@ -213,7 +207,7 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { }, time.Second*15, 500*time.Millisecond).Should(BeNil()) // Update the Calico node with new IPs. - Expect(testutils.UpdateCalicoNode(c, cn.Name, func(cn *libapi.Node) { + Expect(testutils.UpdateCalicoNode(c, cn.Name, func(cn *internalapi.Node) { cn.Spec.BGP.IPv4Address = "172.100.2.3" cn.Spec.BGP.IPv4IPIPTunnelAddr = "" cn.Spec.IPv4VXLANTunnelAddr = "10.10.20.1" @@ -227,8 +221,8 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { }, time.Second*15, 500*time.Millisecond).Should(BeNil()) // Update the wireguard IPs. - Expect(testutils.UpdateCalicoNode(c, cn.Name, func(cn *libapi.Node) { - cn.Spec.Wireguard = &libapi.NodeWireguardSpec{ + Expect(testutils.UpdateCalicoNode(c, cn.Name, func(cn *internalapi.Node) { + cn.Spec.Wireguard = &internalapi.NodeWireguardSpec{ InterfaceIPv4Address: "192.168.100.1", InterfaceIPv6Address: "dead:beef::100:1", } @@ -243,22 +237,22 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { // Add an internal IP and an external IP to the Addresses in the node spec. // Also add a duplicate IP and make sure it is not added. // Also add interfaces to the node spec to check the hostendpoint is updated with the interface addresses. - Expect(testutils.UpdateCalicoNode(c, cn.Name, func(cn *libapi.Node) { - cn.Spec.Addresses = []libapi.NodeAddress{ + Expect(testutils.UpdateCalicoNode(c, cn.Name, func(cn *internalapi.Node) { + cn.Spec.Addresses = []internalapi.NodeAddress{ { Address: "192.168.200.1", - Type: libapi.InternalIP, + Type: internalapi.InternalIP, }, { Address: "192.168.200.2", - Type: libapi.ExternalIP, + Type: internalapi.ExternalIP, }, { Address: "172.100.2.3", - Type: libapi.InternalIP, + Type: internalapi.InternalIP, }, } - cn.Spec.Interfaces = []libapi.NodeInterface{ + cn.Spec.Interfaces = []internalapi.NodeInterface{ { Name: "eth0", Addresses: []string{ @@ -295,7 +289,7 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { // Delete the Kubernetes node. err = k8sClient.CoreV1().Nodes().Delete(context.Background(), kNodeName, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) - Eventually(func() *libapi.Node { + Eventually(func() *internalapi.Node { node, _ := c.Nodes().Get(context.Background(), cNodeName, options.GetOptions{}) return node }, time.Second*2, 500*time.Millisecond).Should(BeNil()) @@ -310,7 +304,7 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { Expect(err).ToNot(HaveOccurred()) // Run controller with auto HEP enabled - nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name()) + nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile) labels := map[string]string{"calico-label": "calico-value", "calico-label2": "value2"} @@ -390,7 +384,7 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { // Run the controller now. _, err = c.KubeControllersConfiguration().Create(context.Background(), autoHepEnabledKcc, options.SetOptions{}) Expect(err).ToNot(HaveOccurred()) - nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name()) + nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile) // Expect the node label to sync. expectedNodeLabels := map[string]string{"auto": "hep"} @@ -424,7 +418,7 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { It("should delete hostendpoints when AUTO_HOST_ENDPOINTS is disabled", func() { _, err := c.KubeControllersConfiguration().Create(context.Background(), autoHepEnabledKcc, options.SetOptions{}) Expect(err).ToNot(HaveOccurred()) - nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name()) + nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile) // Create a kubernetes node with some labels. kn := &v1.Node{ @@ -468,7 +462,7 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { kcc.Spec.Controllers.Node.HostEndpoint.AutoCreate = api.Disabled _, err = c.KubeControllersConfiguration().Update(context.Background(), kcc, options.SetOptions{}) Expect(err).ToNot(HaveOccurred()) - nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name()) + nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile) // Expect the hostendpoint for the node to be deleted. Eventually(func() error { return testutils.ExpectHostendpointDeleted(c, expectedHepName) }, @@ -478,7 +472,7 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { It("should delete default and keep template hostendpoints when createDefaultHostEndpoints is disabled", func() { _, err := c.KubeControllersConfiguration().Create(context.Background(), autoHepTemplateKcc, options.SetOptions{}) Expect(err).ToNot(HaveOccurred()) - nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name()) + nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile) cn := calicoNode(cNodeName, "", map[string]string{"calico-label": "calico-value"}) _, err = c.Nodes().Create(context.Background(), cn, options.SetOptions{}) @@ -514,7 +508,7 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { kcc.Spec.Controllers.Node.HostEndpoint.CreateDefaultHostEndpoint = api.DefaultHostEndpointsDisabled _, err = c.KubeControllersConfiguration().Update(context.Background(), kcc, options.SetOptions{}) Expect(err).ToNot(HaveOccurred()) - nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name()) + nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile) // Expect the default hostendpoint for the node to be deleted. Eventually(func() error { return testutils.ExpectHostendpointDeleted(c, expectedDefaultHepName) }, @@ -529,7 +523,7 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { It("should update host endpoint labels when node labels are updated", func() { _, err := c.KubeControllersConfiguration().Create(context.Background(), autoHepTemplateKcc, options.SetOptions{}) Expect(err).ToNot(HaveOccurred()) - nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name()) + nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile) cn := calicoNode(cNodeName, "", map[string]string{"calico-label": "calico-value"}) cn, err = c.Nodes().Create(context.Background(), cn, options.SetOptions{}) @@ -588,7 +582,7 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { It("should update host endpoint name when template name is updated", func() { _, err := c.KubeControllersConfiguration().Create(context.Background(), autoHepTemplateKcc, options.SetOptions{}) Expect(err).ToNot(HaveOccurred()) - nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name()) + nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile) cn := calicoNode(cNodeName, "", map[string]string{"calico-label": "calico-value"}) _, err = c.Nodes().Create(context.Background(), cn, options.SetOptions{}) @@ -630,7 +624,7 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { } _, err = c.KubeControllersConfiguration().Update(context.Background(), kcc, options.SetOptions{}) Expect(err).ToNot(HaveOccurred()) - nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name()) + nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile) // Expect default hostendpoint to be unchanged Eventually(func() error { @@ -647,7 +641,7 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { It("should update template hostendpoints when template is updated", func() { _, err := c.KubeControllersConfiguration().Create(context.Background(), autoHepTemplateKcc, options.SetOptions{}) Expect(err).ToNot(HaveOccurred()) - nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name()) + nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile) node1Name := "node1" node2Name := "node2" @@ -727,7 +721,7 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { } _, err = c.KubeControllersConfiguration().Update(context.Background(), kcc, options.SetOptions{}) Expect(err).ToNot(HaveOccurred()) - nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name()) + nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile) // Expect the default hostendpoints to be present Eventually(func() error { @@ -772,7 +766,7 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { kcc.Spec.Controllers.Node.HostEndpoint.AutoCreate = api.Disabled _, err = c.KubeControllersConfiguration().Update(context.Background(), kcc, options.SetOptions{}) Expect(err).ToNot(HaveOccurred()) - nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name()) + nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile) // Expect default hostendpoints to be deleted Eventually(func() error { return testutils.ExpectHostendpointDeleted(c, expectedDefaultHepName1) }, @@ -790,7 +784,7 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { It("should sync auto host endpoint if it has been updated by something other than kube controller", func() { _, err := c.KubeControllersConfiguration().Create(context.Background(), autoHepTemplateKcc, options.SetOptions{}) Expect(err).ToNot(HaveOccurred()) - nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name()) + nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile) cn := calicoNode(cNodeName, "", map[string]string{"calico-label": "calico-value"}) _, err = c.Nodes().Create(context.Background(), cn, options.SetOptions{}) @@ -865,13 +859,13 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { It("should omit invalid node IP from generated autohep", func() { _, err := c.KubeControllersConfiguration().Create(context.Background(), autoHepTemplateKcc, options.SetOptions{}) Expect(err).ToNot(HaveOccurred()) - nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name()) + nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile) - cn := libapi.NewNode() + cn := internalapi.NewNode() cn.Name = "node" - cn.Spec = libapi.NodeSpec{ - BGP: &libapi.NodeBGPSpec{ + cn.Spec = internalapi.NodeSpec{ + BGP: &internalapi.NodeBGPSpec{ IPv4Address: "172.16.1.1/24", IPv6Address: "", // invalid ip format for hostEndpoint.InterfaceIPs }, @@ -893,7 +887,7 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { It("should properly hash hostendpoint name", func() { _, err := c.KubeControllersConfiguration().Create(context.Background(), autoHepTemplateKcc, options.SetOptions{}) Expect(err).ToNot(HaveOccurred()) - nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name()) + nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile) cn := calicoNode(cNodeName, "", map[string]string{"calico-label": "calico-value"}) _, err = c.Nodes().Create(context.Background(), cn, options.SetOptions{}) @@ -924,7 +918,7 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { } _, err = c.KubeControllersConfiguration().Update(context.Background(), kcc, options.SetOptions{}) Expect(err).ToNot(HaveOccurred()) - nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name()) + nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile) // Expect template hostendpoint to have new hashed name expectedTemplateHepName = "2vhzifmgzoee1hgruafve8iewjr1cabezq3o51myej0-auto-hep" @@ -933,19 +927,19 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { }, time.Second*15, 500*time.Millisecond).Should(BeNil()) }) - It("should create valid hep for template with interfaceSelector", func() { + It("should create valid hep for template with interfacePattern", func() { _, err := c.KubeControllersConfiguration().Create(context.Background(), autoHepInterfaceTemplateKcc, options.SetOptions{}) Expect(err).ToNot(HaveOccurred()) - nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name()) + nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile) - cn := libapi.NewNode() + cn := internalapi.NewNode() cn.Name = "node" - cn.Spec = libapi.NodeSpec{ - BGP: &libapi.NodeBGPSpec{ + cn.Spec = internalapi.NodeSpec{ + BGP: &internalapi.NodeBGPSpec{ IPv4Address: "172.16.1.1/24", }, - Interfaces: []libapi.NodeInterface{ + Interfaces: []internalapi.NodeInterface{ { Name: "eth0", Addresses: []string{"5.5.5.5"}, @@ -976,20 +970,20 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { Expect(err).ToNot(HaveOccurred()) Expect(len(heps.Items)).To(Equal(1)) - // Update the template with InterfaceSelector that should result in multiple host endpoints being created. + // Update the template with InterfacePattern that should result in multiple host endpoints being created. nodeController.Stop() kcc, err := c.KubeControllersConfiguration().Get(context.Background(), "default", options.GetOptions{}) Expect(err).ToNot(HaveOccurred()) kcc.Spec.Controllers.Node.HostEndpoint.Templates = []api.Template{ { - GenerateName: "template", - InterfaceSelector: "eth0|eth1", - Labels: map[string]string{"template-label": "template-value"}, + GenerateName: "template", + InterfacePattern: "eth0|eth1", + Labels: map[string]string{"template-label": "template-value"}, }, } _, err = c.KubeControllersConfiguration().Update(context.Background(), kcc, options.SetOptions{}) Expect(err).ToNot(HaveOccurred()) - nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name()) + nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile) // Expect the eth0 host endpoint to be unchanged Eventually(func() error { @@ -1019,15 +1013,15 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { Expect(err).ToNot(HaveOccurred()) kcc.Spec.Controllers.Node.HostEndpoint.Templates = []api.Template{ { - GenerateName: "template", - InterfaceSelector: "eth0|eth1", - InterfaceCIDRs: []string{"172.16.1.1/24"}, - Labels: map[string]string{"template-label": "template-value"}, + GenerateName: "template", + InterfacePattern: "eth0|eth1", + InterfaceCIDRs: []string{"172.16.1.1/24"}, + Labels: map[string]string{"template-label": "template-value"}, }, } _, err = c.KubeControllersConfiguration().Update(context.Background(), kcc, options.SetOptions{}) Expect(err).ToNot(HaveOccurred()) - nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name()) + nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile) expectedTemplateIPs = []string{"172.16.1.1", "5.5.5.5"} Eventually(func() error { @@ -1044,20 +1038,20 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { Expect(err).ToNot(HaveOccurred()) Expect(len(heps.Items)).To(Equal(2)) - // Update the template InterfaceSelector which will result in eth0 host endpoint to be deleted + // Update the template InterfacePattern which will result in eth0 host endpoint to be deleted nodeController.Stop() kcc, err = c.KubeControllersConfiguration().Get(context.Background(), "default", options.GetOptions{}) Expect(err).ToNot(HaveOccurred()) kcc.Spec.Controllers.Node.HostEndpoint.Templates = []api.Template{ { - GenerateName: "template", - InterfaceSelector: "eth1", - Labels: map[string]string{"template-label": "template-value"}, + GenerateName: "template", + InterfacePattern: "eth1", + Labels: map[string]string{"template-label": "template-value"}, }, } _, err = c.KubeControllersConfiguration().Update(context.Background(), kcc, options.SetOptions{}) Expect(err).ToNot(HaveOccurred()) - nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name()) + nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile) // Expect the eth0 host endpoint to be deleted Eventually(func() error { return testutils.ExpectHostendpointDeleted(c, expectedTemplateHepName) }, @@ -1077,17 +1071,15 @@ var _ = Describe("Auto Hostendpoint FV tests", func() { }) }) -func calicoNode(name string, k8sNodeName string, labels map[string]string) *libapi.Node { +func calicoNode(name string, k8sNodeName string, labels map[string]string) *internalapi.Node { // Create a Calico node with a reference to it. - node := libapi.NewNode() + node := internalapi.NewNode() node.Name = name node.Labels = make(map[string]string) - for k, v := range labels { - node.Labels[k] = v - } + maps.Copy(node.Labels, labels) - node.Spec = libapi.NodeSpec{ - BGP: &libapi.NodeBGPSpec{ + node.Spec = internalapi.NodeSpec{ + BGP: &internalapi.NodeBGPSpec{ IPv4Address: "172.16.1.1/24", IPv6Address: "fe80::1", IPv4IPIPTunnelAddr: "192.168.100.1", @@ -1096,7 +1088,7 @@ func calicoNode(name string, k8sNodeName string, labels map[string]string) *liba // Add in the orchRef if a k8s node was provided. if k8sNodeName != "" { - node.Spec.OrchRefs = []libapi.OrchRef{{NodeName: k8sNodeName, Orchestrator: "k8s"}} + node.Spec.OrchRefs = []internalapi.OrchRef{{NodeName: k8sNodeName, Orchestrator: "k8s"}} } return node } diff --git a/kube-controllers/pkg/controllers/node/controller.go b/kube-controllers/pkg/controllers/node/controller.go index 71ba4d8c8b2..590716a0fde 100644 --- a/kube-controllers/pkg/controllers/node/controller.go +++ b/kube-controllers/pkg/controllers/node/controller.go @@ -27,7 +27,7 @@ import ( "github.com/projectcalico/calico/kube-controllers/pkg/config" "github.com/projectcalico/calico/kube-controllers/pkg/controllers/controller" "github.com/projectcalico/calico/kube-controllers/pkg/controllers/utils" - api "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" ) @@ -102,7 +102,7 @@ func NewNodeController(ctx context.Context, // Setup event handlers for nodes and pods learned through the // respective informers. nodeHandlers := cache.ResourceEventHandlerFuncs{ - DeleteFunc: func(obj interface{}) { + DeleteFunc: func(obj any) { // Call all of the registered node deletion funcs. for _, f := range nodeDeletionFuncs { f(obj.(*v1.Node)) @@ -110,7 +110,7 @@ func NewNodeController(ctx context.Context, }, } podHandlers := cache.ResourceEventHandlerFuncs{ - DeleteFunc: func(obj interface{}) { + DeleteFunc: func(obj any) { // Call all of the registered pod deletion funcs. for _, f := range podDeletionFuncs { f(obj.(*v1.Pod)) @@ -147,7 +147,7 @@ func NewNodeController(ctx context.Context, } // getK8sNodeName is a helper method that searches a calicoNode for its kubernetes nodeRef. -func getK8sNodeName(calicoNode api.Node) (string, error) { +func getK8sNodeName(calicoNode internalapi.Node) (string, error) { for _, orchRef := range calicoNode.Spec.OrchRefs { if orchRef.Orchestrator == "k8s" { if orchRef.NodeName == "" { @@ -196,7 +196,7 @@ func (c *NodeController) Run(stopCh chan struct{}) { // kick puts an item on the channel in non-blocking write. This means if there // is already something pending, it has no effect. This allows us to coalesce // multiple requests into a single pending request. -func kick(c chan<- interface{}) { +func kick(c chan<- any) { select { case c <- nil: // pass diff --git a/kube-controllers/pkg/controllers/node/etcd_ipam_gc_fv_test.go b/kube-controllers/pkg/controllers/node/etcd_ipam_gc_fv_test.go index ba831bc2d11..e289203006b 100644 --- a/kube-controllers/pkg/controllers/node/etcd_ipam_gc_fv_test.go +++ b/kube-controllers/pkg/controllers/node/etcd_ipam_gc_fv_test.go @@ -16,10 +16,9 @@ package node_test import ( "context" - "os" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" v1 "k8s.io/api/core/v1" @@ -43,7 +42,8 @@ var _ = Describe("kube-controllers IPAM FV tests (etcd mode)", func() { c client.Interface k8sClient *kubernetes.Clientset controllerManager *containers.Container - kconfigFile *os.File + kconfigFile string + cleanupKubeconfig func() ) const kNodeName = "k8snodename" @@ -58,18 +58,11 @@ var _ = Describe("kube-controllers IPAM FV tests (etcd mode)", func() { apiserver = testutils.RunK8sApiserver(etcd.IP) // Write out a kubeconfig file we can mount into the container. - var err error - kconfigFile, err = os.CreateTemp("", "ginkgo-nodecontroller") - Expect(err).NotTo(HaveOccurred()) - data := testutils.BuildKubeconfig(apiserver.IP) - _, err = kconfigFile.Write([]byte(data)) - Expect(err).NotTo(HaveOccurred()) - - // Make the kubeconfig readable by the container. - Expect(kconfigFile.Chmod(os.ModePerm)).NotTo(HaveOccurred()) + kconfigFile, cleanupKubeconfig = testutils.BuildKubeconfig(apiserver.IP) // Build a client we can use for the test. - k8sClient, err = testutils.GetK8sClient(kconfigFile.Name()) + var err error + k8sClient, err = testutils.GetK8sClient(kconfigFile) Expect(err).NotTo(HaveOccurred()) // Wait for the apiserver to be available. @@ -102,18 +95,18 @@ var _ = Describe("kube-controllers IPAM FV tests (etcd mode)", func() { Expect(err).NotTo(HaveOccurred()) _ = c.Close() - _ = os.Remove(kconfigFile.Name()) controllerManager.Stop() nodeController.Stop() apiserver.Stop() etcd.Stop() + cleanupKubeconfig() }) // This test makes sure our IPAM garbage collection properly handles when the Kubernetes node name // does not match the Calico node name in etcd. It("should properly garbage collect IP addresses for mismatched node names", func() { // Run controller. - nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name()) + nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile) // Create a kubernetes node. kn := &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: kNodeName}} @@ -161,7 +154,7 @@ var _ = Describe("kube-controllers IPAM FV tests (etcd mode)", func() { It("should never garbage collect IP addresses that do not belong to Kubernetes pods", func() { // Run controller. - nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name()) + nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile) // Use the same name for k8s and Calico node. commonNodeName := "common-node-name" @@ -228,7 +221,7 @@ var _ = Describe("kube-controllers IPAM FV tests (etcd mode)", func() { It("should garbage collect IP addresses if there is no Calico node, even if there happens to be a Kubernetes node", func() { // Run controller. - nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name()) + nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile) // Create a kubernetes node. commonNodeName := "common-node-name" @@ -260,7 +253,7 @@ var _ = Describe("kube-controllers IPAM FV tests (etcd mode)", func() { It("should garbage collect IP addresses if there is no Calico node AND no Kubernetes node", func() { // Run controller. - nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile.Name()) + nodeController = testutils.RunNodeController(apiconfig.EtcdV3, etcd.IP, kconfigFile) // Allocate an IP address on a node that doesn't exist. commonNodeName := "common-node-name" diff --git a/kube-controllers/pkg/controllers/node/fake_client.go b/kube-controllers/pkg/controllers/node/fake_client.go index f981a2d90ab..ac0a7e20cd9 100644 --- a/kube-controllers/pkg/controllers/node/fake_client.go +++ b/kube-controllers/pkg/controllers/node/fake_client.go @@ -19,7 +19,7 @@ import ( "strings" "sync" - apiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/clientv3" @@ -32,7 +32,7 @@ import ( func NewFakeCalicoClient() *FakeCalicoClient { nc := fakeNodeClient{ - nodes: make(map[string]*apiv3.Node), + nodes: make(map[string]*internalapi.Node), } ipamClient := fakeIPAMClient{ affinitiesReleased: make(map[string]bool), @@ -143,6 +143,10 @@ func (f *FakeCalicoClient) HostEndpoints() clientv3.HostEndpointInterface { panic("not implemented") } +func (f *FakeCalicoClient) LiveMigrations() clientv3.LiveMigrationInterface { + panic("not implemented") +} + // WorkloadEndpoints returns an interface for managing workload endpoint resources. func (f *FakeCalicoClient) WorkloadEndpoints() clientv3.WorkloadEndpointInterface { panic("not implemented") @@ -188,7 +192,7 @@ func (f *FakeCalicoClient) CalicoNodeStatus() clientv3.CalicoNodeStatusInterface panic("not implemented") } -func (f *FakeCalicoClient) IPAMConfig() clientv3.IPAMConfigInterface { +func (f *FakeCalicoClient) IPAMConfiguration() clientv3.IPAMConfigurationInterface { panic("not implemented") } @@ -217,10 +221,10 @@ func (f *FakeCalicoClient) Close() error { // fakeNodeClient implements the clientv3 NodeInterface for testing purposes. type fakeNodeClient struct { sync.Mutex - nodes map[string]*apiv3.Node + nodes map[string]*internalapi.Node } -func (f *fakeNodeClient) Create(ctx context.Context, res *apiv3.Node, opts options.SetOptions) (*apiv3.Node, error) { +func (f *fakeNodeClient) Create(ctx context.Context, res *internalapi.Node, opts options.SetOptions) (*internalapi.Node, error) { f.Lock() defer f.Unlock() @@ -231,15 +235,15 @@ func (f *fakeNodeClient) Create(ctx context.Context, res *apiv3.Node, opts optio return res, nil } -func (f *fakeNodeClient) Update(ctx context.Context, res *apiv3.Node, opts options.SetOptions) (*apiv3.Node, error) { +func (f *fakeNodeClient) Update(ctx context.Context, res *internalapi.Node, opts options.SetOptions) (*internalapi.Node, error) { panic("not implemented") // TODO: Implement } -func (f *fakeNodeClient) Delete(ctx context.Context, name string, opts options.DeleteOptions) (*apiv3.Node, error) { +func (f *fakeNodeClient) Delete(ctx context.Context, name string, opts options.DeleteOptions) (*internalapi.Node, error) { panic("not implemented") // TODO: Implement } -func (f *fakeNodeClient) Get(ctx context.Context, name string, opts options.GetOptions) (*apiv3.Node, error) { +func (f *fakeNodeClient) Get(ctx context.Context, name string, opts options.GetOptions) (*internalapi.Node, error) { f.Lock() defer f.Unlock() @@ -249,7 +253,7 @@ func (f *fakeNodeClient) Get(ctx context.Context, name string, opts options.GetO return f.nodes[name], nil } -func (f *fakeNodeClient) List(ctx context.Context, opts options.ListOptions) (*apiv3.NodeList, error) { +func (f *fakeNodeClient) List(ctx context.Context, opts options.ListOptions) (*internalapi.NodeList, error) { panic("not implemented") // TODO: Implement } @@ -309,9 +313,13 @@ func (f *fakeIPAMClient) ReleaseIPs(ctx context.Context, opts ...ipam.ReleaseOpt return nil, opts, nil } -// GetAssignmentAttributes returns the attributes stored with the given IP address -// upon assignment, as well as the handle used for assignment (if any). -func (f *fakeIPAMClient) GetAssignmentAttributes(ctx context.Context, addr cnet.IP) (map[string]string, *string, error) { +// GetAssignmentAttributes returns the AllocationAttribute for the given IP address. +func (f *fakeIPAMClient) GetAssignmentAttributes(ctx context.Context, addr cnet.IP) (*model.AllocationAttribute, error) { + panic("not implemented") // TODO: Implement +} + +// SetOwnerAttributes sets ActiveOwnerAttrs and/or AlternateOwnerAttrs for an IP atomically. +func (f *fakeIPAMClient) SetOwnerAttributes(ctx context.Context, ip cnet.IP, handleID string, updates *ipam.OwnerAttributeUpdates, preconditions *ipam.OwnerAttributePreconditions) error { panic("not implemented") // TODO: Implement } diff --git a/kube-controllers/pkg/controllers/node/hostendpoints.go b/kube-controllers/pkg/controllers/node/hostendpoints.go index 45da01222dd..bde140bc0eb 100644 --- a/kube-controllers/pkg/controllers/node/hostendpoints.go +++ b/kube-controllers/pkg/controllers/node/hostendpoints.go @@ -19,6 +19,7 @@ import ( "crypto/sha256" "encoding/base64" "fmt" + "maps" "net" "reflect" "regexp" @@ -34,7 +35,7 @@ import ( "github.com/projectcalico/calico/kube-controllers/pkg/config" "github.com/projectcalico/calico/kube-controllers/pkg/controllers/utils" - libapi "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" @@ -112,10 +113,10 @@ func NewAutoHEPController(cfg config.NodeControllerConfig, client client.Interfa return &autoHostEndpointController{ config: cfg, client: client, - nodeCache: make(map[string]*libapi.Node), + nodeCache: make(map[string]*internalapi.Node), nodeUpdates: make(chan string, utils.BatchUpdateSize), - syncerUpdates: make(chan interface{}, utils.BatchUpdateSize), - syncChan: make(chan interface{}, 1), + syncerUpdates: make(chan any, utils.BatchUpdateSize), + syncChan: make(chan any, 1), autoHEPTracker: hostEndpointTracker{hostEndpointsByNode: make(map[string]map[string]*api.HostEndpoint)}, } } @@ -124,10 +125,10 @@ type autoHostEndpointController struct { config config.NodeControllerConfig client client.Interface syncStatus bapi.SyncStatus - nodeCache map[string]*libapi.Node + nodeCache map[string]*internalapi.Node nodeUpdates chan string - syncerUpdates chan interface{} - syncChan chan interface{} + syncerUpdates chan any + syncChan chan any autoHEPTracker hostEndpointTracker } @@ -170,14 +171,13 @@ func (c *autoHostEndpointController) onUpdate(update bapi.Update) { switch update.Key.(type) { case model.ResourceKey: switch update.KVPair.Key.(model.ResourceKey).Kind { - case libapi.KindNode, api.KindHostEndpoint: + case internalapi.KindNode, api.KindHostEndpoint: c.syncerUpdates <- update.KVPair } } - } -func (c *autoHostEndpointController) handleUpdate(update interface{}) { +func (c *autoHostEndpointController) handleUpdate(update any) { switch update := update.(type) { case bapi.SyncStatus: c.syncStatus = update @@ -188,7 +188,7 @@ func (c *autoHostEndpointController) handleUpdate(update interface{}) { } case model.KVPair: switch update.Key.(model.ResourceKey).Kind { - case libapi.KindNode: + case internalapi.KindNode: c.handleNodeUpdate(update) case api.KindHostEndpoint: c.handleHostEndpointUpdate(update) @@ -204,7 +204,7 @@ func (c *autoHostEndpointController) handleNodeUpdate(kvp model.KVPair) { // Node deleted, we want to remove all auto HostEndpoints associated with this node delete(c.nodeCache, nodeName) } else { - node := kvp.Value.(*libapi.Node) + node := kvp.Value.(*internalapi.Node) c.nodeCache[node.Name] = node } @@ -324,7 +324,7 @@ func (c *autoHostEndpointController) syncHostEndpointsForNode(nodeName string) { if nodeSelector.Evaluate(node.Labels) { expectedIPs := c.getExpectedIPsMatchingInterfaceCIDRs(node, template) - if len(expectedIPs) == 0 && template.InterfaceSelector == "" { + if len(expectedIPs) == 0 && template.InterfacePattern == "" { // Because we do not specify interfaceName in HostEndpoint, expectedIPs should not be empty. // If expectedIPs are empty the HostEndpoint will be invalid, and we should not create it // If there is an existing HostEndpoint with this name, it will be deleted further down @@ -334,8 +334,8 @@ func (c *autoHostEndpointController) syncHostEndpointsForNode(nodeName string) { continue } - if template.InterfaceSelector == "" { - // When interfaceSelector is empty this template will always generate at most one AutoHostEndpoint + if template.InterfacePattern == "" { + // When interfacePattern is empty this template will always generate at most one AutoHostEndpoint hostEndpointName, err := generateAutoHostEndpointName(node.Name, template.GenerateName, "") if err != nil { logrus.WithError(err).Error("failed to generate host endpoint name") @@ -346,14 +346,14 @@ func (c *autoHostEndpointController) syncHostEndpointsForNode(nodeName string) { hostEndpointsMatchingNode[hostEndpointName] = true - // If there is no InterfaceSelector we only create one host endpoint, we can continue to the next template + // If there is no InterfacePattern we only create one host endpoint, we can continue to the next template continue } for _, iface := range node.Spec.Interfaces { - // If we reached here we know the template has interfaceSelector specified, we want to create a HostEndpoint for each interface that matches the regex selector. - if regexp.MustCompile(template.InterfaceSelector).MatchString(iface.Name) { - // Generate Host Endpoint for interface matching the InterfaceSelector from the template + // If we reached here we know the template has interfacePattern specified, we want to create a HostEndpoint for each interface that matches the regex selector. + if regexp.MustCompile(template.InterfacePattern).MatchString(iface.Name) { + // Generate Host Endpoint for interface matching the InterfacePattern from the template hostEndpointName, err := generateAutoHostEndpointName(node.Name, template.GenerateName, iface.Name) if err != nil { logrus.WithError(err).Error("failed to generate host endpoint name") @@ -464,7 +464,7 @@ func hashNameIfTooLong(name string) (string, error) { // mergeExpectedIPsWithInterfaceIPs merges the provided expected IPs with the IP addresses // found on the specified interface of the given node. -func (c *autoHostEndpointController) mergeExpectedIPsWithInterfaceIPs(expectedIPs []string, iface libapi.NodeInterface) []string { +func (c *autoHostEndpointController) mergeExpectedIPsWithInterfaceIPs(expectedIPs []string, iface internalapi.NodeInterface) []string { for _, addr := range iface.Addresses { if !slices.Contains(expectedIPs, addr) { expectedIPs = append(expectedIPs, addr) @@ -475,14 +475,14 @@ func (c *autoHostEndpointController) mergeExpectedIPsWithInterfaceIPs(expectedIP } // getExpectedIPsMatchingInterfaceCIDRs finds the matching node IPs to the CIDRs specified in the template -func (c *autoHostEndpointController) getExpectedIPsMatchingInterfaceCIDRs(node *libapi.Node, template config.AutoHostEndpointTemplate) []string { +func (c *autoHostEndpointController) getExpectedIPsMatchingInterfaceCIDRs(node *internalapi.Node, template config.AutoHostEndpointTemplate) []string { filteredExpectedIPs := []string{} expectedIPs := c.getExpectedIPs(node) for _, ipAddrs := range expectedIPs { ip := net.ParseIP(ipAddrs) for _, interfaceSelectorCIDR := range template.InterfaceCIDRs { - _, cidr, err := net.ParseCIDR(interfaceSelectorCIDR) + _, cidr, err := net.ParseCIDR(string(interfaceSelectorCIDR)) if err != nil { logrus.WithError(err).Errorf("failed to parse interface selector cidr %s", interfaceSelectorCIDR) return nil @@ -499,7 +499,7 @@ func (c *autoHostEndpointController) getExpectedIPsMatchingInterfaceCIDRs(node * } // getExpectedIPs returns all the known IPs on the node resource -func (c *autoHostEndpointController) getExpectedIPs(node *libapi.Node) []string { +func (c *autoHostEndpointController) getExpectedIPs(node *internalapi.Node) []string { expectedIPs := []string{} ipMap := make(map[string]struct{}) // used to avoid adding duplicates to expectedIPs if node.Spec.BGP != nil { @@ -537,7 +537,7 @@ func (c *autoHostEndpointController) getExpectedIPs(node *libapi.Node) []string } for _, addr := range node.Spec.Addresses { // Add internal IPs only - if addr.Type == libapi.InternalIP { + if addr.Type == internalapi.InternalIP { if _, ok := ipMap[addr.Address]; !ok { ipMap[addr.Address] = struct{}{} expectedIPs = append(expectedIPs, addr.Address) @@ -569,11 +569,9 @@ func (c *autoHostEndpointController) getExpectedIPs(node *libapi.Node) []string } // generateAutoHostEndpoint returns a HostEndpoint created based on the specific parameters -func (c *autoHostEndpointController) generateAutoHostEndpoint(node *libapi.Node, templateLabels map[string]string, hepName string, expectedIPs []string, interfaceName string) *api.HostEndpoint { +func (c *autoHostEndpointController) generateAutoHostEndpoint(node *internalapi.Node, templateLabels map[string]string, hepName string, expectedIPs []string, interfaceName string) *api.HostEndpoint { hepLabels := make(map[string]string) - for k, v := range node.Labels { - hepLabels[k] = v - } + maps.Copy(hepLabels, node.Labels) for k, v := range templateLabels { if _, ok := hepLabels[k]; ok { f := logrus.Fields{"key": k, "nodeVal": hepLabels[k], "userVal": v} diff --git a/kube-controllers/pkg/controllers/node/ipam.go b/kube-controllers/pkg/controllers/node/ipam.go index 28ee029d4e1..996e70902fb 100644 --- a/kube-controllers/pkg/controllers/node/ipam.go +++ b/kube-controllers/pkg/controllers/node/ipam.go @@ -1,4 +1,4 @@ -// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2025-2026 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -37,7 +37,7 @@ import ( "github.com/projectcalico/calico/kube-controllers/pkg/config" "github.com/projectcalico/calico/kube-controllers/pkg/controllers/flannelmigration" "github.com/projectcalico/calico/kube-controllers/pkg/controllers/utils" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" @@ -123,7 +123,7 @@ func NewIPAMController(cfg config.NodeControllerConfig, c client.Interface, cs k leakGracePeriod = &cfg.LeakGracePeriod.Duration } - syncChan := make(chan interface{}, 1) + syncChan := make(chan any, 1) // Create a rate limited that compares two distinct limiters and uses the max. This rate limiter is used // only to control the retry rate of whole IPAM sync executions. @@ -159,7 +159,7 @@ func NewIPAMController(cfg config.NodeControllerConfig, c client.Interface, cs k podDeletionChan: make(chan *v1.Pod, utils.BatchUpdateSize), // Buffered channels for potentially bursty channels. - syncerUpdates: make(chan interface{}, utils.BatchUpdateSize), + syncerUpdates: make(chan any, utils.BatchUpdateSize), allBlocks: make(map[string]model.KVPair), allocationsByBlock: make(map[string]map[string]*allocation), @@ -198,10 +198,10 @@ type IPAMController struct { kubernetesNodesByCalicoName map[string]string // syncChan triggers processing in response to an update. - syncChan chan interface{} + syncChan chan any // For update / deletion events from the syncer. - syncerUpdates chan interface{} + syncerUpdates chan any // Raw block storage, keyed by CIDR. allBlocks map[string]model.KVPair @@ -270,7 +270,7 @@ func (c *IPAMController) onUpdate(update bapi.Update) { switch update.Key.(type) { case model.ResourceKey: switch update.KVPair.Key.(model.ResourceKey).Kind { - case libapiv3.KindNode, apiv3.KindIPPool, apiv3.KindClusterInformation: + case internalapi.KindNode, apiv3.KindIPPool, apiv3.KindClusterInformation: c.syncerUpdates <- update.KVPair } case model.BlockKey: @@ -370,7 +370,7 @@ func (c *IPAMController) acceptScheduleRequests(stopCh <-chan struct{}) { // handleUpdate fans out proper handling of the update depending on the // information in the update. -func (c *IPAMController) handleUpdate(upd interface{}) { +func (c *IPAMController) handleUpdate(upd any) { switch upd := upd.(type) { case bapi.SyncStatus: c.syncStatus = upd @@ -384,7 +384,7 @@ func (c *IPAMController) handleUpdate(upd interface{}) { switch upd.Key.(type) { case model.ResourceKey: switch upd.Key.(model.ResourceKey).Kind { - case libapiv3.KindNode: + case internalapi.KindNode: c.handleNodeUpdate(upd) return case apiv3.KindIPPool: @@ -414,7 +414,7 @@ func (c *IPAMController) handleBlockUpdate(kvp model.KVPair) { // handleNodeUpdate wraps up the logic to execute when receiving a node update. func (c *IPAMController) handleNodeUpdate(kvp model.KVPair) { if kvp.Value != nil { - n := kvp.Value.(*libapiv3.Node) + n := kvp.Value.(*internalapi.Node) kn, err := getK8sNodeName(*n) if err != nil { log.WithError(err).Info("Unable to get corresponding k8s node name") @@ -439,7 +439,10 @@ func (c *IPAMController) handleNodeUpdate(kvp model.KVPair) { } func (c *IPAMController) handlePoolUpdate(kvp model.KVPair) { - if kvp.Value != nil { + if kvp.Value != nil && kvp.Value.(*apiv3.IPPool).GetDeletionTimestamp() == nil { + // If the deletion timestamp is set, treat this as a deletion. There may be a window between + // deletion of the IP pool, and finalization completing. During this time, we treat the pool + // as though it has been deleted. pool := kvp.Value.(*apiv3.IPPool) c.onPoolUpdated(pool) } else { @@ -470,8 +473,8 @@ func (c *IPAMController) onBlockUpdated(kvp model.KVPair) { // release their affinity if needed. var n string if b.Affinity != nil { - if strings.HasPrefix(*b.Affinity, "host:") { - n = strings.TrimPrefix(*b.Affinity, "host:") + if after, ok := strings.CutPrefix(*b.Affinity, "host:"); ok { + n = after c.nodesByBlock[blockCIDR] = n if _, ok := c.blocksByNode[n]; !ok { c.blocksByNode[n] = map[string]bool{} @@ -499,15 +502,15 @@ func (c *IPAMController) onBlockUpdated(kvp model.KVPair) { // If there is no handle, then skip this IP. We need the handle // in order to release the IP below. - if attr.AttrPrimary == nil { + if attr.HandleID == nil { continue } - handle := *attr.AttrPrimary + handle := *attr.HandleID alloc := allocation{ ip: ordinalToIP(b, ord).String(), handle: handle, - attrs: attr.AttrSecondary, + attrs: attr.ActiveOwnerAttrs, sequenceNumber: b.GetSequenceNumberForOrdinal(ord), block: blockCIDR, } @@ -1020,7 +1023,7 @@ func (c *IPAMController) allocationIsValid(a *allocation, preferCache bool) bool logc.Warn("Pod converted to nil WorkloadEndpoint") continue } - wep := kvp.Value.(*libapiv3.WorkloadEndpoint) + wep := kvp.Value.(*internalapi.WorkloadEndpoint) for _, nw := range wep.Spec.IPNetworks { ip, _, err := net.ParseCIDR(nw) if err != nil { diff --git a/kube-controllers/pkg/controllers/node/ipam_test.go b/kube-controllers/pkg/controllers/node/ipam_test.go index f608daa479b..b62851ddc51 100644 --- a/kube-controllers/pkg/controllers/node/ipam_test.go +++ b/kube-controllers/pkg/controllers/node/ipam_test.go @@ -19,7 +19,7 @@ import ( "math/big" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/sirupsen/logrus" @@ -32,7 +32,7 @@ import ( "github.com/projectcalico/calico/kube-controllers/pkg/config" "github.com/projectcalico/calico/kube-controllers/pkg/converter" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" @@ -136,11 +136,11 @@ var _ = Describe("IPAM controller UTs", func() { pods = make(chan *v1.Pod, 1) _, err := podInformer.AddEventHandler(&cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { + AddFunc: func(obj any) { pod := obj.(*v1.Pod) pods <- pod }, - DeleteFunc: func(obj interface{}) { + DeleteFunc: func(obj any) { pod := obj.(*v1.Pod) pods <- pod }, @@ -148,11 +148,11 @@ var _ = Describe("IPAM controller UTs", func() { Expect(err).NotTo(HaveOccurred()) nodes = make(chan *v1.Node, 1) _, err = nodeInformer.AddEventHandler(&cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { + AddFunc: func(obj any) { node := obj.(*v1.Node) nodes <- node }, - DeleteFunc: func(obj interface{}) { + DeleteFunc: func(obj any) { node := obj.(*v1.Node) nodes <- node }, @@ -184,10 +184,10 @@ var _ = Describe("IPAM controller UTs", func() { c.Start(stopChan) calicoNodeName := "cname" - key := model.ResourceKey{Name: calicoNodeName, Kind: libapiv3.KindNode} - n := libapiv3.Node{} + key := model.ResourceKey{Name: calicoNodeName, Kind: internalapi.KindNode} + n := internalapi.Node{} n.Name = calicoNodeName - n.Spec.OrchRefs = []libapiv3.OrchRef{ + n.Spec.OrchRefs = []internalapi.OrchRef{ {NodeName: "kname", Orchestrator: apiv3.OrchestratorKubernetes}, } kvp := model.KVPair{ @@ -302,8 +302,8 @@ var _ = Describe("IPAM controller UTs", func() { b.Allocations[0] = &idx b.Unallocated = []int{1, 2, 3} b.Attributes = append(b.Attributes, model.AllocationAttribute{ - AttrPrimary: &handle, - AttrSecondary: map[string]string{ + HandleID: &handle, + ActiveOwnerAttrs: map[string]string{ ipam.AttributeNode: "cnode", ipam.AttributePod: "test-pod", ipam.AttributeNamespace: "test-namespace", @@ -314,7 +314,7 @@ var _ = Describe("IPAM controller UTs", func() { expectedAllocation := &allocation{ ip: "10.0.0.0", handle: handle, - attrs: b.Attributes[0].AttrSecondary, + attrs: b.Attributes[0].ActiveOwnerAttrs, block: "10.0.0.0/30", } @@ -602,8 +602,8 @@ var _ = Describe("IPAM controller UTs", func() { Unallocated: []int{1, 2, 3}, Attributes: []model.AllocationAttribute{ { - AttrPrimary: &handle, - AttrSecondary: map[string]string{ + HandleID: &handle, + ActiveOwnerAttrs: map[string]string{ ipam.AttributeNode: "cnode", ipam.AttributePod: "test-pod", ipam.AttributeNamespace: "test-namespace", @@ -692,9 +692,9 @@ var _ = Describe("IPAM controller UTs", func() { It("should clean up leaked IP addresses", func() { // Add a new block with one allocation - on a valid node but no corresponding pod. - n := libapiv3.Node{} + n := internalapi.Node{} n.Name = "cnode" - n.Spec.OrchRefs = []libapiv3.OrchRef{{NodeName: "kname", Orchestrator: apiv3.OrchestratorKubernetes}} + n.Spec.OrchRefs = []internalapi.OrchRef{{NodeName: "kname", Orchestrator: apiv3.OrchestratorKubernetes}} _, err := cli.Nodes().Create(context.TODO(), &n, options.SetOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -721,8 +721,8 @@ var _ = Describe("IPAM controller UTs", func() { Unallocated: []int{1, 2, 3}, Attributes: []model.AllocationAttribute{ { - AttrPrimary: &handle, - AttrSecondary: map[string]string{ + HandleID: &handle, + ActiveOwnerAttrs: map[string]string{ ipam.AttributeNode: "cnode", ipam.AttributePod: "test-pod", ipam.AttributeNamespace: "test-namespace", @@ -764,9 +764,9 @@ var _ = Describe("IPAM controller UTs", func() { It("should handle blocks losing their affinity", func() { // Create Calico and k8s nodes for the test. - n := libapiv3.Node{} + n := internalapi.Node{} n.Name = "cnode" - n.Spec.OrchRefs = []libapiv3.OrchRef{{NodeName: "kname", Orchestrator: apiv3.OrchestratorKubernetes}} + n.Spec.OrchRefs = []internalapi.OrchRef{{NodeName: "kname", Orchestrator: apiv3.OrchestratorKubernetes}} _, err := cli.Nodes().Create(context.TODO(), &n, options.SetOptions{}) Expect(err).NotTo(HaveOccurred()) kn := v1.Node{} @@ -802,8 +802,8 @@ var _ = Describe("IPAM controller UTs", func() { Unallocated: []int{1, 2, 3}, Attributes: []model.AllocationAttribute{ { - AttrPrimary: &handle, - AttrSecondary: map[string]string{ + HandleID: &handle, + ActiveOwnerAttrs: map[string]string{ ipam.AttributeNode: "cnode", ipam.AttributePod: pod.Name, ipam.AttributeNamespace: pod.Namespace, @@ -844,8 +844,8 @@ var _ = Describe("IPAM controller UTs", func() { Unallocated: []int{1, 2, 3}, Attributes: []model.AllocationAttribute{ { - AttrPrimary: &handle, - AttrSecondary: map[string]string{ + HandleID: &handle, + ActiveOwnerAttrs: map[string]string{ ipam.AttributeNode: "cnode", ipam.AttributePod: pod.Name, ipam.AttributeNamespace: pod.Namespace, @@ -928,9 +928,9 @@ var _ = Describe("IPAM controller UTs", func() { It("should NOT clean up IPs if another valid IP shares the handle", func() { // Create Calico and k8s nodes for the test. - n := libapiv3.Node{} + n := internalapi.Node{} n.Name = "cnode" - n.Spec.OrchRefs = []libapiv3.OrchRef{{NodeName: "kname", Orchestrator: apiv3.OrchestratorKubernetes}} + n.Spec.OrchRefs = []internalapi.OrchRef{{NodeName: "kname", Orchestrator: apiv3.OrchestratorKubernetes}} _, err := cli.Nodes().Create(context.TODO(), &n, options.SetOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -969,8 +969,8 @@ var _ = Describe("IPAM controller UTs", func() { Unallocated: []int{1, 2, 3}, Attributes: []model.AllocationAttribute{ { - AttrPrimary: &handle, - AttrSecondary: map[string]string{ + HandleID: &handle, + ActiveOwnerAttrs: map[string]string{ ipam.AttributeNode: "cnode", ipam.AttributePod: pod.Name, ipam.AttributeNamespace: pod.Namespace, @@ -995,8 +995,8 @@ var _ = Describe("IPAM controller UTs", func() { Unallocated: []int{1, 2, 3}, Attributes: []model.AllocationAttribute{ { - AttrPrimary: &handle, - AttrSecondary: map[string]string{ + HandleID: &handle, + ActiveOwnerAttrs: map[string]string{ ipam.AttributeNode: "cnode", ipam.AttributePod: pod.Name, ipam.AttributeNamespace: pod.Namespace, @@ -1104,9 +1104,9 @@ var _ = Describe("IPAM controller UTs", func() { c.config.LeakGracePeriod = &metav1.Duration{Duration: 0 * time.Second} // Add a new block with one allocation - on a valid node but no corresponding pod. - n := libapiv3.Node{} + n := internalapi.Node{} n.Name = "cnode" - n.Spec.OrchRefs = []libapiv3.OrchRef{{NodeName: "kname", Orchestrator: apiv3.OrchestratorKubernetes}} + n.Spec.OrchRefs = []internalapi.OrchRef{{NodeName: "kname", Orchestrator: apiv3.OrchestratorKubernetes}} _, err := cli.Nodes().Create(context.TODO(), &n, options.SetOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -1133,8 +1133,8 @@ var _ = Describe("IPAM controller UTs", func() { Unallocated: []int{1, 2, 3}, Attributes: []model.AllocationAttribute{ { - AttrPrimary: &handle, - AttrSecondary: map[string]string{ + HandleID: &handle, + ActiveOwnerAttrs: map[string]string{ ipam.AttributeNode: "cnode", ipam.AttributePod: "test-pod", ipam.AttributeNamespace: "test-namespace", @@ -1176,9 +1176,9 @@ var _ = Describe("IPAM controller UTs", func() { It("should clean up empty blocks", func() { // Create Calico and k8s nodes for the test. - n := libapiv3.Node{} + n := internalapi.Node{} n.Name = "cnode" - n.Spec.OrchRefs = []libapiv3.OrchRef{{NodeName: "kname", Orchestrator: apiv3.OrchestratorKubernetes}} + n.Spec.OrchRefs = []internalapi.OrchRef{{NodeName: "kname", Orchestrator: apiv3.OrchestratorKubernetes}} _, err := cli.Nodes().Create(context.TODO(), &n, options.SetOptions{}) Expect(err).NotTo(HaveOccurred()) kn := v1.Node{} @@ -1214,8 +1214,8 @@ var _ = Describe("IPAM controller UTs", func() { Unallocated: []int{1, 2, 3}, Attributes: []model.AllocationAttribute{ { - AttrPrimary: &handle, - AttrSecondary: map[string]string{ + HandleID: &handle, + ActiveOwnerAttrs: map[string]string{ ipam.AttributeNode: "cnode", ipam.AttributePod: pod.Name, ipam.AttributeNamespace: pod.Namespace, @@ -1285,9 +1285,9 @@ var _ = Describe("IPAM controller UTs", func() { It("should clean up empty blocks even if the node is full", func() { // Create Calico and k8s nodes for the test. - n := libapiv3.Node{} + n := internalapi.Node{} n.Name = "cnode" - n.Spec.OrchRefs = []libapiv3.OrchRef{{NodeName: "kname", Orchestrator: apiv3.OrchestratorKubernetes}} + n.Spec.OrchRefs = []internalapi.OrchRef{{NodeName: "kname", Orchestrator: apiv3.OrchestratorKubernetes}} _, err := cli.Nodes().Create(context.TODO(), &n, options.SetOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -1324,8 +1324,8 @@ var _ = Describe("IPAM controller UTs", func() { Unallocated: []int{}, Attributes: []model.AllocationAttribute{ { - AttrPrimary: &handle, - AttrSecondary: map[string]string{ + HandleID: &handle, + ActiveOwnerAttrs: map[string]string{ ipam.AttributeNode: "cnode", ipam.AttributePod: pod.Name, ipam.AttributeNamespace: pod.Namespace, @@ -1398,9 +1398,9 @@ var _ = Describe("IPAM controller UTs", func() { It("should NOT clean up all blocks assigned to a node", func() { // Create Calico and k8s nodes for the test. - n := libapiv3.Node{} + n := internalapi.Node{} n.Name = "cnode" - n.Spec.OrchRefs = []libapiv3.OrchRef{{NodeName: "kname", Orchestrator: apiv3.OrchestratorKubernetes}} + n.Spec.OrchRefs = []internalapi.OrchRef{{NodeName: "kname", Orchestrator: apiv3.OrchestratorKubernetes}} _, err := cli.Nodes().Create(context.TODO(), &n, options.SetOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -1425,9 +1425,9 @@ var _ = Describe("IPAM controller UTs", func() { c.Start(stopChan) // Add 5 empty blocks to the node. - for i := 0; i < 5; i++ { + for i := range 5 { unallocated := make([]int, 64) - for i := 0; i < len(unallocated); i++ { + for i := range unallocated { unallocated[i] = i } cidr := net.MustParseCIDR(fmt.Sprintf("10.0.%d.0/24", i)) @@ -1487,9 +1487,9 @@ var _ = Describe("IPAM controller UTs", func() { // Reference: https://github.com/projectcalico/calico/issues/7987 It("should clean up small IPAM blocks", func() { // Create Calico and k8s nodes for the test. - n := libapiv3.Node{} + n := internalapi.Node{} n.Name = "cnode" - n.Spec.OrchRefs = []libapiv3.OrchRef{{NodeName: "kname", Orchestrator: apiv3.OrchestratorKubernetes}} + n.Spec.OrchRefs = []internalapi.OrchRef{{NodeName: "kname", Orchestrator: apiv3.OrchestratorKubernetes}} _, err := cli.Nodes().Create(context.TODO(), &n, options.SetOptions{}) Expect(err).NotTo(HaveOccurred()) kn := v1.Node{} @@ -1504,9 +1504,9 @@ var _ = Describe("IPAM controller UTs", func() { // Add small, empty blocks to the node. // Use a block size of 31, resulting in 2 allocations per block. - for i := 0; i < 5; i++ { + for i := range 5 { unallocated := make([]int, 64) - for i := 0; i < len(unallocated); i++ { + for i := range unallocated { unallocated[i] = i } cidr := net.MustParseCIDR(fmt.Sprintf("10.0.%d.0/31", i)) @@ -1618,9 +1618,9 @@ var _ = Describe("IPAM controller UTs", func() { // Create Calico and k8s nodes for the test. for _, name := range []string{"node1", "node2"} { - n := libapiv3.Node{} + n := internalapi.Node{} n.Name = name - n.Spec.OrchRefs = []libapiv3.OrchRef{{NodeName: name, Orchestrator: apiv3.OrchestratorKubernetes}} + n.Spec.OrchRefs = []internalapi.OrchRef{{NodeName: name, Orchestrator: apiv3.OrchestratorKubernetes}} _, err := cli.Nodes().Create(context.TODO(), &n, options.SetOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -1719,10 +1719,10 @@ var _ = Describe("IPAM controller UTs", func() { c.Start(stopChan) // Create 5k nodes. - for i := 0; i < numNodes; i++ { - n := libapiv3.Node{} + for i := range numNodes { + n := internalapi.Node{} n.Name = fmt.Sprintf("node%d", i) - n.Spec.OrchRefs = []libapiv3.OrchRef{{NodeName: n.Name, Orchestrator: apiv3.OrchestratorKubernetes}} + n.Spec.OrchRefs = []internalapi.OrchRef{{NodeName: n.Name, Orchestrator: apiv3.OrchestratorKubernetes}} _, err := cli.Nodes().Create(context.TODO(), &n, options.SetOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -1736,7 +1736,7 @@ var _ = Describe("IPAM controller UTs", func() { // For each node, create 30 pods and assign them IPs. Pre-create the blocks, and then // send them all in at once to separate the test setup from the controller processing. - for nodeNum := 0; nodeNum < numNodes; nodeNum++ { + for nodeNum := range numNodes { // Determine the block CIDR for this node. Each node is given a /26, // which means for 5k nodes we need a /13 IP pool. baseIPInt := big.NewInt(int64(0x0a000000 + nodeNum*64)) @@ -1746,7 +1746,7 @@ var _ = Describe("IPAM controller UTs", func() { podIP := baseIP nodeName := fmt.Sprintf("node%d", nodeNum) nodePods := []v1.Pod{} - for podNum := 0; podNum < podsPerNode; podNum++ { + for podNum := range podsPerNode { p := v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: fmt.Sprintf("pod%d-%d", nodeNum, podNum), @@ -1869,8 +1869,8 @@ func createBlock(pods []v1.Pod, host, cidrStr string) bapi.Update { attrs := []model.AllocationAttribute{} for i, pod := range pods { attrs = append(attrs, model.AllocationAttribute{ - AttrPrimary: &pod.Name, - AttrSecondary: map[string]string{ + HandleID: &pod.Name, + ActiveOwnerAttrs: map[string]string{ ipam.AttributeNode: host, ipam.AttributePod: pod.Name, ipam.AttributeNamespace: pod.Namespace, diff --git a/kube-controllers/pkg/controllers/node/kdd_ipam_gc_fv_test.go b/kube-controllers/pkg/controllers/node/kdd_ipam_gc_fv_test.go index e340e52048b..1a2fad50ad1 100644 --- a/kube-controllers/pkg/controllers/node/kdd_ipam_gc_fv_test.go +++ b/kube-controllers/pkg/controllers/node/kdd_ipam_gc_fv_test.go @@ -16,11 +16,9 @@ package node_test import ( "context" - "fmt" - "os" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" v1 "k8s.io/api/core/v1" @@ -59,7 +57,6 @@ var _ = Describe("IPAM garbage collection FV tests with short leak grace period" bc backend.Client k8sClient *kubernetes.Clientset controllerManager *containers.Container - kconfigfile *os.File nodeA string ) @@ -72,17 +69,10 @@ var _ = Describe("IPAM garbage collection FV tests with short leak grace period" // Write out a kubeconfig file var err error - kconfigfile, err = os.CreateTemp("", "ginkgo-policycontroller") - Expect(err).NotTo(HaveOccurred()) - defer func() { _ = os.Remove(kconfigfile.Name()) }() - data := testutils.BuildKubeconfig(apiserver.IP) - _, err = kconfigfile.Write([]byte(data)) - Expect(err).NotTo(HaveOccurred()) - - // Make the kubeconfig readable by the container. - Expect(kconfigfile.Chmod(os.ModePerm)).NotTo(HaveOccurred()) + kconfigfile, cancel := testutils.BuildKubeconfig(apiserver.IP) + defer cancel() - k8sClient, err = testutils.GetK8sClient(kconfigfile.Name()) + k8sClient, err = testutils.GetK8sClient(kconfigfile) Expect(err).NotTo(HaveOccurred()) // Wait for the apiserver to be available. @@ -93,20 +83,13 @@ var _ = Describe("IPAM garbage collection FV tests with short leak grace period" // Apply the necessary CRDs. There can sometimes be a delay between starting // the API server and when CRDs are apply-able, so retry here. - apply := func() error { - out, err := apiserver.ExecOutput("kubectl", "apply", "-f", "/crds/") - if err != nil { - return fmt.Errorf("%s: %s", err, out) - } - return nil - } - Eventually(apply, 10*time.Second, retryInterval).ShouldNot(HaveOccurred()) + testutils.ApplyCRDs(apiserver) // Make a Calico client and backend client. type accessor interface { Backend() backend.Client } - calicoClient = testutils.GetCalicoClient(apiconfig.Kubernetes, "", kconfigfile.Name()) + calicoClient = testutils.GetCalicoClient(apiconfig.Kubernetes, "", kconfigfile) bc = calicoClient.(accessor).Backend() // Create an IP pool with room for 4 blocks. @@ -160,7 +143,7 @@ var _ = Describe("IPAM garbage collection FV tests with short leak grace period" }) // Start the controller. - controller = testutils.RunPolicyController(apiconfig.Kubernetes, "", kconfigfile.Name(), "node") + controller = testutils.RunKubeControllers(apiconfig.Kubernetes, "", kconfigfile, "node") // Run controller manager. controllerManager = testutils.RunK8sControllerManager(apiserver.IP) @@ -591,8 +574,9 @@ var _ = Describe("IPAM garbage collection FV tests with long leak grace period", bc backend.Client k8sClient *kubernetes.Clientset controllerManager *containers.Container - kconfigfile *os.File + kconfigfile string nodeA string + removeKubeconfig func() ) BeforeEach(func() { @@ -604,17 +588,9 @@ var _ = Describe("IPAM garbage collection FV tests with long leak grace period", // Write out a kubeconfig file var err error - kconfigfile, err = os.CreateTemp("", "ginkgo-policycontroller") - Expect(err).NotTo(HaveOccurred()) - defer func() { _ = os.Remove(kconfigfile.Name()) }() - data := testutils.BuildKubeconfig(apiserver.IP) - _, err = kconfigfile.Write([]byte(data)) - Expect(err).NotTo(HaveOccurred()) - - // Make the kubeconfig readable by the container. - Expect(kconfigfile.Chmod(os.ModePerm)).NotTo(HaveOccurred()) + kconfigfile, removeKubeconfig = testutils.BuildKubeconfig(apiserver.IP) - k8sClient, err = testutils.GetK8sClient(kconfigfile.Name()) + k8sClient, err = testutils.GetK8sClient(kconfigfile) Expect(err).NotTo(HaveOccurred()) // Wait for the apiserver to be available. @@ -625,20 +601,13 @@ var _ = Describe("IPAM garbage collection FV tests with long leak grace period", // Apply the necessary CRDs. There can sometimes be a delay between starting // the API server and when CRDs are apply-able, so retry here. - apply := func() error { - out, err := apiserver.ExecOutput("kubectl", "apply", "-f", "/crds/") - if err != nil { - return fmt.Errorf("%s: %s", err, out) - } - return nil - } - Eventually(apply, 10*time.Second, retryInterval).ShouldNot(HaveOccurred()) + testutils.ApplyCRDs(apiserver) // Make a Calico client and backend client. type accessor interface { Backend() backend.Client } - calicoClient = testutils.GetCalicoClient(apiconfig.Kubernetes, "", kconfigfile.Name()) + calicoClient = testutils.GetCalicoClient(apiconfig.Kubernetes, "", kconfigfile) bc = calicoClient.(accessor).Backend() // Create an IP pool with room for 4 blocks. @@ -693,7 +662,7 @@ var _ = Describe("IPAM garbage collection FV tests with long leak grace period", }) // Start the controller. - controller = testutils.RunPolicyController(apiconfig.Kubernetes, "", kconfigfile.Name(), "node") + controller = testutils.RunKubeControllers(apiconfig.Kubernetes, "", kconfigfile, "node") // Run controller manager. controllerManager = testutils.RunK8sControllerManager(apiserver.IP) @@ -709,6 +678,7 @@ var _ = Describe("IPAM garbage collection FV tests with long leak grace period", controller.Stop() apiserver.Stop() etcd.Stop() + removeKubeconfig() }) It("should NOT clean up empty blocks within the grace period", func() { diff --git a/kube-controllers/pkg/controllers/node/labels.go b/kube-controllers/pkg/controllers/node/labels.go index c603a2ad288..0ca0f45c126 100644 --- a/kube-controllers/pkg/controllers/node/labels.go +++ b/kube-controllers/pkg/controllers/node/labels.go @@ -25,7 +25,7 @@ import ( "k8s.io/client-go/tools/cache" "github.com/projectcalico/calico/kube-controllers/pkg/controllers/utils" - apiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" @@ -38,7 +38,7 @@ type nodeLabelController struct { k8sNodeMapper map[string]string // calicoNodeCache stores calicoNodes received via the syncer in local map - calicoNodeCache map[string]*apiv3.Node + calicoNodeCache map[string]*internalapi.Node // For interacting with the Calico API to update nodes. client client.Interface @@ -46,21 +46,21 @@ type nodeLabelController struct { nodeInformer cache.SharedIndexInformer nodeLister v1lister.NodeLister syncStatus bapi.SyncStatus - syncerUpdates chan interface{} + syncerUpdates chan any k8sNodeUpdate chan *v1.Node - syncChan chan interface{} + syncChan chan any } func NewNodeLabelController(client client.Interface, nodeInformer cache.SharedIndexInformer) *nodeLabelController { c := &nodeLabelController{ k8sNodeMapper: map[string]string{}, - calicoNodeCache: map[string]*apiv3.Node{}, + calicoNodeCache: map[string]*internalapi.Node{}, client: client, nodeInformer: nodeInformer, nodeLister: v1lister.NewNodeLister(nodeInformer.GetIndexer()), - syncerUpdates: make(chan interface{}, utils.BatchUpdateSize), + syncerUpdates: make(chan any, utils.BatchUpdateSize), k8sNodeUpdate: make(chan *v1.Node, utils.BatchUpdateSize), - syncChan: make(chan interface{}, 1), + syncChan: make(chan any, 1), } _, err := c.nodeInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ @@ -75,19 +75,19 @@ func NewNodeLabelController(client client.Interface, nodeInformer cache.SharedIn return c } -func (c *nodeLabelController) OnKubernetesNodeAdd(obj interface{}) { +func (c *nodeLabelController) OnKubernetesNodeAdd(obj any) { if n, ok := obj.(*v1.Node); ok { c.k8sNodeUpdate <- n } } -func (c *nodeLabelController) OnKubernetesNodeUpdate(objOld interface{}, objNew interface{}) { +func (c *nodeLabelController) OnKubernetesNodeUpdate(objOld any, objNew any) { if n, ok := objNew.(*v1.Node); ok { c.k8sNodeUpdate <- n } } -func (c *nodeLabelController) OnKubernetesNodeDelete(obj interface{}) { +func (c *nodeLabelController) OnKubernetesNodeDelete(obj any) { if n, ok := obj.(*v1.Node); ok { c.k8sNodeUpdate <- n } @@ -111,13 +111,13 @@ func (c *nodeLabelController) onUpdate(update bapi.Update) { switch update.Key.(type) { case model.ResourceKey: switch update.KVPair.Key.(model.ResourceKey).Kind { - case apiv3.KindNode: + case internalapi.KindNode: c.syncerUpdates <- update.KVPair } } } -func (c *nodeLabelController) handleUpdate(update interface{}) { +func (c *nodeLabelController) handleUpdate(update any) { switch update := update.(type) { case bapi.SyncStatus: c.syncStatus = update @@ -130,7 +130,7 @@ func (c *nodeLabelController) handleUpdate(update interface{}) { switch update.Key.(type) { case model.ResourceKey: switch update.Key.(model.ResourceKey).Kind { - case apiv3.KindNode: + case internalapi.KindNode: c.handleNodeUpdate(update) } } @@ -157,7 +157,7 @@ func (c *nodeLabelController) handleNodeUpdate(update model.KVPair) { return } - n := update.Value.(*apiv3.Node) + n := update.Value.(*internalapi.Node) kn, err := getK8sNodeName(*n) if err != nil { diff --git a/kube-controllers/pkg/controllers/node/metrics_fv_test.go b/kube-controllers/pkg/controllers/node/metrics_fv_test.go index 6ca7fad55e9..c593eb96959 100644 --- a/kube-controllers/pkg/controllers/node/metrics_fv_test.go +++ b/kube-controllers/pkg/controllers/node/metrics_fv_test.go @@ -24,7 +24,7 @@ import ( "strings" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" v1 "k8s.io/api/core/v1" @@ -52,6 +52,7 @@ var _ = Describe("kube-controllers metrics FV tests", func() { bc backend.Client k8sClient *kubernetes.Clientset controllerManager *containers.Container + v3CRDs bool ) BeforeEach(func() { @@ -62,17 +63,11 @@ var _ = Describe("kube-controllers metrics FV tests", func() { apiserver = testutils.RunK8sApiserver(etcd.IP) // Write out a kubeconfig file - kconfigfile, err := os.CreateTemp("", "ginkgo-policycontroller") - Expect(err).NotTo(HaveOccurred()) - defer func() { _ = os.Remove(kconfigfile.Name()) }() - data := testutils.BuildKubeconfig(apiserver.IP) - _, err = kconfigfile.Write([]byte(data)) - Expect(err).NotTo(HaveOccurred()) + kconfigfile, cancel := testutils.BuildKubeconfig(apiserver.IP) + defer cancel() - // Make the kubeconfig readable by the container. - Expect(kconfigfile.Chmod(os.ModePerm)).NotTo(HaveOccurred()) - - k8sClient, err = testutils.GetK8sClient(kconfigfile.Name()) + var err error + k8sClient, err = testutils.GetK8sClient(kconfigfile) Expect(err).NotTo(HaveOccurred()) // Wait for the apiserver to be available. @@ -83,14 +78,7 @@ var _ = Describe("kube-controllers metrics FV tests", func() { // Apply the necessary CRDs. There can sometimes be a delay between starting // the API server and when CRDs are apply-able, so retry here. - apply := func() error { - out, err := apiserver.ExecOutput("kubectl", "apply", "-f", "/crds/") - if err != nil { - return fmt.Errorf("%s: %s", err, out) - } - return nil - } - Eventually(apply, 10*time.Second, 1*time.Second).ShouldNot(HaveOccurred()) + testutils.ApplyCRDs(apiserver) // Wait for the applied CRDs to become registered API resources. Eventually(func() error { @@ -98,12 +86,21 @@ var _ = Describe("kube-controllers metrics FV tests", func() { if err != nil { return err } - serverResources, err := k8sClient.ServerResourcesForGroupVersion("crd.projectcalico.org/v1") + + // Determine the correct group and regex to use to match CRD files. + group := "crd.projectcalico.org/v1" + re := regexp.MustCompile(`^crd.projectcalico.org_(\w+).yaml$`) + if os.Getenv("CALICO_API_GROUP") == "projectcalico.org/v3" { + group = "projectcalico.org/v3" + re = regexp.MustCompile(`^projectcalico.org_(\w+).yaml$`) + v3CRDs = true + } + + serverResources, err := k8sClient.ServerResourcesForGroupVersion(group) if err != nil { return err } - re := regexp.MustCompile(`^crd.projectcalico.org_(\w+).yaml$`) expected := set.New[string]() for _, e := range crdDirEntries { m := re.FindStringSubmatch(e.Name()) @@ -117,7 +114,7 @@ var _ = Describe("kube-controllers metrics FV tests", func() { } if !actual.ContainsAll(expected) { - return fmt.Errorf("crd.projectcalico.org/v1 resources are not completely available. expected=%s actual=%s", expected, actual) + return fmt.Errorf("%s resources are not completely available. expected=%s actual=%s", group, expected, actual) } return nil }, 15*time.Second, 3*time.Second).ShouldNot(HaveOccurred()) @@ -126,7 +123,7 @@ var _ = Describe("kube-controllers metrics FV tests", func() { type accessor interface { Backend() backend.Client } - calicoClient = testutils.GetCalicoClient(apiconfig.Kubernetes, "", kconfigfile.Name()) + calicoClient = testutils.GetCalicoClient(apiconfig.Kubernetes, "", kconfigfile) bc = calicoClient.(accessor).Backend() // Shorten the leak grace period for testing, allowing reclamations to happen quickly. @@ -152,7 +149,7 @@ var _ = Describe("kube-controllers metrics FV tests", func() { Expect(err).NotTo(HaveOccurred()) // Run the node controller, which exports the metrics under test. - kubeControllers = testutils.RunPolicyController(apiconfig.Kubernetes, "", kconfigfile.Name(), "node") + kubeControllers = testutils.RunKubeControllers(apiconfig.Kubernetes, "", kconfigfile, "node") // Run controller manager. controllerManager = testutils.RunK8sControllerManager(apiserver.IP) @@ -551,6 +548,14 @@ var _ = Describe("kube-controllers metrics FV tests", func() { 5*time.Second, ) + // This test doesn't work exactly the same when using the projectcalico.org/v3 API as CRDs. Namely, in v3 CRD mode, + // we keep IP pools around with a Finalizer on them until all the blocks are gone (as a safety measure). So we can't + // test the exact same scenario of "replacing" a deleted pool with a new one of the same CIDR. + if v3CRDs { + // Skip the rest of this test if using v3 CRDs. + return + } + // Create an IP Pool that is analogous to the deleted pool 2. createIPPool("test-ippool-2-analogue", "172.16.0.0/16", calicoClient) @@ -724,7 +729,7 @@ func deletePodWithIP(pod string, ip string, k8sClient *kubernetes.Clientset, cal ExpectWithOffset(1, err).NotTo(HaveOccurred()) } -func validateExpectedAndUnexpectedMetrics(expectedMetrics []string, notExpectedMetrics []string, host string, intervals ...interface{}) { +func validateExpectedAndUnexpectedMetrics(expectedMetrics []string, notExpectedMetrics []string, host string, intervals ...any) { EventuallyWithOffset(1, func() error { out, err := getMetrics(fmt.Sprintf("http://%s:9094/metrics", host)) Expect(err).NotTo(HaveOccurred()) diff --git a/kube-controllers/pkg/controllers/node/node_controller_fv_test.go b/kube-controllers/pkg/controllers/node/node_controller_fv_test.go index e5c2e130f90..15b6f0b0a25 100644 --- a/kube-controllers/pkg/controllers/node/node_controller_fv_test.go +++ b/kube-controllers/pkg/controllers/node/node_controller_fv_test.go @@ -19,11 +19,10 @@ import ( "errors" "fmt" "net" - "os" "reflect" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/sirupsen/logrus" @@ -34,7 +33,7 @@ import ( "github.com/projectcalico/calico/felix/fv/containers" "github.com/projectcalico/calico/kube-controllers/tests/testutils" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapi "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" backend "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" @@ -47,13 +46,12 @@ import ( var _ = Describe("Calico node controller FV tests (KDD mode)", func() { var ( etcd *containers.Container - policyController *containers.Container + kubeControllers *containers.Container apiserver *containers.Container calicoClient client.Interface bc backend.Client k8sClient *kubernetes.Clientset controllerManager *containers.Container - kconfigfile *os.File ) BeforeEach(func() { @@ -65,17 +63,10 @@ var _ = Describe("Calico node controller FV tests (KDD mode)", func() { // Write out a kubeconfig file var err error - kconfigfile, err = os.CreateTemp("", "ginkgo-policycontroller") - Expect(err).NotTo(HaveOccurred()) - defer func() { _ = os.Remove(kconfigfile.Name()) }() - data := testutils.BuildKubeconfig(apiserver.IP) - _, err = kconfigfile.Write([]byte(data)) - Expect(err).NotTo(HaveOccurred()) - - // Make the kubeconfig readable by the container. - Expect(kconfigfile.Chmod(os.ModePerm)).NotTo(HaveOccurred()) + kconfigfile, cancel := testutils.BuildKubeconfig(apiserver.IP) + defer cancel() - k8sClient, err = testutils.GetK8sClient(kconfigfile.Name()) + k8sClient, err = testutils.GetK8sClient(kconfigfile) Expect(err).NotTo(HaveOccurred()) // Wait for the apiserver to be available. @@ -90,24 +81,17 @@ var _ = Describe("Calico node controller FV tests (KDD mode)", func() { // Apply the necessary CRDs. There can sometimes be a delay between starting // the API server and when CRDs are apply-able, so retry here. - apply := func() error { - out, err := apiserver.ExecOutput("kubectl", "apply", "-f", "/crds/") - if err != nil { - return fmt.Errorf("%s: %s", err, out) - } - return nil - } - Eventually(apply, 10*time.Second).ShouldNot(HaveOccurred()) + testutils.ApplyCRDs(apiserver) // Make a Calico client and backend client. type accessor interface { Backend() backend.Client } - calicoClient = testutils.GetCalicoClient(apiconfig.Kubernetes, "", kconfigfile.Name()) + calicoClient = testutils.GetCalicoClient(apiconfig.Kubernetes, "", kconfigfile) bc = calicoClient.(accessor).Backend() // In KDD mode, we only support the node controller right now. - policyController = testutils.RunPolicyController(apiconfig.Kubernetes, "", kconfigfile.Name(), "node") + kubeControllers = testutils.RunKubeControllers(apiconfig.Kubernetes, "", kconfigfile, "node") // Run controller manager. controllerManager = testutils.RunK8sControllerManager(apiserver.IP) @@ -116,7 +100,7 @@ var _ = Describe("Calico node controller FV tests (KDD mode)", func() { AfterEach(func() { _ = calicoClient.Close() controllerManager.Stop() - policyController.Stop() + kubeControllers.Stop() apiserver.Stop() etcd.Stop() }) @@ -372,8 +356,8 @@ var _ = Describe("Calico node controller FV tests (KDD mode)", func() { // See https://github.com/projectcalico/libcalico-go/pull/1345 kvp := blocks.KVPairs[0] b := kvp.Value.(*model.AllocationBlock) - malformedHandle := fmt.Sprintf("%s\r\neth0", *b.Attributes[0].AttrPrimary) - blocks.KVPairs[0].Value.(*model.AllocationBlock).Attributes[0].AttrPrimary = &malformedHandle + malformedHandle := fmt.Sprintf("%s\r\neth0", *b.Attributes[0].HandleID) + blocks.KVPairs[0].Value.(*model.AllocationBlock).Attributes[0].HandleID = &malformedHandle _, err = bc.Update(context.Background(), blocks.KVPairs[0]) Expect(err).NotTo(HaveOccurred()) @@ -423,7 +407,7 @@ var _ = Describe("Calico node controller FV tests (KDD mode)", func() { var _ = Describe("Calico node controller FV tests (etcd mode)", func() { var ( etcd *containers.Container - policyController *containers.Container + kubeControllers *containers.Container apiserver *containers.Container calicoClient client.Interface k8sClient *kubernetes.Clientset @@ -442,20 +426,14 @@ var _ = Describe("Calico node controller FV tests (etcd mode)", func() { apiserver = testutils.RunK8sApiserver(etcd.IP) // Write out a kubeconfig file - kconfigfile, err := os.CreateTemp("", "ginkgo-policycontroller") - Expect(err).NotTo(HaveOccurred()) - defer func() { _ = os.Remove(kconfigfile.Name()) }() - data := testutils.BuildKubeconfig(apiserver.IP) - _, err = kconfigfile.Write([]byte(data)) - Expect(err).NotTo(HaveOccurred()) - - // Make the kubeconfig readable by the container. - Expect(kconfigfile.Chmod(os.ModePerm)).NotTo(HaveOccurred()) + kconfigfile, cancel := testutils.BuildKubeconfig(apiserver.IP) + defer cancel() // Run the controller. - policyController = testutils.RunPolicyController(apiconfig.EtcdV3, etcd.IP, kconfigfile.Name(), "") + kubeControllers = testutils.RunKubeControllers(apiconfig.EtcdV3, etcd.IP, kconfigfile, "") - k8sClient, err = testutils.GetK8sClient(kconfigfile.Name()) + var err error + k8sClient, err = testutils.GetK8sClient(kconfigfile) Expect(err).NotTo(HaveOccurred()) // Wait for the apiserver to be available. @@ -475,7 +453,7 @@ var _ = Describe("Calico node controller FV tests (etcd mode)", func() { AfterEach(func() { _ = calicoClient.Close() controllerManager.Stop() - policyController.Stop() + kubeControllers.Stop() apiserver.Stop() etcd.Stop() }) @@ -489,10 +467,10 @@ var _ = Describe("Calico node controller FV tests (etcd mode)", func() { } _, err := k8sClient.CoreV1().Nodes().Create(context.Background(), kn, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - cn := libapi.NewNode() + cn := internalapi.NewNode() cn.Name = cNodeName - cn.Spec = libapi.NodeSpec{ - OrchRefs: []libapi.OrchRef{ + cn.Spec = internalapi.NodeSpec{ + OrchRefs: []internalapi.OrchRef{ { NodeName: kNodeName, Orchestrator: "k8s", @@ -505,7 +483,7 @@ var _ = Describe("Calico node controller FV tests (etcd mode)", func() { err = k8sClient.CoreV1().Nodes().Delete(context.Background(), kNodeName, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) - Eventually(func() *libapi.Node { + Eventually(func() *internalapi.Node { node, _ := calicoClient.Nodes().Get(context.Background(), cNodeName, options.GetOptions{}) return node }, time.Second*2, 500*time.Millisecond).Should(BeNil()) @@ -520,12 +498,12 @@ var _ = Describe("Calico node controller FV tests (etcd mode)", func() { _, err := k8sClient.CoreV1().Nodes().Create(context.Background(), kn, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - cn := &libapi.Node{ + cn := &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: cNodeName, }, - Spec: libapi.NodeSpec{ - OrchRefs: []libapi.OrchRef{ + Spec: internalapi.NodeSpec{ + OrchRefs: []internalapi.OrchRef{ { NodeName: kNodeName, Orchestrator: "mesos", @@ -538,7 +516,7 @@ var _ = Describe("Calico node controller FV tests (etcd mode)", func() { err = k8sClient.CoreV1().Nodes().Delete(context.Background(), kNodeName, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) - Consistently(func() *libapi.Node { + Consistently(func() *internalapi.Node { node, _ := calicoClient.Nodes().Get(context.Background(), cNodeName, options.GetOptions{}) return node }, time.Second*15, 500*time.Millisecond).ShouldNot(BeNil()) @@ -549,16 +527,16 @@ var _ = Describe("Calico node controller FV tests (etcd mode)", func() { }) It("should not be removed if orchrefs are nil.", func() { - cn := &libapi.Node{ + cn := &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: cNodeName, }, - Spec: libapi.NodeSpec{}, + Spec: internalapi.NodeSpec{}, } _, err := calicoClient.Nodes().Create(context.Background(), cn, options.SetOptions{}) Expect(err).NotTo(HaveOccurred()) - Consistently(func() *libapi.Node { + Consistently(func() *internalapi.Node { node, _ := calicoClient.Nodes().Get(context.Background(), cNodeName, options.GetOptions{}) return node }, time.Second*15, 500*time.Millisecond).ShouldNot(BeNil()) @@ -575,12 +553,12 @@ var _ = Describe("Calico node controller FV tests (etcd mode)", func() { Expect(err).NotTo(HaveOccurred()) // Create the node object in Calico's datastore. - cn := &libapi.Node{ + cn := &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: cNodeName, }, - Spec: libapi.NodeSpec{ - OrchRefs: []libapi.OrchRef{ + Spec: internalapi.NodeSpec{ + OrchRefs: []internalapi.OrchRef{ { NodeName: kNodeName, Orchestrator: "k8s", @@ -618,12 +596,12 @@ var _ = Describe("Calico node controller FV tests (etcd mode)", func() { Expect(err).NotTo(HaveOccurred()) // Create the WEP, using the address. - wep := libapi.WorkloadEndpoint{ + wep := internalapi.WorkloadEndpoint{ ObjectMeta: metav1.ObjectMeta{ Name: "caliconodename-k8s-mypod-mywep", Namespace: "default", }, - Spec: libapi.WorkloadEndpointSpec{ + Spec: internalapi.WorkloadEndpointSpec{ InterfaceName: "eth0", Pod: "mypod", Endpoint: "mywep", @@ -741,7 +719,7 @@ var _ = Describe("Calico node controller FV tests (etcd mode)", func() { Expect(err).NotTo(HaveOccurred()) // Check that the node is removed from Calico - Eventually(func() *libapi.Node { + Eventually(func() *internalapi.Node { node, _ := calicoClient.Nodes().Get(context.Background(), cNodeName, options.GetOptions{}) return node }, time.Second*2, 500*time.Millisecond).Should(BeNil()) @@ -832,11 +810,11 @@ var _ = Describe("Calico node controller FV tests (etcd mode)", func() { Expect(err).NotTo(HaveOccurred()) // Create a Calico node with a reference to it. - cn := libapi.NewNode() + cn := internalapi.NewNode() cn.Name = cNodeName cn.Labels = map[string]string{"calico-label": "calico-value", "label1": "badvalue"} - cn.Spec = libapi.NodeSpec{ - OrchRefs: []libapi.OrchRef{ + cn.Spec = internalapi.NodeSpec{ + OrchRefs: []internalapi.OrchRef{ { NodeName: kNodeName, Orchestrator: "k8s", @@ -874,7 +852,7 @@ var _ = Describe("Calico node controller FV tests (etcd mode)", func() { // Delete the Kubernetes node. err = k8sClient.CoreV1().Nodes().Delete(context.Background(), kNodeName, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) - Eventually(func() *libapi.Node { + Eventually(func() *internalapi.Node { node, _ := calicoClient.Nodes().Get(context.Background(), cNodeName, options.GetOptions{}) return node }, time.Second*2, 500*time.Millisecond).Should(BeNil()) diff --git a/kube-controllers/pkg/controllers/node/node_suite_test.go b/kube-controllers/pkg/controllers/node/node_suite_test.go index 5451e8ea539..ace3440e1b0 100644 --- a/kube-controllers/pkg/controllers/node/node_suite_test.go +++ b/kube-controllers/pkg/controllers/node/node_suite_test.go @@ -17,9 +17,8 @@ package node_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/sirupsen/logrus" "github.com/projectcalico/calico/libcalico-go/lib/testutils" @@ -31,7 +30,8 @@ func init() { } func Test(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/node_controller_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Node controller Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/node_controller_suite.xml" + ginkgo.RunSpecs(t, "Node controller Suite", suiteConfig, reporterConfig) } diff --git a/kube-controllers/pkg/controllers/pod/pod_controller.go b/kube-controllers/pkg/controllers/pod/pod_controller.go index e93d9b67043..f2bca7458d2 100644 --- a/kube-controllers/pkg/controllers/pod/pod_controller.go +++ b/kube-controllers/pkg/controllers/pod/pod_controller.go @@ -31,7 +31,7 @@ import ( "github.com/projectcalico/calico/kube-controllers/pkg/config" "github.com/projectcalico/calico/kube-controllers/pkg/controllers/controller" "github.com/projectcalico/calico/kube-controllers/pkg/converter" - libapi "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/errors" "github.com/projectcalico/calico/libcalico-go/lib/options" @@ -39,7 +39,7 @@ import ( type WorkloadEndpointCache struct { sync.RWMutex - m map[string]libapi.WorkloadEndpoint + m map[string]internalapi.WorkloadEndpoint } // podController implements the Controller interface for managing Kubernetes pods @@ -58,7 +58,7 @@ func NewPodController(ctx context.Context, k8sClientset *kubernetes.Clientset, c podConverter := converter.NewPodConverter() // Function returns map of key->WorkloadEndpointData from the Calico datastore. - listFunc := func() (map[string]interface{}, error) { + listFunc := func() (map[string]any, error) { // Get all workloadEndpoints for kubernetes orchestrator from the Calico datastore workloadEndpoints, err := c.WorkloadEndpoints().List(ctx, options.ListOptions{}) if err != nil { @@ -66,7 +66,7 @@ func NewPodController(ctx context.Context, k8sClientset *kubernetes.Clientset, c } // Iterate through and collect data from workload endpoints that we care about. - m := make(map[string]interface{}) + m := make(map[string]any) for _, wep := range workloadEndpoints.Items { // We only care about Kubernetes workload endpoints. if wep.Spec.Orchestrator == api.OrchestratorKubernetes { @@ -83,7 +83,7 @@ func NewPodController(ctx context.Context, k8sClientset *kubernetes.Clientset, c cacheArgs := rcache.ResourceCacheArgs{ ListFunc: listFunc, - ObjectType: reflect.TypeOf(converter.WorkloadEndpointData{}), + ObjectType: reflect.TypeFor[converter.WorkloadEndpointData](), // We don't handle the cases where data is missing in the cache // or in the datastore, so disable those events in the reconciler. They @@ -95,12 +95,12 @@ func NewPodController(ctx context.Context, k8sClientset *kubernetes.Clientset, c } resourceCache := rcache.NewResourceCache(cacheArgs) - workloadEndpointCache := WorkloadEndpointCache{m: make(map[string]libapi.WorkloadEndpoint)} + workloadEndpointCache := WorkloadEndpointCache{m: make(map[string]internalapi.WorkloadEndpoint)} // Bind the Calico cache to kubernetes cache with the help of an informer. This way we make sure that // whenever the kubernetes cache is updated, changes get reflected in the Calico cache as well. if _, err := informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { + AddFunc: func(obj any) { key, err := cache.MetaNamespaceKeyFunc(obj) if err != nil { log.WithError(err).Error("Failed to generate key") @@ -135,7 +135,7 @@ func NewPodController(ctx context.Context, k8sClientset *kubernetes.Clientset, c resourceCache.Prime(k, wepData) } }, - UpdateFunc: func(oldObj interface{}, newObj interface{}) { + UpdateFunc: func(oldObj any, newObj any) { key, err := cache.MetaNamespaceKeyFunc(newObj) if err != nil { log.WithError(err).Error("Failed to generate key") @@ -168,7 +168,7 @@ func NewPodController(ctx context.Context, k8sClientset *kubernetes.Clientset, c } }, - DeleteFunc: func(obj interface{}) { + DeleteFunc: func(obj any) { key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) if err != nil { log.WithError(err).Error("Failed to generate key") diff --git a/kube-controllers/pkg/controllers/pod/pod_controller_fv_test.go b/kube-controllers/pkg/controllers/pod/pod_controller_fv_test.go index ce508b12270..84fbf3cbe99 100644 --- a/kube-controllers/pkg/controllers/pod/pod_controller_fv_test.go +++ b/kube-controllers/pkg/controllers/pod/pod_controller_fv_test.go @@ -17,11 +17,10 @@ package pod_test import ( "context" "fmt" - "os" "time" "github.com/google/uuid" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" v1 "k8s.io/api/core/v1" @@ -31,7 +30,7 @@ import ( "github.com/projectcalico/calico/felix/fv/containers" "github.com/projectcalico/calico/kube-controllers/tests/testutils" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapi "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/names" "github.com/projectcalico/calico/libcalico-go/lib/options" @@ -40,7 +39,7 @@ import ( var _ = Describe("Calico pod controller FV tests (etcd mode)", func() { var ( etcd *containers.Container - policyController *containers.Container + kubeControllers *containers.Container apiserver *containers.Container calicoClient client.Interface k8sClient *kubernetes.Clientset @@ -56,20 +55,14 @@ var _ = Describe("Calico pod controller FV tests (etcd mode)", func() { apiserver = testutils.RunK8sApiserver(etcd.IP) // Write out a kubeconfig file - kconfigfile, err := os.CreateTemp("", "ginkgo-policycontroller") - Expect(err).NotTo(HaveOccurred()) - defer func() { _ = os.Remove(kconfigfile.Name()) }() - data := testutils.BuildKubeconfig(apiserver.IP) - _, err = kconfigfile.Write([]byte(data)) - Expect(err).NotTo(HaveOccurred()) - - // Make the kubeconfig readable by the container. - Expect(kconfigfile.Chmod(os.ModePerm)).NotTo(HaveOccurred()) + kconfigfile, cancel := testutils.BuildKubeconfig(apiserver.IP) + defer cancel() // Run the controller. - policyController = testutils.RunPolicyController(apiconfig.EtcdV3, etcd.IP, kconfigfile.Name(), "") + kubeControllers = testutils.RunKubeControllers(apiconfig.EtcdV3, etcd.IP, kconfigfile, "") - k8sClient, err = testutils.GetK8sClient(kconfigfile.Name()) + var err error + k8sClient, err = testutils.GetK8sClient(kconfigfile) Expect(err).NotTo(HaveOccurred()) // Wait for the apiserver to be available. @@ -89,7 +82,7 @@ var _ = Describe("Calico pod controller FV tests (etcd mode)", func() { AfterEach(func() { _ = calicoClient.Close() controllerManager.Stop() - policyController.Stop() + kubeControllers.Stop() apiserver.Stop() etcd.Stop() }) @@ -144,7 +137,7 @@ var _ = Describe("Calico pod controller FV tests (etcd mode)", func() { } wepName, err := wepIDs.CalculateWorkloadEndpointName(false) Expect(err).NotTo(HaveOccurred()) - wep := libapi.NewWorkloadEndpoint() + wep := internalapi.NewWorkloadEndpoint() wep.Name = wepName wep.Namespace = podNamespace wep.Labels = map[string]string{ @@ -152,7 +145,7 @@ var _ = Describe("Calico pod controller FV tests (etcd mode)", func() { "projectcalico.org/namespace": podNamespace, "projectcalico.org/orchestrator": api.OrchestratorKubernetes, } - wep.Spec = libapi.WorkloadEndpointSpec{ + wep.Spec = internalapi.WorkloadEndpointSpec{ ContainerID: "container-id-1", Orchestrator: "k8s", Pod: podName, @@ -195,8 +188,8 @@ var _ = Describe("Calico pod controller FV tests (etcd mode)", func() { By("updating the workload endpoint's container ID", func() { var err error - var gwep *libapi.WorkloadEndpoint - for i := 0; i < 5; i++ { + var gwep *internalapi.WorkloadEndpoint + for range 5 { // This emulates a scenario in which the CNI plugin can be called for the same Kubernetes // Pod multiple times with a different container ID. gwep, err = calicoClient.WorkloadEndpoints().Get(context.Background(), wep.Namespace, wep.Name, options.GetOptions{}) @@ -227,7 +220,7 @@ var _ = Describe("Calico pod controller FV tests (etcd mode)", func() { Expect(err).NotTo(HaveOccurred()) }) - var w *libapi.WorkloadEndpoint + var w *internalapi.WorkloadEndpoint By("waiting for the labels to appear in the datastore", func() { Eventually(func() error { var err error @@ -316,7 +309,7 @@ var _ = Describe("Calico pod controller FV tests (etcd mode)", func() { } wepName, err := wepIDs.CalculateWorkloadEndpointName(false) Expect(err).NotTo(HaveOccurred()) - wep := libapi.NewWorkloadEndpoint() + wep := internalapi.NewWorkloadEndpoint() wep.Name = wepName wep.Namespace = podNamespace wep.Labels = map[string]string{ @@ -324,7 +317,7 @@ var _ = Describe("Calico pod controller FV tests (etcd mode)", func() { "projectcalico.org/namespace": podNamespace, "projectcalico.org/orchestrator": api.OrchestratorKubernetes, } - wep.Spec = libapi.WorkloadEndpointSpec{ + wep.Spec = internalapi.WorkloadEndpointSpec{ ContainerID: "container-id-1", Orchestrator: "k8s", Pod: podName, diff --git a/kube-controllers/pkg/controllers/pod/pod_suite_test.go b/kube-controllers/pkg/controllers/pod/pod_suite_test.go index 3788d43203c..88fcd928d30 100644 --- a/kube-controllers/pkg/controllers/pod/pod_suite_test.go +++ b/kube-controllers/pkg/controllers/pod/pod_suite_test.go @@ -17,9 +17,8 @@ package pod_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/sirupsen/logrus" "github.com/projectcalico/calico/libcalico-go/lib/testutils" @@ -31,7 +30,8 @@ func init() { } func Test(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/pod_controller_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Pod controller suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/pod_controller_suite.xml" + ginkgo.RunSpecs(t, "Pod controller suite", suiteConfig, reporterConfig) } diff --git a/kube-controllers/pkg/controllers/serviceaccount/serviceaccount_controller.go b/kube-controllers/pkg/controllers/serviceaccount/serviceaccount_controller.go index a6a64578525..a6f42affee3 100644 --- a/kube-controllers/pkg/controllers/serviceaccount/serviceaccount_controller.go +++ b/kube-controllers/pkg/controllers/serviceaccount/serviceaccount_controller.go @@ -55,9 +55,9 @@ func NewServiceAccountController(ctx context.Context, k8sClientset *kubernetes.C // Function returns map of profile_name:object stored by policy controller // in the Calico datastore. Identifies controller written objects by // their naming convention. - listFunc := func() (map[string]interface{}, error) { + listFunc := func() (map[string]any, error) { log.Debugf("Listing profiles from Calico datastore: to check for ServiceAccount") - filteredProfiles := make(map[string]interface{}) + filteredProfiles := make(map[string]any) // Get all profile objects from Calico datastore. profileList, err := c.Profiles().List(ctx, options.ListOptions{}) @@ -83,7 +83,7 @@ func NewServiceAccountController(ctx context.Context, k8sClientset *kubernetes.C // Create a Cache to store Profiles in. cacheArgs := rcache.ResourceCacheArgs{ ListFunc: listFunc, - ObjectType: reflect.TypeOf(api.Profile{}), + ObjectType: reflect.TypeFor[api.Profile](), LogTypeDesc: "ServiceAccount", } ccache := rcache.NewResourceCache(cacheArgs) @@ -98,7 +98,7 @@ func NewServiceAccountController(ctx context.Context, k8sClientset *kubernetes.C ObjectType: &v1.ServiceAccount{}, ResyncPeriod: 0, Handler: cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { + AddFunc: func(obj any) { log.Debugf("Got ADD event for ServiceAccount: %#v", obj) profile, err := serviceAccountConverter.Convert(obj) if err != nil { @@ -110,7 +110,7 @@ func NewServiceAccountController(ctx context.Context, k8sClientset *kubernetes.C k := serviceAccountConverter.GetKey(profile) ccache.Set(k, profile) }, - UpdateFunc: func(oldObj interface{}, newObj interface{}) { + UpdateFunc: func(oldObj any, newObj any) { log.Debugf("Got UPDATE event for ServiceAccount") log.Debugf("Old object: \n%#v\n", oldObj) log.Debugf("New object: \n%#v\n", newObj) @@ -126,7 +126,7 @@ func NewServiceAccountController(ctx context.Context, k8sClientset *kubernetes.C k := serviceAccountConverter.GetKey(profile) ccache.Set(k, profile) }, - DeleteFunc: func(obj interface{}) { + DeleteFunc: func(obj any) { // Convert the ServiceAccount into a Profile. log.Debugf("Got DELETE event for ServiceAccount: %#v", obj) profile, err := serviceAccountConverter.Convert(obj) diff --git a/kube-controllers/pkg/controllers/serviceaccount/serviceaccount_controller_fv_test.go b/kube-controllers/pkg/controllers/serviceaccount/serviceaccount_controller_fv_test.go index 7a54840f6f6..ade75b8635b 100644 --- a/kube-controllers/pkg/controllers/serviceaccount/serviceaccount_controller_fv_test.go +++ b/kube-controllers/pkg/controllers/serviceaccount/serviceaccount_controller_fv_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022 Tigera, Inc. All rights reserved. +// Copyright (c) 2017-2025 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,10 +16,9 @@ package serviceaccount_test import ( "context" - "os" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" v1 "k8s.io/api/core/v1" @@ -36,7 +35,7 @@ import ( var _ = Describe("Calico serviceaccount controller FV tests (etcd mode)", func() { var ( etcd *containers.Container - policyController *containers.Container + kubeControllers *containers.Container apiserver *containers.Container calicoClient client.Interface k8sClient *kubernetes.Clientset @@ -52,20 +51,13 @@ var _ = Describe("Calico serviceaccount controller FV tests (etcd mode)", func() apiserver = testutils.RunK8sApiserver(etcd.IP) // Write out a kubeconfig file - kconfigfile, err := os.CreateTemp("", "ginkgo-policycontroller") - Expect(err).NotTo(HaveOccurred()) - defer func() { _ = os.Remove(kconfigfile.Name()) }() - data := testutils.BuildKubeconfig(apiserver.IP) - _, err = kconfigfile.Write([]byte(data)) - Expect(err).NotTo(HaveOccurred()) - - // Make the kubeconfig readable by the container. - Expect(kconfigfile.Chmod(os.ModePerm)).NotTo(HaveOccurred()) + kconfigfile, cancel := testutils.BuildKubeconfig(apiserver.IP) + defer cancel() - // Run the controller. - policyController = testutils.RunPolicyController(apiconfig.EtcdV3, etcd.IP, kconfigfile.Name(), "") + kubeControllers = testutils.RunKubeControllers(apiconfig.EtcdV3, etcd.IP, kconfigfile, "") - k8sClient, err = testutils.GetK8sClient(kconfigfile.Name()) + var err error + k8sClient, err = testutils.GetK8sClient(kconfigfile) Expect(err).NotTo(HaveOccurred()) // Wait for the apiserver to be available. @@ -85,7 +77,7 @@ var _ = Describe("Calico serviceaccount controller FV tests (etcd mode)", func() AfterEach(func() { _ = calicoClient.Close() controllerManager.Stop() - policyController.Stop() + kubeControllers.Stop() apiserver.Stop() etcd.Stop() }) diff --git a/kube-controllers/pkg/controllers/serviceaccount/serviceaccount_suite_test.go b/kube-controllers/pkg/controllers/serviceaccount/serviceaccount_suite_test.go index 15a5afad3d7..dc8f9a7b8d3 100644 --- a/kube-controllers/pkg/controllers/serviceaccount/serviceaccount_suite_test.go +++ b/kube-controllers/pkg/controllers/serviceaccount/serviceaccount_suite_test.go @@ -17,9 +17,8 @@ package serviceaccount_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/sirupsen/logrus" "github.com/projectcalico/calico/libcalico-go/lib/testutils" @@ -31,7 +30,8 @@ func init() { } func Test(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/serviceaccount_controller_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "ServiceAccount controller suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/serviceaccount_controller_suite.xml" + ginkgo.RunSpecs(t, "ServiceAccount controller suite", suiteConfig, reporterConfig) } diff --git a/kube-controllers/pkg/controllers/utils/syncer.go b/kube-controllers/pkg/controllers/utils/syncer.go index 6a8bce105d3..20131807bbb 100644 --- a/kube-controllers/pkg/controllers/utils/syncer.go +++ b/kube-controllers/pkg/controllers/utils/syncer.go @@ -20,7 +20,7 @@ import ( apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/sirupsen/logrus" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/backend/watchersyncer" @@ -31,14 +31,16 @@ const ( Etcdv3 = "etcdv3" ) -type UpdateHandler func(bapi.Update) -type StatusHandler func(bapi.SyncStatus) +type ( + UpdateHandler func(bapi.Update) + StatusHandler func(bapi.SyncStatus) +) func NewDataFeed(c client.Interface, dataStore string) *DataFeed { // Kinds to register with on the syncer API. resourceTypes := []watchersyncer.ResourceType{ { - ListInterface: model.ResourceListOptions{Kind: libapiv3.KindNode}, + ListInterface: model.ResourceListOptions{Kind: internalapi.KindNode}, }, { ListInterface: model.ResourceListOptions{Kind: apiv3.KindClusterInformation}, @@ -52,13 +54,27 @@ func NewDataFeed(c client.Interface, dataStore string) *DataFeed { { ListInterface: model.ResourceListOptions{Kind: apiv3.KindHostEndpoint}, }, + + // Network policy types + { + ListInterface: model.ResourceListOptions{Kind: apiv3.KindNetworkPolicy}, + }, + { + ListInterface: model.ResourceListOptions{Kind: apiv3.KindGlobalNetworkPolicy}, + }, + { + ListInterface: model.ResourceListOptions{Kind: apiv3.KindStagedNetworkPolicy}, + }, + { + ListInterface: model.ResourceListOptions{Kind: apiv3.KindStagedGlobalNetworkPolicy}, + }, } type accessor interface { Backend() bapi.Client } d := &DataFeed{ - registrations: map[interface{}][]UpdateHandler{}, + registrations: map[any][]UpdateHandler{}, statusRegistrations: []StatusHandler{}, dataStore: dataStore, } @@ -70,12 +86,19 @@ type DataFeed struct { syncer bapi.Syncer // Registrations - registrations map[interface{}][]UpdateHandler + registrations map[any][]UpdateHandler statusRegistrations []StatusHandler dataStore string } func (d *DataFeed) Start() { + // We can skip this if there are no registrations. + if len(d.registrations) == 0 && len(d.statusRegistrations) == 0 { + logrus.Info("No registrations for data feed, skipping start") + return + } + + logrus.Info("Starting syncer") d.syncer.Start() } @@ -130,8 +153,8 @@ func (d *DataFeed) updateResourceVersion(update bapi.Update) { switch key := update.Key.(type) { case model.ResourceKey: switch key.Kind { - case libapiv3.KindNode: - node := update.Value.(*libapiv3.Node) + case internalapi.KindNode: + node := update.Value.(*internalapi.Node) node.ResourceVersion = update.Revision case apiv3.KindClusterInformation: clusterInformation := update.Value.(*apiv3.ClusterInformation) diff --git a/kube-controllers/pkg/converter/converter.go b/kube-controllers/pkg/converter/converter.go index 6c9a1d041b0..ad3566bba59 100644 --- a/kube-controllers/pkg/converter/converter.go +++ b/kube-controllers/pkg/converter/converter.go @@ -17,10 +17,10 @@ package converter // Converter Responsible for conversion of given kubernetes object to equivalent calico object type Converter interface { // Converts kubernetes object to calico representation of it. - Convert(k8sObj interface{}) (interface{}, error) + Convert(k8sObj any) (any, error) // Returns appropriate key for the object - GetKey(obj interface{}) string + GetKey(obj any) string // DeleteArgsFromKey returns name and namespace of the object to pass to Delete // for the given key as generated by GetKey. diff --git a/kube-controllers/pkg/converter/converter_suite_test.go b/kube-controllers/pkg/converter/converter_suite_test.go index b22da2e8e4e..8e26636169d 100644 --- a/kube-controllers/pkg/converter/converter_suite_test.go +++ b/kube-controllers/pkg/converter/converter_suite_test.go @@ -17,13 +17,13 @@ package converter_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" ) func Test(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/converter_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Converter Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/converter_suite.xml" + ginkgo.RunSpecs(t, "Converter Suite", suiteConfig, reporterConfig) } diff --git a/kube-controllers/pkg/converter/namespace_converter.go b/kube-controllers/pkg/converter/namespace_converter.go index 10e5595769f..6e11379c98e 100644 --- a/kube-controllers/pkg/converter/namespace_converter.go +++ b/kube-controllers/pkg/converter/namespace_converter.go @@ -32,7 +32,7 @@ type namespaceConverter struct { func NewNamespaceConverter() Converter { return &namespaceConverter{} } -func (nc *namespaceConverter) Convert(k8sObj interface{}) (interface{}, error) { +func (nc *namespaceConverter) Convert(k8sObj any) (any, error) { c := conversion.NewConverter() namespace, ok := k8sObj.(*v1.Namespace) if !ok { @@ -61,7 +61,7 @@ func (nc *namespaceConverter) Convert(k8sObj interface{}) (interface{}, error) { // GetKey returns name of the Profile as its key. For Profiles // backed by Kubernetes namespaces and managed by this controller, the name // is of format `kns.name`. -func (nc *namespaceConverter) GetKey(obj interface{}) string { +func (nc *namespaceConverter) GetKey(obj any) string { profile := obj.(api.Profile) return profile.Name } diff --git a/kube-controllers/pkg/converter/namespace_converter_test.go b/kube-controllers/pkg/converter/namespace_converter_test.go index 1ab0349a791..5edc1e66c61 100644 --- a/kube-controllers/pkg/converter/namespace_converter_test.go +++ b/kube-controllers/pkg/converter/namespace_converter_test.go @@ -15,7 +15,7 @@ package converter_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" k8sapi "k8s.io/api/core/v1" diff --git a/kube-controllers/pkg/converter/networkpolicy_converter.go b/kube-controllers/pkg/converter/networkpolicy_converter.go index eb5d8921d9e..aa43d7fc8dc 100644 --- a/kube-controllers/pkg/converter/networkpolicy_converter.go +++ b/kube-controllers/pkg/converter/networkpolicy_converter.go @@ -28,8 +28,11 @@ import ( cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" ) -type policyConverter struct { -} +// KubernetesNetworkPolicyEtcdPrefix is the prefix added to Kubernetes NetworkPolicy names when +// storing them in etcd to avoid name conflicts with Calico NetworkPolicies. +const KubernetesNetworkPolicyEtcdPrefix = "knp.default." + +type policyConverter struct{} // NewPolicyConverter Constructor for policyConverter func NewPolicyConverter() Converter { @@ -37,7 +40,7 @@ func NewPolicyConverter() Converter { } // Convert takes a Kubernetes NetworkPolicy and returns a Calico api.NetworkPolicy representation. -func (p *policyConverter) Convert(k8sObj interface{}) (interface{}, error) { +func (p *policyConverter) Convert(k8sObj any) (any, error) { np, ok := k8sObj.(*networkingv1.NetworkPolicy) if !ok { @@ -62,15 +65,18 @@ func (p *policyConverter) Convert(k8sObj interface{}) (interface{}, error) { } cnp := kvp.Value.(*api.NetworkPolicy) + // Add a prefix to the name to avoid conflicts with Calico NetworkPolicies. + policyName := KubernetesNetworkPolicyEtcdPrefix + np.Name + // Isolate the metadata fields that we care about. ResourceVersion, CreationTimeStamp, etc are // not relevant so we ignore them. This prevents unnecessary updates. - cnp.ObjectMeta = metav1.ObjectMeta{Name: cnp.Name, Namespace: cnp.Namespace} + cnp.ObjectMeta = metav1.ObjectMeta{Name: policyName, Namespace: cnp.Namespace} return *cnp, err } // GetKey returns the 'namespace/name' for the given Calico NetworkPolicy as its key. -func (p *policyConverter) GetKey(obj interface{}) string { +func (p *policyConverter) GetKey(obj any) string { policy := obj.(api.NetworkPolicy) return fmt.Sprintf("%s/%s", policy.Namespace, policy.Name) } diff --git a/kube-controllers/pkg/converter/networkpolicy_converter_test.go b/kube-controllers/pkg/converter/networkpolicy_converter_test.go index c711d46fb43..b40c847b535 100644 --- a/kube-controllers/pkg/converter/networkpolicy_converter_test.go +++ b/kube-controllers/pkg/converter/networkpolicy_converter_test.go @@ -15,7 +15,7 @@ package converter_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" diff --git a/kube-controllers/pkg/converter/pod_converter.go b/kube-controllers/pkg/converter/pod_converter.go index c61b0476a60..f9e4f5d1355 100644 --- a/kube-controllers/pkg/converter/pod_converter.go +++ b/kube-controllers/pkg/converter/pod_converter.go @@ -22,7 +22,7 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/client-go/tools/cache" - api "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" ) @@ -37,7 +37,7 @@ type WorkloadEndpointData struct { } type PodConverter interface { - Convert(k8sObj interface{}) ([]WorkloadEndpointData, error) + Convert(k8sObj any) ([]WorkloadEndpointData, error) GetKey(obj WorkloadEndpointData) string DeleteArgsFromKey(key string) (string, string) } @@ -47,7 +47,7 @@ type podConverter struct{} // BuildWorkloadEndpointData generates the correct WorkloadEndpointData for the given // list of WorkloadEndpoints, extracting fields that the policy controller is responsible // for syncing. -func BuildWorkloadEndpointData(weps ...api.WorkloadEndpoint) []WorkloadEndpointData { +func BuildWorkloadEndpointData(weps ...internalapi.WorkloadEndpoint) []WorkloadEndpointData { var retWEPs []WorkloadEndpointData for _, wep := range weps { retWEPs = append(retWEPs, WorkloadEndpointData{ @@ -63,7 +63,7 @@ func BuildWorkloadEndpointData(weps ...api.WorkloadEndpoint) []WorkloadEndpointD // MergeWorkloadEndpointData applies the given WorkloadEndpointData to the provided // WorkloadEndpoint, updating relevant fields with new values. -func MergeWorkloadEndpointData(wep *api.WorkloadEndpoint, upd WorkloadEndpointData) { +func MergeWorkloadEndpointData(wep *internalapi.WorkloadEndpoint, upd WorkloadEndpointData) { if wep.Spec.Pod != upd.PodName || wep.Namespace != upd.Namespace { log.Fatalf("Bad attempt to merge data for %s/%s into wep %s/%s", upd.PodName, upd.Namespace, wep.Name, wep.Namespace) } @@ -76,7 +76,7 @@ func NewPodConverter() PodConverter { return &podConverter{} } -func (p *podConverter) Convert(k8sObj interface{}) ([]WorkloadEndpointData, error) { +func (p *podConverter) Convert(k8sObj any) ([]WorkloadEndpointData, error) { // Convert Pod into a workload endpoint. c := conversion.NewConverter() pod, err := ExtractPodFromUpdate(k8sObj) @@ -99,10 +99,10 @@ func (p *podConverter) Convert(k8sObj interface{}) ([]WorkloadEndpointData, erro return BuildWorkloadEndpointData(kvpsToWEPs(kvps)...), nil } -func kvpsToWEPs(kvps []*model.KVPair) []api.WorkloadEndpoint { - var weps []api.WorkloadEndpoint +func kvpsToWEPs(kvps []*model.KVPair) []internalapi.WorkloadEndpoint { + var weps []internalapi.WorkloadEndpoint for _, kvp := range kvps { - wep := kvp.Value.(*api.WorkloadEndpoint) + wep := kvp.Value.(*internalapi.WorkloadEndpoint) if wep != nil { weps = append(weps, *wep) } @@ -128,7 +128,7 @@ func (p *podConverter) DeleteArgsFromKey(key string) (string, string) { // ExtractPodFromUpdate takes an update as received from the informer and returns the pod object, if present. // some updates (particularly deletes) can include tombstone placeholders rather than an exact pod object. This // function should be called in order to safely handles those cases. -func ExtractPodFromUpdate(obj interface{}) (*v1.Pod, error) { +func ExtractPodFromUpdate(obj any) (*v1.Pod, error) { pod, ok := obj.(*v1.Pod) if !ok { tombstone, ok := obj.(cache.DeletedFinalStateUnknown) diff --git a/kube-controllers/pkg/converter/pod_converter_test.go b/kube-controllers/pkg/converter/pod_converter_test.go index e076f8d1e4c..1da32837a8b 100644 --- a/kube-controllers/pkg/converter/pod_converter_test.go +++ b/kube-controllers/pkg/converter/pod_converter_test.go @@ -15,14 +15,14 @@ package converter_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/cache" "github.com/projectcalico/calico/kube-controllers/pkg/converter" - api "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" ) var _ = Describe("PodConverter", func() { @@ -171,7 +171,7 @@ var _ = Describe("PodConverter", func() { "key": "value", "foo": "bar", } - wep := api.NewWorkloadEndpoint() + wep := internalapi.NewWorkloadEndpoint() wep.Name = "nodename-k8s-testwep-eth0" wep.Namespace = "default" wep.Spec.Pod = "testwep" diff --git a/kube-controllers/pkg/converter/serviceaccount_converter.go b/kube-controllers/pkg/converter/serviceaccount_converter.go index ef89510c33b..79cf0cad410 100644 --- a/kube-controllers/pkg/converter/serviceaccount_converter.go +++ b/kube-controllers/pkg/converter/serviceaccount_converter.go @@ -33,7 +33,7 @@ func NewServiceAccountConverter() Converter { return &serviceAccountConverter{} } -func (nc *serviceAccountConverter) Convert(k8sObj interface{}) (interface{}, error) { +func (nc *serviceAccountConverter) Convert(k8sObj any) (any, error) { c := conversion.NewConverter() serviceAccount, ok := k8sObj.(*v1.ServiceAccount) if !ok { @@ -62,7 +62,7 @@ func (nc *serviceAccountConverter) Convert(k8sObj interface{}) (interface{}, err // GetKey returns name of the Profile as its key. For Profiles // backed by Kubernetes serviceaccounts and managed by this controller, the name // is of format `ksa.namespace.name`. -func (nc *serviceAccountConverter) GetKey(obj interface{}) string { +func (nc *serviceAccountConverter) GetKey(obj any) string { profile := obj.(api.Profile) return profile.Name } diff --git a/kube-controllers/pkg/converter/serviceaccount_converter_test.go b/kube-controllers/pkg/converter/serviceaccount_converter_test.go index e77a8868613..52fbe69c91e 100644 --- a/kube-controllers/pkg/converter/serviceaccount_converter_test.go +++ b/kube-controllers/pkg/converter/serviceaccount_converter_test.go @@ -15,7 +15,7 @@ package converter_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" k8sapi "k8s.io/api/core/v1" diff --git a/kube-controllers/pkg/status/status_suite_test.go b/kube-controllers/pkg/status/status_suite_test.go index e3d5e1df6e2..680f65e4271 100644 --- a/kube-controllers/pkg/status/status_suite_test.go +++ b/kube-controllers/pkg/status/status_suite_test.go @@ -17,9 +17,8 @@ package status_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/sirupsen/logrus" "github.com/projectcalico/calico/libcalico-go/lib/testutils" @@ -31,7 +30,8 @@ func init() { } func TestConfig(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/status_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "pkg/status suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/status_suite.xml" + ginkgo.RunSpecs(t, "pkg/status suite", suiteConfig, reporterConfig) } diff --git a/kube-controllers/pkg/status/status_test.go b/kube-controllers/pkg/status/status_test.go index 9480fa6cadd..4e021d9dd1c 100644 --- a/kube-controllers/pkg/status/status_test.go +++ b/kube-controllers/pkg/status/status_test.go @@ -19,7 +19,7 @@ import ( "strconv" "sync" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -103,7 +103,7 @@ var _ = Describe("Status pkg UTs", func() { By("status file should handle a lot of concurrent not-ready updates for a lot of keys", func() { wg := sync.WaitGroup{} wg.Add(100) - for i := 0; i < 100; i++ { + for i := range 100 { go func(j int) { st.SetReady("anykey"+strconv.Itoa(j), false, "reason"+strconv.Itoa(j)) wg.Done() @@ -129,7 +129,7 @@ var _ = Describe("Status pkg UTs", func() { wg := sync.WaitGroup{} wg.Add(200) go st.SetReady("anykey", true, "reason") - for i := 0; i < 200; i++ { + for i := range 200 { go func(j int) { st.SetReady("anykey"+strconv.Itoa(j), true, "reason"+strconv.Itoa(j)) wg.Done() diff --git a/kube-controllers/run-uts b/kube-controllers/run-uts index 230902d810a..27ec7252a80 100755 --- a/kube-controllers/run-uts +++ b/kube-controllers/run-uts @@ -1,10 +1,6 @@ #!/bin/bash -# Turn off the annoying ginkgo warning. -# TODO: We should actually upgrade ginkgo! -export ACK_GINKGO_RC=true - -if [ -z "$1" ]; then +if [ -z "$1" ]; then echo "No packages need to be tested" exit 0 fi @@ -19,7 +15,7 @@ RC=0 # Go through each package we've been told to test. If there are actually test files present, # then run those tests. If the package is included in SKIP, we'll skip them. -for PKG in "$@"; do +for PKG in "$@"; do # Skip any tests we've been told to skip. if [[ "$SKIP" == *"$PKG"* ]]; then echo "Skipping tests for package: ${PKG}" @@ -27,10 +23,10 @@ for PKG in "$@"; do fi HAS_TESTS=$(find ${PKG} -name "ut.test") - if [ ! -z "${HAS_TESTS}" ]; then + if [ ! -z "${HAS_TESTS}" ]; then echo "Running tests for package: ${PKG}"; ${PKG}/ut.test ${GINKGO_ARGS} || ((RC++)) - else + else echo "WARNING: No tests to run in ${PKG}, skipping" fi done diff --git a/kube-controllers/tests/testutils/flannel_migration_utils.go b/kube-controllers/tests/testutils/flannel_migration_utils.go index 26c646bdfab..3b1d5024186 100644 --- a/kube-controllers/tests/testutils/flannel_migration_utils.go +++ b/kube-controllers/tests/testutils/flannel_migration_utils.go @@ -21,6 +21,7 @@ import ( "context" "encoding/json" "fmt" + "maps" "os" "time" @@ -44,6 +45,7 @@ func RunFlannelMigrationController(kconfigfile string, nodeName, subnetEnv strin "-e", "ENABLED_CONTROLLERS=flannelmigration", "-e", "LOG_LEVEL=debug", "-e", "FLANNEL_DAEMONSET_NAMESPACE=kube-system", + "-e", "FV_TEST=true", // Indicate that this is an FV test run, enabling some test hooks. "-e", fmt.Sprintf("POD_NODE_NAME=%s", nodeName), "-e", fmt.Sprintf("FLANNEL_SUBNET_ENV=%s", subnetEnv), "-e", fmt.Sprintf("DEBUG_WAIT_BEFORE_START=%d", waitBeforeStart), @@ -114,9 +116,7 @@ func (f *FlannelCluster) AddFlannelNode(nodeName, podCidr, backend, mac, ip stri defaultLabels["node-role.kubernetes.io/master"] = "" defaultLabels["node-role.kubernetes.io/control-plane"] = "" } - for k, v := range labels { - defaultLabels[k] = v - } + maps.Copy(defaultLabels, labels) flannelNode := newFlannelNode(podCidr, backend, mac, ip) diff --git a/kube-controllers/tests/testutils/loadbalancer_controller_utils.go b/kube-controllers/tests/testutils/loadbalancer_controller_utils.go index 823af95ac41..cdfbcba94ab 100644 --- a/kube-controllers/tests/testutils/loadbalancer_controller_utils.go +++ b/kube-controllers/tests/testutils/loadbalancer_controller_utils.go @@ -36,6 +36,7 @@ func RunLoadBalancerController(datastoreType apiconfig.DatastoreType, etcdIP, kc "-e", fmt.Sprintf("DATASTORE_TYPE=%s", datastoreType), "-e", fmt.Sprintf("ENABLED_CONTROLLERS=%s", ctrls), "-e", fmt.Sprintf("KUBECONFIG=%s", kconfigfile), + "-e", "FV_TEST=true", // Indicate that this is an FV test run, enabling some test hooks. "-e", "LOG_LEVEL=debug", "-e", "RECONCILER_PERIOD=10s", "-v", fmt.Sprintf("%s:%s", kconfigfile, kconfigfile), diff --git a/kube-controllers/tests/testutils/node_controller_utils.go b/kube-controllers/tests/testutils/node_controller_utils.go index 1aad7d47098..224e845b3c0 100644 --- a/kube-controllers/tests/testutils/node_controller_utils.go +++ b/kube-controllers/tests/testutils/node_controller_utils.go @@ -32,7 +32,7 @@ import ( "github.com/projectcalico/calico/felix/fv/containers" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - v3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" "github.com/projectcalico/calico/libcalico-go/lib/options" @@ -51,6 +51,7 @@ func RunNodeController(datastoreType apiconfig.DatastoreType, etcdIP, kconfigfil "-e", fmt.Sprintf("ETCD_ENDPOINTS=http://%s:2379", etcdIP), "-e", fmt.Sprintf("DATASTORE_TYPE=%s", datastoreType), "-e", fmt.Sprintf("ENABLED_CONTROLLERS=%s", ctrls), + "-e", "FV_TEST=true", // Indicate that this is an FV test run, enabling some test hooks. "-e", "SYNC_NODE_LABELS=true", "-e", "LOG_LEVEL=debug", "-e", fmt.Sprintf("KUBECONFIG=%s", kconfigfile), @@ -149,7 +150,7 @@ func UpdateK8sNode(c *kubernetes.Clientset, name string, update func(n *v1.Node) var err error var kn *v1.Node - for i := 0; i < 10; i++ { + for range 10 { // Retry node update in the event of an update conflict. kn, err = c.CoreV1().Nodes().Get(context.Background(), name, metav1.GetOptions{}) if err != nil { @@ -174,11 +175,11 @@ func UpdateK8sNode(c *kubernetes.Clientset, name string, update func(n *v1.Node) } // UpdateCalicoNode updates a Calico node resource, handling retries if there are update conflicts. -func UpdateCalicoNode(c client.Interface, name string, update func(n *v3.Node)) error { +func UpdateCalicoNode(c client.Interface, name string, update func(n *internalapi.Node)) error { var err error - var cn *v3.Node + var cn *internalapi.Node - for i := 0; i < 10; i++ { + for range 10 { // Retry node update in the event of an update conflict. cn, err = c.Nodes().Get(context.Background(), name, options.GetOptions{}) if err != nil { diff --git a/kube-controllers/tests/testutils/policy_controller_utils.go b/kube-controllers/tests/testutils/policy_controller_utils.go index 333cc09cada..9fcc4981574 100644 --- a/kube-controllers/tests/testutils/policy_controller_utils.go +++ b/kube-controllers/tests/testutils/policy_controller_utils.go @@ -25,7 +25,7 @@ import ( "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" ) -func RunPolicyController(datastoreType apiconfig.DatastoreType, etcdIP, kconfigfile, ctrls string) *containers.Container { +func RunKubeControllers(datastoreType apiconfig.DatastoreType, etcdIP, kconfigfile, ctrls string) *containers.Container { if ctrls == "" { // Default to all controllers. ctrls = "workloadendpoint,namespace,policy,node,serviceaccount" @@ -37,6 +37,7 @@ func RunPolicyController(datastoreType apiconfig.DatastoreType, etcdIP, kconfigf "-e", fmt.Sprintf("ENABLED_CONTROLLERS=%s", ctrls), "-e", fmt.Sprintf("KUBECONFIG=%s", kconfigfile), "-e", "LOG_LEVEL=debug", + "-e", "FV_TEST=true", // Indicate that this is an FV test run, enabling some test hooks. "-e", "RECONCILER_PERIOD=10s", "-v", fmt.Sprintf("%s:%s", kconfigfile, kconfigfile), os.Getenv("CONTAINER_NAME")) diff --git a/kube-controllers/tests/testutils/utils.go b/kube-controllers/tests/testutils/utils.go index 5f380b2222f..501c0416b89 100644 --- a/kube-controllers/tests/testutils/utils.go +++ b/kube-controllers/tests/testutils/utils.go @@ -23,6 +23,7 @@ import ( "fmt" "os" "os/exec" + "time" //nolint:staticcheck // Ignore ST1001: should not use dot imports . "github.com/onsi/gomega" @@ -58,7 +59,7 @@ contexts: user: calico current-context: test-context` -func BuildKubeconfig(apiserverIP string) string { +func BuildKubeconfig(apiserverIP string) (string, func()) { // Load contents of test cert / key and fill them into the kubeconfig. adminCertBytes, err := os.ReadFile(os.Getenv("CERTS_PATH") + "/admin.pem") Expect(err).NotTo(HaveOccurred()) @@ -68,8 +69,38 @@ func BuildKubeconfig(apiserverIP string) string { Expect(err).NotTo(HaveOccurred()) encodedAdminKey := base64.StdEncoding.EncodeToString(adminKeyBytes) - // Put it all together. - return fmt.Sprintf(kubeconfigTemplate, apiserverIP, encodedAdminCert, encodedAdminKey) + // Put it all together, and write it to a temp file. + data := fmt.Sprintf(kubeconfigTemplate, apiserverIP, encodedAdminCert, encodedAdminKey) + + // Create a temp file to hold the kubeconfig. + f, err := os.CreateTemp("", "kube-ctrls-test") + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + defer func() { Expect(f.Close()).To(Succeed()) }() + + // Write the kubeconfig data to the file. + _, err = f.Write([]byte(data)) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + + // Make the kubeconfig readable by the container. + Expect(f.Chmod(os.ModePerm)).NotTo(HaveOccurred()) + + // Return the name of the kubeconfig file and a cleanup function. + return f.Name(), func() { + Expect(os.Remove(f.Name())).To(Succeed()) + } +} + +func ApplyCRDs(apiserver *containers.Container) { + // Apply the necessary CRDs. There can sometimes be a delay between starting + // the API server and when CRDs are apply-able, so retry here. + apply := func() error { + out, err := apiserver.ExecOutput("kubectl", "apply", "-f", "/crds/") + if err != nil { + return fmt.Errorf("%s: %s", err, out) + } + return nil + } + EventuallyWithOffset(1, apply, 10*time.Second, 1*time.Second).ShouldNot(HaveOccurred()) } func RunK8sApiserver(etcdIp string) *containers.Container { @@ -132,22 +163,30 @@ func RunEtcd() *containers.Container { } func GetCalicoClient(dsType apiconfig.DatastoreType, etcdIP, kcfg string) client.Interface { - cfg := apiconfig.NewCalicoAPIConfig() + // Load the config from the environment. + cfg, err := apiconfig.LoadClientConfigFromEnvironment() + Expect(err).NotTo(HaveOccurred()) + + // Override with the given config. cfg.Spec.DatastoreType = dsType cfg.Spec.EtcdEndpoints = fmt.Sprintf("http://%s:2379", etcdIP) cfg.Spec.Kubeconfig = kcfg - client, err := client.New(*cfg) + client, err := client.New(*cfg) Expect(err).NotTo(HaveOccurred()) return client } func GetBackendClient(etcdIP string) api.Client { - cfg := apiconfig.NewCalicoAPIConfig() + // Load the config from the environment. + cfg, err := apiconfig.LoadClientConfigFromEnvironment() + Expect(err).NotTo(HaveOccurred()) + + // Override with the given config. cfg.Spec.DatastoreType = apiconfig.EtcdV3 cfg.Spec.EtcdEndpoints = fmt.Sprintf("http://%s:2379", etcdIP) - be, err := backend.NewClient(*cfg) + be, err := backend.NewClient(*cfg) Expect(err).NotTo(HaveOccurred()) return be } @@ -168,18 +207,17 @@ func GetK8sClient(kubeconfig string) (*kubernetes.Clientset, error) { } func Stop(c *containers.Container) { - var args = []string{"stop", c.Name} + args := []string{"stop", c.Name} log.WithField("container", c.Name).Info("Stopping container") cmd := exec.Command("docker", args...) err := cmd.Run() Expect(err).NotTo(HaveOccurred()) out, _ := cmd.CombinedOutput() log.Info(out) - } func Start(c *containers.Container) { - var args = []string{"start", c.Name} + args := []string{"start", c.Name} log.WithField("container", c.Name).Info("Starting container") cmd := exec.Command("docker", args...) err := cmd.Run() diff --git a/lib.Makefile b/lib.Makefile index 999a7e83e25..92f4a213ee2 100644 --- a/lib.Makefile +++ b/lib.Makefile @@ -39,7 +39,7 @@ VALIDARCHES = $(filter-out $(EXCLUDEARCH),$(ARCHES)) # Note: OS is always set on Windows ifeq ($(OS),Windows_NT) BUILDARCH = x86_64 -BUILDOS = x86_64 +BUILDOS = Windows else BUILDARCH ?= $(shell uname -m) BUILDOS ?= $(shell uname -s | tr A-Z a-z) @@ -84,7 +84,7 @@ endif # This is only needed when running non-native binaries. register: ifneq ($(BUILDARCH),$(ARCH)) - docker run --privileged --rm tonistiigi/binfmt --install all || true + docker run --privileged --rm calico/binfmt:qemu-v10.1.3 --install all || true endif # If this is a release, also tag and push additional images. @@ -207,7 +207,12 @@ ifeq ($(GIT_USE_SSH),true) endif # Get version from git. We allow setting this manually for the hashrelease process. -GIT_VERSION ?= $(shell git describe --tags --dirty --always --abbrev=12) +# By default, includes commit count and hash (--long). During releases (RELEASE=true), +# only the tag is used without the commit count suffix. +GIT_VERSION ?= $(shell git describe --tags --dirty --always --abbrev=12 --long) +ifeq ($(RELEASE),true) + GIT_VERSION := $(shell git describe --tags --dirty --always --abbrev=12) +endif # Figure out version information. To support builds from release tarballs, we default to # if this isn't a git checkout. @@ -280,6 +285,39 @@ endif REPO_ROOT := $(shell git rev-parse --show-toplevel) CERTS_PATH := $(REPO_ROOT)/hack/test/certs +# Support for git worktrees. In a worktree, .git is a file pointing to +# /.git/worktrees/. When Docker containers need git access, +# the main .git directory must also be mounted, and GIT_DIR / GIT_WORK_TREE +# must be set so that git can find objects and the correct working tree. +_GIT_DIR := $(shell git rev-parse --absolute-git-dir 2>/dev/null) +_GIT_COMMON_DIR := $(realpath $(shell git rev-parse --git-common-dir 2>/dev/null)) +ifneq ($(_GIT_DIR),$(_GIT_COMMON_DIR)) +# Running in a git worktree: mount the main .git directory at its host path +# so the worktree .git file pointer resolves inside the container. +# DOCKER_GIT_WORK_TREE can be overridden by Makefiles that mount the repo at +# a different container path (e.g. api/Makefile). +DOCKER_GIT_WORK_TREE ?= /go/src/github.com/projectcalico/calico +DOCKER_GIT_WORKTREE_ARGS := \ + -v $(_GIT_COMMON_DIR):$(_GIT_COMMON_DIR):ro \ + -e GIT_DIR=$(_GIT_DIR) \ + -e GIT_WORK_TREE=$(DOCKER_GIT_WORK_TREE) +else +DOCKER_GIT_WORKTREE_ARGS := +endif + +# Configure the Calico API group to use. Projects importing this Makefile can override this variable +# if they need to. +# Supported values: +# - crd.projectcalico.org/v1 (default) +# - projectcalico.org/v3 +CALICO_API_GROUP ?= crd.projectcalico.org/v1 + +# Where to find Calico CRD files depends on which API group we are using to back them. +CALICO_CRD_PATH ?= api/config/crd/ +ifneq ($(CALICO_API_GROUP),projectcalico.org/v3) +CALICO_CRD_PATH = libcalico-go/config/crd/ +endif + # The image to use for building calico/base-dependent modules (e.g. apiserver, typha). ifdef USE_UBI_AS_CALICO_BASE CALICO_BASE ?= $(UBI_IMAGE) @@ -300,23 +338,25 @@ DOCKER_BUILD=docker buildx build --load --platform=linux/$(ARCH) $(DOCKER_PULL)\ --build-arg CALICO_BASE=$(CALICO_BASE) \ --build-arg BPFTOOL_IMAGE=$(BPFTOOL_IMAGE) -DOCKER_RUN := mkdir -p $(REPO_ROOT)/.go-pkg-cache bin $(GOMOD_CACHE) && \ +DOCKER_RUN_PRIV_NET := mkdir -p $(REPO_ROOT)/.go-pkg-cache bin $(GOMOD_CACHE) && \ docker run --rm \ - --net=host \ --init \ $(EXTRA_DOCKER_ARGS) \ + $(DOCKER_GIT_WORKTREE_ARGS) \ -e LOCAL_USER_ID=$(LOCAL_USER_ID) \ -e GOCACHE=/go-cache \ $(GOARCH_FLAGS) \ -e GOPATH=/go \ -e OS=$(BUILDOS) \ -e GOOS=$(BUILDOS) \ + -e CALICO_API_GROUP=$(CALICO_API_GROUP) \ -e "GOFLAGS=$(GOFLAGS)" \ - -e ACK_GINKGO_DEPRECATIONS=1.16.5 \ -v $(REPO_ROOT):/go/src/github.com/projectcalico/calico:rw \ -v $(REPO_ROOT)/.go-pkg-cache:/go-cache:rw \ -w /go/src/$(PACKAGE_NAME) +DOCKER_RUN := $(DOCKER_RUN_PRIV_NET) --net=host + DOCKER_GO_BUILD := $(DOCKER_RUN) $(CALICO_BUILD) # A target that does nothing but it always stale, used to force a rebuild on certain targets based on some non-file criteria. @@ -500,10 +540,9 @@ git-commit: ############################################################################### ifdef LOCAL_CRANE -CRANE_CMD = bash -c $(double_quote)crane +CRANE_CMD = crane else -CRANE_CMD = docker run -t --entrypoint /bin/sh -v $(DOCKER_CONFIG):/root/.docker/config.json $(CALICO_BUILD) -c \ - $(double_quote)crane +CRANE_CMD = $(REPO_ROOT)/bin/crane endif ifdef LOCAL_PYTHON @@ -524,10 +563,10 @@ GIT = $(GIT_CMD) DOCKER = $(DOCKER_CMD) RELEASE_PY3 = $(PYTHON3_CMD) else -CRANE = @echo [DRY RUN] $(CRANE_CMD) -GIT = @echo [DRY RUN] $(GIT_CMD) -DOCKER = @echo [DRY RUN] $(DOCKER_CMD) -RELEASE_PY3 = @echo [DRY RUN] $(PYTHON3_CMD) +CRANE = echo [DRY RUN] $(CRANE_CMD) +GIT = echo [DRY RUN] $(GIT_CMD) +DOCKER = echo [DRY RUN] $(DOCKER_CMD) +RELEASE_PY3 = echo [DRY RUN] $(PYTHON3_CMD) endif QUAY_SET_EXPIRY_SCRIPT = $(REPO_ROOT)/hack/set_quay_expiry.py @@ -1071,6 +1110,14 @@ var-require-all-%: var-require-one-of-%: @$(MAKE) --quiet --no-print-directory var-require REQUIRED_VARS=$* +# build-images echos the images that would be built. +# If WINDOWS_IMAGE is set then it echos the windows image that would be built as well. +build-images: var-require-all-BUILD_IMAGES + $(if $(WINDOWS_IMAGE),\ + @echo $(BUILD_IMAGES) $(WINDOWS_IMAGE),\ + @echo $(BUILD_IMAGES)\ + ) + # sem-cut-release triggers the cut-release pipeline (or test-cut-release if CONFIRM is not specified) in semaphore to # cut the release. The pipeline is triggered for the current commit, and the branch it's triggered on is calculated # from the RELEASE_VERSION, CNX, and OS variables given. @@ -1211,9 +1258,9 @@ release-retag-dev-images-in-registry-%: # release-retag-dev-image-in-registry-% retags the build image specified by $* in the dev registry specified by # DEV_REGISTRY with the release tag specified by RELEASE_TAG. If DEV_REGISTRY is in the list of registries specified by # RELEASE_REGISTRIES then the retag is not done -release-retag-dev-image-in-registry-%: +release-retag-dev-image-in-registry-%: bin/crane $(if $(filter-out $(RELEASE_REGISTRIES),$(DEV_REGISTRY)),\ - $(CRANE) cp $(DEV_REGISTRY)/$(call unescapefs,$*):$(DEV_TAG) $(DEV_REGISTRY)/$(call unescapefs,$*):$(RELEASE_TAG))$(double_quote) + $(CRANE) cp $(DEV_REGISTRY)/$(call unescapefs,$*):$(DEV_TAG) $(DEV_REGISTRY)/$(call unescapefs,$*):$(RELEASE_TAG)) # release-dev-images-to-registry-% copies and retags all the build / arch images specified by BUILD_IMAGES and # VALIDARCHES from the registry specified by DEV_REGISTRY to the registry specified by RELEASE_REGISTRY using the tag @@ -1223,16 +1270,16 @@ release-dev-images-to-registry-%: # release-dev-image-to-registry-% copies the build image and build arch images specified by $* and VALIDARCHES from # the dev repo specified by DEV_TAG and RELEASE. -release-dev-image-to-registry-%: +release-dev-image-to-registry-%: bin/crane $(if $(SKIP_MANIFEST_RELEASE),,\ - $(CRANE) cp $(DEV_REGISTRY)/$(call unescapefs,$*):$(DEV_TAG) $(RELEASE_REGISTRY)/$(call unescapefs,$*):$(RELEASE_TAG))$(double_quote) + $(CRANE) cp $(DEV_REGISTRY)/$(call unescapefs,$*):$(DEV_TAG) $(RELEASE_REGISTRY)/$(call unescapefs,$*):$(RELEASE_TAG)) $(if $(SKIP_ARCH_RELEASE),,\ $(MAKE) $(addprefix release-dev-image-arch-to-registry-,$(VALIDARCHES)) BUILD_IMAGE=$(call unescapefs,$*)) # release-dev-image-to-registry-% copies the build arch image specified by BUILD_IMAGE and ARCH from the dev repo # specified by DEV_TAG and RELEASE. -release-dev-image-arch-to-registry-%: - $(CRANE) cp $(DEV_REGISTRY)/$(BUILD_IMAGE):$(DEV_TAG)-$* $(RELEASE_REGISTRY)/$(BUILD_IMAGE):$(RELEASE_TAG)-$*$(double_quote) +release-dev-image-arch-to-registry-%: bin/crane + $(CRANE) cp $(DEV_REGISTRY)/$(BUILD_IMAGE):$(DEV_TAG)-$* $(RELEASE_REGISTRY)/$(BUILD_IMAGE):$(RELEASE_TAG)-$* # release-prereqs checks that the environment is configured properly to create a release. .PHONY: release-prereqs @@ -1253,23 +1300,22 @@ bin/yq: tar -zxvf $(TMP)/yq4.tar.gz -C $(TMP) mv $(TMP)/yq_linux_$(BUILDARCH) bin/yq -# This setup is used to download and install the 'crane' binary into the local bin/ directory. -# The binary will be placed at: ./bin/crane +# This setup is used to download and install the `crane` binary into $(REPOROOT)/bin/crane. # Normalize architecture for go-containerregistry filenames -CRANE_BUILDARCH := $(shell uname -m | sed 's/amd64/x86_64/;s/x86_64/x86_64/;s/aarch64/arm64/') -ifeq ($(CRANE_BUILDARCH),) - $(error Unsupported or unknown architecture: $(shell uname -m)) +CRANE_ARCH = $(subst amd64,x86_64,$(BUILDARCH)) +ifeq ($(OS),Windows_NT) +CRANE_OS = Windows +else +CRANE_OS = $(shell uname -s) endif -CRANE_FILENAME := go-containerregistry_Linux_$(CRANE_BUILDARCH).tar.gz -CRANE_URL := https://github.com/google/go-containerregistry/releases/download/$(CRANE_VERSION)/$(CRANE_FILENAME) +CRANE_URL = https://github.com/google/go-containerregistry/releases/download/$(CRANE_VERSION)/go-containerregistry_$(CRANE_OS)_$(CRANE_ARCH).tar.gz -# Install crane binary into bin/ -bin/crane: - mkdir -p bin - $(eval CRANE_TMP := $(shell mktemp -d)) - curl -sSfL --retry 5 -o $(CRANE_TMP)/crane.tar.gz $(CRANE_URL) - tar -xzf $(CRANE_TMP)/crane.tar.gz -C $(CRANE_TMP) crane - mv $(CRANE_TMP)/crane bin/crane +.PHONY: bin/crane +bin/crane: $(REPO_ROOT)/bin/crane +$(REPO_ROOT)/bin/crane: + $(info ::: Downloading crane from $(CRANE_URL)) + @mkdir -p $(REPO_ROOT)/bin + @curl -sSfL --retry 5 $(CRANE_URL) | tar xz -C $(REPO_ROOT)/bin crane ############################################################################### # Common functions for launching a local Kubernetes control plane. @@ -1295,7 +1341,15 @@ run-k8s-apiserver: stop-k8s-apiserver run-etcd --tls-private-key-file=/home/user/certs/kubernetes-key.pem \ --enable-priority-and-fairness=false \ --max-mutating-requests-inflight=0 \ - --max-requests-inflight=0 + --max-requests-inflight=0 \ + --enable-aggregator-routing \ + --requestheader-client-ca-file=/home/user/certs/ca.pem \ + --requestheader-username-headers=X-Remote-User \ + --requestheader-group-headers=X-Remote-Group \ + --requestheader-extra-headers-prefix=X-Remote-Extra- \ + --proxy-client-cert-file=/home/user/certs/kubernetes.pem \ + --proxy-client-key-file=/home/user/certs/kubernetes-key.pem + # Wait until the apiserver is accepting requests. while ! docker exec $(APISERVER_NAME) kubectl get nodes; do echo "Waiting for apiserver to come up..."; sleep 2; done @@ -1311,7 +1365,7 @@ run-k8s-apiserver: stop-k8s-apiserver run-etcd # Create CustomResourceDefinition (CRD) for Calico resources while ! docker exec $(APISERVER_NAME) kubectl \ - apply -f /go/src/github.com/projectcalico/calico/libcalico-go/config/crd/; \ + apply -f /go/src/github.com/projectcalico/calico/$(CALICO_CRD_PATH); \ do echo "Trying to create CRDs"; \ sleep 1; \ done @@ -1366,13 +1420,20 @@ $(REPO_ROOT)/.$(KIND_NAME).created: $(KUBECTL) $(KIND) # Wait for controller manager to be running and healthy, then create Calico CRDs. while ! KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) get serviceaccount default; do echo "Waiting for default serviceaccount to be created..."; sleep 2; done - while ! KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) create -f $(REPO_ROOT)/libcalico-go/config/crd; do echo "Waiting for CRDs to be created"; sleep 2; done + while ! KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) create -f $(REPO_ROOT)/charts/crd.projectcalico.org.v1/templates/; do echo "Waiting for operator CRDs to be created"; sleep 2; done + while ! KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) create -f $(REPO_ROOT)/$(CALICO_CRD_PATH); do echo "Waiting for calico CRDs to be created"; sleep 2; done + + # These may have already been created, depending on where we're getting our CRDs from. So use apply. + while ! KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) apply -f $(REPO_ROOT)/libcalico-go/config/crd/policy.networking.k8s.io_clusternetworkpolicies.yaml; do echo "Waiting for CRDs to be created"; sleep 2; done + + # Install mutating admission policies. + while ! KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) apply -f $(REPO_ROOT)/api/admission/; do echo "Waiting for mutating admission policies to be created"; sleep 2; done + touch $@ kind-cluster-destroy: $(KIND) $(KUBECTL) # We need to drain the cluster gracefully when shutting down to avoid a netdev unregister error from the kernel. # This requires we execute CNI del on pods with pod networking. - -$(KUBECTL) --kubeconfig=$(KIND_KUBECONFIG) drain kind-control-plane kind-worker kind-worker2 kind-worker3 --ignore-daemonsets --force --timeout=10m -$(KIND) delete cluster --name $(KIND_NAME) rm -f $(KIND_KUBECONFIG) rm -f $(REPO_ROOT)/.$(KIND_NAME).created @@ -1480,32 +1541,6 @@ help: # Common functions for building windows images. ############################################################################### -# When running on semaphore, just copy the docker config, otherwise run -# 'docker-credential-gcr configure-docker' as well. -ifdef SEMAPHORE -DOCKER_CREDENTIAL_CMD = cp /root/.docker/config.json_host /root/.docker/config.json -else -DOCKER_CREDENTIAL_CMD = cp /root/.docker/config.json_host /root/.docker/config.json && \ - docker-credential-gcr configure-docker -endif - -# This needs the $(WINDOWS_DIST)/bin/docker-credential-gcr binary in $PATH and -# also the local ~/.config/gcloud dir to be able to push to gcr.io. It mounts -# $(DOCKER_CONFIG) and copies it so that it can be written to on the container, -# but not have any effect on the host config. -CRANE_BINDMOUNT_CMD := \ - docker run --rm \ - --net=host \ - --init \ - --entrypoint /bin/sh \ - -e LOCAL_USER_ID=$(LOCAL_USER_ID) \ - -v $(CURDIR):/go/src/$(PACKAGE_NAME):rw \ - -v $(DOCKER_CONFIG):/root/.docker/config.json_host:ro \ - -e PATH=$${PATH}:/go/src/$(PACKAGE_NAME)/$(WINDOWS_DIST)/bin \ - -v $(HOME)/.config/gcloud:/root/.config/gcloud \ - -w /go/src/$(PACKAGE_NAME) \ - $(CALICO_BUILD) -c $(double_quote)$(DOCKER_CREDENTIAL_CMD) && crane - DOCKER_MANIFEST_CMD := docker manifest ifdef CONFIRM @@ -1613,7 +1648,7 @@ image-windows: setup-windows-builder var-require-all-WINDOWS_VERSIONS $(MAKE) windows-sub-image-$${version}; \ done; -release-windows-with-tag: var-require-one-of-CONFIRM-DRYRUN var-require-all-IMAGETAG-DEV_REGISTRIES image-windows docker-credential-gcr-binary +release-windows-with-tag: var-require-one-of-CONFIRM-DRYRUN var-require-all-IMAGETAG-DEV_REGISTRIES image-windows docker-credential-gcr-binary bin/crane for registry in $(DEV_REGISTRIES); do \ echo Pushing Windows images to $${registry}; \ all_images=""; \ @@ -1622,7 +1657,7 @@ release-windows-with-tag: var-require-one-of-CONFIRM-DRYRUN var-require-all-IMAG image_tar="$(WINDOWS_DIST)/$(WINDOWS_IMAGE)-$(GIT_VERSION)-$${win_ver}.tar"; \ image="$${registry}/$(WINDOWS_IMAGE):$(IMAGETAG)-windows-$${win_ver}"; \ echo Pushing image $${image} ...; \ - $(CRANE_BINDMOUNT) push $${image_tar} $${image}$(double_quote) & \ + $(CRANE) push $${image_tar} $${image} & \ all_images="$${all_images} $${image}"; \ done; \ wait; \ @@ -1636,10 +1671,15 @@ release-windows-with-tag: var-require-one-of-CONFIRM-DRYRUN var-require-all-IMAG $(RELEASE_PY3) $(QUAY_SET_EXPIRY_SCRIPT) add --expiry-days=$(QUAY_EXPIRE_DAYS) $${manifest_image} $${all_images} || true; \ done; -release-windows: var-require-one-of-CONFIRM-DRYRUN var-require-all-DEV_REGISTRIES-WINDOWS_IMAGE var-require-one-of-VERSION-BRANCH_NAME +release-windows: var-require-one-of-CONFIRM-DRYRUN var-require-all-DEV_REGISTRIES-WINDOWS_IMAGE var-require-one-of-VERSION-BRANCH_NAME bin/crane describe_tag=$(if $(IMAGETAG_PREFIX),$(IMAGETAG_PREFIX)-)$(shell git describe --tags --dirty --long --always --abbrev=12); \ release_tag=$(if $(VERSION),$(VERSION),$(if $(IMAGETAG_PREFIX),$(IMAGETAG_PREFIX)-)$(BRANCH_NAME)); \ $(MAKE) release-windows-with-tag IMAGETAG=$${describe_tag}; \ for registry in $(DEV_REGISTRIES); do \ - $(CRANE_BINDMOUNT) cp $${registry}/$(WINDOWS_IMAGE):$${describe_tag} $${registry}/$(WINDOWS_IMAGE):$${release_tag}$(double_quote); \ + $(CRANE) cp $${registry}/$(WINDOWS_IMAGE):$${describe_tag} $${registry}/$(WINDOWS_IMAGE):$${release_tag}; \ done; + +# Name of a test image that is used by both "node" and "calicoctl", so defined here. This image is +# built by node/Makefile. +TEST_CONTAINER_NAME_VER?=latest +TEST_CONTAINER_NAME?=calico/test:$(TEST_CONTAINER_NAME_VER)-$(ARCH) diff --git a/lib/httpmachinery/go.mod b/lib/httpmachinery/go.mod index 1720f06d071..0cd447d2d02 100644 --- a/lib/httpmachinery/go.mod +++ b/lib/httpmachinery/go.mod @@ -1,30 +1,31 @@ module github.com/projectcalico/calico/lib/httpmachinery -go 1.25.1 +go 1.25.7 require ( github.com/go-playground/form v3.1.4+incompatible - github.com/go-playground/validator/v10 v10.27.0 + github.com/go-playground/validator/v10 v10.28.0 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 - github.com/onsi/gomega v1.38.0 + github.com/onsi/gomega v1.38.2 github.com/sirupsen/logrus v1.9.3 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.11.1 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/gabriel-vasile/mimetype v1.4.10 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/objx v0.5.2 // indirect - golang.org/x/crypto v0.39.0 // indirect - golang.org/x/net v0.41.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.26.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.45.0 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.31.0 // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/lib/httpmachinery/go.sum b/lib/httpmachinery/go.sum index 4226509ec25..fe938eca550 100644 --- a/lib/httpmachinery/go.sum +++ b/lib/httpmachinery/go.sum @@ -1,10 +1,12 @@ +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= -github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0= +github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/form v3.1.4+incompatible h1:lvKiHVxE2WvzDIoyMnWcjyiBxKt2+uFJyZcPYWsLnjI= @@ -13,8 +15,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= -github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688= +github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -27,10 +29,10 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= -github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= -github.com/onsi/gomega v1.38.0 h1:c/WX+w8SLAinvuKKQFh77WEucCnPk4j2OTUr7lt7BeY= -github.com/onsi/gomega v1.38.0/go.mod h1:OcXcwId0b9QsE7Y49u+BTrL4IdKOBOKnD6VQNTJEB6o= +github.com/onsi/ginkgo/v2 v2.25.1 h1:Fwp6crTREKM+oA6Cz4MsO8RhKQzs2/gOIVOUscMAfZY= +github.com/onsi/ginkgo/v2 v2.25.1/go.mod h1:ppTWQ1dh9KM/F1XgpeRqelR+zHVwV81DGRSDnFxK7Sk= +github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= +github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -39,21 +41,23 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= -golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= -golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= -golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= diff --git a/lib/std/chanutil/chan.go b/lib/std/chanutil/chan.go index fd1fef3a8a1..ce390b77a30 100644 --- a/lib/std/chanutil/chan.go +++ b/lib/std/chanutil/chan.go @@ -143,3 +143,22 @@ func Clear[R any](c <-chan R) { } } } + +// WaitForCloseWithDeadline waits for the channel to close. It returns an error if the deadline is exceeded or the context +// is cancelled. +func WaitForCloseWithDeadline[E any](ctx context.Context, ch <-chan E, duration time.Duration) error { + timeCh := time.After(duration) + for { + select { + case <-ctx.Done(): + return ctx.Err() + case _, ok := <-ch: + if !ok { + return nil + } + continue + case <-timeCh: + return ErrDeadlineExceeded + } + } +} diff --git a/lib/std/chanutil/chan_test.go b/lib/std/chanutil/chan_test.go index 9f8d193996d..73ecce601bb 100644 --- a/lib/std/chanutil/chan_test.go +++ b/lib/std/chanutil/chan_test.go @@ -233,3 +233,38 @@ func TestChanUtil_Clear(t *testing.T) { }) } } + +func TestChanUtil_WaitForCloseWithDeadline(t *testing.T) { + t.Run("channel closes before deadline", func(t *testing.T) { + ch := make(chan string) + go func() { + time.Sleep(10 * time.Millisecond) + close(ch) + }() + err := chanutil.WaitForCloseWithDeadline(context.Background(), ch, 100*time.Millisecond) + if err != nil { + t.Fatalf("Expected nil error, got '%v'", err) + } + }) + + t.Run("context cancelled before channel closes", func(t *testing.T) { + ch := make(chan string) + ctx, cancel := context.WithCancel(context.Background()) + go func() { + time.Sleep(10 * time.Millisecond) + cancel() + }() + err := chanutil.WaitForCloseWithDeadline(ctx, ch, 100*time.Millisecond) + if !errors.Is(err, context.Canceled) { + t.Fatalf("Expected context canceled error, got '%v'", err) + } + }) + + t.Run("deadline exceeded before channel closes", func(t *testing.T) { + ch := make(chan string) + err := chanutil.WaitForCloseWithDeadline(context.Background(), ch, 10*time.Millisecond) + if !errors.Is(err, chanutil.ErrDeadlineExceeded) { + t.Fatalf("Expected deadline exceeded error, got '%v'", err) + } + }) +} diff --git a/lib/std/go.mod b/lib/std/go.mod index 31d423dab67..ea362bf28a8 100644 --- a/lib/std/go.mod +++ b/lib/std/go.mod @@ -1,3 +1,3 @@ module github.com/projectcalico/calico/lib/std -go 1.25.1 +go 1.25.7 diff --git a/lib/std/time/controlled.go b/lib/std/time/controlled.go index 8974d18fff6..2a2ddf50d24 100644 --- a/lib/std/time/controlled.go +++ b/lib/std/time/controlled.go @@ -100,6 +100,9 @@ func (clock *controlledClock) Now() Time { clock.Lock() defer clock.Unlock() + if localOverride != nil { + return clock.Unix(clock.now, 0).In(localOverride) + } return clock.Unix(clock.now, 0) } diff --git a/lib/std/time/standard.go b/lib/std/time/standard.go index 158bb9fdb9c..d09e4da01ec 100644 --- a/lib/std/time/standard.go +++ b/lib/std/time/standard.go @@ -9,6 +9,9 @@ func newStdClock() Clock { } func (std *stdClock) Now() Time { + if localOverride != nil { + return time.Now().In(localOverride) + } return time.Now() } diff --git a/lib/std/time/time.go b/lib/std/time/time.go index 1deb24c3bf2..c4da395ffb0 100644 --- a/lib/std/time/time.go +++ b/lib/std/time/time.go @@ -1,6 +1,9 @@ package time -import "time" +import ( + "testing" + "time" +) func init() { activeClock = newStdClock() @@ -13,27 +16,38 @@ type ( Duration = time.Duration + Weekday = time.Weekday + Month = time.Month Location = time.Location ) const ( - January Month = 1 + iota - February - March - April - May - June - July - August - September - October - November - December + Sunday Weekday = time.Sunday + Monday Weekday = time.Monday + Tuesday Weekday = time.Tuesday + Wednesday Weekday = time.Wednesday + Thursday Weekday = time.Thursday + Friday Weekday = time.Friday + Saturday Weekday = time.Saturday + + January Month = time.January + February Month = time.February + March Month = time.March + April Month = time.April + May Month = time.May + June Month = time.June + July Month = time.July + August Month = time.August + September Month = time.September + October Month = time.October + November Month = time.November + December Month = time.December ) const ( + Day = 24 * time.Hour Hour = time.Hour Minute = time.Minute Second = time.Second @@ -50,11 +64,16 @@ const ( RFC850 = time.RFC850 Kitchen = time.Kitchen Stamp = time.Stamp + DateOnly = time.DateOnly + DateTime = time.DateTime + UnixDate = time.UnixDate ) var ( UTC = time.UTC Local = time.Local + + localOverride *time.Location ) // DoWithClock temporarily sets the shim as the time and runs the given function. After the function is run, the time @@ -65,6 +84,28 @@ func DoWithClock(shim Clock, fn func() error) error { return fn() } +type CleanUpRegisterable interface { + Cleanup(func()) +} + +// ShimLocalForTesting temporarily sets the local used by the time package to the given location. This helps with time +// comparisons where two equal times with different locations are considered unequal. +func ShimLocalForTesting(t *testing.T, local *time.Location) { + localOverride = local + t.Cleanup(func() { localOverride = nil }) +} + +// ShimClockForTestingT temporarily sets the shim as the time and runs the given function for a test and when the test +// is done, the time is returned to its original state. +func ShimClockForTestingT(t CleanUpRegisterable, shim Clock) { + original := activeClock + activeClock = shim + + t.Cleanup(func() { + activeClock = original + }) +} + // Clock is our shim interface to the time package. type Clock interface { Now() Time @@ -128,3 +169,15 @@ func Parse(layout, value string) (Time, error) { func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time { return time.Date(year, month, day, hour, min, sec, nsec, loc) } + +func FixedZone(name string, offset int) *Location { + return time.FixedZone(name, offset) +} + +func ParseDuration(s string) (Duration, error) { + return time.ParseDuration(s) +} + +func ParseInLocation(layout, value string, loc *Location) (Time, error) { + return time.ParseInLocation(layout, value, loc) +} diff --git a/lib/std/uniquelabels/cache.go b/lib/std/uniquelabels/cache.go new file mode 100644 index 00000000000..786a4ac38db --- /dev/null +++ b/lib/std/uniquelabels/cache.go @@ -0,0 +1,82 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// 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 uniquelabels + +import ( + "hash/maphash" + "sync" +) + +// recentCache is a small, direct-mapped cache of recently-computed Map values. +// It is common for Make to be called in quick succession with the same input +// from different call sites. Since the keys and values are interned, the +// handleMap is the main allocation that can be avoided by caching. +var recentCache = recentMapCache{ + seed: maphash.MakeSeed(), +} + +const recentMapCacheSize = 128 // Must be a power of two. + +type recentMapCache struct { + mu sync.Mutex + seed maphash.Seed + entries [recentMapCacheSize]recentMapCacheEntry +} + +type recentMapCacheEntry struct { + hash uint64 + m Map +} + +// Lookup checks the cache for a Map matching the given map[string]string. +// Returns the cached Map and true on hit, or the zero Map and false on miss. +// The returned hash should be passed to Store on miss. +func (c *recentMapCache) Lookup(m map[string]string) (Map, uint64, bool) { + hash := c.hashMapStringString(m) + idx := hash & (recentMapCacheSize - 1) + + c.mu.Lock() + defer c.mu.Unlock() + entry := c.entries[idx] + if entry.hash == hash && entry.m.EquivalentTo(m) { + return entry.m, hash, true + } + return Map{}, hash, false +} + +// Store stores a Map in the cache at the slot determined by hash. +func (c *recentMapCache) Store(hash uint64, result Map) { + idx := hash & (recentMapCacheSize - 1) + c.mu.Lock() + defer c.mu.Unlock() + c.entries[idx] = recentMapCacheEntry{hash: hash, m: result} +} + +// hashMapStringString computes an order-independent hash of a +// map[string]string. Each key/value pair is hashed independently and XORed +// together so that iteration order does not matter. +func (c *recentMapCache) hashMapStringString(m map[string]string) uint64 { + var combined uint64 + var h maphash.Hash + h.SetSeed(c.seed) + for k, v := range m { + h.Reset() + h.WriteString(k) + h.WriteByte(0) // Separator so "ab"+"" != "a"+"b". + h.WriteString(v) + combined ^= h.Sum64() + } + return combined +} diff --git a/lib/std/uniquelabels/unique_labels.go b/lib/std/uniquelabels/unique_labels.go index cba030559f5..110b6b36976 100644 --- a/lib/std/uniquelabels/unique_labels.go +++ b/lib/std/uniquelabels/unique_labels.go @@ -48,25 +48,53 @@ type Map struct { m handleMap } +// EquivalentTo reports whether this Map contains exactly the same entries as +// the given map[string]string. Iterates the Map's handles, converting back to +// strings via pointer dereference rather than iterating the input map (which +// would require uniquestr.Make per key lookup). +func (i Map) EquivalentTo(m map[string]string) bool { + if i.IsNil() != (m == nil) { + return false + } + if len(m) != i.Len() { + return false + } + for k, v := range i.AllStrings() { + if mv, ok := m[k]; !ok || mv != v { + return false + } + } + return true +} + // Make makes an interned copy of the given map. In order to benefit from // interning the map, the original map must be discarded and only the interned // copy should be kept. // // If passed nil, returns the zero value of Map. If passed an empty map, // returns the singleton Empty. +// +// Make caches recently-returned Maps so that repeated calls with the same input +// return the same Map, avoiding redundant handleMap allocations. func Make(m map[string]string) Map { - var hm handleMap if m == nil { return Nil } if len(m) == 0 { return Empty } - hm = make(handleMap, len(m)) - for k, v := range m { - hm[uniquestr.Make(k)] = uniquestr.Make(v) + + if cached, hash, ok := recentCache.Lookup(m); ok { + return cached + } else { + hm := make(handleMap, len(m)) + for k, v := range m { + hm[uniquestr.Make(k)] = uniquestr.Make(v) + } + result := Map{m: hm} + recentCache.Store(hash, result) + return result } - return Map{m: hm} } // Equals returns true if the map contains the same key/value pairs as the diff --git a/lib/std/uniquelabels/unique_labels_test.go b/lib/std/uniquelabels/unique_labels_test.go index 5d0d636e9e1..854ab26a6e4 100644 --- a/lib/std/uniquelabels/unique_labels_test.go +++ b/lib/std/uniquelabels/unique_labels_test.go @@ -19,6 +19,7 @@ import ( "encoding/json" "fmt" "reflect" + "sync" "testing" "github.com/projectcalico/calico/lib/std/uniquestr" @@ -123,6 +124,149 @@ func TestAllHandles(t *testing.T) { } } +func sameUnderlyingMap(a, b Map) bool { + return reflect.ValueOf(a.m).UnsafePointer() == reflect.ValueOf(b.m).UnsafePointer() +} + +func TestMakeCacheHit(t *testing.T) { + input := map[string]string{"a": "b", "c": "d"} + + m1 := Make(input) + m2 := Make(input) + + // Both calls should return the same underlying handleMap from the cache. + if !sameUnderlyingMap(m1, m2) { + t.Errorf("expected Make to return cached handleMap on repeat call") + } +} + +func TestMakeCacheMiss(t *testing.T) { + m1 := Make(map[string]string{"a": "b"}) + m2 := Make(map[string]string{"x": "y"}) + + if sameUnderlyingMap(m1, m2) { + t.Errorf("different inputs should produce different Map instances") + } +} + +func TestMakeCacheEviction(t *testing.T) { + // Use a private cache so we don't interfere with other tests. + c := recentMapCache{seed: recentCache.seed} + + input1 := map[string]string{"a": "b"} + input2 := map[string]string{"x": "y"} + + // Force both inputs into the same slot by computing their hashes and + // using Store directly. + hash1 := c.hashMapStringString(input1) + m1 := Make(input1) + c.Store(hash1, m1) + + // Verify it's cached. + cached, _, ok := c.Lookup(input1) + if !ok { + t.Fatal("expected cache hit for input1") + } + if !sameUnderlyingMap(cached, m1) { + t.Fatal("cached map should be the same instance") + } + + // Overwrite the same slot with a different entry that has the same + // index (we force this by using the same hash value). + m2 := Make(input2) + c.Store(hash1, m2) // Same hash => same slot, evicts input1. + + // input1 should now miss. + _, _, ok = c.Lookup(input1) + if ok { + t.Error("expected cache miss for input1 after eviction") + } + + // input2 won't hit either because the stored hash doesn't match + // input2's real hash (we used hash1). That's fine — this tests that + // the old entry was evicted. +} + +func TestMakeCacheHashCollision(t *testing.T) { + // Two different inputs that are forced into the same cache slot + // should still return correct (different) results from Make. + // We can't easily force a natural collision, but we can verify + // that Make returns correct values regardless of cache state. + inputs := make([]map[string]string, recentMapCacheSize+1) + for i := range inputs { + inputs[i] = map[string]string{"key": fmt.Sprintf("value-%d", i)} + } + + // Fill the cache beyond capacity to force collisions. + results := make([]Map, len(inputs)) + for i, input := range inputs { + results[i] = Make(input) + } + + // Every result should have the correct content regardless of cache state. + for i, input := range inputs { + if !results[i].EquivalentTo(input) { + t.Errorf("Make result %d has wrong content", i) + } + } +} + +func TestMakeCacheConcurrent(t *testing.T) { + const goroutines = 16 + const iterations = 1000 + inputs := []map[string]string{ + {"a": "1"}, + {"b": "2"}, + {"c": "3"}, + {"a": "1", "b": "2"}, + } + + var wg sync.WaitGroup + wg.Add(goroutines) + for range goroutines { + go func() { + defer wg.Done() + for i := range iterations { + input := inputs[i%len(inputs)] + m := Make(input) + if !m.EquivalentTo(input) { + t.Errorf("concurrent Make returned wrong content") + return + } + } + }() + } + wg.Wait() +} + +func TestEquivalentTo(t *testing.T) { + for _, tc := range []struct { + name string + mapInput map[string]string + compare map[string]string + want bool + }{ + {"equal", map[string]string{"a": "b"}, map[string]string{"a": "b"}, true}, + {"different value", map[string]string{"a": "b"}, map[string]string{"a": "x"}, false}, + {"extra key", map[string]string{"a": "b"}, map[string]string{"a": "b", "c": "d"}, false}, + {"missing key", map[string]string{"a": "b", "c": "d"}, map[string]string{"a": "b"}, false}, + {"both empty", map[string]string{}, map[string]string{}, true}, + {"empty vs non-empty", map[string]string{}, map[string]string{"a": "b"}, false}, + {"both nil", nil, nil, true}, + {"nil vs empty", nil, map[string]string{}, false}, + {"empty vs nil", map[string]string{}, nil, false}, + {"nil vs non-empty", nil, map[string]string{"a": "b"}, false}, + } { + t.Run(tc.name, func(t *testing.T) { + m := Make(tc.mapInput) + got := m.EquivalentTo(tc.compare) + if got != tc.want { + t.Errorf("Make(%v).EquivalentTo(%v) = %v, want %v", tc.mapInput, tc.compare, got, tc.want) + } + }) + } +} + func TestIntersectAndFilter(t *testing.T) { for _, tc := range []struct { description string diff --git a/libcalico-go/Makefile b/libcalico-go/Makefile index 7d508a9f383..0d186903244 100644 --- a/libcalico-go/Makefile +++ b/libcalico-go/Makefile @@ -6,10 +6,13 @@ LOCAL_CHECKS = check-gen-files KIND_CONFIG = $(KIND_DIR)/kind-single.config -NETPOL_TAG = v0.1.5 -NETPOL_CRD_URL = https://raw.githubusercontent.com/kubernetes-sigs/network-policy-api/refs/tags/$(NETPOL_TAG)/config/crd/experimental -NETPOL_ANP_CRD = policy.networking.k8s.io_adminnetworkpolicies.yaml -NETPOL_BANP_CRD = policy.networking.k8s.io_baselineadminnetworkpolicies.yaml +# For a branch/tag use this format: +# NETPOL_CNP_REF = refs/heads/main +# For a commit, just use the commit ID: +# NETPOL_CNP_REF = +NETPOL_CNP_REF = 412bf65729a56bc9f0569a5e5a95685ac22350d9 +NETPOL_CNP_CRD_URL = https://raw.githubusercontent.com/kubernetes-sigs/network-policy-api/$(NETPOL_CNP_REF)/config/crd/standard +NETPOL_CNP_CRD = policy.networking.k8s.io_clusternetworkpolicies.yaml ############################################################################### # Download and include ../lib.Makefile @@ -23,23 +26,18 @@ include ../lib.Makefile BINDIR?=bin # Create a list of files upon which the generated file depends, skip the generated file itself -UPGRADE_SRCS := $(filter-out ./lib/upgrade/migrator/clients/v1/k8s/custom/zz_generated.deepcopy.go, \ - $(wildcard ./lib/upgrade/migrator/clients/v1/k8s/custom/*.go)) - -# Create a list of files upon which the generated file depends, skip the generated file itself -APIS_SRCS := $(filter-out ./lib/apis/v3/zz_generated.deepcopy.go, $(wildcard ./lib/apis/v3/*.go)) +APIS_SRCS := $(filter-out ./lib/apis/internalapi/zz_generated.deepcopy.go, $(wildcard ./lib/apis/internalapi/*.go)) .PHONY: clean clean: rm -rf $(BINDIR) checkouts - find . -name '*.coverprofile' -type f -delete + find . -name 'coverprofile.out' -type f -delete ############################################################################### # Building the binary ############################################################################### -GENERATED_FILES:=./lib/apis/v3/zz_generated.deepcopy.go \ - ./lib/upgrade/migrator/clients/v1/k8s/custom/zz_generated.deepcopy.go \ - ./lib/apis/v3/generated.openapi.go \ +GENERATED_FILES:=./lib/apis/internalapi/zz_generated.deepcopy.go \ + ./lib/apis/internalapi/generated.openapi.go \ ./lib/apis/v1/generated.openapi.go \ ./lib/selector/tokenizer/kind_string.go @@ -65,39 +63,30 @@ gen-crds: rm -f config/crd/_nodes.yaml # Remove the first yaml separator line. $(DOCKER_GO_BUILD) sh -c 'find ./config/crd -name "*.yaml" | xargs sed -i 1d' - # Add K8S AdminNetworkPolicy CRD - curl -sSfL --retry 3 $(NETPOL_CRD_URL)/$(NETPOL_ANP_CRD) -o ./config/crd/$(NETPOL_ANP_CRD) - curl -sSfL --retry 3 $(NETPOL_CRD_URL)/$(NETPOL_BANP_CRD) -o ./config/crd/$(NETPOL_BANP_CRD) + # Add K8S ClusterNetworkPolicy CRD + curl -sSfL --retry 3 $(NETPOL_CNP_CRD_URL)/$(NETPOL_CNP_CRD) -o ./config/crd/$(NETPOL_CNP_CRD) # Run prettier to fix indentation docker run --rm --user $(id -u):$(id -g) -v $(CURDIR)/config/crd/:/work/config/crd/ tmknom/prettier --write --parser=yaml /work # Patch in manual tweaks to the generated CRDs. patch -p2 < patches/0001-Add-nullable-to-IPAM-block-allocations-field.patch -./lib/upgrade/migrator/clients/v1/k8s/custom/zz_generated.deepcopy.go: $(UPGRADE_SRCS) - $(DOCKER_GO_BUILD) sh -c 'deepcopy-gen \ - --v 1 --logtostderr \ - --go-header-file "./docs/boilerplate.go.txt" \ - --bounding-dirs "github.com/projectcalico/calico/libcalico-go" \ - --output-file zz_generated.deepcopy.go \ - "$(PACKAGE_NAME)/lib/upgrade/migrator/clients/v1/k8s/custom"' - -./lib/apis/v3/zz_generated.deepcopy.go: $(APIS_SRCS) +./lib/apis/internalapi/zz_generated.deepcopy.go: $(APIS_SRCS) $(DOCKER_GO_BUILD) sh -c 'deepcopy-gen \ --v 1 --logtostderr \ --go-header-file "./docs/boilerplate.go.txt" \ --bounding-dirs "github.com/projectcalico/calico/libcalico-go" \ --output-file zz_generated.deepcopy.go \ - "$(PACKAGE_NAME)/lib/apis/v3"' + "$(PACKAGE_NAME)/lib/apis/internalapi"' # Generate OpenAPI spec -./lib/apis/v3/generated.openapi.go: $(APIS_SRCS) +./lib/apis/internalapi/generated.openapi.go: $(APIS_SRCS) $(DOCKER_GO_BUILD) \ sh -c 'openapi-gen \ --v 1 --logtostderr \ --go-header-file "./docs/boilerplate.go.txt" \ - --output-dir "./lib/apis/v3" \ - --output-pkg "$(PACKAGE_NAME)/lib/apis/v3" \ - "$(PACKAGE_NAME)/lib/apis/v3" \ + --output-dir "./lib/apis/internalapi" \ + --output-pkg "$(PACKAGE_NAME)/lib/apis/internalapi" \ + "$(PACKAGE_NAME)/lib/apis/internalapi" \ "$(PACKAGE_NAME)/lib/apis/v1"' $(DOCKER_GO_BUILD) \ @@ -142,6 +131,7 @@ ut-cover: WHAT?=. GINKGO_FOCUS?=.* +GINKGO_SKIP?= .PHONY:ut ## Run the fast set of unit tests in a container. @@ -152,11 +142,7 @@ ut: .PHONY:fv ## Run functional tests against a real datastore in a container. fv: fv-setup - $(DOCKER_RUN) --privileged \ - -e KUBECONFIG=/kubeconfig.yaml \ - -v $(KIND_KUBECONFIG):/kubeconfig.yaml \ - --dns $(shell docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' coredns) \ - $(CALICO_BUILD) sh -c 'cd /go/src/$(PACKAGE_NAME) && ginkgo -r -focus "$(GINKGO_FOCUS).*\[Datastore\]|\[Datastore\].*$(GINKGO_FOCUS)" $(WHAT)' + $(MAKE) fv-fast $(MAKE) fv-teardown ## Run the setup required to run the FVs, but don't run any FVs. @@ -171,9 +157,10 @@ fv-teardown: stop-etcd stop-etcd-tls kind-cluster-destroy stop-coredns fv-fast: $(DOCKER_RUN) --privileged \ -e KUBECONFIG=/kubeconfig.yaml \ + -e CALICO_API_GROUP=$(CALICO_API_GROUP) \ -v $(KIND_KUBECONFIG):/kubeconfig.yaml \ --dns $(shell docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' coredns) \ - $(CALICO_BUILD) sh -c 'cd /go/src/$(PACKAGE_NAME) && ginkgo -r -focus "$(GINKGO_FOCUS).*\[Datastore\]|\[Datastore\].*$(GINKGO_FOCUS)" $(WHAT)' + $(CALICO_BUILD) sh -c 'cd /go/src/$(PACKAGE_NAME) && ginkgo -r -skip "$(GINKGO_SKIP)" -focus "$(GINKGO_FOCUS).*\[Datastore\]|\[Datastore\].*$(GINKGO_FOCUS)" $(WHAT)' # Create a kind cluster, and deploy some resources required for the libcalico-go tests. # TODO: Remove the need for this extra target, these should be created by the tests that need them. diff --git a/libcalico-go/config/crd/crd.projectcalico.org_bgpconfigurations.yaml b/libcalico-go/config/crd/crd.projectcalico.org_bgpconfigurations.yaml index 1d10c04c90d..b01b07be677 100644 --- a/libcalico-go/config/crd/crd.projectcalico.org_bgpconfigurations.yaml +++ b/libcalico-go/config/crd/crd.projectcalico.org_bgpconfigurations.yaml @@ -30,6 +30,9 @@ spec: format: int32 type: integer bindMode: + enum: + - None + - NodeIP type: string communities: items: @@ -40,11 +43,14 @@ spec: pattern: ^(\d+):(\d+)$|^(\d+):(\d+):(\d+)$ type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set ignoredInterfaces: items: type: string type: array + x-kubernetes-list-type: set listenPort: maximum: 65535 minimum: 1 @@ -54,6 +60,8 @@ spec: localWorkloadPeeringIPV6: type: string logSeverityScreen: + default: Info + pattern: ^(?i)(Trace|Debug|Info|Warning|Error|Fatal)?$ type: string nodeMeshMaxRestartTime: type: string @@ -79,27 +87,36 @@ spec: items: properties: cidr: + format: cidr type: string communities: items: type: string type: array type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceClusterIPs: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceExternalIPs: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceLoadBalancerAggregation: default: Enabled enum: @@ -110,9 +127,12 @@ spec: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set type: object type: object served: true diff --git a/libcalico-go/config/crd/crd.projectcalico.org_bgpfilters.yaml b/libcalico-go/config/crd/crd.projectcalico.org_bgpfilters.yaml index 826d3f2c4fd..3eb7eb87963 100644 --- a/libcalico-go/config/crd/crd.projectcalico.org_bgpfilters.yaml +++ b/libcalico-go/config/crd/crd.projectcalico.org_bgpfilters.yaml @@ -30,12 +30,21 @@ spec: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -50,22 +59,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array exportV6: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -80,22 +102,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array importV4: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -110,22 +145,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array importV6: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -140,11 +188,15 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array type: object type: object diff --git a/libcalico-go/config/crd/crd.projectcalico.org_bgppeers.yaml b/libcalico-go/config/crd/crd.projectcalico.org_bgppeers.yaml index 8e05dbc1935..ce6220f9944 100644 --- a/libcalico-go/config/crd/crd.projectcalico.org_bgppeers.yaml +++ b/libcalico-go/config/crd/crd.projectcalico.org_bgppeers.yaml @@ -45,15 +45,10 @@ spec: maxRestartTime: type: string nextHopMode: - allOf: - - enum: - - Auto - - Self - - Keep - - enum: - - Auto - - Self - - Keep + enum: + - Auto + - Self + - Keep type: string node: type: string @@ -85,11 +80,18 @@ spec: reachableBy: type: string reversePeering: - enum: - - Auto - - Manual + allOf: + - enum: + - Auto + - Manual + - enum: + - Auto + - Manual type: string sourceAddress: + enum: + - UseNodeIP + - None type: string ttlSecurity: type: integer diff --git a/libcalico-go/config/crd/crd.projectcalico.org_caliconodestatuses.yaml b/libcalico-go/config/crd/crd.projectcalico.org_caliconodestatuses.yaml index 166fa6d545f..c9e04ba6e31 100644 --- a/libcalico-go/config/crd/crd.projectcalico.org_caliconodestatuses.yaml +++ b/libcalico-go/config/crd/crd.projectcalico.org_caliconodestatuses.yaml @@ -28,6 +28,10 @@ spec: properties: classes: items: + enum: + - Agent + - BGP + - Routes type: string type: array node: @@ -49,6 +53,9 @@ spec: routerID: type: string state: + enum: + - Ready + - NotReady type: string version: type: string @@ -62,6 +69,9 @@ spec: routerID: type: string state: + enum: + - Ready + - NotReady type: string version: type: string @@ -85,8 +95,20 @@ spec: since: type: string state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close type: string type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer type: string type: object type: array @@ -98,8 +120,20 @@ spec: since: type: string state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close type: string type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer type: string type: object type: array @@ -129,9 +163,18 @@ spec: peerIP: type: string sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer type: string type: object type: + enum: + - FIB + - RIB type: string type: object type: array @@ -149,9 +192,18 @@ spec: peerIP: type: string sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer type: string type: object type: + enum: + - FIB + - RIB type: string type: object type: array diff --git a/libcalico-go/config/crd/crd.projectcalico.org_felixconfigurations.yaml b/libcalico-go/config/crd/crd.projectcalico.org_felixconfigurations.yaml index 96a2d3bf490..f6efec65c52 100644 --- a/libcalico-go/config/crd/crd.projectcalico.org_felixconfigurations.yaml +++ b/libcalico-go/config/crd/crd.projectcalico.org_felixconfigurations.yaml @@ -297,15 +297,9 @@ spec: if it detects the current value is 2 (strict mode that hurts performance). When set to "Strict", Felix will not modify the JIT hardening setting. [Default: Auto] type: string - bpfKubeProxyEndpointSlicesEnabled: + bpfKubeProxyHealthzPort: description: |- - BPFKubeProxyEndpointSlicesEnabled is deprecated and has no effect. BPF - kube-proxy always accepts endpoint slices. This option will be removed in - the next release. - type: boolean - bpfKubeProxyHealtzPort: - description: |- - BPFKubeProxyHealtzPort, in BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. + BPFKubeProxyHealthzPort, in BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. The health check server is used by external load balancers to determine if this node should receive traffic. [Default: 10256] type: integer bpfKubeProxyIptablesCleanupEnabled: @@ -346,6 +340,23 @@ spec: [Default: Off]. pattern: ^(?i)(Off|Info|Debug)?$ type: string + bpfMaglevMaxEndpointsPerService: + description: |- + BPFMaglevMaxEndpointsPerService is the maximum number of endpoints + expected to be part of a single Maglev-enabled service. + + Influences the size of the per-service Maglev lookup-tables generated by Felix + and thus the amount of memory reserved. + + [Default: 100] + type: integer + bpfMaglevMaxServices: + description: |- + BPFMaglevMaxServices is the maximum number of expected Maglev-enabled + services that Felix will allocate lookup-tables for. + + [Default: 100] + type: integer bpfMapSizeConntrack: description: |- BPFMapSizeConntrack sets the size for the conntrack map. This map must be large enough to hold @@ -434,17 +445,14 @@ spec: type: string bpfRedirectToPeer: description: |- - BPFRedirectToPeer controls which whether it is allowed to forward straight to the - peer side of the workload devices. It is allowed for any host L2 devices by default - (L2Only), but it breaks TCP dump on the host side of workload device as it bypasses - it on ingress. Value of Enabled also allows redirection from L3 host devices like - IPIP tunnel or Wireguard directly to the peer side of the workload's device. This - makes redirection faster, however, it breaks tools like tcpdump on the peer side. - Use Enabled with caution. [Default: L2Only] + BPFRedirectToPeer controls whether traffic may be forwarded directly to the peer side of a workload’s device. + Note that the legacy "L2Only" option is now deprecated and if set it is treated like "Enabled. + Setting this option to "Enabled" allows direct redirection (including from L3 host devices such as IPIP tunnels or WireGuard), + which can improve redirection performance but causes the redirected packets to bypass the host‑side ingress path. + As a result, packet‑capture tools on the host side of the workload device (for example, tcpdump) will not see that traffic. [Default: Enabled] enum: - Enabled - Disabled - - L2Only type: string cgroupV2Path: description: @@ -795,6 +803,10 @@ spec: Warning: changing this on a running system can leave "orphaned" rules in the "other" backend. These should be cleaned up to avoid confusing interactions. + enum: + - Legacy + - NFT + - Auto pattern: ^(?i)(Auto|Legacy|NFT)?$ type: string iptablesFilterAllowAction: @@ -810,26 +822,10 @@ spec: with an iptables "DROP" action. If you want to use "REJECT" action instead you can configure it in here. pattern: ^(?i)(Drop|Reject)?$ type: string - iptablesLockFilePath: - description: |- - IptablesLockFilePath is the location of the iptables lock file. You may need to change this - if the lock file is not in its standard location (for example if you have mapped it into Felix's - container at a different path). [Default: /run/xtables.lock] - type: string iptablesLockProbeInterval: description: |- - IptablesLockProbeInterval when IptablesLockTimeout is enabled: the time that Felix will wait between - attempts to acquire the iptables lock if it is not available. Lower values make Felix more - responsive when the lock is contended, but use more CPU. [Default: 50ms] - pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ - type: string - iptablesLockTimeout: - description: |- - IptablesLockTimeout is the time that Felix itself will wait for the iptables lock (rather than delegating the - lock handling to the `iptables` command). - - Deprecated: `iptables-restore` v1.8+ always takes the lock, so enabling this feature results in deadlock. - [Default: 0s disabled] + IptablesLockProbeInterval configures the interval between attempts to claim + the xtables lock. Shorter intervals are more responsive but use more CPU. [Default: 50ms] pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ type: string iptablesMangleAllowAction: @@ -889,6 +885,19 @@ spec: pattern: ^.* x-kubernetes-int-or-string: true type: array + logActionRateLimit: + description: |- + LogActionRateLimit sets the rate of hitting a Log action. The value must be in the format "N/unit", + where N is a number and unit is one of: second, minute, hour, or day. For example: "10/second" or "100/hour". + pattern: ^[1-9]\d{0,3}/(?:second|minute|hour|day)$ + type: string + logActionRateLimitBurst: + description: + LogActionRateLimitBurst sets the rate limit burst of + hitting a Log action when LogActionRateLimit is enabled. + maximum: 9999 + minimum: 0 + type: integer logDebugFilenameRegex: description: |- LogDebugFilenameRegex controls which source code files have their Debug log output included in the logs. @@ -901,9 +910,17 @@ spec: none to disable file logging. [Default: /var/log/calico/felix.log]" type: string logPrefix: - description: - "LogPrefix is the log prefix that Felix uses when rendering - LOG rules. [Default: calico-packet]" + description: |- + LogPrefix is the log prefix that Felix uses when rendering LOG rules. It is possible to use the following specifiers + to include extra information in the log prefix. + - %t: Tier name. + - %k: Kind (short names). + - %n: Policy or profile name. + - %p: Policy or profile name (namespace/name for namespaced kinds or just name for non namespaced kinds). + Calico includes ": " characters at the end of the generated log prefix. + Note that iptables shows up to 29 characters for the log prefix and nftables up to 127 characters. Extra characters are truncated. + [Default: calico-packet] + pattern: "^([a-zA-Z0-9%: /_-])*$" type: string logSeverityFile: description: @@ -1007,9 +1024,10 @@ spec: format: int32 type: integer nftablesMode: + default: Auto description: "NFTablesMode configures nftables support in Felix. [Default: - Disabled]" + Auto]" enum: - Disabled - Enabled @@ -1045,6 +1063,21 @@ spec: PrometheusGoMetricsEnabled disables Go runtime metrics collection, which the Prometheus client does by default, when set to false. This reduces the number of metrics reported, reducing Prometheus load. [Default: true] type: boolean + prometheusMetricsCAFile: + description: |- + PrometheusMetricsCAFile defines the absolute path to the TLS CA certificate file used for securing the /metrics endpoint. + This certificate must be valid and accessible by the calico-node process. + type: string + prometheusMetricsCertFile: + description: |- + PrometheusMetricsCertFile defines the absolute path to the TLS certificate file used for securing the /metrics endpoint. + This certificate must be valid and accessible by the calico-node process. + type: string + prometheusMetricsClientAuth: + description: |- + PrometheusMetricsClientAuth specifies the client authentication type for the /metrics endpoint. + This determines how the server validates client certificates. Default is "RequireAndVerifyClientCert". + type: string prometheusMetricsEnabled: description: "PrometheusMetricsEnabled enables the Prometheus metrics @@ -1055,6 +1088,11 @@ spec: "PrometheusMetricsHost is the host that the Prometheus metrics server should bind to. [Default: empty]" type: string + prometheusMetricsKeyFile: + description: |- + PrometheusMetricsKeyFile defines the absolute path to the private key file corresponding to the TLS certificate + used for securing the /metrics endpoint. The private key must be valid and accessible by the calico-node process. + type: string prometheusMetricsPort: description: "PrometheusMetricsPort is the TCP port that the Prometheus diff --git a/libcalico-go/config/crd/crd.projectcalico.org_globalnetworkpolicies.yaml b/libcalico-go/config/crd/crd.projectcalico.org_globalnetworkpolicies.yaml index 324f2171a21..5b6335d9200 100644 --- a/libcalico-go/config/crd/crd.projectcalico.org_globalnetworkpolicies.yaml +++ b/libcalico-go/config/crd/crd.projectcalico.org_globalnetworkpolicies.yaml @@ -34,6 +34,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -43,6 +48,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -73,6 +79,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -103,11 +110,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -119,8 +133,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -143,6 +161,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -173,6 +192,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -192,6 +212,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -201,6 +226,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -231,6 +257,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -261,11 +288,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -277,8 +311,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -301,6 +339,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -331,6 +370,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -352,6 +392,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array preDNAT: @@ -361,11 +403,18 @@ spec: serviceAccountSelector: type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true diff --git a/libcalico-go/config/crd/crd.projectcalico.org_globalnetworksets.yaml b/libcalico-go/config/crd/crd.projectcalico.org_globalnetworksets.yaml index 11c05760571..615c08528b8 100644 --- a/libcalico-go/config/crd/crd.projectcalico.org_globalnetworksets.yaml +++ b/libcalico-go/config/crd/crd.projectcalico.org_globalnetworksets.yaml @@ -30,6 +30,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true diff --git a/libcalico-go/config/crd/crd.projectcalico.org_hostendpoints.yaml b/libcalico-go/config/crd/crd.projectcalico.org_hostendpoints.yaml index d9b542930e0..bd727425279 100644 --- a/libcalico-go/config/crd/crd.projectcalico.org_hostendpoints.yaml +++ b/libcalico-go/config/crd/crd.projectcalico.org_hostendpoints.yaml @@ -30,6 +30,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set interfaceName: type: string node: @@ -40,6 +41,8 @@ spec: name: type: string port: + maximum: 65535 + minimum: 0 type: integer protocol: anyOf: @@ -57,6 +60,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true diff --git a/libcalico-go/config/crd/crd.projectcalico.org_ipamblocks.yaml b/libcalico-go/config/crd/crd.projectcalico.org_ipamblocks.yaml index 50a0452832e..27b1a8e9cdd 100644 --- a/libcalico-go/config/crd/crd.projectcalico.org_ipamblocks.yaml +++ b/libcalico-go/config/crd/crd.projectcalico.org_ipamblocks.yaml @@ -28,6 +28,9 @@ spec: properties: affinity: type: string + affinityClaimTime: + format: date-time + type: string allocations: items: type: integer @@ -38,6 +41,10 @@ spec: attributes: items: properties: + alternate: + additionalProperties: + type: string + type: object handle_id: type: string secondary: diff --git a/libcalico-go/config/crd/crd.projectcalico.org_ipamconfigs.yaml b/libcalico-go/config/crd/crd.projectcalico.org_ipamconfigs.yaml index b7e9260b9c2..5cbb52cb2c5 100644 --- a/libcalico-go/config/crd/crd.projectcalico.org_ipamconfigs.yaml +++ b/libcalico-go/config/crd/crd.projectcalico.org_ipamconfigs.yaml @@ -28,6 +28,11 @@ spec: properties: autoAllocateBlocks: type: boolean + kubeVirtVMAddressPersistence: + enum: + - Enabled + - Disabled + type: string maxBlocksPerHost: maximum: 2147483647 minimum: 0 diff --git a/libcalico-go/config/crd/crd.projectcalico.org_ippools.yaml b/libcalico-go/config/crd/crd.projectcalico.org_ippools.yaml index e2f990de36b..afe3963be8a 100644 --- a/libcalico-go/config/crd/crd.projectcalico.org_ippools.yaml +++ b/libcalico-go/config/crd/crd.projectcalico.org_ippools.yaml @@ -28,39 +28,47 @@ spec: properties: allowedUses: items: + enum: + - Workload + - Tunnel + - LoadBalancer type: string type: array + x-kubernetes-list-type: set assignmentMode: + default: Automatic enum: - Automatic - Manual type: string blockSize: + maximum: 128 + minimum: 0 type: integer cidr: + format: cidr type: string disableBGPExport: type: boolean disabled: type: boolean - ipip: - properties: - enabled: - type: boolean - mode: - type: string - type: object ipipMode: + enum: + - Never + - Always + - CrossSubnet type: string namespaceSelector: type: string - nat-outgoing: - type: boolean natOutgoing: type: boolean nodeSelector: type: string vxlanMode: + enum: + - Never + - Always + - CrossSubnet type: string required: - cidr diff --git a/libcalico-go/config/crd/crd.projectcalico.org_ipreservations.yaml b/libcalico-go/config/crd/crd.projectcalico.org_ipreservations.yaml index 61ddf451f11..251ba2b7be4 100644 --- a/libcalico-go/config/crd/crd.projectcalico.org_ipreservations.yaml +++ b/libcalico-go/config/crd/crd.projectcalico.org_ipreservations.yaml @@ -27,9 +27,11 @@ spec: spec: properties: reservedCIDRs: + format: cidr items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true diff --git a/libcalico-go/config/crd/crd.projectcalico.org_kubecontrollersconfigurations.yaml b/libcalico-go/config/crd/crd.projectcalico.org_kubecontrollersconfigurations.yaml index c2cacd9e217..c895fc6903d 100644 --- a/libcalico-go/config/crd/crd.projectcalico.org_kubecontrollersconfigurations.yaml +++ b/libcalico-go/config/crd/crd.projectcalico.org_kubecontrollersconfigurations.yaml @@ -31,6 +31,10 @@ spec: loadBalancer: properties: assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly type: string type: object namespace: @@ -43,6 +47,9 @@ spec: hostEndpoint: properties: autoCreate: + enum: + - Enabled + - Disabled type: string createDefaultHostEndpoint: type: string @@ -56,7 +63,8 @@ spec: items: type: string type: array - interfaceSelector: + x-kubernetes-list-type: set + interfacePattern: type: string labels: additionalProperties: @@ -72,6 +80,9 @@ spec: reconcilerPeriod: type: string syncLabels: + enum: + - Enabled + - Disabled type: string type: object policy: @@ -79,6 +90,15 @@ spec: reconcilerPeriod: type: string type: object + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object serviceAccount: properties: reconcilerPeriod: @@ -92,14 +112,30 @@ spec: type: object debugProfilePort: format: int32 + maximum: 65535 + minimum: 0 type: integer etcdV3CompactionPeriod: type: string healthChecks: + default: Enabled + enum: + - Enabled + - Disabled type: string logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic type: string prometheusMetricsPort: + maximum: 65535 + minimum: 0 type: integer required: - controllers @@ -117,6 +153,10 @@ spec: loadBalancer: properties: assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly type: string type: object namespace: @@ -129,6 +169,9 @@ spec: hostEndpoint: properties: autoCreate: + enum: + - Enabled + - Disabled type: string createDefaultHostEndpoint: type: string @@ -142,7 +185,8 @@ spec: items: type: string type: array - interfaceSelector: + x-kubernetes-list-type: set + interfacePattern: type: string labels: additionalProperties: @@ -158,6 +202,9 @@ spec: reconcilerPeriod: type: string syncLabels: + enum: + - Enabled + - Disabled type: string type: object policy: @@ -165,6 +212,15 @@ spec: reconcilerPeriod: type: string type: object + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object serviceAccount: properties: reconcilerPeriod: @@ -178,14 +234,30 @@ spec: type: object debugProfilePort: format: int32 + maximum: 65535 + minimum: 0 type: integer etcdV3CompactionPeriod: type: string healthChecks: + default: Enabled + enum: + - Enabled + - Disabled type: string logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic type: string prometheusMetricsPort: + maximum: 65535 + minimum: 0 type: integer required: - controllers @@ -194,3 +266,5 @@ spec: type: object served: true storage: true + subresources: + status: {} diff --git a/libcalico-go/config/crd/crd.projectcalico.org_networkpolicies.yaml b/libcalico-go/config/crd/crd.projectcalico.org_networkpolicies.yaml index e15f411dbf9..3b13ce41832 100644 --- a/libcalico-go/config/crd/crd.projectcalico.org_networkpolicies.yaml +++ b/libcalico-go/config/crd/crd.projectcalico.org_networkpolicies.yaml @@ -30,6 +30,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -39,6 +44,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -69,6 +75,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -99,11 +106,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -115,8 +129,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -139,6 +157,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -169,6 +188,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -188,6 +208,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -197,6 +222,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -227,6 +253,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -257,11 +284,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -273,8 +307,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -297,6 +335,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -327,6 +366,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -346,6 +386,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array selector: @@ -353,11 +395,18 @@ spec: serviceAccountSelector: type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true diff --git a/libcalico-go/config/crd/crd.projectcalico.org_networksets.yaml b/libcalico-go/config/crd/crd.projectcalico.org_networksets.yaml index 26b2f55e472..7e43fbd8f03 100644 --- a/libcalico-go/config/crd/crd.projectcalico.org_networksets.yaml +++ b/libcalico-go/config/crd/crd.projectcalico.org_networksets.yaml @@ -30,6 +30,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true diff --git a/libcalico-go/config/crd/crd.projectcalico.org_stagedglobalnetworkpolicies.yaml b/libcalico-go/config/crd/crd.projectcalico.org_stagedglobalnetworkpolicies.yaml index 9c069717f5e..82301073935 100644 --- a/libcalico-go/config/crd/crd.projectcalico.org_stagedglobalnetworkpolicies.yaml +++ b/libcalico-go/config/crd/crd.projectcalico.org_stagedglobalnetworkpolicies.yaml @@ -34,6 +34,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -43,6 +48,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -73,6 +79,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -103,11 +110,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -119,8 +133,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -143,6 +161,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -173,6 +192,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -192,6 +212,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -201,6 +226,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -231,6 +257,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -261,11 +288,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -277,8 +311,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -301,6 +339,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -331,6 +370,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -352,6 +392,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array preDNAT: @@ -361,13 +403,25 @@ spec: serviceAccountSelector: type: string stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true diff --git a/libcalico-go/config/crd/crd.projectcalico.org_stagedkubernetesnetworkpolicies.yaml b/libcalico-go/config/crd/crd.projectcalico.org_stagedkubernetesnetworkpolicies.yaml index 094fc67791a..242e6cd2cb5 100644 --- a/libcalico-go/config/crd/crd.projectcalico.org_stagedkubernetesnetworkpolicies.yaml +++ b/libcalico-go/config/crd/crd.projectcalico.org_stagedkubernetesnetworkpolicies.yaml @@ -237,8 +237,16 @@ spec: policyTypes: items: type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string type: object type: object diff --git a/libcalico-go/config/crd/crd.projectcalico.org_stagednetworkpolicies.yaml b/libcalico-go/config/crd/crd.projectcalico.org_stagednetworkpolicies.yaml index 7ca505bcb11..99c60f94f6c 100644 --- a/libcalico-go/config/crd/crd.projectcalico.org_stagednetworkpolicies.yaml +++ b/libcalico-go/config/crd/crd.projectcalico.org_stagednetworkpolicies.yaml @@ -30,6 +30,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -39,6 +44,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -69,6 +75,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -99,11 +106,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -115,8 +129,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -139,6 +157,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -169,6 +188,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -188,6 +208,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -197,6 +222,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -227,6 +253,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -257,11 +284,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -273,8 +307,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -297,6 +335,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -327,6 +366,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -346,6 +386,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array selector: @@ -353,13 +395,25 @@ spec: serviceAccountSelector: type: string stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true diff --git a/libcalico-go/config/crd/crd.projectcalico.org_tiers.yaml b/libcalico-go/config/crd/crd.projectcalico.org_tiers.yaml index 8d1b3312054..e74d9238a3e 100644 --- a/libcalico-go/config/crd/crd.projectcalico.org_tiers.yaml +++ b/libcalico-go/config/crd/crd.projectcalico.org_tiers.yaml @@ -27,13 +27,35 @@ spec: spec: properties: defaultAction: - enum: - - Pass - - Deny + allOf: + - enum: + - Allow + - Deny + - Log + - Pass + - enum: + - Pass + - Deny type: string order: type: number type: object + required: + - metadata + - spec type: object + x-kubernetes-validations: + - message: The 'kube-admin' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-admin' ? self.spec.defaultAction == + 'Pass' : true" + - message: The 'kube-baseline' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-baseline' ? self.spec.defaultAction + == 'Pass' : true" + - message: The 'default' tier must have default action 'Deny' + rule: + "self.metadata.name == 'default' ? self.spec.defaultAction == 'Deny' + : true" served: true storage: true diff --git a/libcalico-go/config/crd/policy.networking.k8s.io_baselineadminnetworkpolicies.yaml b/libcalico-go/config/crd/policy.networking.k8s.io_baselineadminnetworkpolicies.yaml deleted file mode 100644 index fddc29a85f3..00000000000 --- a/libcalico-go/config/crd/policy.networking.k8s.io_baselineadminnetworkpolicies.yaml +++ /dev/null @@ -1,1083 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/30 - policy.networking.k8s.io/bundle-version: v0.1.1 - policy.networking.k8s.io/channel: experimental - creationTimestamp: null - name: baselineadminnetworkpolicies.policy.networking.k8s.io -spec: - group: policy.networking.k8s.io - names: - kind: BaselineAdminNetworkPolicy - listKind: BaselineAdminNetworkPolicyList - plural: baselineadminnetworkpolicies - shortNames: - - banp - singular: baselineadminnetworkpolicy - scope: Cluster - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: |- - BaselineAdminNetworkPolicy is a cluster level resource that is part of the - AdminNetworkPolicy API. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: Specification of the desired behavior of BaselineAdminNetworkPolicy. - properties: - egress: - description: |- - Egress is the list of Egress rules to be applied to the selected pods if - they are not matched by any AdminNetworkPolicy or NetworkPolicy rules. - A total of 100 Egress rules will be allowed in each BANP instance. - The relative precedence of egress rules within a single BANP object - will be determined by the order in which the rule is written. - Thus, a rule that appears at the top of the egress rules - would take the highest precedence. - BANPs with no egress rules do not affect egress traffic. - - - Support: Core - items: - description: |- - BaselineAdminNetworkPolicyEgressRule describes an action to take on a particular - set of traffic originating from pods selected by a BaselineAdminNetworkPolicy's - Subject field. - - properties: - action: - description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic - Deny: denies the selected traffic - - - Support: Core - enum: - - Allow - - Deny - type: string - name: - description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - BaselineAdminNetworkPolicies. - - - Support: Core - maxLength: 100 - type: string - ports: - description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of destination ports for the outgoing egress traffic. - If Ports is not set then the rule does not filter traffic via port. - items: - description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). - Exactly one field must be set. - maxProperties: 1 - minProperties: 1 - properties: - namedPort: - description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - - type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core - properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol - type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core - properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start - type: object - type: object - maxItems: 100 - type: array - to: - description: |- - To is the list of destinations whose traffic this rule applies to. - If any AdminNetworkPolicyEgressPeer matches the destination of outgoing - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core - items: - description: |- - AdminNetworkPolicyEgressPeer defines a peer to allow traffic to. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: |- - Namespaces defines a way to select all pods within a set of Namespaces. - Note that host-networked pods are not included in this type of peer. - - - Support: Core - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - networks: - description: |- - Networks defines a way to select peers via CIDR blocks. - This is intended for representing entities that live outside the cluster, - which can't be selected by pods, namespaces and nodes peers, but note - that cluster-internal traffic will be checked against the rule as - well. So if you Allow or Deny traffic to `"0.0.0.0/0"`, that will allow - or deny all IPv4 pod-to-pod traffic as well. If you don't want that, - add a rule that Passes all pod traffic before the Networks rule. - - - Each item in Networks should be provided in the CIDR format and should be - IPv4 or IPv6, for example "10.0.0.0/8" or "fd00::/8". - - - Networks can have upto 25 CIDRs specified. - - - Support: Extended - - - - items: - description: |- - CIDR is an IP address range in CIDR notation (for example, "10.0.0.0/8" or "fd00::/8"). - This string must be validated by implementations using net.ParseCIDR - TODO: Introduce CEL CIDR validation regex isCIDR() in Kube 1.31 when it is available. - maxLength: 43 - type: string - x-kubernetes-validations: - - message: - CIDR must be either an IPv4 or IPv6 address. - IPv4 address embedded in IPv6 addresses are not - supported - rule: self.contains(':') != self.contains('.') - maxItems: 25 - minItems: 1 - type: array - x-kubernetes-list-type: set - nodes: - description: |- - Nodes defines a way to select a set of nodes in - the cluster. This field follows standard label selector - semantics; if present but empty, it selects all Nodes. - - - Support: Extended - - - - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: |- - Pods defines a way to select a set of pods in - a set of namespaces. Note that host-networked pods - are not included in this type of peer. - - - Support: Core - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - maxItems: 100 - minItems: 1 - type: array - required: - - action - - to - type: object - x-kubernetes-validations: - - message: - networks/nodes peer cannot be set with namedPorts since - there are no namedPorts for networks/nodes - rule: - "!(self.to.exists(peer, has(peer.networks) || has(peer.nodes)) - && has(self.ports) && self.ports.exists(port, has(port.namedPort)))" - maxItems: 100 - type: array - ingress: - description: |- - Ingress is the list of Ingress rules to be applied to the selected pods - if they are not matched by any AdminNetworkPolicy or NetworkPolicy rules. - A total of 100 Ingress rules will be allowed in each BANP instance. - The relative precedence of ingress rules within a single BANP object - will be determined by the order in which the rule is written. - Thus, a rule that appears at the top of the ingress rules - would take the highest precedence. - BANPs with no ingress rules do not affect ingress traffic. - - - Support: Core - items: - description: |- - BaselineAdminNetworkPolicyIngressRule describes an action to take on a particular - set of traffic destined for pods selected by a BaselineAdminNetworkPolicy's - Subject field. - properties: - action: - description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic - Deny: denies the selected traffic - - - Support: Core - enum: - - Allow - - Deny - type: string - from: - description: |- - From is the list of sources whose traffic this rule applies to. - If any AdminNetworkPolicyIngressPeer matches the source of incoming - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core - items: - description: |- - AdminNetworkPolicyIngressPeer defines an in-cluster peer to allow traffic from. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: |- - Namespaces defines a way to select all pods within a set of Namespaces. - Note that host-networked pods are not included in this type of peer. - - - Support: Core - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: |- - Pods defines a way to select a set of pods in - a set of namespaces. Note that host-networked pods - are not included in this type of peer. - - - Support: Core - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - maxItems: 100 - minItems: 1 - type: array - name: - description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - BaselineAdminNetworkPolicies. - - - Support: Core - maxLength: 100 - type: string - ports: - description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of ports which should be matched on - the pods selected for this policy i.e the subject of the policy. - So it matches on the destination port for the ingress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core - items: - description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). - Exactly one field must be set. - maxProperties: 1 - minProperties: 1 - properties: - namedPort: - description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - - type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core - properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol - type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core - properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start - type: object - type: object - maxItems: 100 - type: array - required: - - action - - from - type: object - maxItems: 100 - type: array - subject: - description: |- - Subject defines the pods to which this BaselineAdminNetworkPolicy applies. - Note that host-networked pods are not included in subject selection. - - - Support: Core - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: Namespaces is used to select pods via namespace selectors. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: - Pods is used to select pods via namespace AND pod - selectors. - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - required: - - subject - type: object - status: - description: Status is the status to be reported by the implementation. - properties: - conditions: - items: - description: - "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - required: - - conditions - type: object - required: - - metadata - - spec - type: object - x-kubernetes-validations: - - message: - Only one baseline admin network policy with metadata.name="default" - can be created in the cluster - rule: self.metadata.name == 'default' - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: null - storedVersions: null diff --git a/libcalico-go/config/crd/policy.networking.k8s.io_adminnetworkpolicies.yaml b/libcalico-go/config/crd/policy.networking.k8s.io_clusternetworkpolicies.yaml similarity index 58% rename from libcalico-go/config/crd/policy.networking.k8s.io_adminnetworkpolicies.yaml rename to libcalico-go/config/crd/policy.networking.k8s.io_clusternetworkpolicies.yaml index 3fd0b0f5a9b..cced723679c 100644 --- a/libcalico-go/config/crd/policy.networking.k8s.io_adminnetworkpolicies.yaml +++ b/libcalico-go/config/crd/policy.networking.k8s.io_clusternetworkpolicies.yaml @@ -2,35 +2,35 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/30 - policy.networking.k8s.io/bundle-version: v0.1.1 - policy.networking.k8s.io/channel: experimental - creationTimestamp: null - name: adminnetworkpolicies.policy.networking.k8s.io + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/300 + policy.networking.k8s.io/bundle-version: v0.1.7 + policy.networking.k8s.io/channel: standard + name: clusternetworkpolicies.policy.networking.k8s.io spec: group: policy.networking.k8s.io names: - kind: AdminNetworkPolicy - listKind: AdminNetworkPolicyList - plural: adminnetworkpolicies + kind: ClusterNetworkPolicy + listKind: ClusterNetworkPolicyList + plural: clusternetworkpolicies shortNames: - - anp - singular: adminnetworkpolicy + - cnp + singular: clusternetworkpolicy scope: Cluster versions: - additionalPrinterColumns: + - jsonPath: .spec.tier + name: Tier + type: string - jsonPath: .spec.priority name: Priority type: string - jsonPath: .metadata.creationTimestamp name: Age type: date - name: v1alpha1 + name: v1alpha2 schema: openAPIV3Schema: - description: |- - AdminNetworkPolicy is a cluster level resource that is part of the - AdminNetworkPolicy API. + description: ClusterNetworkPolicy is a cluster-wide network policy resource. properties: apiVersion: description: |- @@ -50,172 +50,233 @@ spec: metadata: type: object spec: - description: Specification of the desired behavior of AdminNetworkPolicy. + description: Spec defines the desired behavior of ClusterNetworkPolicy. properties: egress: description: |- Egress is the list of Egress rules to be applied to the selected pods. - A total of 100 rules will be allowed in each ANP instance. - The relative precedence of egress rules within a single ANP object (all of - which share the priority) will be determined by the order in which the rule - is written. Thus, a rule that appears at the top of the egress rules - would take the highest precedence. - ANPs with no egress rules do not affect egress traffic. + A maximum of 25 rules is allowed in this block. - Support: Core + The relative precedence of egress rules within a single CNP object + (all of which share the priority) will be determined by the order + in which the rule is written. + Thus, a rule that appears at the top of the egress rules + would take the highest precedence. + CNPs with no egress rules do not affect egress traffic. items: description: |- - AdminNetworkPolicyEgressRule describes an action to take on a particular - set of traffic originating from pods selected by a AdminNetworkPolicy's + ClusterNetworkPolicyEgressRule describes an action to take on a particular + set of traffic originating from pods selected by a ClusterNetworkPolicy's Subject field. + properties: action: description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic (even if it would otherwise have been denied by NetworkPolicy) - Deny: denies the selected traffic - Pass: instructs the selected traffic to skip any remaining ANP rules, and - then pass execution to any NetworkPolicies that select the pod. - If the pod is not selected by any NetworkPolicies then execution - is passed to any BaselineAdminNetworkPolicies that select the pod. + Action specifies the effect this rule will have on matching + traffic. Currently the following actions are supported: + + - Accept: Accepts the selected traffic, allowing it to + egress. No further ClusterNetworkPolicy or NetworkPolicy + rules will be processed. + - Deny: Drops the selected traffic. No further + ClusterNetworkPolicy or NetworkPolicy rules will be + processed. - Support: Core + - Pass: Skips all further ClusterNetworkPolicy rules in the + current tier for the selected traffic, and passes + evaluation to the next tier. enum: - - Allow + - Accept - Deny - Pass type: string name: description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - AdminNetworkPolicies. - - - Support: Core + Name is an identifier for this rule, that may be no more than + 100 characters in length. This field should be used by the implementation + to help improve observability, readability and error-reporting + for any applied policies. maxLength: 100 type: string - ports: + protocols: description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of destination ports for the outgoing egress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core + Protocols allows for more fine-grain matching of traffic on + protocol-specific attributes such as the port. If + unspecified, protocol-specific attributes will not be used + to match traffic. items: description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). + ClusterNetworkPolicyProtocol describes additional protocol-specific match rules. Exactly one field must be set. maxProperties: 1 minProperties: 1 properties: - namedPort: + destinationNamedPort: description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - + DestinationNamedPort selects a destination port on a pod based on the + ContainerPort name. You can't use this in a rule that targets resources + without named ports (e.g. Nodes or Networks). type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core + sctp: + description: SCTP specific protocol matches. + minProperties: 1 properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core + tcp: + description: TCP specific protocol matches. + minProperties: 1 properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + udp: + description: UDP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object type: object - maxItems: 100 + maxItems: 25 + minItems: 1 type: array to: description: |- - To is the List of destinations whose traffic this rule applies to. - If any AdminNetworkPolicyEgressPeer matches the destination of outgoing - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core + To is the list of destinations whose traffic this rule applies to. If any + element matches the destination of outgoing traffic then the specified + action is applied. This field must be defined and contain at least one + item. items: description: |- - AdminNetworkPolicyEgressPeer defines a peer to allow traffic to. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. + ClusterNetworkPolicyEgressPeer defines a peer to allow traffic to. + + Exactly one of the fields must be set for a given peer and this is enforced + by the validation rules on the CRD. If an implementation sees no fields are + set then it can infer that the deployed CRD is of an incompatible version + with an unknown field. In that case it should fail closed. + + For "Accept" rules, "fail closed" means: "treat the rule as matching no + traffic". For "Deny" and "Pass" rules, "fail closed" means: "treat the rule + as a 'Deny all' rule". maxProperties: 1 minProperties: 1 properties: @@ -223,9 +284,6 @@ spec: description: |- Namespaces defines a way to select all pods within a set of Namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: matchExpressions: description: @@ -255,11 +313,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -276,107 +336,37 @@ spec: This is intended for representing entities that live outside the cluster, which can't be selected by pods, namespaces and nodes peers, but note that cluster-internal traffic will be checked against the rule as - well. So if you Allow or Deny traffic to `"0.0.0.0/0"`, that will allow + well. So if you Accept or Deny traffic to `"0.0.0.0/0"`, that will allow or deny all IPv4 pod-to-pod traffic as well. If you don't want that, add a rule that Passes all pod traffic before the Networks rule. - Each item in Networks should be provided in the CIDR format and should be IPv4 or IPv6, for example "10.0.0.0/8" or "fd00::/8". - - Networks can have upto 25 CIDRs specified. - - - Support: Extended - - - + Networks can have up to 25 CIDRs specified. items: description: |- - CIDR is an IP address range in CIDR notation (for example, "10.0.0.0/8" or "fd00::/8"). - This string must be validated by implementations using net.ParseCIDR - TODO: Introduce CEL CIDR validation regex isCIDR() in Kube 1.31 when it is available. + CIDR is an IP address range in CIDR notation + (for example, "10.0.0.0/8" or "fd00::/8"). maxLength: 43 type: string x-kubernetes-validations: - - message: - CIDR must be either an IPv4 or IPv6 address. - IPv4 address embedded in IPv6 addresses are not - supported - rule: self.contains(':') != self.contains('.') + - message: Invalid CIDR format provided + rule: isCIDR(self) maxItems: 25 minItems: 1 type: array x-kubernetes-list-type: set - nodes: - description: |- - Nodes defines a way to select a set of nodes in - the cluster. This field follows standard label selector - semantics; if present but empty, it selects all Nodes. - - - Support: Extended - - - - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic pods: description: |- Pods defines a way to select a set of pods in a set of namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -407,11 +397,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -424,8 +416,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -456,11 +448,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -472,77 +466,80 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object - maxItems: 100 + maxItems: 25 minItems: 1 type: array required: - action - to type: object - x-kubernetes-validations: - - message: - networks/nodes peer cannot be set with namedPorts since - there are no namedPorts for networks/nodes - rule: - "!(self.to.exists(peer, has(peer.networks) || has(peer.nodes)) - && has(self.ports) && self.ports.exists(port, has(port.namedPort)))" - maxItems: 100 + maxItems: 25 type: array ingress: description: |- Ingress is the list of Ingress rules to be applied to the selected pods. - A total of 100 rules will be allowed in each ANP instance. - The relative precedence of ingress rules within a single ANP object (all of - which share the priority) will be determined by the order in which the rule - is written. Thus, a rule that appears at the top of the ingress rules - would take the highest precedence. - ANPs with no ingress rules do not affect ingress traffic. + A maximum of 25 rules is allowed in this block. - Support: Core + The relative precedence of ingress rules within a single CNP object + (all of which share the priority) will be determined by the order + in which the rule is written. + Thus, a rule that appears at the top of the ingress rules + would take the highest precedence. + CNPs with no ingress rules do not affect ingress traffic. items: description: |- - AdminNetworkPolicyIngressRule describes an action to take on a particular - set of traffic destined for pods selected by an AdminNetworkPolicy's + ClusterNetworkPolicyIngressRule describes an action to take on a particular + set of traffic destined for pods selected by a ClusterNetworkPolicy's Subject field. properties: action: description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic (even if it would otherwise have been denied by NetworkPolicy) - Deny: denies the selected traffic - Pass: instructs the selected traffic to skip any remaining ANP rules, and - then pass execution to any NetworkPolicies that select the pod. - If the pod is not selected by any NetworkPolicies then execution - is passed to any BaselineAdminNetworkPolicies that select the pod. + Action specifies the effect this rule will have on matching + traffic. Currently the following actions are supported: + + - Accept: Accepts the selected traffic, allowing it into + the destination. No further ClusterNetworkPolicy or + NetworkPolicy rules will be processed. + + Note: while Accept ensures traffic is accepted by + Kubernetes network policy, it is still possible that the + packet is blocked in other ways: custom nftable rules, + high-layers e.g. service mesh. + - Deny: Drops the selected traffic. No further + ClusterNetworkPolicy or NetworkPolicy rules will be + processed. - Support: Core + - Pass: Skips all further ClusterNetworkPolicy rules in the + current tier for the selected traffic, and passes + evaluation to the next tier. enum: - - Allow + - Accept - Deny - Pass type: string from: description: |- From is the list of sources whose traffic this rule applies to. - If any AdminNetworkPolicyIngressPeer matches the source of incoming + If any element matches the source of incoming traffic then the specified action is applied. This field must be defined and contain at least one item. - - - Support: Core items: description: |- - AdminNetworkPolicyIngressPeer defines an in-cluster peer to allow traffic from. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. + ClusterNetworkPolicyIngressPeer defines a peer to allow traffic from. + + Exactly one of the fields must be set for a given peer and this is enforced + by the validation rules on the CRD. If an implementation sees no fields are + set then it can infer that the deployed CRD is of an incompatible version + with an unknown field. In that case it should fail closed. + + For "Accept" rules, "fail closed" means: "treat the rule as matching no + traffic". For "Deny" and "Pass" rules, "fail closed" means: "treat the rule + as a 'Deny all' rule". maxProperties: 1 minProperties: 1 properties: @@ -550,9 +547,6 @@ spec: description: |- Namespaces defines a way to select all pods within a set of Namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: matchExpressions: description: @@ -582,11 +576,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -602,14 +598,11 @@ spec: Pods defines a way to select a set of pods in a set of namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -640,11 +633,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -657,8 +652,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -689,11 +684,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -705,154 +702,206 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object - maxItems: 100 + maxItems: 25 minItems: 1 type: array name: description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - AdminNetworkPolicies. - - - Support: Core + Name is an identifier for this rule, that may be no more than + 100 characters in length. This field should be used by the implementation + to help improve observability, readability and error-reporting + for any applied policies. maxLength: 100 type: string - ports: + protocols: description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of ports which should be matched on - the pods selected for this policy i.e the subject of the policy. - So it matches on the destination port for the ingress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core + Protocols allows for more fine-grain matching of traffic on + protocol-specific attributes such as the port. If + unspecified, protocol-specific attributes will not be used + to match traffic. items: description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). + ClusterNetworkPolicyProtocol describes additional protocol-specific match rules. Exactly one field must be set. maxProperties: 1 minProperties: 1 properties: - namedPort: + destinationNamedPort: description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - + DestinationNamedPort selects a destination port on a pod based on the + ContainerPort name. You can't use this in a rule that targets resources + without named ports (e.g. Nodes or Networks). type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core + sctp: + description: SCTP specific protocol matches. + minProperties: 1 properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core + tcp: + description: TCP specific protocol matches. + minProperties: 1 properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + udp: + description: UDP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object type: object - maxItems: 100 + maxItems: 25 + minItems: 1 type: array required: - action - from type: object - maxItems: 100 + maxItems: 25 type: array priority: description: |- - Priority is a value from 0 to 1000. Rules with lower priority values have - higher precedence, and are checked before rules with higher priority values. - All AdminNetworkPolicy rules have higher precedence than NetworkPolicy or - BaselineAdminNetworkPolicy rules - The behavior is undefined if two ANP objects have same priority. - - - Support: Core + Priority is a value from 0 to 1000 indicating the precedence of + the policy within its tier. Policies with lower priority values have + higher precedence, and are checked before policies with higher priority + values in the same tier. All Admin tier rules have higher precedence than + NetworkPolicy or Baseline tier rules. + If two (or more) policies in the same tier with the same priority + could match a connection, then the implementation can apply any of the + matching policies to the connection, and there is no way for the user to + reliably determine which one it will choose. Administrators must be + careful about assigning the priorities for policies with rules that will + match many connections, and ensure that policies have unique priority + values in cases where ambiguity would be unacceptable. format: int32 maximum: 1000 minimum: 0 type: integer subject: - description: |- - Subject defines the pods to which this AdminNetworkPolicy applies. - Note that host-networked pods are not included in subject selection. - - - Support: Core + description: + Subject defines the pods to which this ClusterNetworkPolicy + applies. maxProperties: 1 minProperties: 1 properties: @@ -887,11 +936,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -909,8 +960,8 @@ spec: properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -940,11 +991,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -957,8 +1010,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -988,11 +1041,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -1004,13 +1059,48 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object + tier: + description: |- + Tier is used as the top-level grouping for network policy prioritization. + + Policy tiers are evaluated in the following order: + * Admin tier + * NetworkPolicy tier + * Baseline tier + + ClusterNetworkPolicy can use 2 of these tiers: Admin and Baseline. + + The Admin tier takes precedence over all other policies. Policies + defined in this tier are used to set cluster-wide security rules + that cannot be overridden in the other tiers. If Admin tier has + made a final decision (Accept or Deny) on a connection, then no + further evaluation is done. + + NetworkPolicy tier is the tier for the namespaced v1.NetworkPolicy. + These policies are intended for the application developer to describe + the security policy associated with their deployments inside their + namespace. v1.NetworkPolicy always makes a final decision for selected + pods. Further evaluation only happens for Pods not selected by a + v1.NetworkPolicy. + + Baseline tier is a cluster-wide policy that can be overridden by the + v1.NetworkPolicy. If Baseline tier has made a final decision (Accept or + Deny) on a connection, then no further evaluation is done. + + If a given connection wasn't allowed or denied by any of the tiers, + the default kubernetes policy is applied, which says that + all pods can communicate with each other. + enum: + - Admin + - Baseline + type: string required: - priority - subject + - tier type: object status: description: Status is the status to be reported by the implementation. @@ -1018,16 +1108,8 @@ spec: conditions: items: description: - "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -1068,12 +1150,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/libcalico-go/deps.txt b/libcalico-go/deps.txt index 54ee184ff98..927ad67d4c2 100644 --- a/libcalico-go/deps.txt +++ b/libcalico-go/deps.txt @@ -2,17 +2,21 @@ This file contains the list of modules that this package depends on in order to trigger CI on changes -go 1.25.1 +go 1.25.7 +github.com/Masterminds/semver/v3 v3.4.0 github.com/beorn7/perks v1.0.1 github.com/cespare/xxhash/v2 v2.3.0 github.com/coreos/go-semver v0.3.1 -github.com/coreos/go-systemd/v22 v22.5.0 +github.com/coreos/go-systemd/v22 v22.6.0 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc -github.com/emicklei/go-restful/v3 v3.11.0 -github.com/evanphx/json-patch v5.9.0+incompatible +github.com/emicklei/go-restful v2.15.0+incompatible +github.com/emicklei/go-restful/v3 v3.12.2 +github.com/evanphx/json-patch v5.9.11+incompatible github.com/evanphx/json-patch/v5 v5.9.11 github.com/fsnotify/fsnotify v1.9.0 -github.com/fxamacker/cbor/v2 v2.7.0 +github.com/fxamacker/cbor/v2 v2.9.0 +github.com/go-kit/log v0.2.1 +github.com/go-logfmt/logfmt v0.6.0 github.com/go-logr/logr v1.4.3 github.com/go-openapi/jsonpointer v0.21.0 github.com/go-openapi/jsonreference v0.20.2 @@ -22,11 +26,11 @@ github.com/go-playground/universal-translator v0.18.1 github.com/gogo/protobuf v1.3.2 github.com/golang/protobuf v1.5.4 github.com/golang/snappy v1.0.0 -github.com/google/gnostic-models v0.6.9 +github.com/google/gnostic-models v0.7.0 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 -github.com/grpc-ecosystem/grpc-gateway v1.16.0 -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 github.com/jinzhu/copier v0.4.0 github.com/josharian/intern v1.0.0 github.com/json-iterator/go v1.1.12 @@ -35,68 +39,71 @@ github.com/leodido/go-urn v1.4.0 github.com/mailru/easyjson v0.7.7 github.com/mattn/go-runewidth v0.0.16 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd -github.com/modern-go/reflect2 v1.0.2 +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 -github.com/nxadm/tail v1.4.8 github.com/olekukonko/tablewriter v0.0.5 github.com/onsi/ginkgo v1.16.5 -github.com/onsi/gomega v1.38.0 +github.com/onsi/ginkgo/v2 v2.28.1 +github.com/onsi/gomega v1.39.1 +github.com/openshift/custom-resource-status v1.1.2 github.com/pkg/errors v0.9.1 +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/projectcalico/calico -github.com/projectcalico/calico/lib/std v0.0.0-00010101000000-000000000000 -github.com/projectcalico/go-json v0.0.0-20161128004156-6219dc7339ba -github.com/projectcalico/go-yaml-wrapper v0.0.0-20191112210931-090425220c54 -github.com/prometheus/client_golang v1.23.0 +github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 -github.com/prometheus/common v0.65.0 -github.com/prometheus/procfs v0.17.0 +github.com/prometheus/common v0.67.2 +github.com/prometheus/procfs v0.19.2 github.com/rivo/uniseg v0.4.7 github.com/sirupsen/logrus v1.9.3 -github.com/spf13/pflag v1.0.7 +github.com/spf13/pflag v1.0.10 github.com/tchap/go-patricia/v2 v2.3.3 github.com/vishvananda/netlink v1.3.1 github.com/vishvananda/netns v0.0.5 github.com/x448/float16 v0.8.4 -go.etcd.io/etcd/api/v3 v3.6.4 -go.etcd.io/etcd/client/pkg/v3 v3.6.4 -go.etcd.io/etcd/client/v2 v2.305.22 -go.etcd.io/etcd/client/v3 v3.6.4 +go.etcd.io/etcd/api/v3 v3.6.5 +go.etcd.io/etcd/client/pkg/v3 v3.6.5 +go.etcd.io/etcd/client/v2 v2.305.24 +go.etcd.io/etcd/client/v3 v3.6.5 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 -go.yaml.in/yaml/v2 v2.4.2 -golang.org/x/crypto v0.41.0 -golang.org/x/net v0.43.0 -golang.org/x/oauth2 v0.30.0 -golang.org/x/sync v0.16.0 -golang.org/x/sys v0.35.0 -golang.org/x/term v0.34.0 -golang.org/x/text v0.28.0 -golang.org/x/time v0.12.0 +go.yaml.in/yaml/v2 v2.4.3 +go.yaml.in/yaml/v3 v3.0.4 +golang.org/x/crypto v0.47.0 +golang.org/x/mod v0.32.0 +golang.org/x/net v0.49.0 +golang.org/x/oauth2 v0.34.0 +golang.org/x/sync v0.19.0 +golang.org/x/sys v0.40.0 +golang.org/x/term v0.39.0 +golang.org/x/text v0.33.0 +golang.org/x/time v0.14.0 +golang.org/x/tools v0.41.0 golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 google.golang.org/genproto v0.0.0-20250603155806-513f23925822 -google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 -google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a -google.golang.org/grpc v1.72.2 -google.golang.org/protobuf v1.36.7 +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c +google.golang.org/grpc v1.76.0 +google.golang.org/protobuf v1.36.10 gopkg.in/evanphx/json-patch.v4 v4.12.0 gopkg.in/go-playground/validator.v9 v9.30.2 gopkg.in/inf.v0 v0.9.1 -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 -gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 -k8s.io/api v0.33.5 -k8s.io/apiextensions-apiserver v0.33.5 -k8s.io/apimachinery v0.33.5 -k8s.io/client-go v0.33.5 +k8s.io/api v0.34.3 +k8s.io/apiextensions-apiserver v0.34.3 +k8s.io/apimachinery v0.34.3 +k8s.io/client-go v0.34.3 k8s.io/klog v0.2.0 k8s.io/klog/v2 v2.130.1 -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff -k8s.io/utils v0.0.0-20241210054802-24370beab758 -sigs.k8s.io/controller-runtime v0.20.4 +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 +kubevirt.io/api v1.8.0-alpha.0 +kubevirt.io/containerized-data-importer-api v1.63.1 +kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 +sigs.k8s.io/controller-runtime v0.22.3 sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 -sigs.k8s.io/kind v0.29.0 -sigs.k8s.io/network-policy-api v0.1.5 +sigs.k8s.io/kind v0.30.0 +sigs.k8s.io/network-policy-api v0.1.8-0.20260212153203-412bf65729a5 sigs.k8s.io/randfill v1.0.0 -sigs.k8s.io/structured-merge-diff/v4 v4.6.0 +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 sigs.k8s.io/yaml v1.6.0 diff --git a/libcalico-go/lib/apiconfig/apiconfig.go b/libcalico-go/lib/apiconfig/apiconfig.go index 6b0df2e8293..c87db2935aa 100644 --- a/libcalico-go/lib/apiconfig/apiconfig.go +++ b/libcalico-go/lib/apiconfig/apiconfig.go @@ -80,9 +80,12 @@ type KubeConfig struct { // This contains the contents that would normally be in the file pointed at by Kubeconfig. KubeconfigInline string `json:"kubeconfigInline" ignored:"true"` // K8sClientQPS overrides the QPS for the Kube client. - K8sClientQPS float32 `json:"k8sClientQPS"` + K8sClientQPS float32 `json:"k8sClientQPS" envconfig:"K8S_CLIENT_QPS" default:""` + K8sClientBurst int `json:"k8sClientBurst" envconfig:"K8S_CLIENT_BURST" default:""` // K8sCurrentContext provides a context override for kubeconfig. K8sCurrentContext string `json:"k8sCurrentContext" envconfig:"K8S_CURRENT_CONTEXT" default:""` + + CalicoAPIGroup string `json:"calicoAPIGroup" envconfig:"CALICO_API_GROUP"` } // NewCalicoAPIConfig creates a new (zeroed) CalicoAPIConfig struct with the @@ -99,7 +102,6 @@ func NewCalicoAPIConfig() *CalicoAPIConfig { // IsAlphaFeatureSet checks if the comma separated features have the // name set in it. func IsAlphaFeatureSet(features, name string) bool { - fs := strings.Split(features, ",") for _, f := range fs { if f == name { diff --git a/libcalico-go/lib/apiconfig/load.go b/libcalico-go/lib/apiconfig/load.go index f9ece04f761..aaef819078d 100644 --- a/libcalico-go/lib/apiconfig/load.go +++ b/libcalico-go/lib/apiconfig/load.go @@ -22,8 +22,8 @@ import ( "github.com/kelseyhightower/envconfig" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - yaml "github.com/projectcalico/go-yaml-wrapper" log "github.com/sirupsen/logrus" + "sigs.k8s.io/yaml" ) // LoadClientConfig loads the ClientConfig from the specified file (if specified) diff --git a/libcalico-go/lib/apis/crd.projectcalico.org/v1/README.md b/libcalico-go/lib/apis/crd.projectcalico.org/v1/README.md index b5e56f24741..4cb34ce9b3a 100644 --- a/libcalico-go/lib/apis/crd.projectcalico.org/v1/README.md +++ b/libcalico-go/lib/apis/crd.projectcalico.org/v1/README.md @@ -1,9 +1,19 @@ -# CustomResourceDefinitions +# libcalico-go/lib/apis/crd.projectcalico.org/v1 -This directory is used for generating CustomResourceDefinition resources to install into a Kubernetes -cluster. Calico APIs that are backed using CRDs should be added here so that auto-generation picks up -the API. See other structs in this directory as an example. +Kubernetes CRD type definitions for the `crd.projectcalico.org` API group. These are the +kubebuilder-annotated structs that define the actual CRD schemas stored in the Kubernetes API server. + +Covers both user-facing resources (NetworkPolicy, GlobalNetworkPolicy, BGPPeer, IPPool, Tier, +FelixConfiguration, etc.) and internal IPAM resources (IPAMBlock, IPAMHandle, IPAMConfig, +BlockAffinity). The IPAM types embed their specs from the `internalapi` package. + +Used by the Kubernetes backend (`libcalico-go/lib/backend/k8s`) and its raw CRD client for +direct CRD operations. -Once added, run `make gen-crds` in the root of this repository to update the custom resource definition yamls. +## Generating CRDs + +Calico APIs that are backed using CRDs should be added here so that auto-generation picks up +the API. See other structs in this directory as an example. -Then, once merged, use those yamls to update the CRDs served from https://github.com/projectcalico/calico +Once added, run `make gen-crds` in the root of this repository to update the custom resource +definition YAMLs. diff --git a/libcalico-go/lib/apis/crd.projectcalico.org/v1/block_affinity_types.go b/libcalico-go/lib/apis/crd.projectcalico.org/v1/block_affinity_types.go index 2c888668e14..5e03be27e4a 100644 --- a/libcalico-go/lib/apis/crd.projectcalico.org/v1/block_affinity_types.go +++ b/libcalico-go/lib/apis/crd.projectcalico.org/v1/block_affinity_types.go @@ -17,7 +17,7 @@ package v1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - v3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" ) // +genclient @@ -28,5 +28,5 @@ import ( type BlockAffinity struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec v3.BlockAffinitySpec `json:"spec,omitempty"` + Spec internalapi.BlockAffinitySpec `json:"spec,omitempty"` } diff --git a/libcalico-go/lib/apis/crd.projectcalico.org/v1/ipamblock_types.go b/libcalico-go/lib/apis/crd.projectcalico.org/v1/ipamblock_types.go index 5c830a4b97c..3ae434dcbeb 100644 --- a/libcalico-go/lib/apis/crd.projectcalico.org/v1/ipamblock_types.go +++ b/libcalico-go/lib/apis/crd.projectcalico.org/v1/ipamblock_types.go @@ -17,7 +17,7 @@ package v1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - v3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" ) // +genclient @@ -28,5 +28,5 @@ import ( type IPAMBlock struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec v3.IPAMBlockSpec `json:"spec,omitempty"` + Spec internalapi.IPAMBlockSpec `json:"spec,omitempty"` } diff --git a/libcalico-go/lib/apis/crd.projectcalico.org/v1/ipamconfig_types.go b/libcalico-go/lib/apis/crd.projectcalico.org/v1/ipamconfig_types.go index 2d07c818e8a..43e5bfba2f2 100644 --- a/libcalico-go/lib/apis/crd.projectcalico.org/v1/ipamconfig_types.go +++ b/libcalico-go/lib/apis/crd.projectcalico.org/v1/ipamconfig_types.go @@ -17,7 +17,7 @@ package v1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - v3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" ) // +genclient @@ -28,5 +28,5 @@ import ( type IPAMConfig struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec v3.IPAMConfigSpec `json:"spec,omitempty"` + Spec internalapi.IPAMConfigSpec `json:"spec,omitempty"` } diff --git a/libcalico-go/lib/apis/crd.projectcalico.org/v1/ipamhandle_types.go b/libcalico-go/lib/apis/crd.projectcalico.org/v1/ipamhandle_types.go index d3426e9b385..3ad50a8c4c0 100644 --- a/libcalico-go/lib/apis/crd.projectcalico.org/v1/ipamhandle_types.go +++ b/libcalico-go/lib/apis/crd.projectcalico.org/v1/ipamhandle_types.go @@ -17,7 +17,7 @@ package v1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - v3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" ) // +genclient @@ -28,5 +28,5 @@ import ( type IPAMHandle struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec v3.IPAMHandleSpec `json:"spec,omitempty"` + Spec internalapi.IPAMHandleSpec `json:"spec,omitempty"` } diff --git a/libcalico-go/lib/apis/crd.projectcalico.org/v1/kubecontrollersconfiguration_types.go b/libcalico-go/lib/apis/crd.projectcalico.org/v1/kubecontrollersconfiguration_types.go index 7e43ec15812..568b9f00832 100644 --- a/libcalico-go/lib/apis/crd.projectcalico.org/v1/kubecontrollersconfiguration_types.go +++ b/libcalico-go/lib/apis/crd.projectcalico.org/v1/kubecontrollersconfiguration_types.go @@ -24,6 +24,7 @@ import ( // +k8s:openapi-gen=true // +kubebuilder:resource:scope=Cluster +// +kubebuilder:subresource:status type KubeControllersConfiguration struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/libcalico-go/lib/backend/k8s/scheme/scheme.go b/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/scheme.go similarity index 82% rename from libcalico-go/lib/backend/k8s/scheme/scheme.go rename to libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/scheme.go index f52a7d067ea..c57d2bca0b8 100644 --- a/libcalico-go/lib/backend/k8s/scheme/scheme.go +++ b/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/scheme.go @@ -24,11 +24,17 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/kubernetes/scheme" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" ) var addToSchemeOnce sync.Once +const ( + GroupName = "crd.projectcalico.org" + Version = "v1" + GroupVersion = GroupName + "/" + Version +) + func BuilderCRDv1() *runtime.SchemeBuilder { builder := runtime.NewSchemeBuilder( func(scheme *runtime.Scheme) error { @@ -67,14 +73,14 @@ func BuilderCRDv1() *runtime.SchemeBuilder { &apiv3.TierList{}, &apiv3.HostEndpoint{}, &apiv3.HostEndpointList{}, - &libapiv3.BlockAffinity{}, - &libapiv3.BlockAffinityList{}, - &libapiv3.IPAMBlock{}, - &libapiv3.IPAMBlockList{}, - &libapiv3.IPAMHandle{}, - &libapiv3.IPAMHandleList{}, - &libapiv3.IPAMConfig{}, - &libapiv3.IPAMConfigList{}, + &internalapi.BlockAffinity{}, + &internalapi.BlockAffinityList{}, + &internalapi.IPAMBlock{}, + &internalapi.IPAMBlockList{}, + &internalapi.IPAMHandle{}, + &internalapi.IPAMHandleList{}, + &internalapi.IPAMConfig{}, + &internalapi.IPAMConfigList{}, &apiv3.KubeControllersConfiguration{}, &apiv3.KubeControllersConfigurationList{}, &apiv3.CalicoNodeStatus{}, @@ -96,6 +102,8 @@ func AddCalicoResourcesToGlobalScheme() { }) } +// AddCalicoResourcesToScheme adds resources necessary for the crd.projectcalico.org/v1 API +// backend to the current scheme, so they can be used by Kubernetes clients. func AddCalicoResourcesToScheme(s *runtime.Scheme) error { schemeBuilder := BuilderCRDv1() err := schemeBuilder.AddToScheme(s) diff --git a/libcalico-go/lib/apis/crd.projectcalico.org/v1/tier_types.go b/libcalico-go/lib/apis/crd.projectcalico.org/v1/tier_types.go index 90a7480eecd..79d9d955812 100644 --- a/libcalico-go/lib/apis/crd.projectcalico.org/v1/tier_types.go +++ b/libcalico-go/lib/apis/crd.projectcalico.org/v1/tier_types.go @@ -24,8 +24,11 @@ import ( // +k8s:openapi-gen=true // +kubebuilder:resource:scope=Cluster +// +kubebuilder:validation:XValidation:rule="self.metadata.name == 'kube-admin' ? self.spec.defaultAction == 'Pass' : true", message="The 'kube-admin' tier must have default action 'Pass'" +// +kubebuilder:validation:XValidation:rule="self.metadata.name == 'kube-baseline' ? self.spec.defaultAction == 'Pass' : true", message="The 'kube-baseline' tier must have default action 'Pass'" +// +kubebuilder:validation:XValidation:rule="self.metadata.name == 'default' ? self.spec.defaultAction == 'Deny' : true", message="The 'default' tier must have default action 'Deny'" type Tier struct { metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - Spec v3.TierSpec `json:"spec,omitempty"` + metav1.ObjectMeta `json:"metadata"` + Spec v3.TierSpec `json:"spec"` } diff --git a/libcalico-go/lib/apis/internalapi/README.md b/libcalico-go/lib/apis/internalapi/README.md new file mode 100644 index 00000000000..05f9530b93e --- /dev/null +++ b/libcalico-go/lib/apis/internalapi/README.md @@ -0,0 +1,10 @@ +# libcalico-go/lib/apis/internalapi + +Internal Calico resource types that are not part of the public API (`api/pkg/apis/projectcalico/v3`). + +Contains types that are either stored in Kubernetes CRDs but not exposed to end users, and/or types that are used internally +by Calico but are not backed by CRDs (i.e., WorkloadEndpoint which is stored in etcd mode, but backed by a Kubernetes Pod when using +the Kubernetes API for storage). + +- **Node** / **WorkloadEndpoint** -- internal representations backed by Kubernetes objects in KDD mode. +- **IPAMBlock**, **IPAMHandle**, **IPAMConfig**, **BlockAffinity** -- IPAM implementation details stored as CRDs but managed exclusively by Calico's IPAM subsystem. diff --git a/libcalico-go/lib/apis/v3/blockaffinity.go b/libcalico-go/lib/apis/internalapi/blockaffinity.go similarity index 99% rename from libcalico-go/lib/apis/v3/blockaffinity.go rename to libcalico-go/lib/apis/internalapi/blockaffinity.go index d48cab9eec9..942b020edc2 100644 --- a/libcalico-go/lib/apis/v3/blockaffinity.go +++ b/libcalico-go/lib/apis/internalapi/blockaffinity.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package v3 +package internalapi import ( apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" diff --git a/libcalico-go/lib/apis/v3/doc.go b/libcalico-go/lib/apis/internalapi/doc.go similarity index 98% rename from libcalico-go/lib/apis/v3/doc.go rename to libcalico-go/lib/apis/internalapi/doc.go index 32518b34030..b9620c165bd 100644 --- a/libcalico-go/lib/apis/v3/doc.go +++ b/libcalico-go/lib/apis/internalapi/doc.go @@ -23,4 +23,4 @@ format also used by calicoctl is directly mapped from the JSON. // +k8s:deepcopy-gen=package,register // +k8s:openapi-gen=true -package v3 +package internalapi diff --git a/libcalico-go/lib/apis/v3/generated.openapi.go b/libcalico-go/lib/apis/internalapi/generated.openapi.go similarity index 84% rename from libcalico-go/lib/apis/v3/generated.openapi.go rename to libcalico-go/lib/apis/internalapi/generated.openapi.go index 46ba1684816..e81dfada608 100644 --- a/libcalico-go/lib/apis/v3/generated.openapi.go +++ b/libcalico-go/lib/apis/internalapi/generated.openapi.go @@ -17,7 +17,7 @@ // Code generated by openapi-gen. DO NOT EDIT. -package v3 +package internalapi import ( common "k8s.io/kube-openapi/pkg/common" @@ -26,135 +26,205 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.BGPPeer": schema_libcalico_go_lib_apis_v1_BGPPeer(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.BGPPeerList": schema_libcalico_go_lib_apis_v1_BGPPeerList(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.BGPPeerMetadata": schema_libcalico_go_lib_apis_v1_BGPPeerMetadata(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.BGPPeerSpec": schema_libcalico_go_lib_apis_v1_BGPPeerSpec(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.CalicoAPIConfig": schema_libcalico_go_lib_apis_v1_CalicoAPIConfig(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.CalicoAPIConfigMetadata": schema_libcalico_go_lib_apis_v1_CalicoAPIConfigMetadata(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.CalicoAPIConfigSpec": schema_libcalico_go_lib_apis_v1_CalicoAPIConfigSpec(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.EndpointPort": schema_libcalico_go_lib_apis_v1_EndpointPort(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.EntityRule": schema_libcalico_go_lib_apis_v1_EntityRule(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.EtcdConfig": schema_libcalico_go_lib_apis_v1_EtcdConfig(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.HostEndpoint": schema_libcalico_go_lib_apis_v1_HostEndpoint(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.HostEndpointList": schema_libcalico_go_lib_apis_v1_HostEndpointList(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.HostEndpointMetadata": schema_libcalico_go_lib_apis_v1_HostEndpointMetadata(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.HostEndpointSpec": schema_libcalico_go_lib_apis_v1_HostEndpointSpec(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.ICMPFields": schema_libcalico_go_lib_apis_v1_ICMPFields(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPIPConfiguration": schema_libcalico_go_lib_apis_v1_IPIPConfiguration(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPNAT": schema_libcalico_go_lib_apis_v1_IPNAT(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPPool": schema_libcalico_go_lib_apis_v1_IPPool(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPPoolList": schema_libcalico_go_lib_apis_v1_IPPoolList(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPPoolMetadata": schema_libcalico_go_lib_apis_v1_IPPoolMetadata(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPPoolSpec": schema_libcalico_go_lib_apis_v1_IPPoolSpec(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.KubeConfig": schema_libcalico_go_lib_apis_v1_KubeConfig(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Node": schema_libcalico_go_lib_apis_v1_Node(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.NodeBGPSpec": schema_libcalico_go_lib_apis_v1_NodeBGPSpec(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.NodeList": schema_libcalico_go_lib_apis_v1_NodeList(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.NodeMetadata": schema_libcalico_go_lib_apis_v1_NodeMetadata(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.NodeSpec": schema_libcalico_go_lib_apis_v1_NodeSpec(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.OrchRef": schema_libcalico_go_lib_apis_v1_OrchRef(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Policy": schema_libcalico_go_lib_apis_v1_Policy(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.PolicyList": schema_libcalico_go_lib_apis_v1_PolicyList(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.PolicyMetadata": schema_libcalico_go_lib_apis_v1_PolicyMetadata(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.PolicySpec": schema_libcalico_go_lib_apis_v1_PolicySpec(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Profile": schema_libcalico_go_lib_apis_v1_Profile(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.ProfileList": schema_libcalico_go_lib_apis_v1_ProfileList(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.ProfileMetadata": schema_libcalico_go_lib_apis_v1_ProfileMetadata(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.ProfileSpec": schema_libcalico_go_lib_apis_v1_ProfileSpec(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Rule": schema_libcalico_go_lib_apis_v1_Rule(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Tier": schema_libcalico_go_lib_apis_v1_Tier(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.TierList": schema_libcalico_go_lib_apis_v1_TierList(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.TierMetadata": schema_libcalico_go_lib_apis_v1_TierMetadata(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.TierSpec": schema_libcalico_go_lib_apis_v1_TierSpec(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.WorkloadEndpoint": schema_libcalico_go_lib_apis_v1_WorkloadEndpoint(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.WorkloadEndpointList": schema_libcalico_go_lib_apis_v1_WorkloadEndpointList(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.WorkloadEndpointMetadata": schema_libcalico_go_lib_apis_v1_WorkloadEndpointMetadata(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.WorkloadEndpointSpec": schema_libcalico_go_lib_apis_v1_WorkloadEndpointSpec(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.AllocationAttribute": schema_libcalico_go_lib_apis_v3_AllocationAttribute(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.BlockAffinity": schema_libcalico_go_lib_apis_v3_BlockAffinity(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.BlockAffinityList": schema_libcalico_go_lib_apis_v3_BlockAffinityList(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.BlockAffinitySpec": schema_libcalico_go_lib_apis_v3_BlockAffinitySpec(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.IPAMBlock": schema_libcalico_go_lib_apis_v3_IPAMBlock(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.IPAMBlockList": schema_libcalico_go_lib_apis_v3_IPAMBlockList(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.IPAMBlockSpec": schema_libcalico_go_lib_apis_v3_IPAMBlockSpec(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.IPAMConfig": schema_libcalico_go_lib_apis_v3_IPAMConfig(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.IPAMConfigList": schema_libcalico_go_lib_apis_v3_IPAMConfigList(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.IPAMConfigSpec": schema_libcalico_go_lib_apis_v3_IPAMConfigSpec(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.IPAMHandle": schema_libcalico_go_lib_apis_v3_IPAMHandle(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.IPAMHandleList": schema_libcalico_go_lib_apis_v3_IPAMHandleList(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.IPAMHandleSpec": schema_libcalico_go_lib_apis_v3_IPAMHandleSpec(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.IPNAT": schema_libcalico_go_lib_apis_v3_IPNAT(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.Node": schema_libcalico_go_lib_apis_v3_Node(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.NodeAddress": schema_libcalico_go_lib_apis_v3_NodeAddress(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.NodeBGPSpec": schema_libcalico_go_lib_apis_v3_NodeBGPSpec(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.NodeInterface": schema_libcalico_go_lib_apis_v3_NodeInterface(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.NodeList": schema_libcalico_go_lib_apis_v3_NodeList(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.NodeSpec": schema_libcalico_go_lib_apis_v3_NodeSpec(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.NodeStatus": schema_libcalico_go_lib_apis_v3_NodeStatus(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.NodeWireguardSpec": schema_libcalico_go_lib_apis_v3_NodeWireguardSpec(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.OrchRef": schema_libcalico_go_lib_apis_v3_OrchRef(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.QoSControls": schema_libcalico_go_lib_apis_v3_QoSControls(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.WorkloadEndpoint": schema_libcalico_go_lib_apis_v3_WorkloadEndpoint(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.WorkloadEndpointList": schema_libcalico_go_lib_apis_v3_WorkloadEndpointList(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.WorkloadEndpointPort": schema_libcalico_go_lib_apis_v3_WorkloadEndpointPort(ref), - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.WorkloadEndpointSpec": schema_libcalico_go_lib_apis_v3_WorkloadEndpointSpec(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.AllocationAttribute": schema_libcalico_go_lib_apis_internalapi_AllocationAttribute(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.BlockAffinity": schema_libcalico_go_lib_apis_internalapi_BlockAffinity(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.BlockAffinityList": schema_libcalico_go_lib_apis_internalapi_BlockAffinityList(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.BlockAffinitySpec": schema_libcalico_go_lib_apis_internalapi_BlockAffinitySpec(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.IPAMBlock": schema_libcalico_go_lib_apis_internalapi_IPAMBlock(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.IPAMBlockList": schema_libcalico_go_lib_apis_internalapi_IPAMBlockList(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.IPAMBlockSpec": schema_libcalico_go_lib_apis_internalapi_IPAMBlockSpec(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.IPAMConfig": schema_libcalico_go_lib_apis_internalapi_IPAMConfig(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.IPAMConfigList": schema_libcalico_go_lib_apis_internalapi_IPAMConfigList(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.IPAMConfigSpec": schema_libcalico_go_lib_apis_internalapi_IPAMConfigSpec(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.IPAMHandle": schema_libcalico_go_lib_apis_internalapi_IPAMHandle(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.IPAMHandleList": schema_libcalico_go_lib_apis_internalapi_IPAMHandleList(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.IPAMHandleSpec": schema_libcalico_go_lib_apis_internalapi_IPAMHandleSpec(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.IPNAT": schema_libcalico_go_lib_apis_internalapi_IPNAT(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.LiveMigration": schema_libcalico_go_lib_apis_internalapi_LiveMigration(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.LiveMigrationList": schema_libcalico_go_lib_apis_internalapi_LiveMigrationList(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.LiveMigrationSpec": schema_libcalico_go_lib_apis_internalapi_LiveMigrationSpec(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.Node": schema_libcalico_go_lib_apis_internalapi_Node(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.NodeAddress": schema_libcalico_go_lib_apis_internalapi_NodeAddress(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.NodeBGPSpec": schema_libcalico_go_lib_apis_internalapi_NodeBGPSpec(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.NodeInterface": schema_libcalico_go_lib_apis_internalapi_NodeInterface(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.NodeList": schema_libcalico_go_lib_apis_internalapi_NodeList(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.NodeSpec": schema_libcalico_go_lib_apis_internalapi_NodeSpec(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.NodeStatus": schema_libcalico_go_lib_apis_internalapi_NodeStatus(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.NodeWireguardSpec": schema_libcalico_go_lib_apis_internalapi_NodeWireguardSpec(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.OrchRef": schema_libcalico_go_lib_apis_internalapi_OrchRef(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.QoSControls": schema_libcalico_go_lib_apis_internalapi_QoSControls(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.WorkloadEndpoint": schema_libcalico_go_lib_apis_internalapi_WorkloadEndpoint(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.WorkloadEndpointIdentifier": schema_libcalico_go_lib_apis_internalapi_WorkloadEndpointIdentifier(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.WorkloadEndpointList": schema_libcalico_go_lib_apis_internalapi_WorkloadEndpointList(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.WorkloadEndpointPort": schema_libcalico_go_lib_apis_internalapi_WorkloadEndpointPort(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.WorkloadEndpointSpec": schema_libcalico_go_lib_apis_internalapi_WorkloadEndpointSpec(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.BGPPeer": schema_libcalico_go_lib_apis_v1_BGPPeer(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.BGPPeerList": schema_libcalico_go_lib_apis_v1_BGPPeerList(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.BGPPeerMetadata": schema_libcalico_go_lib_apis_v1_BGPPeerMetadata(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.BGPPeerSpec": schema_libcalico_go_lib_apis_v1_BGPPeerSpec(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.CalicoAPIConfig": schema_libcalico_go_lib_apis_v1_CalicoAPIConfig(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.CalicoAPIConfigMetadata": schema_libcalico_go_lib_apis_v1_CalicoAPIConfigMetadata(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.CalicoAPIConfigSpec": schema_libcalico_go_lib_apis_v1_CalicoAPIConfigSpec(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.EndpointPort": schema_libcalico_go_lib_apis_v1_EndpointPort(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.EntityRule": schema_libcalico_go_lib_apis_v1_EntityRule(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.EtcdConfig": schema_libcalico_go_lib_apis_v1_EtcdConfig(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.HostEndpoint": schema_libcalico_go_lib_apis_v1_HostEndpoint(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.HostEndpointList": schema_libcalico_go_lib_apis_v1_HostEndpointList(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.HostEndpointMetadata": schema_libcalico_go_lib_apis_v1_HostEndpointMetadata(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.HostEndpointSpec": schema_libcalico_go_lib_apis_v1_HostEndpointSpec(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.ICMPFields": schema_libcalico_go_lib_apis_v1_ICMPFields(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPIPConfiguration": schema_libcalico_go_lib_apis_v1_IPIPConfiguration(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPNAT": schema_libcalico_go_lib_apis_v1_IPNAT(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPPool": schema_libcalico_go_lib_apis_v1_IPPool(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPPoolList": schema_libcalico_go_lib_apis_v1_IPPoolList(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPPoolMetadata": schema_libcalico_go_lib_apis_v1_IPPoolMetadata(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPPoolSpec": schema_libcalico_go_lib_apis_v1_IPPoolSpec(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.KubeConfig": schema_libcalico_go_lib_apis_v1_KubeConfig(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Node": schema_libcalico_go_lib_apis_v1_Node(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.NodeBGPSpec": schema_libcalico_go_lib_apis_v1_NodeBGPSpec(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.NodeList": schema_libcalico_go_lib_apis_v1_NodeList(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.NodeMetadata": schema_libcalico_go_lib_apis_v1_NodeMetadata(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.NodeSpec": schema_libcalico_go_lib_apis_v1_NodeSpec(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.OrchRef": schema_libcalico_go_lib_apis_v1_OrchRef(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Policy": schema_libcalico_go_lib_apis_v1_Policy(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.PolicyList": schema_libcalico_go_lib_apis_v1_PolicyList(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.PolicyMetadata": schema_libcalico_go_lib_apis_v1_PolicyMetadata(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.PolicySpec": schema_libcalico_go_lib_apis_v1_PolicySpec(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Profile": schema_libcalico_go_lib_apis_v1_Profile(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.ProfileList": schema_libcalico_go_lib_apis_v1_ProfileList(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.ProfileMetadata": schema_libcalico_go_lib_apis_v1_ProfileMetadata(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.ProfileSpec": schema_libcalico_go_lib_apis_v1_ProfileSpec(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Rule": schema_libcalico_go_lib_apis_v1_Rule(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Tier": schema_libcalico_go_lib_apis_v1_Tier(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.TierList": schema_libcalico_go_lib_apis_v1_TierList(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.TierMetadata": schema_libcalico_go_lib_apis_v1_TierMetadata(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.TierSpec": schema_libcalico_go_lib_apis_v1_TierSpec(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.WorkloadEndpoint": schema_libcalico_go_lib_apis_v1_WorkloadEndpoint(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.WorkloadEndpointList": schema_libcalico_go_lib_apis_v1_WorkloadEndpointList(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.WorkloadEndpointMetadata": schema_libcalico_go_lib_apis_v1_WorkloadEndpointMetadata(ref), + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.WorkloadEndpointSpec": schema_libcalico_go_lib_apis_v1_WorkloadEndpointSpec(ref), + } +} + +func schema_libcalico_go_lib_apis_internalapi_AllocationAttribute(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "handle_id": { + SchemaProps: spec.SchemaProps{ + Description: "HandleID is the primary identifier for the allocation.", + Type: []string{"string"}, + Format: "", + }, + }, + "secondary": { + SchemaProps: spec.SchemaProps{ + Description: "ActiveOwnerAttrs contains attributes of the active owner (the pod currently using the IP).", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "alternate": { + SchemaProps: spec.SchemaProps{ + Description: "AlternateOwnerAttrs contains attributes of the previous or potential owner (used during live migration to track the source or target pod).", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, } } -func schema_libcalico_go_lib_apis_v1_BGPPeer(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_BlockAffinity(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "BGPPeer contains information about a BGP peer resource that is a peer of a Calico compute node.", + Description: "BlockAffinity maintains a block affinity's state", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "TypeMetadata": { + "kind": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", }, }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Metadata for a BGPPeer.", + Description: "Standard object's metadata.", Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.BGPPeerMetadata"), + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Specification for a BGPPeer.", + Description: "Specification of the BlockAffinity.", Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.BGPPeerSpec"), + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.BlockAffinitySpec"), }, }, }, - Required: []string{"TypeMetadata"}, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.BGPPeerMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.BGPPeerSpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.BlockAffinitySpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } -func schema_libcalico_go_lib_apis_v1_BGPPeerList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_BlockAffinityList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "BGPPeerList contains a list of BGP Peer resources. List types are returned from List() enumerations in the client interface.", + Description: "BlockAffinityList contains a list of BlockAffinity resources.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "TypeMetadata": { + "kind": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", }, }, "metadata": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata"), + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), }, }, "items": { @@ -164,447 +234,349 @@ func schema_libcalico_go_lib_apis_v1_BGPPeerList(ref common.ReferenceCallback) c Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.BGPPeer"), + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.BlockAffinity"), }, }, }, }, }, }, - Required: []string{"TypeMetadata", "items"}, + Required: []string{"metadata", "items"}, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.BGPPeer", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.BlockAffinity", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, } } -func schema_libcalico_go_lib_apis_v1_BGPPeerMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_BlockAffinitySpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "BGPPeerMetadata contains the metadata for a BGPPeer resource.", + Description: "BlockAffinitySpec contains the specification for a BlockAffinity resource.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "ObjectMetadata": { + "state": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"), + Default: "", + Type: []string{"string"}, + Format: "", }, }, - "scope": { + "node": { SchemaProps: spec.SchemaProps{ - Description: "The scope of the peer. This may be global or node. A global peer is a BGP device that peers with all Calico nodes. A node peer is a BGP device that peers with the specified Calico node (specified by the node hostname).", - Default: "", - Type: []string{"string"}, - Format: "", + Default: "", + Type: []string{"string"}, + Format: "", }, }, - "node": { + "type": { SchemaProps: spec.SchemaProps{ - Description: "The node name identifying the Calico node instance that is peering with this peer. When modifying a BGP peer, the node must be specified when the scope is `node`, and must be omitted when the scope is `global`.", - Type: []string{"string"}, - Format: "", + Type: []string{"string"}, + Format: "", }, }, - "peerIP": { + "cidr": { SchemaProps: spec.SchemaProps{ - Description: "The IP address of the peer.", - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IP"), + Default: "", + Type: []string{"string"}, + Format: "", }, }, - }, - Required: []string{"ObjectMetadata", "scope", "peerIP"}, - }, - }, - Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata", "github.com/projectcalico/calico/libcalico-go/lib/net.IP"}, - } -} - -func schema_libcalico_go_lib_apis_v1_BGPPeerSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "BGPPeerSpec contains the specification for a BGPPeer resource.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "asNumber": { + "deleted": { SchemaProps: spec.SchemaProps{ - Description: "The AS Number of the peer.", - Default: 0, - Type: []string{"integer"}, - Format: "int64", + Description: "Deleted indicates that this block affinity is being deleted. This field is a string for compatibility with older releases that mistakenly treat this field as a string.", + Default: "", + Type: []string{"string"}, + Format: "", }, }, }, - Required: []string{"asNumber"}, + Required: []string{"state", "node", "cidr", "deleted"}, }, }, } } -func schema_libcalico_go_lib_apis_v1_CalicoAPIConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_IPAMBlock(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "CalicoAPIConfig contains the connection information for a Calico CalicoAPIConfig resource", + Description: "IPAMBlock contains information about a block for IP address assignment.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "TypeMetadata": { + "kind": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", }, }, "metadata": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.CalicoAPIConfigMetadata"), + Description: "Standard object's metadata.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.CalicoAPIConfigSpec"), + Description: "Specification of the IPAMBlock.", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.IPAMBlockSpec"), }, }, }, - Required: []string{"TypeMetadata"}, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.CalicoAPIConfigMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.CalicoAPIConfigSpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.IPAMBlockSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } -func schema_libcalico_go_lib_apis_v1_CalicoAPIConfigMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_IPAMBlockList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "CalicoAPIConfigMetadata contains the metadata for a Calico CalicoAPIConfig resource.", + Description: "IPAMBlockList contains a list of IPAMBlock resources.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "ObjectMetadata": { + "kind": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"), + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", }, }, - }, - Required: []string{"ObjectMetadata"}, - }, - }, - Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"}, - } -} - -func schema_libcalico_go_lib_apis_v1_CalicoAPIConfigSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "CalicoAPIConfigSpec contains the specification for a Calico CalicoAPIConfig resource.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "datastoreType": { + "apiVersion": { SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", }, }, - "EtcdConfig": { + "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Inline the etcd config fields", - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.EtcdConfig"), + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), }, }, - "KubeConfig": { + "items": { SchemaProps: spec.SchemaProps{ - Description: "Inline the k8s config fields.", - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.KubeConfig"), + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.IPAMBlock"), + }, + }, + }, }, }, }, - Required: []string{"datastoreType", "EtcdConfig", "KubeConfig"}, + Required: []string{"metadata", "items"}, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.EtcdConfig", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.KubeConfig"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.IPAMBlock", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, } } -func schema_libcalico_go_lib_apis_v1_EndpointPort(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_IPAMBlockSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, + Description: "IPAMBlockSpec contains the specification for an IPAMBlock resource.", + Type: []string{"object"}, Properties: map[string]spec.Schema{ - "name": { + "cidr": { SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", + Description: "The block's CIDR.", + Default: "", + Type: []string{"string"}, + Format: "", }, }, - "protocol": { + "affinity": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/projectcalico/api/pkg/lib/numorstring.Protocol"), + Description: "Affinity of the block, if this block has one. If set, it will be of the form \"host:\". If not set, this block is not affine to a host.", + Type: []string{"string"}, + Format: "", }, }, - "port": { + "affinityClaimTime": { SchemaProps: spec.SchemaProps{ - Default: 0, - Type: []string{"integer"}, - Format: "int32", - }, - }, - }, - Required: []string{"name", "protocol", "port"}, - }, - }, - Dependencies: []string{ - "github.com/projectcalico/api/pkg/lib/numorstring.Protocol"}, - } -} - -func schema_libcalico_go_lib_apis_v1_EntityRule(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "An EntityRule is a sub-component of a Rule comprising the match criteria specific to a particular entity (that is either the source or destination).\n\nA source EntityRule matches the source endpoint and originating traffic. A destination EntityRule matches the destination endpoint and terminating traffic.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "tag": { - SchemaProps: spec.SchemaProps{ - Description: "Tag is an optional field that restricts the rule to only apply to traffic that originates from (or terminates at) endpoints that have profiles with the given tag in them.", - Type: []string{"string"}, - Format: "", - }, - }, - "net": { - SchemaProps: spec.SchemaProps{ - Description: "Net is an optional field that restricts the rule to only apply to traffic that originates from (or terminates at) IP addresses in the given subnet. Deprecated: superseded by the Nets field.", - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IPNet"), + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), }, }, - "nets": { + "allocations": { SchemaProps: spec.SchemaProps{ - Description: "Nets is an optional field that restricts the rule to only apply to traffic that originates from (or terminates at) IP addresses in any of the given subnets.", + Description: "Array of allocations in-use within this block. nil entries mean the allocation is free. For non-nil entries at index i, the index is the ordinal of the allocation within this block and the value is the index of the associated attributes in the Attributes array.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IPNet"), + Type: []string{"integer"}, + Format: "int32", }, }, }, }, }, - "selector": { - SchemaProps: spec.SchemaProps{ - Description: "Selector is an optional field that contains a selector expression (see Policy for sample syntax). Only traffic that originates from (terminates at) endpoints matching the selector will be matched.\n\nNote that: in addition to the negated version of the Selector (see NotSelector below), the selector expression syntax itself supports negation. The two types of negation are subtly different. One negates the set of matched endpoints, the other negates the whole match:\n\n\tSelector = \"!has(my_label)\" matches packets that are from other Calico-controlled\n\tendpoints that do not have the label \"my_label\".\n\n\tNotSelector = \"has(my_label)\" matches packets that are not from Calico-controlled\n\tendpoints that do have the label \"my_label\".\n\nThe effect is that the latter will accept packets from non-Calico sources whereas the former is limited to packets from Calico-controlled endpoints.", - Type: []string{"string"}, - Format: "", - }, - }, - "ports": { + "unallocated": { SchemaProps: spec.SchemaProps{ - Description: "Ports is an optional field that restricts the rule to only apply to traffic that has a source (destination) port that matches one of these ranges/values. This value is a list of integers or strings that represent ranges of ports.\n\nSince only some protocols have ports, if any ports are specified it requires the Protocol match in the Rule to be set to \"tcp\" or \"udp\".", + Description: "Unallocated is an ordered list of allocations which are free in the block.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/projectcalico/api/pkg/lib/numorstring.Port"), + Default: 0, + Type: []string{"integer"}, + Format: "int32", }, }, }, }, }, - "notTag": { - SchemaProps: spec.SchemaProps{ - Description: "NotTag is the negated version of the Tag field.", - Type: []string{"string"}, - Format: "", - }, - }, - "notNet": { - SchemaProps: spec.SchemaProps{ - Description: "NotNet is an optional field that restricts the rule to only apply to traffic that does not originate from (or terminate at) an IP address in the given subnet. Deprecated: superseded by NotNets.", - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IPNet"), - }, - }, - "notNets": { + "attributes": { SchemaProps: spec.SchemaProps{ - Description: "NotNets is an optional field that restricts the rule to only apply to traffic that does not originate from (or terminate at) an IP address in any of the given subnets.", + Description: "Attributes is an array of arbitrary metadata associated with allocations in the block. To find attributes for a given allocation, use the value of the allocation's entry in the Allocations array as the index of the element in this array.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IPNet"), + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.AllocationAttribute"), }, }, }, }, }, - "notSelector": { + "sequenceNumber": { SchemaProps: spec.SchemaProps{ - Description: "NotSelector is the negated version of the Selector field. See Selector field for subtleties with negated selectors.", - Type: []string{"string"}, - Format: "", + Description: "We store a sequence number that is updated each time the block is written. Each allocation will also store the sequence number of the block at the time of its creation. When releasing an IP, passing the sequence number associated with the allocation allows us to protect against a race condition and ensure the IP hasn't been released and re-allocated since the release request.", + Default: 0, + Type: []string{"integer"}, + Format: "int64", }, }, - "notPorts": { + "sequenceNumberForAllocation": { SchemaProps: spec.SchemaProps{ - Description: "NotPorts is the negated version of the Ports field.\n\nSince only some protocols have ports, if any ports are specified it requires the Protocol match in the Rule to be set to \"tcp\" or \"udp\".", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ + Description: "Map of allocated ordinal within the block to sequence number of the block at the time of allocation. Kubernetes does not allow numerical keys for maps, so the key is cast to a string.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/projectcalico/api/pkg/lib/numorstring.Port"), + Default: 0, + Type: []string{"integer"}, + Format: "int64", }, }, }, }, }, - }, - }, - }, - Dependencies: []string{ - "github.com/projectcalico/api/pkg/lib/numorstring.Port", "github.com/projectcalico/calico/libcalico-go/lib/net.IPNet"}, - } -} - -func schema_libcalico_go_lib_apis_v1_EtcdConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "etcdScheme": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "etcdAuthority": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "etcdEndpoints": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "etcdDiscoverySrv": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "etcdUsername": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "etcdPassword": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "etcdKeyFile": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "etcdCertFile": { + "deleted": { SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", + Description: "Deleted is an internal boolean used to workaround a limitation in the Kubernetes API whereby deletion will not return a conflict error if the block has been updated. It should not be set manually.", + Default: false, + Type: []string{"boolean"}, + Format: "", }, }, - "etcdCACertFile": { + "strictAffinity": { SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", + Description: "StrictAffinity on the IPAMBlock is deprecated and no longer used by the code. Use IPAMConfig StrictAffinity instead.", + Default: false, + Type: []string{"boolean"}, + Format: "", }, }, }, - Required: []string{"etcdScheme", "etcdAuthority", "etcdEndpoints", "etcdDiscoverySrv", "etcdUsername", "etcdPassword", "etcdKeyFile", "etcdCertFile", "etcdCACertFile"}, + Required: []string{"cidr", "allocations", "unallocated", "attributes", "strictAffinity"}, }, }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.AllocationAttribute", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } -func schema_libcalico_go_lib_apis_v1_HostEndpoint(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_IPAMConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "HostEndpoint contains information about a \"bare-metal\" interfaces attached to the host that is running Calico's agent, Felix. By default, Calico doesn't apply any policy to such interfaces.", + Description: "IPAMConfig contains information about a block for IP address assignment.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "TypeMetadata": { + "kind": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", }, }, "metadata": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.HostEndpointMetadata"), + Description: "Standard object's metadata.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.HostEndpointSpec"), + Description: "Specification of the IPAMConfig.", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.IPAMConfigSpec"), }, }, }, - Required: []string{"TypeMetadata"}, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.HostEndpointMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.HostEndpointSpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.IPAMConfigSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } -func schema_libcalico_go_lib_apis_v1_HostEndpointList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_IPAMConfigList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "HostEndpointList contains a list of Host Endpoint resources. List types are returned from List() enumerations in the client interface.", + Description: "IPAMConfigList contains a list of IPAMConfig resources.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "TypeMetadata": { + "kind": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", }, }, "metadata": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata"), + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), }, }, "items": { @@ -614,191 +586,199 @@ func schema_libcalico_go_lib_apis_v1_HostEndpointList(ref common.ReferenceCallba Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.HostEndpoint"), + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.IPAMConfig"), }, }, }, }, }, }, - Required: []string{"TypeMetadata", "items"}, + Required: []string{"metadata", "items"}, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.HostEndpoint", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.IPAMConfig", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, } } -func schema_libcalico_go_lib_apis_v1_HostEndpointMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_IPAMConfigSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "HostEndpointMetadata contains the Metadata for a HostEndpoint resource.", + Description: "IPAMConfigSpec contains the specification for an IPAMConfig resource.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "ObjectMetadata": { + "strictAffinity": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"), + Default: false, + Type: []string{"boolean"}, + Format: "", }, }, - "name": { + "autoAllocateBlocks": { SchemaProps: spec.SchemaProps{ - Description: "The name of the endpoint.", - Type: []string{"string"}, - Format: "", + Default: false, + Type: []string{"boolean"}, + Format: "", }, }, - "node": { + "maxBlocksPerHost": { SchemaProps: spec.SchemaProps{ - Description: "The node name identifying the Calico node instance.", + Description: "MaxBlocksPerHost, if non-zero, is the max number of blocks that can be affine to each host.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "kubeVirtVMAddressPersistence": { + SchemaProps: spec.SchemaProps{ + Description: "KubeVirtVMAddressPersistence controls whether KubeVirt VirtualMachine workloads maintain persistent IP addresses across VM lifecycle events. When set to VMAddressPersistenceEnabled, Calico automatically ensures that KubeVirt VMs retain their IP addresses when their underlying pods are recreated during VM operations such as reboot, live migration, or pod eviction. IP persistency is ensured when the VirtualMachineInstance (VMI) resource is deleted and recreated by the VM controller. When set to VMAddressPersistenceDisabled, VMs receive new IP addresses whenever their pods are recreated, following standard pod IP allocation behavior. Live migration target pods are not allowed when this is set to VMAddressPersistenceDisabled and will result in an error. If nil, defaults to VMAddressPersistenceEnabled (IP persistence enabled if not specified).", Type: []string{"string"}, Format: "", }, }, - "labels": { - SchemaProps: spec.SchemaProps{ - Description: "The labels applied to the host endpoint. It is expected that many endpoints share the same labels. For example, they could be used to label all \"production\" workloads with \"deployment=prod\" so that security policy can be applied to production workloads.", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - }, - }, }, - Required: []string{"ObjectMetadata"}, + Required: []string{"strictAffinity", "autoAllocateBlocks"}, }, }, - Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"}, } } -func schema_libcalico_go_lib_apis_v1_HostEndpointSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_IPAMHandle(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "HostEndpointSpec contains the specification for a HostEndpoint resource.", + Description: "IPAMHandle contains information about an IPAMHandle resource.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "interfaceName": { + "kind": { SchemaProps: spec.SchemaProps{ - Description: "The name of the linux interface to apply policy to; for example \"eth0\". If \"InterfaceName\" is not present then at least one expected IP must be specified.", + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", Type: []string{"string"}, Format: "", }, }, - "expectedIPs": { + "apiVersion": { SchemaProps: spec.SchemaProps{ - Description: "The expected IP addresses (IPv4 and IPv6) of the endpoint. If \"InterfaceName\" is not present, Calico will look for an interface matching any of the IPs in the list and apply policy to that.\n\nNote:\n\tWhen using the selector|tag match criteria in an ingress or egress security Policy\n\tor Profile, Calico converts the selector into a set of IP addresses. For host\n\tendpoints, the ExpectedIPs field is used for that purpose. (If only the interface\n\tname is specified, Calico does not learn the IPs of the interface for use in match\n\tcriteria.)", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IP"), - }, - }, - }, + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", }, }, - "profiles": { + "metadata": { SchemaProps: spec.SchemaProps{ - Description: "A list of identifiers of security Profile objects that apply to this endpoint. Each profile is applied in the order that they appear in this list. Profile rules are applied after the selector-based security policy.", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, + Description: "Standard object's metadata.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), }, }, - "ports": { + "spec": { SchemaProps: spec.SchemaProps{ - Description: "Ports contains the endpoint's named ports, which may be referenced in security policy rules.", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.EndpointPort"), - }, - }, - }, + Description: "Specification of the IPAMHandle.", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.IPAMHandleSpec"), }, }, }, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.EndpointPort", "github.com/projectcalico/calico/libcalico-go/lib/net.IP"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.IPAMHandleSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } -func schema_libcalico_go_lib_apis_v1_ICMPFields(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_IPAMHandleList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "ICMPFields defines structure for ICMP and NotICMP sub-struct for ICMP code and type", + Description: "IPAMHandleList contains a list of IPAMHandle resources.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "type": { + "kind": { SchemaProps: spec.SchemaProps{ - Description: "Match on a specific ICMP type. For example a value of 8 refers to ICMP Echo Request (i.e. pings).", - Type: []string{"integer"}, - Format: "int32", + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", }, }, - "code": { + "apiVersion": { SchemaProps: spec.SchemaProps{ - Description: "Match on a specific ICMP code. If specified, the Type value must also be specified. This is a technical limitation imposed by the kernel's iptables firewall, which Calico uses to enforce the rule.", - Type: []string{"integer"}, - Format: "int32", + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.IPAMHandle"), + }, + }, + }, }, }, }, + Required: []string{"metadata", "items"}, }, }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.IPAMHandle", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, } } -func schema_libcalico_go_lib_apis_v1_IPIPConfiguration(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_IPAMHandleSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, + Description: "IPAMHandleSpec contains the specification for an IPAMHandle resource.", + Type: []string{"object"}, Properties: map[string]spec.Schema{ - "enabled": { + "handleID": { SchemaProps: spec.SchemaProps{ - Description: "When enabled is true, ipip tunneling will be used to deliver packets to destinations within this pool.", - Type: []string{"boolean"}, - Format: "", + Default: "", + Type: []string{"string"}, + Format: "", }, }, - "mode": { + "block": { SchemaProps: spec.SchemaProps{ - Description: "The IPIP mode. This can be one of \"always\" or \"cross-subnet\". A mode of \"always\" will also use IPIP tunneling for routing to destination IP addresses within this pool. A mode of \"cross-subnet\" will only use IPIP tunneling when the destination node is on a different subnet to the originating node. The default value (if not specified) is \"always\".", - Type: []string{"string"}, - Format: "", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + "deleted": { + SchemaProps: spec.SchemaProps{ + Default: false, + Type: []string{"boolean"}, + Format: "", }, }, }, + Required: []string{"handleID", "block"}, }, }, } } -func schema_libcalico_go_lib_apis_v1_IPNAT(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_IPNAT(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -808,75 +788,93 @@ func schema_libcalico_go_lib_apis_v1_IPNAT(ref common.ReferenceCallback) common. "internalIP": { SchemaProps: spec.SchemaProps{ Description: "The internal IP address which must be associated with the owning endpoint via the configured IPNetworks for the endpoint.", - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IP"), + Default: "", + Type: []string{"string"}, + Format: "", }, }, "externalIP": { SchemaProps: spec.SchemaProps{ Description: "The external IP address.", - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IP"), + Default: "", + Type: []string{"string"}, + Format: "", }, }, }, Required: []string{"internalIP", "externalIP"}, }, }, - Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/net.IP"}, } } -func schema_libcalico_go_lib_apis_v1_IPPool(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_LiveMigration(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "IPPool contains the details of a Calico IP pool resource. A pool resource is used by Calico in two ways:\n - to provide a set of IP addresses from which Calico IPAM assigns addresses\n for workloads.\n - to provide configuration specific to IP address range, such as configuration\n for the BGP daemon (e.g. when to use a GRE tunnel to encapsulate packets\n between compute hosts).", + Description: "LiveMigration describes a live-migration operation in progress. It is orchestrator-independent and holds the fields that Calico needs for optimal live migration processing.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "TypeMetadata": { + "kind": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", }, }, "metadata": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPPoolMetadata"), + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), }, }, "spec": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPPoolSpec"), + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.LiveMigrationSpec"), }, }, }, - Required: []string{"TypeMetadata"}, + Required: []string{"metadata", "spec"}, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPPoolMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPPoolSpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.LiveMigrationSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } -func schema_libcalico_go_lib_apis_v1_IPPoolList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_LiveMigrationList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "IPPoolList contains a list of IP pool resources. List types are returned from List() enumerations in the client interface.", + Description: "LiveMigrationList contains a list of LiveMigration resources.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "TypeMetadata": { + "kind": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", }, }, "metadata": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata"), + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), }, }, "items": { @@ -886,237 +884,233 @@ func schema_libcalico_go_lib_apis_v1_IPPoolList(ref common.ReferenceCallback) co Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPPool"), + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.LiveMigration"), }, }, }, }, }, }, - Required: []string{"TypeMetadata", "items"}, + Required: []string{"metadata", "items"}, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPPool", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.LiveMigration", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, } } -func schema_libcalico_go_lib_apis_v1_IPPoolMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_LiveMigrationSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "IPPoolMetadata contains the metadata for an IP pool resource.", + Description: "LiveMigrationSpec contains the specification for a LiveMigration resource.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "ObjectMetadata": { + "Source": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"), + Description: "Source identifies the WorkloadEndpoint that this live migration operation is moving from.", + Ref: ref("k8s.io/apimachinery/pkg/types.NamespacedName"), }, }, - "cidr": { + "Destination": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IPNet"), + Description: "Destination identifies the WorkloadEndpoint that this live migration operation is moving to.", + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.WorkloadEndpointIdentifier"), }, }, }, - Required: []string{"ObjectMetadata", "cidr"}, + Required: []string{"Source", "Destination"}, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata", "github.com/projectcalico/calico/libcalico-go/lib/net.IPNet"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.WorkloadEndpointIdentifier", "k8s.io/apimachinery/pkg/types.NamespacedName"}, } } -func schema_libcalico_go_lib_apis_v1_IPPoolSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_Node(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "IPPoolSpec contains the specification for an IP pool resource.", + Description: "Node contains information about a Node resource.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "ipip": { + "kind": { SchemaProps: spec.SchemaProps{ - Description: "Contains configuration for ipip tunneling for this pool. If not specified, then ipip tunneling is disabled for this pool.", - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPIPConfiguration"), + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", }, }, - "nat-outgoing": { + "apiVersion": { SchemaProps: spec.SchemaProps{ - Description: "When nat-outgoing is true, packets sent from Calico networked containers in this pool to destinations outside of this pool will be masqueraded.", - Type: []string{"boolean"}, + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, Format: "", }, }, - "disabled": { + "metadata": { SchemaProps: spec.SchemaProps{ - Description: "When disabled is true, Calico IPAM will not assign addresses from this pool.", - Type: []string{"boolean"}, - Format: "", + Description: "Standard object's metadata.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Specification of the Node.", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.NodeSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status of the Node.", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.NodeStatus"), }, }, }, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPIPConfiguration"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.NodeSpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.NodeStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } -func schema_libcalico_go_lib_apis_v1_KubeConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_NodeAddress(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, + Description: "NodeAddress represents an address assigned to a node.", + Type: []string{"object"}, Properties: map[string]spec.Schema{ - "kubeconfig": { + "address": { SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", + Description: "Address is a string representation of the actual address.", + Default: "", + Type: []string{"string"}, + Format: "", }, }, - "k8sAPIEndpoint": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "k8sKeyFile": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "k8sCertFile": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "k8sCAFile": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "k8sAPIToken": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "k8sInsecureSkipTLSVerify": { - SchemaProps: spec.SchemaProps{ - Default: false, - Type: []string{"boolean"}, - Format: "", - }, - }, - "k8sDisableNodePoll": { + "type": { SchemaProps: spec.SchemaProps{ - Default: false, - Type: []string{"boolean"}, - Format: "", + Description: "Type is the node IP type", + Type: []string{"string"}, + Format: "", }, }, }, - Required: []string{"kubeconfig", "k8sAPIEndpoint", "k8sKeyFile", "k8sCertFile", "k8sCAFile", "k8sAPIToken", "k8sInsecureSkipTLSVerify", "k8sDisableNodePoll"}, + Required: []string{"address"}, }, }, } } -func schema_libcalico_go_lib_apis_v1_Node(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_NodeBGPSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "Node contains the details of a node resource which contains the configuration for a Calico node instance running on a compute host.\n\nIn addition to creating a Node resource through calicoctl or the Calico API, the Calico node instance must also be running on the specific host and should be provided the same Name as that configured on the Node resource. Note that, by default, the Calico node instance uses the hostname of the compute host when it is not explicitly specified - in this case, the equivalent Node resource should be created using the same hostname as the Name of the Node resource.\n\nOperations on the Node resources is expected to be required when adding a new host into a Calico network, and when removing a host from a Calico network, and occasionally to modify certain configuration. Care should be taken when operating on Node resources: deleting a Node resource will remove all Node specific data.", + Description: "NodeBGPSpec contains the specification for the Node BGP configuration.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "TypeMetadata": { + "asNumber": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), + Description: "The AS Number of the node. If this is not specified, the global default value will be used.", + Type: []string{"integer"}, + Format: "int64", }, }, - "metadata": { + "ipv4Address": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.NodeMetadata"), + Description: "IPv4Address is the IPv4 address and network of this node. The IPv4 address should always be specified if you are using BGP.", + Type: []string{"string"}, + Format: "", }, }, - "spec": { + "ipv6Address": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.NodeSpec"), + Description: "IPv6Address is the IPv6 address and network of this node. Not required if you are not using BGP or you do not require IPv6 routing.", + Type: []string{"string"}, + Format: "", + }, + }, + "ipv4IPIPTunnelAddr": { + SchemaProps: spec.SchemaProps{ + Description: "IPv4IPIPTunnelAddr is the IPv4 address of the IP in IP tunnel.", + Type: []string{"string"}, + Format: "", + }, + }, + "routeReflectorClusterID": { + SchemaProps: spec.SchemaProps{ + Description: "RouteReflectorClusterID enables this node as a route reflector within the given cluster.", + Type: []string{"string"}, + Format: "", }, }, }, - Required: []string{"TypeMetadata"}, }, }, - Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.NodeMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.NodeSpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, } } -func schema_libcalico_go_lib_apis_v1_NodeBGPSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_NodeInterface(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "NodeSpec contains the specification for a Calico Node resource.", - Type: []string{"object"}, + Type: []string{"object"}, Properties: map[string]spec.Schema{ - "asNumber": { - SchemaProps: spec.SchemaProps{ - Description: "The AS Number of the node. If this is not specified, the global default value will be used.", - Type: []string{"integer"}, - Format: "int64", - }, - }, - "ipv4Address": { + "name": { SchemaProps: spec.SchemaProps{ - Description: "IPv4Address is the IPv4 address and network of this node. At least one of the IPv4 and IPv6 addresses should be specified.", - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IPNet"), + Type: []string{"string"}, + Format: "", }, }, - "ipv6Address": { + "addresses": { SchemaProps: spec.SchemaProps{ - Description: "IPv6Address is the IPv6 address and network of this node. At least one of the IPv4 and IPv6 addresses should be specified.", - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IPNet"), + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, }, }, }, }, }, - Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/net.IPNet"}, } } -func schema_libcalico_go_lib_apis_v1_NodeList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_NodeList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "A NodeList contains a list of Node resources. List types are returned from List() enumerations on the client interface.", + Description: "NodeList contains a list of Node resources.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "TypeMetadata": { + "kind": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", }, }, "metadata": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata"), + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), }, }, "items": { @@ -1126,61 +1120,60 @@ func schema_libcalico_go_lib_apis_v1_NodeList(ref common.ReferenceCallback) comm Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Node"), + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.Node"), }, }, }, }, }, }, - Required: []string{"TypeMetadata", "items"}, + Required: []string{"metadata", "items"}, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Node", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.Node", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, } } -func schema_libcalico_go_lib_apis_v1_NodeMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_NodeSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "NodeMetadata contains the metadata for a Calico Node resource.", + Description: "NodeSpec contains the specification for a Node resource.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "ObjectMetadata": { + "bgp": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"), + Description: "BGP configuration for this node.", + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.NodeBGPSpec"), }, }, - "name": { + "ipv4VXLANTunnelAddr": { SchemaProps: spec.SchemaProps{ - Description: "The name of the node.", + Description: "IPv4VXLANTunnelAddr is the IPv4 address of the VXLAN tunnel.", Type: []string{"string"}, Format: "", }, }, - }, - Required: []string{"ObjectMetadata"}, - }, - }, - Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"}, - } -} - -func schema_libcalico_go_lib_apis_v1_NodeSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "NodeSpec contains the specification for a Calico Node resource.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "bgp": { + "vxlanTunnelMACAddr": { SchemaProps: spec.SchemaProps{ - Description: "BGP configuration for this node. If this omitted, the Calico node will be run in policy-only mode.", - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.NodeBGPSpec"), + Description: "VXLANTunnelMACAddr is the MAC address of the VXLAN tunnel.", + Type: []string{"string"}, + Format: "", + }, + }, + "ipv6VXLANTunnelAddr": { + SchemaProps: spec.SchemaProps{ + Description: "IPv6VXLANTunnelAddr is the address of the IPv6 VXLAN tunnel.", + Type: []string{"string"}, + Format: "", + }, + }, + "vxlanTunnelMACAddrV6": { + SchemaProps: spec.SchemaProps{ + Description: "VXLANTunnelMACAddrV6 is the MAC address of the IPv6 VXLAN tunnel.", + Type: []string{"string"}, + Format: "", }, }, "orchRefs": { @@ -1191,7 +1184,41 @@ func schema_libcalico_go_lib_apis_v1_NodeSpec(ref common.ReferenceCallback) comm Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.OrchRef"), + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.OrchRef"), + }, + }, + }, + }, + }, + "wireguard": { + SchemaProps: spec.SchemaProps{ + Description: "Wireguard configuration for this node.", + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.NodeWireguardSpec"), + }, + }, + "addresses": { + SchemaProps: spec.SchemaProps{ + Description: "Addresses list address that a client can reach the node at.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.NodeAddress"), + }, + }, + }, + }, + }, + "interfaces": { + SchemaProps: spec.SchemaProps{ + Description: "Interfaces is a list of interfaces on the node.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.NodeInterface"), }, }, }, @@ -1201,628 +1228,569 @@ func schema_libcalico_go_lib_apis_v1_NodeSpec(ref common.ReferenceCallback) comm }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.NodeBGPSpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.OrchRef"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.NodeAddress", "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.NodeBGPSpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.NodeInterface", "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.NodeWireguardSpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.OrchRef"}, } } -func schema_libcalico_go_lib_apis_v1_OrchRef(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_NodeStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "OrchRef is used to correlate a Calico node to its corresponding representation in a given orchestrator", - Type: []string{"object"}, + Type: []string{"object"}, Properties: map[string]spec.Schema{ - "nodeName": { + "wireguardPublicKey": { SchemaProps: spec.SchemaProps{ - Description: "NodeName represents the name for this node according to the orchestrator.", + Description: "WireguardPublicKey is the IPv4 Wireguard public-key for this node. wireguardPublicKey validates if the string is a valid base64 encoded key.", Type: []string{"string"}, Format: "", }, }, - "orchestrator": { + "wireguardPublicKeyV6": { SchemaProps: spec.SchemaProps{ - Description: "Orchestrator represents the orchestrator using this node.", - Default: "", + Description: "WireguardPublicKeyV6 is the IPv6 Wireguard public-key for this node. wireguardPublicKey validates if the string is a valid base64 encoded key.", Type: []string{"string"}, Format: "", }, }, + "podCIDRs": { + SchemaProps: spec.SchemaProps{ + Description: "PodCIDR is a reflection of the Kubernetes node's spec.PodCIDRs field.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, }, - Required: []string{"orchestrator"}, }, }, } } -func schema_libcalico_go_lib_apis_v1_Policy(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_NodeWireguardSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "Policy contains information about a tiered security Policy resource. This contains a set of security rules to apply. Security policies allow a selector-based security model which can override the security profiles directly referenced by an endpoint.\n\nEach policy must do one of the following:\n\n - Match the packet and apply a “next-tier” action; this skips the rest of the tier, deferring\n to the next tier (or the explicit profiles if this is the last tier.\n - Match the packet and apply an “allow” action; this immediately accepts the packet, skipping\n all further tiers and profiles. This is not recommended in general, because it prevents\n further policy from being executed.\n - Match the packet and apply a \"deny\" action; this drops the packet immediately, skipping all\n further tiers and profiles.\n - Fail to match the packet; in which case the packet proceeds to the next policy in the tier.\n If there are no more policies in the tier then the packet is dropped.\n\nNote:\n\n\tIf no policies in a tier match an endpoint then the packet skips the tier completely. The\n\t“default deny” behavior described above only applies if some of the policies in a tier match\n\tthe endpoint.\n\nCalico implements the security policy for each endpoint individually and only the policies that have matching selectors are implemented. This ensures that the number of rules that actually need to be inserted into the kernel is proportional to the number of local endpoints rather than the total amount of policy. If no policies in a tier match a given endpoint then that tier is skipped.", + Description: "NodeWireguardSpec contains the specification for the Node wireguard configuration.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "TypeMetadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), - }, - }, - "metadata": { + "interfaceIPv4Address": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.PolicyMetadata"), + Description: "InterfaceIPv4Address is the IP address for the IPv4 Wireguard interface.", + Type: []string{"string"}, + Format: "", }, }, - "spec": { + "interfaceIPv6Address": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.PolicySpec"), + Description: "InterfaceIPv6Address is the IP address for the IPv6 Wireguard interface.", + Type: []string{"string"}, + Format: "", }, }, }, - Required: []string{"TypeMetadata"}, }, }, - Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.PolicyMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.PolicySpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, } } -func schema_libcalico_go_lib_apis_v1_PolicyList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_OrchRef(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "PolicyList contains a list of selector-based security Policy resources. List types are returned from List() enumerations on the client interface.", + Description: "OrchRef is used to correlate a Calico node to its corresponding representation in a given orchestrator", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "TypeMetadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), - }, - }, - "metadata": { + "nodeName": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata"), + Description: "NodeName represents the name for this node according to the orchestrator.", + Type: []string{"string"}, + Format: "", }, }, - "items": { + "orchestrator": { SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Policy"), - }, - }, - }, + Description: "Orchestrator represents the orchestrator using this node.", + Default: "", + Type: []string{"string"}, + Format: "", }, }, }, - Required: []string{"TypeMetadata", "items"}, + Required: []string{"orchestrator"}, }, }, - Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Policy", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, } } -func schema_libcalico_go_lib_apis_v1_PolicyMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_QoSControls(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "PolicyMetadata contains the metadata for a selector-based security Policy resource.", + Description: "QoSControls contains QoS limits configuration.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "ObjectMetadata": { + "ingressBandwidth": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"), + Description: "Ingress bandwidth controls. These are only applied if IngressBandwidth != 0. In that case:\n\n- IngressBandwidth must be between 1000 (1k) and 10^15 (1P).\n\n- IngressBurst must be between 1000 (1k) and 34359738360 (32Gi - 8). If specified as 0,\n it is defaulted to 4294967296 (4Gi).\n\n- IngressPeakrate may be 0 if it is acceptable for bursts to be transmitted at line rate.\n In that case IngressMinburst should also be 0. But if IngressPeakrate != 0:\n\n - IngressPeakrate must be between 1010 (1.01k) and 1.01 x 10^15 (1.01P).\n\n - IngressMinburst must either be 0 - in which case Calico will use the MTU of the\n relevant workload interface as the minimum burst size - or be between 1000 (1k) and\n 10^8 (100M).\n\nIngress bandwidth rate limit in bits per second", + Type: []string{"integer"}, + Format: "int64", }, }, - "name": { + "ingressBurst": { SchemaProps: spec.SchemaProps{ - Description: "The name of the selector-based security policy.", - Type: []string{"string"}, - Format: "", + Description: "Ingress bandwidth burst size in bits", + Type: []string{"integer"}, + Format: "int64", }, }, - "tier": { + "ingressPeakrate": { SchemaProps: spec.SchemaProps{ - Description: "The name of the tier that this policy belongs to. If this is omitted, the default tier (name is \"default\") is assumed. The specified tier must exist in order to create security policies within the tier, the \"default\" tier is created automatically if it does not exist, this means for deployments requiring only a single Tier, the tier name may be omitted on all policy management requests.", - Type: []string{"string"}, - Format: "", + Description: "Ingress bandwidth peakrate limit in bits per second", + Type: []string{"integer"}, + Format: "int64", }, }, - "annotations": { + "ingressMinburst": { SchemaProps: spec.SchemaProps{ - Description: "Arbitrary key-value information to be used by clients.", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, + Description: "Ingress bandwidth minburst size in bytes (not bits because it is typically the MTU)", + Type: []string{"integer"}, + Format: "int64", }, }, - }, - Required: []string{"ObjectMetadata"}, - }, - }, - Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"}, - } -} - -func schema_libcalico_go_lib_apis_v1_PolicySpec(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "PolicySpec contains the specification for a selector-based security Policy resource.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "order": { + "egressBandwidth": { SchemaProps: spec.SchemaProps{ - Description: "Order is an optional field that specifies the order in which the policy is applied within a given tier. Policies with higher \"order\" are applied after those with lower order. If the order is omitted, it may be considered to be \"infinite\" - i.e. the policy will be applied last. Policies with identical order and within the same Tier will be applied in alphanumerical order based on the Policy \"Name\".", - Type: []string{"number"}, - Format: "double", + Description: "Egress bandwidth controls. These are only applied if EgressBandwidth != 0. The same detail applies here as for ingress bandwidth, except using the corresponding Egress fields.\n\nEgress bandwidth rate limit in bits per second", + Type: []string{"integer"}, + Format: "int64", }, }, - "ingress": { + "egressBurst": { SchemaProps: spec.SchemaProps{ - Description: "The ordered set of ingress rules. Each rule contains a set of packet match criteria and a corresponding action to apply.", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Rule"), - }, - }, - }, + Description: "Egress bandwidth burst size in bits", + Type: []string{"integer"}, + Format: "int64", }, }, - "egress": { + "egressPeakrate": { SchemaProps: spec.SchemaProps{ - Description: "The ordered set of egress rules. Each rule contains a set of packet match criteria and a corresponding action to apply.", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Rule"), - }, - }, - }, + Description: "Egress bandwidth peakrate limit in bits per second", + Type: []string{"integer"}, + Format: "int64", }, }, - "selector": { + "egressMinburst": { SchemaProps: spec.SchemaProps{ - Description: "The selector is an expression used to pick out the endpoints that the policy should be applied to.\n\nSelector expressions follow this syntax:\n\n\tlabel == \"string_literal\" -> comparison, e.g. my_label == \"foo bar\"\n\tlabel != \"string_literal\" -> not equal; also matches if label is not present\n\tlabel in { \"a\", \"b\", \"c\", ... } -> true if the value of label X is one of \"a\", \"b\", \"c\"\n\tlabel not in { \"a\", \"b\", \"c\", ... } -> true if the value of label X is not one of \"a\", \"b\", \"c\"\n\thas(label_name) -> True if that label is present\n\t! expr -> negation of expr\n\texpr && expr -> Short-circuit and\n\texpr || expr -> Short-circuit or\n\t( expr ) -> parens for grouping\n\tall() or the empty selector -> matches all endpoints.\n\nLabel names are allowed to contain alphanumerics, -, _ and /. String literals are more permissive but they do not support escape characters.\n\nExamples (with made-up labels):\n\n\ttype == \"webserver\" && deployment == \"prod\"\n\ttype in {\"frontend\", \"backend\"}\n\tdeployment != \"dev\"\n\t! has(label_name)", - Default: "", - Type: []string{"string"}, - Format: "", + Description: "Egress bandwidth minburst size in bytes (not bits because it is typically the MTU)", + Type: []string{"integer"}, + Format: "int64", }, }, - "doNotTrack": { + "ingressPacketRate": { SchemaProps: spec.SchemaProps{ - Description: "DoNotTrack indicates whether packets matched by the rules in this policy should go through the data plane's connection tracking, such as Linux conntrack. If True, the rules in this policy are applied before any data plane connection tracking, and packets allowed by this policy are marked as not to be tracked.", - Type: []string{"boolean"}, - Format: "", + Description: "Ingress packet rate limit in packets per second. Only applied if non-zero. When non-zero:\n\n- IngressPacketRate must be between 1 and 10^4 (10k).\n\n- IngressPacketBurst must be between 1 and 10^4 (10k). If specified as 0, it is\n defaulted to 5.", + Type: []string{"integer"}, + Format: "int64", }, }, - "preDNAT": { + "ingressPacketBurst": { SchemaProps: spec.SchemaProps{ - Description: "PreDNAT indicates to apply the rules in this policy before any DNAT.", - Type: []string{"boolean"}, - Format: "", + Description: "Ingress packet rate burst size in number of packets", + Type: []string{"integer"}, + Format: "int64", }, }, - "types": { + "egressPacketRate": { SchemaProps: spec.SchemaProps{ - Description: "Types indicates whether this policy applies to ingress, or to egress, or to both. When not explicitly specified (and so the value on creation is empty or nil), Calico defaults Types according to what IngressRules and EgressRules are present in the policy. The default is:\n\n- [ PolicyTypeIngress ], if there are no EgressRules (including the case where there are\n also no IngressRules)\n\n- [ PolicyTypeEgress ], if there are EgressRules but no IngressRules\n\n- [ PolicyTypeIngress, PolicyTypeEgress ], if there are both IngressRules and EgressRules.\n\nWhen the policy is read back again, Types will always be one of these values, never empty or nil.", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, + Description: "Egress packet rate limit in packets per second. Only applied if non-zero. The same detail applies here as for egress packet rate, except using the corresponding Egress fields.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "egressPacketBurst": { + SchemaProps: spec.SchemaProps{ + Description: "Egress packet rate burst size in number of packets", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "ingressMaxConnections": { + SchemaProps: spec.SchemaProps{ + Description: "Ingress maximum number of connections (absolute number of connections, no unit). Only applied if non-zero. When non-zero, must be between 1 and 4294967295.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "egressMaxConnections": { + SchemaProps: spec.SchemaProps{ + Description: "Egress maximum number of connections (absolute number of connections, no unit). Only applied if non-zero. When non-zero, must be between 1 and 4294967295.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "dscp": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/projectcalico/api/pkg/lib/numorstring.DSCP"), }, }, }, - Required: []string{"selector"}, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Rule"}, + "github.com/projectcalico/api/pkg/lib/numorstring.DSCP"}, } } -func schema_libcalico_go_lib_apis_v1_Profile(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_WorkloadEndpoint(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "Profile contains the details a security profile resource. A profile is set of security rules to apply on an endpoint. An endpoint (either a host endpoint or an endpoint on a workload) can reference zero or more profiles. The profile rules are applied directly to the endpoint *after* the selector-based security policy has been applied, and in the order the profiles are declared on the endpoint.", + Description: "WorkloadEndpoint contains information about a WorkloadEndpoint resource that is a peer of a Calico compute node.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "TypeMetadata": { + "kind": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", }, }, "metadata": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.ProfileMetadata"), + Description: "Standard object's metadata.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.ProfileSpec"), + Description: "Specification of the WorkloadEndpoint.", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.WorkloadEndpointSpec"), }, }, }, - Required: []string{"TypeMetadata"}, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.ProfileMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.ProfileSpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.WorkloadEndpointSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } -func schema_libcalico_go_lib_apis_v1_ProfileList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_WorkloadEndpointIdentifier(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "A ProfileList contains a list of security Profile resources. List types are returned from List() enumerations on the client interface.", - Type: []string{"object"}, + Type: []string{"object"}, Properties: map[string]spec.Schema{ - "TypeMetadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), - }, - }, - "metadata": { + "NamespacedName": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata"), + Description: "NamespacedName is used when the WorkloadEndpoint can be identified directly by its name and namespace.", + Ref: ref("k8s.io/apimachinery/pkg/types.NamespacedName"), }, }, - "items": { + "Selector": { SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Profile"), - }, - }, - }, + Description: "Selector is used when the WorkloadEndpoint must be identified by a selector expression.", + Type: []string{"string"}, + Format: "", }, }, }, - Required: []string{"TypeMetadata", "items"}, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Profile", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, + "k8s.io/apimachinery/pkg/types.NamespacedName"}, } } -func schema_libcalico_go_lib_apis_v1_ProfileMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_WorkloadEndpointList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "ProfileMetadata contains the metadata for a security Profile resource.", + Description: "WorkloadEndpointList contains a list of WorkloadEndpoint resources.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "ObjectMetadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"), - }, - }, - "name": { + "kind": { SchemaProps: spec.SchemaProps{ - Description: "The name of the endpoint.", + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", Type: []string{"string"}, Format: "", }, }, - "tags": { - SchemaProps: spec.SchemaProps{ - Description: "A list of tags that are applied to each endpoint that references this profile.", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - }, - }, - "labels": { + "apiVersion": { SchemaProps: spec.SchemaProps{ - Description: "The labels to apply to each endpoint that references this profile. It is expected that many endpoints share the same labels. For example, they could be used to label all \"production\" workloads with \"deployment=prod\" so that security policy can be applied to production workloads.", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", }, }, - }, - Required: []string{"ObjectMetadata"}, - }, - }, - Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"}, - } -} - -func schema_libcalico_go_lib_apis_v1_ProfileSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "ProfileSpec contains the specification for a security Profile resource.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "ingress": { + "metadata": { SchemaProps: spec.SchemaProps{ - Description: "The ordered set of ingress rules. Each rule contains a set of packet match criteria and a corresponding action to apply.", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Rule"), - }, - }, - }, + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), }, }, - "egress": { + "items": { SchemaProps: spec.SchemaProps{ - Description: "The ordered set of egress rules. Each rule contains a set of packet match criteria and a corresponding action to apply.", - Type: []string{"array"}, + Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Rule"), + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.WorkloadEndpoint"), }, }, }, }, }, }, + Required: []string{"metadata", "items"}, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Rule"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.WorkloadEndpoint", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, } } -func schema_libcalico_go_lib_apis_v1_Rule(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_WorkloadEndpointPort(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "A Rule encapsulates a set of match criteria and an action. Both selector-based security Policy and security Profiles reference rules - separated out as a list of rules for both ingress and egress packet matching.\n\nEach positive match criteria has a negated version, prefixed with \"Not\". All the match criteria within a rule must be satisfied for a packet to match. A single rule can contain the positive and negative version of a match and both must be satisfied for the rule to match.", + Description: "WorkloadEndpointPort represents one endpoint's named or mapped port", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "action": { + "name": { SchemaProps: spec.SchemaProps{ Default: "", Type: []string{"string"}, Format: "", }, }, - "ipVersion": { - SchemaProps: spec.SchemaProps{ - Description: "IPVersion is an optional field that restricts the rule to only match a specific IP version.", - Type: []string{"integer"}, - Format: "int32", - }, - }, "protocol": { SchemaProps: spec.SchemaProps{ - Description: "Protocol is an optional field that restricts the rule to only apply to traffic of a specific IP protocol. Required if any of the EntityRules contain Ports (because ports only apply to certain protocols).\n\nMust be one of these string values: \"tcp\", \"udp\", \"icmp\", \"icmpv6\", \"sctp\", \"udplite\" or an integer in the range 1-255.", - Ref: ref("github.com/projectcalico/api/pkg/lib/numorstring.Protocol"), - }, - }, - "icmp": { - SchemaProps: spec.SchemaProps{ - Description: "ICMP is an optional field that restricts the rule to apply to a specific type and code of ICMP traffic. This should only be specified if the Protocol field is set to \"icmp\" or \"icmpv6\".", - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.ICMPFields"), - }, - }, - "notProtocol": { - SchemaProps: spec.SchemaProps{ - Description: "NotProtocol is the negated version of the Protocol field.", - Ref: ref("github.com/projectcalico/api/pkg/lib/numorstring.Protocol"), + Ref: ref("github.com/projectcalico/api/pkg/lib/numorstring.Protocol"), }, }, - "notICMP": { + "port": { SchemaProps: spec.SchemaProps{ - Description: "NotICMP is the negated version of the ICMP field.", - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.ICMPFields"), + Default: 0, + Type: []string{"integer"}, + Format: "int32", }, }, - "source": { + "hostPort": { SchemaProps: spec.SchemaProps{ - Description: "Source contains the match criteria that apply to source entity.", - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.EntityRule"), + Default: 0, + Type: []string{"integer"}, + Format: "int32", }, }, - "destination": { + "hostIP": { SchemaProps: spec.SchemaProps{ - Description: "Destination contains the match criteria that apply to destination entity.", - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.EntityRule"), + Default: "", + Type: []string{"string"}, + Format: "", }, }, }, - Required: []string{"action"}, + Required: []string{"name", "protocol", "port", "hostPort", "hostIP"}, }, }, Dependencies: []string{ - "github.com/projectcalico/api/pkg/lib/numorstring.Protocol", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.EntityRule", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.ICMPFields"}, + "github.com/projectcalico/api/pkg/lib/numorstring.Protocol"}, } } -func schema_libcalico_go_lib_apis_v1_Tier(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_internalapi_WorkloadEndpointSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "Tier contains the details of a security policy tier resource. A tier contains a set of policies that are applied to packets. Multiple tiers may be created and each tier is applied in the order specified in the tier specification.\n\nSee Policy for more information.", + Description: "WorkloadEndpointMetadata contains the specification for a WorkloadEndpoint resource.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "TypeMetadata": { + "orchestrator": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), + Description: "The name of the orchestrator.", + Type: []string{"string"}, + Format: "", }, }, - "metadata": { + "workload": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.TierMetadata"), + Description: "The name of the workload.", + Type: []string{"string"}, + Format: "", }, }, - "spec": { + "node": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.TierSpec"), + Description: "The node name identifying the Calico node instance.", + Type: []string{"string"}, + Format: "", }, }, - }, - Required: []string{"TypeMetadata"}, - }, - }, - Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.TierMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.TierSpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, - } -} - -func schema_libcalico_go_lib_apis_v1_TierList(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "A TierList contains a list of tier resources. List types are returned from List() enumerations in the client interface.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "TypeMetadata": { + "containerID": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), + Description: "The container ID.", + Type: []string{"string"}, + Format: "", }, }, - "metadata": { + "pod": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata"), + Description: "The Pod name.", + Type: []string{"string"}, + Format: "", }, }, - "items": { + "endpoint": { SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, + Description: "The Endpoint name.", + Type: []string{"string"}, + Format: "", + }, + }, + "serviceAccountName": { + SchemaProps: spec.SchemaProps{ + Description: "ServiceAccountName, if specified, is the name of the k8s ServiceAccount for this pod.", + Type: []string{"string"}, + Format: "", + }, + }, + "ipNetworks": { + SchemaProps: spec.SchemaProps{ + Description: "IPNetworks is a list of subnets allocated to this endpoint. IP packets will only be allowed to leave this interface if they come from an address in one of these subnets. Currently only /32 for IPv4 and /128 for IPv6 networks are supported.", + Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Tier"), + Default: "", + Type: []string{"string"}, + Format: "", }, }, }, }, }, - }, - Required: []string{"TypeMetadata", "items"}, - }, - }, - Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Tier", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, - } -} - -func schema_libcalico_go_lib_apis_v1_TierMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "TierMetadata contains the metadata for a security policy Tier.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "ObjectMetadata": { + "ipNATs": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"), + Description: "IPNATs is a list of 1:1 NAT mappings to apply to the endpoint. Inbound connections to the external IP will be forwarded to the internal IP. Connections initiated from the internal IP will not have their source address changed, except when an endpoint attempts to connect one of its own external IPs. Each internal IP must be associated with the same endpoint via the configured IPNetworks.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.IPNAT"), + }, + }, + }, }, }, - "name": { + "ipv4Gateway": { SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", + Description: "IPv4Gateway is the gateway IPv4 address for traffic from the workload.", + Type: []string{"string"}, + Format: "", }, }, - }, - Required: []string{"ObjectMetadata"}, - }, - }, - Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"}, - } -} - -func schema_libcalico_go_lib_apis_v1_TierSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "TierSpec contains the specification for a security policy Tier.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "order": { + "ipv6Gateway": { SchemaProps: spec.SchemaProps{ - Description: "Order is an optional field that specifies the order in which the tier is applied. Tiers with higher \"order\" are applied after those with lower order. If the order is omitted, it may be considered to be \"infinite\" - i.e. the tier will be applied last. Tiers with identical order will be applied in alphanumerical order based on the Tier \"Name\".", - Type: []string{"number"}, - Format: "double", + Description: "IPv6Gateway is the gateway IPv6 address for traffic from the workload.", + Type: []string{"string"}, + Format: "", + }, + }, + "profiles": { + SchemaProps: spec.SchemaProps{ + Description: "A list of security Profile resources that apply to this endpoint. Each profile is applied in the order that they appear in this list. Profile rules are applied after the selector-based security policy.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "interfaceName": { + SchemaProps: spec.SchemaProps{ + Description: "InterfaceName the name of the Linux interface on the host: for example, tap80.", + Type: []string{"string"}, + Format: "", + }, + }, + "mac": { + SchemaProps: spec.SchemaProps{ + Description: "MAC is the MAC address of the endpoint interface.", + Type: []string{"string"}, + Format: "", + }, + }, + "ports": { + SchemaProps: spec.SchemaProps{ + Description: "Ports contains the endpoint's named ports, which may be referenced in security policy rules.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.WorkloadEndpointPort"), + }, + }, + }, + }, + }, + "allowSpoofedSourcePrefixes": { + SchemaProps: spec.SchemaProps{ + Description: "AllowSpoofedSourcePrefixes is a list of CIDRs that the endpoint should be able to send traffic from, bypassing the RPF check.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "qosControls": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.QoSControls"), }, }, }, }, }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.IPNAT", "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.QoSControls", "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi.WorkloadEndpointPort"}, } } -func schema_libcalico_go_lib_apis_v1_WorkloadEndpoint(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_BGPPeer(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, + Description: "BGPPeer contains information about a BGP peer resource that is a peer of a Calico compute node.", + Type: []string{"object"}, Properties: map[string]spec.Schema{ "TypeMetadata": { SchemaProps: spec.SchemaProps{ @@ -1832,30 +1800,32 @@ func schema_libcalico_go_lib_apis_v1_WorkloadEndpoint(ref common.ReferenceCallba }, "metadata": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.WorkloadEndpointMetadata"), + Description: "Metadata for a BGPPeer.", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.BGPPeerMetadata"), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.WorkloadEndpointSpec"), + Description: "Specification for a BGPPeer.", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.BGPPeerSpec"), }, }, }, - Required: []string{"TypeMetadata"}, + Required: []string{"TypeMetadata", "metadata", "spec"}, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.WorkloadEndpointMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.WorkloadEndpointSpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.BGPPeerMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.BGPPeerSpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, } } -func schema_libcalico_go_lib_apis_v1_WorkloadEndpointList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_BGPPeerList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "WorkloadEndpointList contains a list of Workload Endpoint resources. List types are returned from List() enumerations in the client interface.", + Description: "BGPPeerList contains a list of BGP Peer resources. List types are returned from List() enumerations in the client interface.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "TypeMetadata": { @@ -1877,26 +1847,26 @@ func schema_libcalico_go_lib_apis_v1_WorkloadEndpointList(ref common.ReferenceCa Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.WorkloadEndpoint"), + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.BGPPeer"), }, }, }, }, }, }, - Required: []string{"TypeMetadata", "items"}, + Required: []string{"TypeMetadata", "metadata", "items"}, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.WorkloadEndpoint", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.BGPPeer", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, } } -func schema_libcalico_go_lib_apis_v1_WorkloadEndpointMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_BGPPeerMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "WorkloadEndpointMetadata contains the Metadata for a WorkloadEndpoint resource.", + Description: "BGPPeerMetadata contains the metadata for a BGPPeer resource.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "ObjectMetadata": { @@ -1905,417 +1875,419 @@ func schema_libcalico_go_lib_apis_v1_WorkloadEndpointMetadata(ref common.Referen Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"), }, }, - "name": { - SchemaProps: spec.SchemaProps{ - Description: "The name of the endpoint. This may be omitted on a create, in which case an endpoint ID will be automatically created, and the endpoint ID will be included in the response.", - Type: []string{"string"}, - Format: "", - }, - }, - "workload": { - SchemaProps: spec.SchemaProps{ - Description: "The name of the workload.", - Type: []string{"string"}, - Format: "", - }, - }, - "orchestrator": { + "scope": { SchemaProps: spec.SchemaProps{ - Description: "The name of the orchestrator.", + Description: "The scope of the peer. This may be global or node. A global peer is a BGP device that peers with all Calico nodes. A node peer is a BGP device that peers with the specified Calico node (specified by the node hostname).", + Default: "", Type: []string{"string"}, Format: "", }, }, "node": { SchemaProps: spec.SchemaProps{ - Description: "The node name identifying the Calico node instance.", + Description: "The node name identifying the Calico node instance that is peering with this peer. When modifying a BGP peer, the node must be specified when the scope is `node`, and must be omitted when the scope is `global`.", Type: []string{"string"}, Format: "", }, }, - "activeInstanceID": { + "peerIP": { SchemaProps: spec.SchemaProps{ - Description: "ActiveInstanceID is an optional field that orchestrators may use to store additional information about the endpoint. The primary use case is to store a unique identifier for the active instance of a container. For example, with Calico CNI, a re-spawned container may use the same endpoint indexing (Node, Orchestrator, Workload, Endpoint) for the new container as for the old - the ActiveInstanceID is used to store an additional unique ID which the CNI plugin uses to determine whether the DEL operation needs to delete the Calico WorkloadEndpoint. This field is not an index field of the WorkloadEndpoint resource.", - Type: []string{"string"}, - Format: "", + Description: "The IP address of the peer.", + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IP"), }, }, - "labels": { + }, + Required: []string{"ObjectMetadata", "scope", "peerIP"}, + }, + }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata", "github.com/projectcalico/calico/libcalico-go/lib/net.IP"}, + } +} + +func schema_libcalico_go_lib_apis_v1_BGPPeerSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "BGPPeerSpec contains the specification for a BGPPeer resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "asNumber": { SchemaProps: spec.SchemaProps{ - Description: "The labels applied to the workload endpoint. It is expected that many endpoints share the same labels. For example, they could be used to label all \"production\" workloads with \"deployment=prod\" so that security policy can be applied to production workloads.", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, + Description: "The AS Number of the peer.", + Default: 0, + Type: []string{"integer"}, + Format: "int64", }, }, }, - Required: []string{"ObjectMetadata"}, + Required: []string{"asNumber"}, }, }, - Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"}, } } -func schema_libcalico_go_lib_apis_v1_WorkloadEndpointSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_CalicoAPIConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "WorkloadEndpointMetadata contains the specification for a WorkloadEndpoint resource.", + Description: "CalicoAPIConfig contains the connection information for a Calico CalicoAPIConfig resource", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "ipNetworks": { + "TypeMetadata": { SchemaProps: spec.SchemaProps{ - Description: "IPNetworks is a list of subnets allocated to this endpoint. IP packets will only be allowed to leave this interface if they come from an address in one of these subnets.\n\nCurrently only /32 for IPv4 and /128 for IPv6 networks are supported.", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IPNet"), - }, - }, - }, + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), }, }, - "ipNATs": { + "metadata": { SchemaProps: spec.SchemaProps{ - Description: "IPNATs is a list of 1:1 NAT mappings to apply to the endpoint. Inbound connections to the external IP will be forwarded to the internal IP. Connections initiated from the internal IP will not have their source address changed, except when an endpoint attempts to connect one of its own external IPs. Each internal IP must be associated with the same endpoint via the configured IPNetworks.", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPNAT"), - }, - }, - }, + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.CalicoAPIConfigMetadata"), }, }, - "ipv4Gateway": { + "spec": { SchemaProps: spec.SchemaProps{ - Description: "IPv4Gateway is the gateway IPv4 address for traffic from the workload.", - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IP"), + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.CalicoAPIConfigSpec"), }, }, - "ipv6Gateway": { + }, + Required: []string{"TypeMetadata", "metadata", "spec"}, + }, + }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.CalicoAPIConfigMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.CalicoAPIConfigSpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, + } +} + +func schema_libcalico_go_lib_apis_v1_CalicoAPIConfigMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CalicoAPIConfigMetadata contains the metadata for a Calico CalicoAPIConfig resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "ObjectMetadata": { SchemaProps: spec.SchemaProps{ - Description: "IPv6Gateway is the gateway IPv6 address for traffic from the workload.", - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IP"), + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"), }, }, - "profiles": { - SchemaProps: spec.SchemaProps{ - Description: "A list of security Profile resources that apply to this endpoint. Each profile is applied in the order that they appear in this list. Profile rules are applied after the selector-based security policy.", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - }, - }, - "interfaceName": { - SchemaProps: spec.SchemaProps{ - Description: "InterfaceName the name of the Linux interface on the host: for example, tap80.", - Type: []string{"string"}, - Format: "", - }, - }, - "mac": { + }, + Required: []string{"ObjectMetadata"}, + }, + }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"}, + } +} + +func schema_libcalico_go_lib_apis_v1_CalicoAPIConfigSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CalicoAPIConfigSpec contains the specification for a Calico CalicoAPIConfig resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "datastoreType": { SchemaProps: spec.SchemaProps{ - Description: "MAC is the MAC address of the endpoint interface.", - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.MAC"), + Default: "", + Type: []string{"string"}, + Format: "", }, }, - "ports": { + "EtcdConfig": { SchemaProps: spec.SchemaProps{ - Description: "Ports contains the endpoint's named ports, which may be referenced in security policy rules.", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.EndpointPort"), - }, - }, - }, + Description: "Inline the etcd config fields", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.EtcdConfig"), }, }, - "allow_spoofed_source_prefixes": { + "KubeConfig": { SchemaProps: spec.SchemaProps{ - Description: "AllowSpoofedSourcePrefixes is a list of CIDRs this workload endpoint is allowed to send traffic from, i.e. this allows the workload endpoint to spoof its IP address using addresses in these prefixes", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IPNet"), - }, - }, - }, + Description: "Inline the k8s config fields.", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.KubeConfig"), }, }, }, + Required: []string{"datastoreType", "EtcdConfig", "KubeConfig"}, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.EndpointPort", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPNAT", "github.com/projectcalico/calico/libcalico-go/lib/net.IP", "github.com/projectcalico/calico/libcalico-go/lib/net.IPNet", "github.com/projectcalico/calico/libcalico-go/lib/net.MAC"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.EtcdConfig", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.KubeConfig"}, } } -func schema_libcalico_go_lib_apis_v3_AllocationAttribute(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_EndpointPort(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: map[string]spec.Schema{ - "handle_id": { + "name": { SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", + Default: "", + Type: []string{"string"}, + Format: "", }, }, - "secondary": { + "protocol": { SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, + Ref: ref("github.com/projectcalico/api/pkg/lib/numorstring.Protocol"), + }, + }, + "port": { + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int32", }, }, }, + Required: []string{"name", "protocol", "port"}, }, }, + Dependencies: []string{ + "github.com/projectcalico/api/pkg/lib/numorstring.Protocol"}, } } -func schema_libcalico_go_lib_apis_v3_BlockAffinity(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_EntityRule(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "BlockAffinity maintains a block affinity's state", + Description: "An EntityRule is a sub-component of a Rule comprising the match criteria specific to a particular entity (that is either the source or destination).\n\nA source EntityRule matches the source endpoint and originating traffic. A destination EntityRule matches the destination endpoint and terminating traffic.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "kind": { + "tag": { SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Description: "Tag is an optional field that restricts the rule to only apply to traffic that originates from (or terminates at) endpoints that have profiles with the given tag in them.", Type: []string{"string"}, Format: "", }, }, - "apiVersion": { + "net": { SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", + Description: "Net is an optional field that restricts the rule to only apply to traffic that originates from (or terminates at) IP addresses in the given subnet. Deprecated: superseded by the Nets field.", + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IPNet"), }, }, - "metadata": { + "nets": { SchemaProps: spec.SchemaProps{ - Description: "Standard object's metadata.", - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + Description: "Nets is an optional field that restricts the rule to only apply to traffic that originates from (or terminates at) IP addresses in any of the given subnets.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IPNet"), + }, + }, + }, }, }, - "spec": { + "selector": { SchemaProps: spec.SchemaProps{ - Description: "Specification of the BlockAffinity.", - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v3.BlockAffinitySpec"), + Description: "Selector is an optional field that contains a selector expression (see Policy for sample syntax). Only traffic that originates from (terminates at) endpoints matching the selector will be matched.\n\nNote that: in addition to the negated version of the Selector (see NotSelector below), the selector expression syntax itself supports negation. The two types of negation are subtly different. One negates the set of matched endpoints, the other negates the whole match:\n\n\tSelector = \"!has(my_label)\" matches packets that are from other Calico-controlled\n\tendpoints that do not have the label \"my_label\".\n\n\tNotSelector = \"has(my_label)\" matches packets that are not from Calico-controlled\n\tendpoints that do have the label \"my_label\".\n\nThe effect is that the latter will accept packets from non-Calico sources whereas the former is limited to packets from Calico-controlled endpoints.", + Type: []string{"string"}, + Format: "", }, }, - }, - }, - }, - Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.BlockAffinitySpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, - } -} - -func schema_libcalico_go_lib_apis_v3_BlockAffinityList(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "BlockAffinityList contains a list of BlockAffinity resources.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { + "ports": { SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", + Description: "Ports is an optional field that restricts the rule to only apply to traffic that has a source (destination) port that matches one of these ranges/values. This value is a list of integers or strings that represent ranges of ports.\n\nSince only some protocols have ports, if any ports are specified it requires the Protocol match in the Rule to be set to \"tcp\" or \"udp\".", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/projectcalico/api/pkg/lib/numorstring.Port"), + }, + }, + }, }, }, - "apiVersion": { + "notTag": { SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Description: "NotTag is the negated version of the Tag field.", Type: []string{"string"}, Format: "", }, }, - "metadata": { + "notNet": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + Description: "NotNet is an optional field that restricts the rule to only apply to traffic that does not originate from (or terminate at) an IP address in the given subnet. Deprecated: superseded by NotNets.", + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IPNet"), }, }, - "items": { + "notNets": { SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, + Description: "NotNets is an optional field that restricts the rule to only apply to traffic that does not originate from (or terminate at) an IP address in any of the given subnets.", + Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v3.BlockAffinity"), + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IPNet"), + }, + }, + }, + }, + }, + "notSelector": { + SchemaProps: spec.SchemaProps{ + Description: "NotSelector is the negated version of the Selector field. See Selector field for subtleties with negated selectors.", + Type: []string{"string"}, + Format: "", + }, + }, + "notPorts": { + SchemaProps: spec.SchemaProps{ + Description: "NotPorts is the negated version of the Ports field.\n\nSince only some protocols have ports, if any ports are specified it requires the Protocol match in the Rule to be set to \"tcp\" or \"udp\".", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/projectcalico/api/pkg/lib/numorstring.Port"), }, }, }, }, }, }, - Required: []string{"metadata", "items"}, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.BlockAffinity", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + "github.com/projectcalico/api/pkg/lib/numorstring.Port", "github.com/projectcalico/calico/libcalico-go/lib/net.IPNet"}, } } -func schema_libcalico_go_lib_apis_v3_BlockAffinitySpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_EtcdConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "BlockAffinitySpec contains the specification for a BlockAffinity resource.", - Type: []string{"object"}, + Type: []string{"object"}, Properties: map[string]spec.Schema{ - "state": { + "etcdScheme": { SchemaProps: spec.SchemaProps{ Default: "", Type: []string{"string"}, Format: "", }, }, - "node": { + "etcdAuthority": { SchemaProps: spec.SchemaProps{ Default: "", Type: []string{"string"}, Format: "", }, }, - "type": { + "etcdEndpoints": { SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", + Default: "", + Type: []string{"string"}, + Format: "", }, }, - "cidr": { + "etcdDiscoverySrv": { SchemaProps: spec.SchemaProps{ Default: "", Type: []string{"string"}, Format: "", }, }, - "deleted": { + "etcdUsername": { SchemaProps: spec.SchemaProps{ - Description: "Deleted indicates that this block affinity is being deleted. This field is a string for compatibility with older releases that mistakenly treat this field as a string.", - Default: "", - Type: []string{"string"}, - Format: "", + Default: "", + Type: []string{"string"}, + Format: "", }, }, - }, - Required: []string{"state", "node", "cidr", "deleted"}, - }, - }, - } -} - -func schema_libcalico_go_lib_apis_v3_IPAMBlock(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "IPAMBlock contains information about a block for IP address assignment.", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { + "etcdPassword": { SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", + Default: "", + Type: []string{"string"}, + Format: "", }, }, - "apiVersion": { + "etcdKeyFile": { SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", + Default: "", + Type: []string{"string"}, + Format: "", }, }, - "metadata": { + "etcdCertFile": { SchemaProps: spec.SchemaProps{ - Description: "Standard object's metadata.", - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + Default: "", + Type: []string{"string"}, + Format: "", }, }, - "spec": { + "etcdCACertFile": { SchemaProps: spec.SchemaProps{ - Description: "Specification of the IPAMBlock.", - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v3.IPAMBlockSpec"), + Default: "", + Type: []string{"string"}, + Format: "", }, }, }, + Required: []string{"etcdScheme", "etcdAuthority", "etcdEndpoints", "etcdDiscoverySrv", "etcdUsername", "etcdPassword", "etcdKeyFile", "etcdCertFile", "etcdCACertFile"}, }, }, - Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.IPAMBlockSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } -func schema_libcalico_go_lib_apis_v3_IPAMBlockList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_HostEndpoint(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "IPAMBlockList contains a list of IPAMBlock resources.", + Description: "HostEndpoint contains information about a \"bare-metal\" interfaces attached to the host that is running Calico's agent, Felix. By default, Calico doesn't apply any policy to such interfaces.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "kind": { + "TypeMetadata": { SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), }, }, - "apiVersion": { + "metadata": { SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.HostEndpointMetadata"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.HostEndpointSpec"), + }, + }, + }, + Required: []string{"TypeMetadata", "metadata", "spec"}, + }, + }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.HostEndpointMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.HostEndpointSpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, + } +} + +func schema_libcalico_go_lib_apis_v1_HostEndpointList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HostEndpointList contains a list of Host Endpoint resources. List types are returned from List() enumerations in the client interface.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "TypeMetadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), }, }, "metadata": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata"), }, }, "items": { @@ -2325,330 +2297,269 @@ func schema_libcalico_go_lib_apis_v3_IPAMBlockList(ref common.ReferenceCallback) Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v3.IPAMBlock"), + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.HostEndpoint"), }, }, }, }, }, }, - Required: []string{"metadata", "items"}, + Required: []string{"TypeMetadata", "metadata", "items"}, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.IPAMBlock", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.HostEndpoint", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, } } -func schema_libcalico_go_lib_apis_v3_IPAMBlockSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_HostEndpointMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "IPAMBlockSpec contains the specification for an IPAMBlock resource.", + Description: "HostEndpointMetadata contains the Metadata for a HostEndpoint resource.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "cidr": { + "ObjectMetadata": { SchemaProps: spec.SchemaProps{ - Description: "The block's CIDR.", - Default: "", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"), + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "The name of the endpoint.", Type: []string{"string"}, Format: "", }, }, - "affinity": { + "node": { SchemaProps: spec.SchemaProps{ - Description: "Affinity of the block, if this block has one. If set, it will be of the form \"host:\". If not set, this block is not affine to a host.", + Description: "The node name identifying the Calico node instance.", Type: []string{"string"}, Format: "", }, }, - "allocations": { + "labels": { SchemaProps: spec.SchemaProps{ - Description: "Array of allocations in-use within this block. nil entries mean the allocation is free. For non-nil entries at index i, the index is the ordinal of the allocation within this block and the value is the index of the associated attributes in the Attributes array.", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ + Description: "The labels applied to the host endpoint. It is expected that many endpoints share the same labels. For example, they could be used to label all \"production\" workloads with \"deployment=prod\" so that security policy can be applied to production workloads.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Type: []string{"integer"}, - Format: "int32", + Default: "", + Type: []string{"string"}, + Format: "", }, }, }, }, }, - "unallocated": { + }, + Required: []string{"ObjectMetadata"}, + }, + }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"}, + } +} + +func schema_libcalico_go_lib_apis_v1_HostEndpointSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HostEndpointSpec contains the specification for a HostEndpoint resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "interfaceName": { SchemaProps: spec.SchemaProps{ - Description: "Unallocated is an ordered list of allocations which are free in the block.", + Description: "The name of the linux interface to apply policy to; for example \"eth0\". If \"InterfaceName\" is not present then at least one expected IP must be specified.", + Type: []string{"string"}, + Format: "", + }, + }, + "expectedIPs": { + SchemaProps: spec.SchemaProps{ + Description: "The expected IP addresses (IPv4 and IPv6) of the endpoint. If \"InterfaceName\" is not present, Calico will look for an interface matching any of the IPs in the list and apply policy to that.\n\nNote:\n\tWhen using the selector|tag match criteria in an ingress or egress security Policy\n\tor Profile, Calico converts the selector into a set of IP addresses. For host\n\tendpoints, the ExpectedIPs field is used for that purpose. (If only the interface\n\tname is specified, Calico does not learn the IPs of the interface for use in match\n\tcriteria.)", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: 0, - Type: []string{"integer"}, - Format: "int32", + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IP"), }, }, }, }, }, - "attributes": { + "profiles": { SchemaProps: spec.SchemaProps{ - Description: "Attributes is an array of arbitrary metadata associated with allocations in the block. To find attributes for a given allocation, use the value of the allocation's entry in the Allocations array as the index of the element in this array.", + Description: "A list of identifiers of security Profile objects that apply to this endpoint. Each profile is applied in the order that they appear in this list. Profile rules are applied after the selector-based security policy.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v3.AllocationAttribute"), + Default: "", + Type: []string{"string"}, + Format: "", }, }, }, }, }, - "sequenceNumber": { - SchemaProps: spec.SchemaProps{ - Description: "We store a sequence number that is updated each time the block is written. Each allocation will also store the sequence number of the block at the time of its creation. When releasing an IP, passing the sequence number associated with the allocation allows us to protect against a race condition and ensure the IP hasn't been released and re-allocated since the release request.", - Default: 0, - Type: []string{"integer"}, - Format: "int64", - }, - }, - "sequenceNumberForAllocation": { + "ports": { SchemaProps: spec.SchemaProps{ - Description: "Map of allocated ordinal within the block to sequence number of the block at the time of allocation. Kubernetes does not allow numerical keys for maps, so the key is cast to a string.", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, + Description: "Ports contains the endpoint's named ports, which may be referenced in security policy rules.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: 0, - Type: []string{"integer"}, - Format: "int64", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.EndpointPort"), }, }, }, }, }, - "deleted": { - SchemaProps: spec.SchemaProps{ - Description: "Deleted is an internal boolean used to workaround a limitation in the Kubernetes API whereby deletion will not return a conflict error if the block has been updated. It should not be set manually.", - Default: false, - Type: []string{"boolean"}, - Format: "", - }, - }, - "strictAffinity": { - SchemaProps: spec.SchemaProps{ - Description: "StrictAffinity on the IPAMBlock is deprecated and no longer used by the code. Use IPAMConfig StrictAffinity instead.", - Default: false, - Type: []string{"boolean"}, - Format: "", - }, - }, }, - Required: []string{"cidr", "allocations", "unallocated", "attributes", "strictAffinity"}, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.AllocationAttribute"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.EndpointPort", "github.com/projectcalico/calico/libcalico-go/lib/net.IP"}, } } -func schema_libcalico_go_lib_apis_v3_IPAMConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_ICMPFields(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "IPAMConfig contains information about a block for IP address assignment.", + Description: "ICMPFields defines structure for ICMP and NotICMP sub-struct for ICMP code and type", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { + "type": { SchemaProps: spec.SchemaProps{ - Description: "Standard object's metadata.", - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + Description: "Match on a specific ICMP type. For example a value of 8 refers to ICMP Echo Request (i.e. pings).", + Type: []string{"integer"}, + Format: "int32", }, }, - "spec": { + "code": { SchemaProps: spec.SchemaProps{ - Description: "Specification of the IPAMConfig.", - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v3.IPAMConfigSpec"), + Description: "Match on a specific ICMP code. If specified, the Type value must also be specified. This is a technical limitation imposed by the kernel's iptables firewall, which Calico uses to enforce the rule.", + Type: []string{"integer"}, + Format: "int32", }, }, }, }, }, - Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.IPAMConfigSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } -func schema_libcalico_go_lib_apis_v3_IPAMConfigList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_IPIPConfiguration(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "IPAMConfigList contains a list of IPAMConfig resources.", - Type: []string{"object"}, + Type: []string{"object"}, Properties: map[string]spec.Schema{ - "kind": { + "enabled": { SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, + Description: "When enabled is true, ipip tunneling will be used to deliver packets to destinations within this pool.", + Type: []string{"boolean"}, Format: "", }, }, - "apiVersion": { + "mode": { SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Description: "The IPIP mode. This can be one of \"always\" or \"cross-subnet\". A mode of \"always\" will also use IPIP tunneling for routing to destination IP addresses within this pool. A mode of \"cross-subnet\" will only use IPIP tunneling when the destination node is on a different subnet to the originating node. The default value (if not specified) is \"always\".", Type: []string{"string"}, Format: "", }, }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), - }, - }, - "items": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v3.IPAMConfig"), - }, - }, - }, - }, - }, }, - Required: []string{"metadata", "items"}, }, }, - Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.IPAMConfig", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, } } -func schema_libcalico_go_lib_apis_v3_IPAMConfigSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_IPNAT(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "IPAMConfigSpec contains the specification for an IPAMConfig resource.", + Description: "IPNat contains a single NAT mapping for a WorkloadEndpoint resource.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "strictAffinity": { - SchemaProps: spec.SchemaProps{ - Default: false, - Type: []string{"boolean"}, - Format: "", - }, - }, - "autoAllocateBlocks": { + "internalIP": { SchemaProps: spec.SchemaProps{ - Default: false, - Type: []string{"boolean"}, - Format: "", + Description: "The internal IP address which must be associated with the owning endpoint via the configured IPNetworks for the endpoint.", + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IP"), }, }, - "maxBlocksPerHost": { + "externalIP": { SchemaProps: spec.SchemaProps{ - Description: "MaxBlocksPerHost, if non-zero, is the max number of blocks that can be affine to each host.", - Type: []string{"integer"}, - Format: "int32", + Description: "The external IP address.", + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IP"), }, }, }, - Required: []string{"strictAffinity", "autoAllocateBlocks"}, + Required: []string{"internalIP", "externalIP"}, }, }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/net.IP"}, } } -func schema_libcalico_go_lib_apis_v3_IPAMHandle(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_IPPool(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "IPAMHandle contains information about an IPAMHandle resource.", + Description: "IPPool contains the details of a Calico IP pool resource. A pool resource is used by Calico in two ways:\n - to provide a set of IP addresses from which Calico IPAM assigns addresses\n for workloads.\n - to provide configuration specific to IP address range, such as configuration\n for the BGP daemon (e.g. when to use a GRE tunnel to encapsulate packets\n between compute hosts).", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { + "TypeMetadata": { SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), }, }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object's metadata.", - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPPoolMetadata"), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Specification of the IPAMHandle.", - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v3.IPAMHandleSpec"), + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPPoolSpec"), }, }, }, + Required: []string{"TypeMetadata", "metadata", "spec"}, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.IPAMHandleSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPPoolMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPPoolSpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, } } -func schema_libcalico_go_lib_apis_v3_IPAMHandleList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_IPPoolList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "IPAMHandleList contains a list of IPAMHandle resources.", + Description: "IPPoolList contains a list of IP pool resources. List types are returned from List() enumerations in the client interface.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { + "TypeMetadata": { SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), }, }, "metadata": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata"), }, }, "items": { @@ -2658,178 +2569,191 @@ func schema_libcalico_go_lib_apis_v3_IPAMHandleList(ref common.ReferenceCallback Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v3.IPAMHandle"), + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPPool"), }, }, }, }, }, }, - Required: []string{"metadata", "items"}, + Required: []string{"TypeMetadata", "metadata", "items"}, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.IPAMHandle", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPPool", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, } } -func schema_libcalico_go_lib_apis_v3_IPAMHandleSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_IPPoolMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "IPAMHandleSpec contains the specification for an IPAMHandle resource.", + Description: "IPPoolMetadata contains the metadata for an IP pool resource.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "handleID": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "block": { + "ObjectMetadata": { SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: 0, - Type: []string{"integer"}, - Format: "int32", - }, - }, - }, + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"), }, }, - "deleted": { + "cidr": { SchemaProps: spec.SchemaProps{ - Default: false, - Type: []string{"boolean"}, - Format: "", + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IPNet"), }, }, }, - Required: []string{"handleID", "block"}, + Required: []string{"ObjectMetadata", "cidr"}, }, }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata", "github.com/projectcalico/calico/libcalico-go/lib/net.IPNet"}, } } -func schema_libcalico_go_lib_apis_v3_IPNAT(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_IPPoolSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "IPNat contains a single NAT mapping for a WorkloadEndpoint resource.", + Description: "IPPoolSpec contains the specification for an IP pool resource.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "internalIP": { + "ipip": { SchemaProps: spec.SchemaProps{ - Description: "The internal IP address which must be associated with the owning endpoint via the configured IPNetworks for the endpoint.", - Default: "", - Type: []string{"string"}, + Description: "Contains configuration for ipip tunneling for this pool. If not specified, then ipip tunneling is disabled for this pool.", + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPIPConfiguration"), + }, + }, + "nat-outgoing": { + SchemaProps: spec.SchemaProps{ + Description: "When nat-outgoing is true, packets sent from Calico networked containers in this pool to destinations outside of this pool will be masqueraded.", + Type: []string{"boolean"}, Format: "", }, }, - "externalIP": { + "disabled": { SchemaProps: spec.SchemaProps{ - Description: "The external IP address.", - Default: "", - Type: []string{"string"}, + Description: "When disabled is true, Calico IPAM will not assign addresses from this pool.", + Type: []string{"boolean"}, Format: "", }, }, }, - Required: []string{"internalIP", "externalIP"}, }, }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPIPConfiguration"}, } } -func schema_libcalico_go_lib_apis_v3_Node(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_KubeConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "Node contains information about a Node resource.", - Type: []string{"object"}, + Type: []string{"object"}, Properties: map[string]spec.Schema{ - "kind": { + "kubeconfig": { SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", + Default: "", + Type: []string{"string"}, + Format: "", }, }, - "apiVersion": { + "k8sAPIEndpoint": { SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", + Default: "", + Type: []string{"string"}, + Format: "", }, }, - "metadata": { + "k8sKeyFile": { SchemaProps: spec.SchemaProps{ - Description: "Standard object's metadata.", - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + Default: "", + Type: []string{"string"}, + Format: "", }, }, - "spec": { + "k8sCertFile": { SchemaProps: spec.SchemaProps{ - Description: "Specification of the Node.", - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v3.NodeSpec"), + Default: "", + Type: []string{"string"}, + Format: "", }, }, - "status": { + "k8sCAFile": { SchemaProps: spec.SchemaProps{ - Description: "Status of the Node.", - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v3.NodeStatus"), + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "k8sAPIToken": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "k8sInsecureSkipTLSVerify": { + SchemaProps: spec.SchemaProps{ + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + "k8sDisableNodePoll": { + SchemaProps: spec.SchemaProps{ + Default: false, + Type: []string{"boolean"}, + Format: "", }, }, }, + Required: []string{"kubeconfig", "k8sAPIEndpoint", "k8sKeyFile", "k8sCertFile", "k8sCAFile", "k8sAPIToken", "k8sInsecureSkipTLSVerify", "k8sDisableNodePoll"}, }, }, - Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.NodeSpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.NodeStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } -func schema_libcalico_go_lib_apis_v3_NodeAddress(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_Node(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "NodeAddress represents an address assigned to a node.", + Description: "Node contains the details of a node resource which contains the configuration for a Calico node instance running on a compute host.\n\nIn addition to creating a Node resource through calicoctl or the Calico API, the Calico node instance must also be running on the specific host and should be provided the same Name as that configured on the Node resource. Note that, by default, the Calico node instance uses the hostname of the compute host when it is not explicitly specified - in this case, the equivalent Node resource should be created using the same hostname as the Name of the Node resource.\n\nOperations on the Node resources is expected to be required when adding a new host into a Calico network, and when removing a host from a Calico network, and occasionally to modify certain configuration. Care should be taken when operating on Node resources: deleting a Node resource will remove all Node specific data.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "address": { + "TypeMetadata": { SchemaProps: spec.SchemaProps{ - Description: "Address is a string representation of the actual address.", - Default: "", - Type: []string{"string"}, - Format: "", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), }, }, - "type": { + "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Type is the node IP type", - Type: []string{"string"}, - Format: "", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.NodeMetadata"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.NodeSpec"), }, }, }, - Required: []string{"address"}, + Required: []string{"TypeMetadata", "metadata", "spec"}, }, }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.NodeMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.NodeSpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, } } -func schema_libcalico_go_lib_apis_v3_NodeBGPSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_NodeBGPSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "NodeBGPSpec contains the specification for the Node BGP configuration.", + Description: "NodeSpec contains the specification for a Calico Node resource.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "asNumber": { @@ -2841,239 +2765,352 @@ func schema_libcalico_go_lib_apis_v3_NodeBGPSpec(ref common.ReferenceCallback) c }, "ipv4Address": { SchemaProps: spec.SchemaProps{ - Description: "IPv4Address is the IPv4 address and network of this node. The IPv4 address should always be specified if you are using BGP.", - Type: []string{"string"}, - Format: "", + Description: "IPv4Address is the IPv4 address and network of this node. At least one of the IPv4 and IPv6 addresses should be specified.", + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IPNet"), }, }, "ipv6Address": { SchemaProps: spec.SchemaProps{ - Description: "IPv6Address is the IPv6 address and network of this node. Not required if you are not using BGP or you do not require IPv6 routing.", - Type: []string{"string"}, - Format: "", - }, - }, - "ipv4IPIPTunnelAddr": { - SchemaProps: spec.SchemaProps{ - Description: "IPv4IPIPTunnelAddr is the IPv4 address of the IP in IP tunnel.", - Type: []string{"string"}, - Format: "", - }, - }, - "routeReflectorClusterID": { - SchemaProps: spec.SchemaProps{ - Description: "RouteReflectorClusterID enables this node as a route reflector within the given cluster.", - Type: []string{"string"}, - Format: "", + Description: "IPv6Address is the IPv6 address and network of this node. At least one of the IPv4 and IPv6 addresses should be specified.", + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IPNet"), }, }, }, }, }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/net.IPNet"}, } } -func schema_libcalico_go_lib_apis_v3_NodeInterface(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_NodeList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, + Description: "A NodeList contains a list of Node resources. List types are returned from List() enumerations on the client interface.", + Type: []string{"object"}, Properties: map[string]spec.Schema{ - "name": { + "TypeMetadata": { SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), }, }, - "addresses": { + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata"), + }, + }, + "items": { SchemaProps: spec.SchemaProps{ Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Node"), }, }, }, }, }, }, + Required: []string{"TypeMetadata", "metadata", "items"}, }, }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Node", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, } } -func schema_libcalico_go_lib_apis_v3_NodeList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_NodeMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "NodeList contains a list of Node resources.", + Description: "NodeMetadata contains the metadata for a Calico Node resource.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "kind": { + "ObjectMetadata": { SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"), }, }, - "apiVersion": { + "name": { SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Description: "The name of the node.", Type: []string{"string"}, Format: "", }, }, - "metadata": { + }, + Required: []string{"ObjectMetadata"}, + }, + }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"}, + } +} + +func schema_libcalico_go_lib_apis_v1_NodeSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NodeSpec contains the specification for a Calico Node resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "bgp": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + Description: "BGP configuration for this node. If this omitted, the Calico node will be run in policy-only mode.", + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.NodeBGPSpec"), }, }, - "items": { + "orchRefs": { SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, + Description: "OrchRefs for this node.", + Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v3.Node"), + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.OrchRef"), }, }, }, }, }, }, - Required: []string{"metadata", "items"}, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.Node", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.NodeBGPSpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.OrchRef"}, } } -func schema_libcalico_go_lib_apis_v3_NodeSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_OrchRef(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "NodeSpec contains the specification for a Node resource.", + Description: "OrchRef is used to correlate a Calico node to its corresponding representation in a given orchestrator", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "bgp": { + "nodeName": { SchemaProps: spec.SchemaProps{ - Description: "BGP configuration for this node.", - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v3.NodeBGPSpec"), + Description: "NodeName represents the name for this node according to the orchestrator.", + Type: []string{"string"}, + Format: "", }, }, - "ipv4VXLANTunnelAddr": { + "orchestrator": { SchemaProps: spec.SchemaProps{ - Description: "IPv4VXLANTunnelAddr is the IPv4 address of the VXLAN tunnel.", + Description: "Orchestrator represents the orchestrator using this node.", + Default: "", Type: []string{"string"}, Format: "", }, }, - "vxlanTunnelMACAddr": { + }, + Required: []string{"orchestrator"}, + }, + }, + } +} + +func schema_libcalico_go_lib_apis_v1_Policy(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Policy contains information about a tiered security Policy resource. This contains a set of security rules to apply. Security policies allow a selector-based security model which can override the security profiles directly referenced by an endpoint.\n\nEach policy must do one of the following:\n\n - Match the packet and apply a “next-tier” action; this skips the rest of the tier, deferring\n to the next tier (or the explicit profiles if this is the last tier.\n - Match the packet and apply an “allow” action; this immediately accepts the packet, skipping\n all further tiers and profiles. This is not recommended in general, because it prevents\n further policy from being executed.\n - Match the packet and apply a \"deny\" action; this drops the packet immediately, skipping all\n further tiers and profiles.\n - Fail to match the packet; in which case the packet proceeds to the next policy in the tier.\n If there are no more policies in the tier then the packet is dropped.\n\nNote:\n\n\tIf no policies in a tier match an endpoint then the packet skips the tier completely. The\n\t“default deny” behavior described above only applies if some of the policies in a tier match\n\tthe endpoint.\n\nCalico implements the security policy for each endpoint individually and only the policies that have matching selectors are implemented. This ensures that the number of rules that actually need to be inserted into the kernel is proportional to the number of local endpoints rather than the total amount of policy. If no policies in a tier match a given endpoint then that tier is skipped.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "TypeMetadata": { SchemaProps: spec.SchemaProps{ - Description: "VXLANTunnelMACAddr is the MAC address of the VXLAN tunnel.", - Type: []string{"string"}, - Format: "", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), }, }, - "ipv6VXLANTunnelAddr": { + "metadata": { SchemaProps: spec.SchemaProps{ - Description: "IPv6VXLANTunnelAddr is the address of the IPv6 VXLAN tunnel.", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.PolicyMetadata"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.PolicySpec"), + }, + }, + }, + Required: []string{"TypeMetadata", "metadata", "spec"}, + }, + }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.PolicyMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.PolicySpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, + } +} + +func schema_libcalico_go_lib_apis_v1_PolicyList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PolicyList contains a list of selector-based security Policy resources. List types are returned from List() enumerations on the client interface.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "TypeMetadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Policy"), + }, + }, + }, + }, + }, + }, + Required: []string{"TypeMetadata", "metadata", "items"}, + }, + }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Policy", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, + } +} + +func schema_libcalico_go_lib_apis_v1_PolicyMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PolicyMetadata contains the metadata for a selector-based security Policy resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "ObjectMetadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"), + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "The name of the selector-based security policy.", Type: []string{"string"}, Format: "", }, }, - "vxlanTunnelMACAddrV6": { + "tier": { SchemaProps: spec.SchemaProps{ - Description: "VXLANTunnelMACAddrV6 is the MAC address of the IPv6 VXLAN tunnel.", + Description: "The name of the tier that this policy belongs to. If this is omitted, the default tier (name is \"default\") is assumed. The specified tier must exist in order to create security policies within the tier, the \"default\" tier is created automatically if it does not exist, this means for deployments requiring only a single Tier, the tier name may be omitted on all policy management requests.", Type: []string{"string"}, Format: "", }, }, - "orchRefs": { + "annotations": { SchemaProps: spec.SchemaProps{ - Description: "OrchRefs for this node.", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ + Description: "Arbitrary key-value information to be used by clients.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v3.OrchRef"), + Default: "", + Type: []string{"string"}, + Format: "", }, }, }, }, }, - "wireguard": { + }, + Required: []string{"ObjectMetadata"}, + }, + }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"}, + } +} + +func schema_libcalico_go_lib_apis_v1_PolicySpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PolicySpec contains the specification for a selector-based security Policy resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "order": { SchemaProps: spec.SchemaProps{ - Description: "Wireguard configuration for this node.", - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v3.NodeWireguardSpec"), + Description: "Order is an optional field that specifies the order in which the policy is applied within a given tier. Policies with higher \"order\" are applied after those with lower order. If the order is omitted, it may be considered to be \"infinite\" - i.e. the policy will be applied last. Policies with identical order and within the same Tier will be applied in alphanumerical order based on the Policy \"Name\".", + Type: []string{"number"}, + Format: "double", }, }, - "addresses": { + "ingress": { SchemaProps: spec.SchemaProps{ - Description: "Addresses list address that a client can reach the node at.", + Description: "The ordered set of ingress rules. Each rule contains a set of packet match criteria and a corresponding action to apply.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v3.NodeAddress"), + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Rule"), }, }, }, }, }, - "interfaces": { + "egress": { SchemaProps: spec.SchemaProps{ - Description: "Interfaces is a list of interfaces on the node.", + Description: "The ordered set of egress rules. Each rule contains a set of packet match criteria and a corresponding action to apply.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v3.NodeInterface"), + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Rule"), }, }, }, }, }, - }, - }, - }, - Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.NodeAddress", "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.NodeBGPSpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.NodeInterface", "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.NodeWireguardSpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.OrchRef"}, - } -} - -func schema_libcalico_go_lib_apis_v3_NodeStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "wireguardPublicKey": { + "selector": { SchemaProps: spec.SchemaProps{ - Description: "WireguardPublicKey is the IPv4 Wireguard public-key for this node. wireguardPublicKey validates if the string is a valid base64 encoded key.", + Description: "The selector is an expression used to pick out the endpoints that the policy should be applied to.\n\nSelector expressions follow this syntax:\n\n\tlabel == \"string_literal\" -> comparison, e.g. my_label == \"foo bar\"\n\tlabel != \"string_literal\" -> not equal; also matches if label is not present\n\tlabel in { \"a\", \"b\", \"c\", ... } -> true if the value of label X is one of \"a\", \"b\", \"c\"\n\tlabel not in { \"a\", \"b\", \"c\", ... } -> true if the value of label X is not one of \"a\", \"b\", \"c\"\n\thas(label_name) -> True if that label is present\n\t! expr -> negation of expr\n\texpr && expr -> Short-circuit and\n\texpr || expr -> Short-circuit or\n\t( expr ) -> parens for grouping\n\tall() or the empty selector -> matches all endpoints.\n\nLabel names are allowed to contain alphanumerics, -, _ and /. String literals are more permissive but they do not support escape characters.\n\nExamples (with made-up labels):\n\n\ttype == \"webserver\" && deployment == \"prod\"\n\ttype in {\"frontend\", \"backend\"}\n\tdeployment != \"dev\"\n\t! has(label_name)", + Default: "", Type: []string{"string"}, Format: "", }, }, - "wireguardPublicKeyV6": { + "doNotTrack": { SchemaProps: spec.SchemaProps{ - Description: "WireguardPublicKeyV6 is the IPv6 Wireguard public-key for this node. wireguardPublicKey validates if the string is a valid base64 encoded key.", - Type: []string{"string"}, + Description: "DoNotTrack indicates whether packets matched by the rules in this policy should go through the data plane's connection tracking, such as Linux conntrack. If True, the rules in this policy are applied before any data plane connection tracking, and packets allowed by this policy are marked as not to be tracked.", + Type: []string{"boolean"}, Format: "", }, }, - "podCIDRs": { + "preDNAT": { SchemaProps: spec.SchemaProps{ - Description: "PodCIDR is a reflection of the Kubernetes node's spec.PodCIDRs field.", + Description: "PreDNAT indicates to apply the rules in this policy before any DNAT.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "types": { + SchemaProps: spec.SchemaProps{ + Description: "Types indicates whether this policy applies to ingress, or to egress, or to both. When not explicitly specified (and so the value on creation is empty or nil), Calico defaults Types according to what IngressRules and EgressRules are present in the policy. The default is:\n\n- [ PolicyTypeIngress ], if there are no EgressRules (including the case where there are\n also no IngressRules)\n\n- [ PolicyTypeEgress ], if there are EgressRules but no IngressRules\n\n- [ PolicyTypeIngress, PolicyTypeEgress ], if there are both IngressRules and EgressRules.\n\nWhen the policy is read back again, Types will always be one of these values, never empty or nil.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -3087,253 +3124,311 @@ func schema_libcalico_go_lib_apis_v3_NodeStatus(ref common.ReferenceCallback) co }, }, }, + Required: []string{"selector"}, }, }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Rule"}, } } -func schema_libcalico_go_lib_apis_v3_NodeWireguardSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_Profile(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "NodeWireguardSpec contains the specification for the Node wireguard configuration.", + Description: "Profile contains the details a security profile resource. A profile is set of security rules to apply on an endpoint. An endpoint (either a host endpoint or an endpoint on a workload) can reference zero or more profiles. The profile rules are applied directly to the endpoint *after* the selector-based security policy has been applied, and in the order the profiles are declared on the endpoint.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "interfaceIPv4Address": { + "TypeMetadata": { SchemaProps: spec.SchemaProps{ - Description: "InterfaceIPv4Address is the IP address for the IPv4 Wireguard interface.", - Type: []string{"string"}, - Format: "", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), }, }, - "interfaceIPv6Address": { + "metadata": { SchemaProps: spec.SchemaProps{ - Description: "InterfaceIPv6Address is the IP address for the IPv6 Wireguard interface.", - Type: []string{"string"}, - Format: "", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.ProfileMetadata"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.ProfileSpec"), }, }, }, + Required: []string{"TypeMetadata", "metadata", "spec"}, }, }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.ProfileMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.ProfileSpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, } } -func schema_libcalico_go_lib_apis_v3_OrchRef(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_ProfileList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "OrchRef is used to correlate a Calico node to its corresponding representation in a given orchestrator", + Description: "A ProfileList contains a list of security Profile resources. List types are returned from List() enumerations on the client interface.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "nodeName": { + "TypeMetadata": { SchemaProps: spec.SchemaProps{ - Description: "NodeName represents the name for this node according to the orchestrator.", - Type: []string{"string"}, - Format: "", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), }, }, - "orchestrator": { + "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Orchestrator represents the orchestrator using this node.", - Default: "", - Type: []string{"string"}, - Format: "", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Profile"), + }, + }, + }, }, }, }, - Required: []string{"orchestrator"}, + Required: []string{"TypeMetadata", "metadata", "items"}, }, }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Profile", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, } } -func schema_libcalico_go_lib_apis_v3_QoSControls(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_ProfileMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "QoSControls contains QoS limits configuration.", + Description: "ProfileMetadata contains the metadata for a security Profile resource.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "ingressBandwidth": { - SchemaProps: spec.SchemaProps{ - Description: "Ingress bandwidth controls. These are only applied if IngressBandwidth != 0. In that case:\n\n- IngressBandwidth must be between 1000 (1k) and 10^15 (1P).\n\n- IngressBurst must be between 1000 (1k) and 34359738360 (32Gi - 8). If specified as 0,\n it is defaulted to 4294967296 (4Gi).\n\n- IngressPeakrate may be 0 if it is acceptable for bursts to be transmitted at line rate.\n In that case IngressMinburst should also be 0. But if IngressPeakrate != 0:\n\n - IngressPeakrate must be between 1010 (1.01k) and 1.01 x 10^15 (1.01P).\n\n - IngressMinburst must either be 0 - in which case Calico will use the MTU of the\n relevant workload interface as the minimum burst size - or be between 1000 (1k) and\n 10^8 (100M).\n\nIngress bandwidth rate limit in bits per second", - Type: []string{"integer"}, - Format: "int64", - }, - }, - "ingressBurst": { + "ObjectMetadata": { SchemaProps: spec.SchemaProps{ - Description: "Ingress bandwidth burst size in bits", - Type: []string{"integer"}, - Format: "int64", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"), }, }, - "ingressPeakrate": { + "name": { SchemaProps: spec.SchemaProps{ - Description: "Ingress bandwidth peakrate limit in bits per second", - Type: []string{"integer"}, - Format: "int64", + Description: "The name of the endpoint.", + Type: []string{"string"}, + Format: "", }, }, - "ingressMinburst": { + "tags": { SchemaProps: spec.SchemaProps{ - Description: "Ingress bandwidth minburst size in bytes (not bits because it is typically the MTU)", - Type: []string{"integer"}, - Format: "int64", + Description: "A list of tags that are applied to each endpoint that references this profile.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, }, }, - "egressBandwidth": { + "labels": { SchemaProps: spec.SchemaProps{ - Description: "Egress bandwidth controls. These are only applied if EgressBandwidth != 0. The same detail applies here as for ingress bandwidth, except using the corresponding Egress fields.\n\nEgress bandwidth rate limit in bits per second", - Type: []string{"integer"}, - Format: "int64", + Description: "The labels to apply to each endpoint that references this profile. It is expected that many endpoints share the same labels. For example, they could be used to label all \"production\" workloads with \"deployment=prod\" so that security policy can be applied to production workloads.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, }, }, - "egressBurst": { + }, + Required: []string{"ObjectMetadata"}, + }, + }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"}, + } +} + +func schema_libcalico_go_lib_apis_v1_ProfileSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ProfileSpec contains the specification for a security Profile resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "ingress": { SchemaProps: spec.SchemaProps{ - Description: "Egress bandwidth burst size in bits", - Type: []string{"integer"}, - Format: "int64", + Description: "The ordered set of ingress rules. Each rule contains a set of packet match criteria and a corresponding action to apply.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Rule"), + }, + }, + }, }, }, - "egressPeakrate": { + "egress": { SchemaProps: spec.SchemaProps{ - Description: "Egress bandwidth peakrate limit in bits per second", - Type: []string{"integer"}, - Format: "int64", + Description: "The ordered set of egress rules. Each rule contains a set of packet match criteria and a corresponding action to apply.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Rule"), + }, + }, + }, }, }, - "egressMinburst": { + }, + }, + }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Rule"}, + } +} + +func schema_libcalico_go_lib_apis_v1_Rule(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "A Rule encapsulates a set of match criteria and an action. Both selector-based security Policy and security Profiles reference rules - separated out as a list of rules for both ingress and egress packet matching.\n\nEach positive match criteria has a negated version, prefixed with \"Not\". All the match criteria within a rule must be satisfied for a packet to match. A single rule can contain the positive and negative version of a match and both must be satisfied for the rule to match.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "action": { SchemaProps: spec.SchemaProps{ - Description: "Egress bandwidth minburst size in bytes (not bits because it is typically the MTU)", - Type: []string{"integer"}, - Format: "int64", + Default: "", + Type: []string{"string"}, + Format: "", }, }, - "ingressPacketRate": { + "ipVersion": { SchemaProps: spec.SchemaProps{ - Description: "Ingress packet rate limit in packets per second. Only applied if non-zero. When non-zero:\n\n- IngressPacketRate must be between 1 and 10^4 (10k).\n\n- IngressPacketBurst must be between 1 and 10^4 (10k). If specified as 0, it is\n defaulted to 5.", + Description: "IPVersion is an optional field that restricts the rule to only match a specific IP version.", Type: []string{"integer"}, - Format: "int64", + Format: "int32", }, }, - "ingressPacketBurst": { + "protocol": { SchemaProps: spec.SchemaProps{ - Description: "Ingress packet rate burst size in number of packets", - Type: []string{"integer"}, - Format: "int64", + Description: "Protocol is an optional field that restricts the rule to only apply to traffic of a specific IP protocol. Required if any of the EntityRules contain Ports (because ports only apply to certain protocols).\n\nMust be one of these string values: \"tcp\", \"udp\", \"icmp\", \"icmpv6\", \"sctp\", \"udplite\" or an integer in the range 1-255.", + Ref: ref("github.com/projectcalico/api/pkg/lib/numorstring.Protocol"), }, }, - "egressPacketRate": { + "icmp": { SchemaProps: spec.SchemaProps{ - Description: "Egress packet rate limit in packets per second. Only applied if non-zero. The same detail applies here as for egress packet rate, except using the corresponding Egress fields.", - Type: []string{"integer"}, - Format: "int64", + Description: "ICMP is an optional field that restricts the rule to apply to a specific type and code of ICMP traffic. This should only be specified if the Protocol field is set to \"icmp\" or \"icmpv6\".", + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.ICMPFields"), }, }, - "egressPacketBurst": { + "notProtocol": { SchemaProps: spec.SchemaProps{ - Description: "Egress packet rate burst size in number of packets", - Type: []string{"integer"}, - Format: "int64", + Description: "NotProtocol is the negated version of the Protocol field.", + Ref: ref("github.com/projectcalico/api/pkg/lib/numorstring.Protocol"), }, }, - "ingressMaxConnections": { + "notICMP": { SchemaProps: spec.SchemaProps{ - Description: "Ingress maximum number of connections (absolute number of connections, no unit). Only applied if non-zero. When non-zero, must be between 1 and 4294967295.", - Type: []string{"integer"}, - Format: "int64", + Description: "NotICMP is the negated version of the ICMP field.", + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.ICMPFields"), }, }, - "egressMaxConnections": { + "source": { SchemaProps: spec.SchemaProps{ - Description: "Egress maximum number of connections (absolute number of connections, no unit). Only applied if non-zero. When non-zero, must be between 1 and 4294967295.", - Type: []string{"integer"}, - Format: "int64", + Description: "Source contains the match criteria that apply to source entity.", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.EntityRule"), }, }, - "dscp": { + "destination": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/projectcalico/api/pkg/lib/numorstring.DSCP"), + Description: "Destination contains the match criteria that apply to destination entity.", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.EntityRule"), }, }, }, + Required: []string{"action", "source", "destination"}, }, }, Dependencies: []string{ - "github.com/projectcalico/api/pkg/lib/numorstring.DSCP"}, + "github.com/projectcalico/api/pkg/lib/numorstring.Protocol", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.EntityRule", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.ICMPFields"}, } } -func schema_libcalico_go_lib_apis_v3_WorkloadEndpoint(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_Tier(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "WorkloadEndpoint contains information about a WorkloadEndpoint resource that is a peer of a Calico compute node.", + Description: "Tier contains the details of a security policy tier resource. A tier contains a set of policies that are applied to packets. Multiple tiers may be created and each tier is applied in the order specified in the tier specification.\n\nSee Policy for more information.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { + "TypeMetadata": { SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), }, }, "metadata": { SchemaProps: spec.SchemaProps{ - Description: "Standard object's metadata.", - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.TierMetadata"), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Description: "Specification of the WorkloadEndpoint.", - Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v3.WorkloadEndpointSpec"), + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.TierSpec"), }, }, }, + Required: []string{"TypeMetadata", "metadata", "spec"}, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.WorkloadEndpointSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.TierMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.TierSpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, } } -func schema_libcalico_go_lib_apis_v3_WorkloadEndpointList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_TierList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "WorkloadEndpointList contains a list of WorkloadEndpoint resources.", + Description: "A TierList contains a list of tier resources. List types are returned from List() enumerations in the client interface.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { + "TypeMetadata": { SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), }, }, "metadata": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata"), }, }, "items": { @@ -3343,131 +3438,197 @@ func schema_libcalico_go_lib_apis_v3_WorkloadEndpointList(ref common.ReferenceCa Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v3.WorkloadEndpoint"), + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Tier"), }, }, }, }, }, }, - Required: []string{"metadata", "items"}, + Required: []string{"TypeMetadata", "metadata", "items"}, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.WorkloadEndpoint", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.Tier", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, } } -func schema_libcalico_go_lib_apis_v3_WorkloadEndpointPort(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_TierMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "WorkloadEndpointPort represents one endpoint's named or mapped port", + Description: "TierMetadata contains the metadata for a security policy Tier.", Type: []string{"object"}, Properties: map[string]spec.Schema{ + "ObjectMetadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"), + }, + }, "name": { SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", + Type: []string{"string"}, + Format: "", }, }, - "protocol": { + }, + Required: []string{"ObjectMetadata"}, + }, + }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"}, + } +} + +func schema_libcalico_go_lib_apis_v1_TierSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TierSpec contains the specification for a security policy Tier.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "order": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/projectcalico/api/pkg/lib/numorstring.Protocol"), + Description: "Order is an optional field that specifies the order in which the tier is applied. Tiers with higher \"order\" are applied after those with lower order. If the order is omitted, it may be considered to be \"infinite\" - i.e. the tier will be applied last. Tiers with identical order will be applied in alphanumerical order based on the Tier \"Name\".", + Type: []string{"number"}, + Format: "double", }, }, - "port": { + }, + }, + }, + } +} + +func schema_libcalico_go_lib_apis_v1_WorkloadEndpoint(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "TypeMetadata": { SchemaProps: spec.SchemaProps{ - Default: 0, - Type: []string{"integer"}, - Format: "int32", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), }, }, - "hostPort": { + "metadata": { SchemaProps: spec.SchemaProps{ - Default: 0, - Type: []string{"integer"}, - Format: "int32", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.WorkloadEndpointMetadata"), }, }, - "hostIP": { + "spec": { SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.WorkloadEndpointSpec"), }, }, }, - Required: []string{"name", "protocol", "port", "hostPort", "hostIP"}, + Required: []string{"TypeMetadata", "metadata", "spec"}, }, }, Dependencies: []string{ - "github.com/projectcalico/api/pkg/lib/numorstring.Protocol"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.WorkloadEndpointMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.WorkloadEndpointSpec", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, } } -func schema_libcalico_go_lib_apis_v3_WorkloadEndpointSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_libcalico_go_lib_apis_v1_WorkloadEndpointList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "WorkloadEndpointMetadata contains the specification for a WorkloadEndpoint resource.", + Description: "WorkloadEndpointList contains a list of Workload Endpoint resources. List types are returned from List() enumerations in the client interface.", Type: []string{"object"}, Properties: map[string]spec.Schema{ - "orchestrator": { + "TypeMetadata": { SchemaProps: spec.SchemaProps{ - Description: "The name of the orchestrator.", - Type: []string{"string"}, - Format: "", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"), }, }, - "workload": { + "metadata": { SchemaProps: spec.SchemaProps{ - Description: "The name of the workload.", - Type: []string{"string"}, - Format: "", + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata"), }, }, - "node": { + "items": { SchemaProps: spec.SchemaProps{ - Description: "The node name identifying the Calico node instance.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.WorkloadEndpoint"), + }, + }, + }, + }, + }, + }, + Required: []string{"TypeMetadata", "metadata", "items"}, + }, + }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.WorkloadEndpoint", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ListMetadata", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.TypeMetadata"}, + } +} + +func schema_libcalico_go_lib_apis_v1_WorkloadEndpointMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "WorkloadEndpointMetadata contains the Metadata for a WorkloadEndpoint resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "ObjectMetadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"), + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "The name of the endpoint. This may be omitted on a create, in which case an endpoint ID will be automatically created, and the endpoint ID will be included in the response.", Type: []string{"string"}, Format: "", }, }, - "containerID": { + "workload": { SchemaProps: spec.SchemaProps{ - Description: "The container ID.", + Description: "The name of the workload.", Type: []string{"string"}, Format: "", }, }, - "pod": { + "orchestrator": { SchemaProps: spec.SchemaProps{ - Description: "The Pod name.", + Description: "The name of the orchestrator.", Type: []string{"string"}, Format: "", }, }, - "endpoint": { + "node": { SchemaProps: spec.SchemaProps{ - Description: "The Endpoint name.", + Description: "The node name identifying the Calico node instance.", Type: []string{"string"}, Format: "", }, }, - "serviceAccountName": { + "activeInstanceID": { SchemaProps: spec.SchemaProps{ - Description: "ServiceAccountName, if specified, is the name of the k8s ServiceAccount for this pod.", + Description: "ActiveInstanceID is an optional field that orchestrators may use to store additional information about the endpoint. The primary use case is to store a unique identifier for the active instance of a container. For example, with Calico CNI, a re-spawned container may use the same endpoint indexing (Node, Orchestrator, Workload, Endpoint) for the new container as for the old - the ActiveInstanceID is used to store an additional unique ID which the CNI plugin uses to determine whether the DEL operation needs to delete the Calico WorkloadEndpoint. This field is not an index field of the WorkloadEndpoint resource.", Type: []string{"string"}, Format: "", }, }, - "ipNetworks": { + "labels": { SchemaProps: spec.SchemaProps{ - Description: "IPNetworks is a list of subnets allocated to this endpoint. IP packets will only be allowed to leave this interface if they come from an address in one of these subnets. Currently only /32 for IPv4 and /128 for IPv6 networks are supported.", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ + Description: "The labels applied to the workload endpoint. It is expected that many endpoints share the same labels. For example, they could be used to label all \"production\" workloads with \"deployment=prod\" so that security policy can be applied to production workloads.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: "", @@ -3478,6 +3639,35 @@ func schema_libcalico_go_lib_apis_v3_WorkloadEndpointSpec(ref common.ReferenceCa }, }, }, + }, + Required: []string{"ObjectMetadata"}, + }, + }, + Dependencies: []string{ + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned.ObjectMetadata"}, + } +} + +func schema_libcalico_go_lib_apis_v1_WorkloadEndpointSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "WorkloadEndpointMetadata contains the specification for a WorkloadEndpoint resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "ipNetworks": { + SchemaProps: spec.SchemaProps{ + Description: "IPNetworks is a list of subnets allocated to this endpoint. IP packets will only be allowed to leave this interface if they come from an address in one of these subnets.\n\nCurrently only /32 for IPv4 and /128 for IPv6 networks are supported.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IPNet"), + }, + }, + }, + }, + }, "ipNATs": { SchemaProps: spec.SchemaProps{ Description: "IPNATs is a list of 1:1 NAT mappings to apply to the endpoint. Inbound connections to the external IP will be forwarded to the internal IP. Connections initiated from the internal IP will not have their source address changed, except when an endpoint attempts to connect one of its own external IPs. Each internal IP must be associated with the same endpoint via the configured IPNetworks.", @@ -3486,7 +3676,7 @@ func schema_libcalico_go_lib_apis_v3_WorkloadEndpointSpec(ref common.ReferenceCa Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v3.IPNAT"), + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPNAT"), }, }, }, @@ -3495,15 +3685,13 @@ func schema_libcalico_go_lib_apis_v3_WorkloadEndpointSpec(ref common.ReferenceCa "ipv4Gateway": { SchemaProps: spec.SchemaProps{ Description: "IPv4Gateway is the gateway IPv4 address for traffic from the workload.", - Type: []string{"string"}, - Format: "", + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IP"), }, }, "ipv6Gateway": { SchemaProps: spec.SchemaProps{ Description: "IPv6Gateway is the gateway IPv6 address for traffic from the workload.", - Type: []string{"string"}, - Format: "", + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IP"), }, }, "profiles": { @@ -3531,8 +3719,7 @@ func schema_libcalico_go_lib_apis_v3_WorkloadEndpointSpec(ref common.ReferenceCa "mac": { SchemaProps: spec.SchemaProps{ Description: "MAC is the MAC address of the endpoint interface.", - Type: []string{"string"}, - Format: "", + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.MAC"), }, }, "ports": { @@ -3543,36 +3730,29 @@ func schema_libcalico_go_lib_apis_v3_WorkloadEndpointSpec(ref common.ReferenceCa Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v3.WorkloadEndpointPort"), + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v1.EndpointPort"), }, }, }, }, }, - "allowSpoofedSourcePrefixes": { + "allow_spoofed_source_prefixes": { SchemaProps: spec.SchemaProps{ - Description: "AllowSpoofedSourcePrefixes is a list of CIDRs that the endpoint should be able to send traffic from, bypassing the RPF check.", + Description: "AllowSpoofedSourcePrefixes is a list of CIDRs this workload endpoint is allowed to send traffic from, i.e. this allows the workload endpoint to spoof its IP address using addresses in these prefixes", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", + Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/net.IPNet"), }, }, }, }, }, - "qosControls": { - SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/projectcalico/calico/libcalico-go/lib/apis/v3.QoSControls"), - }, - }, }, }, }, Dependencies: []string{ - "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.IPNAT", "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.QoSControls", "github.com/projectcalico/calico/libcalico-go/lib/apis/v3.WorkloadEndpointPort"}, + "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.EndpointPort", "github.com/projectcalico/calico/libcalico-go/lib/apis/v1.IPNAT", "github.com/projectcalico/calico/libcalico-go/lib/net.IP", "github.com/projectcalico/calico/libcalico-go/lib/net.IPNet", "github.com/projectcalico/calico/libcalico-go/lib/net.MAC"}, } } diff --git a/libcalico-go/lib/apis/v3/ipam_block.go b/libcalico-go/lib/apis/internalapi/ipam_block.go similarity index 86% rename from libcalico-go/lib/apis/v3/ipam_block.go rename to libcalico-go/lib/apis/internalapi/ipam_block.go index a3f41c06d0c..d8141a2a3d4 100644 --- a/libcalico-go/lib/apis/v3/ipam_block.go +++ b/libcalico-go/lib/apis/internalapi/ipam_block.go @@ -1,4 +1,4 @@ -// Copyright (c) 2019 Tigera, Inc. All rights reserved. +// Copyright (c) 2019-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package v3 +package internalapi import ( apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -43,7 +43,8 @@ type IPAMBlockSpec struct { // Affinity of the block, if this block has one. If set, it will be of the form // "host:". If not set, this block is not affine to a host. - Affinity *string `json:"affinity,omitempty"` + Affinity *string `json:"affinity,omitempty"` + AffinityClaimTime *metav1.Time `json:"affinityClaimTime,omitempty"` // Array of allocations in-use within this block. nil entries mean the allocation is free. // For non-nil entries at index i, the index is the ordinal of the allocation within this block @@ -84,8 +85,13 @@ type IPAMBlockSpec struct { } type AllocationAttribute struct { - AttrPrimary *string `json:"handle_id,omitempty"` - AttrSecondary map[string]string `json:"secondary,omitempty"` + // HandleID is the primary identifier for the allocation. + HandleID *string `json:"handle_id,omitempty"` + // ActiveOwnerAttrs contains attributes of the active owner (the pod currently using the IP). + ActiveOwnerAttrs map[string]string `json:"secondary,omitempty"` + // AlternateOwnerAttrs contains attributes of the previous or potential owner + // (used during live migration to track the source or target pod). + AlternateOwnerAttrs map[string]string `json:"alternate,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/libcalico-go/lib/apis/v3/ipam_config.go b/libcalico-go/lib/apis/internalapi/ipam_config.go similarity index 57% rename from libcalico-go/lib/apis/v3/ipam_config.go rename to libcalico-go/lib/apis/internalapi/ipam_config.go index 4114ca9ef43..e528c637d9a 100644 --- a/libcalico-go/lib/apis/v3/ipam_config.go +++ b/libcalico-go/lib/apis/internalapi/ipam_config.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package v3 +package internalapi import ( apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -20,9 +20,21 @@ import ( ) const ( - KindIPAMConfig = "IPAMConfig" - KindIPAMConfigList = "IPAMConfigList" - GlobalIPAMConfigName = "default" + // For historical reasons, crd.projectcalico.org/v1 internal API uses IPAMConfig as the kind for the backing CRD, as well + // as for the clientv3 API, whereas the projectcalico.org/v3 API uses IPAMConfiguration. + KindIPAMConfig = "IPAMConfig" + KindIPAMConfigList = "IPAMConfigList" +) + +// VMAddressPersistence controls whether KubeVirt VirtualMachine workloads +// maintain persistent IP addresses across VM lifecycle events. +type VMAddressPersistence string + +const ( + // VMAddressPersistenceEnabled enables IP persistence for KubeVirt VMs. + VMAddressPersistenceEnabled VMAddressPersistence = "Enabled" + // VMAddressPersistenceDisabled disables IP persistence for KubeVirt VMs. + VMAddressPersistenceDisabled VMAddressPersistence = "Disabled" ) // +genclient @@ -50,6 +62,20 @@ type IPAMConfigSpec struct { // +kubebuilder:validation:Maximum:=2147483647 // +optional MaxBlocksPerHost int `json:"maxBlocksPerHost,omitempty"` + + // KubeVirtVMAddressPersistence controls whether KubeVirt VirtualMachine workloads + // maintain persistent IP addresses across VM lifecycle events. + // When set to VMAddressPersistenceEnabled, Calico automatically ensures that KubeVirt VMs retain their + // IP addresses when their underlying pods are recreated during VM operations such as + // reboot, live migration, or pod eviction. IP persistency is ensured when the + // VirtualMachineInstance (VMI) resource is deleted and recreated by the VM controller. + // When set to VMAddressPersistenceDisabled, VMs receive new IP addresses whenever their pods are recreated, + // following standard pod IP allocation behavior. Live migration target pods are not allowed + // when this is set to VMAddressPersistenceDisabled and will result in an error. + // If nil, defaults to VMAddressPersistenceEnabled (IP persistence enabled if not specified). + // +kubebuilder:validation:Enum=Enabled;Disabled + // +optional + KubeVirtVMAddressPersistence *VMAddressPersistence `json:"kubeVirtVMAddressPersistence,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/libcalico-go/lib/apis/v3/ipam_handle.go b/libcalico-go/lib/apis/internalapi/ipam_handle.go similarity index 99% rename from libcalico-go/lib/apis/v3/ipam_handle.go rename to libcalico-go/lib/apis/internalapi/ipam_handle.go index 915abc08a75..f121f6a9eec 100644 --- a/libcalico-go/lib/apis/v3/ipam_handle.go +++ b/libcalico-go/lib/apis/internalapi/ipam_handle.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package v3 +package internalapi import ( apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" diff --git a/libcalico-go/lib/apis/internalapi/livemigration.go b/libcalico-go/lib/apis/internalapi/livemigration.go new file mode 100644 index 00000000000..2db7cdfb5a1 --- /dev/null +++ b/libcalico-go/lib/apis/internalapi/livemigration.go @@ -0,0 +1,91 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// 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 internalapi + +import ( + apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +const ( + KindLiveMigration = "LiveMigration" + KindLiveMigrationList = "LiveMigrationList" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// LiveMigration describes a live-migration operation in progress. It is +// orchestrator-independent and holds the fields that Calico needs for +// optimal live migration processing. +type LiveMigration struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + Spec LiveMigrationSpec `json:"spec"` +} + +// LiveMigrationSpec contains the specification for a LiveMigration resource. +type LiveMigrationSpec struct { + // Source identifies the WorkloadEndpoint that this live migration operation is moving from. + Source *types.NamespacedName + + // Destination identifies the WorkloadEndpoint that this live migration operation is moving + // to. + Destination *WorkloadEndpointIdentifier +} + +// +kubebuilder:validation:ExactlyOneOf +type WorkloadEndpointIdentifier struct { + // NamespacedName is used when the WorkloadEndpoint can be identified directly by its + // name and namespace. + // +optional + NamespacedName *types.NamespacedName + + // Selector is used when the WorkloadEndpoint must be identified by a selector expression. + // +optional + Selector *string +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// LiveMigrationList contains a list of LiveMigration resources. +type LiveMigrationList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + Items []LiveMigration `json:"items"` +} + +// NewLiveMigration creates a new (zeroed) LiveMigration struct with the +// TypeMetadata initialised to the current version. +func NewLiveMigration() *LiveMigration { + return &LiveMigration{ + TypeMeta: metav1.TypeMeta{ + Kind: KindLiveMigration, + APIVersion: apiv3.GroupVersionCurrent, + }, + } +} + +// NewLiveMigrationList creates a new (zeroed) LiveMigrationList struct with the +// TypeMetadata initialised to the current version. +func NewLiveMigrationList() *LiveMigrationList { + return &LiveMigrationList{ + TypeMeta: metav1.TypeMeta{ + Kind: KindLiveMigrationList, + APIVersion: apiv3.GroupVersionCurrent, + }, + } +} diff --git a/libcalico-go/lib/apis/v3/node.go b/libcalico-go/lib/apis/internalapi/node.go similarity index 99% rename from libcalico-go/lib/apis/v3/node.go rename to libcalico-go/lib/apis/internalapi/node.go index d553f34c79e..b1b7828776b 100644 --- a/libcalico-go/lib/apis/v3/node.go +++ b/libcalico-go/lib/apis/internalapi/node.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package v3 +package internalapi import ( apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" diff --git a/libcalico-go/lib/apis/v3/v3_suite_test.go b/libcalico-go/lib/apis/internalapi/v3_suite_test.go similarity index 68% rename from libcalico-go/lib/apis/v3/v3_suite_test.go rename to libcalico-go/lib/apis/internalapi/v3_suite_test.go index fe89ca356e6..cfeb0b16950 100644 --- a/libcalico-go/lib/apis/v3/v3_suite_test.go +++ b/libcalico-go/lib/apis/internalapi/v3_suite_test.go @@ -12,18 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -package v3_test +package internalapi_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" ) func TestV2(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../report/v3_api_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "v3 API Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../report/v3_api_suite.xml" + ginkgo.RunSpecs(t, "v3 API Suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/apis/v3/workloadendpoint.go b/libcalico-go/lib/apis/internalapi/workloadendpoint.go similarity index 99% rename from libcalico-go/lib/apis/v3/workloadendpoint.go rename to libcalico-go/lib/apis/internalapi/workloadendpoint.go index 5608d7440b2..de6f02f8268 100644 --- a/libcalico-go/lib/apis/v3/workloadendpoint.go +++ b/libcalico-go/lib/apis/internalapi/workloadendpoint.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package v3 +package internalapi import ( apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" diff --git a/libcalico-go/lib/apis/v3/zz_generated.deepcopy.go b/libcalico-go/lib/apis/internalapi/zz_generated.deepcopy.go similarity index 84% rename from libcalico-go/lib/apis/v3/zz_generated.deepcopy.go rename to libcalico-go/lib/apis/internalapi/zz_generated.deepcopy.go index 7e2257ccf5a..dafe36b8be6 100644 --- a/libcalico-go/lib/apis/v3/zz_generated.deepcopy.go +++ b/libcalico-go/lib/apis/internalapi/zz_generated.deepcopy.go @@ -17,23 +17,31 @@ // Code generated by deepcopy-gen. DO NOT EDIT. -package v3 +package internalapi import ( numorstring "github.com/projectcalico/api/pkg/lib/numorstring" runtime "k8s.io/apimachinery/pkg/runtime" + types "k8s.io/apimachinery/pkg/types" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AllocationAttribute) DeepCopyInto(out *AllocationAttribute) { *out = *in - if in.AttrPrimary != nil { - in, out := &in.AttrPrimary, &out.AttrPrimary + if in.HandleID != nil { + in, out := &in.HandleID, &out.HandleID *out = new(string) **out = **in } - if in.AttrSecondary != nil { - in, out := &in.AttrSecondary, &out.AttrSecondary + if in.ActiveOwnerAttrs != nil { + in, out := &in.ActiveOwnerAttrs, &out.ActiveOwnerAttrs + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.AlternateOwnerAttrs != nil { + in, out := &in.AlternateOwnerAttrs, &out.AlternateOwnerAttrs *out = make(map[string]string, len(*in)) for key, val := range *in { (*out)[key] = val @@ -196,6 +204,10 @@ func (in *IPAMBlockSpec) DeepCopyInto(out *IPAMBlockSpec) { *out = new(string) **out = **in } + if in.AffinityClaimTime != nil { + in, out := &in.AffinityClaimTime, &out.AffinityClaimTime + *out = (*in).DeepCopy() + } if in.Allocations != nil { in, out := &in.Allocations, &out.Allocations *out = make([]*int, len(*in)) @@ -244,7 +256,7 @@ func (in *IPAMConfig) DeepCopyInto(out *IPAMConfig) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) return } @@ -302,6 +314,11 @@ func (in *IPAMConfigList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IPAMConfigSpec) DeepCopyInto(out *IPAMConfigSpec) { *out = *in + if in.KubeVirtVMAddressPersistence != nil { + in, out := &in.KubeVirtVMAddressPersistence, &out.KubeVirtVMAddressPersistence + *out = new(VMAddressPersistence) + **out = **in + } return } @@ -414,6 +431,92 @@ func (in *IPNAT) DeepCopy() *IPNAT { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LiveMigration) DeepCopyInto(out *LiveMigration) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LiveMigration. +func (in *LiveMigration) DeepCopy() *LiveMigration { + if in == nil { + return nil + } + out := new(LiveMigration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LiveMigration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LiveMigrationList) DeepCopyInto(out *LiveMigrationList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]LiveMigration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LiveMigrationList. +func (in *LiveMigrationList) DeepCopy() *LiveMigrationList { + if in == nil { + return nil + } + out := new(LiveMigrationList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LiveMigrationList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LiveMigrationSpec) DeepCopyInto(out *LiveMigrationSpec) { + *out = *in + if in.Source != nil { + in, out := &in.Source, &out.Source + *out = new(types.NamespacedName) + **out = **in + } + if in.Destination != nil { + in, out := &in.Destination, &out.Destination + *out = new(WorkloadEndpointIdentifier) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LiveMigrationSpec. +func (in *LiveMigrationSpec) DeepCopy() *LiveMigrationSpec { + if in == nil { + return nil + } + out := new(LiveMigrationSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Node) DeepCopyInto(out *Node) { *out = *in @@ -677,6 +780,32 @@ func (in *WorkloadEndpoint) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkloadEndpointIdentifier) DeepCopyInto(out *WorkloadEndpointIdentifier) { + *out = *in + if in.NamespacedName != nil { + in, out := &in.NamespacedName, &out.NamespacedName + *out = new(types.NamespacedName) + **out = **in + } + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + *out = new(string) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadEndpointIdentifier. +func (in *WorkloadEndpointIdentifier) DeepCopy() *WorkloadEndpointIdentifier { + if in == nil { + return nil + } + out := new(WorkloadEndpointIdentifier) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WorkloadEndpointList) DeepCopyInto(out *WorkloadEndpointList) { *out = *in diff --git a/libcalico-go/lib/apis/v1/README.md b/libcalico-go/lib/apis/v1/README.md new file mode 100644 index 00000000000..52539d2c4b7 --- /dev/null +++ b/libcalico-go/lib/apis/v1/README.md @@ -0,0 +1,9 @@ +# libcalico-go/lib/apis/v1 + +Legacy v1 API type definitions for the original Calico northbound client API. + +Defines resource structs (BGPPeer, HostEndpoint, Policy, IPPool, Profile, Node, WorkloadEndpoint, Tier) +and the `CalicoAPIConfig` for datastore connection configuration. Used by the v1 validator +(`libcalico-go/lib/validator/v1`), the etcd backend, calicoctl's resource loader, and test utilities. + +This is not the current API. The public Calico API is defined in `api/pkg/apis/projectcalico/v3/`. diff --git a/libcalico-go/lib/apis/v1/apiconfig.go b/libcalico-go/lib/apis/v1/apiconfig.go index c61c4b7a6f3..1394b661824 100644 --- a/libcalico-go/lib/apis/v1/apiconfig.go +++ b/libcalico-go/lib/apis/v1/apiconfig.go @@ -26,8 +26,8 @@ const ( // CalicoAPIConfig contains the connection information for a Calico CalicoAPIConfig resource type CalicoAPIConfig struct { unversioned.TypeMetadata - Metadata CalicoAPIConfigMetadata `json:"metadata,omitempty"` - Spec CalicoAPIConfigSpec `json:"spec,omitempty"` + Metadata CalicoAPIConfigMetadata `json:"metadata"` + Spec CalicoAPIConfigSpec `json:"spec"` } // CalicoAPIConfigMetadata contains the metadata for a Calico CalicoAPIConfig resource. diff --git a/libcalico-go/lib/apis/v1/bgppeer.go b/libcalico-go/lib/apis/v1/bgppeer.go index 41fcbb08e3c..802ffeb62d3 100644 --- a/libcalico-go/lib/apis/v1/bgppeer.go +++ b/libcalico-go/lib/apis/v1/bgppeer.go @@ -30,10 +30,10 @@ type BGPPeer struct { unversioned.TypeMetadata // Metadata for a BGPPeer. - Metadata BGPPeerMetadata `json:"metadata,omitempty"` + Metadata BGPPeerMetadata `json:"metadata"` // Specification for a BGPPeer. - Spec BGPPeerSpec `json:"spec,omitempty"` + Spec BGPPeerSpec `json:"spec"` } func (t BGPPeer) GetResourceMetadata() unversioned.ResourceMetadata { @@ -88,7 +88,7 @@ func NewBGPPeer() *BGPPeer { // enumerations in the client interface. type BGPPeerList struct { unversioned.TypeMetadata - Metadata unversioned.ListMetadata `json:"metadata,omitempty"` + Metadata unversioned.ListMetadata `json:"metadata"` Items []BGPPeer `json:"items" validate:"dive"` } diff --git a/libcalico-go/lib/apis/v1/generated.openapi.go b/libcalico-go/lib/apis/v1/generated.openapi.go index 48eda72518f..0c4d5f9468d 100644 --- a/libcalico-go/lib/apis/v1/generated.openapi.go +++ b/libcalico-go/lib/apis/v1/generated.openapi.go @@ -102,7 +102,7 @@ func schema_libcalico_go_lib_apis_v1_BGPPeer(ref common.ReferenceCallback) commo }, }, }, - Required: []string{"TypeMetadata"}, + Required: []string{"TypeMetadata", "metadata", "spec"}, }, }, Dependencies: []string{ @@ -143,7 +143,7 @@ func schema_libcalico_go_lib_apis_v1_BGPPeerList(ref common.ReferenceCallback) c }, }, }, - Required: []string{"TypeMetadata", "items"}, + Required: []string{"TypeMetadata", "metadata", "items"}, }, }, Dependencies: []string{ @@ -242,7 +242,7 @@ func schema_libcalico_go_lib_apis_v1_CalicoAPIConfig(ref common.ReferenceCallbac }, }, }, - Required: []string{"TypeMetadata"}, + Required: []string{"TypeMetadata", "metadata", "spec"}, }, }, Dependencies: []string{ @@ -552,7 +552,7 @@ func schema_libcalico_go_lib_apis_v1_HostEndpoint(ref common.ReferenceCallback) }, }, }, - Required: []string{"TypeMetadata"}, + Required: []string{"TypeMetadata", "metadata", "spec"}, }, }, Dependencies: []string{ @@ -593,7 +593,7 @@ func schema_libcalico_go_lib_apis_v1_HostEndpointList(ref common.ReferenceCallba }, }, }, - Required: []string{"TypeMetadata", "items"}, + Required: []string{"TypeMetadata", "metadata", "items"}, }, }, Dependencies: []string{ @@ -824,7 +824,7 @@ func schema_libcalico_go_lib_apis_v1_IPPool(ref common.ReferenceCallback) common }, }, }, - Required: []string{"TypeMetadata"}, + Required: []string{"TypeMetadata", "metadata", "spec"}, }, }, Dependencies: []string{ @@ -865,7 +865,7 @@ func schema_libcalico_go_lib_apis_v1_IPPoolList(ref common.ReferenceCallback) co }, }, }, - Required: []string{"TypeMetadata", "items"}, + Required: []string{"TypeMetadata", "metadata", "items"}, }, }, Dependencies: []string{ @@ -1030,7 +1030,7 @@ func schema_libcalico_go_lib_apis_v1_Node(ref common.ReferenceCallback) common.O }, }, }, - Required: []string{"TypeMetadata"}, + Required: []string{"TypeMetadata", "metadata", "spec"}, }, }, Dependencies: []string{ @@ -1105,7 +1105,7 @@ func schema_libcalico_go_lib_apis_v1_NodeList(ref common.ReferenceCallback) comm }, }, }, - Required: []string{"TypeMetadata", "items"}, + Required: []string{"TypeMetadata", "metadata", "items"}, }, }, Dependencies: []string{ @@ -1232,7 +1232,7 @@ func schema_libcalico_go_lib_apis_v1_Policy(ref common.ReferenceCallback) common }, }, }, - Required: []string{"TypeMetadata"}, + Required: []string{"TypeMetadata", "metadata", "spec"}, }, }, Dependencies: []string{ @@ -1273,7 +1273,7 @@ func schema_libcalico_go_lib_apis_v1_PolicyList(ref common.ReferenceCallback) co }, }, }, - Required: []string{"TypeMetadata", "items"}, + Required: []string{"TypeMetadata", "metadata", "items"}, }, }, Dependencies: []string{ @@ -1447,7 +1447,7 @@ func schema_libcalico_go_lib_apis_v1_Profile(ref common.ReferenceCallback) commo }, }, }, - Required: []string{"TypeMetadata"}, + Required: []string{"TypeMetadata", "metadata", "spec"}, }, }, Dependencies: []string{ @@ -1488,7 +1488,7 @@ func schema_libcalico_go_lib_apis_v1_ProfileList(ref common.ReferenceCallback) c }, }, }, - Required: []string{"TypeMetadata", "items"}, + Required: []string{"TypeMetadata", "metadata", "items"}, }, }, Dependencies: []string{ @@ -1659,7 +1659,7 @@ func schema_libcalico_go_lib_apis_v1_Rule(ref common.ReferenceCallback) common.O }, }, }, - Required: []string{"action"}, + Required: []string{"action", "source", "destination"}, }, }, Dependencies: []string{ @@ -1693,7 +1693,7 @@ func schema_libcalico_go_lib_apis_v1_Tier(ref common.ReferenceCallback) common.O }, }, }, - Required: []string{"TypeMetadata"}, + Required: []string{"TypeMetadata", "metadata", "spec"}, }, }, Dependencies: []string{ @@ -1734,7 +1734,7 @@ func schema_libcalico_go_lib_apis_v1_TierList(ref common.ReferenceCallback) comm }, }, }, - Required: []string{"TypeMetadata", "items"}, + Required: []string{"TypeMetadata", "metadata", "items"}, }, }, Dependencies: []string{ @@ -1815,7 +1815,7 @@ func schema_libcalico_go_lib_apis_v1_WorkloadEndpoint(ref common.ReferenceCallba }, }, }, - Required: []string{"TypeMetadata"}, + Required: []string{"TypeMetadata", "metadata", "spec"}, }, }, Dependencies: []string{ @@ -1856,7 +1856,7 @@ func schema_libcalico_go_lib_apis_v1_WorkloadEndpointList(ref common.ReferenceCa }, }, }, - Required: []string{"TypeMetadata", "items"}, + Required: []string{"TypeMetadata", "metadata", "items"}, }, }, Dependencies: []string{ diff --git a/libcalico-go/lib/apis/v1/hostendpoint.go b/libcalico-go/lib/apis/v1/hostendpoint.go index 9cb020d7016..c07709798b1 100644 --- a/libcalico-go/lib/apis/v1/hostendpoint.go +++ b/libcalico-go/lib/apis/v1/hostendpoint.go @@ -25,8 +25,8 @@ import ( // running Calico's agent, Felix. By default, Calico doesn't apply any policy to such interfaces. type HostEndpoint struct { unversioned.TypeMetadata - Metadata HostEndpointMetadata `json:"metadata,omitempty"` - Spec HostEndpointSpec `json:"spec,omitempty"` + Metadata HostEndpointMetadata `json:"metadata"` + Spec HostEndpointSpec `json:"spec"` } func (t HostEndpoint) GetResourceMetadata() unversioned.ResourceMetadata { @@ -97,7 +97,7 @@ func NewHostEndpoint() *HostEndpoint { // enumerations in the client interface. type HostEndpointList struct { unversioned.TypeMetadata - Metadata unversioned.ListMetadata `json:"metadata,omitempty"` + Metadata unversioned.ListMetadata `json:"metadata"` Items []HostEndpoint `json:"items" validate:"dive"` } diff --git a/libcalico-go/lib/apis/v1/ippool.go b/libcalico-go/lib/apis/v1/ippool.go index 7365bac4547..605e819b6d1 100644 --- a/libcalico-go/lib/apis/v1/ippool.go +++ b/libcalico-go/lib/apis/v1/ippool.go @@ -31,8 +31,8 @@ import ( // between compute hosts). type IPPool struct { unversioned.TypeMetadata - Metadata IPPoolMetadata `json:"metadata,omitempty"` - Spec IPPoolSpec `json:"spec,omitempty"` + Metadata IPPoolMetadata `json:"metadata"` + Spec IPPoolSpec `json:"spec"` } func (t IPPool) GetResourceMetadata() unversioned.ResourceMetadata { @@ -93,7 +93,7 @@ func NewIPPool() *IPPool { // enumerations in the client interface. type IPPoolList struct { unversioned.TypeMetadata - Metadata unversioned.ListMetadata `json:"metadata,omitempty"` + Metadata unversioned.ListMetadata `json:"metadata"` Items []IPPool `json:"items" validate:"dive"` } diff --git a/libcalico-go/lib/apis/v1/node.go b/libcalico-go/lib/apis/v1/node.go index 8bba38559e4..0be21e5eb4c 100644 --- a/libcalico-go/lib/apis/v1/node.go +++ b/libcalico-go/lib/apis/v1/node.go @@ -39,8 +39,8 @@ import ( // on Node resources: deleting a Node resource will remove all Node specific data. type Node struct { unversioned.TypeMetadata - Metadata NodeMetadata `json:"metadata,omitempty"` - Spec NodeSpec `json:"spec,omitempty"` + Metadata NodeMetadata `json:"metadata"` + Spec NodeSpec `json:"spec"` } func (t Node) GetResourceMetadata() unversioned.ResourceMetadata { @@ -109,7 +109,7 @@ func NewNode() *Node { // enumerations on the client interface. type NodeList struct { unversioned.TypeMetadata - Metadata unversioned.ListMetadata `json:"metadata,omitempty"` + Metadata unversioned.ListMetadata `json:"metadata"` Items []Node `json:"items" validate:"dive,omitempty"` } diff --git a/libcalico-go/lib/apis/v1/policy.go b/libcalico-go/lib/apis/v1/policy.go index c8a4cd40bcd..938bfec8eed 100644 --- a/libcalico-go/lib/apis/v1/policy.go +++ b/libcalico-go/lib/apis/v1/policy.go @@ -48,8 +48,8 @@ import ( // total amount of policy. If no policies in a tier match a given endpoint then that tier is skipped. type Policy struct { unversioned.TypeMetadata - Metadata PolicyMetadata `json:"metadata,omitempty"` - Spec PolicySpec `json:"spec,omitempty"` + Metadata PolicyMetadata `json:"metadata"` + Spec PolicySpec `json:"spec"` } func (t Policy) GetResourceMetadata() unversioned.ResourceMetadata { @@ -173,7 +173,7 @@ func NewPolicy() *Policy { // enumerations on the client interface. type PolicyList struct { unversioned.TypeMetadata - Metadata unversioned.ListMetadata `json:"metadata,omitempty"` + Metadata unversioned.ListMetadata `json:"metadata"` Items []Policy `json:"items" validate:"dive"` } diff --git a/libcalico-go/lib/apis/v1/profile.go b/libcalico-go/lib/apis/v1/profile.go index 45a6995076f..2147a560a4c 100644 --- a/libcalico-go/lib/apis/v1/profile.go +++ b/libcalico-go/lib/apis/v1/profile.go @@ -27,8 +27,8 @@ import ( // endpoint. type Profile struct { unversioned.TypeMetadata - Metadata ProfileMetadata `json:"metadata,omitempty"` - Spec ProfileSpec `json:"spec,omitempty"` + Metadata ProfileMetadata `json:"metadata"` + Spec ProfileSpec `json:"spec"` } func (t Profile) GetResourceMetadata() unversioned.ResourceMetadata { @@ -84,7 +84,7 @@ func NewProfile() *Profile { // enumerations on the client interface. type ProfileList struct { unversioned.TypeMetadata - Metadata unversioned.ListMetadata `json:"metadata,omitempty"` + Metadata unversioned.ListMetadata `json:"metadata"` Items []Profile `json:"items" validate:"dive,omitempty"` } diff --git a/libcalico-go/lib/apis/v1/rule.go b/libcalico-go/lib/apis/v1/rule.go index 6ae9471f06f..c1258af7058 100644 --- a/libcalico-go/lib/apis/v1/rule.go +++ b/libcalico-go/lib/apis/v1/rule.go @@ -54,10 +54,10 @@ type Rule struct { NotICMP *ICMPFields `json:"notICMP,omitempty" validate:"omitempty"` // Source contains the match criteria that apply to source entity. - Source EntityRule `json:"source,omitempty" validate:"omitempty"` + Source EntityRule `json:"source" validate:"omitempty"` // Destination contains the match criteria that apply to destination entity. - Destination EntityRule `json:"destination,omitempty" validate:"omitempty"` + Destination EntityRule `json:"destination" validate:"omitempty"` } // ICMPFields defines structure for ICMP and NotICMP sub-struct for ICMP code and type diff --git a/libcalico-go/lib/apis/v1/tier.go b/libcalico-go/lib/apis/v1/tier.go index e24411137e9..d59e5b2580e 100644 --- a/libcalico-go/lib/apis/v1/tier.go +++ b/libcalico-go/lib/apis/v1/tier.go @@ -27,8 +27,8 @@ import ( // See Policy for more information. type Tier struct { unversioned.TypeMetadata - Metadata TierMetadata `json:"metadata,omitempty"` - Spec TierSpec `json:"spec,omitempty"` + Metadata TierMetadata `json:"metadata"` + Spec TierSpec `json:"spec"` } func (t Tier) GetResourceMetadata() unversioned.ResourceMetadata { @@ -72,7 +72,7 @@ func NewTier() *Tier { // enumerations in the client interface. type TierList struct { unversioned.TypeMetadata - Metadata unversioned.ListMetadata `json:"metadata,omitempty"` + Metadata unversioned.ListMetadata `json:"metadata"` Items []Tier `json:"items" validate:"dive"` } diff --git a/libcalico-go/lib/apis/v1/workloadendpoint.go b/libcalico-go/lib/apis/v1/workloadendpoint.go index c9c19ab7cc5..f058f33b6ae 100644 --- a/libcalico-go/lib/apis/v1/workloadendpoint.go +++ b/libcalico-go/lib/apis/v1/workloadendpoint.go @@ -25,8 +25,8 @@ import ( type WorkloadEndpoint struct { unversioned.TypeMetadata - Metadata WorkloadEndpointMetadata `json:"metadata,omitempty"` - Spec WorkloadEndpointSpec `json:"spec,omitempty"` + Metadata WorkloadEndpointMetadata `json:"metadata"` + Spec WorkloadEndpointSpec `json:"spec"` } func (t WorkloadEndpoint) GetResourceMetadata() unversioned.ResourceMetadata { @@ -151,7 +151,7 @@ func NewWorkloadEndpoint() *WorkloadEndpoint { // from List() enumerations in the client interface. type WorkloadEndpointList struct { unversioned.TypeMetadata - Metadata unversioned.ListMetadata `json:"metadata,omitempty"` + Metadata unversioned.ListMetadata `json:"metadata"` Items []WorkloadEndpoint `json:"items" validate:"dive"` } diff --git a/libcalico-go/lib/apis/v3/register.go b/libcalico-go/lib/apis/v3/register.go deleted file mode 100644 index 9fd0223b3a8..00000000000 --- a/libcalico-go/lib/apis/v3/register.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) 2016-2025 Tigera, Inc. All rights reserved. - -// 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 v3 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -var SchemeGroupVersion = schema.GroupVersion{Group: "crd.projectcalico.org", Version: "v1"} - -var ( - SchemeBuilder runtime.SchemeBuilder - localSchemeBuilder = &SchemeBuilder - AddToScheme = localSchemeBuilder.AddToScheme - AllKnownTypes = []runtime.Object{ - &BlockAffinity{}, - &BlockAffinityList{}, - &IPAMBlock{}, - &IPAMBlockList{}, - &IPAMHandle{}, - &IPAMHandleList{}, - } -) - -func init() { - // We only register manually written functions here. The registration of the - // generated functions takes place in the generated files. The separation - // makes the code compile even when the generated files are missing. - localSchemeBuilder.Register(addKnownTypes) -} - -// Resource takes an unqualified resource and returns a Group qualified GroupResource -func Resource(resource string) schema.GroupResource { - return SchemeGroupVersion.WithResource(resource).GroupResource() -} - -// Adds the list of known types to api.Scheme. -func addKnownTypes(scheme *runtime.Scheme) error { - scheme.AddKnownTypes(SchemeGroupVersion, AllKnownTypes...) - metav1.AddToGroupVersion(scheme, SchemeGroupVersion) - return nil -} diff --git a/libcalico-go/lib/backend/backend_suite_test.go b/libcalico-go/lib/backend/backend_suite_test.go index c154d733866..af29dd57761 100644 --- a/libcalico-go/lib/backend/backend_suite_test.go +++ b/libcalico-go/lib/backend/backend_suite_test.go @@ -17,16 +17,16 @@ package backend_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestBackend(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/backend_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Backend Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/backend_suite.xml" + ginkgo.RunSpecs(t, "Backend Suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/backend/clean_e2e_test.go b/libcalico-go/lib/backend/clean_e2e_test.go index 53fe63e52ac..7a32d8a51d5 100644 --- a/libcalico-go/lib/backend/clean_e2e_test.go +++ b/libcalico-go/lib/backend/clean_e2e_test.go @@ -18,7 +18,7 @@ import ( "context" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/libcalico-go/lib/backend/encap/ipip.go b/libcalico-go/lib/backend/encap/ipip.go index bd45a94cc17..a257a806ded 100644 --- a/libcalico-go/lib/backend/encap/ipip.go +++ b/libcalico-go/lib/backend/encap/ipip.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016 Tigera, Inc. All rights reserved. +// Copyright (c) 2016-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ package encap type Mode string const ( - Undefined Mode = "" + Never Mode = "" Always = "always" CrossSubnet = "cross-subnet" ) diff --git a/libcalico-go/lib/backend/etcd/etcd_suite_test.go b/libcalico-go/lib/backend/etcd/etcd_suite_test.go index 45a8bc0b49a..a328cf0025d 100644 --- a/libcalico-go/lib/backend/etcd/etcd_suite_test.go +++ b/libcalico-go/lib/backend/etcd/etcd_suite_test.go @@ -17,16 +17,16 @@ package etcd_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestEtcd(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../report/etcd_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Etcd Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../report/etcd_suite.xml" + ginkgo.RunSpecs(t, "Etcd Suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/backend/etcd/syncer.go b/libcalico-go/lib/backend/etcd/syncer.go index 9a0c1433edb..7e01a29d7c2 100644 --- a/libcalico-go/lib/backend/etcd/syncer.go +++ b/libcalico-go/lib/backend/etcd/syncer.go @@ -134,11 +134,11 @@ func (syn *etcdSyncer) Start() { // Channel used to send updates from the watcher thread to the merge // thread. We give it a large buffer because we want the watcher thread // to be free running and only block if we get really backed up. - watcherUpdateC := make(chan interface{}, 20000) + watcherUpdateC := make(chan any, 20000) // Channel used to send updates from the snapshot thread to the merge // thread. No buffer: we want the snapshot to be slowed down if the // merge thread is under load. - snapshotUpdateC := make(chan interface{}) + snapshotUpdateC := make(chan any) // Channel used to signal from the merge thread to the snapshot thread // that a new snapshot is required. To avoid deadlock with the channel // above, the merge only sends a new snapshot request once the old @@ -168,7 +168,7 @@ func (sync *etcdSyncer) Stop() { // stale replica, the snapshot thread retries until it reads a snapshot that is // new enough. func (syn *etcdSyncer) readSnapshotsFromEtcd( - snapshotUpdateC chan<- interface{}, + snapshotUpdateC chan<- any, snapshotRequestC <-chan snapshotRequest, ) { log.Info("Syncer snapshot-reading thread started") @@ -226,7 +226,7 @@ func (syn *etcdSyncer) readSnapshotsFromEtcd( } // sendSnapshotNode sends the node and its children over the channel as events. -func sendSnapshotNode(node *client.Node, snapshotUpdates chan<- interface{}, resp *client.Response) { +func sendSnapshotNode(node *client.Node, snapshotUpdates chan<- any, resp *client.Response) { if !node.Dir { snapshotUpdates <- snapshotUpdate{ snapshotIndex: resp.Index, @@ -247,7 +247,7 @@ func sendSnapshotNode(node *client.Node, snapshotUpdates chan<- interface{}, res // comment for the etcdSyncer, the watcher goroutine is free-running; it always // tries to keep up with etcd but it emits events when it drops out of sync // so that the merge goroutine can trigger a new resync via snapshot. -func (syn *etcdSyncer) watchEtcd(watcherUpdateC chan<- interface{}) { +func (syn *etcdSyncer) watchEtcd(watcherUpdateC chan<- any) { log.Info("etcd watch thread started.") var timeOfLastError time.Time // Each trip around the outer loop establishes the current etcd index @@ -417,8 +417,8 @@ func (syn *etcdSyncer) pollClusterID(interval time.Duration) { // and waiting until it finishes (and hence the snapshot thread is no longer sending) // before sending it a request for a new snapshot. func (syn *etcdSyncer) mergeUpdates( - snapshotUpdateC <-chan interface{}, - watcherUpdateC <-chan interface{}, + snapshotUpdateC <-chan any, + watcherUpdateC <-chan any, snapshotRequestC chan<- snapshotRequest, ) { var minRequiredSnapshotIndex uint64 @@ -428,7 +428,7 @@ func (syn *etcdSyncer) mergeUpdates( syn.callbacks.OnStatusUpdated(api.WaitForDatastore) for { - var event interface{} + var event any select { case event = <-snapshotUpdateC: log.WithField("event", event).Debug("Snapshot update") @@ -536,7 +536,7 @@ func (syn *etcdSyncer) sendUpdate(key string, value string, revision uint64, upd } log.Debugf("Parsed etcd key: %v", parsedKey) - var parsedValue interface{} + var parsedValue any var err error parsedValue, err = model.ParseValue(parsedKey, []byte(value)) diff --git a/libcalico-go/lib/backend/etcdv3/conversion.go b/libcalico-go/lib/backend/etcdv3/conversion.go index 287043a71db..063547575d1 100644 --- a/libcalico-go/lib/backend/etcdv3/conversion.go +++ b/libcalico-go/lib/backend/etcdv3/conversion.go @@ -89,9 +89,7 @@ func convertWatchEvent(e *clientv3.Event, l model.ListInterface) (*api.WatchEven }, nil } -var ( - ErrMissingValue = fmt.Errorf("missing etcd KV") -) +var ErrMissingValue = fmt.Errorf("missing etcd KV") // etcdToKVPair converts an etcd KeyValue into model.KVPair. func etcdToKVPair(key model.Key, ekv *mvccpb.KeyValue) (*model.KVPair, error) { @@ -113,9 +111,13 @@ func etcdToKVPair(key model.Key, ekv *mvccpb.KeyValue) (*model.KVPair, error) { } } - return &model.KVPair{ + kvp := &model.KVPair{ Key: key, Value: v, Revision: strconv.FormatInt(ekv.ModRevision, 10), - }, nil + } + if err = prepForReturn(kvp); err != nil { + return nil, err + } + return kvp, nil } diff --git a/libcalico-go/lib/backend/etcdv3/etcdv3.go b/libcalico-go/lib/backend/etcdv3/etcdv3.go index 442a72812a5..c284bd04ad2 100644 --- a/libcalico-go/lib/backend/etcdv3/etcdv3.go +++ b/libcalico-go/lib/backend/etcdv3/etcdv3.go @@ -34,10 +34,10 @@ import ( calicotls "github.com/projectcalico/calico/crypto/pkg/tls" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" - "github.com/projectcalico/calico/libcalico-go/lib/names" "github.com/projectcalico/calico/libcalico-go/lib/resources" ) @@ -164,6 +164,10 @@ func (c *etcdV3Client) Create(ctx context.Context, d *model.KVPair) (*model.KVPa return nil, err } + if err = prepForWrite(d); err != nil { + return nil, err + } + key, value, err := getKeyValueStrings(d) if err != nil { return nil, err @@ -209,6 +213,9 @@ func (c *etcdV3Client) Create(ctx context.Context, d *model.KVPair) (*model.KVPa d.Value = v d.Revision = strconv.FormatInt(txnResp.Header.Revision, 10) + if err = prepForReturn(d); err != nil { + return nil, err + } return d, nil } @@ -226,6 +233,9 @@ func (c *etcdV3Client) Update(ctx context.Context, d *model.KVPair) (*model.KVPa if err != nil { return nil, err } + if err = prepForWrite(d); err != nil { + return nil, err + } key, value, err := getKeyValueStrings(d) if err != nil { @@ -253,7 +263,6 @@ func (c *etcdV3Client) Update(ctx context.Context, d *model.KVPair) (*model.KVPa ).Else( clientv3.OpGet(key), ).Commit() - if err != nil { logCxt.WithError(err).Warning("Update failed") return nil, cerrors.ErrorDatastoreError{Err: err} @@ -279,6 +288,9 @@ func (c *etcdV3Client) Update(ctx context.Context, d *model.KVPair) (*model.KVPa d.Value = v d.Revision = strconv.FormatInt(txnResp.Header.Revision, 10) + if err = prepForReturn(d); err != nil { + return nil, err + } return d, nil } @@ -295,6 +307,9 @@ func (c *etcdV3Client) Apply(ctx context.Context, d *model.KVPair) (*model.KVPai if err != nil { return nil, err } + if err = prepForWrite(d); err != nil { + return nil, err + } key, value, err := getKeyValueStrings(d) if err != nil { @@ -318,6 +333,9 @@ func (c *etcdV3Client) Apply(ctx context.Context, d *model.KVPair) (*model.KVPai d.Value = v d.Revision = strconv.FormatInt(resp.Header.Revision, 10) + if err = prepForReturn(d); err != nil { + return nil, err + } return d, nil } @@ -332,8 +350,6 @@ func (c *etcdV3Client) Delete(ctx context.Context, k model.Key, revision string) logCxt := log.WithFields(log.Fields{"model-etcdKey": k, "rev": revision}) logCxt.Debug("Processing Delete request") - k = defaultPolicyKey(k) - key, err := model.KeyToDefaultDeletePath(k) if err != nil { return nil, err @@ -400,8 +416,6 @@ func (c *etcdV3Client) Get(ctx context.Context, k model.Key, revision string) (* logCxt := log.WithFields(log.Fields{"model-etcdKey": k, "rev": revision}) logCxt.Debug("Processing Get request") - k = defaultPolicyKey(k) - key, err := model.KeyToDefaultPath(k) if err != nil { logCxt.Error("Unable to convert model.Key to an etcdv3 etcdKey") @@ -469,6 +483,9 @@ func (c *etcdV3Client) List(ctx context.Context, l model.ListInterface, revision list := []*model.KVPair{} for _, p := range resp.Kvs { if kv := convertListResponse(p, l); kv != nil { + if err = prepForReturn(kv); err != nil { + return nil, err + } list = append(list, kv) } } @@ -537,7 +554,7 @@ func calculateListKeyAndOptions(logCxt *log.Entry, l model.ListInterface) (strin // EnsureInitialized makes sure that the etcd data is initialized for use by // Calico. func (c *etcdV3Client) EnsureInitialized() error { - //TODO - still need to worry about ready flag. + // TODO - still need to worry about ready flag. return nil } @@ -547,7 +564,6 @@ func (c *etcdV3Client) Clean() error { _, err := c.etcdClient.Txn(context.Background()).If().Then( clientv3.OpDelete("/calico/", clientv3.WithPrefix()), ).Commit() - if err != nil { return cerrors.ErrorDatastoreError{Err: err} } @@ -651,6 +667,52 @@ func storePolicyName(name string, annotations map[string]string) (map[string]str return annotations, nil } +func prepForWrite(d *model.KVPair) error { + // We receive v3 block affinity objects from the client, but they are stored as internalapi objects + // for historical reasons. + if _, ok := d.Value.(*apiv3.BlockAffinity); ok { + value := d.Value.(*apiv3.BlockAffinity) + + // Convert the v3 API object to an internalapi object for storage. + v1Obj := internalapi.NewBlockAffinity() + v1Obj.ObjectMeta = value.ObjectMeta + v1Obj.Spec = internalapi.BlockAffinitySpec{ + State: string(value.Spec.State), + Node: value.Spec.Node, + Type: value.Spec.Type, + CIDR: value.Spec.CIDR, + Deleted: fmt.Sprintf("%t", value.Spec.Deleted), + } + d.Value = v1Obj + } + return nil +} + +func prepForReturn(d *model.KVPair) error { + // We store internalapi block affinity objects, but we return v3 API objects to the client. + // So convert them here. + if _, ok := d.Value.(*internalapi.BlockAffinity); ok { + value := d.Value.(*internalapi.BlockAffinity) + + // Convert the internalapi object to a v3 API object for return. + v3Obj := apiv3.NewBlockAffinity() + v3Obj.ObjectMeta = value.ObjectMeta + deleted, err := strconv.ParseBool(value.Spec.Deleted) + if err != nil { + return fmt.Errorf("error parsing BlockAffinity.Spec.Deleted field: %w", err) + } + v3Obj.Spec = apiv3.BlockAffinitySpec{ + State: apiv3.BlockAffinityState(value.Spec.State), + Node: value.Spec.Node, + Type: value.Spec.Type, + CIDR: value.Spec.CIDR, + Deleted: deleted, + } + d.Value = v3Obj + } + return nil +} + func defaultPolicyName(d *model.KVPair) error { if _, ok := d.Value.(*apiv3.NetworkPolicy); ok { value := d.Value.(*apiv3.NetworkPolicy) @@ -663,14 +725,6 @@ func defaultPolicyName(d *model.KVPair) error { } value.Annotations = annotations - - // Now that we've captured the original name, canonicalize the name for storage, - // adding the tier prefix to ensure policies with the same name in different tiers do not conflict. - polName, err := names.BackendTieredPolicyName(value.Name, value.Spec.Tier) - if err != nil { - return err - } - value.Name = polName } if _, ok := d.Value.(*apiv3.GlobalNetworkPolicy); ok { @@ -682,12 +736,6 @@ func defaultPolicyName(d *model.KVPair) error { } value.Annotations = annotations - - polName, err := names.BackendTieredPolicyName(value.Name, value.Spec.Tier) - if err != nil { - return err - } - value.Name = polName } if _, ok := d.Value.(*apiv3.StagedNetworkPolicy); ok { @@ -699,12 +747,6 @@ func defaultPolicyName(d *model.KVPair) error { } value.Annotations = annotations - - polName, err := names.BackendTieredPolicyName(value.Name, value.Spec.Tier) - if err != nil { - return err - } - value.Name = polName } if _, ok := d.Value.(*apiv3.StagedGlobalNetworkPolicy); ok { @@ -716,32 +758,7 @@ func defaultPolicyName(d *model.KVPair) error { } value.Annotations = annotations - - polName, err := names.BackendTieredPolicyName(value.Name, value.Spec.Tier) - if err != nil { - return err - } - value.Name = polName } - d.Key = defaultPolicyKey(d.Key) - return nil } - -func defaultPolicyKey(k model.Key) model.Key { - if _, ok := k.(model.ResourceKey); ok { - resourceKey := k.(model.ResourceKey) - if resourceKey.Kind == apiv3.KindNetworkPolicy || - resourceKey.Kind == apiv3.KindStagedNetworkPolicy || - resourceKey.Kind == apiv3.KindGlobalNetworkPolicy || - resourceKey.Kind == apiv3.KindStagedGlobalNetworkPolicy { - // To avoid conflicts all policies need to have the tier prefix added to the name to ensure they are unique - polName := names.TieredPolicyName(resourceKey.Name) - resourceKey.Name = polName - - return resourceKey - } - } - return k -} diff --git a/libcalico-go/lib/backend/etcdv3/etcdv3_suite_test.go b/libcalico-go/lib/backend/etcdv3/etcdv3_suite_test.go index 850ddfff3ac..19f22545a38 100644 --- a/libcalico-go/lib/backend/etcdv3/etcdv3_suite_test.go +++ b/libcalico-go/lib/backend/etcdv3/etcdv3_suite_test.go @@ -17,16 +17,16 @@ package etcdv3_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestEtcd(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../report/etcdv3_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "EtcdV3 Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../report/etcdv3_suite.xml" + ginkgo.RunSpecs(t, "EtcdV3 Suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/backend/etcdv3/etcdv3_test.go b/libcalico-go/lib/backend/etcdv3/etcdv3_test.go index 7bc2c84f0f8..f33567cec8d 100644 --- a/libcalico-go/lib/backend/etcdv3/etcdv3_test.go +++ b/libcalico-go/lib/backend/etcdv3/etcdv3_test.go @@ -1,7 +1,7 @@ package etcdv3_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" diff --git a/libcalico-go/lib/backend/k8s/k8s.go b/libcalico-go/lib/backend/k8s/client.go similarity index 65% rename from libcalico-go/lib/backend/k8s/k8s.go rename to libcalico-go/lib/backend/k8s/client.go index b9a4ea5f43c..2c2a160ec37 100644 --- a/libcalico-go/lib/backend/k8s/k8s.go +++ b/libcalico-go/lib/backend/k8s/client.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2016-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -27,8 +27,6 @@ import ( apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sruntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" @@ -37,37 +35,32 @@ import ( _ "k8s.io/client-go/plugin/pkg/client/auth" // Import all auth providers. "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" - adminpolicyclient "sigs.k8s.io/network-policy-api/pkg/client/clientset/versioned/typed/apis/v1alpha1" + kubevirtclient "kubevirt.io/client-go/kubevirt/typed/core/v1" + netpolicyclient "sigs.k8s.io/network-policy-api/pkg/client/clientset/versioned/typed/apis/v1alpha2" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + v1scheme "github.com/projectcalico/calico/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/resources" - calischeme "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/scheme" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" - "github.com/projectcalico/calico/libcalico-go/lib/net" "github.com/projectcalico/calico/libcalico-go/lib/set" "github.com/projectcalico/calico/libcalico-go/lib/winutils" ) var ( - resourceKeyType = reflect.TypeOf(model.ResourceKey{}) - resourceListType = reflect.TypeOf(model.ResourceListOptions{}) + resourceKeyType = reflect.TypeFor[model.ResourceKey]() + resourceListType = reflect.TypeFor[model.ResourceListOptions]() ) type KubeClient struct { // Main Kubernetes clients. ClientSet *kubernetes.Clientset - // Client for interacting with CustomResourceDefinition. - crdClientV1 *rest.RESTClient - - // Client for interacting with K8S Admin Network Policy, and BaselineAdminNetworkPolicy. - k8sAdminPolicyClient *adminpolicyclient.PolicyV1alpha1Client - - disableNodePoll bool + // Client for interacting with K8S Cluster Network Policy. + k8sClusterPolicyClient *netpolicyclient.PolicyV1alpha2Client // Contains methods for converting Kubernetes resources to // Calico resources. @@ -84,231 +77,256 @@ type KubeClient struct { } func NewKubeClient(ca *apiconfig.CalicoAPIConfigSpec) (api.Client, error) { + // Whether or not we are writing to projectcalico.org/v3 resources. If true, we're running in + // "no API server" mode where the v3 resources are backed by CRDs directly. Otherwise, we're running + // with the Calico API server and should instead use crd.projectcalico.org/v1 resources directly. + group := BackendAPIGroup(ca) + log.WithField("apiGroup", group).Info("Using API group for CRD backend") + config, cs, err := CreateKubernetesClientset(ca) if err != nil { return nil, err } - crdClientV1, err := buildCRDClientV1(*config) + restClient, err := restClient(*config, group) if err != nil { - return nil, fmt.Errorf("Failed to build V1 CRD client: %v", err) + return nil, fmt.Errorf("failed to build CRD client: %v", err) } - k8sAdminPolicyClient, err := buildK8SAdminPolicyClient(config) + cnpClient, err := clusterNetworkPolicyClient(config) if err != nil { - return nil, fmt.Errorf("Failed to build K8S Admin Network Policy client: %v", err) + return nil, fmt.Errorf("Failed to build ClusterNetworkPolicy client: %v", err) } - kubeClient := &KubeClient{ + c := &KubeClient{ ClientSet: cs, - crdClientV1: crdClientV1, - k8sAdminPolicyClient: k8sAdminPolicyClient, - disableNodePoll: ca.K8sDisableNodePoll, clientsByResourceKind: make(map[string]resources.K8sResourceClient), clientsByKeyType: make(map[reflect.Type]resources.K8sResourceClient), clientsByListType: make(map[reflect.Type]resources.K8sResourceClient), } - // Create the Calico sub-clients and register them. - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), + // These resources are backed by Calico custom resource definitions (CRDs). Whether they are + // backed by projectcalico.org/v3 or crd.projectcalico.org/v1 is configurable. + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), apiv3.KindIPPool, - resources.NewIPPoolClient(cs, crdClientV1), + resources.NewIPPoolClient(restClient, group), ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), apiv3.KindIPReservation, - resources.NewIPReservationClient(cs, crdClientV1), + resources.NewIPReservationClient(restClient, group), ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), apiv3.KindGlobalNetworkPolicy, - resources.NewGlobalNetworkPolicyClient(cs, crdClientV1), + resources.NewGlobalNetworkPolicyClient(restClient, group), ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), apiv3.KindStagedGlobalNetworkPolicy, - resources.NewStagedGlobalNetworkPolicyClient(cs, crdClientV1), - ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), - model.KindKubernetesAdminNetworkPolicy, - resources.NewKubernetesAdminNetworkPolicyClient(k8sAdminPolicyClient), + resources.NewStagedGlobalNetworkPolicyClient(restClient, group), ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), - model.KindKubernetesBaselineAdminNetworkPolicy, - resources.NewKubernetesBaselineAdminNetworkPolicyClient(k8sAdminPolicyClient), - ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), apiv3.KindGlobalNetworkSet, - resources.NewGlobalNetworkSetClient(cs, crdClientV1), + resources.NewGlobalNetworkSetClient(restClient, group), ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), apiv3.KindNetworkPolicy, - resources.NewNetworkPolicyClient(cs, crdClientV1), + resources.NewNetworkPolicyClient(restClient, group), ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), apiv3.KindStagedNetworkPolicy, - resources.NewStagedNetworkPolicyClient(cs, crdClientV1), - ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), - model.KindKubernetesNetworkPolicy, - resources.NewKubernetesNetworkPolicyClient(cs), + resources.NewStagedNetworkPolicyClient(restClient, group), ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), apiv3.KindStagedKubernetesNetworkPolicy, - resources.NewStagedKubernetesNetworkPolicyClient(cs, crdClientV1), + resources.NewStagedKubernetesNetworkPolicyClient(restClient, group), ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), - model.KindKubernetesEndpointSlice, - resources.NewKubernetesEndpointSliceClient(cs), - ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), apiv3.KindNetworkSet, - resources.NewNetworkSetClient(cs, crdClientV1), + resources.NewNetworkSetClient(restClient, group), ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), apiv3.KindTier, - resources.NewTierClient(cs, crdClientV1), + resources.NewTierClient(restClient, group), ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), apiv3.KindBGPPeer, - resources.NewBGPPeerClient(cs, crdClientV1), + resources.NewBGPPeerClient(restClient, group), ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), apiv3.KindBGPConfiguration, - resources.NewBGPConfigClient(cs, crdClientV1), + resources.NewBGPConfigClient(restClient, group), ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), apiv3.KindFelixConfiguration, - resources.NewFelixConfigClient(cs, crdClientV1), + resources.NewFelixConfigClient(restClient, group), ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), apiv3.KindClusterInformation, - resources.NewClusterInfoClient(cs, crdClientV1), - ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), - libapiv3.KindNode, - resources.NewNodeClient(cs, ca.K8sUsePodCIDR), - ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), - apiv3.KindProfile, - resources.NewProfileClient(cs), + resources.NewClusterInfoClient(restClient, group), ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), apiv3.KindHostEndpoint, - resources.NewHostEndpointClient(cs, crdClientV1), + resources.NewHostEndpointClient(restClient, group), ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), - libapiv3.KindWorkloadEndpoint, - resources.NewWorkloadEndpointClient(cs), - ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), apiv3.KindKubeControllersConfiguration, - resources.NewKubeControllersConfigClient(cs, crdClientV1), + resources.NewKubeControllersConfigClient(restClient, group), ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), apiv3.KindCalicoNodeStatus, - resources.NewCalicoNodeStatusClient(cs, crdClientV1), + resources.NewCalicoNodeStatusClient(restClient, group), ) - kubeClient.registerResourceClient( + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), + apiv3.KindBlockAffinity, + resources.NewBlockAffinityClientV3(restClient, group), + ) + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), + apiv3.KindBGPFilter, + resources.NewBGPFilterClient(restClient, group), + ) + // IPAMConfig can come to us from two places: + // - The lib/ipam code, which uses the older v1 API 'IPAMConfig' + // - The lib/clientv3 code, which uses the newer v3 API 'IPAMConfiguration' + // We always register the v3 client, but also register the v1 client for the + // older IPAM code below if it's in use. + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), + apiv3.KindIPAMConfiguration, + resources.NewIPAMConfigClientV3(restClient, group), + ) + + // These resources are backed directly by core Kubernetes APIs or third-party + // APIs, and do not use CRDs. + kvClient, err := kubevirtclient.NewForConfig(config) + if err != nil { + return nil, fmt.Errorf("failed to build KubeVirt client: %v", err) + } + c.registerResourceClient( reflect.TypeOf(model.ResourceKey{}), reflect.TypeOf(model.ResourceListOptions{}), + internalapi.KindLiveMigration, + resources.NewLiveMigrationClient(func(namespace string) resources.VMIMClient { + return kvClient.VirtualMachineInstanceMigrations(namespace) + }), + ) + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), model.KindKubernetesService, resources.NewServiceClient(cs), ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), - libapiv3.KindIPAMConfig, - resources.NewIPAMConfigClient(cs, crdClientV1), + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), + model.KindKubernetesEndpointSlice, + resources.NewKubernetesEndpointSliceClient(cs), ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), - libapiv3.KindBlockAffinity, - resources.NewBlockAffinityClient(cs, crdClientV1), + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), + internalapi.KindWorkloadEndpoint, + resources.NewWorkloadEndpointClient(cs), ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.ResourceKey{}), - reflect.TypeOf(model.ResourceListOptions{}), - apiv3.KindBGPFilter, - resources.NewBGPFilterClient(cs, crdClientV1), + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), + internalapi.KindNode, + resources.NewNodeClient(cs, ca.K8sUsePodCIDR), + ) + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), + apiv3.KindProfile, + resources.NewProfileClient(cs), + ) + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), + model.KindKubernetesNetworkPolicy, + resources.NewKubernetesNetworkPolicyClient(cs), + ) + + c.registerResourceClient( + reflect.TypeFor[model.ResourceKey](), + reflect.TypeFor[model.ResourceListOptions](), + model.KindKubernetesClusterNetworkPolicy, + resources.NewKubernetesClusterNetworkPolicyClient(cnpClient), ) if !ca.K8sUsePodCIDR { // Using Calico IPAM - use CRDs to back IPAM resources. log.Debug("Calico is configured to use calico-ipam") - kubeClient.registerResourceClient( - reflect.TypeOf(model.BlockAffinityKey{}), - reflect.TypeOf(model.BlockAffinityListOptions{}), - libapiv3.KindBlockAffinity, - resources.NewBlockAffinityClient(cs, crdClientV1), + + // lib/ipam uses different types for these resources, so register them separately + // from the v3 resources already registered above. + c.registerResourceClient( + reflect.TypeFor[model.BlockAffinityKey](), + reflect.TypeFor[model.BlockAffinityListOptions](), + internalapi.KindBlockAffinity, + resources.NewBlockAffinityClientV1(restClient, group), ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.BlockKey{}), - reflect.TypeOf(model.BlockListOptions{}), - libapiv3.KindIPAMBlock, - resources.NewIPAMBlockClient(cs, crdClientV1), + c.registerResourceClient( + reflect.TypeFor[model.IPAMConfigKey](), + nil, + internalapi.KindIPAMConfig, + resources.NewIPAMConfigClientV1(restClient, group), ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.IPAMHandleKey{}), - reflect.TypeOf(model.IPAMHandleListOptions{}), - libapiv3.KindIPAMHandle, - resources.NewIPAMHandleClient(cs, crdClientV1), + + // These do not get registered as part of the v3 API, and are only + // accessed from the lib/ipam code. + c.registerResourceClient( + reflect.TypeFor[model.BlockKey](), + reflect.TypeFor[model.BlockListOptions](), + internalapi.KindIPAMBlock, + resources.NewIPAMBlockClient(restClient, group), ) - kubeClient.registerResourceClient( - reflect.TypeOf(model.IPAMConfigKey{}), - nil, - libapiv3.KindIPAMConfig, - resources.NewIPAMConfigClient(cs, crdClientV1), + c.registerResourceClient( + reflect.TypeFor[model.IPAMHandleKey](), + reflect.TypeFor[model.IPAMHandleListOptions](), + internalapi.KindIPAMHandle, + resources.NewIPAMHandleClient(restClient, group), ) } - return kubeClient, nil + return c, nil } // deduplicate removes any duplicated values and returns a new slice, keeping the order unchanged @@ -404,6 +422,11 @@ func CreateKubernetesClientset(ca *apiconfig.CalicoAPIConfigSpec) (*rest.Config, // efficiently. The IPAM code can create bursts of requests to the API, so // in order to keep pod creation times sensible we allow a higher request rate. config.Burst = 100 + if ca.K8sClientBurst != 0 { + config.Burst = ca.K8sClientBurst + } + log.Debugf("Kubernetes client QPS set to %v, burst set to %v", config.QPS, config.Burst) + cs, err := kubernetes.NewForConfig(config) if err != nil { return nil, nil, resources.K8sErrorToCalico(err, nil) @@ -448,19 +471,11 @@ func (c *KubeClient) getResourceClientFromList(list model.ListInterface) resourc } } -// EnsureInitialized checks that the necessary custom resource definitions -// exist in the backend. This usually passes when using etcd -// as a backend but can often fail when using KDD as it relies -// on various custom resources existing. -// To ensure the datastore is initialized, this function checks that a -// known custom resource is defined: GlobalFelixConfig. It accomplishes this -// by trying to set the ClusterType (an instance of GlobalFelixConfig). func (c *KubeClient) EnsureInitialized() error { return nil } -// Remove Calico-creatable data from the datastore. This is purely used for the -// test framework. +// Remove Calico-creatable data from the datastore. This is purely used for the test framework. func (c *KubeClient) Clean() error { timeout := 2 * time.Minute log.Warningf("Cleaning KDD of all Calico-creatable data: timeout %v", timeout) @@ -487,9 +502,10 @@ func (c *KubeClient) Clean() error { apiv3.KindIPReservation, apiv3.KindHostEndpoint, apiv3.KindKubeControllersConfiguration, - libapiv3.KindIPAMConfig, - libapiv3.KindBlockAffinity, + apiv3.KindIPAMConfiguration, + apiv3.KindBlockAffinity, apiv3.KindBGPFilter, + internalapi.KindIPAMConfig, } // Deletion can fail due to CAS conflicts if multiple resources are @@ -614,11 +630,11 @@ func (c *KubeClient) Clean() error { } // Get a list of Nodes and remove all BGP configuration from the nodes. - if nodes, err := c.List(ctx, model.ResourceListOptions{Kind: libapiv3.KindNode}, ""); err != nil { + if nodes, err := c.List(ctx, model.ResourceListOptions{Kind: internalapi.KindNode}, ""); err != nil { log.Warning("Failed to list Nodes") } else { for _, nodeKvp := range nodes.KVPairs { - node := nodeKvp.Value.(*libapiv3.Node) + node := nodeKvp.Value.(*internalapi.Node) node.Spec.BGP = nil if _, err := c.Update(ctx, nodeKvp); err != nil { log.WithField("Node", node.Name).Warning("Failed to remove Calico config from node") @@ -639,18 +655,25 @@ func (c *KubeClient) Close() error { return nil } -// buildK8SAdminPolicyClient builds a RESTClient configured to interact (Baseline) Admin Network Policy. -func buildK8SAdminPolicyClient(cfg *rest.Config) (*adminpolicyclient.PolicyV1alpha1Client, error) { - return adminpolicyclient.NewForConfig(cfg) +// clusterNetworkPolicyClient builds a RESTClient configured to interact Cluster Network Policy. +func clusterNetworkPolicyClient(cfg *rest.Config) (*netpolicyclient.PolicyV1alpha2Client, error) { + return netpolicyclient.NewForConfig(cfg) } -// buildCRDClientV1 builds a RESTClient configured to interact with Calico CustomResourceDefinitions -func buildCRDClientV1(cfg rest.Config) (*rest.RESTClient, error) { +// restClient builds a RESTClient configured to interact with Calico CustomResourceDefinitions +func restClient(cfg rest.Config, group resources.BackingAPIGroup) (*rest.RESTClient, error) { // Generate config using the base config. - cfg.GroupVersion = &schema.GroupVersion{ - Group: "crd.projectcalico.org", - Version: "v1", + switch group { + case resources.BackingAPIGroupV3: + cfg.GroupVersion = &schema.GroupVersion{Group: "projectcalico.org", Version: "v3"} + apiv3.AddToGlobalScheme() + case resources.BackingAPIGroupV1: + cfg.GroupVersion = &schema.GroupVersion{Group: "crd.projectcalico.org", Version: "v1"} + v1scheme.AddCalicoResourcesToGlobalScheme() + default: + return nil, fmt.Errorf("unknown backing API group: %v", group) } + cfg.APIPath = "/apis" cfg.ContentType = k8sruntime.ContentTypeJSON cfg.NegotiatedSerializer = serializer.WithoutConversionCodecFactory{CodecFactory: scheme.Codecs} @@ -660,8 +683,6 @@ func buildCRDClientV1(cfg rest.Config) (*rest.RESTClient, error) { return nil, err } - calischeme.AddCalicoResourcesToGlobalScheme() - return cli, nil } @@ -694,6 +715,29 @@ func (c *KubeClient) Update(ctx context.Context, d *model.KVPair) (*model.KVPair return client.Update(ctx, d) } +// UpdateStatus updates the status of an existing entry in the datastore using +// the status subresource. This errors if the entry does not exist. +func (c *KubeClient) UpdateStatus(ctx context.Context, d *model.KVPair) (*model.KVPair, error) { + log.Debugf("Performing 'UpdateStatus' for %+v", d) + client := c.getResourceClientFromKey(d.Key) + if client == nil { + log.Debug("Attempt to 'UpdateStatus' using kubernetes backend is not supported.") + return nil, cerrors.ErrorOperationNotSupported{ + Identifier: d.Key, + Operation: "UpdateStatus", + } + } + // Use the UpdateStatus method on the resource client if it supports it, + // otherwise fall back to Update. + type statusUpdater interface { + UpdateStatus(ctx context.Context, object *model.KVPair) (*model.KVPair, error) + } + if su, ok := client.(statusUpdater); ok { + return su.UpdateStatus(ctx, d) + } + return client.Update(ctx, d) +} + // Set an existing entry in the datastore. This ignores whether an entry already // exists. This is not exposed in the main client - but we keep here for the backend // API. @@ -797,83 +841,3 @@ func (c *KubeClient) Watch(ctx context.Context, l model.ListInterface, options a } return client.Watch(ctx, l, options) } - -func (c *KubeClient) getReadyStatus(ctx context.Context, k model.ReadyFlagKey, revision string) (*model.KVPair, error) { - return &model.KVPair{Key: k, Value: true}, nil -} - -func (c *KubeClient) listHostConfig(ctx context.Context, l model.HostConfigListOptions, revision string) (*model.KVPairList, error) { - kvps := []*model.KVPair{} - - // Short circuit if they aren't asking for information we can provide. - if l.Name != "" && l.Name != "IpInIpTunnelAddr" { - return &model.KVPairList{ - KVPairs: kvps, - Revision: revision, - }, nil - } - - // First see if we were handed a specific host, if not list all Nodes - if l.Hostname == "" { - nodes, err := c.ClientSet.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) - if err != nil { - return nil, resources.K8sErrorToCalico(err, l) - } - - for _, node := range nodes.Items { - kvp, err := getTunIp(&node) - if err != nil || kvp == nil { - continue - } - - kvps = append(kvps, kvp) - } - } else { - node, err := c.ClientSet.CoreV1().Nodes().Get(ctx, l.Hostname, metav1.GetOptions{}) - if err != nil { - return nil, resources.K8sErrorToCalico(err, l) - } - - kvp, err := getTunIp(node) - if err != nil || kvp == nil { - return &model.KVPairList{ - KVPairs: []*model.KVPair{}, - Revision: revision, - }, nil - } - - kvps = append(kvps, kvp) - } - - return &model.KVPairList{ - KVPairs: kvps, - Revision: revision, - }, nil -} - -func getTunIp(n *v1.Node) (*model.KVPair, error) { - if n.Spec.PodCIDR == "" { - log.Warnf("Node %s does not have podCIDR for HostConfig", n.Name) - return nil, nil - } - - ip, _, err := net.ParseCIDR(n.Spec.PodCIDR) - if err != nil { - log.Warnf("Invalid podCIDR for HostConfig: %s, %s", n.Name, n.Spec.PodCIDR) - return nil, err - } - // We need to get the IP for the podCIDR and increment it to the - // first IP in the CIDR. - tunIp := ip.To4() - tunIp[3]++ - - kvp := &model.KVPair{ - Key: model.HostConfigKey{ - Hostname: n.Name, - Name: "IpInIpTunnelAddr", - }, - Value: tunIp.String(), - } - - return kvp, nil -} diff --git a/libcalico-go/lib/backend/k8s/k8s_client_test.go b/libcalico-go/lib/backend/k8s/client_config_test.go similarity index 98% rename from libcalico-go/lib/backend/k8s/k8s_client_test.go rename to libcalico-go/lib/backend/k8s/client_config_test.go index ee82fd33c81..e7ffca7777b 100644 --- a/libcalico-go/lib/backend/k8s/k8s_client_test.go +++ b/libcalico-go/lib/backend/k8s/client_config_test.go @@ -15,13 +15,12 @@ package k8s import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "k8s.io/client-go/tools/clientcmd" ) var _ = Describe("CreateKubernetesClientset fillLoadingRulesFromKubeConfigSpec", func() { - When("There are multiple Kubeconfig files specified", func() { It("Should fill Precedence instead of ExplicitPath", func() { loadingRules := clientcmd.ClientConfigLoadingRules{} @@ -41,5 +40,4 @@ var _ = Describe("CreateKubernetesClientset fillLoadingRulesFromKubeConfigSpec", Expect(loadingRules.Precedence).To(BeEmpty()) }) }) - }) diff --git a/libcalico-go/lib/backend/k8s/k8s_test.go b/libcalico-go/lib/backend/k8s/client_test.go similarity index 86% rename from libcalico-go/lib/backend/k8s/k8s_test.go rename to libcalico-go/lib/backend/k8s/client_test.go index 03fd0c99b9d..211480adf90 100644 --- a/libcalico-go/lib/backend/k8s/k8s_test.go +++ b/libcalico-go/lib/backend/k8s/client_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2016-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ import ( "sync" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -34,12 +34,16 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/utils/ptr" ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" - adminpolicy "sigs.k8s.io/network-policy-api/apis/v1alpha1" - adminpolicyclient "sigs.k8s.io/network-policy-api/pkg/client/clientset/versioned/typed/apis/v1alpha1" + clusternetpolicy "sigs.k8s.io/network-policy-api/apis/v1alpha2" + netpolicyclient "sigs.k8s.io/network-policy-api/pkg/client/clientset/versioned/typed/apis/v1alpha2" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + v1scheme "github.com/projectcalico/calico/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/resources" @@ -52,10 +56,17 @@ import ( "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) +// If true, run the tests in v3 CRD mode. Otherwise, run against crd.projectcalico.org/v1 +var ( + v3CRD bool +) + var ( zeroOrder = float64(0.0) calicoAllowPolicyModelSpec = apiv3.GlobalNetworkPolicySpec{ + Tier: "default", Order: &zeroOrder, + Types: []apiv3.PolicyType{apiv3.PolicyTypeIngress, apiv3.PolicyTypeEgress}, Ingress: []apiv3.Rule{ { Action: "Allow", @@ -68,7 +79,9 @@ var ( }, } calicoDisallowPolicyModelSpec = apiv3.GlobalNetworkPolicySpec{ + Tier: "default", Order: &zeroOrder, + Types: []apiv3.PolicyType{apiv3.PolicyTypeIngress, apiv3.PolicyTypeEgress}, Ingress: []apiv3.Rule{ { Action: "Deny", @@ -83,7 +96,9 @@ var ( // Used for testing Syncer conversion calicoAllowPolicyModelV1 = model.Policy{ + Tier: "default", Order: &zeroOrder, + Types: []string{"ingress", "egress"}, InboundRules: []model.Rule{ { Action: "allow", @@ -96,7 +111,9 @@ var ( }, } calicoDisallowPolicyModelV1 = model.Policy{ + Tier: "default", Order: &zeroOrder, + Types: []string{"ingress", "egress"}, InboundRules: []model.Rule{ { Action: "deny", @@ -125,7 +142,7 @@ var ( // Use a back-off set of intervals for testing deletion of a namespace // which can sometimes be slow. - slowCheck = []interface{}{ + slowCheck = []any{ 60 * time.Second, 1 * time.Second, } @@ -283,22 +300,12 @@ func ExpectModifiedEvent(events <-chan api.WatchEvent) *api.WatchEvent { } func expectEventOfType(events <-chan api.WatchEvent, type_ api.WatchEventType) *api.WatchEvent { - var receivedEvent *api.WatchEvent -poll: - for i := 0; i < 10; i++ { - select { - case e := <-events: - // Got an event. Check it's OK. - ExpectWithOffset(2, e.Error).NotTo(HaveOccurred()) - ExpectWithOffset(2, e.Type).To(Equal(type_)) - receivedEvent = &e - break poll - default: - time.Sleep(50 * time.Millisecond) - } - } - ExpectWithOffset(2, receivedEvent).NotTo(BeNil(), "Did not receive watch event") - return receivedEvent + var e api.WatchEvent + EventuallyWithOffset(2, events, "5s", "100ms").Should(Receive(&e)) + Expect(e).NotTo(BeNil()) + ExpectWithOffset(2, e.Error).NotTo(HaveOccurred()) + ExpectWithOffset(2, e.Type).To(Equal(type_)) + return &e } // GetSyncerValueFunc returns a function that can be used to query the value of @@ -306,8 +313,8 @@ poll: // // The returned function returns the cached entry or nil if the entry does not // exist in the cache. -func (c *cb) GetSyncerValueFunc(key model.Key) func() interface{} { - return func() interface{} { +func (c *cb) GetSyncerValueFunc(key model.Key) func() any { + return func() any { log.Infof("Checking entry in cache: %v", key) c.Lock.Lock() defer c.Lock.Unlock() @@ -325,8 +332,8 @@ func (c *cb) GetSyncerValueFunc(key model.Key) func() interface{} { // the Value may itself by nil. // // The returned function returns true if the entry is present. -func (c *cb) GetSyncerValuePresentFunc(key model.Key) func() interface{} { - return func() interface{} { +func (c *cb) GetSyncerValuePresentFunc(key model.Key) func() any { + return func() any { log.Infof("Checking entry in cache: %v", key) c.Lock.Lock() defer c.Lock.Unlock() @@ -336,12 +343,15 @@ func (c *cb) GetSyncerValuePresentFunc(key model.Key) func() interface{} { } func CreateClientAndSyncer(cfg apiconfig.KubeConfig) (*KubeClient, *cb, api.Syncer) { + log.WithField("cfg", cfg).Info("Creating test backend client") + // First create the client. - caCfg := apiconfig.CalicoAPIConfigSpec{KubeConfig: cfg} - c, err := NewKubeClient(&caCfg) - if err != nil { - panic(err) + caCfg := apiconfig.CalicoAPIConfigSpec{ + DatastoreType: apiconfig.Kubernetes, + KubeConfig: cfg, } + c, err := NewKubeClient(&caCfg) + Expect(err).NotTo(HaveOccurred(), "Failed to create the backend client.") // Ensure the backend is initialized. err = c.EnsureInitialized() @@ -362,38 +372,83 @@ func CreateClientAndSyncer(cfg apiconfig.KubeConfig) (*KubeClient, *cb, api.Sync var _ = testutils.E2eDatastoreDescribe("Test UIDs and owner references", testutils.DatastoreK8s, func(cfg apiconfig.CalicoAPIConfig) { var ( c *KubeClient + cs *kubernetes.Clientset cli ctrlclient.Client ctx context.Context ) BeforeEach(func() { + v3CRD = UsingV3CRDs(&cfg.Spec) log.SetLevel(log.DebugLevel) // Create a k8s backend KVP client. var err error - apicfg := apiconfig.KubeConfig{Kubeconfig: "/kubeconfig.yaml", K8sDisableNodePoll: true} - c, _, _ = CreateClientAndSyncer(apicfg) + c, _, _ = CreateClientAndSyncer(cfg.Spec.KubeConfig) - // Create a controller-runtime client. - // Create a client for interacting with CRDs directly. - config, _, err := CreateKubernetesClientset(&cfg.Spec) + // Create a controller-runtime client for interacting with CRDs directly. + config, clientset, err := CreateKubernetesClientset(&cfg.Spec) + Expect(err).NotTo(HaveOccurred()) + cs = clientset + + // The CRD client needs to be configured with the correct scheme based on the + // API version of the underlying CRDs. + cli, err = newCRDClient(config) Expect(err).NotTo(HaveOccurred()) - cli, err = ctrlclient.New(config, ctrlclient.Options{}) ctx = context.Background() }) It("should properly read / write owner references", func() { - podUID := types.UID("3b5bff7a-501d-429f-b581-8954440883f4") - npUID := types.UID("19e9c0f4-501d-429f-b581-8954440883f4") - npUIDv1, err := conversion.ConvertUID(npUID) + // Create owner objects so the Kubernetes garbage collector doesn't delete the + // dependent NetworkPolicy. With v3 CRDs, owner references are stored as real + // Kubernetes ownerReferences (not in an annotation), so the GC will act on them + // and delete the NetworkPolicy if the owners don't exist. + pod, err := cs.CoreV1().Pods("default").Create(ctx, &k8sapi.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-owner-ref-pod", + }, + Spec: k8sapi.PodSpec{ + Containers: []k8sapi.Container{ + {Name: "pause", Image: "busybox", Command: []string{"sleep", "infinity"}}, + }, + }, + }, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + defer func() { + _ = cs.CoreV1().Pods("default").Delete(ctx, pod.Name, metav1.DeleteOptions{}) + }() + podUID := pod.UID + + nsKVP := model.KVPair{ + Key: model.ResourceKey{ + Name: "test-owner-ref-networkset", + Namespace: "default", + Kind: apiv3.KindNetworkSet, + }, + Value: &apiv3.NetworkSet{ + TypeMeta: metav1.TypeMeta{ + Kind: apiv3.KindNetworkSet, + APIVersion: apiv3.GroupVersionCurrent, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-owner-ref-networkset", + Namespace: "default", + }, + }, + } + nsResult, err := c.Create(ctx, &nsKVP) + Expect(err).NotTo(HaveOccurred()) + defer func() { + _, _ = c.Delete(ctx, nsKVP.Key, "") + }() + nsUID := nsResult.Value.(*apiv3.NetworkSet).UID + nsUIDv1, err := conversion.ConvertUID(nsUID) Expect(err).NotTo(HaveOccurred()) name := "test-owner-ref-policy" - keyName := fmt.Sprintf("default.%s", name) kvp := model.KVPair{ Key: model.ResourceKey{ - Name: keyName, + Name: name, Namespace: "default", Kind: apiv3.KindNetworkPolicy, }, @@ -418,7 +473,7 @@ var _ = testutils.E2eDatastoreDescribe("Test UIDs and owner references", testuti APIVersion: apiv3.GroupVersionCurrent, Kind: apiv3.KindNetworkSet, Name: "test-owner-ref-networkset", - UID: npUID, + UID: nsUID, }, }, }, @@ -432,28 +487,55 @@ var _ = testutils.E2eDatastoreDescribe("Test UIDs and owner references", testuti Expect(err).NotTo(HaveOccurred()) Expect(kvp2.Value.(*apiv3.NetworkPolicy).ObjectMeta.OwnerReferences).To(Equal(kvp.Value.(*apiv3.NetworkPolicy).ObjectMeta.OwnerReferences)) - // Query the underlying crd.projectcalico.org/v1 resource and check that the - // UID belonging to the pod is unchanged, but that the UID belonging to the - // Calico resource has been translated. + // Query the underlying custom resource and check that the UID is as expected. + // The Pod UID should be unchanged, but the NetworkSet UID behavior varies based on API group: + // - crd.projectcalico.org: UID belonging to the Calico resource has been translated. + // - projectcalico.org/v3: UID belonging to the Calico resource is unchanged. crd := &apiv3.NetworkPolicy{} - err = cli.Get(ctx, types.NamespacedName{Name: keyName, Namespace: "default"}, crd) - Expect(err).NotTo(HaveOccurred()) + Eventually(func() error { + return cli.Get(ctx, types.NamespacedName{Name: name, Namespace: "default"}, crd) + }, "5s", "100ms").ShouldNot(HaveOccurred()) - // The OwnerReferences are stored in an annotation. Load it. - meta := metav1.ObjectMeta{} - annot := crd.Annotations["projectcalico.org/metadata"] - err = json.Unmarshal([]byte(annot), &meta) - Expect(err).NotTo(HaveOccurred()) + var meta metav1.ObjectMeta + if v3CRD { + // The OwnerReferences are stored in the object metadata. + meta = crd.ObjectMeta + } else { + // The OwnerReferences are stored in an annotation. Load it. + meta = metav1.ObjectMeta{} + annot := crd.Annotations["projectcalico.org/metadata"] + err = json.Unmarshal([]byte(annot), &meta) + Expect(err).NotTo(HaveOccurred()) + } - // Compare the OwnerReferences. pod UID should be unchanged, but np UID should be translated. Expect(meta.OwnerReferences).To(HaveLen(2)) Expect(meta.OwnerReferences[0].UID).To(Equal(podUID)) Expect(meta.OwnerReferences[0].APIVersion).To(Equal("v1")) - Expect(meta.OwnerReferences[1].UID).To(Equal(npUIDv1)) - Expect(meta.OwnerReferences[1].APIVersion).To(Equal("crd.projectcalico.org/v1")) + + if v3CRD { + // UID should be unchanged if we're in v3 CRD mode. + Expect(meta.OwnerReferences[1].UID).To(Equal(nsUID)) + Expect(meta.OwnerReferences[1].APIVersion).To(Equal("projectcalico.org/v3")) + } else { + // Compare the OwnerReferences. pod UID should be unchanged, but NetworkSet UID should be translated. + Expect(meta.OwnerReferences[1].UID).To(Equal(nsUIDv1)) + Expect(meta.OwnerReferences[1].APIVersion).To(Equal("crd.projectcalico.org/v1")) + } }) }) +func newCRDClient(config *rest.Config) (cli ctrlclient.Client, err error) { + // The CRD client needs to be configured with the correct scheme based on the + // API version of the underlying CRDs. + scheme := runtime.NewScheme() + if v3CRD { + apiv3.AddToScheme(scheme) + } else { + v1scheme.AddCalicoResourcesToScheme(scheme) + } + return ctrlclient.New(config, ctrlclient.Options{Scheme: scheme}) +} + var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", testutils.DatastoreK8s, func(cfg apiconfig.CalicoAPIConfig) { var ( c *KubeClient @@ -466,17 +548,24 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", BeforeEach(func() { log.SetLevel(log.DebugLevel) + v3CRD = UsingV3CRDs(&cfg.Spec) + + // Increase the QPS to avoid throttling during tests. + cfg.Spec.KubeConfig.K8sClientQPS = 1000 // Create a Kubernetes client, callbacks, and a syncer. - apicfg := apiconfig.KubeConfig{Kubeconfig: "/kubeconfig.yaml"} - c, cb, syncer = CreateClientAndSyncer(apicfg) + c, cb, syncer = CreateClientAndSyncer(cfg.Spec.KubeConfig) // Create a controller-runtime client. // Create a client for interacting with CRDs directly. config, _, err := CreateKubernetesClientset(&cfg.Spec) Expect(err).NotTo(HaveOccurred()) config.ContentType = runtime.ContentTypeJSON - cli, err = ctrlclient.New(config, ctrlclient.Options{}) + + // The CRD client needs to be configured with the correct scheme based on the + // API version of the underlying CRDs. + cli, err = newCRDClient(config) + Expect(err).NotTo(HaveOccurred()) // Start the syncer. syncer.Start() @@ -631,7 +720,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", It("should handle the static default-allow Profile", func() { findAllowAllProfileEvent := func(c <-chan api.WatchEvent) bool { found := false - for i := 0; i < 10; i++ { + for range 10 { select { case e := <-c: if e.Type == api.WatchAdded && @@ -1057,7 +1146,10 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", gnpClient := c.GetResourceClientFromResourceKind(apiv3.KindGlobalNetworkPolicy) kvp1Name := "default.my-test-gnp" - kvp1KeyV1 := model.PolicyKey{Name: kvp1Name, Tier: "default"} + kvp1KeyV1 := model.PolicyKey{ + Name: kvp1Name, + Kind: apiv3.KindGlobalNetworkPolicy, + } kvp1a := &model.KVPair{ Key: model.ResourceKey{Name: kvp1Name, Kind: apiv3.KindGlobalNetworkPolicy}, Value: &apiv3.GlobalNetworkPolicy{ @@ -1087,7 +1179,10 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", } kvp2Name := "default.my-test-gnp2" - kvp2KeyV1 := model.PolicyKey{Name: kvp2Name, Tier: "default"} + kvp2KeyV1 := model.PolicyKey{ + Name: kvp2Name, + Kind: apiv3.KindGlobalNetworkPolicy, + } kvp2a := &model.KVPair{ Key: model.ResourceKey{Name: kvp2Name, Kind: apiv3.KindGlobalNetworkPolicy}, Value: &apiv3.GlobalNetworkPolicy{ @@ -1240,8 +1335,10 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", var kvpRes *model.KVPair stagedCalicoAllowPolicyModelSpec := apiv3.StagedGlobalNetworkPolicySpec{ + Tier: "default", StagedAction: apiv3.StagedActionSet, Order: &zeroOrder, + Types: []apiv3.PolicyType{apiv3.PolicyTypeIngress, apiv3.PolicyTypeEgress}, Ingress: []apiv3.Rule{ { Action: "Allow", @@ -1254,8 +1351,10 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", }, } stagedCalicoDisallowPolicyModelSpec := apiv3.StagedGlobalNetworkPolicySpec{ + Tier: "default", StagedAction: apiv3.StagedActionSet, Order: &zeroOrder, + Types: []apiv3.PolicyType{apiv3.PolicyTypeIngress, apiv3.PolicyTypeEgress}, Ingress: []apiv3.Rule{ { Action: "Deny", @@ -1268,11 +1367,13 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", }, } - gnpClient := c.GetResourceClientFromResourceKind(apiv3.KindStagedGlobalNetworkPolicy) + sgnpClient := c.GetResourceClientFromResourceKind(apiv3.KindStagedGlobalNetworkPolicy) kvp1Name := "my-test-sgnp" - kvp1KeyV1 := model.PolicyKey{Name: kvp1Name, Tier: "default"} + kvp1KeyV1 := model.PolicyKey{ + Name: kvp1Name, + } kvp1a := &model.KVPair{ - Key: model.ResourceKey{Name: fmt.Sprintf("default.%s", kvp1Name), Kind: apiv3.KindStagedGlobalNetworkPolicy}, + Key: model.ResourceKey{Name: kvp1Name, Kind: apiv3.KindStagedGlobalNetworkPolicy}, Value: &apiv3.StagedGlobalNetworkPolicy{ TypeMeta: metav1.TypeMeta{ Kind: apiv3.KindStagedGlobalNetworkPolicy, @@ -1286,7 +1387,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", } kvp1b := &model.KVPair{ - Key: model.ResourceKey{Name: fmt.Sprintf("default.%s", kvp1Name), Kind: apiv3.KindStagedGlobalNetworkPolicy}, + Key: model.ResourceKey{Name: kvp1Name, Kind: apiv3.KindStagedGlobalNetworkPolicy}, Value: &apiv3.StagedGlobalNetworkPolicy{ TypeMeta: metav1.TypeMeta{ Kind: apiv3.KindStagedGlobalNetworkPolicy, @@ -1299,10 +1400,15 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", }, } - kvp2Name := "my-test-sgnp2" - kvp2KeyV1 := model.PolicyKey{Name: kvp2Name, Tier: "default"} + kvp2Name := "default.my-test-sgnp2" + kvp2KeyV1 := model.PolicyKey{ + Name: kvp2Name, + } kvp2a := &model.KVPair{ - Key: model.ResourceKey{Name: fmt.Sprintf("default.%s", kvp2Name), Kind: apiv3.KindStagedGlobalNetworkPolicy}, + Key: model.ResourceKey{ + Name: kvp2Name, + Kind: apiv3.KindStagedGlobalNetworkPolicy, + }, Value: &apiv3.StagedGlobalNetworkPolicy{ TypeMeta: metav1.TypeMeta{ Kind: apiv3.KindStagedGlobalNetworkPolicy, @@ -1316,7 +1422,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", } kvp2b := &model.KVPair{ - Key: model.ResourceKey{Name: fmt.Sprintf("default.%s", kvp2Name), Kind: apiv3.KindStagedGlobalNetworkPolicy}, + Key: model.ResourceKey{Name: kvp2Name, Kind: apiv3.KindStagedGlobalNetworkPolicy}, Value: &apiv3.StagedGlobalNetworkPolicy{ TypeMeta: metav1.TypeMeta{ Kind: apiv3.KindStagedGlobalNetworkPolicy, @@ -1339,33 +1445,35 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", By("Creating a Staged Global Network Policy", func() { var err error - kvpRes, err = gnpClient.Create(ctx, kvp1a) + kvpRes, err = sgnpClient.Create(ctx, kvp1a) Expect(err).NotTo(HaveOccurred()) }) + By("Attempting to recreate an existing Staged Global Network Policy", func() { - _, err := gnpClient.Create(ctx, kvp1a) + _, err := sgnpClient.Create(ctx, kvp1a) Expect(err).To(HaveOccurred()) }) By("Updating an existing Staged Global Network Policy", func() { kvp1b.Revision = kvpRes.Revision - _, err := gnpClient.Update(ctx, kvp1b) + _, err := sgnpClient.Update(ctx, kvp1b) Expect(err).NotTo(HaveOccurred()) }) + By("Create another Staged Global Network Policy", func() { var err error - kvpRes, err = gnpClient.Create(ctx, kvp2a) + kvpRes, err = sgnpClient.Create(ctx, kvp2a) Expect(err).NotTo(HaveOccurred()) }) By("Updating the Staged Global Network Policy created by Create", func() { kvp2b.Revision = kvpRes.Revision - _, err := gnpClient.Update(ctx, kvp2b) + _, err := sgnpClient.Update(ctx, kvp2b) Expect(err).NotTo(HaveOccurred()) }) By("Deleted the Staged Global Network Policy created by Apply", func() { - _, err := gnpClient.Delete(ctx, kvp2a.Key, "", nil) + _, err := sgnpClient.Delete(ctx, kvp2a.Key, "", nil) Expect(err).NotTo(HaveOccurred()) }) @@ -1381,9 +1489,9 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", }) By("Getting an existing Staged Global Network Policy", func() { - kvp, err := c.Get(ctx, model.ResourceKey{Name: "default.my-test-sgnp", Kind: apiv3.KindStagedGlobalNetworkPolicy}, "") + kvp, err := c.Get(ctx, model.ResourceKey{Name: kvp1Name, Kind: apiv3.KindStagedGlobalNetworkPolicy}, "") Expect(err).ToNot(HaveOccurred()) - Expect(kvp.Key.(model.ResourceKey).Name).To(Equal("default.my-test-sgnp")) + Expect(kvp.Key.(model.ResourceKey).Name).To(Equal(kvp1Name)) Expect(kvp.Value.(*apiv3.StagedGlobalNetworkPolicy).Spec).To(Equal(kvp1b.Value.(*apiv3.StagedGlobalNetworkPolicy).Spec)) }) @@ -1391,12 +1499,12 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", kvps, err := c.List(ctx, model.ResourceListOptions{Kind: apiv3.KindStagedGlobalNetworkPolicy}, "") Expect(err).ToNot(HaveOccurred()) Expect(kvps.KVPairs).To(HaveLen(1)) - Expect(kvps.KVPairs[len(kvps.KVPairs)-1].Key.(model.ResourceKey).Name).To(Equal("default.my-test-sgnp")) + Expect(kvps.KVPairs[len(kvps.KVPairs)-1].Key.(model.ResourceKey).Name).To(Equal(kvp1Name)) Expect(kvps.KVPairs[len(kvps.KVPairs)-1].Value.(*apiv3.StagedGlobalNetworkPolicy).Spec).To(Equal(kvp1b.Value.(*apiv3.StagedGlobalNetworkPolicy).Spec)) }) By("Deleting an existing Staged Global Network Policy", func() { - _, err := gnpClient.Delete(ctx, kvp1a.Key, "", nil) + _, err := sgnpClient.Delete(ctx, kvp1a.Key, "", nil) Expect(err).NotTo(HaveOccurred()) }) @@ -1532,7 +1640,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", Expect(err).ToNot(HaveOccurred()) Expect(kvps.KVPairs).To(HaveLen(2)) keys := []model.Key{} - vals := []interface{}{} + vals := []any{} for _, k := range kvps.KVPairs { keys = append(keys, k.Key) vals = append(vals, k.Value.(*apiv3.HostEndpoint).Spec) @@ -1613,7 +1721,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", Spec: apiv3.NetworkSetSpec{ Nets: []string{ "8.8.8.8/32", - "aa:bb::cc", + "aa:bb::cc/128", }, }, }, @@ -1683,7 +1791,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", Expect(err).ToNot(HaveOccurred()) Expect(kvps.KVPairs).To(HaveLen(2)) keys := []model.Key{} - vals := []interface{}{} + vals := []any{} for _, k := range kvps.KVPairs { keys = append(keys, k.Key) vals = append(vals, k.Value.(*apiv3.NetworkSet).Spec) @@ -1854,7 +1962,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", Expect(err).ToNot(HaveOccurred()) Expect(kvps.KVPairs).To(HaveLen(2)) keys := []model.Key{} - vals := []interface{}{} + vals := []any{} for _, k := range kvps.KVPairs { keys = append(keys, k.Key) vals = append(vals, k.Value.(*apiv3.BGPPeer).Spec) @@ -1890,7 +1998,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", var nodename, peername1, peername2 string By("Listing all Nodes to find a suitable Node name", func() { - nodes, err := c.List(ctx, model.ResourceListOptions{Kind: libapiv3.KindNode}, "") + nodes, err := c.List(ctx, model.ResourceListOptions{Kind: internalapi.KindNode}, "") Expect(err).NotTo(HaveOccurred()) // Get the hostname so we can make a Get call kvp := *nodes.KVPairs[0] @@ -1952,7 +2060,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", }, Spec: apiv3.BGPPeerSpec{ Node: nodename, - PeerIP: "aa:bb::cc", + PeerIP: "aa:bb::cc/128", ASNumber: numorstring.ASNumber(6514), }, }, @@ -1972,7 +2080,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", }, Spec: apiv3.BGPPeerSpec{ Node: nodename, - PeerIP: "aa:bb::cc", + PeerIP: "aa:bb::cc/128", }, }, } @@ -2047,7 +2155,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", Expect(err).ToNot(HaveOccurred()) Expect(kvps.KVPairs).To(HaveLen(2)) keys := []model.Key{} - vals := []interface{}{} + vals := []any{} for _, k := range kvps.KVPairs { keys = append(keys, k.Key) vals = append(vals, k.Value.(*apiv3.BGPPeer).Spec) @@ -2118,7 +2226,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", By("Waiting for the pod to start", func() { // Wait up to 120s for pod to start running. log.Warnf("[TEST] Waiting for pod %s to start", pod.ObjectMeta.Name) - for i := 0; i < 120; i++ { + for range 120 { p, err := c.ClientSet.CoreV1().Pods("default").Get(ctx, pod.ObjectMeta.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) if p.Status.Phase == k8sapi.PodRunning { @@ -2149,23 +2257,23 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", By("Performing a List() operation", func() { // Perform List and ensure it shows up in the Calico API. - weps, err := c.List(ctx, model.ResourceListOptions{Kind: libapiv3.KindWorkloadEndpoint}, "") + weps, err := c.List(ctx, model.ResourceListOptions{Kind: internalapi.KindWorkloadEndpoint}, "") Expect(err).NotTo(HaveOccurred()) Expect(len(weps.KVPairs)).To(BeNumerically(">", 0)) }) By("Performing a List(Name=wepName) operation", func() { // Perform List, including a workload Name - weps, err := c.List(ctx, model.ResourceListOptions{Name: wepName, Namespace: "default", Kind: libapiv3.KindWorkloadEndpoint}, "") + weps, err := c.List(ctx, model.ResourceListOptions{Name: wepName, Namespace: "default", Kind: internalapi.KindWorkloadEndpoint}, "") Expect(err).NotTo(HaveOccurred()) Expect(len(weps.KVPairs)).To(Equal(1)) }) By("Performing a Get() operation then updating the wep", func() { // Perform a Get and ensure no error in the Calico API. - wep, err := c.Get(ctx, model.ResourceKey{Name: wepName, Namespace: "default", Kind: libapiv3.KindWorkloadEndpoint}, "") + wep, err := c.Get(ctx, model.ResourceKey{Name: wepName, Namespace: "default", Kind: internalapi.KindWorkloadEndpoint}, "") Expect(err).NotTo(HaveOccurred()) - fmt.Printf("Updating Wep %+v\n", wep.Value.(*libapiv3.WorkloadEndpoint).Spec) + fmt.Printf("Updating Wep %+v\n", wep.Value.(*internalapi.WorkloadEndpoint).Spec) ctxCNI := resources.ContextWithPatchMode(ctx, resources.PatchModeCNI) _, err = c.Update(ctxCNI, wep) Expect(err).NotTo(HaveOccurred()) @@ -2188,8 +2296,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", By("Expecting a Syncer snapshot to include the update with type 'KVNew'", func() { // Create a new syncer / callback pair so that it performs a snapshot. - cfg := apiconfig.KubeConfig{Kubeconfig: "/kubeconfig.yaml"} - _, snapshotCallbacks, snapshotSyncer := CreateClientAndSyncer(cfg) + _, snapshotCallbacks, snapshotSyncer := CreateClientAndSyncer(cfg.Spec.KubeConfig) defer snapshotSyncer.Stop() go snapshotCallbacks.ProcessUpdates() snapshotSyncer.Start() @@ -2214,7 +2321,6 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", // There are several states that we consider "finished", run the test for each one. for _, finishPhase := range []k8sapi.PodPhase{k8sapi.PodSucceeded, k8sapi.PodFailed, "Terminating"} { - finishPhase := finishPhase It(fmt.Sprintf("should treat a finished Pod (%v) as a deletion", finishPhase), func() { pod, wepName := createPodAndMarkAsRunning("finished-pod-" + strings.ToLower(string(finishPhase))) var err error @@ -2237,7 +2343,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", }) var wepKV *model.KVPair - key := model.ResourceKey{Name: wepName, Namespace: "default", Kind: libapiv3.KindWorkloadEndpoint} + key := model.ResourceKey{Name: wepName, Namespace: "default", Kind: internalapi.KindWorkloadEndpoint} By("Checking the pod is visible before we mark it as finished", func() { // Perform a Get and ensure no error in the Calico API. var err error @@ -2246,7 +2352,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", Expect(wepKV).NotTo(BeNil()) // Perform List and ensure it shows up in the Calico API. - weps, err := c.List(ctx, model.ResourceListOptions{Kind: libapiv3.KindWorkloadEndpoint}, "") + weps, err := c.List(ctx, model.ResourceListOptions{Kind: internalapi.KindWorkloadEndpoint}, "") Expect(err).NotTo(HaveOccurred()) Expect(len(weps.KVPairs)).To(BeNumerically(">", 0)) }) @@ -2263,7 +2369,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", // can finish). wepKV, err = c.Get(ctx, key, "") Expect(err).NotTo(HaveOccurred()) - Expect(wepKV.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNetworks).NotTo(HaveLen(0)) + Expect(wepKV.Value.(*internalapi.WorkloadEndpoint).Spec.IPNetworks).NotTo(HaveLen(0)) // Deleting in the Calico API with incorrect UID should fail. realUID := wepKV.UID @@ -2274,14 +2380,14 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", wepKV.UID = realUID wepKV2, err := c.Get(ctx, key, "") Expect(err).NotTo(HaveOccurred()) - Expect(wepKV2.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNetworks).NotTo(HaveLen(0)) + Expect(wepKV2.Value.(*internalapi.WorkloadEndpoint).Spec.IPNetworks).NotTo(HaveLen(0)) // Successful deletion in the Calico API should make the IPs disappear. _, err = c.DeleteKVP(ctx, wepKV) Expect(err).NotTo(HaveOccurred()) wepKV2, err = c.Get(ctx, key, "") Expect(err).NotTo(HaveOccurred()) - Expect(wepKV2.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNetworks).To(HaveLen(0)) + Expect(wepKV2.Value.(*internalapi.WorkloadEndpoint).Spec.IPNetworks).To(HaveLen(0)) return } @@ -2361,12 +2467,12 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", By("Checking the pod is visible before we remove its IP", func() { // Perform a Get and ensure no error in the Calico API. - wep, err := c.Get(ctx, model.ResourceKey{Name: wepName, Namespace: "default", Kind: libapiv3.KindWorkloadEndpoint}, "") + wep, err := c.Get(ctx, model.ResourceKey{Name: wepName, Namespace: "default", Kind: internalapi.KindWorkloadEndpoint}, "") Expect(err).NotTo(HaveOccurred()) Expect(wep).NotTo(BeNil()) // Perform List and ensure it shows up in the Calico API. - weps, err := c.List(ctx, model.ResourceListOptions{Kind: libapiv3.KindWorkloadEndpoint}, "") + weps, err := c.List(ctx, model.ResourceListOptions{Kind: internalapi.KindWorkloadEndpoint}, "") Expect(err).NotTo(HaveOccurred()) Expect(len(weps.KVPairs)).To(BeNumerically(">", 0)) }) @@ -2415,12 +2521,12 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", // Add the IP via our API. This simulates what the CNI plugin does. wep, err := c.Get( ctx, - model.ResourceKey{Name: wepName, Namespace: "default", Kind: libapiv3.KindWorkloadEndpoint}, + model.ResourceKey{Name: wepName, Namespace: "default", Kind: internalapi.KindWorkloadEndpoint}, "", ) Expect(err).NotTo(HaveOccurred()) - wep.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNetworks = []string{"192.168.1.1"} - fmt.Printf("Updating Wep %+v\n", wep.Value.(*libapiv3.WorkloadEndpoint).Spec) + wep.Value.(*internalapi.WorkloadEndpoint).Spec.IPNetworks = []string{"192.168.1.1"} + fmt.Printf("Updating Wep %+v\n", wep.Value.(*internalapi.WorkloadEndpoint).Spec) ctxCNI := resources.ContextWithPatchMode(ctx, resources.PatchModeCNI) _, err = c.Update(ctxCNI, wep) Expect(err).NotTo(HaveOccurred()) @@ -2431,9 +2537,9 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", Expect(p.Annotations["cni.projectcalico.org/podIP"]).To(Equal("192.168.1.1")) // Get the wep through our API to check that the annotation round-trips. - wep, err = c.Get(ctx, model.ResourceKey{Name: wepName, Namespace: "default", Kind: libapiv3.KindWorkloadEndpoint}, "") + wep, err = c.Get(ctx, model.ResourceKey{Name: wepName, Namespace: "default", Kind: internalapi.KindWorkloadEndpoint}, "") Expect(err).NotTo(HaveOccurred()) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNetworks).To(ConsistOf("192.168.1.1/32")) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.IPNetworks).To(ConsistOf("192.168.1.1/32")) }) By("Setting the pod phase to Running", func() { @@ -2455,7 +2561,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", // Wait up to 120s for pod to start running. log.Warnf("[TEST] Waiting for pod %s to start", pod.ObjectMeta.Name) - for i := 0; i < 120; i++ { + for range 120 { p, err := c.ClientSet.CoreV1().Pods("default").Get(ctx, pod.ObjectMeta.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) if p.Status.Phase == k8sapi.PodRunning { @@ -2516,7 +2622,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", var nodename string By("Listing all Nodes to find a suitable Node name", func() { - nodes, err := c.List(ctx, model.ResourceListOptions{Kind: libapiv3.KindNode}, "") + nodes, err := c.List(ctx, model.ResourceListOptions{Kind: internalapi.KindNode}, "") Expect(err).NotTo(HaveOccurred()) kvp := *nodes.KVPairs[0] nodename = kvp.Key.(model.ResourceKey).Name @@ -2564,7 +2670,6 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", }) It("should support setting and getting FelixConfig", func() { - enabled := apiv3.FloatingIPsEnabled fc := &model.KVPair{ Key: model.ResourceKey{ Name: "myfelixconfig", @@ -2580,7 +2685,8 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", }, Spec: apiv3.FelixConfigurationSpec{ InterfacePrefix: "xali-", - FloatingIPs: &enabled, + FloatingIPs: ptr.To(apiv3.FloatingIPsEnabled), + NFTablesMode: ptr.To(apiv3.NFTablesModeAuto), }, }, } @@ -2591,16 +2697,20 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", updFC, err = c.Create(ctx, fc) Expect(err).NotTo(HaveOccurred()) Expect(updFC.Key.(model.ResourceKey).Name).To(Equal("myfelixconfig")) + // Set the ResourceVersion (since it is auto populated by the Kubernetes datastore) to make it easier to compare objects. Expect(fc.Value.(*apiv3.FelixConfiguration).GetObjectMeta().GetResourceVersion()).To(Equal("")) fc.Value.(*apiv3.FelixConfiguration).GetObjectMeta().SetResourceVersion(updFC.Value.(*apiv3.FelixConfiguration).GetObjectMeta().GetResourceVersion()) - // UID and CreationTimestamp are auto-generated, make sure we don't fail the assertion based on it. + // UID, CreationTimestamp, ManagedFields are auto-generated or filled in by the server, make sure we don't fail the assertion based on it. fc.Value.(*apiv3.FelixConfiguration).ObjectMeta.UID = updFC.Value.(*apiv3.FelixConfiguration).ObjectMeta.UID fc.Value.(*apiv3.FelixConfiguration).ObjectMeta.CreationTimestamp = updFC.Value.(*apiv3.FelixConfiguration).ObjectMeta.CreationTimestamp + fc.Value.(*apiv3.FelixConfiguration).ObjectMeta.ManagedFields = updFC.Value.(*apiv3.FelixConfiguration).ObjectMeta.ManagedFields + fc.Value.(*apiv3.FelixConfiguration).ObjectMeta.Generation = updFC.Value.(*apiv3.FelixConfiguration).ObjectMeta.Generation // Assert the created object matches what we created. - Expect(updFC.Value.(*apiv3.FelixConfiguration)).To(Equal(fc.Value.(*apiv3.FelixConfiguration))) + Expect(updFC.Value.(*apiv3.FelixConfiguration).Spec).To(Equal(fc.Value.(*apiv3.FelixConfiguration).Spec)) + Expect(updFC.Value.(*apiv3.FelixConfiguration).ObjectMeta).To(Equal(fc.Value.(*apiv3.FelixConfiguration).ObjectMeta)) Expect(updFC.Revision).NotTo(BeNil()) // Unset the ResourceVersion for the original resource since we modified it just for the sake of comparing in the tests. @@ -2755,7 +2865,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", ip := "192.168.0.101" By("Listing all Nodes", func() { - nodes, err := c.List(ctx, model.ResourceListOptions{Kind: libapiv3.KindNode}, "") + nodes, err := c.List(ctx, model.ResourceListOptions{Kind: internalapi.KindNode}, "") Expect(err).NotTo(HaveOccurred()) // Get the hostname so we can make a Get call kvp = *nodes.KVPairs[0] @@ -2763,7 +2873,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", }) By("Listing a specific Node", func() { - nodes, err := c.List(ctx, model.ResourceListOptions{Name: nodeHostname, Kind: libapiv3.KindNode}, "") + nodes, err := c.List(ctx, model.ResourceListOptions{Name: nodeHostname, Kind: internalapi.KindNode}, "") Expect(err).NotTo(HaveOccurred()) Expect(nodes.KVPairs).To(HaveLen(1)) Expect(nodes.KVPairs[0].Key).To(Equal(kvp.Key)) @@ -2771,13 +2881,13 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", }) By("Listing a specific invalid Node", func() { - nodes, err := c.List(ctx, model.ResourceListOptions{Name: "foobarbaz-node", Kind: libapiv3.KindNode}, "") + nodes, err := c.List(ctx, model.ResourceListOptions{Name: "foobarbaz-node", Kind: internalapi.KindNode}, "") Expect(err).NotTo(HaveOccurred()) Expect(nodes.KVPairs).To(HaveLen(0)) }) By("Getting a specific nodeHostname", func() { - n, err := c.Get(ctx, model.ResourceKey{Name: nodeHostname, Kind: libapiv3.KindNode}, "") + n, err := c.Get(ctx, model.ResourceKey{Name: nodeHostname, Kind: internalapi.KindNode}, "") Expect(err).NotTo(HaveOccurred()) // Check to see we have the right Node @@ -2790,7 +2900,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", }) By("Getting nonexistent Node", func() { - _, err := c.Get(ctx, model.ResourceKey{Name: "Fake", Kind: libapiv3.KindNode}, "") + _, err := c.Get(ctx, model.ResourceKey{Name: "Fake", Kind: internalapi.KindNode}, "") Expect(err).To(HaveOccurred()) }) @@ -2805,14 +2915,14 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", testKvp := model.KVPair{ Key: model.ResourceKey{ Name: kvp.Key.(model.ResourceKey).Name, - Kind: libapiv3.KindNode, + Kind: internalapi.KindNode, }, - Value: &libapiv3.Node{ + Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: kvp.Key.(model.ResourceKey).Name, }, - Spec: libapiv3.NodeSpec{ - BGP: &libapiv3.NodeBGPSpec{ + Spec: internalapi.NodeSpec{ + BGP: &internalapi.NodeBGPSpec{ ASNumber: &newAsn, IPv4Address: ip, }, @@ -2821,20 +2931,20 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", } node, err := c.Update(ctx, &testKvp) Expect(err).NotTo(HaveOccurred()) - Expect(*node.Value.(*libapiv3.Node).Spec.BGP.ASNumber).To(Equal(newAsn)) + Expect(*node.Value.(*internalapi.Node).Spec.BGP.ASNumber).To(Equal(newAsn)) // Also check that Get() returns the changes getNode, err := c.Get(ctx, kvp.Key.(model.ResourceKey), "") Expect(err).NotTo(HaveOccurred()) - Expect(*getNode.Value.(*libapiv3.Node).Spec.BGP.ASNumber).To(Equal(newAsn)) - Expect(getNode.Value.(*libapiv3.Node).Spec.BGP.IPv4IPIPTunnelAddr).To(Equal("")) + Expect(*getNode.Value.(*internalapi.Node).Spec.BGP.ASNumber).To(Equal(newAsn)) + Expect(getNode.Value.(*internalapi.Node).Spec.BGP.IPv4IPIPTunnelAddr).To(Equal("")) // We do not support creating Nodes, we should see an error // if the Node does not exist. missingKvp := model.KVPair{ Key: model.ResourceKey{ Name: "IDontExist", - Kind: libapiv3.KindNode, + Kind: internalapi.KindNode, }, } _, err = c.Create(ctx, &missingKvp) @@ -2846,14 +2956,14 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", testKvp := model.KVPair{ Key: model.ResourceKey{ Name: kvp.Key.(model.ResourceKey).Name, - Kind: libapiv3.KindNode, + Kind: internalapi.KindNode, }, - Value: &libapiv3.Node{ + Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Name: kvp.Key.(model.ResourceKey).Name, }, - Spec: libapiv3.NodeSpec{ - BGP: &libapiv3.NodeBGPSpec{ + Spec: internalapi.NodeSpec{ + BGP: &internalapi.NodeBGPSpec{ IPv4Address: ip, IPv4IPIPTunnelAddr: "10.0.0.1", }, @@ -2863,14 +2973,14 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", node, err := c.Update(ctx, &testKvp) Expect(err).NotTo(HaveOccurred()) - Expect(node.Value.(*libapiv3.Node).Spec.BGP.ASNumber).To(BeNil()) - Expect(node.Value.(*libapiv3.Node).Spec.BGP.IPv4IPIPTunnelAddr).To(Equal("10.0.0.1")) + Expect(node.Value.(*internalapi.Node).Spec.BGP.ASNumber).To(BeNil()) + Expect(node.Value.(*internalapi.Node).Spec.BGP.IPv4IPIPTunnelAddr).To(Equal("10.0.0.1")) // Also check that Get() returns the changes getNode, err := c.Get(ctx, kvp.Key.(model.ResourceKey), "") Expect(err).NotTo(HaveOccurred()) - Expect(getNode.Value.(*libapiv3.Node).Spec.BGP.ASNumber).To(BeNil()) - Expect(getNode.Value.(*libapiv3.Node).Spec.BGP.IPv4IPIPTunnelAddr).To(Equal("10.0.0.1")) + Expect(getNode.Value.(*internalapi.Node).Spec.BGP.ASNumber).To(BeNil()) + Expect(getNode.Value.(*internalapi.Node).Spec.BGP.IPv4IPIPTunnelAddr).To(Equal("10.0.0.1")) }) By("Syncing HostIPs over the Syncer", func() { @@ -2883,8 +2993,10 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", }) By("Not syncing Nodes when K8sDisableNodePoll is enabled", func() { - cfg := apiconfig.KubeConfig{Kubeconfig: "/kubeconfig.yaml", K8sDisableNodePoll: true} - _, snapshotCallbacks, snapshotSyncer := CreateClientAndSyncer(cfg) + // Make a copy of the given config, modify it to disable Node polling. + cfg := cfg + cfg.Spec.KubeConfig.K8sDisableNodePoll = true + _, snapshotCallbacks, snapshotSyncer := CreateClientAndSyncer(cfg.Spec.KubeConfig) defer snapshotSyncer.Stop() go snapshotCallbacks.ProcessUpdates() snapshotSyncer.Start() @@ -2898,8 +3010,10 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", }) By("Syncing HostConfig for a Node on Syncer start", func() { - cfg := apiconfig.KubeConfig{Kubeconfig: "/kubeconfig.yaml", K8sDisableNodePoll: true} - _, snapshotCallbacks, snapshotSyncer := CreateClientAndSyncer(cfg) + // Make a copy of the given config, modify it to disable Node polling. + cfg := cfg + cfg.Spec.KubeConfig.K8sDisableNodePoll = true + _, snapshotCallbacks, snapshotSyncer := CreateClientAndSyncer(cfg.Spec.KubeConfig) defer snapshotSyncer.Stop() go snapshotCallbacks.ProcessUpdates() snapshotSyncer.Start() @@ -2922,21 +3036,30 @@ var _ = testutils.E2eDatastoreDescribe("Test Syncer API for Kubernetes backend", var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.DatastoreK8s, func(cfg apiconfig.CalicoAPIConfig) { var ( - c *KubeClient - anpClient *adminpolicyclient.PolicyV1alpha1Client - ctx context.Context + c *KubeClient + kcnpClient *netpolicyclient.PolicyV1alpha2Client + ctx context.Context ) BeforeEach(func() { - // Create a client + v3CRD = UsingV3CRDs(&cfg.Spec) + + // Create a client, with a high QPS limit to avoid throttling during tests + // that create lots of objects. + cfg.Spec.KubeConfig.K8sClientQPS = 1000 client, err := NewKubeClient(&cfg.Spec) Expect(err).NotTo(HaveOccurred()) c = client.(*KubeClient) config, _, err := CreateKubernetesClientset(&cfg.Spec) Expect(err).NotTo(HaveOccurred()) + + // Increase the QPS to avoid throttling during tests, as some of the tests + // perform a lot of API calls rapidly. + config.QPS = 100 + config.Burst = 1000 config.ContentType = runtime.ContentTypeJSON - anpClient, err = buildK8SAdminPolicyClient(config) + kcnpClient, err = clusterNetworkPolicyClient(config) Expect(err).NotTo(HaveOccurred()) ctx = context.Background() @@ -2958,18 +3081,22 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore _, err := c.ClientSet.CoreV1().ServiceAccounts("default").Create(ctx, &sa, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) } + deleteAllServiceAccounts := func() { var zero int64 err := c.ClientSet.CoreV1().ServiceAccounts("default").DeleteCollection(ctx, metav1.DeleteOptions{GracePeriodSeconds: &zero}, metav1.ListOptions{}) Expect(err).NotTo(HaveOccurred()) } + BeforeEach(func() { createTestServiceAccount("test-sa-1") createTestServiceAccount("test-sa-2") }) + AfterEach(func() { deleteAllServiceAccounts() }) + It("supports watching a specific profile (from namespace)", func() { watch, err := c.Watch(ctx, model.ResourceListOptions{Name: "kns.default", Kind: apiv3.KindProfile}, api.WatchOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -2977,6 +3104,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore event := ExpectAddedEvent(watch.ResultChan()) Expect(event.New.Key.String()).To(Equal("Profile(kns.default)")) }) + It("supports watching a specific profile (from serviceAccount)", func() { watch, err := c.Watch(ctx, model.ResourceListOptions{Name: "ksa.default.test-sa-1", Kind: apiv3.KindProfile}, api.WatchOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -2984,12 +3112,14 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore event := ExpectAddedEvent(watch.ResultChan()) Expect(event.New.Key.String()).To(Equal("Profile(ksa.default.test-sa-1)")) }) + It("supports watching all profiles", func() { watch, err := c.Watch(ctx, model.ResourceListOptions{Kind: apiv3.KindProfile}, api.WatchOptions{}) Expect(err).NotTo(HaveOccurred()) defer watch.Stop() ExpectAddedEvent(watch.ResultChan()) }) + It("rejects names without prefixes", func() { _, err := c.Watch(ctx, model.ResourceListOptions{Name: "default", Kind: apiv3.KindProfile}, api.WatchOptions{}) Expect(err).To(HaveOccurred()) @@ -2997,15 +3127,16 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore }) }) - Describe("watching AdminNetworkPolicies", func() { - createTestAdminNetworkPolicy := func(name string) { - anp := &adminpolicy.AdminNetworkPolicy{ + Describe("watching ClusterNetworkPolicies", func() { + createTestClusterNetworkPolicy := func(name string, tier clusternetpolicy.Tier) { + cnp := &clusternetpolicy.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: name, }, - Spec: adminpolicy.AdminNetworkPolicySpec{ + Spec: clusternetpolicy.ClusterNetworkPolicySpec{ + Tier: tier, Priority: 100, - Subject: adminpolicy.AdminNetworkPolicySubject{ + Subject: clusternetpolicy.ClusterNetworkPolicySubject{ Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ "label": "value", @@ -3014,12 +3145,12 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore }, }, } - _, err := anpClient.AdminNetworkPolicies().Create(ctx, anp, metav1.CreateOptions{}) + _, err := kcnpClient.ClusterNetworkPolicies().Create(ctx, cnp, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) } - deleteAllAdminNetworkPolicies := func() { + deleteAllClusterNetworkPolicies := func() { var zero int64 - err := anpClient.AdminNetworkPolicies().DeleteCollection( + err := kcnpClient.ClusterNetworkPolicies().DeleteCollection( ctx, metav1.DeleteOptions{GracePeriodSeconds: &zero}, metav1.ListOptions{}, @@ -3027,35 +3158,44 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore Expect(err).NotTo(HaveOccurred()) } BeforeEach(func() { - createTestAdminNetworkPolicy("test-admin-net-policy-1") - createTestAdminNetworkPolicy("test-admin-net-policy-2") + createTestClusterNetworkPolicy("test-cluster-net-policy-1", clusternetpolicy.AdminTier) + createTestClusterNetworkPolicy("test-cluster-net-policy-2", clusternetpolicy.BaselineTier) }) AfterEach(func() { - deleteAllAdminNetworkPolicies() + deleteAllClusterNetworkPolicies() }) - It("supports watching all adminnetworkpolicies", func() { - watch, err := c.Watch(ctx, model.ResourceListOptions{Kind: model.KindKubernetesAdminNetworkPolicy}, api.WatchOptions{Revision: ""}) + It("supports watching all k8s cluster network policies", func() { + watch, err := c.Watch(ctx, model.ResourceListOptions{Kind: model.KindKubernetesClusterNetworkPolicy}, api.WatchOptions{Revision: ""}) Expect(err).NotTo(HaveOccurred()) defer watch.Stop() ExpectAddedEvent(watch.ResultChan()) }) It("supports resuming watch from previous revision", func() { - watch, err := c.Watch(ctx, model.ResourceListOptions{Kind: model.KindKubernetesAdminNetworkPolicy}, api.WatchOptions{Revision: ""}) + watch, err := c.Watch(ctx, model.ResourceListOptions{Kind: model.KindKubernetesClusterNetworkPolicy}, api.WatchOptions{Revision: ""}) Expect(err).NotTo(HaveOccurred()) event := ExpectAddedEvent(watch.ResultChan()) watch.Stop() - watch, err = c.Watch(ctx, model.ResourceListOptions{Kind: model.KindKubernetesAdminNetworkPolicy}, api.WatchOptions{Revision: event.New.Revision}) + watch, err = c.Watch(ctx, model.ResourceListOptions{Kind: model.KindKubernetesClusterNetworkPolicy}, api.WatchOptions{Revision: event.New.Revision}) Expect(err).NotTo(HaveOccurred()) watch.Stop() }) - It("should handle a list for many network policies with a revision", func() { + It("should handle a list for many cluster network policies in Admin tier with a revision", func() { for i := 3; i < 1000; i++ { - createTestAdminNetworkPolicy(fmt.Sprintf("test-admin-net-policy-%d", i)) + createTestClusterNetworkPolicy(fmt.Sprintf("test-cluster-net-policy-%d", i), clusternetpolicy.AdminTier) } - kvs, err := c.List(ctx, model.ResourceListOptions{Kind: model.KindKubernetesAdminNetworkPolicy}, "") + kvs, err := c.List(ctx, model.ResourceListOptions{Kind: model.KindKubernetesClusterNetworkPolicy}, "") Expect(err).NotTo(HaveOccurred()) - _, err = c.List(ctx, model.ResourceListOptions{Kind: model.KindKubernetesAdminNetworkPolicy}, kvs.Revision) + _, err = c.List(ctx, model.ResourceListOptions{Kind: model.KindKubernetesClusterNetworkPolicy}, kvs.Revision) + Expect(err).NotTo(HaveOccurred()) + }) + It("should handle a list for many cluster network policies in Baseline tier with a revision", func() { + for i := 3; i < 1000; i++ { + createTestClusterNetworkPolicy(fmt.Sprintf("test-cluster-net-policy-%d", i), clusternetpolicy.BaselineTier) + } + kvs, err := c.List(ctx, model.ResourceListOptions{Kind: model.KindKubernetesClusterNetworkPolicy}, "") + Expect(err).NotTo(HaveOccurred()) + _, err = c.List(ctx, model.ResourceListOptions{Kind: model.KindKubernetesClusterNetworkPolicy}, kvs.Revision) Expect(err).NotTo(HaveOccurred()) }) }) @@ -3162,15 +3302,16 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore }) }) - Describe("watching / listing network polices (k8s and Calico) and admin network policies", func() { - createTestAdminNetworkPolicy := func(name string) { - anp := &adminpolicy.AdminNetworkPolicy{ + Describe("watching / listing network polices (k8s and Calico) and cluster network policies", func() { + createTestClusterNetworkPolicy := func(name string) { + cnp := &clusternetpolicy.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: name, }, - Spec: adminpolicy.AdminNetworkPolicySpec{ + Spec: clusternetpolicy.ClusterNetworkPolicySpec{ + Tier: clusternetpolicy.AdminTier, Priority: 100, - Subject: adminpolicy.AdminNetworkPolicySubject{ + Subject: clusternetpolicy.ClusterNetworkPolicySubject{ Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ "label": "value", @@ -3179,12 +3320,12 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore }, }, } - _, err := anpClient.AdminNetworkPolicies().Create(ctx, anp, metav1.CreateOptions{}) + _, err := kcnpClient.ClusterNetworkPolicies().Create(ctx, cnp, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) } - deleteAllAdminNetworkPolicies := func() { + deleteAllClusterNetworkPolicies := func() { var zero int64 - err := anpClient.AdminNetworkPolicies().DeleteCollection( + err := kcnpClient.ClusterNetworkPolicies().DeleteCollection( ctx, metav1.DeleteOptions{GracePeriodSeconds: &zero}, metav1.ListOptions{}, @@ -3229,9 +3370,9 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore Expect(err).NotTo(HaveOccurred()) } BeforeEach(func() { - // Create 2x Calico NP and 2x k8s NP and 2x k8s ANP - createTestAdminNetworkPolicy("test-admin-net-policy-1") - createTestAdminNetworkPolicy("test-admin-net-policy-2") + // Create 2x Calico NP and 2x k8s NP,and 2x k8s CNP + createTestClusterNetworkPolicy("test-cluster-net-policy-1") + createTestClusterNetworkPolicy("test-cluster-net-policy-2") createCalicoNetworkPolicy("test-net-policy-1") createCalicoNetworkPolicy("test-net-policy-2") createK8sNetworkPolicy("test-net-policy-3") @@ -3241,7 +3382,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore AfterEach(func() { log.Info("[Test] Beginning Cleanup ----") deleteAllNetworkPolicies() - deleteAllAdminNetworkPolicies() + deleteAllClusterNetworkPolicies() }) It("supports resuming watch from previous revision (calico)", func() { @@ -3323,7 +3464,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore // Modify the kubernetes policies found := 0 for _, kvp := range l.KVPairs { - name := strings.TrimPrefix(kvp.Value.(*apiv3.NetworkPolicy).Name, "knp.default.") + name := kvp.Value.(*apiv3.NetworkPolicy).Name p, err := c.ClientSet.NetworkingV1().NetworkPolicies("default").Get(ctx, name, metav1.GetOptions{}) Expect(err).ToNot(HaveOccurred()) p.SetLabels(map[string]string{"test": "00"}) @@ -3348,7 +3489,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore // Make a second change to one of the NPs for _, kvp := range l.KVPairs { - name := strings.TrimPrefix(kvp.Value.(*apiv3.NetworkPolicy).Name, "knp.default.") + name := kvp.Value.(*apiv3.NetworkPolicy).Name p, err := c.ClientSet.NetworkingV1().NetworkPolicies("default").Get(ctx, name, metav1.GetOptions{}) Expect(err).ToNot(HaveOccurred()) p.SetLabels(map[string]string{"test": "01"}) @@ -3370,9 +3511,9 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore watch.Stop() }) - It("supports resuming watch from previous revision k8s admin network policy", func() { - // Should only return k8s ANPs - l, err := c.List(ctx, model.ResourceListOptions{Kind: model.KindKubernetesAdminNetworkPolicy}, "") + It("supports resuming watch from previous revision k8s cluster network policy", func() { + // Should only return k8s CNPs + l, err := c.List(ctx, model.ResourceListOptions{Kind: model.KindKubernetesClusterNetworkPolicy}, "") Expect(err).NotTo(HaveOccurred()) Expect(l.KVPairs).To(HaveLen(2)) @@ -3389,18 +3530,18 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore // Modify the kubernetes policies found := 0 for _, kvp := range l.KVPairs { - name := strings.TrimPrefix(kvp.Value.(*apiv3.GlobalNetworkPolicy).Name, "kanp.adminnetworkpolicy.") - p, err := anpClient.AdminNetworkPolicies().Get(ctx, name, metav1.GetOptions{}) + name := strings.TrimPrefix(kvp.Value.(*apiv3.GlobalNetworkPolicy).Name, "kcnp.kube-admin.") + p, err := kcnpClient.ClusterNetworkPolicies().Get(ctx, name, metav1.GetOptions{}) Expect(err).ToNot(HaveOccurred()) p.SetLabels(map[string]string{"test": "00"}) - _, err = anpClient.AdminNetworkPolicies().Update(ctx, p, metav1.UpdateOptions{}) + _, err = kcnpClient.ClusterNetworkPolicies().Update(ctx, p, metav1.UpdateOptions{}) Expect(err).ToNot(HaveOccurred()) found++ } Expect(found).To(Equal(2)) log.WithField("revision", l.Revision).Info("[TEST] first watch") - watch, err := c.Watch(ctx, model.ResourceListOptions{Kind: model.KindKubernetesAdminNetworkPolicy}, api.WatchOptions{Revision: l.Revision}) + watch, err := c.Watch(ctx, model.ResourceListOptions{Kind: model.KindKubernetesClusterNetworkPolicy}, api.WatchOptions{Revision: l.Revision}) Expect(err).NotTo(HaveOccurred()) event := ExpectModifiedEvent(watch.ResultChan()) @@ -3412,20 +3553,20 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore Expect(watch.ResultChan()).ToNot(Receive()) watch.Stop() - // Make a second change to one of the NPs + // Make a second change to one of the k8s CNPs for _, kvp := range l.KVPairs { - name := strings.TrimPrefix(kvp.Value.(*apiv3.GlobalNetworkPolicy).Name, "kanp.adminnetworkpolicy.") - p, err := anpClient.AdminNetworkPolicies().Get(ctx, name, metav1.GetOptions{}) + name := strings.TrimPrefix(kvp.Value.(*apiv3.GlobalNetworkPolicy).Name, "kcnp.kube-admin.") + p, err := kcnpClient.ClusterNetworkPolicies().Get(ctx, name, metav1.GetOptions{}) Expect(err).ToNot(HaveOccurred()) p.SetLabels(map[string]string{"test": "01"}) - _, err = anpClient.AdminNetworkPolicies().Update(ctx, p, metav1.UpdateOptions{}) + _, err = kcnpClient.ClusterNetworkPolicies().Update(ctx, p, metav1.UpdateOptions{}) Expect(err).ToNot(HaveOccurred()) break } // Resume watching at the revision of the event we got log.WithField("revision", event.New.Revision).Info("second watch") - watch, err = c.Watch(ctx, model.ResourceListOptions{Kind: model.KindKubernetesAdminNetworkPolicy}, api.WatchOptions{Revision: event.New.Revision}) + watch, err = c.Watch(ctx, model.ResourceListOptions{Kind: model.KindKubernetesClusterNetworkPolicy}, api.WatchOptions{Revision: event.New.Revision}) Expect(err).NotTo(HaveOccurred()) // We should only get 1 update, because the event from the previous watch should have been "latest" @@ -3443,7 +3584,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore Expect(l.KVPairs).To(HaveLen(2)) // Watch from part way - for i := 0; i < 2; i++ { + for i := range 2 { revision := l.KVPairs[i].Revision log.WithFields(log.Fields{ "revision": revision, @@ -3465,7 +3606,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore Expect(l.KVPairs).To(HaveLen(2)) // Watch from part way - for i := 0; i < 2; i++ { + for i := range 2 { revision := l.KVPairs[i].Revision log.WithFields(log.Fields{ "revision": revision, @@ -3480,14 +3621,14 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore } }) - It("supports watching from part way through a list of Admin Network Policies", func() { - // Only 2 k8s ANPs - l, err := c.List(ctx, model.ResourceListOptions{Kind: model.KindKubernetesAdminNetworkPolicy}, "") + It("supports watching from part way through a list of Cluster Network Policies", func() { + // Only 2 k8s CNPs + l, err := c.List(ctx, model.ResourceListOptions{Kind: model.KindKubernetesClusterNetworkPolicy}, "") Expect(err).ToNot(HaveOccurred()) Expect(l.KVPairs).To(HaveLen(2)) // Watch from part way - for i := 0; i < 2; i++ { + for i := range 2 { revision := l.KVPairs[i].Revision log.WithFields(log.Fields{ "revision": revision, @@ -3587,19 +3728,19 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore deleteAllPods() }) It("supports watching a specific workloadEndpoint", func() { - watch, err := c.Watch(ctx, model.ResourceListOptions{Name: "127.0.0.1-k8s-test--pod--1-eth0", Namespace: "default", Kind: libapiv3.KindWorkloadEndpoint}, api.WatchOptions{Revision: ""}) + watch, err := c.Watch(ctx, model.ResourceListOptions{Name: "127.0.0.1-k8s-test--pod--1-eth0", Namespace: "default", Kind: internalapi.KindWorkloadEndpoint}, api.WatchOptions{Revision: ""}) Expect(err).NotTo(HaveOccurred()) defer watch.Stop() event := ExpectAddedEvent(watch.ResultChan()) Expect(event.New.Key.String()).To(Equal("WorkloadEndpoint(default/127.0.0.1-k8s-test--pod--1-eth0)")) }) It("rejects watching a specific workloadEndpoint without a namespace", func() { - _, err := c.Watch(ctx, model.ResourceListOptions{Name: "127.0.0.1-k8s-test--pod--1-eth0", Kind: libapiv3.KindWorkloadEndpoint}, api.WatchOptions{Revision: ""}) + _, err := c.Watch(ctx, model.ResourceListOptions{Name: "127.0.0.1-k8s-test--pod--1-eth0", Kind: internalapi.KindWorkloadEndpoint}, api.WatchOptions{Revision: ""}) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(Equal("cannot watch a specific WorkloadEndpoint without a namespace")) }) It("supports watching all workloadEndpoints", func() { - watch, err := c.Watch(ctx, model.ResourceListOptions{Kind: libapiv3.KindWorkloadEndpoint}, api.WatchOptions{Revision: ""}) + watch, err := c.Watch(ctx, model.ResourceListOptions{Kind: internalapi.KindWorkloadEndpoint}, api.WatchOptions{Revision: ""}) Expect(err).NotTo(HaveOccurred()) defer watch.Stop() ExpectAddedEvent(watch.ResultChan()) @@ -3609,18 +3750,19 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore It("Should support watching Nodes", func() { By("Watching a single node", func() { name := "127.0.0.1" // Node created by test/mock-node.yaml - watch, err := c.Watch(ctx, model.ResourceListOptions{Name: name, Kind: libapiv3.KindNode}, api.WatchOptions{Revision: ""}) + watch, err := c.Watch(ctx, model.ResourceListOptions{Name: name, Kind: internalapi.KindNode}, api.WatchOptions{Revision: ""}) Expect(err).NotTo(HaveOccurred()) defer watch.Stop() // We should get at least one event from the watch. var receivedEvent bool - for i := 0; i < 10; i++ { + for range 10 { select { case e := <-watch.ResultChan(): // Got an event. Check it's OK. - Expect(e.Error).NotTo(HaveOccurred()) - Expect(e.Type).To(Equal(api.WatchAdded)) + Expect(e.Error).NotTo(HaveOccurred(), fmt.Sprintf("Got: %s", e)) + // Nodes get updated out-of-band by k8s, so we may see created or modified here. + Expect(e.Type).To(Or(Equal(api.WatchAdded), Equal(api.WatchModified)), fmt.Sprintf("Got: %s", e)) receivedEvent = true break default: @@ -3631,18 +3773,18 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore }) By("Watching all nodes", func() { - watch, err := c.Watch(ctx, model.ResourceListOptions{Kind: libapiv3.KindNode}, api.WatchOptions{Revision: ""}) + watch, err := c.Watch(ctx, model.ResourceListOptions{Kind: internalapi.KindNode}, api.WatchOptions{Revision: ""}) Expect(err).NotTo(HaveOccurred()) defer watch.Stop() // We should get at least one event from the watch. var receivedEvent bool - for i := 0; i < 10; i++ { + for range 10 { select { case e := <-watch.ResultChan(): // Got an event. Check it's OK. - Expect(e.Error).NotTo(HaveOccurred()) - Expect(e.Type).To(Equal(api.WatchAdded)) + Expect(e.Error).NotTo(HaveOccurred(), fmt.Sprintf("Got: %s", e)) + Expect(e.Type).To(Or(Equal(api.WatchAdded), Equal(api.WatchModified))) receivedEvent = true break default: @@ -3672,7 +3814,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore // We should get at least one event from the watch. var receivedEvent bool - for i := 0; i < 10; i++ { + for range 10 { select { case e := <-watch.ResultChan(): // Got an event. Check it's OK. @@ -3710,24 +3852,14 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore Expect(err).NotTo(HaveOccurred()) // We should get at least one event from the watch. - var receivedEvent bool - for i := 0; i < 10; i++ { - select { - case e := <-watch.ResultChan(): - // Got an event. Check it's OK. - Expect(e.Error).NotTo(HaveOccurred()) - Expect(e.Type).To(Equal(api.WatchAdded)) - receivedEvent = true - break - default: - time.Sleep(50 * time.Millisecond) - } - } - Expect(receivedEvent).To(BeTrue(), "Did not receive watch event") + var e api.WatchEvent + Eventually(watch.ResultChan(), "5s", "100ms").Should(Receive(&e), "Did not receive watch event") + Expect(e.Error).NotTo(HaveOccurred()) + Expect(e.Type).To(Equal(api.WatchAdded)) }) }) - It("should handle a CRUD of IPAM Config (v1 format)", func() { + It("should handle a CRUD of IPAMConfig (v1 format)", func() { ipamKVP := &model.KVPair{ Key: model.IPAMConfigKey{}, Value: &model.IPAMConfig{ @@ -3737,10 +3869,10 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore } v3Key := model.ResourceKey{ Name: "default", - Kind: "IPAMConfig", + Kind: "IPAMConfiguration", } - By("Creating an IPAM Config", func() { + By("Creating an IPAMConfig", func() { kvpRes, err := c.Create(ctx, ipamKVP) Expect(err).NotTo(HaveOccurred()) Expect(kvpRes.Value).To(Equal(ipamKVP.Value)) @@ -3748,16 +3880,19 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore var createdAt metav1.Time var uid types.UID + By("Reading it with the v3 client and checking metadata", func() { + // We should get an IPAMConfiguration and not an IPAMConfig since we're + // using the v3 key. v3Res, err := c.Get(ctx, v3Key, "") Expect(err).NotTo(HaveOccurred()) - createdAt = v3Res.Value.(*libapiv3.IPAMConfig).CreationTimestamp - uid = v3Res.Value.(*libapiv3.IPAMConfig).UID + createdAt = v3Res.Value.(*apiv3.IPAMConfiguration).CreationTimestamp + uid = v3Res.Value.(*apiv3.IPAMConfiguration).UID Expect(createdAt).NotTo(Equal(metav1.Time{})) Expect(uid).NotTo(Equal("")) }) - By("Reading and updating an IPAM Config", func() { + By("Reading and updating an IPAMConfig", func() { kvpRes, err := c.Get(ctx, ipamKVP.Key, "") Expect(err).NotTo(HaveOccurred()) Expect(kvpRes.Value).To(Equal(ipamKVP.Value)) @@ -3772,64 +3907,71 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore }) By("Reading it with the v3 client and checking metadata hasn't changed", func() { + // We should get an IPAMConfiguration and not an IPAMConfig since we're + // using the v3 key. v3Res, err := c.Get(ctx, v3Key, "") Expect(err).NotTo(HaveOccurred()) - Expect(v3Res.Value.(*libapiv3.IPAMConfig).CreationTimestamp).To(Equal(createdAt)) - Expect(v3Res.Value.(*libapiv3.IPAMConfig).UID).To(Equal(uid)) + Expect(v3Res.Value.(*apiv3.IPAMConfiguration).CreationTimestamp).To(Equal(createdAt)) + Expect(v3Res.Value.(*apiv3.IPAMConfiguration).UID).To(Equal(uid)) }) - By("Deleting an IPAM Config", func() { + By("Deleting an IPAMConfig", func() { _, err := c.Delete(ctx, ipamKVP.Key, "") Expect(err).NotTo(HaveOccurred()) }) }) - It("should handle a CRUD of IPAM config (v3 format)", func() { - ipamKVP := &model.KVPair{ + It("should handle a CRUD of IPAMConfiguration (v3 format)", func() { + v3KVP := &model.KVPair{ Key: model.ResourceKey{ Name: "default", - Kind: "IPAMConfig", + Kind: "IPAMConfiguration", }, - Value: &libapiv3.IPAMConfig{ + Value: &apiv3.IPAMConfiguration{ TypeMeta: metav1.TypeMeta{ - Kind: "IPAMConfig", + Kind: "IPAMConfiguration", APIVersion: "projectcalico.org/v3", }, ObjectMeta: metav1.ObjectMeta{ Name: "default", }, - Spec: libapiv3.IPAMConfigSpec{ + Spec: apiv3.IPAMConfigurationSpec{ StrictAffinity: false, AutoAllocateBlocks: true, }, }, } - kvpRes, err := c.Create(ctx, ipamKVP) - By("Creating an IPAM Config", func() { + kvpRes, err := c.Create(ctx, v3KVP) + By("Creating an IPAMConfiguration", func() { Expect(err).NotTo(HaveOccurred()) - Expect(kvpRes.Value.(*libapiv3.IPAMConfig).Spec).To(Equal(ipamKVP.Value.(*libapiv3.IPAMConfig).Spec)) + Expect(kvpRes.Value.(*apiv3.IPAMConfiguration).Spec).To(Equal(v3KVP.Value.(*apiv3.IPAMConfiguration).Spec)) }) By("Expecting the creation timestamp to be set") - createdAt := kvpRes.Value.(*libapiv3.IPAMConfig).CreationTimestamp + createdAt := kvpRes.Value.(*apiv3.IPAMConfiguration).CreationTimestamp Expect(createdAt).NotTo(Equal(metav1.Time{})) - By("Reading and updating an IPAM Config", func() { - kvpRes, err := c.Get(ctx, ipamKVP.Key, "") + By("Reading and updating an IPAMConfiguration", func() { + kvpRes, err := c.Get(ctx, v3KVP.Key, "") Expect(err).NotTo(HaveOccurred()) - Expect(kvpRes.Value.(*libapiv3.IPAMConfig).Spec).To(Equal(ipamKVP.Value.(*libapiv3.IPAMConfig).Spec)) - kvpRes.Value.(*libapiv3.IPAMConfig).Spec.StrictAffinity = true - kvpRes.Value.(*libapiv3.IPAMConfig).Spec.AutoAllocateBlocks = false + // Expect the spec to match the original. + Expect(kvpRes.Value.(*apiv3.IPAMConfiguration).Spec).To(Equal(v3KVP.Value.(*apiv3.IPAMConfiguration).Spec)) + + // Prepare to update the IPAMConfiguration + kvpRes.Value.(*apiv3.IPAMConfiguration).Spec.StrictAffinity = true + kvpRes.Value.(*apiv3.IPAMConfiguration).Spec.AutoAllocateBlocks = false + kvpRes2, err := c.Update(ctx, kvpRes) Expect(err).NotTo(HaveOccurred()) - Expect(kvpRes2.Value.(*libapiv3.IPAMConfig).Spec).NotTo(Equal(ipamKVP.Value.(*libapiv3.IPAMConfig).Spec)) - Expect(kvpRes.Value.(*libapiv3.IPAMConfig).Spec).To(Equal(kvpRes.Value.(*libapiv3.IPAMConfig).Spec)) + // Expect the spec to have changed. + Expect(kvpRes2.Value.(*apiv3.IPAMConfiguration).Spec).NotTo(Equal(v3KVP.Value.(*apiv3.IPAMConfiguration).Spec)) + Expect(kvpRes.Value.(*apiv3.IPAMConfiguration).Spec).To(Equal(kvpRes.Value.(*apiv3.IPAMConfiguration).Spec)) // Expect the creation time stamp to be the same. - Expect(kvpRes2.Value.(*libapiv3.IPAMConfig).CreationTimestamp).To(Equal(createdAt)) + Expect(kvpRes2.Value.(*apiv3.IPAMConfiguration).CreationTimestamp).To(Equal(createdAt)) }) By("Updating the IPAMConfig using the v1 client", func() { @@ -3843,16 +3985,15 @@ var _ = testutils.E2eDatastoreDescribe("Test Watch support", testutils.Datastore }) By("Checking the update using the v3 client", func() { - kvpRes, err := c.Get(ctx, ipamKVP.Key, "") + kvpRes, err := c.Get(ctx, v3KVP.Key, "") Expect(err).NotTo(HaveOccurred()) - Expect(kvpRes.Value.(*libapiv3.IPAMConfig).Spec.MaxBlocksPerHost).To(Equal(1000)) - // Expect the creation time stamp to be the same. - Expect(kvpRes.Value.(*libapiv3.IPAMConfig).CreationTimestamp).To(Equal(createdAt)) + Expect(kvpRes.Value.(*apiv3.IPAMConfiguration).Spec.MaxBlocksPerHost).To(Equal(int32(1000))) + Expect(kvpRes.Value.(*apiv3.IPAMConfiguration).CreationTimestamp).To(Equal(createdAt)) }) - By("Deleting an IPAM Config", func() { - _, err := c.Delete(ctx, ipamKVP.Key, "") + By("Deleting an IPAMConfiguration", func() { + _, err := c.Delete(ctx, v3KVP.Key, "") Expect(err).NotTo(HaveOccurred()) }) }) @@ -3872,9 +4013,13 @@ var _ = testutils.E2eDatastoreDescribe("Test Inline kubeconfig support", testuti cfg.Spec = apiconfig.CalicoAPIConfigSpec{ KubeConfig: apiconfig.KubeConfig{ KubeconfigInline: string(conf), + CalicoAPIGroup: cfg.Spec.KubeConfig.CalicoAPIGroup, + K8sClientQPS: 1000, }, } + v3CRD = UsingV3CRDs(&cfg.Spec) + // Create a client using the config. client, err := NewKubeClient(&cfg.Spec) Expect(err).NotTo(HaveOccurred()) @@ -3898,6 +4043,7 @@ var _ = testutils.E2eDatastoreDescribe("Test Inline kubeconfig support", testuti _, err := c.ClientSet.CoreV1().Namespaces().Create(ctx, &ns, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) }) + By("Deleting the namespace", func() { testutils.DeleteNamespace(c.ClientSet, ns.ObjectMeta.Name) }) diff --git a/libcalico-go/lib/backend/k8s/conversion/clusternetworkpolicy.go b/libcalico-go/lib/backend/k8s/conversion/clusternetworkpolicy.go new file mode 100644 index 00000000000..8f92aae9c55 --- /dev/null +++ b/libcalico-go/lib/backend/k8s/conversion/clusternetworkpolicy.go @@ -0,0 +1,434 @@ +// Copyright (c) 2025-2026 Tigera, Inc. All rights reserved. + +// 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 conversion + +import ( + "fmt" + "sort" + + apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "github.com/projectcalico/api/pkg/lib/numorstring" + "github.com/sirupsen/logrus" + log "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + clusternetpol "sigs.k8s.io/network-policy-api/apis/v1alpha2" + + "github.com/projectcalico/calico/libcalico-go/lib/backend/model" + cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" + "github.com/projectcalico/calico/libcalico-go/lib/names" + cnet "github.com/projectcalico/calico/libcalico-go/lib/net" +) + +// K8sClusterNetworkPolicyToCalico converts a k8s ClusterNetworkPolicy to a model.KVPair. +func (c converter) K8sClusterNetworkPolicyToCalico(kcnp *clusternetpol.ClusterNetworkPolicy) (*model.KVPair, error) { + // Pull out important fields. + tier := clusterNetworkPolicyTier(kcnp) + + order := float64(kcnp.Spec.Priority) + errorTracker := cerrors.ErrorClusterNetworkPolicyConversion{PolicyName: kcnp.Name} + + // Generate the ingress rules list. + var ingressRules []apiv3.Rule + for _, r := range kcnp.Spec.Ingress { + rules, err := k8sClusterNetPolIngressRuleToCalico(r) + if err != nil { + log.WithError(err).Warn("dropping k8s rule that couldn't be converted.") + // Add rule to conversion error slice + errorTracker.BadIngressRule(&r, fmt.Sprintf("k8s rule couldn't be converted: %s", err)) + failClosedRule := k8sClusterNetPolHandleFailedRules(r.Action) + if failClosedRule != nil { + ingressRules = append(ingressRules, *failClosedRule) + } + } else { + ingressRules = append(ingressRules, rules...) + } + } + + // Generate the egress rules list. + var egressRules []apiv3.Rule + for _, r := range kcnp.Spec.Egress { + rules, err := k8sClusterNetPolEgressRuleToCalico(r) + if err != nil { + log.WithError(err).Warn("dropping k8s rule that couldn't be converted.") + // Add rule to conversion error slice + errorTracker.BadEgressRule(&r, fmt.Sprintf("k8s rule couldn't be converted: %s", err)) + failClosedRule := k8sClusterNetPolHandleFailedRules(r.Action) + if failClosedRule != nil { + egressRules = append(egressRules, *failClosedRule) + } + } else { + egressRules = append(egressRules, rules...) + } + } + + // Either Namespaces or Pods is set. Use one of them to populate the selectors. + var nsSelector, podSelector string + if kcnp.Spec.Subject.Namespaces != nil { + nsSelector = k8sSelectorToCalico(kcnp.Spec.Subject.Namespaces, SelectorNamespace) + // Make sure projectcalico.org/orchestrator == 'k8s' label is added to exclude heps. + podSelector = k8sSelectorToCalico(nil, SelectorPod) + } else { + nsSelector = k8sSelectorToCalico(&kcnp.Spec.Subject.Pods.NamespaceSelector, SelectorNamespace) + podSelector = k8sSelectorToCalico(&kcnp.Spec.Subject.Pods.PodSelector, SelectorPod) + } + + var uid types.UID + var err error + if kcnp.UID != "" { + uid, err = ConvertUID(kcnp.UID) + if err != nil { + return nil, err + } + } + + gnp := apiv3.NewGlobalNetworkPolicy() + gnp.ObjectMeta = metav1.ObjectMeta{ + Name: kcnp.Name, + CreationTimestamp: kcnp.CreationTimestamp, + UID: uid, + ResourceVersion: kcnp.ResourceVersion, + } + gnp.Spec = apiv3.GlobalNetworkPolicySpec{ + Tier: tier, + Order: &order, + NamespaceSelector: nsSelector, + Selector: podSelector, + Ingress: ingressRules, + Egress: egressRules, + Types: clusterNetPolicyTypes(ingressRules, egressRules), + } + + // Build the KVPair. + kvp := &model.KVPair{ + Key: model.ResourceKey{ + Name: kcnp.Name, + Kind: model.KindKubernetesClusterNetworkPolicy, + }, + Value: gnp, + Revision: kcnp.ResourceVersion, + } + + // Return the KVPair with conversion errors if applicable + return kvp, errorTracker.GetError() +} + +func clusterNetPolicyTypes(ingressRules []apiv3.Rule, egressRules []apiv3.Rule) []apiv3.PolicyType { + // Calculate Types setting. The ANP Tiers are default-Pass so the only + // reason to enable a policy type is if we have rules. + var policyTypes []apiv3.PolicyType + if len(ingressRules) != 0 { + policyTypes = append(policyTypes, apiv3.PolicyTypeIngress) + } + if len(egressRules) != 0 { + policyTypes = append(policyTypes, apiv3.PolicyTypeEgress) + } + return policyTypes +} + +func clusterNetworkPolicyTier(kcnp *clusternetpol.ClusterNetworkPolicy) string { + switch kcnp.Spec.Tier { + case clusternetpol.AdminTier: + return names.KubeAdminTierName + case clusternetpol.BaselineTier: + return names.KubeBaselineTierName + default: + return "" + } +} + +func k8sClusterNetPolHandleFailedRules(action clusternetpol.ClusterNetworkPolicyRuleAction) *apiv3.Rule { + if action == clusternetpol.ClusterNetworkPolicyRuleActionDeny || + action == clusternetpol.ClusterNetworkPolicyRuleActionPass { + logrus.Warn("replacing failed rule with a deny-all one.") + return &apiv3.Rule{ + Action: apiv3.Deny, + } + } + return nil +} + +func k8sClusterNetPolIngressRuleToCalico(rule clusternetpol.ClusterNetworkPolicyIngressRule) ([]apiv3.Rule, error) { + action, err := K8sClusterNetworkPolicyActionToCalico(rule.Action) + if err != nil { + return nil, err + } + return combinePortsWithCNPIngressPeers(rule.Protocols, rule.From, rule.Name, action) +} + +func k8sClusterNetPolEgressRuleToCalico(rule clusternetpol.ClusterNetworkPolicyEgressRule) ([]apiv3.Rule, error) { + action, err := K8sClusterNetworkPolicyActionToCalico(rule.Action) + if err != nil { + return nil, err + } + return combinePortsWithCNPEgressPeers(rule.Protocols, rule.To, rule.Name, action) +} + +func K8sClusterNetworkPolicyActionToCalico(action clusternetpol.ClusterNetworkPolicyRuleAction) (apiv3.Action, error) { + switch action { + case clusternetpol.ClusterNetworkPolicyRuleActionAccept: + return apiv3.Allow, nil + case clusternetpol.ClusterNetworkPolicyRuleActionDeny, + clusternetpol.ClusterNetworkPolicyRuleActionPass: + return apiv3.Action(action), nil + default: + return "", fmt.Errorf("unsupported cluster network policy action %v", action) + } +} + +func combinePortsWithCNPIngressPeers( + cnpProtocols []clusternetpol.ClusterNetworkPolicyProtocol, + cnpPeers []clusternetpol.ClusterNetworkPolicyIngressPeer, + ruleName string, + action apiv3.Action, +) (rules []apiv3.Rule, err error) { + protocolPorts, sortedProtocols, err := unpackCNPProtocols(cnpProtocols) + if err != nil { + return nil, err + } + + // Combine destinations with sources to generate rules. We generate one rule per protocol, + // with each rule containing all the allowed ports. + for _, protocolStr := range sortedProtocols { + calicoPorts := protocolPorts[protocolStr] + calicoPorts = SimplifyPorts(calicoPorts) + + var protocol *numorstring.Protocol + if protocolStr != "" { + p := numorstring.ProtocolFromString(protocolStr) + protocol = &p + } + + // Based on specifications at least one Peer is set. + var selector, nsSelector string + for _, peer := range cnpPeers { + var found bool + if peer.Namespaces != nil { + selector = "" + nsSelector = k8sSelectorToCalico(peer.Namespaces, SelectorNamespace) + found = true + } + if peer.Pods != nil { + selector = k8sSelectorToCalico(&peer.Pods.PodSelector, SelectorPod) + nsSelector = k8sSelectorToCalico(&peer.Pods.NamespaceSelector, SelectorNamespace) + found = true + } + if !found { + return nil, fmt.Errorf("none of supported fields in 'From' is set.") + } + + // Build inbound rule and append to list. + rules = append(rules, apiv3.Rule{ + Metadata: k8sClusterNetworkPolicyToCalicoMetadata(ruleName), + Action: action, + Protocol: protocol, + Source: apiv3.EntityRule{ + Selector: selector, + NamespaceSelector: nsSelector, + }, + Destination: apiv3.EntityRule{ + Ports: calicoPorts, + }, + }) + } + } + return rules, nil +} + +func combinePortsWithCNPEgressPeers( + cnpProtocols []clusternetpol.ClusterNetworkPolicyProtocol, + cnpPeers []clusternetpol.ClusterNetworkPolicyEgressPeer, + ruleName string, + action apiv3.Action, +) (rules []apiv3.Rule, err error) { + protocolPorts, sortedProtocols, err := unpackCNPProtocols(cnpProtocols) + if err != nil { + return nil, err + } + + // Combine destinations with sources to generate rules. We generate one rule per protocol, + // with each rule containing all the allowed ports. + for _, protocolStr := range sortedProtocols { + calicoPorts := protocolPorts[protocolStr] + calicoPorts = SimplifyPorts(calicoPorts) + + var protocol *numorstring.Protocol + if protocolStr != "" { + p := numorstring.ProtocolFromString(protocolStr) + protocol = &p + } + + // Based on specifications at least one Peer is set. + for _, peer := range cnpPeers { + var selector, nsSelector string + var nets []string + // One and only one of the following fields is set (based on specification). + var found bool + if peer.Namespaces != nil { + nsSelector = k8sSelectorToCalico(peer.Namespaces, SelectorNamespace) + found = true + } + if peer.Pods != nil { + selector = k8sSelectorToCalico(&peer.Pods.PodSelector, SelectorPod) + nsSelector = k8sSelectorToCalico(&peer.Pods.NamespaceSelector, SelectorNamespace) + found = true + } + if len(peer.Networks) != 0 { + for _, n := range peer.Networks { + _, ipNet, err := cnet.ParseCIDR(string(n)) + if err != nil { + return nil, fmt.Errorf("invalid CIDR in ANP rule: %w", err) + } + nets = append(nets, ipNet.String()) + } + found = true + } + if !found { + return nil, fmt.Errorf("none of supported fields in 'To' is set.") + } + + // Build outbound rule and append to list. + rules = append(rules, apiv3.Rule{ + Metadata: k8sClusterNetworkPolicyToCalicoMetadata(ruleName), + Action: action, + Protocol: protocol, + Destination: apiv3.EntityRule{ + Ports: calicoPorts, + Selector: selector, + NamespaceSelector: nsSelector, + Nets: nets, + }, + }) + } + } + + return rules, nil +} + +func unpackCNPProtocols(cnpProtocols []clusternetpol.ClusterNetworkPolicyProtocol) ( + map[string][]numorstring.Port, + []string, error, +) { + // If there are no ports, represent that as zero struct. + protocols := []clusternetpol.ClusterNetworkPolicyProtocol{{}} + if cnpProtocols != nil && len(cnpProtocols) != 0 { + protocols = cnpProtocols + } + + protocolPorts := map[string][]numorstring.Port{} + + for _, p := range protocols { + protocol, calicoPort, err := k8sCNPPortToCalicoFields(&p) + if err != nil { + return nil, nil, fmt.Errorf("failed to parse k8s protocol: %s", err) + } + + if protocol == nil && calicoPort == nil { + // If nil, no ports were specified, or an empty port struct was provided, which we translate to allowing all. + // We want to use a nil protocol and a nil list of ports, which will allow any destination (for ingress). + // Given we're gonna allow all, we may as well break here and keep only this rule + protocolPorts = map[string][]numorstring.Port{"": nil} + break + } + + pStr := protocol.String() + // treat nil as 'all ports' + if calicoPort == nil { + protocolPorts[pStr] = nil + } else if _, ok := protocolPorts[pStr]; !ok || len(protocolPorts[pStr]) > 0 { + // don't overwrite a nil (allow all ports) if present; if no ports yet for this protocol + // or 1+ ports which aren't 'all ports', then add the present ports + protocolPorts[pStr] = append(protocolPorts[pStr], *calicoPort) + } + } + + protos := make([]string, 0, len(protocolPorts)) + for p := range protocolPorts { + protos = append(protos, p) + } + // Ensure deterministic output + sort.Strings(protos) + return protocolPorts, protos, nil +} + +func k8sCNPPortToCalicoFields(cnpProto *clusternetpol.ClusterNetworkPolicyProtocol) ( + protocol *numorstring.Protocol, + dstPort *numorstring.Port, + err error, +) { + // If no port info, return zero values for all fields (protocol, dstPorts). + if cnpProto == nil { + return + } + + if cnpProto.TCP != nil { + dstPort, err = k8sCNPPortToCalico(cnpProto.TCP.DestinationPort) + p := numorstring.ProtocolFromString(numorstring.ProtocolTCP) + protocol = &p + return + } + + if cnpProto.UDP != nil { + dstPort, err = k8sCNPPortToCalico(cnpProto.UDP.DestinationPort) + p := numorstring.ProtocolFromString(numorstring.ProtocolUDP) + protocol = &p + return + } + + if cnpProto.SCTP != nil { + dstPort, err = k8sCNPPortToCalico(cnpProto.SCTP.DestinationPort) + p := numorstring.ProtocolFromString(numorstring.ProtocolSCTP) + protocol = &p + return + } + + // TODO: Add support for NamedPorts + if len(cnpProto.DestinationNamedPort) != 0 { + err = fmt.Errorf("named ports are not supported yet.") + return + } + + return +} + +func k8sCNPPortToCalico(port *clusternetpol.Port) (*numorstring.Port, error) { + // Only one of the Number or Range is set. + if port == nil { + return nil, nil + } + if port.Number != 0 { + p := numorstring.SinglePort(uint16(port.Number)) + return &p, nil + } + if port.Range != nil { + p, err := numorstring.PortFromRange(uint16(port.Range.Start), uint16(port.Range.End)) + if err != nil { + return nil, err + } + return &p, nil + } + return nil, nil +} + +func k8sClusterNetworkPolicyToCalicoMetadata(ruleName string) *apiv3.RuleMetadata { + if ruleName == "" { + return nil + } + return &apiv3.RuleMetadata{ + Annotations: map[string]string{ + K8sCNPRuleNameLabel: ruleName, + }, + } +} diff --git a/libcalico-go/lib/backend/k8s/conversion/adminnetworkpolicy_test.go b/libcalico-go/lib/backend/k8s/conversion/clusternetworkpolicy_test.go similarity index 62% rename from libcalico-go/lib/backend/k8s/conversion/adminnetworkpolicy_test.go rename to libcalico-go/lib/backend/k8s/conversion/clusternetworkpolicy_test.go index d9c04daafbc..4c0c3800066 100644 --- a/libcalico-go/lib/backend/k8s/conversion/adminnetworkpolicy_test.go +++ b/libcalico-go/lib/backend/k8s/conversion/clusternetworkpolicy_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Tigera, Inc. All rights reserved. +// Copyright (c) 2025-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,72 +15,35 @@ package conversion import ( - "fmt" - - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" - kapiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - adminpolicy "sigs.k8s.io/network-policy-api/apis/v1alpha1" + clusternetpol "sigs.k8s.io/network-policy-api/apis/v1alpha2" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" - "github.com/projectcalico/calico/libcalico-go/lib/names" ) -var _ = Describe("Test AdminNetworkPolicy conversion", func() { - // Use a single instance of the Converter for these tests. - c := NewConverter() - - convertToGNP := func( - anp *adminpolicy.AdminNetworkPolicy, - order float64, - expectedErr *cerrors.ErrorAdminPolicyConversion, - ) *apiv3.GlobalNetworkPolicy { - // Parse the policy. - pol, err := c.K8sAdminNetworkPolicyToCalico(anp) - - if expectedErr == nil { - Expect(err).To(BeNil()) - } else { - Expect(err).To(Equal(*expectedErr)) - } - - // Assert key fields are correct. - policyName := fmt.Sprintf("%v%v", names.K8sAdminNetworkPolicyNamePrefix, anp.Name) - Expect(pol.Key.(model.ResourceKey).Name).To(Equal(policyName)) - - gnp, ok := pol.Value.(*apiv3.GlobalNetworkPolicy) - Expect(ok).To(BeTrue()) - - // Make sure the type information is correct. - Expect(gnp.Kind).To(Equal(apiv3.KindGlobalNetworkPolicy)) - Expect(gnp.APIVersion).To(Equal(apiv3.GroupVersionCurrent)) - - // Assert value fields are correct. - Expect(*gnp.Spec.Order).To(Equal(order)) - Expect(gnp.Spec.Tier).To(Equal(names.AdminNetworkPolicyTierName)) - - return gnp - } - - It("should parse a basic k8s AdminNetworkPolicy to a GlobalNetworkPolicy", func() { - ports := []adminpolicy.AdminNetworkPolicyPort{{ - PortNumber: &adminpolicy.Port{ - Port: 80, +var _ = Describe("Test ClusterNetworkPolicy conversion - Admin tier", func() { + It("should parse a basic k8s ClusterNetworkPolicy to a GlobalNetworkPolicy", func() { + protocols := []clusternetpol.ClusterNetworkPolicyProtocol{{ + TCP: &clusternetpol.ClusterNetworkPolicyProtocolTCP{ + DestinationPort: &clusternetpol.Port{ + Number: 80, + }, }, }} - anp := adminpolicy.AdminNetworkPolicy{ + cnp := clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.AdminNetworkPolicySpec{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ Priority: 100, - Subject: adminpolicy.AdminNetworkPolicySubject{ + Tier: clusternetpol.AdminTier, + Subject: clusternetpol.ClusterNetworkPolicySubject{ Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ "label": "value", @@ -88,12 +51,12 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Ingress: []adminpolicy.AdminNetworkPolicyIngressRule{ + Ingress: []clusternetpol.ClusterNetworkPolicyIngressRule{ { - Name: "The first ingress rule", - Action: "Allow", - Ports: &ports, - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + Name: "The first ingress rule", + Action: "Accept", + Protocols: protocols, + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -109,14 +72,14 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { } // Convert the policy - gnp := convertToGNP(&anp, float64(100.0), nil) + gnp := convertToGNP(&cnp, nil) // Check the selector is correct, and that the matches are sorted. Expect(gnp.Spec.NamespaceSelector).To(Equal("label == 'value' && label2 == 'value2'")) protoTCP := numorstring.ProtocolFromString("TCP") Expect(gnp.Spec.Ingress).To(ConsistOf( apiv3.Rule{ - Metadata: k8sAdminNetworkPolicyToCalicoMetadata("The first ingress rule"), + Metadata: k8sClusterNetworkPolicyToCalicoMetadata("The first ingress rule"), Action: "Allow", Protocol: &protoTCP, // Defaulted to TCP. Source: apiv3.EntityRule{ @@ -132,23 +95,32 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { Expect(gnp.Spec.Egress).To(HaveLen(0)) }) - It("should drop rules with invalid action in a k8s AdminNetworkPolicy", func() { - ports := []adminpolicy.AdminNetworkPolicyPort{ + It("should drop rules with invalid action in a k8s ClusterNetworkPolicy", func() { + protocols := []clusternetpol.ClusterNetworkPolicyProtocol{ { - PortNumber: &adminpolicy.Port{Port: 80}, + TCP: &clusternetpol.ClusterNetworkPolicyProtocolTCP{ + DestinationPort: &clusternetpol.Port{ + Number: 80, + }, + }, }, { - PortRange: &adminpolicy.PortRange{Start: 2000, End: 3000}, + TCP: &clusternetpol.ClusterNetworkPolicyProtocolTCP{ + DestinationPort: &clusternetpol.Port{ + Range: &clusternetpol.PortRange{Start: 2000, End: 3000}, + }, + }, }, } - anp := adminpolicy.AdminNetworkPolicy{ + cnp := clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.AdminNetworkPolicySpec{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ Priority: 300, - Subject: adminpolicy.AdminNetworkPolicySubject{ + Tier: clusternetpol.AdminTier, + Subject: clusternetpol.ClusterNetworkPolicySubject{ Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ "label": "value", @@ -156,11 +128,11 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Ingress: []adminpolicy.AdminNetworkPolicyIngressRule{ + Ingress: []clusternetpol.ClusterNetworkPolicyIngressRule{ { Name: "A random ingress rule", Action: "Log", - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -172,10 +144,10 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, { - Name: "A random ingress rule 2", - Action: "Allow", - Ports: &ports, - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + Name: "A random ingress rule 2", + Action: "Accept", + Protocols: protocols, + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -187,12 +159,12 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Egress: []adminpolicy.AdminNetworkPolicyEgressRule{ + Egress: []clusternetpol.ClusterNetworkPolicyEgressRule{ { - Name: "A random egress rule", - Action: "Deny", - Ports: &ports, - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + Name: "A random egress rule", + Action: "Deny", + Protocols: protocols, + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -206,7 +178,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { { Name: "A random egress rule 2", Action: "Drop", - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -221,15 +193,15 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, } - expectedErr := cerrors.ErrorAdminPolicyConversion{ + expectedErr := cerrors.ErrorClusterNetworkPolicyConversion{ PolicyName: "test.policy", - Rules: []cerrors.ErrorAdminPolicyConversionRule{ + Rules: []cerrors.ErrorClusterNetworkPolicyConversionRule{ { EgressRule: nil, - IngressRule: &adminpolicy.AdminNetworkPolicyIngressRule{ + IngressRule: &clusternetpol.ClusterNetworkPolicyIngressRule{ Name: "A random ingress rule", Action: "Log", - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{"k2": "v2", "k": "v"}, @@ -239,14 +211,14 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Reason: "k8s rule couldn't be converted: unsupported admin network policy action Log", + Reason: "k8s rule couldn't be converted: unsupported cluster network policy action Log", }, { IngressRule: nil, - EgressRule: &adminpolicy.AdminNetworkPolicyEgressRule{ + EgressRule: &clusternetpol.ClusterNetworkPolicyEgressRule{ Name: "A random egress rule 2", Action: "Drop", - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{"k30": "v30", "k40": "v40"}, @@ -256,20 +228,20 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Reason: "k8s rule couldn't be converted: unsupported admin network policy action Drop", + Reason: "k8s rule couldn't be converted: unsupported cluster network policy action Drop", }, }, } // Convert the policy - gnp := convertToGNP(&anp, float64(300.0), &expectedErr) + gnp := convertToGNP(&cnp, &expectedErr) protoTCP := numorstring.ProtocolFromString("TCP") Expect(len(gnp.Spec.Ingress)).To(Equal(1)) Expect(gnp.Spec.Ingress).To(ConsistOf( apiv3.Rule{ - Metadata: k8sAdminNetworkPolicyToCalicoMetadata("A random ingress rule 2"), + Metadata: k8sClusterNetworkPolicyToCalicoMetadata("A random ingress rule 2"), Action: "Allow", Protocol: &protoTCP, // Defaulted to TCP. Source: apiv3.EntityRule{ @@ -284,7 +256,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { Expect(len(gnp.Spec.Egress)).To(Equal(1)) Expect(gnp.Spec.Egress).To(ConsistOf( apiv3.Rule{ - Metadata: k8sAdminNetworkPolicyToCalicoMetadata("A random egress rule"), + Metadata: k8sClusterNetworkPolicyToCalicoMetadata("A random egress rule"), Action: "Deny", Protocol: &protoTCP, // Defaulted to TCP. Source: apiv3.EntityRule{}, @@ -296,15 +268,16 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { )) }) - It("should parse a k8s AdminNetworkPolicy with no ports", func() { - anp := adminpolicy.AdminNetworkPolicy{ + It("should parse a k8s ClusterNetworkPolicy with no ports", func() { + cnp := clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.AdminNetworkPolicySpec{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ Priority: 200, - Subject: adminpolicy.AdminNetworkPolicySubject{ + Tier: clusternetpol.AdminTier, + Subject: clusternetpol.ClusterNetworkPolicySubject{ Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ "label": "value", @@ -312,11 +285,11 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Ingress: []adminpolicy.AdminNetworkPolicyIngressRule{ + Ingress: []clusternetpol.ClusterNetworkPolicyIngressRule{ { Name: "A random ingress rule", Action: "Pass", - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -328,11 +301,11 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Egress: []adminpolicy.AdminNetworkPolicyEgressRule{ + Egress: []clusternetpol.ClusterNetworkPolicyEgressRule{ { Name: "A random egress rule", Action: "Deny", - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -348,11 +321,11 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { } // Convert the policy - gnp := convertToGNP(&anp, float64(200.0), nil) + gnp := convertToGNP(&cnp, nil) Expect(gnp.Spec.Ingress).To(ConsistOf( apiv3.Rule{ - Metadata: k8sAdminNetworkPolicyToCalicoMetadata("A random ingress rule"), + Metadata: k8sClusterNetworkPolicyToCalicoMetadata("A random ingress rule"), Action: "Pass", Protocol: nil, // We only default to TCP when ports exist Source: apiv3.EntityRule{NamespaceSelector: "k == 'v' && k2 == 'v2'"}, @@ -361,7 +334,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { )) Expect(gnp.Spec.Egress).To(ConsistOf( apiv3.Rule{ - Metadata: k8sAdminNetworkPolicyToCalicoMetadata("A random egress rule"), + Metadata: k8sClusterNetworkPolicyToCalicoMetadata("A random egress rule"), Action: "Deny", Protocol: nil, // We only default to TCP when ports exist Source: apiv3.EntityRule{}, @@ -370,32 +343,49 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { )) }) - It("should drop rules with invalid ports in a k8s AdminNetworkPolicy", func() { - goodPorts := []adminpolicy.AdminNetworkPolicyPort{ + It("should drop rules with invalid ports in a k8s ClusterNetworkPolicy", func() { + goodProtos := []clusternetpol.ClusterNetworkPolicyProtocol{ { - PortNumber: &adminpolicy.Port{Port: 80}, + TCP: &clusternetpol.ClusterNetworkPolicyProtocolTCP{ + DestinationPort: &clusternetpol.Port{ + Number: 80, + }, + }, }, { - PortRange: &adminpolicy.PortRange{Start: 2000, End: 3000}, + TCP: &clusternetpol.ClusterNetworkPolicyProtocolTCP{ + DestinationPort: &clusternetpol.Port{ + Range: &clusternetpol.PortRange{Start: 2000, End: 3000}, + }, + }, }, } - badPorts := []adminpolicy.AdminNetworkPolicyPort{ + badProtos := []clusternetpol.ClusterNetworkPolicyProtocol{ { - PortNumber: &adminpolicy.Port{Port: 80}, + TCP: &clusternetpol.ClusterNetworkPolicyProtocolTCP{ + DestinationPort: &clusternetpol.Port{ + Number: 80, + }, + }, }, { - PortRange: &adminpolicy.PortRange{Start: 1000, End: 10}, + TCP: &clusternetpol.ClusterNetworkPolicyProtocolTCP{ + DestinationPort: &clusternetpol.Port{ + Range: &clusternetpol.PortRange{Start: 1000, End: 10}, + }, + }, }, } - anp := adminpolicy.AdminNetworkPolicy{ + cnp := clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.AdminNetworkPolicySpec{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ Priority: 300, - Subject: adminpolicy.AdminNetworkPolicySubject{ + Tier: clusternetpol.AdminTier, + Subject: clusternetpol.ClusterNetworkPolicySubject{ Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ "label": "value", @@ -403,12 +393,12 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Ingress: []adminpolicy.AdminNetworkPolicyIngressRule{ + Ingress: []clusternetpol.ClusterNetworkPolicyIngressRule{ { - Name: "A random ingress rule", - Action: "Allow", - Ports: &badPorts, - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + Name: "A random ingress rule", + Action: "Accept", + Protocols: badProtos, + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -420,10 +410,10 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, { - Name: "A random ingress rule 2", - Action: "Pass", - Ports: &goodPorts, - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + Name: "A random ingress rule 2", + Action: "Pass", + Protocols: goodProtos, + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -435,12 +425,12 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Egress: []adminpolicy.AdminNetworkPolicyEgressRule{ + Egress: []clusternetpol.ClusterNetworkPolicyEgressRule{ { - Name: "A random egress rule", - Action: "Deny", - Ports: &goodPorts, - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + Name: "A random egress rule", + Action: "Deny", + Protocols: goodProtos, + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -452,10 +442,10 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, { - Name: "A random egress rule 2", - Action: "Allow", - Ports: &badPorts, - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + Name: "A random egress rule 2", + Action: "Accept", + Protocols: badProtos, + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -470,16 +460,16 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, } - expectedErr := cerrors.ErrorAdminPolicyConversion{ + expectedErr := cerrors.ErrorClusterNetworkPolicyConversion{ PolicyName: "test.policy", - Rules: []cerrors.ErrorAdminPolicyConversionRule{ + Rules: []cerrors.ErrorClusterNetworkPolicyConversionRule{ { EgressRule: nil, - IngressRule: &adminpolicy.AdminNetworkPolicyIngressRule{ - Name: "A random ingress rule", - Action: "Allow", - Ports: &badPorts, - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + IngressRule: &clusternetpol.ClusterNetworkPolicyIngressRule{ + Name: "A random ingress rule", + Action: "Accept", + Protocols: badProtos, + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{"k2": "v2", "k": "v"}, @@ -489,15 +479,15 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Reason: "k8s rule couldn't be converted: failed to parse k8s port: minimum port number (1000) is greater than maximum port number (10) in port range", + Reason: "k8s rule couldn't be converted: failed to parse k8s protocol: minimum port number (1000) is greater than maximum port number (10) in port range", }, { IngressRule: nil, - EgressRule: &adminpolicy.AdminNetworkPolicyEgressRule{ - Name: "A random egress rule 2", - Action: "Allow", - Ports: &badPorts, - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + EgressRule: &clusternetpol.ClusterNetworkPolicyEgressRule{ + Name: "A random egress rule 2", + Action: "Accept", + Protocols: badProtos, + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{"k30": "v30", "k40": "v40"}, @@ -507,20 +497,20 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Reason: "k8s rule couldn't be converted: failed to parse k8s port: minimum port number (1000) is greater than maximum port number (10) in port range", + Reason: "k8s rule couldn't be converted: failed to parse k8s protocol: minimum port number (1000) is greater than maximum port number (10) in port range", }, }, } // Convert the policy - gnp := convertToGNP(&anp, float64(300.0), &expectedErr) + gnp := convertToGNP(&cnp, &expectedErr) protoTCP := numorstring.ProtocolFromString("TCP") Expect(len(gnp.Spec.Ingress)).To(Equal(1)) Expect(gnp.Spec.Ingress).To(ConsistOf( apiv3.Rule{ - Metadata: k8sAdminNetworkPolicyToCalicoMetadata("A random ingress rule 2"), + Metadata: k8sClusterNetworkPolicyToCalicoMetadata("A random ingress rule 2"), Action: "Pass", Protocol: &protoTCP, // Defaulted to TCP. Source: apiv3.EntityRule{ @@ -538,7 +528,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { Expect(len(gnp.Spec.Egress)).To(Equal(1)) Expect(gnp.Spec.Egress).To(ConsistOf( apiv3.Rule{ - Metadata: k8sAdminNetworkPolicyToCalicoMetadata("A random egress rule"), + Metadata: k8sClusterNetworkPolicyToCalicoMetadata("A random egress rule"), Action: "Deny", Protocol: &protoTCP, // Defaulted to TCP. Source: apiv3.EntityRule{}, @@ -553,15 +543,16 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { )) }) - It("should parse an AdminNetworkPolicy with no rules", func() { - anp := adminpolicy.AdminNetworkPolicy{ + It("should parse an ClusterNetworkPolicy with no rules", func() { + cnp := clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.AdminNetworkPolicySpec{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ Priority: 500, - Subject: adminpolicy.AdminNetworkPolicySubject{ + Tier: clusternetpol.AdminTier, + Subject: clusternetpol.ClusterNetworkPolicySubject{ Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ "label": "value", @@ -573,7 +564,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { } // Convert the policy - gnp := convertToGNP(&anp, float64(500.0), nil) + gnp := convertToGNP(&cnp, nil) // Assert value fields are correct. Expect(gnp.Spec.NamespaceSelector).To(Equal("label == 'value' && label2 == 'value2'")) @@ -583,15 +574,16 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { Expect(gnp.Spec.Egress).To(HaveLen(0)) }) - It("should parse an AdminNetworkPolicy with Namespaces subject and multiple peers", func() { - anp := adminpolicy.AdminNetworkPolicy{ + It("should parse an ClusterNetworkPolicy with Namespaces subject and multiple peers", func() { + cnp := clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.AdminNetworkPolicySpec{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ Priority: 600, - Subject: adminpolicy.AdminNetworkPolicySubject{ + Tier: clusternetpol.AdminTier, + Subject: clusternetpol.ClusterNetworkPolicySubject{ Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ "label": "value", @@ -599,11 +591,11 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Ingress: []adminpolicy.AdminNetworkPolicyIngressRule{ + Ingress: []clusternetpol.ClusterNetworkPolicyIngressRule{ { Name: "A random ingress rule", Action: "Pass", - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -612,7 +604,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, { - Pods: &adminpolicy.NamespacedPod{ + Pods: &clusternetpol.NamespacedPod{ PodSelector: metav1.LabelSelector{ MatchLabels: map[string]string{ "k2": "v2", @@ -623,11 +615,11 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Egress: []adminpolicy.AdminNetworkPolicyEgressRule{ + Egress: []clusternetpol.ClusterNetworkPolicyEgressRule{ { Name: "A random egress rule", Action: "Deny", - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -636,7 +628,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, { - Pods: &adminpolicy.NamespacedPod{ + Pods: &clusternetpol.NamespacedPod{ NamespaceSelector: metav1.LabelSelector{ MatchLabels: map[string]string{ "k4": "v4", @@ -655,7 +647,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, } - gnp := convertToGNP(&anp, float64(600.0), nil) + gnp := convertToGNP(&cnp, nil) Expect(gnp.Spec.NamespaceSelector).To(Equal("label == 'value' && label2 == 'value2'")) Expect(gnp.Spec.Selector).To(Equal("projectcalico.org/orchestrator == 'k8s'")) @@ -676,16 +668,17 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { Expect(gnp.Spec.Types[1]).To(Equal(apiv3.PolicyTypeEgress)) }) - It("should parse an AdminNetworkPolicy with Pods subject and multiple peers", func() { - anp := adminpolicy.AdminNetworkPolicy{ + It("should parse an ClusterNetworkPolicy with Pods subject and multiple peers", func() { + cnp := clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.AdminNetworkPolicySpec{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ Priority: 600, - Subject: adminpolicy.AdminNetworkPolicySubject{ - Pods: &adminpolicy.NamespacedPod{ + Tier: clusternetpol.AdminTier, + Subject: clusternetpol.ClusterNetworkPolicySubject{ + Pods: &clusternetpol.NamespacedPod{ PodSelector: metav1.LabelSelector{ MatchLabels: map[string]string{ "label2": "value2", @@ -693,11 +686,11 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Ingress: []adminpolicy.AdminNetworkPolicyIngressRule{ + Ingress: []clusternetpol.ClusterNetworkPolicyIngressRule{ { Name: "A random ingress rule", Action: "Pass", - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -706,7 +699,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, { - Pods: &adminpolicy.NamespacedPod{ + Pods: &clusternetpol.NamespacedPod{ PodSelector: metav1.LabelSelector{ MatchLabels: map[string]string{ "k2": "v2", @@ -717,11 +710,11 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Egress: []adminpolicy.AdminNetworkPolicyEgressRule{ + Egress: []clusternetpol.ClusterNetworkPolicyEgressRule{ { Name: "A random egress rule", Action: "Deny", - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -730,7 +723,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, { - Pods: &adminpolicy.NamespacedPod{ + Pods: &clusternetpol.NamespacedPod{ NamespaceSelector: metav1.LabelSelector{ MatchLabels: map[string]string{ "k4": "v4", @@ -749,7 +742,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, } - gnp := convertToGNP(&anp, float64(600.0), nil) + gnp := convertToGNP(&cnp, nil) Expect(gnp.Spec.NamespaceSelector).To(Equal("all()")) Expect(gnp.Spec.Selector).To(Equal("projectcalico.org/orchestrator == 'k8s' && label2 == 'value2'")) @@ -770,21 +763,26 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { Expect(gnp.Spec.Types[1]).To(Equal(apiv3.PolicyTypeEgress)) }) - It("should parse a k8s AdminNetworkPolicy with a DoesNotExist expression ", func() { - ports := []adminpolicy.AdminNetworkPolicyPort{ + It("should parse a k8s ClusterNetworkPolicy with a DoesNotExist expression ", func() { + protos := []clusternetpol.ClusterNetworkPolicyProtocol{ { - PortNumber: &adminpolicy.Port{Port: 80}, + TCP: &clusternetpol.ClusterNetworkPolicyProtocolTCP{ + DestinationPort: &clusternetpol.Port{ + Number: 80, + }, + }, }, } - anp := adminpolicy.AdminNetworkPolicy{ + cnp := clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.AdminNetworkPolicySpec{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ Priority: 600, - Subject: adminpolicy.AdminNetworkPolicySubject{ - Pods: &adminpolicy.NamespacedPod{ + Tier: clusternetpol.AdminTier, + Subject: clusternetpol.ClusterNetworkPolicySubject{ + Pods: &clusternetpol.NamespacedPod{ PodSelector: metav1.LabelSelector{ MatchLabels: map[string]string{ "label": "value", @@ -793,14 +791,14 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Ingress: []adminpolicy.AdminNetworkPolicyIngressRule{ + Ingress: []clusternetpol.ClusterNetworkPolicyIngressRule{ { - Name: "A random ingress rule", - Action: "Allow", - Ports: &ports, - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + Name: "A random ingress rule", + Action: "Accept", + Protocols: protos, + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { - Pods: &adminpolicy.NamespacedPod{ + Pods: &clusternetpol.NamespacedPod{ PodSelector: metav1.LabelSelector{ MatchExpressions: []metav1.LabelSelectorRequirement{ {Key: "toast", Operator: metav1.LabelSelectorOpDoesNotExist}, @@ -814,7 +812,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, } - gnp := convertToGNP(&anp, float64(600.0), nil) + gnp := convertToGNP(&cnp, nil) // Check the selector is correct, and that the matches are sorted. Expect(gnp.Spec.Selector).To(Equal( @@ -823,7 +821,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { Expect(gnp.Spec.Ingress).To(ConsistOf( apiv3.Rule{ Action: "Allow", - Metadata: k8sAdminNetworkPolicyToCalicoMetadata("A random ingress rule"), + Metadata: k8sClusterNetworkPolicyToCalicoMetadata("A random ingress rule"), Protocol: &protoTCP, // Defaulted to TCP. Source: apiv3.EntityRule{ NamespaceSelector: "all()", @@ -843,23 +841,32 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { Expect(gnp.Spec.Types[0]).To(Equal(apiv3.PolicyTypeIngress)) }) - It("should parse an AdminNetworkPolicy with multiple peers and ports", func() { - ports := []adminpolicy.AdminNetworkPolicyPort{ + It("should parse an ClusterNetworkPolicy with multiple peers and ports", func() { + protos := []clusternetpol.ClusterNetworkPolicyProtocol{ { - PortNumber: &adminpolicy.Port{Port: 80}, + TCP: &clusternetpol.ClusterNetworkPolicyProtocolTCP{ + DestinationPort: &clusternetpol.Port{ + Number: 80, + }, + }, }, { - PortRange: &adminpolicy.PortRange{Start: 20, End: 30, Protocol: kapiv1.ProtocolUDP}, + UDP: &clusternetpol.ClusterNetworkPolicyProtocolUDP{ + DestinationPort: &clusternetpol.Port{ + Range: &clusternetpol.PortRange{Start: 20, End: 30}, + }, + }, }, } - anp := adminpolicy.AdminNetworkPolicy{ + cnp := clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.AdminNetworkPolicySpec{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ Priority: 600, - Subject: adminpolicy.AdminNetworkPolicySubject{ + Tier: clusternetpol.AdminTier, + Subject: clusternetpol.ClusterNetworkPolicySubject{ Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ "label": "value", @@ -867,12 +874,12 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Ingress: []adminpolicy.AdminNetworkPolicyIngressRule{ + Ingress: []clusternetpol.ClusterNetworkPolicyIngressRule{ { - Name: "A random ingress rule", - Action: "Pass", - Ports: &ports, - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + Name: "A random ingress rule", + Action: "Pass", + Protocols: protos, + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -881,7 +888,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, { - Pods: &adminpolicy.NamespacedPod{ + Pods: &clusternetpol.NamespacedPod{ PodSelector: metav1.LabelSelector{ MatchLabels: map[string]string{ "k2": "v2", @@ -892,12 +899,12 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Egress: []adminpolicy.AdminNetworkPolicyEgressRule{ + Egress: []clusternetpol.ClusterNetworkPolicyEgressRule{ { - Name: "A random egress rule", - Action: "Deny", - Ports: &ports, - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + Name: "A random egress rule", + Action: "Deny", + Protocols: protos, + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -906,7 +913,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, { - Pods: &adminpolicy.NamespacedPod{ + Pods: &clusternetpol.NamespacedPod{ NamespaceSelector: metav1.LabelSelector{ MatchLabels: map[string]string{ "k4": "v4", @@ -925,7 +932,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, } - gnp := convertToGNP(&anp, float64(600.0), nil) + gnp := convertToGNP(&cnp, nil) Expect(gnp.Spec.NamespaceSelector).To(Equal("label == 'value' && label2 == 'value2'")) @@ -965,22 +972,23 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { Expect(gnp.Spec.Types[1]).To(Equal(apiv3.PolicyTypeEgress)) }) - It("should parse an AdminNetworkPolicy with empty namespaces in Subject", func() { - anp := adminpolicy.AdminNetworkPolicy{ + It("should parse an ClusterNetworkPolicy with empty namespaces in Subject", func() { + cnp := clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.AdminNetworkPolicySpec{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ Priority: 500, - Subject: adminpolicy.AdminNetworkPolicySubject{ + Tier: clusternetpol.AdminTier, + Subject: clusternetpol.ClusterNetworkPolicySubject{ Namespaces: &metav1.LabelSelector{}, }, }, } // Convert the policy - gnp := convertToGNP(&anp, float64(500.0), nil) + gnp := convertToGNP(&cnp, nil) Expect(gnp.Spec.Selector).To(Equal("projectcalico.org/orchestrator == 'k8s'")) Expect(gnp.Spec.NamespaceSelector).To(Equal("all()")) @@ -988,16 +996,17 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { Expect(gnp.Spec.Egress).To(HaveLen(0)) }) - It("should parse an AdminNetworkPolicy with empty podSelector in Subject", func() { - anp := adminpolicy.AdminNetworkPolicy{ + It("should parse an ClusterNetworkPolicy with empty podSelector in Subject", func() { + cnp := clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.AdminNetworkPolicySpec{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ Priority: 600, - Subject: adminpolicy.AdminNetworkPolicySubject{ - Pods: &adminpolicy.NamespacedPod{ + Tier: clusternetpol.AdminTier, + Subject: clusternetpol.ClusterNetworkPolicySubject{ + Pods: &clusternetpol.NamespacedPod{ PodSelector: metav1.LabelSelector{}, }, }, @@ -1005,7 +1014,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { } // Convert the policy - gnp := convertToGNP(&anp, float64(600.0), nil) + gnp := convertToGNP(&cnp, nil) Expect(gnp.Spec.Selector).To(Equal("projectcalico.org/orchestrator == 'k8s'")) Expect(gnp.Spec.NamespaceSelector).To(Equal("all()")) @@ -1013,16 +1022,17 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { Expect(gnp.Spec.Egress).To(HaveLen(0)) }) - It("should parse an AdminNetworkPolicy with a rule with namespaceSelector", func() { - anp := adminpolicy.AdminNetworkPolicy{ + It("should parse an ClusterNetworkPolicy with a rule with namespaceSelector", func() { + cnp := clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.AdminNetworkPolicySpec{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ + Tier: clusternetpol.AdminTier, Priority: 600, - Subject: adminpolicy.AdminNetworkPolicySubject{ - Pods: &adminpolicy.NamespacedPod{ + Subject: clusternetpol.ClusterNetworkPolicySubject{ + Pods: &clusternetpol.NamespacedPod{ PodSelector: metav1.LabelSelector{ MatchLabels: map[string]string{ "label": "value", @@ -1030,11 +1040,11 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Ingress: []adminpolicy.AdminNetworkPolicyIngressRule{ + Ingress: []clusternetpol.ClusterNetworkPolicyIngressRule{ { Name: "A random ingress rule", - Action: "Allow", - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + Action: "Accept", + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -1049,7 +1059,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, } - gnp := convertToGNP(&anp, float64(600.0), nil) + gnp := convertToGNP(&cnp, nil) Expect(gnp.Spec.Selector).To(Equal("projectcalico.org/orchestrator == 'k8s' && label == 'value'")) Expect(gnp.Spec.NamespaceSelector).To(Equal("all()")) @@ -1066,16 +1076,17 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { Expect(gnp.Spec.Types[0]).To(Equal(apiv3.PolicyTypeIngress)) }) - It("should parse an AdminNetworkPolicy with a rule with podSelector", func() { - anp := adminpolicy.AdminNetworkPolicy{ + It("should parse an ClusterNetworkPolicy with a rule with podSelector", func() { + cnp := clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.AdminNetworkPolicySpec{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ Priority: 600, - Subject: adminpolicy.AdminNetworkPolicySubject{ - Pods: &adminpolicy.NamespacedPod{ + Tier: clusternetpol.AdminTier, + Subject: clusternetpol.ClusterNetworkPolicySubject{ + Pods: &clusternetpol.NamespacedPod{ PodSelector: metav1.LabelSelector{ MatchLabels: map[string]string{ "label": "value", @@ -1083,13 +1094,13 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Egress: []adminpolicy.AdminNetworkPolicyEgressRule{ + Egress: []clusternetpol.ClusterNetworkPolicyEgressRule{ { Name: "A random egress rule", - Action: "Allow", - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + Action: "Accept", + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { - Pods: &adminpolicy.NamespacedPod{ + Pods: &clusternetpol.NamespacedPod{ PodSelector: metav1.LabelSelector{ MatchLabels: map[string]string{ "namespaceRole": "dev", @@ -1104,7 +1115,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, } - gnp := convertToGNP(&anp, float64(600.0), nil) + gnp := convertToGNP(&cnp, nil) Expect(gnp.Spec.Selector).To(Equal("projectcalico.org/orchestrator == 'k8s' && label == 'value'")) Expect(gnp.Spec.NamespaceSelector).To(Equal("all()")) @@ -1121,16 +1132,17 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { Expect(gnp.Spec.Types[0]).To(Equal(apiv3.PolicyTypeEgress)) }) - It("should faild parsing an AdminNetworkPolicy with a rule with neither namespaces or pods set", func() { - anp := adminpolicy.AdminNetworkPolicy{ + It("should faild parsing an ClusterNetworkPolicy with a rule with neither namespaces or pods set", func() { + cnp := clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.AdminNetworkPolicySpec{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ + Tier: clusternetpol.AdminTier, Priority: 600, - Subject: adminpolicy.AdminNetworkPolicySubject{ - Pods: &adminpolicy.NamespacedPod{ + Subject: clusternetpol.ClusterNetworkPolicySubject{ + Pods: &clusternetpol.NamespacedPod{ PodSelector: metav1.LabelSelector{ MatchLabels: map[string]string{ "label": "value", @@ -1138,11 +1150,11 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Ingress: []adminpolicy.AdminNetworkPolicyIngressRule{ + Ingress: []clusternetpol.ClusterNetworkPolicyIngressRule{ { Name: "A random ingress rule", - Action: "Allow", - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + Action: "Accept", + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { Namespaces: nil, Pods: nil, @@ -1150,11 +1162,11 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Egress: []adminpolicy.AdminNetworkPolicyEgressRule{ + Egress: []clusternetpol.ClusterNetworkPolicyEgressRule{ { Name: "A random egress rule", Action: "Pass", - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { Namespaces: nil, Pods: nil, @@ -1165,15 +1177,15 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, } - expectedErr := cerrors.ErrorAdminPolicyConversion{ + expectedErr := cerrors.ErrorClusterNetworkPolicyConversion{ PolicyName: "test.policy", - Rules: []cerrors.ErrorAdminPolicyConversionRule{ + Rules: []cerrors.ErrorClusterNetworkPolicyConversionRule{ { EgressRule: nil, - IngressRule: &adminpolicy.AdminNetworkPolicyIngressRule{ + IngressRule: &clusternetpol.ClusterNetworkPolicyIngressRule{ Name: "A random ingress rule", - Action: "Allow", - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + Action: "Accept", + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { Namespaces: nil, Pods: nil, @@ -1184,10 +1196,10 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, { IngressRule: nil, - EgressRule: &adminpolicy.AdminNetworkPolicyEgressRule{ + EgressRule: &clusternetpol.ClusterNetworkPolicyEgressRule{ Name: "A random egress rule", Action: "Pass", - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { Namespaces: nil, Pods: nil, @@ -1199,7 +1211,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, } - gnp := convertToGNP(&anp, float64(600.0), &expectedErr) + gnp := convertToGNP(&cnp, &expectedErr) Expect(gnp.Spec.Selector).To(Equal("projectcalico.org/orchestrator == 'k8s' && label == 'value'")) Expect(gnp.Spec.NamespaceSelector).To(Equal("all()")) @@ -1212,16 +1224,17 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { Expect(gnp.Spec.Ingress).To(HaveLen(0)) }) - It("should parse an AdminNetworkPolicy with a rule with empty namespaceSelector", func() { - anp := adminpolicy.AdminNetworkPolicy{ + It("should parse an ClusterNetworkPolicy with a rule with empty namespaceSelector", func() { + cnp := clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.AdminNetworkPolicySpec{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ Priority: 600, - Subject: adminpolicy.AdminNetworkPolicySubject{ - Pods: &adminpolicy.NamespacedPod{ + Tier: clusternetpol.AdminTier, + Subject: clusternetpol.ClusterNetworkPolicySubject{ + Pods: &clusternetpol.NamespacedPod{ PodSelector: metav1.LabelSelector{ MatchLabels: map[string]string{ "label": "value", @@ -1229,11 +1242,11 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Ingress: []adminpolicy.AdminNetworkPolicyIngressRule{ + Ingress: []clusternetpol.ClusterNetworkPolicyIngressRule{ { Name: "A random ingress rule", - Action: "Allow", - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + Action: "Accept", + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{}, @@ -1245,7 +1258,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, } - gnp := convertToGNP(&anp, float64(600.0), nil) + gnp := convertToGNP(&cnp, nil) Expect(gnp.Spec.Selector).To(Equal("projectcalico.org/orchestrator == 'k8s' && label == 'value'")) Expect(gnp.Spec.NamespaceSelector).To(Equal("all()")) @@ -1262,16 +1275,17 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { Expect(gnp.Spec.Types[0]).To(Equal(apiv3.PolicyTypeIngress)) }) - It("should parse an AdminNetworkPolicy with a rule with empty podSelector", func() { - anp := adminpolicy.AdminNetworkPolicy{ + It("should parse an ClusterNetworkPolicy with a rule with empty podSelector", func() { + cnp := clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.AdminNetworkPolicySpec{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ + Tier: clusternetpol.AdminTier, Priority: 600, - Subject: adminpolicy.AdminNetworkPolicySubject{ - Pods: &adminpolicy.NamespacedPod{ + Subject: clusternetpol.ClusterNetworkPolicySubject{ + Pods: &clusternetpol.NamespacedPod{ PodSelector: metav1.LabelSelector{ MatchLabels: map[string]string{ "label": "value", @@ -1279,13 +1293,13 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Egress: []adminpolicy.AdminNetworkPolicyEgressRule{ + Egress: []clusternetpol.ClusterNetworkPolicyEgressRule{ { Name: "A random egress rule", Action: "Pass", - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { - Pods: &adminpolicy.NamespacedPod{ + Pods: &clusternetpol.NamespacedPod{ NamespaceSelector: metav1.LabelSelector{ MatchLabels: map[string]string{}, }, @@ -1300,7 +1314,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, } - gnp := convertToGNP(&anp, float64(600.0), nil) + gnp := convertToGNP(&cnp, nil) Expect(gnp.Spec.Selector).To(Equal("projectcalico.org/orchestrator == 'k8s' && label == 'value'")) Expect(gnp.Spec.NamespaceSelector).To(Equal("all()")) @@ -1317,16 +1331,17 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { Expect(gnp.Spec.Types[0]).To(Equal(apiv3.PolicyTypeEgress)) }) - It("should parse an AdminNetworkPolicy with a rule with a rule with both Namespaces and Pods", func() { - anp := adminpolicy.AdminNetworkPolicy{ + It("should parse an ClusterNetworkPolicy with a rule with a rule with both Namespaces and Pods", func() { + cnp := clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.AdminNetworkPolicySpec{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ + Tier: clusternetpol.AdminTier, Priority: 1000, - Subject: adminpolicy.AdminNetworkPolicySubject{ - Pods: &adminpolicy.NamespacedPod{ + Subject: clusternetpol.ClusterNetworkPolicySubject{ + Pods: &clusternetpol.NamespacedPod{ PodSelector: metav1.LabelSelector{ MatchLabels: map[string]string{ "label": "value", @@ -1334,13 +1349,13 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Ingress: []adminpolicy.AdminNetworkPolicyIngressRule{ + Ingress: []clusternetpol.ClusterNetworkPolicyIngressRule{ { Name: "A random ingress rule", Action: "Deny", - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { - Pods: &adminpolicy.NamespacedPod{ + Pods: &clusternetpol.NamespacedPod{ NamespaceSelector: metav1.LabelSelector{ MatchLabels: map[string]string{ "namespaceRole": "dev", @@ -1361,7 +1376,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, } - gnp := convertToGNP(&anp, float64(1000.0), nil) + gnp := convertToGNP(&cnp, nil) Expect(gnp.Spec.Selector).To(Equal("projectcalico.org/orchestrator == 'k8s' && label == 'value'")) Expect(gnp.Spec.NamespaceSelector).To(Equal("all()")) @@ -1378,15 +1393,16 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { Expect(gnp.Spec.Types[0]).To(Equal(apiv3.PolicyTypeIngress)) }) - It("should parse an AdminNetworkPolicy with a Subject with MatchExpressions", func() { - anp := adminpolicy.AdminNetworkPolicy{ + It("should parse an ClusterNetworkPolicy with a Subject with MatchExpressions", func() { + cnp := clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.AdminNetworkPolicySpec{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ + Tier: clusternetpol.AdminTier, Priority: 500, - Subject: adminpolicy.AdminNetworkPolicySubject{ + Subject: clusternetpol.ClusterNetworkPolicySubject{ Namespaces: &metav1.LabelSelector{ MatchExpressions: []metav1.LabelSelectorRequirement{ { @@ -1401,7 +1417,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { } // Convert the policy - gnp := convertToGNP(&anp, float64(500.0), nil) + gnp := convertToGNP(&cnp, nil) // Assert value fields are correct. Expect(gnp.Spec.NamespaceSelector).To(Equal("k in { 'v1', 'v2' }")) @@ -1412,25 +1428,34 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { Expect(gnp.Spec.Egress).To(HaveLen(0)) }) - It("should replace an unsupported AdminNeworkPolicy rule with Deny action with a deny-all one", func() { - ports := []adminpolicy.AdminNetworkPolicyPort{ + It("should replace an unsupported ClusterNetworkPolicy rule with Deny action with a deny-all one", func() { + goodProtos := []clusternetpol.ClusterNetworkPolicyProtocol{ { - PortNumber: &adminpolicy.Port{Port: 80}, + TCP: &clusternetpol.ClusterNetworkPolicyProtocolTCP{ + DestinationPort: &clusternetpol.Port{ + Number: 80, + }, + }, }, } - badPorts := []adminpolicy.AdminNetworkPolicyPort{ + badProtos := []clusternetpol.ClusterNetworkPolicyProtocol{ { - PortRange: &adminpolicy.PortRange{Start: 40, End: 20, Protocol: kapiv1.ProtocolUDP}, + UDP: &clusternetpol.ClusterNetworkPolicyProtocolUDP{ + DestinationPort: &clusternetpol.Port{ + Range: &clusternetpol.PortRange{Start: 40, End: 20}, + }, + }, }, } - anp := adminpolicy.AdminNetworkPolicy{ + cnp := clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.AdminNetworkPolicySpec{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ + Tier: clusternetpol.AdminTier, Priority: 600, - Subject: adminpolicy.AdminNetworkPolicySubject{ + Subject: clusternetpol.ClusterNetworkPolicySubject{ Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ "label": "value", @@ -1438,12 +1463,12 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Ingress: []adminpolicy.AdminNetworkPolicyIngressRule{ + Ingress: []clusternetpol.ClusterNetworkPolicyIngressRule{ { - Name: "A random ingress rule", - Action: "Pass", - Ports: &badPorts, - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + Name: "A random ingress rule", + Action: "Pass", + Protocols: badProtos, + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -1454,10 +1479,10 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, { - Name: "A random ingress rule 2", - Action: "Allow", - Ports: &badPorts, - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + Name: "A random ingress rule 2", + Action: "Accept", + Protocols: badProtos, + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -1468,12 +1493,12 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Egress: []adminpolicy.AdminNetworkPolicyEgressRule{ + Egress: []clusternetpol.ClusterNetworkPolicyEgressRule{ { - Name: "A random egress rule", - Action: "Deny", - Ports: &badPorts, - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + Name: "A random egress rule", + Action: "Deny", + Protocols: badProtos, + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -1484,10 +1509,10 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, { - Name: "A random egress rule 2", - Action: "Deny", - Ports: &ports, - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + Name: "A random egress rule 2", + Action: "Deny", + Protocols: goodProtos, + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -1501,16 +1526,16 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, } - expectedErr := cerrors.ErrorAdminPolicyConversion{ + expectedErr := cerrors.ErrorClusterNetworkPolicyConversion{ PolicyName: "test.policy", - Rules: []cerrors.ErrorAdminPolicyConversionRule{ + Rules: []cerrors.ErrorClusterNetworkPolicyConversionRule{ { EgressRule: nil, - IngressRule: &adminpolicy.AdminNetworkPolicyIngressRule{ - Name: "A random ingress rule", - Action: "Pass", - Ports: &badPorts, - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + IngressRule: &clusternetpol.ClusterNetworkPolicyIngressRule{ + Name: "A random ingress rule", + Action: "Pass", + Protocols: badProtos, + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{"k": "v"}, @@ -1520,15 +1545,15 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Reason: "k8s rule couldn't be converted: failed to parse k8s port: minimum port number (40) is greater than maximum port number (20) in port range", + Reason: "k8s rule couldn't be converted: failed to parse k8s protocol: minimum port number (40) is greater than maximum port number (20) in port range", }, { EgressRule: nil, - IngressRule: &adminpolicy.AdminNetworkPolicyIngressRule{ - Name: "A random ingress rule 2", - Action: "Allow", - Ports: &badPorts, - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + IngressRule: &clusternetpol.ClusterNetworkPolicyIngressRule{ + Name: "A random ingress rule 2", + Action: "Accept", + Protocols: badProtos, + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{"k": "v"}, @@ -1538,15 +1563,15 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Reason: "k8s rule couldn't be converted: failed to parse k8s port: minimum port number (40) is greater than maximum port number (20) in port range", + Reason: "k8s rule couldn't be converted: failed to parse k8s protocol: minimum port number (40) is greater than maximum port number (20) in port range", }, { IngressRule: nil, - EgressRule: &adminpolicy.AdminNetworkPolicyEgressRule{ - Name: "A random egress rule", - Action: "Deny", - Ports: &badPorts, - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + EgressRule: &clusternetpol.ClusterNetworkPolicyEgressRule{ + Name: "A random egress rule", + Action: "Deny", + Protocols: badProtos, + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{"k3": "v3"}, @@ -1556,12 +1581,12 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Reason: "k8s rule couldn't be converted: failed to parse k8s port: minimum port number (40) is greater than maximum port number (20) in port range", + Reason: "k8s rule couldn't be converted: failed to parse k8s protocol: minimum port number (40) is greater than maximum port number (20) in port range", }, }, } - gnp := convertToGNP(&anp, float64(600.0), &expectedErr) + gnp := convertToGNP(&cnp, &expectedErr) Expect(gnp.Spec.NamespaceSelector).To(Equal("label == 'value' && label2 == 'value2'")) @@ -1583,15 +1608,16 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { Expect(gnp.Spec.Types[1]).To(Equal(apiv3.PolicyTypeEgress)) }) - It("should parse a k8s AdminNetworkPolicy with a Networks peer", func() { - anp := adminpolicy.AdminNetworkPolicy{ + It("should parse a k8s ClusterNetworkPolicy with a Networks peer", func() { + cnp := clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.AdminNetworkPolicySpec{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ + Tier: clusternetpol.AdminTier, Priority: 200, - Subject: adminpolicy.AdminNetworkPolicySubject{ + Subject: clusternetpol.ClusterNetworkPolicySubject{ Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ "label": "value", @@ -1599,11 +1625,11 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Ingress: []adminpolicy.AdminNetworkPolicyIngressRule{ + Ingress: []clusternetpol.ClusterNetworkPolicyIngressRule{ { Name: "A random ingress rule", Action: "Pass", - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -1614,11 +1640,11 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Egress: []adminpolicy.AdminNetworkPolicyEgressRule{ + Egress: []clusternetpol.ClusterNetworkPolicyEgressRule{ { Name: "A random egress rule", Action: "Deny", - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -1627,7 +1653,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, { - Networks: []adminpolicy.CIDR{"10.10.10.0/24", "1.1.1.1/32"}, + Networks: []clusternetpol.CIDR{"10.10.10.0/24", "1.1.1.1/32"}, }, }, }, @@ -1636,11 +1662,11 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { } // Convert the policy - gnp := convertToGNP(&anp, float64(200.0), nil) + gnp := convertToGNP(&cnp, nil) Expect(gnp.Spec.Ingress).To(ConsistOf( apiv3.Rule{ - Metadata: k8sAdminNetworkPolicyToCalicoMetadata("A random ingress rule"), + Metadata: k8sClusterNetworkPolicyToCalicoMetadata("A random ingress rule"), Action: "Pass", Protocol: nil, // We only default to TCP when ports exist Source: apiv3.EntityRule{NamespaceSelector: "k == 'v'"}, @@ -1649,14 +1675,14 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { )) Expect(gnp.Spec.Egress).To(ConsistOf( apiv3.Rule{ - Metadata: k8sAdminNetworkPolicyToCalicoMetadata("A random egress rule"), + Metadata: k8sClusterNetworkPolicyToCalicoMetadata("A random egress rule"), Action: "Deny", Protocol: nil, // We only default to TCP when ports exist Source: apiv3.EntityRule{}, Destination: apiv3.EntityRule{NamespaceSelector: "k3 == 'v3'"}, }, apiv3.Rule{ - Metadata: k8sAdminNetworkPolicyToCalicoMetadata("A random egress rule"), + Metadata: k8sClusterNetworkPolicyToCalicoMetadata("A random egress rule"), Action: "Deny", Protocol: nil, // We only default to TCP when ports exist Source: apiv3.EntityRule{}, @@ -1665,15 +1691,16 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { )) }) - It("should parse a k8s AdminNetworkPolicy with an any address Network peer", func() { - anp := adminpolicy.AdminNetworkPolicy{ + It("should parse a k8s ClusterNetworkPolicy with an any address Network peer", func() { + cnp := clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.AdminNetworkPolicySpec{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ + Tier: clusternetpol.AdminTier, Priority: 200, - Subject: adminpolicy.AdminNetworkPolicySubject{ + Subject: clusternetpol.ClusterNetworkPolicySubject{ Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ "label": "value", @@ -1681,11 +1708,11 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Ingress: []adminpolicy.AdminNetworkPolicyIngressRule{ + Ingress: []clusternetpol.ClusterNetworkPolicyIngressRule{ { Name: "A random ingress rule", Action: "Pass", - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -1696,11 +1723,11 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Egress: []adminpolicy.AdminNetworkPolicyEgressRule{ + Egress: []clusternetpol.ClusterNetworkPolicyEgressRule{ { Name: "A random egress rule", Action: "Deny", - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -1709,7 +1736,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, { - Networks: []adminpolicy.CIDR{"0.0.0.0/0", "::/0"}, + Networks: []clusternetpol.CIDR{"0.0.0.0/0", "::/0"}, }, }, }, @@ -1718,11 +1745,11 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { } // Convert the policy - gnp := convertToGNP(&anp, float64(200.0), nil) + gnp := convertToGNP(&cnp, nil) Expect(gnp.Spec.Ingress).To(ConsistOf( apiv3.Rule{ - Metadata: k8sAdminNetworkPolicyToCalicoMetadata("A random ingress rule"), + Metadata: k8sClusterNetworkPolicyToCalicoMetadata("A random ingress rule"), Action: "Pass", Protocol: nil, // We only default to TCP when ports exist Source: apiv3.EntityRule{NamespaceSelector: "k == 'v'"}, @@ -1731,14 +1758,14 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { )) Expect(gnp.Spec.Egress).To(ConsistOf( apiv3.Rule{ - Metadata: k8sAdminNetworkPolicyToCalicoMetadata("A random egress rule"), + Metadata: k8sClusterNetworkPolicyToCalicoMetadata("A random egress rule"), Action: "Deny", Protocol: nil, // We only default to TCP when ports exist Source: apiv3.EntityRule{}, Destination: apiv3.EntityRule{NamespaceSelector: "k3 == 'v3'"}, }, apiv3.Rule{ - Metadata: k8sAdminNetworkPolicyToCalicoMetadata("A random egress rule"), + Metadata: k8sClusterNetworkPolicyToCalicoMetadata("A random egress rule"), Action: "Deny", Protocol: nil, // We only default to TCP when ports exist Source: apiv3.EntityRule{}, @@ -1747,20 +1774,25 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { )) }) - It("should parse a k8s AdminNetworkPolicy with a Networks peer and ports", func() { - ports := []adminpolicy.AdminNetworkPolicyPort{ + It("should parse a k8s ClusterNetworkPolicy with a Networks peer and ports", func() { + protos := []clusternetpol.ClusterNetworkPolicyProtocol{ { - PortNumber: &adminpolicy.Port{Port: 80}, + TCP: &clusternetpol.ClusterNetworkPolicyProtocolTCP{ + DestinationPort: &clusternetpol.Port{ + Number: 80, + }, + }, }, } - anp := adminpolicy.AdminNetworkPolicy{ + cnp := clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.AdminNetworkPolicySpec{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ + Tier: clusternetpol.AdminTier, Priority: 200, - Subject: adminpolicy.AdminNetworkPolicySubject{ + Subject: clusternetpol.ClusterNetworkPolicySubject{ Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ "label": "value", @@ -1768,12 +1800,12 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Ingress: []adminpolicy.AdminNetworkPolicyIngressRule{ + Ingress: []clusternetpol.ClusterNetworkPolicyIngressRule{ { - Name: "A random ingress rule", - Action: "Pass", - Ports: &ports, - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + Name: "A random ingress rule", + Action: "Pass", + Protocols: protos, + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -1784,12 +1816,12 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Egress: []adminpolicy.AdminNetworkPolicyEgressRule{ + Egress: []clusternetpol.ClusterNetworkPolicyEgressRule{ { - Name: "A random egress rule", - Action: "Deny", - Ports: &ports, - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + Name: "A random egress rule", + Action: "Deny", + Protocols: protos, + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -1798,7 +1830,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, { - Networks: []adminpolicy.CIDR{"10.10.10.0/24", "1.1.1.1/32"}, + Networks: []clusternetpol.CIDR{"10.10.10.0/24", "1.1.1.1/32"}, }, }, }, @@ -1807,12 +1839,12 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { } // Convert the policy - gnp := convertToGNP(&anp, float64(200.0), nil) + gnp := convertToGNP(&cnp, nil) protocolTCP := numorstring.ProtocolFromString(numorstring.ProtocolTCP) Expect(gnp.Spec.Ingress).To(ConsistOf( apiv3.Rule{ - Metadata: k8sAdminNetworkPolicyToCalicoMetadata("A random ingress rule"), + Metadata: k8sClusterNetworkPolicyToCalicoMetadata("A random ingress rule"), Action: "Pass", Protocol: &protocolTCP, Source: apiv3.EntityRule{NamespaceSelector: "k == 'v'"}, @@ -1825,7 +1857,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { )) Expect(gnp.Spec.Egress).To(ConsistOf( apiv3.Rule{ - Metadata: k8sAdminNetworkPolicyToCalicoMetadata("A random egress rule"), + Metadata: k8sClusterNetworkPolicyToCalicoMetadata("A random egress rule"), Action: "Deny", Protocol: &protocolTCP, Source: apiv3.EntityRule{}, @@ -1837,7 +1869,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, apiv3.Rule{ - Metadata: k8sAdminNetworkPolicyToCalicoMetadata("A random egress rule"), + Metadata: k8sClusterNetworkPolicyToCalicoMetadata("A random egress rule"), Action: "Deny", Protocol: &protocolTCP, Source: apiv3.EntityRule{}, @@ -1851,20 +1883,25 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { )) }) - It("should parse a k8s AdminNetworkPolicy with an invalid networks peer", func() { - ports := []adminpolicy.AdminNetworkPolicyPort{ + It("should parse a k8s ClusterNetworkPolicy with an invalid networks peer", func() { + protos := []clusternetpol.ClusterNetworkPolicyProtocol{ { - PortNumber: &adminpolicy.Port{Port: 80}, + TCP: &clusternetpol.ClusterNetworkPolicyProtocolTCP{ + DestinationPort: &clusternetpol.Port{ + Number: 80, + }, + }, }, } - anp := adminpolicy.AdminNetworkPolicy{ + cnp := clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.AdminNetworkPolicySpec{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ + Tier: clusternetpol.AdminTier, Priority: 200, - Subject: adminpolicy.AdminNetworkPolicySubject{ + Subject: clusternetpol.ClusterNetworkPolicySubject{ Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ "label": "value", @@ -1872,12 +1909,12 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Ingress: []adminpolicy.AdminNetworkPolicyIngressRule{ + Ingress: []clusternetpol.ClusterNetworkPolicyIngressRule{ { - Name: "A random ingress rule", - Action: "Pass", - Ports: &ports, - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + Name: "A random ingress rule", + Action: "Pass", + Protocols: protos, + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -1888,12 +1925,12 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, }, - Egress: []adminpolicy.AdminNetworkPolicyEgressRule{ + Egress: []clusternetpol.ClusterNetworkPolicyEgressRule{ { - Name: "A random egress rule", - Action: "Deny", - Ports: &ports, - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + Name: "A random egress rule", + Action: "Deny", + Protocols: protos, + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -1902,7 +1939,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, { - Networks: []adminpolicy.CIDR{"10.10.10.0/24", "1.1.1.1/66"}, + Networks: []clusternetpol.CIDR{"10.10.10.0/24", "1.1.1.1/66"}, }, }, }, @@ -1910,16 +1947,16 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, } - expectedErr := cerrors.ErrorAdminPolicyConversion{ + expectedErr := cerrors.ErrorClusterNetworkPolicyConversion{ PolicyName: "test.policy", - Rules: []cerrors.ErrorAdminPolicyConversionRule{ + Rules: []cerrors.ErrorClusterNetworkPolicyConversionRule{ { IngressRule: nil, - EgressRule: &adminpolicy.AdminNetworkPolicyEgressRule{ - Name: "A random egress rule", - Action: "Deny", - Ports: &ports, - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + EgressRule: &clusternetpol.ClusterNetworkPolicyEgressRule{ + Name: "A random egress rule", + Action: "Deny", + Protocols: protos, + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{"k3": "v3"}, @@ -1927,7 +1964,7 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { }, }, { - Networks: []adminpolicy.CIDR{"10.10.10.0/24", "1.1.1.1/66"}, + Networks: []clusternetpol.CIDR{"10.10.10.0/24", "1.1.1.1/66"}, }, }, }, @@ -1937,12 +1974,12 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { } // Convert the policy - gnp := convertToGNP(&anp, float64(200.0), &expectedErr) + gnp := convertToGNP(&cnp, &expectedErr) protocolTCP := numorstring.ProtocolFromString(numorstring.ProtocolTCP) Expect(gnp.Spec.Ingress).To(ConsistOf( apiv3.Rule{ - Metadata: k8sAdminNetworkPolicyToCalicoMetadata("A random ingress rule"), + Metadata: k8sClusterNetworkPolicyToCalicoMetadata("A random ingress rule"), Action: "Pass", Protocol: &protocolTCP, Source: apiv3.EntityRule{NamespaceSelector: "k == 'v'"}, @@ -1963,55 +2000,26 @@ var _ = Describe("Test AdminNetworkPolicy conversion", func() { // Most of the conversion logic is shared with ANP, so only testing a few // cases for BANP. -var _ = Describe("Test BaselineAdminNetworkPolicy conversion", func() { - // Use a single instance of the Converter for these tests. - c := NewConverter() - - convertToGNP := func( - banp *adminpolicy.BaselineAdminNetworkPolicy, - expectedErr *cerrors.ErrorAdminPolicyConversion, - ) *apiv3.GlobalNetworkPolicy { - // Parse the policy. - pol, err := c.K8sBaselineAdminNetworkPolicyToCalico(banp) - - if expectedErr == nil { - Expect(err).To(BeNil()) - } else { - Expect(err).To(Equal(*expectedErr)) - } - - // Assert key fields are correct. - policyName := fmt.Sprintf("%v%v", names.K8sBaselineAdminNetworkPolicyNamePrefix, banp.Name) - Expect(pol.Key.(model.ResourceKey).Name).To(Equal(policyName)) - - gnp, ok := pol.Value.(*apiv3.GlobalNetworkPolicy) - Expect(ok).To(BeTrue()) - - // Make sure the type information is correct. - Expect(gnp.Kind).To(Equal(apiv3.KindGlobalNetworkPolicy)) - Expect(gnp.APIVersion).To(Equal(apiv3.GroupVersionCurrent)) - - // Assert value fields are correct. Order is always 1000 for a BANP. - Expect(*gnp.Spec.Order).To(Equal(1000.0)) - Expect(gnp.Spec.Tier).To(Equal(names.BaselineAdminNetworkPolicyTierName)) - - return gnp - } - - It("should parse a basic k8s BaselineAdminNetworkPolicy to a GlobalNetworkPolicy", func() { - ports := []adminpolicy.AdminNetworkPolicyPort{{ - PortNumber: &adminpolicy.Port{ - Port: 80, +var _ = Describe("Test ClusterNetworkPolicy conversion - Baseline tier", func() { + It("should parse a basic k8s ClusterNetworkPolicy to a GlobalNetworkPolicy", func() { + protos := []clusternetpol.ClusterNetworkPolicyProtocol{ + { + TCP: &clusternetpol.ClusterNetworkPolicyProtocolTCP{ + DestinationPort: &clusternetpol.Port{ + Number: 80, + }, + }, }, - }} - banp := adminpolicy.BaselineAdminNetworkPolicy{ + } + bcnp := clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "default", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.BaselineAdminNetworkPolicySpec{ - Subject: adminpolicy.AdminNetworkPolicySubject{ - Pods: &adminpolicy.NamespacedPod{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ + Tier: clusternetpol.BaselineTier, + Subject: clusternetpol.ClusterNetworkPolicySubject{ + Pods: &clusternetpol.NamespacedPod{ NamespaceSelector: metav1.LabelSelector{ MatchLabels: map[string]string{ "label": "value", @@ -2025,12 +2033,12 @@ var _ = Describe("Test BaselineAdminNetworkPolicy conversion", func() { }, }, }, - Ingress: []adminpolicy.BaselineAdminNetworkPolicyIngressRule{ + Ingress: []clusternetpol.ClusterNetworkPolicyIngressRule{ { - Name: "The first ingress rule", - Action: "Allow", - Ports: &ports, - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + Name: "The first ingress rule", + Action: "Accept", + Protocols: protos, + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -2042,21 +2050,21 @@ var _ = Describe("Test BaselineAdminNetworkPolicy conversion", func() { }, }, }, - Egress: []adminpolicy.BaselineAdminNetworkPolicyEgressRule{ + Egress: []clusternetpol.ClusterNetworkPolicyEgressRule{ { Name: "The first egress rule", Action: "Deny", - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { - Networks: []adminpolicy.CIDR{"10.0.0.0/8"}, + Networks: []clusternetpol.CIDR{"10.0.0.0/8"}, }, }, }, { - Action: "Allow", - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + Action: "Accept", + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { - Networks: []adminpolicy.CIDR{"0.0.0.0/0"}, + Networks: []clusternetpol.CIDR{"0.0.0.0/0"}, }, }, }, @@ -2065,7 +2073,7 @@ var _ = Describe("Test BaselineAdminNetworkPolicy conversion", func() { } // Convert the policy - gnp := convertToGNP(&banp, nil) + gnp := convertToGNP(&bcnp, nil) // Check the selector is correct, and that the matches are sorted. Expect(gnp.Spec.NamespaceSelector).To(Equal("label == 'value' && label2 == 'value2'")) @@ -2073,7 +2081,7 @@ var _ = Describe("Test BaselineAdminNetworkPolicy conversion", func() { protoTCP := numorstring.ProtocolFromString("TCP") Expect(gnp.Spec.Ingress).To(ConsistOf( apiv3.Rule{ - Metadata: k8sAdminNetworkPolicyToCalicoMetadata("The first ingress rule"), + Metadata: k8sClusterNetworkPolicyToCalicoMetadata("The first ingress rule"), Action: "Allow", Protocol: &protoTCP, // Defaulted to TCP. Source: apiv3.EntityRule{ @@ -2088,7 +2096,7 @@ var _ = Describe("Test BaselineAdminNetworkPolicy conversion", func() { // There should be no Egress rules Expect(gnp.Spec.Egress).To(ConsistOf( apiv3.Rule{ - Metadata: k8sAdminNetworkPolicyToCalicoMetadata("The first egress rule"), + Metadata: k8sClusterNetworkPolicyToCalicoMetadata("The first egress rule"), Action: "Deny", Destination: apiv3.EntityRule{ Nets: []string{"10.0.0.0/8"}, @@ -2103,19 +2111,24 @@ var _ = Describe("Test BaselineAdminNetworkPolicy conversion", func() { )) }) - It("should parse a k8s BaselineAdminNetworkPolicy with an invalid networks peer", func() { - ports := []adminpolicy.AdminNetworkPolicyPort{ + It("should parse a k8s ClusterNetworkPolicy with an invalid networks peer", func() { + protos := []clusternetpol.ClusterNetworkPolicyProtocol{ { - PortNumber: &adminpolicy.Port{Port: 80}, + TCP: &clusternetpol.ClusterNetworkPolicyProtocolTCP{ + DestinationPort: &clusternetpol.Port{ + Number: 80, + }, + }, }, } - anp := adminpolicy.BaselineAdminNetworkPolicy{ + cnp := clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "default", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.BaselineAdminNetworkPolicySpec{ - Subject: adminpolicy.AdminNetworkPolicySubject{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ + Tier: clusternetpol.BaselineTier, + Subject: clusternetpol.ClusterNetworkPolicySubject{ Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ "label": "value", @@ -2123,21 +2136,21 @@ var _ = Describe("Test BaselineAdminNetworkPolicy conversion", func() { }, }, }, - Ingress: []adminpolicy.BaselineAdminNetworkPolicyIngressRule{ + Ingress: []clusternetpol.ClusterNetworkPolicyIngressRule{ { - Action: "Deny", - Ports: &ports, - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + Action: "Deny", + Protocols: protos, + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ {}, }, }, }, - Egress: []adminpolicy.BaselineAdminNetworkPolicyEgressRule{ + Egress: []clusternetpol.ClusterNetworkPolicyEgressRule{ { - Name: "A random egress rule", - Action: "Deny", - Ports: &ports, - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + Name: "A random egress rule", + Action: "Deny", + Protocols: protos, + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -2146,7 +2159,7 @@ var _ = Describe("Test BaselineAdminNetworkPolicy conversion", func() { }, }, { - Networks: []adminpolicy.CIDR{"10.10.10.0/24", "1.1.1.1/66"}, + Networks: []clusternetpol.CIDR{"10.10.10.0/24", "1.1.1.1/66"}, }, }, }, @@ -2154,25 +2167,25 @@ var _ = Describe("Test BaselineAdminNetworkPolicy conversion", func() { }, } - expectedErr := cerrors.ErrorAdminPolicyConversion{ + expectedErr := cerrors.ErrorClusterNetworkPolicyConversion{ PolicyName: "default", - Rules: []cerrors.ErrorAdminPolicyConversionRule{ + Rules: []cerrors.ErrorClusterNetworkPolicyConversionRule{ { - IngressRule: &adminpolicy.BaselineAdminNetworkPolicyIngressRule{ - Action: "Deny", - Ports: &ports, - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + IngressRule: &clusternetpol.ClusterNetworkPolicyIngressRule{ + Action: "Deny", + Protocols: protos, + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ {}, }, }, Reason: "k8s rule couldn't be converted: none of supported fields in 'From' is set.", }, { - EgressRule: &adminpolicy.BaselineAdminNetworkPolicyEgressRule{ - Name: "A random egress rule", - Action: "Deny", - Ports: &ports, - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + EgressRule: &clusternetpol.ClusterNetworkPolicyEgressRule{ + Name: "A random egress rule", + Action: "Deny", + Protocols: protos, + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { Namespaces: &metav1.LabelSelector{ MatchLabels: map[string]string{"k3": "v3"}, @@ -2180,7 +2193,7 @@ var _ = Describe("Test BaselineAdminNetworkPolicy conversion", func() { }, }, { - Networks: []adminpolicy.CIDR{"10.10.10.0/24", "1.1.1.1/66"}, + Networks: []clusternetpol.CIDR{"10.10.10.0/24", "1.1.1.1/66"}, }, }, }, @@ -2190,7 +2203,7 @@ var _ = Describe("Test BaselineAdminNetworkPolicy conversion", func() { } // Convert the policy - gnp := convertToGNP(&anp, &expectedErr) + gnp := convertToGNP(&cnp, &expectedErr) Expect(gnp.Spec.Ingress).To(ConsistOf( apiv3.Rule{ @@ -2204,3 +2217,36 @@ var _ = Describe("Test BaselineAdminNetworkPolicy conversion", func() { )) }) }) + +func convertToGNP( + cnp *clusternetpol.ClusterNetworkPolicy, + expectedErr *cerrors.ErrorClusterNetworkPolicyConversion, +) *apiv3.GlobalNetworkPolicy { + // Use a single instance of the Converter for these tests. + c := NewConverter() + + // Parse the policy. + pol, err := c.K8sClusterNetworkPolicyToCalico(cnp) + + if expectedErr == nil { + ExpectWithOffset(1, err).To(BeNil()) + } else { + ExpectWithOffset(1, err).To(Equal(*expectedErr)) + } + + // Assert key fields are correct. + tier := clusterNetworkPolicyTier(cnp) + + gnp, ok := pol.Value.(*apiv3.GlobalNetworkPolicy) + Expect(ok).To(BeTrue()) + + // Make sure the type information is correct. + Expect(gnp.Kind).To(Equal(apiv3.KindGlobalNetworkPolicy)) + Expect(gnp.APIVersion).To(Equal(apiv3.GroupVersionCurrent)) + + // Assert value fields are correct. + Expect(*gnp.Spec.Order).To(Equal(float64(cnp.Spec.Priority))) + Expect(gnp.Spec.Tier).To(Equal(tier)) + + return gnp +} diff --git a/libcalico-go/lib/backend/k8s/conversion/constants.go b/libcalico-go/lib/backend/k8s/conversion/constants.go index c79e3c4739c..e0870354022 100644 --- a/libcalico-go/lib/backend/k8s/conversion/constants.go +++ b/libcalico-go/lib/backend/k8s/conversion/constants.go @@ -46,7 +46,7 @@ const ( // AdminPolicyRuleNameLabel is a label that show a rule's name before conversion to Calico data model. // As an example, it holds an admin network policy rule name before conversion to GNPs. - AdminPolicyRuleNameLabel = "name" + K8sCNPRuleNameLabel = "name" // QoSControls related annotations AnnotationK8sQoSIngressBandwidth = "kubernetes.io/ingress-bandwidth" diff --git a/libcalico-go/lib/backend/k8s/conversion/conversion.go b/libcalico-go/lib/backend/k8s/conversion/conversion.go index 998cd4ff2d6..399fe15bc81 100644 --- a/libcalico-go/lib/backend/k8s/conversion/conversion.go +++ b/libcalico-go/lib/backend/k8s/conversion/conversion.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2024 Tigera, Inc. All rights reserved. +// Copyright (c) 2016-2025 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,7 +23,6 @@ import ( "github.com/google/uuid" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" - "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus" kapiv1 "k8s.io/api/core/v1" discovery "k8s.io/api/discovery/v1" @@ -31,7 +30,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" - adminpolicy "sigs.k8s.io/network-policy-api/apis/v1alpha1" + clusternetpol "sigs.k8s.io/network-policy-api/apis/v1alpha2" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" @@ -57,10 +56,8 @@ type Converter interface { IsScheduled(pod *kapiv1.Pod) bool IsHostNetworked(pod *kapiv1.Pod) bool HasIPAddress(pod *kapiv1.Pod) bool - StagedKubernetesNetworkPolicyToStagedName(stagedK8sName string) string K8sNetworkPolicyToCalico(np *networkingv1.NetworkPolicy) (*model.KVPair, error) - K8sAdminNetworkPolicyToCalico(anp *adminpolicy.AdminNetworkPolicy) (*model.KVPair, error) - K8sBaselineAdminNetworkPolicyToCalico(banp *adminpolicy.BaselineAdminNetworkPolicy) (*model.KVPair, error) + K8sClusterNetworkPolicyToCalico(kcnp *clusternetpol.ClusterNetworkPolicy) (*model.KVPair, error) EndpointSliceToKVP(svc *discovery.EndpointSlice) (*model.KVPair, error) ServiceToKVP(service *kapiv1.Service) (*model.KVPair, error) ProfileNameToNamespace(profileName string) (string, error) @@ -245,11 +242,6 @@ func getPodIPs(pod *kapiv1.Pod) ([]*cnet.IPNet, error) { return podIPNets, nil } -// StagedKubernetesNetworkPolicyToStagedName converts a StagedKubernetesNetworkPolicy name into a StagedNetworkPolicy name -func (c converter) StagedKubernetesNetworkPolicyToStagedName(stagedK8sName string) string { - return names.K8sNetworkPolicyNamePrefix + stagedK8sName -} - // EndpointSliceToKVP converts a k8s EndpointSlice to a model.KVPair. func (c converter) EndpointSliceToKVP(slice *discovery.EndpointSlice) (*model.KVPair, error) { return &model.KVPair{ @@ -275,489 +267,6 @@ func (c converter) ServiceToKVP(service *kapiv1.Service) (*model.KVPair, error) }, nil } -// K8sAdminNetworkPolicyToCalico converts a k8s AdminNetworkPolicy to a model.KVPair. -func (c converter) K8sAdminNetworkPolicyToCalico(anp *adminpolicy.AdminNetworkPolicy) (*model.KVPair, error) { - // Pull out important fields. - policyName := names.K8sAdminNetworkPolicyNamePrefix + anp.Name - order := float64(anp.Spec.Priority) - errorTracker := cerrors.ErrorAdminPolicyConversion{PolicyName: anp.Name} - - // Generate the ingress rules list. - var ingressRules []apiv3.Rule - for _, r := range anp.Spec.Ingress { - rules, err := k8sANPIngressRuleToCalico(r) - if err != nil { - log.WithError(err).Warn("dropping k8s rule that couldn't be converted.") - // Add rule to conversion error slice - errorTracker.BadIngressRule(&r, fmt.Sprintf("k8s rule couldn't be converted: %s", err)) - failClosedRule := k8sANPHandleFailedRules(r.Action) - if failClosedRule != nil { - ingressRules = append(ingressRules, *failClosedRule) - } - } else { - ingressRules = append(ingressRules, rules...) - } - } - - // Generate the egress rules list. - var egressRules []apiv3.Rule - for _, r := range anp.Spec.Egress { - rules, err := k8sANPEgressRuleToCalico(r) - if err != nil { - log.WithError(err).Warn("dropping k8s rule that couldn't be converted.") - // Add rule to conversion error slice - errorTracker.BadEgressRule(&r, fmt.Sprintf("k8s rule couldn't be converted: %s", err)) - failClosedRule := k8sANPHandleFailedRules(r.Action) - if failClosedRule != nil { - egressRules = append(egressRules, *failClosedRule) - } - } else { - egressRules = append(egressRules, rules...) - } - } - - // Either Namespaces or Pods is set. Use one of them to populate the selectors. - var nsSelector, podSelector string - if anp.Spec.Subject.Namespaces != nil { - nsSelector = k8sSelectorToCalico(anp.Spec.Subject.Namespaces, SelectorNamespace) - // Make sure projectcalico.org/orchestrator == 'k8s' label is added to exclude heps. - podSelector = k8sSelectorToCalico(nil, SelectorPod) - } else { - nsSelector = k8sSelectorToCalico(&anp.Spec.Subject.Pods.NamespaceSelector, SelectorNamespace) - podSelector = k8sSelectorToCalico(&anp.Spec.Subject.Pods.PodSelector, SelectorPod) - } - - var uid types.UID - var err error - if anp.UID != "" { - uid, err = ConvertUID(anp.UID) - if err != nil { - return nil, err - } - } - - gnp := apiv3.NewGlobalNetworkPolicy() - gnp.ObjectMeta = metav1.ObjectMeta{ - Name: policyName, - CreationTimestamp: anp.CreationTimestamp, - UID: uid, - ResourceVersion: anp.ResourceVersion, - } - gnp.Spec = apiv3.GlobalNetworkPolicySpec{ - Tier: names.AdminNetworkPolicyTierName, - Order: &order, - NamespaceSelector: nsSelector, - Selector: podSelector, - Ingress: ingressRules, - Egress: egressRules, - Types: c.calculateANPPolicyTypes(ingressRules, egressRules), - } - - // Build the KVPair. - kvp := &model.KVPair{ - Key: model.ResourceKey{ - Name: policyName, - Kind: apiv3.KindGlobalNetworkPolicy, - }, - Value: gnp, - Revision: anp.ResourceVersion, - } - - // Return the KVPair with conversion errors if applicable - return kvp, errorTracker.GetError() -} - -func k8sANPHandleFailedRules(action adminpolicy.AdminNetworkPolicyRuleAction) *apiv3.Rule { - if action == adminpolicy.AdminNetworkPolicyRuleActionDeny || - action == adminpolicy.AdminNetworkPolicyRuleActionPass { - logrus.Warn("replacing failed rule with a deny-all one.") - return &apiv3.Rule{ - Action: apiv3.Deny, - } - } - return nil -} - -func k8sANPIngressRuleToCalico(rule adminpolicy.AdminNetworkPolicyIngressRule) ([]apiv3.Rule, error) { - action, err := K8sAdminNetworkPolicyActionToCalico(rule.Action) - if err != nil { - return nil, err - } - return combinePortsWithANPIngressPeers(rule.Ports, rule.From, rule.Name, action) -} - -func k8sANPEgressRuleToCalico(rule adminpolicy.AdminNetworkPolicyEgressRule) ([]apiv3.Rule, error) { - action, err := K8sAdminNetworkPolicyActionToCalico(rule.Action) - if err != nil { - return nil, err - } - return combinePortsWithANPEgressPeers(rule.Ports, rule.To, rule.Name, action) -} - -// K8sBaselineAdminNetworkPolicyToCalico converts a k8s BaselineAdminNetworkPolicy to a model.KVPair. -func (c converter) K8sBaselineAdminNetworkPolicyToCalico(anp *adminpolicy.BaselineAdminNetworkPolicy) (*model.KVPair, error) { - // Pull out important fields. - policyName := names.K8sBaselineAdminNetworkPolicyNamePrefix + anp.Name - order := float64(1000) - errorTracker := cerrors.ErrorAdminPolicyConversion{PolicyName: anp.Name} - - // Generate the ingress rules list. - var ingressRules []apiv3.Rule - for _, r := range anp.Spec.Ingress { - rules, err := k8sBANPIngressRuleToCalico(r) - if err != nil { - log.WithError(err).Warn("dropping k8s rule that couldn't be converted.") - // Add rule to conversion error slice - errorTracker.BadIngressRule(&r, fmt.Sprintf("k8s rule couldn't be converted: %s", err)) - failClosedRule := k8sBANPHandleFailedRules(r.Action) - if failClosedRule != nil { - ingressRules = append(ingressRules, *failClosedRule) - } - } else { - ingressRules = append(ingressRules, rules...) - } - } - - // Generate the egress rules list. - var egressRules []apiv3.Rule - for _, r := range anp.Spec.Egress { - rules, err := k8sBANPEgressRuleToCalico(r) - if err != nil { - log.WithError(err).Warn("dropping k8s rule that couldn't be converted.") - // Add rule to conversion error slice - errorTracker.BadEgressRule(&r, fmt.Sprintf("k8s rule couldn't be converted: %s", err)) - failClosedRule := k8sBANPHandleFailedRules(r.Action) - if failClosedRule != nil { - egressRules = append(egressRules, *failClosedRule) - } - } else { - egressRules = append(egressRules, rules...) - } - } - - // Either Namespaces or Pods is set. Use one of them to populate the selectors. - var nsSelector, podSelector string - if anp.Spec.Subject.Namespaces != nil { - nsSelector = k8sSelectorToCalico(anp.Spec.Subject.Namespaces, SelectorNamespace) - // Make sure projectcalico.org/orchestrator == 'k8s' label is added to exclude heps. - podSelector = k8sSelectorToCalico(nil, SelectorPod) - } else { - nsSelector = k8sSelectorToCalico(&anp.Spec.Subject.Pods.NamespaceSelector, SelectorNamespace) - podSelector = k8sSelectorToCalico(&anp.Spec.Subject.Pods.PodSelector, SelectorPod) - } - - var uid types.UID - var err error - if anp.UID != "" { - uid, err = ConvertUID(anp.UID) - if err != nil { - return nil, err - } - } - - gnp := apiv3.NewGlobalNetworkPolicy() - gnp.ObjectMeta = metav1.ObjectMeta{ - Name: policyName, - CreationTimestamp: anp.CreationTimestamp, - UID: uid, - ResourceVersion: anp.ResourceVersion, - } - gnp.Spec = apiv3.GlobalNetworkPolicySpec{ - Tier: names.BaselineAdminNetworkPolicyTierName, - Order: &order, - NamespaceSelector: nsSelector, - Selector: podSelector, - Ingress: ingressRules, - Egress: egressRules, - Types: c.calculateANPPolicyTypes(ingressRules, egressRules), - } - - // Build the KVPair. - kvp := &model.KVPair{ - Key: model.ResourceKey{ - Name: policyName, - Kind: apiv3.KindGlobalNetworkPolicy, - }, - Value: gnp, - Revision: anp.ResourceVersion, - } - - // Return the KVPair with conversion errors if applicable - return kvp, errorTracker.GetError() -} - -func (c converter) calculateANPPolicyTypes(ingressRules []apiv3.Rule, egressRules []apiv3.Rule) []apiv3.PolicyType { - // Calculate Types setting. The ANP Tiers are default-Pass so the only - // reason to enable a policy type is if we have rules. - var policyTypes []apiv3.PolicyType - if len(ingressRules) != 0 { - policyTypes = append(policyTypes, apiv3.PolicyTypeIngress) - } - if len(egressRules) != 0 { - policyTypes = append(policyTypes, apiv3.PolicyTypeEgress) - } - return policyTypes -} - -func k8sBANPHandleFailedRules(action adminpolicy.BaselineAdminNetworkPolicyRuleAction) *apiv3.Rule { - if action == adminpolicy.BaselineAdminNetworkPolicyRuleActionDeny { - logrus.Warn("replacing failed rule with a deny-all one.") - return &apiv3.Rule{ - Action: apiv3.Deny, - } - } - return nil -} - -func k8sBANPIngressRuleToCalico(rule adminpolicy.BaselineAdminNetworkPolicyIngressRule) (rules []apiv3.Rule, err error) { - action, err := K8sBaselineAdminNetworkPolicyActionToCalico(rule.Action) - if err != nil { - return nil, err - } - return combinePortsWithANPIngressPeers(rule.Ports, rule.From, rule.Name, action) -} - -func combinePortsWithANPIngressPeers( - anpPorts *[]adminpolicy.AdminNetworkPolicyPort, - anpPeers []adminpolicy.AdminNetworkPolicyIngressPeer, - ruleName string, - action apiv3.Action, -) (rules []apiv3.Rule, err error) { - protocolPorts, sortedProtocols, err := unpackANPPorts(anpPorts) - if err != nil { - return nil, err - } - - // Combine destinations with sources to generate rules. We generate one rule per protocol, - // with each rule containing all the allowed ports. - for _, protocolStr := range sortedProtocols { - calicoPorts := protocolPorts[protocolStr] - calicoPorts = SimplifyPorts(calicoPorts) - - var protocol *numorstring.Protocol - if protocolStr != "" { - p := numorstring.ProtocolFromString(protocolStr) - protocol = &p - } - - // Based on specifications at least one Peer is set. - var selector, nsSelector string - for _, peer := range anpPeers { - var found bool - if peer.Namespaces != nil { - selector = "" - nsSelector = k8sSelectorToCalico(peer.Namespaces, SelectorNamespace) - found = true - } - if peer.Pods != nil { - selector = k8sSelectorToCalico(&peer.Pods.PodSelector, SelectorPod) - nsSelector = k8sSelectorToCalico(&peer.Pods.NamespaceSelector, SelectorNamespace) - found = true - } - if !found { - return nil, fmt.Errorf("none of supported fields in 'From' is set.") - } - - // Build inbound rule and append to list. - rules = append(rules, apiv3.Rule{ - Metadata: k8sAdminNetworkPolicyToCalicoMetadata(ruleName), - Action: action, - Protocol: protocol, - Source: apiv3.EntityRule{ - Selector: selector, - NamespaceSelector: nsSelector, - }, - Destination: apiv3.EntityRule{ - Ports: calicoPorts, - }, - }) - } - } - return rules, nil -} - -func unpackANPPorts(k8sPorts *[]adminpolicy.AdminNetworkPolicyPort) (map[string][]numorstring.Port, []string, error) { - // If there are no ports, represent that as zero struct. - ports := []adminpolicy.AdminNetworkPolicyPort{{}} - if k8sPorts != nil && len(*k8sPorts) != 0 { - ports = *k8sPorts - } - - protocolPorts := map[string][]numorstring.Port{} - - for _, port := range ports { - protocol, calicoPort, err := k8sAdminPolicyPortToCalicoFields(&port) - if err != nil { - return nil, nil, fmt.Errorf("failed to parse k8s port: %s", err) - } - - if protocol == nil && calicoPort == nil { - // If nil, no ports were specified, or an empty port struct was provided, which we translate to allowing all. - // We want to use a nil protocol and a nil list of ports, which will allow any destination (for ingress). - // Given we're gonna allow all, we may as well break here and keep only this rule - protocolPorts = map[string][]numorstring.Port{"": nil} - break - } - - pStr := protocol.String() - // treat nil as 'all ports' - if calicoPort == nil { - protocolPorts[pStr] = nil - } else if _, ok := protocolPorts[pStr]; !ok || len(protocolPorts[pStr]) > 0 { - // don't overwrite a nil (allow all ports) if present; if no ports yet for this protocol - // or 1+ ports which aren't 'all ports', then add the present ports - protocolPorts[pStr] = append(protocolPorts[pStr], *calicoPort) - } - } - - protocols := make([]string, 0, len(protocolPorts)) - for k := range protocolPorts { - protocols = append(protocols, k) - } - // Ensure deterministic output - sort.Strings(protocols) - return protocolPorts, protocols, nil -} - -func k8sBANPEgressRuleToCalico(rule adminpolicy.BaselineAdminNetworkPolicyEgressRule) ([]apiv3.Rule, error) { - action, err := K8sBaselineAdminNetworkPolicyActionToCalico(rule.Action) - if err != nil { - return nil, err - } - return combinePortsWithANPEgressPeers(rule.Ports, rule.To, rule.Name, action) -} - -func combinePortsWithANPEgressPeers( - rulePorts *[]adminpolicy.AdminNetworkPolicyPort, - rulePeers []adminpolicy.AdminNetworkPolicyEgressPeer, - ruleName string, - action apiv3.Action, -) (rules []apiv3.Rule, err error) { - protocolPorts, sortedProtocols, err := unpackANPPorts(rulePorts) - if err != nil { - return nil, err - } - - // Combine destinations with sources to generate rules. We generate one rule per protocol, - // with each rule containing all the allowed ports. - for _, protocolStr := range sortedProtocols { - calicoPorts := protocolPorts[protocolStr] - calicoPorts = SimplifyPorts(calicoPorts) - - var protocol *numorstring.Protocol - if protocolStr != "" { - p := numorstring.ProtocolFromString(protocolStr) - protocol = &p - } - - // Based on specifications at least one Peer is set. - for _, peer := range rulePeers { - var selector, nsSelector string - var nets []string - // One and only one of the following fields is set (based on specification). - var found bool - if peer.Namespaces != nil { - nsSelector = k8sSelectorToCalico(peer.Namespaces, SelectorNamespace) - found = true - } - if peer.Pods != nil { - selector = k8sSelectorToCalico(&peer.Pods.PodSelector, SelectorPod) - nsSelector = k8sSelectorToCalico(&peer.Pods.NamespaceSelector, SelectorNamespace) - found = true - } - if len(peer.Networks) != 0 { - for _, n := range peer.Networks { - _, ipNet, err := cnet.ParseCIDR(string(n)) - if err != nil { - return nil, fmt.Errorf("invalid CIDR in ANP rule: %w", err) - } - nets = append(nets, ipNet.String()) - } - found = true - } - if !found { - return nil, fmt.Errorf("none of supported fields in 'To' is set.") - } - - // Build outbound rule and append to list. - rules = append(rules, apiv3.Rule{ - Metadata: k8sAdminNetworkPolicyToCalicoMetadata(ruleName), - Action: action, - Protocol: protocol, - Destination: apiv3.EntityRule{ - Ports: calicoPorts, - Selector: selector, - NamespaceSelector: nsSelector, - Nets: nets, - }, - }) - } - } - - return rules, nil -} - -func K8sAdminNetworkPolicyActionToCalico(action adminpolicy.AdminNetworkPolicyRuleAction) (apiv3.Action, error) { - switch action { - case adminpolicy.AdminNetworkPolicyRuleActionAllow, - adminpolicy.AdminNetworkPolicyRuleActionDeny, - adminpolicy.AdminNetworkPolicyRuleActionPass: - return apiv3.Action(action), nil - default: - return "", fmt.Errorf("unsupported admin network policy action %v", action) - } -} - -func K8sBaselineAdminNetworkPolicyActionToCalico(action adminpolicy.BaselineAdminNetworkPolicyRuleAction) (apiv3.Action, error) { - switch action { - case adminpolicy.BaselineAdminNetworkPolicyRuleActionAllow, - adminpolicy.BaselineAdminNetworkPolicyRuleActionDeny: - return apiv3.Action(action), nil - default: - return "", fmt.Errorf("unsupported admin network policy action %v", action) - } -} - -func k8sAdminNetworkPolicyToCalicoMetadata(ruleName string) *apiv3.RuleMetadata { - if ruleName == "" { - return nil - } - return &apiv3.RuleMetadata{ - Annotations: map[string]string{ - AdminPolicyRuleNameLabel: ruleName, - }, - } -} - -func k8sAdminPolicyPortToCalicoFields(port *adminpolicy.AdminNetworkPolicyPort) ( - protocol *numorstring.Protocol, - dstPort *numorstring.Port, - err error, -) { - // If no port info, return zero values for all fields (protocol, dstPorts). - if port == nil { - return - } - // Only one of the PortNumber or PortRange is set. - if port.PortNumber != nil { - dstPort = k8sAdminPolicyPortToCalico(port.PortNumber) - proto := ensureProtocol(port.PortNumber.Protocol) - protocol = k8sProtocolToCalico(&proto) - return - } - if port.PortRange != nil { - dstPort, err = k8sAdminPolicyPortRangeToCalico(port.PortRange) - if err != nil { - return - } - proto := ensureProtocol(port.PortRange.Protocol) - protocol = k8sProtocolToCalico(&proto) - return - } - // TODO: Add support for NamedPorts - return -} - func ensureProtocol(proto kapiv1.Protocol) kapiv1.Protocol { if proto != "" { return proto @@ -765,30 +274,8 @@ func ensureProtocol(proto kapiv1.Protocol) kapiv1.Protocol { return kapiv1.ProtocolTCP } -func k8sAdminPolicyPortToCalico(port *adminpolicy.Port) *numorstring.Port { - if port == nil { - return nil - } - p := numorstring.SinglePort(uint16(port.Port)) - return &p -} - -func k8sAdminPolicyPortRangeToCalico(port *adminpolicy.PortRange) (*numorstring.Port, error) { - if port == nil { - return nil, nil - } - p, err := numorstring.PortFromRange(uint16(port.Start), uint16(port.End)) - if err != nil { - return nil, err - } - return &p, nil -} - // K8sNetworkPolicyToCalico converts a k8s NetworkPolicy to a model.KVPair. func (c converter) K8sNetworkPolicyToCalico(np *networkingv1.NetworkPolicy) (*model.KVPair, error) { - // Pull out important fields. - policyName := names.K8sNetworkPolicyNamePrefix + np.Name - // We insert all the NetworkPolicy Policies at order 1000.0 after conversion. // This order might change in future. order := float64(1000.0) @@ -864,7 +351,7 @@ func (c converter) K8sNetworkPolicyToCalico(np *networkingv1.NetworkPolicy) (*mo // Create the NetworkPolicy. policy := apiv3.NewNetworkPolicy() policy.ObjectMeta = metav1.ObjectMeta{ - Name: policyName, + Name: np.Name, Namespace: np.Namespace, CreationTimestamp: np.CreationTimestamp, UID: uid, @@ -872,6 +359,7 @@ func (c converter) K8sNetworkPolicyToCalico(np *networkingv1.NetworkPolicy) (*mo } policy.Spec = apiv3.NetworkPolicySpec{ Order: &order, + Tier: names.DefaultTierName, Selector: k8sSelectorToCalico(&np.Spec.PodSelector, SelectorPod), Ingress: ingressRules, Egress: egressRules, @@ -881,9 +369,9 @@ func (c converter) K8sNetworkPolicyToCalico(np *networkingv1.NetworkPolicy) (*mo // Build the KVPair. kvp := &model.KVPair{ Key: model.ResourceKey{ - Name: policyName, + Name: np.Name, Namespace: np.Namespace, - Kind: apiv3.KindNetworkPolicy, + Kind: model.KindKubernetesNetworkPolicy, }, Value: policy, Revision: np.ResourceVersion, @@ -1332,18 +820,6 @@ func (c converter) SplitProfileRevision(rev string) (nsRev string, saRev string, return } -func stringsToIPNets(ipStrings []string) ([]*cnet.IPNet, error) { - var podIPNets []*cnet.IPNet - for _, ip := range ipStrings { - _, ipNet, err := cnet.ParseCIDROrIP(ip) - if err != nil { - return nil, err - } - podIPNets = append(podIPNets, ipNet) - } - return podIPNets, nil -} - // ConvertUID converts a UID to a new UID in a deterministic way. This is useful when we want to generate a new UID // for a resource that is derived from another resource, but we don't want to use the same UID in order to // ensure that the new resource is treated as unique. This is important, as two objects with the same UID causes @@ -1364,19 +840,14 @@ func reverseUID(uid uuid.UUID) (uuid.UUID, error) { // v4 UUIDs used by Kubernetes use bits in the 7th byte to indicate the version and // bits in the 9th byte to indicate the variant. Reverse the bits in the surrounding bytes but leave these intact. nuid := make([]byte, len(uid)) - copy(nuid, uid[:]) - // Reverse the bits in the first 6 bytes. - for ii := range uid[:6] { - nuid[ii] = byte(bits.Reverse(uint(uid[ii])) >> 56) + // Reverse all bytes first. + for i := range len(uid) { + nuid[i] = bits.Reverse8(uid[i]) } - // Reverse the bits in the 8th byte. - nuid[7] = byte(bits.Reverse(uint(uid[7])) >> 56) + // Then restore version and variant. + nuid[6], nuid[8] = uid[6], uid[8] - // Reverse the bits in the remaining bytes. - for ii := range uid[9:] { - nuid[ii+9] = byte(bits.Reverse(uint(uid[ii+9])) >> 56) - } return uuid.FromBytes(nuid) } diff --git a/libcalico-go/lib/backend/k8s/conversion/conversion_suite_test.go b/libcalico-go/lib/backend/k8s/conversion/conversion_suite_test.go index b52c425e9f3..7f794e08e98 100644 --- a/libcalico-go/lib/backend/k8s/conversion/conversion_suite_test.go +++ b/libcalico-go/lib/backend/k8s/conversion/conversion_suite_test.go @@ -17,16 +17,16 @@ package conversion_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestConversion(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../../report/conversion_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Conversion Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../../report/conversion_suite.xml" + ginkgo.RunSpecs(t, "Conversion Suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/backend/k8s/conversion/conversion_test.go b/libcalico-go/lib/backend/k8s/conversion/conversion_test.go index 5ab1a5bbb1d..2543aff81da 100644 --- a/libcalico-go/lib/backend/k8s/conversion/conversion_test.go +++ b/libcalico-go/lib/backend/k8s/conversion/conversion_test.go @@ -19,8 +19,7 @@ import ( "strings" "github.com/google/uuid" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -30,7 +29,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" ) @@ -265,56 +264,56 @@ var _ = Describe("Test Pod conversion", func() { Expect(err).NotTo(HaveOccurred()) // Make sure the type information is correct. - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Kind).To(Equal(libapiv3.KindWorkloadEndpoint)) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).APIVersion).To(Equal(apiv3.GroupVersionCurrent)) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Kind).To(Equal(internalapi.KindWorkloadEndpoint)) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).APIVersion).To(Equal(apiv3.GroupVersionCurrent)) // Assert key fields. Expect(wep.Key.(model.ResourceKey).Name).To(Equal("nodeA-k8s-podA-eth0")) Expect(wep.Key.(model.ResourceKey).Namespace).To(Equal("default")) - Expect(wep.Key.(model.ResourceKey).Kind).To(Equal(libapiv3.KindWorkloadEndpoint)) - - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.Pod).To(Equal("podA")) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.Node).To(Equal("nodeA")) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.Endpoint).To(Equal("eth0")) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.Orchestrator).To(Equal("k8s")) - Expect(len(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNetworks)).To(Equal(1)) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNetworks[0]).To(Equal("192.168.0.1/32")) - Expect(len(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.Profiles)).To(Equal(1)) + Expect(wep.Key.(model.ResourceKey).Kind).To(Equal(internalapi.KindWorkloadEndpoint)) + + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.Pod).To(Equal("podA")) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.Node).To(Equal("nodeA")) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.Endpoint).To(Equal("eth0")) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.Orchestrator).To(Equal("k8s")) + Expect(len(wep.Value.(*internalapi.WorkloadEndpoint).Spec.IPNetworks)).To(Equal(1)) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.IPNetworks[0]).To(Equal("192.168.0.1/32")) + Expect(len(wep.Value.(*internalapi.WorkloadEndpoint).Spec.Profiles)).To(Equal(1)) expectedLabels := map[string]string{ "labelA": "valueA", "labelB": "valueB", "projectcalico.org/namespace": "default", "projectcalico.org/orchestrator": "k8s", } - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).ObjectMeta.Labels).To(Equal(expectedLabels)) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).ObjectMeta.Labels).To(Equal(expectedLabels)) nsProtoTCP := numorstring.ProtocolFromString("tcp") nsProtoUDP := numorstring.ProtocolFromString("udp") nsProtoSCTP := numorstring.ProtocolFromString("sctp") - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.Ports).To(ConsistOf( + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.Ports).To(ConsistOf( // No proto defaults to TCP (as defined in k8s API spec) - libapiv3.WorkloadEndpointPort{Name: "no-proto", Port: 1234, Protocol: nsProtoTCP}, + internalapi.WorkloadEndpointPort{Name: "no-proto", Port: 1234, Protocol: nsProtoTCP}, // Explicit TCP proto is OK too. - libapiv3.WorkloadEndpointPort{Name: "tcp-proto", Port: 1024, Protocol: nsProtoTCP}, + internalapi.WorkloadEndpointPort{Name: "tcp-proto", Port: 1024, Protocol: nsProtoTCP}, // Host port should be parsed - libapiv3.WorkloadEndpointPort{Name: "tcp-proto-with-host-port", Port: 8080, Protocol: nsProtoTCP, HostPort: 5678}, + internalapi.WorkloadEndpointPort{Name: "tcp-proto-with-host-port", Port: 8080, Protocol: nsProtoTCP, HostPort: 5678}, // Host IP should be passed through - libapiv3.WorkloadEndpointPort{Name: "tcp-proto-with-host-port-and-ip", Port: 8081, Protocol: nsProtoTCP, HostPort: 6789, HostIP: "1.2.3.4"}, + internalapi.WorkloadEndpointPort{Name: "tcp-proto-with-host-port-and-ip", Port: 8081, Protocol: nsProtoTCP, HostPort: 6789, HostIP: "1.2.3.4"}, // Host port but no name - libapiv3.WorkloadEndpointPort{Port: 500, Protocol: nsProtoTCP, HostPort: 5000}, + internalapi.WorkloadEndpointPort{Port: 500, Protocol: nsProtoTCP, HostPort: 5000}, // UDP is also an option. - libapiv3.WorkloadEndpointPort{Name: "udp-proto", Port: 432, Protocol: nsProtoUDP}, + internalapi.WorkloadEndpointPort{Name: "udp-proto", Port: 432, Protocol: nsProtoUDP}, // SCTP. - libapiv3.WorkloadEndpointPort{Name: "sctp-proto", Port: 891, Protocol: nsProtoSCTP}, + internalapi.WorkloadEndpointPort{Name: "sctp-proto", Port: 891, Protocol: nsProtoSCTP}, // initContainer sidecar with a named port - libapiv3.WorkloadEndpointPort{Name: "init-port", Port: 3000, Protocol: nsProtoTCP}, + internalapi.WorkloadEndpointPort{Name: "init-port", Port: 3000, Protocol: nsProtoTCP}, // Unknown protocol port is ignored. )) // Assert the interface name is fixed. The calculation of this name should be consistent // between releases otherwise there will be issues upgrading a node with networked Pods. // If this fails, fix the code not this expect! - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.InterfaceName).To(Equal("cali7f94ce7c295")) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.InterfaceName).To(Equal("cali7f94ce7c295")) // Assert ResourceVersion is present. Expect(wep.Revision).To(Equal("1234")) @@ -387,9 +386,9 @@ var _ = Describe("Test Pod conversion", func() { Expect(err).NotTo(HaveOccurred()) // Check both IPs were converted. - Expect(len(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNetworks)).To(Equal(2)) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNetworks[0]).To(Equal("192.168.0.1/32")) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNetworks[1]).To(Equal("fd5f:8067::1/128")) + Expect(len(wep.Value.(*internalapi.WorkloadEndpoint).Spec.IPNetworks)).To(Equal(2)) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.IPNetworks[0]).To(Equal("192.168.0.1/32")) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.IPNetworks[1]).To(Equal("fd5f:8067::1/128")) }) It("should look in the calico annotation for the IP", func() { @@ -416,7 +415,7 @@ var _ = Describe("Test Pod conversion", func() { wep, err := podToWorkloadEndpoint(c, &pod) Expect(err).NotTo(HaveOccurred()) Expect(c.HasIPAddress(&pod)).To(BeTrue()) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNetworks).To(ConsistOf("192.168.0.1/32")) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.IPNetworks).To(ConsistOf("192.168.0.1/32")) }) It("should look in the dual stack calico annotation for the IPs", func() { @@ -444,7 +443,7 @@ var _ = Describe("Test Pod conversion", func() { wep, err := podToWorkloadEndpoint(c, &pod) Expect(err).NotTo(HaveOccurred()) Expect(c.HasIPAddress(&pod)).To(BeTrue()) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNetworks).To(ConsistOf("192.168.0.1/32", "fd5f:8067::1/128")) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.IPNetworks).To(ConsistOf("192.168.0.1/32", "fd5f:8067::1/128")) }) It("should look for the AWS VPC CNI annotation for pod IPs", func() { @@ -471,7 +470,7 @@ var _ = Describe("Test Pod conversion", func() { wep, err := podToWorkloadEndpoint(c, &pod) Expect(err).NotTo(HaveOccurred()) Expect(c.HasIPAddress(&pod)).To(BeTrue()) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNetworks).To(ConsistOf("192.168.0.1/32")) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.IPNetworks).To(ConsistOf("192.168.0.1/32")) }) It("should handle IP annotations with /32 and /128", func() { @@ -501,7 +500,7 @@ var _ = Describe("Test Pod conversion", func() { wep, err := podToWorkloadEndpoint(c, &pod) Expect(err).NotTo(HaveOccurred()) Expect(c.HasIPAddress(&pod)).To(BeTrue()) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNetworks).To(ConsistOf("192.168.0.1/32", "fd5f:8067::1/128")) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.IPNetworks).To(ConsistOf("192.168.0.1/32", "fd5f:8067::1/128")) }) It("should look in the calico annotation for a floating IP", func() { @@ -529,10 +528,10 @@ var _ = Describe("Test Pod conversion", func() { wep, err := podToWorkloadEndpoint(c, &pod) Expect(err).NotTo(HaveOccurred()) Expect(c.HasIPAddress(&pod)).To(BeTrue()) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNetworks).To(ConsistOf("192.168.0.1/32")) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.IPNetworks).To(ConsistOf("192.168.0.1/32")) // Assert that the endpoint contains the appropriate DNAT - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNATs).To(ConsistOf(libapiv3.IPNAT{InternalIP: "192.168.0.1", ExternalIP: "1.1.1.1"})) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.IPNATs).To(ConsistOf(internalapi.IPNAT{InternalIP: "192.168.0.1", ExternalIP: "1.1.1.1"})) }) It("should find the right address family target for dual stack floating IPs", func() { @@ -561,12 +560,12 @@ var _ = Describe("Test Pod conversion", func() { wep, err := podToWorkloadEndpoint(c, &pod) Expect(err).NotTo(HaveOccurred()) Expect(c.HasIPAddress(&pod)).To(BeTrue()) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNetworks).To(ConsistOf("192.168.0.1/32", "fd5f:8067::1/128")) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.IPNetworks).To(ConsistOf("192.168.0.1/32", "fd5f:8067::1/128")) // Assert that the endpoint contains the appropriate DNAT - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNATs).To(ConsistOf( - libapiv3.IPNAT{InternalIP: "192.168.0.1", ExternalIP: "1.1.1.1"}, - libapiv3.IPNAT{InternalIP: "fd5f:8067::1", ExternalIP: "fd80:100:100::10"}, + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.IPNATs).To(ConsistOf( + internalapi.IPNAT{InternalIP: "192.168.0.1", ExternalIP: "1.1.1.1"}, + internalapi.IPNAT{InternalIP: "fd5f:8067::1", ExternalIP: "fd80:100:100::10"}, )) }) @@ -590,7 +589,7 @@ var _ = Describe("Test Pod conversion", func() { wep, err := podToWorkloadEndpoint(c, &pod) Expect(err).NotTo(HaveOccurred()) Expect(c.HasIPAddress(&pod)).To(BeTrue()) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.AllowSpoofedSourcePrefixes).To(ConsistOf([]string{"1.1.1.0/24", "8.8.8.8/32"})) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.AllowSpoofedSourcePrefixes).To(ConsistOf([]string{"1.1.1.0/24", "8.8.8.8/32"})) }) It("should error on empty string in the source spoofing disabling annotation", func() { @@ -657,7 +656,7 @@ var _ = Describe("Test Pod conversion", func() { wep, err := podToWorkloadEndpoint(c, &pod) Expect(err).NotTo(HaveOccurred()) Expect(c.HasIPAddress(&pod)).To(BeTrue()) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.AllowSpoofedSourcePrefixes).To(ConsistOf([]string{"1.1.0.0/16"})) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.AllowSpoofedSourcePrefixes).To(ConsistOf([]string{"1.1.0.0/16"})) }) It("should return an error for a bad pod IP", func() { @@ -745,7 +744,7 @@ var _ = Describe("Test Pod conversion", func() { wep, err := podToWorkloadEndpoint(c, &pod) Expect(err).NotTo(HaveOccurred()) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNetworks).To(ConsistOf("192.168.0.2/32")) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.IPNetworks).To(ConsistOf("192.168.0.2/32")) }) It("should treat running pod with empty podIP annotation and no deletion timestamp as Running", func() { @@ -773,7 +772,7 @@ var _ = Describe("Test Pod conversion", func() { Expect(err).NotTo(HaveOccurred()) Expect(c.HasIPAddress(&pod)).To(BeTrue()) Expect(IsFinished(&pod)).To(BeFalse()) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNetworks).To(ConsistOf("192.168.0.1/32")) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.IPNetworks).To(ConsistOf("192.168.0.1/32")) }) It("should treat running pod with empty podIP with a deletion timestamp as finished", func() { @@ -803,7 +802,7 @@ var _ = Describe("Test Pod conversion", func() { Expect(err).NotTo(HaveOccurred()) Expect(c.HasIPAddress(&pod)).To(BeTrue()) Expect(IsFinished(&pod)).To(BeTrue()) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNetworks).To(BeEmpty()) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.IPNetworks).To(BeEmpty()) }) It("should treat running pod with no podIP annotation with a deletion timestamp as running", func() { @@ -832,7 +831,7 @@ var _ = Describe("Test Pod conversion", func() { Expect(err).NotTo(HaveOccurred()) Expect(c.HasIPAddress(&pod)).To(BeTrue()) Expect(IsFinished(&pod)).To(BeFalse()) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNetworks).To(ConsistOf("192.168.0.1/32")) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.IPNetworks).To(ConsistOf("192.168.0.1/32")) }) It("should treat finished pod with no podIP annotation with a deletion timestamp as finished", func() { @@ -861,7 +860,7 @@ var _ = Describe("Test Pod conversion", func() { Expect(err).NotTo(HaveOccurred()) Expect(c.HasIPAddress(&pod)).To(BeTrue()) Expect(IsFinished(&pod)).To(BeTrue()) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNetworks).To(BeEmpty()) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.IPNetworks).To(BeEmpty()) }) DescribeTable("PodToDefaultWorkloadEndpoint reject/accept phase tests", @@ -887,9 +886,9 @@ var _ = Describe("Test Pod conversion", func() { kvp, err := podToWorkloadEndpoint(c, &pod) Expect(err).NotTo(HaveOccurred()) if expectedResult { - Expect(kvp.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNetworks).To(HaveLen(1)) + Expect(kvp.Value.(*internalapi.WorkloadEndpoint).Spec.IPNetworks).To(HaveLen(1)) } else { - Expect(kvp.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNetworks).To(HaveLen(0)) + Expect(kvp.Value.(*internalapi.WorkloadEndpoint).Spec.IPNetworks).To(HaveLen(0)) } }, Entry("Pending", kapiv1.PodPending, true), @@ -949,15 +948,15 @@ var _ = Describe("Test Pod conversion", func() { // Assert key fields. Expect(wep.Key.(model.ResourceKey).Name).To(Equal("nodeA-k8s-podB-eth0")) Expect(wep.Key.(model.ResourceKey).Namespace).To(Equal("default")) - Expect(wep.Key.(model.ResourceKey).Kind).To(Equal(libapiv3.KindWorkloadEndpoint)) + Expect(wep.Key.(model.ResourceKey).Kind).To(Equal(internalapi.KindWorkloadEndpoint)) // Assert value fields. - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.Pod).To(Equal("podB")) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.Node).To(Equal("nodeA")) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.Endpoint).To(Equal("eth0")) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.Orchestrator).To(Equal("k8s")) - Expect(len(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.IPNetworks)).To(Equal(1)) - Expect(len(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.Profiles)).To(Equal(1)) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).ObjectMeta.Labels).To(Equal(map[string]string{ + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.Pod).To(Equal("podB")) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.Node).To(Equal("nodeA")) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.Endpoint).To(Equal("eth0")) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.Orchestrator).To(Equal("k8s")) + Expect(len(wep.Value.(*internalapi.WorkloadEndpoint).Spec.IPNetworks)).To(Equal(1)) + Expect(len(wep.Value.(*internalapi.WorkloadEndpoint).Spec.Profiles)).To(Equal(1)) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).ObjectMeta.Labels).To(Equal(map[string]string{ "projectcalico.org/namespace": "default", "projectcalico.org/orchestrator": "k8s", })) @@ -965,7 +964,7 @@ var _ = Describe("Test Pod conversion", func() { // Assert the interface name is fixed. The calculation of this name should be consistent // between releases otherwise there will be issues upgrading a node with networked Pods. // If this fails, fix the code not this expect! - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.InterfaceName).To(Equal("cali92cf1f5e9f6")) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.InterfaceName).To(Equal("cali92cf1f5e9f6")) }) It("should not parse a Pod with no NodeName", func() { @@ -1033,16 +1032,16 @@ var _ = Describe("Test Pod conversion", func() { Expect(err).NotTo(HaveOccurred()) // Make sure the type information is correct. - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Kind).To(Equal(libapiv3.KindWorkloadEndpoint)) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).APIVersion).To(Equal(apiv3.GroupVersionCurrent)) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Kind).To(Equal(internalapi.KindWorkloadEndpoint)) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).APIVersion).To(Equal(apiv3.GroupVersionCurrent)) // Assert key fields. Expect(wep.Key.(model.ResourceKey).Name).To(Equal("nodeA-k8s-podA-eth0")) Expect(wep.Key.(model.ResourceKey).Namespace).To(Equal("default")) - Expect(wep.Key.(model.ResourceKey).Kind).To(Equal(libapiv3.KindWorkloadEndpoint)) + Expect(wep.Key.(model.ResourceKey).Kind).To(Equal(internalapi.KindWorkloadEndpoint)) // Check for only values that are ServiceAccount related. - Expect(len(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.Profiles)).To(Equal(2)) + Expect(len(wep.Value.(*internalapi.WorkloadEndpoint).Spec.Profiles)).To(Equal(2)) expectedLabels := map[string]string{ "labelA": "valueA", "labelB": "valueB", @@ -1050,8 +1049,8 @@ var _ = Describe("Test Pod conversion", func() { "projectcalico.org/orchestrator": "k8s", apiv3.LabelServiceAccount: "sa-test", } - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).ObjectMeta.Labels).To(Equal(expectedLabels)) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.ServiceAccountName).To(Equal("sa-test")) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).ObjectMeta.Labels).To(Equal(expectedLabels)) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.ServiceAccountName).To(Equal("sa-test")) // Assert ResourceVersion is present. Expect(wep.Revision).To(Equal("1234")) @@ -1107,24 +1106,24 @@ var _ = Describe("Test Pod conversion", func() { Expect(err).NotTo(HaveOccurred()) // Make sure the type information is correct. - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Kind).To(Equal(libapiv3.KindWorkloadEndpoint)) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).APIVersion).To(Equal(apiv3.GroupVersionCurrent)) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Kind).To(Equal(internalapi.KindWorkloadEndpoint)) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).APIVersion).To(Equal(apiv3.GroupVersionCurrent)) // Assert key fields. Expect(wep.Key.(model.ResourceKey).Name).To(Equal("nodeA-k8s-podA-eth0")) Expect(wep.Key.(model.ResourceKey).Namespace).To(Equal("default")) - Expect(wep.Key.(model.ResourceKey).Kind).To(Equal(libapiv3.KindWorkloadEndpoint)) + Expect(wep.Key.(model.ResourceKey).Kind).To(Equal(internalapi.KindWorkloadEndpoint)) // Check for only values that are ServiceAccount related. - Expect(len(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.Profiles)).To(Equal(2)) + Expect(len(wep.Value.(*internalapi.WorkloadEndpoint).Spec.Profiles)).To(Equal(2)) expectedLabels := map[string]string{ "labelA": "valueA", "labelB": "valueB", "projectcalico.org/namespace": "default", "projectcalico.org/orchestrator": "k8s", } - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).ObjectMeta.Labels).To(Equal(expectedLabels)) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.ServiceAccountName).To(Equal(longName)) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).ObjectMeta.Labels).To(Equal(expectedLabels)) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.ServiceAccountName).To(Equal(longName)) // Assert ResourceVersion is present. Expect(wep.Revision).To(Equal("1234")) @@ -1150,7 +1149,7 @@ var _ = Describe("Test Pod conversion", func() { Expect(err).NotTo(HaveOccurred()) // Make sure the GenerateName information is correct. - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).GenerateName).To(Equal(gname)) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).GenerateName).To(Equal(gname)) }) It("should pass network-status annotations from pod to workloadendpoint", func() { @@ -1181,7 +1180,7 @@ var _ = Describe("Test Pod conversion", func() { wep, err := podToWorkloadEndpoint(c, &pod) Expect(err).NotTo(HaveOccurred()) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Annotations).Should(HaveKeyWithValue("k8s.v1.cni.cncf.io/network-status", ` + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Annotations).Should(HaveKeyWithValue("k8s.v1.cni.cncf.io/network-status", ` [{ "name": "k8s-pod-network", "ips": [ @@ -1232,8 +1231,8 @@ var _ = Describe("Test Pod conversion", func() { wep, err := podToWorkloadEndpoint(c, &pod) Expect(err).NotTo(HaveOccurred()) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.QoSControls).ToNot(BeNil()) - expectedQoSControls := &libapiv3.QoSControls{ + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.QoSControls).ToNot(BeNil()) + expectedQoSControls := &internalapi.QoSControls{ IngressBandwidth: 1000000, EgressBandwidth: 2000000, IngressBurst: 3000000, @@ -1250,7 +1249,7 @@ var _ = Describe("Test Pod conversion", func() { EgressMaxConnections: 14000000, DSCP: &dscp, } - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.QoSControls).To(BeEquivalentTo(expectedQoSControls)) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.QoSControls).To(BeEquivalentTo(expectedQoSControls)) }) It("should cap invalid QoSControl annotations to min/max values", func() { @@ -1289,8 +1288,8 @@ var _ = Describe("Test Pod conversion", func() { wep, err := podToWorkloadEndpoint(c, &pod) Expect(err).NotTo(HaveOccurred()) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.QoSControls).ToNot(BeNil()) - expectedQoSControls := &libapiv3.QoSControls{ + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.QoSControls).ToNot(BeNil()) + expectedQoSControls := &internalapi.QoSControls{ IngressBandwidth: 1000, EgressBandwidth: 1000000000000000, IngressBurst: 1000, @@ -1306,7 +1305,7 @@ var _ = Describe("Test Pod conversion", func() { IngressMaxConnections: 1, EgressMaxConnections: 4294967295, } - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.QoSControls).To(BeEquivalentTo(expectedQoSControls)) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.QoSControls).To(BeEquivalentTo(expectedQoSControls)) }) It("should ignore burst QoSControl annotation if bandwidth is not present", func() { @@ -1333,7 +1332,7 @@ var _ = Describe("Test Pod conversion", func() { wep, err := podToWorkloadEndpoint(c, &pod) Expect(err).NotTo(HaveOccurred()) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.QoSControls).To(BeNil()) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.QoSControls).To(BeNil()) }) It("should ignore peakrate QoSControl annotation if bandwidth is not present", func() { @@ -1360,7 +1359,7 @@ var _ = Describe("Test Pod conversion", func() { wep, err := podToWorkloadEndpoint(c, &pod) Expect(err).NotTo(HaveOccurred()) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.QoSControls).To(BeNil()) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.QoSControls).To(BeNil()) }) It("should ignore minburst QoSControl annotation if peakrate is not present", func() { @@ -1387,7 +1386,7 @@ var _ = Describe("Test Pod conversion", func() { wep, err := podToWorkloadEndpoint(c, &pod) Expect(err).NotTo(HaveOccurred()) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.QoSControls).To(BeNil()) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.QoSControls).To(BeNil()) }) It("should ignore packet burst QoSControl annotation if packet rate is not present", func() { @@ -1414,7 +1413,7 @@ var _ = Describe("Test Pod conversion", func() { wep, err := podToWorkloadEndpoint(c, &pod) Expect(err).NotTo(HaveOccurred()) - Expect(wep.Value.(*libapiv3.WorkloadEndpoint).Spec.QoSControls).To(BeNil()) + Expect(wep.Value.(*internalapi.WorkloadEndpoint).Spec.QoSControls).To(BeNil()) }) }) @@ -1445,7 +1444,7 @@ var _ = Describe("Test UID conversion", func() { }) It("should always maintain the v4 version metadata bits, and convert back", func() { - for i := 0; i < 100000; i++ { + for range 100000 { original := types.UID(uuid.New().String()) converted, err := ConvertUID(original) Expect(err).NotTo(HaveOccurred()) @@ -1512,7 +1511,7 @@ var _ = Describe("Test NetworkPolicy conversion", func() { Expect(pol.Value.(*apiv3.NetworkPolicy).APIVersion).To(Equal(apiv3.GroupVersionCurrent)) // Assert key fields are correct. - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) // Assert value fields are correct. Expect(int(*pol.Value.(*apiv3.NetworkPolicy).Spec.Order)).To(Equal(1000)) @@ -1983,7 +1982,7 @@ var _ = Describe("Test NetworkPolicy conversion", func() { Expect(err).NotTo(HaveOccurred()) // Assert key fields are correct. - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) // Assert value fields are correct. Expect(int(*pol.Value.(*apiv3.NetworkPolicy).Spec.Order)).To(Equal(1000)) @@ -2017,7 +2016,7 @@ var _ = Describe("Test NetworkPolicy conversion", func() { }) By("generating the correct name and namespace", func() { - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.testPolicy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("testPolicy")) Expect(pol.Key.(model.ResourceKey).Namespace).To(Equal("default")) }) @@ -2086,9 +2085,9 @@ var _ = Describe("Test NetworkPolicy conversion", func() { By("parsing the policy", func() { pol, err = c.K8sNetworkPolicyToCalico(&np) Expect(err).NotTo(HaveOccurred()) - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) Expect(pol.Key.(model.ResourceKey).Namespace).To(Equal("default")) - Expect(pol.Value.(*apiv3.NetworkPolicy).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Value.(*apiv3.NetworkPolicy).Name).To(Equal("test.policy")) Expect(pol.Value.(*apiv3.NetworkPolicy).Namespace).To(Equal("default")) Expect(int(*pol.Value.(*apiv3.NetworkPolicy).Spec.Order)).To(Equal(1000)) }) @@ -2158,7 +2157,7 @@ var _ = Describe("Test NetworkPolicy conversion", func() { Expect(pol.Value.(*apiv3.NetworkPolicy).APIVersion).To(Equal(apiv3.GroupVersionCurrent)) // Assert key fields are correct. - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) // Assert value fields are correct. Expect(int(*pol.Value.(*apiv3.NetworkPolicy).Spec.Order)).To(Equal(1000)) @@ -2242,9 +2241,9 @@ var _ = Describe("Test NetworkPolicy conversion", func() { By("parsing the policy", func() { pol, err = c.K8sNetworkPolicyToCalico(&np) Expect(err).NotTo(HaveOccurred()) - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) Expect(pol.Key.(model.ResourceKey).Namespace).To(Equal("default")) - Expect(pol.Value.(*apiv3.NetworkPolicy).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Value.(*apiv3.NetworkPolicy).Name).To(Equal("test.policy")) Expect(pol.Value.(*apiv3.NetworkPolicy).Namespace).To(Equal("default")) Expect(int(*pol.Value.(*apiv3.NetworkPolicy).Spec.Order)).To(Equal(1000)) }) @@ -2297,7 +2296,7 @@ var _ = Describe("Test NetworkPolicy conversion", func() { Expect(err).NotTo(HaveOccurred()) // Assert key fields are correct. - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) // Assert value fields are correct. Expect(int(*pol.Value.(*apiv3.NetworkPolicy).Spec.Order)).To(Equal(1000)) @@ -2346,7 +2345,7 @@ var _ = Describe("Test NetworkPolicy conversion", func() { Expect(err).NotTo(HaveOccurred()) // Assert key fields are correct. - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) // Assert value fields are correct. Expect(int(*pol.Value.(*apiv3.NetworkPolicy).Spec.Order)).To(Equal(1000)) @@ -2393,7 +2392,7 @@ var _ = Describe("Test NetworkPolicy conversion", func() { Expect(err).NotTo(HaveOccurred()) // Assert key fields are correct. - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) // Assert value fields are correct. Expect(int(*pol.Value.(*apiv3.NetworkPolicy).Spec.Order)).To(Equal(1000)) @@ -2441,7 +2440,7 @@ var _ = Describe("Test NetworkPolicy conversion", func() { Expect(err).NotTo(HaveOccurred()) // Assert key fields are correct. - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) // Assert value fields are correct. Expect(int(*pol.Value.(*apiv3.NetworkPolicy).Spec.Order)).To(Equal(1000)) @@ -2498,7 +2497,7 @@ var _ = Describe("Test NetworkPolicy conversion", func() { Expect(err).NotTo(HaveOccurred()) // Assert key fields are correct. - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) // Assert value fields are correct. Expect(int(*pol.Value.(*apiv3.NetworkPolicy).Spec.Order)).To(Equal(1000)) @@ -2541,7 +2540,7 @@ var _ = Describe("Test NetworkPolicy conversion", func() { Expect(err).NotTo(HaveOccurred()) // Assert key fields are correct. - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) // Assert value fields are correct. Expect(int(*pol.Value.(*apiv3.NetworkPolicy).Spec.Order)).To(Equal(1000)) @@ -2586,7 +2585,7 @@ var _ = Describe("Test NetworkPolicy conversion", func() { Expect(err).NotTo(HaveOccurred()) // Assert key fields are correct. - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) // Assert value fields are correct. Expect(int(*pol.Value.(*apiv3.NetworkPolicy).Spec.Order)).To(Equal(1000)) @@ -2636,7 +2635,7 @@ var _ = Describe("Test NetworkPolicy conversion", func() { Expect(err).NotTo(HaveOccurred()) // Assert key fields are correct. - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) // Assert value fields are correct. Expect(int(*pol.Value.(*apiv3.NetworkPolicy).Spec.Order)).To(Equal(1000)) @@ -2683,7 +2682,7 @@ var _ = Describe("Test NetworkPolicy conversion", func() { Expect(err).NotTo(HaveOccurred()) // Assert key fields are correct. - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) // Assert value fields are correct. Expect(int(*pol.Value.(*apiv3.NetworkPolicy).Spec.Order)).To(Equal(1000)) @@ -2733,7 +2732,7 @@ var _ = Describe("Test NetworkPolicy conversion", func() { Expect(err).NotTo(HaveOccurred()) // Assert key fields are correct. - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) // Assert value fields are correct. Expect(int(*pol.Value.(*apiv3.NetworkPolicy).Spec.Order)).To(Equal(1000)) @@ -2781,7 +2780,7 @@ var _ = Describe("Test NetworkPolicy conversion", func() { Expect(err).NotTo(HaveOccurred()) // Assert key fields are correct. - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) // Assert value fields are correct. Expect(int(*pol.Value.(*apiv3.NetworkPolicy).Spec.Order)).To(Equal(1000)) @@ -2829,7 +2828,7 @@ var _ = Describe("Test NetworkPolicy conversion", func() { Expect(err).NotTo(HaveOccurred()) // Assert key fields are correct. - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) // Assert value fields are correct. Expect(int(*pol.Value.(*apiv3.NetworkPolicy).Spec.Order)).To(Equal(1000)) @@ -2893,7 +2892,7 @@ var _ = Describe("Test NetworkPolicy conversion", func() { Expect(err).NotTo(HaveOccurred()) // Assert key fields are correct. - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) eightyName, _ := numorstring.PortFromString("80") ninetyName, _ := numorstring.PortFromString("90") @@ -3023,7 +3022,7 @@ var _ = Describe("Test NetworkPolicy conversion", func() { Expect(err).NotTo(HaveOccurred()) // Assert key fields are correct. - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) // Assert value fields are correct. Expect(int(*pol.Value.(*apiv3.NetworkPolicy).Spec.Order)).To(Equal(1000)) @@ -3095,7 +3094,7 @@ var _ = Describe("Test NetworkPolicy conversion (k8s <= 1.7, no policyTypes)", f Expect(err).NotTo(HaveOccurred()) // Assert key fields are correct. - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) // Assert value fields are correct. Expect(int(*pol.Value.(*apiv3.NetworkPolicy).Spec.Order)).To(Equal(1000)) @@ -3141,7 +3140,7 @@ var _ = Describe("Test NetworkPolicy conversion (k8s <= 1.7, no policyTypes)", f Expect(err).NotTo(HaveOccurred()) // Assert key fields are correct. - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) // Assert value fields are correct. Expect(int(*pol.Value.(*apiv3.NetworkPolicy).Spec.Order)).To(Equal(1000)) @@ -3198,9 +3197,9 @@ var _ = Describe("Test NetworkPolicy conversion (k8s <= 1.7, no policyTypes)", f By("parsing the policy", func() { pol, err = c.K8sNetworkPolicyToCalico(&np) Expect(err).NotTo(HaveOccurred()) - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) Expect(pol.Key.(model.ResourceKey).Namespace).To(Equal("default")) - Expect(pol.Value.(*apiv3.NetworkPolicy).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Value.(*apiv3.NetworkPolicy).Name).To(Equal("test.policy")) Expect(pol.Value.(*apiv3.NetworkPolicy).Namespace).To(Equal("default")) Expect(int(*pol.Value.(*apiv3.NetworkPolicy).Spec.Order)).To(Equal(1000)) }) @@ -3278,9 +3277,9 @@ var _ = Describe("Test NetworkPolicy conversion (k8s <= 1.7, no policyTypes)", f By("parsing the policy", func() { pol, err = c.K8sNetworkPolicyToCalico(&np) Expect(err).NotTo(HaveOccurred()) - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) Expect(pol.Key.(model.ResourceKey).Namespace).To(Equal("default")) - Expect(pol.Value.(*apiv3.NetworkPolicy).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Value.(*apiv3.NetworkPolicy).Name).To(Equal("test.policy")) Expect(pol.Value.(*apiv3.NetworkPolicy).Namespace).To(Equal("default")) Expect(int(*pol.Value.(*apiv3.NetworkPolicy).Spec.Order)).To(Equal(1000)) }) @@ -3332,7 +3331,7 @@ var _ = Describe("Test NetworkPolicy conversion (k8s <= 1.7, no policyTypes)", f Expect(err).NotTo(HaveOccurred()) // Assert key fields are correct. - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) // Assert value fields are correct. Expect(int(*pol.Value.(*apiv3.NetworkPolicy).Spec.Order)).To(Equal(1000)) @@ -3372,7 +3371,7 @@ var _ = Describe("Test NetworkPolicy conversion (k8s <= 1.7, no policyTypes)", f Expect(err).NotTo(HaveOccurred()) // Assert key fields are correct. - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) // Assert value fields are correct. Expect(int(*pol.Value.(*apiv3.NetworkPolicy).Spec.Order)).To(Equal(1000)) @@ -3416,7 +3415,7 @@ var _ = Describe("Test NetworkPolicy conversion (k8s <= 1.7, no policyTypes)", f Expect(err).NotTo(HaveOccurred()) // Assert key fields are correct. - Expect(pol.Key.(model.ResourceKey).Name).To(Equal("knp.default.test.policy")) + Expect(pol.Key.(model.ResourceKey).Name).To(Equal("test.policy")) // Assert value fields are correct. Expect(int(*pol.Value.(*apiv3.NetworkPolicy).Spec.Order)).To(Equal(1000)) @@ -3740,7 +3739,7 @@ var _ = Describe("Test ServiceAccount conversion", func() { var _ = DescribeTable("Test port simplification", func(inputPorts string, expectedOutput string) { var ports []numorstring.Port - for _, p := range strings.Split(inputPorts, ",") { + for p := range strings.SplitSeq(inputPorts, ",") { if p == "" { continue } diff --git a/libcalico-go/lib/backend/k8s/conversion/workload_endpoint_default.go b/libcalico-go/lib/backend/k8s/conversion/workload_endpoint_default.go index 036e2173c79..3c59de8fee4 100644 --- a/libcalico-go/lib/backend/k8s/conversion/workload_endpoint_default.go +++ b/libcalico-go/lib/backend/k8s/conversion/workload_endpoint_default.go @@ -19,6 +19,7 @@ import ( "encoding/hex" "errors" "fmt" + "maps" "math" "os" "strconv" @@ -31,7 +32,7 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/json" "github.com/projectcalico/calico/libcalico-go/lib/names" @@ -44,8 +45,8 @@ var ( maxBandwidth = resource.MustParse("1P") // Burst sizes in bits minBurst = resource.MustParse("1k") - defaultBurst = resource.MustParse("4Gi") // 512 Mi bytes - maxBurst = resource.MustParse(strconv.Itoa(math.MaxUint32 * 8)) // 34359738360, approx. 4Gi bytes + defaultBurst = resource.MustParse("4Gi") // 512 Mi bytes + maxBurst = resource.MustParse(strconv.FormatUint(math.MaxUint32*8, 10)) // 34359738360, approx. 4Gi bytes // Peakrate in bits per second // Peakrate should always be greater than bandwidth, so we make the min and max slightly higher than those minPeakrate = resource.MustParse("1.01k") @@ -66,7 +67,7 @@ var ( minNumConnections = resource.MustParse("1") // The connection limit is an uint32 (maximum value 4294967295). // See https://github.com/torvalds/linux/blob/16b70698aa3ae7888826d0c84567c72241cf6713/include/uapi/linux/netfilter/xt_connlimit.h#L25 - maxNumConnections = resource.MustParse(strconv.Itoa(math.MaxUint32)) + maxNumConnections = resource.MustParse(strconv.FormatUint(math.MaxUint32, 10)) ) type defaultWorkloadEndpointConverter struct{} @@ -77,7 +78,7 @@ func (wc defaultWorkloadEndpointConverter) VethNameForWorkload(namespace, podnam // A SHA1 is always 20 bytes long, and so is sufficient for generating the // veth name and mac addr. h := sha1.New() - h.Write([]byte(fmt.Sprintf("%s.%s", namespace, podname))) + h.Write(fmt.Appendf(nil, "%s.%s", namespace, podname)) prefix := os.Getenv("FELIX_INTERFACEPREFIX") if prefix == "" { // Prefix is not set. Default to "cali" @@ -156,9 +157,7 @@ func (wc defaultWorkloadEndpointConverter) podToDefaultWorkloadEndpoint(pod *kap // Build the labels map. Start with the pod labels, and append two additional labels for // namespace and orchestrator matches. labels := make(map[string]string) - for k, v := range pod.Labels { - labels[k] = v - } + maps.Copy(labels, pod.Labels) labels[apiv3.LabelNamespace] = pod.Namespace labels[apiv3.LabelOrchestrator] = apiv3.OrchestratorKubernetes @@ -168,7 +167,7 @@ func (wc defaultWorkloadEndpointConverter) podToDefaultWorkloadEndpoint(pod *kap } // Pull out floating IP annotation - var floatingIPs []libapiv3.IPNAT + var floatingIPs []internalapi.IPNAT if annotation, ok := pod.Annotations["cni.projectcalico.org/floatingIPs"]; ok && len(podIPNets) > 0 { // Parse Annotation data var ips []string @@ -198,14 +197,14 @@ func (wc defaultWorkloadEndpointConverter) podToDefaultWorkloadEndpoint(pod *kap for _, ip := range ips { if strings.Contains(ip, ":") { if podnetV6 != nil { - floatingIPs = append(floatingIPs, libapiv3.IPNAT{ + floatingIPs = append(floatingIPs, internalapi.IPNAT{ InternalIP: podnetV6.IP.String(), ExternalIP: ip, }) } } else { if podnetV4 != nil { - floatingIPs = append(floatingIPs, libapiv3.IPNAT{ + floatingIPs = append(floatingIPs, internalapi.IPNAT{ InternalIP: podnetV4.IP.String(), ExternalIP: ip, }) @@ -221,7 +220,7 @@ func (wc defaultWorkloadEndpointConverter) podToDefaultWorkloadEndpoint(pod *kap } // Map any named ports through. - var endpointPorts []libapiv3.WorkloadEndpointPort + var endpointPorts []internalapi.WorkloadEndpointPort endpointPorts = appendEndpointPorts(endpointPorts, pod, pod.Spec.Containers) endpointPorts = appendEndpointPorts(endpointPorts, pod, pod.Spec.InitContainers) @@ -236,7 +235,7 @@ func (wc defaultWorkloadEndpointConverter) podToDefaultWorkloadEndpoint(pod *kap } // Create the workload endpoint. - wep := libapiv3.NewWorkloadEndpoint() + wep := internalapi.NewWorkloadEndpoint() wep.ObjectMeta = metav1.ObjectMeta{ Name: wepName, Namespace: pod.Namespace, @@ -245,7 +244,7 @@ func (wc defaultWorkloadEndpointConverter) podToDefaultWorkloadEndpoint(pod *kap Labels: labels, GenerateName: pod.GenerateName, } - wep.Spec = libapiv3.WorkloadEndpointSpec{ + wep.Spec = internalapi.WorkloadEndpointSpec{ Orchestrator: "k8s", Node: pod.Spec.NodeName, Pod: pod.Name, @@ -273,7 +272,7 @@ func (wc defaultWorkloadEndpointConverter) podToDefaultWorkloadEndpoint(pod *kap Key: model.ResourceKey{ Name: wepName, Namespace: pod.Namespace, - Kind: libapiv3.KindWorkloadEndpoint, + Kind: internalapi.KindWorkloadEndpoint, }, Value: wep, Revision: pod.ResourceVersion, @@ -281,7 +280,7 @@ func (wc defaultWorkloadEndpointConverter) podToDefaultWorkloadEndpoint(pod *kap return &kvp, nil } -func appendEndpointPorts(ports []libapiv3.WorkloadEndpointPort, pod *kapiv1.Pod, containers []kapiv1.Container) []libapiv3.WorkloadEndpointPort { +func appendEndpointPorts(ports []internalapi.WorkloadEndpointPort, pod *kapiv1.Pod, containers []kapiv1.Container) []internalapi.WorkloadEndpointPort { for _, container := range containers { for _, containerPort := range container.Ports { if containerPort.ContainerPort != 0 && (containerPort.HostPort != 0 || containerPort.Name != "") { @@ -302,7 +301,7 @@ func appendEndpointPorts(ports []libapiv3.WorkloadEndpointPort, pod *kapiv1.Pod, continue } - ports = append(ports, libapiv3.WorkloadEndpointPort{ + ports = append(ports, internalapi.WorkloadEndpointPort{ Name: containerPort.Name, Protocol: modelProto, Port: uint16(containerPort.ContainerPort), @@ -339,8 +338,8 @@ func HandleSourceIPSpoofingAnnotation(annot map[string]string) ([]string, error) return sourcePrefixes, nil } -func handleQoSControlsAnnotations(annotations map[string]string) (*libapiv3.QoSControls, error) { - qosControls := &libapiv3.QoSControls{} +func handleQoSControlsAnnotations(annotations map[string]string) (*internalapi.QoSControls, error) { + qosControls := &internalapi.QoSControls{} var errs []error // k8s bandwidth annotations @@ -541,7 +540,7 @@ func handleQoSControlsAnnotations(annotations map[string]string) (*libapiv3.QoSC } // return nil if no control is configured - if (*qosControls == libapiv3.QoSControls{}) { + if (*qosControls == internalapi.QoSControls{}) { qosControls = nil } diff --git a/libcalico-go/lib/backend/k8s/discovery.go b/libcalico-go/lib/backend/k8s/discovery.go new file mode 100644 index 00000000000..596b37c554f --- /dev/null +++ b/libcalico-go/lib/backend/k8s/discovery.go @@ -0,0 +1,80 @@ +// Copyright (c) 2016-2025 Tigera, Inc. All rights reserved. + +// 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 k8s + +import ( + "strings" + + apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "github.com/sirupsen/logrus" + log "github.com/sirupsen/logrus" + + "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" + v1scheme "github.com/projectcalico/calico/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme" + "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/resources" +) + +// BackendAPIGroup returns the API group that should be used for the Calico CRD +// backend, based on the configuration and/or auto-discovery of the API server. +func BackendAPIGroup(cfg *apiconfig.CalicoAPIConfigSpec) resources.BackingAPIGroup { + if UsingV3CRDs(cfg) { + return resources.BackingAPIGroupV3 + } + return resources.BackingAPIGroupV1 +} + +// UsingV3CRDs determines whether or not we should be using the projectcalico.org/v3 API group +// for Calico CRDs, or the crd.projectcalico.org/v1 API group. This is determined either by +// explicit configuration (if specified), or by auto-discovery of the API groups supported by the API server. +func UsingV3CRDs(cfg *apiconfig.CalicoAPIConfigSpec) bool { + if cfg != nil && cfg.CalicoAPIGroup != "" && cfg.DatastoreType == apiconfig.Kubernetes { + logrus.WithField("apiGroup", cfg.CalicoAPIGroup).Info("Using explicitly configured Calico API group") + return strings.EqualFold(cfg.CalicoAPIGroup, apiv3.GroupVersionCurrent) + } + logrus.Info("No explicit Calico API group configured, attempting to auto-discover") + + // Try to perform auto-discovery of the API group, by contacting the API server. + // If we can't contact the API server, we default to not using v3 CRDs. + _, cs, err := CreateKubernetesClientset(cfg) + if err != nil { + log.WithError(err).Warn("Failed to create clientset, cannot autodiscover API group, defaulting to crd.projectcalico.org/v1") + return false + } + apiGroups, err := cs.Discovery().ServerGroups() + if err != nil { + log.WithError(err).Warn("Failed to query API server for supported API groups, cannot autodiscover API group, defaulting to crd.projectcalico.org/v1") + return false + } + + v3present, v1present := false, false + for _, g := range apiGroups.Groups { + if g.Name == apiv3.GroupName { + v3present = true + } + if g.Name == v1scheme.GroupName { + v1present = true + } + } + + logrus.WithFields(log.Fields{ + "v3present": v3present, + "v1present": v1present, + }).Info("Auto-discovered Calico API groups") + + // If v3 is present but v1 is not, this means we should use the projectcalico.org/v3 API group. + // If both are present, it likely means that crd.projectcalico.org/v1 is present and used to implement the + // projectcalico.org/v3 API group via the API server, so we should use crd.projectcalico.org/v1 directly. + return v3present && !v1present +} diff --git a/libcalico-go/lib/backend/k8s/rawcrdclient/raw_crd_client.go b/libcalico-go/lib/backend/k8s/rawcrdclient/raw_crd_client.go index caf17cebddf..18da5192ea2 100644 --- a/libcalico-go/lib/backend/k8s/rawcrdclient/raw_crd_client.go +++ b/libcalico-go/lib/backend/k8s/rawcrdclient/raw_crd_client.go @@ -15,17 +15,21 @@ package rawcrdclient import ( + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" - schemecrdv1 "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/scheme" + schemecrdv1 "github.com/projectcalico/calico/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme" ) // New returns a new controller-runtime client configured to access // raw CRDs. This is used in tests to create invalid or pre-upgrade resources // that cannot be created with the main client due to its validation(!) -func New(cfg *rest.Config) (client.Client, error) { +// +// If usev3 is true, the client will be configured to use the projectcalico.org/v3 API group. Otherwise, it will +// use the crd.projectcalico.org/v1 API group. +func New(cfg *rest.Config, usev3 bool) (client.Client, error) { cfgCopy := *cfg // Force JSON, our types aren't all instrumented for protobuf. @@ -33,7 +37,12 @@ func New(cfg *rest.Config) (client.Client, error) { cfgCopy.ContentConfig.AcceptContentTypes = "application/json" scheme := runtime.NewScheme() - err := schemecrdv1.AddCalicoResourcesToScheme(scheme) + var err error + if usev3 { + err = v3.AddToScheme(scheme) + } else { + err = schemecrdv1.AddCalicoResourcesToScheme(scheme) + } if err != nil { return nil, err } diff --git a/libcalico-go/lib/backend/k8s/resources/apigroups.go b/libcalico-go/lib/backend/k8s/resources/apigroups.go new file mode 100644 index 00000000000..4d1952a707e --- /dev/null +++ b/libcalico-go/lib/backend/k8s/resources/apigroups.go @@ -0,0 +1,32 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// 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 resources + +import ( + apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + + crdv1 "github.com/projectcalico/calico/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme" +) + +// BackingAPIGroup represents the API group used for Calico CRD backend. +type BackingAPIGroup string + +const ( + // BackingAPIGroupV1 represents the use of crd.projectcalico.org/v1 custom resource definitions. + BackingAPIGroupV1 BackingAPIGroup = crdv1.GroupVersion + + // BackingAPIGroupV3 represents the use of projectcalico.org/v3 custom resource definitions. + BackingAPIGroupV3 BackingAPIGroup = apiv3.GroupVersionCurrent +) diff --git a/libcalico-go/lib/backend/k8s/resources/bgpconfig.go b/libcalico-go/lib/backend/k8s/resources/bgpconfig.go index 7e7cea215ab..4e501ada913 100644 --- a/libcalico-go/lib/backend/k8s/resources/bgpconfig.go +++ b/libcalico-go/lib/backend/k8s/resources/bgpconfig.go @@ -18,29 +18,20 @@ import ( "reflect" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) const ( BGPConfigResourceName = "BGPConfigurations" - BGPConfigCRDName = "bgpconfigurations.crd.projectcalico.org" ) -func NewBGPConfigClient(c kubernetes.Interface, r rest.Interface) K8sResourceClient { - return &customK8sResourceClient{ - clientSet: c, +func NewBGPConfigClient(r rest.Interface, group BackingAPIGroup) K8sResourceClient { + return &customResourceClient{ restClient: r, - name: BGPConfigCRDName, resource: BGPConfigResourceName, - description: "Calico BGP Configuration", - k8sResourceType: reflect.TypeOf(apiv3.BGPConfiguration{}), - k8sResourceTypeMeta: metav1.TypeMeta{ - Kind: apiv3.KindBGPConfiguration, - APIVersion: apiv3.GroupVersionCurrent, - }, - k8sListType: reflect.TypeOf(apiv3.BGPConfigurationList{}), - resourceKind: apiv3.KindBGPConfiguration, + k8sResourceType: reflect.TypeFor[apiv3.BGPConfiguration](), + k8sListType: reflect.TypeFor[apiv3.BGPConfigurationList](), + kind: apiv3.KindBGPConfiguration, + apiGroup: group, } } diff --git a/libcalico-go/lib/backend/k8s/resources/bgpfilter.go b/libcalico-go/lib/backend/k8s/resources/bgpfilter.go index b382641b55b..b913191335b 100644 --- a/libcalico-go/lib/backend/k8s/resources/bgpfilter.go +++ b/libcalico-go/lib/backend/k8s/resources/bgpfilter.go @@ -18,29 +18,20 @@ import ( "reflect" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) const ( BGPFilterResourceName = "BGPFilters" - BGPFilterCRDName = "BGPFilters.crd.projectcalico.org" ) -func NewBGPFilterClient(c kubernetes.Interface, r rest.Interface) K8sResourceClient { - return &customK8sResourceClient{ - clientSet: c, +func NewBGPFilterClient(r rest.Interface, group BackingAPIGroup) K8sResourceClient { + return &customResourceClient{ restClient: r, - name: BGPFilterCRDName, resource: BGPFilterResourceName, - description: "BGPFilter", - k8sResourceType: reflect.TypeOf(apiv3.BGPFilter{}), - k8sResourceTypeMeta: metav1.TypeMeta{ - Kind: apiv3.KindBGPFilter, - APIVersion: apiv3.GroupVersionCurrent, - }, - k8sListType: reflect.TypeOf(apiv3.BGPFilterList{}), - resourceKind: apiv3.KindBGPFilter, + k8sResourceType: reflect.TypeFor[apiv3.BGPFilter](), + k8sListType: reflect.TypeFor[apiv3.BGPFilterList](), + kind: apiv3.KindBGPFilter, + apiGroup: group, } } diff --git a/libcalico-go/lib/backend/k8s/resources/bgppeer.go b/libcalico-go/lib/backend/k8s/resources/bgppeer.go index 072fa6c08ae..927fb3df573 100644 --- a/libcalico-go/lib/backend/k8s/resources/bgppeer.go +++ b/libcalico-go/lib/backend/k8s/resources/bgppeer.go @@ -18,29 +18,20 @@ import ( "reflect" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) const ( BGPPeerResourceName = "BGPPeers" - BGPPeerCRDName = "bgppeers.crd.projectcalico.org" ) -func NewBGPPeerClient(c kubernetes.Interface, r rest.Interface) K8sResourceClient { - return &customK8sResourceClient{ - clientSet: c, +func NewBGPPeerClient(r rest.Interface, group BackingAPIGroup) K8sResourceClient { + return &customResourceClient{ restClient: r, - name: BGPPeerCRDName, resource: BGPPeerResourceName, - description: "Calico BGP Peers", - k8sResourceType: reflect.TypeOf(apiv3.BGPPeer{}), - k8sResourceTypeMeta: metav1.TypeMeta{ - Kind: apiv3.KindBGPPeer, - APIVersion: apiv3.GroupVersionCurrent, - }, - k8sListType: reflect.TypeOf(apiv3.BGPPeerList{}), - resourceKind: apiv3.KindBGPPeer, + k8sResourceType: reflect.TypeFor[apiv3.BGPPeer](), + k8sListType: reflect.TypeFor[apiv3.BGPPeerList](), + kind: apiv3.KindBGPPeer, + apiGroup: group, } } diff --git a/libcalico-go/lib/backend/k8s/resources/caliconodestatus.go b/libcalico-go/lib/backend/k8s/resources/caliconodestatus.go index 08787b35208..ef8a15753f7 100644 --- a/libcalico-go/lib/backend/k8s/resources/caliconodestatus.go +++ b/libcalico-go/lib/backend/k8s/resources/caliconodestatus.go @@ -18,29 +18,20 @@ import ( "reflect" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) const ( CalicoNodeStatusResourceName = "CalicoNodeStatuses" - CalicoNodeStatusCRDName = "CalicoNodeStatuses.crd.projectcalico.org" ) -func NewCalicoNodeStatusClient(c kubernetes.Interface, r rest.Interface) K8sResourceClient { - return &customK8sResourceClient{ - clientSet: c, +func NewCalicoNodeStatusClient(r rest.Interface, group BackingAPIGroup) K8sResourceClient { + return &customResourceClient{ restClient: r, - name: CalicoNodeStatusCRDName, resource: CalicoNodeStatusResourceName, - description: "CalicoNodeStatus", - k8sResourceType: reflect.TypeOf(apiv3.CalicoNodeStatus{}), - k8sResourceTypeMeta: metav1.TypeMeta{ - Kind: apiv3.KindCalicoNodeStatus, - APIVersion: apiv3.GroupVersionCurrent, - }, - k8sListType: reflect.TypeOf(apiv3.CalicoNodeStatusList{}), - resourceKind: apiv3.KindCalicoNodeStatus, + k8sResourceType: reflect.TypeFor[apiv3.CalicoNodeStatus](), + k8sListType: reflect.TypeFor[apiv3.CalicoNodeStatusList](), + kind: apiv3.KindCalicoNodeStatus, + apiGroup: group, } } diff --git a/libcalico-go/lib/backend/k8s/resources/clusterinfo.go b/libcalico-go/lib/backend/k8s/resources/clusterinfo.go index 2c12b1a81ee..33b1007aa7c 100644 --- a/libcalico-go/lib/backend/k8s/resources/clusterinfo.go +++ b/libcalico-go/lib/backend/k8s/resources/clusterinfo.go @@ -18,29 +18,20 @@ import ( "reflect" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) const ( ClusterInfoResourceName = "ClusterInformations" - ClusterInfoCRDName = "clusterinformations.crd.projectcalico.org" ) -func NewClusterInfoClient(c kubernetes.Interface, r rest.Interface) K8sResourceClient { - return &customK8sResourceClient{ - clientSet: c, +func NewClusterInfoClient(r rest.Interface, group BackingAPIGroup) K8sResourceClient { + return &customResourceClient{ restClient: r, - name: ClusterInfoCRDName, resource: ClusterInfoResourceName, - description: "Calico Cluster Information", - k8sResourceType: reflect.TypeOf(apiv3.ClusterInformation{}), - k8sResourceTypeMeta: metav1.TypeMeta{ - Kind: apiv3.KindClusterInformation, - APIVersion: apiv3.GroupVersionCurrent, - }, - k8sListType: reflect.TypeOf(apiv3.ClusterInformationList{}), - resourceKind: apiv3.KindClusterInformation, + k8sResourceType: reflect.TypeFor[apiv3.ClusterInformation](), + k8sListType: reflect.TypeFor[apiv3.ClusterInformationList](), + kind: apiv3.KindClusterInformation, + apiGroup: group, } } diff --git a/libcalico-go/lib/backend/k8s/resources/customresource.go b/libcalico-go/lib/backend/k8s/resources/customresource.go index de938ba5982..1e4963db613 100644 --- a/libcalico-go/lib/backend/k8s/resources/customresource.go +++ b/libcalico-go/lib/backend/k8s/resources/customresource.go @@ -16,19 +16,16 @@ package resources import ( "context" - "errors" "fmt" "reflect" - "strings" - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes" scheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "k8s.io/client-go/tools/cache" @@ -36,41 +33,28 @@ import ( "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/names" ) -// customK8sResourceClient implements the K8sResourceClient interface and provides a generic +// customResourceClient implements the K8sResourceClient interface and provides a generic // mechanism for a 1:1 mapping between a Calico Resource and an equivalent Kubernetes // custom resource type. -type customK8sResourceClient struct { - clientSet kubernetes.Interface +type customResourceClient struct { restClient rest.Interface - // Name of the CRD. Not used. - name string - // resource is the kind of the CRD managed by this client, used as part of the // endpoint generated for Kubernetes API calls. resource string - // CRD description. Not used. - description string - // Types used to generate the returned structs. k8sResourceType reflect.Type k8sListType reflect.Type - // k8sResourceTypeMeta is the TypeMeta to set for all resources - // returned by this client. It is used to set the GroupVersion. - k8sResourceTypeMeta metav1.TypeMeta - // Whether or not the CRD managed by this is namespaced. Used for generating // Kubernetes API call endpoints. namespaced bool - // resourceKind is the kind to set for TypeMeta.Kind for all resources - // returned by this client. - resourceKind string + // kind is the Kind to set for TypeMeta.Kind for all resources returned by this client. + kind string // versionConverter is an optional hook to convert the returned data from the CRD // from one format to another before returning it to the caller. @@ -78,6 +62,9 @@ type customK8sResourceClient struct { // validator used to validate resources. validator Validator + + // apiGroup indicates which backing API group to use for CRDs managed by this client. + apiGroup BackingAPIGroup } // VersionConverter converts v1 or v3 k8s resources into v3 resources. @@ -92,7 +79,7 @@ type Validator interface { } // Create creates a new Custom K8s Resource instance in the k8s API from the supplied KVPair. -func (c *customK8sResourceClient) Create(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { +func (c *customResourceClient) Create(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { logContext := log.WithFields(log.Fields{ "Key": kvp.Key, "Value": kvp.Value, @@ -141,7 +128,7 @@ func (c *customK8sResourceClient) Create(ctx context.Context, kvp *model.KVPair) } // Update updates an existing Custom K8s Resource instance in the k8s API from the supplied KVPair. -func (c *customK8sResourceClient) Update(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { +func (c *customResourceClient) Update(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { logContext := log.WithFields(log.Fields{ "Key": kvp.Key, "Value": kvp.Value, @@ -169,7 +156,6 @@ func (c *customK8sResourceClient) Update(ctx context.Context, kvp *model.KVPair) // Send the update request using the name. name := resIn.GetObjectMeta().GetName() - name = c.defaultPolicyName(name) namespace := resIn.GetObjectMeta().GetNamespace() logContext = logContext.WithField("Name", name) logContext.Debug("Update resource by name") @@ -199,7 +185,7 @@ func (c *customK8sResourceClient) Update(ctx context.Context, kvp *model.KVPair) } // UpdateStatus updates status section of an existing Custom K8s Resource instance in the k8s API from the supplied KVPair. -func (c *customK8sResourceClient) UpdateStatus(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { +func (c *customResourceClient) UpdateStatus(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { logContext := log.WithFields(log.Fields{ "Key": kvp.Key, "Value": kvp.Value, @@ -220,7 +206,6 @@ func (c *customK8sResourceClient) UpdateStatus(ctx context.Context, kvp *model.K // Send the update request using the name. name := resIn.GetObjectMeta().GetName() - name = c.defaultPolicyName(name) namespace := resIn.GetObjectMeta().GetNamespace() logContext = logContext.WithField("Name", name) logContext.Debug("Update resource status by name") @@ -249,12 +234,12 @@ func (c *customK8sResourceClient) UpdateStatus(ctx context.Context, kvp *model.K return kvp, nil } -func (c *customK8sResourceClient) DeleteKVP(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { +func (c *customResourceClient) DeleteKVP(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { return c.Delete(ctx, kvp.Key, kvp.Revision, kvp.UID) } // Delete deletes an existing Custom K8s Resource instance in the k8s API using the supplied KVPair. -func (c *customK8sResourceClient) Delete(ctx context.Context, k model.Key, revision string, uid *types.UID) (*model.KVPair, error) { +func (c *customResourceClient) Delete(ctx context.Context, k model.Key, revision string, uid *types.UID) (*model.KVPair, error) { logContext := log.WithFields(log.Fields{ "Key": k, "Resource": c.resource, @@ -267,7 +252,6 @@ func (c *customK8sResourceClient) Delete(ctx context.Context, k model.Key, revis logContext.WithError(err).Debug("Error deleting resource") return nil, err } - name = c.defaultPolicyName(name) existing, err := c.Get(ctx, k, revision) if err != nil { @@ -278,13 +262,21 @@ func (c *customK8sResourceClient) Delete(ctx context.Context, k model.Key, revis opts := &metav1.DeleteOptions{} if uid != nil { - // The UID in the v3 resources is a translation of the UID in the CR. Translate it - // before passing as a precondition. - uid, err := conversion.ConvertUID(*uid) - if err != nil { - return nil, err + switch c.apiGroup { + case BackingAPIGroupV1: + // The UID in the v3 resources is a translation of the UID in the CR. Translate it + // before passing as a precondition. + uid, err := conversion.ConvertUID(*uid) + if err != nil { + return nil, err + } + opts.Preconditions = &metav1.Preconditions{UID: &uid} + case BackingAPIGroupV3: + // If no transform is required, then we can pass the UID as-is. + opts.Preconditions = &metav1.Preconditions{UID: uid} + default: + return nil, fmt.Errorf("unknown backing API group: %v", c.apiGroup) } - opts.Preconditions = &metav1.Preconditions{UID: &uid} } // Delete the resource using the name. @@ -305,7 +297,7 @@ func (c *customK8sResourceClient) Delete(ctx context.Context, k model.Key, revis } // Get gets an existing Custom K8s Resource instance in the k8s API using the supplied Key. -func (c *customK8sResourceClient) Get(ctx context.Context, key model.Key, revision string) (*model.KVPair, error) { +func (c *customResourceClient) Get(ctx context.Context, key model.Key, revision string) (*model.KVPair, error) { logContext := log.WithFields(log.Fields{ "Key": key, "Resource": c.resource, @@ -317,7 +309,6 @@ func (c *customK8sResourceClient) Get(ctx context.Context, key model.Key, revisi logContext.WithError(err).Debug("Error getting resource") return nil, err } - name = c.defaultPolicyName(name) namespace := key.(model.ResourceKey).Namespace // Add the name and namespace to the log context now that we know it, and query Kubernetes. @@ -340,7 +331,7 @@ func (c *customK8sResourceClient) Get(ctx context.Context, key model.Key, revisi // List lists configured Custom K8s Resource instances in the k8s API matching the // supplied ListInterface. It will use list paging if necessary to reduce the load on the Kubernetes API server. -func (c *customK8sResourceClient) List(ctx context.Context, list model.ListInterface, revision string) (*model.KVPairList, error) { +func (c *customResourceClient) List(ctx context.Context, list model.ListInterface, revision string) (*model.KVPairList, error) { logContext := log.WithFields(log.Fields{ "ListInterface": list, "Resource": c.resource, @@ -374,25 +365,6 @@ func (c *customK8sResourceClient) List(ctx context.Context, list model.ListInter Resource(c.resource). VersionedParams(&opts, scheme.ParameterCodec) - // If the prefix is specified, look for the resources with the label - // of prefix. - if resList.Prefix { - // The prefix has a trailing "." character, remove it, since it is not valid for k8s labels - if !strings.HasSuffix(resList.Name, ".") { - return nil, errors.New("internal error: custom resource list invoked for a prefix not in the form '.'") - } - name := resList.Name[:len(resList.Name)-1] - if name == "default" { - req = req.VersionedParams(&metav1.ListOptions{ - LabelSelector: "!" + apiv3.LabelTier, - }, scheme.ParameterCodec) - } else { - req = req.VersionedParams(&metav1.ListOptions{ - LabelSelector: apiv3.LabelTier + "=" + name, - }, scheme.ParameterCodec) - } - } - // Perform the request. err := req.Do(ctx).Into(out) if err != nil { @@ -417,7 +389,7 @@ func (c *customK8sResourceClient) List(ctx context.Context, list model.ListInter return pagedList(ctx, logContext, revision, list, convertFunc, listFunc) } -func (c *customK8sResourceClient) Watch(ctx context.Context, list model.ListInterface, options api.WatchOptions) (api.WatchInterface, error) { +func (c *customResourceClient) Watch(ctx context.Context, list model.ListInterface, options api.WatchOptions) (api.WatchInterface, error) { rlo, ok := list.(model.ResourceListOptions) if !ok { return nil, fmt.Errorf("ListInterface is not a ResourceListOptions: %s", list) @@ -449,11 +421,11 @@ func (c *customK8sResourceClient) Watch(ctx context.Context, list model.ListInte // EnsureInitialized is a no-op since the CRD should be // initialized in advance. -func (c *customK8sResourceClient) EnsureInitialized() error { +func (c *customResourceClient) EnsureInitialized() error { return nil } -func (c *customK8sResourceClient) listInterfaceToKey(l model.ListInterface) model.Key { +func (c *customResourceClient) listInterfaceToKey(l model.ListInterface) model.Key { pl := l.(model.ResourceListOptions) key := model.ResourceKey{Name: pl.Name, Kind: pl.Kind} @@ -467,18 +439,28 @@ func (c *customK8sResourceClient) listInterfaceToKey(l model.ListInterface) mode return nil } -func (c *customK8sResourceClient) keyToName(k model.Key) (string, error) { +func (c *customResourceClient) keyToName(k model.Key) (string, error) { return k.(model.ResourceKey).Name, nil } -func (c *customK8sResourceClient) nameToKey(name string) (model.Key, error) { +func (c *customResourceClient) nameToKey(name string) (model.Key, error) { return model.ResourceKey{ Name: name, - Kind: c.resourceKind, + Kind: c.kind, }, nil } -func (c *customK8sResourceClient) convertResourceToKVPair(r Resource) (*model.KVPair, error) { +func (c *customResourceClient) typeMeta() *metav1.TypeMeta { + return &metav1.TypeMeta{ + // apiVersion is the API group and version for the resources returned by this client. + // Note that this is not necessarily the same as the API version used in the Kubernetes API. For example, + // CRs may be stored using crd.projectcalico.org/v1, but returned to callers with projectcalico.org/v3. + APIVersion: v3.GroupVersionCurrent, + Kind: c.kind, + } +} + +func (c *customResourceClient) convertResourceToKVPair(r Resource) (*model.KVPair, error) { var err error // If the resource has a VersionConverter defined then pass the resource through @@ -490,45 +472,38 @@ func (c *customK8sResourceClient) convertResourceToKVPair(r Resource) (*model.KV } } - gvk := c.k8sResourceTypeMeta.GetObjectKind().GroupVersionKind() - gvk.Kind = c.resourceKind + gvk := c.typeMeta().GetObjectKind().GroupVersionKind() + gvk.Kind = c.kind r.GetObjectKind().SetGroupVersionKind(gvk) kvp := &model.KVPair{ Key: model.ResourceKey{ Name: r.GetObjectMeta().GetName(), Namespace: r.GetObjectMeta().GetNamespace(), - Kind: c.resourceKind, + Kind: c.kind, }, Revision: r.GetObjectMeta().GetResourceVersion(), } - if err := ConvertK8sResourceToCalicoResource(r); err != nil { - return kvp, err + if c.apiGroup == BackingAPIGroupV1 { + // Convert the resource from crd.projectcalico.org/v1 to projectcalico.org/v3. + if err := ConvertK8sResourceToCalicoResource(r); err != nil { + return kvp, err + } } kvp.Value = r return kvp, nil } -func (c *customK8sResourceClient) convertKVPairToResource(kvp *model.KVPair) (Resource, error) { +func (c *customResourceClient) convertKVPairToResource(kvp *model.KVPair) (Resource, error) { resource := kvp.Value.(Resource) resource.GetObjectMeta().SetResourceVersion(kvp.Revision) - resOut, err := ConvertCalicoResourceToK8sResource(resource) - if err != nil { - return resOut, err - } - - return resOut, nil -} -func (c *customK8sResourceClient) defaultPolicyName(name string) string { - if c.resourceKind == apiv3.KindGlobalNetworkPolicy || - c.resourceKind == apiv3.KindNetworkPolicy || - c.resourceKind == apiv3.KindStagedGlobalNetworkPolicy || - c.resourceKind == apiv3.KindStagedNetworkPolicy { - // Policies in default tier are stored in the backend with the default prefix, if the prefix is not present we prefix it now - name = names.TieredPolicyName(name) + if c.apiGroup == BackingAPIGroupV3 { + // Use the input resource as-is, no transformation needed. + return resource, nil } - return name + // Perform the transform from projectcalico.org/v3 to crd.projectcalico.org/v1 + return ConvertCalicoResourceToK8sResource(resource) } diff --git a/libcalico-go/lib/backend/k8s/resources/customresource_test.go b/libcalico-go/lib/backend/k8s/resources/customresource_test.go index aec6c2ed6cc..8a3362b2bbc 100644 --- a/libcalico-go/lib/backend/k8s/resources/customresource_test.go +++ b/libcalico-go/lib/backend/k8s/resources/customresource_test.go @@ -17,7 +17,7 @@ package resources import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/sirupsen/logrus" @@ -28,7 +28,7 @@ import ( "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest/fake" - calischeme "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/scheme" + calischeme "github.com/projectcalico/calico/libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/net" ) @@ -40,7 +40,7 @@ func init() { var _ = Describe("Custom resource conversion methods (tested using BGPPeer)", func() { // Create an empty client since we are only testing conversion functions. - client := NewBGPPeerClient(nil, nil).(*customK8sResourceClient) + client := NewBGPPeerClient(nil, BackingAPIGroupV1).(*customResourceClient) // Define some useful test data. listIncomplete := model.ResourceListOptions{} @@ -240,7 +240,7 @@ var _ = Describe("Custom resource conversion methods (tested using BGPPeer)", fu }) var _ = Describe("Custom resource conversion methods (tested using namespaced NetworkSet)", func() { - var client *customK8sResourceClient + var client *customResourceClient var fakeREST *fake.RESTClient BeforeEach(func() { @@ -252,7 +252,7 @@ var _ = Describe("Custom resource conversion methods (tested using namespaced Ne }, VersionedAPIPath: "/apis", } - client = NewNetworkSetClient(nil, fakeREST).(*customK8sResourceClient) + client = NewNetworkSetClient(fakeREST, BackingAPIGroupV1).(*customResourceClient) }) It("should get by name", func() { diff --git a/libcalico-go/lib/backend/k8s/resources/errors.go b/libcalico-go/lib/backend/k8s/resources/errors.go index 9b5f91ac72d..d8d3c2c1e93 100644 --- a/libcalico-go/lib/backend/k8s/resources/errors.go +++ b/libcalico-go/lib/backend/k8s/resources/errors.go @@ -25,7 +25,7 @@ import ( // K8sErrorToCalico returns the equivalent libcalico error for the given // kubernetes error. -func K8sErrorToCalico(ke error, id interface{}) error { +func K8sErrorToCalico(ke error, id any) error { if ke == nil { return nil } diff --git a/libcalico-go/lib/backend/k8s/resources/felixconfig.go b/libcalico-go/lib/backend/k8s/resources/felixconfig.go index 9cef53d7812..2943b063c22 100644 --- a/libcalico-go/lib/backend/k8s/resources/felixconfig.go +++ b/libcalico-go/lib/backend/k8s/resources/felixconfig.go @@ -18,29 +18,20 @@ import ( "reflect" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) const ( FelixConfigResourceName = "FelixConfigurations" - FelixConfigCRDName = "felixconfigurations.crd.projectcalico.org" ) -func NewFelixConfigClient(c kubernetes.Interface, r rest.Interface) K8sResourceClient { - return &customK8sResourceClient{ - clientSet: c, +func NewFelixConfigClient(r rest.Interface, group BackingAPIGroup) K8sResourceClient { + return &customResourceClient{ restClient: r, - name: FelixConfigCRDName, resource: FelixConfigResourceName, - description: "Calico Felix Configuration", - k8sResourceType: reflect.TypeOf(apiv3.FelixConfiguration{}), - k8sResourceTypeMeta: metav1.TypeMeta{ - Kind: apiv3.KindFelixConfiguration, - APIVersion: apiv3.GroupVersionCurrent, - }, - k8sListType: reflect.TypeOf(apiv3.FelixConfigurationList{}), - resourceKind: apiv3.KindFelixConfiguration, + k8sResourceType: reflect.TypeFor[apiv3.FelixConfiguration](), + k8sListType: reflect.TypeFor[apiv3.FelixConfigurationList](), + kind: apiv3.KindFelixConfiguration, + apiGroup: group, } } diff --git a/libcalico-go/lib/backend/k8s/resources/globalnetworkpolicies.go b/libcalico-go/lib/backend/k8s/resources/globalnetworkpolicies.go index bd1c067a2e7..197d5e378bb 100644 --- a/libcalico-go/lib/backend/k8s/resources/globalnetworkpolicies.go +++ b/libcalico-go/lib/backend/k8s/resources/globalnetworkpolicies.go @@ -18,29 +18,20 @@ import ( "reflect" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) const ( GlobalNetworkPolicyResourceName = "GlobalNetworkPolicies" - GlobalNetworkPolicyCRDName = "globalnetworkpolicies.crd.projectcalico.org" ) -func NewGlobalNetworkPolicyClient(c kubernetes.Interface, r rest.Interface) K8sResourceClient { - return &customK8sResourceClient{ - clientSet: c, +func NewGlobalNetworkPolicyClient(r rest.Interface, group BackingAPIGroup) K8sResourceClient { + return &customResourceClient{ restClient: r, - name: GlobalNetworkPolicyCRDName, resource: GlobalNetworkPolicyResourceName, - description: "Calico Global Network Policies", - k8sResourceType: reflect.TypeOf(apiv3.GlobalNetworkPolicy{}), - k8sResourceTypeMeta: metav1.TypeMeta{ - Kind: apiv3.KindGlobalNetworkPolicy, - APIVersion: apiv3.GroupVersionCurrent, - }, - k8sListType: reflect.TypeOf(apiv3.GlobalNetworkPolicyList{}), - resourceKind: apiv3.KindGlobalNetworkPolicy, + k8sResourceType: reflect.TypeFor[apiv3.GlobalNetworkPolicy](), + k8sListType: reflect.TypeFor[apiv3.GlobalNetworkPolicyList](), + kind: apiv3.KindGlobalNetworkPolicy, + apiGroup: group, } } diff --git a/libcalico-go/lib/backend/k8s/resources/globalnetworkset.go b/libcalico-go/lib/backend/k8s/resources/globalnetworkset.go index 5db74143534..64a3c220958 100644 --- a/libcalico-go/lib/backend/k8s/resources/globalnetworkset.go +++ b/libcalico-go/lib/backend/k8s/resources/globalnetworkset.go @@ -18,29 +18,20 @@ import ( "reflect" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) const ( GlobalNetworkSetResourceName = "GlobalNetworkSets" - GlobalNetworkSetCRDName = "globalnetworksets.crd.projectcalico.org" ) -func NewGlobalNetworkSetClient(c kubernetes.Interface, r rest.Interface) K8sResourceClient { - return &customK8sResourceClient{ - clientSet: c, +func NewGlobalNetworkSetClient(r rest.Interface, group BackingAPIGroup) K8sResourceClient { + return &customResourceClient{ restClient: r, - name: GlobalNetworkSetCRDName, resource: GlobalNetworkSetResourceName, - description: "Calico Global Network Sets", - k8sResourceType: reflect.TypeOf(apiv3.GlobalNetworkSet{}), - k8sResourceTypeMeta: metav1.TypeMeta{ - Kind: apiv3.KindGlobalNetworkSet, - APIVersion: apiv3.GroupVersionCurrent, - }, - k8sListType: reflect.TypeOf(apiv3.GlobalNetworkSetList{}), - resourceKind: apiv3.KindGlobalNetworkSet, + k8sResourceType: reflect.TypeFor[apiv3.GlobalNetworkSet](), + k8sListType: reflect.TypeFor[apiv3.GlobalNetworkSetList](), + kind: apiv3.KindGlobalNetworkSet, + apiGroup: group, } } diff --git a/libcalico-go/lib/backend/k8s/resources/hostendpoint.go b/libcalico-go/lib/backend/k8s/resources/hostendpoint.go index 2ac287103a1..7347876ab08 100644 --- a/libcalico-go/lib/backend/k8s/resources/hostendpoint.go +++ b/libcalico-go/lib/backend/k8s/resources/hostendpoint.go @@ -18,29 +18,20 @@ import ( "reflect" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) const ( HostEndpointResourceName = "HostEndpoints" - HostEndpointCRDName = "hostendpoints.crd.projectcalico.org" ) -func NewHostEndpointClient(c kubernetes.Interface, r rest.Interface) K8sResourceClient { - return &customK8sResourceClient{ - clientSet: c, +func NewHostEndpointClient(r rest.Interface, group BackingAPIGroup) K8sResourceClient { + return &customResourceClient{ restClient: r, - name: HostEndpointCRDName, resource: HostEndpointResourceName, - description: "Calico HostEndpoints", - k8sResourceType: reflect.TypeOf(apiv3.HostEndpoint{}), - k8sResourceTypeMeta: metav1.TypeMeta{ - Kind: apiv3.KindHostEndpoint, - APIVersion: apiv3.GroupVersionCurrent, - }, - k8sListType: reflect.TypeOf(apiv3.HostEndpointList{}), - resourceKind: apiv3.KindHostEndpoint, + k8sResourceType: reflect.TypeFor[apiv3.HostEndpoint](), + k8sListType: reflect.TypeFor[apiv3.HostEndpointList](), + kind: apiv3.KindHostEndpoint, + apiGroup: group, } } diff --git a/libcalico-go/lib/backend/k8s/resources/ipam_affinity.go b/libcalico-go/lib/backend/k8s/resources/ipam_affinity.go deleted file mode 100644 index 78ed6d0efdb..00000000000 --- a/libcalico-go/lib/backend/k8s/resources/ipam_affinity.go +++ /dev/null @@ -1,455 +0,0 @@ -// Copyright (c) 2019 Tigera, Inc. All rights reserved. - -// 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 resources - -import ( - "context" - "crypto/sha256" - "encoding/hex" - "fmt" - "reflect" - "strconv" - - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - log "github.com/sirupsen/logrus" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/cache" - - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" - "github.com/projectcalico/calico/libcalico-go/lib/backend/api" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" - "github.com/projectcalico/calico/libcalico-go/lib/names" - "github.com/projectcalico/calico/libcalico-go/lib/net" -) - -const ( - BlockAffinityResourceName = "BlockAffinities" - BlockAffinityCRDName = "blockaffinities.crd.projectcalico.org" -) - -func NewBlockAffinityClient(c kubernetes.Interface, r rest.Interface) K8sResourceClient { - // Create a resource client which manages k8s CRDs. - rc := customK8sResourceClient{ - clientSet: c, - restClient: r, - name: BlockAffinityCRDName, - resource: BlockAffinityResourceName, - description: "Calico IPAM block affinities", - k8sResourceType: reflect.TypeOf(libapiv3.BlockAffinity{}), - k8sResourceTypeMeta: metav1.TypeMeta{ - Kind: libapiv3.KindBlockAffinity, - APIVersion: apiv3.GroupVersionCurrent, - }, - k8sListType: reflect.TypeOf(libapiv3.BlockAffinityList{}), - resourceKind: libapiv3.KindBlockAffinity, - } - - return &blockAffinityClient{rc: rc} -} - -// blockAffinityClient implements the api.Client interface for BlockAffinity objects. It -// handles the translation between v1 objects understood by the IPAM codebase in lib/ipam, -// and the CRDs which are used to actually store the data in the Kubernetes API. -// It uses a customK8sResourceClient under the covers to perform CRUD operations on -// kubernetes CRDs. -type blockAffinityClient struct { - rc customK8sResourceClient -} - -// toV1 converts the given v3 CRD KVPair into a v1 model representation -// which can be passed to the IPAM code. -func (c *blockAffinityClient) toV1(kvpv3 *model.KVPair) (*model.KVPair, error) { - // Parse the CIDR into a struct. - _, cidr, err := net.ParseCIDR(kvpv3.Value.(*libapiv3.BlockAffinity).Spec.CIDR) - if err != nil { - log.WithField("cidr", cidr).WithError(err).Error("failed to parse cidr") - return nil, err - } - state := model.BlockAffinityState(kvpv3.Value.(*libapiv3.BlockAffinity).Spec.State) - - // Determine deleted status. - deletedString := kvpv3.Value.(*libapiv3.BlockAffinity).Spec.Deleted - del := false - if deletedString != "" { - del, err = strconv.ParseBool(deletedString) - if err != nil { - return nil, fmt.Errorf("Failed to parse deleted value as bool: %s", err) - } - } - - return &model.KVPair{ - Key: model.BlockAffinityKey{ - CIDR: *cidr, - AffinityType: kvpv3.Value.(*libapiv3.BlockAffinity).Spec.Type, - Host: kvpv3.Value.(*libapiv3.BlockAffinity).Spec.Node, - }, - Value: &model.BlockAffinity{ - State: state, - Deleted: del, - }, - Revision: kvpv3.Revision, - UID: &kvpv3.Value.(*libapiv3.BlockAffinity).UID, - }, nil -} - -// parseKey parses the given model.Key, returning a suitable name, CIDR -// and host for use in the Kubernetes API. -func (c *blockAffinityClient) parseKey(k model.Key) (name, cidr, host, affinityType string) { - host = k.(model.BlockAffinityKey).Host - affinityType = k.(model.BlockAffinityKey).AffinityType - cidr = fmt.Sprintf("%s", k.(model.BlockAffinityKey).CIDR) - cidrname := names.CIDRToName(k.(model.BlockAffinityKey).CIDR) - - // Include the hostname as well. - name = fmt.Sprintf("%s-%s", host, cidrname) - - if len(name) >= 253 { - // If the name is too long, we need to shorten it. - // Remove enough characters to get it below the 253 character limit, - // as well as 11 characters to add a hash which helps with uniqueness, - // and two characters for the `-` separators between clauses. - name = fmt.Sprintf("%s-%s", host[:252-len(cidrname)-13], cidrname) - - // Add a hash to help with uniqueness. - h := sha256.New() - h.Write([]byte(fmt.Sprintf("%s+%s", host, cidrname))) - name = fmt.Sprintf("%s-%s", name, hex.EncodeToString(h.Sum(nil))[:11]) - } - return -} - -// toV3 takes the given v1 KVPair and converts it into a v3 representation, suitable -// for writing as a CRD to the Kubernetes API. -func (c *blockAffinityClient) toV3(kvpv1 *model.KVPair) *model.KVPair { - name, cidr, host, affinityType := c.parseKey(kvpv1.Key) - state := kvpv1.Value.(*model.BlockAffinity).State - value := &libapiv3.BlockAffinity{ - TypeMeta: metav1.TypeMeta{ - Kind: libapiv3.KindBlockAffinity, - APIVersion: "crd.projectcalico.org/v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - ResourceVersion: kvpv1.Revision, - }, - Spec: libapiv3.BlockAffinitySpec{ - State: string(state), - Node: host, - Type: affinityType, - CIDR: cidr, - Deleted: fmt.Sprintf("%t", kvpv1.Value.(*model.BlockAffinity).Deleted), - }, - } - model.EnsureBlockAffinityLabels(value) - - return &model.KVPair{ - Key: model.ResourceKey{ - Name: name, - Kind: libapiv3.KindBlockAffinity, - }, - Value: value, - Revision: kvpv1.Revision, - } -} - -// isV1BlockAffinityKey checks if the key is in the v1 format. -func isV1BlockAffinityKey(key model.Key) bool { - switch key.(type) { - case model.BlockAffinityKey: - return true - case model.ResourceKey: - return false - default: - log.Panic("blockAffinityClient : wrong key interface type") - } - return false -} - -func (c *blockAffinityClient) createV1(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { - nkvp, err := c.createV3(ctx, c.toV3(kvp)) - if err != nil { - return nil, err - } - - v1kvp, err := c.toV1(nkvp) - if err != nil { - return nil, err - } - return v1kvp, nil -} - -func (c *blockAffinityClient) createV3(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { - return c.rc.Create(ctx, ensureV3Labels(kvp)) -} - -func ensureV3Labels(kvp *model.KVPair) *model.KVPair { - v3Value := kvp.Value.(*libapiv3.BlockAffinity) - v3Value = v3Value.DeepCopy() - model.EnsureBlockAffinityLabels(v3Value) - newKVP := *kvp - newKVP.Value = v3Value - return &newKVP -} - -func (c *blockAffinityClient) Create(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { - if isV1BlockAffinityKey(kvp.Key) { - // If this is a V1 resource, then it is from the IPAM code. - // Convert it, but treat it as a V1 resource. - return c.createV1(ctx, kvp) - } - // If this is a V3 resource, then it is already in CRD format. - return c.createV3(ctx, kvp) -} - -func (c *blockAffinityClient) updateV1(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { - nkvp, err := c.updateV3(ctx, c.toV3(kvp)) - if err != nil { - return nil, err - } - - v1kvp, err := c.toV1(nkvp) - if err != nil { - return nil, err - } - return v1kvp, nil -} - -func (c *blockAffinityClient) updateV3(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { - return c.rc.Update(ctx, ensureV3Labels(kvp)) -} - -func (c *blockAffinityClient) Update(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { - if isV1BlockAffinityKey(kvp.Key) { - // If this is a V1 resource, then it is from the IPAM code. - // Convert it, but treat it as a V1 resource. - return c.updateV1(ctx, kvp) - } - // If this is a V3 resource, then it is already in CRD format. - return c.updateV3(ctx, kvp) -} - -func (c *blockAffinityClient) deleteKVPV1(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { - // We need to mark as deleted first, since the Kubernetes API doesn't support - // compare-and-delete. This update operation allows us to eliminate races with other clients. - name, _, _, _ := c.parseKey(kvp.Key) - kvp.Value.(*model.BlockAffinity).Deleted = true - v1kvp, err := c.Update(ctx, kvp) - if err != nil { - return nil, err - } - - // Now actually delete the object. - k := model.ResourceKey{Name: name, Kind: libapiv3.KindBlockAffinity} - kvp, err = c.rc.Delete(ctx, k, v1kvp.Revision, kvp.UID) - if err != nil { - return nil, err - } - return c.toV1(kvp) -} - -func (c *blockAffinityClient) deleteKVPV3(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { - // We need to mark as deleted first, since the Kubernetes API doesn't support - // compare-and-delete. This update operation allows us to eliminate races with other clients. - var err error - nkvp := kvp - if kvp.Value == nil { - // Need to check if a value is given since V3 deletes can be made by providing a key only. - // Look up missing values with the provided key. - nkvp, err = c.getV3(ctx, kvp.Key.(model.ResourceKey), kvp.Revision) - if err != nil { - if _, ok := err.(cerrors.ErrorResourceDoesNotExist); ok { - return nil, fmt.Errorf("Unable to find block affinity. Block affinity may have already been deleted.") - } - return nil, fmt.Errorf("Error retrieving block affinity for deletion: %s", err) - } - } - - // Pass in the revision for the key-value pair to ensure that deletion occurs for the specified revision, - // not the revision that is retrieved by the above Get (which should be the most recent). - if kvp.Revision == "" { - return nil, fmt.Errorf("Unable to delete block affinity without a resource version") - } - nkvp.Revision = kvp.Revision - nkvp.Value.(*libapiv3.BlockAffinity).Spec.Deleted = fmt.Sprintf("%t", true) - nkvp, err = c.Update(ctx, nkvp) - if err != nil { - return nil, err - } - - // Now actually delete the object. - return c.rc.Delete(ctx, nkvp.Key, nkvp.Revision, &nkvp.Value.(*libapiv3.BlockAffinity).UID) -} - -func (c *blockAffinityClient) DeleteKVP(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { - if isV1BlockAffinityKey(kvp.Key) { - // If this is a V1 resource, then it is from the IPAM code. - // Convert it, but treat it as a V1 resource. - return c.deleteKVPV1(ctx, kvp) - } - // If this is a V3 resource, then it is already in CRD format. - return c.deleteKVPV3(ctx, kvp) -} - -func (c *blockAffinityClient) Delete(ctx context.Context, key model.Key, revision string, uid *types.UID) (*model.KVPair, error) { - // Delete should not be used for affinities, since we need the object UID for correctness. - log.Warn("Operation Delete is not supported on BlockAffinity type - use DeleteKVP") - return nil, cerrors.ErrorOperationNotSupported{ - Identifier: key, - Operation: "Delete", - } -} - -func (c *blockAffinityClient) getV1(ctx context.Context, key model.BlockAffinityKey, revision string) (*model.KVPair, error) { - // Get the object. - name, _, _, _ := c.parseKey(key) - k := model.ResourceKey{Name: name, Kind: libapiv3.KindBlockAffinity} - kvp, err := c.rc.Get(ctx, k, revision) - if err != nil { - return nil, err - } - - // Convert it to v1. - v1kvp, err := c.toV1(kvp) - if err != nil { - return nil, err - } - - // If this object has been marked as deleted, then we need to clean it up and - // return not found. - if v1kvp.Value.(*model.BlockAffinity).Deleted { - if _, err := c.DeleteKVP(ctx, v1kvp); err != nil { - return nil, err - } - return nil, cerrors.ErrorResourceDoesNotExist{Err: fmt.Errorf("Resource was deleted"), Identifier: key} - } - - return v1kvp, nil -} - -func (c *blockAffinityClient) getV3(ctx context.Context, key model.ResourceKey, revision string) (*model.KVPair, error) { - return c.rc.Get(ctx, key, revision) -} - -func (c *blockAffinityClient) Get(ctx context.Context, key model.Key, revision string) (*model.KVPair, error) { - if isV1BlockAffinityKey(key) { - // If this is a V1 resource, then it is from the IPAM code. - // Convert it, but treat it as a V1 resource. - return c.getV1(ctx, key.(model.BlockAffinityKey), revision) - } - // If this is a V3 resource, then it is already in CRD format. - return c.getV3(ctx, key.(model.ResourceKey), revision) -} - -func isV1List(list model.ListInterface) bool { - switch list.(type) { - case model.BlockAffinityListOptions: - return true - case model.ResourceListOptions: - return false - default: - log.Panic("blockAffinityClient : wrong key interface type") - } - return false -} - -func (c *blockAffinityClient) listV1(ctx context.Context, list model.BlockAffinityListOptions, revision string) (*model.KVPairList, error) { - log.Debugf("Listing v1 block affinities with host %s, affinity type %s, IP version %d", list.Host, list.AffinityType, list.IPVersion) - l := model.ResourceListOptions{ - Kind: libapiv3.KindBlockAffinity, - LabelSelector: model.CalculateBlockAffinityLabelSelector(list), - } - v3list, err := c.listV3(ctx, l, revision) - if err != nil { - return nil, err - } - - host := list.Host - affinityType := list.AffinityType - requestedIPVersion := list.IPVersion - - kvpl := &model.KVPairList{ - KVPairs: []*model.KVPair{}, - Revision: v3list.Revision, - } - for _, i := range v3list.KVPairs { - v1kvp, err := c.toV1(i) - if err != nil { - return nil, err - } - if (host == "" || v1kvp.Key.(model.BlockAffinityKey).Host == host) && - (affinityType == "" || v1kvp.Key.(model.BlockAffinityKey).AffinityType == affinityType) { - cidr := v1kvp.Key.(model.BlockAffinityKey).CIDR - cidrPtr := &cidr - if (requestedIPVersion == 0 || requestedIPVersion == cidrPtr.Version()) && !v1kvp.Value.(*model.BlockAffinity).Deleted { - // Matches the given host and IP version. - kvpl.KVPairs = append(kvpl.KVPairs, v1kvp) - } - } - } - return kvpl, nil -} - -func (c *blockAffinityClient) listV3(ctx context.Context, list model.ResourceListOptions, revision string) (*model.KVPairList, error) { - log.Debugf("Listing v3 block affinities matching %v, revision=%v", list, revision) - return c.rc.List(ctx, list, revision) -} - -func (c *blockAffinityClient) List(ctx context.Context, list model.ListInterface, revision string) (*model.KVPairList, error) { - if isV1List(list) { - // If this is a V1 resource, then it is from the IPAM code. - // Convert it, but treat it as a V1 resource. - return c.listV1(ctx, list.(model.BlockAffinityListOptions), revision) - } - // If this is a V3 resource, then it is already in CRD format. - return c.listV3(ctx, list.(model.ResourceListOptions), revision) -} - -func (c *blockAffinityClient) toKVPairV1(r Resource) (*model.KVPair, error) { - conv, err := c.rc.convertResourceToKVPair(r) - if err != nil { - return nil, err - } - return c.toV1(conv) -} - -func (c *blockAffinityClient) toKVPairV3(r Resource) (*model.KVPair, error) { - return c.rc.convertResourceToKVPair(r) -} - -func (c *blockAffinityClient) Watch(ctx context.Context, list model.ListInterface, options api.WatchOptions) (api.WatchInterface, error) { - resl := model.ResourceListOptions{Kind: libapiv3.KindBlockAffinity} - k8sWatchClient := cache.NewListWatchFromClient(c.rc.restClient, c.rc.resource, "", fields.Everything()) - k8sOpts := watchOptionsToK8sListOptions(options) - k8sWatch, err := k8sWatchClient.WatchFunc(k8sOpts) - if err != nil { - return nil, K8sErrorToCalico(err, list) - } - if isV1List(list) { - // If this is a V1 resource, then it is from the IPAM code. - // Convert resources back to a V1 resource. - return newK8sWatcherConverter(ctx, resl.Kind+" (custom)", c.toKVPairV1, k8sWatch), nil - } - - return newK8sWatcherConverter(ctx, resl.Kind+" (custom)", c.toKVPairV3, k8sWatch), nil -} - -func (c *blockAffinityClient) EnsureInitialized() error { - return nil -} diff --git a/libcalico-go/lib/backend/k8s/resources/ipam_affinity_test.go b/libcalico-go/lib/backend/k8s/resources/ipam_affinity_test.go index 20ca5a2455e..1fad1515272 100644 --- a/libcalico-go/lib/backend/k8s/resources/ipam_affinity_test.go +++ b/libcalico-go/lib/backend/k8s/resources/ipam_affinity_test.go @@ -19,7 +19,7 @@ import ( "encoding/json" "io" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/client/clientset_generated/clientset/scheme" @@ -30,7 +30,7 @@ import ( "k8s.io/client-go/rest/fake" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/resources" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" @@ -80,154 +80,172 @@ var _ = Describe("BlockAffinityClient tests with fake REST client", func() { var client resources.K8sResourceClient var fakeREST *fake.RESTClient - BeforeEach(func() { - fakeREST = &fake.RESTClient{ - NegotiatedSerializer: serializer.WithoutConversionCodecFactory{CodecFactory: scheme.Codecs}, - GroupVersion: schema.GroupVersion{ - Group: "crd.projectcalico.org", - Version: "v1", - }, - VersionedAPIPath: "/apis", - } - client = resources.NewBlockAffinityClient(nil, fakeREST) - }) - - It("should list all (v3)", func() { - l, err := client.List(context.TODO(), model.ResourceListOptions{ - Kind: apiv3.KindBlockAffinity, - }, "") - - // Expect an error since the client is not implemented. - Expect(err).To(HaveOccurred()) - Expect(l).To(BeNil()) - - // But we should be able to check the request... - url := fakeREST.Req.URL - logrus.Debug("URL: ", url) - Expect(url.Path).To(Equal("/apis/blockaffinities")) - Expect(url.Query()).NotTo(HaveKey("metadata.name")) - }) - - It("should list all (v1)", func() { - l, err := client.List(context.TODO(), model.BlockAffinityListOptions{}, "") - - // Expect an error since the client is not implemented. - Expect(err).To(HaveOccurred()) - Expect(l).To(BeNil()) - - // But we should be able to check the request... - url := fakeREST.Req.URL - logrus.Debug("URL: ", url) - Expect(url.Path).To(Equal("/apis/blockaffinities")) - Expect(url.Query()).NotTo(HaveKey("metadata.name")) - }) - - It("should use a fieldSelector for a list name match (v3)", func() { - l, err := client.List(context.TODO(), model.ResourceListOptions{ - Name: "foo", - Kind: apiv3.KindBlockAffinity, - }, "") - - // Expect an error since the client is not implemented. - Expect(err).To(HaveOccurred()) - Expect(l).To(BeNil()) - - // But we should be able to check the request... - url := fakeREST.Req.URL - logrus.Debug("URL: ", url) - Expect(url.Path).To(Equal("/apis/blockaffinities")) - Expect(url.Query().Get("fieldSelector")).To(Equal("metadata.name=foo")) - }) - - It("should _not_ use a fieldSelector for a list name match (v1)", func() { - l, err := client.List(context.TODO(), model.BlockAffinityListOptions{ - Host: "host", - AffinityType: "", - IPVersion: 0, - }, "") - - // Expect an error since the client is not implemented. - Expect(err).To(HaveOccurred()) - Expect(l).To(BeNil()) - - // But we should be able to check the request... - url := fakeREST.Req.URL - logrus.Debug("URL: ", url) - Expect(url.Path).To(Equal("/apis/blockaffinities")) - - // TODO We can't currently use a field selector here but we'd like to! - // The name of the resource combines the host and CIDR, so we can't - // filter on just the host. Possible fix: put the host in a label - // so we can filter on that. - Expect(url.Query()).NotTo(HaveKey("metadata.name")) + Context("v3.BlockAffinity tests", func() { + BeforeEach(func() { + fakeREST = &fake.RESTClient{ + NegotiatedSerializer: serializer.WithoutConversionCodecFactory{CodecFactory: scheme.Codecs}, + GroupVersion: schema.GroupVersion{ + Group: "crd.projectcalico.org", + Version: "v1", + }, + VersionedAPIPath: "/apis", + } + client = resources.NewBlockAffinityClientV3(fakeREST, resources.BackingAPIGroupV1) + }) + + It("should list all", func() { + l, err := client.List(context.TODO(), model.ResourceListOptions{ + Kind: apiv3.KindBlockAffinity, + }, "") + + // Expect an error since the client is not implemented. + Expect(err).To(HaveOccurred()) + Expect(l).To(BeNil()) + + // But we should be able to check the request... + url := fakeREST.Req.URL + logrus.Debug("URL: ", url) + Expect(url.Path).To(Equal("/apis/blockaffinities")) + Expect(url.Query()).NotTo(HaveKey("metadata.name")) + }) + + It("should use a fieldSelector for a list name match", func() { + l, err := client.List(context.TODO(), model.ResourceListOptions{ + Name: "foo", + Kind: apiv3.KindBlockAffinity, + }, "") + + // Expect an error since the client is not implemented. + Expect(err).To(HaveOccurred()) + Expect(l).To(BeNil()) + + // But we should be able to check the request... + url := fakeREST.Req.URL + logrus.Debug("URL: ", url) + Expect(url.Path).To(Equal("/apis/blockaffinities")) + Expect(url.Query().Get("fieldSelector")).To(Equal("metadata.name=foo")) + }) + + It("should auto-label v3 BlockAffinity on create", func() { + val := &apiv3.BlockAffinity{ + TypeMeta: metav1.TypeMeta{ + Kind: apiv3.KindBlockAffinity, + APIVersion: apiv3.GroupVersionCurrent, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "ba1", + Labels: map[string]string{}, + }, + Spec: apiv3.BlockAffinitySpec{ + State: apiv3.StateConfirmed, + Node: "v3-host", + Type: string(ipam.AffinityTypeHost), + CIDR: "10.20.0.0/24", + Deleted: false, + }, + } + + kvp := &model.KVPair{ + Key: model.ResourceKey{Name: "ba1", Kind: apiv3.KindBlockAffinity}, + Value: val, + } + + // We expect an error since fake REST doesn't return a response, but the request will be captured. + _, _ = client.Create(context.TODO(), kvp) + + // Inspect the request body for labels. + req := fakeREST.Req + Expect(req).ToNot(BeNil()) + Expect(req.URL.Path).To(Equal("/apis/blockaffinities")) + bodyBytes, err := io.ReadAll(req.Body) + Expect(err).NotTo(HaveOccurred()) + + // The posted object should be in the backend libapiv3 format. + var posted internalapi.BlockAffinity + Expect(json.Unmarshal(bodyBytes, &posted)).To(Succeed(), string(bodyBytes)) + Expect(posted.Labels).To(HaveKey(apiv3.LabelHostnameHash)) + Expect(posted.Labels[apiv3.LabelHostnameHash]).NotTo(BeEmpty()) + Expect(posted.Labels).To(HaveKeyWithValue(apiv3.LabelAffinityType, string(ipam.AffinityTypeHost))) + Expect(posted.Labels).To(HaveKeyWithValue(apiv3.LabelIPVersion, "4")) + }) }) - It("should auto-label v1 BlockAffinity on create", func() { - kvp := &model.KVPair{ - Key: model.BlockAffinityKey{ - Host: "my-host", - CIDR: net.MustParseCIDR("192.168.1.0/24"), - AffinityType: string(ipam.AffinityTypeHost), - }, - Value: &model.BlockAffinity{State: model.StateConfirmed}, - } - - // We expect an error since fake REST doesn't return a response, but the request will be captured. - _, _ = client.Create(context.TODO(), kvp) - - // Inspect the request body for labels. - req := fakeREST.Req - Expect(req).ToNot(BeNil()) - Expect(req.URL.Path).To(Equal("/apis/blockaffinities")) - bodyBytes, err := io.ReadAll(req.Body) - Expect(err).NotTo(HaveOccurred()) - - var posted libapiv3.BlockAffinity - Expect(json.Unmarshal(bodyBytes, &posted)).To(Succeed()) - Expect(posted.Labels).To(HaveKey(apiv3.LabelHostnameHash)) - Expect(posted.Labels[apiv3.LabelHostnameHash]).NotTo(BeEmpty()) - Expect(posted.Labels).To(HaveKeyWithValue(apiv3.LabelAffinityType, string(ipam.AffinityTypeHost))) - Expect(posted.Labels).To(HaveKeyWithValue(apiv3.LabelIPVersion, "4")) - }) - - It("should auto-label v3 BlockAffinity on create", func() { - val := &libapiv3.BlockAffinity{ - TypeMeta: metav1.TypeMeta{ - Kind: libapiv3.KindBlockAffinity, - APIVersion: apiv3.GroupVersionCurrent, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "ba1", - Labels: map[string]string{}, - }, - Spec: libapiv3.BlockAffinitySpec{ - State: string(model.StateConfirmed), - Node: "v3-host", - Type: string(ipam.AffinityTypeHost), - CIDR: "10.20.0.0/24", - Deleted: "false", - }, - } - kvp := &model.KVPair{ - Key: model.ResourceKey{Name: "ba1", Kind: libapiv3.KindBlockAffinity}, - Value: val, - } - - // We expect an error since fake REST doesn't return a response, but the request will be captured. - _, _ = client.Create(context.TODO(), kvp) - - // Inspect the request body for labels. - req := fakeREST.Req - Expect(req).ToNot(BeNil()) - Expect(req.URL.Path).To(Equal("/apis/blockaffinities")) - bodyBytes, err := io.ReadAll(req.Body) - Expect(err).NotTo(HaveOccurred()) - - var posted libapiv3.BlockAffinity - Expect(json.Unmarshal(bodyBytes, &posted)).To(Succeed()) - Expect(posted.Labels).To(HaveKey(apiv3.LabelHostnameHash)) - Expect(posted.Labels[apiv3.LabelHostnameHash]).NotTo(BeEmpty()) - Expect(posted.Labels).To(HaveKeyWithValue(apiv3.LabelAffinityType, string(ipam.AffinityTypeHost))) - Expect(posted.Labels).To(HaveKeyWithValue(apiv3.LabelIPVersion, "4")) + Context("v1.BlockAffinity tests", func() { + BeforeEach(func() { + fakeREST = &fake.RESTClient{ + NegotiatedSerializer: serializer.WithoutConversionCodecFactory{CodecFactory: scheme.Codecs}, + GroupVersion: schema.GroupVersion{ + Group: "crd.projectcalico.org", + Version: "v1", + }, + VersionedAPIPath: "/apis", + } + client = resources.NewBlockAffinityClientV1(fakeREST, resources.BackingAPIGroupV1) + }) + + It("should list all", func() { + l, err := client.List(context.TODO(), model.BlockAffinityListOptions{}, "") + + // Expect an error since the client is not implemented. + Expect(err).To(HaveOccurred()) + Expect(l).To(BeNil()) + + // But we should be able to check the request... + url := fakeREST.Req.URL + logrus.Debug("URL: ", url) + Expect(url.Path).To(Equal("/apis/blockaffinities")) + Expect(url.Query()).NotTo(HaveKey("metadata.name")) + }) + + It("should _not_ use a fieldSelector for a list name match", func() { + l, err := client.List(context.TODO(), model.BlockAffinityListOptions{ + Host: "host", + AffinityType: "", + IPVersion: 0, + }, "") + + // Expect an error since the client is not implemented. + Expect(err).To(HaveOccurred()) + Expect(l).To(BeNil()) + + // But we should be able to check the request... + url := fakeREST.Req.URL + logrus.Debug("URL: ", url) + Expect(url.Path).To(Equal("/apis/blockaffinities")) + + // TODO We can't currently use a field selector here but we'd like to! + // The name of the resource combines the host and CIDR, so we can't + // filter on just the host. Possible fix: put the host in a label + // so we can filter on that. + Expect(url.Query()).NotTo(HaveKey("metadata.name")) + }) + + It("should auto-label v1 BlockAffinity on create", func() { + kvp := &model.KVPair{ + Key: model.BlockAffinityKey{ + Host: "my-host", + CIDR: net.MustParseCIDR("192.168.1.0/24"), + AffinityType: string(ipam.AffinityTypeHost), + }, + Value: &model.BlockAffinity{State: model.StateConfirmed}, + } + + // We expect an error since fake REST doesn't return a response, but the request will be captured. + _, _ = client.Create(context.TODO(), kvp) + + // Inspect the request body for labels. + req := fakeREST.Req + Expect(req).ToNot(BeNil()) + Expect(req.URL.Path).To(Equal("/apis/blockaffinities")) + bodyBytes, err := io.ReadAll(req.Body) + Expect(err).NotTo(HaveOccurred()) + + var posted internalapi.BlockAffinity + Expect(json.Unmarshal(bodyBytes, &posted)).To(Succeed(), string(bodyBytes)) + Expect(posted.Labels).To(HaveKey(apiv3.LabelHostnameHash)) + Expect(posted.Labels[apiv3.LabelHostnameHash]).NotTo(BeEmpty()) + Expect(posted.Labels).To(HaveKeyWithValue(apiv3.LabelAffinityType, string(ipam.AffinityTypeHost))) + Expect(posted.Labels).To(HaveKeyWithValue(apiv3.LabelIPVersion, "4")) + }) }) }) diff --git a/libcalico-go/lib/backend/k8s/resources/ipam_affinity_v1.go b/libcalico-go/lib/backend/k8s/resources/ipam_affinity_v1.go new file mode 100644 index 00000000000..71b62139f28 --- /dev/null +++ b/libcalico-go/lib/backend/k8s/resources/ipam_affinity_v1.go @@ -0,0 +1,344 @@ +// Copyright (c) 2019-2026 Tigera, Inc. All rights reserved. + +// 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 resources + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "fmt" + "reflect" + "strconv" + + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + log "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/cache" + + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" + "github.com/projectcalico/calico/libcalico-go/lib/backend/api" + "github.com/projectcalico/calico/libcalico-go/lib/backend/model" + cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" + "github.com/projectcalico/calico/libcalico-go/lib/names" + "github.com/projectcalico/calico/libcalico-go/lib/net" +) + +// NewBlockAffinityClientV1 returns a new client for managing BlockAffinity resources, as used by the +// libcalico-go/lib/ipam code. +func NewBlockAffinityClientV1(r rest.Interface, group BackingAPIGroup) K8sResourceClient { + // Create a resource client which manages k8s CRDs. + rc := customResourceClient{ + restClient: r, + resource: BlockAffinityResourceName, + k8sResourceType: reflect.TypeFor[internalapi.BlockAffinity](), + k8sListType: reflect.TypeFor[internalapi.BlockAffinityList](), + kind: v3.KindBlockAffinity, + apiGroup: group, + } + + if group == BackingAPIGroupV3 { + // If this is a v3 resource, then we need to use the v3 API types, as they + // differ. + rc.k8sResourceType = reflect.TypeFor[v3.BlockAffinity]() + rc.k8sListType = reflect.TypeFor[v3.BlockAffinityList]() + } + + return &blockAffinityClientV1{ + rc: rc, + crdIsV3: group == BackingAPIGroupV3, + } +} + +type blockAffinityClientV1 struct { + rc customResourceClient + crdIsV3 bool +} + +// toModelV1 converts the given CRD KVPair into a model representation +// which can be passed to the IPAM code. +func (c *blockAffinityClientV1) toModelV1(kvpv3 *model.KVPair) (*model.KVPair, error) { + if c.crdIsV3 { + // Parse the CIDR into a struct. + _, cidr, err := net.ParseCIDR(kvpv3.Value.(*v3.BlockAffinity).Spec.CIDR) + if err != nil { + log.WithField("cidr", cidr).WithError(err).Error("failed to parse cidr") + return nil, err + } + state := model.BlockAffinityState(kvpv3.Value.(*v3.BlockAffinity).Spec.State) + + // Determine deleted status. + del := kvpv3.Value.(*v3.BlockAffinity).Spec.Deleted + + // Default affinity type to "host" if not set. Older versions of Calico's CRD backend + // did not set this field, assuming "host" as the default. + affinityType := "host" + if kvpv3.Value.(*v3.BlockAffinity).Spec.Type != "" { + affinityType = kvpv3.Value.(*v3.BlockAffinity).Spec.Type + } + + return &model.KVPair{ + Key: model.BlockAffinityKey{ + CIDR: *cidr, + AffinityType: affinityType, + Host: kvpv3.Value.(*v3.BlockAffinity).Spec.Node, + }, + Value: &model.BlockAffinity{ + State: state, + Deleted: del, + }, + Revision: kvpv3.Revision, + UID: &kvpv3.Value.(*v3.BlockAffinity).UID, + }, nil + + } else { + // Parse the CIDR into a struct. + _, cidr, err := net.ParseCIDR(kvpv3.Value.(*internalapi.BlockAffinity).Spec.CIDR) + if err != nil { + log.WithField("cidr", cidr).WithError(err).Error("failed to parse cidr") + return nil, err + } + state := model.BlockAffinityState(kvpv3.Value.(*internalapi.BlockAffinity).Spec.State) + + // Determine deleted status. + deletedString := kvpv3.Value.(*internalapi.BlockAffinity).Spec.Deleted + del := false + if deletedString != "" { + del, err = strconv.ParseBool(deletedString) + if err != nil { + return nil, fmt.Errorf("Failed to parse deleted value as bool: %s", err) + } + } + + // Default affinity type to "host" if not set. Older versions of Calico's CRD backend + // did not set this field, assuming "host" as the default. + affinityType := "host" + if kvpv3.Value.(*internalapi.BlockAffinity).Spec.Type != "" { + affinityType = kvpv3.Value.(*internalapi.BlockAffinity).Spec.Type + } + + return &model.KVPair{ + Key: model.BlockAffinityKey{ + CIDR: *cidr, + AffinityType: affinityType, + Host: kvpv3.Value.(*internalapi.BlockAffinity).Spec.Node, + }, + Value: &model.BlockAffinity{ + State: state, + Deleted: del, + }, + Revision: kvpv3.Revision, + UID: &kvpv3.Value.(*internalapi.BlockAffinity).UID, + }, nil + } +} + +// parseKey parses the given model.Key, returning a suitable name, CIDR +// and host for use in the Kubernetes API. +func (c *blockAffinityClientV1) parseKey(k model.Key) (name, cidr, host, affinityType string) { + host = k.(model.BlockAffinityKey).Host + affinityType = k.(model.BlockAffinityKey).AffinityType + cidr = fmt.Sprintf("%s", k.(model.BlockAffinityKey).CIDR) + cidrname := names.CIDRToName(k.(model.BlockAffinityKey).CIDR) + + // Include the hostname as well. + name = fmt.Sprintf("%s-%s", host, cidrname) + + if len(name) >= 253 { + // If the name is too long, we need to shorten it. + // Remove enough characters to get it below the 253 character limit, + // as well as 11 characters to add a hash which helps with uniqueness, + // and two characters for the `-` separators between clauses. + name = fmt.Sprintf("%s-%s", host[:252-len(cidrname)-13], cidrname) + + // Add a hash to help with uniqueness. + h := sha256.New() + h.Write(fmt.Appendf(nil, "%s+%s", host, cidrname)) + name = fmt.Sprintf("%s-%s", name, hex.EncodeToString(h.Sum(nil))[:11]) + } + return +} + +// toCRD converts the given model KVPair containing a model.BlockAffinity into a +// CRD KVPair containing a CRD representation of the BlockAffinity. +func (c *blockAffinityClientV1) toCRD(kvpv1 *model.KVPair) *model.KVPair { + name, cidr, host, affinityType := c.parseKey(kvpv1.Key) + state := kvpv1.Value.(*model.BlockAffinity).State + + // Build the CRD representation. + value := buildCRD( + string(state), + host, + affinityType, + cidr, + kvpv1.Value.(*model.BlockAffinity).Deleted, + name, + kvpv1.Revision, + c.crdIsV3, + ) + + return &model.KVPair{ + Key: model.ResourceKey{ + Name: name, + Kind: v3.KindBlockAffinity, + }, + Value: value, + Revision: kvpv1.Revision, + } +} + +func (c *blockAffinityClientV1) Create(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { + nkvp, err := c.rc.Create(ctx, c.toCRD(kvp)) + if err != nil { + return nil, err + } + + v1kvp, err := c.toModelV1(nkvp) + if err != nil { + return nil, err + } + return v1kvp, nil +} + +func (c *blockAffinityClientV1) Update(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { + nkvp, err := c.rc.Update(ctx, c.toCRD(kvp)) + if err != nil { + return nil, err + } + + v1kvp, err := c.toModelV1(nkvp) + if err != nil { + return nil, err + } + return v1kvp, nil +} + +func (c *blockAffinityClientV1) DeleteKVP(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { + // We need to mark as deleted first, since the Kubernetes API doesn't support + // compare-and-delete. This update operation allows us to eliminate races with other clients. + name, _, _, _ := c.parseKey(kvp.Key) + kvp.Value.(*model.BlockAffinity).Deleted = true + v1kvp, err := c.Update(ctx, kvp) + if err != nil { + return nil, err + } + + // Now actually delete the object. + k := model.ResourceKey{Name: name, Kind: internalapi.KindBlockAffinity} + kvp, err = c.rc.Delete(ctx, k, v1kvp.Revision, kvp.UID) + if err != nil { + return nil, err + } + return c.toModelV1(kvp) +} + +func (c *blockAffinityClientV1) Delete(ctx context.Context, key model.Key, revision string, uid *types.UID) (*model.KVPair, error) { + // Delete should not be used for affinities, since we need the object UID for correctness. + log.Warn("Operation Delete is not supported on BlockAffinity type - use DeleteKVP") + return nil, cerrors.ErrorOperationNotSupported{ + Identifier: key, + Operation: "Delete", + } +} + +func (c *blockAffinityClientV1) Get(ctx context.Context, key model.Key, revision string) (*model.KVPair, error) { + // Get the object. + name, _, _, _ := c.parseKey(key) + k := model.ResourceKey{Name: name, Kind: internalapi.KindBlockAffinity} + kvp, err := c.rc.Get(ctx, k, revision) + if err != nil { + return nil, err + } + + // Convert it to v1. + v1kvp, err := c.toModelV1(kvp) + if err != nil { + return nil, err + } + + // If this object has been marked as deleted, then we need to clean it up and + // return not found. + if v1kvp.Value.(*model.BlockAffinity).Deleted { + if _, err := c.DeleteKVP(ctx, v1kvp); err != nil { + return nil, err + } + return nil, cerrors.ErrorResourceDoesNotExist{Err: fmt.Errorf("Resource was deleted"), Identifier: key} + } + + return v1kvp, nil +} + +func (c *blockAffinityClientV1) List(ctx context.Context, li model.ListInterface, revision string) (*model.KVPairList, error) { + list := li.(model.BlockAffinityListOptions) + log.Debugf("Listing v1 block affinities with host %s, affinity type %s, IP version %d", list.Host, list.AffinityType, list.IPVersion) + l := model.ResourceListOptions{ + Kind: internalapi.KindBlockAffinity, + LabelSelector: model.CalculateBlockAffinityLabelSelector(list), + } + crdList, err := c.rc.List(ctx, l, revision) + if err != nil { + return nil, err + } + + host := list.Host + affinityType := list.AffinityType + requestedIPVersion := list.IPVersion + + kvpl := &model.KVPairList{ + KVPairs: []*model.KVPair{}, + Revision: crdList.Revision, + } + for _, i := range crdList.KVPairs { + v1kvp, err := c.toModelV1(i) + if err != nil { + return nil, err + } + + if (host == "" || v1kvp.Key.(model.BlockAffinityKey).Host == host) && + (affinityType == "" || v1kvp.Key.(model.BlockAffinityKey).AffinityType == affinityType) { + cidr := v1kvp.Key.(model.BlockAffinityKey).CIDR + cidrPtr := &cidr + if (requestedIPVersion == 0 || requestedIPVersion == cidrPtr.Version()) && !v1kvp.Value.(*model.BlockAffinity).Deleted { + // Matches the given host and IP version. + kvpl.KVPairs = append(kvpl.KVPairs, v1kvp) + } + } + } + return kvpl, nil +} + +func (c *blockAffinityClientV1) toKVPairV1(r Resource) (*model.KVPair, error) { + conv, err := c.rc.convertResourceToKVPair(r) + if err != nil { + return nil, err + } + return c.toModelV1(conv) +} + +func (c *blockAffinityClientV1) Watch(ctx context.Context, list model.ListInterface, options api.WatchOptions) (api.WatchInterface, error) { + resl := model.ResourceListOptions{Kind: internalapi.KindBlockAffinity} + k8sWatchClient := cache.NewListWatchFromClient(c.rc.restClient, c.rc.resource, "", fields.Everything()) + k8sOpts := watchOptionsToK8sListOptions(options) + k8sWatch, err := k8sWatchClient.WatchFunc(k8sOpts) + if err != nil { + return nil, K8sErrorToCalico(err, list) + } + return newK8sWatcherConverter(ctx, resl.Kind+" (custom)", c.toKVPairV1, k8sWatch), nil +} + +func (c *blockAffinityClientV1) EnsureInitialized() error { + return nil +} diff --git a/libcalico-go/lib/backend/k8s/resources/ipam_affinity_v3.go b/libcalico-go/lib/backend/k8s/resources/ipam_affinity_v3.go new file mode 100644 index 00000000000..dbebf870340 --- /dev/null +++ b/libcalico-go/lib/backend/k8s/resources/ipam_affinity_v3.go @@ -0,0 +1,259 @@ +// Copyright (c) 2019-2026 Tigera, Inc. All rights reserved. + +// 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 resources + +import ( + "context" + "fmt" + "reflect" + + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + log "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/cache" + + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" + "github.com/projectcalico/calico/libcalico-go/lib/backend/api" + "github.com/projectcalico/calico/libcalico-go/lib/backend/model" + cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" +) + +const ( + BlockAffinityResourceName = "BlockAffinities" +) + +// NewBlockAffinityClientV3 returns a new client for managing BlockAffinity resources, as used by the +// libcalico-go/lib/clientv3 code. +func NewBlockAffinityClientV3(r rest.Interface, group BackingAPIGroup) K8sResourceClient { + // Create a resource client which manages k8s CRDs. + rc := customResourceClient{ + restClient: r, + resource: BlockAffinityResourceName, + k8sResourceType: reflect.TypeFor[internalapi.BlockAffinity](), + k8sListType: reflect.TypeFor[internalapi.BlockAffinityList](), + kind: v3.KindBlockAffinity, + versionconverter: ipamAffinityVersionConverter{}, + apiGroup: group, + } + + if group == BackingAPIGroupV3 { + // If this is a v3 resource, then we need to use the v3 API types, as they + // differ. + rc.k8sResourceType = reflect.TypeFor[v3.BlockAffinity]() + rc.k8sListType = reflect.TypeFor[v3.BlockAffinityList]() + } + + return &blockAffinityClientV3{ + rc: rc, + crdIsV3: group == BackingAPIGroupV3, + } +} + +type blockAffinityClientV3 struct { + rc customResourceClient + crdIsV3 bool +} + +// ipamAffinityVersionConverter handles conversion between the v3 API and CRD representations of ipamAffinity. +type ipamAffinityVersionConverter struct{} + +// ConvertFromK8s converts the given CRD KVPair into a v3 API representation which can be passed back to the clientv3 code. +func (c ipamAffinityVersionConverter) ConvertFromK8s(r Resource) (Resource, error) { + switch o := r.(type) { + case *internalapi.BlockAffinity: + // This is a crd.projectcalico.org/v1 CRD, convert it to the v3 API struct expected by clientv3. + return &v3.BlockAffinity{ + TypeMeta: metav1.TypeMeta{ + Kind: v3.KindBlockAffinity, + APIVersion: "projectcalico.org/v3", + }, + ObjectMeta: o.ObjectMeta, + Spec: v3.BlockAffinitySpec{ + State: v3.BlockAffinityState(o.Spec.State), + Node: o.Spec.Node, + Type: o.Spec.Type, + CIDR: o.Spec.CIDR, + Deleted: o.Spec.Deleted == "true", + }, + }, nil + case *v3.BlockAffinity: + // No conversion necessary - already using v3 CRDs. + return r, nil + } + return nil, fmt.Errorf("invalid type for IPAM configuration KVPair: %T", r) +} + +func getBackingAffinityTypeMeta(isV3 bool) metav1.TypeMeta { + if isV3 { + // If this is a v3 resource, then we need to use the v3 API version. + return metav1.TypeMeta{ + Kind: v3.KindBlockAffinity, + APIVersion: "projectcalico.org/v3", + } + } + return metav1.TypeMeta{ + Kind: internalapi.KindBlockAffinity, + APIVersion: "crd.projectcalico.org/v1", + } +} + +// buildCRD builds a CRD representation of a BlockAffinity resource with the given parameters. +func buildCRD(state, host, affType, cidr string, deleted bool, name, revision string, isV3 bool) Resource { + if isV3 { + // If this is a v3 resource, then we need to use the canonical v3 API version and types. + ba := &v3.BlockAffinity{ + TypeMeta: getBackingAffinityTypeMeta(isV3), + ObjectMeta: metav1.ObjectMeta{ + Name: name, + ResourceVersion: revision, + }, + Spec: v3.BlockAffinitySpec{ + State: v3.BlockAffinityState(state), + Node: host, + Type: affType, + CIDR: cidr, + Deleted: deleted, + }, + } + model.EnsureBlockAffinityLabelsV3(ba) + return ba + } + + // If this is a v1 resource, then we need to use the old v1 API version and types. + ba := &internalapi.BlockAffinity{ + TypeMeta: getBackingAffinityTypeMeta(isV3), + ObjectMeta: metav1.ObjectMeta{ + Name: name, + ResourceVersion: revision, + }, + Spec: internalapi.BlockAffinitySpec{ + State: string(state), + Node: host, + Type: affType, + CIDR: cidr, + Deleted: fmt.Sprintf("%t", deleted), + }, + } + model.EnsureBlockAffinityLabels(ba) + return ba +} + +func (c *blockAffinityClientV3) toCRD(kvpv3 *model.KVPair) *model.KVPair { + // Extract fields from the v3 BlockAffinity. + obj := kvpv3.Value.(*v3.BlockAffinity) + name := kvpv3.Key.(model.ResourceKey).Name + + // Build the CRD representation. + value := buildCRD( + string(obj.Spec.State), + obj.Spec.Node, + obj.Spec.Type, + obj.Spec.CIDR, + obj.Spec.Deleted, + name, + kvpv3.Revision, + c.crdIsV3, + ) + + return &model.KVPair{ + Key: model.ResourceKey{ + Name: name, + Kind: v3.KindBlockAffinity, + }, + Value: value, + Revision: kvpv3.Revision, + } +} + +func (c *blockAffinityClientV3) Create(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { + return c.rc.Create(ctx, c.toCRD(kvp)) +} + +func (c *blockAffinityClientV3) Update(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { + return c.rc.Update(ctx, c.toCRD(kvp)) +} + +func (c *blockAffinityClientV3) DeleteKVP(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { + // We need to mark as deleted first, since the Kubernetes API doesn't support + // compare-and-delete. This update operation allows us to eliminate races with other clients. + var err error + nkvp := kvp + if kvp.Value == nil { + // Need to check if a value is given since V3 deletes can be made by providing a key only. + // Look up missing values with the provided key. + nkvp, err = c.Get(ctx, kvp.Key.(model.ResourceKey), kvp.Revision) + if err != nil { + if _, ok := err.(cerrors.ErrorResourceDoesNotExist); ok { + return nil, fmt.Errorf("Unable to find block affinity. Block affinity may have already been deleted.") + } + return nil, fmt.Errorf("Error retrieving block affinity for deletion: %s", err) + } + } + + // Pass in the revision for the key-value pair to ensure that deletion occurs for the specified revision, + // not the revision that is retrieved by the above Get (which should be the most recent). + if kvp.Revision == "" { + return nil, fmt.Errorf("Unable to delete block affinity without a resource version") + } + nkvp.Revision = kvp.Revision + nkvp.Value.(*v3.BlockAffinity).Spec.Deleted = true + nkvp, err = c.Update(ctx, nkvp) + if err != nil { + return nil, err + } + + // Now actually delete the object. + return c.rc.Delete(ctx, nkvp.Key, nkvp.Revision, &nkvp.Value.(*v3.BlockAffinity).UID) +} + +func (c *blockAffinityClientV3) Delete(ctx context.Context, key model.Key, revision string, uid *types.UID) (*model.KVPair, error) { + // Delete should not be used for affinities, since we need the object UID for correctness. + log.Warn("Operation Delete is not supported on BlockAffinity type - use DeleteKVP") + return nil, cerrors.ErrorOperationNotSupported{ + Identifier: key, + Operation: "Delete", + } +} + +func (c *blockAffinityClientV3) Get(ctx context.Context, key model.Key, revision string) (*model.KVPair, error) { + return c.rc.Get(ctx, key, revision) +} + +func (c *blockAffinityClientV3) List(ctx context.Context, list model.ListInterface, revision string) (*model.KVPairList, error) { + log.Debugf("Listing v3 block affinities matching %v, revision=%v", list, revision) + return c.rc.List(ctx, list, revision) +} + +func (c *blockAffinityClientV3) toKVPairV3(r Resource) (*model.KVPair, error) { + return c.rc.convertResourceToKVPair(r) +} + +func (c *blockAffinityClientV3) Watch(ctx context.Context, list model.ListInterface, options api.WatchOptions) (api.WatchInterface, error) { + resl := model.ResourceListOptions{Kind: internalapi.KindBlockAffinity} + k8sWatchClient := cache.NewListWatchFromClient(c.rc.restClient, c.rc.resource, "", fields.Everything()) + k8sOpts := watchOptionsToK8sListOptions(options) + k8sWatch, err := k8sWatchClient.WatchFunc(k8sOpts) + if err != nil { + return nil, K8sErrorToCalico(err, list) + } + return newK8sWatcherConverter(ctx, resl.Kind+" (custom)", c.toKVPairV3, k8sWatch), nil +} + +func (c *blockAffinityClientV3) EnsureInitialized() error { + return nil +} diff --git a/libcalico-go/lib/backend/k8s/resources/ipam_block.go b/libcalico-go/lib/backend/k8s/resources/ipam_block.go index d6ff3bee8dc..2a8cb09cd4b 100644 --- a/libcalico-go/lib/backend/k8s/resources/ipam_block.go +++ b/libcalico-go/lib/backend/k8s/resources/ipam_block.go @@ -1,4 +1,4 @@ -// Copyright (c) 2019 Tigera, Inc. All rights reserved. +// Copyright (c) 2019-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,16 +19,15 @@ import ( "fmt" "reflect" - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/cache" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" @@ -38,27 +37,29 @@ import ( const ( IPAMBlockResourceName = "IPAMBlocks" - IPAMBlockCRDName = "ipamblocks.crd.projectcalico.org" ) -func NewIPAMBlockClient(c kubernetes.Interface, r rest.Interface) K8sResourceClient { +func NewIPAMBlockClient(r rest.Interface, group BackingAPIGroup) K8sResourceClient { // Create a resource client which manages k8s CRDs. - rc := customK8sResourceClient{ - clientSet: c, + rc := customResourceClient{ restClient: r, - name: IPAMBlockCRDName, resource: IPAMBlockResourceName, - description: "Calico IPAM blocks", - k8sResourceType: reflect.TypeOf(libapiv3.IPAMBlock{}), - k8sResourceTypeMeta: metav1.TypeMeta{ - Kind: libapiv3.KindIPAMBlock, - APIVersion: apiv3.GroupVersionCurrent, - }, - k8sListType: reflect.TypeOf(libapiv3.IPAMBlockList{}), - resourceKind: libapiv3.KindIPAMBlock, + k8sResourceType: reflect.TypeFor[internalapi.IPAMBlock](), + k8sListType: reflect.TypeFor[internalapi.IPAMBlockList](), + kind: internalapi.KindIPAMBlock, + apiGroup: group, } - return &ipamBlockClient{rc: rc} + if group == BackingAPIGroupV3 { + // If this is a v3 resource, then we need to use the v3 API types, as they differ. + rc.k8sResourceType = reflect.TypeFor[v3.IPAMBlock]() + rc.k8sListType = reflect.TypeFor[v3.IPAMBlockList]() + } + + return &ipamBlockClient{ + rc: rc, + v3: group == BackingAPIGroupV3, + } } // ipamBlockClient implements the api.Client interface for IPAMBlocks. It handles the translation between @@ -66,16 +67,17 @@ func NewIPAMBlockClient(c kubernetes.Interface, r rest.Interface) K8sResourceCli // to actually store the data in the Kubernetes API. It uses a customK8sResourceClient under // the covers to perform CRUD operations on kubernetes CRDs. type ipamBlockClient struct { - rc customK8sResourceClient + rc customResourceClient + v3 bool } func (c *ipamBlockClient) Create(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { - nkvp := IPAMBlockV1toV3(kvp) + nkvp := c.IPAMBlockV1toV3(kvp) b, err := c.rc.Create(ctx, nkvp) if err != nil { return nil, err } - v1kvp, err := IPAMBlockV3toV1(b) + v1kvp, err := c.IPAMBlockV3toV1(b) if err != nil { return nil, err } @@ -83,12 +85,12 @@ func (c *ipamBlockClient) Create(ctx context.Context, kvp *model.KVPair) (*model } func (c *ipamBlockClient) Update(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { - nkvp := IPAMBlockV1toV3(kvp) + nkvp := c.IPAMBlockV1toV3(kvp) b, err := c.rc.Update(ctx, nkvp) if err != nil { return nil, err } - v1kvp, err := IPAMBlockV3toV1(b) + v1kvp, err := c.IPAMBlockV3toV1(b) if err != nil { return nil, err } @@ -106,12 +108,12 @@ func (c *ipamBlockClient) DeleteKVP(ctx context.Context, kvp *model.KVPair) (*mo } // Now actually delete the object. - k := model.ResourceKey{Name: name, Kind: libapiv3.KindIPAMBlock} + k := model.ResourceKey{Name: name, Kind: internalapi.KindIPAMBlock} kvp, err = c.rc.Delete(ctx, k, v1kvp.Revision, kvp.UID) if err != nil { return nil, err } - return IPAMBlockV3toV1(kvp) + return c.IPAMBlockV3toV1(kvp) } func (c *ipamBlockClient) Delete(ctx context.Context, key model.Key, revision string, uid *types.UID) (*model.KVPair, error) { @@ -126,14 +128,14 @@ func (c *ipamBlockClient) Delete(ctx context.Context, key model.Key, revision st func (c *ipamBlockClient) Get(ctx context.Context, key model.Key, revision string) (*model.KVPair, error) { // Get the object. name, _ := parseKey(key) - k := model.ResourceKey{Name: name, Kind: libapiv3.KindIPAMBlock} + k := model.ResourceKey{Name: name, Kind: internalapi.KindIPAMBlock} kvp, err := c.rc.Get(ctx, k, revision) if err != nil { return nil, err } // Convert it back to V1 format. - v1kvp, err := IPAMBlockV3toV1(kvp) + v1kvp, err := c.IPAMBlockV3toV1(kvp) if err != nil { return nil, err } @@ -151,7 +153,7 @@ func (c *ipamBlockClient) Get(ctx context.Context, key model.Key, revision strin } func (c *ipamBlockClient) List(ctx context.Context, list model.ListInterface, revision string) (*model.KVPairList, error) { - l := model.ResourceListOptions{Kind: libapiv3.KindIPAMBlock} + l := model.ResourceListOptions{Kind: internalapi.KindIPAMBlock} v3list, err := c.rc.List(ctx, l, revision) if err != nil { return nil, err @@ -162,7 +164,7 @@ func (c *ipamBlockClient) List(ctx context.Context, list model.ListInterface, re Revision: v3list.Revision, } for _, i := range v3list.KVPairs { - v1kvp, err := IPAMBlockV3toV1(i) + v1kvp, err := c.IPAMBlockV3toV1(i) if err != nil { return nil, err } @@ -172,7 +174,7 @@ func (c *ipamBlockClient) List(ctx context.Context, list model.ListInterface, re } func (c *ipamBlockClient) Watch(ctx context.Context, list model.ListInterface, options api.WatchOptions) (api.WatchInterface, error) { - resl := model.ResourceListOptions{Kind: libapiv3.KindIPAMBlock} + resl := model.ResourceListOptions{Kind: internalapi.KindIPAMBlock} k8sWatchClient := cache.NewListWatchFromClient(c.rc.restClient, c.rc.resource, "", fields.Everything()) k8sOpts := watchOptionsToK8sListOptions(options) k8sWatch, err := k8sWatchClient.WatchFunc(k8sOpts) @@ -184,7 +186,7 @@ func (c *ipamBlockClient) Watch(ctx context.Context, list model.ListInterface, o if err != nil { return nil, err } - return IPAMBlockV3toV1(conv) + return c.IPAMBlockV3toV1(conv) } return newK8sWatcherConverter(ctx, resl.Kind+" (custom)", toKVPair, k8sWatch), nil @@ -196,83 +198,177 @@ func (c *ipamBlockClient) EnsureInitialized() error { return nil } -func IPAMBlockV3toV1(kvpv3 *model.KVPair) (*model.KVPair, error) { - cidrStr := kvpv3.Value.(*libapiv3.IPAMBlock).Spec.CIDR - _, cidr, err := net.ParseCIDR(cidrStr) - if err != nil { - return nil, err - } +func (c *ipamBlockClient) IPAMBlockV3toV1(kvpv3 *model.KVPair) (*model.KVPair, error) { + switch kvpv3.Value.(type) { + case *v3.IPAMBlock: + cidrStr := kvpv3.Value.(*v3.IPAMBlock).Spec.CIDR + _, cidr, err := net.ParseCIDR(cidrStr) + if err != nil { + return nil, err + } - ab := kvpv3.Value.(*libapiv3.IPAMBlock) + ab := kvpv3.Value.(*v3.IPAMBlock) - // Convert attributes. - attrs := []model.AllocationAttribute{} - for _, a := range ab.Spec.Attributes { - attrs = append(attrs, model.AllocationAttribute{ - AttrPrimary: a.AttrPrimary, - AttrSecondary: a.AttrSecondary, - }) - } + // Convert attributes. + attrs := []model.AllocationAttribute{} + for _, a := range ab.Spec.Attributes { + attrs = append(attrs, model.AllocationAttribute{ + HandleID: a.HandleID, + ActiveOwnerAttrs: a.ActiveOwnerAttrs, + AlternateOwnerAttrs: a.AlternateOwnerAttrs, + }) + } - return &model.KVPair{ - Key: model.BlockKey{ - CIDR: *cidr, - }, - Value: &model.AllocationBlock{ - CIDR: *cidr, - Affinity: ab.Spec.Affinity, - Allocations: ab.Spec.Allocations, - Unallocated: ab.Spec.Unallocated, - Attributes: attrs, - Deleted: ab.Spec.Deleted, - SequenceNumber: ab.Spec.SequenceNumber, - SequenceNumberForAllocation: ab.Spec.SequenceNumberForAllocation, - }, - Revision: kvpv3.Revision, - UID: &ab.UID, - }, nil -} + return &model.KVPair{ + Key: model.BlockKey{ + CIDR: *cidr, + }, + Value: &model.AllocationBlock{ + CIDR: *cidr, + Affinity: ab.Spec.Affinity, + AffinityClaimTime: ab.Spec.AffinityClaimTime, + Allocations: ab.Spec.Allocations, + Unallocated: ab.Spec.Unallocated, + Attributes: attrs, + Deleted: ab.Spec.Deleted, + SequenceNumber: ab.Spec.SequenceNumber, + SequenceNumberForAllocation: ab.Spec.SequenceNumberForAllocation, + }, + Revision: kvpv3.Revision, + UID: &ab.UID, + }, nil + case *internalapi.IPAMBlock: + cidrStr := kvpv3.Value.(*internalapi.IPAMBlock).Spec.CIDR + _, cidr, err := net.ParseCIDR(cidrStr) + if err != nil { + return nil, err + } -func IPAMBlockV1toV3(kvpv1 *model.KVPair) *model.KVPair { - name, cidr := parseKey(kvpv1.Key) + ab := kvpv3.Value.(*internalapi.IPAMBlock) - ab := kvpv1.Value.(*model.AllocationBlock) + // Convert attributes. + attrs := []model.AllocationAttribute{} + for _, a := range ab.Spec.Attributes { + attrs = append(attrs, model.AllocationAttribute{ + HandleID: a.HandleID, + ActiveOwnerAttrs: a.ActiveOwnerAttrs, + AlternateOwnerAttrs: a.AlternateOwnerAttrs, + }) + } - // Convert attributes. - attrs := []libapiv3.AllocationAttribute{} - for _, a := range ab.Attributes { - attrs = append(attrs, libapiv3.AllocationAttribute{ - AttrPrimary: a.AttrPrimary, - AttrSecondary: a.AttrSecondary, - }) + return &model.KVPair{ + Key: model.BlockKey{ + CIDR: *cidr, + }, + Value: &model.AllocationBlock{ + CIDR: *cidr, + Affinity: ab.Spec.Affinity, + AffinityClaimTime: ab.Spec.AffinityClaimTime, + Allocations: ab.Spec.Allocations, + Unallocated: ab.Spec.Unallocated, + Attributes: attrs, + Deleted: ab.Spec.Deleted, + SequenceNumber: ab.Spec.SequenceNumber, + SequenceNumberForAllocation: ab.Spec.SequenceNumberForAllocation, + }, + Revision: kvpv3.Revision, + UID: &ab.UID, + }, nil } + return nil, fmt.Errorf("unexpected type %T for IPAMBlock", kvpv3.Value) +} + +func (c *ipamBlockClient) IPAMBlockV1toV3(kvpv1 *model.KVPair) *model.KVPair { + if c.v3 { + name, cidr := parseKey(kvpv1.Key) + + ab := kvpv1.Value.(*model.AllocationBlock) + + // Convert attributes. + attrs := []v3.AllocationAttribute{} + for _, a := range ab.Attributes { + attrs = append(attrs, v3.AllocationAttribute{ + HandleID: a.HandleID, + ActiveOwnerAttrs: a.ActiveOwnerAttrs, + AlternateOwnerAttrs: a.AlternateOwnerAttrs, + }) + } - return &model.KVPair{ - Key: model.ResourceKey{ - Name: name, - Kind: libapiv3.KindIPAMBlock, - }, - Value: &libapiv3.IPAMBlock{ - TypeMeta: metav1.TypeMeta{ - Kind: libapiv3.KindIPAMBlock, - APIVersion: "crd.projectcalico.org/v1", + apiVersion := "projectcalico.org/v3" + + return &model.KVPair{ + Key: model.ResourceKey{ + Name: name, + Kind: internalapi.KindIPAMBlock, }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - ResourceVersion: kvpv1.Revision, + Value: &v3.IPAMBlock{ + TypeMeta: metav1.TypeMeta{ + Kind: internalapi.KindIPAMBlock, + APIVersion: apiVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + ResourceVersion: kvpv1.Revision, + }, + Spec: v3.IPAMBlockSpec{ + CIDR: cidr, + Allocations: ab.Allocations, + Unallocated: ab.Unallocated, + Affinity: ab.Affinity, + AffinityClaimTime: ab.AffinityClaimTime, + Attributes: attrs, + Deleted: ab.Deleted, + SequenceNumber: ab.SequenceNumber, + SequenceNumberForAllocation: ab.SequenceNumberForAllocation, + }, }, - Spec: libapiv3.IPAMBlockSpec{ - CIDR: cidr, - Allocations: ab.Allocations, - Unallocated: ab.Unallocated, - Affinity: ab.Affinity, - Attributes: attrs, - Deleted: ab.Deleted, - SequenceNumber: ab.SequenceNumber, - SequenceNumberForAllocation: ab.SequenceNumberForAllocation, + Revision: kvpv1.Revision, + } + } else { + name, cidr := parseKey(kvpv1.Key) + + ab := kvpv1.Value.(*model.AllocationBlock) + + // Convert attributes. + attrs := []internalapi.AllocationAttribute{} + for _, a := range ab.Attributes { + attrs = append(attrs, internalapi.AllocationAttribute{ + HandleID: a.HandleID, + ActiveOwnerAttrs: a.ActiveOwnerAttrs, + AlternateOwnerAttrs: a.AlternateOwnerAttrs, + }) + } + + apiVersion := "crd.projectcalico.org/v1" + + return &model.KVPair{ + Key: model.ResourceKey{ + Name: name, + Kind: internalapi.KindIPAMBlock, }, - }, - Revision: kvpv1.Revision, + Value: &internalapi.IPAMBlock{ + TypeMeta: metav1.TypeMeta{ + Kind: internalapi.KindIPAMBlock, + APIVersion: apiVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + ResourceVersion: kvpv1.Revision, + }, + Spec: internalapi.IPAMBlockSpec{ + CIDR: cidr, + Allocations: ab.Allocations, + Unallocated: ab.Unallocated, + Affinity: ab.Affinity, + AffinityClaimTime: ab.AffinityClaimTime, + Attributes: attrs, + Deleted: ab.Deleted, + SequenceNumber: ab.SequenceNumber, + SequenceNumberForAllocation: ab.SequenceNumberForAllocation, + }, + }, + Revision: kvpv1.Revision, + } } } diff --git a/libcalico-go/lib/backend/k8s/resources/ipam_block_test.go b/libcalico-go/lib/backend/k8s/resources/ipam_block_test.go index 4a4d7592f07..bd2c4826cef 100644 --- a/libcalico-go/lib/backend/k8s/resources/ipam_block_test.go +++ b/libcalico-go/lib/backend/k8s/resources/ipam_block_test.go @@ -17,7 +17,7 @@ package resources import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/api/pkg/client/clientset_generated/clientset/scheme" "github.com/sirupsen/logrus" @@ -25,7 +25,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/client-go/rest/fake" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" ) @@ -42,12 +42,12 @@ var _ = Describe("ipamBlockClient tests with fake REST client", func() { }, VersionedAPIPath: "/apis", } - client = NewIPAMBlockClient(nil, fakeREST) + client = NewIPAMBlockClient(fakeREST, BackingAPIGroupV1) }) It("should list all (v3)", func() { l, err := client.List(context.TODO(), model.ResourceListOptions{ - Kind: libapiv3.KindIPAMBlock, + Kind: internalapi.KindIPAMBlock, }, "") // Expect an error since the client is not implemented. diff --git a/libcalico-go/lib/backend/k8s/resources/ipam_config.go b/libcalico-go/lib/backend/k8s/resources/ipam_config.go index e67eec30bdf..2058e31f1e0 100644 --- a/libcalico-go/lib/backend/k8s/resources/ipam_config.go +++ b/libcalico-go/lib/backend/k8s/resources/ipam_config.go @@ -1,4 +1,4 @@ -// Copyright (c) 2019 Tigera, Inc. All rights reserved. +// Copyright (c) 2019-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,249 +15,35 @@ package resources import ( - "context" "reflect" - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - log "github.com/sirupsen/logrus" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "k8s.io/client-go/rest" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" - "github.com/projectcalico/calico/libcalico-go/lib/backend/api" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" ) -const ( - IPAMConfigResourceName = "IPAMConfigs" - IPAMConfigCRDName = "ipamconfigs.crd.projectcalico.org" -) - -func NewIPAMConfigClient(c kubernetes.Interface, r rest.Interface) K8sResourceClient { - return &ipamConfigClient{ - rc: customK8sResourceClient{ - clientSet: c, - restClient: r, - name: IPAMConfigCRDName, - resource: IPAMConfigResourceName, - description: "Calico IPAM configuration", - k8sResourceType: reflect.TypeOf(libapiv3.IPAMConfig{}), - k8sResourceTypeMeta: metav1.TypeMeta{ - Kind: libapiv3.KindIPAMConfig, - APIVersion: apiv3.GroupVersionCurrent, - }, - k8sListType: reflect.TypeOf(libapiv3.IPAMConfigList{}), - resourceKind: libapiv3.KindIPAMConfig, - }, +// ipamConfigResourceClient returns a customResourceClient for IPAMConfig resources based on the +// specified REST client and whether to use v3 CRDs. +func ipamConfigResourceClient(r rest.Interface, group BackingAPIGroup) customResourceClient { + resource := IPAMConfigResourceName + if group == BackingAPIGroupV3 { + resource = IPAMConfigResourceNameV3 } -} - -// ipamConfigClient implements the api.Client interface for IPAMConfig objects. It -// handles the translation between v1 objects understood by the IPAM codebase in lib/ipam, -// and the CRDs which are used to actually store the data in the Kubernetes API. -// It uses a customK8sResourceClient under the covers to perform CRUD operations on -// kubernetes CRDs. -type ipamConfigClient struct { - rc customK8sResourceClient -} - -// toV1 converts the given v3 CRD KVPair into a v1 model representation -// which can be passed to the IPAM code. -func (c ipamConfigClient) toV1(kvpv3 *model.KVPair) (*model.KVPair, error) { - v3obj := kvpv3.Value.(*libapiv3.IPAMConfig) - - return &model.KVPair{ - Key: model.IPAMConfigKey{}, - Value: &model.IPAMConfig{ - StrictAffinity: v3obj.Spec.StrictAffinity, - AutoAllocateBlocks: v3obj.Spec.AutoAllocateBlocks, - MaxBlocksPerHost: v3obj.Spec.MaxBlocksPerHost, - }, - Revision: kvpv3.Revision, - UID: &kvpv3.Value.(*libapiv3.IPAMConfig).UID, - }, nil -} -// For the first point, toV3 takes the given v1 KVPair and converts it into a v3 representation, suitable -// for writing as a CRD to the Kubernetes API. -func (c ipamConfigClient) toV3(kvpv1 *model.KVPair) *model.KVPair { - // Build object meta. - // We only support a singleton resource with name "default". - m := metav1.ObjectMeta{} - m.SetName(model.IPAMConfigGlobalName) - m.SetResourceVersion(kvpv1.Revision) - - v1obj := kvpv1.Value.(*model.IPAMConfig) - return &model.KVPair{ - Key: model.ResourceKey{ - Name: model.IPAMConfigGlobalName, - Kind: libapiv3.KindIPAMConfig, - }, - Value: &libapiv3.IPAMConfig{ - TypeMeta: metav1.TypeMeta{ - Kind: libapiv3.KindIPAMConfig, - APIVersion: "crd.projectcalico.org/v1", - }, - ObjectMeta: m, - Spec: libapiv3.IPAMConfigSpec{ - StrictAffinity: v1obj.StrictAffinity, - AutoAllocateBlocks: v1obj.AutoAllocateBlocks, - MaxBlocksPerHost: v1obj.MaxBlocksPerHost, - }, - }, - Revision: kvpv1.Revision, + rc := customResourceClient{ + restClient: r, + resource: resource, + k8sResourceType: reflect.TypeFor[internalapi.IPAMConfig](), + k8sListType: reflect.TypeFor[internalapi.IPAMConfigList](), + kind: v3.KindIPAMConfiguration, + apiGroup: group, } -} - -// There's two possible kV formats to be passed to backend ipamConfig. -// 1. Libcalico-go IPAM passes a v1 model.IPAMConfig directly. [libcalico-go/lib/ipam/ipam.go] -// 2. Calico-apiserver storage passes a kv with libapiv3.IPAMConfig - -// isV1Key return if the Key is in v1 format. -func isV1Key(key model.Key) bool { - switch key.(type) { - case model.IPAMConfigKey: // used by Calico IPAM [libcalico-go/lib/ipam/ipam.go] - return true - case model.ResourceKey: // used by clientv3 resource API [libcalico-go/lib/clientv3/resources.go] - return false - default: - log.Panic("ipamConfigClient : wrong key interface type") - } - return false -} - -func (c *ipamConfigClient) createV1(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { - nkvp, err := c.rc.Create(ctx, c.toV3(kvp)) - if err != nil { - return nil, err - } - kvp, err = c.toV1(nkvp) - if err != nil { - return nil, err - } - return kvp, nil -} - -func (c *ipamConfigClient) createV3(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { - return c.rc.Create(ctx, kvp) -} - -func (c *ipamConfigClient) Create(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { - log.Debug("Received Create request on IPAMConfig type") - if isV1Key(kvp.Key) { - // From the IPAM code - we need to convert to CRD format. - return c.createV1(ctx, kvp) - } - // From the v3 client - it's already in CRD format. - return c.createV3(ctx, kvp) -} - -func (c *ipamConfigClient) updateV1(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { - nkvp, err := c.rc.Update(ctx, c.toV3(kvp)) - if err != nil { - return nil, err - } - kvp, err = c.toV1(nkvp) - if err != nil { - return nil, err - } - return kvp, nil -} - -func (c *ipamConfigClient) updateV3(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { - return c.rc.Update(ctx, kvp) -} -func (c *ipamConfigClient) Update(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { - log.Debug("Received Update request on IPAMConfig type") - if isV1Key(kvp.Key) { - // From the IPAM code - we need to convert to CRD format. - return c.updateV1(ctx, kvp) + if group == BackingAPIGroupV3 { + // If this is a v3 resource, then we need to use the v3 API types, as they differ. + rc.k8sResourceType = reflect.TypeFor[v3.IPAMConfiguration]() + rc.k8sListType = reflect.TypeFor[v3.IPAMConfigurationList]() } - // From the v3 client - it's already in CRD format. - return c.updateV3(ctx, kvp) -} - -func (c *ipamConfigClient) DeleteKVP(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { - return c.Delete(ctx, kvp.Key, kvp.Revision, kvp.UID) -} - -func (c *ipamConfigClient) deleteV1(ctx context.Context, key model.Key, revision string, uid *types.UID) (*model.KVPair, error) { - k := model.ResourceKey{ - Name: model.IPAMConfigGlobalName, - Kind: libapiv3.KindIPAMConfig, - } - kvp, err := c.rc.Delete(ctx, k, revision, uid) - if err != nil { - return nil, err - } - v1nkvp, err := c.toV1(kvp) - if err != nil { - return nil, err - } - return v1nkvp, nil -} - -func (c *ipamConfigClient) deleteV3(ctx context.Context, key model.Key, revision string, uid *types.UID) (*model.KVPair, error) { - return c.rc.Delete(ctx, key, revision, uid) -} - -func (c *ipamConfigClient) Delete(ctx context.Context, key model.Key, revision string, uid *types.UID) (*model.KVPair, error) { - log.Debug("Received Delete request on IPAMConfig type") - if isV1Key(key) { - // From the IPAM code - we need to convert to CRD format. - return c.deleteV1(ctx, key, revision, uid) - } - // From the v3 client - it's already in CRD format. - return c.deleteV3(ctx, key, revision, uid) -} - -func (c *ipamConfigClient) getV1(ctx context.Context, key model.Key, revision string) (*model.KVPair, error) { - k := model.ResourceKey{ - Name: model.IPAMConfigGlobalName, - Kind: libapiv3.KindIPAMConfig, - } - kvp, err := c.rc.Get(ctx, k, revision) - if err != nil { - return nil, err - } - v1kvp, err := c.toV1(kvp) - if err != nil { - return nil, err - } - return v1kvp, nil -} - -func (c *ipamConfigClient) getV3(ctx context.Context, key model.Key, revision string) (*model.KVPair, error) { - return c.rc.Get(ctx, key, revision) -} - -func (c *ipamConfigClient) Get(ctx context.Context, key model.Key, revision string) (*model.KVPair, error) { - log.Debug("Received Get request on IPAMConfig type") - if isV1Key(key) { - // From the IPAM code - we need to convert to CRD format. - return c.getV1(ctx, key, revision) - } - // From the v3 client - it's already in CRD format. - return c.getV3(ctx, key, revision) -} - -func (c *ipamConfigClient) List(ctx context.Context, list model.ListInterface, revision string) (*model.KVPairList, error) { - // List can only ever come from the v3 client, by passing a ResourceListOptions. - log.Debug("Received List request on IPAMConfig type") - return c.rc.List(ctx, list, revision) -} - -func (c *ipamConfigClient) Watch(ctx context.Context, list model.ListInterface, options api.WatchOptions) (api.WatchInterface, error) { - // List can only ever come from the v3 client, by passing a ResourceListOptions. - log.Debug("Received Watch request on IPAMConfig type") - return c.rc.Watch(ctx, list, options) -} - -// EnsureInitialized is a no-op since the CRD should be -// initialized in advance. -func (c *ipamConfigClient) EnsureInitialized() error { - return nil + return rc } diff --git a/libcalico-go/lib/backend/k8s/resources/ipam_config_v1.go b/libcalico-go/lib/backend/k8s/resources/ipam_config_v1.go new file mode 100644 index 00000000000..fc2b906c206 --- /dev/null +++ b/libcalico-go/lib/backend/k8s/resources/ipam_config_v1.go @@ -0,0 +1,251 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// 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 resources + +import ( + "context" + "fmt" + + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + log "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/rest" + + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" + "github.com/projectcalico/calico/libcalico-go/lib/backend/api" + "github.com/projectcalico/calico/libcalico-go/lib/backend/model" +) + +const ( + IPAMConfigResourceName = "IPAMConfigs" +) + +// NewIPAMConfigClientOld returns a new client for managing IPAMConfig resources, as used by the +// libcalico-go/lib/ipam code. +func NewIPAMConfigClientV1(r rest.Interface, group BackingAPIGroup) K8sResourceClient { + return &v1IPAMConfigClient{ + rc: ipamConfigResourceClient(r, group), + v3: group == BackingAPIGroupV3, + } +} + +type v1IPAMConfigClient struct { + rc customResourceClient + v3 bool +} + +// crdToV1 converts the given CRD KVPair into a model representation which can be passed back to the IPAM code. +func (c v1IPAMConfigClient) crdToV1(kvp *model.KVPair) (*model.KVPair, error) { + switch kvp.Value.(type) { + case *internalapi.IPAMConfig: + v3obj := kvp.Value.(*internalapi.IPAMConfig) + var kubeVirtVMAddressPersistence *string + if v3obj.Spec.KubeVirtVMAddressPersistence != nil { + val := string(*v3obj.Spec.KubeVirtVMAddressPersistence) + kubeVirtVMAddressPersistence = &val + } + return &model.KVPair{ + Key: model.IPAMConfigKey{}, + Value: &model.IPAMConfig{ + StrictAffinity: v3obj.Spec.StrictAffinity, + AutoAllocateBlocks: v3obj.Spec.AutoAllocateBlocks, + MaxBlocksPerHost: v3obj.Spec.MaxBlocksPerHost, + KubeVirtVMAddressPersistence: kubeVirtVMAddressPersistence, + }, + Revision: kvp.Revision, + UID: &kvp.Value.(*internalapi.IPAMConfig).UID, + }, nil + case *v3.IPAMConfiguration: + v3obj := kvp.Value.(*v3.IPAMConfiguration) + var kubeVirtVMAddressPersistence *string + if v3obj.Spec.KubeVirtVMAddressPersistence != nil { + val := string(*v3obj.Spec.KubeVirtVMAddressPersistence) + kubeVirtVMAddressPersistence = &val + } + return &model.KVPair{ + Key: model.IPAMConfigKey{}, + Value: &model.IPAMConfig{ + StrictAffinity: v3obj.Spec.StrictAffinity, + AutoAllocateBlocks: v3obj.Spec.AutoAllocateBlocks, + MaxBlocksPerHost: int(v3obj.Spec.MaxBlocksPerHost), + KubeVirtVMAddressPersistence: kubeVirtVMAddressPersistence, + }, + Revision: kvp.Revision, + UID: &v3obj.UID, + }, nil + } + return nil, fmt.Errorf("invalid type for IPAM configuration KVPair: %T", kvp.Value) +} + +func (c v1IPAMConfigClient) getBackingTypeMeta() metav1.TypeMeta { + if c.v3 { + // If this is a v3 resource, then we need to use the v3 API version. + return metav1.TypeMeta{ + Kind: v3.KindIPAMConfiguration, + APIVersion: "projectcalico.org/v3", + } + } + return metav1.TypeMeta{ + Kind: internalapi.KindIPAMConfig, + APIVersion: "crd.projectcalico.org/v1", + } +} + +// toCRD converts the given model KVPair into a CRD KVPair which can be stored +// in the Kubernetes API. +func (c v1IPAMConfigClient) toCRD(kvpv1 *model.KVPair) *model.KVPair { + // Build object meta. + // We only support a singleton resource with name "default". + m := metav1.ObjectMeta{} + m.SetName(model.IPAMConfigGlobalName) + m.SetResourceVersion(kvpv1.Revision) + + typeMeta := c.getBackingTypeMeta() + + if c.v3 { + v1obj := kvpv1.Value.(*model.IPAMConfig) + var kubeVirtVMAddressPersistence *v3.VMAddressPersistence + if v1obj.KubeVirtVMAddressPersistence != nil { + val := v3.VMAddressPersistence(*v1obj.KubeVirtVMAddressPersistence) + kubeVirtVMAddressPersistence = &val + } + return &model.KVPair{ + Key: model.ResourceKey{ + Name: model.IPAMConfigGlobalName, + Kind: v3.KindIPAMConfiguration, + }, + Value: &v3.IPAMConfiguration{ + TypeMeta: typeMeta, + ObjectMeta: m, + Spec: v3.IPAMConfigurationSpec{ + StrictAffinity: v1obj.StrictAffinity, + AutoAllocateBlocks: v1obj.AutoAllocateBlocks, + MaxBlocksPerHost: int32(v1obj.MaxBlocksPerHost), + KubeVirtVMAddressPersistence: kubeVirtVMAddressPersistence, + }, + }, + Revision: kvpv1.Revision, + } + } else { + v1obj := kvpv1.Value.(*model.IPAMConfig) + var kubeVirtVMAddressPersistence *internalapi.VMAddressPersistence + if v1obj.KubeVirtVMAddressPersistence != nil { + val := internalapi.VMAddressPersistence(*v1obj.KubeVirtVMAddressPersistence) + kubeVirtVMAddressPersistence = &val + } + return &model.KVPair{ + Key: model.ResourceKey{ + Name: model.IPAMConfigGlobalName, + Kind: internalapi.KindIPAMConfig, + }, + Value: &internalapi.IPAMConfig{ + TypeMeta: typeMeta, + ObjectMeta: m, + Spec: internalapi.IPAMConfigSpec{ + StrictAffinity: v1obj.StrictAffinity, + AutoAllocateBlocks: v1obj.AutoAllocateBlocks, + MaxBlocksPerHost: v1obj.MaxBlocksPerHost, + KubeVirtVMAddressPersistence: kubeVirtVMAddressPersistence, + }, + }, + Revision: kvpv1.Revision, + } + } +} + +func (c *v1IPAMConfigClient) Create(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { + log.Debug("Received Create request on IPAMConfig type") + nkvp, err := c.rc.Create(ctx, c.toCRD(kvp)) + if err != nil { + return nil, err + } + kvp, err = c.crdToV1(nkvp) + if err != nil { + return nil, err + } + return kvp, nil +} + +func (c *v1IPAMConfigClient) Update(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { + log.Debug("Received Update request on IPAMConfig type") + nkvp, err := c.rc.Update(ctx, c.toCRD(kvp)) + if err != nil { + return nil, err + } + kvp, err = c.crdToV1(nkvp) + if err != nil { + return nil, err + } + return kvp, nil +} + +func (c *v1IPAMConfigClient) DeleteKVP(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { + return c.Delete(ctx, kvp.Key, kvp.Revision, kvp.UID) +} + +func (c *v1IPAMConfigClient) Delete(ctx context.Context, key model.Key, revision string, uid *types.UID) (*model.KVPair, error) { + log.Debug("Received Delete request on IPAMConfig type") + k := model.ResourceKey{ + Name: model.IPAMConfigGlobalName, + Kind: internalapi.KindIPAMConfig, + } + if c.v3 { + k.Kind = v3.KindIPAMConfiguration + } + kvp, err := c.rc.Delete(ctx, k, revision, uid) + if err != nil { + return nil, err + } + v1nkvp, err := c.crdToV1(kvp) + if err != nil { + return nil, err + } + return v1nkvp, nil +} + +func (c *v1IPAMConfigClient) Get(ctx context.Context, key model.Key, revision string) (*model.KVPair, error) { + log.Debug("Received Get request on IPAMConfig type") + k := model.ResourceKey{ + Name: model.IPAMConfigGlobalName, + Kind: internalapi.KindIPAMConfig, + } + if c.v3 { + k.Kind = v3.KindIPAMConfiguration + } + kvp, err := c.rc.Get(ctx, k, revision) + if err != nil { + return nil, err + } + v1kvp, err := c.crdToV1(kvp) + if err != nil { + return nil, err + } + return v1kvp, nil +} + +func (c *v1IPAMConfigClient) List(ctx context.Context, list model.ListInterface, revision string) (*model.KVPairList, error) { + // List s not supported for IPAMConfig resource from the lib/ipam code. + return nil, fmt.Errorf("List is not supported for IPAMConfig resource") +} + +func (c *v1IPAMConfigClient) Watch(ctx context.Context, list model.ListInterface, options api.WatchOptions) (api.WatchInterface, error) { + // Watch is not supported for IPAMConfig resource from the lib/ipam code. + return nil, fmt.Errorf("Watch is not supported for IPAMConfig resource") +} + +func (c *v1IPAMConfigClient) EnsureInitialized() error { + return nil +} diff --git a/libcalico-go/lib/backend/k8s/resources/ipam_config_v3.go b/libcalico-go/lib/backend/k8s/resources/ipam_config_v3.go new file mode 100644 index 00000000000..b43e15ca735 --- /dev/null +++ b/libcalico-go/lib/backend/k8s/resources/ipam_config_v3.go @@ -0,0 +1,150 @@ +// Copyright (c) 2019-2026 Tigera, Inc. All rights reserved. + +// 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 resources + +import ( + "context" + "fmt" + + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + log "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/rest" + + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" + "github.com/projectcalico/calico/libcalico-go/lib/backend/api" + "github.com/projectcalico/calico/libcalico-go/lib/backend/model" +) + +const ( + IPAMConfigResourceNameV3 = "IPAMConfigurations" +) + +// NewIPAMConfigClientV3 returns a new client for managing IPAMConfiguration resources, as used by the +// libcalico-go/lib/clientv3 code. +func NewIPAMConfigClientV3(r rest.Interface, group BackingAPIGroup) K8sResourceClient { + rc := ipamConfigResourceClient(r, group) + rc.versionconverter = ipamConfigurationVersionConverter{} + + return &v3IPAMConfigurationClient{ + rc: rc, + v3: group == BackingAPIGroupV3, + } +} + +type v3IPAMConfigurationClient struct { + rc customResourceClient + v3 bool +} + +// ipamConfigurationVersionConverter handles conversion between the v3 API and CRD representations of IPAMConfiguration. +type ipamConfigurationVersionConverter struct{} + +// ConvertFromK8s converts the given CRD KVPair into a v3 API representation which can be passed back to the clientv3 code. +func (c ipamConfigurationVersionConverter) ConvertFromK8s(r Resource) (Resource, error) { + switch o := r.(type) { + case *internalapi.IPAMConfig: + // This is a crd.projectcalico.org/v1 CRD, convert it to the v3 API struct expected by clientv3. + var kubeVirtVMAddressPersistence *v3.VMAddressPersistence + if o.Spec.KubeVirtVMAddressPersistence != nil { + val := v3.VMAddressPersistence(*o.Spec.KubeVirtVMAddressPersistence) + kubeVirtVMAddressPersistence = &val + } + return &v3.IPAMConfiguration{ + TypeMeta: metav1.TypeMeta{ + Kind: v3.KindIPAMConfiguration, + APIVersion: "projectcalico.org/v3", + }, + ObjectMeta: o.ObjectMeta, + Spec: v3.IPAMConfigurationSpec{ + StrictAffinity: o.Spec.StrictAffinity, + AutoAllocateBlocks: o.Spec.AutoAllocateBlocks, + MaxBlocksPerHost: int32(o.Spec.MaxBlocksPerHost), + KubeVirtVMAddressPersistence: kubeVirtVMAddressPersistence, + }, + }, nil + case *v3.IPAMConfiguration: + // No conversion necessary - already using v3 CRDs. + return r, nil + } + return nil, fmt.Errorf("invalid type for IPAM configuration KVPair: %T", r) +} + +func (c v3IPAMConfigurationClient) getBackingTypeMeta() metav1.TypeMeta { + if c.v3 { + // If this is a v3 resource, then we need to use the v3 API version. + return metav1.TypeMeta{ + Kind: v3.KindIPAMConfiguration, + APIVersion: "projectcalico.org/v3", + } + } + return metav1.TypeMeta{ + Kind: internalapi.KindIPAMConfig, + APIVersion: "crd.projectcalico.org/v1", + } +} + +func (c *v3IPAMConfigurationClient) Create(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { + log.Debug("Received Create request on IPAMConfiguration type") + + // Ensure the correct type meta is set based on the backing CRD version. + kvp.Value.(*v3.IPAMConfiguration).TypeMeta = c.getBackingTypeMeta() + + return c.rc.Create(ctx, kvp) +} + +func (c *v3IPAMConfigurationClient) Update(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { + // Ensure the correct type meta is set based on the backing CRD version. + kvp.Value.(*v3.IPAMConfiguration).TypeMeta = c.getBackingTypeMeta() + + return c.rc.Update(ctx, kvp) +} + +func (c *v3IPAMConfigurationClient) DeleteKVP(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { + return c.Delete(ctx, kvp.Key, kvp.Revision, kvp.UID) +} + +func (c *v3IPAMConfigurationClient) Delete(ctx context.Context, key model.Key, revision string, uid *types.UID) (*model.KVPair, error) { + log.Debug("Received Delete request on IPAMConfiguration type") + return c.rc.Delete(ctx, key, revision, uid) +} + +func (c *v3IPAMConfigurationClient) Get(ctx context.Context, key model.Key, revision string) (*model.KVPair, error) { + log.Debug("Received Get request on IPAMConfiguration type") + out, err := c.rc.Get(ctx, key, revision) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *v3IPAMConfigurationClient) List(ctx context.Context, list model.ListInterface, revision string) (*model.KVPairList, error) { + // List can only ever come from the v3 client, by passing a ResourceListOptions. + log.Debug("Received List request on IPAMConfiguration type") + return c.rc.List(ctx, list, revision) +} + +func (c *v3IPAMConfigurationClient) Watch(ctx context.Context, list model.ListInterface, options api.WatchOptions) (api.WatchInterface, error) { + // List can only ever come from the v3 client, by passing a ResourceListOptions. + log.Debug("Received Watch request on IPAMConfiguration type") + return c.rc.Watch(ctx, list, options) +} + +// EnsureInitialized is a no-op since the CRD should be +// initialized in advance. +func (c *v3IPAMConfigurationClient) EnsureInitialized() error { + return nil +} diff --git a/libcalico-go/lib/backend/k8s/resources/ipam_handle.go b/libcalico-go/lib/backend/k8s/resources/ipam_handle.go index 4021387b0a9..8c430a0caf9 100644 --- a/libcalico-go/lib/backend/k8s/resources/ipam_handle.go +++ b/libcalico-go/lib/backend/k8s/resources/ipam_handle.go @@ -20,14 +20,13 @@ import ( "reflect" "strings" - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" @@ -35,27 +34,29 @@ import ( const ( IPAMHandleResourceName = "IPAMHandles" - IPAMHandleCRDName = "ipamhandles.crd.projectcalico.org" ) -func NewIPAMHandleClient(c kubernetes.Interface, r rest.Interface) K8sResourceClient { +func NewIPAMHandleClient(r rest.Interface, group BackingAPIGroup) K8sResourceClient { // Create a resource client which manages k8s CRDs. - rc := customK8sResourceClient{ - clientSet: c, + rc := customResourceClient{ restClient: r, - name: IPAMHandleCRDName, resource: IPAMHandleResourceName, - description: "Calico IPAM handles", - k8sResourceType: reflect.TypeOf(libapiv3.IPAMHandle{}), - k8sResourceTypeMeta: metav1.TypeMeta{ - Kind: libapiv3.KindIPAMHandle, - APIVersion: apiv3.GroupVersionCurrent, - }, - k8sListType: reflect.TypeOf(libapiv3.IPAMHandleList{}), - resourceKind: libapiv3.KindIPAMHandle, + k8sResourceType: reflect.TypeFor[internalapi.IPAMHandle](), + k8sListType: reflect.TypeFor[internalapi.IPAMHandleList](), + kind: internalapi.KindIPAMHandle, + apiGroup: group, + } + + if group == BackingAPIGroupV3 { + // If this is a v3 resource, then we need to use the v3 API types, as they differ. + rc.k8sResourceType = reflect.TypeFor[v3.IPAMHandle]() + rc.k8sListType = reflect.TypeFor[v3.IPAMHandleList]() } - return &ipamHandleClient{rc: rc} + return &ipamHandleClient{ + rc: rc, + v3: group == BackingAPIGroupV3, + } } // affinityHandleClient implements the api.Client interface for IPAMHandle objects. It @@ -64,25 +65,40 @@ func NewIPAMHandleClient(c kubernetes.Interface, r rest.Interface) K8sResourceCl // It uses a customK8sResourceClient under the covers to perform CRUD operations on // kubernetes CRDs. type ipamHandleClient struct { - rc customK8sResourceClient + rc customResourceClient + v3 bool } +// toV1 converts a CRD KVPair to a model KVPair, which is used by the IPAM codebase. func (c *ipamHandleClient) toV1(kvpv3 *model.KVPair) *model.KVPair { - v3Handle := kvpv3.Value.(*libapiv3.IPAMHandle) - handleID := v3Handle.Spec.HandleID - block := v3Handle.Spec.Block - del := v3Handle.Spec.Deleted + var handleID string + var block map[string]int + var del bool + var uid types.UID + + if c.v3 { + v3Handle := kvpv3.Value.(*v3.IPAMHandle) + handleID = v3Handle.Spec.HandleID + block = v3Handle.Spec.Block + del = v3Handle.Spec.Deleted + uid = v3Handle.UID + } else { + v3Handle := kvpv3.Value.(*internalapi.IPAMHandle) + handleID = v3Handle.Spec.HandleID + block = v3Handle.Spec.Block + del = v3Handle.Spec.Deleted + uid = v3Handle.UID + } + return &model.KVPair{ - Key: model.IPAMHandleKey{ - HandleID: handleID, - }, + Key: model.IPAMHandleKey{HandleID: handleID}, Value: &model.IPAMHandle{ HandleID: handleID, Block: block, Deleted: del, }, Revision: kvpv3.Revision, - UID: &v3Handle.UID, + UID: &uid, } } @@ -90,6 +106,7 @@ func (c *ipamHandleClient) parseKey(k model.Key) string { return strings.ToLower(k.(model.IPAMHandleKey).HandleID) } +// toV3 converts a model KVPair to a CRD KVPair, which is used for the Kubernetes API. func (c *ipamHandleClient) toV3(kvpv1 *model.KVPair) *model.KVPair { name := c.parseKey(kvpv1.Key) handle := kvpv1.Key.(model.IPAMHandleKey).HandleID @@ -101,27 +118,50 @@ func (c *ipamHandleClient) toV3(kvpv1 *model.KVPair) *model.KVPair { uid = *kvpv1.UID } - return &model.KVPair{ - Key: model.ResourceKey{ - Name: name, - Kind: libapiv3.KindIPAMHandle, + var val any + val = &internalapi.IPAMHandle{ + TypeMeta: metav1.TypeMeta{ + Kind: internalapi.KindIPAMHandle, + APIVersion: "crd.projectcalico.org/v1", }, - Value: &libapiv3.IPAMHandle{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + ResourceVersion: kvpv1.Revision, + UID: uid, + }, + Spec: internalapi.IPAMHandleSpec{ + HandleID: handle, + Block: block, + Deleted: del, + }, + } + + if c.v3 { + // If this is a v3 resource, then we need to use the v3 API version. + val = &v3.IPAMHandle{ TypeMeta: metav1.TypeMeta{ - Kind: libapiv3.KindIPAMHandle, - APIVersion: "crd.projectcalico.org/v1", + Kind: internalapi.KindIPAMHandle, + APIVersion: "projectcalico.org/v3", }, ObjectMeta: metav1.ObjectMeta{ Name: name, ResourceVersion: kvpv1.Revision, UID: uid, }, - Spec: libapiv3.IPAMHandleSpec{ + Spec: v3.IPAMHandleSpec{ HandleID: handle, Block: block, Deleted: del, }, + } + } + + return &model.KVPair{ + Key: model.ResourceKey{ + Name: name, + Kind: internalapi.KindIPAMHandle, }, + Value: val, Revision: kvpv1.Revision, } } @@ -155,7 +195,7 @@ func (c *ipamHandleClient) DeleteKVP(ctx context.Context, kvp *model.KVPair) (*m } // Now actually delete the object. - k := model.ResourceKey{Name: name, Kind: libapiv3.KindIPAMHandle} + k := model.ResourceKey{Name: name, Kind: internalapi.KindIPAMHandle} kvp, err = c.rc.Delete(ctx, k, v1kvp.Revision, kvp.UID) if err != nil { return nil, err @@ -174,7 +214,7 @@ func (c *ipamHandleClient) Delete(ctx context.Context, key model.Key, revision s func (c *ipamHandleClient) Get(ctx context.Context, key model.Key, revision string) (*model.KVPair, error) { name := c.parseKey(key) - k := model.ResourceKey{Name: name, Kind: libapiv3.KindIPAMHandle} + k := model.ResourceKey{Name: name, Kind: internalapi.KindIPAMHandle} kvp, err := c.rc.Get(ctx, k, revision) if err != nil { return nil, err @@ -196,7 +236,7 @@ func (c *ipamHandleClient) Get(ctx context.Context, key model.Key, revision stri } func (c *ipamHandleClient) List(ctx context.Context, list model.ListInterface, revision string) (*model.KVPairList, error) { - l := model.ResourceListOptions{Kind: libapiv3.KindIPAMHandle} + l := model.ResourceListOptions{Kind: internalapi.KindIPAMHandle} v3list, err := c.rc.List(ctx, l, revision) if err != nil { return nil, err diff --git a/libcalico-go/lib/backend/k8s/resources/ipam_handle_test.go b/libcalico-go/lib/backend/k8s/resources/ipam_handle_test.go index 729bf4bf595..441ad5c5be4 100644 --- a/libcalico-go/lib/backend/k8s/resources/ipam_handle_test.go +++ b/libcalico-go/lib/backend/k8s/resources/ipam_handle_test.go @@ -17,7 +17,7 @@ package resources_test import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" diff --git a/libcalico-go/lib/backend/k8s/resources/ippool.go b/libcalico-go/lib/backend/k8s/resources/ippool.go index a073b65f7f4..5674f966308 100644 --- a/libcalico-go/lib/backend/k8s/resources/ippool.go +++ b/libcalico-go/lib/backend/k8s/resources/ippool.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2024 Tigera, Inc. All rights reserved. +// Copyright (c) 2016-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,8 +19,6 @@ import ( "reflect" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "github.com/projectcalico/calico/libcalico-go/lib/backend/encap" @@ -30,24 +28,17 @@ import ( const ( IPPoolResourceName = "IPPools" - IPPoolCRDName = "ippools.crd.projectcalico.org" ) -func NewIPPoolClient(c kubernetes.Interface, r rest.Interface) K8sResourceClient { - return &customK8sResourceClient{ - clientSet: c, - restClient: r, - name: IPPoolCRDName, - resource: IPPoolResourceName, - description: "Calico IP Pools", - k8sResourceType: reflect.TypeOf(apiv3.IPPool{}), - k8sResourceTypeMeta: metav1.TypeMeta{ - Kind: apiv3.KindIPPool, - APIVersion: apiv3.GroupVersionCurrent, - }, - k8sListType: reflect.TypeOf(apiv3.IPPoolList{}), - resourceKind: apiv3.KindIPPool, +func NewIPPoolClient(r rest.Interface, group BackingAPIGroup) K8sResourceClient { + return &customResourceClient{ + restClient: r, + resource: IPPoolResourceName, + k8sResourceType: reflect.TypeFor[apiv3.IPPool](), + k8sListType: reflect.TypeFor[apiv3.IPPoolList](), + kind: apiv3.KindIPPool, versionconverter: IPPoolv1v3Converter{}, + apiGroup: group, } } @@ -60,28 +51,6 @@ func (c IPPoolv1v3Converter) ConvertFromK8s(inRes Resource) (Resource, error) { if !ok { return nil, fmt.Errorf("invalid type conversion") } - - // If IPIP field is not nil, then it means the resource has v1 IPIP data - // and we must convert it to v3 equivalent data. - if ipp.Spec.IPIP != nil { - if !ipp.Spec.IPIP.Enabled { - ipp.Spec.IPIPMode = apiv3.IPIPModeNever - } else if ipp.Spec.IPIP.Mode == encap.CrossSubnet { - ipp.Spec.IPIPMode = apiv3.IPIPModeCrossSubnet - } else { - ipp.Spec.IPIPMode = apiv3.IPIPModeAlways - } - - // Set IPIP to nil since we've already converted v1 IPIP fields to v3. - ipp.Spec.IPIP = nil - } - - // Take a logical OR of the v1 NATOutgoing field with the v3 NATOutgoing. - ipp.Spec.NATOutgoing = ipp.Spec.NATOutgoingV1 || ipp.Spec.NATOutgoing - - // Set v1 NatOutgoing to false since we've already converted it to v3 NatOutgoing. - ipp.Spec.NATOutgoingV1 = false - return ipp, nil } @@ -105,7 +74,7 @@ func IPPoolV3ToV1(kvp *model.KVPair) (*model.KVPair, error) { ipipMode = encap.CrossSubnet default: ipipInterface = "" - ipipMode = encap.Undefined + ipipMode = encap.Never } var vxlanMode encap.Mode @@ -115,7 +84,7 @@ func IPPoolV3ToV1(kvp *model.KVPair) (*model.KVPair, error) { case apiv3.VXLANModeCrossSubnet: vxlanMode = encap.CrossSubnet default: - vxlanMode = encap.Undefined + vxlanMode = encap.Never } if v3res.Spec.AssignmentMode == nil { diff --git a/libcalico-go/lib/backend/k8s/resources/ipreservation.go b/libcalico-go/lib/backend/k8s/resources/ipreservation.go index 6f2b323930a..5c9c0f927c6 100644 --- a/libcalico-go/lib/backend/k8s/resources/ipreservation.go +++ b/libcalico-go/lib/backend/k8s/resources/ipreservation.go @@ -18,29 +18,20 @@ import ( "reflect" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) const ( IPReservationResourceName = "IPReservations" - IPReservationCRDName = "ipreservations.crd.projectcalico.org" ) -func NewIPReservationClient(c kubernetes.Interface, r rest.Interface) K8sResourceClient { - return &customK8sResourceClient{ - clientSet: c, +func NewIPReservationClient(r rest.Interface, group BackingAPIGroup) K8sResourceClient { + return &customResourceClient{ restClient: r, - name: IPReservationCRDName, resource: IPReservationResourceName, - description: "Calico IP Reservations", - k8sResourceType: reflect.TypeOf(apiv3.IPReservation{}), - k8sResourceTypeMeta: metav1.TypeMeta{ - Kind: apiv3.KindIPReservation, - APIVersion: apiv3.GroupVersionCurrent, - }, - k8sListType: reflect.TypeOf(apiv3.IPReservationList{}), - resourceKind: apiv3.KindIPReservation, + k8sResourceType: reflect.TypeFor[apiv3.IPReservation](), + k8sListType: reflect.TypeFor[apiv3.IPReservationList](), + kind: apiv3.KindIPReservation, + apiGroup: group, } } diff --git a/libcalico-go/lib/backend/k8s/resources/k8sservice_test.go b/libcalico-go/lib/backend/k8s/resources/k8sservice_test.go index bd98a5c404d..fb08e6c25b3 100644 --- a/libcalico-go/lib/backend/k8s/resources/k8sservice_test.go +++ b/libcalico-go/lib/backend/k8s/resources/k8sservice_test.go @@ -17,7 +17,7 @@ package resources import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" k8sapi "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/libcalico-go/lib/backend/k8s/resources/kubeadminnetworkpolicy.go b/libcalico-go/lib/backend/k8s/resources/kubeadminnetworkpolicy.go deleted file mode 100644 index 0a185699f21..00000000000 --- a/libcalico-go/lib/backend/k8s/resources/kubeadminnetworkpolicy.go +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) 2024 Tigera, Inc. All rights reserved. - -// 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 resources - -import ( - "context" - "errors" - "fmt" - - log "github.com/sirupsen/logrus" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - adminpolicy "sigs.k8s.io/network-policy-api/apis/v1alpha1" - adminpolicyclient "sigs.k8s.io/network-policy-api/pkg/client/clientset/versioned/typed/apis/v1alpha1" - - "github.com/projectcalico/calico/libcalico-go/lib/backend/api" - "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" -) - -// NewKubernetesAdminNetworkPolicyClient returns a new client for interacting with Kubernetes AdminNetworkPolicy objects. -// Note that this client is only intended for use by the felix syncer in KDD mode, and as such is largely unimplemented -// except for the functions required by the syncer. -func NewKubernetesAdminNetworkPolicyClient( - anpClient *adminpolicyclient.PolicyV1alpha1Client, -) K8sResourceClient { - return &adminNetworkPolicyClient{ - Converter: conversion.NewConverter(), - adminPolicyClient: anpClient, - } -} - -// Implements the api.Client interface for Kubernetes NetworkPolicy. -type adminNetworkPolicyClient struct { - conversion.Converter - adminPolicyClient *adminpolicyclient.PolicyV1alpha1Client -} - -func (c *adminNetworkPolicyClient) Create(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { - log.Debug("Received Create request on AdminNetworkPolicy type") - return nil, cerrors.ErrorOperationNotSupported{ - Identifier: kvp.Key, - Operation: "Create", - } -} - -func (c *adminNetworkPolicyClient) Update(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { - log.Debug("Received Update request on AdminNetworkPolicy type") - return nil, cerrors.ErrorOperationNotSupported{ - Identifier: kvp.Key, - Operation: "Update", - } -} - -func (c *adminNetworkPolicyClient) DeleteKVP(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { - return c.Delete(ctx, kvp.Key, kvp.Revision, kvp.UID) -} - -func (c *adminNetworkPolicyClient) Delete(ctx context.Context, key model.Key, revision string, uid *types.UID) (*model.KVPair, error) { - log.Debug("Received Delete request on AdminNetworkPolicy type") - return nil, cerrors.ErrorOperationNotSupported{ - Identifier: key, - Operation: "Delete", - } -} - -func (c *adminNetworkPolicyClient) Get(ctx context.Context, key model.Key, revision string) (*model.KVPair, error) { - log.Debug("Received Get request on AdminNetworkPolicy type") - return nil, cerrors.ErrorOperationNotSupported{ - Identifier: key, - Operation: "Get", - } -} - -func (c *adminNetworkPolicyClient) List(ctx context.Context, list model.ListInterface, revision string) (*model.KVPairList, error) { - logContext := log.WithField("Resource", "AdminNetworkPolicy") - logContext.Debug("Received List request") - - listFunc := func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { - return c.adminPolicyClient.AdminNetworkPolicies().List(ctx, opts) - } - convertFunc := func(r Resource) ([]*model.KVPair, error) { - anp := r.(*adminpolicy.AdminNetworkPolicy) - kvp, err := c.K8sAdminNetworkPolicyToCalico(anp) - // Silently ignore rule conversion errors. We don't expect any conversion errors - // since the data given to us here is validated by the Kubernetes API. The conversion - // code ignores any rules that it cannot parse, and we will pass the valid ones to Felix. - var e *cerrors.ErrorAdminPolicyConversion - if err != nil && !errors.As(err, &e) { - return nil, err - } - return []*model.KVPair{kvp}, nil - } - return pagedList(ctx, logContext, revision, list, convertFunc, listFunc) -} - -func (c *adminNetworkPolicyClient) Watch(ctx context.Context, list model.ListInterface, options api.WatchOptions) (api.WatchInterface, error) { - _, ok := list.(model.ResourceListOptions) - if !ok { - return nil, fmt.Errorf("ListInterface is not a ResourceListOptions: %s", list) - } - log.Debugf("Watching Kubernetes AdminNetworkPolicy at revision %q", options.Revision) - k8sOpts := watchOptionsToK8sListOptions(options) - k8sRawWatch, err := c.adminPolicyClient.AdminNetworkPolicies().Watch(ctx, k8sOpts) - if err != nil { - return nil, K8sErrorToCalico(err, list) - } - converter := func(r Resource) (*model.KVPair, error) { - anp, ok := r.(*adminpolicy.AdminNetworkPolicy) - if !ok { - return nil, errors.New("Kubernetes AdminNetworkPolicy conversion with incorrect k8s resource type") - } - - return c.K8sAdminNetworkPolicyToCalico(anp) - } - return newK8sWatcherConverter(ctx, "Kubernetes AdminNetworkPolicy", converter, k8sRawWatch), nil -} - -func (c *adminNetworkPolicyClient) EnsureInitialized() error { - return nil -} diff --git a/libcalico-go/lib/backend/k8s/resources/kubebaselineadminnetworkpolicy.go b/libcalico-go/lib/backend/k8s/resources/kubebaselineadminnetworkpolicy.go deleted file mode 100644 index 7f326aeca22..00000000000 --- a/libcalico-go/lib/backend/k8s/resources/kubebaselineadminnetworkpolicy.go +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (c) 2024 Tigera, Inc. All rights reserved. - -// 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 resources - -import ( - "context" - "errors" - "fmt" - - log "github.com/sirupsen/logrus" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - adminpolicy "sigs.k8s.io/network-policy-api/apis/v1alpha1" - adminpolicyclient "sigs.k8s.io/network-policy-api/pkg/client/clientset/versioned/typed/apis/v1alpha1" - - "github.com/projectcalico/calico/libcalico-go/lib/backend/api" - "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" -) - -// NewKubernetesBaselineAdminNetworkPolicyClient returns a new client for interacting with Kubernetes BaselineAdminNetworkPolicy objects. -// Note that this client is only intended for use by the felix syncer in KDD mode, and as such is largely unimplemented -// except for the functions required by the syncer. -func NewKubernetesBaselineAdminNetworkPolicyClient( - anpClient *adminpolicyclient.PolicyV1alpha1Client, -) K8sResourceClient { - return &baselineAdminNetworkPolicyClient{ - Converter: conversion.NewConverter(), - adminPolicyClient: anpClient, - } -} - -// Implements the api.Client interface for Kubernetes NetworkPolicy. -type baselineAdminNetworkPolicyClient struct { - conversion.Converter - adminPolicyClient *adminpolicyclient.PolicyV1alpha1Client -} - -func (c *baselineAdminNetworkPolicyClient) Create(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { - log.Debug("Received Create request on BaselineAdminNetworkPolicy type") - return nil, cerrors.ErrorOperationNotSupported{ - Identifier: kvp.Key, - Operation: "Create", - } -} - -func (c *baselineAdminNetworkPolicyClient) Update(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { - log.Debug("Received Update request on BaselineAdminNetworkPolicy type") - return nil, cerrors.ErrorOperationNotSupported{ - Identifier: kvp.Key, - Operation: "Update", - } -} - -func (c *baselineAdminNetworkPolicyClient) DeleteKVP(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { - return c.Delete(ctx, kvp.Key, kvp.Revision, kvp.UID) -} - -func (c *baselineAdminNetworkPolicyClient) Delete(ctx context.Context, key model.Key, revision string, uid *types.UID) (*model.KVPair, error) { - log.Debug("Received Delete request on BaselineAdminNetworkPolicy type") - return nil, cerrors.ErrorOperationNotSupported{ - Identifier: key, - Operation: "Delete", - } -} - -func (c *baselineAdminNetworkPolicyClient) Get(ctx context.Context, key model.Key, revision string) (*model.KVPair, error) { - log.Debug("Received Get request on BaselineAdminNetworkPolicy type") - return nil, cerrors.ErrorOperationNotSupported{ - Identifier: key, - Operation: "Get", - } -} - -func (c *baselineAdminNetworkPolicyClient) List(ctx context.Context, list model.ListInterface, revision string) (*model.KVPairList, error) { - logContext := log.WithField("Resource", "BaselineAdminNetworkPolicy") - logContext.Debug("Received List request") - - listFunc := func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { - return c.adminPolicyClient.BaselineAdminNetworkPolicies().List(ctx, opts) - } - convertFunc := func(r Resource) ([]*model.KVPair, error) { - anp := r.(*adminpolicy.BaselineAdminNetworkPolicy) - kvp, err := c.K8sBaselineAdminNetworkPolicyToCalico(anp) - // Silently ignore rule conversion errors. We don't expect any conversion errors - // since the data given to us here is validated by the Kubernetes API. The conversion - // code ignores any rules that it cannot parse, and we will pass the valid ones to Felix. - var e *cerrors.ErrorAdminPolicyConversion - if err != nil && !errors.As(err, &e) { - return nil, err - } - return []*model.KVPair{kvp}, nil - } - return pagedList(ctx, logContext, revision, list, convertFunc, listFunc) -} - -func (c *baselineAdminNetworkPolicyClient) Watch(ctx context.Context, list model.ListInterface, options api.WatchOptions) (api.WatchInterface, error) { - _, ok := list.(model.ResourceListOptions) - if !ok { - return nil, fmt.Errorf("ListInterface is not a ResourceListOptions: %s", list) - } - - log.Debugf("Watching Kubernetes BaselineAdminNetworkPolicy at revision %q", options.Revision) - k8sOpts := watchOptionsToK8sListOptions(options) - k8sRawWatch, err := c.adminPolicyClient.BaselineAdminNetworkPolicies().Watch(ctx, k8sOpts) - if err != nil { - return nil, K8sErrorToCalico(err, list) - } - converter := func(r Resource) (*model.KVPair, error) { - anp, ok := r.(*adminpolicy.BaselineAdminNetworkPolicy) - if !ok { - return nil, errors.New("Kubernetes BaselineAdminNetworkPolicy conversion with incorrect k8s resource type") - } - - return c.K8sBaselineAdminNetworkPolicyToCalico(anp) - } - return newK8sWatcherConverter(ctx, "Kubernetes BaselineAdminNetworkPolicy", converter, k8sRawWatch), nil -} - -func (c *baselineAdminNetworkPolicyClient) EnsureInitialized() error { - return nil -} diff --git a/libcalico-go/lib/backend/k8s/resources/kubeclusternetworkpolicy.go b/libcalico-go/lib/backend/k8s/resources/kubeclusternetworkpolicy.go new file mode 100644 index 00000000000..25f6717ee6d --- /dev/null +++ b/libcalico-go/lib/backend/k8s/resources/kubeclusternetworkpolicy.go @@ -0,0 +1,135 @@ +// Copyright (c) 2025 Tigera, Inc. All rights reserved. + +// 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 resources + +import ( + "context" + "errors" + "fmt" + + log "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + clusternetpol "sigs.k8s.io/network-policy-api/apis/v1alpha2" + client "sigs.k8s.io/network-policy-api/pkg/client/clientset/versioned/typed/apis/v1alpha2" + + "github.com/projectcalico/calico/libcalico-go/lib/backend/api" + "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" + "github.com/projectcalico/calico/libcalico-go/lib/backend/model" + cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" +) + +// NewKubernetesClusterNetworkPolicyClient returns a new client for interacting with k8s ClusterNetworkPolicy objects. +// Note that this client is only intended for use by the felix syncer in KDD mode, +// and as such is largely unimplemented except for the functions required by the syncer. +func NewKubernetesClusterNetworkPolicyClient( + client *client.PolicyV1alpha2Client, +) K8sResourceClient { + return &clusterNetworkPolicyClient{ + Converter: conversion.NewConverter(), + client: client, + } +} + +// Implements the api.Client interface for Kubernetes ClusterNetworkPolicy. +type clusterNetworkPolicyClient struct { + conversion.Converter + client *client.PolicyV1alpha2Client +} + +func (c *clusterNetworkPolicyClient) Create(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { + log.Debug("Received Create request on ClusterNetworkPolicy type") + return nil, cerrors.ErrorOperationNotSupported{ + Identifier: kvp.Key, + Operation: "Create", + } +} + +func (c *clusterNetworkPolicyClient) Update(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { + log.Debug("Received Update request on ClusterNetworkPolicy type") + return nil, cerrors.ErrorOperationNotSupported{ + Identifier: kvp.Key, + Operation: "Update", + } +} + +func (c *clusterNetworkPolicyClient) DeleteKVP(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { + return c.Delete(ctx, kvp.Key, kvp.Revision, kvp.UID) +} + +func (c *clusterNetworkPolicyClient) Delete(ctx context.Context, key model.Key, revision string, uid *types.UID) (*model.KVPair, error) { + log.Debug("Received Delete request on ClusterNetworkPolicy type") + return nil, cerrors.ErrorOperationNotSupported{ + Identifier: key, + Operation: "Delete", + } +} + +func (c *clusterNetworkPolicyClient) Get(ctx context.Context, key model.Key, revision string) (*model.KVPair, error) { + log.Debug("Received Get request on ClusterNetworkPolicy type") + return nil, cerrors.ErrorOperationNotSupported{ + Identifier: key, + Operation: "Get", + } +} + +func (c *clusterNetworkPolicyClient) List(ctx context.Context, list model.ListInterface, revision string) (*model.KVPairList, error) { + logContext := log.WithField("Resource", "ClusterNetworkPolicy") + logContext.Debug("Received List request") + + listFunc := func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { + return c.client.ClusterNetworkPolicies().List(ctx, opts) + } + convertFunc := func(r Resource) ([]*model.KVPair, error) { + kcnp := r.(*clusternetpol.ClusterNetworkPolicy) + kvp, err := c.K8sClusterNetworkPolicyToCalico(kcnp) + // Silently ignore rule conversion errors. We don't expect any conversion errors + // since the data given to us here is validated by the Kubernetes API. The conversion + // code ignores any rules that it cannot parse, and we will pass the valid ones to Felix. + var e *cerrors.ErrorClusterNetworkPolicyConversion + if err != nil && !errors.As(err, &e) { + return nil, err + } + return []*model.KVPair{kvp}, nil + } + return pagedList(ctx, logContext, revision, list, convertFunc, listFunc) +} + +func (c *clusterNetworkPolicyClient) Watch(ctx context.Context, list model.ListInterface, options api.WatchOptions) (api.WatchInterface, error) { + _, ok := list.(model.ResourceListOptions) + if !ok { + return nil, fmt.Errorf("ListInterface is not a ResourceListOptions: %s", list) + } + log.Debugf("Watching Kubernetes ClusterNetworkPolicy at revision %q", options.Revision) + k8sOpts := watchOptionsToK8sListOptions(options) + k8sRawWatch, err := c.client.ClusterNetworkPolicies().Watch(ctx, k8sOpts) + if err != nil { + return nil, K8sErrorToCalico(err, list) + } + converter := func(r Resource) (*model.KVPair, error) { + kcnp, ok := r.(*clusternetpol.ClusterNetworkPolicy) + if !ok { + return nil, errors.New("Kubernetes ClusterNetworkPolicy conversion with incorrect k8s resource type") + } + + return c.K8sClusterNetworkPolicyToCalico(kcnp) + } + return newK8sWatcherConverter(ctx, "Kubernetes ClusterNetworkPolicy", converter, k8sRawWatch), nil +} + +func (c *clusterNetworkPolicyClient) EnsureInitialized() error { + return nil +} diff --git a/libcalico-go/lib/backend/k8s/resources/kubecontrollersconfig.go b/libcalico-go/lib/backend/k8s/resources/kubecontrollersconfig.go index 9244997aa61..5491b99933f 100644 --- a/libcalico-go/lib/backend/k8s/resources/kubecontrollersconfig.go +++ b/libcalico-go/lib/backend/k8s/resources/kubecontrollersconfig.go @@ -20,31 +20,22 @@ import ( apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) const ( KubeControllersConfigResourceName = "KubeControllersConfigurations" - KubeControllersConfigCRDName = "kubecontrollersconfigurations.crd.projectcalico.org" ) -func NewKubeControllersConfigClient(c kubernetes.Interface, r rest.Interface) K8sResourceClient { - return &customK8sResourceClient{ - clientSet: c, +func NewKubeControllersConfigClient(r rest.Interface, group BackingAPIGroup) K8sResourceClient { + return &customResourceClient{ restClient: r, - name: KubeControllersConfigCRDName, resource: KubeControllersConfigResourceName, - description: "Calico Kubernetes Controllers Configuration", - k8sResourceType: reflect.TypeOf(apiv3.KubeControllersConfiguration{}), - k8sResourceTypeMeta: metav1.TypeMeta{ - Kind: apiv3.KindKubeControllersConfiguration, - APIVersion: apiv3.GroupVersionCurrent, - }, - k8sListType: reflect.TypeOf(apiv3.KubeControllersConfigurationList{}), - resourceKind: apiv3.KindKubeControllersConfiguration, - validator: kubeControllersConfigValidator{}, + k8sResourceType: reflect.TypeFor[apiv3.KubeControllersConfiguration](), + k8sListType: reflect.TypeFor[apiv3.KubeControllersConfigurationList](), + kind: apiv3.KindKubeControllersConfiguration, + validator: kubeControllersConfigValidator{}, + apiGroup: group, } } diff --git a/libcalico-go/lib/backend/k8s/resources/kubecontrollersconfig_test.go b/libcalico-go/lib/backend/k8s/resources/kubecontrollersconfig_test.go index 561b20b05e5..b0932196e62 100644 --- a/libcalico-go/lib/backend/k8s/resources/kubecontrollersconfig_test.go +++ b/libcalico-go/lib/backend/k8s/resources/kubecontrollersconfig_test.go @@ -15,13 +15,13 @@ package resources import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" ) var _ = Describe("k8s sync label validation tests", func() { - client := NewKubeControllersConfigClient(nil, nil).(*customK8sResourceClient) + client := NewKubeControllersConfigClient(nil, BackingAPIGroupV1).(*customResourceClient) It("should accept enabled sync labels", func() { res := &apiv3.KubeControllersConfiguration{ Spec: apiv3.KubeControllersConfigurationSpec{ diff --git a/libcalico-go/lib/backend/k8s/resources/livemigration.go b/libcalico-go/lib/backend/k8s/resources/livemigration.go new file mode 100644 index 00000000000..94b93306b02 --- /dev/null +++ b/libcalico-go/lib/backend/k8s/resources/livemigration.go @@ -0,0 +1,208 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// 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 resources + +import ( + "context" + "fmt" + + log "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + kwatch "k8s.io/apimachinery/pkg/watch" + kubevirtv1 "kubevirt.io/api/core/v1" + + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" + "github.com/projectcalico/calico/libcalico-go/lib/backend/api" + "github.com/projectcalico/calico/libcalico-go/lib/backend/model" + cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" +) + +// VMIMClient provides read access to VirtualMachineInstanceMigration resources +// in a specific namespace. This interface decouples the resources package from +// the kubevirt.io/client-go dependency (whose log package registers a -v flag +// that conflicts with klog in binaries that transitively import this package). +type VMIMClient interface { + Get(ctx context.Context, name string, opts metav1.GetOptions) (*kubevirtv1.VirtualMachineInstanceMigration, error) + List(ctx context.Context, opts metav1.ListOptions) (*kubevirtv1.VirtualMachineInstanceMigrationList, error) + Watch(ctx context.Context, opts metav1.ListOptions) (kwatch.Interface, error) +} + +func NewLiveMigrationClient(vmimClient func(namespace string) VMIMClient) K8sResourceClient { + return &LiveMigrationClient{vmimClient: vmimClient} +} + +// LiveMigrationClient implements the K8sResourceClient interface for LiveMigration +// resources. LiveMigration is backed by KubeVirt VirtualMachineInstanceMigration +// resources in the Kubernetes datastore. +type LiveMigrationClient struct { + vmimClient func(namespace string) VMIMClient +} + +func (c *LiveMigrationClient) Create(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { + return nil, cerrors.ErrorOperationNotSupported{ + Identifier: kvp.Key, + Operation: "Create", + Reason: "LiveMigration is read-only in the Kubernetes backend", + } +} + +func (c *LiveMigrationClient) Update(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { + return nil, cerrors.ErrorOperationNotSupported{ + Identifier: kvp.Key, + Operation: "Update", + Reason: "LiveMigration is read-only in the Kubernetes backend", + } +} + +func (c *LiveMigrationClient) Delete(ctx context.Context, key model.Key, revision string, uid *types.UID) (*model.KVPair, error) { + return nil, cerrors.ErrorOperationNotSupported{ + Identifier: key, + Operation: "Delete", + Reason: "LiveMigration is read-only in the Kubernetes backend", + } +} + +func (c *LiveMigrationClient) DeleteKVP(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { + return nil, cerrors.ErrorOperationNotSupported{ + Identifier: kvp.Key, + Operation: "DeleteKVP", + Reason: "LiveMigration is read-only in the Kubernetes backend", + } +} + +func (c *LiveMigrationClient) Get(ctx context.Context, key model.Key, revision string) (*model.KVPair, error) { + k := key.(model.ResourceKey) + vmim, err := c.vmimClient(k.Namespace).Get(ctx, k.Name, metav1.GetOptions{ResourceVersion: revision}) + if err != nil { + return nil, K8sErrorToCalico(err, key) + } + if !vmimShouldEmitLiveMigration(vmim) { + return nil, cerrors.ErrorResourceDoesNotExist{Identifier: key} + } + return convertVMIMToLiveMigration(vmim), nil +} + +func (c *LiveMigrationClient) List(ctx context.Context, list model.ListInterface, revision string) (*model.KVPairList, error) { + logContext := log.WithField("Resource", "LiveMigration") + logContext.Debug("Received List request") + l := list.(model.ResourceListOptions) + + opts := metav1.ListOptions{ResourceVersion: revision} + if revision != "" { + opts.ResourceVersionMatch = metav1.ResourceVersionMatchNotOlderThan + } + + result, err := c.vmimClient(l.Namespace).List(ctx, opts) + if err != nil { + return nil, K8sErrorToCalico(err, list) + } + + kvps := []*model.KVPair{} + for i := range result.Items { + if !vmimShouldEmitLiveMigration(&result.Items[i]) { + continue + } + kvps = append(kvps, convertVMIMToLiveMigration(&result.Items[i])) + } + + return &model.KVPairList{ + KVPairs: kvps, + Revision: result.ResourceVersion, + }, nil +} + +func (c *LiveMigrationClient) Watch(ctx context.Context, list model.ListInterface, options api.WatchOptions) (api.WatchInterface, error) { + rlo := list.(model.ResourceListOptions) + k8sOpts := watchOptionsToK8sListOptions(options) + k8sWatch, err := c.vmimClient(rlo.Namespace).Watch(ctx, k8sOpts) + if err != nil { + return nil, K8sErrorToCalico(err, list) + } + return newK8sWatcherConverter(ctx, "VirtualMachineInstanceMigration", convertVMIMResourceToLiveMigration, k8sWatch), nil +} + +func (c *LiveMigrationClient) EnsureInitialized() error { + return nil +} + +// vmimShouldEmitLiveMigration returns true if the VMIM is in a phase that warrants emitting a +// LiveMigration resource and has the required fields set. In more detail: only if the migration is +// actively preparing, running, or failing, and we have the VM Name, Source Pod, and Object UID +// established. +func vmimShouldEmitLiveMigration(vmim *kubevirtv1.VirtualMachineInstanceMigration) bool { + switch vmim.Status.Phase { + case kubevirtv1.MigrationTargetReady, kubevirtv1.MigrationRunning, kubevirtv1.MigrationFailed: + default: + return false + } + if vmim.Spec.VMIName == "" { + return false + } + if vmim.Status.MigrationState == nil || vmim.Status.MigrationState.SourcePod == "" { + return false + } + if vmim.UID == "" { + return false + } + return true +} + +// convertVMIMToLiveMigration converts a KubeVirt VirtualMachineInstanceMigration +// to a Calico LiveMigration KVPair. +func convertVMIMToLiveMigration(vmim *kubevirtv1.VirtualMachineInstanceMigration) *model.KVPair { + var lm *internalapi.LiveMigration + if vmimShouldEmitLiveMigration(vmim) { + lm = internalapi.NewLiveMigration() + lm.Name = vmim.Name + lm.Namespace = vmim.Namespace + lm.ResourceVersion = vmim.ResourceVersion + lm.CreationTimestamp = vmim.CreationTimestamp + lm.UID = vmim.UID + lm.Labels = vmim.Labels + lm.Annotations = vmim.Annotations + selector := fmt.Sprintf( + "%s == '%s' && %s == '%s'", + kubevirtv1.MigrationSelectorLabel, + vmim.Spec.VMIName, + kubevirtv1.MigrationJobLabel, + string(vmim.UID), + ) + lm.Spec = internalapi.LiveMigrationSpec{ + Source: &types.NamespacedName{ + Name: vmim.Status.MigrationState.SourcePod, + Namespace: vmim.Namespace, + }, + Destination: &internalapi.WorkloadEndpointIdentifier{ + Selector: &selector, + }, + } + } + return &model.KVPair{ + Key: model.ResourceKey{ + Kind: internalapi.KindLiveMigration, + Namespace: vmim.Namespace, + Name: vmim.Name, + }, + Value: lm, + Revision: vmim.ResourceVersion, + } +} + +// convertVMIMResourceToLiveMigration is a ConvertK8sResourceToKVPair adapter +// for the watch converter. +func convertVMIMResourceToLiveMigration(r Resource) (*model.KVPair, error) { + return convertVMIMToLiveMigration(r.(*kubevirtv1.VirtualMachineInstanceMigration)), nil +} diff --git a/libcalico-go/lib/backend/k8s/resources/livemigration_test.go b/libcalico-go/lib/backend/k8s/resources/livemigration_test.go new file mode 100644 index 00000000000..cfe851a94eb --- /dev/null +++ b/libcalico-go/lib/backend/k8s/resources/livemigration_test.go @@ -0,0 +1,539 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// 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 resources_test + +import ( + "context" + "fmt" + "sync" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + kubevirtv1 "kubevirt.io/api/core/v1" + kubevirtfake "kubevirt.io/client-go/kubevirt/fake" + + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" + "github.com/projectcalico/calico/libcalico-go/lib/backend/api" + "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/resources" + "github.com/projectcalico/calico/libcalico-go/lib/backend/model" + cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" +) + +func newVMIM(namespace, name, resourceVersion string, phase kubevirtv1.VirtualMachineInstanceMigrationPhase, vmiName, sourcePod, uid string) *kubevirtv1.VirtualMachineInstanceMigration { + vmim := &kubevirtv1.VirtualMachineInstanceMigration{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + ResourceVersion: resourceVersion, + UID: types.UID(uid), + }, + Spec: kubevirtv1.VirtualMachineInstanceMigrationSpec{ + VMIName: vmiName, + }, + Status: kubevirtv1.VirtualMachineInstanceMigrationStatus{ + Phase: phase, + }, + } + if sourcePod != "" { + vmim.Status.MigrationState = &kubevirtv1.VirtualMachineInstanceMigrationState{ + SourcePod: sourcePod, + } + } + return vmim +} + +func newLiveMigrationClient(kvFake *kubevirtfake.Clientset) resources.K8sResourceClient { + return resources.NewLiveMigrationClient(func(ns string) resources.VMIMClient { + return kvFake.KubevirtV1().VirtualMachineInstanceMigrations(ns) + }) +} + +var _ = Describe("LiveMigrationClient", func() { + ctx := context.Background() + + Describe("Get", func() { + It("converts a matching VMIM to a LiveMigration with spec populated", func() { + vmim := newVMIM("test-ns", "vmim-1", "100", kubevirtv1.MigrationRunning, "my-vmi", "source-pod-abc", "uid-123") + kvFake := kubevirtfake.NewSimpleClientset(vmim) + + client := newLiveMigrationClient(kvFake) + kvp, err := client.Get(ctx, model.ResourceKey{ + Kind: internalapi.KindLiveMigration, + Namespace: "test-ns", + Name: "vmim-1", + }, "") + + Expect(err).NotTo(HaveOccurred()) + Expect(kvp).NotTo(BeNil()) + + lm := kvp.Value.(*internalapi.LiveMigration) + Expect(lm.Name).To(Equal("vmim-1")) + Expect(lm.Namespace).To(Equal("test-ns")) + Expect(lm.TypeMeta).To(Equal(metav1.TypeMeta{ + Kind: internalapi.KindLiveMigration, + APIVersion: apiv3.GroupVersionCurrent, + })) + Expect(*lm.Spec.Destination.Selector).To(Equal( + "kubevirt.io/vmi-name == 'my-vmi' && kubevirt.io/migrationJobUID == 'uid-123'", + )) + Expect(*lm.Spec.Source).To(Equal(types.NamespacedName{ + Name: "source-pod-abc", + Namespace: "test-ns", + })) + Expect(kvp.Key).To(Equal(model.ResourceKey{ + Kind: internalapi.KindLiveMigration, + Namespace: "test-ns", + Name: "vmim-1", + })) + Expect(kvp.Revision).To(Equal("100")) + }) + + It("returns an error when the VMIM does not exist", func() { + kvFake := kubevirtfake.NewSimpleClientset() + + client := newLiveMigrationClient(kvFake) + _, err := client.Get(ctx, model.ResourceKey{ + Kind: internalapi.KindLiveMigration, + Namespace: "test-ns", + Name: "nonexistent", + }, "") + + Expect(err).To(HaveOccurred()) + }) + + It("returns not-found when VMIM is in a non-matching phase", func() { + vmim := newVMIM("test-ns", "vmim-done", "100", kubevirtv1.MigrationSucceeded, "my-vmi", "source-pod-abc", "uid-123") + kvFake := kubevirtfake.NewSimpleClientset(vmim) + + client := newLiveMigrationClient(kvFake) + _, err := client.Get(ctx, model.ResourceKey{ + Kind: internalapi.KindLiveMigration, + Namespace: "test-ns", + Name: "vmim-done", + }, "") + + Expect(err).To(HaveOccurred()) + Expect(err).To(BeAssignableToTypeOf(cerrors.ErrorResourceDoesNotExist{})) + }) + + It("returns not-found when VMIM is in matching phase but missing sourcePod", func() { + vmim := newVMIM("test-ns", "vmim-no-source", "100", kubevirtv1.MigrationRunning, "my-vmi", "", "uid-123") + kvFake := kubevirtfake.NewSimpleClientset(vmim) + + client := newLiveMigrationClient(kvFake) + _, err := client.Get(ctx, model.ResourceKey{ + Kind: internalapi.KindLiveMigration, + Namespace: "test-ns", + Name: "vmim-no-source", + }, "") + + Expect(err).To(HaveOccurred()) + Expect(err).To(BeAssignableToTypeOf(cerrors.ErrorResourceDoesNotExist{})) + }) + }) + + Describe("List", func() { + It("lists matching VMIMs and converts them to LiveMigrations with spec", func() { + vmim1 := newVMIM("test-ns", "vmim-1", "100", kubevirtv1.MigrationRunning, "vmi-a", "src-pod-1", "uid-1") + vmim2 := newVMIM("test-ns", "vmim-2", "101", kubevirtv1.MigrationTargetReady, "vmi-b", "src-pod-2", "uid-2") + kvFake := kubevirtfake.NewSimpleClientset(vmim1, vmim2) + + client := newLiveMigrationClient(kvFake) + kvps, err := client.List(ctx, model.ResourceListOptions{ + Namespace: "test-ns", + Kind: internalapi.KindLiveMigration, + }, "") + + Expect(err).NotTo(HaveOccurred()) + Expect(kvps.KVPairs).To(HaveLen(2)) + + lm1 := kvps.KVPairs[0].Value.(*internalapi.LiveMigration) + Expect(lm1.Name).To(Equal("vmim-1")) + Expect(lm1.Namespace).To(Equal("test-ns")) + Expect(lm1.Spec.Source.Name).To(Equal("src-pod-1")) + + lm2 := kvps.KVPairs[1].Value.(*internalapi.LiveMigration) + Expect(lm2.Name).To(Equal("vmim-2")) + Expect(lm2.Namespace).To(Equal("test-ns")) + Expect(lm2.Spec.Source.Name).To(Equal("src-pod-2")) + }) + + It("lists VMIMs across all namespaces", func() { + vmim1 := newVMIM("ns-a", "vmim-1", "100", kubevirtv1.MigrationRunning, "vmi-a", "src-pod-1", "uid-1") + vmim2 := newVMIM("ns-b", "vmim-2", "101", kubevirtv1.MigrationFailed, "vmi-b", "src-pod-2", "uid-2") + kvFake := kubevirtfake.NewSimpleClientset(vmim1, vmim2) + + client := newLiveMigrationClient(kvFake) + kvps, err := client.List(ctx, model.ResourceListOptions{ + Kind: internalapi.KindLiveMigration, + }, "") + + Expect(err).NotTo(HaveOccurred()) + Expect(kvps.KVPairs).To(HaveLen(2)) + }) + + It("returns empty list when no VMIMs exist", func() { + kvFake := kubevirtfake.NewSimpleClientset() + + client := newLiveMigrationClient(kvFake) + kvps, err := client.List(ctx, model.ResourceListOptions{ + Namespace: "test-ns", + Kind: internalapi.KindLiveMigration, + }, "") + + Expect(err).NotTo(HaveOccurred()) + Expect(kvps.KVPairs).To(HaveLen(0)) + }) + + It("filters out non-matching VMIMs", func() { + matching := newVMIM("test-ns", "vmim-running", "100", kubevirtv1.MigrationRunning, "vmi-a", "src-pod-1", "uid-1") + nonMatching := newVMIM("test-ns", "vmim-succeeded", "101", kubevirtv1.MigrationSucceeded, "vmi-b", "src-pod-2", "uid-2") + noSourcePod := newVMIM("test-ns", "vmim-no-src", "102", kubevirtv1.MigrationRunning, "vmi-c", "", "uid-3") + kvFake := kubevirtfake.NewSimpleClientset(matching, nonMatching, noSourcePod) + + client := newLiveMigrationClient(kvFake) + kvps, err := client.List(ctx, model.ResourceListOptions{ + Namespace: "test-ns", + Kind: internalapi.KindLiveMigration, + }, "") + + Expect(err).NotTo(HaveOccurred()) + Expect(kvps.KVPairs).To(HaveLen(1)) + lm := kvps.KVPairs[0].Value.(*internalapi.LiveMigration) + Expect(lm.Name).To(Equal("vmim-running")) + }) + }) + + Describe("Watch", func() { + It("receives watch events for matching VMIM resources as LiveMigrations", func() { + kvFake := kubevirtfake.NewSimpleClientset() + + client := newLiveMigrationClient(kvFake) + w, err := client.Watch(ctx, model.ResourceListOptions{ + Namespace: "test-ns", + Kind: internalapi.KindLiveMigration, + }, api.WatchOptions{}) + Expect(err).NotTo(HaveOccurred()) + + timer := time.NewTimer(2 * time.Second) + defer timer.Stop() + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + defer GinkgoRecover() + select { + case event := <-w.ResultChan(): + Expect(event.Error).NotTo(HaveOccurred()) + lm := event.New.Value.(*internalapi.LiveMigration) + Expect(lm.Name).To(Equal("vmim-watch-1")) + Expect(lm.Namespace).To(Equal("test-ns")) + Expect(lm.Spec.Source.Name).To(Equal("src-pod-w")) + case <-timer.C: + Fail(fmt.Sprintf("expected a watch event before timer expired")) + } + }() + + vmim := newVMIM("test-ns", "vmim-watch-1", "200", kubevirtv1.MigrationRunning, "vmi-w", "src-pod-w", "uid-w") + _, err = kvFake.KubevirtV1().VirtualMachineInstanceMigrations("test-ns").Create(ctx, vmim, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + wg.Wait() + w.Stop() + }) + + It("emits Deleted event when VMIM transitions to non-matching phase", func() { + kvFake := kubevirtfake.NewSimpleClientset() + + client := newLiveMigrationClient(kvFake) + w, err := client.Watch(ctx, model.ResourceListOptions{ + Namespace: "test-ns", + Kind: internalapi.KindLiveMigration, + }, api.WatchOptions{}) + Expect(err).NotTo(HaveOccurred()) + + // Create a matching VMIM. + vmim := newVMIM("test-ns", "vmim-trans", "300", kubevirtv1.MigrationRunning, "vmi-t", "src-pod-t", "uid-t") + _, err = kvFake.KubevirtV1().VirtualMachineInstanceMigrations("test-ns").Create(ctx, vmim, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + timer := time.NewTimer(2 * time.Second) + defer timer.Stop() + + // First event should be Added. + select { + case event := <-w.ResultChan(): + Expect(event.Error).NotTo(HaveOccurred()) + Expect(event.Type).To(Equal(api.WatchAdded)) + lm := event.New.Value.(*internalapi.LiveMigration) + Expect(lm.Name).To(Equal("vmim-trans")) + case <-timer.C: + Fail("expected Added event before timer expired") + } + + // Update to non-matching phase (Succeeded). + vmimUpdated := newVMIM("test-ns", "vmim-trans", "301", kubevirtv1.MigrationSucceeded, "vmi-t", "src-pod-t", "uid-t") + _, err = kvFake.KubevirtV1().VirtualMachineInstanceMigrations("test-ns").Update(ctx, vmimUpdated, metav1.UpdateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + // Second event should be Modified with nil value. + select { + case event := <-w.ResultChan(): + Expect(event.Error).NotTo(HaveOccurred()) + Expect(event.Type).To(Equal(api.WatchModified)) + Expect(event.New.Value).To(BeNil()) + case <-timer.C: + Fail("expected Deleted event before timer expired") + } + + w.Stop() + }) + + It("does not emit events for non-matching VMIMs", func() { + kvFake := kubevirtfake.NewSimpleClientset() + + client := newLiveMigrationClient(kvFake) + w, err := client.Watch(ctx, model.ResourceListOptions{ + Namespace: "test-ns", + Kind: internalapi.KindLiveMigration, + }, api.WatchOptions{}) + Expect(err).NotTo(HaveOccurred()) + + // Create a non-matching VMIM (Scheduling phase). + vmim := newVMIM("test-ns", "vmim-sched", "400", kubevirtv1.MigrationScheduling, "vmi-s", "src-pod-s", "uid-s") + _, err = kvFake.KubevirtV1().VirtualMachineInstanceMigrations("test-ns").Create(ctx, vmim, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + // Expect an Added event but with nil value. + timer := time.NewTimer(200 * time.Millisecond) + defer timer.Stop() + select { + case event := <-w.ResultChan(): + Expect(event.Error).NotTo(HaveOccurred()) + Expect(event.Type).To(Equal(api.WatchAdded)) + Expect(event.New.Value).To(BeNil()) + case <-timer.C: + // Expected: no event received. + } + + w.Stop() + }) + + Context("typical VMIM progressions", func() { + var ( + kvFake *kubevirtfake.Clientset + vmim *kubevirtv1.VirtualMachineInstanceMigration + w api.WatchInterface + err error + expectLiveMigration func() + expectEventWithNilValue func(api.WatchEventType) + ) + + BeforeEach(func() { + kvFake = kubevirtfake.NewSimpleClientset() + + client := newLiveMigrationClient(kvFake) + w, err = client.Watch(ctx, model.ResourceListOptions{ + Namespace: "test-ns", + Kind: internalapi.KindLiveMigration, + }, api.WatchOptions{}) + Expect(err).NotTo(HaveOccurred()) + + expectAndCheckEvent := func(check func(api.WatchEvent)) { + timer := time.NewTimer(200 * time.Millisecond) + defer timer.Stop() + select { + case event := <-w.ResultChan(): + if check != nil { + check(event) + } else { + Expect(event).To(BeNil()) + } + case <-timer.C: + if check != nil { + Fail("expected to get watch event") + } + // Else as expected: no event received. + } + } + + expectLiveMigration = func() { + expectAndCheckEvent(func(e api.WatchEvent) { + Expect(e.Type).To(Equal(api.WatchModified)) + Expect(e.New).NotTo(BeNil()) + Expect(e.New.Value).To(BeAssignableToTypeOf(&internalapi.LiveMigration{})) + lm := e.New.Value.(*internalapi.LiveMigration) + Expect(lm.Spec.Source.Namespace).To(Equal("test-ns")) + Expect(lm.Spec.Source.Name).To(Equal("virt-launcher-vm12-snq7w")) + Expect(lm.Spec.Destination.NamespacedName).To(BeNil()) + Expect(*lm.Spec.Destination.Selector).To(Equal("kubevirt.io/vmi-name == 'vm12' && kubevirt.io/migrationJobUID == 'c05275a7-f85b-42d5-a1d0-acdd49c26d57'")) + }) + } + + expectEventWithNilValue = func(expectedEventType api.WatchEventType) { + expectAndCheckEvent(func(e api.WatchEvent) { + Expect(e.Type).To(Equal(expectedEventType)) + Expect(e.New).NotTo(BeNil()) + Expect(e.New.Value).To(BeNil()) + }) + } + + // Simulate the sequence of VMIM states that we have seen in actual + // usage with KubeVirt, and check the resulting sequence of + // LiveMigration events and contents. + + By("Pending") + vmim = newVMIM("test-ns", "vmim-progression", "400", kubevirtv1.MigrationPending, "vm12", "virt-launcher-vm12-snq7w", "c05275a7-f85b-42d5-a1d0-acdd49c26d57") + vmim, err = kvFake.KubevirtV1().VirtualMachineInstanceMigrations("test-ns").Create(ctx, vmim, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + expectEventWithNilValue(api.WatchAdded) + + By("Scheduling") + vmim.Status.Phase = kubevirtv1.MigrationScheduled + vmim, err = kvFake.KubevirtV1().VirtualMachineInstanceMigrations("test-ns").Update(ctx, vmim, metav1.UpdateOptions{}) + Expect(err).NotTo(HaveOccurred()) + expectEventWithNilValue(api.WatchModified) + + By("PreparingTarget") + vmim.Status.Phase = kubevirtv1.MigrationPreparingTarget + vmim, err = kvFake.KubevirtV1().VirtualMachineInstanceMigrations("test-ns").Update(ctx, vmim, metav1.UpdateOptions{}) + Expect(err).NotTo(HaveOccurred()) + expectEventWithNilValue(api.WatchModified) + + By("TargetReady") + vmim.Status.Phase = kubevirtv1.MigrationTargetReady + vmim, err = kvFake.KubevirtV1().VirtualMachineInstanceMigrations("test-ns").Update(ctx, vmim, metav1.UpdateOptions{}) + Expect(err).NotTo(HaveOccurred()) + expectLiveMigration() + + By("Running") + vmim.Status.Phase = kubevirtv1.MigrationRunning + vmim, err = kvFake.KubevirtV1().VirtualMachineInstanceMigrations("test-ns").Update(ctx, vmim, metav1.UpdateOptions{}) + Expect(err).NotTo(HaveOccurred()) + expectLiveMigration() + }) + + AfterEach(func() { + w.Stop() + }) + + It("emits LiveMigrations as expected when migration succeeds ", func() { + By("Succeeded") + vmim.Status.Phase = kubevirtv1.MigrationSucceeded + vmim, err = kvFake.KubevirtV1().VirtualMachineInstanceMigrations("test-ns").Update(ctx, vmim, metav1.UpdateOptions{}) + Expect(err).NotTo(HaveOccurred()) + expectEventWithNilValue(api.WatchModified) + }) + + It("emits LiveMigrations as expected when migration fails ", func() { + By("Failed") + vmim.Status.Phase = kubevirtv1.MigrationFailed + vmim, err = kvFake.KubevirtV1().VirtualMachineInstanceMigrations("test-ns").Update(ctx, vmim, metav1.UpdateOptions{}) + Expect(err).NotTo(HaveOccurred()) + // Note, in this case the LiveMigration continues to exist so as to + // maintain the existing routing for the target pod, until the + // target pod is cleaned up. In most failure cases this means + // continuing the state of Felix not programming a route at all for + // the target pod. (On the other hand, if the LiveMigration was + // deleted at this point, Felix _would_ program a route for the + // target pod.) If there are cases where live migration fails + // _after_ the target pod has become live - which means that Felix + // has programmed a higher priority route for the target pod - then + // we will probably want to enhance LiveMigration to indicate a + // failure at that point, and make Felix respond to that by removing + // the target pod route. + expectLiveMigration() + }) + }) + }) + + Describe("Read-only stubs", func() { + It("Create returns ErrorOperationNotSupported", func() { + kvFake := kubevirtfake.NewSimpleClientset() + client := newLiveMigrationClient(kvFake) + + kvp := &model.KVPair{ + Key: model.ResourceKey{ + Kind: internalapi.KindLiveMigration, + Namespace: "test-ns", + Name: "test", + }, + Value: internalapi.NewLiveMigration(), + } + + _, err := client.Create(ctx, kvp) + Expect(err).To(HaveOccurred()) + Expect(err).To(BeAssignableToTypeOf(cerrors.ErrorOperationNotSupported{})) + Expect(err.Error()).To(ContainSubstring("read-only")) + }) + + It("Update returns ErrorOperationNotSupported", func() { + kvFake := kubevirtfake.NewSimpleClientset() + client := newLiveMigrationClient(kvFake) + + kvp := &model.KVPair{ + Key: model.ResourceKey{ + Kind: internalapi.KindLiveMigration, + Namespace: "test-ns", + Name: "test", + }, + Value: internalapi.NewLiveMigration(), + } + + _, err := client.Update(ctx, kvp) + Expect(err).To(HaveOccurred()) + Expect(err).To(BeAssignableToTypeOf(cerrors.ErrorOperationNotSupported{})) + Expect(err.Error()).To(ContainSubstring("read-only")) + }) + + It("Delete returns ErrorOperationNotSupported", func() { + kvFake := kubevirtfake.NewSimpleClientset() + client := newLiveMigrationClient(kvFake) + + _, err := client.Delete(ctx, model.ResourceKey{ + Kind: internalapi.KindLiveMigration, + Namespace: "test-ns", + Name: "test", + }, "", nil) + Expect(err).To(HaveOccurred()) + Expect(err).To(BeAssignableToTypeOf(cerrors.ErrorOperationNotSupported{})) + Expect(err.Error()).To(ContainSubstring("read-only")) + }) + + It("DeleteKVP returns ErrorOperationNotSupported", func() { + kvFake := kubevirtfake.NewSimpleClientset() + client := newLiveMigrationClient(kvFake) + + kvp := &model.KVPair{ + Key: model.ResourceKey{ + Kind: internalapi.KindLiveMigration, + Namespace: "test-ns", + Name: "test", + }, + Value: internalapi.NewLiveMigration(), + } + + _, err := client.DeleteKVP(ctx, kvp) + Expect(err).To(HaveOccurred()) + Expect(err).To(BeAssignableToTypeOf(cerrors.ErrorOperationNotSupported{})) + Expect(err.Error()).To(ContainSubstring("read-only")) + }) + }) +}) diff --git a/libcalico-go/lib/backend/k8s/resources/networkpolicy.go b/libcalico-go/lib/backend/k8s/resources/networkpolicy.go index 5cd68b5d4de..3887823fe96 100644 --- a/libcalico-go/lib/backend/k8s/resources/networkpolicy.go +++ b/libcalico-go/lib/backend/k8s/resources/networkpolicy.go @@ -18,30 +18,21 @@ import ( "reflect" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) const ( NetworkPolicyResourceName = "NetworkPolicies" - NetworkPolicyCRDName = "networkpolicies.crd.projectcalico.org" ) -func NewNetworkPolicyClient(c kubernetes.Interface, r rest.Interface) K8sResourceClient { - return &customK8sResourceClient{ - clientSet: c, +func NewNetworkPolicyClient(r rest.Interface, group BackingAPIGroup) K8sResourceClient { + return &customResourceClient{ restClient: r, - name: NetworkPolicyCRDName, resource: NetworkPolicyResourceName, - description: "Calico Network Policies", - k8sResourceType: reflect.TypeOf(apiv3.NetworkPolicy{}), - k8sResourceTypeMeta: metav1.TypeMeta{ - Kind: apiv3.KindNetworkPolicy, - APIVersion: apiv3.GroupVersionCurrent, - }, - k8sListType: reflect.TypeOf(apiv3.NetworkPolicyList{}), - resourceKind: apiv3.KindNetworkPolicy, - namespaced: true, + k8sResourceType: reflect.TypeFor[apiv3.NetworkPolicy](), + k8sListType: reflect.TypeFor[apiv3.NetworkPolicyList](), + kind: apiv3.KindNetworkPolicy, + namespaced: true, + apiGroup: group, } } diff --git a/libcalico-go/lib/backend/k8s/resources/networkset.go b/libcalico-go/lib/backend/k8s/resources/networkset.go index 7154cf10156..e24978fb769 100644 --- a/libcalico-go/lib/backend/k8s/resources/networkset.go +++ b/libcalico-go/lib/backend/k8s/resources/networkset.go @@ -18,30 +18,21 @@ import ( "reflect" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) const ( NetworkSetResourceName = "NetworkSets" - NetworkSetCRDName = "networksets.crd.projectcalico.org" ) -func NewNetworkSetClient(c kubernetes.Interface, r rest.Interface) K8sResourceClient { - return &customK8sResourceClient{ - clientSet: c, +func NewNetworkSetClient(r rest.Interface, group BackingAPIGroup) K8sResourceClient { + return &customResourceClient{ restClient: r, - name: NetworkSetCRDName, resource: NetworkSetResourceName, - description: "Calico Network Sets", - k8sResourceType: reflect.TypeOf(apiv3.NetworkSet{}), - k8sResourceTypeMeta: metav1.TypeMeta{ - Kind: apiv3.KindNetworkSet, - APIVersion: apiv3.GroupVersionCurrent, - }, - k8sListType: reflect.TypeOf(apiv3.NetworkSetList{}), - resourceKind: apiv3.KindNetworkSet, - namespaced: true, + k8sResourceType: reflect.TypeFor[apiv3.NetworkSet](), + k8sListType: reflect.TypeFor[apiv3.NetworkSetList](), + kind: apiv3.KindNetworkSet, + namespaced: true, + apiGroup: group, } } diff --git a/libcalico-go/lib/backend/k8s/resources/node.go b/libcalico-go/lib/backend/k8s/resources/node.go index 3ac003acfb7..000c7fa094d 100644 --- a/libcalico-go/lib/backend/k8s/resources/node.go +++ b/libcalico-go/lib/backend/k8s/resources/node.go @@ -31,7 +31,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" @@ -49,13 +49,14 @@ const ( nodeBgpVXLANTunnelMACAddrV6Annotation = "projectcalico.org/VXLANTunnelMACAddrV6" nodeBgpIpv6AddrAnnotation = "projectcalico.org/IPv6Address" nodeBgpAsnAnnotation = "projectcalico.org/ASNumber" - nodeBgpCIDAnnotation = "projectcalico.org/RouteReflectorClusterID" nodeK8sLabelAnnotation = "projectcalico.org/kube-labels" nodeWireguardIpv4IfaceAddrAnnotation = "projectcalico.org/IPv4WireguardInterfaceAddr" nodeWireguardIpv6IfaceAddrAnnotation = "projectcalico.org/IPv6WireguardInterfaceAddr" nodeWireguardPublicKeyAnnotation = "projectcalico.org/WireguardPublicKey" nodeWireguardPublicKeyV6Annotation = "projectcalico.org/WireguardPublicKeyV6" nodeInterfacesAnnotation = "projectcalico.org/Interfaces" + + RouteReflectorClusterIDAnnotation = "projectcalico.org/RouteReflectorClusterID" ) func NewNodeClient(c kubernetes.Interface, usePodCIDR bool) K8sResourceClient { @@ -89,7 +90,7 @@ func (c *nodeClient) Update(ctx context.Context, kvp *model.KVPair) (*model.KVPa return nil, K8sErrorToCalico(err, kvp.Key) } - node, err := mergeCalicoNodeIntoK8sNode(kvp.Value.(*libapiv3.Node), oldNode) + node, err := mergeCalicoNodeIntoK8sNode(kvp.Value.(*internalapi.Node), oldNode) if err != nil { return nil, err } @@ -199,7 +200,7 @@ func (c *nodeClient) Watch(ctx context.Context, list model.ListInterface, option // K8sNodeToCalico converts a Kubernetes format node, with Calico annotations, to a Calico Node. func K8sNodeToCalico(k8sNode *kapiv1.Node, usePodCIDR bool) (*model.KVPair, error) { // Create a new CalicoNode resource and copy the settings across from the k8s Node. - calicoNode := libapiv3.NewNode() + calicoNode := internalapi.NewNode() calicoNode.ObjectMeta.Name = k8sNode.Name SetCalicoMetadataFromK8sAnnotations(calicoNode, k8sNode) @@ -211,11 +212,11 @@ func K8sNodeToCalico(k8sNode *kapiv1.Node, usePodCIDR bool) (*model.KVPair, erro } // Extract the BGP configuration stored in the annotations. - bgpSpec := &libapiv3.NodeBGPSpec{} + bgpSpec := &internalapi.NodeBGPSpec{} annotations := k8sNode.ObjectMeta.Annotations bgpSpec.IPv4Address = getAnnotation(k8sNode, nodeBgpIpv4AddrAnnotation, validatorv3.ValidateCIDRv4) bgpSpec.IPv6Address = getAnnotation(k8sNode, nodeBgpIpv6AddrAnnotation, validatorv3.ValidateCIDRv6) - bgpSpec.RouteReflectorClusterID = getAnnotation(k8sNode, nodeBgpCIDAnnotation, validatorv3.ValidateIPv4Network) + bgpSpec.RouteReflectorClusterID = getAnnotation(k8sNode, RouteReflectorClusterIDAnnotation, validatorv3.ValidateIPv4Network) asnString, ok := annotations[nodeBgpAsnAnnotation] if ok { @@ -228,10 +229,10 @@ func K8sNodeToCalico(k8sNode *kapiv1.Node, usePodCIDR bool) (*model.KVPair, erro } // Initialize the wireguard spec. We'll include it if it contains non-zero data. - wireguardSpec := &libapiv3.NodeWireguardSpec{} + wireguardSpec := &internalapi.NodeWireguardSpec{} // Add in an orchestrator reference back to the Kubernetes node name. - calicoNode.Spec.OrchRefs = []libapiv3.OrchRef{{NodeName: k8sNode.Name, Orchestrator: apiv3.OrchestratorKubernetes}} + calicoNode.Spec.OrchRefs = []internalapi.OrchRef{{NodeName: k8sNode.Name, Orchestrator: apiv3.OrchestratorKubernetes}} // If using host-local IPAM, assign an IPIP and wireguard tunnel address statically. They can both have the same IP. if usePodCIDR && k8sNode.Spec.PodCIDR != "" { @@ -255,12 +256,12 @@ func K8sNodeToCalico(k8sNode *kapiv1.Node, usePodCIDR bool) (*model.KVPair, erro } // Only set the BGP spec if it is not empty. - if !reflect.DeepEqual(*bgpSpec, libapiv3.NodeBGPSpec{}) { + if !reflect.DeepEqual(*bgpSpec, internalapi.NodeBGPSpec{}) { calicoNode.Spec.BGP = bgpSpec } // Only set the Wireguard spec if it is not empty. - if !reflect.DeepEqual(*wireguardSpec, libapiv3.NodeWireguardSpec{}) { + if !reflect.DeepEqual(*wireguardSpec, internalapi.NodeWireguardSpec{}) { calicoNode.Spec.Wireguard = wireguardSpec } @@ -271,10 +272,10 @@ func K8sNodeToCalico(k8sNode *kapiv1.Node, usePodCIDR bool) (*model.KVPair, erro calicoNode.Spec.VXLANTunnelMACAddrV6 = getAnnotation(k8sNode, nodeBgpVXLANTunnelMACAddrV6Annotation, validatorv3.ValidateMAC) // Set the node status - nodeStatus := libapiv3.NodeStatus{} + nodeStatus := internalapi.NodeStatus{} nodeStatus.WireguardPublicKey = annotations[nodeWireguardPublicKeyAnnotation] nodeStatus.WireguardPublicKeyV6 = annotations[nodeWireguardPublicKeyV6Annotation] - if !reflect.DeepEqual(nodeStatus, libapiv3.NodeStatus{}) { + if !reflect.DeepEqual(nodeStatus, internalapi.NodeStatus{}) { calicoNode.Status = nodeStatus } @@ -296,38 +297,38 @@ func K8sNodeToCalico(k8sNode *kapiv1.Node, usePodCIDR bool) (*model.KVPair, erro return &model.KVPair{ Key: model.ResourceKey{ Name: k8sNode.Name, - Kind: libapiv3.KindNode, + Kind: internalapi.KindNode, }, Value: calicoNode, Revision: k8sNode.ObjectMeta.ResourceVersion, }, nil } -func fillAllAddresses(calicoNode *libapiv3.Node, k8sNode *kapiv1.Node) { +func fillAllAddresses(calicoNode *internalapi.Node, k8sNode *kapiv1.Node) { if bgp := calicoNode.Spec.BGP; bgp != nil { if addr := bgp.IPv4Address; addr != "" { - calicoNode.Spec.Addresses = append(calicoNode.Spec.Addresses, libapiv3.NodeAddress{Address: addr, Type: libapiv3.CalicoNodeIP}) + calicoNode.Spec.Addresses = append(calicoNode.Spec.Addresses, internalapi.NodeAddress{Address: addr, Type: internalapi.CalicoNodeIP}) } if addr := bgp.IPv6Address; addr != "" { - calicoNode.Spec.Addresses = append(calicoNode.Spec.Addresses, libapiv3.NodeAddress{Address: addr, Type: libapiv3.CalicoNodeIP}) + calicoNode.Spec.Addresses = append(calicoNode.Spec.Addresses, internalapi.NodeAddress{Address: addr, Type: internalapi.CalicoNodeIP}) } } for _, kaddr := range k8sNode.Status.Addresses { switch kaddr.Type { case kapiv1.NodeInternalIP: - calicoNode.Spec.Addresses = append(calicoNode.Spec.Addresses, libapiv3.NodeAddress{Address: kaddr.Address, Type: libapiv3.InternalIP}) + calicoNode.Spec.Addresses = append(calicoNode.Spec.Addresses, internalapi.NodeAddress{Address: kaddr.Address, Type: internalapi.InternalIP}) case kapiv1.NodeExternalIP: - calicoNode.Spec.Addresses = append(calicoNode.Spec.Addresses, libapiv3.NodeAddress{Address: kaddr.Address, Type: libapiv3.ExternalIP}) + calicoNode.Spec.Addresses = append(calicoNode.Spec.Addresses, internalapi.NodeAddress{Address: kaddr.Address, Type: internalapi.ExternalIP}) default: continue } } } -func fillAllInterfaces(calicoNode *libapiv3.Node, k8sNode *kapiv1.Node) { +func fillAllInterfaces(calicoNode *internalapi.Node, k8sNode *kapiv1.Node) { annotations := k8sNode.ObjectMeta.Annotations - interfaces := []libapiv3.NodeInterface{} + interfaces := []internalapi.NodeInterface{} if _, ok := annotations[nodeInterfacesAnnotation]; !ok { // The annotation is not present, so we don't have any interfaces to fill. return @@ -352,7 +353,7 @@ func fillAllInterfaces(calicoNode *libapiv3.Node, k8sNode *kapiv1.Node) { // mergeCalicoNodeIntoK8sNode takes a k8s node and a Calico node and puts the values from the Calico // node into the k8s node. -func mergeCalicoNodeIntoK8sNode(calicoNode *libapiv3.Node, k8sNode *kapiv1.Node) (*kapiv1.Node, error) { +func mergeCalicoNodeIntoK8sNode(calicoNode *internalapi.Node, k8sNode *kapiv1.Node) (*kapiv1.Node, error) { // Nodes inherit labels from Kubernetes, but we also have our own set of labels that are stored in an annotation. // For nodes that are being updated, we want to avoid writing k8s labels that we inherited into our annotation // and we don't want to touch the k8s labels directly. Take a copy of the node resource and update its labels @@ -399,7 +400,7 @@ func mergeCalicoNodeIntoK8sNode(calicoNode *libapiv3.Node, k8sNode *kapiv1.Node) delete(k8sNode.Annotations, nodeBgpIpv4IPIPTunnelAddrAnnotation) delete(k8sNode.Annotations, nodeBgpIpv6AddrAnnotation) delete(k8sNode.Annotations, nodeBgpAsnAnnotation) - delete(k8sNode.Annotations, nodeBgpCIDAnnotation) + delete(k8sNode.Annotations, RouteReflectorClusterIDAnnotation) } else { // If the BGP spec is not nil, then handle each field within the BGP spec individually. if calicoNode.Spec.BGP.IPv4Address != "" { @@ -427,9 +428,9 @@ func mergeCalicoNodeIntoK8sNode(calicoNode *libapiv3.Node, k8sNode *kapiv1.Node) } if calicoNode.Spec.BGP.RouteReflectorClusterID != "" { - k8sNode.Annotations[nodeBgpCIDAnnotation] = calicoNode.Spec.BGP.RouteReflectorClusterID + k8sNode.Annotations[RouteReflectorClusterIDAnnotation] = calicoNode.Spec.BGP.RouteReflectorClusterID } else { - delete(k8sNode.Annotations, nodeBgpCIDAnnotation) + delete(k8sNode.Annotations, RouteReflectorClusterIDAnnotation) } } @@ -484,7 +485,7 @@ func mergeCalicoNodeIntoK8sNode(calicoNode *libapiv3.Node, k8sNode *kapiv1.Node) // // Note: if a Kubernetes label shadows a Calico label, the Calico label will be lost when the resource is written // back to the datastore. This is consistent with kube-controllers' behavior. -func mergeCalicoAndK8sLabels(calicoNode *libapiv3.Node, k8sNode *kapiv1.Node) error { +func mergeCalicoAndK8sLabels(calicoNode *internalapi.Node, k8sNode *kapiv1.Node) error { // Now, copy the Kubernetes Node labels over. Note: this may overwrite Calico labels of the same name, but that's // consistent with the kube-controllers behavior. for k, v := range k8sNode.Labels { @@ -510,7 +511,7 @@ func mergeCalicoAndK8sLabels(calicoNode *libapiv3.Node, k8sNode *kapiv1.Node) er // restoreCalicoLabels tries to undo the transformation done by mergeCalicoLabels. If no changes are needed, it // returns the input value; otherwise, it returns a copy. -func restoreCalicoLabels(calicoNode *libapiv3.Node) (*libapiv3.Node, error) { +func restoreCalicoLabels(calicoNode *internalapi.Node) (*internalapi.Node, error) { rawLabels := calicoNode.Annotations[nodeK8sLabelAnnotation] if rawLabels == "" { return calicoNode, nil diff --git a/libcalico-go/lib/backend/k8s/resources/node_test.go b/libcalico-go/lib/backend/k8s/resources/node_test.go index 17b6d786e2e..e1f7112b1cf 100644 --- a/libcalico-go/lib/backend/k8s/resources/node_test.go +++ b/libcalico-go/lib/backend/k8s/resources/node_test.go @@ -17,13 +17,13 @@ package resources import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/api/pkg/lib/numorstring" k8sapi "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/net" ) @@ -42,15 +42,15 @@ var _ = Describe("Test Node conversion", func() { }, Status: k8sapi.NodeStatus{ Addresses: []k8sapi.NodeAddress{ - k8sapi.NodeAddress{ + { Type: k8sapi.NodeInternalIP, Address: "172.17.17.10", }, - k8sapi.NodeAddress{ + { Type: k8sapi.NodeExternalIP, Address: "192.168.1.100", }, - k8sapi.NodeAddress{ + { Type: k8sapi.NodeHostName, Address: "172-17-17-10", }, @@ -65,13 +65,13 @@ var _ = Describe("Test Node conversion", func() { Expect(err).NotTo(HaveOccurred()) // Ensure we got the correct values. - bgpIpv4Address := n.Value.(*libapiv3.Node).Spec.BGP.IPv4Address - ipInIpAddr := n.Value.(*libapiv3.Node).Spec.BGP.IPv4IPIPTunnelAddr - asn := n.Value.(*libapiv3.Node).Spec.BGP.ASNumber - nodeInterfaces := n.Value.(*libapiv3.Node).Spec.Interfaces + bgpIpv4Address := n.Value.(*internalapi.Node).Spec.BGP.IPv4Address + ipInIpAddr := n.Value.(*internalapi.Node).Spec.BGP.IPv4IPIPTunnelAddr + asn := n.Value.(*internalapi.Node).Spec.BGP.ASNumber + nodeInterfaces := n.Value.(*internalapi.Node).Spec.Interfaces ip := net.ParseIP("172.17.17.10") - parsedInterfaces := []libapiv3.NodeInterface{ + parsedInterfaces := []internalapi.NodeInterface{ { Name: "eth1", Addresses: []string{"172.31.11.4"}, @@ -94,21 +94,21 @@ var _ = Describe("Test Node conversion", func() { Name: "TestNode", ResourceVersion: "1234", Annotations: map[string]string{ - nodeBgpIpv4AddrAnnotation: "172.17.17.10", - nodeBgpCIDAnnotation: "288.0.4.5", + nodeBgpIpv4AddrAnnotation: "172.17.17.10", + RouteReflectorClusterIDAnnotation: "288.0.4.5", }, }, Status: k8sapi.NodeStatus{ Addresses: []k8sapi.NodeAddress{ - k8sapi.NodeAddress{ + { Type: k8sapi.NodeInternalIP, Address: "172.17.17.10", }, - k8sapi.NodeAddress{ + { Type: k8sapi.NodeExternalIP, Address: "192.168.1.100", }, - k8sapi.NodeAddress{ + { Type: k8sapi.NodeHostName, Address: "172-17-17-10", }, @@ -121,7 +121,7 @@ var _ = Describe("Test Node conversion", func() { n, err := K8sNodeToCalico(&node, false) Expect(err).NotTo(HaveOccurred()) - rrClusterID := n.Value.(*libapiv3.Node).Spec.BGP.RouteReflectorClusterID + rrClusterID := n.Value.(*internalapi.Node).Spec.BGP.RouteReflectorClusterID Expect(rrClusterID).To(Equal("")) }) @@ -131,21 +131,21 @@ var _ = Describe("Test Node conversion", func() { Name: "TestNode", ResourceVersion: "1234", Annotations: map[string]string{ - nodeBgpIpv4AddrAnnotation: "172.17.17.10", - nodeBgpCIDAnnotation: "fd10::10", + nodeBgpIpv4AddrAnnotation: "172.17.17.10", + RouteReflectorClusterIDAnnotation: "fd10::10", }, }, Status: k8sapi.NodeStatus{ Addresses: []k8sapi.NodeAddress{ - k8sapi.NodeAddress{ + { Type: k8sapi.NodeInternalIP, Address: "172.17.17.10", }, - k8sapi.NodeAddress{ + { Type: k8sapi.NodeExternalIP, Address: "192.168.1.100", }, - k8sapi.NodeAddress{ + { Type: k8sapi.NodeHostName, Address: "172-17-17-10", }, @@ -158,7 +158,7 @@ var _ = Describe("Test Node conversion", func() { n, err := K8sNodeToCalico(&node, false) Expect(err).NotTo(HaveOccurred()) - rrClusterID := n.Value.(*libapiv3.Node).Spec.BGP.RouteReflectorClusterID + rrClusterID := n.Value.(*internalapi.Node).Spec.BGP.RouteReflectorClusterID Expect(rrClusterID).To(Equal("")) }) @@ -168,22 +168,22 @@ var _ = Describe("Test Node conversion", func() { Name: "TestNode", ResourceVersion: "1234", Annotations: map[string]string{ - nodeBgpIpv4AddrAnnotation: "172.17.17.10", - nodeBgpAsnAnnotation: "2546", - nodeBgpCIDAnnotation: "248.0.4.5", + nodeBgpIpv4AddrAnnotation: "172.17.17.10", + nodeBgpAsnAnnotation: "2546", + RouteReflectorClusterIDAnnotation: "248.0.4.5", }, }, Status: k8sapi.NodeStatus{ Addresses: []k8sapi.NodeAddress{ - k8sapi.NodeAddress{ + { Type: k8sapi.NodeInternalIP, Address: "172.17.17.10", }, - k8sapi.NodeAddress{ + { Type: k8sapi.NodeExternalIP, Address: "192.168.1.100", }, - k8sapi.NodeAddress{ + { Type: k8sapi.NodeHostName, Address: "172-17-17-10", }, @@ -198,10 +198,10 @@ var _ = Describe("Test Node conversion", func() { Expect(err).NotTo(HaveOccurred()) // Ensure we got the correct values. - bgpIpv4Address := n.Value.(*libapiv3.Node).Spec.BGP.IPv4Address - ipInIpAddr := n.Value.(*libapiv3.Node).Spec.BGP.IPv4IPIPTunnelAddr - asn := n.Value.(*libapiv3.Node).Spec.BGP.ASNumber - rrClusterID := n.Value.(*libapiv3.Node).Spec.BGP.RouteReflectorClusterID + bgpIpv4Address := n.Value.(*internalapi.Node).Spec.BGP.IPv4Address + ipInIpAddr := n.Value.(*internalapi.Node).Spec.BGP.IPv4IPIPTunnelAddr + asn := n.Value.(*internalapi.Node).Spec.BGP.ASNumber + rrClusterID := n.Value.(*internalapi.Node).Spec.BGP.RouteReflectorClusterID ip := net.ParseIP("172.17.17.10") @@ -223,15 +223,15 @@ var _ = Describe("Test Node conversion", func() { }, Status: k8sapi.NodeStatus{ Addresses: []k8sapi.NodeAddress{ - k8sapi.NodeAddress{ + { Type: k8sapi.NodeInternalIP, Address: "fd10::10", }, - k8sapi.NodeAddress{ + { Type: k8sapi.NodeExternalIP, Address: "fd20::100", }, - k8sapi.NodeAddress{ + { Type: k8sapi.NodeHostName, Address: "fd10-10", }, @@ -246,8 +246,8 @@ var _ = Describe("Test Node conversion", func() { Expect(err).NotTo(HaveOccurred()) // Ensure we got the correct values. - bgpIpv6Address := n.Value.(*libapiv3.Node).Spec.BGP.IPv6Address - ipInIpAddr := n.Value.(*libapiv3.Node).Spec.BGP.IPv4IPIPTunnelAddr + bgpIpv6Address := n.Value.(*internalapi.Node).Spec.BGP.IPv6Address + ipInIpAddr := n.Value.(*internalapi.Node).Spec.BGP.IPv4IPIPTunnelAddr ip := net.ParseIP("fd10::10") @@ -269,7 +269,7 @@ var _ = Describe("Test Node conversion", func() { n, err := K8sNodeToCalico(&node, false) Expect(err).NotTo(HaveOccurred()) - Expect(n.Value.(*libapiv3.Node).Spec.BGP).To(BeNil()) + Expect(n.Value.(*internalapi.Node).Spec.BGP).To(BeNil()) }) It("Should parse and remove BGP info when given Calico Node with empty BGP spec", func() { @@ -285,7 +285,7 @@ var _ = Describe("Test Node conversion", func() { Spec: k8sapi.NodeSpec{}, } - calicoNode := libapiv3.NewNode() + calicoNode := internalapi.NewNode() newK8sNode, err := mergeCalicoNodeIntoK8sNode(calicoNode, k8sNode) Expect(err).NotTo(HaveOccurred()) @@ -316,22 +316,22 @@ var _ = Describe("Test Node conversion", func() { asn, _ := numorstring.ASNumberFromString("2456") By("Merging calico node config into the k8s node") - calicoNode := libapiv3.NewNode() + calicoNode := internalapi.NewNode() calicoNode.Name = "TestNode" calicoNode.ResourceVersion = "1234" calicoNode.Labels = cl calicoNode.Annotations = ca - calicoNode.Spec = libapiv3.NodeSpec{ - BGP: &libapiv3.NodeBGPSpec{ + calicoNode.Spec = internalapi.NodeSpec{ + BGP: &internalapi.NodeBGPSpec{ IPv4Address: "172.17.17.10/24", IPv6Address: "aa:bb:cc::ffff/120", ASNumber: &asn, RouteReflectorClusterID: "245.0.0.3", }, - OrchRefs: []libapiv3.OrchRef{ + OrchRefs: []internalapi.OrchRef{ {NodeName: k8sNode.Name, Orchestrator: "k8s"}, }, - Interfaces: []libapiv3.NodeInterface{ + Interfaces: []internalapi.NodeInterface{ { Name: "eth1", Addresses: []string{"172.31.11.4"}, @@ -348,7 +348,7 @@ var _ = Describe("Test Node conversion", func() { Expect(newK8sNode.Annotations).To(HaveKeyWithValue(nodeBgpIpv4AddrAnnotation, "172.17.17.10/24")) Expect(newK8sNode.Annotations).To(HaveKeyWithValue(nodeBgpIpv6AddrAnnotation, "aa:bb:cc::ffff/120")) Expect(newK8sNode.Annotations).To(HaveKeyWithValue(nodeBgpAsnAnnotation, "2456")) - Expect(newK8sNode.Annotations).To(HaveKeyWithValue(nodeBgpCIDAnnotation, "245.0.0.3")) + Expect(newK8sNode.Annotations).To(HaveKeyWithValue(RouteReflectorClusterIDAnnotation, "245.0.0.3")) Expect(newK8sNode.Annotations).To(HaveKeyWithValue(nodeInterfacesAnnotation, "[{\"name\":\"eth1\",\"addresses\":[\"172.31.11.4\"]},{\"name\":\"eth0\",\"addresses\":[\"172.17.17.10\",\"172.17.17.11\"]}]")) // The calico node annotations and labels should not have escaped directly into the node annotations @@ -366,9 +366,9 @@ var _ = Describe("Test Node conversion", func() { calicoNodeWithMergedLabels := calicoNode.DeepCopy() calicoNodeWithMergedLabels.Annotations[nodeK8sLabelAnnotation] = "{\"net.beta.kubernetes.io/role\":\"control-plane\"}" calicoNodeWithMergedLabels.Labels["net.beta.kubernetes.io/role"] = "control-plane" - calicoNodeWithMergedLabels.Spec.Addresses = []libapiv3.NodeAddress{ - libapiv3.NodeAddress{Address: "172.17.17.10/24", Type: libapiv3.CalicoNodeIP}, - libapiv3.NodeAddress{Address: "aa:bb:cc::ffff/120", Type: libapiv3.CalicoNodeIP}, + calicoNodeWithMergedLabels.Spec.Addresses = []internalapi.NodeAddress{ + {Address: "172.17.17.10/24", Type: internalapi.CalicoNodeIP}, + {Address: "aa:bb:cc::ffff/120", Type: internalapi.CalicoNodeIP}, } Expect(newCalicoNode.Value).To(Equal(calicoNodeWithMergedLabels)) }) @@ -394,11 +394,11 @@ var _ = Describe("Test Node conversion", func() { } By("Merging calico node config into the k8s node") - calicoNode := libapiv3.NewNode() + calicoNode := internalapi.NewNode() calicoNode.Name = "TestNode" calicoNode.ResourceVersion = "1234" calicoNode.Labels = cl - calicoNode.Spec.OrchRefs = []libapiv3.OrchRef{ + calicoNode.Spec.OrchRefs = []internalapi.OrchRef{ {NodeName: k8sNode.Name, Orchestrator: "k8s"}, } @@ -435,7 +435,7 @@ var _ = Describe("Test Node conversion", func() { }) It("restoreCalicoLabels should error if annotations are malformed", func() { - calicoNode := libapiv3.NewNode() + calicoNode := internalapi.NewNode() calicoNode.Annotations = map[string]string{} calicoNode.Annotations[nodeK8sLabelAnnotation] = "Garbage" _, err := restoreCalicoLabels(calicoNode) @@ -466,15 +466,15 @@ var _ = Describe("Test Node conversion", func() { }, Status: k8sapi.NodeStatus{ Addresses: []k8sapi.NodeAddress{ - k8sapi.NodeAddress{ + { Type: k8sapi.NodeInternalIP, Address: "172.17.17.10", }, - k8sapi.NodeAddress{ + { Type: k8sapi.NodeExternalIP, Address: "192.168.1.100", }, - k8sapi.NodeAddress{ + { Type: k8sapi.NodeHostName, Address: "172-17-17-10", }, @@ -489,9 +489,9 @@ var _ = Describe("Test Node conversion", func() { Expect(err).NotTo(HaveOccurred()) // Ensure we got the correct values. - bgpIpv4Address := n.Value.(*libapiv3.Node).Spec.BGP.IPv4Address - ipInIpAddr := n.Value.(*libapiv3.Node).Spec.BGP.IPv4IPIPTunnelAddr - asn := n.Value.(*libapiv3.Node).Spec.BGP.ASNumber + bgpIpv4Address := n.Value.(*internalapi.Node).Spec.BGP.IPv4Address + ipInIpAddr := n.Value.(*internalapi.Node).Spec.BGP.IPv4IPIPTunnelAddr + asn := n.Value.(*internalapi.Node).Spec.BGP.ASNumber ip := net.ParseIP("172.17.17.10") @@ -520,10 +520,10 @@ var _ = Describe("Test Node conversion", func() { Expect(err).NotTo(HaveOccurred()) // Ensure we got the correct values. - bgpIpv4Address := n.Value.(*libapiv3.Node).Spec.BGP.IPv4Address - ipInIpAddr := n.Value.(*libapiv3.Node).Spec.BGP.IPv4IPIPTunnelAddr - wg := n.Value.(*libapiv3.Node).Spec.Wireguard - asn := n.Value.(*libapiv3.Node).Spec.BGP.ASNumber + bgpIpv4Address := n.Value.(*internalapi.Node).Spec.BGP.IPv4Address + ipInIpAddr := n.Value.(*internalapi.Node).Spec.BGP.IPv4IPIPTunnelAddr + wg := n.Value.(*internalapi.Node).Spec.Wireguard + asn := n.Value.(*internalapi.Node).Spec.BGP.ASNumber ip := net.ParseIP("172.17.17.10") Expect(bgpIpv4Address).To(Equal(ip.String())) @@ -552,10 +552,10 @@ var _ = Describe("Test Node conversion", func() { Expect(err).NotTo(HaveOccurred()) // Ensure we got the correct values. - bgpIpv4Address := n.Value.(*libapiv3.Node).Spec.BGP.IPv4Address - ipInIpAddr := n.Value.(*libapiv3.Node).Spec.BGP.IPv4IPIPTunnelAddr - wg := n.Value.(*libapiv3.Node).Spec.Wireguard - asn := n.Value.(*libapiv3.Node).Spec.BGP.ASNumber + bgpIpv4Address := n.Value.(*internalapi.Node).Spec.BGP.IPv4Address + ipInIpAddr := n.Value.(*internalapi.Node).Spec.BGP.IPv4IPIPTunnelAddr + wg := n.Value.(*internalapi.Node).Spec.Wireguard + asn := n.Value.(*internalapi.Node).Spec.BGP.ASNumber ip := net.ParseIP("172.17.17.10") Expect(bgpIpv4Address).To(Equal(ip.String())) @@ -582,10 +582,9 @@ var _ = Describe("Test Node conversion", func() { Expect(err).NotTo(HaveOccurred()) // Ensure we got the correct values. - ipInIpAddr := n.Value.(*libapiv3.Node).Spec.BGP.IPv4IPIPTunnelAddr + ipInIpAddr := n.Value.(*internalapi.Node).Spec.BGP.IPv4IPIPTunnelAddr Expect(ipInIpAddr).To(Equal("")) }) - }) It("should parse addresses of all types into Calico Node", func() { @@ -606,15 +605,15 @@ var _ = Describe("Test Node conversion", func() { }, Status: k8sapi.NodeStatus{ Addresses: []k8sapi.NodeAddress{ - k8sapi.NodeAddress{ + { Type: k8sapi.NodeInternalIP, Address: "172.17.17.10", }, - k8sapi.NodeAddress{ + { Type: k8sapi.NodeExternalIP, Address: "192.168.1.100", }, - k8sapi.NodeAddress{ + { Type: k8sapi.NodeHostName, Address: "172-17-17-10", }, @@ -625,12 +624,12 @@ var _ = Describe("Test Node conversion", func() { n, err := K8sNodeToCalico(&node, false) Expect(err).NotTo(HaveOccurred()) - addrs := n.Value.(*libapiv3.Node).Spec.Addresses - Expect(addrs).To(ConsistOf([]libapiv3.NodeAddress{ - {Address: "fd10::10", Type: libapiv3.CalicoNodeIP}, - {Address: "172.17.17.10", Type: libapiv3.CalicoNodeIP}, // from BGP - {Address: "172.17.17.10", Type: libapiv3.InternalIP}, // from k8s InternalIP - {Address: "192.168.1.100", Type: libapiv3.ExternalIP}, + addrs := n.Value.(*internalapi.Node).Spec.Addresses + Expect(addrs).To(ConsistOf([]internalapi.NodeAddress{ + {Address: "fd10::10", Type: internalapi.CalicoNodeIP}, + {Address: "172.17.17.10", Type: internalapi.CalicoNodeIP}, // from BGP + {Address: "172.17.17.10", Type: internalapi.InternalIP}, // from k8s InternalIP + {Address: "192.168.1.100", Type: internalapi.ExternalIP}, })) }) }) diff --git a/libcalico-go/lib/backend/k8s/resources/patchmode_test.go b/libcalico-go/lib/backend/k8s/resources/patchmode_test.go index ec27dcd6807..d3a5786bc29 100644 --- a/libcalico-go/lib/backend/k8s/resources/patchmode_test.go +++ b/libcalico-go/lib/backend/k8s/resources/patchmode_test.go @@ -17,7 +17,7 @@ package resources import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/libcalico-go/lib/backend/k8s/resources/profile.go b/libcalico-go/lib/backend/k8s/resources/profile.go index f5abfd22f42..78cd7ad36a8 100644 --- a/libcalico-go/lib/backend/k8s/resources/profile.go +++ b/libcalico-go/lib/backend/k8s/resources/profile.go @@ -453,7 +453,7 @@ func (pw *profileWatcher) processProfileEvents() { // event needs to be such that the Watch client can resume watching when a watch fails. // The watch client expects a slash separated list of resource versions in the format // . - var value interface{} + var value any switch e.Type { case api.WatchModified, api.WatchAdded: value = e.New.Value diff --git a/libcalico-go/lib/backend/k8s/resources/profile_test.go b/libcalico-go/lib/backend/k8s/resources/profile_test.go index b6b4d752619..0874fc53f65 100644 --- a/libcalico-go/lib/backend/k8s/resources/profile_test.go +++ b/libcalico-go/lib/backend/k8s/resources/profile_test.go @@ -17,7 +17,7 @@ package resources import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/libcalico-go/lib/backend/k8s/resources/resources.go b/libcalico-go/lib/backend/k8s/resources/resources.go index a82c8adf04c..545d05282b7 100644 --- a/libcalico-go/lib/backend/k8s/resources/resources.go +++ b/libcalico-go/lib/backend/k8s/resources/resources.go @@ -26,7 +26,6 @@ import ( "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/json" - "github.com/projectcalico/calico/libcalico-go/lib/names" ) const ( @@ -131,6 +130,9 @@ func SetCalicoMetadataFromK8sAnnotations(calicoRes Resource, k8sRes Resource) { } } +// ConvertCalicoResourceToK8sResource converts the given Resource from its projectcalico.org/v3 representation +// to its crd.projectcalico.org/v1 representation. +// // Store Calico Metadata in the k8s resource annotations for CRD backed resources. // This should store all Metadata except for those stored in Annotations and Labels and // store them in annotations. @@ -189,27 +191,6 @@ func ConvertCalicoResourceToK8sResource(resIn Resource) (Resource, error) { meta.Namespace = rom.GetNamespace() meta.ResourceVersion = rom.GetResourceVersion() - switch resKind { - // For NetworkPolicy and GlobalNetworkPolicy, we need to prefix the name with the tier name. - // This ensures two policies with the same name, but in different tiers, do not resolve to the same backing object. - case apiv3.KindGlobalNetworkPolicy: - policy := resIn.(*apiv3.GlobalNetworkPolicy) - backendName := names.TieredPolicyName(policy.Name) - meta.Name = backendName - case apiv3.KindNetworkPolicy: - policy := resIn.(*apiv3.NetworkPolicy) - backendName := names.TieredPolicyName(policy.Name) - meta.Name = backendName - case apiv3.KindStagedGlobalNetworkPolicy: - policy := resIn.(*apiv3.StagedGlobalNetworkPolicy) - backendName := names.TieredPolicyName(policy.Name) - meta.Name = backendName - case apiv3.KindStagedNetworkPolicy: - policy := resIn.(*apiv3.StagedNetworkPolicy) - backendName := names.TieredPolicyName(policy.Name) - meta.Name = backendName - } - // Explicitly nil out the labels on the underlying object so that they are not duplicated. // We make an exception for projectcalico.org/ labels, which we own and may use on the v1 API. var v1Labels map[string]string @@ -249,6 +230,9 @@ func isOurs(k string) bool { return strings.Contains(k, "projectcalico.org/") || strings.Contains(k, "operator.tigera.io/") } +// ConvertK8sResourceToCalicoResource converts the given Resource from its crd.projectcalico.org/v1 representation +// to its projectcalico.org/v3 representation. +// // Retrieve all of the Calico Metadata from the k8s resource annotations for CRD backed // resources. This should remove the relevant Calico Metadata annotation when it has finished. func ConvertK8sResourceToCalicoResource(res Resource) error { diff --git a/libcalico-go/lib/backend/k8s/resources/resources_suite_test.go b/libcalico-go/lib/backend/k8s/resources/resources_suite_test.go index 642127ba2df..ab6be84f6be 100644 --- a/libcalico-go/lib/backend/k8s/resources/resources_suite_test.go +++ b/libcalico-go/lib/backend/k8s/resources/resources_suite_test.go @@ -17,16 +17,16 @@ package resources_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestModel(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../../report/k8s_resources_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "K8s resources Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../../report/k8s_resources_suite.xml" + ginkgo.RunSpecs(t, "K8s resources Suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/backend/k8s/resources/resources_test.go b/libcalico-go/lib/backend/k8s/resources/resources_test.go index 40e82e8eea6..f7ace8da095 100644 --- a/libcalico-go/lib/backend/k8s/resources/resources_test.go +++ b/libcalico-go/lib/backend/k8s/resources/resources_test.go @@ -3,8 +3,7 @@ package resources_test import ( "errors" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" @@ -32,21 +31,11 @@ var _ = Describe("ConvertK8sResourceOneToOneAdapter", func() { Expect(resources).Should(matcher1) Expect(err).Should(matcher2) - }, TableEntry{ - Description: "resource not nil error nil returns resource list of one with no error", - Parameters: []interface{}{&model.KVPair{}, nil, []*model.KVPair{{}}, nil}, }, - TableEntry{ - Description: "resource nil error nil returns empty resource list with no error", - Parameters: []interface{}{nil, nil, []*model.KVPair(nil), nil}, - }, - TableEntry{ - Description: "resource nil error not nil returns resource empty list with error", - Parameters: []interface{}{nil, errors.New(""), []*model.KVPair(nil), errors.New("")}, - }, - TableEntry{ - Description: "resource not nil error not nil returns empty resource list with error", - Parameters: []interface{}{&model.KVPair{}, errors.New(""), []*model.KVPair(nil), errors.New("")}, - }, + + Entry("resource not nil error nil returns resource list of one with no error", &model.KVPair{}, nil, []*model.KVPair{{}}, nil), + Entry("resource nil error nil returns empty resource list with no error", nil, nil, []*model.KVPair(nil), nil), + Entry("resource nil error not nil returns resource empty list with error", nil, errors.New(""), []*model.KVPair(nil), errors.New("")), + Entry("resource not nil error not nil returns empty resource list with error", &model.KVPair{}, errors.New(""), []*model.KVPair(nil), errors.New("")), ) }) diff --git a/libcalico-go/lib/backend/k8s/resources/stagedglobalnetworkpolicy.go b/libcalico-go/lib/backend/k8s/resources/stagedglobalnetworkpolicy.go index f479cced50a..46760c334c4 100644 --- a/libcalico-go/lib/backend/k8s/resources/stagedglobalnetworkpolicy.go +++ b/libcalico-go/lib/backend/k8s/resources/stagedglobalnetworkpolicy.go @@ -18,29 +18,20 @@ import ( "reflect" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) const ( StagedGlobalNetworkPolicyResourceName = "StagedGlobalNetworkPolicies" - StagedGlobalNetworkPolicyCRDName = "stagedglobalnetworkpolicies.crd.projectcalico.org" ) -func NewStagedGlobalNetworkPolicyClient(c *kubernetes.Clientset, r *rest.RESTClient) K8sResourceClient { - return &customK8sResourceClient{ - clientSet: c, +func NewStagedGlobalNetworkPolicyClient(r rest.Interface, group BackingAPIGroup) K8sResourceClient { + return &customResourceClient{ restClient: r, - name: StagedGlobalNetworkPolicyCRDName, resource: StagedGlobalNetworkPolicyResourceName, - description: "Calico Staged Global Network Policies", - k8sResourceType: reflect.TypeOf(apiv3.StagedGlobalNetworkPolicy{}), - k8sResourceTypeMeta: metav1.TypeMeta{ - Kind: apiv3.KindStagedGlobalNetworkPolicy, - APIVersion: apiv3.GroupVersionCurrent, - }, - k8sListType: reflect.TypeOf(apiv3.StagedGlobalNetworkPolicyList{}), - resourceKind: apiv3.KindStagedGlobalNetworkPolicy, + k8sResourceType: reflect.TypeFor[apiv3.StagedGlobalNetworkPolicy](), + k8sListType: reflect.TypeFor[apiv3.StagedGlobalNetworkPolicyList](), + kind: apiv3.KindStagedGlobalNetworkPolicy, + apiGroup: group, } } diff --git a/libcalico-go/lib/backend/k8s/resources/stagedkubernetesnetworkpolicy.go b/libcalico-go/lib/backend/k8s/resources/stagedkubernetesnetworkpolicy.go index 23db470a069..32573b9a86b 100644 --- a/libcalico-go/lib/backend/k8s/resources/stagedkubernetesnetworkpolicy.go +++ b/libcalico-go/lib/backend/k8s/resources/stagedkubernetesnetworkpolicy.go @@ -18,29 +18,21 @@ import ( "reflect" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) const ( StagedKubernetesNetworkPolicyResourceName = "StagedKubernetesNetworkPolicies" - StagedKubernetesNetworkPolicyCRDName = "stagedkubernetesnetworkpolicies.crd.projectcalico.org" ) -func NewStagedKubernetesNetworkPolicyClient(c *kubernetes.Clientset, r *rest.RESTClient) K8sResourceClient { - return &customK8sResourceClient{ +func NewStagedKubernetesNetworkPolicyClient(r rest.Interface, group BackingAPIGroup) K8sResourceClient { + return &customResourceClient{ restClient: r, - name: StagedKubernetesNetworkPolicyCRDName, resource: StagedKubernetesNetworkPolicyResourceName, - description: "Calico Staged Kubernetes Network Policies", - k8sResourceType: reflect.TypeOf(apiv3.StagedKubernetesNetworkPolicy{}), - k8sResourceTypeMeta: metav1.TypeMeta{ - Kind: apiv3.KindStagedKubernetesNetworkPolicy, - APIVersion: apiv3.GroupVersionCurrent, - }, - k8sListType: reflect.TypeOf(apiv3.StagedKubernetesNetworkPolicyList{}), - resourceKind: apiv3.KindStagedKubernetesNetworkPolicy, - namespaced: true, + k8sResourceType: reflect.TypeFor[apiv3.StagedKubernetesNetworkPolicy](), + k8sListType: reflect.TypeFor[apiv3.StagedKubernetesNetworkPolicyList](), + kind: apiv3.KindStagedKubernetesNetworkPolicy, + namespaced: true, + apiGroup: group, } } diff --git a/libcalico-go/lib/backend/k8s/resources/stagednetworkpolicy.go b/libcalico-go/lib/backend/k8s/resources/stagednetworkpolicy.go index a7c381c1cb5..6f3cb768b36 100644 --- a/libcalico-go/lib/backend/k8s/resources/stagednetworkpolicy.go +++ b/libcalico-go/lib/backend/k8s/resources/stagednetworkpolicy.go @@ -18,29 +18,21 @@ import ( "reflect" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) const ( StagedNetworkPolicyResourceName = "StagedNetworkPolicies" - StagedNetworkPolicyCRDName = "stagednetworkpolicies.crd.projectcalico.org" ) -func NewStagedNetworkPolicyClient(c *kubernetes.Clientset, r *rest.RESTClient) K8sResourceClient { - return &customK8sResourceClient{ +func NewStagedNetworkPolicyClient(r rest.Interface, group BackingAPIGroup) K8sResourceClient { + return &customResourceClient{ restClient: r, - name: StagedNetworkPolicyCRDName, resource: StagedNetworkPolicyResourceName, - description: "Calico Staged Network Policies", - k8sResourceType: reflect.TypeOf(apiv3.StagedNetworkPolicy{}), - k8sResourceTypeMeta: metav1.TypeMeta{ - Kind: apiv3.KindStagedNetworkPolicy, - APIVersion: apiv3.GroupVersionCurrent, - }, - k8sListType: reflect.TypeOf(apiv3.StagedNetworkPolicyList{}), - resourceKind: apiv3.KindStagedNetworkPolicy, - namespaced: true, + k8sResourceType: reflect.TypeFor[apiv3.StagedNetworkPolicy](), + k8sListType: reflect.TypeFor[apiv3.StagedNetworkPolicyList](), + kind: apiv3.KindStagedNetworkPolicy, + namespaced: true, + apiGroup: group, } } diff --git a/libcalico-go/lib/backend/k8s/resources/tiers.go b/libcalico-go/lib/backend/k8s/resources/tiers.go index 2948f89d1b3..baac3e314b1 100644 --- a/libcalico-go/lib/backend/k8s/resources/tiers.go +++ b/libcalico-go/lib/backend/k8s/resources/tiers.go @@ -19,8 +19,6 @@ import ( "reflect" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" cresources "github.com/projectcalico/calico/libcalico-go/lib/resources" @@ -28,32 +26,25 @@ import ( const ( TierResourceName = "Tiers" - TierCRDName = "tiers.crd.projectcalico.org" ) -func NewTierClient(c kubernetes.Interface, r rest.Interface) K8sResourceClient { - return &customK8sResourceClient{ - clientSet: c, - restClient: r, - name: TierCRDName, - resource: TierResourceName, - description: "Calico Tiers", - k8sResourceType: reflect.TypeOf(apiv3.Tier{}), - k8sResourceTypeMeta: metav1.TypeMeta{ - Kind: apiv3.KindTier, - APIVersion: apiv3.GroupVersionCurrent, - }, - k8sListType: reflect.TypeOf(apiv3.TierList{}), - resourceKind: apiv3.KindTier, - versionconverter: tierv1v3Converter{}, +func NewTierClient(r rest.Interface, group BackingAPIGroup) K8sResourceClient { + return &customResourceClient{ + restClient: r, + resource: TierResourceName, + k8sResourceType: reflect.TypeFor[apiv3.Tier](), + k8sListType: reflect.TypeFor[apiv3.TierList](), + kind: apiv3.KindTier, + versionconverter: tierDefaulter{}, + apiGroup: group, } } -// tierv1v3Converter implements VersionConverter interface. -type tierv1v3Converter struct{} +// tierDefaulter implements VersionConverter interface. +type tierDefaulter struct{} -// ConvertFromK8s converts v1 Tier Resource to v3 Tier resource -func (c tierv1v3Converter) ConvertFromK8s(inRes Resource) (Resource, error) { +// ConvertFromK8s sets defaults on the Tier when reading it from the Kubernetes API. +func (c tierDefaulter) ConvertFromK8s(inRes Resource) (Resource, error) { tier, ok := inRes.(*apiv3.Tier) if !ok { return nil, fmt.Errorf("invalid type conversion") diff --git a/libcalico-go/lib/backend/k8s/resources/watcher.go b/libcalico-go/lib/backend/k8s/resources/watcher.go index 33219627bab..63b516eea0b 100644 --- a/libcalico-go/lib/backend/k8s/resources/watcher.go +++ b/libcalico-go/lib/backend/k8s/resources/watcher.go @@ -117,6 +117,7 @@ func (crw *k8sWatcherConverter) processK8sEvents() { } for _, e := range events { + crw.logCxt.Debugf("Sending event: %s", e) select { case crw.resultChan <- *e: crw.logCxt.Debug("Kubernetes event converted and sent to backend watcher") @@ -183,7 +184,6 @@ func (crw *k8sWatcherConverter) convertEvent(kevent kwatch.Event) []*api.WatchEv Error: fmt.Errorf("unhandled Kubernetes watcher event type: %v", kevent.Type), }} } - } func (crw *k8sWatcherConverter) buildEventsFromKVPs(kvps []*model.KVPair, t kwatch.EventType) []*api.WatchEvent { diff --git a/libcalico-go/lib/backend/k8s/resources/watcher_test.go b/libcalico-go/lib/backend/k8s/resources/watcher_test.go index ed148e3564c..b94fcbde316 100644 --- a/libcalico-go/lib/backend/k8s/resources/watcher_test.go +++ b/libcalico-go/lib/backend/k8s/resources/watcher_test.go @@ -15,7 +15,7 @@ package resources import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" diff --git a/libcalico-go/lib/backend/k8s/resources/workloadendpoint.go b/libcalico-go/lib/backend/k8s/resources/workloadendpoint.go index 754ca164091..3998db1a3f5 100644 --- a/libcalico-go/lib/backend/k8s/resources/workloadendpoint.go +++ b/libcalico-go/lib/backend/k8s/resources/workloadendpoint.go @@ -29,7 +29,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" @@ -116,7 +116,7 @@ func (c *WorkloadEndpointClient) patchInAnnotations( func (c *WorkloadEndpointClient) calcCNIAnnotations(kvp *model.KVPair) map[string]string { annotations := make(map[string]string) - wep := kvp.Value.(*libapiv3.WorkloadEndpoint) + wep := kvp.Value.(*internalapi.WorkloadEndpoint) ips := wep.Spec.IPNetworks if len(ips) == 0 { return annotations @@ -194,8 +194,8 @@ func (c *WorkloadEndpointClient) patchPodAnnotations( } func calculateAnnotationPatch(revision string, uid *types.UID, annotations map[string]string) ([]byte, error) { - patch := map[string]interface{}{} - metadata := map[string]interface{}{} + patch := map[string]any{} + metadata := map[string]any{} patch["metadata"] = metadata if len(annotations) > 0 { metadata["annotations"] = annotations @@ -250,7 +250,7 @@ func (c *WorkloadEndpointClient) Get(ctx context.Context, key model.Key, revisio // Find the WorkloadEndpoint that has a name matching the name in the given key for _, kvp := range kvps { - wep := kvp.Value.(*libapiv3.WorkloadEndpoint) + wep := kvp.Value.(*internalapi.WorkloadEndpoint) if wep.Name == key.(model.ResourceKey).Name { return kvp, nil } @@ -369,7 +369,7 @@ func (c *WorkloadEndpointClient) listUsingName( } // Find the WorkloadEndpoint that has a name matching the name in the given key for _, kvp := range kvps { - wep := kvp.Value.(*libapiv3.WorkloadEndpoint) + wep := kvp.Value.(*internalapi.WorkloadEndpoint) if wep.Name == wepName { tmpKVPs = []*model.KVPair{kvp} break @@ -446,7 +446,7 @@ func (c *WorkloadEndpointClient) convertAndFilterPodFn(wepID names.WorkloadEndpo return nil, err } for _, wep := range weps { - if wep.Value.(*libapiv3.WorkloadEndpoint).Name != wepName { + if wep.Value.(*internalapi.WorkloadEndpoint).Name != wepName { continue } return []*model.KVPair{wep}, nil diff --git a/libcalico-go/lib/backend/k8s/resources/workloadendpoint_test.go b/libcalico-go/lib/backend/k8s/resources/workloadendpoint_test.go index 90d3f04064a..6f961dde689 100644 --- a/libcalico-go/lib/backend/k8s/resources/workloadendpoint_test.go +++ b/libcalico-go/lib/backend/k8s/resources/workloadendpoint_test.go @@ -22,7 +22,7 @@ import ( "time" "github.com/google/uuid" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" k8sapi "k8s.io/api/core/v1" @@ -31,7 +31,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/fake" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/resources" @@ -67,12 +67,12 @@ var _ = Describe("WorkloadEndpointClient", func() { wepName, err := wepIDs.CalculateWorkloadEndpointName(false) Expect(err).ShouldNot(HaveOccurred()) - wep := &libapiv3.WorkloadEndpoint{ + wep := &internalapi.WorkloadEndpoint{ ObjectMeta: metav1.ObjectMeta{ Name: wepName, Namespace: "testNamespace", }, - Spec: libapiv3.WorkloadEndpointSpec{ + Spec: internalapi.WorkloadEndpointSpec{ IPNetworks: []string{}, }, } @@ -81,7 +81,7 @@ var _ = Describe("WorkloadEndpointClient", func() { Key: model.ResourceKey{ Name: wep.Name, Namespace: wep.Namespace, - Kind: libapiv3.KindWorkloadEndpoint, + Kind: internalapi.KindWorkloadEndpoint, }, Value: wep, } @@ -117,12 +117,12 @@ var _ = Describe("WorkloadEndpointClient", func() { wepName, err := wepIDs.CalculateWorkloadEndpointName(false) Expect(err).ShouldNot(HaveOccurred()) - wep := &libapiv3.WorkloadEndpoint{ + wep := &internalapi.WorkloadEndpoint{ ObjectMeta: metav1.ObjectMeta{ Name: wepName, Namespace: "testNamespace", }, - Spec: libapiv3.WorkloadEndpointSpec{ + Spec: internalapi.WorkloadEndpointSpec{ ContainerID: "abcde12345", IPNetworks: []string{"192.168.91.117/32", "192.168.91.118/32"}, }, @@ -132,7 +132,7 @@ var _ = Describe("WorkloadEndpointClient", func() { Key: model.ResourceKey{ Name: wep.Name, Namespace: wep.Namespace, - Kind: libapiv3.KindWorkloadEndpoint, + Kind: internalapi.KindWorkloadEndpoint, }, Value: wep, } @@ -174,12 +174,12 @@ var _ = Describe("WorkloadEndpointClient", func() { wepName, err := wepIDs.CalculateWorkloadEndpointName(false) Expect(err).ShouldNot(HaveOccurred()) - wep := &libapiv3.WorkloadEndpoint{ + wep := &internalapi.WorkloadEndpoint{ ObjectMeta: metav1.ObjectMeta{ Name: wepName, Namespace: "testNamespace", }, - Spec: libapiv3.WorkloadEndpointSpec{ + Spec: internalapi.WorkloadEndpointSpec{ IPNetworks: []string{}, }, } @@ -188,7 +188,7 @@ var _ = Describe("WorkloadEndpointClient", func() { Key: model.ResourceKey{ Name: wep.Name, Namespace: wep.Namespace, - Kind: libapiv3.KindWorkloadEndpoint, + Kind: internalapi.KindWorkloadEndpoint, }, Value: wep, } @@ -224,12 +224,12 @@ var _ = Describe("WorkloadEndpointClient", func() { wepName, err := wepIDs.CalculateWorkloadEndpointName(false) Expect(err).ShouldNot(HaveOccurred()) - wep := &libapiv3.WorkloadEndpoint{ + wep := &internalapi.WorkloadEndpoint{ ObjectMeta: metav1.ObjectMeta{ Name: wepName, Namespace: "testNamespace", }, - Spec: libapiv3.WorkloadEndpointSpec{ + Spec: internalapi.WorkloadEndpointSpec{ IPNetworks: []string{"192.168.91.117/32", "192.168.91.118/32"}, ContainerID: "abcd1234", }, @@ -239,7 +239,7 @@ var _ = Describe("WorkloadEndpointClient", func() { Key: model.ResourceKey{ Name: wep.Name, Namespace: wep.Namespace, - Kind: libapiv3.KindWorkloadEndpoint, + Kind: internalapi.KindWorkloadEndpoint, }, Value: wep, } @@ -293,7 +293,7 @@ var _ = Describe("WorkloadEndpointClient", func() { key := model.ResourceKey{ Name: wepName, Namespace: "testNamespace", - Kind: libapiv3.KindWorkloadEndpoint, + Kind: internalapi.KindWorkloadEndpoint, } wep, err := wepClient.Get(context.Background(), key, "") Expect(err).NotTo(HaveOccurred()) @@ -354,13 +354,13 @@ var _ = Describe("WorkloadEndpointClient", func() { wep, err := wepClient.Get(context.Background(), model.ResourceKey{ Name: wepName, Namespace: "testNamespace", - Kind: libapiv3.KindWorkloadEndpoint, + Kind: internalapi.KindWorkloadEndpoint, }, "") Expect(err).ShouldNot(HaveOccurred()) - Expect(wep.Value).Should(Equal(&libapiv3.WorkloadEndpoint{ + Expect(wep.Value).Should(Equal(&internalapi.WorkloadEndpoint{ TypeMeta: metav1.TypeMeta{ - Kind: libapiv3.KindWorkloadEndpoint, + Kind: internalapi.KindWorkloadEndpoint, APIVersion: apiv3.GroupVersionCurrent, }, ObjectMeta: metav1.ObjectMeta{ @@ -371,7 +371,7 @@ var _ = Describe("WorkloadEndpointClient", func() { apiv3.LabelOrchestrator: "k8s", }, }, - Spec: libapiv3.WorkloadEndpointSpec{ + Spec: internalapi.WorkloadEndpointSpec{ Orchestrator: "k8s", Node: "test-node", Pod: "simplePod", @@ -404,11 +404,11 @@ var _ = Describe("WorkloadEndpointClient", func() { model.ResourceListOptions{ Name: "test--node-k8s-simplePod-eth0", Namespace: "testNamespace", - Kind: libapiv3.KindWorkloadEndpoint, + Kind: internalapi.KindWorkloadEndpoint, }, - []*libapiv3.WorkloadEndpoint{{ + []*internalapi.WorkloadEndpoint{{ TypeMeta: metav1.TypeMeta{ - Kind: libapiv3.KindWorkloadEndpoint, + Kind: internalapi.KindWorkloadEndpoint, APIVersion: apiv3.GroupVersionCurrent, }, ObjectMeta: metav1.ObjectMeta{ @@ -419,7 +419,7 @@ var _ = Describe("WorkloadEndpointClient", func() { apiv3.LabelOrchestrator: "k8s", }, }, - Spec: libapiv3.WorkloadEndpointSpec{ + Spec: internalapi.WorkloadEndpointSpec{ Orchestrator: "k8s", Node: "test-node", Pod: "simplePod", @@ -448,9 +448,9 @@ var _ = Describe("WorkloadEndpointClient", func() { model.ResourceListOptions{ Name: "test--node-k8s-simplePod-ens4", Namespace: "testNamespace", - Kind: libapiv3.KindWorkloadEndpoint, + Kind: internalapi.KindWorkloadEndpoint, }, - []*libapiv3.WorkloadEndpoint(nil), + []*internalapi.WorkloadEndpoint(nil), ) }) }) @@ -472,11 +472,11 @@ var _ = Describe("WorkloadEndpointClient", func() { model.ResourceListOptions{ Name: "test--node-k8s-simplePod", Namespace: "testNamespace", - Kind: libapiv3.KindWorkloadEndpoint, + Kind: internalapi.KindWorkloadEndpoint, }, - []*libapiv3.WorkloadEndpoint{{ + []*internalapi.WorkloadEndpoint{{ TypeMeta: metav1.TypeMeta{ - Kind: libapiv3.KindWorkloadEndpoint, + Kind: internalapi.KindWorkloadEndpoint, APIVersion: apiv3.GroupVersionCurrent, }, ObjectMeta: metav1.ObjectMeta{ @@ -487,7 +487,7 @@ var _ = Describe("WorkloadEndpointClient", func() { apiv3.LabelOrchestrator: "k8s", }, }, - Spec: libapiv3.WorkloadEndpointSpec{ + Spec: internalapi.WorkloadEndpointSpec{ Orchestrator: "k8s", Node: "test-node", Pod: "simplePod", @@ -519,14 +519,14 @@ var _ = Describe("WorkloadEndpointClient", func() { _, err := wepClient.List(context.Background(), model.ResourceListOptions{ Name: "test--node-k8s", Namespace: "testNamespace", - Kind: libapiv3.KindWorkloadEndpoint, + Kind: internalapi.KindWorkloadEndpoint, }, "") Expect(err).Should(Equal(cerrors.ErrorResourceDoesNotExist{ Identifier: model.ResourceListOptions{ Name: "test--node-k8s", Namespace: "testNamespace", - Kind: libapiv3.KindWorkloadEndpoint, + Kind: internalapi.KindWorkloadEndpoint, }, Err: errors.New("malformed WorkloadEndpoint name - unable to determine Pod name"), })) @@ -564,12 +564,12 @@ var _ = Describe("WorkloadEndpointClient", func() { }, model.ResourceListOptions{ Namespace: "testNamespace", - Kind: libapiv3.KindWorkloadEndpoint, + Kind: internalapi.KindWorkloadEndpoint, }, - []*libapiv3.WorkloadEndpoint{ + []*internalapi.WorkloadEndpoint{ { TypeMeta: metav1.TypeMeta{ - Kind: libapiv3.KindWorkloadEndpoint, + Kind: internalapi.KindWorkloadEndpoint, APIVersion: apiv3.GroupVersionCurrent, }, ObjectMeta: metav1.ObjectMeta{ @@ -580,7 +580,7 @@ var _ = Describe("WorkloadEndpointClient", func() { apiv3.LabelOrchestrator: "k8s", }, }, - Spec: libapiv3.WorkloadEndpointSpec{ + Spec: internalapi.WorkloadEndpointSpec{ Orchestrator: "k8s", Node: "test-node", Pod: "simplePod", @@ -592,7 +592,7 @@ var _ = Describe("WorkloadEndpointClient", func() { }, { TypeMeta: metav1.TypeMeta{ - Kind: libapiv3.KindWorkloadEndpoint, + Kind: internalapi.KindWorkloadEndpoint, APIVersion: apiv3.GroupVersionCurrent, }, ObjectMeta: metav1.ObjectMeta{ @@ -603,7 +603,7 @@ var _ = Describe("WorkloadEndpointClient", func() { apiv3.LabelOrchestrator: "k8s", }, }, - Spec: libapiv3.WorkloadEndpointSpec{ + Spec: internalapi.WorkloadEndpointSpec{ Orchestrator: "k8s", Node: "test-node", Pod: "simplePod2", @@ -634,9 +634,9 @@ var _ = Describe("WorkloadEndpointClient", func() { PodIP: "192.168.91.113", }, }, - }, []*libapiv3.WorkloadEndpoint{{ + }, []*internalapi.WorkloadEndpoint{{ TypeMeta: metav1.TypeMeta{ - Kind: libapiv3.KindWorkloadEndpoint, + Kind: internalapi.KindWorkloadEndpoint, APIVersion: apiv3.GroupVersionCurrent, }, ObjectMeta: metav1.ObjectMeta{ @@ -647,7 +647,7 @@ var _ = Describe("WorkloadEndpointClient", func() { apiv3.LabelOrchestrator: "k8s", }, }, - Spec: libapiv3.WorkloadEndpointSpec{ + Spec: internalapi.WorkloadEndpointSpec{ Orchestrator: "k8s", Node: "test-node", Pod: "simplePod", @@ -698,10 +698,10 @@ var _ = Describe("WorkloadEndpointClient", func() { PodIP: "192.168.91.115", }, }, - }, []*libapiv3.WorkloadEndpoint{ + }, []*internalapi.WorkloadEndpoint{ { TypeMeta: metav1.TypeMeta{ - Kind: libapiv3.KindWorkloadEndpoint, + Kind: internalapi.KindWorkloadEndpoint, APIVersion: apiv3.GroupVersionCurrent, }, ObjectMeta: metav1.ObjectMeta{ @@ -712,7 +712,7 @@ var _ = Describe("WorkloadEndpointClient", func() { apiv3.LabelOrchestrator: "k8s", }, }, - Spec: libapiv3.WorkloadEndpointSpec{ + Spec: internalapi.WorkloadEndpointSpec{ Orchestrator: "k8s", Node: "test-node", Pod: "termPod", @@ -724,7 +724,7 @@ var _ = Describe("WorkloadEndpointClient", func() { }, { TypeMeta: metav1.TypeMeta{ - Kind: libapiv3.KindWorkloadEndpoint, + Kind: internalapi.KindWorkloadEndpoint, APIVersion: apiv3.GroupVersionCurrent, }, ObjectMeta: metav1.ObjectMeta{ @@ -735,7 +735,7 @@ var _ = Describe("WorkloadEndpointClient", func() { apiv3.LabelOrchestrator: "k8s", }, }, - Spec: libapiv3.WorkloadEndpointSpec{ + Spec: internalapi.WorkloadEndpointSpec{ Orchestrator: "k8s", Node: "test-node", Pod: "termPod2", @@ -751,22 +751,22 @@ var _ = Describe("WorkloadEndpointClient", func() { }) }) -func testListWorkloadEndpoints(pods []runtime.Object, listOptions model.ResourceListOptions, expectedWEPs []*libapiv3.WorkloadEndpoint) { +func testListWorkloadEndpoints(pods []runtime.Object, listOptions model.ResourceListOptions, expectedWEPs []*internalapi.WorkloadEndpoint) { k8sClient := fake.NewSimpleClientset(pods...) wepClient := resources.NewWorkloadEndpointClient(k8sClient).(*resources.WorkloadEndpointClient) kvps, err := wepClient.List(context.Background(), listOptions, "") Expect(err).ShouldNot(HaveOccurred()) - var weps []*libapiv3.WorkloadEndpoint + var weps []*internalapi.WorkloadEndpoint for _, kvp := range kvps.KVPairs { - weps = append(weps, kvp.Value.(*libapiv3.WorkloadEndpoint)) + weps = append(weps, kvp.Value.(*internalapi.WorkloadEndpoint)) } Expect(weps).Should(Equal(expectedWEPs)) } -func testWatchWorkloadEndpoints(pods []*k8sapi.Pod, expectedWEPs []*libapiv3.WorkloadEndpoint) { +func testWatchWorkloadEndpoints(pods []*k8sapi.Pod, expectedWEPs []*internalapi.WorkloadEndpoint) { k8sClient := fake.NewSimpleClientset() ctx := context.Background() @@ -780,9 +780,7 @@ func testWatchWorkloadEndpoints(pods []*k8sapi.Pod, expectedWEPs []*libapiv3.Wor var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { defer GinkgoRecover() i := 0 @@ -800,7 +798,7 @@ func testWatchWorkloadEndpoints(pods []*k8sapi.Pod, expectedWEPs []*libapiv3.Wor Fail(fmt.Sprintf("expected exactly %d events before timer expired, received %d", len(expectedWEPs), i)) } } - }() + }) for _, pod := range pods { _, err = k8sClient.CoreV1().Pods(pod.Namespace).Create(ctx, pod, metav1.CreateOptions{}) diff --git a/libcalico-go/lib/backend/k8s/k8s_suite_test.go b/libcalico-go/lib/backend/k8s/suite_test.go similarity index 73% rename from libcalico-go/lib/backend/k8s/k8s_suite_test.go rename to libcalico-go/lib/backend/k8s/suite_test.go index 239e4ba223e..459369aed94 100644 --- a/libcalico-go/lib/backend/k8s/k8s_suite_test.go +++ b/libcalico-go/lib/backend/k8s/suite_test.go @@ -17,16 +17,16 @@ package k8s_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestModel(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../report/kdd_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "KDD Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../report/kdd_suite.xml" + ginkgo.RunSpecs(t, "k8s backend suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/backend/model/bgp_node.go b/libcalico-go/lib/backend/model/bgp_node.go index b0ecfb5198c..5123e5fb6c5 100644 --- a/libcalico-go/lib/backend/model/bgp_node.go +++ b/libcalico-go/lib/backend/model/bgp_node.go @@ -22,7 +22,7 @@ import ( ) var ( - typeBGPNode = reflect.TypeOf(BGPNode{}) + typeBGPNode = reflect.TypeFor[BGPNode]() ) type BGPNodeKey struct { @@ -50,6 +50,10 @@ func (key BGPNodeKey) valueType() (reflect.Type, error) { return typeBGPNode, nil } +func (key BGPNodeKey) parseValue(data []byte) (any, error) { + return parseJSONPointer[BGPNode](key, data) +} + func (key BGPNodeKey) String() string { return fmt.Sprintf("BGPNodeKey(host=%s)", key.Host) } diff --git a/libcalico-go/lib/backend/model/bgpconfig.go b/libcalico-go/lib/backend/model/bgpconfig.go index f6aef1caa3f..9785ee5d0b9 100644 --- a/libcalico-go/lib/backend/model/bgpconfig.go +++ b/libcalico-go/lib/backend/model/bgpconfig.go @@ -56,6 +56,10 @@ func (key GlobalBGPConfigKey) valueType() (reflect.Type, error) { return typeGlobalBGPConfig, nil } +func (key GlobalBGPConfigKey) parseValue(data []byte) (any, error) { + return parseRawString(data), nil +} + func (key GlobalBGPConfigKey) String() string { return fmt.Sprintf("GlobalBGPConfig(name=%s)", key.Name) } @@ -119,6 +123,10 @@ func (key NodeBGPConfigKey) valueType() (reflect.Type, error) { return typeNodeBGPConfig, nil } +func (key NodeBGPConfigKey) parseValue(data []byte) (any, error) { + return parseRawString(data), nil +} + func (key NodeBGPConfigKey) String() string { return fmt.Sprintf("HostBGPConfig(node=%s; name=%s)", key.Nodename, key.Name) } diff --git a/libcalico-go/lib/backend/model/bgppeer.go b/libcalico-go/lib/backend/model/bgppeer.go index 44a23a2c345..6811c4e8350 100644 --- a/libcalico-go/lib/backend/model/bgppeer.go +++ b/libcalico-go/lib/backend/model/bgppeer.go @@ -31,7 +31,7 @@ import ( var ( matchGlobalBGPPeer = regexp.MustCompile("^/?calico/bgp/v1/global/peer_v./([^/]+)$") matchHostBGPPeer = regexp.MustCompile("^/?calico/bgp/v1/host/([^/]+)/peer_v./([^/]+)$") - typeBGPPeer = reflect.TypeOf(BGPPeer{}) + typeBGPPeer = reflect.TypeFor[BGPPeer]() ipPortSeparator = "-" defaultPort uint16 = 179 ) @@ -66,6 +66,10 @@ func (key NodeBGPPeerKey) valueType() (reflect.Type, error) { return typeBGPPeer, nil } +func (key NodeBGPPeerKey) parseValue(rawData []byte) (any, error) { + return parseJSONPointer[BGPPeer](key, rawData) +} + func (key NodeBGPPeerKey) String() string { return fmt.Sprintf("BGPPeer(node=%s, ip=%s, port=%d)", key.Nodename, key.PeerIP, key.Port) } @@ -148,6 +152,10 @@ func (key GlobalBGPPeerKey) valueType() (reflect.Type, error) { return typeBGPPeer, nil } +func (key GlobalBGPPeerKey) parseValue(rawData []byte) (any, error) { + return parseJSONPointer[BGPPeer](key, rawData) +} + func (key GlobalBGPPeerKey) String() string { return fmt.Sprintf("BGPPeer(global, ip=%s, port=%d)", key.PeerIP, key.Port) } diff --git a/libcalico-go/lib/backend/model/bgppeer_test.go b/libcalico-go/lib/backend/model/bgppeer_test.go index 09cd0bf3deb..d36106de551 100644 --- a/libcalico-go/lib/backend/model/bgppeer_test.go +++ b/libcalico-go/lib/backend/model/bgppeer_test.go @@ -17,7 +17,7 @@ package model_test import ( "net" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/projectcalico/calico/libcalico-go/lib/backend/model" diff --git a/libcalico-go/lib/backend/model/block.go b/libcalico-go/lib/backend/model/block.go index 8ed4cbb0269..26a2e3a5592 100644 --- a/libcalico-go/lib/backend/model/block.go +++ b/libcalico-go/lib/backend/model/block.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2024 Tigera, Inc. All rights reserved. +// Copyright (c) 2016-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import ( "strings" log "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/projectcalico/calico/libcalico-go/lib/errors" "github.com/projectcalico/calico/libcalico-go/lib/net" @@ -40,13 +41,28 @@ const ( IPAMBlockAttributeTypeWireguard = "wireguardTunnelAddress" IPAMBlockAttributeTypeWireguardV6 = "wireguardV6TunnelAddress" IPAMBlockAttributeTimestamp = "timestamp" - IPAMAffinityTypeHost = "host" - IPAMAffinityTypeVirtual = "virtual" + + // KubeVirt VM pod attributes + + // Name of the VMI object (also the name of the VM object if VM is present, as they share the same name) + IPAMBlockAttributeVMIName = "vmi-name" + + // UID of the VMI object + IPAMBlockAttributeVMIUID = "vmi-uid" + + // UID of the VM object (only present if VMI is owned by a VM) + IPAMBlockAttributeVMUID = "vm-uid" + + // UID of the VirtualMachineInstanceMigration object (only present on migration target pods) + IPAMBlockAttributeVMIMUID = "vmim-uid" + + IPAMAffinityTypeHost = "host" + IPAMAffinityTypeVirtual = "virtual" ) var ( matchBlock = regexp.MustCompile("^/?calico/ipam/v2/assignment/ipv./block/([^/]+)$") - typeBlock = reflect.TypeOf(AllocationBlock{}) + typeBlock = reflect.TypeFor[AllocationBlock]() ) type BlockKey struct { @@ -74,6 +90,10 @@ func (key BlockKey) valueType() (reflect.Type, error) { return typeBlock, nil } +func (key BlockKey) parseValue(rawData []byte) (any, error) { + return parseJSONPointer[AllocationBlock](key, rawData) +} + func (key BlockKey) String() string { return fmt.Sprintf("BlockKey(cidr=%s)", key.CIDR.String()) } @@ -114,6 +134,10 @@ type AllocationBlock struct { // "host:". If not set, this block is not affine to a host. Affinity *string `json:"affinity"` + // Time the affinity was claimed; may be zero for old blocks that predate + // this field. + AffinityClaimTime *metav1.Time `json:"affinity_claim_time,omitempty"` + // Array of allocations in-use within this block. nil entries mean the allocation is free. // For non-nil entries at index i, the index is the ordinal of the allocation within this block // and the value is the index of the associated attributes in the Attributes array. @@ -209,7 +233,7 @@ func (b *AllocationBlock) NonAffineAllocations() []Allocation { continue } attrs := b.Attributes[*attrIdx] - host := attrs.AttrSecondary[IPAMBlockAttributeNode] + host := attrs.ActiveOwnerAttrs[IPAMBlockAttributeNode] if myHost != "" && host == myHost { continue // Skip allocations that are affine to this block. } @@ -246,6 +270,11 @@ func (b *AllocationBlock) OrdinalToIP(ord int) net.IP { } type AllocationAttribute struct { - AttrPrimary *string `json:"handle_id"` - AttrSecondary map[string]string `json:"secondary"` + // HandleID is the primary identifier for the allocation. + HandleID *string `json:"handle_id,omitempty"` + // ActiveOwnerAttrs contains attributes of the active owner (the pod currently using the IP). + ActiveOwnerAttrs map[string]string `json:"secondary,omitempty"` + // AlternateOwnerAttrs contains attributes of the previous or potential owner + // (used during live migration to track the source or target pod). + AlternateOwnerAttrs map[string]string `json:"alternate,omitempty"` } diff --git a/libcalico-go/lib/backend/model/block_affinity.go b/libcalico-go/lib/backend/model/block_affinity.go index b1b27744541..f20930bf337 100644 --- a/libcalico-go/lib/backend/model/block_affinity.go +++ b/libcalico-go/lib/backend/model/block_affinity.go @@ -27,14 +27,14 @@ import ( log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/labels" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/errors" "github.com/projectcalico/calico/libcalico-go/lib/net" ) var ( matchBlockAffinity = regexp.MustCompile(fmt.Sprintf("^/?calico/ipam/v2/(@|%s|%s)/([^/]+)/ipv./block/([^/]+)$", IPAMAffinityTypeHost, IPAMAffinityTypeVirtual)) - typeBlockAff = reflect.TypeOf(BlockAffinity{}) + typeBlockAff = reflect.TypeFor[BlockAffinity]() ) type BlockAffinityState string @@ -83,6 +83,15 @@ func (key BlockAffinityKey) valueType() (reflect.Type, error) { return typeBlockAff, nil } +func (key BlockAffinityKey) parseValue(rawData []byte) (any, error) { + if len(rawData) == 0 { + // Special case: block affinities used to be stored with no value. + // "Upgrade" to equivalent empty JSON. + rawData = []byte("{}") + } + return parseJSONPointer[BlockAffinity](key, rawData) +} + func (key BlockAffinityKey) String() string { return fmt.Sprintf("BlockAffinityKey(cidr=%s, host=%s, affinityType=%s)", key.CIDR, key.Host, key.AffinityType) } @@ -143,7 +152,23 @@ func (options BlockAffinityListOptions) KeyFromDefaultPath(path string) Key { } } -func EnsureBlockAffinityLabels(ba *libapiv3.BlockAffinity) { +func EnsureBlockAffinityLabelsV3(ba *apiv3.BlockAffinity) { + if ba.Labels == nil { + ba.Labels = make(map[string]string) + } + // Hostnames can be longer than labels are allowed to be, so we hash it down. + ba.Labels[apiv3.LabelHostnameHash] = hashHostnameForLabel(ba.Spec.Node) + ba.Labels[apiv3.LabelAffinityType] = ba.Spec.Type + var ipVersion string + if strings.Contains(ba.Spec.CIDR, ":") { + ipVersion = "6" + } else { + ipVersion = "4" + } + ba.Labels[apiv3.LabelIPVersion] = ipVersion +} + +func EnsureBlockAffinityLabels(ba *internalapi.BlockAffinity) { if ba.Labels == nil { ba.Labels = make(map[string]string) } diff --git a/libcalico-go/lib/backend/model/block_affinity_test.go b/libcalico-go/lib/backend/model/block_affinity_test.go index 4588323d793..c48cfb4d19b 100644 --- a/libcalico-go/lib/backend/model/block_affinity_test.go +++ b/libcalico-go/lib/backend/model/block_affinity_test.go @@ -15,20 +15,20 @@ package model_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v4 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" k8slabels "k8s.io/apimachinery/pkg/labels" - v3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" ) var _ = Describe("BlockAffinity labels and selectors", func() { Context("EnsureBlockAffinityLabels", func() { It("should initialize labels and set hostname hash, affinity type, and IPv4 version", func() { - ba := &v3.BlockAffinity{ - Spec: v3.BlockAffinitySpec{ + ba := &internalapi.BlockAffinity{ + Spec: internalapi.BlockAffinitySpec{ Node: "node-a", Type: model.IPAMAffinityTypeHost, CIDR: "10.0.0.0/24", @@ -46,8 +46,8 @@ var _ = Describe("BlockAffinity labels and selectors", func() { }) It("should preserve existing custom labels while overwriting Calico-managed ones", func() { - ba := &v3.BlockAffinity{ - Spec: v3.BlockAffinitySpec{ + ba := &internalapi.BlockAffinity{ + Spec: internalapi.BlockAffinitySpec{ Node: "node-b", Type: model.IPAMAffinityTypeVirtual, CIDR: "2001:db8::/64", @@ -65,8 +65,8 @@ var _ = Describe("BlockAffinity labels and selectors", func() { }) It("should generate different non-empty hostname hashes for different nodes", func() { - a := &v3.BlockAffinity{Spec: v3.BlockAffinitySpec{Node: "node-a", Type: model.IPAMAffinityTypeHost, CIDR: "10.0.0.0/26"}} - b := &v3.BlockAffinity{Spec: v3.BlockAffinitySpec{Node: "node-b", Type: model.IPAMAffinityTypeHost, CIDR: "10.0.1.0/26"}} + a := &internalapi.BlockAffinity{Spec: internalapi.BlockAffinitySpec{Node: "node-a", Type: model.IPAMAffinityTypeHost, CIDR: "10.0.0.0/26"}} + b := &internalapi.BlockAffinity{Spec: internalapi.BlockAffinitySpec{Node: "node-b", Type: model.IPAMAffinityTypeHost, CIDR: "10.0.1.0/26"}} model.EnsureBlockAffinityLabels(a) model.EnsureBlockAffinityLabels(b) @@ -90,12 +90,12 @@ var _ = Describe("BlockAffinity labels and selectors", func() { sel := model.CalculateBlockAffinityLabelSelector(opts) Expect(sel).ToNot(BeNil()) - ba := &v3.BlockAffinity{Spec: v3.BlockAffinitySpec{Node: "node-a", Type: model.IPAMAffinityTypeHost, CIDR: "10.0.0.0/24"}} + ba := &internalapi.BlockAffinity{Spec: internalapi.BlockAffinitySpec{Node: "node-a", Type: model.IPAMAffinityTypeHost, CIDR: "10.0.0.0/24"}} model.EnsureBlockAffinityLabels(ba) Expect(sel.Matches(k8slabels.Set(ba.Labels))).To(BeTrue()) // Different host should not match. - other := &v3.BlockAffinity{Spec: v3.BlockAffinitySpec{Node: "node-b", Type: model.IPAMAffinityTypeHost, CIDR: "10.0.0.0/24"}} + other := &internalapi.BlockAffinity{Spec: internalapi.BlockAffinitySpec{Node: "node-b", Type: model.IPAMAffinityTypeHost, CIDR: "10.0.0.0/24"}} model.EnsureBlockAffinityLabels(other) Expect(sel.Matches(k8slabels.Set(other.Labels))).To(BeFalse()) }) @@ -105,11 +105,11 @@ var _ = Describe("BlockAffinity labels and selectors", func() { sel := model.CalculateBlockAffinityLabelSelector(opts) Expect(sel).ToNot(BeNil()) - v := &v3.BlockAffinity{Spec: v3.BlockAffinitySpec{Node: "n", Type: model.IPAMAffinityTypeVirtual, CIDR: "10.0.0.0/24"}} + v := &internalapi.BlockAffinity{Spec: internalapi.BlockAffinitySpec{Node: "n", Type: model.IPAMAffinityTypeVirtual, CIDR: "10.0.0.0/24"}} model.EnsureBlockAffinityLabels(v) Expect(sel.Matches(k8slabels.Set(v.Labels))).To(BeTrue()) - h := &v3.BlockAffinity{Spec: v3.BlockAffinitySpec{Node: "n", Type: model.IPAMAffinityTypeHost, CIDR: "10.0.0.0/24"}} + h := &internalapi.BlockAffinity{Spec: internalapi.BlockAffinitySpec{Node: "n", Type: model.IPAMAffinityTypeHost, CIDR: "10.0.0.0/24"}} model.EnsureBlockAffinityLabels(h) Expect(sel.Matches(k8slabels.Set(h.Labels))).To(BeFalse()) }) @@ -119,11 +119,11 @@ var _ = Describe("BlockAffinity labels and selectors", func() { sel := model.CalculateBlockAffinityLabelSelector(opts) Expect(sel).ToNot(BeNil()) - v6 := &v3.BlockAffinity{Spec: v3.BlockAffinitySpec{Node: "n", Type: model.IPAMAffinityTypeHost, CIDR: "2001:db8::/64"}} + v6 := &internalapi.BlockAffinity{Spec: internalapi.BlockAffinitySpec{Node: "n", Type: model.IPAMAffinityTypeHost, CIDR: "2001:db8::/64"}} model.EnsureBlockAffinityLabels(v6) Expect(sel.Matches(k8slabels.Set(v6.Labels))).To(BeTrue()) - v4b := &v3.BlockAffinity{Spec: v3.BlockAffinitySpec{Node: "n", Type: model.IPAMAffinityTypeHost, CIDR: "10.0.0.0/24"}} + v4b := &internalapi.BlockAffinity{Spec: internalapi.BlockAffinitySpec{Node: "n", Type: model.IPAMAffinityTypeHost, CIDR: "10.0.0.0/24"}} model.EnsureBlockAffinityLabels(v4b) Expect(sel.Matches(k8slabels.Set(v4b.Labels))).To(BeFalse()) }) @@ -133,19 +133,19 @@ var _ = Describe("BlockAffinity labels and selectors", func() { sel := model.CalculateBlockAffinityLabelSelector(opts) Expect(sel).ToNot(BeNil()) - match := &v3.BlockAffinity{Spec: v3.BlockAffinitySpec{Node: "node-x", Type: model.IPAMAffinityTypeHost, CIDR: "10.0.0.0/24"}} + match := &internalapi.BlockAffinity{Spec: internalapi.BlockAffinitySpec{Node: "node-x", Type: model.IPAMAffinityTypeHost, CIDR: "10.0.0.0/24"}} model.EnsureBlockAffinityLabels(match) Expect(sel.Matches(k8slabels.Set(match.Labels))).To(BeTrue()) - wrongHost := &v3.BlockAffinity{Spec: v3.BlockAffinitySpec{Node: "node-y", Type: model.IPAMAffinityTypeHost, CIDR: "10.0.0.0/24"}} + wrongHost := &internalapi.BlockAffinity{Spec: internalapi.BlockAffinitySpec{Node: "node-y", Type: model.IPAMAffinityTypeHost, CIDR: "10.0.0.0/24"}} model.EnsureBlockAffinityLabels(wrongHost) Expect(sel.Matches(k8slabels.Set(wrongHost.Labels))).To(BeFalse()) - wrongType := &v3.BlockAffinity{Spec: v3.BlockAffinitySpec{Node: "node-x", Type: model.IPAMAffinityTypeVirtual, CIDR: "10.0.0.0/24"}} + wrongType := &internalapi.BlockAffinity{Spec: internalapi.BlockAffinitySpec{Node: "node-x", Type: model.IPAMAffinityTypeVirtual, CIDR: "10.0.0.0/24"}} model.EnsureBlockAffinityLabels(wrongType) Expect(sel.Matches(k8slabels.Set(wrongType.Labels))).To(BeFalse()) - wrongIPVer := &v3.BlockAffinity{Spec: v3.BlockAffinitySpec{Node: "node-x", Type: model.IPAMAffinityTypeHost, CIDR: "2001:db8::/64"}} + wrongIPVer := &internalapi.BlockAffinity{Spec: internalapi.BlockAffinitySpec{Node: "node-x", Type: model.IPAMAffinityTypeHost, CIDR: "2001:db8::/64"}} model.EnsureBlockAffinityLabels(wrongIPVer) Expect(sel.Matches(k8slabels.Set(wrongIPVer.Labels))).To(BeFalse()) }) diff --git a/libcalico-go/lib/backend/model/block_test.go b/libcalico-go/lib/backend/model/block_test.go index 327c142a09c..b6760e3a81a 100644 --- a/libcalico-go/lib/backend/model/block_test.go +++ b/libcalico-go/lib/backend/model/block_test.go @@ -15,14 +15,21 @@ package model_test import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/net" ) +func mustParseCIDR(s string) net.IPNet { + _, ipNet, err := net.ParseCIDR(s) + if err != nil { + panic(err) + } + return *ipNet +} + var _ = Describe("AllocationBlock tests", func() { It("should calculate non-affine allocations correctly", func() { affinity := "host:myhost" @@ -40,9 +47,9 @@ var _ = Describe("AllocationBlock tests", func() { }, Affinity: &affinity, Attributes: []model.AllocationAttribute{ - {AttrSecondary: map[string]string{"node": "myhost"}}, - {AttrSecondary: map[string]string{"node": "otherhost"}}, - {AttrSecondary: map[string]string{"node": "anotherhost"}}, + {ActiveOwnerAttrs: map[string]string{"node": "myhost"}}, + {ActiveOwnerAttrs: map[string]string{"node": "otherhost"}}, + {ActiveOwnerAttrs: map[string]string{"node": "anotherhost"}}, }, } diff --git a/libcalico-go/lib/backend/model/felixconfig.go b/libcalico-go/lib/backend/model/felixconfig.go index cce75e4742a..9eabc893ce6 100644 --- a/libcalico-go/lib/backend/model/felixconfig.go +++ b/libcalico-go/lib/backend/model/felixconfig.go @@ -52,6 +52,10 @@ func (key ReadyFlagKey) valueType() (reflect.Type, error) { return typeReadyFlag, nil } +func (key ReadyFlagKey) parseValue(rawData []byte) (any, error) { + return parseRawBool(rawData), nil +} + func (key ReadyFlagKey) String() string { return "ReadyFlagKey()" } @@ -80,6 +84,10 @@ func (key GlobalConfigKey) valueType() (reflect.Type, error) { return typeGlobalConfig, nil } +func (key GlobalConfigKey) parseValue(rawData []byte) (any, error) { + return parseRawString(rawData), nil +} + func (key GlobalConfigKey) String() string { return fmt.Sprintf("GlobalFelixConfig(name=%s)", key.Name) } @@ -140,6 +148,10 @@ func (key HostConfigKey) valueType() (reflect.Type, error) { return typeHostConfig, nil } +func (key HostConfigKey) parseValue(rawData []byte) (any, error) { + return parseRawString(rawData), nil +} + func (key HostConfigKey) String() string { return fmt.Sprintf("HostConfig(node=%s,name=%s)", key.Hostname, key.Name) } diff --git a/libcalico-go/lib/backend/model/hostendpoint.go b/libcalico-go/lib/backend/model/hostendpoint.go index 1d47ce3e22f..76598053459 100644 --- a/libcalico-go/lib/backend/model/hostendpoint.go +++ b/libcalico-go/lib/backend/model/hostendpoint.go @@ -28,7 +28,7 @@ import ( var ( matchHostEndpoint = regexp.MustCompile("^/?calico/v1/host/([^/]+)/endpoint/([^/]+)$") - typeHostEndpoint = reflect.TypeOf(HostEndpoint{}) + typeHostEndpoint = reflect.TypeFor[HostEndpoint]() ) type HostEndpointKey struct { @@ -66,6 +66,10 @@ func (key HostEndpointKey) valueType() (reflect.Type, error) { return typeHostEndpoint, nil } +func (key HostEndpointKey) parseValue(rawData []byte) (any, error) { + return parseJSONPointer[HostEndpoint](key, rawData) +} + func (key HostEndpointKey) String() string { return fmt.Sprintf("HostEndpoint(node=%s, name=%s)", key.Hostname, key.EndpointID) } @@ -114,7 +118,7 @@ type HostEndpoint struct { Name string `json:"name,omitempty" validate:"omitempty,interface"` ExpectedIPv4Addrs []net.IP `json:"expected_ipv4_addrs,omitempty" validate:"omitempty,dive,ipv4"` ExpectedIPv6Addrs []net.IP `json:"expected_ipv6_addrs,omitempty" validate:"omitempty,dive,ipv6"` - Labels uniquelabels.Map `json:"labels,omitempty" validate:"omitempty,labels"` + Labels uniquelabels.Map `json:"labels" validate:"omitempty,labels"` ProfileIDs []string `json:"profile_ids,omitempty" validate:"omitempty,dive,name"` Ports []EndpointPort `json:"ports,omitempty" validate:"dive"` QoSControls *QoSControls `json:"qosControls,omitempty"` diff --git a/libcalico-go/lib/backend/model/hostendpointstatus.go b/libcalico-go/lib/backend/model/hostendpointstatus.go index ceff29915c7..c0cc94dc902 100644 --- a/libcalico-go/lib/backend/model/hostendpointstatus.go +++ b/libcalico-go/lib/backend/model/hostendpointstatus.go @@ -26,7 +26,7 @@ import ( var ( matchHostEndpointStatus = regexp.MustCompile("^/?calico/felix/v1/host/([^/]+)/endpoint/([^/]+)$") - typeHostEndpointStatus = reflect.TypeOf(HostEndpointStatus{}) + typeHostEndpointStatus = reflect.TypeFor[HostEndpointStatus]() ) type HostEndpointStatusKey struct { @@ -58,6 +58,10 @@ func (key HostEndpointStatusKey) valueType() (reflect.Type, error) { return typeHostEndpointStatus, nil } +func (key HostEndpointStatusKey) parseValue(rawData []byte) (any, error) { + return parseJSONPointer[HostEndpointStatus](key, rawData) +} + func (key HostEndpointStatusKey) String() string { return fmt.Sprintf("HostEndpointStatus(hostname=%s, name=%s)", key.Hostname, key.EndpointID) } diff --git a/libcalico-go/lib/backend/model/ipam_config.go b/libcalico-go/lib/backend/model/ipam_config.go index ce6acf488c9..1618fca00c6 100644 --- a/libcalico-go/lib/backend/model/ipam_config.go +++ b/libcalico-go/lib/backend/model/ipam_config.go @@ -22,7 +22,7 @@ const ( IPAMConfigGlobalName = "default" ) -var typeIPAMConfig = reflect.TypeOf(IPAMConfig{}) +var typeIPAMConfig = reflect.TypeFor[IPAMConfig]() type IPAMConfigKey struct{} @@ -42,6 +42,10 @@ func (key IPAMConfigKey) valueType() (reflect.Type, error) { return typeIPAMConfig, nil } +func (key IPAMConfigKey) parseValue(rawData []byte) (any, error) { + return parseJSONPointer[IPAMConfig](key, rawData) +} + func (key IPAMConfigKey) String() string { return "IPAMConfigKey()" } @@ -50,4 +54,8 @@ type IPAMConfig struct { StrictAffinity bool `json:"strict_affinity,omitempty"` AutoAllocateBlocks bool `json:"auto_allocate_blocks,omitempty"` MaxBlocksPerHost int `json:"maxBlocksPerHost,omitempty"` + // KubeVirtVMAddressPersistence controls whether KubeVirt VirtualMachine workloads + // maintain persistent IP addresses across VM lifecycle events (reboot, migration, pod eviction). + // Valid values: "Enabled", "Disabled". Default: "Enabled" if not specified. + KubeVirtVMAddressPersistence *string `json:"kubeVirtVMAddressPersistence,omitempty"` } diff --git a/libcalico-go/lib/backend/model/ipam_handle.go b/libcalico-go/lib/backend/model/ipam_handle.go index e0c387f78bd..d72f22d3199 100644 --- a/libcalico-go/lib/backend/model/ipam_handle.go +++ b/libcalico-go/lib/backend/model/ipam_handle.go @@ -26,7 +26,7 @@ import ( var ( matchHandle = regexp.MustCompile("^/?calico/ipam/v2/handle/([^/]+)$") - typeHandle = reflect.TypeOf(IPAMHandle{}) + typeHandle = reflect.TypeFor[IPAMHandle]() ) type IPAMHandleKey struct { @@ -53,6 +53,10 @@ func (key IPAMHandleKey) valueType() (reflect.Type, error) { return typeHandle, nil } +func (key IPAMHandleKey) parseValue(rawData []byte) (any, error) { + return parseJSONPointer[IPAMHandle](key, rawData) +} + func (key IPAMHandleKey) String() string { return fmt.Sprintf("IPAMHandleKey(id=%s)", key.HandleID) } diff --git a/libcalico-go/lib/backend/model/ipam_host.go b/libcalico-go/lib/backend/model/ipam_host.go index aa8d4719e90..636766079ec 100644 --- a/libcalico-go/lib/backend/model/ipam_host.go +++ b/libcalico-go/lib/backend/model/ipam_host.go @@ -22,7 +22,7 @@ import ( ) var ( - typeIPAMHost = reflect.TypeOf(IPAMHost{}) + typeIPAMHost = reflect.TypeFor[IPAMHost]() ) type IPAMHostKey struct { @@ -50,6 +50,10 @@ func (key IPAMHostKey) valueType() (reflect.Type, error) { return typeIPAMHost, nil } +func (key IPAMHostKey) parseValue(rawData []byte) (any, error) { + return parseJSONPointer[IPAMHost](key, rawData) +} + func (key IPAMHostKey) String() string { return fmt.Sprintf("IPAMHostKey(host=%s)", key.Host) } diff --git a/libcalico-go/lib/backend/model/ippool.go b/libcalico-go/lib/backend/model/ippool.go index 3b19320cf32..04b33c94a73 100644 --- a/libcalico-go/lib/backend/model/ippool.go +++ b/libcalico-go/lib/backend/model/ippool.go @@ -30,7 +30,7 @@ import ( var ( matchIPPool = regexp.MustCompile("^/?calico/v1/ipam/v./pool/([^/]+)$") - typeIPPool = reflect.TypeOf(IPPool{}) + typeIPPool = reflect.TypeFor[IPPool]() ) type IPPoolKey struct { @@ -58,6 +58,10 @@ func (key IPPoolKey) valueType() (reflect.Type, error) { return typeIPPool, nil } +func (key IPPoolKey) parseValue(rawData []byte) (any, error) { + return parseJSONPointer[IPPool](key, rawData) +} + func (key IPPoolKey) String() string { return fmt.Sprintf("IPPool(cidr=%s)", key.CIDR) } diff --git a/libcalico-go/lib/backend/model/keys.go b/libcalico-go/lib/backend/model/keys.go index c907ede96e5..af2b97a6a9d 100644 --- a/libcalico-go/lib/backend/model/keys.go +++ b/libcalico-go/lib/backend/model/keys.go @@ -15,14 +15,12 @@ package model import ( - "bytes" "fmt" net2 "net" "reflect" "strings" "time" - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -38,13 +36,23 @@ const ( ) // RawString is used a value type to indicate that the value is a bare non-JSON string -type rawString string -type rawBool bool -type rawIP net.IP +type ( + rawString string + rawBool bool + rawIP net.IP +) + +var ( + rawStringType = reflect.TypeFor[rawString]() + rawBoolType = reflect.TypeFor[rawBool]() + rawIPType = reflect.TypeFor[rawIP]() +) -var rawStringType = reflect.TypeOf(rawString("")) -var rawBoolType = reflect.TypeOf(rawBool(true)) -var rawIPType = reflect.TypeOf(rawIP{}) +// LegacyKey is an interface implemented by keys that carry old information but +// that can be upgraded to a modern Key before use. +type LegacyKey interface { + Upgrade() Key +} // Key represents a parsed datastore key. type Key interface { @@ -69,6 +77,7 @@ type Key interface { // valueType returns the object type associated with this key. valueType() (reflect.Type, error) + parseValue(data []byte) (any, error) // String returns a unique string representation of this key. The string // returned by this method must uniquely identify this Key. @@ -104,7 +113,7 @@ type LabelSelectingListInterface interface { // JSON format). type KVPair struct { Key Key - Value interface{} + Value any Revision string UID *types.UID TTL time.Duration // For writes, if non-zero, key has a TTL. @@ -152,9 +161,9 @@ func KeyToDefaultPath(key Key) (string, error) { // // "/calico/v1/policy/tier/a/metadata" // -// and KeyToDefaultPath(PolicyKey{Tier: "a", Name: "b"}): +// and KeyToDefaultPath(PolicyKey{Kind: "NetworkPolicy", Namespace: "default", Name: "b"}): // -// "/calico/v1/policy/tier/a/policy/b" +// "/calico/v1/policy/NetworkPolicy/default/b" func KeyToDefaultDeletePath(key Key) (string, error) { return key.defaultDeletePath() } @@ -321,7 +330,7 @@ func keyFromDefaultPathInner(path string, parts []string) Key { } return ReadyFlagKey{} case "policy": - if len(parts) < 6 { + if len(parts) < 5 { return nil } switch parts[3] { @@ -338,13 +347,12 @@ func keyFromDefaultPathInner(path string, parts []string) Key { Name: unescapeName(parts[4]), } case "policy": + // This is a legacy policy key of form /calico/v1/policy/tier//policy/. + // We can translate this to a modern PolicyKey by parsing the necessary info from the Name. if len(parts) != 7 { return nil } - return PolicyKey{ - Tier: unescapeName(parts[4]), - Name: unescapeName(parts[6]), - } + return parseLegacyPolicyName(unescapeName(parts[4]), unescapeName(parts[6])) } case "profile": pk := unescapeName(parts[4]) @@ -354,6 +362,16 @@ func keyFromDefaultPathInner(path string, parts []string) Key { case "labels": return ProfileLabelsKey{ProfileKey: ProfileKey{pk}} } + default: + // Policy key of form /calico/v1/policy/// + if len(parts) != 6 { + return nil + } + return PolicyKey{ + Kind: unescapeName(parts[3]), + Namespace: unescapeName(parts[4]), + Name: unescapeName(parts[5]), + } } } case "bgp": @@ -406,13 +424,13 @@ func keyFromDefaultPathInner(path string, parts []string) Key { log.Warnf("(BUG) unknown resource type: %v", path) return nil } - if namespace.IsNamespaced(ri.kind) { + if namespace.IsNamespaced(ri.Kind()) { log.Warnf("(BUG) Path is a global resource, but resource is namespaced: %v", path) return nil } log.Debugf("Path is a global resource: %v", path) return ResourceKey{ - Kind: ri.kind, + Kind: ri.Kind(), Name: unescapeName(parts[5]), } case 7: @@ -421,13 +439,13 @@ func keyFromDefaultPathInner(path string, parts []string) Key { log.Warnf("(BUG) unknown resource type: %v", path) return nil } - if !namespace.IsNamespaced(ri.kind) { + if !namespace.IsNamespaced(ri.Kind()) { log.Warnf("(BUG) Path is a namespaced resource, but resource is global: %v", path) return nil } log.Debugf("Path is a namespaced resource: %v", path) return ResourceKey{ - Kind: ri.kind, + Kind: ri.Kind(), Namespace: unescapeName(parts[5]), Name: unescapeName(parts[6]), } @@ -500,13 +518,13 @@ func OldKeyFromDefaultPath(path string) Key { log.Warnf("(BUG) unknown resource type: %v", path) return nil } - if namespace.IsNamespaced(ri.kind) { + if namespace.IsNamespaced(ri.Kind()) { log.Warnf("(BUG) Path is a global resource, but resource is namespaced: %v", path) return nil } log.Debugf("Path is a global resource: %v", path) return ResourceKey{ - Kind: ri.kind, + Kind: ri.Kind(), Name: unescapeName(m[2]), } } else if m := matchNamespacedResource.FindStringSubmatch(path); m != nil { @@ -515,21 +533,22 @@ func OldKeyFromDefaultPath(path string) Key { log.Warnf("(BUG) unknown resource type: %v", path) return nil } - if !namespace.IsNamespaced(ri.kind) { + if !namespace.IsNamespaced(ri.Kind()) { log.Warnf("(BUG) Path is a namespaced resource, but resource is global: %v", path) return nil } log.Debugf("Path is a namespaced resource: %v", path) return ResourceKey{ - Kind: resourceInfoByPlural[unescapeName(m[1])].kind, + Kind: resourceInfoByPlural[unescapeName(m[1])].Kind(), Namespace: unescapeName(m[2]), Name: unescapeName(m[3]), } } else if m := matchPolicy.FindStringSubmatch(path); m != nil { log.Debugf("Path is a policy: %v", path) return PolicyKey{ - Tier: unescapeName(m[1]), - Name: unescapeName(m[2]), + Kind: unescapeName(m[1]), + Namespace: unescapeName(m[2]), + Name: unescapeName(m[3]), } } else if m := matchProfile.FindStringSubmatch(path); m != nil { log.Debugf("Path is a profile: %v (%v)", path, m[2]) @@ -594,95 +613,55 @@ func OldKeyFromDefaultPath(path string) Key { return nil } -// ParseValue parses the default JSON representation of our data into one of -// our value structs, according to the type of key. I.e. if passed a -// PolicyKey as the first parameter, it will try to parse rawData into a -// Policy struct. -func ParseValue(key Key, rawData []byte) (interface{}, error) { - valueType, err := key.valueType() - if err != nil { - return nil, err - } - if valueType == rawStringType { - return string(rawData), nil - } - if valueType == rawBoolType { - return string(rawData) == "true", nil - } - if valueType == rawIPType { - ip := net2.ParseIP(string(rawData)) - if ip == nil { - return nil, nil - } - return &net.IP{IP: ip}, nil - } - value := reflect.New(valueType) - elem := value.Elem() - if elem.Kind() == reflect.Struct && elem.NumField() > 0 { - if elem.Field(0).Type() == reflect.ValueOf(key).Type() { - elem.Field(0).Set(reflect.ValueOf(key)) - } - } - iface := value.Interface() - err = json.Unmarshal(rawData, iface) - if err != nil { - // This is a special case to address backwards compatibility from the time when we had no state information as block affinity value. - // example: - // Key: "/calico/ipam/v2/host/myhost.io/ipv4/block/172.29.82.0-26" - // Value: "" - // In 3.0.7 we added block affinity state as the value, so old "" value is no longer a valid JSON, so for that - // particular case we replace the "" with a "{}" so it can be parsed and we don't leak blocks after upgrade to Calico 3.0.7 - // See: https://github.com/projectcalico/calico/issues/1956 - if bytes.Equal(rawData, []byte(``)) && valueType == typeBlockAff { - rawData = []byte(`{}`) - if err = json.Unmarshal(rawData, iface); err != nil { - return nil, err - } - } else { - log.Warningf("Failed to unmarshal %#v into value %#v", - string(rawData), value) - return nil, err - } - } +func parseRawString(rawData []byte) string { + return string(rawData) +} - if elem.Kind() != reflect.Struct { - // Pointer to a map or slice, unwrap. - iface = elem.Interface() - } +func parseRawBool(rawData []byte) bool { + return string(rawData) == "true" +} - if valueType == reflect.TypeOf(apiv3.NetworkPolicy{}) { - policy := iface.(*apiv3.NetworkPolicy) - policy.Name, policy.Annotations, err = determinePolicyName(policy.Name, policy.Spec.Tier, policy.Annotations) - if err != nil { - return nil, err - } +func parseRawIP(rawData []byte) *net.IP { + ip := net2.ParseIP(string(rawData)) + if ip == nil { + return nil } + return &net.IP{IP: ip} +} - if valueType == reflect.TypeOf(apiv3.GlobalNetworkPolicy{}) { - policy := iface.(*apiv3.GlobalNetworkPolicy) - policy.Name, policy.Annotations, err = determinePolicyName(policy.Name, policy.Spec.Tier, policy.Annotations) - if err != nil { - return nil, err - } - } +func parseJSONValue[V any](key Key, rawData []byte) (V, error) { + var v V - if valueType == reflect.TypeOf(apiv3.StagedNetworkPolicy{}) { - policy := iface.(*apiv3.StagedNetworkPolicy) - policy.Name, policy.Annotations, err = determinePolicyName(policy.Name, policy.Spec.Tier, policy.Annotations) - if err != nil { - return nil, err - } + err := json.Unmarshal(rawData, &v) + if err != nil { + log.Warningf("Failed to unmarshal %v %#v into value %#v", + key, string(rawData), v) + var zero V + return zero, err } - if valueType == reflect.TypeOf(apiv3.StagedGlobalNetworkPolicy{}) { - policy := iface.(*apiv3.StagedGlobalNetworkPolicy) - policy.Name, policy.Annotations, err = determinePolicyName(policy.Name, policy.Spec.Tier, policy.Annotations) - if err != nil { - return nil, err - } + return v, nil +} + +func parseJSONPointer[V any](key Key, rawData []byte) (*V, error) { + var v V + + err := json.Unmarshal(rawData, &v) + if err != nil { + log.Warningf("Failed to unmarshal %v %#v into value %#v", + key, string(rawData), v) + return nil, err } - return iface, nil + return &v, nil +} + +// ParseValue parses the default JSON representation of our data into one of +// our value structs, according to the type of key. I.e. if passed a +// PolicyKey as the first parameter, it will try to parse rawData into a +// Policy struct. +func ParseValue(key Key, rawData []byte) (any, error) { + return key.parseValue(rawData) } // SerializeValue serializes a value in the model to a []byte to be stored in the datastore. This @@ -699,10 +678,10 @@ func SerializeValue(d *KVPair) ([]byte, error) { return []byte(d.Value.(string)), nil } if valueType == rawBoolType { - return []byte(fmt.Sprint(d.Value)), nil + return fmt.Append(nil, d.Value), nil } if valueType == rawIPType { - return []byte(fmt.Sprint(d.Value)), nil + return fmt.Append(nil, d.Value), nil } return json.Marshal(d.Value) } @@ -714,7 +693,7 @@ func determinePolicyName(name, tier string, annotations map[string]string) (stri meta := &metav1.ObjectMeta{} err := json.Unmarshal([]byte(annotations[metadataAnnotation]), meta) if err != nil { - return "", nil, err + return "", nil, fmt.Errorf("CRD annotation %q contained invalid JSON: %w", metadataAnnotation, err) } delete(annotations, metadataAnnotation) return meta.Name, annotations, nil diff --git a/libcalico-go/lib/backend/model/keys_test.go b/libcalico-go/lib/backend/model/keys_test.go index f0f49a3ec0f..033f635117a 100644 --- a/libcalico-go/lib/backend/model/keys_test.go +++ b/libcalico-go/lib/backend/model/keys_test.go @@ -12,19 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -package model_test +package model import ( "reflect" "testing" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/sirupsen/logrus" - . "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/net" ) @@ -41,10 +39,6 @@ import ( var interestingPaths = []string{ "/calico/v1/config/foobar", "/calico/v1/config/foobar/bazz", - "/calico/v1/policy/profile/foo%2fbar/rules", - "/calico/v1/policy/profile/foo%2fbar/labels", - "/calico/v1/policy/tier/default/policy/biff%2fbop", - "/calico/v1/policy/tier", "/calico/v1/host/foobar/workload/open%2fstack/work%2fload/endpoint/end%2fpoint", "/calico/v1/host/foobar/endpoint", "/calico/v1/host/foobar/endpoint/biff", @@ -57,12 +51,6 @@ var interestingPaths = []string{ "/calico/v1/netset/foo", "/calico/v1/Ready", "/calico/v1/Ready/garbage", - "/calico/v1/policy/tier", - "/calico/v1/policy/tier/foo", - "/calico/v1/policy/tier/foo/policy", - "/calico/v1/policy/tier/foo/policy/bar", - "/calico/v1/policy/tier/foo/metadata", - "/calico/v1/policy/profile", "/calico/bgp/v1/global", "/calico/bgp/v1/global/peer_v4", "/calico/bgp/v1/global/peer_v4/name", @@ -171,7 +159,6 @@ func safeKeysEqual(a, b Key) bool { } var _ = Describe("keys with region component", func() { - It("should not parse workload endpoint status with wrong region", func() { Expect((WorkloadEndpointStatusListOptions{RegionString: "region-Asia"}).KeyFromDefaultPath("/calico/felix/v2/region-Europe/host/h1/workload/o1/w1/endpoint/e1")).To(BeNil()) }) @@ -274,24 +261,6 @@ var _ = DescribeTable( Expect(serialized).To(Equal(strKey)) } }, - Entry( - "profile rules with a /", - "/calico/v1/policy/profile/foo%2fbar/rules", - ProfileRulesKey{ProfileKey: ProfileKey{Name: "foo/bar"}}, - false, - ), - Entry( - "profile labels with a /", - "/calico/v1/policy/profile/foo%2fbar/labels", - ProfileLabelsKey{ProfileKey: ProfileKey{Name: "foo/bar"}}, - false, - ), - Entry( - "policy with a /", - "/calico/v1/policy/tier/default/policy/biff%2fbop", - PolicyKey{Tier: "default", Name: "biff/bop"}, - false, - ), Entry( "workload with a /", "/calico/v1/host/foobar/workload/open%2fstack/work%2fload/endpoint/end%2fpoint", @@ -403,11 +372,154 @@ var _ = DescribeTable( nil, true, ), + Entry( + "NetworkPolicy", + "/calico/v1/policy/NetworkPolicy/default/my-policy", + PolicyKey{ + Kind: "NetworkPolicy", + Namespace: "default", + Name: "my-policy", + }, + false, + ), + Entry( + "StagedNetworkPolicy", + "/calico/v1/policy/StagedNetworkPolicy/default/my-staged-policy", + PolicyKey{ + Kind: "StagedNetworkPolicy", + Namespace: "default", + Name: "my-staged-policy", + }, + false, + ), + Entry( + "GlobalNetworkPolicy", + "/calico/v1/policy/GlobalNetworkPolicy//my-global-policy", + PolicyKey{ + Kind: "GlobalNetworkPolicy", + Name: "my-global-policy", + }, + false, + ), +) + +// Test parsing of legacy style PolicyKey of form /calico/v1/policy/tier//policy/ +var _ = DescribeTable( + "key parsing (legacy keys)", + func(strKey string, expected Key, shouldFail bool) { + key := KeyFromDefaultPath(strKey) + if shouldFail { + Expect(key).To(BeNil()) + } else { + Expect(key).To(Equal(expected)) + upg := expected.(LegacyPolicyKey).Upgrade() + Expect(upg).To(Equal(PolicyKey{ + Kind: expected.(LegacyPolicyKey).Kind, + Namespace: expected.(LegacyPolicyKey).Namespace, + Name: expected.(LegacyPolicyKey).Name, + })) + + val, err := key.parseValue([]byte(`{"spec":{}}`)) + Expect(err).ToNot(HaveOccurred()) + Expect(val).To(Equal(&Policy{ + Tier: expected.(LegacyPolicyKey).Tier, + })) + } + }, + + Entry( + "Legacy NetworkPolicy", + "/calico/v1/policy/tier/mytier/policy/ns%2fname", + LegacyPolicyKey{ + PolicyKey: PolicyKey{ + Kind: "NetworkPolicy", + Namespace: "ns", + Name: "name", + }, + Tier: "mytier", + }, + false, + ), + Entry( + "Legacy GlobalNetworkPolicy", + "/calico/v1/policy/tier/default/policy/name", + LegacyPolicyKey{ + PolicyKey: PolicyKey{ + Kind: "GlobalNetworkPolicy", + Name: "name", + }, + Tier: "default", + }, + false, + ), + Entry( + "Legacy StagedNetworkPolicy", + "/calico/v1/policy/tier/default/policy/ns%2fstaged:name", + LegacyPolicyKey{ + PolicyKey: PolicyKey{ + Kind: "StagedNetworkPolicy", + Namespace: "ns", + Name: "name", + }, + Tier: "default", + }, + false, + ), + Entry( + "Legacy StagedGlobalNetworkPolicy", + "/calico/v1/policy/tier/default/policy/staged:name", + LegacyPolicyKey{ + PolicyKey: PolicyKey{ + Kind: "StagedGlobalNetworkPolicy", + Name: "name", + }, + Tier: "default", + }, + false, + ), + Entry( + "Legacy ClusterNetworkPolicy", + "/calico/v1/policy/tier/kube-admin/policy/kcnp.kube-admin.name", + LegacyPolicyKey{ + PolicyKey: PolicyKey{ + Kind: "KubernetesClusterNetworkPolicy", + Name: "kube-admin.name", + }, + Tier: "kube-admin", + }, + false, + ), + Entry( + "Legacy KubernetesNetworkPolicy", + "/calico/v1/policy/tier/default/policy/ns%2fknp.default.name", + LegacyPolicyKey{ + PolicyKey: PolicyKey{ + Kind: "KubernetesNetworkPolicy", + Namespace: "ns", + Name: "name", + }, + Tier: "default", + }, + false, + ), + Entry( + "Legacy StagedKubernetesNetworkPolicy", + "/calico/v1/policy/tier/default/policy/ns%2fstaged:knp.default.name", + LegacyPolicyKey{ + PolicyKey: PolicyKey{ + Kind: "StagedKubernetesNetworkPolicy", + Namespace: "ns", + Name: "name", + }, + Tier: "default", + }, + false, + ), ) var _ = DescribeTable( "value parsing", - func(key Key, rawVal string, expectedVal interface{}) { + func(key Key, rawVal string, expectedVal any) { val, err := ParseValue(key, []byte(rawVal)) Expect(err).ToNot(HaveOccurred()) Expect(val).To(Equal(expectedVal)) @@ -540,26 +652,28 @@ func mustParseCIDR(s string) net.IPNet { return *ipNet } -var benchResult any -var _ = benchResult -var benchKeys = []Key{ - WorkloadEndpointKey{ - Hostname: "ip-12-23-24-52.cloud.foo.bar.baz", - OrchestratorID: "kubernetes", - WorkloadID: "some-pod-name-12346", - EndpointID: "eth0", - }, - WireguardKey{NodeName: "ip-12-23-24-53.cloud.foo.bar.baz"}, - HostEndpointKey{ - Hostname: "ip-12-23-24-52.cloud.foo.bar.baz", - EndpointID: "eth0", - }, - ResourceKey{ - Name: "projectcalico-default-allow", - Namespace: "default", - Kind: apiv3.KindNetworkPolicy, - }, -} +var ( + benchResult any + _ = benchResult + benchKeys = []Key{ + WorkloadEndpointKey{ + Hostname: "ip-12-23-24-52.cloud.foo.bar.baz", + OrchestratorID: "kubernetes", + WorkloadID: "some-pod-name-12346", + EndpointID: "eth0", + }, + WireguardKey{NodeName: "ip-12-23-24-53.cloud.foo.bar.baz"}, + HostEndpointKey{ + Hostname: "ip-12-23-24-52.cloud.foo.bar.baz", + EndpointID: "eth0", + }, + ResourceKey{ + Name: "projectcalico-default-allow", + Namespace: "default", + Kind: apiv3.KindNetworkPolicy, + }, + } +) func BenchmarkOldKeyFromDefaultPath(b *testing.B) { benchmarkKeyFromDefaultPathImpl(b, OldKeyFromDefaultPath) diff --git a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom/doc.go b/libcalico-go/lib/backend/model/kubeclusternetworkpolicy.go similarity index 78% rename from libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom/doc.go rename to libcalico-go/lib/backend/model/kubeclusternetworkpolicy.go index d6905183e7d..e5788bbb93c 100644 --- a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom/doc.go +++ b/libcalico-go/lib/backend/model/kubeclusternetworkpolicy.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. +// Copyright (c) 2025 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +k8s:deepcopy-gen=package,register +package model -package custom +const ( + KindKubernetesClusterNetworkPolicy = "KubernetesClusterNetworkPolicy" +) diff --git a/libcalico-go/lib/backend/model/model_suite_test.go b/libcalico-go/lib/backend/model/model_suite_test.go index d0760d7ddd2..3e2e49326b7 100644 --- a/libcalico-go/lib/backend/model/model_suite_test.go +++ b/libcalico-go/lib/backend/model/model_suite_test.go @@ -17,16 +17,16 @@ package model_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestModel(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../report/model_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Model Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../report/model_suite.xml" + ginkgo.RunSpecs(t, "Model Suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/backend/model/networkset.go b/libcalico-go/lib/backend/model/networkset.go index 029b29b6fd7..ee491ae1f5d 100644 --- a/libcalico-go/lib/backend/model/networkset.go +++ b/libcalico-go/lib/backend/model/networkset.go @@ -18,6 +18,7 @@ import ( "fmt" "reflect" "regexp" + "strings" log "github.com/sirupsen/logrus" @@ -28,7 +29,7 @@ import ( var ( matchNetworkSet = regexp.MustCompile("^/?calico/v1/netset/([^/]+)$") - typeNetworkSet = reflect.TypeOf(NetworkSet{}) + typeNetworkSet = reflect.TypeFor[NetworkSet]() ) type NetworkSetKey struct { @@ -55,10 +56,25 @@ func (key NetworkSetKey) valueType() (reflect.Type, error) { return typeNetworkSet, nil } +func (key NetworkSetKey) parseValue(rawData []byte) (any, error) { + return parseJSONPointer[NetworkSet](key, rawData) +} + func (key NetworkSetKey) String() string { return fmt.Sprintf("NetworkSet(name=%s)", key.Name) } +// GetNamespace extracts the namespace from a namespaced NetworkSetKey. +// NetworkSets with names in the format "namespace/name" are namespaced. +// Returns empty string for global (non-namespaced) NetworkSets. +func (key NetworkSetKey) GetNamespace() string { + if strings.Contains(key.Name, "/") { + parts := strings.SplitN(key.Name, "/", 2) + return parts[0] + } + return "" +} + type NetworkSetListOptions struct { Name string } @@ -89,6 +105,6 @@ func (options NetworkSetListOptions) KeyFromDefaultPath(path string) Key { type NetworkSet struct { Nets []net.IPNet `json:"nets,omitempty" validate:"omitempty,dive,cidr"` - Labels uniquelabels.Map `json:"labels,omitempty" validate:"omitempty,labels"` + Labels uniquelabels.Map `json:"labels" validate:"omitempty,labels"` ProfileIDs []string `json:"profile_ids,omitempty" validate:"omitempty,dive,name"` } diff --git a/libcalico-go/lib/backend/model/node.go b/libcalico-go/lib/backend/model/node.go index 158f82775bb..d7b4dfe5046 100644 --- a/libcalico-go/lib/backend/model/node.go +++ b/libcalico-go/lib/backend/model/node.go @@ -28,11 +28,11 @@ import ( ) var ( - typeNode = reflect.TypeOf(Node{}) - typeHostMetadata = reflect.TypeOf(HostMetadata{}) - typeOrchRefs = reflect.TypeOf([]OrchRef{}) + typeNode = reflect.TypeFor[Node]() + typeHostMetadata = reflect.TypeFor[HostMetadata]() + typeOrchRefs = reflect.TypeFor[[]OrchRef]() typeHostIp = rawIPType - typeWireguard = reflect.TypeOf(Wireguard{}) + typeWireguard = reflect.TypeFor[Wireguard]() matchHostMetadata = regexp.MustCompile(`^/?calico/v1/host/([^/]+)/metadata$`) matchHostIp = regexp.MustCompile(`^/?calico/v1/host/([^/]+)/bird_ip$`) matchWireguard = regexp.MustCompile(`^/?calico/v1/host/([^/]+)/wireguard$`) @@ -86,6 +86,10 @@ func (key NodeKey) valueType() (reflect.Type, error) { return typeNode, nil } +func (key NodeKey) parseValue(rawData []byte) (any, error) { + return parseJSONPointer[Node](key, rawData) +} + func (key NodeKey) String() string { return fmt.Sprintf("Node(name=%s)", key.Hostname) } @@ -139,6 +143,10 @@ func (key HostMetadataKey) valueType() (reflect.Type, error) { return typeHostMetadata, nil } +func (key HostMetadataKey) parseValue(rawData []byte) (any, error) { + return parseJSONPointer[HostMetadata](key, rawData) +} + func (key HostMetadataKey) String() string { return fmt.Sprintf("Node(name=%s)", key.Hostname) } @@ -187,6 +195,10 @@ func (key HostIPKey) valueType() (reflect.Type, error) { return typeHostIp, nil } +func (key HostIPKey) parseValue(rawData []byte) (any, error) { + return parseRawIP(rawData), nil +} + func (key HostIPKey) String() string { return fmt.Sprintf("Node(name=%s)", key.Hostname) } @@ -212,6 +224,10 @@ func (key OrchRefKey) valueType() (reflect.Type, error) { return typeOrchRefs, nil } +func (key OrchRefKey) parseValue(rawData []byte) (any, error) { + return parseJSONValue[[]OrchRef](key, rawData) +} + func (key OrchRefKey) String() string { return fmt.Sprintf("OrchRefs(nodename=%s)", key.Hostname) } @@ -253,6 +269,10 @@ func (key WireguardKey) valueType() (reflect.Type, error) { return typeWireguard, nil } +func (key WireguardKey) parseValue(rawData []byte) (any, error) { + return parseJSONPointer[Wireguard](key, rawData) +} + func (key WireguardKey) String() string { return fmt.Sprintf("Node(nodename=%s)", key.NodeName) } diff --git a/libcalico-go/lib/backend/model/policy.go b/libcalico-go/lib/backend/model/policy.go index b7508e60cd6..ae8d8a92c0f 100644 --- a/libcalico-go/lib/backend/model/policy.go +++ b/libcalico-go/lib/backend/model/policy.go @@ -21,77 +21,47 @@ import ( "strings" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - log "github.com/sirupsen/logrus" "github.com/projectcalico/calico/libcalico-go/lib/errors" ) var ( - matchPolicy = regexp.MustCompile("^/?calico/v1/policy/tier/([^/]+)/policy/([^/]+)$") - typePolicy = reflect.TypeOf(Policy{}) + matchPolicy = regexp.MustCompile("^/?calico/v1/policy/([^/]+)/([^/]+)/([^/]+)$") + typePolicy = reflect.TypeFor[Policy]() ) -// Policy names with this prefix are staged rather than enforced. We *could* add an additional field to the Policy -// key to relay this information and still allow the names to clash (since we want staged policies with the same name -// as their non-staged counterpart). This approach is less invasive to the existing Felix and dataplane driver code. -const PolicyNamePrefixStaged = "staged:" - -// stagedToEnforcedV1Name converts the v1 name from staged (if it is) to the equivalent enforced name, and returns -// whether the original name indicated a staged policy. -func stagedToEnforcedV1Name(name string) (bool, string) { - var namespace string - var staged bool - if parts := strings.Split(name, "/"); len(parts) == 2 { - namespace, name = parts[0], parts[1] - } - if staged = strings.HasPrefix(name, PolicyNamePrefixStaged); staged { - name = strings.TrimPrefix(name, PolicyNamePrefixStaged) - } - if namespace == "" { - return staged, name - } - return staged, namespace + "/" + name -} - -// PolicyIsStaged returns true if the name of the policy indicates that it is a staged policy. -func PolicyIsStaged(name string) bool { - staged, _ := stagedToEnforcedV1Name(name) - return staged -} - -// PolicyNameLessThan checks if name1 is less that name2. Used for policy sorting. Staged policies are considered to be -// less than the non-staged equivalent. -func PolicyNameLessThan(name1, name2 string) bool { - staged1, name1 := stagedToEnforcedV1Name(name1) - staged2, name2 := stagedToEnforcedV1Name(name2) - - if name1 != name2 { - return name1 < name2 - } - - if staged1 == staged2 { +// KindIsStaged returns true if the the policy kind indicates that it is a staged policy. +func KindIsStaged(kind string) bool { + switch kind { + case apiv3.KindStagedNetworkPolicy, + apiv3.KindStagedGlobalNetworkPolicy, + apiv3.KindStagedKubernetesNetworkPolicy: + return true + default: return false } - - // Names are equal, but staging is not. Staged policies are considered lower than enforced. - return staged1 } type PolicyKey struct { - Name string `json:"-" validate:"required,name"` - Tier string `json:"-" validate:"required,name"` + Name string `json:"-" validate:"required,name"` + Namespace string `json:"-" validate:"omitempty,name"` + Kind string `json:"-" validate:"omitempty,name"` } func (key PolicyKey) defaultPath() (string, error) { - if key.Tier == "" { - return "", errors.ErrorInsufficientIdentifiers{Name: "tier"} - } if key.Name == "" { return "", errors.ErrorInsufficientIdentifiers{Name: "name"} } - e := fmt.Sprintf("/calico/v1/policy/tier/%s/policy/%s", - key.Tier, escapeName(key.Name)) - return e, nil + if key.Kind == "" { + return "", errors.ErrorInsufficientIdentifiers{Name: "kind"} + } + switch key.Kind { + case apiv3.KindNetworkPolicy, apiv3.KindStagedNetworkPolicy, apiv3.KindStagedKubernetesNetworkPolicy: + if key.Namespace == "" { + return "", errors.ErrorInsufficientIdentifiers{Name: "namespace"} + } + } + return fmt.Sprintf("/calico/v1/policy/%s/%s/%s", key.Kind, key.Namespace, key.Name), nil } func (key PolicyKey) defaultDeletePath() (string, error) { @@ -99,57 +69,24 @@ func (key PolicyKey) defaultDeletePath() (string, error) { } func (key PolicyKey) defaultDeleteParentPaths() ([]string, error) { - return nil, nil + return nil, fmt.Errorf("defaultDeleteParentPaths is not implemented for PolicyKey") } func (key PolicyKey) valueType() (reflect.Type, error) { return typePolicy, nil } -func (key PolicyKey) String() string { - return fmt.Sprintf("Policy(tier=%s, name=%s)", key.Tier, key.Name) -} - -type PolicyListOptions struct { - Name string - Tier string -} - -func (options PolicyListOptions) defaultPathRoot() string { - k := "/calico/v1/policy/tier" - if options.Tier == "" { - return k - } - k = k + fmt.Sprintf("/%s/policy", options.Tier) - if options.Name == "" { - return k - } - k = k + fmt.Sprintf("/%s", escapeName(options.Name)) - return k +func (key PolicyKey) parseValue(rawData []byte) (any, error) { + return parseJSONPointer[Policy](key, rawData) } -func (options PolicyListOptions) KeyFromDefaultPath(path string) Key { - log.Debugf("Get Policy key from %s", path) - r := matchPolicy.FindAllStringSubmatch(path, -1) - if len(r) != 1 { - log.Debugf("Didn't match regex") - return nil - } - tier := r[0][1] - name := unescapeName(r[0][2]) - if options.Tier != "" && tier != options.Tier { - log.Infof("Didn't match tier %s != %s", options.Tier, tier) - return nil - } - if options.Name != "" && name != options.Name { - log.Debugf("Didn't match name %s != %s", options.Name, name) - return nil - } - return PolicyKey{Tier: tier, Name: name} +func (key PolicyKey) String() string { + return fmt.Sprintf("Policy(Name=%s, Namespace=%s, Kind=%s)", key.Name, key.Namespace, key.Kind) } type Policy struct { Namespace string `json:"namespace,omitempty" validate:"omitempty"` + Tier string `json:"tier,omitempty" validate:"omitempty"` Order *float64 `json:"order,omitempty" validate:"omitempty"` InboundRules []Rule `json:"inbound_rules,omitempty" validate:"omitempty,dive"` OutboundRules []Rule `json:"outbound_rules,omitempty" validate:"omitempty,dive"` @@ -165,6 +102,9 @@ type Policy struct { func (p Policy) String() string { parts := make([]string, 0) + if p.Tier != "" { + parts = append(parts, fmt.Sprintf("tier:%v", p.Tier)) + } if p.Order != nil { parts = append(parts, fmt.Sprintf("order:%v", *p.Order)) } @@ -191,3 +131,116 @@ func (p Policy) String() string { } return strings.Join(parts, ",") } + +// parseLegacyPolicyName builds a legacy policy key from the given name. +// Legacy policy key names included the namespace and name of the policy, with +// hints of the kind that can be used to reconstruct the full key. +// +// Legacy policy keys will be sent by Typha versions prior to v3.32.0. +func parseLegacyPolicyName(tier, name string) LegacyPolicyKey { + // First, split based on "/" to see if we have a namespace. + parts := strings.SplitN(name, "/", 2) + if len(parts) == 2 { + // We have a namespace. This can be one of a few different kinds: + // - knp.default. -> KubernetesNetworkPolicy + // - staged: -> StagedNetworkPolicy + // - staged:knp.default. -> StagedKubernetesNetworkPolicy + // - -> NetworkPolicy + namespace := parts[0] + policyName := parts[1] + + // Next, try to infer the kind from the policy name. + kind := apiv3.KindNetworkPolicy + if strings.HasPrefix(policyName, "knp.default.") { + kind = KindKubernetesNetworkPolicy + policyName = strings.TrimPrefix(policyName, "knp.default.") + } else if strings.HasPrefix(policyName, "staged:knp.default.") { + kind = apiv3.KindStagedKubernetesNetworkPolicy + policyName = strings.TrimPrefix(policyName, "staged:knp.default.") + } else if strings.HasPrefix(policyName, "staged:") { + kind = apiv3.KindStagedNetworkPolicy + policyName = strings.TrimPrefix(policyName, "staged:") + } + + return LegacyPolicyKey{ + PolicyKey: PolicyKey{ + Name: policyName, + Namespace: namespace, + Kind: kind, + }, + Tier: tier, + } + } + + // No namespace, so this is a global policy. It can be one of: + // - -> GlobalNetworkPolicy + // - staged: -> StagedGlobalNetworkPolicy + // - kcnp. -> KubernetesClusterNetworkPolicy + kind := apiv3.KindGlobalNetworkPolicy + policyName := name + if strings.HasPrefix(policyName, "staged:") { + kind = apiv3.KindStagedGlobalNetworkPolicy + policyName = strings.TrimPrefix(policyName, "staged:") + } else if strings.HasPrefix(policyName, "kcnp.") { + kind = KindKubernetesClusterNetworkPolicy + policyName = strings.TrimPrefix(policyName, "kcnp.") + } + return LegacyPolicyKey{ + PolicyKey: PolicyKey{ + Name: policyName, + Kind: kind, + }, + Tier: tier, + } +} + +// LegacyPolicyKey represents a Policy key sent to us by an older version of Typha that still includes the +// tier in the key name. It wraps a modern PolicyKey and adds the tier so that we can reconstruct the full Policy object +// when parsing the value. +type LegacyPolicyKey struct { + // Wraps a PolicyKey with additional information needed to handle legacy keys. + PolicyKey + + // Tier is not part of the etcd key, but is included here to enable compatibility + // with older versions of Typha. Older Typha versions included the tier in the key name but not the value, + // so we need to capture it here when parsing legacy keys and use it when building the Value (which is where + // newer Calico versions expect to find it). + Tier string `json:"-" validate:"-"` +} + +func (l LegacyPolicyKey) defaultPath() (string, error) { + panic("LegacyPolicyKey should not be used except to generate a modern PolicyKey") +} + +func (l LegacyPolicyKey) defaultDeletePath() (string, error) { + panic("LegacyPolicyKey should not be used except to generate a modern PolicyKey") +} + +func (l LegacyPolicyKey) defaultDeleteParentPaths() ([]string, error) { + panic("LegacyPolicyKey should not be used except to generate a modern PolicyKey") +} + +func (l LegacyPolicyKey) valueType() (reflect.Type, error) { + return typePolicy, nil +} + +func (l LegacyPolicyKey) parseValue(data []byte) (any, error) { + p, err := l.PolicyKey.parseValue(data) + if err != nil { + return nil, err + } + p.(*Policy).Tier = l.Tier + return p, nil +} + +func (l LegacyPolicyKey) String() string { + return fmt.Sprintf("LegacyPolicy(Name=%s, Namespace=%s, Kind=%s, Tier=%s)", l.Name, l.Namespace, l.Kind, l.Tier) +} + +func (key LegacyPolicyKey) Upgrade() Key { + return PolicyKey{ + Name: key.Name, + Namespace: key.Namespace, + Kind: key.Kind, + } +} diff --git a/libcalico-go/lib/backend/model/policy_test.go b/libcalico-go/lib/backend/model/policy_test.go index 317c089f74c..ef1f6775051 100644 --- a/libcalico-go/lib/backend/model/policy_test.go +++ b/libcalico-go/lib/backend/model/policy_test.go @@ -15,7 +15,7 @@ package model_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -27,8 +27,8 @@ var _ = Describe("Policy functions", func() { order := 10.5 p := model.Policy{ Order: &order, - InboundRules: []model.Rule{model.Rule{Action: "Deny"}}, - OutboundRules: []model.Rule{model.Rule{Action: "Allow"}}, + InboundRules: []model.Rule{{Action: "Deny"}}, + OutboundRules: []model.Rule{{Action: "Allow"}}, Selector: "apples=='oranges'", DoNotTrack: false, PreDNAT: true, @@ -39,23 +39,8 @@ var _ = Describe("Policy functions", func() { Expect(p.String()).To(Equal(`order:10.5,selector:"apples=='oranges'",inbound:Deny,outbound:Allow,untracked:false,pre_dnat:true,apply_on_forward:true,types:Ingress;Egress,performance_hints:[AssumeNeededOnEveryNode]`)) }) - It("Policy should identify as staged by name", func() { - Expect(model.PolicyIsStaged("staged:policy1")).To(BeTrue()) - Expect(model.PolicyIsStaged("policy1")).To(BeFalse()) - }) - - It("Staged policy name should be less than non-staged equivalent", func() { - Expect(model.PolicyNameLessThan("tier1.policy0", "tier1.policy1")).To(BeTrue()) - Expect(model.PolicyNameLessThan("tier1.policy1", "tier1.policy0")).To(BeFalse()) - Expect(model.PolicyNameLessThan("staged:tier1.policy1", "tier1.policy1")).To(BeTrue()) - Expect(model.PolicyNameLessThan("ns1/staged:knp.default.policy1", "ns1/knp.default.policy1")).To(BeTrue()) - Expect(model.PolicyNameLessThan("knp.default.policy1", "staged:knp.default.policy1")).To(BeFalse()) - Expect(model.PolicyNameLessThan("ns1/staged:tier2.policy0", "ns1/tier2.policy1")).To(BeTrue()) - Expect(model.PolicyNameLessThan("ns1/staged:tier2.policy0", "ns1/tier2.policy0")).To(BeTrue()) - Expect(model.PolicyNameLessThan("ns1/tier2.policy1", "ns1/staged:tier2.policy0")).To(BeFalse()) - Expect(model.PolicyNameLessThan("ns1/staged:tier2.policy0", "ns1/staged:tier2.policy1")).To(BeTrue()) - Expect(model.PolicyNameLessThan("ns1/staged:tier2.policy1", "ns1/staged:tier2.policy0")).To(BeFalse()) - Expect(model.PolicyNameLessThan("ns1/staged:tier2.policy1", "ns1/staged:tier2.policy1")).To(BeFalse()) - Expect(model.PolicyNameLessThan("ns1/staged:tier2.policy1", "ns2/staged:tier1.policy1")).To(BeTrue()) + It("Policy should identify as staged by kind", func() { + Expect(model.KindIsStaged(v3.KindStagedNetworkPolicy)).To(BeTrue()) + Expect(model.KindIsStaged(v3.KindNetworkPolicy)).To(BeFalse()) }) }) diff --git a/libcalico-go/lib/backend/model/profile.go b/libcalico-go/lib/backend/model/profile.go index 27c57bee66a..832b008d781 100644 --- a/libcalico-go/lib/backend/model/profile.go +++ b/libcalico-go/lib/backend/model/profile.go @@ -27,7 +27,7 @@ import ( var ( matchProfile = regexp.MustCompile("^/?calico/v1/policy/profile/([^/]+)/(rules|labels)$") - typeProfile = reflect.TypeOf(Profile{}) + typeProfile = reflect.TypeFor[Profile]() ) // The profile key actually returns the common parent of the three separate entries. @@ -57,6 +57,10 @@ func (key ProfileKey) valueType() (reflect.Type, error) { return typeProfile, nil } +func (key ProfileKey) parseValue(rawData []byte) (any, error) { + return parseJSONPointer[Profile](key, rawData) +} + func (key ProfileKey) String() string { return fmt.Sprintf("Profile(name=%s)", key.Name) } @@ -72,7 +76,11 @@ func (key ProfileRulesKey) defaultPath() (string, error) { } func (key ProfileRulesKey) valueType() (reflect.Type, error) { - return reflect.TypeOf(ProfileRules{}), nil + return reflect.TypeFor[ProfileRules](), nil +} + +func (key ProfileRulesKey) parseValue(rawData []byte) (any, error) { + return parseJSONPointer[ProfileRules](key, rawData) } func (key ProfileRulesKey) String() string { @@ -90,7 +98,11 @@ func (key ProfileLabelsKey) defaultPath() (string, error) { } func (key ProfileLabelsKey) valueType() (reflect.Type, error) { - return reflect.TypeOf(map[string]string{}), nil + return reflect.TypeFor[map[string]string](), nil +} + +func (key ProfileLabelsKey) parseValue(rawData []byte) (any, error) { + return parseJSONValue[map[string]string](key, rawData) } func (key ProfileLabelsKey) String() string { diff --git a/libcalico-go/lib/backend/model/resource.go b/libcalico-go/lib/backend/model/resource.go index f54e5f55d2f..62337817a4f 100644 --- a/libcalico-go/lib/backend/model/resource.go +++ b/libcalico-go/lib/backend/model/resource.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2016-2026 Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -26,18 +26,48 @@ import ( discovery "k8s.io/api/discovery/v1" "k8s.io/apimachinery/pkg/labels" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/namespace" ) +type resourceInfo interface { + TypeOf() reflect.Type + Plural() string + KindLower() string + Kind() string + ParseValue(key ResourceKey, data []byte) (any, error) +} + // Name/type information about a single resource. -type resourceInfo struct { +type resourceInfoTyped[V any] struct { typeOf reflect.Type plural string kindLower string kind string } +var _ resourceInfo = &resourceInfoTyped[any]{} + +func (ri resourceInfoTyped[V]) TypeOf() reflect.Type { + return ri.typeOf +} + +func (ri resourceInfoTyped[V]) ParseValue(key ResourceKey, data []byte) (any, error) { + return parseJSONPointer[V](key, data) +} + +func (ri resourceInfoTyped[V]) Plural() string { + return ri.plural +} + +func (ri resourceInfoTyped[V]) KindLower() string { + return ri.kindLower +} + +func (ri resourceInfoTyped[V]) Kind() string { + return ri.kind +} + var ( matchGlobalResource = regexp.MustCompile("^/calico/resources/v3/projectcalico[.]org/([^/]+)/([^/]+)$") matchNamespacedResource = regexp.MustCompile("^/calico/resources/v3/projectcalico[.]org/([^/]+)/([^/]+)/([^/]+)$") @@ -45,11 +75,12 @@ var ( resourceInfoByPlural = make(map[string]resourceInfo) ) -func registerResourceInfo(kind string, plural string, typeOf reflect.Type) { +func registerResourceInfo[V any](kind string, plural string) { kindLower := strings.ToLower(kind) plural = strings.ToLower(plural) - ri := resourceInfo{ - typeOf: typeOf, + var v V + ri := resourceInfoTyped[V]{ + typeOf: reflect.TypeOf(v), kindLower: kindLower, kind: kind, plural: plural, @@ -67,145 +98,40 @@ func AllResourcePlurals() []string { } func init() { - registerResourceInfo( - apiv3.KindBGPPeer, - "bgppeers", - reflect.TypeOf(apiv3.BGPPeer{}), - ) - registerResourceInfo( - apiv3.KindBGPConfiguration, - "bgpconfigurations", - reflect.TypeOf(apiv3.BGPConfiguration{}), - ) - registerResourceInfo( - apiv3.KindClusterInformation, - "clusterinformations", - reflect.TypeOf(apiv3.ClusterInformation{}), - ) - registerResourceInfo( - apiv3.KindFelixConfiguration, - "felixconfigurations", - reflect.TypeOf(apiv3.FelixConfiguration{}), - ) - registerResourceInfo( - apiv3.KindGlobalNetworkPolicy, - "globalnetworkpolicies", - reflect.TypeOf(apiv3.GlobalNetworkPolicy{}), - ) - registerResourceInfo( - apiv3.KindStagedGlobalNetworkPolicy, - "stagedglobalnetworkpolicies", - reflect.TypeOf(apiv3.StagedGlobalNetworkPolicy{}), - ) - registerResourceInfo( - apiv3.KindHostEndpoint, - "hostendpoints", - reflect.TypeOf(apiv3.HostEndpoint{}), - ) - registerResourceInfo( - apiv3.KindGlobalNetworkSet, - "globalnetworksets", - reflect.TypeOf(apiv3.GlobalNetworkSet{}), - ) - registerResourceInfo( - KindKubernetesAdminNetworkPolicy, - "kubernetesadminnetworkpolicies", - reflect.TypeOf(apiv3.GlobalNetworkPolicy{}), - ) - registerResourceInfo( - KindKubernetesBaselineAdminNetworkPolicy, - "kubernetesbaselineadminnetworkpolicies", - reflect.TypeOf(apiv3.GlobalNetworkPolicy{}), - ) - registerResourceInfo( - apiv3.KindIPPool, - "ippools", - reflect.TypeOf(apiv3.IPPool{}), - ) - registerResourceInfo( - apiv3.KindIPReservation, - "ipreservations", - reflect.TypeOf(apiv3.IPReservation{}), - ) - registerResourceInfo( - apiv3.KindNetworkPolicy, - "networkpolicies", - reflect.TypeOf(apiv3.NetworkPolicy{}), - ) - registerResourceInfo( - apiv3.KindStagedNetworkPolicy, - "stagednetworkpolicies", - reflect.TypeOf(apiv3.StagedNetworkPolicy{}), - ) - registerResourceInfo( - KindKubernetesNetworkPolicy, - "kubernetesnetworkpolicies", - reflect.TypeOf(apiv3.NetworkPolicy{}), - ) - registerResourceInfo( - apiv3.KindStagedKubernetesNetworkPolicy, - "stagedkubernetesnetworkpolicies", - reflect.TypeOf(apiv3.StagedKubernetesNetworkPolicy{}), - ) - registerResourceInfo( - KindKubernetesEndpointSlice, - "kubernetesendpointslices", - reflect.TypeOf(discovery.EndpointSlice{}), - ) - registerResourceInfo( - apiv3.KindNetworkSet, - "networksets", - reflect.TypeOf(apiv3.NetworkSet{}), - ) - registerResourceInfo( - apiv3.KindTier, - "tiers", - reflect.TypeOf(apiv3.Tier{}), - ) - registerResourceInfo( - libapiv3.KindNode, - "nodes", - reflect.TypeOf(libapiv3.Node{}), - ) - registerResourceInfo( - apiv3.KindCalicoNodeStatus, - "caliconodestatuses", - reflect.TypeOf(apiv3.CalicoNodeStatus{}), - ) - registerResourceInfo( - apiv3.KindProfile, - "profiles", - reflect.TypeOf(apiv3.Profile{}), - ) - registerResourceInfo( - libapiv3.KindWorkloadEndpoint, - "workloadendpoints", - reflect.TypeOf(libapiv3.WorkloadEndpoint{}), - ) - registerResourceInfo( - libapiv3.KindIPAMConfig, - "ipamconfigs", - reflect.TypeOf(libapiv3.IPAMConfig{}), - ) - registerResourceInfo( - apiv3.KindKubeControllersConfiguration, - "kubecontrollersconfigurations", - reflect.TypeOf(apiv3.KubeControllersConfiguration{})) - registerResourceInfo( - KindKubernetesService, - "kubernetesservice", - reflect.TypeOf(kapiv1.Service{}), - ) - registerResourceInfo( - libapiv3.KindBlockAffinity, - "blockaffinities", - reflect.TypeOf(libapiv3.BlockAffinity{}), - ) - registerResourceInfo( - apiv3.KindBGPFilter, - "BGPFilters", - reflect.TypeOf(apiv3.BGPFilter{}), - ) + // Register projectcalico.org/v3 resources. + registerResourceInfo[apiv3.BGPPeer](apiv3.KindBGPPeer, "bgppeers") + registerResourceInfo[apiv3.BGPConfiguration](apiv3.KindBGPConfiguration, "bgpconfigurations") + registerResourceInfo[apiv3.ClusterInformation](apiv3.KindClusterInformation, "clusterinformations") + registerResourceInfo[apiv3.FelixConfiguration](apiv3.KindFelixConfiguration, "felixconfigurations") + registerResourceInfo[apiv3.GlobalNetworkPolicy](apiv3.KindGlobalNetworkPolicy, "globalnetworkpolicies") + registerResourceInfo[apiv3.StagedGlobalNetworkPolicy](apiv3.KindStagedGlobalNetworkPolicy, "stagedglobalnetworkpolicies") + registerResourceInfo[apiv3.HostEndpoint](apiv3.KindHostEndpoint, "hostendpoints") + registerResourceInfo[apiv3.GlobalNetworkSet](apiv3.KindGlobalNetworkSet, "globalnetworksets") + registerResourceInfo[apiv3.IPPool](apiv3.KindIPPool, "ippools") + registerResourceInfo[apiv3.IPReservation](apiv3.KindIPReservation, "ipreservations") + registerResourceInfo[apiv3.NetworkPolicy](apiv3.KindNetworkPolicy, "networkpolicies") + registerResourceInfo[apiv3.StagedNetworkPolicy](apiv3.KindStagedNetworkPolicy, "stagednetworkpolicies") + registerResourceInfo[apiv3.StagedKubernetesNetworkPolicy](apiv3.KindStagedKubernetesNetworkPolicy, "stagedkubernetesnetworkpolicies") + registerResourceInfo[discovery.EndpointSlice](KindKubernetesEndpointSlice, "kubernetesendpointslices") + registerResourceInfo[apiv3.NetworkSet](apiv3.KindNetworkSet, "networksets") + registerResourceInfo[apiv3.Tier](apiv3.KindTier, "tiers") + registerResourceInfo[apiv3.CalicoNodeStatus](apiv3.KindCalicoNodeStatus, "caliconodestatuses") + registerResourceInfo[apiv3.Profile](apiv3.KindProfile, "profiles") + registerResourceInfo[apiv3.KubeControllersConfiguration](apiv3.KindKubeControllersConfiguration, "kubecontrollersconfigurations") + registerResourceInfo[apiv3.BGPFilter](apiv3.KindBGPFilter, "BGPFilters") + registerResourceInfo[apiv3.IPAMConfiguration](apiv3.KindIPAMConfiguration, "ipamconfigurations") + + // Register libcalico-go/v3 resources. + registerResourceInfo[internalapi.Node](internalapi.KindNode, "nodes") + registerResourceInfo[internalapi.WorkloadEndpoint](internalapi.KindWorkloadEndpoint, "workloadendpoints") + registerResourceInfo[internalapi.IPAMConfig](internalapi.KindIPAMConfig, "ipamconfigs") + registerResourceInfo[internalapi.BlockAffinity](internalapi.KindBlockAffinity, "blockaffinities") + registerResourceInfo[internalapi.LiveMigration](internalapi.KindLiveMigration, "livemigrations") + + // Register Kubernetes resources. + registerResourceInfo[kapiv1.Service](KindKubernetesService, "kubernetesservice") + registerResourceInfo[apiv3.NetworkPolicy](KindKubernetesNetworkPolicy, "kubernetesnetworkpolicies") + registerResourceInfo[apiv3.GlobalNetworkPolicy](KindKubernetesClusterNetworkPolicy, "kubernetesclusternetworkpolicies") } type ResourceKey struct { @@ -227,9 +153,9 @@ func (key ResourceKey) defaultDeletePath() (string, error) { return "", fmt.Errorf("couldn't convert key: %+v", key) } if namespace.IsNamespaced(key.Kind) { - return fmt.Sprintf("/calico/resources/v3/projectcalico.org/%s/%s/%s", ri.plural, key.Namespace, key.Name), nil + return fmt.Sprintf("/calico/resources/v3/projectcalico.org/%s/%s/%s", ri.Plural(), key.Namespace, key.Name), nil } - return fmt.Sprintf("/calico/resources/v3/projectcalico.org/%s/%s", ri.plural, key.Name), nil + return fmt.Sprintf("/calico/resources/v3/projectcalico.org/%s/%s", ri.Plural(), key.Name), nil } func (key ResourceKey) defaultDeleteParentPaths() ([]string, error) { @@ -239,9 +165,47 @@ func (key ResourceKey) defaultDeleteParentPaths() ([]string, error) { func (key ResourceKey) valueType() (reflect.Type, error) { ri, ok := resourceInfoByKindLower[strings.ToLower(key.Kind)] if !ok { - return nil, fmt.Errorf("unexpected resource kind: %s", key.Kind) + return nil, fmt.Errorf("unknown resource kind: %s", key.Kind) } - return ri.typeOf, nil + return ri.TypeOf(), nil +} + +func (key ResourceKey) parseValue(rawData []byte) (any, error) { + ri, ok := resourceInfoByKindLower[strings.ToLower(key.Kind)] + if !ok { + return nil, fmt.Errorf("unknown resource kind: %s", key.Kind) + } + v, err := ri.ParseValue(key, rawData) + if err != nil { + return nil, err + } + + // Special case handling for network policy names to handle migration of + // names based on tier. + switch policy := v.(type) { + case *apiv3.NetworkPolicy: + policy.Name, policy.Annotations, err = determinePolicyName(policy.Name, policy.Spec.Tier, policy.Annotations) + if err != nil { + return nil, err + } + case *apiv3.GlobalNetworkPolicy: + policy.Name, policy.Annotations, err = determinePolicyName(policy.Name, policy.Spec.Tier, policy.Annotations) + if err != nil { + return nil, err + } + case *apiv3.StagedNetworkPolicy: + policy.Name, policy.Annotations, err = determinePolicyName(policy.Name, policy.Spec.Tier, policy.Annotations) + if err != nil { + return nil, err + } + case *apiv3.StagedGlobalNetworkPolicy: + policy.Name, policy.Annotations, err = determinePolicyName(policy.Name, policy.Spec.Tier, policy.Annotations) + if err != nil { + return nil, err + } + } + + return v, err } func (key ResourceKey) String() string { @@ -251,6 +215,11 @@ func (key ResourceKey) String() string { return fmt.Sprintf("%s(%s)", key.Kind, key.Name) } +// GetNamespace returns the namespace field of the ResourceKey. +func (key ResourceKey) GetNamespace() string { + return key.Namespace +} + type ResourceListOptions struct { // The name of the resource. Name string @@ -302,7 +271,7 @@ func (options ResourceListOptions) KeyFromDefaultPath(path string) Key { if len(options.Kind) == 0 { panic("Kind must be specified in List option but is not") } - if kindPlural != ri.plural { + if kindPlural != ri.Plural() { log.Debugf("Didn't match kind %s != %s", kindPlural, kindPlural) return nil } @@ -330,8 +299,8 @@ func (options ResourceListOptions) KeyFromDefaultPath(path string) Key { } kindPlural := r[0][1] name := r[0][2] - if kindPlural != ri.plural { - log.Debugf("Didn't match kind %s != %s", kindPlural, ri.plural) + if kindPlural != ri.Plural() { + log.Debugf("Didn't match kind %s != %s", kindPlural, ri.Plural()) return nil } if len(options.Name) != 0 { @@ -352,7 +321,7 @@ func (options ResourceListOptions) defaultPathRoot() string { log.Panic("Unexpected resource kind: " + options.Kind) } - k := "/calico/resources/v3/projectcalico.org/" + ri.plural + k := "/calico/resources/v3/projectcalico.org/" + ri.Plural() if namespace.IsNamespaced(options.Kind) { if options.Namespace == "" { return k diff --git a/libcalico-go/lib/backend/model/rule.go b/libcalico-go/lib/backend/model/rule.go index b99942cff98..0a3d5b118df 100644 --- a/libcalico-go/lib/backend/model/rule.go +++ b/libcalico-go/lib/backend/model/rule.go @@ -84,8 +84,6 @@ type Rule struct { // These fields allow us to pass through application layer selectors from the V3 datamodel. HTTPMatch *HTTPMatch `json:"http,omitempty" validate:"omitempty"` - LogPrefix string `json:"log_prefix,omitempty" validate:"omitempty"` - Metadata *RuleMetadata `json:"metadata,omitempty" validate:"omitempty"` } diff --git a/libcalico-go/lib/backend/model/rule_test.go b/libcalico-go/lib/backend/model/rule_test.go index 15e068c5651..d3d7214b7f7 100644 --- a/libcalico-go/lib/backend/model/rule_test.go +++ b/libcalico-go/lib/backend/model/rule_test.go @@ -17,7 +17,7 @@ package model_test import ( "fmt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -116,7 +116,6 @@ var ruleStringTests = []ruleTest{ var _ = Describe("Rule", func() { for _, test := range ruleStringTests { - test := test // For closure Describe(fmt.Sprintf("%#v", test.rule), func() { It("should stringify as "+test.expectedOutput, func() { Expect(fmt.Sprintf("%s", test.rule)).To(Equal(test.expectedOutput)) diff --git a/libcalico-go/lib/backend/model/statusreports.go b/libcalico-go/lib/backend/model/statusreports.go index c770ac10938..b0455cd53bd 100644 --- a/libcalico-go/lib/backend/model/statusreports.go +++ b/libcalico-go/lib/backend/model/statusreports.go @@ -28,7 +28,7 @@ import ( var ( matchActiveStatusReport = regexp.MustCompile("^/?calico/felix/v2/([^/]+)/host/([^/]+)/status$") matchLastStatusReport = regexp.MustCompile("^/?calico/felix/v2/([^/]+)/host/([^/]+)/last_reported_status") - typeStatusReport = reflect.TypeOf(StatusReport{}) + typeStatusReport = reflect.TypeFor[StatusReport]() ) type ActiveStatusReportKey struct { @@ -62,6 +62,10 @@ func (key ActiveStatusReportKey) valueType() (reflect.Type, error) { return typeStatusReport, nil } +func (key ActiveStatusReportKey) parseValue(rawData []byte) (any, error) { + return parseJSONPointer[StatusReport](key, rawData) +} + func (key ActiveStatusReportKey) String() string { return fmt.Sprintf("StatusReport(hostname=%s)", key.Hostname) } @@ -135,6 +139,10 @@ func (key LastStatusReportKey) valueType() (reflect.Type, error) { return typeStatusReport, nil } +func (key LastStatusReportKey) parseValue(rawData []byte) (any, error) { + return parseJSONPointer[StatusReport](key, rawData) +} + func (key LastStatusReportKey) String() string { return fmt.Sprintf("StatusReport(hostname=%s)", key.Hostname) } diff --git a/libcalico-go/lib/backend/model/tier.go b/libcalico-go/lib/backend/model/tier.go index 122fad66490..53a42e6a981 100644 --- a/libcalico-go/lib/backend/model/tier.go +++ b/libcalico-go/lib/backend/model/tier.go @@ -27,7 +27,7 @@ import ( var ( matchTier = regexp.MustCompile("^/?calico/v1/policy/tier/([^/]+)/metadata$") - typeTier = reflect.TypeOf(Tier{}) + typeTier = reflect.TypeFor[Tier]() ) type TierKey struct { @@ -55,6 +55,10 @@ func (key TierKey) valueType() (reflect.Type, error) { return typeTier, nil } +func (key TierKey) parseValue(rawData []byte) (any, error) { + return parseJSONPointer[Tier](key, rawData) +} + func (key TierKey) String() string { return fmt.Sprintf("Tier(name=%s)", key.Name) } diff --git a/libcalico-go/lib/backend/model/workloadendpoint.go b/libcalico-go/lib/backend/model/workloadendpoint.go index 9d7acece824..d2adebecb41 100644 --- a/libcalico-go/lib/backend/model/workloadendpoint.go +++ b/libcalico-go/lib/backend/model/workloadendpoint.go @@ -18,11 +18,12 @@ import ( "fmt" "reflect" "regexp" + "strings" log "github.com/sirupsen/logrus" "github.com/projectcalico/calico/lib/std/uniquelabels" - v3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/errors" "github.com/projectcalico/calico/libcalico-go/lib/net" ) @@ -82,7 +83,11 @@ func (key WorkloadEndpointKey) defaultDeleteParentPaths() ([]string, error) { } func (key WorkloadEndpointKey) valueType() (reflect.Type, error) { - return reflect.TypeOf(WorkloadEndpoint{}), nil + return reflect.TypeFor[WorkloadEndpoint](), nil +} + +func (key WorkloadEndpointKey) parseValue(rawData []byte) (any, error) { + return parseJSONPointer[WorkloadEndpoint](key, rawData) } func (key WorkloadEndpointKey) String() string { @@ -90,6 +95,17 @@ func (key WorkloadEndpointKey) String() string { key.Hostname, key.OrchestratorID, key.WorkloadID, key.EndpointID) } +// GetNamespace extracts and returns the namespace from the WorkloadID. +// WorkloadID is expected to be in the format "namespace/name". +// Returns an empty string if the WorkloadID doesn't contain a namespace. +func (key WorkloadEndpointKey) GetNamespace() string { + parts := strings.SplitN(key.WorkloadID, "/", 2) + if len(parts) == 2 { + return parts[0] + } + return "" +} + var _ EndpointKey = WorkloadEndpointKey{} type WorkloadEndpointListOptions struct { @@ -165,7 +181,7 @@ type WorkloadEndpoint struct { IPv6Nets []net.IPNet `json:"ipv6_nets"` IPv4NAT []IPNAT `json:"ipv4_nat,omitempty"` IPv6NAT []IPNAT `json:"ipv6_nat,omitempty"` - Labels uniquelabels.Map `json:"labels,omitempty"` + Labels uniquelabels.Map `json:"labels"` IPv4Gateway *net.IP `json:"ipv4_gateway,omitempty" validate:"omitempty,ipv4"` IPv6Gateway *net.IP `json:"ipv6_gateway,omitempty" validate:"omitempty,ipv6"` Ports []EndpointPort `json:"ports,omitempty" validate:"dive"` @@ -201,4 +217,4 @@ type IPNAT struct { ExtIP net.IP `json:"ext_ip" validate:"ip"` } -type QoSControls = v3.QoSControls +type QoSControls = internalapi.QoSControls diff --git a/libcalico-go/lib/backend/model/workloadendpointstatus.go b/libcalico-go/lib/backend/model/workloadendpointstatus.go index 2ca4c84a57c..f8bf43bd207 100644 --- a/libcalico-go/lib/backend/model/workloadendpointstatus.go +++ b/libcalico-go/lib/backend/model/workloadendpointstatus.go @@ -89,7 +89,11 @@ func (key WorkloadEndpointStatusKey) defaultDeleteParentPaths() ([]string, error } func (key WorkloadEndpointStatusKey) valueType() (reflect.Type, error) { - return reflect.TypeOf(WorkloadEndpointStatus{}), nil + return reflect.TypeFor[WorkloadEndpointStatus](), nil +} + +func (key WorkloadEndpointStatusKey) parseValue(rawData []byte) (any, error) { + return parseJSONPointer[WorkloadEndpointStatus](key, rawData) } func (key WorkloadEndpointStatusKey) String() string { diff --git a/libcalico-go/lib/backend/syncersv1/bgpsyncer/bgpsyncer.go b/libcalico-go/lib/backend/syncersv1/bgpsyncer/bgpsyncer.go index 9c7ed0d3174..31fc1d62173 100644 --- a/libcalico-go/lib/backend/syncersv1/bgpsyncer/bgpsyncer.go +++ b/libcalico-go/lib/backend/syncersv1/bgpsyncer/bgpsyncer.go @@ -18,7 +18,7 @@ import ( apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/backend/syncersv1/updateprocessors" @@ -40,7 +40,7 @@ func New(client api.Client, callbacks api.SyncerCallbacks, node string, cfg apic ListInterface: model.ResourceListOptions{Kind: apiv3.KindBGPConfiguration}, }, { - ListInterface: model.ResourceListOptions{Kind: libapiv3.KindNode}, + ListInterface: model.ResourceListOptions{Kind: internalapi.KindNode}, }, { ListInterface: model.ResourceListOptions{Kind: apiv3.KindBGPPeer}, diff --git a/libcalico-go/lib/backend/syncersv1/bgpsyncer/bgpsyncer_e2e_test.go b/libcalico-go/lib/backend/syncersv1/bgpsyncer/bgpsyncer_e2e_test.go index e8bc4cbd320..89896e6d3cb 100644 --- a/libcalico-go/lib/backend/syncersv1/bgpsyncer/bgpsyncer_e2e_test.go +++ b/libcalico-go/lib/backend/syncersv1/bgpsyncer/bgpsyncer_e2e_test.go @@ -18,14 +18,14 @@ import ( "context" "fmt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/encap" @@ -114,13 +114,13 @@ var _ = testutils.E2eDatastoreDescribe("BGP syncer tests", testutils.DatastoreAl syncTester.ExpectCacheSize(expectedCacheSize) syncTester.ExpectPath("/calico/resources/v3/projectcalico.org/bgpconfigurations/default") - var node *libapiv3.Node + var node *internalapi.Node if config.Spec.DatastoreType == apiconfig.Kubernetes { // For Kubernetes, update the existing node config to have some BGP configuration. By("Configuring a node with BGP configuration") node, err = c.Nodes().Get(ctx, "127.0.0.1", options.GetOptions{}) Expect(err).NotTo(HaveOccurred()) - node.Spec.BGP = &libapiv3.NodeBGPSpec{ + node.Spec.BGP = &internalapi.NodeBGPSpec{ IPv4Address: "1.2.3.4/24", IPv6Address: "aa:bb::cc/120", } @@ -132,10 +132,10 @@ var _ = testutils.E2eDatastoreDescribe("BGP syncer tests", testutils.DatastoreAl By("Creating a node with BGP configuration") node, err = c.Nodes().Create( ctx, - &libapiv3.Node{ + &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{Name: "127.0.0.1"}, - Spec: libapiv3.NodeSpec{ - BGP: &libapiv3.NodeBGPSpec{ + Spec: internalapi.NodeSpec{ + BGP: &internalapi.NodeBGPSpec{ IPv4Address: "1.2.3.4/24", IPv6Address: "aa:bb::cc/120", }, @@ -293,7 +293,7 @@ var _ = testutils.E2eDatastoreDescribe("BGP syncer tests", testutils.DatastoreAl // The syncer only monitors affine blocks for one host, so IP allocations for a different // host should not result in updates. hostname := "not-this-host" - node, err = c.Nodes().Create(ctx, &libapiv3.Node{ObjectMeta: metav1.ObjectMeta{Name: hostname}}, options.SetOptions{}) + node, err = c.Nodes().Create(ctx, &internalapi.Node{ObjectMeta: metav1.ObjectMeta{Name: hostname}}, options.SetOptions{}) Expect(err).NotTo(HaveOccurred()) expectedCacheSize += 1 syncTester.ExpectCacheSize(expectedCacheSize) diff --git a/libcalico-go/lib/backend/syncersv1/bgpsyncer/bgpsyncer_suite_test.go b/libcalico-go/lib/backend/syncersv1/bgpsyncer/bgpsyncer_suite_test.go index 4ae21a35b97..1448baa2aff 100644 --- a/libcalico-go/lib/backend/syncersv1/bgpsyncer/bgpsyncer_suite_test.go +++ b/libcalico-go/lib/backend/syncersv1/bgpsyncer/bgpsyncer_suite_test.go @@ -17,16 +17,16 @@ package bgpsyncer import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestClient(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../../report/bgpsyncer_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "BGP syncer test suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../../report/bgpsyncer_suite.xml" + ginkgo.RunSpecs(t, "BGP syncer test suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/backend/syncersv1/dedupebuffer/dedupe_buffer.go b/libcalico-go/lib/backend/syncersv1/dedupebuffer/dedupe_buffer.go index 3a318e35fa4..d39a5a51dba 100644 --- a/libcalico-go/lib/backend/syncersv1/dedupebuffer/dedupe_buffer.go +++ b/libcalico-go/lib/backend/syncersv1/dedupebuffer/dedupe_buffer.go @@ -381,7 +381,7 @@ func (d *DedupeBuffer) onInSyncAfterReconnection() { log.Infof("In sync with Typha, synthesizing deletions for %d "+ "resources not seen during the resync.", d.liveKeysNotSeenSinceReconnect.Len()) - d.liveKeysNotSeenSinceReconnect.Iter(func(key string) error { + for key := range d.liveKeysNotSeenSinceReconnect.All() { parsedKey := model.KeyFromDefaultPath(key) if parsedKey == nil { // Not clear how this could happen since these keys came from the @@ -395,8 +395,7 @@ func (d *DedupeBuffer) onInSyncAfterReconnection() { }, UpdateType: api.UpdateTypeKVDeleted, }) - return nil - }) + } } var _ api.SyncerCallbacks = (*DedupeBuffer)(nil) diff --git a/libcalico-go/lib/converter/converter_suite_test.go b/libcalico-go/lib/backend/syncersv1/dedupebuffer/dedupe_buffer_suite_test.go similarity index 67% rename from libcalico-go/lib/converter/converter_suite_test.go rename to libcalico-go/lib/backend/syncersv1/dedupebuffer/dedupe_buffer_suite_test.go index ae3c2cd2e61..616db2ef863 100644 --- a/libcalico-go/lib/converter/converter_suite_test.go +++ b/libcalico-go/lib/backend/syncersv1/dedupebuffer/dedupe_buffer_suite_test.go @@ -12,21 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -package converter_test +package dedupebuffer import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) -func TestConverter(t *testing.T) { +func TestBackend(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/converter_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Converter Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/dedupe_buffer_suite.xml" + ginkgo.RunSpecs(t, "Dedupe buffer suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/backend/syncersv1/dedupebuffer/dedupe_buffer_test.go b/libcalico-go/lib/backend/syncersv1/dedupebuffer/dedupe_buffer_test.go index bad85ac691d..a16542b4da2 100644 --- a/libcalico-go/lib/backend/syncersv1/dedupebuffer/dedupe_buffer_test.go +++ b/libcalico-go/lib/backend/syncersv1/dedupebuffer/dedupe_buffer_test.go @@ -16,6 +16,7 @@ package dedupebuffer import ( "fmt" + "maps" "sync" "testing" @@ -574,9 +575,7 @@ func (r *Receiver) FinalValues() map[string]string { r.mutex.Lock() defer r.mutex.Unlock() fvCopy := map[string]string{} - for k, v := range r.finalValues { - fvCopy[k] = v - } + maps.Copy(fvCopy, r.finalValues) return fvCopy } diff --git a/libcalico-go/lib/backend/syncersv1/felixsyncer/felixsyncer_e2e_test.go b/libcalico-go/lib/backend/syncersv1/felixsyncer/felixsyncer_e2e_test.go index 9a49af01696..60d65682264 100644 --- a/libcalico-go/lib/backend/syncersv1/felixsyncer/felixsyncer_e2e_test.go +++ b/libcalico-go/lib/backend/syncersv1/felixsyncer/felixsyncer_e2e_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2024 Tigera, Inc. All rights reserved. +// Copyright (c) 2017-2025 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ package felixsyncer_test import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -27,7 +27,7 @@ import ( "github.com/projectcalico/calico/lib/std/uniquelabels" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/encap" @@ -80,8 +80,8 @@ func calculateDefaultFelixSyncerEntries(cs kubernetes.Interface, dt apiconfig.Da expected = append(expected, *cnodekv) if node.Name == controlPlaneNodeName { - for _, ip := range cnodekv.Value.(*libapiv3.Node).Spec.Addresses { - if ip.Type == libapiv3.InternalIP { + for _, ip := range cnodekv.Value.(*internalapi.Node).Spec.Addresses { + if ip.Type == internalapi.InternalIP { expected = append(expected, model.KVPair{ Key: model.HostIPKey{ Hostname: node.Name, @@ -290,19 +290,19 @@ var _ = testutils.E2eDatastoreDescribe("Felix syncer tests", testutils.Datastore // Verify our cache size is correct. syncTester.ExpectCacheSize(expectedCacheSize) - var node *libapiv3.Node + var node *internalapi.Node wip := net.MustParseIP("192.168.12.34") if config.Spec.DatastoreType == apiconfig.Kubernetes { // For Kubernetes, update the existing node config to have some BGP configuration. By("Configuring a node with an IP address and tunnel MAC address") var ( oldValuesSaved bool - oldBGPSpec *libapiv3.NodeBGPSpec + oldBGPSpec *internalapi.NodeBGPSpec oldVXLANTunnelMACAddr string - oldWireguardSpec *libapiv3.NodeWireguardSpec + oldWireguardSpec *internalapi.NodeWireguardSpec oldWireguardPublicKey string ) - for i := 0; i < 5; i++ { + for range 5 { // This can fail due to an update conflict, so we allow a few retries. node, err = c.Nodes().Get(ctx, "127.0.0.1", options.GetOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -323,16 +323,16 @@ var _ = testutils.E2eDatastoreDescribe("Felix syncer tests", testutils.Datastore oldWireguardPublicKey = node.Status.WireguardPublicKey oldValuesSaved = true } - node.Spec.BGP = &libapiv3.NodeBGPSpec{ + node.Spec.BGP = &internalapi.NodeBGPSpec{ IPv4Address: "1.2.3.4/24", IPv6Address: "aa:bb::cc/120", IPv4IPIPTunnelAddr: "192.168.0.1", } node.Spec.VXLANTunnelMACAddr = "66:cf:23:df:22:07" - node.Spec.Wireguard = &libapiv3.NodeWireguardSpec{ + node.Spec.Wireguard = &internalapi.NodeWireguardSpec{ InterfaceIPv4Address: "192.168.12.34", } - node.Status = libapiv3.NodeStatus{ + node.Status = internalapi.NodeStatus{ WireguardPublicKey: "jlkVyQYooZYzI2wFfNhSZez5eWh44yfq1wKVjLvSXgY=", } node, err = c.Nodes().Update(ctx, node, options.SetOptions{}) @@ -342,7 +342,7 @@ var _ = testutils.E2eDatastoreDescribe("Felix syncer tests", testutils.Datastore } Expect(err).NotTo(HaveOccurred()) addCleanup(func() { - for i := 0; i < 5; i++ { + for range 5 { // This can fail due to an update conflict, so we allow a few retries. node, err = c.Nodes().Get(ctx, "127.0.0.1", options.GetOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -375,20 +375,20 @@ var _ = testutils.E2eDatastoreDescribe("Felix syncer tests", testutils.Datastore By("Creating a node with an IP address") node, err = c.Nodes().Create( ctx, - &libapiv3.Node{ + &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{Name: "127.0.0.1"}, - Spec: libapiv3.NodeSpec{ - BGP: &libapiv3.NodeBGPSpec{ + Spec: internalapi.NodeSpec{ + BGP: &internalapi.NodeBGPSpec{ IPv4Address: "1.2.3.4/24", IPv6Address: "aa:bb::cc/120", IPv4IPIPTunnelAddr: "192.168.0.1", }, VXLANTunnelMACAddr: "66:cf:23:df:22:07", - Wireguard: &libapiv3.NodeWireguardSpec{ + Wireguard: &internalapi.NodeWireguardSpec{ InterfaceIPv4Address: "192.168.12.34", }, }, - Status: libapiv3.NodeStatus{ + Status: internalapi.NodeStatus{ WireguardPublicKey: "jlkVyQYooZYzI2wFfNhSZez5eWh44yfq1wKVjLvSXgY=", }, }, @@ -408,21 +408,21 @@ var _ = testutils.E2eDatastoreDescribe("Felix syncer tests", testutils.Datastore model.GlobalConfigKey{Name: "ClusterGUID"}, MatchRegexp("[a-f0-9]{32}"), ) - // Creating the node also creates default and adminnetworkpolicy tiers. + // Creating the node also creates default, kube-admin, and kube-baseline tiers. order := apiv3.DefaultTierOrder syncTester.ExpectData(model.KVPair{ Key: model.TierKey{Name: "default"}, Value: &model.Tier{Order: &order, DefaultAction: apiv3.Deny}, }) - anpOrder := apiv3.AdminNetworkPolicyTierOrder + adminTierOrder := apiv3.KubeAdminTierOrder syncTester.ExpectData(model.KVPair{ - Key: model.TierKey{Name: names.AdminNetworkPolicyTierName}, - Value: &model.Tier{Order: &anpOrder, DefaultAction: apiv3.Pass}, + Key: model.TierKey{Name: names.KubeAdminTierName}, + Value: &model.Tier{Order: &adminTierOrder, DefaultAction: apiv3.Pass}, }) - banpOrder := apiv3.BaselineAdminNetworkPolicyTierOrder + baselineTierOrder := apiv3.KubeBaselineTierOrder syncTester.ExpectData(model.KVPair{ - Key: model.TierKey{Name: names.BaselineAdminNetworkPolicyTierName}, - Value: &model.Tier{Order: &banpOrder, DefaultAction: apiv3.Pass}, + Key: model.TierKey{Name: names.KubeBaselineTierName}, + Value: &model.Tier{Order: &baselineTierOrder, DefaultAction: apiv3.Pass}, }) syncTester.ExpectData(model.KVPair{ Key: model.HostConfigKey{Hostname: "127.0.0.1", Name: "IpInIpTunnelAddr"}, diff --git a/libcalico-go/lib/backend/syncersv1/felixsyncer/felixsyncerv1.go b/libcalico-go/lib/backend/syncersv1/felixsyncer/felixsyncerv1.go index 67dbdbf5feb..0d74a65a2a6 100644 --- a/libcalico-go/lib/backend/syncersv1/felixsyncer/felixsyncerv1.go +++ b/libcalico-go/lib/backend/syncersv1/felixsyncer/felixsyncerv1.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2017-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import ( apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/backend/syncersv1/updateprocessors" @@ -44,7 +44,7 @@ func New(client api.Client, cfg apiconfig.CalicoAPIConfigSpec, callbacks api.Syn additionalTypes := []watchersyncer.ResourceType{ { ListInterface: model.ResourceListOptions{Kind: apiv3.KindGlobalNetworkPolicy}, - UpdateProcessor: updateprocessors.NewGlobalNetworkPolicyUpdateProcessor(), + UpdateProcessor: updateprocessors.NewGlobalNetworkPolicyUpdateProcessor(apiv3.KindGlobalNetworkPolicy), }, { ListInterface: model.ResourceListOptions{Kind: apiv3.KindStagedGlobalNetworkPolicy}, @@ -59,7 +59,7 @@ func New(client api.Client, cfg apiconfig.CalicoAPIConfigSpec, callbacks api.Syn UpdateProcessor: updateprocessors.NewIPPoolUpdateProcessor(), }, { - ListInterface: model.ResourceListOptions{Kind: libapiv3.KindNode}, + ListInterface: model.ResourceListOptions{Kind: internalapi.KindNode}, UpdateProcessor: updateprocessors.NewFelixNodeUpdateProcessor(cfg.K8sUsePodCIDR), }, { @@ -67,12 +67,12 @@ func New(client api.Client, cfg apiconfig.CalicoAPIConfigSpec, callbacks api.Syn UpdateProcessor: updateprocessors.NewProfileUpdateProcessor(), }, { - ListInterface: model.ResourceListOptions{Kind: libapiv3.KindWorkloadEndpoint}, + ListInterface: model.ResourceListOptions{Kind: internalapi.KindWorkloadEndpoint}, UpdateProcessor: updateprocessors.NewWorkloadEndpointUpdateProcessor(), }, { ListInterface: model.ResourceListOptions{Kind: apiv3.KindNetworkPolicy}, - UpdateProcessor: updateprocessors.NewNetworkPolicyUpdateProcessor(), + UpdateProcessor: updateprocessors.NewNetworkPolicyUpdateProcessor(apiv3.KindNetworkPolicy), }, { ListInterface: model.ResourceListOptions{Kind: apiv3.KindStagedNetworkPolicy}, @@ -100,6 +100,9 @@ func New(client api.Client, cfg apiconfig.CalicoAPIConfigSpec, callbacks api.Syn { ListInterface: model.ResourceListOptions{Kind: apiv3.KindBGPPeer}, }, + { + ListInterface: model.ResourceListOptions{Kind: internalapi.KindLiveMigration}, + }, } // If running in kdd mode, also watch Kubernetes network policies directly. @@ -107,15 +110,11 @@ func New(client api.Client, cfg apiconfig.CalicoAPIConfigSpec, callbacks api.Syn if cfg.DatastoreType == apiconfig.Kubernetes { additionalTypes = append(additionalTypes, watchersyncer.ResourceType{ ListInterface: model.ResourceListOptions{Kind: model.KindKubernetesNetworkPolicy}, - UpdateProcessor: updateprocessors.NewNetworkPolicyUpdateProcessor(), - }) - additionalTypes = append(additionalTypes, watchersyncer.ResourceType{ - ListInterface: model.ResourceListOptions{Kind: model.KindKubernetesAdminNetworkPolicy}, - UpdateProcessor: updateprocessors.NewGlobalNetworkPolicyUpdateProcessor(), + UpdateProcessor: updateprocessors.NewNetworkPolicyUpdateProcessor(model.KindKubernetesNetworkPolicy), }) additionalTypes = append(additionalTypes, watchersyncer.ResourceType{ - ListInterface: model.ResourceListOptions{Kind: model.KindKubernetesBaselineAdminNetworkPolicy}, - UpdateProcessor: updateprocessors.NewGlobalNetworkPolicyUpdateProcessor(), + ListInterface: model.ResourceListOptions{Kind: model.KindKubernetesClusterNetworkPolicy}, + UpdateProcessor: updateprocessors.NewGlobalNetworkPolicyUpdateProcessor(model.KindKubernetesClusterNetworkPolicy), }) additionalTypes = append(additionalTypes, watchersyncer.ResourceType{ ListInterface: model.ResourceListOptions{Kind: model.KindKubernetesEndpointSlice}, diff --git a/libcalico-go/lib/backend/syncersv1/felixsyncer/felixsyncerv1_suite_test.go b/libcalico-go/lib/backend/syncersv1/felixsyncer/felixsyncerv1_suite_test.go index a3039a6287a..143759b0213 100644 --- a/libcalico-go/lib/backend/syncersv1/felixsyncer/felixsyncerv1_suite_test.go +++ b/libcalico-go/lib/backend/syncersv1/felixsyncer/felixsyncerv1_suite_test.go @@ -17,16 +17,16 @@ package felixsyncer import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestClient(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../../report/felix_syncer_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Felix syncer test Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../../report/felix_syncer_suite.xml" + ginkgo.RunSpecs(t, "Felix syncer test Suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/backend/syncersv1/nodestatussyncer/nodestatussyncer_e2e_test.go b/libcalico-go/lib/backend/syncersv1/nodestatussyncer/nodestatussyncer_e2e_test.go index a0615293cf7..d32996f5ab9 100644 --- a/libcalico-go/lib/backend/syncersv1/nodestatussyncer/nodestatussyncer_e2e_test.go +++ b/libcalico-go/lib/backend/syncersv1/nodestatussyncer/nodestatussyncer_e2e_test.go @@ -17,7 +17,7 @@ package nodestatussyncer_test import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/libcalico-go/lib/backend/syncersv1/nodestatussyncer/nodestatussyncer_suite_test.go b/libcalico-go/lib/backend/syncersv1/nodestatussyncer/nodestatussyncer_suite_test.go index 76f5c2c92b5..33c23d872cb 100644 --- a/libcalico-go/lib/backend/syncersv1/nodestatussyncer/nodestatussyncer_suite_test.go +++ b/libcalico-go/lib/backend/syncersv1/nodestatussyncer/nodestatussyncer_suite_test.go @@ -17,16 +17,16 @@ package nodestatussyncer_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestClient(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../../report/nodestatussyncer_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Node status syncer test suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../../report/nodestatussyncer_suite.xml" + ginkgo.RunSpecs(t, "Node status syncer test suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/tunnelipsyncer.go b/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/tunnelipsyncer.go index d83f7641ea6..d4bd3701f21 100644 --- a/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/tunnelipsyncer.go +++ b/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/tunnelipsyncer.go @@ -17,7 +17,7 @@ package tunnelipsyncer import ( apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/backend/syncersv1/updateprocessors" @@ -33,7 +33,7 @@ func New(client api.Client, callbacks api.SyncerCallbacks, node string) api.Sync UpdateProcessor: updateprocessors.NewIPPoolUpdateProcessor(), }, { - ListInterface: model.ResourceListOptions{Kind: libapiv3.KindNode, Name: node}, + ListInterface: model.ResourceListOptions{Kind: internalapi.KindNode, Name: node}, }, } diff --git a/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/tunnelipsyncer_e2e_test.go b/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/tunnelipsyncer_e2e_test.go index 9c93239a4e1..1344b26c93a 100644 --- a/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/tunnelipsyncer_e2e_test.go +++ b/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/tunnelipsyncer_e2e_test.go @@ -17,7 +17,7 @@ package tunnelipsyncer_test import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/tunnelipsyncer_suite_test.go b/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/tunnelipsyncer_suite_test.go index 552514d8fec..2d767dd81a7 100644 --- a/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/tunnelipsyncer_suite_test.go +++ b/libcalico-go/lib/backend/syncersv1/tunnelipsyncer/tunnelipsyncer_suite_test.go @@ -17,16 +17,16 @@ package tunnelipsyncer import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestClient(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../../report/tunnelipsyncer_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Tunnel IP syncer test suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../../report/tunnelipsyncer_suite.xml" + ginkgo.RunSpecs(t, "Tunnel IP syncer test suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/bgpnodeprocessor.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/bgpnodeprocessor.go index 54c4ddb7d02..9b471b9aedf 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/bgpnodeprocessor.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/bgpnodeprocessor.go @@ -19,7 +19,7 @@ import ( log "github.com/sirupsen/logrus" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/backend/watchersyncer" "github.com/projectcalico/calico/libcalico-go/lib/net" @@ -51,11 +51,11 @@ func (c *bgpNodeUpdateProcessor) Process(kvp *model.KVPair) ([]*model.KVPair, er // Extract the separate bits of BGP config - these are stored as separate keys in the // v1 model. For a delete these will all be nil. - var asNum, ipv4, netv4, ipv6, netv6, rrClusterID interface{} - var node *libapiv3.Node + var asNum, ipv4, netv4, ipv6, netv6, rrClusterID any + var node *internalapi.Node var ok bool if kvp.Value != nil { - node, ok = kvp.Value.(*libapiv3.Node) + node, ok = kvp.Value.(*internalapi.Node) if !ok { return nil, errors.New("Incorrect value type - expecting resource of kind Node") } @@ -194,7 +194,7 @@ func (c *bgpNodeUpdateProcessor) OnSyncerStarting() { func (c *bgpNodeUpdateProcessor) extractName(k model.Key) (string, error) { rk, ok := k.(model.ResourceKey) - if !ok || rk.Kind != libapiv3.KindNode { + if !ok || rk.Kind != internalapi.KindNode { return "", errors.New("Incorrect key type - expecting resource of kind Node") } return rk.Name, nil diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/bgpnodeprocessor_test.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/bgpnodeprocessor_test.go index 0b41d132fcd..db8f5817ddd 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/bgpnodeprocessor_test.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/bgpnodeprocessor_test.go @@ -19,12 +19,12 @@ import ( "fmt" "reflect" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/backend/syncersv1/updateprocessors" "github.com/projectcalico/calico/libcalico-go/lib/net" @@ -32,7 +32,7 @@ import ( var _ = Describe("Test the (BGP) Node update processor", func() { v3NodeKey1 := model.ResourceKey{ - Kind: libapiv3.KindNode, + Kind: internalapi.KindNode, Name: "bgpnode1", } numBgpConfigs := 6 @@ -47,9 +47,9 @@ var _ = Describe("Test the (BGP) Node update processor", func() { // our validation. Note that it expects a node name of bgpnode1. It("should handle conversion of valid Nodes", func() { By("converting a zero-ed Node") - res := libapiv3.NewNode() + res := internalapi.NewNode() res.Name = "bgpnode1" - expected := map[string]interface{}{ + expected := map[string]any{ "ip_addr_v4": "", "ip_addr_v6": "", "network_v4": nil, @@ -70,9 +70,9 @@ var _ = Describe("Test the (BGP) Node update processor", func() { ) By("converting a zero-ed but non-nil BGPNodeSpec") - res = libapiv3.NewNode() + res = internalapi.NewNode() res.Name = "bgpnode1" - res.Spec.BGP = &libapiv3.NodeBGPSpec{} + res.Spec.BGP = &internalapi.NodeBGPSpec{} kvps, err = up.Process(&model.KVPair{ Key: v3NodeKey1, Value: res, @@ -87,12 +87,12 @@ var _ = Describe("Test the (BGP) Node update processor", func() { ) By("converting a Node with an IPv4 (specified without the network) only - expect /32 net") - res = libapiv3.NewNode() + res = internalapi.NewNode() res.Name = "bgpnode1" - res.Spec.BGP = &libapiv3.NodeBGPSpec{ + res.Spec.BGP = &internalapi.NodeBGPSpec{ IPv4Address: "1.2.3.4", } - expected = map[string]interface{}{ + expected = map[string]any{ "ip_addr_v4": "1.2.3.4", "ip_addr_v6": "", "network_v4": "1.2.3.4/32", @@ -113,12 +113,12 @@ var _ = Describe("Test the (BGP) Node update processor", func() { ) By("converting a Node with an IPv6 (specified without the network) only - expect /128 net") - res = libapiv3.NewNode() + res = internalapi.NewNode() res.Name = "bgpnode1" - res.Spec.BGP = &libapiv3.NodeBGPSpec{ + res.Spec.BGP = &internalapi.NodeBGPSpec{ IPv6Address: "aa:bb:cc::", } - expected = map[string]interface{}{ + expected = map[string]any{ "ip_addr_v4": "", "ip_addr_v6": "aa:bb:cc::", "network_v4": nil, @@ -139,15 +139,15 @@ var _ = Describe("Test the (BGP) Node update processor", func() { ) By("converting a Node with IPv4 and IPv6 network and AS number") - res = libapiv3.NewNode() + res = internalapi.NewNode() res.Name = "bgpnode1" asn := numorstring.ASNumber(12345) - res.Spec.BGP = &libapiv3.NodeBGPSpec{ + res.Spec.BGP = &internalapi.NodeBGPSpec{ IPv4Address: "1.2.3.4/24", IPv6Address: "aa:bb:cc::ffff/120", ASNumber: &asn, } - expected = map[string]interface{}{ + expected = map[string]any{ "ip_addr_v4": "1.2.3.4", "ip_addr_v6": "aa:bb:cc::ffff", "network_v4": "1.2.3.0/24", @@ -170,7 +170,7 @@ var _ = Describe("Test the (BGP) Node update processor", func() { It("should fail to convert an invalid resource", func() { By("trying to convert with the wrong key type") - res := libapiv3.NewNode() + res := internalapi.NewNode() _, err := up.Process(&model.KVPair{ Key: model.GlobalConfigKey{ @@ -191,10 +191,10 @@ var _ = Describe("Test the (BGP) Node update processor", func() { Expect(err).To(HaveOccurred()) By("trying to convert with an invalid IPv4 address - treat as unassigned") - res = libapiv3.NewNode() + res = internalapi.NewNode() res.Name = "bgpnode1" asn := numorstring.ASNumber(12345) - res.Spec.BGP = &libapiv3.NodeBGPSpec{ + res.Spec.BGP = &internalapi.NodeBGPSpec{ IPv4Address: "1.2.3.4/240", IPv6Address: "aa:bb:cc::ffff/120", ASNumber: &asn, @@ -204,7 +204,7 @@ var _ = Describe("Test the (BGP) Node update processor", func() { Value: res, }) // IPv4 address should be blank, network should be nil (deleted) - expected := map[string]interface{}{ + expected := map[string]any{ "ip_addr_v4": "", "ip_addr_v6": "aa:bb:cc::ffff", "network_v4": nil, @@ -221,9 +221,9 @@ var _ = Describe("Test the (BGP) Node update processor", func() { ) By("trying to convert with an invalid IPv6 address - treat as unassigned") - res = libapiv3.NewNode() + res = internalapi.NewNode() res.Name = "bgpnode1" - res.Spec.BGP = &libapiv3.NodeBGPSpec{ + res.Spec.BGP = &internalapi.NodeBGPSpec{ IPv4Address: "1.2.3.4/24", IPv6Address: "aazz::qq/100", } @@ -232,7 +232,7 @@ var _ = Describe("Test the (BGP) Node update processor", func() { Value: res, }) // IPv6 address should be blank, network should be nil (deleted) - expected = map[string]interface{}{ + expected = map[string]any{ "ip_addr_v4": "1.2.3.4", "ip_addr_v6": "", "network_v4": "1.2.3.0/24", @@ -250,13 +250,13 @@ var _ = Describe("Test the (BGP) Node update processor", func() { }) It("should handle route reflector cluster ID field", func() { - res := libapiv3.NewNode() + res := internalapi.NewNode() res.Name = "bgpnode1" - res.Spec.BGP = &libapiv3.NodeBGPSpec{ + res.Spec.BGP = &internalapi.NodeBGPSpec{ IPv4Address: "172.17.0.2/24", RouteReflectorClusterID: "255.0.0.1", } - expected := map[string]interface{}{ + expected := map[string]any{ "ip_addr_v4": "172.17.0.2", "ip_addr_v6": "", "network_v4": "172.17.0.0/24", @@ -280,7 +280,7 @@ var _ = Describe("Test the (BGP) Node update processor", func() { var _ = Describe("Test the (BGP) Node update processor with USE_POD_CIDR=true", func() { v3NodeKey1 := model.ResourceKey{ - Kind: libapiv3.KindNode, + Kind: internalapi.KindNode, Name: "mynode", } up := updateprocessors.NewBGPNodeUpdateProcessor(true) @@ -291,7 +291,7 @@ var _ = Describe("Test the (BGP) Node update processor with USE_POD_CIDR=true", It("should properly convert nodes into block affinities for BGP", func() { By("converting a node with PodCIDRs set") - res := libapiv3.NewNode() + res := internalapi.NewNode() res.Name = "mynode" res.Status.PodCIDRs = []string{ "192.168.1.0/24", diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/clusterinfoprocessor.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/clusterinfoprocessor.go index 54ce3405be2..f00bdcdbc21 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/clusterinfoprocessor.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/clusterinfoprocessor.go @@ -26,7 +26,7 @@ import ( // Create a new NewClusterInfoUpdateProcessor. func NewClusterInfoUpdateProcessor() watchersyncer.SyncerUpdateProcessor { return NewConfigUpdateProcessor( - reflect.TypeOf(apiv3.ClusterInformationSpec{}), + reflect.TypeFor[apiv3.ClusterInformationSpec](), DisallowAnnotations, func(node, name string) model.Key { if name == "DatastoreReady" { @@ -46,6 +46,6 @@ func NewClusterInfoUpdateProcessor() watchersyncer.SyncerUpdateProcessor { ) } -func datastoreReadyToBool(value interface{}) interface{} { +func datastoreReadyToBool(value any) any { return value.(bool) } diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/configurationprocessor.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/configurationprocessor.go index 1e6af3d54e9..f769d124666 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/configurationprocessor.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/configurationprocessor.go @@ -86,7 +86,7 @@ type NodeConfigKeyFn func(node, name string) model.Key type GlobalConfigKeyFn func(name string) model.Key // Convert an arbitrary value to the value used in the v1 model. -type ConfigFieldValueToV1ModelValue func(value interface{}) interface{} +type ConfigFieldValueToV1ModelValue func(value any) any var ( globalConfigName = "default" @@ -184,7 +184,7 @@ func (c *configUpdateProcessor) processAddOrModified(kvp *model.KVPair) ([]*mode // Create a KVP for each field in the Spec struct. var kvps []*model.KVPair numFields := len(c.names) - for i := 0; i < numFields; i++ { + for i := range numFields { fieldInfo := c.specType.Field(i) name := getConfigName(fieldInfo) @@ -208,9 +208,9 @@ func (c *configUpdateProcessor) processAddOrModified(kvp *model.KVPair) ([]*mode // Extract the field value and dereference pointers, storing a nil value if the pointer is nil // or if it's a zero length string. - var value interface{} + var value any field := specValue.Field(i) - if field.Kind() == reflect.Ptr { + if field.Kind() == reflect.Pointer { if !field.IsNil() { value = field.Elem().Interface() } @@ -245,11 +245,11 @@ func (c *configUpdateProcessor) processAddOrModified(kvp *model.KVPair) ([]*mode value = strings.Join(vt, ",") case map[string]string: // Make it a comma separate list of key value pairs - var kvp string + var kvp strings.Builder for k, v := range vt { - kvp += k + "=" + v + "," + kvp.WriteString(k + "=" + v + ",") } - value = kvp + value = kvp.String() default: value = fmt.Sprintf("%v", vt) } @@ -268,7 +268,7 @@ func (c *configUpdateProcessor) processAddOrModified(kvp *model.KVPair) ([]*mode // on previous requests. This ensures we send deletes for them in case our previous // settings had it set. The WatcherSyncer handles gracefully multiple deletes for // the same key, so it doesn't matter if it wasn't previously set. - var value interface{} + var value any for name := range c.additionalNames { // If we have an override for this additional config option then use that value, // otherwise leave the value as nil to ensure we delete the option if it was diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/configurationprocessor_test.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/configurationprocessor_test.go index 38c940c3eef..d328214cd00 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/configurationprocessor_test.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/configurationprocessor_test.go @@ -16,15 +16,17 @@ package updateprocessors_test import ( "fmt" + "maps" + "reflect" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/backend/syncersv1/updateprocessors" "github.com/projectcalico/calico/libcalico-go/lib/net" @@ -42,8 +44,8 @@ const ( wireguardMarker = "*WIREGUARDMARKER*" ) -const ( - numBaseFelixConfigs = 168 +var ( + numBaseFelixConfigs = reflect.TypeFor[apiv3.FelixConfigurationSpec]().NumField() ) var _ = Describe("Test the generic configuration update processor and the concrete implementations", func() { @@ -71,7 +73,7 @@ var _ = Describe("Test the generic configuration update processor and the concre numFelixConfigs := numBaseFelixConfigs numClusterConfigs := 5 numNodeClusterConfigs := 4 - felixMappedNames := map[string]interface{}{ + felixMappedNames := map[string]any{ "RouteRefreshInterval": nil, "IptablesRefreshInterval": nil, "IpsetsRefreshInterval": nil, @@ -161,7 +163,7 @@ var _ = Describe("Test the generic configuration update processor and the concre By("Testing incorrect resource type value on Process with add/mod") _, err = cc.Process(&model.KVPair{ Key: globalFelixKey, - Value: libapiv3.NewWorkloadEndpoint(), + Value: internalapi.NewWorkloadEndpoint(), }) Expect(err).To(HaveOccurred()) @@ -222,7 +224,9 @@ var _ = Describe("Test the generic configuration update processor and the concre res.Spec.NftablesFilterDenyAction = "Accept" res.Spec.NftablesFilterAllowAction = "Drop" res.Spec.NftablesMangleAllowAction = "Accept" - expected := map[string]interface{}{ + res.Spec.BPFMaglevMaxEndpointsPerService = &intype + res.Spec.BPFMaglevMaxServices = &intype + expected := map[string]any{ "RouteRefreshInterval": "12.345", "IptablesLockProbeIntervalMillis": "54.321", "EndpointReportingDelaySecs": "0", @@ -240,6 +244,8 @@ var _ = Describe("Test the generic configuration update processor and the concre "NftablesFilterDenyAction": "Accept", "NftablesFilterAllowAction": "Drop", "NftablesMangleAllowAction": "Accept", + "BPFMaglevMaxServices": "3", + "BPFMaglevMaxEndpointsPerService": "3", } kvps, err := cc.Process(&model.KVPair{ Key: perNodeFelixKey, @@ -268,7 +274,7 @@ var _ = Describe("Test the generic configuration update processor and the concre Timeout: metav1.Duration{Duration: 25 * time.Second}, }, } - expected := map[string]interface{}{ + expected := map[string]any{ "HealthTimeoutOverrides": "Foo=20s,Bar=25s", } kvps, err := cc.Process(&model.KVPair{ @@ -290,7 +296,7 @@ var _ = Describe("Test the generic configuration update processor and the concre res := apiv3.NewClusterInformation() res.Spec.ClusterGUID = "abcedfg" res.Spec.ClusterType = "Mesos,K8s" - expected := map[string]interface{}{ + expected := map[string]any{ "ClusterGUID": "abcedfg", "ClusterType": "Mesos,K8s", } @@ -313,7 +319,7 @@ var _ = Describe("Test the generic configuration update processor and the concre res := apiv3.NewClusterInformation() ready := true res.Spec.DatastoreReady = &ready - expected := map[string]interface{}{ + expected := map[string]any{ "ready-flag": true, } kvps, err := cc.Process(&model.KVPair{ @@ -335,7 +341,7 @@ var _ = Describe("Test the generic configuration update processor and the concre res := apiv3.NewClusterInformation() ready := false res.Spec.DatastoreReady = &ready - expected := map[string]interface{}{ + expected := map[string]any{ "ready-flag": false, } kvps, err := cc.Process(&model.KVPair{ @@ -392,15 +398,13 @@ var _ = Describe("Test the generic configuration update processor and the concre // to be nil in the KVPair. // You can use expectedValues to verify certain fields were included in the response even // if the values were nil. -func checkExpectedConfigs(kvps []*model.KVPair, dataType int, expectedNum int, expectedValues map[string]interface{}) { +func checkExpectedConfigs(kvps []*model.KVPair, dataType int, expectedNum int, expectedValues map[string]any) { // Copy/convert input data. We keep track of: // - all field names, so that we can check for duplicates // - extra fields that we have not yet seen // - expected field values that we have not yet validated - ev := make(map[string]interface{}, len(expectedValues)) - for k, v := range expectedValues { - ev[k] = v - } + ev := make(map[string]any, len(expectedValues)) + maps.Copy(ev, expectedValues) allNames := map[string]struct{}{} By(" - checking the expected number of results") diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/conflictresolvingcacheproc.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/conflictresolvingcacheproc.go index 8de19b5c868..0dbeded67ba 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/conflictresolvingcacheproc.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/conflictresolvingcacheproc.go @@ -16,6 +16,7 @@ package updateprocessors import ( "fmt" + "slices" "sort" log "github.com/sirupsen/logrus" @@ -147,13 +148,7 @@ func (c *conflictResolvingCache) Process(kvp *model.KVPair) ([]*model.KVPair, er // Get the current set of names that map to this key, and if this name is not // in the list - add it and sort the list. cns := c.orderedNamesByV1Key[v1Key] - inList := false - for _, cn := range cns { - if cn == name { - inList = true - break - } - } + inList := slices.Contains(cns, name) if !inList { cns = append(cns, name) sort.Strings(cns) diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/conflictresolvingcacheproc_test.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/conflictresolvingcacheproc_test.go index e31bba0d767..ce00f176b84 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/conflictresolvingcacheproc_test.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/conflictresolvingcacheproc_test.go @@ -15,7 +15,7 @@ package updateprocessors_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/felixconfigprocessor.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/felixconfigprocessor.go index f5f98074db2..97c77dba24f 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/felixconfigprocessor.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/felixconfigprocessor.go @@ -31,7 +31,7 @@ import ( // consumption by Felix. func NewFelixConfigUpdateProcessor() watchersyncer.SyncerUpdateProcessor { return NewConfigUpdateProcessor( - reflect.TypeOf(apiv3.FelixConfigurationSpec{}), + reflect.TypeFor[apiv3.FelixConfigurationSpec](), AllowAnnotations, func(node, name string) model.Key { return model.HostConfigKey{Hostname: node, Name: name} }, func(name string) model.Key { return model.GlobalConfigKey{Name: name} }, @@ -47,7 +47,7 @@ func NewFelixConfigUpdateProcessor() watchersyncer.SyncerUpdateProcessor { } // Convert a slice of ProtoPorts to the string representation required by Felix. -func protoPortSliceToString(value interface{}) interface{} { +func protoPortSliceToString(value any) any { pps := value.([]apiv3.ProtoPort) if len(pps) == 0 { return "none" @@ -73,7 +73,7 @@ func protoPortSliceToString(value interface{}) interface{} { // Converts multiple route table ranges to its string config representation. // e.g. RouteTableRanges{{Min: 0, Max: 250}, {Min: 255, Max: 3000}} => "0-250,255-3000" -func routeTableRangeListToString(value interface{}) interface{} { +func routeTableRangeListToString(value any) any { ranges := value.(apiv3.RouteTableRanges) rangesStr := make([]string, 0) for _, r := range ranges { @@ -84,12 +84,12 @@ func routeTableRangeListToString(value interface{}) interface{} { // Converts a route table range to its string config representation. // e.g. RouteTableRange{Min: 0, Max: 250} => "0-250" -func routeTableRangeToString(value interface{}) interface{} { +func routeTableRangeToString(value any) any { r := value.(apiv3.RouteTableRange) return fmt.Sprintf("%d-%d", r.Min, r.Max) } -func healthTimeoutOverridesToString(value interface{}) interface{} { +func healthTimeoutOverridesToString(value any) any { htos := value.([]apiv3.HealthTimeoutOverride) if len(htos) == 0 { return nil @@ -101,7 +101,7 @@ func healthTimeoutOverridesToString(value interface{}) interface{} { return strings.Join(parts, ",") } -func structToKeyValueString(input interface{}) (string, error) { +func structToKeyValueString(input any) (string, error) { // Get the type and value of the input struct v := reflect.ValueOf(input) t := reflect.TypeOf(input) @@ -127,7 +127,7 @@ func structToKeyValueString(input interface{}) (string, error) { parts = append(parts, fmt.Sprintf("%s=%s", field.Name, s)) } // Handle pointer to string fields - if value.Kind() == reflect.Ptr && value.Type().Elem().Kind() == reflect.String { + if value.Kind() == reflect.Pointer && value.Type().Elem().Kind() == reflect.String { if !value.IsNil() { parts = append(parts, fmt.Sprintf("%s=%s", field.Name, value.Elem().String())) } @@ -138,7 +138,7 @@ func structToKeyValueString(input interface{}) (string, error) { return strings.Join(parts, ","), nil } -func bpfConntrackTimeoutsToString(value interface{}) interface{} { +func bpfConntrackTimeoutsToString(value any) any { res, _ := structToKeyValueString(value) return res } diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/felixnodeprocessor.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/felixnodeprocessor.go index 30f78c595fa..f0f1de7ec86 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/felixnodeprocessor.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/felixnodeprocessor.go @@ -21,7 +21,7 @@ import ( log "github.com/sirupsen/logrus" wg "golang.zx2c4.com/wireguard/wgctrl/wgtypes" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/backend/watchersyncer" cnet "github.com/projectcalico/calico/libcalico-go/lib/net" @@ -55,11 +55,11 @@ func (c *FelixNodeUpdateProcessor) Process(kvp *model.KVPair) ([]*model.KVPair, // v1 model. For a delete these will all be nil. If we fail to convert any value then // just treat that as a delete on the underlying key and return the error alongside // the updates. - var ipv4, ipv4Tunl, vxlanTunlIp, vxlanTunlMac, vxlanV6TunlIp, vxlanV6TunlMac, wgConfig interface{} - var node *libapiv3.Node + var ipv4, ipv4Tunl, vxlanTunlIp, vxlanTunlMac, vxlanV6TunlIp, vxlanV6TunlMac, wgConfig any + var node *internalapi.Node var ok bool if kvp.Value != nil { - node, ok = kvp.Value.(*libapiv3.Node) + node, ok = kvp.Value.(*internalapi.Node) if !ok { return nil, errors.New("Incorrect value type - expecting resource of kind Node") } @@ -95,13 +95,13 @@ func (c *FelixNodeUpdateProcessor) Process(kvp *model.KVPair) ([]*model.KVPair, } // Look for internal node address, if BGP is not running if ipv4 == nil { - ip, _ := cresources.FindNodeAddress(node, libapiv3.InternalIP, 4) + ip, _ := cresources.FindNodeAddress(node, internalapi.InternalIP, 4) if ip != nil { ipv4 = ip } } if ipv4 == nil { - ip, _ := cresources.FindNodeAddress(node, libapiv3.ExternalIP, 4) + ip, _ := cresources.FindNodeAddress(node, internalapi.ExternalIP, 4) if ip != nil { ipv4 = ip } @@ -271,7 +271,7 @@ func (c *FelixNodeUpdateProcessor) Process(kvp *model.KVPair) ([]*model.KVPair, // preserve that relationship here. Key: model.ResourceKey{ Name: name, - Kind: libapiv3.KindNode, + Kind: internalapi.KindNode, }, Value: kvp.Value, Revision: kvp.Revision, @@ -338,7 +338,7 @@ func (c *FelixNodeUpdateProcessor) OnSyncerStarting() { func (c *FelixNodeUpdateProcessor) extractName(k model.Key) (string, error) { rk, ok := k.(model.ResourceKey) - if !ok || rk.Kind != libapiv3.KindNode { + if !ok || rk.Kind != internalapi.KindNode { return "", errors.New("Incorrect key type - expecting resource of kind Node") } return rk.Name, nil diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/felixnodeprocessor_test.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/felixnodeprocessor_test.go index e04d5bf126e..74c54619049 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/felixnodeprocessor_test.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/felixnodeprocessor_test.go @@ -19,11 +19,11 @@ import ( "fmt" "reflect" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/backend/syncersv1/updateprocessors" "github.com/projectcalico/calico/libcalico-go/lib/net" @@ -31,7 +31,7 @@ import ( var _ = Describe("Test the (Felix) Node update processor", func() { v3NodeKey1 := model.ResourceKey{ - Kind: libapiv3.KindNode, + Kind: internalapi.KindNode, Name: "mynode", } numFelixConfigs := 8 @@ -47,9 +47,9 @@ var _ = Describe("Test the (Felix) Node update processor", func() { // expects a node name of mynode. It("should handle conversion of valid Nodes", func() { By("converting a zero-ed Node") - res := libapiv3.NewNode() + res := internalapi.NewNode() res.Name = "mynode" - expected := map[string]interface{}{ + expected := map[string]any{ hostIPMarker: nil, nodeMarker: res, "IpInIpTunnelAddr": nil, @@ -68,9 +68,9 @@ var _ = Describe("Test the (Felix) Node update processor", func() { ) By("converting a zero-ed but non-nil BGPNodeSpec") - res = libapiv3.NewNode() + res = internalapi.NewNode() res.Name = "mynode" - res.Spec.BGP = &libapiv3.NodeBGPSpec{} + res.Spec.BGP = &internalapi.NodeBGPSpec{} expected[nodeMarker] = res kvps, err = up.Process(&model.KVPair{ Key: v3NodeKey1, @@ -86,9 +86,9 @@ var _ = Describe("Test the (Felix) Node update processor", func() { ) By("converting a zero-ed but non-nil WireguardSpec") - res = libapiv3.NewNode() + res = internalapi.NewNode() res.Name = "mynode" - res.Spec.Wireguard = &libapiv3.NodeWireguardSpec{} + res.Spec.Wireguard = &internalapi.NodeWireguardSpec{} expected[nodeMarker] = res expected[wireguardMarker] = nil kvps, err = up.Process(&model.KVPair{ @@ -105,13 +105,13 @@ var _ = Describe("Test the (Felix) Node update processor", func() { ) By("converting a Node with an IPv4 (specified without the network) only") - res = libapiv3.NewNode() + res = internalapi.NewNode() res.Name = "mynode" - res.Spec.BGP = &libapiv3.NodeBGPSpec{ + res.Spec.BGP = &internalapi.NodeBGPSpec{ IPv4Address: "1.2.3.4", } ip := net.MustParseIP("1.2.3.4") - expected = map[string]interface{}{ + expected = map[string]any{ hostIPMarker: &ip, nodeMarker: res, "IpInIpTunnelAddr": nil, @@ -129,12 +129,12 @@ var _ = Describe("Test the (Felix) Node update processor", func() { ) By("converting a Node with Wireguard interface IPv4 address") - res = libapiv3.NewNode() + res = internalapi.NewNode() res.Name = "mynode" - res.Spec.Wireguard = &libapiv3.NodeWireguardSpec{ + res.Spec.Wireguard = &internalapi.NodeWireguardSpec{ InterfaceIPv4Address: "1.2.3.4", } - expected = map[string]interface{}{ + expected = map[string]any{ nodeMarker: res, wireguardMarker: &model.Wireguard{ InterfaceIPv4Addr: &ip, @@ -153,13 +153,13 @@ var _ = Describe("Test the (Felix) Node update processor", func() { ) By("converting a Node with Wireguard public-key") - res = libapiv3.NewNode() + res = internalapi.NewNode() res.Name = "mynode" key := "jlkVyQYooZYzI2wFfNhSZez5eWh44yfq1wKVjLvSXgY=" - res.Status = libapiv3.NodeStatus{ + res.Status = internalapi.NodeStatus{ WireguardPublicKey: key, } - expected = map[string]interface{}{ + expected = map[string]any{ nodeMarker: res, wireguardMarker: &model.Wireguard{ PublicKey: key, @@ -178,15 +178,15 @@ var _ = Describe("Test the (Felix) Node update processor", func() { ) By("converting a Node with Wireguard interface address and public-key") - res = libapiv3.NewNode() + res = internalapi.NewNode() res.Name = "mynode" - res.Spec.Wireguard = &libapiv3.NodeWireguardSpec{ + res.Spec.Wireguard = &internalapi.NodeWireguardSpec{ InterfaceIPv4Address: "1.2.3.4", } - res.Status = libapiv3.NodeStatus{ + res.Status = internalapi.NodeStatus{ WireguardPublicKey: key, } - expected = map[string]interface{}{ + expected = map[string]any{ nodeMarker: res, wireguardMarker: &model.Wireguard{ InterfaceIPv4Addr: &ip, @@ -206,14 +206,14 @@ var _ = Describe("Test the (Felix) Node update processor", func() { ) By("converting a Node with IPv4 and IPv6 networks and no other config") - res = libapiv3.NewNode() + res = internalapi.NewNode() res.Name = "mynode" - res.Spec.BGP = &libapiv3.NodeBGPSpec{ + res.Spec.BGP = &internalapi.NodeBGPSpec{ IPv4Address: "100.200.100.200/24", IPv6Address: "aa:bb::cc/120", } ip = net.MustParseIP("100.200.100.200") - expected = map[string]interface{}{ + expected = map[string]any{ hostIPMarker: &ip, nodeMarker: res, "IpInIpTunnelAddr": nil, @@ -231,13 +231,13 @@ var _ = Describe("Test the (Felix) Node update processor", func() { ) By("converting a Node with IPv6 networks and an IPv4 tunnel address and no other config") - res = libapiv3.NewNode() + res = internalapi.NewNode() res.Name = "mynode" - res.Spec.BGP = &libapiv3.NodeBGPSpec{ + res.Spec.BGP = &internalapi.NodeBGPSpec{ IPv6Address: "aa:bb::cc/120", IPv4IPIPTunnelAddr: "192.100.100.100", } - expected = map[string]interface{}{ + expected = map[string]any{ hostIPMarker: nil, nodeMarker: res, "IpInIpTunnelAddr": "192.100.100.100", @@ -255,15 +255,15 @@ var _ = Describe("Test the (Felix) Node update processor", func() { ) By("converting a Node with IPv4 address and an IPv4 VXLAN tunnel address and MAC") - res = libapiv3.NewNode() + res = internalapi.NewNode() res.Name = "mynode" - res.Spec.BGP = &libapiv3.NodeBGPSpec{ + res.Spec.BGP = &internalapi.NodeBGPSpec{ IPv4Address: "192.100.100.100", } res.Spec.IPv4VXLANTunnelAddr = "192.200.200.200" res.Spec.VXLANTunnelMACAddr = "00:11:22:33:44:55" ip = net.MustParseIP("192.100.100.100") - expected = map[string]interface{}{ + expected = map[string]any{ hostIPMarker: &ip, nodeMarker: res, "IPv4VXLANTunnelAddr": "192.200.200.200", @@ -282,14 +282,14 @@ var _ = Describe("Test the (Felix) Node update processor", func() { ) By("converting a Node with IPv6 address and an IPv6 VXLAN tunnel address and MAC") - res = libapiv3.NewNode() + res = internalapi.NewNode() res.Name = "mynode" - res.Spec.BGP = &libapiv3.NodeBGPSpec{ + res.Spec.BGP = &internalapi.NodeBGPSpec{ IPv6Address: "fd10:10::10", } res.Spec.IPv6VXLANTunnelAddr = "fd10:11::11" res.Spec.VXLANTunnelMACAddrV6 = "55:44:33:22:11:00" - expected = map[string]interface{}{ + expected = map[string]any{ hostIPMarker: nil, nodeMarker: res, "IPv6VXLANTunnelAddr": "fd10:11::11", @@ -308,9 +308,9 @@ var _ = Describe("Test the (Felix) Node update processor", func() { ) By("converting a Node with both IPv4 and IPv6 addresses and both IPv4 and IPv6 VXLAN tunnel addresses and MACs") - res = libapiv3.NewNode() + res = internalapi.NewNode() res.Name = "mynode" - res.Spec.BGP = &libapiv3.NodeBGPSpec{ + res.Spec.BGP = &internalapi.NodeBGPSpec{ IPv4Address: "192.100.100.100", IPv6Address: "fd10:10::10", } @@ -319,7 +319,7 @@ var _ = Describe("Test the (Felix) Node update processor", func() { res.Spec.IPv6VXLANTunnelAddr = "fd10:11::11" res.Spec.VXLANTunnelMACAddrV6 = "55:44:33:22:11:00" ip = net.MustParseIP("192.100.100.100") - expected = map[string]interface{}{ + expected = map[string]any{ hostIPMarker: &ip, nodeMarker: res, "IPv4VXLANTunnelAddr": "192.200.200.200", @@ -342,7 +342,7 @@ var _ = Describe("Test the (Felix) Node update processor", func() { It("should fail to convert an invalid resource", func() { By("trying to convert with the wrong key type") - res := libapiv3.NewNode() + res := internalapi.NewNode() _, err := up.Process(&model.KVPair{ Key: model.GlobalConfigKey{ @@ -363,9 +363,9 @@ var _ = Describe("Test the (Felix) Node update processor", func() { Expect(err).To(HaveOccurred()) By("trying to convert with an invalid IPv4 address - expect delete for that key") - res = libapiv3.NewNode() + res = internalapi.NewNode() res.Name = "mynode" - res.Spec.BGP = &libapiv3.NodeBGPSpec{ + res.Spec.BGP = &internalapi.NodeBGPSpec{ IPv4Address: "1.2.3.4/240", IPv4IPIPTunnelAddr: "192.100.100.100", } @@ -374,7 +374,7 @@ var _ = Describe("Test the (Felix) Node update processor", func() { Value: res, }) Expect(err).To(HaveOccurred()) - expected := map[string]interface{}{ + expected := map[string]any{ hostIPMarker: nil, nodeMarker: res, "IpInIpTunnelAddr": "192.100.100.100", @@ -387,9 +387,9 @@ var _ = Describe("Test the (Felix) Node update processor", func() { ) By("trying to convert with an invalid Wireguard interface IPv4 address - expect delete for that key") - res = libapiv3.NewNode() + res = internalapi.NewNode() res.Name = "mynode" - res.Spec.Wireguard = &libapiv3.NodeWireguardSpec{ + res.Spec.Wireguard = &internalapi.NodeWireguardSpec{ InterfaceIPv4Address: "1.2.3.4/240", } kvps, err = up.Process(&model.KVPair{ @@ -397,7 +397,7 @@ var _ = Describe("Test the (Felix) Node update processor", func() { Value: res, }) Expect(err).To(HaveOccurred()) - expected = map[string]interface{}{ + expected = map[string]any{ nodeMarker: res, wireguardMarker: nil, } @@ -409,9 +409,9 @@ var _ = Describe("Test the (Felix) Node update processor", func() { ) By("trying to convert with a tunnel address specified as a network - expect delete for that key") - res = libapiv3.NewNode() + res = internalapi.NewNode() res.Name = "mynode" - res.Spec.BGP = &libapiv3.NodeBGPSpec{ + res.Spec.BGP = &internalapi.NodeBGPSpec{ IPv4Address: "1.2.3.4/24", IPv4IPIPTunnelAddr: "192.100.100.100/24", } @@ -421,7 +421,7 @@ var _ = Describe("Test the (Felix) Node update processor", func() { }) Expect(err).To(HaveOccurred()) ip := net.MustParseIP("1.2.3.4") - expected = map[string]interface{}{ + expected = map[string]any{ hostIPMarker: &ip, nodeMarker: res, "IpInIpTunnelAddr": nil, @@ -434,15 +434,15 @@ var _ = Describe("Test the (Felix) Node update processor", func() { ) By("trying to convert a Node with IPv4 address as a network and an IPv4 VXLAN tunnel address as a network and no MAC - expect delete for those keys") - res = libapiv3.NewNode() + res = internalapi.NewNode() res.Name = "mynode" - res.Spec.BGP = &libapiv3.NodeBGPSpec{ + res.Spec.BGP = &internalapi.NodeBGPSpec{ IPv4Address: "192.100.100.100/24", } res.Spec.IPv4VXLANTunnelAddr = "192.200.200.200/32" res.Spec.VXLANTunnelMACAddr = "" ip = net.MustParseIP("192.100.100.100") - expected = map[string]interface{}{ + expected = map[string]any{ hostIPMarker: &ip, nodeMarker: res, "IPv4VXLANTunnelAddr": nil, @@ -461,14 +461,14 @@ var _ = Describe("Test the (Felix) Node update processor", func() { ) By("trying to convert a Node with IPv6 address as a network and an IPv6 VXLAN tunnel address as a network and no MAC - expect delete for those keys") - res = libapiv3.NewNode() + res = internalapi.NewNode() res.Name = "mynode" - res.Spec.BGP = &libapiv3.NodeBGPSpec{ + res.Spec.BGP = &internalapi.NodeBGPSpec{ IPv6Address: "fd10:10::10/122", } res.Spec.IPv6VXLANTunnelAddr = "fd10:11::11/122" res.Spec.VXLANTunnelMACAddrV6 = "" - expected = map[string]interface{}{ + expected = map[string]any{ hostIPMarker: nil, nodeMarker: res, "IPv6VXLANTunnelAddr": nil, @@ -487,9 +487,9 @@ var _ = Describe("Test the (Felix) Node update processor", func() { ) By("trying to convert a Node with both IPv4 and IPv6 addresses as networks and both IPv4 and IPv6 VXLAN tunnel addresses as networks and no MACs - expect delete for those keys") - res = libapiv3.NewNode() + res = internalapi.NewNode() res.Name = "mynode" - res.Spec.BGP = &libapiv3.NodeBGPSpec{ + res.Spec.BGP = &internalapi.NodeBGPSpec{ IPv4Address: "192.100.100.100/24", IPv6Address: "fd10:10::10/122", } @@ -498,7 +498,7 @@ var _ = Describe("Test the (Felix) Node update processor", func() { res.Spec.IPv6VXLANTunnelAddr = "fd10:11::11/112" res.Spec.VXLANTunnelMACAddrV6 = "" ip = net.MustParseIP("192.100.100.100") - expected = map[string]interface{}{ + expected = map[string]any{ hostIPMarker: &ip, nodeMarker: res, "IPv4VXLANTunnelAddr": nil, @@ -522,7 +522,7 @@ var _ = Describe("Test the (Felix) Node update processor", func() { var _ = Describe("Test the (Felix) Node update processor with USE_POD_CIDR=true", func() { v3NodeKey1 := model.ResourceKey{ - Kind: libapiv3.KindNode, + Kind: internalapi.KindNode, Name: "mynode", } up := updateprocessors.NewFelixNodeUpdateProcessor(true) @@ -544,7 +544,7 @@ var _ = Describe("Test the (Felix) Node update processor with USE_POD_CIDR=true" It("should properly convert nodes into blocks for Felix", func() { By("converting a node with PodCIDRs set") - res := libapiv3.NewNode() + res := internalapi.NewNode() res.Name = "mynode" res.Status.PodCIDRs = []string{ "192.168.1.0/24", diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/globalnetworkpolicyprocessor.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/globalnetworkpolicyprocessor.go index c1dac74097b..6b2520bcb94 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/globalnetworkpolicyprocessor.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/globalnetworkpolicyprocessor.go @@ -23,35 +23,32 @@ import ( "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/backend/watchersyncer" - "github.com/projectcalico/calico/libcalico-go/lib/names" ) // Create a new SyncerUpdateProcessor to sync GlobalNetworkPolicy data in v1 format for // consumption by Felix. -func NewGlobalNetworkPolicyUpdateProcessor() watchersyncer.SyncerUpdateProcessor { +func NewGlobalNetworkPolicyUpdateProcessor(keyKind string) watchersyncer.SyncerUpdateProcessor { return NewSimpleUpdateProcessor( - apiv3.KindGlobalNetworkPolicy, - convertGlobalNetworkPolicyV3ToV1Key, + keyKind, + gnpKeyConverter(keyKind), ConvertGlobalNetworkPolicyV3ToV1Value, ) } -func convertGlobalNetworkPolicyV3ToV1Key(v3key model.ResourceKey) (model.Key, error) { - if v3key.Name == "" { - return model.PolicyKey{}, errors.New("Missing Name field to create a v1 NetworkPolicy Key") +func gnpKeyConverter(kind string) func(model.ResourceKey) (model.Key, error) { + return func(v3key model.ResourceKey) (model.Key, error) { + if v3key.Name == "" { + return model.PolicyKey{}, errors.New("Missing Name field to create a model.PolicyKey") + } + return model.PolicyKey{ + Name: v3key.Name, + Namespace: "", + Kind: kind, + }, nil } - tier, err := names.TierFromPolicyName(v3key.Name) - if err != nil { - return model.PolicyKey{}, err - } - return model.PolicyKey{ - Name: v3key.Name, - Tier: tier, - }, nil - } -func ConvertGlobalNetworkPolicyV3ToV1Value(val interface{}) (interface{}, error) { +func ConvertGlobalNetworkPolicyV3ToV1Value(val any) (any, error) { v3res, ok := val.(*apiv3.GlobalNetworkPolicy) if !ok { return nil, errors.New("Value is not a valid GlobalNetworkPolicy resource value") @@ -74,6 +71,7 @@ func ConvertGlobalNetworkPolicyV3ToV1Value(val interface{}) (interface{}, error) v1value := &model.Policy{ Namespace: "", // Empty string used to signal a GlobalNetworkPolicy. + Tier: tierOrDefault(spec.Tier), Order: spec.Order, InboundRules: RulesAPIV3ToBackend(spec.Ingress, ""), OutboundRules: RulesAPIV3ToBackend(spec.Egress, ""), diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/globalnetworkpolicyprocessor_test.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/globalnetworkpolicyprocessor_test.go index 39ad4724a32..46a4158388e 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/globalnetworkpolicyprocessor_test.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/globalnetworkpolicyprocessor_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2024 Tigera, Inc. All rights reserved. +// Copyright (c) 2017-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,18 +15,18 @@ package updateprocessors_test import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - adminpolicy "sigs.k8s.io/network-policy-api/apis/v1alpha1" + clusternetpol "sigs.k8s.io/network-policy-api/apis/v1alpha2" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/backend/syncersv1/updateprocessors" + "github.com/projectcalico/calico/libcalico-go/lib/names" cnet "github.com/projectcalico/calico/libcalico-go/lib/net" ) @@ -92,7 +92,7 @@ var _ = Describe("Test the GlobalNetworkPolicy update processor", func() { noSelWithSAandNSSelector.Spec.NamespaceSelector = "name == 'testing'" Context("test processing of a valid GlobalNetworkPolicy from V3 to V1", func() { - up := updateprocessors.NewGlobalNetworkPolicyUpdateProcessor() + up := updateprocessors.NewGlobalNetworkPolicyUpdateProcessor(apiv3.KindGlobalNetworkPolicy) // Basic tests with minimal and full GlobalNetworkPolicies. It("should accept a GlobalNetworkPolicy with a minimal configuration", func() { @@ -100,10 +100,14 @@ var _ = Describe("Test the GlobalNetworkPolicy update processor", func() { Expect(err).NotTo(HaveOccurred()) Expect(kvps).To(HaveLen(1)) - v1Key := model.PolicyKey{Tier: "default", Name: "minimal"} + v1Key := model.PolicyKey{ + Name: "minimal", + Kind: apiv3.KindGlobalNetworkPolicy, + } Expect(kvps[0]).To(Equal(&model.KVPair{ Key: v1Key, Value: &model.Policy{ + Tier: "default", PreDNAT: true, ApplyOnForward: true, }, @@ -117,7 +121,10 @@ var _ = Describe("Test the GlobalNetworkPolicy update processor", func() { policy := fullGNPv1() policy.Selector = `mylabel == 'selectme'` - v1Key := model.PolicyKey{Tier: "default", Name: "full"} + v1Key := model.PolicyKey{ + Name: "full", + Kind: apiv3.KindGlobalNetworkPolicy, + } Expect(kvps).To(Equal([]*model.KVPair{{Key: v1Key, Value: &policy, Revision: testRev}})) By("should be able to delete the full network policy") @@ -139,7 +146,10 @@ var _ = Describe("Test the GlobalNetworkPolicy update processor", func() { kvps, err := up.Process(&model.KVPair{Key: emptyGNPKey, Value: apiv3.NewHostEndpoint(), Revision: testRev}) Expect(err).NotTo(HaveOccurred()) - v1Key := model.PolicyKey{Tier: "default", Name: "empty"} + v1Key := model.PolicyKey{ + Name: "empty", + Kind: apiv3.KindGlobalNetworkPolicy, + } Expect(kvps).To(Equal([]*model.KVPair{{Key: v1Key, Value: nil}})) }) @@ -150,7 +160,10 @@ var _ = Describe("Test the GlobalNetworkPolicy update processor", func() { policy := fullGNPv1() policy.Selector = `(mylabel == 'selectme') && pcsa.role == "development"` - v1Key := model.PolicyKey{Tier: "default", Name: "valid-sa-selector"} + v1Key := model.PolicyKey{ + Name: "valid-sa-selector", + Kind: apiv3.KindGlobalNetworkPolicy, + } Expect(kvps).To(Equal([]*model.KVPair{{Key: v1Key, Value: &policy, Revision: testRev}})) }) @@ -160,7 +173,10 @@ var _ = Describe("Test the GlobalNetworkPolicy update processor", func() { policy := fullGNPv1() policy.Selector = `mylabel == 'selectme'` - v1Key := model.PolicyKey{Tier: "default", Name: "invalid-sa-selector"} + v1Key := model.PolicyKey{ + Name: "invalid-sa-selector", + Kind: apiv3.KindGlobalNetworkPolicy, + } Expect(kvps).To(Equal([]*model.KVPair{{Key: v1Key, Value: &policy, Revision: testRev}})) }) @@ -170,7 +186,10 @@ var _ = Describe("Test the GlobalNetworkPolicy update processor", func() { policy := fullGNPv1() policy.Selector = `(mylabel == 'selectme') && has(projectcalico.org/serviceaccount)` - v1Key := model.PolicyKey{Tier: "default", Name: "all-sa-selector"} + v1Key := model.PolicyKey{ + Name: "all-sa-selector", + Kind: apiv3.KindGlobalNetworkPolicy, + } Expect(kvps).To(Equal([]*model.KVPair{{Key: v1Key, Value: &policy, Revision: testRev}})) }) @@ -181,7 +200,10 @@ var _ = Describe("Test the GlobalNetworkPolicy update processor", func() { policy := fullGNPv1() policy.Selector = `(mylabel == 'selectme') && pcns.name == "testing"` - v1Key := model.PolicyKey{Tier: "default", Name: "valid-ns-selector"} + v1Key := model.PolicyKey{ + Name: "valid-ns-selector", + Kind: apiv3.KindGlobalNetworkPolicy, + } Expect(kvps).To(Equal([]*model.KVPair{{Key: v1Key, Value: &policy, Revision: testRev}})) }) @@ -191,7 +213,10 @@ var _ = Describe("Test the GlobalNetworkPolicy update processor", func() { policy := fullGNPv1() policy.Selector = `mylabel == 'selectme'` - v1Key := model.PolicyKey{Tier: "default", Name: "invalid-ns-selector"} + v1Key := model.PolicyKey{ + Name: "invalid-ns-selector", + Kind: apiv3.KindGlobalNetworkPolicy, + } Expect(kvps).To(Equal([]*model.KVPair{{Key: v1Key, Value: &policy, Revision: testRev}})) }) @@ -201,7 +226,10 @@ var _ = Describe("Test the GlobalNetworkPolicy update processor", func() { policy := fullGNPv1() policy.Selector = `(mylabel == 'selectme') && has(projectcalico.org/namespace)` - v1Key := model.PolicyKey{Tier: "default", Name: "all-ns-selector"} + v1Key := model.PolicyKey{ + Name: "all-ns-selector", + Kind: apiv3.KindGlobalNetworkPolicy, + } Expect(kvps).To(Equal([]*model.KVPair{{Key: v1Key, Value: &policy, Revision: testRev}})) }) @@ -212,90 +240,123 @@ var _ = Describe("Test the GlobalNetworkPolicy update processor", func() { policy := fullGNPv1() policy.Selector = `((mylabel == 'selectme') && pcns.name == "testing") && pcsa.role == "development"` - v1Key := model.PolicyKey{Tier: "default", Name: "sa-and-ns-selector"} + v1Key := model.PolicyKey{ + Name: "sa-and-ns-selector", + Kind: apiv3.KindGlobalNetworkPolicy, + } Expect(kvps).To(Equal([]*model.KVPair{{Key: v1Key, Value: &policy, Revision: testRev}})) }) // GlobalNetworkPolicies without a Selector and with combinations of ServiceAccount and Namespace selectors. It("should accept a GlobalNetworkPolicy without a Selector but with a ServiceAccountSelector", func() { - kvps, err := up.Process(&model.KVPair{Key: noSelWithSASelectorKey, Value: noSelWithSASelector, - Revision: testRev}) + kvps, err := up.Process(&model.KVPair{ + Key: noSelWithSASelectorKey, Value: noSelWithSASelector, + Revision: testRev, + }) Expect(err).NotTo(HaveOccurred()) policy := fullGNPv1() policy.Selector = `pcsa.role == "development"` - v1Key := model.PolicyKey{Tier: "default", Name: "no-sel-with-sa-selector"} + v1Key := model.PolicyKey{ + Name: "no-sel-with-sa-selector", + Kind: apiv3.KindGlobalNetworkPolicy, + } Expect(kvps).To(Equal([]*model.KVPair{{Key: v1Key, Value: &policy, Revision: testRev}})) }) It("should accept a GlobalNetworkPolicy without a Selector but with a NamespaceSelector", func() { - kvps, err := up.Process(&model.KVPair{Key: noSelWithNSSelectorKey, Value: noSelWithNSSelector, - Revision: testRev}) + kvps, err := up.Process(&model.KVPair{ + Key: noSelWithNSSelectorKey, Value: noSelWithNSSelector, + Revision: testRev, + }) Expect(err).NotTo(HaveOccurred()) policy := fullGNPv1() policy.Selector = `pcns.name == "testing"` - v1Key := model.PolicyKey{Tier: "default", Name: "no-sel-with-ns-selector"} + v1Key := model.PolicyKey{ + Name: "no-sel-with-ns-selector", + Kind: apiv3.KindGlobalNetworkPolicy, + } Expect(kvps).To(Equal([]*model.KVPair{{Key: v1Key, Value: &policy, Revision: testRev}})) }) It("should accept a GlobalNetworkPolicy without a Selector but with a NamespaceSelector and ServiceAccountSelector", func() { - kvps, err := up.Process(&model.KVPair{Key: noSelWithSAandNSSelectorKey, - Value: noSelWithSAandNSSelector, Revision: testRev}) + kvps, err := up.Process(&model.KVPair{ + Key: noSelWithSAandNSSelectorKey, + Value: noSelWithSAandNSSelector, Revision: testRev, + }) Expect(err).NotTo(HaveOccurred()) policy := fullGNPv1() policy.Selector = `(pcns.name == "testing") && pcsa.role == "development"` - v1Key := model.PolicyKey{Tier: "default", Name: "no-sel-with-ns-and-sa-selector"} - Expect(kvps).To(Equal([]*model.KVPair{{Key: v1Key, Value: &policy, Revision: testRev}})) + v1Key := model.PolicyKey{ + Name: "no-sel-with-ns-and-sa-selector", + Kind: apiv3.KindGlobalNetworkPolicy, + } + Expect(kvps).To(Equal( + []*model.KVPair{{ + Key: v1Key, + Value: &policy, + Revision: testRev, + }}, + )) }) }) }) -// Define AdminNetworkPolicies and the corresponding expected v1 KVPairs. +// Define ClusterNetworkPolicies and the corresponding expected v1 KVPairs. // -// anp1 is an AdminNetworkPolicy with a single Egress rule, which contains ports only, +// kcnp1 is a k8s ClusterNetworkPolicy with a single Egress rule, which contains protocols only, // and no selectors. var ( - anpOrder = float64(1000.0) - ports = []adminpolicy.AdminNetworkPolicyPort{{ - PortNumber: &adminpolicy.Port{ - Port: 80, + kcnpOrder = float64(1000.0) + protos = []clusternetpol.ClusterNetworkPolicyProtocol{ + { + TCP: &clusternetpol.ClusterNetworkPolicyProtocolTCP{ + DestinationPort: &clusternetpol.Port{ + Number: 80, + }, + }, }, - }} - anp1 = adminpolicy.AdminNetworkPolicy{ + } + kcnp1 = clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.AdminNetworkPolicySpec{ - Subject: adminpolicy.AdminNetworkPolicySubject{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ + Subject: clusternetpol.ClusterNetworkPolicySubject{ Namespaces: &metav1.LabelSelector{}, }, + Tier: clusternetpol.AdminTier, Priority: 1000, - Egress: []adminpolicy.AdminNetworkPolicyEgressRule{ + Egress: []clusternetpol.ClusterNetworkPolicyEgressRule{ { - Action: "Allow", - To: []adminpolicy.AdminNetworkPolicyEgressPeer{ + Action: "Accept", + To: []clusternetpol.ClusterNetworkPolicyEgressPeer{ { Namespaces: &metav1.LabelSelector{}, }, }, - Ports: &ports, + Protocols: protos, }, }, }, } ) -// expected1 is the expected v1 KVPair representation of np1 from above. +// expected1 is the expected v1 KVPair representation of kcnp1 from above. var ( expectedModel1 = []*model.KVPair{ { - Key: model.PolicyKey{Tier: "adminnetworkpolicy", Name: "kanp.adminnetworkpolicy.test.policy"}, + Key: model.PolicyKey{ + Name: "test.policy", + Kind: model.KindKubernetesClusterNetworkPolicy, + }, Value: &model.Policy{ - Order: &anpOrder, + Tier: names.KubeAdminTierName, + Order: &kcnpOrder, Selector: "(projectcalico.org/orchestrator == 'k8s') && has(projectcalico.org/namespace)", Types: []string{"egress"}, ApplyOnForward: false, @@ -314,23 +375,24 @@ var ( } ) -// np2 is a NetworkPolicy with a single Ingress rule which allows from all namespaces. -var anp2 = adminpolicy.AdminNetworkPolicy{ +// kcnp2 is a k8s ClusterNetworkPolicy with a single Ingress rule which allows from all namespaces. +var kcnp2 = clusternetpol.ClusterNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", UID: types.UID("30316465-6365-4463-ad63-3564622d3638"), }, - Spec: adminpolicy.AdminNetworkPolicySpec{ - Subject: adminpolicy.AdminNetworkPolicySubject{ - Pods: &adminpolicy.NamespacedPod{ + Spec: clusternetpol.ClusterNetworkPolicySpec{ + Subject: clusternetpol.ClusterNetworkPolicySubject{ + Pods: &clusternetpol.NamespacedPod{ PodSelector: metav1.LabelSelector{}, }, }, Priority: 1000, - Ingress: []adminpolicy.AdminNetworkPolicyIngressRule{ + Tier: clusternetpol.BaselineTier, + Ingress: []clusternetpol.ClusterNetworkPolicyIngressRule{ { - Action: "Allow", - From: []adminpolicy.AdminNetworkPolicyIngressPeer{ + Action: "Accept", + From: []clusternetpol.ClusterNetworkPolicyIngressPeer{ { Namespaces: &metav1.LabelSelector{}, }, @@ -343,11 +405,12 @@ var anp2 = adminpolicy.AdminNetworkPolicy{ var expectedModel2 = []*model.KVPair{ { Key: model.PolicyKey{ - Name: "kanp.adminnetworkpolicy.test.policy", - Tier: "adminnetworkpolicy", + Name: "test.policy", + Kind: model.KindKubernetesClusterNetworkPolicy, }, Value: &model.Policy{ - Order: &anpOrder, + Tier: names.KubeBaselineTierName, + Order: &kcnpOrder, Selector: "(projectcalico.org/orchestrator == 'k8s') && has(projectcalico.org/namespace)", Types: []string{"ingress"}, ApplyOnForward: false, @@ -364,14 +427,14 @@ var expectedModel2 = []*model.KVPair{ }, } -var _ = Describe("Test the AdminNetworkPolicy update processor + conversion", func() { - up := updateprocessors.NewGlobalNetworkPolicyUpdateProcessor() +var _ = Describe("Test the ClusterNetworkPolicy update processor + conversion", func() { + up := updateprocessors.NewGlobalNetworkPolicyUpdateProcessor(model.KindKubernetesClusterNetworkPolicy) DescribeTable("GlobalNetworkPolicy update processor + conversion tests", - func(anp adminpolicy.AdminNetworkPolicy, expected []*model.KVPair) { - // First, convert the NetworkPolicy using the k8s conversion logic. + func(cnp clusternetpol.ClusterNetworkPolicy, expected []*model.KVPair) { + // First, convert the ClusterNetworkPolicy using the k8s conversion logic. c := conversion.NewConverter() - kvp, err := c.K8sAdminNetworkPolicyToCalico(&anp) + kvp, err := c.K8sClusterNetworkPolicyToCalico(&cnp) Expect(err).NotTo(HaveOccurred()) // Next, run the policy through the update processor. @@ -382,7 +445,7 @@ var _ = Describe("Test the AdminNetworkPolicy update processor + conversion", fu Expect(out).To(Equal(expected)) }, - Entry("should handle an AdminNetworkPolicy with no rule selectors", anp1, expectedModel1), - Entry("should handle an AdminNetworkPolicy with an empty ns selector", anp2, expectedModel2), + Entry("should handle an ClusterNetworkPolicy with no rule selectors", kcnp1, expectedModel1), + Entry("should handle an ClusterNetworkPolicy with an empty ns selector", kcnp2, expectedModel2), ) }) diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/hostendpointprocessor_test.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/hostendpointprocessor_test.go index e154a5625d1..0543f3096f5 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/hostendpointprocessor_test.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/hostendpointprocessor_test.go @@ -15,7 +15,7 @@ package updateprocessors_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/ippoolprocessor_test.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/ippoolprocessor_test.go index 67cff53a569..427b549d17b 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/ippoolprocessor_test.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/ippoolprocessor_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2019,2021 Tigera, Inc. All rights reserved. +// Copyright (c) 2019-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ package updateprocessors_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -63,7 +63,7 @@ var _ = Describe("Test the IPPool update processor", func() { Key: v1PoolKeyCidr1, Value: &model.IPPool{ CIDR: v1PoolKeyCidr1.CIDR, - IPIPMode: encap.Undefined, + IPIPMode: encap.Never, Masquerade: false, IPAM: true, Disabled: false, @@ -118,7 +118,7 @@ var _ = Describe("Test the IPPool update processor", func() { Value: &model.IPPool{ CIDR: v1PoolKeyCidr2.CIDR, IPIPInterface: "", - IPIPMode: encap.Undefined, + IPIPMode: encap.Never, Masquerade: false, IPAM: true, Disabled: false, @@ -146,7 +146,7 @@ var _ = Describe("Test the IPPool update processor", func() { Value: &model.IPPool{ CIDR: v1PoolKeyCidr2.CIDR, IPIPInterface: "", - IPIPMode: encap.Undefined, + IPIPMode: encap.Never, Masquerade: false, IPAM: true, Disabled: false, @@ -203,7 +203,7 @@ var _ = Describe("Test the IPPool update processor", func() { Key: v1PoolKeyCidr1, Value: &model.IPPool{ CIDR: v1PoolKeyCidr1.CIDR, - IPIPMode: encap.Undefined, + IPIPMode: encap.Never, Masquerade: false, IPAM: true, Disabled: false, diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/networkpolicyprocessor.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/networkpolicyprocessor.go index 5c91a3200ec..ba58936e7fc 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/networkpolicyprocessor.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/networkpolicyprocessor.go @@ -24,34 +24,35 @@ import ( "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/backend/watchersyncer" - "github.com/projectcalico/calico/libcalico-go/lib/names" ) // Create a new SyncerUpdateProcessor to sync NetworkPolicy data in v1 format for // consumption by Felix. -func NewNetworkPolicyUpdateProcessor() watchersyncer.SyncerUpdateProcessor { +func NewNetworkPolicyUpdateProcessor(keyKind string) watchersyncer.SyncerUpdateProcessor { return NewSimpleUpdateProcessor( - apiv3.KindNetworkPolicy, - convertNetworkPolicyV3ToV1Key, + keyKind, + npKeyConverter(keyKind), ConvertNetworkPolicyV3ToV1Value, ) } -func convertNetworkPolicyV3ToV1Key(v3key model.ResourceKey) (model.Key, error) { - if v3key.Name == "" || v3key.Namespace == "" { - return model.PolicyKey{}, errors.New("Missing Name or Namespace field to create a v1 NetworkPolicy Key") - } - tier, err := names.TierFromPolicyName(v3key.Name) - if err != nil { - return model.PolicyKey{}, err +// npKeyConverter returns a function that converts a v3 ResourceKey to a v1 Key +// of the specified kind. We multiplex several kinds of policies into the same model.PolicyKey, +// and so callers must specify the exact kind to use. +func npKeyConverter(kind string) func(model.ResourceKey) (model.Key, error) { + return func(v3key model.ResourceKey) (model.Key, error) { + if v3key.Name == "" || v3key.Namespace == "" { + return model.PolicyKey{}, errors.New("Missing Name or Namespace field to create a v1 NetworkPolicy Key") + } + return model.PolicyKey{ + Name: v3key.Name, + Namespace: v3key.Namespace, + Kind: kind, + }, nil } - return model.PolicyKey{ - Name: v3key.Namespace + "/" + v3key.Name, - Tier: tier, - }, nil } -func ConvertNetworkPolicyV3ToV1Value(val interface{}) (interface{}, error) { +func ConvertNetworkPolicyV3ToV1Value(val any) (any, error) { v3res, ok := val.(*apiv3.NetworkPolicy) if !ok { return nil, errors.New("Value is not a valid NetworkPolicy resource value") @@ -73,6 +74,7 @@ func ConvertNetworkPolicyV3ToV1Value(val interface{}) (interface{}, error) { v1value := &model.Policy{ Namespace: v3res.Namespace, + Tier: tierOrDefault(spec.Tier), Order: spec.Order, InboundRules: RulesAPIV3ToBackend(spec.Ingress, v3res.Namespace), OutboundRules: RulesAPIV3ToBackend(spec.Egress, v3res.Namespace), @@ -98,3 +100,10 @@ func policyTypesAPIV3ToBackend(ptypes []apiv3.PolicyType) []string { func policyTypeAPIV3ToBackend(ptype apiv3.PolicyType) string { return strings.ToLower(string(ptype)) } + +func tierOrDefault(tier string) string { + if tier == "" { + return "default" + } + return tier +} diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/networkpolicyprocessor_test.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/networkpolicyprocessor_test.go index e83a3e2b809..b3c3c5b5e1b 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/networkpolicyprocessor_test.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/networkpolicyprocessor_test.go @@ -17,8 +17,7 @@ package updateprocessors_test import ( "fmt" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -28,7 +27,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/backend/syncersv1/updateprocessors" @@ -66,17 +65,22 @@ var _ = Describe("Test the NetworkPolicy update processor", func() { allSASelector.Spec.ServiceAccountSelector = "all()" Context("test processing of a valid NetworkPolicy from V3 to V1", func() { - up := updateprocessors.NewNetworkPolicyUpdateProcessor() + up := updateprocessors.NewNetworkPolicyUpdateProcessor(apiv3.KindNetworkPolicy) It("should accept a NetworkPolicy with a minimal configuration", func() { kvps, err := up.Process(&model.KVPair{Key: minimalNPKey, Value: minimalNP, Revision: testRev}) Expect(err).NotTo(HaveOccurred()) Expect(kvps).To(HaveLen(1)) - v1Key := model.PolicyKey{Tier: "default", Name: ns1 + "/minimal"} + v1Key := model.PolicyKey{ + Name: "minimal", + Namespace: ns1, + Kind: apiv3.KindNetworkPolicy, + } Expect(kvps[0]).To(Equal(&model.KVPair{ Key: v1Key, Value: &model.Policy{ + Tier: "default", Namespace: ns1, Selector: "projectcalico.org/namespace == 'namespace1'", ApplyOnForward: false, @@ -92,7 +96,11 @@ var _ = Describe("Test the NetworkPolicy update processor", func() { policy := fullNPv1(ns2) policy.Selector = fmt.Sprintf("(mylabel == 'selectme') && projectcalico.org/namespace == '%s'", ns2) - v1Key := model.PolicyKey{Tier: "default", Name: ns2 + "/full"} + v1Key := model.PolicyKey{ + Name: "full", + Namespace: ns2, + Kind: apiv3.KindNetworkPolicy, + } Expect(kvps).To(Equal([]*model.KVPair{{Key: v1Key, Value: &policy, Revision: testRev}})) By("should be able to delete the full network policy") @@ -114,7 +122,11 @@ var _ = Describe("Test the NetworkPolicy update processor", func() { kvps, err := up.Process(&model.KVPair{Key: emptyNPKey, Value: apiv3.NewHostEndpoint(), Revision: testRev}) Expect(err).NotTo(HaveOccurred()) - v1Key := model.PolicyKey{Tier: "default", Name: ns1 + "/empty"} + v1Key := model.PolicyKey{ + Name: "empty", + Namespace: ns1, + Kind: apiv3.KindNetworkPolicy, + } Expect(kvps).To(Equal([]*model.KVPair{{Key: v1Key, Value: nil}})) }) @@ -124,7 +136,11 @@ var _ = Describe("Test the NetworkPolicy update processor", func() { policy := fullNPv1(ns2) policy.Selector = `((mylabel == 'selectme') && projectcalico.org/namespace == 'namespace2') && pcsa.role == "development"` - v1Key := model.PolicyKey{Tier: "default", Name: ns2 + "/valid-sa-selector"} + v1Key := model.PolicyKey{ + Name: "valid-sa-selector", + Namespace: ns2, + Kind: apiv3.KindNetworkPolicy, + } Expect(kvps).To(Equal([]*model.KVPair{{Key: v1Key, Value: &policy, Revision: testRev}})) }) @@ -134,7 +150,11 @@ var _ = Describe("Test the NetworkPolicy update processor", func() { policy := fullNPv1(ns2) policy.Selector = `(mylabel == 'selectme') && projectcalico.org/namespace == 'namespace2'` - v1Key := model.PolicyKey{Tier: "default", Name: ns2 + "/invalid-sa-selector"} + v1Key := model.PolicyKey{ + Namespace: ns2, + Name: "invalid-sa-selector", + Kind: apiv3.KindNetworkPolicy, + } Expect(kvps).To(Equal([]*model.KVPair{{Key: v1Key, Value: &policy, Revision: testRev}})) }) @@ -144,7 +164,11 @@ var _ = Describe("Test the NetworkPolicy update processor", func() { policy := fullNPv1(ns2) policy.Selector = `((mylabel == 'selectme') && projectcalico.org/namespace == 'namespace2') && all()` - v1Key := model.PolicyKey{Tier: "default", Name: ns2 + "/all-sa-selector"} + v1Key := model.PolicyKey{ + Namespace: ns2, + Name: "all-sa-selector", + Kind: apiv3.KindNetworkPolicy, + } Expect(kvps).To(Equal([]*model.KVPair{{Key: v1Key, Value: &policy, Revision: testRev}})) }) }) @@ -152,12 +176,12 @@ var _ = Describe("Test the NetworkPolicy update processor", func() { // Define network policies and the corresponding expected v1 KVPairs. // -// np1 is a NetworkPolicy with a single Egress rule, which contains ports only, +// knp1 is a NetworkPolicy with a single Egress rule, which contains ports only, // and no selectors. var ( protocol = kapiv1.ProtocolTCP port = intstr.FromInt(80) - np1 = networkingv1.NetworkPolicy{ + knp1 = networkingv1.NetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", Namespace: "default", @@ -180,13 +204,18 @@ var ( } ) -// expected1 is the expected v1 KVPair representation of np1 from above. +// expectedKNP1 is the expected v1 KVPair representation of np1 from above. var ( - tcp = numorstring.ProtocolFromStringV1("tcp") - expected1 = []*model.KVPair{ + tcp = numorstring.ProtocolFromStringV1("tcp") + expectedKNP1 = []*model.KVPair{ { - Key: model.PolicyKey{Tier: "default", Name: "default/knp.default.test.policy"}, + Key: model.PolicyKey{ + Name: "test.policy", + Namespace: "default", + Kind: model.KindKubernetesNetworkPolicy, + }, Value: &model.Policy{ + Tier: "default", Namespace: "default", Order: &testDefaultPolicyOrder, Selector: "(projectcalico.org/orchestrator == 'k8s') && projectcalico.org/namespace == 'default'", @@ -206,8 +235,8 @@ var ( } ) -// np2 is a NetworkPolicy with a single Ingress rule which allows from all namespaces. -var np2 = networkingv1.NetworkPolicy{ +// knp2 is a NetworkPolicy with a single Ingress rule which allows from all namespaces. +var knp2 = networkingv1.NetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "test.policy", Namespace: "default", @@ -228,13 +257,15 @@ var np2 = networkingv1.NetworkPolicy{ }, } -var expected2 = []*model.KVPair{ +var expectedKNP2 = []*model.KVPair{ { Key: model.PolicyKey{ - Name: "default/knp.default.test.policy", - Tier: "default", + Name: "test.policy", + Namespace: "default", + Kind: model.KindKubernetesNetworkPolicy, }, Value: &model.Policy{ + Tier: "default", Namespace: "default", Order: &testDefaultPolicyOrder, Selector: "(projectcalico.org/orchestrator == 'k8s') && projectcalico.org/namespace == 'default'", @@ -253,8 +284,8 @@ var expected2 = []*model.KVPair{ }, } -var _ = Describe("Test the NetworkPolicy update processor + conversion", func() { - up := updateprocessors.NewNetworkPolicyUpdateProcessor() +var _ = Describe("Test KubernetesNetworkPolicy update processor + conversion", func() { + up := updateprocessors.NewNetworkPolicyUpdateProcessor(model.KindKubernetesNetworkPolicy) DescribeTable("NetworkPolicy update processor + conversion tests", func(np networkingv1.NetworkPolicy, expected []*model.KVPair) { @@ -271,14 +302,14 @@ var _ = Describe("Test the NetworkPolicy update processor + conversion", func() Expect(out).To(Equal(expected)) }, - Entry("should handle a NetworkPolicy with no rule selectors", np1, expected1), - Entry("should handle a NetworkPolicy with an empty ns selector", np2, expected2), + Entry("should handle a NetworkPolicy with no rule selectors", knp1, expectedKNP1), + Entry("should handle a NetworkPolicy with an empty ns selector", knp2, expectedKNP2), ) }) var _ = Describe("Test end-to-end pod and network policy processing", func() { // Define processors to use in the test. - npProcessor := updateprocessors.NewNetworkPolicyUpdateProcessor() + npProcessor := updateprocessors.NewNetworkPolicyUpdateProcessor(apiv3.KindNetworkPolicy) wepProcessor := updateprocessors.NewWorkloadEndpointUpdateProcessor() It("should handle a basic pod and network policy", func() { @@ -482,7 +513,7 @@ var _ = Describe("Test end-to-end pod and network policy processing", func() { Expect(len(kvps)).To(Equal(1)) // Expect the serviceaccount name to be set on the resulting WEP. - Expect(kvps[0].Value.(*libapiv3.WorkloadEndpoint).Spec.ServiceAccountName).To(Equal(longName)) + Expect(kvps[0].Value.(*internalapi.WorkloadEndpoint).Spec.ServiceAccountName).To(Equal(longName)) // Process kvps, err = wepProcessor.Process(kvps[0]) diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/networksetprocessor.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/networksetprocessor.go index 5221bd08c62..8fbb76d556d 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/networksetprocessor.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/networksetprocessor.go @@ -16,6 +16,7 @@ package updateprocessors import ( "errors" + "maps" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" @@ -29,7 +30,11 @@ import ( // Create a new SyncerUpdateProcessor to sync NetworkSet data in v1 format for consumption by Felix. func NewNetworkSetUpdateProcessor() watchersyncer.SyncerUpdateProcessor { - return NewSimpleUpdateProcessor(apiv3.KindNetworkSet, convertNetworkSetV2ToV1Key, convertNetworkSetV2ToV1Value) + return NewSimpleUpdateProcessor( + apiv3.KindNetworkSet, + convertNetworkSetV2ToV1Key, + convertNetworkSetV2ToV1Value, + ) } func convertNetworkSetV2ToV1Key(v3key model.ResourceKey) (model.Key, error) { @@ -41,7 +46,7 @@ func convertNetworkSetV2ToV1Key(v3key model.ResourceKey) (model.Key, error) { }, nil } -func convertNetworkSetV2ToV1Value(val interface{}) (interface{}, error) { +func convertNetworkSetV2ToV1Value(val any) (any, error) { v3res, ok := val.(*apiv3.NetworkSet) if !ok { return nil, errors.New("Value is not a valid NetworkSet resource value") @@ -49,7 +54,7 @@ func convertNetworkSetV2ToV1Value(val interface{}) (interface{}, error) { var addrs []cnet.IPNet for _, cidrString := range v3res.Spec.Nets { - _, ipNet, err := cnet.ParseCIDROrIP(cidrString) + _, ipNet, err := cnet.ParseCIDROrIP(string(cidrString)) if err != nil { log.WithError(err).WithFields(log.Fields{ "CIDR": cidrString, @@ -61,9 +66,7 @@ func convertNetworkSetV2ToV1Value(val interface{}) (interface{}, error) { // Add in the Calico namespace label for storage purposes. labelsWithCalicoNamespace := make(map[string]string, len(v3res.GetLabels())+1) - for k, v := range v3res.GetLabels() { - labelsWithCalicoNamespace[k] = v - } + maps.Copy(labelsWithCalicoNamespace, v3res.GetLabels()) labelsWithCalicoNamespace[apiv3.LabelNamespace] = v3res.Namespace // Also include the namespace profile in the profile IDs so that we get namespace label inheritance. diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/networksetprocessor_test.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/networksetprocessor_test.go index 7ffe25ae486..208d462a489 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/networksetprocessor_test.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/networksetprocessor_test.go @@ -15,7 +15,7 @@ package updateprocessors_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -75,7 +75,10 @@ var _ = Describe("Test the NetworkSet update processor", func() { By("adding another CIDR to the existing NetworkSet") cidr2str := "1.2.3.123/32" _, cidr2IPNet, _ := net.ParseCIDROrIP(cidr2str) - res.Spec.Nets = []string{cidr1str, cidr2str} + res.Spec.Nets = []string{ + cidr1str, + cidr2str, + } kvps, err = up.Process(&model.KVPair{ Key: v3NetworkSetKey1, diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/processor.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/processor.go index 8ce96bd318e..a0818b51c21 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/processor.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/processor.go @@ -15,7 +15,6 @@ package updateprocessors import ( - "errors" "fmt" log "github.com/sirupsen/logrus" @@ -34,47 +33,44 @@ import ( // resource key. The simple update processor is for converting objects that can create // the v1 key using only the information available in the resource key. -// Function signature to convert a v3 model.ResourceKey to a v1 model.Key type -type ConvertV2ToV1Key func(v3Key model.ResourceKey) (model.Key, error) +// Function signature to convert a v3 model.KVPair to a v1 model.Key type +type ConvertV3KeyToV1Key func(key model.ResourceKey) (model.Key, error) // Function to convert a v3 resource to the v1 value. The converter may filter out // results by returning nil. The generic watchersyncer will handle filtered out events // by either sending no event or sending delete events depending on whether the entry // is currently in the cache. -type ConvertV2ToV1Value func(interface{}) (interface{}, error) +type ConvertV3ToV1Value func(any) (any, error) -func NewSimpleUpdateProcessor(v3Kind string, kConverter ConvertV2ToV1Key, vConverter ConvertV2ToV1Value) watchersyncer.SyncerUpdateProcessor { - return &simpleUpdateProcessor{ +func NewSimpleUpdateProcessor(v3Kind string, kConverter ConvertV3KeyToV1Key, vConverter ConvertV3ToV1Value) watchersyncer.SyncerUpdateProcessor { + up := &simpleUpdateProcessor{ v3Kind: v3Kind, keyConverter: kConverter, valueConverter: vConverter, } + return up } type simpleUpdateProcessor struct { v3Kind string - keyConverter ConvertV2ToV1Key - valueConverter ConvertV2ToV1Value + keyConverter ConvertV3KeyToV1Key + valueConverter ConvertV3ToV1Value } func (sup *simpleUpdateProcessor) Process(kvp *model.KVPair) ([]*model.KVPair, error) { // Check the v3 resource is the correct type. rk, ok := kvp.Key.(model.ResourceKey) if !ok || rk.Kind != sup.v3Kind { - return nil, fmt.Errorf("Incorrect key type - expecting resource of kind %s", sup.v3Kind) + return nil, fmt.Errorf("Incorrect key type %s - expecting resource of kind %s", rk.Kind, sup.v3Kind) } - // Convert the v3 resource to the equivalent v1 resource type. - v3key, ok := kvp.Key.(model.ResourceKey) - if !ok { - return nil, errors.New("Key is not a valid V2 resource key") - } - v1key, err := sup.keyConverter(v3key) + // Convert the v3 resource to the equivalent v1 resource type, started with the Key. + v1key, err := sup.keyConverter(rk) if err != nil { - return nil, err + return nil, fmt.Errorf("Unable to convert v3 key to v1 key: %v", err) } - var v1value interface{} + var v1value any // Deletion events will have a value of nil. Do not convert anything for a deletion event. if kvp.Value != nil { v1value, err = sup.valueConverter(kvp.Value) diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/profileprocessor.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/profileprocessor.go index 88beb7e031b..52d9924c4a8 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/profileprocessor.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/profileprocessor.go @@ -101,7 +101,7 @@ func (pup *profileUpdateProcessor) OnSyncerStarting() { // Do nothing } -func convertProfileV2ToV1Value(val interface{}) (*model.Profile, error) { +func convertProfileV2ToV1Value(val any) (*model.Profile, error) { v3res, ok := val.(*apiv3.Profile) if !ok { return nil, errors.New("Value is not a valid Profile resource value") diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/profileprocessor_test.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/profileprocessor_test.go index b32e01c37d5..94912fdd280 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/profileprocessor_test.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/profileprocessor_test.go @@ -15,7 +15,7 @@ package updateprocessors_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/rules.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/rules.go index 962c1e4c29c..c5034992d93 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/rules.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/rules.go @@ -16,6 +16,7 @@ package updateprocessors import ( "fmt" + "maps" "strings" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -207,9 +208,7 @@ func RuleAPIV3ToBackend(ar apiv3.Rule, ns string) model.Rule { if ar.Metadata != nil { if ar.Metadata.Annotations != nil { r.Metadata = &model.RuleMetadata{Annotations: make(map[string]string)} - for k, v := range ar.Metadata.Annotations { - r.Metadata.Annotations[k] = v - } + maps.Copy(r.Metadata.Annotations, ar.Metadata.Annotations) } } return r diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/rules_test.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/rules_test.go index 8903a6ac401..fa3e7586435 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/rules_test.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/rules_test.go @@ -17,7 +17,7 @@ package updateprocessors_test import ( "fmt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/shared_test.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/shared_test.go index 5a9babebb7a..9e64a0f48f8 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/shared_test.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/shared_test.go @@ -11,23 +11,27 @@ import ( cnet "github.com/projectcalico/calico/libcalico-go/lib/net" ) -var srcSelector string = "mylabel == selector1" -var dstSelector string = "mylabel == selector2" -var notSrcSelector string = "has(label1)" -var notDstSelector string = "has(label2)" +var ( + srcSelector string = "mylabel == selector1" + dstSelector string = "mylabel == selector2" + notSrcSelector string = "has(label1)" + notDstSelector string = "has(label2)" +) // v1 and v3 ingress rule. -var v4 = 4 -var itype = 1 -var intype = 3 -var icode = 4 -var incode = 6 -var ProtocolTCPV1 = numorstring.ProtocolFromStringV1("tcp") -var ProtocolUDPV1 = numorstring.ProtocolFromStringV1("udp") -var port80 = numorstring.SinglePort(uint16(80)) -var Port443 = numorstring.SinglePort(uint16(443)) -var ProtocolTCPv3 = numorstring.ProtocolFromString("TCP") -var ProtocolUDPv3 = numorstring.ProtocolFromString("UDP") +var ( + v4 = 4 + itype = 1 + intype = 3 + icode = 4 + incode = 6 + ProtocolTCPV1 = numorstring.ProtocolFromStringV1("tcp") + ProtocolUDPV1 = numorstring.ProtocolFromStringV1("udp") + port80 = numorstring.SinglePort(uint16(80)) + Port443 = numorstring.SinglePort(uint16(443)) + ProtocolTCPv3 = numorstring.ProtocolFromString("TCP") + ProtocolUDPv3 = numorstring.ProtocolFromString("UDP") +) var v1TestIngressRule = model.Rule{ Action: "allow", @@ -85,12 +89,14 @@ var v3TestIngressRule = apiv3.Rule{ } // v1 and v3 egress rule. -var etype = 2 -var entype = 7 -var ecode = 5 -var encode = 8 -var eproto = numorstring.ProtocolFromInt(uint8(30)) -var enproto = numorstring.ProtocolFromInt(uint8(62)) +var ( + etype = 2 + entype = 7 + ecode = 5 + encode = 8 + eproto = numorstring.ProtocolFromInt(uint8(30)) + enproto = numorstring.ProtocolFromInt(uint8(62)) +) var v1TestEgressRule = model.Rule{ Action: "allow", @@ -153,8 +159,10 @@ var v3TestEgressRule = apiv3.Rule{ }, } -var testPolicyOrder101 = float64(101) -var testDefaultPolicyOrder = float64(1000) +var ( + testPolicyOrder101 = float64(101) + testDefaultPolicyOrder = float64(1000) +) // v3 model.KVPair revision var testRev string = "1234" @@ -167,6 +175,7 @@ func mustParseCIDR(cidr string) *cnet.IPNet { // fullGNPv1 returns a v1 GNP with all fields filled out. func fullGNPv1() (p model.Policy) { return model.Policy{ + Tier: "default", Order: &testPolicyOrder101, DoNotTrack: true, InboundRules: []model.Rule{v1TestIngressRule}, @@ -204,6 +213,7 @@ func fullNPv1(namespace string) (p model.Policy) { } return model.Policy{ + Tier: "default", Namespace: namespace, Order: &testPolicyOrder101, InboundRules: []model.Rule{ir}, diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/stagedglobalnetworkpolicyprocessor.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/stagedglobalnetworkpolicyprocessor.go index dbf3e14ce77..9c1d7b651e6 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/stagedglobalnetworkpolicyprocessor.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/stagedglobalnetworkpolicyprocessor.go @@ -22,7 +22,6 @@ import ( "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/backend/watchersyncer" - "github.com/projectcalico/calico/libcalico-go/lib/names" ) // NewStagedGlobalNetworkPolicyUpdateProcessor create a new SyncerUpdateProcessor to sync StagedGlobalNetworkPolicy data @@ -39,18 +38,14 @@ func ConvertStagedGlobalNetworkPolicyV3ToV1Key(v3key model.ResourceKey) (model.K if v3key.Name == "" { return model.PolicyKey{}, errors.New("Missing Name field to create a v1 NetworkPolicy Key") } - tier, err := names.TierFromPolicyName(v3key.Name) - if err != nil { - return model.PolicyKey{}, err - } return model.PolicyKey{ - Name: model.PolicyNamePrefixStaged + v3key.Name, - Tier: tier, + Name: v3key.Name, + Namespace: "", + Kind: apiv3.KindStagedGlobalNetworkPolicy, }, nil - } -func ConvertStagedGlobalNetworkPolicyV3ToV1Value(val interface{}) (interface{}, error) { +func ConvertStagedGlobalNetworkPolicyV3ToV1Value(val any) (any, error) { staged, ok := val.(*apiv3.StagedGlobalNetworkPolicy) if !ok { return nil, errors.New("Value is not a valid StagedGlobalNetworkPolicy resource value") diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/stagedglobalnetworkpolicyprocessor_test.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/stagedglobalnetworkpolicyprocessor_test.go index c8aeec1bf1b..34ed654899d 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/stagedglobalnetworkpolicyprocessor_test.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/stagedglobalnetworkpolicyprocessor_test.go @@ -15,7 +15,7 @@ package updateprocessors_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -44,16 +44,16 @@ var _ = Describe("Test the StagedGlobalNetworkPolicy update processor", func() { Name: name3, } v1StagedGlobalNetworkPolicyKey1 := model.PolicyKey{ - Name: model.PolicyNamePrefixStaged + name1, - Tier: "default", + Name: name1, + Kind: apiv3.KindStagedGlobalNetworkPolicy, } v1StagedGlobalNetworkPolicyKey2 := model.PolicyKey{ - Name: model.PolicyNamePrefixStaged + name2, - Tier: "default", + Name: name2, + Kind: apiv3.KindStagedGlobalNetworkPolicy, } v1StagedGlobalNetworkPolicyKey3 := model.PolicyKey{ - Name: model.PolicyNamePrefixStaged + name3, - Tier: mytier, + Name: name3, + Kind: apiv3.KindStagedGlobalNetworkPolicy, } It("should handle conversion of valid StagedGlobalNetworkPolicys", func() { @@ -75,6 +75,7 @@ var _ = Describe("Test the StagedGlobalNetworkPolicy update processor", func() { Expect(kvps[0]).To(Equal(&model.KVPair{ Key: v1StagedGlobalNetworkPolicyKey1, Value: &model.Policy{ + Tier: "default", PreDNAT: true, ApplyOnForward: true, StagedAction: &res.Spec.StagedAction, @@ -185,6 +186,7 @@ var _ = Describe("Test the StagedGlobalNetworkPolicy update processor", func() { { Key: v1StagedGlobalNetworkPolicyKey2, Value: &model.Policy{ + Tier: "default", Order: &order, InboundRules: []model.Rule{v1irule}, OutboundRules: []model.Rule{v1erule}, @@ -215,6 +217,7 @@ var _ = Describe("Test the StagedGlobalNetworkPolicy update processor", func() { Expect(kvps[0]).To(Equal(&model.KVPair{ Key: v1StagedGlobalNetworkPolicyKey3, Value: &model.Policy{ + Tier: mytier, PreDNAT: true, ApplyOnForward: true, StagedAction: &res.Spec.StagedAction, diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/stagedkubernetesnetworkpolicyprocessor.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/stagedkubernetesnetworkpolicyprocessor.go index 42a00f7e2c7..c22fa749c9b 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/stagedkubernetesnetworkpolicyprocessor.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/stagedkubernetesnetworkpolicyprocessor.go @@ -23,7 +23,6 @@ import ( "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/backend/watchersyncer" - "github.com/projectcalico/calico/libcalico-go/lib/names" ) // NewStagedKubernetesNetworkPolicyUpdateProcessor create a new SyncerUpdateProcessor to sync StagedKubernetesNetworkPolicy data in v1 format for @@ -41,20 +40,14 @@ func ConvertStagedKubernetesNetworkPolicyV3ToV1Key(v3key model.ResourceKey) (mod return model.PolicyKey{}, errors.New("Missing Name or Namespace field to create a v1 StagedKubernetesNetworkPolicy Key") } - c := conversion.NewConverter() - name := c.StagedKubernetesNetworkPolicyToStagedName(v3key.Name) - - tier, err := names.TierFromPolicyName(name) - if err != nil { - return model.PolicyKey{}, err - } return model.PolicyKey{ - Name: v3key.Namespace + "/" + model.PolicyNamePrefixStaged + name, - Tier: tier, + Name: v3key.Name, + Namespace: v3key.Namespace, + Kind: apiv3.KindStagedKubernetesNetworkPolicy, }, nil } -func ConvertStagedKubernetesNetworkPolicyV3ToV1Value(val interface{}) (interface{}, error) { +func ConvertStagedKubernetesNetworkPolicyV3ToV1Value(val any) (any, error) { staged, ok := val.(*apiv3.StagedKubernetesNetworkPolicy) if !ok { return nil, errors.New("Value is not a valid StagedKubernetesNetworkPolicy resource value") diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/stagedkubernetesnetworkpolicyprocessor_test.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/stagedkubernetesnetworkpolicyprocessor_test.go index e5346604e3f..0abd67055e6 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/stagedkubernetesnetworkpolicyprocessor_test.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/stagedkubernetesnetworkpolicyprocessor_test.go @@ -15,7 +15,7 @@ package updateprocessors_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -34,8 +34,9 @@ var _ = Describe("Test the StagedKubernetesNetworkPolicy update processor", func } v1StagedKubernetesNetworkPolicyKey1 := model.PolicyKey{ - Name: ns1 + "/" + model.PolicyNamePrefixStaged + "knp.default." + name1, - Tier: "default", + Kind: apiv3.KindStagedKubernetesNetworkPolicy, + Name: name1, + Namespace: ns1, } It("should handle conversion of valid StagedKubernetesNetworkPolicy", func() { @@ -58,6 +59,7 @@ var _ = Describe("Test the StagedKubernetesNetworkPolicy update processor", func Expect(kvps[0]).To(Equal(&model.KVPair{ Key: v1StagedKubernetesNetworkPolicyKey1, Value: &model.Policy{ + Tier: "default", Namespace: ns1, Selector: "(projectcalico.org/orchestrator == 'k8s') && projectcalico.org/namespace == 'namespace1'", ApplyOnForward: false, @@ -68,5 +70,4 @@ var _ = Describe("Test the StagedKubernetesNetworkPolicy update processor", func Revision: "abcde", })) }) - }) diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/stagednetworkpolicyprocessor.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/stagednetworkpolicyprocessor.go index d4331609326..c4680d974cb 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/stagednetworkpolicyprocessor.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/stagednetworkpolicyprocessor.go @@ -22,7 +22,6 @@ import ( "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/backend/watchersyncer" - "github.com/projectcalico/calico/libcalico-go/lib/names" ) // NewStagedNetworkPolicyUpdateProcessor create a new SyncerUpdateProcessor to sync StagedNetworkPolicy data in v1 format for @@ -39,17 +38,14 @@ func ConvertStagedNetworkPolicyV3ToV1Key(v3key model.ResourceKey) (model.Key, er if v3key.Name == "" || v3key.Namespace == "" { return model.PolicyKey{}, errors.New("Missing Name or Namespace field to create a v1 StagedNetworkPolicy Key") } - tier, err := names.TierFromPolicyName(v3key.Name) - if err != nil { - return model.PolicyKey{}, err - } return model.PolicyKey{ - Name: v3key.Namespace + "/" + model.PolicyNamePrefixStaged + v3key.Name, - Tier: tier, + Name: v3key.Name, + Namespace: v3key.Namespace, + Kind: apiv3.KindStagedNetworkPolicy, }, nil } -func ConvertStagedNetworkPolicyV3ToV1Value(val interface{}) (interface{}, error) { +func ConvertStagedNetworkPolicyV3ToV1Value(val any) (any, error) { staged, ok := val.(*apiv3.StagedNetworkPolicy) if !ok { return nil, errors.New("Value is not a valid StagedNetworkPolicy resource value") diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/stagednetworkpolicyprocessor_test.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/stagednetworkpolicyprocessor_test.go index 24c7790a537..55bfb7bc2bd 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/stagednetworkpolicyprocessor_test.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/stagednetworkpolicyprocessor_test.go @@ -15,7 +15,7 @@ package updateprocessors_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -50,16 +50,19 @@ var _ = Describe("Test the StagedNetworkPolicy update processor", func() { Namespace: ns3, } v1StagedNetworkPolicyKey1 := model.PolicyKey{ - Name: ns1 + "/" + model.PolicyNamePrefixStaged + name1, - Tier: "default", + Name: name1, + Namespace: ns1, + Kind: apiv3.KindStagedNetworkPolicy, } v1StagedNetworkPolicyKey2 := model.PolicyKey{ - Name: ns2 + "/" + model.PolicyNamePrefixStaged + name2, - Tier: "default", + Name: name2, + Namespace: ns2, + Kind: apiv3.KindStagedNetworkPolicy, } v1StagedNetworkPolicyKey3 := model.PolicyKey{ - Name: ns3 + "/" + model.PolicyNamePrefixStaged + name3, - Tier: mytier, + Name: name3, + Namespace: ns3, + Kind: apiv3.KindStagedNetworkPolicy, } It("should handle conversion of valid StagedNetworkPolicys", func() { @@ -81,6 +84,7 @@ var _ = Describe("Test the StagedNetworkPolicy update processor", func() { Expect(kvps[0]).To(Equal(&model.KVPair{ Key: v1StagedNetworkPolicyKey1, Value: &model.Policy{ + Tier: "default", Namespace: ns1, Selector: "projectcalico.org/namespace == 'namespace1'", ApplyOnForward: false, @@ -192,6 +196,7 @@ var _ = Describe("Test the StagedNetworkPolicy update processor", func() { { Key: v1StagedNetworkPolicyKey2, Value: &model.Policy{ + Tier: "default", Namespace: ns2, Order: &order, InboundRules: []model.Rule{v1irule}, @@ -220,6 +225,7 @@ var _ = Describe("Test the StagedNetworkPolicy update processor", func() { Expect(kvps[0]).To(Equal(&model.KVPair{ Key: v1StagedNetworkPolicyKey3, Value: &model.Policy{ + Tier: mytier, Namespace: "namespace3", Selector: "projectcalico.org/namespace == 'namespace3'", ApplyOnForward: false, diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/tierprocessor.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/tierprocessor.go index 91086a1b03e..2c6c6560519 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/tierprocessor.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/tierprocessor.go @@ -26,7 +26,11 @@ import ( // Create a new SyncerUpdateProcessor to sync Tiers data in v1 format for // consumption by Felix. func NewTierUpdateProcessor() watchersyncer.SyncerUpdateProcessor { - return NewSimpleUpdateProcessor(apiv3.KindTier, ConvertTierV3ToV1Key, ConvertTierV3ToV1Value) + return NewSimpleUpdateProcessor( + apiv3.KindTier, + ConvertTierV3ToV1Key, + ConvertTierV3ToV1Value, + ) } func ConvertTierV3ToV1Key(v3key model.ResourceKey) (model.Key, error) { @@ -36,10 +40,9 @@ func ConvertTierV3ToV1Key(v3key model.ResourceKey) (model.Key, error) { return model.TierKey{ Name: v3key.Name, }, nil - } -func ConvertTierV3ToV1Value(val interface{}) (interface{}, error) { +func ConvertTierV3ToV1Value(val any) (any, error) { v3res, ok := val.(*apiv3.Tier) if !ok { return nil, errors.New("Value is not a valid Tier resource value") diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/tierprocessor_test.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/tierprocessor_test.go index 5b18b230de6..21456b09775 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/tierprocessor_test.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/tierprocessor_test.go @@ -15,7 +15,7 @@ package updateprocessors_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/updateprocessors_suite_test.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/updateprocessors_suite_test.go index 4cf2f481221..a74ad9a0c8c 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/updateprocessors_suite_test.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/updateprocessors_suite_test.go @@ -17,16 +17,16 @@ package updateprocessors import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestClient(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../../report/syncer_update_proc_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Syncer update processors suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../../report/syncer_update_proc_suite.xml" + ginkgo.RunSpecs(t, "Syncer update processors suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/workloadendpointprocessor.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/workloadendpointprocessor.go index 04cccebcc5b..4907b28dabf 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/workloadendpointprocessor.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/workloadendpointprocessor.go @@ -24,7 +24,7 @@ import ( log "github.com/sirupsen/logrus" "github.com/projectcalico/calico/lib/std/uniquelabels" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/backend/watchersyncer" @@ -35,15 +35,19 @@ import ( // Create a new SyncerUpdateProcessor to sync WorkloadEndpoint data in v1 format for // consumption by Felix. func NewWorkloadEndpointUpdateProcessor() watchersyncer.SyncerUpdateProcessor { - return NewSimpleUpdateProcessor(libapiv3.KindWorkloadEndpoint, convertWorkloadEndpointV3ToV1Key, convertWorkloadEndpointV2ToV1Value) + return NewSimpleUpdateProcessor( + internalapi.KindWorkloadEndpoint, + convertWorkloadEndpointV3ToV1Key, + convertWorkloadEndpointV2ToV1Value, + ) } func convertWorkloadEndpointV3ToV1Key(key model.ResourceKey) (model.Key, error) { return names.ConvertWorkloadEndpointV3KeyToV1Key(key) } -func convertWorkloadEndpointV2ToV1Value(val interface{}) (interface{}, error) { - v3res, ok := val.(*libapiv3.WorkloadEndpoint) +func convertWorkloadEndpointV2ToV1Value(val any) (any, error) { + v3res, ok := val.(*internalapi.WorkloadEndpoint) if !ok { return nil, errors.New("Value is not a valid WorkloadEndpoint resource value") } @@ -186,7 +190,7 @@ func convertWorkloadEndpointV2ToV1Value(val interface{}) (interface{}, error) { return v1value, nil } -func ConvertV2ToV1IPNAT(ipnat libapiv3.IPNAT) *model.IPNAT { +func ConvertV2ToV1IPNAT(ipnat internalapi.IPNAT) *model.IPNAT { internalip := cnet.ParseIP(ipnat.InternalIP) externalip := cnet.ParseIP(ipnat.ExternalIP) if internalip != nil && externalip != nil { diff --git a/libcalico-go/lib/backend/syncersv1/updateprocessors/workloadendpointprocessor_test.go b/libcalico-go/lib/backend/syncersv1/updateprocessors/workloadendpointprocessor_test.go index d2e5c2ab5c9..cba6e89cb29 100644 --- a/libcalico-go/lib/backend/syncersv1/updateprocessors/workloadendpointprocessor_test.go +++ b/libcalico-go/lib/backend/syncersv1/updateprocessors/workloadendpointprocessor_test.go @@ -17,13 +17,13 @@ package updateprocessors_test import ( "net" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" "github.com/projectcalico/calico/lib/std/uniquelabels" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/backend/syncersv1/updateprocessors" @@ -47,12 +47,12 @@ var _ = Describe("Test the WorkloadEndpoint update processor", func() { iface2 := "iface2" v3WorkloadEndpointKey1 := model.ResourceKey{ - Kind: libapiv3.KindWorkloadEndpoint, + Kind: internalapi.KindWorkloadEndpoint, Name: name1, Namespace: ns1, } v3WorkloadEndpointKey2 := model.ResourceKey{ - Kind: libapiv3.KindWorkloadEndpoint, + Kind: internalapi.KindWorkloadEndpoint, Name: name2, Namespace: ns2, } @@ -77,7 +77,7 @@ var _ = Describe("Test the WorkloadEndpoint update processor", func() { up := updateprocessors.NewWorkloadEndpointUpdateProcessor() By("converting a WorkloadEndpoint with minimum configuration") - res := libapiv3.NewWorkloadEndpoint() + res := internalapi.NewWorkloadEndpoint() res.Namespace = ns1 res.Labels = map[string]string{ "projectcalico.org/namespace": ns1, @@ -116,7 +116,7 @@ var _ = Describe("Test the WorkloadEndpoint update processor", func() { })) By("adding another WorkloadEndpoint with a full configuration") - res = libapiv3.NewWorkloadEndpoint() + res = internalapi.NewWorkloadEndpoint() res.Namespace = ns2 res.Labels = map[string]string{ "testLabel": "label", @@ -135,7 +135,7 @@ var _ = Describe("Test the WorkloadEndpoint update processor", func() { _, ipn, err = cnet.ParseCIDROrIP("10.100.10.1") Expect(err).NotTo(HaveOccurred()) expectedIPv4Net = *(ipn.Network()) - res.Spec.IPNATs = []libapiv3.IPNAT{ + res.Spec.IPNATs = []internalapi.IPNAT{ { InternalIP: "10.100.1.1", ExternalIP: "10.1.10.1", @@ -146,7 +146,7 @@ var _ = Describe("Test the WorkloadEndpoint update processor", func() { expectedIPv4Gateway, _, err := cnet.ParseCIDROrIP("10.10.10.1") res.Spec.IPv6Gateway = "2001:0db8:85a3:0000:0000:8a2e:0370:7334" expectedIPv6Gateway, _, err := cnet.ParseCIDROrIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334") - res.Spec.Ports = []libapiv3.WorkloadEndpointPort{ + res.Spec.Ports = []internalapi.WorkloadEndpointPort{ { Name: "portname", Protocol: numorstring.ProtocolFromInt(uint8(30)), @@ -209,7 +209,7 @@ var _ = Describe("Test the WorkloadEndpoint update processor", func() { up := updateprocessors.NewWorkloadEndpointUpdateProcessor() By("trying to convert with the wrong key type.") - res := libapiv3.NewWorkloadEndpoint() + res := internalapi.NewWorkloadEndpoint() res.Name = name1 res.Namespace = ns1 @@ -239,9 +239,9 @@ var _ = Describe("Test the WorkloadEndpoint update processor", func() { })) By("trying to convert without enough information to create a v1 key.") - eres := libapiv3.NewWorkloadEndpoint() + eres := internalapi.NewWorkloadEndpoint() v3WorkloadEndpointKey1 := model.ResourceKey{ - Kind: libapiv3.KindWorkloadEndpoint, + Kind: internalapi.KindWorkloadEndpoint, Name: name1, } @@ -257,7 +257,7 @@ var _ = Describe("Test the WorkloadEndpoint update processor", func() { up := updateprocessors.NewWorkloadEndpointUpdateProcessor() By("converting a WorkloadEndpoint with no IPNetworks") - res := libapiv3.NewWorkloadEndpoint() + res := internalapi.NewWorkloadEndpoint() res.Namespace = ns1 res.Labels = map[string]string{ "projectcalico.org/namespace": ns1, @@ -286,7 +286,7 @@ var _ = Describe("Test the WorkloadEndpoint update processor", func() { up := updateprocessors.NewWorkloadEndpointUpdateProcessor() By("converting a WorkloadEndpoint with bad AllowSpoofedSourcePrefixes") - res := libapiv3.NewWorkloadEndpoint() + res := internalapi.NewWorkloadEndpoint() res.Namespace = ns1 res.Labels = map[string]string{ "projectcalico.org/namespace": ns1, @@ -323,7 +323,7 @@ var _ = Describe("Test the WorkloadEndpoint update processor", func() { nsLabel := conversion.NamespaceLabelPrefix + "ns1" saLabel := conversion.ServiceAccountLabelPrefix + "sa1" - res := libapiv3.NewWorkloadEndpoint() + res := internalapi.NewWorkloadEndpoint() res.Namespace = ns1 res.Labels = map[string]string{ "projectcalico.org/namespace": ns1, @@ -368,7 +368,7 @@ var _ = Describe("Test the WorkloadEndpoint update processor", func() { It("should add a label representing the serviceaccount name", func() { up := updateprocessors.NewWorkloadEndpointUpdateProcessor() - res := libapiv3.NewWorkloadEndpoint() + res := internalapi.NewWorkloadEndpoint() res.Namespace = ns1 res.Labels = map[string]string{ "projectcalico.org/namespace": ns1, @@ -413,7 +413,7 @@ var _ = Describe("Test the WorkloadEndpoint update processor", func() { It("should convert a WEP with QoSControls configured", func() { up := updateprocessors.NewWorkloadEndpointUpdateProcessor() - res := libapiv3.NewWorkloadEndpoint() + res := internalapi.NewWorkloadEndpoint() res.Namespace = ns1 res.Labels = map[string]string{ "projectcalico.org/namespace": ns1, @@ -425,7 +425,7 @@ var _ = Describe("Test the WorkloadEndpoint update processor", func() { res.Spec.Endpoint = eid1 res.Spec.InterfaceName = iface1 res.Spec.IPNetworks = []string{"10.100.10.1"} - res.Spec.QoSControls = &libapiv3.QoSControls{ + res.Spec.QoSControls = &internalapi.QoSControls{ IngressBandwidth: 1000000, EgressBandwidth: 2000000, IngressBurst: 3000000, diff --git a/libcalico-go/lib/backend/watchersyncer/watchercache.go b/libcalico-go/lib/backend/watchersyncer/watchercache.go index cb2ac3bb5db..7e1bb217df6 100644 --- a/libcalico-go/lib/backend/watchersyncer/watchercache.go +++ b/libcalico-go/lib/backend/watchersyncer/watchercache.go @@ -44,7 +44,7 @@ type watcherCache struct { watch api.WatchInterface resources map[string]cacheEntry oldResources map[string]cacheEntry - results chan<- interface{} + results chan<- any hasSynced bool resourceType ResourceType currentWatchRevision string @@ -84,7 +84,7 @@ type cacheEntry struct { } // Create a new watcherCache. -func newWatcherCache(client api.Client, resourceType ResourceType, results chan<- interface{}, watchTimeout time.Duration) *watcherCache { +func newWatcherCache(client api.Client, resourceType ResourceType, results chan<- any, watchTimeout time.Duration) *watcherCache { return &watcherCache{ logger: logrus.WithField("ListRoot", listRootForLog(resourceType.ListInterface)), client: client, @@ -298,6 +298,7 @@ func (wc *watcherCache) maybeResyncAndCreateWatcher(ctx context.Context) { wc.resyncBlockedUntil = time.Now().Add(ListRetryInterval) continue } + wc.logger.WithField("numKVs", len(l.KVPairs)).Debug("List completed.") wc.lastSuccessfulConnTime = time.Now() wc.markInstalled() diff --git a/libcalico-go/lib/backend/watchersyncer/watchersyncer.go b/libcalico-go/lib/backend/watchersyncer/watchersyncer.go index 8c1e4a147e7..3bf7ba46ed4 100644 --- a/libcalico-go/lib/backend/watchersyncer/watchersyncer.go +++ b/libcalico-go/lib/backend/watchersyncer/watchersyncer.go @@ -88,7 +88,7 @@ var _ = WithWatchRetryTimeout func New(client api.Client, resourceTypes []ResourceType, callbacks api.SyncerCallbacks, options ...Option) api.Syncer { rs := &watcherSyncer{ watcherCaches: make([]*watcherCache, len(resourceTypes)), - results: make(chan interface{}, 2000), + results: make(chan any, 2000), callbacks: callbacks, watchRetryTimeout: DefaultWatchRetryTimeout, } @@ -105,7 +105,7 @@ func New(client api.Client, resourceTypes []ResourceType, callbacks api.SyncerCa type watcherSyncer struct { status api.SyncStatus watcherCaches []*watcherCache - results chan interface{} + results chan any numSynced int callbacks api.SyncerCallbacks wgwc *sync.WaitGroup @@ -184,7 +184,7 @@ func (ws *watcherSyncer) run(ctx context.Context) { // Append results into the one update until we either flush the channel or we // hit our fixed limit per update. consolidatationloop: - for ii := 0; ii < maxUpdatesToConsolidate; ii++ { + for range maxUpdatesToConsolidate { select { case next := <-ws.results: updates = ws.processResult(updates, next) @@ -202,7 +202,7 @@ func (ws *watcherSyncer) run(ctx context.Context) { // Process a result from the result channel. We don't immediately action updates, but // instead start grouping them together so that we can send a larger single update to // Felix. -func (ws *watcherSyncer) processResult(updates []api.Update, result interface{}) []api.Update { +func (ws *watcherSyncer) processResult(updates []api.Update, result any) []api.Update { // Switch on the result type. switch r := result.(type) { case []api.Update: diff --git a/libcalico-go/lib/backend/watchersyncer/watchersyncer_suite_test.go b/libcalico-go/lib/backend/watchersyncer/watchersyncer_suite_test.go index 0b2c3240e05..1a10ca84de5 100644 --- a/libcalico-go/lib/backend/watchersyncer/watchersyncer_suite_test.go +++ b/libcalico-go/lib/backend/watchersyncer/watchersyncer_suite_test.go @@ -17,16 +17,16 @@ package watchersyncer_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestClient(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../report/backend_watcher_syncer_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Backend multi-watcher/syncer test suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../report/backend_watcher_syncer_suite.xml" + ginkgo.RunSpecs(t, "Backend multi-watcher/syncer test suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/backend/watchersyncer/watchersyncer_test.go b/libcalico-go/lib/backend/watchersyncer/watchersyncer_test.go index b84fb6a55e1..7d9661d924c 100644 --- a/libcalico-go/lib/backend/watchersyncer/watchersyncer_test.go +++ b/libcalico-go/lib/backend/watchersyncer/watchersyncer_test.go @@ -21,7 +21,7 @@ import ( "time" "github.com/google/uuid" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" @@ -1029,7 +1029,7 @@ func newWatcherSyncerTester(l []watchersyncer.ResourceType) *watcherSyncerTester lws[name] = &listWatchSource{ name: name, watchCallError: make(chan error, 50), - listCallResults: make(chan interface{}, 200), + listCallResults: make(chan any, 200), stopEvents: make(chan struct{}, 200), results: make(chan api.WatchEvent, 200), } @@ -1138,7 +1138,7 @@ func (rst *watcherSyncerTester) expectStop(r watchersyncer.ResourceType) { // Call to specify the response of the client List invocation. The List call will block // until the response has been specified. // The response should either be of type error, or type *KVPairList. -func (rst *watcherSyncerTester) clientListResponse(r watchersyncer.ResourceType, response interface{}) { +func (rst *watcherSyncerTester) clientListResponse(r watchersyncer.ResourceType, response any) { name := model.ListOptionsToDefaultPathRoot(r.ListInterface) log.WithFields(log.Fields{ "Name": name, @@ -1266,7 +1266,7 @@ type listWatchSource struct { // The list results. This channel with contain either: // - an error // - a *model.KVPairList - listCallResults chan interface{} + listCallResults chan any // Stop events channel. We add an event each time stop is called for a watcher. stopEvents chan struct{} diff --git a/libcalico-go/lib/clientv3/bgpconfig_e2e_test.go b/libcalico-go/lib/clientv3/bgpconfig_e2e_test.go index 19174275883..3ccb8fa13d8 100644 --- a/libcalico-go/lib/clientv3/bgpconfig_e2e_test.go +++ b/libcalico-go/lib/clientv3/bgpconfig_e2e_test.go @@ -18,8 +18,7 @@ import ( "context" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -110,6 +109,7 @@ var _ = testutils.E2eDatastoreDescribe("BGPConfiguration tests", testutils.Datas ServiceLoadBalancerAggregation: &enabled, NodeMeshMaxRestartTime: &restartTime, } + specInfo := apiv3.BGPConfigurationSpec{ LogSeverityScreen: "Info", ServiceLoadBalancerAggregation: &enabled, @@ -142,7 +142,7 @@ var _ = testutils.E2eDatastoreDescribe("BGPConfiguration tests", testutils.Datas Spec: specInfo, }, options.SetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(Equal("error with field Metadata.ResourceVersion = '12345' (field must not be set for a Create request)")) + Expect(outError.Error()).To(Equal("error with field Metadata.ResourceVersion = '12345' (field must not be set for a Create request)"), outError.Error()) By("Creating a new BGPConfiguration with name1/specInfo") res1, outError := c.BGPConfigurations().Create(ctx, &apiv3.BGPConfiguration{ diff --git a/libcalico-go/lib/clientv3/bgpfilter_e2e_test.go b/libcalico-go/lib/clientv3/bgpfilter_e2e_test.go index 1d96540ab18..eaa8866139f 100644 --- a/libcalico-go/lib/clientv3/bgpfilter_e2e_test.go +++ b/libcalico-go/lib/clientv3/bgpfilter_e2e_test.go @@ -19,8 +19,7 @@ import ( "fmt" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/libcalico-go/lib/clientv3/bgppeer_e2e_test.go b/libcalico-go/lib/clientv3/bgppeer_e2e_test.go index c0cbba91ed0..98718f5c023 100644 --- a/libcalico-go/lib/clientv3/bgppeer_e2e_test.go +++ b/libcalico-go/lib/clientv3/bgppeer_e2e_test.go @@ -18,8 +18,7 @@ import ( "context" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -89,6 +88,43 @@ var _ = testutils.E2eDatastoreDescribe("BGPPeer tests", testutils.DatastoreAll, }, } + DescribeTable("BGPPeer PeerIP validation", func(peerIP string, shouldError bool) { + c, err := clientv3.New(config) + Expect(err).NotTo(HaveOccurred()) + + be, err := backend.NewClient(config) + Expect(err).NotTo(HaveOccurred()) + be.Clean() + + By("Attempting to create a new BGPPeer with PeerIP: " + peerIP) + _, outError := c.BGPPeers().Create(ctx, &apiv3.BGPPeer{ + ObjectMeta: metav1.ObjectMeta{Name: "bgppeer-peerip-validation"}, + Spec: apiv3.BGPPeerSpec{ + Node: "node1", + PeerIP: peerIP, + ASNumber: numorstring.ASNumber(6512), + }, + }, options.SetOptions{}) + + if shouldError { + Expect(outError).To(HaveOccurred()) + } else { + Expect(outError).NotTo(HaveOccurred()) + } + }, + // Valid + Entry("valid IPv4 address", "192.168.1.1", false), + Entry("valid IPv4 address with port", "192.168.5.101:179", false), + Entry("valid IPv6 address", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", false), + Entry("valid IPv6 address, shortened", "fe80::11", false), + Entry("valid IPv6 address with port", "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:179", false), + + // Invalid + Entry("invalid IPv4 address", "300.168.1.1", true), + Entry("invalid IPv4 address with port", "300.168.5.101:179", true), + Entry("invalid IPv6 address", "2001:0db8:85a3:0000:0000:8a2e:0370:7334:12345", true), + ) + DescribeTable("BGPPeer e2e CRUD tests", func(name1, name2 string, spec1, spec2 apiv3.BGPPeerSpec) { c, err := clientv3.New(config) diff --git a/libcalico-go/lib/clientv3/blockaffinity.go b/libcalico-go/lib/clientv3/blockaffinity.go index 6ea45d3d4ce..d9001dd6751 100644 --- a/libcalico-go/lib/clientv3/blockaffinity.go +++ b/libcalico-go/lib/clientv3/blockaffinity.go @@ -16,22 +16,21 @@ package clientv3 import ( "context" - "fmt" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "github.com/projectcalico/calico/libcalico-go/lib/options" validator "github.com/projectcalico/calico/libcalico-go/lib/validator/v3" "github.com/projectcalico/calico/libcalico-go/lib/watch" ) -// TODO: Configure this to work with the public facing API instead of the lib v3 API. // BlockAffinityInterface has methods to work with BlockAffinity resources. type BlockAffinityInterface interface { - Create(ctx context.Context, res *libapiv3.BlockAffinity, opts options.SetOptions) (*libapiv3.BlockAffinity, error) - Update(ctx context.Context, res *libapiv3.BlockAffinity, opts options.SetOptions) (*libapiv3.BlockAffinity, error) - Delete(ctx context.Context, name string, opts options.DeleteOptions) (*libapiv3.BlockAffinity, error) - Get(ctx context.Context, name string, opts options.GetOptions) (*libapiv3.BlockAffinity, error) - List(ctx context.Context, opts options.ListOptions) (*libapiv3.BlockAffinityList, error) + Create(ctx context.Context, res *v3.BlockAffinity, opts options.SetOptions) (*v3.BlockAffinity, error) + Update(ctx context.Context, res *v3.BlockAffinity, opts options.SetOptions) (*v3.BlockAffinity, error) + Delete(ctx context.Context, name string, opts options.DeleteOptions) (*v3.BlockAffinity, error) + Get(ctx context.Context, name string, opts options.GetOptions) (*v3.BlockAffinity, error) + List(ctx context.Context, opts options.ListOptions) (*v3.BlockAffinityList, error) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) } @@ -42,58 +41,58 @@ type blockAffinities struct { // Create takes the representation of a BlockAffinity and creates it. Returns the stored // representation of the BlockAffinity, and an error, if there is any. -func (r blockAffinities) Create(ctx context.Context, res *libapiv3.BlockAffinity, opts options.SetOptions) (*libapiv3.BlockAffinity, error) { +func (r blockAffinities) Create(ctx context.Context, res *v3.BlockAffinity, opts options.SetOptions) (*v3.BlockAffinity, error) { if err := validator.Validate(res); err != nil { return nil, err } - out, err := r.client.resources.Create(ctx, opts, libapiv3.KindBlockAffinity, res) + out, err := r.client.resources.Create(ctx, opts, v3.KindBlockAffinity, res) if out != nil { - return out.(*libapiv3.BlockAffinity), err + return out.(*v3.BlockAffinity), err } return nil, err } // Update takes the representation of a BlockAffinity and updates it. Returns the stored // representation of the BlockAffinity, and an error, if there is any. -func (r blockAffinities) Update(ctx context.Context, res *libapiv3.BlockAffinity, opts options.SetOptions) (*libapiv3.BlockAffinity, error) { +func (r blockAffinities) Update(ctx context.Context, res *v3.BlockAffinity, opts options.SetOptions) (*v3.BlockAffinity, error) { if err := validator.Validate(res); err != nil { return nil, err } - out, err := r.client.resources.Update(ctx, opts, libapiv3.KindBlockAffinity, res) + out, err := r.client.resources.Update(ctx, opts, v3.KindBlockAffinity, res) if out != nil { - return out.(*libapiv3.BlockAffinity), err + return out.(*v3.BlockAffinity), err } return nil, err } // Delete takes name of the BlockAffinity and deletes it. Returns an error if one occurs. -func (r blockAffinities) Delete(ctx context.Context, name string, opts options.DeleteOptions) (*libapiv3.BlockAffinity, error) { - out, err := r.client.resources.Delete(ctx, opts, libapiv3.KindBlockAffinity, noNamespace, name) +func (r blockAffinities) Delete(ctx context.Context, name string, opts options.DeleteOptions) (*v3.BlockAffinity, error) { + out, err := r.client.resources.Delete(ctx, opts, v3.KindBlockAffinity, noNamespace, name) if out != nil { - return out.(*libapiv3.BlockAffinity), err + return out.(*v3.BlockAffinity), err } return nil, err } // Get takes name of the BlockAffinity, and returns the corresponding BlockAffinity object, // and an error if there is any. -func (r blockAffinities) Get(ctx context.Context, name string, opts options.GetOptions) (*libapiv3.BlockAffinity, error) { - out, err := r.client.resources.Get(ctx, opts, libapiv3.KindBlockAffinity, noNamespace, name) +func (r blockAffinities) Get(ctx context.Context, name string, opts options.GetOptions) (*v3.BlockAffinity, error) { + out, err := r.client.resources.Get(ctx, opts, v3.KindBlockAffinity, noNamespace, name) if out != nil { - if out.(*libapiv3.BlockAffinity).Spec.Deleted != fmt.Sprintf("%t", true) { + if !out.(*v3.BlockAffinity).Spec.Deleted { // Filter out block affinities that are being deleted. - return out.(*libapiv3.BlockAffinity), err + return out.(*v3.BlockAffinity), err } } return nil, err } // List returns the list of BlockAffinity objects that match the supplied options. -func (r blockAffinities) List(ctx context.Context, opts options.ListOptions) (*libapiv3.BlockAffinityList, error) { - res := &libapiv3.BlockAffinityList{} - if err := r.client.resources.List(ctx, opts, libapiv3.KindBlockAffinity, libapiv3.KindBlockAffinityList, res); err != nil { +func (r blockAffinities) List(ctx context.Context, opts options.ListOptions) (*v3.BlockAffinityList, error) { + res := &v3.BlockAffinityList{} + if err := r.client.resources.List(ctx, opts, v3.KindBlockAffinity, v3.KindBlockAffinityList, res); err != nil { return nil, err } return res, nil @@ -102,5 +101,5 @@ func (r blockAffinities) List(ctx context.Context, opts options.ListOptions) (*l // Watch returns a watch.Interface that watches the BlockAffinities that match the // supplied options. func (r blockAffinities) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) { - return r.client.resources.Watch(ctx, opts, libapiv3.KindBlockAffinity, nil) + return r.client.resources.Watch(ctx, opts, v3.KindBlockAffinity, nil) } diff --git a/libcalico-go/lib/clientv3/blockaffinity_e2e_test.go b/libcalico-go/lib/clientv3/blockaffinity_e2e_test.go index 538d57b65c0..7ded39f194a 100644 --- a/libcalico-go/lib/clientv3/blockaffinity_e2e_test.go +++ b/libcalico-go/lib/clientv3/blockaffinity_e2e_test.go @@ -18,13 +18,12 @@ import ( "context" "fmt" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" "github.com/projectcalico/calico/libcalico-go/lib/backend" bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/clientv3" @@ -37,18 +36,18 @@ var _ = testutils.E2eDatastoreDescribe("Block affinity tests", testutils.Datasto ctx := context.Background() name1 := "affinity-1" name2 := "affinity-2" - spec1 := libapiv3.BlockAffinitySpec{ + spec1 := v3.BlockAffinitySpec{ State: "confirmed", Node: "node-1", CIDR: "10.0.0.0/24", - Deleted: "false", + Deleted: false, Type: "host", } - spec2 := libapiv3.BlockAffinitySpec{ + spec2 := v3.BlockAffinitySpec{ State: "confirmed", Node: "node-2", CIDR: "10.1.0.0/24", - Deleted: "false", + Deleted: false, Type: "host", } @@ -67,9 +66,9 @@ var _ = testutils.E2eDatastoreDescribe("Block affinity tests", testutils.Datasto }) DescribeTable("Block affinity e2e CRUD tests", - func(name1, name2 string, spec1, spec2 libapiv3.BlockAffinitySpec) { + func(name1, name2 string, spec1, spec2 v3.BlockAffinitySpec) { By("Updating the block affinity before it is created") - _, outError := c.BlockAffinities().Update(ctx, &libapiv3.BlockAffinity{ + _, outError := c.BlockAffinities().Update(ctx, &v3.BlockAffinity{ ObjectMeta: metav1.ObjectMeta{Name: name1, ResourceVersion: "1234", CreationTimestamp: metav1.Now(), UID: uid}, Spec: spec1, }, options.SetOptions{}) @@ -77,7 +76,7 @@ var _ = testutils.E2eDatastoreDescribe("Block affinity tests", testutils.Datasto Expect(outError.Error()).To(ContainSubstring("resource does not exist: BlockAffinity(" + name1 + ") with error:")) By("Attempting to creating a new block affinity with name1/spec1 and a non-empty ResourceVersion") - _, outError = c.BlockAffinities().Create(ctx, &libapiv3.BlockAffinity{ + _, outError = c.BlockAffinities().Create(ctx, &v3.BlockAffinity{ ObjectMeta: metav1.ObjectMeta{Name: name1, ResourceVersion: "12345"}, Spec: spec1, }, options.SetOptions{}) @@ -90,18 +89,18 @@ var _ = testutils.E2eDatastoreDescribe("Block affinity tests", testutils.Datasto Expect(outError.Error()).To(ContainSubstring("resource does not exist: BlockAffinity(" + name1 + ") with error:")) By("Creating a new BlockAffinity with name1/spec1") - res1, outError := c.BlockAffinities().Create(ctx, &libapiv3.BlockAffinity{ + res1, outError := c.BlockAffinities().Create(ctx, &v3.BlockAffinity{ ObjectMeta: metav1.ObjectMeta{Name: name1}, Spec: spec1, }, options.SetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res1).To(MatchResource(libapiv3.KindBlockAffinity, testutils.ExpectNoNamespace, name1, spec1)) + Expect(res1).To(MatchResource(v3.KindBlockAffinity, testutils.ExpectNoNamespace, name1, spec1)) // Track the version of the original data for name1. rv1_1 := res1.ResourceVersion By("Attempting to create the same BlockAffinity with name1 but with spec2") - _, outError = c.BlockAffinities().Create(ctx, &libapiv3.BlockAffinity{ + _, outError = c.BlockAffinities().Create(ctx, &v3.BlockAffinity{ ObjectMeta: metav1.ObjectMeta{Name: name1}, Spec: spec2, }, options.SetOptions{}) @@ -111,7 +110,7 @@ var _ = testutils.E2eDatastoreDescribe("Block affinity tests", testutils.Datasto By("Getting BlockAffinity (name1) and comparing the output against spec1") res, outError := c.BlockAffinities().Get(ctx, name1, options.GetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res).To(MatchResource(libapiv3.KindBlockAffinity, testutils.ExpectNoNamespace, name1, spec1)) + Expect(res).To(MatchResource(v3.KindBlockAffinity, testutils.ExpectNoNamespace, name1, spec1)) Expect(res.ResourceVersion).To(Equal(res1.ResourceVersion)) fmt.Printf("Getting res %v", res) @@ -124,39 +123,39 @@ var _ = testutils.E2eDatastoreDescribe("Block affinity tests", testutils.Datasto outList, outError := c.BlockAffinities().List(ctx, options.ListOptions{}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(libapiv3.KindBlockAffinity, testutils.ExpectNoNamespace, name1, spec1), + testutils.Resource(v3.KindBlockAffinity, testutils.ExpectNoNamespace, name1, spec1), )) By("Creating a new BlockAffinity with name2/spec2") - res2, outError := c.BlockAffinities().Create(ctx, &libapiv3.BlockAffinity{ + res2, outError := c.BlockAffinities().Create(ctx, &v3.BlockAffinity{ ObjectMeta: metav1.ObjectMeta{Name: name2}, Spec: spec2, }, options.SetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res2).To(MatchResource(libapiv3.KindBlockAffinity, testutils.ExpectNoNamespace, name2, spec2)) + Expect(res2).To(MatchResource(v3.KindBlockAffinity, testutils.ExpectNoNamespace, name2, spec2)) By("Getting BlockAffinity (name2) and comparing the output against spec2") res, outError = c.BlockAffinities().Get(ctx, name2, options.GetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res2).To(MatchResource(libapiv3.KindBlockAffinity, testutils.ExpectNoNamespace, name2, spec2)) + Expect(res2).To(MatchResource(v3.KindBlockAffinity, testutils.ExpectNoNamespace, name2, spec2)) Expect(res.ResourceVersion).To(Equal(res2.ResourceVersion)) By("Listing all the BlockAffinities, expecting two results with name1/spec1 and name2/spec2") outList, outError = c.BlockAffinities().List(ctx, options.ListOptions{}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(libapiv3.KindBlockAffinity, testutils.ExpectNoNamespace, name1, spec1), - testutils.Resource(libapiv3.KindBlockAffinity, testutils.ExpectNoNamespace, name2, spec2), + testutils.Resource(v3.KindBlockAffinity, testutils.ExpectNoNamespace, name1, spec1), + testutils.Resource(v3.KindBlockAffinity, testutils.ExpectNoNamespace, name2, spec2), )) By("Updating BlockAffinity name1 with spec2") res1.Spec = spec2 res1, outError = c.BlockAffinities().Update(ctx, res1, options.SetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res1).To(MatchResource(libapiv3.KindBlockAffinity, testutils.ExpectNoNamespace, name1, spec2)) + Expect(res1).To(MatchResource(v3.KindBlockAffinity, testutils.ExpectNoNamespace, name1, spec2)) By("Attempting to update the BlockAffinity without a Creation Timestamp") - res, outError = c.BlockAffinities().Update(ctx, &libapiv3.BlockAffinity{ + res, outError = c.BlockAffinities().Update(ctx, &v3.BlockAffinity{ ObjectMeta: metav1.ObjectMeta{Name: name1, ResourceVersion: "1234", UID: uid}, Spec: spec1, }, options.SetOptions{}) @@ -165,7 +164,7 @@ var _ = testutils.E2eDatastoreDescribe("Block affinity tests", testutils.Datasto Expect(outError.Error()).To(Equal("error with field Metadata.CreationTimestamp = '0001-01-01 00:00:00 +0000 UTC' (field must be set for an Update request)")) By("Attempting to update the BlockAffinity without a UID") - res, outError = c.BlockAffinities().Update(ctx, &libapiv3.BlockAffinity{ + res, outError = c.BlockAffinities().Update(ctx, &v3.BlockAffinity{ ObjectMeta: metav1.ObjectMeta{Name: name1, ResourceVersion: "1234", CreationTimestamp: metav1.Now()}, Spec: spec1, }, options.SetOptions{}) @@ -194,14 +193,14 @@ var _ = testutils.E2eDatastoreDescribe("Block affinity tests", testutils.Datasto By("Getting BlockAffinity (name1) with the original resource version and comparing the output against spec1") res, outError = c.BlockAffinities().Get(ctx, name1, options.GetOptions{ResourceVersion: rv1_1}) Expect(outError).NotTo(HaveOccurred()) - Expect(res).To(MatchResource(libapiv3.KindBlockAffinity, testutils.ExpectNoNamespace, name1, spec1)) + Expect(res).To(MatchResource(v3.KindBlockAffinity, testutils.ExpectNoNamespace, name1, spec1)) Expect(res.ResourceVersion).To(Equal(rv1_1)) } By("Getting BlockAffinity (name1) with the updated resource version and comparing the output against spec2") res, outError = c.BlockAffinities().Get(ctx, name1, options.GetOptions{ResourceVersion: rv1_2}) Expect(outError).NotTo(HaveOccurred()) - Expect(res).To(MatchResource(libapiv3.KindBlockAffinity, testutils.ExpectNoNamespace, name1, spec2)) + Expect(res).To(MatchResource(v3.KindBlockAffinity, testutils.ExpectNoNamespace, name1, spec2)) Expect(res.ResourceVersion).To(Equal(rv1_2)) if config.Spec.DatastoreType != apiconfig.Kubernetes { @@ -209,7 +208,7 @@ var _ = testutils.E2eDatastoreDescribe("Block affinity tests", testutils.Datasto outList, outError = c.BlockAffinities().List(ctx, options.ListOptions{ResourceVersion: rv1_1}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(libapiv3.KindBlockAffinity, testutils.ExpectNoNamespace, name1, spec1), + testutils.Resource(v3.KindBlockAffinity, testutils.ExpectNoNamespace, name1, spec1), )) } @@ -217,21 +216,21 @@ var _ = testutils.E2eDatastoreDescribe("Block affinity tests", testutils.Datasto outList, outError = c.BlockAffinities().List(ctx, options.ListOptions{}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(libapiv3.KindBlockAffinity, testutils.ExpectNoNamespace, name1, spec2), - testutils.Resource(libapiv3.KindBlockAffinity, testutils.ExpectNoNamespace, name2, spec2), + testutils.Resource(v3.KindBlockAffinity, testutils.ExpectNoNamespace, name1, spec2), + testutils.Resource(v3.KindBlockAffinity, testutils.ExpectNoNamespace, name2, spec2), )) - By("Attempt to update BlockAffinity (name1) to have the Deleted flag set to \"true\"") + By("Attempt to update BlockAffinity (name1) to have the Deleted flag set to true") res1.ResourceVersion = rv1_2 res1.ObjectMeta.ResourceVersion = rv1_2 - spec2.Deleted = "true" + spec2.Deleted = true res1.Spec = spec2 _, outError = c.BlockAffinities().Update(ctx, res1, options.SetOptions{}) Expect(outError).To(HaveOccurred()) Expect(outError.Error()).To(Equal("error with field Spec.Deleted = '{confirmed node-2 host 10.1.0.0/24 true}' (spec.Deleted cannot be set to \"true\")")) By("Deleting BlockAffinity (name1)") - spec2.Deleted = "false" + spec2.Deleted = false _, outError = c.BlockAffinities().Delete(ctx, name1, options.DeleteOptions{ResourceVersion: res1.ResourceVersion}) Expect(outError).NotTo(HaveOccurred()) @@ -239,7 +238,7 @@ var _ = testutils.E2eDatastoreDescribe("Block affinity tests", testutils.Datasto outList, outError = c.BlockAffinities().List(ctx, options.ListOptions{}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(libapiv3.KindBlockAffinity, testutils.ExpectNoNamespace, name2, spec2), + testutils.Resource(v3.KindBlockAffinity, testutils.ExpectNoNamespace, name2, spec2), )) By("Deleting BlockAffinity (name2)") @@ -283,7 +282,7 @@ var _ = testutils.E2eDatastoreDescribe("Block affinity tests", testutils.Datasto By("Configuring a BlockAffinity name1/spec1 and storing the response") outRes1, err := c.BlockAffinities().Create( ctx, - &libapiv3.BlockAffinity{ + &v3.BlockAffinity{ ObjectMeta: metav1.ObjectMeta{Name: name1}, Spec: spec1, }, @@ -291,19 +290,19 @@ var _ = testutils.E2eDatastoreDescribe("Block affinity tests", testutils.Datasto ) rev1 := outRes1.ResourceVersion modifiedOutRes1 := outRes1.DeepCopy() - modifiedOutRes1.Spec.Deleted = fmt.Sprintf("%t", true) + modifiedOutRes1.Spec.Deleted = true By("Configuring a BlockAffinity name2/spec2 and storing the response") outRes2, err := c.BlockAffinities().Create( ctx, - &libapiv3.BlockAffinity{ + &v3.BlockAffinity{ ObjectMeta: metav1.ObjectMeta{Name: name2}, Spec: spec2, }, options.SetOptions{}, ) modifiedOutRes2 := outRes2.DeepCopy() - modifiedOutRes2.Spec.Deleted = fmt.Sprintf("%t", true) + modifiedOutRes2.Spec.Deleted = true By("Starting a watcher from revision rev1 - this should skip the first creation") w, err := c.BlockAffinities().Watch(ctx, options.ListOptions{ResourceVersion: rev1}) @@ -318,7 +317,7 @@ var _ = testutils.E2eDatastoreDescribe("Block affinity tests", testutils.Datasto // Kubernetes does not have version control on delete so need an extra update is called per delete. if config.Spec.DatastoreType == apiconfig.EtcdV3 { By("Checking for two events, create res2 and delete res1") - testWatcher1.ExpectEvents(libapiv3.KindBlockAffinity, []watch.Event{ + testWatcher1.ExpectEvents(v3.KindBlockAffinity, []watch.Event{ { Type: watch.Added, Object: outRes2, @@ -331,7 +330,7 @@ var _ = testutils.E2eDatastoreDescribe("Block affinity tests", testutils.Datasto testWatcher1.Stop() } else { By("Checking for three events, create res2 and delete re1") - testWatcher1.ExpectEvents(libapiv3.KindBlockAffinity, []watch.Event{ + testWatcher1.ExpectEvents(v3.KindBlockAffinity, []watch.Event{ { Type: watch.Added, Object: outRes2, @@ -358,7 +357,7 @@ var _ = testutils.E2eDatastoreDescribe("Block affinity tests", testutils.Datasto By("Modifying res2") outRes3, err := c.BlockAffinities().Update( ctx, - &libapiv3.BlockAffinity{ + &v3.BlockAffinity{ ObjectMeta: outRes2.ObjectMeta, Spec: spec1, }, @@ -366,10 +365,10 @@ var _ = testutils.E2eDatastoreDescribe("Block affinity tests", testutils.Datasto ) Expect(err).NotTo(HaveOccurred()) modifiedOutRes3 := outRes3.DeepCopy() - modifiedOutRes3.Spec.Deleted = fmt.Sprintf("%t", true) + modifiedOutRes3.Spec.Deleted = true if config.Spec.DatastoreType == apiconfig.EtcdV3 { // Etcd has version control so it does not update the block affinity before deletion. - testWatcher2.ExpectEvents(libapiv3.KindBlockAffinity, []watch.Event{ + testWatcher2.ExpectEvents(v3.KindBlockAffinity, []watch.Event{ { Type: watch.Added, Object: outRes1, @@ -389,7 +388,7 @@ var _ = testutils.E2eDatastoreDescribe("Block affinity tests", testutils.Datasto }, }) } else { - testWatcher2.ExpectEvents(libapiv3.KindBlockAffinity, []watch.Event{ + testWatcher2.ExpectEvents(v3.KindBlockAffinity, []watch.Event{ { Type: watch.Added, Object: outRes1, @@ -423,7 +422,7 @@ var _ = testutils.E2eDatastoreDescribe("Block affinity tests", testutils.Datasto Expect(err).NotTo(HaveOccurred()) testWatcher2_1 := testutils.NewTestResourceWatch(config.Spec.DatastoreType, w) defer testWatcher2_1.Stop() - testWatcher2_1.ExpectEvents(libapiv3.KindBlockAffinity, []watch.Event{ + testWatcher2_1.ExpectEvents(v3.KindBlockAffinity, []watch.Event{ { Type: watch.Added, Object: outRes1, @@ -441,7 +440,7 @@ var _ = testutils.E2eDatastoreDescribe("Block affinity tests", testutils.Datasto Expect(err).NotTo(HaveOccurred()) testWatcher3 := testutils.NewTestResourceWatch(config.Spec.DatastoreType, w) defer testWatcher3.Stop() - testWatcher3.ExpectEvents(libapiv3.KindBlockAffinity, []watch.Event{ + testWatcher3.ExpectEvents(v3.KindBlockAffinity, []watch.Event{ { Type: watch.Added, Object: outRes3, @@ -452,7 +451,7 @@ var _ = testutils.E2eDatastoreDescribe("Block affinity tests", testutils.Datasto By("Configuring BlockAffinity name1/spec1 again and storing the response") outRes1, err = c.BlockAffinities().Create( ctx, - &libapiv3.BlockAffinity{ + &v3.BlockAffinity{ ObjectMeta: metav1.ObjectMeta{Name: name1}, Spec: spec1, }, @@ -464,7 +463,7 @@ var _ = testutils.E2eDatastoreDescribe("Block affinity tests", testutils.Datasto Expect(err).NotTo(HaveOccurred()) testWatcher4 := testutils.NewTestResourceWatch(config.Spec.DatastoreType, w) defer testWatcher4.Stop() - testWatcher4.ExpectEventsAnyOrder(libapiv3.KindBlockAffinity, []watch.Event{ + testWatcher4.ExpectEventsAnyOrder(v3.KindBlockAffinity, []watch.Event{ { Type: watch.Added, Object: outRes1, @@ -480,7 +479,7 @@ var _ = testutils.E2eDatastoreDescribe("Block affinity tests", testutils.Datasto // Clean() does deletions in parallel so we cannot predict the order of events. _, err = c.BlockAffinities().Delete(ctx, name1, options.DeleteOptions{ResourceVersion: outRes1.ResourceVersion}) Expect(err).NotTo(HaveOccurred()) - testWatcher4.ExpectEvents(libapiv3.KindBlockAffinity, []watch.Event{ + testWatcher4.ExpectEvents(v3.KindBlockAffinity, []watch.Event{ { Type: watch.Modified, Previous: outRes1, @@ -493,7 +492,7 @@ var _ = testutils.E2eDatastoreDescribe("Block affinity tests", testutils.Datasto }) _, err = c.BlockAffinities().Delete(ctx, name2, options.DeleteOptions{ResourceVersion: outRes3.ResourceVersion}) Expect(err).NotTo(HaveOccurred()) - testWatcher4.ExpectEvents(libapiv3.KindBlockAffinity, []watch.Event{ + testWatcher4.ExpectEvents(v3.KindBlockAffinity, []watch.Event{ { Type: watch.Modified, Previous: outRes3, @@ -509,7 +508,7 @@ var _ = testutils.E2eDatastoreDescribe("Block affinity tests", testutils.Datasto Expect(be.Clean()).To(Succeed()) if config.Spec.DatastoreType == apiconfig.EtcdV3 { // Etcd has version control so it does not modify the block affinity before deletion. - testWatcher4.ExpectEventsAnyOrder(libapiv3.KindBlockAffinity, []watch.Event{ + testWatcher4.ExpectEventsAnyOrder(v3.KindBlockAffinity, []watch.Event{ { Type: watch.Deleted, Previous: outRes1, diff --git a/libcalico-go/lib/clientv3/caliconodestatus_e2e_test.go b/libcalico-go/lib/clientv3/caliconodestatus_e2e_test.go index a29416377e6..ff1350a14e2 100644 --- a/libcalico-go/lib/clientv3/caliconodestatus_e2e_test.go +++ b/libcalico-go/lib/clientv3/caliconodestatus_e2e_test.go @@ -18,8 +18,7 @@ import ( "context" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -84,7 +83,7 @@ var _ = testutils.E2eDatastoreDescribe("CalicoNodeStatus tests", testutils.Datas PeersV4: []apiv3.CalicoNodePeer{ { PeerIP: "172.17.0.5", - Type: "nodeMesh", + Type: "NodeMesh", State: apiv3.BGPSessionStateEstablished, Since: "09:19:28", }, @@ -92,7 +91,7 @@ var _ = testutils.E2eDatastoreDescribe("CalicoNodeStatus tests", testutils.Datas PeersV6: []apiv3.CalicoNodePeer{ { PeerIP: "2001:20::8", - Type: "nodeMesh", + Type: "NodeMesh", State: apiv3.BGPSessionStateEstablished, Since: "09:19:28", }, @@ -148,7 +147,7 @@ var _ = testutils.E2eDatastoreDescribe("CalicoNodeStatus tests", testutils.Datas PeersV4: []apiv3.CalicoNodePeer{ { PeerIP: "172.17.0.6", - Type: "nodeMesh", + Type: "NodeMesh", State: apiv3.BGPSessionStateEstablished, Since: "09:19:28", }, @@ -156,7 +155,7 @@ var _ = testutils.E2eDatastoreDescribe("CalicoNodeStatus tests", testutils.Datas PeersV6: []apiv3.CalicoNodePeer{ { PeerIP: "2001:10::8", - Type: "nodeMesh", + Type: "NodeMesh", State: apiv3.BGPSessionStateEstablished, Since: "09:19:28", }, diff --git a/libcalico-go/lib/clientv3/client.go b/libcalico-go/lib/clientv3/client.go index 7adb71c7e1f..7ccf3c74843 100644 --- a/libcalico-go/lib/clientv3/client.go +++ b/libcalico-go/lib/clientv3/client.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2017-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -65,7 +65,6 @@ func New(config apiconfig.CalicoAPIConfig) (Interface, error) { // NewFromEnv loads the config from ENV variables and returns a connected client. func NewFromEnv() (Interface, error) { - config, err := apiconfig.LoadClientConfigFromEnvironment() if err != nil { return nil, err @@ -181,8 +180,8 @@ func (c client) CalicoNodeStatus() CalicoNodeStatusInterface { } // IPAMConfig returns an interface for managing the IPAMConfig resource. -func (c client) IPAMConfig() IPAMConfigInterface { - return IPAMConfigs{client: c} +func (c client) IPAMConfiguration() IPAMConfigurationInterface { + return IPAMConfigurations{client: c} } // BlockAffinity returns an interface for viewing the IPAM block affinity resources. @@ -190,6 +189,11 @@ func (c client) BlockAffinities() BlockAffinityInterface { return blockAffinities{client: c} } +// LiveMigrations returns an interface for managing LiveMigration resources. +func (c client) LiveMigrations() LiveMigrationInterface { + return liveMigrations{client: c} +} + // BGPFilter returns an interface for managing the BGPFilter resource. func (c client) BGPFilter() BGPFilterInterface { return BGPFilter{client: c} @@ -208,6 +212,17 @@ func filterIPPool(pool *v3.IPPool, ipVersion int) bool { log.Debugf("Skipping disabled IP pool (%s)", pool.Name) return false } + + if pool.Status != nil { + // Skip any pools that have been marked as unavailable for allocations by the IP pool controller in kube-controllers. + for _, condition := range pool.Status.Conditions { + if condition.Type == v3.IPPoolConditionAllocatable && condition.Status == metav1.ConditionFalse { + log.Debugf("Skipping IP pool (%s) with condition Allocatable=false", pool.Name) + return false + } + } + } + if _, cidr, err := net.ParseCIDR(pool.Spec.CIDR); err == nil && cidr.Version() == ipVersion { log.Debugf("Adding pool (%s) to the IPPool list", cidr.String()) return true @@ -276,15 +291,15 @@ func (c client) EnsureInitialized(ctx context.Context, calicoVersion, clusterTyp errs = append(errs, err) } - err = c.ensureTierExists(ctx, names.AdminNetworkPolicyTierName, v3.Pass, v3.AdminNetworkPolicyTierOrder) + err = c.ensureTierExists(ctx, names.KubeAdminTierName, v3.Pass, v3.KubeAdminTierOrder) if err != nil { - log.WithError(err).Info("Unable to initialize adminnetworkpolicy Tier") + log.WithError(err).Info("Unable to initialize kube-admin Tier") errs = append(errs, err) } - err = c.ensureTierExists(ctx, names.BaselineAdminNetworkPolicyTierName, v3.Pass, v3.BaselineAdminNetworkPolicyTierOrder) + err = c.ensureTierExists(ctx, names.KubeBaselineTierName, v3.Pass, v3.KubeBaselineTierOrder) if err != nil { - log.WithError(err).Info("Unable to initialize baselineadminnetworkpolicy Tier") + log.WithError(err).Info("Unable to initialize kube-baseline Tier") errs = append(errs, err) } diff --git a/libcalico-go/lib/clientv3/client_suite_test.go b/libcalico-go/lib/clientv3/client_suite_test.go index d9b1d332ce9..9ef18db6fff 100644 --- a/libcalico-go/lib/clientv3/client_suite_test.go +++ b/libcalico-go/lib/clientv3/client_suite_test.go @@ -17,16 +17,16 @@ package clientv3 import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestClient(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/client_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "client Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/client_suite.xml" + ginkgo.RunSpecs(t, "client Suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/clientv3/client_test.go b/libcalico-go/lib/clientv3/client_test.go index b5004d34b63..1acd9968d99 100644 --- a/libcalico-go/lib/clientv3/client_test.go +++ b/libcalico-go/lib/clientv3/client_test.go @@ -1,7 +1,7 @@ package clientv3 import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/libcalico-go/lib/clientv3/clusterinfo_e2e_test.go b/libcalico-go/lib/clientv3/clusterinfo_e2e_test.go index 870692e4af8..dcab93b7f0e 100644 --- a/libcalico-go/lib/clientv3/clusterinfo_e2e_test.go +++ b/libcalico-go/lib/clientv3/clusterinfo_e2e_test.go @@ -17,8 +17,7 @@ package clientv3_test import ( "context" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/libcalico-go/lib/clientv3/common_test.go b/libcalico-go/lib/clientv3/common_test.go index 09945022ffd..05035356ea6 100644 --- a/libcalico-go/lib/clientv3/common_test.go +++ b/libcalico-go/lib/clientv3/common_test.go @@ -18,7 +18,7 @@ import ( "context" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/libcalico-go/lib/clientv3/felixconfig_e2e_test.go b/libcalico-go/lib/clientv3/felixconfig_e2e_test.go index 555fdf841a6..cbe74639273 100644 --- a/libcalico-go/lib/clientv3/felixconfig_e2e_test.go +++ b/libcalico-go/lib/clientv3/felixconfig_e2e_test.go @@ -18,11 +18,11 @@ import ( "context" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" "github.com/projectcalico/calico/libcalico-go/lib/backend" @@ -52,6 +52,7 @@ var _ = testutils.E2eDatastoreDescribe("FelixConfiguration tests", testutils.Dat FloatingIPs: &fipDisabled, BPFConnectTimeLoadBalancing: &ctlbEnabled, BPFHostNetworkedNATWithoutCTLB: &hostNetworkedNATDisabled, + NFTablesMode: ptr.To(apiv3.NFTablesModeAuto), } spec2 := apiv3.FelixConfigurationSpec{ UseInternalDataplaneDriver: &ptrFalse, @@ -61,6 +62,7 @@ var _ = testutils.E2eDatastoreDescribe("FelixConfiguration tests", testutils.Dat FloatingIPs: &fipEnabled, BPFConnectTimeLoadBalancing: &ctlbEnabled, BPFHostNetworkedNATWithoutCTLB: &hostNetworkedNATDisabled, + NFTablesMode: ptr.To(apiv3.NFTablesModeDisabled), } DescribeTable("FelixConfiguration e2e CRUD tests", diff --git a/libcalico-go/lib/clientv3/globalnetworkpolicy.go b/libcalico-go/lib/clientv3/globalnetworkpolicy.go index c989f82fc55..c63c7f6c06c 100644 --- a/libcalico-go/lib/clientv3/globalnetworkpolicy.go +++ b/libcalico-go/lib/clientv3/globalnetworkpolicy.go @@ -16,9 +16,9 @@ package clientv3 import ( "context" - "fmt" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "github.com/projectcalico/api/pkg/defaults" log "github.com/sirupsen/logrus" "github.com/projectcalico/calico/libcalico-go/lib/names" @@ -58,19 +58,13 @@ func (r globalNetworkPolicies) Create(ctx context.Context, res *apiv3.GlobalNetw resCopy := *res res = &resCopy } - defaultPolicyTypesField(res.Spec.Ingress, res.Spec.Egress, &res.Spec.Types) - if err := validator.Validate(res); err != nil { - return nil, err - } - err := names.ValidateTieredPolicyName(res.Name, tier) - if err != nil { + if _, err := defaults.Default(res); err != nil { return nil, err } - // Add tier labels to policy for lookup. - if tier != "default" { - res.GetObjectMeta().SetLabels(addTierLabel(res.GetObjectMeta().GetLabels(), tier)) + if err := validator.Validate(res); err != nil { + return nil, err } out, err := r.client.resources.Create(ctx, opts, apiv3.KindGlobalNetworkPolicy, res) @@ -95,32 +89,20 @@ func (r globalNetworkPolicies) Update(ctx context.Context, res *apiv3.GlobalNetw resCopy := *res res = &resCopy } - defaultPolicyTypesField(res.Spec.Ingress, res.Spec.Egress, &res.Spec.Types) - if err := validator.Validate(res); err != nil { - return nil, err - } - err := names.ValidateTieredPolicyName(res.Name, res.Spec.Tier) - if err != nil { + if _, err := defaults.Default(res); err != nil { return nil, err } - // Add tier labels to policy for lookup. - tier := names.TierOrDefault(res.Spec.Tier) - if tier != "default" { - res.GetObjectMeta().SetLabels(addTierLabel(res.GetObjectMeta().GetLabels(), tier)) + if err := validator.Validate(res); err != nil { + return nil, err } out, err := r.client.resources.Update(ctx, opts, apiv3.KindGlobalNetworkPolicy, res) if out != nil { - // Add the tier labels if necessary - out.GetObjectMeta().SetLabels(defaultTierLabelIfMissing(out.GetObjectMeta().GetLabels())) return out.(*apiv3.GlobalNetworkPolicy), err } - // Add the tier labels if necessary - res.GetObjectMeta().SetLabels(defaultTierLabelIfMissing(res.GetObjectMeta().GetLabels())) - return nil, err } @@ -142,21 +124,7 @@ func (r globalNetworkPolicies) Get(ctx context.Context, name string, opts option if out != nil { // Add the tier labels if necessary out.GetObjectMeta().SetLabels(defaultTierLabelIfMissing(out.GetObjectMeta().GetLabels())) - // Fill in the tier information from the policy name if we find it missing. - // We expect backend policies to have the right name (prefixed with tier name). - res_out := out.(*apiv3.GlobalNetworkPolicy) - if res_out.Spec.Tier == "" { - tier, tierErr := names.TierFromPolicyName(res_out.Name) - if tierErr != nil { - log.WithError(tierErr).Infof("Skipping setting tier for name %v", res_out.Name) - return res_out, tierErr - } - res_out.Spec.Tier = tier - } - if res_out.Name != name { - return nil, fmt.Errorf("resource not found GlobalNetworkPolicy(%s)", name) - } - return res_out, err + return out.(*apiv3.GlobalNetworkPolicy), err } return nil, err } @@ -164,11 +132,6 @@ func (r globalNetworkPolicies) Get(ctx context.Context, name string, opts option // List returns the list of GlobalNetworkPolicy objects that match the supplied options. func (r globalNetworkPolicies) List(ctx context.Context, opts options.ListOptions) (*apiv3.GlobalNetworkPolicyList, error) { res := &apiv3.GlobalNetworkPolicyList{} - // Add the name prefix if name is provided - if opts.Name != "" && !opts.Prefix { - opts.Name = names.TieredPolicyName(opts.Name) - } - if err := r.client.resources.List(ctx, opts, apiv3.KindGlobalNetworkPolicy, apiv3.KindGlobalNetworkPolicyList, res); err != nil { return nil, err } @@ -176,16 +139,6 @@ func (r globalNetworkPolicies) List(ctx context.Context, opts options.ListOption // Make sure the tier labels are added for i := range res.Items { res.Items[i].GetObjectMeta().SetLabels(defaultTierLabelIfMissing(res.Items[i].GetObjectMeta().GetLabels())) - // Fill in the tier information from the policy name if we find it missing. - // We expect backend policies to have the right name (prefixed with tier name). - if res.Items[i].Spec.Tier == "" { - tier, tierErr := names.TierFromPolicyName(res.Items[i].Name) - if tierErr != nil { - log.WithError(tierErr).Infof("Skipping setting tier for name %v", res.Items[i].Name) - continue - } - res.Items[i].Spec.Tier = tier - } } return res, nil @@ -194,45 +147,9 @@ func (r globalNetworkPolicies) List(ctx context.Context, opts options.ListOption // Watch returns a watch.Interface that watches the globalNetworkPolicies that match the // supplied options. func (r globalNetworkPolicies) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) { - // Add the name prefix if name is provided - if opts.Name != "" { - opts.Name = names.TieredPolicyName(opts.Name) - } - return r.client.resources.Watch(ctx, opts, apiv3.KindGlobalNetworkPolicy, &policyConverter{}) } -func defaultPolicyTypesField(ingressRules, egressRules []apiv3.Rule, types *[]apiv3.PolicyType) { - if len(*types) == 0 { - // Default the Types field according to what inbound and outbound rules are present - // in the policy. - if len(egressRules) == 0 { - // Policy has no egress rules, so apply this policy to ingress only. (Note: - // intentionally including the case where the policy also has no ingress - // rules.) - *types = []apiv3.PolicyType{apiv3.PolicyTypeIngress} - } else if len(ingressRules) == 0 { - // Policy has egress rules but no ingress rules, so apply this policy to - // egress only. - *types = []apiv3.PolicyType{apiv3.PolicyTypeEgress} - } else { - // Policy has both ingress and egress rules, so apply this policy to both - // ingress and egress. - *types = []apiv3.PolicyType{apiv3.PolicyTypeIngress, apiv3.PolicyTypeEgress} - } - } -} - -func addTierLabel(labels map[string]string, prefix string) map[string]string { - // Create the map if it is nil - if labels == nil { - labels = make(map[string]string) - } - - labels[apiv3.LabelTier] = prefix - return labels -} - func defaultTierLabelIfMissing(labels map[string]string) map[string]string { // Create the map if it is nil if labels == nil { diff --git a/libcalico-go/lib/clientv3/globalnetworkpolicy_e2e_test.go b/libcalico-go/lib/clientv3/globalnetworkpolicy_e2e_test.go index 22c26c3fbd4..f5888ece563 100644 --- a/libcalico-go/lib/clientv3/globalnetworkpolicy_e2e_test.go +++ b/libcalico-go/lib/clientv3/globalnetworkpolicy_e2e_test.go @@ -18,35 +18,32 @@ import ( "context" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" "github.com/projectcalico/calico/libcalico-go/lib/backend" bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s" "github.com/projectcalico/calico/libcalico-go/lib/clientv3" - "github.com/projectcalico/calico/libcalico-go/lib/names" "github.com/projectcalico/calico/libcalico-go/lib/options" "github.com/projectcalico/calico/libcalico-go/lib/testutils" "github.com/projectcalico/calico/libcalico-go/lib/watch" ) -func tieredGNPName(p, t string) string { - name, _ := names.BackendTieredPolicyName(p, t) - return name -} - var ( ingressEgress = []apiv3.PolicyType{apiv3.PolicyTypeIngress, apiv3.PolicyTypeEgress} ingress = []apiv3.PolicyType{apiv3.PolicyTypeIngress} egress = []apiv3.PolicyType{apiv3.PolicyTypeEgress} ) +// If true, run the tests in v3 CRD mode. Otherwise, run against crd.projectcalico.org/v1 +var ( + v3CRD bool +) + var _ = testutils.E2eDatastoreDescribe("GlobalNetworkPolicy tests", testutils.DatastoreAll, func(config apiconfig.CalicoAPIConfig) { ctx := context.Background() order1 := 99.999 @@ -95,6 +92,8 @@ var _ = testutils.E2eDatastoreDescribe("GlobalNetworkPolicy tests", testutils.Da err = c.EnsureInitialized(ctx, "", "") Expect(err).NotTo(HaveOccurred()) + + v3CRD = k8s.UsingV3CRDs(&config.Spec) }) DescribeTable("GlobalNetworkPolicy e2e CRUD tests", @@ -120,7 +119,7 @@ var _ = testutils.E2eDatastoreDescribe("GlobalNetworkPolicy tests", testutils.Da Spec: spec1, }, options.SetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: GlobalNetworkPolicy(" + tieredGNPName(name1, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: GlobalNetworkPolicy(" + name1 + ") with error:")) By("Attempting to creating a new GlobalNetworkPolicy with name1/spec1 and a non-empty ResourceVersion") polToCreate := &apiv3.GlobalNetworkPolicy{ @@ -143,7 +142,7 @@ var _ = testutils.E2eDatastoreDescribe("GlobalNetworkPolicy tests", testutils.Da Expect(polToCreate).To(Equal(polToCreateCopy), "Create() unexpectedly modified input policy") Expect(outError).NotTo(HaveOccurred()) spec1.Types = types1 - Expect(res1).To(MatchResource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name1, tier), spec1)) + Expect(res1).To(MatchResource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, name1, spec1)) // Track the version of the original data for name1. rv1_1 := res1.ResourceVersion @@ -154,24 +153,24 @@ var _ = testutils.E2eDatastoreDescribe("GlobalNetworkPolicy tests", testutils.Da Spec: spec2, }, options.SetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource already exists: GlobalNetworkPolicy(" + tieredGNPName(name1, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource already exists: GlobalNetworkPolicy(" + name1 + ") with error:")) By("Getting GlobalNetworkPolicy (name1) and comparing the output against spec1") - res, outError := c.GlobalNetworkPolicies().Get(ctx, tieredGNPName(name1, tier), options.GetOptions{}) + res, outError := c.GlobalNetworkPolicies().Get(ctx, name1, options.GetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res).To(MatchResource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name1, tier), spec1)) + Expect(res).To(MatchResource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, name1, spec1)) Expect(res.ResourceVersion).To(Equal(res1.ResourceVersion)) By("Getting GlobalNetworkPolicy (name2) before it is created") _, outError = c.GlobalNetworkPolicies().Get(ctx, name2, options.GetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: GlobalNetworkPolicy(" + tieredGNPName(name2, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: GlobalNetworkPolicy(" + name2 + ") with error:")) By("Listing all the GlobalNetworkPolicies, expecting a single result with name1/spec1") outList, outError := c.GlobalNetworkPolicies().List(ctx, options.ListOptions{}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name1, tier), spec1), + testutils.Resource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, name1, spec1), )) By("Creating a new GlobalNetworkPolicy with name2/spec2") @@ -181,20 +180,20 @@ var _ = testutils.E2eDatastoreDescribe("GlobalNetworkPolicy tests", testutils.Da }, options.SetOptions{}) Expect(outError).NotTo(HaveOccurred()) spec2.Types = types2 - Expect(res2).To(MatchResource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name2, tier), spec2)) + Expect(res2).To(MatchResource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, name2, spec2)) By("Getting GlobalNetworkPolicy (name2) and comparing the output against spec2") res, outError = c.GlobalNetworkPolicies().Get(ctx, name2, options.GetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res2).To(MatchResource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name2, tier), spec2)) + Expect(res2).To(MatchResource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, name2, spec2)) Expect(res.ResourceVersion).To(Equal(res2.ResourceVersion)) By("Listing all the GlobalNetworkPolicies, expecting a two results with name1/spec1 and name2/spec2") outList, outError = c.GlobalNetworkPolicies().List(ctx, options.ListOptions{}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name1, tier), spec1), - testutils.Resource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name2, tier), spec2), + testutils.Resource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, name1, spec1), + testutils.Resource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, name2, spec2), )) By("Updating GlobalNetworkPolicy name1 with spec2") @@ -203,7 +202,7 @@ var _ = testutils.E2eDatastoreDescribe("GlobalNetworkPolicy tests", testutils.Da res1out, outError := c.GlobalNetworkPolicies().Update(ctx, res1, options.SetOptions{}) Expect(outError).NotTo(HaveOccurred()) Expect(res1.Spec).To(Equal(res1Copy.Spec), "Update() unexpectedly modified input") - Expect(res1).To(MatchResource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name1, tier), spec2)) + Expect(res1).To(MatchResource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, name1, spec2)) res1 = res1out By("Attempting to update the GlobalNetworkPolicy without a Creation Timestamp") @@ -239,20 +238,20 @@ var _ = testutils.E2eDatastoreDescribe("GlobalNetworkPolicy tests", testutils.Da res1.ResourceVersion = rv1_1 _, outError = c.GlobalNetworkPolicies().Update(ctx, res1, options.SetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(Equal("update conflict: GlobalNetworkPolicy(" + tieredGNPName(name1, tier) + ")")) + Expect(outError.Error()).To(Equal("update conflict: GlobalNetworkPolicy(" + name1 + ")")) if config.Spec.DatastoreType != apiconfig.Kubernetes { By("Getting GlobalNetworkPolicy (name1) with the original resource version and comparing the output against spec1") res, outError = c.GlobalNetworkPolicies().Get(ctx, name1, options.GetOptions{ResourceVersion: rv1_1}) Expect(outError).NotTo(HaveOccurred()) - Expect(res).To(MatchResource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name1, tier), spec1)) + Expect(res).To(MatchResource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, name1, spec1)) Expect(res.ResourceVersion).To(Equal(rv1_1)) } By("Getting GlobalNetworkPolicy (name1) with the updated resource version and comparing the output against spec2") res, outError = c.GlobalNetworkPolicies().Get(ctx, name1, options.GetOptions{ResourceVersion: rv1_2}) Expect(outError).NotTo(HaveOccurred()) - Expect(res).To(MatchResource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name1, tier), spec2)) + Expect(res).To(MatchResource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, name1, spec2)) Expect(res.ResourceVersion).To(Equal(rv1_2)) if config.Spec.DatastoreType != apiconfig.Kubernetes { @@ -260,7 +259,7 @@ var _ = testutils.E2eDatastoreDescribe("GlobalNetworkPolicy tests", testutils.Da outList, outError = c.GlobalNetworkPolicies().List(ctx, options.ListOptions{ResourceVersion: rv1_1}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name1, tier), spec1), + testutils.Resource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, name1, spec1), )) } @@ -268,21 +267,21 @@ var _ = testutils.E2eDatastoreDescribe("GlobalNetworkPolicy tests", testutils.Da outList, outError = c.GlobalNetworkPolicies().List(ctx, options.ListOptions{}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name1, tier), spec2), - testutils.Resource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name2, tier), spec2), + testutils.Resource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, name1, spec2), + testutils.Resource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, name2, spec2), )) if config.Spec.DatastoreType != apiconfig.Kubernetes { By("Deleting GlobalNetworkPolicy (name1) with the old resource version") _, outError = c.GlobalNetworkPolicies().Delete(ctx, name1, options.DeleteOptions{ResourceVersion: rv1_1}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(Equal("update conflict: GlobalNetworkPolicy(" + tieredGNPName(name1, tier) + ")")) + Expect(outError.Error()).To(Equal("update conflict: GlobalNetworkPolicy(" + name1 + ")")) } By("Deleting GlobalNetworkPolicy (name1) with the new resource version") dres, outError := c.GlobalNetworkPolicies().Delete(ctx, name1, options.DeleteOptions{ResourceVersion: rv1_2}) Expect(outError).NotTo(HaveOccurred()) - Expect(dres).To(MatchResource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name1, tier), spec2)) + Expect(dres).To(MatchResource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, name1, spec2)) time.Sleep(1 * time.Second) if config.Spec.DatastoreType != apiconfig.Kubernetes { @@ -295,7 +294,7 @@ var _ = testutils.E2eDatastoreDescribe("GlobalNetworkPolicy tests", testutils.Da time.Sleep(2 * time.Second) _, outError = c.GlobalNetworkPolicies().Get(ctx, name2, options.GetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: GlobalNetworkPolicy(" + tieredGNPName(name2, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: GlobalNetworkPolicy(" + name2 + ") with error:")) By("Creating GlobalNetworkPolicy name2 with a 2s TTL and waiting for the entry to be deleted") _, outError = c.GlobalNetworkPolicies().Create(ctx, &apiv3.GlobalNetworkPolicy{ @@ -309,21 +308,21 @@ var _ = testutils.E2eDatastoreDescribe("GlobalNetworkPolicy tests", testutils.Da time.Sleep(2 * time.Second) _, outError = c.GlobalNetworkPolicies().Get(ctx, name2, options.GetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: GlobalNetworkPolicy(" + tieredGNPName(name2, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: GlobalNetworkPolicy(" + name2 + ") with error:")) } if config.Spec.DatastoreType == apiconfig.Kubernetes { By("Deleting GlobalNetworkPolicy (name2) for KDD that does not support TTL") dres, outError = c.GlobalNetworkPolicies().Delete(ctx, name2, options.DeleteOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(dres).To(MatchResource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name2, tier), spec2)) + Expect(dres).To(MatchResource(apiv3.KindGlobalNetworkPolicy, testutils.ExpectNoNamespace, name2, spec2)) time.Sleep(1 * time.Second) } By("Attempting to delete GlobalNetworkPolicy (name2) again") _, outError = c.GlobalNetworkPolicies().Delete(ctx, name2, options.DeleteOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: GlobalNetworkPolicy(" + tieredGNPName(name2, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: GlobalNetworkPolicy(" + name2 + ") with error:")) By("Listing all GlobalNetworkPolicies and expecting no items") outList, outError = c.GlobalNetworkPolicies().List(ctx, options.ListOptions{}) @@ -333,7 +332,7 @@ var _ = testutils.E2eDatastoreDescribe("GlobalNetworkPolicy tests", testutils.Da By("Getting GlobalNetworkPolicy (name2) and expecting an error") _, outError = c.GlobalNetworkPolicies().Get(ctx, name2, options.GetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: GlobalNetworkPolicy(" + tieredGNPName(name2, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: GlobalNetworkPolicy(" + name2 + ") with error:")) }, // Pass two fully populated GlobalNetworkPolicySpecs in default tier and expect the series of operations to succeed. @@ -347,7 +346,7 @@ var _ = testutils.E2eDatastoreDescribe("GlobalNetworkPolicy tests", testutils.Da ) DescribeTable("GlobalNetworkPolicy default tier name test", - func(policyName string, incorrectPrefixPolicyName string) { + func(policyName string, prefixPolicyName string) { By("Getting the policy before it was created") _, err := c.GlobalNetworkPolicies().Get(ctx, policyName, options.GetOptions{}) Expect(err).To(HaveOccurred()) @@ -369,12 +368,12 @@ var _ = testutils.E2eDatastoreDescribe("GlobalNetworkPolicy tests", testutils.Da Expect(err).ToNot(HaveOccurred()) Expect(returnedPolicy.Name).To(Equal(policyName)) - By("Creating the policy with incorrect prefix name") + By("Creating the policy with prefix name") _, err = c.GlobalNetworkPolicies().Create(ctx, &apiv3.GlobalNetworkPolicy{ - ObjectMeta: metav1.ObjectMeta{Name: incorrectPrefixPolicyName}, + ObjectMeta: metav1.ObjectMeta{Name: prefixPolicyName}, }, options.SetOptions{}) - Expect(err).To(HaveOccurred()) + Expect(err).NotTo(HaveOccurred()) By("Getting the policy") returnedPolicy, err = c.GlobalNetworkPolicies().Get(ctx, policyName, options.GetOptions{}) @@ -386,89 +385,21 @@ var _ = testutils.E2eDatastoreDescribe("GlobalNetworkPolicy tests", testutils.Da Expect(err).ToNot(HaveOccurred()) Expect(returnedPolicy.Name).To(Equal(policyName)) - By("Getting the policy with incorrect prefix") - _, err = c.GlobalNetworkPolicies().Get(ctx, incorrectPrefixPolicyName, options.GetOptions{}) - Expect(err).To(HaveOccurred()) - - By("Updating the policy with incorrect prefix") - _, err = c.GlobalNetworkPolicies().Update(ctx, &apiv3.GlobalNetworkPolicy{ - ObjectMeta: metav1.ObjectMeta{Name: incorrectPrefixPolicyName, ResourceVersion: "1234", CreationTimestamp: metav1.Now(), UID: uid}, - Spec: spec1, - }, options.SetOptions{}) - Expect(err).To(HaveOccurred()) + By("Getting the policy with prefix") + _, err = c.GlobalNetworkPolicies().Get(ctx, prefixPolicyName, options.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) By("Deleting policy") returnedPolicy, err = c.GlobalNetworkPolicies().Delete(ctx, policyName, options.DeleteOptions{}) Expect(returnedPolicy.Name).To(Equal(policyName)) Expect(err).ToNot(HaveOccurred()) + _, err = c.GlobalNetworkPolicies().Delete(ctx, prefixPolicyName, options.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) }, Entry("GlobalNetworkPolicy without default tier prefix", "netpol", "default.netpol"), Entry("GlobalNetworkPolicy with default tier prefix", "default.netpol", "netpol"), ) - Describe("GlobalNetworkPolicy without name on the projectcalico.org annotation", func() { - It("Should return the name without default prefix", func() { - if config.Spec.DatastoreType == apiconfig.Kubernetes { - config, _, err := k8s.CreateKubernetesClientset(&config.Spec) - Expect(err).NotTo(HaveOccurred()) - config.ContentType = "application/json" - cli, err := ctrlclient.New(config, ctrlclient.Options{}) - Expect(err).NotTo(HaveOccurred()) - - // Create v1 crd with empty metadata annotation name - annotations := map[string]string{} - annotations["projectcalico.org/metadata"] = "{}" - policy := &apiv3.GlobalNetworkPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: annotations, - Name: "default.prefix-test-policy"}, - Spec: apiv3.GlobalNetworkPolicySpec{}, - } - err = cli.Create(context.Background(), policy) - Expect(err).NotTo(HaveOccurred()) - - // We should be able to get it without the default. prefix - _, err = c.GlobalNetworkPolicies().Get(ctx, "prefix-test-policy", options.GetOptions{}) - Expect(err).ToNot(HaveOccurred()) - } - }) - }) - - DescribeTable("GlobalNetworkPolicy name validation tests", - func(policyName string, tier string, expectError bool) { - if tier != "default" { - // Create the tier if required before running other tiered policy tests. - tierSpec := apiv3.TierSpec{Order: &tierOrder} - By("Creating the tier") - _, resErr := c.Tiers().Create(ctx, &apiv3.Tier{ - ObjectMeta: metav1.ObjectMeta{Name: tier}, - Spec: tierSpec, - }, options.SetOptions{}) - Expect(resErr).NotTo(HaveOccurred()) - } - - _, err := c.GlobalNetworkPolicies().Create(ctx, - &apiv3.GlobalNetworkPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: policyName}, - Spec: apiv3.GlobalNetworkPolicySpec{ - Tier: tier, - }, - }, options.SetOptions{}) - - if expectError { - Expect(err).To(HaveOccurred()) - } else { - Expect(err).ToNot(HaveOccurred()) - } - }, - Entry("GlobalNetworkPolicy in default tier without prefix", "netpol", "default", false), - Entry("GlobalNetworkPolicy in default tier with prefix", "default.netpol", "default", false), - Entry("GlobalNetworkPolicy in custom tier with correct prefix", "tier1.netpol", "tier1", false), - Entry("GlobalNetworkPolicy in custom tier without prefix", "netpol", "tier1", true), - Entry("GlobalNetworkPolicy in custom tier with incorrect prefix", "tier1.netpol", "tier2", true), - ) - Describe("GlobalNetworkPolicy watch functionality", func() { It("should handle watch events for different resource versions and event types", func() { By("Listing GlobalNetworkPolicies with the latest resource version and checking for two results with name1/spec2 and name2/spec2") diff --git a/libcalico-go/lib/clientv3/globalnetworkset_e2e_test.go b/libcalico-go/lib/clientv3/globalnetworkset_e2e_test.go index e5852c7c3ae..f9a78119baf 100644 --- a/libcalico-go/lib/clientv3/globalnetworkset_e2e_test.go +++ b/libcalico-go/lib/clientv3/globalnetworkset_e2e_test.go @@ -18,8 +18,7 @@ import ( "context" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/libcalico-go/lib/clientv3/hostendpoint_e2e_test.go b/libcalico-go/lib/clientv3/hostendpoint_e2e_test.go index 18589c96369..f680cca45ce 100644 --- a/libcalico-go/lib/clientv3/hostendpoint_e2e_test.go +++ b/libcalico-go/lib/clientv3/hostendpoint_e2e_test.go @@ -18,8 +18,7 @@ import ( "context" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" diff --git a/libcalico-go/lib/clientv3/interface.go b/libcalico-go/lib/clientv3/interface.go index 214b7770af6..8120606ac08 100644 --- a/libcalico-go/lib/clientv3/interface.go +++ b/libcalico-go/lib/clientv3/interface.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2017-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -39,8 +39,9 @@ type Interface interface { ClusterInformationClient KubeControllersConfigurationClient CalicoNodeStatusClient - IPAMConfigClient + IPAMConfigurationClient BlockAffinitiesClient + LiveMigrationsClient // Tiers returns an interface for managing tier resources. Tiers() TierInterface // StagedGlobalNetworkPolicies returns an interface for managing staged global network policy resources. @@ -148,9 +149,9 @@ type CalicoNodeStatusClient interface { CalicoNodeStatus() CalicoNodeStatusInterface } -type IPAMConfigClient interface { +type IPAMConfigurationClient interface { // IPAMConfig returns an interface for managing IPAMConfig resources. - IPAMConfig() IPAMConfigInterface + IPAMConfiguration() IPAMConfigurationInterface } type BlockAffinitiesClient interface { @@ -158,6 +159,11 @@ type BlockAffinitiesClient interface { BlockAffinities() BlockAffinityInterface } +type LiveMigrationsClient interface { + // LiveMigrations returns an interface for managing LiveMigration resources. + LiveMigrations() LiveMigrationInterface +} + type BGPFilterClient interface { // BGPFilter returns an interface for managing BGPFilter resources. BGPFilter() BGPFilterInterface diff --git a/libcalico-go/lib/clientv3/ipamconfig.go b/libcalico-go/lib/clientv3/ipamconfig.go index 3a65a21756c..a33ba3c301a 100644 --- a/libcalico-go/lib/clientv3/ipamconfig.go +++ b/libcalico-go/lib/clientv3/ipamconfig.go @@ -18,96 +18,101 @@ import ( "context" "errors" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "github.com/projectcalico/calico/libcalico-go/lib/options" validator "github.com/projectcalico/calico/libcalico-go/lib/validator/v3" "github.com/projectcalico/calico/libcalico-go/lib/watch" ) -// IPAMConfigInterface has methods to work with IPAMConfig resources. -type IPAMConfigInterface interface { - Create(ctx context.Context, res *libapiv3.IPAMConfig, opts options.SetOptions) (*libapiv3.IPAMConfig, error) - Update(ctx context.Context, res *libapiv3.IPAMConfig, opts options.SetOptions) (*libapiv3.IPAMConfig, error) - Delete(ctx context.Context, name string, opts options.DeleteOptions) (*libapiv3.IPAMConfig, error) - Get(ctx context.Context, name string, opts options.GetOptions) (*libapiv3.IPAMConfig, error) - List(ctx context.Context, opts options.ListOptions) (*libapiv3.IPAMConfigList, error) +const ( + GlobalIPAMConfigName = "default" +) + +// IPAMConfigurationInterface has methods to work with IPAMConfiguration resources. +type IPAMConfigurationInterface interface { + Create(ctx context.Context, res *v3.IPAMConfiguration, opts options.SetOptions) (*v3.IPAMConfiguration, error) + Update(ctx context.Context, res *v3.IPAMConfiguration, opts options.SetOptions) (*v3.IPAMConfiguration, error) + Delete(ctx context.Context, name string, opts options.DeleteOptions) (*v3.IPAMConfiguration, error) + Get(ctx context.Context, name string, opts options.GetOptions) (*v3.IPAMConfiguration, error) + List(ctx context.Context, opts options.ListOptions) (*v3.IPAMConfigurationList, error) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) } -// IPAMConfigs implements IPAMConfigInterface -type IPAMConfigs struct { +// IPAMConfigurations implements IPAMConfigurationInterface +type IPAMConfigurations struct { client client } -func validateMetadata(res *libapiv3.IPAMConfig) error { - if res.ObjectMeta.GetName() != libapiv3.GlobalIPAMConfigName { - return errors.New("Cannot create a IPAMConfiguration resource with a name other than \"default\"") +func validateMetadata(res *v3.IPAMConfiguration) error { + if res.ObjectMeta.GetName() != GlobalIPAMConfigName { + return errors.New("Cannot create an IPAMConfiguration resource with a name other than \"default\"") } return nil } -// Create takes the representation of a IPAMConfig and creates it. Returns the stored -// representation of the IPAMConfig, and an error, if there is any. -func (r IPAMConfigs) Create(ctx context.Context, res *libapiv3.IPAMConfig, opts options.SetOptions) (*libapiv3.IPAMConfig, error) { +// Create takes the representation of a IPAMConfiguration and creates it. Returns the stored +// representation of the IPAMConfiguration, and an error, if there is any. +func (r IPAMConfigurations) Create(ctx context.Context, res *v3.IPAMConfiguration, opts options.SetOptions) (*v3.IPAMConfiguration, error) { if err := validator.Validate(res); err != nil { return nil, err } else if err := validateMetadata(res); err != nil { return nil, err } - out, err := r.client.resources.Create(ctx, opts, libapiv3.KindIPAMConfig, res) + out, err := r.client.resources.Create(ctx, opts, v3.KindIPAMConfiguration, res) if out != nil { - return out.(*libapiv3.IPAMConfig), err + return out.(*v3.IPAMConfiguration), err } return nil, err } -// Update takes the representation of a IPAMConfig and updates it. Returns the stored -// representation of the IPAMConfig, and an error, if there is any. -func (r IPAMConfigs) Update(ctx context.Context, res *libapiv3.IPAMConfig, opts options.SetOptions) (*libapiv3.IPAMConfig, error) { +// Update takes the representation of a IPAMConfiguration and updates it. Returns the stored +// representation of the IPAMConfiguration, and an error, if there is any. +func (r IPAMConfigurations) Update(ctx context.Context, res *v3.IPAMConfiguration, opts options.SetOptions) (*v3.IPAMConfiguration, error) { if err := validator.Validate(res); err != nil { return nil, err } else if err := validateMetadata(res); err != nil { return nil, err } - out, err := r.client.resources.Update(ctx, opts, libapiv3.KindIPAMConfig, res) + out, err := r.client.resources.Update(ctx, opts, v3.KindIPAMConfiguration, res) if out != nil { - return out.(*libapiv3.IPAMConfig), err + return out.(*v3.IPAMConfiguration), err } return nil, err } -// Delete takes name of the IPAMConfig and deletes it. Returns an error if one occurs. -func (r IPAMConfigs) Delete(ctx context.Context, name string, opts options.DeleteOptions) (*libapiv3.IPAMConfig, error) { - out, err := r.client.resources.Delete(ctx, opts, libapiv3.KindIPAMConfig, noNamespace, name) +// Delete takes name of the IPAMConfiguration and deletes it. Returns an error if one occurs. +func (r IPAMConfigurations) Delete(ctx context.Context, name string, opts options.DeleteOptions) (*v3.IPAMConfiguration, error) { + out, err := r.client.resources.Delete(ctx, opts, v3.KindIPAMConfiguration, noNamespace, name) if out != nil { - return out.(*libapiv3.IPAMConfig), err + return out.(*v3.IPAMConfiguration), err } return nil, err } -// Get takes name of the IPAMConfig, and returns the corresponding IPAMConfig object, +// Get takes name of the IPAMConfiguration, and returns the corresponding IPAMConfiguration object, // and an error if there is any. -func (r IPAMConfigs) Get(ctx context.Context, name string, opts options.GetOptions) (*libapiv3.IPAMConfig, error) { - out, err := r.client.resources.Get(ctx, opts, libapiv3.KindIPAMConfig, noNamespace, name) +func (r IPAMConfigurations) Get(ctx context.Context, name string, opts options.GetOptions) (*v3.IPAMConfiguration, error) { + out, err := r.client.resources.Get(ctx, opts, v3.KindIPAMConfiguration, noNamespace, name) if out != nil { - return out.(*libapiv3.IPAMConfig), err + return out.(*v3.IPAMConfiguration), err } return nil, err } -// List returns the list of IPAMConfig objects that match the supplied options. -func (r IPAMConfigs) List(ctx context.Context, opts options.ListOptions) (*libapiv3.IPAMConfigList, error) { - res := &libapiv3.IPAMConfigList{} - if err := r.client.resources.List(ctx, opts, libapiv3.KindIPAMConfig, libapiv3.KindIPAMConfigList, res); err != nil { +// List returns the list of IPAMConfiguration objects that match the supplied options. +func (r IPAMConfigurations) List(ctx context.Context, opts options.ListOptions) (*v3.IPAMConfigurationList, error) { + res := &v3.IPAMConfigurationList{} + if err := r.client.resources.List(ctx, opts, v3.KindIPAMConfiguration, v3.KindIPAMConfigurationList, res); err != nil { return nil, err } return res, nil } -// Watch returns a watch.Interface that watches the IPAMConfigs that match the +// Watch returns a watch.Interface that watches the IPAMConfigurations that match the // supplied options. -func (r IPAMConfigs) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) { - return r.client.resources.Watch(ctx, opts, libapiv3.KindIPAMConfig, nil) +func (r IPAMConfigurations) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) { + return r.client.resources.Watch(ctx, opts, v3.KindIPAMConfiguration, nil) } diff --git a/libcalico-go/lib/clientv3/ipamconfig_e2e_test.go b/libcalico-go/lib/clientv3/ipamconfig_e2e_test.go index 3f3b4c0059d..cb3ad3313a8 100644 --- a/libcalico-go/lib/clientv3/ipamconfig_e2e_test.go +++ b/libcalico-go/lib/clientv3/ipamconfig_e2e_test.go @@ -17,14 +17,13 @@ package clientv3_test import ( "context" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" "github.com/projectcalico/calico/libcalico-go/lib/backend" bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/clientv3" @@ -32,15 +31,15 @@ import ( "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) -var _ = testutils.E2eDatastoreDescribe("IPAMConfig tests", testutils.DatastoreAll, func(config apiconfig.CalicoAPIConfig) { +var _ = testutils.E2eDatastoreDescribe("IPAMConfiguration tests", testutils.DatastoreAll, func(config apiconfig.CalicoAPIConfig) { ctx := context.Background() name := "default" - spec1 := libapiv3.IPAMConfigSpec{ + spec1 := v3.IPAMConfigurationSpec{ StrictAffinity: true, AutoAllocateBlocks: true, MaxBlocksPerHost: 2, } - spec2 := libapiv3.IPAMConfigSpec{ + spec2 := v3.IPAMConfigurationSpec{ StrictAffinity: false, AutoAllocateBlocks: true, MaxBlocksPerHost: 0, @@ -60,69 +59,69 @@ var _ = testutils.E2eDatastoreDescribe("IPAMConfig tests", testutils.DatastoreAl Expect(err).NotTo(HaveOccurred()) }) - DescribeTable("IPAMConfig e2e CRUD tests", - func(name string, spec1, spec2 libapiv3.IPAMConfigSpec) { - By("Updating the IPAMConfig before it is created") - _, outError := c.IPAMConfig().Update(ctx, &libapiv3.IPAMConfig{ + DescribeTable("IPAMConfiguration e2e CRUD tests", + func(name string, spec1, spec2 v3.IPAMConfigurationSpec) { + By("Updating the IPAMConfiguration before it is created") + _, outError := c.IPAMConfiguration().Update(ctx, &v3.IPAMConfiguration{ ObjectMeta: metav1.ObjectMeta{Name: name, ResourceVersion: "1234", CreationTimestamp: metav1.Now(), UID: uid}, Spec: spec1, }, options.SetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: IPAMConfig(" + name + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: IPAMConfiguration(" + name + ") with error:")) - By("Attempting to creating a new IPAMConfig with spec1 and a non-empty ResourceVersion") - _, outError = c.IPAMConfig().Create(ctx, &libapiv3.IPAMConfig{ + By("Attempting to creating a new IPAMConfiguration with spec1 and a non-empty ResourceVersion") + _, outError = c.IPAMConfiguration().Create(ctx, &v3.IPAMConfiguration{ ObjectMeta: metav1.ObjectMeta{Name: name, ResourceVersion: "12345"}, Spec: spec1, }, options.SetOptions{}) Expect(outError).To(HaveOccurred()) Expect(outError.Error()).To(Equal("error with field Metadata.ResourceVersion = '12345' (field must not be set for a Create request)")) - By("Getting IPAMConfig before it is created") - _, outError = c.IPAMConfig().Get(ctx, name, options.GetOptions{}) + By("Getting IPAMConfiguration before it is created") + _, outError = c.IPAMConfiguration().Get(ctx, name, options.GetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: IPAMConfig(" + name + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: IPAMConfiguration(" + name + ") with error:")) - By("Attempting to create a new IPAMConfig with a non-default name and spec1") - _, outError = c.IPAMConfig().Create(ctx, &libapiv3.IPAMConfig{ + By("Attempting to create a new IPAMConfiguration with a non-default name and spec1") + _, outError = c.IPAMConfiguration().Create(ctx, &v3.IPAMConfiguration{ ObjectMeta: metav1.ObjectMeta{Name: "not-default"}, Spec: spec1, }, options.SetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(Equal("Cannot create a IPAMConfiguration resource with a name other than \"default\"")) + Expect(outError.Error()).To(Equal("Cannot create an IPAMConfiguration resource with a name other than \"default\"")) - By("Creating a new IPAMConfig with spec1") - res1, outError := c.IPAMConfig().Create(ctx, &libapiv3.IPAMConfig{ + By("Creating a new IPAMConfiguration with spec1") + res1, outError := c.IPAMConfiguration().Create(ctx, &v3.IPAMConfiguration{ ObjectMeta: metav1.ObjectMeta{Name: name}, Spec: spec1, }, options.SetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res1).To(MatchResource(libapiv3.KindIPAMConfig, testutils.ExpectNoNamespace, name, spec1)) - Expect(res1.GroupVersionKind()).To(Equal(schema.GroupVersionKind{Group: "projectcalico.org", Version: "v3", Kind: "IPAMConfig"})) + Expect(res1).To(MatchResource(v3.KindIPAMConfiguration, testutils.ExpectNoNamespace, name, spec1)) + Expect(res1.GroupVersionKind()).To(Equal(schema.GroupVersionKind{Group: "projectcalico.org", Version: "v3", Kind: "IPAMConfiguration"})) - By("Attempting to create the same IPAMConfig but with spec2") - _, outError = c.IPAMConfig().Create(ctx, &libapiv3.IPAMConfig{ + By("Attempting to create the same IPAMConfiguration but with spec2") + _, outError = c.IPAMConfiguration().Create(ctx, &v3.IPAMConfiguration{ ObjectMeta: metav1.ObjectMeta{Name: name}, Spec: spec2, }, options.SetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource already exists: IPAMConfig(" + name + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource already exists: IPAMConfiguration(" + name + ")")) - By("Getting IPAMConfig and comparing the output against spec1") - res, outError := c.IPAMConfig().Get(ctx, name, options.GetOptions{}) + By("Getting IPAMConfiguration and comparing the output against spec1") + res, outError := c.IPAMConfiguration().Get(ctx, name, options.GetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res).To(MatchResource(libapiv3.KindIPAMConfig, testutils.ExpectNoNamespace, name, spec1)) + Expect(res).To(MatchResource(v3.KindIPAMConfiguration, testutils.ExpectNoNamespace, name, spec1)) Expect(res.ResourceVersion).To(Equal(res1.ResourceVersion)) - Expect(res.GroupVersionKind()).To(Equal(schema.GroupVersionKind{Group: "projectcalico.org", Version: "v3", Kind: "IPAMConfig"})) + Expect(res.GroupVersionKind()).To(Equal(schema.GroupVersionKind{Group: "projectcalico.org", Version: "v3", Kind: "IPAMConfiguration"})) - By("Updating IPAMConfig with spec2") + By("Updating IPAMConfiguration with spec2") res1.Spec = spec2 - res1, outError = c.IPAMConfig().Update(ctx, res1, options.SetOptions{}) + res1, outError = c.IPAMConfiguration().Update(ctx, res1, options.SetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res1).To(MatchResource(libapiv3.KindIPAMConfig, testutils.ExpectNoNamespace, name, spec2)) + Expect(res1).To(MatchResource(v3.KindIPAMConfiguration, testutils.ExpectNoNamespace, name, spec2)) - By("Attempting to update the IPAMConfig without a Creation Timestamp") - res, outError = c.IPAMConfig().Update(ctx, &libapiv3.IPAMConfig{ + By("Attempting to update the IPAMConfiguration without a Creation Timestamp") + res, outError = c.IPAMConfiguration().Update(ctx, &v3.IPAMConfiguration{ ObjectMeta: metav1.ObjectMeta{Name: name, ResourceVersion: "1234", UID: uid}, Spec: spec1, }, options.SetOptions{}) @@ -130,8 +129,8 @@ var _ = testutils.E2eDatastoreDescribe("IPAMConfig tests", testutils.DatastoreAl Expect(res).To(BeNil()) Expect(outError.Error()).To(Equal("error with field Metadata.CreationTimestamp = '0001-01-01 00:00:00 +0000 UTC' (field must be set for an Update request)")) - By("Attempting to update the IPAMConfig without a UID") - res, outError = c.IPAMConfig().Update(ctx, &libapiv3.IPAMConfig{ + By("Attempting to update the IPAMConfiguration without a UID") + res, outError = c.IPAMConfiguration().Update(ctx, &v3.IPAMConfiguration{ ObjectMeta: metav1.ObjectMeta{Name: name, ResourceVersion: "1234", CreationTimestamp: metav1.Now()}, Spec: spec1, }, options.SetOptions{}) @@ -139,31 +138,31 @@ var _ = testutils.E2eDatastoreDescribe("IPAMConfig tests", testutils.DatastoreAl Expect(res).To(BeNil()) Expect(outError.Error()).To(Equal("error with field Metadata.UID = '' (field must be set for an Update request)")) - By("Deleting IPAMConfig with the new resource version") - dres, outError := c.IPAMConfig().Delete(ctx, name, options.DeleteOptions{}) + By("Deleting IPAMConfiguration with the new resource version") + dres, outError := c.IPAMConfiguration().Delete(ctx, name, options.DeleteOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(dres).To(testutils.MatchResource(libapiv3.KindIPAMConfig, testutils.ExpectNoNamespace, name, spec2)) + Expect(dres).To(testutils.MatchResource(v3.KindIPAMConfiguration, testutils.ExpectNoNamespace, name, spec2)) - By("Listing IPAMConfig and expecting error") - l, outError := c.IPAMConfig().List(ctx, options.ListOptions{}) + By("Listing IPAMConfiguration and expecting error") + l, outError := c.IPAMConfiguration().List(ctx, options.ListOptions{}) Expect(outError).NotTo(HaveOccurred()) for _, res := range l.Items { - Expect(res.GroupVersionKind()).To(Equal(schema.GroupVersionKind{Group: "projectcalico.org", Version: "v3", Kind: "IPAMConfig"})) + Expect(res.GroupVersionKind()).To(Equal(schema.GroupVersionKind{Group: "projectcalico.org", Version: "v3", Kind: "IPAMConfiguration"})) } }, - // Test 1: Pass two fully populated IPAMConfigSpecs and expect the series of operations to succeed. - Entry("Two fully populated IPAMConfigSpecs", name, spec1, spec2), + // Test 1: Pass two fully populated IPAMConfigurationSpecs and expect the series of operations to succeed. + Entry("Two fully populated IPAMConfigurationSpecs", name, spec1, spec2), ) It("should reject MaxBlocksPerHost less than zero", func() { - _, err := c.IPAMConfig().Create(ctx, &libapiv3.IPAMConfig{ + _, err := c.IPAMConfiguration().Create(ctx, &v3.IPAMConfiguration{ ObjectMeta: metav1.ObjectMeta{Name: "default"}, - Spec: libapiv3.IPAMConfigSpec{ + Spec: v3.IPAMConfigurationSpec{ MaxBlocksPerHost: -1, }, }, options.SetOptions{}) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("error with field MaxBlocksPerHost = '-1' (must be greater than or equal to 0)")) + Expect(err.Error()).To(Equal("error with field MaxBlocksPerHost = '-1' (must be greater than or equal to 0)"), err.Error()) }) }) diff --git a/libcalico-go/lib/clientv3/ippool.go b/libcalico-go/lib/clientv3/ippool.go index e12de8bb5ec..22ce1a63ca7 100644 --- a/libcalico-go/lib/clientv3/ippool.go +++ b/libcalico-go/lib/clientv3/ippool.go @@ -15,6 +15,7 @@ package clientv3 import ( + "bytes" "context" "fmt" "net" @@ -296,6 +297,47 @@ func (r ipPools) Watch(ctx context.Context, opts options.ListOptions) (watch.Int return r.client.resources.Watch(ctx, opts, apiv3.KindIPPool, nil) } +// cidrChangeOK returns true if the new CIDR is an acceptable update to the old one. +// +// Allowed cases: +// - The CIDRs match exactly. +// - The CIDRs are semantically identical and the new CIDR is simply the canonical +// form of the old one (i.e., the update only normalizes formatting). +// +// This accounts for clients that may have stored non-canonical CIDRs by bypassing +// our validation logic, allowing updates that preserve the same network while +// converting the CIDR to its canonical form. +func cidrChangeOK(oldCIDR, newCIDR string) bool { + // 1. If strings match exactly, allow. + if oldCIDR == newCIDR { + return true + } + + // Parse both + _, oldNet, errOld := net.ParseCIDR(oldCIDR) + _, newNet, errNew := net.ParseCIDR(newCIDR) + + // If either fails to parse → reject + if errOld != nil || errNew != nil { + return false + } + + // 2. Check semantic equality (same network IP + mask) + sameNetwork := oldNet.IP.Equal(newNet.IP) && + bytes.Equal(oldNet.Mask, newNet.Mask) + if !sameNetwork { + return false + } + + // 3. New CIDR must already be canonical (IPNet.String()); don't allow introducing + // a non-canonical CIDR. + if newCIDR != newNet.String() { + return false + } + + return true +} + // validateAndSetDefaults validates IPPool fields and sets default values that are // not assigned. // The old pool will be unassigned for a Create. @@ -333,9 +375,9 @@ func (r ipPools) validateAndSetDefaults(ctx context.Context, new, old *apiv3.IPP } // If there was a previous pool then this must be an Update, validate that the - // CIDR has not changed. Since we are using normalized CIDRs we can just do a - // simple string comparison. - if old != nil && old.Spec.CIDR != new.Spec.CIDR { + // CIDR has not changed. Use semantic comparison to handle different textual + // representations of the same CIDR (e.g., IPv6 with different zero compression). + if old != nil && !cidrChangeOK(old.Spec.CIDR, new.Spec.CIDR) { errFields = append(errFields, cerrors.ErroredField{ Name: "IPPool.Spec.CIDR", Reason: "IPPool CIDR cannot be modified", diff --git a/libcalico-go/lib/clientv3/ippool_e2e_test.go b/libcalico-go/lib/clientv3/ippool_e2e_test.go index 40d880af9d2..74bad417231 100644 --- a/libcalico-go/lib/clientv3/ippool_e2e_test.go +++ b/libcalico-go/lib/clientv3/ippool_e2e_test.go @@ -19,15 +19,14 @@ import ( "time" "github.com/google/uuid" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend" backendapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" @@ -1065,7 +1064,7 @@ var _ = testutils.E2eDatastoreDescribe("IPPool tests (etcd only)", testutils.Dat // Create test node host := "host-test" - _, err = c.Nodes().Create(ctx, &libapiv3.Node{ObjectMeta: metav1.ObjectMeta{Name: host}}, options.SetOptions{}) + _, err = c.Nodes().Create(ctx, &internalapi.Node{ObjectMeta: metav1.ObjectMeta{Name: host}}, options.SetOptions{}) Expect(err).NotTo(HaveOccurred()) // Allocate an IP so that a block is allocated @@ -1141,7 +1140,7 @@ var _ = testutils.E2eDatastoreDescribe("IPPool tests (etcd only)", testutils.Dat Expect(err).NotTo(HaveOccurred()) host := "host-test" - _, err = c.Nodes().Create(ctx, &libapiv3.Node{ObjectMeta: metav1.ObjectMeta{Name: host}}, options.SetOptions{}) + _, err = c.Nodes().Create(ctx, &internalapi.Node{ObjectMeta: metav1.ObjectMeta{Name: host}}, options.SetOptions{}) Expect(err).NotTo(HaveOccurred()) // Allocate an IP so that a block is allocated diff --git a/libcalico-go/lib/clientv3/ippool_kdd_conversion_test.go b/libcalico-go/lib/clientv3/ippool_kdd_conversion_test.go deleted file mode 100644 index 420b13d5b4f..00000000000 --- a/libcalico-go/lib/clientv3/ippool_kdd_conversion_test.go +++ /dev/null @@ -1,396 +0,0 @@ -// Copyright (c) 2017,2019,2021 Tigera, Inc. All rights reserved. - -// 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 clientv3_test - -import ( - "context" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - "github.com/projectcalico/calico/libcalico-go/lib/backend" - "github.com/projectcalico/calico/libcalico-go/lib/backend/encap" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/clientv3" - "github.com/projectcalico/calico/libcalico-go/lib/options" - "github.com/projectcalico/calico/libcalico-go/lib/testutils" - "github.com/projectcalico/calico/libcalico-go/lib/watch" -) - -var _ = testutils.E2eDatastoreDescribe("IPPool KDD v1 to v3 migration tests", testutils.DatastoreK8s, func(config apiconfig.CalicoAPIConfig) { - ctx := context.Background() - name1 := "ippool-1" - name2 := "ippool-2" - automatic := apiv3.Automatic - - spec1_v3 := apiv3.IPPoolSpec{ - CIDR: "1.2.3.0/24", - NATOutgoing: true, - IPIPMode: apiv3.IPIPModeCrossSubnet, - VXLANMode: apiv3.VXLANModeNever, - BlockSize: 26, - NodeSelector: "all()", - AllowedUses: []apiv3.IPPoolAllowedUse{apiv3.IPPoolAllowedUseWorkload, apiv3.IPPoolAllowedUseTunnel}, - AssignmentMode: &automatic, - } - kvp1 := &model.KVPair{ - Key: model.ResourceKey{ - Name: name1, - Kind: apiv3.KindIPPool, - }, - Value: &apiv3.IPPool{ - TypeMeta: metav1.TypeMeta{ - Kind: apiv3.KindIPPool, - APIVersion: apiv3.GroupVersionCurrent, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name1, - }, - Spec: apiv3.IPPoolSpec{ - CIDR: "1.2.3.0/24", - Disabled: false, - NATOutgoing: true, - NodeSelector: "all()", - VXLANMode: apiv3.VXLANModeNever, - IPIP: &apiv3.IPIPConfiguration{ - Enabled: true, - Mode: encap.CrossSubnet, - }, - BlockSize: 26, - AssignmentMode: &automatic, - }, - }, - } - - spec2_v3 := apiv3.IPPoolSpec{ - CIDR: "2001::/120", - NATOutgoing: true, - IPIPMode: apiv3.IPIPModeNever, - VXLANMode: apiv3.VXLANModeNever, - BlockSize: 122, - NodeSelector: "all()", - AllowedUses: []apiv3.IPPoolAllowedUse{apiv3.IPPoolAllowedUseWorkload, apiv3.IPPoolAllowedUseTunnel}, - AssignmentMode: &automatic, - } - kvp2 := &model.KVPair{ - Key: model.ResourceKey{ - Name: name1, - Kind: apiv3.KindIPPool, - }, - Value: &apiv3.IPPool{ - TypeMeta: metav1.TypeMeta{ - Kind: apiv3.KindIPPool, - APIVersion: apiv3.GroupVersionCurrent, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name1, - }, - Spec: apiv3.IPPoolSpec{ - CIDR: "2001::/120", - Disabled: false, - NATOutgoing: true, - BlockSize: 122, - NodeSelector: "all()", - VXLANMode: apiv3.VXLANModeNever, - IPIP: &apiv3.IPIPConfiguration{ - Enabled: false, - }, - AssignmentMode: &automatic, - }, - }, - } - - spec3_v3 := apiv3.IPPoolSpec{ - CIDR: "1.1.1.0/24", - NATOutgoing: false, - IPIPMode: apiv3.IPIPModeAlways, - VXLANMode: apiv3.VXLANModeNever, - BlockSize: 26, - NodeSelector: "all()", - AllowedUses: []apiv3.IPPoolAllowedUse{apiv3.IPPoolAllowedUseWorkload, apiv3.IPPoolAllowedUseTunnel}, - AssignmentMode: &automatic, - } - kvp3 := &model.KVPair{ - Key: model.ResourceKey{ - Name: name1, - Kind: apiv3.KindIPPool, - }, - Value: &apiv3.IPPool{ - TypeMeta: metav1.TypeMeta{ - Kind: apiv3.KindIPPool, - APIVersion: apiv3.GroupVersionCurrent, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name1, - }, - Spec: apiv3.IPPoolSpec{ - CIDR: "1.1.1.0/24", - Disabled: false, - VXLANMode: apiv3.VXLANModeNever, - IPIP: &apiv3.IPIPConfiguration{ - Enabled: true, - }, - BlockSize: 26, - NodeSelector: "all()", - AssignmentMode: &automatic, - }, - }, - } - - spec5_v3 := apiv3.IPPoolSpec{ - CIDR: "1.2.3.0/24", - NATOutgoing: true, - IPIPMode: apiv3.IPIPModeAlways, - VXLANMode: apiv3.VXLANModeNever, - BlockSize: 26, - NodeSelector: "all()", - AllowedUses: []apiv3.IPPoolAllowedUse{apiv3.IPPoolAllowedUseWorkload, apiv3.IPPoolAllowedUseTunnel}, - AssignmentMode: &automatic, - } - kvp5 := &model.KVPair{ - Key: model.ResourceKey{ - Name: name1, - Kind: apiv3.KindIPPool, - }, - Value: &apiv3.IPPool{ - TypeMeta: metav1.TypeMeta{ - Kind: apiv3.KindIPPool, - APIVersion: apiv3.GroupVersionCurrent, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name1, - }, - Spec: apiv3.IPPoolSpec{ - CIDR: "1.2.3.0/24", - Disabled: false, - VXLANMode: apiv3.VXLANModeNever, - IPIP: &apiv3.IPIPConfiguration{ - Enabled: true, - Mode: encap.Always, - }, - NATOutgoing: true, - NATOutgoingV1: false, - BlockSize: 26, - NodeSelector: "all()", - AssignmentMode: &automatic, - }, - }, - } - - spec6_v3 := apiv3.IPPoolSpec{ - CIDR: "1.2.3.0/24", - NATOutgoing: true, - IPIPMode: apiv3.IPIPModeCrossSubnet, - VXLANMode: apiv3.VXLANModeNever, - BlockSize: 26, - NodeSelector: "has(x)", - AllowedUses: []apiv3.IPPoolAllowedUse{apiv3.IPPoolAllowedUseWorkload, apiv3.IPPoolAllowedUseTunnel}, - AssignmentMode: &automatic, - } - kvp6 := &model.KVPair{ - Key: model.ResourceKey{ - Name: name1, - Kind: apiv3.KindIPPool, - }, - Value: &apiv3.IPPool{ - TypeMeta: metav1.TypeMeta{ - Kind: apiv3.KindIPPool, - APIVersion: apiv3.GroupVersionCurrent, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name1, - }, - Spec: apiv3.IPPoolSpec{ - CIDR: "1.2.3.0/24", - Disabled: false, - VXLANMode: apiv3.VXLANModeNever, - IPIPMode: apiv3.IPIPModeCrossSubnet, - IPIP: nil, - NATOutgoing: false, - NATOutgoingV1: true, - BlockSize: 26, - NodeSelector: "has(x)", - AssignmentMode: &automatic, - }, - }, - } - - DescribeTable("IPPool CRD with v1 IPIP field tests", - func(name1, name2 string, spec_v3 apiv3.IPPoolSpec, kvp *model.KVPair) { - c, err := clientv3.New(config) - Expect(err).NotTo(HaveOccurred()) - - be, err := backend.NewClient(config) - Expect(err).NotTo(HaveOccurred()) - be.Clean() - - By("Attempting to creating a new IPPool with the non-writable v1 IPIP field") - _, outError := c.IPPools().Create(ctx, &apiv3.IPPool{ - ObjectMeta: metav1.ObjectMeta{Name: name1}, - Spec: kvp.Value.(*apiv3.IPPool).Spec, - }, options.SetOptions{}) - Expect(outError).To(HaveOccurred()) - - By("Creating IPPool with v1 IPIP field directly in the backend") - outKVP1, err := be.Create(ctx, kvp) - Expect(err).NotTo(HaveOccurred()) - - outPool, err := be.Get(ctx, kvp.Key, outKVP1.Revision) - Expect(err).NotTo(HaveOccurred()) - Expect(outPool.Value.(*apiv3.IPPool).Spec.IPIP).To(BeNil()) - - // We don't expect the AllowedUses field to be filled in by the backend client. That is defaulted - // in the frontend. - spec_v3_copy := spec_v3 - spec_v3_copy.AllowedUses = nil - Expect(outPool.Value.(*apiv3.IPPool).Spec).To(Equal(spec_v3_copy)) - - By("Updating the IPPool from the API client with the non-writable v1 IPIP field") - _, outError = c.IPPools().Update(ctx, &apiv3.IPPool{ - // R.I.P: 'a-rabbit-ate-my-carrot' is no longer a valid UID according to Calico. - ObjectMeta: metav1.ObjectMeta{Name: name1, ResourceVersion: "555", CreationTimestamp: metav1.Now(), UID: uid}, - Spec: kvp.Value.(*apiv3.IPPool).Spec, - }, options.SetOptions{}) - Expect(outError).To(HaveOccurred()) - - By("Listing all the IPPools, expecting a single result with name1/spec_v1") - outList, outError := c.IPPools().List(ctx, options.ListOptions{}) - Expect(outError).NotTo(HaveOccurred()) - Expect(outList.Items).To(ConsistOf( - testutils.Resource(apiv3.KindIPPool, testutils.ExpectNoNamespace, name1, spec_v3), - )) - - By("Creating a new IPPool with name2/spec_v3") - - By("Creating another IPPool with v1 IPIP field directly in the backend name2/kvp_v3") - kvpName2 := *kvp - kvpName2.Key = model.ResourceKey{ - Name: name2, - Kind: apiv3.KindIPPool, - } - - // Also need to change the Value Metadata Name because that will be the CRD key and needs to be unique. - kvpName2.Value.(*apiv3.IPPool).Name = name2 - - _, err = be.Create(ctx, &kvpName2) - Expect(err).NotTo(HaveOccurred()) - - By("Listing all the IPPools, expecting a two results with name1/spec_v1 and name2/spec_v3") - outList, outError = c.IPPools().List(ctx, options.ListOptions{}) - Expect(outError).NotTo(HaveOccurred()) - Expect(outList.Items).To(ConsistOf( - testutils.Resource(apiv3.KindIPPool, testutils.ExpectNoNamespace, name1, spec_v3), - testutils.Resource(apiv3.KindIPPool, testutils.ExpectNoNamespace, name2, spec_v3), - )) - }, - - Entry("IPv4 IPPool CRD with v1 IPIP field and IPIP Enabled set to true and Mode CrossSubnet", name1, name2, spec1_v3, kvp1), - Entry("IPv6 IPPool CRD with v1 IPIP field and IPIP Enabled set to false and Mode Never", name1, name2, spec2_v3, kvp2), - Entry("IPv4 IPPool CRD with v1 IPIP field and IPIP Enabled set to true and Mode Always", name1, name2, spec3_v3, kvp3), - Entry("IPv4 IPPool CRD with v1 NATOutgoingV1 field set to false and v3 NATOutgoing set to true", name1, name2, spec5_v3, kvp5), - Entry("IPv4 IPPool CRD with v1 NATOutgoingV1 field set to true (v1 IPIP set to nil) and v3 NATOutgoing set to false", name1, name2, spec6_v3, kvp6), - ) - - Describe("IPPool watch functionality", func() { - It("should handle watch events for backend IPPool with v1 IPIP field", func() { - c, err := clientv3.New(config) - Expect(err).NotTo(HaveOccurred()) - - be, err := backend.NewClient(config) - Expect(err).NotTo(HaveOccurred()) - be.Clean() - - By("Listing IPPools with no resource version and checking for no results") - outList, outError := c.IPPools().List(ctx, options.ListOptions{}) - Expect(outError).NotTo(HaveOccurred()) - Expect(outList.Items).To(HaveLen(0)) - rev0 := outList.ResourceVersion - - By("Creating IPPool with v1 IPIP field directly in the backend") - kvp1Name1 := *kvp1 - kvp1Name1.Key = model.ResourceKey{ - Name: name1, - Kind: apiv3.KindIPPool, - } - - // Also need to change the Value Metadata Name because that will be the CRD key and needs to be unique. - kvp1Name1.Value.(*apiv3.IPPool).Name = name1 - - outKVP1, err := be.Create(ctx, &kvp1Name1) - Expect(err).NotTo(HaveOccurred()) - outRes1 := outKVP1.Value.(*apiv3.IPPool) - - By("Creating another IPPool with v1 IPIP field directly in the backend") - kvp2Name2 := *kvp2 - kvp2Name2.Key = model.ResourceKey{ - Name: name2, - Kind: apiv3.KindIPPool, - } - - // Also need to change the Value Metadata Name because that will be the CRD key and needs to be unique. - kvp2Name2.Value.(*apiv3.IPPool).Name = name2 - - outKVP2, err := be.Create(ctx, &kvp2Name2) - Expect(err).NotTo(HaveOccurred()) - outRes2 := outKVP2.Value.(*apiv3.IPPool) - - By("Deleting IPPool with v1 IPIP field directly in the backend") - outKVP3, err := be.Delete(ctx, kvp1.Key, "") - Expect(err).NotTo(HaveOccurred()) - outRes3 := outKVP3.Value.(*apiv3.IPPool) - - By("Starting a watcher from rev0 - this should get all events") - w, err := c.IPPools().Watch(ctx, options.ListOptions{ResourceVersion: rev0}) - Expect(err).NotTo(HaveOccurred()) - testWatcher2 := testutils.NewTestResourceWatch(config.Spec.DatastoreType, w) - defer testWatcher2.Stop() - - By("Modifying res2") - outRes4, err := c.IPPools().Update( - ctx, - &apiv3.IPPool{ - ObjectMeta: metav1.ObjectMeta{Name: name2, ResourceVersion: outRes2.ResourceVersion, CreationTimestamp: metav1.Now(), UID: outKVP2.Value.(*apiv3.IPPool).ObjectMeta.UID}, - Spec: spec2_v3, - }, - options.SetOptions{}, - ) - Expect(err).NotTo(HaveOccurred()) - testWatcher2.ExpectEvents(apiv3.KindIPPool, []watch.Event{ - { - Type: watch.Added, - Object: outRes1, - }, - { - Type: watch.Added, - Object: outRes2, - }, - { - Type: watch.Deleted, - Previous: outRes3, - }, - { - Type: watch.Modified, - Previous: outRes2, - Object: outRes4, - }, - }) - testWatcher2.Stop() - }) - }) -}) diff --git a/libcalico-go/lib/clientv3/ippool_test.go b/libcalico-go/lib/clientv3/ippool_test.go new file mode 100644 index 00000000000..f8ac6224b0a --- /dev/null +++ b/libcalico-go/lib/clientv3/ippool_test.go @@ -0,0 +1,243 @@ +// Copyright (c) 2017-2025 Tigera, Inc. All rights reserved. + +// 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 clientv3 + +import ( + "context" + "testing" + + apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" +) + +func TestChangeCidrOK(t *testing.T) { + testCases := []struct { + name string + oldCidr string + newCidr string + expected bool + }{ + { + name: "Canonical to non-canonical should be rejected", + oldCidr: "2001:cafe:42::/56", + newCidr: "2001:cafe:42:0000:00::/56", + expected: false, + }, + { + name: "Non-canonical to non-canonical should be rejected", + oldCidr: "2001:cafe:42:0:0:0:0:0/56", + newCidr: "2001:cafe:42:0000:00::/56", + expected: false, + }, + { + name: "Non-canonical to canonical should be accepted", + oldCidr: "2001:cafe:42::00/56", + newCidr: "2001:cafe:42::/56", + expected: true, + }, + { + name: "Canonical to canonical but different text should be rejected", + oldCidr: "2001:db8::/32", + newCidr: "2001:0db8::/32", // new must match canonical string exactly + expected: false, + }, + { + name: "Same network but new CIDR not canonical should be rejected", + oldCidr: "2001:cafe:42::/56", + newCidr: "2001:cafe:42:0:0::/56", + expected: false, + }, + { + name: "Old invalid but new canonical valid should be rejected", + oldCidr: "2001:cafe:::bad/56", + newCidr: "2001:cafe::/56", + expected: false, // reject (old fails parse) + }, + { + name: "Valid canonical to valid canonical but different networks rejected", + oldCidr: "fd00::/48", + newCidr: "fd00:1::/48", + expected: false, + }, + { + name: "IPv6 uppercase vs lowercase canonical mismatch rejected", + oldCidr: "2001:CAFE:42::/56", + newCidr: "2001:cafe:42::/56", + expected: true, + }, + { + name: "IPv6 non-canonical masked version to canonical should be accepted", + oldCidr: "2001:0db8:0000:0000::/64", + newCidr: "2001:db8::/64", + expected: true, + }, + { + name: "Reject when old is canonical but new adds redundant fields", + oldCidr: "2001:cafe:42::/56", + newCidr: "2001:cafe:42:0:0:0:0:0/56", + expected: false, + }, + { + name: "Accept IPv6 equivalent non-canonical to canonical", + oldCidr: "2001:cafe:42:0000::/56", + newCidr: "2001:cafe:42::/56", + expected: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := cidrChangeOK(tc.oldCidr, tc.newCidr) + if result != tc.expected { + t.Errorf("cidrChangeOK(%q, %q) = %v, want %v", + tc.oldCidr, tc.newCidr, result, tc.expected) + } + }) + } +} + +// TestValidateAndSetDefaults_CIDRComparison tests the validateAndSetDefaults function +// with different CIDR representations to ensure semantic comparison works correctly. +func TestValidateAndSetDefaults_CIDRComparison(t *testing.T) { + ctx := context.Background() + + // Create a mock ipPools instance - we don't need actual backend for these tests + // since we're testing validation logic that doesn't require backend access + r := ipPools{} + + testCases := []struct { + name string + oldCIDR string + newCIDR string + blockSize int + shouldError bool + description string + }{ + { + name: "IPv6 same network different representation - should succeed", + oldCIDR: "2001:cafe:42::00/56", // Non-normalized (as it might be stored) + newCIDR: "2001:cafe:42::/56", // Normalized form + blockSize: 122, + shouldError: false, + description: "Update with same IPv6 network but different textual representation should be allowed", + }, + { + name: "IPv6 expanded vs compressed - should succeed", + oldCIDR: "2001:0db8:85a3:0000:0000:0000:0000:0000/64", // Expanded (as it might be stored) + newCIDR: "2001:db8:85a3::/64", // Compressed form + blockSize: 122, + shouldError: false, + description: "IPv6 expanded and compressed forms should be treated as identical", + }, + { + name: "IPv6 different networks - should fail", + oldCIDR: "2001:cafe:42::/56", + newCIDR: "2001:cafe:43::/56", + blockSize: 122, + shouldError: true, + description: "Different IPv6 networks should be rejected", + }, + { + name: "IPv6 different prefix length - should fail", + oldCIDR: "2001:cafe:42::/56", + newCIDR: "2001:cafe:42::/64", + blockSize: 122, + shouldError: true, + description: "Different prefix lengths should be rejected", + }, + { + name: "IPv4 same CIDR - should succeed", + oldCIDR: "192.168.1.0/24", + newCIDR: "192.168.1.0/24", + blockSize: 26, + shouldError: false, + description: "Identical IPv4 CIDRs should be allowed", + }, + { + name: "IPv4 different networks - should fail", + oldCIDR: "192.168.1.0/24", + newCIDR: "192.168.2.0/24", + blockSize: 26, + shouldError: true, + description: "Different IPv4 networks should be rejected", + }, + { + name: "IPv4 different prefix - should fail", + oldCIDR: "192.168.1.0/24", + newCIDR: "192.168.1.0/25", + blockSize: 26, + shouldError: true, + description: "Different IPv4 prefix lengths should be rejected", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Create old IPPool with the old CIDR + oldPool := &apiv3.IPPool{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pool", + }, + Spec: apiv3.IPPoolSpec{ + CIDR: tc.oldCIDR, + BlockSize: tc.blockSize, + }, + } + + // Create new IPPool with the new CIDR + newPool := &apiv3.IPPool{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pool", + }, + Spec: apiv3.IPPoolSpec{ + CIDR: tc.newCIDR, + BlockSize: tc.blockSize, + }, + } + + // Call validateAndSetDefaults with skipCIDROverlap=true to skip overlap checks + // which would require a backend + err := r.validateAndSetDefaults(ctx, newPool, oldPool, true) + + if tc.shouldError { + if err == nil { + t.Errorf("%s: expected error but got none", tc.description) + } else { + // Verify it's the correct error type + if validationErr, ok := err.(cerrors.ErrorValidation); ok { + found := false + for _, field := range validationErr.ErroredFields { + if field.Name == "IPPool.Spec.CIDR" { + found = true + break + } + } + if !found { + t.Errorf("%s: got error but not the expected CIDR modification error: %v", tc.description, err) + } + } else { + t.Errorf("%s: expected ErrorValidation but got: %T", tc.description, err) + } + } + } else { + if err != nil { + t.Errorf("%s: expected no error but got: %v", tc.description, err) + } + } + }) + } +} diff --git a/libcalico-go/lib/clientv3/kubecontrollersconfig.go b/libcalico-go/lib/clientv3/kubecontrollersconfig.go index c46f4621692..e33e12bc0ec 100644 --- a/libcalico-go/lib/clientv3/kubecontrollersconfig.go +++ b/libcalico-go/lib/clientv3/kubecontrollersconfig.go @@ -33,6 +33,7 @@ import ( type KubeControllersConfigurationInterface interface { Create(ctx context.Context, res *apiv3.KubeControllersConfiguration, opts options.SetOptions) (*apiv3.KubeControllersConfiguration, error) Update(ctx context.Context, res *apiv3.KubeControllersConfiguration, opts options.SetOptions) (*apiv3.KubeControllersConfiguration, error) + UpdateStatus(ctx context.Context, res *apiv3.KubeControllersConfiguration, opts options.SetOptions) (*apiv3.KubeControllersConfiguration, error) Delete(ctx context.Context, name string, opts options.DeleteOptions) (*apiv3.KubeControllersConfiguration, error) Get(ctx context.Context, name string, opts options.GetOptions) (*apiv3.KubeControllersConfiguration, error) List(ctx context.Context, opts options.ListOptions) (*apiv3.KubeControllersConfigurationList, error) @@ -98,12 +99,12 @@ func (r kubeControllersConfiguration) validateAndFillDefaults(res *apiv3.KubeCon } templateName[template.GenerateName] = true - // At least InterfaceCIDRs or InterfaceSelector has to be set - if len(template.InterfaceCIDRs) == 0 && template.InterfaceSelector == "" { + // At least InterfaceCIDRs or InterfacePattern has to be set + if len(template.InterfaceCIDRs) == 0 && template.InterfacePattern == "" { return cerrors.ErrorValidation{ ErroredFields: []cerrors.ErroredField{{ Name: "KubeControllersConfiguration.Node.HostEndpoint.Templates." + template.GenerateName, - Reason: "InterfaceCIDRs or InterfaceSelector must be specified", + Reason: "InterfaceCIDRs or InterfacePattern must be specified", }}, } } @@ -160,6 +161,17 @@ func (r kubeControllersConfiguration) Update(ctx context.Context, res *apiv3.Kub return nil, err } +// UpdateStatus takes the representation of a KubeControllersConfiguration and updates its status. +// Returns the stored representation of the KubeControllersConfiguration, and an error +// if there is any. +func (r kubeControllersConfiguration) UpdateStatus(ctx context.Context, res *apiv3.KubeControllersConfiguration, opts options.SetOptions) (*apiv3.KubeControllersConfiguration, error) { + out, err := r.client.resources.UpdateStatus(ctx, opts, apiv3.KindKubeControllersConfiguration, res) + if out != nil { + return out.(*apiv3.KubeControllersConfiguration), err + } + return nil, err +} + // Delete takes name of the KubeControllersConfiguration and deletes it. Returns an // error if one occurs. func (r kubeControllersConfiguration) Delete(ctx context.Context, name string, opts options.DeleteOptions) (*apiv3.KubeControllersConfiguration, error) { diff --git a/libcalico-go/lib/clientv3/kubecontrollersconfig_e2e_test.go b/libcalico-go/lib/clientv3/kubecontrollersconfig_e2e_test.go index 7c11d5a0a35..7a9cbd97126 100644 --- a/libcalico-go/lib/clientv3/kubecontrollersconfig_e2e_test.go +++ b/libcalico-go/lib/clientv3/kubecontrollersconfig_e2e_test.go @@ -18,8 +18,7 @@ import ( "context" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -70,7 +69,7 @@ var _ = testutils.E2eDatastoreDescribe("KubeControllersConfiguration tests", tes }, } status1 := apiv3.KubeControllersConfigurationStatus{ - RunningConfig: apiv3.KubeControllersConfigurationSpec{ + RunningConfig: &apiv3.KubeControllersConfigurationSpec{ LogSeverityScreen: "Debug", HealthChecks: apiv3.Enabled, Controllers: apiv3.ControllersConfig{ @@ -91,7 +90,7 @@ var _ = testutils.E2eDatastoreDescribe("KubeControllersConfiguration tests", tes }, } status2 := apiv3.KubeControllersConfigurationStatus{ - RunningConfig: apiv3.KubeControllersConfigurationSpec{ + RunningConfig: &apiv3.KubeControllersConfigurationSpec{ HealthChecks: apiv3.Disabled, Controllers: apiv3.ControllersConfig{ Node: &apiv3.NodeControllerConfig{ @@ -263,7 +262,7 @@ var _ = testutils.E2eDatastoreDescribe("KubeControllersConfiguration tests", tes By("Setting status1 on resource") res.Status = status1 - res, outError = c.KubeControllersConfiguration().Update(ctx, res, options.SetOptions{}) + res, outError = c.KubeControllersConfiguration().UpdateStatus(ctx, res, options.SetOptions{}) Expect(outError).ToNot(HaveOccurred()) Expect(res).To(MatchResourceWithStatus(apiv3.KindKubeControllersConfiguration, testutils.ExpectNoNamespace, name, spec2, status1)) @@ -274,7 +273,7 @@ var _ = testutils.E2eDatastoreDescribe("KubeControllersConfiguration tests", tes By("Setting status2 on resource") res.Status = status2 - res, outError = c.KubeControllersConfiguration().Update(ctx, res, options.SetOptions{}) + res, outError = c.KubeControllersConfiguration().UpdateStatus(ctx, res, options.SetOptions{}) Expect(outError).ToNot(HaveOccurred()) Expect(res).To(MatchResourceWithStatus(apiv3.KindKubeControllersConfiguration, testutils.ExpectNoNamespace, name, spec2, status2)) rv1_3 := res.ResourceVersion @@ -462,7 +461,7 @@ var _ = testutils.E2eDatastoreDescribe("KubeControllersConfiguration tests", tes }, } - templateEmptyCIDRInterfaceSelector := apiv3.AutoHostEndpointConfig{ + templateEmptyCIDRInterfacePattern := apiv3.AutoHostEndpointConfig{ AutoCreate: "Enabled", CreateDefaultHostEndpoint: "Enabled", Templates: []apiv3.Template{ @@ -527,19 +526,19 @@ var _ = testutils.E2eDatastoreDescribe("KubeControllersConfiguration tests", tes CreateDefaultHostEndpoint: "Enabled", Templates: []apiv3.Template{ { - GenerateName: "template", - InterfaceSelector: "eth.*", - Labels: map[string]string{"label": "value"}, - NodeSelector: "all()", + GenerateName: "template", + InterfacePattern: "eth.*", + Labels: map[string]string{"label": "value"}, + NodeSelector: "all()", }, }, } - By("Creating kcc with empty CIDR and interfaceSelector") - kcc.Spec.Controllers.Node.HostEndpoint = &templateEmptyCIDRInterfaceSelector + By("Creating kcc with empty CIDR and interfacePattern") + kcc.Spec.Controllers.Node.HostEndpoint = &templateEmptyCIDRInterfacePattern _, outError := c.KubeControllersConfiguration().Create(ctx, kcc, options.SetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("InterfaceCIDRs or InterfaceSelector must be specified")) + Expect(outError.Error()).To(ContainSubstring("InterfaceCIDRs or InterfacePattern must be specified")) By("Creating kcc with empty template name") kcc.Spec.Controllers.Node.HostEndpoint = &templateEmptyName diff --git a/libcalico-go/lib/clientv3/livemigration.go b/libcalico-go/lib/clientv3/livemigration.go new file mode 100644 index 00000000000..a104ac35b55 --- /dev/null +++ b/libcalico-go/lib/clientv3/livemigration.go @@ -0,0 +1,119 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// 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 clientv3 + +import ( + "context" + + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" + cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" + "github.com/projectcalico/calico/libcalico-go/lib/options" + validator "github.com/projectcalico/calico/libcalico-go/lib/validator/v3" + "github.com/projectcalico/calico/libcalico-go/lib/watch" +) + +// LiveMigrationInterface has methods to work with LiveMigration resources. +type LiveMigrationInterface interface { + Create(ctx context.Context, res *internalapi.LiveMigration, opts options.SetOptions) (*internalapi.LiveMigration, error) + Update(ctx context.Context, res *internalapi.LiveMigration, opts options.SetOptions) (*internalapi.LiveMigration, error) + Delete(ctx context.Context, namespace, name string, opts options.DeleteOptions) (*internalapi.LiveMigration, error) + Get(ctx context.Context, namespace, name string, opts options.GetOptions) (*internalapi.LiveMigration, error) + List(ctx context.Context, opts options.ListOptions) (*internalapi.LiveMigrationList, error) + Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) +} + +// liveMigrations implements LiveMigrationInterface +type liveMigrations struct { + client client +} + +// Create takes the representation of a LiveMigration and creates it. Returns the stored +// representation of the LiveMigration, and an error, if there is any. +func (r liveMigrations) Create(ctx context.Context, res *internalapi.LiveMigration, opts options.SetOptions) (*internalapi.LiveMigration, error) { + if err := validator.Validate(res); err != nil { + return nil, err + } + + out, err := r.client.resources.Create(ctx, opts, internalapi.KindLiveMigration, res) + if out != nil { + return out.(*internalapi.LiveMigration), err + } + return nil, err +} + +// Update takes the representation of a LiveMigration and updates it. Returns the stored +// representation of the LiveMigration, and an error, if there is any. +func (r liveMigrations) Update(ctx context.Context, res *internalapi.LiveMigration, opts options.SetOptions) (*internalapi.LiveMigration, error) { + if err := validator.Validate(res); err != nil { + return nil, err + } + + out, err := r.client.resources.Update(ctx, opts, internalapi.KindLiveMigration, res) + if out != nil { + return out.(*internalapi.LiveMigration), err + } + return nil, err +} + +// Delete takes name of the LiveMigration and deletes it. Returns an error if one occurs. +func (r liveMigrations) Delete(ctx context.Context, namespace, name string, opts options.DeleteOptions) (*internalapi.LiveMigration, error) { + out, err := r.client.resources.Delete(ctx, opts, internalapi.KindLiveMigration, namespace, name) + if out != nil { + return out.(*internalapi.LiveMigration), err + } + return nil, err +} + +// Get takes name of the LiveMigration, and returns the corresponding LiveMigration object, +// and an error if there is any. +func (r liveMigrations) Get(ctx context.Context, namespace, name string, opts options.GetOptions) (*internalapi.LiveMigration, error) { + out, err := r.client.resources.Get(ctx, opts, internalapi.KindLiveMigration, namespace, name) + if out != nil { + return out.(*internalapi.LiveMigration), err + } + return nil, err +} + +// List returns the list of LiveMigration objects that match the supplied options. +func (r liveMigrations) List(ctx context.Context, opts options.ListOptions) (*internalapi.LiveMigrationList, error) { + res := &internalapi.LiveMigrationList{} + if err := r.client.resources.List(ctx, opts, internalapi.KindLiveMigration, internalapi.KindLiveMigrationList, res); err != nil { + return nil, err + } + return res, nil +} + +// Watch returns a watch.Interface that watches the LiveMigrations that match the +// supplied options. +func (r liveMigrations) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) { + // In Kubernetes, where a LiveMigration resource doesn't have its own storage but is instead + // backed by the KubeVirt VirtualMachineInstanceMigration (VMIM) resource with the same name + // and namespace, we have implemented the conversion such that the emitted LiveMigration KV + // pair has `Value == nil` when the VirtualMachineInstanceMigration is in a state that Felix + // can treat equivalently to the LiveMigration not existing. Typha and Felix handle this + // well, i.e. as though the LiveMigration has been deleted. (And correspondingly, if the + // VMIM then transitions to a state of interest, as though the LiveMigration has been + // created again.) + // + // However the v3 API Watch machinery does not currently handle `Value == nil`. + // Specifically, `convertEvent` calls `w.client.kvPairToResource(backendEvent.New)`, and + // `kvPairToResource` will panic in that case. Hence we document and firewall against this + // here. + return nil, cerrors.ErrorOperationNotSupported{ + Operation: "Watch", + Identifier: internalapi.KindLiveMigration, + Reason: "Watch is not supported for LiveMigration resources", + } +} diff --git a/libcalico-go/lib/clientv3/livemigration_e2e_test.go b/libcalico-go/lib/clientv3/livemigration_e2e_test.go new file mode 100644 index 00000000000..87f5b6658b5 --- /dev/null +++ b/libcalico-go/lib/clientv3/livemigration_e2e_test.go @@ -0,0 +1,265 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// 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 clientv3_test + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" + "github.com/projectcalico/calico/libcalico-go/lib/backend" + "github.com/projectcalico/calico/libcalico-go/lib/clientv3" + "github.com/projectcalico/calico/libcalico-go/lib/options" + "github.com/projectcalico/calico/libcalico-go/lib/testutils" +) + +var _ = testutils.E2eDatastoreDescribe("LiveMigration tests", testutils.DatastoreEtcdV3, func(config apiconfig.CalicoAPIConfig) { + ctx := context.Background() + namespace1 := "namespace-1" + namespace2 := "namespace-2" + name1 := "livemigration-1" + name2 := "livemigration-2" + destSelector1 := "kubevirt.io/vmi-name == 'vmi-1'" + destSelector2 := "kubevirt.io/vmi-name == 'vmi-2'" + spec1 := internalapi.LiveMigrationSpec{ + Source: &types.NamespacedName{ + Namespace: "ns-src-1", + Name: "wep-src-1", + }, + Destination: &internalapi.WorkloadEndpointIdentifier{ + Selector: &destSelector1, + }, + } + spec2 := internalapi.LiveMigrationSpec{ + Source: &types.NamespacedName{ + Namespace: "ns-src-2", + Name: "wep-src-2", + }, + Destination: &internalapi.WorkloadEndpointIdentifier{ + Selector: &destSelector2, + }, + } + + DescribeTable("LiveMigration e2e CRUD tests", + func(namespace1, namespace2, name1, name2 string, spec1, spec2 internalapi.LiveMigrationSpec) { + c, err := clientv3.New(config) + Expect(err).NotTo(HaveOccurred()) + + be, err := backend.NewClient(config) + Expect(err).NotTo(HaveOccurred()) + be.Clean() + + By("Updating the LiveMigration before it is created") + _, outError := c.LiveMigrations().Update(ctx, &internalapi.LiveMigration{ + ObjectMeta: metav1.ObjectMeta{Namespace: namespace1, Name: name1, ResourceVersion: "1234", CreationTimestamp: metav1.Now(), UID: uid}, + Spec: spec1, + }, options.SetOptions{}) + Expect(outError).To(HaveOccurred()) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: LiveMigration(" + namespace1 + "/" + name1 + ") with error:")) + + By("Attempting to get a LiveMigration before it is created") + _, outError = c.LiveMigrations().Get(ctx, namespace1, name1, options.GetOptions{}) + Expect(outError).To(HaveOccurred()) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: LiveMigration(" + namespace1 + "/" + name1 + ") with error:")) + + By("Attempting to create a new LiveMigration with a non-empty ResourceVersion") + _, outError = c.LiveMigrations().Create(ctx, &internalapi.LiveMigration{ + ObjectMeta: metav1.ObjectMeta{Namespace: namespace1, Name: name1, ResourceVersion: "12345"}, + Spec: spec1, + }, options.SetOptions{}) + Expect(outError).To(HaveOccurred()) + Expect(outError.Error()).To(Equal("error with field Metadata.ResourceVersion = '12345' (field must not be set for a Create request)")) + + By("Creating a new LiveMigration with namespace1/name1/spec1") + res1, outError := c.LiveMigrations().Create(ctx, &internalapi.LiveMigration{ + ObjectMeta: metav1.ObjectMeta{Namespace: namespace1, Name: name1}, + Spec: spec1, + }, options.SetOptions{}) + Expect(outError).NotTo(HaveOccurred()) + Expect(res1).To(MatchResource(internalapi.KindLiveMigration, namespace1, name1, spec1)) + + // Track the version of the original data for name1. + rv1_1 := res1.ResourceVersion + + By("Attempting to create the same LiveMigration with name1 but with spec2") + _, outError = c.LiveMigrations().Create(ctx, &internalapi.LiveMigration{ + ObjectMeta: metav1.ObjectMeta{Namespace: namespace1, Name: name1}, + Spec: spec2, + }, options.SetOptions{}) + Expect(outError).To(HaveOccurred()) + Expect(outError.Error()).To(ContainSubstring("resource already exists: LiveMigration(" + namespace1 + "/" + name1 + ") with error:")) + + By("Getting LiveMigration (name1) and comparing the output against spec1") + res, outError := c.LiveMigrations().Get(ctx, namespace1, name1, options.GetOptions{}) + Expect(outError).NotTo(HaveOccurred()) + Expect(res).To(MatchResource(internalapi.KindLiveMigration, namespace1, name1, spec1)) + Expect(res.ResourceVersion).To(Equal(res1.ResourceVersion)) + + By("Getting LiveMigration (name2) before it is created") + _, outError = c.LiveMigrations().Get(ctx, namespace2, name2, options.GetOptions{}) + Expect(outError).To(HaveOccurred()) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: LiveMigration(" + namespace2 + "/" + name2 + ") with error:")) + + By("Listing all the LiveMigrations in namespace1, expecting a single result with name1/spec1") + outList, outError := c.LiveMigrations().List(ctx, options.ListOptions{Namespace: namespace1}) + Expect(outError).NotTo(HaveOccurred()) + Expect(outList.Items).To(ConsistOf( + testutils.Resource(internalapi.KindLiveMigration, namespace1, name1, spec1), + )) + + By("Creating a new LiveMigration with namespace2/name2/spec2") + res2, outError := c.LiveMigrations().Create(ctx, &internalapi.LiveMigration{ + ObjectMeta: metav1.ObjectMeta{Name: name2, Namespace: namespace2}, + Spec: spec2, + }, options.SetOptions{}) + Expect(outError).NotTo(HaveOccurred()) + Expect(res2).To(MatchResource(internalapi.KindLiveMigration, namespace2, name2, spec2)) + + By("Getting LiveMigration (name2) and comparing the output against spec2") + res, outError = c.LiveMigrations().Get(ctx, namespace2, name2, options.GetOptions{}) + Expect(outError).NotTo(HaveOccurred()) + Expect(res).To(MatchResource(internalapi.KindLiveMigration, namespace2, name2, spec2)) + Expect(res.ResourceVersion).To(Equal(res2.ResourceVersion)) + + By("Listing all the LiveMigrations using an empty namespace (all-namespaces), expecting two results") + outList, outError = c.LiveMigrations().List(ctx, options.ListOptions{}) + Expect(outError).NotTo(HaveOccurred()) + Expect(outList.Items).To(ConsistOf( + testutils.Resource(internalapi.KindLiveMigration, namespace1, name1, spec1), + testutils.Resource(internalapi.KindLiveMigration, namespace2, name2, spec2), + )) + + By("Listing all the LiveMigrations in namespace2, expecting a single result with name2/spec2") + outList, outError = c.LiveMigrations().List(ctx, options.ListOptions{Namespace: namespace2}) + Expect(outError).NotTo(HaveOccurred()) + Expect(outList.Items).To(ConsistOf( + testutils.Resource(internalapi.KindLiveMigration, namespace2, name2, spec2), + )) + + By("Updating LiveMigration name1 with spec2") + res1.Spec = spec2 + res1Out, outError := c.LiveMigrations().Update(ctx, res1, options.SetOptions{}) + Expect(outError).NotTo(HaveOccurred()) + res1 = res1Out + Expect(res1).To(MatchResource(internalapi.KindLiveMigration, namespace1, name1, spec2)) + + // Track the version of the updated name1 data. + rv1_2 := res1.ResourceVersion + + By("Updating LiveMigration name1 using the previous resource version") + res1.Spec = spec1 + res1.ResourceVersion = rv1_1 + _, outError = c.LiveMigrations().Update(ctx, res1, options.SetOptions{}) + Expect(outError).To(HaveOccurred()) + Expect(outError.Error()).To(Equal("update conflict: LiveMigration(" + namespace1 + "/" + name1 + ")")) + + By("Getting LiveMigration (name1) with the original resource version and comparing the output against spec1") + res, outError = c.LiveMigrations().Get(ctx, namespace1, name1, options.GetOptions{ResourceVersion: rv1_1}) + Expect(outError).NotTo(HaveOccurred()) + Expect(res).To(MatchResource(internalapi.KindLiveMigration, namespace1, name1, spec1)) + Expect(res.ResourceVersion).To(Equal(rv1_1)) + + By("Getting LiveMigration (name1) with the updated resource version and comparing the output against spec2") + res, outError = c.LiveMigrations().Get(ctx, namespace1, name1, options.GetOptions{ResourceVersion: rv1_2}) + Expect(outError).NotTo(HaveOccurred()) + Expect(res).To(MatchResource(internalapi.KindLiveMigration, namespace1, name1, spec2)) + Expect(res.ResourceVersion).To(Equal(rv1_2)) + + By("Deleting LiveMigration (name1) with the old resource version") + _, outError = c.LiveMigrations().Delete(ctx, namespace1, name1, options.DeleteOptions{ResourceVersion: rv1_1}) + Expect(outError).To(HaveOccurred()) + Expect(outError.Error()).To(Equal("update conflict: LiveMigration(" + namespace1 + "/" + name1 + ")")) + + By("Deleting LiveMigration (name1) with the new resource version") + dres, outError := c.LiveMigrations().Delete(ctx, namespace1, name1, options.DeleteOptions{ResourceVersion: rv1_2}) + Expect(outError).NotTo(HaveOccurred()) + Expect(dres).To(MatchResource(internalapi.KindLiveMigration, namespace1, name1, spec2)) + + By("Deleting LiveMigration (name2)") + dres, outError = c.LiveMigrations().Delete(ctx, namespace2, name2, options.DeleteOptions{}) + Expect(outError).NotTo(HaveOccurred()) + Expect(dres).To(MatchResource(internalapi.KindLiveMigration, namespace2, name2, spec2)) + + By("Attempting to delete LiveMigration (name2) again") + _, outError = c.LiveMigrations().Delete(ctx, namespace2, name2, options.DeleteOptions{}) + Expect(outError).To(HaveOccurred()) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: LiveMigration(" + namespace2 + "/" + name2 + ") with error:")) + + By("Listing all LiveMigrations and expecting no items") + outList, outError = c.LiveMigrations().List(ctx, options.ListOptions{}) + Expect(outError).NotTo(HaveOccurred()) + Expect(outList.Items).To(HaveLen(0)) + + By("Getting LiveMigration (name2) and expecting an error") + _, outError = c.LiveMigrations().Get(ctx, namespace2, name2, options.GetOptions{}) + Expect(outError).To(HaveOccurred()) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: LiveMigration(" + namespace2 + "/" + name2 + ") with error:")) + }, + + Entry("Two fully populated LiveMigrationSpecs", + namespace1, namespace2, + name1, name2, + spec1, spec2, + ), + ) + + Describe("LiveMigration watch functionality", func() { + It("should return an error for an attempted Watch", func() { + c, err := clientv3.New(config) + Expect(err).NotTo(HaveOccurred()) + + be, err := backend.NewClient(config) + Expect(err).NotTo(HaveOccurred()) + be.Clean() + + By("Listing LiveMigrations with the latest resource version and checking for no results") + outList, outError := c.LiveMigrations().List(ctx, options.ListOptions{}) + Expect(outError).NotTo(HaveOccurred()) + Expect(outList.Items).To(HaveLen(0)) + + By("Configuring a LiveMigration namespace1/name1/spec1") + outRes1, err := c.LiveMigrations().Create( + ctx, + &internalapi.LiveMigration{ + ObjectMeta: metav1.ObjectMeta{Namespace: namespace1, Name: name1}, + Spec: spec1, + }, + options.SetOptions{}, + ) + rev1 := outRes1.ResourceVersion + Expect(err).NotTo(HaveOccurred()) + + By("Configuring a LiveMigration namespace2/name2/spec2") + _, err = c.LiveMigrations().Create( + ctx, + &internalapi.LiveMigration{ + ObjectMeta: metav1.ObjectMeta{Namespace: namespace2, Name: name2}, + Spec: spec2, + }, + options.SetOptions{}, + ) + Expect(err).NotTo(HaveOccurred()) + + By("Starting a watcher from revision rev1 - should return an error") + _, err = c.LiveMigrations().Watch(ctx, options.ListOptions{ResourceVersion: rev1}) + Expect(err).To(HaveOccurred()) + }) + }) +}) diff --git a/libcalico-go/lib/clientv3/networkpolicy.go b/libcalico-go/lib/clientv3/networkpolicy.go index 054a551fdb6..9c475a6ccc8 100644 --- a/libcalico-go/lib/clientv3/networkpolicy.go +++ b/libcalico-go/lib/clientv3/networkpolicy.go @@ -16,9 +16,9 @@ package clientv3 import ( "context" - "fmt" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "github.com/projectcalico/api/pkg/defaults" log "github.com/sirupsen/logrus" "github.com/projectcalico/calico/libcalico-go/lib/names" @@ -58,31 +58,22 @@ func (r networkPolicies) Create(ctx context.Context, res *apiv3.NetworkPolicy, o resCopy := *res res = &resCopy } - defaultPolicyTypesField(res.Spec.Ingress, res.Spec.Egress, &res.Spec.Types) - if err := validator.Validate(res); err != nil { - return nil, err - } - err := names.ValidateTieredPolicyName(res.Name, tier) - if err != nil { + // Run defaulting logic. + if _, err := defaults.Default(res); err != nil { return nil, err } - // Add tier labels to policy for lookup. - if tier != "default" { - res.GetObjectMeta().SetLabels(addTierLabel(res.GetObjectMeta().GetLabels(), tier)) + if err := validator.Validate(res); err != nil { + return nil, err } out, err := r.client.resources.Create(ctx, opts, apiv3.KindNetworkPolicy, res) if out != nil { // Add the tier labels if necessary - out.GetObjectMeta().SetLabels(defaultTierLabelIfMissing(out.GetObjectMeta().GetLabels())) return out.(*apiv3.NetworkPolicy), err } - // Add the tier labels if necessary - res.GetObjectMeta().SetLabels(defaultTierLabelIfMissing(res.GetObjectMeta().GetLabels())) - return nil, err } @@ -95,32 +86,21 @@ func (r networkPolicies) Update(ctx context.Context, res *apiv3.NetworkPolicy, o resCopy := *res res = &resCopy } - defaultPolicyTypesField(res.Spec.Ingress, res.Spec.Egress, &res.Spec.Types) - if err := validator.Validate(res); err != nil { - return nil, err - } - err := names.ValidateTieredPolicyName(res.Name, res.Spec.Tier) - if err != nil { + // Run defaulting logic. + if _, err := defaults.Default(res); err != nil { return nil, err } - // Add tier labels to policy for lookup. - tier := names.TierOrDefault(res.Spec.Tier) - if tier != "default" { - res.GetObjectMeta().SetLabels(addTierLabel(res.GetObjectMeta().GetLabels(), tier)) + if err := validator.Validate(res); err != nil { + return nil, err } out, err := r.client.resources.Update(ctx, opts, apiv3.KindNetworkPolicy, res) if out != nil { // Add the tier labels if necessary - out.GetObjectMeta().SetLabels(defaultTierLabelIfMissing(out.GetObjectMeta().GetLabels())) return out.(*apiv3.NetworkPolicy), err } - - // Add the tier labels if necessary - res.GetObjectMeta().SetLabels(defaultTierLabelIfMissing(res.GetObjectMeta().GetLabels())) - return nil, err } @@ -142,21 +122,7 @@ func (r networkPolicies) Get(ctx context.Context, namespace, name string, opts o if out != nil { // Add the tier labels if necessary out.GetObjectMeta().SetLabels(defaultTierLabelIfMissing(out.GetObjectMeta().GetLabels())) - // Fill in the tier information from the policy name if we find it missing. - // We expect backend policies to have the right name (prefixed with tier name). - res_out := out.(*apiv3.NetworkPolicy) - if res_out.Spec.Tier == "" { - tier, tierErr := names.TierFromPolicyName(res_out.Name) - if tierErr != nil { - log.WithError(tierErr).Infof("Skipping setting tier for name %v", res_out.Name) - return res_out, tierErr - } - res_out.Spec.Tier = tier - } - if res_out.Name != name { - return nil, fmt.Errorf("resource not found NetworkPolicy(%s/%s)", namespace, name) - } - return res_out, err + return out.(*apiv3.NetworkPolicy), err } return nil, err } @@ -164,11 +130,6 @@ func (r networkPolicies) Get(ctx context.Context, namespace, name string, opts o // List returns the list of NetworkPolicy objects that match the supplied options. func (r networkPolicies) List(ctx context.Context, opts options.ListOptions) (*apiv3.NetworkPolicyList, error) { res := &apiv3.NetworkPolicyList{} - // Add the name prefix if name is provided - if opts.Name != "" && !opts.Prefix { - opts.Name = names.TieredPolicyName(opts.Name) - } - if err := r.client.resources.List(ctx, opts, apiv3.KindNetworkPolicy, apiv3.KindNetworkPolicyList, res); err != nil { return nil, err } @@ -176,16 +137,6 @@ func (r networkPolicies) List(ctx context.Context, opts options.ListOptions) (*a // Make sure the tier labels are added for i := range res.Items { res.Items[i].GetObjectMeta().SetLabels(defaultTierLabelIfMissing(res.Items[i].GetObjectMeta().GetLabels())) - // Fill in the tier information from the policy name if we find it missing. - // We expect backend policies to have the right name (prefixed with tier name). - if res.Items[i].Spec.Tier == "" { - tier, tierErr := names.TierFromPolicyName(res.Items[i].Name) - if tierErr != nil { - log.WithError(tierErr).Infof("Skipping setting tier for name %v", res.Items[i].Name) - continue - } - res.Items[i].Spec.Tier = tier - } } return res, nil @@ -194,10 +145,5 @@ func (r networkPolicies) List(ctx context.Context, opts options.ListOptions) (*a // Watch returns a watch.Interface that watches the NetworkPolicies that match the // supplied options. func (r networkPolicies) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) { - // Add the name prefix if name is provided - if opts.Name != "" { - opts.Name = names.TieredPolicyName(opts.Name) - } - return r.client.resources.Watch(ctx, opts, apiv3.KindNetworkPolicy, &policyConverter{}) } diff --git a/libcalico-go/lib/clientv3/networkpolicy_e2e_test.go b/libcalico-go/lib/clientv3/networkpolicy_e2e_test.go index fff1ffd9f12..517fce32135 100644 --- a/libcalico-go/lib/clientv3/networkpolicy_e2e_test.go +++ b/libcalico-go/lib/clientv3/networkpolicy_e2e_test.go @@ -18,12 +18,10 @@ import ( "context" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" "github.com/projectcalico/calico/libcalico-go/lib/backend" @@ -31,19 +29,13 @@ import ( "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/clientv3" - "github.com/projectcalico/calico/libcalico-go/lib/names" "github.com/projectcalico/calico/libcalico-go/lib/options" "github.com/projectcalico/calico/libcalico-go/lib/testutils" "github.com/projectcalico/calico/libcalico-go/lib/watch" ) -func tieredNetworkPolicyName(ns, policyName, t string) string { - policyName, _ = names.BackendTieredPolicyName(policyName, t) - return ns + "/" + policyName -} - -func tieredPolicyName(p, t string) string { - return tieredGNPName(p, t) +func namespacedName(ns, name string) string { + return ns + "/" + name } // UID to use in tests, since our code expects valid UIDs. @@ -87,6 +79,8 @@ var _ = testutils.E2eDatastoreDescribe("NetworkPolicy tests", testutils.Datastor var be bapi.Client BeforeEach(func() { + v3CRD = k8s.UsingV3CRDs(&config.Spec) + var err error c, err = clientv3.New(config) Expect(err).NotTo(HaveOccurred()) @@ -123,7 +117,7 @@ var _ = testutils.E2eDatastoreDescribe("NetworkPolicy tests", testutils.Datastor Spec: spec1, }, options.SetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: NetworkPolicy(" + tieredNetworkPolicyName(namespace1, name1, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: NetworkPolicy(" + namespacedName(namespace1, name1) + ") with error:")) By("Attempting to creating a new NetworkPolicy with name1/spec1 and a non-empty ResourceVersion") _, outError = c.NetworkPolicies().Create(ctx, &apiv3.NetworkPolicy{ @@ -140,7 +134,7 @@ var _ = testutils.E2eDatastoreDescribe("NetworkPolicy tests", testutils.Datastor }, options.SetOptions{}) Expect(outError).NotTo(HaveOccurred()) spec1.Types = types1 - Expect(res1).To(MatchResource(apiv3.KindNetworkPolicy, namespace1, tieredPolicyName(name1, tier), spec1)) + Expect(res1).To(MatchResource(apiv3.KindNetworkPolicy, namespace1, name1, spec1)) // Track the version of the original data for name1. rv1_1 := res1.ResourceVersion @@ -151,24 +145,24 @@ var _ = testutils.E2eDatastoreDescribe("NetworkPolicy tests", testutils.Datastor Spec: spec2, }, options.SetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource already exists: NetworkPolicy(" + tieredNetworkPolicyName(namespace1, name1, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource already exists: NetworkPolicy(" + namespacedName(namespace1, name1) + ") with error:")) By("Getting NetworkPolicy (name1) and comparing the output against spec1") res, outError := c.NetworkPolicies().Get(ctx, namespace1, name1, options.GetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res).To(MatchResource(apiv3.KindNetworkPolicy, namespace1, tieredPolicyName(name1, tier), spec1)) + Expect(res).To(MatchResource(apiv3.KindNetworkPolicy, namespace1, name1, spec1)) Expect(res.ResourceVersion).To(Equal(res1.ResourceVersion)) By("Getting NetworkPolicy (name2) before it is created") _, outError = c.NetworkPolicies().Get(ctx, namespace2, name2, options.GetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: NetworkPolicy(" + tieredNetworkPolicyName(namespace2, name2, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: NetworkPolicy(" + namespacedName(namespace2, name2) + ") with error:")) By("Listing all the NetworkPolicies in namespace1, expecting a single result with name1/spec1") outList, outError := c.NetworkPolicies().List(ctx, options.ListOptions{Namespace: namespace1}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(apiv3.KindNetworkPolicy, namespace1, tieredPolicyName(name1, tier), spec1), + testutils.Resource(apiv3.KindNetworkPolicy, namespace1, name1, spec1), )) By("Creating a new NetworkPolicy with name2/spec2") @@ -178,34 +172,34 @@ var _ = testutils.E2eDatastoreDescribe("NetworkPolicy tests", testutils.Datastor }, options.SetOptions{}) Expect(outError).NotTo(HaveOccurred()) spec2.Types = types2 - Expect(res2).To(MatchResource(apiv3.KindNetworkPolicy, namespace2, tieredPolicyName(name2, tier), spec2)) + Expect(res2).To(MatchResource(apiv3.KindNetworkPolicy, namespace2, name2, spec2)) By("Getting NetworkPolicy (name2) and comparing the output against spec2") res, outError = c.NetworkPolicies().Get(ctx, namespace2, name2, options.GetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res).To(MatchResource(apiv3.KindNetworkPolicy, namespace2, tieredPolicyName(name2, tier), spec2)) + Expect(res).To(MatchResource(apiv3.KindNetworkPolicy, namespace2, name2, spec2)) Expect(res.ResourceVersion).To(Equal(res2.ResourceVersion)) By("Listing all the NetworkPolicies using an empty namespace (all-namespaces), expecting a two results with name1/spec1 and name2/spec2") outList, outError = c.NetworkPolicies().List(ctx, options.ListOptions{}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(apiv3.KindNetworkPolicy, namespace1, tieredPolicyName(name1, tier), spec1), - testutils.Resource(apiv3.KindNetworkPolicy, namespace2, tieredPolicyName(name2, tier), spec2), + testutils.Resource(apiv3.KindNetworkPolicy, namespace1, name1, spec1), + testutils.Resource(apiv3.KindNetworkPolicy, namespace2, name2, spec2), )) By("Listing all the NetworkPolicies in namespace2, expecting a one results with name2/spec2") outList, outError = c.NetworkPolicies().List(ctx, options.ListOptions{Namespace: namespace2}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(apiv3.KindNetworkPolicy, namespace2, tieredPolicyName(name2, tier), spec2), + testutils.Resource(apiv3.KindNetworkPolicy, namespace2, name2, spec2), )) By("Updating NetworkPolicy name1 with spec2") res1.Spec = spec2 res1, outError = c.NetworkPolicies().Update(ctx, res1, options.SetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res1).To(MatchResource(apiv3.KindNetworkPolicy, namespace1, tieredPolicyName(name1, tier), spec2)) + Expect(res1).To(MatchResource(apiv3.KindNetworkPolicy, namespace1, name1, spec2)) By("Attempting to update the NetworkPolicy without a Creation Timestamp") res, outError = c.NetworkPolicies().Update(ctx, &apiv3.NetworkPolicy{ @@ -240,20 +234,20 @@ var _ = testutils.E2eDatastoreDescribe("NetworkPolicy tests", testutils.Datastor res1.ResourceVersion = rv1_1 _, outError = c.NetworkPolicies().Update(ctx, res1, options.SetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(Equal("update conflict: NetworkPolicy(" + tieredNetworkPolicyName(namespace1, name1, tier) + ")")) + Expect(outError.Error()).To(Equal("update conflict: NetworkPolicy(" + namespacedName(namespace1, name1) + ")")) if config.Spec.DatastoreType != apiconfig.Kubernetes { By("Getting NetworkPolicy (name1) with the original resource version and comparing the output against spec1") res, outError = c.NetworkPolicies().Get(ctx, namespace1, name1, options.GetOptions{ResourceVersion: rv1_1}) Expect(outError).NotTo(HaveOccurred()) - Expect(res).To(MatchResource(apiv3.KindNetworkPolicy, namespace1, tieredPolicyName(name1, tier), spec1)) + Expect(res).To(MatchResource(apiv3.KindNetworkPolicy, namespace1, name1, spec1)) Expect(res.ResourceVersion).To(Equal(rv1_1)) } By("Getting NetworkPolicy (name1) with the updated resource version and comparing the output against spec2") res, outError = c.NetworkPolicies().Get(ctx, namespace1, name1, options.GetOptions{ResourceVersion: rv1_2}) Expect(outError).NotTo(HaveOccurred()) - Expect(res).To(MatchResource(apiv3.KindNetworkPolicy, namespace1, tieredPolicyName(name1, tier), spec2)) + Expect(res).To(MatchResource(apiv3.KindNetworkPolicy, namespace1, name1, spec2)) Expect(res.ResourceVersion).To(Equal(rv1_2)) if config.Spec.DatastoreType != apiconfig.Kubernetes { @@ -261,7 +255,7 @@ var _ = testutils.E2eDatastoreDescribe("NetworkPolicy tests", testutils.Datastor outList, outError = c.NetworkPolicies().List(ctx, options.ListOptions{Namespace: namespace1, ResourceVersion: rv1_1}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(apiv3.KindNetworkPolicy, namespace1, tieredPolicyName(name1, tier), spec1), + testutils.Resource(apiv3.KindNetworkPolicy, namespace1, name1, spec1), )) } @@ -269,21 +263,21 @@ var _ = testutils.E2eDatastoreDescribe("NetworkPolicy tests", testutils.Datastor outList, outError = c.NetworkPolicies().List(ctx, options.ListOptions{}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(apiv3.KindNetworkPolicy, namespace1, tieredPolicyName(name1, tier), spec2), - testutils.Resource(apiv3.KindNetworkPolicy, namespace2, tieredPolicyName(name2, tier), spec2), + testutils.Resource(apiv3.KindNetworkPolicy, namespace1, name1, spec2), + testutils.Resource(apiv3.KindNetworkPolicy, namespace2, name2, spec2), )) if config.Spec.DatastoreType != apiconfig.Kubernetes { By("Deleting NetworkPolicy (name1) with the old resource version") _, outError = c.NetworkPolicies().Delete(ctx, namespace1, name1, options.DeleteOptions{ResourceVersion: rv1_1}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(Equal("update conflict: NetworkPolicy(" + tieredNetworkPolicyName(namespace1, name1, tier) + ")")) + Expect(outError.Error()).To(Equal("update conflict: NetworkPolicy(" + namespacedName(namespace1, name1) + ")")) } By("Deleting NetworkPolicy (name1) with the new resource version") dres, outError := c.NetworkPolicies().Delete(ctx, namespace1, name1, options.DeleteOptions{ResourceVersion: rv1_2}) Expect(outError).NotTo(HaveOccurred()) - Expect(dres).To(MatchResource(apiv3.KindNetworkPolicy, namespace1, tieredPolicyName(name1, tier), spec2)) + Expect(dres).To(MatchResource(apiv3.KindNetworkPolicy, namespace1, name1, spec2)) if config.Spec.DatastoreType != apiconfig.Kubernetes { By("Updating NetworkPolicy name2 with a 2s TTL and waiting for the entry to be deleted") @@ -295,7 +289,7 @@ var _ = testutils.E2eDatastoreDescribe("NetworkPolicy tests", testutils.Datastor time.Sleep(2 * time.Second) _, outError = c.NetworkPolicies().Get(ctx, namespace2, name2, options.GetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: NetworkPolicy(" + tieredNetworkPolicyName(namespace2, name2, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: NetworkPolicy(" + namespacedName(namespace2, name2) + ") with error:")) By("Creating NetworkPolicy name2 with a 2s TTL and waiting for the entry to be deleted") _, outError = c.NetworkPolicies().Create(ctx, &apiv3.NetworkPolicy{ @@ -309,20 +303,20 @@ var _ = testutils.E2eDatastoreDescribe("NetworkPolicy tests", testutils.Datastor time.Sleep(2 * time.Second) _, outError = c.NetworkPolicies().Get(ctx, namespace2, name2, options.GetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: NetworkPolicy(" + tieredNetworkPolicyName(namespace2, name2, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: NetworkPolicy(" + namespacedName(namespace2, name2) + ") with error:")) } if config.Spec.DatastoreType == apiconfig.Kubernetes { By("Attempting to deleting NetworkPolicy (name2) again") dres, outError = c.NetworkPolicies().Delete(ctx, namespace2, name2, options.DeleteOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(dres).To(MatchResource(apiv3.KindNetworkPolicy, namespace2, tieredPolicyName(name2, tier), spec2)) + Expect(dres).To(MatchResource(apiv3.KindNetworkPolicy, namespace2, name2, spec2)) } By("Attempting to delete NetworkPolicy (name2) again") _, outError = c.NetworkPolicies().Delete(ctx, namespace2, name2, options.DeleteOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: NetworkPolicy(" + tieredNetworkPolicyName(namespace2, name2, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: NetworkPolicy(" + namespacedName(namespace2, name2) + ") with error:")) By("Listing all NetworkPolicies and expecting no items") outList, outError = c.NetworkPolicies().List(ctx, options.ListOptions{}) @@ -332,7 +326,7 @@ var _ = testutils.E2eDatastoreDescribe("NetworkPolicy tests", testutils.Datastor By("Getting NetworkPolicy (name2) and expecting an error") _, outError = c.NetworkPolicies().Get(ctx, namespace2, name2, options.GetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: NetworkPolicy(" + tieredNetworkPolicyName(namespace2, name2, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: NetworkPolicy(" + namespacedName(namespace2, name2) + ") with error:")) }, // Pass two fully populated PolicySpecs and expect the series of operations to succeed. @@ -369,7 +363,7 @@ var _ = testutils.E2eDatastoreDescribe("NetworkPolicy tests", testutils.Datastor ) DescribeTable("NetworkPolicy default tier name test", - func(policyName string, incorrectPrefixPolicyName string) { + func(policyName string, prefixedPolicyName string) { namespace := "default" By("Getting the policy before it was created") _, err := c.NetworkPolicies().Get(ctx, namespace, policyName, options.GetOptions{}) @@ -392,12 +386,12 @@ var _ = testutils.E2eDatastoreDescribe("NetworkPolicy tests", testutils.Datastor Expect(err).ToNot(HaveOccurred()) Expect(returnedPolicy.Name).To(Equal(policyName)) - By("Creating the policy with incorrect prefix name") + By("Creating the policy with prefix name") _, err = c.NetworkPolicies().Create(ctx, &apiv3.NetworkPolicy{ - ObjectMeta: metav1.ObjectMeta{Name: incorrectPrefixPolicyName, Namespace: namespace}, + ObjectMeta: metav1.ObjectMeta{Name: prefixedPolicyName, Namespace: namespace}, }, options.SetOptions{}) - Expect(err).To(HaveOccurred()) + Expect(err).NotTo(HaveOccurred()) By("Getting the policy") returnedPolicy, err = c.NetworkPolicies().Get(ctx, namespace, policyName, options.GetOptions{}) @@ -409,56 +403,21 @@ var _ = testutils.E2eDatastoreDescribe("NetworkPolicy tests", testutils.Datastor Expect(err).ToNot(HaveOccurred()) Expect(returnedPolicy.Name).To(Equal(policyName)) - By("Getting the policy with incorrect prefix") - _, err = c.NetworkPolicies().Get(ctx, namespace, incorrectPrefixPolicyName, options.GetOptions{}) - Expect(err).To(HaveOccurred()) - - By("Updating the policy with incorrect prefix") - _, err = c.NetworkPolicies().Update(ctx, &apiv3.NetworkPolicy{ - ObjectMeta: metav1.ObjectMeta{Name: incorrectPrefixPolicyName, ResourceVersion: "1234", CreationTimestamp: metav1.Now(), UID: uid}, - Spec: spec1, - }, options.SetOptions{}) - Expect(err).To(HaveOccurred()) + By("Getting the policy with prefix") + _, err = c.NetworkPolicies().Get(ctx, namespace, prefixedPolicyName, options.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) By("Deleting policy") returnedPolicy, err = c.NetworkPolicies().Delete(ctx, namespace, policyName, options.DeleteOptions{}) Expect(returnedPolicy.Name).To(Equal(policyName)) Expect(err).ToNot(HaveOccurred()) + _, err = c.NetworkPolicies().Delete(ctx, namespace, prefixedPolicyName, options.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) }, Entry("NetworkPolicy without default tier prefix", "netpol", "default.netpol"), Entry("NetworkPolicy with default tier prefix", "default.netpol", "netpol"), ) - Describe("NetworkPolicy without name on the projectcalico.org annotation", func() { - It("Should return the name without default prefix", func() { - if config.Spec.DatastoreType == apiconfig.Kubernetes { - config, _, err := k8s.CreateKubernetesClientset(&config.Spec) - Expect(err).NotTo(HaveOccurred()) - config.ContentType = "application/json" - cli, err := ctrlclient.New(config, ctrlclient.Options{}) - Expect(err).NotTo(HaveOccurred()) - - // Create v1 crd with empty metadata annotation name - annotations := map[string]string{} - annotations["projectcalico.org/metadata"] = "{}" - policy := &apiv3.NetworkPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: annotations, - Name: "default.prefix-test-policy", - Namespace: "default", - }, - Spec: apiv3.NetworkPolicySpec{}, - } - err = cli.Create(context.Background(), policy) - Expect(err).NotTo(HaveOccurred()) - - // We should be able to get it without the default. prefix - _, err = c.NetworkPolicies().Get(ctx, "default", "prefix-test-policy", options.GetOptions{}) - Expect(err).ToNot(HaveOccurred()) - } - }) - }) - DescribeTable("NetworkPolicy name validation tests", func(policyName string, tier string, expectError bool) { namespace := "default" @@ -478,7 +437,8 @@ var _ = testutils.E2eDatastoreDescribe("NetworkPolicy tests", testutils.Datastor &apiv3.NetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: policyName, - Namespace: namespace}, + Namespace: namespace, + }, Spec: apiv3.NetworkPolicySpec{ Tier: tier, }, @@ -490,11 +450,13 @@ var _ = testutils.E2eDatastoreDescribe("NetworkPolicy tests", testutils.Datastor Expect(err).ToNot(HaveOccurred()) } }, + + // These should all succeed, as we have no name foramtting requirements. Entry("NetworkPolicy in default tier without prefix", "netpol", "default", false), Entry("NetworkPolicy in default tier with prefix", "default.netpol", "default", false), - Entry("NetworkPolicy in custom tier with correct prefix", "tier1.netpol", "tier1", false), - Entry("NetworkPolicy in custom tier without prefix", "netpol", "tier1", true), - Entry("NetworkPolicy in custom tier with incorrect prefix", "tier1.netpol", "tier2", true), + Entry("NetworkPolicy in custom tier with tier prefix", "tier1.netpol", "tier1", false), + Entry("NetworkPolicy in custom tier without tier prefix", "netpol", "tier1", false), + Entry("NetworkPolicy in custom tier with different tier prefix", "tier1.netpol", "tier2", false), ) Describe("NetworkPolicy watch functionality", func() { @@ -763,6 +725,6 @@ var _ = testutils.E2eDatastoreDescribe("NetworkPolicy tests", testutils.Datastor Expect(np.GetName()).To(Equal(name)) } }, - nameNormalizationTests..., + nameNormalizationTests, ) }) diff --git a/libcalico-go/lib/clientv3/networkset_e2e_test.go b/libcalico-go/lib/clientv3/networkset_e2e_test.go index bf4421cf82f..f498add3ceb 100644 --- a/libcalico-go/lib/clientv3/networkset_e2e_test.go +++ b/libcalico-go/lib/clientv3/networkset_e2e_test.go @@ -18,8 +18,7 @@ import ( "context" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/libcalico-go/lib/clientv3/node.go b/libcalico-go/lib/clientv3/node.go index efd2a4c206d..0a44829f020 100644 --- a/libcalico-go/lib/clientv3/node.go +++ b/libcalico-go/lib/clientv3/node.go @@ -20,7 +20,7 @@ import ( log "github.com/sirupsen/logrus" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/errors" "github.com/projectcalico/calico/libcalico-go/lib/ipam" "github.com/projectcalico/calico/libcalico-go/lib/names" @@ -33,11 +33,11 @@ import ( // NodeInterface has methods to work with Node resources. type NodeInterface interface { - Create(ctx context.Context, res *libapiv3.Node, opts options.SetOptions) (*libapiv3.Node, error) - Update(ctx context.Context, res *libapiv3.Node, opts options.SetOptions) (*libapiv3.Node, error) - Delete(ctx context.Context, name string, opts options.DeleteOptions) (*libapiv3.Node, error) - Get(ctx context.Context, name string, opts options.GetOptions) (*libapiv3.Node, error) - List(ctx context.Context, opts options.ListOptions) (*libapiv3.NodeList, error) + Create(ctx context.Context, res *internalapi.Node, opts options.SetOptions) (*internalapi.Node, error) + Update(ctx context.Context, res *internalapi.Node, opts options.SetOptions) (*internalapi.Node, error) + Delete(ctx context.Context, name string, opts options.DeleteOptions) (*internalapi.Node, error) + Get(ctx context.Context, name string, opts options.GetOptions) (*internalapi.Node, error) + List(ctx context.Context, opts options.ListOptions) (*internalapi.NodeList, error) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) } @@ -48,7 +48,7 @@ type nodes struct { // Create takes the representation of a Node and creates it. Returns the stored // representation of the Node, and an error, if there is any. -func (r nodes) Create(ctx context.Context, res *libapiv3.Node, opts options.SetOptions) (*libapiv3.Node, error) { +func (r nodes) Create(ctx context.Context, res *internalapi.Node, opts options.SetOptions) (*internalapi.Node, error) { if err := validator.Validate(res); err != nil { return nil, err } @@ -60,29 +60,29 @@ func (r nodes) Create(ctx context.Context, res *libapiv3.Node, opts options.SetO if err != nil { return nil, err } - out, err := r.client.resources.Create(ctx, opts, libapiv3.KindNode, res) + out, err := r.client.resources.Create(ctx, opts, internalapi.KindNode, res) if out != nil { - return out.(*libapiv3.Node), err + return out.(*internalapi.Node), err } return nil, err } // Update takes the representation of a Node and updates it. Returns the stored // representation of the Node, and an error, if there is any. -func (r nodes) Update(ctx context.Context, res *libapiv3.Node, opts options.SetOptions) (*libapiv3.Node, error) { +func (r nodes) Update(ctx context.Context, res *internalapi.Node, opts options.SetOptions) (*internalapi.Node, error) { if err := validator.Validate(res); err != nil { return nil, err } - out, err := r.client.resources.Update(ctx, opts, libapiv3.KindNode, res) + out, err := r.client.resources.Update(ctx, opts, internalapi.KindNode, res) if out != nil { - return out.(*libapiv3.Node), err + return out.(*internalapi.Node), err } return nil, err } // Delete takes name of the Node and deletes it. Returns an error if one occurs. -func (r nodes) Delete(ctx context.Context, name string, opts options.DeleteOptions) (*libapiv3.Node, error) { +func (r nodes) Delete(ctx context.Context, name string, opts options.DeleteOptions) (*internalapi.Node, error) { pname, err := names.WorkloadEndpointIdentifiers{Node: name}.CalculateWorkloadEndpointName(true) if err != nil { return nil, err @@ -240,27 +240,27 @@ func (r nodes) Delete(ctx context.Context, name string, opts options.DeleteOptio } // Delete the node. - out, err := r.client.resources.Delete(ctx, opts, libapiv3.KindNode, noNamespace, name) + out, err := r.client.resources.Delete(ctx, opts, internalapi.KindNode, noNamespace, name) if out != nil { - return out.(*libapiv3.Node), err + return out.(*internalapi.Node), err } return nil, err } // Get takes name of the Node, and returns the corresponding Node object, // and an error if there is any. -func (r nodes) Get(ctx context.Context, name string, opts options.GetOptions) (*libapiv3.Node, error) { - out, err := r.client.resources.Get(ctx, opts, libapiv3.KindNode, noNamespace, name) +func (r nodes) Get(ctx context.Context, name string, opts options.GetOptions) (*internalapi.Node, error) { + out, err := r.client.resources.Get(ctx, opts, internalapi.KindNode, noNamespace, name) if out != nil { - return out.(*libapiv3.Node), err + return out.(*internalapi.Node), err } return nil, err } // List returns the list of Node objects that match the supplied options. -func (r nodes) List(ctx context.Context, opts options.ListOptions) (*libapiv3.NodeList, error) { - res := &libapiv3.NodeList{} - if err := r.client.resources.List(ctx, opts, libapiv3.KindNode, libapiv3.KindNodeList, res); err != nil { +func (r nodes) List(ctx context.Context, opts options.ListOptions) (*internalapi.NodeList, error) { + res := &internalapi.NodeList{} + if err := r.client.resources.List(ctx, opts, internalapi.KindNode, internalapi.KindNodeList, res); err != nil { return nil, err } return res, nil @@ -269,5 +269,5 @@ func (r nodes) List(ctx context.Context, opts options.ListOptions) (*libapiv3.No // Watch returns a watch.Interface that watches the Nodes that match the // supplied options. func (r nodes) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) { - return r.client.resources.Watch(ctx, opts, libapiv3.KindNode, nil) + return r.client.resources.Watch(ctx, opts, internalapi.KindNode, nil) } diff --git a/libcalico-go/lib/clientv3/node_e2e_test.go b/libcalico-go/lib/clientv3/node_e2e_test.go index f52ba7ff634..1e9c3543cc1 100644 --- a/libcalico-go/lib/clientv3/node_e2e_test.go +++ b/libcalico-go/lib/clientv3/node_e2e_test.go @@ -20,14 +20,13 @@ import ( "net" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/clientv3" @@ -61,7 +60,7 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (kdd)", testutils.DatastoreK8 // Add a label and check it gets written. By("Adding a label to the node") node.Labels = map[string]string{"test-label": "foo"} - node.Spec.BGP = &libapiv3.NodeBGPSpec{IPv4Address: "10.0.0.1"} + node.Spec.BGP = &internalapi.NodeBGPSpec{IPv4Address: "10.0.0.1"} _, err = c.Nodes().Update(ctx, node, options.SetOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -101,7 +100,7 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (kdd)", testutils.DatastoreK8 // Update the BGP spec. By("Updating the BGP spec") - node.Spec.BGP = &libapiv3.NodeBGPSpec{} + node.Spec.BGP = &internalapi.NodeBGPSpec{} node.Spec.BGP.IPv4IPIPTunnelAddr = "192.168.1.1" _, err = c.Nodes().Update(ctx, node, options.SetOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -169,7 +168,7 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (kdd)", testutils.DatastoreK8 // Update the Wireguard spec. By("Updating the Wireguard spec") - node.Spec.Wireguard = &libapiv3.NodeWireguardSpec{} + node.Spec.Wireguard = &internalapi.NodeWireguardSpec{} node.Spec.Wireguard.InterfaceIPv4Address = "192.168.1.1" _, err = c.Nodes().Update(ctx, node, options.SetOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -187,13 +186,13 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor ctx := context.Background() name1 := "node-1" name2 := "node-2" - spec1 := libapiv3.NodeSpec{ + spec1 := internalapi.NodeSpec{ IPv4VXLANTunnelAddr: "192.168.50.5", - BGP: &libapiv3.NodeBGPSpec{ + BGP: &internalapi.NodeBGPSpec{ IPv4Address: "1.2.3.4", IPv4IPIPTunnelAddr: "192.168.50.6", }, - OrchRefs: []libapiv3.OrchRef{ + OrchRefs: []internalapi.OrchRef{ { Orchestrator: "k8s", NodeName: "node1", @@ -203,16 +202,16 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor NodeName: "node1", }, }, - Wireguard: &libapiv3.NodeWireguardSpec{ + Wireguard: &internalapi.NodeWireguardSpec{ InterfaceIPv4Address: "192.168.50.7", }, } - spec2 := libapiv3.NodeSpec{ - BGP: &libapiv3.NodeBGPSpec{ + spec2 := internalapi.NodeSpec{ + BGP: &internalapi.NodeBGPSpec{ IPv4Address: "10.20.30.40", IPv6Address: "aa:bb:cc::ff", }, - OrchRefs: []libapiv3.OrchRef{ + OrchRefs: []internalapi.OrchRef{ { Orchestrator: "k8s", NodeName: "node2", @@ -223,7 +222,7 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor }, }, } - status := libapiv3.NodeStatus{ + status := internalapi.NodeStatus{ WireguardPublicKey: "jlkVyQYooZYzI2wFfNhSZez5eWh44yfq1wKVjLvSXgY=", } @@ -237,7 +236,7 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor be.Clean() // Create a node. - n, err := c.Nodes().Create(ctx, &libapiv3.Node{ + n, err := c.Nodes().Create(ctx, &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{Name: name1}, Spec: spec1, }, options.SetOptions{}) @@ -298,12 +297,12 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor }) Expect(err).NotTo(HaveOccurred()) - wep := libapiv3.WorkloadEndpoint{ + wep := internalapi.WorkloadEndpoint{ ObjectMeta: metav1.ObjectMeta{ Name: "node--1-k8s-mypod-mywep", Namespace: "default", }, - Spec: libapiv3.WorkloadEndpointSpec{ + Spec: internalapi.WorkloadEndpointSpec{ InterfaceName: "eth0", Pod: "mypod", Endpoint: "mywep", @@ -434,7 +433,7 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor }) DescribeTable("Node e2e CRUD tests", - func(name1, name2 string, spec1, spec2 libapiv3.NodeSpec, status libapiv3.NodeStatus) { + func(name1, name2 string, spec1, spec2 internalapi.NodeSpec, status internalapi.NodeStatus) { c, err := clientv3.New(config) Expect(err).NotTo(HaveOccurred()) @@ -443,7 +442,7 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor be.Clean() By("Updating the Node before it is created") - _, outError := c.Nodes().Update(ctx, &libapiv3.Node{ + _, outError := c.Nodes().Update(ctx, &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{Name: name1, ResourceVersion: "1234", CreationTimestamp: metav1.Now(), UID: uid}, Spec: spec1, }, options.SetOptions{}) @@ -451,7 +450,7 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor Expect(outError.Error()).To(ContainSubstring("resource does not exist: Node(" + name1 + ") with error:")) By("Attempting to creating a new Node with name1/spec1 and a non-empty ResourceVersion") - _, outError = c.Nodes().Create(ctx, &libapiv3.Node{ + _, outError = c.Nodes().Create(ctx, &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{Name: name1, ResourceVersion: "12345"}, Spec: spec1, }, options.SetOptions{}) @@ -459,18 +458,18 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor Expect(outError.Error()).To(Equal("error with field Metadata.ResourceVersion = '12345' (field must not be set for a Create request)")) By("Creating a new Node with name1/spec1") - res1, outError := c.Nodes().Create(ctx, &libapiv3.Node{ + res1, outError := c.Nodes().Create(ctx, &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{Name: name1}, Spec: spec1, }, options.SetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res1).To(MatchResource(libapiv3.KindNode, testutils.ExpectNoNamespace, name1, spec1)) + Expect(res1).To(MatchResource(internalapi.KindNode, testutils.ExpectNoNamespace, name1, spec1)) // Track the version of the original data for name1. rv1_1 := res1.ResourceVersion By("Attempting to create the same Node with name1 but with spec2") - _, outError = c.Nodes().Create(ctx, &libapiv3.Node{ + _, outError = c.Nodes().Create(ctx, &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{Name: name1}, Spec: spec2, }, options.SetOptions{}) @@ -480,7 +479,7 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor By("Getting Node (name1) and comparing the output against spec1") res, outError := c.Nodes().Get(ctx, name1, options.GetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res).To(MatchResource(libapiv3.KindNode, testutils.ExpectNoNamespace, name1, spec1)) + Expect(res).To(MatchResource(internalapi.KindNode, testutils.ExpectNoNamespace, name1, spec1)) Expect(res.ResourceVersion).To(Equal(res1.ResourceVersion)) By("Getting Node (name2) before it is created") @@ -492,39 +491,39 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor outList, outError := c.Nodes().List(ctx, options.ListOptions{}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(libapiv3.KindNode, testutils.ExpectNoNamespace, name1, spec1), + testutils.Resource(internalapi.KindNode, testutils.ExpectNoNamespace, name1, spec1), )) By("Creating a new Node with name2/spec2") - res2, outError := c.Nodes().Create(ctx, &libapiv3.Node{ + res2, outError := c.Nodes().Create(ctx, &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{Name: name2}, Spec: spec2, }, options.SetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res2).To(MatchResource(libapiv3.KindNode, testutils.ExpectNoNamespace, name2, spec2)) + Expect(res2).To(MatchResource(internalapi.KindNode, testutils.ExpectNoNamespace, name2, spec2)) By("Getting Node (name2) and comparing the output against spec2") res, outError = c.Nodes().Get(ctx, name2, options.GetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res2).To(MatchResource(libapiv3.KindNode, testutils.ExpectNoNamespace, name2, spec2)) + Expect(res2).To(MatchResource(internalapi.KindNode, testutils.ExpectNoNamespace, name2, spec2)) Expect(res.ResourceVersion).To(Equal(res2.ResourceVersion)) By("Listing all the Nodes, expecting a two results with name1/spec1 and name2/spec2") outList, outError = c.Nodes().List(ctx, options.ListOptions{}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(libapiv3.KindNode, testutils.ExpectNoNamespace, name1, spec1), - testutils.Resource(libapiv3.KindNode, testutils.ExpectNoNamespace, name2, spec2), + testutils.Resource(internalapi.KindNode, testutils.ExpectNoNamespace, name1, spec1), + testutils.Resource(internalapi.KindNode, testutils.ExpectNoNamespace, name2, spec2), )) By("Updating Node name1 with spec2") res1.Spec = spec2 res1, outError = c.Nodes().Update(ctx, res1, options.SetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res1).To(MatchResource(libapiv3.KindNode, testutils.ExpectNoNamespace, name1, spec2)) + Expect(res1).To(MatchResource(internalapi.KindNode, testutils.ExpectNoNamespace, name1, spec2)) By("Attempting to update the Node without a Creation Timestamp") - res, outError = c.Nodes().Update(ctx, &libapiv3.Node{ + res, outError = c.Nodes().Update(ctx, &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{Name: name1, ResourceVersion: "1234", UID: uid}, Spec: spec1, }, options.SetOptions{}) @@ -533,7 +532,7 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor Expect(outError.Error()).To(Equal("error with field Metadata.CreationTimestamp = '0001-01-01 00:00:00 +0000 UTC' (field must be set for an Update request)")) By("Attempting to update the Node without a UID") - res, outError = c.Nodes().Update(ctx, &libapiv3.Node{ + res, outError = c.Nodes().Update(ctx, &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{Name: name1, ResourceVersion: "1234", CreationTimestamp: metav1.Now()}, Spec: spec1, }, options.SetOptions{}) @@ -561,28 +560,28 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor By("Getting Node (name1) with the original resource version and comparing the output against spec1") res, outError = c.Nodes().Get(ctx, name1, options.GetOptions{ResourceVersion: rv1_1}) Expect(outError).NotTo(HaveOccurred()) - Expect(res).To(MatchResource(libapiv3.KindNode, testutils.ExpectNoNamespace, name1, spec1)) + Expect(res).To(MatchResource(internalapi.KindNode, testutils.ExpectNoNamespace, name1, spec1)) Expect(res.ResourceVersion).To(Equal(rv1_1)) By("Getting Node (name1) with the updated resource version and comparing the output against spec2") res, outError = c.Nodes().Get(ctx, name1, options.GetOptions{ResourceVersion: rv1_2}) Expect(outError).NotTo(HaveOccurred()) - Expect(res).To(MatchResource(libapiv3.KindNode, testutils.ExpectNoNamespace, name1, spec2)) + Expect(res).To(MatchResource(internalapi.KindNode, testutils.ExpectNoNamespace, name1, spec2)) Expect(res.ResourceVersion).To(Equal(rv1_2)) By("Listing Nodes with the original resource version and checking for a single result with name1/spec1") outList, outError = c.Nodes().List(ctx, options.ListOptions{ResourceVersion: rv1_1}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(libapiv3.KindNode, testutils.ExpectNoNamespace, name1, spec1), + testutils.Resource(internalapi.KindNode, testutils.ExpectNoNamespace, name1, spec1), )) By("Listing Nodes with the latest resource version and checking for two results with name1/spec2 and name2/spec2") outList, outError = c.Nodes().List(ctx, options.ListOptions{}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(libapiv3.KindNode, testutils.ExpectNoNamespace, name1, spec2), - testutils.Resource(libapiv3.KindNode, testutils.ExpectNoNamespace, name2, spec2), + testutils.Resource(internalapi.KindNode, testutils.ExpectNoNamespace, name1, spec2), + testutils.Resource(internalapi.KindNode, testutils.ExpectNoNamespace, name2, spec2), )) By("Deleting Node (name1) with the old resource version") @@ -593,7 +592,7 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor By("Deleting Node (name1) with the new resource version") dres, outError := c.Nodes().Delete(ctx, name1, options.DeleteOptions{ResourceVersion: rv1_2}) Expect(outError).NotTo(HaveOccurred()) - Expect(dres).To(MatchResource(libapiv3.KindNode, testutils.ExpectNoNamespace, name1, spec2)) + Expect(dres).To(MatchResource(internalapi.KindNode, testutils.ExpectNoNamespace, name1, spec2)) By("Updating Node name2 with a 2s TTL and waiting for the entry to be deleted") _, outError = c.Nodes().Update(ctx, res2, options.SetOptions{TTL: 2 * time.Second}) @@ -607,7 +606,7 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor Expect(outError.Error()).To(ContainSubstring("resource does not exist: Node(" + name2 + ") with error:")) By("Creating Node name2 with a 2s TTL and waiting for the entry to be deleted") - _, outError = c.Nodes().Create(ctx, &libapiv3.Node{ + _, outError = c.Nodes().Create(ctx, &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{Name: name2}, Spec: spec2, }, options.SetOptions{TTL: 2 * time.Second}) @@ -636,7 +635,7 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor Expect(outError.Error()).To(ContainSubstring("resource does not exist: Node(" + name2 + ") with error:")) By("Setting status no node resource") - res1, outError = c.Nodes().Create(ctx, &libapiv3.Node{ + res1, outError = c.Nodes().Create(ctx, &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{Name: name1}, Spec: spec1, }, options.SetOptions{}) @@ -644,12 +643,12 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor res1.Status = status res, outError = c.Nodes().Update(ctx, res1, options.SetOptions{}) Expect(outError).ToNot(HaveOccurred()) - Expect(res).To(MatchResourceWithStatus(libapiv3.KindNode, testutils.ExpectNoNamespace, name1, spec1, status)) + Expect(res).To(MatchResourceWithStatus(internalapi.KindNode, testutils.ExpectNoNamespace, name1, spec1, status)) By("Getting resource and verifying status is present") res, outError = c.Nodes().Get(ctx, name1, options.GetOptions{}) Expect(outError).ToNot(HaveOccurred()) - Expect(res).To(MatchResourceWithStatus(libapiv3.KindNode, testutils.ExpectNoNamespace, name1, spec1, status)) + Expect(res).To(MatchResourceWithStatus(internalapi.KindNode, testutils.ExpectNoNamespace, name1, spec1, status)) }, // Test 1: Pass two fully populated NodeSpecs and expect the series of operations to succeed. @@ -674,7 +673,7 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor By("Configuring a Node name1/spec1 and storing the response") outRes1, err := c.Nodes().Create( ctx, - &libapiv3.Node{ + &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{Name: name1}, Spec: spec1, }, @@ -685,7 +684,7 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor By("Configuring a Node name2/spec2 and storing the response") outRes2, err := c.Nodes().Create( ctx, - &libapiv3.Node{ + &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{Name: name2}, Spec: spec2, }, @@ -703,7 +702,7 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor Expect(err).NotTo(HaveOccurred()) By("Checking for two events, create res2 and delete re1") - testWatcher1.ExpectEvents(libapiv3.KindNode, []watch.Event{ + testWatcher1.ExpectEvents(internalapi.KindNode, []watch.Event{ { Type: watch.Added, Object: outRes2, @@ -724,14 +723,14 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor By("Modifying res2") outRes3, err := c.Nodes().Update( ctx, - &libapiv3.Node{ + &internalapi.Node{ ObjectMeta: outRes2.ObjectMeta, Spec: spec1, }, options.SetOptions{}, ) Expect(err).NotTo(HaveOccurred()) - testWatcher2.ExpectEvents(libapiv3.KindNode, []watch.Event{ + testWatcher2.ExpectEvents(internalapi.KindNode, []watch.Event{ { Type: watch.Added, Object: outRes1, @@ -759,7 +758,7 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor Expect(err).NotTo(HaveOccurred()) testWatcher2_1 := testutils.NewTestResourceWatch(config.Spec.DatastoreType, w) defer testWatcher2_1.Stop() - testWatcher2_1.ExpectEvents(libapiv3.KindNode, []watch.Event{ + testWatcher2_1.ExpectEvents(internalapi.KindNode, []watch.Event{ { Type: watch.Added, Object: outRes1, @@ -777,7 +776,7 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor Expect(err).NotTo(HaveOccurred()) testWatcher3 := testutils.NewTestResourceWatch(config.Spec.DatastoreType, w) defer testWatcher3.Stop() - testWatcher3.ExpectEvents(libapiv3.KindNode, []watch.Event{ + testWatcher3.ExpectEvents(internalapi.KindNode, []watch.Event{ { Type: watch.Added, Object: outRes3, @@ -788,7 +787,7 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor By("Configuring Node name1/spec1 again and storing the response") outRes1, err = c.Nodes().Create( ctx, - &libapiv3.Node{ + &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{Name: name1}, Spec: spec1, }, @@ -800,7 +799,7 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor Expect(err).NotTo(HaveOccurred()) testWatcher4 := testutils.NewTestResourceWatch(config.Spec.DatastoreType, w) defer testWatcher4.Stop() - testWatcher4.ExpectEventsAnyOrder(libapiv3.KindNode, []watch.Event{ + testWatcher4.ExpectEventsAnyOrder(internalapi.KindNode, []watch.Event{ { Type: watch.Added, Object: outRes1, @@ -813,7 +812,7 @@ var _ = testutils.E2eDatastoreDescribe("Node tests (etcdv3)", testutils.Datastor By("Cleaning the datastore and expecting deletion events for each configured resource (tests prefix deletes results in individual events for each key)") be.Clean() - testWatcher4.ExpectEventsAnyOrder(libapiv3.KindNode, []watch.Event{ + testWatcher4.ExpectEventsAnyOrder(internalapi.KindNode, []watch.Event{ { Type: watch.Deleted, Previous: outRes1, diff --git a/libcalico-go/lib/clientv3/profile_e2e_test.go b/libcalico-go/lib/clientv3/profile_e2e_test.go index e9b0876e403..0df160c16b7 100644 --- a/libcalico-go/lib/clientv3/profile_e2e_test.go +++ b/libcalico-go/lib/clientv3/profile_e2e_test.go @@ -18,8 +18,7 @@ import ( "context" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" diff --git a/libcalico-go/lib/clientv3/resources.go b/libcalico-go/lib/clientv3/resources.go index 8e638b0adb4..251ae980b8e 100644 --- a/libcalico-go/lib/clientv3/resources.go +++ b/libcalico-go/lib/clientv3/resources.go @@ -56,6 +56,7 @@ type resourceList interface { type resourceInterface interface { Create(ctx context.Context, opts options.SetOptions, kind string, in resource) (resource, error) Update(ctx context.Context, opts options.SetOptions, kind string, in resource) (resource, error) + UpdateStatus(ctx context.Context, opts options.SetOptions, kind string, in resource) (resource, error) Delete(ctx context.Context, opts options.DeleteOptions, kind, ns, name string) (resource, error) Get(ctx context.Context, opts options.GetOptions, kind, ns, name string) (resource, error) List(ctx context.Context, opts options.ListOptions, kind, listkind string, inout resourceList) error @@ -162,6 +163,61 @@ func (c *resources) Update(ctx context.Context, opts options.SetOptions, kind st return nil, err } +// UpdateStatus updates the status of a resource in the backend datastore. +// For backends that support it (Kubernetes), this uses the status subresource. +// For backends that don't (etcd), this falls back to a regular Update. +func (c *resources) UpdateStatus(ctx context.Context, opts options.SetOptions, kind string, in resource) (resource, error) { + // A ResourceVersion should always be specified on an UpdateStatus. + if len(in.GetObjectMeta().GetResourceVersion()) == 0 { + logWithResource(in).Info("Rejecting UpdateStatus request with empty resource version") + return nil, cerrors.ErrorValidation{ + ErroredFields: []cerrors.ErroredField{{ + Name: "Metadata.ResourceVersion", + Reason: "field must be set for an Update request", + Value: in.GetObjectMeta().GetResourceVersion(), + }}, + } + } + if err := c.checkNamespace(in.GetObjectMeta().GetNamespace(), kind); err != nil { + return nil, err + } + creationTimestamp := in.GetObjectMeta().GetCreationTimestamp() + if creationTimestamp.IsZero() { + return nil, cerrors.ErrorValidation{ + ErroredFields: []cerrors.ErroredField{{ + Name: "Metadata.CreationTimestamp", + Reason: "field must be set for an Update request", + Value: in.GetObjectMeta().GetCreationTimestamp(), + }}, + } + } + if in.GetObjectMeta().GetUID() == "" { + return nil, cerrors.ErrorValidation{ + ErroredFields: []cerrors.ErroredField{{ + Name: "Metadata.UID", + Reason: "field must be set for an Update request", + Value: in.GetObjectMeta().GetUID(), + }}, + } + } + + // If the backend supports UpdateStatus, use it. Otherwise fall back to Update. + if sc, ok := c.backend.(bapi.StatusClient); ok { + kvp, err := sc.UpdateStatus(ctx, c.resourceToKVPair(opts, kind, in)) + if kvp != nil { + return c.kvPairToResource(kvp), err + } + return nil, err + } + + // Fallback to regular Update for backends that don't support status subresource (e.g., etcd). + kvp, err := c.backend.Update(ctx, c.resourceToKVPair(opts, kind, in)) + if kvp != nil { + return c.kvPairToResource(kvp), err + } + return nil, err +} + // Delete deletes a resource from the backend datastore. func (c *resources) Delete(ctx context.Context, opts options.DeleteOptions, kind, ns, name string) (resource, error) { if err := c.checkNamespace(ns, kind); err != nil { diff --git a/libcalico-go/lib/clientv3/stagedglobalnetworkpolicy.go b/libcalico-go/lib/clientv3/stagedglobalnetworkpolicy.go index ea7ae43289a..d2f80dd0b96 100644 --- a/libcalico-go/lib/clientv3/stagedglobalnetworkpolicy.go +++ b/libcalico-go/lib/clientv3/stagedglobalnetworkpolicy.go @@ -16,9 +16,9 @@ package clientv3 import ( "context" - "fmt" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "github.com/projectcalico/api/pkg/defaults" log "github.com/sirupsen/logrus" "github.com/projectcalico/calico/libcalico-go/lib/names" @@ -58,35 +58,24 @@ func (r stagedGlobalNetworkPolicies) Create(ctx context.Context, res *apiv3.Stag resCopy := *res res = &resCopy } - if res.Spec.StagedAction != apiv3.StagedActionDelete { - defaultPolicyTypesField(res.Spec.Ingress, res.Spec.Egress, &res.Spec.Types) - } else { - res.Spec.Types = []apiv3.PolicyType(nil) - } - if err := validator.Validate(res); err != nil { + // Run defaulting logic. + if _, err := defaults.Default(res); err != nil { return nil, err } - err := names.ValidateTieredPolicyName(res.Name, tier) - if err != nil { - return nil, err + if res.Spec.StagedAction == apiv3.StagedActionDelete { + res.Spec.Types = nil } - // Add tier labels to policy for lookup. - if tier != "default" { - res.GetObjectMeta().SetLabels(addTierLabel(res.GetObjectMeta().GetLabels(), tier)) + if err := validator.Validate(res); err != nil { + return nil, err } out, err := r.client.resources.Create(ctx, opts, apiv3.KindStagedGlobalNetworkPolicy, res) if out != nil { - // Add the tier labels if necessary - out.GetObjectMeta().SetLabels(defaultTierLabelIfMissing(out.GetObjectMeta().GetLabels())) return out.(*apiv3.StagedGlobalNetworkPolicy), err } - // Add the tier labels if necessary - res.GetObjectMeta().SetLabels(defaultTierLabelIfMissing(res.GetObjectMeta().GetLabels())) - return nil, err } @@ -99,36 +88,23 @@ func (r stagedGlobalNetworkPolicies) Update(ctx context.Context, res *apiv3.Stag resCopy := *res res = &resCopy } - if res.Spec.StagedAction != apiv3.StagedActionDelete { - defaultPolicyTypesField(res.Spec.Ingress, res.Spec.Egress, &res.Spec.Types) - } else { - res.Spec.Types = []apiv3.PolicyType(nil) - } - - if err := validator.Validate(res); err != nil { + // Run defaulting logic. + if _, err := defaults.Default(res); err != nil { return nil, err } - err := names.ValidateTieredPolicyName(res.Name, res.Spec.Tier) - if err != nil { - return nil, err + if res.Spec.StagedAction == apiv3.StagedActionDelete { + res.Spec.Types = nil } - // Add tier labels to policy for lookup. - tier := names.TierOrDefault(res.Spec.Tier) - if tier != "default" { - res.GetObjectMeta().SetLabels(addTierLabel(res.GetObjectMeta().GetLabels(), tier)) + if err := validator.Validate(res); err != nil { + return nil, err } out, err := r.client.resources.Update(ctx, opts, apiv3.KindStagedGlobalNetworkPolicy, res) if out != nil { - // Add the tier labels if necessary - out.GetObjectMeta().SetLabels(defaultTierLabelIfMissing(out.GetObjectMeta().GetLabels())) return out.(*apiv3.StagedGlobalNetworkPolicy), err } - // Add the tier labels if necessary - res.GetObjectMeta().SetLabels(defaultTierLabelIfMissing(res.GetObjectMeta().GetLabels())) - return nil, err } @@ -150,21 +126,7 @@ func (r stagedGlobalNetworkPolicies) Get(ctx context.Context, name string, opts if out != nil { // Add the tier labels if necessary out.GetObjectMeta().SetLabels(defaultTierLabelIfMissing(out.GetObjectMeta().GetLabels())) - // Fill in the tier information from the policy name if we find it missing. - // We expect backend policies to have the right name (prefixed with tier name). - res_out := out.(*apiv3.StagedGlobalNetworkPolicy) - if res_out.Spec.Tier == "" { - tier, tierErr := names.TierFromPolicyName(res_out.Name) - if tierErr != nil { - log.WithError(tierErr).Infof("Skipping setting tier for name %v", res_out.Name) - return res_out, tierErr - } - res_out.Spec.Tier = tier - } - if res_out.Name != name { - return nil, fmt.Errorf("resource not found GlobalNetworkPolicy(%s)", name) - } - return res_out, err + return out.(*apiv3.StagedGlobalNetworkPolicy), err } return nil, err } @@ -172,28 +134,13 @@ func (r stagedGlobalNetworkPolicies) Get(ctx context.Context, name string, opts // List returns the list of StagedGlobalNetworkPolicy objects that match the supplied options. func (r stagedGlobalNetworkPolicies) List(ctx context.Context, opts options.ListOptions) (*apiv3.StagedGlobalNetworkPolicyList, error) { res := &apiv3.StagedGlobalNetworkPolicyList{} - // Add the name prefix if name is provided - if opts.Name != "" && !opts.Prefix { - opts.Name = names.TieredPolicyName(opts.Name) - } - if err := r.client.resources.List(ctx, opts, apiv3.KindStagedGlobalNetworkPolicy, apiv3.KindStagedGlobalNetworkPolicyList, res); err != nil { return nil, err } // Make sure the tier labels are added - for i, _ := range res.Items { + for i := range res.Items { res.Items[i].GetObjectMeta().SetLabels(defaultTierLabelIfMissing(res.Items[i].GetObjectMeta().GetLabels())) - // Fill in the tier information from the policy name if we find it missing. - // We expect backend policies to have the right name (prefixed with tier name). - if res.Items[i].Spec.Tier == "" { - tier, tierErr := names.TierFromPolicyName(res.Items[i].Name) - if tierErr != nil { - log.WithError(tierErr).Infof("Skipping setting tier for name %v", res.Items[i].Name) - continue - } - res.Items[i].Spec.Tier = tier - } } return res, nil @@ -202,10 +149,5 @@ func (r stagedGlobalNetworkPolicies) List(ctx context.Context, opts options.List // Watch returns a watch.Interface that watches the stagedGlobalNetworkPolicies that match the // supplied options. func (r stagedGlobalNetworkPolicies) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) { - // Add the name prefix if name is provided - if opts.Name != "" { - opts.Name = names.TieredPolicyName(opts.Name) - } - return r.client.resources.Watch(ctx, opts, apiv3.KindStagedGlobalNetworkPolicy, &policyConverter{}) } diff --git a/libcalico-go/lib/clientv3/stagedglobalnetworkpolicy_e2e_test.go b/libcalico-go/lib/clientv3/stagedglobalnetworkpolicy_e2e_test.go index c5215ace2ce..809d0d041ff 100644 --- a/libcalico-go/lib/clientv3/stagedglobalnetworkpolicy_e2e_test.go +++ b/libcalico-go/lib/clientv3/stagedglobalnetworkpolicy_e2e_test.go @@ -18,13 +18,11 @@ import ( "context" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" "github.com/projectcalico/calico/libcalico-go/lib/backend" @@ -78,6 +76,7 @@ var _ = testutils.E2eDatastoreDescribe("StagedGlobalNetworkPolicy tests", testut var be bapi.Client BeforeEach(func() { + v3CRD = k8s.UsingV3CRDs(&config.Spec) var err error c, err = clientv3.New(config) Expect(err).NotTo(HaveOccurred()) @@ -114,7 +113,7 @@ var _ = testutils.E2eDatastoreDescribe("StagedGlobalNetworkPolicy tests", testut Spec: spec1, }, options.SetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedGlobalNetworkPolicy(" + tieredGNPName(name1, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedGlobalNetworkPolicy(" + name1 + ") with error:")) By("Attempting to creating a new StagedGlobalNetworkPolicy with name1/spec1 and a non-empty ResourceVersion") polToCreate := &apiv3.StagedGlobalNetworkPolicy{ @@ -137,7 +136,7 @@ var _ = testutils.E2eDatastoreDescribe("StagedGlobalNetworkPolicy tests", testut res1, outError := c.StagedGlobalNetworkPolicies().Create(ctx, polToCreate, options.SetOptions{}) Expect(polToCreate.Spec).To(Equal(polToCreateCopy.Spec), "Create() unexpectedly modified input policy") Expect(outError).NotTo(HaveOccurred()) - Expect(res1).To(MatchResource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name1, tier), spec1)) + Expect(res1).To(MatchResource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, name1, spec1)) // Track the version of the original data for name1. rv1_1 := res1.ResourceVersion @@ -148,24 +147,24 @@ var _ = testutils.E2eDatastoreDescribe("StagedGlobalNetworkPolicy tests", testut Spec: spec2, }, options.SetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource already exists: StagedGlobalNetworkPolicy(" + tieredGNPName(name1, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource already exists: StagedGlobalNetworkPolicy(" + name1 + ") with error:")) By("Getting StagedGlobalNetworkPolicy (name1) and comparing the output against spec1") res, outError := c.StagedGlobalNetworkPolicies().Get(ctx, name1, options.GetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res).To(MatchResource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name1, tier), spec1)) + Expect(res).To(MatchResource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, name1, spec1)) Expect(res.ResourceVersion).To(Equal(res1.ResourceVersion)) By("Getting StagedGlobalNetworkPolicy (name2) before it is created") _, outError = c.StagedGlobalNetworkPolicies().Get(ctx, name2, options.GetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedGlobalNetworkPolicy(" + tieredGNPName(name2, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedGlobalNetworkPolicy(" + name2 + ") with error:")) By("Listing all the StagedGlobalNetworkPolicies, expecting a single result with name1/spec1") outList, outError := c.StagedGlobalNetworkPolicies().List(ctx, options.ListOptions{}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name1, tier), spec1), + testutils.Resource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, name1, spec1), )) By("Creating a new StagedGlobalNetworkPolicy with name2/spec2") @@ -175,20 +174,20 @@ var _ = testutils.E2eDatastoreDescribe("StagedGlobalNetworkPolicy tests", testut Spec: spec2, }, options.SetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res2).To(MatchResource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name2, tier), spec2)) + Expect(res2).To(MatchResource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, name2, spec2)) By("Getting StagedGlobalNetworkPolicy (name2) and comparing the output against spec2") res, outError = c.StagedGlobalNetworkPolicies().Get(ctx, name2, options.GetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res2).To(MatchResource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name2, tier), spec2)) + Expect(res2).To(MatchResource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, name2, spec2)) Expect(res.ResourceVersion).To(Equal(res2.ResourceVersion)) By("Listing all the StagedGlobalNetworkPolicies, expecting a two results with name1/spec1 and name2/spec2") outList, outError = c.StagedGlobalNetworkPolicies().List(ctx, options.ListOptions{}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name1, tier), spec1), - testutils.Resource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name2, tier), spec2), + testutils.Resource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, name1, spec1), + testutils.Resource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, name2, spec2), )) By("Updating StagedGlobalNetworkPolicy name1 with spec2") @@ -197,7 +196,7 @@ var _ = testutils.E2eDatastoreDescribe("StagedGlobalNetworkPolicy tests", testut res1out, outError := c.StagedGlobalNetworkPolicies().Update(ctx, res1, options.SetOptions{}) Expect(outError).NotTo(HaveOccurred()) Expect(res1.Spec).To(Equal(res1Copy.Spec), "Update() unexpectedly modified input") - Expect(res1).To(MatchResource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name1, tier), spec2)) + Expect(res1).To(MatchResource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, name1, spec2)) res1 = res1out By("Attempting to update the StagedGlobalNetworkPolicy without a Creation Timestamp") @@ -233,20 +232,20 @@ var _ = testutils.E2eDatastoreDescribe("StagedGlobalNetworkPolicy tests", testut res1.ResourceVersion = rv1_1 _, outError = c.StagedGlobalNetworkPolicies().Update(ctx, res1, options.SetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(Equal("update conflict: StagedGlobalNetworkPolicy(" + tieredGNPName(name1, tier) + ")")) + Expect(outError.Error()).To(Equal("update conflict: StagedGlobalNetworkPolicy(" + name1 + ")")) if config.Spec.DatastoreType != apiconfig.Kubernetes { By("Getting StagedGlobalNetworkPolicy (name1) with the original resource version and comparing the output against spec1") res, outError = c.StagedGlobalNetworkPolicies().Get(ctx, name1, options.GetOptions{ResourceVersion: rv1_1}) Expect(outError).NotTo(HaveOccurred()) - Expect(res).To(MatchResource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name1, tier), spec1)) + Expect(res).To(MatchResource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, name1, spec1)) Expect(res.ResourceVersion).To(Equal(rv1_1)) } By("Getting StagedGlobalNetworkPolicy (name1) with the updated resource version and comparing the output against spec2") res, outError = c.StagedGlobalNetworkPolicies().Get(ctx, name1, options.GetOptions{ResourceVersion: rv1_2}) Expect(outError).NotTo(HaveOccurred()) - Expect(res).To(MatchResource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name1, tier), spec2)) + Expect(res).To(MatchResource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, name1, spec2)) Expect(res.ResourceVersion).To(Equal(rv1_2)) if config.Spec.DatastoreType != apiconfig.Kubernetes { @@ -254,7 +253,7 @@ var _ = testutils.E2eDatastoreDescribe("StagedGlobalNetworkPolicy tests", testut outList, outError = c.StagedGlobalNetworkPolicies().List(ctx, options.ListOptions{ResourceVersion: rv1_1}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name1, tier), spec1), + testutils.Resource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, name1, spec1), )) } @@ -262,21 +261,21 @@ var _ = testutils.E2eDatastoreDescribe("StagedGlobalNetworkPolicy tests", testut outList, outError = c.StagedGlobalNetworkPolicies().List(ctx, options.ListOptions{}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name1, tier), spec2), - testutils.Resource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name2, tier), spec2), + testutils.Resource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, name1, spec2), + testutils.Resource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, name2, spec2), )) if config.Spec.DatastoreType != apiconfig.Kubernetes { By("Deleting StagedGlobalNetworkPolicy (name1) with the old resource version") _, outError = c.StagedGlobalNetworkPolicies().Delete(ctx, name1, options.DeleteOptions{ResourceVersion: rv1_1}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(Equal("update conflict: StagedGlobalNetworkPolicy(" + tieredGNPName(name1, tier) + ")")) + Expect(outError.Error()).To(Equal("update conflict: StagedGlobalNetworkPolicy(" + name1 + ")")) } By("Deleting StagedGlobalNetworkPolicy (name1) with the new resource version") dres, outError := c.StagedGlobalNetworkPolicies().Delete(ctx, name1, options.DeleteOptions{ResourceVersion: rv1_2}) Expect(outError).NotTo(HaveOccurred()) - Expect(dres).To(MatchResource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name1, tier), spec2)) + Expect(dres).To(MatchResource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, name1, spec2)) time.Sleep(1 * time.Second) if config.Spec.DatastoreType != apiconfig.Kubernetes { @@ -289,7 +288,7 @@ var _ = testutils.E2eDatastoreDescribe("StagedGlobalNetworkPolicy tests", testut time.Sleep(2 * time.Second) _, outError = c.StagedGlobalNetworkPolicies().Get(ctx, name2, options.GetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedGlobalNetworkPolicy(" + tieredGNPName(name2, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedGlobalNetworkPolicy(" + name2 + ") with error:")) By("Creating StagedGlobalNetworkPolicy name2 with a 2s TTL and waiting for the entry to be deleted") _, outError = c.StagedGlobalNetworkPolicies().Create(ctx, &apiv3.StagedGlobalNetworkPolicy{ @@ -303,21 +302,21 @@ var _ = testutils.E2eDatastoreDescribe("StagedGlobalNetworkPolicy tests", testut time.Sleep(2 * time.Second) _, outError = c.StagedGlobalNetworkPolicies().Get(ctx, name2, options.GetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedGlobalNetworkPolicy(" + tieredGNPName(name2, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedGlobalNetworkPolicy(" + name2 + ") with error:")) } if config.Spec.DatastoreType == apiconfig.Kubernetes { By("Deleting StagedGlobalNetworkPolicy (name2) for KDD that does not support TTL") dres, outError = c.StagedGlobalNetworkPolicies().Delete(ctx, name2, options.DeleteOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(dres).To(MatchResource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, tieredGNPName(name2, tier), spec2)) + Expect(dres).To(MatchResource(apiv3.KindStagedGlobalNetworkPolicy, testutils.ExpectNoNamespace, name2, spec2)) time.Sleep(1 * time.Second) } By("Attempting to delete StagedGlobalNetworkPolicy (name2) again") _, outError = c.StagedGlobalNetworkPolicies().Delete(ctx, name2, options.DeleteOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedGlobalNetworkPolicy(" + tieredGNPName(name2, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedGlobalNetworkPolicy(" + name2 + ") with error:")) By("Listing all StagedGlobalNetworkPolicies and expecting no items") outList, outError = c.StagedGlobalNetworkPolicies().List(ctx, options.ListOptions{}) @@ -327,7 +326,7 @@ var _ = testutils.E2eDatastoreDescribe("StagedGlobalNetworkPolicy tests", testut By("Getting StagedGlobalNetworkPolicy (name2) and expecting an error") _, outError = c.StagedGlobalNetworkPolicies().Get(ctx, name2, options.GetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedGlobalNetworkPolicy(" + tieredGNPName(name2, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedGlobalNetworkPolicy(" + name2 + ") with error:")) }, // Pass two fully populated StagedGlobalNetworkPolicySpecs in a tier, and expect the series of operations to succeed. @@ -341,7 +340,7 @@ var _ = testutils.E2eDatastoreDescribe("StagedGlobalNetworkPolicy tests", testut ) DescribeTable("StagedGlobalNetworkPolicy default tier name test", - func(policyName string, incorrectPrefixPolicyName string) { + func(policyName string, prefixPolicyName string) { By("Getting the policy before it was created") _, err := c.StagedGlobalNetworkPolicies().Get(ctx, policyName, options.GetOptions{}) Expect(err).To(HaveOccurred()) @@ -363,12 +362,12 @@ var _ = testutils.E2eDatastoreDescribe("StagedGlobalNetworkPolicy tests", testut Expect(err).ToNot(HaveOccurred()) Expect(returnedPolicy.Name).To(Equal(policyName)) - By("Creating the policy with incorrect prefix name") + By("Creating the policy with prefix name") _, err = c.StagedGlobalNetworkPolicies().Create(ctx, &apiv3.StagedGlobalNetworkPolicy{ - ObjectMeta: metav1.ObjectMeta{Name: incorrectPrefixPolicyName}, + ObjectMeta: metav1.ObjectMeta{Name: prefixPolicyName}, }, options.SetOptions{}) - Expect(err).To(HaveOccurred()) + Expect(err).NotTo(HaveOccurred()) By("Getting the policy") returnedPolicy, err = c.StagedGlobalNetworkPolicies().Get(ctx, policyName, options.GetOptions{}) @@ -380,54 +379,21 @@ var _ = testutils.E2eDatastoreDescribe("StagedGlobalNetworkPolicy tests", testut Expect(err).ToNot(HaveOccurred()) Expect(returnedPolicy.Name).To(Equal(policyName)) - By("Getting the policy with incorrect prefix") - _, err = c.StagedGlobalNetworkPolicies().Get(ctx, incorrectPrefixPolicyName, options.GetOptions{}) - Expect(err).To(HaveOccurred()) - - By("Updating the policy with incorrect prefix") - _, err = c.StagedGlobalNetworkPolicies().Update(ctx, &apiv3.StagedGlobalNetworkPolicy{ - ObjectMeta: metav1.ObjectMeta{Name: incorrectPrefixPolicyName, ResourceVersion: "1234", CreationTimestamp: metav1.Now(), UID: uid}, - Spec: spec1, - }, options.SetOptions{}) - Expect(err).To(HaveOccurred()) + By("Getting the policy with prefix") + _, err = c.StagedGlobalNetworkPolicies().Get(ctx, prefixPolicyName, options.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) By("Deleting policy") returnedPolicy, err = c.StagedGlobalNetworkPolicies().Delete(ctx, policyName, options.DeleteOptions{}) Expect(returnedPolicy.Name).To(Equal(policyName)) - Expect(err).ToNot(HaveOccurred()) + Expect(err).NotTo(HaveOccurred()) + _, err = c.StagedGlobalNetworkPolicies().Delete(ctx, prefixPolicyName, options.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) }, Entry("StagedGlobalNetworkPolicy without default tier prefix", "netpol", "default.netpol"), Entry("StagedGlobalNetworkPolicy with default tier prefix", "default.netpol", "netpol"), ) - Describe("StagedGlobalNetworkPolicy without name on the projectcalico.org annotation", func() { - It("Should return the name without default prefix", func() { - if config.Spec.DatastoreType == apiconfig.Kubernetes { - config, _, err := k8s.CreateKubernetesClientset(&config.Spec) - Expect(err).NotTo(HaveOccurred()) - config.ContentType = "application/json" - cli, err := ctrlclient.New(config, ctrlclient.Options{}) - Expect(err).NotTo(HaveOccurred()) - - // Create v1 crd with empty metadata annotation name - annotations := map[string]string{} - annotations["projectcalico.org/metadata"] = "{}" - policy := &apiv3.StagedGlobalNetworkPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: annotations, - Name: "default.prefix-test-policy"}, - Spec: apiv3.StagedGlobalNetworkPolicySpec{}, - } - err = cli.Create(context.Background(), policy) - Expect(err).NotTo(HaveOccurred()) - - // We should be able to get it without the default. prefix - _, err = c.StagedGlobalNetworkPolicies().Get(ctx, "prefix-test-policy", options.GetOptions{}) - Expect(err).ToNot(HaveOccurred()) - } - }) - }) - DescribeTable("StagedGlobalNetworkPolicy name validation tests", func(policyName string, tier string, expectError bool) { if tier != "default" { @@ -444,7 +410,8 @@ var _ = testutils.E2eDatastoreDescribe("StagedGlobalNetworkPolicy tests", testut _, err := c.StagedGlobalNetworkPolicies().Create(ctx, &apiv3.StagedGlobalNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ - Name: policyName}, + Name: policyName, + }, Spec: apiv3.StagedGlobalNetworkPolicySpec{ Tier: tier, }, @@ -456,11 +423,13 @@ var _ = testutils.E2eDatastoreDescribe("StagedGlobalNetworkPolicy tests", testut Expect(err).ToNot(HaveOccurred()) } }, + + // We don't enforce any name restrictions. Entry("StagedGlobalNetworkPolicy in default tier without prefix", "netpol", "default", false), Entry("StagedGlobalNetworkPolicy in default tier with prefix", "default.netpol", "default", false), - Entry("StagedGlobalNetworkPolicy in custom tier with correct prefix", "tier1.netpol", "tier1", false), - Entry("StagedGlobalNetworkPolicy in custom tier without prefix", "netpol", "tier1", true), - Entry("StagedGlobalNetworkPolicy in custom tier with incorrect prefix", "tier1.netpol", "tier2", true), + Entry("StagedGlobalNetworkPolicy in custom tier with tier prefix", "tier1.netpol", "tier1", false), + Entry("StagedGlobalNetworkPolicy in custom tier without prefix", "netpol", "tier1", false), + Entry("StagedGlobalNetworkPolicy in custom tier with mismatched prefix", "tier1.netpol", "tier2", false), ) Describe("StagedGlobalNetworkPolicy watch functionality", func() { diff --git a/libcalico-go/lib/clientv3/stagedkubernetesnetworkpolicy_e2e_test.go b/libcalico-go/lib/clientv3/stagedkubernetesnetworkpolicy_e2e_test.go index f49193d798d..83477e3008b 100644 --- a/libcalico-go/lib/clientv3/stagedkubernetesnetworkpolicy_e2e_test.go +++ b/libcalico-go/lib/clientv3/stagedkubernetesnetworkpolicy_e2e_test.go @@ -18,8 +18,7 @@ import ( "context" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -690,6 +689,6 @@ var _ = testutils.E2eDatastoreDescribe("StagedKubernetesNetworkPolicy tests", te Expect(np.GetName()).To(Equal(name)) } }, - nameNormalizationTests..., + nameNormalizationTests, ) }) diff --git a/libcalico-go/lib/clientv3/stagednetworkpolicy.go b/libcalico-go/lib/clientv3/stagednetworkpolicy.go index 39c5f64911a..8c284c9bb4b 100644 --- a/libcalico-go/lib/clientv3/stagednetworkpolicy.go +++ b/libcalico-go/lib/clientv3/stagednetworkpolicy.go @@ -16,13 +16,11 @@ package clientv3 import ( "context" - "fmt" - "strings" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "github.com/projectcalico/api/pkg/defaults" log "github.com/sirupsen/logrus" - cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" "github.com/projectcalico/calico/libcalico-go/lib/names" "github.com/projectcalico/calico/libcalico-go/lib/options" validator "github.com/projectcalico/calico/libcalico-go/lib/validator/v3" @@ -60,44 +58,23 @@ func (r stagedNetworkPolicies) Create(ctx context.Context, res *apiv3.StagedNetw resCopy := *res res = &resCopy } - if res.Spec.StagedAction != apiv3.StagedActionDelete { - defaultPolicyTypesField(res.Spec.Ingress, res.Spec.Egress, &res.Spec.Types) - } else { - res.Spec.Types = []apiv3.PolicyType(nil) + // Run defaulting logic. + if _, err := defaults.Default(res); err != nil { + return nil, err } - - if strings.HasPrefix(res.GetObjectMeta().GetName(), names.K8sNetworkPolicyNamePrefix) { - // We don't support Create of a StagedNetworkPolicy with such prefix - return nil, cerrors.ErrorOperationNotSupported{ - Identifier: names.K8sNetworkPolicyNamePrefix, - Operation: "Create", - Reason: "Cannot create a StagedNetworkPolicy with that name prefix", - } + if res.Spec.StagedAction == apiv3.StagedActionDelete { + res.Spec.Types = nil } if err := validator.Validate(res); err != nil { return nil, err } - err := names.ValidateTieredPolicyName(res.Name, tier) - if err != nil { - return nil, err - } - - // Add tier labels to policy for lookup. - if tier != "default" { - res.GetObjectMeta().SetLabels(addTierLabel(res.GetObjectMeta().GetLabels(), tier)) - } out, err := r.client.resources.Create(ctx, opts, apiv3.KindStagedNetworkPolicy, res) if out != nil { - // Add the tier labels if necessary - out.GetObjectMeta().SetLabels(defaultTierLabelIfMissing(out.GetObjectMeta().GetLabels())) return out.(*apiv3.StagedNetworkPolicy), err } - // Add the tier labels if necessary - res.GetObjectMeta().SetLabels(defaultTierLabelIfMissing(res.GetObjectMeta().GetLabels())) - return nil, err } @@ -111,59 +88,28 @@ func (r stagedNetworkPolicies) Update(ctx context.Context, res *apiv3.StagedNetw res = &resCopy } - if res.Spec.StagedAction != apiv3.StagedActionDelete { - defaultPolicyTypesField(res.Spec.Ingress, res.Spec.Egress, &res.Spec.Types) - } else { - res.Spec.Types = []apiv3.PolicyType(nil) + // Run defaulting logic. + if _, err := defaults.Default(res); err != nil { + return nil, err } - - if strings.HasPrefix(res.GetObjectMeta().GetName(), names.K8sNetworkPolicyNamePrefix) { - // We don't support Create of a StagedNetworkPolicy with such prefix - return nil, cerrors.ErrorOperationNotSupported{ - Identifier: names.K8sNetworkPolicyNamePrefix, - Operation: "Update", - Reason: "Cannot creaupdatete a StagedNetworkPolicy with that name prefix", - } + if res.Spec.StagedAction == apiv3.StagedActionDelete { + res.Spec.Types = nil } if err := validator.Validate(res); err != nil { return nil, err } - err := names.ValidateTieredPolicyName(res.Name, res.Spec.Tier) - if err != nil { - return nil, err - } - - // Add tier labels to policy for lookup. - tier := names.TierOrDefault(res.Spec.Tier) - if tier != "default" { - res.GetObjectMeta().SetLabels(addTierLabel(res.GetObjectMeta().GetLabels(), tier)) - } out, err := r.client.resources.Update(ctx, opts, apiv3.KindStagedNetworkPolicy, res) if out != nil { - // Add the tier labels if necessary - out.GetObjectMeta().SetLabels(defaultTierLabelIfMissing(out.GetObjectMeta().GetLabels())) return out.(*apiv3.StagedNetworkPolicy), err } - // Add the tier labels if necessary - res.GetObjectMeta().SetLabels(defaultTierLabelIfMissing(res.GetObjectMeta().GetLabels())) - return nil, err } // Delete takes name of the StagedNetworkPolicy and deletes it. Returns an error if one occurs. func (r stagedNetworkPolicies) Delete(ctx context.Context, namespace, name string, opts options.DeleteOptions) (*apiv3.StagedNetworkPolicy, error) { - if strings.HasPrefix(name, names.K8sNetworkPolicyNamePrefix) { - // We don't support Create of a StagedNetworkPolicy with such prefix - return nil, cerrors.ErrorOperationNotSupported{ - Identifier: names.K8sNetworkPolicyNamePrefix, - Operation: "Delete", - Reason: "No staged network policies should be available to be deleted for the knp prefix", - } - } - out, err := r.client.resources.Delete(ctx, opts, apiv3.KindStagedNetworkPolicy, namespace, name) if out != nil { // Add the tier labels if necessary @@ -180,21 +126,7 @@ func (r stagedNetworkPolicies) Get(ctx context.Context, namespace, name string, if out != nil { // Add the tier labels if necessary out.GetObjectMeta().SetLabels(defaultTierLabelIfMissing(out.GetObjectMeta().GetLabels())) - // Fill in the tier information from the policy name if we find it missing. - // We expect backend policies to have the right name (prefixed with tier name). - resOut := out.(*apiv3.StagedNetworkPolicy) - if resOut.Spec.Tier == "" { - tier, tierErr := names.TierFromPolicyName(resOut.Name) - if tierErr != nil { - log.WithError(tierErr).Infof("Skipping setting tier for name %v", resOut.Name) - return resOut, tierErr - } - resOut.Spec.Tier = tier - } - if resOut.Name != name { - return nil, fmt.Errorf("resource not found GlobalNetworkPolicy(%s)", name) - } - return resOut, err + return out.(*apiv3.StagedNetworkPolicy), err } return nil, err } @@ -202,11 +134,6 @@ func (r stagedNetworkPolicies) Get(ctx context.Context, namespace, name string, // List returns the list of StagedNetworkPolicy objects that match the supplied options. func (r stagedNetworkPolicies) List(ctx context.Context, opts options.ListOptions) (*apiv3.StagedNetworkPolicyList, error) { res := &apiv3.StagedNetworkPolicyList{} - // Add the name prefix if name is provided - if opts.Name != "" && !opts.Prefix { - opts.Name = names.TieredPolicyName(opts.Name) - } - if err := r.client.resources.List(ctx, opts, apiv3.KindStagedNetworkPolicy, apiv3.KindStagedNetworkPolicyList, res); err != nil { return nil, err } @@ -214,16 +141,6 @@ func (r stagedNetworkPolicies) List(ctx context.Context, opts options.ListOption // Make sure the tier labels are added for i := range res.Items { res.Items[i].GetObjectMeta().SetLabels(defaultTierLabelIfMissing(res.Items[i].GetObjectMeta().GetLabels())) - // Fill in the tier information from the policy name if we find it missing. - // We expect backend policies to have the right name (prefixed with tier name). - if res.Items[i].Spec.Tier == "" { - tier, tierErr := names.TierFromPolicyName(res.Items[i].Name) - if tierErr != nil { - log.WithError(tierErr).Infof("Skipping setting tier for name %v", res.Items[i].Name) - continue - } - res.Items[i].Spec.Tier = tier - } } return res, nil @@ -232,10 +149,5 @@ func (r stagedNetworkPolicies) List(ctx context.Context, opts options.ListOption // Watch returns a watch.Interface that watches the stagedNetworkPolicies that match the // supplied options. func (r stagedNetworkPolicies) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) { - // Add the name prefix if name is provided - if opts.Name != "" { - opts.Name = names.TieredPolicyName(opts.Name) - } - return r.client.resources.Watch(ctx, opts, apiv3.KindStagedNetworkPolicy, &policyConverter{}) } diff --git a/libcalico-go/lib/clientv3/stagednetworkpolicy_e2e_test.go b/libcalico-go/lib/clientv3/stagednetworkpolicy_e2e_test.go index d821cb23b94..164b262b8c3 100644 --- a/libcalico-go/lib/clientv3/stagednetworkpolicy_e2e_test.go +++ b/libcalico-go/lib/clientv3/stagednetworkpolicy_e2e_test.go @@ -18,13 +18,11 @@ import ( "context" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" "github.com/projectcalico/calico/libcalico-go/lib/backend" @@ -78,6 +76,8 @@ var _ = testutils.E2eDatastoreDescribe("StagedNetworkPolicy tests", testutils.Da var be bapi.Client BeforeEach(func() { + v3CRD = k8s.UsingV3CRDs(&config.Spec) + var err error c, err = clientv3.New(config) Expect(err).NotTo(HaveOccurred()) @@ -116,7 +116,7 @@ var _ = testutils.E2eDatastoreDescribe("StagedNetworkPolicy tests", testutils.Da Spec: spec1, }, options.SetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedNetworkPolicy(" + tieredNetworkPolicyName(namespace1, name1, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedNetworkPolicy(" + namespacedName(namespace1, name1) + ") with error:")) if config.Spec.DatastoreType == apiconfig.Kubernetes { By("Creating the StagedNetworkPolicy in a non-existing namespace") @@ -125,7 +125,7 @@ var _ = testutils.E2eDatastoreDescribe("StagedNetworkPolicy tests", testutils.Da Spec: spec1, }, options.SetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedNetworkPolicy(" + tieredNetworkPolicyName("non-existing", name1, tier) + ") with error: namespaces \"non-existing\" not found")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedNetworkPolicy(" + namespacedName("non-existing", name1) + ") with error: namespaces \"non-existing\" not found")) } By("Attempting to creating a new StagedNetworkPolicy with name1/spec1 and a non-empty ResourceVersion") @@ -153,24 +153,24 @@ var _ = testutils.E2eDatastoreDescribe("StagedNetworkPolicy tests", testutils.Da Spec: spec2, }, options.SetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource already exists: StagedNetworkPolicy(" + tieredNetworkPolicyName(namespace1, name1, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource already exists: StagedNetworkPolicy(" + namespacedName(namespace1, name1) + ") with error:")) By("Getting StagedNetworkPolicy (name1) and comparing the output against spec1") res, outError := c.StagedNetworkPolicies().Get(ctx, namespace1, name1, options.GetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res).To(MatchResource(apiv3.KindStagedNetworkPolicy, namespace1, tieredPolicyName(name1, tier), spec1)) + Expect(res).To(MatchResource(apiv3.KindStagedNetworkPolicy, namespace1, name1, spec1)) Expect(res.ResourceVersion).To(Equal(res1.ResourceVersion)) By("Getting StagedNetworkPolicy (name2) before it is created") _, outError = c.StagedNetworkPolicies().Get(ctx, namespace2, name2, options.GetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedNetworkPolicy(" + tieredNetworkPolicyName(namespace2, name2, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedNetworkPolicy(" + namespacedName(namespace2, name2) + ") with error:")) By("Listing all the NetworkPolicies in namespace1, expecting a single result with name1/spec1") outList, outError := c.StagedNetworkPolicies().List(ctx, options.ListOptions{Namespace: namespace1}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(apiv3.KindStagedNetworkPolicy, namespace1, tieredPolicyName(name1, tier), spec1), + testutils.Resource(apiv3.KindStagedNetworkPolicy, namespace1, name1, spec1), )) By("Creating a new StagedNetworkPolicy with name2/spec2") @@ -180,34 +180,34 @@ var _ = testutils.E2eDatastoreDescribe("StagedNetworkPolicy tests", testutils.Da Spec: spec2, }, options.SetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res2).To(MatchResource(apiv3.KindStagedNetworkPolicy, namespace2, tieredPolicyName(name2, tier), spec2)) + Expect(res2).To(MatchResource(apiv3.KindStagedNetworkPolicy, namespace2, name2, spec2)) By("Getting StagedNetworkPolicy (name2) and comparing the output against spec2") res, outError = c.StagedNetworkPolicies().Get(ctx, namespace2, name2, options.GetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res).To(MatchResource(apiv3.KindStagedNetworkPolicy, namespace2, tieredPolicyName(name2, tier), spec2)) + Expect(res).To(MatchResource(apiv3.KindStagedNetworkPolicy, namespace2, name2, spec2)) Expect(res.ResourceVersion).To(Equal(res2.ResourceVersion)) By("Listing all the NetworkPolicies using an empty namespace (all-namespaces), expecting a two results with name1/spec1 and name2/spec2") outList, outError = c.StagedNetworkPolicies().List(ctx, options.ListOptions{}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(apiv3.KindStagedNetworkPolicy, namespace1, tieredPolicyName(name1, tier), spec1), - testutils.Resource(apiv3.KindStagedNetworkPolicy, namespace2, tieredPolicyName(name2, tier), spec2), + testutils.Resource(apiv3.KindStagedNetworkPolicy, namespace1, name1, spec1), + testutils.Resource(apiv3.KindStagedNetworkPolicy, namespace2, name2, spec2), )) By("Listing all the NetworkPolicies in namespace2, expecting a one results with name2/spec2") outList, outError = c.StagedNetworkPolicies().List(ctx, options.ListOptions{Namespace: namespace2}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(apiv3.KindStagedNetworkPolicy, namespace2, tieredPolicyName(name2, tier), spec2), + testutils.Resource(apiv3.KindStagedNetworkPolicy, namespace2, name2, spec2), )) By("Updating StagedNetworkPolicy name1 with spec2") res1.Spec = spec2 res1, outError = c.StagedNetworkPolicies().Update(ctx, res1, options.SetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res1).To(MatchResource(apiv3.KindStagedNetworkPolicy, namespace1, tieredPolicyName(name1, tier), spec2)) + Expect(res1).To(MatchResource(apiv3.KindStagedNetworkPolicy, namespace1, name1, spec2)) By("Attempting to update the StagedNetworkPolicy without a Creation Timestamp") res, outError = c.StagedNetworkPolicies().Update(ctx, &apiv3.StagedNetworkPolicy{ @@ -242,20 +242,20 @@ var _ = testutils.E2eDatastoreDescribe("StagedNetworkPolicy tests", testutils.Da res1.ResourceVersion = rv1_1 _, outError = c.StagedNetworkPolicies().Update(ctx, res1, options.SetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(Equal("update conflict: StagedNetworkPolicy(" + tieredNetworkPolicyName(namespace1, name1, tier) + ")")) + Expect(outError.Error()).To(Equal("update conflict: StagedNetworkPolicy(" + namespacedName(namespace1, name1) + ")")) if config.Spec.DatastoreType != apiconfig.Kubernetes { By("Getting StagedNetworkPolicy (name1) with the original resource version and comparing the output against spec1") res, outError = c.StagedNetworkPolicies().Get(ctx, namespace1, name1, options.GetOptions{ResourceVersion: rv1_1}) Expect(outError).NotTo(HaveOccurred()) - Expect(res).To(MatchResource(apiv3.KindStagedNetworkPolicy, namespace1, tieredPolicyName(name1, tier), spec1)) + Expect(res).To(MatchResource(apiv3.KindStagedNetworkPolicy, namespace1, name1, spec1)) Expect(res.ResourceVersion).To(Equal(rv1_1)) } By("Getting StagedNetworkPolicy (name1) with the updated resource version and comparing the output against spec2") res, outError = c.StagedNetworkPolicies().Get(ctx, namespace1, name1, options.GetOptions{ResourceVersion: rv1_2}) Expect(outError).NotTo(HaveOccurred()) - Expect(res).To(MatchResource(apiv3.KindStagedNetworkPolicy, namespace1, tieredPolicyName(name1, tier), spec2)) + Expect(res).To(MatchResource(apiv3.KindStagedNetworkPolicy, namespace1, name1, spec2)) Expect(res.ResourceVersion).To(Equal(rv1_2)) if config.Spec.DatastoreType != apiconfig.Kubernetes { @@ -263,7 +263,7 @@ var _ = testutils.E2eDatastoreDescribe("StagedNetworkPolicy tests", testutils.Da outList, outError = c.StagedNetworkPolicies().List(ctx, options.ListOptions{Namespace: namespace1, ResourceVersion: rv1_1}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(apiv3.KindStagedNetworkPolicy, namespace1, tieredPolicyName(name1, tier), spec1), + testutils.Resource(apiv3.KindStagedNetworkPolicy, namespace1, name1, spec1), )) } @@ -271,21 +271,21 @@ var _ = testutils.E2eDatastoreDescribe("StagedNetworkPolicy tests", testutils.Da outList, outError = c.StagedNetworkPolicies().List(ctx, options.ListOptions{}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(apiv3.KindStagedNetworkPolicy, namespace1, tieredPolicyName(name1, tier), spec2), - testutils.Resource(apiv3.KindStagedNetworkPolicy, namespace2, tieredPolicyName(name2, tier), spec2), + testutils.Resource(apiv3.KindStagedNetworkPolicy, namespace1, name1, spec2), + testutils.Resource(apiv3.KindStagedNetworkPolicy, namespace2, name2, spec2), )) if config.Spec.DatastoreType != apiconfig.Kubernetes { By("Deleting StagedNetworkPolicy (name1) with the old resource version") _, outError = c.StagedNetworkPolicies().Delete(ctx, namespace1, name1, options.DeleteOptions{ResourceVersion: rv1_1}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(Equal("update conflict: StagedNetworkPolicy(" + tieredNetworkPolicyName(namespace1, name1, tier) + ")")) + Expect(outError.Error()).To(Equal("update conflict: StagedNetworkPolicy(" + namespacedName(namespace1, name1) + ")")) } By("Deleting StagedNetworkPolicy (name1) with the new resource version") dres, outError := c.StagedNetworkPolicies().Delete(ctx, namespace1, name1, options.DeleteOptions{ResourceVersion: rv1_2}) Expect(outError).NotTo(HaveOccurred()) - Expect(dres).To(MatchResource(apiv3.KindStagedNetworkPolicy, namespace1, tieredPolicyName(name1, tier), spec2)) + Expect(dres).To(MatchResource(apiv3.KindStagedNetworkPolicy, namespace1, name1, spec2)) if config.Spec.DatastoreType != apiconfig.Kubernetes { By("Updating StagedNetworkPolicy name2 with a 2s TTL and waiting for the entry to be deleted") @@ -297,7 +297,7 @@ var _ = testutils.E2eDatastoreDescribe("StagedNetworkPolicy tests", testutils.Da time.Sleep(2 * time.Second) _, outError = c.StagedNetworkPolicies().Get(ctx, namespace2, name2, options.GetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedNetworkPolicy(" + tieredNetworkPolicyName(namespace2, name2, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedNetworkPolicy(" + namespacedName(namespace2, name2) + ") with error:")) By("Creating StagedNetworkPolicy name2 with a 2s TTL and waiting for the entry to be deleted") _, outError = c.StagedNetworkPolicies().Create(ctx, &apiv3.StagedNetworkPolicy{ @@ -311,20 +311,20 @@ var _ = testutils.E2eDatastoreDescribe("StagedNetworkPolicy tests", testutils.Da time.Sleep(2 * time.Second) _, outError = c.StagedNetworkPolicies().Get(ctx, namespace2, name2, options.GetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedNetworkPolicy(" + tieredNetworkPolicyName(namespace2, name2, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedNetworkPolicy(" + namespacedName(namespace2, name2) + ") with error:")) } if config.Spec.DatastoreType == apiconfig.Kubernetes { By("Attempting to deleting StagedNetworkPolicy (name2) again") dres, outError = c.StagedNetworkPolicies().Delete(ctx, namespace2, name2, options.DeleteOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(dres).To(MatchResource(apiv3.KindStagedNetworkPolicy, namespace2, tieredPolicyName(name2, tier), spec2)) + Expect(dres).To(MatchResource(apiv3.KindStagedNetworkPolicy, namespace2, name2, spec2)) } By("Attempting to delete StagedNetworkPolicy (name2) again") _, outError = c.StagedNetworkPolicies().Delete(ctx, namespace2, name2, options.DeleteOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedNetworkPolicy(" + tieredNetworkPolicyName(namespace2, name2, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedNetworkPolicy(" + namespacedName(namespace2, name2) + ") with error:")) By("Listing all NetworkPolicies and expecting no items") outList, outError = c.StagedNetworkPolicies().List(ctx, options.ListOptions{}) @@ -334,7 +334,7 @@ var _ = testutils.E2eDatastoreDescribe("StagedNetworkPolicy tests", testutils.Da By("Getting StagedNetworkPolicy (name2) and expecting an error") _, outError = c.StagedNetworkPolicies().Get(ctx, namespace2, name2, options.GetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedNetworkPolicy(" + tieredNetworkPolicyName(namespace2, name2, tier) + ") with error:")) + Expect(outError.Error()).To(ContainSubstring("resource does not exist: StagedNetworkPolicy(" + namespacedName(namespace2, name2) + ") with error:")) }, // Pass two fully populated PolicySpecs and expect the series of operations to succeed. @@ -372,7 +372,7 @@ var _ = testutils.E2eDatastoreDescribe("StagedNetworkPolicy tests", testutils.Da ) DescribeTable("StagedNetworkPolicy default tier name test", - func(policyName string, incorrectPrefixPolicyName string) { + func(policyName string, prefixedPolicyName string) { namespace := "default" By("Getting the policy before it was created") _, err := c.StagedNetworkPolicies().Get(ctx, namespace, policyName, options.GetOptions{}) @@ -395,12 +395,12 @@ var _ = testutils.E2eDatastoreDescribe("StagedNetworkPolicy tests", testutils.Da Expect(err).ToNot(HaveOccurred()) Expect(returnedPolicy.Name).To(Equal(policyName)) - By("Creating the policy with incorrect prefix name") + By("Creating another policy with prefixed name") _, err = c.StagedNetworkPolicies().Create(ctx, &apiv3.StagedNetworkPolicy{ - ObjectMeta: metav1.ObjectMeta{Name: incorrectPrefixPolicyName, Namespace: namespace}, + ObjectMeta: metav1.ObjectMeta{Name: prefixedPolicyName, Namespace: namespace}, }, options.SetOptions{}) - Expect(err).To(HaveOccurred()) + Expect(err).NotTo(HaveOccurred()) By("Getting the policy") returnedPolicy, err = c.StagedNetworkPolicies().Get(ctx, namespace, policyName, options.GetOptions{}) @@ -412,56 +412,21 @@ var _ = testutils.E2eDatastoreDescribe("StagedNetworkPolicy tests", testutils.Da Expect(err).ToNot(HaveOccurred()) Expect(returnedPolicy.Name).To(Equal(policyName)) - By("Getting the policy with incorrect prefix") - _, err = c.StagedNetworkPolicies().Get(ctx, namespace, incorrectPrefixPolicyName, options.GetOptions{}) - Expect(err).To(HaveOccurred()) - - By("Updating the policy with incorrect prefix") - _, err = c.StagedNetworkPolicies().Update(ctx, &apiv3.StagedNetworkPolicy{ - ObjectMeta: metav1.ObjectMeta{Name: incorrectPrefixPolicyName, ResourceVersion: "1234", CreationTimestamp: metav1.Now(), UID: uid}, - Spec: spec1, - }, options.SetOptions{}) - Expect(err).To(HaveOccurred()) + By("Getting the policy with prefix") + _, err = c.StagedNetworkPolicies().Get(ctx, namespace, prefixedPolicyName, options.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) By("Deleting policy") returnedPolicy, err = c.StagedNetworkPolicies().Delete(ctx, namespace, policyName, options.DeleteOptions{}) Expect(returnedPolicy.Name).To(Equal(policyName)) Expect(err).ToNot(HaveOccurred()) + _, err = c.StagedNetworkPolicies().Delete(ctx, namespace, prefixedPolicyName, options.DeleteOptions{}) + Expect(err).ToNot(HaveOccurred()) }, Entry("StagedNetworkPolicy without default tier prefix", "netpol", "default.netpol"), Entry("StagedNetworkPolicy with default tier prefix", "default.netpol", "netpol"), ) - Describe("StagedNetworkPolicy without name on the projectcalico.org annotation", func() { - It("Should return the name without default prefix", func() { - if config.Spec.DatastoreType == apiconfig.Kubernetes { - config, _, err := k8s.CreateKubernetesClientset(&config.Spec) - Expect(err).NotTo(HaveOccurred()) - config.ContentType = "application/json" - cli, err := ctrlclient.New(config, ctrlclient.Options{}) - Expect(err).NotTo(HaveOccurred()) - - // Create v1 crd with empty metadata annotation name - annotations := map[string]string{} - annotations["projectcalico.org/metadata"] = "{}" - policy := &apiv3.StagedNetworkPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: annotations, - Name: "default.prefix-test-policy", - Namespace: "default", - }, - Spec: apiv3.StagedNetworkPolicySpec{}, - } - err = cli.Create(context.Background(), policy) - Expect(err).NotTo(HaveOccurred()) - - // We should be able to get it without the default. prefix - _, err = c.StagedNetworkPolicies().Get(ctx, "default", "prefix-test-policy", options.GetOptions{}) - Expect(err).ToNot(HaveOccurred()) - } - }) - }) - DescribeTable("StagedNetworkPolicy name validation tests", func(policyName string, tier string, expectError bool) { namespace := "default" @@ -481,7 +446,8 @@ var _ = testutils.E2eDatastoreDescribe("StagedNetworkPolicy tests", testutils.Da &apiv3.StagedNetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: policyName, - Namespace: namespace}, + Namespace: namespace, + }, Spec: apiv3.StagedNetworkPolicySpec{ Tier: tier, }, @@ -492,12 +458,20 @@ var _ = testutils.E2eDatastoreDescribe("StagedNetworkPolicy tests", testutils.Da } else { Expect(err).ToNot(HaveOccurred()) } + + // Should be gettable if created successfully. + if !expectError { + _, err = c.StagedNetworkPolicies().Get(ctx, namespace, policyName, options.GetOptions{}) + Expect(err).ToNot(HaveOccurred()) + } }, + + // These should all pass, since we don't restrict name structure. Entry("StagedNetworkPolicy in default tier without prefix", "netpol", "default", false), Entry("StagedNetworkPolicy in default tier with prefix", "default.netpol", "default", false), - Entry("StagedNetworkPolicy in custom tier with correct prefix", "tier1.netpol", "tier1", false), - Entry("StagedNetworkPolicy in custom tier without prefix", "netpol", "tier1", true), - Entry("StagedNetworkPolicy in custom tier with incorrect prefix", "tier1.netpol", "tier2", true), + Entry("StagedNetworkPolicy in custom tier with matching tier prefix", "tier1.netpol", "tier1", false), + Entry("StagedNetworkPolicy in custom tier without prefix", "netpol", "tier1", false), + Entry("StagedNetworkPolicy in custom tier with other tier prefix", "tier1.netpol", "tier2", false), ) Describe("StagedNetworkPolicy watch functionality", func() { @@ -757,6 +731,6 @@ var _ = testutils.E2eDatastoreDescribe("StagedNetworkPolicy tests", testutils.Da Expect(np.GetName()).To(Equal(name)) } }, - nameNormalizationTests..., + nameNormalizationTests, ) }) diff --git a/libcalico-go/lib/clientv3/tier.go b/libcalico-go/lib/clientv3/tier.go index 62b7417a076..58b4de5a70d 100644 --- a/libcalico-go/lib/clientv3/tier.go +++ b/libcalico-go/lib/clientv3/tier.go @@ -86,7 +86,7 @@ func (r tiers) Update(ctx context.Context, res *apiv3.Tier, opts options.SetOpti // Delete takes name of the Tier and deletes it. Returns an error if one occurs. func (r tiers) Delete(ctx context.Context, name string, opts options.DeleteOptions) (*apiv3.Tier, error) { - if name == names.DefaultTierName || name == names.AdminNetworkPolicyTierName { + if names.TierIsStatic(name) { return nil, cerrors.ErrorOperationNotSupported{ Identifier: name, Operation: "Delete", @@ -94,13 +94,8 @@ func (r tiers) Delete(ctx context.Context, name string, opts options.DeleteOptio } } - // List the (Staged)NetworkPolicy and (Staged)GlobalNetworkPolicy resources that are prefixed with this tier name. - // Note that a prefix matching may return additional results that are not actually in this tier, - // so we also need to check the spec field to be certain. - policyListOptions := options.ListOptions{ - Prefix: true, - Name: name + ".", - } + // List all policies, and checks none of them reference this tier. + policyListOptions := options.ListOptions{} // Check NetworkPolicy resources. if npList, err := r.client.NetworkPolicies().List(ctx, policyListOptions); err != nil { diff --git a/libcalico-go/lib/clientv3/tier_e2e_test.go b/libcalico-go/lib/clientv3/tier_e2e_test.go index ca1084cdaab..e5a7e86ee39 100644 --- a/libcalico-go/lib/clientv3/tier_e2e_test.go +++ b/libcalico-go/lib/clientv3/tier_e2e_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Tigera, Inc. All rights reserved. +// Copyright (c) 2024-2025 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,8 +18,7 @@ import ( "context" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/format" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" @@ -63,15 +62,14 @@ var _ = testutils.E2eDatastoreDescribe("Tier tests", testutils.DatastoreAll, fun Order: &defaultOrder, DefaultAction: &actionDeny, } - anpOrder := apiv3.AdminNetworkPolicyTierOrder - anpSpec := apiv3.TierSpec{ - Order: &anpOrder, + kcnpAdminTierOrder := apiv3.KubeAdminTierOrder + kcnpAdminSpec := apiv3.TierSpec{ + Order: &kcnpAdminTierOrder, DefaultAction: &actionPass, } - - banpOrder := apiv3.BaselineAdminNetworkPolicyTierOrder - banpSpec := apiv3.TierSpec{ - Order: &banpOrder, + kcnpBaselineTierOrder := apiv3.KubeBaselineTierOrder + kcnpBaselineSpec := apiv3.TierSpec{ + Order: &kcnpBaselineTierOrder, DefaultAction: &actionPass, } @@ -190,30 +188,55 @@ var _ = testutils.E2eDatastoreDescribe("Tier tests", testutils.DatastoreAll, fun Expect(outError).To(HaveOccurred()) Expect(outError.Error()).Should(ContainSubstring("default tier order must be 1e+06")) - By("Creating the adminnetworkpolicy tier with an invalid order") + By("Creating the kube-admin tier with an invalid order") + res, outError = c.Tiers().Create(ctx, &apiv3.Tier{ + ObjectMeta: metav1.ObjectMeta{Name: names.KubeAdminTierName}, + Spec: spec1, + }, options.SetOptions{}) + Expect(res).To(BeNil()) + Expect(outError).To(HaveOccurred()) + Expect(outError.Error()).Should(ContainSubstring("kube-admin tier order must be 1000")) + + By("Cannot delete the kube-admin Tier") + _, outError = c.Tiers().Delete(ctx, names.KubeAdminTierName, options.DeleteOptions{}) + Expect(outError).To(HaveOccurred()) + Expect(outError.Error()).To(Equal("operation Delete is not supported on kube-admin: Cannot delete kube-admin tier")) + + By("Getting kube-admin Tier") + defRes, outError = c.Tiers().Get(ctx, names.KubeAdminTierName, options.GetOptions{}) + Expect(outError).NotTo(HaveOccurred()) + Expect(defRes).To(MatchResource(apiv3.KindTier, testutils.ExpectNoNamespace, names.KubeAdminTierName, kcnpAdminSpec)) + + By("Cannot update the kube-admin Tier") + defRes.Spec = spec1 + _, outError = c.Tiers().Update(ctx, defRes, options.SetOptions{}) + Expect(outError).To(HaveOccurred()) + Expect(outError.Error()).Should(ContainSubstring("kube-admin tier order must be 1000")) + + By("Creating the kube-baseline tier with an invalid order") res, outError = c.Tiers().Create(ctx, &apiv3.Tier{ - ObjectMeta: metav1.ObjectMeta{Name: names.AdminNetworkPolicyTierName}, + ObjectMeta: metav1.ObjectMeta{Name: names.KubeBaselineTierName}, Spec: spec1, }, options.SetOptions{}) Expect(res).To(BeNil()) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).Should(ContainSubstring("adminnetworkpolicy tier order must be 1000")) + Expect(outError.Error()).Should(ContainSubstring("kube-baseline tier order must be 1e+07")) - By("Cannot delete the adminnetworkpolicy Tier") - _, outError = c.Tiers().Delete(ctx, names.AdminNetworkPolicyTierName, options.DeleteOptions{}) + By("Cannot delete the kube-baseline Tier") + _, outError = c.Tiers().Delete(ctx, names.KubeBaselineTierName, options.DeleteOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).To(Equal("operation Delete is not supported on adminnetworkpolicy: Cannot delete adminnetworkpolicy tier")) + Expect(outError.Error()).To(Equal("operation Delete is not supported on kube-baseline: Cannot delete kube-baseline tier")) - By("Getting adminnetworkpolicy Tier") - defRes, outError = c.Tiers().Get(ctx, names.AdminNetworkPolicyTierName, options.GetOptions{}) + By("Getting kube-baseline Tier") + defRes, outError = c.Tiers().Get(ctx, names.KubeBaselineTierName, options.GetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(defRes).To(MatchResource(apiv3.KindTier, testutils.ExpectNoNamespace, names.AdminNetworkPolicyTierName, anpSpec)) + Expect(defRes).To(MatchResource(apiv3.KindTier, testutils.ExpectNoNamespace, names.KubeBaselineTierName, kcnpBaselineSpec)) - By("Cannot update the adminnetworkpolicy Tier") + By("Cannot update the kube-baseline Tier") defRes.Spec = spec1 _, outError = c.Tiers().Update(ctx, defRes, options.SetOptions{}) Expect(outError).To(HaveOccurred()) - Expect(outError.Error()).Should(ContainSubstring("adminnetworkpolicy tier order must be 1000")) + Expect(outError.Error()).Should(ContainSubstring("kube-baseline tier order must be 1e+07")) By("Updating the Tier before it is created") res, outError = c.Tiers().Update(ctx, &apiv3.Tier{ @@ -277,9 +300,9 @@ var _ = testutils.E2eDatastoreDescribe("Tier tests", testutils.DatastoreAll, fun checkAndFilterDefaultTiers := func(outList *apiv3.TierList) []apiv3.Tier { // Tiers are returned in name order, and the default ones happen // to sort first in these tests. - Expect(&outList.Items[0]).To(MatchResource(apiv3.KindTier, testutils.ExpectNoNamespace, names.AdminNetworkPolicyTierName, anpSpec)) - Expect(&outList.Items[1]).To(MatchResource(apiv3.KindTier, testutils.ExpectNoNamespace, names.BaselineAdminNetworkPolicyTierName, banpSpec)) - Expect(&outList.Items[2]).To(MatchResource(apiv3.KindTier, testutils.ExpectNoNamespace, defaultName, defaultSpec)) + Expect(&outList.Items[0]).To(MatchResource(apiv3.KindTier, testutils.ExpectNoNamespace, defaultName, defaultSpec)) + Expect(&outList.Items[1]).To(MatchResource(apiv3.KindTier, testutils.ExpectNoNamespace, names.KubeAdminTierName, kcnpAdminSpec)) + Expect(&outList.Items[2]).To(MatchResource(apiv3.KindTier, testutils.ExpectNoNamespace, names.KubeBaselineTierName, kcnpBaselineSpec)) return outList.Items[3:] } outList, outError := c.Tiers().List(ctx, options.ListOptions{}) @@ -466,7 +489,7 @@ var _ = testutils.E2eDatastoreDescribe("Tier tests", testutils.DatastoreAll, fun Expect(outError).To(HaveOccurred()) Expect(outError.Error()).To(ContainSubstring("resource does not exist: Tier(" + name2 + ") with error:")) - By("Listing all Tiers and expecting only the default and adminnetworkpolicy tiers") + By("Listing all Tiers and expecting only the default, kube-admin and kube-baseline tiers") outList, outError = c.Tiers().List(ctx, options.ListOptions{}) Expect(outError).NotTo(HaveOccurred()) nonDefaultTiers = checkAndFilterDefaultTiers(outList) @@ -640,7 +663,7 @@ var _ = testutils.E2eDatastoreDescribe("Tier tests", testutils.DatastoreAll, fun By("Cleaning the datastore and expecting deletion events for each configured resource (tests prefix deletes results in individual events for each key)") Expect(be.Clean()).NotTo(HaveOccurred()) - testWatcher4.ExpectEvents(apiv3.KindTier, []watch.Event{ + testWatcher4.ExpectEventsAnyOrder(apiv3.KindTier, []watch.Event{ { Type: watch.Deleted, Previous: outRes1, diff --git a/libcalico-go/lib/clientv3/watch_e2e_test.go b/libcalico-go/lib/clientv3/watch_e2e_test.go index 4050d9d2456..4231f07bae9 100644 --- a/libcalico-go/lib/clientv3/watch_e2e_test.go +++ b/libcalico-go/lib/clientv3/watch_e2e_test.go @@ -21,7 +21,7 @@ import ( "sync" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -148,7 +148,7 @@ var _ = testutils.E2eDatastoreDescribe("Additional watch tests", testutils.Datas // Create a number of goroutines to handle a Watcher. Each go routine will pull // events off the watcher and exit once the results channel is closed. watchers := make([]watch.Interface, numWatchers) - for i := 0; i < numWatchers; i++ { + for i := range numWatchers { watchers[i], err = c.BGPPeers().Watch(ctx, options.ListOptions{}) Expect(err).NotTo(HaveOccurred()) defer watchers[i].Stop() @@ -174,7 +174,7 @@ var _ = testutils.E2eDatastoreDescribe("Additional watch tests", testutils.Datas // Loop through the watchers and start stopping them with a random sized pause // interval between each. - for i := 0; i < numWatchers; i++ { + for i := range numWatchers { d := time.Millisecond * time.Duration(5+rand.Int()%50) log.Infof("Sleeping for %v ms and then stopping watcher", d) time.Sleep(d) diff --git a/libcalico-go/lib/clientv3/workloadendpoint.go b/libcalico-go/lib/clientv3/workloadendpoint.go index 71a5cff6703..794ea737fcb 100644 --- a/libcalico-go/lib/clientv3/workloadendpoint.go +++ b/libcalico-go/lib/clientv3/workloadendpoint.go @@ -17,10 +17,11 @@ package clientv3 import ( "context" "fmt" + "maps" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/errors" "github.com/projectcalico/calico/libcalico-go/lib/names" "github.com/projectcalico/calico/libcalico-go/lib/options" @@ -30,11 +31,11 @@ import ( // WorkloadEndpointInterface has methods to work with WorkloadEndpoint resources. type WorkloadEndpointInterface interface { - Create(ctx context.Context, res *libapiv3.WorkloadEndpoint, opts options.SetOptions) (*libapiv3.WorkloadEndpoint, error) - Update(ctx context.Context, res *libapiv3.WorkloadEndpoint, opts options.SetOptions) (*libapiv3.WorkloadEndpoint, error) - Delete(ctx context.Context, namespace, name string, opts options.DeleteOptions) (*libapiv3.WorkloadEndpoint, error) - Get(ctx context.Context, namespace, name string, opts options.GetOptions) (*libapiv3.WorkloadEndpoint, error) - List(ctx context.Context, opts options.ListOptions) (*libapiv3.WorkloadEndpointList, error) + Create(ctx context.Context, res *internalapi.WorkloadEndpoint, opts options.SetOptions) (*internalapi.WorkloadEndpoint, error) + Update(ctx context.Context, res *internalapi.WorkloadEndpoint, opts options.SetOptions) (*internalapi.WorkloadEndpoint, error) + Delete(ctx context.Context, namespace, name string, opts options.DeleteOptions) (*internalapi.WorkloadEndpoint, error) + Get(ctx context.Context, namespace, name string, opts options.GetOptions) (*internalapi.WorkloadEndpoint, error) + List(ctx context.Context, opts options.ListOptions) (*internalapi.WorkloadEndpointList, error) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) } @@ -45,7 +46,7 @@ type workloadEndpoints struct { // Create takes the representation of a WorkloadEndpoint and creates it. Returns the stored // representation of the WorkloadEndpoint, and an error, if there is any. -func (r workloadEndpoints) Create(ctx context.Context, res *libapiv3.WorkloadEndpoint, opts options.SetOptions) (*libapiv3.WorkloadEndpoint, error) { +func (r workloadEndpoints) Create(ctx context.Context, res *internalapi.WorkloadEndpoint, opts options.SetOptions) (*internalapi.WorkloadEndpoint, error) { if res != nil { // Since we're about to default some fields, take a (shallow) copy of the input data // before we do so. @@ -58,16 +59,16 @@ func (r workloadEndpoints) Create(ctx context.Context, res *libapiv3.WorkloadEnd return nil, err } r.updateLabelsForStorage(res) - out, err := r.client.resources.Create(ctx, opts, libapiv3.KindWorkloadEndpoint, res) + out, err := r.client.resources.Create(ctx, opts, internalapi.KindWorkloadEndpoint, res) if out != nil { - return out.(*libapiv3.WorkloadEndpoint), err + return out.(*internalapi.WorkloadEndpoint), err } return nil, err } // Update takes the representation of a WorkloadEndpoint and updates it. Returns the stored // representation of the WorkloadEndpoint, and an error, if there is any. -func (r workloadEndpoints) Update(ctx context.Context, res *libapiv3.WorkloadEndpoint, opts options.SetOptions) (*libapiv3.WorkloadEndpoint, error) { +func (r workloadEndpoints) Update(ctx context.Context, res *internalapi.WorkloadEndpoint, opts options.SetOptions) (*internalapi.WorkloadEndpoint, error) { if res != nil { // Since we're about to default some fields, take a (shallow) copy of the input data // before we do so. @@ -80,36 +81,36 @@ func (r workloadEndpoints) Update(ctx context.Context, res *libapiv3.WorkloadEnd return nil, err } r.updateLabelsForStorage(res) - out, err := r.client.resources.Update(ctx, opts, libapiv3.KindWorkloadEndpoint, res) + out, err := r.client.resources.Update(ctx, opts, internalapi.KindWorkloadEndpoint, res) if out != nil { - return out.(*libapiv3.WorkloadEndpoint), err + return out.(*internalapi.WorkloadEndpoint), err } return nil, err } // Delete takes name of the WorkloadEndpoint and deletes it. Returns an error if one occurs. -func (r workloadEndpoints) Delete(ctx context.Context, namespace, name string, opts options.DeleteOptions) (*libapiv3.WorkloadEndpoint, error) { - out, err := r.client.resources.Delete(ctx, opts, libapiv3.KindWorkloadEndpoint, namespace, name) +func (r workloadEndpoints) Delete(ctx context.Context, namespace, name string, opts options.DeleteOptions) (*internalapi.WorkloadEndpoint, error) { + out, err := r.client.resources.Delete(ctx, opts, internalapi.KindWorkloadEndpoint, namespace, name) if out != nil { - return out.(*libapiv3.WorkloadEndpoint), err + return out.(*internalapi.WorkloadEndpoint), err } return nil, err } // Get takes name of the WorkloadEndpoint, and returns the corresponding WorkloadEndpoint object, // and an error if there is any. -func (r workloadEndpoints) Get(ctx context.Context, namespace, name string, opts options.GetOptions) (*libapiv3.WorkloadEndpoint, error) { - out, err := r.client.resources.Get(ctx, opts, libapiv3.KindWorkloadEndpoint, namespace, name) +func (r workloadEndpoints) Get(ctx context.Context, namespace, name string, opts options.GetOptions) (*internalapi.WorkloadEndpoint, error) { + out, err := r.client.resources.Get(ctx, opts, internalapi.KindWorkloadEndpoint, namespace, name) if out != nil { - return out.(*libapiv3.WorkloadEndpoint), err + return out.(*internalapi.WorkloadEndpoint), err } return nil, err } // List returns the list of WorkloadEndpoint objects that match the supplied options. -func (r workloadEndpoints) List(ctx context.Context, opts options.ListOptions) (*libapiv3.WorkloadEndpointList, error) { - res := &libapiv3.WorkloadEndpointList{} - if err := r.client.resources.List(ctx, opts, libapiv3.KindWorkloadEndpoint, libapiv3.KindWorkloadEndpointList, res); err != nil { +func (r workloadEndpoints) List(ctx context.Context, opts options.ListOptions) (*internalapi.WorkloadEndpointList, error) { + res := &internalapi.WorkloadEndpointList{} + if err := r.client.resources.List(ctx, opts, internalapi.KindWorkloadEndpoint, internalapi.KindWorkloadEndpointList, res); err != nil { return nil, err } return res, nil @@ -118,12 +119,12 @@ func (r workloadEndpoints) List(ctx context.Context, opts options.ListOptions) ( // Watch returns a watch.Interface that watches the NetworkPolicies that match the // supplied options. func (r workloadEndpoints) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) { - return r.client.resources.Watch(ctx, opts, libapiv3.KindWorkloadEndpoint, nil) + return r.client.resources.Watch(ctx, opts, internalapi.KindWorkloadEndpoint, nil) } // assignOrValidateName either assigns the name calculated from the Spec fields, or validates // the name against the spec fields. -func (r workloadEndpoints) assignOrValidateName(res *libapiv3.WorkloadEndpoint) error { +func (r workloadEndpoints) assignOrValidateName(res *internalapi.WorkloadEndpoint) error { // Validate the workload endpoint indices and the name match. wepids := names.IdentifiersForV3WorkloadEndpoint(res) expectedName, err := wepids.CalculateWorkloadEndpointName(false) @@ -150,11 +151,9 @@ func (r workloadEndpoints) assignOrValidateName(res *libapiv3.WorkloadEndpoint) // updateLabelsForStorage updates the set of labels that we persist. It adds/overrides // the Namespace and Orchestrator labels which must be set to the correct values and are // not user configurable. -func (r workloadEndpoints) updateLabelsForStorage(res *libapiv3.WorkloadEndpoint) { +func (r workloadEndpoints) updateLabelsForStorage(res *internalapi.WorkloadEndpoint) { labelsCopy := make(map[string]string, len(res.GetLabels())+2) - for k, v := range res.GetLabels() { - labelsCopy[k] = v - } + maps.Copy(labelsCopy, res.GetLabels()) labelsCopy[apiv3.LabelNamespace] = res.Namespace labelsCopy[apiv3.LabelOrchestrator] = res.Spec.Orchestrator res.SetLabels(labelsCopy) diff --git a/libcalico-go/lib/clientv3/workloadendpoint_e2e_test.go b/libcalico-go/lib/clientv3/workloadendpoint_e2e_test.go index fd9c4525168..756abbef6c5 100644 --- a/libcalico-go/lib/clientv3/workloadendpoint_e2e_test.go +++ b/libcalico-go/lib/clientv3/workloadendpoint_e2e_test.go @@ -18,15 +18,14 @@ import ( "context" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend" "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/options" @@ -41,14 +40,14 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas namespace2 := "namespace-2" name1 := "node--1-k8s-abcdef-eth0" name2 := "node--2-cni-a232323a-eth0" - spec1_1 := libapiv3.WorkloadEndpointSpec{ + spec1_1 := internalapi.WorkloadEndpointSpec{ Node: "node-1", Orchestrator: "k8s", Pod: "abcdef", ContainerID: "a12345a", Endpoint: "eth0", InterfaceName: "cali09123", - Ports: []libapiv3.WorkloadEndpointPort{ + Ports: []internalapi.WorkloadEndpointPort{ { Port: 1234, Name: "foobar", @@ -61,14 +60,14 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas }, }, } - spec1_2 := libapiv3.WorkloadEndpointSpec{ + spec1_2 := internalapi.WorkloadEndpointSpec{ Node: "node-1", Orchestrator: "k8s", Pod: "abcdef", ContainerID: "a12345a", Endpoint: "eth0", InterfaceName: "foobar", - Ports: []libapiv3.WorkloadEndpointPort{ + Ports: []internalapi.WorkloadEndpointPort{ { Port: 5678, Name: "bazzbiff", @@ -76,14 +75,14 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas }, }, } - spec2_1 := libapiv3.WorkloadEndpointSpec{ + spec2_1 := internalapi.WorkloadEndpointSpec{ Node: "node-2", Orchestrator: "cni", Endpoint: "eth0", ContainerID: "a232323a", InterfaceName: "cali09122", } - spec2_2 := libapiv3.WorkloadEndpointSpec{ + spec2_2 := internalapi.WorkloadEndpointSpec{ Node: "node-2", Orchestrator: "cni", Endpoint: "eth0", @@ -92,7 +91,7 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas } DescribeTable("WorkloadEndpoint e2e CRUD tests", - func(namespace1, namespace2, name1, name2 string, spec1_1, spec1_2, spec2_1 libapiv3.WorkloadEndpointSpec) { + func(namespace1, namespace2, name1, name2 string, spec1_1, spec1_2, spec2_1 internalapi.WorkloadEndpointSpec) { c, err := clientv3.New(config) Expect(err).NotTo(HaveOccurred()) @@ -101,7 +100,7 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas be.Clean() By("Updating the WorkloadEndpoint before it is created") - _, outError := c.WorkloadEndpoints().Update(ctx, &libapiv3.WorkloadEndpoint{ + _, outError := c.WorkloadEndpoints().Update(ctx, &internalapi.WorkloadEndpoint{ ObjectMeta: metav1.ObjectMeta{Namespace: namespace1, Name: name1, ResourceVersion: "1234", CreationTimestamp: metav1.Now(), UID: uid}, Spec: spec1_1, }, options.SetOptions{}) @@ -114,7 +113,7 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas Expect(outError.Error()).To(ContainSubstring("resource does not exist: WorkloadEndpoint(" + namespace1 + "/" + name1 + ") with error:")) By("Attempting to create a new WorkloadEndpoint with name1/spec1_1 and a non-empty ResourceVersion") - _, outError = c.WorkloadEndpoints().Create(ctx, &libapiv3.WorkloadEndpoint{ + _, outError = c.WorkloadEndpoints().Create(ctx, &internalapi.WorkloadEndpoint{ ObjectMeta: metav1.ObjectMeta{Namespace: namespace1, Name: name1, ResourceVersion: "12345", CreationTimestamp: metav1.Now(), UID: uid}, Spec: spec1_1, }, options.SetOptions{}) @@ -122,7 +121,7 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas Expect(outError.Error()).To(Equal("error with field Metadata.ResourceVersion = '12345' (field must not be set for a Create request)")) By("Creating a new WorkloadEndpoint with namespace1/name1/spec1_1 - name gets assigned automatically") - wepToCreate := &libapiv3.WorkloadEndpoint{ + wepToCreate := &internalapi.WorkloadEndpoint{ ObjectMeta: metav1.ObjectMeta{Namespace: namespace1}, Spec: spec1_1, } @@ -130,7 +129,7 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas res1, outError := c.WorkloadEndpoints().Create(ctx, wepToCreate, options.SetOptions{}) Expect(outError).NotTo(HaveOccurred()) Expect(wepToCreate).To(Equal(wepToCreateCopy), "Create() unexpectedly modified input") - Expect(res1).To(MatchResource(libapiv3.KindWorkloadEndpoint, namespace1, name1, spec1_1)) + Expect(res1).To(MatchResource(internalapi.KindWorkloadEndpoint, namespace1, name1, spec1_1)) Expect(res1.Labels[apiv3.LabelOrchestrator]).To(Equal(res1.Spec.Orchestrator)) Expect(res1.Labels[apiv3.LabelNamespace]).To(Equal(res1.Namespace)) @@ -138,7 +137,7 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas rv1_1 := res1.ResourceVersion By("Attempting to create the same WorkloadEndpoint with name1 but with spec1_2") - _, outError = c.WorkloadEndpoints().Create(ctx, &libapiv3.WorkloadEndpoint{ + _, outError = c.WorkloadEndpoints().Create(ctx, &internalapi.WorkloadEndpoint{ ObjectMeta: metav1.ObjectMeta{Namespace: namespace1, Name: name1}, Spec: spec1_2, }, options.SetOptions{}) @@ -148,7 +147,7 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas By("Getting WorkloadEndpoint (name1) and comparing the output against spec1_1") res, outError := c.WorkloadEndpoints().Get(ctx, namespace1, name1, options.GetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res).To(MatchResource(libapiv3.KindWorkloadEndpoint, namespace1, name1, spec1_1)) + Expect(res).To(MatchResource(internalapi.KindWorkloadEndpoint, namespace1, name1, spec1_1)) Expect(res.ResourceVersion).To(Equal(res1.ResourceVersion)) Expect(res.Labels[apiv3.LabelOrchestrator]).To(Equal(res.Spec.Orchestrator)) Expect(res.Labels[apiv3.LabelNamespace]).To(Equal(res.Namespace)) @@ -162,38 +161,38 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas outList, outError := c.WorkloadEndpoints().List(ctx, options.ListOptions{Namespace: namespace1}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(libapiv3.KindWorkloadEndpoint, namespace1, name1, spec1_1), + testutils.Resource(internalapi.KindWorkloadEndpoint, namespace1, name1, spec1_1), )) Expect(outList.Items[0].Labels[apiv3.LabelOrchestrator]).To(Equal(outList.Items[0].Spec.Orchestrator)) Expect(outList.Items[0].Labels[apiv3.LabelNamespace]).To(Equal(outList.Items[0].Namespace)) By("Creating a new WorkloadEndpoint with name2/spec2_1") - res2, outError := c.WorkloadEndpoints().Create(ctx, &libapiv3.WorkloadEndpoint{ + res2, outError := c.WorkloadEndpoints().Create(ctx, &internalapi.WorkloadEndpoint{ ObjectMeta: metav1.ObjectMeta{Name: name2, Namespace: namespace2}, Spec: spec2_1, }, options.SetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res2).To(MatchResource(libapiv3.KindWorkloadEndpoint, namespace2, name2, spec2_1)) + Expect(res2).To(MatchResource(internalapi.KindWorkloadEndpoint, namespace2, name2, spec2_1)) By("Getting WorkloadEndpoint (name2) and comparing the output against spec1_2") res, outError = c.WorkloadEndpoints().Get(ctx, namespace2, name2, options.GetOptions{}) Expect(outError).NotTo(HaveOccurred()) - Expect(res).To(MatchResource(libapiv3.KindWorkloadEndpoint, namespace2, name2, spec2_1)) + Expect(res).To(MatchResource(internalapi.KindWorkloadEndpoint, namespace2, name2, spec2_1)) Expect(res.ResourceVersion).To(Equal(res2.ResourceVersion)) By("Listing all the WorkloadEndpoints using an empty namespace (all-namespaces), expecting a two results with name1/spec1_1 and name2/spec1_2") outList, outError = c.WorkloadEndpoints().List(ctx, options.ListOptions{}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(libapiv3.KindWorkloadEndpoint, namespace1, name1, spec1_1), - testutils.Resource(libapiv3.KindWorkloadEndpoint, namespace2, name2, spec2_1), + testutils.Resource(internalapi.KindWorkloadEndpoint, namespace1, name1, spec1_1), + testutils.Resource(internalapi.KindWorkloadEndpoint, namespace2, name2, spec2_1), )) By("Listing all the WorkloadEndpoints in namespace2, expecting a one results with name2/spec2_1") outList, outError = c.WorkloadEndpoints().List(ctx, options.ListOptions{Namespace: namespace2}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(libapiv3.KindWorkloadEndpoint, namespace2, name2, spec2_1), + testutils.Resource(internalapi.KindWorkloadEndpoint, namespace2, name2, spec2_1), )) By("Updating WorkloadEndpoint name1 with spec1_2") @@ -203,12 +202,12 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas Expect(outError).NotTo(HaveOccurred()) Expect(res1).To(Equal(res1Copy), "Update() unexpectedly modified input") res1 = res1Out - Expect(res1).To(MatchResource(libapiv3.KindWorkloadEndpoint, namespace1, name1, spec1_2)) + Expect(res1).To(MatchResource(internalapi.KindWorkloadEndpoint, namespace1, name1, spec1_2)) Expect(res1.Labels[apiv3.LabelOrchestrator]).To(Equal(res1.Spec.Orchestrator)) Expect(res1.Labels[apiv3.LabelNamespace]).To(Equal(res1.Namespace)) By("Attempting to update the WorkloadEndpoint without a Creation Timestamp") - res, outError = c.WorkloadEndpoints().Update(ctx, &libapiv3.WorkloadEndpoint{ + res, outError = c.WorkloadEndpoints().Update(ctx, &internalapi.WorkloadEndpoint{ ObjectMeta: metav1.ObjectMeta{Namespace: namespace1, Name: name1, ResourceVersion: "1234", UID: uid}, Spec: spec1_1, }, options.SetOptions{}) @@ -217,7 +216,7 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas Expect(outError.Error()).To(Equal("error with field Metadata.CreationTimestamp = '0001-01-01 00:00:00 +0000 UTC' (field must be set for an Update request)")) By("Attempting to update the WorkloadEndpoint without a UID") - res, outError = c.WorkloadEndpoints().Update(ctx, &libapiv3.WorkloadEndpoint{ + res, outError = c.WorkloadEndpoints().Update(ctx, &internalapi.WorkloadEndpoint{ ObjectMeta: metav1.ObjectMeta{Namespace: namespace1, Name: name1, ResourceVersion: "1234", CreationTimestamp: metav1.Now()}, Spec: spec1_1, }, options.SetOptions{}) @@ -245,28 +244,28 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas By("Getting WorkloadEndpoint (name1) with the original resource version and comparing the output against spec1_1") res, outError = c.WorkloadEndpoints().Get(ctx, namespace1, name1, options.GetOptions{ResourceVersion: rv1_1}) Expect(outError).NotTo(HaveOccurred()) - Expect(res).To(MatchResource(libapiv3.KindWorkloadEndpoint, namespace1, name1, spec1_1)) + Expect(res).To(MatchResource(internalapi.KindWorkloadEndpoint, namespace1, name1, spec1_1)) Expect(res.ResourceVersion).To(Equal(rv1_1)) By("Getting WorkloadEndpoint (name1) with the updated resource version and comparing the output against spec1_2") res, outError = c.WorkloadEndpoints().Get(ctx, namespace1, name1, options.GetOptions{ResourceVersion: rv1_2}) Expect(outError).NotTo(HaveOccurred()) - Expect(res).To(MatchResource(libapiv3.KindWorkloadEndpoint, namespace1, name1, spec1_2)) + Expect(res).To(MatchResource(internalapi.KindWorkloadEndpoint, namespace1, name1, spec1_2)) Expect(res.ResourceVersion).To(Equal(rv1_2)) By("Listing WorkloadEndpoints with the original resource version and checking for a single result with name1/spec1_1") outList, outError = c.WorkloadEndpoints().List(ctx, options.ListOptions{Namespace: namespace1, ResourceVersion: rv1_1}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(libapiv3.KindWorkloadEndpoint, namespace1, name1, spec1_1), + testutils.Resource(internalapi.KindWorkloadEndpoint, namespace1, name1, spec1_1), )) By("Listing WorkloadEndpoints (all namespaces) with the latest resource version and checking for two results with name1/spec1_2 and name2/spec1_2") outList, outError = c.WorkloadEndpoints().List(ctx, options.ListOptions{}) Expect(outError).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(libapiv3.KindWorkloadEndpoint, namespace1, name1, spec1_2), - testutils.Resource(libapiv3.KindWorkloadEndpoint, namespace2, name2, spec2_1), + testutils.Resource(internalapi.KindWorkloadEndpoint, namespace1, name1, spec1_2), + testutils.Resource(internalapi.KindWorkloadEndpoint, namespace2, name2, spec2_1), )) By("Deleting WorkloadEndpoint (name1) with the old resource version") @@ -277,7 +276,7 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas By("Deleting WorkloadEndpoint (name1) with the new resource version") dres, outError := c.WorkloadEndpoints().Delete(ctx, namespace1, name1, options.DeleteOptions{ResourceVersion: rv1_2}) Expect(outError).NotTo(HaveOccurred()) - Expect(dres).To(MatchResource(libapiv3.KindWorkloadEndpoint, namespace1, name1, spec1_2)) + Expect(dres).To(MatchResource(internalapi.KindWorkloadEndpoint, namespace1, name1, spec1_2)) Expect(dres.Labels[apiv3.LabelOrchestrator]).To(Equal(dres.Spec.Orchestrator)) Expect(dres.Labels[apiv3.LabelNamespace]).To(Equal(dres.Namespace)) @@ -293,7 +292,7 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas Expect(outError.Error()).To(ContainSubstring("resource does not exist: WorkloadEndpoint(" + namespace2 + "/" + name2 + ") with error:")) By("Creating WorkloadEndpoint name2 with a 2s TTL and waiting for the entry to be deleted") - _, outError = c.WorkloadEndpoints().Create(ctx, &libapiv3.WorkloadEndpoint{ + _, outError = c.WorkloadEndpoints().Create(ctx, &internalapi.WorkloadEndpoint{ ObjectMeta: metav1.ObjectMeta{Namespace: namespace2, Name: name2}, Spec: spec2_1, }, options.SetOptions{TTL: 2 * time.Second}) @@ -348,7 +347,7 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas By("Configuring a WorkloadEndpoint namespace1/name1/spec1_1 and storing the response") outRes1, err := c.WorkloadEndpoints().Create( ctx, - &libapiv3.WorkloadEndpoint{ + &internalapi.WorkloadEndpoint{ ObjectMeta: metav1.ObjectMeta{Namespace: namespace1, Name: name1}, Spec: spec1_1, }, @@ -360,7 +359,7 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas By("Configuring a WorkloadEndpoint namespace2/name2/spec2_1 and storing the response") outRes2, err := c.WorkloadEndpoints().Create( ctx, - &libapiv3.WorkloadEndpoint{ + &internalapi.WorkloadEndpoint{ ObjectMeta: metav1.ObjectMeta{Namespace: namespace2, Name: name2}, Spec: spec2_1, }, @@ -379,7 +378,7 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas Expect(err).NotTo(HaveOccurred()) By("Checking for two events, create res2 and delete re1") - testWatcher1.ExpectEvents(libapiv3.KindWorkloadEndpoint, []watch.Event{ + testWatcher1.ExpectEvents(internalapi.KindWorkloadEndpoint, []watch.Event{ { Type: watch.Added, Object: outRes2, @@ -400,14 +399,14 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas By("Modifying res2") outRes3, err := c.WorkloadEndpoints().Update( ctx, - &libapiv3.WorkloadEndpoint{ + &internalapi.WorkloadEndpoint{ ObjectMeta: outRes2.ObjectMeta, Spec: spec2_2, }, options.SetOptions{}, ) Expect(err).NotTo(HaveOccurred()) - testWatcher2.ExpectEvents(libapiv3.KindWorkloadEndpoint, []watch.Event{ + testWatcher2.ExpectEvents(internalapi.KindWorkloadEndpoint, []watch.Event{ { Type: watch.Added, Object: outRes1, @@ -435,7 +434,7 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas Expect(err).NotTo(HaveOccurred()) testWatcher2_1 := testutils.NewTestResourceWatch(config.Spec.DatastoreType, w) defer testWatcher2_1.Stop() - testWatcher2_1.ExpectEvents(libapiv3.KindWorkloadEndpoint, []watch.Event{ + testWatcher2_1.ExpectEvents(internalapi.KindWorkloadEndpoint, []watch.Event{ { Type: watch.Added, Object: outRes1, @@ -453,7 +452,7 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas Expect(err).NotTo(HaveOccurred()) testWatcher3 := testutils.NewTestResourceWatch(config.Spec.DatastoreType, w) defer testWatcher3.Stop() - testWatcher3.ExpectEvents(libapiv3.KindWorkloadEndpoint, []watch.Event{ + testWatcher3.ExpectEvents(internalapi.KindWorkloadEndpoint, []watch.Event{ { Type: watch.Added, Object: outRes3, @@ -466,7 +465,7 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas Expect(err).NotTo(HaveOccurred()) testWatcher4 := testutils.NewTestResourceWatch(config.Spec.DatastoreType, w) defer testWatcher4.Stop() - testWatcher4.ExpectEvents(libapiv3.KindWorkloadEndpoint, []watch.Event{ + testWatcher4.ExpectEvents(internalapi.KindWorkloadEndpoint, []watch.Event{ { Type: watch.Added, Object: outRes1, @@ -492,9 +491,9 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas By("Creating two WorkloadEndpoint with same namespace, node, orchestrator and overlapping Pod") outRes1, err := c.WorkloadEndpoints().Create( ctx, - &libapiv3.WorkloadEndpoint{ + &internalapi.WorkloadEndpoint{ ObjectMeta: metav1.ObjectMeta{Namespace: "namespace1", Name: "node--1-k8s-pod-eth0"}, - Spec: libapiv3.WorkloadEndpointSpec{ + Spec: internalapi.WorkloadEndpointSpec{ Node: "node-1", Orchestrator: "k8s", Pod: "pod", @@ -508,9 +507,9 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas outRes2, err := c.WorkloadEndpoints().Create( ctx, - &libapiv3.WorkloadEndpoint{ + &internalapi.WorkloadEndpoint{ ObjectMeta: metav1.ObjectMeta{Namespace: "namespace1", Name: "node--1-k8s-pod--1-eth0"}, - Spec: libapiv3.WorkloadEndpointSpec{ + Spec: internalapi.WorkloadEndpointSpec{ Node: "node-1", Orchestrator: "k8s", Pod: "pod-1", @@ -525,9 +524,9 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas By("Creating a workload in a different namespace, but with a largely overlapping name") outRes3, err := c.WorkloadEndpoints().Create( ctx, - &libapiv3.WorkloadEndpoint{ + &internalapi.WorkloadEndpoint{ ObjectMeta: metav1.ObjectMeta{Namespace: "namespace2", Name: "node--1-k8s-pod--2-eth0"}, - Spec: libapiv3.WorkloadEndpointSpec{ + Spec: internalapi.WorkloadEndpointSpec{ Node: "node-1", Orchestrator: "k8s", Pod: "pod-2", @@ -543,38 +542,38 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas outList, err := c.WorkloadEndpoints().List(ctx, options.ListOptions{Namespace: "namespace1", Name: "node--1-k8s-pod-eth0"}) Expect(err).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(libapiv3.KindWorkloadEndpoint, "namespace1", "node--1-k8s-pod-eth0", outRes1.Spec), + testutils.Resource(internalapi.KindWorkloadEndpoint, "namespace1", "node--1-k8s-pod-eth0", outRes1.Spec), )) By("Doing a short prefix get to retrieve both workload endpoints in namespace1") outList, err = c.WorkloadEndpoints().List(ctx, options.ListOptions{Namespace: "namespace1", Name: "node--1-k8s-pod-", Prefix: true}) Expect(err).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(libapiv3.KindWorkloadEndpoint, "namespace1", "node--1-k8s-pod--1-eth0", outRes2.Spec), - testutils.Resource(libapiv3.KindWorkloadEndpoint, "namespace1", "node--1-k8s-pod-eth0", outRes1.Spec), + testutils.Resource(internalapi.KindWorkloadEndpoint, "namespace1", "node--1-k8s-pod--1-eth0", outRes2.Spec), + testutils.Resource(internalapi.KindWorkloadEndpoint, "namespace1", "node--1-k8s-pod-eth0", outRes1.Spec), )) By("Doing a longer prefix get to retrieve one workload endpoints in namespace1") outList, err = c.WorkloadEndpoints().List(ctx, options.ListOptions{Namespace: "namespace1", Name: "node--1-k8s-pod--1", Prefix: true}) Expect(err).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(libapiv3.KindWorkloadEndpoint, "namespace1", "node--1-k8s-pod--1-eth0", outRes2.Spec), + testutils.Resource(internalapi.KindWorkloadEndpoint, "namespace1", "node--1-k8s-pod--1-eth0", outRes2.Spec), )) By("Doing a short prefix get with wildcarded namespace to retrieve all workload endpoints") outList, err = c.WorkloadEndpoints().List(ctx, options.ListOptions{Name: "node--1-k8s-pod-", Prefix: true}) Expect(err).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(libapiv3.KindWorkloadEndpoint, "namespace1", "node--1-k8s-pod--1-eth0", outRes2.Spec), - testutils.Resource(libapiv3.KindWorkloadEndpoint, "namespace1", "node--1-k8s-pod-eth0", outRes1.Spec), - testutils.Resource(libapiv3.KindWorkloadEndpoint, "namespace2", "node--1-k8s-pod--2-eth0", outRes3.Spec), + testutils.Resource(internalapi.KindWorkloadEndpoint, "namespace1", "node--1-k8s-pod--1-eth0", outRes2.Spec), + testutils.Resource(internalapi.KindWorkloadEndpoint, "namespace1", "node--1-k8s-pod-eth0", outRes1.Spec), + testutils.Resource(internalapi.KindWorkloadEndpoint, "namespace2", "node--1-k8s-pod--2-eth0", outRes3.Spec), )) By("Doing a long prefix get with wildcarded names to retrieve the workload endpoint in namespace2") outList, err = c.WorkloadEndpoints().List(ctx, options.ListOptions{Name: "node--1-k8s-pod--2", Prefix: true}) Expect(err).NotTo(HaveOccurred()) Expect(outList.Items).To(ConsistOf( - testutils.Resource(libapiv3.KindWorkloadEndpoint, "namespace2", "node--1-k8s-pod--2-eth0", outRes3.Spec), + testutils.Resource(internalapi.KindWorkloadEndpoint, "namespace2", "node--1-k8s-pod--2-eth0", outRes3.Spec), )) By("Deleting all endpoints") @@ -599,9 +598,9 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas By("Creating a workload endpoint with missing ContainerID for CNI") _, err = c.WorkloadEndpoints().Create( ctx, - &libapiv3.WorkloadEndpoint{ + &internalapi.WorkloadEndpoint{ ObjectMeta: metav1.ObjectMeta{Namespace: "namespace1", Name: "node--1-cni-container-eth0"}, - Spec: libapiv3.WorkloadEndpointSpec{ + Spec: internalapi.WorkloadEndpointSpec{ Node: "node-1", Orchestrator: "cni", Pod: "pod", @@ -616,9 +615,9 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas By("Creating a workload endpoint with missing Workload for arbitrary orchestrator") _, err = c.WorkloadEndpoints().Create( ctx, - &libapiv3.WorkloadEndpoint{ + &internalapi.WorkloadEndpoint{ ObjectMeta: metav1.ObjectMeta{Namespace: "namespace1", Name: "node--1-cni-container-eth0"}, - Spec: libapiv3.WorkloadEndpointSpec{ + Spec: internalapi.WorkloadEndpointSpec{ Node: "node-1", Orchestrator: "other", Pod: "pod", @@ -634,9 +633,9 @@ var _ = testutils.E2eDatastoreDescribe("WorkloadEndpoint tests", testutils.Datas By("Creating a workload endpoint with correct name and indices for k8s") wep, err := c.WorkloadEndpoints().Create( ctx, - &libapiv3.WorkloadEndpoint{ + &internalapi.WorkloadEndpoint{ ObjectMeta: metav1.ObjectMeta{Namespace: "namespace1", Name: "node--1-k8s-pod-eth0"}, - Spec: libapiv3.WorkloadEndpointSpec{ + Spec: internalapi.WorkloadEndpointSpec{ Node: "node-1", Orchestrator: "k8s", Pod: "pod", diff --git a/libcalico-go/lib/consistenthash/constants.go b/libcalico-go/lib/consistenthash/constants.go new file mode 100644 index 00000000000..d43056254aa --- /dev/null +++ b/libcalico-go/lib/consistenthash/constants.go @@ -0,0 +1,5 @@ +package consistenthash + +const ( + MaglevEndpointLUTFactor = 5 +) diff --git a/libcalico-go/lib/consistenthash/primes.go b/libcalico-go/lib/consistenthash/primes.go new file mode 100644 index 00000000000..7e7eb384cd5 --- /dev/null +++ b/libcalico-go/lib/consistenthash/primes.go @@ -0,0 +1,683 @@ +package consistenthash + +import ( + "sort" + + "github.com/sirupsen/logrus" +) + +// Every prime that can be represented in a uint16. +var pr = []uint16{ + 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, + 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, + 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, + 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, + 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, + 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, + 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, + 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, + 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, + 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, + 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, + 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, + 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, + 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, + 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, + 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, + 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, + 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, + 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, + 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, + 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, + 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, + 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, + 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, + 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, + 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, + 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, + 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, + 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, + 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, + 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, + 2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, + 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213, + 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, + 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, + 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, + 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, + 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, + 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, + 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, + 2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819, + 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903, + 2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, + 3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079, + 3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, + 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, + 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, + 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, + 3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, + 3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571, + 3581, 3583, 3593, 3607, 3613, 3617, 3623, 3631, 3637, 3643, + 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727, + 3733, 3739, 3761, 3767, 3769, 3779, 3793, 3797, 3803, 3821, + 3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, 3907, + 3911, 3917, 3919, 3923, 3929, 3931, 3943, 3947, 3967, 3989, + 4001, 4003, 4007, 4013, 4019, 4021, 4027, 4049, 4051, 4057, + 4073, 4079, 4091, 4093, 4099, 4111, 4127, 4129, 4133, 4139, + 4153, 4157, 4159, 4177, 4201, 4211, 4217, 4219, 4229, 4231, + 4241, 4243, 4253, 4259, 4261, 4271, 4273, 4283, 4289, 4297, + 4327, 4337, 4339, 4349, 4357, 4363, 4373, 4391, 4397, 4409, + 4421, 4423, 4441, 4447, 4451, 4457, 4463, 4481, 4483, 4493, + 4507, 4513, 4517, 4519, 4523, 4547, 4549, 4561, 4567, 4583, + 4591, 4597, 4603, 4621, 4637, 4639, 4643, 4649, 4651, 4657, + 4663, 4673, 4679, 4691, 4703, 4721, 4723, 4729, 4733, 4751, + 4759, 4783, 4787, 4789, 4793, 4799, 4801, 4813, 4817, 4831, + 4861, 4871, 4877, 4889, 4903, 4909, 4919, 4931, 4933, 4937, + 4943, 4951, 4957, 4967, 4969, 4973, 4987, 4993, 4999, 5003, + 5009, 5011, 5021, 5023, 5039, 5051, 5059, 5077, 5081, 5087, + 5099, 5101, 5107, 5113, 5119, 5147, 5153, 5167, 5171, 5179, + 5189, 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, 5279, + 5281, 5297, 5303, 5309, 5323, 5333, 5347, 5351, 5381, 5387, + 5393, 5399, 5407, 5413, 5417, 5419, 5431, 5437, 5441, 5443, + 5449, 5471, 5477, 5479, 5483, 5501, 5503, 5507, 5519, 5521, + 5527, 5531, 5557, 5563, 5569, 5573, 5581, 5591, 5623, 5639, + 5641, 5647, 5651, 5653, 5657, 5659, 5669, 5683, 5689, 5693, + 5701, 5711, 5717, 5737, 5741, 5743, 5749, 5779, 5783, 5791, + 5801, 5807, 5813, 5821, 5827, 5839, 5843, 5849, 5851, 5857, + 5861, 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, 5939, + 5953, 5981, 5987, 6007, 6011, 6029, 6037, 6043, 6047, 6053, + 6067, 6073, 6079, 6089, 6091, 6101, 6113, 6121, 6131, 6133, + 6143, 6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, 6221, + 6229, 6247, 6257, 6263, 6269, 6271, 6277, 6287, 6299, 6301, + 6311, 6317, 6323, 6329, 6337, 6343, 6353, 6359, 6361, 6367, + 6373, 6379, 6389, 6397, 6421, 6427, 6449, 6451, 6469, 6473, + 6481, 6491, 6521, 6529, 6547, 6551, 6553, 6563, 6569, 6571, + 6577, 6581, 6599, 6607, 6619, 6637, 6653, 6659, 6661, 6673, + 6679, 6689, 6691, 6701, 6703, 6709, 6719, 6733, 6737, 6761, + 6763, 6779, 6781, 6791, 6793, 6803, 6823, 6827, 6829, 6833, + 6841, 6857, 6863, 6869, 6871, 6883, 6899, 6907, 6911, 6917, + 6947, 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, 6997, + 7001, 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, 7103, + 7109, 7121, 7127, 7129, 7151, 7159, 7177, 7187, 7193, 7207, + 7211, 7213, 7219, 7229, 7237, 7243, 7247, 7253, 7283, 7297, + 7307, 7309, 7321, 7331, 7333, 7349, 7351, 7369, 7393, 7411, + 7417, 7433, 7451, 7457, 7459, 7477, 7481, 7487, 7489, 7499, + 7507, 7517, 7523, 7529, 7537, 7541, 7547, 7549, 7559, 7561, + 7573, 7577, 7583, 7589, 7591, 7603, 7607, 7621, 7639, 7643, + 7649, 7669, 7673, 7681, 7687, 7691, 7699, 7703, 7717, 7723, + 7727, 7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, 7829, + 7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, 7919, + 7927, 7933, 7937, 7949, 7951, 7963, 7993, 8009, 8011, 8017, + 8039, 8053, 8059, 8069, 8081, 8087, 8089, 8093, 8101, 8111, + 8117, 8123, 8147, 8161, 8167, 8171, 8179, 8191, 8209, 8219, + 8221, 8231, 8233, 8237, 8243, 8263, 8269, 8273, 8287, 8291, + 8293, 8297, 8311, 8317, 8329, 8353, 8363, 8369, 8377, 8387, + 8389, 8419, 8423, 8429, 8431, 8443, 8447, 8461, 8467, 8501, + 8513, 8521, 8527, 8537, 8539, 8543, 8563, 8573, 8581, 8597, + 8599, 8609, 8623, 8627, 8629, 8641, 8647, 8663, 8669, 8677, + 8681, 8689, 8693, 8699, 8707, 8713, 8719, 8731, 8737, 8741, + 8747, 8753, 8761, 8779, 8783, 8803, 8807, 8819, 8821, 8831, + 8837, 8839, 8849, 8861, 8863, 8867, 8887, 8893, 8923, 8929, + 8933, 8941, 8951, 8963, 8969, 8971, 8999, 9001, 9007, 9011, + 9013, 9029, 9041, 9043, 9049, 9059, 9067, 9091, 9103, 9109, + 9127, 9133, 9137, 9151, 9157, 9161, 9173, 9181, 9187, 9199, + 9203, 9209, 9221, 9227, 9239, 9241, 9257, 9277, 9281, 9283, + 9293, 9311, 9319, 9323, 9337, 9341, 9343, 9349, 9371, 9377, + 9391, 9397, 9403, 9413, 9419, 9421, 9431, 9433, 9437, 9439, + 9461, 9463, 9467, 9473, 9479, 9491, 9497, 9511, 9521, 9533, + 9539, 9547, 9551, 9587, 9601, 9613, 9619, 9623, 9629, 9631, + 9643, 9649, 9661, 9677, 9679, 9689, 9697, 9719, 9721, 9733, + 9739, 9743, 9749, 9767, 9769, 9781, 9787, 9791, 9803, 9811, + 9817, 9829, 9833, 9839, 9851, 9857, 9859, 9871, 9883, 9887, + 9901, 9907, 9923, 9929, 9931, 9941, 9949, 9967, 9973, 10007, + 10009, 10037, 10039, 10061, 10067, 10069, 10079, 10091, 10093, 10099, + 10103, 10111, 10133, 10139, 10141, 10151, 10159, 10163, 10169, 10177, + 10181, 10193, 10211, 10223, 10243, 10247, 10253, 10259, 10267, 10271, + 10273, 10289, 10301, 10303, 10313, 10321, 10331, 10333, 10337, 10343, + 10357, 10369, 10391, 10399, 10427, 10429, 10433, 10453, 10457, 10459, + 10463, 10477, 10487, 10499, 10501, 10513, 10529, 10531, 10559, 10567, + 10589, 10597, 10601, 10607, 10613, 10627, 10631, 10639, 10651, 10657, + 10663, 10667, 10687, 10691, 10709, 10711, 10723, 10729, 10733, 10739, + 10753, 10771, 10781, 10789, 10799, 10831, 10837, 10847, 10853, 10859, + 10861, 10867, 10883, 10889, 10891, 10903, 10909, 10937, 10939, 10949, + 10957, 10973, 10979, 10987, 10993, 11003, 11027, 11047, 11057, 11059, + 11069, 11071, 11083, 11087, 11093, 11113, 11117, 11119, 11131, 11149, + 11159, 11161, 11171, 11173, 11177, 11197, 11213, 11239, 11243, 11251, + 11257, 11261, 11273, 11279, 11287, 11299, 11311, 11317, 11321, 11329, + 11351, 11353, 11369, 11383, 11393, 11399, 11411, 11423, 11437, 11443, + 11447, 11467, 11471, 11483, 11489, 11491, 11497, 11503, 11519, 11527, + 11549, 11551, 11579, 11587, 11593, 11597, 11617, 11621, 11633, 11657, + 11677, 11681, 11689, 11699, 11701, 11717, 11719, 11731, 11743, 11777, + 11779, 11783, 11789, 11801, 11807, 11813, 11821, 11827, 11831, 11833, + 11839, 11863, 11867, 11887, 11897, 11903, 11909, 11923, 11927, 11933, + 11939, 11941, 11953, 11959, 11969, 11971, 11981, 11987, 12007, 12011, + 12037, 12041, 12043, 12049, 12071, 12073, 12097, 12101, 12107, 12109, + 12113, 12119, 12143, 12149, 12157, 12161, 12163, 12197, 12203, 12211, + 12227, 12239, 12241, 12251, 12253, 12263, 12269, 12277, 12281, 12289, + 12301, 12323, 12329, 12343, 12347, 12373, 12377, 12379, 12391, 12401, + 12409, 12413, 12421, 12433, 12437, 12451, 12457, 12473, 12479, 12487, + 12491, 12497, 12503, 12511, 12517, 12527, 12539, 12541, 12547, 12553, + 12569, 12577, 12583, 12589, 12601, 12611, 12613, 12619, 12637, 12641, + 12647, 12653, 12659, 12671, 12689, 12697, 12703, 12713, 12721, 12739, + 12743, 12757, 12763, 12781, 12791, 12799, 12809, 12821, 12823, 12829, + 12841, 12853, 12889, 12893, 12899, 12907, 12911, 12917, 12919, 12923, + 12941, 12953, 12959, 12967, 12973, 12979, 12983, 13001, 13003, 13007, + 13009, 13033, 13037, 13043, 13049, 13063, 13093, 13099, 13103, 13109, + 13121, 13127, 13147, 13151, 13159, 13163, 13171, 13177, 13183, 13187, + 13217, 13219, 13229, 13241, 13249, 13259, 13267, 13291, 13297, 13309, + 13313, 13327, 13331, 13337, 13339, 13367, 13381, 13397, 13399, 13411, + 13417, 13421, 13441, 13451, 13457, 13463, 13469, 13477, 13487, 13499, + 13513, 13523, 13537, 13553, 13567, 13577, 13591, 13597, 13613, 13619, + 13627, 13633, 13649, 13669, 13679, 13681, 13687, 13691, 13693, 13697, + 13709, 13711, 13721, 13723, 13729, 13751, 13757, 13759, 13763, 13781, + 13789, 13799, 13807, 13829, 13831, 13841, 13859, 13873, 13877, 13879, + 13883, 13901, 13903, 13907, 13913, 13921, 13931, 13933, 13963, 13967, + 13997, 13999, 14009, 14011, 14029, 14033, 14051, 14057, 14071, 14081, + 14083, 14087, 14107, 14143, 14149, 14153, 14159, 14173, 14177, 14197, + 14207, 14221, 14243, 14249, 14251, 14281, 14293, 14303, 14321, 14323, + 14327, 14341, 14347, 14369, 14387, 14389, 14401, 14407, 14411, 14419, + 14423, 14431, 14437, 14447, 14449, 14461, 14479, 14489, 14503, 14519, + 14533, 14537, 14543, 14549, 14551, 14557, 14561, 14563, 14591, 14593, + 14621, 14627, 14629, 14633, 14639, 14653, 14657, 14669, 14683, 14699, + 14713, 14717, 14723, 14731, 14737, 14741, 14747, 14753, 14759, 14767, + 14771, 14779, 14783, 14797, 14813, 14821, 14827, 14831, 14843, 14851, + 14867, 14869, 14879, 14887, 14891, 14897, 14923, 14929, 14939, 14947, + 14951, 14957, 14969, 14983, 15013, 15017, 15031, 15053, 15061, 15073, + 15077, 15083, 15091, 15101, 15107, 15121, 15131, 15137, 15139, 15149, + 15161, 15173, 15187, 15193, 15199, 15217, 15227, 15233, 15241, 15259, + 15263, 15269, 15271, 15277, 15287, 15289, 15299, 15307, 15313, 15319, + 15329, 15331, 15349, 15359, 15361, 15373, 15377, 15383, 15391, 15401, + 15413, 15427, 15439, 15443, 15451, 15461, 15467, 15473, 15493, 15497, + 15511, 15527, 15541, 15551, 15559, 15569, 15581, 15583, 15601, 15607, + 15619, 15629, 15641, 15643, 15647, 15649, 15661, 15667, 15671, 15679, + 15683, 15727, 15731, 15733, 15737, 15739, 15749, 15761, 15767, 15773, + 15787, 15791, 15797, 15803, 15809, 15817, 15823, 15859, 15877, 15881, + 15887, 15889, 15901, 15907, 15913, 15919, 15923, 15937, 15959, 15971, + 15973, 15991, 16001, 16007, 16033, 16057, 16061, 16063, 16067, 16069, + 16073, 16087, 16091, 16097, 16103, 16111, 16127, 16139, 16141, 16183, + 16187, 16189, 16193, 16217, 16223, 16229, 16231, 16249, 16253, 16267, + 16273, 16301, 16319, 16333, 16339, 16349, 16361, 16363, 16369, 16381, + 16411, 16417, 16421, 16427, 16433, 16447, 16451, 16453, 16477, 16481, + 16487, 16493, 16519, 16529, 16547, 16553, 16561, 16567, 16573, 16603, + 16607, 16619, 16631, 16633, 16649, 16651, 16657, 16661, 16673, 16691, + 16693, 16699, 16703, 16729, 16741, 16747, 16759, 16763, 16787, 16811, + 16823, 16829, 16831, 16843, 16871, 16879, 16883, 16889, 16901, 16903, + 16921, 16927, 16931, 16937, 16943, 16963, 16979, 16981, 16987, 16993, + 17011, 17021, 17027, 17029, 17033, 17041, 17047, 17053, 17077, 17093, + 17099, 17107, 17117, 17123, 17137, 17159, 17167, 17183, 17189, 17191, + 17203, 17207, 17209, 17231, 17239, 17257, 17291, 17293, 17299, 17317, + 17321, 17327, 17333, 17341, 17351, 17359, 17377, 17383, 17387, 17389, + 17393, 17401, 17417, 17419, 17431, 17443, 17449, 17467, 17471, 17477, + 17483, 17489, 17491, 17497, 17509, 17519, 17539, 17551, 17569, 17573, + 17579, 17581, 17597, 17599, 17609, 17623, 17627, 17657, 17659, 17669, + 17681, 17683, 17707, 17713, 17729, 17737, 17747, 17749, 17761, 17783, + 17789, 17791, 17807, 17827, 17837, 17839, 17851, 17863, 17881, 17891, + 17903, 17909, 17911, 17921, 17923, 17929, 17939, 17957, 17959, 17971, + 17977, 17981, 17987, 17989, 18013, 18041, 18043, 18047, 18049, 18059, + 18061, 18077, 18089, 18097, 18119, 18121, 18127, 18131, 18133, 18143, + 18149, 18169, 18181, 18191, 18199, 18211, 18217, 18223, 18229, 18233, + 18251, 18253, 18257, 18269, 18287, 18289, 18301, 18307, 18311, 18313, + 18329, 18341, 18353, 18367, 18371, 18379, 18397, 18401, 18413, 18427, + 18433, 18439, 18443, 18451, 18457, 18461, 18481, 18493, 18503, 18517, + 18521, 18523, 18539, 18541, 18553, 18583, 18587, 18593, 18617, 18637, + 18661, 18671, 18679, 18691, 18701, 18713, 18719, 18731, 18743, 18749, + 18757, 18773, 18787, 18793, 18797, 18803, 18839, 18859, 18869, 18899, + 18911, 18913, 18917, 18919, 18947, 18959, 18973, 18979, 19001, 19009, + 19013, 19031, 19037, 19051, 19069, 19073, 19079, 19081, 19087, 19121, + 19139, 19141, 19157, 19163, 19181, 19183, 19207, 19211, 19213, 19219, + 19231, 19237, 19249, 19259, 19267, 19273, 19289, 19301, 19309, 19319, + 19333, 19373, 19379, 19381, 19387, 19391, 19403, 19417, 19421, 19423, + 19427, 19429, 19433, 19441, 19447, 19457, 19463, 19469, 19471, 19477, + 19483, 19489, 19501, 19507, 19531, 19541, 19543, 19553, 19559, 19571, + 19577, 19583, 19597, 19603, 19609, 19661, 19681, 19687, 19697, 19699, + 19709, 19717, 19727, 19739, 19751, 19753, 19759, 19763, 19777, 19793, + 19801, 19813, 19819, 19841, 19843, 19853, 19861, 19867, 19889, 19891, + 19913, 19919, 19927, 19937, 19949, 19961, 19963, 19973, 19979, 19991, + 19993, 19997, 20011, 20021, 20023, 20029, 20047, 20051, 20063, 20071, + 20089, 20101, 20107, 20113, 20117, 20123, 20129, 20143, 20147, 20149, + 20161, 20173, 20177, 20183, 20201, 20219, 20231, 20233, 20249, 20261, + 20269, 20287, 20297, 20323, 20327, 20333, 20341, 20347, 20353, 20357, + 20359, 20369, 20389, 20393, 20399, 20407, 20411, 20431, 20441, 20443, + 20477, 20479, 20483, 20507, 20509, 20521, 20533, 20543, 20549, 20551, + 20563, 20593, 20599, 20611, 20627, 20639, 20641, 20663, 20681, 20693, + 20707, 20717, 20719, 20731, 20743, 20747, 20749, 20753, 20759, 20771, + 20773, 20789, 20807, 20809, 20849, 20857, 20873, 20879, 20887, 20897, + 20899, 20903, 20921, 20929, 20939, 20947, 20959, 20963, 20981, 20983, + 21001, 21011, 21013, 21017, 21019, 21023, 21031, 21059, 21061, 21067, + 21089, 21101, 21107, 21121, 21139, 21143, 21149, 21157, 21163, 21169, + 21179, 21187, 21191, 21193, 21211, 21221, 21227, 21247, 21269, 21277, + 21283, 21313, 21317, 21319, 21323, 21341, 21347, 21377, 21379, 21383, + 21391, 21397, 21401, 21407, 21419, 21433, 21467, 21481, 21487, 21491, + 21493, 21499, 21503, 21517, 21521, 21523, 21529, 21557, 21559, 21563, + 21569, 21577, 21587, 21589, 21599, 21601, 21611, 21613, 21617, 21647, + 21649, 21661, 21673, 21683, 21701, 21713, 21727, 21737, 21739, 21751, + 21757, 21767, 21773, 21787, 21799, 21803, 21817, 21821, 21839, 21841, + 21851, 21859, 21863, 21871, 21881, 21893, 21911, 21929, 21937, 21943, + 21961, 21977, 21991, 21997, 22003, 22013, 22027, 22031, 22037, 22039, + 22051, 22063, 22067, 22073, 22079, 22091, 22093, 22109, 22111, 22123, + 22129, 22133, 22147, 22153, 22157, 22159, 22171, 22189, 22193, 22229, + 22247, 22259, 22271, 22273, 22277, 22279, 22283, 22291, 22303, 22307, + 22343, 22349, 22367, 22369, 22381, 22391, 22397, 22409, 22433, 22441, + 22447, 22453, 22469, 22481, 22483, 22501, 22511, 22531, 22541, 22543, + 22549, 22567, 22571, 22573, 22613, 22619, 22621, 22637, 22639, 22643, + 22651, 22669, 22679, 22691, 22697, 22699, 22709, 22717, 22721, 22727, + 22739, 22741, 22751, 22769, 22777, 22783, 22787, 22807, 22811, 22817, + 22853, 22859, 22861, 22871, 22877, 22901, 22907, 22921, 22937, 22943, + 22961, 22963, 22973, 22993, 23003, 23011, 23017, 23021, 23027, 23029, + 23039, 23041, 23053, 23057, 23059, 23063, 23071, 23081, 23087, 23099, + 23117, 23131, 23143, 23159, 23167, 23173, 23189, 23197, 23201, 23203, + 23209, 23227, 23251, 23269, 23279, 23291, 23293, 23297, 23311, 23321, + 23327, 23333, 23339, 23357, 23369, 23371, 23399, 23417, 23431, 23447, + 23459, 23473, 23497, 23509, 23531, 23537, 23539, 23549, 23557, 23561, + 23563, 23567, 23581, 23593, 23599, 23603, 23609, 23623, 23627, 23629, + 23633, 23663, 23669, 23671, 23677, 23687, 23689, 23719, 23741, 23743, + 23747, 23753, 23761, 23767, 23773, 23789, 23801, 23813, 23819, 23827, + 23831, 23833, 23857, 23869, 23873, 23879, 23887, 23893, 23899, 23909, + 23911, 23917, 23929, 23957, 23971, 23977, 23981, 23993, 24001, 24007, + 24019, 24023, 24029, 24043, 24049, 24061, 24071, 24077, 24083, 24091, + 24097, 24103, 24107, 24109, 24113, 24121, 24133, 24137, 24151, 24169, + 24179, 24181, 24197, 24203, 24223, 24229, 24239, 24247, 24251, 24281, + 24317, 24329, 24337, 24359, 24371, 24373, 24379, 24391, 24407, 24413, + 24419, 24421, 24439, 24443, 24469, 24473, 24481, 24499, 24509, 24517, + 24527, 24533, 24547, 24551, 24571, 24593, 24611, 24623, 24631, 24659, + 24671, 24677, 24683, 24691, 24697, 24709, 24733, 24749, 24763, 24767, + 24781, 24793, 24799, 24809, 24821, 24841, 24847, 24851, 24859, 24877, + 24889, 24907, 24917, 24919, 24923, 24943, 24953, 24967, 24971, 24977, + 24979, 24989, 25013, 25031, 25033, 25037, 25057, 25073, 25087, 25097, + 25111, 25117, 25121, 25127, 25147, 25153, 25163, 25169, 25171, 25183, + 25189, 25219, 25229, 25237, 25243, 25247, 25253, 25261, 25301, 25303, + 25307, 25309, 25321, 25339, 25343, 25349, 25357, 25367, 25373, 25391, + 25409, 25411, 25423, 25439, 25447, 25453, 25457, 25463, 25469, 25471, + 25523, 25537, 25541, 25561, 25577, 25579, 25583, 25589, 25601, 25603, + 25609, 25621, 25633, 25639, 25643, 25657, 25667, 25673, 25679, 25693, + 25703, 25717, 25733, 25741, 25747, 25759, 25763, 25771, 25793, 25799, + 25801, 25819, 25841, 25847, 25849, 25867, 25873, 25889, 25903, 25913, + 25919, 25931, 25933, 25939, 25943, 25951, 25969, 25981, 25997, 25999, + 26003, 26017, 26021, 26029, 26041, 26053, 26083, 26099, 26107, 26111, + 26113, 26119, 26141, 26153, 26161, 26171, 26177, 26183, 26189, 26203, + 26209, 26227, 26237, 26249, 26251, 26261, 26263, 26267, 26293, 26297, + 26309, 26317, 26321, 26339, 26347, 26357, 26371, 26387, 26393, 26399, + 26407, 26417, 26423, 26431, 26437, 26449, 26459, 26479, 26489, 26497, + 26501, 26513, 26539, 26557, 26561, 26573, 26591, 26597, 26627, 26633, + 26641, 26647, 26669, 26681, 26683, 26687, 26693, 26699, 26701, 26711, + 26713, 26717, 26723, 26729, 26731, 26737, 26759, 26777, 26783, 26801, + 26813, 26821, 26833, 26839, 26849, 26861, 26863, 26879, 26881, 26891, + 26893, 26903, 26921, 26927, 26947, 26951, 26953, 26959, 26981, 26987, + 26993, 27011, 27017, 27031, 27043, 27059, 27061, 27067, 27073, 27077, + 27091, 27103, 27107, 27109, 27127, 27143, 27179, 27191, 27197, 27211, + 27239, 27241, 27253, 27259, 27271, 27277, 27281, 27283, 27299, 27329, + 27337, 27361, 27367, 27397, 27407, 27409, 27427, 27431, 27437, 27449, + 27457, 27479, 27481, 27487, 27509, 27527, 27529, 27539, 27541, 27551, + 27581, 27583, 27611, 27617, 27631, 27647, 27653, 27673, 27689, 27691, + 27697, 27701, 27733, 27737, 27739, 27743, 27749, 27751, 27763, 27767, + 27773, 27779, 27791, 27793, 27799, 27803, 27809, 27817, 27823, 27827, + 27847, 27851, 27883, 27893, 27901, 27917, 27919, 27941, 27943, 27947, + 27953, 27961, 27967, 27983, 27997, 28001, 28019, 28027, 28031, 28051, + 28057, 28069, 28081, 28087, 28097, 28099, 28109, 28111, 28123, 28151, + 28163, 28181, 28183, 28201, 28211, 28219, 28229, 28277, 28279, 28283, + 28289, 28297, 28307, 28309, 28319, 28349, 28351, 28387, 28393, 28403, + 28409, 28411, 28429, 28433, 28439, 28447, 28463, 28477, 28493, 28499, + 28513, 28517, 28537, 28541, 28547, 28549, 28559, 28571, 28573, 28579, + 28591, 28597, 28603, 28607, 28619, 28621, 28627, 28631, 28643, 28649, + 28657, 28661, 28663, 28669, 28687, 28697, 28703, 28711, 28723, 28729, + 28751, 28753, 28759, 28771, 28789, 28793, 28807, 28813, 28817, 28837, + 28843, 28859, 28867, 28871, 28879, 28901, 28909, 28921, 28927, 28933, + 28949, 28961, 28979, 29009, 29017, 29021, 29023, 29027, 29033, 29059, + 29063, 29077, 29101, 29123, 29129, 29131, 29137, 29147, 29153, 29167, + 29173, 29179, 29191, 29201, 29207, 29209, 29221, 29231, 29243, 29251, + 29269, 29287, 29297, 29303, 29311, 29327, 29333, 29339, 29347, 29363, + 29383, 29387, 29389, 29399, 29401, 29411, 29423, 29429, 29437, 29443, + 29453, 29473, 29483, 29501, 29527, 29531, 29537, 29567, 29569, 29573, + 29581, 29587, 29599, 29611, 29629, 29633, 29641, 29663, 29669, 29671, + 29683, 29717, 29723, 29741, 29753, 29759, 29761, 29789, 29803, 29819, + 29833, 29837, 29851, 29863, 29867, 29873, 29879, 29881, 29917, 29921, + 29927, 29947, 29959, 29983, 29989, 30011, 30013, 30029, 30047, 30059, + 30071, 30089, 30091, 30097, 30103, 30109, 30113, 30119, 30133, 30137, + 30139, 30161, 30169, 30181, 30187, 30197, 30203, 30211, 30223, 30241, + 30253, 30259, 30269, 30271, 30293, 30307, 30313, 30319, 30323, 30341, + 30347, 30367, 30389, 30391, 30403, 30427, 30431, 30449, 30467, 30469, + 30491, 30493, 30497, 30509, 30517, 30529, 30539, 30553, 30557, 30559, + 30577, 30593, 30631, 30637, 30643, 30649, 30661, 30671, 30677, 30689, + 30697, 30703, 30707, 30713, 30727, 30757, 30763, 30773, 30781, 30803, + 30809, 30817, 30829, 30839, 30841, 30851, 30853, 30859, 30869, 30871, + 30881, 30893, 30911, 30931, 30937, 30941, 30949, 30971, 30977, 30983, + 31013, 31019, 31033, 31039, 31051, 31063, 31069, 31079, 31081, 31091, + 31121, 31123, 31139, 31147, 31151, 31153, 31159, 31177, 31181, 31183, + 31189, 31193, 31219, 31223, 31231, 31237, 31247, 31249, 31253, 31259, + 31267, 31271, 31277, 31307, 31319, 31321, 31327, 31333, 31337, 31357, + 31379, 31387, 31391, 31393, 31397, 31469, 31477, 31481, 31489, 31511, + 31513, 31517, 31531, 31541, 31543, 31547, 31567, 31573, 31583, 31601, + 31607, 31627, 31643, 31649, 31657, 31663, 31667, 31687, 31699, 31721, + 31723, 31727, 31729, 31741, 31751, 31769, 31771, 31793, 31799, 31817, + 31847, 31849, 31859, 31873, 31883, 31891, 31907, 31957, 31963, 31973, + 31981, 31991, 32003, 32009, 32027, 32029, 32051, 32057, 32059, 32063, + 32069, 32077, 32083, 32089, 32099, 32117, 32119, 32141, 32143, 32159, + 32173, 32183, 32189, 32191, 32203, 32213, 32233, 32237, 32251, 32257, + 32261, 32297, 32299, 32303, 32309, 32321, 32323, 32327, 32341, 32353, + 32359, 32363, 32369, 32371, 32377, 32381, 32401, 32411, 32413, 32423, + 32429, 32441, 32443, 32467, 32479, 32491, 32497, 32503, 32507, 32531, + 32533, 32537, 32561, 32563, 32569, 32573, 32579, 32587, 32603, 32609, + 32611, 32621, 32633, 32647, 32653, 32687, 32693, 32707, 32713, 32717, + 32719, 32749, 32771, 32779, 32783, 32789, 32797, 32801, 32803, 32831, + 32833, 32839, 32843, 32869, 32887, 32909, 32911, 32917, 32933, 32939, + 32941, 32957, 32969, 32971, 32983, 32987, 32993, 32999, 33013, 33023, + 33029, 33037, 33049, 33053, 33071, 33073, 33083, 33091, 33107, 33113, + 33119, 33149, 33151, 33161, 33179, 33181, 33191, 33199, 33203, 33211, + 33223, 33247, 33287, 33289, 33301, 33311, 33317, 33329, 33331, 33343, + 33347, 33349, 33353, 33359, 33377, 33391, 33403, 33409, 33413, 33427, + 33457, 33461, 33469, 33479, 33487, 33493, 33503, 33521, 33529, 33533, + 33547, 33563, 33569, 33577, 33581, 33587, 33589, 33599, 33601, 33613, + 33617, 33619, 33623, 33629, 33637, 33641, 33647, 33679, 33703, 33713, + 33721, 33739, 33749, 33751, 33757, 33767, 33769, 33773, 33791, 33797, + 33809, 33811, 33827, 33829, 33851, 33857, 33863, 33871, 33889, 33893, + 33911, 33923, 33931, 33937, 33941, 33961, 33967, 33997, 34019, 34031, + 34033, 34039, 34057, 34061, 34123, 34127, 34129, 34141, 34147, 34157, + 34159, 34171, 34183, 34211, 34213, 34217, 34231, 34253, 34259, 34261, + 34267, 34273, 34283, 34297, 34301, 34303, 34313, 34319, 34327, 34337, + 34351, 34361, 34367, 34369, 34381, 34403, 34421, 34429, 34439, 34457, + 34469, 34471, 34483, 34487, 34499, 34501, 34511, 34513, 34519, 34537, + 34543, 34549, 34583, 34589, 34591, 34603, 34607, 34613, 34631, 34649, + 34651, 34667, 34673, 34679, 34687, 34693, 34703, 34721, 34729, 34739, + 34747, 34757, 34759, 34763, 34781, 34807, 34819, 34841, 34843, 34847, + 34849, 34871, 34877, 34883, 34897, 34913, 34919, 34939, 34949, 34961, + 34963, 34981, 35023, 35027, 35051, 35053, 35059, 35069, 35081, 35083, + 35089, 35099, 35107, 35111, 35117, 35129, 35141, 35149, 35153, 35159, + 35171, 35201, 35221, 35227, 35251, 35257, 35267, 35279, 35281, 35291, + 35311, 35317, 35323, 35327, 35339, 35353, 35363, 35381, 35393, 35401, + 35407, 35419, 35423, 35437, 35447, 35449, 35461, 35491, 35507, 35509, + 35521, 35527, 35531, 35533, 35537, 35543, 35569, 35573, 35591, 35593, + 35597, 35603, 35617, 35671, 35677, 35729, 35731, 35747, 35753, 35759, + 35771, 35797, 35801, 35803, 35809, 35831, 35837, 35839, 35851, 35863, + 35869, 35879, 35897, 35899, 35911, 35923, 35933, 35951, 35963, 35969, + 35977, 35983, 35993, 35999, 36007, 36011, 36013, 36017, 36037, 36061, + 36067, 36073, 36083, 36097, 36107, 36109, 36131, 36137, 36151, 36161, + 36187, 36191, 36209, 36217, 36229, 36241, 36251, 36263, 36269, 36277, + 36293, 36299, 36307, 36313, 36319, 36341, 36343, 36353, 36373, 36383, + 36389, 36433, 36451, 36457, 36467, 36469, 36473, 36479, 36493, 36497, + 36523, 36527, 36529, 36541, 36551, 36559, 36563, 36571, 36583, 36587, + 36599, 36607, 36629, 36637, 36643, 36653, 36671, 36677, 36683, 36691, + 36697, 36709, 36713, 36721, 36739, 36749, 36761, 36767, 36779, 36781, + 36787, 36791, 36793, 36809, 36821, 36833, 36847, 36857, 36871, 36877, + 36887, 36899, 36901, 36913, 36919, 36923, 36929, 36931, 36943, 36947, + 36973, 36979, 36997, 37003, 37013, 37019, 37021, 37039, 37049, 37057, + 37061, 37087, 37097, 37117, 37123, 37139, 37159, 37171, 37181, 37189, + 37199, 37201, 37217, 37223, 37243, 37253, 37273, 37277, 37307, 37309, + 37313, 37321, 37337, 37339, 37357, 37361, 37363, 37369, 37379, 37397, + 37409, 37423, 37441, 37447, 37463, 37483, 37489, 37493, 37501, 37507, + 37511, 37517, 37529, 37537, 37547, 37549, 37561, 37567, 37571, 37573, + 37579, 37589, 37591, 37607, 37619, 37633, 37643, 37649, 37657, 37663, + 37691, 37693, 37699, 37717, 37747, 37781, 37783, 37799, 37811, 37813, + 37831, 37847, 37853, 37861, 37871, 37879, 37889, 37897, 37907, 37951, + 37957, 37963, 37967, 37987, 37991, 37993, 37997, 38011, 38039, 38047, + 38053, 38069, 38083, 38113, 38119, 38149, 38153, 38167, 38177, 38183, + 38189, 38197, 38201, 38219, 38231, 38237, 38239, 38261, 38273, 38281, + 38287, 38299, 38303, 38317, 38321, 38327, 38329, 38333, 38351, 38371, + 38377, 38393, 38431, 38447, 38449, 38453, 38459, 38461, 38501, 38543, + 38557, 38561, 38567, 38569, 38593, 38603, 38609, 38611, 38629, 38639, + 38651, 38653, 38669, 38671, 38677, 38693, 38699, 38707, 38711, 38713, + 38723, 38729, 38737, 38747, 38749, 38767, 38783, 38791, 38803, 38821, + 38833, 38839, 38851, 38861, 38867, 38873, 38891, 38903, 38917, 38921, + 38923, 38933, 38953, 38959, 38971, 38977, 38993, 39019, 39023, 39041, + 39043, 39047, 39079, 39089, 39097, 39103, 39107, 39113, 39119, 39133, + 39139, 39157, 39161, 39163, 39181, 39191, 39199, 39209, 39217, 39227, + 39229, 39233, 39239, 39241, 39251, 39293, 39301, 39313, 39317, 39323, + 39341, 39343, 39359, 39367, 39371, 39373, 39383, 39397, 39409, 39419, + 39439, 39443, 39451, 39461, 39499, 39503, 39509, 39511, 39521, 39541, + 39551, 39563, 39569, 39581, 39607, 39619, 39623, 39631, 39659, 39667, + 39671, 39679, 39703, 39709, 39719, 39727, 39733, 39749, 39761, 39769, + 39779, 39791, 39799, 39821, 39827, 39829, 39839, 39841, 39847, 39857, + 39863, 39869, 39877, 39883, 39887, 39901, 39929, 39937, 39953, 39971, + 39979, 39983, 39989, 40009, 40013, 40031, 40037, 40039, 40063, 40087, + 40093, 40099, 40111, 40123, 40127, 40129, 40151, 40153, 40163, 40169, + 40177, 40189, 40193, 40213, 40231, 40237, 40241, 40253, 40277, 40283, + 40289, 40343, 40351, 40357, 40361, 40387, 40423, 40427, 40429, 40433, + 40459, 40471, 40483, 40487, 40493, 40499, 40507, 40519, 40529, 40531, + 40543, 40559, 40577, 40583, 40591, 40597, 40609, 40627, 40637, 40639, + 40693, 40697, 40699, 40709, 40739, 40751, 40759, 40763, 40771, 40787, + 40801, 40813, 40819, 40823, 40829, 40841, 40847, 40849, 40853, 40867, + 40879, 40883, 40897, 40903, 40927, 40933, 40939, 40949, 40961, 40973, + 40993, 41011, 41017, 41023, 41039, 41047, 41051, 41057, 41077, 41081, + 41113, 41117, 41131, 41141, 41143, 41149, 41161, 41177, 41179, 41183, + 41189, 41201, 41203, 41213, 41221, 41227, 41231, 41233, 41243, 41257, + 41263, 41269, 41281, 41299, 41333, 41341, 41351, 41357, 41381, 41387, + 41389, 41399, 41411, 41413, 41443, 41453, 41467, 41479, 41491, 41507, + 41513, 41519, 41521, 41539, 41543, 41549, 41579, 41593, 41597, 41603, + 41609, 41611, 41617, 41621, 41627, 41641, 41647, 41651, 41659, 41669, + 41681, 41687, 41719, 41729, 41737, 41759, 41761, 41771, 41777, 41801, + 41809, 41813, 41843, 41849, 41851, 41863, 41879, 41887, 41893, 41897, + 41903, 41911, 41927, 41941, 41947, 41953, 41957, 41959, 41969, 41981, + 41983, 41999, 42013, 42017, 42019, 42023, 42043, 42061, 42071, 42073, + 42083, 42089, 42101, 42131, 42139, 42157, 42169, 42179, 42181, 42187, + 42193, 42197, 42209, 42221, 42223, 42227, 42239, 42257, 42281, 42283, + 42293, 42299, 42307, 42323, 42331, 42337, 42349, 42359, 42373, 42379, + 42391, 42397, 42403, 42407, 42409, 42433, 42437, 42443, 42451, 42457, + 42461, 42463, 42467, 42473, 42487, 42491, 42499, 42509, 42533, 42557, + 42569, 42571, 42577, 42589, 42611, 42641, 42643, 42649, 42667, 42677, + 42683, 42689, 42697, 42701, 42703, 42709, 42719, 42727, 42737, 42743, + 42751, 42767, 42773, 42787, 42793, 42797, 42821, 42829, 42839, 42841, + 42853, 42859, 42863, 42899, 42901, 42923, 42929, 42937, 42943, 42953, + 42961, 42967, 42979, 42989, 43003, 43013, 43019, 43037, 43049, 43051, + 43063, 43067, 43093, 43103, 43117, 43133, 43151, 43159, 43177, 43189, + 43201, 43207, 43223, 43237, 43261, 43271, 43283, 43291, 43313, 43319, + 43321, 43331, 43391, 43397, 43399, 43403, 43411, 43427, 43441, 43451, + 43457, 43481, 43487, 43499, 43517, 43541, 43543, 43573, 43577, 43579, + 43591, 43597, 43607, 43609, 43613, 43627, 43633, 43649, 43651, 43661, + 43669, 43691, 43711, 43717, 43721, 43753, 43759, 43777, 43781, 43783, + 43787, 43789, 43793, 43801, 43853, 43867, 43889, 43891, 43913, 43933, + 43943, 43951, 43961, 43963, 43969, 43973, 43987, 43991, 43997, 44017, + 44021, 44027, 44029, 44041, 44053, 44059, 44071, 44087, 44089, 44101, + 44111, 44119, 44123, 44129, 44131, 44159, 44171, 44179, 44189, 44201, + 44203, 44207, 44221, 44249, 44257, 44263, 44267, 44269, 44273, 44279, + 44281, 44293, 44351, 44357, 44371, 44381, 44383, 44389, 44417, 44449, + 44453, 44483, 44491, 44497, 44501, 44507, 44519, 44531, 44533, 44537, + 44543, 44549, 44563, 44579, 44587, 44617, 44621, 44623, 44633, 44641, + 44647, 44651, 44657, 44683, 44687, 44699, 44701, 44711, 44729, 44741, + 44753, 44771, 44773, 44777, 44789, 44797, 44809, 44819, 44839, 44843, + 44851, 44867, 44879, 44887, 44893, 44909, 44917, 44927, 44939, 44953, + 44959, 44963, 44971, 44983, 44987, 45007, 45013, 45053, 45061, 45077, + 45083, 45119, 45121, 45127, 45131, 45137, 45139, 45161, 45179, 45181, + 45191, 45197, 45233, 45247, 45259, 45263, 45281, 45289, 45293, 45307, + 45317, 45319, 45329, 45337, 45341, 45343, 45361, 45377, 45389, 45403, + 45413, 45427, 45433, 45439, 45481, 45491, 45497, 45503, 45523, 45533, + 45541, 45553, 45557, 45569, 45587, 45589, 45599, 45613, 45631, 45641, + 45659, 45667, 45673, 45677, 45691, 45697, 45707, 45737, 45751, 45757, + 45763, 45767, 45779, 45817, 45821, 45823, 45827, 45833, 45841, 45853, + 45863, 45869, 45887, 45893, 45943, 45949, 45953, 45959, 45971, 45979, + 45989, 46021, 46027, 46049, 46051, 46061, 46073, 46091, 46093, 46099, + 46103, 46133, 46141, 46147, 46153, 46171, 46181, 46183, 46187, 46199, + 46219, 46229, 46237, 46261, 46271, 46273, 46279, 46301, 46307, 46309, + 46327, 46337, 46349, 46351, 46381, 46399, 46411, 46439, 46441, 46447, + 46451, 46457, 46471, 46477, 46489, 46499, 46507, 46511, 46523, 46549, + 46559, 46567, 46573, 46589, 46591, 46601, 46619, 46633, 46639, 46643, + 46649, 46663, 46679, 46681, 46687, 46691, 46703, 46723, 46727, 46747, + 46751, 46757, 46769, 46771, 46807, 46811, 46817, 46819, 46829, 46831, + 46853, 46861, 46867, 46877, 46889, 46901, 46919, 46933, 46957, 46993, + 46997, 47017, 47041, 47051, 47057, 47059, 47087, 47093, 47111, 47119, + 47123, 47129, 47137, 47143, 47147, 47149, 47161, 47189, 47207, 47221, + 47237, 47251, 47269, 47279, 47287, 47293, 47297, 47303, 47309, 47317, + 47339, 47351, 47353, 47363, 47381, 47387, 47389, 47407, 47417, 47419, + 47431, 47441, 47459, 47491, 47497, 47501, 47507, 47513, 47521, 47527, + 47533, 47543, 47563, 47569, 47581, 47591, 47599, 47609, 47623, 47629, + 47639, 47653, 47657, 47659, 47681, 47699, 47701, 47711, 47713, 47717, + 47737, 47741, 47743, 47777, 47779, 47791, 47797, 47807, 47809, 47819, + 47837, 47843, 47857, 47869, 47881, 47903, 47911, 47917, 47933, 47939, + 47947, 47951, 47963, 47969, 47977, 47981, 48017, 48023, 48029, 48049, + 48073, 48079, 48091, 48109, 48119, 48121, 48131, 48157, 48163, 48179, + 48187, 48193, 48197, 48221, 48239, 48247, 48259, 48271, 48281, 48299, + 48311, 48313, 48337, 48341, 48353, 48371, 48383, 48397, 48407, 48409, + 48413, 48437, 48449, 48463, 48473, 48479, 48481, 48487, 48491, 48497, + 48523, 48527, 48533, 48539, 48541, 48563, 48571, 48589, 48593, 48611, + 48619, 48623, 48647, 48649, 48661, 48673, 48677, 48679, 48731, 48733, + 48751, 48757, 48761, 48767, 48779, 48781, 48787, 48799, 48809, 48817, + 48821, 48823, 48847, 48857, 48859, 48869, 48871, 48883, 48889, 48907, + 48947, 48953, 48973, 48989, 48991, 49003, 49009, 49019, 49031, 49033, + 49037, 49043, 49057, 49069, 49081, 49103, 49109, 49117, 49121, 49123, + 49139, 49157, 49169, 49171, 49177, 49193, 49199, 49201, 49207, 49211, + 49223, 49253, 49261, 49277, 49279, 49297, 49307, 49331, 49333, 49339, + 49363, 49367, 49369, 49391, 49393, 49409, 49411, 49417, 49429, 49433, + 49451, 49459, 49463, 49477, 49481, 49499, 49523, 49529, 49531, 49537, + 49547, 49549, 49559, 49597, 49603, 49613, 49627, 49633, 49639, 49663, + 49667, 49669, 49681, 49697, 49711, 49727, 49739, 49741, 49747, 49757, + 49783, 49787, 49789, 49801, 49807, 49811, 49823, 49831, 49843, 49853, + 49871, 49877, 49891, 49919, 49921, 49927, 49937, 49939, 49943, 49957, + 49991, 49993, 49999, 50021, 50023, 50033, 50047, 50051, 50053, 50069, + 50077, 50087, 50093, 50101, 50111, 50119, 50123, 50129, 50131, 50147, + 50153, 50159, 50177, 50207, 50221, 50227, 50231, 50261, 50263, 50273, + 50287, 50291, 50311, 50321, 50329, 50333, 50341, 50359, 50363, 50377, + 50383, 50387, 50411, 50417, 50423, 50441, 50459, 50461, 50497, 50503, + 50513, 50527, 50539, 50543, 50549, 50551, 50581, 50587, 50591, 50593, + 50599, 50627, 50647, 50651, 50671, 50683, 50707, 50723, 50741, 50753, + 50767, 50773, 50777, 50789, 50821, 50833, 50839, 50849, 50857, 50867, + 50873, 50891, 50893, 50909, 50923, 50929, 50951, 50957, 50969, 50971, + 50989, 50993, 51001, 51031, 51043, 51047, 51059, 51061, 51071, 51109, + 51131, 51133, 51137, 51151, 51157, 51169, 51193, 51197, 51199, 51203, + 51217, 51229, 51239, 51241, 51257, 51263, 51283, 51287, 51307, 51329, + 51341, 51343, 51347, 51349, 51361, 51383, 51407, 51413, 51419, 51421, + 51427, 51431, 51437, 51439, 51449, 51461, 51473, 51479, 51481, 51487, + 51503, 51511, 51517, 51521, 51539, 51551, 51563, 51577, 51581, 51593, + 51599, 51607, 51613, 51631, 51637, 51647, 51659, 51673, 51679, 51683, + 51691, 51713, 51719, 51721, 51749, 51767, 51769, 51787, 51797, 51803, + 51817, 51827, 51829, 51839, 51853, 51859, 51869, 51871, 51893, 51899, + 51907, 51913, 51929, 51941, 51949, 51971, 51973, 51977, 51991, 52009, + 52021, 52027, 52051, 52057, 52067, 52069, 52081, 52103, 52121, 52127, + 52147, 52153, 52163, 52177, 52181, 52183, 52189, 52201, 52223, 52237, + 52249, 52253, 52259, 52267, 52289, 52291, 52301, 52313, 52321, 52361, + 52363, 52369, 52379, 52387, 52391, 52433, 52453, 52457, 52489, 52501, + 52511, 52517, 52529, 52541, 52543, 52553, 52561, 52567, 52571, 52579, + 52583, 52609, 52627, 52631, 52639, 52667, 52673, 52691, 52697, 52709, + 52711, 52721, 52727, 52733, 52747, 52757, 52769, 52783, 52807, 52813, + 52817, 52837, 52859, 52861, 52879, 52883, 52889, 52901, 52903, 52919, + 52937, 52951, 52957, 52963, 52967, 52973, 52981, 52999, 53003, 53017, + 53047, 53051, 53069, 53077, 53087, 53089, 53093, 53101, 53113, 53117, + 53129, 53147, 53149, 53161, 53171, 53173, 53189, 53197, 53201, 53231, + 53233, 53239, 53267, 53269, 53279, 53281, 53299, 53309, 53323, 53327, + 53353, 53359, 53377, 53381, 53401, 53407, 53411, 53419, 53437, 53441, + 53453, 53479, 53503, 53507, 53527, 53549, 53551, 53569, 53591, 53593, + 53597, 53609, 53611, 53617, 53623, 53629, 53633, 53639, 53653, 53657, + 53681, 53693, 53699, 53717, 53719, 53731, 53759, 53773, 53777, 53783, + 53791, 53813, 53819, 53831, 53849, 53857, 53861, 53881, 53887, 53891, + 53897, 53899, 53917, 53923, 53927, 53939, 53951, 53959, 53987, 53993, + 54001, 54011, 54013, 54037, 54049, 54059, 54083, 54091, 54101, 54121, + 54133, 54139, 54151, 54163, 54167, 54181, 54193, 54217, 54251, 54269, + 54277, 54287, 54293, 54311, 54319, 54323, 54331, 54347, 54361, 54367, + 54371, 54377, 54401, 54403, 54409, 54413, 54419, 54421, 54437, 54443, + 54449, 54469, 54493, 54497, 54499, 54503, 54517, 54521, 54539, 54541, + 54547, 54559, 54563, 54577, 54581, 54583, 54601, 54617, 54623, 54629, + 54631, 54647, 54667, 54673, 54679, 54709, 54713, 54721, 54727, 54751, + 54767, 54773, 54779, 54787, 54799, 54829, 54833, 54851, 54869, 54877, + 54881, 54907, 54917, 54919, 54941, 54949, 54959, 54973, 54979, 54983, + 55001, 55009, 55021, 55049, 55051, 55057, 55061, 55073, 55079, 55103, + 55109, 55117, 55127, 55147, 55163, 55171, 55201, 55207, 55213, 55217, + 55219, 55229, 55243, 55249, 55259, 55291, 55313, 55331, 55333, 55337, + 55339, 55343, 55351, 55373, 55381, 55399, 55411, 55439, 55441, 55457, + 55469, 55487, 55501, 55511, 55529, 55541, 55547, 55579, 55589, 55603, + 55609, 55619, 55621, 55631, 55633, 55639, 55661, 55663, 55667, 55673, + 55681, 55691, 55697, 55711, 55717, 55721, 55733, 55763, 55787, 55793, + 55799, 55807, 55813, 55817, 55819, 55823, 55829, 55837, 55843, 55849, + 55871, 55889, 55897, 55901, 55903, 55921, 55927, 55931, 55933, 55949, + 55967, 55987, 55997, 56003, 56009, 56039, 56041, 56053, 56081, 56087, + 56093, 56099, 56101, 56113, 56123, 56131, 56149, 56167, 56171, 56179, + 56197, 56207, 56209, 56237, 56239, 56249, 56263, 56267, 56269, 56299, + 56311, 56333, 56359, 56369, 56377, 56383, 56393, 56401, 56417, 56431, + 56437, 56443, 56453, 56467, 56473, 56477, 56479, 56489, 56501, 56503, + 56509, 56519, 56527, 56531, 56533, 56543, 56569, 56591, 56597, 56599, + 56611, 56629, 56633, 56659, 56663, 56671, 56681, 56687, 56701, 56711, + 56713, 56731, 56737, 56747, 56767, 56773, 56779, 56783, 56807, 56809, + 56813, 56821, 56827, 56843, 56857, 56873, 56891, 56893, 56897, 56909, + 56911, 56921, 56923, 56929, 56941, 56951, 56957, 56963, 56983, 56989, + 56993, 56999, 57037, 57041, 57047, 57059, 57073, 57077, 57089, 57097, + 57107, 57119, 57131, 57139, 57143, 57149, 57163, 57173, 57179, 57191, + 57193, 57203, 57221, 57223, 57241, 57251, 57259, 57269, 57271, 57283, + 57287, 57301, 57329, 57331, 57347, 57349, 57367, 57373, 57383, 57389, + 57397, 57413, 57427, 57457, 57467, 57487, 57493, 57503, 57527, 57529, + 57557, 57559, 57571, 57587, 57593, 57601, 57637, 57641, 57649, 57653, + 57667, 57679, 57689, 57697, 57709, 57713, 57719, 57727, 57731, 57737, + 57751, 57773, 57781, 57787, 57791, 57793, 57803, 57809, 57829, 57839, + 57847, 57853, 57859, 57881, 57899, 57901, 57917, 57923, 57943, 57947, + 57973, 57977, 57991, 58013, 58027, 58031, 58043, 58049, 58057, 58061, + 58067, 58073, 58099, 58109, 58111, 58129, 58147, 58151, 58153, 58169, + 58171, 58189, 58193, 58199, 58207, 58211, 58217, 58229, 58231, 58237, + 58243, 58271, 58309, 58313, 58321, 58337, 58363, 58367, 58369, 58379, + 58391, 58393, 58403, 58411, 58417, 58427, 58439, 58441, 58451, 58453, + 58477, 58481, 58511, 58537, 58543, 58549, 58567, 58573, 58579, 58601, + 58603, 58613, 58631, 58657, 58661, 58679, 58687, 58693, 58699, 58711, + 58727, 58733, 58741, 58757, 58763, 58771, 58787, 58789, 58831, 58889, + 58897, 58901, 58907, 58909, 58913, 58921, 58937, 58943, 58963, 58967, + 58979, 58991, 58997, 59009, 59011, 59021, 59023, 59029, 59051, 59053, + 59063, 59069, 59077, 59083, 59093, 59107, 59113, 59119, 59123, 59141, + 59149, 59159, 59167, 59183, 59197, 59207, 59209, 59219, 59221, 59233, + 59239, 59243, 59263, 59273, 59281, 59333, 59341, 59351, 59357, 59359, + 59369, 59377, 59387, 59393, 59399, 59407, 59417, 59419, 59441, 59443, + 59447, 59453, 59467, 59471, 59473, 59497, 59509, 59513, 59539, 59557, + 59561, 59567, 59581, 59611, 59617, 59621, 59627, 59629, 59651, 59659, + 59663, 59669, 59671, 59693, 59699, 59707, 59723, 59729, 59743, 59747, + 59753, 59771, 59779, 59791, 59797, 59809, 59833, 59863, 59879, 59887, + 59921, 59929, 59951, 59957, 59971, 59981, 59999, 60013, 60017, 60029, + 60037, 60041, 60077, 60083, 60089, 60091, 60101, 60103, 60107, 60127, + 60133, 60139, 60149, 60161, 60167, 60169, 60209, 60217, 60223, 60251, + 60257, 60259, 60271, 60289, 60293, 60317, 60331, 60337, 60343, 60353, + 60373, 60383, 60397, 60413, 60427, 60443, 60449, 60457, 60493, 60497, + 60509, 60521, 60527, 60539, 60589, 60601, 60607, 60611, 60617, 60623, + 60631, 60637, 60647, 60649, 60659, 60661, 60679, 60689, 60703, 60719, + 60727, 60733, 60737, 60757, 60761, 60763, 60773, 60779, 60793, 60811, + 60821, 60859, 60869, 60887, 60889, 60899, 60901, 60913, 60917, 60919, + 60923, 60937, 60943, 60953, 60961, 61001, 61007, 61027, 61031, 61043, + 61051, 61057, 61091, 61099, 61121, 61129, 61141, 61151, 61153, 61169, + 61211, 61223, 61231, 61253, 61261, 61283, 61291, 61297, 61331, 61333, + 61339, 61343, 61357, 61363, 61379, 61381, 61403, 61409, 61417, 61441, + 61463, 61469, 61471, 61483, 61487, 61493, 61507, 61511, 61519, 61543, + 61547, 61553, 61559, 61561, 61583, 61603, 61609, 61613, 61627, 61631, + 61637, 61643, 61651, 61657, 61667, 61673, 61681, 61687, 61703, 61717, + 61723, 61729, 61751, 61757, 61781, 61813, 61819, 61837, 61843, 61861, + 61871, 61879, 61909, 61927, 61933, 61949, 61961, 61967, 61979, 61981, + 61987, 61991, 62003, 62011, 62017, 62039, 62047, 62053, 62057, 62071, + 62081, 62099, 62119, 62129, 62131, 62137, 62141, 62143, 62171, 62189, + 62191, 62201, 62207, 62213, 62219, 62233, 62273, 62297, 62299, 62303, + 62311, 62323, 62327, 62347, 62351, 62383, 62401, 62417, 62423, 62459, + 62467, 62473, 62477, 62483, 62497, 62501, 62507, 62533, 62539, 62549, + 62563, 62581, 62591, 62597, 62603, 62617, 62627, 62633, 62639, 62653, + 62659, 62683, 62687, 62701, 62723, 62731, 62743, 62753, 62761, 62773, + 62791, 62801, 62819, 62827, 62851, 62861, 62869, 62873, 62897, 62903, + 62921, 62927, 62929, 62939, 62969, 62971, 62981, 62983, 62987, 62989, + 63029, 63031, 63059, 63067, 63073, 63079, 63097, 63103, 63113, 63127, + 63131, 63149, 63179, 63197, 63199, 63211, 63241, 63247, 63277, 63281, + 63299, 63311, 63313, 63317, 63331, 63337, 63347, 63353, 63361, 63367, + 63377, 63389, 63391, 63397, 63409, 63419, 63421, 63439, 63443, 63463, + 63467, 63473, 63487, 63493, 63499, 63521, 63527, 63533, 63541, 63559, + 63577, 63587, 63589, 63599, 63601, 63607, 63611, 63617, 63629, 63647, + 63649, 63659, 63667, 63671, 63689, 63691, 63697, 63703, 63709, 63719, + 63727, 63737, 63743, 63761, 63773, 63781, 63793, 63799, 63803, 63809, + 63823, 63839, 63841, 63853, 63857, 63863, 63901, 63907, 63913, 63929, + 63949, 63977, 63997, 64007, 64013, 64019, 64033, 64037, 64063, 64067, + 64081, 64091, 64109, 64123, 64151, 64153, 64157, 64171, 64187, 64189, + 64217, 64223, 64231, 64237, 64271, 64279, 64283, 64301, 64303, 64319, + 64327, 64333, 64373, 64381, 64399, 64403, 64433, 64439, 64451, 64453, + 64483, 64489, 64499, 64513, 64553, 64567, 64577, 64579, 64591, 64601, + 64609, 64613, 64621, 64627, 64633, 64661, 64663, 64667, 64679, 64693, + 64709, 64717, 64747, 64763, 64781, 64783, 64793, 64811, 64817, 64849, + 64853, 64871, 64877, 64879, 64891, 64901, 64919, 64921, 64927, 64937, + 64951, 64969, 64997, 65003, 65011, 65027, 65029, 65033, 65053, 65063, + 65071, 65089, 65099, 65101, 65111, 65119, 65123, 65129, 65141, 65147, + 65167, 65171, 65173, 65179, 65183, 65203, 65213, 65239, 65257, 65267, + 65269, 65287, 65293, 65309, 65323, 65327, 65353, 65357, 65371, 65381, + 65393, 65407, 65413, 65419, 65423, 65437, 65447, 65449, 65479, 65497, + 65519, 65521, +} + +// NextPrimeUint16 finds the next prime number after i, up to the max uint16 prime. +// An int input resulting in a result exceeding uint16's size will cause a panic. +// This is a very limited func designed only to pick +// an arbitrary prime number for Maglev-style loadbalancing. +// It should not be used for cryptography or anything else remotely security-related. +func NextPrimeUint16(i int) uint16 { + if i > 65521 { + logrus.WithField("i", i).Panic("Integer has no next prime uint16") + } + idx := sort.Search(len(pr), func(x int) bool { return int(pr[x]) >= i }) + if idx == len(pr) { + return pr[len(pr)-1] + } + + return pr[idx] +} diff --git a/libcalico-go/lib/consistenthash/primes_suite_test.go b/libcalico-go/lib/consistenthash/primes_suite_test.go new file mode 100644 index 00000000000..378625efa8c --- /dev/null +++ b/libcalico-go/lib/consistenthash/primes_suite_test.go @@ -0,0 +1,18 @@ +package consistenthash_test + +import ( + "testing" + + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + + "github.com/projectcalico/calico/libcalico-go/lib/testutils" +) + +func TestConverter(t *testing.T) { + testutils.HookLogrusForGinkgo() + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/consistenthash_primes_suite.xml" + ginkgo.RunSpecs(t, "ConsistentHash primes Suite", suiteConfig, reporterConfig) +} diff --git a/libcalico-go/lib/consistenthash/primes_test.go b/libcalico-go/lib/consistenthash/primes_test.go new file mode 100644 index 00000000000..0dbe68ce265 --- /dev/null +++ b/libcalico-go/lib/consistenthash/primes_test.go @@ -0,0 +1,18 @@ +package consistenthash_test + +import ( + . "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + + "github.com/projectcalico/calico/libcalico-go/lib/consistenthash" +) + +var _ = DescribeTable("RulesAPIToBackend", + func(input int, expected int) { + gomega.Expect(consistenthash.NextPrimeUint16(input)).To(gomega.BeEquivalentTo(expected)) + }, + Entry("Negative number should return 2", -1, 2), + Entry("0 should return 2", 0, 2), + Entry("Numbers should get the closest prime (1/2)", 31742, 31751), + Entry("Numbers should get the closest prime (2/2)", 31740, 31741), +) diff --git a/libcalico-go/lib/converter/bgppeer.go b/libcalico-go/lib/converter/bgppeer.go deleted file mode 100644 index ac4ff6a31f0..00000000000 --- a/libcalico-go/lib/converter/bgppeer.go +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) 2016-2017 Tigera, Inc. All rights reserved. - -// 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 converter - -import ( - api "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/errors" - "github.com/projectcalico/calico/libcalico-go/lib/scope" -) - -// BGPPeerConverter implements a set of functions used for converting between -// API and backend representations of the BGPPeer resource. -type BGPPeerConverter struct{} - -// ConvertMetadataToKey converts a BGPPeerMetadata to a GlobalBGPPeerKey or HostBGPPeerKey. -func (p BGPPeerConverter) ConvertMetadataToKey(m unversioned.ResourceMetadata) (model.Key, error) { - pm := m.(api.BGPPeerMetadata) - - if pm.Scope == scope.Global { - return model.GlobalBGPPeerKey{ - PeerIP: pm.PeerIP, - }, nil - } else if pm.Scope == scope.Node { - return model.NodeBGPPeerKey{ - PeerIP: pm.PeerIP, - Nodename: pm.Node, - }, nil - } else { - return nil, errors.ErrorInsufficientIdentifiers{ - Name: "scope", - } - } -} - -// ConvertAPIToKVPair converts an API Policy structure to a KVPair containing a -// backend BGPPeer and GlobalBGPPeerKey/HostBGPPeerKey. -func (p BGPPeerConverter) ConvertAPIToKVPair(a unversioned.Resource) (*model.KVPair, error) { - ap := a.(api.BGPPeer) - k, err := p.ConvertMetadataToKey(ap.Metadata) - if err != nil { - return nil, err - } - - d := model.KVPair{ - Key: k, - Value: &model.BGPPeer{ - PeerIP: ap.Metadata.PeerIP, - ASNum: ap.Spec.ASNumber, - }, - } - - return &d, nil -} - -// ConvertKVPairToAPI converts a KVPair containing a backend BGPPeer and GlobalBGPPeerKey/HostBGPPeerKey -// to an API BGPPeer structure. -func (p BGPPeerConverter) ConvertKVPairToAPI(d *model.KVPair) (unversioned.Resource, error) { - apiBGPPeer := api.NewBGPPeer() - - switch k := d.Key.(type) { - case model.GlobalBGPPeerKey: - apiBGPPeer.Metadata.Scope = scope.Global - apiBGPPeer.Metadata.PeerIP = k.PeerIP - apiBGPPeer.Metadata.Node = "" - case model.NodeBGPPeerKey: - apiBGPPeer.Metadata.Scope = scope.Node - apiBGPPeer.Metadata.PeerIP = k.PeerIP - apiBGPPeer.Metadata.Node = k.Nodename - } - - backendBGPPeer := d.Value.(*model.BGPPeer) - apiBGPPeer.Spec.ASNumber = backendBGPPeer.ASNum - - return apiBGPPeer, nil -} diff --git a/libcalico-go/lib/converter/ippool.go b/libcalico-go/lib/converter/ippool.go deleted file mode 100644 index e2b336c30f4..00000000000 --- a/libcalico-go/lib/converter/ippool.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) 2016-2017 Tigera, Inc. All rights reserved. - -// 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 converter - -import ( - api "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned" - "github.com/projectcalico/calico/libcalico-go/lib/backend/encap" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" -) - -// IPPoolConverter implements a set of functions used for converting between -// API and backend representations of the IPPool resource. -type IPPoolConverter struct{} - -// ConvertMetadataToKey converts an IPPoolMetadata to an IPPoolKey. -func (p IPPoolConverter) ConvertMetadataToKey(m unversioned.ResourceMetadata) (model.Key, error) { - pm := m.(api.IPPoolMetadata) - k := model.IPPoolKey{ - CIDR: pm.CIDR, - } - return k, nil -} - -// ConvertAPIToKVPair converts an API Policy structure to a KVPair containing a -// backend IPPool and IPPoolKey. -func (p IPPoolConverter) ConvertAPIToKVPair(a unversioned.Resource) (*model.KVPair, error) { - ap := a.(api.IPPool) - k, err := p.ConvertMetadataToKey(ap.Metadata) - if err != nil { - return nil, err - } - - // Only valid interface for now is tunl0. - var ipipInterface string - var ipipMode encap.Mode - if ap.Spec.IPIP != nil { - if ap.Spec.IPIP.Enabled { - ipipInterface = "tunl0" - } else { - ipipInterface = "" - } - ipipMode = ap.Spec.IPIP.Mode - } - - d := model.KVPair{ - Key: k, - Value: &model.IPPool{ - CIDR: ap.Metadata.CIDR, - IPIPInterface: ipipInterface, - IPIPMode: ipipMode, - Masquerade: ap.Spec.NATOutgoing, - IPAM: !ap.Spec.Disabled, - Disabled: ap.Spec.Disabled, - }, - } - - return &d, nil -} - -// ConvertKVPairToAPI converts a KVPair containing a backend IPPool and IPPoolKey -// to an API IPPool structure. -func (_ IPPoolConverter) ConvertKVPairToAPI(d *model.KVPair) (unversioned.Resource, error) { - backendPool := d.Value.(*model.IPPool) - - apiPool := api.NewIPPool() - apiPool.Metadata.CIDR = backendPool.CIDR - apiPool.Spec.NATOutgoing = backendPool.Masquerade - apiPool.Spec.Disabled = backendPool.Disabled - - // If any IPIP configuration is present then include the IPIP spec.. - if backendPool.IPIPInterface != "" || backendPool.IPIPMode != encap.Undefined { - apiPool.Spec.IPIP = &api.IPIPConfiguration{ - Enabled: backendPool.IPIPInterface != "", - Mode: backendPool.IPIPMode, - } - } - - return apiPool, nil -} diff --git a/libcalico-go/lib/converter/policy.go b/libcalico-go/lib/converter/policy.go deleted file mode 100644 index c252c683e4e..00000000000 --- a/libcalico-go/lib/converter/policy.go +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) 2016-2024 Tigera, Inc. All rights reserved. - -// 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 converter - -import ( - api "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/names" -) - -// PolicyConverter implements a set of functions used for converting between -// API and backend representations of the Policy resource. -type PolicyConverter struct{} - -// ConvertMetadataToKey converts a PolicyMetadata to a PolicyKey -func (p PolicyConverter) ConvertMetadataToKey(m unversioned.ResourceMetadata) (model.Key, error) { - pm := m.(api.PolicyMetadata) - k := model.PolicyKey{ - Name: pm.Name, - Tier: names.TierOrDefault(pm.Tier), - } - return k, nil -} - -// ConvertAPIToKVPair converts an API Policy structure to a KVPair containing a -// backend Policy and PolicyKey. -func (p PolicyConverter) ConvertAPIToKVPair(a unversioned.Resource) (*model.KVPair, error) { - ap := a.(api.Policy) - k, err := p.ConvertMetadataToKey(ap.Metadata) - if err != nil { - return nil, err - } - - d := model.KVPair{ - Key: k, - Value: &model.Policy{ - Order: ap.Spec.Order, - InboundRules: RulesAPIToBackend(ap.Spec.IngressRules), - OutboundRules: RulesAPIToBackend(ap.Spec.EgressRules), - Selector: ap.Spec.Selector, - DoNotTrack: ap.Spec.DoNotTrack, - Annotations: ap.Metadata.Annotations, - PreDNAT: ap.Spec.PreDNAT, - Types: nil, // filled in below - }, - } - - if ap.Spec.DoNotTrack || ap.Spec.PreDNAT { - // This case happens when there is a preexisting policy in the datastore, from before - // the ApplyOnForward feature was available. DoNotTrack or PreDNAT policy applies to - // forward traffic by nature. So in this case we return ApplyOnForward flag as true. - d.Value.(*model.Policy).ApplyOnForward = true - } - - if len(ap.Spec.Types) == 0 { - // Default the Types field according to what inbound and outbound rules are present - // in the policy. - if len(ap.Spec.EgressRules) == 0 { - // Policy has no egress rules, so apply this policy to ingress only. (Note: - // intentionally including the case where the policy also has no ingress - // rules.) - d.Value.(*model.Policy).Types = []string{string(api.PolicyTypeIngress)} - } else if len(ap.Spec.IngressRules) == 0 { - // Policy has egress rules but no ingress rules, so apply this policy to - // egress only. - d.Value.(*model.Policy).Types = []string{string(api.PolicyTypeEgress)} - } else { - // Policy has both ingress and egress rules, so apply this policy to both - // ingress and egress. - d.Value.(*model.Policy).Types = []string{string(api.PolicyTypeIngress), string(api.PolicyTypeEgress)} - } - } else { - // Convert from the API-specified Types. - d.Value.(*model.Policy).Types = make([]string, len(ap.Spec.Types)) - for i, t := range ap.Spec.Types { - d.Value.(*model.Policy).Types[i] = string(t) - } - } - - return &d, nil -} - -// ConvertKVPairToAPI converts a KVPair containing a backend Policy and PolicyKey -// to an API Policy structure. -func (p PolicyConverter) ConvertKVPairToAPI(d *model.KVPair) (unversioned.Resource, error) { - bp := d.Value.(*model.Policy) - bk := d.Key.(model.PolicyKey) - - ap := api.NewPolicy() - ap.Metadata.Name = bk.Name - ap.Metadata.Tier = bk.Tier - ap.Metadata.Annotations = bp.Annotations - ap.Spec.Order = bp.Order - ap.Spec.IngressRules = RulesBackendToAPI(bp.InboundRules) - ap.Spec.EgressRules = RulesBackendToAPI(bp.OutboundRules) - ap.Spec.Selector = bp.Selector - ap.Spec.DoNotTrack = bp.DoNotTrack - ap.Spec.PreDNAT = bp.PreDNAT - ap.Spec.Types = nil - - if len(bp.Types) == 0 { - // This case happens when there is a preexisting policy in an etcd datastore, from - // before the explicit Types feature was available. Calico's previous behaviour was - // always to apply policy to both ingress and egress traffic, so in this case we - // return Types as [ ingress, egress ]. - if bp.PreDNAT { - ap.Spec.Types = []api.PolicyType{api.PolicyTypeIngress} - } else { - ap.Spec.Types = []api.PolicyType{api.PolicyTypeIngress, api.PolicyTypeEgress} - } - } else { - // Convert from the backend-specified Types. - ap.Spec.Types = make([]api.PolicyType, len(bp.Types)) - for i, t := range bp.Types { - ap.Spec.Types[i] = api.PolicyType(t) - } - } - - return ap, nil -} diff --git a/libcalico-go/lib/converter/profile.go b/libcalico-go/lib/converter/profile.go deleted file mode 100644 index 527e711062b..00000000000 --- a/libcalico-go/lib/converter/profile.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) 2016-2017 Tigera, Inc. All rights reserved. - -// 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 converter - -import ( - log "github.com/sirupsen/logrus" - - api "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" -) - -// ProfileConverter implements a set of functions used for converting between -// API and backend representations of the Profile resource. -type ProfileConverter struct{} - -// ConvertMetadataToKey converts a ProfileMetadata to a ProfileKey -func (p ProfileConverter) ConvertMetadataToKey(m unversioned.ResourceMetadata) (model.Key, error) { - hm := m.(api.ProfileMetadata) - k := model.ProfileKey{ - Name: hm.Name, - } - return k, nil -} - -// ConvertAPIToKVPair converts an API Profile structure to a KVPair containing a -// backend Profile and ProfileKey. -func (c ProfileConverter) ConvertAPIToKVPair(a unversioned.Resource) (*model.KVPair, error) { - ap := a.(api.Profile) - k, err := c.ConvertMetadataToKey(ap.Metadata) - if err != nil { - return nil, err - } - - // Fix up tags and labels so to be empty values rather than nil. Felix does not - // expect a null value in the JSON, so we fix up to make Labels an empty map - // and tags an empty slice. - tags := ap.Metadata.Tags - if tags == nil { - log.Debug("Tags is nil - convert to empty map for backend") - tags = []string{} - } - labels := ap.Metadata.Labels - if labels == nil { - log.Debug("Labels is nil - convert to empty map for backend") - labels = map[string]string{} - } - - d := model.KVPair{ - Key: k, - Value: &model.Profile{ - Rules: model.ProfileRules{ - InboundRules: RulesAPIToBackend(ap.Spec.IngressRules), - OutboundRules: RulesAPIToBackend(ap.Spec.EgressRules), - }, - Tags: tags, - Labels: labels, - }, - } - - return &d, nil -} - -// ConvertKVPairToAPI converts a KVPair containing a backend Profile and ProfileKey -// to an API Profile structure. -func (c ProfileConverter) ConvertKVPairToAPI(d *model.KVPair) (unversioned.Resource, error) { - bp := d.Value.(*model.Profile) - bk := d.Key.(model.ProfileKey) - - ap := api.NewProfile() - ap.Metadata.Name = bk.Name - ap.Metadata.Labels = bp.Labels - if len(bp.Tags) == 0 { - ap.Metadata.Tags = nil - } else { - ap.Metadata.Tags = bp.Tags - } - ap.Spec.IngressRules = RulesBackendToAPI(bp.Rules.InboundRules) - ap.Spec.EgressRules = RulesBackendToAPI(bp.Rules.OutboundRules) - - return ap, nil -} diff --git a/libcalico-go/lib/converter/rule.go b/libcalico-go/lib/converter/rule.go deleted file mode 100644 index 5f5d7643c36..00000000000 --- a/libcalico-go/lib/converter/rule.go +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 converter - -import ( - "sync" - - log "github.com/sirupsen/logrus" - - api "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/net" -) - -// RulesAPIToBackend converts an API Rule structure slice to a Backend Rule structure slice. -func RulesAPIToBackend(ars []api.Rule) []model.Rule { - if ars == nil { - return []model.Rule{} - } - - brs := make([]model.Rule, len(ars)) - for idx, ar := range ars { - brs[idx] = ruleAPIToBackend(ar) - } - return brs -} - -// RulesBackendToAPI converts a Backend Rule structure slice to an API Rule structure slice. -func RulesBackendToAPI(brs []model.Rule) []api.Rule { - if brs == nil { - return nil - } - - ars := make([]api.Rule, len(brs)) - for idx, br := range brs { - ars[idx] = ruleBackendToAPI(br) - } - return ars -} - -var logDeprecationOnce sync.Once - -// ruleAPIToBackend converts an API Rule structure to a Backend Rule structure. -func ruleAPIToBackend(ar api.Rule) model.Rule { - var icmpCode, icmpType, notICMPCode, notICMPType *int - if ar.ICMP != nil { - icmpCode = ar.ICMP.Code - icmpType = ar.ICMP.Type - } - - if ar.NotICMP != nil { - notICMPCode = ar.NotICMP.Code - notICMPType = ar.NotICMP.Type - } - - if ar.Source.Net != nil || ar.Source.NotNet != nil || - ar.Destination.Net != nil || ar.Destination.NotNet != nil { - logDeprecationOnce.Do(func() { - log.Warning("The Net and NotNet fields in Source/Destination " + - "EntityRules are deprecated. Please use Nets or NotNets.") - }) - } - - return model.Rule{ - Action: ruleActionAPIToBackend(ar.Action), - IPVersion: ar.IPVersion, - Protocol: ar.Protocol, - ICMPCode: icmpCode, - ICMPType: icmpType, - NotProtocol: ar.NotProtocol, - NotICMPCode: notICMPCode, - NotICMPType: notICMPType, - - SrcTag: ar.Source.Tag, - SrcNet: normalizeIPNet(ar.Source.Net), - SrcNets: normalizeIPNets(ar.Source.Nets), - SrcSelector: ar.Source.Selector, - SrcPorts: ar.Source.Ports, - DstTag: ar.Destination.Tag, - DstNet: normalizeIPNet(ar.Destination.Net), - DstNets: normalizeIPNets(ar.Destination.Nets), - DstSelector: ar.Destination.Selector, - DstPorts: ar.Destination.Ports, - - NotSrcTag: ar.Source.NotTag, - NotSrcNet: normalizeIPNet(ar.Source.NotNet), - NotSrcNets: normalizeIPNets(ar.Source.NotNets), - NotSrcSelector: ar.Source.NotSelector, - NotSrcPorts: ar.Source.NotPorts, - NotDstTag: ar.Destination.NotTag, - NotDstNet: normalizeIPNet(ar.Destination.NotNet), - NotDstNets: normalizeIPNets(ar.Destination.NotNets), - NotDstSelector: ar.Destination.NotSelector, - NotDstPorts: ar.Destination.NotPorts, - } -} - -// normalizeIPNet converts an IPNet to a network by ensuring the IP address is correctly masked. -func normalizeIPNet(n *net.IPNet) *net.IPNet { - if n == nil { - return nil - } - return n.Network() -} - -// normalizeIPNets converts an []*IPNet to a slice of networks by ensuring the IP addresses -// are correctly masked. -func normalizeIPNets(nets []*net.IPNet) []*net.IPNet { - if nets == nil { - return nil - } - out := make([]*net.IPNet, len(nets)) - for i, n := range nets { - out[i] = normalizeIPNet(n) - } - return out -} - -// ruleBackendToAPI convert a Backend Rule structure to an API Rule structure. -func ruleBackendToAPI(br model.Rule) api.Rule { - var icmp, notICMP *api.ICMPFields - if br.ICMPCode != nil || br.ICMPType != nil { - icmp = &api.ICMPFields{ - Code: br.ICMPCode, - Type: br.ICMPType, - } - } - if br.NotICMPCode != nil || br.NotICMPType != nil { - notICMP = &api.ICMPFields{ - Code: br.NotICMPCode, - Type: br.NotICMPType, - } - } - - // Normalize the backend source Net/Nets/NotNet/NotNets - // This is because of a bug where we didn't normalize - // source (Not)Net(s) while converting API to backend in v1. - br.SrcNet = normalizeIPNet(br.SrcNet) - br.SrcNets = normalizeIPNets(br.SrcNets) - br.NotSrcNet = normalizeIPNet(br.NotSrcNet) - br.NotSrcNets = normalizeIPNets(br.NotSrcNets) - // Also normalize destination (Not)Net(s) for consistency. - br.DstNet = normalizeIPNet(br.DstNet) - br.DstNets = normalizeIPNets(br.DstNets) - br.NotDstNet = normalizeIPNet(br.NotDstNet) - br.NotDstNets = normalizeIPNets(br.NotDstNets) - - return api.Rule{ - Action: ruleActionBackendToAPI(br.Action), - IPVersion: br.IPVersion, - Protocol: br.Protocol, - ICMP: icmp, - NotProtocol: br.NotProtocol, - NotICMP: notICMP, - Source: api.EntityRule{ - Tag: br.SrcTag, - Nets: br.AllSrcNets(), - Selector: br.SrcSelector, - Ports: br.SrcPorts, - NotTag: br.NotSrcTag, - NotNets: br.AllNotSrcNets(), - NotSelector: br.NotSrcSelector, - NotPorts: br.NotSrcPorts, - }, - - Destination: api.EntityRule{ - Tag: br.DstTag, - Nets: br.AllDstNets(), - Selector: br.DstSelector, - Ports: br.DstPorts, - NotTag: br.NotDstTag, - NotNets: br.AllNotDstNets(), - NotSelector: br.NotDstSelector, - NotPorts: br.NotDstPorts, - }, - } -} - -// ruleActionAPIToBackend converts the rule action field value from the API -// value to the equivalent backend value. -func ruleActionAPIToBackend(action string) string { - if action == "Pass" { - return "next-tier" - } - return action -} - -// ruleActionBackendToAPI converts the rule action field value from the backend -// value to the equivalent API value. -func ruleActionBackendToAPI(action string) string { - if action == "" { - return "allow" - } else if action == "next-tier" { - return "pass" - } - return action -} diff --git a/libcalico-go/lib/converter/rule_test.go b/libcalico-go/lib/converter/rule_test.go deleted file mode 100644 index d61e88aea9f..00000000000 --- a/libcalico-go/lib/converter/rule_test.go +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 converter_test - -import ( - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" - - api "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - . "github.com/projectcalico/calico/libcalico-go/lib/converter" - "github.com/projectcalico/calico/libcalico-go/lib/net" -) - -var ( - cidr1 = net.MustParseCIDR("10.0.0.1/24") - cidr2 = net.MustParseCIDR("11.0.0.1/24") - cidr3 = net.MustParseCIDR("12.0.0.1/24") - cidr4 = net.MustParseCIDR("13.0.0.1/24") - cidr1Net = net.MustParseNetwork("10.0.0.0/24") - cidr2Net = net.MustParseNetwork("11.0.0.0/24") - cidr3Net = net.MustParseNetwork("12.0.0.0/24") - cidr4Net = net.MustParseNetwork("13.0.0.0/24") -) - -var _ = DescribeTable("RulesAPIToBackend", - func(input api.Rule, expected model.Rule) { - output := RulesAPIToBackend([]api.Rule{input}) - Expect(output).To(ConsistOf(expected)) - }, - Entry("empty rule", api.Rule{}, model.Rule{}), - Entry("should normalize source and destination net fields", - api.Rule{ - Source: api.EntityRule{ - Net: &cidr1, - NotNet: &cidr2, - }, - Destination: api.EntityRule{ - Net: &cidr3, - NotNet: &cidr4, - }, - }, - model.Rule{ - SrcNet: &cidr1Net, - NotSrcNet: &cidr2Net, - DstNet: &cidr3Net, - NotDstNet: &cidr4Net, - }, - ), - Entry("should normalize source and destination nets fields", - api.Rule{ - Source: api.EntityRule{ - Nets: []*net.IPNet{&cidr1}, - NotNets: []*net.IPNet{&cidr2}, - }, - Destination: api.EntityRule{ - Nets: []*net.IPNet{&cidr3}, - NotNets: []*net.IPNet{&cidr4}, - }, - }, - model.Rule{ - SrcNets: []*net.IPNet{&cidr1Net}, - NotSrcNets: []*net.IPNet{&cidr2Net}, - DstNets: []*net.IPNet{&cidr3Net}, - NotDstNets: []*net.IPNet{&cidr4Net}, - }, - ), -) - -var _ = DescribeTable("RulesBackendToAPI", - func(input model.Rule, expected api.Rule) { - output := RulesBackendToAPI([]model.Rule{input}) - Expect(output).To(ConsistOf(expected)) - }, - Entry("empty rule should get explicit action", model.Rule{}, api.Rule{Action: "allow"}), - Entry("should convert net to nets fields", - model.Rule{ - SrcNet: &cidr1, - NotSrcNet: &cidr2, - DstNet: &cidr3, - NotDstNet: &cidr4, - }, - api.Rule{ - Action: "allow", - Source: api.EntityRule{ - Nets: []*net.IPNet{&cidr1Net}, - NotNets: []*net.IPNet{&cidr2Net}, - }, - Destination: api.EntityRule{ - Nets: []*net.IPNet{&cidr3Net}, - NotNets: []*net.IPNet{&cidr4Net}, - }, - }, - ), - Entry("should pass through nets fields", - model.Rule{ - SrcNets: []*net.IPNet{&cidr1}, - NotSrcNets: []*net.IPNet{&cidr2}, - DstNets: []*net.IPNet{&cidr3}, - NotDstNets: []*net.IPNet{&cidr4}, - }, - api.Rule{ - Action: "allow", - Source: api.EntityRule{ - Nets: []*net.IPNet{&cidr1Net}, - NotNets: []*net.IPNet{&cidr2Net}, - }, - Destination: api.EntityRule{ - Nets: []*net.IPNet{&cidr3Net}, - NotNets: []*net.IPNet{&cidr4Net}, - }, - }, - ), -) diff --git a/libcalico-go/lib/converter/workloadendpoint.go b/libcalico-go/lib/converter/workloadendpoint.go deleted file mode 100644 index a618af49b07..00000000000 --- a/libcalico-go/lib/converter/workloadendpoint.go +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) 2016-2017 Tigera, Inc. All rights reserved. - -// 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 converter - -import ( - "github.com/projectcalico/calico/lib/std/uniquelabels" - api "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/net" -) - -// WorkloadEndpointConverter implements a set of functions used for converting between -// API and backend representations of the WorkloadEndpoint resource. -type WorkloadEndpointConverter struct{} - -// ConvertMetadataToKey converts a WorkloadEndpointMetadata to a WorkloadEndpointKey -func (w *WorkloadEndpointConverter) ConvertMetadataToKey(m unversioned.ResourceMetadata) (model.Key, error) { - hm := m.(api.WorkloadEndpointMetadata) - k := model.WorkloadEndpointKey{ - Hostname: hm.Node, - OrchestratorID: hm.Orchestrator, - WorkloadID: hm.Workload, - EndpointID: hm.Name, - } - return k, nil -} - -// ConvertAPIToKVPair converts an API WorkloadEndpoint structure to a KVPair containing a -// backend WorkloadEndpoint and WorkloadEndpointKey. -func (w *WorkloadEndpointConverter) ConvertAPIToKVPair(a unversioned.Resource) (*model.KVPair, error) { - ah := a.(api.WorkloadEndpoint) - k, err := w.ConvertMetadataToKey(ah.Metadata) - if err != nil { - return nil, err - } - - // IP networks are stored in the datastore in separate IPv4 and IPv6 - // fields. We normalise the network to ensure the IP is correctly - // masked. - ipv4Nets := []net.IPNet{} - ipv6Nets := []net.IPNet{} - for _, n := range ah.Spec.IPNetworks { - n = *(n.Network()) - if n.Version() == 4 { - ipv4Nets = append(ipv4Nets, n) - } else { - ipv6Nets = append(ipv6Nets, n) - } - } - - ipv4NAT := []model.IPNAT{} - ipv6NAT := []model.IPNAT{} - for _, n := range ah.Spec.IPNATs { - nat := model.IPNAT{IntIP: n.InternalIP, ExtIP: n.ExternalIP} - if n.InternalIP.Version() == 4 { - ipv4NAT = append(ipv4NAT, nat) - } else { - ipv6NAT = append(ipv6NAT, nat) - } - } - - var ports []model.EndpointPort - for _, port := range ah.Spec.Ports { - ports = append(ports, model.EndpointPort{ - Name: port.Name, - Protocol: port.Protocol, - Port: port.Port, - }) - } - - d := model.KVPair{ - Key: k, - Value: &model.WorkloadEndpoint{ - Labels: uniquelabels.Make(ah.Metadata.Labels), - ActiveInstanceID: ah.Metadata.ActiveInstanceID, - State: "active", - Name: ah.Spec.InterfaceName, - Mac: ah.Spec.MAC, - ProfileIDs: ah.Spec.Profiles, - IPv4Nets: ipv4Nets, - IPv6Nets: ipv6Nets, - IPv4NAT: ipv4NAT, - IPv6NAT: ipv6NAT, - IPv4Gateway: ah.Spec.IPv4Gateway, - IPv6Gateway: ah.Spec.IPv6Gateway, - Ports: ports, - AllowSpoofedSourcePrefixes: ah.Spec.AllowSpoofedSourcePrefixes, - }, - Revision: ah.Metadata.Revision, - } - - return &d, nil -} - -// ConvertKVPairToAPI converts a KVPair containing a backend WorkloadEndpoint and WorkloadEndpointKey -// to an API WorkloadEndpoint structure. -func (w *WorkloadEndpointConverter) ConvertKVPairToAPI(d *model.KVPair) (unversioned.Resource, error) { - bh := d.Value.(*model.WorkloadEndpoint) - bk := d.Key.(model.WorkloadEndpointKey) - - nets := bh.IPv4Nets - nets = append(nets, bh.IPv6Nets...) - - nats := []api.IPNAT{} - mnats := bh.IPv4NAT - mnats = append(mnats, bh.IPv6NAT...) - for _, mnat := range mnats { - nat := api.IPNAT{InternalIP: mnat.IntIP, ExternalIP: mnat.ExtIP} - nats = append(nats, nat) - } - - allowedSources := []net.IPNet{} - allowedSources = append(allowedSources, bh.AllowSpoofedSourcePrefixes...) - - ah := api.NewWorkloadEndpoint() - ah.Metadata.Node = bk.Hostname - ah.Metadata.Orchestrator = bk.OrchestratorID - ah.Metadata.Workload = bk.WorkloadID - ah.Metadata.Name = bk.EndpointID - ah.Metadata.Labels = bh.Labels.RecomputeOriginalMap() - ah.Spec.InterfaceName = bh.Name - ah.Metadata.ActiveInstanceID = bh.ActiveInstanceID - ah.Spec.MAC = bh.Mac - ah.Spec.Profiles = bh.ProfileIDs - if len(nets) == 0 { - ah.Spec.IPNetworks = nil - } else { - ah.Spec.IPNetworks = nets - } - if len(nats) == 0 { - ah.Spec.IPNATs = nil - } else { - ah.Spec.IPNATs = nats - } - ah.Spec.IPv4Gateway = bh.IPv4Gateway - ah.Spec.IPv6Gateway = bh.IPv6Gateway - ah.Spec.AllowSpoofedSourcePrefixes = allowedSources - - var ports []api.EndpointPort - for _, port := range bh.Ports { - ports = append(ports, api.EndpointPort{ - Name: port.Name, - Protocol: port.Protocol, - Port: port.Port, - }) - } - ah.Spec.Ports = ports - - ah.Metadata.Revision = d.Revision - - return ah, nil -} diff --git a/libcalico-go/lib/dispatcher/blocking_test.go b/libcalico-go/lib/dispatcher/blocking_test.go index 2b1cf66133a..400377310f9 100644 --- a/libcalico-go/lib/dispatcher/blocking_test.go +++ b/libcalico-go/lib/dispatcher/blocking_test.go @@ -17,7 +17,7 @@ package dispatcher_test import ( "context" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/dispatcher" @@ -25,12 +25,12 @@ import ( var _ = Describe("Dispatching", func() { Context("BlockingDispatcher", func() { - var blockingDispatcher *dispatcher.BlockingDispatcher[interface{}] + var blockingDispatcher *dispatcher.BlockingDispatcher[any] var ctx context.Context var cancel context.CancelFunc var dispatcherExited chan struct{} - var input chan interface{} - runDispatcherInBackgroundWithOutputChans := func(outputs ...chan interface{}) { + var input chan any + runDispatcherInBackgroundWithOutputChans := func(outputs ...chan any) { go func() { blockingDispatcher.DispatchForever(ctx, outputs...) close(dispatcherExited) @@ -39,8 +39,8 @@ var _ = Describe("Dispatching", func() { BeforeEach(func() { var err error - input = make(chan interface{}) - blockingDispatcher, err = dispatcher.NewBlockingDispatcher[interface{}](input) + input = make(chan any) + blockingDispatcher, err = dispatcher.NewBlockingDispatcher[any](input) Expect(err).NotTo(HaveOccurred()) ctx, cancel = context.WithCancel(context.Background()) @@ -53,7 +53,7 @@ var _ = Describe("Dispatching", func() { }) It("should dispatch a message to all outputs", func() { - output1, output2 := make(chan interface{}, 0), make(chan interface{}, 0) + output1, output2 := make(chan any, 0), make(chan any, 0) dummyInput := 666 runDispatcherInBackgroundWithOutputChans(output1, output2) @@ -62,7 +62,7 @@ var _ = Describe("Dispatching", func() { Eventually(input).Should(BeSent(dummyInput), "Couldn't send input to dispatcher") // Should output to each consumer in order. - for _, o := range []chan interface{}{output1, output2} { + for _, o := range []chan any{output1, output2} { Eventually(o, "10s").Should(Receive(Equal(dummyInput)), "Output channel didn't receive from dispatcher") } }) diff --git a/libcalico-go/lib/dispatcher/dispatcher_suite_test.go b/libcalico-go/lib/dispatcher/dispatcher_suite_test.go index cef55ea57c5..1437b9b266a 100644 --- a/libcalico-go/lib/dispatcher/dispatcher_suite_test.go +++ b/libcalico-go/lib/dispatcher/dispatcher_suite_test.go @@ -17,16 +17,16 @@ package dispatcher_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestDispatcher(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/dispatcher_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Dispatcher Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/dispatcher_suite.xml" + ginkgo.RunSpecs(t, "Dispatcher Suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/epstatusfile/epstatusfile_suite_test.go b/libcalico-go/lib/epstatusfile/epstatusfile_suite_test.go index 5b8d4458c1d..9a3771255ba 100644 --- a/libcalico-go/lib/epstatusfile/epstatusfile_suite_test.go +++ b/libcalico-go/lib/epstatusfile/epstatusfile_suite_test.go @@ -17,9 +17,8 @@ package epstatusfile import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestJitter(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/epstatusfile_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Epstatusfile Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/epstatusfile_suite.xml" + ginkgo.RunSpecs(t, "Epstatusfile Suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/epstatusfile/status_file_watcher_test.go b/libcalico-go/lib/epstatusfile/status_file_watcher_test.go index 56151d22d4b..fe37fb6c0b7 100644 --- a/libcalico-go/lib/epstatusfile/status_file_watcher_test.go +++ b/libcalico-go/lib/epstatusfile/status_file_watcher_test.go @@ -24,7 +24,7 @@ import ( "time" "github.com/fsnotify/fsnotify" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" ) diff --git a/libcalico-go/lib/epstatusfile/status_file_writer_test.go b/libcalico-go/lib/epstatusfile/status_file_writer_test.go index c72da69ec31..4cb7883329c 100644 --- a/libcalico-go/lib/epstatusfile/status_file_writer_test.go +++ b/libcalico-go/lib/epstatusfile/status_file_writer_test.go @@ -18,7 +18,7 @@ import ( "encoding/json" "os" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/libcalico-go/lib/errors/errors.go b/libcalico-go/lib/errors/errors.go index 92c9928f1ac..8949805716b 100644 --- a/libcalico-go/lib/errors/errors.go +++ b/libcalico-go/lib/errors/errors.go @@ -17,6 +17,7 @@ package errors import ( "fmt" "net/http" + "strings" networkingv1 "k8s.io/api/networking/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -26,7 +27,7 @@ import ( // Error indicating a problem connecting to the backend. type ErrorDatastoreError struct { Err error - Identifier interface{} + Identifier any } func (e ErrorDatastoreError) Unwrap() error { @@ -58,7 +59,7 @@ func (e ErrorDatastoreError) Status() metav1.Status { // update a nonexistent resource. type ErrorResourceDoesNotExist struct { Err error - Identifier interface{} + Identifier any } func (e ErrorResourceDoesNotExist) Error() string { @@ -72,7 +73,7 @@ func (e ErrorResourceDoesNotExist) Unwrap() error { // Error indicating an operation is not supported. type ErrorOperationNotSupported struct { Operation string - Identifier interface{} + Identifier any Reason string } @@ -88,7 +89,7 @@ func (e ErrorOperationNotSupported) Error() string { // resource that already exists. type ErrorResourceAlreadyExists struct { Err error - Identifier interface{} + Identifier any } func (e ErrorResourceAlreadyExists) Unwrap() error { @@ -119,7 +120,7 @@ type ErrorValidation struct { type ErroredField struct { Name string - Value interface{} + Value any Reason string } @@ -164,7 +165,7 @@ func (e ErrorInsufficientIdentifiers) Error() string { // Error indicating an atomic update attempt that failed due to a update conflict. type ErrorResourceUpdateConflict struct { Err error - Identifier interface{} + Identifier any } func (e ErrorResourceUpdateConflict) Unwrap() error { @@ -212,7 +213,7 @@ func (e ErrorPartialFailure) Error() string { // UpdateErrorIdentifier modifies the supplied error to use the new resource // identifier. -func UpdateErrorIdentifier(err error, id interface{}) error { +func UpdateErrorIdentifier(err error, id any) error { if err == nil { return nil } @@ -248,48 +249,49 @@ func (e ErrorParsingDatastoreEntry) Error() string { return fmt.Sprintf("failed to parse datastore entry key=%s; value=%s: %v", e.RawKey, e.RawValue, e.Err) } -type ErrorAdminPolicyConversion struct { +type ErrorClusterNetworkPolicyConversion struct { PolicyName string - Rules []ErrorAdminPolicyConversionRule + Rules []ErrorClusterNetworkPolicyConversionRule } -func (e *ErrorAdminPolicyConversion) BadEgressRule(rule any, reason string) { - e.Rules = append(e.Rules, ErrorAdminPolicyConversionRule{ +func (e *ErrorClusterNetworkPolicyConversion) BadEgressRule(rule any, reason string) { + e.Rules = append(e.Rules, ErrorClusterNetworkPolicyConversionRule{ EgressRule: rule, IngressRule: nil, Reason: reason, }) } -func (e *ErrorAdminPolicyConversion) BadIngressRule(rule any, reason string) { - e.Rules = append(e.Rules, ErrorAdminPolicyConversionRule{ +func (e *ErrorClusterNetworkPolicyConversion) BadIngressRule(rule any, reason string) { + e.Rules = append(e.Rules, ErrorClusterNetworkPolicyConversionRule{ EgressRule: nil, IngressRule: rule, Reason: reason, }) } -func (e ErrorAdminPolicyConversion) Error() string { - s := fmt.Sprintf("policy: %s", e.PolicyName) +func (e ErrorClusterNetworkPolicyConversion) Error() string { + var s strings.Builder + s.WriteString(fmt.Sprintf("policy: %s", e.PolicyName)) switch { case len(e.Rules) == 0: - s += ": unknown policy conversion error" + s.WriteString(": unknown policy conversion error") case len(e.Rules) == 1: f := e.Rules[0] - s += fmt.Sprintf(": error with rule %s", f) + s.WriteString(fmt.Sprintf(": error with rule %s", f)) default: - s += ": error with the following rules:\n" + s.WriteString(": error with the following rules:\n") for _, f := range e.Rules { - s += fmt.Sprintf("- %s\n", f) + s.WriteString(fmt.Sprintf("- %s\n", f)) } } - return s + return s.String() } -func (e ErrorAdminPolicyConversion) GetError() error { +func (e ErrorClusterNetworkPolicyConversion) GetError() error { if len(e.Rules) == 0 { return nil } @@ -297,13 +299,13 @@ func (e ErrorAdminPolicyConversion) GetError() error { return e } -type ErrorAdminPolicyConversionRule struct { +type ErrorClusterNetworkPolicyConversionRule struct { EgressRule any IngressRule any Reason string } -func (e ErrorAdminPolicyConversionRule) String() string { +func (e ErrorClusterNetworkPolicyConversionRule) String() string { var fieldString string switch { @@ -376,23 +378,24 @@ func (e *ErrorPolicyConversion) BadIngressRule( } func (e ErrorPolicyConversion) Error() string { - s := fmt.Sprintf("policy: %s", e.PolicyName) + var s strings.Builder + s.WriteString(fmt.Sprintf("policy: %s", e.PolicyName)) switch { case len(e.Rules) == 0: - s += ": unknown policy conversion error" + s.WriteString(": unknown policy conversion error") case len(e.Rules) == 1: f := e.Rules[0] - s += fmt.Sprintf(": error with rule %s", f) + s.WriteString(fmt.Sprintf(": error with rule %s", f)) default: - s += ": error with the following rules:\n" + s.WriteString(": error with the following rules:\n") for _, f := range e.Rules { - s += fmt.Sprintf("- %s\n", f) + s.WriteString(fmt.Sprintf("- %s\n", f)) } } - return s + return s.String() } func (e ErrorPolicyConversion) GetError() error { diff --git a/libcalico-go/lib/errors/errors_suite_test.go b/libcalico-go/lib/errors/errors_suite_test.go index 537d2f162f9..421694ea493 100644 --- a/libcalico-go/lib/errors/errors_suite_test.go +++ b/libcalico-go/lib/errors/errors_suite_test.go @@ -17,16 +17,16 @@ package errors_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestErrors(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/errors_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Errors Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/errors_suite.xml" + ginkgo.RunSpecs(t, "Errors Suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/errors/errors_test.go b/libcalico-go/lib/errors/errors_test.go index c1cef195610..34d146cf5d0 100644 --- a/libcalico-go/lib/errors/errors_test.go +++ b/libcalico-go/lib/errors/errors_test.go @@ -15,7 +15,7 @@ package errors_test import ( - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" networkingv1 "k8s.io/api/networking/v1" diff --git a/libcalico-go/lib/errors/panic.go b/libcalico-go/lib/errors/panic.go index 7be8ab3c44f..262f2d5a89d 100644 --- a/libcalico-go/lib/errors/panic.go +++ b/libcalico-go/lib/errors/panic.go @@ -17,7 +17,7 @@ package errors import log "github.com/sirupsen/logrus" // PanicIfErrored logs and panics if the supplied error is non-nil. -func PanicIfErrored(err error, msgformat string, args ...interface{}) { +func PanicIfErrored(err error, msgformat string, args ...any) { if err != nil { log.WithError(err).Panicf(msgformat, args...) } diff --git a/libcalico-go/lib/hash/unique_id.go b/libcalico-go/lib/hash/unique_id.go index 15775eb8280..fcfad8a5d3d 100644 --- a/libcalico-go/lib/hash/unique_id.go +++ b/libcalico-go/lib/hash/unique_id.go @@ -16,9 +16,14 @@ package hash import ( "crypto" + "crypto/sha256" "encoding/base64" + + log "github.com/sirupsen/logrus" ) +const shortenedPrefix = "_" + // MakeUniqueID uses a secure hash to create a unique ID from content. // The hash is prefixed with ":". func MakeUniqueID(prefix, content string) string { @@ -34,3 +39,37 @@ func MakeUniqueID(prefix, content string) string { hashBytes := hash.Sum(make([]byte, 0, hash.Size())) return prefix + ":" + base64.RawURLEncoding.EncodeToString(hashBytes) } + +// GetLengthLimitedID returns an ID that consists of the given prefix and, either the given suffix, +// or, if that would exceed the length limit, a cryptographic hash of the suffix, truncated to the +// required length. +// If the combined prefix+suffix fits within maxLength, it returns the original string unchanged. +// If it's too long, it hashes the suffix using SHA256, base64-encodes it, and truncates to fit. +// Uses shortenedPrefix ("_") to indicate when a hash was used, to avoid collisions. +func GetLengthLimitedID(fixedPrefix, suffix string, maxLength int) string { + if len(suffix) == 0 { + suffix = shortenedPrefix + } + prefixLen := len(fixedPrefix) + suffixLen := len(suffix) + totalLen := prefixLen + suffixLen + if totalLen > maxLength || (totalLen == maxLength && suffix[0:1] == shortenedPrefix) { + // Either it's just too long, or it's exactly the right length but it happens to + // start with the character that we use to denote a shortened string, which could + // result in a clash. Hash the value and truncate... + hasher := sha256.New() + _, err := hasher.Write([]byte(suffix)) + if err != nil { + log.WithError(err).Panic("Failed to write suffix to hash.") + } + hash := base64.RawURLEncoding.EncodeToString(hasher.Sum(nil)) + charsLeftForHash := maxLength - 1 - prefixLen + if charsLeftForHash <= 0 { + log.Panicf("GetLengthLimitedID: maxLength %d is too small for prefix %q (length %d); "+ + "need at least %d", maxLength, fixedPrefix, prefixLen, prefixLen+2) + } + return fixedPrefix + shortenedPrefix + hash[0:charsLeftForHash] + } + // No need to shorten. + return fixedPrefix + suffix +} diff --git a/libcalico-go/lib/hash/unique_id_test.go b/libcalico-go/lib/hash/unique_id_test.go new file mode 100644 index 00000000000..521bd5a8d69 --- /dev/null +++ b/libcalico-go/lib/hash/unique_id_test.go @@ -0,0 +1,96 @@ +// Copyright (c) 2016-2017 Tigera, Inc. All rights reserved. +// +// 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 hash_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + . "github.com/projectcalico/calico/libcalico-go/lib/hash" +) + +func TestHash(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Hash Suite") +} + +var _ = Describe("MakeUniqueID", func() { + It("should include prefix in output", func() { + result := MakeUniqueID("test", "content") + Expect(result).To(HavePrefix("test:")) + }) + + It("should produce deterministic output", func() { + result1 := MakeUniqueID("prefix", "content") + result2 := MakeUniqueID("prefix", "content") + Expect(result1).To(Equal(result2)) + }) + + It("should produce different outputs for different content", func() { + result1 := MakeUniqueID("prefix", "content1") + result2 := MakeUniqueID("prefix", "content2") + Expect(result1).NotTo(Equal(result2)) + }) + + It("should produce different outputs for different prefixes", func() { + result1 := MakeUniqueID("prefix1", "content") + result2 := MakeUniqueID("prefix2", "content") + Expect(result1).NotTo(Equal(result2)) + }) + + It("should handle empty content", func() { + result := MakeUniqueID("prefix", "") + Expect(result).To(HavePrefix("prefix:")) + Expect(len(result)).To(BeNumerically(">", len("prefix:"))) + }) + + It("should handle empty prefix", func() { + result := MakeUniqueID("", "content") + Expect(result).To(HavePrefix(":")) + Expect(len(result)).To(BeNumerically(">", 1)) + }) + + It("should produce base64-encoded hash", func() { + result := MakeUniqueID("test", "content") + hashPart := result[len("test:"):] + // SHA224 produces 28 bytes, base64.RawURLEncoding produces 38 characters + Expect(len(hashPart)).To(Equal(38)) + }) +}) + +var _ = Describe("GetLengthLimitedID", func() { + It("should return the suffix if short enough", func() { + Expect(GetLengthLimitedID("felix", "1234", 10)).To(Equal("felix1234")) + }) + It("should return the suffix if exact length without _ prefix", func() { + Expect(GetLengthLimitedID("felix", "123456", 11)).To(Equal("felix123456")) + }) + It("should return the hash if exact length with _ prefix", func() { + Expect(GetLengthLimitedID("felix", "_2345", 10)).To(Equal("felix_kMQI")) + }) + It("should return the hash if too long prefix", func() { + Expect(GetLengthLimitedID("felix", "12345678910", 13)).To(Equal("felix_Y2QCZIS")) + }) + It("should treat empty suffix as shortenedPrefix", func() { + Expect(GetLengthLimitedID("felix", "", 10)).To(Equal("felix_")) + }) + It("should panic when maxLength is too small to hold prefix + shortened hash", func() { + Expect(func() { + GetLengthLimitedID("felix", "toolong", 6) + }).To(Panic()) + }) +}) diff --git a/libcalico-go/lib/health/health.go b/libcalico-go/lib/health/health.go index 7989263474e..c4ed1a74d0d 100644 --- a/libcalico-go/lib/health/health.go +++ b/libcalico-go/lib/health/health.go @@ -17,6 +17,7 @@ package health import ( "bytes" "fmt" + "maps" "net" "net/http" "sort" @@ -36,9 +37,7 @@ var ( func SetGlobalTimeoutOverrides(overrides map[string]time.Duration) { overridesCopy := map[string]time.Duration{} - for k, v := range overrides { - overridesCopy[k] = v - } + maps.Copy(overridesCopy, overrides) globalOverridesLock.Lock() defer globalOverridesLock.Unlock() globalTimeoutOverrides = overrides diff --git a/libcalico-go/lib/health/health_suite_test.go b/libcalico-go/lib/health/health_suite_test.go index 1fb22ad8f7f..e06c607e7ff 100644 --- a/libcalico-go/lib/health/health_suite_test.go +++ b/libcalico-go/lib/health/health_suite_test.go @@ -17,16 +17,16 @@ package health_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestHealth(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/health_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Health Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/health_suite.xml" + ginkgo.RunSpecs(t, "Health Suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/health/health_test.go b/libcalico-go/lib/health/health_test.go index 072e75ef3ae..0f75cca4d21 100644 --- a/libcalico-go/lib/health/health_test.go +++ b/libcalico-go/lib/health/health_test.go @@ -18,7 +18,7 @@ import ( "strings" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/health" diff --git a/libcalico-go/lib/hwm/hwm_suite_test.go b/libcalico-go/lib/hwm/hwm_suite_test.go index 1019215558b..f76f0b5491d 100644 --- a/libcalico-go/lib/hwm/hwm_suite_test.go +++ b/libcalico-go/lib/hwm/hwm_suite_test.go @@ -17,13 +17,13 @@ package hwm_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" ) func TestHwm(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/hwm_tracker_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "HWM Tracker Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/hwm_tracker_suite.xml" + ginkgo.RunSpecs(t, "HWM Tracker Suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/hwm/hwm_test.go b/libcalico-go/lib/hwm/hwm_test.go index 3f3b58a7fc9..bb715a53cac 100644 --- a/libcalico-go/lib/hwm/hwm_test.go +++ b/libcalico-go/lib/hwm/hwm_test.go @@ -15,7 +15,7 @@ package hwm_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/projectcalico/calico/libcalico-go/lib/hwm" diff --git a/libcalico-go/lib/ipam/addr_filter_test.go b/libcalico-go/lib/ipam/addr_filter_test.go index 81b8e41b3b7..d63deca2510 100644 --- a/libcalico-go/lib/ipam/addr_filter_test.go +++ b/libcalico-go/lib/ipam/addr_filter_test.go @@ -52,7 +52,6 @@ var cidrFilterTests = []struct { func TestCIDRSliceFilter_Matches(t *testing.T) { for _, test := range cidrFilterTests { - test := test t.Run(fmt.Sprintf("filter_%s", strings.Join(test.FilterCIDRs, ",")), func(t *testing.T) { var filter cidrSliceFilter for _, cidr := range test.FilterCIDRs { @@ -118,7 +117,6 @@ func TestCIDRSliceFilter_filterOutDuplicates(t *testing.T) { Expected: []string{"10.0.0.0/24", "11.0.0.0/8"}, }, } { - test := test t.Run(fmt.Sprintf("filterOutDuplicates_%s", strings.Join(test.CIDRs, ",")), func(t *testing.T) { RegisterTestingT(t) var filter cidrSliceFilter diff --git a/libcalico-go/lib/ipam/interface.go b/libcalico-go/lib/ipam/interface.go index 8a0231e804d..b8195bc11ab 100644 --- a/libcalico-go/lib/ipam/interface.go +++ b/libcalico-go/lib/ipam/interface.go @@ -43,9 +43,12 @@ type Interface interface { // so that they are available to be used in another assignment. ReleaseIPs(ctx context.Context, ips ...ReleaseOptions) ([]cnet.IP, []ReleaseOptions, error) - // GetAssignmentAttributes returns the attributes stored with the given IP address - // upon assignment, as well as the handle used for assignment (if any). - GetAssignmentAttributes(ctx context.Context, addr cnet.IP) (map[string]string, *string, error) + // GetAssignmentAttributes returns the AllocationAttribute for the given IP address, + // which includes the handle ID, ActiveOwnerAttrs, and AlternateOwnerAttrs. + // This provides an atomic snapshot of all allocation attributes for the IP. + // If the IP is not assigned, it returns a nil *model.AllocationAttribute and an + // ErrorResourceDoesNotExist error. + GetAssignmentAttributes(ctx context.Context, addr cnet.IP) (*model.AllocationAttribute, error) // IPsByHandle returns a list of all IP addresses that have been // assigned using the provided handle. @@ -109,4 +112,18 @@ type Interface interface { // UpgradeHost checks the resources related to the given node and, if it // finds any that are in older formats, upgrades them. It is idempotent. UpgradeHost(ctx context.Context, nodeName string) error + + // SetOwnerAttributes sets ActiveOwnerAttrs and/or AlternateOwnerAttrs for an IP atomically. + // + // Parameters: + // - updates: Specifies the attribute values to set. See OwnerAttributeUpdates for details. + // - preconditions: Optional verification of expected owners before setting attributes. + // If nil, no verification is performed. + // + // Use cases: + // - Set AlternateOwnerAttrs only: updates.AlternateOwnerAttrs= + // - Clear ActiveOwnerAttrs: updates.ClearActiveOwner=true + // - Swap attributes: updates.ActiveOwnerAttrs=, updates.AlternateOwnerAttrs= + // - Set both: updates.ActiveOwnerAttrs=, updates.AlternateOwnerAttrs= + SetOwnerAttributes(ctx context.Context, ip cnet.IP, handleID string, updates *OwnerAttributeUpdates, preconditions *OwnerAttributePreconditions) error } diff --git a/libcalico-go/lib/ipam/ipam.go b/libcalico-go/lib/ipam/ipam.go index 2a10e2a094f..a9786322730 100644 --- a/libcalico-go/lib/ipam/ipam.go +++ b/libcalico-go/lib/ipam/ipam.go @@ -20,6 +20,7 @@ import ( "fmt" "math/bits" "runtime" + "slices" "strings" "time" @@ -29,7 +30,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" @@ -42,8 +43,7 @@ import ( const ( // Number of retries when we have an error writing data // to etcd. - datastoreRetries = 100 - ipamKeyErrRetries = 3 + datastoreRetries = 100 // Common attributes which may be set on allocations by clients. Moved to the model package so they can be used // by the AllocationBlock code too. @@ -59,6 +59,12 @@ const ( AttributeTypeWireguard = model.IPAMBlockAttributeTypeWireguard AttributeTypeWireguardV6 = model.IPAMBlockAttributeTypeWireguardV6 + // KubeVirt VM pod attributes + AttributeVMIName = model.IPAMBlockAttributeVMIName + AttributeVMIUID = model.IPAMBlockAttributeVMIUID + AttributeVMUID = model.IPAMBlockAttributeVMUID + AttributeVMIMUID = model.IPAMBlockAttributeVMIMUID + // Host affinity used for Service LoadBalancer loadBalancerAffinityHost = "virtual:load-balancer" ) @@ -121,7 +127,7 @@ func (c ipamClient) AutoAssign(ctx context.Context, args AutoAssignArgs) (*IPAMA return nil, nil, fmt.Errorf("provided IPv4 IPPools list contains one or more IPv6 IPPools") } } - v4ia, err = c.autoAssign(ctx, args.Num4, args.HandleID, args.Attrs, args.IPv4Pools, 4, hostname, args.MaxBlocksPerHost, args.HostReservedAttrIPv4s, args.IntendedUse, args.Namespace) + v4ia, err = c.autoAssign(ctx, args.Num4, args.HandleID, args.Attrs, args.IPv4Pools, 4, hostname, args.MaxBlocksPerHost, args.HostReservedAttrIPv4s, args.IntendedUse, args.Namespace, args.MaxAllocToHandlePerIPVersion) if err != nil { log.Errorf("Error assigning IPV4 addresses: %v", err) return v4ia, nil, err @@ -136,7 +142,7 @@ func (c ipamClient) AutoAssign(ctx context.Context, args AutoAssignArgs) (*IPAMA return nil, nil, fmt.Errorf("provided IPv6 IPPools list contains one or more IPv4 IPPools") } } - v6ia, err = c.autoAssign(ctx, args.Num6, args.HandleID, args.Attrs, args.IPv6Pools, 6, hostname, args.MaxBlocksPerHost, args.HostReservedAttrIPv6s, args.IntendedUse, args.Namespace) + v6ia, err = c.autoAssign(ctx, args.Num6, args.HandleID, args.Attrs, args.IPv6Pools, 6, hostname, args.MaxBlocksPerHost, args.HostReservedAttrIPv6s, args.IntendedUse, args.Namespace, args.MaxAllocToHandlePerIPVersion) if err != nil { log.Errorf("Error assigning IPV6 addresses: %v", err) return v4ia, v6ia, err @@ -252,7 +258,7 @@ func detectOS(ctx context.Context) string { // If no pools are requested, all enabled pools are returned. // Also applies selector logic on node labels and namespace labels to determine if the pool is a match. // Returns the set of matching pools as well as the full set of ip pools. -func (c ipamClient) determinePools(ctx context.Context, requestedPoolNets []net.IPNet, version int, node libapiv3.Node, namespace *corev1.Namespace, maxPrefixLen int) (matchingPools, enabledPools []v3.IPPool, err error) { +func (c ipamClient) determinePools(ctx context.Context, requestedPoolNets []net.IPNet, version int, node internalapi.Node, namespace *corev1.Namespace, maxPrefixLen int) (matchingPools, enabledPools []v3.IPPool, err error) { // Get all the enabled IP pools from the datastore. enabledPools, err = c.pools.GetEnabledPools(ctx, version) if err != nil { @@ -352,7 +358,7 @@ func (c ipamClient) prepareAffinityBlocksForHost(ctx context.Context, requestedP // Retrieve node for given hostname to use for ip pool node selection var node *model.KVPair var err error - var v3n *libapiv3.Node + var v3n *internalapi.Node var ok bool affinityCfg := AffinityConfig{ AffinityType: AffinityTypeHost, @@ -360,20 +366,20 @@ func (c ipamClient) prepareAffinityBlocksForHost(ctx context.Context, requestedP } // Use is not LoadBalancer, continue as normal with node if use != v3.IPPoolAllowedUseLoadBalancer { - node, err = c.client.Get(ctx, model.ResourceKey{Kind: libapiv3.KindNode, Name: host}, "") + node, err = c.client.Get(ctx, model.ResourceKey{Kind: internalapi.KindNode, Name: host}, "") if err != nil { log.WithError(err).WithField("node", host).Error("failed to get node for host") return nil, nil, err } // Make sure the returned value is OK. - v3n, ok = node.Value.(*libapiv3.Node) + v3n, ok = node.Value.(*internalapi.Node) if !ok { return nil, nil, fmt.Errorf("Datastore returned malformed node object") } } else { // Special case for Service LoadBalancer that is affined to virtual node - v3n = libapiv3.NewNode() + v3n = internalapi.NewNode() v3n.Name = v3.VirtualLoadBalancer affinityCfg.AffinityType = AffinityTypeVirtual } @@ -450,8 +456,10 @@ func (c ipamClient) prepareAffinityBlocksForHost(ctx context.Context, requestedP } // Release the block affinity, requiring it to be empty. - for i := 0; i < datastoreRetries; i++ { - if err = c.blockReaderWriter.releaseBlockAffinity(ctx, affinityCfg, block, true); err != nil { + for range datastoreRetries { + if err = c.blockReaderWriter.releaseBlockAffinity(ctx, affinityCfg, block, releaseAffinityOpts{ + RequireEmpty: true, + }); err != nil { if _, ok := err.(errBlockClaimConflict); ok { // Not claimed by this host - ignore. } else if _, ok := err.(errBlockNotEmpty); ok { @@ -476,11 +484,8 @@ func (c ipamClient) prepareAffinityBlocksForHost(ctx context.Context, requestedP func filterPoolsByUse(pools []v3.IPPool, use v3.IPPoolAllowedUse) []v3.IPPool { var filteredPools []v3.IPPool for _, p := range pools { - for _, allowed := range p.Spec.AllowedUses { - if allowed == use { - filteredPools = append(filteredPools, p) - break - } + if slices.Contains(p.Spec.AllowedUses, use) { + filteredPools = append(filteredPools, p) } } return filteredPools @@ -521,7 +526,7 @@ func (s *blockAssignState) findOrClaimBlock(ctx context.Context, minFreeIps int) // Checking this block - if we hit a CAS error, we'll try this block again. // For any other error, we'll break out and try the next affine block. - for i := 0; i < datastoreRetries; i++ { + for range datastoreRetries { // Get the affinity. logCtx.Infof("Trying affinity for %s", cidr) aff, err := s.client.blockReaderWriter.queryAffinity(ctx, s.affinityCfg, cidr, "") @@ -568,7 +573,7 @@ func (s *blockAssignState) findOrClaimBlock(ctx context.Context, minFreeIps int) } logCtx.Debugf("Allocate new blocks? Config: %+v", config) if config.AutoAllocateBlocks { - for i := 0; i < datastoreRetries; i++ { + for range datastoreRetries { // First, try to find a usable block. findUsableBlock will usually return a new block, or in rare scenarios an already // allocated affine block. This may happen due to a race condition where another process on the host allocates a new block // after we decide that a new block is required to satisfy this request, but before we actually allocate a new block. @@ -587,7 +592,7 @@ func (s *blockAssignState) findOrClaimBlock(ctx context.Context, minFreeIps int) logCtx := log.WithFields(log.Fields{string(s.affinityCfg.AffinityType): s.affinityCfg.Host, "subnet": subnet}) logCtx.Infof("Found unclaimed block in %v", time.Since(findStart)) - for j := 0; j < datastoreRetries; j++ { + for range datastoreRetries { // We found an unclaimed block - claim affinity for it. pa, err := s.client.blockReaderWriter.getPendingAffinity(ctx, s.affinityCfg, *subnet) if err != nil { @@ -647,11 +652,9 @@ type IPAMAssignments struct { } func (i *IPAMAssignments) AddMsg(msg string) { - for _, m := range i.Msgs { - if msg == m { - // Don't add duplicate msgs - return - } + if slices.Contains(i.Msgs, msg) { + // Don't add duplicate msgs + return } i.Msgs = append(i.Msgs, msg) } @@ -677,7 +680,20 @@ func (i *IPAMAssignments) PartialFulfillmentError() error { var ErrUseRequired = errors.New("must specify the intended use when assigning an IP") -func (c ipamClient) autoAssign(ctx context.Context, num int, handleID *string, attrs map[string]string, requestedPools []net.IPNet, version int, host string, maxNumBlocks int, rsvdAttr *HostReservedAttr, use v3.IPPoolAllowedUse, namespace *corev1.Namespace) (*IPAMAssignments, error) { +// ErrInsufficientIPsByHandle is returned when a handle has some IPs allocated but not enough yet. +// This indicates a concurrent operation is in progress and the caller should retry. +type ErrInsufficientIPsByHandle struct { + HandleID string + ExistingCount int + RequiredCount int +} + +func (e ErrInsufficientIPsByHandle) Error() string { + return fmt.Sprintf("handle %s has %d IPs allocated but requires %d (concurrent operation in progress)", + e.HandleID, e.ExistingCount, e.RequiredCount) +} + +func (c ipamClient) autoAssign(ctx context.Context, num int, handleID *string, attrs map[string]string, requestedPools []net.IPNet, version int, host string, maxNumBlocks int, rsvdAttr *HostReservedAttr, use v3.IPPoolAllowedUse, namespace *corev1.Namespace, maxAlloc int) (*IPAMAssignments, error) { // Default parameters. if use == "" { log.Error("Attempting to auto-assign an IP without specifying intended use.") @@ -790,9 +806,9 @@ func (c ipamClient) autoAssign(ctx context.Context, num int, handleID *string, a } // We have got a block b. - for i := 0; i < datastoreRetries; i++ { + for range datastoreRetries { assignStart := time.Now() - newIPs, err := c.assignFromExistingBlock(ctx, b, rem, handleID, attrs, affinityCfg, config.StrictAffinity, reservations) + newIPs, err := c.assignFromExistingBlock(ctx, b, rem, handleID, attrs, affinityCfg, config.StrictAffinity, reservations, maxAlloc) if err != nil { if _, ok := err.(cerrors.ErrorResourceUpdateConflict); ok { log.WithError(err).Debug("CAS Error assigning from new block - retry") @@ -809,6 +825,23 @@ func (c ipamClient) autoAssign(ctx context.Context, num int, handleID *string, a // Block b is in sync with datastore. Retry assigning IP. continue } + + // Check if error is due to maxAlloc constraint + if maxAllocErr, ok := err.(ErrMaxAllocReached); ok { + logCtx.WithError(maxAllocErr) + existingIPs, handleErr := c.handleMaxAllocReached(ctx, *handleID, num, version, logCtx) + if handleErr != nil { + // Either insufficient IPs or query failed - both cases should retry + // because the concurrent operation may complete on the next attempt + logCtx.WithError(handleErr).Debug("Will retry after maxAlloc handling") + continue + } + // Sufficient IPs found + ia.IPs = append(ia.IPs, existingIPs...) + rem = num - len(ia.IPs) + break + } + logCtx.WithError(err).Warningf("Failed to assign IPs in newly allocated block") ia.AddMsg("Failed to assign IPs in newly allocated block") break @@ -859,7 +892,7 @@ func (c ipamClient) autoAssign(ctx context.Context, num int, handleID *string, a continue } - for i := 0; i < datastoreRetries; i++ { + for range datastoreRetries { b, err := c.blockReaderWriter.queryBlock(ctx, *blockCIDR, "") if err != nil { logCtx.WithError(err).Warn("Failed to get non-affine block") @@ -868,12 +901,30 @@ func (c ipamClient) autoAssign(ctx context.Context, num int, handleID *string, a // Attempt to assign from the block. logCtx.Infof("Attempting to assign IPs from non-affine block %s", blockCIDR.String()) - newIPs, err := c.assignFromExistingBlock(ctx, b, rem, handleID, attrs, affinityCfg, false, reservations) + newIPs, err := c.assignFromExistingBlock(ctx, b, rem, handleID, attrs, affinityCfg, false, reservations, maxAlloc) if err != nil { if _, ok := err.(cerrors.ErrorResourceUpdateConflict); ok { logCtx.WithError(err).Debug("CAS error assigning from non-affine block - retry") continue } + + // Check if error is due to maxAlloc constraint + if maxAllocErr, ok := err.(ErrMaxAllocReached); ok { + logCtx.WithError(maxAllocErr) + existingIPs, handleErr := c.handleMaxAllocReached(ctx, *handleID, num, version, logCtx) + if handleErr != nil { + // Either insufficient IPs or query failed - both cases should retry + // because the concurrent operation may complete on the next attempt + logCtx.WithError(handleErr).Debug("Will retry after maxAlloc handling in non-affine block") + continue + } + // Sufficient IPs found + ia.IPs = append(ia.IPs, existingIPs...) + rem = num - len(ia.IPs) + // Exit both loops since we found IPs + goto doneLookingForIPs + } + logCtx.WithError(err).Warningf("Failed to assign IPs from non-affine block in pool %s", p.Spec.CIDR) break } @@ -892,6 +943,7 @@ func (c ipamClient) autoAssign(ctx context.Context, num int, handleID *string, a } } +doneLookingForIPs: logCtx.Infof("Auto-assigned %d out of %d IPv%ds: %v", len(ia.IPs), num, version, ia.IPs) return ia, nil } @@ -933,7 +985,7 @@ func (c ipamClient) AssignIP(ctx context.Context, args AssignIPArgs) error { blockCIDR := getBlockCIDRForAddress(args.IP, pool) log.Debugf("IP %s is in block '%s'", args.IP.String(), blockCIDR.String()) - for i := 0; i < datastoreRetries; i++ { + for range datastoreRetries { obj, err := c.blockReaderWriter.queryBlock(ctx, blockCIDR, "") if err != nil { if _, ok := err.(cerrors.ErrorResourceDoesNotExist); !ok { @@ -975,14 +1027,57 @@ func (c ipamClient) AssignIP(ctx context.Context, args AssignIPArgs) error { block := allocationBlock{obj.Value.(*model.AllocationBlock)} err = block.assign(cfg.StrictAffinity, args.IP, args.HandleID, args.Attrs, affinityCfg) if err != nil { + // Only attempt idempotent reuse when MaxAllocToHandlePerIPVersion is set (non-zero), + // since that is the only case where re-assigning an existing IP to the same handle is expected. + if _, ok := err.(cerrors.ErrorResourceAlreadyExists); ok && args.HandleID != nil && args.MaxAllocToHandlePerIPVersion > 0 { + existingAttr, handleErr := block.allocationAttributesForIP(args.IP) + if handleErr == nil && existingAttr != nil && existingAttr.HandleID != nil && *existingAttr.HandleID == *args.HandleID { + log.Infof("IP %s already assigned to handle %s, treating as idempotent success", args.IP, *args.HandleID) + return nil + } + } log.Errorf("Failed to assign address %v: %v", args.IP, err) return err } // Increment handle. if args.HandleID != nil { - err := c.incrementHandle(ctx, *args.HandleID, blockCIDR, 1) + err := c.incrementHandle(ctx, *args.HandleID, blockCIDR, 1, args.MaxAllocToHandlePerIPVersion) if err != nil { + // Check if error is due to maxAlloc constraint + if maxAllocErr, ok := err.(ErrMaxAllocReached); ok { + logCtx := log.WithFields(log.Fields{ + "handleID": *args.HandleID, + "ip": args.IP.String(), + }) + logCtx.WithError(maxAllocErr) + + // Use the helper function to query existing IPs + assignIPVersion := 4 + if args.IP.IP.To4() == nil { + assignIPVersion = 6 + } + existingIPNets, handleErr := c.handleMaxAllocReached(ctx, *args.HandleID, 1, assignIPVersion, logCtx) + if handleErr != nil { + // Query failed or insufficient IPs - retry + logCtx.WithError(handleErr).Debug("Will retry AssignIP after maxAlloc handling") + continue + } + + // We got existing IPs - check if the requested IP is in the list + for _, existingIPNet := range existingIPNets { + if existingIPNet.IP.Equal(args.IP.IP) { + logCtx.Info("Requested IP is already allocated to this handle, treating as success") + return nil + } + } + + // The handle has other IP(s) but not the requested one + logCtx.WithField("existingIPs", existingIPNets).Error("Handle already has different IP(s) allocated") + return fmt.Errorf("handle %s already has IP(s) allocated, cannot assign additional IP %s (maxAllocPerIPVersion=%d)", + *args.HandleID, args.IP.String(), args.MaxAllocToHandlePerIPVersion) + } + log.WithError(err).Warn("Failed to increment handle") return fmt.Errorf("failed to increment handle: %w", err) } @@ -1014,6 +1109,58 @@ func (c ipamClient) AssignIP(ctx context.Context, args AssignIPArgs) error { return errors.New("Max retries hit - excessive concurrent IPAM requests") } +// handleMaxAllocReached handles the case where incrementHandle fails due to maxAlloc constraint. +// It queries for existing IPs allocated to the handle, filtered by IP version, and returns +// exactly num IPs if sufficient allocations already exist. +// Returns: +// - []net.IPNet (len == num): Exactly num existing IPs if sufficient IPs (>= num) were found +// - nil, ErrInsufficientIPsByHandle: If partial/no IPs found (caller should retry) +// - nil, error: If the query failed +func (c ipamClient) handleMaxAllocReached(ctx context.Context, handleID string, num int, ipVersion int, logCtx *log.Entry) ([]net.IPNet, error) { + logCtx.Info("MaxAlloc limit reached, checking for existing allocation") + + // Query for existing IPs allocated to this handle + existingIPs, queryErr := c.IPsByHandle(ctx, handleID) + if queryErr != nil { + logCtx.WithError(queryErr).Warn("Failed to query existing IPs by handle after maxAlloc error") + return nil, queryErr + } + + // Filter by IP version to avoid returning IPs from other address families. + var filtered []net.IP + for _, ip := range existingIPs { + isV4 := ip.IP.To4() != nil + if (ipVersion == 4 && isV4) || (ipVersion == 6 && !isV4) { + filtered = append(filtered, ip) + } + } + + if len(filtered) >= num { + logCtx.WithField("existingIPs", filtered).Info("Found sufficient existing IP allocations, reusing them") + result := make([]net.IPNet, 0, num) + for _, ip := range filtered[:num] { + result = append(result, *ip.Network()) + } + return result, nil + } + + // Partial or no IPs - concurrent operation in progress + if len(filtered) > 0 { + logCtx.WithFields(log.Fields{ + "existingCount": len(filtered), + "requiredCount": num, + }).Info("Partial allocation found, concurrent operation in progress - will retry") + } else { + logCtx.Info("No existing IPs found yet, concurrent operation in progress - will retry") + } + + return nil, ErrInsufficientIPsByHandle{ + HandleID: handleID, + ExistingCount: len(filtered), + RequiredCount: num, + } +} + // ReleaseIPs releases any of the given IP addresses that are currently assigned, // so that they are available to be used in another assignment. // ReleaseIPs returns: @@ -1021,7 +1168,7 @@ func (c ipamClient) AssignIP(ctx context.Context, args AssignIPArgs) error { // - A list of ReleaseOptions that did not encounter an error (either not allocated, or successfully released). // - An error, if one occurred. func (c ipamClient) ReleaseIPs(ctx context.Context, ips ...ReleaseOptions) ([]net.IP, []ReleaseOptions, error) { - for i := 0; i < len(ips); i++ { + for i := range ips { // Validate the input. if ips[i].Address == "" { return nil, nil, fmt.Errorf("No IP address specified in options: %+v", ips[i]) @@ -1035,11 +1182,10 @@ func (c ipamClient) ReleaseIPs(ctx context.Context, ips ...ReleaseOptions) ([]ne unallocated := []net.IP{} // Get IP pools up front so we don't need to query for each IP address. - v4Pools, err := c.pools.GetEnabledPools(ctx, 4) - if err != nil { - return nil, nil, err - } - v6Pools, err := c.pools.GetEnabledPools(ctx, 6) + // When releasing an IP, we get _all_ pools, even disabled ones. If we + // didn't, then deletion from a disabled pool would still succeed, but it'd + // go through the "full scan" path which is inefficient. + allPools, err := c.pools.GetAllPools(ctx) if err != nil { return nil, nil, err } @@ -1047,6 +1193,7 @@ func (c ipamClient) ReleaseIPs(ctx context.Context, ips ...ReleaseOptions) ([]ne // Group IP addresses by block to minimize the number of writes // to the datastore required to release the given addresses. ipsByBlock := map[string][]ReleaseOptions{} + var cachedBlockKVs *model.KVPairList for _, opts := range ips { var blockCIDR string @@ -1055,27 +1202,19 @@ func (c ipamClient) ReleaseIPs(ctx context.Context, ips ...ReleaseOptions) ([]ne return nil, nil, err } - // Find the IP pools for this address in the enabled pools if possible. - var pool *v3.IPPool - switch ip.Version() { - case 4: - pool, err = c.blockReaderWriter.getPoolForIP(ctx, *ip, v4Pools) - if err != nil { - log.WithError(err).Warnf("Failed to get pool for IP") - return nil, nil, err - } - case 6: - pool, err = c.blockReaderWriter.getPoolForIP(ctx, *ip, v6Pools) - if err != nil { - log.WithError(err).Warnf("Failed to get pool for IP") - return nil, nil, err - } + // Find the IP pools for this address, if possible. + pool, err := c.blockReaderWriter.getPoolForIP(ctx, *ip, allPools) + if err != nil { + log.WithError(err).Warnf("Failed to get pool for IP") + return nil, nil, err } if pool == nil { - if cidr, err := c.blockReaderWriter.getBlockForIP(ctx, *ip); err != nil { + log.Warnf("The IP %s is not in any configured pool, trying to find the block by full scan.", ip.String()) + if cidr, newCachedKVs, err := c.blockReaderWriter.getBlockForIP(ctx, *ip, cachedBlockKVs); err != nil { return nil, nil, err } else { + cachedBlockKVs = newCachedKVs if cidr == nil { // The IP isn't in any block so it's already unallocated. unallocated = append(unallocated, *ip) @@ -1166,7 +1305,7 @@ func (c ipamClient) ReleaseIPs(ctx context.Context, ips ...ReleaseOptions) ([]ne func (c ipamClient) releaseIPsFromBlock(ctx context.Context, handleMap map[string]*model.KVPair, ips []ReleaseOptions, blockCIDR net.IPNet) ([]net.IP, error) { logCtx := log.WithField("cidr", blockCIDR) - for i := 0; i < datastoreRetries; i++ { + for i := range datastoreRetries { logCtx.Debug("Getting block so we can release IPs") // Get allocation block for cidr. @@ -1244,7 +1383,7 @@ func (c ipamClient) releaseIPsFromBlock(ctx context.Context, handleMap map[strin return nil, errors.New("Max retries hit - excessive concurrent IPAM requests") } -func (c ipamClient) assignFromExistingBlock(ctx context.Context, block *model.KVPair, num int, handleID *string, attrs map[string]string, affinityCfg AffinityConfig, affCheck bool, reservations addrFilter) ([]net.IPNet, error) { +func (c ipamClient) assignFromExistingBlock(ctx context.Context, block *model.KVPair, num int, handleID *string, attrs map[string]string, affinityCfg AffinityConfig, affCheck bool, reservations addrFilter, maxAlloc int) ([]net.IPNet, error) { blockCIDR := block.Key.(model.BlockKey).CIDR logCtx := log.WithFields(log.Fields{string(affinityCfg.AffinityType): affinityCfg.Host, "block": blockCIDR}) if handleID != nil { @@ -1268,10 +1407,13 @@ func (c ipamClient) assignFromExistingBlock(ctx context.Context, block *model.KV // Increment handle count. if handleID != nil { logCtx.Debug("Incrementing handle") - err := c.incrementHandle(ctx, *handleID, blockCIDR, num) + err := c.incrementHandle(ctx, *handleID, blockCIDR, num, maxAlloc) if err != nil { - log.WithError(err).Warn("Failed to increment handle") - return nil, fmt.Errorf("failed to increment handle: %w", err) + // If incrementHandle fails due to maxAlloc constraint, return the error so caller can handle it. + // The IPs allocated in the block's memory structure won't be persisted since + // we return before updateBlock. + logCtx.WithError(err).Info("Failed to increment handle") + return nil, err } } @@ -1349,7 +1491,7 @@ func (c ipamClient) ClaimAffinity(ctx context.Context, cidr net.IPNet, affinityC // Claim all blocks within the given cidr. blocks := blockGenerator(pool, cidr) for blockCIDR := blocks(); blockCIDR != nil; blockCIDR = blocks() { - for i := 0; i < datastoreRetries; i++ { + for range datastoreRetries { // First, claim a pending affinity. pa, err := c.blockReaderWriter.getPendingAffinity(ctx, affinityCfg, *blockCIDR) if err != nil { @@ -1421,8 +1563,10 @@ func (c ipamClient) ReleaseAffinity(ctx context.Context, cidr net.IPNet, host st blocks := blockGenerator(pool, cidr) for blockCIDR := blocks(); blockCIDR != nil; blockCIDR = blocks() { logCtx := log.WithField("cidr", blockCIDR) - for i := 0; i < datastoreRetries; i++ { - err := c.blockReaderWriter.releaseBlockAffinity(ctx, affinityCfg, *blockCIDR, mustBeEmpty) + for range datastoreRetries { + err := c.blockReaderWriter.releaseBlockAffinity(ctx, affinityCfg, *blockCIDR, releaseAffinityOpts{ + RequireEmpty: mustBeEmpty, + }) if err != nil { if _, ok := err.(errBlockClaimConflict); ok { // Not claimed by this host - ignore. @@ -1456,7 +1600,9 @@ func (c ipamClient) ReleaseBlockAffinity(ctx context.Context, block *model.Alloc return err } - err = c.blockReaderWriter.releaseBlockAffinity(ctx, *affinityCfg, block.CIDR, mustBeEmpty) + err = c.blockReaderWriter.releaseBlockAffinity(ctx, *affinityCfg, block.CIDR, releaseAffinityOpts{ + RequireEmpty: mustBeEmpty, + }) if err != nil { if _, ok := err.(errBlockClaimConflict); ok { // Not claimed by this host - ignore. @@ -1492,8 +1638,10 @@ func (c ipamClient) ReleaseHostAffinities(ctx context.Context, affinityCfg Affin for _, blockCIDR := range blockCIDRs { logCtx := log.WithField("cidr", blockCIDR) - for i := 0; i < datastoreRetries; i++ { - err := c.blockReaderWriter.releaseBlockAffinity(ctx, affinityCfg, blockCIDR, mustBeEmpty) + for range datastoreRetries { + err := c.blockReaderWriter.releaseBlockAffinity(ctx, affinityCfg, blockCIDR, releaseAffinityOpts{ + RequireEmpty: mustBeEmpty, + }) if err != nil { if _, ok := err.(errBlockClaimConflict); ok { // Claimed by a different host. Move to next block. @@ -1522,7 +1670,7 @@ func (c ipamClient) ReleaseHostAffinities(ctx context.Context, affinityCfg Affin // the specified pool across all hosts. func (c ipamClient) ReleasePoolAffinities(ctx context.Context, pool net.IPNet) error { log.Infof("Releasing block affinities within pool '%s'", pool.String()) - for i := 0; i < ipamKeyErrRetries; i++ { + for range datastoreRetries { retry := false pairs, err := c.affinityConfigsByBlocks(ctx, pool) if err != nil { @@ -1537,8 +1685,8 @@ func (c ipamClient) ReleasePoolAffinities(ctx context.Context, pool net.IPNet) e for blockString, affinityCfg := range pairs { _, blockCIDR, _ := net.ParseCIDR(blockString) logCtx := log.WithField("cidr", blockCIDR) - for i := 0; i < datastoreRetries; i++ { - err = c.blockReaderWriter.releaseBlockAffinity(ctx, affinityCfg, *blockCIDR, false) + for range datastoreRetries { + err = c.blockReaderWriter.releaseBlockAffinity(ctx, affinityCfg, *blockCIDR, releaseAffinityOpts{}) if err != nil { if _, ok := err.(errBlockClaimConflict); ok { retry = true @@ -1578,7 +1726,7 @@ func (c ipamClient) RemoveIPAMHost(ctx context.Context, affinityCfg AffinityConf logCtx := log.WithField("host", affinityCfg.Host) logCtx.Debug("Removing IPAM data for host") - for i := 0; i < datastoreRetries; i++ { + for range datastoreRetries { // Release affinities for this host. logCtx.Debug("Releasing IPAM affinities for host") if err := c.ReleaseHostAffinities(ctx, affinityCfg, false); err != nil { @@ -1701,7 +1849,7 @@ func (c ipamClient) ReleaseByHandle(ctx context.Context, handleID string) error func (c ipamClient) releaseByHandle(ctx context.Context, blockCIDR net.IPNet, opts ReleaseOptions) error { logCtx := log.WithFields(log.Fields{"handle": opts.Handle, "cidr": blockCIDR}) - for i := 0; i < datastoreRetries; i++ { + for i := range datastoreRetries { logCtx.Debug("Querying block so we can release IPs by handle") obj, err := c.blockReaderWriter.queryBlock(ctx, blockCIDR, "") if err != nil { @@ -1772,7 +1920,7 @@ func (c ipamClient) releaseByHandle(ctx context.Context, blockCIDR net.IPNet, op return nil } - for i := 0; i < datastoreRetries; i++ { + for range datastoreRetries { obj, err := c.blockReaderWriter.queryBlock(ctx, blockCIDR, "") if err != nil { if _, ok := err.(cerrors.ErrorResourceDoesNotExist); ok { @@ -1803,10 +1951,22 @@ func (c ipamClient) releaseByHandle(ctx context.Context, blockCIDR net.IPNet, op return errors.New("Hit max retries") } -func (c ipamClient) incrementHandle(ctx context.Context, handleID string, blockCIDR net.IPNet, num int) error { +// ErrMaxAllocReached is returned when a handle already has the maximum number of allocations. +type ErrMaxAllocReached struct { + HandleID string + CurrentCount int + MaxAlloc int +} + +func (e ErrMaxAllocReached) Error() string { + return fmt.Sprintf("handle %s already has %d allocation(s), cannot allocate more (maxAlloc=%d)", + e.HandleID, e.CurrentCount, e.MaxAlloc) +} + +func (c ipamClient) incrementHandle(ctx context.Context, handleID string, blockCIDR net.IPNet, num int, maxAlloc int) error { var obj *model.KVPair var err error - for i := 0; i < datastoreRetries; i++ { + for range datastoreRetries { obj, err = c.blockReaderWriter.queryHandle(ctx, handleID, "") if err != nil { if _, ok := err.(cerrors.ErrorResourceDoesNotExist); ok { @@ -1829,6 +1989,25 @@ func (c ipamClient) incrementHandle(ctx context.Context, handleID string, blockC // Get the handle from the KVPair. handle := allocationHandle{obj.Value.(*model.IPAMHandle)} + // Check if maxAlloc constraint would be violated before incrementing + // maxAlloc is enforced per IP version (IPv4/IPv6) + if maxAlloc > 0 { + // Determine IP version from the block CIDR + ipVersion := 4 + if blockCIDR.IP.To4() == nil { + ipVersion = 6 + } + + currentCount := handle.totalCountByVersion(ipVersion) + if currentCount+num > maxAlloc { + return ErrMaxAllocReached{ + HandleID: handleID, + CurrentCount: currentCount, + MaxAlloc: maxAlloc, + } + } + } + // Increment the handle for this block. handle.incrementBlock(blockCIDR, num) @@ -1856,7 +2035,7 @@ func (c ipamClient) incrementHandle(ctx context.Context, handleID string, blockC } func (c ipamClient) decrementHandle(ctx context.Context, handleID string, blockCIDR net.IPNet, num int, obj *model.KVPair) error { - for i := 0; i < datastoreRetries; i++ { + for i := range datastoreRetries { var err error // Query the handle if either of these conditions is true: // - This is the first iteration, and the caller did not provide the current handle. @@ -1904,35 +2083,6 @@ func (c ipamClient) decrementHandle(ctx context.Context, handleID string, blockC return errors.New("Max retries hit - excessive concurrent IPAM requests") } -// GetAssignmentAttributes returns the attributes stored with the given IP address -// upon assignment, as well as the handle used for assignment (if any). -func (c ipamClient) GetAssignmentAttributes(ctx context.Context, addr net.IP) (map[string]string, *string, error) { - pool, err := c.blockReaderWriter.getPoolForIP(ctx, addr, nil) - if err != nil { - return nil, nil, err - } - if pool == nil { - log.Errorf("Error reading pool for %s", addr.String()) - return nil, nil, cerrors.ErrorResourceDoesNotExist{Identifier: addr.String(), Err: errors.New("No valid IPPool")} - } - blockCIDR := getBlockCIDRForAddress(addr, pool) - obj, err := c.blockReaderWriter.queryBlock(ctx, blockCIDR, "") - if err != nil { - log.Errorf("Error reading block %s: %v", blockCIDR, err) - return nil, nil, err - } - block := allocationBlock{obj.Value.(*model.AllocationBlock)} - attrs, err := block.attributesForIP(addr) - if err != nil { - return nil, nil, err - } - handle, err := block.handleForIP(addr) - if err != nil { - return nil, nil, err - } - return attrs, handle, nil -} - // GetIPAMConfig returns the global IPAM configuration. If no IPAM configuration // has been set, returns a default configuration with StrictAffinity disabled // and AutoAllocateBlocks enabled. @@ -1958,12 +2108,14 @@ func (c ipamClient) GetIPAMConfig(ctx context.Context) (*IPAMConfig, error) { } // Create the default config because it doesn't already exist. + enabled := "Enabled" kvp := &model.KVPair{ Key: model.IPAMConfigKey{}, Value: &model.IPAMConfig{ - StrictAffinity: false, - AutoAllocateBlocks: true, - MaxBlocksPerHost: 0, + StrictAffinity: false, + AutoAllocateBlocks: true, + MaxBlocksPerHost: 0, + KubeVirtVMAddressPersistence: &enabled, // Default: enabled for auto-detection }, } @@ -2059,10 +2211,16 @@ func (c ipamClient) convertIPAMConfigToBackend(cfg *IPAMConfig) *model.IPAMConfi } func (c ipamClient) convertBackendToIPAMConfig(cfg *model.IPAMConfig) *IPAMConfig { + var persistence *VMAddressPersistence + if cfg.KubeVirtVMAddressPersistence != nil { + enum := VMAddressPersistence(*cfg.KubeVirtVMAddressPersistence) + persistence = &enum + } return &IPAMConfig{ - StrictAffinity: cfg.StrictAffinity, - AutoAllocateBlocks: cfg.AutoAllocateBlocks, - MaxBlocksPerHost: cfg.MaxBlocksPerHost, + StrictAffinity: cfg.StrictAffinity, + AutoAllocateBlocks: cfg.AutoAllocateBlocks, + MaxBlocksPerHost: cfg.MaxBlocksPerHost, + KubeVirtVMAddressPersistence: persistence, } } @@ -2090,7 +2248,7 @@ func (c ipamClient) ensureConsistentAffinity(ctx context.Context, b *model.Alloc // we should release the block's affinity to this node so it can be // used elsewhere. logCtx.Debugf("Looking up node labels for host affinity") - node, err := c.client.Get(ctx, model.ResourceKey{Kind: libapiv3.KindNode, Name: affinityCfg.Host}, "") + node, err := c.client.Get(ctx, model.ResourceKey{Kind: internalapi.KindNode, Name: affinityCfg.Host}, "") if err != nil { if _, ok := err.(cerrors.ErrorResourceDoesNotExist); !ok { logCtx.WithError(err).WithField("node", affinityCfg.Host).Error("Failed to get node for host") @@ -2101,7 +2259,7 @@ func (c ipamClient) ensureConsistentAffinity(ctx context.Context, b *model.Alloc } // Make sure the returned value is a valid node. - v3n, ok := node.Value.(*libapiv3.Node) + v3n, ok := node.Value.(*internalapi.Node) if !ok { return fmt.Errorf("Datastore returned malformed node object") } @@ -2125,7 +2283,9 @@ func (c ipamClient) ensureConsistentAffinity(ctx context.Context, b *model.Alloc logCtx.WithField("selector", pool.Spec.NodeSelector).Debug("Pool no longer selects node, releasing block affinity") // Pool does not match this node's label, release this block's affinity. - if err = c.blockReaderWriter.releaseBlockAffinity(ctx, *affinityCfg, b.CIDR, true); err != nil { + if err = c.blockReaderWriter.releaseBlockAffinity(ctx, *affinityCfg, b.CIDR, releaseAffinityOpts{ + RequireEmpty: true, + }); err != nil { if _, ok := err.(errBlockClaimConflict); ok { // Not claimed by this host - ignore. } else if _, ok := err.(errBlockNotEmpty); ok { @@ -2352,9 +2512,9 @@ func (c ipamClient) getReservedIPs(ctx context.Context) (addrFilter, error) { } var cidrs cidrSliceFilter for _, r := range reservations.Items { - for _, cidrStr := range r.Spec.ReservedCIDRs { - cidrStr = strings.TrimSpace(cidrStr) - if len(cidrStr) == 0 { + for _, cidrVal := range r.Spec.ReservedCIDRs { + cidrStr := strings.TrimSpace(string(cidrVal)) + if len(cidrVal) == 0 { // Defensive, validation should prevent. continue } @@ -2403,7 +2563,7 @@ func (c ipamClient) attemptUpgradeHost(ctx context.Context, nodeName string) err return err } kvs, err := c.client.List(ctx, model.ResourceListOptions{ - Kind: libapiv3.KindBlockAffinity, + Kind: internalapi.KindBlockAffinity, LabelSelector: ls, }, "") if err != nil { @@ -2413,7 +2573,7 @@ func (c ipamClient) attemptUpgradeHost(ctx context.Context, nodeName string) err var errs []error numUpgraded := 0 for _, kv := range kvs.KVPairs { - if val, ok := kv.Value.(*libapiv3.BlockAffinity); ok { + if val, ok := kv.Value.(*internalapi.BlockAffinity); ok { if val.Spec.Node != nodeName { // Only do _our_ affinities to avoid n x n conflicts with other // nodes also doing this operation. @@ -2429,6 +2589,22 @@ func (c ipamClient) attemptUpgradeHost(ctx context.Context, nodeName string) err continue } numUpgraded++ + } else if val, ok := kv.Value.(*v3.BlockAffinity); ok { + if val.Spec.Node != nodeName { + // Only do _our_ affinities to avoid n x n conflicts with other + // nodes also doing this operation. + continue + } + model.EnsureBlockAffinityLabelsV3(val) + _, err := c.client.Update(ctx, kv) + if err != nil { + errs = append(errs, err) + if errors.Is(err, context.DeadlineExceeded) { + break + } + continue + } + numUpgraded++ } } if numUpgraded == 0 && len(errs) == 0 { diff --git a/libcalico-go/lib/ipam/ipam_attributes.go b/libcalico-go/lib/ipam/ipam_attributes.go new file mode 100644 index 00000000000..cd567859dd0 --- /dev/null +++ b/libcalico-go/lib/ipam/ipam_attributes.go @@ -0,0 +1,151 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. +// +// 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 ipam + +import ( + "context" + "errors" + "fmt" + + log "github.com/sirupsen/logrus" + + "github.com/projectcalico/calico/libcalico-go/lib/backend/model" + cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" + cnet "github.com/projectcalico/calico/libcalico-go/lib/net" +) + +// GetAssignmentAttributes returns the AllocationAttribute for the given IP address, +// which includes the handle ID, ActiveOwnerAttrs, and AlternateOwnerAttrs. +// This provides an atomic snapshot of all allocation attributes for the IP. +// Returns nil if the IP is not assigned. +func (c ipamClient) GetAssignmentAttributes(ctx context.Context, addr cnet.IP) (*model.AllocationAttribute, error) { + pool, err := c.blockReaderWriter.getPoolForIP(ctx, addr, nil) + if err != nil { + return nil, err + } + if pool == nil { + log.Errorf("Error reading pool for %s", addr.String()) + return nil, cerrors.ErrorResourceDoesNotExist{Identifier: addr.String(), Err: errors.New("No valid IPPool")} + } + blockCIDR := getBlockCIDRForAddress(addr, pool) + obj, err := c.blockReaderWriter.queryBlock(ctx, blockCIDR, "") + if err != nil { + log.Errorf("Error reading block %s: %v", blockCIDR, err) + return nil, err + } + block := allocationBlock{obj.Value.(*model.AllocationBlock)} + return block.allocationAttributesForIP(addr) +} + +// EmptyAttributeOwner returns an AttributeOwner with empty namespace and name. +// This can be used with Matches to check if attributes are empty. +func EmptyAttributeOwner() *AttributeOwner { + return &AttributeOwner{ + Namespace: "", + Name: "", + } +} + +// Matches checks if the given attributes match this owner. +func (o *AttributeOwner) Matches(attrs map[string]string) bool { + if o == nil { + log.Panic("Matches called on nil AttributeOwner") + } + + // If this is the empty owner, match if attrs is empty + if o.Namespace == "" && o.Name == "" { + return attrs == nil || len(attrs) == 0 + } + + // Otherwise, compare pod and namespace + if attrs == nil || len(attrs) == 0 { + return false + } + + actualPod, podExists := attrs[AttributePod] + actualNamespace, nsExists := attrs[AttributeNamespace] + + return podExists && nsExists && actualPod == o.Name && actualNamespace == o.Namespace +} + +// SetOwnerAttributes sets ActiveOwnerAttrs and/or AlternateOwnerAttrs for an IP atomically. +func (c ipamClient) SetOwnerAttributes(ctx context.Context, ip cnet.IP, handleID string, updates *OwnerAttributeUpdates, preconditions *OwnerAttributePreconditions) error { + if updates == nil { + return fmt.Errorf("updates cannot be nil") + } + if updates.ClearActiveOwner && updates.ActiveOwnerAttrs != nil { + return fmt.Errorf("cannot set both ClearActiveOwner=true and ActiveOwnerAttrs: these are mutually exclusive") + } + if updates.ClearAlternateOwner && updates.AlternateOwnerAttrs != nil { + return fmt.Errorf("cannot set both ClearAlternateOwner=true and AlternateOwnerAttrs: these are mutually exclusive") + } + if preconditions != nil { + if preconditions.ExpectedActiveOwner != nil && preconditions.VerifyActiveOwnerEmpty { + return fmt.Errorf("cannot set both ExpectedActiveOwner and VerifyActiveOwnerEmpty: these are mutually exclusive") + } + if preconditions.ExpectedAlternateOwner != nil && preconditions.VerifyAlternateOwnerEmpty { + return fmt.Errorf("cannot set both ExpectedAlternateOwner and VerifyAlternateOwnerEmpty: these are mutually exclusive") + } + } + + logCtx := log.WithFields(log.Fields{ + "ip": ip, + "handleID": handleID, + "updates": updates, + "preconditions": preconditions, + }) + logCtx.Info("Setting owner attributes") + + pool, err := c.blockReaderWriter.getPoolForIP(ctx, ip, nil) + if err != nil { + return err + } + if pool == nil { + return fmt.Errorf("the provided IP address %s is not in a configured pool", ip) + } + + blockCIDR := getBlockCIDRForAddress(ip, pool) + logCtx.Debugf("IP %s is in block '%s'", ip, blockCIDR) + + for i := 0; i < datastoreRetries; i++ { + obj, err := c.blockReaderWriter.queryBlock(ctx, blockCIDR, "") + if err != nil { + logCtx.WithError(err).Error("Error getting block") + return err + } + + block := allocationBlock{obj.Value.(*model.AllocationBlock)} + err = block.setOwnerAttributes(ip, handleID, updates, preconditions) + if err != nil { + logCtx.WithError(err).Error("Failed to set owner attributes") + return err + } + + _, err = c.blockReaderWriter.updateBlock(ctx, obj) + if err != nil { + if _, ok := err.(cerrors.ErrorResourceUpdateConflict); ok { + logCtx.WithError(err).Debug("CAS error setting owner attributes - retry") + continue + } + logCtx.WithError(err).Error("Failed to update block") + return err + } + + logCtx.Info("Successfully set owner attributes") + return nil + } + + return fmt.Errorf("max retries hit - excessive concurrent IPAM requests") +} diff --git a/libcalico-go/lib/ipam/ipam_block.go b/libcalico-go/lib/ipam/ipam_block.go index 49c6666e17d..6213250a898 100644 --- a/libcalico-go/lib/ipam/ipam_block.go +++ b/libcalico-go/lib/ipam/ipam_block.go @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2021 Tigera, Inc. All rights reserved. +// Copyright (c) 2016-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import ( "fmt" "net" "reflect" + "slices" "strings" "time" @@ -56,7 +57,7 @@ func newBlock(cidr cnet.IPNet, rsvdAttr *HostReservedAttr) allocationBlock { b.SequenceNumber = uint64(time.Now().UnixNano()) // Initialize unallocated ordinals. - for i := 0; i < numAddresses; i++ { + for i := range numAddresses { b.Unallocated[i] = i } @@ -83,7 +84,7 @@ func newBlock(cidr cnet.IPNet, rsvdAttr *HostReservedAttr) allocationBlock { // Create slice of IPs and perform the allocations. log.Debugf("Reserving allocation attribute: %#v handle %s", attrs, handleID) - attr := model.AllocationAttribute{AttrPrimary: &handleID, AttrSecondary: attrs} + attr := model.AllocationAttribute{HandleID: &handleID, ActiveOwnerAttrs: attrs} b.Attributes = append(b.Attributes, attr) } @@ -253,7 +254,7 @@ func (b *allocationBlock) containsOnlyReservedIPs() bool { continue } attrs := b.Attributes[*attrIdx] - if attrs.AttrPrimary == nil || strings.ToLower(*attrs.AttrPrimary) != WindowsReservedHandle { + if attrs.HandleID == nil || strings.ToLower(*attrs.HandleID) != WindowsReservedHandle { return false } } @@ -315,7 +316,7 @@ func (b *allocationBlock) release(addresses []ReleaseOptions) ([]cnet.IP, map[st // Compare handles. handleID := "" - if h := b.Attributes[*attrIdx].AttrPrimary; h != nil { + if h := b.Attributes[*attrIdx].HandleID; h != nil { // The handle in the allocation may be malformed, so requires sanitation // before use in the code. handleID = sanitizeHandle(*h) @@ -433,7 +434,7 @@ func (b allocationBlock) attributeRefCounts() map[int]int { func (b allocationBlock) attributeIndexesByHandle(handleID string) []int { indexes := []int{} for i, attr := range b.Attributes { - if attr.AttrPrimary != nil && sanitizeHandle(*attr.AttrPrimary) == handleID { + if attr.HandleID != nil && sanitizeHandle(*attr.HandleID) == handleID { indexes = append(indexes, i) } } @@ -511,29 +512,36 @@ func (b allocationBlock) attributesForIP(ip cnet.IP) (map[string]string, error) log.Debugf("IP %s is not currently assigned in block", ip) return nil, cerrors.ErrorResourceDoesNotExist{Identifier: ip.String(), Err: errors.New("IP is unassigned")} } - return b.Attributes[*attrIndex].AttrSecondary, nil + return b.Attributes[*attrIndex].ActiveOwnerAttrs, nil } -func (b allocationBlock) handleForIP(ip cnet.IP) (*string, error) { - // Convert to an ordinal. +// allocationAttributesForIP returns the full AllocationAttribute for the given IP, +// including HandleID, ActiveOwnerAttrs, and AlternateOwnerAttrs in a single lookup. +func (b allocationBlock) allocationAttributesForIP(ip cnet.IP) (*model.AllocationAttribute, error) { ordinal, err := b.IPToOrdinal(ip) if err != nil { return nil, err } - // Check if allocated. attrIndex := b.Allocations[ordinal] if attrIndex == nil { log.Debugf("IP %s is not currently assigned in block", ip) return nil, cerrors.ErrorResourceDoesNotExist{Identifier: ip.String(), Err: errors.New("IP is unassigned")} } - if h := b.Attributes[*attrIndex].AttrPrimary; h != nil { + + attr := b.Attributes[*attrIndex] + handle := attr.HandleID + if handle != nil { // The handle in the allocation may be malformed, so requires sanitation // before use in the code. - s := sanitizeHandle(*h) - return &s, nil - } - return nil, nil + s := sanitizeHandle(*handle) + handle = &s + } + return &model.AllocationAttribute{ + HandleID: handle, + ActiveOwnerAttrs: attr.ActiveOwnerAttrs, + AlternateOwnerAttrs: attr.AlternateOwnerAttrs, + }, nil } func (b *allocationBlock) findOrAddAttribute(handleID *string, attrs map[string]string) int { @@ -541,7 +549,7 @@ func (b *allocationBlock) findOrAddAttribute(handleID *string, attrs map[string] if handleID != nil { logCtx = log.WithField("handle", *handleID) } - attr := model.AllocationAttribute{AttrPrimary: handleID, AttrSecondary: attrs} + attr := model.AllocationAttribute{HandleID: handleID, ActiveOwnerAttrs: attrs} for idx, existing := range b.Attributes { if reflect.DeepEqual(attr, existing) { log.Debugf("Attribute '%+v' already exists", attr) @@ -556,6 +564,16 @@ func (b *allocationBlock) findOrAddAttribute(handleID *string, attrs map[string] return attrIndex } +func (b *allocationBlock) affinityClaimTime() time.Time { + if b.AllocationBlock == nil { + return time.Time{} + } + if b.AffinityClaimTime == nil { + return time.Time{} + } + return b.AffinityClaimTime.Time +} + func getBlockCIDRForAddress(addr cnet.IP, pool *v3.IPPool) cnet.IPNet { var mask net.IPMask if addr.Version() == 6 { @@ -582,10 +600,83 @@ func largerThanOrEqualToBlock(blockCIDR cnet.IPNet, pool *v3.IPPool) bool { } func intInSlice(searchInt int, slice []int) bool { - for _, v := range slice { - if v == searchInt { - return true + return slices.Contains(slice, searchInt) +} + +// setOwnerAttributes sets ActiveOwnerAttrs and/or AlternateOwnerAttrs for an IP address atomically. +// Callers must validate updates and preconditions before calling this method. +func (b *allocationBlock) setOwnerAttributes(ip cnet.IP, handleID string, updates *OwnerAttributeUpdates, preconditions *OwnerAttributePreconditions) error { + logCtx := log.WithFields(log.Fields{ + "ip": ip, + "handleID": handleID, + "updates": updates, + "preconditions": preconditions, + }) + + ordinal, err := b.IPToOrdinal(ip) + if err != nil { + return err + } + + attrIndex := b.Allocations[ordinal] + if attrIndex == nil { + logCtx.Debug("IP is not currently assigned in block") + return cerrors.ErrorResourceDoesNotExist{Identifier: ip.String(), Err: errors.New("IP is unassigned")} + } + + attr := &b.Attributes[*attrIndex] + + if attr.HandleID == nil || sanitizeHandle(*attr.HandleID) != handleID { + return fmt.Errorf("IP %s is not assigned to handle %s", ip, handleID) + } + + // Verify all preconditions before making any changes. + if updates.ActiveOwnerAttrs != nil || updates.ClearActiveOwner { + if err := verifyExpectedOwner(attr.ActiveOwnerAttrs, preconditions.expectedActiveOwner(), ip, "ActiveOwnerAttrs"); err != nil { + return err + } + } + if updates.AlternateOwnerAttrs != nil || updates.ClearAlternateOwner { + if err := verifyExpectedOwner(attr.AlternateOwnerAttrs, preconditions.expectedAlternateOwner(), ip, "AlternateOwnerAttrs"); err != nil { + return err + } + } + + // Apply updates now that all preconditions are verified. + if updates.ActiveOwnerAttrs != nil || updates.ClearActiveOwner { + if updates.ClearActiveOwner { + attr.ActiveOwnerAttrs = nil + } else { + attr.ActiveOwnerAttrs = updates.ActiveOwnerAttrs + } + } + if updates.AlternateOwnerAttrs != nil || updates.ClearAlternateOwner { + if updates.ClearAlternateOwner { + attr.AlternateOwnerAttrs = nil + } else { + attr.AlternateOwnerAttrs = updates.AlternateOwnerAttrs } } - return false + + return nil +} + +// verifyExpectedOwner checks that currentAttrs matches expectedOwner. Returns nil if +// expectedOwner is nil (no check requested) or the match succeeds. +func verifyExpectedOwner(currentAttrs map[string]string, expectedOwner *AttributeOwner, ip cnet.IP, field string) error { + if expectedOwner == nil { + return nil + } + if expectedOwner.Matches(currentAttrs) { + return nil + } + var currentPod, currentNamespace string + if currentAttrs != nil { + currentPod = currentAttrs[AttributePod] + currentNamespace = currentAttrs[AttributeNamespace] + } + return cerrors.ErrorResourceUpdateConflict{ + Err: fmt.Errorf("cannot set %s: expected pod=%s namespace=%s but found pod=%s namespace=%s", field, expectedOwner.Name, expectedOwner.Namespace, currentPod, currentNamespace), + Identifier: ip.String(), + } } diff --git a/libcalico-go/lib/ipam/ipam_block_reader_writer.go b/libcalico-go/lib/ipam/ipam_block_reader_writer.go index 12b1c58c8a0..311fe4f00cb 100644 --- a/libcalico-go/lib/ipam/ipam_block_reader_writer.go +++ b/libcalico-go/lib/ipam/ipam_block_reader_writer.go @@ -26,6 +26,7 @@ import ( v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" @@ -33,6 +34,12 @@ import ( cnet "github.com/projectcalico/calico/libcalico-go/lib/net" ) +// EmptyBlockMinReclaimAge is the minimum time since a block was claimed before +// another node will consider reclaiming that block if the node needs a block and +// the block is empty. Prevents a block from ping-ponging between two nodes +// with no chance for either node to actually allocate an IP from it. +const EmptyBlockMinReclaimAge = time.Minute + type blockReaderWriter struct { client bapi.Client pools PoolAccessorInterface @@ -108,7 +115,14 @@ func findContainingPool(pools []v3.IPPool, addr net.IP) (*v3.IPPool, error) { // // Note that the block may become claimed between receiving the CIDR from this function and attempting to claim the corresponding // block as this function does not reserve the returned IPNet. -func (rw blockReaderWriter) findUsableBlock(ctx context.Context, affinityCfg AffinityConfig, version int, pools []v3.IPPool, reservations addrFilter, config IPAMConfig) (*cnet.IPNet, error) { +func (rw blockReaderWriter) findUsableBlock( + ctx context.Context, + affinityCfg AffinityConfig, + version int, + pools []v3.IPPool, + reservations addrFilter, + config IPAMConfig, +) (*cnet.IPNet, error) { // If there are no pools, we cannot assign addresses. if len(pools) == 0 { return nil, fmt.Errorf("no configured Calico pools for %s:%s", affinityCfg.AffinityType, affinityCfg.Host) @@ -124,22 +138,36 @@ func (rw blockReaderWriter) findUsableBlock(ctx context.Context, affinityCfg Aff type blockInfo struct { numFree int affinityCfg AffinityConfig + empty bool + cidr cnet.IPNet + claimTime time.Time + seqNo uint64 } // Build a map for faster lookups. exists := map[string]blockInfo{} for _, e := range existingBlocks.KVPairs { - host := e.Value.(*model.AllocationBlock).Host() - affinityType := AffinityType(e.Value.(*model.AllocationBlock).AffinityType()) + allocBlock := e.Value.(*model.AllocationBlock) + host := allocBlock.Host() + affinityType := AffinityType(allocBlock.AffinityType()) bAffinityCfg := AffinityConfig{ AffinityType: affinityType, Host: host, } - numFree := allocationBlock{e.Value.(*model.AllocationBlock)}.NumFreeAddresses(reservations) - exists[e.Key.(model.BlockKey).CIDR.String()] = blockInfo{numFree: numFree, affinityCfg: bAffinityCfg} + block := allocationBlock{allocBlock} + numFree := block.NumFreeAddresses(reservations) + exists[e.Key.(model.BlockKey).CIDR.String()] = blockInfo{ + numFree: numFree, + affinityCfg: bAffinityCfg, + cidr: e.Key.(model.BlockKey).CIDR, + empty: block.empty(), + claimTime: block.affinityClaimTime(), + seqNo: block.SequenceNumber, + } } // Iterate through pools to find a new block. + var emptyBlocks []blockInfo for _, pool := range pools { // Use a block generator to iterate through all of the blocks // that fall within the pool. @@ -158,13 +186,63 @@ func (rw blockReaderWriter) findUsableBlock(ctx context.Context, affinityCfg Aff log.Infof("Found free block: %+v", *subnet) return subnet, nil } else if info.affinityCfg == affinityCfg && info.numFree != 0 { - // Belongs to this host and has free allocations. Check that the IPs really are free (not reserved). + // Belongs to this host and has free allocations. We already took account of any reservations above. log.Debugf("Block %s already assigned to host, has free space", subnet.String()) return subnet, nil + } else if info.empty && info.numFree != 0 { + log.Debugf("Block %s is empty, may try to reclaim it if we run out.", subnet.String()) + emptyBlocks = append(emptyBlocks, info) } log.Debugf("Block %s already exists and is either affine to another host or has no space, try another", subnet.String()) } } + + if len(emptyBlocks) > 0 { + // This step is very important for small special-purpose IPAM pools + // with small blocks (and especially /32 blocks). Without it, we can + // be unable to allocate an IP even though there are fewer workloads + // using the special pool than there are total blocks (because the + // blocks get stranded, affine to other nodes). + log.Info("Ran out of unclaimed blocks, trying to reclaim an empty one.") + for i, block := range emptyBlocks { + age := time.Since(block.claimTime) + if age < EmptyBlockMinReclaimAge { + // Avoid a race where two nodes fight over a block, each freeing + // it before the other has a chance to use it. + log.Infof("Block %s was claimed %.1fs ago by another node, not trying to reclaim it.", + block.cidr.String(), age.Seconds()) + continue + } + err := rw.releaseBlockAffinity(ctx, block.affinityCfg, block.cidr, releaseAffinityOpts{ + RequireEmpty: true, + // Pass the sequence number through to make sure that we only + // release the affinity if the block hasn't been changed since + // we read it. Otherwise, two hosts that try to reclaim the + // same block race, and we may free the new version of the block + // that was just created by the other node. + RequiredBlockSequenceNumber: &block.seqNo, + }) + if err != nil { + log.Warnf("Failed to release affinity while trying to reclaim block. %v left to try: %s", len(emptyBlocks)-i, err) + if ctx.Err() != nil { + log.Warn("Context expired while trying to reclaim empty blocks. Giving up.") + return nil, ctx.Err() + } + // If not a context expiry, keep trying other blocks. + continue + } + + // We reclaimed a block. Return it to the caller, who'll try to + // claim it. + log.Infof("Reclaimed empty block: %s", block.cidr.String()) + subnet := block.cidr + return &subnet, nil + } + + log.Warn("Unable to reclaim any empty blocks.") + } + + log.Warn("No free blocks found in any pool.") return nil, noFreeBlocksError("No Free Blocks") } @@ -217,6 +295,8 @@ func (rw blockReaderWriter) claimAffineBlock(ctx context.Context, aff *model.KVP affinityKeyStr := fmt.Sprintf("%s:%s", affinityCfg.AffinityType, host) block := newBlock(subnet, rsvdAttr) block.Affinity = &affinityKeyStr + now := metav1.NewTime(time.Now()) + block.AffinityClaimTime = &now // Create the new block in the datastore. o := model.KVPair{ @@ -299,9 +379,21 @@ func (rw blockReaderWriter) confirmAffinity(ctx context.Context, aff *model.KVPa return confirmed, nil } +type releaseAffinityOpts struct { + RequireEmpty bool + // RequiredBlockSequenceNumber if non-nil, is checked against the sequence + // number in the block and, if it doesn't match, the release is aborted. + RequiredBlockSequenceNumber *uint64 +} + // releaseBlockAffinity releases the host's affinity to the given block, and returns an affinityClaimedError if // the host does not claim an affinity for the block. -func (rw blockReaderWriter) releaseBlockAffinity(ctx context.Context, affinityCfg AffinityConfig, blockCIDR cnet.IPNet, requireEmpty bool) error { +func (rw blockReaderWriter) releaseBlockAffinity( + ctx context.Context, + affinityCfg AffinityConfig, + blockCIDR cnet.IPNet, + opts releaseAffinityOpts, +) error { // Make sure hostname is not empty. if affinityCfg.Host == "" { log.Errorf("Hostname can't be empty") @@ -327,10 +419,19 @@ func (rw blockReaderWriter) releaseBlockAffinity(ctx context.Context, affinityCf } b := allocationBlock{obj.Value.(*model.AllocationBlock)} + if opts.RequiredBlockSequenceNumber != nil && *opts.RequiredBlockSequenceNumber != b.SequenceNumber { + // Block modified since caller read it and they want us to abort in + // that case. + return fmt.Errorf("IPAM block updated since we last read it") + } + // Check that the block affinity matches the given affinity. if b.Affinity != nil && !affinityMatches(affinityCfg, b.AllocationBlock) { // This means the affinity is stale - we can delete it. - logCtx.Errorf("Mismatched affinity: %s != %s - try to delete stale affinity", *b.Affinity, "host:"+affinityCfg.Host) + logCtx.Errorf("Mismatched affinity: %s != %s - try to delete stale affinity", + *b.Affinity, + fmt.Sprintf("%s:%s", affinityCfg.AffinityType, affinityCfg.Host), + ) if err := rw.deleteAffinity(ctx, aff); err != nil { logCtx.Warn("Failed to delete stale affinity") } @@ -338,7 +439,7 @@ func (rw blockReaderWriter) releaseBlockAffinity(ctx context.Context, affinityCf } // Don't release block affinity if we require it to be empty and it's not empty. - if requireEmpty && !b.empty() { + if opts.RequireEmpty && !b.empty() { logCtx.WithField("inUseIPs", b.inUseIPs()).Info("Block must be empty but is not empty, refusing to remove affinity.") return errBlockNotEmpty{Block: b} } @@ -369,6 +470,7 @@ func (rw blockReaderWriter) releaseBlockAffinity(ctx context.Context, affinityCf // non-affine blocks. logCtx.Debug("Block is not empty - remove the affinity") b.Affinity = nil + b.AffinityClaimTime = nil // Pass back the original KVPair with the new // block information so we can do a CAS. @@ -602,13 +704,18 @@ func randomBlockGenerator(ipPool v3.IPPool, hostName string) func() *cnet.IPNet } // Find the block for a given IP (without needing a pool) -func (rw blockReaderWriter) getBlockForIP(ctx context.Context, ip cnet.IP) (*cnet.IPNet, error) { - // Lookup all blocks by providing an empty BlockListOptions to the List operation. - opts := model.BlockListOptions{IPVersion: ip.Version()} - datastoreObjs, err := rw.client.List(ctx, opts, "") - if err != nil { - log.Errorf("Error getting affine blocks: %v", err) - return nil, err +func (rw blockReaderWriter) getBlockForIP(ctx context.Context, ip cnet.IP, cachedKVList *model.KVPairList) (*cnet.IPNet, *model.KVPairList, error) { + datastoreObjs := cachedKVList + + if datastoreObjs == nil { + // Lookup all blocks by providing an empty BlockListOptions to the List operation. + opts := model.BlockListOptions{IPVersion: ip.Version()} + var err error + datastoreObjs, err = rw.client.List(ctx, opts, "") + if err != nil { + log.Errorf("Error getting affine blocks: %v", err) + return nil, nil, err + } } // Iterate through and extract the block CIDRs. @@ -616,11 +723,11 @@ func (rw blockReaderWriter) getBlockForIP(ctx context.Context, ip cnet.IP) (*cne k := o.Key.(model.BlockKey) if k.CIDR.IPNet.Contains(ip.IP) { log.Debugf("Found IP %s in block %s", ip.String(), k.String()) - return &k.CIDR, nil + return &k.CIDR, datastoreObjs, nil } } // No blocks found. log.Debugf("IP %s could not be found in any blocks", ip.String()) - return nil, nil + return nil, datastoreObjs, nil } diff --git a/libcalico-go/lib/ipam/ipam_block_reader_writer_test.go b/libcalico-go/lib/ipam/ipam_block_reader_writer_test.go index a68885e71b3..ad94911feff 100644 --- a/libcalico-go/lib/ipam/ipam_block_reader_writer_test.go +++ b/libcalico-go/lib/ipam/ipam_block_reader_writer_test.go @@ -19,11 +19,13 @@ import ( "errors" "fmt" "sync" + "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" @@ -32,6 +34,7 @@ import ( "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" + "github.com/projectcalico/calico/libcalico-go/lib/ipam/ipamtestutils" cnet "github.com/projectcalico/calico/libcalico-go/lib/net" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -47,6 +50,17 @@ func newFakeClient() *fakeClient { } } +func newFakeClientWithPassThru(client api.Client) *fakeClient { + fc := newFakeClient() + fc.createFuncs["default"] = client.Create + fc.updateFuncs["default"] = client.Update + fc.getFuncs["default"] = client.Get + fc.deleteKVPFuncs["default"] = client.DeleteKVP + fc.deleteFuncs["default"] = client.Delete + fc.listFuncs["default"] = client.List + return fc +} + // fakeClient implements the backend api.Client interface. type fakeClient struct { createFuncs map[string]func(ctx context.Context, object *model.KVPair) (*model.KVPair, error) @@ -68,6 +82,7 @@ func (c *fakeClient) Create(ctx context.Context, object *model.KVPair) (*model.K panic(fmt.Sprintf("Create called on unexpected object: %+v", object)) } + func (c *fakeClient) Update(ctx context.Context, object *model.KVPair) (*model.KVPair, error) { if f, ok := c.updateFuncs[object.Key.String()]; ok { return f(ctx, object) @@ -75,8 +90,8 @@ func (c *fakeClient) Update(ctx context.Context, object *model.KVPair) (*model.K return f(ctx, object) } panic(fmt.Sprintf("Update called on unexpected object: %+v", object)) - } + func (c *fakeClient) Apply(ctx context.Context, object *model.KVPair) (*model.KVPair, error) { panic("should not be called") } @@ -102,9 +117,12 @@ func (c *fakeClient) Delete(ctx context.Context, key model.Key, revision string) } func (c *fakeClient) Get(ctx context.Context, key model.Key, revision string) (*model.KVPair, error) { - if f, ok := c.getFuncs[key.String()]; ok { + lookupKey := key.String() + if f, ok := c.getFuncs[lookupKey]; ok { + log.Infof("FAKE CLIENT: Get(ctx, %q, %q): custom shim function", lookupKey, revision) return f(ctx, key, revision) } else if f, ok := c.getFuncs["default"]; ok { + log.Infof("FAKE CLIENT: Get(ctx, %q, %q): default", lookupKey, revision) return f(ctx, key, revision) } panic(fmt.Sprintf("Get called on unexpected object: %+v", key)) @@ -145,19 +163,17 @@ type backendClientAccessor interface { } var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", testutils.DatastoreAll, func(config apiconfig.CalicoAPIConfig) { - log.SetLevel(log.DebugLevel) Context("IPAM block allocation race conditions", func() { - var ( bc api.Client net *cnet.IPNet ctx context.Context hostA, hostB string fc *fakeClient - resv *fakeReservations - pools *ipPoolAccessor + resv *ipamtestutils.FakeReservations + pools *ipamtestutils.IPPoolAccessor rw blockReaderWriter ic *ipamClient kc *kubernetes.Clientset @@ -169,21 +185,21 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes // Make the client and clean the data store. bc, err = backend.NewClient(config) Expect(err).NotTo(HaveOccurred()) - bc.Clean() + Expect(bc.Clean()).To(Succeed()) // If running in KDD mode, extract the k8s clientset. if config.Spec.DatastoreType == "kubernetes" { kc = bc.(*k8s.KubeClient).ClientSet } - resv = &fakeReservations{} + resv = &ipamtestutils.FakeReservations{} hostA = "host-a" hostB = "host-b" applyNode(bc, kc, hostA, nil) applyNode(bc, kc, hostB, nil) - pools = &ipPoolAccessor{pools: map[string]pool{"10.0.0.0/26": {enabled: true}}} + pools = &ipamtestutils.IPPoolAccessor{Pools: map[string]ipamtestutils.Pool{"10.0.0.0/26": {Enabled: true}}} ctx = context.Background() @@ -194,7 +210,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes It("should handle multiple racing block affinity claims from different hosts", func() { By("setting up the client for the test", func() { // Pool has room for 16 blocks. - pls := &ipPoolAccessor{pools: map[string]pool{"10.0.0.0/22": {enabled: true}}} + pls := &ipamtestutils.IPPoolAccessor{Pools: map[string]ipamtestutils.Pool{"10.0.0.0/22": {Enabled: true}}} rw = blockReaderWriter{client: bc, pools: pls} ic = &ipamClient{ reservations: resv, @@ -208,7 +224,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes wg := sync.WaitGroup{} var testErr error - for i := 0; i < 32; i++ { + for i := range 32 { wg.Add(1) j := i go func() { @@ -219,7 +235,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes applyNode(bc, kc, testhost, nil) defer deleteNode(bc, kc, testhost) - ia, err := ic.autoAssign(ctx, 1, &testhost, nil, nil, 4, testhost, 0, nil, v3.IPPoolAllowedUseWorkload, nil) + ia, err := ic.autoAssign(ctx, 1, &testhost, nil, nil, 4, testhost, 0, nil, v3.IPPoolAllowedUseWorkload, nil, 0) if err != nil { log.WithError(err).Errorf("Auto assign failed for host %s", testhost) testErr = err @@ -262,7 +278,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes By("checking each allocated IP is within the correct block for that host", func() { // Iterate through all the hosts. If the host has an affine block, // make sure the IPs assigned to that host are within the block. - for i := 0; i < 32; i++ { + for i := range 32 { hostname := fmt.Sprintf("host-%d", i) affs, err := bc.List(ctx, model.BlockAffinityListOptions{Host: hostname, AffinityType: string(AffinityTypeHost)}, "") Expect(err).NotTo(HaveOccurred()) @@ -285,7 +301,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes It("should handle multiple racing block affinity claims from the same host", func() { By("setting up the client for the test", func() { // Pool has room for 16 blocks. - pls := &ipPoolAccessor{pools: map[string]pool{"10.0.0.0/25": {enabled: true}}} + pls := &ipamtestutils.IPPoolAccessor{Pools: map[string]ipamtestutils.Pool{"10.0.0.0/25": {Enabled: true}}} rw = blockReaderWriter{client: bc, pools: pls} ic = &ipamClient{ client: bc, @@ -301,13 +317,13 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes testhost := "single-host" applyNode(bc, kc, testhost, nil) - for i := 0; i < 4; i++ { + for range 4 { wg.Add(1) go func() { defer GinkgoRecover() defer wg.Done() - ia, err := ic.autoAssign(ctx, 1, nil, nil, nil, 4, testhost, 0, nil, v3.IPPoolAllowedUseWorkload, nil) + ia, err := ic.autoAssign(ctx, 1, nil, nil, nil, 4, testhost, 0, nil, v3.IPPoolAllowedUseWorkload, nil, 0) if err != nil { log.WithError(err).Errorf("Auto assign failed for host %s", testhost) testErr = err @@ -351,7 +367,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes It("should handle multiple racing claims for the same affinity", func() { By("setting up the client for the test", func() { // Pool has room for 16 blocks. - pls := &ipPoolAccessor{pools: map[string]pool{"10.0.0.0/22": {enabled: true}}} + pls := &ipamtestutils.IPPoolAccessor{Pools: map[string]ipamtestutils.Pool{"10.0.0.0/22": {Enabled: true}}} rw = blockReaderWriter{client: bc, pools: pls} ic = &ipamClient{ client: bc, @@ -369,7 +385,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes wg := sync.WaitGroup{} var testErr error - for i := 0; i < 4; i++ { + for range 4 { wg.Add(1) go func() { defer GinkgoRecover() @@ -423,7 +439,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes wg := sync.WaitGroup{} var testErr error - for i := 0; i < 4; i++ { + for range 4 { wg.Add(1) go func() { defer GinkgoRecover() @@ -435,7 +451,6 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes log.WithError(err).Errorf("Failed to release affinity for host %s", testhost) testErr = err } - }() } @@ -533,9 +548,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes // - hostA proc2 creates the block. // - hostA proc1 tries to delete the affinity. - var ( - blockKVP, affinityKVP model.KVPair - ) + var blockKVP, affinityKVP model.KVPair By("setting up the client for the test", func() { b := newBlock(*net, nil) @@ -607,7 +620,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes By("attempting to release the block", func() { affinityCfg := AffinityConfig{AffinityType: AffinityTypeHost, Host: hostA} - err := rw.releaseBlockAffinity(ctx, affinityCfg, *net, false) + err := rw.releaseBlockAffinity(ctx, affinityCfg, *net, releaseAffinityOpts{}) Expect(err).NotTo(BeNil()) // Should hit a resource update conflict. @@ -649,6 +662,8 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes // was already taken by another host. b := newBlock(*net, nil) b.Affinity = &affStrA + now := metav1.NewTime(time.Now()) + b.AffinityClaimTime = &now blockKVP := &model.KVPair{ Key: model.BlockKey{CIDR: *net}, Value: b.AllocationBlock, @@ -656,6 +671,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes b2 := newBlock(*net, nil) b2.Affinity = &affStrB + b2.AffinityClaimTime = &now blockKVP2 := &model.KVPair{ Key: model.BlockKey{CIDR: *net}, Value: b2.AllocationBlock, @@ -727,7 +743,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes } By("attempting to claim the block on multiple hosts at the same time", func() { - ia, err := ic.autoAssign(ctx, 1, nil, nil, nil, 4, hostA, 0, nil, v3.IPPoolAllowedUseWorkload, nil) + ia, err := ic.autoAssign(ctx, 1, nil, nil, nil, 4, hostA, 0, nil, v3.IPPoolAllowedUseWorkload, nil, 0) // Shouldn't return an error. Expect(err).NotTo(HaveOccurred()) @@ -757,7 +773,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes }) By("attempting to claim another address", func() { - ia, err := ic.autoAssign(ctx, 1, nil, nil, nil, 4, hostA, 0, nil, v3.IPPoolAllowedUseWorkload, nil) + ia, err := ic.autoAssign(ctx, 1, nil, nil, nil, 4, hostA, 0, nil, v3.IPPoolAllowedUseWorkload, nil, 0) // Shouldn't return an error. Expect(err).NotTo(HaveOccurred()) @@ -777,6 +793,137 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes }) }) + ageTheBlock := func() { + block, err := bc.Get(ctx, model.BlockKey{CIDR: *net}, "") + Expect(err).NotTo(HaveOccurred()) + overAMinuteAgo := metav1.NewTime(time.Now().Add(-61 * time.Second)) + block.Value.(*model.AllocationBlock).AffinityClaimTime = &overAMinuteAgo + _, err = bc.Update(ctx, block) + Expect(err).NotTo(HaveOccurred()) + } + + It("should reclaim an empty block", func() { + // Create the block reader / writer which will simulate the failure scenario. + rw = blockReaderWriter{client: bc, pools: pools} + ic = &ipamClient{ + client: bc, + pools: pools, + blockReaderWriter: rw, + reservations: resv, + } + + By("enabling strict affinity", func() { + kv := model.KVPair{ + Key: model.IPAMConfigKey{}, + Value: &model.IPAMConfig{ + AutoAllocateBlocks: true, + StrictAffinity: true, + }, + } + _, err := bc.Create(ctx, &kv) + Expect(err).NotTo(HaveOccurred()) + }) + + var theIP string + By("claiming a block on one host", func() { + ia, err := ic.autoAssign(ctx, 1, nil, nil, nil, 4, hostA, 0, nil, v3.IPPoolAllowedUseWorkload, nil, 0) + Expect(err).NotTo(HaveOccurred()) + Expect(len(ia.IPs)).To(Equal(1)) + theIP = ia.IPs[0].IP.String() + }) + + By("trying to claim the block on another host while it is non-empty", func() { + ia, err := ic.autoAssign(ctx, 1, nil, nil, nil, 4, hostB, 0, nil, v3.IPPoolAllowedUseWorkload, nil, 0) + Expect(err).NotTo(HaveOccurred(), "expected no error when pool full") + Expect(ia.IPs).To(HaveLen(0), "shouldn't get any IPs returned") + }) + + By("freeing the IP so the block is empty", func() { + _, _, err := ic.ReleaseIPs(ctx, ReleaseOptions{ + Address: theIP, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + By("trying to claim the block on another host now that it is empty (but still recently claimed)", func() { + ia, err := ic.autoAssign(ctx, 1, nil, nil, nil, 4, hostB, 0, nil, v3.IPPoolAllowedUseWorkload, nil, 0) + Expect(err).NotTo(HaveOccurred(), "expected no error when pool full") + Expect(ia.IPs).To(HaveLen(0), "shouldn't get any IPs returned") + }) + + By("artificially aging the block", ageTheBlock) + + By("trying to claim the block on another host now that it is empty and old enough to be reclaimed", func() { + ia, err := ic.autoAssign(ctx, 1, nil, nil, nil, 4, hostB, 0, nil, v3.IPPoolAllowedUseWorkload, nil, 0) + Expect(err).NotTo(HaveOccurred(), "allocation should succeed via block reclaim") + Expect(len(ia.IPs)).To(Equal(1)) + }) + }) + + It("should handle failure to reclaim a block", func() { + fc := newFakeClientWithPassThru(bc) + // Create the block reader / writer which will simulate the failure scenario. + rw = blockReaderWriter{client: fc, pools: pools} + ic = &ipamClient{ + client: fc, + pools: pools, + blockReaderWriter: rw, + reservations: resv, + } + + By("enabling strict affinity", func() { + kv := model.KVPair{ + Key: model.IPAMConfigKey{}, + Value: &model.IPAMConfig{ + AutoAllocateBlocks: true, + StrictAffinity: true, + }, + } + _, err := bc.Create(ctx, &kv) + Expect(err).NotTo(HaveOccurred()) + }) + + var theIP string + By("claiming a block on one host", func() { + ia, err := ic.autoAssign(ctx, 1, nil, nil, nil, 4, hostA, 0, nil, v3.IPPoolAllowedUseWorkload, nil, 0) + Expect(err).NotTo(HaveOccurred()) + Expect(len(ia.IPs)).To(Equal(1)) + theIP = ia.IPs[0].IP.String() + }) + + By("freeing the IP so the block is empty", func() { + _, _, err := ic.ReleaseIPs(ctx, ReleaseOptions{ + Address: theIP, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + By("artificially aging the block", ageTheBlock) + + By("trying to claim the reclaimable block, with a delete error queued", func() { + f := fc.deleteKVPFuncs["default"] + fc.deleteKVPFuncs["default"] = func(ctx context.Context, kvp *model.KVPair) (*model.KVPair, error) { + return nil, errors.New("expected error") + } + ia, err := ic.autoAssign(ctx, 1, nil, nil, nil, 4, hostB, 0, nil, v3.IPPoolAllowedUseWorkload, nil, 0) + Expect(err).NotTo(HaveOccurred()) + Expect(len(ia.IPs)).To(Equal(0), "expected no IPs returned due to failure to free affinity") + fc.deleteKVPFuncs["default"] = f + }) + + By("trying to claim the reclaimable block, with a context error queued", func() { + subCtx, cancel := context.WithCancel(ctx) + defer cancel() + fc.getFuncs[model.BlockKey{CIDR: *net}.String()] = func(ctx context.Context, key model.Key, revision string) (*model.KVPair, error) { + cancel() + return nil, subCtx.Err() + } + ia, err := ic.autoAssign(subCtx, 1, nil, nil, nil, 4, hostB, 0, nil, v3.IPPoolAllowedUseWorkload, nil, 0) + Expect(err).To(Equal(subCtx.Err())) + Expect(len(ia.IPs)).To(Equal(0), "expected no IPs returned due to context failure") + }) + }) + It("should not delete an IPAM block when affinity released when empty but another process allocates an IP at the same time", func() { // Tests the scenario where: // - hostA proc1 queries for the block @@ -871,7 +1018,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes Context("test claiming / releasing affinities", func() { var ( rw blockReaderWriter - p *ipPoolAccessor + p *ipamtestutils.IPPoolAccessor bc api.Client ctx context.Context host string @@ -896,7 +1043,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes client: bc, pools: p, } - p = &ipPoolAccessor{pools: map[string]pool{}} + p = &ipamtestutils.IPPoolAccessor{Pools: map[string]ipamtestutils.Pool{}} ctx = context.Background() _, net, err = cnet.ParseCIDR("10.1.0.0/26") @@ -979,13 +1126,13 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes By("releasing the affinity", func() { affinityCfg := AffinityConfig{AffinityType: AffinityTypeHost, Host: host} - err := rw.releaseBlockAffinity(ctx, affinityCfg, *net, false) + err := rw.releaseBlockAffinity(ctx, affinityCfg, *net, releaseAffinityOpts{}) Expect(err).NotTo(HaveOccurred()) }) By("releasing the virtual affinity", func() { affinityCfg := AffinityConfig{AffinityType: AffinityTypeVirtual, Host: host} - err := rw.releaseBlockAffinity(ctx, affinityCfg, *loadBalancerNet, false) + err := rw.releaseBlockAffinity(ctx, affinityCfg, *loadBalancerNet, releaseAffinityOpts{}) Expect(err).NotTo(HaveOccurred()) }) @@ -1015,7 +1162,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes By("releasing the affinity again", func() { affinityCfg := AffinityConfig{AffinityType: AffinityTypeHost, Host: host} - err := rw.releaseBlockAffinity(ctx, affinityCfg, *net, false) + err := rw.releaseBlockAffinity(ctx, affinityCfg, *net, releaseAffinityOpts{}) Expect(err).To(HaveOccurred()) _, ok := err.(cerrors.ErrorResourceDoesNotExist) Expect(ok).To(BeTrue()) @@ -1023,7 +1170,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes By("releasing the virtual affinity again", func() { affinityCfg := AffinityConfig{AffinityType: AffinityTypeVirtual, Host: host} - err := rw.releaseBlockAffinity(ctx, affinityCfg, *loadBalancerNet, false) + err := rw.releaseBlockAffinity(ctx, affinityCfg, *loadBalancerNet, releaseAffinityOpts{}) Expect(err).To(HaveOccurred()) _, ok := err.(cerrors.ErrorResourceDoesNotExist) Expect(ok).To(BeTrue()) @@ -1034,7 +1181,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes Context("test block allocation with host reserves", func() { var ( rw blockReaderWriter - p *ipPoolAccessor + p *ipamtestutils.IPPoolAccessor bc api.Client ctx context.Context host string @@ -1061,7 +1208,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes client: bc, pools: p, } - p = &ipPoolAccessor{pools: map[string]pool{"10.0.0.0/30": {enabled: true, blockSize: 30, cidr: "10.0.0.0/30"}}} + p = &ipamtestutils.IPPoolAccessor{Pools: map[string]ipamtestutils.Pool{"10.0.0.0/30": {Enabled: true, BlockSize: 30, CIDR: "10.0.0.0/30"}}} ctx = context.Background() _, net, err = cnet.ParseCIDR("10.0.0.0/30") @@ -1090,9 +1237,8 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes Expect(b.Allocations[2]).To(BeNil()) Expect(*b.Allocations[3]).To(Equal(0)) // Attributes[0] should be the reservation attribute. - Expect(*b.Attributes[0].AttrPrimary).To(Equal("test-handle")) - Expect(b.Attributes[0].AttrSecondary["note"]).To(Equal("ipam ut")) - + Expect(*b.Attributes[0].HandleID).To(Equal("test-handle")) + Expect(b.Attributes[0].ActiveOwnerAttrs["note"]).To(Equal("ipam ut")) }) It("should allocate one ip", func() { @@ -1100,9 +1246,9 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes client: bc, pools: p, blockReaderWriter: rw, - reservations: &fakeReservations{}, + reservations: &ipamtestutils.FakeReservations{}, } - ia, err := ic.autoAssign(ctx, 1, nil, nil, nil, 4, host, 0, rsvdAttr, v3.IPPoolAllowedUseTunnel, nil) + ia, err := ic.autoAssign(ctx, 1, nil, nil, nil, 4, host, 0, rsvdAttr, v3.IPPoolAllowedUseTunnel, nil, 0) Expect(err).ShouldNot(HaveOccurred()) Expect(len(ia.IPs)).To(Equal(1)) Expect(ia.IPs[0].String()).To(Equal("10.0.0.2/30")) @@ -1111,7 +1257,6 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests", tes }) var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests (kdd only)", testutils.DatastoreK8s, func(config apiconfig.CalicoAPIConfig) { - var ( bc api.Client net *cnet.IPNet @@ -1206,5 +1351,4 @@ var _ = testutils.E2eDatastoreDescribe("IPAM affine block allocation tests (kdd _, err = bc.DeleteKVP(ctx, kvpb) Expect(err).NotTo(HaveOccurred()) }) - }) diff --git a/libcalico-go/lib/ipam/ipam_handle.go b/libcalico-go/lib/ipam/ipam_handle.go index dbfcece5a00..29a87a95af5 100644 --- a/libcalico-go/lib/ipam/ipam_handle.go +++ b/libcalico-go/lib/ipam/ipam_handle.go @@ -17,6 +17,7 @@ package ipam import ( "errors" "fmt" + "strings" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" cnet "github.com/projectcalico/calico/libcalico-go/lib/net" @@ -63,3 +64,25 @@ func (h allocationHandle) decrementBlock(blockCidr cnet.IPNet, num int) (*int, e func (h allocationHandle) empty() bool { return len(h.Block) == 0 } + +// totalCount returns the total number of IP allocations across all blocks for this handle. +func (h allocationHandle) totalCount() int { + total := 0 + for _, count := range h.Block { + total += count + } + return total +} + +// totalCountByVersion returns the total number of IP allocations for a specific IP version. +// version should be 4 for IPv4 or 6 for IPv6. +func (h allocationHandle) totalCountByVersion(version int) int { + total := 0 + for blockCIDR, count := range h.Block { + isIPv6 := strings.Contains(blockCIDR, ":") + if (version == 4 && !isIPv6) || (version == 6 && isIPv6) { + total += count + } + } + return total +} diff --git a/libcalico-go/lib/ipam/ipam_suite_test.go b/libcalico-go/lib/ipam/ipam_suite_test.go index 232b41100fe..e5a415046e1 100644 --- a/libcalico-go/lib/ipam/ipam_suite_test.go +++ b/libcalico-go/lib/ipam/ipam_suite_test.go @@ -17,16 +17,16 @@ package ipam import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestIpam(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/ipam_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "IPAM Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/ipam_suite.xml" + ginkgo.RunSpecs(t, "IPAM Suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/ipam/ipam_test.go b/libcalico-go/lib/ipam/ipam_test.go index 991cfed479c..5c918a60888 100644 --- a/libcalico-go/lib/ipam/ipam_test.go +++ b/libcalico-go/lib/ipam/ipam_test.go @@ -20,13 +20,12 @@ import ( "fmt" "net" "runtime" - "sort" "strings" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/onsi/gomega/gmeasure" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" @@ -36,99 +35,18 @@ import ( "k8s.io/client-go/kubernetes" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend" bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" + "github.com/projectcalico/calico/libcalico-go/lib/ipam/ipamtestutils" cnet "github.com/projectcalico/calico/libcalico-go/lib/net" - "github.com/projectcalico/calico/libcalico-go/lib/options" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) -// Implement an IP pools accessor for the IPAM client. This is a "mock" version -// of the accessor that we populate directly, rather than requiring the pool -// data to be persisted in etcd. -type ipPoolAccessor struct { - pools map[string]pool -} - -type pool struct { - cidr string - blockSize int - enabled bool - nodeSelector string - namespaceSelector string - allowedUses []v3.IPPoolAllowedUse - assignmentMode v3.AssignmentMode -} - -func (i *ipPoolAccessor) GetEnabledPools(ctx context.Context, ipVersion int) ([]v3.IPPool, error) { - sorted := make([]string, 0) - // Get a sorted list of enabled pool CIDR strings. - for p, e := range i.pools { - if e.enabled { - sorted = append(sorted, p) - } - } - return i.getPools(sorted, ipVersion, "GetEnabledPools"), nil -} - -func (i *ipPoolAccessor) getPools(sorted []string, ipVersion int, caller string) []v3.IPPool { - sort.Strings(sorted) - - // Convert to IPNets and sort out the correct IP versions. Sorting the results - // mimics more closely the behavior of etcd and allows the tests to be - // deterministic. - pools := make([]v3.IPPool, 0) - automatic := v3.Automatic - var poolsToPrint []string - for _, p := range sorted { - c := cnet.MustParseCIDR(p) - if (ipVersion == 0) || (c.Version() == ipVersion) { - pool := v3.IPPool{Spec: v3.IPPoolSpec{ - CIDR: p, - NodeSelector: i.pools[p].nodeSelector, - NamespaceSelector: i.pools[p].namespaceSelector, - AllowedUses: i.pools[p].allowedUses, - AssignmentMode: &automatic, - }} - if len(pool.Spec.AllowedUses) == 0 { - pool.Spec.AllowedUses = []v3.IPPoolAllowedUse{v3.IPPoolAllowedUseWorkload, v3.IPPoolAllowedUseTunnel} - } - if i.pools[p].blockSize == 0 { - if ipVersion == 4 { - pool.Spec.BlockSize = 26 - } else { - pool.Spec.BlockSize = 122 - } - } else { - pool.Spec.BlockSize = i.pools[p].blockSize - } - pools = append(pools, pool) - - // Compact string for printing so the log doesn't cost too much to print! - poolsToPrint = append(poolsToPrint, fmt.Sprintf("{%s(%v) %q %v}", - p, pool.Spec.BlockSize, pool.Spec.NodeSelector, i.pools[p].allowedUses)) - } - } - - log.Debugf("%v returns: %v", caller, poolsToPrint) - - return pools -} - -func (i *ipPoolAccessor) GetAllPools(ctx context.Context) ([]v3.IPPool, error) { - sorted := make([]string, 0) - // Get a sorted list of pool CIDR strings. - for p := range i.pools { - sorted = append(sorted, p) - } - return i.getPools(sorted, 0, "GetAllPools"), nil -} - -var ipPools = &ipPoolAccessor{pools: map[string]pool{}} +var ipPools = &ipamtestutils.IPPoolAccessor{Pools: map[string]ipamtestutils.Pool{}} // buildReleaseOptions is a helper function for the tests to easily // turn a slice of IPs into a slice of release options. @@ -149,14 +67,6 @@ type testArgsClaimAff struct { expError error } -type fakeReservations struct { - Reservations []v3.IPReservation -} - -func (f *fakeReservations) List(ctx context.Context, opts options.ListOptions) (*v3.IPReservationList, error) { - return &v3.IPReservationList{Items: f.Reservations}, nil -} - var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, func(config apiconfig.CalicoAPIConfig) { // Create a new backend client and an IPAM Client using the IP Pools Accessor. // Tests that need to ensure a clean datastore should invoke Clean() on the datastore at the start of the @@ -164,13 +74,13 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun var bc bapi.Client var ic Interface var kc *kubernetes.Clientset - var reservations *fakeReservations + var reservations *ipamtestutils.FakeReservations BeforeEach(func() { var err error config.Spec.K8sClientQPS = 500 bc, err = backend.NewClient(config) Expect(err).NotTo(HaveOccurred()) - reservations = &fakeReservations{} + reservations = &ipamtestutils.FakeReservations{} ic = NewIPAMClient(bc, ipPools, reservations) // If running in KDD mode, extract the k8s clientset. @@ -180,7 +90,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun }) Context("Measuring allocation performance", func() { - var pa *ipPoolAccessor + var pa *ipamtestutils.IPPoolAccessor var hostname string var err error var pool20, pool32, pool26 []cnet.IPNet @@ -200,27 +110,27 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun log.SetLevel(log.InfoLevel) // Build a new pool accessor for these tests. - pa = &ipPoolAccessor{pools: map[string]pool{}} + pa = &ipamtestutils.IPPoolAccessor{Pools: map[string]ipamtestutils.Pool{}} // Create many pools pool26 = nil - for i := 0; i < 100; i++ { + for i := range 100 { cidr := fmt.Sprintf("10.%d.0.0/16", i) - pa.pools[cidr] = pool{enabled: true, blockSize: 26} + pa.Pools[cidr] = ipamtestutils.Pool{Enabled: true, BlockSize: 26} pool26 = append(pool26, cnet.MustParseCIDR(cidr)) } pool32 = nil - for i := 0; i < 100; i++ { + for i := range 100 { cidr := fmt.Sprintf("11.%d.0.0/16", i) - pa.pools[cidr] = pool{enabled: true, blockSize: 32} + pa.Pools[cidr] = ipamtestutils.Pool{Enabled: true, BlockSize: 32} pool32 = append(pool32, cnet.MustParseCIDR(cidr)) } pool20 = nil - for i := 0; i < 50; i++ { + for i := range 50 { cidr := fmt.Sprintf("12.%d.0.0/16", i) - pa.pools[cidr] = pool{enabled: true, blockSize: 20} + pa.Pools[cidr] = ipamtestutils.Pool{Enabled: true, BlockSize: 20} pool20 = append(pool20, cnet.MustParseCIDR(cidr)) } @@ -236,13 +146,16 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun log.SetLevel(origLogLevel) }) - Measure("It should be able to allocate a single address quickly - blocksize 32", func(b Benchmarker) { - runtime := b.Time("runtime", func() { + It("should be able to allocate a single address quickly - blocksize 32", func() { + experiment := gmeasure.NewExperiment("allocate single address - blocksize 32") + AddReportEntry(experiment.Name, experiment) + + duration := experiment.MeasureDuration("runtime", func() { // Build a new backend client. We use a different client for each iteration of the test // so that the k8s QPS /burst limits don't carry across tests. This is more realistic. bc, err = backend.NewClient(config) Expect(err).NotTo(HaveOccurred()) - ic = NewIPAMClient(bc, pa, &fakeReservations{}) + ic = NewIPAMClient(bc, pa, &ipamtestutils.FakeReservations{}) v4ia, _, outErr := ic.AutoAssign(context.Background(), AutoAssignArgs{Num4: 1, IPv4Pools: pool32, Hostname: hostname, IntendedUse: v3.IPPoolAllowedUseWorkload}) Expect(outErr).NotTo(HaveOccurred()) @@ -250,16 +163,19 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun Expect(len(v4ia.IPs)).To(Equal(1)) }) - Expect(runtime.Seconds()).Should(BeNumerically("<", 5)) - }, 100) + Expect(duration.Seconds()).Should(BeNumerically("<", 5)) + }) + + It("should be able to allocate a single address quickly - blocksize 26", func() { + experiment := gmeasure.NewExperiment("allocate single address - blocksize 26") + AddReportEntry(experiment.Name, experiment) - Measure("It should be able to allocate a single address quickly - blocksize 26", func(b Benchmarker) { - runtime := b.Time("runtime", func() { + duration := experiment.MeasureDuration("runtime", func() { // Build a new backend client. We use a different client for each iteration of the test // so that the k8s QPS /burst limits don't carry across tests. This is more realistic. bc, err = backend.NewClient(config) Expect(err).NotTo(HaveOccurred()) - ic = NewIPAMClient(bc, pa, &fakeReservations{}) + ic = NewIPAMClient(bc, pa, &ipamtestutils.FakeReservations{}) v4ia, _, outErr := ic.AutoAssign(context.Background(), AutoAssignArgs{Num4: 1, IPv4Pools: pool26, Hostname: hostname, IntendedUse: v3.IPPoolAllowedUseWorkload}) Expect(outErr).NotTo(HaveOccurred()) @@ -267,16 +183,19 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun Expect(len(v4ia.IPs)).To(Equal(1)) }) - Expect(runtime.Seconds()).Should(BeNumerically("<", 5)) - }, 100) + Expect(duration.Seconds()).Should(BeNumerically("<", 5)) + }) + + It("should be able to allocate a single address quickly - blocksize 20", func() { + experiment := gmeasure.NewExperiment("allocate single address - blocksize 20") + AddReportEntry(experiment.Name, experiment) - Measure("It should be able to allocate a single address quickly - blocksize 20", func(b Benchmarker) { - runtime := b.Time("runtime", func() { + duration := experiment.MeasureDuration("runtime", func() { // Build a new backend client. We use a different client for each iteration of the test // so that the k8s QPS /burst limits don't carry across tests. This is more realistic. bc, err = backend.NewClient(config) Expect(err).NotTo(HaveOccurred()) - ic = NewIPAMClient(bc, pa, &fakeReservations{}) + ic = NewIPAMClient(bc, pa, &ipamtestutils.FakeReservations{}) v4ia, _, outErr := ic.AutoAssign(context.Background(), AutoAssignArgs{Num4: 1, IPv4Pools: pool20, Hostname: hostname, IntendedUse: v3.IPPoolAllowedUseWorkload}) Expect(outErr).NotTo(HaveOccurred()) @@ -284,16 +203,19 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun Expect(len(v4ia.IPs)).To(Equal(1)) }) - Expect(runtime.Seconds()).Should(BeNumerically("<", 5)) - }, 100) + Expect(duration.Seconds()).Should(BeNumerically("<", 5)) + }) - Measure("It should be able to allocate a lot of addresses quickly", func(b Benchmarker) { - runtime := b.Time("runtime", func() { + It("should be able to allocate a lot of addresses quickly", func() { + experiment := gmeasure.NewExperiment("allocate many addresses") + AddReportEntry(experiment.Name, experiment) + + duration := experiment.MeasureDuration("runtime", func() { // Build a new backend client. We use a different client for each iteration of the test // so that the k8s QPS /burst limits don't carry across tests. This is more realistic. bc, err = backend.NewClient(config) Expect(err).NotTo(HaveOccurred()) - ic = NewIPAMClient(bc, pa, &fakeReservations{}) + ic = NewIPAMClient(bc, pa, &ipamtestutils.FakeReservations{}) v4ia, _, outErr := ic.AutoAssign(context.Background(), AutoAssignArgs{Num4: 64, IPv4Pools: pool20, Hostname: hostname, IntendedUse: v3.IPPoolAllowedUseWorkload}) Expect(outErr).NotTo(HaveOccurred()) @@ -301,14 +223,14 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun Expect(len(v4ia.IPs)).To(Equal(64)) }) - Expect(runtime.Seconds()).Should(BeNumerically("<", 5)) - }, 20) + Expect(duration.Seconds()).Should(BeNumerically("<", 5)) + }) Context("with 1000 nodes", func() { BeforeEach(func() { var eg errgroup.Group eg.SetLimit(runtime.NumCPU()) - for i := 0; i < 1000; i++ { + for i := range 1000 { eg.Go(func() error { defer GinkgoRecover() applyNode(bc, kc, fmt.Sprintf("%s-%d", hostname, i), map[string]string{"foo": "bar"}) @@ -322,7 +244,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun // Clean up the nodes. var eg errgroup.Group eg.SetLimit(runtime.NumCPU()) - for i := 0; i < 1000; i++ { + for i := range 1000 { eg.Go(func() error { defer GinkgoRecover() deleteNode(bc, kc, fmt.Sprintf("%s-%d", hostname, i)) @@ -336,17 +258,17 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun allocOneIPPerNode := func() { var eg errgroup.Group eg.SetLimit(runtime.NumCPU()) - for i := 0; i < 1000; i++ { + for i := range 1000 { eg.Go(func() error { defer GinkgoRecover() - for j := 0; j < 1; j++ { + for range 1 { // Build a new backend client. We use a different client for each iteration of the test // so that the k8s QPS /burst limits don't carry across "hosts". This is more realistic. bc, err = backend.NewClient(config) if err != nil { return err } - ic = NewIPAMClient(bc, pa, &fakeReservations{}) + ic = NewIPAMClient(bc, pa, &ipamtestutils.FakeReservations{}) v4ia, _, err := ic.AutoAssign( context.Background(), @@ -369,27 +291,36 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun Expect(eg.Wait()).To(Succeed()) } - Measure("time to allocate first IP per node across 1000 nodes", func(b Benchmarker) { - b.Time("runtime", func() { + It("time to allocate first IP per node across 1000 nodes", func() { + experiment := gmeasure.NewExperiment("allocate first IP per node across 1000 nodes") + AddReportEntry(experiment.Name, experiment) + + experiment.MeasureDuration("runtime", func() { allocOneIPPerNode() }) - }, 1) + }) + + It("time to allocate second IP per node across 1000 nodes", func() { + experiment := gmeasure.NewExperiment("allocate second IP per node across 1000 nodes") + AddReportEntry(experiment.Name, experiment) - Measure("time to allocate second IP per node across 1000 nodes", func(b Benchmarker) { allocOneIPPerNode() // Pre-create one IPAM block per node. - b.Time("runtime", func() { + experiment.MeasureDuration("runtime", func() { allocOneIPPerNode() }) - }, 1) + }) }) - Measure("It should be able to allocate and release addresses quickly", func(b Benchmarker) { - runtime := b.Time("runtime", func() { + It("should be able to allocate and release addresses quickly", func() { + experiment := gmeasure.NewExperiment("allocate and release addresses") + AddReportEntry(experiment.Name, experiment) + + duration := experiment.MeasureDuration("runtime", func() { // Build a new backend client. We use a different client for each iteration of the test // so that the k8s QPS /burst limits don't carry across tests. This is more realistic. bc, err = backend.NewClient(config) Expect(err).NotTo(HaveOccurred()) - ic = NewIPAMClient(bc, pa, &fakeReservations{}) + ic = NewIPAMClient(bc, pa, &ipamtestutils.FakeReservations{}) v4ia, _, err := ic.AutoAssign(context.Background(), AutoAssignArgs{Num4: 1, Hostname: hostname, IntendedUse: v3.IPPoolAllowedUseWorkload}) v4IP := make([]ReleaseOptions, 0, 0) @@ -405,8 +336,8 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun Expect(len(rel)).To(Equal(1)) }) - Expect(runtime.Seconds()).Should(BeNumerically("<", 5)) - }, 20) + Expect(duration.Seconds()).Should(BeNumerically("<", 5)) + }) }) Describe("ReleaseIPs test", func() { @@ -427,7 +358,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun // Run this test several times so that we can assert the logic works with a multitude of different sequence numbers, and // that the sequence number is actually incremented properly. var expectedSeqNum uint64 - for i := 0; i < 10; i++ { + for range 10 { // Allocate an IP address. v4, _, err := ic.AutoAssign(context.Background(), AutoAssignArgs{Num4: 1, Num6: 0, Hostname: hostname, HandleID: &handle, IntendedUse: v3.IPPoolAllowedUseTunnel}) Expect(err).NotTo(HaveOccurred()) @@ -490,6 +421,71 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun } }) + It("should wrap IP allocations correctly", func() { + // Create a small pool and assign / release IPs until we wrap around to the beginning. + applyPoolWithBlockSize("10.0.0.0/24", true, "all()", 30) + applyNode(bc, kc, "wrap-node", map[string]string{"foo": "bar"}) + allocatedIPs := map[string]bool{} + ipsInOrder := []string{} + + // A /30 block has 4 addresses, so after allocating and releasing 4 addresses, the 5th allocation + // should wrap back to the first address. + totalIPs := 4 + for range totalIPs { + args := AutoAssignArgs{Num4: 1, Hostname: "wrap-node", IntendedUse: v3.IPPoolAllowedUseWorkload} + v4, _, err := ic.AutoAssign(context.Background(), args) + Expect(err).NotTo(HaveOccurred()) + Expect(len(v4.IPs)).To(Equal(1)) + + ipStr := v4.IPs[0].IP.String() + _, allocated := allocatedIPs[ipStr] + Expect(allocated).To(BeFalse(), "IP %s was already allocated!", ipStr) + allocatedIPs[ipStr] = true + ipsInOrder = append(ipsInOrder, ipStr) + + // Release the IP again. + u, rel, err := ic.ReleaseIPs(context.TODO(), ReleaseOptions{Address: ipStr}) + Expect(err).NotTo(HaveOccurred()) + Expect(len(u)).To(Equal(0)) + Expect(len(rel)).To(Equal(1)) + } + + // Now, allocate one more IP - should wrap to the first one. + args := AutoAssignArgs{Num4: 1, Hostname: "wrap-node", IntendedUse: v3.IPPoolAllowedUseWorkload} + v4, _, err := ic.AutoAssign(context.Background(), args) + Expect(err).NotTo(HaveOccurred()) + Expect(len(v4.IPs)).To(Equal(1)) + ipStr := v4.IPs[0].IP.String() + Expect(ipStr).To(Equal(ipsInOrder[0]), "Expected wrapped allocation to return first IP again") + }) + + It("should not assign from deleted IP pools", func() { + // Create an IP pool, allocate an IP, delete the pool and create a new IP pool. + // Assert new allocations do not come from the deleted pool. + applyPool("10.0.0.0/24", true, "all()") + applyNode(bc, kc, "deletion-node", map[string]string{"foo": "bar"}) + v4, _, err := ic.AutoAssign(context.Background(), AutoAssignArgs{Num4: 1, Hostname: "deletion-node", IntendedUse: v3.IPPoolAllowedUseWorkload}) + Expect(err).NotTo(HaveOccurred()) + Expect(len(v4.IPs)).To(Equal(1)) + allocatedIP := v4.IPs[0].IP.String() + + // Assert the allocated IP is from the first pool. + Expect(strings.HasPrefix(allocatedIP, "10.0.0.")).To(BeTrue(), "Expected allocation to come from first pool") + + // Delete the pool. + deleteAllPools() + + // Create a new pool. + applyPool("11.0.0.0/24", true, "all()") + + // Allocate a new IP - should not come from the deleted pool. + v4, _, err = ic.AutoAssign(context.Background(), AutoAssignArgs{Num4: 1, Hostname: "deletion-node", IntendedUse: v3.IPPoolAllowedUseWorkload}) + Expect(err).NotTo(HaveOccurred()) + Expect(len(v4.IPs)).To(Equal(1)) + newAllocatedIP := v4.IPs[0].IP.String() + Expect(strings.HasPrefix(newAllocatedIP, "11.0.0.")).To(BeTrue(), "Expected new allocation to come from new pool") + }) + It("should handle when an IP is released causing block deletion, then reallocated with the same handle", func() { // This test simulates a scenario where client A queries the allocation and determines that the // IP should be released. In the meantime, client B releases and re-allocates the address with the same IP and handle, thus @@ -572,15 +568,16 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun It("should release a multitude of IPs in different blocks", func() { // Create an IP pool with a blocksize such that we'll get multiple blocks per-node. - applyPoolWithBlockSize("10.0.0.0/24", true, "all()", 30) + applyPoolWithBlockSize("10.0.0.0/24", true, "name in {'node1', 'node2'}", 30) + applyPoolWithBlockSize("11.0.0.0/24", true, "name in {'node3', 'node4'}", 30) applyPool("fe80:ba:ad:beef::00/120", true, "all()") // Assign a number of IPs in different blocks on different nodes. ips := []cnet.IP{} for _, node := range []string{"node1", "node2", "node3", "node4"} { // 4 nodes - applyNode(bc, kc, node, map[string]string{"foo": "bar"}) - for i := 0; i < 6; i++ { + applyNode(bc, kc, node, map[string]string{"foo": "bar", "name": node}) + for range 6 { // 6 addresses of each family per-node. v4ia, v6ia, err := ic.AutoAssign(context.Background(), AutoAssignArgs{Num4: 1, Num6: 1, Hostname: node, IntendedUse: v3.IPPoolAllowedUseWorkload}) Expect(err).NotTo(HaveOccurred()) @@ -617,6 +614,9 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun // for a total of 25 per-node, 100 in all. Expect(len(ips)).To(Equal(100)) + // Disable a pool to ensure we test releasing from a disabled pool. + applyPoolWithBlockSize("11.0.0.0/24", false, "name in {'node3', 'node4'}", 30) + // Release them all. This should complete within a minute easily. ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() @@ -644,10 +644,9 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun sentinelIP := net.ParseIP("10.0.0.1") It("Should return ResourceNotExist on no valid pool", func() { - attrs, handle, err := ic.GetAssignmentAttributes(context.Background(), cnet.IP{IP: sentinelIP}) + allocAttr, err := ic.GetAssignmentAttributes(context.Background(), cnet.IP{IP: sentinelIP}) Expect(err).To(BeAssignableToTypeOf(cerrors.ErrorResourceDoesNotExist{})) - Expect(attrs).To(BeEmpty()) - Expect(handle).To(BeNil()) + Expect(allocAttr).To(BeNil()) }) Context("With valid pool", func() { @@ -668,10 +667,9 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun }) It("Should return ResourceNotExist error on no block", func() { - attrs, handle, err := ic.GetAssignmentAttributes(context.Background(), cnet.IP{IP: sentinelIP}) + allocAttr, err := ic.GetAssignmentAttributes(context.Background(), cnet.IP{IP: sentinelIP}) Expect(err).To(BeAssignableToTypeOf(cerrors.ErrorResourceDoesNotExist{})) - Expect(attrs).To(BeEmpty()) - Expect(handle).To(BeNil()) + Expect(allocAttr).To(BeNil()) }) It("Should return correct attributes on allocated ip", func() { @@ -689,11 +687,12 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun err := ic.AssignIP(context.Background(), args) Expect(err).NotTo(HaveOccurred()) - attrs, returnedHandle, err := ic.GetAssignmentAttributes(context.Background(), cnet.IP{IP: sentinelIP}) + allocAttr, err := ic.GetAssignmentAttributes(context.Background(), cnet.IP{IP: sentinelIP}) Expect(err).NotTo(HaveOccurred()) - Expect(attrs).To(Equal(ipAttr)) - Expect(returnedHandle).NotTo(BeNil()) - Expect(*returnedHandle).To(Equal(handle)) + Expect(allocAttr).NotTo(BeNil()) + Expect(allocAttr.ActiveOwnerAttrs).To(Equal(ipAttr)) + Expect(allocAttr.HandleID).NotTo(BeNil()) + Expect(*allocAttr.HandleID).To(Equal(handle)) }) It("Should return ResourceNotExist on unallocated ip", func() { @@ -711,10 +710,9 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun Expect(err).NotTo(HaveOccurred()) // Block exists but sentinel ip is not allocated. - attrs, handle, err := ic.GetAssignmentAttributes(context.Background(), cnet.IP{IP: sentinelIP}) + allocAttr, err := ic.GetAssignmentAttributes(context.Background(), cnet.IP{IP: sentinelIP}) Expect(err).To(BeAssignableToTypeOf(cerrors.ErrorResourceDoesNotExist{})) - Expect(attrs).To(BeEmpty()) - Expect(handle).To(BeNil()) + Expect(allocAttr).To(BeNil()) }) }) }) @@ -895,11 +893,46 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun Expect(len(blocks.KVPairs)).To(Equal(0)) }) + It("should release older blocks created without an explicit affinity type", func() { + // Use the backend client to create a block with no affinity type. This simulates a block created by an older version of Calico, + // which predate the introduction of affinity types. + cidr := "10.0.0.0/24" + b := newBlock(cnet.MustParseCIDR(cidr), nil) + blockKVP := model.KVPair{ + Key: model.BlockKey{CIDR: cnet.MustParseCIDR(cidr)}, + Value: b.AllocationBlock, + } + affKVP := model.KVPair{ + Key: model.BlockAffinityKey{ + CIDR: cnet.MustParseCIDR(cidr), + Host: hostname, + AffinityType: "", + }, + Value: &model.BlockAffinity{State: "confirmed"}, + } + _, err := bc.Create(context.Background(), &blockKVP) + Expect(err).NotTo(HaveOccurred()) + _, err = bc.Create(context.Background(), &affKVP) + Expect(err).NotTo(HaveOccurred()) + + // Release the block affinity. It should succeed even though the affinity type is blank. + err = ic.ReleasePoolAffinities(context.Background(), cnet.MustParseCIDR("10.0.0.0/16")) + Expect(err).NotTo(HaveOccurred()) + + // The block and affinity should both be gone. + blocks, err := bc.List(context.Background(), model.BlockListOptions{}, "") + Expect(err).NotTo(HaveOccurred()) + Expect(len(blocks.KVPairs)).To(Equal(0)) + affs, err := bc.List(context.Background(), model.BlockAffinityListOptions{}, "") + Expect(err).NotTo(HaveOccurred()) + Expect(len(affs.KVPairs)).To(Equal(0)) + }) + It("should release all non-empty blocks if there are multiple", func() { // Allocate several blocks to the node. The pool is a /30, so 4 addresses // per each block. handle := "test-handle" - for i := 0; i < 12; i++ { + for range 12 { v4ia, _, err := ic.AutoAssign(context.Background(), AutoAssignArgs{Num4: 1, Hostname: hostname, HandleID: &handle, IntendedUse: v3.IPPoolAllowedUseWorkload}) Expect(err).NotTo(HaveOccurred()) Expect(v4ia).ToNot(BeNil()) @@ -940,7 +973,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun // Allocate several blocks to the node. The pool is a /30, so 4 addresses // per each block. handle := "test-handle" - for i := 0; i < 12; i++ { + for range 12 { v4ia, _, err := ic.AutoAssign(context.Background(), AutoAssignArgs{Num4: 1, Hostname: hostname, HandleID: &handle, IntendedUse: v3.IPPoolAllowedUseWorkload}) Expect(err).NotTo(HaveOccurred()) Expect(v4ia).ToNot(BeNil()) @@ -1022,10 +1055,11 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun It("Should be able to re-assign the sentinel IP", func() { assignIPutil(ic, sentinelIP, host) - attrs, handle, attrErr := ic.GetAssignmentAttributes(context.Background(), cnet.IP{IP: sentinelIP}) + allocAttr, attrErr := ic.GetAssignmentAttributes(context.Background(), cnet.IP{IP: sentinelIP}) Expect(attrErr).NotTo(HaveOccurred()) - Expect(attrs).To(BeEmpty()) - Expect(handle).To(BeNil()) + Expect(allocAttr).NotTo(BeNil()) + Expect(allocAttr.ActiveOwnerAttrs).To(BeEmpty()) + Expect(allocAttr.HandleID).To(BeNil()) }) It("Should fail to assign any more addresses", func() { @@ -1177,6 +1211,1022 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun Expect(err).To(HaveOccurred()) }) }) + + Context("IP assignment with MaxAllocToHandlePerIPVersion", func() { + var hostname string + var handle string + + BeforeEach(func() { + hostname = "test-host-maxalloc" + handle = "vmi-handle-maxalloc" + + Expect(bc.Clean()).To(Succeed()) + deleteAllPools() + applyPool("10.0.0.0/24", true, "") + applyPool("fd80:24e2:f998:72d6::/120", true, "") + applyNode(bc, kc, hostname, nil) + }) + + It("should reuse existing IPs when MaxAllocToHandlePerIPVersion is reached on a second AutoAssign with the same handle", func() { + ctx := context.Background() + + var firstV4IP, firstV6IP cnet.IPNet + + By("first AutoAssign: allocating 1 IPv4 + 1 IPv6 with MaxAllocToHandlePerIPVersion=1", func() { + args := AutoAssignArgs{ + Num4: 1, + Num6: 1, + HandleID: &handle, + Hostname: hostname, + IntendedUse: v3.IPPoolAllowedUseWorkload, + MaxAllocToHandlePerIPVersion: 1, + } + v4ia, v6ia, err := ic.AutoAssign(ctx, args) + Expect(err).NotTo(HaveOccurred()) + Expect(v4ia).ToNot(BeNil()) + Expect(v4ia.IPs).To(HaveLen(1)) + Expect(v6ia).ToNot(BeNil()) + Expect(v6ia.IPs).To(HaveLen(1)) + + firstV4IP = v4ia.IPs[0] + firstV6IP = v6ia.IPs[0] + }) + + By("verifying handle has exactly 2 IPs (1 IPv4 + 1 IPv6)", func() { + ips, err := ic.IPsByHandle(ctx, handle) + Expect(err).NotTo(HaveOccurred()) + Expect(ips).To(HaveLen(2)) + }) + + By("second AutoAssign with the same handle and MaxAllocToHandlePerIPVersion=1: should reuse existing IPs", func() { + args := AutoAssignArgs{ + Num4: 1, + Num6: 1, + HandleID: &handle, + Hostname: hostname, + IntendedUse: v3.IPPoolAllowedUseWorkload, + MaxAllocToHandlePerIPVersion: 1, + } + v4ia, v6ia, err := ic.AutoAssign(ctx, args) + Expect(err).NotTo(HaveOccurred()) + Expect(v4ia).ToNot(BeNil()) + Expect(v4ia.IPs).To(HaveLen(1)) + Expect(v6ia).ToNot(BeNil()) + Expect(v6ia.IPs).To(HaveLen(1)) + + // The reused IPs should be identical to the first allocation + Expect(v4ia.IPs[0].IP.Equal(firstV4IP.IP)).To(BeTrue(), + fmt.Sprintf("Expected reused IPv4 %s to match first allocation %s", v4ia.IPs[0].IP, firstV4IP.IP)) + Expect(v6ia.IPs[0].IP.Equal(firstV6IP.IP)).To(BeTrue(), + fmt.Sprintf("Expected reused IPv6 %s to match first allocation %s", v6ia.IPs[0].IP, firstV6IP.IP)) + }) + + By("verifying handle still has exactly 2 IPs after reuse (no duplicates)", func() { + ips, err := ic.IPsByHandle(ctx, handle) + Expect(err).NotTo(HaveOccurred()) + Expect(ips).To(HaveLen(2)) + }) + + By("releasing the IPs by handle", func() { + err := ic.ReleaseByHandle(ctx, handle) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying handle is empty after release", func() { + _, err := ic.IPsByHandle(ctx, handle) + Expect(err).To(HaveOccurred()) + }) + }) + + It("should reuse existing IP when MaxAllocToHandlePerIPVersion is reached on a second AssignIP with the same handle", func() { + ctx := context.Background() + + requestedIP := cnet.MustParseIP("10.0.0.1") + + By("first AssignIP: assigning a specific IP with MaxAllocToHandlePerIPVersion=1", func() { + err := ic.AssignIP(ctx, AssignIPArgs{ + IP: requestedIP, + HandleID: &handle, + Hostname: hostname, + Attrs: map[string]string{"pod": "source-pod"}, + MaxAllocToHandlePerIPVersion: 1, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying handle has exactly 1 IP", func() { + ips, err := ic.IPsByHandle(ctx, handle) + Expect(err).NotTo(HaveOccurred()) + Expect(ips).To(HaveLen(1)) + Expect(ips[0].IP.Equal(requestedIP.IP)).To(BeTrue()) + }) + + By("second AssignIP with the same handle, same IP, and MaxAllocToHandlePerIPVersion=1: should succeed (idempotent)", func() { + err := ic.AssignIP(ctx, AssignIPArgs{ + IP: requestedIP, + HandleID: &handle, + Hostname: hostname, + Attrs: map[string]string{"pod": "target-pod"}, + MaxAllocToHandlePerIPVersion: 1, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying handle still has exactly 1 IP after idempotent assign", func() { + ips, err := ic.IPsByHandle(ctx, handle) + Expect(err).NotTo(HaveOccurred()) + Expect(ips).To(HaveLen(1)) + Expect(ips[0].IP.Equal(requestedIP.IP)).To(BeTrue()) + }) + + By("second AssignIP with the same handle but a DIFFERENT IP and MaxAllocToHandlePerIPVersion=1: should fail", func() { + differentIP := cnet.MustParseIP("10.0.0.2") + err := ic.AssignIP(ctx, AssignIPArgs{ + IP: differentIP, + HandleID: &handle, + Hostname: hostname, + Attrs: map[string]string{"pod": "target-pod"}, + MaxAllocToHandlePerIPVersion: 1, + }) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("already has IP(s) allocated")) + }) + + By("releasing the IP by handle", func() { + err := ic.ReleaseByHandle(ctx, handle) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + It("should allow two AutoAssigns and reuse on third when MaxAllocToHandlePerIPVersion is 2", func() { + ctx := context.Background() + + var firstV4IP, secondV4IP cnet.IPNet + + By("first AutoAssign: allocating 1 IPv4 with MaxAllocToHandlePerIPVersion=2", func() { + args := AutoAssignArgs{ + Num4: 1, + HandleID: &handle, + Hostname: hostname, + IntendedUse: v3.IPPoolAllowedUseWorkload, + MaxAllocToHandlePerIPVersion: 2, + } + v4ia, _, err := ic.AutoAssign(ctx, args) + Expect(err).NotTo(HaveOccurred()) + Expect(v4ia).ToNot(BeNil()) + Expect(v4ia.IPs).To(HaveLen(1)) + + firstV4IP = v4ia.IPs[0] + }) + + By("verifying handle has 1 IP", func() { + ips, err := ic.IPsByHandle(ctx, handle) + Expect(err).NotTo(HaveOccurred()) + Expect(ips).To(HaveLen(1)) + }) + + By("second AutoAssign: allocating another IPv4 with MaxAllocToHandlePerIPVersion=2 - should succeed", func() { + args := AutoAssignArgs{ + Num4: 1, + HandleID: &handle, + Hostname: hostname, + IntendedUse: v3.IPPoolAllowedUseWorkload, + MaxAllocToHandlePerIPVersion: 2, + } + v4ia, _, err := ic.AutoAssign(ctx, args) + Expect(err).NotTo(HaveOccurred()) + Expect(v4ia).ToNot(BeNil()) + Expect(v4ia.IPs).To(HaveLen(1)) + + secondV4IP = v4ia.IPs[0] + // Second IP should be different from the first + Expect(secondV4IP.IP.Equal(firstV4IP.IP)).To(BeFalse(), + fmt.Sprintf("Second IPv4 %s should differ from first %s", secondV4IP.IP, firstV4IP.IP)) + }) + + By("verifying handle has 2 IPs", func() { + ips, err := ic.IPsByHandle(ctx, handle) + Expect(err).NotTo(HaveOccurred()) + Expect(ips).To(HaveLen(2)) + }) + + By("third AutoAssign with MaxAllocToHandlePerIPVersion=2: should hit limit and reuse existing IPs", func() { + args := AutoAssignArgs{ + Num4: 1, + HandleID: &handle, + Hostname: hostname, + IntendedUse: v3.IPPoolAllowedUseWorkload, + MaxAllocToHandlePerIPVersion: 2, + } + v4ia, _, err := ic.AutoAssign(ctx, args) + Expect(err).NotTo(HaveOccurred()) + Expect(v4ia).ToNot(BeNil()) + Expect(v4ia.IPs).To(HaveLen(1), "Should return one existing IP on reuse") + + // Returned IP should be one of the previously allocated IPs + reusedIP := v4ia.IPs[0].IP + Expect(reusedIP.Equal(firstV4IP.IP) || reusedIP.Equal(secondV4IP.IP)).To(BeTrue(), + fmt.Sprintf("Reused IP %s should be one of the previously allocated IPs", reusedIP)) + }) + + By("verifying handle still has exactly 2 IPs (no duplicates)", func() { + ips, err := ic.IPsByHandle(ctx, handle) + Expect(err).NotTo(HaveOccurred()) + Expect(ips).To(HaveLen(2)) + }) + + By("releasing the IPs by handle", func() { + err := ic.ReleaseByHandle(ctx, handle) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying handle is empty after release", func() { + _, err := ic.IPsByHandle(ctx, handle) + Expect(err).To(HaveOccurred()) + }) + }) + }) + + Context("SetOwnerAttributes", func() { + var hostname string + var handle string + var allocatedIP cnet.IP + + BeforeEach(func() { + hostname = "test-host-owner-attrs" + handle = "vmi-handle-owner-attrs" + + Expect(bc.Clean()).To(Succeed()) + deleteAllPools() + applyPool("10.0.0.0/24", true, "") + applyNode(bc, kc, hostname, nil) + + // Allocate an IP so we have something to set owner attributes on. + ctx := context.Background() + args := AutoAssignArgs{ + Num4: 1, + HandleID: &handle, + Hostname: hostname, + IntendedUse: v3.IPPoolAllowedUseWorkload, + } + v4ia, _, err := ic.AutoAssign(ctx, args) + Expect(err).NotTo(HaveOccurred()) + Expect(v4ia).ToNot(BeNil()) + Expect(v4ia.IPs).To(HaveLen(1)) + allocatedIP = cnet.IP{IP: v4ia.IPs[0].IP} + }) + + It("should set ActiveOwnerAttrs on a freshly allocated IP", func() { + ctx := context.Background() + + activeAttrs := map[string]string{ + AttributePod: "pod-a", + AttributeNamespace: "ns-a", + } + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ActiveOwnerAttrs: activeAttrs, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + // Verify via GetAssignmentAttributes + allocAttr, err := ic.GetAssignmentAttributes(ctx, allocatedIP) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.ActiveOwnerAttrs).To(Equal(activeAttrs)) + Expect(allocAttr.AlternateOwnerAttrs).To(BeNil()) + }) + + It("should set AlternateOwnerAttrs on a freshly allocated IP", func() { + ctx := context.Background() + + altAttrs := map[string]string{ + AttributePod: "pod-b", + AttributeNamespace: "ns-b", + } + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + AlternateOwnerAttrs: altAttrs, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + allocAttr, err := ic.GetAssignmentAttributes(ctx, allocatedIP) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.ActiveOwnerAttrs).To(BeNil()) + Expect(allocAttr.AlternateOwnerAttrs).To(Equal(altAttrs)) + }) + + It("should set both ActiveOwnerAttrs and AlternateOwnerAttrs atomically", func() { + ctx := context.Background() + + activeAttrs := map[string]string{ + AttributePod: "pod-source", + AttributeNamespace: "ns-source", + } + altAttrs := map[string]string{ + AttributePod: "pod-target", + AttributeNamespace: "ns-target", + } + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ActiveOwnerAttrs: activeAttrs, + AlternateOwnerAttrs: altAttrs, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + allocAttr, err := ic.GetAssignmentAttributes(ctx, allocatedIP) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.ActiveOwnerAttrs).To(Equal(activeAttrs)) + Expect(allocAttr.AlternateOwnerAttrs).To(Equal(altAttrs)) + }) + + It("should clear ActiveOwnerAttrs with ClearActiveOwner", func() { + ctx := context.Background() + + // First set active owner + activeAttrs := map[string]string{ + AttributePod: "pod-a", + AttributeNamespace: "ns-a", + } + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ActiveOwnerAttrs: activeAttrs, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + // Verify it was set + allocAttr, err := ic.GetAssignmentAttributes(ctx, allocatedIP) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.ActiveOwnerAttrs).To(Equal(activeAttrs)) + + // Now clear it + err = ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ClearActiveOwner: true, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + allocAttr, err = ic.GetAssignmentAttributes(ctx, allocatedIP) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.ActiveOwnerAttrs).To(BeNil()) + }) + + It("should clear AlternateOwnerAttrs with ClearAlternateOwner", func() { + ctx := context.Background() + + // First set alternate owner + altAttrs := map[string]string{ + AttributePod: "pod-b", + AttributeNamespace: "ns-b", + } + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + AlternateOwnerAttrs: altAttrs, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + // Verify it was set + allocAttr, err := ic.GetAssignmentAttributes(ctx, allocatedIP) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.AlternateOwnerAttrs).To(Equal(altAttrs)) + + // Now clear it + err = ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ClearAlternateOwner: true, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + allocAttr, err = ic.GetAssignmentAttributes(ctx, allocatedIP) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.AlternateOwnerAttrs).To(BeNil()) + }) + + It("should clear both owners simultaneously", func() { + ctx := context.Background() + + // Set both owners + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ActiveOwnerAttrs: map[string]string{ + AttributePod: "pod-a", + AttributeNamespace: "ns-a", + }, + AlternateOwnerAttrs: map[string]string{ + AttributePod: "pod-b", + AttributeNamespace: "ns-b", + }, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + // Clear both + err = ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ClearActiveOwner: true, + ClearAlternateOwner: true, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + allocAttr, err := ic.GetAssignmentAttributes(ctx, allocatedIP) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.ActiveOwnerAttrs).To(BeNil()) + Expect(allocAttr.AlternateOwnerAttrs).To(BeNil()) + }) + + It("should overwrite existing ActiveOwnerAttrs with new values", func() { + ctx := context.Background() + + // Set initial active owner + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ActiveOwnerAttrs: map[string]string{ + AttributePod: "pod-old", + AttributeNamespace: "ns-old", + }, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + // Overwrite with new owner + newAttrs := map[string]string{ + AttributePod: "pod-new", + AttributeNamespace: "ns-new", + } + err = ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ActiveOwnerAttrs: newAttrs, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + allocAttr, err := ic.GetAssignmentAttributes(ctx, allocatedIP) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.ActiveOwnerAttrs).To(Equal(newAttrs)) + }) + + It("should not modify AlternateOwnerAttrs when only ActiveOwnerAttrs is updated", func() { + ctx := context.Background() + + // Set both owners + altAttrs := map[string]string{ + AttributePod: "pod-alt", + AttributeNamespace: "ns-alt", + } + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ActiveOwnerAttrs: map[string]string{ + AttributePod: "pod-active", + AttributeNamespace: "ns-active", + }, + AlternateOwnerAttrs: altAttrs, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + // Update only active owner + newActiveAttrs := map[string]string{ + AttributePod: "pod-active-new", + AttributeNamespace: "ns-active-new", + } + err = ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ActiveOwnerAttrs: newActiveAttrs, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + // Alternate should remain unchanged + allocAttr, err := ic.GetAssignmentAttributes(ctx, allocatedIP) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.ActiveOwnerAttrs).To(Equal(newActiveAttrs)) + Expect(allocAttr.AlternateOwnerAttrs).To(Equal(altAttrs)) + }) + + It("should not modify ActiveOwnerAttrs when only AlternateOwnerAttrs is updated", func() { + ctx := context.Background() + + // Set both owners + activeAttrs := map[string]string{ + AttributePod: "pod-active", + AttributeNamespace: "ns-active", + } + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ActiveOwnerAttrs: activeAttrs, + AlternateOwnerAttrs: map[string]string{ + AttributePod: "pod-alt", + AttributeNamespace: "ns-alt", + }, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + // Update only alternate owner + newAltAttrs := map[string]string{ + AttributePod: "pod-alt-new", + AttributeNamespace: "ns-alt-new", + } + err = ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + AlternateOwnerAttrs: newAltAttrs, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + // Active should remain unchanged + allocAttr, err := ic.GetAssignmentAttributes(ctx, allocatedIP) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.ActiveOwnerAttrs).To(Equal(activeAttrs)) + Expect(allocAttr.AlternateOwnerAttrs).To(Equal(newAltAttrs)) + }) + + // --- Precondition tests --- + + It("should succeed when ExpectedActiveOwner matches the current active owner", func() { + ctx := context.Background() + + activeAttrs := map[string]string{ + AttributePod: "pod-source", + AttributeNamespace: "ns-source", + } + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ActiveOwnerAttrs: activeAttrs, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + // Now clear active with precondition that the current owner matches + err = ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ClearActiveOwner: true, + }, &OwnerAttributePreconditions{ + ExpectedActiveOwner: &AttributeOwner{ + Namespace: "ns-source", + Name: "pod-source", + }, + }) + Expect(err).NotTo(HaveOccurred()) + + allocAttr, err := ic.GetAssignmentAttributes(ctx, allocatedIP) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.ActiveOwnerAttrs).To(BeNil()) + }) + + It("should fail when ExpectedActiveOwner does not match the current active owner", func() { + ctx := context.Background() + + activeAttrs := map[string]string{ + AttributePod: "pod-source", + AttributeNamespace: "ns-source", + } + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ActiveOwnerAttrs: activeAttrs, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + // Try to clear with wrong precondition + err = ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ClearActiveOwner: true, + }, &OwnerAttributePreconditions{ + ExpectedActiveOwner: &AttributeOwner{ + Namespace: "ns-wrong", + Name: "pod-wrong", + }, + }) + Expect(err).To(HaveOccurred()) + Expect(err).To(BeAssignableToTypeOf(cerrors.ErrorResourceUpdateConflict{})) + + // Active should remain unchanged + allocAttr, err := ic.GetAssignmentAttributes(ctx, allocatedIP) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.ActiveOwnerAttrs).To(Equal(activeAttrs)) + }) + + It("should succeed when ExpectedAlternateOwner matches the current alternate owner", func() { + ctx := context.Background() + + altAttrs := map[string]string{ + AttributePod: "pod-target", + AttributeNamespace: "ns-target", + } + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + AlternateOwnerAttrs: altAttrs, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + // Clear alternate with correct precondition + err = ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ClearAlternateOwner: true, + }, &OwnerAttributePreconditions{ + ExpectedAlternateOwner: &AttributeOwner{ + Namespace: "ns-target", + Name: "pod-target", + }, + }) + Expect(err).NotTo(HaveOccurred()) + + allocAttr, err := ic.GetAssignmentAttributes(ctx, allocatedIP) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.AlternateOwnerAttrs).To(BeNil()) + }) + + It("should fail when ExpectedAlternateOwner does not match the current alternate owner", func() { + ctx := context.Background() + + altAttrs := map[string]string{ + AttributePod: "pod-target", + AttributeNamespace: "ns-target", + } + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + AlternateOwnerAttrs: altAttrs, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + // Try to clear with wrong precondition + err = ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ClearAlternateOwner: true, + }, &OwnerAttributePreconditions{ + ExpectedAlternateOwner: &AttributeOwner{ + Namespace: "ns-wrong", + Name: "pod-wrong", + }, + }) + Expect(err).To(HaveOccurred()) + Expect(err).To(BeAssignableToTypeOf(cerrors.ErrorResourceUpdateConflict{})) + + // Alternate should remain unchanged + allocAttr, err := ic.GetAssignmentAttributes(ctx, allocatedIP) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.AlternateOwnerAttrs).To(Equal(altAttrs)) + }) + + It("should succeed when VerifyActiveOwnerEmpty is true and active owner is empty", func() { + ctx := context.Background() + + // Active owner is empty on fresh allocation - set it with VerifyActiveOwnerEmpty precondition + activeAttrs := map[string]string{ + AttributePod: "pod-new", + AttributeNamespace: "ns-new", + } + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ActiveOwnerAttrs: activeAttrs, + }, &OwnerAttributePreconditions{ + VerifyActiveOwnerEmpty: true, + }) + Expect(err).NotTo(HaveOccurred()) + + allocAttr, err := ic.GetAssignmentAttributes(ctx, allocatedIP) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.ActiveOwnerAttrs).To(Equal(activeAttrs)) + }) + + It("should fail when VerifyActiveOwnerEmpty is true but active owner is not empty", func() { + ctx := context.Background() + + // Set active owner first + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ActiveOwnerAttrs: map[string]string{ + AttributePod: "pod-existing", + AttributeNamespace: "ns-existing", + }, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + // Now try to set with VerifyActiveOwnerEmpty - should fail + err = ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ActiveOwnerAttrs: map[string]string{ + AttributePod: "pod-new", + AttributeNamespace: "ns-new", + }, + }, &OwnerAttributePreconditions{ + VerifyActiveOwnerEmpty: true, + }) + Expect(err).To(HaveOccurred()) + Expect(err).To(BeAssignableToTypeOf(cerrors.ErrorResourceUpdateConflict{})) + }) + + It("should succeed when VerifyAlternateOwnerEmpty is true and alternate owner is empty", func() { + ctx := context.Background() + + altAttrs := map[string]string{ + AttributePod: "pod-target", + AttributeNamespace: "ns-target", + } + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + AlternateOwnerAttrs: altAttrs, + }, &OwnerAttributePreconditions{ + VerifyAlternateOwnerEmpty: true, + }) + Expect(err).NotTo(HaveOccurred()) + + allocAttr, err := ic.GetAssignmentAttributes(ctx, allocatedIP) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.AlternateOwnerAttrs).To(Equal(altAttrs)) + }) + + It("should fail when VerifyAlternateOwnerEmpty is true but alternate owner is not empty", func() { + ctx := context.Background() + + // Set alternate owner first + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + AlternateOwnerAttrs: map[string]string{ + AttributePod: "pod-existing", + AttributeNamespace: "ns-existing", + }, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + // Now try to set with VerifyAlternateOwnerEmpty - should fail + err = ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + AlternateOwnerAttrs: map[string]string{ + AttributePod: "pod-new", + AttributeNamespace: "ns-new", + }, + }, &OwnerAttributePreconditions{ + VerifyAlternateOwnerEmpty: true, + }) + Expect(err).To(HaveOccurred()) + Expect(err).To(BeAssignableToTypeOf(cerrors.ErrorResourceUpdateConflict{})) + }) + + // --- Validation error tests --- + + It("should fail when updates is nil", func() { + ctx := context.Background() + + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, nil, nil) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("updates cannot be nil")) + }) + + It("should fail when ClearActiveOwner and ActiveOwnerAttrs are both set", func() { + ctx := context.Background() + + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ClearActiveOwner: true, + ActiveOwnerAttrs: map[string]string{ + AttributePod: "pod-a", + AttributeNamespace: "ns-a", + }, + }, nil) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("mutually exclusive")) + }) + + It("should fail when ClearAlternateOwner and AlternateOwnerAttrs are both set", func() { + ctx := context.Background() + + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ClearAlternateOwner: true, + AlternateOwnerAttrs: map[string]string{ + AttributePod: "pod-b", + AttributeNamespace: "ns-b", + }, + }, nil) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("mutually exclusive")) + }) + + It("should fail when ExpectedActiveOwner and VerifyActiveOwnerEmpty are both set", func() { + ctx := context.Background() + + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ClearActiveOwner: true, + }, &OwnerAttributePreconditions{ + ExpectedActiveOwner: &AttributeOwner{ + Namespace: "ns-a", + Name: "pod-a", + }, + VerifyActiveOwnerEmpty: true, + }) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("mutually exclusive")) + }) + + It("should fail when ExpectedAlternateOwner and VerifyAlternateOwnerEmpty are both set", func() { + ctx := context.Background() + + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ClearAlternateOwner: true, + }, &OwnerAttributePreconditions{ + ExpectedAlternateOwner: &AttributeOwner{ + Namespace: "ns-b", + Name: "pod-b", + }, + VerifyAlternateOwnerEmpty: true, + }) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("mutually exclusive")) + }) + + // --- Handle mismatch and unallocated IP tests --- + + It("should fail when the handle does not match the allocation", func() { + ctx := context.Background() + + err := ic.SetOwnerAttributes(ctx, allocatedIP, "wrong-handle", &OwnerAttributeUpdates{ + ActiveOwnerAttrs: map[string]string{ + AttributePod: "pod-a", + AttributeNamespace: "ns-a", + }, + }, nil) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("not assigned to handle")) + }) + + It("should fail when the IP is not allocated", func() { + ctx := context.Background() + + unallocatedIP := cnet.IP{IP: net.ParseIP("10.0.0.200")} + err := ic.SetOwnerAttributes(ctx, unallocatedIP, handle, &OwnerAttributeUpdates{ + ActiveOwnerAttrs: map[string]string{ + AttributePod: "pod-a", + AttributeNamespace: "ns-a", + }, + }, nil) + Expect(err).To(HaveOccurred()) + }) + + It("should fail when the IP is not in any pool", func() { + ctx := context.Background() + + outOfPoolIP := cnet.IP{IP: net.ParseIP("192.168.1.1")} + err := ic.SetOwnerAttributes(ctx, outOfPoolIP, handle, &OwnerAttributeUpdates{ + ActiveOwnerAttrs: map[string]string{ + AttributePod: "pod-a", + AttributeNamespace: "ns-a", + }, + }, nil) + Expect(err).To(HaveOccurred()) + }) + + // --- Live migration simulation test --- + + It("should simulate a live migration owner swap: set active, add alternate, clear active, clear alternate", func() { + ctx := context.Background() + + By("setting the source pod as active owner", func() { + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ActiveOwnerAttrs: map[string]string{ + AttributePod: "virt-launcher-source", + AttributeNamespace: "vm-namespace", + }, + }, nil) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying source is active, no alternate", func() { + allocAttr, err := ic.GetAssignmentAttributes(ctx, allocatedIP) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.ActiveOwnerAttrs[AttributePod]).To(Equal("virt-launcher-source")) + Expect(allocAttr.AlternateOwnerAttrs).To(BeNil()) + }) + + By("target pod arrives: set alternate owner with VerifyAlternateOwnerEmpty precondition", func() { + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + AlternateOwnerAttrs: map[string]string{ + AttributePod: "virt-launcher-target", + AttributeNamespace: "vm-namespace", + }, + }, &OwnerAttributePreconditions{ + VerifyAlternateOwnerEmpty: true, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying both owners are set", func() { + allocAttr, err := ic.GetAssignmentAttributes(ctx, allocatedIP) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.ActiveOwnerAttrs[AttributePod]).To(Equal("virt-launcher-source")) + Expect(allocAttr.AlternateOwnerAttrs[AttributePod]).To(Equal("virt-launcher-target")) + }) + + By("source pod is deleted: clear active owner with ExpectedActiveOwner precondition", func() { + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ClearActiveOwner: true, + }, &OwnerAttributePreconditions{ + ExpectedActiveOwner: &AttributeOwner{ + Namespace: "vm-namespace", + Name: "virt-launcher-source", + }, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying only alternate remains", func() { + allocAttr, err := ic.GetAssignmentAttributes(ctx, allocatedIP) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.ActiveOwnerAttrs).To(BeNil()) + Expect(allocAttr.AlternateOwnerAttrs[AttributePod]).To(Equal("virt-launcher-target")) + }) + + By("migration completes: promote target to active and clear alternate atomically", func() { + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ActiveOwnerAttrs: map[string]string{ + AttributePod: "virt-launcher-target", + AttributeNamespace: "vm-namespace", + }, + ClearAlternateOwner: true, + }, &OwnerAttributePreconditions{ + VerifyActiveOwnerEmpty: true, + ExpectedAlternateOwner: &AttributeOwner{ + Namespace: "vm-namespace", + Name: "virt-launcher-target", + }, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying final state: target is active, no alternate", func() { + allocAttr, err := ic.GetAssignmentAttributes(ctx, allocatedIP) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.ActiveOwnerAttrs[AttributePod]).To(Equal("virt-launcher-target")) + Expect(allocAttr.ActiveOwnerAttrs[AttributeNamespace]).To(Equal("vm-namespace")) + Expect(allocAttr.AlternateOwnerAttrs).To(BeNil()) + }) + }) + + // --- Precondition with both active and alternate verification --- + + It("should support preconditions on both active and alternate owners simultaneously", func() { + ctx := context.Background() + + // Set both owners + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ActiveOwnerAttrs: map[string]string{ + AttributePod: "pod-active", + AttributeNamespace: "ns-active", + }, + AlternateOwnerAttrs: map[string]string{ + AttributePod: "pod-alt", + AttributeNamespace: "ns-alt", + }, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + // Clear both with preconditions on both - should succeed + err = ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ClearActiveOwner: true, + ClearAlternateOwner: true, + }, &OwnerAttributePreconditions{ + ExpectedActiveOwner: &AttributeOwner{ + Namespace: "ns-active", + Name: "pod-active", + }, + ExpectedAlternateOwner: &AttributeOwner{ + Namespace: "ns-alt", + Name: "pod-alt", + }, + }) + Expect(err).NotTo(HaveOccurred()) + + allocAttr, err := ic.GetAssignmentAttributes(ctx, allocatedIP) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.ActiveOwnerAttrs).To(BeNil()) + Expect(allocAttr.AlternateOwnerAttrs).To(BeNil()) + }) + + It("should fail when active precondition passes but alternate precondition fails", func() { + ctx := context.Background() + + // Set both owners + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ActiveOwnerAttrs: map[string]string{ + AttributePod: "pod-active", + AttributeNamespace: "ns-active", + }, + AlternateOwnerAttrs: map[string]string{ + AttributePod: "pod-alt", + AttributeNamespace: "ns-alt", + }, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + // Active precondition is correct, alternate is wrong + err = ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ClearActiveOwner: true, + ClearAlternateOwner: true, + }, &OwnerAttributePreconditions{ + ExpectedActiveOwner: &AttributeOwner{ + Namespace: "ns-active", + Name: "pod-active", + }, + ExpectedAlternateOwner: &AttributeOwner{ + Namespace: "ns-wrong", + Name: "pod-wrong", + }, + }) + Expect(err).To(HaveOccurred()) + Expect(err).To(BeAssignableToTypeOf(cerrors.ErrorResourceUpdateConflict{})) + + // Both owners should remain unchanged (atomic - active was not cleared either) + allocAttr, err := ic.GetAssignmentAttributes(ctx, allocatedIP) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.ActiveOwnerAttrs[AttributePod]).To(Equal("pod-active")) + Expect(allocAttr.AlternateOwnerAttrs[AttributePod]).To(Equal("pod-alt")) + }) + + // --- No-op test --- + + It("should be a no-op when no updates are specified", func() { + ctx := context.Background() + + // Set initial state + err := ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{ + ActiveOwnerAttrs: map[string]string{ + AttributePod: "pod-a", + AttributeNamespace: "ns-a", + }, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + // Call with empty updates (no clear flags, no attrs) + err = ic.SetOwnerAttributes(ctx, allocatedIP, handle, &OwnerAttributeUpdates{}, nil) + Expect(err).NotTo(HaveOccurred()) + + // State should be unchanged + allocAttr, err := ic.GetAssignmentAttributes(ctx, allocatedIP) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.ActiveOwnerAttrs[AttributePod]).To(Equal("pod-a")) + }) + }) + }) Describe("IPAM IP borrowing", func() { @@ -1349,7 +2399,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun applyPoolWithBlockSize("10.0.0.0/28", true, `foo == "bar"`, 30) // We should be able to assign 8 addresses to node0, fully using its two blocks. - for i := 0; i < 8; i++ { + for range 8 { v4ia, _, err := ic.AutoAssign(context.Background(), AutoAssignArgs{ IntendedUse: v3.IPPoolAllowedUseWorkload, Num4: 1, @@ -2374,7 +3424,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun applyPool(pool2.String(), true, "") applyPool(pool3.String(), false, "") applyPool(pool4_v6.String(), true, "") - ipPools.pools[pool_big_block_size.String()] = pool{enabled: true, nodeSelector: "", blockSize: 31} + ipPools.Pools[pool_big_block_size.String()] = ipamtestutils.Pool{Enabled: true, NodeSelector: "", BlockSize: 31} _, _, outErr := ic.EnsureBlock(context.Background(), args) Expect(outErr).To(HaveOccurred()) }) @@ -2474,7 +3524,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun applyNode(bc, kc, host, nil) // ippool with 4 ips - ipPools.pools[pool1.String()] = pool{enabled: true, nodeSelector: "", blockSize: 30} + ipPools.Pools[pool1.String()] = ipamtestutils.Pool{Enabled: true, NodeSelector: "", BlockSize: 30} pools, _ = ipPools.GetEnabledPools(context.Background(), 4) Expect(len(pools)).To(Equal(1)) @@ -2624,7 +3674,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun }) DescribeTable("AutoAssign: requested IPs vs returned IPs", - func(host string, cleanEnv bool, pools []pool, usePool string, inv4, inv6 int, expv4ia, expv6ia *IPAMAssignments, blockLimit int, strictAffinity bool, expError error) { + func(host string, cleanEnv bool, pools []ipamtestutils.Pool, usePool string, inv4, inv6 int, expv4ia, expv6ia *IPAMAssignments, blockLimit int, strictAffinity bool, expError error) { if cleanEnv { Expect(bc.Clean()).To(Succeed()) deleteAllPools() @@ -2633,7 +3683,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun defer deleteNode(bc, kc, host) for _, v := range pools { - ipPools.pools[v.cidr] = pool{cidr: v.cidr, enabled: v.enabled, blockSize: v.blockSize, allowedUses: v.allowedUses} + ipPools.Pools[v.CIDR] = ipamtestutils.Pool{CIDR: v.CIDR, Enabled: v.Enabled, BlockSize: v.BlockSize, AllowedUses: v.AllowedUses} } parts := strings.Split(usePool, "+") @@ -2708,9 +3758,9 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun } }, - Entry("allowed use: requesting workload IP from tunnel pool should fail", "test-host", true, []pool{ - {cidr: "192.168.2.0/24", blockSize: 32, enabled: true, allowedUses: []v3.IPPoolAllowedUse{v3.IPPoolAllowedUseWorkload}}, - {cidr: "192.168.3.0/24", blockSize: 32, enabled: true, allowedUses: []v3.IPPoolAllowedUse{v3.IPPoolAllowedUseTunnel}}, + Entry("allowed use: requesting workload IP from tunnel pool should fail", "test-host", true, []ipamtestutils.Pool{ + {CIDR: "192.168.2.0/24", BlockSize: 32, Enabled: true, AllowedUses: []v3.IPPoolAllowedUse{v3.IPPoolAllowedUseWorkload}}, + {CIDR: "192.168.3.0/24", BlockSize: 32, Enabled: true, AllowedUses: []v3.IPPoolAllowedUse{v3.IPPoolAllowedUseTunnel}}, }, "192.168.3.0/24+workload", 1, @@ -2719,9 +3769,9 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun 0, false, ErrNoQualifiedPool, ), - Entry("allowed use: requesting workload IP from workload pool", "test-host", true, []pool{ - {cidr: "192.168.2.0/24", blockSize: 32, enabled: true, allowedUses: []v3.IPPoolAllowedUse{v3.IPPoolAllowedUseWorkload}}, - {cidr: "192.168.3.0/24", blockSize: 32, enabled: true, allowedUses: []v3.IPPoolAllowedUse{v3.IPPoolAllowedUseTunnel}}, + Entry("allowed use: requesting workload IP from workload pool", "test-host", true, []ipamtestutils.Pool{ + {CIDR: "192.168.2.0/24", BlockSize: 32, Enabled: true, AllowedUses: []v3.IPPoolAllowedUse{v3.IPPoolAllowedUseWorkload}}, + {CIDR: "192.168.3.0/24", BlockSize: 32, Enabled: true, AllowedUses: []v3.IPPoolAllowedUse{v3.IPPoolAllowedUseTunnel}}, }, "192.168.2.0/24+workload", 1, @@ -2737,9 +3787,9 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun 0, false, nil, ), - Entry("allowed use: requesting workload IP from any workload pool", "test-host", true, []pool{ - {cidr: "192.168.2.0/24", blockSize: 32, enabled: true, allowedUses: []v3.IPPoolAllowedUse{v3.IPPoolAllowedUseWorkload}}, - {cidr: "192.168.3.0/24", blockSize: 32, enabled: true, allowedUses: []v3.IPPoolAllowedUse{v3.IPPoolAllowedUseTunnel}}, + Entry("allowed use: requesting workload IP from any workload pool", "test-host", true, []ipamtestutils.Pool{ + {CIDR: "192.168.2.0/24", BlockSize: 32, Enabled: true, AllowedUses: []v3.IPPoolAllowedUse{v3.IPPoolAllowedUseWorkload}}, + {CIDR: "192.168.3.0/24", BlockSize: 32, Enabled: true, AllowedUses: []v3.IPPoolAllowedUse{v3.IPPoolAllowedUseTunnel}}, }, "any+workload", 1, @@ -2755,8 +3805,8 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun 0, false, nil, ), - Entry("allowed use: requesting workload IP from any workload pool when there are no workload pools", "test-host", true, []pool{ - {cidr: "192.168.3.0/24", blockSize: 32, enabled: true, allowedUses: []v3.IPPoolAllowedUse{v3.IPPoolAllowedUseTunnel}}, + Entry("allowed use: requesting workload IP from any workload pool when there are no workload pools", "test-host", true, []ipamtestutils.Pool{ + {CIDR: "192.168.3.0/24", BlockSize: 32, Enabled: true, AllowedUses: []v3.IPPoolAllowedUse{v3.IPPoolAllowedUseTunnel}}, }, "any+workload", 1, @@ -2766,9 +3816,9 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun 0, false, ErrNoQualifiedPool, ), - Entry("allowed use: tunnel IP from tunnel pool", "test-host", true, []pool{ - {cidr: "192.168.2.0/24", blockSize: 32, enabled: true, allowedUses: []v3.IPPoolAllowedUse{v3.IPPoolAllowedUseWorkload}}, - {cidr: "192.168.3.0/24", blockSize: 32, enabled: true, allowedUses: []v3.IPPoolAllowedUse{v3.IPPoolAllowedUseTunnel}}, + Entry("allowed use: tunnel IP from tunnel pool", "test-host", true, []ipamtestutils.Pool{ + {CIDR: "192.168.2.0/24", BlockSize: 32, Enabled: true, AllowedUses: []v3.IPPoolAllowedUse{v3.IPPoolAllowedUseWorkload}}, + {CIDR: "192.168.3.0/24", BlockSize: 32, Enabled: true, AllowedUses: []v3.IPPoolAllowedUse{v3.IPPoolAllowedUseTunnel}}, }, "192.168.3.0/24+tunnel", 1, @@ -2786,9 +3836,9 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun // Test 1a: AutoAssign 1 IPv4, 1 IPv6 with tiny block - expect one of each to be returned. Entry("1 v4 1 v6 - tiny block", "test-host", true, - []pool{ - {cidr: "192.168.1.0/24", blockSize: 32, enabled: true}, - {cidr: "fd80:24e2:f998:72d6::/120", blockSize: 128, enabled: true}, + []ipamtestutils.Pool{ + {CIDR: "192.168.1.0/24", BlockSize: 32, Enabled: true}, + {CIDR: "fd80:24e2:f998:72d6::/120", BlockSize: 128, Enabled: true}, }, "192.168.1.0/24", 1, 1, &IPAMAssignments{ @@ -2809,9 +3859,9 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun // Test 1b: AutoAssign 1 IPv4, 1 IPv6 with massive block - expect one of each to be returned. Entry("1 v4 1 v6 - big block", "test-host", true, - []pool{ - {cidr: "192.168.0.0/16", blockSize: 20, enabled: true}, - {cidr: "fd80:24e2:f998:72d6::/110", blockSize: 116, enabled: true}, + []ipamtestutils.Pool{ + {CIDR: "192.168.0.0/16", BlockSize: 20, Enabled: true}, + {CIDR: "fd80:24e2:f998:72d6::/110", BlockSize: 116, Enabled: true}, }, "192.168.0.0/16", 1, 1, &IPAMAssignments{ @@ -2832,9 +3882,9 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun // Test 1c: AutoAssign 1 IPv4, 1 IPv6 with default block - expect one of each to be returned. Entry("1 v4 1 v6 - default block", "test-host", true, - []pool{ - {cidr: "192.168.1.0/24", blockSize: 26, enabled: true}, - {cidr: "fd80:24e2:f998:72d6::/120", blockSize: 122, enabled: true}, + []ipamtestutils.Pool{ + {CIDR: "192.168.1.0/24", BlockSize: 26, Enabled: true}, + {CIDR: "fd80:24e2:f998:72d6::/120", BlockSize: 122, Enabled: true}, }, "192.168.1.0/24", 1, 1, &IPAMAssignments{ @@ -2855,9 +3905,9 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun // Test 2a: AutoAssign 256 IPv4, 256 IPv6 with default blocksize- expect 256 IPv4 + IPv6 addresses. Entry("256 v4 256 v6", "test-host", true, - []pool{ - {cidr: "192.168.1.0/24", blockSize: 26, enabled: true}, - {cidr: "fd80:24e2:f998:72d6::/120", blockSize: 122, enabled: true}, + []ipamtestutils.Pool{ + {CIDR: "192.168.1.0/24", BlockSize: 26, Enabled: true}, + {CIDR: "fd80:24e2:f998:72d6::/120", BlockSize: 122, Enabled: true}, }, "192.168.1.0/24", 256, 256, &IPAMAssignments{ @@ -2877,9 +3927,9 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun // Test 2b: AutoAssign 256 IPv4, 256 IPv6 with small blocksize- expect 256 IPv4 + IPv6 addresses. Entry("256 v4 256 v6 - small blocks", "test-host", true, - []pool{ - {cidr: "192.168.1.0/24", blockSize: 30, enabled: true}, - {cidr: "fd80:24e2:f998:72d6::/120", blockSize: 126, enabled: true}, + []ipamtestutils.Pool{ + {CIDR: "192.168.1.0/24", BlockSize: 30, Enabled: true}, + {CIDR: "fd80:24e2:f998:72d6::/120", BlockSize: 126, Enabled: true}, }, "192.168.1.0/24", 256, 256, &IPAMAssignments{ @@ -2900,9 +3950,9 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun // Test 2a: AutoAssign 256 IPv4, 256 IPv6 with num blocks limit expect 64 IPv4 + IPv6 addresses. Entry("256 v4 0 v6 block limit", "test-host", true, - []pool{ - {cidr: "192.168.1.0/24", blockSize: 26, enabled: true}, - {cidr: "fd80:24e2:f998:72d6::/120", blockSize: 122, enabled: true}, + []ipamtestutils.Pool{ + {CIDR: "192.168.1.0/24", BlockSize: 26, Enabled: true}, + {CIDR: "fd80:24e2:f998:72d6::/120", BlockSize: 122, Enabled: true}, }, "192.168.1.0/24", 256, 0, &IPAMAssignments{ @@ -2914,9 +3964,9 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun }, nil, 1, false, ErrBlockLimit), Entry("256 v4 0 v6 block limit 2", "test-host", true, - []pool{ - {cidr: "192.168.1.0/24", blockSize: 26, enabled: true}, - {cidr: "fd80:24e2:f998:72d6::/120", blockSize: 122, enabled: true}, + []ipamtestutils.Pool{ + {CIDR: "192.168.1.0/24", BlockSize: 26, Enabled: true}, + {CIDR: "fd80:24e2:f998:72d6::/120", BlockSize: 122, Enabled: true}, }, "192.168.1.0/24", 256, 0, &IPAMAssignments{ @@ -2928,9 +3978,9 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun }, nil, 2, false, ErrBlockLimit), Entry("0 v4 256 v6 block limit", "test-host", true, - []pool{ - {cidr: "192.168.1.0/24", blockSize: 26, enabled: true}, - {cidr: "fd80:24e2:f998:72d6::/120", blockSize: 122, enabled: true}, + []ipamtestutils.Pool{ + {CIDR: "192.168.1.0/24", BlockSize: 26, Enabled: true}, + {CIDR: "fd80:24e2:f998:72d6::/120", BlockSize: 122, Enabled: true}, }, "192.168.1.0/24", 0, 256, nil, &IPAMAssignments{ @@ -2944,9 +3994,9 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun // Test 3: AutoAssign 257 IPv4, 0 IPv6 - expect 256 IPv4 addresses, no IPv6, and no error. Entry("257 v4 0 v6", "test-host", true, - []pool{ - {cidr: "192.168.1.0/24", blockSize: 26, enabled: true}, - {cidr: "fd80:24e2:f998:72d6::/120", blockSize: 122, enabled: true}, + []ipamtestutils.Pool{ + {CIDR: "192.168.1.0/24", BlockSize: 26, Enabled: true}, + {CIDR: "fd80:24e2:f998:72d6::/120", BlockSize: 122, Enabled: true}, }, "192.168.1.0/24", 257, 0, &IPAMAssignments{ @@ -2960,9 +4010,9 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun // Test 4: AutoAssign 0 IPv4, 257 IPv6 - expect 256 IPv6 addresses, no IPv6, and no error. Entry("0 v4 257 v6", "test-host", true, - []pool{ - {cidr: "192.168.1.0/24", blockSize: 26, enabled: true}, - {cidr: "fd80:24e2:f998:72d6::/120", blockSize: 122, enabled: true}, + []ipamtestutils.Pool{ + {CIDR: "192.168.1.0/24", BlockSize: 26, Enabled: true}, + {CIDR: "fd80:24e2:f998:72d6::/120", BlockSize: 122, Enabled: true}, }, "192.168.1.0/24", 0, 257, nil, &IPAMAssignments{ @@ -2977,9 +4027,9 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun // Test 5: (use pool of size /25 so only two blocks are contained): // - Assign 1 address on host A (Expect 1 address). Entry("1 v4 0 v6 host-a", "host-a", true, - []pool{ - {cidr: "10.0.0.0/25", blockSize: 26, enabled: true}, - {cidr: "fd80:24e2:f998:72d6::/121", blockSize: 122, enabled: true}, + []ipamtestutils.Pool{ + {CIDR: "10.0.0.0/25", BlockSize: 26, Enabled: true}, + {CIDR: "fd80:24e2:f998:72d6::/121", BlockSize: 122, Enabled: true}, }, "10.0.0.0/25", 1, 0, &IPAMAssignments{ @@ -2993,9 +4043,9 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun // - Assign 1 address on host B (Expect 1 address, different block). Entry("1 v4 0 v6 host-b", "host-b", false, - []pool{ - {cidr: "10.0.0.0/25", blockSize: 26, enabled: true}, - {cidr: "fd80:24e2:f998:72d6::/121", blockSize: 122, enabled: true}, + []ipamtestutils.Pool{ + {CIDR: "10.0.0.0/25", BlockSize: 26, Enabled: true}, + {CIDR: "fd80:24e2:f998:72d6::/121", BlockSize: 122, Enabled: true}, }, "10.0.0.0/25", 1, 0, &IPAMAssignments{ @@ -3009,9 +4059,9 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun // - Assign 64 more addresses on host A (Expect 63 addresses from host A's block, 1 address from host B's block). Entry("64 v4 0 v6 host-a", "host-a", false, - []pool{ - {cidr: "10.0.0.0/25", blockSize: 26, enabled: true}, - {cidr: "fd80:24e2:f998:72d6::/121", blockSize: 122, enabled: true}, + []ipamtestutils.Pool{ + {CIDR: "10.0.0.0/25", BlockSize: 26, Enabled: true}, + {CIDR: "fd80:24e2:f998:72d6::/121", BlockSize: 122, Enabled: true}, }, "10.0.0.0/25", 64, 0, &IPAMAssignments{ @@ -3024,10 +4074,10 @@ var _ = testutils.E2eDatastoreDescribe("IPAM tests", testutils.DatastoreAll, fun nil, 0, false, nil), // - Try to assign 256 addresses with strict affinity enabled, expect 64 addresses. Entry("256 v4 0 v6 strict affinity", "test-host", true, - []pool{ - {cidr: "192.168.1.0/26", blockSize: 26, enabled: true}, - {cidr: "192.168.1.64/26", blockSize: 26, enabled: true}, - {cidr: "fd80:24e2:f998:72d6::/120", blockSize: 122, enabled: true}, + []ipamtestutils.Pool{ + {CIDR: "192.168.1.0/26", BlockSize: 26, Enabled: true}, + {CIDR: "192.168.1.64/26", BlockSize: 26, Enabled: true}, + {CIDR: "fd80:24e2:f998:72d6::/120", BlockSize: 122, Enabled: true}, }, "192.168.1.0/26", 256, 0, &IPAMAssignments{ @@ -3331,16 +4381,16 @@ var ( var _ = DescribeTable("determinePools tests IPV4", func(pool1Enabled, pool2Enabled bool, pool1Selector, pool2Selector string, requestPool1, requestPool2 bool, expectation []string, expectErr bool) { // Seed data - ipPools.pools = map[string]pool{ - v4Pool1CIDR: {enabled: pool1Enabled, nodeSelector: pool1Selector, assignmentMode: v3.Automatic}, - v4Pool2CIDR: {enabled: pool2Enabled, nodeSelector: pool2Selector, assignmentMode: v3.Automatic}, + ipPools.Pools = map[string]ipamtestutils.Pool{ + v4Pool1CIDR: {Enabled: pool1Enabled, NodeSelector: pool1Selector, AssignmentMode: v3.Automatic}, + v4Pool2CIDR: {Enabled: pool2Enabled, NodeSelector: pool2Selector, AssignmentMode: v3.Automatic}, } // Create a new IPAM client, giving a nil datastore client since determining pools // doesn't require datastore access (we mock out the IP pool accessor). - ic := NewIPAMClient(nil, ipPools, &fakeReservations{}) + ic := NewIPAMClient(nil, ipPools, &ipamtestutils.FakeReservations{}) // Create a node object for the test. - node := libapiv3.Node{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}} + node := internalapi.Node{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}} // Prep input data reqPools := []cnet.IPNet{} @@ -3400,16 +4450,16 @@ var ( var _ = DescribeTable("determinePools tests IPV6", func(pool1Enabled, pool2Enabled bool, pool1Selector, pool2Selector string, requestPool1, requestPool2 bool, expectation []string, expectErr bool) { // Seed data - ipPools.pools = map[string]pool{ - v6Pool1CIDR: {enabled: pool1Enabled, nodeSelector: pool1Selector, assignmentMode: v3.Automatic}, - v6Pool2CIDR: {enabled: pool2Enabled, nodeSelector: pool2Selector, assignmentMode: v3.Automatic}, + ipPools.Pools = map[string]ipamtestutils.Pool{ + v6Pool1CIDR: {Enabled: pool1Enabled, NodeSelector: pool1Selector, AssignmentMode: v3.Automatic}, + v6Pool2CIDR: {Enabled: pool2Enabled, NodeSelector: pool2Selector, AssignmentMode: v3.Automatic}, } // Create a new IPAM client, giving a nil datastore client since determining pools // doesn't require datastore access (we mock out the IP pool accessor). - ic := NewIPAMClient(nil, ipPools, &fakeReservations{}) + ic := NewIPAMClient(nil, ipPools, &ipamtestutils.FakeReservations{}) // Create a node object for the test. - node := libapiv3.Node{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}} + node := internalapi.Node{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}} // Prep input data reqPools := []cnet.IPNet{} @@ -3498,73 +4548,27 @@ func getAffineBlocks(backend bapi.Client, host string) []cnet.IPNet { func deleteAllPools() { log.Infof("Deleting all pools") - ipPools.pools = map[string]pool{} + ipPools.Pools = map[string]ipamtestutils.Pool{} } func applyPool(cidr string, enabled bool, nodeSelector string) { - ipPools.pools[cidr] = pool{enabled: enabled, nodeSelector: nodeSelector, assignmentMode: v3.Automatic} + ipPools.Pools[cidr] = ipamtestutils.Pool{Enabled: enabled, NodeSelector: nodeSelector, AssignmentMode: v3.Automatic} } func applyPoolWithUses(cidr string, enabled bool, nodeSelector string, uses []v3.IPPoolAllowedUse) { - ipPools.pools[cidr] = pool{enabled: enabled, nodeSelector: nodeSelector, allowedUses: uses, assignmentMode: v3.Automatic} + ipPools.Pools[cidr] = ipamtestutils.Pool{Enabled: enabled, NodeSelector: nodeSelector, AllowedUses: uses, AssignmentMode: v3.Automatic} } func applyPoolWithBlockSize(cidr string, enabled bool, nodeSelector string, blockSize int) { - ipPools.pools[cidr] = pool{enabled: enabled, nodeSelector: nodeSelector, blockSize: blockSize, assignmentMode: v3.Automatic} + ipPools.Pools[cidr] = ipamtestutils.Pool{Enabled: enabled, NodeSelector: nodeSelector, BlockSize: blockSize, AssignmentMode: v3.Automatic} } func deletePool(cidr string) { - delete(ipPools.pools, cidr) + delete(ipPools.Pools, cidr) } func applyNode(c bapi.Client, kc *kubernetes.Clientset, host string, labels map[string]string) { - ExpectWithOffset(1, tryApplyNode(c, kc, host, labels)).NotTo(HaveOccurred()) -} - -func tryApplyNode(c bapi.Client, kc *kubernetes.Clientset, host string, labels map[string]string) error { - if kc != nil { - // If a k8s clientset was provided, create the node in Kubernetes. - n := corev1.Node{ - TypeMeta: metav1.TypeMeta{ - Kind: "Node", - APIVersion: "v1", - }, - } - n.Name = host - n.Labels = labels - - // Create/Update the node - _, err := kc.CoreV1().Nodes().Create(context.Background(), &n, metav1.CreateOptions{}) - if err != nil { - if kerrors.IsAlreadyExists(err) { - oldNode, _ := kc.CoreV1().Nodes().Get(context.Background(), host, metav1.GetOptions{}) - oldNode.Labels = labels - - _, err = kc.CoreV1().Nodes().Update(context.Background(), oldNode, metav1.UpdateOptions{}) - if err != nil { - return err - } - } else { - return err - } - } - log.WithField("node", host).WithError(err).Info("node applied") - } else { - // Otherwise, create it in Calico. - _, err := c.Apply(context.Background(), &model.KVPair{ - Key: model.ResourceKey{Name: host, Kind: libapiv3.KindNode}, - Value: libapiv3.Node{ - ObjectMeta: metav1.ObjectMeta{Labels: labels}, - Spec: libapiv3.NodeSpec{OrchRefs: []libapiv3.OrchRef{ - {Orchestrator: "k8s", NodeName: host}, - }}, - }, - }) - if err != nil { - return err - } - } - return nil + ipamtestutils.ApplyNode(c, kc, host, labels) } func deleteNode(c bapi.Client, kc *kubernetes.Clientset, host string) { @@ -3574,7 +4578,7 @@ func deleteNode(c bapi.Client, kc *kubernetes.Clientset, host string) { Fail(fmt.Sprintf("Error deleting node %s: %v", host, err)) } } else { - _, err := c.Delete(context.Background(), &model.ResourceKey{Name: host, Kind: libapiv3.KindNode}, "") + _, err := c.Delete(context.Background(), &model.ResourceKey{Name: host, Kind: internalapi.KindNode}, "") if err != nil { if _, ok := err.(cerrors.ErrorResourceDoesNotExist); !ok { Fail(fmt.Sprintf("Error deleting node %s: %v", host, err)) @@ -3669,13 +4673,13 @@ var _ = Describe("determinePools with namespace selector", func() { var ( ic ipamClient ctx context.Context - accessor *ipPoolAccessor + accessor *ipamtestutils.IPPoolAccessor ) BeforeEach(func() { ctx = context.Background() - accessor = &ipPoolAccessor{ - pools: make(map[string]pool), + accessor = &ipamtestutils.IPPoolAccessor{ + Pools: make(map[string]ipamtestutils.Pool), } ic = ipamClient{ pools: accessor, @@ -3693,15 +4697,15 @@ var _ = Describe("determinePools with namespace selector", func() { expectedMatch bool, description string, ) { - accessor.pools[poolCIDR] = pool{ - cidr: poolCIDR, - enabled: true, - nodeSelector: poolNodeSelector, - namespaceSelector: poolNamespaceSelector, - blockSize: 26, + accessor.Pools[poolCIDR] = ipamtestutils.Pool{ + CIDR: poolCIDR, + Enabled: true, + NodeSelector: poolNodeSelector, + NamespaceSelector: poolNamespaceSelector, + BlockSize: 26, } - node := libapiv3.Node{ + node := internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Labels: nodeLabels, }, @@ -3820,29 +4824,29 @@ var _ = Describe("determinePools with namespace selector", func() { ) It("should handle multiple pools with different namespace selectors", func() { - accessor.pools["10.0.0.0/24"] = pool{ - cidr: "10.0.0.0/24", - enabled: true, - nodeSelector: "", - namespaceSelector: "environment == 'production'", - blockSize: 26, + accessor.Pools["10.0.0.0/24"] = ipamtestutils.Pool{ + CIDR: "10.0.0.0/24", + Enabled: true, + NodeSelector: "", + NamespaceSelector: "environment == 'production'", + BlockSize: 26, } - accessor.pools["10.1.0.0/24"] = pool{ - cidr: "10.1.0.0/24", - enabled: true, - nodeSelector: "", - namespaceSelector: "environment == 'development'", - blockSize: 26, + accessor.Pools["10.1.0.0/24"] = ipamtestutils.Pool{ + CIDR: "10.1.0.0/24", + Enabled: true, + NodeSelector: "", + NamespaceSelector: "environment == 'development'", + BlockSize: 26, } - accessor.pools["10.2.0.0/24"] = pool{ - cidr: "10.2.0.0/24", - enabled: true, - nodeSelector: "", - namespaceSelector: "", - blockSize: 26, + accessor.Pools["10.2.0.0/24"] = ipamtestutils.Pool{ + CIDR: "10.2.0.0/24", + Enabled: true, + NodeSelector: "", + NamespaceSelector: "", + BlockSize: 26, } - node := libapiv3.Node{ + node := internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{}, }, @@ -3876,15 +4880,15 @@ var _ = Describe("determinePools with namespace selector", func() { }) It("should handle invalid namespace selector syntax", func() { - accessor.pools["10.0.0.0/24"] = pool{ - cidr: "10.0.0.0/24", - enabled: true, - nodeSelector: "", - namespaceSelector: "invalid selector syntax [", - blockSize: 26, + accessor.Pools["10.0.0.0/24"] = ipamtestutils.Pool{ + CIDR: "10.0.0.0/24", + Enabled: true, + NodeSelector: "", + NamespaceSelector: "invalid selector syntax [", + BlockSize: 26, } - node := libapiv3.Node{ + node := internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{}, }, @@ -3910,15 +4914,15 @@ var _ = Describe("determinePools with namespace selector", func() { }) It("should prioritize requested pools over namespace selectors", func() { - accessor.pools["10.0.0.0/24"] = pool{ - cidr: "10.0.0.0/24", - enabled: true, - nodeSelector: "", - namespaceSelector: "environment == 'production'", - blockSize: 26, + accessor.Pools["10.0.0.0/24"] = ipamtestutils.Pool{ + CIDR: "10.0.0.0/24", + Enabled: true, + NodeSelector: "", + NamespaceSelector: "environment == 'production'", + BlockSize: 26, } - node := libapiv3.Node{ + node := internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{}, }, diff --git a/libcalico-go/lib/ipam/ipam_types.go b/libcalico-go/lib/ipam/ipam_types.go index bc1cf9712fc..78bc7fdf68c 100644 --- a/libcalico-go/lib/ipam/ipam_types.go +++ b/libcalico-go/lib/ipam/ipam_types.go @@ -24,6 +24,17 @@ import ( cnet "github.com/projectcalico/calico/libcalico-go/lib/net" ) +// VMAddressPersistence controls whether KubeVirt VirtualMachine workloads +// maintain persistent IP addresses across VM lifecycle events. +type VMAddressPersistence string + +const ( + // VMAddressPersistenceEnabled enables IP persistence for KubeVirt VMs. + VMAddressPersistenceEnabled VMAddressPersistence = "Enabled" + // VMAddressPersistenceDisabled disables IP persistence for KubeVirt VMs. + VMAddressPersistenceDisabled VMAddressPersistence = "Disabled" +) + // AssignIPArgs defines the set of arguments for assigning a specific IP address. type AssignIPArgs struct { // The IP address to assign. @@ -46,6 +57,11 @@ type AssignIPArgs struct { // The intended use for the IP address. Used to determine the affinityType of the host. IntendedUse v3.IPPoolAllowedUse + + // MaxAllocToHandlePerIPVersion specifies the maximum number of IPs per IP version (IPv4/IPv6) + // that can be allocated for this handle. If 0, no limit is enforced. + // Used for KubeVirt VMI pods to ensure only one IP allocation per VMI per IP version. + MaxAllocToHandlePerIPVersion int } // AutoAssignArgs defines the set of arguments for assigning one or more @@ -91,6 +107,11 @@ type AutoAssignArgs struct { // This field is required. IntendedUse v3.IPPoolAllowedUse + // MaxAllocToHandlePerIPVersion specifies the maximum number of IPs per IP version (IPv4/IPv6) + // that can be allocated across all blocks for this handle. If 0, no limit is enforced. + // Used for KubeVirt VMI pods to ensure only one IP allocation per VMI per IP version. + MaxAllocToHandlePerIPVersion int + // The namespace object for namespaceSelector support. Namespace *corev1.Namespace } @@ -112,6 +133,18 @@ type IPAMConfig struct { // If non-zero, MaxBlocksPerHost specifies the max number of blocks that may // be affine to a node. MaxBlocksPerHost int + + // KubeVirtVMAddressPersistence controls whether KubeVirt VirtualMachine workloads + // maintain persistent IP addresses across VM lifecycle events. + // When set to VMAddressPersistenceEnabled, Calico automatically ensures that KubeVirt VMs retain their + // IP addresses when their underlying pods are recreated during VM operations such as + // reboot, live migration, or pod eviction. IP persistency is ensured when the + // VirtualMachineInstance (VMI) resource is deleted and recreated by the VM controller. + // When set to VMAddressPersistenceDisabled, VMs receive new IP addresses whenever their pods are recreated, + // following standard pod IP allocation behavior. Live migration target pods are not allowed + // when this is set to VMAddressPersistenceDisabled and will result in an error. + // If nil, defaults to VMAddressPersistenceEnabled (IP persistence enabled if not specified). + KubeVirtVMAddressPersistence *VMAddressPersistence } // GetUtilizationArgs defines the set of arguments for requesting IP utilization. @@ -211,3 +244,83 @@ func (opts *ReleaseOptions) AsNetIP() (*cnet.IP, error) { } return nil, fmt.Errorf("failed to parse IP: %s", opts.Address) } + +// AttributeOwner represents the owner of an IP allocation attribute. +type AttributeOwner struct { + // Namespace is the Kubernetes namespace of the pod. + Namespace string + // Name is the name of the pod. + Name string +} + +// OwnerAttributeUpdates specifies the attribute values to set for ActiveOwnerAttrs and/or AlternateOwnerAttrs. +// These are the actual values that will be written to the IP allocation. +type OwnerAttributeUpdates struct { + // ActiveOwnerAttrs specifies attributes to set for ActiveOwnerAttrs. + // If nil and ClearActiveOwner is false, ActiveOwnerAttrs is not modified. + // If ClearActiveOwner is true, ActiveOwnerAttrs must be nil (error if both are set). + ActiveOwnerAttrs map[string]string + + // ClearActiveOwner indicates that ActiveOwnerAttrs should be cleared (set to nil). + // If true, ActiveOwnerAttrs must be nil. An error is returned if both are set. + ClearActiveOwner bool + + // AlternateOwnerAttrs specifies attributes to set for AlternateOwnerAttrs. + // If nil and ClearAlternateOwner is false, AlternateOwnerAttrs is not modified. + // If ClearAlternateOwner is true, AlternateOwnerAttrs must be nil (error if both are set). + AlternateOwnerAttrs map[string]string + + // ClearAlternateOwner indicates that AlternateOwnerAttrs should be cleared (set to nil). + // If true, AlternateOwnerAttrs must be nil. An error is returned if both are set. + ClearAlternateOwner bool +} + +// OwnerAttributePreconditions specifies expected owners for verification before setting attributes. +// These are used to prevent overwriting attributes that belong to a different pod. +type OwnerAttributePreconditions struct { + // ExpectedActiveOwner verifies current ActiveOwnerAttrs matches the specified owner before setting. + // If nil, no match on ExpectedActiveOwner is performed. + // Verification can still occur via VerifyActiveOwnerEmpty if that field is true. + // An error is returned if both ExpectedActiveOwner and VerifyActiveOwnerEmpty are set. + ExpectedActiveOwner *AttributeOwner + + // VerifyActiveOwnerEmpty verifies that ActiveOwnerAttrs is empty (nil or empty map) before setting. + // If true, ActiveOwnerAttrs must be empty for the operation to proceed. + // An error is returned if both ExpectedActiveOwner and VerifyActiveOwnerEmpty are set. + VerifyActiveOwnerEmpty bool + + // ExpectedAlternateOwner verifies current AlternateOwnerAttrs matches the specified owner before setting. + // If nil, no match on ExpectedAlternateOwner is performed. + // Verification can still occur via VerifyAlternateOwnerEmpty if that field is true. + // An error is returned if both ExpectedAlternateOwner and VerifyAlternateOwnerEmpty are set. + ExpectedAlternateOwner *AttributeOwner + + // VerifyAlternateOwnerEmpty verifies that AlternateOwnerAttrs is empty (nil or empty map) before setting. + // If true, AlternateOwnerAttrs must be empty for the operation to proceed. + // An error is returned if both ExpectedAlternateOwner and VerifyAlternateOwnerEmpty are set. + VerifyAlternateOwnerEmpty bool +} + +// expectedActiveOwner returns the expected active owner for precondition verification. +// Returns nil if no check is needed. Safe to call on a nil receiver. +func (p *OwnerAttributePreconditions) expectedActiveOwner() *AttributeOwner { + if p == nil { + return nil + } + if p.VerifyActiveOwnerEmpty { + return EmptyAttributeOwner() + } + return p.ExpectedActiveOwner +} + +// expectedAlternateOwner returns the expected alternate owner for precondition verification. +// Returns nil if no check is needed. Safe to call on a nil receiver. +func (p *OwnerAttributePreconditions) expectedAlternateOwner() *AttributeOwner { + if p == nil { + return nil + } + if p.VerifyAlternateOwnerEmpty { + return EmptyAttributeOwner() + } + return p.ExpectedAlternateOwner +} diff --git a/libcalico-go/lib/ipam/ipam_upgrade_kdd_test.go b/libcalico-go/lib/ipam/ipam_upgrade_kdd_test.go index 03c70609941..8f32adfd540 100644 --- a/libcalico-go/lib/ipam/ipam_upgrade_kdd_test.go +++ b/libcalico-go/lib/ipam/ipam_upgrade_kdd_test.go @@ -15,15 +15,16 @@ package ipam import ( "context" + "os" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" crclient "sigs.k8s.io/controller-runtime/pkg/client" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend" bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s" @@ -37,6 +38,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM UpgradeHost (Kubernetes datastore o bc bapi.Client ic Interface crdClient crclient.Client + useV3 = os.Getenv("CALICO_API_GROUP") == "projectcalico.org" ) BeforeEach(func() { @@ -45,12 +47,12 @@ var _ = testutils.E2eDatastoreDescribe("IPAM UpgradeHost (Kubernetes datastore o bc, err = backend.NewClient(config) Expect(err).NotTo(HaveOccurred()) Expect(bc.Clean()).NotTo(HaveOccurred()) - ic = NewIPAMClient(bc, ipPools, &fakeReservations{}) + ic = NewIPAMClient(bc, ipPools, &ipamtestutils.FakeReservations{}) // Build a raw CRD REST client using the same kubeconfig as the backend client. cfg, _, err := k8s.CreateKubernetesClientset(&config.Spec) Expect(err).NotTo(HaveOccurred()) - crdClient, err = rawcrdclient.New(cfg) + crdClient, err = rawcrdclient.New(cfg, useV3) Expect(err).NotTo(HaveOccurred()) }) @@ -60,6 +62,10 @@ var _ = testutils.E2eDatastoreDescribe("IPAM UpgradeHost (Kubernetes datastore o }) It("should add labels to this host's block affinities only", func() { + if !useV3 { + Skip("Block affinity upgrade not needed for v1 API group") + } + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() @@ -71,7 +77,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM UpgradeHost (Kubernetes datastore o Expect(ipamtestutils.CreateUnlabeledBlockAffinity(ctx, crdClient, hostB, "10.10.1.0/26")).To(Succeed()) // Sanity check: List and assert they currently have no labels. - var list libapiv3.BlockAffinityList + var list internalapi.BlockAffinityList Expect(crdClient.List(ctx, &list)).To(Succeed()) Expect(list.Items).To(HaveLen(3)) for _, item := range list.Items { @@ -82,7 +88,7 @@ var _ = testutils.E2eDatastoreDescribe("IPAM UpgradeHost (Kubernetes datastore o Expect(ic.UpgradeHost(ctx, hostA)).To(Succeed()) // Re-list and verify labels applied to hostA's affinities, but not hostB. - list = libapiv3.BlockAffinityList{} + list = internalapi.BlockAffinityList{} Expect(crdClient.List(ctx, &list)).To(Succeed()) By("verifying labels exist for host-a and not for host-b") for _, item := range list.Items { diff --git a/libcalico-go/lib/ipam/ipam_win_test.go b/libcalico-go/lib/ipam/ipam_win_test.go index e80d5b99a6b..cee67576f43 100644 --- a/libcalico-go/lib/ipam/ipam_win_test.go +++ b/libcalico-go/lib/ipam/ipam_win_test.go @@ -19,8 +19,7 @@ import ( "fmt" "net" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" @@ -31,6 +30,7 @@ import ( bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" + "github.com/projectcalico/calico/libcalico-go/lib/ipam/ipamtestutils" cnet "github.com/projectcalico/calico/libcalico-go/lib/net" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -38,7 +38,7 @@ import ( // Simulating ipamClient Interface type ipamClientWindows struct { client bapi.Client - pools ipPoolAccessor + pools ipamtestutils.IPPoolAccessor blockReaderWriter blockReaderWriter } @@ -51,7 +51,7 @@ func (c ipamClientWindows) GetAssignmentBlockCIDR(ctx context.Context, addr cnet } var ( - ipPoolsWindows = &ipPoolAccessor{pools: map[string]pool{}} + ipPoolsWindows = &ipamtestutils.IPPoolAccessor{Pools: map[string]ipamtestutils.Pool{}} rsvdAttrWindows = &HostReservedAttr{ StartOfBlock: 3, EndOfBlock: 1, @@ -89,7 +89,7 @@ var _ = testutils.E2eDatastoreDescribe("Windows: IPAM tests", testutils.Datastor var err error bc, err = backend.NewClient(config) Expect(err).NotTo(HaveOccurred()) - ic = NewIPAMClient(bc, ipPoolsWindows, &fakeReservations{}) + ic = NewIPAMClient(bc, ipPoolsWindows, &ipamtestutils.FakeReservations{}) // If running in KDD mode, extract the k8s clientset. if config.Spec.DatastoreType == "kubernetes" { @@ -104,7 +104,7 @@ var _ = testutils.E2eDatastoreDescribe("Windows: IPAM tests", testutils.Datastor applyNode(bc, kc, "Windows-TestHost-1", nil) defer deleteNode(bc, kc, "Windows-TestHost-1") - ipPoolsWindows.pools["100.0.0.0/24"] = pool{cidr: "100.0.0.0/24", enabled: true, blockSize: 26} + ipPoolsWindows.Pools["100.0.0.0/24"] = ipamtestutils.Pool{CIDR: "100.0.0.0/24", Enabled: true, BlockSize: 26} fromPool := cnet.MustParseNetwork("100.0.0.0/24") @@ -131,7 +131,7 @@ var _ = testutils.E2eDatastoreDescribe("Windows: IPAM tests", testutils.Datastor // Test Case: Reserved IPs should not be allocated // test case is only written for IPv4 DescribeTable("Windows: IPAM AutoAssign should not assign reserved IPs", - func(host string, cleanEnv bool, pools []pool, usePool string, inv4 int, expv4 int, expError error, windowsHost string) { + func(host string, cleanEnv bool, pools []ipamtestutils.Pool, usePool string, inv4 int, expv4 int, expError error, windowsHost string) { if cleanEnv { bc.Clean() deleteAllPoolsWindows() @@ -141,7 +141,7 @@ var _ = testutils.E2eDatastoreDescribe("Windows: IPAM tests", testutils.Datastor defer setAffinity(ic, false) for _, v := range pools { - ipPoolsWindows.pools[v.cidr] = pool{cidr: v.cidr, enabled: v.enabled, blockSize: v.blockSize} + ipPoolsWindows.Pools[v.CIDR] = ipamtestutils.Pool{CIDR: v.CIDR, Enabled: v.Enabled, BlockSize: v.BlockSize} } // Host must exist before trying to autoassign to it @@ -183,14 +183,13 @@ var _ = testutils.E2eDatastoreDescribe("Windows: IPAM tests", testutils.Datastor for _, ip := range outv4ia.IPs { Expect(reservedIPs).NotTo(ContainElement(ip.String())) } - }, // Test 1: AutoAssign 256 IPv4 - expect NOT to assign 100.0.0.0, 100.0.0.1, 100.0.0.2, 100.0.0.63, // 100.0.0.64, 100.0.0.65, 100.0.0.66, 100.0.0.127, // 100.0.0.128, 100.0.0.129, 100.0.0.130, 100.0.0.191, // 100.0.0.192, 100.0.0.193, 100.0.0.194, 100.0.0.255 IPs. - Entry("256 v4 ", "testHost", true, []pool{{cidr: "100.0.0.0/24", blockSize: 26, enabled: true}}, "100.0.0.0/24", 256, 240, nil, "windows"), + Entry("256 v4 ", "testHost", true, []ipamtestutils.Pool{{CIDR: "100.0.0.0/24", BlockSize: 26, Enabled: true}}, "100.0.0.0/24", 256, 240, nil, "windows"), ) // This test is to check if Windows host runs out of IPs from the block with which it has affinity, then IPs from other blocks should not be assigned. @@ -200,7 +199,6 @@ var _ = testutils.E2eDatastoreDescribe("Windows: IPAM tests", testutils.Datastor // Request for another 100 IPs by a Linux host, created initially, will get all 100 IPs. // Request for another 100 IPs by the other Linux host, created initially, will not get all 100 IPs as all the IPs exhausted. Describe("Windows: IPAM AutoAssign should not assign IPs from non-affine block for Windows", func() { - BeforeEach(func() { bc.Clean() deleteAllPoolsWindows() @@ -222,7 +220,7 @@ var _ = testutils.E2eDatastoreDescribe("Windows: IPAM tests", testutils.Datastor }) It("Windows: Should not be able to assign IPs from non-affine block for Windows and Linux", func() { - ipPoolsWindows.pools["100.0.0.0/24"] = pool{cidr: "100.0.0.0/24", enabled: true, blockSize: 26} + ipPoolsWindows.Pools["100.0.0.0/24"] = ipamtestutils.Pool{CIDR: "100.0.0.0/24", Enabled: true, BlockSize: 26} fromPool := cnet.MustParseNetwork("100.0.0.0/24") @@ -357,8 +355,8 @@ var _ = testutils.E2eDatastoreDescribe("Windows: IPAM tests", testutils.Datastor // Call once in order to assign an IP address and create a block. It("Windows: should have assigned an IP address with no error", func() { deleteAllPoolsWindows() - ipPoolsWindows.pools["100.0.0.0/24"] = pool{cidr: "100.0.0.0/24", enabled: true, blockSize: 26} - ipPoolsWindows.pools["200.0.0.0/24"] = pool{cidr: "200.0.0.0/24", enabled: true, blockSize: 26} + ipPoolsWindows.Pools["100.0.0.0/24"] = ipamtestutils.Pool{CIDR: "100.0.0.0/24", Enabled: true, BlockSize: 26} + ipPoolsWindows.Pools["200.0.0.0/24"] = ipamtestutils.Pool{CIDR: "200.0.0.0/24", Enabled: true, BlockSize: 26} ctx := context.WithValue(context.Background(), "windowsHost", "windows") v4ia, _, outErr := ic.AutoAssign(ctx, args) Expect(outErr).NotTo(HaveOccurred()) @@ -375,7 +373,6 @@ var _ = testutils.E2eDatastoreDescribe("Windows: IPAM tests", testutils.Datastor Expect(checkWindowsValidIP(v4ia_next.IPs[0].IP, 26)).To(BeTrue()) Expect(isValidWindowsHandle(bc, ipPoolsWindows, v4ia_next.IPs[0].IP, ctx)).To(BeTrue()) }) - }) Describe("Windows: IPAM AutoAssign from different pools", func() { @@ -402,7 +399,6 @@ var _ = testutils.E2eDatastoreDescribe("Windows: IPAM tests", testutils.Datastor }) It("Windows: Should get an IP from pool1 when explicitly requesting from that pool", func() { - args_1 := AutoAssignArgs{ IntendedUse: v3.IPPoolAllowedUseWorkload, Num4: 1, @@ -485,11 +481,10 @@ var _ = testutils.E2eDatastoreDescribe("Windows: IPAM tests", testutils.Datastor Expect(v4ia_6).ToNot(BeNil()) Expect(len(v4ia_6.IPs)).To(Equal(0)) }) - }) DescribeTable("Windows: AutoAssign: requested IPs vs returned IPs", - func(host string, cleanEnv bool, pools []pool, rsvd *HostReservedAttr, usePool string, inv4, inv6 int, expv4ia, expv6ia *IPAMAssignments, expError error) { + func(host string, cleanEnv bool, pools []ipamtestutils.Pool, rsvd *HostReservedAttr, usePool string, inv4, inv6 int, expv4ia, expv6ia *IPAMAssignments, expError error) { if cleanEnv { bc.Clean() deleteAllPoolsWindows() @@ -499,7 +494,7 @@ var _ = testutils.E2eDatastoreDescribe("Windows: IPAM tests", testutils.Datastor defer setAffinity(ic, false) for _, v := range pools { - ipPoolsWindows.pools[v.cidr] = pool{cidr: v.cidr, enabled: v.enabled, blockSize: v.blockSize} + ipPoolsWindows.Pools[v.CIDR] = ipamtestutils.Pool{CIDR: v.CIDR, Enabled: v.Enabled, BlockSize: v.BlockSize} } // Host must exist before trying to autoassign to it @@ -549,9 +544,9 @@ var _ = testutils.E2eDatastoreDescribe("Windows: IPAM tests", testutils.Datastor }, // Test 1: AutoAssign 256 IPv4, 256 IPv6 - expect 240 IPv4 + IPv6 addresses. - Entry("256 v4 256 v6", "testHost", true, []pool{ - {cidr: "192.168.1.0/24", blockSize: 26, enabled: true}, - {cidr: "fd80:24e2:f998:72d6::/120", blockSize: 122, enabled: true}, + Entry("256 v4 256 v6", "testHost", true, []ipamtestutils.Pool{ + {CIDR: "192.168.1.0/24", BlockSize: 26, Enabled: true}, + {CIDR: "fd80:24e2:f998:72d6::/120", BlockSize: 122, Enabled: true}, }, rsvdAttrWindows, "192.168.1.0/24", 256, 256, &IPAMAssignments{ IPs: make([]cnet.IPNet, 240), @@ -570,7 +565,7 @@ var _ = testutils.E2eDatastoreDescribe("Windows: IPAM tests", testutils.Datastor nil), // Test 2: AutoAssign 257 IPv4, 0 IPv6 - expect 240 IPv4 addresses, no IPv6, and no error. - Entry("257 v4 0 v6", "testHost", true, []pool{{cidr: "192.168.1.0/24", blockSize: 26, enabled: true}}, rsvdAttrWindows, "192.168.1.0/24", 257, 0, + Entry("257 v4 0 v6", "testHost", true, []ipamtestutils.Pool{{CIDR: "192.168.1.0/24", BlockSize: 26, Enabled: true}}, rsvdAttrWindows, "192.168.1.0/24", 257, 0, &IPAMAssignments{ IPs: make([]cnet.IPNet, 240), IPVersion: 4, @@ -581,9 +576,9 @@ var _ = testutils.E2eDatastoreDescribe("Windows: IPAM tests", testutils.Datastor nil, nil), // Test 3: AutoAssign 0 IPv4, 257 IPv6 - expect 240 IPv6 addresses, no IPv4, and no error. - Entry("0 v4 257 v6", "testHost", true, []pool{ - {cidr: "192.168.1.0/24", blockSize: 26, enabled: true}, - {cidr: "fd80:24e2:f998:72d6::/120", blockSize: 122, enabled: true}, + Entry("0 v4 257 v6", "testHost", true, []ipamtestutils.Pool{ + {CIDR: "192.168.1.0/24", BlockSize: 26, Enabled: true}, + {CIDR: "fd80:24e2:f998:72d6::/120", BlockSize: 122, Enabled: true}, }, rsvdAttrWindows, "192.168.1.0/24", 0, 257, nil, &IPAMAssignments{ IPs: make([]cnet.IPNet, 240), @@ -595,12 +590,12 @@ var _ = testutils.E2eDatastoreDescribe("Windows: IPAM tests", testutils.Datastor nil), // Test 4: AutoAssign with invalid HostReserveAttr should return error. - Entry("1 v4 0 v6", "testHost", true, []pool{{cidr: "192.168.1.0/24", blockSize: 26, enabled: true}}, rsvdAttrTooBig, "192.168.1.0/24", 1, 0, nil, nil, ErrNoQualifiedPool), + Entry("1 v4 0 v6", "testHost", true, []ipamtestutils.Pool{{CIDR: "192.168.1.0/24", BlockSize: 26, Enabled: true}}, rsvdAttrTooBig, "192.168.1.0/24", 1, 0, nil, nil, ErrNoQualifiedPool), - Entry("0 v4 1 v6", "testHost", true, []pool{{cidr: "fd80:24e2:f998:72d6::/120", blockSize: 122, enabled: true}}, rsvdAttrTooBig, "fd80:24e2:f998:72d6::/120", 0, 1, nil, nil, ErrNoQualifiedPool), + Entry("0 v4 1 v6", "testHost", true, []ipamtestutils.Pool{{CIDR: "fd80:24e2:f998:72d6::/120", BlockSize: 122, Enabled: true}}, rsvdAttrTooBig, "fd80:24e2:f998:72d6::/120", 0, 1, nil, nil, ErrNoQualifiedPool), // Test 5 AutoAssign 240 IPv4, expect 240 IPv4 and empty IPAMAssingments.Msgs - Entry("240 v4 0 v6", "testHost", true, []pool{{cidr: "192.168.1.0/24", blockSize: 26, enabled: true}}, rsvdAttrWindows, "192.168.1.0/24", 240, 0, + Entry("240 v4 0 v6", "testHost", true, []ipamtestutils.Pool{{CIDR: "192.168.1.0/24", BlockSize: 26, Enabled: true}}, rsvdAttrWindows, "192.168.1.0/24", 240, 0, &IPAMAssignments{ IPs: make([]cnet.IPNet, 240), IPVersion: 4, @@ -623,12 +618,12 @@ func setAffinity(ic Interface, affinity bool) { func deleteAllPoolsWindows() { log.Infof("Windows: Deleting all pools") - ipPoolsWindows.pools = map[string]pool{} + ipPoolsWindows.Pools = map[string]ipamtestutils.Pool{} } func applyPoolWindows(cidr string, enabled bool) { - log.Infof("Windows: Adding pool: %s, enabled: %v", cidr, enabled) - ipPoolsWindows.pools[cidr] = pool{enabled: enabled} + log.Infof("Windows: Adding pool: %s, Enabled: %v", cidr, enabled) + ipPoolsWindows.Pools[cidr] = ipamtestutils.Pool{Enabled: enabled} } // checkWindowsIP() receives an IP and block size and returns bool - @@ -643,7 +638,7 @@ func checkWindowsValidIP(ip net.IP, blockSize uint) bool { var ipBinary uint32 ipBinary = 0 - for i := 0; i < 4; i++ { + for i := range 4 { ipBinary = ipBinary << 8 ipBinary = ipBinary | uint32(ipv4[i]) @@ -657,7 +652,7 @@ func checkWindowsValidIP(ip net.IP, blockSize uint) bool { } // Return boolean after checking if the valid handle is allocated -func isValidWindowsHandle(backend bapi.Client, ipPoolsWindows *ipPoolAccessor, ip net.IP, ctx context.Context) bool { +func isValidWindowsHandle(backend bapi.Client, ipPoolsWindows *ipamtestutils.IPPoolAccessor, ip net.IP, ctx context.Context) bool { c := &ipamClientWindows{ client: backend, pools: *ipPoolsWindows, @@ -687,10 +682,10 @@ func isValidWindowsHandle(backend bapi.Client, ipPoolsWindows *ipPoolAccessor, i attrs := block.Attributes[*attrIdx] // If primary attribute is not nil then it must contain "windows-reserved-IPAM-handle" // Primary attribute will be set only for reserved IPs. - if attrs.AttrPrimary == nil { + if attrs.HandleID == nil { return false } - if *attrs.AttrPrimary == "windows-reserved-ipam-handle" { + if *attrs.HandleID == "windows-reserved-ipam-handle" { return true } } diff --git a/libcalico-go/lib/ipam/ipamtestutils/ipam_utils.go b/libcalico-go/lib/ipam/ipamtestutils/ipam_utils.go new file mode 100644 index 00000000000..b8c176a8eca --- /dev/null +++ b/libcalico-go/lib/ipam/ipamtestutils/ipam_utils.go @@ -0,0 +1,166 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// 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 ipamtestutils + +import ( + "context" + "fmt" + "sort" + + . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + log "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" + bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" + "github.com/projectcalico/calico/libcalico-go/lib/backend/model" + cnet "github.com/projectcalico/calico/libcalico-go/lib/net" + "github.com/projectcalico/calico/libcalico-go/lib/options" +) + +// Pool holds test configuration for an IP pool. +type Pool struct { + CIDR string + BlockSize int + Enabled bool + NodeSelector string + NamespaceSelector string + AllowedUses []v3.IPPoolAllowedUse + AssignmentMode v3.AssignmentMode +} + +// IPPoolAccessor is a mock IP pool accessor for IPAM tests. +type IPPoolAccessor struct { + Pools map[string]Pool +} + +func (i *IPPoolAccessor) GetAllPools(ctx context.Context) ([]v3.IPPool, error) { + poolNames := make([]string, 0) + for p := range i.Pools { + poolNames = append(poolNames, p) + } + return i.getPools(poolNames, 0, "GetAllPools"), nil +} + +func (i *IPPoolAccessor) GetEnabledPools(ctx context.Context, ipVersion int) ([]v3.IPPool, error) { + poolNames := make([]string, 0) + for p, e := range i.Pools { + if e.Enabled { + poolNames = append(poolNames, p) + } + } + return i.getPools(poolNames, ipVersion, "GetEnabledPools"), nil +} + +func (i *IPPoolAccessor) getPools(poolNames []string, ipVersion int, caller string) []v3.IPPool { + sort.Strings(poolNames) + + pools := make([]v3.IPPool, 0) + automatic := v3.Automatic + var poolsToPrint []string + for _, p := range poolNames { + c := cnet.MustParseCIDR(p) + if (ipVersion == 0) || (c.Version() == ipVersion) { + pool := v3.IPPool{Spec: v3.IPPoolSpec{ + CIDR: p, + NodeSelector: i.Pools[p].NodeSelector, + NamespaceSelector: i.Pools[p].NamespaceSelector, + AllowedUses: i.Pools[p].AllowedUses, + AssignmentMode: &automatic, + }} + if len(pool.Spec.AllowedUses) == 0 { + pool.Spec.AllowedUses = []v3.IPPoolAllowedUse{v3.IPPoolAllowedUseWorkload, v3.IPPoolAllowedUseTunnel} + } + if i.Pools[p].BlockSize == 0 { + if c.Version() == 4 { + pool.Spec.BlockSize = 26 + } else { + pool.Spec.BlockSize = 122 + } + } else { + pool.Spec.BlockSize = i.Pools[p].BlockSize + } + pools = append(pools, pool) + + poolsToPrint = append(poolsToPrint, fmt.Sprintf("{%s(%v) %q %v}", + p, pool.Spec.BlockSize, pool.Spec.NodeSelector, i.Pools[p].AllowedUses)) + } + } + + log.Debugf("Mock %v returns: %v", caller, poolsToPrint) + + return pools +} + +// FakeReservations is a mock IP reservation lister that returns no reservations. +type FakeReservations struct { + Reservations []v3.IPReservation +} + +func (f *FakeReservations) List(ctx context.Context, opts options.ListOptions) (*v3.IPReservationList, error) { + return &v3.IPReservationList{Items: f.Reservations}, nil +} + +// ApplyNode creates or updates a node in the backend for tests. +func ApplyNode(c bapi.Client, kc *kubernetes.Clientset, host string, labels map[string]string) { + ExpectWithOffset(1, TryApplyNode(c, kc, host, labels)).NotTo(HaveOccurred()) +} + +// TryApplyNode creates or updates a node, returning any error. +func TryApplyNode(c bapi.Client, kc *kubernetes.Clientset, host string, labels map[string]string) error { + if kc != nil { + n := corev1.Node{ + TypeMeta: metav1.TypeMeta{ + Kind: "Node", + APIVersion: "v1", + }, + } + n.Name = host + n.Labels = labels + + _, err := kc.CoreV1().Nodes().Create(context.Background(), &n, metav1.CreateOptions{}) + if err != nil { + if kerrors.IsAlreadyExists(err) { + oldNode, _ := kc.CoreV1().Nodes().Get(context.Background(), host, metav1.GetOptions{}) + oldNode.Labels = labels + _, err = kc.CoreV1().Nodes().Update(context.Background(), oldNode, metav1.UpdateOptions{}) + if err != nil { + return err + } + } else { + return err + } + } + log.WithField("node", host).WithError(err).Info("node applied") + } else { + _, err := c.Apply(context.Background(), &model.KVPair{ + Key: model.ResourceKey{Name: host, Kind: internalapi.KindNode}, + Value: internalapi.Node{ + ObjectMeta: metav1.ObjectMeta{Labels: labels}, + Spec: internalapi.NodeSpec{OrchRefs: []internalapi.OrchRef{ + {NodeName: host, Orchestrator: "k8s"}, + }}, + }, + }) + if err != nil { + return err + } + } + return nil +} diff --git a/libcalico-go/lib/ipam/ipamtestutils/raw_block_affinity.go b/libcalico-go/lib/ipam/ipamtestutils/raw_block_affinity.go index f7624ea6c9a..268403fdee8 100644 --- a/libcalico-go/lib/ipam/ipamtestutils/raw_block_affinity.go +++ b/libcalico-go/lib/ipam/ipamtestutils/raw_block_affinity.go @@ -22,7 +22,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" crclient "sigs.k8s.io/controller-runtime/pkg/client" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" + "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s" "github.com/projectcalico/calico/libcalico-go/lib/names" cnet "github.com/projectcalico/calico/libcalico-go/lib/net" ) @@ -36,20 +38,46 @@ func CreateUnlabeledBlockAffinity(ctx context.Context, cclient crclient.Client, return err } name := fmt.Sprintf("%s-%s", host, names.CIDRToName(*ipn)) - ba := &libapiv3.BlockAffinity{ + + // Determine which API group to use. + cfg, err := apiconfig.LoadClientConfig("") + if err != nil { + return err + } + v3CRD := k8s.UsingV3CRDs(&cfg.Spec) + + if !v3CRD { + // Use the crd.projectcalico.org/v1 API group. + ba := &internalapi.BlockAffinity{ + TypeMeta: metav1.TypeMeta{ + Kind: internalapi.KindBlockAffinity, + APIVersion: "crd.projectcalico.org/v1", + }, + ObjectMeta: metav1.ObjectMeta{Name: name}, + Spec: internalapi.BlockAffinitySpec{ + State: string(apiv3.StateConfirmed), + Node: host, + Type: "host", + CIDR: cidr, + Deleted: "false", + }, + } + return cclient.Create(ctx, ba) + } + + // Use the projectcalico.org/v3 API group. + ba := &apiv3.BlockAffinity{ TypeMeta: metav1.TypeMeta{ - Kind: libapiv3.KindBlockAffinity, - APIVersion: "crd.projectcalico.org/v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, + Kind: apiv3.KindBlockAffinity, + APIVersion: "projectcalico.org/v3", }, - Spec: libapiv3.BlockAffinitySpec{ - State: string(apiv3.StateConfirmed), + ObjectMeta: metav1.ObjectMeta{Name: name}, + Spec: apiv3.BlockAffinitySpec{ + State: apiv3.StateConfirmed, Node: host, Type: "host", CIDR: cidr, - Deleted: "false", + Deleted: false, }, } return cclient.Create(ctx, ba) diff --git a/libcalico-go/lib/ipam/namespace_selector_test.go b/libcalico-go/lib/ipam/namespace_selector_test.go index 3c915dff80d..4c61414a91c 100644 --- a/libcalico-go/lib/ipam/namespace_selector_test.go +++ b/libcalico-go/lib/ipam/namespace_selector_test.go @@ -15,7 +15,7 @@ package ipam import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" corev1 "k8s.io/api/core/v1" diff --git a/libcalico-go/lib/ipam/pools.go b/libcalico-go/lib/ipam/pools.go index 4a673e4eaf7..967b04d63bb 100644 --- a/libcalico-go/lib/ipam/pools.go +++ b/libcalico-go/lib/ipam/pools.go @@ -19,7 +19,7 @@ import ( v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" corev1 "k8s.io/api/core/v1" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/selector" ) @@ -33,7 +33,7 @@ type PoolAccessorInterface interface { // SelectsNode determines whether or not the IPPool's nodeSelector // matches the labels on the given node. -func SelectsNode(pool v3.IPPool, n libapiv3.Node) (bool, error) { +func SelectsNode(pool v3.IPPool, n internalapi.Node) (bool, error) { // No node selector means that the pool matches the node. if len(pool.Spec.NodeSelector) == 0 { return true, nil diff --git a/libcalico-go/lib/ipam/randomblock_test.go b/libcalico-go/lib/ipam/randomblock_test.go index e82d3ac64c1..f434c85cd4a 100644 --- a/libcalico-go/lib/ipam/randomblock_test.go +++ b/libcalico-go/lib/ipam/randomblock_test.go @@ -18,8 +18,7 @@ import ( "math/big" "net" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" ) diff --git a/libcalico-go/lib/ipam/vmipam/ipam_kubevirt.go b/libcalico-go/lib/ipam/vmipam/ipam_kubevirt.go new file mode 100644 index 00000000000..e193b35ebce --- /dev/null +++ b/libcalico-go/lib/ipam/vmipam/ipam_kubevirt.go @@ -0,0 +1,243 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. +// +// 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 vmipam + +import ( + "context" + "errors" + "fmt" + + log "github.com/sirupsen/logrus" + + cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" + "github.com/projectcalico/calico/libcalico-go/lib/hash" + "github.com/projectcalico/calico/libcalico-go/lib/ipam" + cnet "github.com/projectcalico/calico/libcalico-go/lib/net" +) + +// Error types for EnsureActiveVMOwnerAttrs +var ( + // ErrAlternateOwnerEmpty is returned when AlternateOwnerAttrs is empty, + // indicating the target pod was deleted before the promotion could complete. + ErrAlternateOwnerEmpty = errors.New("AlternateOwnerAttrs is empty") + + // ErrAlternateOwnerMismatch is returned when AlternateOwnerAttrs doesn't match + // the expected target owner, indicating an unexpected state. + ErrAlternateOwnerMismatch = errors.New("AlternateOwnerAttrs doesn't match expected target owner") +) + +// CreateVMHandleID generates a consistent handle ID for a KubeVirt VM allocation. +// This ensures both CNI plugin and Felix use the same handle format. +// +// The handle ID is constructed with a prefix and suffix, and is length-limited to 128 +// characters. If the full ID would exceed this limit, the suffix is hashed and truncated. +// +// Parameters: +// - networkName: The Calico network name (from annotation "projectcalico.org/network"). +// If empty, defaults to "k8s-pod-network". +// - namespace: The Kubernetes namespace of the VM. +// - vmName: The name of the VirtualMachine (VM and VMI share the same name). +// +// Returns: +// - Handle ID in format: "{networkName}.vmi.{namespace}.{vmName}" (length-limited to 128 chars) +// +// Examples: +// - CreateVMHandleID("", "default", "vm1") -> "k8s-pod-network.vmi.default.vm1" +// - CreateVMHandleID("multus-net1", "default", "vm1") -> "multus-net1.vmi.default.vm1" +// - CreateVMHandleID("net", "ns", "very-long-name...") -> "net.vmi._" (if exceeds 128 chars) +func CreateVMHandleID(networkName, namespace, vmName string) string { + if networkName == "" { + networkName = "k8s-pod-network" + } + + // Create suffix from namespace and VM name. + // Use dot separator instead of slash to ensure valid Kubernetes resource name. + // Kubernetes namespace names follow RFC 1123 DNS label rules which do not allow dots, + // so the first '.' after 'vmi.' is always the namespace/vmName boundary. + // If the namespace itself is "vmi", the boundary is the second '.' after 'vmi.'. + // We don't need to escape dots in vmName. + suffix := fmt.Sprintf("%s.%s", namespace, vmName) + + // Build prefix: networkName.vmi. + prefix := fmt.Sprintf("%s.vmi.", networkName) + + // Use GetLengthLimitedID with max length 128 + // This will keep the suffix unhashed if it fits, otherwise hash and truncate + return hash.GetLengthLimitedID(prefix, suffix, 128) +} + +// EnsureActiveVMOwnerAttrs atomically verifies the target pod is in AlternateOwnerAttrs +// and promotes it to ActiveOwnerAttrs for all IPs allocated to a migrated VMI. +// +// This is called by Felix when KubeVirt live migration completes to transfer +// IP ownership to the target pod. Unlike a strict swap, this function only verifies +// the target pod is registered in AlternateOwnerAttrs - the source pod may have +// already been deleted and its attributes cleared from ActiveOwnerAttrs. +// +// This function is idempotent - it can be safely retried. If an IP already has the target +// as the active owner, it will be skipped (success). This handles partial success scenarios +// where some IPs were swapped successfully before an error occurred. +// +// The function operates on all IPs allocated to the VMI handle, ensuring consistent +// ownership transfer across all IP addresses (IPv4 and IPv6) associated with the VMI. +// +// Parameters: +// - ctx: Context for the operation +// - ipamClient: IPAM client interface for performing the operations +// - networkName: The Calico network name (empty string defaults to "k8s-pod-network") +// - namespace: The Kubernetes namespace of the VMI +// - vmiName: The name of the VirtualMachineInstance +// - targetPodName: The name of the target pod (for verification) +// +// Returns: +// - nil: Success, target is active owner for all IPs (already was or just swapped) +// - ErrAlternateOwnerEmpty: AlternateOwnerAttrs is empty and target is not active (target pod deleted) +// - ErrAlternateOwnerMismatch: AlternateOwnerAttrs contains a different pod than expected +// - other error: Transient failure (network, CAS conflict, no IPs found, etc.) - caller should retry +// +// After a successful operation, the L3 route resolver will automatically update +// IPIP/VXLAN routes based on the new ActiveOwnerAttrs, directing traffic to the +// target node. +func EnsureActiveVMOwnerAttrs( + ctx context.Context, + ipamClient ipam.Interface, + networkName string, + namespace string, + vmiName string, + targetPodName string, +) error { + // Step 1: Generate handleID using the same logic as CNI plugin + handleID := CreateVMHandleID(networkName, namespace, vmiName) + + // Step 2: Get all IPs allocated to this handle + ips, err := ipamClient.IPsByHandle(ctx, handleID) + if err != nil { + return fmt.Errorf("failed to get IPs for handle %s: %w", handleID, err) + } + + if len(ips) == 0 { + return fmt.Errorf("no IPs found for handle %s", handleID) + } + + // Step 3: Build expected target owner + expectedTargetOwner := &ipam.AttributeOwner{ + Namespace: namespace, + Name: targetPodName, + } + + var lastErr error + for _, ip := range ips { + if err := verifyAndSwapSingleIP(ctx, ipamClient, ip, handleID, expectedTargetOwner); err != nil { + lastErr = err + log.WithError(err).WithField("ip", ip).Warning("Failed to swap owner for IP") + + // If this is a non-retryable error, return immediately + if errors.Is(err, ErrAlternateOwnerEmpty) || errors.Is(err, ErrAlternateOwnerMismatch) { + return err + } + // For transient errors, continue to try other IPs + } + } + + // If we had any transient errors, return the last one for retry + // On retry, already-swapped IPs will be detected as already correct and skipped + return lastErr +} + +const swapRetries = 5 + +// verifyAndSwapSingleIP reads the current owner attributes, verifies the target is +// in AlternateOwnerAttrs, and atomically promotes it to ActiveOwnerAttrs. +// +// Both active and alternate owners are verified as preconditions to prevent +// resurrecting a deleted owner due to races (e.g., source pod deleted between +// our read and the CAS write). If a precondition fails, the function re-reads +// the current state and retries with updated preconditions. +func verifyAndSwapSingleIP( + ctx context.Context, + ipamClient ipam.Interface, + ip cnet.IP, + handleID string, + expectedTargetOwner *ipam.AttributeOwner, +) error { + for attempt := 0; attempt < swapRetries; attempt++ { + allocAttr, err := ipamClient.GetAssignmentAttributes(ctx, ip) + if err != nil { + return fmt.Errorf("failed to get assignment attributes for IP %s: %w", ip, err) + } + if allocAttr == nil { + return fmt.Errorf("IP %s is not assigned", ip) + } + + // Verify handle ID matches + if allocAttr.HandleID == nil || *allocAttr.HandleID != handleID { + return fmt.Errorf("IP %s is not assigned to handle %s (current handle: %v)", + ip, handleID, allocAttr.HandleID) + } + + // IDEMPOTENCY: If target is already the active owner, nothing to do + if expectedTargetOwner.Matches(allocAttr.ActiveOwnerAttrs) { + return nil + } + + // Check if AlternateOwnerAttrs is empty + if len(allocAttr.AlternateOwnerAttrs) == 0 { + return fmt.Errorf("%w for IP %s", ErrAlternateOwnerEmpty, ip) + } + + // Verify AlternateOwnerAttrs matches expected target owner + if !expectedTargetOwner.Matches(allocAttr.AlternateOwnerAttrs) { + return fmt.Errorf("%w: expected %v, got namespace=%s pod=%s for IP %s", + ErrAlternateOwnerMismatch, + expectedTargetOwner, + allocAttr.AlternateOwnerAttrs[ipam.AttributeNamespace], + allocAttr.AlternateOwnerAttrs[ipam.AttributePod], + ip) + } + + // Build updates and preconditions based on the current state. + // Both active and alternate are verified to prevent resurrecting a deleted owner. + updates := &ipam.OwnerAttributeUpdates{ + ActiveOwnerAttrs: allocAttr.AlternateOwnerAttrs, // Target becomes active + } + preconditions := &ipam.OwnerAttributePreconditions{ + ExpectedAlternateOwner: expectedTargetOwner, + } + + // ActiveOwnerAttrs may be empty if the source pod was already deleted and its + // attributes were cleared by CNI cleanup before this swap runs. + if len(allocAttr.ActiveOwnerAttrs) > 0 { + updates.AlternateOwnerAttrs = allocAttr.ActiveOwnerAttrs // Source becomes alternate + preconditions.ExpectedActiveOwner = &ipam.AttributeOwner{ + Namespace: allocAttr.ActiveOwnerAttrs[ipam.AttributeNamespace], + Name: allocAttr.ActiveOwnerAttrs[ipam.AttributePod], + } + } else { + updates.ClearAlternateOwner = true // No source to swap in, clear alternate + preconditions.VerifyActiveOwnerEmpty = true + } + + err = ipamClient.SetOwnerAttributes(ctx, ip, handleID, updates, preconditions) + if err != nil { + if _, ok := err.(cerrors.ErrorResourceUpdateConflict); ok { + log.WithField("ip", ip).Debug("Precondition conflict during swap, retrying") + continue + } + return fmt.Errorf("failed to promote target to active owner for IP %s: %w", ip, err) + } + return nil + } + return fmt.Errorf("max retries (%d) exceeded for IP %s", swapRetries, ip) +} diff --git a/libcalico-go/lib/ipam/vmipam/ipam_kubevirt_test.go b/libcalico-go/lib/ipam/vmipam/ipam_kubevirt_test.go new file mode 100644 index 00000000000..15157a9dcbd --- /dev/null +++ b/libcalico-go/lib/ipam/vmipam/ipam_kubevirt_test.go @@ -0,0 +1,283 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// 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 vmipam + +import ( + "context" + "errors" + "fmt" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "k8s.io/client-go/kubernetes" + + "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" + "github.com/projectcalico/calico/libcalico-go/lib/backend" + bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" + "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s" + "github.com/projectcalico/calico/libcalico-go/lib/ipam" + "github.com/projectcalico/calico/libcalico-go/lib/ipam/ipamtestutils" + cnet "github.com/projectcalico/calico/libcalico-go/lib/net" + "github.com/projectcalico/calico/libcalico-go/lib/testutils" +) + +func TestVMIPAM(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "VM IPAM Suite") +} + +var _ = testutils.E2eDatastoreDescribe("VM IPAM tests", testutils.DatastoreAll, func(config apiconfig.CalicoAPIConfig) { + var bc bapi.Client + var ic ipam.Interface + var kc *kubernetes.Clientset + var ipPools *ipamtestutils.IPPoolAccessor + + BeforeEach(func() { + var err error + config.Spec.K8sClientQPS = 500 + bc, err = backend.NewClient(config) + Expect(err).NotTo(HaveOccurred()) + ipPools = &ipamtestutils.IPPoolAccessor{Pools: map[string]ipamtestutils.Pool{}} + ic = ipam.NewIPAMClient(bc, ipPools, &ipamtestutils.FakeReservations{}) + + if config.Spec.DatastoreType == "kubernetes" { + kc = bc.(*k8s.KubeClient).ClientSet + } + }) + + Context("EnsureActiveVMOwnerAttrs", func() { + var hostname string + var handle string + var allocatedIPs []cnet.IP + + BeforeEach(func() { + hostname = "test-host-swap" + handle = "k8s-pod-network.vmi.test-ns.test-vm" + + Expect(bc.Clean()).To(Succeed()) + ipPools.Pools = map[string]ipamtestutils.Pool{} + ipPools.Pools["10.0.0.0/24"] = ipamtestutils.Pool{Enabled: true} + ipPools.Pools["fd80:24e2:f998:72d6::/120"] = ipamtestutils.Pool{Enabled: true} + ipamtestutils.ApplyNode(bc, kc, hostname, nil) + + ctx := context.Background() + + // Allocate dual-stack IPs for the VMI handle + v4ia, v6ia, err := ic.AutoAssign(ctx, ipam.AutoAssignArgs{ + Num4: 1, + Num6: 1, + HandleID: &handle, + Hostname: hostname, + IntendedUse: v3.IPPoolAllowedUseWorkload, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(v4ia.IPs).To(HaveLen(1)) + Expect(v6ia.IPs).To(HaveLen(1)) + + allocatedIPs = []cnet.IP{ + {IP: v4ia.IPs[0].IP}, + {IP: v6ia.IPs[0].IP}, + } + + // Set source pod as active owner and target pod as alternate owner on all IPs + for _, ip := range allocatedIPs { + err := ic.SetOwnerAttributes(ctx, ip, handle, &ipam.OwnerAttributeUpdates{ + ActiveOwnerAttrs: map[string]string{ + ipam.AttributePod: "virt-launcher-source", + ipam.AttributeNamespace: "test-ns", + }, + AlternateOwnerAttrs: map[string]string{ + ipam.AttributePod: "virt-launcher-target", + ipam.AttributeNamespace: "test-ns", + }, + }, nil) + Expect(err).NotTo(HaveOccurred()) + } + }) + + It("should promote target to active owner on all IPs", func() { + ctx := context.Background() + + err := EnsureActiveVMOwnerAttrs(ctx, ic, "", "test-ns", "test-vm", "virt-launcher-target") + Expect(err).NotTo(HaveOccurred()) + + for _, ip := range allocatedIPs { + allocAttr, err := ic.GetAssignmentAttributes(ctx, ip) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.ActiveOwnerAttrs[ipam.AttributePod]).To(Equal("virt-launcher-target")) + Expect(allocAttr.ActiveOwnerAttrs[ipam.AttributeNamespace]).To(Equal("test-ns")) + // Source moves to alternate + Expect(allocAttr.AlternateOwnerAttrs[ipam.AttributePod]).To(Equal("virt-launcher-source")) + Expect(allocAttr.AlternateOwnerAttrs[ipam.AttributeNamespace]).To(Equal("test-ns")) + } + }) + + It("should be idempotent - second call succeeds when target is already active", func() { + ctx := context.Background() + + err := EnsureActiveVMOwnerAttrs(ctx, ic, "", "test-ns", "test-vm", "virt-launcher-target") + Expect(err).NotTo(HaveOccurred()) + + // Call again - should succeed (idempotent) + err = EnsureActiveVMOwnerAttrs(ctx, ic, "", "test-ns", "test-vm", "virt-launcher-target") + Expect(err).NotTo(HaveOccurred()) + + for _, ip := range allocatedIPs { + allocAttr, err := ic.GetAssignmentAttributes(ctx, ip) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.ActiveOwnerAttrs[ipam.AttributePod]).To(Equal("virt-launcher-target")) + } + }) + + It("should return ErrAlternateOwnerEmpty when alternate is cleared before swap", func() { + ctx := context.Background() + + // Clear alternate owner on all IPs (simulates target pod deleted before swap) + for _, ip := range allocatedIPs { + err := ic.SetOwnerAttributes(ctx, ip, handle, &ipam.OwnerAttributeUpdates{ + ClearAlternateOwner: true, + }, nil) + Expect(err).NotTo(HaveOccurred()) + } + + err := EnsureActiveVMOwnerAttrs(ctx, ic, "", "test-ns", "test-vm", "virt-launcher-target") + Expect(err).To(HaveOccurred()) + Expect(errors.Is(err, ErrAlternateOwnerEmpty)).To(BeTrue(), + fmt.Sprintf("Expected ErrAlternateOwnerEmpty, got: %v", err)) + }) + + It("should return ErrAlternateOwnerMismatch when alternate is a different pod", func() { + ctx := context.Background() + + // Overwrite alternate with a different pod + for _, ip := range allocatedIPs { + err := ic.SetOwnerAttributes(ctx, ip, handle, &ipam.OwnerAttributeUpdates{ + AlternateOwnerAttrs: map[string]string{ + ipam.AttributePod: "virt-launcher-unexpected", + ipam.AttributeNamespace: "test-ns", + }, + }, nil) + Expect(err).NotTo(HaveOccurred()) + } + + err := EnsureActiveVMOwnerAttrs(ctx, ic, "", "test-ns", "test-vm", "virt-launcher-target") + Expect(err).To(HaveOccurred()) + Expect(errors.Is(err, ErrAlternateOwnerMismatch)).To(BeTrue(), + fmt.Sprintf("Expected ErrAlternateOwnerMismatch, got: %v", err)) + }) + + It("should fail when no IPs are allocated to the handle", func() { + ctx := context.Background() + + // Release all IPs + err := ic.ReleaseByHandle(ctx, handle) + Expect(err).NotTo(HaveOccurred()) + + err = EnsureActiveVMOwnerAttrs(ctx, ic, "", "test-ns", "test-vm", "virt-launcher-target") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to get IPs for handle")) + }) + + It("should succeed when source was already deleted but target is in alternate", func() { + ctx := context.Background() + + // Clear active owner (simulates source pod deleted and its attrs cleared) + for _, ip := range allocatedIPs { + err := ic.SetOwnerAttributes(ctx, ip, handle, &ipam.OwnerAttributeUpdates{ + ClearActiveOwner: true, + }, nil) + Expect(err).NotTo(HaveOccurred()) + } + + // Swap should still succeed - it only verifies alternate + err := EnsureActiveVMOwnerAttrs(ctx, ic, "", "test-ns", "test-vm", "virt-launcher-target") + Expect(err).NotTo(HaveOccurred()) + + for _, ip := range allocatedIPs { + allocAttr, err := ic.GetAssignmentAttributes(ctx, ip) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.ActiveOwnerAttrs[ipam.AttributePod]).To(Equal("virt-launcher-target")) + // Active was empty, so alternate should now be empty (nil map swapped in) + Expect(allocAttr.AlternateOwnerAttrs).To(BeNil()) + } + }) + + It("should work with custom network name", func() { + ctx := context.Background() + + // Allocate IPs with a custom network handle + customHandle := "multus-net1.vmi.test-ns.test-vm" + v4ia, _, err := ic.AutoAssign(ctx, ipam.AutoAssignArgs{ + Num4: 1, + HandleID: &customHandle, + Hostname: hostname, + IntendedUse: v3.IPPoolAllowedUseWorkload, + }) + Expect(err).NotTo(HaveOccurred()) + customIP := cnet.IP{IP: v4ia.IPs[0].IP} + + // Set owners + err = ic.SetOwnerAttributes(ctx, customIP, customHandle, &ipam.OwnerAttributeUpdates{ + ActiveOwnerAttrs: map[string]string{ + ipam.AttributePod: "virt-launcher-source", + ipam.AttributeNamespace: "test-ns", + }, + AlternateOwnerAttrs: map[string]string{ + ipam.AttributePod: "virt-launcher-target", + ipam.AttributeNamespace: "test-ns", + }, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + err = EnsureActiveVMOwnerAttrs(ctx, ic, "multus-net1", "test-ns", "test-vm", "virt-launcher-target") + Expect(err).NotTo(HaveOccurred()) + + allocAttr, err := ic.GetAssignmentAttributes(ctx, customIP) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.ActiveOwnerAttrs[ipam.AttributePod]).To(Equal("virt-launcher-target")) + }) + + It("should handle partial success - already-swapped IPs are skipped on retry", func() { + ctx := context.Background() + + // Manually swap only the first IP + firstIP := allocatedIPs[0] + err := ic.SetOwnerAttributes(ctx, firstIP, handle, &ipam.OwnerAttributeUpdates{ + ActiveOwnerAttrs: map[string]string{ + ipam.AttributePod: "virt-launcher-target", + ipam.AttributeNamespace: "test-ns", + }, + AlternateOwnerAttrs: map[string]string{ + ipam.AttributePod: "virt-launcher-source", + ipam.AttributeNamespace: "test-ns", + }, + }, nil) + Expect(err).NotTo(HaveOccurred()) + + // Now call the function - first IP should be skipped, second IP should be swapped + err = EnsureActiveVMOwnerAttrs(ctx, ic, "", "test-ns", "test-vm", "virt-launcher-target") + Expect(err).NotTo(HaveOccurred()) + + // Both IPs should now have target as active + for _, ip := range allocatedIPs { + allocAttr, err := ic.GetAssignmentAttributes(ctx, ip) + Expect(err).NotTo(HaveOccurred()) + Expect(allocAttr.ActiveOwnerAttrs[ipam.AttributePod]).To(Equal("virt-launcher-target")) + } + }) + }) +}) diff --git a/libcalico-go/lib/jitter/jitter_suite_test.go b/libcalico-go/lib/jitter/jitter_suite_test.go index 265dc3f758b..c945c34c5c1 100644 --- a/libcalico-go/lib/jitter/jitter_suite_test.go +++ b/libcalico-go/lib/jitter/jitter_suite_test.go @@ -17,9 +17,8 @@ package jitter import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestJitter(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/jitter_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Jitter Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/jitter_suite.xml" + ginkgo.RunSpecs(t, "Jitter Suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/jitter/jittered_ticker_test.go b/libcalico-go/lib/jitter/jittered_ticker_test.go index d95764dc926..31afcdffb33 100644 --- a/libcalico-go/lib/jitter/jittered_ticker_test.go +++ b/libcalico-go/lib/jitter/jittered_ticker_test.go @@ -18,7 +18,7 @@ import ( "fmt" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/sirupsen/logrus" ) @@ -39,12 +39,12 @@ var _ = Describe("Real 20ms + 10ms Ticker", func() { now := time.Now() duration := now.Sub(startTime) Expect(duration).To(BeNumerically(">=", 20*time.Millisecond)) - }, 1) + }) It("should produce longer and shorter ticks", func() { lastTime := startTime foundLT5 := false foundGT5 := false - for i := 0; i < 40; i++ { + for range 40 { <-ticker.C now := time.Now() duration := time.Now().Sub(lastTime) @@ -61,7 +61,7 @@ var _ = Describe("Real 20ms + 10ms Ticker", func() { } Expect(foundLT5).To(BeTrue()) Expect(foundGT5).To(BeTrue()) - }, 1) + }) }) var _ = Describe("Delay calculation", func() { @@ -70,7 +70,7 @@ var _ = Describe("Delay calculation", func() { ticker = NewTicker(20*time.Millisecond, 10*time.Millisecond) ticker.Stop() }) - for i := 0; i < 10; i++ { + for i := range 10 { It(fmt.Sprintf("should tick before max delay, trial: %v", i), func() { duration := ticker.calculateDelay() Expect(duration).To(BeNumerically("<=", 30*time.Millisecond)) @@ -84,7 +84,7 @@ var _ = Describe("Delay calculation", func() { It("should produce longer and shorter ticks", func() { foundLT5 := false foundGT5 := false - for i := 0; i < 40; i++ { + for range 40 { duration := ticker.calculateDelay() logrus.WithField("duration", duration).Debug("Tick") if duration < 25*time.Millisecond { @@ -98,7 +98,7 @@ var _ = Describe("Delay calculation", func() { } Expect(foundLT5).To(BeTrue()) Expect(foundGT5).To(BeTrue()) - }, 1) + }) }) var _ = Describe("Ticker constructor", func() { diff --git a/libcalico-go/lib/kubevirt/client.go b/libcalico-go/lib/kubevirt/client.go new file mode 100644 index 00000000000..05f5e1d3fd7 --- /dev/null +++ b/libcalico-go/lib/kubevirt/client.go @@ -0,0 +1,52 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. +// +// 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. + +// This file provides utilities for creating KubeVirt clients. +package kubevirt + +import ( + "fmt" + + "k8s.io/client-go/rest" + kubevirtcorev1 "kubevirt.io/client-go/kubevirt/typed/core/v1" +) + +// NewVirtClient creates a VirtClientInterface from a rest.Config. +func NewVirtClient(restConfig *rest.Config) (VirtClientInterface, error) { + kvClient, err := kubevirtcorev1.NewForConfig(restConfig) + if err != nil { + return nil, fmt.Errorf("cannot obtain KubeVirt client: %w", err) + } + return &virtClientAdapter{client: kvClient}, nil +} + +// virtClientAdapter adapts the typed KubeVirt client to our VirtClientInterface. +type virtClientAdapter struct { + client kubevirtcorev1.KubevirtV1Interface +} + +// VirtualMachineInstance implements VirtClientInterface. +func (v *virtClientAdapter) VirtualMachineInstance(namespace string) VMIInterface { + return v.client.VirtualMachineInstances(namespace) +} + +// VirtualMachine implements VirtClientInterface. +func (v *virtClientAdapter) VirtualMachine(namespace string) VMInterface { + return v.client.VirtualMachines(namespace) +} + +// VirtualMachineInstanceMigration implements VirtClientInterface. +func (v *virtClientAdapter) VirtualMachineInstanceMigration(namespace string) VMIMInterface { + return v.client.VirtualMachineInstanceMigrations(namespace) +} diff --git a/libcalico-go/lib/kubevirt/fake/fake.go b/libcalico-go/lib/kubevirt/fake/fake.go new file mode 100644 index 00000000000..1793f83245f --- /dev/null +++ b/libcalico-go/lib/kubevirt/fake/fake.go @@ -0,0 +1,323 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. +// +// 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 fake + +import ( + "context" + "sync" + + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + kubevirtv1 "kubevirt.io/api/core/v1" + + "github.com/projectcalico/calico/libcalico-go/lib/kubevirt" +) + +// FakeVirtClient is a fake implementation of VirtClientInterface for testing. +type FakeVirtClient struct { + mu sync.RWMutex + vmis map[string]map[string]*kubevirtv1.VirtualMachineInstance // namespace -> name -> VMI + vms map[string]map[string]*kubevirtv1.VirtualMachine // namespace -> name -> VM + migrations map[string]map[string]*kubevirtv1.VirtualMachineInstanceMigration // namespace -> name -> VMIM +} + +// NewFakeVirtClient creates a new fake VirtClient for testing. +func NewFakeVirtClient() *FakeVirtClient { + return &FakeVirtClient{ + vmis: make(map[string]map[string]*kubevirtv1.VirtualMachineInstance), + vms: make(map[string]map[string]*kubevirtv1.VirtualMachine), + migrations: make(map[string]map[string]*kubevirtv1.VirtualMachineInstanceMigration), + } +} + +// VirtualMachineInstance implements VirtClientInterface. +func (f *FakeVirtClient) VirtualMachineInstance(namespace string) kubevirt.VMIInterface { + return &fakeVMIInterface{ + client: f, + namespace: namespace, + } +} + +// VirtualMachine implements VirtClientInterface. +func (f *FakeVirtClient) VirtualMachine(namespace string) kubevirt.VMInterface { + return &fakeVMInterface{ + client: f, + namespace: namespace, + } +} + +// VirtualMachineInstanceMigration implements VirtClientInterface. +func (f *FakeVirtClient) VirtualMachineInstanceMigration(namespace string) kubevirt.VMIMInterface { + return &fakeVMIMInterface{ + client: f, + namespace: namespace, + } +} + +// AddVMI adds a VMI to the fake client. +func (f *FakeVirtClient) AddVMI(vmi *kubevirtv1.VirtualMachineInstance) { + f.mu.Lock() + defer f.mu.Unlock() + + if f.vmis[vmi.Namespace] == nil { + f.vmis[vmi.Namespace] = make(map[string]*kubevirtv1.VirtualMachineInstance) + } + f.vmis[vmi.Namespace][vmi.Name] = vmi.DeepCopy() +} + +// DeleteVMI removes a VMI from the fake client. +func (f *FakeVirtClient) DeleteVMI(namespace, name string) { + f.mu.Lock() + defer f.mu.Unlock() + + if f.vmis[namespace] != nil { + delete(f.vmis[namespace], name) + } +} + +// UpdateVMI updates a VMI in the fake client. +func (f *FakeVirtClient) UpdateVMI(vmi *kubevirtv1.VirtualMachineInstance) { + f.AddVMI(vmi) // AddVMI already does deep copy +} + +// AddVM adds a VM to the fake client. +func (f *FakeVirtClient) AddVM(vm *kubevirtv1.VirtualMachine) { + f.mu.Lock() + defer f.mu.Unlock() + + if f.vms[vm.Namespace] == nil { + f.vms[vm.Namespace] = make(map[string]*kubevirtv1.VirtualMachine) + } + f.vms[vm.Namespace][vm.Name] = vm.DeepCopy() +} + +// UpdateVM updates a VM in the fake client. +func (f *FakeVirtClient) UpdateVM(vm *kubevirtv1.VirtualMachine) { + f.AddVM(vm) // AddVM already does deep copy +} + +// fakeVMIInterface is a fake implementation of VMIInterface. +type fakeVMIInterface struct { + client *FakeVirtClient + namespace string +} + +// Get implements VMIInterface. +func (f *fakeVMIInterface) Get(ctx context.Context, name string, options metav1.GetOptions) (*kubevirtv1.VirtualMachineInstance, error) { + f.client.mu.RLock() + defer f.client.mu.RUnlock() + + nsVMIs, exists := f.client.vmis[f.namespace] + if !exists { + return nil, k8serrors.NewNotFound(schema.GroupResource{Group: "kubevirt.io", Resource: "virtualmachineinstances"}, name) + } + + vmi, exists := nsVMIs[name] + if !exists { + return nil, k8serrors.NewNotFound(schema.GroupResource{Group: "kubevirt.io", Resource: "virtualmachineinstances"}, name) + } + + return vmi.DeepCopy(), nil +} + +// List implements VMIInterface. +func (f *fakeVMIInterface) List(ctx context.Context, options metav1.ListOptions) (*kubevirtv1.VirtualMachineInstanceList, error) { + f.client.mu.RLock() + defer f.client.mu.RUnlock() + + list := &kubevirtv1.VirtualMachineInstanceList{ + Items: []kubevirtv1.VirtualMachineInstance{}, + } + + nsVMIs, exists := f.client.vmis[f.namespace] + if !exists { + return list, nil + } + + for _, vmi := range nsVMIs { + list.Items = append(list.Items, *vmi.DeepCopy()) + } + + return list, nil +} + +// fakeVMInterface is a fake implementation of VMInterface. +type fakeVMInterface struct { + client *FakeVirtClient + namespace string +} + +// Get implements VMInterface. +func (f *fakeVMInterface) Get(ctx context.Context, name string, options metav1.GetOptions) (*kubevirtv1.VirtualMachine, error) { + f.client.mu.RLock() + defer f.client.mu.RUnlock() + + nsVMs, exists := f.client.vms[f.namespace] + if !exists { + return nil, k8serrors.NewNotFound(schema.GroupResource{Group: "kubevirt.io", Resource: "virtualmachines"}, name) + } + + vm, exists := nsVMs[name] + if !exists { + return nil, k8serrors.NewNotFound(schema.GroupResource{Group: "kubevirt.io", Resource: "virtualmachines"}, name) + } + + return vm.DeepCopy(), nil +} + +// List implements VMInterface. +func (f *fakeVMInterface) List(ctx context.Context, options metav1.ListOptions) (*kubevirtv1.VirtualMachineList, error) { + f.client.mu.RLock() + defer f.client.mu.RUnlock() + + list := &kubevirtv1.VirtualMachineList{ + Items: []kubevirtv1.VirtualMachine{}, + } + + nsVMs, exists := f.client.vms[f.namespace] + if !exists { + return list, nil + } + + for _, vm := range nsVMs { + list.Items = append(list.Items, *vm.DeepCopy()) + } + + return list, nil +} + +// VMIBuilder helps construct VMI objects for testing. +type VMIBuilder struct { + vmi *kubevirtv1.VirtualMachineInstance +} + +// NewVMIBuilder creates a new VMI builder. +func NewVMIBuilder(name, namespace, uid string) *VMIBuilder { + return &VMIBuilder{ + vmi: &kubevirtv1.VirtualMachineInstance{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + UID: types.UID(uid), + }, + Status: kubevirtv1.VirtualMachineInstanceStatus{ + ActivePods: make(map[types.UID]string), + }, + }, + } +} + +// WithDeletionTimestamp sets the deletion timestamp on the VMI. +func (b *VMIBuilder) WithDeletionTimestamp(t metav1.Time) *VMIBuilder { + b.vmi.DeletionTimestamp = &t + return b +} + +// WithActivePod adds a pod to the VMI's ActivePods. +func (b *VMIBuilder) WithActivePod(podUID, nodeName string) *VMIBuilder { + if b.vmi.Status.ActivePods == nil { + b.vmi.Status.ActivePods = make(map[types.UID]string) + } + b.vmi.Status.ActivePods[types.UID(podUID)] = nodeName + return b +} + +// WithMigration sets the migration state on the VMI. +func (b *VMIBuilder) WithMigration(migrationUID, sourcePod, targetPod string) *VMIBuilder { + b.vmi.Status.MigrationState = &kubevirtv1.VirtualMachineInstanceMigrationState{ + MigrationUID: types.UID(migrationUID), + SourcePod: sourcePod, + TargetPod: targetPod, + } + return b +} + +// Build returns the constructed VMI. +func (b *VMIBuilder) Build() *kubevirtv1.VirtualMachineInstance { + return b.vmi +} + +// AddMigration adds a VirtualMachineInstanceMigration to the fake client. +func (f *FakeVirtClient) AddMigration(vmim *kubevirtv1.VirtualMachineInstanceMigration) { + f.mu.Lock() + defer f.mu.Unlock() + + if f.migrations[vmim.Namespace] == nil { + f.migrations[vmim.Namespace] = make(map[string]*kubevirtv1.VirtualMachineInstanceMigration) + } + f.migrations[vmim.Namespace][vmim.Name] = vmim.DeepCopy() +} + +// DeleteMigration removes a VirtualMachineInstanceMigration from the fake client. +func (f *FakeVirtClient) DeleteMigration(namespace, name string) { + f.mu.Lock() + defer f.mu.Unlock() + + if f.migrations[namespace] != nil { + delete(f.migrations[namespace], name) + } +} + +// UpdateMigration updates a VirtualMachineInstanceMigration in the fake client. +func (f *FakeVirtClient) UpdateMigration(vmim *kubevirtv1.VirtualMachineInstanceMigration) { + f.AddMigration(vmim) // AddMigration already does deep copy +} + +// fakeVMIMInterface is a fake implementation of VMIMInterface. +type fakeVMIMInterface struct { + client *FakeVirtClient + namespace string +} + +// Get implements VMIMInterface. +func (f *fakeVMIMInterface) Get(ctx context.Context, name string, options metav1.GetOptions) (*kubevirtv1.VirtualMachineInstanceMigration, error) { + f.client.mu.RLock() + defer f.client.mu.RUnlock() + + nsMigrations, exists := f.client.migrations[f.namespace] + if !exists { + return nil, k8serrors.NewNotFound(schema.GroupResource{Group: "kubevirt.io", Resource: "virtualmachineinstancemigrations"}, name) + } + + vmim, exists := nsMigrations[name] + if !exists { + return nil, k8serrors.NewNotFound(schema.GroupResource{Group: "kubevirt.io", Resource: "virtualmachineinstancemigrations"}, name) + } + + return vmim.DeepCopy(), nil +} + +// List implements VMIMInterface. +func (f *fakeVMIMInterface) List(ctx context.Context, options metav1.ListOptions) (*kubevirtv1.VirtualMachineInstanceMigrationList, error) { + f.client.mu.RLock() + defer f.client.mu.RUnlock() + + list := &kubevirtv1.VirtualMachineInstanceMigrationList{ + Items: []kubevirtv1.VirtualMachineInstanceMigration{}, + } + + nsMigrations, exists := f.client.migrations[f.namespace] + if !exists { + return list, nil + } + + for _, vmim := range nsMigrations { + list.Items = append(list.Items, *vmim.DeepCopy()) + } + + return list, nil +} diff --git a/libcalico-go/lib/kubevirt/interface.go b/libcalico-go/lib/kubevirt/interface.go new file mode 100644 index 00000000000..db6213e46dd --- /dev/null +++ b/libcalico-go/lib/kubevirt/interface.go @@ -0,0 +1,59 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. +// +// 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 kubevirt + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kubevirtv1 "kubevirt.io/api/core/v1" +) + +// VirtClientInterface defines the minimal KubeVirt client interface needed for VirtualMachineInstance, VirtualMachine, and VirtualMachineInstanceMigration operations. +// This interface allows for easy mocking and testing without requiring a real KubeVirt cluster. +type VirtClientInterface interface { + // VirtualMachineInstance returns an interface for VirtualMachineInstance operations in the given namespace. + VirtualMachineInstance(namespace string) VMIInterface + + // VirtualMachine returns an interface for VirtualMachine operations in the given namespace. + VirtualMachine(namespace string) VMInterface + + // VirtualMachineInstanceMigration returns an interface for VirtualMachineInstanceMigration operations in the given namespace. + VirtualMachineInstanceMigration(namespace string) VMIMInterface +} + +// VMIInterface defines the VirtualMachineInstance operations we need. +type VMIInterface interface { + // Get retrieves a VirtualMachineInstance by name. + Get(ctx context.Context, name string, options metav1.GetOptions) (*kubevirtv1.VirtualMachineInstance, error) + // List retrieves all VirtualMachineInstances in the namespace. + List(ctx context.Context, options metav1.ListOptions) (*kubevirtv1.VirtualMachineInstanceList, error) +} + +// VMInterface defines the VirtualMachine operations we need. +type VMInterface interface { + // Get retrieves a VirtualMachine by name. + Get(ctx context.Context, name string, options metav1.GetOptions) (*kubevirtv1.VirtualMachine, error) + // List retrieves all VirtualMachines in the namespace. + List(ctx context.Context, options metav1.ListOptions) (*kubevirtv1.VirtualMachineList, error) +} + +// VMIMInterface defines the VirtualMachineInstanceMigration operations we need. +type VMIMInterface interface { + // Get retrieves a VirtualMachineInstanceMigration by name. + Get(ctx context.Context, name string, options metav1.GetOptions) (*kubevirtv1.VirtualMachineInstanceMigration, error) + // List retrieves all VirtualMachineInstanceMigrations in the namespace. + List(ctx context.Context, options metav1.ListOptions) (*kubevirtv1.VirtualMachineInstanceMigrationList, error) +} diff --git a/libcalico-go/lib/kubevirt/vmi.go b/libcalico-go/lib/kubevirt/vmi.go new file mode 100644 index 00000000000..b21c9a49d01 --- /dev/null +++ b/libcalico-go/lib/kubevirt/vmi.go @@ -0,0 +1,246 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. +// +// 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. + +// This file provides utilities for working with KubeVirt Virt-launcher pods. +package kubevirt + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kubevirtv1 "kubevirt.io/api/core/v1" +) + +const ( + // LabelKubeVirtMigrationJobUID is only present on migration target pods + // Value from kubevirtv1.MigrationJobLabel + LabelKubeVirtMigrationJobUID = kubevirtv1.MigrationJobLabel + + // VMI API Group, Version, and Resource for dynamic client + VMIGroup = "kubevirt.io" + VMIVersion = "v1" + + // VM API Group and Version (same as VMI) + VMGroup = "kubevirt.io" + VMVersion = "v1" +) + +// PodVMIInfo contains KubeVirt VMI-related information extracted from a pod's +// ownerReferences and verified against the actual VMI resource via the Kubernetes API. +type PodVMIInfo struct { + *VMIResource // Embedded: Name, Namespace, UID, VMOwner + + // MigrationJobUID is only present on migration target pods + // (extracted from the kubevirt.io/migrationJobUID label) + MigrationJobUID string +} + +// VMIResource contains information about a VirtualMachineInstance resource queried from the Kubernetes API +type VMIResource struct { + // Name is the VMI name + Name string + // Namespace is the VMI namespace + Namespace string + // UID is the VMI UID + UID string + // DeletionTimestamp is the VMI's deletion timestamp (nil if not being deleted) + DeletionTimestamp *metav1.Time + // VMOwner is a pointer to the VirtualMachine resource that owns this VMI (if any) + // This is populated if the VMI has an ownerReference pointing to a VM object + // nil if the VMI is not owned by a VM + VMOwner *kubevirtv1.VirtualMachine +} + +// IsVMObjectDeletionInProgress returns true if the VM owner has a deletion timestamp set +// Returns false if VMOwner is nil or if VMOwner has no deletion timestamp +func (v *VMIResource) IsVMObjectDeletionInProgress() bool { + if v == nil || v.VMOwner == nil { + return false + } + return v.VMOwner.DeletionTimestamp != nil && !v.VMOwner.DeletionTimestamp.IsZero() +} + +// IsVMIObjectDeletionInProgress returns true if the VMI has a deletion timestamp set +func (v *VMIResource) IsVMIObjectDeletionInProgress() bool { + if v == nil { + return false + } + return v.DeletionTimestamp != nil && !v.DeletionTimestamp.IsZero() +} + +// GetName returns the VMI name +func (v *VMIResource) GetName() string { + if v == nil { + return "" + } + return v.Name +} + +// GetVMIUID returns the VMI UID (explicit name for clarity in KubeVirt IPAM context) +func (v *VMIResource) GetVMIUID() string { + if v == nil { + return "" + } + return v.UID +} + +// GetVMUID returns the UID of the VM object that owns this VMI. +// Returns empty string if VMOwner is nil (VMI is not owned by a VM). +func (v *VMIResource) GetVMUID() string { + if v == nil || v.VMOwner == nil { + return "" + } + return string(v.VMOwner.UID) +} + +// GetNamespace returns the VMI namespace +func (v *VMIResource) GetNamespace() string { + if v == nil { + return "" + } + return v.Namespace +} + +// FindVMIOwnerRef checks a pod's ownerReferences for a VirtualMachineInstance controller owner. +// Returns the VMI ownerReference if found, nil otherwise. +func FindVMIOwnerRef(pod *corev1.Pod) *metav1.OwnerReference { + if pod == nil { + return nil + } + for i := range pod.OwnerReferences { + owner := &pod.OwnerReferences[i] + if owner.APIVersion == VMIGroup+"/"+VMIVersion && + owner.Kind == "VirtualMachineInstance" && + owner.Controller != nil && *owner.Controller { + return owner + } + } + return nil +} + +// GetPodVMIInfo determines if a pod is a KubeVirt virt-launcher pod by checking its +// ownerReferences for a VirtualMachineInstance owner, then verifies it against the +// actual VMI resource via the Kubernetes API. +// Returns: +// - (*PodVMIInfo, nil) if the pod is a valid virt-launcher pod with verified VMI +// - (nil, nil) if the pod is not owned by a VMI (not a virt-launcher pod) +// - (nil, error) if verification fails or VMI query fails +func GetPodVMIInfo(pod *corev1.Pod, virtClient VirtClientInterface) (*PodVMIInfo, error) { + vmiOwner := FindVMIOwnerRef(pod) + if vmiOwner == nil { + // Not a virt-launcher pod (no VMI owner) + return nil, nil + } + + // Extract VMI name and UID from ownerReference + vmiName := vmiOwner.Name + vmiUID := string(vmiOwner.UID) + + if vmiName == "" || vmiUID == "" { + return nil, fmt.Errorf("pod %s/%s has invalid VMI ownerReference: name=%q uid=%q", + pod.Namespace, pod.Name, vmiName, vmiUID) + } + + // Query the actual VMI resource to verify and get complete information + vmiResource, err := GetVMIResourceByName( + context.Background(), + virtClient, + pod.Namespace, + vmiName, + ) + if err != nil { + return nil, fmt.Errorf("failed to query VMI resource %s/%s: %w", + pod.Namespace, vmiName, err) + } + + // Verify that the VMI UID from ownerReference matches the actual VMI resource + if vmiResource.UID != vmiUID { + return nil, fmt.Errorf("VMI UID mismatch: pod ownerReference has %s but VMI resource has %s", + vmiUID, vmiResource.UID) + } + + // Check for migration target label + migrationUIDFromLabel := "" + if pod.Labels != nil { + if migrationUID, ok := pod.Labels[LabelKubeVirtMigrationJobUID]; ok { + migrationUIDFromLabel = migrationUID + } + } + + // Create PodVMIInfo with embedded VMIResource + info := &PodVMIInfo{ + VMIResource: vmiResource, + MigrationJobUID: migrationUIDFromLabel, + } + + return info, nil +} + +// IsMigrationTarget returns true if this pod is a migration target pod. +// Migration target pods have the kubevirt.io/migrationJobUID label set. +func (v *PodVMIInfo) IsMigrationTarget() bool { + return v.MigrationJobUID != "" +} + +// GetVMIMigrationUID returns the migration job UID. +// Returns empty string if this is not a migration target pod. +func (v *PodVMIInfo) GetVMIMigrationUID() string { + return v.MigrationJobUID +} + +// GetVMIResourceByName queries the Kubernetes API for a VirtualMachineInstance with the given name +// and returns a VMIResource containing its metadata. +// If the VMI has an ownerReference pointing to a VirtualMachine, the VM resource is also fetched and stored in VMOwner. +// Returns: +// - (*VMIResource, nil) if the VMI is found +// - (nil, error) if there was an error querying the API or VMI not found +func GetVMIResourceByName(ctx context.Context, virtClient VirtClientInterface, namespace, vmiName string) (*VMIResource, error) { + // Get the VMI + vmi, err := virtClient.VirtualMachineInstance(namespace).Get(ctx, vmiName, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + vmiResource := &VMIResource{ + Name: vmi.Name, + Namespace: vmi.Namespace, + UID: string(vmi.UID), + DeletionTimestamp: vmi.DeletionTimestamp, + VMOwner: nil, + } + + // Check if VMI has an ownerReference pointing to a VirtualMachine + var vmOwner *metav1.OwnerReference + for i := range vmi.OwnerReferences { + owner := &vmi.OwnerReferences[i] + if owner.APIVersion == VMGroup+"/"+VMVersion && + owner.Kind == "VirtualMachine" { + vmOwner = owner + break + } + } + + // If VMI is owned by a VM, fetch the VM resource + if vmOwner != nil && vmOwner.Name != "" { + vm, err := virtClient.VirtualMachine(namespace).Get(ctx, vmOwner.Name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + vmiResource.VMOwner = vm + } + + return vmiResource, nil +} diff --git a/libcalico-go/lib/kubevirt/vmi_test.go b/libcalico-go/lib/kubevirt/vmi_test.go new file mode 100644 index 00000000000..0fb663b0170 --- /dev/null +++ b/libcalico-go/lib/kubevirt/vmi_test.go @@ -0,0 +1,248 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. +// +// 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 kubevirt_test + +import ( + "testing" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + kubevirtv1 "kubevirt.io/api/core/v1" + + "github.com/projectcalico/calico/libcalico-go/lib/kubevirt" + fakekubevirt "github.com/projectcalico/calico/libcalico-go/lib/kubevirt/fake" +) + +// TestGetPodVMIInfo_NotVirtLauncherPod tests that non-virt-launcher pods return nil. +func TestGetPodVMIInfo_NotVirtLauncherPod(t *testing.T) { + fakeClient := fakekubevirt.NewFakeVirtClient() + + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "regular-pod", + Namespace: "default", + UID: "pod-123", + }, + } + + info, err := kubevirt.GetPodVMIInfo(pod, fakeClient) + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + if info != nil { + t.Errorf("Expected nil info for non-virt-launcher pod, got: %+v", info) + } +} + +// TestGetPodVMIInfo_VirtLauncherPod tests that virt-launcher pods are correctly identified. +func TestGetPodVMIInfo_VirtLauncherPod(t *testing.T) { + fakeClient := fakekubevirt.NewFakeVirtClient() + + vmiUID := "vmi-12345" + vmiName := "test-vmi" + podUID := "pod-67890" + namespace := "default" + + // Create a VMI + vmi := fakekubevirt.NewVMIBuilder(vmiName, namespace, vmiUID). + WithActivePod(podUID, "node1"). + Build() + fakeClient.AddVMI(vmi) + + // Create a pod owned by the VMI + controllerTrue := true + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "virt-launcher-test-vmi-abcde", + Namespace: namespace, + UID: types.UID(podUID), + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "kubevirt.io/v1", + Kind: "VirtualMachineInstance", + Name: vmiName, + UID: types.UID(vmiUID), + Controller: &controllerTrue, + }, + }, + }, + } + + info, err := kubevirt.GetPodVMIInfo(pod, fakeClient) + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + if info == nil { + t.Fatal("Expected PodVMIInfo, got nil") + } + + if info.GetName() != vmiName { + t.Errorf("Expected VMI name %s, got %s", vmiName, info.GetName()) + } + if info.GetVMIUID() != vmiUID { + t.Errorf("Expected VMI UID %s, got %s", vmiUID, info.GetVMIUID()) + } + if info.VMIResource == nil { + t.Error("Expected VMIResource to be non-nil") + } + if info.IsMigrationTarget() { + t.Error("Expected IsMigrationTarget to be false") + } +} + +// TestGetPodVMIInfo_MigrationTargetPod tests migration target pod detection. +func TestGetPodVMIInfo_MigrationTargetPod(t *testing.T) { + fakeClient := fakekubevirt.NewFakeVirtClient() + + vmiUID := "vmi-12345" + vmiName := "test-vmi" + sourcePodUID := "pod-source" + targetPodUID := "pod-target" + targetPodName := "virt-launcher-test-vmi-target" + migrationUID := "migration-99999" + migrationName := "test-migration" + namespace := "default" + + // Create a VMI with migration in progress + vmi := fakekubevirt.NewVMIBuilder(vmiName, namespace, vmiUID). + WithActivePod(sourcePodUID, "node1"). + WithActivePod(targetPodUID, "node2"). + WithMigration(migrationUID, "virt-launcher-test-vmi-source", targetPodName). + Build() + fakeClient.AddVMI(vmi) + + // Create the VMIM resource to reflect a realistic migration scenario. + vmim := &kubevirtv1.VirtualMachineInstanceMigration{ + ObjectMeta: metav1.ObjectMeta{ + Name: migrationName, + Namespace: namespace, + UID: types.UID(migrationUID), + }, + Spec: kubevirtv1.VirtualMachineInstanceMigrationSpec{ + VMIName: vmiName, + }, + } + fakeClient.AddMigration(vmim) + + // Create target pod with migration label + controllerTrue := true + targetPod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: targetPodName, + Namespace: namespace, + UID: types.UID(targetPodUID), + Labels: map[string]string{ + kubevirt.LabelKubeVirtMigrationJobUID: migrationUID, + }, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "kubevirt.io/v1", + Kind: "VirtualMachineInstance", + Name: vmiName, + UID: types.UID(vmiUID), + Controller: &controllerTrue, + }, + }, + }, + } + + info, err := kubevirt.GetPodVMIInfo(targetPod, fakeClient) + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + if info == nil { + t.Fatal("Expected PodVMIInfo, got nil") + } + + if !info.IsMigrationTarget() { + t.Error("Expected IsMigrationTarget to be true") + } + if info.GetVMIMigrationUID() != migrationUID { + t.Errorf("Expected migration UID %s, got %s", migrationUID, info.GetVMIMigrationUID()) + } +} + +// TestGetPodVMIInfo_VMIBeingDeleted tests VMI deletion detection. +func TestGetPodVMIInfo_VMIBeingDeleted(t *testing.T) { + fakeClient := fakekubevirt.NewFakeVirtClient() + + vmiUID := "vmi-12345" + vmiName := "test-vmi" + vmUID := "vm-12345" + vmName := "test-vm" + podUID := "pod-67890" + namespace := "default" + + // Create a VM with deletion timestamp + now := metav1.Now() + vm := &kubevirtv1.VirtualMachine{ + ObjectMeta: metav1.ObjectMeta{ + Name: vmName, + Namespace: namespace, + UID: types.UID(vmUID), + DeletionTimestamp: &now, + }, + } + fakeClient.AddVM(vm) + + // Create a VMI with ownerReference to the VM + controllerTrue := true + blockOwnerDeletion := true + vmi := fakekubevirt.NewVMIBuilder(vmiName, namespace, vmiUID). + WithActivePod(podUID, "node1"). + Build() + vmi.OwnerReferences = []metav1.OwnerReference{ + { + APIVersion: "kubevirt.io/v1", + Kind: "VirtualMachine", + Name: vmName, + UID: types.UID(vmUID), + Controller: &controllerTrue, + BlockOwnerDeletion: &blockOwnerDeletion, + }, + } + fakeClient.AddVMI(vmi) + + // Create a pod owned by the VMI + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "virt-launcher-test-vmi-abcde", + Namespace: namespace, + UID: types.UID(podUID), + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "kubevirt.io/v1", + Kind: "VirtualMachineInstance", + Name: vmiName, + UID: types.UID(vmiUID), + Controller: &controllerTrue, + }, + }, + }, + } + + info, err := kubevirt.GetPodVMIInfo(pod, fakeClient) + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + if info == nil { + t.Fatal("Expected PodVMIInfo, got nil") + } + + if !info.IsVMObjectDeletionInProgress() { + t.Error("Expected IsVMObjectDeletionInProgress to be true") + } +} diff --git a/libcalico-go/lib/logutils/logutils.go b/libcalico-go/lib/logutils/logutils.go index e223e38d6e5..47113908e6c 100644 --- a/libcalico-go/lib/logutils/logutils.go +++ b/libcalico-go/lib/logutils/logutils.go @@ -601,7 +601,7 @@ func SafeParseLogLevel(logLevel string) log.Level { // for logrus. typically, it should be used via the ConfigureLoggingForTestingT // helper. type TestingTWriter struct { - T *testing.T + T testing.TB } func (l TestingTWriter) Write(p []byte) (n int, err error) { @@ -613,6 +613,12 @@ func (l TestingTWriter) Write(p []byte) (n int, err error) { // RedirectLogrusToTestingT redirects logrus output to the given testing.T. It // returns a func() that can be called to restore the original log output. func RedirectLogrusToTestingT(t *testing.T) (cancel func()) { + return RedirectLogrusToTestingTB(t) +} + +// RedirectLogrusToTestingTB redirects logrus output to the given testing.TB. It +// returns a func() that can be called to restore the original log output. +func RedirectLogrusToTestingTB(t testing.TB) (cancel func()) { oldOut := log.StandardLogger().Out cancel = func() { log.SetOutput(oldOut) @@ -628,8 +634,16 @@ var confForTestingOnce sync.Once // wants to capture log output. It registers a cleanup with the testing.T to // remove the log redirection at the end of the test. func ConfigureLoggingForTestingT(t *testing.T) { + ConfigureLoggingForTestingTB(t) +} + +// ConfigureLoggingForTestingTB configures logrus to write to the logger of the +// given testing.TB. It should be called at the start of each "go test" that +// wants to capture log output. It registers a cleanup with the testing.TB to +// remove the log redirection at the end of the test. +func ConfigureLoggingForTestingTB(t testing.TB) { confForTestingOnce.Do(func() { log.SetFormatter(&Formatter{Component: "test"}) }) - t.Cleanup(RedirectLogrusToTestingT(t)) + t.Cleanup(RedirectLogrusToTestingTB(t)) } diff --git a/libcalico-go/lib/logutils/logutils_suite_test.go b/libcalico-go/lib/logutils/logutils_suite_test.go index f4f37210797..3366e4bddad 100644 --- a/libcalico-go/lib/logutils/logutils_suite_test.go +++ b/libcalico-go/lib/logutils/logutils_suite_test.go @@ -17,16 +17,16 @@ package logutils_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestLogutils(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/logutils_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Logutils Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/logutils_suite.xml" + ginkgo.RunSpecs(t, "Logutils Suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/logutils/logutils_test.go b/libcalico-go/lib/logutils/logutils_test.go index 80995555054..232e0c65204 100644 --- a/libcalico-go/lib/logutils/logutils_test.go +++ b/libcalico-go/lib/logutils/logutils_test.go @@ -27,8 +27,7 @@ import ( "testing" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/format" "github.com/prometheus/client_golang/prometheus" diff --git a/libcalico-go/lib/logutils/ratelimitedlogger.go b/libcalico-go/lib/logutils/ratelimitedlogger.go index 4cad40fd74a..45eb03dcb22 100644 --- a/libcalico-go/lib/logutils/ratelimitedlogger.go +++ b/libcalico-go/lib/logutils/ratelimitedlogger.go @@ -181,7 +181,7 @@ func (logger *RateLimitedLogger) WithError(err error) *RateLimitedLogger { } // WithField adds a single field to the RateLimitedLogger. -func (logger *RateLimitedLogger) WithField(key string, value interface{}) *RateLimitedLogger { +func (logger *RateLimitedLogger) WithField(key string, value any) *RateLimitedLogger { return &RateLimitedLogger{ data: logger.data, entry: logger.entry.WithField(key, value), @@ -196,7 +196,7 @@ func (logger *RateLimitedLogger) WithFields(fields logrus.Fields) *RateLimitedLo } } -func (logger *RateLimitedLogger) Debug(args ...interface{}) { +func (logger *RateLimitedLogger) Debug(args ...any) { if logger.level() >= logrus.DebugLevel { if entry := logger.logEntry(); entry != nil { entry.Debug(args...) @@ -204,13 +204,13 @@ func (logger *RateLimitedLogger) Debug(args ...interface{}) { } } -func (logger *RateLimitedLogger) Print(args ...interface{}) { +func (logger *RateLimitedLogger) Print(args ...any) { if entry := logger.logEntry(); entry != nil { entry.Print(args...) } } -func (logger *RateLimitedLogger) Info(args ...interface{}) { +func (logger *RateLimitedLogger) Info(args ...any) { if logger.level() >= logrus.InfoLevel { if entry := logger.logEntry(); entry != nil { entry.Info(args...) @@ -218,7 +218,7 @@ func (logger *RateLimitedLogger) Info(args ...interface{}) { } } -func (logger *RateLimitedLogger) Warn(args ...interface{}) { +func (logger *RateLimitedLogger) Warn(args ...any) { if logger.level() >= logrus.WarnLevel { if entry := logger.logEntry(); entry != nil { entry.Warn(args...) @@ -226,7 +226,7 @@ func (logger *RateLimitedLogger) Warn(args ...interface{}) { } } -func (logger *RateLimitedLogger) Warning(args ...interface{}) { +func (logger *RateLimitedLogger) Warning(args ...any) { if logger.level() >= logrus.WarnLevel { if entry := logger.logEntry(); entry != nil { entry.Warning(args...) @@ -234,7 +234,7 @@ func (logger *RateLimitedLogger) Warning(args ...interface{}) { } } -func (logger *RateLimitedLogger) Error(args ...interface{}) { +func (logger *RateLimitedLogger) Error(args ...any) { if logger.level() >= logrus.ErrorLevel { if entry := logger.logEntry(); entry != nil { entry.Error(args...) @@ -242,7 +242,7 @@ func (logger *RateLimitedLogger) Error(args ...interface{}) { } } -func (logger *RateLimitedLogger) Debugf(format string, args ...interface{}) { +func (logger *RateLimitedLogger) Debugf(format string, args ...any) { if logger.level() >= logrus.DebugLevel { if entry := logger.logEntry(); entry != nil { entry.Debugf(format, args...) @@ -250,7 +250,7 @@ func (logger *RateLimitedLogger) Debugf(format string, args ...interface{}) { } } -func (logger *RateLimitedLogger) Infof(format string, args ...interface{}) { +func (logger *RateLimitedLogger) Infof(format string, args ...any) { if logger.level() >= logrus.InfoLevel { if entry := logger.logEntry(); entry != nil { entry.Infof(format, args...) @@ -258,13 +258,13 @@ func (logger *RateLimitedLogger) Infof(format string, args ...interface{}) { } } -func (logger *RateLimitedLogger) Printf(format string, args ...interface{}) { +func (logger *RateLimitedLogger) Printf(format string, args ...any) { if entry := logger.logEntry(); entry != nil { entry.Printf(format, args...) } } -func (logger *RateLimitedLogger) Warnf(format string, args ...interface{}) { +func (logger *RateLimitedLogger) Warnf(format string, args ...any) { if logger.level() >= logrus.WarnLevel { if entry := logger.logEntry(); entry != nil { entry.Warnf(format, args...) @@ -272,7 +272,7 @@ func (logger *RateLimitedLogger) Warnf(format string, args ...interface{}) { } } -func (logger *RateLimitedLogger) Warningf(format string, args ...interface{}) { +func (logger *RateLimitedLogger) Warningf(format string, args ...any) { if logger.level() >= logrus.WarnLevel { if entry := logger.logEntry(); entry != nil { entry.Warningf(format, args...) @@ -280,7 +280,7 @@ func (logger *RateLimitedLogger) Warningf(format string, args ...interface{}) { } } -func (logger *RateLimitedLogger) Errorf(format string, args ...interface{}) { +func (logger *RateLimitedLogger) Errorf(format string, args ...any) { if logger.level() >= logrus.ErrorLevel { if entry := logger.logEntry(); entry != nil { entry.Errorf(format, args...) @@ -290,7 +290,7 @@ func (logger *RateLimitedLogger) Errorf(format string, args ...interface{}) { // Entry Println family functions -func (logger *RateLimitedLogger) Debugln(args ...interface{}) { +func (logger *RateLimitedLogger) Debugln(args ...any) { if logger.level() >= logrus.DebugLevel { if entry := logger.logEntry(); entry != nil { entry.Debugln(args...) @@ -298,7 +298,7 @@ func (logger *RateLimitedLogger) Debugln(args ...interface{}) { } } -func (logger *RateLimitedLogger) Infoln(args ...interface{}) { +func (logger *RateLimitedLogger) Infoln(args ...any) { if logger.level() >= logrus.InfoLevel { if entry := logger.logEntry(); entry != nil { entry.Infoln(args...) @@ -306,13 +306,13 @@ func (logger *RateLimitedLogger) Infoln(args ...interface{}) { } } -func (logger *RateLimitedLogger) Println(args ...interface{}) { +func (logger *RateLimitedLogger) Println(args ...any) { if entry := logger.logEntry(); entry != nil { entry.Println(args...) } } -func (logger *RateLimitedLogger) Warnln(args ...interface{}) { +func (logger *RateLimitedLogger) Warnln(args ...any) { if logger.level() >= logrus.WarnLevel { if entry := logger.logEntry(); entry != nil { entry.Warnln(args...) @@ -320,7 +320,7 @@ func (logger *RateLimitedLogger) Warnln(args ...interface{}) { } } -func (logger *RateLimitedLogger) Warningln(args ...interface{}) { +func (logger *RateLimitedLogger) Warningln(args ...any) { if logger.level() >= logrus.WarnLevel { if entry := logger.logEntry(); entry != nil { entry.Warningln(args...) @@ -328,7 +328,7 @@ func (logger *RateLimitedLogger) Warningln(args ...interface{}) { } } -func (logger *RateLimitedLogger) Errorln(args ...interface{}) { +func (logger *RateLimitedLogger) Errorln(args ...any) { if logger.level() >= logrus.ErrorLevel { if entry := logger.logEntry(); entry != nil { entry.Errorln(args...) diff --git a/libcalico-go/lib/logutils/ratelimitedlogger_test.go b/libcalico-go/lib/logutils/ratelimitedlogger_test.go index 327eaab44cc..c5fecafe46a 100644 --- a/libcalico-go/lib/logutils/ratelimitedlogger_test.go +++ b/libcalico-go/lib/logutils/ratelimitedlogger_test.go @@ -19,7 +19,7 @@ import ( "os" "time" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" diff --git a/libcalico-go/lib/metricsserver/metrics_server.go b/libcalico-go/lib/metricsserver/metrics_server.go index 3bc2fb8c62f..402cd2958cb 100644 --- a/libcalico-go/lib/metricsserver/metrics_server.go +++ b/libcalico-go/lib/metricsserver/metrics_server.go @@ -15,18 +15,24 @@ package metricsserver import ( + "crypto/tls" "fmt" + "net" "net/http" "time" + "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/sirupsen/logrus" + + calicotls "github.com/projectcalico/calico/crypto/pkg/tls" ) -func ServePrometheusMetricsForever(host string, port int) { +func ServePrometheusMetricsHTTP(gatherer prometheus.Gatherer, host string, port int) { mux := http.NewServeMux() - mux.Handle("/metrics", promhttp.Handler()) - addr := fmt.Sprintf("[%v]:%v", host, port) + handler := promhttp.HandlerFor(gatherer, promhttp.HandlerOpts{}) + mux.Handle("/metrics", handler) + addr := net.JoinHostPort(host, fmt.Sprint(port)) for { logrus.WithFields(logrus.Fields{ @@ -35,7 +41,64 @@ func ServePrometheusMetricsForever(host string, port int) { }).Info("Starting prometheus metrics endpoint") err := http.ListenAndServe(addr, mux) logrus.WithError(err).Error( - "Prometheus metrics endpoint failed, trying to restart it...") + "Prometheus http metrics endpoint failed, trying to restart it...") time.Sleep(1 * time.Second) } } + +// ServePrometheusMetricsHTTPS starts a secure Prometheus metrics server with dynamic TLS certificate reloading. +func ServePrometheusMetricsHTTPS(gatherer prometheus.Gatherer, host string, port int, certFile, keyFile, clientAuthType, caFile string) error { + mux := http.NewServeMux() + handler := promhttp.HandlerFor(gatherer, promhttp.HandlerOpts{}) + mux.Handle("/metrics", handler) + addr := net.JoinHostPort(host, fmt.Sprint(port)) + + // Initial TLS config loading to catch errors early. + tlsConfig, err := calicotls.NewMutualTLSConfig(certFile, keyFile, caFile) + if err != nil { + return fmt.Errorf("Failed to load initial TLS configuration: %v", err) + } + + // Set the client authentication type if provided. + authType, err := calicotls.StringToTLSClientAuthType(clientAuthType) + if err != nil { + return fmt.Errorf("Failed to convert ClientAuthType %v", err) + } + tlsConfig.ClientAuth = authType + + // Enable dynamic certificate reloading. + tlsConfig.GetConfigForClient = func(*tls.ClientHelloInfo) (*tls.Config, error) { + tlsConfig, err := calicotls.NewMutualTLSConfig(certFile, keyFile, caFile) + if err != nil { + logrus.WithError(err).Error("Failed to reload TLS configuration") + return nil, err + } + // Set the client authentication type if provided. + authType, err := calicotls.StringToTLSClientAuthType(clientAuthType) + if err != nil { + return nil, err + } + tlsConfig.ClientAuth = authType + return tlsConfig, nil + } + + server := &http.Server{ + Addr: addr, + TLSConfig: tlsConfig, + Handler: mux, + } + + // Restart server on failure. + for { + logrus.WithFields(logrus.Fields{ + "host": host, + "port": port, + }).Info("Starting Prometheus metrics endpoint with TLS") + + err = server.ListenAndServeTLS("", "") + if err != nil { + logrus.WithError(err).Error("Prometheus https metrics endpoint failed, restarting...") + time.Sleep(200 * time.Millisecond) + } + } +} diff --git a/libcalico-go/lib/metricsserver/metrics_server_test.go b/libcalico-go/lib/metricsserver/metrics_server_test.go new file mode 100644 index 00000000000..7b535ea6f4e --- /dev/null +++ b/libcalico-go/lib/metricsserver/metrics_server_test.go @@ -0,0 +1,408 @@ +package metricsserver + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "net" + "net/http" + "os" + "sync" + "testing" + "time" + + . "github.com/onsi/gomega" + "github.com/prometheus/client_golang/prometheus" +) + +// TestServePrometheusMetricsHTTPS unit test for ServePrometheusMetricsHTTPS. +func TestServePrometheusMetricsHTTPS(t *testing.T) { + RegisterTestingT(t) + host := "127.0.0.1" + + certFile, keyFile, caFile, caKeyFile, cleanup := createTestCertFiles(t) + defer cleanup() + + // Prepare test cases + for _, tt := range []struct { + name string + certFile string + keyFile string + clientAuthType string + caFile string + clientHasCert bool + expectedStatus int + expectedError []string + rotateCertificates bool + rotateExpectedError []string + rotateExpectedStatus int + }{ + { + name: "Valid TLS: NoClientCert", + certFile: certFile, + keyFile: keyFile, + clientAuthType: "NoClientCert", + caFile: caFile, + clientHasCert: false, + expectedStatus: http.StatusOK, + }, + { + name: "Missing Certificate: NoClientCert", + certFile: "missing.crt", + keyFile: keyFile, + clientAuthType: "NoClientCert", + caFile: caFile, + clientHasCert: false, + expectedError: []string{"Failed to load initial TLS configuration: failed to load x509 key pair: open missing.crt: no such file or directory"}, + }, + { + name: "Missing Private Key: NoClientCert", + certFile: certFile, + keyFile: "missing.key", + clientAuthType: "NoClientCert", + caFile: caFile, + clientHasCert: false, + expectedError: []string{"Failed to load initial TLS configuration: failed to load x509 key pair: open missing.key: no such file or directory"}, + }, + { + name: "Valid TLS: NoClientCert, Rotate Certificates", + certFile: certFile, + keyFile: keyFile, + clientAuthType: "NoClientCert", + caFile: caFile, + clientHasCert: false, + expectedStatus: http.StatusOK, + rotateCertificates: true, + rotateExpectedStatus: http.StatusOK, + }, + { + name: "Valid TLS: RequireAndVerifyClientCert, Client has valid CA", + certFile: certFile, + keyFile: keyFile, + clientAuthType: "RequireAndVerifyClientCert", + caFile: caFile, + clientHasCert: true, + expectedStatus: http.StatusOK, + }, + { + name: "Valid TLS: RequireAndVerifyClientCert, Client has valid CA, Rotate Certificates", + certFile: certFile, + keyFile: keyFile, + clientAuthType: "RequireAndVerifyClientCert", + caFile: caFile, + clientHasCert: true, + expectedStatus: http.StatusOK, + rotateCertificates: true, + rotateExpectedStatus: http.StatusOK, + }, + { + name: "Valid TLS: RequireAndVerifyClientCert, Client is missing CA", + certFile: certFile, + keyFile: keyFile, + clientAuthType: "RequireAndVerifyClientCert", + caFile: caFile, + clientHasCert: false, + expectedError: []string{"Get \"https://127.0.0.1:", "remote error: tls: certificate required"}, + }, + { + name: "Invalid clientAuth Type", + certFile: certFile, + keyFile: keyFile, + clientAuthType: "InvalidType", + caFile: caFile, + clientHasCert: false, + expectedError: []string{"Failed to convert ClientAuthType invalid client authentication type: InvalidType. Defaulting to RequireAndVerifyClientCert"}, + }, + } { + t.Run(tt.name, func(t *testing.T) { + RegisterTestingT(t) + errorChannel := make(chan error, 1) + var once sync.Once + recordConnectionError := func(err error) { + once.Do(func() { + errorChannel <- err + }) + } + err := error(nil) + + // Dynamically find an open port + listener, err := net.Listen("tcp", ":0") + if err != nil { + t.Fatalf("Failed to find an open port %v", err) + } + port := listener.Addr().(*net.TCPAddr).Port + listener.Close() + + done := make(chan error, 1) + go func() { + err = ServePrometheusMetricsHTTPS(prometheus.DefaultGatherer, host, port, tt.certFile, tt.keyFile, tt.clientAuthType, tt.caFile) + done <- err + }() + + select { + case err := <-done: + recordConnectionError(err) + case <-time.After(1 * time.Second): + // Success: the server is still running. + } + close(done) + + if err != nil && len(tt.expectedError) > 0 { + Expect(err).To(HaveOccurred()) + for _, expected := range tt.expectedError { + Expect(err.Error()).To(ContainSubstring(expected)) + } + return + } + + // Load CA cert for client auth + caCert, err := os.ReadFile(tt.caFile) + if err != nil { + t.Fatalf("Failed to read CA cert for client use: %v", err) + } + caCertPool := x509.NewCertPool() + if !caCertPool.AppendCertsFromPEM(caCert) { + t.Fatal("Failed to parse CA cert for client use") + } + + // Configure client tls + clientTLSConfig := &tls.Config{ + RootCAs: caCertPool, + } + if tt.clientHasCert { + clientTLSConfig.Certificates = []tls.Certificate{loadTestClientCert(t, certFile, keyFile)} + } + + client := &http.Client{ + Timeout: 10 * time.Second, + Transport: &http.Transport{ + TLSClientConfig: clientTLSConfig, + // Allow connection reuse + MaxIdleConns: 10, + IdleConnTimeout: 30 * time.Second, + DisableCompression: true, + TLSHandshakeTimeout: 10 * time.Second, + }, + } + + resp, err := getResponseFromMetricsEndpoint(client, host, port) + if err != nil && len(tt.expectedError) > 0 { + Expect(err).To(HaveOccurred()) + for _, expected := range tt.expectedError { + Expect(err.Error()).To(ContainSubstring(expected)) + } + return + } else { + Expect(resp.StatusCode).To(Equal(tt.expectedStatus)) + } + + if tt.rotateCertificates { + // Rotate certificates + err = rotateCertificates(certFile, keyFile, caFile, caKeyFile) + if err != nil { + t.Fatalf("Failed to rotate certificates: %v", err) + } + + resp, err = getResponseFromMetricsEndpoint(client, host, port) + if err != nil { + t.Fatalf("Failed to get response from metrics endpoint: %v", err) + } + + Expect(resp.StatusCode).To(Equal(tt.rotateExpectedStatus)) + } + + if len(tt.expectedError) > 0 || len(tt.rotateExpectedError) > 0 { + t.Fatalf("Test should have failed but didn't") + } + close(errorChannel) + }) + } +} + +func getResponseFromMetricsEndpoint(client *http.Client, host string, port int) (*http.Response, error) { + // Retry logic for certificate rotation + var resp *http.Response + var err error + maxRetries := 10 + retryDelay := 100 * time.Millisecond + + for range maxRetries { + resp, err = client.Get(fmt.Sprintf("https://%s:%d/metrics", host, port)) + if err == nil { + break + } + time.Sleep(retryDelay) + } + if resp != nil && resp.Body != nil { + defer resp.Body.Close() + } + return resp, err +} + +func createTestCertFiles(t *testing.T) (certFile, keyFile, caFile, caKeyFile string, cleanup func()) { + // Generate CA certificate + caCert, caKey, err := generateCA() + if err != nil { + t.Fatalf("Failed to generate CA certificate: %v", err) + } + + // Generate server certificate signed by CA + cert, key, err := generateCert(caCert, caKey) + if err != nil { + t.Fatalf("Failed to generate server certificate: %v", err) + } + + // Write certificates to temporary files + certFile = writeTempFile(t, string(cert)) + keyFile = writeTempFile(t, string(key)) + caFile = writeTempFile(t, string(caCert)) + caKeyFile = writeTempFile(t, string(caKey)) + + // Return cleanup function to remove files + cleanup = func() { + os.Remove(certFile) + os.Remove(keyFile) + os.Remove(caFile) + os.Remove(caKeyFile) + } + return certFile, keyFile, caFile, caKeyFile, cleanup +} + +func generateCA() ([]byte, []byte, error) { + ca := &x509.Certificate{ + SerialNumber: big.NewInt(2025), + Subject: pkix.Name{ + Organization: []string{"Test CA"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + IsCA: true, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + } + + caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return nil, nil, err + } + + caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey) + if err != nil { + return nil, nil, err + } + + caPEM := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: caBytes, + }) + + caPrivKeyPEM := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey), + }) + + return caPEM, caPrivKeyPEM, nil +} + +func generateCert(caCertPEM, caKeyPEM []byte) ([]byte, []byte, error) { + caCertBlock, _ := pem.Decode(caCertPEM) + caCert, err := x509.ParseCertificate(caCertBlock.Bytes) + if err != nil { + return nil, nil, err + } + + caKeyBlock, _ := pem.Decode(caKeyPEM) + caKey, err := x509.ParsePKCS1PrivateKey(caKeyBlock.Bytes) + if err != nil { + return nil, nil, err + } + + cert := &x509.Certificate{ + SerialNumber: big.NewInt(2025), + Subject: pkix.Name{ + Organization: []string{"Test Server"}, + }, + DNSNames: []string{"localhost"}, + IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + } + + certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return nil, nil, err + } + + certBytes, err := x509.CreateCertificate(rand.Reader, cert, caCert, &certPrivKey.PublicKey, caKey) + if err != nil { + return nil, nil, err + } + + certPEM := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: certBytes, + }) + + certPrivKeyPEM := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey), + }) + + return certPEM, certPrivKeyPEM, nil +} + +func loadTestClientCert(t *testing.T, certFile, keyFile string) tls.Certificate { + cert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + t.Fatalf("Failed to load client certificate: %v", err) + } + return cert +} + +func writeTempFile(t *testing.T, content string) string { + tmpFile, err := os.CreateTemp("", "testcert") + if err != nil { + t.Fatalf("Failed to create temp file: %v", err) + } + defer tmpFile.Close() + + if _, err := tmpFile.WriteString(content); err != nil { + t.Fatalf("Failed to write to temp file: %v", err) + } + return tmpFile.Name() +} + +func rotateCertificates(certFile, keyFile, caFile, caKeyFile string) error { + // Read CA certificate and key + caCertPEM, err := os.ReadFile(caFile) + if err != nil { + return fmt.Errorf("failed to read CA certificate: %w", err) + } + caKeyPEM, err := os.ReadFile(caKeyFile) + if err != nil { + return fmt.Errorf("failed to read CA key: %w", err) + } + // Generate new certificates + newCert, newKey, err := generateCert(caCertPEM, caKeyPEM) + if err != nil { + return fmt.Errorf("failed to generate new certificates: %w", err) + } + + // Write new certificates to the specified files + if err := os.WriteFile(certFile, newCert, 0o644); err != nil { + return fmt.Errorf("failed to write new certificate: %w", err) + } + if err := os.WriteFile(keyFile, newKey, 0o644); err != nil { + return fmt.Errorf("failed to write new key: %w", err) + } + + return nil +} diff --git a/libcalico-go/lib/multireadbuf/multireadbuf_test.go b/libcalico-go/lib/multireadbuf/multireadbuf_test.go index 8a216fd3a61..6930dfee0d8 100644 --- a/libcalico-go/lib/multireadbuf/multireadbuf_test.go +++ b/libcalico-go/lib/multireadbuf/multireadbuf_test.go @@ -87,7 +87,7 @@ func benchmarkNReaders(b *testing.B, numReaders int, writeTo bool) { out []byte err error }, numReaders) - for i := 0; i < numReaders; i++ { + for i := range numReaders { wg.Add(1) if writeTo { // Use the WriteTo() method, which avoids copies. @@ -117,10 +117,7 @@ func benchmarkNReaders(b *testing.B, numReaders int, writeTo bool) { remainingData := data for len(remainingData) > 0 { - wrSize := 1000 - if wrSize > len(remainingData) { - wrSize = len(remainingData) - } + wrSize := min(1000, len(remainingData)) n, err := mrb.Write(remainingData[:wrSize]) if err != nil { b.Fatalf("Write returned unexpected error: %v", err) diff --git a/libcalico-go/lib/names/names_suite_test.go b/libcalico-go/lib/names/names_suite_test.go index 557d085b511..7446f99fb98 100644 --- a/libcalico-go/lib/names/names_suite_test.go +++ b/libcalico-go/lib/names/names_suite_test.go @@ -17,13 +17,13 @@ package names import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" ) func TestClient(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/names_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "names Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/names_suite.xml" + ginkgo.RunSpecs(t, "names Suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/names/policy.go b/libcalico-go/lib/names/policy.go index f18282001da..036b5d9b8e0 100644 --- a/libcalico-go/lib/names/policy.go +++ b/libcalico-go/lib/names/policy.go @@ -14,168 +14,15 @@ package names -import ( - "errors" - "fmt" - "strings" - - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" -) - const ( - DefaultTierName = "default" - AdminNetworkPolicyTierName = "adminnetworkpolicy" - BaselineAdminNetworkPolicyTierName = "baselineadminnetworkpolicy" - - // K8sNetworkPolicyNamePrefix is the prefix used when translating a - // Kubernetes network policy into a Calico one. - K8sNetworkPolicyNamePrefix = "knp.default." - // K8sAdminNetworkPolicyNamePrefix is the prefix for a Kubernetes - // AdminNetworkPolicy resources, which are cluster-scoped and live in a - // tier ahead of the default tier. - K8sAdminNetworkPolicyNamePrefix = "kanp.adminnetworkpolicy." - // K8sBaselineAdminNetworkPolicyNamePrefix is the prefix for the singleton - // BaselineAdminNetworkPolicy resource, which is cluster-scoped and lives - // in a tier after the default tier. - K8sBaselineAdminNetworkPolicyNamePrefix = "kbanp.baselineadminnetworkpolicy." + DefaultTierName = "default" + KubeAdminTierName = "kube-admin" + KubeBaselineTierName = "kube-baseline" // OpenStackNetworkPolicyNamePrefix is the prefix for OpenStack security groups. OpenStackNetworkPolicyNamePrefix = "ossg." ) -// TierFromPolicyName extracts the tier from a tiered policy name. -// If the policy is a K8s policy (with prefix "knp.default"), then tier name is -// is the "default" tier. If there are no tier name prefix, then again the -// "default" tier name is returned. -// Otherwise, the first full word that occurs before the first "." (dot) is returned -// as the tier value. -func TierFromPolicyName(name string) (string, error) { - if name == "" { - return "", errors.New("Tiered policy name is empty") - } - // If it is a K8s (admin) network policy, then simply return the policy name as is. - if strings.HasPrefix(name, K8sNetworkPolicyNamePrefix) { - return DefaultTierName, nil - } - if strings.HasPrefix(name, K8sAdminNetworkPolicyNamePrefix) { - return AdminNetworkPolicyTierName, nil - } - if strings.HasPrefix(name, K8sBaselineAdminNetworkPolicyNamePrefix) { - return BaselineAdminNetworkPolicyTierName, nil - } - // Policy derived from OpenStack security groups is named as "ossg.default.", but should go into the default tier. - if strings.HasPrefix(name, OpenStackNetworkPolicyNamePrefix) { - return DefaultTierName, nil - } - parts := strings.SplitN(name, ".", 2) - if len(parts) < 2 { - // A name without a prefix. - return DefaultTierName, nil - } - // Return the first word before the first dot. - return parts[0], nil -} - -// BackendTieredPolicyName returns a policy name suitable for use by any -// backend. It will always return a policy name prefixed with the appropriate -// tier or error. The tier name is passed in as-is from the Policy Spec of a -// (Staged)NetworkPolicy or a (Staged)GlobalNetworkPolicy resource. -func BackendTieredPolicyName(policy, tier string) (string, error) { - tieredPolicy := TieredPolicyName(policy) - return tieredPolicy, validateBackendTieredPolicyName(tieredPolicy, tier) -} - -func validateBackendTieredPolicyName(policy, tier string) error { - if policy == "" { - return errors.New("Policy name is empty") - } - if policyNameIsFormatted(policy) { - return nil - } - - t := TierOrDefault(tier) - parts := strings.SplitN(policy, ".", 2) - if len(parts) != 2 || !strings.HasPrefix(policy, t+".") { - return fmt.Errorf("Incorrectly formatted policy name %s", policy) - } - return nil -} - -// ValidateTieredPolicyName validates v3 policy name, policies in the default tier can be named without the default. prefix. -// Policy names in non default tier have to be in a format of tier.name -func ValidateTieredPolicyName(policy, tier string) error { - if policy == "" { - return errors.New("Policy name is empty") - } - if policyNameIsFormatted(policy) { - return nil - } - - tier = TierOrDefault(tier) - parts := strings.SplitN(policy, ".", 2) - - if len(parts) == 1 && tier == "default" { - // Policy in default tier, without the default. prefix - return nil - } - - if len(parts) == 2 && strings.HasPrefix(policy, tier+".") { - // Policy in format of tier.name with tier matching the prefix - return nil - } - - // If we reached here the policy name is invalid. Either incorrect prefix or with additional . in the name - return fmt.Errorf("Incorrectly formatted policy name %s", policy) -} - -func TieredPolicyName(policy string) string { - if policy == "" { - return "" - } - if policyNameIsFormatted(policy) { - return policy - } - - parts := strings.SplitN(policy, ".", 2) - if len(parts) == 1 { - // Default tier name. - return fmt.Sprintf("default.%v", policy) - } else if len(parts) == 2 { - // The policy name is already prefixed appropriately. - return policy - } - return "" -} - -// ClientTieredPolicyName returns a policy name suitable for returning to -// the user of the client. The tier name is passed in as-is from the Policy -// spec for (Staged)NetworkPolicy or a (Staged)GlobalNetworkPolicy. -func ClientTieredPolicyName(policy string) (string, error) { - if policy == "" { - return "", errors.New("Policy name is empty") - } - if policyNameIsFormatted(policy) { - return policy, nil - } - parts := strings.SplitN(policy, ".", 2) - if len(parts) != 2 { - return "", fmt.Errorf("Invalid policy name %s", policy) - } else if parts[0] == DefaultTierName { - return parts[1], nil - } - return policy, nil -} - -func policyNameIsFormatted(policy string) bool { - // If it is a K8s (admin) network policy, or derived from an OpenStack security group, we - // expect the policy name to be formatted properly in the first place. - return strings.HasPrefix(policy, K8sNetworkPolicyNamePrefix) || - strings.HasPrefix(policy, K8sAdminNetworkPolicyNamePrefix) || - strings.HasPrefix(policy, K8sBaselineAdminNetworkPolicyNamePrefix) || - strings.HasPrefix(policy, OpenStackNetworkPolicyNamePrefix) -} - // TierOrDefault returns the tier name, or the default if blank. func TierOrDefault(tier string) string { if len(tier) == 0 { @@ -185,67 +32,6 @@ func TierOrDefault(tier string) string { } } -// deconstructPolicyName deconstructs the v1 policy name that is constructed by the SyncerUpdateProcessors in -// libcalico-go and extracts the v3 fields: namespace, tier, name. -// -// The v1 policy name is of the format: -// - /. for a namespaced NetworkPolicies -// - . for GlobalNetworkPolicies. -// - /knp.default. for a k8s NetworkPolicies -// - kanp.adminnetworkpolicy. for a k8s AdminNetworkPolicies -// - kbanp.baselineadminnetworkpolicy. for a k8s BaselineAdminNetworkPolicies -// and for the staged counterparts, respectively: -// - /staged:. -// - staged:. -// - /staged:knp.default. -// -// The namespace is returned blank for GlobalNetworkPolicies. -// For k8s network policies, the tier is always "default" and the name will be returned including the -// knp.default prefix. -// -// Staged policies will have the simplified name prefixed with "staged:", eg: -// - /staged:. => Name=staged:, Namespace=, Tier= -// - staged:. => Name=staged:, Namespace=, Tier= -// - /staged:knp.default. => Name=staged:knp.default., Namespace=, Tier=default -func DeconstructPolicyName(name string) (string, string, string, error) { - var namespace string - - // Split the name to extract the namespace. - parts := strings.Split(name, "/") - switch len(parts) { - case 1: // GlobalNetworkPolicy - name = parts[0] - case 2: // NetworkPolicy (Calico or Kubernetes) - namespace = parts[0] - name = parts[1] - default: - return "", "", "", fmt.Errorf("could not parse policy %s", name) - } - - // Remove the staged prefix if present so we can extract the tier. - var stagedPrefix string - if model.PolicyIsStaged(name) { - stagedPrefix = model.PolicyNamePrefixStaged - name = name[len(model.PolicyNamePrefixStaged):] - } - - // If policy name starts with "knp.default" then this is k8s network policy. - if strings.HasPrefix(name, K8sNetworkPolicyNamePrefix) { - return namespace, DefaultTierName, stagedPrefix + name, nil - } - // If policy name starts with "kanp.adminnetworkpolicy" then this is k8s admin network policy. - if strings.HasPrefix(name, K8sAdminNetworkPolicyNamePrefix) { - return namespace, AdminNetworkPolicyTierName, stagedPrefix + name, nil - } - // If policy name starts with "kbanp.baselineadminnetworkpolicy" then this is k8s baseline admin network policy. - if strings.HasPrefix(name, K8sBaselineAdminNetworkPolicyNamePrefix) { - return namespace, BaselineAdminNetworkPolicyTierName, stagedPrefix + name, nil - } - - // This is a non-kubernetes policy, so extract the tier name from the policy name. - if parts = strings.SplitN(name, ".", 2); len(parts) == 2 { - return namespace, parts[0], stagedPrefix + parts[1], nil - } - - return "", "", "", fmt.Errorf("could not parse policy %s", name) +func TierIsStatic(name string) bool { + return name == DefaultTierName || name == KubeAdminTierName || name == KubeBaselineTierName } diff --git a/libcalico-go/lib/names/policy_test.go b/libcalico-go/lib/names/policy_test.go deleted file mode 100644 index a5fd771d656..00000000000 --- a/libcalico-go/lib/names/policy_test.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) 2024-2025 Tigera, Inc. All rights reserved. -// -// 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 names_test - -import ( - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" - - "github.com/projectcalico/calico/libcalico-go/lib/names" -) - -var _ = DescribeTable("Parse Tiered policy name", - func(policy string, expectError bool, expectedTier string) { - tier, err := names.TierFromPolicyName(policy) - if expectError { - Expect(err).To(HaveOccurred()) - } else { - Expect(err).NotTo(HaveOccurred()) - Expect(tier).To(Equal(expectedTier)) - } - }, - Entry("Empty policy name", "", true, ""), - Entry("K8s network policy", "knp.default.foopolicy", false, "default"), - Entry("K8s admin network policy", "kanp.adminnetworkpolicy.barpolicy", false, "adminnetworkpolicy"), - Entry("K8s baseline admin network policy", "kbanp.baselineadminnetworkpolicy.barpolicy", false, "baselineadminnetworkpolicy"), - Entry("Policy name without tier", "foopolicy", false, "default"), - Entry("Correct tiered policy name", "baztier.foopolicy", false, "baztier"), - Entry("OpenStack-derived policy name", "ossg.default.19bed2d3-12fc-4cc0-92d7-bea430a28a85", false, "default"), -) - -var _ = DescribeTable("Backend Tiered policy name", - func(policy, tier string, expectError bool, expectedTpn string) { - tpn, err := names.BackendTieredPolicyName(policy, tier) - if expectError { - Expect(err).To(HaveOccurred()) - } else { - Expect(err).NotTo(HaveOccurred()) - Expect(tpn).To(Equal(expectedTpn)) - } - }, - Entry("Empty policy name", "", "foo", true, ""), - Entry("Empty tier spec with correctly formatted name", "footier.bazpolicy", "", true, ""), - Entry("Tier spec present with incorrectly formatted name", "bazpolicy", "footier", true, ""), - Entry("Correcty formatted tiered policy name but not matching tier spec", "footier.bazpolicy", "baztier", true, ""), - Entry("K8s Network Policy and empty tier", "knp.default.foobar", "", false, "knp.default.foobar"), - Entry("K8s Admin Network Policy and empty tier", "kanp.adminnetworkpolicy.foobar", "", false, "kanp.adminnetworkpolicy.foobar"), - Entry("K8s Baseline Admin Network Policy and empty tier", "kbanp.baselineadminnetworkpolicy.foobar", "", false, "kbanp.baselineadminnetworkpolicy.foobar"), - Entry("Network Policy and empty tier", "foobar", "", false, "default.foobar"), - Entry("Matching tier spec and correctly formatted tiered policy name", "footier.bazpolicy", "footier", false, "footier.bazpolicy"), -) - -var _ = DescribeTable("Tiered policy name", - func(policy, expectedTpn string) { - tpn := names.TieredPolicyName(policy) - Expect(tpn).To(Equal(expectedTpn)) - }, - Entry("Empty policy name", "", ""), - Entry("Correctly formatted name", "footier.bazpolicy", "footier.bazpolicy"), - Entry("Policy in default tier", "bazpolicy", "default.bazpolicy"), - Entry("Policy in default tier with prefix", "default.bazpolicy", "default.bazpolicy"), - Entry("K8s network policy", "knp.default.bazpolicy", "knp.default.bazpolicy"), - Entry("K8s admin network policy", "kanp.adminnetworkpolicy.foopolicy", "kanp.adminnetworkpolicy.foopolicy"), - Entry("K8s baseline admin network policy", "kbanp.baselineadminnetworkpolicy.foopolicy", "kbanp.baselineadminnetworkpolicy.foopolicy"), -) - -var _ = DescribeTable("Client Tiered policy name", - func(policy string, expectError bool, expectedTpn string) { - tpn, err := names.ClientTieredPolicyName(policy) - if expectError { - Expect(err).To(HaveOccurred()) - } else { - Expect(err).NotTo(HaveOccurred()) - Expect(tpn).To(Equal(expectedTpn)) - } - }, - Entry("Empty policy name", "foo", true, ""), - Entry("Incorrectly formatted name", "bazpolicy", true, ""), - Entry("Correctly formatted name", "footier.bazpolicy", false, "footier.bazpolicy"), - Entry("Default tier", "default.bazpolicy", false, "bazpolicy"), - Entry("K8s Network Policy", "knp.default.bazpolicy", false, "knp.default.bazpolicy"), - Entry("K8s Admin Network Policy", "kanp.adminnetworkpolicy.bazpolicy", false, "kanp.adminnetworkpolicy.bazpolicy"), - Entry("K8s Baseline Admin Network Policy", "kbanp.baselineadminnetworkpolicy.bazpolicy", false, "kbanp.baselineadminnetworkpolicy.bazpolicy"), -) diff --git a/libcalico-go/lib/names/workloadendpoint.go b/libcalico-go/lib/names/workloadendpoint.go index 2bf638ff184..f22f8a0535d 100644 --- a/libcalico-go/lib/names/workloadendpoint.go +++ b/libcalico-go/lib/names/workloadendpoint.go @@ -20,7 +20,7 @@ import ( "reflect" "strings" - v3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" ) @@ -287,7 +287,7 @@ func ConvertWorkloadEndpointV3KeyToV1Key(v3key model.ResourceKey) (model.Workloa }, nil } -func IdentifiersForV3WorkloadEndpoint(res *v3.WorkloadEndpoint) WorkloadEndpointIdentifiers { +func IdentifiersForV3WorkloadEndpoint(res *internalapi.WorkloadEndpoint) WorkloadEndpointIdentifiers { wepids := WorkloadEndpointIdentifiers{ Node: res.Spec.Node, Orchestrator: res.Spec.Orchestrator, diff --git a/libcalico-go/lib/names/workloadendpoint_test.go b/libcalico-go/lib/names/workloadendpoint_test.go index 377aa069988..d23c7e74cd7 100644 --- a/libcalico-go/lib/names/workloadendpoint_test.go +++ b/libcalico-go/lib/names/workloadendpoint_test.go @@ -15,7 +15,7 @@ package names_test import ( - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" diff --git a/libcalico-go/lib/names/workloadendpointstatus.go b/libcalico-go/lib/names/workloadendpointstatus.go index ca7ac907729..196e780ce01 100644 --- a/libcalico-go/lib/names/workloadendpointstatus.go +++ b/libcalico-go/lib/names/workloadendpointstatus.go @@ -20,7 +20,7 @@ import ( "github.com/sirupsen/logrus" - v3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" ) @@ -64,7 +64,7 @@ func WorkloadEndpointKeyToStatusFilename(key *model.WorkloadEndpointKey) string // V3WorkloadEndpointToWorkloadEndpointKey generates a WorkloadEndpointKey from the given WorkloadEndpoint. // Returns nil if passed endpoint is nil. -func V3WorkloadEndpointToWorkloadEndpointKey(ep *v3.WorkloadEndpoint) (*model.WorkloadEndpointKey, error) { +func V3WorkloadEndpointToWorkloadEndpointKey(ep *internalapi.WorkloadEndpoint) (*model.WorkloadEndpointKey, error) { if ep == nil { return nil, nil } @@ -82,7 +82,7 @@ func V3WorkloadEndpointToWorkloadEndpointKey(ep *v3.WorkloadEndpoint) (*model.Wo } } v3Key := model.ResourceKey{ - Kind: v3.KindWorkloadEndpoint, + Kind: internalapi.KindWorkloadEndpoint, Name: name, Namespace: ep.GetNamespace(), } diff --git a/libcalico-go/lib/names/workloadendpointstatus_test.go b/libcalico-go/lib/names/workloadendpointstatus_test.go index a4c2defaaf6..dc9cf27a7e0 100644 --- a/libcalico-go/lib/names/workloadendpointstatus_test.go +++ b/libcalico-go/lib/names/workloadendpointstatus_test.go @@ -15,11 +15,11 @@ package names_test import ( - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - v3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/names" ) @@ -37,13 +37,13 @@ var _ = DescribeTable("WorkloadEndpointKey to endpoint-status filename", ) var _ = DescribeTable("V3 WorkloadEndpoint to model WorkloadEndpointKey", - func(ep *v3.WorkloadEndpoint, expectedKey *model.WorkloadEndpointKey) { + func(ep *internalapi.WorkloadEndpoint, expectedKey *model.WorkloadEndpointKey) { genKey, err := names.V3WorkloadEndpointToWorkloadEndpointKey(ep) Expect(err).NotTo(HaveOccurred()) Expect(genKey).To(BeEquivalentTo(expectedKey)) }, Entry("Valid, FV endpoint (etcd datastore)", - &v3.WorkloadEndpoint{ + &internalapi.WorkloadEndpoint{ //"felixfv default%2Fworkload-endpoint-status-tests-0-idx7 workload-endpoint-status-tests-0-idx7" TypeMeta: v1.TypeMeta{ Kind: "WorkloadEndpoint", @@ -66,7 +66,7 @@ var _ = DescribeTable("V3 WorkloadEndpoint to model WorkloadEndpointKey", Finalizers: nil, ManagedFields: nil, }, - Spec: v3.WorkloadEndpointSpec{ + Spec: internalapi.WorkloadEndpointSpec{ Orchestrator: "felixfv", Workload: "workload-endpoint-status-tests-0-idx7", Node: "felix-0-821432-15-felixfv", @@ -94,7 +94,7 @@ var _ = DescribeTable("V3 WorkloadEndpoint to model WorkloadEndpointKey", ), Entry("Valid, FV endpoint (kubernetes datastore)", - &v3.WorkloadEndpoint{ + &internalapi.WorkloadEndpoint{ //"k8s default%2Fworkload-endpoint-status-tests-0-idx3 eth0" TypeMeta: v1.TypeMeta{ Kind: "WorkloadEndpoint", @@ -117,7 +117,7 @@ var _ = DescribeTable("V3 WorkloadEndpoint to model WorkloadEndpointKey", Finalizers: nil, ManagedFields: nil, }, - Spec: v3.WorkloadEndpointSpec{ + Spec: internalapi.WorkloadEndpointSpec{ Orchestrator: "k8s", Workload: "workload-endpoint-status-tests-0-idx3", Node: "felix-0-821432-9-felixfv", diff --git a/libcalico-go/lib/namespace/namespace_suite_test.go b/libcalico-go/lib/namespace/namespace_suite_test.go index 0513cfe68c2..f4194a84220 100644 --- a/libcalico-go/lib/namespace/namespace_suite_test.go +++ b/libcalico-go/lib/namespace/namespace_suite_test.go @@ -17,16 +17,16 @@ package namespace_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestClient(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/lib_namespace_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "lib/namespace suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/lib_namespace_suite.xml" + ginkgo.RunSpecs(t, "lib/namespace suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/namespace/resource.go b/libcalico-go/lib/namespace/resource.go index 800e964f5d5..3c57e838506 100644 --- a/libcalico-go/lib/namespace/resource.go +++ b/libcalico-go/lib/namespace/resource.go @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2025 Tigera, Inc. All rights reserved. +// Copyright (c) 2017-2026 Tigera, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ package namespace import ( apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" ) const ( @@ -30,7 +30,8 @@ const ( func IsNamespaced(kind string) bool { switch kind { - case libapiv3.KindWorkloadEndpoint, + case internalapi.KindWorkloadEndpoint, + internalapi.KindLiveMigration, apiv3.KindNetworkPolicy, apiv3.KindStagedNetworkPolicy, apiv3.KindStagedKubernetesNetworkPolicy, diff --git a/libcalico-go/lib/namespace/resource_test.go b/libcalico-go/lib/namespace/resource_test.go index e76f8fec539..c830019e4ef 100644 --- a/libcalico-go/lib/namespace/resource_test.go +++ b/libcalico-go/lib/namespace/resource_test.go @@ -15,8 +15,7 @@ package namespace_test import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/namespace" diff --git a/libcalico-go/lib/net/ipnet.go b/libcalico-go/lib/net/ipnet.go index fb400e3382a..60fe0ac1010 100644 --- a/libcalico-go/lib/net/ipnet.go +++ b/libcalico-go/lib/net/ipnet.go @@ -74,6 +74,10 @@ func (i IPNet) Covers(n net.IPNet) bool { } func (i IPNet) NthIP(n int) IP { + return i.nthIP(int64(n)) +} + +func (i IPNet) nthIP(n int64) IP { bigN := big.NewInt(int64(n)) return IncrementIP(IP{i.IP}, bigN) } diff --git a/libcalico-go/lib/net/ipnet_test.go b/libcalico-go/lib/net/ipnet_test.go index 6871e747aeb..b79d21534b1 100644 --- a/libcalico-go/lib/net/ipnet_test.go +++ b/libcalico-go/lib/net/ipnet_test.go @@ -34,7 +34,6 @@ func TestIPNet_Covers(t *testing.T) { {A: "10.0.0.0/9", B: "10.0.0.0/8", ACoversB: false}, {A: "11.0.0.0/9", B: "10.0.0.0/9", ACoversB: false}, } { - test := test t.Run(fmt.Sprintf("%s_Covers_%s", test.A, test.B), func(t *testing.T) { RegisterTestingT(t) larger := MustParseCIDR(test.A) @@ -48,7 +47,7 @@ func TestIPNet_Covers(t *testing.T) { func TestIPNet_NthIP(t *testing.T) { for _, test := range []struct { CIDR string - N int + N int64 ExpectedIP string }{ {CIDR: "10.0.0.0/8", N: 0, ExpectedIP: "10.0.0.0"}, @@ -64,12 +63,11 @@ func TestIPNet_NthIP(t *testing.T) { {CIDR: "255.255.255.255/32", N: 0, ExpectedIP: "255.255.255.255"}, {CIDR: "255.255.255.254/32", N: 1, ExpectedIP: "255.255.255.255"}, } { - test := test t.Run(fmt.Sprintf("IPNet(%s).NthIP(%v) should be %v", test.CIDR, test.N, test.ExpectedIP), func(t *testing.T) { RegisterTestingT(t) cidr := MustParseCIDR(test.CIDR) expected := MustParseIP(test.ExpectedIP) - ip := cidr.NthIP(test.N) + ip := cidr.nthIP(test.N) Expect(ip.Equal(expected.IP)).To(BeTrue(), fmt.Sprintf("%v != %v", ip.String(), expected.String())) }) diff --git a/libcalico-go/lib/net/ipv6_ll.go b/libcalico-go/lib/net/ipv6_ll.go new file mode 100644 index 00000000000..e368b8f6112 --- /dev/null +++ b/libcalico-go/lib/net/ipv6_ll.go @@ -0,0 +1,45 @@ +// Copyright (c) 2025 Tigera, Inc. All rights reserved. + +// 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 net + +import "net" + +// MustMACToIPv6LinkLocal converts a MAC address to the associated IPv6 +// link-local address. +func MustMACToIPv6LinkLocal(mac net.HardwareAddr) net.IP { + // First convert MAC to EUI-64. + var eui [8]byte + if len(mac) == 8 { + copy(eui[:], mac) + } else if len(mac) == 6 { + copy(eui[:3], mac[:3]) + eui[3] = 0xff + eui[4] = 0xfe + copy(eui[5:], mac[3:]) + } else { + panic("MustMACToIPv6LinkLocal: invalid MAC address: " + mac.String()) + } + + // Flip 7th bit, as required by LL algorithm. + eui[0] = eui[0] ^ 0x02 + + var addr [16]byte + // LL prefix. + addr[0] = 0xfe + addr[1] = 0x80 + // Modified EUI in the second half. + copy(addr[8:], eui[:]) + return addr[:] +} diff --git a/libcalico-go/lib/net/ipv6_ll_test.go b/libcalico-go/lib/net/ipv6_ll_test.go new file mode 100644 index 00000000000..91b3029b891 --- /dev/null +++ b/libcalico-go/lib/net/ipv6_ll_test.go @@ -0,0 +1,78 @@ +// Copyright (c) 2025 Tigera, Inc. All rights reserved. + +// 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 net + +import ( + "net" + "testing" +) + +func TestMACToIPv6LinkLocal(t *testing.T) { + for _, test := range []struct { + in string + expected string + shouldPanic bool + }{ + { + in: "00:00:00:00:00:00", + expected: "fe80::0200:00ff:fe00:0000", + }, + { + // Normal 48-bit MAC address, should get expanded to 64 bits by + // inserting ff:ee. + in: "11:22:33:44:55:66", + expected: "fe80::1322:33ff:fe44:5566", + }, + { + // EUI-64; no expansion needed. + in: "11:22:33:44:55:66:77:88", + expected: "fe80::1322:3344:5566:7788", + }, + { + in: "nil", + shouldPanic: true, + }, + } { + t.Run(test.in, func(t *testing.T) { + var mac net.HardwareAddr + if test.in != "nil" { + var err error + mac, err = net.ParseMAC(test.in) + if err != nil { + t.Errorf("Error parsing MAC %q", test.in) + } + } + if test.shouldPanic { + panicked := false + func() { + defer func() { + if r := recover(); r != nil { + panicked = true + } + }() + MustMACToIPv6LinkLocal(mac) + }() + if !panicked { + t.Errorf("should have panicked on %q", test.in) + } + } else { + out := MustMACToIPv6LinkLocal(mac) + if !net.ParseIP(test.expected).Equal(out) { + t.Errorf("MustMACToIPv6LinkLocal(%q) = %v; want %v", test.in, out, test.expected) + } + } + }) + } +} diff --git a/libcalico-go/lib/resources/node.go b/libcalico-go/lib/resources/node.go index e9c0ebf30c8..de0005cd395 100644 --- a/libcalico-go/lib/resources/node.go +++ b/libcalico-go/lib/resources/node.go @@ -17,13 +17,13 @@ package resources import ( log "github.com/sirupsen/logrus" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" cnet "github.com/projectcalico/calico/libcalico-go/lib/net" ) // FindNodeAddress returns node address of the specified type. Type can be one of // CalicoNodeIP, InternalIP or ExternalIP -func FindNodeAddress(node *libapiv3.Node, ipType string, ipVersion int) (*cnet.IP, *cnet.IPNet) { +func FindNodeAddress(node *internalapi.Node, ipType string, ipVersion int) (*cnet.IP, *cnet.IPNet) { for _, addr := range node.Spec.Addresses { if addr.Type == ipType { ip, cidr, err := cnet.ParseCIDROrIP(addr.Address) diff --git a/libcalico-go/lib/resources/tier.go b/libcalico-go/lib/resources/tier.go index 8b23077635e..2b872e57ced 100644 --- a/libcalico-go/lib/resources/tier.go +++ b/libcalico-go/lib/resources/tier.go @@ -20,7 +20,7 @@ import ( func DefaultTierFields(res *apiv3.Tier) { // nil order was allowed before, and it was used for the default tier. - // For the implementation of BaselineAdminNetworkPolicy, we need to add a tier + // For the implementation of ClusterNetworkPolicy, we need to add a tier // after the default one. As such, the default tier order is changed to 1,000,000 from nil. // To keep the behavior in sync with user defined tiers with nil order, nil order is // treated similar to the value of 1,000,000. diff --git a/libcalico-go/lib/selector/parser/ast.go b/libcalico-go/lib/selector/parser/ast.go index a32fcf47ec8..94514a4d0fd 100644 --- a/libcalico-go/lib/selector/parser/ast.go +++ b/libcalico-go/lib/selector/parser/ast.go @@ -17,7 +17,10 @@ package parser import ( _ "crypto/sha256" // register hash func "fmt" + "iter" + "maps" "strings" + "sync" log "github.com/sirupsen/logrus" @@ -39,11 +42,34 @@ func (l MapAsLabels) GetHandle(labelName uniquestr.Handle) (handle uniquestr.Han return uniquestr.Make(value), present } -type LabelRestrictions map[uniquestr.Handle]LabelRestriction +// LabelRestrictions wraps the label restriction map, preventing callers from +// modifying the underlying map (which may be cached). +type LabelRestrictions struct { + m map[uniquestr.Handle]LabelRestriction +} + +// MakeLabelRestrictions creates a LabelRestrictions wrapper around the given map. +// The caller should not modify the map after passing it to this function. +func MakeLabelRestrictions(m map[uniquestr.Handle]LabelRestriction) LabelRestrictions { + return LabelRestrictions{m: m} +} + +func (lr LabelRestrictions) All() iter.Seq2[uniquestr.Handle, LabelRestriction] { + return maps.All(lr.m) +} + +func (lr LabelRestrictions) Get(key uniquestr.Handle) (LabelRestriction, bool) { + v, ok := lr.m[key] + return v, ok +} + +func (lr LabelRestrictions) Len() int { + return len(lr.m) +} func (lr LabelRestrictions) String() string { m := map[string]LabelRestriction{} - for k, v := range lr { + for k, v := range lr.m { m[k.Value()] = v } return fmt.Sprint(m) @@ -82,7 +108,7 @@ func (r LabelRestriction) PossibleToSatisfy() bool { } type Visitor interface { - Visit(n interface{}) + Visit(n any) } // PrefixVisitor implements the Visitor interface to allow prefixing of @@ -91,7 +117,7 @@ type PrefixVisitor struct { Prefix string } -func (v PrefixVisitor) Visit(n interface{}) { +func (v PrefixVisitor) Visit(n any) { log.Debugf("PrefixVisitor visiting node %#v", n) switch np := n.(type) { case *LabelEqValueNode: @@ -116,10 +142,9 @@ func (v PrefixVisitor) Visit(n interface{}) { } type Selector struct { - root Node - stringRep string - hash string - labelRestrictions LabelRestrictions + root Node + stringRep string + hash string } // Evaluate the selector against the given labels map. @@ -145,8 +170,36 @@ func (sel *Selector) UniqueID() string { return sel.hash } +var ( + lastRestrictionMutex sync.Mutex + lastRestrictionSelector *Selector + lastLabelRestrictions map[uniquestr.Handle]LabelRestriction +) + +// LabelRestrictions returns a set of lower bound restrictions on the labels +// that would match this selector. For example, for "a == 'b' && has(c)", the +// label restrictions would be "a must be present and have value 'b', c must be +// present". +// +// Since it's common to call LabelRestrictions multiple times for the same +// selector in quick succession, we cache the most recently calculated value +// at package level. The cache is thread safe and deals with the calculation +// graph's common usage pattern. func (sel *Selector) LabelRestrictions() LabelRestrictions { - return sel.labelRestrictions + // We used to store the label restrictions in a field, but, if there are many selectors active + // the maps really add up. Calculate them on demand, but cache the most recently calculated + // one because the LabelRestrictions are used multiple times when adding a particular selector + // to the named port index. (Since that is single threaded, caching exactly 1 gives a big win.) + // + // The LabelRestrictions struct wraps the map to prevent callers from accidentally modifying + // the cached map. + lastRestrictionMutex.Lock() + defer lastRestrictionMutex.Unlock() + if lastRestrictionSelector != sel { + lastRestrictionSelector = sel + lastLabelRestrictions = sel.root.LabelRestrictions() + } + return MakeLabelRestrictions(lastLabelRestrictions) } func (sel *Selector) Equal(other *Selector) bool { @@ -165,7 +218,6 @@ func (sel *Selector) updateFields() { str := strings.Join(fragments, "") sel.stringRep = str sel.hash = hash.MakeUniqueID("s", str) - sel.labelRestrictions = sel.root.LabelRestrictions() } type Node interface { diff --git a/libcalico-go/lib/selector/parser/label_restriction_test.go b/libcalico-go/lib/selector/parser/label_restriction_test.go index a37b9604aac..726d5fae3c5 100644 --- a/libcalico-go/lib/selector/parser/label_restriction_test.go +++ b/libcalico-go/lib/selector/parser/label_restriction_test.go @@ -123,10 +123,11 @@ func TestLabelRestrictions(t *testing.T) { lrs := sel.LabelRestrictions() var res LabelRestrictions if test.Res != nil { - res = LabelRestrictions{} + m := map[uniquestr.Handle]LabelRestriction{} for k, v := range test.Res { - res[uniquestr.Make(k)] = v + m[uniquestr.Make(k)] = v } + res = MakeLabelRestrictions(m) } Expect(lrs).To(Equal(res), fmt.Sprintf("Selector %s should produce restrictions: %v", test.Sel, test.Res)) lrs = sel.LabelRestrictions() @@ -149,6 +150,42 @@ func TestLabelRestrictions(t *testing.T) { } } +func TestLabelRestrictionsCache(t *testing.T) { + RegisterTestingT(t) + + selA, err := Parse("has(a)") + Expect(err).NotTo(HaveOccurred()) + selB, err := Parse("has(b)") + Expect(err).NotTo(HaveOccurred()) + + // Clear cache. + lastRestrictionSelector = nil + lastLabelRestrictions = nil + + // First call should populate the cache. + lrsA1 := selA.LabelRestrictions() + Expect(lastRestrictionSelector).To(BeIdenticalTo(selA)) + + // Second call to the same selector should be a cache hit; + // the cached selector pointer should not change. + lrsA2 := selA.LabelRestrictions() + Expect(lastRestrictionSelector).To(BeIdenticalTo(selA), + "cache hit should not change the cached selector") + Expect(lrsA2).To(Equal(lrsA1)) + + // Calling on a different selector should update the cache. + lrsB := selB.LabelRestrictions() + Expect(lastRestrictionSelector).To(BeIdenticalTo(selB), + "cache miss should update the cached selector") + Expect(lrsB).NotTo(Equal(lrsA1)) + + // Going back to selA should update the cache again. + lrsA3 := selA.LabelRestrictions() + Expect(lastRestrictionSelector).To(BeIdenticalTo(selA), + "cache should update when switching back to first selector") + Expect(lrsA3).To(Equal(lrsA1)) +} + func handleSlice(ss ...string) []uniquestr.Handle { var hs = make([]uniquestr.Handle, len(ss)) for i, s := range ss { diff --git a/libcalico-go/lib/selector/parser/parser_suite_test.go b/libcalico-go/lib/selector/parser/parser_suite_test.go index 2874d0a17c8..412c56bc81f 100644 --- a/libcalico-go/lib/selector/parser/parser_suite_test.go +++ b/libcalico-go/lib/selector/parser/parser_suite_test.go @@ -17,13 +17,13 @@ package parser_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" ) func TestParser(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../report/parser_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Parser Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../report/parser_suite.xml" + ginkgo.RunSpecs(t, "Parser Suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/selector/parser/parser_test.go b/libcalico-go/lib/selector/parser/parser_test.go index f09a515d05c..77fc989d3ac 100644 --- a/libcalico-go/lib/selector/parser/parser_test.go +++ b/libcalico-go/lib/selector/parser/parser_test.go @@ -17,8 +17,7 @@ package parser_test import ( "fmt" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/selector/parser" @@ -280,7 +279,6 @@ var _ = Describe("Parser", func() { }) for _, test := range canonicalisationTests { - test := test It(fmt.Sprintf("should canonicalise %v as %v with UID %v and round-trip", test.input, test.expected, test.expectedUid), func() { sel, err := parser.Parse(test.input) @@ -296,7 +294,6 @@ var _ = Describe("Parser", func() { } for _, test := range canonicalisationTests { - test := test if test.expectedUid == "" { continue } diff --git a/libcalico-go/lib/selector/parser/stringset_test.go b/libcalico-go/lib/selector/parser/stringset_test.go index ef57631e11d..f19faa4979d 100644 --- a/libcalico-go/lib/selector/parser/stringset_test.go +++ b/libcalico-go/lib/selector/parser/stringset_test.go @@ -17,8 +17,7 @@ package parser_test import ( "fmt" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/lib/std/uniquestr" diff --git a/libcalico-go/lib/selector/selectors_suite_test.go b/libcalico-go/lib/selector/selectors_suite_test.go index 5f78f4970fb..77c886284c8 100644 --- a/libcalico-go/lib/selector/selectors_suite_test.go +++ b/libcalico-go/lib/selector/selectors_suite_test.go @@ -17,16 +17,16 @@ package selector_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestSelectors(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/selectors_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Selectors Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/selectors_suite.xml" + ginkgo.RunSpecs(t, "Selectors Suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/selector/tokenizer/kind_string.go b/libcalico-go/lib/selector/tokenizer/kind_string.go index 878eac0b9d1..18a434d6fbc 100644 --- a/libcalico-go/lib/selector/tokenizer/kind_string.go +++ b/libcalico-go/lib/selector/tokenizer/kind_string.go @@ -37,8 +37,9 @@ const _Kind_name = "TokNoneTokLabelTokStringLiteralTokLBraceTokRBraceTokCommaTok var _Kind_index = [...]uint8{0, 7, 15, 31, 40, 49, 57, 62, 67, 72, 78, 86, 97, 110, 121, 127, 133, 142, 151, 157, 162, 171, 177} func (i Kind) String() string { - if i < 0 || i >= Kind(len(_Kind_index)-1) { + idx := int(i) - 0 + if i < 0 || idx >= len(_Kind_index)-1 { return "Kind(" + strconv.FormatInt(int64(i), 10) + ")" } - return _Kind_name[_Kind_index[i]:_Kind_index[i+1]] + return _Kind_name[_Kind_index[idx]:_Kind_index[idx+1]] } diff --git a/libcalico-go/lib/selector/tokenizer/tokenizer_suite_test.go b/libcalico-go/lib/selector/tokenizer/tokenizer_suite_test.go index d96482caca9..1c9923d2496 100644 --- a/libcalico-go/lib/selector/tokenizer/tokenizer_suite_test.go +++ b/libcalico-go/lib/selector/tokenizer/tokenizer_suite_test.go @@ -17,13 +17,13 @@ package tokenizer_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" ) func TestTokenizer(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../report/tokenizer_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Tokenizer Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../report/tokenizer_suite.xml" + ginkgo.RunSpecs(t, "Tokenizer Suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/selector/tokenizer/tokenizer_test.go b/libcalico-go/lib/selector/tokenizer/tokenizer_test.go index bcae1cb7737..141c65f92c1 100644 --- a/libcalico-go/lib/selector/tokenizer/tokenizer_test.go +++ b/libcalico-go/lib/selector/tokenizer/tokenizer_test.go @@ -18,7 +18,7 @@ import ( "fmt" "strings" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/selector/tokenizer" @@ -267,7 +267,6 @@ var tokenTests = []struct { var _ = Describe("Token", func() { for _, test := range tokenTests { - test := test // Take copy for closure if test.expected == nil { It(fmt.Sprintf("should return error for input %#v", test.input), func() { _, err := tokenizer.Tokenize(test.input) diff --git a/libcalico-go/lib/set/adaptive.go b/libcalico-go/lib/set/adaptive.go index 35c9878c400..4905b41030c 100644 --- a/libcalico-go/lib/set/adaptive.go +++ b/libcalico-go/lib/set/adaptive.go @@ -15,6 +15,8 @@ package set import ( + "iter" + "slices" "unsafe" ) @@ -92,11 +94,9 @@ func (a *Adaptive[T]) Add(item T) { tSlice := a.loadSliceFromPointer() // First scan to see if the item is already present. - for _, t := range tSlice { - if t == item { - // The element is already in the set. - return - } + if slices.Contains(tSlice, item) { + // The element is already in the set. + return } // If we get here, need to add the element to the set. @@ -138,10 +138,9 @@ func (a *Adaptive[T]) AddAll(itemArray []T) { } func (a *Adaptive[T]) AddSet(other Set[T]) { - other.Iter(func(item T) error { + for item := range other.All() { a.Add(item) - return nil - }) + } } func (a *Adaptive[T]) Discard(item T) { @@ -229,12 +228,7 @@ func (a *Adaptive[T]) Contains(t T) bool { tSlice := a.loadSliceFromPointer() // Scan for the item. - for _, v := range tSlice { - if v == t { - return true - } - } - return false + return slices.Contains(tSlice, t) } } @@ -281,6 +275,37 @@ func (a *Adaptive[T]) Iter(f func(item T) error) { } } +// All returns an iterator for use with Go's range-over-func feature. +// The iterator supports discarding from the set during iteration without panicking. +func (a *Adaptive[T]) All() iter.Seq[T] { + return func(yield func(T) bool) { + switch a.size { + case 0: + return + case sizeStoredInMap: + // Map-backed: safe to iterate and mutate directly + m := a.loadMapFromPointer() + for v := range m { + if !yield(v) { + return + } + } + default: + // Array-backed: take a snapshot to allow safe mutation during iteration + tSlice := a.loadSliceFromPointer() + var tCopy [adaptiveSetArrayLimit]T + copy(tCopy[:], tSlice) + tSlice = tCopy[:a.size] + + for _, v := range tSlice { + if !yield(v) { + return + } + } + } + } +} + // loadSliceFromPointer loads the array stored in our unsafe pointer, returning // it as a slice with len == a.size and cap == arrCapForSize[a.size]. func (a *Adaptive[T]) loadSliceFromPointer() []T { @@ -304,10 +329,9 @@ func (a *Adaptive[T]) loadMapFromPointer() map[T]v { func (a *Adaptive[T]) Copy() Set[T] { other := NewAdaptive[T]() - a.Iter(func(item T) error { + for item := range a.All() { other.Add(item) - return nil - }) + } return other } @@ -316,13 +340,12 @@ func (a *Adaptive[T]) Equals(s Set[T]) bool { return false } equal := true - a.Iter(func(item T) error { + for item := range a.All() { if !s.Contains(item) { equal = false - return StopIteration + break } - return nil - }) + } return equal } @@ -331,22 +354,20 @@ func (a *Adaptive[T]) ContainsAll(s Set[T]) bool { return false } seenAll := true - s.Iter(func(item T) error { + for item := range s.All() { if !a.Contains(item) { seenAll = false - return StopIteration + break } - return nil - }) + } return seenAll } func (a *Adaptive[T]) Slice() []T { s := make([]T, 0, a.Len()) - a.Iter(func(item T) error { + for item := range a.All() { s = append(s, item) - return nil - }) + } return s } diff --git a/libcalico-go/lib/set/adaptive_test.go b/libcalico-go/lib/set/adaptive_test.go index 05ceeddcacd..3b40e465a86 100644 --- a/libcalico-go/lib/set/adaptive_test.go +++ b/libcalico-go/lib/set/adaptive_test.go @@ -18,7 +18,7 @@ import ( "sort" "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" "github.com/projectcalico/calico/libcalico-go/lib/set" ) @@ -47,6 +47,81 @@ var _ = Describe("Adaptive set", func() { Fail("Sets are not equal") } }) + + Describe("Adaptive.All() iterator", func() { + It("should iterate over small array-backed set", func() { + s := set.AdaptiveFrom(1, 2, 3) + seen := make(map[int]bool) + for item := range s.All() { + seen[item] = true + } + if len(seen) != 3 { + Fail("Did not see all items") + } + }) + + It("should allow discarding during iteration of array-backed set", func() { + s := set.AdaptiveFrom(1, 2, 3, 4, 5) + for item := range s.All() { + if item%2 == 0 { + s.Discard(item) + } + } + if s.Len() != 3 { + Fail("Expected 3 items remaining") + } + if !s.Contains(1) || !s.Contains(3) || !s.Contains(5) { + Fail("Expected odd numbers to remain") + } + }) + + It("should iterate over large map-backed set", func() { + // Create a set large enough to trigger map backing (>16 items) + items := make([]int, 20) + for i := range items { + items[i] = i + } + s := set.AdaptiveFromArray(items) + seen := make(map[int]bool) + for item := range s.All() { + seen[item] = true + } + if len(seen) != 20 { + Fail("Did not see all items") + } + }) + + It("should allow discarding during iteration of map-backed set", func() { + // Create a set large enough to trigger map backing (>16 items) + items := make([]int, 20) + for i := range items { + items[i] = i + } + s := set.AdaptiveFromArray(items) + for item := range s.All() { + if item%2 == 0 { + s.Discard(item) + } + } + if s.Len() != 10 { + Fail("Expected 10 items remaining") + } + }) + + It("should support early termination", func() { + s := set.AdaptiveFrom(1, 2, 3, 4, 5) + count := 0 + for range s.All() { + count++ + if count >= 2 { + break + } + } + if count != 2 { + Fail("Expected to stop after 2 iterations") + } + }) + }) }) func FuzzAdaptiveSet(f *testing.F) { @@ -68,12 +143,11 @@ func FuzzAdaptiveSet(f *testing.F) { if !s1.Equals(s2) || !s2.Equals(s1) { t.Fatal("Sets are not equal") } - s1.Iter(func(item string) error { + for item := range s1.All() { if !s2.Contains(item) { t.Fatal("Set 2 does not contain item") } - return nil - }) + } if !s1.Contains(item) { t.Fatal("Set does not contain item") } @@ -88,7 +162,7 @@ func FuzzAdaptiveSet(f *testing.F) { } sort.Strings(sl1) sort.Strings(sl2) - for i := 0; i < len(sl1); i++ { + for i := range sl1 { if sl1[i] != sl2[i] { t.Fatal("Slices are not the same") } diff --git a/libcalico-go/lib/set/benchmarks_test.go b/libcalico-go/lib/set/benchmarks_test.go index d522f7d4873..9eba880a490 100644 --- a/libcalico-go/lib/set/benchmarks_test.go +++ b/libcalico-go/lib/set/benchmarks_test.go @@ -67,7 +67,7 @@ func benchmarkSet(b *testing.B, factory func() set.Set[int], items int) { var s set.Set[int] for i := 0; i < b.N; i++ { s = factory() - for j := 0; j < items; j++ { + for j := range items { s.Add(j) } } @@ -76,7 +76,7 @@ func benchmarkSet(b *testing.B, factory func() set.Set[int], items int) { b.Run("Contains", func(b *testing.B) { b.ReportAllocs() s := factory() - for j := 0; j < items; j++ { + for j := range items { s.Add(j) } var x bool diff --git a/libcalico-go/lib/set/diff_test.go b/libcalico-go/lib/set/diff_test.go index 60fdae0cde3..486f599f971 100644 --- a/libcalico-go/lib/set/diff_test.go +++ b/libcalico-go/lib/set/diff_test.go @@ -15,7 +15,7 @@ package set_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/set" diff --git a/libcalico-go/lib/set/interface.go b/libcalico-go/lib/set/interface.go index 4521dbf71a5..ef808ffdd67 100644 --- a/libcalico-go/lib/set/interface.go +++ b/libcalico-go/lib/set/interface.go @@ -14,6 +14,7 @@ package set import ( "errors" "fmt" + "iter" ) type Set[T any] interface { @@ -25,6 +26,9 @@ type Set[T any] interface { Clear() Contains(T) bool Iter(func(item T) error) + // All returns an iterator for use with Go's range-over-func feature. + // The iterator supports deletion from the set during iteration without panicking. + All() iter.Seq[T] Copy() Set[T] Equals(Set[T]) bool ContainsAll(Set[T]) bool diff --git a/libcalico-go/lib/set/set.go b/libcalico-go/lib/set/set.go index 6dad4eb525f..05b7516c054 100644 --- a/libcalico-go/lib/set/set.go +++ b/libcalico-go/lib/set/set.go @@ -17,6 +17,7 @@ package set import ( "bytes" "fmt" + "iter" log "github.com/sirupsen/logrus" ) @@ -56,15 +57,14 @@ func stringify[T any](set Set[T]) string { var buf bytes.Buffer _, _ = buf.WriteString("set.Set{") first := true - set.Iter(func(item T) error { + for item := range set.All() { if !first { buf.WriteString(",") } else { first = false } _, _ = fmt.Fprint(&buf, item) - return nil - }) + } _, _ = buf.WriteString("}") return buf.String() } @@ -85,10 +85,9 @@ func (set Typed[T]) AddAll(itemArray []T) { // AddSet adds the contents of set "other" into the set. func (set Typed[T]) AddSet(other Set[T]) { - other.Iter(func(item T) error { + for item := range other.All() { set.Add(item) - return nil - }) + } } func (set Typed[T]) Discard(item T) { @@ -121,6 +120,19 @@ loop: } } +// All returns an iterator for use with Go's range-over-func feature. +// The iterator supports discarding from the set during iteration without panicking, +// since the underlying map allows safe mutation during iteration. +func (set Typed[T]) All() iter.Seq[T] { + return func(yield func(T) bool) { + for item := range set { + if !yield(item) { + return + } + } + } +} + func (set Typed[T]) Copy() Set[T] { cpy := New[T]() for item := range set { @@ -152,13 +164,10 @@ func (set Typed[T]) ContainsAll(other Set[T]) bool { if other.Len() > set.Len() { return false } - result := true - other.Iter(func(item T) error { + for item := range other.All() { if !set.Contains(item) { - result = false - return StopIteration + return false } - return nil - }) - return result + } + return true } diff --git a/libcalico-go/lib/set/set_suite_test.go b/libcalico-go/lib/set/set_suite_test.go index f4ba8f52259..1e87b1a5abc 100644 --- a/libcalico-go/lib/set/set_suite_test.go +++ b/libcalico-go/lib/set/set_suite_test.go @@ -17,16 +17,16 @@ package set_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestSet(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/set_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Set Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/set_suite.xml" + ginkgo.RunSpecs(t, "Set Suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/set/set_test.go b/libcalico-go/lib/set/set_test.go index 8b2d46e1b3d..192e3c7b577 100644 --- a/libcalico-go/lib/set/set_test.go +++ b/libcalico-go/lib/set/set_test.go @@ -17,7 +17,7 @@ package set_test import ( "fmt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/set" @@ -49,10 +49,9 @@ func describeSetTests( }) It("should iterate over no items", func() { called := false - s.Iter(func(item int) error { + for range s.All() { called = true - return nil - }) + } Expect(called).To(BeFalse()) }) It("should do nothing on clear", func() { @@ -152,7 +151,7 @@ func describeSetTests( It("should iterate over 1 and 2 in some order", func() { seen1 := false seen2 := false - s.Iter(func(item int) error { + for item := range s.All() { if item == 1 { Expect(seen1).To(BeFalse()) seen1 = true @@ -162,8 +161,7 @@ func describeSetTests( } else { Fail("Unexpected item") } - return nil - }) + } Expect(seen1).To(BeTrue()) Expect(seen2).To(BeTrue()) }) @@ -288,7 +286,7 @@ var _ = Describe("EmptySet", func() { Expect(func() { empty.Discard("foo") }).NotTo(Panic()) }) It("should iterate 0 times", func() { - empty.Iter(func(item interface{}) error { + empty.Iter(func(item any) error { Fail("Iterated > 0 times") return nil }) @@ -296,4 +294,88 @@ var _ = Describe("EmptySet", func() { It("should stringify", func() { Expect(empty.String()).To(Equal("set.Set{}")) }) + It("should iterate 0 times with All()", func() { + for range empty.All() { + Fail("Iterated > 0 times") + } + }) +}) + +var _ = Describe("Set.All() iterator", func() { + It("should iterate over empty set", func() { + s := set.New[int]() + count := 0 + for range s.All() { + count++ + } + Expect(count).To(Equal(0)) + }) + + It("should iterate over all elements", func() { + s := set.From(1, 2, 3, 4, 5) + seen := make(map[int]bool) + for item := range s.All() { + seen[item] = true + } + Expect(seen).To(HaveLen(5)) + Expect(seen[1]).To(BeTrue()) + Expect(seen[2]).To(BeTrue()) + Expect(seen[3]).To(BeTrue()) + Expect(seen[4]).To(BeTrue()) + Expect(seen[5]).To(BeTrue()) + }) + + It("should support early termination", func() { + s := set.From(1, 2, 3, 4, 5) + count := 0 + for range s.All() { + count++ + if count >= 2 { + break + } + } + Expect(count).To(Equal(2)) + Expect(s.Len()).To(Equal(5)) // Set should be unchanged + }) + + It("should allow discarding during iteration", func() { + s := set.From(1, 2, 3, 4, 5) + for item := range s.All() { + if item%2 == 0 { + s.Discard(item) + } + } + Expect(s.Len()).To(Equal(3)) + Expect(s.Contains(1)).To(BeTrue()) + Expect(s.Contains(2)).To(BeFalse()) + Expect(s.Contains(3)).To(BeTrue()) + Expect(s.Contains(4)).To(BeFalse()) + Expect(s.Contains(5)).To(BeTrue()) + }) + + It("should allow discarding of all items during iteration", func() { + s := set.From(1, 2, 3, 4, 5) + count := 0 + for item := range s.All() { + s.Discard(item) + count++ + } + Expect(count).To(Equal(5)) + Expect(s.Len()).To(Equal(0)) + }) + + It("should allow addition during iteration", func() { + s := set.From(1, 2, 3) + count := 0 + for item := range s.All() { + count++ + if item == 1 { + s.Add(100) // May or may not be visited depending on map iteration order + } + } + // Count will be at least 3 (original items), but may include 100 if it's visited + Expect(count).To(BeNumerically(">=", 3)) + Expect(count).To(BeNumerically("<=", 4)) + Expect(s.Contains(100)).To(BeTrue()) + }) }) diff --git a/libcalico-go/lib/set/union.go b/libcalico-go/lib/set/union.go index 5aaf16db8c0..2fd0810c4a1 100644 --- a/libcalico-go/lib/set/union.go +++ b/libcalico-go/lib/set/union.go @@ -27,12 +27,11 @@ func IterUnion[T comparable](sets []Set[T], f func(item T) bool) { } if len(sets) == 1 { - sets[0].Iter(func(item T) error { + for item := range sets[0].All() { if !f(item) { - return StopIteration + break } - return nil - }) + } return } @@ -46,20 +45,20 @@ func IterUnion[T comparable](sets []Set[T], f func(item T) bool) { }) stop := false for i, s1 := range sets { - s1.Iter(func(item T) error { + itemsLoop: + for item := range s1.All() { // To check if we've seen this item before, look for it in // the sets we've already scanned. - for j := 0; j < i; j++ { + for j := range i { if sets[j].Contains(item) { - return nil + continue itemsLoop } } if !f(item) { stop = true - return StopIteration + break } - return nil - }) + } if stop { return } @@ -71,19 +70,18 @@ func IterUnion[T comparable](sets []Set[T], f func(item T) bool) { seen := New[T]() stop := false for i, s := range sets { - s.Iter(func(item T) error { + for item := range s.All() { if i != 0 && seen.Contains(item) { - return nil + continue } if !f(item) { stop = true - return StopIteration + break } if i < len(sets)-1 { seen.Add(item) } - return nil - }) + } if stop { return } diff --git a/libcalico-go/lib/set/union_test.go b/libcalico-go/lib/set/union_test.go index ba92c0c98bd..4658c1978c7 100644 --- a/libcalico-go/lib/set/union_test.go +++ b/libcalico-go/lib/set/union_test.go @@ -36,27 +36,28 @@ func TestIterUnion(t *testing.T) { {{1, 2}, {2}, {1, 2, 3}, {2, 3, 4, 5}, {2, 6}}, {{1, 2}, {2}, {1, 2, 3}, {2, 3, 4, 5}, {2, 6}, {2, 3}}, } { - testSets := testSets // First sub-test verifies the actual union is correct. t.Run(fmt.Sprint(testSets), func(t *testing.T) { - expected := New[int]() - var sets []Set[int] - for _, i := range testSets { - // We trust FromArray in this test; it is tested elsewhere... - sets = append(sets, FromArray(i)) - // Trivial implementation of union for us to compare against. - for _, item := range i { - expected.Add(item) + for range 100 { + expected := New[int]() + var sets []Set[int] + for _, i := range testSets { + // We trust FromArray in this test; it is tested elsewhere... + sets = append(sets, FromArray(i)) + // Trivial implementation of union for us to compare against. + for _, item := range i { + expected.Add(item) + } } + actual := New[int]() + IterUnion(sets, func(item int) bool { + Expect(actual.Contains(item)).To(BeFalse(), fmt.Sprintf("IterUnion produced duplicate value: %v", item)) + actual.Add(item) + return true + }) + Expect(actual).To(Equal(expected), fmt.Sprintf("Union of %v was incorrect", sets)) } - actual := New[int]() - IterUnion(sets, func(item int) bool { - Expect(actual.Contains(item)).To(BeFalse(), fmt.Sprintf("IterUnion produced duplicate value: %v", item)) - actual.Add(item) - return true - }) - Expect(actual).To(Equal(expected), fmt.Sprintf("Union of %v was incorrect", sets)) }) // Second sub-test verifies that we can stop by returning false. diff --git a/libcalico-go/lib/testutils/e2e_describe.go b/libcalico-go/lib/testutils/e2e_describe.go index c48273655a7..3b1eb937d95 100644 --- a/libcalico-go/lib/testutils/e2e_describe.go +++ b/libcalico-go/lib/testutils/e2e_describe.go @@ -11,13 +11,14 @@ // 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 testutils import ( "fmt" "os" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" @@ -65,7 +66,8 @@ func E2eDatastoreDescribe(description string, datastores DatastoreType, body fun Spec: apiconfig.CalicoAPIConfigSpec{ DatastoreType: apiconfig.Kubernetes, KubeConfig: apiconfig.KubeConfig{ - Kubeconfig: kubeconfig, + Kubeconfig: kubeconfig, + CalicoAPIGroup: os.Getenv("CALICO_API_GROUP"), }, }, }) diff --git a/libcalico-go/lib/testutils/ginkgolog.go b/libcalico-go/lib/testutils/ginkgolog.go index 810bea62733..cd2da2ab329 100644 --- a/libcalico-go/lib/testutils/ginkgolog.go +++ b/libcalico-go/lib/testutils/ginkgolog.go @@ -15,7 +15,7 @@ package testutils import ( - "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/v2" "github.com/sirupsen/logrus" "github.com/projectcalico/calico/libcalico-go/lib/logutils" diff --git a/libcalico-go/lib/testutils/resources.go b/libcalico-go/lib/testutils/resources.go index 174883e83c0..f13fbbe72dd 100644 --- a/libcalico-go/lib/testutils/resources.go +++ b/libcalico-go/lib/testutils/resources.go @@ -20,14 +20,14 @@ import ( "sync" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - "github.com/projectcalico/go-yaml-wrapper" log "github.com/sirupsen/logrus" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/yaml" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" "github.com/projectcalico/calico/libcalico-go/lib/watch" @@ -37,23 +37,25 @@ const ExpectNoNamespace = "" type resourceMatcher struct { kind, namespace, name string - spec interface{} - status interface{} + spec any + status any } -func Resource(kind, namespace, name string, spec interface{}, optionalDescription ...interface{}) *resourceMatcher { +func Resource(kind, namespace, name string, spec any, optionalDescription ...any) *resourceMatcher { return &resourceMatcher{kind, namespace, name, spec, nil} } -func ResourceWithStatus(kind, namespace, name string, spec, status interface{}, optionalDescription ...interface{}) *resourceMatcher { +func ResourceWithStatus(kind, namespace, name string, spec, status any, optionalDescription ...any) *resourceMatcher { return &resourceMatcher{kind, namespace, name, spec, status} } // Another name for the same matcher (which reads better when checking a single item). -var MatchResource = Resource -var MatchResourceWithStatus = ResourceWithStatus +var ( + MatchResource = Resource + MatchResourceWithStatus = ResourceWithStatus +) -func (m *resourceMatcher) Match(actual interface{}) (success bool, err error) { +func (m *resourceMatcher) Match(actual any) (success bool, err error) { // 'actual' here may be a resource struct like v3.HostEndpoint, or a pointer to a resource // struct. If it's a pointer we can immediately convert it to runtime.Object. res, ok := actual.(runtime.Object) @@ -65,23 +67,48 @@ func (m *resourceMatcher) Match(actual interface{}) (success bool, err error) { res = ptr.Interface().(runtime.Object) } ma := res.(v1.ObjectMetaAccessor) - success = (ma.GetObjectMeta().GetNamespace() == m.namespace) && - (ma.GetObjectMeta().GetName() == m.name) && - (ma.GetObjectMeta().GetResourceVersion() != "") && - (res.GetObjectKind().GroupVersionKind().Kind == m.kind) && - (res.GetObjectKind().GroupVersionKind().Group == apiv3.Group) && - (res.GetObjectKind().GroupVersionKind().Version == apiv3.VersionCurrent) && - (m.spec == nil || reflect.DeepEqual(getSpec(res), m.spec)) && - (m.status == nil || reflect.DeepEqual(getStatus(res), m.status)) + success = true + if ma.GetObjectMeta().GetNamespace() != m.namespace { + success = false + err = fmt.Errorf("namespace does not match (expected %q, got %q)", m.namespace, ma.GetObjectMeta().GetNamespace()) + } + if ma.GetObjectMeta().GetName() != m.name { + success = false + err = fmt.Errorf("name does not match (expected %q, got %q)", m.name, ma.GetObjectMeta().GetName()) + } + if ma.GetObjectMeta().GetResourceVersion() == "" { + success = false + err = fmt.Errorf("resource version is empty") + } + if res.GetObjectKind().GroupVersionKind().Kind != m.kind { + success = false + err = fmt.Errorf("kind does not match (expected %q, got %q)", m.kind, res.GetObjectKind().GroupVersionKind().Kind) + } + if res.GetObjectKind().GroupVersionKind().Group != apiv3.Group { + success = false + err = fmt.Errorf("group does not match (expected %q, got %q)", apiv3.Group, res.GetObjectKind().GroupVersionKind().Group) + } + if res.GetObjectKind().GroupVersionKind().Version != apiv3.VersionCurrent { + success = false + err = fmt.Errorf("version does not match (expected %q, got %q)", apiv3.VersionCurrent, res.GetObjectKind().GroupVersionKind().Version) + } + if m.spec != nil && !reflect.DeepEqual(getSpec(res), m.spec) { + success = false + err = fmt.Errorf("spec does not match (expected %+v, got %+v)", m.spec, getSpec(res)) + } + if m.status != nil && !reflect.DeepEqual(getStatus(res), m.status) { + success = false + err = fmt.Errorf("status does not match (expected %+v, got %+v)", m.status, getStatus(res)) + } return } -func (m *resourceMatcher) FailureMessage(actual interface{}) (message string) { +func (m *resourceMatcher) FailureMessage(actual any) (message string) { message = fmt.Sprintf("Expected\n\t%#v\nto match\n\t%#v", actual, m) return } -func (m *resourceMatcher) NegatedFailureMessage(actual interface{}) (message string) { +func (m *resourceMatcher) NegatedFailureMessage(actual any) (message string) { message = fmt.Sprintf("Expected\n\t%#v\nnot to match\n\t%#v", actual, m) return } @@ -227,7 +254,7 @@ func (t *testResourceWatcher) expectEvents(kind string, anyOrder bool, expectedE } // Fail the test. - Expect(len(t.events)).To(Equal(len(expectedEvents))) + ExpectWithOffset(2, len(t.events)).To(Equal(len(expectedEvents))) } else { actualEvents = t.events } @@ -280,7 +307,7 @@ func (t *testResourceWatcher) expectEvents(kind string, anyOrder bool, expectedE } // And verify we got the correct number of events. - Expect(actualEvents).To(HaveLen(len(expectedEvents))) + ExpectWithOffset(2, actualEvents).To(HaveLen(len(expectedEvents))) for i, expectedEvent := range expectedEvents { actualEvent := actualEvents[i] @@ -290,8 +317,8 @@ func (t *testResourceWatcher) expectEvents(kind string, anyOrder bool, expectedE Expect(actualEvent.Type).To(Equal(expectedEvent.Type), traceString) if expectedEvent.Object != nil { - Expect(actualEvent.Object).NotTo(BeNil(), traceString) - Expect(actualEvent.Object).To(MatchResourceWithStatus( + ExpectWithOffset(2, actualEvent.Object).NotTo(BeNil(), traceString) + ExpectWithOffset(2, actualEvent.Object).To(MatchResourceWithStatus( kind, expectedEvent.Object.(v1.ObjectMetaAccessor).GetObjectMeta().GetNamespace(), expectedEvent.Object.(v1.ObjectMetaAccessor).GetObjectMeta().GetName(), @@ -300,14 +327,14 @@ func (t *testResourceWatcher) expectEvents(kind string, anyOrder bool, expectedE traceString, )) } else { - Expect(actualEvent.Object).To(BeNil(), traceString) + ExpectWithOffset(2, actualEvent.Object).To(BeNil(), traceString) } // Kubernetes does not provide the "previous" value in a modified event, so don't // check for that if the datastore is KDD. if expectedEvent.Previous != nil && (expectedEvent.Type == watch.Deleted || t.datastoreType != apiconfig.Kubernetes) { - Expect(actualEvent.Previous).NotTo(BeNil(), traceString) - Expect(actualEvent.Previous).To(MatchResourceWithStatus( + ExpectWithOffset(2, actualEvent.Previous).NotTo(BeNil(), traceString) + ExpectWithOffset(2, actualEvent.Previous).To(MatchResourceWithStatus( kind, expectedEvent.Previous.(v1.ObjectMetaAccessor).GetObjectMeta().GetNamespace(), expectedEvent.Previous.(v1.ObjectMetaAccessor).GetObjectMeta().GetName(), @@ -316,7 +343,7 @@ func (t *testResourceWatcher) expectEvents(kind string, anyOrder bool, expectedE traceString, )) } else { - Expect(actualEvent.Previous).To(BeNil(), traceString) + ExpectWithOffset(2, actualEvent.Previous).To(BeNil(), traceString) } } @@ -354,7 +381,7 @@ func (t *testResourceWatcher) sortEvents(events []watch.Event) []watch.Event { } // getSpec returns the Spec structure from the supplied resource. -func getSpec(res runtime.Object) interface{} { +func getSpec(res runtime.Object) any { v, err := conversion.EnforcePtr(res) Expect(err).NotTo(HaveOccurred()) @@ -366,7 +393,7 @@ func getSpec(res runtime.Object) interface{} { } // getStatus returns the Status structure from the supplied resource. -func getStatus(res runtime.Object) interface{} { +func getStatus(res runtime.Object) any { v, err := conversion.EnforcePtr(res) Expect(err).NotTo(HaveOccurred()) diff --git a/libcalico-go/lib/testutils/stacktrace/stacktrace.go b/libcalico-go/lib/testutils/stacktrace/stacktrace.go new file mode 100644 index 00000000000..071c27941f6 --- /dev/null +++ b/libcalico-go/lib/testutils/stacktrace/stacktrace.go @@ -0,0 +1,40 @@ +package stacktrace + +import ( + "fmt" + "runtime" + "strings" +) + +// MiniStackStrace returns a short stack trace showing the first couple of callers +// that don't contain any element of filesToSkip in their file names. +func MiniStackStrace(filesToSkip ...string) string { + // Find the first/second caller outside this package. +callerLoop: + for i := 2; ; i++ { + _, file, line, ok := runtime.Caller(i) + if !ok { + return "unknown:0" + } + if strings.Contains(file, "/stacktrace/") { + continue + } + for _, pkg := range filesToSkip { + if strings.Contains(file, pkg) { + continue callerLoop + } + } + parts := strings.Split(file, "/") + file = parts[len(parts)-1] + firstCaller := fmt.Sprintf("%s:%d", file, line) + + _, file, line, ok = runtime.Caller(i + 1) + if !ok || !strings.Contains(file, "/calico/") { + return firstCaller + } else { + parts := strings.Split(file, "/") + file = parts[len(parts)-1] + return fmt.Sprintf("%s:%d>%s", file, line, firstCaller) + } + } +} diff --git a/libcalico-go/lib/testutils/syncertester.go b/libcalico-go/lib/testutils/syncertester.go index 63f0a8c68e1..904a4aebb70 100644 --- a/libcalico-go/lib/testutils/syncertester.go +++ b/libcalico-go/lib/testutils/syncertester.go @@ -18,19 +18,20 @@ import ( "encoding/json" "errors" "fmt" + "maps" "reflect" "strings" "sync" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" gomegatypes "github.com/onsi/gomega/types" log "github.com/sirupsen/logrus" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" ) @@ -280,7 +281,7 @@ func (st *SyncerTester) ExpectData(kvp model.KVPair) { // ExpectPath verifies that a KVPair with a specified path is in the cache. This will mark the cache entry as "Seen". func (st *SyncerTester) ExpectPath(path string) { - kv := func() interface{} { + kv := func() any { return st.GetCacheKVPair(path) } EventuallyWithOffset(1, kv, "6s", "20ms").ShouldNot(BeNil()) @@ -292,7 +293,7 @@ func (st *SyncerTester) ExpectValueMatches(k model.Key, match gomegatypes.Gomega key, err := model.KeyToDefaultPath(k) ExpectWithOffset(1, err).NotTo(HaveOccurred()) - value := func() interface{} { + value := func() any { return st.GetCacheValue(key) } @@ -323,7 +324,7 @@ func (st *SyncerTester) GetCacheKVPair(k string) *model.KVPair { } // GetCacheValue returns the value of the KVPair from the cache or nil if not present. -func (st *SyncerTester) GetCacheValue(k string) interface{} { +func (st *SyncerTester) GetCacheValue(k string) any { st.lock.Lock() defer st.lock.Unlock() return st.cache[k].Value @@ -334,9 +335,7 @@ func (st *SyncerTester) CacheSnapshot() map[string]CacheEntry { st.lock.Lock() defer st.lock.Unlock() cacheCopy := map[string]CacheEntry{} - for k, v := range st.cache { - cacheCopy[k] = v - } + maps.Copy(cacheCopy, st.cache) return cacheCopy } @@ -418,11 +417,8 @@ func (st *SyncerTester) hasUpdates(expectedUpdates []api.Update, checkOrder bool // If we need to check the order, let's do that now - failing at the first miss. if checkOrder { - num := len(actualUpdates) - if len(expectedUpdates) < num { - num = len(expectedUpdates) - } - for i := 0; i < num; i++ { + num := min(len(expectedUpdates), len(actualUpdates)) + for i := range num { if !updatesEqual(actualUpdates[i], expectedUpdates[i]) { errs = append(errs, fmt.Sprintf( "Incorrect order of updates at index %d;\nExpected:\n%v;\nReceived:\n%v", @@ -585,7 +581,7 @@ func isExternallyControlled(key model.Key) bool { return true case model.ResourceKey: switch key.(model.ResourceKey).Kind { - case libapiv3.KindNode, model.KindKubernetesEndpointSlice, model.KindKubernetesService: + case internalapi.KindNode, model.KindKubernetesEndpointSlice, model.KindKubernetesService: return true } } @@ -701,6 +697,7 @@ func kvpsEqual(actual, expected model.KVPair) bool { // We should ignore the actual value of the field. actual.Value.(*model.AllocationBlock).SequenceNumber = 0 actual.Value.(*model.AllocationBlock).SequenceNumberForAllocation = nil + actual.Value.(*model.AllocationBlock).AffinityClaimTime = nil return reflect.DeepEqual(actual.Value, expected.Value) default: diff --git a/libcalico-go/lib/upgrade/converters/bgppeer.go b/libcalico-go/lib/upgrade/converters/bgppeer.go deleted file mode 100644 index 0ff52fb4a73..00000000000 --- a/libcalico-go/lib/upgrade/converters/bgppeer.go +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 converters - -import ( - "fmt" - - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - log "github.com/sirupsen/logrus" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - apiv1 "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/errors" - "github.com/projectcalico/calico/libcalico-go/lib/scope" -) - -// BGPPeer implements the Converter interface. -type BGPPeer struct{} - -// APIV1ToBackendV1 converts v1 BGPPeer API to v1 BGPPeer KVPair. -func (bp BGPPeer) APIV1ToBackendV1(rIn unversioned.Resource) (*model.KVPair, error) { - ap, ok := rIn.(*apiv1.BGPPeer) - if !ok { - return nil, fmt.Errorf("Conversion to BGPPeer is not possible with %v", rIn) - } - - if len(ap.Metadata.PeerIP.IP) == 0 { - return nil, fmt.Errorf("no PeerIP is set, invalid BGPPeer: %v", rIn) - } - - k, err := bp.convertMetadataToKey(ap.Metadata) - if err != nil { - return nil, err - } - - d := model.KVPair{ - Key: k, - Value: &model.BGPPeer{ - PeerIP: ap.Metadata.PeerIP, - ASNum: ap.Spec.ASNumber, - }, - } - - log.WithFields(log.Fields{ - "APIV1": rIn, - "KVPair": d, - }).Debug("Converted BGPPeer") - - return &d, nil -} - -func (_ BGPPeer) convertMetadataToKey(bpm apiv1.BGPPeerMetadata) (model.Key, error) { - if bpm.Scope == scope.Global { - if bpm.Node != "" { - return nil, fmt.Errorf("With Global scope having a Node is invalid: %v", bpm) - } - return model.GlobalBGPPeerKey{ - PeerIP: bpm.PeerIP, - }, nil - } else if bpm.Scope == scope.Node { - if bpm.Node == "" { - return nil, fmt.Errorf("With Node scope a Node must be defined: %v", bpm) - } - return model.NodeBGPPeerKey{ - PeerIP: bpm.PeerIP, - Nodename: bpm.Node, - }, nil - } else { - return nil, errors.ErrorInsufficientIdentifiers{ - Name: "scope", - } - } -} - -// BackendV1ToAPIV3 converts v1 BGPPeer KVPair to v3 API. -func (bp BGPPeer) BackendV1ToAPIV3(kvp *model.KVPair) (Resource, error) { - peer, ok := kvp.Value.(*model.BGPPeer) - if !ok { - return nil, fmt.Errorf("value is not a valid BGPPeer resource Value: %v", kvp.Value) - } - - r := apiv3.NewBGPPeer() - r.Spec = apiv3.BGPPeerSpec{ - PeerIP: peer.PeerIP.String(), - ASNumber: peer.ASNum, - } - - switch kvp.Key.(type) { - case model.GlobalBGPPeerKey: - r.ObjectMeta = v1.ObjectMeta{Name: convertIpToName(peer.PeerIP.IP)} - case model.NodeBGPPeerKey: - nk := kvp.Key.(model.NodeBGPPeerKey) - - // Node names are normalized but we don't add any qualifying hashes (so we just use - // the normalizeName function to convert). - n := ConvertNodeName(nk.Nodename) - r.Spec.Node = n - r.ObjectMeta = v1.ObjectMeta{Name: n + "." + convertIpToName(peer.PeerIP.IP)} - default: - return nil, fmt.Errorf("Invalid key for BGPPeer: %v", kvp.Key) - } - - log.WithFields(log.Fields{ - "KVPair": *kvp, - "APIV3": r, - }).Debug("Converted BGPPeer") - return r, nil -} diff --git a/libcalico-go/lib/upgrade/converters/bgppeer_test.go b/libcalico-go/lib/upgrade/converters/bgppeer_test.go deleted file mode 100644 index a5672aaed96..00000000000 --- a/libcalico-go/lib/upgrade/converters/bgppeer_test.go +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 converters - -import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - apiv1 "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/net" - "github.com/projectcalico/calico/libcalico-go/lib/scope" -) - -var _ = DescribeTable("v1->v3 bgp peer conversion tests table (success)", - func(v1API *apiv1.BGPPeer, v1KVP *model.KVPair, v3API apiv3.BGPPeer) { - p := BGPPeer{} - - // Test and assert v1 API to v1 backend logic. - v1KVPResult, err := p.APIV1ToBackendV1(v1API) - Expect(err).NotTo(HaveOccurred()) - md := v1API.Metadata - if md.Scope == "global" { - Expect(v1KVPResult.Key.(model.GlobalBGPPeerKey).PeerIP).To(Equal(v1KVP.Key.(model.GlobalBGPPeerKey).PeerIP)) - } else { - Expect(v1KVPResult.Key.(model.NodeBGPPeerKey).PeerIP).To(Equal(v1KVP.Key.(model.NodeBGPPeerKey).PeerIP)) - } - Expect(v1KVPResult.Value.(*model.BGPPeer)).To(Equal(v1KVP.Value)) - - // Test and assert v1 backend to v3 API logic. - v3APIResult, err := p.BackendV1ToAPIV3(v1KVP) - Expect(err).NotTo(HaveOccurred()) - Expect(v3APIResult.(*apiv3.BGPPeer).Name).To(Equal(v3API.Name)) - Expect(v3APIResult.(*apiv3.BGPPeer).Spec).To(Equal(v3API.Spec)) - }, - - Entry("global scoped BGPPeer", - &apiv1.BGPPeer{ - Metadata: apiv1.BGPPeerMetadata{ - Scope: scope.Global, - PeerIP: *net.ParseIP("10.0.0.1"), - }, - Spec: apiv1.BGPPeerSpec{ - ASNumber: 255, - }, - }, - &model.KVPair{ - Key: model.GlobalBGPPeerKey{ - PeerIP: *net.ParseIP("10.0.0.1"), - }, - Value: &model.BGPPeer{ - PeerIP: *net.ParseIP("10.0.0.1"), - ASNum: 255, - }, - }, - apiv3.BGPPeer{ - ObjectMeta: v1.ObjectMeta{ - Name: "10-0-0-1", - }, - Spec: apiv3.BGPPeerSpec{ - PeerIP: "10.0.0.1", - ASNumber: 255, - }, - }, - ), - Entry("global scoped ipv6 BGPPeer", - &apiv1.BGPPeer{ - Metadata: apiv1.BGPPeerMetadata{ - Scope: scope.Global, - PeerIP: *net.ParseIP("Aa:bb::"), - }, - Spec: apiv1.BGPPeerSpec{ - ASNumber: 255, - }, - }, - &model.KVPair{ - Key: model.GlobalBGPPeerKey{ - PeerIP: *net.ParseIP("Aa:bb::"), - }, - Value: &model.BGPPeer{ - PeerIP: *net.ParseIP("Aa:bb::"), - ASNum: 255, - }, - }, - apiv3.BGPPeer{ - ObjectMeta: v1.ObjectMeta{ - Name: "00aa-00bb-0000-0000-0000-0000-0000-0000", - }, - Spec: apiv3.BGPPeerSpec{ - PeerIP: "aa:bb::", - ASNumber: 255, - }, - }, - ), - Entry("node scoped BGPPeer", - &apiv1.BGPPeer{ - Metadata: apiv1.BGPPeerMetadata{ - Scope: scope.Node, - Node: "namedNode", - PeerIP: *net.ParseIP("10.0.0.1"), - }, - Spec: apiv1.BGPPeerSpec{ - ASNumber: 255, - }, - }, - &model.KVPair{ - Key: model.NodeBGPPeerKey{ - Nodename: "namedNode", - PeerIP: *net.ParseIP("10.0.0.1"), - }, - Value: &model.BGPPeer{ - PeerIP: *net.ParseIP("10.0.0.1"), - ASNum: 255, - }, - }, - apiv3.BGPPeer{ - ObjectMeta: v1.ObjectMeta{ - Name: "namednode.10-0-0-1", - }, - Spec: apiv3.BGPPeerSpec{ - PeerIP: "10.0.0.1", - Node: "namednode", - ASNumber: 255, - }, - }, - ), - Entry("node scoped BGPPeer with ipv6", - &apiv1.BGPPeer{ - Metadata: apiv1.BGPPeerMetadata{ - Scope: scope.Node, - Node: "namedNode", - PeerIP: *net.ParseIP("Aa:bb::"), - }, - Spec: apiv1.BGPPeerSpec{ - ASNumber: 255, - }, - }, - &model.KVPair{ - Key: model.NodeBGPPeerKey{ - Nodename: "namedNode", - PeerIP: *net.ParseIP("Aa:bb::"), - }, - Value: &model.BGPPeer{ - PeerIP: *net.ParseIP("Aa:bb::"), - ASNum: 255, - }, - }, - apiv3.BGPPeer{ - ObjectMeta: v1.ObjectMeta{ - Name: "namednode.00aa-00bb-0000-0000-0000-0000-0000-0000", - }, - Spec: apiv3.BGPPeerSpec{ - PeerIP: "aa:bb::", - Node: "namednode", - ASNumber: 255, - }, - }, - ), -) - -var _ = DescribeTable("v1->v3 bgp peer conversion tests table (fail)", - func(v1API *apiv1.BGPPeer) { - // Failure cases. - p := BGPPeer{} - - // Test and assert v1 API to v1 backend logic. - _, err := p.APIV1ToBackendV1(v1API) - Expect(err).To(HaveOccurred()) - }, - - Entry("missing PeerIP", - &apiv1.BGPPeer{ - Metadata: apiv1.BGPPeerMetadata{ - Scope: scope.Global, - }, - Spec: apiv1.BGPPeerSpec{ - ASNumber: 255, - }, - }, - ), - Entry("scope set global with node specified", - &apiv1.BGPPeer{ - Metadata: apiv1.BGPPeerMetadata{ - Scope: scope.Global, - Node: "namedNode", - PeerIP: *net.ParseIP("10.0.0.1"), - }, - Spec: apiv1.BGPPeerSpec{ - ASNumber: 255, - }, - }, - ), - Entry("scope set node with NO node specified", - &apiv1.BGPPeer{ - Metadata: apiv1.BGPPeerMetadata{ - Scope: scope.Node, - PeerIP: *net.ParseIP("10.0.0.1"), - }, - Spec: apiv1.BGPPeerSpec{ - ASNumber: 255, - }, - }, - ), -) - -var _ = Describe("v1->v3 BGP Peer conversion tests", func() { - It("APIV1ToBackendV1 with the wrong resource produces an error", func() { - resource := &apiv1.IPPool{ - Metadata: apiv1.IPPoolMetadata{}, - Spec: apiv1.IPPoolSpec{}, - } - - p := BGPPeer{} - _, err := p.APIV1ToBackendV1(resource) - - Expect(err).To(HaveOccurred()) - }) - - It("BackendV1ToAPIV3 with wrong resource produces an error", func() { - resource := &model.KVPair{ - Key: model.IPPoolKey{}, - Value: &model.IPPool{}, - } - - p := BGPPeer{} - _, err := p.BackendV1ToAPIV3(resource) - - Expect(err).To(HaveOccurred()) - }) -}) diff --git a/libcalico-go/lib/upgrade/converters/converter.go b/libcalico-go/lib/upgrade/converters/converter.go deleted file mode 100644 index 436e60f51e3..00000000000 --- a/libcalico-go/lib/upgrade/converters/converter.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 converters - -import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" -) - -// Resource is implemented by all Calico resources. -type Resource interface { - runtime.Object - v1.ObjectMetaAccessor -} - -type Converter interface { - // APIV1ToBackendV1 converts unversioned resource (v1 API) to v1 KVPair. - APIV1ToBackendV1(unversioned.Resource) (*model.KVPair, error) - - // BackendV1ToAPIV3 converts v1 KVPair to v3 resource (v3 API) - BackendV1ToAPIV3(*model.KVPair) (Resource, error) -} diff --git a/libcalico-go/lib/upgrade/converters/converter_suite_test.go b/libcalico-go/lib/upgrade/converters/converter_suite_test.go deleted file mode 100644 index 51e159c86e2..00000000000 --- a/libcalico-go/lib/upgrade/converters/converter_suite_test.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2017-2018 Tigera, Inc. All rights reserved. - -// 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 converters - -import ( - "testing" - - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" - - "github.com/projectcalico/calico/libcalico-go/lib/testutils" -) - -func TestClient(t *testing.T) { - testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../report/converter_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "calico-upgrade converter pkg suite", []Reporter{junitReporter}) -} diff --git a/libcalico-go/lib/upgrade/converters/hostendpoint.go b/libcalico-go/lib/upgrade/converters/hostendpoint.go deleted file mode 100644 index 8f7eb3ab175..00000000000 --- a/libcalico-go/lib/upgrade/converters/hostendpoint.go +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 converters - -import ( - "fmt" - - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - "github.com/projectcalico/api/pkg/lib/numorstring" - log "github.com/sirupsen/logrus" - - "github.com/projectcalico/calico/lib/std/uniquelabels" - apiv1 "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - cnet "github.com/projectcalico/calico/libcalico-go/lib/net" -) - -// HostEndpoint implements the Converter interface. -type HostEndpoint struct{} - -// APIV1ToBackendV1 converts an APIv1 HostEndpoint structure to a KVPair containing a -// backend HostEndpoint and HostEndpointKey. -// This is part of the converter interface. -func (_ HostEndpoint) APIV1ToBackendV1(a unversioned.Resource) (*model.KVPair, error) { - ah, ok := a.(*apiv1.HostEndpoint) - if !ok { - return nil, fmt.Errorf("value is not a valid v1 HostEndpoint") - } - var ipv4Addrs []cnet.IP - var ipv6Addrs []cnet.IP - for _, ip := range ah.Spec.ExpectedIPs { - if ip.Version() == 4 { - ipv4Addrs = append(ipv4Addrs, ip) - } else { - ipv6Addrs = append(ipv6Addrs, ip) - } - } - - var ports []model.EndpointPort - for _, port := range ah.Spec.Ports { - ports = append(ports, model.EndpointPort{ - Name: port.Name, - Protocol: port.Protocol, - Port: port.Port, - }) - } - - d := model.KVPair{ - Key: model.HostEndpointKey{ - Hostname: ah.Metadata.Node, - EndpointID: ah.Metadata.Name, - }, - Value: &model.HostEndpoint{ - Labels: uniquelabels.Make(ah.Metadata.Labels), - Name: ah.Spec.InterfaceName, - ProfileIDs: ah.Spec.Profiles, - ExpectedIPv4Addrs: ipv4Addrs, - ExpectedIPv6Addrs: ipv6Addrs, - Ports: ports, - }, - } - - log.WithFields(log.Fields{ - "v1HostEndpoint": a, - "KVPair": d, - }).Debug("Converted HostEndpoint to KVPair") - - return &d, nil -} - -// BackendV1ToAPIV3 converts a KVPair containing a backend HostEndpoint and HostEndpointKey -// to an APIv3 HostEndpoint structure. -// This is part of the Converter interface. -func (_ HostEndpoint) BackendV1ToAPIV3(d *model.KVPair) (Resource, error) { - bh, ok := d.Value.(*model.HostEndpoint) - if !ok { - return nil, fmt.Errorf("value is not a valid HostEndpoint value") - } - bk, ok := d.Key.(model.HostEndpointKey) - if !ok { - return nil, fmt.Errorf("key is not a valid HostEndpoint key") - } - - var ips []string - for _, ip := range bh.ExpectedIPv4Addrs { - ips = append(ips, ip.String()) - } - for _, ip := range bh.ExpectedIPv6Addrs { - ips = append(ips, ip.String()) - } - - var ports []apiv3.EndpointPort - for _, port := range bh.Ports { - ports = append(ports, apiv3.EndpointPort{ - Name: port.Name, - Protocol: numorstring.ProtocolV3FromProtocolV1(port.Protocol), - Port: port.Port, - }) - } - - nodeName := ConvertNodeName(bk.Hostname) - - ah := apiv3.NewHostEndpoint() - ah.Name = convertName(fmt.Sprintf("%s.%s", nodeName, bk.EndpointID)) - ah.Labels = bh.Labels.RecomputeOriginalMap() - ah.Spec = apiv3.HostEndpointSpec{ - Node: nodeName, - Ports: ports, - InterfaceName: bh.Name, - Profiles: convertProfiles(bh.ProfileIDs), - ExpectedIPs: ips, - } - - log.WithFields(log.Fields{ - "KVPair": d, - "v3HostEndpoint": ah, - }).Debug("Converted KVPair to v3 Resource") - - return ah, nil -} diff --git a/libcalico-go/lib/upgrade/converters/hostendpoint_test.go b/libcalico-go/lib/upgrade/converters/hostendpoint_test.go deleted file mode 100644 index 31dbb0f132a..00000000000 --- a/libcalico-go/lib/upgrade/converters/hostendpoint_test.go +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 converters - -import ( - "fmt" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - "github.com/projectcalico/api/pkg/lib/numorstring" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/projectcalico/calico/lib/std/uniquelabels" - apiv1 "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - cnet "github.com/projectcalico/calico/libcalico-go/lib/net" -) - -var hepTable = []TableEntry{ - Entry("Valid basic v1 hep has data moved to right place", - &apiv1.HostEndpoint{ - Metadata: apiv1.HostEndpointMetadata{ - Name: "my-hep", - Node: "my-node", - }, - Spec: apiv1.HostEndpointSpec{ - InterfaceName: "eth3", - }, - }, - &model.KVPair{ - Key: model.HostEndpointKey{ - EndpointID: "my-hep", - Hostname: "my-node", - }, - Value: &model.HostEndpoint{ - Name: "eth3", - }, - }, - apiv3.HostEndpoint{ - ObjectMeta: v1.ObjectMeta{ - Name: "my-node.my-hep", - }, - Spec: apiv3.HostEndpointSpec{ - InterfaceName: "eth3", - Node: "my-node", - }, - }, - ), - Entry("Valid filled v1 hep has data moved to right place", - &apiv1.HostEndpoint{ - Metadata: apiv1.HostEndpointMetadata{ - Name: "my-hep", - Node: "my-node", - Labels: map[string]string{ - "foo": "bar", - }, - }, - Spec: apiv1.HostEndpointSpec{ - ExpectedIPs: []cnet.IP{ - cnet.MustParseIP("192.168.1.1"), - cnet.MustParseIP("fe80::"), - }, - Profiles: []string{"my-prof"}, - InterfaceName: "eth3", - Ports: []apiv1.EndpointPort{ - { - Name: "my-port", - Protocol: numorstring.ProtocolFromStringV1("tcp"), - Port: 12345, - }, - }, - }, - }, - &model.KVPair{ - Key: model.HostEndpointKey{ - EndpointID: "my-hep", - Hostname: "my-node", - }, - Value: &model.HostEndpoint{ - Name: "eth3", - Labels: uniquelabels.Make(map[string]string{ - "foo": "bar", - }), - Ports: []model.EndpointPort{ - { - Name: "my-port", - Port: 12345, - Protocol: numorstring.ProtocolFromStringV1("tcp"), - }, - }, - ProfileIDs: []string{"my-prof"}, - ExpectedIPv4Addrs: []cnet.IP{cnet.MustParseIP("192.168.1.1")}, - ExpectedIPv6Addrs: []cnet.IP{cnet.MustParseIP("fe80::")}, - }, - }, - apiv3.HostEndpoint{ - ObjectMeta: v1.ObjectMeta{ - Name: fmt.Sprintf("%s.%s", "my-node", "my-hep"), - Labels: map[string]string{ - "foo": "bar", - }, - }, - Spec: apiv3.HostEndpointSpec{ - InterfaceName: "eth3", - Node: "my-node", - Profiles: []string{"my-prof"}, - ExpectedIPs: []string{ - "192.168.1.1", - "fe80::", - }, - Ports: []apiv3.EndpointPort{ - { - Protocol: numorstring.ProtocolFromString("tcp"), - Port: 12345, - Name: "my-port", - }, - }, - }, - }, - ), -} - -var _ = DescribeTable("v1->v3 HostEndpoint conversion tests", - func(v1API *apiv1.HostEndpoint, v1KVP *model.KVPair, v3API apiv3.HostEndpoint) { - p := HostEndpoint{} - - // Check v1API->v1KVP. - convertedKvp, err := p.APIV1ToBackendV1(v1API) - Expect(err).NotTo(HaveOccurred()) - - Expect(convertedKvp.Key.(model.HostEndpointKey)).To(Equal(v1KVP.Key.(model.HostEndpointKey))) - Expect(convertedKvp.Value.(*model.HostEndpoint)).To(Equal(v1KVP.Value)) - - // Check v1KVP->v3API. - convertedv3, err := p.BackendV1ToAPIV3(v1KVP) - Expect(err).NotTo(HaveOccurred()) - Expect(convertedv3.(*apiv3.HostEndpoint).ObjectMeta).To(Equal(v3API.ObjectMeta)) - Expect(convertedv3.(*apiv3.HostEndpoint).Spec).To(Equal(v3API.Spec)) - }, - - // Add entries from table above. - hepTable..., -) - -var _ = Describe("v1-v3 HostEndpoint conversion tests", func() { - It("correctly builds new name", func() { - a := basicHep() - a.Metadata.Node = "noD2{" - a.Metadata.Name = "H3P.N/me" - - b, err := convertHEPV1ToV3(a) - - Expect(err).ToNot(HaveOccurred()) - Expect(b.ObjectMeta.Name).To(Equal("nod2.h3p.n.me-7188e863"), "Should convert new name") - }) - - It("converts protocol names", func() { - a := basicHep() - a.Spec.Ports = []apiv1.EndpointPort{ - { - Name: "my-port", - Protocol: numorstring.ProtocolFromStringV1("tcp"), - Port: 5, - }, - } - - b, err := convertHEPV1ToV3(a) - - Expect(err).ToNot(HaveOccurred()) - Expect(len(b.Spec.Ports)).To(Equal(1)) - Expect(b.Spec.Ports[0].Protocol.StrVal).To(Equal("TCP"), "Should convert protocol name") - }) - - It("retains ports when only protocol is given", func() { - a := basicHep() - a.Spec.Ports = []apiv1.EndpointPort{ - { - Protocol: numorstring.ProtocolFromStringV1("udp"), - }, - } - - b, err := convertHEPV1ToV3(a) - - Expect(err).ToNot(HaveOccurred()) - Expect(len(b.Spec.Ports)).To(Equal(1)) - Expect(b.Spec.Ports[0].Protocol.StrVal).To(Equal("UDP")) - }) - - It("retains ports when only port number is given", func() { - a := basicHep() - a.Spec.Ports = []apiv1.EndpointPort{ - { - Port: 5, - }, - } - - b, err := convertHEPV1ToV3(a) - - Expect(err).ToNot(HaveOccurred()) - Expect(len(b.Spec.Ports)).To(Equal(1)) - Expect(b.Spec.Ports[0].Port).To(Equal(uint16(5))) - }) - - It("converts profile names", func() { - a := basicHep() - a.Spec.Profiles = []string{ - "pRo/le", - } - - b, err := convertHEPV1ToV3(a) - - Expect(err).ToNot(HaveOccurred()) - Expect(len(b.Spec.Profiles)).To(Equal(1)) - Expect(b.Spec.Profiles[0]).To(Equal("pro.le-b1554692"), "Should convert profile name") - }) - - It("converts profile names", func() { - a := basicHep() - a.Spec.Profiles = []string{ - "k8s_ns.my-prof", - } - - b, err := convertHEPV1ToV3(a) - - Expect(err).ToNot(HaveOccurred()) - Expect(len(b.Spec.Profiles)).To(Equal(1)) - Expect(b.Spec.Profiles[0]).To(Equal("kns.my-prof"), "Should convert profile name") - }) -}) - -func basicHep() *apiv1.HostEndpoint { - return &apiv1.HostEndpoint{ - Metadata: apiv1.HostEndpointMetadata{ - Name: "my-hep", - Node: "my-node", - }, - Spec: apiv1.HostEndpointSpec{ - InterfaceName: "eth3", - }, - } -} - -func convertHEPV1ToV3(a *apiv1.HostEndpoint) (*apiv3.HostEndpoint, error) { - h := HostEndpoint{} - - b, err := h.APIV1ToBackendV1(a) - if err != nil { - return nil, err - } - - c, err := h.BackendV1ToAPIV3(b) - if err != nil { - return nil, err - } - - return c.(*apiv3.HostEndpoint), nil -} diff --git a/libcalico-go/lib/upgrade/converters/ippool.go b/libcalico-go/lib/upgrade/converters/ippool.go deleted file mode 100644 index e7fa81a964d..00000000000 --- a/libcalico-go/lib/upgrade/converters/ippool.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) 2017,2021 Tigera, Inc. All rights reserved. - -// 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 converters - -import ( - "fmt" - "strings" - - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - - apiv1 "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned" - "github.com/projectcalico/calico/libcalico-go/lib/backend/encap" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/names" -) - -// IPPool implements the Converter interface. -type IPPool struct{} - -// APIV1ToBackendV1 converts v1 IPPool API to v1 IPPool KVPair. -func (_ IPPool) APIV1ToBackendV1(rIn unversioned.Resource) (*model.KVPair, error) { - p := rIn.(*apiv1.IPPool) - - var ipipInterface string - var ipipMode encap.Mode - if p.Spec.IPIP != nil { - if p.Spec.IPIP.Enabled { - ipipInterface = "tunl0" - } else { - ipipInterface = "" - } - ipipMode = p.Spec.IPIP.Mode - } - - d := model.KVPair{ - Key: model.IPPoolKey{ - CIDR: p.Metadata.CIDR, - }, - Value: &model.IPPool{ - CIDR: p.Metadata.CIDR, - IPIPInterface: ipipInterface, - IPIPMode: ipipMode, - Masquerade: p.Spec.NATOutgoing, - IPAM: !p.Spec.Disabled, - Disabled: p.Spec.Disabled, - }, - } - - return &d, nil -} - -// BackendV1ToAPIV3 converts v1 IPPool KVPair to v3 API. -func (_ IPPool) BackendV1ToAPIV3(kvp *model.KVPair) (Resource, error) { - pool, ok := kvp.Value.(*model.IPPool) - if !ok { - return nil, fmt.Errorf("value is not a valid IPPool resource Value") - } - - ipp := apiv3.NewIPPool() - ipp.Name = names.CIDRToName(pool.CIDR) - ipp.Spec = apiv3.IPPoolSpec{ - CIDR: pool.CIDR.String(), - IPIPMode: convertIPIPMode(pool.IPIPMode, pool.IPIPInterface), - VXLANMode: apiv3.VXLANModeNever, - NATOutgoing: pool.Masquerade, - Disabled: pool.Disabled, - NodeSelector: "all()", - AllowedUses: []apiv3.IPPoolAllowedUse{ - apiv3.IPPoolAllowedUseWorkload, - apiv3.IPPoolAllowedUseTunnel, - }, - AssignmentMode: convertAssignmentMode(pool.AssignmentMode), - } - - // Set the blocksize based on IP address family. - if pool.CIDR.IP.To4() != nil { - ipp.Spec.BlockSize = 26 - } else { - ipp.Spec.BlockSize = 122 - } - - return ipp, nil -} - -func convertIPIPMode(mode encap.Mode, ipipInterface string) apiv3.IPIPMode { - ipipMode := strings.ToLower(string(mode)) - - if ipipInterface == "" { - return apiv3.IPIPModeNever - } else if ipipMode == "cross-subnet" { - return apiv3.IPIPModeCrossSubnet - } - return apiv3.IPIPModeAlways -} - -func convertAssignmentMode(assignmentMode apiv3.AssignmentMode) *apiv3.AssignmentMode { - automatic := apiv3.Automatic - if assignmentMode == "" { - return &automatic - } - return &assignmentMode -} diff --git a/libcalico-go/lib/upgrade/converters/ippool_test.go b/libcalico-go/lib/upgrade/converters/ippool_test.go deleted file mode 100644 index ba19ce16908..00000000000 --- a/libcalico-go/lib/upgrade/converters/ippool_test.go +++ /dev/null @@ -1,282 +0,0 @@ -// Copyright (c) 2017,2019,2021 Tigera, Inc. All rights reserved. - -// 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 converters - -import ( - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - apiv1 "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/backend/encap" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - cnet "github.com/projectcalico/calico/libcalico-go/lib/net" -) - -var poolTable = []TableEntry{ - Entry("fully populated IPv4 IPPool", - &apiv1.IPPool{ - Metadata: apiv1.IPPoolMetadata{ - CIDR: cnet.MustParseCIDR("10.0.0.1/24"), - }, - Spec: apiv1.IPPoolSpec{ - IPIP: &apiv1.IPIPConfiguration{ - Enabled: true, - Mode: encap.Undefined, - }, - NATOutgoing: false, - Disabled: false, - }, - }, - &model.KVPair{ - Key: model.IPPoolKey{ - CIDR: cnet.MustParseCIDR("10.0.0.1/24"), - }, - Value: &model.IPPool{ - CIDR: cnet.MustParseCIDR("10.0.0.1/24"), - IPIPInterface: "tunl0", - IPIPMode: encap.Undefined, - Masquerade: false, - Disabled: false, - IPAM: true, - }, - }, - apiv3.IPPool{ - ObjectMeta: v1.ObjectMeta{ - Name: "10-0-0-1-24", - }, - Spec: apiv3.IPPoolSpec{ - CIDR: "10.0.0.1/24", - IPIPMode: apiv3.IPIPModeAlways, - VXLANMode: apiv3.VXLANModeNever, - NATOutgoing: false, - Disabled: false, - BlockSize: 26, - NodeSelector: "all()", - AllowedUses: []apiv3.IPPoolAllowedUse{ - apiv3.IPPoolAllowedUseWorkload, - apiv3.IPPoolAllowedUseTunnel, - }, - AssignmentMode: convertAssignmentMode(apiv3.Automatic), - }, - }, - ), - Entry("fully populated IPv6 IPPool", - &apiv1.IPPool{ - Metadata: apiv1.IPPoolMetadata{ - CIDR: cnet.MustParseCIDR("2001::/120"), - }, - Spec: apiv1.IPPoolSpec{ - IPIP: &apiv1.IPIPConfiguration{ - Enabled: true, - Mode: encap.Always, - }, - NATOutgoing: false, - Disabled: true, - }, - }, - &model.KVPair{ - Key: model.IPPoolKey{ - CIDR: cnet.MustParseCIDR("2001::/120"), - }, - Value: &model.IPPool{ - CIDR: cnet.MustParseCIDR("2001::/120"), - IPIPInterface: "tunl0", - IPIPMode: encap.Always, - Masquerade: false, - Disabled: true, - IPAM: false, - }, - }, - apiv3.IPPool{ - ObjectMeta: v1.ObjectMeta{ - Name: "2001---120", - }, - Spec: apiv3.IPPoolSpec{ - CIDR: "2001::/120", - IPIPMode: apiv3.IPIPModeAlways, - VXLANMode: apiv3.VXLANModeNever, - NATOutgoing: false, - Disabled: true, - BlockSize: 122, - NodeSelector: "all()", - AllowedUses: []apiv3.IPPoolAllowedUse{ - apiv3.IPPoolAllowedUseWorkload, - apiv3.IPPoolAllowedUseTunnel, - }, - AssignmentMode: convertAssignmentMode(apiv3.Automatic), - }, - }, - ), - Entry("IPv4 IPPool with IPIPMode blank, should be converted to IPIPMode Never", - &apiv1.IPPool{ - Metadata: apiv1.IPPoolMetadata{ - CIDR: cnet.MustParseCIDR("5.5.5.5/25"), - }, - Spec: apiv1.IPPoolSpec{ - IPIP: &apiv1.IPIPConfiguration{ - Enabled: false, - Mode: encap.Undefined, - }, - NATOutgoing: true, - Disabled: true, - }, - }, - &model.KVPair{ - Key: model.IPPoolKey{ - CIDR: cnet.MustParseCIDR("5.5.5.5/25"), - }, - Value: &model.IPPool{ - CIDR: cnet.MustParseCIDR("5.5.5.5/25"), - IPIPInterface: "", - IPIPMode: "", - Masquerade: true, - Disabled: true, - IPAM: false, - }, - }, - apiv3.IPPool{ - ObjectMeta: v1.ObjectMeta{ - Name: "5-5-5-5-25", - }, - Spec: apiv3.IPPoolSpec{ - CIDR: "5.5.5.5/25", - IPIPMode: apiv3.IPIPModeNever, - VXLANMode: apiv3.VXLANModeNever, - NATOutgoing: true, - Disabled: true, - BlockSize: 26, - NodeSelector: "all()", - AllowedUses: []apiv3.IPPoolAllowedUse{ - apiv3.IPPoolAllowedUseWorkload, - apiv3.IPPoolAllowedUseTunnel, - }, - AssignmentMode: convertAssignmentMode(apiv3.Automatic), - }, - }, - ), - Entry("IPv4 IPPool with IPIPMode unspecified, should be converted to IPIPMode Never", - &apiv1.IPPool{ - Metadata: apiv1.IPPoolMetadata{ - CIDR: cnet.MustParseCIDR("6.6.6.6/26"), - }, - Spec: apiv1.IPPoolSpec{ - IPIP: &apiv1.IPIPConfiguration{ - Enabled: false, - }, - NATOutgoing: true, - Disabled: true, - }, - }, - &model.KVPair{ - Key: model.IPPoolKey{ - CIDR: cnet.MustParseCIDR("6.6.6.6/26"), - }, - Value: &model.IPPool{ - CIDR: cnet.MustParseCIDR("6.6.6.6/26"), - Masquerade: true, - Disabled: true, - IPAM: false, - }, - }, - apiv3.IPPool{ - ObjectMeta: v1.ObjectMeta{ - Name: "6-6-6-6-26", - }, - Spec: apiv3.IPPoolSpec{ - CIDR: "6.6.6.6/26", - IPIPMode: apiv3.IPIPModeNever, - VXLANMode: apiv3.VXLANModeNever, - NATOutgoing: true, - Disabled: true, - BlockSize: 26, - NodeSelector: "all()", - AllowedUses: []apiv3.IPPoolAllowedUse{ - apiv3.IPPoolAllowedUseWorkload, - apiv3.IPPoolAllowedUseTunnel, - }, - AssignmentMode: convertAssignmentMode(apiv3.Automatic), - }, - }, - ), - Entry("partially populated IPv4 IPPool with IPIPMode set to cross-subnet", - &apiv1.IPPool{ - Metadata: apiv1.IPPoolMetadata{ - CIDR: cnet.MustParseCIDR("1.1.1.1/11"), - }, - Spec: apiv1.IPPoolSpec{ - IPIP: &apiv1.IPIPConfiguration{ - Enabled: true, - Mode: encap.CrossSubnet, - }, - NATOutgoing: false, - Disabled: true, - }, - }, - &model.KVPair{ - Key: model.IPPoolKey{ - CIDR: cnet.MustParseCIDR("1.1.1.1/11"), - }, - Value: &model.IPPool{ - CIDR: cnet.MustParseCIDR("1.1.1.1/11"), - Masquerade: false, - IPIPInterface: "tunl0", - IPIPMode: encap.CrossSubnet, - Disabled: true, - IPAM: false, - }, - }, - apiv3.IPPool{ - ObjectMeta: v1.ObjectMeta{ - Name: "1-1-1-1-11", - }, - Spec: apiv3.IPPoolSpec{ - CIDR: "1.1.1.1/11", - IPIPMode: apiv3.IPIPModeCrossSubnet, - VXLANMode: apiv3.VXLANModeNever, - NATOutgoing: false, - Disabled: true, - BlockSize: 26, - NodeSelector: "all()", - AllowedUses: []apiv3.IPPoolAllowedUse{ - apiv3.IPPoolAllowedUseWorkload, - apiv3.IPPoolAllowedUseTunnel, - }, - AssignmentMode: convertAssignmentMode(apiv3.Automatic), - }, - }, - ), -} - -var _ = DescribeTable("v1->v3 IPPool conversion tests", - func(v1API *apiv1.IPPool, v1KVP *model.KVPair, v3API apiv3.IPPool) { - p := IPPool{} - - // Test and assert v1 API to v1 backend logic. - v1KVPResult, err := p.APIV1ToBackendV1(v1API) - Expect(err).NotTo(HaveOccurred()) - Expect(v1KVPResult.Key.(model.IPPoolKey).CIDR).To(Equal(v1KVP.Key.(model.IPPoolKey).CIDR)) - Expect(v1KVPResult.Value.(*model.IPPool)).To(Equal(v1KVP.Value)) - - // Test and assert v1 backend to v3 API logic. - v3APIResult, err := p.BackendV1ToAPIV3(v1KVP) - Expect(err).NotTo(HaveOccurred()) - Expect(v3APIResult.(*apiv3.IPPool).Name).To(Equal(v3API.Name)) - Expect(v3APIResult.(*apiv3.IPPool).Spec).To(Equal(v3API.Spec)) - }, - - poolTable..., -) diff --git a/libcalico-go/lib/upgrade/converters/names.go b/libcalico-go/lib/upgrade/converters/names.go deleted file mode 100644 index f15d6e7a824..00000000000 --- a/libcalico-go/lib/upgrade/converters/names.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 converters - -import ( - "crypto/sha1" - "encoding/hex" - "fmt" - "net" - "regexp" - "strings" -) - -var ( - nonNameChar = regexp.MustCompile("[^-.a-z0-9]+") - dotDashSeq = regexp.MustCompile("[.-]*[.][.-]*") - trailingLeadingDotsDashes = regexp.MustCompile("^[.-]*(.*?)[.-]*$") -) - -// Convert the v1 node name to a standard v3 name. This uses the standard name normalization -// but does not add a qualifier. Any overlapping names will result in a failed upgrade, so the -// pre-upgrade validation script will check for conflicting names. -func ConvertNodeName(v1Name string) string { - return normalizeName(v1Name) -} - -// Convert a name to normalized form. This is used for conversion of os.Hostname to a -// suitable node name, and is also used for the v2->v3 migration code. -// - Convert to lowercase -// - Convert [/] to . -// - Convert any other char that is not in the set [-.a-z0-9] to - -// - Convert any multi-byte sequence of [-.] with at least one [.] to a single . -// - Remove leading and trailing dashes and dots -func normalizeName(name string) string { - name = strings.ToLower(name) - name = strings.Replace(name, "/", ".", -1) - name = nonNameChar.ReplaceAllString(name, "-") - name = dotDashSeq.ReplaceAllString(name, ".") - - // Extract the trailing and leading dots and dashes. This should always match even if - // the matched substring is empty. The second item in the returned submatch - // slice is the captured match group. - submatches := trailingLeadingDotsDashes.FindStringSubmatch(name) - name = submatches[1] - return name -} - -// Convert the v1 name to a standard v3 name. This uses the standard name normalization, -// and adds an additional qualifier if the name was modified. The qualifier is calculated -// from the original name. -func convertName(v1Name string) string { - name := normalizeName(v1Name) - - // If the name is different append a qualifier. - return qualifiedName(v1Name, name) -} - -// Convert the v1 name to a standard v3 name with no dots. -func convertNameNoDots(v1Name string) string { - // Normalize the name and then convert dots to dashes. - name := normalizeName(v1Name) - name = strings.Replace(name, ".", "-", -1) - - // If the name is different append a qualifier. - return qualifiedName(v1Name, name) -} - -func qualifiedName(orig, final string) string { - // If the name was not modified, just return the unmodified name. - if orig == final { - return orig - } - // The name was modified. Calculate an 8-byte hex qualifier to append. - h := sha1.New() - h.Write([]byte(orig)) - return fmt.Sprintf("%s-%s", final, strings.ToLower(hex.EncodeToString(h.Sum(nil))[:8])) -} - -// Convert an IP to an IPv4 or IPv6 representation -// - IPv4 addresses will be of the format 1-2-3-4 -// - IPv6 addresses will be of the format 00aa-00bb-0000-0000-0000-0000-0000-0000 -// with all zeros expanded -func convertIpToName(ip net.IP) string { - name := "" - if ip.To4() != nil { - name = strings.Replace(ip.String(), ".", "-", 3) - } else { - ip6 := ip.To16() - bytes := []string{} - for i := 0; i < len(ip6); i += 2 { - bytes = append(bytes, fmt.Sprintf("%.2x%.2x", ip6[i], ip6[i+1])) - } - name = strings.Join(bytes, "-") - } - - return name -} diff --git a/libcalico-go/lib/upgrade/converters/names_test.go b/libcalico-go/lib/upgrade/converters/names_test.go deleted file mode 100644 index 10a92a7328a..00000000000 --- a/libcalico-go/lib/upgrade/converters/names_test.go +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 converters - -import ( - "net" - "strings" - - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" -) - -var namesTable = []TableEntry{ - Entry("Convert name abcdef", "abcdef", "abcdef", false), - Entry("Convert name Abcdef", "Abcdef", "abcdef", true), - Entry("Convert name abc-def", "abc-def", "abc-def", false), - Entry("Convert name abc---def", "abc---def", "abc---def", false), - Entry("Convert name abc/def", "abc/def", "abc.def", true), - Entry("Convert name abc$$def", "abc$$def", "abc-def", true), - Entry("Convert name abc$!$def", "abc$!$def", "abc-def", true), - Entry("Convert name abc..def", "abc..def", "abc.def", true), - Entry("Convert name abc...def", "abc...def", "abc.def", true), - Entry("Convert name abc.-def", "abc.-def", "abc.def", true), - Entry("Convert name abc.-.def", "abc.-.def", "abc.def", true), - Entry("Convert name abc-.def", "abc-.def", "abc.def", true), - Entry("Convert name abc-.-def", "abc-.-def", "abc.def", true), - Entry("Convert name aBcDe019", "aBcDe019", "abcde019", true), - Entry("Convert name abc$def", "abc$def", "abc-def", true), - Entry("Convert name -abc.def", "-abc.def", "abc.def", true), - Entry("Convert name abc.def-", "abc.def-", "abc.def", true), - Entry("Convert name .abc.def", ".abc.def", "abc.def", true), - Entry("Convert name abc.def.", "abc.def.", "abc.def", true), - Entry("Convert name -.abc.def", "-.abc.def", "abc.def", true), - Entry("Convert name abc.def.-", "abc.def.-", "abc.def", true), - Entry("Convert name $ABC/DeF-123.-456!", "$ABC/DeF-123.-456!", "abc.def-123.456", true), -} - -var _ = DescribeTable("v1->v3 name conversion tests", - func(v1Name string, v3Name string, expectQualifier bool) { - // Get the converted name. - c1 := convertName(v1Name) - if !expectQualifier { - // No qualifier is expected, the names should match exactly. - Expect(c1).To(Equal(v3Name)) - } else { - // A qualifier is expected, the first part of the name should match and the - // last 9 chars should be the qualifier. - Expect(c1[:len(c1)-9]).To(Equal(v3Name)) - Expect(c1[len(c1)-9:]).To(MatchRegexp("[-][0-9a-f]{8}")) - - // Convert an upper case variant of the input. The first part of the name should - // match and the last 9 chars should be the qualifier - however the two converted - // names should be different. - c2 := convertName(strings.ToUpper(v1Name)) - Expect(c2[:len(c2)-9]).To(Equal(v3Name)) - Expect(c2[len(c2)-9:]).To(MatchRegexp("[-][0-9a-f]{8}")) - Expect(c2).NotTo(Equal(c1)) - } - }, - - namesTable..., -) - -var namesNoDotsTable = []TableEntry{ - Entry("Convert name abcdef", "abcdef", "abcdef", false), - Entry("Convert name Abcdef", "Abcdef", "abcdef", true), - Entry("Convert name abc-def", "abc-def", "abc-def", false), - Entry("Convert name abc---def", "abc---def", "abc---def", false), - Entry("Convert name abc/def", "abc/def", "abc-def", true), - Entry("Convert name abc..def", "abc..def", "abc-def", true), - Entry("Convert name abc...def", "abc...def", "abc-def", true), - Entry("Convert name abc.-def", "abc.-def", "abc-def", true), - Entry("Convert name abc.-.def", "abc.-.def", "abc-def", true), - Entry("Convert name abc-.def", "abc-.def", "abc-def", true), - Entry("Convert name abc-.-def", "abc-.-def", "abc-def", true), - Entry("Convert name aBcDe019", "aBcDe019", "abcde019", true), - Entry("Convert name abc$def", "abc$def", "abc-def", true), - Entry("Convert name -abc.def", "-abc.def", "abc-def", true), - Entry("Convert name abc.def-", "abc.def-", "abc-def", true), - Entry("Convert name .abc.def", ".abc.def", "abc-def", true), - Entry("Convert name abc.def.", "abc.def.", "abc-def", true), - Entry("Convert name -.abc.def", "-.abc.def", "abc-def", true), - Entry("Convert name abc.def.-", "abc.def.-", "abc-def", true), - Entry("Convert name $ABC/DeF-123.-456!", "$ABC/DeF-123.-456!", "abc-def-123-456", true), -} - -var _ = DescribeTable("v1->v3 name conversion tests (no dots)", - func(v1Name, v3Name string, expectQualifier bool) { - // Get the converted name. - c1 := convertNameNoDots(v1Name) - if !expectQualifier { - // No qualifier is expected, the names should match exactly. - Expect(c1).To(Equal(v3Name)) - } else { - // A qualifier is expected, the first part of the name should match and the - // last 9 chars should be the qualifier. - Expect(c1[:len(c1)-9]).To(Equal(v3Name)) - Expect(c1[len(c1)-9:]).To(MatchRegexp("[-][0-9a-f]{8}")) - - // Convert an upper case variant of the input. The first part of the name should - // match and the last 9 chars should be the qualifier - however the two converted - // names should be different. - c2 := convertNameNoDots(strings.ToUpper(v1Name)) - Expect(c2[:len(c2)-9]).To(Equal(v3Name)) - Expect(c2[len(c2)-9:]).To(MatchRegexp("[-][0-9a-f]{8}")) - Expect(c2).NotTo(Equal(c1)) - } - }, - - namesNoDotsTable..., -) - -var ipToNameTable = []TableEntry{ - Entry("Parse IP 192.168.0.1", net.ParseIP("192.168.0.1"), "192-168-0-1"), - Entry("Parse IP Aa:Bb::", net.ParseIP("Aa:bb::"), "00aa-00bb-0000-0000-0000-0000-0000-0000"), - Entry("Parse IP 0Aa:bb::50", net.ParseIP("0Aa:bb::50"), "00aa-00bb-0000-0000-0000-0000-0000-0050"), -} - -var _ = DescribeTable("v1->v3 IP to name conversion tests", - func(ip net.IP, name string) { - Expect(convertIpToName(ip)).To(Equal(name), ip.String()) - }, - ipToNameTable..., -) - -var nodeNamesTable = []TableEntry{ - Entry("Convert abc-def", "abc-def", "abc-def"), - Entry("Convert abc---def", "abc---def", "abc---def"), - Entry("Convert abc/def", "abc/def", "abc.def"), - Entry("Convert abc$$def", "abc$$def", "abc-def"), - Entry("Convert abc$!$def", "abc$!$def", "abc-def"), - Entry("Convert abc..def", "abc..def", "abc.def"), - Entry("Convert abc...def", "abc...def", "abc.def"), - Entry("Convert abc.-def", "abc.-def", "abc.def"), - Entry("Convert abc.-.def", "abc.-.def", "abc.def"), - Entry("Convert abc-.def", "abc-.def", "abc.def"), - Entry("Convert abc-.-def", "abc-.-def", "abc.def"), - Entry("Convert aBcDe019", "aBcDe019", "abcde019"), - Entry("Convert abc$def", "abc$def", "abc-def"), - Entry("Convert -abc.def", "-abc.def", "abc.def"), - Entry("Convert abc.def-", "abc.def-", "abc.def"), - Entry("Convert .abc.def", ".abc.def", "abc.def"), - Entry("Convert abc.def.", "abc.def.", "abc.def"), - Entry("Convert -.abc.def", "-.abc.def", "abc.def"), - Entry("Convert abc.def.-", "abc.def.-", "abc.def"), - Entry("Convert $ABC/DEF-123.-456!", "$ABC/DEF-123.-456!", "abc.def-123.456"), -} - -var _ = DescribeTable("v1->v3 node name conversion tests", - func(before, after string) { - Expect(ConvertNodeName(before)).To(Equal(after), before) - }, - nodeNamesTable..., -) diff --git a/libcalico-go/lib/upgrade/converters/node.go b/libcalico-go/lib/upgrade/converters/node.go deleted file mode 100644 index 1959ddec38f..00000000000 --- a/libcalico-go/lib/upgrade/converters/node.go +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 converters - -import ( - "fmt" - "net" - - log "github.com/sirupsen/logrus" - - apiv1 "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - cnet "github.com/projectcalico/calico/libcalico-go/lib/net" -) - -type Node struct{} - -// convertAPIToKVPair converts an API Node structure to a KVPair containing a -// backend Node and NodeKey. -// This is part of the conversionHelper interface. -func (n Node) APIV1ToBackendV1(a unversioned.Resource) (*model.KVPair, error) { - an, ok := a.(*apiv1.Node) - if !ok { - return nil, fmt.Errorf("Conversion to Node is not possible with %v", a) - } - - k, err := n.convertMetadataToKey(an.Metadata) - if err != nil { - return nil, err - } - - v := model.Node{} - if an.Spec.BGP != nil { - if an.Spec.BGP.IPv4Address == nil && an.Spec.BGP.IPv6Address == nil { - return nil, fmt.Errorf("Invalid NodeBGPSpec, missing address: %v", an.Spec.BGP) - } - if an.Spec.BGP.IPv4Address != nil { - v.BGPIPv4Addr = &cnet.IP{IP: an.Spec.BGP.IPv4Address.IP} - v.BGPIPv4Net = an.Spec.BGP.IPv4Address.Network() - } - if an.Spec.BGP.IPv6Address != nil { - v.BGPIPv6Addr = &cnet.IP{IP: an.Spec.BGP.IPv6Address.IP} - v.BGPIPv6Net = an.Spec.BGP.IPv6Address.Network() - } - v.BGPASNumber = an.Spec.BGP.ASNumber - } - - for _, orchRef := range an.Spec.OrchRefs { - v.OrchRefs = append(v.OrchRefs, model.OrchRef{ - Orchestrator: orchRef.Orchestrator, - NodeName: orchRef.NodeName, - }) - } - - kv := &model.KVPair{Key: k, Value: &v} - - log.WithFields(log.Fields{ - "APIV1": a, - "KVPair": *kv, - }).Debug("Converted Node") - return kv, nil -} - -// convertMetadataToKey converts a NodeMetadata to a NodeKey -func (_ Node) convertMetadataToKey(m unversioned.ResourceMetadata) (model.Key, error) { - nm := m.(apiv1.NodeMetadata) - k := model.NodeKey{ - Hostname: nm.Name, - } - return k, nil -} - -// convertKVPairToAPI converts a KVPair containing a backend Node and NodeKey -// to an API Node structure. -// The Node.Spec.BGP.IPv4IPIPTunnelAddr field will need to be populated -// still since it comes from another resource. -// This is part of the conversionHelper interface. -func (_ Node) BackendV1ToAPIV3(d *model.KVPair) (Resource, error) { - bv, ok := d.Value.(*model.Node) - if !ok { - return nil, fmt.Errorf("Value is not a valid Node resource: %v", d.Value) - } - - bk, ok := d.Key.(model.NodeKey) - if !ok { - return nil, fmt.Errorf("Key is not a valid NodeKey resource: %v", d.Value) - } - - apiNode := libapiv3.NewNode() - - apiNode.ObjectMeta.Name = ConvertNodeName(bk.Hostname) - - if bv.BGPIPv4Addr != nil || bv.BGPIPv6Addr != nil { - apiNode.Spec.BGP = &libapiv3.NodeBGPSpec{ - ASNumber: bv.BGPASNumber, - } - - // If the backend has an IPv4 address then fill in the IPv4Address - // field. If the IP network does not exist assume a full mask. - if bv.BGPIPv4Addr != nil { - if bv.BGPIPv4Net != nil { - // Stored network is normalised, so copy across the - // IP separately. - ipAndNet := net.IPNet{IP: bv.BGPIPv4Addr.IP, Mask: bv.BGPIPv4Net.Mask} - apiNode.Spec.BGP.IPv4Address = ipAndNet.String() - } else { - // No network is stored, assume a full masked network. - apiNode.Spec.BGP.IPv4Address = bv.BGPIPv4Addr.Network().String() - } - } - - // If the backend has an IPv6 address then fill in the IPv6Address - // field. If the IP network does not exist assume a full mask. - if bv.BGPIPv6Addr != nil { - if bv.BGPIPv6Net != nil { - // Stored network is normalised, so copy across the - // IP separately. - ipAndNet := net.IPNet{IP: bv.BGPIPv6Addr.IP, Mask: bv.BGPIPv6Net.Mask} - apiNode.Spec.BGP.IPv6Address = ipAndNet.String() - } else { - // No network is stored, assume a full masked network. - apiNode.Spec.BGP.IPv6Address = bv.BGPIPv6Addr.Network().String() - } - } - } - - for _, orchref := range bv.OrchRefs { - apiNode.Spec.OrchRefs = append(apiNode.Spec.OrchRefs, libapiv3.OrchRef{ - NodeName: orchref.NodeName, - Orchestrator: orchref.Orchestrator, - }) - } - - log.WithFields(log.Fields{ - "KVPair": *d, - "APIV3": apiNode, - }).Debug("Converted Node") - return apiNode, nil -} diff --git a/libcalico-go/lib/upgrade/converters/node_test.go b/libcalico-go/lib/upgrade/converters/node_test.go deleted file mode 100644 index 4919f17d1ee..00000000000 --- a/libcalico-go/lib/upgrade/converters/node_test.go +++ /dev/null @@ -1,359 +0,0 @@ -// Copyright (c) 2017-2020 Tigera, Inc. All rights reserved. - -// 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 converters - -import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" - "github.com/projectcalico/api/pkg/lib/numorstring" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - apiv1 "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - cnet "github.com/projectcalico/calico/libcalico-go/lib/net" -) - -var asn, _ = numorstring.ASNumberFromString("1") -var ipv4String = "192.168.1.1/24" -var ipv4IPNet = cnet.MustParseCIDR(ipv4String) -var ipv4IPNetMask = ipv4IPNet.Network() -var ipv4IP = cnet.MustParseIP("192.168.1.1") -var ipv6String = "fed::5/64" -var ipv6IPNet = cnet.MustParseCIDR(ipv6String) -var ipv6IPNetMask = ipv6IPNet.Network() -var ipv6IP = cnet.MustParseIP("fed::5") - -var nodeTable = []TableEntry{ - Entry("Valid basic v1 node has data moved to right place", - &apiv1.Node{ - Metadata: apiv1.NodeMetadata{ - Name: "my-node", - }, - Spec: apiv1.NodeSpec{ - BGP: &apiv1.NodeBGPSpec{ - ASNumber: &asn, - IPv4Address: &ipv4IPNet, - IPv6Address: &ipv6IPNet, - }, - }, - }, - &model.KVPair{ - Key: model.NodeKey{ - Hostname: "my-node", - }, - Value: &model.Node{ - BGPASNumber: &asn, - BGPIPv4Addr: &ipv4IP, - BGPIPv4Net: ipv4IPNetMask, - BGPIPv6Addr: &ipv6IP, - BGPIPv6Net: ipv6IPNetMask, - }, - }, - libapiv3.Node{ - ObjectMeta: v1.ObjectMeta{ - Name: "my-node", - }, - Spec: libapiv3.NodeSpec{ - BGP: &libapiv3.NodeBGPSpec{ - ASNumber: &asn, - IPv4Address: ipv4String, - IPv6Address: ipv6String, - }, - }, - }, - ), - Entry("Check name conversion", - &apiv1.Node{ - Metadata: apiv1.NodeMetadata{ - Name: "myNode.here", - }, - Spec: apiv1.NodeSpec{ - BGP: &apiv1.NodeBGPSpec{ - ASNumber: &asn, - IPv4Address: &ipv4IPNet, - IPv6Address: &ipv6IPNet, - }, - }, - }, - &model.KVPair{ - Key: model.NodeKey{ - Hostname: "myNode.here", - }, - Value: &model.Node{ - BGPASNumber: &asn, - BGPIPv4Addr: &ipv4IP, - BGPIPv4Net: ipv4IPNetMask, - BGPIPv6Addr: &ipv6IP, - BGPIPv6Net: ipv6IPNetMask, - }, - }, - libapiv3.Node{ - ObjectMeta: v1.ObjectMeta{ - Name: "mynode.here", - }, - Spec: libapiv3.NodeSpec{ - BGP: &libapiv3.NodeBGPSpec{ - ASNumber: &asn, - IPv4Address: ipv4String, - IPv6Address: ipv6String, - }, - }, - }, - ), - Entry("Conversion with only IPv6", - &apiv1.Node{ - Metadata: apiv1.NodeMetadata{ - Name: "my-node", - }, - Spec: apiv1.NodeSpec{ - BGP: &apiv1.NodeBGPSpec{ - IPv6Address: &ipv6IPNet, - }, - }, - }, - &model.KVPair{ - Key: model.NodeKey{ - Hostname: "my-node", - }, - Value: &model.Node{ - BGPIPv6Addr: &ipv6IP, - BGPIPv6Net: ipv6IPNetMask, - }, - }, - libapiv3.Node{ - ObjectMeta: v1.ObjectMeta{ - Name: "my-node", - }, - Spec: libapiv3.NodeSpec{ - BGP: &libapiv3.NodeBGPSpec{ - IPv6Address: ipv6String, - }, - }, - }, - ), - Entry("Conversion with only IPv4", - &apiv1.Node{ - Metadata: apiv1.NodeMetadata{ - Name: "my-node", - }, - Spec: apiv1.NodeSpec{ - BGP: &apiv1.NodeBGPSpec{ - IPv4Address: &ipv4IPNet, - }, - }, - }, - &model.KVPair{ - Key: model.NodeKey{ - Hostname: "my-node", - }, - Value: &model.Node{ - BGPIPv4Addr: &ipv4IP, - BGPIPv4Net: ipv4IPNetMask, - }, - }, - libapiv3.Node{ - ObjectMeta: v1.ObjectMeta{ - Name: "my-node", - }, - Spec: libapiv3.NodeSpec{ - BGP: &libapiv3.NodeBGPSpec{ - IPv4Address: ipv4String, - }, - }, - }, - ), - Entry("Conversion with OrchRefs", - &apiv1.Node{ - Metadata: apiv1.NodeMetadata{ - Name: "my-node", - }, - Spec: apiv1.NodeSpec{ - BGP: &apiv1.NodeBGPSpec{ - IPv4Address: &ipv4IPNet, - }, - OrchRefs: []apiv1.OrchRef{ - {Orchestrator: "orch1"}, - {Orchestrator: "orch2", NodeName: "orch2NodeName"}, - }, - }, - }, - &model.KVPair{ - Key: model.NodeKey{ - Hostname: "my-node", - }, - Value: &model.Node{ - BGPIPv4Addr: &ipv4IP, - BGPIPv4Net: ipv4IPNetMask, - OrchRefs: []model.OrchRef{ - {Orchestrator: "orch1"}, - {Orchestrator: "orch2", NodeName: "orch2NodeName"}, - }, - }, - }, - libapiv3.Node{ - ObjectMeta: v1.ObjectMeta{ - Name: "my-node", - }, - Spec: libapiv3.NodeSpec{ - BGP: &libapiv3.NodeBGPSpec{ - IPv4Address: ipv4String, - }, - OrchRefs: []libapiv3.OrchRef{ - {Orchestrator: "orch1"}, - {Orchestrator: "orch2", NodeName: "orch2NodeName"}, - }, - }, - }, - ), -} - -var _ = DescribeTable("v1->v3 Node conversion tests", - func(v1API *apiv1.Node, v1KVP *model.KVPair, v3API libapiv3.Node) { - p := Node{} - // Check v1API->v1KVP. - convertedKvp, err := p.APIV1ToBackendV1(v1API) - Expect(err).NotTo(HaveOccurred()) - - Expect(convertedKvp.Key.(model.NodeKey)).To(Equal(v1KVP.Key.(model.NodeKey))) - Expect(convertedKvp.Value.(*model.Node)).To(Equal(v1KVP.Value)) - - // Check v1KVP->v3API. - convertedv3, err := p.BackendV1ToAPIV3(v1KVP) - Expect(err).NotTo(HaveOccurred()) - Expect(convertedv3.(*libapiv3.Node).ObjectMeta).To(Equal(v3API.ObjectMeta)) - Expect(convertedv3.(*libapiv3.Node).Spec).To(Equal(v3API.Spec)) - }, - - nodeTable..., -) - -var nodeV1FailTable = []TableEntry{ - Entry("No IPv4 or IPv6 Address", - &apiv1.Node{ - Metadata: apiv1.NodeMetadata{ - Name: "my-node", - }, - Spec: apiv1.NodeSpec{ - BGP: &apiv1.NodeBGPSpec{ - ASNumber: &asn, - }, - }, - }, - ), - Entry("Incorrect Resource", - apiv1.IPPool{ - Metadata: apiv1.IPPoolMetadata{}, - Spec: apiv1.IPPoolSpec{}, - }, - ), -} - -var _ = DescribeTable("v1->v3 Node conversion tests (failure)", - func(v1API unversioned.Resource) { - p := Node{} - // Check v1API->v1KVP. - _, err := p.APIV1ToBackendV1(v1API) - Expect(err).To(HaveOccurred()) - }, - - nodeV1FailTable..., -) - -var _ = Describe("v1->v3 Node conversion tests (failure)", func() { - It("BackendV1ToAPIV3 with wrong Key produces an error", func() { - resource := &model.KVPair{ - Key: model.IPPoolKey{}, - Value: &model.Node{ - BGPIPv4Addr: &ipv4IP, - BGPIPv4Net: ipv4IPNetMask, - }, - } - p := Node{} - _, err := p.BackendV1ToAPIV3(resource) - Expect(err).To(HaveOccurred()) - }) - - It("BackendV1ToAPIV3 with wrong Value produces an error", func() { - resource := &model.KVPair{ - Key: model.NodeKey{ - Hostname: "my-node", - }, - Value: &model.IPPool{}, - } - p := Node{} - _, err := p.BackendV1ToAPIV3(resource) - Expect(err).To(HaveOccurred()) - }) -}) - -var nodeKVtoV3Table = []TableEntry{ - Entry("Conversion without the Net fields", - &model.KVPair{ - Key: model.NodeKey{ - Hostname: "my-node", - }, - Value: &model.Node{ - BGPASNumber: &asn, - BGPIPv4Addr: &ipv4IP, - BGPIPv6Addr: &ipv6IP, - }, - }, - libapiv3.Node{ - ObjectMeta: v1.ObjectMeta{ - Name: "my-node", - }, - Spec: libapiv3.NodeSpec{ - BGP: &libapiv3.NodeBGPSpec{ - ASNumber: &asn, - IPv4Address: "192.168.1.1/32", - // Note this is /128 instead of /64 - IPv6Address: "fed::5/128", - }, - }, - }, - ), - Entry("Conversion without any Address fields", - &model.KVPair{ - Key: model.NodeKey{ - Hostname: "my-node", - }, - Value: &model.Node{ - BGPASNumber: &asn, - }, - }, - libapiv3.Node{ - ObjectMeta: v1.ObjectMeta{ - Name: "my-node", - }, - Spec: libapiv3.NodeSpec{}, - }, - ), -} - -var _ = DescribeTable("KVP v1->v3 Node conversion tests", - func(v1KVP *model.KVPair, v3API libapiv3.Node) { - p := Node{} - // Check v1KVP->v3API. - convertedv3, err := p.BackendV1ToAPIV3(v1KVP) - Expect(err).NotTo(HaveOccurred()) - Expect(convertedv3.(*libapiv3.Node).ObjectMeta).To(Equal(v3API.ObjectMeta)) - Expect(convertedv3.(*libapiv3.Node).Spec).To(Equal(v3API.Spec)) - }, - - nodeKVtoV3Table..., -) diff --git a/libcalico-go/lib/upgrade/converters/policy.go b/libcalico-go/lib/upgrade/converters/policy.go deleted file mode 100644 index bfcc1d095a2..00000000000 --- a/libcalico-go/lib/upgrade/converters/policy.go +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) 2017-2024 Tigera, Inc. All rights reserved. - -// 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 converters - -import ( - "fmt" - "strings" - - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - log "github.com/sirupsen/logrus" - - apiv1 "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" -) - -// Policy implements the Converter interface. -type Policy struct{} - -// APIV1ToBackendV1 converts v1 Policy API to v1 Policy KVPair. -func (_ Policy) APIV1ToBackendV1(a unversioned.Resource) (*model.KVPair, error) { - ap := a.(*apiv1.Policy) - - d := model.KVPair{ - Key: model.PolicyKey{ - Name: ap.Metadata.Name, - Tier: ap.Metadata.Tier, - }, - Value: &model.Policy{ - Order: ap.Spec.Order, - InboundRules: rulesAPIV1ToBackend(ap.Spec.IngressRules), - OutboundRules: rulesAPIV1ToBackend(ap.Spec.EgressRules), - Selector: ap.Spec.Selector, - DoNotTrack: ap.Spec.DoNotTrack, - Annotations: ap.Metadata.Annotations, - PreDNAT: ap.Spec.PreDNAT, - Types: nil, // filled in below - }, - } - - if ap.Spec.DoNotTrack || ap.Spec.PreDNAT { - // This case happens when there is a preexisting policy in the datastore, from before - // the ApplyOnForward feature was available. DoNotTrack or PreDNAT policy applies to - // forward traffic by nature. So in this case we return ApplyOnForward flag as true. - d.Value.(*model.Policy).ApplyOnForward = true - } - - if len(ap.Spec.Types) == 0 { - // Default the Types field according to what inbound and outbound rules are present - // in the policy. - if len(ap.Spec.EgressRules) == 0 { - // Policy has no egress rules, so apply this policy to ingress only. (Note: - // intentionally including the case where the policy also has no ingress - // rules.) - d.Value.(*model.Policy).Types = []string{string(apiv1.PolicyTypeIngress)} - } else if len(ap.Spec.IngressRules) == 0 { - // Policy has egress rules but no ingress rules, so apply this policy to - // egress only. - d.Value.(*model.Policy).Types = []string{string(apiv1.PolicyTypeEgress)} - } else { - // Policy has both ingress and egress rules, so apply this policy to both - // ingress and egress. - d.Value.(*model.Policy).Types = []string{string(apiv1.PolicyTypeIngress), string(apiv1.PolicyTypeEgress)} - } - } else { - // Convert from the API-specified Types. - d.Value.(*model.Policy).Types = make([]string, len(ap.Spec.Types)) - for i, t := range ap.Spec.Types { - d.Value.(*model.Policy).Types[i] = string(t) - } - } - - log.WithFields(log.Fields{ - "APIv1": ap, - "KVPair": d, - }).Debugf("Converted Policy '%s' V1 API to V1 backend", ap.Metadata.Name) - - return &d, nil -} - -// BackendV1ToAPIV3 converts v1 Policy KVPair to v3 API. -func (_ Policy) BackendV1ToAPIV3(kvp *model.KVPair) (Resource, error) { - bp, ok := kvp.Value.(*model.Policy) - if !ok { - return nil, fmt.Errorf("value is not a valid Policy resource") - } - bk, ok := kvp.Key.(model.PolicyKey) - if !ok { - return nil, fmt.Errorf("value is not a valid Policy resource key") - } - - ap := apiv3.NewGlobalNetworkPolicy() - tier := convertNameNoDots(bk.Tier) - if tier == "" { - tier = "default" - } - ap.Name = tier + "." + convertNameNoDots(bk.Name) - ap.Annotations = bp.Annotations - ap.Labels = map[string]string{apiv3.LabelTier: tier} - ap.Spec.Order = bp.Order - ap.Spec.Ingress = rulesV1BackendToV3API(bp.InboundRules) - ap.Spec.Egress = rulesV1BackendToV3API(bp.OutboundRules) - ap.Spec.Selector = convertSelector(bp.Selector) - ap.Spec.DoNotTrack = bp.DoNotTrack - ap.Spec.PreDNAT = bp.PreDNAT - ap.Spec.ApplyOnForward = bp.ApplyOnForward - ap.Spec.Types = nil // Set later. - ap.Spec.Tier = tier - - if !bp.ApplyOnForward && (bp.DoNotTrack || bp.PreDNAT) { - // This case happens when there is a preexisting policy in the datastore, from before - // the ApplyOnForward feature was available. DoNotTrack or PreDNAT policy applies to - // forward traffic by nature. So in this case we return ApplyOnForward flag as true. - ap.Spec.ApplyOnForward = true - } - - if len(bp.Types) == 0 { - // This case happens when there is a preexisting policy in an etcd datastore, from - // before the explicit Types feature was available. Calico's previous behaviour was - // always to apply policy to both ingress and egress traffic, so in this case we - // return Types as [ ingress, egress ]. - if bp.PreDNAT { - ap.Spec.Types = []apiv3.PolicyType{apiv3.PolicyTypeIngress} - } else { - ap.Spec.Types = []apiv3.PolicyType{apiv3.PolicyTypeIngress, apiv3.PolicyTypeEgress} - } - } else { - // Convert from the backend-specified Types. - ap.Spec.Types = make([]apiv3.PolicyType, len(bp.Types)) - for i, t := range bp.Types { - if strings.ToLower(t) == "ingress" { - ap.Spec.Types[i] = apiv3.PolicyTypeIngress - } else if strings.ToLower(t) == "egress" { - ap.Spec.Types[i] = apiv3.PolicyTypeEgress - } else { - return nil, fmt.Errorf("invalid policy type: '%s'", t) - } - } - } - - log.WithFields(log.Fields{ - "KVPairV1": bp, - "APIv3": ap, - }).Debugf("Converted Policy '%s' V1 backend to GlobalNetworkPolicy '%s' V3 API", bk.Name, ap.Name) - - return ap, nil -} diff --git a/libcalico-go/lib/upgrade/converters/policy_test.go b/libcalico-go/lib/upgrade/converters/policy_test.go deleted file mode 100644 index 4270bec28ea..00000000000 --- a/libcalico-go/lib/upgrade/converters/policy_test.go +++ /dev/null @@ -1,443 +0,0 @@ -// Copyright (c) 2017-2024 Tigera, Inc. All rights reserved. - -// 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 converters - -import ( - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - apiv1 "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" -) - -var order1 = 1000.00 -var order2 = 999.99 -var policyTable = []TableEntry{ - Entry("fully populated Policy", - &apiv1.Policy{ - Metadata: apiv1.PolicyMetadata{ - Name: "nameyMcPolicyName", - Tier: "tieryMcTierFace", - }, - Spec: apiv1.PolicySpec{ - Order: &order1, - IngressRules: []apiv1.Rule{V1InRule1, V1InRule2}, - EgressRules: []apiv1.Rule{V1EgressRule1, V1EgressRule2}, - Selector: "calico/k8s_ns == 'default' || thing == 'value'", - DoNotTrack: false, // DoNotTrack and PreDNAT cannot both be true. - PreDNAT: true, - Types: []apiv1.PolicyType{apiv1.PolicyTypeIngress}, - }, - }, - &model.KVPair{ - Key: model.PolicyKey{ - Name: "nameyMcPolicyName", - Tier: "tieryMcTierFace", - }, - Value: &model.Policy{ - Order: &order1, - InboundRules: []model.Rule{V1ModelInRule1, V1ModelInRule2}, - OutboundRules: []model.Rule{V1ModelEgressRule1, V1ModelEgressRule2}, - Selector: "calico/k8s_ns == 'default' || thing == 'value'", - DoNotTrack: false, - PreDNAT: true, - ApplyOnForward: true, - Types: []string{"ingress"}, - }, - }, - apiv3.GlobalNetworkPolicy{ - ObjectMeta: v1.ObjectMeta{ - Name: "tierymctierface-df3092e8.nameymcpolicyname-32df456f", - Labels: map[string]string{apiv3.LabelTier: "tierymctierface-df3092e8"}, - }, - Spec: apiv3.GlobalNetworkPolicySpec{ - Order: &order1, - Ingress: []apiv3.Rule{V3InRule1, V3InRule2}, - Egress: []apiv3.Rule{V3EgressRule1, V3EgressRule2}, - Selector: "projectcalico.org/namespace == 'default' || thing == 'value'", - DoNotTrack: false, - PreDNAT: true, - ApplyOnForward: true, - Types: []apiv3.PolicyType{apiv3.PolicyTypeIngress}, - Tier: "tierymctierface-df3092e8", - }, - }, - ), - Entry("policy name conversion", - &apiv1.Policy{ - Metadata: apiv1.PolicyMetadata{ - Name: "MaKe.-.MaKe", - Tier: "TiEr.-.TiEr", - }, - Spec: apiv1.PolicySpec{ - Order: &order1, - IngressRules: []apiv1.Rule{V1InRule2}, - EgressRules: []apiv1.Rule{V1EgressRule1}, - Selector: "thing == 'value'", - DoNotTrack: true, - PreDNAT: false, - Types: []apiv1.PolicyType{apiv1.PolicyTypeIngress}, - }, - }, - &model.KVPair{ - Key: model.PolicyKey{ - Name: "MaKe.-.MaKe", - Tier: "TiEr.-.TiEr", - }, - Value: &model.Policy{ - Order: &order1, - InboundRules: []model.Rule{V1ModelInRule2}, - OutboundRules: []model.Rule{V1ModelEgressRule1}, - Selector: "thing == 'value'", - DoNotTrack: true, - PreDNAT: false, - ApplyOnForward: true, - Types: []string{"ingress"}, - }, - }, - apiv3.GlobalNetworkPolicy{ - ObjectMeta: v1.ObjectMeta{ - Name: "tier-tier-dcb6465f.make-make-1b6971c8", - Labels: map[string]string{apiv3.LabelTier: "tier-tier-dcb6465f"}, - }, - Spec: apiv3.GlobalNetworkPolicySpec{ - Order: &order1, - Ingress: []apiv3.Rule{V3InRule2}, - Egress: []apiv3.Rule{V3EgressRule1}, - Selector: "thing == 'value'", - DoNotTrack: true, - PreDNAT: false, - ApplyOnForward: true, - Types: []apiv3.PolicyType{apiv3.PolicyTypeIngress}, - Tier: "tier-tier-dcb6465f", - }, - }, - ), - Entry("explicit default tier policy with PreDNAT set to true should convert ApplyOnForward to true in v3 API", - &apiv1.Policy{ - Metadata: apiv1.PolicyMetadata{ - Name: "RAWR", - Tier: "default", - }, - Spec: apiv1.PolicySpec{ - Order: &order1, - IngressRules: []apiv1.Rule{V1InRule2}, - PreDNAT: true, - Types: []apiv1.PolicyType{apiv1.PolicyTypeIngress}, - }, - }, - &model.KVPair{ - Key: model.PolicyKey{ - Name: "RAWR", - Tier: "default", - }, - Value: &model.Policy{ - Order: &order1, - InboundRules: []model.Rule{V1ModelInRule2}, - OutboundRules: []model.Rule{}, - DoNotTrack: false, - PreDNAT: true, - ApplyOnForward: true, - Types: []string{"ingress"}, - }, - }, - apiv3.GlobalNetworkPolicy{ - ObjectMeta: v1.ObjectMeta{ - Name: "default.rawr-03d81e1d", - Labels: map[string]string{apiv3.LabelTier: "default"}, - }, - Spec: apiv3.GlobalNetworkPolicySpec{ - Order: &order1, - Ingress: []apiv3.Rule{V3InRule2}, - Egress: []apiv3.Rule{}, - DoNotTrack: false, - PreDNAT: true, - ApplyOnForward: true, // notice this gets converted to true, because PreDNAT are true. - Types: []apiv3.PolicyType{apiv3.PolicyTypeIngress}, - Tier: "default", - }, - }, - ), - Entry("policy with DoNotTrack set to true "+ - "should convert ApplyOnForward to true in v3 API", - &apiv1.Policy{ - Metadata: apiv1.PolicyMetadata{ - Name: "RAWR", - }, - Spec: apiv1.PolicySpec{ - Order: &order1, - IngressRules: []apiv1.Rule{V1InRule2}, - DoNotTrack: true, - Types: []apiv1.PolicyType{apiv1.PolicyTypeIngress}, - }, - }, - &model.KVPair{ - Key: model.PolicyKey{ - Name: "RAWR", - }, - Value: &model.Policy{ - Order: &order1, - InboundRules: []model.Rule{V1ModelInRule2}, - OutboundRules: []model.Rule{}, - DoNotTrack: true, - PreDNAT: false, - ApplyOnForward: true, - Types: []string{"ingress"}, - }, - }, - apiv3.GlobalNetworkPolicy{ - ObjectMeta: v1.ObjectMeta{ - Name: "default.rawr-03d81e1d", - Labels: map[string]string{apiv3.LabelTier: "default"}, - }, - Spec: apiv3.GlobalNetworkPolicySpec{ - Order: &order1, - Ingress: []apiv3.Rule{V3InRule2}, - Egress: []apiv3.Rule{}, - DoNotTrack: true, - PreDNAT: false, - ApplyOnForward: true, // notice this gets converted to true, because DoNotTrack are true. - Types: []apiv3.PolicyType{apiv3.PolicyTypeIngress}, - Tier: "default", - }, - }, - ), - Entry("policy with PreDNAT and DoNotTrack both set to false should NOT convert ApplyOnForward to true in v3 API", - &apiv1.Policy{ - Metadata: apiv1.PolicyMetadata{ - Name: "meow", - }, - Spec: apiv1.PolicySpec{ - Order: &order1, - IngressRules: []apiv1.Rule{V1InRule2}, - DoNotTrack: false, - PreDNAT: false, - Types: []apiv1.PolicyType{apiv1.PolicyTypeIngress}, - }, - }, - &model.KVPair{ - Key: model.PolicyKey{ - Name: "meow", - }, - Value: &model.Policy{ - Order: &order1, - InboundRules: []model.Rule{V1ModelInRule2}, - OutboundRules: []model.Rule{}, - DoNotTrack: false, - PreDNAT: false, - ApplyOnForward: false, - Types: []string{"ingress"}, - }, - }, - apiv3.GlobalNetworkPolicy{ - ObjectMeta: v1.ObjectMeta{ - Name: "default.meow", - Labels: map[string]string{apiv3.LabelTier: "default"}, - }, - Spec: apiv3.GlobalNetworkPolicySpec{ - Order: &order1, - Ingress: []apiv3.Rule{V3InRule2}, - Egress: []apiv3.Rule{}, - DoNotTrack: false, - PreDNAT: false, - ApplyOnForward: false, - Types: []apiv3.PolicyType{apiv3.PolicyTypeIngress}, - Tier: "default", - }, - }, - ), - Entry("policy with non-strictly masked CIDR should get converted to strictly masked CIDR in v3 API", - &apiv1.Policy{ - Metadata: apiv1.PolicyMetadata{ - Name: "MaKe.-.MaKe", - }, - Spec: apiv1.PolicySpec{ - Order: &order1, - // Source Nets selector in V1InRule1 and V1EgressRule1 are non-strictly masked CIDRs. - IngressRules: []apiv1.Rule{V1InRule1}, - EgressRules: []apiv1.Rule{V1EgressRule1}, - Selector: "thing == 'value'", - DoNotTrack: true, - PreDNAT: false, - Types: []apiv1.PolicyType{apiv1.PolicyTypeIngress, apiv1.PolicyTypeEgress}, - }, - }, - &model.KVPair{ - Key: model.PolicyKey{ - Name: "MaKe.-.MaKe", - }, - Value: &model.Policy{ - Order: &order1, - // Source Nets selector in V1ModelInRule1 and V1ModelEgressRule1 are non-strictly masked CIDRs. - InboundRules: []model.Rule{V1ModelInRule1}, - OutboundRules: []model.Rule{V1ModelEgressRule1}, - Selector: "thing == 'value'", - DoNotTrack: true, - PreDNAT: false, - ApplyOnForward: true, - Types: []string{"ingress", "egress"}, - }, - }, - apiv3.GlobalNetworkPolicy{ - ObjectMeta: v1.ObjectMeta{ - Name: "default.make-make-1b6971c8", - Labels: map[string]string{apiv3.LabelTier: "default"}, - }, - Spec: apiv3.GlobalNetworkPolicySpec{ - Order: &order1, - // Source Nets selector in V3InRule1 and V3EgressRule1 are strictly masked CIDRs. - Ingress: []apiv3.Rule{V3InRule1}, - Egress: []apiv3.Rule{V3EgressRule1}, - Selector: "thing == 'value'", - DoNotTrack: true, - PreDNAT: false, - ApplyOnForward: true, - Types: []apiv3.PolicyType{apiv3.PolicyTypeIngress, apiv3.PolicyTypeEgress}, - Tier: "default", - }, - }, - ), - Entry("policy test3", - &apiv1.Policy{ - Metadata: apiv1.PolicyMetadata{ - Name: "policy1", - }, - Spec: apiv1.PolicySpec{ - Order: &order1, - // Source Nets selector in V1InRule1 and V1EgressRule1 are non-strictly masked CIDRs. - IngressRules: []apiv1.Rule{ - { - Action: "deny", - Source: apiv1.EntityRule{ - Selector: "type=='application'", - }, - }, - }, - Selector: "type=='database'", - }, - }, - &model.KVPair{ - Key: model.PolicyKey{ - Name: "policy1", - }, - Value: &model.Policy{ - Order: &order1, - // Source Nets selector in V1ModelInRule1 and V1ModelEgressRule1 are non-strictly masked CIDRs. - InboundRules: []model.Rule{{ - Action: "deny", - SrcSelector: "type=='application'", - }}, - OutboundRules: []model.Rule{}, - Selector: "type=='database'", - Types: []string{"ingress"}, - }, - }, - apiv3.GlobalNetworkPolicy{ - ObjectMeta: v1.ObjectMeta{ - Name: "default.policy1", - Labels: map[string]string{apiv3.LabelTier: "default"}, - }, - Spec: apiv3.GlobalNetworkPolicySpec{ - Order: &order1, - // Source Nets selector in V3InRule1 and V3EgressRule1 are strictly masked CIDRs. - Ingress: []apiv3.Rule{{ - Action: apiv3.Deny, - Source: apiv3.EntityRule{ - Selector: "type=='application'", - }, - }}, - Egress: []apiv3.Rule{}, - Selector: "type=='database'", - Types: []apiv3.PolicyType{apiv3.PolicyTypeIngress}, - Tier: "default", - }, - }, - ), -} - -var _ = DescribeTable("v1->v3 policy conversion tests", - func(v1API unversioned.Resource, v1KVP *model.KVPair, v3API apiv3.GlobalNetworkPolicy) { - p := Policy{} - - // Test and assert v1 API to v1 backend logic. - v1KVPResult, err := p.APIV1ToBackendV1(v1API) - Expect(err).NotTo(HaveOccurred()) - Expect(v1KVPResult.Key.(model.PolicyKey).Name).To(Equal(v1KVP.Key.(model.PolicyKey).Name)) - Expect(v1KVPResult.Value.(*model.Policy)).To(Equal(v1KVP.Value)) - - // Test and assert v1 backend to v3 API logic. - v3APIResult, err := p.BackendV1ToAPIV3(v1KVP) - Expect(err).NotTo(HaveOccurred()) - Expect(v3APIResult.(*apiv3.GlobalNetworkPolicy).Name).To(Equal(v3API.Name)) - Expect(v3APIResult.(*apiv3.GlobalNetworkPolicy).Spec).To(Equal(v3API.Spec)) - }, - - policyTable..., -) - -var policyTableBackend = []TableEntry{ - Entry("converting model with no Types with preDNAT to v3 API only has Ingress for Types", - &model.KVPair{ - Key: model.PolicyKey{ - Name: "somekey", - }, - Value: &model.Policy{ - Order: &order1, - // Source Nets selector in V1ModelInRule1 and V1ModelEgressRule1 are non-strictly masked CIDRs. - InboundRules: []model.Rule{V1ModelInRule1}, - OutboundRules: []model.Rule{}, - Selector: "thing == 'value'", - DoNotTrack: true, - PreDNAT: true, - ApplyOnForward: true, - Types: []string{}, - }, - }, - apiv3.GlobalNetworkPolicy{ - ObjectMeta: v1.ObjectMeta{ - Name: "default.somekey", - }, - Spec: apiv3.GlobalNetworkPolicySpec{ - Order: &order1, - // Source Nets selector in V3InRule1 and V3EgressRule1 are strictly masked CIDRs. - Ingress: []apiv3.Rule{V3InRule1}, - Egress: []apiv3.Rule{}, - Selector: "thing == 'value'", - DoNotTrack: true, - PreDNAT: true, - ApplyOnForward: true, - Types: []apiv3.PolicyType{apiv3.PolicyTypeIngress}, - Tier: "default", - }, - }, - ), -} - -var _ = DescribeTable("v1->v3 policy conversion tests (backend)", - func(v1KVP *model.KVPair, v3API apiv3.GlobalNetworkPolicy) { - p := Policy{} - - // Test and assert v1 backend to v3 API logic. - v3APIResult, err := p.BackendV1ToAPIV3(v1KVP) - Expect(err).NotTo(HaveOccurred()) - Expect(v3APIResult.(*apiv3.GlobalNetworkPolicy).Name).To(Equal(v3API.Name)) - Expect(v3APIResult.(*apiv3.GlobalNetworkPolicy).Spec).To(Equal(v3API.Spec)) - }, - - policyTableBackend..., -) diff --git a/libcalico-go/lib/upgrade/converters/profile.go b/libcalico-go/lib/upgrade/converters/profile.go deleted file mode 100644 index 530d1b76d7b..00000000000 --- a/libcalico-go/lib/upgrade/converters/profile.go +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) 2017-2018 Tigera, Inc. All rights reserved. - -// 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 converters - -import ( - "fmt" - "strings" - - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - log "github.com/sirupsen/logrus" - - apiv1 "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" -) - -// Profile implements the Converter interface. -type Profile struct{} - -// APIV1ToBackendV1 converts v1 Profile API to v1 Profile KVPair. -func (_ Profile) APIV1ToBackendV1(a unversioned.Resource) (*model.KVPair, error) { - ap := a.(*apiv1.Profile) - - // Fix up tags and labels so to be empty values rather than nil. Felix does not - // expect a null value in the JSON, so we fix up to make Labels an empty map - // and tags an empty slice. - tags := ap.Metadata.Tags - if tags == nil { - log.Debug("Tags is nil - convert to empty map for backend") - tags = []string{} - } - labels := ap.Metadata.Labels - if labels == nil { - log.Debug("Labels is nil - convert to empty map for backend") - labels = map[string]string{} - } - - d := model.KVPair{ - Key: model.ProfileKey{ - Name: ap.Metadata.Name, - }, - Value: &model.Profile{ - Rules: model.ProfileRules{ - InboundRules: rulesAPIV1ToBackend(ap.Spec.IngressRules), - OutboundRules: rulesAPIV1ToBackend(ap.Spec.EgressRules), - }, - Tags: tags, - Labels: labels, - }, - } - - log.WithFields(log.Fields{ - "APIv1": ap, - "KVPair": d, - }).Debugf("Converted Profile: '%s' V1 API to V1 backend", ap.Metadata.Name) - - return &d, nil -} - -// BackendV1ToAPIV3 converts v1 Profile KVPair to v3 API. -func (_ Profile) BackendV1ToAPIV3(kvp *model.KVPair) (Resource, error) { - bp, ok := kvp.Value.(*model.Profile) - if !ok { - return nil, fmt.Errorf("value is not a valid Profile resource") - } - bk, ok := kvp.Key.(model.ProfileKey) - if !ok { - return nil, fmt.Errorf("value is not a valid Profile resource key") - } - - ap := apiv3.NewProfile() - ap.Name = convertProfileName(bk.Name) - - // Merge Tags and Labels into LabelsToApply. - combinedLabelsToApply := bp.Labels - if combinedLabelsToApply == nil && len(bp.Tags) != 0 { - combinedLabelsToApply = map[string]string{} - } - for _, t := range bp.Tags { - // Check to make sure the key doesn't already exist before merging it. - if val, ok := combinedLabelsToApply[t]; ok { - return nil, fmt.Errorf("Tag: '%s' and Label '%s == %s' have the same value for Profile: %s. Change the Label key before proceeding", t, t, val, ap.Name) - } - combinedLabelsToApply[t] = "" - } - - ap.Spec.LabelsToApply = combinedLabelsToApply - - ap.Spec.Ingress = rulesV1BackendToV3API(bp.Rules.InboundRules) - ap.Spec.Egress = rulesV1BackendToV3API(bp.Rules.OutboundRules) - - log.WithFields(log.Fields{ - "KVPairV1": bp, - "APIv3": ap, - }).Debugf("Converted Profile: '%s' V1 backend to V3 API", ap.Name) - - return ap, nil -} - -// convertProfileName updates the Kubernetes namespace portion from "k8s_ns" to "kns". -func convertProfileName(in string) string { - if strings.HasPrefix(in, "k8s_ns.") { - prof := "kns." + strings.TrimPrefix(in, "k8s_ns.") - return convertName(prof) - } - return convertName(in) -} diff --git a/libcalico-go/lib/upgrade/converters/profile_test.go b/libcalico-go/lib/upgrade/converters/profile_test.go deleted file mode 100644 index 471239a80d2..00000000000 --- a/libcalico-go/lib/upgrade/converters/profile_test.go +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright (c) 2017-2018 Tigera, Inc. All rights reserved. - -// 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 converters - -import ( - "fmt" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - apiv1 "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" -) - -var _ = DescribeTable("v1->v3 profile conversion tests table", - func(v1API *apiv1.Profile, v1KVP *model.KVPair, v3API apiv3.Profile) { - p := Profile{} - - // Test and assert v1 API to v1 backend logic. - v1KVPResult, err := p.APIV1ToBackendV1(v1API) - Expect(err).NotTo(HaveOccurred()) - Expect(v1KVPResult.Key.(model.ProfileKey).Name).To(Equal(v1KVP.Key.(model.ProfileKey).Name)) - Expect(v1KVPResult.Value.(*model.Profile)).To(Equal(v1KVP.Value)) - - // Test and assert v1 backend to v3 API logic. - v3APIResult, err := p.BackendV1ToAPIV3(v1KVP) - Expect(err).NotTo(HaveOccurred()) - Expect(v3APIResult.(*apiv3.Profile).Name).To(Equal(v3API.Name)) - Expect(v3APIResult.(*apiv3.Profile).Spec).To(Equal(v3API.Spec)) - }, - - Entry("fully populated Profile", - &apiv1.Profile{ - Metadata: apiv1.ProfileMetadata{ - Name: "nameyMcProfileName", - Tags: []string{"meep", "mop"}, - Labels: map[string]string{"pncs.thingy": "val"}, - }, - Spec: apiv1.ProfileSpec{ - IngressRules: []apiv1.Rule{V1InRule1, V1InRule2}, - EgressRules: []apiv1.Rule{V1EgressRule1, V1EgressRule2}, - }, - }, - &model.KVPair{ - Key: model.ProfileKey{ - Name: "nameyMcProfileName", - }, - Value: &model.Profile{ - Rules: model.ProfileRules{ - InboundRules: []model.Rule{V1ModelInRule1, V1ModelInRule2}, - OutboundRules: []model.Rule{V1ModelEgressRule1, V1ModelEgressRule2}, - }, - Tags: []string{"meep", "mop"}, - Labels: map[string]string{"pncs.thingy": "val"}, - }, - }, - apiv3.Profile{ - ObjectMeta: v1.ObjectMeta{ - Name: "nameymcprofilename-9740ed19", - }, - Spec: apiv3.ProfileSpec{ - Ingress: []apiv3.Rule{V3InRule1, V3InRule2}, - Egress: []apiv3.Rule{V3EgressRule1, V3EgressRule2}, - LabelsToApply: map[string]string{"pncs.thingy": "val", "meep": "", "mop": ""}, - }, - }, - ), - - Entry("Profile name conversion", - &apiv1.Profile{ - Metadata: apiv1.ProfileMetadata{ - Name: "k8s_ns.FlUx-.-CaPaCiToR$$", - Tags: []string{"lalala"}, - }, - Spec: apiv1.ProfileSpec{ - IngressRules: []apiv1.Rule{V1InRule1, V1InRule2}, - EgressRules: []apiv1.Rule{}, - }, - }, - &model.KVPair{ - Key: model.ProfileKey{ - Name: "k8s_ns.FlUx-.-CaPaCiToR$$", - }, - Value: &model.Profile{ - Rules: model.ProfileRules{ - InboundRules: []model.Rule{V1ModelInRule1, V1ModelInRule2}, - OutboundRules: []model.Rule{}, - }, - Tags: []string{"lalala"}, - Labels: map[string]string{}, - }, - }, - apiv3.Profile{ - ObjectMeta: v1.ObjectMeta{ - Name: "kns.flux.capacitor-a9ad9f16", - }, - Spec: apiv3.ProfileSpec{ - Ingress: []apiv3.Rule{V3InRule1, V3InRule2}, - Egress: []apiv3.Rule{}, - LabelsToApply: map[string]string{"lalala": ""}, - }, - }, - ), -) - -var _ = Describe("v1->v3 profile conversion tests", func() { - It("Profile conversion should exit with an error when a Tag has the same value as a Label key", func() { - p := Profile{} - - v1KVP := &model.KVPair{ - Key: model.ProfileKey{ - Name: "makemake", - }, - Value: &model.Profile{ - Rules: model.ProfileRules{ - InboundRules: []model.Rule{V1ModelInRule1, V1ModelInRule2}, - OutboundRules: []model.Rule{}, - }, - Tags: []string{"lalala", "covfefe", "meep"}, - Labels: map[string]string{"thing1": "val1", "covfefe": "hot", "thing2": "val2"}, - }, - } - - // Assert that converting v1 backend to v3 API returns an error. - _, err := p.BackendV1ToAPIV3(v1KVP) - Expect(err).To(HaveOccurred()) - e := fmt.Sprintf("Tag: '%s' and Label '%s == %s' have the same value for Profile: %s. Change the Label key before proceeding", "covfefe", "covfefe", "hot", "makemake") - Expect(err.Error()).To(Equal(e)) - }) - - It("Profile conversion should succeed when there are no labels but there are tags", func() { - p := Profile{} - - v1KVP := &model.KVPair{ - Key: model.ProfileKey{ - Name: "makemake", - }, - Value: &model.Profile{ - Rules: model.ProfileRules{ - InboundRules: []model.Rule{V1ModelInRule1, V1ModelInRule2}, - OutboundRules: []model.Rule{}, - }, - Tags: []string{"lalala", "covfefe", "meep"}, - Labels: nil, - }, - } - - // Assert that converting v1 backend to v3 API returns an error. - _, err := p.BackendV1ToAPIV3(v1KVP) - Expect(err).NotTo(HaveOccurred()) - }) - - It("Profile conversion should succeed when there are no tags or labels", func() { - p := Profile{} - - v1KVP := &model.KVPair{ - Key: model.ProfileKey{ - Name: "makemake", - }, - Value: &model.Profile{ - Rules: model.ProfileRules{ - InboundRules: []model.Rule{V1ModelInRule1, V1ModelInRule2}, - OutboundRules: []model.Rule{}, - }, - Tags: nil, - Labels: nil, - }, - } - - // Assert that converting v1 backend to v3 API returns an error. - _, err := p.BackendV1ToAPIV3(v1KVP) - Expect(err).NotTo(HaveOccurred()) - }) -}) diff --git a/libcalico-go/lib/upgrade/converters/rule.go b/libcalico-go/lib/upgrade/converters/rule.go deleted file mode 100644 index d4cb731b2df..00000000000 --- a/libcalico-go/lib/upgrade/converters/rule.go +++ /dev/null @@ -1,281 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 converters - -import ( - "fmt" - "strings" - - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - "github.com/projectcalico/api/pkg/lib/numorstring" - log "github.com/sirupsen/logrus" - - apiv1 "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/net" -) - -// rulesAPIV1ToBackend converts an API Rule structure slice to a Backend Rule structure slice. -func rulesAPIV1ToBackend(ars []apiv1.Rule) []model.Rule { - if ars == nil { - return []model.Rule{} - } - - brs := make([]model.Rule, len(ars)) - for idx, ar := range ars { - brs[idx] = ruleAPIToBackend(ar) - } - return brs -} - -// rulesV1BackendToV3API converts a Backend Rule structure slice to an API Rule structure slice. -func rulesV1BackendToV3API(brs []model.Rule) []apiv3.Rule { - if brs == nil { - return nil - } - - ars := make([]apiv3.Rule, len(brs)) - for idx, br := range brs { - ars[idx] = rulebackendToAPIv3(br) - } - return ars -} - -// ruleAPIToBackend converts an API Rule structure to a Backend Rule structure. -func ruleAPIToBackend(ar apiv1.Rule) model.Rule { - var icmpCode, icmpType, notICMPCode, notICMPType *int - if ar.ICMP != nil { - icmpCode = ar.ICMP.Code - icmpType = ar.ICMP.Type - } - - if ar.NotICMP != nil { - notICMPCode = ar.NotICMP.Code - notICMPType = ar.NotICMP.Type - } - - return model.Rule{ - Action: ruleActionV1APIToV1Backend(ar.Action), - IPVersion: ar.IPVersion, - Protocol: ar.Protocol, - ICMPCode: icmpCode, - ICMPType: icmpType, - NotProtocol: ar.NotProtocol, - NotICMPCode: notICMPCode, - NotICMPType: notICMPType, - - SrcTag: ar.Source.Tag, - SrcNet: normalizeIPNet(ar.Source.Net), - SrcNets: normalizeIPNets(ar.Source.Nets), - SrcSelector: ar.Source.Selector, - SrcPorts: ar.Source.Ports, - DstTag: ar.Destination.Tag, - DstNet: normalizeIPNet(ar.Destination.Net), - DstNets: normalizeIPNets(ar.Destination.Nets), - DstSelector: ar.Destination.Selector, - DstPorts: ar.Destination.Ports, - - NotSrcTag: ar.Source.NotTag, - NotSrcNet: normalizeIPNet(ar.Source.NotNet), - NotSrcNets: normalizeIPNets(ar.Source.NotNets), - NotSrcSelector: ar.Source.NotSelector, - NotSrcPorts: ar.Source.NotPorts, - NotDstTag: ar.Destination.NotTag, - NotDstNet: normalizeIPNet(ar.Destination.NotNet), - NotDstNets: normalizeIPNets(ar.Destination.NotNets), - NotDstSelector: ar.Destination.NotSelector, - NotDstPorts: ar.Destination.NotPorts, - } -} - -// normalizeIPNet converts an IPNet to a network by ensuring the IP address is correctly masked. -func normalizeIPNet(n *net.IPNet) *net.IPNet { - if n == nil { - return nil - } - return n.Network() -} - -// normalizeIPNets converts an []*IPNet to a slice of networks by ensuring the IP addresses -// are correctly masked. -func normalizeIPNets(nets []*net.IPNet) []*net.IPNet { - if nets == nil { - return nil - } - out := make([]*net.IPNet, len(nets)) - for i, n := range nets { - out[i] = normalizeIPNet(n) - } - return out -} - -// rulebackendToAPIv3 convert a Backend Rule structure to an API Rule structure. -func rulebackendToAPIv3(br model.Rule) apiv3.Rule { - var icmp, notICMP *apiv3.ICMPFields - if br.ICMPCode != nil || br.ICMPType != nil { - icmp = &apiv3.ICMPFields{ - Code: br.ICMPCode, - Type: br.ICMPType, - } - } - if br.NotICMPCode != nil || br.NotICMPType != nil { - notICMP = &apiv3.ICMPFields{ - Code: br.NotICMPCode, - Type: br.NotICMPType, - } - } - - // Normalize the backend source Net/Nets/NotNet/NotNets - // This is because of a bug where we didn't normalize - // source (not)net(s) while converting API to backend in v1. - br.SrcNet = normalizeIPNet(br.SrcNet) - br.SrcNets = normalizeIPNets(br.SrcNets) - br.NotSrcNet = normalizeIPNet(br.NotSrcNet) - br.NotSrcNets = normalizeIPNets(br.NotSrcNets) - // Also normalize destination (Not)Net(s) for consistency. - br.DstNet = normalizeIPNet(br.DstNet) - br.DstNets = normalizeIPNets(br.DstNets) - br.NotDstNet = normalizeIPNet(br.NotDstNet) - br.NotDstNets = normalizeIPNets(br.NotDstNets) - - srcNets := br.AllSrcNets() - var srcNetsStr []string - for _, net := range srcNets { - srcNetsStr = append(srcNetsStr, net.String()) - } - - dstNets := br.AllDstNets() - var dstNetsStr []string - for _, net := range dstNets { - dstNetsStr = append(dstNetsStr, net.String()) - } - - notSrcNets := br.AllNotSrcNets() - var notSrcNetsStr []string - for _, net := range notSrcNets { - notSrcNetsStr = append(notSrcNetsStr, net.String()) - } - - notDstNets := br.AllNotDstNets() - var notDstNetsStr []string - for _, net := range notDstNets { - notDstNetsStr = append(notDstNetsStr, net.String()) - } - - srcSelector := mergeTagsAndSelectors(convertSelector(br.SrcSelector), br.SrcTag) - dstSelector := mergeTagsAndSelectors(convertSelector(br.DstSelector), br.DstTag) - notSrcSelector := mergeTagsAndSelectors(convertSelector(br.NotSrcSelector), br.NotSrcTag) - notDstSelector := mergeTagsAndSelectors(convertSelector(br.NotDstSelector), br.NotDstTag) - - var v3Protocol *numorstring.Protocol - if br.Protocol != nil { - protocol := numorstring.ProtocolV3FromProtocolV1(*br.Protocol) - v3Protocol = &protocol - } - - var v3NotProtocol *numorstring.Protocol - if br.NotProtocol != nil { - notProtocol := numorstring.ProtocolV3FromProtocolV1(*br.NotProtocol) - v3NotProtocol = ¬Protocol - } - - return apiv3.Rule{ - Action: ruleActionV1ToV3API(br.Action), - IPVersion: br.IPVersion, - Protocol: v3Protocol, - ICMP: icmp, - NotProtocol: v3NotProtocol, - NotICMP: notICMP, - Source: apiv3.EntityRule{ - Nets: srcNetsStr, - Selector: srcSelector, - Ports: br.SrcPorts, - NotNets: notSrcNetsStr, - NotSelector: notSrcSelector, - NotPorts: br.NotSrcPorts, - }, - - Destination: apiv3.EntityRule{ - Nets: dstNetsStr, - Selector: dstSelector, - Ports: br.DstPorts, - NotNets: notDstNetsStr, - NotSelector: notDstSelector, - NotPorts: br.NotDstPorts, - }, - } -} - -// mergeTagsAndSelectors merges tags into selectors. -// Tags are deprecated in v3.0+, so we convert Tags to selectors. -// For example: -// A rule that looks like this with Calico v1 API: -// -// apiv1.Rule{ -// Action: "allow", -// Source: apiv1.EntityRule{ -// Tag: "tag1", -// Selector: "label1 == 'value1' || make == 'cake'", -// }, -// } -// -// That rule will be converted to the following for Calico v3 API: -// -// apiv3.Rule{ -// Action: "allow", -// Source: apiv3.EntityRule{ -// Selector: "(label1 == 'value1' || make == 'cake') && tag1 == ''", -// }, -// } -func mergeTagsAndSelectors(sel, tag string) string { - if tag != "" { - if sel != "" { - sel = fmt.Sprintf("(%s) && %s == ''", sel, tag) - } else { - sel = fmt.Sprintf("%s == ''", tag) - } - } - - return sel -} - -// ruleActionV1APIToV1Backend converts the rule action field value from the API -// value to the equivalent backend value. -func ruleActionV1APIToV1Backend(action string) string { - if strings.ToLower(action) == "pass" { - return "next-tier" - } - return action -} - -// ruleActionV1ToV3API converts the rule action field value from the backend -// value to the equivalent API value. -func ruleActionV1ToV3API(inAction string) apiv3.Action { - if inAction == "" { - return apiv3.Allow - } else if inAction == "next-tier" { - return apiv3.Pass - } else { - for _, action := range []apiv3.Action{apiv3.Allow, apiv3.Deny, apiv3.Log, apiv3.Pass} { - if strings.EqualFold(inAction, string(action)) { - return action - } - } - } - - log.Warnf("Unknown Rule action: '%s'", inAction) - - return apiv3.Action(inAction) -} diff --git a/libcalico-go/lib/upgrade/converters/rules_def.go b/libcalico-go/lib/upgrade/converters/rules_def.go deleted file mode 100644 index fbf3e1b7b21..00000000000 --- a/libcalico-go/lib/upgrade/converters/rules_def.go +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 converters - -import ( - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - "github.com/projectcalico/api/pkg/lib/numorstring" - - apiv1 "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/net" -) - -var ipv4 = 4 -var ipv6 = 6 -var v1strProtocol1 = numorstring.ProtocolFromStringV1("icmp") -var v3strProtocol1 = numorstring.ProtocolFromString("ICMP") -var v1strProtocol2 = numorstring.ProtocolFromStringV1("udp") -var v3strProtocol2 = numorstring.ProtocolFromString("UDP") -var numProtocol1 = numorstring.ProtocolFromInt(240) - -var icmpType1 = 100 -var icmpCode1 = 200 - -var cidr1StrictMaskStr = "192.168.0.128/25" -var cidr2StrictMaskStr = "20.0.0.0/24" -var cidr3StrictMaskStr = "30.0.0.0/24" -var cidr4StrictMaskStr = "40.0.0.0/24" -var cidr1Str = "192.168.0.129/25" -var cidr2Str = "20.0.0.2/24" -var cidr3Str = "30.0.0.3/24" -var cidr4Str = "40.0.0.4/24" -var cidrv61Str = "abcd:5555::/120" -var cidrv62Str = "abcd:2345::/120" - -var cidr1 = net.MustParseCIDR(cidr1Str) -var cidr1Net = net.MustParseNetwork(cidr1Str) -var cidr2 = net.MustParseCIDR(cidr2Str) -var cidr2Net = net.MustParseNetwork(cidr2Str) -var cidr3 = net.MustParseCIDR(cidr3Str) -var cidr3Net = net.MustParseNetwork(cidr3Str) -var cidr4 = net.MustParseCIDR(cidr4Str) -var cidr4Net = net.MustParseNetwork(cidr4Str) -var cidrv61 = net.MustParseNetwork(cidrv61Str) -var cidrv62 = net.MustParseNetwork(cidrv62Str) - -var icmp1 = apiv1.ICMPFields{ - Type: &icmpType1, - Code: &icmpCode1, -} - -var v3icmp1 = apiv3.ICMPFields{ - Type: &icmpType1, - Code: &icmpCode1, -} - -var V1InRule1 = apiv1.Rule{ - Action: "allow", - IPVersion: &ipv4, - Protocol: &v1strProtocol1, - ICMP: &icmp1, - Source: apiv1.EntityRule{ - Tag: "tag1", - Net: &cidr1, - Nets: []*net.IPNet{&cidr3, &cidr4}, - NotNet: &cidr2, - NotNets: []*net.IPNet{&cidr1, &cidr3}, - Selector: "has(calico/k8s_ns) || bake == 'cake'", - }, - Destination: apiv1.EntityRule{ - Tag: "kingindanorth", - Net: &cidr2, - Nets: []*net.IPNet{&cidr1, &cidr3}, - NotNet: &cidr1, - NotNets: []*net.IPNet{&cidr3, &cidr4}, - }, -} - -var V1ModelInRule1 = model.Rule{ - Action: "allow", - IPVersion: &ipv4, - Protocol: &v1strProtocol1, - ICMPType: &icmpType1, - ICMPCode: &icmpCode1, - SrcTag: "tag1", - SrcNet: &cidr1Net, - SrcNets: []*net.IPNet{&cidr3Net, &cidr4Net}, - NotSrcNet: &cidr2Net, - NotSrcNets: []*net.IPNet{&cidr1Net, &cidr3Net}, - DstTag: "kingindanorth", - DstNet: &cidr2Net, - DstNets: []*net.IPNet{&cidr1Net, &cidr3Net}, - NotDstNet: &cidr1Net, - NotDstNets: []*net.IPNet{&cidr3Net, &cidr4Net}, - SrcSelector: "has(calico/k8s_ns) || bake == 'cake'", -} - -var V3InRule1 = apiv3.Rule{ - Action: apiv3.Allow, - IPVersion: &ipv4, - Protocol: &v3strProtocol1, - ICMP: &v3icmp1, - Source: apiv3.EntityRule{ - Nets: []string{cidr3StrictMaskStr, cidr4StrictMaskStr, cidr1StrictMaskStr}, - NotNets: []string{cidr1StrictMaskStr, cidr3StrictMaskStr, cidr2StrictMaskStr}, - Selector: "(has(projectcalico.org/namespace) || bake == 'cake') && tag1 == ''", - }, - Destination: apiv3.EntityRule{ - Nets: []string{cidr1StrictMaskStr, cidr3StrictMaskStr, cidr2StrictMaskStr}, - NotNets: []string{cidr3StrictMaskStr, cidr4StrictMaskStr, cidr1StrictMaskStr}, - Selector: "kingindanorth == ''", - }, -} - -var V1InRule2 = apiv1.Rule{ - Action: "deny", - IPVersion: &ipv6, - Protocol: &numProtocol1, - ICMP: &icmp1, - Source: apiv1.EntityRule{ - Tag: "tag2", - Net: &cidrv61, - Selector: "has(label2)", - }, -} - -var V1ModelInRule2 = model.Rule{ - Action: "deny", - IPVersion: &ipv6, - Protocol: &numProtocol1, - ICMPType: &icmpType1, - ICMPCode: &icmpCode1, - SrcTag: "tag2", - SrcNet: &cidrv61, - SrcSelector: "has(label2)", -} - -var V3InRule2 = apiv3.Rule{ - Action: apiv3.Deny, - IPVersion: &ipv6, - Protocol: &numProtocol1, - ICMP: &v3icmp1, - Source: apiv3.EntityRule{ - Nets: []string{cidrv61Str}, - Selector: "(has(label2)) && tag2 == ''", - }, -} - -var V1EgressRule1 = apiv1.Rule{ - Action: "pass", - IPVersion: &ipv4, - Protocol: &numProtocol1, - ICMP: &icmp1, - Source: apiv1.EntityRule{ - Tag: "tag3", - Net: &cidr2, - Selector: "all()", - }, -} - -var V1ModelEgressRule1 = model.Rule{ - Action: "next-tier", - IPVersion: &ipv4, - Protocol: &numProtocol1, - ICMPType: &icmpType1, - ICMPCode: &icmpCode1, - SrcTag: "tag3", - SrcNet: &cidr2Net, - SrcSelector: "all()", -} - -var V3EgressRule1 = apiv3.Rule{ - Action: apiv3.Pass, - IPVersion: &ipv4, - Protocol: &numProtocol1, - ICMP: &v3icmp1, - Source: apiv3.EntityRule{ - Nets: []string{cidr2StrictMaskStr}, - Selector: "(all()) && tag3 == ''", - }, -} - -var V1EgressRule2 = apiv1.Rule{ - Action: "allow", - IPVersion: &ipv6, - Protocol: &v1strProtocol2, - ICMP: &icmp1, - Source: apiv1.EntityRule{ - Tag: "tag4", - Net: &cidrv62, - Selector: "label2 == '1234'", - }, -} - -var V1ModelEgressRule2 = model.Rule{ - Action: "allow", - IPVersion: &ipv6, - Protocol: &v1strProtocol2, - ICMPType: &icmpType1, - ICMPCode: &icmpCode1, - SrcTag: "tag4", - SrcNet: &cidrv62, - SrcSelector: "label2 == '1234'", -} - -var V3EgressRule2 = apiv3.Rule{ - Action: apiv3.Allow, - IPVersion: &ipv6, - Protocol: &v3strProtocol2, - ICMP: &v3icmp1, - Source: apiv3.EntityRule{ - Nets: []string{cidrv62Str}, - Selector: "(label2 == '1234') && tag4 == ''", - }, -} diff --git a/libcalico-go/lib/upgrade/converters/selectors_test.go b/libcalico-go/lib/upgrade/converters/selectors_test.go deleted file mode 100644 index aff2544a69f..00000000000 --- a/libcalico-go/lib/upgrade/converters/selectors_test.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 converters - -import ( - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" -) - -var selectorTable = []TableEntry{ - Entry("foo == 'bar'", "foo == 'bar'", "foo == 'bar'"), - Entry("calico/k8s_ns == 'default'", "calico/k8s_ns == 'default'", "projectcalico.org/namespace == 'default'"), - Entry("calico/k8s_ns in {'default'}", "calico/k8s_ns in {'default'}", "projectcalico.org/namespace in {'default'}"), - Entry("has(calico/k8s_ns)", "has(calico/k8s_ns)", "has(projectcalico.org/namespace)"), - Entry("has(calico/k8s_ns) || foo == 'bar'", "has(calico/k8s_ns) || foo == 'bar'", "has(projectcalico.org/namespace) || foo == 'bar'"), -} - -var _ = DescribeTable("v1->v3 selector conversion tests", - func(v1, v3 string) { - Expect(convertSelector(v1)).To(Equal(v3), v1) - }, - selectorTable..., -) diff --git a/libcalico-go/lib/upgrade/converters/tier.go b/libcalico-go/lib/upgrade/converters/tier.go deleted file mode 100644 index 144c4255199..00000000000 --- a/libcalico-go/lib/upgrade/converters/tier.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) 2024 Tigera, Inc. All rights reserved. - -// 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 converters - -import ( - "fmt" - - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - log "github.com/sirupsen/logrus" - - apiv1 "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" -) - -// Tier implements the Converter interface. -type Tier struct{} - -// APIV1ToBackendV1 converts v1 Tier API to v1 Tier KVPair. -func (_ Tier) APIV1ToBackendV1(a unversioned.Resource) (*model.KVPair, error) { - ap := a.(*apiv1.Tier) - - d := model.KVPair{ - Key: model.TierKey{ - Name: ap.Metadata.Name, - }, - Value: &model.Tier{ - Order: ap.Spec.Order, - }, - } - - log.WithFields(log.Fields{ - "APIv1": ap, - "KVPair": d, - }).Debugf("Converted Tier: '%s' V1 API to V1 backend", ap.Metadata.Name) - - return &d, nil -} - -// BackendV1ToAPIV3 converts v1 Tier KVPair to v3 API. -func (_ Tier) BackendV1ToAPIV3(kvp *model.KVPair) (Resource, error) { - bp, ok := kvp.Value.(*model.Tier) - if !ok { - return nil, fmt.Errorf("value is not a valid Tier resource") - } - bk, ok := kvp.Key.(model.TierKey) - if !ok { - return nil, fmt.Errorf("value is not a valid Tier resource key") - } - - ap := apiv3.NewTier() - ap.Name = convertName(bk.Name) - ap.Spec.Order = bp.Order - - log.WithFields(log.Fields{ - "KVPairV1": bp, - "APIv3": ap, - }).Debugf("Converted Tier: '%s' V1 backend to V3 API", ap.Name) - - return ap, nil -} diff --git a/libcalico-go/lib/upgrade/converters/tier_test.go b/libcalico-go/lib/upgrade/converters/tier_test.go deleted file mode 100644 index b078b9727d1..00000000000 --- a/libcalico-go/lib/upgrade/converters/tier_test.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) 2024 Tigera, Inc. All rights reserved. - -// 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 converters - -import ( - "testing" - - . "github.com/onsi/gomega" - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - apiv1 "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" -) - -var order17 = 17.0 -var tierTable = []struct { - description string - v1API unversioned.Resource - v1KVP *model.KVPair - v3API apiv3.Tier -}{ - { - description: "fully populated Tier", - v1API: &apiv1.Tier{ - Metadata: apiv1.TierMetadata{ - Name: "nameyMcTierName", - }, - Spec: apiv1.TierSpec{ - Order: &order17, - }, - }, - v1KVP: &model.KVPair{ - Key: model.TierKey{ - Name: "nameyMcTierName", - }, - Value: &model.Tier{ - Order: &order17, - }, - }, - v3API: apiv3.Tier{ - ObjectMeta: v1.ObjectMeta{ - Name: "nameymctiername-b645ff6a", - }, - Spec: apiv3.TierSpec{ - Order: &order17, - }, - }, - }, -} - -func TestCanConvertV1ToV3Tier(t *testing.T) { - - for _, entry := range tierTable { - t.Run(entry.description, func(t *testing.T) { - RegisterTestingT(t) - - p := Tier{} - - // Test and assert v1 API to v1 backend logic. - v1KVPResult, err := p.APIV1ToBackendV1(entry.v1API) - Expect(err).NotTo(HaveOccurred(), entry.description) - Expect(v1KVPResult.Key.(model.TierKey).Name).To(Equal(entry.v1KVP.Key.(model.TierKey).Name)) - Expect(v1KVPResult.Value.(*model.Tier)).To(Equal(entry.v1KVP.Value)) - - // Test and assert v1 backend to v3 API logic. - v3APIResult, err := p.BackendV1ToAPIV3(entry.v1KVP) - Expect(err).NotTo(HaveOccurred(), entry.description) - Expect(v3APIResult.(*apiv3.Tier).Name).To(Equal(entry.v3API.Name), entry.description) - Expect(v3APIResult.(*apiv3.Tier).Spec).To(Equal(entry.v3API.Spec), entry.description) - }) - } -} diff --git a/libcalico-go/lib/upgrade/converters/workloadendpoint.go b/libcalico-go/lib/upgrade/converters/workloadendpoint.go deleted file mode 100644 index 23f8a8ed8ab..00000000000 --- a/libcalico-go/lib/upgrade/converters/workloadendpoint.go +++ /dev/null @@ -1,282 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 converters - -import ( - "fmt" - "strings" - - log "github.com/sirupsen/logrus" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/projectcalico/calico/lib/std/uniquelabels" - apiv1 "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/names" - "github.com/projectcalico/calico/libcalico-go/lib/net" -) - -type WorkloadEndpoint struct{} - -// APIV1ToBackendV1 converts v1 WorkloadEndpoint API to v1 WorkloadEndpoint KVPair. -func (_ WorkloadEndpoint) APIV1ToBackendV1(rIn unversioned.Resource) (*model.KVPair, error) { - ah := rIn.(*apiv1.WorkloadEndpoint) - k, err := convertMetadataToKey(ah.Metadata) - if err != nil { - return nil, err - } - - // IP networks are stored in the datastore in separate IPv4 and IPv6 - // fields. We normalise the network to ensure the IP is correctly - // masked. - ipv4Nets := []net.IPNet{} - ipv6Nets := []net.IPNet{} - for _, n := range ah.Spec.IPNetworks { - n = *(n.Network()) - if n.Version() == 4 { - ipv4Nets = append(ipv4Nets, n) - } else { - ipv6Nets = append(ipv6Nets, n) - } - } - - ipv4NAT := []model.IPNAT{} - ipv6NAT := []model.IPNAT{} - for _, n := range ah.Spec.IPNATs { - nat := model.IPNAT{IntIP: n.InternalIP, ExtIP: n.ExternalIP} - if n.InternalIP.Version() == 4 { - ipv4NAT = append(ipv4NAT, nat) - } else { - ipv6NAT = append(ipv6NAT, nat) - } - } - - var ports []model.EndpointPort - for _, port := range ah.Spec.Ports { - ports = append(ports, model.EndpointPort{ - Name: port.Name, - Protocol: port.Protocol, - Port: port.Port, - }) - } - - var allowedSources []net.IPNet - allowedSources = append(allowedSources, ah.Spec.AllowSpoofedSourcePrefixes...) - - d := model.KVPair{ - Key: k, - Value: &model.WorkloadEndpoint{ - Labels: uniquelabels.Make(ah.Metadata.Labels), - ActiveInstanceID: ah.Metadata.ActiveInstanceID, - State: "active", - Name: ah.Spec.InterfaceName, - Mac: ah.Spec.MAC, - ProfileIDs: ah.Spec.Profiles, - IPv4Nets: ipv4Nets, - IPv6Nets: ipv6Nets, - IPv4NAT: ipv4NAT, - IPv6NAT: ipv6NAT, - IPv4Gateway: ah.Spec.IPv4Gateway, - IPv6Gateway: ah.Spec.IPv6Gateway, - Ports: ports, - AllowSpoofedSourcePrefixes: allowedSources, - }, - Revision: ah.Metadata.Revision, - } - - log.Debugf("Converted: %+v\n To: %+v", ah, d) - - return &d, nil -} - -// BackendV1ToAPIV3 converts v1 WorkloadEndpoint KVPair to v3 API. -func (_ WorkloadEndpoint) BackendV1ToAPIV3(kvp *model.KVPair) (Resource, error) { - wepKey, ok := kvp.Key.(model.WorkloadEndpointKey) - if !ok { - return nil, fmt.Errorf("value is not a valid WorkloadEndpoint resource key") - } - wepValue, ok := kvp.Value.(*model.WorkloadEndpoint) - if !ok { - return nil, fmt.Errorf("value is not a valid WorkloadEndpoint resource Value") - } - - labels := convertLabels(wepValue.Labels.RecomputeOriginalMap()) - namespace := "default" - - var err error - var pod string - var container string - var workload string - - // Populate our values based on the orchestrator. - switch wepKey.OrchestratorID { - case "k8s": - if namespace, pod, err = getPodNamespaceName(wepKey.WorkloadID); err != nil { - return nil, err - } - container = wepValue.ActiveInstanceID - case "cni": - container = wepKey.WorkloadID - case "libnetwork": - workload = "libnetwork" - default: - workload = convertName(wepKey.WorkloadID) - } - - ipNets := convertIPNetworks(wepValue.IPv4Nets) - ipNets = append(ipNets, convertIPNetworks(wepValue.IPv6Nets)...) - - ipNats := convertIPNATs(wepValue.IPv4NAT) - ipNats = append(ipNats, convertIPNATs(wepValue.IPv6NAT)...) - - allowedSources := convertIPNetworks(wepValue.AllowSpoofedSourcePrefixes) - - wep := libapiv3.NewWorkloadEndpoint() - - wep.ObjectMeta = v1.ObjectMeta{ - Namespace: namespace, - Labels: labels, - } - wep.Spec = libapiv3.WorkloadEndpointSpec{ - Orchestrator: convertName(wepKey.OrchestratorID), - Workload: workload, - Node: ConvertNodeName(wepKey.Hostname), - Pod: pod, - ContainerID: container, - Endpoint: convertName(wepKey.EndpointID), - IPNetworks: ipNets, - IPNATs: ipNats, - Profiles: convertProfiles(wepValue.ProfileIDs), - InterfaceName: wepValue.Name, - Ports: convertPorts(wepValue.Ports), - AllowSpoofedSourcePrefixes: allowedSources, - } - - if wepValue.IPv4Gateway != nil { - wep.Spec.IPv4Gateway = wepValue.IPv4Gateway.String() - } - if wepValue.IPv6Gateway != nil { - wep.Spec.IPv6Gateway = wepValue.IPv6Gateway.String() - } - if wepValue.Mac != nil { - wep.Spec.MAC = wepValue.Mac.String() - } - - // Figure out the new name based on WEP fields. - wepids := names.WorkloadEndpointIdentifiers{ - Node: wep.Spec.Node, - Orchestrator: wep.Spec.Orchestrator, - Endpoint: wep.Spec.Endpoint, - Workload: wep.Spec.Workload, - Pod: wep.Spec.Pod, - ContainerID: wep.Spec.ContainerID, - } - - name, err := wepids.CalculateWorkloadEndpointName(false) - if err != nil { - return nil, err - } - wep.ObjectMeta.Name = name - - log.Debugf("Converted: %+v\n To: %+v", kvp, wep) - - return wep, nil -} - -func convertMetadataToKey(m unversioned.ResourceMetadata) (model.Key, error) { - hm := m.(apiv1.WorkloadEndpointMetadata) - k := model.WorkloadEndpointKey{ - Hostname: hm.Node, - OrchestratorID: hm.Orchestrator, - WorkloadID: hm.Workload, - EndpointID: hm.Name, - } - return k, nil -} - -// convertLabels creates a new map of labels, and updates the v1 namespace label to the v3 style. -func convertLabels(v1Labels map[string]string) map[string]string { - labels := map[string]string{} - for k, v := range v1Labels { - labels[k] = v - } - - if val, ok := labels["calico/k8s_ns"]; ok { - labels["projectcalico.org/namespace"] = val - delete(labels, "calico/k8s_ns") - } - - return labels -} - -// convertIPNetworks updates the old []net.IPNet to []string. -func convertIPNetworks(ipNetworks []net.IPNet) []string { - var ipNets []string - for _, ipNet := range ipNetworks { - ipNets = append(ipNets, ipNet.String()) - } - - return ipNets -} - -// convertIPNATs updates the type of IPNAT struct used. -func convertIPNATs(v1IPNATs []model.IPNAT) []libapiv3.IPNAT { - var ipNATs []libapiv3.IPNAT - for _, ipNAT := range v1IPNATs { - ipNATs = append(ipNATs, libapiv3.IPNAT{ - InternalIP: ipNAT.IntIP.String(), - ExternalIP: ipNAT.ExtIP.String(), - }) - } - - return ipNATs -} - -// convertProfiles updates the Kubernetes namespace portion from "k8s_ns" to "kns" for each profile. -func convertProfiles(v1Profiles []string) []string { - var v3Profiles []string - for _, p := range v1Profiles { - v3Profiles = append(v3Profiles, convertProfileName(p)) - } - - return v3Profiles -} - -// getPodNamespaceName separates the workload string which is in the format "namespace.podName" into -// both parts and returns both the namespace and pod name. -func getPodNamespaceName(workload string) (string, string, error) { - parts := strings.SplitN(workload, ".", 2) - if len(parts) != 2 { - return "", "", fmt.Errorf("malformed k8s workload ID '%s': workload was not added "+ - "through the Calico CNI plugin and cannot be converted", workload) - } - return parts[0], parts[1], nil -} - -// convertPorts updates to the new libapiv3.WorkloadEndpointPort struct. -func convertPorts(v1Ports []model.EndpointPort) []libapiv3.WorkloadEndpointPort { - var v3Ports []libapiv3.WorkloadEndpointPort - for _, p := range v1Ports { - v3Ports = append(v3Ports, libapiv3.WorkloadEndpointPort{ - Name: p.Name, - Protocol: p.Protocol, - Port: p.Port, - }) - } - - return v3Ports -} diff --git a/libcalico-go/lib/upgrade/converters/workloadendpoint_test.go b/libcalico-go/lib/upgrade/converters/workloadendpoint_test.go deleted file mode 100644 index 6bda9734733..00000000000 --- a/libcalico-go/lib/upgrade/converters/workloadendpoint_test.go +++ /dev/null @@ -1,488 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 converters_test - -import ( - cnet "net" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" - "github.com/projectcalico/api/pkg/lib/numorstring" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/projectcalico/calico/lib/std/uniquelabels" - apiv1 "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/apis/v1/unversioned" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/net" - "github.com/projectcalico/calico/libcalico-go/lib/upgrade/converters" -) - -var wepTable = []TableEntry{ - Entry("fully populated WEP", - &apiv1.WorkloadEndpoint{ - Metadata: apiv1.WorkloadEndpointMetadata{ - Name: "eth0", - Workload: "default.frontend-5gs43", - Orchestrator: "k8s", - Node: "TestNode", - ActiveInstanceID: "1337495556942031415926535", - Labels: makeLabelsV1(), - }, - Spec: apiv1.WorkloadEndpointSpec{ - IPNetworks: []net.IPNet{net.MustParseNetwork("10.0.0.1/32"), net.MustParseNetwork("2001::/128")}, - IPNATs: makeIPNATv1(), - IPv4Gateway: net.ParseIP("10.0.0.254"), - IPv6Gateway: net.ParseIP("2001::"), - Profiles: makeProfilesV1(), - InterfaceName: "cali1234", - MAC: makeMac(), - Ports: makeEndpointPortsV1(), - AllowSpoofedSourcePrefixes: []net.IPNet{net.MustParseNetwork("8.8.8.8/32")}, - }, - }, - &model.KVPair{ - Key: model.WorkloadEndpointKey{ - Hostname: "TestNode", - OrchestratorID: "k8s", - WorkloadID: "default.frontend-5gs43", - EndpointID: "eth0", - }, - Value: &model.WorkloadEndpoint{ - Labels: uniquelabels.Make(makeLabelsV1()), - ActiveInstanceID: "1337495556942031415926535", - State: "active", - Name: "cali1234", - Mac: makeMac(), - ProfileIDs: makeProfilesV1(), - IPv4Nets: []net.IPNet{net.MustParseNetwork("10.0.0.1/32")}, - IPv6Nets: []net.IPNet{net.MustParseNetwork("2001::/128")}, - IPv4NAT: makeIPv4NATKvp(), - IPv6NAT: makeIPv6NATKvp(), - IPv4Gateway: net.ParseIP("10.0.0.254"), - IPv6Gateway: net.ParseIP("2001::"), - Ports: makeEndpointPortsKvp(), - AllowSpoofedSourcePrefixes: []net.IPNet{net.MustParseNetwork("8.8.8.8/32")}, - }, - }, - libapiv3.WorkloadEndpoint{ - ObjectMeta: v1.ObjectMeta{ - Name: "testnode-k8s-frontend--5gs43-eth0", - Labels: makeLabelsV3(), - }, - Spec: libapiv3.WorkloadEndpointSpec{ - Orchestrator: "k8s", - Node: "testnode", - Pod: "frontend-5gs43", - Endpoint: "eth0", - ContainerID: "1337495556942031415926535", - IPNetworks: []string{"10.0.0.1/32", "2001::/128"}, - IPNATs: makeIPNATv3(), - IPv4Gateway: "10.0.0.254", - IPv6Gateway: "2001::", - Profiles: makeProfilesV3(), - InterfaceName: "cali1234", - MAC: "02:42:7d:c6:f0:80", - Ports: makeEndpointPortsV3(), - AllowSpoofedSourcePrefixes: []string{"8.8.8.8/32"}, - }, - }, - ), - Entry("IPv4 only WEP", - &apiv1.WorkloadEndpoint{ - Metadata: apiv1.WorkloadEndpointMetadata{ - Name: "eth0", - Workload: "default.frontend-5gs43", - Orchestrator: "k8s", - Node: "TestNode", - ActiveInstanceID: "1337495556942031415926535", - Labels: makeLabelsV1(), - }, - Spec: apiv1.WorkloadEndpointSpec{ - IPNetworks: []net.IPNet{net.MustParseNetwork("10.0.0.1/32")}, - IPNATs: []apiv1.IPNAT{ - { - InternalIP: net.MustParseIP("10.0.0.1"), - ExternalIP: net.MustParseIP("172.0.0.1"), - }, - }, - IPv4Gateway: net.ParseIP("10.0.0.254"), - Profiles: makeProfilesV1(), - InterfaceName: "cali1234", - MAC: makeMac(), - Ports: makeEndpointPortsV1(), - }, - }, - &model.KVPair{ - Key: model.WorkloadEndpointKey{ - Hostname: "TestNode", - OrchestratorID: "k8s", - WorkloadID: "default.frontend-5gs43", - EndpointID: "eth0", - }, - Value: &model.WorkloadEndpoint{ - Labels: uniquelabels.Make(makeLabelsV1()), - ActiveInstanceID: "1337495556942031415926535", - State: "active", - Name: "cali1234", - Mac: makeMac(), - ProfileIDs: makeProfilesV1(), - IPv4Nets: []net.IPNet{net.MustParseNetwork("10.0.0.1/32")}, - IPv6Nets: []net.IPNet{}, - IPv4NAT: makeIPv4NATKvp(), - IPv6NAT: []model.IPNAT{}, - IPv4Gateway: net.ParseIP("10.0.0.254"), - Ports: makeEndpointPortsKvp(), - }, - }, - libapiv3.WorkloadEndpoint{ - ObjectMeta: v1.ObjectMeta{ - Name: "testnode-k8s-frontend--5gs43-eth0", - Labels: makeLabelsV3(), - }, - Spec: libapiv3.WorkloadEndpointSpec{ - Orchestrator: "k8s", - Node: "testnode", - Pod: "frontend-5gs43", - ContainerID: "1337495556942031415926535", - Endpoint: "eth0", - IPNetworks: []string{"10.0.0.1/32"}, - IPNATs: []libapiv3.IPNAT{{ - InternalIP: "10.0.0.1", - ExternalIP: "172.0.0.1", - }}, - IPv4Gateway: "10.0.0.254", - Profiles: makeProfilesV3(), - InterfaceName: "cali1234", - MAC: "02:42:7d:c6:f0:80", - Ports: makeEndpointPortsV3(), - }, - }, - ), - Entry("IPv6 only WEP", - &apiv1.WorkloadEndpoint{ - Metadata: apiv1.WorkloadEndpointMetadata{ - Name: "eth0", - Workload: "default.frontend-5gs43", - Orchestrator: "k8s", - Node: "TestNode", - ActiveInstanceID: "133749555694203141592653c", - Labels: makeLabelsV1(), - }, - Spec: apiv1.WorkloadEndpointSpec{ - IPNetworks: []net.IPNet{net.MustParseNetwork("2001::/128")}, - IPNATs: []apiv1.IPNAT{ - { - InternalIP: net.MustParseIP("2001::"), - ExternalIP: net.MustParseIP("2002::"), - }, - }, - IPv6Gateway: net.ParseIP("2001::"), - Profiles: makeProfilesV1(), - InterfaceName: "cali1234", - MAC: makeMac(), - Ports: makeEndpointPortsV1(), - }, - }, - &model.KVPair{ - Key: model.WorkloadEndpointKey{ - Hostname: "TestNode", - OrchestratorID: "k8s", - WorkloadID: "default.frontend-5gs43", - EndpointID: "eth0", - }, - Value: &model.WorkloadEndpoint{ - Labels: uniquelabels.Make(makeLabelsV1()), - ActiveInstanceID: "133749555694203141592653c", - State: "active", - Name: "cali1234", - Mac: makeMac(), - ProfileIDs: makeProfilesV1(), - IPv4Nets: []net.IPNet{}, - IPv6Nets: []net.IPNet{net.MustParseNetwork("2001::/128")}, - IPv4NAT: []model.IPNAT{}, - IPv6NAT: makeIPv6NATKvp(), - IPv6Gateway: net.ParseIP("2001::"), - Ports: makeEndpointPortsKvp(), - }, - }, - libapiv3.WorkloadEndpoint{ - ObjectMeta: v1.ObjectMeta{ - Name: "testnode-k8s-frontend--5gs43-eth0", - Labels: makeLabelsV3(), - }, - Spec: libapiv3.WorkloadEndpointSpec{ - Orchestrator: "k8s", - Node: "testnode", - Pod: "frontend-5gs43", - ContainerID: "133749555694203141592653c", - Endpoint: "eth0", - IPNetworks: []string{"2001::/128"}, - IPNATs: []libapiv3.IPNAT{{ - InternalIP: "2001::", - ExternalIP: "2002::", - }}, - IPv6Gateway: "2001::", - Profiles: makeProfilesV3(), - InterfaceName: "cali1234", - MAC: "02:42:7d:c6:f0:80", - Ports: makeEndpointPortsV3(), - }, - }, - ), - Entry("WEP missing labels", - &apiv1.WorkloadEndpoint{ - Metadata: apiv1.WorkloadEndpointMetadata{ - Name: "eth0", - Workload: "default.frontend-5gs43", - Orchestrator: "k8s", - Node: "TestNode", - ActiveInstanceID: "133749555694203141592653a", - Labels: map[string]string{}, - }, - Spec: apiv1.WorkloadEndpointSpec{ - IPNetworks: []net.IPNet{net.MustParseNetwork("10.0.0.1/32"), net.MustParseNetwork("2001::/128")}, - IPNATs: makeIPNATv1(), - IPv4Gateway: net.ParseIP("10.0.0.254"), - IPv6Gateway: net.ParseIP("2001::"), - Profiles: makeProfilesV1(), - InterfaceName: "cali1234", - MAC: makeMac(), - Ports: makeEndpointPortsV1(), - }, - }, - &model.KVPair{ - Key: model.WorkloadEndpointKey{ - Hostname: "TestNode", - OrchestratorID: "k8s", - WorkloadID: "default.frontend-5gs43", - EndpointID: "eth0", - }, - Value: &model.WorkloadEndpoint{ - Labels: uniquelabels.Empty, - ActiveInstanceID: "133749555694203141592653a", - State: "active", - Name: "cali1234", - Mac: makeMac(), - ProfileIDs: makeProfilesV1(), - IPv4Nets: []net.IPNet{net.MustParseNetwork("10.0.0.1/32")}, - IPv6Nets: []net.IPNet{net.MustParseNetwork("2001::/128")}, - IPv4NAT: makeIPv4NATKvp(), - IPv6NAT: makeIPv6NATKvp(), - IPv4Gateway: net.ParseIP("10.0.0.254"), - IPv6Gateway: net.ParseIP("2001::"), - Ports: makeEndpointPortsKvp(), - }, - }, - libapiv3.WorkloadEndpoint{ - ObjectMeta: v1.ObjectMeta{ - Name: "testnode-k8s-frontend--5gs43-eth0", - Labels: map[string]string{}, - }, - Spec: libapiv3.WorkloadEndpointSpec{ - Orchestrator: "k8s", - Node: "testnode", - Pod: "frontend-5gs43", - ContainerID: "133749555694203141592653a", - Endpoint: "eth0", - IPNetworks: []string{"10.0.0.1/32", "2001::/128"}, - IPNATs: makeIPNATv3(), - IPv4Gateway: "10.0.0.254", - IPv6Gateway: "2001::", - Profiles: makeProfilesV3(), - InterfaceName: "cali1234", - MAC: "02:42:7d:c6:f0:80", - Ports: makeEndpointPortsV3(), - }, - }, - ), -} - -var _ = DescribeTable("v1->v3 workload endpoint conversion tests", - func(v1API unversioned.Resource, v1KVP *model.KVPair, v3API libapiv3.WorkloadEndpoint) { - w := converters.WorkloadEndpoint{} - - // Test and assert v1 API to v1 backend logic. - v1KVPResult, err := w.APIV1ToBackendV1(v1API) - Expect(err).NotTo(HaveOccurred()) - - // Metadata to Key. - Expect(v1KVPResult.Key.(model.WorkloadEndpointKey).Hostname).To(Equal(v1KVP.Key.(model.WorkloadEndpointKey).Hostname)) - Expect(v1KVPResult.Key.(model.WorkloadEndpointKey).OrchestratorID).To(Equal(v1KVP.Key.(model.WorkloadEndpointKey).OrchestratorID)) - Expect(v1KVPResult.Key.(model.WorkloadEndpointKey).WorkloadID).To(Equal(v1KVP.Key.(model.WorkloadEndpointKey).WorkloadID)) - Expect(v1KVPResult.Key.(model.WorkloadEndpointKey).EndpointID).To(Equal(v1KVP.Key.(model.WorkloadEndpointKey).EndpointID)) - // Spec to Value. - Expect(*v1KVPResult.Value.(*model.WorkloadEndpoint)).To(Equal(*v1KVP.Value.(*model.WorkloadEndpoint))) - - // Test and assert v1 backend to v3 API logic. - v3APIResult, err := w.BackendV1ToAPIV3(v1KVP) - Expect(err).NotTo(HaveOccurred()) - Expect(v3APIResult.(*libapiv3.WorkloadEndpoint).ObjectMeta.Name).To(Equal(v3API.ObjectMeta.Name)) - Expect(v3APIResult.(*libapiv3.WorkloadEndpoint).ObjectMeta.Labels).To(Equal(v3API.ObjectMeta.Labels)) - Expect(v3APIResult.(*libapiv3.WorkloadEndpoint).Spec).To(Equal(v3API.Spec)) - }, - wepTable..., -) - -var _ = Describe("v1->v3 workload endpoint conversion tests", func() { - It("Test invalid k8s workloadID (no dot in name) fails to convert", func() { - w := converters.WorkloadEndpoint{} - wepBackendV1 := &model.KVPair{ - Key: model.WorkloadEndpointKey{ - Hostname: "TestNode", - OrchestratorID: "k8s", - WorkloadID: "default/frontend-5gs43", - EndpointID: "eth0", - }, - Value: &model.WorkloadEndpoint{ - Labels: uniquelabels.Make(makeLabelsV1()), - ActiveInstanceID: "1337495556942031415926535", - State: "active", - Name: "cali1234", - Mac: makeMac(), - ProfileIDs: makeProfilesV1(), - IPv4Nets: []net.IPNet{net.MustParseNetwork("10.0.0.1/32")}, - IPv6Nets: []net.IPNet{}, - IPv4NAT: makeIPv4NATKvp(), - IPv6NAT: []model.IPNAT{}, - IPv4Gateway: net.ParseIP("10.0.0.254"), - Ports: makeEndpointPortsKvp(), - }, - } - _, err := w.BackendV1ToAPIV3(wepBackendV1) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("malformed k8s workload ID 'default/frontend-5gs43': workload was not added " + - "through the Calico CNI plugin and cannot be converted")) - }) -}) - -func makeLabelsV1() map[string]string { - return map[string]string{ - "calico/k8s_ns": "default", - "test": "someValue", - } -} - -// makeLabelsV3 creates some dummy labels to use in tests. -func makeLabelsV3() map[string]string { - return map[string]string{ - "projectcalico.org/namespace": "default", - "test": "someValue", - } -} - -func makeIPNATv1() []apiv1.IPNAT { - return []apiv1.IPNAT{ - { - InternalIP: net.MustParseIP("10.0.0.1"), - ExternalIP: net.MustParseIP("172.0.0.1"), - }, - { - InternalIP: net.MustParseIP("2001::"), - ExternalIP: net.MustParseIP("2002::"), - }, - } -} - -func makeIPv4NATKvp() []model.IPNAT { - var ipv4NAT []model.IPNAT - for _, ipnat := range makeIPNATv1() { - nat := model.IPNAT{IntIP: ipnat.InternalIP, ExtIP: ipnat.ExternalIP} - if ipnat.InternalIP.Version() == 4 { - ipv4NAT = append(ipv4NAT, nat) - } - } - return ipv4NAT -} - -func makeIPv6NATKvp() []model.IPNAT { - var ipv6NAT []model.IPNAT - for _, ipnat := range makeIPNATv1() { - nat := model.IPNAT{IntIP: ipnat.InternalIP, ExtIP: ipnat.ExternalIP} - if ipnat.InternalIP.Version() == 6 { - ipv6NAT = append(ipv6NAT, nat) - } - } - return ipv6NAT -} - -func makeIPNATv3() []libapiv3.IPNAT { - return []libapiv3.IPNAT{ - { - InternalIP: "10.0.0.1", - ExternalIP: "172.0.0.1", - }, - { - InternalIP: "2001::", - ExternalIP: "2002::", - }, - } -} - -func makeProfilesV1() []string { - return []string{ - "k8s_ns.profile1", - "profile2", - } -} - -func makeProfilesV3() []string { - return []string{ - "kns.profile1", - "profile2", - } -} - -func makeMac() *net.MAC { - mac, err := cnet.ParseMAC("02:42:7d:c6:f0:80") - if err != nil { - panic(err) - } - return &net.MAC{HardwareAddr: mac} -} - -func makeEndpointPortsV1() []apiv1.EndpointPort { - return []apiv1.EndpointPort{ - { - Name: "ep1", - Protocol: numorstring.ProtocolFromString("tcp"), - Port: 80, - }, - } -} - -func makeEndpointPortsKvp() []model.EndpointPort { - var ports []model.EndpointPort - for _, port := range makeEndpointPortsV1() { - ports = append(ports, model.EndpointPort{ - Name: port.Name, - Protocol: port.Protocol, - Port: port.Port, - }) - } - return ports -} - -func makeEndpointPortsV3() []libapiv3.WorkloadEndpointPort { - return []libapiv3.WorkloadEndpointPort{ - { - Name: "ep1", - Protocol: numorstring.ProtocolFromString("tcp"), - Port: 80, - }, - } -} diff --git a/libcalico-go/lib/upgrade/migrator/clients/clients.go b/libcalico-go/lib/upgrade/migrator/clients/clients.go deleted file mode 100644 index a445ad1a34d..00000000000 --- a/libcalico-go/lib/upgrade/migrator/clients/clients.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2017-2022 Tigera, Inc. All rights reserved. - -// 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 clients - -import ( - "fmt" - - "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - apiv1 "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/upgrade/migrator/clients/v1/k8s" -) - -type V1ClientInterface interface { - Apply(d *model.KVPair) (*model.KVPair, error) - Update(d *model.KVPair) (*model.KVPair, error) - Get(k model.Key) (*model.KVPair, error) - List(l model.ListInterface) ([]*model.KVPair, error) - IsKDD() bool -} - -// LoadKDDClientV1FromAPIConfigV3 loads the KDD v1 client given the -// v3 API Config (since the v1 and v3 client use the same access information). -func LoadKDDClientV1FromAPIConfigV3(apiConfigv3 *apiconfig.CalicoAPIConfig) (V1ClientInterface, error) { - if apiConfigv3.Spec.DatastoreType != apiconfig.Kubernetes { - return nil, fmt.Errorf("not valid for this datastore type: %s", apiConfigv3.Spec.DatastoreType) - } - kc := &apiv1.KubeConfig{ - Kubeconfig: apiConfigv3.Spec.Kubeconfig, - K8sAPIEndpoint: apiConfigv3.Spec.K8sAPIEndpoint, - K8sKeyFile: apiConfigv3.Spec.K8sKeyFile, - K8sCertFile: apiConfigv3.Spec.K8sCertFile, - K8sCAFile: apiConfigv3.Spec.K8sCAFile, - K8sAPIToken: apiConfigv3.Spec.K8sAPIToken, - K8sInsecureSkipTLSVerify: apiConfigv3.Spec.K8sInsecureSkipTLSVerify, - } - - // Create the backend etcdv2 client (v1 API). We wrap this in the compat module to handle - // multi-key backed resources. - return k8s.NewKubeClient(kc) -} diff --git a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom/globalfelixconfig.go b/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom/globalfelixconfig.go deleted file mode 100644 index 5c896d2d890..00000000000 --- a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom/globalfelixconfig.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 custom - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -type GlobalFelixConfig struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - Spec GlobalFelixConfigSpec `json:"spec"` -} - -type GlobalFelixConfigSpec struct { - // The reason we have Name field in Spec is because k8s metadata - // name field requires the string to be lowercase, so Name field - // in Spec is to preserve the casing. - Name string `json:"name"` - Value string `json:"value"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -type GlobalFelixConfigList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - Items []GlobalFelixConfig `json:"items"` -} diff --git a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom/register.go b/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom/register.go deleted file mode 100644 index 869d2bc06fe..00000000000 --- a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom/register.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) 2016-2017 Tigera, Inc. All rights reserved. - -// 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 custom - -import ( - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -var SchemeGroupVersion = schema.GroupVersion{Group: "crd.projectcalico.org", Version: "v1"} - -var ( - SchemeBuilder runtime.SchemeBuilder - localSchemeBuilder = &SchemeBuilder - AddToScheme = localSchemeBuilder.AddToScheme -) - -func init() { - // We only register manually written functions here. The registration of the - // generated functions takes place in the generated files. The separation - // makes the code compile even when the generated files are missing. - localSchemeBuilder.Register(addKnownTypes) -} - -// Resource takes an unqualified resource and returns a Group qualified GroupResource -func Resource(resource string) schema.GroupResource { - return SchemeGroupVersion.WithResource(resource).GroupResource() -} - -// Adds the list of known types to api.Scheme. -func addKnownTypes(scheme *runtime.Scheme) error { - return nil -} diff --git a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom/zz_generated.deepcopy.go b/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom/zz_generated.deepcopy.go deleted file mode 100644 index fa7b2792b11..00000000000 --- a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom/zz_generated.deepcopy.go +++ /dev/null @@ -1,176 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -// Copyright (c) 2016-2021 Tigera, Inc. All rights reserved. - -// 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. - -// Code generated by deepcopy-gen. DO NOT EDIT. - -package custom - -import ( - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GlobalBGPConfig) DeepCopyInto(out *GlobalBGPConfig) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalBGPConfig. -func (in *GlobalBGPConfig) DeepCopy() *GlobalBGPConfig { - if in == nil { - return nil - } - out := new(GlobalBGPConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GlobalBGPConfig) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GlobalBGPConfigList) DeepCopyInto(out *GlobalBGPConfigList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]GlobalBGPConfig, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalBGPConfigList. -func (in *GlobalBGPConfigList) DeepCopy() *GlobalBGPConfigList { - if in == nil { - return nil - } - out := new(GlobalBGPConfigList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GlobalBGPConfigList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GlobalBGPConfigSpec) DeepCopyInto(out *GlobalBGPConfigSpec) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalBGPConfigSpec. -func (in *GlobalBGPConfigSpec) DeepCopy() *GlobalBGPConfigSpec { - if in == nil { - return nil - } - out := new(GlobalBGPConfigSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GlobalFelixConfig) DeepCopyInto(out *GlobalFelixConfig) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalFelixConfig. -func (in *GlobalFelixConfig) DeepCopy() *GlobalFelixConfig { - if in == nil { - return nil - } - out := new(GlobalFelixConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GlobalFelixConfig) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GlobalFelixConfigList) DeepCopyInto(out *GlobalFelixConfigList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]GlobalFelixConfig, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalFelixConfigList. -func (in *GlobalFelixConfigList) DeepCopy() *GlobalFelixConfigList { - if in == nil { - return nil - } - out := new(GlobalFelixConfigList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GlobalFelixConfigList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GlobalFelixConfigSpec) DeepCopyInto(out *GlobalFelixConfigSpec) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalFelixConfigSpec. -func (in *GlobalFelixConfigSpec) DeepCopy() *GlobalFelixConfigSpec { - if in == nil { - return nil - } - out := new(GlobalFelixConfigSpec) - in.DeepCopyInto(out) - return out -} diff --git a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/k8s.go b/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/k8s.go deleted file mode 100644 index 6ea5b786ff2..00000000000 --- a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/k8s.go +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright (c) 2016-2018 Tigera, Inc. All rights reserved. - -// 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 k8s - -import ( - "fmt" - - log "github.com/sirupsen/logrus" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/scheme" - _ "k8s.io/client-go/plugin/pkg/client/auth" // Import all auth providers. - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - - capi "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/errors" - "github.com/projectcalico/calico/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom" - "github.com/projectcalico/calico/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources" - "github.com/projectcalico/calico/libcalico-go/lib/winutils" -) - -type KubeClient struct { - // Main Kubernetes clients. - clientSet *kubernetes.Clientset - - // Client for interacting with CustomResourceDefinition. - crdClientV1 *rest.RESTClient - - // Clients for interacting with Calico resources. - nodeBgpPeerClient resources.K8sResourceClient - globalBgpConfigClient resources.K8sResourceClient - globalFelixConfigClient resources.K8sResourceClient -} - -func NewKubeClient(kc *capi.KubeConfig) (*KubeClient, error) { - // Use the kubernetes client code to load the kubeconfig file and combine it with the overrides. - configOverrides := &clientcmd.ConfigOverrides{} - var overridesMap = []struct { - variable *string - value string - }{ - {&configOverrides.ClusterInfo.Server, kc.K8sAPIEndpoint}, - {&configOverrides.AuthInfo.ClientCertificate, kc.K8sCertFile}, - {&configOverrides.AuthInfo.ClientKey, kc.K8sKeyFile}, - {&configOverrides.ClusterInfo.CertificateAuthority, kc.K8sCAFile}, - {&configOverrides.AuthInfo.Token, kc.K8sAPIToken}, - } - - // Set an explicit path to the kubeconfig if one - // was provided. - loadingRules := clientcmd.ClientConfigLoadingRules{} - if kc.Kubeconfig != "" { - loadingRules.ExplicitPath = kc.Kubeconfig - } - - // Using the override map above, populate any non-empty values. - for _, override := range overridesMap { - if override.value != "" { - *override.variable = override.value - } - } - if kc.K8sInsecureSkipTLSVerify { - configOverrides.ClusterInfo.InsecureSkipTLSVerify = true - } - - // A kubeconfig file was provided. Use it to load a config, passing through - // any overrides. - config, err := winutils.NewNonInteractiveDeferredLoadingClientConfig( - &loadingRules, configOverrides) - if err != nil { - return nil, resources.K8sErrorToCalico(err, nil) - } - - // Create the clientset - cs, err := kubernetes.NewForConfig(config) - if err != nil { - return nil, resources.K8sErrorToCalico(err, nil) - } - log.Debugf("Created k8s clientSet: %+v", cs) - - crdClientV1, err := buildCRDClientV1(*config) - if err != nil { - return nil, fmt.Errorf("failed to build V1 CRD client: %s", err) - } - - kubeClient := &KubeClient{ - clientSet: cs, - crdClientV1: crdClientV1, - } - - // Create the Calico sub-clients. - kubeClient.nodeBgpPeerClient = resources.NewNodeBGPPeerClient(cs) - kubeClient.globalBgpConfigClient = resources.NewGlobalBGPConfigClient(cs, crdClientV1) - kubeClient.globalFelixConfigClient = resources.NewGlobalFelixConfigClient(cs, crdClientV1) - - return kubeClient, nil -} - -func (c *KubeClient) IsKDD() bool { - return true -} - -// buildCRDClientV1 builds a RESTClient configured to interact with Calico CustomResourceDefinitions -func buildCRDClientV1(cfg rest.Config) (*rest.RESTClient, error) { - // Generate config using the base config. - cfg.GroupVersion = &schema.GroupVersion{ - Group: "crd.projectcalico.org", - Version: "v1", - } - cfg.APIPath = "/apis" - cfg.ContentType = runtime.ContentTypeJSON - cfg.NegotiatedSerializer = serializer.WithoutConversionCodecFactory{CodecFactory: scheme.Codecs} - - cli, err := rest.RESTClientFor(&cfg) - if err != nil { - return nil, err - } - - // We also need to register resources. - schemeBuilder := runtime.NewSchemeBuilder( - func(scheme *runtime.Scheme) error { - scheme.AddKnownTypes( - *cfg.GroupVersion, - &custom.GlobalFelixConfig{}, - &custom.GlobalFelixConfigList{}, - &custom.GlobalBGPConfig{}, - &custom.GlobalBGPConfigList{}, - ) - return nil - }) - - schemeBuilder.AddToScheme(scheme.Scheme) - - return cli, nil -} - -// Update an existing entry in the datastore. This errors if the entry does -// not exist. (Not implemented for KDD.) -func (c *KubeClient) Update(d *model.KVPair) (*model.KVPair, error) { - log.Warn("Attempt to 'Update' using kubernetes backend is not supported.") - return nil, errors.ErrorOperationNotSupported{ - Identifier: d.Key, - Operation: "Update", - } -} - -// Set an existing entry in the datastore. This ignores whether an entry already -// exists. -func (c *KubeClient) Apply(d *model.KVPair) (*model.KVPair, error) { - log.Warn("Attempt to 'Apply' using kubernetes backend is not supported.") - return nil, errors.ErrorOperationNotSupported{ - Identifier: d.Key, - Operation: "Apply", - } -} - -// Get an entry from the datastore. This errors if the entry does not exist. -func (c *KubeClient) Get(k model.Key) (*model.KVPair, error) { - log.Debugf("Performing 'Get' for %+v", k) - switch k.(type) { - case model.GlobalConfigKey: - return c.globalFelixConfigClient.Get(k) - case model.NodeBGPPeerKey: - return c.nodeBgpPeerClient.Get(k) - case model.GlobalBGPConfigKey: - return c.globalBgpConfigClient.Get(k) - default: - return nil, errors.ErrorOperationNotSupported{ - Identifier: k, - Operation: "Get", - } - } -} - -// List entries in the datastore. This may return an empty list if there are -// no entries matching the request in the ListInterface. -func (c *KubeClient) List(l model.ListInterface) ([]*model.KVPair, error) { - log.Debugf("Performing 'List' for %+v", l) - switch l.(type) { - case model.NodeBGPPeerListOptions: - k, _, err := c.nodeBgpPeerClient.List(l) - return k, err - case model.GlobalConfigListOptions: - k, _, err := c.globalFelixConfigClient.List(l) - return k, err - case model.GlobalBGPConfigListOptions: - k, _, err := c.globalBgpConfigClient.List(l) - return k, err - default: - return nil, errors.ErrorOperationNotSupported{ - Identifier: l, - Operation: "List", - } - } -} diff --git a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/client.go b/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/client.go deleted file mode 100644 index 6517eadce45..00000000000 --- a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/client.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 resources - -import ( - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" -) - -// K8sResourceClient is the interface to the k8s datastore for CRUD operations -// on an individual resource (one for each of the *model* types supported by -// the K8s backend). -// -// Defining a separate client interface from api.Client allows the k8s-specific -// client to diverge - for example, the List operation also returns additional -// revision information. -type K8sResourceClient interface { - // Get returns the object identified by the given key as a KVPair with - // revision information. - Get(key model.Key) (*model.KVPair, error) - - // List returns a slice of KVPairs matching the input list options. - // list should be passed one of the model.ListOptions structs. - // Non-zero fields in the struct are used as filters. - List(list model.ListInterface) ([]*model.KVPair, string, error) -} diff --git a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/customnoderesource.go b/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/customnoderesource.go deleted file mode 100644 index 60a52dfe39a..00000000000 --- a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/customnoderesource.go +++ /dev/null @@ -1,336 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 resources - -import ( - "context" - "sort" - "strings" - - log "github.com/sirupsen/logrus" - apiv1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/errors" -) - -// Action strings - used for context logging. -type action string - -const ( - actApply action = "Apply" - actCreate action = "Create" - actUpdate action = "Update" -) - -// CustomK8sNodeResourceConverter defines an interface to map between the model and the -// annotation representation of a resource., -type CustomK8sNodeResourceConverter interface { - // ListInterfaceToNodeAndName converts the ListInterface to the node name - // and resource name. - ListInterfaceToNodeAndName(model.ListInterface) (string, string, error) - - // KeyToNodeAndName converts the Key to the node name and resource name. - KeyToNodeAndName(model.Key) (string, string, error) - - // NodeAndNameToKey converts the Node name and resource name to a Key. - NodeAndNameToKey(string, string) (model.Key, error) -} - -// CustomK8sNodeResourceClientConfig is the config required for initializing a new -// per-node K8sResourceClient -type CustomK8sNodeResourceClientConfig struct { - ClientSet *kubernetes.Clientset - ResourceType string - Converter CustomK8sNodeResourceConverter - Namespace string -} - -// NewCustomK8sNodeResourceClient creates a new per-node K8sResourceClient. -func NewCustomK8sNodeResourceClient(config CustomK8sNodeResourceClientConfig) K8sResourceClient { - return &nodeRetryWrapper{ - retryWrapper: &retryWrapper{ - client: &customK8sNodeResourceClient{ - CustomK8sNodeResourceClientConfig: config, - annotationKeyPrefix: config.Namespace + "/", - }, - }, - } -} - -// nodeRetryWrapper extends the retryWrapper to include the ExtractResourcesFromNode -// method. -type nodeRetryWrapper struct { - *retryWrapper -} - -// customK8sNodeResourceClient implements the K8sResourceClient interface. It -// should only be created using newCustomK8sNodeResourceClientConfig since that -// ensures it is wrapped with a retryWrapper. -type customK8sNodeResourceClient struct { - CustomK8sNodeResourceClientConfig - annotationKeyPrefix string -} - -func (c *customK8sNodeResourceClient) Get(key model.Key) (*model.KVPair, error) { - logContext := log.WithFields(log.Fields{ - "Key": key, - "Resource": c.ResourceType, - "Namespace": c.Namespace, - }) - logContext.Debug("Get per-Node resource") - - // Get the names and the latest Node settings associated with the Key. - name, node, err := c.getNameAndNodeFromKey(key) - if err != nil { - logContext.WithError(err).Info("Failed to get resource") - return nil, err - } - - // Extract the resource from the annotations. It should exist. - kvps, err := c.extractResourcesFromAnnotation(node, name) - if err != nil { - logContext.WithError(err).Error("Failed to get resource: error in data") - return nil, err - } - if len(kvps) != 1 { - logContext.Warning("Failed to get resource: resource does not exist") - return nil, errors.ErrorResourceDoesNotExist{Identifier: key} - } - - return kvps[0], nil -} - -func (c *customK8sNodeResourceClient) List(list model.ListInterface) ([]*model.KVPair, string, error) { - logContext := log.WithFields(log.Fields{ - "ListInterface": list, - "Resource": c.ResourceType, - "Namespace": c.Namespace, - }) - logContext.Debug("List per-Node Resources") - kvps := []*model.KVPair{} - - // Extract the Node and Name from the ListInterface. - nodeName, resName, err := c.Converter.ListInterfaceToNodeAndName(list) - if err != nil { - logContext.WithError(err).Info("Failed to list resources: error in list interface conversion") - return nil, "", err - } - - ctx := context.Background() - - // Get a list of the required nodes - which will either be all of them - // or a specific node. - var nodes []apiv1.Node - var rev string - if nodeName != "" { - newLogContext := logContext.WithField("NodeName", nodeName) - node, err := c.ClientSet.CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{}) - if err != nil { - err = K8sErrorToCalico(err, nodeName) - if _, ok := err.(errors.ErrorResourceDoesNotExist); !ok { - newLogContext.WithError(err).Error("Failed to list resources: unable to query node") - return nil, "", err - } - newLogContext.WithError(err).Warning("Return no results for resource list: node does not exist") - return kvps, "", nil - } - nodes = append(nodes, *node) - rev = node.GetResourceVersion() - } else { - nodeList, err := c.ClientSet.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) - if err != nil { - logContext.WithError(err).Info("Failed to list resources: unable to list Nodes") - return nil, "", K8sErrorToCalico(err, nodeName) - } - nodes = nodeList.Items - rev = nodeList.GetResourceVersion() - } - - // Loop through each of the nodes and extract the required data. - for _, node := range nodes { - nodeKVPs, err := c.extractResourcesFromAnnotation(&node, resName) - if err != nil { - logContext.WithField("NodeName", node.GetName()).WithError(err).Error("Error listing resources: error in data") - } - kvps = append(kvps, nodeKVPs...) - } - - return kvps, rev, nil -} - -// getNameAndNodeFromKey extracts the resource name from the key -// and gets the Node resource from the Kubernetes API. -// Returns: the resource name, the Node resource. -func (c *customK8sNodeResourceClient) getNameAndNodeFromKey(key model.Key) (string, *apiv1.Node, error) { - logContext := log.WithFields(log.Fields{ - "Key": key, - "Resource": c.ResourceType, - "Namespace": c.Namespace, - }) - logContext.Debug("Extract node and resource info from Key") - - // Get the node and resource name. - nodeName, resName, err := c.Converter.KeyToNodeAndName(key) - if err != nil { - logContext.WithError(err).Info("Error converting Key to Node and Resource name.") - return "", nil, err - } - - // Get the current node settings. - node, err := c.ClientSet.CoreV1().Nodes().Get(context.Background(), nodeName, metav1.GetOptions{}) - if err != nil { - logContext.WithError(err).Info("Error getting Node configuration") - return "", nil, K8sErrorToCalico(err, key) - } - - return resName, node, nil -} - -// nameToAnnotationKey converts the resource name to the annotations key. -func (c *customK8sNodeResourceClient) nameToAnnotationKey(name string) string { - return c.annotationKeyPrefix + name -} - -// annotationKeyToName converts the annotations key to a resource name, or returns -// and empty string if the annotation key does not represent a resource. -func (c *customK8sNodeResourceClient) annotationKeyToName(key string) string { - if strings.HasPrefix(key, c.annotationKeyPrefix) { - return key[len(c.annotationKeyPrefix):] - } - return "" -} - -// applyResourceToAnnotation applies the per-Node resource to the Node annotation. -func (c *customK8sNodeResourceClient) applyResourceToAnnotation(node *apiv1.Node, resName string, kvp *model.KVPair, action action) (*model.KVPair, error) { - logContext := log.WithFields(log.Fields{ - "Value": kvp.Value, - "Resource": c.ResourceType, - "Action": action, - "Namespace": c.Namespace, - }) - - logContext.Debug("Updating value in annotation") - data, err := model.SerializeValue(kvp) - if err != nil { - logContext.Error("Unable to convert value for annotation") - return nil, err - } - if node.Annotations == nil { - node.Annotations = map[string]string{} - } - node.Annotations[c.nameToAnnotationKey(resName)] = string(data) - - // Update the Node resource. - node, err = c.ClientSet.CoreV1().Nodes().Update(context.Background(), node, metav1.UpdateOptions{}) - if err != nil { - // Failed to update the Node. Just log info and perform a retry. The retryWrapper will - // log Error if this continues to fail. - logContext.WithError(err).Warning("Error updating Kubernetes Node") - err = K8sErrorToCalico(err, kvp.Key) - - // If this is an update conflict, indicate to the retryWrapper - // that we can retry the action. - if _, ok := err.(errors.ErrorResourceUpdateConflict); ok { - err = retryError{err: err} - } - return nil, err - } - - // Return the Key and Value with updated Revision information. - return &model.KVPair{ - Key: kvp.Key, - Value: kvp.Value, - Revision: node.GetObjectMeta().GetResourceVersion(), - }, nil -} - -// extractResourcesFromAnnotation queries the current Kubernetes Node resource -// and parses the per-node resource entries configured in the annotations. -// Returns the Node resource configuration and the slice of parsed resources. -func (c *customK8sNodeResourceClient) extractResourcesFromAnnotation(node *apiv1.Node, name string) ([]*model.KVPair, error) { - logContext := log.WithFields(log.Fields{ - "ResourceType": name, - "Resource": c.ResourceType, - "Namespace": c.Namespace, - }) - logContext.Debug("Extract node and resource info from Key") - - // Extract the resource entries from the annotation. We do this either by - // extracting the requested entry if it exists, or by iterating through each - // annotation and checking if it corresponds to a resource. - resStrings := make(map[string]string, 0) - resNames := []string{} - if name != "" { - ak := c.nameToAnnotationKey(name) - if v, ok := node.Annotations[ak]; ok { - resStrings[name] = v - resNames = append(resNames, name) - } - } else { - for ak, v := range node.Annotations { - if n := c.annotationKeyToName(ak); n != "" { - resStrings[n] = v - resNames = append(resNames, n) - } - } - } - - // Sort the resource names to ensure the KVPairs are ordered. - sort.Strings(resNames) - - // Process each entry in name order and add to the return set of KVPairs. - // Use the node revision as the revision of each entry. If we hit an error - // unmarshalling then return the error if we are querying an exact entry, but - // swallow the error if we are listing multiple (otherwise a single bad entry - // would prevent all resources being listed). - kvps := []*model.KVPair{} - for _, resName := range resNames { - key, err := c.Converter.NodeAndNameToKey(node.GetName(), resName) - if err != nil { - logContext.WithField("ResourceType", resName).WithError(err).Error("Error unmarshalling key") - if name != "" { - return nil, err - } - continue - } - - value, err := model.ParseValue(key, []byte(resStrings[resName])) - if err != nil { - logContext.WithField("ResourceType", resName).WithError(err).Error("Error unmarshalling value") - if name != "" { - return nil, err - } - continue - } - kvp := &model.KVPair{ - Key: key, - Value: value, - Revision: node.GetResourceVersion(), - } - kvps = append(kvps, kvp) - } - - return kvps, nil -} - -// ExtractResourcesFromNode returns the resources stored in the Node configuration. -// -// This convenience method is expected to be removed in a future libcalico-go release. -func (c *customK8sNodeResourceClient) ExtractResourcesFromNode(node *apiv1.Node) ([]*model.KVPair, error) { - return c.extractResourcesFromAnnotation(node, "") -} diff --git a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/customresource.go b/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/customresource.go deleted file mode 100644 index 896431a44b2..00000000000 --- a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/customresource.go +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 resources - -import ( - "context" - "reflect" - - log "github.com/sirupsen/logrus" - kerrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/errors" -) - -// Interface required to satisfy use as a Kubernetes Custom Resource type. -type CustomK8sResource interface { - runtime.Object - metav1.ObjectMetaAccessor -} - -// Interface required to satisfy use as a Kubernetes Custom Resource List type. -type CustomK8sList interface { - runtime.Object - metav1.ListMetaAccessor -} - -// CustomK8sResourceConverter defines an interface to map between KVPair representation -// and a custom Kubernetes resource. -type CustomK8sResourceConverter interface { - // ListInterfaceToKey converts a ListInterface to a Key if the - // ListInterface specifies a specific instance, otherwise returns nil. - ListInterfaceToKey(model.ListInterface) model.Key - - // Convert the Key to the Resource name. - KeyToName(model.Key) (string, error) - - // Convert the Resource name to the Key. - NameToKey(string) (model.Key, error) - - // Convert the Resource to a KVPair. - ToKVPair(CustomK8sResource) (*model.KVPair, error) - - // Convert a KVPair to a Resource. - FromKVPair(*model.KVPair) (CustomK8sResource, error) -} - -// customK8sResourceClient implements the K8sResourceClient interface and provides a generic -// mechanism for a 1:1 mapping between a Calico Resource and an equivalent Kubernetes -// custom resource type. -type customK8sResourceClient struct { - clientSet *kubernetes.Clientset - restClient *rest.RESTClient - name string - resource string - description string - k8sResourceType reflect.Type - k8sListType reflect.Type - converter CustomK8sResourceConverter -} - -// Get gets an existing Custom K8s Resource instance in the k8s API using the supplied Key. -func (c *customK8sResourceClient) Get(key model.Key) (*model.KVPair, error) { - logContext := log.WithFields(log.Fields{ - "Key": key, - "Resource": c.resource, - }) - logContext.Debug("Get custom Kubernetes resource") - name, err := c.converter.KeyToName(key) - if err != nil { - logContext.WithError(err).Info("Error getting resource") - return nil, err - } - - // Add the name to the log context now that we know it, and query - // Kubernetes. - logContext = logContext.WithField("Name", name) - logContext.Debug("Get custom Kubernetes resource by name") - resOut := reflect.New(c.k8sResourceType).Interface().(CustomK8sResource) - err = c.restClient.Get(). - Resource(c.resource). - Name(name). - Do(context.Background()).Into(resOut) - if err != nil { - logContext.WithError(err).Info("Error getting resource") - return nil, K8sErrorToCalico(err, key) - } - - return c.converter.ToKVPair(resOut) -} - -// List lists configured Custom K8s Resource instances in the k8s API matching the -// supplied ListInterface. -func (c *customK8sResourceClient) List(list model.ListInterface) ([]*model.KVPair, string, error) { - logContext := log.WithFields(log.Fields{ - "ListInterface": list, - "Resource": c.resource, - }) - logContext.Debug("List Custom K8s Resource") - kvps := []*model.KVPair{} - - // Attempt to convert the ListInterface to a Key. If possible, the parameters - // indicate a fully qualified resource, and we'll need to use Get instead of - // List. - if key := c.converter.ListInterfaceToKey(list); key != nil { - logContext.Debug("Performing List using Get") - if kvp, err := c.Get(key); err != nil { - // The error will already be a Calico error type. Ignore - // error that it doesn't exist - we'll return an empty - // list. - if _, ok := err.(errors.ErrorResourceDoesNotExist); !ok { - log.WithField("Resource", c.resource).WithError(err).Info("Error listing resource") - return nil, "", err - } - return kvps, "", nil - } else { - kvps = append(kvps, kvp) - return kvps, kvp.Revision, nil - } - } - - // Since we are not performing an exact Get, Kubernetes will return a - // list of resources. - reslOut := reflect.New(c.k8sListType).Interface().(CustomK8sList) - - // Perform the request. - err := c.restClient.Get(). - Resource(c.resource). - Do(context.Background()).Into(reslOut) - if err != nil { - // Don't return errors for "not found". This just - // means there are no matching Custom K8s Resources, and we should return - // an empty list. - if !kerrors.IsNotFound(err) { - log.WithError(err).Info("Error listing resources") - return nil, "", K8sErrorToCalico(err, list) - } - return kvps, reslOut.GetListMeta().GetResourceVersion(), nil - } - - // We expect the list type to have an "Items" field that we can - // iterate over. - elem := reflect.ValueOf(reslOut).Elem() - items := reflect.ValueOf(elem.FieldByName("Items").Interface()) - for idx := 0; idx < items.Len(); idx++ { - res := items.Index(idx).Addr().Interface().(CustomK8sResource) - - if kvp, err := c.converter.ToKVPair(res); err == nil { - kvps = append(kvps, kvp) - } else { - logContext.WithError(err).WithField("Item", res).Warning("unable to process resource, skipping") - } - } - return kvps, reslOut.GetListMeta().GetResourceVersion(), nil -} diff --git a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/errors.go b/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/errors.go deleted file mode 100644 index 8a852f7532c..00000000000 --- a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/errors.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2016 Tigera, Inc. All rights reserved. - -// 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 resources - -import ( - kerrors "k8s.io/apimachinery/pkg/api/errors" - - "github.com/projectcalico/calico/libcalico-go/lib/errors" -) - -// K8sErrorToCalico returns the equivalent libcalico error for the given -// kubernetes error. -func K8sErrorToCalico(ke error, id interface{}) error { - if ke == nil { - return nil - } - - if kerrors.IsAlreadyExists(ke) { - return errors.ErrorResourceAlreadyExists{ - Err: ke, - Identifier: id, - } - } - if kerrors.IsNotFound(ke) { - return errors.ErrorResourceDoesNotExist{ - Err: ke, - Identifier: id, - } - } - if kerrors.IsForbidden(ke) || kerrors.IsUnauthorized(ke) { - return errors.ErrorConnectionUnauthorized{ - Err: ke, - } - } - if kerrors.IsConflict(ke) { - return errors.ErrorResourceUpdateConflict{ - Err: ke, - Identifier: id, - } - } - return errors.ErrorDatastoreError{ - Err: ke, - Identifier: id, - } -} diff --git a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/globalbgpconfig.go b/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/globalbgpconfig.go deleted file mode 100644 index 0643008bd8e..00000000000 --- a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/globalbgpconfig.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 resources - -import ( - "fmt" - "reflect" - "strings" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom" -) - -const ( - GlobalBGPConfigResourceName = "GlobalBGPConfigs" - GlobalBGPConfigCRDName = "globalbgpconfigs.projectcalico.org" -) - -func NewGlobalBGPConfigClient(c *kubernetes.Clientset, r *rest.RESTClient) K8sResourceClient { - return &customK8sResourceClient{ - clientSet: c, - restClient: r, - name: GlobalBGPConfigCRDName, - resource: GlobalBGPConfigResourceName, - description: "Calico Global BGP Configuration", - k8sResourceType: reflect.TypeOf(custom.GlobalBGPConfig{}), - k8sListType: reflect.TypeOf(custom.GlobalBGPConfigList{}), - converter: GlobalBGPConfigConverter{}, - } -} - -// GlobalBGPConfigConverter implements the K8sResourceConverter interface. -type GlobalBGPConfigConverter struct { -} - -func (_ GlobalBGPConfigConverter) ListInterfaceToKey(l model.ListInterface) model.Key { - if name := l.(model.GlobalBGPConfigListOptions).Name; name != "" { - return model.GlobalBGPConfigKey{Name: name} - } - return nil -} - -func (_ GlobalBGPConfigConverter) KeyToName(k model.Key) (string, error) { - return strings.ToLower(k.(model.GlobalBGPConfigKey).Name), nil -} - -func (_ GlobalBGPConfigConverter) NameToKey(name string) (model.Key, error) { - return nil, fmt.Errorf("Mapping of Name to Key is not possible for global BGP config") -} - -func (c GlobalBGPConfigConverter) ToKVPair(r CustomK8sResource) (*model.KVPair, error) { - t := r.(*custom.GlobalBGPConfig) - return &model.KVPair{ - Key: model.GlobalBGPConfigKey{ - Name: t.Spec.Name, - }, - Value: t.Spec.Value, - Revision: t.ResourceVersion, - }, nil -} - -func (c GlobalBGPConfigConverter) FromKVPair(kvp *model.KVPair) (CustomK8sResource, error) { - name, err := c.KeyToName(kvp.Key) - if err != nil { - return nil, err - } - crd := custom.GlobalBGPConfig{ - TypeMeta: metav1.TypeMeta{ - Kind: "GlobalBGPConfig", - APIVersion: "crd.projectcalico.org/v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Spec: custom.GlobalBGPConfigSpec{ - Name: kvp.Key.(model.GlobalBGPConfigKey).Name, - Value: kvp.Value.(string), - }, - } - crd.ResourceVersion = kvp.Revision - return &crd, nil -} diff --git a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/globalbgpconfig_test.go b/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/globalbgpconfig_test.go deleted file mode 100644 index 148ec897f67..00000000000 --- a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/globalbgpconfig_test.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 resources_test - -import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom" - "github.com/projectcalico/calico/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources" -) - -var _ = Describe("Global BGP config conversion methods", func() { - - converter := resources.GlobalBGPConfigConverter{} - - // Define some useful test data. - listIncomplete := model.GlobalBGPConfigListOptions{} - - // Compatible set of list, key and name (used for Key to Name conversion) - list1 := model.GlobalBGPConfigListOptions{ - Name: "AbCd", - } - key1 := model.GlobalBGPConfigKey{ - Name: "AbCd", - } - name1 := "abcd" - - // Compatible set of KVPair and Kubernetes Resource. - value1 := "test" - kvp1 := &model.KVPair{ - Key: key1, - Value: value1, - Revision: "rv", - } - res1 := &custom.GlobalBGPConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: name1, - ResourceVersion: "rv", - }, - Spec: custom.GlobalBGPConfigSpec{ - Name: key1.Name, - Value: value1, - }, - } - - It("should convert an incomplete ListInterface to no Key", func() { - Expect(converter.ListInterfaceToKey(listIncomplete)).To(BeNil()) - }) - - It("should convert a qualified ListInterface to the equivalent Key", func() { - Expect(converter.ListInterfaceToKey(list1)).To(Equal(key1)) - }) - - It("should convert a Key to the equivalent resource name", func() { - n, err := converter.KeyToName(key1) - Expect(err).NotTo(HaveOccurred()) - Expect(n).To(Equal(name1)) - }) - - It("should not convert a resource name to the equivalent Key - this is not possible due to case switching", func() { - _, err := converter.NameToKey("test") - Expect(err).To(HaveOccurred()) - }) - - It("should convert between a KVPair and the equivalent Kubernetes resource", func() { - r, err := converter.FromKVPair(kvp1) - Expect(err).NotTo(HaveOccurred()) - Expect(r.GetObjectMeta().GetName()).To(Equal(res1.Name)) - Expect(r.GetObjectMeta().GetResourceVersion()).To(Equal(res1.ResourceVersion)) - Expect(r).To(BeAssignableToTypeOf(&custom.GlobalBGPConfig{})) - Expect(r.(*custom.GlobalBGPConfig).Spec).To(Equal(res1.Spec)) - }) - - It("should convert between a Kubernetes resource and the equivalent KVPair", func() { - kvp, err := converter.ToKVPair(res1) - Expect(err).NotTo(HaveOccurred()) - Expect(kvp.Key).To(Equal(kvp1.Key)) - Expect(kvp.Revision).To(Equal(kvp1.Revision)) - Expect(kvp.Value).To(BeAssignableToTypeOf(value1)) - Expect(kvp.Value).To(Equal(kvp1.Value)) - }) -}) diff --git a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/globalfelixconfig.go b/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/globalfelixconfig.go deleted file mode 100644 index d264053e6eb..00000000000 --- a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/globalfelixconfig.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 resources - -import ( - "fmt" - "reflect" - "strings" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom" -) - -const ( - GlobalFelixConfigResourceName = "GlobalFelixConfigs" - GlobalFelixConfigCRDName = "globalconfigs.crd.projectcalico.org" -) - -func NewGlobalFelixConfigClient(c *kubernetes.Clientset, r *rest.RESTClient) K8sResourceClient { - return &customK8sResourceClient{ - clientSet: c, - restClient: r, - name: GlobalFelixConfigCRDName, - resource: GlobalFelixConfigResourceName, - description: "Calico Global Felix Configuration", - k8sResourceType: reflect.TypeOf(custom.GlobalFelixConfig{}), - k8sListType: reflect.TypeOf(custom.GlobalFelixConfigList{}), - converter: GlobalFelixConfigConverter{}, - } -} - -// GlobalFelixConfigConverter implements the K8sResourceConverter interface. -type GlobalFelixConfigConverter struct { -} - -func (_ GlobalFelixConfigConverter) ListInterfaceToKey(l model.ListInterface) model.Key { - pl := l.(model.GlobalConfigListOptions) - if pl.Name != "" { - return model.GlobalConfigKey{Name: pl.Name} - } - return nil -} - -func (_ GlobalFelixConfigConverter) KeyToName(k model.Key) (string, error) { - return strings.ToLower(k.(model.GlobalConfigKey).Name), nil -} - -func (_ GlobalFelixConfigConverter) NameToKey(name string) (model.Key, error) { - return nil, fmt.Errorf("Mapping of Name to Key is not possible for global felix config") -} - -func (c GlobalFelixConfigConverter) ToKVPair(r CustomK8sResource) (*model.KVPair, error) { - t := r.(*custom.GlobalFelixConfig) - return &model.KVPair{ - Key: model.GlobalConfigKey{ - Name: t.Spec.Name, - }, - Value: t.Spec.Value, - Revision: t.ResourceVersion, - }, nil -} - -func (c GlobalFelixConfigConverter) FromKVPair(kvp *model.KVPair) (CustomK8sResource, error) { - name, err := c.KeyToName(kvp.Key) - if err != nil { - return nil, err - } - crd := custom.GlobalFelixConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Spec: custom.GlobalFelixConfigSpec{ - Name: kvp.Key.(model.GlobalConfigKey).Name, - Value: kvp.Value.(string), - }, - } - crd.ResourceVersion = kvp.Revision - return &crd, nil -} diff --git a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/globalfelixconfig_test.go b/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/globalfelixconfig_test.go deleted file mode 100644 index a1429f023e4..00000000000 --- a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/globalfelixconfig_test.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 resources_test - -import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/custom" - "github.com/projectcalico/calico/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources" -) - -var _ = Describe("Global Felix config conversion methods", func() { - - converter := resources.GlobalFelixConfigConverter{} - - // Define some useful test data. - listIncomplete := model.GlobalConfigListOptions{} - - // Compatible set of list, key and name (used for Key to Name conversion) - list1 := model.GlobalConfigListOptions{ - Name: "AbCd", - } - key1 := model.GlobalConfigKey{ - Name: "AbCd", - } - name1 := "abcd" - - // Compatible set of KVPair and Kubernetes Resource. - value1 := "test" - kvp1 := &model.KVPair{ - Key: key1, - Value: value1, - Revision: "rv", - } - res1 := &custom.GlobalFelixConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: name1, - ResourceVersion: "rv", - }, - Spec: custom.GlobalFelixConfigSpec{ - Name: key1.Name, - Value: value1, - }, - } - - It("should convert an incomplete ListInterface to no Key", func() { - Expect(converter.ListInterfaceToKey(listIncomplete)).To(BeNil()) - }) - - It("should convert a qualified ListInterface to the equivalent Key", func() { - Expect(converter.ListInterfaceToKey(list1)).To(Equal(key1)) - }) - - It("should convert a Key to the equivalent resource name", func() { - n, err := converter.KeyToName(key1) - Expect(err).NotTo(HaveOccurred()) - Expect(n).To(Equal(name1)) - }) - - It("should not convert a resource name to the equivalent Key - this is not possible due to case switching", func() { - _, err := converter.NameToKey("test") - Expect(err).To(HaveOccurred()) - }) - - It("should convert between a KVPair and the equivalent Kubernetes resource", func() { - r, err := converter.FromKVPair(kvp1) - Expect(err).NotTo(HaveOccurred()) - Expect(r.GetObjectMeta().GetName()).To(Equal(res1.Name)) - Expect(r.GetObjectMeta().GetResourceVersion()).To(Equal(res1.ResourceVersion)) - Expect(r).To(BeAssignableToTypeOf(&custom.GlobalFelixConfig{})) - Expect(r.(*custom.GlobalFelixConfig).Spec).To(Equal(res1.Spec)) - }) - - It("should convert between a Kubernetes resource and the equivalent KVPair", func() { - kvp, err := converter.ToKVPair(res1) - Expect(err).NotTo(HaveOccurred()) - Expect(kvp.Key).To(Equal(kvp1.Key)) - Expect(kvp.Revision).To(Equal(kvp1.Revision)) - Expect(kvp.Value).To(BeAssignableToTypeOf(value1)) - Expect(kvp.Value).To(Equal(kvp1.Value)) - }) -}) diff --git a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/names.go b/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/names.go deleted file mode 100644 index 704f51a3ff5..00000000000 --- a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/names.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 resources - -import ( - "fmt" - "strings" - - log "github.com/sirupsen/logrus" - - "github.com/projectcalico/calico/libcalico-go/lib/net" -) - -// This file contains various name conversion methods that can be used to convert -// between Calico key types and resource names. - -// IPToResourceName converts an IP address to a name used for a k8s resource. -func IPToResourceName(ip net.IP) string { - name := "" - - if ip.To4() != nil { - name = strings.Replace(ip.String(), ".", "-", 3) - } else { - // IPv6 address can end in a "::" which would be a string ending in "--", - // which is not allowed in k8s name field, so we expand the IPv6 address and then replace ":" with "-". - // fe08:123:445:: will look like fe08-0123-0445-0000-0000-0000-0000-0000 - ip6 := ip.To16() - bytes := []string{} - - // Go through pairs of bytes in the address and convert them to a hex string. - for i := 0; i < len(ip6); i += 2 { - bytes = append(bytes, fmt.Sprintf("%.2x%.2x", ip6[i], ip6[i+1])) - } - - // Combine them all into a name. - name = strings.Join(bytes, "-") - } - - log.WithFields(log.Fields{ - "Name": name, - "IP": ip.String(), - }).Debug("Converting IP to resource name") - - return name -} - -// ResourceNameToIP converts a name used for a k8s resource to an IP address. -func ResourceNameToIP(name string) (*net.IP, error) { - ip := net.ParseIP(resourceNameToIPString(name)) - if ip == nil { - return nil, fmt.Errorf("invalid resource name %s: does not follow Calico IP name format", name) - } - return ip, nil -} - -// resourceNameToIPString converts a name used for a k8s resource to an IP address string. -// This function does not check the validity of the result - it merely reverses the -// character conversion used to convert an IP address to a k8s compatible name. -func resourceNameToIPString(name string) string { - // The IP address is stored in the name with periods and colons replaced - // by dashes. To determine if this is IPv4 or IPv6 count the dashes. If - // either of the following are true, it's IPv6: - // - There is a "--" - // - The number of "-" is greater than 3. - var ipstr string - if strings.Contains(name, "--") || strings.Count(name, "-") > 3 { - // IPv6: replace - with : - ipstr = strings.Replace(name, "-", ":", 7) - } else { - // IPv4: replace - with . - ipstr = strings.Replace(name, "-", ".", 3) - } - - log.WithFields(log.Fields{ - "Name": name, - "IP": ipstr, - }).Debug("Converting resource name to IP String") - return ipstr -} diff --git a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/nodebgppeer.go b/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/nodebgppeer.go deleted file mode 100644 index 0b95adcec5f..00000000000 --- a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/nodebgppeer.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 resources - -import ( - "k8s.io/client-go/kubernetes" - - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" -) - -const ( - perNodeBgpPeerAnnotationNamespace = "peer.bgp.projectcalico.org" -) - -func NewNodeBGPPeerClient(c *kubernetes.Clientset) K8sResourceClient { - return NewCustomK8sNodeResourceClient(CustomK8sNodeResourceClientConfig{ - ClientSet: c, - ResourceType: "NodeBGPPeer", - Converter: NodeBGPPeerConverter{}, - Namespace: perNodeBgpPeerAnnotationNamespace, - }) -} - -// NodeBGPPeerConverter implements the CustomK8sNodeResourceConverter interface. -type NodeBGPPeerConverter struct{} - -func (_ NodeBGPPeerConverter) ListInterfaceToNodeAndName(l model.ListInterface) (string, string, error) { - pl := l.(model.NodeBGPPeerListOptions) - if pl.PeerIP.IP == nil { - return pl.Nodename, "", nil - } else { - return pl.Nodename, IPToResourceName(pl.PeerIP), nil - } -} - -func (_ NodeBGPPeerConverter) KeyToNodeAndName(k model.Key) (string, string, error) { - pk := k.(model.NodeBGPPeerKey) - return pk.Nodename, IPToResourceName(pk.PeerIP), nil -} - -func (_ NodeBGPPeerConverter) NodeAndNameToKey(node, name string) (model.Key, error) { - ip, err := ResourceNameToIP(name) - if err != nil { - return nil, err - } - - return model.NodeBGPPeerKey{ - Nodename: node, - PeerIP: *ip, - }, nil -} diff --git a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/nodebgppeer_test.go b/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/nodebgppeer_test.go deleted file mode 100644 index b82f85d3120..00000000000 --- a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/nodebgppeer_test.go +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 resources_test - -import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/net" - "github.com/projectcalico/calico/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources" -) - -var _ = Describe("Node BGP conversion methods", func() { - - converter := resources.NodeBGPPeerConverter{} - - It("should convert an empty ListInterface", func() { - node, name, err := converter.ListInterfaceToNodeAndName( - model.NodeBGPPeerListOptions{}, - ) - Expect(err).To(BeNil()) - Expect(node).To(Equal("")) - Expect(name).To(Equal("")) - }) - - It("should convert a List interface with a Node name only", func() { - node, name, err := converter.ListInterfaceToNodeAndName( - model.NodeBGPPeerListOptions{ - Nodename: "node", - }, - ) - Expect(err).To(BeNil()) - Expect(node).To(Equal("node")) - Expect(name).To(Equal("")) - }) - - It("should convert a List interface with a PeerIP only", func() { - node, name, err := converter.ListInterfaceToNodeAndName( - model.NodeBGPPeerListOptions{ - PeerIP: net.MustParseIP("1.2.3.4"), - }, - ) - Expect(err).To(BeNil()) - Expect(node).To(Equal("")) - Expect(name).To(Equal("1-2-3-4")) - }) - - It("should convert a List interface with node and PeerIP (IPv4)", func() { - node, name, err := converter.ListInterfaceToNodeAndName( - model.NodeBGPPeerListOptions{ - Nodename: "nodeX", - PeerIP: net.MustParseIP("1.2.3.40"), - }, - ) - Expect(err).To(BeNil()) - Expect(node).To(Equal("nodeX")) - Expect(name).To(Equal("1-2-3-40")) - }) - - It("should convert a List interface with node and PeerIP (IPv6)", func() { - node, name, err := converter.ListInterfaceToNodeAndName( - model.NodeBGPPeerListOptions{ - Nodename: "nodeX", - PeerIP: net.MustParseIP("1::2:3:4"), - }, - ) - Expect(err).To(BeNil()) - Expect(node).To(Equal("nodeX")) - Expect(name).To(Equal("0001-0000-0000-0000-0000-0002-0003-0004")) - }) - - It("should convert a Key with node and PeerIP (IPv4)", func() { - node, name, err := converter.KeyToNodeAndName( - model.NodeBGPPeerKey{ - Nodename: "nodeY", - PeerIP: net.MustParseIP("1.2.3.50"), - }, - ) - Expect(err).To(BeNil()) - Expect(node).To(Equal("nodeY")) - Expect(name).To(Equal("1-2-3-50")) - }) - - It("should convert a Key with node and PeerIP (IPv6)", func() { - node, name, err := converter.KeyToNodeAndName( - model.NodeBGPPeerKey{ - Nodename: "nodeY", - PeerIP: net.MustParseIP("aa:ff::12"), - }, - ) - Expect(err).To(BeNil()) - Expect(node).To(Equal("nodeY")) - Expect(name).To(Equal("00aa-00ff-0000-0000-0000-0000-0000-0012")) - }) - - It("should convert a valid node name and resource name to a Key (IPv4)", func() { - key, err := converter.NodeAndNameToKey("nodeA", "1-2-3-4") - Expect(err).To(BeNil()) - Expect(key).To(Equal(model.NodeBGPPeerKey{ - Nodename: "nodeA", - PeerIP: net.MustParseIP("1.2.3.4"), - })) - }) - - It("should convert a valid node name and resource name to a Key (IPv6)", func() { - key, err := converter.NodeAndNameToKey("nodeB", "abcd-2000--30-40") - Expect(err).To(BeNil()) - Expect(key).To(Equal(model.NodeBGPPeerKey{ - Nodename: "nodeB", - PeerIP: net.MustParseIP("abcd:2000::30:40"), - })) - }) - - It("should fail to convert a valid node name and invalid resource name to a Key", func() { - _, err := converter.NodeAndNameToKey("nodeB", "foobarbaz") - Expect(err).ToNot(BeNil()) - }) -}) diff --git a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/resources_suite_test.go b/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/resources_suite_test.go deleted file mode 100644 index 67740bc545b..00000000000 --- a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/resources_suite_test.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2016,2018 Tigera, Inc. All rights reserved. - -// 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 resources_test - -import ( - "testing" - - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" - - "github.com/projectcalico/calico/libcalico-go/lib/testutils" -) - -func TestModel(t *testing.T) { - testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../../../../../report/resources_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "calico-upgrade KDD v1 resources Suite", []Reporter{junitReporter}) -} diff --git a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/retrywrapper.go b/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/retrywrapper.go deleted file mode 100644 index 3a8af545da8..00000000000 --- a/libcalico-go/lib/upgrade/migrator/clients/v1/k8s/resources/retrywrapper.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 resources - -import ( - log "github.com/sirupsen/logrus" - - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" -) - -const ( - maxActionRetries = 5 -) - -// retryWrapper implements the K8sResourceClient interface and is used to wrap -// another K8sResourceClient to provide retry functionality when the failure -// case is of type retryError. -type retryWrapper struct { - client K8sResourceClient -} - -// retryError is an error type used to indicate to the retryWrapper to retry -// a specific action. -// -// If the action is retried the max number of times, then the retryWrapper will -// return the underlying error. -type retryError struct { - err error -} - -func (r retryError) Error() string { - return r.err.Error() -} - -func (r *retryWrapper) Get(key model.Key) (*model.KVPair, error) { - var kvp *model.KVPair - var err error - for i := 0; i < maxActionRetries; i++ { - if kvp, err = r.client.Get(key); err == nil { - // No error, exit returning the KVPair. - return kvp, nil - } else if _, ok := err.(retryError); !ok { - return nil, err - } - } - - // Excessive retries. Return the last error. - log.WithField("Key", key).Error("Failed to get object: too many retries") - return nil, err.(retryError).err -} - -func (r *retryWrapper) List(list model.ListInterface) ([]*model.KVPair, string, error) { - var rev string - var kvps []*model.KVPair - var err error - for i := 0; i < maxActionRetries; i++ { - if kvps, rev, err = r.client.List(list); err == nil { - // No error, exit returning the KVPair. - return kvps, rev, nil - } else if _, ok := err.(retryError); !ok { - return nil, "", err - } - } - - // Excessive retries. Return the last error. - log.WithField("List", list).Error("Failed to list object: too many retries") - return nil, "", err.(retryError).err -} diff --git a/libcalico-go/lib/upgrade/migrator/felixconfig.go b/libcalico-go/lib/upgrade/migrator/felixconfig.go deleted file mode 100644 index 6b325dfcb57..00000000000 --- a/libcalico-go/lib/upgrade/migrator/felixconfig.go +++ /dev/null @@ -1,385 +0,0 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - -// 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 migrator - -import ( - "fmt" - "reflect" - "strconv" - "strings" - "time" - - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - log "github.com/sirupsen/logrus" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/net" - "github.com/projectcalico/calico/libcalico-go/lib/upgrade/converters" -) - -// Query the v1 format of GlobalConfigList and convert to the v3 format of -// FelixConfiguration and ClusterInformation. -func (m *migrationHelper) queryAndConvertFelixConfigV1ToV3( - data *MigrationData, -) error { - // Query all of the global config into a slice of KVPairs. - m.statusBullet("handling FelixConfiguration (global) resource") - kvps, err := m.clientv1.List(model.GlobalConfigListOptions{}) - if err != nil { - return fmt.Errorf("error querying FelixConfiguration: %v", err) - } - - // Parse the separate KVPairs into a global FelixConfiguration resource and a - // global ClusterInformation resource. Note that if this is KDD we set the Ready - // flag to true, otherwise to false. - globalConfig := apiv3.NewFelixConfiguration() - globalConfig.Name = "default" - if err := m.parseFelixConfigV1IntoResourceV3(kvps, globalConfig, data); err != nil { - return fmt.Errorf("error converting FelixConfiguration: %v", err) - } - - m.statusBullet("handling ClusterInformation (global) resource") - clusterInfo := apiv3.NewClusterInformation() - clusterInfo.Name = "default" - if err = m.parseFelixConfigV1IntoResourceV3(kvps, clusterInfo, data); err != nil { - return fmt.Errorf("error converting ClusterInformation: %v", err) - } - // Update the ready flag in the resource based on the datastore type. For KDD the ready - // flag should be true, for etcd it should be false. - ready := m.clientv1.IsKDD() - clusterInfo.Spec.DatastoreReady = &ready - - if m.clientv1.IsKDD() { - m.statusBullet("skipping FelixConfiguration (per-node) resources - not supported") - } else { - // Query all of the per-host felix config into a slice of KVPairs. - m.statusBullet("handling FelixConfiguration (per-node) resources") - kvps, err = m.clientv1.List(model.HostConfigListOptions{}) - if err != nil { - return fmt.Errorf("error querying FelixConfiguration: %v", err) - } - - // Sort the configuration into slices of KVPairs for each node, converting the - // nodename as we go. - nodeKvps := make(map[string][]*model.KVPair, 0) - for _, kvp := range kvps { - // Extract the key, update it and store the updated key. Store in the node-specific - // bucket. - hk := kvp.Key.(model.HostConfigKey) - hk.Hostname = converters.ConvertNodeName(hk.Hostname) - kvp.Key = hk - - nodeKvps[hk.Hostname] = append(nodeKvps[hk.Hostname], kvp) - } - - // For each node, get the felix config kvps and convert to v3 per-node - // FelixConfiguration resource. - for node, kvps := range nodeKvps { - // Convert to v3 resource. - nodeConfig := apiv3.NewFelixConfiguration() - nodeConfig.Name = fmt.Sprintf("node.%s", node) - if err := m.parseFelixConfigV1IntoResourceV3(kvps, nodeConfig, data); err != nil { - return fmt.Errorf("error converting FelixConfiguration: %v", err) - } - } - } - - return nil -} - -// This function converts a slice of v1 KVPairs into the appropriate v3 values and -// merges the results into a single v3 resource Spec for felix configuration (global -// or per host) or a clusterInfo. -// Conversion errors are added to the MigrationData struct. -func (m *migrationHelper) parseFelixConfigV1IntoResourceV3( - kvps []*model.KVPair, - res converters.Resource, - data *MigrationData, -) error { - logCxtRes := log.WithFields(log.Fields{ - "kind": res.GetObjectKind().GroupVersionKind().Kind, - "name": res.GetObjectMeta().GetName(), - }) - - // Convert the KVP slice into a name value map. - keysv1 := map[string]model.Key{} - configv1 := map[string]string{} - for _, kvp := range kvps { - if kvp.Value == nil { - continue - } - switch key := kvp.Key.(type) { - case model.GlobalConfigKey: - configv1[key.Name] = kvp.Value.(string) - keysv1[key.Name] = key - case model.HostConfigKey: - configv1[key.Name] = kvp.Value.(string) - keysv1[key.Name] = key - } - } - - // Extract the Spec from the resource FelixConfiguration or ClusterInfo. - specValue := reflect.ValueOf(res).Elem().FieldByName("Spec") - if !specValue.IsValid() { - return fmt.Errorf("unable to process config resource type: %v", res) - } - - // Loop through the Spec setting each field from the supplied KVPair data. - setField := false - specType := specValue.Type() - for i := 0; i < specType.NumField(); i++ { - field := specType.Field(i) - fieldValue := specValue.Field(i) - - // Get the v1 config value associated with the field. - configName := m.getConfigName(field) - logCxt := logCxtRes.WithFields(log.Fields{ - "field": field.Name, - "configName": configName, - }) - configStrValue, ok := configv1[configName] - if !ok { - logCxt.Debug("config value is not configured in v1") - continue - } - - isPtr := field.Type.Kind() == reflect.Ptr - fieldName := field.Name - - switch { - case strings.HasPrefix(fieldName, "Failsafe"): - // Special-case the Failsafe ports - these require parsing and settings as a struct. - if configStrValue == "none" { - // Has no failsafe ports - vProtoPort := &[]apiv3.ProtoPort{} - fieldValue.Set(reflect.ValueOf(vProtoPort)) - setField = true - continue - } - - vProtoPort, err := m.parseProtoPort(configStrValue) - if err != nil { - logCxt.WithError(err).Info("Failed to parse field") - data.ConversionErrors = append(data.ConversionErrors, ConversionError{ - Cause: err, - KeyV1: keysv1[configName], - ValueV1: configStrValue, - KeyV3: resourceToKey(res), - }) - continue - } - fieldValue.Set(reflect.ValueOf(vProtoPort)) // pointer to proto port slice. - setField = true - continue - case strings.HasPrefix(fieldName, "LogSeverity"): - // The log level fields need to have their value converted to the appropriate v3 value, - // but other than that are treated as normal string fields. - configStrValue = convertLogLevel(configStrValue) - } - - _, ok = fieldValue.Interface().(*metav1.Duration) - if ok { - if duration, err := strconv.ParseFloat(configStrValue, 64); err != nil { - logCxt.WithError(err).Info("Failed to parse float for Duration field") - data.ConversionErrors = append(data.ConversionErrors, ConversionError{ - Cause: fmt.Errorf("failed to parse float for Duration field: %v", err), - KeyV1: keysv1[configName], - ValueV1: configStrValue, - KeyV3: resourceToKey(res), - }) - } else { - switch field.Tag.Get("configv1timescale") { - case "milliseconds": - fieldValue.Set(reflect.ValueOf(&metav1.Duration{Duration: time.Duration(duration * float64(time.Millisecond))})) - default: - fieldValue.Set(reflect.ValueOf(&metav1.Duration{Duration: time.Duration(duration * float64(time.Second))})) - } - setField = true - continue - } - } - - // Set the field value based on the field type. - var kind reflect.Kind - if isPtr { - kind = field.Type.Elem().Kind() - } else { - kind = fieldValue.Kind() - } - - switch kind { - case reflect.Uint32: - if value, err := strconv.ParseUint(configStrValue, 10, 32); err != nil { - logCxt.WithError(err).Info("Failed to parse uint32 field") - data.ConversionErrors = append(data.ConversionErrors, ConversionError{ - Cause: fmt.Errorf("failed to parse uint32 field: %v", err), - KeyV1: keysv1[configName], - ValueV1: configStrValue, - KeyV3: resourceToKey(res), - }) - continue - } else if isPtr { - vu := uint32(value) - fieldValue.Set(reflect.ValueOf(&vu)) - } else { - fieldValue.SetUint(value) - } - case reflect.Int: - if value, err := strconv.ParseInt(configStrValue, 10, 64); err != nil { - logCxt.WithError(err).Info("Failed to parse int field") - data.ConversionErrors = append(data.ConversionErrors, ConversionError{ - Cause: fmt.Errorf("failed to parse int field: %v", err), - KeyV1: keysv1[configName], - ValueV1: configStrValue, - KeyV3: resourceToKey(res), - }) - continue - } else if isPtr { - vi := int(value) - fieldValue.Set(reflect.ValueOf(&vi)) - } else { - fieldValue.SetInt(value) - } - case reflect.Bool: - if value, err := strconv.ParseBool(configStrValue); err != nil { - logCxt.WithError(err).Info("Failed to parse bool field") - data.ConversionErrors = append(data.ConversionErrors, ConversionError{ - Cause: fmt.Errorf("failed to parse bool field: %v", err), - KeyV1: keysv1[configName], - ValueV1: configStrValue, - KeyV3: resourceToKey(res), - }) - continue - } else if isPtr { - fieldValue.Set(reflect.ValueOf(&value)) - } else { - fieldValue.SetBool(value) - } - case reflect.String: - if isPtr { - fieldValue.Set(reflect.ValueOf(&configStrValue)) - } else { - fieldValue.SetString(configStrValue) - } - default: - logCxt.Info("Unhandle field type") - data.ConversionErrors = append(data.ConversionErrors, ConversionError{ - Cause: fmt.Errorf("unhandled field type, please raise an issue on GitHub " + - "(https://github.com/projectcalico/calico) that includes this error message"), - KeyV1: keysv1[configName], - ValueV1: configStrValue, - KeyV3: resourceToKey(res), - }) - continue - } - - // We must have set a field in the spec. - setField = true - } - - if setField { - data.Resources = append(data.Resources, res) - } - return nil -} - -func (m *migrationHelper) parseProtoPortFailed(msg string) error { - return fmt.Errorf("failed to parse ProtoPort-%s", msg) -} - -func (m *migrationHelper) parseProtoPort(raw string) (*[]apiv3.ProtoPort, error) { - var result []apiv3.ProtoPort - for _, portStr := range strings.Split(raw, ",") { - portStr = strings.Trim(portStr, " ") - if portStr == "" { - continue - } - - protocolStr := "tcp" - netStr := "" - - // Check if IPv6 network is set - if strings.Contains(portStr, "[") && strings.Contains(portStr, "]") { - // Grab the IPv6 network - startIndex := strings.Index(portStr, "[") - endIndex := strings.Index(portStr, "]:") - netStr = portStr[startIndex+1 : endIndex] - - // Remove the IPv6 network value from portStr - var withoutIPv6 strings.Builder - withoutIPv6.WriteString(portStr[:startIndex]) - withoutIPv6.WriteString(portStr[endIndex+2:]) - portStr = withoutIPv6.String() - } - - parts := strings.Split(portStr, ":") - if len(parts) > 3 { - return nil, m.parseProtoPortFailed("ports should be :: or : or ") - } - - if len(parts) > 2 { - netStr = parts[1] - protocolStr = strings.ToUpper(parts[0]) - portStr = parts[2] - } - - if len(parts) == 2 { - protocolStr = strings.ToUpper(parts[0]) - portStr = parts[1] - } - - if protocolStr != "TCP" && protocolStr != "UDP" { - return nil, m.parseProtoPortFailed("unknown protocol: " + protocolStr) - } - - port, err := strconv.Atoi(portStr) - if err != nil { - return nil, m.parseProtoPortFailed("ports should be integers") - } - if port < 0 || port > 65535 { - err = m.parseProtoPortFailed("ports must be in range 0-65535") - return nil, err - } - - protoPort := apiv3.ProtoPort{ - Protocol: protocolStr, - Port: uint16(port), - } - - if netStr != "" { - _, netParsed, err := net.ParseCIDROrIP(netStr) - if err != nil { - err = m.parseProtoPortFailed("invalid CIDR or IP " + netStr) - return nil, err - } - protoPort.Net = netParsed.String() - } - - result = append(result, protoPort) - } - - return &result, nil -} - -// Return the config name from the field. The field name is either specified in the -// configname tag, otherwise it just uses the struct field name. -func (m *migrationHelper) getConfigName(field reflect.StructField) string { - name := field.Tag.Get("confignamev1") - if name == "" { - name = field.Name - } - return name -} diff --git a/libcalico-go/lib/upgrade/migrator/felixconfig_test.go b/libcalico-go/lib/upgrade/migrator/felixconfig_test.go deleted file mode 100644 index 06393609002..00000000000 --- a/libcalico-go/lib/upgrade/migrator/felixconfig_test.go +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright (c) 2017-2018 Tigera, Inc. All rights reserved. - -// 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 migrator - -import ( - "time" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/backend/syncersv1/updateprocessors" - cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" -) - -var _ = Describe("Test felix configuration upgrade", func() { - // Define some common values - seconds1 := metav1.Duration{Duration: time.Duration(12.345 * float64(time.Second))} - millis1 := metav1.Duration{Duration: time.Duration(50.325 * float64(time.Millisecond))} - bool1 := false - uint1 := uint32(1313) - seconds2 := metav1.Duration{Duration: time.Duration(32.222 * float64(time.Second))} - millis2 := metav1.Duration{Duration: time.Duration(10.754 * float64(time.Millisecond))} - bool2 := true - uint2 := uint32(1414) - perNodeFelixKey := model.ResourceKey{ - Kind: apiv3.KindFelixConfiguration, - Name: "node.mynode", - } - - perNodeFelix := apiv3.NewFelixConfiguration() - perNodeFelix.Name = "node.mynode" - perNodeFelix.Spec = apiv3.FelixConfigurationSpec{ - RouteRefreshInterval: &seconds1, - IptablesLockProbeInterval: &millis1, - InterfacePrefix: "califoobar", - IPIPEnabled: &bool1, - IptablesMarkMask: &uint1, - FailsafeInboundHostPorts: &[]apiv3.ProtoPort{}, - FailsafeOutboundHostPorts: &[]apiv3.ProtoPort{ - { - Protocol: "TCP", - Port: 1234, - Net: "0.0.0.0/0", - }, - { - Protocol: "UDP", - Port: 22, - Net: "0.0.0.0/0", - }, - { - Protocol: "TCP", - Port: 65535, - Net: "0.0.0.0/0", - }, - }, - } - - globalFelixKey := model.ResourceKey{ - Kind: apiv3.KindFelixConfiguration, - Name: "default", - } - globalFelix := apiv3.NewFelixConfiguration() - globalFelix.Name = "default" - globalFelix.Spec = apiv3.FelixConfigurationSpec{ - RouteRefreshInterval: &seconds2, - IptablesLockProbeInterval: &millis2, - InterfacePrefix: "califoobar", - IPIPEnabled: &bool2, - IptablesMarkMask: &uint2, - FailsafeInboundHostPorts: &[]apiv3.ProtoPort{ - { - Protocol: "TCP", - Port: 1234, - Net: "0.0.0.0/0", - }, - { - Protocol: "UDP", - Port: 22, - Net: "0.0.0.0/0", - }, - { - Protocol: "TCP", - Port: 65535, - Net: "0.0.0.0/0", - }, - }, - } - - globalClusterKey := model.ResourceKey{ - Kind: apiv3.KindClusterInformation, - Name: "default", - } - globalCluster := apiv3.NewClusterInformation() - globalCluster.Name = "default" - globalCluster.Spec = apiv3.ClusterInformationSpec{ - ClusterGUID: "abcedfg", - ClusterType: "Mesos,K8s", - DatastoreReady: &bool1, - } - - It("should handle different field types being assigned", func() { - clientv1 := fakeClientV1{} - - By("using an update processor to create v1 KVPairs from per-node FelixConfiguration") - cp := updateprocessors.NewFelixConfigUpdateProcessor() - kvps, err := cp.Process(&model.KVPair{ - Key: perNodeFelixKey, - Value: perNodeFelix, - }) - Expect(err).NotTo(HaveOccurred()) - clientv1.kvps = kvps - - By("using an update processor to create v1 KVPairs from global FelixConfiguration") - cp = updateprocessors.NewFelixConfigUpdateProcessor() - kvps, err = cp.Process(&model.KVPair{ - Key: globalFelixKey, - Value: globalFelix, - }) - Expect(err).NotTo(HaveOccurred()) - clientv1.kvps = append(clientv1.kvps, kvps...) - - By("using an update processor to create v1 KVPairs from global ClusterInformation") - cp = updateprocessors.NewClusterInfoUpdateProcessor() - kvps, err = cp.Process(&model.KVPair{ - Key: globalClusterKey, - Value: globalCluster, - }) - Expect(err).NotTo(HaveOccurred()) - clientv1.kvps = append(clientv1.kvps, kvps...) - - // Convert the data back to a set of resources. - data := &MigrationData{} - mh := &migrationHelper{clientv1: clientv1} - err = mh.queryAndConvertFelixConfigV1ToV3(data) - Expect(err).NotTo(HaveOccurred()) - By("Checking total conversion is 3") - Expect(data.Resources).To(HaveLen(3)) - By("Checking global felix config") - Expect(data.Resources[0]).To(Equal(globalFelix)) - By("Checking global cluster info") - Expect(data.Resources[1]).To(Equal(globalCluster)) - By("Checking per node felix config") - Expect(data.Resources[2]).To(Equal(perNodeFelix)) - }) -}) - -type fakeClientV1 struct { - kdd bool - kvps []*model.KVPair -} - -func (fc fakeClientV1) Apply(d *model.KVPair) (*model.KVPair, error) { - return nil, nil -} - -func (fc fakeClientV1) Update(d *model.KVPair) (*model.KVPair, error) { - return nil, nil -} - -func (fc fakeClientV1) Get(k model.Key) (*model.KVPair, error) { - ks := k.String() - for _, kvp := range fc.kvps { - if kvp.Key.String() == ks { - return kvp, nil - } - } - return nil, cerrors.ErrorResourceDoesNotExist{Identifier: k} -} - -func (fc fakeClientV1) List(l model.ListInterface) ([]*model.KVPair, error) { - r := []*model.KVPair{} - _, isPL := l.(model.ProfileListOptions) - for _, kvp := range fc.kvps { - p, _ := model.KeyToDefaultPath(kvp.Key) - if l.KeyFromDefaultPath(p) != nil { - r = append(r, kvp) - // This profile specific check allows adding a ProfileKey in the kvps - // that does not get matched with the above To/From DefaultPath check. - // The normal client would return a ProfileKey as it does the work of - // combining the rules/tags/labels. - } else if _, ok := kvp.Key.(model.ProfileKey); ok && isPL { - r = append(r, kvp) - } - } - return r, nil -} - -func (fc fakeClientV1) IsKDD() bool { - return fc.kdd -} diff --git a/libcalico-go/lib/upgrade/migrator/migrate.go b/libcalico-go/lib/upgrade/migrator/migrate.go deleted file mode 100644 index b001f32533b..00000000000 --- a/libcalico-go/lib/upgrade/migrator/migrate.go +++ /dev/null @@ -1,1196 +0,0 @@ -// Copyright (c) 2017-2025 Tigera, Inc. All rights reserved. - -// 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 migrator - -import ( - "context" - "errors" - "fmt" - "math/rand" - "strings" - "time" - - "github.com/coreos/go-semver/semver" - apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - "github.com/projectcalico/api/pkg/lib/numorstring" - log "github.com/sirupsen/logrus" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/uuid" - - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" - bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/clientv3" - cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" - "github.com/projectcalico/calico/libcalico-go/lib/names" - "github.com/projectcalico/calico/libcalico-go/lib/options" - "github.com/projectcalico/calico/libcalico-go/lib/upgrade/converters" - "github.com/projectcalico/calico/libcalico-go/lib/upgrade/migrator/clients" - validatorv3 "github.com/projectcalico/calico/libcalico-go/lib/validator/v3" -) - -const ( - forceEnableReadyRetries = 30 - maxApplyRetries = 5 - numAppliesPerUpdate = 100 - retryInterval = 5 * time.Second - - // The minimum version to upgrade from. This should not include the leading 'v'. - minUpgradeVersion = "2.6.5" -) - -// Interface is the migration interface used for migrating data from version -// v2.x to v3.x. -type Interface interface { - ValidateConversion() (*MigrationData, error) - IsDestinationEmpty() (bool, error) - ShouldMigrate() (bool, error) - CanMigrate() error - Migrate() (*MigrationData, error) - IsMigrationInProgress() (bool, error) - Abort() error - Complete() error -} - -// StatusWriterInterface is an optional interface supplied by the consumer of -// the migration helper used to record status of the migration. -type StatusWriterInterface interface { - Msg(string) - Bullet(string) - Error(string) -} - -// New creates a new migration helper implementing Interface. -func New(clientv3 clientv3.Interface, clientv1 clients.V1ClientInterface, statusWriter StatusWriterInterface) Interface { - return &migrationHelper{ - clientv3: clientv3, - clientv1: clientv1, - statusWriter: statusWriter, - } -} - -// migrationHelper implements the migrate.Interface. -type migrationHelper struct { - clientv3 clientv3.Interface - clientv1 clients.V1ClientInterface - statusWriter StatusWriterInterface -} - -// Error types encountered during validation and migration. -type ErrorType int - -const ( - ErrorGeneric ErrorType = iota - ErrorConvertingData - ErrorMigratingData -) - -type MigrationError struct { - Err error - Type ErrorType - NeedsAbort bool -} - -func (m MigrationError) Error() string { - return m.Err.Error() -} - -// MigrationData includes details about data migrated using the migration helper. -type MigrationData struct { - // The converted resources - Resources []converters.Resource - - // The converted resource names - NameConversions []NameConversion - - // Errors hit attempting to convert the v1 data to v3 format. The - // KeyV3 and ValueV3 will be nil for these conversion errors. - ConversionErrors []ConversionError - - // Errors hit validating the converted v3 data. This suggests an error in the - // conversion script which should be fixed before reattempting the conversion. - ConvertedResourceValidationErrors []ConversionError - - // Name clashes in the converted resources. These need to be resolved through - // reconfiguration before attempting the upgrade. - NameClashes []NameClash - - // Entries that were skipped because they will be handled by the Kubernetes - // Policy controller. - HandledByPolicyCtrl []model.Key -} - -// HasErrors returns whether there are any errors contained in the MigrationData. -func (c *MigrationData) HasErrors() bool { - return len(c.ConversionErrors) != 0 || - len(c.ConvertedResourceValidationErrors) != 0 || - len(c.NameClashes) != 0 -} - -// ConversionError contains details about a specific error converting a -// v1 resource to a v3 resource. -type ConversionError struct { - Cause error - KeyV1 model.Key - ValueV1 interface{} - KeyV3 model.Key - ValueV3 converters.Resource -} - -// NameConversion contains details about name/id conversions. -type NameConversion struct { - KeyV1 model.Key - KeyV3 model.Key -} - -// NameClash contains details about name/id clashes (i.e. when two converted resource -// names (for the same resource type) clash. -type NameClash struct { - KeyV1 model.Key - KeyV3 model.Key - OtherKeyV1 model.Key -} - -// Validate validates that the v1 data can be correctly converted to v3. -// If an error is returned it will be of type MigrationError. -func (m *migrationHelper) ValidateConversion() (*MigrationData, error) { - m.status("Validating conversion of v1 data to v3") - data, err := m.queryAndConvertResources() - if err != nil { - m.statusError("Unable to perform validation, please resolve errors and retry") - m.statusBullet("Cause: %v", err) - return nil, MigrationError{ - Type: ErrorGeneric, - Err: err, - } - } - if data.HasErrors() { - m.statusError("Error converting data, check output for details and resolve issues before starting upgrade") - return data, MigrationError{ - Type: ErrorConvertingData, - Err: fmt.Errorf("error converting data: %v", err), - } - } - m.statusBullet("data conversion successful") - - // Finally, check that we found some data. For KDD if there were no resources - // then that's fine, otherwise we should fail. - if !m.clientv1.IsKDD() && len(data.Resources) == 0 { - m.statusError("No v1 resources detected: is the API configuration correctly configured?") - return nil, MigrationError{ - Type: ErrorGeneric, - Err: errors.New("no v1 resources detected: is the API configuration correctly configured?"), - } - } - - // Everything validated correctly. - m.status("Data conversion validated successfully") - return data, nil -} - -func (m *migrationHelper) IsDestinationEmpty() (bool, error) { - m.status("Validating the v3 datastore") - clean, err := m.v3DatastoreIsClean() - if err != nil { - m.statusError("Unable to validate the v3 datastore") - m.statusBullet("Cause: %v", err) - return false, MigrationError{ - Type: ErrorGeneric, - Err: fmt.Errorf("unable to validate the v3 datastore: %v", err), - } - } - if clean { - m.statusBullet("the v3 datastore is empty") - } else { - m.statusBullet("the v3 datastore is not empty") - } - return clean, nil -} - -// Migrate migrates the data from v1 format to v3. Both a v1 and v3 client are required. -// It returns the converted set of data, a bool indicating whether the migration succeeded. -// If an error is returned it will be of type MigrationError. -func (m *migrationHelper) Migrate() (*MigrationData, error) { - // Now set the Ready flag to False. This will stop Felix from making any data plane updates - // and will prevent the orchestrator plugins from adding any new workloads or IP allocations - if !m.clientv1.IsKDD() { - m.status("Pausing Calico networking") - if err := m.clearReadyV1(); err != nil { - m.statusError("Unable to pause calico networking") - return nil, MigrationError{ - Type: ErrorGeneric, - Err: fmt.Errorf("unable to pause calico networking: %v", err), - } - } - - // Wait for a short period to allow orchestrators to finish any current allocations. - m.status("Calico networking is now paused - waiting for 15s") - time.Sleep(15 * time.Second) - } - - // Now query all the resources again and convert - this is the final snapshot that we will use. - m.status("Querying current v1 snapshot and converting to v3") - data, err := m.queryAndConvertResources() - if err != nil { - m.statusError("Unable to convert the v1 snapshot to v3") - m.statusBullet("cause: %v", err) - return nil, m.abortAfterError( - fmt.Errorf("error converting data: %v", err), ErrorGeneric, - ) - } - if data.HasErrors() { - m.statusError("Error converting data - will attempt to abort upgrade") - return nil, m.abortAfterError( - fmt.Errorf("error converting data: %v", err), ErrorConvertingData, - ) - } - m.statusBullet("data converted successfully") - - m.status("Storing v3 data") - if err = m.storeV3Resources(data); err != nil { - m.statusError("Unable to store the v3 resources") - m.statusBullet("cause: %v", err) - return nil, m.abortAfterError( - fmt.Errorf("error storing converted data: %v", err), ErrorMigratingData, - ) - } - - // And we also need to migrate the IPAM data. - m.status("Migrating IPAM data") - if m.clientv1.IsKDD() { - m.statusBullet("no data to migrate - not supported") - } else if err = m.migrateIPAMData(); err != nil { - m.statusError("Unable to migrate the v3 IPAM data") - m.statusBullet("cause: %v", err) - return nil, m.abortAfterError( - fmt.Errorf("error migrating IPAM data: %v", err), ErrorMigratingData, - ) - } - - m.status("Data migration from v1 to v3 successful") - m.statusBullet("check the output for details of the migrated resources") - m.statusBullet("continue by upgrading your calico/node versions to Calico v3.x") - return data, nil -} - -func (m *migrationHelper) abortAfterError(err error, errType ErrorType) error { - if m.clientv1.IsKDD() { - return MigrationError{Type: errType, Err: err} - } - if ae := m.Abort(); ae == nil { - return MigrationError{Type: errType, Err: err} - } - return MigrationError{Type: errType, Err: err, NeedsAbort: true} -} - -// IsMigrationInProgress infers from ShouldMigrate and the Ready flag if the datastore -// is being migrated and returns true if it is. If migration is needed and the Ready -// flag is false then it is assumed migration is in progress. This could provide a -// false positive if there was an error during migration and migration was aborted. -// The other non-error cases will return false. -func (m *migrationHelper) IsMigrationInProgress() (bool, error) { - migrateNeeded, err := m.ShouldMigrate() - if err != nil { - return false, fmt.Errorf("error checking migration progress status: %v", err) - } else if migrateNeeded { - // Migration is needed. If Ready is true then no upgrade has been started - // (not in progress). If Ready is false then upgrade has been started. - - ready, err := m.isReady() - if err != nil { - return false, fmt.Errorf("error checking migration progress status: %v", err) - } - return !ready, nil - } else { - return false, nil - } -} - -// Abort aborts the upgrade by re-enabling Calico networking in v1. -// If an error is returned it will be of type MigrationError. -func (m *migrationHelper) Abort() error { - m.status("Aborting upgrade") - var err error - if !m.clientv1.IsKDD() { - m.status("Re-enabling Calico networking for v1") - for i := 0; i < forceEnableReadyRetries; i++ { - err = m.setReadyV1() - if err == nil { - break - } - time.Sleep(1 * time.Second) - } - } - if err != nil { - m.statusError("Failed to abort upgrade. Retry command.") - m.statusBullet("cause: %v", err) - return MigrationError{Type: ErrorGeneric, Err: err, NeedsAbort: true} - } - m.status("Upgrade aborted successfully") - return nil -} - -// Complete completes the upgrade by re-enabling Calico networking in v1. -// If an error is returned it will be of type MigrationError. -func (m *migrationHelper) Complete() error { - m.status("Completing upgrade") - var err error - if !m.clientv1.IsKDD() { - m.status("Enabling Calico networking for v3") - for i := 0; i < forceEnableReadyRetries; i++ { - err = m.setReadyV3(true) - if err == nil { - break - } - time.Sleep(1 * time.Second) - } - } - if err != nil { - m.statusError("Failed to complete upgrade. Retry command.") - m.statusBullet("cause: %v", err) - return MigrationError{Type: ErrorGeneric, Err: err} - } - m.status("Upgrade completed successfully") - return nil -} - -type policyCtrlFilterOut func(model.Key) bool - -var noFilter = func(_ model.Key) bool { return false } - -// Filter to filter out K8s backed network policies -var filterGNP = func(k model.Key) bool { - gk := k.(model.PolicyKey) - return strings.HasPrefix(gk.Name, names.K8sNetworkPolicyNamePrefix) -} - -// Filter to filter out K8s (namespace) and OpenStack backed profiles -var filterProfile = func(k model.Key) bool { - gk := k.(model.ProfileKey) - return strings.HasPrefix(gk.Name, "k8s_ns.") || strings.HasPrefix(gk.Name, "openstack-sg-") -} - -// Filter to filter out OpenStack backed workload endpoints -var filterWEP = func(k model.Key) bool { - gk := k.(model.WorkloadEndpointKey) - return gk.OrchestratorID == apiv3.OrchestratorOpenStack -} - -type ic interface { - IsClean() (bool, error) -} - -func (m *migrationHelper) v3DatastoreIsClean() (bool, error) { - bc := m.clientv3.(backendClientAccessor).Backend() - if i, ok := bc.(ic); ok { - return i.IsClean() - } - return true, nil -} - -// queryAndConvertResources queries the v1 resources and converts them to the equivalent -// v3 resources. -// This method returns an error if it is unable to query the current v1 settings. -// Errors from the conversion are returned within the MigrationData - this function will -// attempt to convert everything before returning with the set of converted data and -// conversion errors - this allows a full pre-migration report to be generated in a single -// shot. -func (m *migrationHelper) queryAndConvertResources() (*MigrationData, error) { - data := &MigrationData{} - - // Query and convert global felix configuration and cluster info. - if err := m.queryAndConvertFelixConfigV1ToV3(data); err != nil { - return nil, err - } - - m.statusBullet("handling BGPConfiguration (global) resource") - // Query the global BGP configuration: default AS number; node-to-node mesh. - if err := m.queryAndConvertGlobalBGPConfigV1ToV3(data); err != nil { - return nil, err - } - - if m.clientv1.IsKDD() { - m.statusBullet("skipping Node resources - these do not need migrating") - } else { - m.statusBullet("handling Node resources") - // Query and convert the Nodes - if err := m.queryAndConvertV1ToV3Nodes(data); err != nil { - return nil, err - } - } - - if m.clientv1.IsKDD() { - m.statusBullet("skipping BGPPeer (global) resources - these do not need migrating") - } else { - m.statusBullet("handling BGPPeer (global) resources") - // Query and convert the BGPPeers - if err := m.queryAndConvertV1ToV3Resources( - data, model.GlobalBGPPeerListOptions{}, converters.BGPPeer{}, noFilter, - ); err != nil { - return nil, err - } - } - - m.statusBullet("handling BGPPeer (node) resources") - if err := m.queryAndConvertV1ToV3Resources( - data, model.NodeBGPPeerListOptions{}, converters.BGPPeer{}, noFilter, - ); err != nil { - return nil, err - } - - if m.clientv1.IsKDD() { - m.statusBullet("skipping HostEndpoint resources - not supported") - } else { - m.statusBullet("handling HostEndpoint resources") - // Query and convert the HostEndpoints - if err := m.queryAndConvertV1ToV3Resources( - data, model.HostEndpointListOptions{}, converters.HostEndpoint{}, noFilter, - ); err != nil { - return nil, err - } - } - - if m.clientv1.IsKDD() { - m.statusBullet("skipping IPPool resources - these do not need migrating") - } else { - m.statusBullet("handling IPPool resources") - // Query and convert the IPPools - if err := m.queryAndConvertV1ToV3Resources( - data, model.IPPoolListOptions{}, converters.IPPool{}, noFilter, - ); err != nil { - return nil, err - } - } - - if m.clientv1.IsKDD() { - m.statusBullet("skipping Tier resources - these do not need migrating") - } else { - m.statusBullet("handling Tier resources") - // Query and convert the Tiers - if err := m.queryAndConvertV1ToV3Resources( - data, model.TierListOptions{}, converters.Tier{}, noFilter, - ); err != nil { - return nil, err - } - } - - if m.clientv1.IsKDD() { - m.statusBullet("skipping GlobalNetworkPolicy resources - these do not need migrating") - } else { - m.statusBullet("handling GlobalNetworkPolicy resources") - // Query and convert the Policies - if err := m.queryAndConvertV1ToV3Resources( - data, model.PolicyListOptions{}, converters.Policy{}, filterGNP, - ); err != nil { - return nil, err - } - } - - if m.clientv1.IsKDD() { - m.statusBullet("skipping Profile resources - these do not need migrating") - } else { - m.statusBullet("handling Profile resources") - // Query and convert the Profiles - if err := m.queryAndConvertV1ToV3Resources( - data, model.ProfileListOptions{}, converters.Profile{}, filterProfile, - ); err != nil { - return nil, err - } - } - - if m.clientv1.IsKDD() { - m.statusBullet("skipping WorkloadEndpoint resources - these do not need migrating") - } else { - m.statusBullet("handling WorkloadEndpoint resources") - // Query and convert the WorkloadEndpoints - if err := m.queryAndConvertV1ToV3Resources( - data, model.WorkloadEndpointListOptions{}, converters.WorkloadEndpoint{}, filterWEP, - ); err != nil { - return nil, err - } - } - - return data, nil -} - -// Query the v1 format resources and convert to the v3 format. Successfully -// migrated resources are appended to res, and conversion errors to convErr. -func (m *migrationHelper) queryAndConvertV1ToV3Resources( - data *MigrationData, - listInterface model.ListInterface, - converter converters.Converter, - filterOut policyCtrlFilterOut, -) error { - // Start by listing the results from the v1 client. - kvps, err := m.clientv1.List(listInterface) - if err != nil { - switch err.(type) { - case cerrors.ErrorResourceDoesNotExist, cerrors.ErrorOperationNotSupported: - return nil - default: - return err - } - } - - // Keep track of the converted names so that we can determine if we have any - // name clashes. We don't generally expect this, but we do need to police against - // it just in case. - convertedNames := make(map[string]model.Key, len(kvps)) - - // Pass the results through the supplied converter and check that each result - // validates. - for _, kvp := range kvps { - if filterOut(kvp.Key) { - log.Infof("Filter out Policy Controller created resource: %s", kvp.Key) - data.HandledByPolicyCtrl = append(data.HandledByPolicyCtrl, kvp.Key) - continue - } - - r, err := converter.BackendV1ToAPIV3(kvp) - if err != nil { - data.ConversionErrors = append(data.ConversionErrors, ConversionError{ - KeyV1: kvp.Key, - ValueV1: kvp.Value, - Cause: err, - }) - continue - } - - // Check the converted name for clashes. Store an error if there is a clash and - // continue with additional checks so that we output as much information as possible. - valid := true - convertedName := r.GetObjectMeta().GetNamespace() + "/" + r.GetObjectMeta().GetName() - if k, ok := convertedNames[convertedName]; ok { - data.NameClashes = append(data.NameClashes, NameClash{ - KeyV1: kvp.Key, - KeyV3: resourceToKey(r), - OtherKeyV1: k, - }) - valid = false - } - convertedNames[convertedName] = kvp.Key - - // Check the converted resource validates correctly. - if err := validatorv3.Validate(r); err != nil { - data.ConvertedResourceValidationErrors = append(data.ConvertedResourceValidationErrors, ConversionError{ - KeyV1: kvp.Key, - ValueV1: kvp.Value, - KeyV3: resourceToKey(r), - ValueV3: r, - Cause: err, - }) - valid = false - } - - // Only store the resource and the converted name if it's valid. - if valid { - data.Resources = append(data.Resources, r) - data.NameConversions = append(data.NameConversions, NameConversion{ - KeyV1: kvp.Key, - KeyV3: resourceToKey(r), - }) - } - } - - return nil -} - -func (m *migrationHelper) queryAndConvertGlobalBGPConfigV1ToV3(data *MigrationData) error { - globalBGPConfig := apiv3.NewBGPConfiguration() - globalBGPConfig.Name = "default" - - log.Info("Converting BGP config -> BGPConfiguration(default)") - var setValue bool - if kvp, err := m.clientv1.Get(model.GlobalBGPConfigKey{Name: "AsNumber"}); err != nil { - if _, ok := err.(cerrors.ErrorResourceDoesNotExist); !ok { - return err - } - log.Info("No global default ASNumber configured") - } else if kvp.Value.(string) != "" { - asNum, err := numorstring.ASNumberFromString(kvp.Value.(string)) - if err != nil { - log.WithError(err).WithField("ASNumber", kvp.Value).Info("Invalid global default ASNumber") - data.ConversionErrors = append(data.ConversionErrors, ConversionError{ - ValueV1: kvp.Value, - Cause: fmt.Errorf("default ASNumber is not valid: %s", kvp.Value.(string)), - }) - return err - } - globalBGPConfig.Spec.ASNumber = &asNum - setValue = true - } - - if kvp, err := m.clientv1.Get(model.GlobalBGPConfigKey{Name: "LogLevel"}); err != nil { - if _, ok := err.(cerrors.ErrorResourceDoesNotExist); !ok { - return err - } - log.Info("No global BGP log level configured") - } else if kvp.Value.(string) != "" { - globalBGPConfig.Spec.LogSeverityScreen = convertLogLevel(kvp.Value.(string)) - setValue = true - } - - if kvp, err := m.clientv1.Get(model.GlobalBGPConfigKey{Name: "NodeMeshEnabled"}); err != nil { - if _, ok := err.(cerrors.ErrorResourceDoesNotExist); !ok { - return err - } - log.Info("No global node to node mesh enabled setting configured") - } else if kvp.Value.(string) != "" { - nodeMeshEnabled := strings.ToLower(kvp.Value.(string)) == "true" - globalBGPConfig.Spec.NodeToNodeMeshEnabled = &nodeMeshEnabled - setValue = true - } - if setValue { - data.Resources = append(data.Resources, globalBGPConfig) - } - return nil -} - -// Query the v1 format resources and convert to the v3 format. Successfully -// migrated resources are appended to res, and conversion errors to convErr. -func (m *migrationHelper) queryAndConvertV1ToV3Nodes(data *MigrationData) error { - // Start by querying the nodes and converting them, we don't add the nodes to the list - // of results just yet. - err := m.queryAndConvertV1ToV3Resources( - data, model.NodeListOptions{}, converters.Node{}, noFilter, - ) - if err != nil { - return err - } - - // Query all of the per-node config and extract all of the IPIP tunnel addresses that are - // configured. - kvps, err := m.clientv1.List(model.HostConfigListOptions{Name: "IpInIpTunnelAddr"}) - if err != nil { - switch err.(type) { - case cerrors.ErrorResourceDoesNotExist, cerrors.ErrorOperationNotSupported: - return nil - default: - return err - } - } - addrs := map[string]string{} - for _, kvp := range kvps { - k := kvp.Key.(model.HostConfigKey) - addrs[converters.ConvertNodeName(k.Hostname)] = kvp.Value.(string) - } - - // Update the node resources to include the tunnel addresses. Loop through the converted - // resources and modify any node that has a corresponding tunnel address (it's a pointer - // so we can adjust the in-situ resource). - for _, r := range data.Resources { - if nr, ok := r.(*libapiv3.Node); ok { - addr := addrs[nr.Name] - if addr == "" || nr.Spec.BGP == nil { - continue - } - nr.Spec.BGP.IPv4IPIPTunnelAddr = addr - } - } - - return nil -} - -// ShouldMigrate checks version information and reports if migration is needed -// and is possible. This checks both the v3 and v1 versions and indicates no migration -// if the v3 data is present and indicates a version of v3.0+. This method is used for -// automated KDD migrations where aborted upgrades use rollback to revert the previous -// system state. The rollback will remove the v3 data thus allowing aborted upgrades -// to be retried. See also CanMigrate below. -// -// If neither the v1 nor v3 versions are present, then no migration is required. -func (m *migrationHelper) ShouldMigrate() (bool, error) { - ci, err := m.clientv3.ClusterInformation().Get(context.Background(), "default", options.GetOptions{}) - if err == nil && ci.Spec.CalicoVersion == "" { - // The ClusterInformation exists but the CalicoVersion field is empty. This may happen if a - // non-calico/node or typha component initializes the datastore prior to the node writing in - // the current version, or the node or typha completing the data migration. - log.Debug("ClusterInformation contained empty CalicoVersion - treating as if ClusterInformation is not present") - } else if err == nil { - // The ClusterInformation exists and the CalicoVersion field is not empty, check if migration is - // required. - if yes, err := versionRequiresMigration(ci.Spec.CalicoVersion); err != nil { - log.Errorf("Unexpected CalicoVersion '%s' in ClusterInformation: %v", ci.Spec.CalicoVersion, err) - return false, fmt.Errorf("unexpected CalicoVersion '%s' in ClusterInformation: %v", ci.Spec.CalicoVersion, err) - } else if yes { - log.Debugf("ClusterInformation contained CalicoVersion '%s' and indicates migration is needed", ci.Spec.CalicoVersion) - return true, nil - } - log.Debugf("ClusterInformation contained CalicoVersion '%s' and indicates migration is not needed", ci.Spec.CalicoVersion) - return false, nil - } else if _, ok := err.(cerrors.ErrorResourceDoesNotExist); !ok { - // The error indicates a problem with accessing the resource. - return false, fmt.Errorf("unable to query ClusterInformation to determine Calico version: %v", err) - } - - // The resource does not exist from the clientv3 so we need to check the - // clientv1 version. Grab the version from the clientv1. - v, err := m.getV1ClusterVersion() - if err != nil { - if _, ok := err.(cerrors.ErrorResourceDoesNotExist); ok { - log.Debugf("CalicoVersion does exist in the v1 or v3 data, no migration needed") - // The resource does not exist in the clientv1 (or in the clientv3) - // so no migration is needed because it seems that this is an - // uninitialized datastore. - return false, nil - } - - // The error indicates a problem accessing the resource. - return false, fmt.Errorf("unable to query global Felix configuration to determine Calico version: %v", err) - } - - // Migrate only if it is possible to migrate from the current version. - if yes, err := versionRequiresMigration(v); err != nil { - // Hit an error parsing the version, or we determined that the version is not - // valid to migrate from. - log.Errorf("Unable to migrate data from version '%s': %v", v, err) - return false, fmt.Errorf("unable to migrate data from version '%s': %v", v, err) - } else if !yes { - // Version indicates that migration is not required - however, we should never have a - // v3.0+ version in the v1 API data which suggests a modified/hacked set up which we - // cannot migrate from. - log.Errorf("Unexpected version in the global Felix configuration, currently at %s", v) - return false, fmt.Errorf("unexpected Calico version '%s': migration to v3 should be from a tagged "+ - "release of Calico v%s+", v, minUpgradeVersion) - } - log.Debugf("GlobalConfig contained CalicoVersion '%s' and indicates migration is needed", v) - return true, nil -} - -// CanMigrate checks version information and reports if migration is possible (nil error). -// This differs from ShouldMigrate in that it only checks the v1 API - thus allowing aborted -// upgrades to be retried when the v3 data has not been removed. This method is used by -// the calico-upgrade script which performs additional checks to verify that the v3 -// datastore is clean. If the v1 version is not present, this is considered an error case. -func (m *migrationHelper) CanMigrate() error { - m.status("Checking Calico version is suitable for migration") - v, err := m.getV1ClusterVersion() - if err != nil { - if _, ok := err.(cerrors.ErrorResourceDoesNotExist); ok { - m.statusBullet("no version found in the v1 API datastore") - return errors.New("unable to determine the Calico API version: no version information found - this may be " + - "due to a misconfiguration of the v1 API datastore access details") - } - - // The error indicates a problem accessing the resource. - m.statusBullet("unable to query Calico version in the v1 API datastore: %v", err) - return err - } - - // Migrate only if it is possible to migrate from the current version. - m.statusBullet("determined Calico version of: %s", v) - if yes, err := versionRequiresMigration(v); err != nil { - // Hit an error parsing the version, or we determined that the version is not - // valid to migrate from. - m.statusBullet("unable to parse version") - return fmt.Errorf("unable to migrate data from version '%s': %v", v, err) - } else if !yes { - // Version indicates that migration is not required - however, we should never have a - // v3.0+ version in the v1 API data which suggests a modified/hacked set up which we - // cannot migrate from. - m.statusBullet("unexpected version in the v1 API datastore: should not have a version v3.0+") - return fmt.Errorf("unexpected Calico version '%s': migration to v3 should be from a tagged "+ - "release of Calico v%s+", v, minUpgradeVersion) - } - m.statusBullet("the v1 API data can be migrated to the v3 API") - return nil -} - -// getV1ClusterVersion reads the CalicoVersion from the v1 client interface. -func (m *migrationHelper) getV1ClusterVersion() (string, error) { - kv, err := m.clientv1.Get(model.GlobalConfigKey{Name: "CalicoVersion"}) - if err == nil { - return kv.Value.(string), nil - } - return "", err -} - -// versionRequiresMigration returns true if the given version requires -// upgrading the data model, false otherwise. Returns an error if it was -// not possible to parse the version, or if the version was less than the -// minimum requires for upgrade. -func versionRequiresMigration(v string) (bool, error) { - sv, err := semver.NewVersion(strings.TrimPrefix(v, "v")) - if err != nil { - log.Warnf("unable to parse Calico version %s: %v", v, err) - return false, errors.New("unable to parse the version") - } - - if sv.Major >= 3 { - log.Debugf("major version is already >= 3: %s", v) - // No need to migrate 3.0 or greater - return false, nil - } - // Omit the pre-release from the comparison - we allow pre-releases to enable testing - // of the upgrade. - sv.PreRelease = semver.PreRelease("") - - // Compare the parsed version against the minimum required version. - svMin := semver.New(minUpgradeVersion) - if sv.Compare(*svMin) >= 0 { - // Version is greater than or equal to the minimum upgrade version, and less than 3.0 - return true, nil - } - - // Version is less than the minimum required version (including pre-releases). This is an - // error case and we cannot allow upgrade to continue. - log.Infof("cannot migrate from version %s: migration to v3 requires a tagged release of "+ - "Calico v%s+", v, minUpgradeVersion) - return false, fmt.Errorf("migration to v3 requires a tagged release of Calico v%s+", minUpgradeVersion) -} - -// convertLogLevel converts the v1 log level to the equivalent v3 log level. We -// ignore errors, defaulting to info in the event of a conversion error. -func convertLogLevel(logLevel string) string { - switch strings.ToLower(logLevel) { - case "debug": - return "Debug" - case "info": - return "Info" - case "warning": - return "Warning" - case "error": - return "Error" - case "fatal": - return "Fatal" - case "panic": - return "Fatal" - case "": - return "" - default: - return "Info" - } -} - -// Display a 79-char word wrapped status message and log. -func (m *migrationHelper) status(format string, a ...interface{}) { - msg := fmt.Sprintf(format, a...) - log.Info(strings.TrimSpace(msg)) - if m.statusWriter != nil { - m.statusWriter.Msg(msg) - } -} - -// Display a 79-char word wrapped sub status (a bulleted message) and log. -func (m *migrationHelper) statusBullet(format string, a ...interface{}) { - msg := fmt.Sprintf(format, a...) - log.Info(strings.TrimSpace(msg)) - if m.statusWriter != nil { - m.statusWriter.Bullet(msg) - } -} - -// Display a 79-char word wrapped error message and log. -func (m *migrationHelper) statusError(format string, a ...interface{}) { - msg := fmt.Sprintf(format, a...) - log.Error(strings.TrimSpace(msg)) - if m.statusWriter != nil { - m.statusWriter.Error(msg) - } -} - -func (m *migrationHelper) clearReadyV1() error { - log.WithField("Ready", false).Info("Updating Ready flag in v1") - readyKV, err := m.clientv1.Get(model.ReadyFlagKey{}) - if err != nil { - m.statusBullet("failed to get status of Calico networking in the v1 configuration") - return err - } - if !readyKV.Value.(bool) { - m.statusBullet("Calico networking already paused in the v1 configuration") - return fmt.Errorf("Calico networking already paused do not continue.") - } - readyKV.Value = false - _, err = m.clientv1.Update(readyKV) - - if err != nil { - m.statusBullet("failed to pause Calico networking in the v1 configuration") - return err - } - m.statusBullet("successfully paused Calico networking in the v1 configuration") - return nil -} - -// setReadyV1 sets the ready flag in the v1 datastore. -func (m *migrationHelper) setReadyV1() error { - log.WithField("Ready", true).Info("Updating Ready flag in v1") - _, err := m.clientv1.Apply(&model.KVPair{ - Key: model.ReadyFlagKey{}, - Value: true, - }) - if err != nil { - m.statusBullet("failed to resume Calico networking in the v1 configuration") - return err - } - m.statusBullet("successfully resumed Calico networking in the v1 configuration") - return nil -} - -// setReadyV3 sets the ready flag in the v3 datastore. -func (m *migrationHelper) setReadyV3(ready bool) error { - log.WithField("Ready", ready).Info("Updating Ready flag in v3") - c, err := m.clientv3.ClusterInformation().Get(context.Background(), "default", options.GetOptions{}) - if err == nil { - // ClusterInformation already exists - update the settings. - log.WithField("Ready", ready).Info("Updating Ready flag in v3 ClusterInformation") - c.Spec.DatastoreReady = &ready - _, err = m.clientv3.ClusterInformation().Update(context.Background(), c, options.SetOptions{}) - if err != nil { - log.WithError(err).Info("Hit error setting ready flag in v3 ClusterInformation") - if ready { - m.statusBullet("failed to resume Calico networking in the v3 configuration: %v", err) - } else { - m.statusBullet("failed to pause Calico networking in the v3 configuration: %v", err) - } - return err - } - if ready { - m.statusBullet("successfully resumed Calico networking in the v3 configuration (updated ClusterInformation)") - } else { - m.statusBullet("successfully paused Calico networking in the v3 configuration (updated ClusterInformation)") - } - return nil - } - - if _, ok := err.(cerrors.ErrorResourceDoesNotExist); !ok { - // ClusterInformation could not be queried. - if ready { - m.statusBullet("failed to resume Calico networking in the v3 configuration (unable to query ClusterInformation)") - } else { - m.statusBullet("failed to pause Calico networking in the v3 configuration (unable to query ClusterInformation)") - } - return err - } - - // ClusterInformation does not exist - create a new one. - c = apiv3.NewClusterInformation() - c.Name = "default" - c.Spec.DatastoreReady = &ready - _, err = m.clientv3.ClusterInformation().Create(context.Background(), c, options.SetOptions{}) - if err != nil { - if ready { - m.statusBullet("failed to resume Calico networking in the v3 configuration (unable to create ClusterInformation)") - } else { - m.statusBullet("failed to pause Calico networking in the v3 configuration (unable to create ClusterInformation)") - } - return err - } - if ready { - m.statusBullet("successfully resumed Calico networking in the v1 configuration (created ClusterInformation)") - } else { - m.statusBullet("successfully paused Calico networking in the v1 configuration (created ClusterInformation)") - } - return nil -} - -// isReady reads the Ready flag from the datastore and returns its value -func (m *migrationHelper) isReady() (bool, error) { - kv, err := m.clientv1.Get(model.ReadyFlagKey{}) - if err != nil { - m.statusError("Unable to query the v1 datastore for ready status") - m.statusBullet("Cause: %v", err) - return false, err - } - - if kv.Value.(bool) { - m.statusBullet("Ready flag is true.") - } else { - m.statusBullet("Ready flag is false.") - } - return kv.Value.(bool), nil -} - -// resourceToKey creates a model.Key from a v3 resource. -func resourceToKey(r converters.Resource) model.Key { - return model.ResourceKey{ - Kind: r.GetObjectKind().GroupVersionKind().Kind, - Name: r.GetObjectMeta().GetName(), - Namespace: r.GetObjectMeta().GetNamespace(), - } -} - -// storeV3Resources stores the converted resources in the v3 datastore. -func (m *migrationHelper) storeV3Resources(data *MigrationData) error { - m.statusBullet("Storing resources in v3 format") - for n, r := range data.Resources { - // Convert the resource to a KVPair and access the backend datastore directly. - // This is slightly more efficient, and cuts out some of the unnecessary additional - // processing. Since we are applying directly to the backend we need to set the UUID - // and creation timestamp which is normally handled by clientv3. - r = toStorage(r) - if err := m.applyToBackend(&model.KVPair{ - Key: resourceToKey(r), - Value: r, - }); err != nil { - return err - } - - if (n+1)%numAppliesPerUpdate == 0 { - m.statusBullet("applied %d resources", (n + 1)) - } - } - m.statusBullet("success: resources stored in v3 datastore") - return nil -} - -func toStorage(r converters.Resource) converters.Resource { - // Set timestamp and UID. - r.GetObjectMeta().SetCreationTimestamp(metav1.Now()) - r.GetObjectMeta().SetUID(uuid.NewUUID()) - - // Some types require additional processing when converting to storage. - switch r.(type) { - case *apiv3.GlobalNetworkPolicy, *apiv3.NetworkPolicy: - // For GNP and NP, we need to adjust the name before storage. - r.GetObjectMeta().SetName(convertPolicyNameForStorage(r.GetObjectMeta().GetName())) - } - - return r -} - -func convertPolicyNameForStorage(name string) string { - // Do nothing on names prefixed with "knp." - if strings.HasPrefix(name, "knp.") { - return name - } - return "default." + name -} - -// migrateIPAMData queries, converts and migrates all of the IPAM data from v1 -// to v3 formats. -func (m *migrationHelper) migrateIPAMData() error { - var kvpsv3 []*model.KVPair - - // Query all of the IPAM data: - // - Blocks - // - BlockAffinity - // - IPAMHandle - // AllocationBlocks need to have their host affinity updated to use the - // normalized node name. - m.statusBullet("listing and converting IPAM allocation blocks") - kvps, err := m.clientv1.List(model.BlockListOptions{}) - if err != nil { - m.statusError("Unable to list IPAM allocation blocks") - m.statusBullet("cause: %v", err) - return fmt.Errorf("unable to list IPAM allocation blocks: %v", err) - } - for _, kvp := range kvps { - ab := kvp.Value.(*model.AllocationBlock) - node := "" - if ab.Affinity != nil && strings.HasPrefix(*ab.Affinity, "host:") { - node = strings.TrimPrefix(*ab.Affinity, "host:") - } else if ab.HostAffinity != nil { - node = *ab.HostAffinity - } - - if node != "" { - aff := "host:" + converters.ConvertNodeName(node) - ab.Affinity = &aff - } - kvpsv3 = append(kvpsv3, kvp) - } - - // BlockAffinities need to have their host updated to use the - // normalized node name. - m.statusBullet("listing and converting IPAM affinity blocks") - kvps, err = m.clientv1.List(model.BlockAffinityListOptions{}) - if err != nil { - m.statusError("Unable to list IPAM affinity blocks") - m.statusBullet("cause: %v", err) - return fmt.Errorf("unable to list IPAM affinity blocks: %v", err) - } - for _, kvp := range kvps { - k := kvp.Key.(model.BlockAffinityKey) - k.Host = converters.ConvertNodeName(k.Host) - kvp.Key = k - kvpsv3 = append(kvpsv3, kvp) - } - - // IPAMHandle does not require any conversion. - m.statusBullet("listing IPAM handles") - kvps, err = m.clientv1.List(model.IPAMHandleListOptions{}) - if err != nil { - m.statusError("Unable to list IPAM handles") - m.statusBullet("cause: %v", err) - return fmt.Errorf("unable to list IPAM handles: %v", err) - } - kvpsv3 = append(kvpsv3, kvps...) - - // Create/Apply the converted entries into the v3 datastore. - m.statusBullet("storing IPAM data in v3 format") - for _, kvp := range kvpsv3 { - if err := m.applyToBackend(kvp); err != nil { - m.statusError("Error writing IPAM data to v3 datastore") - return fmt.Errorf("error storing converted IPAM data: %v", err) - } - } - - // We migrated the data successfully. - m.statusBullet("IPAM data migrated successfully") - return nil -} - -// backendClientAccessor is an interface used to access the backend client from the main clientv3. -type backendClientAccessor interface { - Backend() bapi.Client -} - -// applyToBackend applies the supplied KVPair directly to the backend datastore. -func (m *migrationHelper) applyToBackend(kvp *model.KVPair) error { - // Extract the backend client API from the v3 client. - bc := m.clientv3.(backendClientAccessor).Backend() - - // First try creating the resource. If the resource already exists, try an update. - logCxt := log.WithField("Key", kvp.Key) - logCxt.Debug("Attempting to create resource") - kvp.Revision = "" - _, err := bc.Create(context.Background(), kvp) - if err == nil { - logCxt.Debug("Resource created") - return nil - } - if _, ok := err.(cerrors.ErrorResourceAlreadyExists); !ok { - logCxt.WithError(err).Info("Failed to create resource") - return err - } - - logCxt.Debug("Resource already exists, try update") - for i := 0; i < maxApplyRetries; i++ { - // Query the current settings and update the kvp revision so that we can - // perform an update. - logCxt.Debug("Attempting to update resource") - current, err := bc.Get(context.Background(), kvp.Key, "") - if err != nil { - return err - } - kvp.Revision = current.Revision - - _, err = bc.Update(context.Background(), kvp) - if err == nil { - logCxt.Debug("Resource updated") - return nil - } - if _, ok := err.(cerrors.ErrorResourceUpdateConflict); !ok { - break - } - - // We hit a resource update conflict - pause for a short duration before - // retrying. - time.Sleep(time.Duration(float64(retryInterval) * (1 + (0.1 * rand.Float64())))) - } - - logCxt.WithError(err).Info("Failed to update resource") - return err -} diff --git a/libcalico-go/lib/upgrade/migrator/migrate_suite_test.go b/libcalico-go/lib/upgrade/migrator/migrate_suite_test.go deleted file mode 100644 index af983f6e8f7..00000000000 --- a/libcalico-go/lib/upgrade/migrator/migrate_suite_test.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2017-2018 Tigera, Inc. All rights reserved. - -// 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 migrator - -import ( - "testing" - - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" - - "github.com/projectcalico/calico/libcalico-go/lib/testutils" -) - -func TestClient(t *testing.T) { - testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../report/migrate_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "calico-upgrade migration pkg suite", []Reporter{junitReporter}) -} diff --git a/libcalico-go/lib/upgrade/migrator/migrate_test.go b/libcalico-go/lib/upgrade/migrator/migrate_test.go deleted file mode 100644 index bd200bb10bc..00000000000 --- a/libcalico-go/lib/upgrade/migrator/migrate_test.go +++ /dev/null @@ -1,258 +0,0 @@ -// Copyright (c) 2017-2018 Tigera, Inc. All rights reserved. - -// 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 migrator - -import ( - "context" - "errors" - gnet "net" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" - v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - "github.com/projectcalico/calico/libcalico-go/lib/backend" - "github.com/projectcalico/calico/libcalico-go/lib/backend/model" - "github.com/projectcalico/calico/libcalico-go/lib/clientv3" - "github.com/projectcalico/calico/libcalico-go/lib/net" - "github.com/projectcalico/calico/libcalico-go/lib/options" - "github.com/projectcalico/calico/libcalico-go/lib/testutils" - "github.com/projectcalico/calico/libcalico-go/lib/upgrade/converters" - "github.com/projectcalico/calico/libcalico-go/lib/upgrade/migrator/clients" -) - -var _ = Describe("UT for checking the version for migration.", func() { - - DescribeTable("Checking canMigrate.", - func(ver string, result bool, resultHasError bool) { - yes, err := versionRequiresMigration(ver) - - if resultHasError { - Expect(err).To(HaveOccurred()) - } else { - Expect(err).NotTo(HaveOccurred()) - Expect(yes).To(Equal(result)) - } - }, - - Entry("Expect v2.6.5 to migrate", "v2.6.5", true, false), - Entry("Expect v2.6.5-rc1 to migrate", "v2.6.5-rc1", true, false), - Entry("Expect v2.6.5-15-g0986c6cd to migrate", "v2.6.5-15-g0986c6cd", true, false), - Entry("Expect v2.6.6 to migrate", "v2.6.6", true, false), - Entry("Expect v2.6.6-rc1 to migrate", "v2.6.6-rc1", true, false), - Entry("Expect v2.7.0 to migrate", "v2.7.0", true, false), - Entry("Expect v2.7.0-rc1 to migrate", "v2.7.0-rc1", true, false), - Entry("Expect v2.6.4 to not migrate", "v2.6.4", false, true), - Entry("Expect v2.6.4-rc1 to not migrate", "v2.6.4-rc1", false, true), - Entry("Expect v2.6.4-15-g0986c6cd to not migrate", "v2.6.4-15-g0986c6cd", false, true), - Entry("Expect v2.6.x-deadbeef to not migrate", "v2.6.x-deadbeef", false, true), - Entry("Expect v3.0 to not migrate", "v3.0", false, true), - Entry("Expect v3.0.0 to not migrate", "v3.0.0", false, false), - Entry("Expect v3.0.0-beta1 to not migrate", "v3.0.0-beta1", false, false), - Entry("Expect v3.0.0-0 to not migrate", "v3.0.0-0", false, false), - Entry("Expect v3.0.0-a to not migrate", "v3.0.0-a", false, false), - Entry("Expect v3.0.0-beta1-128-g1caef47d to not migrate", "v3.0.0-beta1-128-g1caef47d", false, false), - Entry("Expect master to not migrate", "master", false, true), - Entry("Expect empty string to not migrate", "", false, true), - Entry("Expect garbage to not migrate", "garbage", false, true), - Entry("Expect 1.2.3.4.5 to not migrate", "1.2.3.4.5", false, true), - ) -}) - -func convertAndCheckResourcesConverted(client clients.V1ClientInterface, expectedConversionCount int) { - // Convert the data back to a set of resources. - mh := &migrationHelper{clientv1: client} - data, err := mh.queryAndConvertResources() - Expect(err).NotTo(HaveOccurred()) - Expect(data.ConversionErrors).To(HaveLen(0)) - By("Checking total conversion") - Expect(data.Resources).To(HaveLen(expectedConversionCount)) -} - -var _ = Describe("Test OpenStack migration filters", func() { - - wk := model.WorkloadEndpointKey{ - Hostname: "ahost", - OrchestratorID: "orchestrator", - WorkloadID: "wkid", - EndpointID: "endID", - } - mac, _ := gnet.ParseMAC("ee:ee:ee:ee:ee:ee") - wv := model.WorkloadEndpoint{ - State: "Running", - Name: "wepName", - ActiveInstanceID: "wepActInstID", - Mac: &net.MAC{HardwareAddr: mac}, - ProfileIDs: []string{"wepProfIDs"}, - IPv4Nets: []net.IPNet{}, - IPv6Nets: []net.IPNet{}, - } - - It("should not filter WorkloadEndpoints without openstack as OrchestratorID", func() { - clientv1 := fakeClientV1{ - kvps: []*model.KVPair{ - { - Key: wk, - Value: &wv, - }, - }, - } - - convertAndCheckResourcesConverted(clientv1, 1) - }) - - It("should filter WorkloadEndpoints with openstack as OrchestratorID", func() { - wepOSKey := wk - wepOSKey.OrchestratorID = v3.OrchestratorOpenStack - clientv1 := fakeClientV1{ - kvps: []*model.KVPair{ - { - Key: wepOSKey, - Value: &wv, - }, - }, - } - - convertAndCheckResourcesConverted(clientv1, 0) - }) - - It("should not filter Profiles without openstack-sg prefix", func() { - clientv1 := fakeClientV1{ - kvps: []*model.KVPair{ - { - Key: model.ProfileKey{ - Name: "profilename", - }, - Value: &model.Profile{ - Rules: model.ProfileRules{ - InboundRules: []model.Rule{converters.V1ModelInRule1}, - }, - Tags: []string{}, - Labels: map[string]string{"label1": "value1"}, - }, - }, - }, - } - - convertAndCheckResourcesConverted(clientv1, 1) - }) - It("should filter Profiles with openstack-sg- prefix", func() { - clientv1 := fakeClientV1{ - kvps: []*model.KVPair{ - { - Key: model.ProfileKey{ - Name: "openstack-sg-profilename", - }, - Value: &model.Profile{ - Rules: model.ProfileRules{ - InboundRules: []model.Rule{converters.V1ModelInRule1}, - }, - Tags: []string{}, - Labels: map[string]string{"label1": "value1"}, - }, - }, - }, - } - - convertAndCheckResourcesConverted(clientv1, 0) - }) -}) - -var _ = testutils.E2eDatastoreDescribe("Migration tests", testutils.DatastoreEtcdV3, func(config apiconfig.CalicoAPIConfig) { - - ctx := context.Background() - blank := "" - v2_6_4 := "v2.6.4" - v2_6_5 := "v2.6.5" - v3_0_0 := "v3.0.0" - v3_1_0 := "v3.1.0" - master := "master" - - DescribeTable("ShouldMigrate() tests", - func(v1Version, v3Version *string, expected interface{}) { - // For the v1 version, we use the emulated client since this is an easy implementation. - // For the v3 version, we hook into etcdv3 using the real v3 client. - v3Client, err := clientv3.New(config) - Expect(err).NotTo(HaveOccurred()) - - // Clean the v3 data. - be, err := backend.NewClient(config) - Expect(err).NotTo(HaveOccurred()) - be.Clean() - - // If the v3 version is specified, configure it in the ClusterInformation. - By("Configuring a cluster version in the v3 datastore") - if v3Version != nil { - _, err := v3Client.ClusterInformation().Create(ctx, &v3.ClusterInformation{ - ObjectMeta: v1.ObjectMeta{ - Name: "default", - }, - Spec: v3.ClusterInformationSpec{ - CalicoVersion: *v3Version, - }, - }, options.SetOptions{}) - Expect(err).NotTo(HaveOccurred()) - } - - // Create the dummy v1 client, if necessary including the v1 cluster version. - By("Configuring a cluster version in the v1 datastore") - v1Client := fakeClientV1{} - if v1Version != nil { - v1Client.kvps = append(v1Client.kvps, &model.KVPair{ - Key: model.GlobalConfigKey{Name: "CalicoVersion"}, - Value: *v1Version, - }) - } - - // Create the migration helper. - By("Creating a migration helper and invoking ShouldMigrate()") - mh := &migrationHelper{clientv1: v1Client, clientv3: v3Client} - s, err := mh.ShouldMigrate() - if b, ok := expected.(bool); ok { - Expect(err).NotTo(HaveOccurred()) - Expect(s).To(Equal(b)) - } else { - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal(expected.(error).Error())) - Expect(s).To(BeFalse()) - } - }, - - // Test 1: Pass two fully populated BGPConfigurationSpecs and expect the series of operations to succeed. - Entry("No calico version data", nil, nil, false), - Entry("v1 only calico version (v2.6.4)", &v2_6_4, nil, - errors.New("unable to migrate data from version 'v2.6.4': migration to "+ - "v3 requires a tagged release of Calico v2.6.5+")), - Entry("v1 only calico version (v2.6.5)", &v2_6_5, nil, true), - Entry("v1 only calico version (v3.0.0)", &v3_0_0, nil, - errors.New("unexpected Calico version 'v3.0.0': migration to v3 should be from a tagged "+ - "release of Calico v2.6.5+")), - Entry("v1 only calico version (master)", &master, nil, - errors.New("unable to migrate data from version 'master': unable to parse the version")), - Entry("v3 only calico version (v2.6.4)", nil, &v2_6_4, - errors.New("unexpected CalicoVersion 'v2.6.4' in ClusterInformation: migration to v3 requires a tagged "+ - "release of Calico v2.6.5+")), - Entry("v1 and v3 calico version (v2.6.5)", &v2_6_5, &v2_6_5, true), - Entry("v1 and v3 calico version (v2.6.5 and v3.0.0 resp)", &v2_6_5, &v3_0_0, false), - Entry("v1 and v3 calico version (v2.6.5 and v3.1.0 resp)", &v2_6_5, &v3_1_0, false), - Entry("v1 and v3 calico version (v2.6.5 and blank resp)", &v2_6_5, &blank, true), - Entry("v1 and v3 calico version (blank)", &blank, &blank, - errors.New("unable to migrate data from version '': unable to parse the version")), - Entry("v3 only calico version (blank)", nil, &blank, false), - ) -}) diff --git a/libcalico-go/lib/validator/v1/common_test.go b/libcalico-go/lib/validator/v1/common_test.go index 7d0105a6209..9692b1329f6 100644 --- a/libcalico-go/lib/validator/v1/common_test.go +++ b/libcalico-go/lib/validator/v1/common_test.go @@ -15,7 +15,7 @@ package v1_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/calico/libcalico-go/lib/apis/v1" diff --git a/libcalico-go/lib/validator/v1/validator.go b/libcalico-go/lib/validator/v1/validator.go index f11b94c4afb..6c98fd2dc42 100644 --- a/libcalico-go/lib/validator/v1/validator.go +++ b/libcalico-go/lib/validator/v1/validator.go @@ -67,7 +67,7 @@ var ( // Validate is used to validate the supplied structure according to the // registered field and structure validators. -func Validate(current interface{}) error { +func Validate(current any) error { err := validate.Struct(current) if err == nil { return nil @@ -137,8 +137,8 @@ func reason(r string) string { // extractReason extracts the error reason from the field tag in a validator // field error (if there is one). func extractReason(tag string) string { - if strings.HasPrefix(tag, reasonString) { - return strings.TrimPrefix(tag, reasonString) + if after, ok := strings.CutPrefix(tag, reasonString); ok { + return after } return "" } @@ -147,7 +147,7 @@ func registerFieldValidator(key string, fn validator.Func) { validate.RegisterValidation(key, fn) } -func registerStructValidator(fn validator.StructLevelFunc, t ...interface{}) { +func registerStructValidator(fn validator.StructLevelFunc, t ...any) { validate.RegisterStructValidation(fn, t...) } diff --git a/libcalico-go/lib/validator/v1/validator_suite_test.go b/libcalico-go/lib/validator/v1/validator_suite_test.go index 2b352ba5135..2f3f3106900 100644 --- a/libcalico-go/lib/validator/v1/validator_suite_test.go +++ b/libcalico-go/lib/validator/v1/validator_suite_test.go @@ -17,9 +17,8 @@ package v1_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestValidator(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../report/v1_validator_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "v1 Validator Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../report/v1_validator_suite.xml" + ginkgo.RunSpecs(t, "v1 Validator Suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/validator/v1/validator_test.go b/libcalico-go/lib/validator/v1/validator_test.go index c3f74c70875..affae5034e5 100644 --- a/libcalico-go/lib/validator/v1/validator_test.go +++ b/libcalico-go/lib/validator/v1/validator_test.go @@ -15,7 +15,7 @@ package v1_test import ( - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/api/pkg/lib/numorstring" @@ -67,7 +67,7 @@ func init() { // scenarios. This does not test precise error strings - but does cover a lot of the validation // code paths. DescribeTable("Validator", - func(input interface{}, valid bool) { + func(input any, valid bool) { if valid { Expect(validator.Validate(input)).NotTo(HaveOccurred(), "expected value to be valid") diff --git a/libcalico-go/lib/validator/v3/validator.go b/libcalico-go/lib/validator/v3/validator.go index 84c6820c14a..a814e227846 100644 --- a/libcalico-go/lib/validator/v3/validator.go +++ b/libcalico-go/lib/validator/v3/validator.go @@ -19,6 +19,7 @@ import ( "net" "reflect" "regexp" + "slices" "strconv" "strings" @@ -30,7 +31,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8svalidation "k8s.io/apimachinery/pkg/util/validation" - libapi "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" calicoconversion "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/conversion" "github.com/projectcalico/calico/libcalico-go/lib/errors" "github.com/projectcalico/calico/libcalico-go/lib/names" @@ -143,7 +144,7 @@ var ( // Validate is used to validate the supplied structure according to the // registered field and structure validators. -func Validate(current interface{}) error { +func Validate(current any) error { // Perform field-only validation first, that way the struct validators can assume // individual fields are valid format. if err := validate.Struct(current); err != nil { @@ -244,17 +245,17 @@ func init() { registerStructValidator(validate, validateProtoPort, api.ProtoPort{}) registerStructValidator(validate, validatePort, numorstring.Port{}) registerStructValidator(validate, validateEndpointPort, api.EndpointPort{}) - registerStructValidator(validate, validateWorkloadEndpointPort, libapi.WorkloadEndpointPort{}) - registerStructValidator(validate, validateIPNAT, libapi.IPNAT{}) + registerStructValidator(validate, validateWorkloadEndpointPort, internalapi.WorkloadEndpointPort{}) + registerStructValidator(validate, validateIPNAT, internalapi.IPNAT{}) registerStructValidator(validate, validateICMPFields, api.ICMPFields{}) registerStructValidator(validate, validateIPPoolSpec, api.IPPoolSpec{}) - registerStructValidator(validate, validateNodeSpec, libapi.NodeSpec{}) - registerStructValidator(validate, validateIPAMConfigSpec, libapi.IPAMConfigSpec{}) + registerStructValidator(validate, validateNodeSpec, internalapi.NodeSpec{}) + registerStructValidator(validate, validateIPAMConfigSpec, api.IPAMConfigurationSpec{}) registerStructValidator(validate, validateObjectMeta, metav1.ObjectMeta{}) registerStructValidator(validate, validateTier, api.Tier{}) registerStructValidator(validate, validateHTTPRule, api.HTTPMatch{}) registerStructValidator(validate, validateFelixConfigSpec, api.FelixConfigurationSpec{}) - registerStructValidator(validate, validateWorkloadEndpointSpec, libapi.WorkloadEndpointSpec{}) + registerStructValidator(validate, validateWorkloadEndpointSpec, internalapi.WorkloadEndpointSpec{}) registerStructValidator(validate, validateHostEndpointSpec, api.HostEndpointSpec{}) registerStructValidator(validate, validateRule, api.Rule{}) registerStructValidator(validate, validateEntityRule, api.EntityRule{}) @@ -272,7 +273,7 @@ func init() { registerStructValidator(validate, validateRouteTableIDRange, api.RouteTableIDRange{}) registerStructValidator(validate, validateRouteTableRange, api.RouteTableRange{}) registerStructValidator(validate, validateBGPConfigurationSpec, api.BGPConfigurationSpec{}) - registerStructValidator(validate, validateBlockAffinitySpec, libapi.BlockAffinitySpec{}) + registerStructValidator(validate, validateBlockAffinitySpec, api.BlockAffinitySpec{}) registerStructValidator(validate, validateHealthTimeoutOverride, api.HealthTimeoutOverride{}) } @@ -286,8 +287,8 @@ func reason(r string) string { // extractReason extracts the error reason from the field tag in a validator // field error (if there is one). func extractReason(e validator.FieldError) string { - if strings.HasPrefix(e.Tag(), reasonString) { - return strings.TrimPrefix(e.Tag(), reasonString) + if after, ok := strings.CutPrefix(e.Tag(), reasonString); ok { + return after } return fmt.Sprintf("%sfailed to validate Field: %s because of Tag: %s ", reasonString, @@ -302,7 +303,7 @@ func registerFieldValidator(key string, fn validator.Func) { validate.RegisterValidation(key, fn) } -func registerStructValidator(validator *validator.Validate, fn validator.StructLevelFunc, t ...interface{}) { +func registerStructValidator(validator *validator.Validate, fn validator.StructLevelFunc, t ...any) { validator.RegisterStructValidation(fn, t...) } @@ -797,7 +798,7 @@ func validateKeyValueList(fl validator.FieldLevel) bool { return true } - for _, item := range strings.Split(n, ",") { + for item := range strings.SplitSeq(n, ",") { if item == "" { // Accept empty items (e.g tailing ",") continue @@ -924,7 +925,7 @@ func validatePort(structLevel validator.StructLevel) { } func validateIPNAT(structLevel validator.StructLevel) { - i := structLevel.Current().Interface().(libapi.IPNAT) + i := structLevel.Current().Interface().(internalapi.IPNAT) log.Debugf("Internal IP: %s; External IP: %s", i.InternalIP, i.ExternalIP) iip, _, err := cnet.ParseCIDROrIP(i.InternalIP) @@ -1033,7 +1034,7 @@ func validateFelixConfigSpec(structLevel validator.StructLevel) { } func validateWorkloadEndpointSpec(structLevel validator.StructLevel) { - w := structLevel.Current().Interface().(libapi.WorkloadEndpointSpec) + w := structLevel.Current().Interface().(internalapi.WorkloadEndpointSpec) // The configured networks only support /32 (for IPv4) and /128 (for IPv6) at present. for _, netw := range w.IPNetworks { @@ -1246,13 +1247,7 @@ func validateIPPoolSpec(structLevel validator.StructLevel) { } // Check for invalid combination: Tunnel allowedUse with namespaceSelector - hasTunnelUse := false - for _, use := range pool.AllowedUses { - if use == api.IPPoolAllowedUseTunnel { - hasTunnelUse = true - break - } - } + hasTunnelUse := slices.Contains(pool.AllowedUses, api.IPPoolAllowedUseTunnel) if hasTunnelUse && pool.NamespaceSelector != "" { structLevel.ReportError(reflect.ValueOf(pool.NamespaceSelector), @@ -1261,7 +1256,6 @@ func validateIPPoolSpec(structLevel validator.StructLevel) { // Enhanced validation for NodeSelector based on Calico selector reference if pool.NodeSelector != "" { - // Check for invalid global() selector in nodeSelector context if strings.Contains(pool.NodeSelector, "global(") { structLevel.ReportError(reflect.ValueOf(pool.NodeSelector), @@ -1271,13 +1265,11 @@ func validateIPPoolSpec(structLevel validator.StructLevel) { // Enhanced validation for NamespaceSelector based on Calico selector reference if pool.NamespaceSelector != "" { - // Check for invalid global() selector in namespaceSelector context if strings.Contains(pool.NamespaceSelector, "global(") { structLevel.ReportError(reflect.ValueOf(pool.NamespaceSelector), "IPpool.NamespaceSelector", "", reason("global() selector is not valid for IPPool namespaceSelector - use all() instead"), "") } - } } @@ -1460,7 +1452,7 @@ func validateEntityRule(structLevel validator.StructLevel) { } func validateIPAMConfigSpec(structLevel validator.StructLevel) { - ics := structLevel.Current().Interface().(libapi.IPAMConfigSpec) + ics := structLevel.Current().Interface().(api.IPAMConfigurationSpec) if ics.MaxBlocksPerHost < 0 { structLevel.ReportError(reflect.ValueOf(ics.MaxBlocksPerHost), "MaxBlocksPerHost", "", @@ -1469,10 +1461,10 @@ func validateIPAMConfigSpec(structLevel validator.StructLevel) { } func validateNodeSpec(structLevel validator.StructLevel) { - ns := structLevel.Current().Interface().(libapi.NodeSpec) + ns := structLevel.Current().Interface().(internalapi.NodeSpec) if ns.BGP != nil { - if reflect.DeepEqual(*ns.BGP, libapi.NodeBGPSpec{}) { + if reflect.DeepEqual(*ns.BGP, internalapi.NodeBGPSpec{}) { structLevel.ReportError(reflect.ValueOf(ns.BGP), "BGP", "", reason("Spec.BGP should not be empty"), "") } @@ -1607,7 +1599,7 @@ func validateEndpointPort(structLevel validator.StructLevel) { } func validateWorkloadEndpointPort(structLevel validator.StructLevel) { - port := structLevel.Current().Interface().(libapi.WorkloadEndpointPort) + port := structLevel.Current().Interface().(internalapi.WorkloadEndpointPort) if !port.Protocol.SupportsPorts() { structLevel.ReportError( @@ -1713,25 +1705,25 @@ func validateTier(structLevel validator.StructLevel) { } } - if tier.Name == names.AdminNetworkPolicyTierName { - if tier.Spec.Order == nil || *tier.Spec.Order != api.AdminNetworkPolicyTierOrder { + if tier.Name == names.KubeAdminTierName { + if tier.Spec.Order == nil || *tier.Spec.Order != api.KubeAdminTierOrder { structLevel.ReportError( reflect.ValueOf(tier.Spec.Order), "TierSpec.Order", "", - reason(fmt.Sprintf("adminnetworkpolicy tier order must be %v", api.AdminNetworkPolicyTierOrder)), + reason(fmt.Sprintf("kube-admin tier order must be %v", api.KubeAdminTierOrder)), "", ) } } - if tier.Name == names.BaselineAdminNetworkPolicyTierName { - if tier.Spec.Order == nil || *tier.Spec.Order != api.BaselineAdminNetworkPolicyTierOrder { + if tier.Name == names.KubeBaselineTierName { + if tier.Spec.Order == nil || *tier.Spec.Order != api.KubeBaselineTierOrder { structLevel.ReportError( reflect.ValueOf(tier.Spec.Order), "TierSpec.Order", "", - reason(fmt.Sprintf("baselineadminnetworkpolicy tier order must be %v", api.BaselineAdminNetworkPolicyTierOrder)), + reason(fmt.Sprintf("kube-baseline tier order must be %v", api.KubeBaselineTierOrder)), "", ) } @@ -2309,8 +2301,8 @@ func validateBGPConfigurationSpec(structLevel validator.StructLevel) { } func validateBlockAffinitySpec(structLevel validator.StructLevel) { - spec := structLevel.Current().Interface().(libapi.BlockAffinitySpec) - if spec.Deleted == fmt.Sprintf("%t", true) { + spec := structLevel.Current().Interface().(api.BlockAffinitySpec) + if spec.Deleted { structLevel.ReportError(reflect.ValueOf(spec), "Spec.Deleted", "", reason("spec.Deleted cannot be set to \"true\""), "") } } diff --git a/libcalico-go/lib/validator/v3/validator_suite_test.go b/libcalico-go/lib/validator/v3/validator_suite_test.go index 4feac77752b..b39653c0aa8 100644 --- a/libcalico-go/lib/validator/v3/validator_suite_test.go +++ b/libcalico-go/lib/validator/v3/validator_suite_test.go @@ -17,16 +17,16 @@ package v3_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) func TestValidator(t *testing.T) { testutils.HookLogrusForGinkgo() - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../report/v3_validator_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "v3 Validator Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../report/v3_validator_suite.xml" + ginkgo.RunSpecs(t, "v3 Validator Suite", suiteConfig, reporterConfig) } diff --git a/libcalico-go/lib/validator/v3/validator_test.go b/libcalico-go/lib/validator/v3/validator_test.go index 5f6a18d287a..9b68ee3ebaf 100644 --- a/libcalico-go/lib/validator/v3/validator_test.go +++ b/libcalico-go/lib/validator/v3/validator_test.go @@ -15,43 +15,48 @@ package v3_test import ( + "context" "time" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/api/pkg/lib/numorstring" k8sv1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" - "github.com/projectcalico/calico/libcalico-go/lib/backend/encap" + "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" + "github.com/projectcalico/calico/libcalico-go/lib/backend" + "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/names" - v3 "github.com/projectcalico/calico/libcalico-go/lib/validator/v3" + "github.com/projectcalico/calico/libcalico-go/lib/options" + "github.com/projectcalico/calico/libcalico-go/lib/testutils" + validator "github.com/projectcalico/calico/libcalico-go/lib/validator/v3" ) func init() { // We need some pointers to ints, so just define as values here. - var Vneg1 = -1 - var V0 = 0 - var V4 = 4 - var V6 = 6 - var V128 = 128 - var V254 = 254 - var V255 = 255 - var V256 = 256 - var Vffffffff = 0xffffffff - var V100000000 = 0x100000000 - var tierOrder = float64(100.0) - var defaultTierOrder = api.DefaultTierOrder - var anpTierOrder = api.AdminNetworkPolicyTierOrder - var banpTierOrder = api.BaselineAdminNetworkPolicyTierOrder - var defaultTierBadOrder = float64(10.0) + Vneg1 := -1 + V0 := 0 + V4 := 4 + V6 := 6 + V128 := 128 + V254 := 254 + V255 := 255 + V256 := 256 + Vffffffff := 0xffffffff + V100000000 := 0x100000000 + tierOrder := float64(100.0) + defaultTierOrder := api.DefaultTierOrder + adminTierOrder := api.KubeAdminTierOrder + baselineTierOrder := api.KubeBaselineTierOrder + defaultTierBadOrder := float64(10.0) // We need pointers to bools, so define the values here. - var Vtrue = true - var Vfalse = false + Vtrue := true + Vfalse := false // Set up some values we use in various tests. ipv4_1 := "1.2.3.4" @@ -135,8 +140,8 @@ func init() { // Perform validation on error messages from validator DescribeTable("Validator errors", - func(input interface{}, e string) { - err := v3.Validate(input) + func(input any, e string) { + err := validator.Validate(input) Expect(err).NotTo(BeNil()) Expect(err.Error()).To(Equal(e)) }, @@ -157,12 +162,12 @@ func init() { // scenarios. This does not test precise error strings - but does cover a lot of the validation // code paths. DescribeTable("Validator", - func(input interface{}, valid bool) { + func(input any, valid bool) { if valid { - Expect(v3.Validate(input)).NotTo(HaveOccurred(), + Expect(validator.Validate(input)).NotTo(HaveOccurred(), "expected value to be valid") } else { - Expect(v3.Validate(input)).To(HaveOccurred(), + Expect(validator.Validate(input)).To(HaveOccurred(), "expected value to be invalid") } }, @@ -176,51 +181,51 @@ func init() { Entry("should reject rule with no action", api.Rule{}, false), // (API model) EndpointPorts. - Entry("should accept EndpointPort with tcp protocol", libapiv3.WorkloadEndpointPort{ + Entry("should accept EndpointPort with tcp protocol", internalapi.WorkloadEndpointPort{ Name: "a-valid-port", Protocol: protoTCP, Port: 1234, }, true), - Entry("should accept EndpointPort with udp protocol", libapiv3.WorkloadEndpointPort{ + Entry("should accept EndpointPort with udp protocol", internalapi.WorkloadEndpointPort{ Name: "a-valid-port", Protocol: protoUDP, Port: 1234, }, true), - Entry("should accept EndpointPort with sctp protocol", libapiv3.WorkloadEndpointPort{ + Entry("should accept EndpointPort with sctp protocol", internalapi.WorkloadEndpointPort{ Name: "a-valid-port", Protocol: protoSCTP, Port: 1234, }, true), - Entry("should reject EndpointPort with empty name", libapiv3.WorkloadEndpointPort{ + Entry("should reject EndpointPort with empty name", internalapi.WorkloadEndpointPort{ Name: "", Protocol: protoUDP, Port: 1234, }, false), - Entry("should accept EndpointPort with empty name but HostPort specified", libapiv3.WorkloadEndpointPort{ + Entry("should accept EndpointPort with empty name but HostPort specified", internalapi.WorkloadEndpointPort{ Name: "", Protocol: protoUDP, Port: 1234, HostPort: 2345, }, true), - Entry("should reject EndpointPort with no protocol", libapiv3.WorkloadEndpointPort{ + Entry("should reject EndpointPort with no protocol", internalapi.WorkloadEndpointPort{ Name: "a-valid-port", Port: 1234, }, false), - Entry("should reject EndpointPort with numeric protocol", libapiv3.WorkloadEndpointPort{ + Entry("should reject EndpointPort with numeric protocol", internalapi.WorkloadEndpointPort{ Name: "a-valid-port", Protocol: protoNumeric, Port: 1234, }, false), - Entry("should reject EndpointPort with no port", libapiv3.WorkloadEndpointPort{ + Entry("should reject EndpointPort with no port", internalapi.WorkloadEndpointPort{ Name: "a-valid-port", Protocol: protoTCP, }, false), // (API) WorkloadEndpointSpec. Entry("should accept WorkloadEndpointSpec with a port (m)", - libapiv3.WorkloadEndpointSpec{ + internalapi.WorkloadEndpointSpec{ InterfaceName: "eth0", - Ports: []libapiv3.WorkloadEndpointPort{ + Ports: []internalapi.WorkloadEndpointPort{ { Name: "a-valid-port", Protocol: protoTCP, @@ -231,9 +236,9 @@ func init() { true, ), Entry("should reject WorkloadEndpointSpec with an unnamed port and no host mapping (m)", - libapiv3.WorkloadEndpointSpec{ + internalapi.WorkloadEndpointSpec{ InterfaceName: "eth0", - Ports: []libapiv3.WorkloadEndpointPort{ + Ports: []internalapi.WorkloadEndpointPort{ { Protocol: protoTCP, Port: 1234, @@ -243,9 +248,9 @@ func init() { false, ), Entry("should accept WorkloadEndpointSpec with name-clashing ports (m)", - libapiv3.WorkloadEndpointSpec{ + internalapi.WorkloadEndpointSpec{ InterfaceName: "eth0", - Ports: []libapiv3.WorkloadEndpointPort{ + Ports: []internalapi.WorkloadEndpointPort{ { Name: "a-valid-port", Protocol: protoTCP, @@ -261,9 +266,9 @@ func init() { true, ), Entry("should accept WorkloadEndpointSpec with an unnamed port and a host port (m)", - libapiv3.WorkloadEndpointSpec{ + internalapi.WorkloadEndpointSpec{ InterfaceName: "eth0", - Ports: []libapiv3.WorkloadEndpointPort{ + Ports: []internalapi.WorkloadEndpointPort{ { Protocol: protoTCP, Port: 1234, @@ -274,9 +279,9 @@ func init() { true, ), Entry("should reject WorkloadEndpointSpec with a port with an invalid host IP (m)", - libapiv3.WorkloadEndpointSpec{ + internalapi.WorkloadEndpointSpec{ InterfaceName: "eth0", - Ports: []libapiv3.WorkloadEndpointPort{ + Ports: []internalapi.WorkloadEndpointPort{ { Protocol: protoTCP, Port: 1234, @@ -288,14 +293,14 @@ func init() { false, ), Entry("should reject WorkloadEndpointSpec with an invalid source spoofing config (m)", - libapiv3.WorkloadEndpointSpec{ + internalapi.WorkloadEndpointSpec{ InterfaceName: "eth0", AllowSpoofedSourcePrefixes: []string{"10.abcd"}, }, false, ), Entry("should accept WorkloadEndpointSpec with an ip or prefix in the source spoofing config (m)", - libapiv3.WorkloadEndpointSpec{ + internalapi.WorkloadEndpointSpec{ InterfaceName: "eth0", AllowSpoofedSourcePrefixes: []string{"10.0.0.1", "192.168.0.0/16"}, }, @@ -701,11 +706,11 @@ func init() { ), // (API) Interface. - Entry("should accept a valid interface", libapiv3.WorkloadEndpointSpec{InterfaceName: "Valid_Iface.0-9"}, true), - Entry("should reject an interface that is too long", libapiv3.WorkloadEndpointSpec{InterfaceName: "interfaceTooLong"}, false), - Entry("should reject & in an interface", libapiv3.WorkloadEndpointSpec{InterfaceName: "Invalid&Intface"}, false), - Entry("should reject # in an interface", libapiv3.WorkloadEndpointSpec{InterfaceName: "Invalid#Intface"}, false), - Entry("should reject : in an interface", libapiv3.WorkloadEndpointSpec{InterfaceName: "Invalid:Intface"}, false), + Entry("should accept a valid interface", internalapi.WorkloadEndpointSpec{InterfaceName: "Valid_Iface.0-9"}, true), + Entry("should reject an interface that is too long", internalapi.WorkloadEndpointSpec{InterfaceName: "interfaceTooLong"}, false), + Entry("should reject & in an interface", internalapi.WorkloadEndpointSpec{InterfaceName: "Invalid&Intface"}, false), + Entry("should reject # in an interface", internalapi.WorkloadEndpointSpec{InterfaceName: "Invalid#Intface"}, false), + Entry("should reject : in an interface", internalapi.WorkloadEndpointSpec{InterfaceName: "Invalid:Intface"}, false), // (API) FelixConfiguration. Entry("should accept a valid IptablesBackend value 'Legacy'", api.FelixConfigurationSpec{IptablesBackend: &iptablesBackendLegacy}, true), @@ -804,10 +809,10 @@ func init() { Entry("should reject an invalid MTUIfacePattern value '*'", api.FelixConfigurationSpec{MTUIfacePattern: "*"}, false), Entry("should accept a valid MTUIfacePattern value 'eth.*'", api.FelixConfigurationSpec{MTUIfacePattern: "eth.*"}, true), - Entry("should allow HealthTimeoutOverride 0", api.FelixConfigurationSpec{HealthTimeoutOverrides: []api.HealthTimeoutOverride{{Name: "Valid", Timeout: metav1.Duration{Duration: 0}}}}, true), - Entry("should reject HealthTimeoutOverride -1", api.FelixConfigurationSpec{HealthTimeoutOverrides: []api.HealthTimeoutOverride{{Name: "Valid", Timeout: metav1.Duration{Duration: -1}}}}, false), - Entry("should reject HealthTimeoutOverride with bad name", api.FelixConfigurationSpec{HealthTimeoutOverrides: []api.HealthTimeoutOverride{{Name: "%", Timeout: metav1.Duration{Duration: 10}}}}, false), - Entry("should reject HealthTimeoutOverride with no name", api.FelixConfigurationSpec{HealthTimeoutOverrides: []api.HealthTimeoutOverride{{Name: "", Timeout: metav1.Duration{Duration: 10}}}}, false), + Entry("should allow HealthTimeoutOverride 0", api.FelixConfigurationSpec{HealthTimeoutOverrides: []api.HealthTimeoutOverride{{Name: "Valid", Timeout: v1.Duration{Duration: 0}}}}, true), + Entry("should reject HealthTimeoutOverride -1", api.FelixConfigurationSpec{HealthTimeoutOverrides: []api.HealthTimeoutOverride{{Name: "Valid", Timeout: v1.Duration{Duration: -1}}}}, false), + Entry("should reject HealthTimeoutOverride with bad name", api.FelixConfigurationSpec{HealthTimeoutOverrides: []api.HealthTimeoutOverride{{Name: "%", Timeout: v1.Duration{Duration: 10}}}}, false), + Entry("should reject HealthTimeoutOverride with no name", api.FelixConfigurationSpec{HealthTimeoutOverrides: []api.HealthTimeoutOverride{{Name: "", Timeout: v1.Duration{Duration: 10}}}}, false), // (API) Protocol Entry("should accept protocol TCP", protocolFromString("TCP"), true), @@ -829,102 +834,102 @@ func init() { // (API) IPNAT Entry("should accept valid IPNAT IPv4", - libapiv3.IPNAT{ + internalapi.IPNAT{ InternalIP: ipv4_1, ExternalIP: ipv4_2, }, true), Entry("should accept valid IPNAT IPv6", - libapiv3.IPNAT{ + internalapi.IPNAT{ InternalIP: ipv6_1, ExternalIP: ipv6_2, }, true), Entry("should reject IPNAT mixed IPv4 (int) and IPv6 (ext)", - libapiv3.IPNAT{ + internalapi.IPNAT{ InternalIP: ipv4_1, ExternalIP: ipv6_1, }, false), Entry("should reject IPNAT mixed IPv6 (int) and IPv4 (ext)", - libapiv3.IPNAT{ + internalapi.IPNAT{ InternalIP: ipv6_1, ExternalIP: ipv4_1, }, false), // (API) WorkloadEndpointSpec Entry("should accept workload endpoint with interface only", - libapiv3.WorkloadEndpointSpec{ + internalapi.WorkloadEndpointSpec{ InterfaceName: "cali012371237", }, true), Entry("should accept workload endpoint with networks and no nats", - libapiv3.WorkloadEndpointSpec{ + internalapi.WorkloadEndpointSpec{ InterfaceName: "cali012371237", IPNetworks: []string{netv4_1, netv4_2, netv6_1, netv6_2}, }, true), Entry("should accept workload endpoint with IPv4 NAT covered by network", - libapiv3.WorkloadEndpointSpec{ + internalapi.WorkloadEndpointSpec{ InterfaceName: "cali012371237", IPNetworks: []string{netv4_1}, - IPNATs: []libapiv3.IPNAT{{InternalIP: ipv4_1, ExternalIP: ipv4_2}}, + IPNATs: []internalapi.IPNAT{{InternalIP: ipv4_1, ExternalIP: ipv4_2}}, }, true), Entry("should accept workload endpoint with IPv6 NAT covered by network", - libapiv3.WorkloadEndpointSpec{ + internalapi.WorkloadEndpointSpec{ InterfaceName: "cali012371237", IPNetworks: []string{netv6_1}, - IPNATs: []libapiv3.IPNAT{{InternalIP: ipv6_1, ExternalIP: ipv6_2}}, + IPNATs: []internalapi.IPNAT{{InternalIP: ipv6_1, ExternalIP: ipv6_2}}, }, true), Entry("should accept workload endpoint with IPv4 and IPv6 NAT covered by network", - libapiv3.WorkloadEndpointSpec{ + internalapi.WorkloadEndpointSpec{ InterfaceName: "cali012371237", IPNetworks: []string{netv4_1, netv6_1}, - IPNATs: []libapiv3.IPNAT{ + IPNATs: []internalapi.IPNAT{ {InternalIP: ipv4_1, ExternalIP: ipv4_2}, {InternalIP: ipv6_1, ExternalIP: ipv6_2}, }, }, true), Entry("should accept workload endpoint with mixed-case ContainerID", - libapiv3.WorkloadEndpointSpec{ + internalapi.WorkloadEndpointSpec{ InterfaceName: "cali012371237", ContainerID: "Cath01234-G", }, true), - Entry("should reject workload endpoint with no config", libapiv3.WorkloadEndpointSpec{}, false), + Entry("should reject workload endpoint with no config", internalapi.WorkloadEndpointSpec{}, false), Entry("should reject workload endpoint with IPv4 networks that contain >1 address", - libapiv3.WorkloadEndpointSpec{ + internalapi.WorkloadEndpointSpec{ InterfaceName: "cali012371237", IPNetworks: []string{netv4_3}, }, false), Entry("should reject workload endpoint with IPv6 networks that contain >1 address", - libapiv3.WorkloadEndpointSpec{ + internalapi.WorkloadEndpointSpec{ InterfaceName: "cali012371237", IPNetworks: []string{netv6_3}, }, false), Entry("should reject workload endpoint with nats and no networks", - libapiv3.WorkloadEndpointSpec{ + internalapi.WorkloadEndpointSpec{ InterfaceName: "cali012371237", - IPNATs: []libapiv3.IPNAT{{InternalIP: ipv4_2, ExternalIP: ipv4_1}}, + IPNATs: []internalapi.IPNAT{{InternalIP: ipv4_2, ExternalIP: ipv4_1}}, }, false), Entry("should reject workload endpoint with IPv4 NAT not covered by network", - libapiv3.WorkloadEndpointSpec{ + internalapi.WorkloadEndpointSpec{ InterfaceName: "cali012371237", IPNetworks: []string{netv4_1}, - IPNATs: []libapiv3.IPNAT{{InternalIP: ipv4_2, ExternalIP: ipv4_1}}, + IPNATs: []internalapi.IPNAT{{InternalIP: ipv4_2, ExternalIP: ipv4_1}}, }, false), Entry("should reject workload endpoint with IPv6 NAT not covered by network", - libapiv3.WorkloadEndpointSpec{ + internalapi.WorkloadEndpointSpec{ InterfaceName: "cali012371237", IPNetworks: []string{netv6_1}, - IPNATs: []libapiv3.IPNAT{{InternalIP: ipv6_2, ExternalIP: ipv6_1}}, + IPNATs: []internalapi.IPNAT{{InternalIP: ipv6_2, ExternalIP: ipv6_1}}, }, false), Entry("should reject workload endpoint containerID that starts with a dash", - libapiv3.WorkloadEndpointSpec{ + internalapi.WorkloadEndpointSpec{ InterfaceName: "cali0134", ContainerID: "-abcdefg", }, false), Entry("should reject workload endpoint containerID that ends with a dash", - libapiv3.WorkloadEndpointSpec{ + internalapi.WorkloadEndpointSpec{ InterfaceName: "cali0134", ContainerID: "abcdeSg-", }, false), Entry("should reject workload endpoint containerID that contains a period", - libapiv3.WorkloadEndpointSpec{ + internalapi.WorkloadEndpointSpec{ InterfaceName: "cali0134", ContainerID: "abcde-j.g", }, false), @@ -974,15 +979,18 @@ func init() { // (API) IPPool Entry("should accept IP pool with IPv4 CIDR /26", - api.IPPool{ObjectMeta: v1.ObjectMeta{Name: "pool.name"}, - Spec: api.IPPoolSpec{CIDR: netv4_3}, + api.IPPool{ + ObjectMeta: v1.ObjectMeta{Name: "pool.name"}, + Spec: api.IPPoolSpec{CIDR: netv4_3}, }, true), Entry("should accept IP pool with IPv4 CIDR /10", - api.IPPool{ObjectMeta: v1.ObjectMeta{Name: "pool.name"}, - Spec: api.IPPoolSpec{CIDR: netv4_4}, + api.IPPool{ + ObjectMeta: v1.ObjectMeta{Name: "pool.name"}, + Spec: api.IPPoolSpec{CIDR: netv4_4}, }, true), Entry("should accept IP pool with IPv6 CIDR /122", - api.IPPool{ObjectMeta: v1.ObjectMeta{Name: "pool.name"}, + api.IPPool{ + ObjectMeta: v1.ObjectMeta{Name: "pool.name"}, Spec: api.IPPoolSpec{ CIDR: netv6_3, IPIPMode: api.IPIPModeNever, @@ -990,7 +998,8 @@ func init() { }, }, true), Entry("should accept IP pool with IPv6 CIDR /10", - api.IPPool{ObjectMeta: v1.ObjectMeta{Name: "pool.name"}, + api.IPPool{ + ObjectMeta: v1.ObjectMeta{Name: "pool.name"}, Spec: api.IPPoolSpec{ CIDR: netv6_4, IPIPMode: api.IPIPModeNever, @@ -1002,7 +1011,8 @@ func init() { ObjectMeta: v1.ObjectMeta{Name: "pool.name"}, Spec: api.IPPoolSpec{ CIDR: netv4_5, - Disabled: true}, + Disabled: true, + }, }, true), Entry("should accept a disabled IP pool with IPv6 CIDR /128", api.IPPool{ @@ -1011,7 +1021,8 @@ func init() { CIDR: netv6_1, IPIPMode: api.IPIPModeNever, VXLANMode: api.VXLANModeNever, - Disabled: true}, + Disabled: true, + }, }, true), Entry("should reject IP pool with IPv4 CIDR /27", api.IPPool{ObjectMeta: v1.ObjectMeta{Name: "pool.name"}, Spec: api.IPPoolSpec{CIDR: netv4_5}}, false), Entry("should reject IP pool with IPv6 CIDR /128", api.IPPool{ObjectMeta: v1.ObjectMeta{Name: "pool.name"}, Spec: api.IPPoolSpec{CIDR: netv6_1}}, false), @@ -1306,16 +1317,6 @@ func init() { Entry("should reject VXLAN mode never", api.IPPoolSpec{CIDR: "1.2.3.0/24", VXLANMode: "never"}, false), Entry("should reject VXLAN mode badVal", api.IPPoolSpec{CIDR: "1.2.3.0/24", VXLANMode: "badVal"}, false), - // (API) IPIP APIv1 backwards compatibility. Read-only field IPIP - Entry("should accept a nil IPIP field", api.IPPoolSpec{CIDR: "1.2.3.0/24", IPIPMode: "Never", IPIP: nil}, true), - Entry("should accept it when the IPIP field is not specified", api.IPPoolSpec{CIDR: "1.2.3.0/24", IPIPMode: "Never"}, true), - Entry("should reject a non-nil IPIP field", api.IPPoolSpec{CIDR: "1.2.3.0/24", IPIPMode: "Never", IPIP: &api.IPIPConfiguration{Enabled: true, Mode: encap.Always}}, false), - - // (API) NatOutgoing APIv1 backwards compatibility. Read-only field NatOutgoingV1 - Entry("should accept NATOutgoingV1 field set to true", api.IPPoolSpec{CIDR: "1.2.3.0/24", IPIPMode: "Never", NATOutgoingV1: false}, true), - Entry("should accept it when the NATOutgoingV1 field is not specified", api.IPPoolSpec{CIDR: "1.2.3.0/24", IPIPMode: "Never"}, true), - Entry("should reject NATOutgoingV1 field set to true", api.IPPoolSpec{CIDR: "1.2.3.0/24", IPIPMode: "Never", NATOutgoingV1: true}, false), - // (API) ICMPFields Entry("should accept ICMP with no config", api.ICMPFields{}, true), Entry("should accept ICMP with type with min value", api.ICMPFields{Type: &V0}, true), @@ -2196,37 +2197,37 @@ func init() { }, true), // (API) NodeSpec - Entry("should accept node with IPv4 BGP", libapiv3.NodeSpec{BGP: &libapiv3.NodeBGPSpec{IPv4Address: netv4_1}}, true), - Entry("should accept node with IPv6 BGP", libapiv3.NodeSpec{BGP: &libapiv3.NodeBGPSpec{IPv6Address: netv6_1}}, true), - Entry("should accept node with tunnel IP in BGP", libapiv3.NodeSpec{BGP: &libapiv3.NodeBGPSpec{IPv4IPIPTunnelAddr: "10.0.0.1"}}, true), - Entry("should accept node with no BGP", libapiv3.NodeSpec{}, true), - Entry("should reject node with an empty BGP", libapiv3.NodeSpec{BGP: &libapiv3.NodeBGPSpec{}}, false), - Entry("should reject node with IPv6 address in IPv4 field", libapiv3.NodeSpec{BGP: &libapiv3.NodeBGPSpec{IPv4Address: netv6_1}}, false), - Entry("should reject node with IPv4 address in IPv6 field", libapiv3.NodeSpec{BGP: &libapiv3.NodeBGPSpec{IPv6Address: netv4_1}}, false), - Entry("should reject node with bad RR cluster ID #1", libapiv3.NodeSpec{BGP: &libapiv3.NodeBGPSpec{ + Entry("should accept node with IPv4 BGP", internalapi.NodeSpec{BGP: &internalapi.NodeBGPSpec{IPv4Address: netv4_1}}, true), + Entry("should accept node with IPv6 BGP", internalapi.NodeSpec{BGP: &internalapi.NodeBGPSpec{IPv6Address: netv6_1}}, true), + Entry("should accept node with tunnel IP in BGP", internalapi.NodeSpec{BGP: &internalapi.NodeBGPSpec{IPv4IPIPTunnelAddr: "10.0.0.1"}}, true), + Entry("should accept node with no BGP", internalapi.NodeSpec{}, true), + Entry("should reject node with an empty BGP", internalapi.NodeSpec{BGP: &internalapi.NodeBGPSpec{}}, false), + Entry("should reject node with IPv6 address in IPv4 field", internalapi.NodeSpec{BGP: &internalapi.NodeBGPSpec{IPv4Address: netv6_1}}, false), + Entry("should reject node with IPv4 address in IPv6 field", internalapi.NodeSpec{BGP: &internalapi.NodeBGPSpec{IPv6Address: netv4_1}}, false), + Entry("should reject node with bad RR cluster ID #1", internalapi.NodeSpec{BGP: &internalapi.NodeBGPSpec{ IPv4Address: netv4_1, RouteReflectorClusterID: "abcdef", }}, false), - Entry("should reject node with bad RR cluster ID #2", libapiv3.NodeSpec{BGP: &libapiv3.NodeBGPSpec{ + Entry("should reject node with bad RR cluster ID #2", internalapi.NodeSpec{BGP: &internalapi.NodeBGPSpec{ IPv4Address: netv4_1, RouteReflectorClusterID: "300.34.3.1", }}, false), - Entry("should accept node with good RR cluster ID", libapiv3.NodeSpec{BGP: &libapiv3.NodeBGPSpec{ + Entry("should accept node with good RR cluster ID", internalapi.NodeSpec{BGP: &internalapi.NodeBGPSpec{ IPv4Address: netv4_1, RouteReflectorClusterID: "245.0.0.1", }}, true), // Wireguard config field tests - Entry("should allow valid Wireguard public-key", libapiv3.NodeStatus{ + Entry("should allow valid Wireguard public-key", internalapi.NodeStatus{ WireguardPublicKey: "jlkVyQYooZYzI2wFfNhSZez5eWh44yfq1wKVjLvSXgY=", }, true), - Entry("should allow valid IP address on Wireguard config", libapiv3.NodeSpec{Wireguard: &libapiv3.NodeWireguardSpec{ + Entry("should allow valid IP address on Wireguard config", internalapi.NodeSpec{Wireguard: &internalapi.NodeWireguardSpec{ InterfaceIPv4Address: ipv4_1, }}, true), - Entry("should reject invalid IP address on Wireguard config", libapiv3.NodeSpec{Wireguard: &libapiv3.NodeWireguardSpec{ + Entry("should reject invalid IP address on Wireguard config", internalapi.NodeSpec{Wireguard: &internalapi.NodeWireguardSpec{ InterfaceIPv4Address: "foo.bar", }}, false), - Entry("should reject invalid Wireguard public-key", libapiv3.NodeStatus{ + Entry("should reject invalid Wireguard public-key", internalapi.NodeStatus{ WireguardPublicKey: "foobar", }, false), @@ -2664,67 +2665,80 @@ func init() { ObjectMeta: v1.ObjectMeta{Name: "foo"}, Spec: api.TierSpec{ Order: &tierOrder, - }}, true), + }, + }, true), Entry("Tier: valid name with dash", &api.Tier{ ObjectMeta: v1.ObjectMeta{Name: "fo-o"}, Spec: api.TierSpec{ Order: &tierOrder, - }}, true), + }, + }, true), Entry("Tier: disallow dot in name", &api.Tier{ ObjectMeta: v1.ObjectMeta{Name: "fo.o"}, Spec: api.TierSpec{ Order: &tierOrder, - }}, false), + }, + }, false), Entry("Tier: allow valid name of 63 chars", &api.Tier{ ObjectMeta: v1.ObjectMeta{Name: string(value63)}, Spec: api.TierSpec{ Order: &tierOrder, - }}, true), + }, + }, true), Entry("Tier: disallow a name of 64 chars", &api.Tier{ ObjectMeta: v1.ObjectMeta{Name: string(value64)}, Spec: api.TierSpec{ Order: &tierOrder, - }}, false), + }, + }, false), Entry("Tier: disallow other chars", &api.Tier{ ObjectMeta: v1.ObjectMeta{Name: "t~!s.h.i.ng"}, Spec: api.TierSpec{ Order: &tierOrder, - }}, false), + }, + }, false), Entry("Tier: disallow default tier with an invalid order", &api.Tier{ ObjectMeta: v1.ObjectMeta{Name: names.DefaultTierName}, Spec: api.TierSpec{ Order: &defaultTierBadOrder, - }}, false), + }, + }, false), Entry("Tier: allow default tier with the predefined order", &api.Tier{ ObjectMeta: v1.ObjectMeta{Name: names.DefaultTierName}, Spec: api.TierSpec{ Order: &defaultTierOrder, - }}, true), - Entry("Tier: disallow adminnetworkpolicy tier with an invalid order", &api.Tier{ - ObjectMeta: v1.ObjectMeta{Name: names.AdminNetworkPolicyTierName}, + }, + }, true), + Entry("Tier: disallow kube-admin tier with an invalid order", &api.Tier{ + ObjectMeta: v1.ObjectMeta{Name: names.KubeAdminTierName}, Spec: api.TierSpec{ Order: &defaultTierBadOrder, - }}, false), - Entry("Tier: allow adminnetworkpolicy tier with the predefined order", &api.Tier{ - ObjectMeta: v1.ObjectMeta{Name: names.AdminNetworkPolicyTierName}, + }, + }, false), + Entry("Tier: allow kube-admin tier with the predefined order", &api.Tier{ + ObjectMeta: v1.ObjectMeta{Name: names.KubeAdminTierName}, Spec: api.TierSpec{ - Order: &anpTierOrder, - }}, true), - Entry("Tier: disallow baselineadminnetworkpolicy tier with an invalid order", &api.Tier{ - ObjectMeta: v1.ObjectMeta{Name: names.BaselineAdminNetworkPolicyTierName}, + Order: &adminTierOrder, + }, + }, true), + Entry("Tier: disallow kube-baseline tier with an invalid order", &api.Tier{ + ObjectMeta: v1.ObjectMeta{Name: names.KubeBaselineTierName}, Spec: api.TierSpec{ Order: &defaultTierBadOrder, - }}, false), - Entry("Tier: allow baselineadminnetworkpolicy tier with the predefined order", &api.Tier{ - ObjectMeta: v1.ObjectMeta{Name: names.BaselineAdminNetworkPolicyTierName}, + }, + }, false), + Entry("Tier: allow kube-baseline tier with the predefined order", &api.Tier{ + ObjectMeta: v1.ObjectMeta{Name: names.KubeBaselineTierName}, Spec: api.TierSpec{ - Order: &banpTierOrder, - }}, true), + Order: &baselineTierOrder, + }, + }, true), Entry("Tier: allow a tier with a valid order", &api.Tier{ ObjectMeta: v1.ObjectMeta{Name: "platform"}, Spec: api.TierSpec{ Order: &tierOrder, - }}, true), + }, + }, true), // NetworkPolicySpec Types field checks. Entry("allow valid name", &api.NetworkPolicy{ObjectMeta: v1.ObjectMeta{Name: "thing"}}, true), @@ -3697,15 +3711,15 @@ func init() { }, false), // Block Affinities validation in BlockAffinitySpec - Entry("should accept non-deleted block affinities", libapiv3.BlockAffinitySpec{ - Deleted: "false", + Entry("should accept non-deleted block affinities", api.BlockAffinitySpec{ + Deleted: false, State: "confirmed", CIDR: "10.0.0.0/24", Node: "node-1", Type: "host", }, true), - Entry("should not accept deleted block affinities", libapiv3.BlockAffinitySpec{ - Deleted: "true", + Entry("should not accept deleted block affinities", api.BlockAffinitySpec{ + Deleted: true, State: "confirmed", CIDR: "10.0.0.0/24", Node: "node-1", @@ -3764,6 +3778,81 @@ func init() { ) } +// These tests run e2e validation against a real datastore (compared to the above tests which only verify the validator code). +// This is needed because our architecture places validation in several places, including in the datastore backend when running in +// CRD mode. Note that these tests execute a superset of the validation tests done above. +// +// TODO: Right now, these only run against Kubernetes (CRD) datastore. This is because many of the validaitons are specifically to test +// logic implemented with CEL validations on CRDs, and no equivalents exist for etcd. +var _ = testutils.E2eDatastoreDescribe("e2e validation tests", testutils.DatastoreK8s, func(config apiconfig.CalicoAPIConfig) { + BeforeEach(func() { + // Clean the datastore before each test. + bc, err := backend.NewClient(config) + Expect(err).NotTo(HaveOccurred()) + Expect(bc.Clean()).To(Succeed(), "Failed to clean datastore before test") + }) + + DescribeTable("Tier validation tests", func(tierSpec api.Tier, errStr string) { + // Create a client. + client, err := clientv3.New(config) + Expect(err).NotTo(HaveOccurred()) + + // Try to create the Tier. + _, err = client.Tiers().Create(context.Background(), &tierSpec, options.SetOptions{}) + if errStr == "" { + Expect(err).NotTo(HaveOccurred()) + } else { + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring(errStr), "Expected error message to contain substring") + } + }, + Entry("should accept an empty Tier spec", + api.Tier{ObjectMeta: v1.ObjectMeta{Name: "valid-tier"}, Spec: api.TierSpec{}}, + "", + ), + + Entry("should reject kube-baseline with wrong default action", + api.Tier{ + ObjectMeta: v1.ObjectMeta{Name: "kube-baseline"}, + Spec: api.TierSpec{DefaultAction: ptr.To(api.Deny), Order: ptr.To(api.KubeBaselineTierOrder)}, + }, + "must have default action", + ), + + Entry("should accept kube-baseline with correct default action", + api.Tier{ + ObjectMeta: v1.ObjectMeta{Name: "kube-baseline"}, + Spec: api.TierSpec{DefaultAction: ptr.To(api.Pass), Order: ptr.To(api.KubeBaselineTierOrder)}, + }, + "", + ), + + Entry("should reject kube-admin with wrong default action", + api.Tier{ + ObjectMeta: v1.ObjectMeta{Name: "kube-admin"}, + Spec: api.TierSpec{DefaultAction: ptr.To(api.Deny), Order: ptr.To(api.KubeAdminTierOrder)}, + }, + "must have default action", + ), + + Entry("should accept kube-admin with correct default action", + api.Tier{ + ObjectMeta: v1.ObjectMeta{Name: "kube-admin"}, + Spec: api.TierSpec{DefaultAction: ptr.To(api.Pass), Order: ptr.To(api.KubeAdminTierOrder)}, + }, + "", + ), + + Entry("should reject default tier with wrong default action", + api.Tier{ + ObjectMeta: v1.ObjectMeta{Name: "default"}, + Spec: api.TierSpec{DefaultAction: ptr.To(api.Pass), Order: ptr.To(api.DefaultTierOrder)}, + }, + "must have default action", + ), + ) +}) + func protocolFromString(s string) *numorstring.Protocol { p := numorstring.ProtocolFromString(s) return &p diff --git a/libcalico-go/patches/0001-Add-nullable-to-IPAM-block-allocations-field.patch b/libcalico-go/patches/0001-Add-nullable-to-IPAM-block-allocations-field.patch index 56459118346..5fcf40d34f3 100644 --- a/libcalico-go/patches/0001-Add-nullable-to-IPAM-block-allocations-field.patch +++ b/libcalico-go/patches/0001-Add-nullable-to-IPAM-block-allocations-field.patch @@ -11,7 +11,7 @@ diff --git a/libcalico-go/config/crd/crd.projectcalico.org_ipamblocks.yaml b/lib index 288b068d1d..3c3a54b4a3 100644 --- a/libcalico-go/config/crd/crd.projectcalico.org_ipamblocks.yaml +++ b/libcalico-go/config/crd/crd.projectcalico.org_ipamblocks.yaml -@@ -31,6 +31,9 @@ spec: +@@ -34,6 +34,9 @@ spec: allocations: items: type: integer diff --git a/libcalico-go/run-uts b/libcalico-go/run-uts index 1a74acfbd25..1bd572bc1f0 100755 --- a/libcalico-go/run-uts +++ b/libcalico-go/run-uts @@ -31,8 +31,8 @@ test ! -z "$go_dirs" # Run tests in random order find tests recursively (-r). echo WHAT: $WHAT echo SKIP: $SKIP -ginkgo -cover -coverpkg=${go_dirs} -r --skipPackage $SKIP $WHAT -gocovmerge $(find . -name '*.coverprofile') > combined.coverprofile +ginkgo -cover -coverpkg=${go_dirs} -r --skip-package $SKIP $WHAT +gocovmerge $(find . -name 'coverprofile.out') > combined.coverprofile echo echo '+==============+' diff --git a/manifests/README.md b/manifests/README.md index bdd80f2228a..b54176be651 100644 --- a/manifests/README.md +++ b/manifests/README.md @@ -8,7 +8,7 @@ in the repository root. To make changes to the auto-generated manifests: -1. Modify the source content in either `charts/tigera-operator/` or `charts/calico` +1. Modify the source content in either `charts/tigera-operator/` or `charts/calico` 2. Re-run code generation from the top level of this repository @@ -16,7 +16,7 @@ To make changes to the auto-generated manifests: make gen-manifests ``` -Some of these manifests are not automatically generated. To edit these, modify the manifests directly and +Some of these manifests are not automatically generated. To edit these, modify the manifests directly and commit your changes. **The following manifests are not auto generated:** - alp/istio-inject-configmap-X.yaml @@ -33,4 +33,3 @@ commit your changes. **The following manifests are not auto generated:** - ocp/01-cr-apiserver.yaml - ocp/01-cr-installation.yaml - ocp-tigera-operator-no-resource-loading.yaml -- operator-crds.yaml diff --git a/manifests/alp/istio-inject-configmap-1.1.0.yaml b/manifests/alp/istio-inject-configmap-1.1.0.yaml index ce0f9f3076a..95ddadbf251 100644 --- a/manifests/alp/istio-inject-configmap-1.1.0.yaml +++ b/manifests/alp/istio-inject-configmap-1.1.0.yaml @@ -178,7 +178,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.1.1.yaml b/manifests/alp/istio-inject-configmap-1.1.1.yaml index ff1e2faebbc..644ec879e59 100644 --- a/manifests/alp/istio-inject-configmap-1.1.1.yaml +++ b/manifests/alp/istio-inject-configmap-1.1.1.yaml @@ -178,7 +178,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.1.10.yaml b/manifests/alp/istio-inject-configmap-1.1.10.yaml index b2ba9d4d15a..70bd33ce58a 100644 --- a/manifests/alp/istio-inject-configmap-1.1.10.yaml +++ b/manifests/alp/istio-inject-configmap-1.1.10.yaml @@ -180,7 +180,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.1.11.yaml b/manifests/alp/istio-inject-configmap-1.1.11.yaml index eb42994117a..768ce565720 100644 --- a/manifests/alp/istio-inject-configmap-1.1.11.yaml +++ b/manifests/alp/istio-inject-configmap-1.1.11.yaml @@ -180,7 +180,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.1.12.yaml b/manifests/alp/istio-inject-configmap-1.1.12.yaml index baea4527cdb..364084bf92a 100644 --- a/manifests/alp/istio-inject-configmap-1.1.12.yaml +++ b/manifests/alp/istio-inject-configmap-1.1.12.yaml @@ -180,7 +180,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.1.13.yaml b/manifests/alp/istio-inject-configmap-1.1.13.yaml index b6a4ea27c6a..c56d719ce0b 100644 --- a/manifests/alp/istio-inject-configmap-1.1.13.yaml +++ b/manifests/alp/istio-inject-configmap-1.1.13.yaml @@ -180,7 +180,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.1.14.yaml b/manifests/alp/istio-inject-configmap-1.1.14.yaml index c58708057f1..066b243b4a2 100644 --- a/manifests/alp/istio-inject-configmap-1.1.14.yaml +++ b/manifests/alp/istio-inject-configmap-1.1.14.yaml @@ -180,7 +180,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.1.15.yaml b/manifests/alp/istio-inject-configmap-1.1.15.yaml index 934b5e8dfad..bed504d6368 100644 --- a/manifests/alp/istio-inject-configmap-1.1.15.yaml +++ b/manifests/alp/istio-inject-configmap-1.1.15.yaml @@ -180,7 +180,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.1.16.yaml b/manifests/alp/istio-inject-configmap-1.1.16.yaml index 9cae42073f5..ecb63248376 100644 --- a/manifests/alp/istio-inject-configmap-1.1.16.yaml +++ b/manifests/alp/istio-inject-configmap-1.1.16.yaml @@ -180,7 +180,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.1.17.yaml b/manifests/alp/istio-inject-configmap-1.1.17.yaml index b4768fe10a1..b85a2bb7e7e 100644 --- a/manifests/alp/istio-inject-configmap-1.1.17.yaml +++ b/manifests/alp/istio-inject-configmap-1.1.17.yaml @@ -180,7 +180,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.1.2.yaml b/manifests/alp/istio-inject-configmap-1.1.2.yaml index c028e4664c4..d86fc4a345c 100644 --- a/manifests/alp/istio-inject-configmap-1.1.2.yaml +++ b/manifests/alp/istio-inject-configmap-1.1.2.yaml @@ -178,7 +178,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.1.3.yaml b/manifests/alp/istio-inject-configmap-1.1.3.yaml index 644862ef405..e64c8aa8f6a 100644 --- a/manifests/alp/istio-inject-configmap-1.1.3.yaml +++ b/manifests/alp/istio-inject-configmap-1.1.3.yaml @@ -180,7 +180,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.1.4.yaml b/manifests/alp/istio-inject-configmap-1.1.4.yaml index b9e2add13af..c7febdad26a 100644 --- a/manifests/alp/istio-inject-configmap-1.1.4.yaml +++ b/manifests/alp/istio-inject-configmap-1.1.4.yaml @@ -180,7 +180,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.1.5.yaml b/manifests/alp/istio-inject-configmap-1.1.5.yaml index 9cb6423056d..273125dae9c 100644 --- a/manifests/alp/istio-inject-configmap-1.1.5.yaml +++ b/manifests/alp/istio-inject-configmap-1.1.5.yaml @@ -180,7 +180,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.1.6.yaml b/manifests/alp/istio-inject-configmap-1.1.6.yaml index dcd28933f05..2a2eb65443e 100644 --- a/manifests/alp/istio-inject-configmap-1.1.6.yaml +++ b/manifests/alp/istio-inject-configmap-1.1.6.yaml @@ -180,7 +180,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.1.7.yaml b/manifests/alp/istio-inject-configmap-1.1.7.yaml index 26a16b9ebf1..afbbf7dc223 100644 --- a/manifests/alp/istio-inject-configmap-1.1.7.yaml +++ b/manifests/alp/istio-inject-configmap-1.1.7.yaml @@ -180,7 +180,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.1.8.yaml b/manifests/alp/istio-inject-configmap-1.1.8.yaml index 221935f5c84..37c38c1e335 100644 --- a/manifests/alp/istio-inject-configmap-1.1.8.yaml +++ b/manifests/alp/istio-inject-configmap-1.1.8.yaml @@ -180,7 +180,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.1.9.yaml b/manifests/alp/istio-inject-configmap-1.1.9.yaml index 0b32e262304..640a945b386 100644 --- a/manifests/alp/istio-inject-configmap-1.1.9.yaml +++ b/manifests/alp/istio-inject-configmap-1.1.9.yaml @@ -180,7 +180,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.10.yaml b/manifests/alp/istio-inject-configmap-1.10.yaml index 9a99b108020..66e646b56bd 100644 --- a/manifests/alp/istio-inject-configmap-1.10.yaml +++ b/manifests/alp/istio-inject-configmap-1.10.yaml @@ -433,7 +433,7 @@ data: name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false @@ -720,7 +720,7 @@ data: name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.15.yaml b/manifests/alp/istio-inject-configmap-1.15.yaml index b2700320c11..e7a76bda48b 100644 --- a/manifests/alp/istio-inject-configmap-1.15.yaml +++ b/manifests/alp/istio-inject-configmap-1.15.yaml @@ -434,7 +434,7 @@ data: name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false @@ -719,7 +719,7 @@ data: name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.2.0.yaml b/manifests/alp/istio-inject-configmap-1.2.0.yaml index f9c19905fa6..9a407bbda88 100644 --- a/manifests/alp/istio-inject-configmap-1.2.0.yaml +++ b/manifests/alp/istio-inject-configmap-1.2.0.yaml @@ -301,7 +301,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.2.1.yaml b/manifests/alp/istio-inject-configmap-1.2.1.yaml index f9c19905fa6..9a407bbda88 100644 --- a/manifests/alp/istio-inject-configmap-1.2.1.yaml +++ b/manifests/alp/istio-inject-configmap-1.2.1.yaml @@ -301,7 +301,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.2.2.yaml b/manifests/alp/istio-inject-configmap-1.2.2.yaml index f9c19905fa6..9a407bbda88 100644 --- a/manifests/alp/istio-inject-configmap-1.2.2.yaml +++ b/manifests/alp/istio-inject-configmap-1.2.2.yaml @@ -301,7 +301,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.2.3.yaml b/manifests/alp/istio-inject-configmap-1.2.3.yaml index f9c19905fa6..9a407bbda88 100644 --- a/manifests/alp/istio-inject-configmap-1.2.3.yaml +++ b/manifests/alp/istio-inject-configmap-1.2.3.yaml @@ -301,7 +301,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.2.4.yaml b/manifests/alp/istio-inject-configmap-1.2.4.yaml index f9c19905fa6..9a407bbda88 100644 --- a/manifests/alp/istio-inject-configmap-1.2.4.yaml +++ b/manifests/alp/istio-inject-configmap-1.2.4.yaml @@ -301,7 +301,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.2.5.yaml b/manifests/alp/istio-inject-configmap-1.2.5.yaml index f9c19905fa6..9a407bbda88 100644 --- a/manifests/alp/istio-inject-configmap-1.2.5.yaml +++ b/manifests/alp/istio-inject-configmap-1.2.5.yaml @@ -301,7 +301,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.2.6.yaml b/manifests/alp/istio-inject-configmap-1.2.6.yaml index f9c19905fa6..9a407bbda88 100644 --- a/manifests/alp/istio-inject-configmap-1.2.6.yaml +++ b/manifests/alp/istio-inject-configmap-1.2.6.yaml @@ -301,7 +301,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.2.7.yaml b/manifests/alp/istio-inject-configmap-1.2.7.yaml index f9c19905fa6..9a407bbda88 100644 --- a/manifests/alp/istio-inject-configmap-1.2.7.yaml +++ b/manifests/alp/istio-inject-configmap-1.2.7.yaml @@ -301,7 +301,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.2.8.yaml b/manifests/alp/istio-inject-configmap-1.2.8.yaml index f9c19905fa6..9a407bbda88 100644 --- a/manifests/alp/istio-inject-configmap-1.2.8.yaml +++ b/manifests/alp/istio-inject-configmap-1.2.8.yaml @@ -301,7 +301,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.2.9.yaml b/manifests/alp/istio-inject-configmap-1.2.9.yaml index f9c19905fa6..9a407bbda88 100644 --- a/manifests/alp/istio-inject-configmap-1.2.9.yaml +++ b/manifests/alp/istio-inject-configmap-1.2.9.yaml @@ -301,7 +301,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.3.0.yaml b/manifests/alp/istio-inject-configmap-1.3.0.yaml index b20e6604498..2d7b7232fb6 100644 --- a/manifests/alp/istio-inject-configmap-1.3.0.yaml +++ b/manifests/alp/istio-inject-configmap-1.3.0.yaml @@ -327,7 +327,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.3.1.yaml b/manifests/alp/istio-inject-configmap-1.3.1.yaml index e7b07d139cd..a0fa523df0f 100644 --- a/manifests/alp/istio-inject-configmap-1.3.1.yaml +++ b/manifests/alp/istio-inject-configmap-1.3.1.yaml @@ -333,7 +333,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.3.2.yaml b/manifests/alp/istio-inject-configmap-1.3.2.yaml index e7b07d139cd..a0fa523df0f 100644 --- a/manifests/alp/istio-inject-configmap-1.3.2.yaml +++ b/manifests/alp/istio-inject-configmap-1.3.2.yaml @@ -333,7 +333,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.3.3.yaml b/manifests/alp/istio-inject-configmap-1.3.3.yaml index e7b07d139cd..a0fa523df0f 100644 --- a/manifests/alp/istio-inject-configmap-1.3.3.yaml +++ b/manifests/alp/istio-inject-configmap-1.3.3.yaml @@ -333,7 +333,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.3.4.yaml b/manifests/alp/istio-inject-configmap-1.3.4.yaml index e7b07d139cd..a0fa523df0f 100644 --- a/manifests/alp/istio-inject-configmap-1.3.4.yaml +++ b/manifests/alp/istio-inject-configmap-1.3.4.yaml @@ -333,7 +333,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.3.5.yaml b/manifests/alp/istio-inject-configmap-1.3.5.yaml index e7b07d139cd..a0fa523df0f 100644 --- a/manifests/alp/istio-inject-configmap-1.3.5.yaml +++ b/manifests/alp/istio-inject-configmap-1.3.5.yaml @@ -333,7 +333,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.4.0.yaml b/manifests/alp/istio-inject-configmap-1.4.0.yaml index 3677492b096..cdd83551f25 100644 --- a/manifests/alp/istio-inject-configmap-1.4.0.yaml +++ b/manifests/alp/istio-inject-configmap-1.4.0.yaml @@ -351,7 +351,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.4.1.yaml b/manifests/alp/istio-inject-configmap-1.4.1.yaml index 3677492b096..cdd83551f25 100644 --- a/manifests/alp/istio-inject-configmap-1.4.1.yaml +++ b/manifests/alp/istio-inject-configmap-1.4.1.yaml @@ -351,7 +351,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.4.2.yaml b/manifests/alp/istio-inject-configmap-1.4.2.yaml index 3677492b096..cdd83551f25 100644 --- a/manifests/alp/istio-inject-configmap-1.4.2.yaml +++ b/manifests/alp/istio-inject-configmap-1.4.2.yaml @@ -351,7 +351,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.6.yaml b/manifests/alp/istio-inject-configmap-1.6.yaml index 40dc8ba8405..48f45c82ad5 100644 --- a/manifests/alp/istio-inject-configmap-1.6.yaml +++ b/manifests/alp/istio-inject-configmap-1.6.yaml @@ -363,7 +363,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.7.yaml b/manifests/alp/istio-inject-configmap-1.7.yaml index 04eeb6c6ea8..8b7fb46e522 100644 --- a/manifests/alp/istio-inject-configmap-1.7.yaml +++ b/manifests/alp/istio-inject-configmap-1.7.yaml @@ -369,7 +369,7 @@ data: - mountPath: /var/run/dikastes name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/alp/istio-inject-configmap-1.9.yaml b/manifests/alp/istio-inject-configmap-1.9.yaml index 8068eb6ab57..c531f09872f 100644 --- a/manifests/alp/istio-inject-configmap-1.9.yaml +++ b/manifests/alp/istio-inject-configmap-1.9.yaml @@ -428,7 +428,7 @@ data: name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false @@ -714,7 +714,7 @@ data: name: dikastes-sock - name: dikastes - image: calico/dikastes:master + image: quay.io/calico/dikastes:master args: ["server", "-l", "/var/run/dikastes/dikastes.sock", "-d", "/var/run/felix/nodeagent/socket"] securityContext: allowPrivilegeEscalation: false diff --git a/manifests/apiserver.yaml b/manifests/apiserver.yaml index 38152c5880d..f88eecdffda 100644 --- a/manifests/apiserver.yaml +++ b/manifests/apiserver.yaml @@ -74,7 +74,7 @@ spec: env: - name: DATASTORE_TYPE value: kubernetes - image: calico/apiserver:master + image: quay.io/calico/apiserver:master name: calico-apiserver readinessProbe: httpGet: diff --git a/manifests/calico-bpf.yaml b/manifests/calico-bpf.yaml index df75b571e5c..c8197bf5d2d 100644 --- a/manifests/calico-bpf.yaml +++ b/manifests/calico-bpf.yaml @@ -124,6 +124,9 @@ spec: format: int32 type: integer bindMode: + enum: + - None + - NodeIP type: string communities: items: @@ -134,11 +137,14 @@ spec: pattern: ^(\d+):(\d+)$|^(\d+):(\d+):(\d+)$ type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set ignoredInterfaces: items: type: string type: array + x-kubernetes-list-type: set listenPort: maximum: 65535 minimum: 1 @@ -148,6 +154,8 @@ spec: localWorkloadPeeringIPV6: type: string logSeverityScreen: + default: Info + pattern: ^(?i)(Trace|Debug|Info|Warning|Error|Fatal)?$ type: string nodeMeshMaxRestartTime: type: string @@ -173,27 +181,36 @@ spec: items: properties: cidr: + format: cidr type: string communities: items: type: string type: array type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceClusterIPs: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceExternalIPs: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceLoadBalancerAggregation: default: Enabled enum: @@ -204,9 +221,12 @@ spec: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -245,12 +265,21 @@ spec: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -265,22 +294,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array exportV6: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -295,22 +337,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array importV4: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -325,22 +380,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array importV6: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -355,11 +423,15 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array type: object type: object @@ -414,15 +486,10 @@ spec: maxRestartTime: type: string nextHopMode: - allOf: - - enum: - - Auto - - Self - - Keep - - enum: - - Auto - - Self - - Keep + enum: + - Auto + - Self + - Keep type: string node: type: string @@ -454,11 +521,18 @@ spec: reachableBy: type: string reversePeering: - enum: - - Auto - - Manual + allOf: + - enum: + - Auto + - Manual + - enum: + - Auto + - Manual type: string sourceAddress: + enum: + - UseNodeIP + - None type: string ttlSecurity: type: integer @@ -547,6 +621,10 @@ spec: properties: classes: items: + enum: + - Agent + - BGP + - Routes type: string type: array node: @@ -568,6 +646,9 @@ spec: routerID: type: string state: + enum: + - Ready + - NotReady type: string version: type: string @@ -581,6 +662,9 @@ spec: routerID: type: string state: + enum: + - Ready + - NotReady type: string version: type: string @@ -604,8 +688,20 @@ spec: since: type: string state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close type: string type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer type: string type: object type: array @@ -617,8 +713,20 @@ spec: since: type: string state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close type: string type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer type: string type: object type: array @@ -648,9 +756,18 @@ spec: peerIP: type: string sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer type: string type: object type: + enum: + - FIB + - RIB type: string type: object type: array @@ -668,9 +785,18 @@ spec: peerIP: type: string sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer type: string type: object type: + enum: + - FIB + - RIB type: string type: object type: array @@ -1024,15 +1150,9 @@ spec: if it detects the current value is 2 (strict mode that hurts performance). When set to "Strict", Felix will not modify the JIT hardening setting. [Default: Auto] type: string - bpfKubeProxyEndpointSlicesEnabled: + bpfKubeProxyHealthzPort: description: |- - BPFKubeProxyEndpointSlicesEnabled is deprecated and has no effect. BPF - kube-proxy always accepts endpoint slices. This option will be removed in - the next release. - type: boolean - bpfKubeProxyHealtzPort: - description: |- - BPFKubeProxyHealtzPort, in BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. + BPFKubeProxyHealthzPort, in BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. The health check server is used by external load balancers to determine if this node should receive traffic. [Default: 10256] type: integer bpfKubeProxyIptablesCleanupEnabled: @@ -1073,6 +1193,23 @@ spec: [Default: Off]. pattern: ^(?i)(Off|Info|Debug)?$ type: string + bpfMaglevMaxEndpointsPerService: + description: |- + BPFMaglevMaxEndpointsPerService is the maximum number of endpoints + expected to be part of a single Maglev-enabled service. + + Influences the size of the per-service Maglev lookup-tables generated by Felix + and thus the amount of memory reserved. + + [Default: 100] + type: integer + bpfMaglevMaxServices: + description: |- + BPFMaglevMaxServices is the maximum number of expected Maglev-enabled + services that Felix will allocate lookup-tables for. + + [Default: 100] + type: integer bpfMapSizeConntrack: description: |- BPFMapSizeConntrack sets the size for the conntrack map. This map must be large enough to hold @@ -1161,17 +1298,14 @@ spec: type: string bpfRedirectToPeer: description: |- - BPFRedirectToPeer controls which whether it is allowed to forward straight to the - peer side of the workload devices. It is allowed for any host L2 devices by default - (L2Only), but it breaks TCP dump on the host side of workload device as it bypasses - it on ingress. Value of Enabled also allows redirection from L3 host devices like - IPIP tunnel or Wireguard directly to the peer side of the workload's device. This - makes redirection faster, however, it breaks tools like tcpdump on the peer side. - Use Enabled with caution. [Default: L2Only] + BPFRedirectToPeer controls whether traffic may be forwarded directly to the peer side of a workload’s device. + Note that the legacy "L2Only" option is now deprecated and if set it is treated like "Enabled. + Setting this option to "Enabled" allows direct redirection (including from L3 host devices such as IPIP tunnels or WireGuard), + which can improve redirection performance but causes the redirected packets to bypass the host‑side ingress path. + As a result, packet‑capture tools on the host side of the workload device (for example, tcpdump) will not see that traffic. [Default: Enabled] enum: - Enabled - Disabled - - L2Only type: string cgroupV2Path: description: @@ -1522,6 +1656,10 @@ spec: Warning: changing this on a running system can leave "orphaned" rules in the "other" backend. These should be cleaned up to avoid confusing interactions. + enum: + - Legacy + - NFT + - Auto pattern: ^(?i)(Auto|Legacy|NFT)?$ type: string iptablesFilterAllowAction: @@ -1537,26 +1675,10 @@ spec: with an iptables "DROP" action. If you want to use "REJECT" action instead you can configure it in here. pattern: ^(?i)(Drop|Reject)?$ type: string - iptablesLockFilePath: - description: |- - IptablesLockFilePath is the location of the iptables lock file. You may need to change this - if the lock file is not in its standard location (for example if you have mapped it into Felix's - container at a different path). [Default: /run/xtables.lock] - type: string iptablesLockProbeInterval: description: |- - IptablesLockProbeInterval when IptablesLockTimeout is enabled: the time that Felix will wait between - attempts to acquire the iptables lock if it is not available. Lower values make Felix more - responsive when the lock is contended, but use more CPU. [Default: 50ms] - pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ - type: string - iptablesLockTimeout: - description: |- - IptablesLockTimeout is the time that Felix itself will wait for the iptables lock (rather than delegating the - lock handling to the `iptables` command). - - Deprecated: `iptables-restore` v1.8+ always takes the lock, so enabling this feature results in deadlock. - [Default: 0s disabled] + IptablesLockProbeInterval configures the interval between attempts to claim + the xtables lock. Shorter intervals are more responsive but use more CPU. [Default: 50ms] pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ type: string iptablesMangleAllowAction: @@ -1616,6 +1738,19 @@ spec: pattern: ^.* x-kubernetes-int-or-string: true type: array + logActionRateLimit: + description: |- + LogActionRateLimit sets the rate of hitting a Log action. The value must be in the format "N/unit", + where N is a number and unit is one of: second, minute, hour, or day. For example: "10/second" or "100/hour". + pattern: ^[1-9]\d{0,3}/(?:second|minute|hour|day)$ + type: string + logActionRateLimitBurst: + description: + LogActionRateLimitBurst sets the rate limit burst of + hitting a Log action when LogActionRateLimit is enabled. + maximum: 9999 + minimum: 0 + type: integer logDebugFilenameRegex: description: |- LogDebugFilenameRegex controls which source code files have their Debug log output included in the logs. @@ -1628,9 +1763,17 @@ spec: none to disable file logging. [Default: /var/log/calico/felix.log]" type: string logPrefix: - description: - "LogPrefix is the log prefix that Felix uses when rendering - LOG rules. [Default: calico-packet]" + description: |- + LogPrefix is the log prefix that Felix uses when rendering LOG rules. It is possible to use the following specifiers + to include extra information in the log prefix. + - %t: Tier name. + - %k: Kind (short names). + - %n: Policy or profile name. + - %p: Policy or profile name (namespace/name for namespaced kinds or just name for non namespaced kinds). + Calico includes ": " characters at the end of the generated log prefix. + Note that iptables shows up to 29 characters for the log prefix and nftables up to 127 characters. Extra characters are truncated. + [Default: calico-packet] + pattern: "^([a-zA-Z0-9%: /_-])*$" type: string logSeverityFile: description: @@ -1734,9 +1877,10 @@ spec: format: int32 type: integer nftablesMode: + default: Auto description: "NFTablesMode configures nftables support in Felix. [Default: - Disabled]" + Auto]" enum: - Disabled - Enabled @@ -1772,6 +1916,21 @@ spec: PrometheusGoMetricsEnabled disables Go runtime metrics collection, which the Prometheus client does by default, when set to false. This reduces the number of metrics reported, reducing Prometheus load. [Default: true] type: boolean + prometheusMetricsCAFile: + description: |- + PrometheusMetricsCAFile defines the absolute path to the TLS CA certificate file used for securing the /metrics endpoint. + This certificate must be valid and accessible by the calico-node process. + type: string + prometheusMetricsCertFile: + description: |- + PrometheusMetricsCertFile defines the absolute path to the TLS certificate file used for securing the /metrics endpoint. + This certificate must be valid and accessible by the calico-node process. + type: string + prometheusMetricsClientAuth: + description: |- + PrometheusMetricsClientAuth specifies the client authentication type for the /metrics endpoint. + This determines how the server validates client certificates. Default is "RequireAndVerifyClientCert". + type: string prometheusMetricsEnabled: description: "PrometheusMetricsEnabled enables the Prometheus metrics @@ -1782,6 +1941,11 @@ spec: "PrometheusMetricsHost is the host that the Prometheus metrics server should bind to. [Default: empty]" type: string + prometheusMetricsKeyFile: + description: |- + PrometheusMetricsKeyFile defines the absolute path to the private key file corresponding to the TLS certificate + used for securing the /metrics endpoint. The private key must be valid and accessible by the calico-node process. + type: string prometheusMetricsPort: description: "PrometheusMetricsPort is the TCP port that the Prometheus @@ -2065,6 +2229,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -2074,6 +2243,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2104,6 +2274,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2134,11 +2305,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -2150,8 +2328,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -2174,6 +2356,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2204,6 +2387,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2223,6 +2407,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -2232,6 +2421,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2262,6 +2452,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2292,11 +2483,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -2308,8 +2506,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -2332,6 +2534,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2362,6 +2565,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2383,6 +2587,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array preDNAT: @@ -2392,11 +2598,18 @@ spec: serviceAccountSelector: type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2435,6 +2648,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2473,6 +2687,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set interfaceName: type: string node: @@ -2483,6 +2698,8 @@ spec: name: type: string port: + maximum: 65535 + minimum: 0 type: integer protocol: anyOf: @@ -2500,6 +2717,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2536,6 +2754,9 @@ spec: properties: affinity: type: string + affinityClaimTime: + format: date-time + type: string allocations: items: type: integer @@ -2546,6 +2767,10 @@ spec: attributes: items: properties: + alternate: + additionalProperties: + type: string + type: object handle_id: type: string secondary: @@ -2615,6 +2840,11 @@ spec: properties: autoAllocateBlocks: type: boolean + kubeVirtVMAddressPersistence: + enum: + - Enabled + - Disabled + type: string maxBlocksPerHost: maximum: 2147483647 minimum: 0 @@ -2705,39 +2935,47 @@ spec: properties: allowedUses: items: + enum: + - Workload + - Tunnel + - LoadBalancer type: string type: array + x-kubernetes-list-type: set assignmentMode: + default: Automatic enum: - Automatic - Manual type: string blockSize: + maximum: 128 + minimum: 0 type: integer cidr: + format: cidr type: string disableBGPExport: type: boolean disabled: type: boolean - ipip: - properties: - enabled: - type: boolean - mode: - type: string - type: object ipipMode: + enum: + - Never + - Always + - CrossSubnet type: string namespaceSelector: type: string - nat-outgoing: - type: boolean natOutgoing: type: boolean nodeSelector: type: string vxlanMode: + enum: + - Never + - Always + - CrossSubnet type: string required: - cidr @@ -2776,9 +3014,11 @@ spec: spec: properties: reservedCIDRs: + format: cidr items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2818,6 +3058,10 @@ spec: loadBalancer: properties: assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly type: string type: object namespace: @@ -2830,6 +3074,9 @@ spec: hostEndpoint: properties: autoCreate: + enum: + - Enabled + - Disabled type: string createDefaultHostEndpoint: type: string @@ -2843,7 +3090,8 @@ spec: items: type: string type: array - interfaceSelector: + x-kubernetes-list-type: set + interfacePattern: type: string labels: additionalProperties: @@ -2859,6 +3107,9 @@ spec: reconcilerPeriod: type: string syncLabels: + enum: + - Enabled + - Disabled type: string type: object policy: @@ -2866,6 +3117,15 @@ spec: reconcilerPeriod: type: string type: object + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object serviceAccount: properties: reconcilerPeriod: @@ -2879,14 +3139,30 @@ spec: type: object debugProfilePort: format: int32 + maximum: 65535 + minimum: 0 type: integer etcdV3CompactionPeriod: type: string healthChecks: + default: Enabled + enum: + - Enabled + - Disabled type: string logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic type: string prometheusMetricsPort: + maximum: 65535 + minimum: 0 type: integer required: - controllers @@ -2904,6 +3180,10 @@ spec: loadBalancer: properties: assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly type: string type: object namespace: @@ -2916,6 +3196,9 @@ spec: hostEndpoint: properties: autoCreate: + enum: + - Enabled + - Disabled type: string createDefaultHostEndpoint: type: string @@ -2929,7 +3212,8 @@ spec: items: type: string type: array - interfaceSelector: + x-kubernetes-list-type: set + interfacePattern: type: string labels: additionalProperties: @@ -2945,6 +3229,9 @@ spec: reconcilerPeriod: type: string syncLabels: + enum: + - Enabled + - Disabled type: string type: object policy: @@ -2952,6 +3239,15 @@ spec: reconcilerPeriod: type: string type: object + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object serviceAccount: properties: reconcilerPeriod: @@ -2965,14 +3261,30 @@ spec: type: object debugProfilePort: format: int32 + maximum: 65535 + minimum: 0 type: integer etcdV3CompactionPeriod: type: string healthChecks: + default: Enabled + enum: + - Enabled + - Disabled type: string logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic type: string prometheusMetricsPort: + maximum: 65535 + minimum: 0 type: integer required: - controllers @@ -2981,6 +3293,8 @@ spec: type: object served: true storage: true + subresources: + status: {} --- # Source: calico/templates/kdd-crds.yaml apiVersion: apiextensions.k8s.io/v1 @@ -3015,6 +3329,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3024,6 +3343,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3054,6 +3374,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3084,11 +3405,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3100,8 +3428,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3124,6 +3456,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3154,6 +3487,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3173,6 +3507,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3182,6 +3521,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3212,6 +3552,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3242,11 +3583,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3258,8 +3606,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3282,6 +3634,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3312,6 +3665,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3331,6 +3685,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array selector: @@ -3338,11 +3694,18 @@ spec: serviceAccountSelector: type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -3381,6 +3744,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -3423,6 +3787,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3432,6 +3801,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3462,6 +3832,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3492,11 +3863,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3508,8 +3886,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3532,6 +3914,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3562,6 +3945,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3581,6 +3965,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3590,6 +3979,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3620,6 +4010,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3650,11 +4041,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3666,8 +4064,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3690,6 +4092,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3720,6 +4123,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3741,6 +4145,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array preDNAT: @@ -3750,13 +4156,25 @@ spec: serviceAccountSelector: type: string stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -4002,8 +4420,16 @@ spec: policyTypes: items: type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string type: object type: object @@ -4043,6 +4469,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -4052,6 +4483,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4082,6 +4514,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4112,11 +4545,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -4128,8 +4568,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -4152,6 +4596,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4182,6 +4627,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4201,6 +4647,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -4210,6 +4661,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4240,6 +4692,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4270,11 +4723,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -4286,8 +4746,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -4310,6 +4774,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4340,6 +4805,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4359,6 +4825,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array selector: @@ -4366,13 +4834,25 @@ spec: serviceAccountSelector: type: string stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -4408,14 +4888,36 @@ spec: spec: properties: defaultAction: - enum: - - Pass - - Deny + allOf: + - enum: + - Allow + - Deny + - Log + - Pass + - enum: + - Pass + - Deny type: string order: type: number type: object + required: + - metadata + - spec type: object + x-kubernetes-validations: + - message: The 'kube-admin' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-admin' ? self.spec.defaultAction == + 'Pass' : true" + - message: The 'kube-baseline' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-baseline' ? self.spec.defaultAction + == 'Pass' : true" + - message: The 'default' tier must have default action 'Deny' + rule: + "self.metadata.name == 'default' ? self.spec.defaultAction == 'Deny' + : true" served: true storage: true --- @@ -4424,35 +4926,35 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/30 - policy.networking.k8s.io/bundle-version: v0.1.1 - policy.networking.k8s.io/channel: experimental - creationTimestamp: null - name: adminnetworkpolicies.policy.networking.k8s.io + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/300 + policy.networking.k8s.io/bundle-version: v0.1.7 + policy.networking.k8s.io/channel: standard + name: clusternetworkpolicies.policy.networking.k8s.io spec: group: policy.networking.k8s.io names: - kind: AdminNetworkPolicy - listKind: AdminNetworkPolicyList - plural: adminnetworkpolicies + kind: ClusterNetworkPolicy + listKind: ClusterNetworkPolicyList + plural: clusternetworkpolicies shortNames: - - anp - singular: adminnetworkpolicy + - cnp + singular: clusternetworkpolicy scope: Cluster versions: - additionalPrinterColumns: + - jsonPath: .spec.tier + name: Tier + type: string - jsonPath: .spec.priority name: Priority type: string - jsonPath: .metadata.creationTimestamp name: Age type: date - name: v1alpha1 + name: v1alpha2 schema: openAPIV3Schema: - description: |- - AdminNetworkPolicy is a cluster level resource that is part of the - AdminNetworkPolicy API. + description: ClusterNetworkPolicy is a cluster-wide network policy resource. properties: apiVersion: description: |- @@ -4472,172 +4974,233 @@ spec: metadata: type: object spec: - description: Specification of the desired behavior of AdminNetworkPolicy. + description: Spec defines the desired behavior of ClusterNetworkPolicy. properties: egress: description: |- Egress is the list of Egress rules to be applied to the selected pods. - A total of 100 rules will be allowed in each ANP instance. - The relative precedence of egress rules within a single ANP object (all of - which share the priority) will be determined by the order in which the rule - is written. Thus, a rule that appears at the top of the egress rules - would take the highest precedence. - ANPs with no egress rules do not affect egress traffic. + A maximum of 25 rules is allowed in this block. - Support: Core + The relative precedence of egress rules within a single CNP object + (all of which share the priority) will be determined by the order + in which the rule is written. + Thus, a rule that appears at the top of the egress rules + would take the highest precedence. + CNPs with no egress rules do not affect egress traffic. items: description: |- - AdminNetworkPolicyEgressRule describes an action to take on a particular - set of traffic originating from pods selected by a AdminNetworkPolicy's + ClusterNetworkPolicyEgressRule describes an action to take on a particular + set of traffic originating from pods selected by a ClusterNetworkPolicy's Subject field. + properties: action: description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic (even if it would otherwise have been denied by NetworkPolicy) - Deny: denies the selected traffic - Pass: instructs the selected traffic to skip any remaining ANP rules, and - then pass execution to any NetworkPolicies that select the pod. - If the pod is not selected by any NetworkPolicies then execution - is passed to any BaselineAdminNetworkPolicies that select the pod. + Action specifies the effect this rule will have on matching + traffic. Currently the following actions are supported: + - Accept: Accepts the selected traffic, allowing it to + egress. No further ClusterNetworkPolicy or NetworkPolicy + rules will be processed. - Support: Core + - Deny: Drops the selected traffic. No further + ClusterNetworkPolicy or NetworkPolicy rules will be + processed. + + - Pass: Skips all further ClusterNetworkPolicy rules in the + current tier for the selected traffic, and passes + evaluation to the next tier. enum: - - Allow + - Accept - Deny - Pass type: string name: description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - AdminNetworkPolicies. - - - Support: Core + Name is an identifier for this rule, that may be no more than + 100 characters in length. This field should be used by the implementation + to help improve observability, readability and error-reporting + for any applied policies. maxLength: 100 type: string - ports: + protocols: description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of destination ports for the outgoing egress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core + Protocols allows for more fine-grain matching of traffic on + protocol-specific attributes such as the port. If + unspecified, protocol-specific attributes will not be used + to match traffic. items: description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). + ClusterNetworkPolicyProtocol describes additional protocol-specific match rules. Exactly one field must be set. maxProperties: 1 minProperties: 1 properties: - namedPort: + destinationNamedPort: description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - + DestinationNamedPort selects a destination port on a pod based on the + ContainerPort name. You can't use this in a rule that targets resources + without named ports (e.g. Nodes or Networks). type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core + sctp: + description: SCTP specific protocol matches. + minProperties: 1 properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core + tcp: + description: TCP specific protocol matches. + minProperties: 1 properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + udp: + description: UDP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object type: object - maxItems: 100 + maxItems: 25 + minItems: 1 type: array to: description: |- - To is the List of destinations whose traffic this rule applies to. - If any AdminNetworkPolicyEgressPeer matches the destination of outgoing - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core + To is the list of destinations whose traffic this rule applies to. If any + element matches the destination of outgoing traffic then the specified + action is applied. This field must be defined and contain at least one + item. items: description: |- - AdminNetworkPolicyEgressPeer defines a peer to allow traffic to. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. + ClusterNetworkPolicyEgressPeer defines a peer to allow traffic to. + + Exactly one of the fields must be set for a given peer and this is enforced + by the validation rules on the CRD. If an implementation sees no fields are + set then it can infer that the deployed CRD is of an incompatible version + with an unknown field. In that case it should fail closed. + + For "Accept" rules, "fail closed" means: "treat the rule as matching no + traffic". For "Deny" and "Pass" rules, "fail closed" means: "treat the rule + as a 'Deny all' rule". maxProperties: 1 minProperties: 1 properties: @@ -4645,9 +5208,6 @@ spec: description: |- Namespaces defines a way to select all pods within a set of Namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: matchExpressions: description: @@ -4677,11 +5237,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -4698,1206 +5260,37 @@ spec: This is intended for representing entities that live outside the cluster, which can't be selected by pods, namespaces and nodes peers, but note that cluster-internal traffic will be checked against the rule as - well. So if you Allow or Deny traffic to `"0.0.0.0/0"`, that will allow + well. So if you Accept or Deny traffic to `"0.0.0.0/0"`, that will allow or deny all IPv4 pod-to-pod traffic as well. If you don't want that, add a rule that Passes all pod traffic before the Networks rule. - Each item in Networks should be provided in the CIDR format and should be IPv4 or IPv6, for example "10.0.0.0/8" or "fd00::/8". - - Networks can have upto 25 CIDRs specified. - - - Support: Extended - - - + Networks can have up to 25 CIDRs specified. items: description: |- - CIDR is an IP address range in CIDR notation (for example, "10.0.0.0/8" or "fd00::/8"). - This string must be validated by implementations using net.ParseCIDR - TODO: Introduce CEL CIDR validation regex isCIDR() in Kube 1.31 when it is available. + CIDR is an IP address range in CIDR notation + (for example, "10.0.0.0/8" or "fd00::/8"). maxLength: 43 type: string x-kubernetes-validations: - - message: - CIDR must be either an IPv4 or IPv6 address. - IPv4 address embedded in IPv6 addresses are not - supported - rule: self.contains(':') != self.contains('.') + - message: Invalid CIDR format provided + rule: isCIDR(self) maxItems: 25 minItems: 1 type: array x-kubernetes-list-type: set - nodes: - description: |- - Nodes defines a way to select a set of nodes in - the cluster. This field follows standard label selector - semantics; if present but empty, it selects all Nodes. - - - Support: Extended - - - - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: |- - Pods defines a way to select a set of pods in - a set of namespaces. Note that host-networked pods - are not included in this type of peer. - - - Support: Core - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - maxItems: 100 - minItems: 1 - type: array - required: - - action - - to - type: object - x-kubernetes-validations: - - message: - networks/nodes peer cannot be set with namedPorts since - there are no namedPorts for networks/nodes - rule: - "!(self.to.exists(peer, has(peer.networks) || has(peer.nodes)) - && has(self.ports) && self.ports.exists(port, has(port.namedPort)))" - maxItems: 100 - type: array - ingress: - description: |- - Ingress is the list of Ingress rules to be applied to the selected pods. - A total of 100 rules will be allowed in each ANP instance. - The relative precedence of ingress rules within a single ANP object (all of - which share the priority) will be determined by the order in which the rule - is written. Thus, a rule that appears at the top of the ingress rules - would take the highest precedence. - ANPs with no ingress rules do not affect ingress traffic. - - - Support: Core - items: - description: |- - AdminNetworkPolicyIngressRule describes an action to take on a particular - set of traffic destined for pods selected by an AdminNetworkPolicy's - Subject field. - properties: - action: - description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic (even if it would otherwise have been denied by NetworkPolicy) - Deny: denies the selected traffic - Pass: instructs the selected traffic to skip any remaining ANP rules, and - then pass execution to any NetworkPolicies that select the pod. - If the pod is not selected by any NetworkPolicies then execution - is passed to any BaselineAdminNetworkPolicies that select the pod. - - - Support: Core - enum: - - Allow - - Deny - - Pass - type: string - from: - description: |- - From is the list of sources whose traffic this rule applies to. - If any AdminNetworkPolicyIngressPeer matches the source of incoming - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core - items: - description: |- - AdminNetworkPolicyIngressPeer defines an in-cluster peer to allow traffic from. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: |- - Namespaces defines a way to select all pods within a set of Namespaces. - Note that host-networked pods are not included in this type of peer. - - - Support: Core - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: |- - Pods defines a way to select a set of pods in - a set of namespaces. Note that host-networked pods - are not included in this type of peer. - - - Support: Core - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - maxItems: 100 - minItems: 1 - type: array - name: - description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - AdminNetworkPolicies. - - - Support: Core - maxLength: 100 - type: string - ports: - description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of ports which should be matched on - the pods selected for this policy i.e the subject of the policy. - So it matches on the destination port for the ingress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core - items: - description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). - Exactly one field must be set. - maxProperties: 1 - minProperties: 1 - properties: - namedPort: - description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - - type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core - properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol - type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core - properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start - type: object - type: object - maxItems: 100 - type: array - required: - - action - - from - type: object - maxItems: 100 - type: array - priority: - description: |- - Priority is a value from 0 to 1000. Rules with lower priority values have - higher precedence, and are checked before rules with higher priority values. - All AdminNetworkPolicy rules have higher precedence than NetworkPolicy or - BaselineAdminNetworkPolicy rules - The behavior is undefined if two ANP objects have same priority. - - - Support: Core - format: int32 - maximum: 1000 - minimum: 0 - type: integer - subject: - description: |- - Subject defines the pods to which this AdminNetworkPolicy applies. - Note that host-networked pods are not included in subject selection. - - - Support: Core - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: Namespaces is used to select pods via namespace selectors. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: - Pods is used to select pods via namespace AND pod - selectors. - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - required: - - priority - - subject - type: object - status: - description: Status is the status to be reported by the implementation. - properties: - conditions: - items: - description: - "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - required: - - conditions - type: object - required: - - metadata - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: null - storedVersions: null ---- -# Source: calico/templates/kdd-crds.yaml -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/30 - policy.networking.k8s.io/bundle-version: v0.1.1 - policy.networking.k8s.io/channel: experimental - creationTimestamp: null - name: baselineadminnetworkpolicies.policy.networking.k8s.io -spec: - group: policy.networking.k8s.io - names: - kind: BaselineAdminNetworkPolicy - listKind: BaselineAdminNetworkPolicyList - plural: baselineadminnetworkpolicies - shortNames: - - banp - singular: baselineadminnetworkpolicy - scope: Cluster - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: |- - BaselineAdminNetworkPolicy is a cluster level resource that is part of the - AdminNetworkPolicy API. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: Specification of the desired behavior of BaselineAdminNetworkPolicy. - properties: - egress: - description: |- - Egress is the list of Egress rules to be applied to the selected pods if - they are not matched by any AdminNetworkPolicy or NetworkPolicy rules. - A total of 100 Egress rules will be allowed in each BANP instance. - The relative precedence of egress rules within a single BANP object - will be determined by the order in which the rule is written. - Thus, a rule that appears at the top of the egress rules - would take the highest precedence. - BANPs with no egress rules do not affect egress traffic. - - - Support: Core - items: - description: |- - BaselineAdminNetworkPolicyEgressRule describes an action to take on a particular - set of traffic originating from pods selected by a BaselineAdminNetworkPolicy's - Subject field. - - properties: - action: - description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic - Deny: denies the selected traffic - - - Support: Core - enum: - - Allow - - Deny - type: string - name: - description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - BaselineAdminNetworkPolicies. - - - Support: Core - maxLength: 100 - type: string - ports: - description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of destination ports for the outgoing egress traffic. - If Ports is not set then the rule does not filter traffic via port. - items: - description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). - Exactly one field must be set. - maxProperties: 1 - minProperties: 1 - properties: - namedPort: - description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - - type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core - properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol - type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core - properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start - type: object - type: object - maxItems: 100 - type: array - to: - description: |- - To is the list of destinations whose traffic this rule applies to. - If any AdminNetworkPolicyEgressPeer matches the destination of outgoing - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core - items: - description: |- - AdminNetworkPolicyEgressPeer defines a peer to allow traffic to. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: |- - Namespaces defines a way to select all pods within a set of Namespaces. - Note that host-networked pods are not included in this type of peer. - - - Support: Core - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - networks: - description: |- - Networks defines a way to select peers via CIDR blocks. - This is intended for representing entities that live outside the cluster, - which can't be selected by pods, namespaces and nodes peers, but note - that cluster-internal traffic will be checked against the rule as - well. So if you Allow or Deny traffic to `"0.0.0.0/0"`, that will allow - or deny all IPv4 pod-to-pod traffic as well. If you don't want that, - add a rule that Passes all pod traffic before the Networks rule. - - - Each item in Networks should be provided in the CIDR format and should be - IPv4 or IPv6, for example "10.0.0.0/8" or "fd00::/8". - - - Networks can have upto 25 CIDRs specified. - - - Support: Extended - - - - items: - description: |- - CIDR is an IP address range in CIDR notation (for example, "10.0.0.0/8" or "fd00::/8"). - This string must be validated by implementations using net.ParseCIDR - TODO: Introduce CEL CIDR validation regex isCIDR() in Kube 1.31 when it is available. - maxLength: 43 - type: string - x-kubernetes-validations: - - message: - CIDR must be either an IPv4 or IPv6 address. - IPv4 address embedded in IPv6 addresses are not - supported - rule: self.contains(':') != self.contains('.') - maxItems: 25 - minItems: 1 - type: array - x-kubernetes-list-type: set - nodes: - description: |- - Nodes defines a way to select a set of nodes in - the cluster. This field follows standard label selector - semantics; if present but empty, it selects all Nodes. - - - Support: Extended - - - - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: + pods: description: |- Pods defines a way to select a set of pods in a set of namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -5928,11 +5321,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -5945,8 +5340,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -5977,11 +5372,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -5993,73 +5390,80 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object - maxItems: 100 + maxItems: 25 minItems: 1 type: array required: - action - to type: object - x-kubernetes-validations: - - message: - networks/nodes peer cannot be set with namedPorts since - there are no namedPorts for networks/nodes - rule: - "!(self.to.exists(peer, has(peer.networks) || has(peer.nodes)) - && has(self.ports) && self.ports.exists(port, has(port.namedPort)))" - maxItems: 100 + maxItems: 25 type: array ingress: description: |- - Ingress is the list of Ingress rules to be applied to the selected pods - if they are not matched by any AdminNetworkPolicy or NetworkPolicy rules. - A total of 100 Ingress rules will be allowed in each BANP instance. - The relative precedence of ingress rules within a single BANP object - will be determined by the order in which the rule is written. - Thus, a rule that appears at the top of the ingress rules - would take the highest precedence. - BANPs with no ingress rules do not affect ingress traffic. + Ingress is the list of Ingress rules to be applied to the selected pods. + A maximum of 25 rules is allowed in this block. - Support: Core + The relative precedence of ingress rules within a single CNP object + (all of which share the priority) will be determined by the order + in which the rule is written. + Thus, a rule that appears at the top of the ingress rules + would take the highest precedence. + CNPs with no ingress rules do not affect ingress traffic. items: description: |- - BaselineAdminNetworkPolicyIngressRule describes an action to take on a particular - set of traffic destined for pods selected by a BaselineAdminNetworkPolicy's + ClusterNetworkPolicyIngressRule describes an action to take on a particular + set of traffic destined for pods selected by a ClusterNetworkPolicy's Subject field. properties: action: description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic - Deny: denies the selected traffic + Action specifies the effect this rule will have on matching + traffic. Currently the following actions are supported: + + - Accept: Accepts the selected traffic, allowing it into + the destination. No further ClusterNetworkPolicy or + NetworkPolicy rules will be processed. + Note: while Accept ensures traffic is accepted by + Kubernetes network policy, it is still possible that the + packet is blocked in other ways: custom nftable rules, + high-layers e.g. service mesh. - Support: Core + - Deny: Drops the selected traffic. No further + ClusterNetworkPolicy or NetworkPolicy rules will be + processed. + + - Pass: Skips all further ClusterNetworkPolicy rules in the + current tier for the selected traffic, and passes + evaluation to the next tier. enum: - - Allow + - Accept - Deny + - Pass type: string from: description: |- From is the list of sources whose traffic this rule applies to. - If any AdminNetworkPolicyIngressPeer matches the source of incoming + If any element matches the source of incoming traffic then the specified action is applied. This field must be defined and contain at least one item. - - - Support: Core items: description: |- - AdminNetworkPolicyIngressPeer defines an in-cluster peer to allow traffic from. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. + ClusterNetworkPolicyIngressPeer defines a peer to allow traffic from. + + Exactly one of the fields must be set for a given peer and this is enforced + by the validation rules on the CRD. If an implementation sees no fields are + set then it can infer that the deployed CRD is of an incompatible version + with an unknown field. In that case it should fail closed. + + For "Accept" rules, "fail closed" means: "treat the rule as matching no + traffic". For "Deny" and "Pass" rules, "fail closed" means: "treat the rule + as a 'Deny all' rule". maxProperties: 1 minProperties: 1 properties: @@ -6067,9 +5471,6 @@ spec: description: |- Namespaces defines a way to select all pods within a set of Namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: matchExpressions: description: @@ -6099,11 +5500,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6119,14 +5522,11 @@ spec: Pods defines a way to select a set of pods in a set of namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -6157,11 +5557,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6174,8 +5576,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -6206,11 +5608,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6222,140 +5626,206 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object - maxItems: 100 + maxItems: 25 minItems: 1 type: array name: description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - BaselineAdminNetworkPolicies. - - - Support: Core + Name is an identifier for this rule, that may be no more than + 100 characters in length. This field should be used by the implementation + to help improve observability, readability and error-reporting + for any applied policies. maxLength: 100 type: string - ports: + protocols: description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of ports which should be matched on - the pods selected for this policy i.e the subject of the policy. - So it matches on the destination port for the ingress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core + Protocols allows for more fine-grain matching of traffic on + protocol-specific attributes such as the port. If + unspecified, protocol-specific attributes will not be used + to match traffic. items: description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). + ClusterNetworkPolicyProtocol describes additional protocol-specific match rules. Exactly one field must be set. maxProperties: 1 minProperties: 1 properties: - namedPort: + destinationNamedPort: description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - + DestinationNamedPort selects a destination port on a pod based on the + ContainerPort name. You can't use this in a rule that targets resources + without named ports (e.g. Nodes or Networks). type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core + sctp: + description: SCTP specific protocol matches. + minProperties: 1 properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core + tcp: + description: TCP specific protocol matches. + minProperties: 1 properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + udp: + description: UDP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object type: object - maxItems: 100 + maxItems: 25 + minItems: 1 type: array required: - action - from type: object - maxItems: 100 + maxItems: 25 type: array - subject: + priority: description: |- - Subject defines the pods to which this BaselineAdminNetworkPolicy applies. - Note that host-networked pods are not included in subject selection. - - - Support: Core + Priority is a value from 0 to 1000 indicating the precedence of + the policy within its tier. Policies with lower priority values have + higher precedence, and are checked before policies with higher priority + values in the same tier. All Admin tier rules have higher precedence than + NetworkPolicy or Baseline tier rules. + If two (or more) policies in the same tier with the same priority + could match a connection, then the implementation can apply any of the + matching policies to the connection, and there is no way for the user to + reliably determine which one it will choose. Administrators must be + careful about assigning the priorities for policies with rules that will + match many connections, and ensure that policies have unique priority + values in cases where ambiguity would be unacceptable. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + subject: + description: + Subject defines the pods to which this ClusterNetworkPolicy + applies. maxProperties: 1 minProperties: 1 properties: @@ -6390,11 +5860,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6412,8 +5884,8 @@ spec: properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -6443,11 +5915,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6460,8 +5934,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -6491,11 +5965,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6507,12 +5983,48 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object + tier: + description: |- + Tier is used as the top-level grouping for network policy prioritization. + + Policy tiers are evaluated in the following order: + * Admin tier + * NetworkPolicy tier + * Baseline tier + + ClusterNetworkPolicy can use 2 of these tiers: Admin and Baseline. + + The Admin tier takes precedence over all other policies. Policies + defined in this tier are used to set cluster-wide security rules + that cannot be overridden in the other tiers. If Admin tier has + made a final decision (Accept or Deny) on a connection, then no + further evaluation is done. + + NetworkPolicy tier is the tier for the namespaced v1.NetworkPolicy. + These policies are intended for the application developer to describe + the security policy associated with their deployments inside their + namespace. v1.NetworkPolicy always makes a final decision for selected + pods. Further evaluation only happens for Pods not selected by a + v1.NetworkPolicy. + + Baseline tier is a cluster-wide policy that can be overridden by the + v1.NetworkPolicy. If Baseline tier has made a final decision (Accept or + Deny) on a connection, then no further evaluation is done. + + If a given connection wasn't allowed or denied by any of the tiers, + the default kubernetes policy is applied, which says that + all pods can communicate with each other. + enum: + - Admin + - Baseline + type: string required: + - priority - subject + - tier type: object status: description: Status is the status to be reported by the implementation. @@ -6520,16 +6032,8 @@ spec: conditions: items: description: - "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -6570,12 +6074,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string @@ -6597,11 +6096,6 @@ spec: - metadata - spec type: object - x-kubernetes-validations: - - message: - Only one baseline admin network policy with metadata.name="default" - can be created in the cluster - rule: self.metadata.name == 'default' served: true storage: true subresources: @@ -6717,6 +6211,34 @@ rules: - update # watch for changes - watch + - apiGroups: ["crd.projectcalico.org"] + resources: + - kubecontrollersconfigurations/status + verbs: + - get + - update + # Needed for policy name migrator. + - apiGroups: ["crd.projectcalico.org"] + resources: + - networkpolicies + - stagednetworkpolicies + - globalnetworkpolicies + - stagedglobalnetworkpolicies + verbs: + - watch + - list + - get + - create + - update + - delete + # Needed for policy name migrator to check calico/node daemonset status. + - apiGroups: ["apps"] + resources: + - daemonsets + verbs: + - get + resourceNames: + - calico-node --- # Source: calico/templates/calico-node-rbac.yaml # Include a clusterrole for the calico-node DaemonSet, @@ -6781,11 +6303,10 @@ rules: verbs: - watch - list - # Watch for changes to Kubernetes (Baseline)AdminNetworkPolicies. + # Watch for changes to Kubernetes ClusterNetworkPolicies. - apiGroups: ["policy.networking.k8s.io"] resources: - - adminnetworkpolicies - - baselineadminnetworkpolicies + - clusternetworkpolicies verbs: - watch - list @@ -6903,6 +6424,14 @@ rules: - daemonsets verbs: - get + # For monitoring KubeVirt live migration. + - apiGroups: ["kubevirt.io"] + resources: + - virtualmachineinstancemigrations + verbs: + - get + - list + - watch --- # Source: calico/templates/calico-node-rbac.yaml # CNI cluster role diff --git a/manifests/calico-etcd.yaml b/manifests/calico-etcd.yaml index 22be57165c8..1872f8de315 100644 --- a/manifests/calico-etcd.yaml +++ b/manifests/calico-etcd.yaml @@ -152,6 +152,28 @@ rules: verbs: - watch - list + # Needed for policy name migrator. + - apiGroups: ["crd.projectcalico.org"] + resources: + - networkpolicies + - stagednetworkpolicies + - globalnetworkpolicies + - stagedglobalnetworkpolicies + verbs: + - watch + - list + - get + - create + - update + - delete + # Needed for policy name migrator to check calico/node daemonset status. + - apiGroups: ["apps"] + resources: + - daemonsets + verbs: + - get + resourceNames: + - calico-node --- # Source: calico/templates/calico-node-rbac.yaml # Include a clusterrole for the calico-node DaemonSet, diff --git a/manifests/calico-policy-only.yaml b/manifests/calico-policy-only.yaml index a7e29757ff9..3a82d6da8c9 100644 --- a/manifests/calico-policy-only.yaml +++ b/manifests/calico-policy-only.yaml @@ -134,6 +134,9 @@ spec: format: int32 type: integer bindMode: + enum: + - None + - NodeIP type: string communities: items: @@ -144,11 +147,14 @@ spec: pattern: ^(\d+):(\d+)$|^(\d+):(\d+):(\d+)$ type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set ignoredInterfaces: items: type: string type: array + x-kubernetes-list-type: set listenPort: maximum: 65535 minimum: 1 @@ -158,6 +164,8 @@ spec: localWorkloadPeeringIPV6: type: string logSeverityScreen: + default: Info + pattern: ^(?i)(Trace|Debug|Info|Warning|Error|Fatal)?$ type: string nodeMeshMaxRestartTime: type: string @@ -183,27 +191,36 @@ spec: items: properties: cidr: + format: cidr type: string communities: items: type: string type: array type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceClusterIPs: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceExternalIPs: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceLoadBalancerAggregation: default: Enabled enum: @@ -214,9 +231,12 @@ spec: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -255,12 +275,21 @@ spec: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -275,22 +304,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array exportV6: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -305,22 +347,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array importV4: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -335,22 +390,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array importV6: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -365,11 +433,15 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array type: object type: object @@ -424,15 +496,10 @@ spec: maxRestartTime: type: string nextHopMode: - allOf: - - enum: - - Auto - - Self - - Keep - - enum: - - Auto - - Self - - Keep + enum: + - Auto + - Self + - Keep type: string node: type: string @@ -464,11 +531,18 @@ spec: reachableBy: type: string reversePeering: - enum: - - Auto - - Manual + allOf: + - enum: + - Auto + - Manual + - enum: + - Auto + - Manual type: string sourceAddress: + enum: + - UseNodeIP + - None type: string ttlSecurity: type: integer @@ -557,6 +631,10 @@ spec: properties: classes: items: + enum: + - Agent + - BGP + - Routes type: string type: array node: @@ -578,6 +656,9 @@ spec: routerID: type: string state: + enum: + - Ready + - NotReady type: string version: type: string @@ -591,6 +672,9 @@ spec: routerID: type: string state: + enum: + - Ready + - NotReady type: string version: type: string @@ -614,8 +698,20 @@ spec: since: type: string state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close type: string type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer type: string type: object type: array @@ -627,8 +723,20 @@ spec: since: type: string state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close type: string type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer type: string type: object type: array @@ -658,9 +766,18 @@ spec: peerIP: type: string sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer type: string type: object type: + enum: + - FIB + - RIB type: string type: object type: array @@ -678,9 +795,18 @@ spec: peerIP: type: string sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer type: string type: object type: + enum: + - FIB + - RIB type: string type: object type: array @@ -1034,15 +1160,9 @@ spec: if it detects the current value is 2 (strict mode that hurts performance). When set to "Strict", Felix will not modify the JIT hardening setting. [Default: Auto] type: string - bpfKubeProxyEndpointSlicesEnabled: + bpfKubeProxyHealthzPort: description: |- - BPFKubeProxyEndpointSlicesEnabled is deprecated and has no effect. BPF - kube-proxy always accepts endpoint slices. This option will be removed in - the next release. - type: boolean - bpfKubeProxyHealtzPort: - description: |- - BPFKubeProxyHealtzPort, in BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. + BPFKubeProxyHealthzPort, in BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. The health check server is used by external load balancers to determine if this node should receive traffic. [Default: 10256] type: integer bpfKubeProxyIptablesCleanupEnabled: @@ -1083,6 +1203,23 @@ spec: [Default: Off]. pattern: ^(?i)(Off|Info|Debug)?$ type: string + bpfMaglevMaxEndpointsPerService: + description: |- + BPFMaglevMaxEndpointsPerService is the maximum number of endpoints + expected to be part of a single Maglev-enabled service. + + Influences the size of the per-service Maglev lookup-tables generated by Felix + and thus the amount of memory reserved. + + [Default: 100] + type: integer + bpfMaglevMaxServices: + description: |- + BPFMaglevMaxServices is the maximum number of expected Maglev-enabled + services that Felix will allocate lookup-tables for. + + [Default: 100] + type: integer bpfMapSizeConntrack: description: |- BPFMapSizeConntrack sets the size for the conntrack map. This map must be large enough to hold @@ -1171,17 +1308,14 @@ spec: type: string bpfRedirectToPeer: description: |- - BPFRedirectToPeer controls which whether it is allowed to forward straight to the - peer side of the workload devices. It is allowed for any host L2 devices by default - (L2Only), but it breaks TCP dump on the host side of workload device as it bypasses - it on ingress. Value of Enabled also allows redirection from L3 host devices like - IPIP tunnel or Wireguard directly to the peer side of the workload's device. This - makes redirection faster, however, it breaks tools like tcpdump on the peer side. - Use Enabled with caution. [Default: L2Only] + BPFRedirectToPeer controls whether traffic may be forwarded directly to the peer side of a workload’s device. + Note that the legacy "L2Only" option is now deprecated and if set it is treated like "Enabled. + Setting this option to "Enabled" allows direct redirection (including from L3 host devices such as IPIP tunnels or WireGuard), + which can improve redirection performance but causes the redirected packets to bypass the host‑side ingress path. + As a result, packet‑capture tools on the host side of the workload device (for example, tcpdump) will not see that traffic. [Default: Enabled] enum: - Enabled - Disabled - - L2Only type: string cgroupV2Path: description: @@ -1532,6 +1666,10 @@ spec: Warning: changing this on a running system can leave "orphaned" rules in the "other" backend. These should be cleaned up to avoid confusing interactions. + enum: + - Legacy + - NFT + - Auto pattern: ^(?i)(Auto|Legacy|NFT)?$ type: string iptablesFilterAllowAction: @@ -1547,26 +1685,10 @@ spec: with an iptables "DROP" action. If you want to use "REJECT" action instead you can configure it in here. pattern: ^(?i)(Drop|Reject)?$ type: string - iptablesLockFilePath: - description: |- - IptablesLockFilePath is the location of the iptables lock file. You may need to change this - if the lock file is not in its standard location (for example if you have mapped it into Felix's - container at a different path). [Default: /run/xtables.lock] - type: string iptablesLockProbeInterval: description: |- - IptablesLockProbeInterval when IptablesLockTimeout is enabled: the time that Felix will wait between - attempts to acquire the iptables lock if it is not available. Lower values make Felix more - responsive when the lock is contended, but use more CPU. [Default: 50ms] - pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ - type: string - iptablesLockTimeout: - description: |- - IptablesLockTimeout is the time that Felix itself will wait for the iptables lock (rather than delegating the - lock handling to the `iptables` command). - - Deprecated: `iptables-restore` v1.8+ always takes the lock, so enabling this feature results in deadlock. - [Default: 0s disabled] + IptablesLockProbeInterval configures the interval between attempts to claim + the xtables lock. Shorter intervals are more responsive but use more CPU. [Default: 50ms] pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ type: string iptablesMangleAllowAction: @@ -1626,6 +1748,19 @@ spec: pattern: ^.* x-kubernetes-int-or-string: true type: array + logActionRateLimit: + description: |- + LogActionRateLimit sets the rate of hitting a Log action. The value must be in the format "N/unit", + where N is a number and unit is one of: second, minute, hour, or day. For example: "10/second" or "100/hour". + pattern: ^[1-9]\d{0,3}/(?:second|minute|hour|day)$ + type: string + logActionRateLimitBurst: + description: + LogActionRateLimitBurst sets the rate limit burst of + hitting a Log action when LogActionRateLimit is enabled. + maximum: 9999 + minimum: 0 + type: integer logDebugFilenameRegex: description: |- LogDebugFilenameRegex controls which source code files have their Debug log output included in the logs. @@ -1638,9 +1773,17 @@ spec: none to disable file logging. [Default: /var/log/calico/felix.log]" type: string logPrefix: - description: - "LogPrefix is the log prefix that Felix uses when rendering - LOG rules. [Default: calico-packet]" + description: |- + LogPrefix is the log prefix that Felix uses when rendering LOG rules. It is possible to use the following specifiers + to include extra information in the log prefix. + - %t: Tier name. + - %k: Kind (short names). + - %n: Policy or profile name. + - %p: Policy or profile name (namespace/name for namespaced kinds or just name for non namespaced kinds). + Calico includes ": " characters at the end of the generated log prefix. + Note that iptables shows up to 29 characters for the log prefix and nftables up to 127 characters. Extra characters are truncated. + [Default: calico-packet] + pattern: "^([a-zA-Z0-9%: /_-])*$" type: string logSeverityFile: description: @@ -1744,9 +1887,10 @@ spec: format: int32 type: integer nftablesMode: + default: Auto description: "NFTablesMode configures nftables support in Felix. [Default: - Disabled]" + Auto]" enum: - Disabled - Enabled @@ -1782,6 +1926,21 @@ spec: PrometheusGoMetricsEnabled disables Go runtime metrics collection, which the Prometheus client does by default, when set to false. This reduces the number of metrics reported, reducing Prometheus load. [Default: true] type: boolean + prometheusMetricsCAFile: + description: |- + PrometheusMetricsCAFile defines the absolute path to the TLS CA certificate file used for securing the /metrics endpoint. + This certificate must be valid and accessible by the calico-node process. + type: string + prometheusMetricsCertFile: + description: |- + PrometheusMetricsCertFile defines the absolute path to the TLS certificate file used for securing the /metrics endpoint. + This certificate must be valid and accessible by the calico-node process. + type: string + prometheusMetricsClientAuth: + description: |- + PrometheusMetricsClientAuth specifies the client authentication type for the /metrics endpoint. + This determines how the server validates client certificates. Default is "RequireAndVerifyClientCert". + type: string prometheusMetricsEnabled: description: "PrometheusMetricsEnabled enables the Prometheus metrics @@ -1792,6 +1951,11 @@ spec: "PrometheusMetricsHost is the host that the Prometheus metrics server should bind to. [Default: empty]" type: string + prometheusMetricsKeyFile: + description: |- + PrometheusMetricsKeyFile defines the absolute path to the private key file corresponding to the TLS certificate + used for securing the /metrics endpoint. The private key must be valid and accessible by the calico-node process. + type: string prometheusMetricsPort: description: "PrometheusMetricsPort is the TCP port that the Prometheus @@ -2075,6 +2239,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -2084,6 +2253,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2114,6 +2284,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2144,11 +2315,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -2160,8 +2338,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -2184,6 +2366,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2214,6 +2397,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2233,6 +2417,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -2242,6 +2431,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2272,6 +2462,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2302,11 +2493,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -2318,8 +2516,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -2342,6 +2544,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2372,6 +2575,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2393,6 +2597,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array preDNAT: @@ -2402,11 +2608,18 @@ spec: serviceAccountSelector: type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2445,6 +2658,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2483,6 +2697,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set interfaceName: type: string node: @@ -2493,6 +2708,8 @@ spec: name: type: string port: + maximum: 65535 + minimum: 0 type: integer protocol: anyOf: @@ -2510,6 +2727,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2546,6 +2764,9 @@ spec: properties: affinity: type: string + affinityClaimTime: + format: date-time + type: string allocations: items: type: integer @@ -2556,6 +2777,10 @@ spec: attributes: items: properties: + alternate: + additionalProperties: + type: string + type: object handle_id: type: string secondary: @@ -2625,6 +2850,11 @@ spec: properties: autoAllocateBlocks: type: boolean + kubeVirtVMAddressPersistence: + enum: + - Enabled + - Disabled + type: string maxBlocksPerHost: maximum: 2147483647 minimum: 0 @@ -2715,39 +2945,47 @@ spec: properties: allowedUses: items: + enum: + - Workload + - Tunnel + - LoadBalancer type: string type: array + x-kubernetes-list-type: set assignmentMode: + default: Automatic enum: - Automatic - Manual type: string blockSize: + maximum: 128 + minimum: 0 type: integer cidr: + format: cidr type: string disableBGPExport: type: boolean disabled: type: boolean - ipip: - properties: - enabled: - type: boolean - mode: - type: string - type: object ipipMode: + enum: + - Never + - Always + - CrossSubnet type: string namespaceSelector: type: string - nat-outgoing: - type: boolean natOutgoing: type: boolean nodeSelector: type: string vxlanMode: + enum: + - Never + - Always + - CrossSubnet type: string required: - cidr @@ -2786,9 +3024,11 @@ spec: spec: properties: reservedCIDRs: + format: cidr items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2828,6 +3068,10 @@ spec: loadBalancer: properties: assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly type: string type: object namespace: @@ -2840,6 +3084,9 @@ spec: hostEndpoint: properties: autoCreate: + enum: + - Enabled + - Disabled type: string createDefaultHostEndpoint: type: string @@ -2853,7 +3100,8 @@ spec: items: type: string type: array - interfaceSelector: + x-kubernetes-list-type: set + interfacePattern: type: string labels: additionalProperties: @@ -2869,6 +3117,9 @@ spec: reconcilerPeriod: type: string syncLabels: + enum: + - Enabled + - Disabled type: string type: object policy: @@ -2876,6 +3127,15 @@ spec: reconcilerPeriod: type: string type: object + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object serviceAccount: properties: reconcilerPeriod: @@ -2889,14 +3149,30 @@ spec: type: object debugProfilePort: format: int32 + maximum: 65535 + minimum: 0 type: integer etcdV3CompactionPeriod: type: string healthChecks: + default: Enabled + enum: + - Enabled + - Disabled type: string logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic type: string prometheusMetricsPort: + maximum: 65535 + minimum: 0 type: integer required: - controllers @@ -2914,6 +3190,10 @@ spec: loadBalancer: properties: assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly type: string type: object namespace: @@ -2926,6 +3206,9 @@ spec: hostEndpoint: properties: autoCreate: + enum: + - Enabled + - Disabled type: string createDefaultHostEndpoint: type: string @@ -2939,7 +3222,8 @@ spec: items: type: string type: array - interfaceSelector: + x-kubernetes-list-type: set + interfacePattern: type: string labels: additionalProperties: @@ -2955,6 +3239,9 @@ spec: reconcilerPeriod: type: string syncLabels: + enum: + - Enabled + - Disabled type: string type: object policy: @@ -2962,6 +3249,15 @@ spec: reconcilerPeriod: type: string type: object + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object serviceAccount: properties: reconcilerPeriod: @@ -2975,14 +3271,30 @@ spec: type: object debugProfilePort: format: int32 + maximum: 65535 + minimum: 0 type: integer etcdV3CompactionPeriod: type: string healthChecks: + default: Enabled + enum: + - Enabled + - Disabled type: string logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic type: string prometheusMetricsPort: + maximum: 65535 + minimum: 0 type: integer required: - controllers @@ -2991,6 +3303,8 @@ spec: type: object served: true storage: true + subresources: + status: {} --- # Source: calico/templates/kdd-crds.yaml apiVersion: apiextensions.k8s.io/v1 @@ -3025,6 +3339,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3034,6 +3353,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3064,6 +3384,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3094,11 +3415,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3110,8 +3438,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3134,6 +3466,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3164,6 +3497,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3183,6 +3517,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3192,6 +3531,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3222,6 +3562,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3252,11 +3593,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3268,8 +3616,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3292,6 +3644,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3322,6 +3675,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3341,6 +3695,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array selector: @@ -3348,11 +3704,18 @@ spec: serviceAccountSelector: type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -3391,6 +3754,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -3433,6 +3797,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3442,6 +3811,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3472,6 +3842,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3502,11 +3873,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3518,8 +3896,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3542,6 +3924,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3572,6 +3955,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3591,6 +3975,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3600,6 +3989,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3630,6 +4020,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3660,11 +4051,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3676,8 +4074,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3700,6 +4102,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3730,6 +4133,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3751,6 +4155,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array preDNAT: @@ -3760,13 +4166,25 @@ spec: serviceAccountSelector: type: string stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -4012,8 +4430,16 @@ spec: policyTypes: items: type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string type: object type: object @@ -4053,6 +4479,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -4062,6 +4493,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4092,6 +4524,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4122,11 +4555,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -4138,8 +4578,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -4162,6 +4606,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4192,6 +4637,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4211,6 +4657,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -4220,6 +4671,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4250,6 +4702,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4280,11 +4733,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -4296,8 +4756,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -4320,6 +4784,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4350,6 +4815,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4369,6 +4835,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array selector: @@ -4376,13 +4844,25 @@ spec: serviceAccountSelector: type: string stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -4418,14 +4898,36 @@ spec: spec: properties: defaultAction: - enum: - - Pass - - Deny + allOf: + - enum: + - Allow + - Deny + - Log + - Pass + - enum: + - Pass + - Deny type: string order: type: number type: object + required: + - metadata + - spec type: object + x-kubernetes-validations: + - message: The 'kube-admin' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-admin' ? self.spec.defaultAction == + 'Pass' : true" + - message: The 'kube-baseline' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-baseline' ? self.spec.defaultAction + == 'Pass' : true" + - message: The 'default' tier must have default action 'Deny' + rule: + "self.metadata.name == 'default' ? self.spec.defaultAction == 'Deny' + : true" served: true storage: true --- @@ -4434,35 +4936,35 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/30 - policy.networking.k8s.io/bundle-version: v0.1.1 - policy.networking.k8s.io/channel: experimental - creationTimestamp: null - name: adminnetworkpolicies.policy.networking.k8s.io + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/300 + policy.networking.k8s.io/bundle-version: v0.1.7 + policy.networking.k8s.io/channel: standard + name: clusternetworkpolicies.policy.networking.k8s.io spec: group: policy.networking.k8s.io names: - kind: AdminNetworkPolicy - listKind: AdminNetworkPolicyList - plural: adminnetworkpolicies + kind: ClusterNetworkPolicy + listKind: ClusterNetworkPolicyList + plural: clusternetworkpolicies shortNames: - - anp - singular: adminnetworkpolicy + - cnp + singular: clusternetworkpolicy scope: Cluster versions: - additionalPrinterColumns: + - jsonPath: .spec.tier + name: Tier + type: string - jsonPath: .spec.priority name: Priority type: string - jsonPath: .metadata.creationTimestamp name: Age type: date - name: v1alpha1 + name: v1alpha2 schema: openAPIV3Schema: - description: |- - AdminNetworkPolicy is a cluster level resource that is part of the - AdminNetworkPolicy API. + description: ClusterNetworkPolicy is a cluster-wide network policy resource. properties: apiVersion: description: |- @@ -4482,172 +4984,233 @@ spec: metadata: type: object spec: - description: Specification of the desired behavior of AdminNetworkPolicy. + description: Spec defines the desired behavior of ClusterNetworkPolicy. properties: egress: description: |- Egress is the list of Egress rules to be applied to the selected pods. - A total of 100 rules will be allowed in each ANP instance. - The relative precedence of egress rules within a single ANP object (all of - which share the priority) will be determined by the order in which the rule - is written. Thus, a rule that appears at the top of the egress rules - would take the highest precedence. - ANPs with no egress rules do not affect egress traffic. + A maximum of 25 rules is allowed in this block. - Support: Core + The relative precedence of egress rules within a single CNP object + (all of which share the priority) will be determined by the order + in which the rule is written. + Thus, a rule that appears at the top of the egress rules + would take the highest precedence. + CNPs with no egress rules do not affect egress traffic. items: description: |- - AdminNetworkPolicyEgressRule describes an action to take on a particular - set of traffic originating from pods selected by a AdminNetworkPolicy's + ClusterNetworkPolicyEgressRule describes an action to take on a particular + set of traffic originating from pods selected by a ClusterNetworkPolicy's Subject field. + properties: action: description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic (even if it would otherwise have been denied by NetworkPolicy) - Deny: denies the selected traffic - Pass: instructs the selected traffic to skip any remaining ANP rules, and - then pass execution to any NetworkPolicies that select the pod. - If the pod is not selected by any NetworkPolicies then execution - is passed to any BaselineAdminNetworkPolicies that select the pod. + Action specifies the effect this rule will have on matching + traffic. Currently the following actions are supported: + - Accept: Accepts the selected traffic, allowing it to + egress. No further ClusterNetworkPolicy or NetworkPolicy + rules will be processed. - Support: Core + - Deny: Drops the selected traffic. No further + ClusterNetworkPolicy or NetworkPolicy rules will be + processed. + + - Pass: Skips all further ClusterNetworkPolicy rules in the + current tier for the selected traffic, and passes + evaluation to the next tier. enum: - - Allow + - Accept - Deny - Pass type: string name: description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - AdminNetworkPolicies. - - - Support: Core + Name is an identifier for this rule, that may be no more than + 100 characters in length. This field should be used by the implementation + to help improve observability, readability and error-reporting + for any applied policies. maxLength: 100 type: string - ports: + protocols: description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of destination ports for the outgoing egress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core + Protocols allows for more fine-grain matching of traffic on + protocol-specific attributes such as the port. If + unspecified, protocol-specific attributes will not be used + to match traffic. items: description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). + ClusterNetworkPolicyProtocol describes additional protocol-specific match rules. Exactly one field must be set. maxProperties: 1 minProperties: 1 properties: - namedPort: + destinationNamedPort: description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - + DestinationNamedPort selects a destination port on a pod based on the + ContainerPort name. You can't use this in a rule that targets resources + without named ports (e.g. Nodes or Networks). type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core + sctp: + description: SCTP specific protocol matches. + minProperties: 1 properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core + tcp: + description: TCP specific protocol matches. + minProperties: 1 properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + udp: + description: UDP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object type: object - maxItems: 100 + maxItems: 25 + minItems: 1 type: array to: description: |- - To is the List of destinations whose traffic this rule applies to. - If any AdminNetworkPolicyEgressPeer matches the destination of outgoing - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core + To is the list of destinations whose traffic this rule applies to. If any + element matches the destination of outgoing traffic then the specified + action is applied. This field must be defined and contain at least one + item. items: description: |- - AdminNetworkPolicyEgressPeer defines a peer to allow traffic to. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. + ClusterNetworkPolicyEgressPeer defines a peer to allow traffic to. + + Exactly one of the fields must be set for a given peer and this is enforced + by the validation rules on the CRD. If an implementation sees no fields are + set then it can infer that the deployed CRD is of an incompatible version + with an unknown field. In that case it should fail closed. + + For "Accept" rules, "fail closed" means: "treat the rule as matching no + traffic". For "Deny" and "Pass" rules, "fail closed" means: "treat the rule + as a 'Deny all' rule". maxProperties: 1 minProperties: 1 properties: @@ -4655,9 +5218,6 @@ spec: description: |- Namespaces defines a way to select all pods within a set of Namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: matchExpressions: description: @@ -4687,11 +5247,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -4708,1206 +5270,37 @@ spec: This is intended for representing entities that live outside the cluster, which can't be selected by pods, namespaces and nodes peers, but note that cluster-internal traffic will be checked against the rule as - well. So if you Allow or Deny traffic to `"0.0.0.0/0"`, that will allow + well. So if you Accept or Deny traffic to `"0.0.0.0/0"`, that will allow or deny all IPv4 pod-to-pod traffic as well. If you don't want that, add a rule that Passes all pod traffic before the Networks rule. - Each item in Networks should be provided in the CIDR format and should be IPv4 or IPv6, for example "10.0.0.0/8" or "fd00::/8". - - Networks can have upto 25 CIDRs specified. - - - Support: Extended - - - + Networks can have up to 25 CIDRs specified. items: description: |- - CIDR is an IP address range in CIDR notation (for example, "10.0.0.0/8" or "fd00::/8"). - This string must be validated by implementations using net.ParseCIDR - TODO: Introduce CEL CIDR validation regex isCIDR() in Kube 1.31 when it is available. + CIDR is an IP address range in CIDR notation + (for example, "10.0.0.0/8" or "fd00::/8"). maxLength: 43 type: string x-kubernetes-validations: - - message: - CIDR must be either an IPv4 or IPv6 address. - IPv4 address embedded in IPv6 addresses are not - supported - rule: self.contains(':') != self.contains('.') + - message: Invalid CIDR format provided + rule: isCIDR(self) maxItems: 25 minItems: 1 type: array x-kubernetes-list-type: set - nodes: - description: |- - Nodes defines a way to select a set of nodes in - the cluster. This field follows standard label selector - semantics; if present but empty, it selects all Nodes. - - - Support: Extended - - - - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: |- - Pods defines a way to select a set of pods in - a set of namespaces. Note that host-networked pods - are not included in this type of peer. - - - Support: Core - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - maxItems: 100 - minItems: 1 - type: array - required: - - action - - to - type: object - x-kubernetes-validations: - - message: - networks/nodes peer cannot be set with namedPorts since - there are no namedPorts for networks/nodes - rule: - "!(self.to.exists(peer, has(peer.networks) || has(peer.nodes)) - && has(self.ports) && self.ports.exists(port, has(port.namedPort)))" - maxItems: 100 - type: array - ingress: - description: |- - Ingress is the list of Ingress rules to be applied to the selected pods. - A total of 100 rules will be allowed in each ANP instance. - The relative precedence of ingress rules within a single ANP object (all of - which share the priority) will be determined by the order in which the rule - is written. Thus, a rule that appears at the top of the ingress rules - would take the highest precedence. - ANPs with no ingress rules do not affect ingress traffic. - - - Support: Core - items: - description: |- - AdminNetworkPolicyIngressRule describes an action to take on a particular - set of traffic destined for pods selected by an AdminNetworkPolicy's - Subject field. - properties: - action: - description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic (even if it would otherwise have been denied by NetworkPolicy) - Deny: denies the selected traffic - Pass: instructs the selected traffic to skip any remaining ANP rules, and - then pass execution to any NetworkPolicies that select the pod. - If the pod is not selected by any NetworkPolicies then execution - is passed to any BaselineAdminNetworkPolicies that select the pod. - - - Support: Core - enum: - - Allow - - Deny - - Pass - type: string - from: - description: |- - From is the list of sources whose traffic this rule applies to. - If any AdminNetworkPolicyIngressPeer matches the source of incoming - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core - items: - description: |- - AdminNetworkPolicyIngressPeer defines an in-cluster peer to allow traffic from. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: |- - Namespaces defines a way to select all pods within a set of Namespaces. - Note that host-networked pods are not included in this type of peer. - - - Support: Core - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: |- - Pods defines a way to select a set of pods in - a set of namespaces. Note that host-networked pods - are not included in this type of peer. - - - Support: Core - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - maxItems: 100 - minItems: 1 - type: array - name: - description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - AdminNetworkPolicies. - - - Support: Core - maxLength: 100 - type: string - ports: - description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of ports which should be matched on - the pods selected for this policy i.e the subject of the policy. - So it matches on the destination port for the ingress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core - items: - description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). - Exactly one field must be set. - maxProperties: 1 - minProperties: 1 - properties: - namedPort: - description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - - type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core - properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol - type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core - properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start - type: object - type: object - maxItems: 100 - type: array - required: - - action - - from - type: object - maxItems: 100 - type: array - priority: - description: |- - Priority is a value from 0 to 1000. Rules with lower priority values have - higher precedence, and are checked before rules with higher priority values. - All AdminNetworkPolicy rules have higher precedence than NetworkPolicy or - BaselineAdminNetworkPolicy rules - The behavior is undefined if two ANP objects have same priority. - - - Support: Core - format: int32 - maximum: 1000 - minimum: 0 - type: integer - subject: - description: |- - Subject defines the pods to which this AdminNetworkPolicy applies. - Note that host-networked pods are not included in subject selection. - - - Support: Core - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: Namespaces is used to select pods via namespace selectors. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: - Pods is used to select pods via namespace AND pod - selectors. - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - required: - - priority - - subject - type: object - status: - description: Status is the status to be reported by the implementation. - properties: - conditions: - items: - description: - "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - required: - - conditions - type: object - required: - - metadata - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: null - storedVersions: null ---- -# Source: calico/templates/kdd-crds.yaml -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/30 - policy.networking.k8s.io/bundle-version: v0.1.1 - policy.networking.k8s.io/channel: experimental - creationTimestamp: null - name: baselineadminnetworkpolicies.policy.networking.k8s.io -spec: - group: policy.networking.k8s.io - names: - kind: BaselineAdminNetworkPolicy - listKind: BaselineAdminNetworkPolicyList - plural: baselineadminnetworkpolicies - shortNames: - - banp - singular: baselineadminnetworkpolicy - scope: Cluster - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: |- - BaselineAdminNetworkPolicy is a cluster level resource that is part of the - AdminNetworkPolicy API. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: Specification of the desired behavior of BaselineAdminNetworkPolicy. - properties: - egress: - description: |- - Egress is the list of Egress rules to be applied to the selected pods if - they are not matched by any AdminNetworkPolicy or NetworkPolicy rules. - A total of 100 Egress rules will be allowed in each BANP instance. - The relative precedence of egress rules within a single BANP object - will be determined by the order in which the rule is written. - Thus, a rule that appears at the top of the egress rules - would take the highest precedence. - BANPs with no egress rules do not affect egress traffic. - - - Support: Core - items: - description: |- - BaselineAdminNetworkPolicyEgressRule describes an action to take on a particular - set of traffic originating from pods selected by a BaselineAdminNetworkPolicy's - Subject field. - - properties: - action: - description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic - Deny: denies the selected traffic - - - Support: Core - enum: - - Allow - - Deny - type: string - name: - description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - BaselineAdminNetworkPolicies. - - - Support: Core - maxLength: 100 - type: string - ports: - description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of destination ports for the outgoing egress traffic. - If Ports is not set then the rule does not filter traffic via port. - items: - description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). - Exactly one field must be set. - maxProperties: 1 - minProperties: 1 - properties: - namedPort: - description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - - type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core - properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol - type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core - properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start - type: object - type: object - maxItems: 100 - type: array - to: - description: |- - To is the list of destinations whose traffic this rule applies to. - If any AdminNetworkPolicyEgressPeer matches the destination of outgoing - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core - items: - description: |- - AdminNetworkPolicyEgressPeer defines a peer to allow traffic to. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: |- - Namespaces defines a way to select all pods within a set of Namespaces. - Note that host-networked pods are not included in this type of peer. - - - Support: Core - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - networks: - description: |- - Networks defines a way to select peers via CIDR blocks. - This is intended for representing entities that live outside the cluster, - which can't be selected by pods, namespaces and nodes peers, but note - that cluster-internal traffic will be checked against the rule as - well. So if you Allow or Deny traffic to `"0.0.0.0/0"`, that will allow - or deny all IPv4 pod-to-pod traffic as well. If you don't want that, - add a rule that Passes all pod traffic before the Networks rule. - - - Each item in Networks should be provided in the CIDR format and should be - IPv4 or IPv6, for example "10.0.0.0/8" or "fd00::/8". - - - Networks can have upto 25 CIDRs specified. - - - Support: Extended - - - - items: - description: |- - CIDR is an IP address range in CIDR notation (for example, "10.0.0.0/8" or "fd00::/8"). - This string must be validated by implementations using net.ParseCIDR - TODO: Introduce CEL CIDR validation regex isCIDR() in Kube 1.31 when it is available. - maxLength: 43 - type: string - x-kubernetes-validations: - - message: - CIDR must be either an IPv4 or IPv6 address. - IPv4 address embedded in IPv6 addresses are not - supported - rule: self.contains(':') != self.contains('.') - maxItems: 25 - minItems: 1 - type: array - x-kubernetes-list-type: set - nodes: - description: |- - Nodes defines a way to select a set of nodes in - the cluster. This field follows standard label selector - semantics; if present but empty, it selects all Nodes. - - - Support: Extended - - - - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: + pods: description: |- Pods defines a way to select a set of pods in a set of namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -5938,11 +5331,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -5955,8 +5350,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -5987,11 +5382,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6003,73 +5400,80 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object - maxItems: 100 + maxItems: 25 minItems: 1 type: array required: - action - to type: object - x-kubernetes-validations: - - message: - networks/nodes peer cannot be set with namedPorts since - there are no namedPorts for networks/nodes - rule: - "!(self.to.exists(peer, has(peer.networks) || has(peer.nodes)) - && has(self.ports) && self.ports.exists(port, has(port.namedPort)))" - maxItems: 100 + maxItems: 25 type: array ingress: description: |- - Ingress is the list of Ingress rules to be applied to the selected pods - if they are not matched by any AdminNetworkPolicy or NetworkPolicy rules. - A total of 100 Ingress rules will be allowed in each BANP instance. - The relative precedence of ingress rules within a single BANP object - will be determined by the order in which the rule is written. - Thus, a rule that appears at the top of the ingress rules - would take the highest precedence. - BANPs with no ingress rules do not affect ingress traffic. + Ingress is the list of Ingress rules to be applied to the selected pods. + A maximum of 25 rules is allowed in this block. - Support: Core + The relative precedence of ingress rules within a single CNP object + (all of which share the priority) will be determined by the order + in which the rule is written. + Thus, a rule that appears at the top of the ingress rules + would take the highest precedence. + CNPs with no ingress rules do not affect ingress traffic. items: description: |- - BaselineAdminNetworkPolicyIngressRule describes an action to take on a particular - set of traffic destined for pods selected by a BaselineAdminNetworkPolicy's + ClusterNetworkPolicyIngressRule describes an action to take on a particular + set of traffic destined for pods selected by a ClusterNetworkPolicy's Subject field. properties: action: description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic - Deny: denies the selected traffic + Action specifies the effect this rule will have on matching + traffic. Currently the following actions are supported: + + - Accept: Accepts the selected traffic, allowing it into + the destination. No further ClusterNetworkPolicy or + NetworkPolicy rules will be processed. + Note: while Accept ensures traffic is accepted by + Kubernetes network policy, it is still possible that the + packet is blocked in other ways: custom nftable rules, + high-layers e.g. service mesh. - Support: Core + - Deny: Drops the selected traffic. No further + ClusterNetworkPolicy or NetworkPolicy rules will be + processed. + + - Pass: Skips all further ClusterNetworkPolicy rules in the + current tier for the selected traffic, and passes + evaluation to the next tier. enum: - - Allow + - Accept - Deny + - Pass type: string from: description: |- From is the list of sources whose traffic this rule applies to. - If any AdminNetworkPolicyIngressPeer matches the source of incoming + If any element matches the source of incoming traffic then the specified action is applied. This field must be defined and contain at least one item. - - - Support: Core items: description: |- - AdminNetworkPolicyIngressPeer defines an in-cluster peer to allow traffic from. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. + ClusterNetworkPolicyIngressPeer defines a peer to allow traffic from. + + Exactly one of the fields must be set for a given peer and this is enforced + by the validation rules on the CRD. If an implementation sees no fields are + set then it can infer that the deployed CRD is of an incompatible version + with an unknown field. In that case it should fail closed. + + For "Accept" rules, "fail closed" means: "treat the rule as matching no + traffic". For "Deny" and "Pass" rules, "fail closed" means: "treat the rule + as a 'Deny all' rule". maxProperties: 1 minProperties: 1 properties: @@ -6077,9 +5481,6 @@ spec: description: |- Namespaces defines a way to select all pods within a set of Namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: matchExpressions: description: @@ -6109,11 +5510,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6129,14 +5532,11 @@ spec: Pods defines a way to select a set of pods in a set of namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -6167,11 +5567,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6184,8 +5586,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -6216,11 +5618,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6232,140 +5636,206 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object - maxItems: 100 + maxItems: 25 minItems: 1 type: array name: description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - BaselineAdminNetworkPolicies. - - - Support: Core + Name is an identifier for this rule, that may be no more than + 100 characters in length. This field should be used by the implementation + to help improve observability, readability and error-reporting + for any applied policies. maxLength: 100 type: string - ports: + protocols: description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of ports which should be matched on - the pods selected for this policy i.e the subject of the policy. - So it matches on the destination port for the ingress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core + Protocols allows for more fine-grain matching of traffic on + protocol-specific attributes such as the port. If + unspecified, protocol-specific attributes will not be used + to match traffic. items: description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). + ClusterNetworkPolicyProtocol describes additional protocol-specific match rules. Exactly one field must be set. maxProperties: 1 minProperties: 1 properties: - namedPort: + destinationNamedPort: description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - + DestinationNamedPort selects a destination port on a pod based on the + ContainerPort name. You can't use this in a rule that targets resources + without named ports (e.g. Nodes or Networks). type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core + sctp: + description: SCTP specific protocol matches. + minProperties: 1 properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core + tcp: + description: TCP specific protocol matches. + minProperties: 1 properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + udp: + description: UDP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object type: object - maxItems: 100 + maxItems: 25 + minItems: 1 type: array required: - action - from type: object - maxItems: 100 + maxItems: 25 type: array - subject: + priority: description: |- - Subject defines the pods to which this BaselineAdminNetworkPolicy applies. - Note that host-networked pods are not included in subject selection. - - - Support: Core + Priority is a value from 0 to 1000 indicating the precedence of + the policy within its tier. Policies with lower priority values have + higher precedence, and are checked before policies with higher priority + values in the same tier. All Admin tier rules have higher precedence than + NetworkPolicy or Baseline tier rules. + If two (or more) policies in the same tier with the same priority + could match a connection, then the implementation can apply any of the + matching policies to the connection, and there is no way for the user to + reliably determine which one it will choose. Administrators must be + careful about assigning the priorities for policies with rules that will + match many connections, and ensure that policies have unique priority + values in cases where ambiguity would be unacceptable. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + subject: + description: + Subject defines the pods to which this ClusterNetworkPolicy + applies. maxProperties: 1 minProperties: 1 properties: @@ -6400,11 +5870,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6422,8 +5894,8 @@ spec: properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -6453,11 +5925,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6470,8 +5944,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -6501,11 +5975,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6517,12 +5993,48 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object + tier: + description: |- + Tier is used as the top-level grouping for network policy prioritization. + + Policy tiers are evaluated in the following order: + * Admin tier + * NetworkPolicy tier + * Baseline tier + + ClusterNetworkPolicy can use 2 of these tiers: Admin and Baseline. + + The Admin tier takes precedence over all other policies. Policies + defined in this tier are used to set cluster-wide security rules + that cannot be overridden in the other tiers. If Admin tier has + made a final decision (Accept or Deny) on a connection, then no + further evaluation is done. + + NetworkPolicy tier is the tier for the namespaced v1.NetworkPolicy. + These policies are intended for the application developer to describe + the security policy associated with their deployments inside their + namespace. v1.NetworkPolicy always makes a final decision for selected + pods. Further evaluation only happens for Pods not selected by a + v1.NetworkPolicy. + + Baseline tier is a cluster-wide policy that can be overridden by the + v1.NetworkPolicy. If Baseline tier has made a final decision (Accept or + Deny) on a connection, then no further evaluation is done. + + If a given connection wasn't allowed or denied by any of the tiers, + the default kubernetes policy is applied, which says that + all pods can communicate with each other. + enum: + - Admin + - Baseline + type: string required: + - priority - subject + - tier type: object status: description: Status is the status to be reported by the implementation. @@ -6530,16 +6042,8 @@ spec: conditions: items: description: - "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -6580,12 +6084,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string @@ -6607,11 +6106,6 @@ spec: - metadata - spec type: object - x-kubernetes-validations: - - message: - Only one baseline admin network policy with metadata.name="default" - can be created in the cluster - rule: self.metadata.name == 'default' served: true storage: true subresources: @@ -6727,6 +6221,34 @@ rules: - update # watch for changes - watch + - apiGroups: ["crd.projectcalico.org"] + resources: + - kubecontrollersconfigurations/status + verbs: + - get + - update + # Needed for policy name migrator. + - apiGroups: ["crd.projectcalico.org"] + resources: + - networkpolicies + - stagednetworkpolicies + - globalnetworkpolicies + - stagedglobalnetworkpolicies + verbs: + - watch + - list + - get + - create + - update + - delete + # Needed for policy name migrator to check calico/node daemonset status. + - apiGroups: ["apps"] + resources: + - daemonsets + verbs: + - get + resourceNames: + - calico-node --- # Source: calico/templates/calico-node-rbac.yaml # Include a clusterrole for the calico-node DaemonSet, @@ -6791,11 +6313,10 @@ rules: verbs: - watch - list - # Watch for changes to Kubernetes (Baseline)AdminNetworkPolicies. + # Watch for changes to Kubernetes ClusterNetworkPolicies. - apiGroups: ["policy.networking.k8s.io"] resources: - - adminnetworkpolicies - - baselineadminnetworkpolicies + - clusternetworkpolicies verbs: - watch - list @@ -6880,6 +6401,14 @@ rules: verbs: - create - update + # For monitoring KubeVirt live migration. + - apiGroups: ["kubevirt.io"] + resources: + - virtualmachineinstancemigrations + verbs: + - get + - list + - watch --- # Source: calico/templates/calico-node-rbac.yaml # CNI cluster role diff --git a/manifests/calico-typha.yaml b/manifests/calico-typha.yaml index 3e4294a0559..4951699983d 100644 --- a/manifests/calico-typha.yaml +++ b/manifests/calico-typha.yaml @@ -135,6 +135,9 @@ spec: format: int32 type: integer bindMode: + enum: + - None + - NodeIP type: string communities: items: @@ -145,11 +148,14 @@ spec: pattern: ^(\d+):(\d+)$|^(\d+):(\d+):(\d+)$ type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set ignoredInterfaces: items: type: string type: array + x-kubernetes-list-type: set listenPort: maximum: 65535 minimum: 1 @@ -159,6 +165,8 @@ spec: localWorkloadPeeringIPV6: type: string logSeverityScreen: + default: Info + pattern: ^(?i)(Trace|Debug|Info|Warning|Error|Fatal)?$ type: string nodeMeshMaxRestartTime: type: string @@ -184,27 +192,36 @@ spec: items: properties: cidr: + format: cidr type: string communities: items: type: string type: array type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceClusterIPs: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceExternalIPs: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceLoadBalancerAggregation: default: Enabled enum: @@ -215,9 +232,12 @@ spec: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -256,12 +276,21 @@ spec: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -276,22 +305,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array exportV6: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -306,22 +348,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array importV4: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -336,22 +391,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array importV6: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -366,11 +434,15 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array type: object type: object @@ -425,15 +497,10 @@ spec: maxRestartTime: type: string nextHopMode: - allOf: - - enum: - - Auto - - Self - - Keep - - enum: - - Auto - - Self - - Keep + enum: + - Auto + - Self + - Keep type: string node: type: string @@ -465,11 +532,18 @@ spec: reachableBy: type: string reversePeering: - enum: - - Auto - - Manual + allOf: + - enum: + - Auto + - Manual + - enum: + - Auto + - Manual type: string sourceAddress: + enum: + - UseNodeIP + - None type: string ttlSecurity: type: integer @@ -558,6 +632,10 @@ spec: properties: classes: items: + enum: + - Agent + - BGP + - Routes type: string type: array node: @@ -579,6 +657,9 @@ spec: routerID: type: string state: + enum: + - Ready + - NotReady type: string version: type: string @@ -592,6 +673,9 @@ spec: routerID: type: string state: + enum: + - Ready + - NotReady type: string version: type: string @@ -615,8 +699,20 @@ spec: since: type: string state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close type: string type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer type: string type: object type: array @@ -628,8 +724,20 @@ spec: since: type: string state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close type: string type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer type: string type: object type: array @@ -659,9 +767,18 @@ spec: peerIP: type: string sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer type: string type: object type: + enum: + - FIB + - RIB type: string type: object type: array @@ -679,9 +796,18 @@ spec: peerIP: type: string sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer type: string type: object type: + enum: + - FIB + - RIB type: string type: object type: array @@ -1035,15 +1161,9 @@ spec: if it detects the current value is 2 (strict mode that hurts performance). When set to "Strict", Felix will not modify the JIT hardening setting. [Default: Auto] type: string - bpfKubeProxyEndpointSlicesEnabled: + bpfKubeProxyHealthzPort: description: |- - BPFKubeProxyEndpointSlicesEnabled is deprecated and has no effect. BPF - kube-proxy always accepts endpoint slices. This option will be removed in - the next release. - type: boolean - bpfKubeProxyHealtzPort: - description: |- - BPFKubeProxyHealtzPort, in BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. + BPFKubeProxyHealthzPort, in BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. The health check server is used by external load balancers to determine if this node should receive traffic. [Default: 10256] type: integer bpfKubeProxyIptablesCleanupEnabled: @@ -1084,6 +1204,23 @@ spec: [Default: Off]. pattern: ^(?i)(Off|Info|Debug)?$ type: string + bpfMaglevMaxEndpointsPerService: + description: |- + BPFMaglevMaxEndpointsPerService is the maximum number of endpoints + expected to be part of a single Maglev-enabled service. + + Influences the size of the per-service Maglev lookup-tables generated by Felix + and thus the amount of memory reserved. + + [Default: 100] + type: integer + bpfMaglevMaxServices: + description: |- + BPFMaglevMaxServices is the maximum number of expected Maglev-enabled + services that Felix will allocate lookup-tables for. + + [Default: 100] + type: integer bpfMapSizeConntrack: description: |- BPFMapSizeConntrack sets the size for the conntrack map. This map must be large enough to hold @@ -1172,17 +1309,14 @@ spec: type: string bpfRedirectToPeer: description: |- - BPFRedirectToPeer controls which whether it is allowed to forward straight to the - peer side of the workload devices. It is allowed for any host L2 devices by default - (L2Only), but it breaks TCP dump on the host side of workload device as it bypasses - it on ingress. Value of Enabled also allows redirection from L3 host devices like - IPIP tunnel or Wireguard directly to the peer side of the workload's device. This - makes redirection faster, however, it breaks tools like tcpdump on the peer side. - Use Enabled with caution. [Default: L2Only] + BPFRedirectToPeer controls whether traffic may be forwarded directly to the peer side of a workload’s device. + Note that the legacy "L2Only" option is now deprecated and if set it is treated like "Enabled. + Setting this option to "Enabled" allows direct redirection (including from L3 host devices such as IPIP tunnels or WireGuard), + which can improve redirection performance but causes the redirected packets to bypass the host‑side ingress path. + As a result, packet‑capture tools on the host side of the workload device (for example, tcpdump) will not see that traffic. [Default: Enabled] enum: - Enabled - Disabled - - L2Only type: string cgroupV2Path: description: @@ -1533,6 +1667,10 @@ spec: Warning: changing this on a running system can leave "orphaned" rules in the "other" backend. These should be cleaned up to avoid confusing interactions. + enum: + - Legacy + - NFT + - Auto pattern: ^(?i)(Auto|Legacy|NFT)?$ type: string iptablesFilterAllowAction: @@ -1548,26 +1686,10 @@ spec: with an iptables "DROP" action. If you want to use "REJECT" action instead you can configure it in here. pattern: ^(?i)(Drop|Reject)?$ type: string - iptablesLockFilePath: - description: |- - IptablesLockFilePath is the location of the iptables lock file. You may need to change this - if the lock file is not in its standard location (for example if you have mapped it into Felix's - container at a different path). [Default: /run/xtables.lock] - type: string iptablesLockProbeInterval: description: |- - IptablesLockProbeInterval when IptablesLockTimeout is enabled: the time that Felix will wait between - attempts to acquire the iptables lock if it is not available. Lower values make Felix more - responsive when the lock is contended, but use more CPU. [Default: 50ms] - pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ - type: string - iptablesLockTimeout: - description: |- - IptablesLockTimeout is the time that Felix itself will wait for the iptables lock (rather than delegating the - lock handling to the `iptables` command). - - Deprecated: `iptables-restore` v1.8+ always takes the lock, so enabling this feature results in deadlock. - [Default: 0s disabled] + IptablesLockProbeInterval configures the interval between attempts to claim + the xtables lock. Shorter intervals are more responsive but use more CPU. [Default: 50ms] pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ type: string iptablesMangleAllowAction: @@ -1627,6 +1749,19 @@ spec: pattern: ^.* x-kubernetes-int-or-string: true type: array + logActionRateLimit: + description: |- + LogActionRateLimit sets the rate of hitting a Log action. The value must be in the format "N/unit", + where N is a number and unit is one of: second, minute, hour, or day. For example: "10/second" or "100/hour". + pattern: ^[1-9]\d{0,3}/(?:second|minute|hour|day)$ + type: string + logActionRateLimitBurst: + description: + LogActionRateLimitBurst sets the rate limit burst of + hitting a Log action when LogActionRateLimit is enabled. + maximum: 9999 + minimum: 0 + type: integer logDebugFilenameRegex: description: |- LogDebugFilenameRegex controls which source code files have their Debug log output included in the logs. @@ -1639,9 +1774,17 @@ spec: none to disable file logging. [Default: /var/log/calico/felix.log]" type: string logPrefix: - description: - "LogPrefix is the log prefix that Felix uses when rendering - LOG rules. [Default: calico-packet]" + description: |- + LogPrefix is the log prefix that Felix uses when rendering LOG rules. It is possible to use the following specifiers + to include extra information in the log prefix. + - %t: Tier name. + - %k: Kind (short names). + - %n: Policy or profile name. + - %p: Policy or profile name (namespace/name for namespaced kinds or just name for non namespaced kinds). + Calico includes ": " characters at the end of the generated log prefix. + Note that iptables shows up to 29 characters for the log prefix and nftables up to 127 characters. Extra characters are truncated. + [Default: calico-packet] + pattern: "^([a-zA-Z0-9%: /_-])*$" type: string logSeverityFile: description: @@ -1745,9 +1888,10 @@ spec: format: int32 type: integer nftablesMode: + default: Auto description: "NFTablesMode configures nftables support in Felix. [Default: - Disabled]" + Auto]" enum: - Disabled - Enabled @@ -1783,6 +1927,21 @@ spec: PrometheusGoMetricsEnabled disables Go runtime metrics collection, which the Prometheus client does by default, when set to false. This reduces the number of metrics reported, reducing Prometheus load. [Default: true] type: boolean + prometheusMetricsCAFile: + description: |- + PrometheusMetricsCAFile defines the absolute path to the TLS CA certificate file used for securing the /metrics endpoint. + This certificate must be valid and accessible by the calico-node process. + type: string + prometheusMetricsCertFile: + description: |- + PrometheusMetricsCertFile defines the absolute path to the TLS certificate file used for securing the /metrics endpoint. + This certificate must be valid and accessible by the calico-node process. + type: string + prometheusMetricsClientAuth: + description: |- + PrometheusMetricsClientAuth specifies the client authentication type for the /metrics endpoint. + This determines how the server validates client certificates. Default is "RequireAndVerifyClientCert". + type: string prometheusMetricsEnabled: description: "PrometheusMetricsEnabled enables the Prometheus metrics @@ -1793,6 +1952,11 @@ spec: "PrometheusMetricsHost is the host that the Prometheus metrics server should bind to. [Default: empty]" type: string + prometheusMetricsKeyFile: + description: |- + PrometheusMetricsKeyFile defines the absolute path to the private key file corresponding to the TLS certificate + used for securing the /metrics endpoint. The private key must be valid and accessible by the calico-node process. + type: string prometheusMetricsPort: description: "PrometheusMetricsPort is the TCP port that the Prometheus @@ -2076,6 +2240,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -2085,6 +2254,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2115,6 +2285,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2145,11 +2316,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -2161,8 +2339,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -2185,6 +2367,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2215,6 +2398,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2234,6 +2418,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -2243,6 +2432,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2273,6 +2463,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2303,11 +2494,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -2319,8 +2517,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -2343,6 +2545,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2373,6 +2576,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2394,6 +2598,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array preDNAT: @@ -2403,11 +2609,18 @@ spec: serviceAccountSelector: type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2446,6 +2659,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2484,6 +2698,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set interfaceName: type: string node: @@ -2494,6 +2709,8 @@ spec: name: type: string port: + maximum: 65535 + minimum: 0 type: integer protocol: anyOf: @@ -2511,6 +2728,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2547,6 +2765,9 @@ spec: properties: affinity: type: string + affinityClaimTime: + format: date-time + type: string allocations: items: type: integer @@ -2557,6 +2778,10 @@ spec: attributes: items: properties: + alternate: + additionalProperties: + type: string + type: object handle_id: type: string secondary: @@ -2626,6 +2851,11 @@ spec: properties: autoAllocateBlocks: type: boolean + kubeVirtVMAddressPersistence: + enum: + - Enabled + - Disabled + type: string maxBlocksPerHost: maximum: 2147483647 minimum: 0 @@ -2716,39 +2946,47 @@ spec: properties: allowedUses: items: + enum: + - Workload + - Tunnel + - LoadBalancer type: string type: array + x-kubernetes-list-type: set assignmentMode: + default: Automatic enum: - Automatic - Manual type: string blockSize: + maximum: 128 + minimum: 0 type: integer cidr: + format: cidr type: string disableBGPExport: type: boolean disabled: type: boolean - ipip: - properties: - enabled: - type: boolean - mode: - type: string - type: object ipipMode: + enum: + - Never + - Always + - CrossSubnet type: string namespaceSelector: type: string - nat-outgoing: - type: boolean natOutgoing: type: boolean nodeSelector: type: string vxlanMode: + enum: + - Never + - Always + - CrossSubnet type: string required: - cidr @@ -2787,9 +3025,11 @@ spec: spec: properties: reservedCIDRs: + format: cidr items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2829,6 +3069,10 @@ spec: loadBalancer: properties: assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly type: string type: object namespace: @@ -2841,6 +3085,9 @@ spec: hostEndpoint: properties: autoCreate: + enum: + - Enabled + - Disabled type: string createDefaultHostEndpoint: type: string @@ -2854,7 +3101,8 @@ spec: items: type: string type: array - interfaceSelector: + x-kubernetes-list-type: set + interfacePattern: type: string labels: additionalProperties: @@ -2870,6 +3118,9 @@ spec: reconcilerPeriod: type: string syncLabels: + enum: + - Enabled + - Disabled type: string type: object policy: @@ -2877,6 +3128,15 @@ spec: reconcilerPeriod: type: string type: object + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object serviceAccount: properties: reconcilerPeriod: @@ -2890,14 +3150,30 @@ spec: type: object debugProfilePort: format: int32 + maximum: 65535 + minimum: 0 type: integer etcdV3CompactionPeriod: type: string healthChecks: + default: Enabled + enum: + - Enabled + - Disabled type: string logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic type: string prometheusMetricsPort: + maximum: 65535 + minimum: 0 type: integer required: - controllers @@ -2915,6 +3191,10 @@ spec: loadBalancer: properties: assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly type: string type: object namespace: @@ -2927,6 +3207,9 @@ spec: hostEndpoint: properties: autoCreate: + enum: + - Enabled + - Disabled type: string createDefaultHostEndpoint: type: string @@ -2940,7 +3223,8 @@ spec: items: type: string type: array - interfaceSelector: + x-kubernetes-list-type: set + interfacePattern: type: string labels: additionalProperties: @@ -2956,6 +3240,9 @@ spec: reconcilerPeriod: type: string syncLabels: + enum: + - Enabled + - Disabled type: string type: object policy: @@ -2963,6 +3250,15 @@ spec: reconcilerPeriod: type: string type: object + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object serviceAccount: properties: reconcilerPeriod: @@ -2976,14 +3272,30 @@ spec: type: object debugProfilePort: format: int32 + maximum: 65535 + minimum: 0 type: integer etcdV3CompactionPeriod: type: string healthChecks: + default: Enabled + enum: + - Enabled + - Disabled type: string logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic type: string prometheusMetricsPort: + maximum: 65535 + minimum: 0 type: integer required: - controllers @@ -2992,6 +3304,8 @@ spec: type: object served: true storage: true + subresources: + status: {} --- # Source: calico/templates/kdd-crds.yaml apiVersion: apiextensions.k8s.io/v1 @@ -3026,6 +3340,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3035,6 +3354,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3065,6 +3385,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3095,11 +3416,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3111,8 +3439,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3135,6 +3467,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3165,6 +3498,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3184,6 +3518,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3193,6 +3532,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3223,6 +3563,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3253,11 +3594,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3269,8 +3617,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3293,6 +3645,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3323,6 +3676,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3342,6 +3696,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array selector: @@ -3349,11 +3705,18 @@ spec: serviceAccountSelector: type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -3392,6 +3755,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -3434,6 +3798,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3443,6 +3812,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3473,6 +3843,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3503,11 +3874,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3519,8 +3897,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3543,6 +3925,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3573,6 +3956,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3592,6 +3976,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3601,6 +3990,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3631,6 +4021,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3661,11 +4052,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3677,8 +4075,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3701,6 +4103,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3731,6 +4134,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3752,6 +4156,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array preDNAT: @@ -3761,13 +4167,25 @@ spec: serviceAccountSelector: type: string stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -4013,8 +4431,16 @@ spec: policyTypes: items: type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string type: object type: object @@ -4054,6 +4480,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -4063,6 +4494,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4093,6 +4525,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4123,11 +4556,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -4139,8 +4579,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -4163,6 +4607,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4193,6 +4638,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4212,6 +4658,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -4221,6 +4672,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4251,6 +4703,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4281,11 +4734,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -4297,8 +4757,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -4321,6 +4785,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4351,6 +4816,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4370,6 +4836,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array selector: @@ -4377,13 +4845,25 @@ spec: serviceAccountSelector: type: string stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -4419,14 +4899,36 @@ spec: spec: properties: defaultAction: - enum: - - Pass - - Deny + allOf: + - enum: + - Allow + - Deny + - Log + - Pass + - enum: + - Pass + - Deny type: string order: type: number type: object + required: + - metadata + - spec type: object + x-kubernetes-validations: + - message: The 'kube-admin' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-admin' ? self.spec.defaultAction == + 'Pass' : true" + - message: The 'kube-baseline' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-baseline' ? self.spec.defaultAction + == 'Pass' : true" + - message: The 'default' tier must have default action 'Deny' + rule: + "self.metadata.name == 'default' ? self.spec.defaultAction == 'Deny' + : true" served: true storage: true --- @@ -4435,35 +4937,35 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/30 - policy.networking.k8s.io/bundle-version: v0.1.1 - policy.networking.k8s.io/channel: experimental - creationTimestamp: null - name: adminnetworkpolicies.policy.networking.k8s.io + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/300 + policy.networking.k8s.io/bundle-version: v0.1.7 + policy.networking.k8s.io/channel: standard + name: clusternetworkpolicies.policy.networking.k8s.io spec: group: policy.networking.k8s.io names: - kind: AdminNetworkPolicy - listKind: AdminNetworkPolicyList - plural: adminnetworkpolicies + kind: ClusterNetworkPolicy + listKind: ClusterNetworkPolicyList + plural: clusternetworkpolicies shortNames: - - anp - singular: adminnetworkpolicy + - cnp + singular: clusternetworkpolicy scope: Cluster versions: - additionalPrinterColumns: + - jsonPath: .spec.tier + name: Tier + type: string - jsonPath: .spec.priority name: Priority type: string - jsonPath: .metadata.creationTimestamp name: Age type: date - name: v1alpha1 + name: v1alpha2 schema: openAPIV3Schema: - description: |- - AdminNetworkPolicy is a cluster level resource that is part of the - AdminNetworkPolicy API. + description: ClusterNetworkPolicy is a cluster-wide network policy resource. properties: apiVersion: description: |- @@ -4483,172 +4985,233 @@ spec: metadata: type: object spec: - description: Specification of the desired behavior of AdminNetworkPolicy. + description: Spec defines the desired behavior of ClusterNetworkPolicy. properties: egress: description: |- Egress is the list of Egress rules to be applied to the selected pods. - A total of 100 rules will be allowed in each ANP instance. - The relative precedence of egress rules within a single ANP object (all of - which share the priority) will be determined by the order in which the rule - is written. Thus, a rule that appears at the top of the egress rules - would take the highest precedence. - ANPs with no egress rules do not affect egress traffic. + A maximum of 25 rules is allowed in this block. - Support: Core + The relative precedence of egress rules within a single CNP object + (all of which share the priority) will be determined by the order + in which the rule is written. + Thus, a rule that appears at the top of the egress rules + would take the highest precedence. + CNPs with no egress rules do not affect egress traffic. items: description: |- - AdminNetworkPolicyEgressRule describes an action to take on a particular - set of traffic originating from pods selected by a AdminNetworkPolicy's + ClusterNetworkPolicyEgressRule describes an action to take on a particular + set of traffic originating from pods selected by a ClusterNetworkPolicy's Subject field. + properties: action: description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic (even if it would otherwise have been denied by NetworkPolicy) - Deny: denies the selected traffic - Pass: instructs the selected traffic to skip any remaining ANP rules, and - then pass execution to any NetworkPolicies that select the pod. - If the pod is not selected by any NetworkPolicies then execution - is passed to any BaselineAdminNetworkPolicies that select the pod. + Action specifies the effect this rule will have on matching + traffic. Currently the following actions are supported: + - Accept: Accepts the selected traffic, allowing it to + egress. No further ClusterNetworkPolicy or NetworkPolicy + rules will be processed. - Support: Core + - Deny: Drops the selected traffic. No further + ClusterNetworkPolicy or NetworkPolicy rules will be + processed. + + - Pass: Skips all further ClusterNetworkPolicy rules in the + current tier for the selected traffic, and passes + evaluation to the next tier. enum: - - Allow + - Accept - Deny - Pass type: string name: description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - AdminNetworkPolicies. - - - Support: Core + Name is an identifier for this rule, that may be no more than + 100 characters in length. This field should be used by the implementation + to help improve observability, readability and error-reporting + for any applied policies. maxLength: 100 type: string - ports: + protocols: description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of destination ports for the outgoing egress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core + Protocols allows for more fine-grain matching of traffic on + protocol-specific attributes such as the port. If + unspecified, protocol-specific attributes will not be used + to match traffic. items: description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). + ClusterNetworkPolicyProtocol describes additional protocol-specific match rules. Exactly one field must be set. maxProperties: 1 minProperties: 1 properties: - namedPort: + destinationNamedPort: description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - + DestinationNamedPort selects a destination port on a pod based on the + ContainerPort name. You can't use this in a rule that targets resources + without named ports (e.g. Nodes or Networks). type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core + sctp: + description: SCTP specific protocol matches. + minProperties: 1 properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core + tcp: + description: TCP specific protocol matches. + minProperties: 1 properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + udp: + description: UDP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object type: object - maxItems: 100 + maxItems: 25 + minItems: 1 type: array to: description: |- - To is the List of destinations whose traffic this rule applies to. - If any AdminNetworkPolicyEgressPeer matches the destination of outgoing - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core + To is the list of destinations whose traffic this rule applies to. If any + element matches the destination of outgoing traffic then the specified + action is applied. This field must be defined and contain at least one + item. items: description: |- - AdminNetworkPolicyEgressPeer defines a peer to allow traffic to. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. + ClusterNetworkPolicyEgressPeer defines a peer to allow traffic to. + + Exactly one of the fields must be set for a given peer and this is enforced + by the validation rules on the CRD. If an implementation sees no fields are + set then it can infer that the deployed CRD is of an incompatible version + with an unknown field. In that case it should fail closed. + + For "Accept" rules, "fail closed" means: "treat the rule as matching no + traffic". For "Deny" and "Pass" rules, "fail closed" means: "treat the rule + as a 'Deny all' rule". maxProperties: 1 minProperties: 1 properties: @@ -4656,9 +5219,6 @@ spec: description: |- Namespaces defines a way to select all pods within a set of Namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: matchExpressions: description: @@ -4688,11 +5248,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -4709,1206 +5271,37 @@ spec: This is intended for representing entities that live outside the cluster, which can't be selected by pods, namespaces and nodes peers, but note that cluster-internal traffic will be checked against the rule as - well. So if you Allow or Deny traffic to `"0.0.0.0/0"`, that will allow + well. So if you Accept or Deny traffic to `"0.0.0.0/0"`, that will allow or deny all IPv4 pod-to-pod traffic as well. If you don't want that, add a rule that Passes all pod traffic before the Networks rule. - Each item in Networks should be provided in the CIDR format and should be IPv4 or IPv6, for example "10.0.0.0/8" or "fd00::/8". - - Networks can have upto 25 CIDRs specified. - - - Support: Extended - - - + Networks can have up to 25 CIDRs specified. items: description: |- - CIDR is an IP address range in CIDR notation (for example, "10.0.0.0/8" or "fd00::/8"). - This string must be validated by implementations using net.ParseCIDR - TODO: Introduce CEL CIDR validation regex isCIDR() in Kube 1.31 when it is available. + CIDR is an IP address range in CIDR notation + (for example, "10.0.0.0/8" or "fd00::/8"). maxLength: 43 type: string x-kubernetes-validations: - - message: - CIDR must be either an IPv4 or IPv6 address. - IPv4 address embedded in IPv6 addresses are not - supported - rule: self.contains(':') != self.contains('.') + - message: Invalid CIDR format provided + rule: isCIDR(self) maxItems: 25 minItems: 1 type: array x-kubernetes-list-type: set - nodes: - description: |- - Nodes defines a way to select a set of nodes in - the cluster. This field follows standard label selector - semantics; if present but empty, it selects all Nodes. - - - Support: Extended - - - - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: |- - Pods defines a way to select a set of pods in - a set of namespaces. Note that host-networked pods - are not included in this type of peer. - - - Support: Core - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - maxItems: 100 - minItems: 1 - type: array - required: - - action - - to - type: object - x-kubernetes-validations: - - message: - networks/nodes peer cannot be set with namedPorts since - there are no namedPorts for networks/nodes - rule: - "!(self.to.exists(peer, has(peer.networks) || has(peer.nodes)) - && has(self.ports) && self.ports.exists(port, has(port.namedPort)))" - maxItems: 100 - type: array - ingress: - description: |- - Ingress is the list of Ingress rules to be applied to the selected pods. - A total of 100 rules will be allowed in each ANP instance. - The relative precedence of ingress rules within a single ANP object (all of - which share the priority) will be determined by the order in which the rule - is written. Thus, a rule that appears at the top of the ingress rules - would take the highest precedence. - ANPs with no ingress rules do not affect ingress traffic. - - - Support: Core - items: - description: |- - AdminNetworkPolicyIngressRule describes an action to take on a particular - set of traffic destined for pods selected by an AdminNetworkPolicy's - Subject field. - properties: - action: - description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic (even if it would otherwise have been denied by NetworkPolicy) - Deny: denies the selected traffic - Pass: instructs the selected traffic to skip any remaining ANP rules, and - then pass execution to any NetworkPolicies that select the pod. - If the pod is not selected by any NetworkPolicies then execution - is passed to any BaselineAdminNetworkPolicies that select the pod. - - - Support: Core - enum: - - Allow - - Deny - - Pass - type: string - from: - description: |- - From is the list of sources whose traffic this rule applies to. - If any AdminNetworkPolicyIngressPeer matches the source of incoming - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core - items: - description: |- - AdminNetworkPolicyIngressPeer defines an in-cluster peer to allow traffic from. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: |- - Namespaces defines a way to select all pods within a set of Namespaces. - Note that host-networked pods are not included in this type of peer. - - - Support: Core - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: |- - Pods defines a way to select a set of pods in - a set of namespaces. Note that host-networked pods - are not included in this type of peer. - - - Support: Core - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - maxItems: 100 - minItems: 1 - type: array - name: - description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - AdminNetworkPolicies. - - - Support: Core - maxLength: 100 - type: string - ports: - description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of ports which should be matched on - the pods selected for this policy i.e the subject of the policy. - So it matches on the destination port for the ingress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core - items: - description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). - Exactly one field must be set. - maxProperties: 1 - minProperties: 1 - properties: - namedPort: - description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - - type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core - properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol - type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core - properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start - type: object - type: object - maxItems: 100 - type: array - required: - - action - - from - type: object - maxItems: 100 - type: array - priority: - description: |- - Priority is a value from 0 to 1000. Rules with lower priority values have - higher precedence, and are checked before rules with higher priority values. - All AdminNetworkPolicy rules have higher precedence than NetworkPolicy or - BaselineAdminNetworkPolicy rules - The behavior is undefined if two ANP objects have same priority. - - - Support: Core - format: int32 - maximum: 1000 - minimum: 0 - type: integer - subject: - description: |- - Subject defines the pods to which this AdminNetworkPolicy applies. - Note that host-networked pods are not included in subject selection. - - - Support: Core - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: Namespaces is used to select pods via namespace selectors. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: - Pods is used to select pods via namespace AND pod - selectors. - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - required: - - priority - - subject - type: object - status: - description: Status is the status to be reported by the implementation. - properties: - conditions: - items: - description: - "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - required: - - conditions - type: object - required: - - metadata - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: null - storedVersions: null ---- -# Source: calico/templates/kdd-crds.yaml -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/30 - policy.networking.k8s.io/bundle-version: v0.1.1 - policy.networking.k8s.io/channel: experimental - creationTimestamp: null - name: baselineadminnetworkpolicies.policy.networking.k8s.io -spec: - group: policy.networking.k8s.io - names: - kind: BaselineAdminNetworkPolicy - listKind: BaselineAdminNetworkPolicyList - plural: baselineadminnetworkpolicies - shortNames: - - banp - singular: baselineadminnetworkpolicy - scope: Cluster - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: |- - BaselineAdminNetworkPolicy is a cluster level resource that is part of the - AdminNetworkPolicy API. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: Specification of the desired behavior of BaselineAdminNetworkPolicy. - properties: - egress: - description: |- - Egress is the list of Egress rules to be applied to the selected pods if - they are not matched by any AdminNetworkPolicy or NetworkPolicy rules. - A total of 100 Egress rules will be allowed in each BANP instance. - The relative precedence of egress rules within a single BANP object - will be determined by the order in which the rule is written. - Thus, a rule that appears at the top of the egress rules - would take the highest precedence. - BANPs with no egress rules do not affect egress traffic. - - - Support: Core - items: - description: |- - BaselineAdminNetworkPolicyEgressRule describes an action to take on a particular - set of traffic originating from pods selected by a BaselineAdminNetworkPolicy's - Subject field. - - properties: - action: - description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic - Deny: denies the selected traffic - - - Support: Core - enum: - - Allow - - Deny - type: string - name: - description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - BaselineAdminNetworkPolicies. - - - Support: Core - maxLength: 100 - type: string - ports: - description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of destination ports for the outgoing egress traffic. - If Ports is not set then the rule does not filter traffic via port. - items: - description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). - Exactly one field must be set. - maxProperties: 1 - minProperties: 1 - properties: - namedPort: - description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - - type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core - properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol - type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core - properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start - type: object - type: object - maxItems: 100 - type: array - to: - description: |- - To is the list of destinations whose traffic this rule applies to. - If any AdminNetworkPolicyEgressPeer matches the destination of outgoing - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core - items: - description: |- - AdminNetworkPolicyEgressPeer defines a peer to allow traffic to. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: |- - Namespaces defines a way to select all pods within a set of Namespaces. - Note that host-networked pods are not included in this type of peer. - - - Support: Core - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - networks: - description: |- - Networks defines a way to select peers via CIDR blocks. - This is intended for representing entities that live outside the cluster, - which can't be selected by pods, namespaces and nodes peers, but note - that cluster-internal traffic will be checked against the rule as - well. So if you Allow or Deny traffic to `"0.0.0.0/0"`, that will allow - or deny all IPv4 pod-to-pod traffic as well. If you don't want that, - add a rule that Passes all pod traffic before the Networks rule. - - - Each item in Networks should be provided in the CIDR format and should be - IPv4 or IPv6, for example "10.0.0.0/8" or "fd00::/8". - - - Networks can have upto 25 CIDRs specified. - - - Support: Extended - - - - items: - description: |- - CIDR is an IP address range in CIDR notation (for example, "10.0.0.0/8" or "fd00::/8"). - This string must be validated by implementations using net.ParseCIDR - TODO: Introduce CEL CIDR validation regex isCIDR() in Kube 1.31 when it is available. - maxLength: 43 - type: string - x-kubernetes-validations: - - message: - CIDR must be either an IPv4 or IPv6 address. - IPv4 address embedded in IPv6 addresses are not - supported - rule: self.contains(':') != self.contains('.') - maxItems: 25 - minItems: 1 - type: array - x-kubernetes-list-type: set - nodes: - description: |- - Nodes defines a way to select a set of nodes in - the cluster. This field follows standard label selector - semantics; if present but empty, it selects all Nodes. - - - Support: Extended - - - - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: + pods: description: |- Pods defines a way to select a set of pods in a set of namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -5939,11 +5332,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -5956,8 +5351,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -5988,11 +5383,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6004,73 +5401,80 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object - maxItems: 100 + maxItems: 25 minItems: 1 type: array required: - action - to type: object - x-kubernetes-validations: - - message: - networks/nodes peer cannot be set with namedPorts since - there are no namedPorts for networks/nodes - rule: - "!(self.to.exists(peer, has(peer.networks) || has(peer.nodes)) - && has(self.ports) && self.ports.exists(port, has(port.namedPort)))" - maxItems: 100 + maxItems: 25 type: array ingress: description: |- - Ingress is the list of Ingress rules to be applied to the selected pods - if they are not matched by any AdminNetworkPolicy or NetworkPolicy rules. - A total of 100 Ingress rules will be allowed in each BANP instance. - The relative precedence of ingress rules within a single BANP object - will be determined by the order in which the rule is written. - Thus, a rule that appears at the top of the ingress rules - would take the highest precedence. - BANPs with no ingress rules do not affect ingress traffic. + Ingress is the list of Ingress rules to be applied to the selected pods. + A maximum of 25 rules is allowed in this block. - Support: Core + The relative precedence of ingress rules within a single CNP object + (all of which share the priority) will be determined by the order + in which the rule is written. + Thus, a rule that appears at the top of the ingress rules + would take the highest precedence. + CNPs with no ingress rules do not affect ingress traffic. items: description: |- - BaselineAdminNetworkPolicyIngressRule describes an action to take on a particular - set of traffic destined for pods selected by a BaselineAdminNetworkPolicy's + ClusterNetworkPolicyIngressRule describes an action to take on a particular + set of traffic destined for pods selected by a ClusterNetworkPolicy's Subject field. properties: action: description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic - Deny: denies the selected traffic + Action specifies the effect this rule will have on matching + traffic. Currently the following actions are supported: + + - Accept: Accepts the selected traffic, allowing it into + the destination. No further ClusterNetworkPolicy or + NetworkPolicy rules will be processed. + Note: while Accept ensures traffic is accepted by + Kubernetes network policy, it is still possible that the + packet is blocked in other ways: custom nftable rules, + high-layers e.g. service mesh. - Support: Core + - Deny: Drops the selected traffic. No further + ClusterNetworkPolicy or NetworkPolicy rules will be + processed. + + - Pass: Skips all further ClusterNetworkPolicy rules in the + current tier for the selected traffic, and passes + evaluation to the next tier. enum: - - Allow + - Accept - Deny + - Pass type: string from: description: |- From is the list of sources whose traffic this rule applies to. - If any AdminNetworkPolicyIngressPeer matches the source of incoming + If any element matches the source of incoming traffic then the specified action is applied. This field must be defined and contain at least one item. - - - Support: Core items: description: |- - AdminNetworkPolicyIngressPeer defines an in-cluster peer to allow traffic from. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. + ClusterNetworkPolicyIngressPeer defines a peer to allow traffic from. + + Exactly one of the fields must be set for a given peer and this is enforced + by the validation rules on the CRD. If an implementation sees no fields are + set then it can infer that the deployed CRD is of an incompatible version + with an unknown field. In that case it should fail closed. + + For "Accept" rules, "fail closed" means: "treat the rule as matching no + traffic". For "Deny" and "Pass" rules, "fail closed" means: "treat the rule + as a 'Deny all' rule". maxProperties: 1 minProperties: 1 properties: @@ -6078,9 +5482,6 @@ spec: description: |- Namespaces defines a way to select all pods within a set of Namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: matchExpressions: description: @@ -6110,11 +5511,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6130,14 +5533,11 @@ spec: Pods defines a way to select a set of pods in a set of namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -6168,11 +5568,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6185,8 +5587,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -6217,11 +5619,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6233,140 +5637,206 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object - maxItems: 100 + maxItems: 25 minItems: 1 type: array name: description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - BaselineAdminNetworkPolicies. - - - Support: Core + Name is an identifier for this rule, that may be no more than + 100 characters in length. This field should be used by the implementation + to help improve observability, readability and error-reporting + for any applied policies. maxLength: 100 type: string - ports: + protocols: description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of ports which should be matched on - the pods selected for this policy i.e the subject of the policy. - So it matches on the destination port for the ingress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core + Protocols allows for more fine-grain matching of traffic on + protocol-specific attributes such as the port. If + unspecified, protocol-specific attributes will not be used + to match traffic. items: description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). + ClusterNetworkPolicyProtocol describes additional protocol-specific match rules. Exactly one field must be set. maxProperties: 1 minProperties: 1 properties: - namedPort: + destinationNamedPort: description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - + DestinationNamedPort selects a destination port on a pod based on the + ContainerPort name. You can't use this in a rule that targets resources + without named ports (e.g. Nodes or Networks). type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core + sctp: + description: SCTP specific protocol matches. + minProperties: 1 properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core + tcp: + description: TCP specific protocol matches. + minProperties: 1 properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + udp: + description: UDP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object type: object - maxItems: 100 + maxItems: 25 + minItems: 1 type: array required: - action - from type: object - maxItems: 100 + maxItems: 25 type: array - subject: + priority: description: |- - Subject defines the pods to which this BaselineAdminNetworkPolicy applies. - Note that host-networked pods are not included in subject selection. - - - Support: Core + Priority is a value from 0 to 1000 indicating the precedence of + the policy within its tier. Policies with lower priority values have + higher precedence, and are checked before policies with higher priority + values in the same tier. All Admin tier rules have higher precedence than + NetworkPolicy or Baseline tier rules. + If two (or more) policies in the same tier with the same priority + could match a connection, then the implementation can apply any of the + matching policies to the connection, and there is no way for the user to + reliably determine which one it will choose. Administrators must be + careful about assigning the priorities for policies with rules that will + match many connections, and ensure that policies have unique priority + values in cases where ambiguity would be unacceptable. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + subject: + description: + Subject defines the pods to which this ClusterNetworkPolicy + applies. maxProperties: 1 minProperties: 1 properties: @@ -6401,11 +5871,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6423,8 +5895,8 @@ spec: properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -6454,11 +5926,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6471,8 +5945,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -6502,11 +5976,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6518,12 +5994,48 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object + tier: + description: |- + Tier is used as the top-level grouping for network policy prioritization. + + Policy tiers are evaluated in the following order: + * Admin tier + * NetworkPolicy tier + * Baseline tier + + ClusterNetworkPolicy can use 2 of these tiers: Admin and Baseline. + + The Admin tier takes precedence over all other policies. Policies + defined in this tier are used to set cluster-wide security rules + that cannot be overridden in the other tiers. If Admin tier has + made a final decision (Accept or Deny) on a connection, then no + further evaluation is done. + + NetworkPolicy tier is the tier for the namespaced v1.NetworkPolicy. + These policies are intended for the application developer to describe + the security policy associated with their deployments inside their + namespace. v1.NetworkPolicy always makes a final decision for selected + pods. Further evaluation only happens for Pods not selected by a + v1.NetworkPolicy. + + Baseline tier is a cluster-wide policy that can be overridden by the + v1.NetworkPolicy. If Baseline tier has made a final decision (Accept or + Deny) on a connection, then no further evaluation is done. + + If a given connection wasn't allowed or denied by any of the tiers, + the default kubernetes policy is applied, which says that + all pods can communicate with each other. + enum: + - Admin + - Baseline + type: string required: + - priority - subject + - tier type: object status: description: Status is the status to be reported by the implementation. @@ -6531,16 +6043,8 @@ spec: conditions: items: description: - "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -6581,12 +6085,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string @@ -6608,11 +6107,6 @@ spec: - metadata - spec type: object - x-kubernetes-validations: - - message: - Only one baseline admin network policy with metadata.name="default" - can be created in the cluster - rule: self.metadata.name == 'default' served: true storage: true subresources: @@ -6728,6 +6222,34 @@ rules: - update # watch for changes - watch + - apiGroups: ["crd.projectcalico.org"] + resources: + - kubecontrollersconfigurations/status + verbs: + - get + - update + # Needed for policy name migrator. + - apiGroups: ["crd.projectcalico.org"] + resources: + - networkpolicies + - stagednetworkpolicies + - globalnetworkpolicies + - stagedglobalnetworkpolicies + verbs: + - watch + - list + - get + - create + - update + - delete + # Needed for policy name migrator to check calico/node daemonset status. + - apiGroups: ["apps"] + resources: + - daemonsets + verbs: + - get + resourceNames: + - calico-node --- # Source: calico/templates/calico-node-rbac.yaml # Include a clusterrole for the calico-node DaemonSet, @@ -6792,11 +6314,10 @@ rules: verbs: - watch - list - # Watch for changes to Kubernetes (Baseline)AdminNetworkPolicies. + # Watch for changes to Kubernetes ClusterNetworkPolicies. - apiGroups: ["policy.networking.k8s.io"] resources: - - adminnetworkpolicies - - baselineadminnetworkpolicies + - clusternetworkpolicies verbs: - watch - list @@ -6914,6 +6435,14 @@ rules: - daemonsets verbs: - get + # For monitoring KubeVirt live migration. + - apiGroups: ["kubevirt.io"] + resources: + - virtualmachineinstancemigrations + verbs: + - get + - list + - watch --- # Source: calico/templates/calico-node-rbac.yaml # CNI cluster role diff --git a/manifests/calico-vxlan.yaml b/manifests/calico-vxlan.yaml index 54010bd4219..a71553f677d 100644 --- a/manifests/calico-vxlan.yaml +++ b/manifests/calico-vxlan.yaml @@ -119,6 +119,9 @@ spec: format: int32 type: integer bindMode: + enum: + - None + - NodeIP type: string communities: items: @@ -129,11 +132,14 @@ spec: pattern: ^(\d+):(\d+)$|^(\d+):(\d+):(\d+)$ type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set ignoredInterfaces: items: type: string type: array + x-kubernetes-list-type: set listenPort: maximum: 65535 minimum: 1 @@ -143,6 +149,8 @@ spec: localWorkloadPeeringIPV6: type: string logSeverityScreen: + default: Info + pattern: ^(?i)(Trace|Debug|Info|Warning|Error|Fatal)?$ type: string nodeMeshMaxRestartTime: type: string @@ -168,27 +176,36 @@ spec: items: properties: cidr: + format: cidr type: string communities: items: type: string type: array type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceClusterIPs: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceExternalIPs: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceLoadBalancerAggregation: default: Enabled enum: @@ -199,9 +216,12 @@ spec: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -240,12 +260,21 @@ spec: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -260,22 +289,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array exportV6: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -290,22 +332,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array importV4: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -320,22 +375,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array importV6: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -350,11 +418,15 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array type: object type: object @@ -409,15 +481,10 @@ spec: maxRestartTime: type: string nextHopMode: - allOf: - - enum: - - Auto - - Self - - Keep - - enum: - - Auto - - Self - - Keep + enum: + - Auto + - Self + - Keep type: string node: type: string @@ -449,11 +516,18 @@ spec: reachableBy: type: string reversePeering: - enum: - - Auto - - Manual + allOf: + - enum: + - Auto + - Manual + - enum: + - Auto + - Manual type: string sourceAddress: + enum: + - UseNodeIP + - None type: string ttlSecurity: type: integer @@ -542,6 +616,10 @@ spec: properties: classes: items: + enum: + - Agent + - BGP + - Routes type: string type: array node: @@ -563,6 +641,9 @@ spec: routerID: type: string state: + enum: + - Ready + - NotReady type: string version: type: string @@ -576,6 +657,9 @@ spec: routerID: type: string state: + enum: + - Ready + - NotReady type: string version: type: string @@ -599,8 +683,20 @@ spec: since: type: string state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close type: string type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer type: string type: object type: array @@ -612,8 +708,20 @@ spec: since: type: string state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close type: string type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer type: string type: object type: array @@ -643,9 +751,18 @@ spec: peerIP: type: string sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer type: string type: object type: + enum: + - FIB + - RIB type: string type: object type: array @@ -663,9 +780,18 @@ spec: peerIP: type: string sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer type: string type: object type: + enum: + - FIB + - RIB type: string type: object type: array @@ -1019,15 +1145,9 @@ spec: if it detects the current value is 2 (strict mode that hurts performance). When set to "Strict", Felix will not modify the JIT hardening setting. [Default: Auto] type: string - bpfKubeProxyEndpointSlicesEnabled: + bpfKubeProxyHealthzPort: description: |- - BPFKubeProxyEndpointSlicesEnabled is deprecated and has no effect. BPF - kube-proxy always accepts endpoint slices. This option will be removed in - the next release. - type: boolean - bpfKubeProxyHealtzPort: - description: |- - BPFKubeProxyHealtzPort, in BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. + BPFKubeProxyHealthzPort, in BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. The health check server is used by external load balancers to determine if this node should receive traffic. [Default: 10256] type: integer bpfKubeProxyIptablesCleanupEnabled: @@ -1068,6 +1188,23 @@ spec: [Default: Off]. pattern: ^(?i)(Off|Info|Debug)?$ type: string + bpfMaglevMaxEndpointsPerService: + description: |- + BPFMaglevMaxEndpointsPerService is the maximum number of endpoints + expected to be part of a single Maglev-enabled service. + + Influences the size of the per-service Maglev lookup-tables generated by Felix + and thus the amount of memory reserved. + + [Default: 100] + type: integer + bpfMaglevMaxServices: + description: |- + BPFMaglevMaxServices is the maximum number of expected Maglev-enabled + services that Felix will allocate lookup-tables for. + + [Default: 100] + type: integer bpfMapSizeConntrack: description: |- BPFMapSizeConntrack sets the size for the conntrack map. This map must be large enough to hold @@ -1156,17 +1293,14 @@ spec: type: string bpfRedirectToPeer: description: |- - BPFRedirectToPeer controls which whether it is allowed to forward straight to the - peer side of the workload devices. It is allowed for any host L2 devices by default - (L2Only), but it breaks TCP dump on the host side of workload device as it bypasses - it on ingress. Value of Enabled also allows redirection from L3 host devices like - IPIP tunnel or Wireguard directly to the peer side of the workload's device. This - makes redirection faster, however, it breaks tools like tcpdump on the peer side. - Use Enabled with caution. [Default: L2Only] + BPFRedirectToPeer controls whether traffic may be forwarded directly to the peer side of a workload’s device. + Note that the legacy "L2Only" option is now deprecated and if set it is treated like "Enabled. + Setting this option to "Enabled" allows direct redirection (including from L3 host devices such as IPIP tunnels or WireGuard), + which can improve redirection performance but causes the redirected packets to bypass the host‑side ingress path. + As a result, packet‑capture tools on the host side of the workload device (for example, tcpdump) will not see that traffic. [Default: Enabled] enum: - Enabled - Disabled - - L2Only type: string cgroupV2Path: description: @@ -1517,6 +1651,10 @@ spec: Warning: changing this on a running system can leave "orphaned" rules in the "other" backend. These should be cleaned up to avoid confusing interactions. + enum: + - Legacy + - NFT + - Auto pattern: ^(?i)(Auto|Legacy|NFT)?$ type: string iptablesFilterAllowAction: @@ -1532,26 +1670,10 @@ spec: with an iptables "DROP" action. If you want to use "REJECT" action instead you can configure it in here. pattern: ^(?i)(Drop|Reject)?$ type: string - iptablesLockFilePath: - description: |- - IptablesLockFilePath is the location of the iptables lock file. You may need to change this - if the lock file is not in its standard location (for example if you have mapped it into Felix's - container at a different path). [Default: /run/xtables.lock] - type: string iptablesLockProbeInterval: description: |- - IptablesLockProbeInterval when IptablesLockTimeout is enabled: the time that Felix will wait between - attempts to acquire the iptables lock if it is not available. Lower values make Felix more - responsive when the lock is contended, but use more CPU. [Default: 50ms] - pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ - type: string - iptablesLockTimeout: - description: |- - IptablesLockTimeout is the time that Felix itself will wait for the iptables lock (rather than delegating the - lock handling to the `iptables` command). - - Deprecated: `iptables-restore` v1.8+ always takes the lock, so enabling this feature results in deadlock. - [Default: 0s disabled] + IptablesLockProbeInterval configures the interval between attempts to claim + the xtables lock. Shorter intervals are more responsive but use more CPU. [Default: 50ms] pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ type: string iptablesMangleAllowAction: @@ -1611,6 +1733,19 @@ spec: pattern: ^.* x-kubernetes-int-or-string: true type: array + logActionRateLimit: + description: |- + LogActionRateLimit sets the rate of hitting a Log action. The value must be in the format "N/unit", + where N is a number and unit is one of: second, minute, hour, or day. For example: "10/second" or "100/hour". + pattern: ^[1-9]\d{0,3}/(?:second|minute|hour|day)$ + type: string + logActionRateLimitBurst: + description: + LogActionRateLimitBurst sets the rate limit burst of + hitting a Log action when LogActionRateLimit is enabled. + maximum: 9999 + minimum: 0 + type: integer logDebugFilenameRegex: description: |- LogDebugFilenameRegex controls which source code files have their Debug log output included in the logs. @@ -1623,9 +1758,17 @@ spec: none to disable file logging. [Default: /var/log/calico/felix.log]" type: string logPrefix: - description: - "LogPrefix is the log prefix that Felix uses when rendering - LOG rules. [Default: calico-packet]" + description: |- + LogPrefix is the log prefix that Felix uses when rendering LOG rules. It is possible to use the following specifiers + to include extra information in the log prefix. + - %t: Tier name. + - %k: Kind (short names). + - %n: Policy or profile name. + - %p: Policy or profile name (namespace/name for namespaced kinds or just name for non namespaced kinds). + Calico includes ": " characters at the end of the generated log prefix. + Note that iptables shows up to 29 characters for the log prefix and nftables up to 127 characters. Extra characters are truncated. + [Default: calico-packet] + pattern: "^([a-zA-Z0-9%: /_-])*$" type: string logSeverityFile: description: @@ -1729,9 +1872,10 @@ spec: format: int32 type: integer nftablesMode: + default: Auto description: "NFTablesMode configures nftables support in Felix. [Default: - Disabled]" + Auto]" enum: - Disabled - Enabled @@ -1767,6 +1911,21 @@ spec: PrometheusGoMetricsEnabled disables Go runtime metrics collection, which the Prometheus client does by default, when set to false. This reduces the number of metrics reported, reducing Prometheus load. [Default: true] type: boolean + prometheusMetricsCAFile: + description: |- + PrometheusMetricsCAFile defines the absolute path to the TLS CA certificate file used for securing the /metrics endpoint. + This certificate must be valid and accessible by the calico-node process. + type: string + prometheusMetricsCertFile: + description: |- + PrometheusMetricsCertFile defines the absolute path to the TLS certificate file used for securing the /metrics endpoint. + This certificate must be valid and accessible by the calico-node process. + type: string + prometheusMetricsClientAuth: + description: |- + PrometheusMetricsClientAuth specifies the client authentication type for the /metrics endpoint. + This determines how the server validates client certificates. Default is "RequireAndVerifyClientCert". + type: string prometheusMetricsEnabled: description: "PrometheusMetricsEnabled enables the Prometheus metrics @@ -1777,6 +1936,11 @@ spec: "PrometheusMetricsHost is the host that the Prometheus metrics server should bind to. [Default: empty]" type: string + prometheusMetricsKeyFile: + description: |- + PrometheusMetricsKeyFile defines the absolute path to the private key file corresponding to the TLS certificate + used for securing the /metrics endpoint. The private key must be valid and accessible by the calico-node process. + type: string prometheusMetricsPort: description: "PrometheusMetricsPort is the TCP port that the Prometheus @@ -2060,6 +2224,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -2069,6 +2238,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2099,6 +2269,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2129,11 +2300,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -2145,8 +2323,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -2169,6 +2351,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2199,6 +2382,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2218,6 +2402,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -2227,6 +2416,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2257,6 +2447,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2287,11 +2478,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -2303,8 +2501,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -2327,6 +2529,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2357,6 +2560,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2378,6 +2582,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array preDNAT: @@ -2387,11 +2593,18 @@ spec: serviceAccountSelector: type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2430,6 +2643,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2468,6 +2682,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set interfaceName: type: string node: @@ -2478,6 +2693,8 @@ spec: name: type: string port: + maximum: 65535 + minimum: 0 type: integer protocol: anyOf: @@ -2495,6 +2712,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2531,6 +2749,9 @@ spec: properties: affinity: type: string + affinityClaimTime: + format: date-time + type: string allocations: items: type: integer @@ -2541,6 +2762,10 @@ spec: attributes: items: properties: + alternate: + additionalProperties: + type: string + type: object handle_id: type: string secondary: @@ -2610,6 +2835,11 @@ spec: properties: autoAllocateBlocks: type: boolean + kubeVirtVMAddressPersistence: + enum: + - Enabled + - Disabled + type: string maxBlocksPerHost: maximum: 2147483647 minimum: 0 @@ -2700,39 +2930,47 @@ spec: properties: allowedUses: items: + enum: + - Workload + - Tunnel + - LoadBalancer type: string type: array + x-kubernetes-list-type: set assignmentMode: + default: Automatic enum: - Automatic - Manual type: string blockSize: + maximum: 128 + minimum: 0 type: integer cidr: + format: cidr type: string disableBGPExport: type: boolean disabled: type: boolean - ipip: - properties: - enabled: - type: boolean - mode: - type: string - type: object ipipMode: + enum: + - Never + - Always + - CrossSubnet type: string namespaceSelector: type: string - nat-outgoing: - type: boolean natOutgoing: type: boolean nodeSelector: type: string vxlanMode: + enum: + - Never + - Always + - CrossSubnet type: string required: - cidr @@ -2771,9 +3009,11 @@ spec: spec: properties: reservedCIDRs: + format: cidr items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2813,6 +3053,10 @@ spec: loadBalancer: properties: assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly type: string type: object namespace: @@ -2825,6 +3069,9 @@ spec: hostEndpoint: properties: autoCreate: + enum: + - Enabled + - Disabled type: string createDefaultHostEndpoint: type: string @@ -2838,7 +3085,8 @@ spec: items: type: string type: array - interfaceSelector: + x-kubernetes-list-type: set + interfacePattern: type: string labels: additionalProperties: @@ -2854,6 +3102,9 @@ spec: reconcilerPeriod: type: string syncLabels: + enum: + - Enabled + - Disabled type: string type: object policy: @@ -2861,6 +3112,15 @@ spec: reconcilerPeriod: type: string type: object + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object serviceAccount: properties: reconcilerPeriod: @@ -2874,14 +3134,30 @@ spec: type: object debugProfilePort: format: int32 + maximum: 65535 + minimum: 0 type: integer etcdV3CompactionPeriod: type: string healthChecks: + default: Enabled + enum: + - Enabled + - Disabled type: string logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic type: string prometheusMetricsPort: + maximum: 65535 + minimum: 0 type: integer required: - controllers @@ -2899,6 +3175,10 @@ spec: loadBalancer: properties: assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly type: string type: object namespace: @@ -2911,6 +3191,9 @@ spec: hostEndpoint: properties: autoCreate: + enum: + - Enabled + - Disabled type: string createDefaultHostEndpoint: type: string @@ -2924,7 +3207,8 @@ spec: items: type: string type: array - interfaceSelector: + x-kubernetes-list-type: set + interfacePattern: type: string labels: additionalProperties: @@ -2940,6 +3224,9 @@ spec: reconcilerPeriod: type: string syncLabels: + enum: + - Enabled + - Disabled type: string type: object policy: @@ -2947,6 +3234,15 @@ spec: reconcilerPeriod: type: string type: object + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object serviceAccount: properties: reconcilerPeriod: @@ -2960,14 +3256,30 @@ spec: type: object debugProfilePort: format: int32 + maximum: 65535 + minimum: 0 type: integer etcdV3CompactionPeriod: type: string healthChecks: + default: Enabled + enum: + - Enabled + - Disabled type: string logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic type: string prometheusMetricsPort: + maximum: 65535 + minimum: 0 type: integer required: - controllers @@ -2976,6 +3288,8 @@ spec: type: object served: true storage: true + subresources: + status: {} --- # Source: calico/templates/kdd-crds.yaml apiVersion: apiextensions.k8s.io/v1 @@ -3010,6 +3324,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3019,6 +3338,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3049,6 +3369,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3079,11 +3400,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3095,8 +3423,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3119,6 +3451,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3149,6 +3482,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3168,6 +3502,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3177,6 +3516,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3207,6 +3547,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3237,11 +3578,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3253,8 +3601,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3277,6 +3629,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3307,6 +3660,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3326,6 +3680,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array selector: @@ -3333,11 +3689,18 @@ spec: serviceAccountSelector: type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -3376,6 +3739,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -3418,6 +3782,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3427,6 +3796,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3457,6 +3827,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3487,11 +3858,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3503,8 +3881,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3527,6 +3909,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3557,6 +3940,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3576,6 +3960,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3585,6 +3974,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3615,6 +4005,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3645,11 +4036,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3661,8 +4059,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3685,6 +4087,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3715,6 +4118,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3736,6 +4140,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array preDNAT: @@ -3745,13 +4151,25 @@ spec: serviceAccountSelector: type: string stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -3997,8 +4415,16 @@ spec: policyTypes: items: type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string type: object type: object @@ -4038,6 +4464,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -4047,6 +4478,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4077,6 +4509,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4107,11 +4540,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -4123,8 +4563,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -4147,6 +4591,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4177,6 +4622,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4196,6 +4642,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -4205,6 +4656,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4235,6 +4687,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4265,11 +4718,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -4281,8 +4741,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -4305,6 +4769,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4335,6 +4800,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4354,6 +4820,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array selector: @@ -4361,13 +4829,25 @@ spec: serviceAccountSelector: type: string stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -4403,14 +4883,36 @@ spec: spec: properties: defaultAction: - enum: - - Pass - - Deny + allOf: + - enum: + - Allow + - Deny + - Log + - Pass + - enum: + - Pass + - Deny type: string order: type: number type: object + required: + - metadata + - spec type: object + x-kubernetes-validations: + - message: The 'kube-admin' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-admin' ? self.spec.defaultAction == + 'Pass' : true" + - message: The 'kube-baseline' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-baseline' ? self.spec.defaultAction + == 'Pass' : true" + - message: The 'default' tier must have default action 'Deny' + rule: + "self.metadata.name == 'default' ? self.spec.defaultAction == 'Deny' + : true" served: true storage: true --- @@ -4419,35 +4921,35 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/30 - policy.networking.k8s.io/bundle-version: v0.1.1 - policy.networking.k8s.io/channel: experimental - creationTimestamp: null - name: adminnetworkpolicies.policy.networking.k8s.io + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/300 + policy.networking.k8s.io/bundle-version: v0.1.7 + policy.networking.k8s.io/channel: standard + name: clusternetworkpolicies.policy.networking.k8s.io spec: group: policy.networking.k8s.io names: - kind: AdminNetworkPolicy - listKind: AdminNetworkPolicyList - plural: adminnetworkpolicies + kind: ClusterNetworkPolicy + listKind: ClusterNetworkPolicyList + plural: clusternetworkpolicies shortNames: - - anp - singular: adminnetworkpolicy + - cnp + singular: clusternetworkpolicy scope: Cluster versions: - additionalPrinterColumns: + - jsonPath: .spec.tier + name: Tier + type: string - jsonPath: .spec.priority name: Priority type: string - jsonPath: .metadata.creationTimestamp name: Age type: date - name: v1alpha1 + name: v1alpha2 schema: openAPIV3Schema: - description: |- - AdminNetworkPolicy is a cluster level resource that is part of the - AdminNetworkPolicy API. + description: ClusterNetworkPolicy is a cluster-wide network policy resource. properties: apiVersion: description: |- @@ -4467,172 +4969,233 @@ spec: metadata: type: object spec: - description: Specification of the desired behavior of AdminNetworkPolicy. + description: Spec defines the desired behavior of ClusterNetworkPolicy. properties: egress: description: |- Egress is the list of Egress rules to be applied to the selected pods. - A total of 100 rules will be allowed in each ANP instance. - The relative precedence of egress rules within a single ANP object (all of - which share the priority) will be determined by the order in which the rule - is written. Thus, a rule that appears at the top of the egress rules - would take the highest precedence. - ANPs with no egress rules do not affect egress traffic. + A maximum of 25 rules is allowed in this block. - Support: Core + The relative precedence of egress rules within a single CNP object + (all of which share the priority) will be determined by the order + in which the rule is written. + Thus, a rule that appears at the top of the egress rules + would take the highest precedence. + CNPs with no egress rules do not affect egress traffic. items: description: |- - AdminNetworkPolicyEgressRule describes an action to take on a particular - set of traffic originating from pods selected by a AdminNetworkPolicy's + ClusterNetworkPolicyEgressRule describes an action to take on a particular + set of traffic originating from pods selected by a ClusterNetworkPolicy's Subject field. + properties: action: description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic (even if it would otherwise have been denied by NetworkPolicy) - Deny: denies the selected traffic - Pass: instructs the selected traffic to skip any remaining ANP rules, and - then pass execution to any NetworkPolicies that select the pod. - If the pod is not selected by any NetworkPolicies then execution - is passed to any BaselineAdminNetworkPolicies that select the pod. + Action specifies the effect this rule will have on matching + traffic. Currently the following actions are supported: + - Accept: Accepts the selected traffic, allowing it to + egress. No further ClusterNetworkPolicy or NetworkPolicy + rules will be processed. - Support: Core + - Deny: Drops the selected traffic. No further + ClusterNetworkPolicy or NetworkPolicy rules will be + processed. + + - Pass: Skips all further ClusterNetworkPolicy rules in the + current tier for the selected traffic, and passes + evaluation to the next tier. enum: - - Allow + - Accept - Deny - Pass type: string name: description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - AdminNetworkPolicies. - - - Support: Core + Name is an identifier for this rule, that may be no more than + 100 characters in length. This field should be used by the implementation + to help improve observability, readability and error-reporting + for any applied policies. maxLength: 100 type: string - ports: + protocols: description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of destination ports for the outgoing egress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core + Protocols allows for more fine-grain matching of traffic on + protocol-specific attributes such as the port. If + unspecified, protocol-specific attributes will not be used + to match traffic. items: description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). + ClusterNetworkPolicyProtocol describes additional protocol-specific match rules. Exactly one field must be set. maxProperties: 1 minProperties: 1 properties: - namedPort: + destinationNamedPort: description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - + DestinationNamedPort selects a destination port on a pod based on the + ContainerPort name. You can't use this in a rule that targets resources + without named ports (e.g. Nodes or Networks). type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core + sctp: + description: SCTP specific protocol matches. + minProperties: 1 properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core + tcp: + description: TCP specific protocol matches. + minProperties: 1 properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + udp: + description: UDP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object type: object - maxItems: 100 + maxItems: 25 + minItems: 1 type: array to: description: |- - To is the List of destinations whose traffic this rule applies to. - If any AdminNetworkPolicyEgressPeer matches the destination of outgoing - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core + To is the list of destinations whose traffic this rule applies to. If any + element matches the destination of outgoing traffic then the specified + action is applied. This field must be defined and contain at least one + item. items: description: |- - AdminNetworkPolicyEgressPeer defines a peer to allow traffic to. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. + ClusterNetworkPolicyEgressPeer defines a peer to allow traffic to. + + Exactly one of the fields must be set for a given peer and this is enforced + by the validation rules on the CRD. If an implementation sees no fields are + set then it can infer that the deployed CRD is of an incompatible version + with an unknown field. In that case it should fail closed. + + For "Accept" rules, "fail closed" means: "treat the rule as matching no + traffic". For "Deny" and "Pass" rules, "fail closed" means: "treat the rule + as a 'Deny all' rule". maxProperties: 1 minProperties: 1 properties: @@ -4640,9 +5203,6 @@ spec: description: |- Namespaces defines a way to select all pods within a set of Namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: matchExpressions: description: @@ -4672,11 +5232,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -4693,1206 +5255,37 @@ spec: This is intended for representing entities that live outside the cluster, which can't be selected by pods, namespaces and nodes peers, but note that cluster-internal traffic will be checked against the rule as - well. So if you Allow or Deny traffic to `"0.0.0.0/0"`, that will allow + well. So if you Accept or Deny traffic to `"0.0.0.0/0"`, that will allow or deny all IPv4 pod-to-pod traffic as well. If you don't want that, add a rule that Passes all pod traffic before the Networks rule. - Each item in Networks should be provided in the CIDR format and should be IPv4 or IPv6, for example "10.0.0.0/8" or "fd00::/8". - - Networks can have upto 25 CIDRs specified. - - - Support: Extended - - - + Networks can have up to 25 CIDRs specified. items: description: |- - CIDR is an IP address range in CIDR notation (for example, "10.0.0.0/8" or "fd00::/8"). - This string must be validated by implementations using net.ParseCIDR - TODO: Introduce CEL CIDR validation regex isCIDR() in Kube 1.31 when it is available. + CIDR is an IP address range in CIDR notation + (for example, "10.0.0.0/8" or "fd00::/8"). maxLength: 43 type: string x-kubernetes-validations: - - message: - CIDR must be either an IPv4 or IPv6 address. - IPv4 address embedded in IPv6 addresses are not - supported - rule: self.contains(':') != self.contains('.') + - message: Invalid CIDR format provided + rule: isCIDR(self) maxItems: 25 minItems: 1 type: array x-kubernetes-list-type: set - nodes: - description: |- - Nodes defines a way to select a set of nodes in - the cluster. This field follows standard label selector - semantics; if present but empty, it selects all Nodes. - - - Support: Extended - - - - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: |- - Pods defines a way to select a set of pods in - a set of namespaces. Note that host-networked pods - are not included in this type of peer. - - - Support: Core - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - maxItems: 100 - minItems: 1 - type: array - required: - - action - - to - type: object - x-kubernetes-validations: - - message: - networks/nodes peer cannot be set with namedPorts since - there are no namedPorts for networks/nodes - rule: - "!(self.to.exists(peer, has(peer.networks) || has(peer.nodes)) - && has(self.ports) && self.ports.exists(port, has(port.namedPort)))" - maxItems: 100 - type: array - ingress: - description: |- - Ingress is the list of Ingress rules to be applied to the selected pods. - A total of 100 rules will be allowed in each ANP instance. - The relative precedence of ingress rules within a single ANP object (all of - which share the priority) will be determined by the order in which the rule - is written. Thus, a rule that appears at the top of the ingress rules - would take the highest precedence. - ANPs with no ingress rules do not affect ingress traffic. - - - Support: Core - items: - description: |- - AdminNetworkPolicyIngressRule describes an action to take on a particular - set of traffic destined for pods selected by an AdminNetworkPolicy's - Subject field. - properties: - action: - description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic (even if it would otherwise have been denied by NetworkPolicy) - Deny: denies the selected traffic - Pass: instructs the selected traffic to skip any remaining ANP rules, and - then pass execution to any NetworkPolicies that select the pod. - If the pod is not selected by any NetworkPolicies then execution - is passed to any BaselineAdminNetworkPolicies that select the pod. - - - Support: Core - enum: - - Allow - - Deny - - Pass - type: string - from: - description: |- - From is the list of sources whose traffic this rule applies to. - If any AdminNetworkPolicyIngressPeer matches the source of incoming - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core - items: - description: |- - AdminNetworkPolicyIngressPeer defines an in-cluster peer to allow traffic from. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: |- - Namespaces defines a way to select all pods within a set of Namespaces. - Note that host-networked pods are not included in this type of peer. - - - Support: Core - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: |- - Pods defines a way to select a set of pods in - a set of namespaces. Note that host-networked pods - are not included in this type of peer. - - - Support: Core - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - maxItems: 100 - minItems: 1 - type: array - name: - description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - AdminNetworkPolicies. - - - Support: Core - maxLength: 100 - type: string - ports: - description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of ports which should be matched on - the pods selected for this policy i.e the subject of the policy. - So it matches on the destination port for the ingress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core - items: - description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). - Exactly one field must be set. - maxProperties: 1 - minProperties: 1 - properties: - namedPort: - description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - - type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core - properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol - type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core - properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start - type: object - type: object - maxItems: 100 - type: array - required: - - action - - from - type: object - maxItems: 100 - type: array - priority: - description: |- - Priority is a value from 0 to 1000. Rules with lower priority values have - higher precedence, and are checked before rules with higher priority values. - All AdminNetworkPolicy rules have higher precedence than NetworkPolicy or - BaselineAdminNetworkPolicy rules - The behavior is undefined if two ANP objects have same priority. - - - Support: Core - format: int32 - maximum: 1000 - minimum: 0 - type: integer - subject: - description: |- - Subject defines the pods to which this AdminNetworkPolicy applies. - Note that host-networked pods are not included in subject selection. - - - Support: Core - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: Namespaces is used to select pods via namespace selectors. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: - Pods is used to select pods via namespace AND pod - selectors. - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - required: - - priority - - subject - type: object - status: - description: Status is the status to be reported by the implementation. - properties: - conditions: - items: - description: - "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - required: - - conditions - type: object - required: - - metadata - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: null - storedVersions: null ---- -# Source: calico/templates/kdd-crds.yaml -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/30 - policy.networking.k8s.io/bundle-version: v0.1.1 - policy.networking.k8s.io/channel: experimental - creationTimestamp: null - name: baselineadminnetworkpolicies.policy.networking.k8s.io -spec: - group: policy.networking.k8s.io - names: - kind: BaselineAdminNetworkPolicy - listKind: BaselineAdminNetworkPolicyList - plural: baselineadminnetworkpolicies - shortNames: - - banp - singular: baselineadminnetworkpolicy - scope: Cluster - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: |- - BaselineAdminNetworkPolicy is a cluster level resource that is part of the - AdminNetworkPolicy API. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: Specification of the desired behavior of BaselineAdminNetworkPolicy. - properties: - egress: - description: |- - Egress is the list of Egress rules to be applied to the selected pods if - they are not matched by any AdminNetworkPolicy or NetworkPolicy rules. - A total of 100 Egress rules will be allowed in each BANP instance. - The relative precedence of egress rules within a single BANP object - will be determined by the order in which the rule is written. - Thus, a rule that appears at the top of the egress rules - would take the highest precedence. - BANPs with no egress rules do not affect egress traffic. - - - Support: Core - items: - description: |- - BaselineAdminNetworkPolicyEgressRule describes an action to take on a particular - set of traffic originating from pods selected by a BaselineAdminNetworkPolicy's - Subject field. - - properties: - action: - description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic - Deny: denies the selected traffic - - - Support: Core - enum: - - Allow - - Deny - type: string - name: - description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - BaselineAdminNetworkPolicies. - - - Support: Core - maxLength: 100 - type: string - ports: - description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of destination ports for the outgoing egress traffic. - If Ports is not set then the rule does not filter traffic via port. - items: - description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). - Exactly one field must be set. - maxProperties: 1 - minProperties: 1 - properties: - namedPort: - description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - - type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core - properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol - type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core - properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start - type: object - type: object - maxItems: 100 - type: array - to: - description: |- - To is the list of destinations whose traffic this rule applies to. - If any AdminNetworkPolicyEgressPeer matches the destination of outgoing - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core - items: - description: |- - AdminNetworkPolicyEgressPeer defines a peer to allow traffic to. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: |- - Namespaces defines a way to select all pods within a set of Namespaces. - Note that host-networked pods are not included in this type of peer. - - - Support: Core - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - networks: - description: |- - Networks defines a way to select peers via CIDR blocks. - This is intended for representing entities that live outside the cluster, - which can't be selected by pods, namespaces and nodes peers, but note - that cluster-internal traffic will be checked against the rule as - well. So if you Allow or Deny traffic to `"0.0.0.0/0"`, that will allow - or deny all IPv4 pod-to-pod traffic as well. If you don't want that, - add a rule that Passes all pod traffic before the Networks rule. - - - Each item in Networks should be provided in the CIDR format and should be - IPv4 or IPv6, for example "10.0.0.0/8" or "fd00::/8". - - - Networks can have upto 25 CIDRs specified. - - - Support: Extended - - - - items: - description: |- - CIDR is an IP address range in CIDR notation (for example, "10.0.0.0/8" or "fd00::/8"). - This string must be validated by implementations using net.ParseCIDR - TODO: Introduce CEL CIDR validation regex isCIDR() in Kube 1.31 when it is available. - maxLength: 43 - type: string - x-kubernetes-validations: - - message: - CIDR must be either an IPv4 or IPv6 address. - IPv4 address embedded in IPv6 addresses are not - supported - rule: self.contains(':') != self.contains('.') - maxItems: 25 - minItems: 1 - type: array - x-kubernetes-list-type: set - nodes: - description: |- - Nodes defines a way to select a set of nodes in - the cluster. This field follows standard label selector - semantics; if present but empty, it selects all Nodes. - - - Support: Extended - - - - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: + pods: description: |- Pods defines a way to select a set of pods in a set of namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -5923,11 +5316,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -5940,8 +5335,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -5972,11 +5367,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -5988,73 +5385,80 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object - maxItems: 100 + maxItems: 25 minItems: 1 type: array required: - action - to type: object - x-kubernetes-validations: - - message: - networks/nodes peer cannot be set with namedPorts since - there are no namedPorts for networks/nodes - rule: - "!(self.to.exists(peer, has(peer.networks) || has(peer.nodes)) - && has(self.ports) && self.ports.exists(port, has(port.namedPort)))" - maxItems: 100 + maxItems: 25 type: array ingress: description: |- - Ingress is the list of Ingress rules to be applied to the selected pods - if they are not matched by any AdminNetworkPolicy or NetworkPolicy rules. - A total of 100 Ingress rules will be allowed in each BANP instance. - The relative precedence of ingress rules within a single BANP object - will be determined by the order in which the rule is written. - Thus, a rule that appears at the top of the ingress rules - would take the highest precedence. - BANPs with no ingress rules do not affect ingress traffic. + Ingress is the list of Ingress rules to be applied to the selected pods. + A maximum of 25 rules is allowed in this block. - Support: Core + The relative precedence of ingress rules within a single CNP object + (all of which share the priority) will be determined by the order + in which the rule is written. + Thus, a rule that appears at the top of the ingress rules + would take the highest precedence. + CNPs with no ingress rules do not affect ingress traffic. items: description: |- - BaselineAdminNetworkPolicyIngressRule describes an action to take on a particular - set of traffic destined for pods selected by a BaselineAdminNetworkPolicy's + ClusterNetworkPolicyIngressRule describes an action to take on a particular + set of traffic destined for pods selected by a ClusterNetworkPolicy's Subject field. properties: action: description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic - Deny: denies the selected traffic + Action specifies the effect this rule will have on matching + traffic. Currently the following actions are supported: + + - Accept: Accepts the selected traffic, allowing it into + the destination. No further ClusterNetworkPolicy or + NetworkPolicy rules will be processed. + Note: while Accept ensures traffic is accepted by + Kubernetes network policy, it is still possible that the + packet is blocked in other ways: custom nftable rules, + high-layers e.g. service mesh. - Support: Core + - Deny: Drops the selected traffic. No further + ClusterNetworkPolicy or NetworkPolicy rules will be + processed. + + - Pass: Skips all further ClusterNetworkPolicy rules in the + current tier for the selected traffic, and passes + evaluation to the next tier. enum: - - Allow + - Accept - Deny + - Pass type: string from: description: |- From is the list of sources whose traffic this rule applies to. - If any AdminNetworkPolicyIngressPeer matches the source of incoming + If any element matches the source of incoming traffic then the specified action is applied. This field must be defined and contain at least one item. - - - Support: Core items: description: |- - AdminNetworkPolicyIngressPeer defines an in-cluster peer to allow traffic from. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. + ClusterNetworkPolicyIngressPeer defines a peer to allow traffic from. + + Exactly one of the fields must be set for a given peer and this is enforced + by the validation rules on the CRD. If an implementation sees no fields are + set then it can infer that the deployed CRD is of an incompatible version + with an unknown field. In that case it should fail closed. + + For "Accept" rules, "fail closed" means: "treat the rule as matching no + traffic". For "Deny" and "Pass" rules, "fail closed" means: "treat the rule + as a 'Deny all' rule". maxProperties: 1 minProperties: 1 properties: @@ -6062,9 +5466,6 @@ spec: description: |- Namespaces defines a way to select all pods within a set of Namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: matchExpressions: description: @@ -6094,11 +5495,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6114,14 +5517,11 @@ spec: Pods defines a way to select a set of pods in a set of namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -6152,11 +5552,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6169,8 +5571,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -6201,11 +5603,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6217,140 +5621,206 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object - maxItems: 100 + maxItems: 25 minItems: 1 type: array name: description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - BaselineAdminNetworkPolicies. - - - Support: Core + Name is an identifier for this rule, that may be no more than + 100 characters in length. This field should be used by the implementation + to help improve observability, readability and error-reporting + for any applied policies. maxLength: 100 type: string - ports: + protocols: description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of ports which should be matched on - the pods selected for this policy i.e the subject of the policy. - So it matches on the destination port for the ingress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core + Protocols allows for more fine-grain matching of traffic on + protocol-specific attributes such as the port. If + unspecified, protocol-specific attributes will not be used + to match traffic. items: description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). + ClusterNetworkPolicyProtocol describes additional protocol-specific match rules. Exactly one field must be set. maxProperties: 1 minProperties: 1 properties: - namedPort: + destinationNamedPort: description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - + DestinationNamedPort selects a destination port on a pod based on the + ContainerPort name. You can't use this in a rule that targets resources + without named ports (e.g. Nodes or Networks). type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core + sctp: + description: SCTP specific protocol matches. + minProperties: 1 properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core + tcp: + description: TCP specific protocol matches. + minProperties: 1 properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + udp: + description: UDP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object type: object - maxItems: 100 + maxItems: 25 + minItems: 1 type: array required: - action - from type: object - maxItems: 100 + maxItems: 25 type: array - subject: + priority: description: |- - Subject defines the pods to which this BaselineAdminNetworkPolicy applies. - Note that host-networked pods are not included in subject selection. - - - Support: Core + Priority is a value from 0 to 1000 indicating the precedence of + the policy within its tier. Policies with lower priority values have + higher precedence, and are checked before policies with higher priority + values in the same tier. All Admin tier rules have higher precedence than + NetworkPolicy or Baseline tier rules. + If two (or more) policies in the same tier with the same priority + could match a connection, then the implementation can apply any of the + matching policies to the connection, and there is no way for the user to + reliably determine which one it will choose. Administrators must be + careful about assigning the priorities for policies with rules that will + match many connections, and ensure that policies have unique priority + values in cases where ambiguity would be unacceptable. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + subject: + description: + Subject defines the pods to which this ClusterNetworkPolicy + applies. maxProperties: 1 minProperties: 1 properties: @@ -6385,11 +5855,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6407,8 +5879,8 @@ spec: properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -6438,11 +5910,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6455,8 +5929,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -6486,11 +5960,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6502,12 +5978,48 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object + tier: + description: |- + Tier is used as the top-level grouping for network policy prioritization. + + Policy tiers are evaluated in the following order: + * Admin tier + * NetworkPolicy tier + * Baseline tier + + ClusterNetworkPolicy can use 2 of these tiers: Admin and Baseline. + + The Admin tier takes precedence over all other policies. Policies + defined in this tier are used to set cluster-wide security rules + that cannot be overridden in the other tiers. If Admin tier has + made a final decision (Accept or Deny) on a connection, then no + further evaluation is done. + + NetworkPolicy tier is the tier for the namespaced v1.NetworkPolicy. + These policies are intended for the application developer to describe + the security policy associated with their deployments inside their + namespace. v1.NetworkPolicy always makes a final decision for selected + pods. Further evaluation only happens for Pods not selected by a + v1.NetworkPolicy. + + Baseline tier is a cluster-wide policy that can be overridden by the + v1.NetworkPolicy. If Baseline tier has made a final decision (Accept or + Deny) on a connection, then no further evaluation is done. + + If a given connection wasn't allowed or denied by any of the tiers, + the default kubernetes policy is applied, which says that + all pods can communicate with each other. + enum: + - Admin + - Baseline + type: string required: + - priority - subject + - tier type: object status: description: Status is the status to be reported by the implementation. @@ -6515,16 +6027,8 @@ spec: conditions: items: description: - "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -6565,12 +6069,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string @@ -6592,11 +6091,6 @@ spec: - metadata - spec type: object - x-kubernetes-validations: - - message: - Only one baseline admin network policy with metadata.name="default" - can be created in the cluster - rule: self.metadata.name == 'default' served: true storage: true subresources: @@ -6712,6 +6206,34 @@ rules: - update # watch for changes - watch + - apiGroups: ["crd.projectcalico.org"] + resources: + - kubecontrollersconfigurations/status + verbs: + - get + - update + # Needed for policy name migrator. + - apiGroups: ["crd.projectcalico.org"] + resources: + - networkpolicies + - stagednetworkpolicies + - globalnetworkpolicies + - stagedglobalnetworkpolicies + verbs: + - watch + - list + - get + - create + - update + - delete + # Needed for policy name migrator to check calico/node daemonset status. + - apiGroups: ["apps"] + resources: + - daemonsets + verbs: + - get + resourceNames: + - calico-node --- # Source: calico/templates/calico-node-rbac.yaml # Include a clusterrole for the calico-node DaemonSet, @@ -6776,11 +6298,10 @@ rules: verbs: - watch - list - # Watch for changes to Kubernetes (Baseline)AdminNetworkPolicies. + # Watch for changes to Kubernetes ClusterNetworkPolicies. - apiGroups: ["policy.networking.k8s.io"] resources: - - adminnetworkpolicies - - baselineadminnetworkpolicies + - clusternetworkpolicies verbs: - watch - list @@ -6898,6 +6419,14 @@ rules: - daemonsets verbs: - get + # For monitoring KubeVirt live migration. + - apiGroups: ["kubevirt.io"] + resources: + - virtualmachineinstancemigrations + verbs: + - get + - list + - watch --- # Source: calico/templates/calico-node-rbac.yaml # CNI cluster role diff --git a/manifests/calico.yaml b/manifests/calico.yaml index 9c46cd6d8ed..4b273e613ff 100644 --- a/manifests/calico.yaml +++ b/manifests/calico.yaml @@ -119,6 +119,9 @@ spec: format: int32 type: integer bindMode: + enum: + - None + - NodeIP type: string communities: items: @@ -129,11 +132,14 @@ spec: pattern: ^(\d+):(\d+)$|^(\d+):(\d+):(\d+)$ type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set ignoredInterfaces: items: type: string type: array + x-kubernetes-list-type: set listenPort: maximum: 65535 minimum: 1 @@ -143,6 +149,8 @@ spec: localWorkloadPeeringIPV6: type: string logSeverityScreen: + default: Info + pattern: ^(?i)(Trace|Debug|Info|Warning|Error|Fatal)?$ type: string nodeMeshMaxRestartTime: type: string @@ -168,27 +176,36 @@ spec: items: properties: cidr: + format: cidr type: string communities: items: type: string type: array type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceClusterIPs: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceExternalIPs: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceLoadBalancerAggregation: default: Enabled enum: @@ -199,9 +216,12 @@ spec: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -240,12 +260,21 @@ spec: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -260,22 +289,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array exportV6: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -290,22 +332,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array importV4: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -320,22 +375,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array importV6: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -350,11 +418,15 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array type: object type: object @@ -409,15 +481,10 @@ spec: maxRestartTime: type: string nextHopMode: - allOf: - - enum: - - Auto - - Self - - Keep - - enum: - - Auto - - Self - - Keep + enum: + - Auto + - Self + - Keep type: string node: type: string @@ -449,11 +516,18 @@ spec: reachableBy: type: string reversePeering: - enum: - - Auto - - Manual + allOf: + - enum: + - Auto + - Manual + - enum: + - Auto + - Manual type: string sourceAddress: + enum: + - UseNodeIP + - None type: string ttlSecurity: type: integer @@ -542,6 +616,10 @@ spec: properties: classes: items: + enum: + - Agent + - BGP + - Routes type: string type: array node: @@ -563,6 +641,9 @@ spec: routerID: type: string state: + enum: + - Ready + - NotReady type: string version: type: string @@ -576,6 +657,9 @@ spec: routerID: type: string state: + enum: + - Ready + - NotReady type: string version: type: string @@ -599,8 +683,20 @@ spec: since: type: string state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close type: string type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer type: string type: object type: array @@ -612,8 +708,20 @@ spec: since: type: string state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close type: string type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer type: string type: object type: array @@ -643,9 +751,18 @@ spec: peerIP: type: string sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer type: string type: object type: + enum: + - FIB + - RIB type: string type: object type: array @@ -663,9 +780,18 @@ spec: peerIP: type: string sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer type: string type: object type: + enum: + - FIB + - RIB type: string type: object type: array @@ -1019,15 +1145,9 @@ spec: if it detects the current value is 2 (strict mode that hurts performance). When set to "Strict", Felix will not modify the JIT hardening setting. [Default: Auto] type: string - bpfKubeProxyEndpointSlicesEnabled: + bpfKubeProxyHealthzPort: description: |- - BPFKubeProxyEndpointSlicesEnabled is deprecated and has no effect. BPF - kube-proxy always accepts endpoint slices. This option will be removed in - the next release. - type: boolean - bpfKubeProxyHealtzPort: - description: |- - BPFKubeProxyHealtzPort, in BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. + BPFKubeProxyHealthzPort, in BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. The health check server is used by external load balancers to determine if this node should receive traffic. [Default: 10256] type: integer bpfKubeProxyIptablesCleanupEnabled: @@ -1068,6 +1188,23 @@ spec: [Default: Off]. pattern: ^(?i)(Off|Info|Debug)?$ type: string + bpfMaglevMaxEndpointsPerService: + description: |- + BPFMaglevMaxEndpointsPerService is the maximum number of endpoints + expected to be part of a single Maglev-enabled service. + + Influences the size of the per-service Maglev lookup-tables generated by Felix + and thus the amount of memory reserved. + + [Default: 100] + type: integer + bpfMaglevMaxServices: + description: |- + BPFMaglevMaxServices is the maximum number of expected Maglev-enabled + services that Felix will allocate lookup-tables for. + + [Default: 100] + type: integer bpfMapSizeConntrack: description: |- BPFMapSizeConntrack sets the size for the conntrack map. This map must be large enough to hold @@ -1156,17 +1293,14 @@ spec: type: string bpfRedirectToPeer: description: |- - BPFRedirectToPeer controls which whether it is allowed to forward straight to the - peer side of the workload devices. It is allowed for any host L2 devices by default - (L2Only), but it breaks TCP dump on the host side of workload device as it bypasses - it on ingress. Value of Enabled also allows redirection from L3 host devices like - IPIP tunnel or Wireguard directly to the peer side of the workload's device. This - makes redirection faster, however, it breaks tools like tcpdump on the peer side. - Use Enabled with caution. [Default: L2Only] + BPFRedirectToPeer controls whether traffic may be forwarded directly to the peer side of a workload’s device. + Note that the legacy "L2Only" option is now deprecated and if set it is treated like "Enabled. + Setting this option to "Enabled" allows direct redirection (including from L3 host devices such as IPIP tunnels or WireGuard), + which can improve redirection performance but causes the redirected packets to bypass the host‑side ingress path. + As a result, packet‑capture tools on the host side of the workload device (for example, tcpdump) will not see that traffic. [Default: Enabled] enum: - Enabled - Disabled - - L2Only type: string cgroupV2Path: description: @@ -1517,6 +1651,10 @@ spec: Warning: changing this on a running system can leave "orphaned" rules in the "other" backend. These should be cleaned up to avoid confusing interactions. + enum: + - Legacy + - NFT + - Auto pattern: ^(?i)(Auto|Legacy|NFT)?$ type: string iptablesFilterAllowAction: @@ -1532,26 +1670,10 @@ spec: with an iptables "DROP" action. If you want to use "REJECT" action instead you can configure it in here. pattern: ^(?i)(Drop|Reject)?$ type: string - iptablesLockFilePath: - description: |- - IptablesLockFilePath is the location of the iptables lock file. You may need to change this - if the lock file is not in its standard location (for example if you have mapped it into Felix's - container at a different path). [Default: /run/xtables.lock] - type: string iptablesLockProbeInterval: description: |- - IptablesLockProbeInterval when IptablesLockTimeout is enabled: the time that Felix will wait between - attempts to acquire the iptables lock if it is not available. Lower values make Felix more - responsive when the lock is contended, but use more CPU. [Default: 50ms] - pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ - type: string - iptablesLockTimeout: - description: |- - IptablesLockTimeout is the time that Felix itself will wait for the iptables lock (rather than delegating the - lock handling to the `iptables` command). - - Deprecated: `iptables-restore` v1.8+ always takes the lock, so enabling this feature results in deadlock. - [Default: 0s disabled] + IptablesLockProbeInterval configures the interval between attempts to claim + the xtables lock. Shorter intervals are more responsive but use more CPU. [Default: 50ms] pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ type: string iptablesMangleAllowAction: @@ -1611,6 +1733,19 @@ spec: pattern: ^.* x-kubernetes-int-or-string: true type: array + logActionRateLimit: + description: |- + LogActionRateLimit sets the rate of hitting a Log action. The value must be in the format "N/unit", + where N is a number and unit is one of: second, minute, hour, or day. For example: "10/second" or "100/hour". + pattern: ^[1-9]\d{0,3}/(?:second|minute|hour|day)$ + type: string + logActionRateLimitBurst: + description: + LogActionRateLimitBurst sets the rate limit burst of + hitting a Log action when LogActionRateLimit is enabled. + maximum: 9999 + minimum: 0 + type: integer logDebugFilenameRegex: description: |- LogDebugFilenameRegex controls which source code files have their Debug log output included in the logs. @@ -1623,9 +1758,17 @@ spec: none to disable file logging. [Default: /var/log/calico/felix.log]" type: string logPrefix: - description: - "LogPrefix is the log prefix that Felix uses when rendering - LOG rules. [Default: calico-packet]" + description: |- + LogPrefix is the log prefix that Felix uses when rendering LOG rules. It is possible to use the following specifiers + to include extra information in the log prefix. + - %t: Tier name. + - %k: Kind (short names). + - %n: Policy or profile name. + - %p: Policy or profile name (namespace/name for namespaced kinds or just name for non namespaced kinds). + Calico includes ": " characters at the end of the generated log prefix. + Note that iptables shows up to 29 characters for the log prefix and nftables up to 127 characters. Extra characters are truncated. + [Default: calico-packet] + pattern: "^([a-zA-Z0-9%: /_-])*$" type: string logSeverityFile: description: @@ -1729,9 +1872,10 @@ spec: format: int32 type: integer nftablesMode: + default: Auto description: "NFTablesMode configures nftables support in Felix. [Default: - Disabled]" + Auto]" enum: - Disabled - Enabled @@ -1767,6 +1911,21 @@ spec: PrometheusGoMetricsEnabled disables Go runtime metrics collection, which the Prometheus client does by default, when set to false. This reduces the number of metrics reported, reducing Prometheus load. [Default: true] type: boolean + prometheusMetricsCAFile: + description: |- + PrometheusMetricsCAFile defines the absolute path to the TLS CA certificate file used for securing the /metrics endpoint. + This certificate must be valid and accessible by the calico-node process. + type: string + prometheusMetricsCertFile: + description: |- + PrometheusMetricsCertFile defines the absolute path to the TLS certificate file used for securing the /metrics endpoint. + This certificate must be valid and accessible by the calico-node process. + type: string + prometheusMetricsClientAuth: + description: |- + PrometheusMetricsClientAuth specifies the client authentication type for the /metrics endpoint. + This determines how the server validates client certificates. Default is "RequireAndVerifyClientCert". + type: string prometheusMetricsEnabled: description: "PrometheusMetricsEnabled enables the Prometheus metrics @@ -1777,6 +1936,11 @@ spec: "PrometheusMetricsHost is the host that the Prometheus metrics server should bind to. [Default: empty]" type: string + prometheusMetricsKeyFile: + description: |- + PrometheusMetricsKeyFile defines the absolute path to the private key file corresponding to the TLS certificate + used for securing the /metrics endpoint. The private key must be valid and accessible by the calico-node process. + type: string prometheusMetricsPort: description: "PrometheusMetricsPort is the TCP port that the Prometheus @@ -2060,6 +2224,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -2069,6 +2238,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2099,6 +2269,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2129,11 +2300,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -2145,8 +2323,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -2169,6 +2351,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2199,6 +2382,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2218,6 +2402,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -2227,6 +2416,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2257,6 +2447,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2287,11 +2478,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -2303,8 +2501,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -2327,6 +2529,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2357,6 +2560,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2378,6 +2582,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array preDNAT: @@ -2387,11 +2593,18 @@ spec: serviceAccountSelector: type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2430,6 +2643,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2468,6 +2682,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set interfaceName: type: string node: @@ -2478,6 +2693,8 @@ spec: name: type: string port: + maximum: 65535 + minimum: 0 type: integer protocol: anyOf: @@ -2495,6 +2712,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2531,6 +2749,9 @@ spec: properties: affinity: type: string + affinityClaimTime: + format: date-time + type: string allocations: items: type: integer @@ -2541,6 +2762,10 @@ spec: attributes: items: properties: + alternate: + additionalProperties: + type: string + type: object handle_id: type: string secondary: @@ -2610,6 +2835,11 @@ spec: properties: autoAllocateBlocks: type: boolean + kubeVirtVMAddressPersistence: + enum: + - Enabled + - Disabled + type: string maxBlocksPerHost: maximum: 2147483647 minimum: 0 @@ -2700,39 +2930,47 @@ spec: properties: allowedUses: items: + enum: + - Workload + - Tunnel + - LoadBalancer type: string type: array + x-kubernetes-list-type: set assignmentMode: + default: Automatic enum: - Automatic - Manual type: string blockSize: + maximum: 128 + minimum: 0 type: integer cidr: + format: cidr type: string disableBGPExport: type: boolean disabled: type: boolean - ipip: - properties: - enabled: - type: boolean - mode: - type: string - type: object ipipMode: + enum: + - Never + - Always + - CrossSubnet type: string namespaceSelector: type: string - nat-outgoing: - type: boolean natOutgoing: type: boolean nodeSelector: type: string vxlanMode: + enum: + - Never + - Always + - CrossSubnet type: string required: - cidr @@ -2771,9 +3009,11 @@ spec: spec: properties: reservedCIDRs: + format: cidr items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2813,6 +3053,10 @@ spec: loadBalancer: properties: assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly type: string type: object namespace: @@ -2825,6 +3069,9 @@ spec: hostEndpoint: properties: autoCreate: + enum: + - Enabled + - Disabled type: string createDefaultHostEndpoint: type: string @@ -2838,7 +3085,8 @@ spec: items: type: string type: array - interfaceSelector: + x-kubernetes-list-type: set + interfacePattern: type: string labels: additionalProperties: @@ -2854,6 +3102,9 @@ spec: reconcilerPeriod: type: string syncLabels: + enum: + - Enabled + - Disabled type: string type: object policy: @@ -2861,6 +3112,15 @@ spec: reconcilerPeriod: type: string type: object + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object serviceAccount: properties: reconcilerPeriod: @@ -2874,14 +3134,30 @@ spec: type: object debugProfilePort: format: int32 + maximum: 65535 + minimum: 0 type: integer etcdV3CompactionPeriod: type: string healthChecks: + default: Enabled + enum: + - Enabled + - Disabled type: string logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic type: string prometheusMetricsPort: + maximum: 65535 + minimum: 0 type: integer required: - controllers @@ -2899,6 +3175,10 @@ spec: loadBalancer: properties: assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly type: string type: object namespace: @@ -2911,6 +3191,9 @@ spec: hostEndpoint: properties: autoCreate: + enum: + - Enabled + - Disabled type: string createDefaultHostEndpoint: type: string @@ -2924,7 +3207,8 @@ spec: items: type: string type: array - interfaceSelector: + x-kubernetes-list-type: set + interfacePattern: type: string labels: additionalProperties: @@ -2940,6 +3224,9 @@ spec: reconcilerPeriod: type: string syncLabels: + enum: + - Enabled + - Disabled type: string type: object policy: @@ -2947,6 +3234,15 @@ spec: reconcilerPeriod: type: string type: object + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object serviceAccount: properties: reconcilerPeriod: @@ -2960,14 +3256,30 @@ spec: type: object debugProfilePort: format: int32 + maximum: 65535 + minimum: 0 type: integer etcdV3CompactionPeriod: type: string healthChecks: + default: Enabled + enum: + - Enabled + - Disabled type: string logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic type: string prometheusMetricsPort: + maximum: 65535 + minimum: 0 type: integer required: - controllers @@ -2976,6 +3288,8 @@ spec: type: object served: true storage: true + subresources: + status: {} --- # Source: calico/templates/kdd-crds.yaml apiVersion: apiextensions.k8s.io/v1 @@ -3010,6 +3324,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3019,6 +3338,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3049,6 +3369,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3079,11 +3400,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3095,8 +3423,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3119,6 +3451,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3149,6 +3482,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3168,6 +3502,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3177,6 +3516,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3207,6 +3547,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3237,11 +3578,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3253,8 +3601,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3277,6 +3629,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3307,6 +3660,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3326,6 +3680,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array selector: @@ -3333,11 +3689,18 @@ spec: serviceAccountSelector: type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -3376,6 +3739,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -3418,6 +3782,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3427,6 +3796,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3457,6 +3827,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3487,11 +3858,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3503,8 +3881,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3527,6 +3909,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3557,6 +3940,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3576,6 +3960,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3585,6 +3974,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3615,6 +4005,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3645,11 +4036,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3661,8 +4059,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3685,6 +4087,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3715,6 +4118,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3736,6 +4140,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array preDNAT: @@ -3745,13 +4151,25 @@ spec: serviceAccountSelector: type: string stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -3997,8 +4415,16 @@ spec: policyTypes: items: type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string type: object type: object @@ -4038,6 +4464,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -4047,6 +4478,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4077,6 +4509,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4107,11 +4540,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -4123,8 +4563,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -4147,6 +4591,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4177,6 +4622,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4196,6 +4642,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -4205,6 +4656,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4235,6 +4687,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4265,11 +4718,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -4281,8 +4741,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -4305,6 +4769,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4335,6 +4800,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4354,6 +4820,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array selector: @@ -4361,13 +4829,25 @@ spec: serviceAccountSelector: type: string stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -4403,14 +4883,36 @@ spec: spec: properties: defaultAction: - enum: - - Pass - - Deny + allOf: + - enum: + - Allow + - Deny + - Log + - Pass + - enum: + - Pass + - Deny type: string order: type: number type: object + required: + - metadata + - spec type: object + x-kubernetes-validations: + - message: The 'kube-admin' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-admin' ? self.spec.defaultAction == + 'Pass' : true" + - message: The 'kube-baseline' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-baseline' ? self.spec.defaultAction + == 'Pass' : true" + - message: The 'default' tier must have default action 'Deny' + rule: + "self.metadata.name == 'default' ? self.spec.defaultAction == 'Deny' + : true" served: true storage: true --- @@ -4419,35 +4921,35 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/30 - policy.networking.k8s.io/bundle-version: v0.1.1 - policy.networking.k8s.io/channel: experimental - creationTimestamp: null - name: adminnetworkpolicies.policy.networking.k8s.io + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/300 + policy.networking.k8s.io/bundle-version: v0.1.7 + policy.networking.k8s.io/channel: standard + name: clusternetworkpolicies.policy.networking.k8s.io spec: group: policy.networking.k8s.io names: - kind: AdminNetworkPolicy - listKind: AdminNetworkPolicyList - plural: adminnetworkpolicies + kind: ClusterNetworkPolicy + listKind: ClusterNetworkPolicyList + plural: clusternetworkpolicies shortNames: - - anp - singular: adminnetworkpolicy + - cnp + singular: clusternetworkpolicy scope: Cluster versions: - additionalPrinterColumns: + - jsonPath: .spec.tier + name: Tier + type: string - jsonPath: .spec.priority name: Priority type: string - jsonPath: .metadata.creationTimestamp name: Age type: date - name: v1alpha1 + name: v1alpha2 schema: openAPIV3Schema: - description: |- - AdminNetworkPolicy is a cluster level resource that is part of the - AdminNetworkPolicy API. + description: ClusterNetworkPolicy is a cluster-wide network policy resource. properties: apiVersion: description: |- @@ -4467,172 +4969,233 @@ spec: metadata: type: object spec: - description: Specification of the desired behavior of AdminNetworkPolicy. + description: Spec defines the desired behavior of ClusterNetworkPolicy. properties: egress: description: |- Egress is the list of Egress rules to be applied to the selected pods. - A total of 100 rules will be allowed in each ANP instance. - The relative precedence of egress rules within a single ANP object (all of - which share the priority) will be determined by the order in which the rule - is written. Thus, a rule that appears at the top of the egress rules - would take the highest precedence. - ANPs with no egress rules do not affect egress traffic. + A maximum of 25 rules is allowed in this block. - Support: Core + The relative precedence of egress rules within a single CNP object + (all of which share the priority) will be determined by the order + in which the rule is written. + Thus, a rule that appears at the top of the egress rules + would take the highest precedence. + CNPs with no egress rules do not affect egress traffic. items: description: |- - AdminNetworkPolicyEgressRule describes an action to take on a particular - set of traffic originating from pods selected by a AdminNetworkPolicy's + ClusterNetworkPolicyEgressRule describes an action to take on a particular + set of traffic originating from pods selected by a ClusterNetworkPolicy's Subject field. + properties: action: description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic (even if it would otherwise have been denied by NetworkPolicy) - Deny: denies the selected traffic - Pass: instructs the selected traffic to skip any remaining ANP rules, and - then pass execution to any NetworkPolicies that select the pod. - If the pod is not selected by any NetworkPolicies then execution - is passed to any BaselineAdminNetworkPolicies that select the pod. + Action specifies the effect this rule will have on matching + traffic. Currently the following actions are supported: + - Accept: Accepts the selected traffic, allowing it to + egress. No further ClusterNetworkPolicy or NetworkPolicy + rules will be processed. - Support: Core + - Deny: Drops the selected traffic. No further + ClusterNetworkPolicy or NetworkPolicy rules will be + processed. + + - Pass: Skips all further ClusterNetworkPolicy rules in the + current tier for the selected traffic, and passes + evaluation to the next tier. enum: - - Allow + - Accept - Deny - Pass type: string name: description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - AdminNetworkPolicies. - - - Support: Core + Name is an identifier for this rule, that may be no more than + 100 characters in length. This field should be used by the implementation + to help improve observability, readability and error-reporting + for any applied policies. maxLength: 100 type: string - ports: + protocols: description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of destination ports for the outgoing egress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core + Protocols allows for more fine-grain matching of traffic on + protocol-specific attributes such as the port. If + unspecified, protocol-specific attributes will not be used + to match traffic. items: description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). + ClusterNetworkPolicyProtocol describes additional protocol-specific match rules. Exactly one field must be set. maxProperties: 1 minProperties: 1 properties: - namedPort: + destinationNamedPort: description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - + DestinationNamedPort selects a destination port on a pod based on the + ContainerPort name. You can't use this in a rule that targets resources + without named ports (e.g. Nodes or Networks). type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core + sctp: + description: SCTP specific protocol matches. + minProperties: 1 properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core + tcp: + description: TCP specific protocol matches. + minProperties: 1 properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + udp: + description: UDP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object type: object - maxItems: 100 + maxItems: 25 + minItems: 1 type: array to: description: |- - To is the List of destinations whose traffic this rule applies to. - If any AdminNetworkPolicyEgressPeer matches the destination of outgoing - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core + To is the list of destinations whose traffic this rule applies to. If any + element matches the destination of outgoing traffic then the specified + action is applied. This field must be defined and contain at least one + item. items: description: |- - AdminNetworkPolicyEgressPeer defines a peer to allow traffic to. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. + ClusterNetworkPolicyEgressPeer defines a peer to allow traffic to. + + Exactly one of the fields must be set for a given peer and this is enforced + by the validation rules on the CRD. If an implementation sees no fields are + set then it can infer that the deployed CRD is of an incompatible version + with an unknown field. In that case it should fail closed. + + For "Accept" rules, "fail closed" means: "treat the rule as matching no + traffic". For "Deny" and "Pass" rules, "fail closed" means: "treat the rule + as a 'Deny all' rule". maxProperties: 1 minProperties: 1 properties: @@ -4640,9 +5203,6 @@ spec: description: |- Namespaces defines a way to select all pods within a set of Namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: matchExpressions: description: @@ -4672,11 +5232,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -4693,1206 +5255,37 @@ spec: This is intended for representing entities that live outside the cluster, which can't be selected by pods, namespaces and nodes peers, but note that cluster-internal traffic will be checked against the rule as - well. So if you Allow or Deny traffic to `"0.0.0.0/0"`, that will allow + well. So if you Accept or Deny traffic to `"0.0.0.0/0"`, that will allow or deny all IPv4 pod-to-pod traffic as well. If you don't want that, add a rule that Passes all pod traffic before the Networks rule. - Each item in Networks should be provided in the CIDR format and should be IPv4 or IPv6, for example "10.0.0.0/8" or "fd00::/8". - - Networks can have upto 25 CIDRs specified. - - - Support: Extended - - - + Networks can have up to 25 CIDRs specified. items: description: |- - CIDR is an IP address range in CIDR notation (for example, "10.0.0.0/8" or "fd00::/8"). - This string must be validated by implementations using net.ParseCIDR - TODO: Introduce CEL CIDR validation regex isCIDR() in Kube 1.31 when it is available. + CIDR is an IP address range in CIDR notation + (for example, "10.0.0.0/8" or "fd00::/8"). maxLength: 43 type: string x-kubernetes-validations: - - message: - CIDR must be either an IPv4 or IPv6 address. - IPv4 address embedded in IPv6 addresses are not - supported - rule: self.contains(':') != self.contains('.') + - message: Invalid CIDR format provided + rule: isCIDR(self) maxItems: 25 minItems: 1 type: array x-kubernetes-list-type: set - nodes: - description: |- - Nodes defines a way to select a set of nodes in - the cluster. This field follows standard label selector - semantics; if present but empty, it selects all Nodes. - - - Support: Extended - - - - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: |- - Pods defines a way to select a set of pods in - a set of namespaces. Note that host-networked pods - are not included in this type of peer. - - - Support: Core - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - maxItems: 100 - minItems: 1 - type: array - required: - - action - - to - type: object - x-kubernetes-validations: - - message: - networks/nodes peer cannot be set with namedPorts since - there are no namedPorts for networks/nodes - rule: - "!(self.to.exists(peer, has(peer.networks) || has(peer.nodes)) - && has(self.ports) && self.ports.exists(port, has(port.namedPort)))" - maxItems: 100 - type: array - ingress: - description: |- - Ingress is the list of Ingress rules to be applied to the selected pods. - A total of 100 rules will be allowed in each ANP instance. - The relative precedence of ingress rules within a single ANP object (all of - which share the priority) will be determined by the order in which the rule - is written. Thus, a rule that appears at the top of the ingress rules - would take the highest precedence. - ANPs with no ingress rules do not affect ingress traffic. - - - Support: Core - items: - description: |- - AdminNetworkPolicyIngressRule describes an action to take on a particular - set of traffic destined for pods selected by an AdminNetworkPolicy's - Subject field. - properties: - action: - description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic (even if it would otherwise have been denied by NetworkPolicy) - Deny: denies the selected traffic - Pass: instructs the selected traffic to skip any remaining ANP rules, and - then pass execution to any NetworkPolicies that select the pod. - If the pod is not selected by any NetworkPolicies then execution - is passed to any BaselineAdminNetworkPolicies that select the pod. - - - Support: Core - enum: - - Allow - - Deny - - Pass - type: string - from: - description: |- - From is the list of sources whose traffic this rule applies to. - If any AdminNetworkPolicyIngressPeer matches the source of incoming - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core - items: - description: |- - AdminNetworkPolicyIngressPeer defines an in-cluster peer to allow traffic from. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: |- - Namespaces defines a way to select all pods within a set of Namespaces. - Note that host-networked pods are not included in this type of peer. - - - Support: Core - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: |- - Pods defines a way to select a set of pods in - a set of namespaces. Note that host-networked pods - are not included in this type of peer. - - - Support: Core - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - maxItems: 100 - minItems: 1 - type: array - name: - description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - AdminNetworkPolicies. - - - Support: Core - maxLength: 100 - type: string - ports: - description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of ports which should be matched on - the pods selected for this policy i.e the subject of the policy. - So it matches on the destination port for the ingress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core - items: - description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). - Exactly one field must be set. - maxProperties: 1 - minProperties: 1 - properties: - namedPort: - description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - - type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core - properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol - type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core - properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start - type: object - type: object - maxItems: 100 - type: array - required: - - action - - from - type: object - maxItems: 100 - type: array - priority: - description: |- - Priority is a value from 0 to 1000. Rules with lower priority values have - higher precedence, and are checked before rules with higher priority values. - All AdminNetworkPolicy rules have higher precedence than NetworkPolicy or - BaselineAdminNetworkPolicy rules - The behavior is undefined if two ANP objects have same priority. - - - Support: Core - format: int32 - maximum: 1000 - minimum: 0 - type: integer - subject: - description: |- - Subject defines the pods to which this AdminNetworkPolicy applies. - Note that host-networked pods are not included in subject selection. - - - Support: Core - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: Namespaces is used to select pods via namespace selectors. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: - Pods is used to select pods via namespace AND pod - selectors. - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - required: - - priority - - subject - type: object - status: - description: Status is the status to be reported by the implementation. - properties: - conditions: - items: - description: - "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - required: - - conditions - type: object - required: - - metadata - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: null - storedVersions: null ---- -# Source: calico/templates/kdd-crds.yaml -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/30 - policy.networking.k8s.io/bundle-version: v0.1.1 - policy.networking.k8s.io/channel: experimental - creationTimestamp: null - name: baselineadminnetworkpolicies.policy.networking.k8s.io -spec: - group: policy.networking.k8s.io - names: - kind: BaselineAdminNetworkPolicy - listKind: BaselineAdminNetworkPolicyList - plural: baselineadminnetworkpolicies - shortNames: - - banp - singular: baselineadminnetworkpolicy - scope: Cluster - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: |- - BaselineAdminNetworkPolicy is a cluster level resource that is part of the - AdminNetworkPolicy API. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: Specification of the desired behavior of BaselineAdminNetworkPolicy. - properties: - egress: - description: |- - Egress is the list of Egress rules to be applied to the selected pods if - they are not matched by any AdminNetworkPolicy or NetworkPolicy rules. - A total of 100 Egress rules will be allowed in each BANP instance. - The relative precedence of egress rules within a single BANP object - will be determined by the order in which the rule is written. - Thus, a rule that appears at the top of the egress rules - would take the highest precedence. - BANPs with no egress rules do not affect egress traffic. - - - Support: Core - items: - description: |- - BaselineAdminNetworkPolicyEgressRule describes an action to take on a particular - set of traffic originating from pods selected by a BaselineAdminNetworkPolicy's - Subject field. - - properties: - action: - description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic - Deny: denies the selected traffic - - - Support: Core - enum: - - Allow - - Deny - type: string - name: - description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - BaselineAdminNetworkPolicies. - - - Support: Core - maxLength: 100 - type: string - ports: - description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of destination ports for the outgoing egress traffic. - If Ports is not set then the rule does not filter traffic via port. - items: - description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). - Exactly one field must be set. - maxProperties: 1 - minProperties: 1 - properties: - namedPort: - description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - - type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core - properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol - type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core - properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start - type: object - type: object - maxItems: 100 - type: array - to: - description: |- - To is the list of destinations whose traffic this rule applies to. - If any AdminNetworkPolicyEgressPeer matches the destination of outgoing - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core - items: - description: |- - AdminNetworkPolicyEgressPeer defines a peer to allow traffic to. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: |- - Namespaces defines a way to select all pods within a set of Namespaces. - Note that host-networked pods are not included in this type of peer. - - - Support: Core - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - networks: - description: |- - Networks defines a way to select peers via CIDR blocks. - This is intended for representing entities that live outside the cluster, - which can't be selected by pods, namespaces and nodes peers, but note - that cluster-internal traffic will be checked against the rule as - well. So if you Allow or Deny traffic to `"0.0.0.0/0"`, that will allow - or deny all IPv4 pod-to-pod traffic as well. If you don't want that, - add a rule that Passes all pod traffic before the Networks rule. - - - Each item in Networks should be provided in the CIDR format and should be - IPv4 or IPv6, for example "10.0.0.0/8" or "fd00::/8". - - - Networks can have upto 25 CIDRs specified. - - - Support: Extended - - - - items: - description: |- - CIDR is an IP address range in CIDR notation (for example, "10.0.0.0/8" or "fd00::/8"). - This string must be validated by implementations using net.ParseCIDR - TODO: Introduce CEL CIDR validation regex isCIDR() in Kube 1.31 when it is available. - maxLength: 43 - type: string - x-kubernetes-validations: - - message: - CIDR must be either an IPv4 or IPv6 address. - IPv4 address embedded in IPv6 addresses are not - supported - rule: self.contains(':') != self.contains('.') - maxItems: 25 - minItems: 1 - type: array - x-kubernetes-list-type: set - nodes: - description: |- - Nodes defines a way to select a set of nodes in - the cluster. This field follows standard label selector - semantics; if present but empty, it selects all Nodes. - - - Support: Extended - - - - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: + pods: description: |- Pods defines a way to select a set of pods in a set of namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -5923,11 +5316,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -5940,8 +5335,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -5972,11 +5367,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -5988,73 +5385,80 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object - maxItems: 100 + maxItems: 25 minItems: 1 type: array required: - action - to type: object - x-kubernetes-validations: - - message: - networks/nodes peer cannot be set with namedPorts since - there are no namedPorts for networks/nodes - rule: - "!(self.to.exists(peer, has(peer.networks) || has(peer.nodes)) - && has(self.ports) && self.ports.exists(port, has(port.namedPort)))" - maxItems: 100 + maxItems: 25 type: array ingress: description: |- - Ingress is the list of Ingress rules to be applied to the selected pods - if they are not matched by any AdminNetworkPolicy or NetworkPolicy rules. - A total of 100 Ingress rules will be allowed in each BANP instance. - The relative precedence of ingress rules within a single BANP object - will be determined by the order in which the rule is written. - Thus, a rule that appears at the top of the ingress rules - would take the highest precedence. - BANPs with no ingress rules do not affect ingress traffic. + Ingress is the list of Ingress rules to be applied to the selected pods. + A maximum of 25 rules is allowed in this block. - Support: Core + The relative precedence of ingress rules within a single CNP object + (all of which share the priority) will be determined by the order + in which the rule is written. + Thus, a rule that appears at the top of the ingress rules + would take the highest precedence. + CNPs with no ingress rules do not affect ingress traffic. items: description: |- - BaselineAdminNetworkPolicyIngressRule describes an action to take on a particular - set of traffic destined for pods selected by a BaselineAdminNetworkPolicy's + ClusterNetworkPolicyIngressRule describes an action to take on a particular + set of traffic destined for pods selected by a ClusterNetworkPolicy's Subject field. properties: action: description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic - Deny: denies the selected traffic + Action specifies the effect this rule will have on matching + traffic. Currently the following actions are supported: + + - Accept: Accepts the selected traffic, allowing it into + the destination. No further ClusterNetworkPolicy or + NetworkPolicy rules will be processed. + Note: while Accept ensures traffic is accepted by + Kubernetes network policy, it is still possible that the + packet is blocked in other ways: custom nftable rules, + high-layers e.g. service mesh. - Support: Core + - Deny: Drops the selected traffic. No further + ClusterNetworkPolicy or NetworkPolicy rules will be + processed. + + - Pass: Skips all further ClusterNetworkPolicy rules in the + current tier for the selected traffic, and passes + evaluation to the next tier. enum: - - Allow + - Accept - Deny + - Pass type: string from: description: |- From is the list of sources whose traffic this rule applies to. - If any AdminNetworkPolicyIngressPeer matches the source of incoming + If any element matches the source of incoming traffic then the specified action is applied. This field must be defined and contain at least one item. - - - Support: Core items: description: |- - AdminNetworkPolicyIngressPeer defines an in-cluster peer to allow traffic from. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. + ClusterNetworkPolicyIngressPeer defines a peer to allow traffic from. + + Exactly one of the fields must be set for a given peer and this is enforced + by the validation rules on the CRD. If an implementation sees no fields are + set then it can infer that the deployed CRD is of an incompatible version + with an unknown field. In that case it should fail closed. + + For "Accept" rules, "fail closed" means: "treat the rule as matching no + traffic". For "Deny" and "Pass" rules, "fail closed" means: "treat the rule + as a 'Deny all' rule". maxProperties: 1 minProperties: 1 properties: @@ -6062,9 +5466,6 @@ spec: description: |- Namespaces defines a way to select all pods within a set of Namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: matchExpressions: description: @@ -6094,11 +5495,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6114,14 +5517,11 @@ spec: Pods defines a way to select a set of pods in a set of namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -6152,11 +5552,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6169,8 +5571,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -6201,11 +5603,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6217,140 +5621,206 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object - maxItems: 100 + maxItems: 25 minItems: 1 type: array name: description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - BaselineAdminNetworkPolicies. - - - Support: Core + Name is an identifier for this rule, that may be no more than + 100 characters in length. This field should be used by the implementation + to help improve observability, readability and error-reporting + for any applied policies. maxLength: 100 type: string - ports: + protocols: description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of ports which should be matched on - the pods selected for this policy i.e the subject of the policy. - So it matches on the destination port for the ingress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core + Protocols allows for more fine-grain matching of traffic on + protocol-specific attributes such as the port. If + unspecified, protocol-specific attributes will not be used + to match traffic. items: description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). + ClusterNetworkPolicyProtocol describes additional protocol-specific match rules. Exactly one field must be set. maxProperties: 1 minProperties: 1 properties: - namedPort: + destinationNamedPort: description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - + DestinationNamedPort selects a destination port on a pod based on the + ContainerPort name. You can't use this in a rule that targets resources + without named ports (e.g. Nodes or Networks). type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core + sctp: + description: SCTP specific protocol matches. + minProperties: 1 properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core + tcp: + description: TCP specific protocol matches. + minProperties: 1 properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + udp: + description: UDP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object type: object - maxItems: 100 + maxItems: 25 + minItems: 1 type: array required: - action - from type: object - maxItems: 100 + maxItems: 25 type: array - subject: + priority: description: |- - Subject defines the pods to which this BaselineAdminNetworkPolicy applies. - Note that host-networked pods are not included in subject selection. - - - Support: Core + Priority is a value from 0 to 1000 indicating the precedence of + the policy within its tier. Policies with lower priority values have + higher precedence, and are checked before policies with higher priority + values in the same tier. All Admin tier rules have higher precedence than + NetworkPolicy or Baseline tier rules. + If two (or more) policies in the same tier with the same priority + could match a connection, then the implementation can apply any of the + matching policies to the connection, and there is no way for the user to + reliably determine which one it will choose. Administrators must be + careful about assigning the priorities for policies with rules that will + match many connections, and ensure that policies have unique priority + values in cases where ambiguity would be unacceptable. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + subject: + description: + Subject defines the pods to which this ClusterNetworkPolicy + applies. maxProperties: 1 minProperties: 1 properties: @@ -6385,11 +5855,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6407,8 +5879,8 @@ spec: properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -6438,11 +5910,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6455,8 +5929,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -6486,11 +5960,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6502,12 +5978,48 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object + tier: + description: |- + Tier is used as the top-level grouping for network policy prioritization. + + Policy tiers are evaluated in the following order: + * Admin tier + * NetworkPolicy tier + * Baseline tier + + ClusterNetworkPolicy can use 2 of these tiers: Admin and Baseline. + + The Admin tier takes precedence over all other policies. Policies + defined in this tier are used to set cluster-wide security rules + that cannot be overridden in the other tiers. If Admin tier has + made a final decision (Accept or Deny) on a connection, then no + further evaluation is done. + + NetworkPolicy tier is the tier for the namespaced v1.NetworkPolicy. + These policies are intended for the application developer to describe + the security policy associated with their deployments inside their + namespace. v1.NetworkPolicy always makes a final decision for selected + pods. Further evaluation only happens for Pods not selected by a + v1.NetworkPolicy. + + Baseline tier is a cluster-wide policy that can be overridden by the + v1.NetworkPolicy. If Baseline tier has made a final decision (Accept or + Deny) on a connection, then no further evaluation is done. + + If a given connection wasn't allowed or denied by any of the tiers, + the default kubernetes policy is applied, which says that + all pods can communicate with each other. + enum: + - Admin + - Baseline + type: string required: + - priority - subject + - tier type: object status: description: Status is the status to be reported by the implementation. @@ -6515,16 +6027,8 @@ spec: conditions: items: description: - "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -6565,12 +6069,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string @@ -6592,11 +6091,6 @@ spec: - metadata - spec type: object - x-kubernetes-validations: - - message: - Only one baseline admin network policy with metadata.name="default" - can be created in the cluster - rule: self.metadata.name == 'default' served: true storage: true subresources: @@ -6712,6 +6206,34 @@ rules: - update # watch for changes - watch + - apiGroups: ["crd.projectcalico.org"] + resources: + - kubecontrollersconfigurations/status + verbs: + - get + - update + # Needed for policy name migrator. + - apiGroups: ["crd.projectcalico.org"] + resources: + - networkpolicies + - stagednetworkpolicies + - globalnetworkpolicies + - stagedglobalnetworkpolicies + verbs: + - watch + - list + - get + - create + - update + - delete + # Needed for policy name migrator to check calico/node daemonset status. + - apiGroups: ["apps"] + resources: + - daemonsets + verbs: + - get + resourceNames: + - calico-node --- # Source: calico/templates/calico-node-rbac.yaml # Include a clusterrole for the calico-node DaemonSet, @@ -6776,11 +6298,10 @@ rules: verbs: - watch - list - # Watch for changes to Kubernetes (Baseline)AdminNetworkPolicies. + # Watch for changes to Kubernetes ClusterNetworkPolicies. - apiGroups: ["policy.networking.k8s.io"] resources: - - adminnetworkpolicies - - baselineadminnetworkpolicies + - clusternetworkpolicies verbs: - watch - list @@ -6898,6 +6419,14 @@ rules: - daemonsets verbs: - get + # For monitoring KubeVirt live migration. + - apiGroups: ["kubevirt.io"] + resources: + - virtualmachineinstancemigrations + verbs: + - get + - list + - watch --- # Source: calico/templates/calico-node-rbac.yaml # CNI cluster role diff --git a/manifests/calicoctl-etcd.yaml b/manifests/calicoctl-etcd.yaml index 1ccfad79691..b6489361549 100644 --- a/manifests/calicoctl-etcd.yaml +++ b/manifests/calicoctl-etcd.yaml @@ -14,7 +14,7 @@ spec: hostNetwork: true containers: - name: calicoctl - image: calico/ctl:master + image: quay.io/calico/ctl:master command: - calicoctl args: diff --git a/manifests/calicoctl.yaml b/manifests/calicoctl.yaml index e108f7df70f..cb273ae9acd 100644 --- a/manifests/calicoctl.yaml +++ b/manifests/calicoctl.yaml @@ -22,7 +22,7 @@ spec: serviceAccountName: calicoctl containers: - name: calicoctl - image: calico/ctl:master + image: quay.io/calico/ctl:master command: - calicoctl args: diff --git a/manifests/canal-etcd.yaml b/manifests/canal-etcd.yaml index e72a90690ab..e472bc1d589 100644 --- a/manifests/canal-etcd.yaml +++ b/manifests/canal-etcd.yaml @@ -161,6 +161,28 @@ rules: verbs: - watch - list + # Needed for policy name migrator. + - apiGroups: ["crd.projectcalico.org"] + resources: + - networkpolicies + - stagednetworkpolicies + - globalnetworkpolicies + - stagedglobalnetworkpolicies + verbs: + - watch + - list + - get + - create + - update + - delete + # Needed for policy name migrator to check calico/node daemonset status. + - apiGroups: ["apps"] + resources: + - daemonsets + verbs: + - get + resourceNames: + - calico-node --- # Source: calico/templates/calico-node-rbac.yaml # Include a clusterrole for the calico-node DaemonSet, diff --git a/manifests/canal.yaml b/manifests/canal.yaml index 2f5ff963f84..093e0353310 100644 --- a/manifests/canal.yaml +++ b/manifests/canal.yaml @@ -136,6 +136,9 @@ spec: format: int32 type: integer bindMode: + enum: + - None + - NodeIP type: string communities: items: @@ -146,11 +149,14 @@ spec: pattern: ^(\d+):(\d+)$|^(\d+):(\d+):(\d+)$ type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set ignoredInterfaces: items: type: string type: array + x-kubernetes-list-type: set listenPort: maximum: 65535 minimum: 1 @@ -160,6 +166,8 @@ spec: localWorkloadPeeringIPV6: type: string logSeverityScreen: + default: Info + pattern: ^(?i)(Trace|Debug|Info|Warning|Error|Fatal)?$ type: string nodeMeshMaxRestartTime: type: string @@ -185,27 +193,36 @@ spec: items: properties: cidr: + format: cidr type: string communities: items: type: string type: array type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceClusterIPs: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceExternalIPs: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceLoadBalancerAggregation: default: Enabled enum: @@ -216,9 +233,12 @@ spec: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -257,12 +277,21 @@ spec: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -277,22 +306,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array exportV6: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -307,22 +349,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array importV4: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -337,22 +392,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array importV6: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -367,11 +435,15 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array type: object type: object @@ -426,15 +498,10 @@ spec: maxRestartTime: type: string nextHopMode: - allOf: - - enum: - - Auto - - Self - - Keep - - enum: - - Auto - - Self - - Keep + enum: + - Auto + - Self + - Keep type: string node: type: string @@ -466,11 +533,18 @@ spec: reachableBy: type: string reversePeering: - enum: - - Auto - - Manual + allOf: + - enum: + - Auto + - Manual + - enum: + - Auto + - Manual type: string sourceAddress: + enum: + - UseNodeIP + - None type: string ttlSecurity: type: integer @@ -559,6 +633,10 @@ spec: properties: classes: items: + enum: + - Agent + - BGP + - Routes type: string type: array node: @@ -580,6 +658,9 @@ spec: routerID: type: string state: + enum: + - Ready + - NotReady type: string version: type: string @@ -593,6 +674,9 @@ spec: routerID: type: string state: + enum: + - Ready + - NotReady type: string version: type: string @@ -616,8 +700,20 @@ spec: since: type: string state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close type: string type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer type: string type: object type: array @@ -629,8 +725,20 @@ spec: since: type: string state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close type: string type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer type: string type: object type: array @@ -660,9 +768,18 @@ spec: peerIP: type: string sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer type: string type: object type: + enum: + - FIB + - RIB type: string type: object type: array @@ -680,9 +797,18 @@ spec: peerIP: type: string sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer type: string type: object type: + enum: + - FIB + - RIB type: string type: object type: array @@ -1036,15 +1162,9 @@ spec: if it detects the current value is 2 (strict mode that hurts performance). When set to "Strict", Felix will not modify the JIT hardening setting. [Default: Auto] type: string - bpfKubeProxyEndpointSlicesEnabled: + bpfKubeProxyHealthzPort: description: |- - BPFKubeProxyEndpointSlicesEnabled is deprecated and has no effect. BPF - kube-proxy always accepts endpoint slices. This option will be removed in - the next release. - type: boolean - bpfKubeProxyHealtzPort: - description: |- - BPFKubeProxyHealtzPort, in BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. + BPFKubeProxyHealthzPort, in BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. The health check server is used by external load balancers to determine if this node should receive traffic. [Default: 10256] type: integer bpfKubeProxyIptablesCleanupEnabled: @@ -1085,6 +1205,23 @@ spec: [Default: Off]. pattern: ^(?i)(Off|Info|Debug)?$ type: string + bpfMaglevMaxEndpointsPerService: + description: |- + BPFMaglevMaxEndpointsPerService is the maximum number of endpoints + expected to be part of a single Maglev-enabled service. + + Influences the size of the per-service Maglev lookup-tables generated by Felix + and thus the amount of memory reserved. + + [Default: 100] + type: integer + bpfMaglevMaxServices: + description: |- + BPFMaglevMaxServices is the maximum number of expected Maglev-enabled + services that Felix will allocate lookup-tables for. + + [Default: 100] + type: integer bpfMapSizeConntrack: description: |- BPFMapSizeConntrack sets the size for the conntrack map. This map must be large enough to hold @@ -1173,17 +1310,14 @@ spec: type: string bpfRedirectToPeer: description: |- - BPFRedirectToPeer controls which whether it is allowed to forward straight to the - peer side of the workload devices. It is allowed for any host L2 devices by default - (L2Only), but it breaks TCP dump on the host side of workload device as it bypasses - it on ingress. Value of Enabled also allows redirection from L3 host devices like - IPIP tunnel or Wireguard directly to the peer side of the workload's device. This - makes redirection faster, however, it breaks tools like tcpdump on the peer side. - Use Enabled with caution. [Default: L2Only] + BPFRedirectToPeer controls whether traffic may be forwarded directly to the peer side of a workload’s device. + Note that the legacy "L2Only" option is now deprecated and if set it is treated like "Enabled. + Setting this option to "Enabled" allows direct redirection (including from L3 host devices such as IPIP tunnels or WireGuard), + which can improve redirection performance but causes the redirected packets to bypass the host‑side ingress path. + As a result, packet‑capture tools on the host side of the workload device (for example, tcpdump) will not see that traffic. [Default: Enabled] enum: - Enabled - Disabled - - L2Only type: string cgroupV2Path: description: @@ -1534,6 +1668,10 @@ spec: Warning: changing this on a running system can leave "orphaned" rules in the "other" backend. These should be cleaned up to avoid confusing interactions. + enum: + - Legacy + - NFT + - Auto pattern: ^(?i)(Auto|Legacy|NFT)?$ type: string iptablesFilterAllowAction: @@ -1549,26 +1687,10 @@ spec: with an iptables "DROP" action. If you want to use "REJECT" action instead you can configure it in here. pattern: ^(?i)(Drop|Reject)?$ type: string - iptablesLockFilePath: - description: |- - IptablesLockFilePath is the location of the iptables lock file. You may need to change this - if the lock file is not in its standard location (for example if you have mapped it into Felix's - container at a different path). [Default: /run/xtables.lock] - type: string iptablesLockProbeInterval: description: |- - IptablesLockProbeInterval when IptablesLockTimeout is enabled: the time that Felix will wait between - attempts to acquire the iptables lock if it is not available. Lower values make Felix more - responsive when the lock is contended, but use more CPU. [Default: 50ms] - pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ - type: string - iptablesLockTimeout: - description: |- - IptablesLockTimeout is the time that Felix itself will wait for the iptables lock (rather than delegating the - lock handling to the `iptables` command). - - Deprecated: `iptables-restore` v1.8+ always takes the lock, so enabling this feature results in deadlock. - [Default: 0s disabled] + IptablesLockProbeInterval configures the interval between attempts to claim + the xtables lock. Shorter intervals are more responsive but use more CPU. [Default: 50ms] pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ type: string iptablesMangleAllowAction: @@ -1628,6 +1750,19 @@ spec: pattern: ^.* x-kubernetes-int-or-string: true type: array + logActionRateLimit: + description: |- + LogActionRateLimit sets the rate of hitting a Log action. The value must be in the format "N/unit", + where N is a number and unit is one of: second, minute, hour, or day. For example: "10/second" or "100/hour". + pattern: ^[1-9]\d{0,3}/(?:second|minute|hour|day)$ + type: string + logActionRateLimitBurst: + description: + LogActionRateLimitBurst sets the rate limit burst of + hitting a Log action when LogActionRateLimit is enabled. + maximum: 9999 + minimum: 0 + type: integer logDebugFilenameRegex: description: |- LogDebugFilenameRegex controls which source code files have their Debug log output included in the logs. @@ -1640,9 +1775,17 @@ spec: none to disable file logging. [Default: /var/log/calico/felix.log]" type: string logPrefix: - description: - "LogPrefix is the log prefix that Felix uses when rendering - LOG rules. [Default: calico-packet]" + description: |- + LogPrefix is the log prefix that Felix uses when rendering LOG rules. It is possible to use the following specifiers + to include extra information in the log prefix. + - %t: Tier name. + - %k: Kind (short names). + - %n: Policy or profile name. + - %p: Policy or profile name (namespace/name for namespaced kinds or just name for non namespaced kinds). + Calico includes ": " characters at the end of the generated log prefix. + Note that iptables shows up to 29 characters for the log prefix and nftables up to 127 characters. Extra characters are truncated. + [Default: calico-packet] + pattern: "^([a-zA-Z0-9%: /_-])*$" type: string logSeverityFile: description: @@ -1746,9 +1889,10 @@ spec: format: int32 type: integer nftablesMode: + default: Auto description: "NFTablesMode configures nftables support in Felix. [Default: - Disabled]" + Auto]" enum: - Disabled - Enabled @@ -1784,6 +1928,21 @@ spec: PrometheusGoMetricsEnabled disables Go runtime metrics collection, which the Prometheus client does by default, when set to false. This reduces the number of metrics reported, reducing Prometheus load. [Default: true] type: boolean + prometheusMetricsCAFile: + description: |- + PrometheusMetricsCAFile defines the absolute path to the TLS CA certificate file used for securing the /metrics endpoint. + This certificate must be valid and accessible by the calico-node process. + type: string + prometheusMetricsCertFile: + description: |- + PrometheusMetricsCertFile defines the absolute path to the TLS certificate file used for securing the /metrics endpoint. + This certificate must be valid and accessible by the calico-node process. + type: string + prometheusMetricsClientAuth: + description: |- + PrometheusMetricsClientAuth specifies the client authentication type for the /metrics endpoint. + This determines how the server validates client certificates. Default is "RequireAndVerifyClientCert". + type: string prometheusMetricsEnabled: description: "PrometheusMetricsEnabled enables the Prometheus metrics @@ -1794,6 +1953,11 @@ spec: "PrometheusMetricsHost is the host that the Prometheus metrics server should bind to. [Default: empty]" type: string + prometheusMetricsKeyFile: + description: |- + PrometheusMetricsKeyFile defines the absolute path to the private key file corresponding to the TLS certificate + used for securing the /metrics endpoint. The private key must be valid and accessible by the calico-node process. + type: string prometheusMetricsPort: description: "PrometheusMetricsPort is the TCP port that the Prometheus @@ -2077,6 +2241,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -2086,6 +2255,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2116,6 +2286,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2146,11 +2317,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -2162,8 +2340,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -2186,6 +2368,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2216,6 +2399,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2235,6 +2419,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -2244,6 +2433,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2274,6 +2464,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2304,11 +2495,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -2320,8 +2518,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -2344,6 +2546,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2374,6 +2577,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2395,6 +2599,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array preDNAT: @@ -2404,11 +2610,18 @@ spec: serviceAccountSelector: type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2447,6 +2660,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2485,6 +2699,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set interfaceName: type: string node: @@ -2495,6 +2710,8 @@ spec: name: type: string port: + maximum: 65535 + minimum: 0 type: integer protocol: anyOf: @@ -2512,6 +2729,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2548,6 +2766,9 @@ spec: properties: affinity: type: string + affinityClaimTime: + format: date-time + type: string allocations: items: type: integer @@ -2558,6 +2779,10 @@ spec: attributes: items: properties: + alternate: + additionalProperties: + type: string + type: object handle_id: type: string secondary: @@ -2627,6 +2852,11 @@ spec: properties: autoAllocateBlocks: type: boolean + kubeVirtVMAddressPersistence: + enum: + - Enabled + - Disabled + type: string maxBlocksPerHost: maximum: 2147483647 minimum: 0 @@ -2717,39 +2947,47 @@ spec: properties: allowedUses: items: + enum: + - Workload + - Tunnel + - LoadBalancer type: string type: array + x-kubernetes-list-type: set assignmentMode: + default: Automatic enum: - Automatic - Manual type: string blockSize: + maximum: 128 + minimum: 0 type: integer cidr: + format: cidr type: string disableBGPExport: type: boolean disabled: type: boolean - ipip: - properties: - enabled: - type: boolean - mode: - type: string - type: object ipipMode: + enum: + - Never + - Always + - CrossSubnet type: string namespaceSelector: type: string - nat-outgoing: - type: boolean natOutgoing: type: boolean nodeSelector: type: string vxlanMode: + enum: + - Never + - Always + - CrossSubnet type: string required: - cidr @@ -2788,9 +3026,11 @@ spec: spec: properties: reservedCIDRs: + format: cidr items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2830,6 +3070,10 @@ spec: loadBalancer: properties: assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly type: string type: object namespace: @@ -2842,6 +3086,9 @@ spec: hostEndpoint: properties: autoCreate: + enum: + - Enabled + - Disabled type: string createDefaultHostEndpoint: type: string @@ -2855,7 +3102,8 @@ spec: items: type: string type: array - interfaceSelector: + x-kubernetes-list-type: set + interfacePattern: type: string labels: additionalProperties: @@ -2871,6 +3119,9 @@ spec: reconcilerPeriod: type: string syncLabels: + enum: + - Enabled + - Disabled type: string type: object policy: @@ -2878,6 +3129,15 @@ spec: reconcilerPeriod: type: string type: object + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object serviceAccount: properties: reconcilerPeriod: @@ -2891,14 +3151,30 @@ spec: type: object debugProfilePort: format: int32 + maximum: 65535 + minimum: 0 type: integer etcdV3CompactionPeriod: type: string healthChecks: + default: Enabled + enum: + - Enabled + - Disabled type: string logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic type: string prometheusMetricsPort: + maximum: 65535 + minimum: 0 type: integer required: - controllers @@ -2916,6 +3192,10 @@ spec: loadBalancer: properties: assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly type: string type: object namespace: @@ -2928,6 +3208,9 @@ spec: hostEndpoint: properties: autoCreate: + enum: + - Enabled + - Disabled type: string createDefaultHostEndpoint: type: string @@ -2941,7 +3224,8 @@ spec: items: type: string type: array - interfaceSelector: + x-kubernetes-list-type: set + interfacePattern: type: string labels: additionalProperties: @@ -2957,6 +3241,9 @@ spec: reconcilerPeriod: type: string syncLabels: + enum: + - Enabled + - Disabled type: string type: object policy: @@ -2964,6 +3251,15 @@ spec: reconcilerPeriod: type: string type: object + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object serviceAccount: properties: reconcilerPeriod: @@ -2977,14 +3273,30 @@ spec: type: object debugProfilePort: format: int32 + maximum: 65535 + minimum: 0 type: integer etcdV3CompactionPeriod: type: string healthChecks: + default: Enabled + enum: + - Enabled + - Disabled type: string logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic type: string prometheusMetricsPort: + maximum: 65535 + minimum: 0 type: integer required: - controllers @@ -2993,6 +3305,8 @@ spec: type: object served: true storage: true + subresources: + status: {} --- # Source: calico/templates/kdd-crds.yaml apiVersion: apiextensions.k8s.io/v1 @@ -3027,6 +3341,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3036,6 +3355,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3066,6 +3386,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3096,11 +3417,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3112,8 +3440,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3136,6 +3468,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3166,6 +3499,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3185,6 +3519,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3194,6 +3533,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3224,6 +3564,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3254,11 +3595,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3270,8 +3618,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3294,6 +3646,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3324,6 +3677,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3343,6 +3697,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array selector: @@ -3350,11 +3706,18 @@ spec: serviceAccountSelector: type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -3393,6 +3756,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -3435,6 +3799,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3444,6 +3813,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3474,6 +3844,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3504,11 +3875,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3520,8 +3898,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3544,6 +3926,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3574,6 +3957,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3593,6 +3977,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3602,6 +3991,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3632,6 +4022,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3662,11 +4053,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3678,8 +4076,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3702,6 +4104,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3732,6 +4135,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3753,6 +4157,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array preDNAT: @@ -3762,13 +4168,25 @@ spec: serviceAccountSelector: type: string stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -4014,8 +4432,16 @@ spec: policyTypes: items: type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string type: object type: object @@ -4055,6 +4481,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -4064,6 +4495,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4094,6 +4526,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4124,11 +4557,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -4140,8 +4580,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -4164,6 +4608,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4194,6 +4639,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4213,6 +4659,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -4222,6 +4673,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4252,6 +4704,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4282,11 +4735,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -4298,8 +4758,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -4322,6 +4786,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4352,6 +4817,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4371,6 +4837,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array selector: @@ -4378,13 +4846,25 @@ spec: serviceAccountSelector: type: string stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -4420,14 +4900,36 @@ spec: spec: properties: defaultAction: - enum: - - Pass - - Deny + allOf: + - enum: + - Allow + - Deny + - Log + - Pass + - enum: + - Pass + - Deny type: string order: type: number type: object + required: + - metadata + - spec type: object + x-kubernetes-validations: + - message: The 'kube-admin' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-admin' ? self.spec.defaultAction == + 'Pass' : true" + - message: The 'kube-baseline' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-baseline' ? self.spec.defaultAction + == 'Pass' : true" + - message: The 'default' tier must have default action 'Deny' + rule: + "self.metadata.name == 'default' ? self.spec.defaultAction == 'Deny' + : true" served: true storage: true --- @@ -4436,35 +4938,35 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/30 - policy.networking.k8s.io/bundle-version: v0.1.1 - policy.networking.k8s.io/channel: experimental - creationTimestamp: null - name: adminnetworkpolicies.policy.networking.k8s.io + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/300 + policy.networking.k8s.io/bundle-version: v0.1.7 + policy.networking.k8s.io/channel: standard + name: clusternetworkpolicies.policy.networking.k8s.io spec: group: policy.networking.k8s.io names: - kind: AdminNetworkPolicy - listKind: AdminNetworkPolicyList - plural: adminnetworkpolicies + kind: ClusterNetworkPolicy + listKind: ClusterNetworkPolicyList + plural: clusternetworkpolicies shortNames: - - anp - singular: adminnetworkpolicy + - cnp + singular: clusternetworkpolicy scope: Cluster versions: - additionalPrinterColumns: + - jsonPath: .spec.tier + name: Tier + type: string - jsonPath: .spec.priority name: Priority type: string - jsonPath: .metadata.creationTimestamp name: Age type: date - name: v1alpha1 + name: v1alpha2 schema: openAPIV3Schema: - description: |- - AdminNetworkPolicy is a cluster level resource that is part of the - AdminNetworkPolicy API. + description: ClusterNetworkPolicy is a cluster-wide network policy resource. properties: apiVersion: description: |- @@ -4484,172 +4986,233 @@ spec: metadata: type: object spec: - description: Specification of the desired behavior of AdminNetworkPolicy. + description: Spec defines the desired behavior of ClusterNetworkPolicy. properties: egress: description: |- Egress is the list of Egress rules to be applied to the selected pods. - A total of 100 rules will be allowed in each ANP instance. - The relative precedence of egress rules within a single ANP object (all of - which share the priority) will be determined by the order in which the rule - is written. Thus, a rule that appears at the top of the egress rules - would take the highest precedence. - ANPs with no egress rules do not affect egress traffic. + A maximum of 25 rules is allowed in this block. - Support: Core + The relative precedence of egress rules within a single CNP object + (all of which share the priority) will be determined by the order + in which the rule is written. + Thus, a rule that appears at the top of the egress rules + would take the highest precedence. + CNPs with no egress rules do not affect egress traffic. items: description: |- - AdminNetworkPolicyEgressRule describes an action to take on a particular - set of traffic originating from pods selected by a AdminNetworkPolicy's + ClusterNetworkPolicyEgressRule describes an action to take on a particular + set of traffic originating from pods selected by a ClusterNetworkPolicy's Subject field. + properties: action: description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic (even if it would otherwise have been denied by NetworkPolicy) - Deny: denies the selected traffic - Pass: instructs the selected traffic to skip any remaining ANP rules, and - then pass execution to any NetworkPolicies that select the pod. - If the pod is not selected by any NetworkPolicies then execution - is passed to any BaselineAdminNetworkPolicies that select the pod. + Action specifies the effect this rule will have on matching + traffic. Currently the following actions are supported: + - Accept: Accepts the selected traffic, allowing it to + egress. No further ClusterNetworkPolicy or NetworkPolicy + rules will be processed. - Support: Core + - Deny: Drops the selected traffic. No further + ClusterNetworkPolicy or NetworkPolicy rules will be + processed. + + - Pass: Skips all further ClusterNetworkPolicy rules in the + current tier for the selected traffic, and passes + evaluation to the next tier. enum: - - Allow + - Accept - Deny - Pass type: string name: description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - AdminNetworkPolicies. - - - Support: Core + Name is an identifier for this rule, that may be no more than + 100 characters in length. This field should be used by the implementation + to help improve observability, readability and error-reporting + for any applied policies. maxLength: 100 type: string - ports: + protocols: description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of destination ports for the outgoing egress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core + Protocols allows for more fine-grain matching of traffic on + protocol-specific attributes such as the port. If + unspecified, protocol-specific attributes will not be used + to match traffic. items: description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). + ClusterNetworkPolicyProtocol describes additional protocol-specific match rules. Exactly one field must be set. maxProperties: 1 minProperties: 1 properties: - namedPort: + destinationNamedPort: description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - + DestinationNamedPort selects a destination port on a pod based on the + ContainerPort name. You can't use this in a rule that targets resources + without named ports (e.g. Nodes or Networks). type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core + sctp: + description: SCTP specific protocol matches. + minProperties: 1 properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core + tcp: + description: TCP specific protocol matches. + minProperties: 1 properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + udp: + description: UDP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object type: object - maxItems: 100 + maxItems: 25 + minItems: 1 type: array to: description: |- - To is the List of destinations whose traffic this rule applies to. - If any AdminNetworkPolicyEgressPeer matches the destination of outgoing - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core + To is the list of destinations whose traffic this rule applies to. If any + element matches the destination of outgoing traffic then the specified + action is applied. This field must be defined and contain at least one + item. items: description: |- - AdminNetworkPolicyEgressPeer defines a peer to allow traffic to. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. + ClusterNetworkPolicyEgressPeer defines a peer to allow traffic to. + + Exactly one of the fields must be set for a given peer and this is enforced + by the validation rules on the CRD. If an implementation sees no fields are + set then it can infer that the deployed CRD is of an incompatible version + with an unknown field. In that case it should fail closed. + + For "Accept" rules, "fail closed" means: "treat the rule as matching no + traffic". For "Deny" and "Pass" rules, "fail closed" means: "treat the rule + as a 'Deny all' rule". maxProperties: 1 minProperties: 1 properties: @@ -4657,9 +5220,6 @@ spec: description: |- Namespaces defines a way to select all pods within a set of Namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: matchExpressions: description: @@ -4689,11 +5249,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -4710,1206 +5272,37 @@ spec: This is intended for representing entities that live outside the cluster, which can't be selected by pods, namespaces and nodes peers, but note that cluster-internal traffic will be checked against the rule as - well. So if you Allow or Deny traffic to `"0.0.0.0/0"`, that will allow + well. So if you Accept or Deny traffic to `"0.0.0.0/0"`, that will allow or deny all IPv4 pod-to-pod traffic as well. If you don't want that, add a rule that Passes all pod traffic before the Networks rule. - Each item in Networks should be provided in the CIDR format and should be IPv4 or IPv6, for example "10.0.0.0/8" or "fd00::/8". - - Networks can have upto 25 CIDRs specified. - - - Support: Extended - - - + Networks can have up to 25 CIDRs specified. items: description: |- - CIDR is an IP address range in CIDR notation (for example, "10.0.0.0/8" or "fd00::/8"). - This string must be validated by implementations using net.ParseCIDR - TODO: Introduce CEL CIDR validation regex isCIDR() in Kube 1.31 when it is available. + CIDR is an IP address range in CIDR notation + (for example, "10.0.0.0/8" or "fd00::/8"). maxLength: 43 type: string x-kubernetes-validations: - - message: - CIDR must be either an IPv4 or IPv6 address. - IPv4 address embedded in IPv6 addresses are not - supported - rule: self.contains(':') != self.contains('.') + - message: Invalid CIDR format provided + rule: isCIDR(self) maxItems: 25 minItems: 1 type: array x-kubernetes-list-type: set - nodes: - description: |- - Nodes defines a way to select a set of nodes in - the cluster. This field follows standard label selector - semantics; if present but empty, it selects all Nodes. - - - Support: Extended - - - - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: |- - Pods defines a way to select a set of pods in - a set of namespaces. Note that host-networked pods - are not included in this type of peer. - - - Support: Core - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - maxItems: 100 - minItems: 1 - type: array - required: - - action - - to - type: object - x-kubernetes-validations: - - message: - networks/nodes peer cannot be set with namedPorts since - there are no namedPorts for networks/nodes - rule: - "!(self.to.exists(peer, has(peer.networks) || has(peer.nodes)) - && has(self.ports) && self.ports.exists(port, has(port.namedPort)))" - maxItems: 100 - type: array - ingress: - description: |- - Ingress is the list of Ingress rules to be applied to the selected pods. - A total of 100 rules will be allowed in each ANP instance. - The relative precedence of ingress rules within a single ANP object (all of - which share the priority) will be determined by the order in which the rule - is written. Thus, a rule that appears at the top of the ingress rules - would take the highest precedence. - ANPs with no ingress rules do not affect ingress traffic. - - - Support: Core - items: - description: |- - AdminNetworkPolicyIngressRule describes an action to take on a particular - set of traffic destined for pods selected by an AdminNetworkPolicy's - Subject field. - properties: - action: - description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic (even if it would otherwise have been denied by NetworkPolicy) - Deny: denies the selected traffic - Pass: instructs the selected traffic to skip any remaining ANP rules, and - then pass execution to any NetworkPolicies that select the pod. - If the pod is not selected by any NetworkPolicies then execution - is passed to any BaselineAdminNetworkPolicies that select the pod. - - - Support: Core - enum: - - Allow - - Deny - - Pass - type: string - from: - description: |- - From is the list of sources whose traffic this rule applies to. - If any AdminNetworkPolicyIngressPeer matches the source of incoming - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core - items: - description: |- - AdminNetworkPolicyIngressPeer defines an in-cluster peer to allow traffic from. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: |- - Namespaces defines a way to select all pods within a set of Namespaces. - Note that host-networked pods are not included in this type of peer. - - - Support: Core - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: |- - Pods defines a way to select a set of pods in - a set of namespaces. Note that host-networked pods - are not included in this type of peer. - - - Support: Core - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - maxItems: 100 - minItems: 1 - type: array - name: - description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - AdminNetworkPolicies. - - - Support: Core - maxLength: 100 - type: string - ports: - description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of ports which should be matched on - the pods selected for this policy i.e the subject of the policy. - So it matches on the destination port for the ingress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core - items: - description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). - Exactly one field must be set. - maxProperties: 1 - minProperties: 1 - properties: - namedPort: - description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - - type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core - properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol - type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core - properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start - type: object - type: object - maxItems: 100 - type: array - required: - - action - - from - type: object - maxItems: 100 - type: array - priority: - description: |- - Priority is a value from 0 to 1000. Rules with lower priority values have - higher precedence, and are checked before rules with higher priority values. - All AdminNetworkPolicy rules have higher precedence than NetworkPolicy or - BaselineAdminNetworkPolicy rules - The behavior is undefined if two ANP objects have same priority. - - - Support: Core - format: int32 - maximum: 1000 - minimum: 0 - type: integer - subject: - description: |- - Subject defines the pods to which this AdminNetworkPolicy applies. - Note that host-networked pods are not included in subject selection. - - - Support: Core - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: Namespaces is used to select pods via namespace selectors. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: - Pods is used to select pods via namespace AND pod - selectors. - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - required: - - priority - - subject - type: object - status: - description: Status is the status to be reported by the implementation. - properties: - conditions: - items: - description: - "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - required: - - conditions - type: object - required: - - metadata - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: null - storedVersions: null ---- -# Source: calico/templates/kdd-crds.yaml -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/30 - policy.networking.k8s.io/bundle-version: v0.1.1 - policy.networking.k8s.io/channel: experimental - creationTimestamp: null - name: baselineadminnetworkpolicies.policy.networking.k8s.io -spec: - group: policy.networking.k8s.io - names: - kind: BaselineAdminNetworkPolicy - listKind: BaselineAdminNetworkPolicyList - plural: baselineadminnetworkpolicies - shortNames: - - banp - singular: baselineadminnetworkpolicy - scope: Cluster - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: |- - BaselineAdminNetworkPolicy is a cluster level resource that is part of the - AdminNetworkPolicy API. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: Specification of the desired behavior of BaselineAdminNetworkPolicy. - properties: - egress: - description: |- - Egress is the list of Egress rules to be applied to the selected pods if - they are not matched by any AdminNetworkPolicy or NetworkPolicy rules. - A total of 100 Egress rules will be allowed in each BANP instance. - The relative precedence of egress rules within a single BANP object - will be determined by the order in which the rule is written. - Thus, a rule that appears at the top of the egress rules - would take the highest precedence. - BANPs with no egress rules do not affect egress traffic. - - - Support: Core - items: - description: |- - BaselineAdminNetworkPolicyEgressRule describes an action to take on a particular - set of traffic originating from pods selected by a BaselineAdminNetworkPolicy's - Subject field. - - properties: - action: - description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic - Deny: denies the selected traffic - - - Support: Core - enum: - - Allow - - Deny - type: string - name: - description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - BaselineAdminNetworkPolicies. - - - Support: Core - maxLength: 100 - type: string - ports: - description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of destination ports for the outgoing egress traffic. - If Ports is not set then the rule does not filter traffic via port. - items: - description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). - Exactly one field must be set. - maxProperties: 1 - minProperties: 1 - properties: - namedPort: - description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - - type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core - properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol - type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core - properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start - type: object - type: object - maxItems: 100 - type: array - to: - description: |- - To is the list of destinations whose traffic this rule applies to. - If any AdminNetworkPolicyEgressPeer matches the destination of outgoing - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core - items: - description: |- - AdminNetworkPolicyEgressPeer defines a peer to allow traffic to. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: |- - Namespaces defines a way to select all pods within a set of Namespaces. - Note that host-networked pods are not included in this type of peer. - - - Support: Core - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - networks: - description: |- - Networks defines a way to select peers via CIDR blocks. - This is intended for representing entities that live outside the cluster, - which can't be selected by pods, namespaces and nodes peers, but note - that cluster-internal traffic will be checked against the rule as - well. So if you Allow or Deny traffic to `"0.0.0.0/0"`, that will allow - or deny all IPv4 pod-to-pod traffic as well. If you don't want that, - add a rule that Passes all pod traffic before the Networks rule. - - - Each item in Networks should be provided in the CIDR format and should be - IPv4 or IPv6, for example "10.0.0.0/8" or "fd00::/8". - - - Networks can have upto 25 CIDRs specified. - - - Support: Extended - - - - items: - description: |- - CIDR is an IP address range in CIDR notation (for example, "10.0.0.0/8" or "fd00::/8"). - This string must be validated by implementations using net.ParseCIDR - TODO: Introduce CEL CIDR validation regex isCIDR() in Kube 1.31 when it is available. - maxLength: 43 - type: string - x-kubernetes-validations: - - message: - CIDR must be either an IPv4 or IPv6 address. - IPv4 address embedded in IPv6 addresses are not - supported - rule: self.contains(':') != self.contains('.') - maxItems: 25 - minItems: 1 - type: array - x-kubernetes-list-type: set - nodes: - description: |- - Nodes defines a way to select a set of nodes in - the cluster. This field follows standard label selector - semantics; if present but empty, it selects all Nodes. - - - Support: Extended - - - - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: + pods: description: |- Pods defines a way to select a set of pods in a set of namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -5940,11 +5333,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -5957,8 +5352,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -5989,11 +5384,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6005,73 +5402,80 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object - maxItems: 100 + maxItems: 25 minItems: 1 type: array required: - action - to type: object - x-kubernetes-validations: - - message: - networks/nodes peer cannot be set with namedPorts since - there are no namedPorts for networks/nodes - rule: - "!(self.to.exists(peer, has(peer.networks) || has(peer.nodes)) - && has(self.ports) && self.ports.exists(port, has(port.namedPort)))" - maxItems: 100 + maxItems: 25 type: array ingress: description: |- - Ingress is the list of Ingress rules to be applied to the selected pods - if they are not matched by any AdminNetworkPolicy or NetworkPolicy rules. - A total of 100 Ingress rules will be allowed in each BANP instance. - The relative precedence of ingress rules within a single BANP object - will be determined by the order in which the rule is written. - Thus, a rule that appears at the top of the ingress rules - would take the highest precedence. - BANPs with no ingress rules do not affect ingress traffic. + Ingress is the list of Ingress rules to be applied to the selected pods. + A maximum of 25 rules is allowed in this block. - Support: Core + The relative precedence of ingress rules within a single CNP object + (all of which share the priority) will be determined by the order + in which the rule is written. + Thus, a rule that appears at the top of the ingress rules + would take the highest precedence. + CNPs with no ingress rules do not affect ingress traffic. items: description: |- - BaselineAdminNetworkPolicyIngressRule describes an action to take on a particular - set of traffic destined for pods selected by a BaselineAdminNetworkPolicy's + ClusterNetworkPolicyIngressRule describes an action to take on a particular + set of traffic destined for pods selected by a ClusterNetworkPolicy's Subject field. properties: action: description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic - Deny: denies the selected traffic + Action specifies the effect this rule will have on matching + traffic. Currently the following actions are supported: + + - Accept: Accepts the selected traffic, allowing it into + the destination. No further ClusterNetworkPolicy or + NetworkPolicy rules will be processed. + Note: while Accept ensures traffic is accepted by + Kubernetes network policy, it is still possible that the + packet is blocked in other ways: custom nftable rules, + high-layers e.g. service mesh. - Support: Core + - Deny: Drops the selected traffic. No further + ClusterNetworkPolicy or NetworkPolicy rules will be + processed. + + - Pass: Skips all further ClusterNetworkPolicy rules in the + current tier for the selected traffic, and passes + evaluation to the next tier. enum: - - Allow + - Accept - Deny + - Pass type: string from: description: |- From is the list of sources whose traffic this rule applies to. - If any AdminNetworkPolicyIngressPeer matches the source of incoming + If any element matches the source of incoming traffic then the specified action is applied. This field must be defined and contain at least one item. - - - Support: Core items: description: |- - AdminNetworkPolicyIngressPeer defines an in-cluster peer to allow traffic from. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. + ClusterNetworkPolicyIngressPeer defines a peer to allow traffic from. + + Exactly one of the fields must be set for a given peer and this is enforced + by the validation rules on the CRD. If an implementation sees no fields are + set then it can infer that the deployed CRD is of an incompatible version + with an unknown field. In that case it should fail closed. + + For "Accept" rules, "fail closed" means: "treat the rule as matching no + traffic". For "Deny" and "Pass" rules, "fail closed" means: "treat the rule + as a 'Deny all' rule". maxProperties: 1 minProperties: 1 properties: @@ -6079,9 +5483,6 @@ spec: description: |- Namespaces defines a way to select all pods within a set of Namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: matchExpressions: description: @@ -6111,11 +5512,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6131,14 +5534,11 @@ spec: Pods defines a way to select a set of pods in a set of namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -6169,11 +5569,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6186,8 +5588,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -6218,11 +5620,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6234,140 +5638,206 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object - maxItems: 100 + maxItems: 25 minItems: 1 type: array name: description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - BaselineAdminNetworkPolicies. - - - Support: Core + Name is an identifier for this rule, that may be no more than + 100 characters in length. This field should be used by the implementation + to help improve observability, readability and error-reporting + for any applied policies. maxLength: 100 type: string - ports: + protocols: description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of ports which should be matched on - the pods selected for this policy i.e the subject of the policy. - So it matches on the destination port for the ingress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core + Protocols allows for more fine-grain matching of traffic on + protocol-specific attributes such as the port. If + unspecified, protocol-specific attributes will not be used + to match traffic. items: description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). + ClusterNetworkPolicyProtocol describes additional protocol-specific match rules. Exactly one field must be set. maxProperties: 1 minProperties: 1 properties: - namedPort: + destinationNamedPort: description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - + DestinationNamedPort selects a destination port on a pod based on the + ContainerPort name. You can't use this in a rule that targets resources + without named ports (e.g. Nodes or Networks). type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core + sctp: + description: SCTP specific protocol matches. + minProperties: 1 properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core + tcp: + description: TCP specific protocol matches. + minProperties: 1 properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + udp: + description: UDP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object type: object - maxItems: 100 + maxItems: 25 + minItems: 1 type: array required: - action - from type: object - maxItems: 100 + maxItems: 25 type: array - subject: + priority: description: |- - Subject defines the pods to which this BaselineAdminNetworkPolicy applies. - Note that host-networked pods are not included in subject selection. - - - Support: Core + Priority is a value from 0 to 1000 indicating the precedence of + the policy within its tier. Policies with lower priority values have + higher precedence, and are checked before policies with higher priority + values in the same tier. All Admin tier rules have higher precedence than + NetworkPolicy or Baseline tier rules. + If two (or more) policies in the same tier with the same priority + could match a connection, then the implementation can apply any of the + matching policies to the connection, and there is no way for the user to + reliably determine which one it will choose. Administrators must be + careful about assigning the priorities for policies with rules that will + match many connections, and ensure that policies have unique priority + values in cases where ambiguity would be unacceptable. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + subject: + description: + Subject defines the pods to which this ClusterNetworkPolicy + applies. maxProperties: 1 minProperties: 1 properties: @@ -6402,11 +5872,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6424,8 +5896,8 @@ spec: properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -6455,11 +5927,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6472,8 +5946,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -6503,11 +5977,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6519,12 +5995,48 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object + tier: + description: |- + Tier is used as the top-level grouping for network policy prioritization. + + Policy tiers are evaluated in the following order: + * Admin tier + * NetworkPolicy tier + * Baseline tier + + ClusterNetworkPolicy can use 2 of these tiers: Admin and Baseline. + + The Admin tier takes precedence over all other policies. Policies + defined in this tier are used to set cluster-wide security rules + that cannot be overridden in the other tiers. If Admin tier has + made a final decision (Accept or Deny) on a connection, then no + further evaluation is done. + + NetworkPolicy tier is the tier for the namespaced v1.NetworkPolicy. + These policies are intended for the application developer to describe + the security policy associated with their deployments inside their + namespace. v1.NetworkPolicy always makes a final decision for selected + pods. Further evaluation only happens for Pods not selected by a + v1.NetworkPolicy. + + Baseline tier is a cluster-wide policy that can be overridden by the + v1.NetworkPolicy. If Baseline tier has made a final decision (Accept or + Deny) on a connection, then no further evaluation is done. + + If a given connection wasn't allowed or denied by any of the tiers, + the default kubernetes policy is applied, which says that + all pods can communicate with each other. + enum: + - Admin + - Baseline + type: string required: + - priority - subject + - tier type: object status: description: Status is the status to be reported by the implementation. @@ -6532,16 +6044,8 @@ spec: conditions: items: description: - "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -6582,12 +6086,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string @@ -6609,11 +6108,6 @@ spec: - metadata - spec type: object - x-kubernetes-validations: - - message: - Only one baseline admin network policy with metadata.name="default" - can be created in the cluster - rule: self.metadata.name == 'default' served: true storage: true subresources: @@ -6729,6 +6223,34 @@ rules: - update # watch for changes - watch + - apiGroups: ["crd.projectcalico.org"] + resources: + - kubecontrollersconfigurations/status + verbs: + - get + - update + # Needed for policy name migrator. + - apiGroups: ["crd.projectcalico.org"] + resources: + - networkpolicies + - stagednetworkpolicies + - globalnetworkpolicies + - stagedglobalnetworkpolicies + verbs: + - watch + - list + - get + - create + - update + - delete + # Needed for policy name migrator to check calico/node daemonset status. + - apiGroups: ["apps"] + resources: + - daemonsets + verbs: + - get + resourceNames: + - calico-node --- # Source: calico/templates/calico-node-rbac.yaml # Include a clusterrole for the calico-node DaemonSet, @@ -6794,11 +6316,10 @@ rules: verbs: - watch - list - # Watch for changes to Kubernetes (Baseline)AdminNetworkPolicies. + # Watch for changes to Kubernetes ClusterNetworkPolicies. - apiGroups: ["policy.networking.k8s.io"] resources: - - adminnetworkpolicies - - baselineadminnetworkpolicies + - clusternetworkpolicies verbs: - watch - list @@ -6883,6 +6404,14 @@ rules: verbs: - create - update + # For monitoring KubeVirt live migration. + - apiGroups: ["kubevirt.io"] + resources: + - virtualmachineinstancemigrations + verbs: + - get + - list + - watch --- # Source: calico/templates/calico-node-rbac.yaml # CNI cluster role diff --git a/manifests/crds.yaml b/manifests/crds.yaml index 3342abea15c..dd7b8baa5ae 100644 --- a/manifests/crds.yaml +++ b/manifests/crds.yaml @@ -33,6 +33,9 @@ spec: format: int32 type: integer bindMode: + enum: + - None + - NodeIP type: string communities: items: @@ -43,11 +46,14 @@ spec: pattern: ^(\d+):(\d+)$|^(\d+):(\d+):(\d+)$ type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set ignoredInterfaces: items: type: string type: array + x-kubernetes-list-type: set listenPort: maximum: 65535 minimum: 1 @@ -57,6 +63,8 @@ spec: localWorkloadPeeringIPV6: type: string logSeverityScreen: + default: Info + pattern: ^(?i)(Trace|Debug|Info|Warning|Error|Fatal)?$ type: string nodeMeshMaxRestartTime: type: string @@ -82,27 +90,36 @@ spec: items: properties: cidr: + format: cidr type: string communities: items: type: string type: array type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceClusterIPs: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceExternalIPs: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceLoadBalancerAggregation: default: Enabled enum: @@ -113,9 +130,12 @@ spec: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -154,12 +174,21 @@ spec: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -174,22 +203,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array exportV6: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -204,22 +246,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array importV4: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -234,22 +289,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array importV6: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -264,11 +332,15 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array type: object type: object @@ -323,15 +395,10 @@ spec: maxRestartTime: type: string nextHopMode: - allOf: - - enum: - - Auto - - Self - - Keep - - enum: - - Auto - - Self - - Keep + enum: + - Auto + - Self + - Keep type: string node: type: string @@ -363,11 +430,18 @@ spec: reachableBy: type: string reversePeering: - enum: - - Auto - - Manual + allOf: + - enum: + - Auto + - Manual + - enum: + - Auto + - Manual type: string sourceAddress: + enum: + - UseNodeIP + - None type: string ttlSecurity: type: integer @@ -456,6 +530,10 @@ spec: properties: classes: items: + enum: + - Agent + - BGP + - Routes type: string type: array node: @@ -477,6 +555,9 @@ spec: routerID: type: string state: + enum: + - Ready + - NotReady type: string version: type: string @@ -490,6 +571,9 @@ spec: routerID: type: string state: + enum: + - Ready + - NotReady type: string version: type: string @@ -513,8 +597,20 @@ spec: since: type: string state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close type: string type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer type: string type: object type: array @@ -526,8 +622,20 @@ spec: since: type: string state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close type: string type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer type: string type: object type: array @@ -557,9 +665,18 @@ spec: peerIP: type: string sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer type: string type: object type: + enum: + - FIB + - RIB type: string type: object type: array @@ -577,9 +694,18 @@ spec: peerIP: type: string sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer type: string type: object type: + enum: + - FIB + - RIB type: string type: object type: array @@ -933,15 +1059,9 @@ spec: if it detects the current value is 2 (strict mode that hurts performance). When set to "Strict", Felix will not modify the JIT hardening setting. [Default: Auto] type: string - bpfKubeProxyEndpointSlicesEnabled: + bpfKubeProxyHealthzPort: description: |- - BPFKubeProxyEndpointSlicesEnabled is deprecated and has no effect. BPF - kube-proxy always accepts endpoint slices. This option will be removed in - the next release. - type: boolean - bpfKubeProxyHealtzPort: - description: |- - BPFKubeProxyHealtzPort, in BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. + BPFKubeProxyHealthzPort, in BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. The health check server is used by external load balancers to determine if this node should receive traffic. [Default: 10256] type: integer bpfKubeProxyIptablesCleanupEnabled: @@ -982,6 +1102,23 @@ spec: [Default: Off]. pattern: ^(?i)(Off|Info|Debug)?$ type: string + bpfMaglevMaxEndpointsPerService: + description: |- + BPFMaglevMaxEndpointsPerService is the maximum number of endpoints + expected to be part of a single Maglev-enabled service. + + Influences the size of the per-service Maglev lookup-tables generated by Felix + and thus the amount of memory reserved. + + [Default: 100] + type: integer + bpfMaglevMaxServices: + description: |- + BPFMaglevMaxServices is the maximum number of expected Maglev-enabled + services that Felix will allocate lookup-tables for. + + [Default: 100] + type: integer bpfMapSizeConntrack: description: |- BPFMapSizeConntrack sets the size for the conntrack map. This map must be large enough to hold @@ -1070,17 +1207,14 @@ spec: type: string bpfRedirectToPeer: description: |- - BPFRedirectToPeer controls which whether it is allowed to forward straight to the - peer side of the workload devices. It is allowed for any host L2 devices by default - (L2Only), but it breaks TCP dump on the host side of workload device as it bypasses - it on ingress. Value of Enabled also allows redirection from L3 host devices like - IPIP tunnel or Wireguard directly to the peer side of the workload's device. This - makes redirection faster, however, it breaks tools like tcpdump on the peer side. - Use Enabled with caution. [Default: L2Only] + BPFRedirectToPeer controls whether traffic may be forwarded directly to the peer side of a workload’s device. + Note that the legacy "L2Only" option is now deprecated and if set it is treated like "Enabled. + Setting this option to "Enabled" allows direct redirection (including from L3 host devices such as IPIP tunnels or WireGuard), + which can improve redirection performance but causes the redirected packets to bypass the host‑side ingress path. + As a result, packet‑capture tools on the host side of the workload device (for example, tcpdump) will not see that traffic. [Default: Enabled] enum: - Enabled - Disabled - - L2Only type: string cgroupV2Path: description: @@ -1431,6 +1565,10 @@ spec: Warning: changing this on a running system can leave "orphaned" rules in the "other" backend. These should be cleaned up to avoid confusing interactions. + enum: + - Legacy + - NFT + - Auto pattern: ^(?i)(Auto|Legacy|NFT)?$ type: string iptablesFilterAllowAction: @@ -1446,26 +1584,10 @@ spec: with an iptables "DROP" action. If you want to use "REJECT" action instead you can configure it in here. pattern: ^(?i)(Drop|Reject)?$ type: string - iptablesLockFilePath: - description: |- - IptablesLockFilePath is the location of the iptables lock file. You may need to change this - if the lock file is not in its standard location (for example if you have mapped it into Felix's - container at a different path). [Default: /run/xtables.lock] - type: string iptablesLockProbeInterval: description: |- - IptablesLockProbeInterval when IptablesLockTimeout is enabled: the time that Felix will wait between - attempts to acquire the iptables lock if it is not available. Lower values make Felix more - responsive when the lock is contended, but use more CPU. [Default: 50ms] - pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ - type: string - iptablesLockTimeout: - description: |- - IptablesLockTimeout is the time that Felix itself will wait for the iptables lock (rather than delegating the - lock handling to the `iptables` command). - - Deprecated: `iptables-restore` v1.8+ always takes the lock, so enabling this feature results in deadlock. - [Default: 0s disabled] + IptablesLockProbeInterval configures the interval between attempts to claim + the xtables lock. Shorter intervals are more responsive but use more CPU. [Default: 50ms] pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ type: string iptablesMangleAllowAction: @@ -1525,6 +1647,19 @@ spec: pattern: ^.* x-kubernetes-int-or-string: true type: array + logActionRateLimit: + description: |- + LogActionRateLimit sets the rate of hitting a Log action. The value must be in the format "N/unit", + where N is a number and unit is one of: second, minute, hour, or day. For example: "10/second" or "100/hour". + pattern: ^[1-9]\d{0,3}/(?:second|minute|hour|day)$ + type: string + logActionRateLimitBurst: + description: + LogActionRateLimitBurst sets the rate limit burst of + hitting a Log action when LogActionRateLimit is enabled. + maximum: 9999 + minimum: 0 + type: integer logDebugFilenameRegex: description: |- LogDebugFilenameRegex controls which source code files have their Debug log output included in the logs. @@ -1537,9 +1672,17 @@ spec: none to disable file logging. [Default: /var/log/calico/felix.log]" type: string logPrefix: - description: - "LogPrefix is the log prefix that Felix uses when rendering - LOG rules. [Default: calico-packet]" + description: |- + LogPrefix is the log prefix that Felix uses when rendering LOG rules. It is possible to use the following specifiers + to include extra information in the log prefix. + - %t: Tier name. + - %k: Kind (short names). + - %n: Policy or profile name. + - %p: Policy or profile name (namespace/name for namespaced kinds or just name for non namespaced kinds). + Calico includes ": " characters at the end of the generated log prefix. + Note that iptables shows up to 29 characters for the log prefix and nftables up to 127 characters. Extra characters are truncated. + [Default: calico-packet] + pattern: "^([a-zA-Z0-9%: /_-])*$" type: string logSeverityFile: description: @@ -1643,9 +1786,10 @@ spec: format: int32 type: integer nftablesMode: + default: Auto description: "NFTablesMode configures nftables support in Felix. [Default: - Disabled]" + Auto]" enum: - Disabled - Enabled @@ -1681,6 +1825,21 @@ spec: PrometheusGoMetricsEnabled disables Go runtime metrics collection, which the Prometheus client does by default, when set to false. This reduces the number of metrics reported, reducing Prometheus load. [Default: true] type: boolean + prometheusMetricsCAFile: + description: |- + PrometheusMetricsCAFile defines the absolute path to the TLS CA certificate file used for securing the /metrics endpoint. + This certificate must be valid and accessible by the calico-node process. + type: string + prometheusMetricsCertFile: + description: |- + PrometheusMetricsCertFile defines the absolute path to the TLS certificate file used for securing the /metrics endpoint. + This certificate must be valid and accessible by the calico-node process. + type: string + prometheusMetricsClientAuth: + description: |- + PrometheusMetricsClientAuth specifies the client authentication type for the /metrics endpoint. + This determines how the server validates client certificates. Default is "RequireAndVerifyClientCert". + type: string prometheusMetricsEnabled: description: "PrometheusMetricsEnabled enables the Prometheus metrics @@ -1691,6 +1850,11 @@ spec: "PrometheusMetricsHost is the host that the Prometheus metrics server should bind to. [Default: empty]" type: string + prometheusMetricsKeyFile: + description: |- + PrometheusMetricsKeyFile defines the absolute path to the private key file corresponding to the TLS certificate + used for securing the /metrics endpoint. The private key must be valid and accessible by the calico-node process. + type: string prometheusMetricsPort: description: "PrometheusMetricsPort is the TCP port that the Prometheus @@ -1974,6 +2138,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -1983,6 +2152,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2013,6 +2183,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2043,11 +2214,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -2059,8 +2237,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -2083,6 +2265,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2113,6 +2296,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2132,6 +2316,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -2141,6 +2330,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2171,6 +2361,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2201,11 +2392,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -2217,8 +2415,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -2241,6 +2443,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2271,6 +2474,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2292,6 +2496,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array preDNAT: @@ -2301,11 +2507,18 @@ spec: serviceAccountSelector: type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2344,6 +2557,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2382,6 +2596,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set interfaceName: type: string node: @@ -2392,6 +2607,8 @@ spec: name: type: string port: + maximum: 65535 + minimum: 0 type: integer protocol: anyOf: @@ -2409,6 +2626,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2445,6 +2663,9 @@ spec: properties: affinity: type: string + affinityClaimTime: + format: date-time + type: string allocations: items: type: integer @@ -2455,6 +2676,10 @@ spec: attributes: items: properties: + alternate: + additionalProperties: + type: string + type: object handle_id: type: string secondary: @@ -2524,6 +2749,11 @@ spec: properties: autoAllocateBlocks: type: boolean + kubeVirtVMAddressPersistence: + enum: + - Enabled + - Disabled + type: string maxBlocksPerHost: maximum: 2147483647 minimum: 0 @@ -2614,39 +2844,47 @@ spec: properties: allowedUses: items: + enum: + - Workload + - Tunnel + - LoadBalancer type: string type: array + x-kubernetes-list-type: set assignmentMode: + default: Automatic enum: - Automatic - Manual type: string blockSize: + maximum: 128 + minimum: 0 type: integer cidr: + format: cidr type: string disableBGPExport: type: boolean disabled: type: boolean - ipip: - properties: - enabled: - type: boolean - mode: - type: string - type: object ipipMode: + enum: + - Never + - Always + - CrossSubnet type: string namespaceSelector: type: string - nat-outgoing: - type: boolean natOutgoing: type: boolean nodeSelector: type: string vxlanMode: + enum: + - Never + - Always + - CrossSubnet type: string required: - cidr @@ -2685,9 +2923,11 @@ spec: spec: properties: reservedCIDRs: + format: cidr items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2727,6 +2967,10 @@ spec: loadBalancer: properties: assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly type: string type: object namespace: @@ -2739,6 +2983,9 @@ spec: hostEndpoint: properties: autoCreate: + enum: + - Enabled + - Disabled type: string createDefaultHostEndpoint: type: string @@ -2752,7 +2999,8 @@ spec: items: type: string type: array - interfaceSelector: + x-kubernetes-list-type: set + interfacePattern: type: string labels: additionalProperties: @@ -2768,6 +3016,9 @@ spec: reconcilerPeriod: type: string syncLabels: + enum: + - Enabled + - Disabled type: string type: object policy: @@ -2775,6 +3026,15 @@ spec: reconcilerPeriod: type: string type: object + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object serviceAccount: properties: reconcilerPeriod: @@ -2788,14 +3048,30 @@ spec: type: object debugProfilePort: format: int32 + maximum: 65535 + minimum: 0 type: integer etcdV3CompactionPeriod: type: string healthChecks: + default: Enabled + enum: + - Enabled + - Disabled type: string logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic type: string prometheusMetricsPort: + maximum: 65535 + minimum: 0 type: integer required: - controllers @@ -2813,6 +3089,10 @@ spec: loadBalancer: properties: assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly type: string type: object namespace: @@ -2825,6 +3105,9 @@ spec: hostEndpoint: properties: autoCreate: + enum: + - Enabled + - Disabled type: string createDefaultHostEndpoint: type: string @@ -2838,7 +3121,8 @@ spec: items: type: string type: array - interfaceSelector: + x-kubernetes-list-type: set + interfacePattern: type: string labels: additionalProperties: @@ -2854,6 +3138,9 @@ spec: reconcilerPeriod: type: string syncLabels: + enum: + - Enabled + - Disabled type: string type: object policy: @@ -2861,6 +3148,15 @@ spec: reconcilerPeriod: type: string type: object + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object serviceAccount: properties: reconcilerPeriod: @@ -2874,14 +3170,30 @@ spec: type: object debugProfilePort: format: int32 + maximum: 65535 + minimum: 0 type: integer etcdV3CompactionPeriod: type: string healthChecks: + default: Enabled + enum: + - Enabled + - Disabled type: string logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic type: string prometheusMetricsPort: + maximum: 65535 + minimum: 0 type: integer required: - controllers @@ -2890,6 +3202,8 @@ spec: type: object served: true storage: true + subresources: + status: {} --- # Source: crds/crd.projectcalico.org_networkpolicies.yaml apiVersion: apiextensions.k8s.io/v1 @@ -2924,6 +3238,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -2933,6 +3252,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2963,6 +3283,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2993,11 +3314,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3009,8 +3337,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3033,6 +3365,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3063,6 +3396,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3082,6 +3416,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3091,6 +3430,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3121,6 +3461,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3151,11 +3492,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3167,8 +3515,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3191,6 +3543,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3221,6 +3574,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3240,6 +3594,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array selector: @@ -3247,11 +3603,18 @@ spec: serviceAccountSelector: type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -3290,6 +3653,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -3332,6 +3696,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3341,6 +3710,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3371,6 +3741,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3401,11 +3772,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3417,8 +3795,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3441,6 +3823,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3471,6 +3854,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3490,6 +3874,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3499,6 +3888,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3529,6 +3919,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3559,11 +3950,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3575,8 +3973,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3599,6 +4001,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3629,6 +4032,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3650,6 +4054,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array preDNAT: @@ -3659,13 +4065,25 @@ spec: serviceAccountSelector: type: string stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -3911,8 +4329,16 @@ spec: policyTypes: items: type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string type: object type: object @@ -3952,6 +4378,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3961,6 +4392,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3991,6 +4423,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4021,11 +4454,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -4037,8 +4477,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -4061,6 +4505,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4091,6 +4536,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4110,6 +4556,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -4119,6 +4570,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4149,6 +4601,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4179,11 +4632,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -4195,8 +4655,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -4219,6 +4683,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4249,6 +4714,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4268,6 +4734,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array selector: @@ -4275,13 +4743,25 @@ spec: serviceAccountSelector: type: string stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -4317,51 +4797,73 @@ spec: spec: properties: defaultAction: - enum: - - Pass - - Deny + allOf: + - enum: + - Allow + - Deny + - Log + - Pass + - enum: + - Pass + - Deny type: string order: type: number type: object + required: + - metadata + - spec type: object + x-kubernetes-validations: + - message: The 'kube-admin' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-admin' ? self.spec.defaultAction == + 'Pass' : true" + - message: The 'kube-baseline' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-baseline' ? self.spec.defaultAction + == 'Pass' : true" + - message: The 'default' tier must have default action 'Deny' + rule: + "self.metadata.name == 'default' ? self.spec.defaultAction == 'Deny' + : true" served: true storage: true --- -# Source: crds/policy.networking.k8s.io_adminnetworkpolicies.yaml +# Source: crds/policy.networking.k8s.io_clusternetworkpolicies.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/30 - policy.networking.k8s.io/bundle-version: v0.1.1 - policy.networking.k8s.io/channel: experimental - creationTimestamp: null - name: adminnetworkpolicies.policy.networking.k8s.io + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/300 + policy.networking.k8s.io/bundle-version: v0.1.7 + policy.networking.k8s.io/channel: standard + name: clusternetworkpolicies.policy.networking.k8s.io spec: group: policy.networking.k8s.io names: - kind: AdminNetworkPolicy - listKind: AdminNetworkPolicyList - plural: adminnetworkpolicies + kind: ClusterNetworkPolicy + listKind: ClusterNetworkPolicyList + plural: clusternetworkpolicies shortNames: - - anp - singular: adminnetworkpolicy + - cnp + singular: clusternetworkpolicy scope: Cluster versions: - additionalPrinterColumns: + - jsonPath: .spec.tier + name: Tier + type: string - jsonPath: .spec.priority name: Priority type: string - jsonPath: .metadata.creationTimestamp name: Age type: date - name: v1alpha1 + name: v1alpha2 schema: openAPIV3Schema: - description: |- - AdminNetworkPolicy is a cluster level resource that is part of the - AdminNetworkPolicy API. + description: ClusterNetworkPolicy is a cluster-wide network policy resource. properties: apiVersion: description: |- @@ -4381,172 +4883,233 @@ spec: metadata: type: object spec: - description: Specification of the desired behavior of AdminNetworkPolicy. + description: Spec defines the desired behavior of ClusterNetworkPolicy. properties: egress: description: |- Egress is the list of Egress rules to be applied to the selected pods. - A total of 100 rules will be allowed in each ANP instance. - The relative precedence of egress rules within a single ANP object (all of - which share the priority) will be determined by the order in which the rule - is written. Thus, a rule that appears at the top of the egress rules - would take the highest precedence. - ANPs with no egress rules do not affect egress traffic. + A maximum of 25 rules is allowed in this block. - Support: Core + The relative precedence of egress rules within a single CNP object + (all of which share the priority) will be determined by the order + in which the rule is written. + Thus, a rule that appears at the top of the egress rules + would take the highest precedence. + CNPs with no egress rules do not affect egress traffic. items: description: |- - AdminNetworkPolicyEgressRule describes an action to take on a particular - set of traffic originating from pods selected by a AdminNetworkPolicy's + ClusterNetworkPolicyEgressRule describes an action to take on a particular + set of traffic originating from pods selected by a ClusterNetworkPolicy's Subject field. + properties: action: description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic (even if it would otherwise have been denied by NetworkPolicy) - Deny: denies the selected traffic - Pass: instructs the selected traffic to skip any remaining ANP rules, and - then pass execution to any NetworkPolicies that select the pod. - If the pod is not selected by any NetworkPolicies then execution - is passed to any BaselineAdminNetworkPolicies that select the pod. + Action specifies the effect this rule will have on matching + traffic. Currently the following actions are supported: + - Accept: Accepts the selected traffic, allowing it to + egress. No further ClusterNetworkPolicy or NetworkPolicy + rules will be processed. - Support: Core + - Deny: Drops the selected traffic. No further + ClusterNetworkPolicy or NetworkPolicy rules will be + processed. + + - Pass: Skips all further ClusterNetworkPolicy rules in the + current tier for the selected traffic, and passes + evaluation to the next tier. enum: - - Allow + - Accept - Deny - Pass type: string name: description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - AdminNetworkPolicies. - - - Support: Core + Name is an identifier for this rule, that may be no more than + 100 characters in length. This field should be used by the implementation + to help improve observability, readability and error-reporting + for any applied policies. maxLength: 100 type: string - ports: + protocols: description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of destination ports for the outgoing egress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core + Protocols allows for more fine-grain matching of traffic on + protocol-specific attributes such as the port. If + unspecified, protocol-specific attributes will not be used + to match traffic. items: description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). + ClusterNetworkPolicyProtocol describes additional protocol-specific match rules. Exactly one field must be set. maxProperties: 1 minProperties: 1 properties: - namedPort: + destinationNamedPort: description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - + DestinationNamedPort selects a destination port on a pod based on the + ContainerPort name. You can't use this in a rule that targets resources + without named ports (e.g. Nodes or Networks). type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core + sctp: + description: SCTP specific protocol matches. + minProperties: 1 properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core + tcp: + description: TCP specific protocol matches. + minProperties: 1 properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + udp: + description: UDP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object type: object - maxItems: 100 + maxItems: 25 + minItems: 1 type: array to: description: |- - To is the List of destinations whose traffic this rule applies to. - If any AdminNetworkPolicyEgressPeer matches the destination of outgoing - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core + To is the list of destinations whose traffic this rule applies to. If any + element matches the destination of outgoing traffic then the specified + action is applied. This field must be defined and contain at least one + item. items: description: |- - AdminNetworkPolicyEgressPeer defines a peer to allow traffic to. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. + ClusterNetworkPolicyEgressPeer defines a peer to allow traffic to. + + Exactly one of the fields must be set for a given peer and this is enforced + by the validation rules on the CRD. If an implementation sees no fields are + set then it can infer that the deployed CRD is of an incompatible version + with an unknown field. In that case it should fail closed. + + For "Accept" rules, "fail closed" means: "treat the rule as matching no + traffic". For "Deny" and "Pass" rules, "fail closed" means: "treat the rule + as a 'Deny all' rule". maxProperties: 1 minProperties: 1 properties: @@ -4554,9 +5117,6 @@ spec: description: |- Namespaces defines a way to select all pods within a set of Namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: matchExpressions: description: @@ -4586,11 +5146,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -4607,107 +5169,37 @@ spec: This is intended for representing entities that live outside the cluster, which can't be selected by pods, namespaces and nodes peers, but note that cluster-internal traffic will be checked against the rule as - well. So if you Allow or Deny traffic to `"0.0.0.0/0"`, that will allow + well. So if you Accept or Deny traffic to `"0.0.0.0/0"`, that will allow or deny all IPv4 pod-to-pod traffic as well. If you don't want that, add a rule that Passes all pod traffic before the Networks rule. - Each item in Networks should be provided in the CIDR format and should be IPv4 or IPv6, for example "10.0.0.0/8" or "fd00::/8". - - Networks can have upto 25 CIDRs specified. - - - Support: Extended - - - + Networks can have up to 25 CIDRs specified. items: description: |- - CIDR is an IP address range in CIDR notation (for example, "10.0.0.0/8" or "fd00::/8"). - This string must be validated by implementations using net.ParseCIDR - TODO: Introduce CEL CIDR validation regex isCIDR() in Kube 1.31 when it is available. + CIDR is an IP address range in CIDR notation + (for example, "10.0.0.0/8" or "fd00::/8"). maxLength: 43 type: string x-kubernetes-validations: - - message: - CIDR must be either an IPv4 or IPv6 address. - IPv4 address embedded in IPv6 addresses are not - supported - rule: self.contains(':') != self.contains('.') + - message: Invalid CIDR format provided + rule: isCIDR(self) maxItems: 25 minItems: 1 type: array x-kubernetes-list-type: set - nodes: - description: |- - Nodes defines a way to select a set of nodes in - the cluster. This field follows standard label selector - semantics; if present but empty, it selects all Nodes. - - - Support: Extended - - - - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: + pods: description: |- Pods defines a way to select a set of pods in a set of namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -4738,11 +5230,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -4755,8 +5249,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -4787,11 +5281,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -4803,77 +5299,80 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object - maxItems: 100 + maxItems: 25 minItems: 1 type: array required: - action - to type: object - x-kubernetes-validations: - - message: - networks/nodes peer cannot be set with namedPorts since - there are no namedPorts for networks/nodes - rule: - "!(self.to.exists(peer, has(peer.networks) || has(peer.nodes)) - && has(self.ports) && self.ports.exists(port, has(port.namedPort)))" - maxItems: 100 + maxItems: 25 type: array ingress: description: |- Ingress is the list of Ingress rules to be applied to the selected pods. - A total of 100 rules will be allowed in each ANP instance. - The relative precedence of ingress rules within a single ANP object (all of - which share the priority) will be determined by the order in which the rule - is written. Thus, a rule that appears at the top of the ingress rules - would take the highest precedence. - ANPs with no ingress rules do not affect ingress traffic. + A maximum of 25 rules is allowed in this block. - Support: Core + The relative precedence of ingress rules within a single CNP object + (all of which share the priority) will be determined by the order + in which the rule is written. + Thus, a rule that appears at the top of the ingress rules + would take the highest precedence. + CNPs with no ingress rules do not affect ingress traffic. items: description: |- - AdminNetworkPolicyIngressRule describes an action to take on a particular - set of traffic destined for pods selected by an AdminNetworkPolicy's + ClusterNetworkPolicyIngressRule describes an action to take on a particular + set of traffic destined for pods selected by a ClusterNetworkPolicy's Subject field. properties: action: description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic (even if it would otherwise have been denied by NetworkPolicy) - Deny: denies the selected traffic - Pass: instructs the selected traffic to skip any remaining ANP rules, and - then pass execution to any NetworkPolicies that select the pod. - If the pod is not selected by any NetworkPolicies then execution - is passed to any BaselineAdminNetworkPolicies that select the pod. + Action specifies the effect this rule will have on matching + traffic. Currently the following actions are supported: + + - Accept: Accepts the selected traffic, allowing it into + the destination. No further ClusterNetworkPolicy or + NetworkPolicy rules will be processed. + Note: while Accept ensures traffic is accepted by + Kubernetes network policy, it is still possible that the + packet is blocked in other ways: custom nftable rules, + high-layers e.g. service mesh. - Support: Core + - Deny: Drops the selected traffic. No further + ClusterNetworkPolicy or NetworkPolicy rules will be + processed. + + - Pass: Skips all further ClusterNetworkPolicy rules in the + current tier for the selected traffic, and passes + evaluation to the next tier. enum: - - Allow + - Accept - Deny - Pass type: string from: description: |- From is the list of sources whose traffic this rule applies to. - If any AdminNetworkPolicyIngressPeer matches the source of incoming + If any element matches the source of incoming traffic then the specified action is applied. This field must be defined and contain at least one item. - - - Support: Core items: description: |- - AdminNetworkPolicyIngressPeer defines an in-cluster peer to allow traffic from. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. + ClusterNetworkPolicyIngressPeer defines a peer to allow traffic from. + + Exactly one of the fields must be set for a given peer and this is enforced + by the validation rules on the CRD. If an implementation sees no fields are + set then it can infer that the deployed CRD is of an incompatible version + with an unknown field. In that case it should fail closed. + + For "Accept" rules, "fail closed" means: "treat the rule as matching no + traffic". For "Deny" and "Pass" rules, "fail closed" means: "treat the rule + as a 'Deny all' rule". maxProperties: 1 minProperties: 1 properties: @@ -4881,9 +5380,6 @@ spec: description: |- Namespaces defines a way to select all pods within a set of Namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: matchExpressions: description: @@ -4913,11 +5409,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -4933,14 +5431,11 @@ spec: Pods defines a way to select a set of pods in a set of namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -4971,11 +5466,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -4988,8 +5485,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -5020,11 +5517,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -5036,154 +5535,206 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object - maxItems: 100 + maxItems: 25 minItems: 1 type: array name: description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - AdminNetworkPolicies. - - - Support: Core + Name is an identifier for this rule, that may be no more than + 100 characters in length. This field should be used by the implementation + to help improve observability, readability and error-reporting + for any applied policies. maxLength: 100 type: string - ports: + protocols: description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of ports which should be matched on - the pods selected for this policy i.e the subject of the policy. - So it matches on the destination port for the ingress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core + Protocols allows for more fine-grain matching of traffic on + protocol-specific attributes such as the port. If + unspecified, protocol-specific attributes will not be used + to match traffic. items: description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). + ClusterNetworkPolicyProtocol describes additional protocol-specific match rules. Exactly one field must be set. maxProperties: 1 minProperties: 1 properties: - namedPort: + destinationNamedPort: description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - + DestinationNamedPort selects a destination port on a pod based on the + ContainerPort name. You can't use this in a rule that targets resources + without named ports (e.g. Nodes or Networks). type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core + sctp: + description: SCTP specific protocol matches. + minProperties: 1 properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core + tcp: + description: TCP specific protocol matches. + minProperties: 1 properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + udp: + description: UDP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object type: object - maxItems: 100 + maxItems: 25 + minItems: 1 type: array required: - action - from type: object - maxItems: 100 + maxItems: 25 type: array priority: description: |- - Priority is a value from 0 to 1000. Rules with lower priority values have - higher precedence, and are checked before rules with higher priority values. - All AdminNetworkPolicy rules have higher precedence than NetworkPolicy or - BaselineAdminNetworkPolicy rules - The behavior is undefined if two ANP objects have same priority. - - - Support: Core + Priority is a value from 0 to 1000 indicating the precedence of + the policy within its tier. Policies with lower priority values have + higher precedence, and are checked before policies with higher priority + values in the same tier. All Admin tier rules have higher precedence than + NetworkPolicy or Baseline tier rules. + If two (or more) policies in the same tier with the same priority + could match a connection, then the implementation can apply any of the + matching policies to the connection, and there is no way for the user to + reliably determine which one it will choose. Administrators must be + careful about assigning the priorities for policies with rules that will + match many connections, and ensure that policies have unique priority + values in cases where ambiguity would be unacceptable. format: int32 maximum: 1000 minimum: 0 type: integer subject: - description: |- - Subject defines the pods to which this AdminNetworkPolicy applies. - Note that host-networked pods are not included in subject selection. - - - Support: Core + description: + Subject defines the pods to which this ClusterNetworkPolicy + applies. maxProperties: 1 minProperties: 1 properties: @@ -5218,11 +5769,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -5240,8 +5793,8 @@ spec: properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -5271,11 +5824,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -5288,8 +5843,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -5319,11 +5874,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -5335,13 +5892,48 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object + tier: + description: |- + Tier is used as the top-level grouping for network policy prioritization. + + Policy tiers are evaluated in the following order: + * Admin tier + * NetworkPolicy tier + * Baseline tier + + ClusterNetworkPolicy can use 2 of these tiers: Admin and Baseline. + + The Admin tier takes precedence over all other policies. Policies + defined in this tier are used to set cluster-wide security rules + that cannot be overridden in the other tiers. If Admin tier has + made a final decision (Accept or Deny) on a connection, then no + further evaluation is done. + + NetworkPolicy tier is the tier for the namespaced v1.NetworkPolicy. + These policies are intended for the application developer to describe + the security policy associated with their deployments inside their + namespace. v1.NetworkPolicy always makes a final decision for selected + pods. Further evaluation only happens for Pods not selected by a + v1.NetworkPolicy. + + Baseline tier is a cluster-wide policy that can be overridden by the + v1.NetworkPolicy. If Baseline tier has made a final decision (Accept or + Deny) on a connection, then no further evaluation is done. + + If a given connection wasn't allowed or denied by any of the tiers, + the default kubernetes policy is applied, which says that + all pods can communicate with each other. + enum: + - Admin + - Baseline + type: string required: - priority - subject + - tier type: object status: description: Status is the status to be reported by the implementation. @@ -5349,16 +5941,8 @@ spec: conditions: items: description: - "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -5399,12 +5983,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string @@ -5436,1088 +6015,3 @@ status: plural: "" conditions: null storedVersions: null ---- -# Source: crds/policy.networking.k8s.io_baselineadminnetworkpolicies.yaml -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/30 - policy.networking.k8s.io/bundle-version: v0.1.1 - policy.networking.k8s.io/channel: experimental - creationTimestamp: null - name: baselineadminnetworkpolicies.policy.networking.k8s.io -spec: - group: policy.networking.k8s.io - names: - kind: BaselineAdminNetworkPolicy - listKind: BaselineAdminNetworkPolicyList - plural: baselineadminnetworkpolicies - shortNames: - - banp - singular: baselineadminnetworkpolicy - scope: Cluster - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: |- - BaselineAdminNetworkPolicy is a cluster level resource that is part of the - AdminNetworkPolicy API. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: Specification of the desired behavior of BaselineAdminNetworkPolicy. - properties: - egress: - description: |- - Egress is the list of Egress rules to be applied to the selected pods if - they are not matched by any AdminNetworkPolicy or NetworkPolicy rules. - A total of 100 Egress rules will be allowed in each BANP instance. - The relative precedence of egress rules within a single BANP object - will be determined by the order in which the rule is written. - Thus, a rule that appears at the top of the egress rules - would take the highest precedence. - BANPs with no egress rules do not affect egress traffic. - - - Support: Core - items: - description: |- - BaselineAdminNetworkPolicyEgressRule describes an action to take on a particular - set of traffic originating from pods selected by a BaselineAdminNetworkPolicy's - Subject field. - - properties: - action: - description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic - Deny: denies the selected traffic - - - Support: Core - enum: - - Allow - - Deny - type: string - name: - description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - BaselineAdminNetworkPolicies. - - - Support: Core - maxLength: 100 - type: string - ports: - description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of destination ports for the outgoing egress traffic. - If Ports is not set then the rule does not filter traffic via port. - items: - description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). - Exactly one field must be set. - maxProperties: 1 - minProperties: 1 - properties: - namedPort: - description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - - type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core - properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol - type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core - properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start - type: object - type: object - maxItems: 100 - type: array - to: - description: |- - To is the list of destinations whose traffic this rule applies to. - If any AdminNetworkPolicyEgressPeer matches the destination of outgoing - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core - items: - description: |- - AdminNetworkPolicyEgressPeer defines a peer to allow traffic to. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: |- - Namespaces defines a way to select all pods within a set of Namespaces. - Note that host-networked pods are not included in this type of peer. - - - Support: Core - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - networks: - description: |- - Networks defines a way to select peers via CIDR blocks. - This is intended for representing entities that live outside the cluster, - which can't be selected by pods, namespaces and nodes peers, but note - that cluster-internal traffic will be checked against the rule as - well. So if you Allow or Deny traffic to `"0.0.0.0/0"`, that will allow - or deny all IPv4 pod-to-pod traffic as well. If you don't want that, - add a rule that Passes all pod traffic before the Networks rule. - - - Each item in Networks should be provided in the CIDR format and should be - IPv4 or IPv6, for example "10.0.0.0/8" or "fd00::/8". - - - Networks can have upto 25 CIDRs specified. - - - Support: Extended - - - - items: - description: |- - CIDR is an IP address range in CIDR notation (for example, "10.0.0.0/8" or "fd00::/8"). - This string must be validated by implementations using net.ParseCIDR - TODO: Introduce CEL CIDR validation regex isCIDR() in Kube 1.31 when it is available. - maxLength: 43 - type: string - x-kubernetes-validations: - - message: - CIDR must be either an IPv4 or IPv6 address. - IPv4 address embedded in IPv6 addresses are not - supported - rule: self.contains(':') != self.contains('.') - maxItems: 25 - minItems: 1 - type: array - x-kubernetes-list-type: set - nodes: - description: |- - Nodes defines a way to select a set of nodes in - the cluster. This field follows standard label selector - semantics; if present but empty, it selects all Nodes. - - - Support: Extended - - - - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: |- - Pods defines a way to select a set of pods in - a set of namespaces. Note that host-networked pods - are not included in this type of peer. - - - Support: Core - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - maxItems: 100 - minItems: 1 - type: array - required: - - action - - to - type: object - x-kubernetes-validations: - - message: - networks/nodes peer cannot be set with namedPorts since - there are no namedPorts for networks/nodes - rule: - "!(self.to.exists(peer, has(peer.networks) || has(peer.nodes)) - && has(self.ports) && self.ports.exists(port, has(port.namedPort)))" - maxItems: 100 - type: array - ingress: - description: |- - Ingress is the list of Ingress rules to be applied to the selected pods - if they are not matched by any AdminNetworkPolicy or NetworkPolicy rules. - A total of 100 Ingress rules will be allowed in each BANP instance. - The relative precedence of ingress rules within a single BANP object - will be determined by the order in which the rule is written. - Thus, a rule that appears at the top of the ingress rules - would take the highest precedence. - BANPs with no ingress rules do not affect ingress traffic. - - - Support: Core - items: - description: |- - BaselineAdminNetworkPolicyIngressRule describes an action to take on a particular - set of traffic destined for pods selected by a BaselineAdminNetworkPolicy's - Subject field. - properties: - action: - description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic - Deny: denies the selected traffic - - - Support: Core - enum: - - Allow - - Deny - type: string - from: - description: |- - From is the list of sources whose traffic this rule applies to. - If any AdminNetworkPolicyIngressPeer matches the source of incoming - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core - items: - description: |- - AdminNetworkPolicyIngressPeer defines an in-cluster peer to allow traffic from. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: |- - Namespaces defines a way to select all pods within a set of Namespaces. - Note that host-networked pods are not included in this type of peer. - - - Support: Core - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: |- - Pods defines a way to select a set of pods in - a set of namespaces. Note that host-networked pods - are not included in this type of peer. - - - Support: Core - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - maxItems: 100 - minItems: 1 - type: array - name: - description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - BaselineAdminNetworkPolicies. - - - Support: Core - maxLength: 100 - type: string - ports: - description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of ports which should be matched on - the pods selected for this policy i.e the subject of the policy. - So it matches on the destination port for the ingress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core - items: - description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). - Exactly one field must be set. - maxProperties: 1 - minProperties: 1 - properties: - namedPort: - description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - - type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core - properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol - type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core - properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start - type: object - type: object - maxItems: 100 - type: array - required: - - action - - from - type: object - maxItems: 100 - type: array - subject: - description: |- - Subject defines the pods to which this BaselineAdminNetworkPolicy applies. - Note that host-networked pods are not included in subject selection. - - - Support: Core - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: Namespaces is used to select pods via namespace selectors. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: - Pods is used to select pods via namespace AND pod - selectors. - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - required: - - subject - type: object - status: - description: Status is the status to be reported by the implementation. - properties: - conditions: - items: - description: - "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - required: - - conditions - type: object - required: - - metadata - - spec - type: object - x-kubernetes-validations: - - message: - Only one baseline admin network policy with metadata.name="default" - can be created in the cluster - rule: self.metadata.name == 'default' - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: null - storedVersions: null diff --git a/manifests/csi-driver.yaml b/manifests/csi-driver.yaml index 7e50ae5bac8..740e8071e2e 100644 --- a/manifests/csi-driver.yaml +++ b/manifests/csi-driver.yaml @@ -46,7 +46,7 @@ spec: effect: NoSchedule containers: - name: calico-csi - image: calico/csi:master + image: quay.io/calico/csi:master imagePullPolicy: IfNotPresent args: - --nodeid=$(KUBE_NODE_NAME) @@ -71,7 +71,7 @@ spec: mountPath: /var/lib/kubelet/ mountPropagation: "Bidirectional" - name: csi-node-driver-registrar - image: calico/node-driver-registrar:master + image: quay.io/calico/node-driver-registrar:master imagePullPolicy: IfNotPresent args: - --v=5 diff --git a/manifests/flannel-migration/calico.yaml b/manifests/flannel-migration/calico.yaml index 07fc66c4d7f..63d0a613aca 100644 --- a/manifests/flannel-migration/calico.yaml +++ b/manifests/flannel-migration/calico.yaml @@ -119,6 +119,9 @@ spec: format: int32 type: integer bindMode: + enum: + - None + - NodeIP type: string communities: items: @@ -129,11 +132,14 @@ spec: pattern: ^(\d+):(\d+)$|^(\d+):(\d+):(\d+)$ type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set ignoredInterfaces: items: type: string type: array + x-kubernetes-list-type: set listenPort: maximum: 65535 minimum: 1 @@ -143,6 +149,8 @@ spec: localWorkloadPeeringIPV6: type: string logSeverityScreen: + default: Info + pattern: ^(?i)(Trace|Debug|Info|Warning|Error|Fatal)?$ type: string nodeMeshMaxRestartTime: type: string @@ -168,27 +176,36 @@ spec: items: properties: cidr: + format: cidr type: string communities: items: type: string type: array type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceClusterIPs: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceExternalIPs: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceLoadBalancerAggregation: default: Enabled enum: @@ -199,9 +216,12 @@ spec: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -240,12 +260,21 @@ spec: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -260,22 +289,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array exportV6: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -290,22 +332,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array importV4: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -320,22 +375,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array importV6: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -350,11 +418,15 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array type: object type: object @@ -409,15 +481,10 @@ spec: maxRestartTime: type: string nextHopMode: - allOf: - - enum: - - Auto - - Self - - Keep - - enum: - - Auto - - Self - - Keep + enum: + - Auto + - Self + - Keep type: string node: type: string @@ -449,11 +516,18 @@ spec: reachableBy: type: string reversePeering: - enum: - - Auto - - Manual + allOf: + - enum: + - Auto + - Manual + - enum: + - Auto + - Manual type: string sourceAddress: + enum: + - UseNodeIP + - None type: string ttlSecurity: type: integer @@ -542,6 +616,10 @@ spec: properties: classes: items: + enum: + - Agent + - BGP + - Routes type: string type: array node: @@ -563,6 +641,9 @@ spec: routerID: type: string state: + enum: + - Ready + - NotReady type: string version: type: string @@ -576,6 +657,9 @@ spec: routerID: type: string state: + enum: + - Ready + - NotReady type: string version: type: string @@ -599,8 +683,20 @@ spec: since: type: string state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close type: string type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer type: string type: object type: array @@ -612,8 +708,20 @@ spec: since: type: string state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close type: string type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer type: string type: object type: array @@ -643,9 +751,18 @@ spec: peerIP: type: string sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer type: string type: object type: + enum: + - FIB + - RIB type: string type: object type: array @@ -663,9 +780,18 @@ spec: peerIP: type: string sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer type: string type: object type: + enum: + - FIB + - RIB type: string type: object type: array @@ -1019,15 +1145,9 @@ spec: if it detects the current value is 2 (strict mode that hurts performance). When set to "Strict", Felix will not modify the JIT hardening setting. [Default: Auto] type: string - bpfKubeProxyEndpointSlicesEnabled: + bpfKubeProxyHealthzPort: description: |- - BPFKubeProxyEndpointSlicesEnabled is deprecated and has no effect. BPF - kube-proxy always accepts endpoint slices. This option will be removed in - the next release. - type: boolean - bpfKubeProxyHealtzPort: - description: |- - BPFKubeProxyHealtzPort, in BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. + BPFKubeProxyHealthzPort, in BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. The health check server is used by external load balancers to determine if this node should receive traffic. [Default: 10256] type: integer bpfKubeProxyIptablesCleanupEnabled: @@ -1068,6 +1188,23 @@ spec: [Default: Off]. pattern: ^(?i)(Off|Info|Debug)?$ type: string + bpfMaglevMaxEndpointsPerService: + description: |- + BPFMaglevMaxEndpointsPerService is the maximum number of endpoints + expected to be part of a single Maglev-enabled service. + + Influences the size of the per-service Maglev lookup-tables generated by Felix + and thus the amount of memory reserved. + + [Default: 100] + type: integer + bpfMaglevMaxServices: + description: |- + BPFMaglevMaxServices is the maximum number of expected Maglev-enabled + services that Felix will allocate lookup-tables for. + + [Default: 100] + type: integer bpfMapSizeConntrack: description: |- BPFMapSizeConntrack sets the size for the conntrack map. This map must be large enough to hold @@ -1156,17 +1293,14 @@ spec: type: string bpfRedirectToPeer: description: |- - BPFRedirectToPeer controls which whether it is allowed to forward straight to the - peer side of the workload devices. It is allowed for any host L2 devices by default - (L2Only), but it breaks TCP dump on the host side of workload device as it bypasses - it on ingress. Value of Enabled also allows redirection from L3 host devices like - IPIP tunnel or Wireguard directly to the peer side of the workload's device. This - makes redirection faster, however, it breaks tools like tcpdump on the peer side. - Use Enabled with caution. [Default: L2Only] + BPFRedirectToPeer controls whether traffic may be forwarded directly to the peer side of a workload’s device. + Note that the legacy "L2Only" option is now deprecated and if set it is treated like "Enabled. + Setting this option to "Enabled" allows direct redirection (including from L3 host devices such as IPIP tunnels or WireGuard), + which can improve redirection performance but causes the redirected packets to bypass the host‑side ingress path. + As a result, packet‑capture tools on the host side of the workload device (for example, tcpdump) will not see that traffic. [Default: Enabled] enum: - Enabled - Disabled - - L2Only type: string cgroupV2Path: description: @@ -1517,6 +1651,10 @@ spec: Warning: changing this on a running system can leave "orphaned" rules in the "other" backend. These should be cleaned up to avoid confusing interactions. + enum: + - Legacy + - NFT + - Auto pattern: ^(?i)(Auto|Legacy|NFT)?$ type: string iptablesFilterAllowAction: @@ -1532,26 +1670,10 @@ spec: with an iptables "DROP" action. If you want to use "REJECT" action instead you can configure it in here. pattern: ^(?i)(Drop|Reject)?$ type: string - iptablesLockFilePath: - description: |- - IptablesLockFilePath is the location of the iptables lock file. You may need to change this - if the lock file is not in its standard location (for example if you have mapped it into Felix's - container at a different path). [Default: /run/xtables.lock] - type: string iptablesLockProbeInterval: description: |- - IptablesLockProbeInterval when IptablesLockTimeout is enabled: the time that Felix will wait between - attempts to acquire the iptables lock if it is not available. Lower values make Felix more - responsive when the lock is contended, but use more CPU. [Default: 50ms] - pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ - type: string - iptablesLockTimeout: - description: |- - IptablesLockTimeout is the time that Felix itself will wait for the iptables lock (rather than delegating the - lock handling to the `iptables` command). - - Deprecated: `iptables-restore` v1.8+ always takes the lock, so enabling this feature results in deadlock. - [Default: 0s disabled] + IptablesLockProbeInterval configures the interval between attempts to claim + the xtables lock. Shorter intervals are more responsive but use more CPU. [Default: 50ms] pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ type: string iptablesMangleAllowAction: @@ -1611,6 +1733,19 @@ spec: pattern: ^.* x-kubernetes-int-or-string: true type: array + logActionRateLimit: + description: |- + LogActionRateLimit sets the rate of hitting a Log action. The value must be in the format "N/unit", + where N is a number and unit is one of: second, minute, hour, or day. For example: "10/second" or "100/hour". + pattern: ^[1-9]\d{0,3}/(?:second|minute|hour|day)$ + type: string + logActionRateLimitBurst: + description: + LogActionRateLimitBurst sets the rate limit burst of + hitting a Log action when LogActionRateLimit is enabled. + maximum: 9999 + minimum: 0 + type: integer logDebugFilenameRegex: description: |- LogDebugFilenameRegex controls which source code files have their Debug log output included in the logs. @@ -1623,9 +1758,17 @@ spec: none to disable file logging. [Default: /var/log/calico/felix.log]" type: string logPrefix: - description: - "LogPrefix is the log prefix that Felix uses when rendering - LOG rules. [Default: calico-packet]" + description: |- + LogPrefix is the log prefix that Felix uses when rendering LOG rules. It is possible to use the following specifiers + to include extra information in the log prefix. + - %t: Tier name. + - %k: Kind (short names). + - %n: Policy or profile name. + - %p: Policy or profile name (namespace/name for namespaced kinds or just name for non namespaced kinds). + Calico includes ": " characters at the end of the generated log prefix. + Note that iptables shows up to 29 characters for the log prefix and nftables up to 127 characters. Extra characters are truncated. + [Default: calico-packet] + pattern: "^([a-zA-Z0-9%: /_-])*$" type: string logSeverityFile: description: @@ -1729,9 +1872,10 @@ spec: format: int32 type: integer nftablesMode: + default: Auto description: "NFTablesMode configures nftables support in Felix. [Default: - Disabled]" + Auto]" enum: - Disabled - Enabled @@ -1767,6 +1911,21 @@ spec: PrometheusGoMetricsEnabled disables Go runtime metrics collection, which the Prometheus client does by default, when set to false. This reduces the number of metrics reported, reducing Prometheus load. [Default: true] type: boolean + prometheusMetricsCAFile: + description: |- + PrometheusMetricsCAFile defines the absolute path to the TLS CA certificate file used for securing the /metrics endpoint. + This certificate must be valid and accessible by the calico-node process. + type: string + prometheusMetricsCertFile: + description: |- + PrometheusMetricsCertFile defines the absolute path to the TLS certificate file used for securing the /metrics endpoint. + This certificate must be valid and accessible by the calico-node process. + type: string + prometheusMetricsClientAuth: + description: |- + PrometheusMetricsClientAuth specifies the client authentication type for the /metrics endpoint. + This determines how the server validates client certificates. Default is "RequireAndVerifyClientCert". + type: string prometheusMetricsEnabled: description: "PrometheusMetricsEnabled enables the Prometheus metrics @@ -1777,6 +1936,11 @@ spec: "PrometheusMetricsHost is the host that the Prometheus metrics server should bind to. [Default: empty]" type: string + prometheusMetricsKeyFile: + description: |- + PrometheusMetricsKeyFile defines the absolute path to the private key file corresponding to the TLS certificate + used for securing the /metrics endpoint. The private key must be valid and accessible by the calico-node process. + type: string prometheusMetricsPort: description: "PrometheusMetricsPort is the TCP port that the Prometheus @@ -2060,6 +2224,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -2069,6 +2238,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2099,6 +2269,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2129,11 +2300,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -2145,8 +2323,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -2169,6 +2351,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2199,6 +2382,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2218,6 +2402,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -2227,6 +2416,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2257,6 +2447,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2287,11 +2478,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -2303,8 +2501,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -2327,6 +2529,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -2357,6 +2560,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -2378,6 +2582,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array preDNAT: @@ -2387,11 +2593,18 @@ spec: serviceAccountSelector: type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2430,6 +2643,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2468,6 +2682,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set interfaceName: type: string node: @@ -2478,6 +2693,8 @@ spec: name: type: string port: + maximum: 65535 + minimum: 0 type: integer protocol: anyOf: @@ -2495,6 +2712,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2531,6 +2749,9 @@ spec: properties: affinity: type: string + affinityClaimTime: + format: date-time + type: string allocations: items: type: integer @@ -2541,6 +2762,10 @@ spec: attributes: items: properties: + alternate: + additionalProperties: + type: string + type: object handle_id: type: string secondary: @@ -2610,6 +2835,11 @@ spec: properties: autoAllocateBlocks: type: boolean + kubeVirtVMAddressPersistence: + enum: + - Enabled + - Disabled + type: string maxBlocksPerHost: maximum: 2147483647 minimum: 0 @@ -2700,39 +2930,47 @@ spec: properties: allowedUses: items: + enum: + - Workload + - Tunnel + - LoadBalancer type: string type: array + x-kubernetes-list-type: set assignmentMode: + default: Automatic enum: - Automatic - Manual type: string blockSize: + maximum: 128 + minimum: 0 type: integer cidr: + format: cidr type: string disableBGPExport: type: boolean disabled: type: boolean - ipip: - properties: - enabled: - type: boolean - mode: - type: string - type: object ipipMode: + enum: + - Never + - Always + - CrossSubnet type: string namespaceSelector: type: string - nat-outgoing: - type: boolean natOutgoing: type: boolean nodeSelector: type: string vxlanMode: + enum: + - Never + - Always + - CrossSubnet type: string required: - cidr @@ -2771,9 +3009,11 @@ spec: spec: properties: reservedCIDRs: + format: cidr items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -2813,6 +3053,10 @@ spec: loadBalancer: properties: assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly type: string type: object namespace: @@ -2825,6 +3069,9 @@ spec: hostEndpoint: properties: autoCreate: + enum: + - Enabled + - Disabled type: string createDefaultHostEndpoint: type: string @@ -2838,7 +3085,8 @@ spec: items: type: string type: array - interfaceSelector: + x-kubernetes-list-type: set + interfacePattern: type: string labels: additionalProperties: @@ -2854,6 +3102,9 @@ spec: reconcilerPeriod: type: string syncLabels: + enum: + - Enabled + - Disabled type: string type: object policy: @@ -2861,6 +3112,15 @@ spec: reconcilerPeriod: type: string type: object + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object serviceAccount: properties: reconcilerPeriod: @@ -2874,14 +3134,30 @@ spec: type: object debugProfilePort: format: int32 + maximum: 65535 + minimum: 0 type: integer etcdV3CompactionPeriod: type: string healthChecks: + default: Enabled + enum: + - Enabled + - Disabled type: string logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic type: string prometheusMetricsPort: + maximum: 65535 + minimum: 0 type: integer required: - controllers @@ -2899,6 +3175,10 @@ spec: loadBalancer: properties: assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly type: string type: object namespace: @@ -2911,6 +3191,9 @@ spec: hostEndpoint: properties: autoCreate: + enum: + - Enabled + - Disabled type: string createDefaultHostEndpoint: type: string @@ -2924,7 +3207,8 @@ spec: items: type: string type: array - interfaceSelector: + x-kubernetes-list-type: set + interfacePattern: type: string labels: additionalProperties: @@ -2940,6 +3224,9 @@ spec: reconcilerPeriod: type: string syncLabels: + enum: + - Enabled + - Disabled type: string type: object policy: @@ -2947,6 +3234,15 @@ spec: reconcilerPeriod: type: string type: object + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object serviceAccount: properties: reconcilerPeriod: @@ -2960,14 +3256,30 @@ spec: type: object debugProfilePort: format: int32 + maximum: 65535 + minimum: 0 type: integer etcdV3CompactionPeriod: type: string healthChecks: + default: Enabled + enum: + - Enabled + - Disabled type: string logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic type: string prometheusMetricsPort: + maximum: 65535 + minimum: 0 type: integer required: - controllers @@ -2976,6 +3288,8 @@ spec: type: object served: true storage: true + subresources: + status: {} --- # Source: calico/templates/kdd-crds.yaml apiVersion: apiextensions.k8s.io/v1 @@ -3010,6 +3324,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3019,6 +3338,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3049,6 +3369,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3079,11 +3400,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3095,8 +3423,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3119,6 +3451,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3149,6 +3482,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3168,6 +3502,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3177,6 +3516,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3207,6 +3547,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3237,11 +3578,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3253,8 +3601,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3277,6 +3629,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3307,6 +3660,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3326,6 +3680,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array selector: @@ -3333,11 +3689,18 @@ spec: serviceAccountSelector: type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -3376,6 +3739,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -3418,6 +3782,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3427,6 +3796,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3457,6 +3827,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3487,11 +3858,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3503,8 +3881,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3527,6 +3909,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3557,6 +3940,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3576,6 +3960,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -3585,6 +3974,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3615,6 +4005,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3645,11 +4036,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -3661,8 +4059,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -3685,6 +4087,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -3715,6 +4118,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -3736,6 +4140,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array preDNAT: @@ -3745,13 +4151,25 @@ spec: serviceAccountSelector: type: string stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -3997,8 +4415,16 @@ spec: policyTypes: items: type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string type: object type: object @@ -4038,6 +4464,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -4047,6 +4478,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4077,6 +4509,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4107,11 +4540,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -4123,8 +4563,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -4147,6 +4591,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4177,6 +4622,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4196,6 +4642,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -4205,6 +4656,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4235,6 +4687,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4265,11 +4718,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -4281,8 +4741,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -4305,6 +4769,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -4335,6 +4800,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -4354,6 +4820,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array selector: @@ -4361,13 +4829,25 @@ spec: serviceAccountSelector: type: string stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true @@ -4403,14 +4883,36 @@ spec: spec: properties: defaultAction: - enum: - - Pass - - Deny + allOf: + - enum: + - Allow + - Deny + - Log + - Pass + - enum: + - Pass + - Deny type: string order: type: number type: object + required: + - metadata + - spec type: object + x-kubernetes-validations: + - message: The 'kube-admin' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-admin' ? self.spec.defaultAction == + 'Pass' : true" + - message: The 'kube-baseline' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-baseline' ? self.spec.defaultAction + == 'Pass' : true" + - message: The 'default' tier must have default action 'Deny' + rule: + "self.metadata.name == 'default' ? self.spec.defaultAction == 'Deny' + : true" served: true storage: true --- @@ -4419,35 +4921,35 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/30 - policy.networking.k8s.io/bundle-version: v0.1.1 - policy.networking.k8s.io/channel: experimental - creationTimestamp: null - name: adminnetworkpolicies.policy.networking.k8s.io + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/300 + policy.networking.k8s.io/bundle-version: v0.1.7 + policy.networking.k8s.io/channel: standard + name: clusternetworkpolicies.policy.networking.k8s.io spec: group: policy.networking.k8s.io names: - kind: AdminNetworkPolicy - listKind: AdminNetworkPolicyList - plural: adminnetworkpolicies + kind: ClusterNetworkPolicy + listKind: ClusterNetworkPolicyList + plural: clusternetworkpolicies shortNames: - - anp - singular: adminnetworkpolicy + - cnp + singular: clusternetworkpolicy scope: Cluster versions: - additionalPrinterColumns: + - jsonPath: .spec.tier + name: Tier + type: string - jsonPath: .spec.priority name: Priority type: string - jsonPath: .metadata.creationTimestamp name: Age type: date - name: v1alpha1 + name: v1alpha2 schema: openAPIV3Schema: - description: |- - AdminNetworkPolicy is a cluster level resource that is part of the - AdminNetworkPolicy API. + description: ClusterNetworkPolicy is a cluster-wide network policy resource. properties: apiVersion: description: |- @@ -4467,172 +4969,233 @@ spec: metadata: type: object spec: - description: Specification of the desired behavior of AdminNetworkPolicy. + description: Spec defines the desired behavior of ClusterNetworkPolicy. properties: egress: description: |- Egress is the list of Egress rules to be applied to the selected pods. - A total of 100 rules will be allowed in each ANP instance. - The relative precedence of egress rules within a single ANP object (all of - which share the priority) will be determined by the order in which the rule - is written. Thus, a rule that appears at the top of the egress rules - would take the highest precedence. - ANPs with no egress rules do not affect egress traffic. + A maximum of 25 rules is allowed in this block. - Support: Core + The relative precedence of egress rules within a single CNP object + (all of which share the priority) will be determined by the order + in which the rule is written. + Thus, a rule that appears at the top of the egress rules + would take the highest precedence. + CNPs with no egress rules do not affect egress traffic. items: description: |- - AdminNetworkPolicyEgressRule describes an action to take on a particular - set of traffic originating from pods selected by a AdminNetworkPolicy's + ClusterNetworkPolicyEgressRule describes an action to take on a particular + set of traffic originating from pods selected by a ClusterNetworkPolicy's Subject field. + properties: action: description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic (even if it would otherwise have been denied by NetworkPolicy) - Deny: denies the selected traffic - Pass: instructs the selected traffic to skip any remaining ANP rules, and - then pass execution to any NetworkPolicies that select the pod. - If the pod is not selected by any NetworkPolicies then execution - is passed to any BaselineAdminNetworkPolicies that select the pod. + Action specifies the effect this rule will have on matching + traffic. Currently the following actions are supported: + - Accept: Accepts the selected traffic, allowing it to + egress. No further ClusterNetworkPolicy or NetworkPolicy + rules will be processed. - Support: Core + - Deny: Drops the selected traffic. No further + ClusterNetworkPolicy or NetworkPolicy rules will be + processed. + + - Pass: Skips all further ClusterNetworkPolicy rules in the + current tier for the selected traffic, and passes + evaluation to the next tier. enum: - - Allow + - Accept - Deny - Pass type: string name: description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - AdminNetworkPolicies. - - - Support: Core + Name is an identifier for this rule, that may be no more than + 100 characters in length. This field should be used by the implementation + to help improve observability, readability and error-reporting + for any applied policies. maxLength: 100 type: string - ports: + protocols: description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of destination ports for the outgoing egress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core + Protocols allows for more fine-grain matching of traffic on + protocol-specific attributes such as the port. If + unspecified, protocol-specific attributes will not be used + to match traffic. items: description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). + ClusterNetworkPolicyProtocol describes additional protocol-specific match rules. Exactly one field must be set. maxProperties: 1 minProperties: 1 properties: - namedPort: + destinationNamedPort: description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - + DestinationNamedPort selects a destination port on a pod based on the + ContainerPort name. You can't use this in a rule that targets resources + without named ports (e.g. Nodes or Networks). type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core + sctp: + description: SCTP specific protocol matches. + minProperties: 1 properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core + tcp: + description: TCP specific protocol matches. + minProperties: 1 properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + udp: + description: UDP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object type: object - maxItems: 100 + maxItems: 25 + minItems: 1 type: array to: description: |- - To is the List of destinations whose traffic this rule applies to. - If any AdminNetworkPolicyEgressPeer matches the destination of outgoing - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core + To is the list of destinations whose traffic this rule applies to. If any + element matches the destination of outgoing traffic then the specified + action is applied. This field must be defined and contain at least one + item. items: description: |- - AdminNetworkPolicyEgressPeer defines a peer to allow traffic to. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. + ClusterNetworkPolicyEgressPeer defines a peer to allow traffic to. + + Exactly one of the fields must be set for a given peer and this is enforced + by the validation rules on the CRD. If an implementation sees no fields are + set then it can infer that the deployed CRD is of an incompatible version + with an unknown field. In that case it should fail closed. + + For "Accept" rules, "fail closed" means: "treat the rule as matching no + traffic". For "Deny" and "Pass" rules, "fail closed" means: "treat the rule + as a 'Deny all' rule". maxProperties: 1 minProperties: 1 properties: @@ -4640,9 +5203,6 @@ spec: description: |- Namespaces defines a way to select all pods within a set of Namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: matchExpressions: description: @@ -4672,11 +5232,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -4693,1206 +5255,37 @@ spec: This is intended for representing entities that live outside the cluster, which can't be selected by pods, namespaces and nodes peers, but note that cluster-internal traffic will be checked against the rule as - well. So if you Allow or Deny traffic to `"0.0.0.0/0"`, that will allow + well. So if you Accept or Deny traffic to `"0.0.0.0/0"`, that will allow or deny all IPv4 pod-to-pod traffic as well. If you don't want that, add a rule that Passes all pod traffic before the Networks rule. - Each item in Networks should be provided in the CIDR format and should be IPv4 or IPv6, for example "10.0.0.0/8" or "fd00::/8". - - Networks can have upto 25 CIDRs specified. - - - Support: Extended - - - + Networks can have up to 25 CIDRs specified. items: description: |- - CIDR is an IP address range in CIDR notation (for example, "10.0.0.0/8" or "fd00::/8"). - This string must be validated by implementations using net.ParseCIDR - TODO: Introduce CEL CIDR validation regex isCIDR() in Kube 1.31 when it is available. + CIDR is an IP address range in CIDR notation + (for example, "10.0.0.0/8" or "fd00::/8"). maxLength: 43 type: string x-kubernetes-validations: - - message: - CIDR must be either an IPv4 or IPv6 address. - IPv4 address embedded in IPv6 addresses are not - supported - rule: self.contains(':') != self.contains('.') + - message: Invalid CIDR format provided + rule: isCIDR(self) maxItems: 25 minItems: 1 type: array x-kubernetes-list-type: set - nodes: - description: |- - Nodes defines a way to select a set of nodes in - the cluster. This field follows standard label selector - semantics; if present but empty, it selects all Nodes. - - - Support: Extended - - - - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: |- - Pods defines a way to select a set of pods in - a set of namespaces. Note that host-networked pods - are not included in this type of peer. - - - Support: Core - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - maxItems: 100 - minItems: 1 - type: array - required: - - action - - to - type: object - x-kubernetes-validations: - - message: - networks/nodes peer cannot be set with namedPorts since - there are no namedPorts for networks/nodes - rule: - "!(self.to.exists(peer, has(peer.networks) || has(peer.nodes)) - && has(self.ports) && self.ports.exists(port, has(port.namedPort)))" - maxItems: 100 - type: array - ingress: - description: |- - Ingress is the list of Ingress rules to be applied to the selected pods. - A total of 100 rules will be allowed in each ANP instance. - The relative precedence of ingress rules within a single ANP object (all of - which share the priority) will be determined by the order in which the rule - is written. Thus, a rule that appears at the top of the ingress rules - would take the highest precedence. - ANPs with no ingress rules do not affect ingress traffic. - - - Support: Core - items: - description: |- - AdminNetworkPolicyIngressRule describes an action to take on a particular - set of traffic destined for pods selected by an AdminNetworkPolicy's - Subject field. - properties: - action: - description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic (even if it would otherwise have been denied by NetworkPolicy) - Deny: denies the selected traffic - Pass: instructs the selected traffic to skip any remaining ANP rules, and - then pass execution to any NetworkPolicies that select the pod. - If the pod is not selected by any NetworkPolicies then execution - is passed to any BaselineAdminNetworkPolicies that select the pod. - - - Support: Core - enum: - - Allow - - Deny - - Pass - type: string - from: - description: |- - From is the list of sources whose traffic this rule applies to. - If any AdminNetworkPolicyIngressPeer matches the source of incoming - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core - items: - description: |- - AdminNetworkPolicyIngressPeer defines an in-cluster peer to allow traffic from. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: |- - Namespaces defines a way to select all pods within a set of Namespaces. - Note that host-networked pods are not included in this type of peer. - - - Support: Core - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: |- - Pods defines a way to select a set of pods in - a set of namespaces. Note that host-networked pods - are not included in this type of peer. - - - Support: Core - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - maxItems: 100 - minItems: 1 - type: array - name: - description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - AdminNetworkPolicies. - - - Support: Core - maxLength: 100 - type: string - ports: - description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of ports which should be matched on - the pods selected for this policy i.e the subject of the policy. - So it matches on the destination port for the ingress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core - items: - description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). - Exactly one field must be set. - maxProperties: 1 - minProperties: 1 - properties: - namedPort: - description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - - type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core - properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol - type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core - properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start - type: object - type: object - maxItems: 100 - type: array - required: - - action - - from - type: object - maxItems: 100 - type: array - priority: - description: |- - Priority is a value from 0 to 1000. Rules with lower priority values have - higher precedence, and are checked before rules with higher priority values. - All AdminNetworkPolicy rules have higher precedence than NetworkPolicy or - BaselineAdminNetworkPolicy rules - The behavior is undefined if two ANP objects have same priority. - - - Support: Core - format: int32 - maximum: 1000 - minimum: 0 - type: integer - subject: - description: |- - Subject defines the pods to which this AdminNetworkPolicy applies. - Note that host-networked pods are not included in subject selection. - - - Support: Core - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: Namespaces is used to select pods via namespace selectors. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: - Pods is used to select pods via namespace AND pod - selectors. - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - required: - - priority - - subject - type: object - status: - description: Status is the status to be reported by the implementation. - properties: - conditions: - items: - description: - "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - required: - - conditions - type: object - required: - - metadata - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: null - storedVersions: null ---- -# Source: calico/templates/kdd-crds.yaml -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/30 - policy.networking.k8s.io/bundle-version: v0.1.1 - policy.networking.k8s.io/channel: experimental - creationTimestamp: null - name: baselineadminnetworkpolicies.policy.networking.k8s.io -spec: - group: policy.networking.k8s.io - names: - kind: BaselineAdminNetworkPolicy - listKind: BaselineAdminNetworkPolicyList - plural: baselineadminnetworkpolicies - shortNames: - - banp - singular: baselineadminnetworkpolicy - scope: Cluster - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: |- - BaselineAdminNetworkPolicy is a cluster level resource that is part of the - AdminNetworkPolicy API. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: Specification of the desired behavior of BaselineAdminNetworkPolicy. - properties: - egress: - description: |- - Egress is the list of Egress rules to be applied to the selected pods if - they are not matched by any AdminNetworkPolicy or NetworkPolicy rules. - A total of 100 Egress rules will be allowed in each BANP instance. - The relative precedence of egress rules within a single BANP object - will be determined by the order in which the rule is written. - Thus, a rule that appears at the top of the egress rules - would take the highest precedence. - BANPs with no egress rules do not affect egress traffic. - - - Support: Core - items: - description: |- - BaselineAdminNetworkPolicyEgressRule describes an action to take on a particular - set of traffic originating from pods selected by a BaselineAdminNetworkPolicy's - Subject field. - - properties: - action: - description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic - Deny: denies the selected traffic - - - Support: Core - enum: - - Allow - - Deny - type: string - name: - description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - BaselineAdminNetworkPolicies. - - - Support: Core - maxLength: 100 - type: string - ports: - description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of destination ports for the outgoing egress traffic. - If Ports is not set then the rule does not filter traffic via port. - items: - description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). - Exactly one field must be set. - maxProperties: 1 - minProperties: 1 - properties: - namedPort: - description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - - type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core - properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol - type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core - properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start - type: object - type: object - maxItems: 100 - type: array - to: - description: |- - To is the list of destinations whose traffic this rule applies to. - If any AdminNetworkPolicyEgressPeer matches the destination of outgoing - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core - items: - description: |- - AdminNetworkPolicyEgressPeer defines a peer to allow traffic to. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: |- - Namespaces defines a way to select all pods within a set of Namespaces. - Note that host-networked pods are not included in this type of peer. - - - Support: Core - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - networks: - description: |- - Networks defines a way to select peers via CIDR blocks. - This is intended for representing entities that live outside the cluster, - which can't be selected by pods, namespaces and nodes peers, but note - that cluster-internal traffic will be checked against the rule as - well. So if you Allow or Deny traffic to `"0.0.0.0/0"`, that will allow - or deny all IPv4 pod-to-pod traffic as well. If you don't want that, - add a rule that Passes all pod traffic before the Networks rule. - - - Each item in Networks should be provided in the CIDR format and should be - IPv4 or IPv6, for example "10.0.0.0/8" or "fd00::/8". - - - Networks can have upto 25 CIDRs specified. - - - Support: Extended - - - - items: - description: |- - CIDR is an IP address range in CIDR notation (for example, "10.0.0.0/8" or "fd00::/8"). - This string must be validated by implementations using net.ParseCIDR - TODO: Introduce CEL CIDR validation regex isCIDR() in Kube 1.31 when it is available. - maxLength: 43 - type: string - x-kubernetes-validations: - - message: - CIDR must be either an IPv4 or IPv6 address. - IPv4 address embedded in IPv6 addresses are not - supported - rule: self.contains(':') != self.contains('.') - maxItems: 25 - minItems: 1 - type: array - x-kubernetes-list-type: set - nodes: - description: |- - Nodes defines a way to select a set of nodes in - the cluster. This field follows standard label selector - semantics; if present but empty, it selects all Nodes. - - - Support: Extended - - - - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: + pods: description: |- Pods defines a way to select a set of pods in a set of namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -5923,11 +5316,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -5940,8 +5335,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -5972,11 +5367,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -5988,73 +5385,80 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object - maxItems: 100 + maxItems: 25 minItems: 1 type: array required: - action - to type: object - x-kubernetes-validations: - - message: - networks/nodes peer cannot be set with namedPorts since - there are no namedPorts for networks/nodes - rule: - "!(self.to.exists(peer, has(peer.networks) || has(peer.nodes)) - && has(self.ports) && self.ports.exists(port, has(port.namedPort)))" - maxItems: 100 + maxItems: 25 type: array ingress: description: |- - Ingress is the list of Ingress rules to be applied to the selected pods - if they are not matched by any AdminNetworkPolicy or NetworkPolicy rules. - A total of 100 Ingress rules will be allowed in each BANP instance. - The relative precedence of ingress rules within a single BANP object - will be determined by the order in which the rule is written. - Thus, a rule that appears at the top of the ingress rules - would take the highest precedence. - BANPs with no ingress rules do not affect ingress traffic. + Ingress is the list of Ingress rules to be applied to the selected pods. + A maximum of 25 rules is allowed in this block. - Support: Core + The relative precedence of ingress rules within a single CNP object + (all of which share the priority) will be determined by the order + in which the rule is written. + Thus, a rule that appears at the top of the ingress rules + would take the highest precedence. + CNPs with no ingress rules do not affect ingress traffic. items: description: |- - BaselineAdminNetworkPolicyIngressRule describes an action to take on a particular - set of traffic destined for pods selected by a BaselineAdminNetworkPolicy's + ClusterNetworkPolicyIngressRule describes an action to take on a particular + set of traffic destined for pods selected by a ClusterNetworkPolicy's Subject field. properties: action: description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic - Deny: denies the selected traffic + Action specifies the effect this rule will have on matching + traffic. Currently the following actions are supported: + + - Accept: Accepts the selected traffic, allowing it into + the destination. No further ClusterNetworkPolicy or + NetworkPolicy rules will be processed. + Note: while Accept ensures traffic is accepted by + Kubernetes network policy, it is still possible that the + packet is blocked in other ways: custom nftable rules, + high-layers e.g. service mesh. - Support: Core + - Deny: Drops the selected traffic. No further + ClusterNetworkPolicy or NetworkPolicy rules will be + processed. + + - Pass: Skips all further ClusterNetworkPolicy rules in the + current tier for the selected traffic, and passes + evaluation to the next tier. enum: - - Allow + - Accept - Deny + - Pass type: string from: description: |- From is the list of sources whose traffic this rule applies to. - If any AdminNetworkPolicyIngressPeer matches the source of incoming + If any element matches the source of incoming traffic then the specified action is applied. This field must be defined and contain at least one item. - - - Support: Core items: description: |- - AdminNetworkPolicyIngressPeer defines an in-cluster peer to allow traffic from. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. + ClusterNetworkPolicyIngressPeer defines a peer to allow traffic from. + + Exactly one of the fields must be set for a given peer and this is enforced + by the validation rules on the CRD. If an implementation sees no fields are + set then it can infer that the deployed CRD is of an incompatible version + with an unknown field. In that case it should fail closed. + + For "Accept" rules, "fail closed" means: "treat the rule as matching no + traffic". For "Deny" and "Pass" rules, "fail closed" means: "treat the rule + as a 'Deny all' rule". maxProperties: 1 minProperties: 1 properties: @@ -6062,9 +5466,6 @@ spec: description: |- Namespaces defines a way to select all pods within a set of Namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: matchExpressions: description: @@ -6094,11 +5495,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6114,14 +5517,11 @@ spec: Pods defines a way to select a set of pods in a set of namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -6152,11 +5552,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6169,8 +5571,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -6201,11 +5603,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6217,140 +5621,206 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object - maxItems: 100 + maxItems: 25 minItems: 1 type: array name: description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - BaselineAdminNetworkPolicies. - - - Support: Core + Name is an identifier for this rule, that may be no more than + 100 characters in length. This field should be used by the implementation + to help improve observability, readability and error-reporting + for any applied policies. maxLength: 100 type: string - ports: + protocols: description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of ports which should be matched on - the pods selected for this policy i.e the subject of the policy. - So it matches on the destination port for the ingress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core + Protocols allows for more fine-grain matching of traffic on + protocol-specific attributes such as the port. If + unspecified, protocol-specific attributes will not be used + to match traffic. items: description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). + ClusterNetworkPolicyProtocol describes additional protocol-specific match rules. Exactly one field must be set. maxProperties: 1 minProperties: 1 properties: - namedPort: + destinationNamedPort: description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - + DestinationNamedPort selects a destination port on a pod based on the + ContainerPort name. You can't use this in a rule that targets resources + without named ports (e.g. Nodes or Networks). type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core + sctp: + description: SCTP specific protocol matches. + minProperties: 1 properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core + tcp: + description: TCP specific protocol matches. + minProperties: 1 properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + udp: + description: UDP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object type: object - maxItems: 100 + maxItems: 25 + minItems: 1 type: array required: - action - from type: object - maxItems: 100 + maxItems: 25 type: array - subject: + priority: description: |- - Subject defines the pods to which this BaselineAdminNetworkPolicy applies. - Note that host-networked pods are not included in subject selection. - - - Support: Core + Priority is a value from 0 to 1000 indicating the precedence of + the policy within its tier. Policies with lower priority values have + higher precedence, and are checked before policies with higher priority + values in the same tier. All Admin tier rules have higher precedence than + NetworkPolicy or Baseline tier rules. + If two (or more) policies in the same tier with the same priority + could match a connection, then the implementation can apply any of the + matching policies to the connection, and there is no way for the user to + reliably determine which one it will choose. Administrators must be + careful about assigning the priorities for policies with rules that will + match many connections, and ensure that policies have unique priority + values in cases where ambiguity would be unacceptable. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + subject: + description: + Subject defines the pods to which this ClusterNetworkPolicy + applies. maxProperties: 1 minProperties: 1 properties: @@ -6385,11 +5855,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6407,8 +5879,8 @@ spec: properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -6438,11 +5910,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6455,8 +5929,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -6486,11 +5960,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -6502,12 +5978,48 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object + tier: + description: |- + Tier is used as the top-level grouping for network policy prioritization. + + Policy tiers are evaluated in the following order: + * Admin tier + * NetworkPolicy tier + * Baseline tier + + ClusterNetworkPolicy can use 2 of these tiers: Admin and Baseline. + + The Admin tier takes precedence over all other policies. Policies + defined in this tier are used to set cluster-wide security rules + that cannot be overridden in the other tiers. If Admin tier has + made a final decision (Accept or Deny) on a connection, then no + further evaluation is done. + + NetworkPolicy tier is the tier for the namespaced v1.NetworkPolicy. + These policies are intended for the application developer to describe + the security policy associated with their deployments inside their + namespace. v1.NetworkPolicy always makes a final decision for selected + pods. Further evaluation only happens for Pods not selected by a + v1.NetworkPolicy. + + Baseline tier is a cluster-wide policy that can be overridden by the + v1.NetworkPolicy. If Baseline tier has made a final decision (Accept or + Deny) on a connection, then no further evaluation is done. + + If a given connection wasn't allowed or denied by any of the tiers, + the default kubernetes policy is applied, which says that + all pods can communicate with each other. + enum: + - Admin + - Baseline + type: string required: + - priority - subject + - tier type: object status: description: Status is the status to be reported by the implementation. @@ -6515,16 +6027,8 @@ spec: conditions: items: description: - "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -6565,12 +6069,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string @@ -6592,11 +6091,6 @@ spec: - metadata - spec type: object - x-kubernetes-validations: - - message: - Only one baseline admin network policy with metadata.name="default" - can be created in the cluster - rule: self.metadata.name == 'default' served: true storage: true subresources: @@ -6712,6 +6206,34 @@ rules: - update # watch for changes - watch + - apiGroups: ["crd.projectcalico.org"] + resources: + - kubecontrollersconfigurations/status + verbs: + - get + - update + # Needed for policy name migrator. + - apiGroups: ["crd.projectcalico.org"] + resources: + - networkpolicies + - stagednetworkpolicies + - globalnetworkpolicies + - stagedglobalnetworkpolicies + verbs: + - watch + - list + - get + - create + - update + - delete + # Needed for policy name migrator to check calico/node daemonset status. + - apiGroups: ["apps"] + resources: + - daemonsets + verbs: + - get + resourceNames: + - calico-node --- # Source: calico/templates/calico-node-rbac.yaml # Include a clusterrole for the calico-node DaemonSet, @@ -6776,11 +6298,10 @@ rules: verbs: - watch - list - # Watch for changes to Kubernetes (Baseline)AdminNetworkPolicies. + # Watch for changes to Kubernetes ClusterNetworkPolicies. - apiGroups: ["policy.networking.k8s.io"] resources: - - adminnetworkpolicies - - baselineadminnetworkpolicies + - clusternetworkpolicies verbs: - watch - list @@ -6898,6 +6419,14 @@ rules: - daemonsets verbs: - get + # For monitoring KubeVirt live migration. + - apiGroups: ["kubevirt.io"] + resources: + - virtualmachineinstancemigrations + verbs: + - get + - list + - watch --- # Source: calico/templates/calico-node-rbac.yaml # CNI cluster role diff --git a/manifests/flannel-migration/migration-job.yaml b/manifests/flannel-migration/migration-job.yaml index f1840832b5b..399c5c81a7b 100644 --- a/manifests/flannel-migration/migration-job.yaml +++ b/manifests/flannel-migration/migration-job.yaml @@ -159,7 +159,7 @@ spec: restartPolicy: OnFailure containers: - name: flannel-migration-controller - image: calico/flannel-migration-controller:master + image: quay.io/calico/flannel-migration-controller:master env: # Choose which controllers to run. - name: ENABLED_CONTROLLERS diff --git a/manifests/generate.sh b/manifests/generate.sh index 8e8cb40dcd1..b299a3965af 100755 --- a/manifests/generate.sh +++ b/manifests/generate.sh @@ -75,38 +75,51 @@ for FILE in $(ls ../charts/calico/crds); do ${HELM} template ../charts/calico \ --include-crds \ --show-only $FILE \ - --set version=$CALICO_VERSION \ - --set node.registry=$REGISTRY \ - --set calicoctl.registry=$REGISTRY \ - --set typha.registry=$REGISTRY \ - --set cni.registry=$REGISTRY \ - --set kubeControllers.registry=$REGISTRY \ - --set flannel.registry=$REGISTRY \ - --set flannelMigration.registry=$REGISTRY \ - --set dikastes.registry=$REGISTRY \ - --set csi-driver.registry=$REGISTRY \ + --set version=$CALICO_VERSION \ + --set node.registry=$REGISTRY \ + --set calicoctl.registry=$REGISTRY \ + --set typha.registry=$REGISTRY \ + --set cni.registry=$REGISTRY \ + --set kubeControllers.registry=$REGISTRY \ + --set flannelMigration.registry=$REGISTRY \ + --set dikastes.registry=$REGISTRY \ + --set csi-driver.registry=$REGISTRY \ -f ../charts/values/calico.yaml >> crds.yaml done ########################################################################## # Build manifest which includes both Calico and Operator CRDs. ########################################################################## -echo "# CustomResourceDefinitions for Calico and Tigera operator" > operator-crds.yaml -for FILE in $(ls ../charts/tigera-operator/crds/*.yaml | xargs -n1 basename); do - ${HELM} -n tigera-operator template \ - --include-crds \ - --show-only $FILE \ - --set version=$CALICO_VERSION \ - ../charts/tigera-operator >> operator-crds.yaml +echo "# crd.projectcalico.org/v1 and operator.tigera.io/v1 APIs" > v1_crd_projectcalico_org.yaml +for FILE in $(ls ../charts/crd.projectcalico.org.v1/templates/*.yaml | xargs -n1 basename); do + ${HELM} template \ + --show-only templates/$FILE \ + --set version=$CALICO_VERSION \ + ../charts/crd.projectcalico.org.v1 >> v1_crd_projectcalico_org.yaml done -for FILE in $(ls ../charts/calico/crds); do - ${HELM} template ../charts/calico \ - --include-crds \ - --show-only $FILE \ - --set version=$CALICO_VERSION \ - -f ../charts/values/calico.yaml >> operator-crds.yaml +for FILE in $(ls ../charts/crd.projectcalico.org.v1/templates/calico/*.yaml | xargs -n1 basename); do + ${HELM} template \ + --show-only templates/calico/$FILE \ + --set version=$CALICO_VERSION \ + ../charts/crd.projectcalico.org.v1 >> v1_crd_projectcalico_org.yaml done +# Maintain legacy operator-crds.yaml for a while. +cp v1_crd_projectcalico_org.yaml operator-crds.yaml + +echo "# projectcalico.org/v3 and operator.tigera.io/v1 APIs" > v3_projectcalico_org.yaml +for FILE in $(ls ../charts/projectcalico.org.v3/templates/*.yaml | xargs -n1 basename); do + ${HELM} template \ + --show-only templates/$FILE \ + --set version=$CALICO_VERSION \ + ../charts/projectcalico.org.v3 >> v3_projectcalico_org.yaml +done +for FILE in $(ls ../charts/projectcalico.org.v3/templates/calico/*.yaml | xargs -n1 basename); do + ${HELM} template \ + --show-only templates/calico/$FILE \ + --set version=$CALICO_VERSION \ + ../charts/projectcalico.org.v3 >> v3_projectcalico_org.yaml +done ########################################################################## # Build Calico manifests. @@ -120,7 +133,7 @@ for FILE in $VALUES_FILES; do echo "Generating manifest from charts/values/$FILE" ${HELM} -n kube-system template \ ../charts/calico \ - --set version=$CALICO_VERSION \ + --set version=$CALICO_VERSION \ -f ../charts/values/$FILE > $FILE done @@ -151,7 +164,7 @@ mv $(find ocp/tigera-operator -name "*.yaml") ocp/ && rm -r ocp/tigera-operator # Generating the upgrade manifest for OCP. # It excludes files specific to configuring the BPF dataplane, CRs (03-cr-*) and CRDs to maintain compatibility and not change the existing configuration in already installed clusters. -OCP_VALUES_FILES=$(ls ocp | grep -v -e '01-configmap-kubernetes-services-endpoint\.yaml' -e '02-configmap-calico-resources\.yaml' -e '^03-cr-' -e 'cluster-network-operator\.yaml' -e '\.*crd\.*') +OCP_VALUES_FILES=$(ls ocp | grep -v -e '01-configmap-kubernetes-services-endpoint\.yaml' -e '02-configmap-calico-resources\.yaml' -e '^03-cr-' -e 'cluster-network-operator\.yaml' -e '\.*crd\.*' -e 'mutatingadmissionpolicy') rm -f tigera-operator-ocp-upgrade.yaml for FILE in $OCP_VALUES_FILES; do cat "ocp/$FILE" >> tigera-operator-ocp-upgrade.yaml @@ -159,14 +172,12 @@ for FILE in $OCP_VALUES_FILES; do done ########################################################################## -# Replace image versions for "static" Calico manifests. +# Replace image registry and/or versions for "static" Calico manifests. ########################################################################## -if [[ $CALICO_VERSION != master ]]; then echo "Replacing image versions for static manifests" - for img in $NON_HELM_MANIFEST_IMAGES; do - curr_img=${defaultRegistry}/${img} - new_img=${REGISTRY}/${img} - echo "$curr_img:$defaultCalicoVersion --> $new_img:$CALICO_VERSION" - find . -type f -exec sed -i "s|${curr_img}:[A-Za-z0-9_.-]*|${new_img}:$CALICO_VERSION|g" {} \; - done -fi +for img in $NON_HELM_MANIFEST_IMAGES; do + curr_img=${defaultRegistry}/${img} + new_img=${REGISTRY}/${img} + echo "$curr_img:$defaultCalicoVersion --> $new_img:$CALICO_VERSION" + find . -type f -exec sed -i "s|${curr_img}:[A-Za-z0-9_.-]*|${new_img}:$CALICO_VERSION|g" {} \; +done diff --git a/manifests/ocp/02-role-tigera-operator.yaml b/manifests/ocp/02-role-tigera-operator.yaml index cfc3c87c04c..4045b4ddf15 100644 --- a/manifests/ocp/02-role-tigera-operator.yaml +++ b/manifests/ocp/02-role-tigera-operator.yaml @@ -6,8 +6,10 @@ metadata: labels: k8s-app: tigera-operator rules: - # The tigera/operator installs CustomResourceDefinitions necessary for itself + # The tigera/operator needs the following permissions when configured with the --bootstrapCRDs=true + # argument. When specified, the tigera/operator installs CustomResourceDefinitions necessary for itself # and Calico more broadly to function. + # You can remove this permission block if your operator deployment does not use this option. - apiGroups: - apiextensions.k8s.io resources: @@ -17,7 +19,7 @@ rules: - list - watch - create - # We only allow update access to our own CRDs. + # Allow updating the crd.projectcalico.org CRDs. - apiGroups: - apiextensions.k8s.io resources: @@ -25,11 +27,6 @@ rules: verbs: - update resourceNames: - - apiservers.operator.tigera.io - - gatewayapis.operator.tigera.io - - imagesets.operator.tigera.io - - installations.operator.tigera.io - - tigerastatuses.operator.tigera.io - bgpconfigurations.crd.projectcalico.org - bgpfilters.crd.projectcalico.org - bgppeers.crd.projectcalico.org @@ -52,10 +49,54 @@ rules: - stagedkubernetesnetworkpolicies.crd.projectcalico.org - networksets.crd.projectcalico.org - tiers.crd.projectcalico.org + # Allow updating the projectcalico.org/v3 CRDs. + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - update + resourceNames: + - bgpconfigurations.projectcalico.org + - bgpfilters.projectcalico.org + - bgppeers.projectcalico.org + - blockaffinities.projectcalico.org + - caliconodestatuses.projectcalico.org + - clusterinformations.projectcalico.org + - felixconfigurations.projectcalico.org + - globalnetworkpolicies.projectcalico.org + - stagedglobalnetworkpolicies.projectcalico.org + - globalnetworksets.projectcalico.org + - hostendpoints.projectcalico.org + - ipamblocks.projectcalico.org + - ipamconfigurations.projectcalico.org + - ipamhandles.projectcalico.org + - ippools.projectcalico.org + - ipreservations.projectcalico.org + - kubecontrollersconfigurations.projectcalico.org + - networkpolicies.projectcalico.org + - stagednetworkpolicies.projectcalico.org + - stagedkubernetesnetworkpolicies.projectcalico.org + - networksets.projectcalico.org + - tiers.projectcalico.org + # Allow updating the operator.tigera.io CRDs. + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - update + resourceNames: + - apiservers.operator.tigera.io + - gatewayapis.operator.tigera.io + - imagesets.operator.tigera.io + - installations.operator.tigera.io + - tigerastatuses.operator.tigera.io - whiskers.operator.tigera.io - goldmanes.operator.tigera.io - managementclusterconnections.operator.tigera.io - # We need update and delete access for ANP/BANP CRDs to set owner refs when assuming control of pre-existing CRDs, for example on OCP. + # We need update and delete access for the ClusterNetworkPolicy CRD to set owner references + # when assuming control of pre-existing CRDs, for example on OCP. - apiGroups: - apiextensions.k8s.io resources: @@ -64,8 +105,7 @@ rules: - update - delete resourceNames: - - adminnetworkpolicies.policy.networking.k8s.io - - baselineadminnetworkpolicies.policy.networking.k8s.io + - clusternetworkpolicies.policy.networking.k8s.io - apiGroups: - "" resources: @@ -227,6 +267,7 @@ rules: - watch - apiGroups: - crd.projectcalico.org + - projectcalico.org resources: - felixconfigurations - ippools @@ -238,6 +279,7 @@ rules: - watch - apiGroups: - crd.projectcalico.org + - projectcalico.org resources: - kubecontrollersconfigurations - bgpconfigurations @@ -311,10 +353,19 @@ rules: resources: - mutatingwebhookconfigurations verbs: + - create - delete - get - list - watch + - apiGroups: + - admissionregistration.k8s.io + resources: + - mutatingwebhookconfigurations + resourceNames: + - envoy-gateway-topology-injector.tigera-gateway + verbs: + - update # Needed for operator lock - apiGroups: - coordination.k8s.io @@ -346,6 +397,18 @@ rules: verbs: - list - watch + # The operator needs permissions to create and update validating webhook configuration. + - apiGroups: + - admissionregistration.k8s.io + resources: + - validatingwebhookconfigurations + verbs: + - create + - update + - delete + - get + - list + - watch # When running in OpenShift, we need to update networking config. - apiGroups: - config.openshift.io @@ -392,6 +455,7 @@ rules: # Need these permissions for the calicoctl init container. - apiGroups: - crd.projectcalico.org + - projectcalico.org resources: - bgpconfigurations - bgppeers @@ -407,6 +471,7 @@ rules: - create - apiGroups: - crd.projectcalico.org + - projectcalico.org resources: - ipamblocks verbs: @@ -477,6 +542,8 @@ rules: - tcproutes.gateway.networking.k8s.io - tlsroutes.gateway.networking.k8s.io - udproutes.gateway.networking.k8s.io + - xbackendtrafficpolicies.gateway.networking.x-k8s.io + - xlistenersets.gateway.networking.x-k8s.io - backends.gateway.envoyproxy.io - backendtrafficpolicies.gateway.envoyproxy.io - clienttrafficpolicies.gateway.envoyproxy.io @@ -526,3 +593,13 @@ rules: - update resourceNames: - tigera-gateway-api-gateway-helm-certgen + # For monitoring KubeVirt live migration. The operator does not do this itself, but needs to + # be able to assign this RBAC to Typha and calico-node. + - apiGroups: + - kubevirt.io + resources: + - virtualmachineinstancemigrations + verbs: + - get + - list + - watch diff --git a/manifests/operator-crds.yaml b/manifests/operator-crds.yaml index 48a48e4cc3c..6ae308c1640 100644 --- a/manifests/operator-crds.yaml +++ b/manifests/operator-crds.yaml @@ -1,11 +1,11 @@ -# CustomResourceDefinitions for Calico and Tigera operator +# crd.projectcalico.org/v1 and operator.tigera.io/v1 APIs --- -# Source: crds/operator.tigera.io_apiservers.yaml +# Source: crd.projectcalico.org.v1/templates/operator.tigera.io_apiservers.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.3 + controller-gen.kubebuilder.io/version: v0.18.0 name: apiservers.operator.tigera.io spec: group: operator.tigera.io @@ -429,7 +429,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -444,7 +443,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -618,7 +616,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -633,7 +630,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -731,8 +727,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -809,7 +805,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -824,7 +819,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -998,7 +992,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -1013,7 +1006,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -1155,7 +1147,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -1238,7 +1230,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -1482,7 +1474,6 @@ spec: - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. If this value is nil, the behavior is equivalent to the Honor policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string nodeTaintsPolicy: description: |- @@ -1492,7 +1483,6 @@ spec: has a toleration, are included. - Ignore: node taints are ignored. All nodes are included. If this value is nil, the behavior is equivalent to the Ignore policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string topologyKey: description: |- @@ -1538,206 +1528,15 @@ spec: type: object type: object type: object - logging: - properties: - apiServer: - properties: - logSeverity: - default: Info - description: LogSeverity defines log level for APIServer container. - enum: - - Fatal - - Error - - Warn - - Info - - Debug - - Trace - type: string - type: object - queryServer: - properties: - logSeverity: - default: Info - description: - LogSeverity defines log level for QueryServer - container. - enum: - - Fatal - - Error - - Warn - - Info - - Debug - - Trace - type: string - type: object - type: object - type: object - status: - description: Most recently observed status for the Tigera API server. - properties: - conditions: - description: |- - Conditions represents the latest observed set of conditions for the component. A component may be one or more of - Ready, Progressing, Degraded or other customer types. - items: - description: - Condition contains details for one aspect of the current - state of this API Resource. - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - state: - description: State provides user-readable status. - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} ---- -# Source: crds/operator.tigera.io_gatewayapis.yaml -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.17.3 - name: gatewayapis.operator.tigera.io -spec: - group: operator.tigera.io - names: - kind: GatewayAPI - listKind: GatewayAPIList - plural: gatewayapis - singular: gatewayapi - scope: Cluster - versions: - - name: v1 - schema: - openAPIV3Schema: - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: - GatewayAPISpec has fields that can be used to customize our - GatewayAPI support. - properties: - crdManagement: - description: |- - Configures how to manage and update Gateway API CRDs. The default behaviour - which is - used when this field is not set, or is set to "PreferExisting" - is that the Tigera - operator will create the Gateway API CRDs if they do not already exist, but will not - overwrite any existing Gateway API CRDs. This setting may be preferable if the customer - is using other implementations of the Gateway API concurrently with the Gateway API - support in Calico Enterprise. It is then the customer's responsibility to ensure that - CRDs are installed that meet the needs of all the Gateway API implementations in their - cluster. - Alternatively, if this field is set to "Reconcile", the Tigera operator will keep the - cluster's Gateway API CRDs aligned with those that it would install on a cluster that - does not yet have any version of those CRDs. - enum: - - Reconcile - - PreferExisting - type: string - envoyGatewayConfigRef: - description: |- - Reference to a custom EnvoyGateway YAML to use as the base EnvoyGateway configuration for - the gateway controller. When specified, must identify a ConfigMap resource with an - "envoy-gateway.yaml" key whose value is the desired EnvoyGateway YAML (i.e. following the - same pattern as the default `envoy-gateway-config` ConfigMap). - When not specified, the Tigera operator uses the `envoy-gateway-config` from the Envoy - Gateway helm chart as its base. - Starting from that base, the Tigera operator copies and modifies the EnvoyGateway - resource as follows: - 1. If not already specified, it sets the ControllerName to - "gateway.envoyproxy.io/gatewayclass-controller". - 2. It configures the `tigera/envoy-gateway` and `tigera/envoy-ratelimit` images that will - be used (according to the current Calico version, private registry and image set - settings) and any pull secrets that are needed to pull those images. - 3. It enables use of the Backend API. - The resulting EnvoyGateway is provisioned as the `envoy-gateway-config` ConfigMap (which - the gateway controller then uses as its config). - properties: - name: - type: string - namespace: - type: string - required: - - name - - namespace - type: object - gatewayCertgenJob: - description: Allows customization of the gateway certgen job. + calicoWebhooksDeployment: + description: + CalicoWebhooksDeployment configures the calico-webhooks + Deployment. properties: metadata: - description: |- - If non-nil, non-clashing labels and annotations from this metadata are added into the - job's top-level metadata. + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the Deployment. properties: annotations: additionalProperties: @@ -1758,18 +1557,28 @@ spec: type: object spec: description: - GatewayCertgenJobSpec allows customization of the - gateway certgen job spec. + Spec is the specification of the calico-webhooks + Deployment. properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-webhooks Deployment. + If omitted, the calico-webhooks Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer template: description: - GatewayCertgenJobPodTemplate allows customization - of the gateway certgen job's pod template. + Template describes the calico-webhooks Deployment + pod that will be created. properties: metadata: description: |- - If non-nil, non-clashing labels and annotations from this metadata are added into the - job's pod template. + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. properties: annotations: additionalProperties: @@ -1790,13 +1599,15 @@ spec: type: object spec: description: - GatewayCertgenJobPodSpec allows customization - of the gateway certgen job's pod spec. + Spec is the calico-webhooks Deployment's + PodSpec. properties: affinity: - description: - If non-nil, Affinity sets the affinity - field of the job's pod template. + description: |- + Affinity is a group of affinity scheduling rules for the calico-webhooks pods. + If specified, this overrides any affinity that may be set on the calico-webhooks Deployment. + If omitted, the calico-webhooks Deployment will use its default value for affinity. + WARNING: Please note that this field will override the default calico-webhooks Deployment affinity. properties: nodeAffinity: description: @@ -2106,7 +1917,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -2121,7 +1931,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -2295,7 +2104,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -2310,7 +2118,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -2408,8 +2215,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -2486,7 +2293,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -2501,7 +2307,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -2675,7 +2480,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -2690,7 +2494,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -2776,25 +2579,33 @@ spec: type: object type: object containers: + description: |- + Containers is a list of calico-webhooks containers. + If specified, this overrides the specified calico-webhooks Deployment containers. + If omitted, the calico-webhooks Deployment will use its default values for its containers. items: - description: |- - GatewayCertgenJobContainer allows customization of the gateway certgen job's resource - requirements. + description: + CalicoWebhooksDeploymentContainer is + a calico-webhooks Deployment container. properties: name: + description: |- + Name is an enum which identifies the calico-webhooks Deployment container by name. + Supported values are: calico-webhooks enum: - - envoy-gateway-certgen + - calico-webhooks type: string resources: description: |- - If non-nil, Resources sets the ResourceRequirements of the job's "envoy-gateway-certgen" - container. + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-webhooks Deployment container's resources. + If omitted, the calico-webhooks Deployment will use its default value for this container's resources. properties: claims: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -2853,14 +2664,22 @@ spec: nodeSelector: additionalProperties: type: string - description: - If non-nil, NodeSelector sets the node - selector for where job pods may be scheduled. + description: |- + NodeSelector is the calico-webhooks pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-webhooks Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If used in conjunction with ControlPlaneNodeSelector, that nodeSelector is set on the calico-webhooks Deployment + and each of this field's key/value pairs are added to the calico-webhooks Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-webhooks Deployment will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-webhooks Deployment nodeSelector. type: object tolerations: - description: - If non-nil, Tolerations sets the tolerations - field of the job's pod template. + description: |- + Tolerations is the calico-webhooks pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-webhooks Deployment. + If omitted, the calico-webhooks Deployment will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-webhooks Deployment tolerations. items: description: |- The pod this Toleration is attached to tolerates any taint that matches @@ -2902,962 +2721,1501 @@ spec: type: object type: object type: object - gatewayClasses: + logging: + properties: + apiServer: + properties: + logSeverity: + default: Info + description: LogSeverity defines log level for APIServer container. + enum: + - Fatal + - Error + - Warn + - Info + - Debug + - Trace + type: string + type: object + queryServer: + properties: + logSeverity: + default: Info + description: + LogSeverity defines log level for QueryServer + container. + enum: + - Fatal + - Error + - Warn + - Info + - Debug + - Trace + type: string + type: object + type: object + type: object + status: + description: Most recently observed status for the Tigera API server. + properties: + conditions: description: |- - Configures the GatewayClasses that will be available; please see GatewayClassSpec for - more detail. If GatewayClasses is nil, the Tigera operator defaults to provisioning a - single GatewayClass named "tigera-gateway-class", without any of the detailed - customizations that are allowed within GatewayClassSpec. + Conditions represents the latest observed set of conditions for the component. A component may be one or more of + Ready, Progressing, Degraded or other customer types. items: + description: + Condition contains details for one aspect of the current + state of this API Resource. properties: - envoyProxyRef: + lastTransitionTime: description: |- - Reference to a custom EnvoyProxy resource to use as the base EnvoyProxy configuration for - this GatewayClass. When specified, must identify an EnvoyProxy resource. - When not specified, the Tigera operator uses an empty EnvoyProxy resource as its base. - Starting from that base, the Tigera operator copies and modifies the EnvoyProxy resource - as follows, in the order described: - 1. It configures the `tigera/envoy-proxy` image that will be used (according to the - current Calico version, private registry and image set settings) and any pull secrets - that are needed to pull that image. - 2. It applies customizations as specified by the following `GatewayKind`, - `GatewayDeployment`, `GatewayDaemonSet` and `GatewayService` fields. - The resulting EnvoyProxy is provisioned in the `tigera-gateway` namespace, together with - a GatewayClass that references it. - If a custom EnvoyProxy resource is specified and uses `EnvoyDaemonSet` instead of the - default `EnvoyDeployment`, deployment-related customizations will be applied within - `EnvoyDaemonSet` instead of within `EnvoyDeployment`. - properties: - name: + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + state: + description: State provides user-readable status. + type: string + type: object + type: object + x-kubernetes-validations: + - message: resource name must be 'default' or 'tigera-secure' + rule: self.metadata.name == 'default' || self.metadata.name == 'tigera-secure' + served: true + storage: true + subresources: + status: {} +--- +# Source: crd.projectcalico.org.v1/templates/operator.tigera.io_gatewayapis.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: gatewayapis.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: GatewayAPI + listKind: GatewayAPIList + plural: gatewayapis + singular: gatewayapi + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: + GatewayAPISpec has fields that can be used to customize our + GatewayAPI support. + properties: + crdManagement: + description: |- + Configures how to manage and update Gateway API CRDs. The default behaviour - which is + used when this field is not set, or is set to "PreferExisting" - is that the Tigera + operator will create the Gateway API CRDs if they do not already exist, but will not + overwrite any existing Gateway API CRDs. This setting may be preferable if the customer + is using other implementations of the Gateway API concurrently with the Gateway API + support in Calico Enterprise. It is then the customer's responsibility to ensure that + CRDs are installed that meet the needs of all the Gateway API implementations in their + cluster. + Alternatively, if this field is set to "Reconcile", the Tigera operator will keep the + cluster's Gateway API CRDs aligned with those that it would install on a cluster that + does not yet have any version of those CRDs. + enum: + - Reconcile + - PreferExisting + type: string + envoyGatewayConfigRef: + description: |- + Reference to a custom EnvoyGateway YAML to use as the base EnvoyGateway configuration for + the gateway controller. When specified, must identify a ConfigMap resource with an + "envoy-gateway.yaml" key whose value is the desired EnvoyGateway YAML (i.e. following the + same pattern as the default `envoy-gateway-config` ConfigMap). + When not specified, the Tigera operator uses the `envoy-gateway-config` from the Envoy + Gateway helm chart as its base. + Starting from that base, the Tigera operator copies and modifies the EnvoyGateway + resource as follows: + 1. If not already specified, it sets the ControllerName to + "gateway.envoyproxy.io/gatewayclass-controller". + 2. It configures the `tigera/envoy-gateway` and `tigera/envoy-ratelimit` images that will + be used (according to the current Calico version, private registry and image set + settings) and any pull secrets that are needed to pull those images. + 3. It enables use of the Backend API. + The resulting EnvoyGateway is provisioned as the `envoy-gateway-config` ConfigMap (which + the gateway controller then uses as its config). + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + gatewayCertgenJob: + description: Allows customization of the gateway certgen job. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into the + job's top-level metadata. + properties: + annotations: + additionalProperties: type: string - namespace: + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: type: string - required: - - name - - namespace - type: object - gatewayDaemonSet: - description: |- - Allows customization of Gateways when deployed as Kubernetes DaemonSets, for Gateways in - this GatewayClass. - properties: - spec: - description: - GatewayDeploymentSpec allows customization - of the spec of gateway daemonsets. - properties: - template: - description: - GatewayDeploymentPodTemplate allows customization - of the pod template of gateway daemonsets. - properties: - metadata: - description: |- - If non-nil, non-clashing labels and annotations from this metadata are added into each - daemonset's pod template. - properties: - annotations: - additionalProperties: - type: string - description: |- - Annotations is a map of arbitrary non-identifying metadata. Each of these - key/value pairs are added to the object's annotations provided the key does not - already exist in the object's annotations. - type: object - labels: - additionalProperties: - type: string - description: |- - Labels is a map of string keys and values that may match replicaset and - service selectors. Each of these key/value pairs are added to the - object's labels provided the key does not already exist in the object's labels. - type: object - type: object - spec: - description: - GatewayDaemonSetPodSpec allows customization - of the pod spec of gateway daemonsets. - properties: - affinity: - description: - If non-nil, Affinity sets the affinity - field of the daemonset's pod template. - properties: - nodeAffinity: - description: - Describes node affinity scheduling - rules for the pod. + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + GatewayCertgenJobSpec allows customization of the + gateway certgen job spec. + properties: + template: + description: + GatewayCertgenJobPodTemplate allows customization + of the gateway certgen job's pod template. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into the + job's pod template. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + GatewayCertgenJobPodSpec allows customization + of the gateway certgen job's pod spec. + properties: + affinity: + description: + If non-nil, Affinity sets the affinity + field of the job's pod template. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node matches the corresponding matchExpressions; the - node(s) with the highest sum are the most preferred. - items: - description: |- - An empty preferred scheduling term matches all objects with implicit weight 0 - (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). - properties: - preference: - description: - A node selector term, - associated with the corresponding - weight. + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. properties: - matchExpressions: + key: description: - A list of node - selector requirements by - node's labels. + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: - The label - key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object + type: string type: array x-kubernetes-list-type: atomic - matchFields: + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: description: - A list of node - selector requirements by - node's fields. + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: - The label - key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object + type: string type: array x-kubernetes-list-type: atomic + required: + - key + - operator type: object - x-kubernetes-map-type: atomic - weight: - description: - Weight associated - with matching the corresponding - nodeSelectorTerm, in the range - 1-100. - format: int32 - type: integer - required: - - preference - - weight - type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: description: |- - If the affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to an update), the system - may or may not try to eventually evict the pod from its node. + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. properties: - nodeSelectorTerms: + matchExpressions: description: - Required. A list of - node selector terms. The terms - are ORed. + A list of node selector + requirements by node's labels. items: description: |- - A null or empty node selector term matches no objects. The requirements of - them are ANDed. - The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. properties: - matchExpressions: + key: description: - A list of node - selector requirements by - node's labels. + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: - The label - key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object + type: string type: array x-kubernetes-list-type: atomic - matchFields: + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: description: - A list of node - selector requirements by - node's fields. + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: - The label - key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object + type: string type: array x-kubernetes-list-type: atomic + required: + - key + - operator type: object - x-kubernetes-map-type: atomic type: array x-kubernetes-list-type: atomic - required: - - nodeSelectorTerms type: object x-kubernetes-map-type: atomic - type: object - podAffinity: + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: description: - Describes pod affinity scheduling - rules (e.g. co-locate this pod in the - same node, zone, etc. as some other pod(s)). + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the - node(s) with the highest sum are the most preferred. - items: - description: - The weights of all of - the matched WeightedPodAffinityTerm - fields are added per-node to find - the most preferred node(s) - properties: - podAffinityTerm: - description: - Required. A pod affinity - term, associated with the corresponding - weight. - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: - matchExpressions - is a list of label selector - requirements. The requirements - are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key - is the label key - that the selector - applies to. - type: string - operator: - description: - |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: - |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string type: array x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object + required: + - key + - operator type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. properties: - matchExpressions: + key: description: - matchExpressions - is a list of label selector - requirements. The requirements - are ANDed. + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key - is the label key - that the selector - applies to. - type: string - operator: - description: - |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: - |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object + type: string type: array x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object + required: + - key + - operator type: object - x-kubernetes-map-type: atomic - namespaces: + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. items: type: string type: array x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string required: - - topologyKey + - key + - operator type: object - weight: - description: |- - weight associated with matching the corresponding podAffinityTerm, - in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string type: array x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: + mismatchLabelKeys: description: |- - If the affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to a pod label update), the - system may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding to each - podAffinityTerm are intersected, i.e. all terms must be satisfied. + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. items: - description: |- - Defines a set of pods (namely those matching the labelSelector - relative to the given namespace(s)) that this pod should be - co-located (affinity) or not co-located (anti-affinity) with, - where co-located is defined as running on a node whose value of - the label with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. properties: - matchExpressions: + key: description: - matchExpressions - is a list of label selector - requirements. The requirements - are ANDed. + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is - the label key that - the selector applies - to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object + type: string type: array x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object + required: + - key + - operator type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: - matchExpressions - is a list of label selector - requirements. The requirements - are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is - the label key that - the selector applies - to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: type: string - required: - - topologyKey - type: object + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string type: array x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey type: object - podAntiAffinity: + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: description: - Describes pod anti-affinity - scheduling rules (e.g. avoid putting this - pod in the same node, zone, etc. as some - other pod(s)). + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the anti-affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the - node(s) with the highest sum are the most preferred. - items: - description: - The weights of all of - the matched WeightedPodAffinityTerm - fields are added per-node to find - the most preferred node(s) - properties: - podAffinityTerm: - description: - Required. A pod affinity - term, associated with the corresponding - weight. - properties: - labelSelector: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. properties: - matchExpressions: + key: description: - matchExpressions - is a list of label selector - requirements. The requirements - are ANDed. + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key - is the label key - that the selector - applies to. - type: string - operator: - description: - |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: - |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object + type: string type: array x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object + required: + - key + - operator type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. properties: - matchExpressions: + key: description: - matchExpressions - is a list of label selector - requirements. The requirements - are ANDed. + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key - is the label key - that the selector - applies to. - type: string - operator: - description: - |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: - |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object + type: string type: array x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object + required: + - key + - operator type: object - x-kubernetes-map-type: atomic - namespaces: + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. items: type: string type: array x-kubernetes-list-type: atomic - topologyKey: + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic required: - - topologyKey + - key + - operator type: object - weight: - description: |- - weight associated with matching the corresponding podAffinityTerm, - in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string type: array x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: + topologyKey: description: |- - If the anti-affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the anti-affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to a pod label update), the - system may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding to each - podAffinityTerm are intersected, i.e. all terms must be satisfied. + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + items: + description: |- + GatewayCertgenJobContainer allows customization of the gateway certgen job's resource + requirements. + properties: + name: + enum: + - envoy-gateway-certgen + type: string + resources: + description: |- + If non-nil, Resources sets the ResourceRequirements of the job's "envoy-gateway-certgen" + container. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: + If non-nil, NodeSelector sets the node + selector for where job pods may be scheduled. + type: object + tolerations: + description: + If non-nil, Tolerations sets the tolerations + field of the job's pod template. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + gatewayClasses: + description: |- + Configures the GatewayClasses that will be available; please see GatewayClassSpec for + more detail. If GatewayClasses is nil, the Tigera operator defaults to provisioning a + single GatewayClass named "tigera-gateway-class", without any of the detailed + customizations that are allowed within GatewayClassSpec. + items: + properties: + envoyProxyRef: + description: |- + Reference to a custom EnvoyProxy resource to use as the base EnvoyProxy configuration for + this GatewayClass. When specified, must identify an EnvoyProxy resource. + When not specified, the Tigera operator uses an empty EnvoyProxy resource as its base. + Starting from that base, the Tigera operator copies and modifies the EnvoyProxy resource + as follows, in the order described: + 1. It configures the `tigera/envoy-proxy` image that will be used (according to the + current Calico version, private registry and image set settings) and any pull secrets + that are needed to pull that image. + 2. It applies customizations as specified by the following `GatewayKind`, + `GatewayDeployment`, `GatewayDaemonSet` and `GatewayService` fields. + The resulting EnvoyProxy is provisioned in the `tigera-gateway` namespace, together with + a GatewayClass that references it. + If a custom EnvoyProxy resource is specified and uses `EnvoyDaemonSet` instead of the + default `EnvoyDeployment`, deployment-related customizations will be applied within + `EnvoyDaemonSet` instead of within `EnvoyDeployment`. + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + gatewayDaemonSet: + description: |- + Allows customization of Gateways when deployed as Kubernetes DaemonSets, for Gateways in + this GatewayClass. + properties: + spec: + description: + GatewayDeploymentSpec allows customization + of the spec of gateway daemonsets. + properties: + template: + description: + GatewayDeploymentPodTemplate allows customization + of the pod template of gateway daemonsets. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into each + daemonset's pod template. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + GatewayDaemonSetPodSpec allows customization + of the pod spec of gateway daemonsets. + properties: + affinity: + description: + If non-nil, Affinity sets the affinity + field of the daemonset's pod template. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. items: description: |- - Defines a set of pods (namely those matching the labelSelector - relative to the given namespace(s)) that this pod should be - co-located (affinity) or not co-located (anti-affinity) with, - where co-located is defined as running on a node whose value of - the label with key matches that of any node on which - a pod of the set of pods is running + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. + preference: + description: + A node selector term, + associated with the corresponding + weight. properties: matchExpressions: description: - matchExpressions - is a list of label selector - requirements. The requirements - are ANDed. + A list of node + selector requirements by + node's labels. items: description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. properties: key: description: - key is - the label key that - the selector applies - to. + The label + key that the selector + applies to. type: string operator: description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. type: string values: description: |- - values is an array of string values. If the operator is In or NotIn, + An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. items: type: string type: array @@ -3868,83 +4226,34 @@ spec: type: object type: array x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: + matchFields: description: - matchExpressions - is a list of label selector - requirements. The requirements - are ANDed. + A list of node + selector requirements by + node's fields. items: description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. properties: key: description: - key is - the label key that - the selector applies - to. + The label + key that the selector + applies to. type: string operator: description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. type: string values: description: |- - values is an array of string values. If the operator is In or NotIn, + An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. items: type: string type: array @@ -3955,474 +4264,40 @@ spec: type: object type: array x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object type: object x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string + weight: + description: + Weight associated + with matching the corresponding + nodeSelectorTerm, in the range + 1-100. + format: int32 + type: integer required: - - topologyKey + - preference + - weight type: object type: array x-kubernetes-list-type: atomic - type: object - type: object - containers: - items: - description: |- - GatewayDaemonSetContainer allows customization of the resource requirements of gateway - daemonsets. - properties: - name: - enum: - - envoy - type: string - resources: - description: |- - If non-nil, Resources sets the ResourceRequirements of the daemonset's "envoy" - container. - properties: - claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - This field is immutable. It can only be set for containers. - items: - description: - ResourceClaim references - one entry in PodSpec.ResourceClaims. - properties: - name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. - type: string - request: - description: |- - Request is the name chosen for a request in the referenced claim. - If empty, everything from the claim is made available, otherwise - only the result of this request. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - type: object - required: - - name - type: object - type: array - nodeSelector: - additionalProperties: - type: string - description: |- - If non-nil, NodeSelector sets the node selector for where daemonset pods may be - scheduled. - type: object - tolerations: - description: - If non-nil, Tolerations sets the - tolerations field of the daemonset's pod template. - items: - description: |- - The pod this Toleration is attached to tolerates any taint that matches - the triple using the matching operator . - properties: - effect: - description: |- - Effect indicates the taint effect to match. Empty means match all taint effects. - When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: |- - Key is the taint key that the toleration applies to. Empty means match all taint keys. - If the key is empty, operator must be Exists; this combination means to match all values and all keys. - type: string - operator: - description: |- - Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. - Exists is equivalent to wildcard for value, so that a pod can - tolerate all taints of a particular category. - type: string - tolerationSeconds: - description: |- - TolerationSeconds represents the period of time the toleration (which must be - of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, - it is not set, which means tolerate the taint forever (do not evict). Zero and - negative values will be treated as 0 (evict immediately) by the system. - format: int64 - type: integer - value: - description: |- - Value is the taint value the toleration matches to. - If the operator is Exists, the value should be empty, otherwise just a regular string. - type: string - type: object - type: array - topologySpreadConstraints: - description: |- - If non-nil, TopologySpreadConstraints sets the topology spread constraints of the - daemonset's pod template. TopologySpreadConstraints describes how a group of pods ought - to spread across topology domains. Scheduler will schedule pods in a way which abides by - the constraints. All topologySpreadConstraints are ANDed. - items: - description: - TopologySpreadConstraint specifies - how to spread matching pods among the given - topology. - properties: - labelSelector: - description: |- - LabelSelector is used to find matching pods. - Pods that match this label selector are counted to determine the number of pods - in their corresponding topology domain. - properties: - matchExpressions: - description: - matchExpressions is a - list of label selector requirements. - The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label - key that the selector applies - to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select the pods over which - spreading will be calculated. The keys are used to lookup values from the - incoming pod labels, those key-value labels are ANDed with labelSelector - to select the group of existing pods over which spreading will be calculated - for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. - MatchLabelKeys cannot be set when LabelSelector isn't set. - Keys that don't exist in the incoming pod labels will - be ignored. A null or empty list means only match against labelSelector. - This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). - items: - type: string - type: array - x-kubernetes-list-type: atomic - maxSkew: - description: |- - MaxSkew describes the degree to which pods may be unevenly distributed. - When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference - between the number of matching pods in the target topology and the global minimum. - The global minimum is the minimum number of matching pods in an eligible domain - or zero if the number of eligible domains is less than MinDomains. - For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same - labelSelector spread as 2/2/1: - In this case, the global minimum is 1. - | zone1 | zone2 | zone3 | - | P P | P P | P | - - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; - scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) - violate MaxSkew(1). - - if MaxSkew is 2, incoming pod can be scheduled onto any zone. - When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence - to topologies that satisfy it. - It's a required field. Default value is 1 and 0 is not allowed. - format: int32 - type: integer - minDomains: - description: |- - MinDomains indicates a minimum number of eligible domains. - When the number of eligible domains with matching topology keys is less than minDomains, - Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. - And when the number of eligible domains with matching topology keys equals or greater than minDomains, - this value has no effect on scheduling. - As a result, when the number of eligible domains is less than minDomains, - scheduler won't schedule more than maxSkew Pods to those domains. - If value is nil, the constraint behaves as if MinDomains is equal to 1. - Valid values are integers greater than 0. - When value is not nil, WhenUnsatisfiable must be DoNotSchedule. - For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same - labelSelector spread as 2/2/2: - | zone1 | zone2 | zone3 | - | P P | P P | P P | - The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. - In this situation, new pod with the same labelSelector cannot be scheduled, - because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, - it will violate MaxSkew. - format: int32 - type: integer - nodeAffinityPolicy: - description: |- - NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector - when calculating pod topology spread skew. Options are: - - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. - If this value is nil, the behavior is equivalent to the Honor policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. - type: string - nodeTaintsPolicy: - description: |- - NodeTaintsPolicy indicates how we will treat node taints when calculating - pod topology spread skew. Options are: - - Honor: nodes without taints, along with tainted nodes for which the incoming pod - has a toleration, are included. - - Ignore: node taints are ignored. All nodes are included. - If this value is nil, the behavior is equivalent to the Ignore policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. - type: string - topologyKey: - description: |- - TopologyKey is the key of node labels. Nodes that have a label with this key - and identical values are considered to be in the same topology. - We consider each as a "bucket", and try to put balanced number - of pods into each bucket. - We define a domain as a particular instance of a topology. - Also, we define an eligible domain as a domain whose nodes meet the requirements of - nodeAffinityPolicy and nodeTaintsPolicy. - e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. - And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. - It's a required field. - type: string - whenUnsatisfiable: - description: |- - WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy - the spread constraint. - - DoNotSchedule (default) tells the scheduler not to schedule it. - - ScheduleAnyway tells the scheduler to schedule the pod in any location, - but giving higher precedence to topologies that would help reduce the - skew. - A constraint is considered "Unsatisfiable" for an incoming pod - if and only if every possible node assignment for that pod would violate - "MaxSkew" on some topology. - For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same - labelSelector spread as 3/1/1: - | zone1 | zone2 | zone3 | - | P P P | P | P | - If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled - to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies - MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler - won't make it *more* imbalanced. - It's a required field. - type: string - required: - - maxSkew - - topologyKey - - whenUnsatisfiable - type: object - type: array - type: object - type: object - type: object - type: object - gatewayDeployment: - description: |- - Allows customization of Gateways when deployed as Kubernetes Deployments, for Gateways in - this GatewayClass. - properties: - spec: - description: - GatewayDeploymentSpec allows customization - of the spec of gateway deployments. - properties: - replicas: - description: - If non-nil, Replicas sets the number of - replicas for the deployment. - format: int32 - type: integer - strategy: - description: - The deployment strategy to use to replace - existing pods with new ones. - properties: - rollingUpdate: - description: - Spec to control the desired behavior - of rolling update. - properties: - maxSurge: - anyOf: - - type: integer - - type: string - description: |- - The maximum number of pods that can be scheduled above the desired number of - pods. - Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). - This can not be 0 if MaxUnavailable is 0. - Absolute number is calculated from percentage by rounding up. - Defaults to 25%. - Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when - the rolling update starts, such that the total number of old and new pods do not exceed - 130% of desired pods. Once old pods have been killed, - new ReplicaSet can be scaled up further, ensuring that total number of pods running - at any time during the update is at most 130% of desired pods. - x-kubernetes-int-or-string: true - maxUnavailable: - anyOf: - - type: integer - - type: string - description: |- - The maximum number of pods that can be unavailable during the update. - Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). - Absolute number is calculated from percentage by rounding down. - This can not be 0 if MaxSurge is 0. - Defaults to 25%. - Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods - immediately when the rolling update starts. Once new pods are ready, old ReplicaSet - can be scaled down further, followed by scaling up the new ReplicaSet, ensuring - that the total number of pods available at all times during the update is at - least 70% of desired pods. - x-kubernetes-int-or-string: true - type: object - type: object - template: - description: - GatewayDeploymentPodTemplate allows customization - of the pod template of gateway deployments. - properties: - metadata: - description: |- - If non-nil, non-clashing labels and annotations from this metadata are added into each - deployment's pod template. - properties: - annotations: - additionalProperties: - type: string - description: |- - Annotations is a map of arbitrary non-identifying metadata. Each of these - key/value pairs are added to the object's annotations provided the key does not - already exist in the object's annotations. - type: object - labels: - additionalProperties: - type: string - description: |- - Labels is a map of string keys and values that may match replicaset and - service selectors. Each of these key/value pairs are added to the - object's labels provided the key does not already exist in the object's labels. - type: object - type: object - spec: - description: - GatewayDeploymentPodSpec allows customization - of the pod spec of gateway deployments. - properties: - affinity: - description: - If non-nil, Affinity sets the affinity - field of the deployment's pod template. - properties: - nodeAffinity: - description: - Describes node affinity scheduling - rules for the pod. - properties: - preferredDuringSchedulingIgnoredDuringExecution: + requiredDuringSchedulingIgnoredDuringExecution: description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node matches the corresponding matchExpressions; the - node(s) with the highest sum are the most preferred. - items: - description: |- - An empty preferred scheduling term matches all objects with implicit weight 0 - (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). - properties: - preference: - description: - A node selector term, - associated with the corresponding - weight. + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of + node selector terms. The terms + are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. properties: matchExpressions: description: @@ -4502,131 +4377,20 @@ spec: x-kubernetes-list-type: atomic type: object x-kubernetes-map-type: atomic - weight: - description: - Weight associated - with matching the corresponding - nodeSelectorTerm, in the range - 1-100. - format: int32 - type: integer - required: - - preference - - weight - type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - If the affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to an update), the system - may or may not try to eventually evict the pod from its node. - properties: - nodeSelectorTerms: - description: - Required. A list of - node selector terms. The terms - are ORed. - items: - description: |- - A null or empty node selector term matches no objects. The requirements of - them are ANDed. - The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. - properties: - matchExpressions: - description: - A list of node - selector requirements by - node's labels. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: - The label - key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchFields: - description: - A list of node - selector requirements by - node's fields. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: - The label - key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - type: object - x-kubernetes-map-type: atomic - type: array - x-kubernetes-list-type: atomic - required: - - nodeSelectorTerms - type: object - x-kubernetes-map-type: atomic - type: object - podAffinity: - description: - Describes pod affinity scheduling - rules (e.g. co-locate this pod in the - same node, zone, etc. as some other pod(s)). - properties: - preferredDuringSchedulingIgnoredDuringExecution: + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: description: |- The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose @@ -4716,7 +4480,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -4731,7 +4494,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -4910,7 +4672,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -4925,7 +4686,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -5026,8 +4786,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -5108,7 +4868,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -5123,7 +4882,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -5302,7 +5060,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -5317,7 +5074,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -5407,8 +5163,8 @@ spec: containers: items: description: |- - GatewayDeploymentContainer allows customization of the resource requirements of gateway - deployments. + GatewayDaemonSetContainer allows customization of the resource requirements of gateway + daemonsets. properties: name: enum: @@ -5416,14 +5172,14 @@ spec: type: string resources: description: |- - If non-nil, Resources sets the ResourceRequirements of the deployment's "envoy" + If non-nil, Resources sets the ResourceRequirements of the daemonset's "envoy" container. properties: claims: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -5483,14 +5239,13 @@ spec: additionalProperties: type: string description: |- - If non-nil, NodeSelector sets the node selector for where deployment pods may be + If non-nil, NodeSelector sets the node selector for where daemonset pods may be scheduled. type: object tolerations: description: If non-nil, Tolerations sets the - tolerations field of the deployment's pod - template. + tolerations field of the daemonset's pod template. items: description: |- The pod this Toleration is attached to tolerates any taint that matches @@ -5531,7 +5286,7 @@ spec: topologySpreadConstraints: description: |- If non-nil, TopologySpreadConstraints sets the topology spread constraints of the - deployment's pod template. TopologySpreadConstraints describes how a group of pods ought + daemonset's pod template. TopologySpreadConstraints describes how a group of pods ought to spread across topology domains. Scheduler will schedule pods in a way which abides by the constraints. All topologySpreadConstraints are ANDed. items: @@ -5658,7 +5413,6 @@ spec: - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. If this value is nil, the behavior is equivalent to the Honor policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string nodeTaintsPolicy: description: |- @@ -5668,7 +5422,6 @@ spec: has a toleration, are included. - Ignore: node taints are ignored. All nodes are included. If this value is nil, the behavior is equivalent to the Ignore policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string topologyKey: description: |- @@ -5714,1487 +5467,1481 @@ spec: type: object type: object type: object - gatewayKind: + gatewayDeployment: description: |- - Specifies whether Gateways in this class are deployed as Deployments (default) or as - DaemonSets. It is an error for GatewayKind to specify a choice that is incompatible with - the custom EnvoyProxy, when EnvoyProxyRef is also specified. - enum: - - Deployment - - DaemonSet - type: string - gatewayService: - description: - Allows customization of gateway services, for Gateways - in this GatewayClass. + Allows customization of Gateways when deployed as Kubernetes Deployments, for Gateways in + this GatewayClass. properties: - metadata: - description: |- - If non-nil, non-clashing labels and annotations from this metadata are added into the - each Gateway Service's metadata. - properties: - annotations: - additionalProperties: - type: string - description: |- - Annotations is a map of arbitrary non-identifying metadata. Each of these - key/value pairs are added to the object's annotations provided the key does not - already exist in the object's annotations. - type: object - labels: - additionalProperties: - type: string - description: |- - Labels is a map of string keys and values that may match replicaset and - service selectors. Each of these key/value pairs are added to the - object's labels provided the key does not already exist in the object's labels. - type: object - type: object spec: - description: |- - GatewayServiceSpec allows customization of the services that front gateway deployments. - The LoadBalancer fields allow customization of the corresponding fields in the Kubernetes - ServiceSpec. These can be used for some cloud-independent control of the external load balancer - that is provisioned for each Gateway. For finer-grained cloud-specific control please use - the Metadata.Annotations field in GatewayService. + description: + GatewayDeploymentSpec allows customization + of the spec of gateway deployments. properties: - allocateLoadBalancerNodePorts: - type: boolean - loadBalancerClass: - type: string - loadBalancerIP: - type: string - loadBalancerSourceRanges: - items: - type: string - type: array - type: object - type: object - name: - description: The name of this GatewayClass. - type: string - required: - - name - type: object - type: array - gatewayControllerDeployment: - description: Allows customization of the gateway controller deployment. - properties: - metadata: - description: |- - If non-nil, non-clashing labels and annotations from this metadata are added into the - deployment's top-level metadata. - properties: - annotations: - additionalProperties: - type: string - description: |- - Annotations is a map of arbitrary non-identifying metadata. Each of these - key/value pairs are added to the object's annotations provided the key does not - already exist in the object's annotations. - type: object - labels: - additionalProperties: - type: string - description: |- - Labels is a map of string keys and values that may match replicaset and - service selectors. Each of these key/value pairs are added to the - object's labels provided the key does not already exist in the object's labels. - type: object - type: object - spec: - description: - GatewayControllerDeploymentSpec allows customization - of the gateway controller deployment spec. - properties: - minReadySeconds: - description: - If non-nil, MinReadySeconds sets the minReadySeconds - field for the deployment. - format: int32 - maximum: 2147483647 - minimum: 0 - type: integer - replicas: - description: - If non-nil, Replicas sets the number of replicas - for the deployment. - format: int32 - type: integer - template: - description: |- - GatewayControllerDeploymentPodTemplate allows customization of the gateway controller deployment - pod template. - properties: - metadata: - description: |- - If non-nil, non-clashing labels and annotations from this metadata are added into the - deployment's pod template. - properties: - annotations: - additionalProperties: - type: string - description: |- - Annotations is a map of arbitrary non-identifying metadata. Each of these - key/value pairs are added to the object's annotations provided the key does not - already exist in the object's annotations. - type: object - labels: - additionalProperties: - type: string - description: |- - Labels is a map of string keys and values that may match replicaset and - service selectors. Each of these key/value pairs are added to the - object's labels provided the key does not already exist in the object's labels. - type: object - type: object - spec: - description: |- - GatewayControllerDeploymentPodSpec allows customization of the gateway controller deployment pod - spec. - properties: - affinity: - description: - If non-nil, Affinity sets the affinity - field of the deployment's pod template. - properties: - nodeAffinity: - description: - Describes node affinity scheduling - rules for the pod. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node matches the corresponding matchExpressions; the - node(s) with the highest sum are the most preferred. - items: - description: |- - An empty preferred scheduling term matches all objects with implicit weight 0 - (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + replicas: + description: + If non-nil, Replicas sets the number of + replicas for the deployment. + format: int32 + type: integer + strategy: + description: + The deployment strategy to use to replace + existing pods with new ones. + properties: + rollingUpdate: + description: + Spec to control the desired behavior + of rolling update. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: object + template: + description: + GatewayDeploymentPodTemplate allows customization + of the pod template of gateway deployments. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into each + deployment's pod template. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + GatewayDeploymentPodSpec allows customization + of the pod spec of gateway deployments. + properties: + affinity: + description: + If non-nil, Affinity sets the affinity + field of the deployment's pod template. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. properties: - preference: - description: - A node selector term, associated - with the corresponding weight. - properties: - matchExpressions: - description: - A list of node selector - requirements by node's labels. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. properties: - key: + matchExpressions: description: - The label key - that the selector applies - to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. + A list of node + selector requirements by + node's labels. items: - type: string + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object type: array x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchFields: - description: - A list of node selector - requirements by node's fields. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: + matchFields: description: - The label key - that the selector applies - to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. + A list of node + selector requirements by + node's fields. items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - type: object - x-kubernetes-map-type: atomic - weight: - description: - Weight associated with - matching the corresponding nodeSelectorTerm, - in the range 1-100. - format: int32 - type: integer - required: - - preference - - weight - type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - If the affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to an update), the system - may or may not try to eventually evict the pod from its node. - properties: - nodeSelectorTerms: - description: - Required. A list of node - selector terms. The terms are ORed. - items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated + with matching the corresponding + nodeSelectorTerm, in the range + 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: description: |- - A null or empty node selector term matches no objects. The requirements of - them are ANDed. - The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. properties: - matchExpressions: + nodeSelectorTerms: description: - A list of node selector - requirements by node's labels. + Required. A list of + node selector terms. The terms + are ORed. items: description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. properties: - key: + matchExpressions: description: - The label key - that the selector applies - to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. + A list of node + selector requirements by + node's labels. items: - type: string + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object type: array x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchFields: - description: - A list of node selector - requirements by node's fields. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: + matchFields: description: - The label key - that the selector applies - to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. + A list of node + selector requirements by + node's fields. items: - type: string + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object type: array x-kubernetes-list-type: atomic - required: - - key - - operator type: object + x-kubernetes-map-type: atomic type: array x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms type: object x-kubernetes-map-type: atomic - type: array - x-kubernetes-list-type: atomic - required: - - nodeSelectorTerms - type: object - x-kubernetes-map-type: atomic - type: object - podAffinity: - description: - Describes pod affinity scheduling - rules (e.g. co-locate this pod in the same node, - zone, etc. as some other pod(s)). - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the - node(s) with the highest sum are the most preferred. - items: + type: object + podAffinity: description: - The weights of all of the matched - WeightedPodAffinityTerm fields are added - per-node to find the most preferred node(s) + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the + same node, zone, etc. as some other pod(s)). properties: - podAffinityTerm: - description: - Required. A pod affinity - term, associated with the corresponding - weight. - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: - matchExpressions - is a list of label selector - requirements. The requirements - are ANDed. - items: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of + the matched WeightedPodAffinityTerm + fields are added per-node to find + the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. properties: - key: + matchExpressions: description: - key is the - label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. items: - type: string + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object type: array x-kubernetes-list-type: atomic - required: - - key - - operator + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: - matchExpressions - is a list of label selector - requirements. The requirements - are ANDed. - items: + x-kubernetes-map-type: atomic + matchLabelKeys: description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. properties: - key: + matchExpressions: description: - key is the - label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. items: - type: string + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object type: array x-kubernetes-list-type: atomic - required: - - key - - operator + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: - description: |- - weight associated with matching the corresponding podAffinityTerm, - in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - If the affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to a pod label update), the - system may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding to each - podAffinityTerm are intersected, i.e. all terms must be satisfied. - items: - description: |- - Defines a set of pods (namely those matching the labelSelector - relative to the given namespace(s)) that this pod should be - co-located (affinity) or not co-located (anti-affinity) with, - where co-located is defined as running on a node whose value of - the label with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: - matchExpressions is - a list of label selector requirements. - The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label - key that the selector applies - to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: + x-kubernetes-map-type: atomic + namespaces: description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". items: type: string type: array x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string required: - - key - - operator + - topologyKey type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). - items: - type: string + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object type: array x-kubernetes-list-type: atomic - mismatchLabelKeys: + requiredDuringSchedulingIgnoredDuringExecution: description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: - matchExpressions is - a list of label selector requirements. - The requirements are ANDed. - items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. properties: - key: + matchExpressions: description: - key is the label - key that the selector applies - to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object type: array x-kubernetes-list-type: atomic - required: - - key - - operator + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string + required: + - topologyKey + type: object type: array x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey type: object - type: array - x-kubernetes-list-type: atomic - type: object - podAntiAffinity: - description: - Describes pod anti-affinity scheduling - rules (e.g. avoid putting this pod in the same - node, zone, etc. as some other pod(s)). - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the anti-affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the - node(s) with the highest sum are the most preferred. - items: + podAntiAffinity: description: - The weights of all of the matched - WeightedPodAffinityTerm fields are added - per-node to find the most preferred node(s) + Describes pod anti-affinity + scheduling rules (e.g. avoid putting this + pod in the same node, zone, etc. as some + other pod(s)). properties: - podAffinityTerm: - description: - Required. A pod affinity - term, associated with the corresponding - weight. - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: - matchExpressions - is a list of label selector - requirements. The requirements - are ANDed. - items: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of + the matched WeightedPodAffinityTerm + fields are added per-node to find + the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. properties: - key: + matchExpressions: description: - key is the - label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. items: - type: string + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object type: array x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: - matchExpressions - is a list of label selector - requirements. The requirements - are ANDed. - items: + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. properties: - key: + matchExpressions: description: - key is the - label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. items: - type: string + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object type: array x-kubernetes-list-type: atomic - required: - - key - - operator + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: - description: |- - weight associated with matching the corresponding podAffinityTerm, - in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight - type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - If the anti-affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the anti-affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to a pod label update), the - system may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding to each - podAffinityTerm are intersected, i.e. all terms must be satisfied. - items: - description: |- - Defines a set of pods (namely those matching the labelSelector - relative to the given namespace(s)) that this pod should be - co-located (affinity) or not co-located (anti-affinity) with, - where co-located is defined as running on a node whose value of - the label with key matches that of any node on which - a pod of the set of pods is running - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: - matchExpressions is - a list of label selector requirements. - The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label - key that the selector applies - to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: + x-kubernetes-map-type: atomic + namespaces: description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". items: type: string type: array x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string required: - - key - - operator + - topologyKey type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). - items: - type: string + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object type: array x-kubernetes-list-type: atomic - mismatchLabelKeys: + requiredDuringSchedulingIgnoredDuringExecution: description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: - matchExpressions is - a list of label selector requirements. - The requirements are ANDed. - items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. properties: - key: + matchExpressions: description: - key is the label - key that the selector applies - to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object type: array x-kubernetes-list-type: atomic - required: - - key - - operator + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object type: array x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey type: object - type: array - x-kubernetes-list-type: atomic - type: object - type: object - containers: - items: - description: |- - GatewayControllerDeploymentContainer allows customization of the gateway controller's resource - requirements. - properties: - name: - enum: - - envoy-gateway - type: string - resources: - description: |- - If non-nil, Resources sets the ResourceRequirements of the controller's "envoy-gateway" - container. - properties: - claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - This field is immutable. It can only be set for containers. - items: - description: - ResourceClaim references - one entry in PodSpec.ResourceClaims. + type: object + containers: + items: + description: |- + GatewayDeploymentContainer allows customization of the resource requirements of gateway + deployments. + properties: + name: + enum: + - envoy + type: string + resources: + description: |- + If non-nil, Resources sets the ResourceRequirements of the deployment's "envoy" + container. properties: - name: + claims: description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. - type: string - request: + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true description: |- - Request is the name chosen for a request in the referenced claim. - If empty, everything from the claim is made available, otherwise - only the result of this request. - type: string - required: - - name + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - type: object - required: - - name - type: object - type: array - nodeSelector: - additionalProperties: - type: string - description: |- - If non-nil, NodeSelector sets the node selector for where deployment pods may be - scheduled. - type: object - tolerations: - description: - If non-nil, Tolerations sets the tolerations - field of the deployment's pod template. - items: - description: |- - The pod this Toleration is attached to tolerates any taint that matches - the triple using the matching operator . - properties: - effect: - description: |- - Effect indicates the taint effect to match. Empty means match all taint effects. - When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: |- - Key is the taint key that the toleration applies to. Empty means match all taint keys. - If the key is empty, operator must be Exists; this combination means to match all values and all keys. - type: string - operator: - description: |- - Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. - Exists is equivalent to wildcard for value, so that a pod can - tolerate all taints of a particular category. - type: string - tolerationSeconds: - description: |- - TolerationSeconds represents the period of time the toleration (which must be - of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, - it is not set, which means tolerate the taint forever (do not evict). Zero and - negative values will be treated as 0 (evict immediately) by the system. - format: int64 - type: integer - value: + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string description: |- - Value is the taint value the toleration matches to. - If the operator is Exists, the value should be empty, otherwise just a regular string. - type: string - type: object - type: array - topologySpreadConstraints: - description: |- - If non-nil, TopologySpreadConstraints sets the topology spread constraints of the - deployment's pod template. TopologySpreadConstraints describes how a group of pods ought - to spread across topology domains. Scheduler will schedule pods in a way which abides by - the constraints. All topologySpreadConstraints are ANDed. - items: - description: - TopologySpreadConstraint specifies - how to spread matching pods among the given topology. - properties: - labelSelector: + If non-nil, NodeSelector sets the node selector for where deployment pods may be + scheduled. + type: object + tolerations: + description: + If non-nil, Tolerations sets the + tolerations field of the deployment's pod + template. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: description: |- - LabelSelector is used to find matching pods. - Pods that match this label selector are counted to determine the number of pods - in their corresponding topology domain. - properties: - matchExpressions: - description: - matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: + If non-nil, TopologySpreadConstraints sets the topology spread constraints of the + deployment's pod template. TopologySpreadConstraints describes how a group of pods ought + to spread across topology domains. Scheduler will schedule pods in a way which abides by + the constraints. All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given + topology. + properties: + labelSelector: description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. properties: - key: + matchExpressions: description: - key is the label key - that the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. + matchExpressions is a + list of label selector requirements. + The requirements are ANDed. items: - type: string + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object type: array x-kubernetes-list-type: atomic - required: - - key - - operator + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select the pods over which - spreading will be calculated. The keys are used to lookup values from the - incoming pod labels, those key-value labels are ANDed with labelSelector - to select the group of existing pods over which spreading will be calculated - for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. - MatchLabelKeys cannot be set when LabelSelector isn't set. - Keys that don't exist in the incoming pod labels will - be ignored. A null or empty list means only match against labelSelector. - This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). - items: - type: string - type: array - x-kubernetes-list-type: atomic - maxSkew: - description: |- - MaxSkew describes the degree to which pods may be unevenly distributed. - When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference - between the number of matching pods in the target topology and the global minimum. - The global minimum is the minimum number of matching pods in an eligible domain - or zero if the number of eligible domains is less than MinDomains. - For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same - labelSelector spread as 2/2/1: - In this case, the global minimum is 1. - | zone1 | zone2 | zone3 | - | P P | P P | P | - - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; - scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) - violate MaxSkew(1). - - if MaxSkew is 2, incoming pod can be scheduled onto any zone. - When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence - to topologies that satisfy it. - It's a required field. Default value is 1 and 0 is not allowed. - format: int32 - type: integer - minDomains: - description: |- - MinDomains indicates a minimum number of eligible domains. - When the number of eligible domains with matching topology keys is less than minDomains, - Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. - And when the number of eligible domains with matching topology keys equals or greater than minDomains, - this value has no effect on scheduling. - As a result, when the number of eligible domains is less than minDomains, - scheduler won't schedule more than maxSkew Pods to those domains. - If value is nil, the constraint behaves as if MinDomains is equal to 1. - Valid values are integers greater than 0. - When value is not nil, WhenUnsatisfiable must be DoNotSchedule. - For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same - labelSelector spread as 2/2/2: - | zone1 | zone2 | zone3 | - | P P | P P | P P | - The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. - In this situation, new pod with the same labelSelector cannot be scheduled, - because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, - it will violate MaxSkew. - format: int32 - type: integer - nodeAffinityPolicy: - description: |- - NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector - when calculating pod topology spread skew. Options are: - - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. - If this value is nil, the behavior is equivalent to the Honor policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. - type: string - nodeTaintsPolicy: - description: |- - NodeTaintsPolicy indicates how we will treat node taints when calculating - pod topology spread skew. Options are: - - Honor: nodes without taints, along with tainted nodes for which the incoming pod - has a toleration, are included. - - Ignore: node taints are ignored. All nodes are included. - If this value is nil, the behavior is equivalent to the Ignore policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. - type: string - topologyKey: - description: |- - TopologyKey is the key of node labels. Nodes that have a label with this key - and identical values are considered to be in the same topology. - We consider each as a "bucket", and try to put balanced number - of pods into each bucket. - We define a domain as a particular instance of a topology. - Also, we define an eligible domain as a domain whose nodes meet the requirements of - nodeAffinityPolicy and nodeTaintsPolicy. - e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. - And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. - It's a required field. - type: string - whenUnsatisfiable: - description: |- - WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy - the spread constraint. - - DoNotSchedule (default) tells the scheduler not to schedule it. - - ScheduleAnyway tells the scheduler to schedule the pod in any location, - but giving higher precedence to topologies that would help reduce the - skew. - A constraint is considered "Unsatisfiable" for an incoming pod - if and only if every possible node assignment for that pod would violate - "MaxSkew" on some topology. - For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same - labelSelector spread as 3/1/1: - | zone1 | zone2 | zone3 | - | P P P | P | P | - If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled - to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies - MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler - won't make it *more* imbalanced. - It's a required field. - type: string - required: - - maxSkew - - topologyKey - - whenUnsatisfiable + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array type: object - type: array - type: object - type: object - type: object - type: object - type: object - type: object - served: true - storage: true ---- -# Source: crds/operator.tigera.io_goldmanes.yaml -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.17.3 - name: goldmanes.operator.tigera.io -spec: - group: operator.tigera.io - names: - kind: Goldmane - listKind: GoldmaneList - plural: goldmanes - singular: goldmane - scope: Cluster - versions: - - name: v1 - schema: - openAPIV3Schema: - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - properties: - goldmaneDeployment: - description: - GoldmaneDeployment is the configuration for the goldmane - Deployment. + type: object + type: object + type: object + gatewayKind: + description: |- + Specifies whether Gateways in this class are deployed as Deployments (default) or as + DaemonSets. It is an error for GatewayKind to specify a choice that is incompatible with + the custom EnvoyProxy, when EnvoyProxyRef is also specified. + enum: + - Deployment + - DaemonSet + type: string + gatewayService: + description: + Allows customization of gateway services, for Gateways + in this GatewayClass. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into the + each Gateway Service's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: |- + GatewayServiceSpec allows customization of the services that front gateway deployments. + The LoadBalancer fields allow customization of the corresponding fields in the Kubernetes + ServiceSpec. These can be used for some cloud-independent control of the external load balancer + that is provisioned for each Gateway. For finer-grained cloud-specific control please use + the Metadata.Annotations field in GatewayService. + properties: + allocateLoadBalancerNodePorts: + type: boolean + loadBalancerClass: + type: string + loadBalancerIP: + type: string + loadBalancerSourceRanges: + items: + type: string + type: array + type: object + type: object + name: + description: The name of this GatewayClass. + type: string + required: + - name + type: object + type: array + gatewayControllerDeployment: + description: Allows customization of the gateway controller deployment. properties: metadata: - description: - Metadata is a subset of a Kubernetes object's metadata - that is added to the Deployment. + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into the + deployment's top-level metadata. properties: annotations: additionalProperties: @@ -7214,73 +6961,33 @@ spec: type: object type: object spec: - description: Spec is the specification of the goldmane Deployment. + description: + GatewayControllerDeploymentSpec allows customization + of the gateway controller deployment spec. properties: minReadySeconds: - description: |- - MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should - be ready without any of its container crashing, for it to be considered available. - If specified, this overrides any minReadySeconds value that may be set on the goldmane Deployment. - If omitted, the goldmane Deployment will use its default value for minReadySeconds. + description: + If non-nil, MinReadySeconds sets the minReadySeconds + field for the deployment. format: int32 maximum: 2147483647 minimum: 0 type: integer - strategy: + replicas: description: - The deployment strategy to use to replace existing - pods with new ones. - properties: - rollingUpdate: - description: |- - Rolling update config params. Present only if DeploymentStrategyType = - RollingUpdate. - to be. - properties: - maxSurge: - anyOf: - - type: integer - - type: string - description: |- - The maximum number of pods that can be scheduled above the desired number of - pods. - Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). - This can not be 0 if MaxUnavailable is 0. - Absolute number is calculated from percentage by rounding up. - Defaults to 25%. - Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when - the rolling update starts, such that the total number of old and new pods do not exceed - 130% of desired pods. Once old pods have been killed, - new ReplicaSet can be scaled up further, ensuring that total number of pods running - at any time during the update is at most 130% of desired pods. - x-kubernetes-int-or-string: true - maxUnavailable: - anyOf: - - type: integer - - type: string - description: |- - The maximum number of pods that can be unavailable during the update. - Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). - Absolute number is calculated from percentage by rounding down. - This can not be 0 if MaxSurge is 0. - Defaults to 25%. - Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods - immediately when the rolling update starts. Once new pods are ready, old ReplicaSet - can be scaled down further, followed by scaling up the new ReplicaSet, ensuring - that the total number of pods available at all times during the update is at - least 70% of desired pods. - x-kubernetes-int-or-string: true - type: object - type: object + If non-nil, Replicas sets the number of replicas + for the deployment. + format: int32 + type: integer template: - description: - Template describes the goldmane Deployment pod - that will be created. + description: |- + GatewayControllerDeploymentPodTemplate allows customization of the gateway controller deployment + pod template. properties: metadata: - description: - Metadata is a subset of a Kubernetes object's - metadata that is added to the pod's metadata. + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into the + deployment's pod template. properties: annotations: additionalProperties: @@ -7300,12 +7007,14 @@ spec: type: object type: object spec: - description: Spec is the goldmane Deployment's PodSpec. + description: |- + GatewayControllerDeploymentPodSpec allows customization of the gateway controller deployment pod + spec. properties: affinity: description: - Affinity is a group of affinity scheduling - rules for the goldmane pods. + If non-nil, Affinity sets the affinity + field of the deployment's pod template. properties: nodeAffinity: description: @@ -7615,7 +7324,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -7630,7 +7338,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -7804,7 +7511,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -7819,7 +7525,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -7917,8 +7622,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -7995,7 +7700,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -8010,7 +7714,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -8184,7 +7887,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -8199,7 +7901,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -8285,26 +7986,25 @@ spec: type: object type: object containers: - description: |- - Containers is a list of goldmane containers. - If specified, this overrides the specified EGW Deployment containers. - If omitted, the goldmane Deployment will use its default values for its containers. items: + description: |- + GatewayControllerDeploymentContainer allows customization of the gateway controller's resource + requirements. properties: name: enum: - - goldmane + - envoy-gateway type: string resources: - description: - ResourceRequirements describes - the compute resource requirements. + description: |- + If non-nil, Resources sets the ResourceRequirements of the controller's "envoy-gateway" + container. properties: claims: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -8363,28 +8063,14 @@ spec: nodeSelector: additionalProperties: type: string - description: - NodeSelector gives more control over - the nodes where the goldmane pods will run on. + description: |- + If non-nil, NodeSelector sets the node selector for where deployment pods may be + scheduled. type: object - priorityClassName: - description: - PriorityClassName allows to specify a - PriorityClass resource to be used. - type: string - terminationGracePeriodSeconds: - description: - TerminationGracePeriodSeconds defines - the termination grace period of the goldmane pods - in seconds. - format: int64 - minimum: 0 - type: integer tolerations: - description: |- - Tolerations is the goldmane pod's tolerations. - If specified, this overrides any tolerations that may be set on the goldmane Deployment. - If omitted, the goldmane Deployment will use its default value for tolerations. + description: + If non-nil, Tolerations sets the tolerations + field of the deployment's pod template. items: description: |- The pod this Toleration is attached to tolerates any taint that matches @@ -8424,9 +8110,10 @@ spec: type: array topologySpreadConstraints: description: |- - TopologySpreadConstraints describes how a group of pods ought to spread across topology - domains. Scheduler will schedule pods in a way which abides by the constraints. - All topologySpreadConstraints are ANDed. + If non-nil, TopologySpreadConstraints sets the topology spread constraints of the + deployment's pod template. TopologySpreadConstraints describes how a group of pods ought + to spread across topology domains. Scheduler will schedule pods in a way which abides by + the constraints. All topologySpreadConstraints are ANDed. items: description: TopologySpreadConstraint specifies @@ -8549,7 +8236,6 @@ spec: - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. If this value is nil, the behavior is equivalent to the Honor policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string nodeTaintsPolicy: description: |- @@ -8559,7 +8245,6 @@ spec: has a toleration, are included. - Ignore: node taints are ignored. All nodes are included. If this value is nil, the behavior is equivalent to the Ignore policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string topologyKey: description: |- @@ -8606,179 +8291,32 @@ spec: type: object type: object type: object - status: - description: GoldmaneStatus defines the observed state of Goldmane - properties: - conditions: - description: |- - Conditions represents the latest observed set of conditions for the component. A component may be one or more of - Ready, Progressing, Degraded or other customer types. - items: - description: - Condition contains details for one aspect of the current - state of this API Resource. - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - type: object - type: object - served: true - storage: true - subresources: - status: {} ---- -# Source: crds/operator.tigera.io_imagesets.yaml -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.17.3 - name: imagesets.operator.tigera.io -spec: - group: operator.tigera.io - names: - kind: ImageSet - listKind: ImageSetList - plural: imagesets - singular: imageset - scope: Cluster - versions: - - name: v1 - schema: - openAPIV3Schema: - description: |- - ImageSet is used to specify image digests for the images that the operator deploys. - The name of the ImageSet is expected to be in the format `-`. - The `variant` used is `enterprise` if the InstallationSpec Variant is - `TigeraSecureEnterprise` otherwise it is `calico`. - The `release` must match the version of the variant that the operator is built to deploy, - this version can be obtained by passing the `--version` flag to the operator binary. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: ImageSetSpec defines the desired state of ImageSet. - properties: - images: - description: |- - Images is the list of images to use digests. All images that the operator will deploy - must be specified. - items: - properties: - digest: - description: |- - Digest is the image identifier that will be used for the Image. - The field should not include a leading `@` and must be prefixed with `sha256:`. - type: string - image: - description: |- - Image is an image that the operator deploys and instead of using the built in tag - the operator will use the Digest for the image identifier. - The value should be the *original* image name without registry or tag or digest. - For the image `docker.io/calico/node:v3.17.1` it should be represented as `calico/node` - The "Installation" spec allows defining custom image registries, paths or prefixes. - Even for custom images such as example.com/custompath/customprefix-calico-node:v3.17.1, - this value should still be `calico/node`. - type: string - required: - - digest - - image - type: object - type: array - type: object type: object + x-kubernetes-validations: + - message: resource name must be 'default' + rule: self.metadata.name == 'default' || self.metadata.name == 'tigera-secure' served: true storage: true - subresources: - status: {} --- -# Source: crds/operator.tigera.io_installations.yaml +# Source: crd.projectcalico.org.v1/templates/operator.tigera.io_goldmanes.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.3 - name: installations.operator.tigera.io + controller-gen.kubebuilder.io/version: v0.18.0 + name: goldmanes.operator.tigera.io spec: group: operator.tigera.io names: - kind: Installation - listKind: InstallationList - plural: installations - singular: installation + kind: Goldmane + listKind: GoldmaneList + plural: goldmanes + singular: goldmane scope: Cluster versions: - name: v1 schema: openAPIV3Schema: - description: |- - Installation configures an installation of Calico or Calico Enterprise. At most one instance - of this resource is supported. It must be named "default". The Installation API installs core networking - and network policy components, and provides general install-time configuration. properties: apiVersion: description: |- @@ -8798,29 +8336,11 @@ spec: metadata: type: object spec: - description: - Specification of the desired state for the Calico or Calico - Enterprise installation. properties: - azure: - description: Azure is used to configure azure provider specific options. - properties: - policyMode: - default: Default - description: |- - PolicyMode determines whether the "control-plane" label is applied to namespaces. It offers two options: Default and Manual. - The Default option adds the "control-plane" label to the required namespaces. - The Manual option does not apply the "control-plane" label to any namespace. - Default: Default - enum: - - Default - - Manual - type: string - type: object - calicoKubeControllersDeployment: - description: |- - CalicoKubeControllersDeployment configures the calico-kube-controllers Deployment. If used in - conjunction with the deprecated ComponentResources, then these overrides take precedence. + goldmaneDeployment: + description: + GoldmaneDeployment is the configuration for the goldmane + Deployment. properties: metadata: description: @@ -8845,29 +8365,73 @@ spec: type: object type: object spec: - description: - Spec is the specification of the calico-kube-controllers - Deployment. + description: Spec is the specification of the goldmane Deployment. properties: minReadySeconds: description: |- MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should be ready without any of its container crashing, for it to be considered available. - If specified, this overrides any minReadySeconds value that may be set on the calico-kube-controllers Deployment. - If omitted, the calico-kube-controllers Deployment will use its default value for minReadySeconds. + If specified, this overrides any minReadySeconds value that may be set on the goldmane Deployment. + If omitted, the goldmane Deployment will use its default value for minReadySeconds. format: int32 maximum: 2147483647 minimum: 0 type: integer + strategy: + description: + The deployment strategy to use to replace existing + pods with new ones. + properties: + rollingUpdate: + description: |- + Rolling update config params. Present only if DeploymentStrategyType = + RollingUpdate. + to be. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: object template: description: - Template describes the calico-kube-controllers - Deployment pod that will be created. + Template describes the goldmane Deployment pod + that will be created. properties: metadata: - description: |- - Metadata is a subset of a Kubernetes object's metadata that is added to - the pod's metadata. + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the pod's metadata. properties: annotations: additionalProperties: @@ -8887,16 +8451,12 @@ spec: type: object type: object spec: - description: - Spec is the calico-kube-controllers Deployment's - PodSpec. + description: Spec is the goldmane Deployment's PodSpec. properties: affinity: - description: |- - Affinity is a group of affinity scheduling rules for the calico-kube-controllers pods. - If specified, this overrides any affinity that may be set on the calico-kube-controllers Deployment. - If omitted, the calico-kube-controllers Deployment will use its default value for affinity. - WARNING: Please note that this field will override the default calico-kube-controllers Deployment affinity. + description: + Affinity is a group of affinity scheduling + rules for the goldmane pods. properties: nodeAffinity: description: @@ -9206,7 +8766,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -9221,7 +8780,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -9395,7 +8953,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -9410,7 +8967,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -9508,8 +9064,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -9586,7 +9142,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -9601,7 +9156,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -9775,7 +9329,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -9790,7 +9343,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -9877,34 +9429,25 @@ spec: type: object containers: description: |- - Containers is a list of calico-kube-controllers containers. - If specified, this overrides the specified calico-kube-controllers Deployment containers. - If omitted, the calico-kube-controllers Deployment will use its default values for its containers. + Containers is a list of goldmane containers. + If specified, this overrides the specified EGW Deployment containers. + If omitted, the goldmane Deployment will use its default values for its containers. items: - description: - CalicoKubeControllersDeploymentContainer - is a calico-kube-controllers Deployment container. properties: name: - description: |- - Name is an enum which identifies the calico-kube-controllers Deployment container by name. - Supported values are: calico-kube-controllers, es-calico-kube-controllers enum: - - calico-kube-controllers - - es-calico-kube-controllers + - goldmane type: string resources: - description: |- - Resources allows customization of limits and requests for compute resources such as cpu and memory. - If specified, this overrides the named calico-kube-controllers Deployment container's resources. - If omitted, the calico-kube-controllers Deployment will use its default value for this container's resources. - If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + description: + ResourceRequirements describes + the compute resource requirements. properties: claims: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -9963,22 +9506,28 @@ spec: nodeSelector: additionalProperties: type: string - description: |- - NodeSelector is the calico-kube-controllers pod's scheduling constraints. - If specified, each of the key/value pairs are added to the calico-kube-controllers Deployment nodeSelector provided - the key does not already exist in the object's nodeSelector. - If used in conjunction with ControlPlaneNodeSelector, that nodeSelector is set on the calico-kube-controllers Deployment - and each of this field's key/value pairs are added to the calico-kube-controllers Deployment nodeSelector provided - the key does not already exist in the object's nodeSelector. - If omitted, the calico-kube-controllers Deployment will use its default value for nodeSelector. - WARNING: Please note that this field will modify the default calico-kube-controllers Deployment nodeSelector. + description: + NodeSelector gives more control over + the nodes where the goldmane pods will run on. type: object + priorityClassName: + description: + PriorityClassName allows to specify a + PriorityClass resource to be used. + type: string + terminationGracePeriodSeconds: + description: + TerminationGracePeriodSeconds defines + the termination grace period of the goldmane pods + in seconds. + format: int64 + minimum: 0 + type: integer tolerations: description: |- - Tolerations is the calico-kube-controllers pod's tolerations. - If specified, this overrides any tolerations that may be set on the calico-kube-controllers Deployment. - If omitted, the calico-kube-controllers Deployment will use its default value for tolerations. - WARNING: Please note that this field will override the default calico-kube-controllers Deployment tolerations. + Tolerations is the goldmane pod's tolerations. + If specified, this overrides any tolerations that may be set on the goldmane Deployment. + If omitted, the goldmane Deployment will use its default value for tolerations. items: description: |- The pod this Toleration is attached to tolerates any taint that matches @@ -10016,299 +9565,411 @@ spec: type: string type: object type: array + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array type: object type: object type: object type: object - calicoNetwork: - description: - CalicoNetwork specifies networking configuration options - for Calico. + type: object + status: + description: GoldmaneStatus defines the observed state of Goldmane + properties: + conditions: + description: |- + Conditions represents the latest observed set of conditions for the component. A component may be one or more of + Ready, Progressing, Degraded or other customer types. + items: + description: + Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + x-kubernetes-validations: + - message: resource name must be 'default' + rule: self.metadata.name == 'default' + served: true + storage: true + subresources: + status: {} +--- +# Source: crd.projectcalico.org.v1/templates/operator.tigera.io_imagesets.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: imagesets.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: ImageSet + listKind: ImageSetList + plural: imagesets + singular: imageset + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: |- + ImageSet is used to specify image digests for the images that the operator deploys. + The name of the ImageSet is expected to be in the format `-`. + The `variant` used is `enterprise` if the InstallationSpec Variant is + `TigeraSecureEnterprise` otherwise it is `calico`. + The `release` must match the version of the variant that the operator is built to deploy, + this version can be obtained by passing the `--version` flag to the operator binary. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: ImageSetSpec defines the desired state of ImageSet. + properties: + images: + description: |- + Images is the list of images to use digests. All images that the operator will deploy + must be specified. + items: + properties: + digest: + description: |- + Digest is the image identifier that will be used for the Image. + The field should not include a leading `@` and must be prefixed with `sha256:`. + type: string + image: + description: |- + Image is an image that the operator deploys and instead of using the built in tag + the operator will use the Digest for the image identifier. + The value should be the *original* image name without registry or tag or digest. + For the image `docker.io/calico/node:v3.17.1` it should be represented as `calico/node` + The "Installation" spec allows defining custom image registries, paths or prefixes. + Even for custom images such as example.com/custompath/customprefix-calico-node:v3.17.1, + this value should still be `calico/node`. + type: string + required: + - digest + - image + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: crd.projectcalico.org.v1/templates/operator.tigera.io_installations.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: installations.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: Installation + listKind: InstallationList + plural: installations + singular: installation + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: |- + Installation configures an installation of Calico or Calico Enterprise. At most one instance + of this resource is supported. It must be named "default". The Installation API installs core networking + and network policy components, and provides general install-time configuration. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: + Specification of the desired state for the Calico or Calico + Enterprise installation. + properties: + azure: + description: Azure is used to configure azure provider specific options. properties: - bgp: - description: - BGP configures whether or not to enable Calico's - BGP capabilities. - enum: - - Enabled - - Disabled - type: string - bpfNetworkBootstrap: - description: |- - BPFNetworkBootstrap manages the initial networking setup required to configure the BPF dataplane. - When enabled, the operator tries to bootstraps access to the Kubernetes API Server - by using the Kubernetes service and its associated endpoints. - This field should be enabled only if linuxDataplane is set to "BPF". - If another dataplane is selected, this field must be omitted or explicitly set to Disabled. - When disabled and linuxDataplane is BPF, you must manually provide the Kubernetes API Server - information via the "kubernetes-service-endpoint" ConfigMap. It is invalid to use both the ConfigMap - and have this field set to true at the same time. - Default: Disabled - enum: - - Disabled - - Enabled - type: string - containerIPForwarding: - description: |- - ContainerIPForwarding configures whether ip forwarding will be enabled for containers in the CNI configuration. - Default: Disabled - enum: - - Enabled - - Disabled - type: string - hostPorts: + policyMode: + default: Default description: |- - HostPorts configures whether or not Calico will support Kubernetes HostPorts. Valid only when using the Calico CNI plugin. - Default: Enabled + PolicyMode determines whether the "control-plane" label is applied to namespaces. It offers two options: Default and Manual. + The Default option adds the "control-plane" label to the required namespaces. + The Manual option does not apply the "control-plane" label to any namespace. + Default: Default enum: - - Enabled - - Disabled - type: string - ipPools: - description: |- - IPPools contains a list of IP pools to manage. If nil, a single IPv4 IP pool - will be created by the operator. If an empty list is provided, the operator will not create any IP pools and will instead - wait for IP pools to be created out-of-band. - IP pools in this list will be reconciled by the operator and should not be modified out-of-band. - items: - properties: - allowedUses: - description: |- - AllowedUse controls what the IP pool will be used for. If not specified or empty, defaults to - ["Tunnel", "Workload"] for back-compatibility - items: - type: string - type: array - assignmentMode: - description: - AssignmentMode determines if IP addresses from - this pool should be assigned automatically or on request - only - type: string - blockSize: - description: |- - BlockSize specifies the CIDR prefex length to use when allocating per-node IP blocks from - the main IP pool CIDR. - Default: 26 (IPv4), 122 (IPv6) - format: int32 - type: integer - cidr: - description: - CIDR contains the address range for the IP - Pool in classless inter-domain routing format. - type: string - disableBGPExport: - default: false - description: |- - DisableBGPExport specifies whether routes from this IP pool's CIDR are exported over BGP. - Default: false - type: boolean - disableNewAllocations: - description: |- - DisableNewAllocations specifies whether or not new IP allocations are allowed from this pool. - This is useful when you want to prevent new pods from receiving IP addresses from this pool, without - impacting any existing pods that have already been assigned addresses from this pool. - type: boolean - encapsulation: - description: |- - Encapsulation specifies the encapsulation type that will be used with - the IP Pool. - Default: IPIP - enum: - - IPIPCrossSubnet - - IPIP - - VXLAN - - VXLANCrossSubnet - - None - type: string - name: - description: - Name is the name of the IP pool. If omitted, - this will be generated. - type: string - natOutgoing: - description: |- - NATOutgoing specifies if NAT will be enabled or disabled for outgoing traffic. - Default: Enabled - enum: - - Enabled - - Disabled - type: string - nodeSelector: - description: |- - NodeSelector specifies the node selector that will be set for the IP Pool. - Default: 'all()' - type: string - required: - - cidr - type: object - maxItems: 25 - type: array - kubeProxyManagement: - description: |- - KubeProxyManagement controls whether the operator manages the kube-proxy DaemonSet. - When enabled, the operator will manage the DaemonSet by patching it: - it disables kube-proxy if the dataplane is BPF, or enables it otherwise. - Default: Disabled - enum: - - Disabled - - Enabled - type: string - linuxDataplane: - description: |- - LinuxDataplane is used to select the dataplane used for Linux nodes. In particular, it - causes the operator to add required mounts and environment variables for the particular dataplane. - If not specified, iptables mode is used. - Default: Iptables - enum: - - Iptables - - BPF - - VPP - - Nftables - type: string - linuxPolicySetupTimeoutSeconds: - description: |- - LinuxPolicySetupTimeoutSeconds delays new pods from running containers - until their policy has been programmed in the dataplane. - The specified delay defines the maximum amount of time - that the Calico CNI plugin will wait for policy to be programmed. - Only applies to pods created on Linux nodes. - * A value of 0 disables pod startup delays. - Default: 0 - format: int32 - type: integer - mtu: - description: |- - MTU specifies the maximum transmission unit to use on the pod network. - If not specified, Calico will perform MTU auto-detection based on the cluster network. - format: int32 - type: integer - multiInterfaceMode: - description: |- - MultiInterfaceMode configures what will configure multiple interface per pod. Only valid for Calico Enterprise installations - using the Calico CNI plugin. - Default: None - enum: - - None - - Multus - type: string - nodeAddressAutodetectionV4: - description: |- - NodeAddressAutodetectionV4 specifies an approach to automatically detect node IPv4 addresses. If not specified, - will use default auto-detection settings to acquire an IPv4 address for each node. - properties: - canReach: - description: |- - CanReach enables IP auto-detection based on which source address on the node is used to reach the - specified IP or domain. - type: string - cidrs: - description: |- - CIDRS enables IP auto-detection based on which addresses on the nodes are within - one of the provided CIDRs. - items: - type: string - type: array - firstFound: - description: |- - FirstFound uses default interface matching parameters to select an interface, performing best-effort - filtering based on well-known interface names. - type: boolean - interface: - description: - Interface enables IP auto-detection based on - interfaces that match the given regex. - type: string - kubernetes: - description: - Kubernetes configures Calico to detect node addresses - based on the Kubernetes API. - enum: - - NodeInternalIP - type: string - skipInterface: - description: |- - SkipInterface enables IP auto-detection based on interfaces that do not match - the given regex. - type: string - type: object - nodeAddressAutodetectionV6: - description: |- - NodeAddressAutodetectionV6 specifies an approach to automatically detect node IPv6 addresses. If not specified, - IPv6 addresses will not be auto-detected. - properties: - canReach: - description: |- - CanReach enables IP auto-detection based on which source address on the node is used to reach the - specified IP or domain. - type: string - cidrs: - description: |- - CIDRS enables IP auto-detection based on which addresses on the nodes are within - one of the provided CIDRs. - items: - type: string - type: array - firstFound: - description: |- - FirstFound uses default interface matching parameters to select an interface, performing best-effort - filtering based on well-known interface names. - type: boolean - interface: - description: - Interface enables IP auto-detection based on - interfaces that match the given regex. - type: string - kubernetes: - description: - Kubernetes configures Calico to detect node addresses - based on the Kubernetes API. - enum: - - NodeInternalIP - type: string - skipInterface: - description: |- - SkipInterface enables IP auto-detection based on interfaces that do not match - the given regex. - type: string - type: object - sysctl: - description: Sysctl configures sysctl parameters for tuning plugin - items: - properties: - key: - enum: - - net.ipv4.tcp_keepalive_intvl - - net.ipv4.tcp_keepalive_probes - - net.ipv4.tcp_keepalive_time - type: string - value: - type: string - required: - - key - - value - type: object - type: array - windowsDataplane: - description: |- - WindowsDataplane is used to select the dataplane used for Windows nodes. In particular, it - causes the operator to add required mounts and environment variables for the particular dataplane. - If not specified, it is disabled and the operator will not render the Calico Windows nodes daemonset. - Default: Disabled - enum: - - HNS - - Disabled + - Default + - Manual type: string type: object - calicoNodeDaemonSet: + calicoKubeControllersDeployment: description: |- - CalicoNodeDaemonSet configures the calico-node DaemonSet. If used in + CalicoKubeControllersDeployment configures the calico-kube-controllers Deployment. If used in conjunction with the deprecated ComponentResources, then these overrides take precedence. properties: metadata: description: Metadata is a subset of a Kubernetes object's metadata - that is added to the DaemonSet. + that is added to the Deployment. properties: annotations: additionalProperties: @@ -10328,22 +9989,24 @@ spec: type: object type: object spec: - description: Spec is the specification of the calico-node DaemonSet. + description: + Spec is the specification of the calico-kube-controllers + Deployment. properties: minReadySeconds: description: |- - MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should be ready without any of its container crashing, for it to be considered available. - If specified, this overrides any minReadySeconds value that may be set on the calico-node DaemonSet. - If omitted, the calico-node DaemonSet will use its default value for minReadySeconds. + If specified, this overrides any minReadySeconds value that may be set on the calico-kube-controllers Deployment. + If omitted, the calico-kube-controllers Deployment will use its default value for minReadySeconds. format: int32 maximum: 2147483647 minimum: 0 type: integer template: description: - Template describes the calico-node DaemonSet - pod that will be created. + Template describes the calico-kube-controllers + Deployment pod that will be created. properties: metadata: description: |- @@ -10368,14 +10031,16 @@ spec: type: object type: object spec: - description: Spec is the calico-node DaemonSet's PodSpec. + description: + Spec is the calico-kube-controllers Deployment's + PodSpec. properties: affinity: description: |- - Affinity is a group of affinity scheduling rules for the calico-node pods. - If specified, this overrides any affinity that may be set on the calico-node DaemonSet. - If omitted, the calico-node DaemonSet will use its default value for affinity. - WARNING: Please note that this field will override the default calico-node DaemonSet affinity. + Affinity is a group of affinity scheduling rules for the calico-kube-controllers pods. + If specified, this overrides any affinity that may be set on the calico-kube-controllers Deployment. + If omitted, the calico-kube-controllers Deployment will use its default value for affinity. + WARNING: Please note that this field will override the default calico-kube-controllers Deployment affinity. properties: nodeAffinity: description: @@ -10685,7 +10350,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -10700,7 +10364,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -10874,7 +10537,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -10889,7 +10551,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -10987,8 +10648,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -11065,7 +10726,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -11080,7 +10740,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -11254,7 +10913,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -11269,7 +10927,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -11356,182 +11013,34 @@ spec: type: object containers: description: |- - Containers is a list of calico-node containers. - If specified, this overrides the specified calico-node DaemonSet containers. - If omitted, the calico-node DaemonSet will use its default values for its containers. + Containers is a list of calico-kube-controllers containers. + If specified, this overrides the specified calico-kube-controllers Deployment containers. + If omitted, the calico-kube-controllers Deployment will use its default values for its containers. items: description: - CalicoNodeDaemonSetContainer is a calico-node - DaemonSet container. + CalicoKubeControllersDeploymentContainer + is a calico-kube-controllers Deployment container. properties: name: description: |- - Name is an enum which identifies the calico-node DaemonSet container by name. - Supported values are: calico-node + Name is an enum which identifies the calico-kube-controllers Deployment container by name. + Supported values are: calico-kube-controllers, es-calico-kube-controllers enum: - - calico-node + - calico-kube-controllers + - es-calico-kube-controllers type: string resources: description: |- Resources allows customization of limits and requests for compute resources such as cpu and memory. - If specified, this overrides the named calico-node DaemonSet container's resources. - If omitted, the calico-node DaemonSet will use its default value for this container's resources. + If specified, this overrides the named calico-kube-controllers Deployment container's resources. + If omitted, the calico-kube-controllers Deployment will use its default value for this container's resources. If used in conjunction with the deprecated ComponentResources, then this value takes precedence. properties: claims: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - This field is immutable. It can only be set for containers. - items: - description: - ResourceClaim references - one entry in PodSpec.ResourceClaims. - properties: - name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. - type: string - request: - description: |- - Request is the name chosen for a request in the referenced claim. - If empty, everything from the claim is made available, otherwise - only the result of this request. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - type: object - required: - - name - type: object - type: array - dnsConfig: - description: - DNSConfig allows customization of the - DNS configuration for the calico-node pods. - properties: - nameservers: - description: |- - A list of DNS name server IP addresses. - This will be appended to the base nameservers generated from DNSPolicy. - Duplicated nameservers will be removed. - items: - type: string - type: array - x-kubernetes-list-type: atomic - options: - description: |- - A list of DNS resolver options. - This will be merged with the base options generated from DNSPolicy. - Duplicated entries will be removed. Resolution options given in Options - will override those that appear in the base DNSPolicy. - items: - description: - PodDNSConfigOption defines DNS - resolver options of a pod. - properties: - name: - description: |- - Name is this DNS resolver option's name. - Required. - type: string - value: - description: - Value is this DNS resolver - option's value. - type: string - type: object - type: array - x-kubernetes-list-type: atomic - searches: - description: |- - A list of DNS search domains for host-name lookup. - This will be appended to the base search paths generated from DNSPolicy. - Duplicated search paths will be removed. - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - dnsPolicy: - description: - DNSPolicy is the DNS policy for the calico-node - pods. - enum: - - "" - - Default - - ClusterFirst - - ClusterFirstWithHostNet - - None - type: string - initContainers: - description: |- - InitContainers is a list of calico-node init containers. - If specified, this overrides the specified calico-node DaemonSet init containers. - If omitted, the calico-node DaemonSet will use its default values for its init containers. - items: - description: - CalicoNodeDaemonSetInitContainer is - a calico-node DaemonSet init container. - properties: - name: - description: |- - Name is an enum which identifies the calico-node DaemonSet init container by name. - Supported values are: install-cni, hostpath-init, flexvol-driver, ebpf-bootstrap, node-certs-key-cert-provisioner, calico-node-prometheus-server-tls-key-cert-provisioner, mount-bpffs (deprecated, replaced by ebpf-bootstrap) - enum: - - install-cni - - hostpath-init - - flexvol-driver - - ebpf-bootstrap - - node-certs-key-cert-provisioner - - calico-node-prometheus-server-tls-key-cert-provisioner - - mount-bpffs - type: string - resources: - description: |- - Resources allows customization of limits and requests for compute resources such as cpu and memory. - If specified, this overrides the named calico-node DaemonSet init container's resources. - If omitted, the calico-node DaemonSet will use its default value for this container's resources. - If used in conjunction with the deprecated ComponentResources, then this value takes precedence. - properties: - claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -11591,18 +11100,21 @@ spec: additionalProperties: type: string description: |- - NodeSelector is the calico-node pod's scheduling constraints. - If specified, each of the key/value pairs are added to the calico-node DaemonSet nodeSelector provided + NodeSelector is the calico-kube-controllers pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-kube-controllers Deployment nodeSelector provided the key does not already exist in the object's nodeSelector. - If omitted, the calico-node DaemonSet will use its default value for nodeSelector. - WARNING: Please note that this field will modify the default calico-node DaemonSet nodeSelector. + If used in conjunction with ControlPlaneNodeSelector, that nodeSelector is set on the calico-kube-controllers Deployment + and each of this field's key/value pairs are added to the calico-kube-controllers Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-kube-controllers Deployment will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-kube-controllers Deployment nodeSelector. type: object tolerations: description: |- - Tolerations is the calico-node pod's tolerations. - If specified, this overrides any tolerations that may be set on the calico-node DaemonSet. - If omitted, the calico-node DaemonSet will use its default value for tolerations. - WARNING: Please note that this field will override the default calico-node DaemonSet tolerations. + Tolerations is the calico-kube-controllers pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-kube-controllers Deployment. + If omitted, the calico-kube-controllers Deployment will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-kube-controllers Deployment tolerations. items: description: |- The pod this Toleration is attached to tolerates any taint that matches @@ -11644,10 +11156,290 @@ spec: type: object type: object type: object - calicoNodeWindowsDaemonSet: + calicoNetwork: description: - CalicoNodeWindowsDaemonSet configures the calico-node-windows - DaemonSet. + CalicoNetwork specifies networking configuration options + for Calico. + properties: + bgp: + description: + BGP configures whether or not to enable Calico's + BGP capabilities. + enum: + - Enabled + - Disabled + type: string + bpfNetworkBootstrap: + description: |- + BPFNetworkBootstrap manages the initial networking setup required to configure the BPF dataplane. + When enabled, the operator tries to bootstraps access to the Kubernetes API Server + by using the Kubernetes service and its associated endpoints. + This field should be enabled only if linuxDataplane is set to "BPF". + If another dataplane is selected, this field must be omitted or explicitly set to Disabled. + When disabled and linuxDataplane is BPF, you must manually provide the Kubernetes API Server + information via the "kubernetes-service-endpoint" ConfigMap. It is invalid to use both the ConfigMap + and have this field set to true at the same time. + Default: Disabled + enum: + - Disabled + - Enabled + type: string + containerIPForwarding: + description: |- + ContainerIPForwarding configures whether ip forwarding will be enabled for containers in the CNI configuration. + Default: Disabled + enum: + - Enabled + - Disabled + type: string + hostPorts: + description: |- + HostPorts configures whether or not Calico will support Kubernetes HostPorts. Valid only when using the Calico CNI plugin. + Default: Enabled + enum: + - Enabled + - Disabled + type: string + ipPools: + description: |- + IPPools contains a list of IP pools to manage. If nil, a single IPv4 IP pool + will be created by the operator. If an empty list is provided, the operator will not create any IP pools and will instead + wait for IP pools to be created out-of-band. + IP pools in this list will be reconciled by the operator and should not be modified out-of-band. + items: + properties: + allowedUses: + description: |- + AllowedUse controls what the IP pool will be used for. If not specified or empty, defaults to + ["Tunnel", "Workload"] for back-compatibility + items: + type: string + type: array + assignmentMode: + description: + AssignmentMode determines if IP addresses from + this pool should be assigned automatically or on request + only + type: string + blockSize: + description: |- + BlockSize specifies the CIDR prefex length to use when allocating per-node IP blocks from + the main IP pool CIDR. + Default: 26 (IPv4), 122 (IPv6) + format: int32 + type: integer + cidr: + description: + CIDR contains the address range for the IP + Pool in classless inter-domain routing format. + type: string + disableBGPExport: + default: false + description: |- + DisableBGPExport specifies whether routes from this IP pool's CIDR are exported over BGP. + Default: false + type: boolean + disableNewAllocations: + description: |- + DisableNewAllocations specifies whether or not new IP allocations are allowed from this pool. + This is useful when you want to prevent new pods from receiving IP addresses from this pool, without + impacting any existing pods that have already been assigned addresses from this pool. + type: boolean + encapsulation: + description: |- + Encapsulation specifies the encapsulation type that will be used with + the IP Pool. + Default: IPIP + enum: + - IPIPCrossSubnet + - IPIP + - VXLAN + - VXLANCrossSubnet + - None + type: string + name: + description: + Name is the name of the IP pool. If omitted, + this will be generated. + type: string + natOutgoing: + description: |- + NATOutgoing specifies if NAT will be enabled or disabled for outgoing traffic. + Default: Enabled + enum: + - Enabled + - Disabled + type: string + nodeSelector: + description: |- + NodeSelector specifies the node selector that will be set for the IP Pool. + Default: 'all()' + type: string + required: + - cidr + type: object + maxItems: 25 + type: array + kubeProxyManagement: + description: |- + KubeProxyManagement controls whether the operator manages the kube-proxy DaemonSet. + When enabled, the operator will manage the DaemonSet by patching it: + it disables kube-proxy if the dataplane is BPF, or enables it otherwise. + Default: Disabled + enum: + - Disabled + - Enabled + type: string + linuxDataplane: + description: |- + LinuxDataplane is used to select the dataplane used for Linux nodes. In particular, it + causes the operator to add required mounts and environment variables for the particular dataplane. + If not specified, iptables mode is used. + Default: Iptables + enum: + - Iptables + - BPF + - VPP + - Nftables + type: string + linuxPolicySetupTimeoutSeconds: + description: |- + LinuxPolicySetupTimeoutSeconds delays new pods from running containers + until their policy has been programmed in the dataplane. + The specified delay defines the maximum amount of time + that the Calico CNI plugin will wait for policy to be programmed. + Only applies to pods created on Linux nodes. + * A value of 0 disables pod startup delays. + Default: 0 + format: int32 + type: integer + mtu: + description: |- + MTU specifies the maximum transmission unit to use on the pod network. + If not specified, Calico will perform MTU auto-detection based on the cluster network. + format: int32 + type: integer + multiInterfaceMode: + description: |- + MultiInterfaceMode configures what will configure multiple interface per pod. Only valid for Calico Enterprise installations + using the Calico CNI plugin. + Default: None + enum: + - None + - Multus + type: string + nodeAddressAutodetectionV4: + description: |- + NodeAddressAutodetectionV4 specifies an approach to automatically detect node IPv4 addresses. If not specified, + will use default auto-detection settings to acquire an IPv4 address for each node. + properties: + canReach: + description: |- + CanReach enables IP auto-detection based on which source address on the node is used to reach the + specified IP or domain. + type: string + cidrs: + description: |- + CIDRS enables IP auto-detection based on which addresses on the nodes are within + one of the provided CIDRs. + items: + type: string + type: array + firstFound: + description: |- + FirstFound uses default interface matching parameters to select an interface, performing best-effort + filtering based on well-known interface names. + type: boolean + interface: + description: + Interface enables IP auto-detection based on + interfaces that match the given regex. + type: string + kubernetes: + description: + Kubernetes configures Calico to detect node addresses + based on the Kubernetes API. + enum: + - NodeInternalIP + type: string + skipInterface: + description: |- + SkipInterface enables IP auto-detection based on interfaces that do not match + the given regex. + type: string + type: object + nodeAddressAutodetectionV6: + description: |- + NodeAddressAutodetectionV6 specifies an approach to automatically detect node IPv6 addresses. If not specified, + IPv6 addresses will not be auto-detected. + properties: + canReach: + description: |- + CanReach enables IP auto-detection based on which source address on the node is used to reach the + specified IP or domain. + type: string + cidrs: + description: |- + CIDRS enables IP auto-detection based on which addresses on the nodes are within + one of the provided CIDRs. + items: + type: string + type: array + firstFound: + description: |- + FirstFound uses default interface matching parameters to select an interface, performing best-effort + filtering based on well-known interface names. + type: boolean + interface: + description: + Interface enables IP auto-detection based on + interfaces that match the given regex. + type: string + kubernetes: + description: + Kubernetes configures Calico to detect node addresses + based on the Kubernetes API. + enum: + - NodeInternalIP + type: string + skipInterface: + description: |- + SkipInterface enables IP auto-detection based on interfaces that do not match + the given regex. + type: string + type: object + sysctl: + description: Sysctl configures sysctl parameters for tuning plugin + items: + properties: + key: + enum: + - net.ipv4.tcp_keepalive_intvl + - net.ipv4.tcp_keepalive_probes + - net.ipv4.tcp_keepalive_time + type: string + value: + type: string + required: + - key + - value + type: object + type: array + windowsDataplane: + description: |- + WindowsDataplane is used to select the dataplane used for Windows nodes. In particular, it + causes the operator to add required mounts and environment variables for the particular dataplane. + If not specified, it is disabled and the operator will not render the Calico Windows nodes daemonset. + Default: Disabled + enum: + - HNS + - Disabled + type: string + type: object + calicoNodeDaemonSet: + description: |- + CalicoNodeDaemonSet configures the calico-node DaemonSet. If used in + conjunction with the deprecated ComponentResources, then these overrides take precedence. properties: metadata: description: @@ -11672,23 +11464,21 @@ spec: type: object type: object spec: - description: - Spec is the specification of the calico-node-windows - DaemonSet. + description: Spec is the specification of the calico-node DaemonSet. properties: minReadySeconds: description: |- MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should be ready without any of its container crashing, for it to be considered available. - If specified, this overrides any minReadySeconds value that may be set on the calico-node-windows DaemonSet. - If omitted, the calico-node-windows DaemonSet will use its default value for minReadySeconds. + If specified, this overrides any minReadySeconds value that may be set on the calico-node DaemonSet. + If omitted, the calico-node DaemonSet will use its default value for minReadySeconds. format: int32 maximum: 2147483647 minimum: 0 type: integer template: description: - Template describes the calico-node-windows DaemonSet + Template describes the calico-node DaemonSet pod that will be created. properties: metadata: @@ -11714,16 +11504,14 @@ spec: type: object type: object spec: - description: - Spec is the calico-node-windows DaemonSet's - PodSpec. + description: Spec is the calico-node DaemonSet's PodSpec. properties: affinity: description: |- - Affinity is a group of affinity scheduling rules for the calico-node-windows pods. - If specified, this overrides any affinity that may be set on the calico-node-windows DaemonSet. - If omitted, the calico-node-windows DaemonSet will use its default value for affinity. - WARNING: Please note that this field will override the default calico-node-windows DaemonSet affinity. + Affinity is a group of affinity scheduling rules for the calico-node pods. + If specified, this overrides any affinity that may be set on the calico-node DaemonSet. + If omitted, the calico-node DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default calico-node DaemonSet affinity. properties: nodeAffinity: description: @@ -12033,7 +11821,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -12048,7 +11835,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -12222,7 +12008,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -12237,7 +12022,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -12335,8 +12119,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -12413,7 +12197,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -12428,7 +12211,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -12602,7 +12384,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -12617,7 +12398,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -12704,33 +12484,33 @@ spec: type: object containers: description: |- - Containers is a list of calico-node-windows containers. - If specified, this overrides the specified calico-node-windows DaemonSet containers. - If omitted, the calico-node-windows DaemonSet will use its default values for its containers. + Containers is a list of calico-node containers. + If specified, this overrides the specified calico-node DaemonSet containers. + If omitted, the calico-node DaemonSet will use its default values for its containers. items: description: - CalicoNodeWindowsDaemonSetContainer - is a calico-node-windows DaemonSet container. + CalicoNodeDaemonSetContainer is a calico-node + DaemonSet container. properties: name: description: |- - Name is an enum which identifies the calico-node-windows DaemonSet container by name. - Supported values are: calico-node-windows + Name is an enum which identifies the calico-node DaemonSet container by name. + Supported values are: calico-node enum: - - calico-node-windows + - calico-node type: string resources: description: |- Resources allows customization of limits and requests for compute resources such as cpu and memory. - If specified, this overrides the named calico-node-windows DaemonSet container's resources. - If omitted, the calico-node-windows DaemonSet will use its default value for this container's resources. + If specified, this overrides the named calico-node DaemonSet container's resources. + If omitted, the calico-node DaemonSet will use its default value for this container's resources. If used in conjunction with the deprecated ComponentResources, then this value takes precedence. properties: claims: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -12786,39 +12566,100 @@ spec: - name type: object type: array + dnsConfig: + description: + DNSConfig allows customization of the + DNS configuration for the calico-node pods. + properties: + nameservers: + description: |- + A list of DNS name server IP addresses. + This will be appended to the base nameservers generated from DNSPolicy. + Duplicated nameservers will be removed. + items: + type: string + type: array + x-kubernetes-list-type: atomic + options: + description: |- + A list of DNS resolver options. + This will be merged with the base options generated from DNSPolicy. + Duplicated entries will be removed. Resolution options given in Options + will override those that appear in the base DNSPolicy. + items: + description: + PodDNSConfigOption defines DNS + resolver options of a pod. + properties: + name: + description: |- + Name is this DNS resolver option's name. + Required. + type: string + value: + description: + Value is this DNS resolver + option's value. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + searches: + description: |- + A list of DNS search domains for host-name lookup. + This will be appended to the base search paths generated from DNSPolicy. + Duplicated search paths will be removed. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + dnsPolicy: + description: + DNSPolicy is the DNS policy for the calico-node + pods. + enum: + - "" + - Default + - ClusterFirst + - ClusterFirstWithHostNet + - None + type: string initContainers: description: |- - InitContainers is a list of calico-node-windows init containers. - If specified, this overrides the specified calico-node-windows DaemonSet init containers. - If omitted, the calico-node-windows DaemonSet will use its default values for its init containers. + InitContainers is a list of calico-node init containers. + If specified, this overrides the specified calico-node DaemonSet init containers. + If omitted, the calico-node DaemonSet will use its default values for its init containers. items: description: - CalicoNodeWindowsDaemonSetInitContainer - is a calico-node-windows DaemonSet init container. + CalicoNodeDaemonSetInitContainer is + a calico-node DaemonSet init container. properties: name: description: |- - Name is an enum which identifies the calico-node-windows DaemonSet init container by name. - Supported values are: install-cni;hostpath-init, flexvol-driver, node-certs-key-cert-provisioner, calico-node-windows-prometheus-server-tls-key-cert-provisioner + Name is an enum which identifies the calico-node DaemonSet init container by name. + Supported values are: install-cni, hostpath-init, flexvol-driver, ebpf-bootstrap, node-certs-key-cert-provisioner, calico-node-prometheus-server-tls-key-cert-provisioner, mount-bpffs (deprecated, replaced by ebpf-bootstrap) enum: - install-cni - hostpath-init - flexvol-driver + - ebpf-bootstrap - node-certs-key-cert-provisioner - - calico-node-windows-prometheus-server-tls-key-cert-provisioner + - calico-node-prometheus-server-tls-key-cert-provisioner + - mount-bpffs type: string resources: description: |- Resources allows customization of limits and requests for compute resources such as cpu and memory. - If specified, this overrides the named calico-node-windows DaemonSet init container's resources. - If omitted, the calico-node-windows DaemonSet will use its default value for this container's resources. + If specified, this overrides the named calico-node DaemonSet init container's resources. + If omitted, the calico-node DaemonSet will use its default value for this container's resources. If used in conjunction with the deprecated ComponentResources, then this value takes precedence. properties: claims: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -12878,18 +12719,18 @@ spec: additionalProperties: type: string description: |- - NodeSelector is the calico-node-windows pod's scheduling constraints. - If specified, each of the key/value pairs are added to the calico-node-windows DaemonSet nodeSelector provided + NodeSelector is the calico-node pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-node DaemonSet nodeSelector provided the key does not already exist in the object's nodeSelector. - If omitted, the calico-node-windows DaemonSet will use its default value for nodeSelector. - WARNING: Please note that this field will modify the default calico-node-windows DaemonSet nodeSelector. + If omitted, the calico-node DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-node DaemonSet nodeSelector. type: object tolerations: description: |- - Tolerations is the calico-node-windows pod's tolerations. - If specified, this overrides any tolerations that may be set on the calico-node-windows DaemonSet. - If omitted, the calico-node-windows DaemonSet will use its default value for tolerations. - WARNING: Please note that this field will override the default calico-node-windows DaemonSet tolerations. + Tolerations is the calico-node pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-node DaemonSet. + If omitted, the calico-node DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-node DaemonSet tolerations. items: description: |- The pod this Toleration is attached to tolerates any taint that matches @@ -12931,15 +12772,15 @@ spec: type: object type: object type: object - calicoWindowsUpgradeDaemonSet: - description: |- - Deprecated. The CalicoWindowsUpgradeDaemonSet is deprecated and will be removed from the API in the future. - CalicoWindowsUpgradeDaemonSet configures the calico-windows-upgrade DaemonSet. + calicoNodeWindowsDaemonSet: + description: + CalicoNodeWindowsDaemonSet configures the calico-node-windows + DaemonSet. properties: metadata: description: Metadata is a subset of a Kubernetes object's metadata - that is added to the Deployment. + that is added to the DaemonSet. properties: annotations: additionalProperties: @@ -12960,23 +12801,23 @@ spec: type: object spec: description: - Spec is the specification of the calico-windows-upgrade + Spec is the specification of the calico-node-windows DaemonSet. properties: minReadySeconds: description: |- - MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should be ready without any of its container crashing, for it to be considered available. - If specified, this overrides any minReadySeconds value that may be set on the calico-windows-upgrade DaemonSet. - If omitted, the calico-windows-upgrade DaemonSet will use its default value for minReadySeconds. + If specified, this overrides any minReadySeconds value that may be set on the calico-node-windows DaemonSet. + If omitted, the calico-node-windows DaemonSet will use its default value for minReadySeconds. format: int32 maximum: 2147483647 minimum: 0 type: integer template: description: - Template describes the calico-windows-upgrade - DaemonSet pod that will be created. + Template describes the calico-node-windows DaemonSet + pod that will be created. properties: metadata: description: |- @@ -13002,15 +12843,15 @@ spec: type: object spec: description: - Spec is the calico-windows-upgrade DaemonSet's + Spec is the calico-node-windows DaemonSet's PodSpec. properties: affinity: description: |- - Affinity is a group of affinity scheduling rules for the calico-windows-upgrade pods. - If specified, this overrides any affinity that may be set on the calico-windows-upgrade DaemonSet. - If omitted, the calico-windows-upgrade DaemonSet will use its default value for affinity. - WARNING: Please note that this field will override the default calico-windows-upgrade DaemonSet affinity. + Affinity is a group of affinity scheduling rules for the calico-node-windows pods. + If specified, this overrides any affinity that may be set on the calico-node-windows DaemonSet. + If omitted, the calico-node-windows DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default calico-node-windows DaemonSet affinity. properties: nodeAffinity: description: @@ -13320,7 +13161,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -13335,7 +13175,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -13509,7 +13348,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -13524,7 +13362,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -13622,8 +13459,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -13700,7 +13537,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -13715,7 +13551,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -13889,7 +13724,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -13904,7 +13738,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -13991,33 +13824,125 @@ spec: type: object containers: description: |- - Containers is a list of calico-windows-upgrade containers. - If specified, this overrides the specified calico-windows-upgrade DaemonSet containers. - If omitted, the calico-windows-upgrade DaemonSet will use its default values for its containers. + Containers is a list of calico-node-windows containers. + If specified, this overrides the specified calico-node-windows DaemonSet containers. + If omitted, the calico-node-windows DaemonSet will use its default values for its containers. items: description: - CalicoWindowsUpgradeDaemonSetContainer - is a calico-windows-upgrade DaemonSet container. + CalicoNodeWindowsDaemonSetContainer + is a calico-node-windows DaemonSet container. properties: name: - description: - Name is an enum which identifies - the calico-windows-upgrade DaemonSet container - by name. + description: |- + Name is an enum which identifies the calico-node-windows DaemonSet container by name. + Supported values are: node, felix, confd + calico-node-windows is allowed because it was previously allowed. enum: - - calico-windows-upgrade + - calico-node-windows + - node + - felix + - confd type: string resources: description: |- Resources allows customization of limits and requests for compute resources such as cpu and memory. - If specified, this overrides the named calico-windows-upgrade DaemonSet container's resources. - If omitted, the calico-windows-upgrade DaemonSet will use its default value for this container's resources. + If specified, this overrides the named DaemonSet container's resources. + If omitted, the DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + initContainers: + description: |- + InitContainers is a list of calico-node-windows init containers. + If specified, this overrides the specified calico-node-windows DaemonSet init containers. + If omitted, the calico-node-windows DaemonSet will use its default values for its init containers. + items: + description: + CalicoNodeWindowsDaemonSetInitContainer + is a calico-node-windows DaemonSet init container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node-windows DaemonSet init container by name. + Supported values are: install-cni;hostpath-init, flexvol-driver, node-certs-key-cert-provisioner, calico-node-windows-prometheus-server-tls-key-cert-provisioner + enum: + - install-cni + - hostpath-init + - flexvol-driver + - node-certs-key-cert-provisioner + - calico-node-windows-prometheus-server-tls-key-cert-provisioner + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-node-windows DaemonSet init container's resources. + If omitted, the calico-node-windows DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. properties: claims: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -14077,18 +14002,18 @@ spec: additionalProperties: type: string description: |- - NodeSelector is the calico-windows-upgrade pod's scheduling constraints. - If specified, each of the key/value pairs are added to the calico-windows-upgrade DaemonSet nodeSelector provided + NodeSelector is the calico-node-windows pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-node-windows DaemonSet nodeSelector provided the key does not already exist in the object's nodeSelector. - If omitted, the calico-windows-upgrade DaemonSet will use its default value for nodeSelector. - WARNING: Please note that this field will modify the default calico-windows-upgrade DaemonSet nodeSelector. + If omitted, the calico-node-windows DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-node-windows DaemonSet nodeSelector. type: object tolerations: description: |- - Tolerations is the calico-windows-upgrade pod's tolerations. - If specified, this overrides any tolerations that may be set on the calico-windows-upgrade DaemonSet. - If omitted, the calico-windows-upgrade DaemonSet will use its default value for tolerations. - WARNING: Please note that this field will override the default calico-windows-upgrade DaemonSet tolerations. + Tolerations is the calico-node-windows pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-node-windows DaemonSet. + If omitted, the calico-node-windows DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-node-windows DaemonSet tolerations. items: description: |- The pod this Toleration is attached to tolerates any taint that matches @@ -14130,267 +14055,15 @@ spec: type: object type: object type: object - certificateManagement: - description: |- - CertificateManagement configures pods to submit a CertificateSigningRequest to the certificates.k8s.io/v1 API in order - to obtain TLS certificates. This feature requires that you bring your own CSR signing and approval process, otherwise - pods will be stuck during initialization. - properties: - caCert: - description: - Certificate of the authority that signs the CertificateSigningRequests - in PEM format. - format: byte - type: string - keyAlgorithm: - description: |- - Specify the algorithm used by pods to generate a key pair that is associated with the X.509 certificate request. - Default: RSAWithSize2048 - enum: - - "" - - RSAWithSize2048 - - RSAWithSize4096 - - RSAWithSize8192 - - ECDSAWithCurve256 - - ECDSAWithCurve384 - - ECDSAWithCurve521 - type: string - signatureAlgorithm: - description: |- - Specify the algorithm used for the signature of the X.509 certificate request. - Default: SHA256WithRSA - enum: - - "" - - SHA256WithRSA - - SHA384WithRSA - - SHA512WithRSA - - ECDSAWithSHA256 - - ECDSAWithSHA384 - - ECDSAWithSHA512 - type: string - signerName: - description: |- - When a CSR is issued to the certificates.k8s.io API, the signerName is added to the request in order to accommodate for clusters - with multiple signers. - Must be formatted as: `/`. - type: string - required: - - caCert - - signerName - type: object - cni: - description: CNI specifies the CNI that will be used by this installation. - properties: - binDir: - description: |- - BinDir is the path to the CNI binaries directory. - If you have changed the installation directory for CNI binaries in the container runtime configuration, - please ensure that this field points to the same directory as specified in the container runtime settings. - Default directory depends on the KubernetesProvider. - * For KubernetesProvider GKE, this field defaults to "/home/kubernetes/bin". - * For KubernetesProvider OpenShift, this field defaults to "/var/lib/cni/bin". - * Otherwise, this field defaults to "/opt/cni/bin". - type: string - confDir: - description: |- - ConfDir is the path to the CNI config directory. - If you have changed the installation directory for CNI configuration in the container runtime configuration, - please ensure that this field points to the same directory as specified in the container runtime settings. - Default directory depends on the KubernetesProvider. - * For KubernetesProvider GKE, this field defaults to "/etc/cni/net.d". - * For KubernetesProvider OpenShift, this field defaults to "/var/run/multus/cni/net.d". - * Otherwise, this field defaults to "/etc/cni/net.d". - type: string - ipam: - description: |- - IPAM specifies the pod IP address management that will be used in the Calico or - Calico Enterprise installation. - properties: - type: - description: |- - Specifies the IPAM plugin that will be used in the Calico or Calico Enterprise installation. - * For CNI Plugin Calico, this field defaults to Calico. - * For CNI Plugin GKE, this field defaults to HostLocal. - * For CNI Plugin AzureVNET, this field defaults to AzureVNET. - * For CNI Plugin AmazonVPC, this field defaults to AmazonVPC. - The IPAM plugin is installed and configured only if the CNI plugin is set to Calico, - for all other values of the CNI plugin the plugin binaries and CNI config is a dependency - that is expected to be installed separately. - Default: Calico - enum: - - Calico - - HostLocal - - AmazonVPC - - AzureVNET - type: string - required: - - type - type: object - type: - description: |- - Specifies the CNI plugin that will be used in the Calico or Calico Enterprise installation. - * For KubernetesProvider GKE, this field defaults to GKE. - * For KubernetesProvider AKS, this field defaults to AzureVNET. - * For KubernetesProvider EKS, this field defaults to AmazonVPC. - * If aws-node daemonset exists in kube-system when the Installation resource is created, this field defaults to AmazonVPC. - * For all other cases this field defaults to Calico. - For the value Calico, the CNI plugin binaries and CNI config will be installed as part of deployment, - for all other values the CNI plugin binaries and CNI config is a dependency that is expected - to be installed separately. - Default: Calico - enum: - - Calico - - GKE - - AmazonVPC - - AzureVNET - type: string - required: - - type - type: object - componentResources: - description: |- - Deprecated. Please use CalicoNodeDaemonSet, TyphaDeployment, and KubeControllersDeployment. - ComponentResources can be used to customize the resource requirements for each component. - Node, Typha, and KubeControllers are supported for installations. - items: - description: |- - Deprecated. Please use component resource config fields in Installation.Spec instead. - The ComponentResource struct associates a ResourceRequirements with a component by name - properties: - componentName: - description: ComponentName is an enum which identifies the component - enum: - - Node - - Typha - - KubeControllers - type: string - resourceRequirements: - description: - ResourceRequirements allows customization of limits - and requests for compute resources such as cpu and memory. - properties: - claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - This field is immutable. It can only be set for containers. - items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. - properties: - name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. - type: string - request: - description: |- - Request is the name chosen for a request in the referenced claim. - If empty, everything from the claim is made available, otherwise - only the result of this request. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - type: object - required: - - componentName - - resourceRequirements - type: object - type: array - controlPlaneNodeSelector: - additionalProperties: - type: string - description: |- - ControlPlaneNodeSelector is used to select control plane nodes on which to run Calico - components. This is globally applied to all resources created by the operator excluding daemonsets. - type: object - controlPlaneReplicas: - description: |- - ControlPlaneReplicas defines how many replicas of the control plane core components will be deployed. - This field applies to all control plane components that support High Availability. Defaults to 2. - format: int32 - type: integer - controlPlaneTolerations: + calicoWindowsUpgradeDaemonSet: description: |- - ControlPlaneTolerations specify tolerations which are then globally applied to all resources - created by the operator. - items: - description: |- - The pod this Toleration is attached to tolerates any taint that matches - the triple using the matching operator . - properties: - effect: - description: |- - Effect indicates the taint effect to match. Empty means match all taint effects. - When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: |- - Key is the taint key that the toleration applies to. Empty means match all taint keys. - If the key is empty, operator must be Exists; this combination means to match all values and all keys. - type: string - operator: - description: |- - Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. - Exists is equivalent to wildcard for value, so that a pod can - tolerate all taints of a particular category. - type: string - tolerationSeconds: - description: |- - TolerationSeconds represents the period of time the toleration (which must be - of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, - it is not set, which means tolerate the taint forever (do not evict). Zero and - negative values will be treated as 0 (evict immediately) by the system. - format: int64 - type: integer - value: - description: |- - Value is the taint value the toleration matches to. - If the operator is Exists, the value should be empty, otherwise just a regular string. - type: string - type: object - type: array - csiNodeDriverDaemonSet: - description: - CSINodeDriverDaemonSet configures the csi-node-driver - DaemonSet. + Deprecated. The CalicoWindowsUpgradeDaemonSet is deprecated and will be removed from the API in the future. + CalicoWindowsUpgradeDaemonSet configures the calico-windows-upgrade DaemonSet. properties: metadata: description: Metadata is a subset of a Kubernetes object's metadata - that is added to the DaemonSet. + that is added to the Deployment. properties: annotations: additionalProperties: @@ -14411,23 +14084,23 @@ spec: type: object spec: description: - Spec is the specification of the csi-node-driver + Spec is the specification of the calico-windows-upgrade DaemonSet. properties: minReadySeconds: description: |- - MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should be ready without any of its container crashing, for it to be considered available. - If specified, this overrides any minReadySeconds value that may be set on the csi-node-driver DaemonSet. - If omitted, the csi-node-driver DaemonSet will use its default value for minReadySeconds. + If specified, this overrides any minReadySeconds value that may be set on the calico-windows-upgrade DaemonSet. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for minReadySeconds. format: int32 maximum: 2147483647 minimum: 0 type: integer template: description: - Template describes the csi-node-driver DaemonSet - pod that will be created. + Template describes the calico-windows-upgrade + DaemonSet pod that will be created. properties: metadata: description: |- @@ -14452,14 +14125,16 @@ spec: type: object type: object spec: - description: Spec is the csi-node-driver DaemonSet's PodSpec. + description: + Spec is the calico-windows-upgrade DaemonSet's + PodSpec. properties: affinity: description: |- - Affinity is a group of affinity scheduling rules for the csi-node-driver pods. - If specified, this overrides any affinity that may be set on the csi-node-driver DaemonSet. - If omitted, the csi-node-driver DaemonSet will use its default value for affinity. - WARNING: Please note that this field will override the default csi-node-driver DaemonSet affinity. + Affinity is a group of affinity scheduling rules for the calico-windows-upgrade pods. + If specified, this overrides any affinity that may be set on the calico-windows-upgrade DaemonSet. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default calico-windows-upgrade DaemonSet affinity. properties: nodeAffinity: description: @@ -14769,7 +14444,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -14784,7 +14458,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -14958,7 +14631,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -14973,7 +14645,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -15071,8 +14742,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -15149,7 +14820,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -15164,7 +14834,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -15338,7 +15007,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -15353,7 +15021,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -15440,34 +15107,33 @@ spec: type: object containers: description: |- - Containers is a list of csi-node-driver containers. - If specified, this overrides the specified csi-node-driver DaemonSet containers. - If omitted, the csi-node-driver DaemonSet will use its default values for its containers. + Containers is a list of calico-windows-upgrade containers. + If specified, this overrides the specified calico-windows-upgrade DaemonSet containers. + If omitted, the calico-windows-upgrade DaemonSet will use its default values for its containers. items: description: - CSINodeDriverDaemonSetContainer is - a csi-node-driver DaemonSet container. + CalicoWindowsUpgradeDaemonSetContainer + is a calico-windows-upgrade DaemonSet container. properties: name: - description: |- - Name is an enum which identifies the csi-node-driver DaemonSet container by name. - Supported values are: calico-csi, csi-node-driver-registrar. + description: + Name is an enum which identifies + the calico-windows-upgrade DaemonSet container + by name. enum: - - calico-csi - - csi-node-driver-registrar - - csi-node-driver + - calico-windows-upgrade type: string resources: description: |- Resources allows customization of limits and requests for compute resources such as cpu and memory. - If specified, this overrides the named csi-node-driver DaemonSet container's resources. - If omitted, the csi-node-driver DaemonSet will use its default value for this container's resources. + If specified, this overrides the named calico-windows-upgrade DaemonSet container's resources. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for this container's resources. properties: claims: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -15527,18 +15193,18 @@ spec: additionalProperties: type: string description: |- - NodeSelector is the csi-node-driver pod's scheduling constraints. - If specified, each of the key/value pairs are added to the csi-node-driver DaemonSet nodeSelector provided + NodeSelector is the calico-windows-upgrade pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-windows-upgrade DaemonSet nodeSelector provided the key does not already exist in the object's nodeSelector. - If omitted, the csi-node-driver DaemonSet will use its default value for nodeSelector. - WARNING: Please note that this field will modify the default csi-node-driver DaemonSet nodeSelector. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-windows-upgrade DaemonSet nodeSelector. type: object tolerations: description: |- - Tolerations is the csi-node-driver pod's tolerations. - If specified, this overrides any tolerations that may be set on the csi-node-driver DaemonSet. - If omitted, the csi-node-driver DaemonSet will use its default value for tolerations. - WARNING: Please note that this field will override the default csi-node-driver DaemonSet tolerations. + Tolerations is the calico-windows-upgrade pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-windows-upgrade DaemonSet. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-windows-upgrade DaemonSet tolerations. items: description: |- The pod this Toleration is attached to tolerates any taint that matches @@ -15580,483 +15246,270 @@ spec: type: object type: object type: object - fipsMode: - description: |- - FIPSMode uses images and features only that are using FIPS 140-2 validated cryptographic modules and standards. - Only supported for Variant=Calico. - Default: Disabled - enum: - - Enabled - - Disabled - type: string - flexVolumePath: - description: |- - FlexVolumePath optionally specifies a custom path for FlexVolume. If not specified, FlexVolume will be - enabled by default. If set to 'None', FlexVolume will be disabled. The default is based on the - kubernetesProvider. - type: string - imagePath: - description: |- - ImagePath allows for the path part of an image to be specified. If specified - then the specified value will be used as the image path for each image. If not specified - or empty, the default for each image will be used. - A special case value, UseDefault, is supported to explicitly specify the default - image path will be used for each image. - Image format: - `/:` - This option allows configuring the `` portion of the above format. - type: string - imagePrefix: - description: |- - ImagePrefix allows for the prefix part of an image to be specified. If specified - then the given value will be used as a prefix on each image. If not specified - or empty, no prefix will be used. - A special case value, UseDefault, is supported to explicitly specify the default - image prefix will be used for each image. - Image format: - `/:` - This option allows configuring the `` portion of the above format. - type: string - imagePullSecrets: - description: |- - ImagePullSecrets is an array of references to container registry pull secrets to use. These are - applied to all images to be pulled. - items: - description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - type: array - kubeletVolumePluginPath: - description: |- - KubeletVolumePluginPath optionally specifies enablement of Calico CSI plugin. If not specified, - CSI will be enabled by default. If set to 'None', CSI will be disabled. - Default: /var/lib/kubelet - type: string - kubernetesProvider: - description: |- - KubernetesProvider specifies a particular provider of the Kubernetes platform and enables provider-specific configuration. - If the specified value is empty, the Operator will attempt to automatically determine the current provider. - If the specified value is not empty, the Operator will still attempt auto-detection, but - will additionally compare the auto-detected value to the specified value to confirm they match. - enum: - - "" - - EKS - - GKE - - AKS - - OpenShift - - DockerEnterprise - - RKE2 - - TKG - - Kind - type: string - logging: - description: Logging Configuration for Components - properties: - cni: - description: Customized logging specification for calico-cni plugin - properties: - logFileMaxAgeDays: - description: "Default: 30 (days)" - format: int32 - type: integer - logFileMaxCount: - description: "Default: 10" - format: int32 - type: integer - logFileMaxSize: - anyOf: - - type: integer - - type: string - description: "Default: 100Mi" - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - logSeverity: - description: "Default: Info" - enum: - - Error - - Warning - - Info - - Debug - type: string - type: object - type: object - nodeMetricsPort: - description: |- - NodeMetricsPort specifies which port calico/node serves prometheus metrics on. By default, metrics are not enabled. - If specified, this overrides any FelixConfiguration resources which may exist. If omitted, then - prometheus metrics may still be configured through FelixConfiguration. - format: int32 - type: integer - nodeUpdateStrategy: + certificateManagement: description: |- - NodeUpdateStrategy can be used to customize the desired update strategy, such as the MaxUnavailable - field. + CertificateManagement configures pods to submit a CertificateSigningRequest to the certificates.k8s.io/v1 API in order + to obtain TLS certificates. This feature requires that you bring your own CSR signing and approval process, otherwise + pods will be stuck during initialization. properties: - rollingUpdate: - description: - Rolling update config params. Present only if type - = "RollingUpdate". - properties: - maxSurge: - anyOf: - - type: integer - - type: string - description: |- - The maximum number of nodes with an existing available DaemonSet pod that - can have an updated DaemonSet pod during during an update. - Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). - This can not be 0 if MaxUnavailable is 0. - Absolute number is calculated from percentage by rounding up to a minimum of 1. - Default value is 0. - Example: when this is set to 30%, at most 30% of the total number of nodes - that should be running the daemon pod (i.e. status.desiredNumberScheduled) - can have their a new pod created before the old pod is marked as deleted. - The update starts by launching new pods on 30% of nodes. Once an updated - pod is available (Ready for at least minReadySeconds) the old DaemonSet pod - on that node is marked deleted. If the old pod becomes unavailable for any - reason (Ready transitions to false, is evicted, or is drained) an updated - pod is immediatedly created on that node without considering surge limits. - Allowing surge implies the possibility that the resources consumed by the - daemonset on any given node can double if the readiness check fails, and - so resource intensive daemonsets should take into account that they may - cause evictions during disruption. - x-kubernetes-int-or-string: true - maxUnavailable: - anyOf: - - type: integer - - type: string - description: |- - The maximum number of DaemonSet pods that can be unavailable during the - update. Value can be an absolute number (ex: 5) or a percentage of total - number of DaemonSet pods at the start of the update (ex: 10%). Absolute - number is calculated from percentage by rounding up. - This cannot be 0 if MaxSurge is 0 - Default value is 1. - Example: when this is set to 30%, at most 30% of the total number of nodes - that should be running the daemon pod (i.e. status.desiredNumberScheduled) - can have their pods stopped for an update at any given time. The update - starts by stopping at most 30% of those DaemonSet pods and then brings - up new DaemonSet pods in their place. Once the new pods are available, - it then proceeds onto other DaemonSet pods, thus ensuring that at least - 70% of original number of DaemonSet pods are available at all times during - the update. - x-kubernetes-int-or-string: true - type: object - type: + caCert: description: - Type of daemon set update. Can be "RollingUpdate" - or "OnDelete". Default is RollingUpdate. + Certificate of the authority that signs the CertificateSigningRequests + in PEM format. + format: byte + type: string + keyAlgorithm: + description: |- + Specify the algorithm used by pods to generate a key pair that is associated with the X.509 certificate request. + Default: RSAWithSize2048 + enum: + - "" + - RSAWithSize2048 + - RSAWithSize4096 + - RSAWithSize8192 + - ECDSAWithCurve256 + - ECDSAWithCurve384 + - ECDSAWithCurve521 + type: string + signatureAlgorithm: + description: |- + Specify the algorithm used for the signature of the X.509 certificate request. + Default: SHA256WithRSA + enum: + - "" + - SHA256WithRSA + - SHA384WithRSA + - SHA512WithRSA + - ECDSAWithSHA256 + - ECDSAWithSHA384 + - ECDSAWithSHA512 + type: string + signerName: + description: |- + When a CSR is issued to the certificates.k8s.io API, the signerName is added to the request in order to accommodate for clusters + with multiple signers. + Must be formatted as: `/`. type: string + required: + - caCert + - signerName type: object - nonPrivileged: - description: - NonPrivileged configures Calico to be run in non-privileged - containers as non-root users where possible. - type: string - proxy: - description: |- - Proxy is used to configure the HTTP(S) proxy settings that will be applied to Tigera containers that connect - to destinations outside the cluster. It is expected that NO_PROXY is configured such that destinations within - the cluster (including the API server) are exempt from proxying. + cni: + description: CNI specifies the CNI that will be used by this installation. properties: - httpProxy: + binDir: description: |- - HTTPProxy defines the value of the HTTP_PROXY environment variable that will be set on Tigera containers that connect to - destinations outside the cluster. + BinDir is the path to the CNI binaries directory. + If you have changed the installation directory for CNI binaries in the container runtime configuration, + please ensure that this field points to the same directory as specified in the container runtime settings. + Default directory depends on the KubernetesProvider. + * For KubernetesProvider GKE, this field defaults to "/home/kubernetes/bin". + * For KubernetesProvider OpenShift, this field defaults to "/var/lib/cni/bin". + * Otherwise, this field defaults to "/opt/cni/bin". type: string - httpsProxy: + confDir: description: |- - HTTPSProxy defines the value of the HTTPS_PROXY environment variable that will be set on Tigera containers that connect to - destinations outside the cluster. + ConfDir is the path to the CNI config directory. + If you have changed the installation directory for CNI configuration in the container runtime configuration, + please ensure that this field points to the same directory as specified in the container runtime settings. + Default directory depends on the KubernetesProvider. + * For KubernetesProvider GKE, this field defaults to "/etc/cni/net.d". + * For KubernetesProvider OpenShift, this field defaults to "/var/run/multus/cni/net.d". + * Otherwise, this field defaults to "/etc/cni/net.d". type: string - noProxy: + ipam: description: |- - NoProxy defines the value of the NO_PROXY environment variable that will be set on Tigera containers that connect to - destinations outside the cluster. This value must be set such that destinations within the scope of the cluster, including - the Kubernetes API server, are exempt from being proxied. + IPAM specifies the pod IP address management that will be used in the Calico or + Calico Enterprise installation. + properties: + type: + description: |- + Specifies the IPAM plugin that will be used in the Calico or Calico Enterprise installation. + * For CNI Plugin Calico, this field defaults to Calico. + * For CNI Plugin GKE, this field defaults to HostLocal. + * For CNI Plugin AzureVNET, this field defaults to AzureVNET. + * For CNI Plugin AmazonVPC, this field defaults to AmazonVPC. + The IPAM plugin is installed and configured only if the CNI plugin is set to Calico, + for all other values of the CNI plugin the plugin binaries and CNI config is a dependency + that is expected to be installed separately. + Default: Calico + enum: + - Calico + - HostLocal + - AmazonVPC + - AzureVNET + type: string + required: + - type + type: object + type: + description: |- + Specifies the CNI plugin that will be used in the Calico or Calico Enterprise installation. + * For KubernetesProvider GKE, this field defaults to GKE. + * For KubernetesProvider AKS, this field defaults to AzureVNET. + * For KubernetesProvider EKS, this field defaults to AmazonVPC. + * If aws-node daemonset exists in kube-system when the Installation resource is created, this field defaults to AmazonVPC. + * For all other cases this field defaults to Calico. + For the value Calico, the CNI plugin binaries and CNI config will be installed as part of deployment, + for all other values the CNI plugin binaries and CNI config is a dependency that is expected + to be installed separately. + Default: Calico + enum: + - Calico + - GKE + - AmazonVPC + - AzureVNET type: string + required: + - type type: object - registry: + componentResources: description: |- - Registry is the default Docker registry used for component Docker images. - If specified then the given value must end with a slash character (`/`) and all images will be pulled from this registry. - If not specified then the default registries will be used. A special case value, UseDefault, is - supported to explicitly specify the default registries will be used. - Image format: - `/:` - This option allows configuring the `` portion of the above format. - type: string - serviceCIDRs: - description: - Kubernetes Service CIDRs. Specifying this is required - when using Calico for Windows. - items: - type: string - type: array - tlsCipherSuites: - description: - TLSCipherSuites defines the cipher suite list that the - TLS protocol should use during secure communication. + Deprecated. Please use CalicoNodeDaemonSet, TyphaDeployment, and KubeControllersDeployment. + ComponentResources can be used to customize the resource requirements for each component. + Node, Typha, and KubeControllers are supported for installations. items: + description: |- + Deprecated. Please use component resource config fields in Installation.Spec instead. + The ComponentResource struct associates a ResourceRequirements with a component by name properties: - name: - description: This should be a valid TLS cipher suite name. + componentName: + description: ComponentName is an enum which identifies the component enum: - - TLS_AES_256_GCM_SHA384 - - TLS_CHACHA20_POLY1305_SHA256 - - TLS_AES_128_GCM_SHA256 - - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 - - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 - - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - - TLS_RSA_WITH_AES_256_GCM_SHA384 - - TLS_RSA_WITH_AES_128_GCM_SHA256 - - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA - - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA - - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + - Node + - Typha + - KubeControllers + - NodeWindows + - FelixWindows + - ConfdWindows type: string + resourceRequirements: + description: + ResourceRequirements allows customization of limits + and requests for compute resources such as cpu and memory. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - componentName + - resourceRequirements type: object type: array - typhaAffinity: + controlPlaneNodeSelector: + additionalProperties: + type: string description: |- - Deprecated. Please use Installation.Spec.TyphaDeployment instead. - TyphaAffinity allows configuration of node affinity characteristics for Typha pods. + ControlPlaneNodeSelector is used to select control plane nodes on which to run Calico + components. This is globally applied to all resources created by the operator excluding daemonsets. + type: object + controlPlaneReplicas: + description: |- + ControlPlaneReplicas defines how many replicas of the control plane core components will be deployed. + This field applies to all control plane components that support High Availability. Defaults to 2. + format: int32 + type: integer + controlPlaneTolerations: + description: |- + ControlPlaneTolerations specify tolerations which are then globally applied to all resources + created by the operator. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + csiNodeDriverDaemonSet: + description: + CSINodeDriverDaemonSet configures the csi-node-driver + DaemonSet. properties: - nodeAffinity: - description: - NodeAffinity describes node affinity scheduling rules - for typha. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. - items: - description: |- - An empty preferred scheduling term matches all objects with implicit weight 0 - (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). - properties: - preference: - description: - A node selector term, associated with the - corresponding weight. - properties: - matchExpressions: - description: - A list of node selector requirements - by node's labels. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: - The label key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchFields: - description: - A list of node selector requirements - by node's fields. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: - The label key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - type: object - x-kubernetes-map-type: atomic - weight: - description: - Weight associated with matching the corresponding - nodeSelectorTerm, in the range 1-100. - format: int32 - type: integer - required: - - preference - - weight - type: object - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - WARNING: Please note that if the affinity requirements specified by this field are not met at - scheduling time, the pod will NOT be scheduled onto the node. - There is no fallback to another affinity rules with this setting. - This may cause networking disruption or even catastrophic failure! - PreferredDuringSchedulingIgnoredDuringExecution should be used for affinity - unless there is a specific well understood reason to use RequiredDuringSchedulingIgnoredDuringExecution and - you can guarantee that the RequiredDuringSchedulingIgnoredDuringExecution will always have sufficient nodes to satisfy the requirement. - NOTE: RequiredDuringSchedulingIgnoredDuringExecution is set by default for AKS nodes, - to avoid scheduling Typhas on virtual-nodes. - If the affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to an update), the system - may or may not try to eventually evict the pod from its node. - properties: - nodeSelectorTerms: - description: - Required. A list of node selector terms. - The terms are ORed. - items: - description: |- - A null or empty node selector term matches no objects. The requirements of - them are ANDed. - The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. - properties: - matchExpressions: - description: - A list of node selector requirements - by node's labels. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: - The label key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchFields: - description: - A list of node selector requirements - by node's fields. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: - The label key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - type: object - x-kubernetes-map-type: atomic - type: array - x-kubernetes-list-type: atomic - required: - - nodeSelectorTerms - type: object - x-kubernetes-map-type: atomic - type: object - type: object - typhaDeployment: - description: |- - TyphaDeployment configures the typha Deployment. If used in conjunction with the deprecated - ComponentResources or TyphaAffinity, then these overrides take precedence. - properties: - metadata: + metadata: description: Metadata is a subset of a Kubernetes object's metadata - that is added to the Deployment. + that is added to the DaemonSet. properties: annotations: additionalProperties: @@ -16076,68 +15529,24 @@ spec: type: object type: object spec: - description: Spec is the specification of the typha Deployment. + description: + Spec is the specification of the csi-node-driver + DaemonSet. properties: minReadySeconds: description: |- - MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should be ready without any of its container crashing, for it to be considered available. - If specified, this overrides any minReadySeconds value that may be set on the typha Deployment. - If omitted, the typha Deployment will use its default value for minReadySeconds. + If specified, this overrides any minReadySeconds value that may be set on the csi-node-driver DaemonSet. + If omitted, the csi-node-driver DaemonSet will use its default value for minReadySeconds. format: int32 maximum: 2147483647 minimum: 0 type: integer - strategy: - description: - The deployment strategy to use to replace existing - pods with new ones. - properties: - rollingUpdate: - description: |- - Rolling update config params. Present only if DeploymentStrategyType = - RollingUpdate. - to be. - properties: - maxSurge: - anyOf: - - type: integer - - type: string - description: |- - The maximum number of pods that can be scheduled above the desired number of - pods. - Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). - This can not be 0 if MaxUnavailable is 0. - Absolute number is calculated from percentage by rounding up. - Defaults to 25%. - Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when - the rolling update starts, such that the total number of old and new pods do not exceed - 130% of desired pods. Once old pods have been killed, - new ReplicaSet can be scaled up further, ensuring that total number of pods running - at any time during the update is at most 130% of desired pods. - x-kubernetes-int-or-string: true - maxUnavailable: - anyOf: - - type: integer - - type: string - description: |- - The maximum number of pods that can be unavailable during the update. - Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). - Absolute number is calculated from percentage by rounding down. - This can not be 0 if MaxSurge is 0. - Defaults to 25%. - Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods - immediately when the rolling update starts. Once new pods are ready, old ReplicaSet - can be scaled down further, followed by scaling up the new ReplicaSet, ensuring - that the total number of pods available at all times during the update is at - least 70% of desired pods. - x-kubernetes-int-or-string: true - type: object - type: object template: description: - Template describes the typha Deployment pod that - will be created. + Template describes the csi-node-driver DaemonSet + pod that will be created. properties: metadata: description: |- @@ -16162,15 +15571,14 @@ spec: type: object type: object spec: - description: Spec is the typha Deployment's PodSpec. + description: Spec is the csi-node-driver DaemonSet's PodSpec. properties: affinity: description: |- - Affinity is a group of affinity scheduling rules for the typha pods. - If specified, this overrides any affinity that may be set on the typha Deployment. - If omitted, the typha Deployment will use its default value for affinity. - If used in conjunction with the deprecated TyphaAffinity, then this value takes precedence. - WARNING: Please note that this field will override the default calico-typha Deployment affinity. + Affinity is a group of affinity scheduling rules for the csi-node-driver pods. + If specified, this overrides any affinity that may be set on the csi-node-driver DaemonSet. + If omitted, the csi-node-driver DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default csi-node-driver DaemonSet affinity. properties: nodeAffinity: description: @@ -16480,7 +15888,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -16495,7 +15902,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -16669,7 +16075,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -16684,7 +16089,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -16782,8 +16186,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -16860,7 +16264,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -16875,7 +16278,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -17049,7 +16451,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -17064,7 +16465,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -17151,117 +16551,34 @@ spec: type: object containers: description: |- - Containers is a list of typha containers. - If specified, this overrides the specified typha Deployment containers. - If omitted, the typha Deployment will use its default values for its containers. - items: - description: - TyphaDeploymentContainer is a typha - Deployment container. - properties: - name: - description: |- - Name is an enum which identifies the typha Deployment container by name. - Supported values are: calico-typha - enum: - - calico-typha - type: string - resources: - description: |- - Resources allows customization of limits and requests for compute resources such as cpu and memory. - If specified, this overrides the named typha Deployment container's resources. - If omitted, the typha Deployment will use its default value for this container's resources. - If used in conjunction with the deprecated ComponentResources, then this value takes precedence. - properties: - claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - This field is immutable. It can only be set for containers. - items: - description: - ResourceClaim references - one entry in PodSpec.ResourceClaims. - properties: - name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. - type: string - request: - description: |- - Request is the name chosen for a request in the referenced claim. - If empty, everything from the claim is made available, otherwise - only the result of this request. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - type: object - required: - - name - type: object - type: array - initContainers: - description: |- - InitContainers is a list of typha init containers. - If specified, this overrides the specified typha Deployment init containers. - If omitted, the typha Deployment will use its default values for its init containers. + Containers is a list of csi-node-driver containers. + If specified, this overrides the specified csi-node-driver DaemonSet containers. + If omitted, the csi-node-driver DaemonSet will use its default values for its containers. items: description: - TyphaDeploymentInitContainer is a typha - Deployment init container. + CSINodeDriverDaemonSetContainer is + a csi-node-driver DaemonSet container. properties: name: description: |- - Name is an enum which identifies the typha Deployment init container by name. - Supported values are: typha-certs-key-cert-provisioner + Name is an enum which identifies the csi-node-driver DaemonSet container by name. + Supported values are: calico-csi, csi-node-driver-registrar. enum: - - typha-certs-key-cert-provisioner + - calico-csi + - csi-node-driver-registrar + - csi-node-driver type: string resources: description: |- Resources allows customization of limits and requests for compute resources such as cpu and memory. - If specified, this overrides the named typha Deployment init container's resources. - If omitted, the typha Deployment will use its default value for this init container's resources. - If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + If specified, this overrides the named csi-node-driver DaemonSet container's resources. + If omitted, the csi-node-driver DaemonSet will use its default value for this container's resources. properties: claims: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -17321,30 +16638,18 @@ spec: additionalProperties: type: string description: |- - NodeSelector is the calico-typha pod's scheduling constraints. - If specified, each of the key/value pairs are added to the calico-typha Deployment nodeSelector provided + NodeSelector is the csi-node-driver pod's scheduling constraints. + If specified, each of the key/value pairs are added to the csi-node-driver DaemonSet nodeSelector provided the key does not already exist in the object's nodeSelector. - If omitted, the calico-typha Deployment will use its default value for nodeSelector. - WARNING: Please note that this field will modify the default calico-typha Deployment nodeSelector. + If omitted, the csi-node-driver DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default csi-node-driver DaemonSet nodeSelector. type: object - terminationGracePeriodSeconds: - description: |- - Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. - Value must be non-negative integer. The value zero indicates stop immediately via - the kill signal (no opportunity to shut down). - If this value is nil, the default grace period will be used instead. - The grace period is the duration in seconds after the processes running in the pod are sent - a termination signal and the time when the processes are forcibly halted with a kill signal. - Set this value longer than the expected cleanup time for your process. - Defaults to 30 seconds. - format: int64 - type: integer tolerations: description: |- - Tolerations is the typha pod's tolerations. - If specified, this overrides any tolerations that may be set on the typha Deployment. - If omitted, the typha Deployment will use its default value for tolerations. - WARNING: Please note that this field will override the default calico-typha Deployment tolerations. + Tolerations is the csi-node-driver pod's tolerations. + If specified, this overrides any tolerations that may be set on the csi-node-driver DaemonSet. + If omitted, the csi-node-driver DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default csi-node-driver DaemonSet tolerations. items: description: |- The pod this Toleration is attached to tolerates any taint that matches @@ -17382,518 +16687,881 @@ spec: type: string type: object type: array - topologySpreadConstraints: - description: |- - TopologySpreadConstraints describes how a group of pods ought to spread across topology - domains. Scheduler will schedule pods in a way which abides by the constraints. - All topologySpreadConstraints are ANDed. - items: - description: - TopologySpreadConstraint specifies - how to spread matching pods among the given topology. - properties: - labelSelector: - description: |- - LabelSelector is used to find matching pods. - Pods that match this label selector are counted to determine the number of pods - in their corresponding topology domain. - properties: - matchExpressions: - description: - matchExpressions is a list - of label selector requirements. The requirements - are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key - that the selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select the pods over which - spreading will be calculated. The keys are used to lookup values from the - incoming pod labels, those key-value labels are ANDed with labelSelector - to select the group of existing pods over which spreading will be calculated - for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. - MatchLabelKeys cannot be set when LabelSelector isn't set. - Keys that don't exist in the incoming pod labels will - be ignored. A null or empty list means only match against labelSelector. - This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). - items: - type: string - type: array - x-kubernetes-list-type: atomic - maxSkew: - description: |- - MaxSkew describes the degree to which pods may be unevenly distributed. - When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference - between the number of matching pods in the target topology and the global minimum. - The global minimum is the minimum number of matching pods in an eligible domain - or zero if the number of eligible domains is less than MinDomains. - For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same - labelSelector spread as 2/2/1: - In this case, the global minimum is 1. - | zone1 | zone2 | zone3 | - | P P | P P | P | - - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; - scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) - violate MaxSkew(1). - - if MaxSkew is 2, incoming pod can be scheduled onto any zone. - When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence - to topologies that satisfy it. - It's a required field. Default value is 1 and 0 is not allowed. - format: int32 - type: integer - minDomains: - description: |- - MinDomains indicates a minimum number of eligible domains. - When the number of eligible domains with matching topology keys is less than minDomains, - Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. - And when the number of eligible domains with matching topology keys equals or greater than minDomains, - this value has no effect on scheduling. - As a result, when the number of eligible domains is less than minDomains, - scheduler won't schedule more than maxSkew Pods to those domains. - If value is nil, the constraint behaves as if MinDomains is equal to 1. - Valid values are integers greater than 0. - When value is not nil, WhenUnsatisfiable must be DoNotSchedule. - For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same - labelSelector spread as 2/2/2: - | zone1 | zone2 | zone3 | - | P P | P P | P P | - The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. - In this situation, new pod with the same labelSelector cannot be scheduled, - because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, - it will violate MaxSkew. - format: int32 - type: integer - nodeAffinityPolicy: - description: |- - NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector - when calculating pod topology spread skew. Options are: - - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. - If this value is nil, the behavior is equivalent to the Honor policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. - type: string - nodeTaintsPolicy: - description: |- - NodeTaintsPolicy indicates how we will treat node taints when calculating - pod topology spread skew. Options are: - - Honor: nodes without taints, along with tainted nodes for which the incoming pod - has a toleration, are included. - - Ignore: node taints are ignored. All nodes are included. - If this value is nil, the behavior is equivalent to the Ignore policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. - type: string - topologyKey: - description: |- - TopologyKey is the key of node labels. Nodes that have a label with this key - and identical values are considered to be in the same topology. - We consider each as a "bucket", and try to put balanced number - of pods into each bucket. - We define a domain as a particular instance of a topology. - Also, we define an eligible domain as a domain whose nodes meet the requirements of - nodeAffinityPolicy and nodeTaintsPolicy. - e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. - And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. - It's a required field. - type: string - whenUnsatisfiable: - description: |- - WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy - the spread constraint. - - DoNotSchedule (default) tells the scheduler not to schedule it. - - ScheduleAnyway tells the scheduler to schedule the pod in any location, - but giving higher precedence to topologies that would help reduce the - skew. - A constraint is considered "Unsatisfiable" for an incoming pod - if and only if every possible node assignment for that pod would violate - "MaxSkew" on some topology. - For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same - labelSelector spread as 3/1/1: - | zone1 | zone2 | zone3 | - | P P P | P | P | - If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled - to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies - MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler - won't make it *more* imbalanced. - It's a required field. - type: string - required: - - maxSkew - - topologyKey - - whenUnsatisfiable - type: object - type: array type: object type: object type: object type: object - typhaMetricsPort: - description: - TyphaMetricsPort specifies which port calico/typha serves - prometheus metrics on. By default, metrics are not enabled. - format: int32 - type: integer - variant: + fipsMode: description: |- - Variant is the product to install - one of Calico or TigeraSecureEnterprise - Default: Calico + FIPSMode uses images and features only that are using FIPS 140-2 validated cryptographic modules and standards. + Only supported for Variant=Calico. + Default: Disabled enum: - - Calico - - TigeraSecureEnterprise + - Enabled + - Disabled type: string - windowsNodes: - description: Windows Configuration - properties: - cniBinDir: - description: |- - CNIBinDir is the path to the CNI binaries directory on Windows, it must match what is used as 'bin_dir' under - [plugins] - [plugins."io.containerd.grpc.v1.cri"] - [plugins."io.containerd.grpc.v1.cri".cni] - on the containerd 'config.toml' file on the Windows nodes. - type: string - cniConfigDir: - description: |- - CNIConfigDir is the path to the CNI configuration directory on Windows, it must match what is used as 'conf_dir' under - [plugins] - [plugins."io.containerd.grpc.v1.cri"] - [plugins."io.containerd.grpc.v1.cri".cni] - on the containerd 'config.toml' file on the Windows nodes. - type: string - cniLogDir: - description: - CNILogDir is the path to the Calico CNI logs directory - on Windows. - type: string - vxlanAdapter: - description: - VXLANAdapter is the Network Adapter used for VXLAN, - leave blank for primary NIC - type: string - vxlanMACPrefix: - description: - VXLANMACPrefix is the prefix used when generating - MAC addresses for virtual NICs - pattern: ^[0-9A-Fa-f]{2}-[0-9A-Fa-f]{2}$ - type: string - type: object - type: object - status: - description: - Most recently observed state for the Calico or Calico Enterprise - installation. - properties: - calicoVersion: + flexVolumePath: description: |- - CalicoVersion shows the current running version of calico. - CalicoVersion along with Variant is needed to know the exact - version deployed. + FlexVolumePath optionally specifies a custom path for FlexVolume. If not specified, FlexVolume will be + enabled by default. If set to 'None', FlexVolume will be disabled. The default is based on the + kubernetesProvider. type: string - computed: - description: - Computed is the final installation including overlaid - resources. + imagePath: + description: |- + ImagePath allows for the path part of an image to be specified. If specified + then the specified value will be used as the image path for each image. If not specified + or empty, the default for each image will be used. + A special case value, UseDefault, is supported to explicitly specify the default + image path will be used for each image. + Image format: + `/:` + This option allows configuring the `` portion of the above format. + type: string + imagePrefix: + description: |- + ImagePrefix allows for the prefix part of an image to be specified. If specified + then the given value will be used as a prefix on each image. If not specified + or empty, no prefix will be used. + A special case value, UseDefault, is supported to explicitly specify the default + image prefix will be used for each image. + Image format: + `/:` + This option allows configuring the `` portion of the above format. + type: string + imagePullSecrets: + description: |- + ImagePullSecrets is an array of references to container registry pull secrets to use. These are + applied to all images to be pulled. + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + kubeletVolumePluginPath: + description: |- + KubeletVolumePluginPath optionally specifies enablement of Calico CSI plugin. If not specified, + CSI will be enabled by default. If set to 'None', CSI will be disabled. + Default: /var/lib/kubelet + type: string + kubernetesProvider: + description: |- + KubernetesProvider specifies a particular provider of the Kubernetes platform and enables provider-specific configuration. + If the specified value is empty, the Operator will attempt to automatically determine the current provider. + If the specified value is not empty, the Operator will still attempt auto-detection, but + will additionally compare the auto-detected value to the specified value to confirm they match. + enum: + - "" + - EKS + - GKE + - AKS + - OpenShift + - DockerEnterprise + - RKE2 + - TKG + - Kind + type: string + logging: + description: Logging Configuration for Components properties: - azure: - description: - Azure is used to configure azure provider specific - options. + cni: + description: Customized logging specification for calico-cni plugin properties: - policyMode: - default: Default - description: |- - PolicyMode determines whether the "control-plane" label is applied to namespaces. It offers two options: Default and Manual. - The Default option adds the "control-plane" label to the required namespaces. - The Manual option does not apply the "control-plane" label to any namespace. - Default: Default + logFileMaxAgeDays: + description: "Default: 30 (days)" + format: int32 + type: integer + logFileMaxCount: + description: "Default: 10" + format: int32 + type: integer + logFileMaxSize: + anyOf: + - type: integer + - type: string + description: "Default: 100Mi" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + logSeverity: + description: "Default: Info" enum: - - Default - - Manual + - Error + - Warning + - Info + - Debug type: string type: object - calicoKubeControllersDeployment: - description: |- - CalicoKubeControllersDeployment configures the calico-kube-controllers Deployment. If used in - conjunction with the deprecated ComponentResources, then these overrides take precedence. + type: object + nodeMetricsPort: + description: |- + NodeMetricsPort specifies which port calico/node serves prometheus metrics on. By default, metrics are not enabled. + If specified, this overrides any FelixConfiguration resources which may exist. If omitted, then + prometheus metrics may still be configured through FelixConfiguration. + format: int32 + type: integer + nodeUpdateStrategy: + description: |- + NodeUpdateStrategy can be used to customize the desired update strategy, such as the MaxUnavailable + field. + properties: + rollingUpdate: + description: + Rolling update config params. Present only if type + = "RollingUpdate". properties: - metadata: - description: - Metadata is a subset of a Kubernetes object's - metadata that is added to the Deployment. + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of nodes with an existing available DaemonSet pod that + can have an updated DaemonSet pod during during an update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up to a minimum of 1. + Default value is 0. + Example: when this is set to 30%, at most 30% of the total number of nodes + that should be running the daemon pod (i.e. status.desiredNumberScheduled) + can have their a new pod created before the old pod is marked as deleted. + The update starts by launching new pods on 30% of nodes. Once an updated + pod is available (Ready for at least minReadySeconds) the old DaemonSet pod + on that node is marked deleted. If the old pod becomes unavailable for any + reason (Ready transitions to false, is evicted, or is drained) an updated + pod is immediately created on that node without considering surge limits. + Allowing surge implies the possibility that the resources consumed by the + daemonset on any given node can double if the readiness check fails, and + so resource intensive daemonsets should take into account that they may + cause evictions during disruption. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of DaemonSet pods that can be unavailable during the + update. Value can be an absolute number (ex: 5) or a percentage of total + number of DaemonSet pods at the start of the update (ex: 10%). Absolute + number is calculated from percentage by rounding up. + This cannot be 0 if MaxSurge is 0 + Default value is 1. + Example: when this is set to 30%, at most 30% of the total number of nodes + that should be running the daemon pod (i.e. status.desiredNumberScheduled) + can have their pods stopped for an update at any given time. The update + starts by stopping at most 30% of those DaemonSet pods and then brings + up new DaemonSet pods in their place. Once the new pods are available, + it then proceeds onto other DaemonSet pods, thus ensuring that at least + 70% of original number of DaemonSet pods are available at all times during + the update. + x-kubernetes-int-or-string: true + type: object + type: + description: + Type of daemon set update. Can be "RollingUpdate" + or "OnDelete". Default is RollingUpdate. + type: string + type: object + nonPrivileged: + description: |- + Deprecated. NonPrivileged is deprecated and will be removed from the API in a future release. + Enabling this field is not supported and will cause errors. + NonPrivileged configures Calico to be run in non-privileged containers as non-root users where possible. + type: string + proxy: + description: |- + Proxy is used to configure the HTTP(S) proxy settings that will be applied to Tigera containers that connect + to destinations outside the cluster. It is expected that NO_PROXY is configured such that destinations within + the cluster (including the API server) are exempt from proxying. + properties: + httpProxy: + description: |- + HTTPProxy defines the value of the HTTP_PROXY environment variable that will be set on Tigera containers that connect to + destinations outside the cluster. + type: string + httpsProxy: + description: |- + HTTPSProxy defines the value of the HTTPS_PROXY environment variable that will be set on Tigera containers that connect to + destinations outside the cluster. + type: string + noProxy: + description: |- + NoProxy defines the value of the NO_PROXY environment variable that will be set on Tigera containers that connect to + destinations outside the cluster. This value must be set such that destinations within the scope of the cluster, including + the Kubernetes API server, are exempt from being proxied. + type: string + type: object + registry: + description: |- + Registry is the default Docker registry used for component Docker images. + If specified then the given value must end with a slash character (`/`) and all images will be pulled from this registry. + If not specified then the default registries will be used. A special case value, UseDefault, is + supported to explicitly specify the default registries will be used. + Image format: + `/:` + This option allows configuring the `` portion of the above format. + type: string + serviceCIDRs: + description: + Kubernetes Service CIDRs. Specifying this is required + when using Calico for Windows. + items: + type: string + type: array + tlsCipherSuites: + description: + TLSCipherSuites defines the cipher suite list that the + TLS protocol should use during secure communication. + items: + properties: + name: + description: This should be a valid TLS cipher suite name. + enum: + - TLS_AES_256_GCM_SHA384 + - TLS_CHACHA20_POLY1305_SHA256 + - TLS_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 + - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + - TLS_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA + - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA + - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + type: string + type: object + type: array + typhaAffinity: + description: |- + Deprecated. Please use Installation.Spec.TyphaDeployment instead. + TyphaAffinity allows configuration of node affinity characteristics for Typha pods. + properties: + nodeAffinity: + description: + NodeAffinity describes node affinity scheduling rules + for typha. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated with the + corresponding weight. + properties: + matchExpressions: + description: + A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with matching the corresponding + nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + WARNING: Please note that if the affinity requirements specified by this field are not met at + scheduling time, the pod will NOT be scheduled onto the node. + There is no fallback to another affinity rules with this setting. + This may cause networking disruption or even catastrophic failure! + PreferredDuringSchedulingIgnoredDuringExecution should be used for affinity + unless there is a specific well understood reason to use RequiredDuringSchedulingIgnoredDuringExecution and + you can guarantee that the RequiredDuringSchedulingIgnoredDuringExecution will always have sufficient nodes to satisfy the requirement. + NOTE: RequiredDuringSchedulingIgnoredDuringExecution is set by default for AKS nodes, + to avoid scheduling Typhas on virtual-nodes. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. properties: - annotations: - additionalProperties: - type: string - description: |- - Annotations is a map of arbitrary non-identifying metadata. Each of these - key/value pairs are added to the object's annotations provided the key does not - already exist in the object's annotations. - type: object - labels: - additionalProperties: - type: string + nodeSelectorTerms: + description: + Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + type: object + typhaDeployment: + description: |- + TyphaDeployment configures the typha Deployment. If used in conjunction with the deprecated + ComponentResources or TyphaAffinity, then these overrides take precedence. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the specification of the typha Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the typha Deployment. + If omitted, the typha Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + strategy: + description: + The deployment strategy to use to replace existing + pods with new ones. + properties: + rollingUpdate: description: |- - Labels is a map of string keys and values that may match replicaset and - service selectors. Each of these key/value pairs are added to the - object's labels provided the key does not already exist in the object's labels. + Rolling update config params. Present only if DeploymentStrategyType = + RollingUpdate. + to be. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true type: object type: object - spec: + template: description: - Spec is the specification of the calico-kube-controllers - Deployment. + Template describes the typha Deployment pod that + will be created. properties: - minReadySeconds: + metadata: description: |- - MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should - be ready without any of its container crashing, for it to be considered available. - If specified, this overrides any minReadySeconds value that may be set on the calico-kube-controllers Deployment. - If omitted, the calico-kube-controllers Deployment will use its default value for minReadySeconds. - format: int32 - maximum: 2147483647 - minimum: 0 - type: integer - template: - description: - Template describes the calico-kube-controllers - Deployment pod that will be created. + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. properties: - metadata: + annotations: + additionalProperties: + type: string description: |- - Metadata is a subset of a Kubernetes object's metadata that is added to - the pod's metadata. - properties: - annotations: - additionalProperties: - type: string - description: |- - Annotations is a map of arbitrary non-identifying metadata. Each of these - key/value pairs are added to the object's annotations provided the key does not - already exist in the object's annotations. - type: object - labels: - additionalProperties: - type: string - description: |- - Labels is a map of string keys and values that may match replicaset and - service selectors. Each of these key/value pairs are added to the - object's labels provided the key does not already exist in the object's labels. - type: object + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. type: object - spec: - description: - Spec is the calico-kube-controllers Deployment's - PodSpec. + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the typha Deployment's PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the typha pods. + If specified, this overrides any affinity that may be set on the typha Deployment. + If omitted, the typha Deployment will use its default value for affinity. + If used in conjunction with the deprecated TyphaAffinity, then this value takes precedence. + WARNING: Please note that this field will override the default calico-typha Deployment affinity. properties: - affinity: - description: |- - Affinity is a group of affinity scheduling rules for the calico-kube-controllers pods. - If specified, this overrides any affinity that may be set on the calico-kube-controllers Deployment. - If omitted, the calico-kube-controllers Deployment will use its default value for affinity. - WARNING: Please note that this field will override the default calico-kube-controllers Deployment affinity. - properties: - nodeAffinity: - description: - Describes node affinity scheduling - rules for the pod. + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node matches the corresponding matchExpressions; the - node(s) with the highest sum are the most preferred. + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. items: description: |- - An empty preferred scheduling term matches all objects with implicit weight 0 - (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. properties: - preference: + matchExpressions: description: - A node selector term, - associated with the corresponding - weight. - properties: - matchExpressions: - description: - A list of node - selector requirements by node's - labels. - items: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: - The label - key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchFields: - description: - A list of node - selector requirements by node's - fields. - items: + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: - The label - key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - type: object - x-kubernetes-map-type: atomic - weight: + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: description: - Weight associated with - matching the corresponding nodeSelectorTerm, - in the range 1-100. - format: int32 - type: integer - required: - - preference - - weight + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic type: object + x-kubernetes-map-type: atomic type: array x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - If the affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to an update), the system - may or may not try to eventually evict the pod from its node. - properties: - nodeSelectorTerms: - description: - Required. A list of node - selector terms. The terms are ORed. - items: + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: description: |- - A null or empty node selector term matches no objects. The requirements of - them are ANDed. - The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. properties: matchExpressions: description: - A list of node - selector requirements by node's - labels. + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. items: description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. properties: key: description: - The label - key that the selector + key is the + label key that the selector applies to. type: string operator: description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. type: string values: description: |- - An array of string values. If the operator is In or NotIn, + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. + the values array must be empty. This array is replaced during a strategic + merge patch. items: type: string type: array @@ -17904,34 +17572,80 @@ spec: type: object type: array x-kubernetes-list-type: atomic - matchFields: + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: description: - A list of node - selector requirements by node's - fields. + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. items: description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. properties: key: description: - The label - key that the selector + key is the + label key that the selector applies to. type: string operator: description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. type: string values: description: |- - An array of string values. If the operator is In or NotIn, + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. + the values array must be empty. This array is replaced during a strategic + merge patch. items: type: string type: array @@ -17942,240 +17656,254 @@ spec: type: object type: array x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object type: object x-kubernetes-map-type: atomic - type: array - x-kubernetes-list-type: atomic - required: - - nodeSelectorTerms - type: object - x-kubernetes-map-type: atomic - type: object - podAffinity: - description: - Describes pod affinity scheduling - rules (e.g. co-locate this pod in the same - node, zone, etc. as some other pod(s)). - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the - node(s) with the highest sum are the most preferred. - items: - description: - The weights of all of the - matched WeightedPodAffinityTerm fields - are added per-node to find the most - preferred node(s) - properties: - podAffinityTerm: - description: - Required. A pod affinity - term, associated with the corresponding - weight. - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: - matchExpressions - is a list of label selector - requirements. The requirements - are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is - the label key that - the selector applies - to. - type: string - operator: - description: - |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: - |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). - items: + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: - matchExpressions - is a list of label selector - requirements. The requirements - are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is - the label key that - the selector applies - to. - type: string - operator: - description: - |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: - |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - weight: + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string description: |- - weight associated with matching the corresponding podAffinityTerm, - in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - If the affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to a pod label update), the - system may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding to each - podAffinityTerm are intersected, i.e. all terms must be satisfied. - items: + x-kubernetes-map-type: atomic + namespaces: description: |- - Defines a set of pods (namely those matching the labelSelector - relative to the given namespace(s)) that this pod should be - co-located (affinity) or not co-located (anti-affinity) with, - where co-located is defined as running on a node whose value of - the label with key matches that of any node on which - a pod of the set of pods is running + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. properties: labelSelector: description: |- @@ -18240,7 +17968,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -18255,7 +17982,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -18337,835 +18063,703 @@ spec: required: - topologyKey type: object - type: array - x-kubernetes-list-type: atomic - type: object - podAntiAffinity: - description: - Describes pod anti-affinity scheduling - rules (e.g. avoid putting this pod in the - same node, zone, etc. as some other pod(s)). - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the anti-affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the - node(s) with the highest sum are the most preferred. - items: - description: - The weights of all of the - matched WeightedPodAffinityTerm fields - are added per-node to find the most - preferred node(s) + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. properties: - podAffinityTerm: + matchExpressions: description: - Required. A pod affinity - term, associated with the corresponding - weight. - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: - matchExpressions - is a list of label selector - requirements. The requirements - are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is - the label key that - the selector applies - to. - type: string - operator: - description: - |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: - |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). - items: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: - matchExpressions - is a list of label selector - requirements. The requirements - are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is - the label key that - the selector applies - to. - type: string - operator: - description: - |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: - |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string description: |- - weight associated with matching the corresponding podAffinityTerm, - in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - If the anti-affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the anti-affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to a pod label update), the - system may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding to each - podAffinityTerm are intersected, i.e. all terms must be satisfied. - items: + x-kubernetes-map-type: atomic + matchLabelKeys: description: |- - Defines a set of pods (namely those matching the labelSelector - relative to the given namespace(s)) that this pod should be - co-located (affinity) or not co-located (anti-affinity) with, - where co-located is defined as running on a node whose value of - the label with key matches that of any node on which - a pod of the set of pods is running + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: - matchExpressions - is a list of label selector - requirements. The requirements - are ANDed. - items: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the - label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). - items: - type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object type: array x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). - items: + matchLabels: + additionalProperties: type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: - matchExpressions - is a list of label selector - requirements. The requirements - are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the - label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: - type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey type: object - type: array - x-kubernetes-list-type: atomic - type: object - type: object - containers: - description: |- - Containers is a list of calico-kube-controllers containers. - If specified, this overrides the specified calico-kube-controllers Deployment containers. - If omitted, the calico-kube-controllers Deployment will use its default values for its containers. - items: - description: - CalicoKubeControllersDeploymentContainer - is a calico-kube-controllers Deployment container. - properties: - name: - description: |- - Name is an enum which identifies the calico-kube-controllers Deployment container by name. - Supported values are: calico-kube-controllers, es-calico-kube-controllers - enum: - - calico-kube-controllers - - es-calico-kube-controllers - type: string - resources: - description: |- - Resources allows customization of limits and requests for compute resources such as cpu and memory. - If specified, this overrides the named calico-kube-controllers Deployment container's resources. - If omitted, the calico-kube-controllers Deployment will use its default value for this container's resources. - If used in conjunction with the deprecated ComponentResources, then this value takes precedence. - properties: - claims: + x-kubernetes-map-type: atomic + namespaces: description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - This field is immutable. It can only be set for containers. + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". items: - description: - ResourceClaim references - one entry in PodSpec.ResourceClaims. - properties: - name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. - type: string - request: - description: |- - Request is the name chosen for a request in the referenced claim. - If empty, everything from the claim is made available, otherwise - only the result of this request. - type: string - required: - - name - type: object + type: string type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true + x-kubernetes-list-type: atomic + topologyKey: description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey type: object - required: - - name - type: object - type: array - nodeSelector: - additionalProperties: - type: string - description: |- - NodeSelector is the calico-kube-controllers pod's scheduling constraints. - If specified, each of the key/value pairs are added to the calico-kube-controllers Deployment nodeSelector provided - the key does not already exist in the object's nodeSelector. - If used in conjunction with ControlPlaneNodeSelector, that nodeSelector is set on the calico-kube-controllers Deployment - and each of this field's key/value pairs are added to the calico-kube-controllers Deployment nodeSelector provided - the key does not already exist in the object's nodeSelector. - If omitted, the calico-kube-controllers Deployment will use its default value for nodeSelector. - WARNING: Please note that this field will modify the default calico-kube-controllers Deployment nodeSelector. + type: array + x-kubernetes-list-type: atomic type: object - tolerations: - description: |- - Tolerations is the calico-kube-controllers pod's tolerations. - If specified, this overrides any tolerations that may be set on the calico-kube-controllers Deployment. - If omitted, the calico-kube-controllers Deployment will use its default value for tolerations. - WARNING: Please note that this field will override the default calico-kube-controllers Deployment tolerations. - items: + type: object + containers: + description: |- + Containers is a list of typha containers. + If specified, this overrides the specified typha Deployment containers. + If omitted, the typha Deployment will use its default values for its containers. + items: + description: + TyphaDeploymentContainer is a typha + Deployment container. + properties: + name: description: |- - The pod this Toleration is attached to tolerates any taint that matches - the triple using the matching operator . + Name is an enum which identifies the typha Deployment container by name. + Supported values are: calico-typha + enum: + - calico-typha + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named typha Deployment container's resources. + If omitted, the typha Deployment will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. properties: - effect: + claims: description: |- - Effect indicates the taint effect to match. Empty means match all taint effects. - When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true description: |- - Key is the taint key that the toleration applies to. Empty means match all taint keys. - If the key is empty, operator must be Exists; this combination means to match all values and all keys. - type: string - operator: + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true description: |- - Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. - Exists is equivalent to wildcard for value, so that a pod can - tolerate all taints of a particular category. - type: string - tolerationSeconds: + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + initContainers: + description: |- + InitContainers is a list of typha init containers. + If specified, this overrides the specified typha Deployment init containers. + If omitted, the typha Deployment will use its default values for its init containers. + items: + description: + TyphaDeploymentInitContainer is a typha + Deployment init container. + properties: + name: + description: |- + Name is an enum which identifies the typha Deployment init container by name. + Supported values are: typha-certs-key-cert-provisioner + enum: + - typha-certs-key-cert-provisioner + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named typha Deployment init container's resources. + If omitted, the typha Deployment will use its default value for this init container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: description: |- - TolerationSeconds represents the period of time the toleration (which must be - of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, - it is not set, which means tolerate the taint forever (do not evict). Zero and - negative values will be treated as 0 (evict immediately) by the system. - format: int64 - type: integer - value: + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true description: |- - Value is the taint value the toleration matches to. - If the operator is Exists, the value should be empty, otherwise just a regular string. - type: string + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object type: object - type: array + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-typha pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-typha Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-typha Deployment will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-typha Deployment nodeSelector. type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + If this value is nil, the default grace period will be used instead. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + Defaults to 30 seconds. + format: int64 + type: integer + tolerations: + description: |- + Tolerations is the typha pod's tolerations. + If specified, this overrides any tolerations that may be set on the typha Deployment. + If omitted, the typha Deployment will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-typha Deployment tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array type: object type: object type: object - calicoNetwork: + type: object + typhaMetricsPort: + description: + TyphaMetricsPort specifies which port calico/typha serves + prometheus metrics on. By default, metrics are not enabled. + format: int32 + type: integer + variant: + description: |- + Variant is the product to install - one of Calico or TigeraSecureEnterprise + Default: Calico + enum: + - Calico + - TigeraSecureEnterprise + type: string + windowsNodes: + description: Windows Configuration + properties: + cniBinDir: + description: |- + CNIBinDir is the path to the CNI binaries directory on Windows, it must match what is used as 'bin_dir' under + [plugins] + [plugins."io.containerd.grpc.v1.cri"] + [plugins."io.containerd.grpc.v1.cri".cni] + on the containerd 'config.toml' file on the Windows nodes. + type: string + cniConfigDir: + description: |- + CNIConfigDir is the path to the CNI configuration directory on Windows, it must match what is used as 'conf_dir' under + [plugins] + [plugins."io.containerd.grpc.v1.cri"] + [plugins."io.containerd.grpc.v1.cri".cni] + on the containerd 'config.toml' file on the Windows nodes. + type: string + cniLogDir: description: - CalicoNetwork specifies networking configuration - options for Calico. - properties: - bgp: - description: - BGP configures whether or not to enable Calico's - BGP capabilities. - enum: - - Enabled - - Disabled - type: string - bpfNetworkBootstrap: - description: |- - BPFNetworkBootstrap manages the initial networking setup required to configure the BPF dataplane. - When enabled, the operator tries to bootstraps access to the Kubernetes API Server - by using the Kubernetes service and its associated endpoints. - This field should be enabled only if linuxDataplane is set to "BPF". - If another dataplane is selected, this field must be omitted or explicitly set to Disabled. - When disabled and linuxDataplane is BPF, you must manually provide the Kubernetes API Server - information via the "kubernetes-service-endpoint" ConfigMap. It is invalid to use both the ConfigMap - and have this field set to true at the same time. - Default: Disabled - enum: - - Disabled - - Enabled - type: string - containerIPForwarding: - description: |- - ContainerIPForwarding configures whether ip forwarding will be enabled for containers in the CNI configuration. - Default: Disabled - enum: - - Enabled - - Disabled - type: string - hostPorts: - description: |- - HostPorts configures whether or not Calico will support Kubernetes HostPorts. Valid only when using the Calico CNI plugin. - Default: Enabled - enum: - - Enabled - - Disabled - type: string - ipPools: - description: |- - IPPools contains a list of IP pools to manage. If nil, a single IPv4 IP pool - will be created by the operator. If an empty list is provided, the operator will not create any IP pools and will instead - wait for IP pools to be created out-of-band. - IP pools in this list will be reconciled by the operator and should not be modified out-of-band. - items: - properties: - allowedUses: - description: |- - AllowedUse controls what the IP pool will be used for. If not specified or empty, defaults to - ["Tunnel", "Workload"] for back-compatibility - items: - type: string - type: array - assignmentMode: - description: - AssignmentMode determines if IP addresses - from this pool should be assigned automatically or - on request only - type: string - blockSize: - description: |- - BlockSize specifies the CIDR prefex length to use when allocating per-node IP blocks from - the main IP pool CIDR. - Default: 26 (IPv4), 122 (IPv6) - format: int32 - type: integer - cidr: - description: - CIDR contains the address range for the - IP Pool in classless inter-domain routing format. - type: string - disableBGPExport: - default: false - description: |- - DisableBGPExport specifies whether routes from this IP pool's CIDR are exported over BGP. - Default: false - type: boolean - disableNewAllocations: - description: |- - DisableNewAllocations specifies whether or not new IP allocations are allowed from this pool. - This is useful when you want to prevent new pods from receiving IP addresses from this pool, without - impacting any existing pods that have already been assigned addresses from this pool. - type: boolean - encapsulation: - description: |- - Encapsulation specifies the encapsulation type that will be used with - the IP Pool. - Default: IPIP - enum: - - IPIPCrossSubnet - - IPIP - - VXLAN - - VXLANCrossSubnet - - None - type: string - name: - description: - Name is the name of the IP pool. If omitted, - this will be generated. - type: string - natOutgoing: - description: |- - NATOutgoing specifies if NAT will be enabled or disabled for outgoing traffic. - Default: Enabled - enum: - - Enabled - - Disabled - type: string - nodeSelector: - description: |- - NodeSelector specifies the node selector that will be set for the IP Pool. - Default: 'all()' - type: string - required: - - cidr - type: object - maxItems: 25 - type: array - kubeProxyManagement: - description: |- - KubeProxyManagement controls whether the operator manages the kube-proxy DaemonSet. - When enabled, the operator will manage the DaemonSet by patching it: - it disables kube-proxy if the dataplane is BPF, or enables it otherwise. - Default: Disabled - enum: - - Disabled - - Enabled - type: string - linuxDataplane: - description: |- - LinuxDataplane is used to select the dataplane used for Linux nodes. In particular, it - causes the operator to add required mounts and environment variables for the particular dataplane. - If not specified, iptables mode is used. - Default: Iptables - enum: - - Iptables - - BPF - - VPP - - Nftables - type: string - linuxPolicySetupTimeoutSeconds: - description: |- - LinuxPolicySetupTimeoutSeconds delays new pods from running containers - until their policy has been programmed in the dataplane. - The specified delay defines the maximum amount of time - that the Calico CNI plugin will wait for policy to be programmed. - Only applies to pods created on Linux nodes. - * A value of 0 disables pod startup delays. - Default: 0 - format: int32 - type: integer - mtu: - description: |- - MTU specifies the maximum transmission unit to use on the pod network. - If not specified, Calico will perform MTU auto-detection based on the cluster network. - format: int32 - type: integer - multiInterfaceMode: - description: |- - MultiInterfaceMode configures what will configure multiple interface per pod. Only valid for Calico Enterprise installations - using the Calico CNI plugin. - Default: None - enum: - - None - - Multus - type: string - nodeAddressAutodetectionV4: - description: |- - NodeAddressAutodetectionV4 specifies an approach to automatically detect node IPv4 addresses. If not specified, - will use default auto-detection settings to acquire an IPv4 address for each node. - properties: - canReach: - description: |- - CanReach enables IP auto-detection based on which source address on the node is used to reach the - specified IP or domain. - type: string - cidrs: - description: |- - CIDRS enables IP auto-detection based on which addresses on the nodes are within - one of the provided CIDRs. - items: - type: string - type: array - firstFound: - description: |- - FirstFound uses default interface matching parameters to select an interface, performing best-effort - filtering based on well-known interface names. - type: boolean - interface: - description: - Interface enables IP auto-detection based - on interfaces that match the given regex. - type: string - kubernetes: - description: - Kubernetes configures Calico to detect node - addresses based on the Kubernetes API. - enum: - - NodeInternalIP - type: string - skipInterface: - description: |- - SkipInterface enables IP auto-detection based on interfaces that do not match - the given regex. - type: string - type: object - nodeAddressAutodetectionV6: - description: |- - NodeAddressAutodetectionV6 specifies an approach to automatically detect node IPv6 addresses. If not specified, - IPv6 addresses will not be auto-detected. - properties: - canReach: - description: |- - CanReach enables IP auto-detection based on which source address on the node is used to reach the - specified IP or domain. - type: string - cidrs: - description: |- - CIDRS enables IP auto-detection based on which addresses on the nodes are within - one of the provided CIDRs. - items: - type: string - type: array - firstFound: - description: |- - FirstFound uses default interface matching parameters to select an interface, performing best-effort - filtering based on well-known interface names. - type: boolean - interface: - description: - Interface enables IP auto-detection based - on interfaces that match the given regex. - type: string - kubernetes: - description: - Kubernetes configures Calico to detect node - addresses based on the Kubernetes API. - enum: - - NodeInternalIP - type: string - skipInterface: - description: |- - SkipInterface enables IP auto-detection based on interfaces that do not match - the given regex. - type: string - type: object - sysctl: - description: - Sysctl configures sysctl parameters for tuning - plugin - items: - properties: - key: - enum: - - net.ipv4.tcp_keepalive_intvl - - net.ipv4.tcp_keepalive_probes - - net.ipv4.tcp_keepalive_time - type: string - value: - type: string - required: - - key - - value - type: object - type: array - windowsDataplane: + CNILogDir is the path to the Calico CNI logs directory + on Windows. + type: string + vxlanAdapter: + description: + VXLANAdapter is the Network Adapter used for VXLAN, + leave blank for primary NIC + type: string + vxlanMACPrefix: + description: + VXLANMACPrefix is the prefix used when generating + MAC addresses for virtual NICs + pattern: ^[0-9A-Fa-f]{2}-[0-9A-Fa-f]{2}$ + type: string + type: object + type: object + status: + description: + Most recently observed state for the Calico or Calico Enterprise + installation. + properties: + calicoVersion: + description: |- + CalicoVersion shows the current running version of calico. + CalicoVersion along with Variant is needed to know the exact + version deployed. + type: string + computed: + description: + Computed is the final installation including overlaid + resources. + properties: + azure: + description: + Azure is used to configure azure provider specific + options. + properties: + policyMode: + default: Default description: |- - WindowsDataplane is used to select the dataplane used for Windows nodes. In particular, it - causes the operator to add required mounts and environment variables for the particular dataplane. - If not specified, it is disabled and the operator will not render the Calico Windows nodes daemonset. - Default: Disabled + PolicyMode determines whether the "control-plane" label is applied to namespaces. It offers two options: Default and Manual. + The Default option adds the "control-plane" label to the required namespaces. + The Manual option does not apply the "control-plane" label to any namespace. + Default: Default enum: - - HNS - - Disabled + - Default + - Manual type: string type: object - calicoNodeDaemonSet: + calicoKubeControllersDeployment: description: |- - CalicoNodeDaemonSet configures the calico-node DaemonSet. If used in + CalicoKubeControllersDeployment configures the calico-kube-controllers Deployment. If used in conjunction with the deprecated ComponentResources, then these overrides take precedence. properties: metadata: description: Metadata is a subset of a Kubernetes object's - metadata that is added to the DaemonSet. + metadata that is added to the Deployment. properties: annotations: additionalProperties: @@ -19186,23 +18780,23 @@ spec: type: object spec: description: - Spec is the specification of the calico-node - DaemonSet. + Spec is the specification of the calico-kube-controllers + Deployment. properties: minReadySeconds: description: |- - MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should be ready without any of its container crashing, for it to be considered available. - If specified, this overrides any minReadySeconds value that may be set on the calico-node DaemonSet. - If omitted, the calico-node DaemonSet will use its default value for minReadySeconds. + If specified, this overrides any minReadySeconds value that may be set on the calico-kube-controllers Deployment. + If omitted, the calico-kube-controllers Deployment will use its default value for minReadySeconds. format: int32 maximum: 2147483647 minimum: 0 type: integer template: description: - Template describes the calico-node DaemonSet - pod that will be created. + Template describes the calico-kube-controllers + Deployment pod that will be created. properties: metadata: description: |- @@ -19227,14 +18821,16 @@ spec: type: object type: object spec: - description: Spec is the calico-node DaemonSet's PodSpec. + description: + Spec is the calico-kube-controllers Deployment's + PodSpec. properties: affinity: description: |- - Affinity is a group of affinity scheduling rules for the calico-node pods. - If specified, this overrides any affinity that may be set on the calico-node DaemonSet. - If omitted, the calico-node DaemonSet will use its default value for affinity. - WARNING: Please note that this field will override the default calico-node DaemonSet affinity. + Affinity is a group of affinity scheduling rules for the calico-kube-controllers pods. + If specified, this overrides any affinity that may be set on the calico-kube-controllers Deployment. + If omitted, the calico-kube-controllers Deployment will use its default value for affinity. + WARNING: Please note that this field will override the default calico-kube-controllers Deployment affinity. properties: nodeAffinity: description: @@ -19553,7 +19149,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -19568,7 +19163,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -19746,7 +19340,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -19761,7 +19354,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -19860,8 +19452,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -19942,7 +19534,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -19957,7 +19548,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -20135,7 +19725,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -20150,7 +19739,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -20238,182 +19826,34 @@ spec: type: object containers: description: |- - Containers is a list of calico-node containers. - If specified, this overrides the specified calico-node DaemonSet containers. - If omitted, the calico-node DaemonSet will use its default values for its containers. - items: - description: - CalicoNodeDaemonSetContainer is - a calico-node DaemonSet container. - properties: - name: - description: |- - Name is an enum which identifies the calico-node DaemonSet container by name. - Supported values are: calico-node - enum: - - calico-node - type: string - resources: - description: |- - Resources allows customization of limits and requests for compute resources such as cpu and memory. - If specified, this overrides the named calico-node DaemonSet container's resources. - If omitted, the calico-node DaemonSet will use its default value for this container's resources. - If used in conjunction with the deprecated ComponentResources, then this value takes precedence. - properties: - claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - This field is immutable. It can only be set for containers. - items: - description: - ResourceClaim references - one entry in PodSpec.ResourceClaims. - properties: - name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. - type: string - request: - description: |- - Request is the name chosen for a request in the referenced claim. - If empty, everything from the claim is made available, otherwise - only the result of this request. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - type: object - required: - - name - type: object - type: array - dnsConfig: - description: - DNSConfig allows customization of - the DNS configuration for the calico-node pods. - properties: - nameservers: - description: |- - A list of DNS name server IP addresses. - This will be appended to the base nameservers generated from DNSPolicy. - Duplicated nameservers will be removed. - items: - type: string - type: array - x-kubernetes-list-type: atomic - options: - description: |- - A list of DNS resolver options. - This will be merged with the base options generated from DNSPolicy. - Duplicated entries will be removed. Resolution options given in Options - will override those that appear in the base DNSPolicy. - items: - description: - PodDNSConfigOption defines - DNS resolver options of a pod. - properties: - name: - description: |- - Name is this DNS resolver option's name. - Required. - type: string - value: - description: - Value is this DNS resolver - option's value. - type: string - type: object - type: array - x-kubernetes-list-type: atomic - searches: - description: |- - A list of DNS search domains for host-name lookup. - This will be appended to the base search paths generated from DNSPolicy. - Duplicated search paths will be removed. - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - dnsPolicy: - description: - DNSPolicy is the DNS policy for the - calico-node pods. - enum: - - "" - - Default - - ClusterFirst - - ClusterFirstWithHostNet - - None - type: string - initContainers: - description: |- - InitContainers is a list of calico-node init containers. - If specified, this overrides the specified calico-node DaemonSet init containers. - If omitted, the calico-node DaemonSet will use its default values for its init containers. + Containers is a list of calico-kube-controllers containers. + If specified, this overrides the specified calico-kube-controllers Deployment containers. + If omitted, the calico-kube-controllers Deployment will use its default values for its containers. items: description: - CalicoNodeDaemonSetInitContainer - is a calico-node DaemonSet init container. + CalicoKubeControllersDeploymentContainer + is a calico-kube-controllers Deployment container. properties: name: description: |- - Name is an enum which identifies the calico-node DaemonSet init container by name. - Supported values are: install-cni, hostpath-init, flexvol-driver, ebpf-bootstrap, node-certs-key-cert-provisioner, calico-node-prometheus-server-tls-key-cert-provisioner, mount-bpffs (deprecated, replaced by ebpf-bootstrap) + Name is an enum which identifies the calico-kube-controllers Deployment container by name. + Supported values are: calico-kube-controllers, es-calico-kube-controllers enum: - - install-cni - - hostpath-init - - flexvol-driver - - ebpf-bootstrap - - node-certs-key-cert-provisioner - - calico-node-prometheus-server-tls-key-cert-provisioner - - mount-bpffs + - calico-kube-controllers + - es-calico-kube-controllers type: string resources: description: |- Resources allows customization of limits and requests for compute resources such as cpu and memory. - If specified, this overrides the named calico-node DaemonSet init container's resources. - If omitted, the calico-node DaemonSet will use its default value for this container's resources. + If specified, this overrides the named calico-kube-controllers Deployment container's resources. + If omitted, the calico-kube-controllers Deployment will use its default value for this container's resources. If used in conjunction with the deprecated ComponentResources, then this value takes precedence. properties: claims: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -20473,18 +19913,21 @@ spec: additionalProperties: type: string description: |- - NodeSelector is the calico-node pod's scheduling constraints. - If specified, each of the key/value pairs are added to the calico-node DaemonSet nodeSelector provided + NodeSelector is the calico-kube-controllers pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-kube-controllers Deployment nodeSelector provided the key does not already exist in the object's nodeSelector. - If omitted, the calico-node DaemonSet will use its default value for nodeSelector. - WARNING: Please note that this field will modify the default calico-node DaemonSet nodeSelector. + If used in conjunction with ControlPlaneNodeSelector, that nodeSelector is set on the calico-kube-controllers Deployment + and each of this field's key/value pairs are added to the calico-kube-controllers Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-kube-controllers Deployment will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-kube-controllers Deployment nodeSelector. type: object tolerations: description: |- - Tolerations is the calico-node pod's tolerations. - If specified, this overrides any tolerations that may be set on the calico-node DaemonSet. - If omitted, the calico-node DaemonSet will use its default value for tolerations. - WARNING: Please note that this field will override the default calico-node DaemonSet tolerations. + Tolerations is the calico-kube-controllers pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-kube-controllers Deployment. + If omitted, the calico-kube-controllers Deployment will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-kube-controllers Deployment tolerations. items: description: |- The pod this Toleration is attached to tolerates any taint that matches @@ -20526,10 +19969,292 @@ spec: type: object type: object type: object - calicoNodeWindowsDaemonSet: + calicoNetwork: description: - CalicoNodeWindowsDaemonSet configures the calico-node-windows - DaemonSet. + CalicoNetwork specifies networking configuration + options for Calico. + properties: + bgp: + description: + BGP configures whether or not to enable Calico's + BGP capabilities. + enum: + - Enabled + - Disabled + type: string + bpfNetworkBootstrap: + description: |- + BPFNetworkBootstrap manages the initial networking setup required to configure the BPF dataplane. + When enabled, the operator tries to bootstraps access to the Kubernetes API Server + by using the Kubernetes service and its associated endpoints. + This field should be enabled only if linuxDataplane is set to "BPF". + If another dataplane is selected, this field must be omitted or explicitly set to Disabled. + When disabled and linuxDataplane is BPF, you must manually provide the Kubernetes API Server + information via the "kubernetes-service-endpoint" ConfigMap. It is invalid to use both the ConfigMap + and have this field set to true at the same time. + Default: Disabled + enum: + - Disabled + - Enabled + type: string + containerIPForwarding: + description: |- + ContainerIPForwarding configures whether ip forwarding will be enabled for containers in the CNI configuration. + Default: Disabled + enum: + - Enabled + - Disabled + type: string + hostPorts: + description: |- + HostPorts configures whether or not Calico will support Kubernetes HostPorts. Valid only when using the Calico CNI plugin. + Default: Enabled + enum: + - Enabled + - Disabled + type: string + ipPools: + description: |- + IPPools contains a list of IP pools to manage. If nil, a single IPv4 IP pool + will be created by the operator. If an empty list is provided, the operator will not create any IP pools and will instead + wait for IP pools to be created out-of-band. + IP pools in this list will be reconciled by the operator and should not be modified out-of-band. + items: + properties: + allowedUses: + description: |- + AllowedUse controls what the IP pool will be used for. If not specified or empty, defaults to + ["Tunnel", "Workload"] for back-compatibility + items: + type: string + type: array + assignmentMode: + description: + AssignmentMode determines if IP addresses + from this pool should be assigned automatically or + on request only + type: string + blockSize: + description: |- + BlockSize specifies the CIDR prefex length to use when allocating per-node IP blocks from + the main IP pool CIDR. + Default: 26 (IPv4), 122 (IPv6) + format: int32 + type: integer + cidr: + description: + CIDR contains the address range for the + IP Pool in classless inter-domain routing format. + type: string + disableBGPExport: + default: false + description: |- + DisableBGPExport specifies whether routes from this IP pool's CIDR are exported over BGP. + Default: false + type: boolean + disableNewAllocations: + description: |- + DisableNewAllocations specifies whether or not new IP allocations are allowed from this pool. + This is useful when you want to prevent new pods from receiving IP addresses from this pool, without + impacting any existing pods that have already been assigned addresses from this pool. + type: boolean + encapsulation: + description: |- + Encapsulation specifies the encapsulation type that will be used with + the IP Pool. + Default: IPIP + enum: + - IPIPCrossSubnet + - IPIP + - VXLAN + - VXLANCrossSubnet + - None + type: string + name: + description: + Name is the name of the IP pool. If omitted, + this will be generated. + type: string + natOutgoing: + description: |- + NATOutgoing specifies if NAT will be enabled or disabled for outgoing traffic. + Default: Enabled + enum: + - Enabled + - Disabled + type: string + nodeSelector: + description: |- + NodeSelector specifies the node selector that will be set for the IP Pool. + Default: 'all()' + type: string + required: + - cidr + type: object + maxItems: 25 + type: array + kubeProxyManagement: + description: |- + KubeProxyManagement controls whether the operator manages the kube-proxy DaemonSet. + When enabled, the operator will manage the DaemonSet by patching it: + it disables kube-proxy if the dataplane is BPF, or enables it otherwise. + Default: Disabled + enum: + - Disabled + - Enabled + type: string + linuxDataplane: + description: |- + LinuxDataplane is used to select the dataplane used for Linux nodes. In particular, it + causes the operator to add required mounts and environment variables for the particular dataplane. + If not specified, iptables mode is used. + Default: Iptables + enum: + - Iptables + - BPF + - VPP + - Nftables + type: string + linuxPolicySetupTimeoutSeconds: + description: |- + LinuxPolicySetupTimeoutSeconds delays new pods from running containers + until their policy has been programmed in the dataplane. + The specified delay defines the maximum amount of time + that the Calico CNI plugin will wait for policy to be programmed. + Only applies to pods created on Linux nodes. + * A value of 0 disables pod startup delays. + Default: 0 + format: int32 + type: integer + mtu: + description: |- + MTU specifies the maximum transmission unit to use on the pod network. + If not specified, Calico will perform MTU auto-detection based on the cluster network. + format: int32 + type: integer + multiInterfaceMode: + description: |- + MultiInterfaceMode configures what will configure multiple interface per pod. Only valid for Calico Enterprise installations + using the Calico CNI plugin. + Default: None + enum: + - None + - Multus + type: string + nodeAddressAutodetectionV4: + description: |- + NodeAddressAutodetectionV4 specifies an approach to automatically detect node IPv4 addresses. If not specified, + will use default auto-detection settings to acquire an IPv4 address for each node. + properties: + canReach: + description: |- + CanReach enables IP auto-detection based on which source address on the node is used to reach the + specified IP or domain. + type: string + cidrs: + description: |- + CIDRS enables IP auto-detection based on which addresses on the nodes are within + one of the provided CIDRs. + items: + type: string + type: array + firstFound: + description: |- + FirstFound uses default interface matching parameters to select an interface, performing best-effort + filtering based on well-known interface names. + type: boolean + interface: + description: + Interface enables IP auto-detection based + on interfaces that match the given regex. + type: string + kubernetes: + description: + Kubernetes configures Calico to detect node + addresses based on the Kubernetes API. + enum: + - NodeInternalIP + type: string + skipInterface: + description: |- + SkipInterface enables IP auto-detection based on interfaces that do not match + the given regex. + type: string + type: object + nodeAddressAutodetectionV6: + description: |- + NodeAddressAutodetectionV6 specifies an approach to automatically detect node IPv6 addresses. If not specified, + IPv6 addresses will not be auto-detected. + properties: + canReach: + description: |- + CanReach enables IP auto-detection based on which source address on the node is used to reach the + specified IP or domain. + type: string + cidrs: + description: |- + CIDRS enables IP auto-detection based on which addresses on the nodes are within + one of the provided CIDRs. + items: + type: string + type: array + firstFound: + description: |- + FirstFound uses default interface matching parameters to select an interface, performing best-effort + filtering based on well-known interface names. + type: boolean + interface: + description: + Interface enables IP auto-detection based + on interfaces that match the given regex. + type: string + kubernetes: + description: + Kubernetes configures Calico to detect node + addresses based on the Kubernetes API. + enum: + - NodeInternalIP + type: string + skipInterface: + description: |- + SkipInterface enables IP auto-detection based on interfaces that do not match + the given regex. + type: string + type: object + sysctl: + description: + Sysctl configures sysctl parameters for tuning + plugin + items: + properties: + key: + enum: + - net.ipv4.tcp_keepalive_intvl + - net.ipv4.tcp_keepalive_probes + - net.ipv4.tcp_keepalive_time + type: string + value: + type: string + required: + - key + - value + type: object + type: array + windowsDataplane: + description: |- + WindowsDataplane is used to select the dataplane used for Windows nodes. In particular, it + causes the operator to add required mounts and environment variables for the particular dataplane. + If not specified, it is disabled and the operator will not render the Calico Windows nodes daemonset. + Default: Disabled + enum: + - HNS + - Disabled + type: string + type: object + calicoNodeDaemonSet: + description: |- + CalicoNodeDaemonSet configures the calico-node DaemonSet. If used in + conjunction with the deprecated ComponentResources, then these overrides take precedence. properties: metadata: description: @@ -20555,23 +20280,23 @@ spec: type: object spec: description: - Spec is the specification of the calico-node-windows + Spec is the specification of the calico-node DaemonSet. properties: minReadySeconds: description: |- MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should be ready without any of its container crashing, for it to be considered available. - If specified, this overrides any minReadySeconds value that may be set on the calico-node-windows DaemonSet. - If omitted, the calico-node-windows DaemonSet will use its default value for minReadySeconds. + If specified, this overrides any minReadySeconds value that may be set on the calico-node DaemonSet. + If omitted, the calico-node DaemonSet will use its default value for minReadySeconds. format: int32 maximum: 2147483647 minimum: 0 type: integer template: description: - Template describes the calico-node-windows - DaemonSet pod that will be created. + Template describes the calico-node DaemonSet + pod that will be created. properties: metadata: description: |- @@ -20596,16 +20321,14 @@ spec: type: object type: object spec: - description: - Spec is the calico-node-windows DaemonSet's - PodSpec. + description: Spec is the calico-node DaemonSet's PodSpec. properties: affinity: description: |- - Affinity is a group of affinity scheduling rules for the calico-node-windows pods. - If specified, this overrides any affinity that may be set on the calico-node-windows DaemonSet. - If omitted, the calico-node-windows DaemonSet will use its default value for affinity. - WARNING: Please note that this field will override the default calico-node-windows DaemonSet affinity. + Affinity is a group of affinity scheduling rules for the calico-node pods. + If specified, this overrides any affinity that may be set on the calico-node DaemonSet. + If omitted, the calico-node DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default calico-node DaemonSet affinity. properties: nodeAffinity: description: @@ -20924,7 +20647,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -20939,7 +20661,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -21117,7 +20838,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -21132,7 +20852,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -21231,8 +20950,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -21313,7 +21032,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -21328,7 +21046,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -21506,7 +21223,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -21521,7 +21237,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -21609,33 +21324,33 @@ spec: type: object containers: description: |- - Containers is a list of calico-node-windows containers. - If specified, this overrides the specified calico-node-windows DaemonSet containers. - If omitted, the calico-node-windows DaemonSet will use its default values for its containers. + Containers is a list of calico-node containers. + If specified, this overrides the specified calico-node DaemonSet containers. + If omitted, the calico-node DaemonSet will use its default values for its containers. items: description: - CalicoNodeWindowsDaemonSetContainer - is a calico-node-windows DaemonSet container. + CalicoNodeDaemonSetContainer is + a calico-node DaemonSet container. properties: name: description: |- - Name is an enum which identifies the calico-node-windows DaemonSet container by name. - Supported values are: calico-node-windows + Name is an enum which identifies the calico-node DaemonSet container by name. + Supported values are: calico-node enum: - - calico-node-windows + - calico-node type: string resources: description: |- Resources allows customization of limits and requests for compute resources such as cpu and memory. - If specified, this overrides the named calico-node-windows DaemonSet container's resources. - If omitted, the calico-node-windows DaemonSet will use its default value for this container's resources. + If specified, this overrides the named calico-node DaemonSet container's resources. + If omitted, the calico-node DaemonSet will use its default value for this container's resources. If used in conjunction with the deprecated ComponentResources, then this value takes precedence. properties: claims: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -21691,39 +21406,100 @@ spec: - name type: object type: array + dnsConfig: + description: + DNSConfig allows customization of + the DNS configuration for the calico-node pods. + properties: + nameservers: + description: |- + A list of DNS name server IP addresses. + This will be appended to the base nameservers generated from DNSPolicy. + Duplicated nameservers will be removed. + items: + type: string + type: array + x-kubernetes-list-type: atomic + options: + description: |- + A list of DNS resolver options. + This will be merged with the base options generated from DNSPolicy. + Duplicated entries will be removed. Resolution options given in Options + will override those that appear in the base DNSPolicy. + items: + description: + PodDNSConfigOption defines + DNS resolver options of a pod. + properties: + name: + description: |- + Name is this DNS resolver option's name. + Required. + type: string + value: + description: + Value is this DNS resolver + option's value. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + searches: + description: |- + A list of DNS search domains for host-name lookup. + This will be appended to the base search paths generated from DNSPolicy. + Duplicated search paths will be removed. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + dnsPolicy: + description: + DNSPolicy is the DNS policy for the + calico-node pods. + enum: + - "" + - Default + - ClusterFirst + - ClusterFirstWithHostNet + - None + type: string initContainers: description: |- - InitContainers is a list of calico-node-windows init containers. - If specified, this overrides the specified calico-node-windows DaemonSet init containers. - If omitted, the calico-node-windows DaemonSet will use its default values for its init containers. + InitContainers is a list of calico-node init containers. + If specified, this overrides the specified calico-node DaemonSet init containers. + If omitted, the calico-node DaemonSet will use its default values for its init containers. items: description: - CalicoNodeWindowsDaemonSetInitContainer - is a calico-node-windows DaemonSet init container. + CalicoNodeDaemonSetInitContainer + is a calico-node DaemonSet init container. properties: name: description: |- - Name is an enum which identifies the calico-node-windows DaemonSet init container by name. - Supported values are: install-cni;hostpath-init, flexvol-driver, node-certs-key-cert-provisioner, calico-node-windows-prometheus-server-tls-key-cert-provisioner + Name is an enum which identifies the calico-node DaemonSet init container by name. + Supported values are: install-cni, hostpath-init, flexvol-driver, ebpf-bootstrap, node-certs-key-cert-provisioner, calico-node-prometheus-server-tls-key-cert-provisioner, mount-bpffs (deprecated, replaced by ebpf-bootstrap) enum: - install-cni - hostpath-init - flexvol-driver + - ebpf-bootstrap - node-certs-key-cert-provisioner - - calico-node-windows-prometheus-server-tls-key-cert-provisioner + - calico-node-prometheus-server-tls-key-cert-provisioner + - mount-bpffs type: string resources: description: |- Resources allows customization of limits and requests for compute resources such as cpu and memory. - If specified, this overrides the named calico-node-windows DaemonSet init container's resources. - If omitted, the calico-node-windows DaemonSet will use its default value for this container's resources. + If specified, this overrides the named calico-node DaemonSet init container's resources. + If omitted, the calico-node DaemonSet will use its default value for this container's resources. If used in conjunction with the deprecated ComponentResources, then this value takes precedence. properties: claims: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -21783,18 +21559,18 @@ spec: additionalProperties: type: string description: |- - NodeSelector is the calico-node-windows pod's scheduling constraints. - If specified, each of the key/value pairs are added to the calico-node-windows DaemonSet nodeSelector provided + NodeSelector is the calico-node pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-node DaemonSet nodeSelector provided the key does not already exist in the object's nodeSelector. - If omitted, the calico-node-windows DaemonSet will use its default value for nodeSelector. - WARNING: Please note that this field will modify the default calico-node-windows DaemonSet nodeSelector. + If omitted, the calico-node DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-node DaemonSet nodeSelector. type: object tolerations: description: |- - Tolerations is the calico-node-windows pod's tolerations. - If specified, this overrides any tolerations that may be set on the calico-node-windows DaemonSet. - If omitted, the calico-node-windows DaemonSet will use its default value for tolerations. - WARNING: Please note that this field will override the default calico-node-windows DaemonSet tolerations. + Tolerations is the calico-node pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-node DaemonSet. + If omitted, the calico-node DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-node DaemonSet tolerations. items: description: |- The pod this Toleration is attached to tolerates any taint that matches @@ -21836,15 +21612,15 @@ spec: type: object type: object type: object - calicoWindowsUpgradeDaemonSet: - description: |- - Deprecated. The CalicoWindowsUpgradeDaemonSet is deprecated and will be removed from the API in the future. - CalicoWindowsUpgradeDaemonSet configures the calico-windows-upgrade DaemonSet. + calicoNodeWindowsDaemonSet: + description: + CalicoNodeWindowsDaemonSet configures the calico-node-windows + DaemonSet. properties: metadata: description: Metadata is a subset of a Kubernetes object's - metadata that is added to the Deployment. + metadata that is added to the DaemonSet. properties: annotations: additionalProperties: @@ -21865,22 +21641,22 @@ spec: type: object spec: description: - Spec is the specification of the calico-windows-upgrade + Spec is the specification of the calico-node-windows DaemonSet. properties: minReadySeconds: description: |- - MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should be ready without any of its container crashing, for it to be considered available. - If specified, this overrides any minReadySeconds value that may be set on the calico-windows-upgrade DaemonSet. - If omitted, the calico-windows-upgrade DaemonSet will use its default value for minReadySeconds. + If specified, this overrides any minReadySeconds value that may be set on the calico-node-windows DaemonSet. + If omitted, the calico-node-windows DaemonSet will use its default value for minReadySeconds. format: int32 maximum: 2147483647 minimum: 0 type: integer template: description: - Template describes the calico-windows-upgrade + Template describes the calico-node-windows DaemonSet pod that will be created. properties: metadata: @@ -21907,15 +21683,15 @@ spec: type: object spec: description: - Spec is the calico-windows-upgrade DaemonSet's + Spec is the calico-node-windows DaemonSet's PodSpec. properties: affinity: description: |- - Affinity is a group of affinity scheduling rules for the calico-windows-upgrade pods. - If specified, this overrides any affinity that may be set on the calico-windows-upgrade DaemonSet. - If omitted, the calico-windows-upgrade DaemonSet will use its default value for affinity. - WARNING: Please note that this field will override the default calico-windows-upgrade DaemonSet affinity. + Affinity is a group of affinity scheduling rules for the calico-node-windows pods. + If specified, this overrides any affinity that may be set on the calico-node-windows DaemonSet. + If omitted, the calico-node-windows DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default calico-node-windows DaemonSet affinity. properties: nodeAffinity: description: @@ -22234,7 +22010,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -22249,7 +22024,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -22427,7 +22201,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -22442,7 +22215,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -22541,8 +22313,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -22623,7 +22395,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -22638,7 +22409,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -22816,7 +22586,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -22831,7 +22600,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -22919,33 +22687,125 @@ spec: type: object containers: description: |- - Containers is a list of calico-windows-upgrade containers. - If specified, this overrides the specified calico-windows-upgrade DaemonSet containers. - If omitted, the calico-windows-upgrade DaemonSet will use its default values for its containers. + Containers is a list of calico-node-windows containers. + If specified, this overrides the specified calico-node-windows DaemonSet containers. + If omitted, the calico-node-windows DaemonSet will use its default values for its containers. items: description: - CalicoWindowsUpgradeDaemonSetContainer - is a calico-windows-upgrade DaemonSet container. + CalicoNodeWindowsDaemonSetContainer + is a calico-node-windows DaemonSet container. properties: name: - description: - Name is an enum which identifies - the calico-windows-upgrade DaemonSet container - by name. + description: |- + Name is an enum which identifies the calico-node-windows DaemonSet container by name. + Supported values are: node, felix, confd + calico-node-windows is allowed because it was previously allowed. enum: - - calico-windows-upgrade + - calico-node-windows + - node + - felix + - confd type: string resources: description: |- Resources allows customization of limits and requests for compute resources such as cpu and memory. - If specified, this overrides the named calico-windows-upgrade DaemonSet container's resources. - If omitted, the calico-windows-upgrade DaemonSet will use its default value for this container's resources. + If specified, this overrides the named DaemonSet container's resources. + If omitted, the DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + initContainers: + description: |- + InitContainers is a list of calico-node-windows init containers. + If specified, this overrides the specified calico-node-windows DaemonSet init containers. + If omitted, the calico-node-windows DaemonSet will use its default values for its init containers. + items: + description: + CalicoNodeWindowsDaemonSetInitContainer + is a calico-node-windows DaemonSet init container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node-windows DaemonSet init container by name. + Supported values are: install-cni;hostpath-init, flexvol-driver, node-certs-key-cert-provisioner, calico-node-windows-prometheus-server-tls-key-cert-provisioner + enum: + - install-cni + - hostpath-init + - flexvol-driver + - node-certs-key-cert-provisioner + - calico-node-windows-prometheus-server-tls-key-cert-provisioner + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-node-windows DaemonSet init container's resources. + If omitted, the calico-node-windows DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. properties: claims: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -23005,18 +22865,18 @@ spec: additionalProperties: type: string description: |- - NodeSelector is the calico-windows-upgrade pod's scheduling constraints. - If specified, each of the key/value pairs are added to the calico-windows-upgrade DaemonSet nodeSelector provided + NodeSelector is the calico-node-windows pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-node-windows DaemonSet nodeSelector provided the key does not already exist in the object's nodeSelector. - If omitted, the calico-windows-upgrade DaemonSet will use its default value for nodeSelector. - WARNING: Please note that this field will modify the default calico-windows-upgrade DaemonSet nodeSelector. + If omitted, the calico-node-windows DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-node-windows DaemonSet nodeSelector. type: object tolerations: description: |- - Tolerations is the calico-windows-upgrade pod's tolerations. - If specified, this overrides any tolerations that may be set on the calico-windows-upgrade DaemonSet. - If omitted, the calico-windows-upgrade DaemonSet will use its default value for tolerations. - WARNING: Please note that this field will override the default calico-windows-upgrade DaemonSet tolerations. + Tolerations is the calico-node-windows pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-node-windows DaemonSet. + If omitted, the calico-node-windows DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-node-windows DaemonSet tolerations. items: description: |- The pod this Toleration is attached to tolerates any taint that matches @@ -23058,272 +22918,15 @@ spec: type: object type: object type: object - certificateManagement: - description: |- - CertificateManagement configures pods to submit a CertificateSigningRequest to the certificates.k8s.io/v1 API in order - to obtain TLS certificates. This feature requires that you bring your own CSR signing and approval process, otherwise - pods will be stuck during initialization. - properties: - caCert: - description: - Certificate of the authority that signs the CertificateSigningRequests - in PEM format. - format: byte - type: string - keyAlgorithm: - description: |- - Specify the algorithm used by pods to generate a key pair that is associated with the X.509 certificate request. - Default: RSAWithSize2048 - enum: - - "" - - RSAWithSize2048 - - RSAWithSize4096 - - RSAWithSize8192 - - ECDSAWithCurve256 - - ECDSAWithCurve384 - - ECDSAWithCurve521 - type: string - signatureAlgorithm: - description: |- - Specify the algorithm used for the signature of the X.509 certificate request. - Default: SHA256WithRSA - enum: - - "" - - SHA256WithRSA - - SHA384WithRSA - - SHA512WithRSA - - ECDSAWithSHA256 - - ECDSAWithSHA384 - - ECDSAWithSHA512 - type: string - signerName: - description: |- - When a CSR is issued to the certificates.k8s.io API, the signerName is added to the request in order to accommodate for clusters - with multiple signers. - Must be formatted as: `/`. - type: string - required: - - caCert - - signerName - type: object - cni: - description: CNI specifies the CNI that will be used by this installation. - properties: - binDir: - description: |- - BinDir is the path to the CNI binaries directory. - If you have changed the installation directory for CNI binaries in the container runtime configuration, - please ensure that this field points to the same directory as specified in the container runtime settings. - Default directory depends on the KubernetesProvider. - * For KubernetesProvider GKE, this field defaults to "/home/kubernetes/bin". - * For KubernetesProvider OpenShift, this field defaults to "/var/lib/cni/bin". - * Otherwise, this field defaults to "/opt/cni/bin". - type: string - confDir: - description: |- - ConfDir is the path to the CNI config directory. - If you have changed the installation directory for CNI configuration in the container runtime configuration, - please ensure that this field points to the same directory as specified in the container runtime settings. - Default directory depends on the KubernetesProvider. - * For KubernetesProvider GKE, this field defaults to "/etc/cni/net.d". - * For KubernetesProvider OpenShift, this field defaults to "/var/run/multus/cni/net.d". - * Otherwise, this field defaults to "/etc/cni/net.d". - type: string - ipam: - description: |- - IPAM specifies the pod IP address management that will be used in the Calico or - Calico Enterprise installation. - properties: - type: - description: |- - Specifies the IPAM plugin that will be used in the Calico or Calico Enterprise installation. - * For CNI Plugin Calico, this field defaults to Calico. - * For CNI Plugin GKE, this field defaults to HostLocal. - * For CNI Plugin AzureVNET, this field defaults to AzureVNET. - * For CNI Plugin AmazonVPC, this field defaults to AmazonVPC. - The IPAM plugin is installed and configured only if the CNI plugin is set to Calico, - for all other values of the CNI plugin the plugin binaries and CNI config is a dependency - that is expected to be installed separately. - Default: Calico - enum: - - Calico - - HostLocal - - AmazonVPC - - AzureVNET - type: string - required: - - type - type: object - type: - description: |- - Specifies the CNI plugin that will be used in the Calico or Calico Enterprise installation. - * For KubernetesProvider GKE, this field defaults to GKE. - * For KubernetesProvider AKS, this field defaults to AzureVNET. - * For KubernetesProvider EKS, this field defaults to AmazonVPC. - * If aws-node daemonset exists in kube-system when the Installation resource is created, this field defaults to AmazonVPC. - * For all other cases this field defaults to Calico. - For the value Calico, the CNI plugin binaries and CNI config will be installed as part of deployment, - for all other values the CNI plugin binaries and CNI config is a dependency that is expected - to be installed separately. - Default: Calico - enum: - - Calico - - GKE - - AmazonVPC - - AzureVNET - type: string - required: - - type - type: object - componentResources: - description: |- - Deprecated. Please use CalicoNodeDaemonSet, TyphaDeployment, and KubeControllersDeployment. - ComponentResources can be used to customize the resource requirements for each component. - Node, Typha, and KubeControllers are supported for installations. - items: - description: |- - Deprecated. Please use component resource config fields in Installation.Spec instead. - The ComponentResource struct associates a ResourceRequirements with a component by name - properties: - componentName: - description: - ComponentName is an enum which identifies the - component - enum: - - Node - - Typha - - KubeControllers - type: string - resourceRequirements: - description: - ResourceRequirements allows customization of - limits and requests for compute resources such as cpu - and memory. - properties: - claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - This field is immutable. It can only be set for containers. - items: - description: - ResourceClaim references one entry in - PodSpec.ResourceClaims. - properties: - name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. - type: string - request: - description: |- - Request is the name chosen for a request in the referenced claim. - If empty, everything from the claim is made available, otherwise - only the result of this request. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - type: object - required: - - componentName - - resourceRequirements - type: object - type: array - controlPlaneNodeSelector: - additionalProperties: - type: string - description: |- - ControlPlaneNodeSelector is used to select control plane nodes on which to run Calico - components. This is globally applied to all resources created by the operator excluding daemonsets. - type: object - controlPlaneReplicas: - description: |- - ControlPlaneReplicas defines how many replicas of the control plane core components will be deployed. - This field applies to all control plane components that support High Availability. Defaults to 2. - format: int32 - type: integer - controlPlaneTolerations: + calicoWindowsUpgradeDaemonSet: description: |- - ControlPlaneTolerations specify tolerations which are then globally applied to all resources - created by the operator. - items: - description: |- - The pod this Toleration is attached to tolerates any taint that matches - the triple using the matching operator . - properties: - effect: - description: |- - Effect indicates the taint effect to match. Empty means match all taint effects. - When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: |- - Key is the taint key that the toleration applies to. Empty means match all taint keys. - If the key is empty, operator must be Exists; this combination means to match all values and all keys. - type: string - operator: - description: |- - Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. - Exists is equivalent to wildcard for value, so that a pod can - tolerate all taints of a particular category. - type: string - tolerationSeconds: - description: |- - TolerationSeconds represents the period of time the toleration (which must be - of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, - it is not set, which means tolerate the taint forever (do not evict). Zero and - negative values will be treated as 0 (evict immediately) by the system. - format: int64 - type: integer - value: - description: |- - Value is the taint value the toleration matches to. - If the operator is Exists, the value should be empty, otherwise just a regular string. - type: string - type: object - type: array - csiNodeDriverDaemonSet: - description: - CSINodeDriverDaemonSet configures the csi-node-driver - DaemonSet. + Deprecated. The CalicoWindowsUpgradeDaemonSet is deprecated and will be removed from the API in the future. + CalicoWindowsUpgradeDaemonSet configures the calico-windows-upgrade DaemonSet. properties: metadata: description: Metadata is a subset of a Kubernetes object's - metadata that is added to the DaemonSet. + metadata that is added to the Deployment. properties: annotations: additionalProperties: @@ -23344,23 +22947,23 @@ spec: type: object spec: description: - Spec is the specification of the csi-node-driver + Spec is the specification of the calico-windows-upgrade DaemonSet. properties: minReadySeconds: description: |- - MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should be ready without any of its container crashing, for it to be considered available. - If specified, this overrides any minReadySeconds value that may be set on the csi-node-driver DaemonSet. - If omitted, the csi-node-driver DaemonSet will use its default value for minReadySeconds. + If specified, this overrides any minReadySeconds value that may be set on the calico-windows-upgrade DaemonSet. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for minReadySeconds. format: int32 maximum: 2147483647 minimum: 0 type: integer template: description: - Template describes the csi-node-driver DaemonSet - pod that will be created. + Template describes the calico-windows-upgrade + DaemonSet pod that will be created. properties: metadata: description: |- @@ -23386,15 +22989,15 @@ spec: type: object spec: description: - Spec is the csi-node-driver DaemonSet's + Spec is the calico-windows-upgrade DaemonSet's PodSpec. properties: affinity: description: |- - Affinity is a group of affinity scheduling rules for the csi-node-driver pods. - If specified, this overrides any affinity that may be set on the csi-node-driver DaemonSet. - If omitted, the csi-node-driver DaemonSet will use its default value for affinity. - WARNING: Please note that this field will override the default csi-node-driver DaemonSet affinity. + Affinity is a group of affinity scheduling rules for the calico-windows-upgrade pods. + If specified, this overrides any affinity that may be set on the calico-windows-upgrade DaemonSet. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default calico-windows-upgrade DaemonSet affinity. properties: nodeAffinity: description: @@ -23713,7 +23316,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -23728,7 +23330,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -23906,7 +23507,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -23921,7 +23521,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -24020,8 +23619,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -24102,7 +23701,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -24117,7 +23715,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -24295,7 +23892,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -24310,7 +23906,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -24398,34 +23993,33 @@ spec: type: object containers: description: |- - Containers is a list of csi-node-driver containers. - If specified, this overrides the specified csi-node-driver DaemonSet containers. - If omitted, the csi-node-driver DaemonSet will use its default values for its containers. + Containers is a list of calico-windows-upgrade containers. + If specified, this overrides the specified calico-windows-upgrade DaemonSet containers. + If omitted, the calico-windows-upgrade DaemonSet will use its default values for its containers. items: description: - CSINodeDriverDaemonSetContainer - is a csi-node-driver DaemonSet container. + CalicoWindowsUpgradeDaemonSetContainer + is a calico-windows-upgrade DaemonSet container. properties: name: - description: |- - Name is an enum which identifies the csi-node-driver DaemonSet container by name. - Supported values are: calico-csi, csi-node-driver-registrar. + description: + Name is an enum which identifies + the calico-windows-upgrade DaemonSet container + by name. enum: - - calico-csi - - csi-node-driver-registrar - - csi-node-driver + - calico-windows-upgrade type: string resources: description: |- Resources allows customization of limits and requests for compute resources such as cpu and memory. - If specified, this overrides the named csi-node-driver DaemonSet container's resources. - If omitted, the csi-node-driver DaemonSet will use its default value for this container's resources. + If specified, this overrides the named calico-windows-upgrade DaemonSet container's resources. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for this container's resources. properties: claims: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -24485,18 +24079,18 @@ spec: additionalProperties: type: string description: |- - NodeSelector is the csi-node-driver pod's scheduling constraints. - If specified, each of the key/value pairs are added to the csi-node-driver DaemonSet nodeSelector provided + NodeSelector is the calico-windows-upgrade pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-windows-upgrade DaemonSet nodeSelector provided the key does not already exist in the object's nodeSelector. - If omitted, the csi-node-driver DaemonSet will use its default value for nodeSelector. - WARNING: Please note that this field will modify the default csi-node-driver DaemonSet nodeSelector. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-windows-upgrade DaemonSet nodeSelector. type: object tolerations: description: |- - Tolerations is the csi-node-driver pod's tolerations. - If specified, this overrides any tolerations that may be set on the csi-node-driver DaemonSet. - If omitted, the csi-node-driver DaemonSet will use its default value for tolerations. - WARNING: Please note that this field will override the default csi-node-driver DaemonSet tolerations. + Tolerations is the calico-windows-upgrade pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-windows-upgrade DaemonSet. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-windows-upgrade DaemonSet tolerations. items: description: |- The pod this Toleration is attached to tolerates any taint that matches @@ -24538,655 +24132,5084 @@ spec: type: object type: object type: object - fipsMode: - description: |- - FIPSMode uses images and features only that are using FIPS 140-2 validated cryptographic modules and standards. - Only supported for Variant=Calico. - Default: Disabled - enum: - - Enabled - - Disabled - type: string - flexVolumePath: - description: |- - FlexVolumePath optionally specifies a custom path for FlexVolume. If not specified, FlexVolume will be - enabled by default. If set to 'None', FlexVolume will be disabled. The default is based on the - kubernetesProvider. - type: string - imagePath: - description: |- - ImagePath allows for the path part of an image to be specified. If specified - then the specified value will be used as the image path for each image. If not specified - or empty, the default for each image will be used. - A special case value, UseDefault, is supported to explicitly specify the default - image path will be used for each image. - Image format: - `/:` - This option allows configuring the `` portion of the above format. - type: string - imagePrefix: - description: |- - ImagePrefix allows for the prefix part of an image to be specified. If specified - then the given value will be used as a prefix on each image. If not specified - or empty, no prefix will be used. - A special case value, UseDefault, is supported to explicitly specify the default - image prefix will be used for each image. - Image format: - `/:` - This option allows configuring the `` portion of the above format. - type: string - imagePullSecrets: - description: |- - ImagePullSecrets is an array of references to container registry pull secrets to use. These are - applied to all images to be pulled. - items: - description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - type: array - kubeletVolumePluginPath: - description: |- - KubeletVolumePluginPath optionally specifies enablement of Calico CSI plugin. If not specified, - CSI will be enabled by default. If set to 'None', CSI will be disabled. - Default: /var/lib/kubelet - type: string - kubernetesProvider: - description: |- - KubernetesProvider specifies a particular provider of the Kubernetes platform and enables provider-specific configuration. - If the specified value is empty, the Operator will attempt to automatically determine the current provider. - If the specified value is not empty, the Operator will still attempt auto-detection, but - will additionally compare the auto-detected value to the specified value to confirm they match. - enum: - - "" - - EKS - - GKE - - AKS - - OpenShift - - DockerEnterprise - - RKE2 - - TKG - - Kind - type: string - logging: - description: Logging Configuration for Components - properties: - cni: - description: - Customized logging specification for calico-cni - plugin - properties: - logFileMaxAgeDays: - description: "Default: 30 (days)" - format: int32 - type: integer - logFileMaxCount: - description: "Default: 10" - format: int32 - type: integer - logFileMaxSize: - anyOf: - - type: integer - - type: string - description: "Default: 100Mi" - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - logSeverity: - description: "Default: Info" - enum: - - Error - - Warning - - Info - - Debug - type: string - type: object - type: object - nodeMetricsPort: - description: |- - NodeMetricsPort specifies which port calico/node serves prometheus metrics on. By default, metrics are not enabled. - If specified, this overrides any FelixConfiguration resources which may exist. If omitted, then - prometheus metrics may still be configured through FelixConfiguration. - format: int32 - type: integer - nodeUpdateStrategy: + certificateManagement: description: |- - NodeUpdateStrategy can be used to customize the desired update strategy, such as the MaxUnavailable - field. + CertificateManagement configures pods to submit a CertificateSigningRequest to the certificates.k8s.io/v1 API in order + to obtain TLS certificates. This feature requires that you bring your own CSR signing and approval process, otherwise + pods will be stuck during initialization. properties: - rollingUpdate: - description: - Rolling update config params. Present only if - type = "RollingUpdate". - properties: - maxSurge: - anyOf: - - type: integer - - type: string - description: |- - The maximum number of nodes with an existing available DaemonSet pod that - can have an updated DaemonSet pod during during an update. - Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). - This can not be 0 if MaxUnavailable is 0. - Absolute number is calculated from percentage by rounding up to a minimum of 1. - Default value is 0. - Example: when this is set to 30%, at most 30% of the total number of nodes - that should be running the daemon pod (i.e. status.desiredNumberScheduled) - can have their a new pod created before the old pod is marked as deleted. - The update starts by launching new pods on 30% of nodes. Once an updated - pod is available (Ready for at least minReadySeconds) the old DaemonSet pod - on that node is marked deleted. If the old pod becomes unavailable for any - reason (Ready transitions to false, is evicted, or is drained) an updated - pod is immediatedly created on that node without considering surge limits. - Allowing surge implies the possibility that the resources consumed by the - daemonset on any given node can double if the readiness check fails, and - so resource intensive daemonsets should take into account that they may - cause evictions during disruption. - x-kubernetes-int-or-string: true - maxUnavailable: - anyOf: - - type: integer - - type: string - description: |- - The maximum number of DaemonSet pods that can be unavailable during the - update. Value can be an absolute number (ex: 5) or a percentage of total - number of DaemonSet pods at the start of the update (ex: 10%). Absolute - number is calculated from percentage by rounding up. - This cannot be 0 if MaxSurge is 0 - Default value is 1. - Example: when this is set to 30%, at most 30% of the total number of nodes - that should be running the daemon pod (i.e. status.desiredNumberScheduled) - can have their pods stopped for an update at any given time. The update - starts by stopping at most 30% of those DaemonSet pods and then brings - up new DaemonSet pods in their place. Once the new pods are available, - it then proceeds onto other DaemonSet pods, thus ensuring that at least - 70% of original number of DaemonSet pods are available at all times during - the update. - x-kubernetes-int-or-string: true - type: object - type: + caCert: description: - Type of daemon set update. Can be "RollingUpdate" - or "OnDelete". Default is RollingUpdate. + Certificate of the authority that signs the CertificateSigningRequests + in PEM format. + format: byte + type: string + keyAlgorithm: + description: |- + Specify the algorithm used by pods to generate a key pair that is associated with the X.509 certificate request. + Default: RSAWithSize2048 + enum: + - "" + - RSAWithSize2048 + - RSAWithSize4096 + - RSAWithSize8192 + - ECDSAWithCurve256 + - ECDSAWithCurve384 + - ECDSAWithCurve521 + type: string + signatureAlgorithm: + description: |- + Specify the algorithm used for the signature of the X.509 certificate request. + Default: SHA256WithRSA + enum: + - "" + - SHA256WithRSA + - SHA384WithRSA + - SHA512WithRSA + - ECDSAWithSHA256 + - ECDSAWithSHA384 + - ECDSAWithSHA512 + type: string + signerName: + description: |- + When a CSR is issued to the certificates.k8s.io API, the signerName is added to the request in order to accommodate for clusters + with multiple signers. + Must be formatted as: `/`. type: string + required: + - caCert + - signerName type: object - nonPrivileged: - description: - NonPrivileged configures Calico to be run in non-privileged - containers as non-root users where possible. - type: string - proxy: - description: |- - Proxy is used to configure the HTTP(S) proxy settings that will be applied to Tigera containers that connect - to destinations outside the cluster. It is expected that NO_PROXY is configured such that destinations within - the cluster (including the API server) are exempt from proxying. + cni: + description: CNI specifies the CNI that will be used by this installation. properties: - httpProxy: + binDir: description: |- - HTTPProxy defines the value of the HTTP_PROXY environment variable that will be set on Tigera containers that connect to - destinations outside the cluster. + BinDir is the path to the CNI binaries directory. + If you have changed the installation directory for CNI binaries in the container runtime configuration, + please ensure that this field points to the same directory as specified in the container runtime settings. + Default directory depends on the KubernetesProvider. + * For KubernetesProvider GKE, this field defaults to "/home/kubernetes/bin". + * For KubernetesProvider OpenShift, this field defaults to "/var/lib/cni/bin". + * Otherwise, this field defaults to "/opt/cni/bin". type: string - httpsProxy: + confDir: description: |- - HTTPSProxy defines the value of the HTTPS_PROXY environment variable that will be set on Tigera containers that connect to - destinations outside the cluster. + ConfDir is the path to the CNI config directory. + If you have changed the installation directory for CNI configuration in the container runtime configuration, + please ensure that this field points to the same directory as specified in the container runtime settings. + Default directory depends on the KubernetesProvider. + * For KubernetesProvider GKE, this field defaults to "/etc/cni/net.d". + * For KubernetesProvider OpenShift, this field defaults to "/var/run/multus/cni/net.d". + * Otherwise, this field defaults to "/etc/cni/net.d". type: string - noProxy: + ipam: description: |- - NoProxy defines the value of the NO_PROXY environment variable that will be set on Tigera containers that connect to - destinations outside the cluster. This value must be set such that destinations within the scope of the cluster, including - the Kubernetes API server, are exempt from being proxied. + IPAM specifies the pod IP address management that will be used in the Calico or + Calico Enterprise installation. + properties: + type: + description: |- + Specifies the IPAM plugin that will be used in the Calico or Calico Enterprise installation. + * For CNI Plugin Calico, this field defaults to Calico. + * For CNI Plugin GKE, this field defaults to HostLocal. + * For CNI Plugin AzureVNET, this field defaults to AzureVNET. + * For CNI Plugin AmazonVPC, this field defaults to AmazonVPC. + The IPAM plugin is installed and configured only if the CNI plugin is set to Calico, + for all other values of the CNI plugin the plugin binaries and CNI config is a dependency + that is expected to be installed separately. + Default: Calico + enum: + - Calico + - HostLocal + - AmazonVPC + - AzureVNET + type: string + required: + - type + type: object + type: + description: |- + Specifies the CNI plugin that will be used in the Calico or Calico Enterprise installation. + * For KubernetesProvider GKE, this field defaults to GKE. + * For KubernetesProvider AKS, this field defaults to AzureVNET. + * For KubernetesProvider EKS, this field defaults to AmazonVPC. + * If aws-node daemonset exists in kube-system when the Installation resource is created, this field defaults to AmazonVPC. + * For all other cases this field defaults to Calico. + For the value Calico, the CNI plugin binaries and CNI config will be installed as part of deployment, + for all other values the CNI plugin binaries and CNI config is a dependency that is expected + to be installed separately. + Default: Calico + enum: + - Calico + - GKE + - AmazonVPC + - AzureVNET type: string + required: + - type type: object - registry: + componentResources: description: |- - Registry is the default Docker registry used for component Docker images. - If specified then the given value must end with a slash character (`/`) and all images will be pulled from this registry. - If not specified then the default registries will be used. A special case value, UseDefault, is - supported to explicitly specify the default registries will be used. - Image format: - `/:` - This option allows configuring the `` portion of the above format. - type: string - serviceCIDRs: - description: - Kubernetes Service CIDRs. Specifying this is required - when using Calico for Windows. - items: - type: string - type: array - tlsCipherSuites: - description: - TLSCipherSuites defines the cipher suite list that - the TLS protocol should use during secure communication. + Deprecated. Please use CalicoNodeDaemonSet, TyphaDeployment, and KubeControllersDeployment. + ComponentResources can be used to customize the resource requirements for each component. + Node, Typha, and KubeControllers are supported for installations. items: + description: |- + Deprecated. Please use component resource config fields in Installation.Spec instead. + The ComponentResource struct associates a ResourceRequirements with a component by name properties: - name: - description: This should be a valid TLS cipher suite name. + componentName: + description: + ComponentName is an enum which identifies the + component enum: - - TLS_AES_256_GCM_SHA384 - - TLS_CHACHA20_POLY1305_SHA256 - - TLS_AES_128_GCM_SHA256 - - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 - - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 - - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - - TLS_RSA_WITH_AES_256_GCM_SHA384 - - TLS_RSA_WITH_AES_128_GCM_SHA256 - - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA - - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA - - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + - Node + - Typha + - KubeControllers + - NodeWindows + - FelixWindows + - ConfdWindows type: string + resourceRequirements: + description: + ResourceRequirements allows customization of + limits and requests for compute resources such as cpu + and memory. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references one entry in + PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - componentName + - resourceRequirements type: object type: array - typhaAffinity: + controlPlaneNodeSelector: + additionalProperties: + type: string description: |- - Deprecated. Please use Installation.Spec.TyphaDeployment instead. - TyphaAffinity allows configuration of node affinity characteristics for Typha pods. - properties: - nodeAffinity: - description: - NodeAffinity describes node affinity scheduling - rules for typha. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. - items: - description: |- - An empty preferred scheduling term matches all objects with implicit weight 0 - (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). - properties: - preference: - description: - A node selector term, associated with - the corresponding weight. - properties: - matchExpressions: - description: - A list of node selector requirements - by node's labels. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: - The label key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchFields: - description: - A list of node selector requirements - by node's fields. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. + ControlPlaneNodeSelector is used to select control plane nodes on which to run Calico + components. This is globally applied to all resources created by the operator excluding daemonsets. + type: object + controlPlaneReplicas: + description: |- + ControlPlaneReplicas defines how many replicas of the control plane core components will be deployed. + This field applies to all control plane components that support High Availability. Defaults to 2. + format: int32 + type: integer + controlPlaneTolerations: + description: |- + ControlPlaneTolerations specify tolerations which are then globally applied to all resources + created by the operator. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + csiNodeDriverDaemonSet: + description: + CSINodeDriverDaemonSet configures the csi-node-driver + DaemonSet. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the DaemonSet. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the csi-node-driver + DaemonSet. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the csi-node-driver DaemonSet. + If omitted, the csi-node-driver DaemonSet will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the csi-node-driver DaemonSet + pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the csi-node-driver DaemonSet's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the csi-node-driver pods. + If specified, this overrides any affinity that may be set on the csi-node-driver DaemonSet. + If omitted, the csi-node-driver DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default csi-node-driver DaemonSet affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. properties: - key: - description: - The label key that the selector - applies to. - type: string - operator: + preferredDuringSchedulingIgnoredDuringExecution: description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of csi-node-driver containers. + If specified, this overrides the specified csi-node-driver DaemonSet containers. + If omitted, the csi-node-driver DaemonSet will use its default values for its containers. + items: + description: + CSINodeDriverDaemonSetContainer + is a csi-node-driver DaemonSet container. + properties: + name: + description: |- + Name is an enum which identifies the csi-node-driver DaemonSet container by name. + Supported values are: calico-csi, csi-node-driver-registrar. + enum: + - calico-csi + - csi-node-driver-registrar + - csi-node-driver + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named csi-node-driver DaemonSet container's resources. + If omitted, the csi-node-driver DaemonSet will use its default value for this container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the csi-node-driver pod's scheduling constraints. + If specified, each of the key/value pairs are added to the csi-node-driver DaemonSet nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the csi-node-driver DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default csi-node-driver DaemonSet nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the csi-node-driver pod's tolerations. + If specified, this overrides any tolerations that may be set on the csi-node-driver DaemonSet. + If omitted, the csi-node-driver DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default csi-node-driver DaemonSet tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + fipsMode: + description: |- + FIPSMode uses images and features only that are using FIPS 140-2 validated cryptographic modules and standards. + Only supported for Variant=Calico. + Default: Disabled + enum: + - Enabled + - Disabled + type: string + flexVolumePath: + description: |- + FlexVolumePath optionally specifies a custom path for FlexVolume. If not specified, FlexVolume will be + enabled by default. If set to 'None', FlexVolume will be disabled. The default is based on the + kubernetesProvider. + type: string + imagePath: + description: |- + ImagePath allows for the path part of an image to be specified. If specified + then the specified value will be used as the image path for each image. If not specified + or empty, the default for each image will be used. + A special case value, UseDefault, is supported to explicitly specify the default + image path will be used for each image. + Image format: + `/:` + This option allows configuring the `` portion of the above format. + type: string + imagePrefix: + description: |- + ImagePrefix allows for the prefix part of an image to be specified. If specified + then the given value will be used as a prefix on each image. If not specified + or empty, no prefix will be used. + A special case value, UseDefault, is supported to explicitly specify the default + image prefix will be used for each image. + Image format: + `/:` + This option allows configuring the `` portion of the above format. + type: string + imagePullSecrets: + description: |- + ImagePullSecrets is an array of references to container registry pull secrets to use. These are + applied to all images to be pulled. + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + kubeletVolumePluginPath: + description: |- + KubeletVolumePluginPath optionally specifies enablement of Calico CSI plugin. If not specified, + CSI will be enabled by default. If set to 'None', CSI will be disabled. + Default: /var/lib/kubelet + type: string + kubernetesProvider: + description: |- + KubernetesProvider specifies a particular provider of the Kubernetes platform and enables provider-specific configuration. + If the specified value is empty, the Operator will attempt to automatically determine the current provider. + If the specified value is not empty, the Operator will still attempt auto-detection, but + will additionally compare the auto-detected value to the specified value to confirm they match. + enum: + - "" + - EKS + - GKE + - AKS + - OpenShift + - DockerEnterprise + - RKE2 + - TKG + - Kind + type: string + logging: + description: Logging Configuration for Components + properties: + cni: + description: + Customized logging specification for calico-cni + plugin + properties: + logFileMaxAgeDays: + description: "Default: 30 (days)" + format: int32 + type: integer + logFileMaxCount: + description: "Default: 10" + format: int32 + type: integer + logFileMaxSize: + anyOf: + - type: integer + - type: string + description: "Default: 100Mi" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + logSeverity: + description: "Default: Info" + enum: + - Error + - Warning + - Info + - Debug + type: string + type: object + type: object + nodeMetricsPort: + description: |- + NodeMetricsPort specifies which port calico/node serves prometheus metrics on. By default, metrics are not enabled. + If specified, this overrides any FelixConfiguration resources which may exist. If omitted, then + prometheus metrics may still be configured through FelixConfiguration. + format: int32 + type: integer + nodeUpdateStrategy: + description: |- + NodeUpdateStrategy can be used to customize the desired update strategy, such as the MaxUnavailable + field. + properties: + rollingUpdate: + description: + Rolling update config params. Present only if + type = "RollingUpdate". + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of nodes with an existing available DaemonSet pod that + can have an updated DaemonSet pod during during an update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up to a minimum of 1. + Default value is 0. + Example: when this is set to 30%, at most 30% of the total number of nodes + that should be running the daemon pod (i.e. status.desiredNumberScheduled) + can have their a new pod created before the old pod is marked as deleted. + The update starts by launching new pods on 30% of nodes. Once an updated + pod is available (Ready for at least minReadySeconds) the old DaemonSet pod + on that node is marked deleted. If the old pod becomes unavailable for any + reason (Ready transitions to false, is evicted, or is drained) an updated + pod is immediately created on that node without considering surge limits. + Allowing surge implies the possibility that the resources consumed by the + daemonset on any given node can double if the readiness check fails, and + so resource intensive daemonsets should take into account that they may + cause evictions during disruption. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of DaemonSet pods that can be unavailable during the + update. Value can be an absolute number (ex: 5) or a percentage of total + number of DaemonSet pods at the start of the update (ex: 10%). Absolute + number is calculated from percentage by rounding up. + This cannot be 0 if MaxSurge is 0 + Default value is 1. + Example: when this is set to 30%, at most 30% of the total number of nodes + that should be running the daemon pod (i.e. status.desiredNumberScheduled) + can have their pods stopped for an update at any given time. The update + starts by stopping at most 30% of those DaemonSet pods and then brings + up new DaemonSet pods in their place. Once the new pods are available, + it then proceeds onto other DaemonSet pods, thus ensuring that at least + 70% of original number of DaemonSet pods are available at all times during + the update. + x-kubernetes-int-or-string: true + type: object + type: + description: + Type of daemon set update. Can be "RollingUpdate" + or "OnDelete". Default is RollingUpdate. + type: string + type: object + nonPrivileged: + description: |- + Deprecated. NonPrivileged is deprecated and will be removed from the API in a future release. + Enabling this field is not supported and will cause errors. + NonPrivileged configures Calico to be run in non-privileged containers as non-root users where possible. + type: string + proxy: + description: |- + Proxy is used to configure the HTTP(S) proxy settings that will be applied to Tigera containers that connect + to destinations outside the cluster. It is expected that NO_PROXY is configured such that destinations within + the cluster (including the API server) are exempt from proxying. + properties: + httpProxy: + description: |- + HTTPProxy defines the value of the HTTP_PROXY environment variable that will be set on Tigera containers that connect to + destinations outside the cluster. + type: string + httpsProxy: + description: |- + HTTPSProxy defines the value of the HTTPS_PROXY environment variable that will be set on Tigera containers that connect to + destinations outside the cluster. + type: string + noProxy: + description: |- + NoProxy defines the value of the NO_PROXY environment variable that will be set on Tigera containers that connect to + destinations outside the cluster. This value must be set such that destinations within the scope of the cluster, including + the Kubernetes API server, are exempt from being proxied. + type: string + type: object + registry: + description: |- + Registry is the default Docker registry used for component Docker images. + If specified then the given value must end with a slash character (`/`) and all images will be pulled from this registry. + If not specified then the default registries will be used. A special case value, UseDefault, is + supported to explicitly specify the default registries will be used. + Image format: + `/:` + This option allows configuring the `` portion of the above format. + type: string + serviceCIDRs: + description: + Kubernetes Service CIDRs. Specifying this is required + when using Calico for Windows. + items: + type: string + type: array + tlsCipherSuites: + description: + TLSCipherSuites defines the cipher suite list that + the TLS protocol should use during secure communication. + items: + properties: + name: + description: This should be a valid TLS cipher suite name. + enum: + - TLS_AES_256_GCM_SHA384 + - TLS_CHACHA20_POLY1305_SHA256 + - TLS_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 + - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + - TLS_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA + - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA + - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + type: string + type: object + type: array + typhaAffinity: + description: |- + Deprecated. Please use Installation.Spec.TyphaDeployment instead. + TyphaAffinity allows configuration of node affinity characteristics for Typha pods. + properties: + nodeAffinity: + description: + NodeAffinity describes node affinity scheduling + rules for typha. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + WARNING: Please note that if the affinity requirements specified by this field are not met at + scheduling time, the pod will NOT be scheduled onto the node. + There is no fallback to another affinity rules with this setting. + This may cause networking disruption or even catastrophic failure! + PreferredDuringSchedulingIgnoredDuringExecution should be used for affinity + unless there is a specific well understood reason to use RequiredDuringSchedulingIgnoredDuringExecution and + you can guarantee that the RequiredDuringSchedulingIgnoredDuringExecution will always have sufficient nodes to satisfy the requirement. + NOTE: RequiredDuringSchedulingIgnoredDuringExecution is set by default for AKS nodes, + to avoid scheduling Typhas on virtual-nodes. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + type: object + typhaDeployment: + description: |- + TyphaDeployment configures the typha Deployment. If used in conjunction with the deprecated + ComponentResources or TyphaAffinity, then these overrides take precedence. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the specification of the typha Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the typha Deployment. + If omitted, the typha Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + strategy: + description: + The deployment strategy to use to replace + existing pods with new ones. + properties: + rollingUpdate: + description: |- + Rolling update config params. Present only if DeploymentStrategyType = + RollingUpdate. + to be. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: object + template: + description: + Template describes the typha Deployment pod + that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the typha Deployment's PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the typha pods. + If specified, this overrides any affinity that may be set on the typha Deployment. + If omitted, the typha Deployment will use its default value for affinity. + If used in conjunction with the deprecated TyphaAffinity, then this value takes precedence. + WARNING: Please note that this field will override the default calico-typha Deployment affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of typha containers. + If specified, this overrides the specified typha Deployment containers. + If omitted, the typha Deployment will use its default values for its containers. + items: + description: + TyphaDeploymentContainer is a typha + Deployment container. + properties: + name: + description: |- + Name is an enum which identifies the typha Deployment container by name. + Supported values are: calico-typha + enum: + - calico-typha + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named typha Deployment container's resources. + If omitted, the typha Deployment will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + initContainers: + description: |- + InitContainers is a list of typha init containers. + If specified, this overrides the specified typha Deployment init containers. + If omitted, the typha Deployment will use its default values for its init containers. + items: + description: + TyphaDeploymentInitContainer is + a typha Deployment init container. + properties: + name: + description: |- + Name is an enum which identifies the typha Deployment init container by name. + Supported values are: typha-certs-key-cert-provisioner + enum: + - typha-certs-key-cert-provisioner + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named typha Deployment init container's resources. + If omitted, the typha Deployment will use its default value for this init container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-typha pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-typha Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-typha Deployment will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-typha Deployment nodeSelector. + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + If this value is nil, the default grace period will be used instead. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + Defaults to 30 seconds. + format: int64 + type: integer + tolerations: + description: |- + Tolerations is the typha pod's tolerations. + If specified, this overrides any tolerations that may be set on the typha Deployment. + If omitted, the typha Deployment will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-typha Deployment tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given + topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + type: object + type: object + type: object + typhaMetricsPort: + description: + TyphaMetricsPort specifies which port calico/typha + serves prometheus metrics on. By default, metrics are not enabled. + format: int32 + type: integer + variant: + description: |- + Variant is the product to install - one of Calico or TigeraSecureEnterprise + Default: Calico + enum: + - Calico + - TigeraSecureEnterprise + type: string + windowsNodes: + description: Windows Configuration + properties: + cniBinDir: + description: |- + CNIBinDir is the path to the CNI binaries directory on Windows, it must match what is used as 'bin_dir' under + [plugins] + [plugins."io.containerd.grpc.v1.cri"] + [plugins."io.containerd.grpc.v1.cri".cni] + on the containerd 'config.toml' file on the Windows nodes. + type: string + cniConfigDir: + description: |- + CNIConfigDir is the path to the CNI configuration directory on Windows, it must match what is used as 'conf_dir' under + [plugins] + [plugins."io.containerd.grpc.v1.cri"] + [plugins."io.containerd.grpc.v1.cri".cni] + on the containerd 'config.toml' file on the Windows nodes. + type: string + cniLogDir: + description: + CNILogDir is the path to the Calico CNI logs + directory on Windows. + type: string + vxlanAdapter: + description: + VXLANAdapter is the Network Adapter used for + VXLAN, leave blank for primary NIC + type: string + vxlanMACPrefix: + description: + VXLANMACPrefix is the prefix used when generating + MAC addresses for virtual NICs + pattern: ^[0-9A-Fa-f]{2}-[0-9A-Fa-f]{2}$ + type: string + type: object + type: object + conditions: + description: |- + Conditions represents the latest observed set of conditions for the component. A component may be one or more of + Ready, Progressing, Degraded or other customer types. + items: + description: + Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + imageSet: + description: |- + ImageSet is the name of the ImageSet being used, if there is an ImageSet + that is being used. If an ImageSet is not being used then this will not be set. + type: string + mtu: + description: |- + MTU is the most recently observed value for pod network MTU. This may be an explicitly + configured value, or based on Calico's native auto-detetion. + format: int32 + type: integer + variant: + description: + Variant is the most recently observed installed variant + - one of Calico or TigeraSecureEnterprise + enum: + - Calico + - TigeraSecureEnterprise + type: string + type: object + type: object + x-kubernetes-validations: + - message: resource name must be 'default', 'tigera-secure', or 'overlay' + rule: + self.metadata.name == 'default' || self.metadata.name == 'tigera-secure' + || self.metadata.name == 'overlay' + served: true + storage: true + subresources: + status: {} +--- +# Source: crd.projectcalico.org.v1/templates/operator.tigera.io_istios.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: istios.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: Istio + listKind: IstioList + plural: istios + singular: istio + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: Istio is the Schema for the istios API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: IstioSpec defines the desired state of Istio + properties: + dscpMark: + description: |- + DSCPMark define the value of the DSCP mark done by Felix and recognised by Istio CNI for Transparent + NetworkPolicies. + pattern: ^.* + type: integer + x-kubernetes-int-or-string: true + istioCNI: + description: + IstioCNIDaemonset defines the resource requirements for + the Istio CNI plugin. + properties: + spec: + description: + Spec allows users to specify custom fields for the + Istio CNI Daemonset. + properties: + template: + description: + Template allows users to specify custom fields + for the Istio CNI Daemonset. + properties: + spec: + description: + Spec allows users to specify custom fields + for the Istio CNI Daemonset. + properties: + affinity: + description: + Affinity specifies the affinity for the + deployment. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - type: object - x-kubernetes-map-type: atomic - weight: - description: - Weight associated with matching the - corresponding nodeSelectorTerm, in the range 1-100. - format: int32 - type: integer - required: - - preference - - weight - type: object - type: array - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - WARNING: Please note that if the affinity requirements specified by this field are not met at - scheduling time, the pod will NOT be scheduled onto the node. - There is no fallback to another affinity rules with this setting. - This may cause networking disruption or even catastrophic failure! - PreferredDuringSchedulingIgnoredDuringExecution should be used for affinity - unless there is a specific well understood reason to use RequiredDuringSchedulingIgnoredDuringExecution and - you can guarantee that the RequiredDuringSchedulingIgnoredDuringExecution will always have sufficient nodes to satisfy the requirement. - NOTE: RequiredDuringSchedulingIgnoredDuringExecution is set by default for AKS nodes, - to avoid scheduling Typhas on virtual-nodes. - If the affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to an update), the system - may or may not try to eventually evict the pod from its node. - properties: - nodeSelectorTerms: + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + nodeSelector: + additionalProperties: + type: string description: - Required. A list of node selector terms. - The terms are ORed. + NodeSelector specifies the node affinity + for the deployment. + type: object + resources: + description: + Resources specifies the compute resources + required for the deployment. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references one entry + in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + tolerations: + description: + Tolerations specifies the tolerations + for the deployment. items: description: |- - A null or empty node selector term matches no objects. The requirements of - them are ANDed. - The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . properties: - matchExpressions: - description: - A list of node selector requirements - by node's labels. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: - The label key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchFields: - description: - A list of node selector requirements - by node's fields. - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. - properties: - key: - description: - The label key that the selector - applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string type: object - x-kubernetes-map-type: atomic type: array - x-kubernetes-list-type: atomic - required: - - nodeSelectorTerms type: object - x-kubernetes-map-type: atomic type: object type: object - typhaDeployment: - description: |- - TyphaDeployment configures the typha Deployment. If used in conjunction with the deprecated - ComponentResources or TyphaAffinity, then these overrides take precedence. + type: object + istiod: + description: + IstiodDeployment defines the resource requirements and + node selector for the Istio deployment. + properties: + spec: + description: + Spec allows users to specify custom fields for the + Istiod Deployment. properties: - metadata: + template: description: - Metadata is a subset of a Kubernetes object's - metadata that is added to the Deployment. - properties: - annotations: - additionalProperties: - type: string - description: |- - Annotations is a map of arbitrary non-identifying metadata. Each of these - key/value pairs are added to the object's annotations provided the key does not - already exist in the object's annotations. - type: object - labels: - additionalProperties: - type: string - description: |- - Labels is a map of string keys and values that may match replicaset and - service selectors. Each of these key/value pairs are added to the - object's labels provided the key does not already exist in the object's labels. - type: object - type: object - spec: - description: Spec is the specification of the typha Deployment. + Template allows users to specify custom fields + for the Istiod Deployment. properties: - minReadySeconds: - description: |- - MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should - be ready without any of its container crashing, for it to be considered available. - If specified, this overrides any minReadySeconds value that may be set on the typha Deployment. - If omitted, the typha Deployment will use its default value for minReadySeconds. - format: int32 - maximum: 2147483647 - minimum: 0 - type: integer - strategy: - description: - The deployment strategy to use to replace - existing pods with new ones. - properties: - rollingUpdate: - description: |- - Rolling update config params. Present only if DeploymentStrategyType = - RollingUpdate. - to be. - properties: - maxSurge: - anyOf: - - type: integer - - type: string - description: |- - The maximum number of pods that can be scheduled above the desired number of - pods. - Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). - This can not be 0 if MaxUnavailable is 0. - Absolute number is calculated from percentage by rounding up. - Defaults to 25%. - Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when - the rolling update starts, such that the total number of old and new pods do not exceed - 130% of desired pods. Once old pods have been killed, - new ReplicaSet can be scaled up further, ensuring that total number of pods running - at any time during the update is at most 130% of desired pods. - x-kubernetes-int-or-string: true - maxUnavailable: - anyOf: - - type: integer - - type: string - description: |- - The maximum number of pods that can be unavailable during the update. - Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). - Absolute number is calculated from percentage by rounding down. - This can not be 0 if MaxSurge is 0. - Defaults to 25%. - Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods - immediately when the rolling update starts. Once new pods are ready, old ReplicaSet - can be scaled down further, followed by scaling up the new ReplicaSet, ensuring - that the total number of pods available at all times during the update is at - least 70% of desired pods. - x-kubernetes-int-or-string: true - type: object - type: object - template: + spec: description: - Template describes the typha Deployment pod - that will be created. + Spec allows users to specify custom fields + for the Istiod Deployment. properties: - metadata: - description: |- - Metadata is a subset of a Kubernetes object's metadata that is added to - the pod's metadata. + affinity: + description: + Affinity specifies the affinity for the + deployment. properties: - annotations: - additionalProperties: - type: string - description: |- - Annotations is a map of arbitrary non-identifying metadata. Each of these - key/value pairs are added to the object's annotations provided the key does not - already exist in the object's annotations. - type: object - labels: - additionalProperties: - type: string - description: |- - Labels is a map of string keys and values that may match replicaset and - service selectors. Each of these key/value pairs are added to the - object's labels provided the key does not already exist in the object's labels. + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic type: object - type: object - spec: - description: Spec is the typha Deployment's PodSpec. - properties: - affinity: - description: |- - Affinity is a group of affinity scheduling rules for the typha pods. - If specified, this overrides any affinity that may be set on the typha Deployment. - If omitted, the typha Deployment will use its default value for affinity. - If used in conjunction with the deprecated TyphaAffinity, then this value takes precedence. - WARNING: Please note that this field will override the default calico-typha Deployment affinity. + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). properties: - nodeAffinity: - description: - Describes node affinity scheduling - rules for the pod. - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node matches the corresponding matchExpressions; the - node(s) with the highest sum are the most preferred. - items: - description: |- - An empty preferred scheduling term matches all objects with implicit weight 0 - (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. properties: - preference: - description: - A node selector term, - associated with the corresponding - weight. + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. properties: matchExpressions: description: - A list of node - selector requirements by node's - labels. + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. items: description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. properties: key: description: - The label - key that the selector + key is the + label key that the selector applies to. type: string operator: description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. type: string values: description: |- - An array of string values. If the operator is In or NotIn, + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. + the values array must be empty. This array is replaced during a strategic + merge patch. items: type: string type: array @@ -25197,34 +29220,80 @@ spec: type: object type: array x-kubernetes-list-type: atomic - matchFields: + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: description: - A list of node - selector requirements by node's - fields. + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. items: description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. properties: key: description: - The label - key that the selector + key is the + label key that the selector applies to. type: string operator: description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. type: string values: description: |- - An array of string values. If the operator is In or NotIn, + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. + the values array must be empty. This array is replaced during a strategic + merge patch. items: type: string type: array @@ -25235,67 +29304,288 @@ spec: type: object type: array x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object type: object x-kubernetes-map-type: atomic - weight: - description: - Weight associated with - matching the corresponding nodeSelectorTerm, - in the range 1-100. - format: int32 - type: integer + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string required: - - preference - - weight + - topologyKey type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - If the affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to an update), the system - may or may not try to eventually evict the pod from its node. - properties: - nodeSelectorTerms: - description: - Required. A list of node - selector terms. The terms are ORed. - items: + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string description: |- - A null or empty node selector term matches no objects. The requirements of - them are ANDed. - The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. properties: matchExpressions: description: - A list of node - selector requirements by node's - labels. + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. items: description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. properties: key: description: - The label - key that the selector + key is the + label key that the selector applies to. type: string operator: description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. type: string values: description: |- - An array of string values. If the operator is In or NotIn, + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. + the values array must be empty. This array is replaced during a strategic + merge patch. items: type: string type: array @@ -25306,34 +29596,80 @@ spec: type: object type: array x-kubernetes-list-type: atomic - matchFields: + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: description: - A list of node - selector requirements by node's - fields. + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. items: description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. properties: key: description: - The label - key that the selector + key is the + label key that the selector applies to. type: string operator: description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. type: string values: description: |- - An array of string values. If the operator is In or NotIn, + values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. + the values array must be empty. This array is replaced during a strategic + merge patch. items: type: string type: array @@ -25344,240 +29680,609 @@ spec: type: object type: array x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object type: object x-kubernetes-map-type: atomic - type: array - x-kubernetes-list-type: atomic - required: - - nodeSelectorTerms - type: object - x-kubernetes-map-type: atomic - type: object - podAffinity: - description: - Describes pod affinity scheduling - rules (e.g. co-locate this pod in the same - node, zone, etc. as some other pod(s)). + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + nodeSelector: + additionalProperties: + type: string + description: + NodeSelector specifies the node affinity + for the deployment. + type: object + resources: + description: + Resources specifies the compute resources + required for the deployment. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references one entry + in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + tolerations: + description: + Tolerations specifies the tolerations + for the deployment. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + ztunnel: + description: + ZTunnelDaemonset defines the resource requirements for + the ZTunnelDaemonset component. + properties: + spec: + description: + Spec allows users to specify custom fields for the + ZTunnel Daemonset. + properties: + template: + description: + Template allows users to specify custom fields + for the ZTunnel Daemonset. + properties: + spec: + description: + Spec allows users to specify custom fields + for the ZTunnel Daemonset. + properties: + affinity: + description: + Affinity specifies the affinity for the + deployment. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the - node(s) with the highest sum are the most preferred. + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. items: - description: - The weights of all of the - matched WeightedPodAffinityTerm fields - are added per-node to find the most - preferred node(s) + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. properties: - podAffinityTerm: + matchExpressions: description: - Required. A pod affinity - term, associated with the corresponding - weight. - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: - matchExpressions - is a list of label selector - requirements. The requirements - are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is - the label key that - the selector applies - to. - type: string - operator: - description: - |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: - |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). - items: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). - items: + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: - matchExpressions - is a list of label selector - requirements. The requirements - are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is - the label key that - the selector applies - to. - type: string - operator: - description: - |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: - |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey - type: object - weight: - description: |- - weight associated with matching the corresponding podAffinityTerm, - in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic type: object + x-kubernetes-map-type: atomic type: array x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - If the affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to a pod label update), the - system may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding to each - podAffinityTerm are intersected, i.e. all terms must be satisfied. - items: - description: |- - Defines a set of pods (namely those matching the labelSelector - relative to the given namespace(s)) that this pod should be - co-located (affinity) or not co-located (anti-affinity) with, - where co-located is defined as running on a node whose value of - the label with key matches that of any node on which - a pod of the set of pods is running + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. properties: labelSelector: description: |- @@ -25642,7 +30347,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -25657,7 +30361,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -25739,234 +30442,223 @@ spec: required: - topologyKey type: object - type: array - x-kubernetes-list-type: atomic - type: object - podAntiAffinity: - description: - Describes pod anti-affinity scheduling - rules (e.g. avoid putting this pod in the - same node, zone, etc. as some other pod(s)). - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: |- - The scheduler will prefer to schedule pods to nodes that satisfy - the anti-affinity expressions specified by this field, but it may choose - a node that violates one or more of the expressions. The node that is - most preferred is the one with the greatest sum of weights, i.e. - for each node that meets all of the scheduling requirements (resource - request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the - node(s) with the highest sum are the most preferred. - items: - description: - The weights of all of the - matched WeightedPodAffinityTerm fields - are added per-node to find the most - preferred node(s) - properties: - podAffinityTerm: - description: - Required. A pod affinity - term, associated with the corresponding - weight. - properties: - labelSelector: - description: |- - A label query over a set of resources, in this case pods. - If it's null, this PodAffinityTerm matches with no Pods. - properties: - matchExpressions: - description: - matchExpressions - is a list of label selector - requirements. The requirements - are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is - the label key that - the selector applies - to. - type: string - operator: - description: - |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: - |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both matchLabelKeys and labelSelector. - Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). - items: - type: string - type: array - x-kubernetes-list-type: atomic - mismatchLabelKeys: - description: |- - MismatchLabelKeys is a set of pod label keys to select which pods will - be taken into consideration. The keys are used to lookup values from the - incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` - to select the group of existing pods which pods will be taken into consideration - for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming - pod labels will be ignored. The default value is empty. - The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. - Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). - items: - type: string - type: array - x-kubernetes-list-type: atomic - namespaceSelector: - description: |- - A label query over the set of namespaces that the term applies to. - The term is applied to the union of the namespaces selected by this field - and the ones listed in the namespaces field. - null selector and null or empty namespaces list means "this pod's namespace". - An empty selector ({}) matches all namespaces. - properties: - matchExpressions: - description: - matchExpressions - is a list of label selector - requirements. The requirements - are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is - the label key that - the selector applies - to. - type: string - operator: - description: - |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: - |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - namespaces: - description: |- - namespaces specifies a static list of namespace names that the term applies to. - The term is applied to the union of the namespaces listed in this field - and the ones selected by namespaceSelector. - null or empty namespaces list and null namespaceSelector means "this pod's namespace". - items: + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. type: string - type: array - x-kubernetes-list-type: atomic - topologyKey: - description: |- - This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching - the labelSelector in the specified namespaces, where co-located is defined as running on a node - whose value of the label with key topologyKey matches that of any node on which any of the - selected pods is running. - Empty topologyKey is not allowed. - type: string - required: - - topologyKey + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - weight: + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string description: |- - weight associated with matching the corresponding podAffinityTerm, - in the range 1-100. - format: int32 - type: integer - required: - - podAffinityTerm - - weight + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object type: object - type: array - x-kubernetes-list-type: atomic - requiredDuringSchedulingIgnoredDuringExecution: - description: |- - If the anti-affinity requirements specified by this field are not met at - scheduling time, the pod will not be scheduled onto the node. - If the anti-affinity requirements specified by this field cease to be met - at some point during pod execution (e.g. due to a pod label update), the - system may or may not try to eventually evict the pod from its node. - When there are multiple elements, the lists of nodes corresponding to each - podAffinityTerm are intersected, i.e. all terms must be satisfied. - items: + x-kubernetes-map-type: atomic + namespaces: description: |- - Defines a set of pods (namely those matching the labelSelector - relative to the given namespace(s)) that this pod should be - co-located (affinity) or not co-located (anti-affinity) with, - where co-located is defined as running on a node whose value of - the label with key matches that of any node on which - a pod of the set of pods is running + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. properties: labelSelector: description: |- @@ -26031,7 +30723,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -26046,7 +30737,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -26128,479 +30818,313 @@ spec: required: - topologyKey type: object - type: array - x-kubernetes-list-type: atomic - type: object - type: object - containers: - description: |- - Containers is a list of typha containers. - If specified, this overrides the specified typha Deployment containers. - If omitted, the typha Deployment will use its default values for its containers. - items: - description: - TyphaDeploymentContainer is a typha - Deployment container. - properties: - name: - description: |- - Name is an enum which identifies the typha Deployment container by name. - Supported values are: calico-typha - enum: - - calico-typha - type: string - resources: - description: |- - Resources allows customization of limits and requests for compute resources such as cpu and memory. - If specified, this overrides the named typha Deployment container's resources. - If omitted, the typha Deployment will use its default value for this container's resources. - If used in conjunction with the deprecated ComponentResources, then this value takes precedence. - properties: - claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - This field is immutable. It can only be set for containers. - items: - description: - ResourceClaim references - one entry in PodSpec.ResourceClaims. - properties: - name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. - type: string - request: - description: |- - Request is the name chosen for a request in the referenced claim. - If empty, everything from the claim is made available, otherwise - only the result of this request. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true + weight: description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight type: object - required: - - name - type: object - type: array - initContainers: - description: |- - InitContainers is a list of typha init containers. - If specified, this overrides the specified typha Deployment init containers. - If omitted, the typha Deployment will use its default values for its init containers. - items: - description: - TyphaDeploymentInitContainer is - a typha Deployment init container. - properties: - name: - description: |- - Name is an enum which identifies the typha Deployment init container by name. - Supported values are: typha-certs-key-cert-provisioner - enum: - - typha-certs-key-cert-provisioner - type: string - resources: + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: description: |- - Resources allows customization of limits and requests for compute resources such as cpu and memory. - If specified, this overrides the named typha Deployment init container's resources. - If omitted, the typha Deployment will use its default value for this init container's resources. - If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running properties: - claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - This field is immutable. It can only be set for containers. - items: - description: - ResourceClaim references - one entry in PodSpec.ResourceClaims. - properties: - name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. - type: string - request: - description: |- - Request is the name chosen for a request in the referenced claim. - If empty, everything from the claim is made available, otherwise - only the result of this request. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true + labelSelector: description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - type: object - required: - - name - type: object - type: array - nodeSelector: - additionalProperties: - type: string - description: |- - NodeSelector is the calico-typha pod's scheduling constraints. - If specified, each of the key/value pairs are added to the calico-typha Deployment nodeSelector provided - the key does not already exist in the object's nodeSelector. - If omitted, the calico-typha Deployment will use its default value for nodeSelector. - WARNING: Please note that this field will modify the default calico-typha Deployment nodeSelector. - type: object - terminationGracePeriodSeconds: - description: |- - Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. - Value must be non-negative integer. The value zero indicates stop immediately via - the kill signal (no opportunity to shut down). - If this value is nil, the default grace period will be used instead. - The grace period is the duration in seconds after the processes running in the pod are sent - a termination signal and the time when the processes are forcibly halted with a kill signal. - Set this value longer than the expected cleanup time for your process. - Defaults to 30 seconds. - format: int64 - type: integer - tolerations: - description: |- - Tolerations is the typha pod's tolerations. - If specified, this overrides any tolerations that may be set on the typha Deployment. - If omitted, the typha Deployment will use its default value for tolerations. - WARNING: Please note that this field will override the default calico-typha Deployment tolerations. - items: - description: |- - The pod this Toleration is attached to tolerates any taint that matches - the triple using the matching operator . - properties: - effect: - description: |- - Effect indicates the taint effect to match. Empty means match all taint effects. - When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: |- - Key is the taint key that the toleration applies to. Empty means match all taint keys. - If the key is empty, operator must be Exists; this combination means to match all values and all keys. - type: string - operator: - description: |- - Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. - Exists is equivalent to wildcard for value, so that a pod can - tolerate all taints of a particular category. - type: string - tolerationSeconds: - description: |- - TolerationSeconds represents the period of time the toleration (which must be - of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, - it is not set, which means tolerate the taint forever (do not evict). Zero and - negative values will be treated as 0 (evict immediately) by the system. - format: int64 - type: integer - value: - description: |- - Value is the taint value the toleration matches to. - If the operator is Exists, the value should be empty, otherwise just a regular string. - type: string - type: object - type: array - topologySpreadConstraints: - description: |- - TopologySpreadConstraints describes how a group of pods ought to spread across topology - domains. Scheduler will schedule pods in a way which abides by the constraints. - All topologySpreadConstraints are ANDed. - items: - description: - TopologySpreadConstraint specifies - how to spread matching pods among the given - topology. - properties: - labelSelector: - description: |- - LabelSelector is used to find matching pods. - Pods that match this label selector are counted to determine the number of pods - in their corresponding topology domain. - properties: - matchExpressions: - description: - matchExpressions is a list - of label selector requirements. The - requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label - key that the selector applies - to. - type: string - operator: + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string type: array x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey type: object - x-kubernetes-map-type: atomic - matchLabelKeys: - description: |- - MatchLabelKeys is a set of pod label keys to select the pods over which - spreading will be calculated. The keys are used to lookup values from the - incoming pod labels, those key-value labels are ANDed with labelSelector - to select the group of existing pods over which spreading will be calculated - for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. - MatchLabelKeys cannot be set when LabelSelector isn't set. - Keys that don't exist in the incoming pod labels will - be ignored. A null or empty list means only match against labelSelector. - This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). - items: - type: string - type: array - x-kubernetes-list-type: atomic - maxSkew: - description: |- - MaxSkew describes the degree to which pods may be unevenly distributed. - When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference - between the number of matching pods in the target topology and the global minimum. - The global minimum is the minimum number of matching pods in an eligible domain - or zero if the number of eligible domains is less than MinDomains. - For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same - labelSelector spread as 2/2/1: - In this case, the global minimum is 1. - | zone1 | zone2 | zone3 | - | P P | P P | P | - - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; - scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) - violate MaxSkew(1). - - if MaxSkew is 2, incoming pod can be scheduled onto any zone. - When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence - to topologies that satisfy it. - It's a required field. Default value is 1 and 0 is not allowed. - format: int32 - type: integer - minDomains: - description: |- - MinDomains indicates a minimum number of eligible domains. - When the number of eligible domains with matching topology keys is less than minDomains, - Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. - And when the number of eligible domains with matching topology keys equals or greater than minDomains, - this value has no effect on scheduling. - As a result, when the number of eligible domains is less than minDomains, - scheduler won't schedule more than maxSkew Pods to those domains. - If value is nil, the constraint behaves as if MinDomains is equal to 1. - Valid values are integers greater than 0. - When value is not nil, WhenUnsatisfiable must be DoNotSchedule. - For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same - labelSelector spread as 2/2/2: - | zone1 | zone2 | zone3 | - | P P | P P | P P | - The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. - In this situation, new pod with the same labelSelector cannot be scheduled, - because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, - it will violate MaxSkew. - format: int32 - type: integer - nodeAffinityPolicy: - description: |- - NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector - when calculating pod topology spread skew. Options are: - - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. - If this value is nil, the behavior is equivalent to the Honor policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. - type: string - nodeTaintsPolicy: - description: |- - NodeTaintsPolicy indicates how we will treat node taints when calculating - pod topology spread skew. Options are: - - Honor: nodes without taints, along with tainted nodes for which the incoming pod - has a toleration, are included. - - Ignore: node taints are ignored. All nodes are included. - If this value is nil, the behavior is equivalent to the Ignore policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. - type: string - topologyKey: + type: array + x-kubernetes-list-type: atomic + type: object + type: object + nodeSelector: + additionalProperties: + type: string + description: + NodeSelector specifies the node affinity + for the deployment. + type: object + resources: + description: + Resources specifies the compute resources + required for the deployment. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references one entry + in PodSpec.ResourceClaims. + properties: + name: description: |- - TopologyKey is the key of node labels. Nodes that have a label with this key - and identical values are considered to be in the same topology. - We consider each as a "bucket", and try to put balanced number - of pods into each bucket. - We define a domain as a particular instance of a topology. - Also, we define an eligible domain as a domain whose nodes meet the requirements of - nodeAffinityPolicy and nodeTaintsPolicy. - e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. - And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. - It's a required field. + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. type: string - whenUnsatisfiable: + request: description: |- - WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy - the spread constraint. - - DoNotSchedule (default) tells the scheduler not to schedule it. - - ScheduleAnyway tells the scheduler to schedule the pod in any location, - but giving higher precedence to topologies that would help reduce the - skew. - A constraint is considered "Unsatisfiable" for an incoming pod - if and only if every possible node assignment for that pod would violate - "MaxSkew" on some topology. - For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same - labelSelector spread as 3/1/1: - | zone1 | zone2 | zone3 | - | P P P | P | P | - If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled - to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies - MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler - won't make it *more* imbalanced. - It's a required field. + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. type: string required: - - maxSkew - - topologyKey - - whenUnsatisfiable + - name type: object type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object type: object + tolerations: + description: + Tolerations specifies the tolerations + for the deployment. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array type: object type: object type: object - typhaMetricsPort: - description: - TyphaMetricsPort specifies which port calico/typha - serves prometheus metrics on. By default, metrics are not enabled. - format: int32 - type: integer - variant: - description: |- - Variant is the product to install - one of Calico or TigeraSecureEnterprise - Default: Calico - enum: - - Calico - - TigeraSecureEnterprise - type: string - windowsNodes: - description: Windows Configuration - properties: - cniBinDir: - description: |- - CNIBinDir is the path to the CNI binaries directory on Windows, it must match what is used as 'bin_dir' under - [plugins] - [plugins."io.containerd.grpc.v1.cri"] - [plugins."io.containerd.grpc.v1.cri".cni] - on the containerd 'config.toml' file on the Windows nodes. - type: string - cniConfigDir: - description: |- - CNIConfigDir is the path to the CNI configuration directory on Windows, it must match what is used as 'conf_dir' under - [plugins] - [plugins."io.containerd.grpc.v1.cri"] - [plugins."io.containerd.grpc.v1.cri".cni] - on the containerd 'config.toml' file on the Windows nodes. - type: string - cniLogDir: - description: - CNILogDir is the path to the Calico CNI logs - directory on Windows. - type: string - vxlanAdapter: - description: - VXLANAdapter is the Network Adapter used for - VXLAN, leave blank for primary NIC - type: string - vxlanMACPrefix: - description: - VXLANMACPrefix is the prefix used when generating - MAC addresses for virtual NICs - pattern: ^[0-9A-Fa-f]{2}-[0-9A-Fa-f]{2}$ - type: string - type: object type: object + type: object + status: + description: IstioStatus defines the observed state of Istio + properties: conditions: description: |- Conditions represents the latest observed set of conditions for the component. A component may be one or more of @@ -26661,38 +31185,22 @@ spec: - type type: object type: array - imageSet: - description: |- - ImageSet is the name of the ImageSet being used, if there is an ImageSet - that is being used. If an ImageSet is not being used then this will not be set. - type: string - mtu: - description: |- - MTU is the most recently observed value for pod network MTU. This may be an explicitly - configured value, or based on Calico's native auto-detetion. - format: int32 - type: integer - variant: - description: - Variant is the most recently observed installed variant - - one of Calico or TigeraSecureEnterprise - enum: - - Calico - - TigeraSecureEnterprise - type: string type: object type: object + x-kubernetes-validations: + - message: resource name must be 'default' + rule: self.metadata.name == 'default' served: true storage: true subresources: status: {} --- -# Source: crds/operator.tigera.io_managementclusterconnections.yaml +# Source: crd.projectcalico.org.v1/templates/operator.tigera.io_managementclusterconnections.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.3 + controller-gen.kubebuilder.io/version: v0.18.0 name: managementclusterconnections.operator.tigera.io spec: group: operator.tigera.io @@ -26773,7 +31281,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -26855,7 +31363,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -27037,17 +31545,20 @@ spec: type: array type: object type: object + x-kubernetes-validations: + - message: resource name must be 'default' or 'tigera-secure' + rule: self.metadata.name == 'default' || self.metadata.name == 'tigera-secure' served: true storage: true subresources: status: {} --- -# Source: crds/operator.tigera.io_tigerastatuses.yaml +# Source: crd.projectcalico.org.v1/templates/operator.tigera.io_tigerastatuses.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.3 + controller-gen.kubebuilder.io/version: v0.18.0 name: tigerastatuses.operator.tigera.io spec: group: operator.tigera.io @@ -27160,12 +31671,12 @@ spec: subresources: status: {} --- -# Source: crds/operator.tigera.io_whiskers.yaml +# Source: crd.projectcalico.org.v1/templates/operator.tigera.io_whiskers.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.3 + controller-gen.kubebuilder.io/version: v0.18.0 name: whiskers.operator.tigera.io spec: group: operator.tigera.io @@ -27634,7 +32145,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -27649,7 +32159,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -27823,7 +32332,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -27838,7 +32346,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -27936,8 +32443,8 @@ spec: most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), - compute a sum by iterating through the elements of this field and adding - "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: @@ -28014,7 +32521,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -28029,7 +32535,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -28203,7 +32708,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -28218,7 +32722,6 @@ spec: pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set. - This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default). items: type: string type: array @@ -28324,7 +32827,7 @@ spec: description: |- Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. - This is an alpha field and requires enabling the + This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. items: @@ -28569,7 +33072,6 @@ spec: - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. If this value is nil, the behavior is equivalent to the Honor policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string nodeTaintsPolicy: description: |- @@ -28579,7 +33081,6 @@ spec: has a toleration, are included. - Ignore: node taints are ignored. All nodes are included. If this value is nil, the behavior is equivalent to the Ignore policy. - This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag. type: string topologyKey: description: |- @@ -28691,12 +33192,15 @@ spec: type: array type: object type: object + x-kubernetes-validations: + - message: resource name must be 'default' + rule: self.metadata.name == 'default' served: true storage: true subresources: status: {} --- -# Source: crds/crd.projectcalico.org_bgpconfigurations.yaml +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_bgpconfigurations.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -28729,6 +33233,9 @@ spec: format: int32 type: integer bindMode: + enum: + - None + - NodeIP type: string communities: items: @@ -28739,11 +33246,14 @@ spec: pattern: ^(\d+):(\d+)$|^(\d+):(\d+):(\d+)$ type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set ignoredInterfaces: items: type: string type: array + x-kubernetes-list-type: set listenPort: maximum: 65535 minimum: 1 @@ -28753,6 +33263,8 @@ spec: localWorkloadPeeringIPV6: type: string logSeverityScreen: + default: Info + pattern: ^(?i)(Trace|Debug|Info|Warning|Error|Fatal)?$ type: string nodeMeshMaxRestartTime: type: string @@ -28778,27 +33290,36 @@ spec: items: properties: cidr: + format: cidr type: string communities: items: type: string type: array type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceClusterIPs: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceExternalIPs: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set serviceLoadBalancerAggregation: default: Enabled enum: @@ -28809,15 +33330,18 @@ spec: items: properties: cidr: + format: cidr type: string type: object + x-kubernetes-map-type: atomic type: array + x-kubernetes-list-type: set type: object type: object served: true storage: true --- -# Source: crds/crd.projectcalico.org_bgpfilters.yaml +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_bgpfilters.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -28850,12 +33374,21 @@ spec: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -28870,22 +33403,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array exportV6: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -28900,22 +33446,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array importV4: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -28930,22 +33489,35 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array importV6: items: properties: action: + enum: + - Accept + - Reject type: string cidr: + format: cidr type: string interface: type: string matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn type: string prefixLength: properties: @@ -28960,18 +33532,22 @@ spec: minimum: 0 type: integer type: object + x-kubernetes-map-type: atomic source: + enum: + - RemotePeers type: string required: - action type: object + x-kubernetes-map-type: atomic type: array type: object type: object served: true storage: true --- -# Source: crds/crd.projectcalico.org_bgppeers.yaml +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_bgppeers.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -29019,15 +33595,10 @@ spec: maxRestartTime: type: string nextHopMode: - allOf: - - enum: - - Auto - - Self - - Keep - - enum: - - Auto - - Self - - Keep + enum: + - Auto + - Self + - Keep type: string node: type: string @@ -29059,11 +33630,18 @@ spec: reachableBy: type: string reversePeering: - enum: - - Auto - - Manual + allOf: + - enum: + - Auto + - Manual + - enum: + - Auto + - Manual type: string sourceAddress: + enum: + - UseNodeIP + - None type: string ttlSecurity: type: integer @@ -29072,7 +33650,7 @@ spec: served: true storage: true --- -# Source: crds/crd.projectcalico.org_blockaffinities.yaml +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_blockaffinities.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -29121,7 +33699,7 @@ spec: served: true storage: true --- -# Source: crds/crd.projectcalico.org_caliconodestatuses.yaml +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_caliconodestatuses.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -29152,6 +33730,10 @@ spec: properties: classes: items: + enum: + - Agent + - BGP + - Routes type: string type: array node: @@ -29173,6 +33755,9 @@ spec: routerID: type: string state: + enum: + - Ready + - NotReady type: string version: type: string @@ -29186,6 +33771,9 @@ spec: routerID: type: string state: + enum: + - Ready + - NotReady type: string version: type: string @@ -29209,8 +33797,20 @@ spec: since: type: string state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close type: string type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer type: string type: object type: array @@ -29222,8 +33822,20 @@ spec: since: type: string state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close type: string type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer type: string type: object type: array @@ -29253,9 +33865,18 @@ spec: peerIP: type: string sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer type: string type: object type: + enum: + - FIB + - RIB type: string type: object type: array @@ -29273,9 +33894,18 @@ spec: peerIP: type: string sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer type: string type: object type: + enum: + - FIB + - RIB type: string type: object type: array @@ -29285,7 +33915,7 @@ spec: served: true storage: true --- -# Source: crds/crd.projectcalico.org_clusterinformations.yaml +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_clusterinformations.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -29329,7 +33959,7 @@ spec: served: true storage: true --- -# Source: crds/crd.projectcalico.org_felixconfigurations.yaml +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_felixconfigurations.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -29629,15 +34259,9 @@ spec: if it detects the current value is 2 (strict mode that hurts performance). When set to "Strict", Felix will not modify the JIT hardening setting. [Default: Auto] type: string - bpfKubeProxyEndpointSlicesEnabled: + bpfKubeProxyHealthzPort: description: |- - BPFKubeProxyEndpointSlicesEnabled is deprecated and has no effect. BPF - kube-proxy always accepts endpoint slices. This option will be removed in - the next release. - type: boolean - bpfKubeProxyHealtzPort: - description: |- - BPFKubeProxyHealtzPort, in BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. + BPFKubeProxyHealthzPort, in BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. The health check server is used by external load balancers to determine if this node should receive traffic. [Default: 10256] type: integer bpfKubeProxyIptablesCleanupEnabled: @@ -29678,6 +34302,23 @@ spec: [Default: Off]. pattern: ^(?i)(Off|Info|Debug)?$ type: string + bpfMaglevMaxEndpointsPerService: + description: |- + BPFMaglevMaxEndpointsPerService is the maximum number of endpoints + expected to be part of a single Maglev-enabled service. + + Influences the size of the per-service Maglev lookup-tables generated by Felix + and thus the amount of memory reserved. + + [Default: 100] + type: integer + bpfMaglevMaxServices: + description: |- + BPFMaglevMaxServices is the maximum number of expected Maglev-enabled + services that Felix will allocate lookup-tables for. + + [Default: 100] + type: integer bpfMapSizeConntrack: description: |- BPFMapSizeConntrack sets the size for the conntrack map. This map must be large enough to hold @@ -29766,17 +34407,14 @@ spec: type: string bpfRedirectToPeer: description: |- - BPFRedirectToPeer controls which whether it is allowed to forward straight to the - peer side of the workload devices. It is allowed for any host L2 devices by default - (L2Only), but it breaks TCP dump on the host side of workload device as it bypasses - it on ingress. Value of Enabled also allows redirection from L3 host devices like - IPIP tunnel or Wireguard directly to the peer side of the workload's device. This - makes redirection faster, however, it breaks tools like tcpdump on the peer side. - Use Enabled with caution. [Default: L2Only] + BPFRedirectToPeer controls whether traffic may be forwarded directly to the peer side of a workload’s device. + Note that the legacy "L2Only" option is now deprecated and if set it is treated like "Enabled. + Setting this option to "Enabled" allows direct redirection (including from L3 host devices such as IPIP tunnels or WireGuard), + which can improve redirection performance but causes the redirected packets to bypass the host‑side ingress path. + As a result, packet‑capture tools on the host side of the workload device (for example, tcpdump) will not see that traffic. [Default: Enabled] enum: - Enabled - Disabled - - L2Only type: string cgroupV2Path: description: @@ -30127,6 +34765,10 @@ spec: Warning: changing this on a running system can leave "orphaned" rules in the "other" backend. These should be cleaned up to avoid confusing interactions. + enum: + - Legacy + - NFT + - Auto pattern: ^(?i)(Auto|Legacy|NFT)?$ type: string iptablesFilterAllowAction: @@ -30142,26 +34784,10 @@ spec: with an iptables "DROP" action. If you want to use "REJECT" action instead you can configure it in here. pattern: ^(?i)(Drop|Reject)?$ type: string - iptablesLockFilePath: - description: |- - IptablesLockFilePath is the location of the iptables lock file. You may need to change this - if the lock file is not in its standard location (for example if you have mapped it into Felix's - container at a different path). [Default: /run/xtables.lock] - type: string iptablesLockProbeInterval: description: |- - IptablesLockProbeInterval when IptablesLockTimeout is enabled: the time that Felix will wait between - attempts to acquire the iptables lock if it is not available. Lower values make Felix more - responsive when the lock is contended, but use more CPU. [Default: 50ms] - pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ - type: string - iptablesLockTimeout: - description: |- - IptablesLockTimeout is the time that Felix itself will wait for the iptables lock (rather than delegating the - lock handling to the `iptables` command). - - Deprecated: `iptables-restore` v1.8+ always takes the lock, so enabling this feature results in deadlock. - [Default: 0s disabled] + IptablesLockProbeInterval configures the interval between attempts to claim + the xtables lock. Shorter intervals are more responsive but use more CPU. [Default: 50ms] pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ type: string iptablesMangleAllowAction: @@ -30221,6 +34847,19 @@ spec: pattern: ^.* x-kubernetes-int-or-string: true type: array + logActionRateLimit: + description: |- + LogActionRateLimit sets the rate of hitting a Log action. The value must be in the format "N/unit", + where N is a number and unit is one of: second, minute, hour, or day. For example: "10/second" or "100/hour". + pattern: ^[1-9]\d{0,3}/(?:second|minute|hour|day)$ + type: string + logActionRateLimitBurst: + description: + LogActionRateLimitBurst sets the rate limit burst of + hitting a Log action when LogActionRateLimit is enabled. + maximum: 9999 + minimum: 0 + type: integer logDebugFilenameRegex: description: |- LogDebugFilenameRegex controls which source code files have their Debug log output included in the logs. @@ -30233,9 +34872,17 @@ spec: none to disable file logging. [Default: /var/log/calico/felix.log]" type: string logPrefix: - description: - "LogPrefix is the log prefix that Felix uses when rendering - LOG rules. [Default: calico-packet]" + description: |- + LogPrefix is the log prefix that Felix uses when rendering LOG rules. It is possible to use the following specifiers + to include extra information in the log prefix. + - %t: Tier name. + - %k: Kind (short names). + - %n: Policy or profile name. + - %p: Policy or profile name (namespace/name for namespaced kinds or just name for non namespaced kinds). + Calico includes ": " characters at the end of the generated log prefix. + Note that iptables shows up to 29 characters for the log prefix and nftables up to 127 characters. Extra characters are truncated. + [Default: calico-packet] + pattern: "^([a-zA-Z0-9%: /_-])*$" type: string logSeverityFile: description: @@ -30339,9 +34986,10 @@ spec: format: int32 type: integer nftablesMode: + default: Auto description: "NFTablesMode configures nftables support in Felix. [Default: - Disabled]" + Auto]" enum: - Disabled - Enabled @@ -30377,6 +35025,21 @@ spec: PrometheusGoMetricsEnabled disables Go runtime metrics collection, which the Prometheus client does by default, when set to false. This reduces the number of metrics reported, reducing Prometheus load. [Default: true] type: boolean + prometheusMetricsCAFile: + description: |- + PrometheusMetricsCAFile defines the absolute path to the TLS CA certificate file used for securing the /metrics endpoint. + This certificate must be valid and accessible by the calico-node process. + type: string + prometheusMetricsCertFile: + description: |- + PrometheusMetricsCertFile defines the absolute path to the TLS certificate file used for securing the /metrics endpoint. + This certificate must be valid and accessible by the calico-node process. + type: string + prometheusMetricsClientAuth: + description: |- + PrometheusMetricsClientAuth specifies the client authentication type for the /metrics endpoint. + This determines how the server validates client certificates. Default is "RequireAndVerifyClientCert". + type: string prometheusMetricsEnabled: description: "PrometheusMetricsEnabled enables the Prometheus metrics @@ -30387,6 +35050,11 @@ spec: "PrometheusMetricsHost is the host that the Prometheus metrics server should bind to. [Default: empty]" type: string + prometheusMetricsKeyFile: + description: |- + PrometheusMetricsKeyFile defines the absolute path to the private key file corresponding to the TLS certificate + used for securing the /metrics endpoint. The private key must be valid and accessible by the calico-node process. + type: string prometheusMetricsPort: description: "PrometheusMetricsPort is the TCP port that the Prometheus @@ -30633,7 +35301,7 @@ spec: served: true storage: true --- -# Source: crds/crd.projectcalico.org_globalnetworkpolicies.yaml +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_globalnetworkpolicies.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -30670,6 +35338,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -30679,6 +35352,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -30709,6 +35383,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -30739,11 +35414,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -30755,8 +35437,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -30779,6 +35465,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -30809,6 +35496,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -30828,6 +35516,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -30837,6 +35530,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -30867,6 +35561,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -30897,11 +35592,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -30913,8 +35615,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -30937,6 +35643,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -30967,6 +35674,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -30988,6 +35696,8 @@ spec: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array preDNAT: @@ -30997,17 +35707,24 @@ spec: serviceAccountSelector: type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true storage: true --- -# Source: crds/crd.projectcalico.org_globalnetworksets.yaml +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_globalnetworksets.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -31040,12 +35757,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true storage: true --- -# Source: crds/crd.projectcalico.org_hostendpoints.yaml +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_hostendpoints.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -31078,6 +35796,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set interfaceName: type: string node: @@ -31088,6 +35807,8 @@ spec: name: type: string port: + maximum: 65535 + minimum: 0 type: integer protocol: anyOf: @@ -31105,12 +35826,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true storage: true --- -# Source: crds/crd.projectcalico.org_ipamblocks.yaml +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_ipamblocks.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -31141,6 +35863,9 @@ spec: properties: affinity: type: string + affinityClaimTime: + format: date-time + type: string allocations: items: type: integer @@ -31151,6 +35876,10 @@ spec: attributes: items: properties: + alternate: + additionalProperties: + type: string + type: object handle_id: type: string secondary: @@ -31189,7 +35918,7 @@ spec: served: true storage: true --- -# Source: crds/crd.projectcalico.org_ipamconfigs.yaml +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_ipamconfigs.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -31220,6 +35949,11 @@ spec: properties: autoAllocateBlocks: type: boolean + kubeVirtVMAddressPersistence: + enum: + - Enabled + - Disabled + type: string maxBlocksPerHost: maximum: 2147483647 minimum: 0 @@ -31234,7 +35968,7 @@ spec: served: true storage: true --- -# Source: crds/crd.projectcalico.org_ipamhandles.yaml +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_ipamhandles.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -31279,7 +36013,7 @@ spec: served: true storage: true --- -# Source: crds/crd.projectcalico.org_ippools.yaml +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_ippools.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -31310,39 +36044,47 @@ spec: properties: allowedUses: items: + enum: + - Workload + - Tunnel + - LoadBalancer type: string type: array + x-kubernetes-list-type: set assignmentMode: + default: Automatic enum: - Automatic - Manual type: string blockSize: + maximum: 128 + minimum: 0 type: integer cidr: + format: cidr type: string disableBGPExport: type: boolean disabled: type: boolean - ipip: - properties: - enabled: - type: boolean - mode: - type: string - type: object ipipMode: + enum: + - Never + - Always + - CrossSubnet type: string namespaceSelector: type: string - nat-outgoing: - type: boolean natOutgoing: type: boolean nodeSelector: type: string vxlanMode: + enum: + - Never + - Always + - CrossSubnet type: string required: - cidr @@ -31351,7 +36093,7 @@ spec: served: true storage: true --- -# Source: crds/crd.projectcalico.org_ipreservations.yaml +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_ipreservations.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -31381,15 +36123,17 @@ spec: spec: properties: reservedCIDRs: + format: cidr items: type: string type: array + x-kubernetes-list-type: set type: object type: object served: true storage: true --- -# Source: crds/crd.projectcalico.org_kubecontrollersconfigurations.yaml +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_kubecontrollersconfigurations.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -31423,6 +36167,10 @@ spec: loadBalancer: properties: assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly type: string type: object namespace: @@ -31435,6 +36183,9 @@ spec: hostEndpoint: properties: autoCreate: + enum: + - Enabled + - Disabled type: string createDefaultHostEndpoint: type: string @@ -31448,7 +36199,8 @@ spec: items: type: string type: array - interfaceSelector: + x-kubernetes-list-type: set + interfacePattern: type: string labels: additionalProperties: @@ -31464,6 +36216,9 @@ spec: reconcilerPeriod: type: string syncLabels: + enum: + - Enabled + - Disabled type: string type: object policy: @@ -31471,542 +36226,201 @@ spec: reconcilerPeriod: type: string type: object - serviceAccount: - properties: - reconcilerPeriod: - type: string - type: object - workloadEndpoint: - properties: - reconcilerPeriod: - type: string - type: object - type: object - debugProfilePort: - format: int32 - type: integer - etcdV3CompactionPeriod: - type: string - healthChecks: - type: string - logSeverityScreen: - type: string - prometheusMetricsPort: - type: integer - required: - - controllers - type: object - status: - properties: - environmentVars: - additionalProperties: - type: string - type: object - runningConfig: - properties: - controllers: - properties: - loadBalancer: - properties: - assignIPs: - type: string - type: object - namespace: - properties: - reconcilerPeriod: - type: string - type: object - node: - properties: - hostEndpoint: - properties: - autoCreate: - type: string - createDefaultHostEndpoint: - type: string - templates: - items: - properties: - generateName: - maxLength: 253 - type: string - interfaceCIDRs: - items: - type: string - type: array - interfaceSelector: - type: string - labels: - additionalProperties: - type: string - type: object - nodeSelector: - type: string - type: object - type: array - type: object - leakGracePeriod: - type: string - reconcilerPeriod: - type: string - syncLabels: - type: string - type: object - policy: - properties: - reconcilerPeriod: - type: string - type: object - serviceAccount: - properties: - reconcilerPeriod: - type: string - type: object - workloadEndpoint: - properties: - reconcilerPeriod: - type: string - type: object - type: object - debugProfilePort: - format: int32 - type: integer - etcdV3CompactionPeriod: - type: string - healthChecks: - type: string - logSeverityScreen: - type: string - prometheusMetricsPort: - type: integer - required: - - controllers - type: object - type: object - type: object - served: true - storage: true ---- -# Source: crds/crd.projectcalico.org_networkpolicies.yaml -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.18.0 - name: networkpolicies.crd.projectcalico.org -spec: - group: crd.projectcalico.org - names: - kind: NetworkPolicy - listKind: NetworkPolicyList - plural: networkpolicies - singular: networkpolicy - preserveUnknownFields: false - scope: Namespaced - versions: - - name: v1 - schema: - openAPIV3Schema: - properties: - apiVersion: - type: string - kind: - type: string - metadata: - type: object - spec: - properties: - egress: - items: - properties: - action: - type: string - destination: - properties: - namespaceSelector: - type: string - nets: - items: - type: string - type: array - notNets: - items: - type: string - type: array - notPorts: - items: - anyOf: - - type: integer - - type: string - pattern: ^.* - x-kubernetes-int-or-string: true - type: array - notSelector: - type: string - ports: - items: - anyOf: - - type: integer - - type: string - pattern: ^.* - x-kubernetes-int-or-string: true - type: array - selector: - type: string - serviceAccounts: - properties: - names: - items: - type: string - type: array - selector: - type: string - type: object - services: - properties: - name: - type: string - namespace: - type: string - type: object - type: object - http: - properties: - methods: - items: - type: string - type: array - paths: - items: - properties: - exact: - type: string - prefix: - type: string - type: object - type: array - type: object - icmp: - properties: - code: - type: integer - type: - type: integer - type: object - ipVersion: - type: integer - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - type: object - notICMP: - properties: - code: - type: integer - type: - type: integer - type: object - notProtocol: - anyOf: - - type: integer - - type: string - pattern: ^.* - x-kubernetes-int-or-string: true - protocol: - anyOf: - - type: integer - - type: string - pattern: ^.* - x-kubernetes-int-or-string: true - source: - properties: - namespaceSelector: - type: string - nets: - items: - type: string - type: array - notNets: - items: - type: string - type: array - notPorts: - items: - anyOf: - - type: integer - - type: string - pattern: ^.* - x-kubernetes-int-or-string: true - type: array - notSelector: - type: string - ports: - items: - anyOf: - - type: integer - - type: string - pattern: ^.* - x-kubernetes-int-or-string: true - type: array - selector: - type: string - serviceAccounts: - properties: - names: - items: - type: string - type: array - selector: - type: string - type: object - services: - properties: - name: - type: string - namespace: - type: string - type: object - type: object - required: - - action - type: object - type: array - ingress: - items: - properties: - action: - type: string - destination: - properties: - namespaceSelector: - type: string - nets: - items: - type: string - type: array - notNets: - items: - type: string - type: array - notPorts: - items: - anyOf: - - type: integer - - type: string - pattern: ^.* - x-kubernetes-int-or-string: true - type: array - notSelector: - type: string - ports: - items: - anyOf: - - type: integer - - type: string - pattern: ^.* - x-kubernetes-int-or-string: true - type: array - selector: - type: string - serviceAccounts: - properties: - names: - items: - type: string - type: array - selector: - type: string - type: object - services: - properties: - name: - type: string - namespace: - type: string - type: object - type: object - http: - properties: - methods: - items: - type: string - type: array - paths: - items: - properties: - exact: - type: string - prefix: - type: string - type: object - type: array - type: object - icmp: - properties: - code: - type: integer - type: - type: integer - type: object - ipVersion: - type: integer - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - type: object - notICMP: - properties: - code: - type: integer - type: - type: integer - type: object - notProtocol: - anyOf: - - type: integer - - type: string - pattern: ^.* - x-kubernetes-int-or-string: true - protocol: - anyOf: - - type: integer - - type: string - pattern: ^.* - x-kubernetes-int-or-string: true - source: - properties: - namespaceSelector: - type: string - nets: - items: - type: string - type: array - notNets: - items: - type: string - type: array - notPorts: - items: - anyOf: - - type: integer - - type: string - pattern: ^.* - x-kubernetes-int-or-string: true - type: array - notSelector: - type: string - ports: - items: - anyOf: - - type: integer - - type: string - pattern: ^.* - x-kubernetes-int-or-string: true - type: array - selector: - type: string - serviceAccounts: - properties: - names: - items: - type: string - type: array - selector: - type: string - type: object - services: - properties: - name: - type: string - namespace: - type: string - type: object - type: object - required: - - action - type: object - type: array - order: - type: number - performanceHints: - items: - type: string - type: array - selector: + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object + serviceAccount: + properties: + reconcilerPeriod: + type: string + type: object + workloadEndpoint: + properties: + reconcilerPeriod: + type: string + type: object + type: object + debugProfilePort: + format: int32 + maximum: 65535 + minimum: 0 + type: integer + etcdV3CompactionPeriod: type: string - serviceAccountSelector: + healthChecks: + default: Enabled + enum: + - Enabled + - Disabled type: string - tier: + logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic type: string - types: - items: - type: string - type: array - type: object - type: object - served: true - storage: true ---- -# Source: crds/crd.projectcalico.org_networksets.yaml -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.18.0 - name: networksets.crd.projectcalico.org -spec: - group: crd.projectcalico.org - names: - kind: NetworkSet - listKind: NetworkSetList - plural: networksets - singular: networkset - preserveUnknownFields: false - scope: Namespaced - versions: - - name: v1 - schema: - openAPIV3Schema: - properties: - apiVersion: - type: string - kind: - type: string - metadata: + prometheusMetricsPort: + maximum: 65535 + minimum: 0 + type: integer + required: + - controllers type: object - spec: + status: properties: - nets: - items: + environmentVars: + additionalProperties: type: string - type: array + type: object + runningConfig: + properties: + controllers: + properties: + loadBalancer: + properties: + assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly + type: string + type: object + namespace: + properties: + reconcilerPeriod: + type: string + type: object + node: + properties: + hostEndpoint: + properties: + autoCreate: + enum: + - Enabled + - Disabled + type: string + createDefaultHostEndpoint: + type: string + templates: + items: + properties: + generateName: + maxLength: 253 + type: string + interfaceCIDRs: + items: + type: string + type: array + x-kubernetes-list-type: set + interfacePattern: + type: string + labels: + additionalProperties: + type: string + type: object + nodeSelector: + type: string + type: object + type: array + type: object + leakGracePeriod: + type: string + reconcilerPeriod: + type: string + syncLabels: + enum: + - Enabled + - Disabled + type: string + type: object + policy: + properties: + reconcilerPeriod: + type: string + type: object + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object + serviceAccount: + properties: + reconcilerPeriod: + type: string + type: object + workloadEndpoint: + properties: + reconcilerPeriod: + type: string + type: object + type: object + debugProfilePort: + format: int32 + maximum: 65535 + minimum: 0 + type: integer + etcdV3CompactionPeriod: + type: string + healthChecks: + default: Enabled + enum: + - Enabled + - Disabled + type: string + logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic + type: string + prometheusMetricsPort: + maximum: 65535 + minimum: 0 + type: integer + required: + - controllers + type: object type: object type: object served: true storage: true + subresources: + status: {} --- -# Source: crds/crd.projectcalico.org_stagedglobalnetworkpolicies.yaml +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_networkpolicies.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.18.0 - name: stagedglobalnetworkpolicies.crd.projectcalico.org + name: networkpolicies.crd.projectcalico.org spec: group: crd.projectcalico.org names: - kind: StagedGlobalNetworkPolicy - listKind: StagedGlobalNetworkPolicyList - plural: stagedglobalnetworkpolicies - singular: stagedglobalnetworkpolicy + kind: NetworkPolicy + listKind: NetworkPolicyList + plural: networkpolicies + singular: networkpolicy preserveUnknownFields: false - scope: Cluster + scope: Namespaced versions: - name: v1 schema: @@ -32020,14 +36434,15 @@ spec: type: object spec: properties: - applyOnForward: - type: boolean - doNotTrack: - type: boolean egress: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -32037,6 +36452,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -32067,6 +36483,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -32097,11 +36514,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -32113,8 +36537,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -32137,6 +36565,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -32167,6 +36596,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -32186,6 +36616,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -32195,6 +36630,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -32225,6 +36661,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -32255,380 +36692,148 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - type: object - notICMP: - properties: - code: - type: integer - type: - type: integer - type: object - notProtocol: - anyOf: - - type: integer - - type: string - pattern: ^.* - x-kubernetes-int-or-string: true - protocol: - anyOf: - - type: integer - - type: string - pattern: ^.* - x-kubernetes-int-or-string: true - source: - properties: - namespaceSelector: - type: string - nets: - items: - type: string - type: array - notNets: - items: - type: string - type: array - notPorts: - items: - anyOf: - - type: integer - - type: string - pattern: ^.* - x-kubernetes-int-or-string: true - type: array - notSelector: - type: string - ports: - items: - anyOf: - - type: integer - - type: string - pattern: ^.* - x-kubernetes-int-or-string: true - type: array - selector: - type: string - serviceAccounts: - properties: - names: - items: - type: string - type: array - selector: - type: string - type: object - services: - properties: - name: - type: string - namespace: - type: string - type: object - type: object - required: - - action - type: object - type: array - namespaceSelector: - type: string - order: - type: number - performanceHints: - items: - type: string - type: array - preDNAT: - type: boolean - selector: - type: string - serviceAccountSelector: - type: string - stagedAction: - type: string - tier: - type: string - types: - items: - type: string - type: array - type: object - type: object - served: true - storage: true ---- -# Source: crds/crd.projectcalico.org_stagedkubernetesnetworkpolicies.yaml -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.18.0 - name: stagedkubernetesnetworkpolicies.crd.projectcalico.org -spec: - group: crd.projectcalico.org - names: - kind: StagedKubernetesNetworkPolicy - listKind: StagedKubernetesNetworkPolicyList - plural: stagedkubernetesnetworkpolicies - singular: stagedkubernetesnetworkpolicy - preserveUnknownFields: false - scope: Namespaced - versions: - - name: v1 - schema: - openAPIV3Schema: - properties: - apiVersion: - type: string - kind: - type: string - metadata: - type: object - spec: - properties: - egress: - items: - properties: - ports: - items: - properties: - endPort: - format: int32 - type: integer - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - protocol: - type: string - type: object - type: array - x-kubernetes-list-type: atomic - to: - items: - properties: - ipBlock: - properties: - cidr: - type: string - except: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - cidr - type: object - namespaceSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - type: object - type: array - x-kubernetes-list-type: atomic - type: object - type: array - ingress: - items: - properties: - from: - items: - properties: - ipBlock: - properties: - cidr: - type: string - except: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - cidr - type: object - namespaceSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - type: object - type: array - x-kubernetes-list-type: atomic - ports: - items: - properties: - endPort: - format: int32 - type: integer - port: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: anyOf: - type: integer - type: string + pattern: ^.* x-kubernetes-int-or-string: true - protocol: - type: string - type: object - type: array - x-kubernetes-list-type: atomic - type: object - type: array - podSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: + type: array + notSelector: type: string - values: + ports: items: - type: string + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true type: array - x-kubernetes-list-type: atomic - required: - - key - - operator + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - policyTypes: + required: + - action + type: object + type: array + order: + type: number + performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array - stagedAction: + selector: + type: string + serviceAccountSelector: + type: string + tier: + default: default type: string + types: + items: + enum: + - Ingress + - Egress + type: string + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-list-type: set type: object type: object served: true storage: true --- -# Source: crds/crd.projectcalico.org_stagednetworkpolicies.yaml +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_networksets.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.18.0 - name: stagednetworkpolicies.crd.projectcalico.org + name: networksets.crd.projectcalico.org spec: group: crd.projectcalico.org names: - kind: StagedNetworkPolicy - listKind: StagedNetworkPolicyList - plural: stagednetworkpolicies - singular: stagednetworkpolicy + kind: NetworkSet + listKind: NetworkSetList + plural: networksets + singular: networkset preserveUnknownFields: false scope: Namespaced versions: @@ -32644,10 +36849,58 @@ spec: type: object spec: properties: + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + type: object + type: object + served: true + storage: true +--- +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_stagedglobalnetworkpolicies.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: stagedglobalnetworkpolicies.crd.projectcalico.org +spec: + group: crd.projectcalico.org + names: + kind: StagedGlobalNetworkPolicy + listKind: StagedGlobalNetworkPolicyList + plural: stagedglobalnetworkpolicies + singular: stagedglobalnetworkpolicy + preserveUnknownFields: false + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + applyOnForward: + type: boolean + doNotTrack: + type: boolean egress: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -32657,6 +36910,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -32687,6 +36941,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -32717,11 +36972,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -32733,8 +36995,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -32757,6 +37023,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -32787,6 +37054,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -32806,6 +37074,11 @@ spec: items: properties: action: + enum: + - Allow + - Deny + - Log + - Pass type: string destination: properties: @@ -32815,6 +37088,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -32845,6 +37119,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -32875,11 +37150,18 @@ spec: icmp: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object ipVersion: + enum: + - 4 + - 6 type: integer metadata: properties: @@ -32891,8 +37173,12 @@ spec: notICMP: properties: code: + maximum: 255 + minimum: 0 type: integer type: + maximum: 255 + minimum: 0 type: integer type: object notProtocol: @@ -32915,6 +37201,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set notNets: items: type: string @@ -32945,6 +37232,7 @@ spec: items: type: string type: array + x-kubernetes-list-type: set selector: type: string type: object @@ -32960,45 +37248,63 @@ spec: - action type: object type: array + namespaceSelector: + type: string order: type: number performanceHints: items: + enum: + - AssumeNeededOnEveryNode type: string type: array + preDNAT: + type: boolean selector: type: string serviceAccountSelector: type: string stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore type: string tier: + default: default type: string types: items: + enum: + - Ingress + - Egress type: string + maxItems: 2 + minItems: 1 type: array + x-kubernetes-list-type: set type: object type: object served: true storage: true --- -# Source: crds/crd.projectcalico.org_tiers.yaml +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_stagedkubernetesnetworkpolicies.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.18.0 - name: tiers.crd.projectcalico.org + name: stagedkubernetesnetworkpolicies.crd.projectcalico.org spec: group: crd.projectcalico.org names: - kind: Tier - listKind: TierList - plural: tiers - singular: tier + kind: StagedKubernetesNetworkPolicy + listKind: StagedKubernetesNetworkPolicyList + plural: stagedkubernetesnetworkpolicies + singular: stagedkubernetesnetworkpolicy preserveUnknownFields: false - scope: Cluster + scope: Namespaced versions: - name: v1 schema: @@ -33011,1159 +37317,753 @@ spec: metadata: type: object spec: - properties: - defaultAction: - enum: - - Pass - - Deny - type: string - order: - type: number - type: object - type: object - served: true - storage: true ---- -# Source: crds/policy.networking.k8s.io_adminnetworkpolicies.yaml -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/30 - policy.networking.k8s.io/bundle-version: v0.1.1 - policy.networking.k8s.io/channel: experimental - creationTimestamp: null - name: adminnetworkpolicies.policy.networking.k8s.io -spec: - group: policy.networking.k8s.io - names: - kind: AdminNetworkPolicy - listKind: AdminNetworkPolicyList - plural: adminnetworkpolicies - shortNames: - - anp - singular: adminnetworkpolicy - scope: Cluster - versions: - - additionalPrinterColumns: - - jsonPath: .spec.priority - name: Priority - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: |- - AdminNetworkPolicy is a cluster level resource that is part of the - AdminNetworkPolicy API. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: Specification of the desired behavior of AdminNetworkPolicy. properties: egress: - description: |- - Egress is the list of Egress rules to be applied to the selected pods. - A total of 100 rules will be allowed in each ANP instance. - The relative precedence of egress rules within a single ANP object (all of - which share the priority) will be determined by the order in which the rule - is written. Thus, a rule that appears at the top of the egress rules - would take the highest precedence. - ANPs with no egress rules do not affect egress traffic. - - - Support: Core items: - description: |- - AdminNetworkPolicyEgressRule describes an action to take on a particular - set of traffic originating from pods selected by a AdminNetworkPolicy's - Subject field. - properties: - action: - description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic (even if it would otherwise have been denied by NetworkPolicy) - Deny: denies the selected traffic - Pass: instructs the selected traffic to skip any remaining ANP rules, and - then pass execution to any NetworkPolicies that select the pod. - If the pod is not selected by any NetworkPolicies then execution - is passed to any BaselineAdminNetworkPolicies that select the pod. - - - Support: Core - enum: - - Allow - - Deny - - Pass - type: string - name: - description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - AdminNetworkPolicies. - - - Support: Core - maxLength: 100 - type: string ports: - description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of destination ports for the outgoing egress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core items: - description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). - Exactly one field must be set. - maxProperties: 1 - minProperties: 1 properties: - namedPort: - description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - + endPort: + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + protocol: type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core - properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol - type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core - properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start - type: object type: object - maxItems: 100 type: array + x-kubernetes-list-type: atomic to: - description: |- - To is the List of destinations whose traffic this rule applies to. - If any AdminNetworkPolicyEgressPeer matches the destination of outgoing - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core items: - description: |- - AdminNetworkPolicyEgressPeer defines a peer to allow traffic to. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. - maxProperties: 1 - minProperties: 1 properties: - namespaces: - description: |- - Namespaces defines a way to select all pods within a set of Namespaces. - Note that host-networked pods are not included in this type of peer. - - - Support: Core + ipBlock: properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. + cidr: + type: string + except: items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object + type: array + x-kubernetes-list-type: atomic + required: + - cidr type: object - x-kubernetes-map-type: atomic - networks: - description: |- - Networks defines a way to select peers via CIDR blocks. - This is intended for representing entities that live outside the cluster, - which can't be selected by pods, namespaces and nodes peers, but note - that cluster-internal traffic will be checked against the rule as - well. So if you Allow or Deny traffic to `"0.0.0.0/0"`, that will allow - or deny all IPv4 pod-to-pod traffic as well. If you don't want that, - add a rule that Passes all pod traffic before the Networks rule. - - - Each item in Networks should be provided in the CIDR format and should be - IPv4 or IPv6, for example "10.0.0.0/8" or "fd00::/8". - - - Networks can have upto 25 CIDRs specified. - - - Support: Extended - - - - items: - description: |- - CIDR is an IP address range in CIDR notation (for example, "10.0.0.0/8" or "fd00::/8"). - This string must be validated by implementations using net.ParseCIDR - TODO: Introduce CEL CIDR validation regex isCIDR() in Kube 1.31 when it is available. - maxLength: 43 - type: string - x-kubernetes-validations: - - message: - CIDR must be either an IPv4 or IPv6 address. - IPv4 address embedded in IPv6 addresses are not - supported - rule: self.contains(':') != self.contains('.') - maxItems: 25 - minItems: 1 - type: array - x-kubernetes-list-type: set - nodes: - description: |- - Nodes defines a way to select a set of nodes in - the cluster. This field follows standard label selector - semantics; if present but empty, it selects all Nodes. - - - Support: Extended - - - + namespaceSelector: properties: matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: |- - Pods defines a way to select a set of pods in - a set of namespaces. Note that host-networked pods - are not included in this type of peer. - - - Support: Core - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: + properties: + key: type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: + type: object + x-kubernetes-map-type: atomic + podSelector: + properties: + matchExpressions: + items: + properties: + key: type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector type: object + x-kubernetes-map-type: atomic type: object - maxItems: 100 - minItems: 1 type: array - required: - - action - - to + x-kubernetes-list-type: atomic type: object - x-kubernetes-validations: - - message: - networks/nodes peer cannot be set with namedPorts since - there are no namedPorts for networks/nodes - rule: - "!(self.to.exists(peer, has(peer.networks) || has(peer.nodes)) - && has(self.ports) && self.ports.exists(port, has(port.namedPort)))" - maxItems: 100 type: array ingress: - description: |- - Ingress is the list of Ingress rules to be applied to the selected pods. - A total of 100 rules will be allowed in each ANP instance. - The relative precedence of ingress rules within a single ANP object (all of - which share the priority) will be determined by the order in which the rule - is written. Thus, a rule that appears at the top of the ingress rules - would take the highest precedence. - ANPs with no ingress rules do not affect ingress traffic. - - - Support: Core items: - description: |- - AdminNetworkPolicyIngressRule describes an action to take on a particular - set of traffic destined for pods selected by an AdminNetworkPolicy's - Subject field. properties: - action: - description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic (even if it would otherwise have been denied by NetworkPolicy) - Deny: denies the selected traffic - Pass: instructs the selected traffic to skip any remaining ANP rules, and - then pass execution to any NetworkPolicies that select the pod. - If the pod is not selected by any NetworkPolicies then execution - is passed to any BaselineAdminNetworkPolicies that select the pod. - - - Support: Core - enum: - - Allow - - Deny - - Pass - type: string from: - description: |- - From is the list of sources whose traffic this rule applies to. - If any AdminNetworkPolicyIngressPeer matches the source of incoming - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core items: - description: |- - AdminNetworkPolicyIngressPeer defines an in-cluster peer to allow traffic from. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. - maxProperties: 1 - minProperties: 1 properties: - namespaces: - description: |- - Namespaces defines a way to select all pods within a set of Namespaces. - Note that host-networked pods are not included in this type of peer. - - - Support: Core + ipBlock: + properties: + cidr: + type: string + except: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - cidr + type: object + namespaceSelector: properties: matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. properties: key: - description: - key is the label key that the selector - applies to. type: string operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. type: string values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object x-kubernetes-map-type: atomic - pods: - description: |- - Pods defines a way to select a set of pods in - a set of namespaces. Note that host-networked pods - are not included in this type of peer. - - - Support: Core + podSelector: properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: + matchExpressions: + items: + properties: + key: type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: + operator: type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector type: object + x-kubernetes-map-type: atomic type: object - maxItems: 100 - minItems: 1 type: array - name: - description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - AdminNetworkPolicies. - - - Support: Core - maxLength: 100 - type: string + x-kubernetes-list-type: atomic ports: - description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of ports which should be matched on - the pods selected for this policy i.e the subject of the policy. - So it matches on the destination port for the ingress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core items: - description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). - Exactly one field must be set. - maxProperties: 1 - minProperties: 1 properties: - namedPort: - description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - + endPort: + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + protocol: type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: array + podSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + policyTypes: + items: + type: string + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-list-type: set + stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore + type: string + type: object + type: object + served: true + storage: true +--- +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_stagednetworkpolicies.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: stagednetworkpolicies.crd.projectcalico.org +spec: + group: crd.projectcalico.org + names: + kind: StagedNetworkPolicy + listKind: StagedNetworkPolicyList + plural: stagednetworkpolicies + singular: stagednetworkpolicy + preserveUnknownFields: false + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + egress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core + exact: + type: string + prefix: type: string - required: - - port - - protocol type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core - properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: + enum: + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start - type: object - type: object - maxItems: 100 - type: array + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object required: - action - - from type: object - maxItems: 100 type: array - priority: - description: |- - Priority is a value from 0 to 1000. Rules with lower priority values have - higher precedence, and are checked before rules with higher priority values. - All AdminNetworkPolicy rules have higher precedence than NetworkPolicy or - BaselineAdminNetworkPolicy rules - The behavior is undefined if two ANP objects have same priority. - - - Support: Core - format: int32 - maximum: 1000 - minimum: 0 - type: integer - subject: - description: |- - Subject defines the pods to which this AdminNetworkPolicy applies. - Note that host-networked pods are not included in subject selection. - - - Support: Core - maxProperties: 1 - minProperties: 1 - properties: - namespaces: - description: Namespaces is used to select pods via namespace selectors. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. + ingress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. + names: items: type: string type: array - required: - - key - - operator + x-kubernetes-list-type: set + selector: + type: string type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - pods: - description: - Pods is used to select pods via namespace AND pod - selectors. - properties: - namespaceSelector: - description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: + services: + properties: + name: type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - podSelector: - description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: + namespace: type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: + properties: + exact: + type: string + prefix: + type: string type: object - type: object - x-kubernetes-map-type: atomic - required: - - namespaceSelector - - podSelector - type: object - type: object - required: - - priority - - subject - type: object - status: - description: Status is the status to be reported by the implementation. - properties: - conditions: - items: - description: - "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: enum: - - "True" - - "False" - - Unknown - type: string - type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object required: - - lastTransitionTime - - message - - reason - - status - - type + - action type: object type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - required: - - conditions + order: + type: number + performanceHints: + items: + enum: + - AssumeNeededOnEveryNode + type: string + type: array + selector: + type: string + serviceAccountSelector: + type: string + stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore + type: string + tier: + default: default + type: string + types: + items: + enum: + - Ingress + - Egress + type: string + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-list-type: set + type: object + type: object + served: true + storage: true +--- +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_tiers.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: tiers.crd.projectcalico.org +spec: + group: crd.projectcalico.org + names: + kind: Tier + listKind: TierList + plural: tiers + singular: tier + preserveUnknownFields: false + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + defaultAction: + allOf: + - enum: + - Allow + - Deny + - Log + - Pass + - enum: + - Pass + - Deny + type: string + order: + type: number type: object required: - metadata - spec type: object + x-kubernetes-validations: + - message: The 'kube-admin' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-admin' ? self.spec.defaultAction == + 'Pass' : true" + - message: The 'kube-baseline' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-baseline' ? self.spec.defaultAction + == 'Pass' : true" + - message: The 'default' tier must have default action 'Deny' + rule: + "self.metadata.name == 'default' ? self.spec.defaultAction == 'Deny' + : true" served: true storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: null - storedVersions: null --- -# Source: crds/policy.networking.k8s.io_baselineadminnetworkpolicies.yaml +# Source: crd.projectcalico.org.v1/templates/calico/policy.networking.k8s.io_clusternetworkpolicies.yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/30 - policy.networking.k8s.io/bundle-version: v0.1.1 - policy.networking.k8s.io/channel: experimental - creationTimestamp: null - name: baselineadminnetworkpolicies.policy.networking.k8s.io + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/300 + policy.networking.k8s.io/bundle-version: v0.1.7 + policy.networking.k8s.io/channel: standard + name: clusternetworkpolicies.policy.networking.k8s.io spec: group: policy.networking.k8s.io names: - kind: BaselineAdminNetworkPolicy - listKind: BaselineAdminNetworkPolicyList - plural: baselineadminnetworkpolicies + kind: ClusterNetworkPolicy + listKind: ClusterNetworkPolicyList + plural: clusternetworkpolicies shortNames: - - banp - singular: baselineadminnetworkpolicy + - cnp + singular: clusternetworkpolicy scope: Cluster versions: - additionalPrinterColumns: + - jsonPath: .spec.tier + name: Tier + type: string + - jsonPath: .spec.priority + name: Priority + type: string - jsonPath: .metadata.creationTimestamp name: Age type: date - name: v1alpha1 + name: v1alpha2 schema: openAPIV3Schema: - description: |- - BaselineAdminNetworkPolicy is a cluster level resource that is part of the - AdminNetworkPolicy API. + description: ClusterNetworkPolicy is a cluster-wide network policy resource. properties: apiVersion: description: |- @@ -34183,165 +38083,233 @@ spec: metadata: type: object spec: - description: Specification of the desired behavior of BaselineAdminNetworkPolicy. + description: Spec defines the desired behavior of ClusterNetworkPolicy. properties: egress: description: |- - Egress is the list of Egress rules to be applied to the selected pods if - they are not matched by any AdminNetworkPolicy or NetworkPolicy rules. - A total of 100 Egress rules will be allowed in each BANP instance. - The relative precedence of egress rules within a single BANP object - will be determined by the order in which the rule is written. - Thus, a rule that appears at the top of the egress rules - would take the highest precedence. - BANPs with no egress rules do not affect egress traffic. + Egress is the list of Egress rules to be applied to the selected pods. + A maximum of 25 rules is allowed in this block. - Support: Core + The relative precedence of egress rules within a single CNP object + (all of which share the priority) will be determined by the order + in which the rule is written. + Thus, a rule that appears at the top of the egress rules + would take the highest precedence. + CNPs with no egress rules do not affect egress traffic. items: description: |- - BaselineAdminNetworkPolicyEgressRule describes an action to take on a particular - set of traffic originating from pods selected by a BaselineAdminNetworkPolicy's + ClusterNetworkPolicyEgressRule describes an action to take on a particular + set of traffic originating from pods selected by a ClusterNetworkPolicy's Subject field. + properties: action: description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic - Deny: denies the selected traffic + Action specifies the effect this rule will have on matching + traffic. Currently the following actions are supported: + + - Accept: Accepts the selected traffic, allowing it to + egress. No further ClusterNetworkPolicy or NetworkPolicy + rules will be processed. + - Deny: Drops the selected traffic. No further + ClusterNetworkPolicy or NetworkPolicy rules will be + processed. - Support: Core + - Pass: Skips all further ClusterNetworkPolicy rules in the + current tier for the selected traffic, and passes + evaluation to the next tier. enum: - - Allow + - Accept - Deny + - Pass type: string name: description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - BaselineAdminNetworkPolicies. - - - Support: Core + Name is an identifier for this rule, that may be no more than + 100 characters in length. This field should be used by the implementation + to help improve observability, readability and error-reporting + for any applied policies. maxLength: 100 type: string - ports: + protocols: description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of destination ports for the outgoing egress traffic. - If Ports is not set then the rule does not filter traffic via port. + Protocols allows for more fine-grain matching of traffic on + protocol-specific attributes such as the port. If + unspecified, protocol-specific attributes will not be used + to match traffic. items: description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). + ClusterNetworkPolicyProtocol describes additional protocol-specific match rules. Exactly one field must be set. maxProperties: 1 minProperties: 1 properties: - namedPort: + destinationNamedPort: description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - + DestinationNamedPort selects a destination port on a pod based on the + ContainerPort name. You can't use this in a rule that targets resources + without named ports (e.g. Nodes or Networks). type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core + sctp: + description: SCTP specific protocol matches. + minProperties: 1 properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core + tcp: + description: TCP specific protocol matches. + minProperties: 1 properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + udp: + description: UDP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object type: object - maxItems: 100 + maxItems: 25 + minItems: 1 type: array to: description: |- - To is the list of destinations whose traffic this rule applies to. - If any AdminNetworkPolicyEgressPeer matches the destination of outgoing - traffic then the specified action is applied. - This field must be defined and contain at least one item. - - - Support: Core + To is the list of destinations whose traffic this rule applies to. If any + element matches the destination of outgoing traffic then the specified + action is applied. This field must be defined and contain at least one + item. items: description: |- - AdminNetworkPolicyEgressPeer defines a peer to allow traffic to. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. + ClusterNetworkPolicyEgressPeer defines a peer to allow traffic to. + + Exactly one of the fields must be set for a given peer and this is enforced + by the validation rules on the CRD. If an implementation sees no fields are + set then it can infer that the deployed CRD is of an incompatible version + with an unknown field. In that case it should fail closed. + + For "Accept" rules, "fail closed" means: "treat the rule as matching no + traffic". For "Deny" and "Pass" rules, "fail closed" means: "treat the rule + as a 'Deny all' rule". maxProperties: 1 minProperties: 1 properties: @@ -34349,9 +38317,6 @@ spec: description: |- Namespaces defines a way to select all pods within a set of Namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: matchExpressions: description: @@ -34381,11 +38346,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -34402,107 +38369,37 @@ spec: This is intended for representing entities that live outside the cluster, which can't be selected by pods, namespaces and nodes peers, but note that cluster-internal traffic will be checked against the rule as - well. So if you Allow or Deny traffic to `"0.0.0.0/0"`, that will allow + well. So if you Accept or Deny traffic to `"0.0.0.0/0"`, that will allow or deny all IPv4 pod-to-pod traffic as well. If you don't want that, add a rule that Passes all pod traffic before the Networks rule. - Each item in Networks should be provided in the CIDR format and should be IPv4 or IPv6, for example "10.0.0.0/8" or "fd00::/8". - - Networks can have upto 25 CIDRs specified. - - - Support: Extended - - - + Networks can have up to 25 CIDRs specified. items: description: |- - CIDR is an IP address range in CIDR notation (for example, "10.0.0.0/8" or "fd00::/8"). - This string must be validated by implementations using net.ParseCIDR - TODO: Introduce CEL CIDR validation regex isCIDR() in Kube 1.31 when it is available. + CIDR is an IP address range in CIDR notation + (for example, "10.0.0.0/8" or "fd00::/8"). maxLength: 43 type: string x-kubernetes-validations: - - message: - CIDR must be either an IPv4 or IPv6 address. - IPv4 address embedded in IPv6 addresses are not - supported - rule: self.contains(':') != self.contains('.') + - message: Invalid CIDR format provided + rule: isCIDR(self) maxItems: 25 minItems: 1 type: array x-kubernetes-list-type: set - nodes: - description: |- - Nodes defines a way to select a set of nodes in - the cluster. This field follows standard label selector - semantics; if present but empty, it selects all Nodes. - - - Support: Extended - - - - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic pods: description: |- Pods defines a way to select a set of pods in a set of namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -34533,11 +38430,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -34550,8 +38449,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -34582,11 +38481,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -34598,73 +38499,80 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object - maxItems: 100 + maxItems: 25 minItems: 1 type: array required: - action - to type: object - x-kubernetes-validations: - - message: - networks/nodes peer cannot be set with namedPorts since - there are no namedPorts for networks/nodes - rule: - "!(self.to.exists(peer, has(peer.networks) || has(peer.nodes)) - && has(self.ports) && self.ports.exists(port, has(port.namedPort)))" - maxItems: 100 + maxItems: 25 type: array ingress: description: |- - Ingress is the list of Ingress rules to be applied to the selected pods - if they are not matched by any AdminNetworkPolicy or NetworkPolicy rules. - A total of 100 Ingress rules will be allowed in each BANP instance. - The relative precedence of ingress rules within a single BANP object - will be determined by the order in which the rule is written. - Thus, a rule that appears at the top of the ingress rules - would take the highest precedence. - BANPs with no ingress rules do not affect ingress traffic. + Ingress is the list of Ingress rules to be applied to the selected pods. + A maximum of 25 rules is allowed in this block. - Support: Core + The relative precedence of ingress rules within a single CNP object + (all of which share the priority) will be determined by the order + in which the rule is written. + Thus, a rule that appears at the top of the ingress rules + would take the highest precedence. + CNPs with no ingress rules do not affect ingress traffic. items: description: |- - BaselineAdminNetworkPolicyIngressRule describes an action to take on a particular - set of traffic destined for pods selected by a BaselineAdminNetworkPolicy's + ClusterNetworkPolicyIngressRule describes an action to take on a particular + set of traffic destined for pods selected by a ClusterNetworkPolicy's Subject field. properties: action: description: |- - Action specifies the effect this rule will have on matching traffic. - Currently the following actions are supported: - Allow: allows the selected traffic - Deny: denies the selected traffic + Action specifies the effect this rule will have on matching + traffic. Currently the following actions are supported: + - Accept: Accepts the selected traffic, allowing it into + the destination. No further ClusterNetworkPolicy or + NetworkPolicy rules will be processed. - Support: Core + Note: while Accept ensures traffic is accepted by + Kubernetes network policy, it is still possible that the + packet is blocked in other ways: custom nftable rules, + high-layers e.g. service mesh. + + - Deny: Drops the selected traffic. No further + ClusterNetworkPolicy or NetworkPolicy rules will be + processed. + + - Pass: Skips all further ClusterNetworkPolicy rules in the + current tier for the selected traffic, and passes + evaluation to the next tier. enum: - - Allow + - Accept - Deny + - Pass type: string from: description: |- From is the list of sources whose traffic this rule applies to. - If any AdminNetworkPolicyIngressPeer matches the source of incoming + If any element matches the source of incoming traffic then the specified action is applied. This field must be defined and contain at least one item. - - - Support: Core items: description: |- - AdminNetworkPolicyIngressPeer defines an in-cluster peer to allow traffic from. - Exactly one of the selector pointers must be set for a given peer. If a - consumer observes none of its fields are set, they must assume an unknown - option has been specified and fail closed. + ClusterNetworkPolicyIngressPeer defines a peer to allow traffic from. + + Exactly one of the fields must be set for a given peer and this is enforced + by the validation rules on the CRD. If an implementation sees no fields are + set then it can infer that the deployed CRD is of an incompatible version + with an unknown field. In that case it should fail closed. + + For "Accept" rules, "fail closed" means: "treat the rule as matching no + traffic". For "Deny" and "Pass" rules, "fail closed" means: "treat the rule + as a 'Deny all' rule". maxProperties: 1 minProperties: 1 properties: @@ -34672,9 +38580,6 @@ spec: description: |- Namespaces defines a way to select all pods within a set of Namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: matchExpressions: description: @@ -34704,11 +38609,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -34724,14 +38631,11 @@ spec: Pods defines a way to select a set of pods in a set of namespaces. Note that host-networked pods are not included in this type of peer. - - - Support: Core properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -34762,11 +38666,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -34779,8 +38685,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -34811,11 +38717,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -34827,140 +38735,206 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object - maxItems: 100 + maxItems: 25 minItems: 1 type: array name: description: |- - Name is an identifier for this rule, that may be no more than 100 characters - in length. This field should be used by the implementation to help - improve observability, readability and error-reporting for any applied - BaselineAdminNetworkPolicies. - - - Support: Core + Name is an identifier for this rule, that may be no more than + 100 characters in length. This field should be used by the implementation + to help improve observability, readability and error-reporting + for any applied policies. maxLength: 100 type: string - ports: + protocols: description: |- - Ports allows for matching traffic based on port and protocols. - This field is a list of ports which should be matched on - the pods selected for this policy i.e the subject of the policy. - So it matches on the destination port for the ingress traffic. - If Ports is not set then the rule does not filter traffic via port. - - - Support: Core + Protocols allows for more fine-grain matching of traffic on + protocol-specific attributes such as the port. If + unspecified, protocol-specific attributes will not be used + to match traffic. items: description: |- - AdminNetworkPolicyPort describes how to select network ports on pod(s). + ClusterNetworkPolicyProtocol describes additional protocol-specific match rules. Exactly one field must be set. maxProperties: 1 minProperties: 1 properties: - namedPort: + destinationNamedPort: description: |- - NamedPort selects a port on a pod(s) based on name. - - - Support: Extended - - - + DestinationNamedPort selects a destination port on a pod based on the + ContainerPort name. You can't use this in a rule that targets resources + without named ports (e.g. Nodes or Networks). type: string - portNumber: - description: |- - Port selects a port on a pod(s) based on number. - - - Support: Core + sctp: + description: SCTP specific protocol matches. + minProperties: 1 properties: - port: - description: |- - Number defines a network port value. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - required: - - port - - protocol + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object - portRange: - description: |- - PortRange selects a port range on a pod(s) based on provided start and end - values. - - - Support: Core + tcp: + description: TCP specific protocol matches. + minProperties: 1 properties: - end: - description: |- - End defines a network port that is the end of a port range, the End value - must be greater than Start. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - protocol: - default: TCP - description: |- - Protocol is the network protocol (TCP, UDP, or SCTP) which traffic must - match. If not specified, this field defaults to TCP. - - - Support: Core - type: string - start: - description: |- - Start defines a network port that is the start of a port range, the Start - value must be less than End. - - - Support: Core - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - end - - start + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + udp: + description: UDP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object type: object type: object - maxItems: 100 + maxItems: 25 + minItems: 1 type: array required: - action - from type: object - maxItems: 100 + maxItems: 25 type: array - subject: + priority: description: |- - Subject defines the pods to which this BaselineAdminNetworkPolicy applies. - Note that host-networked pods are not included in subject selection. - - - Support: Core + Priority is a value from 0 to 1000 indicating the precedence of + the policy within its tier. Policies with lower priority values have + higher precedence, and are checked before policies with higher priority + values in the same tier. All Admin tier rules have higher precedence than + NetworkPolicy or Baseline tier rules. + If two (or more) policies in the same tier with the same priority + could match a connection, then the implementation can apply any of the + matching policies to the connection, and there is no way for the user to + reliably determine which one it will choose. Administrators must be + careful about assigning the priorities for policies with rules that will + match many connections, and ensure that policies have unique priority + values in cases where ambiguity would be unacceptable. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + subject: + description: + Subject defines the pods to which this ClusterNetworkPolicy + applies. maxProperties: 1 minProperties: 1 properties: @@ -34995,11 +38969,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -35017,8 +38993,8 @@ spec: properties: namespaceSelector: description: |- - NamespaceSelector follows standard label selector semantics; if empty, - it selects all Namespaces. + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. properties: matchExpressions: description: @@ -35048,11 +39024,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -35065,8 +39043,8 @@ spec: x-kubernetes-map-type: atomic podSelector: description: |- - PodSelector is used to explicitly select pods within a namespace; if empty, - it selects all Pods. + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. properties: matchExpressions: description: @@ -35096,11 +39074,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -35112,12 +39092,48 @@ spec: type: object x-kubernetes-map-type: atomic required: - - namespaceSelector - podSelector type: object type: object + tier: + description: |- + Tier is used as the top-level grouping for network policy prioritization. + + Policy tiers are evaluated in the following order: + * Admin tier + * NetworkPolicy tier + * Baseline tier + + ClusterNetworkPolicy can use 2 of these tiers: Admin and Baseline. + + The Admin tier takes precedence over all other policies. Policies + defined in this tier are used to set cluster-wide security rules + that cannot be overridden in the other tiers. If Admin tier has + made a final decision (Accept or Deny) on a connection, then no + further evaluation is done. + + NetworkPolicy tier is the tier for the namespaced v1.NetworkPolicy. + These policies are intended for the application developer to describe + the security policy associated with their deployments inside their + namespace. v1.NetworkPolicy always makes a final decision for selected + pods. Further evaluation only happens for Pods not selected by a + v1.NetworkPolicy. + + Baseline tier is a cluster-wide policy that can be overridden by the + v1.NetworkPolicy. If Baseline tier has made a final decision (Accept or + Deny) on a connection, then no further evaluation is done. + + If a given connection wasn't allowed or denied by any of the tiers, + the default kubernetes policy is applied, which says that + all pods can communicate with each other. + enum: + - Admin + - Baseline + type: string required: + - priority - subject + - tier type: object status: description: Status is the status to be reported by the implementation. @@ -35125,16 +39141,8 @@ spec: conditions: items: description: - "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -35175,12 +39183,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string @@ -35202,11 +39205,6 @@ spec: - metadata - spec type: object - x-kubernetes-validations: - - message: - Only one baseline admin network policy with metadata.name="default" - can be created in the cluster - rule: self.metadata.name == 'default' served: true storage: true subresources: diff --git a/manifests/tigera-operator-ocp-upgrade.yaml b/manifests/tigera-operator-ocp-upgrade.yaml index ca2e11091db..c233212a4a4 100644 --- a/manifests/tigera-operator-ocp-upgrade.yaml +++ b/manifests/tigera-operator-ocp-upgrade.yaml @@ -77,8 +77,10 @@ metadata: labels: k8s-app: tigera-operator rules: - # The tigera/operator installs CustomResourceDefinitions necessary for itself + # The tigera/operator needs the following permissions when configured with the --bootstrapCRDs=true + # argument. When specified, the tigera/operator installs CustomResourceDefinitions necessary for itself # and Calico more broadly to function. + # You can remove this permission block if your operator deployment does not use this option. - apiGroups: - apiextensions.k8s.io resources: @@ -88,7 +90,7 @@ rules: - list - watch - create - # We only allow update access to our own CRDs. + # Allow updating the crd.projectcalico.org CRDs. - apiGroups: - apiextensions.k8s.io resources: @@ -96,11 +98,6 @@ rules: verbs: - update resourceNames: - - apiservers.operator.tigera.io - - gatewayapis.operator.tigera.io - - imagesets.operator.tigera.io - - installations.operator.tigera.io - - tigerastatuses.operator.tigera.io - bgpconfigurations.crd.projectcalico.org - bgpfilters.crd.projectcalico.org - bgppeers.crd.projectcalico.org @@ -123,10 +120,54 @@ rules: - stagedkubernetesnetworkpolicies.crd.projectcalico.org - networksets.crd.projectcalico.org - tiers.crd.projectcalico.org + # Allow updating the projectcalico.org/v3 CRDs. + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - update + resourceNames: + - bgpconfigurations.projectcalico.org + - bgpfilters.projectcalico.org + - bgppeers.projectcalico.org + - blockaffinities.projectcalico.org + - caliconodestatuses.projectcalico.org + - clusterinformations.projectcalico.org + - felixconfigurations.projectcalico.org + - globalnetworkpolicies.projectcalico.org + - stagedglobalnetworkpolicies.projectcalico.org + - globalnetworksets.projectcalico.org + - hostendpoints.projectcalico.org + - ipamblocks.projectcalico.org + - ipamconfigurations.projectcalico.org + - ipamhandles.projectcalico.org + - ippools.projectcalico.org + - ipreservations.projectcalico.org + - kubecontrollersconfigurations.projectcalico.org + - networkpolicies.projectcalico.org + - stagednetworkpolicies.projectcalico.org + - stagedkubernetesnetworkpolicies.projectcalico.org + - networksets.projectcalico.org + - tiers.projectcalico.org + # Allow updating the operator.tigera.io CRDs. + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - update + resourceNames: + - apiservers.operator.tigera.io + - gatewayapis.operator.tigera.io + - imagesets.operator.tigera.io + - installations.operator.tigera.io + - tigerastatuses.operator.tigera.io - whiskers.operator.tigera.io - goldmanes.operator.tigera.io - managementclusterconnections.operator.tigera.io - # We need update and delete access for ANP/BANP CRDs to set owner refs when assuming control of pre-existing CRDs, for example on OCP. + # We need update and delete access for the ClusterNetworkPolicy CRD to set owner references + # when assuming control of pre-existing CRDs, for example on OCP. - apiGroups: - apiextensions.k8s.io resources: @@ -135,8 +176,7 @@ rules: - update - delete resourceNames: - - adminnetworkpolicies.policy.networking.k8s.io - - baselineadminnetworkpolicies.policy.networking.k8s.io + - clusternetworkpolicies.policy.networking.k8s.io - apiGroups: - "" resources: @@ -298,6 +338,7 @@ rules: - watch - apiGroups: - crd.projectcalico.org + - projectcalico.org resources: - felixconfigurations - ippools @@ -309,6 +350,7 @@ rules: - watch - apiGroups: - crd.projectcalico.org + - projectcalico.org resources: - kubecontrollersconfigurations - bgpconfigurations @@ -382,10 +424,19 @@ rules: resources: - mutatingwebhookconfigurations verbs: + - create - delete - get - list - watch + - apiGroups: + - admissionregistration.k8s.io + resources: + - mutatingwebhookconfigurations + resourceNames: + - envoy-gateway-topology-injector.tigera-gateway + verbs: + - update # Needed for operator lock - apiGroups: - coordination.k8s.io @@ -417,6 +468,18 @@ rules: verbs: - list - watch + # The operator needs permissions to create and update validating webhook configuration. + - apiGroups: + - admissionregistration.k8s.io + resources: + - validatingwebhookconfigurations + verbs: + - create + - update + - delete + - get + - list + - watch # When running in OpenShift, we need to update networking config. - apiGroups: - config.openshift.io @@ -463,6 +526,7 @@ rules: # Need these permissions for the calicoctl init container. - apiGroups: - crd.projectcalico.org + - projectcalico.org resources: - bgpconfigurations - bgppeers @@ -478,6 +542,7 @@ rules: - create - apiGroups: - crd.projectcalico.org + - projectcalico.org resources: - ipamblocks verbs: @@ -548,6 +613,8 @@ rules: - tcproutes.gateway.networking.k8s.io - tlsroutes.gateway.networking.k8s.io - udproutes.gateway.networking.k8s.io + - xbackendtrafficpolicies.gateway.networking.x-k8s.io + - xlistenersets.gateway.networking.x-k8s.io - backends.gateway.envoyproxy.io - backendtrafficpolicies.gateway.envoyproxy.io - clienttrafficpolicies.gateway.envoyproxy.io @@ -597,6 +664,16 @@ rules: - update resourceNames: - tigera-gateway-api-gateway-helm-certgen + # For monitoring KubeVirt live migration. The operator does not do this itself, but needs to + # be able to assign this RBAC to Typha and calico-node. + - apiGroups: + - kubevirt.io + resources: + - virtualmachineinstancemigrations + verbs: + - get + - list + - watch --- apiVersion: v1 kind: ServiceAccount diff --git a/manifests/tigera-operator.yaml b/manifests/tigera-operator.yaml index 8b514a45d38..beaf95b7f1f 100644 --- a/manifests/tigera-operator.yaml +++ b/manifests/tigera-operator.yaml @@ -44,8 +44,10 @@ metadata: labels: k8s-app: tigera-operator rules: - # The tigera/operator installs CustomResourceDefinitions necessary for itself + # The tigera/operator needs the following permissions when configured with the --bootstrapCRDs=true + # argument. When specified, the tigera/operator installs CustomResourceDefinitions necessary for itself # and Calico more broadly to function. + # You can remove this permission block if your operator deployment does not use this option. - apiGroups: - apiextensions.k8s.io resources: @@ -55,7 +57,7 @@ rules: - list - watch - create - # We only allow update access to our own CRDs. + # Allow updating the crd.projectcalico.org CRDs. - apiGroups: - apiextensions.k8s.io resources: @@ -63,11 +65,6 @@ rules: verbs: - update resourceNames: - - apiservers.operator.tigera.io - - gatewayapis.operator.tigera.io - - imagesets.operator.tigera.io - - installations.operator.tigera.io - - tigerastatuses.operator.tigera.io - bgpconfigurations.crd.projectcalico.org - bgpfilters.crd.projectcalico.org - bgppeers.crd.projectcalico.org @@ -90,10 +87,54 @@ rules: - stagedkubernetesnetworkpolicies.crd.projectcalico.org - networksets.crd.projectcalico.org - tiers.crd.projectcalico.org + # Allow updating the projectcalico.org/v3 CRDs. + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - update + resourceNames: + - bgpconfigurations.projectcalico.org + - bgpfilters.projectcalico.org + - bgppeers.projectcalico.org + - blockaffinities.projectcalico.org + - caliconodestatuses.projectcalico.org + - clusterinformations.projectcalico.org + - felixconfigurations.projectcalico.org + - globalnetworkpolicies.projectcalico.org + - stagedglobalnetworkpolicies.projectcalico.org + - globalnetworksets.projectcalico.org + - hostendpoints.projectcalico.org + - ipamblocks.projectcalico.org + - ipamconfigurations.projectcalico.org + - ipamhandles.projectcalico.org + - ippools.projectcalico.org + - ipreservations.projectcalico.org + - kubecontrollersconfigurations.projectcalico.org + - networkpolicies.projectcalico.org + - stagednetworkpolicies.projectcalico.org + - stagedkubernetesnetworkpolicies.projectcalico.org + - networksets.projectcalico.org + - tiers.projectcalico.org + # Allow updating the operator.tigera.io CRDs. + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - update + resourceNames: + - apiservers.operator.tigera.io + - gatewayapis.operator.tigera.io + - imagesets.operator.tigera.io + - installations.operator.tigera.io + - tigerastatuses.operator.tigera.io - whiskers.operator.tigera.io - goldmanes.operator.tigera.io - managementclusterconnections.operator.tigera.io - # We need update and delete access for ANP/BANP CRDs to set owner refs when assuming control of pre-existing CRDs, for example on OCP. + # We need update and delete access for the ClusterNetworkPolicy CRD to set owner references + # when assuming control of pre-existing CRDs, for example on OCP. - apiGroups: - apiextensions.k8s.io resources: @@ -102,8 +143,7 @@ rules: - update - delete resourceNames: - - adminnetworkpolicies.policy.networking.k8s.io - - baselineadminnetworkpolicies.policy.networking.k8s.io + - clusternetworkpolicies.policy.networking.k8s.io - apiGroups: - "" resources: @@ -265,6 +305,7 @@ rules: - watch - apiGroups: - crd.projectcalico.org + - projectcalico.org resources: - felixconfigurations - ippools @@ -276,6 +317,7 @@ rules: - watch - apiGroups: - crd.projectcalico.org + - projectcalico.org resources: - kubecontrollersconfigurations - bgpconfigurations @@ -349,10 +391,19 @@ rules: resources: - mutatingwebhookconfigurations verbs: + - create - delete - get - list - watch + - apiGroups: + - admissionregistration.k8s.io + resources: + - mutatingwebhookconfigurations + resourceNames: + - envoy-gateway-topology-injector.tigera-gateway + verbs: + - update # Needed for operator lock - apiGroups: - coordination.k8s.io @@ -384,6 +435,18 @@ rules: verbs: - list - watch + # The operator needs permissions to create and update validating webhook configuration. + - apiGroups: + - admissionregistration.k8s.io + resources: + - validatingwebhookconfigurations + verbs: + - create + - update + - delete + - get + - list + - watch # Add the appropriate pod security policy permissions - apiGroups: - policy @@ -458,6 +521,8 @@ rules: - tcproutes.gateway.networking.k8s.io - tlsroutes.gateway.networking.k8s.io - udproutes.gateway.networking.k8s.io + - xbackendtrafficpolicies.gateway.networking.x-k8s.io + - xlistenersets.gateway.networking.x-k8s.io - backends.gateway.envoyproxy.io - backendtrafficpolicies.gateway.envoyproxy.io - clienttrafficpolicies.gateway.envoyproxy.io @@ -507,6 +572,16 @@ rules: - update resourceNames: - tigera-gateway-api-gateway-helm-certgen + # For monitoring KubeVirt live migration. The operator does not do this itself, but needs to + # be able to assign this RBAC to Typha and calico-node. + - apiGroups: + - kubevirt.io + resources: + - virtualmachineinstancemigrations + verbs: + - get + - list + - watch --- # Source: tigera-operator/templates/tigera-operator/02-rolebinding-tigera-operator.yaml kind: ClusterRoleBinding diff --git a/manifests/v1_crd_projectcalico_org.yaml b/manifests/v1_crd_projectcalico_org.yaml new file mode 100644 index 00000000000..6ae308c1640 --- /dev/null +++ b/manifests/v1_crd_projectcalico_org.yaml @@ -0,0 +1,39217 @@ +# crd.projectcalico.org/v1 and operator.tigera.io/v1 APIs +--- +# Source: crd.projectcalico.org.v1/templates/operator.tigera.io_apiservers.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: apiservers.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: APIServer + listKind: APIServerList + plural: apiservers + singular: apiserver + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: |- + APIServer installs the Tigera API server and related resources. At most one instance + of this resource is supported. It must be named "default" or "tigera-secure". + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Specification of the desired state for the Tigera API server. + properties: + apiServerDeployment: + description: |- + APIServerDeployment configures the calico-apiserver Deployment. If + used in conjunction with ControlPlaneNodeSelector or ControlPlaneTolerations, then these overrides + take precedence. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the specification of the API server Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the API server Deployment. + If omitted, the API server Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the API server Deployment + pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the API server Deployment's PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the API server pods. + If specified, this overrides any affinity that may be set on the API server Deployment. + If omitted, the API server Deployment will use its default value for affinity. + WARNING: Please note that this field will override the default API server Deployment affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of API server containers. + If specified, this overrides the specified API server Deployment containers. + If omitted, the API server Deployment will use its default values for its containers. + items: + description: + APIServerDeploymentContainer is an + API server Deployment container. + properties: + name: + description: |- + Name is an enum which identifies the API server Deployment container by name. + Supported values are: calico-apiserver, tigera-queryserver, calico-l7-admission-controller + enum: + - calico-apiserver + - tigera-queryserver + - calico-l7-admission-controller + type: string + ports: + description: |- + Ports allows customization of container's ports. + If specified, this overrides the named APIServer Deployment container's ports. + If omitted, the API server Deployment will use its default value for this container's port. + items: + properties: + containerPort: + description: |- + Number of port to expose on the pod's IP address. + This must be a valid port number, 0 < x < 65536. + format: int32 + type: integer + name: + description: |- + Name is an enum which identifies the API server Deployment Container port by name. + Supported values are: apiserver, queryserver, l7admctrl + enum: + - apiserver + - queryserver + - l7admctrl + type: string + required: + - containerPort + - name + type: object + type: array + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named API server Deployment container's resources. + If omitted, the API server Deployment will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + initContainers: + description: |- + InitContainers is a list of API server init containers. + If specified, this overrides the specified API server Deployment init containers. + If omitted, the API server Deployment will use its default values for its init containers. + items: + description: + APIServerDeploymentInitContainer is + an API server Deployment init container. + properties: + name: + description: |- + Name is an enum which identifies the API server Deployment init container by name. + Supported values are: calico-apiserver-certs-key-cert-provisioner + enum: + - calico-apiserver-certs-key-cert-provisioner + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named API server Deployment init container's resources. + If omitted, the API server Deployment will use its default value for this init container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the API server pod's scheduling constraints. + If specified, each of the key/value pairs are added to the API server Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If used in conjunction with ControlPlaneNodeSelector, that nodeSelector is set on the API server Deployment + and each of this field's key/value pairs are added to the API server Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the API server Deployment will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default API server Deployment nodeSelector. + type: object + priorityClassName: + description: + PriorityClassName allows to specify a + PriorityClass resource to be used. + type: string + tolerations: + description: |- + Tolerations is the API server pod's tolerations. + If specified, this overrides any tolerations that may be set on the API server Deployment. + If omitted, the API server Deployment will use its default value for tolerations. + WARNING: Please note that this field will override the default API server Deployment tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + type: object + type: object + type: object + calicoWebhooksDeployment: + description: + CalicoWebhooksDeployment configures the calico-webhooks + Deployment. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the calico-webhooks + Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-webhooks Deployment. + If omitted, the calico-webhooks Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-webhooks Deployment + pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the calico-webhooks Deployment's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-webhooks pods. + If specified, this overrides any affinity that may be set on the calico-webhooks Deployment. + If omitted, the calico-webhooks Deployment will use its default value for affinity. + WARNING: Please note that this field will override the default calico-webhooks Deployment affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-webhooks containers. + If specified, this overrides the specified calico-webhooks Deployment containers. + If omitted, the calico-webhooks Deployment will use its default values for its containers. + items: + description: + CalicoWebhooksDeploymentContainer is + a calico-webhooks Deployment container. + properties: + name: + description: |- + Name is an enum which identifies the calico-webhooks Deployment container by name. + Supported values are: calico-webhooks + enum: + - calico-webhooks + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-webhooks Deployment container's resources. + If omitted, the calico-webhooks Deployment will use its default value for this container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-webhooks pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-webhooks Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If used in conjunction with ControlPlaneNodeSelector, that nodeSelector is set on the calico-webhooks Deployment + and each of this field's key/value pairs are added to the calico-webhooks Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-webhooks Deployment will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-webhooks Deployment nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-webhooks pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-webhooks Deployment. + If omitted, the calico-webhooks Deployment will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-webhooks Deployment tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + logging: + properties: + apiServer: + properties: + logSeverity: + default: Info + description: LogSeverity defines log level for APIServer container. + enum: + - Fatal + - Error + - Warn + - Info + - Debug + - Trace + type: string + type: object + queryServer: + properties: + logSeverity: + default: Info + description: + LogSeverity defines log level for QueryServer + container. + enum: + - Fatal + - Error + - Warn + - Info + - Debug + - Trace + type: string + type: object + type: object + type: object + status: + description: Most recently observed status for the Tigera API server. + properties: + conditions: + description: |- + Conditions represents the latest observed set of conditions for the component. A component may be one or more of + Ready, Progressing, Degraded or other customer types. + items: + description: + Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + state: + description: State provides user-readable status. + type: string + type: object + type: object + x-kubernetes-validations: + - message: resource name must be 'default' or 'tigera-secure' + rule: self.metadata.name == 'default' || self.metadata.name == 'tigera-secure' + served: true + storage: true + subresources: + status: {} +--- +# Source: crd.projectcalico.org.v1/templates/operator.tigera.io_gatewayapis.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: gatewayapis.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: GatewayAPI + listKind: GatewayAPIList + plural: gatewayapis + singular: gatewayapi + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: + GatewayAPISpec has fields that can be used to customize our + GatewayAPI support. + properties: + crdManagement: + description: |- + Configures how to manage and update Gateway API CRDs. The default behaviour - which is + used when this field is not set, or is set to "PreferExisting" - is that the Tigera + operator will create the Gateway API CRDs if they do not already exist, but will not + overwrite any existing Gateway API CRDs. This setting may be preferable if the customer + is using other implementations of the Gateway API concurrently with the Gateway API + support in Calico Enterprise. It is then the customer's responsibility to ensure that + CRDs are installed that meet the needs of all the Gateway API implementations in their + cluster. + Alternatively, if this field is set to "Reconcile", the Tigera operator will keep the + cluster's Gateway API CRDs aligned with those that it would install on a cluster that + does not yet have any version of those CRDs. + enum: + - Reconcile + - PreferExisting + type: string + envoyGatewayConfigRef: + description: |- + Reference to a custom EnvoyGateway YAML to use as the base EnvoyGateway configuration for + the gateway controller. When specified, must identify a ConfigMap resource with an + "envoy-gateway.yaml" key whose value is the desired EnvoyGateway YAML (i.e. following the + same pattern as the default `envoy-gateway-config` ConfigMap). + When not specified, the Tigera operator uses the `envoy-gateway-config` from the Envoy + Gateway helm chart as its base. + Starting from that base, the Tigera operator copies and modifies the EnvoyGateway + resource as follows: + 1. If not already specified, it sets the ControllerName to + "gateway.envoyproxy.io/gatewayclass-controller". + 2. It configures the `tigera/envoy-gateway` and `tigera/envoy-ratelimit` images that will + be used (according to the current Calico version, private registry and image set + settings) and any pull secrets that are needed to pull those images. + 3. It enables use of the Backend API. + The resulting EnvoyGateway is provisioned as the `envoy-gateway-config` ConfigMap (which + the gateway controller then uses as its config). + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + gatewayCertgenJob: + description: Allows customization of the gateway certgen job. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into the + job's top-level metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + GatewayCertgenJobSpec allows customization of the + gateway certgen job spec. + properties: + template: + description: + GatewayCertgenJobPodTemplate allows customization + of the gateway certgen job's pod template. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into the + job's pod template. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + GatewayCertgenJobPodSpec allows customization + of the gateway certgen job's pod spec. + properties: + affinity: + description: + If non-nil, Affinity sets the affinity + field of the job's pod template. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + items: + description: |- + GatewayCertgenJobContainer allows customization of the gateway certgen job's resource + requirements. + properties: + name: + enum: + - envoy-gateway-certgen + type: string + resources: + description: |- + If non-nil, Resources sets the ResourceRequirements of the job's "envoy-gateway-certgen" + container. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: + If non-nil, NodeSelector sets the node + selector for where job pods may be scheduled. + type: object + tolerations: + description: + If non-nil, Tolerations sets the tolerations + field of the job's pod template. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + gatewayClasses: + description: |- + Configures the GatewayClasses that will be available; please see GatewayClassSpec for + more detail. If GatewayClasses is nil, the Tigera operator defaults to provisioning a + single GatewayClass named "tigera-gateway-class", without any of the detailed + customizations that are allowed within GatewayClassSpec. + items: + properties: + envoyProxyRef: + description: |- + Reference to a custom EnvoyProxy resource to use as the base EnvoyProxy configuration for + this GatewayClass. When specified, must identify an EnvoyProxy resource. + When not specified, the Tigera operator uses an empty EnvoyProxy resource as its base. + Starting from that base, the Tigera operator copies and modifies the EnvoyProxy resource + as follows, in the order described: + 1. It configures the `tigera/envoy-proxy` image that will be used (according to the + current Calico version, private registry and image set settings) and any pull secrets + that are needed to pull that image. + 2. It applies customizations as specified by the following `GatewayKind`, + `GatewayDeployment`, `GatewayDaemonSet` and `GatewayService` fields. + The resulting EnvoyProxy is provisioned in the `tigera-gateway` namespace, together with + a GatewayClass that references it. + If a custom EnvoyProxy resource is specified and uses `EnvoyDaemonSet` instead of the + default `EnvoyDeployment`, deployment-related customizations will be applied within + `EnvoyDaemonSet` instead of within `EnvoyDeployment`. + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + gatewayDaemonSet: + description: |- + Allows customization of Gateways when deployed as Kubernetes DaemonSets, for Gateways in + this GatewayClass. + properties: + spec: + description: + GatewayDeploymentSpec allows customization + of the spec of gateway daemonsets. + properties: + template: + description: + GatewayDeploymentPodTemplate allows customization + of the pod template of gateway daemonsets. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into each + daemonset's pod template. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + GatewayDaemonSetPodSpec allows customization + of the pod spec of gateway daemonsets. + properties: + affinity: + description: + If non-nil, Affinity sets the affinity + field of the daemonset's pod template. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by + node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by + node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated + with matching the corresponding + nodeSelectorTerm, in the range + 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of + node selector terms. The terms + are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by + node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by + node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of + the matched WeightedPodAffinityTerm + fields are added per-node to find + the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity + scheduling rules (e.g. avoid putting this + pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of + the matched WeightedPodAffinityTerm + fields are added per-node to find + the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + items: + description: |- + GatewayDaemonSetContainer allows customization of the resource requirements of gateway + daemonsets. + properties: + name: + enum: + - envoy + type: string + resources: + description: |- + If non-nil, Resources sets the ResourceRequirements of the daemonset's "envoy" + container. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + If non-nil, NodeSelector sets the node selector for where daemonset pods may be + scheduled. + type: object + tolerations: + description: + If non-nil, Tolerations sets the + tolerations field of the daemonset's pod template. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + If non-nil, TopologySpreadConstraints sets the topology spread constraints of the + daemonset's pod template. TopologySpreadConstraints describes how a group of pods ought + to spread across topology domains. Scheduler will schedule pods in a way which abides by + the constraints. All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given + topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a + list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + type: object + type: object + type: object + gatewayDeployment: + description: |- + Allows customization of Gateways when deployed as Kubernetes Deployments, for Gateways in + this GatewayClass. + properties: + spec: + description: + GatewayDeploymentSpec allows customization + of the spec of gateway deployments. + properties: + replicas: + description: + If non-nil, Replicas sets the number of + replicas for the deployment. + format: int32 + type: integer + strategy: + description: + The deployment strategy to use to replace + existing pods with new ones. + properties: + rollingUpdate: + description: + Spec to control the desired behavior + of rolling update. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: object + template: + description: + GatewayDeploymentPodTemplate allows customization + of the pod template of gateway deployments. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into each + deployment's pod template. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + GatewayDeploymentPodSpec allows customization + of the pod spec of gateway deployments. + properties: + affinity: + description: + If non-nil, Affinity sets the affinity + field of the deployment's pod template. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by + node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by + node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated + with matching the corresponding + nodeSelectorTerm, in the range + 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of + node selector terms. The terms + are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by + node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by + node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of + the matched WeightedPodAffinityTerm + fields are added per-node to find + the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity + scheduling rules (e.g. avoid putting this + pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of + the matched WeightedPodAffinityTerm + fields are added per-node to find + the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + items: + description: |- + GatewayDeploymentContainer allows customization of the resource requirements of gateway + deployments. + properties: + name: + enum: + - envoy + type: string + resources: + description: |- + If non-nil, Resources sets the ResourceRequirements of the deployment's "envoy" + container. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + If non-nil, NodeSelector sets the node selector for where deployment pods may be + scheduled. + type: object + tolerations: + description: + If non-nil, Tolerations sets the + tolerations field of the deployment's pod + template. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + If non-nil, TopologySpreadConstraints sets the topology spread constraints of the + deployment's pod template. TopologySpreadConstraints describes how a group of pods ought + to spread across topology domains. Scheduler will schedule pods in a way which abides by + the constraints. All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given + topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a + list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + type: object + type: object + type: object + gatewayKind: + description: |- + Specifies whether Gateways in this class are deployed as Deployments (default) or as + DaemonSets. It is an error for GatewayKind to specify a choice that is incompatible with + the custom EnvoyProxy, when EnvoyProxyRef is also specified. + enum: + - Deployment + - DaemonSet + type: string + gatewayService: + description: + Allows customization of gateway services, for Gateways + in this GatewayClass. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into the + each Gateway Service's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: |- + GatewayServiceSpec allows customization of the services that front gateway deployments. + The LoadBalancer fields allow customization of the corresponding fields in the Kubernetes + ServiceSpec. These can be used for some cloud-independent control of the external load balancer + that is provisioned for each Gateway. For finer-grained cloud-specific control please use + the Metadata.Annotations field in GatewayService. + properties: + allocateLoadBalancerNodePorts: + type: boolean + loadBalancerClass: + type: string + loadBalancerIP: + type: string + loadBalancerSourceRanges: + items: + type: string + type: array + type: object + type: object + name: + description: The name of this GatewayClass. + type: string + required: + - name + type: object + type: array + gatewayControllerDeployment: + description: Allows customization of the gateway controller deployment. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into the + deployment's top-level metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + GatewayControllerDeploymentSpec allows customization + of the gateway controller deployment spec. + properties: + minReadySeconds: + description: + If non-nil, MinReadySeconds sets the minReadySeconds + field for the deployment. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + replicas: + description: + If non-nil, Replicas sets the number of replicas + for the deployment. + format: int32 + type: integer + template: + description: |- + GatewayControllerDeploymentPodTemplate allows customization of the gateway controller deployment + pod template. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into the + deployment's pod template. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: |- + GatewayControllerDeploymentPodSpec allows customization of the gateway controller deployment pod + spec. + properties: + affinity: + description: + If non-nil, Affinity sets the affinity + field of the deployment's pod template. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + items: + description: |- + GatewayControllerDeploymentContainer allows customization of the gateway controller's resource + requirements. + properties: + name: + enum: + - envoy-gateway + type: string + resources: + description: |- + If non-nil, Resources sets the ResourceRequirements of the controller's "envoy-gateway" + container. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + If non-nil, NodeSelector sets the node selector for where deployment pods may be + scheduled. + type: object + tolerations: + description: + If non-nil, Tolerations sets the tolerations + field of the deployment's pod template. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + If non-nil, TopologySpreadConstraints sets the topology spread constraints of the + deployment's pod template. TopologySpreadConstraints describes how a group of pods ought + to spread across topology domains. Scheduler will schedule pods in a way which abides by + the constraints. All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + type: object + type: object + type: object + type: object + type: object + x-kubernetes-validations: + - message: resource name must be 'default' + rule: self.metadata.name == 'default' || self.metadata.name == 'tigera-secure' + served: true + storage: true +--- +# Source: crd.projectcalico.org.v1/templates/operator.tigera.io_goldmanes.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: goldmanes.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: Goldmane + listKind: GoldmaneList + plural: goldmanes + singular: goldmane + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + goldmaneDeployment: + description: + GoldmaneDeployment is the configuration for the goldmane + Deployment. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the specification of the goldmane Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the goldmane Deployment. + If omitted, the goldmane Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + strategy: + description: + The deployment strategy to use to replace existing + pods with new ones. + properties: + rollingUpdate: + description: |- + Rolling update config params. Present only if DeploymentStrategyType = + RollingUpdate. + to be. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: object + template: + description: + Template describes the goldmane Deployment pod + that will be created. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the goldmane Deployment's PodSpec. + properties: + affinity: + description: + Affinity is a group of affinity scheduling + rules for the goldmane pods. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of goldmane containers. + If specified, this overrides the specified EGW Deployment containers. + If omitted, the goldmane Deployment will use its default values for its containers. + items: + properties: + name: + enum: + - goldmane + type: string + resources: + description: + ResourceRequirements describes + the compute resource requirements. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: + NodeSelector gives more control over + the nodes where the goldmane pods will run on. + type: object + priorityClassName: + description: + PriorityClassName allows to specify a + PriorityClass resource to be used. + type: string + terminationGracePeriodSeconds: + description: + TerminationGracePeriodSeconds defines + the termination grace period of the goldmane pods + in seconds. + format: int64 + minimum: 0 + type: integer + tolerations: + description: |- + Tolerations is the goldmane pod's tolerations. + If specified, this overrides any tolerations that may be set on the goldmane Deployment. + If omitted, the goldmane Deployment will use its default value for tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + type: object + type: object + type: object + type: object + status: + description: GoldmaneStatus defines the observed state of Goldmane + properties: + conditions: + description: |- + Conditions represents the latest observed set of conditions for the component. A component may be one or more of + Ready, Progressing, Degraded or other customer types. + items: + description: + Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + x-kubernetes-validations: + - message: resource name must be 'default' + rule: self.metadata.name == 'default' + served: true + storage: true + subresources: + status: {} +--- +# Source: crd.projectcalico.org.v1/templates/operator.tigera.io_imagesets.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: imagesets.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: ImageSet + listKind: ImageSetList + plural: imagesets + singular: imageset + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: |- + ImageSet is used to specify image digests for the images that the operator deploys. + The name of the ImageSet is expected to be in the format `-`. + The `variant` used is `enterprise` if the InstallationSpec Variant is + `TigeraSecureEnterprise` otherwise it is `calico`. + The `release` must match the version of the variant that the operator is built to deploy, + this version can be obtained by passing the `--version` flag to the operator binary. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: ImageSetSpec defines the desired state of ImageSet. + properties: + images: + description: |- + Images is the list of images to use digests. All images that the operator will deploy + must be specified. + items: + properties: + digest: + description: |- + Digest is the image identifier that will be used for the Image. + The field should not include a leading `@` and must be prefixed with `sha256:`. + type: string + image: + description: |- + Image is an image that the operator deploys and instead of using the built in tag + the operator will use the Digest for the image identifier. + The value should be the *original* image name without registry or tag or digest. + For the image `docker.io/calico/node:v3.17.1` it should be represented as `calico/node` + The "Installation" spec allows defining custom image registries, paths or prefixes. + Even for custom images such as example.com/custompath/customprefix-calico-node:v3.17.1, + this value should still be `calico/node`. + type: string + required: + - digest + - image + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: crd.projectcalico.org.v1/templates/operator.tigera.io_installations.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: installations.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: Installation + listKind: InstallationList + plural: installations + singular: installation + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: |- + Installation configures an installation of Calico or Calico Enterprise. At most one instance + of this resource is supported. It must be named "default". The Installation API installs core networking + and network policy components, and provides general install-time configuration. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: + Specification of the desired state for the Calico or Calico + Enterprise installation. + properties: + azure: + description: Azure is used to configure azure provider specific options. + properties: + policyMode: + default: Default + description: |- + PolicyMode determines whether the "control-plane" label is applied to namespaces. It offers two options: Default and Manual. + The Default option adds the "control-plane" label to the required namespaces. + The Manual option does not apply the "control-plane" label to any namespace. + Default: Default + enum: + - Default + - Manual + type: string + type: object + calicoKubeControllersDeployment: + description: |- + CalicoKubeControllersDeployment configures the calico-kube-controllers Deployment. If used in + conjunction with the deprecated ComponentResources, then these overrides take precedence. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the calico-kube-controllers + Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-kube-controllers Deployment. + If omitted, the calico-kube-controllers Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-kube-controllers + Deployment pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the calico-kube-controllers Deployment's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-kube-controllers pods. + If specified, this overrides any affinity that may be set on the calico-kube-controllers Deployment. + If omitted, the calico-kube-controllers Deployment will use its default value for affinity. + WARNING: Please note that this field will override the default calico-kube-controllers Deployment affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-kube-controllers containers. + If specified, this overrides the specified calico-kube-controllers Deployment containers. + If omitted, the calico-kube-controllers Deployment will use its default values for its containers. + items: + description: + CalicoKubeControllersDeploymentContainer + is a calico-kube-controllers Deployment container. + properties: + name: + description: |- + Name is an enum which identifies the calico-kube-controllers Deployment container by name. + Supported values are: calico-kube-controllers, es-calico-kube-controllers + enum: + - calico-kube-controllers + - es-calico-kube-controllers + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-kube-controllers Deployment container's resources. + If omitted, the calico-kube-controllers Deployment will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-kube-controllers pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-kube-controllers Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If used in conjunction with ControlPlaneNodeSelector, that nodeSelector is set on the calico-kube-controllers Deployment + and each of this field's key/value pairs are added to the calico-kube-controllers Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-kube-controllers Deployment will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-kube-controllers Deployment nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-kube-controllers pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-kube-controllers Deployment. + If omitted, the calico-kube-controllers Deployment will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-kube-controllers Deployment tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + calicoNetwork: + description: + CalicoNetwork specifies networking configuration options + for Calico. + properties: + bgp: + description: + BGP configures whether or not to enable Calico's + BGP capabilities. + enum: + - Enabled + - Disabled + type: string + bpfNetworkBootstrap: + description: |- + BPFNetworkBootstrap manages the initial networking setup required to configure the BPF dataplane. + When enabled, the operator tries to bootstraps access to the Kubernetes API Server + by using the Kubernetes service and its associated endpoints. + This field should be enabled only if linuxDataplane is set to "BPF". + If another dataplane is selected, this field must be omitted or explicitly set to Disabled. + When disabled and linuxDataplane is BPF, you must manually provide the Kubernetes API Server + information via the "kubernetes-service-endpoint" ConfigMap. It is invalid to use both the ConfigMap + and have this field set to true at the same time. + Default: Disabled + enum: + - Disabled + - Enabled + type: string + containerIPForwarding: + description: |- + ContainerIPForwarding configures whether ip forwarding will be enabled for containers in the CNI configuration. + Default: Disabled + enum: + - Enabled + - Disabled + type: string + hostPorts: + description: |- + HostPorts configures whether or not Calico will support Kubernetes HostPorts. Valid only when using the Calico CNI plugin. + Default: Enabled + enum: + - Enabled + - Disabled + type: string + ipPools: + description: |- + IPPools contains a list of IP pools to manage. If nil, a single IPv4 IP pool + will be created by the operator. If an empty list is provided, the operator will not create any IP pools and will instead + wait for IP pools to be created out-of-band. + IP pools in this list will be reconciled by the operator and should not be modified out-of-band. + items: + properties: + allowedUses: + description: |- + AllowedUse controls what the IP pool will be used for. If not specified or empty, defaults to + ["Tunnel", "Workload"] for back-compatibility + items: + type: string + type: array + assignmentMode: + description: + AssignmentMode determines if IP addresses from + this pool should be assigned automatically or on request + only + type: string + blockSize: + description: |- + BlockSize specifies the CIDR prefex length to use when allocating per-node IP blocks from + the main IP pool CIDR. + Default: 26 (IPv4), 122 (IPv6) + format: int32 + type: integer + cidr: + description: + CIDR contains the address range for the IP + Pool in classless inter-domain routing format. + type: string + disableBGPExport: + default: false + description: |- + DisableBGPExport specifies whether routes from this IP pool's CIDR are exported over BGP. + Default: false + type: boolean + disableNewAllocations: + description: |- + DisableNewAllocations specifies whether or not new IP allocations are allowed from this pool. + This is useful when you want to prevent new pods from receiving IP addresses from this pool, without + impacting any existing pods that have already been assigned addresses from this pool. + type: boolean + encapsulation: + description: |- + Encapsulation specifies the encapsulation type that will be used with + the IP Pool. + Default: IPIP + enum: + - IPIPCrossSubnet + - IPIP + - VXLAN + - VXLANCrossSubnet + - None + type: string + name: + description: + Name is the name of the IP pool. If omitted, + this will be generated. + type: string + natOutgoing: + description: |- + NATOutgoing specifies if NAT will be enabled or disabled for outgoing traffic. + Default: Enabled + enum: + - Enabled + - Disabled + type: string + nodeSelector: + description: |- + NodeSelector specifies the node selector that will be set for the IP Pool. + Default: 'all()' + type: string + required: + - cidr + type: object + maxItems: 25 + type: array + kubeProxyManagement: + description: |- + KubeProxyManagement controls whether the operator manages the kube-proxy DaemonSet. + When enabled, the operator will manage the DaemonSet by patching it: + it disables kube-proxy if the dataplane is BPF, or enables it otherwise. + Default: Disabled + enum: + - Disabled + - Enabled + type: string + linuxDataplane: + description: |- + LinuxDataplane is used to select the dataplane used for Linux nodes. In particular, it + causes the operator to add required mounts and environment variables for the particular dataplane. + If not specified, iptables mode is used. + Default: Iptables + enum: + - Iptables + - BPF + - VPP + - Nftables + type: string + linuxPolicySetupTimeoutSeconds: + description: |- + LinuxPolicySetupTimeoutSeconds delays new pods from running containers + until their policy has been programmed in the dataplane. + The specified delay defines the maximum amount of time + that the Calico CNI plugin will wait for policy to be programmed. + Only applies to pods created on Linux nodes. + * A value of 0 disables pod startup delays. + Default: 0 + format: int32 + type: integer + mtu: + description: |- + MTU specifies the maximum transmission unit to use on the pod network. + If not specified, Calico will perform MTU auto-detection based on the cluster network. + format: int32 + type: integer + multiInterfaceMode: + description: |- + MultiInterfaceMode configures what will configure multiple interface per pod. Only valid for Calico Enterprise installations + using the Calico CNI plugin. + Default: None + enum: + - None + - Multus + type: string + nodeAddressAutodetectionV4: + description: |- + NodeAddressAutodetectionV4 specifies an approach to automatically detect node IPv4 addresses. If not specified, + will use default auto-detection settings to acquire an IPv4 address for each node. + properties: + canReach: + description: |- + CanReach enables IP auto-detection based on which source address on the node is used to reach the + specified IP or domain. + type: string + cidrs: + description: |- + CIDRS enables IP auto-detection based on which addresses on the nodes are within + one of the provided CIDRs. + items: + type: string + type: array + firstFound: + description: |- + FirstFound uses default interface matching parameters to select an interface, performing best-effort + filtering based on well-known interface names. + type: boolean + interface: + description: + Interface enables IP auto-detection based on + interfaces that match the given regex. + type: string + kubernetes: + description: + Kubernetes configures Calico to detect node addresses + based on the Kubernetes API. + enum: + - NodeInternalIP + type: string + skipInterface: + description: |- + SkipInterface enables IP auto-detection based on interfaces that do not match + the given regex. + type: string + type: object + nodeAddressAutodetectionV6: + description: |- + NodeAddressAutodetectionV6 specifies an approach to automatically detect node IPv6 addresses. If not specified, + IPv6 addresses will not be auto-detected. + properties: + canReach: + description: |- + CanReach enables IP auto-detection based on which source address on the node is used to reach the + specified IP or domain. + type: string + cidrs: + description: |- + CIDRS enables IP auto-detection based on which addresses on the nodes are within + one of the provided CIDRs. + items: + type: string + type: array + firstFound: + description: |- + FirstFound uses default interface matching parameters to select an interface, performing best-effort + filtering based on well-known interface names. + type: boolean + interface: + description: + Interface enables IP auto-detection based on + interfaces that match the given regex. + type: string + kubernetes: + description: + Kubernetes configures Calico to detect node addresses + based on the Kubernetes API. + enum: + - NodeInternalIP + type: string + skipInterface: + description: |- + SkipInterface enables IP auto-detection based on interfaces that do not match + the given regex. + type: string + type: object + sysctl: + description: Sysctl configures sysctl parameters for tuning plugin + items: + properties: + key: + enum: + - net.ipv4.tcp_keepalive_intvl + - net.ipv4.tcp_keepalive_probes + - net.ipv4.tcp_keepalive_time + type: string + value: + type: string + required: + - key + - value + type: object + type: array + windowsDataplane: + description: |- + WindowsDataplane is used to select the dataplane used for Windows nodes. In particular, it + causes the operator to add required mounts and environment variables for the particular dataplane. + If not specified, it is disabled and the operator will not render the Calico Windows nodes daemonset. + Default: Disabled + enum: + - HNS + - Disabled + type: string + type: object + calicoNodeDaemonSet: + description: |- + CalicoNodeDaemonSet configures the calico-node DaemonSet. If used in + conjunction with the deprecated ComponentResources, then these overrides take precedence. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the DaemonSet. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the specification of the calico-node DaemonSet. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-node DaemonSet. + If omitted, the calico-node DaemonSet will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-node DaemonSet + pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the calico-node DaemonSet's PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-node pods. + If specified, this overrides any affinity that may be set on the calico-node DaemonSet. + If omitted, the calico-node DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default calico-node DaemonSet affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-node containers. + If specified, this overrides the specified calico-node DaemonSet containers. + If omitted, the calico-node DaemonSet will use its default values for its containers. + items: + description: + CalicoNodeDaemonSetContainer is a calico-node + DaemonSet container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node DaemonSet container by name. + Supported values are: calico-node + enum: + - calico-node + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-node DaemonSet container's resources. + If omitted, the calico-node DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + dnsConfig: + description: + DNSConfig allows customization of the + DNS configuration for the calico-node pods. + properties: + nameservers: + description: |- + A list of DNS name server IP addresses. + This will be appended to the base nameservers generated from DNSPolicy. + Duplicated nameservers will be removed. + items: + type: string + type: array + x-kubernetes-list-type: atomic + options: + description: |- + A list of DNS resolver options. + This will be merged with the base options generated from DNSPolicy. + Duplicated entries will be removed. Resolution options given in Options + will override those that appear in the base DNSPolicy. + items: + description: + PodDNSConfigOption defines DNS + resolver options of a pod. + properties: + name: + description: |- + Name is this DNS resolver option's name. + Required. + type: string + value: + description: + Value is this DNS resolver + option's value. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + searches: + description: |- + A list of DNS search domains for host-name lookup. + This will be appended to the base search paths generated from DNSPolicy. + Duplicated search paths will be removed. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + dnsPolicy: + description: + DNSPolicy is the DNS policy for the calico-node + pods. + enum: + - "" + - Default + - ClusterFirst + - ClusterFirstWithHostNet + - None + type: string + initContainers: + description: |- + InitContainers is a list of calico-node init containers. + If specified, this overrides the specified calico-node DaemonSet init containers. + If omitted, the calico-node DaemonSet will use its default values for its init containers. + items: + description: + CalicoNodeDaemonSetInitContainer is + a calico-node DaemonSet init container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node DaemonSet init container by name. + Supported values are: install-cni, hostpath-init, flexvol-driver, ebpf-bootstrap, node-certs-key-cert-provisioner, calico-node-prometheus-server-tls-key-cert-provisioner, mount-bpffs (deprecated, replaced by ebpf-bootstrap) + enum: + - install-cni + - hostpath-init + - flexvol-driver + - ebpf-bootstrap + - node-certs-key-cert-provisioner + - calico-node-prometheus-server-tls-key-cert-provisioner + - mount-bpffs + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-node DaemonSet init container's resources. + If omitted, the calico-node DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-node pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-node DaemonSet nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-node DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-node DaemonSet nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-node pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-node DaemonSet. + If omitted, the calico-node DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-node DaemonSet tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + calicoNodeWindowsDaemonSet: + description: + CalicoNodeWindowsDaemonSet configures the calico-node-windows + DaemonSet. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the DaemonSet. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the calico-node-windows + DaemonSet. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-node-windows DaemonSet. + If omitted, the calico-node-windows DaemonSet will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-node-windows DaemonSet + pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the calico-node-windows DaemonSet's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-node-windows pods. + If specified, this overrides any affinity that may be set on the calico-node-windows DaemonSet. + If omitted, the calico-node-windows DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default calico-node-windows DaemonSet affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-node-windows containers. + If specified, this overrides the specified calico-node-windows DaemonSet containers. + If omitted, the calico-node-windows DaemonSet will use its default values for its containers. + items: + description: + CalicoNodeWindowsDaemonSetContainer + is a calico-node-windows DaemonSet container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node-windows DaemonSet container by name. + Supported values are: node, felix, confd + calico-node-windows is allowed because it was previously allowed. + enum: + - calico-node-windows + - node + - felix + - confd + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named DaemonSet container's resources. + If omitted, the DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + initContainers: + description: |- + InitContainers is a list of calico-node-windows init containers. + If specified, this overrides the specified calico-node-windows DaemonSet init containers. + If omitted, the calico-node-windows DaemonSet will use its default values for its init containers. + items: + description: + CalicoNodeWindowsDaemonSetInitContainer + is a calico-node-windows DaemonSet init container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node-windows DaemonSet init container by name. + Supported values are: install-cni;hostpath-init, flexvol-driver, node-certs-key-cert-provisioner, calico-node-windows-prometheus-server-tls-key-cert-provisioner + enum: + - install-cni + - hostpath-init + - flexvol-driver + - node-certs-key-cert-provisioner + - calico-node-windows-prometheus-server-tls-key-cert-provisioner + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-node-windows DaemonSet init container's resources. + If omitted, the calico-node-windows DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-node-windows pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-node-windows DaemonSet nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-node-windows DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-node-windows DaemonSet nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-node-windows pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-node-windows DaemonSet. + If omitted, the calico-node-windows DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-node-windows DaemonSet tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + calicoWindowsUpgradeDaemonSet: + description: |- + Deprecated. The CalicoWindowsUpgradeDaemonSet is deprecated and will be removed from the API in the future. + CalicoWindowsUpgradeDaemonSet configures the calico-windows-upgrade DaemonSet. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the calico-windows-upgrade + DaemonSet. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-windows-upgrade DaemonSet. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-windows-upgrade + DaemonSet pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the calico-windows-upgrade DaemonSet's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-windows-upgrade pods. + If specified, this overrides any affinity that may be set on the calico-windows-upgrade DaemonSet. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default calico-windows-upgrade DaemonSet affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-windows-upgrade containers. + If specified, this overrides the specified calico-windows-upgrade DaemonSet containers. + If omitted, the calico-windows-upgrade DaemonSet will use its default values for its containers. + items: + description: + CalicoWindowsUpgradeDaemonSetContainer + is a calico-windows-upgrade DaemonSet container. + properties: + name: + description: + Name is an enum which identifies + the calico-windows-upgrade DaemonSet container + by name. + enum: + - calico-windows-upgrade + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-windows-upgrade DaemonSet container's resources. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for this container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-windows-upgrade pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-windows-upgrade DaemonSet nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-windows-upgrade DaemonSet nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-windows-upgrade pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-windows-upgrade DaemonSet. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-windows-upgrade DaemonSet tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + certificateManagement: + description: |- + CertificateManagement configures pods to submit a CertificateSigningRequest to the certificates.k8s.io/v1 API in order + to obtain TLS certificates. This feature requires that you bring your own CSR signing and approval process, otherwise + pods will be stuck during initialization. + properties: + caCert: + description: + Certificate of the authority that signs the CertificateSigningRequests + in PEM format. + format: byte + type: string + keyAlgorithm: + description: |- + Specify the algorithm used by pods to generate a key pair that is associated with the X.509 certificate request. + Default: RSAWithSize2048 + enum: + - "" + - RSAWithSize2048 + - RSAWithSize4096 + - RSAWithSize8192 + - ECDSAWithCurve256 + - ECDSAWithCurve384 + - ECDSAWithCurve521 + type: string + signatureAlgorithm: + description: |- + Specify the algorithm used for the signature of the X.509 certificate request. + Default: SHA256WithRSA + enum: + - "" + - SHA256WithRSA + - SHA384WithRSA + - SHA512WithRSA + - ECDSAWithSHA256 + - ECDSAWithSHA384 + - ECDSAWithSHA512 + type: string + signerName: + description: |- + When a CSR is issued to the certificates.k8s.io API, the signerName is added to the request in order to accommodate for clusters + with multiple signers. + Must be formatted as: `/`. + type: string + required: + - caCert + - signerName + type: object + cni: + description: CNI specifies the CNI that will be used by this installation. + properties: + binDir: + description: |- + BinDir is the path to the CNI binaries directory. + If you have changed the installation directory for CNI binaries in the container runtime configuration, + please ensure that this field points to the same directory as specified in the container runtime settings. + Default directory depends on the KubernetesProvider. + * For KubernetesProvider GKE, this field defaults to "/home/kubernetes/bin". + * For KubernetesProvider OpenShift, this field defaults to "/var/lib/cni/bin". + * Otherwise, this field defaults to "/opt/cni/bin". + type: string + confDir: + description: |- + ConfDir is the path to the CNI config directory. + If you have changed the installation directory for CNI configuration in the container runtime configuration, + please ensure that this field points to the same directory as specified in the container runtime settings. + Default directory depends on the KubernetesProvider. + * For KubernetesProvider GKE, this field defaults to "/etc/cni/net.d". + * For KubernetesProvider OpenShift, this field defaults to "/var/run/multus/cni/net.d". + * Otherwise, this field defaults to "/etc/cni/net.d". + type: string + ipam: + description: |- + IPAM specifies the pod IP address management that will be used in the Calico or + Calico Enterprise installation. + properties: + type: + description: |- + Specifies the IPAM plugin that will be used in the Calico or Calico Enterprise installation. + * For CNI Plugin Calico, this field defaults to Calico. + * For CNI Plugin GKE, this field defaults to HostLocal. + * For CNI Plugin AzureVNET, this field defaults to AzureVNET. + * For CNI Plugin AmazonVPC, this field defaults to AmazonVPC. + The IPAM plugin is installed and configured only if the CNI plugin is set to Calico, + for all other values of the CNI plugin the plugin binaries and CNI config is a dependency + that is expected to be installed separately. + Default: Calico + enum: + - Calico + - HostLocal + - AmazonVPC + - AzureVNET + type: string + required: + - type + type: object + type: + description: |- + Specifies the CNI plugin that will be used in the Calico or Calico Enterprise installation. + * For KubernetesProvider GKE, this field defaults to GKE. + * For KubernetesProvider AKS, this field defaults to AzureVNET. + * For KubernetesProvider EKS, this field defaults to AmazonVPC. + * If aws-node daemonset exists in kube-system when the Installation resource is created, this field defaults to AmazonVPC. + * For all other cases this field defaults to Calico. + For the value Calico, the CNI plugin binaries and CNI config will be installed as part of deployment, + for all other values the CNI plugin binaries and CNI config is a dependency that is expected + to be installed separately. + Default: Calico + enum: + - Calico + - GKE + - AmazonVPC + - AzureVNET + type: string + required: + - type + type: object + componentResources: + description: |- + Deprecated. Please use CalicoNodeDaemonSet, TyphaDeployment, and KubeControllersDeployment. + ComponentResources can be used to customize the resource requirements for each component. + Node, Typha, and KubeControllers are supported for installations. + items: + description: |- + Deprecated. Please use component resource config fields in Installation.Spec instead. + The ComponentResource struct associates a ResourceRequirements with a component by name + properties: + componentName: + description: ComponentName is an enum which identifies the component + enum: + - Node + - Typha + - KubeControllers + - NodeWindows + - FelixWindows + - ConfdWindows + type: string + resourceRequirements: + description: + ResourceRequirements allows customization of limits + and requests for compute resources such as cpu and memory. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - componentName + - resourceRequirements + type: object + type: array + controlPlaneNodeSelector: + additionalProperties: + type: string + description: |- + ControlPlaneNodeSelector is used to select control plane nodes on which to run Calico + components. This is globally applied to all resources created by the operator excluding daemonsets. + type: object + controlPlaneReplicas: + description: |- + ControlPlaneReplicas defines how many replicas of the control plane core components will be deployed. + This field applies to all control plane components that support High Availability. Defaults to 2. + format: int32 + type: integer + controlPlaneTolerations: + description: |- + ControlPlaneTolerations specify tolerations which are then globally applied to all resources + created by the operator. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + csiNodeDriverDaemonSet: + description: + CSINodeDriverDaemonSet configures the csi-node-driver + DaemonSet. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the DaemonSet. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the csi-node-driver + DaemonSet. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the csi-node-driver DaemonSet. + If omitted, the csi-node-driver DaemonSet will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the csi-node-driver DaemonSet + pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the csi-node-driver DaemonSet's PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the csi-node-driver pods. + If specified, this overrides any affinity that may be set on the csi-node-driver DaemonSet. + If omitted, the csi-node-driver DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default csi-node-driver DaemonSet affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of csi-node-driver containers. + If specified, this overrides the specified csi-node-driver DaemonSet containers. + If omitted, the csi-node-driver DaemonSet will use its default values for its containers. + items: + description: + CSINodeDriverDaemonSetContainer is + a csi-node-driver DaemonSet container. + properties: + name: + description: |- + Name is an enum which identifies the csi-node-driver DaemonSet container by name. + Supported values are: calico-csi, csi-node-driver-registrar. + enum: + - calico-csi + - csi-node-driver-registrar + - csi-node-driver + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named csi-node-driver DaemonSet container's resources. + If omitted, the csi-node-driver DaemonSet will use its default value for this container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the csi-node-driver pod's scheduling constraints. + If specified, each of the key/value pairs are added to the csi-node-driver DaemonSet nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the csi-node-driver DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default csi-node-driver DaemonSet nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the csi-node-driver pod's tolerations. + If specified, this overrides any tolerations that may be set on the csi-node-driver DaemonSet. + If omitted, the csi-node-driver DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default csi-node-driver DaemonSet tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + fipsMode: + description: |- + FIPSMode uses images and features only that are using FIPS 140-2 validated cryptographic modules and standards. + Only supported for Variant=Calico. + Default: Disabled + enum: + - Enabled + - Disabled + type: string + flexVolumePath: + description: |- + FlexVolumePath optionally specifies a custom path for FlexVolume. If not specified, FlexVolume will be + enabled by default. If set to 'None', FlexVolume will be disabled. The default is based on the + kubernetesProvider. + type: string + imagePath: + description: |- + ImagePath allows for the path part of an image to be specified. If specified + then the specified value will be used as the image path for each image. If not specified + or empty, the default for each image will be used. + A special case value, UseDefault, is supported to explicitly specify the default + image path will be used for each image. + Image format: + `/:` + This option allows configuring the `` portion of the above format. + type: string + imagePrefix: + description: |- + ImagePrefix allows for the prefix part of an image to be specified. If specified + then the given value will be used as a prefix on each image. If not specified + or empty, no prefix will be used. + A special case value, UseDefault, is supported to explicitly specify the default + image prefix will be used for each image. + Image format: + `/:` + This option allows configuring the `` portion of the above format. + type: string + imagePullSecrets: + description: |- + ImagePullSecrets is an array of references to container registry pull secrets to use. These are + applied to all images to be pulled. + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + kubeletVolumePluginPath: + description: |- + KubeletVolumePluginPath optionally specifies enablement of Calico CSI plugin. If not specified, + CSI will be enabled by default. If set to 'None', CSI will be disabled. + Default: /var/lib/kubelet + type: string + kubernetesProvider: + description: |- + KubernetesProvider specifies a particular provider of the Kubernetes platform and enables provider-specific configuration. + If the specified value is empty, the Operator will attempt to automatically determine the current provider. + If the specified value is not empty, the Operator will still attempt auto-detection, but + will additionally compare the auto-detected value to the specified value to confirm they match. + enum: + - "" + - EKS + - GKE + - AKS + - OpenShift + - DockerEnterprise + - RKE2 + - TKG + - Kind + type: string + logging: + description: Logging Configuration for Components + properties: + cni: + description: Customized logging specification for calico-cni plugin + properties: + logFileMaxAgeDays: + description: "Default: 30 (days)" + format: int32 + type: integer + logFileMaxCount: + description: "Default: 10" + format: int32 + type: integer + logFileMaxSize: + anyOf: + - type: integer + - type: string + description: "Default: 100Mi" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + logSeverity: + description: "Default: Info" + enum: + - Error + - Warning + - Info + - Debug + type: string + type: object + type: object + nodeMetricsPort: + description: |- + NodeMetricsPort specifies which port calico/node serves prometheus metrics on. By default, metrics are not enabled. + If specified, this overrides any FelixConfiguration resources which may exist. If omitted, then + prometheus metrics may still be configured through FelixConfiguration. + format: int32 + type: integer + nodeUpdateStrategy: + description: |- + NodeUpdateStrategy can be used to customize the desired update strategy, such as the MaxUnavailable + field. + properties: + rollingUpdate: + description: + Rolling update config params. Present only if type + = "RollingUpdate". + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of nodes with an existing available DaemonSet pod that + can have an updated DaemonSet pod during during an update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up to a minimum of 1. + Default value is 0. + Example: when this is set to 30%, at most 30% of the total number of nodes + that should be running the daemon pod (i.e. status.desiredNumberScheduled) + can have their a new pod created before the old pod is marked as deleted. + The update starts by launching new pods on 30% of nodes. Once an updated + pod is available (Ready for at least minReadySeconds) the old DaemonSet pod + on that node is marked deleted. If the old pod becomes unavailable for any + reason (Ready transitions to false, is evicted, or is drained) an updated + pod is immediately created on that node without considering surge limits. + Allowing surge implies the possibility that the resources consumed by the + daemonset on any given node can double if the readiness check fails, and + so resource intensive daemonsets should take into account that they may + cause evictions during disruption. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of DaemonSet pods that can be unavailable during the + update. Value can be an absolute number (ex: 5) or a percentage of total + number of DaemonSet pods at the start of the update (ex: 10%). Absolute + number is calculated from percentage by rounding up. + This cannot be 0 if MaxSurge is 0 + Default value is 1. + Example: when this is set to 30%, at most 30% of the total number of nodes + that should be running the daemon pod (i.e. status.desiredNumberScheduled) + can have their pods stopped for an update at any given time. The update + starts by stopping at most 30% of those DaemonSet pods and then brings + up new DaemonSet pods in their place. Once the new pods are available, + it then proceeds onto other DaemonSet pods, thus ensuring that at least + 70% of original number of DaemonSet pods are available at all times during + the update. + x-kubernetes-int-or-string: true + type: object + type: + description: + Type of daemon set update. Can be "RollingUpdate" + or "OnDelete". Default is RollingUpdate. + type: string + type: object + nonPrivileged: + description: |- + Deprecated. NonPrivileged is deprecated and will be removed from the API in a future release. + Enabling this field is not supported and will cause errors. + NonPrivileged configures Calico to be run in non-privileged containers as non-root users where possible. + type: string + proxy: + description: |- + Proxy is used to configure the HTTP(S) proxy settings that will be applied to Tigera containers that connect + to destinations outside the cluster. It is expected that NO_PROXY is configured such that destinations within + the cluster (including the API server) are exempt from proxying. + properties: + httpProxy: + description: |- + HTTPProxy defines the value of the HTTP_PROXY environment variable that will be set on Tigera containers that connect to + destinations outside the cluster. + type: string + httpsProxy: + description: |- + HTTPSProxy defines the value of the HTTPS_PROXY environment variable that will be set on Tigera containers that connect to + destinations outside the cluster. + type: string + noProxy: + description: |- + NoProxy defines the value of the NO_PROXY environment variable that will be set on Tigera containers that connect to + destinations outside the cluster. This value must be set such that destinations within the scope of the cluster, including + the Kubernetes API server, are exempt from being proxied. + type: string + type: object + registry: + description: |- + Registry is the default Docker registry used for component Docker images. + If specified then the given value must end with a slash character (`/`) and all images will be pulled from this registry. + If not specified then the default registries will be used. A special case value, UseDefault, is + supported to explicitly specify the default registries will be used. + Image format: + `/:` + This option allows configuring the `` portion of the above format. + type: string + serviceCIDRs: + description: + Kubernetes Service CIDRs. Specifying this is required + when using Calico for Windows. + items: + type: string + type: array + tlsCipherSuites: + description: + TLSCipherSuites defines the cipher suite list that the + TLS protocol should use during secure communication. + items: + properties: + name: + description: This should be a valid TLS cipher suite name. + enum: + - TLS_AES_256_GCM_SHA384 + - TLS_CHACHA20_POLY1305_SHA256 + - TLS_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 + - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + - TLS_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA + - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA + - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + type: string + type: object + type: array + typhaAffinity: + description: |- + Deprecated. Please use Installation.Spec.TyphaDeployment instead. + TyphaAffinity allows configuration of node affinity characteristics for Typha pods. + properties: + nodeAffinity: + description: + NodeAffinity describes node affinity scheduling rules + for typha. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated with the + corresponding weight. + properties: + matchExpressions: + description: + A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with matching the corresponding + nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + WARNING: Please note that if the affinity requirements specified by this field are not met at + scheduling time, the pod will NOT be scheduled onto the node. + There is no fallback to another affinity rules with this setting. + This may cause networking disruption or even catastrophic failure! + PreferredDuringSchedulingIgnoredDuringExecution should be used for affinity + unless there is a specific well understood reason to use RequiredDuringSchedulingIgnoredDuringExecution and + you can guarantee that the RequiredDuringSchedulingIgnoredDuringExecution will always have sufficient nodes to satisfy the requirement. + NOTE: RequiredDuringSchedulingIgnoredDuringExecution is set by default for AKS nodes, + to avoid scheduling Typhas on virtual-nodes. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + type: object + typhaDeployment: + description: |- + TyphaDeployment configures the typha Deployment. If used in conjunction with the deprecated + ComponentResources or TyphaAffinity, then these overrides take precedence. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the specification of the typha Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the typha Deployment. + If omitted, the typha Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + strategy: + description: + The deployment strategy to use to replace existing + pods with new ones. + properties: + rollingUpdate: + description: |- + Rolling update config params. Present only if DeploymentStrategyType = + RollingUpdate. + to be. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: object + template: + description: + Template describes the typha Deployment pod that + will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the typha Deployment's PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the typha pods. + If specified, this overrides any affinity that may be set on the typha Deployment. + If omitted, the typha Deployment will use its default value for affinity. + If used in conjunction with the deprecated TyphaAffinity, then this value takes precedence. + WARNING: Please note that this field will override the default calico-typha Deployment affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of typha containers. + If specified, this overrides the specified typha Deployment containers. + If omitted, the typha Deployment will use its default values for its containers. + items: + description: + TyphaDeploymentContainer is a typha + Deployment container. + properties: + name: + description: |- + Name is an enum which identifies the typha Deployment container by name. + Supported values are: calico-typha + enum: + - calico-typha + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named typha Deployment container's resources. + If omitted, the typha Deployment will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + initContainers: + description: |- + InitContainers is a list of typha init containers. + If specified, this overrides the specified typha Deployment init containers. + If omitted, the typha Deployment will use its default values for its init containers. + items: + description: + TyphaDeploymentInitContainer is a typha + Deployment init container. + properties: + name: + description: |- + Name is an enum which identifies the typha Deployment init container by name. + Supported values are: typha-certs-key-cert-provisioner + enum: + - typha-certs-key-cert-provisioner + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named typha Deployment init container's resources. + If omitted, the typha Deployment will use its default value for this init container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-typha pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-typha Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-typha Deployment will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-typha Deployment nodeSelector. + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + If this value is nil, the default grace period will be used instead. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + Defaults to 30 seconds. + format: int64 + type: integer + tolerations: + description: |- + Tolerations is the typha pod's tolerations. + If specified, this overrides any tolerations that may be set on the typha Deployment. + If omitted, the typha Deployment will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-typha Deployment tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + type: object + type: object + type: object + typhaMetricsPort: + description: + TyphaMetricsPort specifies which port calico/typha serves + prometheus metrics on. By default, metrics are not enabled. + format: int32 + type: integer + variant: + description: |- + Variant is the product to install - one of Calico or TigeraSecureEnterprise + Default: Calico + enum: + - Calico + - TigeraSecureEnterprise + type: string + windowsNodes: + description: Windows Configuration + properties: + cniBinDir: + description: |- + CNIBinDir is the path to the CNI binaries directory on Windows, it must match what is used as 'bin_dir' under + [plugins] + [plugins."io.containerd.grpc.v1.cri"] + [plugins."io.containerd.grpc.v1.cri".cni] + on the containerd 'config.toml' file on the Windows nodes. + type: string + cniConfigDir: + description: |- + CNIConfigDir is the path to the CNI configuration directory on Windows, it must match what is used as 'conf_dir' under + [plugins] + [plugins."io.containerd.grpc.v1.cri"] + [plugins."io.containerd.grpc.v1.cri".cni] + on the containerd 'config.toml' file on the Windows nodes. + type: string + cniLogDir: + description: + CNILogDir is the path to the Calico CNI logs directory + on Windows. + type: string + vxlanAdapter: + description: + VXLANAdapter is the Network Adapter used for VXLAN, + leave blank for primary NIC + type: string + vxlanMACPrefix: + description: + VXLANMACPrefix is the prefix used when generating + MAC addresses for virtual NICs + pattern: ^[0-9A-Fa-f]{2}-[0-9A-Fa-f]{2}$ + type: string + type: object + type: object + status: + description: + Most recently observed state for the Calico or Calico Enterprise + installation. + properties: + calicoVersion: + description: |- + CalicoVersion shows the current running version of calico. + CalicoVersion along with Variant is needed to know the exact + version deployed. + type: string + computed: + description: + Computed is the final installation including overlaid + resources. + properties: + azure: + description: + Azure is used to configure azure provider specific + options. + properties: + policyMode: + default: Default + description: |- + PolicyMode determines whether the "control-plane" label is applied to namespaces. It offers two options: Default and Manual. + The Default option adds the "control-plane" label to the required namespaces. + The Manual option does not apply the "control-plane" label to any namespace. + Default: Default + enum: + - Default + - Manual + type: string + type: object + calicoKubeControllersDeployment: + description: |- + CalicoKubeControllersDeployment configures the calico-kube-controllers Deployment. If used in + conjunction with the deprecated ComponentResources, then these overrides take precedence. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the calico-kube-controllers + Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-kube-controllers Deployment. + If omitted, the calico-kube-controllers Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-kube-controllers + Deployment pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the calico-kube-controllers Deployment's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-kube-controllers pods. + If specified, this overrides any affinity that may be set on the calico-kube-controllers Deployment. + If omitted, the calico-kube-controllers Deployment will use its default value for affinity. + WARNING: Please note that this field will override the default calico-kube-controllers Deployment affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-kube-controllers containers. + If specified, this overrides the specified calico-kube-controllers Deployment containers. + If omitted, the calico-kube-controllers Deployment will use its default values for its containers. + items: + description: + CalicoKubeControllersDeploymentContainer + is a calico-kube-controllers Deployment container. + properties: + name: + description: |- + Name is an enum which identifies the calico-kube-controllers Deployment container by name. + Supported values are: calico-kube-controllers, es-calico-kube-controllers + enum: + - calico-kube-controllers + - es-calico-kube-controllers + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-kube-controllers Deployment container's resources. + If omitted, the calico-kube-controllers Deployment will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-kube-controllers pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-kube-controllers Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If used in conjunction with ControlPlaneNodeSelector, that nodeSelector is set on the calico-kube-controllers Deployment + and each of this field's key/value pairs are added to the calico-kube-controllers Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-kube-controllers Deployment will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-kube-controllers Deployment nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-kube-controllers pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-kube-controllers Deployment. + If omitted, the calico-kube-controllers Deployment will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-kube-controllers Deployment tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + calicoNetwork: + description: + CalicoNetwork specifies networking configuration + options for Calico. + properties: + bgp: + description: + BGP configures whether or not to enable Calico's + BGP capabilities. + enum: + - Enabled + - Disabled + type: string + bpfNetworkBootstrap: + description: |- + BPFNetworkBootstrap manages the initial networking setup required to configure the BPF dataplane. + When enabled, the operator tries to bootstraps access to the Kubernetes API Server + by using the Kubernetes service and its associated endpoints. + This field should be enabled only if linuxDataplane is set to "BPF". + If another dataplane is selected, this field must be omitted or explicitly set to Disabled. + When disabled and linuxDataplane is BPF, you must manually provide the Kubernetes API Server + information via the "kubernetes-service-endpoint" ConfigMap. It is invalid to use both the ConfigMap + and have this field set to true at the same time. + Default: Disabled + enum: + - Disabled + - Enabled + type: string + containerIPForwarding: + description: |- + ContainerIPForwarding configures whether ip forwarding will be enabled for containers in the CNI configuration. + Default: Disabled + enum: + - Enabled + - Disabled + type: string + hostPorts: + description: |- + HostPorts configures whether or not Calico will support Kubernetes HostPorts. Valid only when using the Calico CNI plugin. + Default: Enabled + enum: + - Enabled + - Disabled + type: string + ipPools: + description: |- + IPPools contains a list of IP pools to manage. If nil, a single IPv4 IP pool + will be created by the operator. If an empty list is provided, the operator will not create any IP pools and will instead + wait for IP pools to be created out-of-band. + IP pools in this list will be reconciled by the operator and should not be modified out-of-band. + items: + properties: + allowedUses: + description: |- + AllowedUse controls what the IP pool will be used for. If not specified or empty, defaults to + ["Tunnel", "Workload"] for back-compatibility + items: + type: string + type: array + assignmentMode: + description: + AssignmentMode determines if IP addresses + from this pool should be assigned automatically or + on request only + type: string + blockSize: + description: |- + BlockSize specifies the CIDR prefex length to use when allocating per-node IP blocks from + the main IP pool CIDR. + Default: 26 (IPv4), 122 (IPv6) + format: int32 + type: integer + cidr: + description: + CIDR contains the address range for the + IP Pool in classless inter-domain routing format. + type: string + disableBGPExport: + default: false + description: |- + DisableBGPExport specifies whether routes from this IP pool's CIDR are exported over BGP. + Default: false + type: boolean + disableNewAllocations: + description: |- + DisableNewAllocations specifies whether or not new IP allocations are allowed from this pool. + This is useful when you want to prevent new pods from receiving IP addresses from this pool, without + impacting any existing pods that have already been assigned addresses from this pool. + type: boolean + encapsulation: + description: |- + Encapsulation specifies the encapsulation type that will be used with + the IP Pool. + Default: IPIP + enum: + - IPIPCrossSubnet + - IPIP + - VXLAN + - VXLANCrossSubnet + - None + type: string + name: + description: + Name is the name of the IP pool. If omitted, + this will be generated. + type: string + natOutgoing: + description: |- + NATOutgoing specifies if NAT will be enabled or disabled for outgoing traffic. + Default: Enabled + enum: + - Enabled + - Disabled + type: string + nodeSelector: + description: |- + NodeSelector specifies the node selector that will be set for the IP Pool. + Default: 'all()' + type: string + required: + - cidr + type: object + maxItems: 25 + type: array + kubeProxyManagement: + description: |- + KubeProxyManagement controls whether the operator manages the kube-proxy DaemonSet. + When enabled, the operator will manage the DaemonSet by patching it: + it disables kube-proxy if the dataplane is BPF, or enables it otherwise. + Default: Disabled + enum: + - Disabled + - Enabled + type: string + linuxDataplane: + description: |- + LinuxDataplane is used to select the dataplane used for Linux nodes. In particular, it + causes the operator to add required mounts and environment variables for the particular dataplane. + If not specified, iptables mode is used. + Default: Iptables + enum: + - Iptables + - BPF + - VPP + - Nftables + type: string + linuxPolicySetupTimeoutSeconds: + description: |- + LinuxPolicySetupTimeoutSeconds delays new pods from running containers + until their policy has been programmed in the dataplane. + The specified delay defines the maximum amount of time + that the Calico CNI plugin will wait for policy to be programmed. + Only applies to pods created on Linux nodes. + * A value of 0 disables pod startup delays. + Default: 0 + format: int32 + type: integer + mtu: + description: |- + MTU specifies the maximum transmission unit to use on the pod network. + If not specified, Calico will perform MTU auto-detection based on the cluster network. + format: int32 + type: integer + multiInterfaceMode: + description: |- + MultiInterfaceMode configures what will configure multiple interface per pod. Only valid for Calico Enterprise installations + using the Calico CNI plugin. + Default: None + enum: + - None + - Multus + type: string + nodeAddressAutodetectionV4: + description: |- + NodeAddressAutodetectionV4 specifies an approach to automatically detect node IPv4 addresses. If not specified, + will use default auto-detection settings to acquire an IPv4 address for each node. + properties: + canReach: + description: |- + CanReach enables IP auto-detection based on which source address on the node is used to reach the + specified IP or domain. + type: string + cidrs: + description: |- + CIDRS enables IP auto-detection based on which addresses on the nodes are within + one of the provided CIDRs. + items: + type: string + type: array + firstFound: + description: |- + FirstFound uses default interface matching parameters to select an interface, performing best-effort + filtering based on well-known interface names. + type: boolean + interface: + description: + Interface enables IP auto-detection based + on interfaces that match the given regex. + type: string + kubernetes: + description: + Kubernetes configures Calico to detect node + addresses based on the Kubernetes API. + enum: + - NodeInternalIP + type: string + skipInterface: + description: |- + SkipInterface enables IP auto-detection based on interfaces that do not match + the given regex. + type: string + type: object + nodeAddressAutodetectionV6: + description: |- + NodeAddressAutodetectionV6 specifies an approach to automatically detect node IPv6 addresses. If not specified, + IPv6 addresses will not be auto-detected. + properties: + canReach: + description: |- + CanReach enables IP auto-detection based on which source address on the node is used to reach the + specified IP or domain. + type: string + cidrs: + description: |- + CIDRS enables IP auto-detection based on which addresses on the nodes are within + one of the provided CIDRs. + items: + type: string + type: array + firstFound: + description: |- + FirstFound uses default interface matching parameters to select an interface, performing best-effort + filtering based on well-known interface names. + type: boolean + interface: + description: + Interface enables IP auto-detection based + on interfaces that match the given regex. + type: string + kubernetes: + description: + Kubernetes configures Calico to detect node + addresses based on the Kubernetes API. + enum: + - NodeInternalIP + type: string + skipInterface: + description: |- + SkipInterface enables IP auto-detection based on interfaces that do not match + the given regex. + type: string + type: object + sysctl: + description: + Sysctl configures sysctl parameters for tuning + plugin + items: + properties: + key: + enum: + - net.ipv4.tcp_keepalive_intvl + - net.ipv4.tcp_keepalive_probes + - net.ipv4.tcp_keepalive_time + type: string + value: + type: string + required: + - key + - value + type: object + type: array + windowsDataplane: + description: |- + WindowsDataplane is used to select the dataplane used for Windows nodes. In particular, it + causes the operator to add required mounts and environment variables for the particular dataplane. + If not specified, it is disabled and the operator will not render the Calico Windows nodes daemonset. + Default: Disabled + enum: + - HNS + - Disabled + type: string + type: object + calicoNodeDaemonSet: + description: |- + CalicoNodeDaemonSet configures the calico-node DaemonSet. If used in + conjunction with the deprecated ComponentResources, then these overrides take precedence. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the DaemonSet. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the calico-node + DaemonSet. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-node DaemonSet. + If omitted, the calico-node DaemonSet will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-node DaemonSet + pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the calico-node DaemonSet's PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-node pods. + If specified, this overrides any affinity that may be set on the calico-node DaemonSet. + If omitted, the calico-node DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default calico-node DaemonSet affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-node containers. + If specified, this overrides the specified calico-node DaemonSet containers. + If omitted, the calico-node DaemonSet will use its default values for its containers. + items: + description: + CalicoNodeDaemonSetContainer is + a calico-node DaemonSet container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node DaemonSet container by name. + Supported values are: calico-node + enum: + - calico-node + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-node DaemonSet container's resources. + If omitted, the calico-node DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + dnsConfig: + description: + DNSConfig allows customization of + the DNS configuration for the calico-node pods. + properties: + nameservers: + description: |- + A list of DNS name server IP addresses. + This will be appended to the base nameservers generated from DNSPolicy. + Duplicated nameservers will be removed. + items: + type: string + type: array + x-kubernetes-list-type: atomic + options: + description: |- + A list of DNS resolver options. + This will be merged with the base options generated from DNSPolicy. + Duplicated entries will be removed. Resolution options given in Options + will override those that appear in the base DNSPolicy. + items: + description: + PodDNSConfigOption defines + DNS resolver options of a pod. + properties: + name: + description: |- + Name is this DNS resolver option's name. + Required. + type: string + value: + description: + Value is this DNS resolver + option's value. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + searches: + description: |- + A list of DNS search domains for host-name lookup. + This will be appended to the base search paths generated from DNSPolicy. + Duplicated search paths will be removed. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + dnsPolicy: + description: + DNSPolicy is the DNS policy for the + calico-node pods. + enum: + - "" + - Default + - ClusterFirst + - ClusterFirstWithHostNet + - None + type: string + initContainers: + description: |- + InitContainers is a list of calico-node init containers. + If specified, this overrides the specified calico-node DaemonSet init containers. + If omitted, the calico-node DaemonSet will use its default values for its init containers. + items: + description: + CalicoNodeDaemonSetInitContainer + is a calico-node DaemonSet init container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node DaemonSet init container by name. + Supported values are: install-cni, hostpath-init, flexvol-driver, ebpf-bootstrap, node-certs-key-cert-provisioner, calico-node-prometheus-server-tls-key-cert-provisioner, mount-bpffs (deprecated, replaced by ebpf-bootstrap) + enum: + - install-cni + - hostpath-init + - flexvol-driver + - ebpf-bootstrap + - node-certs-key-cert-provisioner + - calico-node-prometheus-server-tls-key-cert-provisioner + - mount-bpffs + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-node DaemonSet init container's resources. + If omitted, the calico-node DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-node pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-node DaemonSet nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-node DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-node DaemonSet nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-node pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-node DaemonSet. + If omitted, the calico-node DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-node DaemonSet tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + calicoNodeWindowsDaemonSet: + description: + CalicoNodeWindowsDaemonSet configures the calico-node-windows + DaemonSet. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the DaemonSet. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the calico-node-windows + DaemonSet. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-node-windows DaemonSet. + If omitted, the calico-node-windows DaemonSet will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-node-windows + DaemonSet pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the calico-node-windows DaemonSet's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-node-windows pods. + If specified, this overrides any affinity that may be set on the calico-node-windows DaemonSet. + If omitted, the calico-node-windows DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default calico-node-windows DaemonSet affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-node-windows containers. + If specified, this overrides the specified calico-node-windows DaemonSet containers. + If omitted, the calico-node-windows DaemonSet will use its default values for its containers. + items: + description: + CalicoNodeWindowsDaemonSetContainer + is a calico-node-windows DaemonSet container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node-windows DaemonSet container by name. + Supported values are: node, felix, confd + calico-node-windows is allowed because it was previously allowed. + enum: + - calico-node-windows + - node + - felix + - confd + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named DaemonSet container's resources. + If omitted, the DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + initContainers: + description: |- + InitContainers is a list of calico-node-windows init containers. + If specified, this overrides the specified calico-node-windows DaemonSet init containers. + If omitted, the calico-node-windows DaemonSet will use its default values for its init containers. + items: + description: + CalicoNodeWindowsDaemonSetInitContainer + is a calico-node-windows DaemonSet init container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node-windows DaemonSet init container by name. + Supported values are: install-cni;hostpath-init, flexvol-driver, node-certs-key-cert-provisioner, calico-node-windows-prometheus-server-tls-key-cert-provisioner + enum: + - install-cni + - hostpath-init + - flexvol-driver + - node-certs-key-cert-provisioner + - calico-node-windows-prometheus-server-tls-key-cert-provisioner + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-node-windows DaemonSet init container's resources. + If omitted, the calico-node-windows DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-node-windows pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-node-windows DaemonSet nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-node-windows DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-node-windows DaemonSet nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-node-windows pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-node-windows DaemonSet. + If omitted, the calico-node-windows DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-node-windows DaemonSet tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + calicoWindowsUpgradeDaemonSet: + description: |- + Deprecated. The CalicoWindowsUpgradeDaemonSet is deprecated and will be removed from the API in the future. + CalicoWindowsUpgradeDaemonSet configures the calico-windows-upgrade DaemonSet. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the calico-windows-upgrade + DaemonSet. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-windows-upgrade DaemonSet. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-windows-upgrade + DaemonSet pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the calico-windows-upgrade DaemonSet's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-windows-upgrade pods. + If specified, this overrides any affinity that may be set on the calico-windows-upgrade DaemonSet. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default calico-windows-upgrade DaemonSet affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-windows-upgrade containers. + If specified, this overrides the specified calico-windows-upgrade DaemonSet containers. + If omitted, the calico-windows-upgrade DaemonSet will use its default values for its containers. + items: + description: + CalicoWindowsUpgradeDaemonSetContainer + is a calico-windows-upgrade DaemonSet container. + properties: + name: + description: + Name is an enum which identifies + the calico-windows-upgrade DaemonSet container + by name. + enum: + - calico-windows-upgrade + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-windows-upgrade DaemonSet container's resources. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for this container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-windows-upgrade pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-windows-upgrade DaemonSet nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-windows-upgrade DaemonSet nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-windows-upgrade pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-windows-upgrade DaemonSet. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-windows-upgrade DaemonSet tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + certificateManagement: + description: |- + CertificateManagement configures pods to submit a CertificateSigningRequest to the certificates.k8s.io/v1 API in order + to obtain TLS certificates. This feature requires that you bring your own CSR signing and approval process, otherwise + pods will be stuck during initialization. + properties: + caCert: + description: + Certificate of the authority that signs the CertificateSigningRequests + in PEM format. + format: byte + type: string + keyAlgorithm: + description: |- + Specify the algorithm used by pods to generate a key pair that is associated with the X.509 certificate request. + Default: RSAWithSize2048 + enum: + - "" + - RSAWithSize2048 + - RSAWithSize4096 + - RSAWithSize8192 + - ECDSAWithCurve256 + - ECDSAWithCurve384 + - ECDSAWithCurve521 + type: string + signatureAlgorithm: + description: |- + Specify the algorithm used for the signature of the X.509 certificate request. + Default: SHA256WithRSA + enum: + - "" + - SHA256WithRSA + - SHA384WithRSA + - SHA512WithRSA + - ECDSAWithSHA256 + - ECDSAWithSHA384 + - ECDSAWithSHA512 + type: string + signerName: + description: |- + When a CSR is issued to the certificates.k8s.io API, the signerName is added to the request in order to accommodate for clusters + with multiple signers. + Must be formatted as: `/`. + type: string + required: + - caCert + - signerName + type: object + cni: + description: CNI specifies the CNI that will be used by this installation. + properties: + binDir: + description: |- + BinDir is the path to the CNI binaries directory. + If you have changed the installation directory for CNI binaries in the container runtime configuration, + please ensure that this field points to the same directory as specified in the container runtime settings. + Default directory depends on the KubernetesProvider. + * For KubernetesProvider GKE, this field defaults to "/home/kubernetes/bin". + * For KubernetesProvider OpenShift, this field defaults to "/var/lib/cni/bin". + * Otherwise, this field defaults to "/opt/cni/bin". + type: string + confDir: + description: |- + ConfDir is the path to the CNI config directory. + If you have changed the installation directory for CNI configuration in the container runtime configuration, + please ensure that this field points to the same directory as specified in the container runtime settings. + Default directory depends on the KubernetesProvider. + * For KubernetesProvider GKE, this field defaults to "/etc/cni/net.d". + * For KubernetesProvider OpenShift, this field defaults to "/var/run/multus/cni/net.d". + * Otherwise, this field defaults to "/etc/cni/net.d". + type: string + ipam: + description: |- + IPAM specifies the pod IP address management that will be used in the Calico or + Calico Enterprise installation. + properties: + type: + description: |- + Specifies the IPAM plugin that will be used in the Calico or Calico Enterprise installation. + * For CNI Plugin Calico, this field defaults to Calico. + * For CNI Plugin GKE, this field defaults to HostLocal. + * For CNI Plugin AzureVNET, this field defaults to AzureVNET. + * For CNI Plugin AmazonVPC, this field defaults to AmazonVPC. + The IPAM plugin is installed and configured only if the CNI plugin is set to Calico, + for all other values of the CNI plugin the plugin binaries and CNI config is a dependency + that is expected to be installed separately. + Default: Calico + enum: + - Calico + - HostLocal + - AmazonVPC + - AzureVNET + type: string + required: + - type + type: object + type: + description: |- + Specifies the CNI plugin that will be used in the Calico or Calico Enterprise installation. + * For KubernetesProvider GKE, this field defaults to GKE. + * For KubernetesProvider AKS, this field defaults to AzureVNET. + * For KubernetesProvider EKS, this field defaults to AmazonVPC. + * If aws-node daemonset exists in kube-system when the Installation resource is created, this field defaults to AmazonVPC. + * For all other cases this field defaults to Calico. + For the value Calico, the CNI plugin binaries and CNI config will be installed as part of deployment, + for all other values the CNI plugin binaries and CNI config is a dependency that is expected + to be installed separately. + Default: Calico + enum: + - Calico + - GKE + - AmazonVPC + - AzureVNET + type: string + required: + - type + type: object + componentResources: + description: |- + Deprecated. Please use CalicoNodeDaemonSet, TyphaDeployment, and KubeControllersDeployment. + ComponentResources can be used to customize the resource requirements for each component. + Node, Typha, and KubeControllers are supported for installations. + items: + description: |- + Deprecated. Please use component resource config fields in Installation.Spec instead. + The ComponentResource struct associates a ResourceRequirements with a component by name + properties: + componentName: + description: + ComponentName is an enum which identifies the + component + enum: + - Node + - Typha + - KubeControllers + - NodeWindows + - FelixWindows + - ConfdWindows + type: string + resourceRequirements: + description: + ResourceRequirements allows customization of + limits and requests for compute resources such as cpu + and memory. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references one entry in + PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - componentName + - resourceRequirements + type: object + type: array + controlPlaneNodeSelector: + additionalProperties: + type: string + description: |- + ControlPlaneNodeSelector is used to select control plane nodes on which to run Calico + components. This is globally applied to all resources created by the operator excluding daemonsets. + type: object + controlPlaneReplicas: + description: |- + ControlPlaneReplicas defines how many replicas of the control plane core components will be deployed. + This field applies to all control plane components that support High Availability. Defaults to 2. + format: int32 + type: integer + controlPlaneTolerations: + description: |- + ControlPlaneTolerations specify tolerations which are then globally applied to all resources + created by the operator. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + csiNodeDriverDaemonSet: + description: + CSINodeDriverDaemonSet configures the csi-node-driver + DaemonSet. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the DaemonSet. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the csi-node-driver + DaemonSet. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the csi-node-driver DaemonSet. + If omitted, the csi-node-driver DaemonSet will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the csi-node-driver DaemonSet + pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the csi-node-driver DaemonSet's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the csi-node-driver pods. + If specified, this overrides any affinity that may be set on the csi-node-driver DaemonSet. + If omitted, the csi-node-driver DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default csi-node-driver DaemonSet affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of csi-node-driver containers. + If specified, this overrides the specified csi-node-driver DaemonSet containers. + If omitted, the csi-node-driver DaemonSet will use its default values for its containers. + items: + description: + CSINodeDriverDaemonSetContainer + is a csi-node-driver DaemonSet container. + properties: + name: + description: |- + Name is an enum which identifies the csi-node-driver DaemonSet container by name. + Supported values are: calico-csi, csi-node-driver-registrar. + enum: + - calico-csi + - csi-node-driver-registrar + - csi-node-driver + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named csi-node-driver DaemonSet container's resources. + If omitted, the csi-node-driver DaemonSet will use its default value for this container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the csi-node-driver pod's scheduling constraints. + If specified, each of the key/value pairs are added to the csi-node-driver DaemonSet nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the csi-node-driver DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default csi-node-driver DaemonSet nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the csi-node-driver pod's tolerations. + If specified, this overrides any tolerations that may be set on the csi-node-driver DaemonSet. + If omitted, the csi-node-driver DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default csi-node-driver DaemonSet tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + fipsMode: + description: |- + FIPSMode uses images and features only that are using FIPS 140-2 validated cryptographic modules and standards. + Only supported for Variant=Calico. + Default: Disabled + enum: + - Enabled + - Disabled + type: string + flexVolumePath: + description: |- + FlexVolumePath optionally specifies a custom path for FlexVolume. If not specified, FlexVolume will be + enabled by default. If set to 'None', FlexVolume will be disabled. The default is based on the + kubernetesProvider. + type: string + imagePath: + description: |- + ImagePath allows for the path part of an image to be specified. If specified + then the specified value will be used as the image path for each image. If not specified + or empty, the default for each image will be used. + A special case value, UseDefault, is supported to explicitly specify the default + image path will be used for each image. + Image format: + `/:` + This option allows configuring the `` portion of the above format. + type: string + imagePrefix: + description: |- + ImagePrefix allows for the prefix part of an image to be specified. If specified + then the given value will be used as a prefix on each image. If not specified + or empty, no prefix will be used. + A special case value, UseDefault, is supported to explicitly specify the default + image prefix will be used for each image. + Image format: + `/:` + This option allows configuring the `` portion of the above format. + type: string + imagePullSecrets: + description: |- + ImagePullSecrets is an array of references to container registry pull secrets to use. These are + applied to all images to be pulled. + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + kubeletVolumePluginPath: + description: |- + KubeletVolumePluginPath optionally specifies enablement of Calico CSI plugin. If not specified, + CSI will be enabled by default. If set to 'None', CSI will be disabled. + Default: /var/lib/kubelet + type: string + kubernetesProvider: + description: |- + KubernetesProvider specifies a particular provider of the Kubernetes platform and enables provider-specific configuration. + If the specified value is empty, the Operator will attempt to automatically determine the current provider. + If the specified value is not empty, the Operator will still attempt auto-detection, but + will additionally compare the auto-detected value to the specified value to confirm they match. + enum: + - "" + - EKS + - GKE + - AKS + - OpenShift + - DockerEnterprise + - RKE2 + - TKG + - Kind + type: string + logging: + description: Logging Configuration for Components + properties: + cni: + description: + Customized logging specification for calico-cni + plugin + properties: + logFileMaxAgeDays: + description: "Default: 30 (days)" + format: int32 + type: integer + logFileMaxCount: + description: "Default: 10" + format: int32 + type: integer + logFileMaxSize: + anyOf: + - type: integer + - type: string + description: "Default: 100Mi" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + logSeverity: + description: "Default: Info" + enum: + - Error + - Warning + - Info + - Debug + type: string + type: object + type: object + nodeMetricsPort: + description: |- + NodeMetricsPort specifies which port calico/node serves prometheus metrics on. By default, metrics are not enabled. + If specified, this overrides any FelixConfiguration resources which may exist. If omitted, then + prometheus metrics may still be configured through FelixConfiguration. + format: int32 + type: integer + nodeUpdateStrategy: + description: |- + NodeUpdateStrategy can be used to customize the desired update strategy, such as the MaxUnavailable + field. + properties: + rollingUpdate: + description: + Rolling update config params. Present only if + type = "RollingUpdate". + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of nodes with an existing available DaemonSet pod that + can have an updated DaemonSet pod during during an update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up to a minimum of 1. + Default value is 0. + Example: when this is set to 30%, at most 30% of the total number of nodes + that should be running the daemon pod (i.e. status.desiredNumberScheduled) + can have their a new pod created before the old pod is marked as deleted. + The update starts by launching new pods on 30% of nodes. Once an updated + pod is available (Ready for at least minReadySeconds) the old DaemonSet pod + on that node is marked deleted. If the old pod becomes unavailable for any + reason (Ready transitions to false, is evicted, or is drained) an updated + pod is immediately created on that node without considering surge limits. + Allowing surge implies the possibility that the resources consumed by the + daemonset on any given node can double if the readiness check fails, and + so resource intensive daemonsets should take into account that they may + cause evictions during disruption. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of DaemonSet pods that can be unavailable during the + update. Value can be an absolute number (ex: 5) or a percentage of total + number of DaemonSet pods at the start of the update (ex: 10%). Absolute + number is calculated from percentage by rounding up. + This cannot be 0 if MaxSurge is 0 + Default value is 1. + Example: when this is set to 30%, at most 30% of the total number of nodes + that should be running the daemon pod (i.e. status.desiredNumberScheduled) + can have their pods stopped for an update at any given time. The update + starts by stopping at most 30% of those DaemonSet pods and then brings + up new DaemonSet pods in their place. Once the new pods are available, + it then proceeds onto other DaemonSet pods, thus ensuring that at least + 70% of original number of DaemonSet pods are available at all times during + the update. + x-kubernetes-int-or-string: true + type: object + type: + description: + Type of daemon set update. Can be "RollingUpdate" + or "OnDelete". Default is RollingUpdate. + type: string + type: object + nonPrivileged: + description: |- + Deprecated. NonPrivileged is deprecated and will be removed from the API in a future release. + Enabling this field is not supported and will cause errors. + NonPrivileged configures Calico to be run in non-privileged containers as non-root users where possible. + type: string + proxy: + description: |- + Proxy is used to configure the HTTP(S) proxy settings that will be applied to Tigera containers that connect + to destinations outside the cluster. It is expected that NO_PROXY is configured such that destinations within + the cluster (including the API server) are exempt from proxying. + properties: + httpProxy: + description: |- + HTTPProxy defines the value of the HTTP_PROXY environment variable that will be set on Tigera containers that connect to + destinations outside the cluster. + type: string + httpsProxy: + description: |- + HTTPSProxy defines the value of the HTTPS_PROXY environment variable that will be set on Tigera containers that connect to + destinations outside the cluster. + type: string + noProxy: + description: |- + NoProxy defines the value of the NO_PROXY environment variable that will be set on Tigera containers that connect to + destinations outside the cluster. This value must be set such that destinations within the scope of the cluster, including + the Kubernetes API server, are exempt from being proxied. + type: string + type: object + registry: + description: |- + Registry is the default Docker registry used for component Docker images. + If specified then the given value must end with a slash character (`/`) and all images will be pulled from this registry. + If not specified then the default registries will be used. A special case value, UseDefault, is + supported to explicitly specify the default registries will be used. + Image format: + `/:` + This option allows configuring the `` portion of the above format. + type: string + serviceCIDRs: + description: + Kubernetes Service CIDRs. Specifying this is required + when using Calico for Windows. + items: + type: string + type: array + tlsCipherSuites: + description: + TLSCipherSuites defines the cipher suite list that + the TLS protocol should use during secure communication. + items: + properties: + name: + description: This should be a valid TLS cipher suite name. + enum: + - TLS_AES_256_GCM_SHA384 + - TLS_CHACHA20_POLY1305_SHA256 + - TLS_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 + - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + - TLS_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA + - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA + - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + type: string + type: object + type: array + typhaAffinity: + description: |- + Deprecated. Please use Installation.Spec.TyphaDeployment instead. + TyphaAffinity allows configuration of node affinity characteristics for Typha pods. + properties: + nodeAffinity: + description: + NodeAffinity describes node affinity scheduling + rules for typha. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + WARNING: Please note that if the affinity requirements specified by this field are not met at + scheduling time, the pod will NOT be scheduled onto the node. + There is no fallback to another affinity rules with this setting. + This may cause networking disruption or even catastrophic failure! + PreferredDuringSchedulingIgnoredDuringExecution should be used for affinity + unless there is a specific well understood reason to use RequiredDuringSchedulingIgnoredDuringExecution and + you can guarantee that the RequiredDuringSchedulingIgnoredDuringExecution will always have sufficient nodes to satisfy the requirement. + NOTE: RequiredDuringSchedulingIgnoredDuringExecution is set by default for AKS nodes, + to avoid scheduling Typhas on virtual-nodes. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + type: object + typhaDeployment: + description: |- + TyphaDeployment configures the typha Deployment. If used in conjunction with the deprecated + ComponentResources or TyphaAffinity, then these overrides take precedence. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the specification of the typha Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the typha Deployment. + If omitted, the typha Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + strategy: + description: + The deployment strategy to use to replace + existing pods with new ones. + properties: + rollingUpdate: + description: |- + Rolling update config params. Present only if DeploymentStrategyType = + RollingUpdate. + to be. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: object + template: + description: + Template describes the typha Deployment pod + that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the typha Deployment's PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the typha pods. + If specified, this overrides any affinity that may be set on the typha Deployment. + If omitted, the typha Deployment will use its default value for affinity. + If used in conjunction with the deprecated TyphaAffinity, then this value takes precedence. + WARNING: Please note that this field will override the default calico-typha Deployment affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of typha containers. + If specified, this overrides the specified typha Deployment containers. + If omitted, the typha Deployment will use its default values for its containers. + items: + description: + TyphaDeploymentContainer is a typha + Deployment container. + properties: + name: + description: |- + Name is an enum which identifies the typha Deployment container by name. + Supported values are: calico-typha + enum: + - calico-typha + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named typha Deployment container's resources. + If omitted, the typha Deployment will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + initContainers: + description: |- + InitContainers is a list of typha init containers. + If specified, this overrides the specified typha Deployment init containers. + If omitted, the typha Deployment will use its default values for its init containers. + items: + description: + TyphaDeploymentInitContainer is + a typha Deployment init container. + properties: + name: + description: |- + Name is an enum which identifies the typha Deployment init container by name. + Supported values are: typha-certs-key-cert-provisioner + enum: + - typha-certs-key-cert-provisioner + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named typha Deployment init container's resources. + If omitted, the typha Deployment will use its default value for this init container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-typha pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-typha Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-typha Deployment will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-typha Deployment nodeSelector. + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + If this value is nil, the default grace period will be used instead. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + Defaults to 30 seconds. + format: int64 + type: integer + tolerations: + description: |- + Tolerations is the typha pod's tolerations. + If specified, this overrides any tolerations that may be set on the typha Deployment. + If omitted, the typha Deployment will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-typha Deployment tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given + topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + type: object + type: object + type: object + typhaMetricsPort: + description: + TyphaMetricsPort specifies which port calico/typha + serves prometheus metrics on. By default, metrics are not enabled. + format: int32 + type: integer + variant: + description: |- + Variant is the product to install - one of Calico or TigeraSecureEnterprise + Default: Calico + enum: + - Calico + - TigeraSecureEnterprise + type: string + windowsNodes: + description: Windows Configuration + properties: + cniBinDir: + description: |- + CNIBinDir is the path to the CNI binaries directory on Windows, it must match what is used as 'bin_dir' under + [plugins] + [plugins."io.containerd.grpc.v1.cri"] + [plugins."io.containerd.grpc.v1.cri".cni] + on the containerd 'config.toml' file on the Windows nodes. + type: string + cniConfigDir: + description: |- + CNIConfigDir is the path to the CNI configuration directory on Windows, it must match what is used as 'conf_dir' under + [plugins] + [plugins."io.containerd.grpc.v1.cri"] + [plugins."io.containerd.grpc.v1.cri".cni] + on the containerd 'config.toml' file on the Windows nodes. + type: string + cniLogDir: + description: + CNILogDir is the path to the Calico CNI logs + directory on Windows. + type: string + vxlanAdapter: + description: + VXLANAdapter is the Network Adapter used for + VXLAN, leave blank for primary NIC + type: string + vxlanMACPrefix: + description: + VXLANMACPrefix is the prefix used when generating + MAC addresses for virtual NICs + pattern: ^[0-9A-Fa-f]{2}-[0-9A-Fa-f]{2}$ + type: string + type: object + type: object + conditions: + description: |- + Conditions represents the latest observed set of conditions for the component. A component may be one or more of + Ready, Progressing, Degraded or other customer types. + items: + description: + Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + imageSet: + description: |- + ImageSet is the name of the ImageSet being used, if there is an ImageSet + that is being used. If an ImageSet is not being used then this will not be set. + type: string + mtu: + description: |- + MTU is the most recently observed value for pod network MTU. This may be an explicitly + configured value, or based on Calico's native auto-detetion. + format: int32 + type: integer + variant: + description: + Variant is the most recently observed installed variant + - one of Calico or TigeraSecureEnterprise + enum: + - Calico + - TigeraSecureEnterprise + type: string + type: object + type: object + x-kubernetes-validations: + - message: resource name must be 'default', 'tigera-secure', or 'overlay' + rule: + self.metadata.name == 'default' || self.metadata.name == 'tigera-secure' + || self.metadata.name == 'overlay' + served: true + storage: true + subresources: + status: {} +--- +# Source: crd.projectcalico.org.v1/templates/operator.tigera.io_istios.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: istios.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: Istio + listKind: IstioList + plural: istios + singular: istio + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: Istio is the Schema for the istios API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: IstioSpec defines the desired state of Istio + properties: + dscpMark: + description: |- + DSCPMark define the value of the DSCP mark done by Felix and recognised by Istio CNI for Transparent + NetworkPolicies. + pattern: ^.* + type: integer + x-kubernetes-int-or-string: true + istioCNI: + description: + IstioCNIDaemonset defines the resource requirements for + the Istio CNI plugin. + properties: + spec: + description: + Spec allows users to specify custom fields for the + Istio CNI Daemonset. + properties: + template: + description: + Template allows users to specify custom fields + for the Istio CNI Daemonset. + properties: + spec: + description: + Spec allows users to specify custom fields + for the Istio CNI Daemonset. + properties: + affinity: + description: + Affinity specifies the affinity for the + deployment. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + nodeSelector: + additionalProperties: + type: string + description: + NodeSelector specifies the node affinity + for the deployment. + type: object + resources: + description: + Resources specifies the compute resources + required for the deployment. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references one entry + in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + tolerations: + description: + Tolerations specifies the tolerations + for the deployment. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + istiod: + description: + IstiodDeployment defines the resource requirements and + node selector for the Istio deployment. + properties: + spec: + description: + Spec allows users to specify custom fields for the + Istiod Deployment. + properties: + template: + description: + Template allows users to specify custom fields + for the Istiod Deployment. + properties: + spec: + description: + Spec allows users to specify custom fields + for the Istiod Deployment. + properties: + affinity: + description: + Affinity specifies the affinity for the + deployment. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + nodeSelector: + additionalProperties: + type: string + description: + NodeSelector specifies the node affinity + for the deployment. + type: object + resources: + description: + Resources specifies the compute resources + required for the deployment. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references one entry + in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + tolerations: + description: + Tolerations specifies the tolerations + for the deployment. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + ztunnel: + description: + ZTunnelDaemonset defines the resource requirements for + the ZTunnelDaemonset component. + properties: + spec: + description: + Spec allows users to specify custom fields for the + ZTunnel Daemonset. + properties: + template: + description: + Template allows users to specify custom fields + for the ZTunnel Daemonset. + properties: + spec: + description: + Spec allows users to specify custom fields + for the ZTunnel Daemonset. + properties: + affinity: + description: + Affinity specifies the affinity for the + deployment. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + nodeSelector: + additionalProperties: + type: string + description: + NodeSelector specifies the node affinity + for the deployment. + type: object + resources: + description: + Resources specifies the compute resources + required for the deployment. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references one entry + in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + tolerations: + description: + Tolerations specifies the tolerations + for the deployment. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + type: object + status: + description: IstioStatus defines the observed state of Istio + properties: + conditions: + description: |- + Conditions represents the latest observed set of conditions for the component. A component may be one or more of + Ready, Progressing, Degraded or other customer types. + items: + description: + Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + x-kubernetes-validations: + - message: resource name must be 'default' + rule: self.metadata.name == 'default' + served: true + storage: true + subresources: + status: {} +--- +# Source: crd.projectcalico.org.v1/templates/operator.tigera.io_managementclusterconnections.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: managementclusterconnections.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: ManagementClusterConnection + listKind: ManagementClusterConnectionList + plural: managementclusterconnections + singular: managementclusterconnection + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: |- + ManagementClusterConnection represents a link between a managed cluster and a management cluster. At most one + instance of this resource is supported. It must be named "tigera-secure". + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: + ManagementClusterConnectionSpec defines the desired state + of ManagementClusterConnection + properties: + guardianDeployment: + description: GuardianDeployment configures the guardian Deployment. + properties: + spec: + description: Spec is the specification of the guardian Deployment. + properties: + template: + description: + Template describes the guardian Deployment pod + that will be created. + properties: + spec: + description: Spec is the guardian Deployment's PodSpec. + properties: + containers: + description: |- + Containers is a list of guardian containers. + If specified, this overrides the specified guardian Deployment containers. + If omitted, the guardian Deployment will use its default values for its containers. + items: + description: + GuardianDeploymentContainer is a guardian + Deployment container. + properties: + name: + description: |- + Name is an enum which identifies the guardian Deployment container by name. + Supported values are: tigera-guardian + enum: + - tigera-guardian + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named guardian Deployment container's resources. + If omitted, the guardian Deployment will use its default value for this container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + initContainers: + description: |- + InitContainers is a list of guardian init containers. + If specified, this overrides the specified guardian Deployment init containers. + If omitted, the guardian Deployment will use its default values for its init containers. + items: + description: + GuardianDeploymentInitContainer is + a guardian Deployment init container. + properties: + name: + description: + Name is an enum which identifies + the guardian Deployment init container by + name. + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named guardian Deployment init container's resources. + If omitted, the guardian Deployment will use its default value for this init container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + type: object + type: object + type: object + type: object + impersonation: + description: |- + Impersonation configures the RBAC impersonation permissions for the guardian deployment. This field is not + applicable to installation variant Calico as no impersonation is ever used. Otherwise, if this field is left nil, + a default set of permissions will be applied. + WARNING: If this field is specified, it completely replaces the default permissions. + For example, providing an empty `impersonation: {}` block will result in guardian + having NO impersonation permissions. Similarly, if you specify `users` but omit `groups`, + guardian will lose its default permissions to impersonate groups. + properties: + groups: + description: |- + Groups is a list of group names that can be impersonated. An empty list infers all groups can be impersonated, + a null values means none. + items: + type: string + type: array + serviceAccounts: + description: |- + ServiceAccounts is a list of service account names that can be impersonated. An empty list infers all service accounts can + be impersonated, a null values means none. + items: + type: string + type: array + users: + description: |- + Users is a list of users that can be impersonated. An empty list infers all users can be impersonated, a null + value means none. + items: + type: string + type: array + type: object + managementClusterAddr: + description: |- + Specify where the managed cluster can reach the management cluster. Ex.: "10.128.0.10:30449". A managed cluster + should be able to access this address. This field is used by managed clusters only. + type: string + tls: + description: + TLS provides options for configuring how Managed Clusters + can establish an mTLS connection with the Management Cluster. + properties: + ca: + description: |- + CA indicates which verification method the tunnel client should use to verify the tunnel server's identity. + When left blank or set to 'Tigera', the tunnel client will expect a self-signed cert to be included in the certificate bundle + and will expect the cert to have a Common Name (CN) of 'voltron'. + When set to 'Public', the tunnel client will use its installed system certs and will use the managementClusterAddr to verify the tunnel server's identity. + Default: Tigera + enum: + - Tigera + - Public + type: string + type: object + type: object + status: + description: + ManagementClusterConnectionStatus defines the observed state + of ManagementClusterConnection + properties: + conditions: + description: |- + Conditions represents the latest observed set of conditions for the component. A component may be one or more of + Ready, Progressing, Degraded or other customer types. + items: + description: + Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + x-kubernetes-validations: + - message: resource name must be 'default' or 'tigera-secure' + rule: self.metadata.name == 'default' || self.metadata.name == 'tigera-secure' + served: true + storage: true + subresources: + status: {} +--- +# Source: crd.projectcalico.org.v1/templates/operator.tigera.io_tigerastatuses.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: tigerastatuses.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: TigeraStatus + listKind: TigeraStatusList + plural: tigerastatuses + singular: tigerastatus + scope: Cluster + versions: + - additionalPrinterColumns: + - description: Whether the component running and stable. + jsonPath: .status.conditions[?(@.type=='Available')].status + name: Available + type: string + - description: Whether the component is processing changes. + jsonPath: .status.conditions[?(@.type=='Progressing')].status + name: Progressing + type: string + - description: Whether the component is degraded. + jsonPath: .status.conditions[?(@.type=='Degraded')].status + name: Degraded + type: string + - description: The time the component's Available status last changed. + jsonPath: .status.conditions[?(@.type=='Available')].lastTransitionTime + name: Since + type: date + name: v1 + schema: + openAPIV3Schema: + description: + TigeraStatus represents the most recently observed status for + Calico or a Calico Enterprise functional area. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: TigeraStatusSpec defines the desired state of TigeraStatus + type: object + status: + description: TigeraStatusStatus defines the observed state of TigeraStatus + properties: + conditions: + description: |- + Conditions represents the latest observed set of conditions for this component. A component may be one or more of + Available, Progressing, or Degraded. + items: + description: + TigeraStatusCondition represents a condition attached + to a particular component. + properties: + lastTransitionTime: + description: + The timestamp representing the start time for the + current status. + format: date-time + type: string + message: + description: + Optionally, a detailed message providing additional + context. + type: string + observedGeneration: + description: |- + observedGeneration represents the generation that the condition was set based upon. + For instance, if generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + type: integer + reason: + description: A brief reason explaining the condition. + type: string + status: + description: + The status of the condition. May be True, False, + or Unknown. + type: string + type: + description: + The type of condition. May be Available, Progressing, + or Degraded. + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + required: + - conditions + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: crd.projectcalico.org.v1/templates/operator.tigera.io_whiskers.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: whiskers.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: Whisker + listKind: WhiskerList + plural: whiskers + singular: whisker + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + notifications: + description: |- + Default: Enabled + This setting enables calls to an external API to retrieve notification banner text in the Whisker UI. + Allowed values are Enabled or Disabled. Defaults to Enabled. + type: string + whiskerDeployment: + description: + WhiskerDeployment is the configuration for the whisker + Deployment. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the specification of the whisker Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the whisker Deployment. + If omitted, the whisker Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + strategy: + description: + The deployment strategy to use to replace existing + pods with new ones. + properties: + rollingUpdate: + description: |- + Rolling update config params. Present only if DeploymentStrategyType = + RollingUpdate. + to be. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: object + template: + description: + Template describes the whisker Deployment pod + that will be created. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the whisker Deployment's PodSpec. + properties: + affinity: + description: + Affinity is a group of affinity scheduling + rules for the whisker pods. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of whisker containers. + If specified, this overrides the specified EGW Deployment containers. + If omitted, the whisker Deployment will use its default values for its containers. + items: + properties: + name: + enum: + - whisker + - whisker-backend + type: string + resources: + description: + ResourceRequirements describes + the compute resource requirements. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: + NodeSelector gives more control over + the nodes where the whisker pods will run on. + type: object + priorityClassName: + description: + PriorityClassName allows to specify a + PriorityClass resource to be used. + type: string + terminationGracePeriodSeconds: + description: + TerminationGracePeriodSeconds defines + the termination grace period of the whisker pods + in seconds. + format: int64 + minimum: 0 + type: integer + tolerations: + description: |- + Tolerations is the whisker pod's tolerations. + If specified, this overrides any tolerations that may be set on the whisker Deployment. + If omitted, the whisker Deployment will use its default value for tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + type: object + type: object + type: object + type: object + status: + description: WhiskerStatus defines the observed state of Whisker + properties: + conditions: + description: |- + Conditions represents the latest observed set of conditions for the component. A component may be one or more of + Ready, Progressing, Degraded or other customer types. + items: + description: + Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + x-kubernetes-validations: + - message: resource name must be 'default' + rule: self.metadata.name == 'default' + served: true + storage: true + subresources: + status: {} +--- +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_bgpconfigurations.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: bgpconfigurations.crd.projectcalico.org +spec: + group: crd.projectcalico.org + names: + kind: BGPConfiguration + listKind: BGPConfigurationList + plural: bgpconfigurations + singular: bgpconfiguration + preserveUnknownFields: false + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + asNumber: + format: int32 + type: integer + bindMode: + enum: + - None + - NodeIP + type: string + communities: + items: + properties: + name: + type: string + value: + pattern: ^(\d+):(\d+)$|^(\d+):(\d+):(\d+)$ + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set + ignoredInterfaces: + items: + type: string + type: array + x-kubernetes-list-type: set + listenPort: + maximum: 65535 + minimum: 1 + type: integer + localWorkloadPeeringIPV4: + type: string + localWorkloadPeeringIPV6: + type: string + logSeverityScreen: + default: Info + pattern: ^(?i)(Trace|Debug|Info|Warning|Error|Fatal)?$ + type: string + nodeMeshMaxRestartTime: + type: string + nodeMeshPassword: + properties: + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + nodeToNodeMeshEnabled: + type: boolean + prefixAdvertisements: + items: + properties: + cidr: + format: cidr + type: string + communities: + items: + type: string + type: array + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set + serviceClusterIPs: + items: + properties: + cidr: + format: cidr + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set + serviceExternalIPs: + items: + properties: + cidr: + format: cidr + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set + serviceLoadBalancerAggregation: + default: Enabled + enum: + - Enabled + - Disabled + type: string + serviceLoadBalancerIPs: + items: + properties: + cidr: + format: cidr + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set + type: object + type: object + served: true + storage: true +--- +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_bgpfilters.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: bgpfilters.crd.projectcalico.org +spec: + group: crd.projectcalico.org + names: + kind: BGPFilter + listKind: BGPFilterList + plural: bgpfilters + singular: bgpfilter + preserveUnknownFields: false + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + exportV4: + items: + properties: + action: + enum: + - Accept + - Reject + type: string + cidr: + format: cidr + type: string + interface: + type: string + matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn + type: string + prefixLength: + properties: + max: + format: int32 + maximum: 32 + minimum: 0 + type: integer + min: + format: int32 + maximum: 32 + minimum: 0 + type: integer + type: object + x-kubernetes-map-type: atomic + source: + enum: + - RemotePeers + type: string + required: + - action + type: object + x-kubernetes-map-type: atomic + type: array + exportV6: + items: + properties: + action: + enum: + - Accept + - Reject + type: string + cidr: + format: cidr + type: string + interface: + type: string + matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn + type: string + prefixLength: + properties: + max: + format: int32 + maximum: 128 + minimum: 0 + type: integer + min: + format: int32 + maximum: 128 + minimum: 0 + type: integer + type: object + x-kubernetes-map-type: atomic + source: + enum: + - RemotePeers + type: string + required: + - action + type: object + x-kubernetes-map-type: atomic + type: array + importV4: + items: + properties: + action: + enum: + - Accept + - Reject + type: string + cidr: + format: cidr + type: string + interface: + type: string + matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn + type: string + prefixLength: + properties: + max: + format: int32 + maximum: 32 + minimum: 0 + type: integer + min: + format: int32 + maximum: 32 + minimum: 0 + type: integer + type: object + x-kubernetes-map-type: atomic + source: + enum: + - RemotePeers + type: string + required: + - action + type: object + x-kubernetes-map-type: atomic + type: array + importV6: + items: + properties: + action: + enum: + - Accept + - Reject + type: string + cidr: + format: cidr + type: string + interface: + type: string + matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn + type: string + prefixLength: + properties: + max: + format: int32 + maximum: 128 + minimum: 0 + type: integer + min: + format: int32 + maximum: 128 + minimum: 0 + type: integer + type: object + x-kubernetes-map-type: atomic + source: + enum: + - RemotePeers + type: string + required: + - action + type: object + x-kubernetes-map-type: atomic + type: array + type: object + type: object + served: true + storage: true +--- +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_bgppeers.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: bgppeers.crd.projectcalico.org +spec: + group: crd.projectcalico.org + names: + kind: BGPPeer + listKind: BGPPeerList + plural: bgppeers + singular: bgppeer + preserveUnknownFields: false + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + asNumber: + format: int32 + type: integer + filters: + items: + type: string + type: array + keepOriginalNextHop: + type: boolean + keepaliveTime: + type: string + localASNumber: + format: int32 + type: integer + localWorkloadSelector: + type: string + maxRestartTime: + type: string + nextHopMode: + enum: + - Auto + - Self + - Keep + type: string + node: + type: string + nodeSelector: + type: string + numAllowedLocalASNumbers: + format: int32 + type: integer + password: + properties: + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + peerIP: + type: string + peerSelector: + type: string + reachableBy: + type: string + reversePeering: + allOf: + - enum: + - Auto + - Manual + - enum: + - Auto + - Manual + type: string + sourceAddress: + enum: + - UseNodeIP + - None + type: string + ttlSecurity: + type: integer + type: object + type: object + served: true + storage: true +--- +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_blockaffinities.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: blockaffinities.crd.projectcalico.org +spec: + group: crd.projectcalico.org + names: + kind: BlockAffinity + listKind: BlockAffinityList + plural: blockaffinities + singular: blockaffinity + preserveUnknownFields: false + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + cidr: + type: string + deleted: + type: string + node: + type: string + state: + type: string + type: + type: string + required: + - cidr + - deleted + - node + - state + type: object + type: object + served: true + storage: true +--- +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_caliconodestatuses.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: caliconodestatuses.crd.projectcalico.org +spec: + group: crd.projectcalico.org + names: + kind: CalicoNodeStatus + listKind: CalicoNodeStatusList + plural: caliconodestatuses + singular: caliconodestatus + preserveUnknownFields: false + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + classes: + items: + enum: + - Agent + - BGP + - Routes + type: string + type: array + node: + type: string + updatePeriodSeconds: + format: int32 + type: integer + type: object + status: + properties: + agent: + properties: + birdV4: + properties: + lastBootTime: + type: string + lastReconfigurationTime: + type: string + routerID: + type: string + state: + enum: + - Ready + - NotReady + type: string + version: + type: string + type: object + birdV6: + properties: + lastBootTime: + type: string + lastReconfigurationTime: + type: string + routerID: + type: string + state: + enum: + - Ready + - NotReady + type: string + version: + type: string + type: object + type: object + bgp: + properties: + numberEstablishedV4: + type: integer + numberEstablishedV6: + type: integer + numberNotEstablishedV4: + type: integer + numberNotEstablishedV6: + type: integer + peersV4: + items: + properties: + peerIP: + type: string + since: + type: string + state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close + type: string + type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer + type: string + type: object + type: array + peersV6: + items: + properties: + peerIP: + type: string + since: + type: string + state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close + type: string + type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer + type: string + type: object + type: array + required: + - numberEstablishedV4 + - numberEstablishedV6 + - numberNotEstablishedV4 + - numberNotEstablishedV6 + type: object + lastUpdated: + format: date-time + nullable: true + type: string + routes: + properties: + routesV4: + items: + properties: + destination: + type: string + gateway: + type: string + interface: + type: string + learnedFrom: + properties: + peerIP: + type: string + sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer + type: string + type: object + type: + enum: + - FIB + - RIB + type: string + type: object + type: array + routesV6: + items: + properties: + destination: + type: string + gateway: + type: string + interface: + type: string + learnedFrom: + properties: + peerIP: + type: string + sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer + type: string + type: object + type: + enum: + - FIB + - RIB + type: string + type: object + type: array + type: object + type: object + type: object + served: true + storage: true +--- +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_clusterinformations.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: clusterinformations.crd.projectcalico.org +spec: + group: crd.projectcalico.org + names: + kind: ClusterInformation + listKind: ClusterInformationList + plural: clusterinformations + singular: clusterinformation + preserveUnknownFields: false + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + calicoVersion: + type: string + clusterGUID: + type: string + clusterType: + type: string + datastoreReady: + type: boolean + variant: + type: string + type: object + type: object + served: true + storage: true +--- +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_felixconfigurations.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: felixconfigurations.crd.projectcalico.org +spec: + group: crd.projectcalico.org + names: + kind: FelixConfiguration + listKind: FelixConfigurationList + plural: felixconfigurations + singular: felixconfiguration + preserveUnknownFields: false + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: Felix Configuration contains the configuration for Felix. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: FelixConfigurationSpec contains the values of the Felix configuration. + properties: + allowIPIPPacketsFromWorkloads: + description: |- + AllowIPIPPacketsFromWorkloads controls whether Felix will add a rule to drop IPIP encapsulated traffic + from workloads. [Default: false] + type: boolean + allowVXLANPacketsFromWorkloads: + description: |- + AllowVXLANPacketsFromWorkloads controls whether Felix will add a rule to drop VXLAN encapsulated traffic + from workloads. [Default: false] + type: boolean + awsSrcDstCheck: + description: |- + AWSSrcDstCheck controls whether Felix will try to change the "source/dest check" setting on the EC2 instance + on which it is running. A value of "Disable" will try to disable the source/dest check. Disabling the check + allows for sending workload traffic without encapsulation within the same AWS subnet. + [Default: DoNothing] + enum: + - DoNothing + - Enable + - Disable + type: string + bpfAttachType: + description: |- + BPFAttachType controls how are the BPF programs at the network interfaces attached. + By default `TCX` is used where available to enable easier coexistence with 3rd party programs. + `TC` can force the legacy method of attaching via a qdisc. `TCX` falls back to `TC` if `TCX` is not available. + [Default: TCX] + enum: + - TC + - TCX + type: string + bpfCTLBLogFilter: + description: |- + BPFCTLBLogFilter specifies, what is logged by connect time load balancer when BPFLogLevel is + debug. Currently has to be specified as 'all' when BPFLogFilters is set + to see CTLB logs. + [Default: unset - means logs are emitted when BPFLogLevel id debug and BPFLogFilters not set.] + type: string + bpfConnectTimeLoadBalancing: + description: |- + BPFConnectTimeLoadBalancing when in BPF mode, controls whether Felix installs the connect-time load + balancer. The connect-time load balancer is required for the host to be able to reach Kubernetes services + and it improves the performance of pod-to-service connections.When set to TCP, connect time load balancing + is available only for services with TCP ports. [Default: TCP] + enum: + - TCP + - Enabled + - Disabled + type: string + bpfConnectTimeLoadBalancingEnabled: + description: |- + BPFConnectTimeLoadBalancingEnabled when in BPF mode, controls whether Felix installs the connection-time load + balancer. The connect-time load balancer is required for the host to be able to reach Kubernetes services + and it improves the performance of pod-to-service connections. The only reason to disable it is for debugging + purposes. + + Deprecated: Use BPFConnectTimeLoadBalancing [Default: true] + type: boolean + bpfConntrackLogLevel: + description: |- + BPFConntrackLogLevel controls the log level of the BPF conntrack cleanup program, which runs periodically + to clean up expired BPF conntrack entries. + [Default: Off]. + enum: + - "Off" + - Debug + type: string + bpfConntrackMode: + description: |- + BPFConntrackCleanupMode controls how BPF conntrack entries are cleaned up. `Auto` will use a BPF program if supported, + falling back to userspace if not. `Userspace` will always use the userspace cleanup code. `BPFProgram` will + always use the BPF program (failing if not supported). + + /To be deprecated in future versions as conntrack map type changed to + lru_hash and userspace cleanup is the only mode that is supported. + [Default: Userspace] + enum: + - Auto + - Userspace + - BPFProgram + type: string + bpfConntrackTimeouts: + description: |- + BPFConntrackTimers overrides the default values for the specified conntrack timer if + set. Each value can be either a duration or `Auto` to pick the value from + a Linux conntrack timeout. + + Configurable timers are: CreationGracePeriod, TCPSynSent, + TCPEstablished, TCPFinsSeen, TCPResetSeen, UDPTimeout, GenericTimeout, + ICMPTimeout. + + Unset values are replaced by the default values with a warning log for + incorrect values. + properties: + creationGracePeriod: + description: |- + CreationGracePeriod gives a generic grace period to new connections + before they are considered for cleanup [Default: 10s]. + pattern: ^(([0-9]*(\.[0-9]*)?(ms|s|h|m|us)+)+|Auto)$ + type: string + genericTimeout: + description: |- + GenericTimeout controls how long it takes before considering this + entry for cleanup after the connection became idle. If set to 'Auto', the + value from nf_conntrack_generic_timeout is used. If nil, Calico uses its + own default value. [Default: 10m]. + pattern: ^(([0-9]*(\.[0-9]*)?(ms|s|h|m|us)+)+|Auto)$ + type: string + icmpTimeout: + description: |- + ICMPTimeout controls how long it takes before considering this + entry for cleanup after the connection became idle. If set to 'Auto', the + value from nf_conntrack_icmp_timeout is used. If nil, Calico uses its + own default value. [Default: 5s]. + pattern: ^(([0-9]*(\.[0-9]*)?(ms|s|h|m|us)+)+|Auto)$ + type: string + tcpEstablished: + description: |- + TCPEstablished controls how long it takes before considering this entry for + cleanup after the connection became idle. If set to 'Auto', the + value from nf_conntrack_tcp_timeout_established is used. If nil, Calico uses + its own default value. [Default: 1h]. + pattern: ^(([0-9]*(\.[0-9]*)?(ms|s|h|m|us)+)+|Auto)$ + type: string + tcpFinsSeen: + description: |- + TCPFinsSeen controls how long it takes before considering this entry for + cleanup after the connection was closed gracefully. If set to 'Auto', the + value from nf_conntrack_tcp_timeout_time_wait is used. If nil, Calico uses + its own default value. [Default: Auto]. + pattern: ^(([0-9]*(\.[0-9]*)?(ms|s|h|m|us)+)+|Auto)$ + type: string + tcpResetSeen: + description: |- + TCPResetSeen controls how long it takes before considering this entry for + cleanup after the connection was aborted. If nil, Calico uses its own + default value. [Default: 40s]. + pattern: ^(([0-9]*(\.[0-9]*)?(ms|s|h|m|us)+)+|Auto)$ + type: string + tcpSynSent: + description: |- + TCPSynSent controls how long it takes before considering this entry for + cleanup after the last SYN without a response. If set to 'Auto', the + value from nf_conntrack_tcp_timeout_syn_sent is used. If nil, Calico uses + its own default value. [Default: 20s]. + pattern: ^(([0-9]*(\.[0-9]*)?(ms|s|h|m|us)+)+|Auto)$ + type: string + udpTimeout: + description: |- + UDPTimeout controls how long it takes before considering this entry for + cleanup after the connection became idle. If nil, Calico uses its own + default value. [Default: 60s]. + pattern: ^(([0-9]*(\.[0-9]*)?(ms|s|h|m|us)+)+|Auto)$ + type: string + type: object + bpfDSROptoutCIDRs: + description: |- + BPFDSROptoutCIDRs is a list of CIDRs which are excluded from DSR. That is, clients + in those CIDRs will access service node ports as if BPFExternalServiceMode was set to + Tunnel. + items: + type: string + type: array + bpfDataIfacePattern: + description: |- + BPFDataIfacePattern is a regular expression that controls which interfaces Felix should attach BPF programs to + in order to catch traffic to/from the network. This needs to match the interfaces that Calico workload traffic + flows over as well as any interfaces that handle incoming traffic to nodeports and services from outside the + cluster. It should not match the workload interfaces (usually named cali...) or any other special device managed + by Calico itself (e.g., tunnels). + type: string + bpfDisableGROForIfaces: + description: |- + BPFDisableGROForIfaces is a regular expression that controls which interfaces Felix should disable the + Generic Receive Offload [GRO] option. It should not match the workload interfaces (usually named cali...). + type: string + bpfDisableUnprivileged: + description: |- + BPFDisableUnprivileged, if enabled, Felix sets the kernel.unprivileged_bpf_disabled sysctl to disable + unprivileged use of BPF. This ensures that unprivileged users cannot access Calico's BPF maps and + cannot insert their own BPF programs to interfere with Calico's. [Default: true] + type: boolean + bpfEnabled: + description: + "BPFEnabled, if enabled Felix will use the BPF dataplane. + [Default: false]" + type: boolean + bpfEnforceRPF: + description: |- + BPFEnforceRPF enforce strict RPF on all host interfaces with BPF programs regardless of + what is the per-interfaces or global setting. Possible values are Disabled, Strict + or Loose. [Default: Loose] + pattern: ^(?i)(Disabled|Strict|Loose)?$ + type: string + bpfExcludeCIDRsFromNAT: + description: |- + BPFExcludeCIDRsFromNAT is a list of CIDRs that are to be excluded from NAT + resolution so that host can handle them. A typical usecase is node local + DNS cache. + items: + type: string + type: array + bpfExportBufferSizeMB: + description: |- + BPFExportBufferSizeMB in BPF mode, controls the buffer size used for sending BPF events to felix. + [Default: 1] + type: integer + bpfExtToServiceConnmark: + description: |- + BPFExtToServiceConnmark in BPF mode, controls a 32bit mark that is set on connections from an + external client to a local service. This mark allows us to control how packets of that + connection are routed within the host and how is routing interpreted by RPF check. [Default: 0] + type: integer + bpfExternalServiceMode: + description: |- + BPFExternalServiceMode in BPF mode, controls how connections from outside the cluster to services (node ports + and cluster IPs) are forwarded to remote workloads. If set to "Tunnel" then both request and response traffic + is tunneled to the remote node. If set to "DSR", the request traffic is tunneled but the response traffic + is sent directly from the remote node. In "DSR" mode, the remote node appears to use the IP of the ingress + node; this requires a permissive L2 network. [Default: Tunnel] + pattern: ^(?i)(Tunnel|DSR)?$ + type: string + bpfForceTrackPacketsFromIfaces: + description: |- + BPFForceTrackPacketsFromIfaces in BPF mode, forces traffic from these interfaces + to skip Calico's iptables NOTRACK rule, allowing traffic from those interfaces to be + tracked by Linux conntrack. Should only be used for interfaces that are not used for + the Calico fabric. For example, a docker bridge device for non-Calico-networked + containers. [Default: docker+] + items: + type: string + type: array + bpfHostConntrackBypass: + description: |- + BPFHostConntrackBypass Controls whether to bypass Linux conntrack in BPF mode for + workloads and services. [Default: true - bypass Linux conntrack] + type: boolean + bpfHostNetworkedNATWithoutCTLB: + description: |- + BPFHostNetworkedNATWithoutCTLB when in BPF mode, controls whether Felix does a NAT without CTLB. This along with BPFConnectTimeLoadBalancing + determines the CTLB behavior. [Default: Enabled] + enum: + - Enabled + - Disabled + type: string + bpfJITHardening: + allOf: + - enum: + - Auto + - Strict + - enum: + - Auto + - Strict + description: |- + BPFJITHardening controls BPF JIT hardening. When set to "Auto", Felix will set JIT hardening to 1 + if it detects the current value is 2 (strict mode that hurts performance). When set to "Strict", + Felix will not modify the JIT hardening setting. [Default: Auto] + type: string + bpfKubeProxyHealthzPort: + description: |- + BPFKubeProxyHealthzPort, in BPF mode, controls the port that Felix's embedded kube-proxy health check server binds to. + The health check server is used by external load balancers to determine if this node should receive traffic. [Default: 10256] + type: integer + bpfKubeProxyIptablesCleanupEnabled: + description: |- + BPFKubeProxyIptablesCleanupEnabled, if enabled in BPF mode, Felix will proactively clean up the upstream + Kubernetes kube-proxy's iptables chains. Should only be enabled if kube-proxy is not running. [Default: true] + type: boolean + bpfKubeProxyMinSyncPeriod: + description: |- + BPFKubeProxyMinSyncPeriod, in BPF mode, controls the minimum time between updates to the dataplane for Felix's + embedded kube-proxy. Lower values give reduced set-up latency. Higher values reduce Felix CPU usage by + batching up more work. [Default: 1s] + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + bpfL3IfacePattern: + description: |- + BPFL3IfacePattern is a regular expression that allows to list tunnel devices like wireguard or vxlan (i.e., L3 devices) + in addition to BPFDataIfacePattern. That is, tunnel interfaces not created by Calico, that Calico workload traffic flows + over as well as any interfaces that handle incoming traffic to nodeports and services from outside the cluster. + type: string + bpfLogFilters: + additionalProperties: + type: string + description: |- + BPFLogFilters is a map of key=values where the value is + a pcap filter expression and the key is an interface name with 'all' + denoting all interfaces, 'weps' all workload endpoints and 'heps' all host + endpoints. + + When specified as an env var, it accepts a comma-separated list of + key=values. + [Default: unset - means all debug logs are emitted] + type: object + bpfLogLevel: + description: |- + BPFLogLevel controls the log level of the BPF programs when in BPF dataplane mode. One of "Off", "Info", or + "Debug". The logs are emitted to the BPF trace pipe, accessible with the command `tc exec bpf debug`. + [Default: Off]. + pattern: ^(?i)(Off|Info|Debug)?$ + type: string + bpfMaglevMaxEndpointsPerService: + description: |- + BPFMaglevMaxEndpointsPerService is the maximum number of endpoints + expected to be part of a single Maglev-enabled service. + + Influences the size of the per-service Maglev lookup-tables generated by Felix + and thus the amount of memory reserved. + + [Default: 100] + type: integer + bpfMaglevMaxServices: + description: |- + BPFMaglevMaxServices is the maximum number of expected Maglev-enabled + services that Felix will allocate lookup-tables for. + + [Default: 100] + type: integer + bpfMapSizeConntrack: + description: |- + BPFMapSizeConntrack sets the size for the conntrack map. This map must be large enough to hold + an entry for each active connection. Warning: changing the size of the conntrack map can cause disruption. + type: integer + bpfMapSizeConntrackCleanupQueue: + description: |- + BPFMapSizeConntrackCleanupQueue sets the size for the map used to hold NAT conntrack entries that are queued + for cleanup. This should be big enough to hold all the NAT entries that expire within one cleanup interval. + minimum: 1 + type: integer + bpfMapSizeConntrackScaling: + description: |- + BPFMapSizeConntrackScaling controls whether and how we scale the conntrack map size depending + on its usage. 'Disabled' make the size stay at the default or whatever is set by + BPFMapSizeConntrack*. 'DoubleIfFull' doubles the size when the map is pretty much full even + after cleanups. [Default: DoubleIfFull] + pattern: ^(?i)(Disabled|DoubleIfFull)?$ + type: string + bpfMapSizeIPSets: + description: |- + BPFMapSizeIPSets sets the size for ipsets map. The IP sets map must be large enough to hold an entry + for each endpoint matched by every selector in the source/destination matches in network policy. Selectors + such as "all()" can result in large numbers of entries (one entry per endpoint in that case). + type: integer + bpfMapSizeIfState: + description: |- + BPFMapSizeIfState sets the size for ifstate map. The ifstate map must be large enough to hold an entry + for each device (host + workloads) on a host. + type: integer + bpfMapSizeNATAffinity: + description: |- + BPFMapSizeNATAffinity sets the size of the BPF map that stores the affinity of a connection (for services that + enable that feature. + type: integer + bpfMapSizeNATBackend: + description: |- + BPFMapSizeNATBackend sets the size for NAT back end map. + This is the total number of endpoints. This is mostly + more than the size of the number of services. + type: integer + bpfMapSizeNATFrontend: + description: |- + BPFMapSizeNATFrontend sets the size for NAT front end map. + FrontendMap should be large enough to hold an entry for each nodeport, + external IP and each port in each service. + type: integer + bpfMapSizePerCpuConntrack: + description: |- + BPFMapSizePerCPUConntrack determines the size of conntrack map based on the number of CPUs. If set to a + non-zero value, overrides BPFMapSizeConntrack with `BPFMapSizePerCPUConntrack * (Number of CPUs)`. + This map must be large enough to hold an entry for each active connection. Warning: changing the size of the + conntrack map can cause disruption. + type: integer + bpfMapSizeRoute: + description: |- + BPFMapSizeRoute sets the size for the routes map. The routes map should be large enough + to hold one entry per workload and a handful of entries per host (enough to cover its own IPs and + tunnel IPs). + type: integer + bpfPSNATPorts: + anyOf: + - type: integer + - type: string + description: |- + BPFPSNATPorts sets the range from which we randomly pick a port if there is a source port + collision. This should be within the ephemeral range as defined by RFC 6056 (1024–65535) and + preferably outside the ephemeral ranges used by common operating systems. Linux uses + 32768–60999, while others mostly use the IANA defined range 49152–65535. It is not necessarily + a problem if this range overlaps with the operating systems. Both ends of the range are + inclusive. [Default: 20000:29999] + pattern: ^.* + x-kubernetes-int-or-string: true + bpfPolicyDebugEnabled: + description: |- + BPFPolicyDebugEnabled when true, Felix records detailed information + about the BPF policy programs, which can be examined with the calico-bpf command-line tool. + type: boolean + bpfProfiling: + description: |- + BPFProfiling controls profiling of BPF programs. At the monent, it can be + Disabled or Enabled. [Default: Disabled] + enum: + - Enabled + - Disabled + type: string + bpfRedirectToPeer: + description: |- + BPFRedirectToPeer controls whether traffic may be forwarded directly to the peer side of a workload’s device. + Note that the legacy "L2Only" option is now deprecated and if set it is treated like "Enabled. + Setting this option to "Enabled" allows direct redirection (including from L3 host devices such as IPIP tunnels or WireGuard), + which can improve redirection performance but causes the redirected packets to bypass the host‑side ingress path. + As a result, packet‑capture tools on the host side of the workload device (for example, tcpdump) will not see that traffic. [Default: Enabled] + enum: + - Enabled + - Disabled + type: string + cgroupV2Path: + description: + CgroupV2Path overrides the default location where to + find the cgroup hierarchy. + type: string + chainInsertMode: + description: |- + ChainInsertMode controls whether Felix hooks the kernel's top-level iptables chains by inserting a rule + at the top of the chain or by appending a rule at the bottom. insert is the safe default since it prevents + Calico's rules from being bypassed. If you switch to append mode, be sure that the other rules in the chains + signal acceptance by falling through to the Calico rules, otherwise the Calico policy will be bypassed. + [Default: insert] + pattern: ^(?i)(Insert|Append)?$ + type: string + dataplaneDriver: + description: |- + DataplaneDriver filename of the external dataplane driver to use. Only used if UseInternalDataplaneDriver + is set to false. + type: string + dataplaneWatchdogTimeout: + description: |- + DataplaneWatchdogTimeout is the readiness/liveness timeout used for Felix's (internal) dataplane driver. + Deprecated: replaced by the generic HealthTimeoutOverrides. + type: string + debugDisableLogDropping: + description: |- + DebugDisableLogDropping disables the dropping of log messages when the log buffer is full. This can + significantly impact performance if log write-out is a bottleneck. [Default: false] + type: boolean + debugHost: + description: |- + DebugHost is the host IP or hostname to bind the debug port to. Only used + if DebugPort is set. [Default:localhost] + type: string + debugMemoryProfilePath: + description: + DebugMemoryProfilePath is the path to write the memory + profile to when triggered by signal. + type: string + debugPort: + description: |- + DebugPort if set, enables Felix's debug HTTP port, which allows memory and CPU profiles + to be retrieved. The debug port is not secure, it should not be exposed to the internet. + type: integer + debugSimulateCalcGraphHangAfter: + description: |- + DebugSimulateCalcGraphHangAfter is used to simulate a hang in the calculation graph after the specified duration. + This is useful in tests of the watchdog system only! + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + debugSimulateDataplaneApplyDelay: + description: |- + DebugSimulateDataplaneApplyDelay adds an artificial delay to every dataplane operation. This is useful for + simulating a heavily loaded system for test purposes only. + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + debugSimulateDataplaneHangAfter: + description: |- + DebugSimulateDataplaneHangAfter is used to simulate a hang in the dataplane after the specified duration. + This is useful in tests of the watchdog system only! + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + defaultEndpointToHostAction: + description: |- + DefaultEndpointToHostAction controls what happens to traffic that goes from a workload endpoint to the host + itself (after the endpoint's egress policy is applied). By default, Calico blocks traffic from workload + endpoints to the host itself with an iptables "DROP" action. If you want to allow some or all traffic from + endpoint to host, set this parameter to RETURN or ACCEPT. Use RETURN if you have your own rules in the iptables + "INPUT" chain; Calico will insert its rules at the top of that chain, then "RETURN" packets to the "INPUT" chain + once it has completed processing workload endpoint egress policy. Use ACCEPT to unconditionally accept packets + from workloads after processing workload endpoint egress policy. [Default: Drop] + pattern: ^(?i)(Drop|Accept|Return)?$ + type: string + deviceRouteProtocol: + description: |- + DeviceRouteProtocol controls the protocol to set on routes programmed by Felix. The protocol is an 8-bit label + used to identify the owner of the route. + type: integer + deviceRouteSourceAddress: + description: |- + DeviceRouteSourceAddress IPv4 address to set as the source hint for routes programmed by Felix. When not set + the source address for local traffic from host to workload will be determined by the kernel. + type: string + deviceRouteSourceAddressIPv6: + description: |- + DeviceRouteSourceAddressIPv6 IPv6 address to set as the source hint for routes programmed by Felix. When not set + the source address for local traffic from host to workload will be determined by the kernel. + type: string + disableConntrackInvalidCheck: + description: |- + DisableConntrackInvalidCheck disables the check for invalid connections in conntrack. While the conntrack + invalid check helps to detect malicious traffic, it can also cause issues with certain multi-NIC scenarios. + type: boolean + endpointReportingDelay: + description: |- + EndpointReportingDelay is the delay before Felix reports endpoint status to the datastore. This is only used + by the OpenStack integration. [Default: 1s] + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + endpointReportingEnabled: + description: |- + EndpointReportingEnabled controls whether Felix reports endpoint status to the datastore. This is only used + by the OpenStack integration. [Default: false] + type: boolean + endpointStatusPathPrefix: + description: |- + EndpointStatusPathPrefix is the path to the directory where endpoint status will be written. Endpoint status + file reporting is disabled if field is left empty. + + Chosen directory should match the directory used by the CNI plugin for PodStartupDelay. + [Default: /var/run/calico] + type: string + externalNodesList: + description: |- + ExternalNodesCIDRList is a list of CIDR's of external, non-Calico nodes from which VXLAN/IPIP overlay traffic + will be allowed. By default, external tunneled traffic is blocked to reduce attack surface. + items: + type: string + type: array + failsafeInboundHostPorts: + description: |- + FailsafeInboundHostPorts is a list of ProtoPort struct objects including UDP/TCP/SCTP ports and CIDRs that Felix will + allow incoming traffic to host endpoints on irrespective of the security policy. This is useful to avoid accidentally + cutting off a host with incorrect configuration. For backwards compatibility, if the protocol is not specified, + it defaults to "tcp". If a CIDR is not specified, it will allow traffic from all addresses. To disable all inbound host ports, + use the value "[]". The default value allows ssh access, DHCP, BGP, etcd and the Kubernetes API. + [Default: tcp:22, udp:68, tcp:179, tcp:2379, tcp:2380, tcp:5473, tcp:6443, tcp:6666, tcp:6667 ] + items: + description: + ProtoPort is combination of protocol, port, and CIDR. + Protocol and port must be specified. + properties: + net: + type: string + port: + type: integer + protocol: + type: string + required: + - port + type: object + type: array + failsafeOutboundHostPorts: + description: |- + FailsafeOutboundHostPorts is a list of PortProto struct objects including UDP/TCP/SCTP ports and CIDRs that Felix + will allow outgoing traffic from host endpoints to irrespective of the security policy. This is useful to avoid accidentally + cutting off a host with incorrect configuration. For backwards compatibility, if the protocol is not specified, it defaults + to "tcp". If a CIDR is not specified, it will allow traffic from all addresses. To disable all outbound host ports, + use the value "[]". The default value opens etcd's standard ports to ensure that Felix does not get cut off from etcd + as well as allowing DHCP, DNS, BGP and the Kubernetes API. + [Default: udp:53, udp:67, tcp:179, tcp:2379, tcp:2380, tcp:5473, tcp:6443, tcp:6666, tcp:6667 ] + items: + description: + ProtoPort is combination of protocol, port, and CIDR. + Protocol and port must be specified. + properties: + net: + type: string + port: + type: integer + protocol: + type: string + required: + - port + type: object + type: array + featureDetectOverride: + description: |- + FeatureDetectOverride is used to override feature detection based on auto-detected platform + capabilities. Values are specified in a comma separated list with no spaces, example; + "SNATFullyRandom=true,MASQFullyRandom=false,RestoreSupportsLock=". A value of "true" or "false" will + force enable/disable feature, empty or omitted values fall back to auto-detection. + pattern: ^([a-zA-Z0-9-_]+=(true|false|),)*([a-zA-Z0-9-_]+=(true|false|))?$ + type: string + featureGates: + description: |- + FeatureGates is used to enable or disable tech-preview Calico features. + Values are specified in a comma separated list with no spaces, example; + "BPFConnectTimeLoadBalancingWorkaround=enabled,XyZ=false". This is + used to enable features that are not fully production ready. + pattern: ^([a-zA-Z0-9-_]+=([^=]+),)*([a-zA-Z0-9-_]+=([^=]+))?$ + type: string + floatingIPs: + description: |- + FloatingIPs configures whether or not Felix will program non-OpenStack floating IP addresses. (OpenStack-derived + floating IPs are always programmed, regardless of this setting.) + enum: + - Enabled + - Disabled + type: string + flowLogsCollectorDebugTrace: + description: |- + When FlowLogsCollectorDebugTrace is set to true, enables the logs in the collector to be + printed in their entirety. + type: boolean + flowLogsFlushInterval: + description: + FlowLogsFlushInterval configures the interval at which + Felix exports flow logs. + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + flowLogsGoldmaneServer: + description: + FlowLogGoldmaneServer is the flow server endpoint to + which flow data should be published. + type: string + flowLogsLocalReporter: + description: + "FlowLogsLocalReporter configures local unix socket for + reporting flow data from each node. [Default: Disabled]" + enum: + - Disabled + - Enabled + type: string + flowLogsPolicyEvaluationMode: + description: |- + Continuous - Felix evaluates active flows on a regular basis to determine the rule + traces in the flow logs. Any policy updates that impact a flow will be reflected in the + pending_policies field, offering a near-real-time view of policy changes across flows. + None - Felix stops evaluating pending traces. + [Default: Continuous] + enum: + - None + - Continuous + type: string + genericXDPEnabled: + description: |- + GenericXDPEnabled enables Generic XDP so network cards that don't support XDP offload or driver + modes can use XDP. This is not recommended since it doesn't provide better performance than + iptables. [Default: false] + type: boolean + goGCThreshold: + description: |- + GoGCThreshold Sets the Go runtime's garbage collection threshold. I.e. the percentage that the heap is + allowed to grow before garbage collection is triggered. In general, doubling the value halves the CPU time + spent doing GC, but it also doubles peak GC memory overhead. A special value of -1 can be used + to disable GC entirely; this should only be used in conjunction with the GoMemoryLimitMB setting. + + This setting is overridden by the GOGC environment variable. + + [Default: 40] + type: integer + goMaxProcs: + description: |- + GoMaxProcs sets the maximum number of CPUs that the Go runtime will use concurrently. A value of -1 means + "use the system default"; typically the number of real CPUs on the system. + + this setting is overridden by the GOMAXPROCS environment variable. + + [Default: -1] + type: integer + goMemoryLimitMB: + description: |- + GoMemoryLimitMB sets a (soft) memory limit for the Go runtime in MB. The Go runtime will try to keep its memory + usage under the limit by triggering GC as needed. To avoid thrashing, it will exceed the limit if GC starts to + take more than 50% of the process's CPU time. A value of -1 disables the memory limit. + + Note that the memory limit, if used, must be considerably less than any hard resource limit set at the container + or pod level. This is because felix is not the only process that must run in the container or pod. + + This setting is overridden by the GOMEMLIMIT environment variable. + + [Default: -1] + type: integer + healthEnabled: + description: |- + HealthEnabled if set to true, enables Felix's health port, which provides readiness and liveness endpoints. + [Default: false] + type: boolean + healthHost: + description: + "HealthHost is the host that the health server should + bind to. [Default: localhost]" + type: string + healthPort: + description: + "HealthPort is the TCP port that the health server should + bind to. [Default: 9099]" + type: integer + healthTimeoutOverrides: + description: |- + HealthTimeoutOverrides allows the internal watchdog timeouts of individual subcomponents to be + overridden. This is useful for working around "false positive" liveness timeouts that can occur + in particularly stressful workloads or if CPU is constrained. For a list of active + subcomponents, see Felix's logs. + items: + properties: + name: + type: string + timeout: + type: string + required: + - name + - timeout + type: object + type: array + interfaceExclude: + description: |- + InterfaceExclude A comma-separated list of interface names that should be excluded when Felix is resolving + host endpoints. The default value ensures that Felix ignores Kubernetes' internal `kube-ipvs0` device. If you + want to exclude multiple interface names using a single value, the list supports regular expressions. For + regular expressions you must wrap the value with `/`. For example having values `/^kube/,veth1` will exclude + all interfaces that begin with `kube` and also the interface `veth1`. [Default: kube-ipvs0] + type: string + interfacePrefix: + description: |- + InterfacePrefix is the interface name prefix that identifies workload endpoints and so distinguishes + them from host endpoint interfaces. Note: in environments other than bare metal, the orchestrators + configure this appropriately. For example our Kubernetes and Docker integrations set the 'cali' value, + and our OpenStack integration sets the 'tap' value. [Default: cali] + type: string + interfaceRefreshInterval: + description: |- + InterfaceRefreshInterval is the period at which Felix rescans local interfaces to verify their state. + The rescan can be disabled by setting the interval to 0. + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + ipForwarding: + description: |- + IPForwarding controls whether Felix sets the host sysctls to enable IP forwarding. IP forwarding is required + when using Calico for workload networking. This should be disabled only on hosts where Calico is used solely for + host protection. In BPF mode, due to a kernel interaction, either IPForwarding must be enabled or BPFEnforceRPF + must be disabled. [Default: Enabled] + enum: + - Enabled + - Disabled + type: string + ipipEnabled: + description: |- + IPIPEnabled overrides whether Felix should configure an IPIP interface on the host. Optional as Felix + determines this based on the existing IP pools. [Default: nil (unset)] + type: boolean + ipipMTU: + description: |- + IPIPMTU controls the MTU to set on the IPIP tunnel device. Optional as Felix auto-detects the MTU based on the + MTU of the host's interfaces. [Default: 0 (auto-detect)] + type: integer + ipsetsRefreshInterval: + description: |- + IpsetsRefreshInterval controls the period at which Felix re-checks all IP sets to look for discrepancies. + Set to 0 to disable the periodic refresh. [Default: 90s] + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + iptablesBackend: + description: |- + IptablesBackend controls which backend of iptables will be used. The default is `Auto`. + + Warning: changing this on a running system can leave "orphaned" rules in the "other" backend. These + should be cleaned up to avoid confusing interactions. + enum: + - Legacy + - NFT + - Auto + pattern: ^(?i)(Auto|Legacy|NFT)?$ + type: string + iptablesFilterAllowAction: + description: |- + IptablesFilterAllowAction controls what happens to traffic that is accepted by a Felix policy chain in the + iptables filter table (which is used for "normal" policy). The default will immediately `Accept` the traffic. Use + `Return` to send the traffic back up to the system chains for further processing. + pattern: ^(?i)(Accept|Return)?$ + type: string + iptablesFilterDenyAction: + description: |- + IptablesFilterDenyAction controls what happens to traffic that is denied by network policy. By default Calico blocks traffic + with an iptables "DROP" action. If you want to use "REJECT" action instead you can configure it in here. + pattern: ^(?i)(Drop|Reject)?$ + type: string + iptablesLockProbeInterval: + description: |- + IptablesLockProbeInterval configures the interval between attempts to claim + the xtables lock. Shorter intervals are more responsive but use more CPU. [Default: 50ms] + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + iptablesMangleAllowAction: + description: |- + IptablesMangleAllowAction controls what happens to traffic that is accepted by a Felix policy chain in the + iptables mangle table (which is used for "pre-DNAT" policy). The default will immediately `Accept` the traffic. + Use `Return` to send the traffic back up to the system chains for further processing. + pattern: ^(?i)(Accept|Return)?$ + type: string + iptablesMarkMask: + description: |- + IptablesMarkMask is the mask that Felix selects its IPTables Mark bits from. Should be a 32 bit hexadecimal + number with at least 8 bits set, none of which clash with any other mark bits in use on the system. + [Default: 0xffff0000] + format: int32 + type: integer + iptablesNATOutgoingInterfaceFilter: + description: |- + This parameter can be used to limit the host interfaces on which Calico will apply SNAT to traffic leaving a + Calico IPAM pool with "NAT outgoing" enabled. This can be useful if you have a main data interface, where + traffic should be SNATted and a secondary device (such as the docker bridge) which is local to the host and + doesn't require SNAT. This parameter uses the iptables interface matching syntax, which allows + as a + wildcard. Most users will not need to set this. Example: if your data interfaces are eth0 and eth1 and you + want to exclude the docker bridge, you could set this to eth+ + type: string + iptablesPostWriteCheckInterval: + description: |- + IptablesPostWriteCheckInterval is the period after Felix has done a write + to the dataplane that it schedules an extra read back in order to check the write was not + clobbered by another process. This should only occur if another application on the system + doesn't respect the iptables lock. [Default: 1s] + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + iptablesRefreshInterval: + description: |- + IptablesRefreshInterval is the period at which Felix re-checks the IP sets + in the dataplane to ensure that no other process has accidentally broken Calico's rules. + Set to 0 to disable IP sets refresh. Note: the default for this value is lower than the + other refresh intervals as a workaround for a Linux kernel bug that was fixed in kernel + version 4.11. If you are using v4.11 or greater you may want to set this to, a higher value + to reduce Felix CPU usage. [Default: 10s] + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + ipv6Support: + description: + IPv6Support controls whether Felix enables support for + IPv6 (if supported by the in-use dataplane). + type: boolean + kubeNodePortRanges: + description: |- + KubeNodePortRanges holds list of port ranges used for service node ports. Only used if felix detects kube-proxy running in ipvs mode. + Felix uses these ranges to separate host and workload traffic. [Default: 30000:32767]. + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + logActionRateLimit: + description: |- + LogActionRateLimit sets the rate of hitting a Log action. The value must be in the format "N/unit", + where N is a number and unit is one of: second, minute, hour, or day. For example: "10/second" or "100/hour". + pattern: ^[1-9]\d{0,3}/(?:second|minute|hour|day)$ + type: string + logActionRateLimitBurst: + description: + LogActionRateLimitBurst sets the rate limit burst of + hitting a Log action when LogActionRateLimit is enabled. + maximum: 9999 + minimum: 0 + type: integer + logDebugFilenameRegex: + description: |- + LogDebugFilenameRegex controls which source code files have their Debug log output included in the logs. + Only logs from files with names that match the given regular expression are included. The filter only applies + to Debug level logs. + type: string + logFilePath: + description: + "LogFilePath is the full path to the Felix log. Set to + none to disable file logging. [Default: /var/log/calico/felix.log]" + type: string + logPrefix: + description: |- + LogPrefix is the log prefix that Felix uses when rendering LOG rules. It is possible to use the following specifiers + to include extra information in the log prefix. + - %t: Tier name. + - %k: Kind (short names). + - %n: Policy or profile name. + - %p: Policy or profile name (namespace/name for namespaced kinds or just name for non namespaced kinds). + Calico includes ": " characters at the end of the generated log prefix. + Note that iptables shows up to 29 characters for the log prefix and nftables up to 127 characters. Extra characters are truncated. + [Default: calico-packet] + pattern: "^([a-zA-Z0-9%: /_-])*$" + type: string + logSeverityFile: + description: + "LogSeverityFile is the log severity above which logs + are sent to the log file. [Default: Info]" + pattern: ^(?i)(Trace|Debug|Info|Warning|Error|Fatal)?$ + type: string + logSeverityScreen: + description: + "LogSeverityScreen is the log severity above which logs + are sent to the stdout. [Default: Info]" + pattern: ^(?i)(Trace|Debug|Info|Warning|Error|Fatal)?$ + type: string + logSeveritySys: + description: |- + LogSeveritySys is the log severity above which logs are sent to the syslog. Set to None for no logging to syslog. + [Default: Info] + pattern: ^(?i)(Trace|Debug|Info|Warning|Error|Fatal)?$ + type: string + maxIpsetSize: + description: |- + MaxIpsetSize is the maximum number of IP addresses that can be stored in an IP set. Not applicable + if using the nftables backend. + type: integer + metadataAddr: + description: |- + MetadataAddr is the IP address or domain name of the server that can answer VM queries for + cloud-init metadata. In OpenStack, this corresponds to the machine running nova-api (or in + Ubuntu, nova-api-metadata). A value of none (case-insensitive) means that Felix should not + set up any NAT rule for the metadata path. [Default: 127.0.0.1] + type: string + metadataPort: + description: |- + MetadataPort is the port of the metadata server. This, combined with global.MetadataAddr (if + not 'None'), is used to set up a NAT rule, from 169.254.169.254:80 to MetadataAddr:MetadataPort. + In most cases this should not need to be changed [Default: 8775]. + type: integer + mtuIfacePattern: + description: |- + MTUIfacePattern is a regular expression that controls which interfaces Felix should scan in order + to calculate the host's MTU. + This should not match workload interfaces (usually named cali...). + type: string + natOutgoingAddress: + description: |- + NATOutgoingAddress specifies an address to use when performing source NAT for traffic in a natOutgoing pool that + is leaving the network. By default the address used is an address on the interface the traffic is leaving on + (i.e. it uses the iptables MASQUERADE target). + type: string + natOutgoingExclusions: + description: |- + When a IP pool setting `natOutgoing` is true, packets sent from Calico networked containers in this IP pool to destinations will be masqueraded. + Configure which type of destinations is excluded from being masqueraded. + - IPPoolsOnly: destinations outside of this IP pool will be masqueraded. + - IPPoolsAndHostIPs: destinations outside of this IP pool and all hosts will be masqueraded. + [Default: IPPoolsOnly] + enum: + - IPPoolsOnly + - IPPoolsAndHostIPs + type: string + natPortRange: + anyOf: + - type: integer + - type: string + description: |- + NATPortRange specifies the range of ports that is used for port mapping when doing outgoing NAT. When unset the default behavior of the + network stack is used. + pattern: ^.* + x-kubernetes-int-or-string: true + netlinkTimeout: + description: |- + NetlinkTimeout is the timeout when talking to the kernel over the netlink protocol, used for programming + routes, rules, and other kernel objects. [Default: 10s] + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + nftablesFilterAllowAction: + description: |- + NftablesFilterAllowAction controls the nftables action that Felix uses to represent the "allow" policy verdict + in the filter table. The default is to `ACCEPT` the traffic, which is a terminal action. Alternatively, + `RETURN` can be used to return the traffic back to the top-level chain for further processing by your rules. + pattern: ^(?i)(Accept|Return)?$ + type: string + nftablesFilterDenyAction: + description: |- + NftablesFilterDenyAction controls what happens to traffic that is denied by network policy. By default, Calico + blocks traffic with a "drop" action. If you want to use a "reject" action instead you can configure it here. + pattern: ^(?i)(Drop|Reject)?$ + type: string + nftablesMangleAllowAction: + description: |- + NftablesMangleAllowAction controls the nftables action that Felix uses to represent the "allow" policy verdict + in the mangle table. The default is to `ACCEPT` the traffic, which is a terminal action. Alternatively, + `RETURN` can be used to return the traffic back to the top-level chain for further processing by your rules. + pattern: ^(?i)(Accept|Return)?$ + type: string + nftablesMarkMask: + description: |- + NftablesMarkMask is the mask that Felix selects its nftables Mark bits from. Should be a 32 bit hexadecimal + number with at least 8 bits set, none of which clash with any other mark bits in use on the system. + [Default: 0xffff0000] + format: int32 + type: integer + nftablesMode: + default: Auto + description: + "NFTablesMode configures nftables support in Felix. [Default: + Auto]" + enum: + - Disabled + - Enabled + - Auto + type: string + nftablesRefreshInterval: + description: + "NftablesRefreshInterval controls the interval at which + Felix periodically refreshes the nftables rules. [Default: 90s]" + type: string + openstackRegion: + description: |- + OpenstackRegion is the name of the region that a particular Felix belongs to. In a multi-region + Calico/OpenStack deployment, this must be configured somehow for each Felix (here in the datamodel, + or in felix.cfg or the environment on each compute node), and must match the [calico] + openstack_region value configured in neutron.conf on each node. [Default: Empty] + type: string + policySyncPathPrefix: + description: |- + PolicySyncPathPrefix is used to by Felix to communicate policy changes to external services, + like Application layer policy. [Default: Empty] + type: string + programClusterRoutes: + description: |- + ProgramClusterRoutes specifies whether Felix should program IPIP routes instead of BIRD. + Felix always programs VXLAN routes. [Default: Disabled] + enum: + - Enabled + - Disabled + type: string + prometheusGoMetricsEnabled: + description: |- + PrometheusGoMetricsEnabled disables Go runtime metrics collection, which the Prometheus client does by default, when + set to false. This reduces the number of metrics reported, reducing Prometheus load. [Default: true] + type: boolean + prometheusMetricsCAFile: + description: |- + PrometheusMetricsCAFile defines the absolute path to the TLS CA certificate file used for securing the /metrics endpoint. + This certificate must be valid and accessible by the calico-node process. + type: string + prometheusMetricsCertFile: + description: |- + PrometheusMetricsCertFile defines the absolute path to the TLS certificate file used for securing the /metrics endpoint. + This certificate must be valid and accessible by the calico-node process. + type: string + prometheusMetricsClientAuth: + description: |- + PrometheusMetricsClientAuth specifies the client authentication type for the /metrics endpoint. + This determines how the server validates client certificates. Default is "RequireAndVerifyClientCert". + type: string + prometheusMetricsEnabled: + description: + "PrometheusMetricsEnabled enables the Prometheus metrics + server in Felix if set to true. [Default: false]" + type: boolean + prometheusMetricsHost: + description: + "PrometheusMetricsHost is the host that the Prometheus + metrics server should bind to. [Default: empty]" + type: string + prometheusMetricsKeyFile: + description: |- + PrometheusMetricsKeyFile defines the absolute path to the private key file corresponding to the TLS certificate + used for securing the /metrics endpoint. The private key must be valid and accessible by the calico-node process. + type: string + prometheusMetricsPort: + description: + "PrometheusMetricsPort is the TCP port that the Prometheus + metrics server should bind to. [Default: 9091]" + type: integer + prometheusProcessMetricsEnabled: + description: |- + PrometheusProcessMetricsEnabled disables process metrics collection, which the Prometheus client does by default, when + set to false. This reduces the number of metrics reported, reducing Prometheus load. [Default: true] + type: boolean + prometheusWireGuardMetricsEnabled: + description: |- + PrometheusWireGuardMetricsEnabled disables wireguard metrics collection, which the Prometheus client does by default, when + set to false. This reduces the number of metrics reported, reducing Prometheus load. [Default: true] + type: boolean + removeExternalRoutes: + description: |- + RemoveExternalRoutes Controls whether Felix will remove unexpected routes to workload interfaces. Felix will + always clean up expected routes that use the configured DeviceRouteProtocol. To add your own routes, you must + use a distinct protocol (in addition to setting this field to false). + type: boolean + reportingInterval: + description: |- + ReportingInterval is the interval at which Felix reports its status into the datastore or 0 to disable. + Must be non-zero in OpenStack deployments. [Default: 30s] + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + reportingTTL: + description: + "ReportingTTL is the time-to-live setting for process-wide + status reports. [Default: 90s]" + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + requireMTUFile: + description: |- + RequireMTUFile specifies whether mtu file is required to start the felix. + Optional as to keep the same as previous behavior. [Default: false] + type: boolean + routeRefreshInterval: + description: |- + RouteRefreshInterval is the period at which Felix re-checks the routes + in the dataplane to ensure that no other process has accidentally broken Calico's rules. + Set to 0 to disable route refresh. [Default: 90s] + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + routeSource: + description: |- + RouteSource configures where Felix gets its routing information. + - WorkloadIPs: use workload endpoints to construct routes. + - CalicoIPAM: the default - use IPAM data to construct routes. + pattern: ^(?i)(WorkloadIPs|CalicoIPAM)?$ + type: string + routeSyncDisabled: + description: |- + RouteSyncDisabled will disable all operations performed on the route table. Set to true to + run in network-policy mode only. + type: boolean + routeTableRange: + description: |- + Deprecated in favor of RouteTableRanges. + Calico programs additional Linux route tables for various purposes. + RouteTableRange specifies the indices of the route tables that Calico should use. + properties: + max: + type: integer + min: + type: integer + required: + - max + - min + type: object + routeTableRanges: + description: |- + Calico programs additional Linux route tables for various purposes. + RouteTableRanges specifies a set of table index ranges that Calico should use. + Deprecates`RouteTableRange`, overrides `RouteTableRange`. + items: + properties: + max: + type: integer + min: + type: integer + required: + - max + - min + type: object + type: array + serviceLoopPrevention: + description: |- + When service IP advertisement is enabled, prevent routing loops to service IPs that are + not in use, by dropping or rejecting packets that do not get DNAT'd by kube-proxy. + Unless set to "Disabled", in which case such routing loops continue to be allowed. + [Default: Drop] + pattern: ^(?i)(Drop|Reject|Disabled)?$ + type: string + sidecarAccelerationEnabled: + description: + "SidecarAccelerationEnabled enables experimental sidecar + acceleration [Default: false]" + type: boolean + usageReportingEnabled: + description: |- + UsageReportingEnabled reports anonymous Calico version number and cluster size to projectcalico.org. Logs warnings returned by the usage + server. For example, if a significant security vulnerability has been discovered in the version of Calico being used. [Default: true] + type: boolean + usageReportingInitialDelay: + description: + "UsageReportingInitialDelay controls the minimum delay + before Felix makes a report. [Default: 300s]" + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + usageReportingInterval: + description: + "UsageReportingInterval controls the interval at which + Felix makes reports. [Default: 86400s]" + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + useInternalDataplaneDriver: + description: |- + UseInternalDataplaneDriver, if true, Felix will use its internal dataplane programming logic. If false, it + will launch an external dataplane driver and communicate with it over protobuf. + type: boolean + vxlanEnabled: + description: |- + VXLANEnabled overrides whether Felix should create the VXLAN tunnel device for IPv4 VXLAN networking. + Optional as Felix determines this based on the existing IP pools. [Default: nil (unset)] + type: boolean + vxlanMTU: + description: |- + VXLANMTU is the MTU to set on the IPv4 VXLAN tunnel device. Optional as Felix auto-detects the MTU based on the + MTU of the host's interfaces. [Default: 0 (auto-detect)] + type: integer + vxlanMTUV6: + description: |- + VXLANMTUV6 is the MTU to set on the IPv6 VXLAN tunnel device. Optional as Felix auto-detects the MTU based on the + MTU of the host's interfaces. [Default: 0 (auto-detect)] + type: integer + vxlanPort: + description: + "VXLANPort is the UDP port number to use for VXLAN traffic. + [Default: 4789]" + type: integer + vxlanVNI: + description: |- + VXLANVNI is the VXLAN VNI to use for VXLAN traffic. You may need to change this if the default value is + in use on your system. [Default: 4096] + type: integer + windowsManageFirewallRules: + description: + "WindowsManageFirewallRules configures whether or not + Felix will program Windows Firewall rules (to allow inbound access + to its own metrics ports). [Default: Disabled]" + enum: + - Enabled + - Disabled + type: string + wireguardEnabled: + description: + "WireguardEnabled controls whether Wireguard is enabled + for IPv4 (encapsulating IPv4 traffic over an IPv4 underlay network). + [Default: false]" + type: boolean + wireguardEnabledV6: + description: + "WireguardEnabledV6 controls whether Wireguard is enabled + for IPv6 (encapsulating IPv6 traffic over an IPv6 underlay network). + [Default: false]" + type: boolean + wireguardHostEncryptionEnabled: + description: + "WireguardHostEncryptionEnabled controls whether Wireguard + host-to-host encryption is enabled. [Default: false]" + type: boolean + wireguardInterfaceName: + description: + "WireguardInterfaceName specifies the name to use for + the IPv4 Wireguard interface. [Default: wireguard.cali]" + type: string + wireguardInterfaceNameV6: + description: + "WireguardInterfaceNameV6 specifies the name to use for + the IPv6 Wireguard interface. [Default: wg-v6.cali]" + type: string + wireguardKeepAlive: + description: + "WireguardPersistentKeepAlive controls Wireguard PersistentKeepalive + option. Set 0 to disable. [Default: 0]" + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + wireguardListeningPort: + description: + "WireguardListeningPort controls the listening port used + by IPv4 Wireguard. [Default: 51820]" + type: integer + wireguardListeningPortV6: + description: + "WireguardListeningPortV6 controls the listening port + used by IPv6 Wireguard. [Default: 51821]" + type: integer + wireguardMTU: + description: + "WireguardMTU controls the MTU on the IPv4 Wireguard + interface. See Configuring MTU [Default: 1440]" + type: integer + wireguardMTUV6: + description: + "WireguardMTUV6 controls the MTU on the IPv6 Wireguard + interface. See Configuring MTU [Default: 1420]" + type: integer + wireguardRoutingRulePriority: + description: + "WireguardRoutingRulePriority controls the priority value + to use for the Wireguard routing rule. [Default: 99]" + type: integer + wireguardThreadingEnabled: + description: |- + WireguardThreadingEnabled controls whether Wireguard has Threaded NAPI enabled. [Default: false] + This increases the maximum number of packets a Wireguard interface can process. + Consider threaded NAPI only if you have high packets per second workloads that are causing dropping packets due to a saturated `softirq` CPU core. + There is a [known issue](https://lore.kernel.org/netdev/CALrw=nEoT2emQ0OAYCjM1d_6Xe_kNLSZ6dhjb5FxrLFYh4kozA@mail.gmail.com/T/) with this setting + that may cause NAPI to get stuck holding the global `rtnl_mutex` when a peer is removed. + Workaround: Make sure your Linux kernel [includes this patch](https://github.com/torvalds/linux/commit/56364c910691f6d10ba88c964c9041b9ab777bd6) to unwedge NAPI. + type: boolean + workloadSourceSpoofing: + description: |- + WorkloadSourceSpoofing controls whether pods can use the allowedSourcePrefixes annotation to send traffic with a source IP + address that is not theirs. This is disabled by default. When set to "Any", pods can request any prefix. + pattern: ^(?i)(Disabled|Any)?$ + type: string + xdpEnabled: + description: + "XDPEnabled enables XDP acceleration for suitable untracked + incoming deny rules. [Default: true]" + type: boolean + xdpRefreshInterval: + description: |- + XDPRefreshInterval is the period at which Felix re-checks all XDP state to ensure that no + other process has accidentally broken Calico's BPF maps or attached programs. Set to 0 to + disable XDP refresh. [Default: 90s] + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + type: object + type: object + served: true + storage: true +--- +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_globalnetworkpolicies.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: globalnetworkpolicies.crd.projectcalico.org +spec: + group: crd.projectcalico.org + names: + kind: GlobalNetworkPolicy + listKind: GlobalNetworkPolicyList + plural: globalnetworkpolicies + singular: globalnetworkpolicy + preserveUnknownFields: false + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + applyOnForward: + type: boolean + doNotTrack: + type: boolean + egress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: + properties: + exact: + type: string + prefix: + type: string + type: object + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: + enum: + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + required: + - action + type: object + type: array + ingress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: + properties: + exact: + type: string + prefix: + type: string + type: object + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: + enum: + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + required: + - action + type: object + type: array + namespaceSelector: + type: string + order: + type: number + performanceHints: + items: + enum: + - AssumeNeededOnEveryNode + type: string + type: array + preDNAT: + type: boolean + selector: + type: string + serviceAccountSelector: + type: string + tier: + default: default + type: string + types: + items: + enum: + - Ingress + - Egress + type: string + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-list-type: set + type: object + type: object + served: true + storage: true +--- +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_globalnetworksets.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: globalnetworksets.crd.projectcalico.org +spec: + group: crd.projectcalico.org + names: + kind: GlobalNetworkSet + listKind: GlobalNetworkSetList + plural: globalnetworksets + singular: globalnetworkset + preserveUnknownFields: false + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + type: object + type: object + served: true + storage: true +--- +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_hostendpoints.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: hostendpoints.crd.projectcalico.org +spec: + group: crd.projectcalico.org + names: + kind: HostEndpoint + listKind: HostEndpointList + plural: hostendpoints + singular: hostendpoint + preserveUnknownFields: false + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + expectedIPs: + items: + type: string + type: array + x-kubernetes-list-type: set + interfaceName: + type: string + node: + type: string + ports: + items: + properties: + name: + type: string + port: + maximum: 65535 + minimum: 0 + type: integer + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + required: + - name + - port + - protocol + type: object + type: array + profiles: + items: + type: string + type: array + x-kubernetes-list-type: set + type: object + type: object + served: true + storage: true +--- +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_ipamblocks.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: ipamblocks.crd.projectcalico.org +spec: + group: crd.projectcalico.org + names: + kind: IPAMBlock + listKind: IPAMBlockList + plural: ipamblocks + singular: ipamblock + preserveUnknownFields: false + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + affinity: + type: string + affinityClaimTime: + format: date-time + type: string + allocations: + items: + type: integer + # TODO: This nullable is manually added in. We should update controller-gen + # to handle []*int properly itself. + nullable: true + type: array + attributes: + items: + properties: + alternate: + additionalProperties: + type: string + type: object + handle_id: + type: string + secondary: + additionalProperties: + type: string + type: object + type: object + type: array + cidr: + type: string + deleted: + type: boolean + sequenceNumber: + default: 0 + format: int64 + type: integer + sequenceNumberForAllocation: + additionalProperties: + format: int64 + type: integer + type: object + strictAffinity: + type: boolean + unallocated: + items: + type: integer + type: array + required: + - allocations + - attributes + - cidr + - strictAffinity + - unallocated + type: object + type: object + served: true + storage: true +--- +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_ipamconfigs.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: ipamconfigs.crd.projectcalico.org +spec: + group: crd.projectcalico.org + names: + kind: IPAMConfig + listKind: IPAMConfigList + plural: ipamconfigs + singular: ipamconfig + preserveUnknownFields: false + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + autoAllocateBlocks: + type: boolean + kubeVirtVMAddressPersistence: + enum: + - Enabled + - Disabled + type: string + maxBlocksPerHost: + maximum: 2147483647 + minimum: 0 + type: integer + strictAffinity: + type: boolean + required: + - autoAllocateBlocks + - strictAffinity + type: object + type: object + served: true + storage: true +--- +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_ipamhandles.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: ipamhandles.crd.projectcalico.org +spec: + group: crd.projectcalico.org + names: + kind: IPAMHandle + listKind: IPAMHandleList + plural: ipamhandles + singular: ipamhandle + preserveUnknownFields: false + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + block: + additionalProperties: + type: integer + type: object + deleted: + type: boolean + handleID: + type: string + required: + - block + - handleID + type: object + type: object + served: true + storage: true +--- +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_ippools.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: ippools.crd.projectcalico.org +spec: + group: crd.projectcalico.org + names: + kind: IPPool + listKind: IPPoolList + plural: ippools + singular: ippool + preserveUnknownFields: false + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + allowedUses: + items: + enum: + - Workload + - Tunnel + - LoadBalancer + type: string + type: array + x-kubernetes-list-type: set + assignmentMode: + default: Automatic + enum: + - Automatic + - Manual + type: string + blockSize: + maximum: 128 + minimum: 0 + type: integer + cidr: + format: cidr + type: string + disableBGPExport: + type: boolean + disabled: + type: boolean + ipipMode: + enum: + - Never + - Always + - CrossSubnet + type: string + namespaceSelector: + type: string + natOutgoing: + type: boolean + nodeSelector: + type: string + vxlanMode: + enum: + - Never + - Always + - CrossSubnet + type: string + required: + - cidr + type: object + type: object + served: true + storage: true +--- +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_ipreservations.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: ipreservations.crd.projectcalico.org +spec: + group: crd.projectcalico.org + names: + kind: IPReservation + listKind: IPReservationList + plural: ipreservations + singular: ipreservation + preserveUnknownFields: false + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + reservedCIDRs: + format: cidr + items: + type: string + type: array + x-kubernetes-list-type: set + type: object + type: object + served: true + storage: true +--- +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_kubecontrollersconfigurations.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: kubecontrollersconfigurations.crd.projectcalico.org +spec: + group: crd.projectcalico.org + names: + kind: KubeControllersConfiguration + listKind: KubeControllersConfigurationList + plural: kubecontrollersconfigurations + singular: kubecontrollersconfiguration + preserveUnknownFields: false + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + controllers: + properties: + loadBalancer: + properties: + assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly + type: string + type: object + namespace: + properties: + reconcilerPeriod: + type: string + type: object + node: + properties: + hostEndpoint: + properties: + autoCreate: + enum: + - Enabled + - Disabled + type: string + createDefaultHostEndpoint: + type: string + templates: + items: + properties: + generateName: + maxLength: 253 + type: string + interfaceCIDRs: + items: + type: string + type: array + x-kubernetes-list-type: set + interfacePattern: + type: string + labels: + additionalProperties: + type: string + type: object + nodeSelector: + type: string + type: object + type: array + type: object + leakGracePeriod: + type: string + reconcilerPeriod: + type: string + syncLabels: + enum: + - Enabled + - Disabled + type: string + type: object + policy: + properties: + reconcilerPeriod: + type: string + type: object + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object + serviceAccount: + properties: + reconcilerPeriod: + type: string + type: object + workloadEndpoint: + properties: + reconcilerPeriod: + type: string + type: object + type: object + debugProfilePort: + format: int32 + maximum: 65535 + minimum: 0 + type: integer + etcdV3CompactionPeriod: + type: string + healthChecks: + default: Enabled + enum: + - Enabled + - Disabled + type: string + logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic + type: string + prometheusMetricsPort: + maximum: 65535 + minimum: 0 + type: integer + required: + - controllers + type: object + status: + properties: + environmentVars: + additionalProperties: + type: string + type: object + runningConfig: + properties: + controllers: + properties: + loadBalancer: + properties: + assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly + type: string + type: object + namespace: + properties: + reconcilerPeriod: + type: string + type: object + node: + properties: + hostEndpoint: + properties: + autoCreate: + enum: + - Enabled + - Disabled + type: string + createDefaultHostEndpoint: + type: string + templates: + items: + properties: + generateName: + maxLength: 253 + type: string + interfaceCIDRs: + items: + type: string + type: array + x-kubernetes-list-type: set + interfacePattern: + type: string + labels: + additionalProperties: + type: string + type: object + nodeSelector: + type: string + type: object + type: array + type: object + leakGracePeriod: + type: string + reconcilerPeriod: + type: string + syncLabels: + enum: + - Enabled + - Disabled + type: string + type: object + policy: + properties: + reconcilerPeriod: + type: string + type: object + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object + serviceAccount: + properties: + reconcilerPeriod: + type: string + type: object + workloadEndpoint: + properties: + reconcilerPeriod: + type: string + type: object + type: object + debugProfilePort: + format: int32 + maximum: 65535 + minimum: 0 + type: integer + etcdV3CompactionPeriod: + type: string + healthChecks: + default: Enabled + enum: + - Enabled + - Disabled + type: string + logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic + type: string + prometheusMetricsPort: + maximum: 65535 + minimum: 0 + type: integer + required: + - controllers + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_networkpolicies.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: networkpolicies.crd.projectcalico.org +spec: + group: crd.projectcalico.org + names: + kind: NetworkPolicy + listKind: NetworkPolicyList + plural: networkpolicies + singular: networkpolicy + preserveUnknownFields: false + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + egress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: + properties: + exact: + type: string + prefix: + type: string + type: object + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: + enum: + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + required: + - action + type: object + type: array + ingress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: + properties: + exact: + type: string + prefix: + type: string + type: object + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: + enum: + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + required: + - action + type: object + type: array + order: + type: number + performanceHints: + items: + enum: + - AssumeNeededOnEveryNode + type: string + type: array + selector: + type: string + serviceAccountSelector: + type: string + tier: + default: default + type: string + types: + items: + enum: + - Ingress + - Egress + type: string + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-list-type: set + type: object + type: object + served: true + storage: true +--- +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_networksets.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: networksets.crd.projectcalico.org +spec: + group: crd.projectcalico.org + names: + kind: NetworkSet + listKind: NetworkSetList + plural: networksets + singular: networkset + preserveUnknownFields: false + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + type: object + type: object + served: true + storage: true +--- +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_stagedglobalnetworkpolicies.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: stagedglobalnetworkpolicies.crd.projectcalico.org +spec: + group: crd.projectcalico.org + names: + kind: StagedGlobalNetworkPolicy + listKind: StagedGlobalNetworkPolicyList + plural: stagedglobalnetworkpolicies + singular: stagedglobalnetworkpolicy + preserveUnknownFields: false + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + applyOnForward: + type: boolean + doNotTrack: + type: boolean + egress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: + properties: + exact: + type: string + prefix: + type: string + type: object + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: + enum: + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + required: + - action + type: object + type: array + ingress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: + properties: + exact: + type: string + prefix: + type: string + type: object + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: + enum: + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + required: + - action + type: object + type: array + namespaceSelector: + type: string + order: + type: number + performanceHints: + items: + enum: + - AssumeNeededOnEveryNode + type: string + type: array + preDNAT: + type: boolean + selector: + type: string + serviceAccountSelector: + type: string + stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore + type: string + tier: + default: default + type: string + types: + items: + enum: + - Ingress + - Egress + type: string + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-list-type: set + type: object + type: object + served: true + storage: true +--- +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_stagedkubernetesnetworkpolicies.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: stagedkubernetesnetworkpolicies.crd.projectcalico.org +spec: + group: crd.projectcalico.org + names: + kind: StagedKubernetesNetworkPolicy + listKind: StagedKubernetesNetworkPolicyList + plural: stagedkubernetesnetworkpolicies + singular: stagedkubernetesnetworkpolicy + preserveUnknownFields: false + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + egress: + items: + properties: + ports: + items: + properties: + endPort: + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + protocol: + type: string + type: object + type: array + x-kubernetes-list-type: atomic + to: + items: + properties: + ipBlock: + properties: + cidr: + type: string + except: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - cidr + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: array + ingress: + items: + properties: + from: + items: + properties: + ipBlock: + properties: + cidr: + type: string + except: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - cidr + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + ports: + items: + properties: + endPort: + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + protocol: + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: array + podSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + policyTypes: + items: + type: string + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-list-type: set + stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore + type: string + type: object + type: object + served: true + storage: true +--- +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_stagednetworkpolicies.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: stagednetworkpolicies.crd.projectcalico.org +spec: + group: crd.projectcalico.org + names: + kind: StagedNetworkPolicy + listKind: StagedNetworkPolicyList + plural: stagednetworkpolicies + singular: stagednetworkpolicy + preserveUnknownFields: false + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + egress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: + properties: + exact: + type: string + prefix: + type: string + type: object + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: + enum: + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + required: + - action + type: object + type: array + ingress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: + properties: + exact: + type: string + prefix: + type: string + type: object + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: + enum: + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + required: + - action + type: object + type: array + order: + type: number + performanceHints: + items: + enum: + - AssumeNeededOnEveryNode + type: string + type: array + selector: + type: string + serviceAccountSelector: + type: string + stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore + type: string + tier: + default: default + type: string + types: + items: + enum: + - Ingress + - Egress + type: string + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-list-type: set + type: object + type: object + served: true + storage: true +--- +# Source: crd.projectcalico.org.v1/templates/calico/crd.projectcalico.org_tiers.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: tiers.crd.projectcalico.org +spec: + group: crd.projectcalico.org + names: + kind: Tier + listKind: TierList + plural: tiers + singular: tier + preserveUnknownFields: false + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + defaultAction: + allOf: + - enum: + - Allow + - Deny + - Log + - Pass + - enum: + - Pass + - Deny + type: string + order: + type: number + type: object + required: + - metadata + - spec + type: object + x-kubernetes-validations: + - message: The 'kube-admin' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-admin' ? self.spec.defaultAction == + 'Pass' : true" + - message: The 'kube-baseline' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-baseline' ? self.spec.defaultAction + == 'Pass' : true" + - message: The 'default' tier must have default action 'Deny' + rule: + "self.metadata.name == 'default' ? self.spec.defaultAction == 'Deny' + : true" + served: true + storage: true +--- +# Source: crd.projectcalico.org.v1/templates/calico/policy.networking.k8s.io_clusternetworkpolicies.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/network-policy-api/pull/300 + policy.networking.k8s.io/bundle-version: v0.1.7 + policy.networking.k8s.io/channel: standard + name: clusternetworkpolicies.policy.networking.k8s.io +spec: + group: policy.networking.k8s.io + names: + kind: ClusterNetworkPolicy + listKind: ClusterNetworkPolicyList + plural: clusternetworkpolicies + shortNames: + - cnp + singular: clusternetworkpolicy + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.tier + name: Tier + type: string + - jsonPath: .spec.priority + name: Priority + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: ClusterNetworkPolicy is a cluster-wide network policy resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired behavior of ClusterNetworkPolicy. + properties: + egress: + description: |- + Egress is the list of Egress rules to be applied to the selected pods. + + A maximum of 25 rules is allowed in this block. + + The relative precedence of egress rules within a single CNP object + (all of which share the priority) will be determined by the order + in which the rule is written. + Thus, a rule that appears at the top of the egress rules + would take the highest precedence. + CNPs with no egress rules do not affect egress traffic. + items: + description: |- + ClusterNetworkPolicyEgressRule describes an action to take on a particular + set of traffic originating from pods selected by a ClusterNetworkPolicy's + Subject field. + + + properties: + action: + description: |- + Action specifies the effect this rule will have on matching + traffic. Currently the following actions are supported: + + - Accept: Accepts the selected traffic, allowing it to + egress. No further ClusterNetworkPolicy or NetworkPolicy + rules will be processed. + + - Deny: Drops the selected traffic. No further + ClusterNetworkPolicy or NetworkPolicy rules will be + processed. + + - Pass: Skips all further ClusterNetworkPolicy rules in the + current tier for the selected traffic, and passes + evaluation to the next tier. + enum: + - Accept + - Deny + - Pass + type: string + name: + description: |- + Name is an identifier for this rule, that may be no more than + 100 characters in length. This field should be used by the implementation + to help improve observability, readability and error-reporting + for any applied policies. + maxLength: 100 + type: string + protocols: + description: |- + Protocols allows for more fine-grain matching of traffic on + protocol-specific attributes such as the port. If + unspecified, protocol-specific attributes will not be used + to match traffic. + items: + description: |- + ClusterNetworkPolicyProtocol describes additional protocol-specific match rules. + Exactly one field must be set. + maxProperties: 1 + minProperties: 1 + properties: + destinationNamedPort: + description: |- + DestinationNamedPort selects a destination port on a pod based on the + ContainerPort name. You can't use this in a rule that targets resources + without named ports (e.g. Nodes or Networks). + type: string + sctp: + description: SCTP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + tcp: + description: TCP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + udp: + description: UDP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + type: object + maxItems: 25 + minItems: 1 + type: array + to: + description: |- + To is the list of destinations whose traffic this rule applies to. If any + element matches the destination of outgoing traffic then the specified + action is applied. This field must be defined and contain at least one + item. + items: + description: |- + ClusterNetworkPolicyEgressPeer defines a peer to allow traffic to. + + Exactly one of the fields must be set for a given peer and this is enforced + by the validation rules on the CRD. If an implementation sees no fields are + set then it can infer that the deployed CRD is of an incompatible version + with an unknown field. In that case it should fail closed. + + For "Accept" rules, "fail closed" means: "treat the rule as matching no + traffic". For "Deny" and "Pass" rules, "fail closed" means: "treat the rule + as a 'Deny all' rule". + maxProperties: 1 + minProperties: 1 + properties: + namespaces: + description: |- + Namespaces defines a way to select all pods within a set of Namespaces. + Note that host-networked pods are not included in this type of peer. + properties: + matchExpressions: + description: + matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + networks: + description: |- + Networks defines a way to select peers via CIDR blocks. + This is intended for representing entities that live outside the cluster, + which can't be selected by pods, namespaces and nodes peers, but note + that cluster-internal traffic will be checked against the rule as + well. So if you Accept or Deny traffic to `"0.0.0.0/0"`, that will allow + or deny all IPv4 pod-to-pod traffic as well. If you don't want that, + add a rule that Passes all pod traffic before the Networks rule. + + Each item in Networks should be provided in the CIDR format and should be + IPv4 or IPv6, for example "10.0.0.0/8" or "fd00::/8". + + Networks can have up to 25 CIDRs specified. + items: + description: |- + CIDR is an IP address range in CIDR notation + (for example, "10.0.0.0/8" or "fd00::/8"). + maxLength: 43 + type: string + x-kubernetes-validations: + - message: Invalid CIDR format provided + rule: isCIDR(self) + maxItems: 25 + minItems: 1 + type: array + x-kubernetes-list-type: set + pods: + description: |- + Pods defines a way to select a set of pods in + a set of namespaces. Note that host-networked pods + are not included in this type of peer. + properties: + namespaceSelector: + description: |- + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. + properties: + matchExpressions: + description: + matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + description: |- + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. + properties: + matchExpressions: + description: + matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - podSelector + type: object + type: object + maxItems: 25 + minItems: 1 + type: array + required: + - action + - to + type: object + maxItems: 25 + type: array + ingress: + description: |- + Ingress is the list of Ingress rules to be applied to the selected pods. + + A maximum of 25 rules is allowed in this block. + + The relative precedence of ingress rules within a single CNP object + (all of which share the priority) will be determined by the order + in which the rule is written. + Thus, a rule that appears at the top of the ingress rules + would take the highest precedence. + CNPs with no ingress rules do not affect ingress traffic. + items: + description: |- + ClusterNetworkPolicyIngressRule describes an action to take on a particular + set of traffic destined for pods selected by a ClusterNetworkPolicy's + Subject field. + properties: + action: + description: |- + Action specifies the effect this rule will have on matching + traffic. Currently the following actions are supported: + + - Accept: Accepts the selected traffic, allowing it into + the destination. No further ClusterNetworkPolicy or + NetworkPolicy rules will be processed. + + Note: while Accept ensures traffic is accepted by + Kubernetes network policy, it is still possible that the + packet is blocked in other ways: custom nftable rules, + high-layers e.g. service mesh. + + - Deny: Drops the selected traffic. No further + ClusterNetworkPolicy or NetworkPolicy rules will be + processed. + + - Pass: Skips all further ClusterNetworkPolicy rules in the + current tier for the selected traffic, and passes + evaluation to the next tier. + enum: + - Accept + - Deny + - Pass + type: string + from: + description: |- + From is the list of sources whose traffic this rule applies to. + If any element matches the source of incoming + traffic then the specified action is applied. + This field must be defined and contain at least one item. + items: + description: |- + ClusterNetworkPolicyIngressPeer defines a peer to allow traffic from. + + Exactly one of the fields must be set for a given peer and this is enforced + by the validation rules on the CRD. If an implementation sees no fields are + set then it can infer that the deployed CRD is of an incompatible version + with an unknown field. In that case it should fail closed. + + For "Accept" rules, "fail closed" means: "treat the rule as matching no + traffic". For "Deny" and "Pass" rules, "fail closed" means: "treat the rule + as a 'Deny all' rule". + maxProperties: 1 + minProperties: 1 + properties: + namespaces: + description: |- + Namespaces defines a way to select all pods within a set of Namespaces. + Note that host-networked pods are not included in this type of peer. + properties: + matchExpressions: + description: + matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + pods: + description: |- + Pods defines a way to select a set of pods in + a set of namespaces. Note that host-networked pods + are not included in this type of peer. + properties: + namespaceSelector: + description: |- + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. + properties: + matchExpressions: + description: + matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + description: |- + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. + properties: + matchExpressions: + description: + matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - podSelector + type: object + type: object + maxItems: 25 + minItems: 1 + type: array + name: + description: |- + Name is an identifier for this rule, that may be no more than + 100 characters in length. This field should be used by the implementation + to help improve observability, readability and error-reporting + for any applied policies. + maxLength: 100 + type: string + protocols: + description: |- + Protocols allows for more fine-grain matching of traffic on + protocol-specific attributes such as the port. If + unspecified, protocol-specific attributes will not be used + to match traffic. + items: + description: |- + ClusterNetworkPolicyProtocol describes additional protocol-specific match rules. + Exactly one field must be set. + maxProperties: 1 + minProperties: 1 + properties: + destinationNamedPort: + description: |- + DestinationNamedPort selects a destination port on a pod based on the + ContainerPort name. You can't use this in a rule that targets resources + without named ports (e.g. Nodes or Networks). + type: string + sctp: + description: SCTP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + tcp: + description: TCP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + udp: + description: UDP specific protocol matches. + minProperties: 1 + properties: + destinationPort: + description: DestinationPort for the match. + maxProperties: 1 + minProperties: 1 + properties: + number: + description: Number defines a network port value. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + range: + description: + Range defines a contiguous range + of ports. + properties: + end: + description: |- + end specifies the last port in the range. It must be + greater than start. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + start: + description: |- + start defines a network port that is the start of a port + range, the Start value must be less than End. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: Start port must be less than End port + rule: self.start < self.end + type: object + type: object + type: object + maxItems: 25 + minItems: 1 + type: array + required: + - action + - from + type: object + maxItems: 25 + type: array + priority: + description: |- + Priority is a value from 0 to 1000 indicating the precedence of + the policy within its tier. Policies with lower priority values have + higher precedence, and are checked before policies with higher priority + values in the same tier. All Admin tier rules have higher precedence than + NetworkPolicy or Baseline tier rules. + If two (or more) policies in the same tier with the same priority + could match a connection, then the implementation can apply any of the + matching policies to the connection, and there is no way for the user to + reliably determine which one it will choose. Administrators must be + careful about assigning the priorities for policies with rules that will + match many connections, and ensure that policies have unique priority + values in cases where ambiguity would be unacceptable. + format: int32 + maximum: 1000 + minimum: 0 + type: integer + subject: + description: + Subject defines the pods to which this ClusterNetworkPolicy + applies. + maxProperties: 1 + minProperties: 1 + properties: + namespaces: + description: Namespaces is used to select pods via namespace selectors. + properties: + matchExpressions: + description: + matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + pods: + description: + Pods is used to select pods via namespace AND pod + selectors. + properties: + namespaceSelector: + description: |- + NamespaceSelector follows standard label selector + semantics; if empty, it selects all Namespaces. + properties: + matchExpressions: + description: + matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + description: |- + PodSelector is used to explicitly select pods within a namespace; + if empty, it selects all Pods. + properties: + matchExpressions: + description: + matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - podSelector + type: object + type: object + tier: + description: |- + Tier is used as the top-level grouping for network policy prioritization. + + Policy tiers are evaluated in the following order: + * Admin tier + * NetworkPolicy tier + * Baseline tier + + ClusterNetworkPolicy can use 2 of these tiers: Admin and Baseline. + + The Admin tier takes precedence over all other policies. Policies + defined in this tier are used to set cluster-wide security rules + that cannot be overridden in the other tiers. If Admin tier has + made a final decision (Accept or Deny) on a connection, then no + further evaluation is done. + + NetworkPolicy tier is the tier for the namespaced v1.NetworkPolicy. + These policies are intended for the application developer to describe + the security policy associated with their deployments inside their + namespace. v1.NetworkPolicy always makes a final decision for selected + pods. Further evaluation only happens for Pods not selected by a + v1.NetworkPolicy. + + Baseline tier is a cluster-wide policy that can be overridden by the + v1.NetworkPolicy. If Baseline tier has made a final decision (Accept or + Deny) on a connection, then no further evaluation is done. + + If a given connection wasn't allowed or denied by any of the tiers, + the default kubernetes policy is applied, which says that + all pods can communicate with each other. + enum: + - Admin + - Baseline + type: string + required: + - priority + - subject + - tier + type: object + status: + description: Status is the status to be reported by the implementation. + properties: + conditions: + items: + description: + Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + required: + - conditions + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/manifests/v3_projectcalico_org.yaml b/manifests/v3_projectcalico_org.yaml new file mode 100644 index 00000000000..7085d320217 --- /dev/null +++ b/manifests/v3_projectcalico_org.yaml @@ -0,0 +1,37626 @@ +# projectcalico.org/v3 and operator.tigera.io/v1 APIs +--- +# Source: projectcalico.org.v3/templates/operator.tigera.io_apiservers.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: apiservers.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: APIServer + listKind: APIServerList + plural: apiservers + singular: apiserver + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: |- + APIServer installs the Tigera API server and related resources. At most one instance + of this resource is supported. It must be named "default" or "tigera-secure". + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Specification of the desired state for the Tigera API server. + properties: + apiServerDeployment: + description: |- + APIServerDeployment configures the calico-apiserver Deployment. If + used in conjunction with ControlPlaneNodeSelector or ControlPlaneTolerations, then these overrides + take precedence. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the specification of the API server Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the API server Deployment. + If omitted, the API server Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the API server Deployment + pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the API server Deployment's PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the API server pods. + If specified, this overrides any affinity that may be set on the API server Deployment. + If omitted, the API server Deployment will use its default value for affinity. + WARNING: Please note that this field will override the default API server Deployment affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of API server containers. + If specified, this overrides the specified API server Deployment containers. + If omitted, the API server Deployment will use its default values for its containers. + items: + description: + APIServerDeploymentContainer is an + API server Deployment container. + properties: + name: + description: |- + Name is an enum which identifies the API server Deployment container by name. + Supported values are: calico-apiserver, tigera-queryserver, calico-l7-admission-controller + enum: + - calico-apiserver + - tigera-queryserver + - calico-l7-admission-controller + type: string + ports: + description: |- + Ports allows customization of container's ports. + If specified, this overrides the named APIServer Deployment container's ports. + If omitted, the API server Deployment will use its default value for this container's port. + items: + properties: + containerPort: + description: |- + Number of port to expose on the pod's IP address. + This must be a valid port number, 0 < x < 65536. + format: int32 + type: integer + name: + description: |- + Name is an enum which identifies the API server Deployment Container port by name. + Supported values are: apiserver, queryserver, l7admctrl + enum: + - apiserver + - queryserver + - l7admctrl + type: string + required: + - containerPort + - name + type: object + type: array + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named API server Deployment container's resources. + If omitted, the API server Deployment will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + initContainers: + description: |- + InitContainers is a list of API server init containers. + If specified, this overrides the specified API server Deployment init containers. + If omitted, the API server Deployment will use its default values for its init containers. + items: + description: + APIServerDeploymentInitContainer is + an API server Deployment init container. + properties: + name: + description: |- + Name is an enum which identifies the API server Deployment init container by name. + Supported values are: calico-apiserver-certs-key-cert-provisioner + enum: + - calico-apiserver-certs-key-cert-provisioner + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named API server Deployment init container's resources. + If omitted, the API server Deployment will use its default value for this init container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the API server pod's scheduling constraints. + If specified, each of the key/value pairs are added to the API server Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If used in conjunction with ControlPlaneNodeSelector, that nodeSelector is set on the API server Deployment + and each of this field's key/value pairs are added to the API server Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the API server Deployment will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default API server Deployment nodeSelector. + type: object + priorityClassName: + description: + PriorityClassName allows to specify a + PriorityClass resource to be used. + type: string + tolerations: + description: |- + Tolerations is the API server pod's tolerations. + If specified, this overrides any tolerations that may be set on the API server Deployment. + If omitted, the API server Deployment will use its default value for tolerations. + WARNING: Please note that this field will override the default API server Deployment tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + type: object + type: object + type: object + calicoWebhooksDeployment: + description: + CalicoWebhooksDeployment configures the calico-webhooks + Deployment. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the calico-webhooks + Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-webhooks Deployment. + If omitted, the calico-webhooks Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-webhooks Deployment + pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the calico-webhooks Deployment's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-webhooks pods. + If specified, this overrides any affinity that may be set on the calico-webhooks Deployment. + If omitted, the calico-webhooks Deployment will use its default value for affinity. + WARNING: Please note that this field will override the default calico-webhooks Deployment affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-webhooks containers. + If specified, this overrides the specified calico-webhooks Deployment containers. + If omitted, the calico-webhooks Deployment will use its default values for its containers. + items: + description: + CalicoWebhooksDeploymentContainer is + a calico-webhooks Deployment container. + properties: + name: + description: |- + Name is an enum which identifies the calico-webhooks Deployment container by name. + Supported values are: calico-webhooks + enum: + - calico-webhooks + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-webhooks Deployment container's resources. + If omitted, the calico-webhooks Deployment will use its default value for this container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-webhooks pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-webhooks Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If used in conjunction with ControlPlaneNodeSelector, that nodeSelector is set on the calico-webhooks Deployment + and each of this field's key/value pairs are added to the calico-webhooks Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-webhooks Deployment will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-webhooks Deployment nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-webhooks pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-webhooks Deployment. + If omitted, the calico-webhooks Deployment will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-webhooks Deployment tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + logging: + properties: + apiServer: + properties: + logSeverity: + default: Info + description: LogSeverity defines log level for APIServer container. + enum: + - Fatal + - Error + - Warn + - Info + - Debug + - Trace + type: string + type: object + queryServer: + properties: + logSeverity: + default: Info + description: + LogSeverity defines log level for QueryServer + container. + enum: + - Fatal + - Error + - Warn + - Info + - Debug + - Trace + type: string + type: object + type: object + type: object + status: + description: Most recently observed status for the Tigera API server. + properties: + conditions: + description: |- + Conditions represents the latest observed set of conditions for the component. A component may be one or more of + Ready, Progressing, Degraded or other customer types. + items: + description: + Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + state: + description: State provides user-readable status. + type: string + type: object + type: object + x-kubernetes-validations: + - message: resource name must be 'default' or 'tigera-secure' + rule: self.metadata.name == 'default' || self.metadata.name == 'tigera-secure' + served: true + storage: true + subresources: + status: {} +--- +# Source: projectcalico.org.v3/templates/operator.tigera.io_gatewayapis.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: gatewayapis.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: GatewayAPI + listKind: GatewayAPIList + plural: gatewayapis + singular: gatewayapi + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: + GatewayAPISpec has fields that can be used to customize our + GatewayAPI support. + properties: + crdManagement: + description: |- + Configures how to manage and update Gateway API CRDs. The default behaviour - which is + used when this field is not set, or is set to "PreferExisting" - is that the Tigera + operator will create the Gateway API CRDs if they do not already exist, but will not + overwrite any existing Gateway API CRDs. This setting may be preferable if the customer + is using other implementations of the Gateway API concurrently with the Gateway API + support in Calico Enterprise. It is then the customer's responsibility to ensure that + CRDs are installed that meet the needs of all the Gateway API implementations in their + cluster. + Alternatively, if this field is set to "Reconcile", the Tigera operator will keep the + cluster's Gateway API CRDs aligned with those that it would install on a cluster that + does not yet have any version of those CRDs. + enum: + - Reconcile + - PreferExisting + type: string + envoyGatewayConfigRef: + description: |- + Reference to a custom EnvoyGateway YAML to use as the base EnvoyGateway configuration for + the gateway controller. When specified, must identify a ConfigMap resource with an + "envoy-gateway.yaml" key whose value is the desired EnvoyGateway YAML (i.e. following the + same pattern as the default `envoy-gateway-config` ConfigMap). + When not specified, the Tigera operator uses the `envoy-gateway-config` from the Envoy + Gateway helm chart as its base. + Starting from that base, the Tigera operator copies and modifies the EnvoyGateway + resource as follows: + 1. If not already specified, it sets the ControllerName to + "gateway.envoyproxy.io/gatewayclass-controller". + 2. It configures the `tigera/envoy-gateway` and `tigera/envoy-ratelimit` images that will + be used (according to the current Calico version, private registry and image set + settings) and any pull secrets that are needed to pull those images. + 3. It enables use of the Backend API. + The resulting EnvoyGateway is provisioned as the `envoy-gateway-config` ConfigMap (which + the gateway controller then uses as its config). + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + gatewayCertgenJob: + description: Allows customization of the gateway certgen job. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into the + job's top-level metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + GatewayCertgenJobSpec allows customization of the + gateway certgen job spec. + properties: + template: + description: + GatewayCertgenJobPodTemplate allows customization + of the gateway certgen job's pod template. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into the + job's pod template. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + GatewayCertgenJobPodSpec allows customization + of the gateway certgen job's pod spec. + properties: + affinity: + description: + If non-nil, Affinity sets the affinity + field of the job's pod template. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + items: + description: |- + GatewayCertgenJobContainer allows customization of the gateway certgen job's resource + requirements. + properties: + name: + enum: + - envoy-gateway-certgen + type: string + resources: + description: |- + If non-nil, Resources sets the ResourceRequirements of the job's "envoy-gateway-certgen" + container. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: + If non-nil, NodeSelector sets the node + selector for where job pods may be scheduled. + type: object + tolerations: + description: + If non-nil, Tolerations sets the tolerations + field of the job's pod template. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + gatewayClasses: + description: |- + Configures the GatewayClasses that will be available; please see GatewayClassSpec for + more detail. If GatewayClasses is nil, the Tigera operator defaults to provisioning a + single GatewayClass named "tigera-gateway-class", without any of the detailed + customizations that are allowed within GatewayClassSpec. + items: + properties: + envoyProxyRef: + description: |- + Reference to a custom EnvoyProxy resource to use as the base EnvoyProxy configuration for + this GatewayClass. When specified, must identify an EnvoyProxy resource. + When not specified, the Tigera operator uses an empty EnvoyProxy resource as its base. + Starting from that base, the Tigera operator copies and modifies the EnvoyProxy resource + as follows, in the order described: + 1. It configures the `tigera/envoy-proxy` image that will be used (according to the + current Calico version, private registry and image set settings) and any pull secrets + that are needed to pull that image. + 2. It applies customizations as specified by the following `GatewayKind`, + `GatewayDeployment`, `GatewayDaemonSet` and `GatewayService` fields. + The resulting EnvoyProxy is provisioned in the `tigera-gateway` namespace, together with + a GatewayClass that references it. + If a custom EnvoyProxy resource is specified and uses `EnvoyDaemonSet` instead of the + default `EnvoyDeployment`, deployment-related customizations will be applied within + `EnvoyDaemonSet` instead of within `EnvoyDeployment`. + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + gatewayDaemonSet: + description: |- + Allows customization of Gateways when deployed as Kubernetes DaemonSets, for Gateways in + this GatewayClass. + properties: + spec: + description: + GatewayDeploymentSpec allows customization + of the spec of gateway daemonsets. + properties: + template: + description: + GatewayDeploymentPodTemplate allows customization + of the pod template of gateway daemonsets. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into each + daemonset's pod template. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + GatewayDaemonSetPodSpec allows customization + of the pod spec of gateway daemonsets. + properties: + affinity: + description: + If non-nil, Affinity sets the affinity + field of the daemonset's pod template. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by + node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by + node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated + with matching the corresponding + nodeSelectorTerm, in the range + 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of + node selector terms. The terms + are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by + node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by + node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of + the matched WeightedPodAffinityTerm + fields are added per-node to find + the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity + scheduling rules (e.g. avoid putting this + pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of + the matched WeightedPodAffinityTerm + fields are added per-node to find + the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + items: + description: |- + GatewayDaemonSetContainer allows customization of the resource requirements of gateway + daemonsets. + properties: + name: + enum: + - envoy + type: string + resources: + description: |- + If non-nil, Resources sets the ResourceRequirements of the daemonset's "envoy" + container. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + If non-nil, NodeSelector sets the node selector for where daemonset pods may be + scheduled. + type: object + tolerations: + description: + If non-nil, Tolerations sets the + tolerations field of the daemonset's pod template. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + If non-nil, TopologySpreadConstraints sets the topology spread constraints of the + daemonset's pod template. TopologySpreadConstraints describes how a group of pods ought + to spread across topology domains. Scheduler will schedule pods in a way which abides by + the constraints. All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given + topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a + list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + type: object + type: object + type: object + gatewayDeployment: + description: |- + Allows customization of Gateways when deployed as Kubernetes Deployments, for Gateways in + this GatewayClass. + properties: + spec: + description: + GatewayDeploymentSpec allows customization + of the spec of gateway deployments. + properties: + replicas: + description: + If non-nil, Replicas sets the number of + replicas for the deployment. + format: int32 + type: integer + strategy: + description: + The deployment strategy to use to replace + existing pods with new ones. + properties: + rollingUpdate: + description: + Spec to control the desired behavior + of rolling update. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: object + template: + description: + GatewayDeploymentPodTemplate allows customization + of the pod template of gateway deployments. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into each + deployment's pod template. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + GatewayDeploymentPodSpec allows customization + of the pod spec of gateway deployments. + properties: + affinity: + description: + If non-nil, Affinity sets the affinity + field of the deployment's pod template. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by + node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by + node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated + with matching the corresponding + nodeSelectorTerm, in the range + 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of + node selector terms. The terms + are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by + node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by + node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of + the matched WeightedPodAffinityTerm + fields are added per-node to find + the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity + scheduling rules (e.g. avoid putting this + pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of + the matched WeightedPodAffinityTerm + fields are added per-node to find + the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key + is the label key + that the selector + applies to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + items: + description: |- + GatewayDeploymentContainer allows customization of the resource requirements of gateway + deployments. + properties: + name: + enum: + - envoy + type: string + resources: + description: |- + If non-nil, Resources sets the ResourceRequirements of the deployment's "envoy" + container. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + If non-nil, NodeSelector sets the node selector for where deployment pods may be + scheduled. + type: object + tolerations: + description: + If non-nil, Tolerations sets the + tolerations field of the deployment's pod + template. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + If non-nil, TopologySpreadConstraints sets the topology spread constraints of the + deployment's pod template. TopologySpreadConstraints describes how a group of pods ought + to spread across topology domains. Scheduler will schedule pods in a way which abides by + the constraints. All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given + topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a + list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + type: object + type: object + type: object + gatewayKind: + description: |- + Specifies whether Gateways in this class are deployed as Deployments (default) or as + DaemonSets. It is an error for GatewayKind to specify a choice that is incompatible with + the custom EnvoyProxy, when EnvoyProxyRef is also specified. + enum: + - Deployment + - DaemonSet + type: string + gatewayService: + description: + Allows customization of gateway services, for Gateways + in this GatewayClass. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into the + each Gateway Service's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: |- + GatewayServiceSpec allows customization of the services that front gateway deployments. + The LoadBalancer fields allow customization of the corresponding fields in the Kubernetes + ServiceSpec. These can be used for some cloud-independent control of the external load balancer + that is provisioned for each Gateway. For finer-grained cloud-specific control please use + the Metadata.Annotations field in GatewayService. + properties: + allocateLoadBalancerNodePorts: + type: boolean + loadBalancerClass: + type: string + loadBalancerIP: + type: string + loadBalancerSourceRanges: + items: + type: string + type: array + type: object + type: object + name: + description: The name of this GatewayClass. + type: string + required: + - name + type: object + type: array + gatewayControllerDeployment: + description: Allows customization of the gateway controller deployment. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into the + deployment's top-level metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + GatewayControllerDeploymentSpec allows customization + of the gateway controller deployment spec. + properties: + minReadySeconds: + description: + If non-nil, MinReadySeconds sets the minReadySeconds + field for the deployment. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + replicas: + description: + If non-nil, Replicas sets the number of replicas + for the deployment. + format: int32 + type: integer + template: + description: |- + GatewayControllerDeploymentPodTemplate allows customization of the gateway controller deployment + pod template. + properties: + metadata: + description: |- + If non-nil, non-clashing labels and annotations from this metadata are added into the + deployment's pod template. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: |- + GatewayControllerDeploymentPodSpec allows customization of the gateway controller deployment pod + spec. + properties: + affinity: + description: + If non-nil, Affinity sets the affinity + field of the deployment's pod template. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + items: + description: |- + GatewayControllerDeploymentContainer allows customization of the gateway controller's resource + requirements. + properties: + name: + enum: + - envoy-gateway + type: string + resources: + description: |- + If non-nil, Resources sets the ResourceRequirements of the controller's "envoy-gateway" + container. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + If non-nil, NodeSelector sets the node selector for where deployment pods may be + scheduled. + type: object + tolerations: + description: + If non-nil, Tolerations sets the tolerations + field of the deployment's pod template. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + If non-nil, TopologySpreadConstraints sets the topology spread constraints of the + deployment's pod template. TopologySpreadConstraints describes how a group of pods ought + to spread across topology domains. Scheduler will schedule pods in a way which abides by + the constraints. All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + type: object + type: object + type: object + type: object + type: object + x-kubernetes-validations: + - message: resource name must be 'default' + rule: self.metadata.name == 'default' || self.metadata.name == 'tigera-secure' + served: true + storage: true +--- +# Source: projectcalico.org.v3/templates/operator.tigera.io_goldmanes.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: goldmanes.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: Goldmane + listKind: GoldmaneList + plural: goldmanes + singular: goldmane + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + goldmaneDeployment: + description: + GoldmaneDeployment is the configuration for the goldmane + Deployment. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the specification of the goldmane Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the goldmane Deployment. + If omitted, the goldmane Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + strategy: + description: + The deployment strategy to use to replace existing + pods with new ones. + properties: + rollingUpdate: + description: |- + Rolling update config params. Present only if DeploymentStrategyType = + RollingUpdate. + to be. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: object + template: + description: + Template describes the goldmane Deployment pod + that will be created. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the goldmane Deployment's PodSpec. + properties: + affinity: + description: + Affinity is a group of affinity scheduling + rules for the goldmane pods. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of goldmane containers. + If specified, this overrides the specified EGW Deployment containers. + If omitted, the goldmane Deployment will use its default values for its containers. + items: + properties: + name: + enum: + - goldmane + type: string + resources: + description: + ResourceRequirements describes + the compute resource requirements. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: + NodeSelector gives more control over + the nodes where the goldmane pods will run on. + type: object + priorityClassName: + description: + PriorityClassName allows to specify a + PriorityClass resource to be used. + type: string + terminationGracePeriodSeconds: + description: + TerminationGracePeriodSeconds defines + the termination grace period of the goldmane pods + in seconds. + format: int64 + minimum: 0 + type: integer + tolerations: + description: |- + Tolerations is the goldmane pod's tolerations. + If specified, this overrides any tolerations that may be set on the goldmane Deployment. + If omitted, the goldmane Deployment will use its default value for tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + type: object + type: object + type: object + type: object + status: + description: GoldmaneStatus defines the observed state of Goldmane + properties: + conditions: + description: |- + Conditions represents the latest observed set of conditions for the component. A component may be one or more of + Ready, Progressing, Degraded or other customer types. + items: + description: + Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + x-kubernetes-validations: + - message: resource name must be 'default' + rule: self.metadata.name == 'default' + served: true + storage: true + subresources: + status: {} +--- +# Source: projectcalico.org.v3/templates/operator.tigera.io_imagesets.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: imagesets.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: ImageSet + listKind: ImageSetList + plural: imagesets + singular: imageset + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: |- + ImageSet is used to specify image digests for the images that the operator deploys. + The name of the ImageSet is expected to be in the format `-`. + The `variant` used is `enterprise` if the InstallationSpec Variant is + `TigeraSecureEnterprise` otherwise it is `calico`. + The `release` must match the version of the variant that the operator is built to deploy, + this version can be obtained by passing the `--version` flag to the operator binary. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: ImageSetSpec defines the desired state of ImageSet. + properties: + images: + description: |- + Images is the list of images to use digests. All images that the operator will deploy + must be specified. + items: + properties: + digest: + description: |- + Digest is the image identifier that will be used for the Image. + The field should not include a leading `@` and must be prefixed with `sha256:`. + type: string + image: + description: |- + Image is an image that the operator deploys and instead of using the built in tag + the operator will use the Digest for the image identifier. + The value should be the *original* image name without registry or tag or digest. + For the image `docker.io/calico/node:v3.17.1` it should be represented as `calico/node` + The "Installation" spec allows defining custom image registries, paths or prefixes. + Even for custom images such as example.com/custompath/customprefix-calico-node:v3.17.1, + this value should still be `calico/node`. + type: string + required: + - digest + - image + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: projectcalico.org.v3/templates/operator.tigera.io_installations.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: installations.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: Installation + listKind: InstallationList + plural: installations + singular: installation + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: |- + Installation configures an installation of Calico or Calico Enterprise. At most one instance + of this resource is supported. It must be named "default". The Installation API installs core networking + and network policy components, and provides general install-time configuration. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: + Specification of the desired state for the Calico or Calico + Enterprise installation. + properties: + azure: + description: Azure is used to configure azure provider specific options. + properties: + policyMode: + default: Default + description: |- + PolicyMode determines whether the "control-plane" label is applied to namespaces. It offers two options: Default and Manual. + The Default option adds the "control-plane" label to the required namespaces. + The Manual option does not apply the "control-plane" label to any namespace. + Default: Default + enum: + - Default + - Manual + type: string + type: object + calicoKubeControllersDeployment: + description: |- + CalicoKubeControllersDeployment configures the calico-kube-controllers Deployment. If used in + conjunction with the deprecated ComponentResources, then these overrides take precedence. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the calico-kube-controllers + Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-kube-controllers Deployment. + If omitted, the calico-kube-controllers Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-kube-controllers + Deployment pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the calico-kube-controllers Deployment's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-kube-controllers pods. + If specified, this overrides any affinity that may be set on the calico-kube-controllers Deployment. + If omitted, the calico-kube-controllers Deployment will use its default value for affinity. + WARNING: Please note that this field will override the default calico-kube-controllers Deployment affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-kube-controllers containers. + If specified, this overrides the specified calico-kube-controllers Deployment containers. + If omitted, the calico-kube-controllers Deployment will use its default values for its containers. + items: + description: + CalicoKubeControllersDeploymentContainer + is a calico-kube-controllers Deployment container. + properties: + name: + description: |- + Name is an enum which identifies the calico-kube-controllers Deployment container by name. + Supported values are: calico-kube-controllers, es-calico-kube-controllers + enum: + - calico-kube-controllers + - es-calico-kube-controllers + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-kube-controllers Deployment container's resources. + If omitted, the calico-kube-controllers Deployment will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-kube-controllers pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-kube-controllers Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If used in conjunction with ControlPlaneNodeSelector, that nodeSelector is set on the calico-kube-controllers Deployment + and each of this field's key/value pairs are added to the calico-kube-controllers Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-kube-controllers Deployment will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-kube-controllers Deployment nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-kube-controllers pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-kube-controllers Deployment. + If omitted, the calico-kube-controllers Deployment will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-kube-controllers Deployment tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + calicoNetwork: + description: + CalicoNetwork specifies networking configuration options + for Calico. + properties: + bgp: + description: + BGP configures whether or not to enable Calico's + BGP capabilities. + enum: + - Enabled + - Disabled + type: string + bpfNetworkBootstrap: + description: |- + BPFNetworkBootstrap manages the initial networking setup required to configure the BPF dataplane. + When enabled, the operator tries to bootstraps access to the Kubernetes API Server + by using the Kubernetes service and its associated endpoints. + This field should be enabled only if linuxDataplane is set to "BPF". + If another dataplane is selected, this field must be omitted or explicitly set to Disabled. + When disabled and linuxDataplane is BPF, you must manually provide the Kubernetes API Server + information via the "kubernetes-service-endpoint" ConfigMap. It is invalid to use both the ConfigMap + and have this field set to true at the same time. + Default: Disabled + enum: + - Disabled + - Enabled + type: string + containerIPForwarding: + description: |- + ContainerIPForwarding configures whether ip forwarding will be enabled for containers in the CNI configuration. + Default: Disabled + enum: + - Enabled + - Disabled + type: string + hostPorts: + description: |- + HostPorts configures whether or not Calico will support Kubernetes HostPorts. Valid only when using the Calico CNI plugin. + Default: Enabled + enum: + - Enabled + - Disabled + type: string + ipPools: + description: |- + IPPools contains a list of IP pools to manage. If nil, a single IPv4 IP pool + will be created by the operator. If an empty list is provided, the operator will not create any IP pools and will instead + wait for IP pools to be created out-of-band. + IP pools in this list will be reconciled by the operator and should not be modified out-of-band. + items: + properties: + allowedUses: + description: |- + AllowedUse controls what the IP pool will be used for. If not specified or empty, defaults to + ["Tunnel", "Workload"] for back-compatibility + items: + type: string + type: array + assignmentMode: + description: + AssignmentMode determines if IP addresses from + this pool should be assigned automatically or on request + only + type: string + blockSize: + description: |- + BlockSize specifies the CIDR prefex length to use when allocating per-node IP blocks from + the main IP pool CIDR. + Default: 26 (IPv4), 122 (IPv6) + format: int32 + type: integer + cidr: + description: + CIDR contains the address range for the IP + Pool in classless inter-domain routing format. + type: string + disableBGPExport: + default: false + description: |- + DisableBGPExport specifies whether routes from this IP pool's CIDR are exported over BGP. + Default: false + type: boolean + disableNewAllocations: + description: |- + DisableNewAllocations specifies whether or not new IP allocations are allowed from this pool. + This is useful when you want to prevent new pods from receiving IP addresses from this pool, without + impacting any existing pods that have already been assigned addresses from this pool. + type: boolean + encapsulation: + description: |- + Encapsulation specifies the encapsulation type that will be used with + the IP Pool. + Default: IPIP + enum: + - IPIPCrossSubnet + - IPIP + - VXLAN + - VXLANCrossSubnet + - None + type: string + name: + description: + Name is the name of the IP pool. If omitted, + this will be generated. + type: string + natOutgoing: + description: |- + NATOutgoing specifies if NAT will be enabled or disabled for outgoing traffic. + Default: Enabled + enum: + - Enabled + - Disabled + type: string + nodeSelector: + description: |- + NodeSelector specifies the node selector that will be set for the IP Pool. + Default: 'all()' + type: string + required: + - cidr + type: object + maxItems: 25 + type: array + kubeProxyManagement: + description: |- + KubeProxyManagement controls whether the operator manages the kube-proxy DaemonSet. + When enabled, the operator will manage the DaemonSet by patching it: + it disables kube-proxy if the dataplane is BPF, or enables it otherwise. + Default: Disabled + enum: + - Disabled + - Enabled + type: string + linuxDataplane: + description: |- + LinuxDataplane is used to select the dataplane used for Linux nodes. In particular, it + causes the operator to add required mounts and environment variables for the particular dataplane. + If not specified, iptables mode is used. + Default: Iptables + enum: + - Iptables + - BPF + - VPP + - Nftables + type: string + linuxPolicySetupTimeoutSeconds: + description: |- + LinuxPolicySetupTimeoutSeconds delays new pods from running containers + until their policy has been programmed in the dataplane. + The specified delay defines the maximum amount of time + that the Calico CNI plugin will wait for policy to be programmed. + Only applies to pods created on Linux nodes. + * A value of 0 disables pod startup delays. + Default: 0 + format: int32 + type: integer + mtu: + description: |- + MTU specifies the maximum transmission unit to use on the pod network. + If not specified, Calico will perform MTU auto-detection based on the cluster network. + format: int32 + type: integer + multiInterfaceMode: + description: |- + MultiInterfaceMode configures what will configure multiple interface per pod. Only valid for Calico Enterprise installations + using the Calico CNI plugin. + Default: None + enum: + - None + - Multus + type: string + nodeAddressAutodetectionV4: + description: |- + NodeAddressAutodetectionV4 specifies an approach to automatically detect node IPv4 addresses. If not specified, + will use default auto-detection settings to acquire an IPv4 address for each node. + properties: + canReach: + description: |- + CanReach enables IP auto-detection based on which source address on the node is used to reach the + specified IP or domain. + type: string + cidrs: + description: |- + CIDRS enables IP auto-detection based on which addresses on the nodes are within + one of the provided CIDRs. + items: + type: string + type: array + firstFound: + description: |- + FirstFound uses default interface matching parameters to select an interface, performing best-effort + filtering based on well-known interface names. + type: boolean + interface: + description: + Interface enables IP auto-detection based on + interfaces that match the given regex. + type: string + kubernetes: + description: + Kubernetes configures Calico to detect node addresses + based on the Kubernetes API. + enum: + - NodeInternalIP + type: string + skipInterface: + description: |- + SkipInterface enables IP auto-detection based on interfaces that do not match + the given regex. + type: string + type: object + nodeAddressAutodetectionV6: + description: |- + NodeAddressAutodetectionV6 specifies an approach to automatically detect node IPv6 addresses. If not specified, + IPv6 addresses will not be auto-detected. + properties: + canReach: + description: |- + CanReach enables IP auto-detection based on which source address on the node is used to reach the + specified IP or domain. + type: string + cidrs: + description: |- + CIDRS enables IP auto-detection based on which addresses on the nodes are within + one of the provided CIDRs. + items: + type: string + type: array + firstFound: + description: |- + FirstFound uses default interface matching parameters to select an interface, performing best-effort + filtering based on well-known interface names. + type: boolean + interface: + description: + Interface enables IP auto-detection based on + interfaces that match the given regex. + type: string + kubernetes: + description: + Kubernetes configures Calico to detect node addresses + based on the Kubernetes API. + enum: + - NodeInternalIP + type: string + skipInterface: + description: |- + SkipInterface enables IP auto-detection based on interfaces that do not match + the given regex. + type: string + type: object + sysctl: + description: Sysctl configures sysctl parameters for tuning plugin + items: + properties: + key: + enum: + - net.ipv4.tcp_keepalive_intvl + - net.ipv4.tcp_keepalive_probes + - net.ipv4.tcp_keepalive_time + type: string + value: + type: string + required: + - key + - value + type: object + type: array + windowsDataplane: + description: |- + WindowsDataplane is used to select the dataplane used for Windows nodes. In particular, it + causes the operator to add required mounts and environment variables for the particular dataplane. + If not specified, it is disabled and the operator will not render the Calico Windows nodes daemonset. + Default: Disabled + enum: + - HNS + - Disabled + type: string + type: object + calicoNodeDaemonSet: + description: |- + CalicoNodeDaemonSet configures the calico-node DaemonSet. If used in + conjunction with the deprecated ComponentResources, then these overrides take precedence. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the DaemonSet. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the specification of the calico-node DaemonSet. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-node DaemonSet. + If omitted, the calico-node DaemonSet will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-node DaemonSet + pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the calico-node DaemonSet's PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-node pods. + If specified, this overrides any affinity that may be set on the calico-node DaemonSet. + If omitted, the calico-node DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default calico-node DaemonSet affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-node containers. + If specified, this overrides the specified calico-node DaemonSet containers. + If omitted, the calico-node DaemonSet will use its default values for its containers. + items: + description: + CalicoNodeDaemonSetContainer is a calico-node + DaemonSet container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node DaemonSet container by name. + Supported values are: calico-node + enum: + - calico-node + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-node DaemonSet container's resources. + If omitted, the calico-node DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + dnsConfig: + description: + DNSConfig allows customization of the + DNS configuration for the calico-node pods. + properties: + nameservers: + description: |- + A list of DNS name server IP addresses. + This will be appended to the base nameservers generated from DNSPolicy. + Duplicated nameservers will be removed. + items: + type: string + type: array + x-kubernetes-list-type: atomic + options: + description: |- + A list of DNS resolver options. + This will be merged with the base options generated from DNSPolicy. + Duplicated entries will be removed. Resolution options given in Options + will override those that appear in the base DNSPolicy. + items: + description: + PodDNSConfigOption defines DNS + resolver options of a pod. + properties: + name: + description: |- + Name is this DNS resolver option's name. + Required. + type: string + value: + description: + Value is this DNS resolver + option's value. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + searches: + description: |- + A list of DNS search domains for host-name lookup. + This will be appended to the base search paths generated from DNSPolicy. + Duplicated search paths will be removed. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + dnsPolicy: + description: + DNSPolicy is the DNS policy for the calico-node + pods. + enum: + - "" + - Default + - ClusterFirst + - ClusterFirstWithHostNet + - None + type: string + initContainers: + description: |- + InitContainers is a list of calico-node init containers. + If specified, this overrides the specified calico-node DaemonSet init containers. + If omitted, the calico-node DaemonSet will use its default values for its init containers. + items: + description: + CalicoNodeDaemonSetInitContainer is + a calico-node DaemonSet init container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node DaemonSet init container by name. + Supported values are: install-cni, hostpath-init, flexvol-driver, ebpf-bootstrap, node-certs-key-cert-provisioner, calico-node-prometheus-server-tls-key-cert-provisioner, mount-bpffs (deprecated, replaced by ebpf-bootstrap) + enum: + - install-cni + - hostpath-init + - flexvol-driver + - ebpf-bootstrap + - node-certs-key-cert-provisioner + - calico-node-prometheus-server-tls-key-cert-provisioner + - mount-bpffs + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-node DaemonSet init container's resources. + If omitted, the calico-node DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-node pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-node DaemonSet nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-node DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-node DaemonSet nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-node pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-node DaemonSet. + If omitted, the calico-node DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-node DaemonSet tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + calicoNodeWindowsDaemonSet: + description: + CalicoNodeWindowsDaemonSet configures the calico-node-windows + DaemonSet. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the DaemonSet. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the calico-node-windows + DaemonSet. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-node-windows DaemonSet. + If omitted, the calico-node-windows DaemonSet will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-node-windows DaemonSet + pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the calico-node-windows DaemonSet's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-node-windows pods. + If specified, this overrides any affinity that may be set on the calico-node-windows DaemonSet. + If omitted, the calico-node-windows DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default calico-node-windows DaemonSet affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-node-windows containers. + If specified, this overrides the specified calico-node-windows DaemonSet containers. + If omitted, the calico-node-windows DaemonSet will use its default values for its containers. + items: + description: + CalicoNodeWindowsDaemonSetContainer + is a calico-node-windows DaemonSet container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node-windows DaemonSet container by name. + Supported values are: node, felix, confd + calico-node-windows is allowed because it was previously allowed. + enum: + - calico-node-windows + - node + - felix + - confd + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named DaemonSet container's resources. + If omitted, the DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + initContainers: + description: |- + InitContainers is a list of calico-node-windows init containers. + If specified, this overrides the specified calico-node-windows DaemonSet init containers. + If omitted, the calico-node-windows DaemonSet will use its default values for its init containers. + items: + description: + CalicoNodeWindowsDaemonSetInitContainer + is a calico-node-windows DaemonSet init container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node-windows DaemonSet init container by name. + Supported values are: install-cni;hostpath-init, flexvol-driver, node-certs-key-cert-provisioner, calico-node-windows-prometheus-server-tls-key-cert-provisioner + enum: + - install-cni + - hostpath-init + - flexvol-driver + - node-certs-key-cert-provisioner + - calico-node-windows-prometheus-server-tls-key-cert-provisioner + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-node-windows DaemonSet init container's resources. + If omitted, the calico-node-windows DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-node-windows pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-node-windows DaemonSet nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-node-windows DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-node-windows DaemonSet nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-node-windows pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-node-windows DaemonSet. + If omitted, the calico-node-windows DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-node-windows DaemonSet tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + calicoWindowsUpgradeDaemonSet: + description: |- + Deprecated. The CalicoWindowsUpgradeDaemonSet is deprecated and will be removed from the API in the future. + CalicoWindowsUpgradeDaemonSet configures the calico-windows-upgrade DaemonSet. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the calico-windows-upgrade + DaemonSet. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-windows-upgrade DaemonSet. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-windows-upgrade + DaemonSet pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the calico-windows-upgrade DaemonSet's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-windows-upgrade pods. + If specified, this overrides any affinity that may be set on the calico-windows-upgrade DaemonSet. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default calico-windows-upgrade DaemonSet affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-windows-upgrade containers. + If specified, this overrides the specified calico-windows-upgrade DaemonSet containers. + If omitted, the calico-windows-upgrade DaemonSet will use its default values for its containers. + items: + description: + CalicoWindowsUpgradeDaemonSetContainer + is a calico-windows-upgrade DaemonSet container. + properties: + name: + description: + Name is an enum which identifies + the calico-windows-upgrade DaemonSet container + by name. + enum: + - calico-windows-upgrade + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-windows-upgrade DaemonSet container's resources. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for this container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-windows-upgrade pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-windows-upgrade DaemonSet nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-windows-upgrade DaemonSet nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-windows-upgrade pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-windows-upgrade DaemonSet. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-windows-upgrade DaemonSet tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + certificateManagement: + description: |- + CertificateManagement configures pods to submit a CertificateSigningRequest to the certificates.k8s.io/v1 API in order + to obtain TLS certificates. This feature requires that you bring your own CSR signing and approval process, otherwise + pods will be stuck during initialization. + properties: + caCert: + description: + Certificate of the authority that signs the CertificateSigningRequests + in PEM format. + format: byte + type: string + keyAlgorithm: + description: |- + Specify the algorithm used by pods to generate a key pair that is associated with the X.509 certificate request. + Default: RSAWithSize2048 + enum: + - "" + - RSAWithSize2048 + - RSAWithSize4096 + - RSAWithSize8192 + - ECDSAWithCurve256 + - ECDSAWithCurve384 + - ECDSAWithCurve521 + type: string + signatureAlgorithm: + description: |- + Specify the algorithm used for the signature of the X.509 certificate request. + Default: SHA256WithRSA + enum: + - "" + - SHA256WithRSA + - SHA384WithRSA + - SHA512WithRSA + - ECDSAWithSHA256 + - ECDSAWithSHA384 + - ECDSAWithSHA512 + type: string + signerName: + description: |- + When a CSR is issued to the certificates.k8s.io API, the signerName is added to the request in order to accommodate for clusters + with multiple signers. + Must be formatted as: `/`. + type: string + required: + - caCert + - signerName + type: object + cni: + description: CNI specifies the CNI that will be used by this installation. + properties: + binDir: + description: |- + BinDir is the path to the CNI binaries directory. + If you have changed the installation directory for CNI binaries in the container runtime configuration, + please ensure that this field points to the same directory as specified in the container runtime settings. + Default directory depends on the KubernetesProvider. + * For KubernetesProvider GKE, this field defaults to "/home/kubernetes/bin". + * For KubernetesProvider OpenShift, this field defaults to "/var/lib/cni/bin". + * Otherwise, this field defaults to "/opt/cni/bin". + type: string + confDir: + description: |- + ConfDir is the path to the CNI config directory. + If you have changed the installation directory for CNI configuration in the container runtime configuration, + please ensure that this field points to the same directory as specified in the container runtime settings. + Default directory depends on the KubernetesProvider. + * For KubernetesProvider GKE, this field defaults to "/etc/cni/net.d". + * For KubernetesProvider OpenShift, this field defaults to "/var/run/multus/cni/net.d". + * Otherwise, this field defaults to "/etc/cni/net.d". + type: string + ipam: + description: |- + IPAM specifies the pod IP address management that will be used in the Calico or + Calico Enterprise installation. + properties: + type: + description: |- + Specifies the IPAM plugin that will be used in the Calico or Calico Enterprise installation. + * For CNI Plugin Calico, this field defaults to Calico. + * For CNI Plugin GKE, this field defaults to HostLocal. + * For CNI Plugin AzureVNET, this field defaults to AzureVNET. + * For CNI Plugin AmazonVPC, this field defaults to AmazonVPC. + The IPAM plugin is installed and configured only if the CNI plugin is set to Calico, + for all other values of the CNI plugin the plugin binaries and CNI config is a dependency + that is expected to be installed separately. + Default: Calico + enum: + - Calico + - HostLocal + - AmazonVPC + - AzureVNET + type: string + required: + - type + type: object + type: + description: |- + Specifies the CNI plugin that will be used in the Calico or Calico Enterprise installation. + * For KubernetesProvider GKE, this field defaults to GKE. + * For KubernetesProvider AKS, this field defaults to AzureVNET. + * For KubernetesProvider EKS, this field defaults to AmazonVPC. + * If aws-node daemonset exists in kube-system when the Installation resource is created, this field defaults to AmazonVPC. + * For all other cases this field defaults to Calico. + For the value Calico, the CNI plugin binaries and CNI config will be installed as part of deployment, + for all other values the CNI plugin binaries and CNI config is a dependency that is expected + to be installed separately. + Default: Calico + enum: + - Calico + - GKE + - AmazonVPC + - AzureVNET + type: string + required: + - type + type: object + componentResources: + description: |- + Deprecated. Please use CalicoNodeDaemonSet, TyphaDeployment, and KubeControllersDeployment. + ComponentResources can be used to customize the resource requirements for each component. + Node, Typha, and KubeControllers are supported for installations. + items: + description: |- + Deprecated. Please use component resource config fields in Installation.Spec instead. + The ComponentResource struct associates a ResourceRequirements with a component by name + properties: + componentName: + description: ComponentName is an enum which identifies the component + enum: + - Node + - Typha + - KubeControllers + - NodeWindows + - FelixWindows + - ConfdWindows + type: string + resourceRequirements: + description: + ResourceRequirements allows customization of limits + and requests for compute resources such as cpu and memory. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - componentName + - resourceRequirements + type: object + type: array + controlPlaneNodeSelector: + additionalProperties: + type: string + description: |- + ControlPlaneNodeSelector is used to select control plane nodes on which to run Calico + components. This is globally applied to all resources created by the operator excluding daemonsets. + type: object + controlPlaneReplicas: + description: |- + ControlPlaneReplicas defines how many replicas of the control plane core components will be deployed. + This field applies to all control plane components that support High Availability. Defaults to 2. + format: int32 + type: integer + controlPlaneTolerations: + description: |- + ControlPlaneTolerations specify tolerations which are then globally applied to all resources + created by the operator. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + csiNodeDriverDaemonSet: + description: + CSINodeDriverDaemonSet configures the csi-node-driver + DaemonSet. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the DaemonSet. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the csi-node-driver + DaemonSet. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the csi-node-driver DaemonSet. + If omitted, the csi-node-driver DaemonSet will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the csi-node-driver DaemonSet + pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the csi-node-driver DaemonSet's PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the csi-node-driver pods. + If specified, this overrides any affinity that may be set on the csi-node-driver DaemonSet. + If omitted, the csi-node-driver DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default csi-node-driver DaemonSet affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of csi-node-driver containers. + If specified, this overrides the specified csi-node-driver DaemonSet containers. + If omitted, the csi-node-driver DaemonSet will use its default values for its containers. + items: + description: + CSINodeDriverDaemonSetContainer is + a csi-node-driver DaemonSet container. + properties: + name: + description: |- + Name is an enum which identifies the csi-node-driver DaemonSet container by name. + Supported values are: calico-csi, csi-node-driver-registrar. + enum: + - calico-csi + - csi-node-driver-registrar + - csi-node-driver + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named csi-node-driver DaemonSet container's resources. + If omitted, the csi-node-driver DaemonSet will use its default value for this container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the csi-node-driver pod's scheduling constraints. + If specified, each of the key/value pairs are added to the csi-node-driver DaemonSet nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the csi-node-driver DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default csi-node-driver DaemonSet nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the csi-node-driver pod's tolerations. + If specified, this overrides any tolerations that may be set on the csi-node-driver DaemonSet. + If omitted, the csi-node-driver DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default csi-node-driver DaemonSet tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + fipsMode: + description: |- + FIPSMode uses images and features only that are using FIPS 140-2 validated cryptographic modules and standards. + Only supported for Variant=Calico. + Default: Disabled + enum: + - Enabled + - Disabled + type: string + flexVolumePath: + description: |- + FlexVolumePath optionally specifies a custom path for FlexVolume. If not specified, FlexVolume will be + enabled by default. If set to 'None', FlexVolume will be disabled. The default is based on the + kubernetesProvider. + type: string + imagePath: + description: |- + ImagePath allows for the path part of an image to be specified. If specified + then the specified value will be used as the image path for each image. If not specified + or empty, the default for each image will be used. + A special case value, UseDefault, is supported to explicitly specify the default + image path will be used for each image. + Image format: + `/:` + This option allows configuring the `` portion of the above format. + type: string + imagePrefix: + description: |- + ImagePrefix allows for the prefix part of an image to be specified. If specified + then the given value will be used as a prefix on each image. If not specified + or empty, no prefix will be used. + A special case value, UseDefault, is supported to explicitly specify the default + image prefix will be used for each image. + Image format: + `/:` + This option allows configuring the `` portion of the above format. + type: string + imagePullSecrets: + description: |- + ImagePullSecrets is an array of references to container registry pull secrets to use. These are + applied to all images to be pulled. + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + kubeletVolumePluginPath: + description: |- + KubeletVolumePluginPath optionally specifies enablement of Calico CSI plugin. If not specified, + CSI will be enabled by default. If set to 'None', CSI will be disabled. + Default: /var/lib/kubelet + type: string + kubernetesProvider: + description: |- + KubernetesProvider specifies a particular provider of the Kubernetes platform and enables provider-specific configuration. + If the specified value is empty, the Operator will attempt to automatically determine the current provider. + If the specified value is not empty, the Operator will still attempt auto-detection, but + will additionally compare the auto-detected value to the specified value to confirm they match. + enum: + - "" + - EKS + - GKE + - AKS + - OpenShift + - DockerEnterprise + - RKE2 + - TKG + - Kind + type: string + logging: + description: Logging Configuration for Components + properties: + cni: + description: Customized logging specification for calico-cni plugin + properties: + logFileMaxAgeDays: + description: "Default: 30 (days)" + format: int32 + type: integer + logFileMaxCount: + description: "Default: 10" + format: int32 + type: integer + logFileMaxSize: + anyOf: + - type: integer + - type: string + description: "Default: 100Mi" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + logSeverity: + description: "Default: Info" + enum: + - Error + - Warning + - Info + - Debug + type: string + type: object + type: object + nodeMetricsPort: + description: |- + NodeMetricsPort specifies which port calico/node serves prometheus metrics on. By default, metrics are not enabled. + If specified, this overrides any FelixConfiguration resources which may exist. If omitted, then + prometheus metrics may still be configured through FelixConfiguration. + format: int32 + type: integer + nodeUpdateStrategy: + description: |- + NodeUpdateStrategy can be used to customize the desired update strategy, such as the MaxUnavailable + field. + properties: + rollingUpdate: + description: + Rolling update config params. Present only if type + = "RollingUpdate". + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of nodes with an existing available DaemonSet pod that + can have an updated DaemonSet pod during during an update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up to a minimum of 1. + Default value is 0. + Example: when this is set to 30%, at most 30% of the total number of nodes + that should be running the daemon pod (i.e. status.desiredNumberScheduled) + can have their a new pod created before the old pod is marked as deleted. + The update starts by launching new pods on 30% of nodes. Once an updated + pod is available (Ready for at least minReadySeconds) the old DaemonSet pod + on that node is marked deleted. If the old pod becomes unavailable for any + reason (Ready transitions to false, is evicted, or is drained) an updated + pod is immediately created on that node without considering surge limits. + Allowing surge implies the possibility that the resources consumed by the + daemonset on any given node can double if the readiness check fails, and + so resource intensive daemonsets should take into account that they may + cause evictions during disruption. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of DaemonSet pods that can be unavailable during the + update. Value can be an absolute number (ex: 5) or a percentage of total + number of DaemonSet pods at the start of the update (ex: 10%). Absolute + number is calculated from percentage by rounding up. + This cannot be 0 if MaxSurge is 0 + Default value is 1. + Example: when this is set to 30%, at most 30% of the total number of nodes + that should be running the daemon pod (i.e. status.desiredNumberScheduled) + can have their pods stopped for an update at any given time. The update + starts by stopping at most 30% of those DaemonSet pods and then brings + up new DaemonSet pods in their place. Once the new pods are available, + it then proceeds onto other DaemonSet pods, thus ensuring that at least + 70% of original number of DaemonSet pods are available at all times during + the update. + x-kubernetes-int-or-string: true + type: object + type: + description: + Type of daemon set update. Can be "RollingUpdate" + or "OnDelete". Default is RollingUpdate. + type: string + type: object + nonPrivileged: + description: |- + Deprecated. NonPrivileged is deprecated and will be removed from the API in a future release. + Enabling this field is not supported and will cause errors. + NonPrivileged configures Calico to be run in non-privileged containers as non-root users where possible. + type: string + proxy: + description: |- + Proxy is used to configure the HTTP(S) proxy settings that will be applied to Tigera containers that connect + to destinations outside the cluster. It is expected that NO_PROXY is configured such that destinations within + the cluster (including the API server) are exempt from proxying. + properties: + httpProxy: + description: |- + HTTPProxy defines the value of the HTTP_PROXY environment variable that will be set on Tigera containers that connect to + destinations outside the cluster. + type: string + httpsProxy: + description: |- + HTTPSProxy defines the value of the HTTPS_PROXY environment variable that will be set on Tigera containers that connect to + destinations outside the cluster. + type: string + noProxy: + description: |- + NoProxy defines the value of the NO_PROXY environment variable that will be set on Tigera containers that connect to + destinations outside the cluster. This value must be set such that destinations within the scope of the cluster, including + the Kubernetes API server, are exempt from being proxied. + type: string + type: object + registry: + description: |- + Registry is the default Docker registry used for component Docker images. + If specified then the given value must end with a slash character (`/`) and all images will be pulled from this registry. + If not specified then the default registries will be used. A special case value, UseDefault, is + supported to explicitly specify the default registries will be used. + Image format: + `/:` + This option allows configuring the `` portion of the above format. + type: string + serviceCIDRs: + description: + Kubernetes Service CIDRs. Specifying this is required + when using Calico for Windows. + items: + type: string + type: array + tlsCipherSuites: + description: + TLSCipherSuites defines the cipher suite list that the + TLS protocol should use during secure communication. + items: + properties: + name: + description: This should be a valid TLS cipher suite name. + enum: + - TLS_AES_256_GCM_SHA384 + - TLS_CHACHA20_POLY1305_SHA256 + - TLS_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 + - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + - TLS_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA + - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA + - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + type: string + type: object + type: array + typhaAffinity: + description: |- + Deprecated. Please use Installation.Spec.TyphaDeployment instead. + TyphaAffinity allows configuration of node affinity characteristics for Typha pods. + properties: + nodeAffinity: + description: + NodeAffinity describes node affinity scheduling rules + for typha. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated with the + corresponding weight. + properties: + matchExpressions: + description: + A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with matching the corresponding + nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + WARNING: Please note that if the affinity requirements specified by this field are not met at + scheduling time, the pod will NOT be scheduled onto the node. + There is no fallback to another affinity rules with this setting. + This may cause networking disruption or even catastrophic failure! + PreferredDuringSchedulingIgnoredDuringExecution should be used for affinity + unless there is a specific well understood reason to use RequiredDuringSchedulingIgnoredDuringExecution and + you can guarantee that the RequiredDuringSchedulingIgnoredDuringExecution will always have sufficient nodes to satisfy the requirement. + NOTE: RequiredDuringSchedulingIgnoredDuringExecution is set by default for AKS nodes, + to avoid scheduling Typhas on virtual-nodes. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + type: object + typhaDeployment: + description: |- + TyphaDeployment configures the typha Deployment. If used in conjunction with the deprecated + ComponentResources or TyphaAffinity, then these overrides take precedence. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the specification of the typha Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the typha Deployment. + If omitted, the typha Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + strategy: + description: + The deployment strategy to use to replace existing + pods with new ones. + properties: + rollingUpdate: + description: |- + Rolling update config params. Present only if DeploymentStrategyType = + RollingUpdate. + to be. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: object + template: + description: + Template describes the typha Deployment pod that + will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the typha Deployment's PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the typha pods. + If specified, this overrides any affinity that may be set on the typha Deployment. + If omitted, the typha Deployment will use its default value for affinity. + If used in conjunction with the deprecated TyphaAffinity, then this value takes precedence. + WARNING: Please note that this field will override the default calico-typha Deployment affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of typha containers. + If specified, this overrides the specified typha Deployment containers. + If omitted, the typha Deployment will use its default values for its containers. + items: + description: + TyphaDeploymentContainer is a typha + Deployment container. + properties: + name: + description: |- + Name is an enum which identifies the typha Deployment container by name. + Supported values are: calico-typha + enum: + - calico-typha + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named typha Deployment container's resources. + If omitted, the typha Deployment will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + initContainers: + description: |- + InitContainers is a list of typha init containers. + If specified, this overrides the specified typha Deployment init containers. + If omitted, the typha Deployment will use its default values for its init containers. + items: + description: + TyphaDeploymentInitContainer is a typha + Deployment init container. + properties: + name: + description: |- + Name is an enum which identifies the typha Deployment init container by name. + Supported values are: typha-certs-key-cert-provisioner + enum: + - typha-certs-key-cert-provisioner + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named typha Deployment init container's resources. + If omitted, the typha Deployment will use its default value for this init container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-typha pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-typha Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-typha Deployment will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-typha Deployment nodeSelector. + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + If this value is nil, the default grace period will be used instead. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + Defaults to 30 seconds. + format: int64 + type: integer + tolerations: + description: |- + Tolerations is the typha pod's tolerations. + If specified, this overrides any tolerations that may be set on the typha Deployment. + If omitted, the typha Deployment will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-typha Deployment tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + type: object + type: object + type: object + typhaMetricsPort: + description: + TyphaMetricsPort specifies which port calico/typha serves + prometheus metrics on. By default, metrics are not enabled. + format: int32 + type: integer + variant: + description: |- + Variant is the product to install - one of Calico or TigeraSecureEnterprise + Default: Calico + enum: + - Calico + - TigeraSecureEnterprise + type: string + windowsNodes: + description: Windows Configuration + properties: + cniBinDir: + description: |- + CNIBinDir is the path to the CNI binaries directory on Windows, it must match what is used as 'bin_dir' under + [plugins] + [plugins."io.containerd.grpc.v1.cri"] + [plugins."io.containerd.grpc.v1.cri".cni] + on the containerd 'config.toml' file on the Windows nodes. + type: string + cniConfigDir: + description: |- + CNIConfigDir is the path to the CNI configuration directory on Windows, it must match what is used as 'conf_dir' under + [plugins] + [plugins."io.containerd.grpc.v1.cri"] + [plugins."io.containerd.grpc.v1.cri".cni] + on the containerd 'config.toml' file on the Windows nodes. + type: string + cniLogDir: + description: + CNILogDir is the path to the Calico CNI logs directory + on Windows. + type: string + vxlanAdapter: + description: + VXLANAdapter is the Network Adapter used for VXLAN, + leave blank for primary NIC + type: string + vxlanMACPrefix: + description: + VXLANMACPrefix is the prefix used when generating + MAC addresses for virtual NICs + pattern: ^[0-9A-Fa-f]{2}-[0-9A-Fa-f]{2}$ + type: string + type: object + type: object + status: + description: + Most recently observed state for the Calico or Calico Enterprise + installation. + properties: + calicoVersion: + description: |- + CalicoVersion shows the current running version of calico. + CalicoVersion along with Variant is needed to know the exact + version deployed. + type: string + computed: + description: + Computed is the final installation including overlaid + resources. + properties: + azure: + description: + Azure is used to configure azure provider specific + options. + properties: + policyMode: + default: Default + description: |- + PolicyMode determines whether the "control-plane" label is applied to namespaces. It offers two options: Default and Manual. + The Default option adds the "control-plane" label to the required namespaces. + The Manual option does not apply the "control-plane" label to any namespace. + Default: Default + enum: + - Default + - Manual + type: string + type: object + calicoKubeControllersDeployment: + description: |- + CalicoKubeControllersDeployment configures the calico-kube-controllers Deployment. If used in + conjunction with the deprecated ComponentResources, then these overrides take precedence. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the calico-kube-controllers + Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-kube-controllers Deployment. + If omitted, the calico-kube-controllers Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-kube-controllers + Deployment pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the calico-kube-controllers Deployment's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-kube-controllers pods. + If specified, this overrides any affinity that may be set on the calico-kube-controllers Deployment. + If omitted, the calico-kube-controllers Deployment will use its default value for affinity. + WARNING: Please note that this field will override the default calico-kube-controllers Deployment affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-kube-controllers containers. + If specified, this overrides the specified calico-kube-controllers Deployment containers. + If omitted, the calico-kube-controllers Deployment will use its default values for its containers. + items: + description: + CalicoKubeControllersDeploymentContainer + is a calico-kube-controllers Deployment container. + properties: + name: + description: |- + Name is an enum which identifies the calico-kube-controllers Deployment container by name. + Supported values are: calico-kube-controllers, es-calico-kube-controllers + enum: + - calico-kube-controllers + - es-calico-kube-controllers + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-kube-controllers Deployment container's resources. + If omitted, the calico-kube-controllers Deployment will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-kube-controllers pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-kube-controllers Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If used in conjunction with ControlPlaneNodeSelector, that nodeSelector is set on the calico-kube-controllers Deployment + and each of this field's key/value pairs are added to the calico-kube-controllers Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-kube-controllers Deployment will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-kube-controllers Deployment nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-kube-controllers pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-kube-controllers Deployment. + If omitted, the calico-kube-controllers Deployment will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-kube-controllers Deployment tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + calicoNetwork: + description: + CalicoNetwork specifies networking configuration + options for Calico. + properties: + bgp: + description: + BGP configures whether or not to enable Calico's + BGP capabilities. + enum: + - Enabled + - Disabled + type: string + bpfNetworkBootstrap: + description: |- + BPFNetworkBootstrap manages the initial networking setup required to configure the BPF dataplane. + When enabled, the operator tries to bootstraps access to the Kubernetes API Server + by using the Kubernetes service and its associated endpoints. + This field should be enabled only if linuxDataplane is set to "BPF". + If another dataplane is selected, this field must be omitted or explicitly set to Disabled. + When disabled and linuxDataplane is BPF, you must manually provide the Kubernetes API Server + information via the "kubernetes-service-endpoint" ConfigMap. It is invalid to use both the ConfigMap + and have this field set to true at the same time. + Default: Disabled + enum: + - Disabled + - Enabled + type: string + containerIPForwarding: + description: |- + ContainerIPForwarding configures whether ip forwarding will be enabled for containers in the CNI configuration. + Default: Disabled + enum: + - Enabled + - Disabled + type: string + hostPorts: + description: |- + HostPorts configures whether or not Calico will support Kubernetes HostPorts. Valid only when using the Calico CNI plugin. + Default: Enabled + enum: + - Enabled + - Disabled + type: string + ipPools: + description: |- + IPPools contains a list of IP pools to manage. If nil, a single IPv4 IP pool + will be created by the operator. If an empty list is provided, the operator will not create any IP pools and will instead + wait for IP pools to be created out-of-band. + IP pools in this list will be reconciled by the operator and should not be modified out-of-band. + items: + properties: + allowedUses: + description: |- + AllowedUse controls what the IP pool will be used for. If not specified or empty, defaults to + ["Tunnel", "Workload"] for back-compatibility + items: + type: string + type: array + assignmentMode: + description: + AssignmentMode determines if IP addresses + from this pool should be assigned automatically or + on request only + type: string + blockSize: + description: |- + BlockSize specifies the CIDR prefex length to use when allocating per-node IP blocks from + the main IP pool CIDR. + Default: 26 (IPv4), 122 (IPv6) + format: int32 + type: integer + cidr: + description: + CIDR contains the address range for the + IP Pool in classless inter-domain routing format. + type: string + disableBGPExport: + default: false + description: |- + DisableBGPExport specifies whether routes from this IP pool's CIDR are exported over BGP. + Default: false + type: boolean + disableNewAllocations: + description: |- + DisableNewAllocations specifies whether or not new IP allocations are allowed from this pool. + This is useful when you want to prevent new pods from receiving IP addresses from this pool, without + impacting any existing pods that have already been assigned addresses from this pool. + type: boolean + encapsulation: + description: |- + Encapsulation specifies the encapsulation type that will be used with + the IP Pool. + Default: IPIP + enum: + - IPIPCrossSubnet + - IPIP + - VXLAN + - VXLANCrossSubnet + - None + type: string + name: + description: + Name is the name of the IP pool. If omitted, + this will be generated. + type: string + natOutgoing: + description: |- + NATOutgoing specifies if NAT will be enabled or disabled for outgoing traffic. + Default: Enabled + enum: + - Enabled + - Disabled + type: string + nodeSelector: + description: |- + NodeSelector specifies the node selector that will be set for the IP Pool. + Default: 'all()' + type: string + required: + - cidr + type: object + maxItems: 25 + type: array + kubeProxyManagement: + description: |- + KubeProxyManagement controls whether the operator manages the kube-proxy DaemonSet. + When enabled, the operator will manage the DaemonSet by patching it: + it disables kube-proxy if the dataplane is BPF, or enables it otherwise. + Default: Disabled + enum: + - Disabled + - Enabled + type: string + linuxDataplane: + description: |- + LinuxDataplane is used to select the dataplane used for Linux nodes. In particular, it + causes the operator to add required mounts and environment variables for the particular dataplane. + If not specified, iptables mode is used. + Default: Iptables + enum: + - Iptables + - BPF + - VPP + - Nftables + type: string + linuxPolicySetupTimeoutSeconds: + description: |- + LinuxPolicySetupTimeoutSeconds delays new pods from running containers + until their policy has been programmed in the dataplane. + The specified delay defines the maximum amount of time + that the Calico CNI plugin will wait for policy to be programmed. + Only applies to pods created on Linux nodes. + * A value of 0 disables pod startup delays. + Default: 0 + format: int32 + type: integer + mtu: + description: |- + MTU specifies the maximum transmission unit to use on the pod network. + If not specified, Calico will perform MTU auto-detection based on the cluster network. + format: int32 + type: integer + multiInterfaceMode: + description: |- + MultiInterfaceMode configures what will configure multiple interface per pod. Only valid for Calico Enterprise installations + using the Calico CNI plugin. + Default: None + enum: + - None + - Multus + type: string + nodeAddressAutodetectionV4: + description: |- + NodeAddressAutodetectionV4 specifies an approach to automatically detect node IPv4 addresses. If not specified, + will use default auto-detection settings to acquire an IPv4 address for each node. + properties: + canReach: + description: |- + CanReach enables IP auto-detection based on which source address on the node is used to reach the + specified IP or domain. + type: string + cidrs: + description: |- + CIDRS enables IP auto-detection based on which addresses on the nodes are within + one of the provided CIDRs. + items: + type: string + type: array + firstFound: + description: |- + FirstFound uses default interface matching parameters to select an interface, performing best-effort + filtering based on well-known interface names. + type: boolean + interface: + description: + Interface enables IP auto-detection based + on interfaces that match the given regex. + type: string + kubernetes: + description: + Kubernetes configures Calico to detect node + addresses based on the Kubernetes API. + enum: + - NodeInternalIP + type: string + skipInterface: + description: |- + SkipInterface enables IP auto-detection based on interfaces that do not match + the given regex. + type: string + type: object + nodeAddressAutodetectionV6: + description: |- + NodeAddressAutodetectionV6 specifies an approach to automatically detect node IPv6 addresses. If not specified, + IPv6 addresses will not be auto-detected. + properties: + canReach: + description: |- + CanReach enables IP auto-detection based on which source address on the node is used to reach the + specified IP or domain. + type: string + cidrs: + description: |- + CIDRS enables IP auto-detection based on which addresses on the nodes are within + one of the provided CIDRs. + items: + type: string + type: array + firstFound: + description: |- + FirstFound uses default interface matching parameters to select an interface, performing best-effort + filtering based on well-known interface names. + type: boolean + interface: + description: + Interface enables IP auto-detection based + on interfaces that match the given regex. + type: string + kubernetes: + description: + Kubernetes configures Calico to detect node + addresses based on the Kubernetes API. + enum: + - NodeInternalIP + type: string + skipInterface: + description: |- + SkipInterface enables IP auto-detection based on interfaces that do not match + the given regex. + type: string + type: object + sysctl: + description: + Sysctl configures sysctl parameters for tuning + plugin + items: + properties: + key: + enum: + - net.ipv4.tcp_keepalive_intvl + - net.ipv4.tcp_keepalive_probes + - net.ipv4.tcp_keepalive_time + type: string + value: + type: string + required: + - key + - value + type: object + type: array + windowsDataplane: + description: |- + WindowsDataplane is used to select the dataplane used for Windows nodes. In particular, it + causes the operator to add required mounts and environment variables for the particular dataplane. + If not specified, it is disabled and the operator will not render the Calico Windows nodes daemonset. + Default: Disabled + enum: + - HNS + - Disabled + type: string + type: object + calicoNodeDaemonSet: + description: |- + CalicoNodeDaemonSet configures the calico-node DaemonSet. If used in + conjunction with the deprecated ComponentResources, then these overrides take precedence. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the DaemonSet. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the calico-node + DaemonSet. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-node DaemonSet. + If omitted, the calico-node DaemonSet will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-node DaemonSet + pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the calico-node DaemonSet's PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-node pods. + If specified, this overrides any affinity that may be set on the calico-node DaemonSet. + If omitted, the calico-node DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default calico-node DaemonSet affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-node containers. + If specified, this overrides the specified calico-node DaemonSet containers. + If omitted, the calico-node DaemonSet will use its default values for its containers. + items: + description: + CalicoNodeDaemonSetContainer is + a calico-node DaemonSet container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node DaemonSet container by name. + Supported values are: calico-node + enum: + - calico-node + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-node DaemonSet container's resources. + If omitted, the calico-node DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + dnsConfig: + description: + DNSConfig allows customization of + the DNS configuration for the calico-node pods. + properties: + nameservers: + description: |- + A list of DNS name server IP addresses. + This will be appended to the base nameservers generated from DNSPolicy. + Duplicated nameservers will be removed. + items: + type: string + type: array + x-kubernetes-list-type: atomic + options: + description: |- + A list of DNS resolver options. + This will be merged with the base options generated from DNSPolicy. + Duplicated entries will be removed. Resolution options given in Options + will override those that appear in the base DNSPolicy. + items: + description: + PodDNSConfigOption defines + DNS resolver options of a pod. + properties: + name: + description: |- + Name is this DNS resolver option's name. + Required. + type: string + value: + description: + Value is this DNS resolver + option's value. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + searches: + description: |- + A list of DNS search domains for host-name lookup. + This will be appended to the base search paths generated from DNSPolicy. + Duplicated search paths will be removed. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + dnsPolicy: + description: + DNSPolicy is the DNS policy for the + calico-node pods. + enum: + - "" + - Default + - ClusterFirst + - ClusterFirstWithHostNet + - None + type: string + initContainers: + description: |- + InitContainers is a list of calico-node init containers. + If specified, this overrides the specified calico-node DaemonSet init containers. + If omitted, the calico-node DaemonSet will use its default values for its init containers. + items: + description: + CalicoNodeDaemonSetInitContainer + is a calico-node DaemonSet init container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node DaemonSet init container by name. + Supported values are: install-cni, hostpath-init, flexvol-driver, ebpf-bootstrap, node-certs-key-cert-provisioner, calico-node-prometheus-server-tls-key-cert-provisioner, mount-bpffs (deprecated, replaced by ebpf-bootstrap) + enum: + - install-cni + - hostpath-init + - flexvol-driver + - ebpf-bootstrap + - node-certs-key-cert-provisioner + - calico-node-prometheus-server-tls-key-cert-provisioner + - mount-bpffs + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-node DaemonSet init container's resources. + If omitted, the calico-node DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-node pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-node DaemonSet nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-node DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-node DaemonSet nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-node pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-node DaemonSet. + If omitted, the calico-node DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-node DaemonSet tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + calicoNodeWindowsDaemonSet: + description: + CalicoNodeWindowsDaemonSet configures the calico-node-windows + DaemonSet. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the DaemonSet. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the calico-node-windows + DaemonSet. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-node-windows DaemonSet. + If omitted, the calico-node-windows DaemonSet will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-node-windows + DaemonSet pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the calico-node-windows DaemonSet's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-node-windows pods. + If specified, this overrides any affinity that may be set on the calico-node-windows DaemonSet. + If omitted, the calico-node-windows DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default calico-node-windows DaemonSet affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-node-windows containers. + If specified, this overrides the specified calico-node-windows DaemonSet containers. + If omitted, the calico-node-windows DaemonSet will use its default values for its containers. + items: + description: + CalicoNodeWindowsDaemonSetContainer + is a calico-node-windows DaemonSet container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node-windows DaemonSet container by name. + Supported values are: node, felix, confd + calico-node-windows is allowed because it was previously allowed. + enum: + - calico-node-windows + - node + - felix + - confd + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named DaemonSet container's resources. + If omitted, the DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + initContainers: + description: |- + InitContainers is a list of calico-node-windows init containers. + If specified, this overrides the specified calico-node-windows DaemonSet init containers. + If omitted, the calico-node-windows DaemonSet will use its default values for its init containers. + items: + description: + CalicoNodeWindowsDaemonSetInitContainer + is a calico-node-windows DaemonSet init container. + properties: + name: + description: |- + Name is an enum which identifies the calico-node-windows DaemonSet init container by name. + Supported values are: install-cni;hostpath-init, flexvol-driver, node-certs-key-cert-provisioner, calico-node-windows-prometheus-server-tls-key-cert-provisioner + enum: + - install-cni + - hostpath-init + - flexvol-driver + - node-certs-key-cert-provisioner + - calico-node-windows-prometheus-server-tls-key-cert-provisioner + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-node-windows DaemonSet init container's resources. + If omitted, the calico-node-windows DaemonSet will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-node-windows pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-node-windows DaemonSet nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-node-windows DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-node-windows DaemonSet nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-node-windows pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-node-windows DaemonSet. + If omitted, the calico-node-windows DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-node-windows DaemonSet tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + calicoWindowsUpgradeDaemonSet: + description: |- + Deprecated. The CalicoWindowsUpgradeDaemonSet is deprecated and will be removed from the API in the future. + CalicoWindowsUpgradeDaemonSet configures the calico-windows-upgrade DaemonSet. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the calico-windows-upgrade + DaemonSet. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the calico-windows-upgrade DaemonSet. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the calico-windows-upgrade + DaemonSet pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the calico-windows-upgrade DaemonSet's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the calico-windows-upgrade pods. + If specified, this overrides any affinity that may be set on the calico-windows-upgrade DaemonSet. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default calico-windows-upgrade DaemonSet affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of calico-windows-upgrade containers. + If specified, this overrides the specified calico-windows-upgrade DaemonSet containers. + If omitted, the calico-windows-upgrade DaemonSet will use its default values for its containers. + items: + description: + CalicoWindowsUpgradeDaemonSetContainer + is a calico-windows-upgrade DaemonSet container. + properties: + name: + description: + Name is an enum which identifies + the calico-windows-upgrade DaemonSet container + by name. + enum: + - calico-windows-upgrade + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named calico-windows-upgrade DaemonSet container's resources. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for this container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-windows-upgrade pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-windows-upgrade DaemonSet nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-windows-upgrade DaemonSet nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the calico-windows-upgrade pod's tolerations. + If specified, this overrides any tolerations that may be set on the calico-windows-upgrade DaemonSet. + If omitted, the calico-windows-upgrade DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-windows-upgrade DaemonSet tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + certificateManagement: + description: |- + CertificateManagement configures pods to submit a CertificateSigningRequest to the certificates.k8s.io/v1 API in order + to obtain TLS certificates. This feature requires that you bring your own CSR signing and approval process, otherwise + pods will be stuck during initialization. + properties: + caCert: + description: + Certificate of the authority that signs the CertificateSigningRequests + in PEM format. + format: byte + type: string + keyAlgorithm: + description: |- + Specify the algorithm used by pods to generate a key pair that is associated with the X.509 certificate request. + Default: RSAWithSize2048 + enum: + - "" + - RSAWithSize2048 + - RSAWithSize4096 + - RSAWithSize8192 + - ECDSAWithCurve256 + - ECDSAWithCurve384 + - ECDSAWithCurve521 + type: string + signatureAlgorithm: + description: |- + Specify the algorithm used for the signature of the X.509 certificate request. + Default: SHA256WithRSA + enum: + - "" + - SHA256WithRSA + - SHA384WithRSA + - SHA512WithRSA + - ECDSAWithSHA256 + - ECDSAWithSHA384 + - ECDSAWithSHA512 + type: string + signerName: + description: |- + When a CSR is issued to the certificates.k8s.io API, the signerName is added to the request in order to accommodate for clusters + with multiple signers. + Must be formatted as: `/`. + type: string + required: + - caCert + - signerName + type: object + cni: + description: CNI specifies the CNI that will be used by this installation. + properties: + binDir: + description: |- + BinDir is the path to the CNI binaries directory. + If you have changed the installation directory for CNI binaries in the container runtime configuration, + please ensure that this field points to the same directory as specified in the container runtime settings. + Default directory depends on the KubernetesProvider. + * For KubernetesProvider GKE, this field defaults to "/home/kubernetes/bin". + * For KubernetesProvider OpenShift, this field defaults to "/var/lib/cni/bin". + * Otherwise, this field defaults to "/opt/cni/bin". + type: string + confDir: + description: |- + ConfDir is the path to the CNI config directory. + If you have changed the installation directory for CNI configuration in the container runtime configuration, + please ensure that this field points to the same directory as specified in the container runtime settings. + Default directory depends on the KubernetesProvider. + * For KubernetesProvider GKE, this field defaults to "/etc/cni/net.d". + * For KubernetesProvider OpenShift, this field defaults to "/var/run/multus/cni/net.d". + * Otherwise, this field defaults to "/etc/cni/net.d". + type: string + ipam: + description: |- + IPAM specifies the pod IP address management that will be used in the Calico or + Calico Enterprise installation. + properties: + type: + description: |- + Specifies the IPAM plugin that will be used in the Calico or Calico Enterprise installation. + * For CNI Plugin Calico, this field defaults to Calico. + * For CNI Plugin GKE, this field defaults to HostLocal. + * For CNI Plugin AzureVNET, this field defaults to AzureVNET. + * For CNI Plugin AmazonVPC, this field defaults to AmazonVPC. + The IPAM plugin is installed and configured only if the CNI plugin is set to Calico, + for all other values of the CNI plugin the plugin binaries and CNI config is a dependency + that is expected to be installed separately. + Default: Calico + enum: + - Calico + - HostLocal + - AmazonVPC + - AzureVNET + type: string + required: + - type + type: object + type: + description: |- + Specifies the CNI plugin that will be used in the Calico or Calico Enterprise installation. + * For KubernetesProvider GKE, this field defaults to GKE. + * For KubernetesProvider AKS, this field defaults to AzureVNET. + * For KubernetesProvider EKS, this field defaults to AmazonVPC. + * If aws-node daemonset exists in kube-system when the Installation resource is created, this field defaults to AmazonVPC. + * For all other cases this field defaults to Calico. + For the value Calico, the CNI plugin binaries and CNI config will be installed as part of deployment, + for all other values the CNI plugin binaries and CNI config is a dependency that is expected + to be installed separately. + Default: Calico + enum: + - Calico + - GKE + - AmazonVPC + - AzureVNET + type: string + required: + - type + type: object + componentResources: + description: |- + Deprecated. Please use CalicoNodeDaemonSet, TyphaDeployment, and KubeControllersDeployment. + ComponentResources can be used to customize the resource requirements for each component. + Node, Typha, and KubeControllers are supported for installations. + items: + description: |- + Deprecated. Please use component resource config fields in Installation.Spec instead. + The ComponentResource struct associates a ResourceRequirements with a component by name + properties: + componentName: + description: + ComponentName is an enum which identifies the + component + enum: + - Node + - Typha + - KubeControllers + - NodeWindows + - FelixWindows + - ConfdWindows + type: string + resourceRequirements: + description: + ResourceRequirements allows customization of + limits and requests for compute resources such as cpu + and memory. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references one entry in + PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - componentName + - resourceRequirements + type: object + type: array + controlPlaneNodeSelector: + additionalProperties: + type: string + description: |- + ControlPlaneNodeSelector is used to select control plane nodes on which to run Calico + components. This is globally applied to all resources created by the operator excluding daemonsets. + type: object + controlPlaneReplicas: + description: |- + ControlPlaneReplicas defines how many replicas of the control plane core components will be deployed. + This field applies to all control plane components that support High Availability. Defaults to 2. + format: int32 + type: integer + controlPlaneTolerations: + description: |- + ControlPlaneTolerations specify tolerations which are then globally applied to all resources + created by the operator. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + csiNodeDriverDaemonSet: + description: + CSINodeDriverDaemonSet configures the csi-node-driver + DaemonSet. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the DaemonSet. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the specification of the csi-node-driver + DaemonSet. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created DaemonSet pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the csi-node-driver DaemonSet. + If omitted, the csi-node-driver DaemonSet will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + template: + description: + Template describes the csi-node-driver DaemonSet + pod that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: + Spec is the csi-node-driver DaemonSet's + PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the csi-node-driver pods. + If specified, this overrides any affinity that may be set on the csi-node-driver DaemonSet. + If omitted, the csi-node-driver DaemonSet will use its default value for affinity. + WARNING: Please note that this field will override the default csi-node-driver DaemonSet affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of csi-node-driver containers. + If specified, this overrides the specified csi-node-driver DaemonSet containers. + If omitted, the csi-node-driver DaemonSet will use its default values for its containers. + items: + description: + CSINodeDriverDaemonSetContainer + is a csi-node-driver DaemonSet container. + properties: + name: + description: |- + Name is an enum which identifies the csi-node-driver DaemonSet container by name. + Supported values are: calico-csi, csi-node-driver-registrar. + enum: + - calico-csi + - csi-node-driver-registrar + - csi-node-driver + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named csi-node-driver DaemonSet container's resources. + If omitted, the csi-node-driver DaemonSet will use its default value for this container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the csi-node-driver pod's scheduling constraints. + If specified, each of the key/value pairs are added to the csi-node-driver DaemonSet nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the csi-node-driver DaemonSet will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default csi-node-driver DaemonSet nodeSelector. + type: object + tolerations: + description: |- + Tolerations is the csi-node-driver pod's tolerations. + If specified, this overrides any tolerations that may be set on the csi-node-driver DaemonSet. + If omitted, the csi-node-driver DaemonSet will use its default value for tolerations. + WARNING: Please note that this field will override the default csi-node-driver DaemonSet tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + fipsMode: + description: |- + FIPSMode uses images and features only that are using FIPS 140-2 validated cryptographic modules and standards. + Only supported for Variant=Calico. + Default: Disabled + enum: + - Enabled + - Disabled + type: string + flexVolumePath: + description: |- + FlexVolumePath optionally specifies a custom path for FlexVolume. If not specified, FlexVolume will be + enabled by default. If set to 'None', FlexVolume will be disabled. The default is based on the + kubernetesProvider. + type: string + imagePath: + description: |- + ImagePath allows for the path part of an image to be specified. If specified + then the specified value will be used as the image path for each image. If not specified + or empty, the default for each image will be used. + A special case value, UseDefault, is supported to explicitly specify the default + image path will be used for each image. + Image format: + `/:` + This option allows configuring the `` portion of the above format. + type: string + imagePrefix: + description: |- + ImagePrefix allows for the prefix part of an image to be specified. If specified + then the given value will be used as a prefix on each image. If not specified + or empty, no prefix will be used. + A special case value, UseDefault, is supported to explicitly specify the default + image prefix will be used for each image. + Image format: + `/:` + This option allows configuring the `` portion of the above format. + type: string + imagePullSecrets: + description: |- + ImagePullSecrets is an array of references to container registry pull secrets to use. These are + applied to all images to be pulled. + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + kubeletVolumePluginPath: + description: |- + KubeletVolumePluginPath optionally specifies enablement of Calico CSI plugin. If not specified, + CSI will be enabled by default. If set to 'None', CSI will be disabled. + Default: /var/lib/kubelet + type: string + kubernetesProvider: + description: |- + KubernetesProvider specifies a particular provider of the Kubernetes platform and enables provider-specific configuration. + If the specified value is empty, the Operator will attempt to automatically determine the current provider. + If the specified value is not empty, the Operator will still attempt auto-detection, but + will additionally compare the auto-detected value to the specified value to confirm they match. + enum: + - "" + - EKS + - GKE + - AKS + - OpenShift + - DockerEnterprise + - RKE2 + - TKG + - Kind + type: string + logging: + description: Logging Configuration for Components + properties: + cni: + description: + Customized logging specification for calico-cni + plugin + properties: + logFileMaxAgeDays: + description: "Default: 30 (days)" + format: int32 + type: integer + logFileMaxCount: + description: "Default: 10" + format: int32 + type: integer + logFileMaxSize: + anyOf: + - type: integer + - type: string + description: "Default: 100Mi" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + logSeverity: + description: "Default: Info" + enum: + - Error + - Warning + - Info + - Debug + type: string + type: object + type: object + nodeMetricsPort: + description: |- + NodeMetricsPort specifies which port calico/node serves prometheus metrics on. By default, metrics are not enabled. + If specified, this overrides any FelixConfiguration resources which may exist. If omitted, then + prometheus metrics may still be configured through FelixConfiguration. + format: int32 + type: integer + nodeUpdateStrategy: + description: |- + NodeUpdateStrategy can be used to customize the desired update strategy, such as the MaxUnavailable + field. + properties: + rollingUpdate: + description: + Rolling update config params. Present only if + type = "RollingUpdate". + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of nodes with an existing available DaemonSet pod that + can have an updated DaemonSet pod during during an update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up to a minimum of 1. + Default value is 0. + Example: when this is set to 30%, at most 30% of the total number of nodes + that should be running the daemon pod (i.e. status.desiredNumberScheduled) + can have their a new pod created before the old pod is marked as deleted. + The update starts by launching new pods on 30% of nodes. Once an updated + pod is available (Ready for at least minReadySeconds) the old DaemonSet pod + on that node is marked deleted. If the old pod becomes unavailable for any + reason (Ready transitions to false, is evicted, or is drained) an updated + pod is immediately created on that node without considering surge limits. + Allowing surge implies the possibility that the resources consumed by the + daemonset on any given node can double if the readiness check fails, and + so resource intensive daemonsets should take into account that they may + cause evictions during disruption. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of DaemonSet pods that can be unavailable during the + update. Value can be an absolute number (ex: 5) or a percentage of total + number of DaemonSet pods at the start of the update (ex: 10%). Absolute + number is calculated from percentage by rounding up. + This cannot be 0 if MaxSurge is 0 + Default value is 1. + Example: when this is set to 30%, at most 30% of the total number of nodes + that should be running the daemon pod (i.e. status.desiredNumberScheduled) + can have their pods stopped for an update at any given time. The update + starts by stopping at most 30% of those DaemonSet pods and then brings + up new DaemonSet pods in their place. Once the new pods are available, + it then proceeds onto other DaemonSet pods, thus ensuring that at least + 70% of original number of DaemonSet pods are available at all times during + the update. + x-kubernetes-int-or-string: true + type: object + type: + description: + Type of daemon set update. Can be "RollingUpdate" + or "OnDelete". Default is RollingUpdate. + type: string + type: object + nonPrivileged: + description: |- + Deprecated. NonPrivileged is deprecated and will be removed from the API in a future release. + Enabling this field is not supported and will cause errors. + NonPrivileged configures Calico to be run in non-privileged containers as non-root users where possible. + type: string + proxy: + description: |- + Proxy is used to configure the HTTP(S) proxy settings that will be applied to Tigera containers that connect + to destinations outside the cluster. It is expected that NO_PROXY is configured such that destinations within + the cluster (including the API server) are exempt from proxying. + properties: + httpProxy: + description: |- + HTTPProxy defines the value of the HTTP_PROXY environment variable that will be set on Tigera containers that connect to + destinations outside the cluster. + type: string + httpsProxy: + description: |- + HTTPSProxy defines the value of the HTTPS_PROXY environment variable that will be set on Tigera containers that connect to + destinations outside the cluster. + type: string + noProxy: + description: |- + NoProxy defines the value of the NO_PROXY environment variable that will be set on Tigera containers that connect to + destinations outside the cluster. This value must be set such that destinations within the scope of the cluster, including + the Kubernetes API server, are exempt from being proxied. + type: string + type: object + registry: + description: |- + Registry is the default Docker registry used for component Docker images. + If specified then the given value must end with a slash character (`/`) and all images will be pulled from this registry. + If not specified then the default registries will be used. A special case value, UseDefault, is + supported to explicitly specify the default registries will be used. + Image format: + `/:` + This option allows configuring the `` portion of the above format. + type: string + serviceCIDRs: + description: + Kubernetes Service CIDRs. Specifying this is required + when using Calico for Windows. + items: + type: string + type: array + tlsCipherSuites: + description: + TLSCipherSuites defines the cipher suite list that + the TLS protocol should use during secure communication. + items: + properties: + name: + description: This should be a valid TLS cipher suite name. + enum: + - TLS_AES_256_GCM_SHA384 + - TLS_CHACHA20_POLY1305_SHA256 + - TLS_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 + - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + - TLS_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA + - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA + - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + type: string + type: object + type: array + typhaAffinity: + description: |- + Deprecated. Please use Installation.Spec.TyphaDeployment instead. + TyphaAffinity allows configuration of node affinity characteristics for Typha pods. + properties: + nodeAffinity: + description: + NodeAffinity describes node affinity scheduling + rules for typha. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + WARNING: Please note that if the affinity requirements specified by this field are not met at + scheduling time, the pod will NOT be scheduled onto the node. + There is no fallback to another affinity rules with this setting. + This may cause networking disruption or even catastrophic failure! + PreferredDuringSchedulingIgnoredDuringExecution should be used for affinity + unless there is a specific well understood reason to use RequiredDuringSchedulingIgnoredDuringExecution and + you can guarantee that the RequiredDuringSchedulingIgnoredDuringExecution will always have sufficient nodes to satisfy the requirement. + NOTE: RequiredDuringSchedulingIgnoredDuringExecution is set by default for AKS nodes, + to avoid scheduling Typhas on virtual-nodes. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node selector terms. + The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + type: object + typhaDeployment: + description: |- + TyphaDeployment configures the typha Deployment. If used in conjunction with the deprecated + ComponentResources or TyphaAffinity, then these overrides take precedence. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the specification of the typha Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the typha Deployment. + If omitted, the typha Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + strategy: + description: + The deployment strategy to use to replace + existing pods with new ones. + properties: + rollingUpdate: + description: |- + Rolling update config params. Present only if DeploymentStrategyType = + RollingUpdate. + to be. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: object + template: + description: + Template describes the typha Deployment pod + that will be created. + properties: + metadata: + description: |- + Metadata is a subset of a Kubernetes object's metadata that is added to + the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the typha Deployment's PodSpec. + properties: + affinity: + description: |- + Affinity is a group of affinity scheduling rules for the typha pods. + If specified, this overrides any affinity that may be set on the typha Deployment. + If omitted, the typha Deployment will use its default value for affinity. + If used in conjunction with the deprecated TyphaAffinity, then this value takes precedence. + WARNING: Please note that this field will override the default calico-typha Deployment affinity. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is + the label key that + the selector applies + to. + type: string + operator: + description: + |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: + |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of typha containers. + If specified, this overrides the specified typha Deployment containers. + If omitted, the typha Deployment will use its default values for its containers. + items: + description: + TyphaDeploymentContainer is a typha + Deployment container. + properties: + name: + description: |- + Name is an enum which identifies the typha Deployment container by name. + Supported values are: calico-typha + enum: + - calico-typha + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named typha Deployment container's resources. + If omitted, the typha Deployment will use its default value for this container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + initContainers: + description: |- + InitContainers is a list of typha init containers. + If specified, this overrides the specified typha Deployment init containers. + If omitted, the typha Deployment will use its default values for its init containers. + items: + description: + TyphaDeploymentInitContainer is + a typha Deployment init container. + properties: + name: + description: |- + Name is an enum which identifies the typha Deployment init container by name. + Supported values are: typha-certs-key-cert-provisioner + enum: + - typha-certs-key-cert-provisioner + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named typha Deployment init container's resources. + If omitted, the typha Deployment will use its default value for this init container's resources. + If used in conjunction with the deprecated ComponentResources, then this value takes precedence. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is the calico-typha pod's scheduling constraints. + If specified, each of the key/value pairs are added to the calico-typha Deployment nodeSelector provided + the key does not already exist in the object's nodeSelector. + If omitted, the calico-typha Deployment will use its default value for nodeSelector. + WARNING: Please note that this field will modify the default calico-typha Deployment nodeSelector. + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + If this value is nil, the default grace period will be used instead. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + Defaults to 30 seconds. + format: int64 + type: integer + tolerations: + description: |- + Tolerations is the typha pod's tolerations. + If specified, this overrides any tolerations that may be set on the typha Deployment. + If omitted, the typha Deployment will use its default value for tolerations. + WARNING: Please note that this field will override the default calico-typha Deployment tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given + topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + type: object + type: object + type: object + typhaMetricsPort: + description: + TyphaMetricsPort specifies which port calico/typha + serves prometheus metrics on. By default, metrics are not enabled. + format: int32 + type: integer + variant: + description: |- + Variant is the product to install - one of Calico or TigeraSecureEnterprise + Default: Calico + enum: + - Calico + - TigeraSecureEnterprise + type: string + windowsNodes: + description: Windows Configuration + properties: + cniBinDir: + description: |- + CNIBinDir is the path to the CNI binaries directory on Windows, it must match what is used as 'bin_dir' under + [plugins] + [plugins."io.containerd.grpc.v1.cri"] + [plugins."io.containerd.grpc.v1.cri".cni] + on the containerd 'config.toml' file on the Windows nodes. + type: string + cniConfigDir: + description: |- + CNIConfigDir is the path to the CNI configuration directory on Windows, it must match what is used as 'conf_dir' under + [plugins] + [plugins."io.containerd.grpc.v1.cri"] + [plugins."io.containerd.grpc.v1.cri".cni] + on the containerd 'config.toml' file on the Windows nodes. + type: string + cniLogDir: + description: + CNILogDir is the path to the Calico CNI logs + directory on Windows. + type: string + vxlanAdapter: + description: + VXLANAdapter is the Network Adapter used for + VXLAN, leave blank for primary NIC + type: string + vxlanMACPrefix: + description: + VXLANMACPrefix is the prefix used when generating + MAC addresses for virtual NICs + pattern: ^[0-9A-Fa-f]{2}-[0-9A-Fa-f]{2}$ + type: string + type: object + type: object + conditions: + description: |- + Conditions represents the latest observed set of conditions for the component. A component may be one or more of + Ready, Progressing, Degraded or other customer types. + items: + description: + Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + imageSet: + description: |- + ImageSet is the name of the ImageSet being used, if there is an ImageSet + that is being used. If an ImageSet is not being used then this will not be set. + type: string + mtu: + description: |- + MTU is the most recently observed value for pod network MTU. This may be an explicitly + configured value, or based on Calico's native auto-detetion. + format: int32 + type: integer + variant: + description: + Variant is the most recently observed installed variant + - one of Calico or TigeraSecureEnterprise + enum: + - Calico + - TigeraSecureEnterprise + type: string + type: object + type: object + x-kubernetes-validations: + - message: resource name must be 'default', 'tigera-secure', or 'overlay' + rule: + self.metadata.name == 'default' || self.metadata.name == 'tigera-secure' + || self.metadata.name == 'overlay' + served: true + storage: true + subresources: + status: {} +--- +# Source: projectcalico.org.v3/templates/operator.tigera.io_istios.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: istios.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: Istio + listKind: IstioList + plural: istios + singular: istio + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: Istio is the Schema for the istios API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: IstioSpec defines the desired state of Istio + properties: + dscpMark: + description: |- + DSCPMark define the value of the DSCP mark done by Felix and recognised by Istio CNI for Transparent + NetworkPolicies. + pattern: ^.* + type: integer + x-kubernetes-int-or-string: true + istioCNI: + description: + IstioCNIDaemonset defines the resource requirements for + the Istio CNI plugin. + properties: + spec: + description: + Spec allows users to specify custom fields for the + Istio CNI Daemonset. + properties: + template: + description: + Template allows users to specify custom fields + for the Istio CNI Daemonset. + properties: + spec: + description: + Spec allows users to specify custom fields + for the Istio CNI Daemonset. + properties: + affinity: + description: + Affinity specifies the affinity for the + deployment. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + nodeSelector: + additionalProperties: + type: string + description: + NodeSelector specifies the node affinity + for the deployment. + type: object + resources: + description: + Resources specifies the compute resources + required for the deployment. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references one entry + in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + tolerations: + description: + Tolerations specifies the tolerations + for the deployment. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + istiod: + description: + IstiodDeployment defines the resource requirements and + node selector for the Istio deployment. + properties: + spec: + description: + Spec allows users to specify custom fields for the + Istiod Deployment. + properties: + template: + description: + Template allows users to specify custom fields + for the Istiod Deployment. + properties: + spec: + description: + Spec allows users to specify custom fields + for the Istiod Deployment. + properties: + affinity: + description: + Affinity specifies the affinity for the + deployment. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + nodeSelector: + additionalProperties: + type: string + description: + NodeSelector specifies the node affinity + for the deployment. + type: object + resources: + description: + Resources specifies the compute resources + required for the deployment. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references one entry + in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + tolerations: + description: + Tolerations specifies the tolerations + for the deployment. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + ztunnel: + description: + ZTunnelDaemonset defines the resource requirements for + the ZTunnelDaemonset component. + properties: + spec: + description: + Spec allows users to specify custom fields for the + ZTunnel Daemonset. + properties: + template: + description: + Template allows users to specify custom fields + for the ZTunnel Daemonset. + properties: + spec: + description: + Spec allows users to specify custom fields + for the ZTunnel Daemonset. + properties: + affinity: + description: + Affinity specifies the affinity for the + deployment. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + nodeSelector: + additionalProperties: + type: string + description: + NodeSelector specifies the node affinity + for the deployment. + type: object + resources: + description: + Resources specifies the compute resources + required for the deployment. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references one entry + in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + tolerations: + description: + Tolerations specifies the tolerations + for the deployment. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + type: object + type: object + type: object + type: object + type: object + status: + description: IstioStatus defines the observed state of Istio + properties: + conditions: + description: |- + Conditions represents the latest observed set of conditions for the component. A component may be one or more of + Ready, Progressing, Degraded or other customer types. + items: + description: + Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + x-kubernetes-validations: + - message: resource name must be 'default' + rule: self.metadata.name == 'default' + served: true + storage: true + subresources: + status: {} +--- +# Source: projectcalico.org.v3/templates/operator.tigera.io_managementclusterconnections.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: managementclusterconnections.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: ManagementClusterConnection + listKind: ManagementClusterConnectionList + plural: managementclusterconnections + singular: managementclusterconnection + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: |- + ManagementClusterConnection represents a link between a managed cluster and a management cluster. At most one + instance of this resource is supported. It must be named "tigera-secure". + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: + ManagementClusterConnectionSpec defines the desired state + of ManagementClusterConnection + properties: + guardianDeployment: + description: GuardianDeployment configures the guardian Deployment. + properties: + spec: + description: Spec is the specification of the guardian Deployment. + properties: + template: + description: + Template describes the guardian Deployment pod + that will be created. + properties: + spec: + description: Spec is the guardian Deployment's PodSpec. + properties: + containers: + description: |- + Containers is a list of guardian containers. + If specified, this overrides the specified guardian Deployment containers. + If omitted, the guardian Deployment will use its default values for its containers. + items: + description: + GuardianDeploymentContainer is a guardian + Deployment container. + properties: + name: + description: |- + Name is an enum which identifies the guardian Deployment container by name. + Supported values are: tigera-guardian + enum: + - tigera-guardian + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named guardian Deployment container's resources. + If omitted, the guardian Deployment will use its default value for this container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + initContainers: + description: |- + InitContainers is a list of guardian init containers. + If specified, this overrides the specified guardian Deployment init containers. + If omitted, the guardian Deployment will use its default values for its init containers. + items: + description: + GuardianDeploymentInitContainer is + a guardian Deployment init container. + properties: + name: + description: + Name is an enum which identifies + the guardian Deployment init container by + name. + type: string + resources: + description: |- + Resources allows customization of limits and requests for compute resources such as cpu and memory. + If specified, this overrides the named guardian Deployment init container's resources. + If omitted, the guardian Deployment will use its default value for this init container's resources. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + type: object + type: object + type: object + type: object + impersonation: + description: |- + Impersonation configures the RBAC impersonation permissions for the guardian deployment. This field is not + applicable to installation variant Calico as no impersonation is ever used. Otherwise, if this field is left nil, + a default set of permissions will be applied. + WARNING: If this field is specified, it completely replaces the default permissions. + For example, providing an empty `impersonation: {}` block will result in guardian + having NO impersonation permissions. Similarly, if you specify `users` but omit `groups`, + guardian will lose its default permissions to impersonate groups. + properties: + groups: + description: |- + Groups is a list of group names that can be impersonated. An empty list infers all groups can be impersonated, + a null values means none. + items: + type: string + type: array + serviceAccounts: + description: |- + ServiceAccounts is a list of service account names that can be impersonated. An empty list infers all service accounts can + be impersonated, a null values means none. + items: + type: string + type: array + users: + description: |- + Users is a list of users that can be impersonated. An empty list infers all users can be impersonated, a null + value means none. + items: + type: string + type: array + type: object + managementClusterAddr: + description: |- + Specify where the managed cluster can reach the management cluster. Ex.: "10.128.0.10:30449". A managed cluster + should be able to access this address. This field is used by managed clusters only. + type: string + tls: + description: + TLS provides options for configuring how Managed Clusters + can establish an mTLS connection with the Management Cluster. + properties: + ca: + description: |- + CA indicates which verification method the tunnel client should use to verify the tunnel server's identity. + When left blank or set to 'Tigera', the tunnel client will expect a self-signed cert to be included in the certificate bundle + and will expect the cert to have a Common Name (CN) of 'voltron'. + When set to 'Public', the tunnel client will use its installed system certs and will use the managementClusterAddr to verify the tunnel server's identity. + Default: Tigera + enum: + - Tigera + - Public + type: string + type: object + type: object + status: + description: + ManagementClusterConnectionStatus defines the observed state + of ManagementClusterConnection + properties: + conditions: + description: |- + Conditions represents the latest observed set of conditions for the component. A component may be one or more of + Ready, Progressing, Degraded or other customer types. + items: + description: + Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + x-kubernetes-validations: + - message: resource name must be 'default' or 'tigera-secure' + rule: self.metadata.name == 'default' || self.metadata.name == 'tigera-secure' + served: true + storage: true + subresources: + status: {} +--- +# Source: projectcalico.org.v3/templates/operator.tigera.io_tigerastatuses.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: tigerastatuses.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: TigeraStatus + listKind: TigeraStatusList + plural: tigerastatuses + singular: tigerastatus + scope: Cluster + versions: + - additionalPrinterColumns: + - description: Whether the component running and stable. + jsonPath: .status.conditions[?(@.type=='Available')].status + name: Available + type: string + - description: Whether the component is processing changes. + jsonPath: .status.conditions[?(@.type=='Progressing')].status + name: Progressing + type: string + - description: Whether the component is degraded. + jsonPath: .status.conditions[?(@.type=='Degraded')].status + name: Degraded + type: string + - description: The time the component's Available status last changed. + jsonPath: .status.conditions[?(@.type=='Available')].lastTransitionTime + name: Since + type: date + name: v1 + schema: + openAPIV3Schema: + description: + TigeraStatus represents the most recently observed status for + Calico or a Calico Enterprise functional area. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: TigeraStatusSpec defines the desired state of TigeraStatus + type: object + status: + description: TigeraStatusStatus defines the observed state of TigeraStatus + properties: + conditions: + description: |- + Conditions represents the latest observed set of conditions for this component. A component may be one or more of + Available, Progressing, or Degraded. + items: + description: + TigeraStatusCondition represents a condition attached + to a particular component. + properties: + lastTransitionTime: + description: + The timestamp representing the start time for the + current status. + format: date-time + type: string + message: + description: + Optionally, a detailed message providing additional + context. + type: string + observedGeneration: + description: |- + observedGeneration represents the generation that the condition was set based upon. + For instance, if generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + type: integer + reason: + description: A brief reason explaining the condition. + type: string + status: + description: + The status of the condition. May be True, False, + or Unknown. + type: string + type: + description: + The type of condition. May be Available, Progressing, + or Degraded. + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + required: + - conditions + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: projectcalico.org.v3/templates/operator.tigera.io_whiskers.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: whiskers.operator.tigera.io +spec: + group: operator.tigera.io + names: + kind: Whisker + listKind: WhiskerList + plural: whiskers + singular: whisker + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + notifications: + description: |- + Default: Enabled + This setting enables calls to an external API to retrieve notification banner text in the Whisker UI. + Allowed values are Enabled or Disabled. Defaults to Enabled. + type: string + whiskerDeployment: + description: + WhiskerDeployment is the configuration for the whisker + Deployment. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's metadata + that is added to the Deployment. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the specification of the whisker Deployment. + properties: + minReadySeconds: + description: |- + MinReadySeconds is the minimum number of seconds for which a newly created Deployment pod should + be ready without any of its container crashing, for it to be considered available. + If specified, this overrides any minReadySeconds value that may be set on the whisker Deployment. + If omitted, the whisker Deployment will use its default value for minReadySeconds. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + strategy: + description: + The deployment strategy to use to replace existing + pods with new ones. + properties: + rollingUpdate: + description: |- + Rolling update config params. Present only if DeploymentStrategyType = + RollingUpdate. + to be. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: object + template: + description: + Template describes the whisker Deployment pod + that will be created. + properties: + metadata: + description: + Metadata is a subset of a Kubernetes object's + metadata that is added to the pod's metadata. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is a map of arbitrary non-identifying metadata. Each of these + key/value pairs are added to the object's annotations provided the key does not + already exist in the object's annotations. + type: object + labels: + additionalProperties: + type: string + description: |- + Labels is a map of string keys and values that may match replicaset and + service selectors. Each of these key/value pairs are added to the + object's labels provided the key does not already exist in the object's labels. + type: object + type: object + spec: + description: Spec is the whisker Deployment's PodSpec. + properties: + affinity: + description: + Affinity is a group of affinity scheduling + rules for the whisker pods. + properties: + nodeAffinity: + description: + Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: + A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: + Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: + Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: + A list of node selector + requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: + A list of node selector + requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: + The label key + that the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: + Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: + Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: + The weights of all of the matched + WeightedPodAffinityTerm fields are added + per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: + Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: + matchExpressions is + a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label + key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + containers: + description: |- + Containers is a list of whisker containers. + If specified, this overrides the specified EGW Deployment containers. + If omitted, the whisker Deployment will use its default values for its containers. + items: + properties: + name: + enum: + - whisker + - whisker-backend + type: string + resources: + description: + ResourceRequirements describes + the compute resource requirements. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + This field depends on the + DynamicResourceAllocation feature gate. + This field is immutable. It can only be set for containers. + items: + description: + ResourceClaim references + one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + required: + - name + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: + NodeSelector gives more control over + the nodes where the whisker pods will run on. + type: object + priorityClassName: + description: + PriorityClassName allows to specify a + PriorityClass resource to be used. + type: string + terminationGracePeriodSeconds: + description: + TerminationGracePeriodSeconds defines + the termination grace period of the whisker pods + in seconds. + format: int64 + minimum: 0 + type: integer + tolerations: + description: |- + Tolerations is the whisker pod's tolerations. + If specified, this overrides any tolerations that may be set on the whisker Deployment. + If omitted, the whisker Deployment will use its default value for tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: + TopologySpreadConstraint specifies + how to spread matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + properties: + matchExpressions: + description: + matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: + key is the label key + that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + type: object + type: object + type: object + type: object + status: + description: WhiskerStatus defines the observed state of Whisker + properties: + conditions: + description: |- + Conditions represents the latest observed set of conditions for the component. A component may be one or more of + Ready, Progressing, Degraded or other customer types. + items: + description: + Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + x-kubernetes-validations: + - message: resource name must be 'default' + rule: self.metadata.name == 'default' + served: true + storage: true + subresources: + status: {} +--- +# Source: projectcalico.org.v3/templates/calico/projectcalico.org_bgpconfigurations.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: bgpconfigurations.projectcalico.org +spec: + group: projectcalico.org + names: + kind: BGPConfiguration + listKind: BGPConfigurationList + plural: bgpconfigurations + shortNames: + - bgpconfig + - bgpconfigs + singular: bgpconfiguration + preserveUnknownFields: false + scope: Cluster + versions: + - name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + asNumber: + format: int32 + type: integer + bindMode: + enum: + - None + - NodeIP + type: string + communities: + items: + properties: + name: + type: string + value: + pattern: ^(\d+):(\d+)$|^(\d+):(\d+):(\d+)$ + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set + ignoredInterfaces: + items: + type: string + type: array + x-kubernetes-list-type: set + listenPort: + maximum: 65535 + minimum: 1 + type: integer + localWorkloadPeeringIPV4: + type: string + localWorkloadPeeringIPV6: + type: string + logSeverityScreen: + default: Info + pattern: ^(?i)(Trace|Debug|Info|Warning|Error|Fatal)?$ + type: string + nodeMeshMaxRestartTime: + type: string + nodeMeshPassword: + properties: + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + nodeToNodeMeshEnabled: + type: boolean + prefixAdvertisements: + items: + properties: + cidr: + format: cidr + type: string + communities: + items: + type: string + type: array + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set + serviceClusterIPs: + items: + properties: + cidr: + format: cidr + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set + serviceExternalIPs: + items: + properties: + cidr: + format: cidr + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set + serviceLoadBalancerAggregation: + default: Enabled + enum: + - Enabled + - Disabled + type: string + serviceLoadBalancerIPs: + items: + properties: + cidr: + format: cidr + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: set + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +--- +# Source: projectcalico.org.v3/templates/calico/projectcalico.org_bgpfilters.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: bgpfilters.projectcalico.org +spec: + group: projectcalico.org + names: + kind: BGPFilter + listKind: BGPFilterList + plural: bgpfilters + singular: bgpfilter + preserveUnknownFields: false + scope: Cluster + versions: + - name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + exportV4: + items: + properties: + action: + enum: + - Accept + - Reject + type: string + cidr: + format: cidr + type: string + interface: + type: string + matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn + type: string + prefixLength: + properties: + max: + format: int32 + maximum: 32 + minimum: 0 + type: integer + min: + format: int32 + maximum: 32 + minimum: 0 + type: integer + type: object + x-kubernetes-map-type: atomic + source: + enum: + - RemotePeers + type: string + required: + - action + type: object + x-kubernetes-map-type: atomic + type: array + exportV6: + items: + properties: + action: + enum: + - Accept + - Reject + type: string + cidr: + format: cidr + type: string + interface: + type: string + matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn + type: string + prefixLength: + properties: + max: + format: int32 + maximum: 128 + minimum: 0 + type: integer + min: + format: int32 + maximum: 128 + minimum: 0 + type: integer + type: object + x-kubernetes-map-type: atomic + source: + enum: + - RemotePeers + type: string + required: + - action + type: object + x-kubernetes-map-type: atomic + type: array + importV4: + items: + properties: + action: + enum: + - Accept + - Reject + type: string + cidr: + format: cidr + type: string + interface: + type: string + matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn + type: string + prefixLength: + properties: + max: + format: int32 + maximum: 32 + minimum: 0 + type: integer + min: + format: int32 + maximum: 32 + minimum: 0 + type: integer + type: object + x-kubernetes-map-type: atomic + source: + enum: + - RemotePeers + type: string + required: + - action + type: object + x-kubernetes-map-type: atomic + type: array + importV6: + items: + properties: + action: + enum: + - Accept + - Reject + type: string + cidr: + format: cidr + type: string + interface: + type: string + matchOperator: + enum: + - Equal + - NotEqual + - In + - NotIn + type: string + prefixLength: + properties: + max: + format: int32 + maximum: 128 + minimum: 0 + type: integer + min: + format: int32 + maximum: 128 + minimum: 0 + type: integer + type: object + x-kubernetes-map-type: atomic + source: + enum: + - RemotePeers + type: string + required: + - action + type: object + x-kubernetes-map-type: atomic + type: array + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +--- +# Source: projectcalico.org.v3/templates/calico/projectcalico.org_bgppeers.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: bgppeers.projectcalico.org +spec: + group: projectcalico.org + names: + kind: BGPPeer + listKind: BGPPeerList + plural: bgppeers + singular: bgppeer + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - description: Selector for the nodes that should have this peering + jsonPath: .spec.nodeSelector + name: Node Selector + type: string + - description: + The node name identifying the Calico node instance that is targeted + by this peer + jsonPath: .spec.node + name: Node + type: string + - description: + The optional Local AS Number to use when peering with this remote + peer + jsonPath: .spec.localASNumber + name: Local ASN + type: string + - description: Selector for the remote nodes to peer with + jsonPath: .spec.peerSelector + name: Peer Selector + type: string + - description: + The IP address of the peer followed by an optional port number + to peer with + jsonPath: .spec.peerIP + name: Peer IP + type: string + - description: The AS Number of the peer + jsonPath: .spec.asNumber + name: Peer ASN + type: string + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + asNumber: + format: int32 + type: integer + filters: + items: + type: string + type: array + keepOriginalNextHop: + type: boolean + keepaliveTime: + type: string + localASNumber: + format: int32 + type: integer + localWorkloadSelector: + type: string + maxRestartTime: + type: string + nextHopMode: + enum: + - Auto + - Self + - Keep + type: string + node: + type: string + nodeSelector: + type: string + numAllowedLocalASNumbers: + format: int32 + type: integer + password: + properties: + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + peerIP: + type: string + peerSelector: + type: string + reachableBy: + type: string + reversePeering: + allOf: + - enum: + - Auto + - Manual + - enum: + - Auto + - Manual + type: string + sourceAddress: + enum: + - UseNodeIP + - None + type: string + ttlSecurity: + type: integer + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} +--- +# Source: projectcalico.org.v3/templates/calico/projectcalico.org_blockaffinities.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: blockaffinities.projectcalico.org +spec: + group: projectcalico.org + names: + kind: BlockAffinity + listKind: BlockAffinityList + plural: blockaffinities + shortNames: + - affinity + - affinities + singular: blockaffinity + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - description: The block CIDR + jsonPath: .spec.cidr + name: CIDR + type: string + - description: The node the block is affine to + jsonPath: .spec.node + name: Node + type: string + - description: The type of block affinity + jsonPath: .spec.type + name: Type + type: string + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + cidr: + format: cidr + type: string + deleted: + default: false + type: boolean + node: + type: string + state: + enum: + - "" + - confirmed + - pending + - pendingDeletion + type: string + type: + type: string + required: + - cidr + - deleted + - node + - state + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} +--- +# Source: projectcalico.org.v3/templates/calico/projectcalico.org_caliconodestatuses.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: caliconodestatuses.projectcalico.org +spec: + group: projectcalico.org + names: + kind: CalicoNodeStatus + listKind: CalicoNodeStatusList + plural: caliconodestatuses + singular: caliconodestatus + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - description: The name of the node + jsonPath: .spec.node + name: Node + type: string + - description: The types of information to monitor for this calico/node + jsonPath: .spec.classes + name: Classes + type: string + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + classes: + items: + enum: + - Agent + - BGP + - Routes + type: string + type: array + node: + type: string + updatePeriodSeconds: + format: int32 + type: integer + type: object + status: + properties: + agent: + properties: + birdV4: + properties: + lastBootTime: + type: string + lastReconfigurationTime: + type: string + routerID: + type: string + state: + enum: + - Ready + - NotReady + type: string + version: + type: string + type: object + birdV6: + properties: + lastBootTime: + type: string + lastReconfigurationTime: + type: string + routerID: + type: string + state: + enum: + - Ready + - NotReady + type: string + version: + type: string + type: object + type: object + bgp: + properties: + numberEstablishedV4: + type: integer + numberEstablishedV6: + type: integer + numberNotEstablishedV4: + type: integer + numberNotEstablishedV6: + type: integer + peersV4: + items: + properties: + peerIP: + type: string + since: + type: string + state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close + type: string + type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer + type: string + type: object + type: array + peersV6: + items: + properties: + peerIP: + type: string + since: + type: string + state: + enum: + - Idle + - Connect + - Active + - OpenSent + - OpenConfirm + - Established + - Close + type: string + type: + enum: + - NodeMesh + - NodePeer + - GlobalPeer + type: string + type: object + type: array + required: + - numberEstablishedV4 + - numberEstablishedV6 + - numberNotEstablishedV4 + - numberNotEstablishedV6 + type: object + lastUpdated: + format: date-time + nullable: true + type: string + routes: + properties: + routesV4: + items: + properties: + destination: + type: string + gateway: + type: string + interface: + type: string + learnedFrom: + properties: + peerIP: + type: string + sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer + type: string + type: object + type: + enum: + - FIB + - RIB + type: string + type: object + type: array + routesV6: + items: + properties: + destination: + type: string + gateway: + type: string + interface: + type: string + learnedFrom: + properties: + peerIP: + type: string + sourceType: + enum: + - Kernel + - Static + - Direct + - NodeMesh + - BGPPeer + type: string + type: object + type: + enum: + - FIB + - RIB + type: string + type: object + type: array + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} +--- +# Source: projectcalico.org.v3/templates/calico/projectcalico.org_clusterinformations.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: clusterinformations.projectcalico.org +spec: + group: projectcalico.org + names: + kind: ClusterInformation + listKind: ClusterInformationList + plural: clusterinformations + shortNames: + - clusterinfo + - clusterinfos + singular: clusterinformation + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - description: The version of Calico that the cluster is running + jsonPath: .spec.calicoVersion + name: Version + type: string + - description: Whether the datastore is ready for use + jsonPath: .spec.datastoreReady + name: Ready + type: boolean + - description: The types associated with the cluster + jsonPath: .spec.clusterType + name: Types + type: string + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + calicoVersion: + type: string + clusterGUID: + type: string + clusterType: + type: string + datastoreReady: + type: boolean + variant: + type: string + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} +--- +# Source: projectcalico.org.v3/templates/calico/projectcalico.org_felixconfigurations.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: felixconfigurations.projectcalico.org +spec: + group: projectcalico.org + names: + kind: FelixConfiguration + listKind: FelixConfigurationList + plural: felixconfigurations + singular: felixconfiguration + preserveUnknownFields: false + scope: Cluster + versions: + - name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + allowIPIPPacketsFromWorkloads: + type: boolean + allowVXLANPacketsFromWorkloads: + type: boolean + awsSrcDstCheck: + enum: + - DoNothing + - Enable + - Disable + type: string + bpfAttachType: + enum: + - TC + - TCX + type: string + bpfCTLBLogFilter: + type: string + bpfConnectTimeLoadBalancing: + enum: + - TCP + - Enabled + - Disabled + type: string + bpfConnectTimeLoadBalancingEnabled: + type: boolean + bpfConntrackLogLevel: + enum: + - "Off" + - Debug + type: string + bpfConntrackMode: + enum: + - Auto + - Userspace + - BPFProgram + type: string + bpfConntrackTimeouts: + properties: + creationGracePeriod: + pattern: ^(([0-9]*(\.[0-9]*)?(ms|s|h|m|us)+)+|Auto)$ + type: string + genericTimeout: + pattern: ^(([0-9]*(\.[0-9]*)?(ms|s|h|m|us)+)+|Auto)$ + type: string + icmpTimeout: + pattern: ^(([0-9]*(\.[0-9]*)?(ms|s|h|m|us)+)+|Auto)$ + type: string + tcpEstablished: + pattern: ^(([0-9]*(\.[0-9]*)?(ms|s|h|m|us)+)+|Auto)$ + type: string + tcpFinsSeen: + pattern: ^(([0-9]*(\.[0-9]*)?(ms|s|h|m|us)+)+|Auto)$ + type: string + tcpResetSeen: + pattern: ^(([0-9]*(\.[0-9]*)?(ms|s|h|m|us)+)+|Auto)$ + type: string + tcpSynSent: + pattern: ^(([0-9]*(\.[0-9]*)?(ms|s|h|m|us)+)+|Auto)$ + type: string + udpTimeout: + pattern: ^(([0-9]*(\.[0-9]*)?(ms|s|h|m|us)+)+|Auto)$ + type: string + type: object + bpfDSROptoutCIDRs: + items: + type: string + type: array + bpfDataIfacePattern: + type: string + bpfDisableGROForIfaces: + type: string + bpfDisableUnprivileged: + type: boolean + bpfEnabled: + type: boolean + bpfEnforceRPF: + pattern: ^(?i)(Disabled|Strict|Loose)?$ + type: string + bpfExcludeCIDRsFromNAT: + items: + type: string + type: array + bpfExportBufferSizeMB: + type: integer + bpfExtToServiceConnmark: + type: integer + bpfExternalServiceMode: + pattern: ^(?i)(Tunnel|DSR)?$ + type: string + bpfForceTrackPacketsFromIfaces: + items: + type: string + type: array + bpfHostConntrackBypass: + type: boolean + bpfHostNetworkedNATWithoutCTLB: + enum: + - Enabled + - Disabled + type: string + bpfJITHardening: + allOf: + - enum: + - Auto + - Strict + - enum: + - Auto + - Strict + type: string + bpfKubeProxyHealthzPort: + type: integer + bpfKubeProxyIptablesCleanupEnabled: + type: boolean + bpfKubeProxyMinSyncPeriod: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + bpfL3IfacePattern: + type: string + bpfLogFilters: + additionalProperties: + type: string + type: object + bpfLogLevel: + pattern: ^(?i)(Off|Info|Debug)?$ + type: string + bpfMaglevMaxEndpointsPerService: + type: integer + bpfMaglevMaxServices: + type: integer + bpfMapSizeConntrack: + type: integer + bpfMapSizeConntrackCleanupQueue: + minimum: 1 + type: integer + bpfMapSizeConntrackScaling: + pattern: ^(?i)(Disabled|DoubleIfFull)?$ + type: string + bpfMapSizeIPSets: + type: integer + bpfMapSizeIfState: + type: integer + bpfMapSizeNATAffinity: + type: integer + bpfMapSizeNATBackend: + type: integer + bpfMapSizeNATFrontend: + type: integer + bpfMapSizePerCpuConntrack: + type: integer + bpfMapSizeRoute: + type: integer + bpfPSNATPorts: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + bpfPolicyDebugEnabled: + type: boolean + bpfProfiling: + enum: + - Enabled + - Disabled + type: string + bpfRedirectToPeer: + enum: + - Enabled + - Disabled + type: string + cgroupV2Path: + type: string + chainInsertMode: + pattern: ^(?i)(Insert|Append)?$ + type: string + dataplaneDriver: + type: string + dataplaneWatchdogTimeout: + type: string + debugDisableLogDropping: + type: boolean + debugHost: + type: string + debugMemoryProfilePath: + type: string + debugPort: + type: integer + debugSimulateCalcGraphHangAfter: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + debugSimulateDataplaneApplyDelay: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + debugSimulateDataplaneHangAfter: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + defaultEndpointToHostAction: + pattern: ^(?i)(Drop|Accept|Return)?$ + type: string + deviceRouteProtocol: + type: integer + deviceRouteSourceAddress: + type: string + deviceRouteSourceAddressIPv6: + type: string + disableConntrackInvalidCheck: + type: boolean + endpointReportingDelay: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + endpointReportingEnabled: + type: boolean + endpointStatusPathPrefix: + type: string + externalNodesList: + items: + type: string + type: array + failsafeInboundHostPorts: + items: + properties: + net: + type: string + port: + type: integer + protocol: + type: string + required: + - port + type: object + type: array + failsafeOutboundHostPorts: + items: + properties: + net: + type: string + port: + type: integer + protocol: + type: string + required: + - port + type: object + type: array + featureDetectOverride: + pattern: ^([a-zA-Z0-9-_]+=(true|false|),)*([a-zA-Z0-9-_]+=(true|false|))?$ + type: string + featureGates: + pattern: ^([a-zA-Z0-9-_]+=([^=]+),)*([a-zA-Z0-9-_]+=([^=]+))?$ + type: string + floatingIPs: + enum: + - Enabled + - Disabled + type: string + flowLogsCollectorDebugTrace: + type: boolean + flowLogsFlushInterval: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + flowLogsGoldmaneServer: + type: string + flowLogsLocalReporter: + enum: + - Disabled + - Enabled + type: string + flowLogsPolicyEvaluationMode: + enum: + - None + - Continuous + type: string + genericXDPEnabled: + type: boolean + goGCThreshold: + type: integer + goMaxProcs: + type: integer + goMemoryLimitMB: + type: integer + healthEnabled: + type: boolean + healthHost: + type: string + healthPort: + type: integer + healthTimeoutOverrides: + items: + properties: + name: + type: string + timeout: + type: string + required: + - name + - timeout + type: object + type: array + interfaceExclude: + type: string + interfacePrefix: + type: string + interfaceRefreshInterval: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + ipForwarding: + enum: + - Enabled + - Disabled + type: string + ipipEnabled: + type: boolean + ipipMTU: + type: integer + ipsetsRefreshInterval: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + iptablesBackend: + enum: + - Legacy + - NFT + - Auto + pattern: ^(?i)(Auto|Legacy|NFT)?$ + type: string + iptablesFilterAllowAction: + pattern: ^(?i)(Accept|Return)?$ + type: string + iptablesFilterDenyAction: + pattern: ^(?i)(Drop|Reject)?$ + type: string + iptablesLockProbeInterval: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + iptablesMangleAllowAction: + pattern: ^(?i)(Accept|Return)?$ + type: string + iptablesMarkMask: + format: int32 + type: integer + iptablesNATOutgoingInterfaceFilter: + type: string + iptablesPostWriteCheckInterval: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + iptablesRefreshInterval: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + ipv6Support: + type: boolean + kubeNodePortRanges: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + logActionRateLimit: + pattern: ^[1-9]\d{0,3}/(?:second|minute|hour|day)$ + type: string + logActionRateLimitBurst: + maximum: 9999 + minimum: 0 + type: integer + logDebugFilenameRegex: + type: string + logFilePath: + type: string + logPrefix: + pattern: "^([a-zA-Z0-9%: /_-])*$" + type: string + logSeverityFile: + pattern: ^(?i)(Trace|Debug|Info|Warning|Error|Fatal)?$ + type: string + logSeverityScreen: + pattern: ^(?i)(Trace|Debug|Info|Warning|Error|Fatal)?$ + type: string + logSeveritySys: + pattern: ^(?i)(Trace|Debug|Info|Warning|Error|Fatal)?$ + type: string + maxIpsetSize: + type: integer + metadataAddr: + type: string + metadataPort: + type: integer + mtuIfacePattern: + type: string + natOutgoingAddress: + type: string + natOutgoingExclusions: + enum: + - IPPoolsOnly + - IPPoolsAndHostIPs + type: string + natPortRange: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + netlinkTimeout: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + nftablesFilterAllowAction: + pattern: ^(?i)(Accept|Return)?$ + type: string + nftablesFilterDenyAction: + pattern: ^(?i)(Drop|Reject)?$ + type: string + nftablesMangleAllowAction: + pattern: ^(?i)(Accept|Return)?$ + type: string + nftablesMarkMask: + format: int32 + type: integer + nftablesMode: + default: Auto + enum: + - Disabled + - Enabled + - Auto + type: string + nftablesRefreshInterval: + type: string + openstackRegion: + type: string + policySyncPathPrefix: + type: string + programClusterRoutes: + enum: + - Enabled + - Disabled + type: string + prometheusGoMetricsEnabled: + type: boolean + prometheusMetricsCAFile: + type: string + prometheusMetricsCertFile: + type: string + prometheusMetricsClientAuth: + type: string + prometheusMetricsEnabled: + type: boolean + prometheusMetricsHost: + type: string + prometheusMetricsKeyFile: + type: string + prometheusMetricsPort: + type: integer + prometheusProcessMetricsEnabled: + type: boolean + prometheusWireGuardMetricsEnabled: + type: boolean + removeExternalRoutes: + type: boolean + reportingInterval: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + reportingTTL: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + requireMTUFile: + type: boolean + routeRefreshInterval: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + routeSource: + pattern: ^(?i)(WorkloadIPs|CalicoIPAM)?$ + type: string + routeSyncDisabled: + type: boolean + routeTableRange: + properties: + max: + type: integer + min: + type: integer + required: + - max + - min + type: object + routeTableRanges: + items: + properties: + max: + type: integer + min: + type: integer + required: + - max + - min + type: object + type: array + serviceLoopPrevention: + pattern: ^(?i)(Drop|Reject|Disabled)?$ + type: string + sidecarAccelerationEnabled: + type: boolean + usageReportingEnabled: + type: boolean + usageReportingInitialDelay: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + usageReportingInterval: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + useInternalDataplaneDriver: + type: boolean + vxlanEnabled: + type: boolean + vxlanMTU: + type: integer + vxlanMTUV6: + type: integer + vxlanPort: + type: integer + vxlanVNI: + type: integer + windowsManageFirewallRules: + enum: + - Enabled + - Disabled + type: string + wireguardEnabled: + type: boolean + wireguardEnabledV6: + type: boolean + wireguardHostEncryptionEnabled: + type: boolean + wireguardInterfaceName: + type: string + wireguardInterfaceNameV6: + type: string + wireguardKeepAlive: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + wireguardListeningPort: + type: integer + wireguardListeningPortV6: + type: integer + wireguardMTU: + type: integer + wireguardMTUV6: + type: integer + wireguardRoutingRulePriority: + type: integer + wireguardThreadingEnabled: + type: boolean + workloadSourceSpoofing: + pattern: ^(?i)(Disabled|Any)?$ + type: string + xdpEnabled: + type: boolean + xdpRefreshInterval: + pattern: ^([0-9]+(\\.[0-9]+)?(ms|s|m|h))*$ + type: string + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +--- +# Source: projectcalico.org.v3/templates/calico/projectcalico.org_globalnetworkpolicies.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: globalnetworkpolicies.projectcalico.org +spec: + group: projectcalico.org + names: + kind: GlobalNetworkPolicy + listKind: GlobalNetworkPolicyList + plural: globalnetworkpolicies + shortNames: + - gnp + - cgnp + singular: globalnetworkpolicy + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.tier + name: Tier + type: string + - jsonPath: .spec.order + name: Order + type: number + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + applyOnForward: + type: boolean + doNotTrack: + type: boolean + egress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: + properties: + exact: + type: string + prefix: + type: string + type: object + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: + enum: + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + required: + - action + type: object + type: array + ingress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: + properties: + exact: + type: string + prefix: + type: string + type: object + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: + enum: + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + required: + - action + type: object + type: array + namespaceSelector: + type: string + order: + type: number + performanceHints: + items: + enum: + - AssumeNeededOnEveryNode + type: string + type: array + preDNAT: + type: boolean + selector: + type: string + serviceAccountSelector: + type: string + tier: + default: default + type: string + types: + items: + enum: + - Ingress + - Egress + type: string + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-list-type: set + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} +--- +# Source: projectcalico.org.v3/templates/calico/projectcalico.org_globalnetworksets.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: globalnetworksets.projectcalico.org +spec: + group: projectcalico.org + names: + kind: GlobalNetworkSet + listKind: GlobalNetworkSetList + plural: globalnetworksets + shortNames: + - gns + singular: globalnetworkset + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} +--- +# Source: projectcalico.org.v3/templates/calico/projectcalico.org_hostendpoints.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: hostendpoints.projectcalico.org +spec: + group: projectcalico.org + names: + kind: HostEndpoint + listKind: HostEndpointList + plural: hostendpoints + shortNames: + - hep + - heps + singular: hostendpoint + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - description: + The node name identifying the Calico node instance that is targeted + by this HostEndpoint + jsonPath: .spec.node + name: Node + type: string + - description: The name of the interface that is targeted by this HostEndpoint + jsonPath: .spec.interfaceName + name: Interface + type: string + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + expectedIPs: + items: + type: string + type: array + x-kubernetes-list-type: set + interfaceName: + type: string + node: + type: string + ports: + items: + properties: + name: + type: string + port: + maximum: 65535 + minimum: 0 + type: integer + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + required: + - name + - port + - protocol + type: object + type: array + profiles: + items: + type: string + type: array + x-kubernetes-list-type: set + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} +--- +# Source: projectcalico.org.v3/templates/calico/projectcalico.org_ipamblocks.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: ipamblocks.projectcalico.org +spec: + group: projectcalico.org + names: + kind: IPAMBlock + listKind: IPAMBlockList + plural: ipamblocks + singular: ipamblock + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - description: The block CIDR + jsonPath: .spec.cidr + name: CIDR + type: string + - description: The block affinity + jsonPath: .spec.affinity + name: Affinity + type: string + - description: The time at which affinity was claimed + jsonPath: .spec.affinityClaimTime + name: ClaimTime + type: date + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + affinity: + pattern: ^(host|virtual):[a-zA-Z0-9\.\-_]+$ + type: string + affinityClaimTime: + format: date-time + type: string + allocations: + items: + type: integer + # TODO: This nullable is manually added in. We should update controller-gen + # to handle []*int properly itself. + nullable: true + type: array + attributes: + items: + properties: + alternate: + additionalProperties: + type: string + type: object + handle_id: + type: string + secondary: + additionalProperties: + type: string + type: object + type: object + type: array + cidr: + format: cidr + type: string + deleted: + type: boolean + sequenceNumber: + default: 0 + format: int64 + type: integer + sequenceNumberForAllocation: + additionalProperties: + format: int64 + type: integer + type: object + strictAffinity: + type: boolean + unallocated: + items: + type: integer + type: array + required: + - allocations + - attributes + - cidr + - strictAffinity + - unallocated + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} +--- +# Source: projectcalico.org.v3/templates/calico/projectcalico.org_ipamconfigurations.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: ipamconfigurations.projectcalico.org +spec: + group: projectcalico.org + names: + kind: IPAMConfiguration + listKind: IPAMConfigurationList + plural: ipamconfigurations + shortNames: + - ipamconfig + - ipamconfigs + singular: ipamconfiguration + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - description: Whether borrowing IP addresses is allowed + jsonPath: .spec.strictAffinity + name: StrictAffinity + type: boolean + - description: The max number of blocks that can be affine to each host + jsonPath: .spec.maxBlocksPerHost + name: MaxBlocksPerHost + type: integer + - description: Whether or not to auto allocate blocks to hosts + jsonPath: .spec.autoAllocateBlocks + name: AutoAllocateBlocks + type: boolean + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + autoAllocateBlocks: + default: true + type: boolean + kubeVirtVMAddressPersistence: + enum: + - Enabled + - Disabled + type: string + maxBlocksPerHost: + default: 0 + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + strictAffinity: + default: false + type: boolean + required: + - autoAllocateBlocks + - strictAffinity + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} +--- +# Source: projectcalico.org.v3/templates/calico/projectcalico.org_ipamhandles.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: ipamhandles.projectcalico.org +spec: + group: projectcalico.org + names: + kind: IPAMHandle + listKind: IPAMHandleList + plural: ipamhandles + singular: ipamhandle + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - description: The handle ID + jsonPath: .spec.handleID + name: ID + type: string + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + block: + additionalProperties: + type: integer + type: object + deleted: + type: boolean + handleID: + type: string + required: + - block + - handleID + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: {} +--- +# Source: projectcalico.org.v3/templates/calico/projectcalico.org_ippools.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: ippools.projectcalico.org +spec: + group: projectcalico.org + names: + kind: IPPool + listKind: IPPoolList + plural: ippools + singular: ippool + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - description: The pool CIDR + jsonPath: .spec.cidr + name: CIDR + type: string + - description: The VXLAN mode for this pool + jsonPath: .spec.vxlanMode + name: VXLAN + type: string + - description: The IPIP mode for this pool + jsonPath: .spec.ipipMode + name: IPIP + type: string + - description: Whether outgoing NAT is enabled for this pool + jsonPath: .spec.natOutgoing + name: NAT + type: boolean + - description: Whether or not this pool is available for IP allocations + jsonPath: .status.conditions[?(@.type=='Allocatable')].status + name: Allocatable + type: string + - description: The age of the pool + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + allowedUses: + items: + enum: + - Workload + - Tunnel + - LoadBalancer + type: string + type: array + x-kubernetes-list-type: set + assignmentMode: + default: Automatic + enum: + - Automatic + - Manual + type: string + blockSize: + maximum: 128 + minimum: 0 + type: integer + cidr: + format: cidr + type: string + disableBGPExport: + type: boolean + disabled: + type: boolean + ipipMode: + enum: + - Never + - Always + - CrossSubnet + type: string + namespaceSelector: + type: string + natOutgoing: + type: boolean + nodeSelector: + type: string + vxlanMode: + enum: + - Never + - Always + - CrossSubnet + type: string + required: + - cidr + type: object + status: + properties: + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + maxLength: 32768 + type: string + observedGeneration: + format: int64 + minimum: 0 + type: integer + reason: + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: projectcalico.org.v3/templates/calico/projectcalico.org_ipreservations.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: ipreservations.projectcalico.org +spec: + group: projectcalico.org + names: + kind: IPReservation + listKind: IPReservationList + plural: ipreservations + singular: ipreservation + preserveUnknownFields: false + scope: Cluster + versions: + - name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + reservedCIDRs: + format: cidr + items: + type: string + type: array + x-kubernetes-list-type: set + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +--- +# Source: projectcalico.org.v3/templates/calico/projectcalico.org_kubecontrollersconfigurations.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: kubecontrollersconfigurations.projectcalico.org +spec: + group: projectcalico.org + names: + kind: KubeControllersConfiguration + listKind: KubeControllersConfigurationList + plural: kubecontrollersconfigurations + shortNames: + - kcc + - kccs + singular: kubecontrollersconfiguration + preserveUnknownFields: false + scope: Cluster + versions: + - name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + controllers: + properties: + loadBalancer: + properties: + assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly + type: string + type: object + namespace: + properties: + reconcilerPeriod: + type: string + type: object + node: + properties: + hostEndpoint: + properties: + autoCreate: + enum: + - Enabled + - Disabled + type: string + createDefaultHostEndpoint: + type: string + templates: + items: + properties: + generateName: + maxLength: 253 + type: string + interfaceCIDRs: + items: + type: string + type: array + x-kubernetes-list-type: set + interfacePattern: + type: string + labels: + additionalProperties: + type: string + type: object + nodeSelector: + type: string + type: object + type: array + type: object + leakGracePeriod: + type: string + reconcilerPeriod: + type: string + syncLabels: + enum: + - Enabled + - Disabled + type: string + type: object + policy: + properties: + reconcilerPeriod: + type: string + type: object + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object + serviceAccount: + properties: + reconcilerPeriod: + type: string + type: object + workloadEndpoint: + properties: + reconcilerPeriod: + type: string + type: object + type: object + debugProfilePort: + format: int32 + maximum: 65535 + minimum: 0 + type: integer + etcdV3CompactionPeriod: + type: string + healthChecks: + default: Enabled + enum: + - Enabled + - Disabled + type: string + logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic + type: string + prometheusMetricsPort: + maximum: 65535 + minimum: 0 + type: integer + required: + - controllers + type: object + status: + properties: + environmentVars: + additionalProperties: + type: string + type: object + runningConfig: + properties: + controllers: + properties: + loadBalancer: + properties: + assignIPs: + default: AllServices + enum: + - AllServices + - RequestedServicesOnly + type: string + type: object + namespace: + properties: + reconcilerPeriod: + type: string + type: object + node: + properties: + hostEndpoint: + properties: + autoCreate: + enum: + - Enabled + - Disabled + type: string + createDefaultHostEndpoint: + type: string + templates: + items: + properties: + generateName: + maxLength: 253 + type: string + interfaceCIDRs: + items: + type: string + type: array + x-kubernetes-list-type: set + interfacePattern: + type: string + labels: + additionalProperties: + type: string + type: object + nodeSelector: + type: string + type: object + type: array + type: object + leakGracePeriod: + type: string + reconcilerPeriod: + type: string + syncLabels: + enum: + - Enabled + - Disabled + type: string + type: object + policy: + properties: + reconcilerPeriod: + type: string + type: object + policyMigration: + properties: + enabled: + default: Enabled + enum: + - Disabled + - Enabled + type: string + type: object + serviceAccount: + properties: + reconcilerPeriod: + type: string + type: object + workloadEndpoint: + properties: + reconcilerPeriod: + type: string + type: object + type: object + debugProfilePort: + format: int32 + maximum: 65535 + minimum: 0 + type: integer + etcdV3CompactionPeriod: + type: string + healthChecks: + default: Enabled + enum: + - Enabled + - Disabled + type: string + logSeverityScreen: + enum: + - None + - Debug + - Info + - Warning + - Error + - Fatal + - Panic + type: string + prometheusMetricsPort: + maximum: 65535 + minimum: 0 + type: integer + required: + - controllers + type: object + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: projectcalico.org.v3/templates/calico/projectcalico.org_networkpolicies.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: networkpolicies.projectcalico.org +spec: + group: projectcalico.org + names: + kind: NetworkPolicy + listKind: NetworkPolicyList + plural: networkpolicies + shortNames: + - cnp + - caliconetworkpolicy + singular: networkpolicy + preserveUnknownFields: false + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.tier + name: Tier + type: string + - jsonPath: .spec.order + name: Order + type: number + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + egress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: + properties: + exact: + type: string + prefix: + type: string + type: object + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: + enum: + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + required: + - action + type: object + type: array + ingress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: + properties: + exact: + type: string + prefix: + type: string + type: object + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: + enum: + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + required: + - action + type: object + type: array + order: + type: number + performanceHints: + items: + enum: + - AssumeNeededOnEveryNode + type: string + type: array + selector: + type: string + serviceAccountSelector: + type: string + tier: + default: default + type: string + types: + items: + enum: + - Ingress + - Egress + type: string + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-list-type: set + type: object + required: + - metadata + - spec + type: object + selectableFields: + - jsonPath: .spec.tier + served: true + storage: true + subresources: {} +--- +# Source: projectcalico.org.v3/templates/calico/projectcalico.org_networksets.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: networksets.projectcalico.org +spec: + group: projectcalico.org + names: + kind: NetworkSet + listKind: NetworkSetList + plural: networksets + singular: networkset + preserveUnknownFields: false + scope: Namespaced + versions: + - name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +--- +# Source: projectcalico.org.v3/templates/calico/projectcalico.org_stagedglobalnetworkpolicies.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: stagedglobalnetworkpolicies.projectcalico.org +spec: + group: projectcalico.org + names: + kind: StagedGlobalNetworkPolicy + listKind: StagedGlobalNetworkPolicyList + plural: stagedglobalnetworkpolicies + shortNames: + - sgnp + singular: stagedglobalnetworkpolicy + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.tier + name: Tier + type: string + - jsonPath: .spec.order + name: Order + type: number + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + applyOnForward: + type: boolean + doNotTrack: + type: boolean + egress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: + properties: + exact: + type: string + prefix: + type: string + type: object + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: + enum: + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + required: + - action + type: object + type: array + ingress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: + properties: + exact: + type: string + prefix: + type: string + type: object + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: + enum: + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + required: + - action + type: object + type: array + namespaceSelector: + type: string + order: + type: number + performanceHints: + items: + enum: + - AssumeNeededOnEveryNode + type: string + type: array + preDNAT: + type: boolean + selector: + type: string + serviceAccountSelector: + type: string + stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore + type: string + tier: + default: default + type: string + types: + items: + enum: + - Ingress + - Egress + type: string + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-list-type: set + type: object + required: + - metadata + - spec + type: object + selectableFields: + - jsonPath: .spec.tier + served: true + storage: true + subresources: {} +--- +# Source: projectcalico.org.v3/templates/calico/projectcalico.org_stagedkubernetesnetworkpolicies.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: stagedkubernetesnetworkpolicies.projectcalico.org +spec: + group: projectcalico.org + names: + kind: StagedKubernetesNetworkPolicy + listKind: StagedKubernetesNetworkPolicyList + plural: stagedkubernetesnetworkpolicies + shortNames: + - sknp + singular: stagedkubernetesnetworkpolicy + preserveUnknownFields: false + scope: Namespaced + versions: + - name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + egress: + items: + properties: + ports: + items: + properties: + endPort: + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + protocol: + type: string + type: object + type: array + x-kubernetes-list-type: atomic + to: + items: + properties: + ipBlock: + properties: + cidr: + type: string + except: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - cidr + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: array + ingress: + items: + properties: + from: + items: + properties: + ipBlock: + properties: + cidr: + type: string + except: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - cidr + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + podSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + ports: + items: + properties: + endPort: + format: int32 + type: integer + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + protocol: + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: array + podSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + policyTypes: + items: + type: string + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-list-type: set + stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore + type: string + type: object + required: + - metadata + - spec + type: object + served: true + storage: true +--- +# Source: projectcalico.org.v3/templates/calico/projectcalico.org_stagednetworkpolicies.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: stagednetworkpolicies.projectcalico.org +spec: + group: projectcalico.org + names: + kind: StagedNetworkPolicy + listKind: StagedNetworkPolicyList + plural: stagednetworkpolicies + shortNames: + - scnp + - snp + singular: stagednetworkpolicy + preserveUnknownFields: false + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.tier + name: Tier + type: string + - jsonPath: .spec.order + name: Order + type: number + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + egress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: + properties: + exact: + type: string + prefix: + type: string + type: object + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: + enum: + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + required: + - action + type: object + type: array + ingress: + items: + properties: + action: + enum: + - Allow + - Deny + - Log + - Pass + type: string + destination: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + http: + properties: + methods: + items: + type: string + type: array + paths: + items: + properties: + exact: + type: string + prefix: + type: string + type: object + type: array + type: object + icmp: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + ipVersion: + enum: + - 4 + - 6 + type: integer + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + type: object + notICMP: + properties: + code: + maximum: 255 + minimum: 0 + type: integer + type: + maximum: 255 + minimum: 0 + type: integer + type: object + notProtocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + protocol: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + source: + properties: + namespaceSelector: + type: string + nets: + items: + type: string + type: array + x-kubernetes-list-type: set + notNets: + items: + type: string + type: array + notPorts: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + notSelector: + type: string + ports: + items: + anyOf: + - type: integer + - type: string + pattern: ^.* + x-kubernetes-int-or-string: true + type: array + selector: + type: string + serviceAccounts: + properties: + names: + items: + type: string + type: array + x-kubernetes-list-type: set + selector: + type: string + type: object + services: + properties: + name: + type: string + namespace: + type: string + type: object + type: object + required: + - action + type: object + type: array + order: + type: number + performanceHints: + items: + enum: + - AssumeNeededOnEveryNode + type: string + type: array + selector: + type: string + serviceAccountSelector: + type: string + stagedAction: + enum: + - Set + - Delete + - Learn + - Ignore + type: string + tier: + default: default + type: string + types: + items: + enum: + - Ingress + - Egress + type: string + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-list-type: set + type: object + required: + - metadata + - spec + type: object + selectableFields: + - jsonPath: .spec.tier + served: true + storage: true + subresources: {} +--- +# Source: projectcalico.org.v3/templates/calico/projectcalico.org_tiers.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: tiers.projectcalico.org +spec: + group: projectcalico.org + names: + kind: Tier + listKind: TierList + plural: tiers + singular: tier + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - description: Order in which the tier is applied + jsonPath: .spec.order + name: Order + type: integer + - description: Default action for the tier + jsonPath: .spec.defaultAction + name: DefaultAction + type: string + name: v3 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + defaultAction: + allOf: + - enum: + - Allow + - Deny + - Log + - Pass + - enum: + - Pass + - Deny + type: string + order: + type: number + type: object + required: + - metadata + - spec + type: object + x-kubernetes-validations: + - message: The 'kube-admin' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-admin' ? self.spec.defaultAction == + 'Pass' : true" + - message: The 'kube-baseline' tier must have default action 'Pass' + rule: + "self.metadata.name == 'kube-baseline' ? self.spec.defaultAction + == 'Pass' : true" + - message: The 'default' tier must have default action 'Deny' + rule: + "self.metadata.name == 'default' ? self.spec.defaultAction == 'Deny' + : true" + served: true + storage: true + subresources: {} diff --git a/metadata.mk b/metadata.mk index 1a09331292f..287f8c4d468 100644 --- a/metadata.mk +++ b/metadata.mk @@ -3,30 +3,27 @@ ################################################################################################# # The version of calico/go-build and calico/base to use. -GO_BUILD_VER=1.25.1-llvm18.1.8-k8s1.33.5 -CALICO_BASE_VER=ubi9-1757635857 - -# Env var to ACK Ginkgo deprecation warnings, may need updating with go-build. -ACK_GINKGO=ACK_GINKGO_DEPRECATIONS=1.16.5 +GO_BUILD_VER=1.25.7-llvm18.1.8-k8s1.34.4 +CALICO_BASE_VER=ubi9-1771532994 # Version of Kubernetes to use for tests, rancher/kubectl, and kubectl binary release. -K8S_VERSION=v1.33.5 +K8S_VERSION=v1.34.3 # Version of various tools used in the build and tests. COREDNS_VERSION=1.5.2 -CRANE_VERSION := v0.20.6 -ETCD_VERSION=v3.5.6 +CRANE_VERSION=v0.20.7 +ETCD_VERSION=v3.5.24 GHR_VERSION=v0.17.0 GITHUB_CLI_VERSION=2.76.2 GOTESTSUM_VERSION=v1.12.3 HELM_VERSION=v3.11.3 -KINDEST_NODE_VERSION=v1.33.4 +KINDEST_NODE_VERSION=v1.34.3 KIND_VERSION=v0.29.0 # Configuration for Semaphore/Github integration. This needs to be set # differently for a forked repo. -ORGANIZATION = projectcalico -GIT_REPO = calico +ORGANIZATION ?= projectcalico +GIT_REPO ?= calico RELEASE_BRANCH_PREFIX ?=release DEV_TAG_SUFFIX ?= 0.dev @@ -53,7 +50,7 @@ WINDOWS_DIST = dist/windows # The Windows HPC container version used as base for Calico Windows images WINDOWS_HPC_VERSION ?= v1.0.0 # The Windows versions used as base for Calico Windows images -WINDOWS_VERSIONS ?= 1809 ltsc2022 +WINDOWS_VERSIONS ?= ltsc2019 ltsc2022 # The CNI plugin and flannel code that will be cloned and rebuilt with this repo's go-build image # whenever the cni-plugin image is created. @@ -61,13 +58,15 @@ CNI_VERSION=master FLANNEL_VERSION=main # The libbpf version to use -LIBBPF_VERSION=v1.4.6 +LIBBPF_VERSION=v1.6.2 # The bpftool image to use; this is the output of the https://github.com/projectcalico/bpftool repo. BPFTOOL_IMAGE=calico/bpftool:v7.5.0 # The operator branch corresponding to this branch. -OPERATOR_BRANCH=master +OPERATOR_BRANCH ?= master +OPERATOR_ORGANIZATION ?= tigera +OPERATOR_GIT_REPO ?= operator # quay.io expiry time for hashrelease/dev images QUAY_EXPIRE_DAYS=90 diff --git a/networking-calico/Dockerfile b/networking-calico/Dockerfile index 2af3307e570..67caacb426e 100644 --- a/networking-calico/Dockerfile +++ b/networking-calico/Dockerfile @@ -4,4 +4,4 @@ FROM python:3.8 COPY --from=etcd /usr/local/bin/etcd /usr/local/bin/etcd -RUN pip3 install tox==3.25.1 +RUN pip3 install tox==3.28.0 diff --git a/networking-calico/Makefile b/networking-calico/Makefile index acdde271a7b..96e143c94ff 100644 --- a/networking-calico/Makefile +++ b/networking-calico/Makefile @@ -17,11 +17,8 @@ tox: build-test-container tox-%: upper-constraints-%.txt $(MAKE) tox PIP_CONSTRAINT=/code/upper-constraints-$*.txt -upper-constraints-yoga.txt: - curl -fsSL --retry 5 https://releases.openstack.org/constraints/upper/yoga -o $@ - upper-constraints-caracal.txt: - curl -fsSL --retry 5 https://raw.githubusercontent.com/openstack/requirements/refs/heads/stable/2024.1/upper-constraints.txt -o $@ + curl -fsSL --retry 5 https://raw.githubusercontent.com/openstack/requirements/refs/heads/$$(./infer-openstack-branch.sh caracal requirements)/upper-constraints.txt -o $@ fmtpy: build-test-container $(RUN_TEST_CONTAINER) tox -e black diff --git a/networking-calico/README.md b/networking-calico/README.md index 74ad4929638..7072322e11d 100644 --- a/networking-calico/README.md +++ b/networking-calico/README.md @@ -16,11 +16,11 @@ features with Calico, please see http://docs.projectcalico.org/master. # Version skew -Calico for OpenStack is tested against two recent LTS OpenStack versions, as [defined by +Calico for OpenStack is tested against one or two recent LTS OpenStack versions, as [defined by Canonical](https://canonical-openstack.readthedocs-hosted.com/en/latest/reference/release-cycle-and-supported-versions/). For recent Calico versions the corresponding versions of OpenStack are as follows. | Calico version | OpenStack versions | | -------------- | ------------------ | -| master | Yoga, Caracal | +| master | Caracal | | v3.30 | Yoga, Caracal | diff --git a/networking-calico/devstack/bootstrap.sh b/networking-calico/devstack/bootstrap.sh index e61701ab81f..e0fd06f85dd 100755 --- a/networking-calico/devstack/bootstrap.sh +++ b/networking-calico/devstack/bootstrap.sh @@ -53,11 +53,16 @@ sudo pip list || true # For a single node Calico/DevStack cluster, the environment should leave # SERVICE_HOST unset. # +# OPENSTACK_RELEASE +# +# The OpenStack release to test with, e.g. "yoga" or "caracal". This is +# used to calculate the value of DEVSTACK_BRANCH when not already set. +# # DEVSTACK_BRANCH # -# By default this script uses the master branch of devstack. To use a -# different branch, set the DEVSTACK_BRANCH environment variable before -# running this script; for example: +# By default this script uses the appropriate DevStack branch for +# OPENSTACK_RELEASE. To use a different branch, set the DEVSTACK_BRANCH +# environment variable before running this script; for example: # # export DEVSTACK_BRANCH=stable/liberty # @@ -80,27 +85,15 @@ sudo pip list || true # # ------------------------------------------------------------------------------ -# Handle branch name transition from "stable/yoga" to "unmaintained/yoga". The DevStack repo -# internally still uses "stable/yoga" for all its defaults even though all the actual branch names -# have changed to "unmaintained/yoga". -if [ "${DEVSTACK_BRANCH}" = unmaintained/yoga ]; then - export CINDER_BRANCH=unmaintained/yoga - export GLANCE_BRANCH=unmaintained/yoga - export KEYSTONE_BRANCH=unmaintained/yoga - export NEUTRON_BRANCH=unmaintained/yoga - export NOVA_BRANCH=unmaintained/yoga - export PLACEMENT_BRANCH=unmaintained/yoga - export REQUIREMENTS_BRANCH=unmaintained/yoga +if [ -z "${DEVSTACK_BRANCH}" ]; then + DEVSTACK_BRANCH=$(./infer-openstack-branch.sh ${OPENSTACK_RELEASE} devstack) fi # Set correct constraints for Tempest to use. We need to do this because we're pinning to a # different version of Tempest than the version that DevStack would naturally use. case "${DEVSTACK_BRANCH}" in - unmaintained/yoga ) - export UPPER_CONSTRAINTS_FILE=https://releases.openstack.org/constraints/upper/yoga - ;; - stable/2024.1 ) # Caracal - export UPPER_CONSTRAINTS_FILE=https://raw.githubusercontent.com/openstack/requirements/refs/heads/stable/2024.1/upper-constraints.txt + * ) + export UPPER_CONSTRAINTS_FILE=https://raw.githubusercontent.com/openstack/requirements/refs/heads/${DEVSTACK_BRANCH}/upper-constraints.txt ;; esac @@ -193,6 +186,7 @@ sudo tools/create-stack-user.sh cd .. sudo mkdir -p /opt/stack sudo mv devstack /opt/stack +sudo chmod +x /opt/stack sudo chown -R stack:stack /opt/stack ls -ld /home/ ls -la /home/ @@ -204,6 +198,11 @@ ls -la /home/semaphore/calico # "semaphore" group. sudo adduser stack semaphore +# Guarantee that the stack user will be able to make a Git clone of /home/semaphore/calico. Since +# we started using partial cloning, *.promisor files under /home/semaphore/calico/.git/objects/pack +# naturally have permissions -rw-------, which means that stack won't be able to read them. +sudo chmod -R g+r /home/semaphore/calico + # Stack! sudo -u stack -H -E bash -x <<'EOF' ls -la /home/semaphore/calico diff --git a/networking-calico/devstack/plugin.sh b/networking-calico/devstack/plugin.sh index 89afd2d685a..be02ecadfe1 100644 --- a/networking-calico/devstack/plugin.sh +++ b/networking-calico/devstack/plugin.sh @@ -25,9 +25,10 @@ if [ "${Q_AGENT}" = calico-felix ]; then sudo apt-add-repository -y ppa:project-calico/master REPOS_UPDATED=False - # Also add BIRD project PPA as a package source. - LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 sudo add-apt-repository -y ppa:cz.nic-labs/bird - + if [ "${CALICO_BGP_MODE}" = bird ]; then + # Also add BIRD project PPA as a package source. + LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 sudo add-apt-repository -y ppa:cz.nic-labs/bird + fi ;; install) @@ -41,8 +42,10 @@ if [ "${Q_AGENT}" = calico-felix ]; then # Install ipset. install_package ipset - # Install BIRD. - install_package bird + if [ "${CALICO_BGP_MODE}" = bird ]; then + # Install BIRD. + install_package bird + fi # Install the Calico agent. sudo mkdir -p /etc/calico @@ -50,6 +53,7 @@ if [ "${Q_AGENT}" = calico-felix ]; then [global] DatastoreType = etcdv3 EtcdEndpoints = http://${SERVICE_HOST}:${ETCD_PORT} +EndpointStatusPathPrefix = none EOF if [ "${ENABLE_DEBUG_LOG_LEVEL}" = True ]; then sudo sh -c "cat >> /etc/calico/felix.cfg" << EOF @@ -138,8 +142,9 @@ EOF # maintain BIRD config for the cluster. export ETCDCTL_API=3 export ETCDCTL_ENDPOINTS=http://$SERVICE_HOST:$ETCD_PORT - run_process calico-bird \ - "${DEST}/calico/devstack/auto-bird-conf.sh ${HOST_IP} ${ETCD_BIN_DIR}/etcdctl" + if [ "${CALICO_BGP_MODE}" = bird ]; then + run_process calico-bird "${DEST}/calico/devstack/auto-bird-conf.sh ${HOST_IP} ${ETCD_BIN_DIR}/etcdctl" + fi # Run the Calico DHCP agent. sudo mkdir /var/log/neutron || true diff --git a/networking-calico/infer-openstack-branch.sh b/networking-calico/infer-openstack-branch.sh new file mode 100755 index 00000000000..9a1e59cb7f2 --- /dev/null +++ b/networking-calico/infer-openstack-branch.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# Infer the current branch name for the OpenStack release $1 in repo name $2. +if [ $# -ne 2 ]; then + echo "Usage: $0 " + echo + echo "For example: $0 caracal devstack" + exit 1 +fi +RELEASE=$1 +REPO=$2 + +# Translate modern OpenStack release names to their YEAR.NUMBER form. +FORMAL_RELEASE=${RELEASE} +case "${RELEASE}" in + caracal ) + FORMAL_RELEASE=2024.1 + ;; + gazpacho ) + FORMAL_RELEASE=2026.1 + ;; +esac + +if [ $(git ls-remote -h https://github.com/openstack/${REPO} refs/heads/stable/${FORMAL_RELEASE} | wc -l) = 1 ]; then + echo stable/${FORMAL_RELEASE} + exit 0 +fi + +if [ $(git ls-remote -h https://github.com/openstack/${REPO} refs/heads/unmaintained/${FORMAL_RELEASE} | wc -l) = 1 ]; then + echo unmaintained/${FORMAL_RELEASE} + exit 0 +fi + +echo "ERROR: can't identify branch for OpenStack ${RELEASE} in ${REPO}" +exit 1 diff --git a/networking-calico/networking_calico/agent/linux/dhcp.py b/networking-calico/networking_calico/agent/linux/dhcp.py index 05bdc93fee1..c41768a51b7 100644 --- a/networking-calico/networking_calico/agent/linux/dhcp.py +++ b/networking-calico/networking_calico/agent/linux/dhcp.py @@ -219,6 +219,8 @@ def _build_cmdline_callback(self, pid_file): # Add '--enable-ra'. cmd.append("--enable-ra") + # Don't advertise the MTU, since we don't have the value here + cmd.append("--ra-param=ns-*,mtu:off,0") # Enumerate precisely the TAP interfaces to listen on. cmd.remove("--interface=tap*") diff --git a/networking-calico/networking_calico/plugins/calico/context.py b/networking-calico/networking_calico/plugins/calico/context.py new file mode 100644 index 00000000000..7adc6c10df2 --- /dev/null +++ b/networking-calico/networking_calico/plugins/calico/context.py @@ -0,0 +1,19 @@ +# Copyright (c) 2025 Tigera, Inc. All Rights Reserved. +# +# 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. + + +class SGRUpdateContext(object): + def __init__(self, context, sgids): + self.plugin_context = context + self.sgids = sgids diff --git a/networking-calico/networking_calico/plugins/calico/plugin.py b/networking-calico/networking_calico/plugins/calico/plugin.py index ae44a471d55..813e48cbfbc 100644 --- a/networking-calico/networking_calico/plugins/calico/plugin.py +++ b/networking-calico/networking_calico/plugins/calico/plugin.py @@ -24,6 +24,8 @@ from oslo_log import log +from networking_calico.plugins.calico.context import SGRUpdateContext + LOG = log.getLogger(__name__) @@ -152,3 +154,27 @@ def create_floatingip( context.fip_update_port_id = new_floatingip["port_id"] self.mechanism_manager._call_on_drivers("update_floatingip", context) return new_floatingip + + def create_security_group_rule(self, context, security_group_rule): + rule = super().create_security_group_rule(context, security_group_rule) + sgids = [rule["security_group_id"]] + self._notify_sg_rule_updated(context, sgids) + return rule + + def create_security_group_rule_bulk(self, context, security_group_rules): + rules = super().create_security_group_rule_bulk_native( + context, security_group_rules + ) + sgids = set([r["security_group_id"] for r in rules]) + self._notify_sg_rule_updated(context, list(sgids)) + return rules + + def delete_security_group_rule(self, context, sgrid): + rule = self.get_security_group_rule(context, sgrid) + super().delete_security_group_rule(context, sgrid) + self._notify_sg_rule_updated(context, [rule["security_group_id"]]) + + def _notify_sg_rule_updated(self, context, sgids): + self.mechanism_manager._call_on_drivers( + "security_groups_rule_updated", SGRUpdateContext(context, sgids) + ) diff --git a/networking-calico/networking_calico/plugins/ml2/drivers/calico/mech_calico.py b/networking-calico/networking_calico/plugins/ml2/drivers/calico/mech_calico.py index 4af73738899..b09e353852f 100644 --- a/networking-calico/networking_calico/plugins/ml2/drivers/calico/mech_calico.py +++ b/networking-calico/networking_calico/plugins/ml2/drivers/calico/mech_calico.py @@ -25,9 +25,10 @@ # # It is implemented as a Neutron/ML2 mechanism driver. import contextlib -import inspect +from datetime import datetime, timedelta import os import re +import threading import uuid from functools import wraps @@ -40,7 +41,6 @@ from keystoneclient.v3.client import Client as KeystoneClient -import neutron.plugins.ml2.rpc as rpc from neutron.agent import rpc as agent_rpc from neutron.conf.agent import common as config from neutron.objects import ports as ports_object @@ -55,10 +55,10 @@ from neutron_lib.plugins import directory as plugin_dir from neutron_lib.plugins.ml2 import api -from oslo_concurrency import lockutils - from oslo_config import cfg +import oslo_context + from oslo_db import exception as db_exc from oslo_log import log @@ -93,6 +93,9 @@ # The default interval between periodic resyncs, in seconds. DEFAULT_RESYNC_INTERVAL_SECS = 60 +# The default maximum interval between resync completions, in seconds. +DEFAULT_RESYNC_MAX_INTERVAL_SECS = 3600 + calico_opts = [ cfg.IntOpt( "num_port_status_threads", @@ -136,6 +139,14 @@ " server starts or is restarted." ), ), + cfg.IntOpt( + "resync_max_interval_secs", + default=DEFAULT_RESYNC_MAX_INTERVAL_SECS, + help=( + "Calico will log an error if the interval between periodic" + " resync completions surpasses this maximum (in seconds)." + ), + ), ] cfg.CONF.register_opts(calico_opts, "calico") @@ -204,6 +215,78 @@ def wrapper(self, *args, **kwargs): return wrapper +# The execution model of the Neutron server is complex. It runs as multiple OS +# processes, each of which has only one OS thread, but each process uses eventlet to run +# multiple green threads in parallel. In the near-ish future eventlet will be removed +# and there will be multiple OS threads instead. The Calico driver code (which, +# broadly, maps between configuration in the Neutron DB and corresponding Calico data in +# the etcd datastore) runs as part of the Neutron server, and so its entry points can be +# called from any of those contexts: from multiple OS processes, or from multiple +# threads within the same OS process, or from multiple green threads within the same OS +# thread. And the processing for such calls can be interleaved - e.g. one thread or +# green thread yields for a while and allows others to run. This makes it difficult to +# follow through logs - for example, to tell if a given log line is part of an +# UPDATE_PORT_POSTCOMMIT for port A or an earlier UPDATE_PORT_POSTCOMMIT for port B, or +# some other trigger into our code. +# +# Happily OpenStack upstream has a solution for this kind of problem in its logging and +# 'context' libraries (oslo_log and oslo_context). Logging honours format strings, for +# each log line, that include arbitrary keys (like `%(key)s`) and the context library +# permits setting thread-local values for those keys, where 'thread' means either OS +# thread or green thread, whichever of those is in use. We can set values meaningful to +# us by creating an instance of oslo_context.context.RequestContext (or subclass) at the +# start of each of our driver entry points. +# +# But there are practical constraints: +# +# - We can't add a Calico-specific key, and customize the format strings to include +# that, because the format strings apply to the whole of the Neutron server, and any +# non-Calico context will be missing that key when it tries to log. (Resulting in +# tracebacks throughout the log file.) +# +# - Anyway, it's more convenient to piggy-back on a key that is already in the default +# format strings, so that we don't need to customize those. +# +# Therefore we arrange for our RequestContext subclass to add Calico-specific context to +# the existing `request_id` key. This then appears in all of the logs that are emitted +# within the processing of a Calico driver entry point, including common Neutron code as +# well as Calico-specific code. For example: +# +# .. 14:19:44 ..INFO networking_calico.plugins.ml2.drivers.calico.subnets \ +# [None CALICO:2:CREATE_SUBNET_POSTCOMMIT ... +# .. 14:19:44 ..DEBUG networking_calico.etcdv3 \ +# [None CALICO:2:CREATE_SUBNET_POSTCOMMIT ... +# .. 14:19:44 ..DEBUG neutron.pecan_wsgi.hooks.policy_enforcement \ +# [None CALICO:2:CREATE_SUBNET_POSTCOMMIT ... +# .. 14:19:44 ..DEBUG neutron_lib.callbacks.manager \ +# [None CALICO:2:CREATE_SUBNET_POSTCOMMIT ... +# +# This helps to see that `CALICO:2:CREATE_SUBNET_POSTCOMMIT` is a different entry point +# than a later operation such as `CALICO:3:CREATE_SUBNET_POSTCOMMIT`, or than processing +# in a different thread such as `CALICO:3:RESYNC`. + +task_id_lock = threading.Lock() +last_task_id = 0 + + +class TrackTask(oslo_context.context.RequestContext): + def __init__(self, log_string): + super(TrackTask, self).__init__(overwrite=True) + with task_id_lock: + global last_task_id + last_task_id += 1 + task_id = last_task_id + self.log_string = f"CALICO:{task_id}:{log_string}" + + def get_logging_values(self): + d = super(TrackTask, self).get_logging_values() + if "request_id" in d: + d["request_id"] = self.log_string + " " + d["request_id"] + else: + d["request_id"] = self.log_string + return d + + class CalicoMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase): """Neutron/ML2 mechanism driver for Project Calico. @@ -246,25 +329,26 @@ def __init__(self): # RPC client for fanning out agent state reports. self.state_report_rpc = None - # Whether the version of update_port_status() available in this version - # of OpenStack has the host argument. computed on first use. - self._cached_update_port_status_has_host_param = None + # Last time we logged about a long port-status queue. Used for rate # limiting. Note: monotonic_time() uses its own epoch so it's only # safe to compare this with other values returned by monotonic_time(). self._last_status_queue_log_time = monotonic_time() + # Last resync completion time + self.last_resync_time = datetime.now() + # Tell the monkeypatch where we are. global mech_driver assert mech_driver is None mech_driver = self # Make sure we initialise even if we don't see any API calls. - eventlet.spawn_after(STARTUP_DELAY_SECS, self._post_fork_init) + eventlet.spawn_after(STARTUP_DELAY_SECS, self._post_fork_init, voting=True) LOG.info("Created Calico mechanism driver %s", self) @logging_exceptions(LOG) - def _post_fork_init(self): + def _post_fork_init(self, voting=False): """_post_fork_init Creates the connection state required for talking to the Neutron DB @@ -291,6 +375,7 @@ def _post_fork_init(self): return # else: either this is the first call or our PID has changed: # (re)initialise. + TrackTask("POST_FORK_INIT") if self._my_pid is not None: # This is unexpected but we can deal with it: Neutron should @@ -320,55 +405,13 @@ def _post_fork_init(self): else: LOG.debug("authcfg[%s] = %s", key, authcfg[key]) - # Compatibility with older versions of openstack [mitaka and below] - try: - user_domain_name = authcfg.user_domain_name - except cfg.NoSuchOptError: - user_domain_name = "Default" - LOG.debug("authcfg[user_domain_name] fallback = %s", user_domain_name) - - try: - username = authcfg.username - except cfg.NoSuchOptError: - username = authcfg.admin_user - LOG.debug("authcfg[username] fallback[admin_user] = %s", username) - - try: - password = authcfg.password - except cfg.NoSuchOptError: - password = authcfg.admin_password - LOG.debug("authcfg[password] fallback[admin_password] = %s", "***") - - try: - project_domain_name = authcfg.project_domain_name - except cfg.NoSuchOptError: - project_domain_name = "Default" - LOG.debug( - "authcfg[project_domain_name] fallback = %s", project_domain_name - ) - - try: - project_name = authcfg.project_name - except cfg.NoSuchOptError: - project_name = authcfg.admin_tenant_name - LOG.debug( - "authcfg[project_name] fallback[admin_tenant_name] = %s", - project_name, - ) - - try: - auth_url = authcfg.auth_url - except cfg.NoSuchOptError: - auth_url = authcfg.identity_uri - LOG.debug("authcfg[auth_url] fallback[identity_uri] = %s", auth_url) - auth = v3.Password( - user_domain_name=user_domain_name, - username=username, - password=password, - project_domain_name=project_domain_name, - project_name=project_name, - auth_url=re.sub(r"/v3/?$", "", auth_url) + "/v3", + user_domain_name=authcfg.user_domain_name, + username=authcfg.username, + password=authcfg.password, + project_domain_name=authcfg.project_domain_name, + project_name=authcfg.project_name, + auth_url=re.sub(r"/v3/?$", "", authcfg.auth_url) + "/v3", ) sess = session.Session(auth=auth) keystone_client = KeystoneClient(session=sess) @@ -393,27 +436,45 @@ def _post_fork_init(self): state_report_topic = topics.PLUGIN self.state_report_rpc = agent_rpc.PluginReportStateAPI(state_report_topic) - # Elector, for performing leader election. - self.elector = Elector( - cfg.CONF.calico.elector_name, - datamodel_v2.neutron_election_key(calico_config.get_region_string()), - old_key=datamodel_v1.NEUTRON_ELECTION_KEY, - interval=MASTER_REFRESH_INTERVAL, - ttl=MASTER_TIMEOUT, - ) + if voting: + # Elector, for performing leader election. + self.elector = Elector( + cfg.CONF.calico.elector_name, + datamodel_v2.neutron_election_key( + calico_config.get_region_string() + ), + old_key=datamodel_v1.NEUTRON_ELECTION_KEY, + interval=MASTER_REFRESH_INTERVAL, + ttl=MASTER_TIMEOUT, + ) + LOG.info( + "PID %s: Initializing Calico Elector; " + "this process WILL participate in leader election.", + current_pid, + ) + + # Start our resynchronization process and status updating. Just in + # case we ever get two same threads running, use an epoch counter + # to tell the old thread to die. + # We deliberately do this last, to ensure that all of the setup + # above is complete before we start running. + self._epoch += 1 + eventlet.spawn(self.resync_monitor_thread, self._epoch) + eventlet.spawn(self.periodic_resync_thread, self._epoch) + if cfg.CONF.calico.etcd_compaction_period_mins > 0: + eventlet.spawn(self.periodic_compaction_thread, self._epoch) + eventlet.spawn(self._status_updating_thread, self._epoch) + for _ in range(cfg.CONF.calico.num_port_status_threads): + eventlet.spawn(self._loop_writing_port_statuses, self._epoch) + else: + LOG.info( + "PID %s: Not a voting participant; " + "skipping elector and leader threads.", + current_pid, + ) self._my_pid = current_pid - # Start our resynchronization process and status updating. Just in - # case we ever get two same threads running, use an epoch counter - # to tell the old thread to die. - # We deliberately do this last, to ensure that all of the setup - # above is complete before we start running. - self._epoch += 1 - eventlet.spawn(self.periodic_resync_thread, self._epoch) - eventlet.spawn(self._status_updating_thread, self._epoch) - for _ in range(cfg.CONF.calico.num_port_status_threads): - eventlet.spawn(self._loop_writing_port_statuses, self._epoch) LOG.info( "Calico mechanism driver initialisation done in process %s", current_pid ) @@ -426,6 +487,7 @@ def _status_updating_thread(self, expected_epoch): Calico mechanism driver. Watches for felix updates in etcd and passes info to Neutron database. """ + TrackTask("STATUS_UPDATING") LOG.info("Status updating thread started.") while self._epoch == expected_epoch: # Only handle updates if we are the master node. @@ -433,7 +495,12 @@ def _status_updating_thread(self, expected_epoch): if self._etcd_watcher is None: LOG.info("Became the master, starting StatusWatcher") self._etcd_watcher = StatusWatcher(self) - self._etcd_watcher_thread = eventlet.spawn(self._etcd_watcher.start) + + def start_etcd_watcher(): + TrackTask("STATUS_ETCD_WATCHER") + self._etcd_watcher.start() + + self._etcd_watcher_thread = eventlet.spawn(start_etcd_watcher) LOG.info( "Started %s as %s", self._etcd_watcher, @@ -563,6 +630,7 @@ def on_port_status_changed(self, hostname, port_id, status_dict, priority="low") @logging_exceptions(LOG) def _loop_writing_port_statuses(self, expected_epoch): + TrackTask("PORT_STATUS_WRITE") LOG.info("Port status write thread started epoch=%s", expected_epoch) admin_context = ctx.get_admin_context() while self._epoch == expected_epoch: @@ -592,17 +660,9 @@ def _try_to_update_port_status(self, admin_context, port_status_key): LOG.info("Reporting port %s deletion", port_id) try: - if self._update_port_status_has_host_param(): - # Later OpenStack versions support passing the hostname. - LOG.debug("update_port_status() supports host parameter") - self.db.update_port_status( - admin_context, port_id, neutron_status, host=hostname - ) - else: - # Older versions don't have a way to specify the hostname so - # we do our best. - LOG.debug("update_port_status() missing host parameter") - self.db.update_port_status(admin_context, port_id, neutron_status) + self.db.update_port_status( + admin_context, port_id, neutron_status, host=hostname + ) except db_exc.DBError as e: # Defensive: pre-Liberty, it was easy to cause deadlocks here if # any code path (in another loaded plugin, say) failed to take @@ -634,6 +694,7 @@ def _try_to_update_port_status(self, admin_context, port_status_key): @logging_exceptions(LOG) def _retry_port_status_update(self, port_status_key): + TrackTask("RETRY_PORT_STATUS_UPDATE") LOG.info("Retrying update to port %s", port_status_key) # Queue up the update so that we'll go via the normal writer threads. # They will re-read the current state of the port from the cache. @@ -641,17 +702,6 @@ def _retry_port_status_update(self, port_status_key): ((PRIORITY_RETRY, monotonic_time()), port_status_key) ) - def _update_port_status_has_host_param(self): - """Check whether update_port_status() supports the host parameter.""" - if self._cached_update_port_status_has_host_param is None: - full_arg_spec = inspect.getfullargspec(self.db.update_port_status) - args = full_arg_spec.args - varkw = full_arg_spec.varkw - has_host_param = varkw or "host" in args - self._cached_update_port_status_has_host_param = has_host_param - LOG.info("update_port_status() supports host arg: %s", has_host_param) - return self._cached_update_port_status_has_host_param - def _get_db(self): if not self.db: self.db = plugin_dir.get_plugin() @@ -705,6 +755,7 @@ def create_network_postcommit(self, context): @requires_state def update_network_postcommit(self, context): + TrackTask("UPDATE_NETWORK_POSTCOMMIT") LOG.info("UPDATE_NETWORK_POSTCOMMIT: %s" % context) # Determine if qos_policy_id is changing. If not, no-op. @@ -746,6 +797,7 @@ def update_existing_ports(self, ports, plugin_context, reason): @requires_state def handle_qos_policy_update(self, context, policy_id): + TrackTask("HANDLE_QOS_POLICY_UPDATE") LOG.info("HANDLE_QOS_POLICY_UPDATE: %s %s", context, policy_id) with db_api.CONTEXT_READER.using(context): @@ -786,6 +838,7 @@ def delete_network_postcommit(self, context): @requires_state def create_subnet_postcommit(self, context): + TrackTask("CREATE_SUBNET_POSTCOMMIT") LOG.info("CREATE_SUBNET_POSTCOMMIT: %s" % context) # Re-read the subnet from the DB. This ensures that a change to the @@ -800,6 +853,7 @@ def create_subnet_postcommit(self, context): @requires_state def update_subnet_postcommit(self, context): + TrackTask("UPDATE_SUBNET_POSTCOMMIT") LOG.info("UPDATE_SUBNET_POSTCOMMIT: %s" % context) # Re-read the subnet from the DB. This ensures that a change to the @@ -816,6 +870,7 @@ def update_subnet_postcommit(self, context): @requires_state def delete_subnet_postcommit(self, context): + TrackTask("DELETE_SUBNET_POSTCOMMIT") LOG.info("DELETE_SUBNET_POSTCOMMIT: %s" % context) self.subnet_syncer.subnet_deleted(context.current["id"]) @@ -832,6 +887,7 @@ def create_port_postcommit(self, context): unchanged while we hold the transaction. We can then write the port to etcd, along with any other information we may need. """ + TrackTask("CREATE_PORT_POSTCOMMIT") LOG.info("CREATE_PORT_POSTCOMMIT: %s", context) port = context._port @@ -859,6 +915,7 @@ def update_port_postcommit(self, context): This is a tricky event, because it can be called in a number of ways during VM migration. We farm out to the appropriate method from here. """ + TrackTask("UPDATE_PORT_POSTCOMMIT") LOG.info("UPDATE_PORT_POSTCOMMIT: %s", context) port = context._port original = context.original @@ -953,6 +1010,7 @@ def update_floatingip(self, plugin_context): Called after a Neutron floating IP has been associated or disassociated from a port. """ + TrackTask("UPDATE_FLOATINGIP") LOG.info("UPDATE_FLOATINGIP: %s", plugin_context) with self._txn_from_context(plugin_context, tag="update_floatingip"): @@ -968,6 +1026,7 @@ def delete_port_postcommit(self, context): There's no database row for us to lock on here, so don't bother. """ + TrackTask("DELETE_PORT_POSTCOMMIT") LOG.info("DELETE_PORT_POSTCOMMIT: %s", context) port = context._port @@ -978,7 +1037,7 @@ def delete_port_postcommit(self, context): self.endpoint_syncer.delete_endpoint(port) @requires_state - def send_sg_updates(self, sgids, context): + def security_groups_rule_updated(self, context): """Called whenever security group rules or membership change. When a security group rule is added, we need to do the following steps: @@ -986,9 +1045,10 @@ def send_sg_updates(self, sgids, context): 1. Reread the security rules from the Neutron DB. 2. Write the updated policy to etcd. """ - LOG.info("Updating security group IDs %s", sgids) - with self._txn_from_context(context, tag="sg-update"): - self.policy_syncer.write_sgs_to_etcd(sgids, context) + TrackTask("SECURITY_GROUPS_RULE_UPDATED") + LOG.info("SECURITY_GROUPS_RULE_UPDATED: %s", context) + with self._txn_from_context(context.plugin_context, tag="sg-update"): + self.policy_syncer.write_sgs_to_etcd(context.sgids, context.plugin_context) @contextlib.contextmanager def _txn_from_context(self, context, tag=""): @@ -1003,26 +1063,26 @@ def _txn_from_context(self, context, tag=""): conn_url = str(session.bind.url).lower() else: conn_url = str(session.connection().engine.url).lower() + + # Since 2015 it has been expected that anyone using MySQL also uses the PyMySQL + # driver, to avoid the problem described in + # https://bugs.launchpad.net/oslo.db/+bug/1350149. Assert that here. if conn_url.startswith("mysql:") or conn_url.startswith("mysql+mysqldb:"): - # Neutron is using the mysqldb driver for accessing the database. - # This has a known incompatibility with eventlet that leads to - # deadlock. Take the neutron-wide db-access lock as a workaround. - # See https://bugs.launchpad.net/oslo.db/+bug/1350149 for a - # description of the issue. - LOG.debug("Waiting for db-access lock tag=%s...", tag) - try: - with lockutils.lock("db-access"): - LOG.debug("...acquired db-access lock tag=%s", tag) - with context.session.begin(subtransactions=True) as txn: - yield txn - finally: - LOG.debug("Released db-access lock tag=%s", tag) - else: - # Liberty or later uses an eventlet-safe mysql library. (Or, we're - # not using mysql at all.) - LOG.debug("Not using mysqldb driver, skipping db-access lock") - with context.session.begin(subtransactions=True) as txn: - yield txn + LOG.error( + "Unsupported MySQL driver detected in SQLAlchemy connection URL: %s. " + "Please use the 'mysql+pymysql' driver to avoid known issues. " + "See https://bugs.launchpad.net/oslo.db/+bug/1350149 for details.", + conn_url, + ) + raise RuntimeError( + "Unsupported MySQL driver detected in SQLAlchemy connection URL: %s. " + "Please use the 'mysql+pymysql' driver to avoid known issues. " + "See https://bugs.launchpad.net/oslo.db/+bug/1350149 for details." + % conn_url + ) + + with context.session.begin(subtransactions=True) as txn: + yield txn def _update_port(self, plugin_context, port): """_update_port @@ -1039,6 +1099,49 @@ def _update_port(self, plugin_context, port): LOG.info("Port unbound, attempting delete if needed.") self.endpoint_syncer.delete_endpoint(port) + def resync_monitor_thread(self, launch_epoch): + """Monitor the interval between completed resyncs. + + Logs an error if the periodic resync duration surpasses + the configured maximum time in seconds. + """ + try: + LOG.info("Resync monitor thread started") + + while self._epoch == launch_epoch: + # Only monitor the resync if we are the master node. + if self.elector.master(): + LOG.info("I am master: monitoring periodic resync") + + curr_time = datetime.now() + time_delta = curr_time - self.last_resync_time + if ( + time_delta.total_seconds() + > cfg.CONF.calico.resync_max_interval_secs + ): + LOG.error( + "The time since the last resync completion has surpassed" + f" {cfg.CONF.calico.resync_max_interval_secs} seconds" + ) + + deadline = self.last_resync_time + timedelta( + seconds=cfg.CONF.calico.resync_max_interval_secs + ) + time_left = (deadline - curr_time).total_seconds() + polling_rate = cfg.CONF.calico.resync_max_interval_secs / 5 + sleep_time = time_left if deadline > curr_time else polling_rate + eventlet.sleep(sleep_time) + else: + LOG.debug("I am not master") + eventlet.sleep(MASTER_CHECK_INTERVAL_SECS) + except Exception: + # TODO(nj) Should we tear down the process. + LOG.exception("Resync monitor thread died!") + if self.elector: + # Stop the elector so that we give up the mastership. + self.elector.stop() + raise + def periodic_resync_thread(self, launch_epoch): """Periodic Neutron DB -> etcd resynchronization logic. @@ -1046,12 +1149,14 @@ def periodic_resync_thread(self, launch_epoch): reconcile them with etcd, ensuring that the etcd database and Neutron are in synchronization with each other. """ + TrackTask("RESYNC") try: LOG.info("Periodic resync thread started") while self._epoch == launch_epoch: # Only do the resync if we are the master node. if self.elector.master(): LOG.info("I am master: doing periodic resync") + start_time = datetime.now() # Since this thread is not associated with any particular # request, we use our own admin context for accessing the @@ -1074,8 +1179,12 @@ def periodic_resync_thread(self, launch_epoch): # Resync ClusterInformation and FelixConfiguration. self.provide_felix_config() - # Possibly request an etcd compaction. - check_request_etcd_compaction() + # mark this resync as finished. + self.last_resync_time = datetime.now() + LOG.info( + "The periodic resync finished after" + f" {self.last_resync_time - start_time}" + ) except Exception: LOG.exception("Error in periodic resync thread.") @@ -1101,6 +1210,44 @@ def periodic_resync_thread(self, launch_epoch): else: LOG.warning("Periodic resync thread exiting.") + def periodic_compaction_thread(self, launch_epoch): + """Periodic etcd compaction logic. + + On a fixed interval, requests etcd compaction to prevent unbounded disk usage + growth. Only the master node performs compaction. + """ + TrackTask("COMPACTION") + try: + LOG.info("Periodic compaction thread started") + while self._epoch == launch_epoch: + # Only do the compaction if we are the master node. + if self.elector.master(): + LOG.info("I am master: doing periodic compaction") + + try: + # Possibly request an etcd compaction. + check_request_etcd_compaction() + except Exception: + LOG.exception("Error in periodic compaction thread") + + # Reschedule ourselves. + eventlet.sleep(60 * cfg.CONF.calico.etcd_compaction_period_mins) + else: + # Shorter sleep interval before we check if we've become the master. + # Avoids waiting a whole etcd_compaction_period_mins if we just miss + # the master update. + LOG.debug("I am not master") + eventlet.sleep(MASTER_CHECK_INTERVAL_SECS) + except Exception: + # TODO(nj) Should we tear down the process. + LOG.exception("Periodic compaction thread died!") + if self.elector: + # Stop the elector so that we give up the mastership. + self.elector.stop() + raise + else: + LOG.warning("Periodic compaction thread exiting.") + @etcdv3.logging_exceptions def provide_felix_config(self): """provide_felix_config @@ -1229,24 +1376,6 @@ def provide_felix_config(self): eventlet.sleep(1) -# This section monkeypatches the AgentNotifierApi.security_groups_rule_updated -# method to ensure that the Calico driver gets told about security group -# updates at all times. This is a deeply unpleasant hack. Please, do as I say, -# not as I do. -# -# For more info, please see issues #635 and #641. -original_sgr_updated = rpc.AgentNotifierApi.security_groups_rule_updated - - -def security_groups_rule_updated(self, context, sgids): - LOG.info("security_groups_rule_updated: %s %s" % (context, sgids)) - mech_driver.send_sg_updates(sgids, context) - original_sgr_updated(self, context, sgids) - - -rpc.AgentNotifierApi.security_groups_rule_updated = security_groups_rule_updated - - def port_status_change(port, original): """port_status_change @@ -1322,10 +1451,6 @@ def check_request_etcd_compaction(): We piggyback on the master election infrastructure so that only one thread of the Neutron server requests compaction, each time that it becomes due. """ - # If periodic etcd compaction is disabled, do nothing here. - if cfg.CONF.calico.etcd_compaction_period_mins == 0: - return - try: # Try to read the compaction trigger key. try: diff --git a/networking-calico/networking_calico/plugins/ml2/drivers/calico/policy.py b/networking-calico/networking_calico/plugins/ml2/drivers/calico/policy.py index 23e9357d00f..1beb5e4d5fc 100644 --- a/networking-calico/networking_calico/plugins/ml2/drivers/calico/policy.py +++ b/networking-calico/networking_calico/plugins/ml2/drivers/calico/policy.py @@ -199,7 +199,6 @@ def _neutron_rule_to_etcd_rule(rule): ) if rule["remote_ip_prefix"] is not None: entity_rule["nets"] = [rule["remote_ip_prefix"]] - LOG.debug("=> Entity rule %s" % entity_rule) if port_spec is not None: if rule["direction"] == "ingress": diff --git a/networking-calico/networking_calico/plugins/ml2/drivers/calico/test/lib.py b/networking-calico/networking_calico/plugins/ml2/drivers/calico/test/lib.py index 8308ce3878a..898b050b7b1 100644 --- a/networking-calico/networking_calico/plugins/ml2/drivers/calico/test/lib.py +++ b/networking-calico/networking_calico/plugins/ml2/drivers/calico/test/lib.py @@ -65,6 +65,7 @@ sys.modules["neutron_lib.plugins.ml2"] = m_neutron_lib.plugins.ml2 sys.modules["oslo_concurrency"] = m_oslo_concurrency = mock.Mock() sys.modules["oslo_config"] = m_oslo_config = mock.MagicMock() +sys.modules["oslo_context"] = m_oslo_context = mock.Mock() sys.modules["oslo_db"] = m_oslo_db = mock.Mock() sys.modules["oslo_log"] = m_oslo_log = mock.Mock() sys.modules["sqlalchemy"] = m_sqlalchemy = mock.Mock() @@ -224,6 +225,7 @@ def stop(self): from networking_calico import datamodel_v3 from networking_calico import etcdutils from networking_calico import etcdv3 +from networking_calico.plugins.calico.context import SGRUpdateContext from networking_calico.plugins.ml2.drivers.calico import election from networking_calico.plugins.ml2.drivers.calico import endpoints from networking_calico.plugins.ml2.drivers.calico import mech_calico @@ -249,6 +251,8 @@ def mock_projects_list(): keystone_client.projects.list.side_effect = mock_projects_list mech_calico.KeystoneClient = mock.Mock() mech_calico.KeystoneClient.return_value = keystone_client +mech_calico.TrackTask = mock.Mock() +mech_calico.TrackTask.return_value = None REAL_EVENTLET_SLEEP_TIME = 0.01 @@ -520,10 +524,10 @@ def simulated_spawn(*args): # Also return it. return thread - def simulated_spawn_after(secs, fn, *args): + def simulated_spawn_after(secs, fn, *args, **kwargs): def sleep_then_run(): simulated_time_sleep(secs) - fn(*args) + fn(*args, **kwargs) return simulated_spawn(sleep_then_run) @@ -704,8 +708,8 @@ def notify_security_group_update(self, id, rules, port, type): if type == "rule": # Call security_groups_rule_updated with the new or changed ID. - mech_calico.security_groups_rule_updated( - mock.MagicMock(), mock.MagicMock(), [id] + self.driver.security_groups_rule_updated( + SGRUpdateContext(mock.MagicMock(), [id]) ) def get_port_security_group_bindings(self, context, filters): diff --git a/networking-calico/networking_calico/plugins/ml2/drivers/calico/test/test_mech_calico.py b/networking-calico/networking_calico/plugins/ml2/drivers/calico/test/test_mech_calico.py new file mode 100644 index 00000000000..635a9a0556d --- /dev/null +++ b/networking-calico/networking_calico/plugins/ml2/drivers/calico/test/test_mech_calico.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2025 Tigera, Inc. +# +# 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. +""" +Tests for CalicoMechanismDriver initialization and voting behavior. + +These tests validate: + - Only voting=True creates an Elector. + - Voting=False never creates an Elector. + - @requires_state does NOT cause a worker to become a voter. + - A worker initialization does not override the parent elector. +""" + +import unittest +import mock + +import networking_calico.plugins.ml2.drivers.calico.test.lib as lib +from networking_calico.plugins.ml2.drivers.calico import mech_calico +from networking_calico import etcdv3 + + +class TestMechanismDriverVoting(lib.Lib, unittest.TestCase): + + def setUp(self): + super(TestMechanismDriverVoting, self).setUp() + + lib.m_oslo_config.cfg.CONF.keystone_authtoken.auth_url = "" + lib.m_oslo_config.cfg.CONF.calico.openstack_region = "no-region" + lib.m_oslo_config.cfg.CONF.calico.etcd_compaction_period_mins = 0 + lib.m_oslo_config.cfg.CONF.calico.resync_interval_secs = 0 + lib.m_oslo_config.cfg.CONF.calico.project_name_cache_max = 0 + + # Mock etcd3gw client so background threads don't touch real etcd. + etcdv3._client = self.clientv3 = mock.Mock() + + self.clientv3.status.return_value = { + "header": {"revision": "123", "cluster_id": "cluster-id"}, + } + self.clientv3.get_prefix.return_value = [] + self.clientv3.watch_prefix.return_value = (iter(()), mock.Mock()) + + # Reset the driver + mech_calico.mech_driver = None + + def tearDown(self): + # Reset global etcd client. + etcdv3._client = None + mech_calico.mech_driver = None + + super(TestMechanismDriverVoting, self).tearDown() + + def _disable_background_threads(self, driver): + """Disable background threads that would touch etcd or do unrelated things.""" + driver.periodic_resync_thread = mock.Mock() + driver._status_updating_thread = mock.Mock() + driver.resync_monitor_thread = mock.Mock() + + @mock.patch.object(mech_calico, "Elector") + def test_parent_creates_elector(self, m_elector): + driver = mech_calico.CalicoMechanismDriver() + self._disable_background_threads(driver) + + driver._my_pid = None + driver._post_fork_init(voting=True) + + m_elector.assert_called_once() + self.assertIs(driver.elector, m_elector.return_value) + + @mock.patch.object(mech_calico, "Elector") + def test_worker_does_not_create_elector(self, m_elector): + driver = mech_calico.CalicoMechanismDriver() + self._disable_background_threads(driver) + + driver._my_pid = None + driver._post_fork_init(voting=False) + + m_elector.assert_not_called() + self.assertIsNone(driver.elector) + + @mock.patch.object(mech_calico, "Elector") + def test_requires_state_does_not_make_worker_voter(self, m_elector): + driver = mech_calico.CalicoMechanismDriver() + self._disable_background_threads(driver) + + driver._my_pid = None + + fake_context = mock.Mock() + fake_context.original = {} + fake_context.current = {} + fake_context._plugin_context = mock.Mock() + + # update_network_postcommit is decorated with @requires_state + driver.update_network_postcommit(fake_context) + + m_elector.assert_not_called() + self.assertIsNone(driver.elector) + + @mock.patch.object(mech_calico, "Elector") + def test_worker_init_does_not_override_parent_elector(self, m_elector): + driver = mech_calico.CalicoMechanismDriver() + self._disable_background_threads(driver) + + driver._my_pid = None + driver._post_fork_init(voting=True) + parent_elector = driver.elector + + # Simulate a worker re-initializing in the same process object + driver._my_pid = 99999 + driver._post_fork_init(voting=False) + + self.assertIs(driver.elector, parent_elector) + m_elector.assert_called_once() diff --git a/networking-calico/networking_calico/plugins/ml2/drivers/calico/test/test_monitor_thread.py b/networking-calico/networking_calico/plugins/ml2/drivers/calico/test/test_monitor_thread.py new file mode 100644 index 00000000000..0ff731f0194 --- /dev/null +++ b/networking-calico/networking_calico/plugins/ml2/drivers/calico/test/test_monitor_thread.py @@ -0,0 +1,192 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2026 Tigera, Inc. All rights reserved. +# +# 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. +""" +networking_calico.plugins.ml2.drivers.calico.test.test_monitor_thread + +Unit tests for the thread that monitors the periodic resync thread. +""" +from datetime import datetime, timedelta +import mock +import unittest + +import networking_calico.plugins.ml2.drivers.calico.test.lib as lib +from networking_calico.plugins.ml2.drivers.calico import mech_calico + + +INITIAL_EPOCH = 0 +TEST_MAX_INTERVAL = 30 + + +class TestResyncMonitorThread(lib.Lib, unittest.TestCase): + """Tests for the driver's resync monitor thread logic.""" + + def setUp(self): + super(TestResyncMonitorThread, self).setUp() + + # thread logic mocks + self.driver.elector = mock.Mock() + self.sleep_patcher = mock.patch("eventlet.sleep") + self.mock_sleep = self.sleep_patcher.start() + + # log mocks + self.log_error = mock.patch.object(mech_calico.LOG, "error").start() + self.log_info = mock.patch.object(mech_calico.LOG, "info").start() + self.log_debug = mock.patch.object(mech_calico.LOG, "debug").start() + + # resync mocks + self.driver.subnet_syncer = mock.Mock() + self.driver.policy_syncer = mock.Mock() + self.driver.endpoint_syncer = mock.Mock() + self.driver.provide_felix_config = mock.Mock() + + def tearDown(self): + self.sleep_patcher.stop() + super(TestResyncMonitorThread, self).tearDown() + + def simulate_epoch_progression(self, expected_sleep_time=None): + def increment_epoch(actual_sleep_time): + if expected_sleep_time is not None: + assert expected_sleep_time == actual_sleep_time + self.driver._epoch += 1 + + return increment_epoch + + def test_monitor_does_nothing_when_not_master(self): + """Test that a driver that is not master does not monitor.""" + self.driver.elector.master.return_value = False + self.mock_sleep.side_effect = self.simulate_epoch_progression() + + self.driver.resync_monitor_thread(INITIAL_EPOCH) + + self.log_debug.assert_called_once_with("I am not master") + self.log_error.assert_not_called() + + def test_monitor_logs_error_when_over_max(self): + """Test that an error is logged when interval surpasses maximum.""" + lib.m_oslo_config.cfg.CONF.calico.resync_max_interval_secs = TEST_MAX_INTERVAL + self.driver.elector.master.return_value = True + fake_resync_time = datetime.now() - timedelta(seconds=TEST_MAX_INTERVAL + 1) + self.driver.last_resync_time = fake_resync_time + self.mock_sleep.side_effect = self.simulate_epoch_progression() + + self.driver.resync_monitor_thread(INITIAL_EPOCH) + + self.log_error.assert_called_once() + self.assertIn( + "The time since the last resync completion has surpassed", + self.log_error.call_args[0][0], + ) + + def test_monitor_no_error_if_interval_under_max(self): + """If interval is below max, no error should be logged.""" + lib.m_oslo_config.cfg.CONF.calico.resync_max_interval_secs = TEST_MAX_INTERVAL + self.driver.elector.master.return_value = True + self.mock_sleep.side_effect = self.simulate_epoch_progression() + + self.driver.resync_monitor_thread(INITIAL_EPOCH) + + self.log_error.assert_not_called() + + def test_monitor_exception_stops_elector(self): + """On unexpected exception, elector.stop() must be called.""" + self.driver.elector.master.return_value = True + + with mock.patch.object(self.driver, "elector") as mock_elector: + mock_elector.master.side_effect = Exception("Test exception") + + with self.assertRaises(Exception): + self.driver.resync_monitor_thread(INITIAL_EPOCH) + + mock_elector.stop.assert_called_once() + + def test_resync_resets_time(self): + """Test that resync resets current interval duration to below max.""" + lib.m_oslo_config.cfg.CONF.calico.resync_max_interval_secs = TEST_MAX_INTERVAL + self.driver.elector.master.return_value = True + fake_resync_time = datetime.now() - timedelta(seconds=TEST_MAX_INTERVAL + 1) + self.driver.last_resync_time = fake_resync_time + + self.mock_sleep.side_effect = self.simulate_epoch_progression() + self.driver.resync_monitor_thread(INITIAL_EPOCH) + + self.log_error.assert_called_once() + self.assertIn( + "The time since the last resync completion has surpassed", + self.log_error.call_args[0][0], + ) + + # Resync + self.mock_sleep.side_effect = self.simulate_epoch_progression() + self.driver.periodic_resync_thread(INITIAL_EPOCH + 1) + + self.mock_sleep.side_effect = self.simulate_epoch_progression() + self.driver.resync_monitor_thread(INITIAL_EPOCH + 2) + + self.log_error.assert_called_once() + + def test_errors_continue_to_log(self): + """Test that errors continue logging if resync does not occur.""" + lib.m_oslo_config.cfg.CONF.calico.resync_max_interval_secs = TEST_MAX_INTERVAL + self.driver.elector.master.return_value = True + fake_resync_time = datetime.now() - timedelta(seconds=TEST_MAX_INTERVAL + 1) + self.driver.last_resync_time = fake_resync_time + + self.mock_sleep.side_effect = self.simulate_epoch_progression() + self.driver.resync_monitor_thread(INITIAL_EPOCH) + + self.log_error.assert_called_once() + self.assertIn( + "The time since the last resync completion has surpassed", + self.log_error.call_args[0][0], + ) + + self.mock_sleep.side_effect = self.simulate_epoch_progression() + self.driver.resync_monitor_thread(INITIAL_EPOCH + 1) + + self.assertEqual(self.log_error.call_count, 2) + self.assertIn( + "The time since the last resync completion has surpassed", + self.log_error.call_args[0][0], + ) + + @mock.patch("networking_calico.plugins.ml2.drivers.calico.mech_calico.datetime") + def test_sleep_time_logic_before_deadline(self, mock_datetime): + """Test that we sleep until deadline if there is time left.""" + lib.m_oslo_config.cfg.CONF.calico.resync_max_interval_secs = TEST_MAX_INTERVAL + self.driver.elector.master.return_value = True + + curr_time = datetime.now() + self.driver.last_resync_time = curr_time + expected_sleep_time = TEST_MAX_INTERVAL + mock_datetime.now.return_value = curr_time + + self.mock_sleep.side_effect = self.simulate_epoch_progression( + expected_sleep_time + ) + self.driver.resync_monitor_thread(INITIAL_EPOCH) + + def test_sleep_time_logic_after_deadline(self): + """Test that we poll if the deadline has passed.""" + lib.m_oslo_config.cfg.CONF.calico.resync_max_interval_secs = TEST_MAX_INTERVAL + self.driver.elector.master.return_value = True + + fake_resync_time = datetime.now() - timedelta(seconds=TEST_MAX_INTERVAL + 1) + self.driver.last_resync_time = fake_resync_time + expected_sleep_time = TEST_MAX_INTERVAL / 5 + + self.mock_sleep.side_effect = self.simulate_epoch_progression( + expected_sleep_time + ) + self.driver.resync_monitor_thread(INITIAL_EPOCH) diff --git a/networking-calico/networking_calico/plugins/ml2/drivers/calico/test/test_plugin_etcd.py b/networking-calico/networking_calico/plugins/ml2/drivers/calico/test/test_plugin_etcd.py index 61849d8a0e2..bfc5e354742 100644 --- a/networking-calico/networking_calico/plugins/ml2/drivers/calico/test/test_plugin_etcd.py +++ b/networking-calico/networking_calico/plugins/ml2/drivers/calico/test/test_plugin_etcd.py @@ -1219,7 +1219,7 @@ def test_subnet_hooks(self): "network_id": "net-id-1", "enable_dhcp": True, "id": "subnet-id-10.65.0--24", - "cidr": "10.65.0/24", + "cidr": "10.65.0.0/24", "gateway_ip": "10.65.0.1", "host_routes": [{"destination": "11.11.0.0/16", "nexthop": "10.65.0.1"}], "dns_nameservers": [], @@ -1228,7 +1228,7 @@ def test_subnet_hooks(self): "network_id": "net-id-2", "enable_dhcp": False, "id": "subnet-id-10.28.0--24", - "cidr": "10.28.0/24", + "cidr": "10.28.0.0/24", "gateway_ip": "10.28.0.1", "host_routes": [], "dns_nameservers": ["172.18.10.55"], @@ -1889,8 +1889,8 @@ def maybe_end_loop(*args, **kwargs): m_watcher = m_StatusWatcher.return_value self.assertEqual( [ - mock.call(m_watcher.start), - mock.call(m_watcher.start), + mock.call(mock.ANY), + mock.call(mock.ANY), ], [c for c in m_spawn.mock_calls if c[0] == ""], ) @@ -1991,37 +1991,33 @@ def test_loop_writing_port_statuses(self): ) def test_try_to_update_port_status(self): - # New OpenStack releases have a host parameter. self.driver._get_db() - # Driver uses reflection to check the function sig so we have to put - # a real function here. mock_calls = [] - def m_update_port_status(context, port_id, status): - """Older version of OpenStack; no host parameter""" - mock_calls.append(mock.call(context, port_id, status)) + def m_update_port_status(context, port_id, status, host=None): + mock_calls.append(mock.call(context, port_id, status, host=host)) self.db.update_port_status = m_update_port_status context = mock.Mock() with mock.patch("eventlet.spawn_after", autospec=True) as m_spawn: self.driver._try_to_update_port_status(context, ("host", "p1")) self.assertEqual( - [mock.call(context, "p1", mech_calico.constants.PORT_STATUS_ERROR)], + [ + mock.call( + context, "p1", mech_calico.constants.PORT_STATUS_ERROR, host="host" + ) + ], mock_calls, ) self.assertEqual([], m_spawn.mock_calls) # No retry on success def test_try_to_update_port_status_fail(self): - # New OpenStack releases have a host parameter. self.driver._get_db() - # Driver uses reflection to check the function sig so we have to put - # a real function here. mock_calls = [] def m_update_port_status(context, port_id, status, host=None): - """Newer version of OpenStack; host parameter""" mock_calls.append(mock.call(context, port_id, status, host=host)) raise lib.DBError() diff --git a/networking-calico/networking_calico/tests/test_dhcp_agent.py b/networking-calico/networking_calico/tests/test_dhcp_agent.py index f0b74f0a0c5..2b52bc4be1a 100644 --- a/networking-calico/networking_calico/tests/test_dhcp_agent.py +++ b/networking-calico/networking_calico/tests/test_dhcp_agent.py @@ -485,6 +485,7 @@ def test_build_cmdline(self, commonutils, device_mgr_cls): "--dhcp-range=set:subnet-v6subnet-1,2001:db8:1::" + ",static,off-link,80,86400s", "--enable-ra", + "--ra-param=ns-*,mtu:off,0", "--interface=tap1", "--interface=tap2", "--interface=tap3", @@ -566,6 +567,7 @@ def test_build_cmdline_slaac(self, commonutils, device_mgr_cls): "--dhcp-range=set:subnet-v6subnet-1,2001:db8:1::" + ",static,off-link,80,86400s", "--enable-ra", + "--ra-param=ns-*,mtu:off,0", "--interface=tap1", "--interface=tap2", "--interface=tap3", diff --git a/node/.semaphore/batches.sh b/node/.semaphore/batches.sh new file mode 100644 index 00000000000..3ed47c15fd1 --- /dev/null +++ b/node/.semaphore/batches.sh @@ -0,0 +1,23 @@ +# Common definitions for the tests that are run on cloud +# VMs rather than Semaphore VMs +set -e + +export batches=(k8s-test) + +run_batch() { + local remote_exec="$1" + local batch="$2" + local vm_name="$3" + local log_file="$4" + + case $batch in + k8s-test) + cmd=("make" "--directory=${CALICO_DIR_NAME}/node" "k8s-test") + ;; + *) + echo "invalid batch name" && exit 1 + ;; + esac + + VM_NAME="$vm_name" ${remote_exec} "${cmd[@]}" >& "$log_file" +} diff --git a/process/testing/winfv-cni-plugin/aso/create-minikube-cluster.sh b/node/.semaphore/cache-test-artifacts similarity index 50% rename from process/testing/winfv-cni-plugin/aso/create-minikube-cluster.sh rename to node/.semaphore/cache-test-artifacts index e8cfe8d7df1..d7711bf36cf 100755 --- a/process/testing/winfv-cni-plugin/aso/create-minikube-cluster.sh +++ b/node/.semaphore/cache-test-artifacts @@ -1,5 +1,5 @@ -#!/bin/bash -# Copyright (c) 2024 Tigera, Inc. All rights reserved. +#!/usr/bin/env bash +# Copyright (c) 2025 Tigera, Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,20 +13,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -set -o errexit -set -o nounset -set -o pipefail -set -x - -LINUX_PIP=$1 +# This script is run locally to push artifacts to our sub-directory in GCS. +# The counterpart to this script is load-test-artifacts, which runs on the test +# VM. It can assume that the artifacts have been downloaded to /tmp. -curl -LO https://github.com/kubernetes/minikube/releases/download/v1.33.0/minikube-linux-amd64 -sudo install minikube-linux-amd64 /usr/local/bin/minikube && rm minikube-linux-amd64 - -minikube start --listen-address=0.0.0.0 --apiserver-ips=$LINUX_PIP +set -e +set -x -PORT_INFO=$(docker port minikube | grep 8443) +cd ../.. -port=$(echo $PORT_INFO | grep -oE '[0-9]{5}' | tail -1) -echo "apiserver listening at port $port" -echo $port > $HOME/port_info \ No newline at end of file +# Future work: pre-build images for node in an earlier job. +tar -czf /tmp/working-copy.tgz \ + --exclude=artifacts \ + ${CALICO_DIR_NAME} +gcloud storage cp /tmp/working-copy.tgz "${GCS_WORKFLOW_DIR}/node/fv-artifacts/" diff --git a/.semaphore/vms/publish-artifacts b/node/.semaphore/load-test-artifacts similarity index 64% rename from .semaphore/vms/publish-artifacts rename to node/.semaphore/load-test-artifacts index 91459b341c3..2fb0250e86d 100755 --- a/.semaphore/vms/publish-artifacts +++ b/node/.semaphore/load-test-artifacts @@ -1,5 +1,6 @@ #!/usr/bin/env bash -# Copyright (c) 2023 Tigera, Inc. All rights reserved. + +# Copyright (c) 2025 Tigera, Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,14 +14,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -# No set -e so that we continue to collect artifacts even if some fail. -set -x - -# set nullglob so that '*' is not used literally if the artifacts dir is empty -shopt -s nullglob +# This script is run on the test VM, to load the component-specific test +# artifacts, which have already been downloaded to /tmp. -cd "artifacts" || exit 1 +set -ex -for file in *; do - artifact push job "$file" -done +echo "No component-specific test artifacts to load." diff --git a/node/Dockerfile-windows b/node/Dockerfile-windows index bdf30ecd84d..eac72355ea9 100644 --- a/node/Dockerfile-windows +++ b/node/Dockerfile-windows @@ -42,7 +42,7 @@ COPY windows-packaging/CalicoWindows/libs/hns/hns.psm1 /CalicoWindows/libs/hns/h COPY windows-packaging/nssm.exe /nssm.exe COPY windows-packaging/CalicoWindows/uninstall-calico-hpc.ps1 /uninstall-calico.ps1 -ENV PATH=$PATH;/CalicoWindows +ENV PATH="$PATH;C:\CalicoWindows;C:\Windows\System32;C:\Windows;C:\Windows\System32\WindowsPowerShell\v1.0;C:\Program Files\PowerShell" WORKDIR /CalicoWindows # The nanoserver image does not have powershell but this works because # this container will be running on the host. diff --git a/node/Dockerfile.amd64 b/node/Dockerfile.amd64 index 4e314f86377..f83b2790c9f 100644 --- a/node/Dockerfile.amd64 +++ b/node/Dockerfile.amd64 @@ -11,66 +11,32 @@ # 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. + ARG GIT_VERSION=unknown -ARG IPTABLES_VER=1.8.8-6 -ARG LIBNFTNL_VER=1.2.2-1 -ARG IPSET_VER=7.11-6 ARG RUNIT_VER=2.1.2 ARG BIRD_IMAGE=calico/bird:latest ARG BPFTOOL_IMAGE + FROM ${BPFTOOL_IMAGE} AS bpftool + FROM ${BIRD_IMAGE} AS bird -# Use this build stage to build iptables rpm and runit binaries. -# We need to rebuild the iptables rpm because the prepackaged rpm does not have legacy iptables binaries. +# Use this build stage to install iptables-legacy and build runit binaries. # We need to build runit because there aren't any rpms for it in AlmaLinux or ubi repositories. -FROM almalinux:8 AS almalinux +FROM almalinux:9 AS almalinux -ARG IPTABLES_VER -ARG LIBNFTNL_VER -ARG IPSET_VER ARG RUNIT_VER -ARG CENTOS_MIRROR_BASE_URL=https://linuxsoft.cern.ch/cern/centos/s9 -ARG LIBNFTNL_SOURCERPM_URL=${CENTOS_MIRROR_BASE_URL}/BaseOS/source/tree/Packages/libnftnl-${LIBNFTNL_VER}.el9.src.rpm -ARG IPTABLES_SOURCERPM_URL=${CENTOS_MIRROR_BASE_URL}/BaseOS/source/tree/Packages/iptables-${IPTABLES_VER}.el9.src.rpm -ARG IPTABLES_LEGACY_SOURCERPM_URL=https://archives.fedoraproject.org/pub/archive/epel/9.3/Everything/source/tree/Packages/i/iptables-epel-${IPTABLES_VER}.el9.2.src.rpm -ARG IPSET_SOURCERPM_URL=${CENTOS_MIRROR_BASE_URL}/BaseOS/source/tree/Packages/ipset-${IPSET_VER}.el9.src.rpm - -# Install build dependencies and security updates. -RUN dnf install -y 'dnf-command(config-manager)' && \ - # Enable PowerTools repo for '-devel' packages - dnf config-manager --set-enabled powertools && \ - # Install required packages for building rpms. yum-utils is not required but it gives us yum-builddep to easily install build deps. - yum install --allowerasing -y rpm-build yum-utils make && \ - # Need these to build runit. - yum install --allowerasing -y wget glibc-static gcc && \ - # Ensure all security updates are installed. - yum -y update-minimal --security - -# In order to rebuild the iptables RPM, we first need to rebuild the libnftnl RPM because building -# iptables requires libnftnl-devel but libnftnl-devel is not available on ubi or AlmaLinux repos. -# (Note: it's not in RHEL8.1 either https://bugzilla.redhat.com/show_bug.cgi?id=1711361). -# Rebuilding libnftnl will give us libnftnl-devel too. -RUN rpm -i ${LIBNFTNL_SOURCERPM_URL} && \ - yum-builddep -y --spec /root/rpmbuild/SPECS/libnftnl.spec && \ - rpmbuild -bb /root/rpmbuild/SPECS/libnftnl.spec && \ - # Now install libnftnl and libnftnl-devel - rpm -Uv /root/rpmbuild/RPMS/x86_64/libnftnl-${LIBNFTNL_VER}.el8.x86_64.rpm && \ - rpm -Uv /root/rpmbuild/RPMS/x86_64/libnftnl-devel-${LIBNFTNL_VER}.el8.x86_64.rpm && \ - # Install source RPM for iptables and install its build dependencies. - rpm -i ${IPTABLES_SOURCERPM_URL} && \ - yum-builddep -y --spec /root/rpmbuild/SPECS/iptables.spec && \ - rpmbuild -bb /root/rpmbuild/SPECS/iptables.spec && \ - # iptables-legacy has been deprecated, but to keep the backwards compatibility - # we need install source RPM for iptables-legacy and install its build dependencies. - rpm -i ${IPTABLES_LEGACY_SOURCERPM_URL} && \ - yum-builddep -y --spec /root/rpmbuild/SPECS/iptables-epel.spec && \ - rpmbuild -bb /root/rpmbuild/SPECS/iptables-epel.spec - -# Install source RPM for ipset and install its build dependencies. -RUN rpm -i ${IPSET_SOURCERPM_URL} && \ - yum-builddep -y --spec /root/rpmbuild/SPECS/ipset.spec && \ - rpmbuild -bb /root/rpmbuild/SPECS/ipset.spec + +RUN dnf upgrade -y && dnf install -y epel-release + +RUN dnf --enablerepo=crb install -y \ + iptables-legacy \ + # Needed for building runit + gcc \ + glibc-static \ + patch + +RUN dnf clean all # Copy the patch that adjusts svlogd log file permissions from 0744 to 0644. # This file is used in the next step to apply the patch during the build process. @@ -83,90 +49,68 @@ RUN curl -sfL https://ftp.debian.org/debian/pool/main/r/runit/runit_${RUNIT_VER} patch -p1 < /svlogd_use_0644_permission_instead_of_0744.patch && \ package/compile -FROM registry.access.redhat.com/ubi8/ubi-minimal:latest AS ubi +FROM registry.access.redhat.com/ubi9/ubi-minimal:latest AS ubi ARG GIT_VERSION -ARG IPTABLES_VER -ARG IPSET_VER ARG RUNIT_VER -# Update base packages to pick up security updates. Must do this before adding the AlmaLinux repo. -RUN microdnf upgrade - -# Copy in runit binaries -COPY --from=almalinux /root/admin/runit-${RUNIT_VER}/command/* /usr/local/bin/ - -# Copy in our rpms -COPY --from=almalinux /root/rpmbuild/RPMS/x86_64/* /tmp/rpms/ - -# Install a subset of packages from UBI prior to removing the UBI repo below. -# We do this because the UBI repo has updated versions with CVE fixes. We can remove -# this once the AlmaLinux repo updates the version of these packages. -# gzip >= 1.9-13.el8_5 -# cryptsetup-libs >= 2.3.3-4.el8_5.1 -RUN microdnf install \ +# Update base packages to pick up security updates - must do this before adding the AlmaLinux repo, +# and install the necessary packages from ubi repos. +RUN microdnf upgrade -y +RUN microdnf install -y \ # Don't install copious docs. --setopt=tsflags=nodocs \ gzip \ cryptsetup-libs \ # Needed for iptables libpcap libmnl libnfnetlink libnetfilter_conntrack \ + ipset \ iputils \ + nftables \ # Need arp net-tools \ # Need kmod to ensure ip6tables-save works correctly kmod \ # Also needed (provides utilities for browsing procfs like ps) - procps \ iproute \ + procps \ # Needed for runit startup script which \ # Needed for the cleanup script findutils -# Since the ubi repos do not contain all the packages we need (they're missing conntrack-tools), +# Since the ubi repos do not contain all the packages we need (e.g. they're missing conntrack-tools), # we're using AlmaLinux repos for missing packages. COPY almalinux.repo /etc/yum.repos.d/almalinux.repo -RUN microdnf --enablerepo=baseos install \ - # Needed for conntrack +RUN microdnf --enablerepo=appstream --enablerepo=baseos install -y \ + # From appstream - needed for conntrack libnetfilter_cthelper libnetfilter_cttimeout libnetfilter_queue \ - conntrack-tools - -# Install iptables-libs via rpm. The libs must installed before installing iproute-tc and nftables via 'microdnf install' -# otherwise they will pull a different version of iptables-libs as a dependency. -RUN rpm -i /tmp/rpms/iptables-libs-${IPTABLES_VER}.el8.x86_64.rpm && \ - rpm -i /tmp/rpms/iptables-legacy-libs-${IPTABLES_VER}.el8.2.x86_64.rpm - -# iproute-tc and nftables depend on iptables-libs and should be installed after it. -RUN microdnf --enablerepo=baseos install iproute-tc nftables - -# Install iptables via rpm. -RUN rpm -i /tmp/rpms/iptables-legacy-${IPTABLES_VER}.el8.2.x86_64.rpm && \ - rpm -i /tmp/rpms/iptables-nft-${IPTABLES_VER}.el8.x86_64.rpm && \ - # Install ipset version - rpm --force -i /tmp/rpms/ipset-libs-${IPSET_VER}.el8.x86_64.rpm && \ - rpm -i /tmp/rpms/ipset-${IPSET_VER}.el8.x86_64.rpm && \ - # Set alternatives - alternatives --install /usr/sbin/iptables iptables /usr/sbin/iptables-legacy 1 && \ - alternatives --install /usr/sbin/ip6tables ip6tables /usr/sbin/ip6tables-legacy 1 + conntrack-tools \ + # From baseos + iproute-tc iptables-nft RUN microdnf clean all -# Change the permissions for ipset so it can be run by any container user. -RUN chgrp 0 /usr/sbin/ipset && \ - chmod g=u /usr/sbin/ipset +# Copy in runit binaries +COPY --from=almalinux /root/admin/runit-${RUNIT_VER}/command/* /usr/local/bin/ + +# Copy in xtables-legacy binary and libs +COPY --from=almalinux /usr/sbin/xtables-legacy-multi /usr/sbin/xtables-legacy-multi +COPY --from=almalinux /lib64/libip4tc.so.2 /lib64/libip4tc.so.2 +COPY --from=almalinux /lib64/libip6tc.so.2 /lib64/libip6tc.so.2 -# Change the permissions for iptables so it can be run by any container user. -RUN chgrp 0 /usr/sbin/iptables && \ - chmod g=u /usr/sbin/iptables +# Set symbolic links for iptables-legacy binaries +RUN set -eux; \ + for bin in iptables-legacy iptables-legacy-restore iptables-legacy-save ip6tables-legacy ip6tables-legacy-restore ip6tables-legacy-save; do \ + ln -sf xtables-legacy-multi /usr/sbin/${bin} || true; \ + done # Copy our bird binaries in -COPY --from=bird /bird* /bin/ - -# Set the suid bit on bird to allow our user to execute them with root permissions. -RUN chmod u+s /bin/bird -RUN chmod u+s /bin/bird6 +COPY --from=bird /bird /bin/bird +COPY --from=bird /birdcl /bin/birdcl +COPY --from=bird /bird6 /bin/bird6 +COPY --from=bird /birdcl6 /bin/birdcl6 # Copy in the filesystem - this contains licenses, etc... COPY filesystem/etc/ /etc @@ -174,26 +118,16 @@ COPY filesystem/included-source/ /included-source COPY filesystem/usr/ /usr COPY filesystem/sbin/* /usr/sbin/ -# Change permissions to make confd templates and output available in /etc/calico -# to all container users. -RUN chgrp -R 0 /etc/calico && \ - chmod -R g=u /etc/calico - -COPY --from=bpftool /bpftool /bin +COPY --from=bpftool /bpftool /bin/bpftool ARG BIN_DIR + # Copy in the calico-node binary COPY ${BIN_DIR}/calico-node-amd64 /bin/calico-node -# Set the suid bit on calico-node -RUN chmod u+s /bin/calico-node - # Copy in the mountns binary COPY ${BIN_DIR}/mountns-amd64 /bin/mountns -# Set the suid bit on mountns -RUN chmod u+s /bin/mountns - # Clean out as many files as we can from the filesystem. We no longer need dnf or the platform python install # or any of its dependencies. COPY clean-up-filesystem.sh /clean-up-filesystem.sh @@ -220,6 +154,14 @@ LABEL org.opencontainers.image.vendor="Project Calico" LABEL org.opencontainers.image.version="${GIT_VERSION}" LABEL org.opencontainers.image.licenses="Apache-2.0" +LABEL description="Calico node handles networking and policy for Calico" +LABEL maintainer="maintainers@tigera.io" +LABEL name="Calico node" +LABEL release=1 +LABEL summary="The primary networking and policy engine for Calico" +LABEL vendor="Project Calico" +LABEL version="${GIT_VERSION}" + COPY --from=ubi / / CMD ["start_runit"] diff --git a/node/Dockerfile.arm64 b/node/Dockerfile.arm64 index 91c4aa1d7b9..a185007611c 100644 --- a/node/Dockerfile.arm64 +++ b/node/Dockerfile.arm64 @@ -11,66 +11,31 @@ # 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. + ARG GIT_VERSION=unknown -ARG IPTABLES_VER=1.8.8-6 -ARG LIBNFTNL_VER=1.2.2-1 -ARG IPSET_VER=7.11-6 ARG RUNIT_VER=2.1.2 ARG BIRD_IMAGE=calico/bird:latest FROM calico/bpftool:v7.4.0 AS bpftool + FROM ${BIRD_IMAGE} AS bird -# Use this build stage to build iptables rpm and runit binaries. -# We need to rebuild the iptables rpm because the prepackaged rpm does not have legacy iptables binaries. +# Use this build stage to install iptables-legacy and build runit binaries. # We need to build runit because there aren't any rpms for it in AlmaLinux or ubi repositories. -FROM almalinux:8 AS almalinux +FROM almalinux:9 AS almalinux -ARG IPTABLES_VER -ARG LIBNFTNL_VER -ARG IPSET_VER ARG RUNIT_VER -ARG CENTOS_MIRROR_BASE_URL=https://linuxsoft.cern.ch/cern/centos/s9 -ARG LIBNFTNL_SOURCERPM_URL=${CENTOS_MIRROR_BASE_URL}/BaseOS/source/tree/Packages/libnftnl-${LIBNFTNL_VER}.el9.src.rpm -ARG IPTABLES_SOURCERPM_URL=${CENTOS_MIRROR_BASE_URL}/BaseOS/source/tree/Packages/iptables-${IPTABLES_VER}.el9.src.rpm -ARG IPTABLES_LEGACY_SOURCERPM_URL=https://archives.fedoraproject.org/pub/archive/epel/9.3/Everything/source/tree/Packages/i/iptables-epel-${IPTABLES_VER}.el9.2.src.rpm -ARG IPSET_SOURCERPM_URL=${CENTOS_MIRROR_BASE_URL}/BaseOS/source/tree/Packages/ipset-${IPSET_VER}.el9.src.rpm - -# Install build dependencies and security updates. -RUN dnf install -y 'dnf-command(config-manager)' && \ - # Enable PowerTools repo for '-devel' packages - dnf config-manager --set-enabled powertools && \ - # Install required packages for building rpms. yum-utils is not required but it gives us yum-builddep to easily install build deps. - yum install --allowerasing -y rpm-build yum-utils make && \ - # Need these to build runit. - yum install --allowerasing -y wget glibc-static gcc && \ - # Ensure all security updates are installed. - yum -y update-minimal --security - -# In order to rebuild the iptables RPM, we first need to rebuild the libnftnl RPM because building -# iptables requires libnftnl-devel but libnftnl-devel is not available on ubi or AlmaLinux repos. -# (Note: it's not in RHEL8.1 either https://bugzilla.redhat.com/show_bug.cgi?id=1711361). -# Rebuilding libnftnl will give us libnftnl-devel too. -RUN rpm -i ${LIBNFTNL_SOURCERPM_URL} && \ - yum-builddep -y --spec /root/rpmbuild/SPECS/libnftnl.spec && \ - rpmbuild -bb /root/rpmbuild/SPECS/libnftnl.spec && \ - # Now install libnftnl and libnftnl-devel - rpm -Uv /root/rpmbuild/RPMS/aarch64/libnftnl-${LIBNFTNL_VER}.el8.aarch64.rpm && \ - rpm -Uv /root/rpmbuild/RPMS/aarch64/libnftnl-devel-${LIBNFTNL_VER}.el8.aarch64.rpm && \ - # Install source RPM for iptables and install its build dependencies. - rpm -i ${IPTABLES_SOURCERPM_URL} && \ - yum-builddep -y --spec /root/rpmbuild/SPECS/iptables.spec && \ - rpmbuild -bb /root/rpmbuild/SPECS/iptables.spec && \ - # iptables-legacy has been deprecated, but to keep the backwards compatibility - # we need install source RPM for iptables-legacy and install its build dependencies. - rpm -i ${IPTABLES_LEGACY_SOURCERPM_URL} && \ - yum-builddep -y --spec /root/rpmbuild/SPECS/iptables-epel.spec && \ - rpmbuild -bb /root/rpmbuild/SPECS/iptables-epel.spec - -# Install source RPM for ipset and install its build dependencies. -RUN rpm -i ${IPSET_SOURCERPM_URL} && \ - yum-builddep -y --spec /root/rpmbuild/SPECS/ipset.spec && \ - rpmbuild -bb /root/rpmbuild/SPECS/ipset.spec + +RUN dnf upgrade -y && dnf install -y epel-release + +RUN dnf --enablerepo=crb install -y \ + iptables-legacy \ + # Needed for building runit + gcc \ + glibc-static \ + patch + +RUN dnf clean all # Copy the patch that adjusts svlogd log file permissions from 0744 to 0644. # This file is used in the next step to apply the patch during the build process. @@ -83,90 +48,68 @@ RUN curl -sfL https://ftp.debian.org/debian/pool/main/r/runit/runit_${RUNIT_VER} patch -p1 < /svlogd_use_0644_permission_instead_of_0744.patch && \ package/compile -FROM registry.access.redhat.com/ubi8/ubi-minimal:latest AS ubi +FROM registry.access.redhat.com/ubi9/ubi-minimal:latest AS ubi ARG GIT_VERSION -ARG IPTABLES_VER -ARG IPSET_VER ARG RUNIT_VER -# Update base packages to pick up security updates. Must do this before adding the AlmaLinux repo. -RUN microdnf upgrade - -# Copy in runit binaries -COPY --from=almalinux /root/admin/runit-${RUNIT_VER}/command/* /usr/local/bin/ - -# Copy in our rpms -COPY --from=almalinux /root/rpmbuild/RPMS/aarch64/* /tmp/rpms/ - -# Install a subset of packages from UBI prior to removing the UBI repo below. -# We do this because the UBI repo has updated versions with CVE fixes. We can remove -# this once the AlmaLinux repo updates the version of these packages. -# gzip >= 1.9-13.el8_5 -# cryptsetup-libs >= 2.3.3-4.el8_5.1 -RUN microdnf install \ +# Update base packages to pick up security updates - must do this before adding the AlmaLinux repo, +# and install the necessary packages from ubi repos. +RUN microdnf upgrade -y +RUN microdnf install -y \ # Don't install copious docs. --setopt=tsflags=nodocs \ gzip \ cryptsetup-libs \ # Needed for iptables libpcap libmnl libnfnetlink libnetfilter_conntrack \ + ipset \ iputils \ + nftables \ # Need arp net-tools \ # Need kmod to ensure ip6tables-save works correctly kmod \ # Also needed (provides utilities for browsing procfs like ps) - procps \ iproute \ + procps \ # Needed for runit startup script which \ # Needed for the cleanup script findutils -# Since the ubi repos do not contain all the packages we need (they're missing conntrack-tools), +# Since the ubi repos do not contain all the packages we need (e.g. they're missing conntrack-tools), # we're using AlmaLinux repos for missing packages. COPY almalinux.repo /etc/yum.repos.d/almalinux.repo -RUN microdnf --enablerepo=baseos install \ - # Needed for conntrack +RUN microdnf --enablerepo=appstream --enablerepo=baseos install -y \ + # From appstream - needed for conntrack libnetfilter_cthelper libnetfilter_cttimeout libnetfilter_queue \ - conntrack-tools - -# Install iptables-libs via rpm. The libs must installed before installing iproute-tc and nftables via 'microdnf install' -# otherwise they will pull a different version of iptables-libs as a dependency. -RUN rpm -i /tmp/rpms/iptables-libs-${IPTABLES_VER}.el8.aarch64.rpm && \ - rpm -i /tmp/rpms/iptables-legacy-libs-${IPTABLES_VER}.el8.2.aarch64.rpm - -# iproute-tc and nftables depend on iptables-libs and should be installed after it. -RUN microdnf --enablerepo=baseos install iproute-tc nftables - -# Install iptables via rpm. -RUN rpm -i /tmp/rpms/iptables-legacy-${IPTABLES_VER}.el8.2.aarch64.rpm && \ - rpm -i /tmp/rpms/iptables-nft-${IPTABLES_VER}.el8.aarch64.rpm && \ - # Install ipset version - rpm --force -i /tmp/rpms/ipset-libs-${IPSET_VER}.el8.aarch64.rpm && \ - rpm -i /tmp/rpms/ipset-${IPSET_VER}.el8.aarch64.rpm && \ - # Set alternatives - alternatives --install /usr/sbin/iptables iptables /usr/sbin/iptables-legacy 1 && \ - alternatives --install /usr/sbin/ip6tables ip6tables /usr/sbin/ip6tables-legacy 1 + conntrack-tools \ + # From baseos + iproute-tc iptables-nft RUN microdnf clean all -# Change the permissions for ipset so it can be run by any container user. -RUN chgrp 0 /usr/sbin/ipset && \ - chmod g=u /usr/sbin/ipset +# Copy in runit binaries +COPY --from=almalinux /root/admin/runit-${RUNIT_VER}/command/* /usr/local/bin/ -# Change the permissions for iptables so it can be run by any container user. -RUN chgrp 0 /usr/sbin/iptables && \ - chmod g=u /usr/sbin/iptables +# Copy in xtables-legacy binary and libs +COPY --from=almalinux /usr/sbin/xtables-legacy-multi /usr/sbin/xtables-legacy-multi +COPY --from=almalinux /lib64/libip4tc.so.2 /lib64/libip4tc.so.2 +COPY --from=almalinux /lib64/libip6tc.so.2 /lib64/libip6tc.so.2 -# Copy our bird binaries in -COPY --from=bird /bird* /bin/ +# Set symbolic links for iptables-legacy binaries +RUN set -eux; \ + for bin in iptables-legacy iptables-legacy-restore iptables-legacy-save ip6tables-legacy ip6tables-legacy-restore ip6tables-legacy-save; do \ + ln -sf xtables-legacy-multi /usr/sbin/${bin} || true; \ + done -# Set the suid bit on bird to allow our user to execute them with root permissions. -RUN chmod u+s /bin/bird -RUN chmod u+s /bin/bird6 +# Copy our bird binaries in +COPY --from=bird /bird /bin/bird +COPY --from=bird /birdcl /bin/birdcl +COPY --from=bird /bird6 /bin/bird6 +COPY --from=bird /birdcl6 /bin/birdcl6 # Copy in the filesystem - this contains licenses, etc... COPY filesystem/etc/ /etc @@ -174,26 +117,16 @@ COPY filesystem/included-source/ /included-source COPY filesystem/usr/ /usr COPY filesystem/sbin/* /usr/sbin/ -# Change permissions to make confd templates and output available in /etc/calico -# to all container users. -RUN chgrp -R 0 /etc/calico && \ - chmod -R g=u /etc/calico - -COPY --from=bpftool /bpftool /bin +COPY --from=bpftool /bpftool /bin/bpftool ARG BIN_DIR + # Copy in the calico-node binary COPY ${BIN_DIR}/calico-node-arm64 /bin/calico-node -# Set the suid bit on calico-node -RUN chmod u+s /bin/calico-node - # Copy in the mountns binary COPY ${BIN_DIR}/mountns-arm64 /bin/mountns -# Set the suid bit on mountns -RUN chmod u+s /bin/mountns - # Clean out as many files as we can from the filesystem. We no longer need dnf or the platform python install # or any of its dependencies. COPY clean-up-filesystem.sh /clean-up-filesystem.sh @@ -220,6 +153,14 @@ LABEL org.opencontainers.image.vendor="Project Calico" LABEL org.opencontainers.image.version="${GIT_VERSION}" LABEL org.opencontainers.image.licenses="Apache-2.0" +LABEL description="Calico node handles networking and policy for Calico" +LABEL maintainer="maintainers@tigera.io" +LABEL name="Calico node" +LABEL release=1 +LABEL summary="The primary networking and policy engine for Calico" +LABEL vendor="Project Calico" +LABEL version="${GIT_VERSION}" + COPY --from=ubi / / CMD ["start_runit"] diff --git a/node/Dockerfile.ppc64le b/node/Dockerfile.ppc64le index a7150628d76..00cee36e28d 100644 --- a/node/Dockerfile.ppc64le +++ b/node/Dockerfile.ppc64le @@ -38,6 +38,14 @@ LABEL org.opencontainers.image.vendor="Project Calico" LABEL org.opencontainers.image.version="${GIT_VERSION}" LABEL org.opencontainers.image.licenses="Apache-2.0" +LABEL description="Calico node handles networking and policy for Calico" +LABEL maintainer="maintainers@tigera.io" +LABEL name="Calico node" +LABEL release=1 +LABEL summary="The primary networking and policy engine for Calico" +LABEL vendor="Project Calico" +LABEL version="${GIT_VERSION}" + # Install remaining runtime deps required for felix from the global repository RUN apk add --no-cache bash ip6tables ipset iputils iproute2 conntrack-tools runit file ca-certificates diff --git a/node/Dockerfile.s390x b/node/Dockerfile.s390x index c781ab873a8..f1323141b76 100644 --- a/node/Dockerfile.s390x +++ b/node/Dockerfile.s390x @@ -35,6 +35,14 @@ LABEL org.opencontainers.image.vendor="Project Calico" LABEL org.opencontainers.image.version="${GIT_VERSION}" LABEL org.opencontainers.image.licenses="Apache-2.0" +LABEL description="Calico node handles networking and policy for Calico" +LABEL maintainer="maintainers@tigera.io" +LABEL name="Calico node" +LABEL release=1 +LABEL summary="The primary networking and policy engine for Calico" +LABEL vendor="Project Calico" +LABEL version="${GIT_VERSION}" + # Install remaining runtime deps required for felix from the global repository RUN apk add --no-cache bash ip6tables ipset iputils iproute2 conntrack-tools runit file ca-certificates diff --git a/node/Makefile b/node/Makefile index 63c277b3420..aed2d8ace36 100644 --- a/node/Makefile +++ b/node/Makefile @@ -5,7 +5,7 @@ PACKAGE_NAME = github.com/projectcalico/calico/node # Name of the images. # e.g., /: NODE_IMAGE ?=node -WINDOWS_IMAGE ?=node-windows +WINDOWS_IMAGE ?=$(NODE_IMAGE)-windows # We don't include the windows images here because we build them differently # using targets within this makefile. @@ -28,6 +28,9 @@ REMOTE_DEPS = $(LIBBPF_A) \ filesystem/etc/calico/confd/conf.d \ filesystem/etc/calico/confd/templates +# Run these tests against the projectcalico.org/v3 API group by default. +CALICO_API_GROUP ?= projectcalico.org/v3 + ############################################################################### # Include ../lib.Makefile # Additions to EXTRA_DOCKER_ARGS need to happen before the include since @@ -67,10 +70,6 @@ BIRD_SOURCE=filesystem/included-source/bird-$(BIRD_VERSION).tar.gz FELIX_GPL_SOURCE=filesystem/included-source/felix-ebpf-gpl.tar.xz INCLUDED_SOURCE=$(BIRD_SOURCE) $(FELIX_GPL_SOURCE) -# Versions and locations of dependencies used in tests. -TEST_CONTAINER_NAME_VER?=latest -TEST_CONTAINER_NAME?=calico/test:$(TEST_CONTAINER_NAME_VER)-$(ARCH) - TEST_CONTAINER_FILES=$(shell find tests/ -type f ! -name '*.created') # Variables controlling the image @@ -131,7 +130,7 @@ MICROSOFT_SDN_GITHUB_RAW_URL := https://raw.githubusercontent.com/microsoft/SDN/ # Variables used by the tests ST_TO_RUN?=tests/st/ K8ST_TO_RUN?=tests/ -# Can exclude the slower tests with "-a '!slow'" +# Can exclude the slower tests with "-m 'not slow'" ST_OPTIONS?= K8ST_REPORT_FILENAME ?= k8s-tests.xml @@ -146,6 +145,7 @@ DATE:=$(shell date -u +'%FT%T%z') SRC_FILES=$(shell find ./pkg -name '*.go') \ $(shell find ./cmd -name '*.go') \ $(shell find ../felix -name '*.go') \ + $(shell find ../app-policy -name '*.go') \ $(shell find ../typha -name '*.go') \ $(shell find ../felix -name '*.[ch]') \ $(shell find ../libcalico-go -name '*.go') \ @@ -302,6 +302,7 @@ K8ST_IMAGE_TARS=calico-node.tar \ calicoctl.tar \ kube-controllers.tar \ operator.tar \ + webhook.tar \ whisker.tar \ whisker-backend.tar \ goldmane.tar @@ -320,12 +321,17 @@ ut fv: $(LIBBPF_A) run-k8s-apiserver -v $(CERTS_PATH):/home/user/certs \ -e KUBECONFIG=/go/src/github.com/projectcalico/calico/hack/test/certs/kubeconfig \ -e ETCD_ENDPOINTS=http://$(LOCAL_IP_ENV):2379 \ + -e CALICO_API_GROUP=$(CALICO_API_GROUP) \ -e GINKGO_ARGS="$(GINKGO_ARGS)" \ $(CALICO_BUILD) ./run-uts $(WHAT) ############################################################################### # System tests ############################################################################### +CHARTS=../bin/tigera-operator-$(GIT_VERSION).tgz \ + ../bin/crd.projectcalico.org.v1-$(GIT_VERSION).tgz \ + ../bin/projectcalico.org.v3-$(GIT_VERSION).tgz + dist/calicoctl: mkdir -p dist make -C ../calicoctl build @@ -354,9 +360,13 @@ test_image: .calico_test.created $(DOCKER_BUILD) --build-arg ETCD_VERSION=$(ETCD_VERSION) -f calico_test/Dockerfile -t $(TEST_CONTAINER_NAME) calico_test touch $@ -chart: ../bin/tigera-operator-$(GIT_VERSION).tgz +chart: $(CHARTS) ../bin/tigera-operator-$(GIT_VERSION).tgz: make -C ../ bin/tigera-operator-$(GIT_VERSION).tgz +../bin/crd.projectcalico.org.v1-$(GIT_VERSION).tgz: + make -C ../ bin/crd.projectcalico.org.v1-$(GIT_VERSION).tgz +../bin/projectcalico.org.v3-$(GIT_VERSION).tgz: + make -C ../ bin/projectcalico.org.v3-$(GIT_VERSION).tgz # Tag to use for images built for e2e testing. This must match the image tag used in # tests/k8st/infra/calico_versions.yml @@ -367,8 +377,8 @@ operator.tar: $(K8ST_IMAGE_TARS) tests/k8st/infra/calico_versions.yml docker save --output $@ docker.io/tigera/operator:$(TEST_BUILD_TAG) calico-node.tar: $(NODE_CONTAINER_CREATED) - docker tag $(NODE_IMAGE):latest-$(ARCH) $(NODE_IMAGE):$(TEST_BUILD_TAG) - docker save --output $@ $(NODE_IMAGE):$(TEST_BUILD_TAG) + docker tag $(NODE_IMAGE):latest-$(ARCH) calico/node:$(TEST_BUILD_TAG) + docker save --output $@ calico/node:$(TEST_BUILD_TAG) calico-typha.tar: ../go.mod $(shell find ../typha -name '*.go') $(shell find ../libcalico-go -name '*.go') make -C ../typha image @@ -415,6 +425,11 @@ goldmane.tar: ../go.mod $(shell find ../goldmane -name '*.go') docker tag calico/goldmane:latest-$(ARCH) calico/goldmane:$(TEST_BUILD_TAG) docker save --output $@ calico/goldmane:$(TEST_BUILD_TAG) +webhook.tar: ../go.mod $(shell find ../webhooks -name '*.go') $(shell find ../libcalico-go -name '*.go') + make -C ../webhooks image + docker tag calico/webhooks:latest-$(ARCH) calico/webhooks:$(TEST_BUILD_TAG) + docker save --output $@ calico/webhooks:$(TEST_BUILD_TAG) + whisker.tar: ../go.mod $(shell find ../whisker) make -C ../whisker image docker tag calico/whisker:latest-$(ARCH) calico/whisker:$(TEST_BUILD_TAG) @@ -433,21 +448,6 @@ load-container-images: $(K8ST_IMAGE_TARS) $(KUBECTL) # calicoctl is deployed as a pod on the cluster and needs to be recreated. KUBECONFIG=$(KIND_KUBECONFIG) $(KUBECTL) apply -f tests/k8st/infra/calicoctl.yaml -.PHONY: st-checks -st-checks: - @if mount | grep -q "cgroup on /sys/fs/cgroup"; then \ - echo "cgroup v1 mounted"; \ - else \ - echo; \ - echo "ERROR: Looks like your system uses cgroupv2, which is "; \ - echo "not compatible with our ancient version of dind."; \ - echo "You can either run this test in a VM, or add "; \ - echo "systemd.unified_cgroup_hierarchy=0 to your kernel "; \ - echo "command line (if supported by your kernel/OS)."; \ - echo; \ - exit 1; \ - fi - .PHONY: k8s-test ## Run the k8s tests k8s-test: @@ -456,8 +456,13 @@ k8s-test: $(MAKE) kind-k8st-cleanup .PHONY: kind-k8st-setup -kind-k8st-setup: $(K8ST_IMAGE_TARS) ../bin/tigera-operator-$(GIT_VERSION).tgz kind-cluster-create - KUBECONFIG=$(KIND_KUBECONFIG) KIND=$(KIND) ARCH=$(ARCH) GIT_VERSION=$(GIT_VERSION) ./tests/k8st/deploy_resources_on_kind_cluster.sh +kind-k8st-setup: $(K8ST_IMAGE_TARS) $(CHARTS) kind-cluster-create + KUBECONFIG=$(KIND_KUBECONFIG) \ + KIND=$(KIND) \ + ARCH=$(ARCH) \ + GIT_VERSION=$(GIT_VERSION) \ + CALICO_API_GROUP=$(CALICO_API_GROUP) \ + ./tests/k8st/deploy_resources_on_kind_cluster.sh .PHONY: kind-k8st-run-test kind-k8st-run-test: .calico_test.created $(KIND_KUBECONFIG) @@ -471,40 +476,16 @@ kind-k8st-run-test: .calico_test.created $(KIND_KUBECONFIG) --net host \ ${TEST_CONTAINER_NAME} \ sh -c 'echo "container started.." && \ - cd /code/tests/k8st && nosetests $(K8ST_TO_RUN) -v --with-xunit --xunit-file="/code/report/$(K8ST_REPORT_FILENAME)" --with-timer' + cd /code/tests/k8st && pytest $(K8ST_TO_RUN) -v --junit-xml="/code/report/$(K8ST_REPORT_FILENAME)"' .PHONY: kind-k8st-cleanup kind-k8st-cleanup: kind-cluster-destroy -.PHONY: st -## Run the system tests -st: st-checks $(REMOTE_DEPS) image dist/calicoctl busybox.tar calico-node.tar workload.tar run-etcd .calico_test.created dist/calico dist/calico-ipam - # Use the host, PID and network namespaces from the host. - # Privileged is needed since 'calico node' write to /proc (to enable ip_forwarding) - # Map the docker socket in so docker can be used from inside the container - # HOST_CHECKOUT_DIR is used for volume mounts on containers started by this one. - # All of code under test is mounted into the container. - # - This also provides access to calicoctl and the docker client - docker run --uts=host \ - --pid=host \ - --net=host \ - --privileged \ - -v $(CURDIR):/code \ - -e HOST_CHECKOUT_DIR=$(CURDIR) \ - -e DEBUG_FAILURES=$(DEBUG_FAILURES) \ - -e MY_IP=$(LOCAL_IP_ENV) \ - -e NODE_CONTAINER_NAME=$(NODE_IMAGE):$(TEST_BUILD_TAG) \ - --rm -t \ - -v /var/run/docker.sock:/var/run/docker.sock \ - $(TEST_CONTAINER_NAME) \ - sh -c 'nosetests $(ST_TO_RUN) -v --with-xunit --xunit-file="/code/report/nosetests.xml" --with-timer $(ST_OPTIONS)' - $(MAKE) stop-etcd - ############################################################################### # CI/CD ############################################################################### .PHONY: ci -ci: static-checks ut image image-windows st +ci: static-checks ut image image-windows ## Deploys images to registry cd: image-all cd-common diff --git a/node/almalinux.repo b/node/almalinux.repo index 26d2049ac42..ff0b0ed7056 100644 --- a/node/almalinux.repo +++ b/node/almalinux.repo @@ -8,3 +8,12 @@ enabled=0 gpgcheck=0 countme=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux + + +[appstream] +name=AlmaLinux $releasever - AppStream +mirrorlist=https://mirrors.almalinux.org/mirrorlist/$releasever/appstream +enabled=0 +gpgcheck=0 +countme=1 +gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux diff --git a/node/calico_test/Dockerfile b/node/calico_test/Dockerfile index c40b96e7c18..d9aea574468 100644 --- a/node/calico_test/Dockerfile +++ b/node/calico_test/Dockerfile @@ -15,13 +15,13 @@ # ### calico/test # This image is used by various calico repositories and components to run UTs -# and STs. It has libcalico, nose, and other common python libraries +# and STs. It has libcalico, pytest, and other common python libraries # already installed # # For UTs: # - volume mount in python code that uses libcalico # - volume mount in your unit tests for this code -# - run 'nosetests' +# - run 'pytest' # # This container can also be used for running STs written in python. This # eliminates all dependencies besides docker on the host system to enable @@ -31,13 +31,16 @@ # containers alongside itself. # - eliminate most isolation, (--uts=host --pid=host --net=host --privileged) # - volume mount your ST source code -# - run 'nosetests' +# - run 'pytest' -FROM docker:18.09 +FROM docker:25 ARG ETCD_VERSION ARG TARGETARCH +# Update apk repositories first +RUN apk update + # Running STs in this container requires that it has all dependencies installed # for executing the tests. Install these dependencies: RUN apk add --no-cache \ @@ -53,10 +56,10 @@ RUN apk add --no-cache \ musl-dev \ netcat-openbsd \ openssl-dev \ - py-setuptools \ - py2-pip \ - python \ - python-dev \ + py3-setuptools \ + py3-pip \ + python3 \ + python3-dev \ tshark # Install etcdctl @@ -64,7 +67,7 @@ RUN curl -sfL https://github.com/coreos/etcd/releases/download/${ETCD_VERSION}/e tar xz --strip-components 1 -C /usr/local/bin etcd-${ETCD_VERSION}-linux-${TARGETARCH}/etcdctl COPY requirements.txt /requirements.txt -RUN pip install -r /requirements.txt +RUN pip3 install -r /requirements.txt --break-system-packages # The container is used by mounting the code-under-test to /code WORKDIR /code/ diff --git a/node/calico_test/requirements.txt b/node/calico_test/requirements.txt index 19d72689cec..d1639b353f5 100644 --- a/node/calico_test/requirements.txt +++ b/node/calico_test/requirements.txt @@ -1,39 +1,30 @@ atomicwrites==1.4.1 -attrs==21.4.0 -backports.functools-lru-cache==1.6.6 -cachetools==3.1.1 -chardet==4.0.0 -configparser==4.0.2 -contextlib2==0.6.0.post1 -deepdiff==3.3.0 -enum34==1.1.10 -funcsigs==1.0.2 -google-auth==2.18.1 -importlib-metadata==2.1.3 -ipaddress==1.0.23 -jsonpickle==2.2.0 -kubernetes==18.20.0 -more-itertools==5.0.0 -netaddr==0.7.19 -nose-parameterized==0.6.0 -nose-timer==0.7.1 -nose==1.3.7 -oauthlib==3.1.0 -packaging==20.9 -pathlib2==2.3.7.post1 -pluggy==0.13.1 -pyasn1-modules==0.3.0 -pyasn1==0.5.1 -pyparsing==2.4.7 -pytest==4.6.11 -python-dateutil==2.8.2 -PyYAML==5.3.1 -requests-oauthlib==1.3.1 -scandir==1.10.0 -simplejson==3.13.2 -six==1.16.0 -termcolor==1.1.0 -typing==3.10.0.0 -urllib3==1.26.19 -wcwidth==0.2.13 -websocket-client==0.59.0 +attrs==25.4.0 +cachetools==7.0.0 +chardet==5.2.0 +configparser +contextlib2 +deepdiff==8.6.1 +google-auth==2.48.0 +importlib-metadata +jsonpickle==4.1.1 +kubernetes==35.0.0 +more-itertools==10.8.0 +netaddr==1.3.0 +oauthlib==3.3.1 +#packaging - already provided by Alpine +pluggy==1.6.0 +pyasn1-modules==0.4.2 +pyasn1==0.6.2 +#pyparsing - already provided by Alpine +pytest==9.0.2 +pytest-timeout +python-dateutil==2.9.0.post0 +PyYAML==6.0.3 +requests-oauthlib==2.0.0 +simplejson==3.20.2 +six==1.17.0 +termcolor==3.3.0 +urllib3==2.6.3 +wcwidth==0.5.3 +websocket-client==1.9.0 diff --git a/node/clean-up-filesystem.sh b/node/clean-up-filesystem.sh index f7cc4430b26..015208484a9 100755 --- a/node/clean-up-filesystem.sh +++ b/node/clean-up-filesystem.sh @@ -112,7 +112,7 @@ bin_allow_list_patterns=( '/test$' ulimit uniq - wait + '/wait$' which whoami yes @@ -275,6 +275,7 @@ packages_to_keep=( libpwquality libreadline libselinux + libsigsegv # used by grep in ubi9 libzstd libz ncurses diff --git a/node/deps.txt b/node/deps.txt index 655f9735eef..68d39ed0dda 100644 --- a/node/deps.txt +++ b/node/deps.txt @@ -2,43 +2,47 @@ This file contains the list of modules that this package depends on in order to trigger CI on changes -go 1.25.1 +go 1.25.7 github.com/BurntSushi/toml v1.5.0 +github.com/DeRuina/timberjack v1.3.9 github.com/alexflint/go-filemutex v1.3.0 -github.com/aws/aws-sdk-go-v2 v1.38.0 -github.com/aws/aws-sdk-go-v2/config v1.31.0 -github.com/aws/aws-sdk-go-v2/credentials v1.18.4 -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.3 -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.3 -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.3 -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 -github.com/aws/aws-sdk-go-v2/service/ec2 v1.242.0 -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.3 -github.com/aws/aws-sdk-go-v2/service/sso v1.28.0 -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.0 -github.com/aws/aws-sdk-go-v2/service/sts v1.37.0 -github.com/aws/smithy-go v1.22.5 +github.com/aws/aws-sdk-go-v2 v1.39.5 +github.com/aws/aws-sdk-go-v2/config v1.31.16 +github.com/aws/aws-sdk-go-v2/credentials v1.18.20 +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.12 +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.12 +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.12 +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 +github.com/aws/aws-sdk-go-v2/service/ec2 v1.259.0 +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.12 +github.com/aws/aws-sdk-go-v2/service/sso v1.30.0 +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.4 +github.com/aws/aws-sdk-go-v2/service/sts v1.39.0 +github.com/aws/smithy-go v1.23.1 github.com/beorn7/perks v1.0.1 github.com/blang/semver/v4 v4.0.0 github.com/cespare/xxhash/v2 v2.3.0 -github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 -github.com/containernetworking/cni v1.2.3 -github.com/containernetworking/plugins v1.6.2 +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 +github.com/containernetworking/cni v1.3.0 +github.com/containernetworking/plugins v1.9.0 github.com/coreos/go-iptables v0.8.0 github.com/coreos/go-semver v0.3.1 -github.com/coreos/go-systemd/v22 v22.5.0 +github.com/coreos/go-systemd/v22 v22.6.0 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/distribution/reference v0.6.0 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 -github.com/emicklei/go-restful/v3 v3.11.0 +github.com/emicklei/go-restful v2.15.0+incompatible +github.com/emicklei/go-restful/v3 v3.12.2 github.com/envoyproxy/go-control-plane v0.13.4 -github.com/envoyproxy/go-control-plane/envoy v1.32.4 +github.com/envoyproxy/go-control-plane/envoy v1.35.0 github.com/envoyproxy/protoc-gen-validate v1.2.1 github.com/fsnotify/fsnotify v1.9.0 -github.com/fxamacker/cbor/v2 v2.7.0 +github.com/fxamacker/cbor/v2 v2.9.0 github.com/gavv/monotime v0.0.0-20190418164738-30dba4353424 github.com/go-ini/ini v1.67.0 +github.com/go-kit/log v0.2.1 +github.com/go-logfmt/logfmt v0.6.0 github.com/go-logr/logr v1.4.3 github.com/go-openapi/jsonpointer v0.21.0 github.com/go-openapi/jsonreference v0.20.2 @@ -46,17 +50,17 @@ github.com/go-openapi/swag v0.23.0 github.com/go-playground/locales v0.14.1 github.com/go-playground/universal-translator v0.18.1 github.com/go-viper/mapstructure/v2 v2.4.0 -github.com/gofrs/flock v0.12.1 +github.com/gofrs/flock v0.13.0 github.com/gogo/protobuf v1.3.2 github.com/golang/protobuf v1.5.4 github.com/golang/snappy v1.0.0 github.com/google/btree v1.1.3 -github.com/google/gnostic-models v0.6.9 +github.com/google/gnostic-models v0.7.0 github.com/google/go-cmp v0.7.0 -github.com/google/gopacket v1.1.19 github.com/google/uuid v1.6.0 -github.com/grpc-ecosystem/grpc-gateway v1.16.0 -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 +github.com/gopacket/gopacket v1.4.0 +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 github.com/jinzhu/copier v0.4.0 github.com/josharian/intern v1.0.0 github.com/josharian/native v1.1.0 @@ -64,6 +68,7 @@ github.com/json-iterator/go v1.1.12 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 github.com/kelseyhightower/envconfig v1.4.0 github.com/kelseyhightower/memkv v0.1.1 +github.com/klauspost/compress v1.18.0 github.com/leodido/go-urn v1.4.0 github.com/lithammer/dedent v1.1.0 github.com/mailru/easyjson v0.7.7 @@ -74,85 +79,86 @@ github.com/mdlayher/netlink v1.7.2 github.com/mdlayher/socket v0.5.1 github.com/mipearson/rfw v0.0.0-20170619235010-6f0a6f3266ba github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd -github.com/modern-go/reflect2 v1.0.2 +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 github.com/natefinch/atomic v1.0.1 github.com/olekukonko/tablewriter v0.0.5 -github.com/onsi/gomega v1.38.0 +github.com/onsi/gomega v1.39.1 github.com/opencontainers/go-digest v1.0.0 +github.com/openshift/custom-resource-status v1.1.2 github.com/pelletier/go-toml v1.9.5 -github.com/pelletier/go-toml/v2 v2.2.3 +github.com/pelletier/go-toml/v2 v2.2.4 github.com/pkg/errors v0.9.1 +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/projectcalico/calico -github.com/projectcalico/calico/lib/std v0.0.0-00010101000000-000000000000 -github.com/projectcalico/go-json v0.0.0-20161128004156-6219dc7339ba -github.com/projectcalico/go-yaml-wrapper v0.0.0-20191112210931-090425220c54 -github.com/prometheus/client_golang v1.23.0 +github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 -github.com/prometheus/common v0.65.0 -github.com/prometheus/procfs v0.17.0 +github.com/prometheus/common v0.67.2 +github.com/prometheus/procfs v0.19.2 github.com/rivo/uniseg v0.4.7 github.com/safchain/ethtool v0.6.2 -github.com/sagikazarmark/locafero v0.7.0 +github.com/sagikazarmark/locafero v0.11.0 github.com/sirupsen/logrus v1.9.3 -github.com/sourcegraph/conc v0.3.0 -github.com/spf13/afero v1.12.0 -github.com/spf13/cast v1.9.2 -github.com/spf13/cobra v1.9.1 -github.com/spf13/pflag v1.0.7 -github.com/spf13/viper v1.20.1 +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 +github.com/spf13/afero v1.15.0 +github.com/spf13/cast v1.10.0 +github.com/spf13/cobra v1.10.1 +github.com/spf13/pflag v1.0.10 +github.com/spf13/viper v1.21.0 github.com/subosito/gotenv v1.6.0 github.com/tchap/go-patricia/v2 v2.3.3 github.com/vishvananda/netlink v1.3.1 github.com/vishvananda/netns v0.0.5 github.com/x448/float16 v0.8.4 -go.etcd.io/etcd/api/v3 v3.6.4 -go.etcd.io/etcd/client/pkg/v3 v3.6.4 -go.etcd.io/etcd/client/v3 v3.6.4 -go.opentelemetry.io/otel v1.35.0 -go.opentelemetry.io/otel/trace v1.35.0 +go.etcd.io/etcd/api/v3 v3.6.5 +go.etcd.io/etcd/client/pkg/v3 v3.6.5 +go.etcd.io/etcd/client/v3 v3.6.5 +go.opentelemetry.io/otel v1.37.0 +go.opentelemetry.io/otel/trace v1.37.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 -go.yaml.in/yaml/v2 v2.4.2 -golang.org/x/crypto v0.41.0 -golang.org/x/net v0.43.0 -golang.org/x/oauth2 v0.30.0 -golang.org/x/sync v0.16.0 -golang.org/x/sys v0.35.0 -golang.org/x/term v0.34.0 -golang.org/x/text v0.28.0 -golang.org/x/time v0.12.0 +go.yaml.in/yaml/v2 v2.4.3 +go.yaml.in/yaml/v3 v3.0.4 +golang.org/x/crypto v0.47.0 +golang.org/x/net v0.49.0 +golang.org/x/oauth2 v0.34.0 +golang.org/x/sync v0.19.0 +golang.org/x/sys v0.40.0 +golang.org/x/term v0.39.0 +golang.org/x/text v0.33.0 +golang.org/x/time v0.14.0 golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 google.golang.org/genproto v0.0.0-20250603155806-513f23925822 -google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 -google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a -google.golang.org/grpc v1.72.2 -google.golang.org/protobuf v1.36.7 +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c +google.golang.org/grpc v1.76.0 +google.golang.org/protobuf v1.36.10 gopkg.in/evanphx/json-patch.v4 v4.12.0 gopkg.in/go-playground/validator.v9 v9.30.2 gopkg.in/inf.v0 v0.9.1 -gopkg.in/natefinch/lumberjack.v2 v2.2.1 -gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 -k8s.io/api v0.33.5 -k8s.io/apiextensions-apiserver v0.33.5 -k8s.io/apimachinery v0.33.5 -k8s.io/apiserver v0.33.5 -k8s.io/client-go v0.33.5 -k8s.io/component-base v0.33.5 -k8s.io/component-helpers v0.33.5 -k8s.io/controller-manager v0.33.5 +k8s.io/api v0.34.3 +k8s.io/apiextensions-apiserver v0.34.3 +k8s.io/apimachinery v0.34.3 +k8s.io/apiserver v0.34.3 +k8s.io/client-go v0.34.3 +k8s.io/component-base v0.34.3 +k8s.io/component-helpers v0.34.3 +k8s.io/controller-manager v0.34.3 k8s.io/klog v0.2.0 k8s.io/klog/v2 v2.130.1 -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff -k8s.io/kubelet v0.33.5 -k8s.io/kubernetes v1.33.5 -k8s.io/utils v0.0.0-20241210054802-24370beab758 -modernc.org/memory v1.10.0 +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b +k8s.io/kubelet v0.34.3 +k8s.io/kubernetes v1.34.3 +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 +kubevirt.io/api v1.8.0-alpha.0 +kubevirt.io/containerized-data-importer-api v1.63.1 +kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 +modernc.org/memory v1.11.0 sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 -sigs.k8s.io/knftables v0.0.18 -sigs.k8s.io/network-policy-api v0.1.5 +sigs.k8s.io/knftables v0.0.19 +sigs.k8s.io/network-policy-api v0.1.8-0.20260212153203-412bf65729a5 sigs.k8s.io/randfill v1.0.0 -sigs.k8s.io/structured-merge-diff/v4 v4.6.0 +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 sigs.k8s.io/yaml v1.6.0 diff --git a/node/pkg/allocateip/allocateip.go b/node/pkg/allocateip/allocateip.go index a2eb12d4af4..4e6d138acf2 100644 --- a/node/pkg/allocateip/allocateip.go +++ b/node/pkg/allocateip/allocateip.go @@ -26,7 +26,7 @@ import ( felixconfig "github.com/projectcalico/calico/felix/config" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapi "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/backend/syncersv1/tunnelipsyncer" @@ -100,7 +100,7 @@ func run( cfg: cfg, client: c, ch: make(chan struct{}), - data: make(map[string]interface{}), + data: make(map[string]any), felixEnvConfig: felixEnvConfig, } @@ -135,7 +135,7 @@ type reconciler struct { cfg *apiconfig.CalicoAPIConfig client client.Interface ch chan struct{} - data map[string]interface{} + data map[string]any felixEnvConfig *felixconfig.Config inSync bool } @@ -170,47 +170,61 @@ func (r *reconciler) OnStatusUpdated(status bapi.SyncStatus) { // OnUpdates handles the syncer resource updates. func (r *reconciler) OnUpdates(updates []bapi.Update) { var updated bool + for _, u := range updates { - switch u.UpdateType { - case bapi.UpdateTypeKVDeleted: - // Resource is deleted. If this resource is in our cache then trigger an update. - if _, ok := r.data[u.Key.String()]; ok { + key := u.Key.String() + + // The presence (or absence) of u.Value determines if this is a delete. + // Even if UpdateType is "New" or "Updated", Value may be nil if validation failed in the syncer. + if u.Value == nil { + log.WithField("key", key).Debug("Received delete or invalid update (Value is nil)") + + // If we had cached data for this key, remove it and trigger reconciliation. + if _, ok := r.data[key]; ok { updated = true + log.WithField("key", key).Debug("Deleted cached entry - trigger reconciliation") } - delete(r.data, u.Key.String()) - case bapi.UpdateTypeKVNew, bapi.UpdateTypeKVUpdated: - // Resource is created or updated. Depending on the resource, we extract and cache the relevant data that - // we are monitoring. If the data has changed then trigger an update. - var data interface{} - switch v := u.Value.(type) { - case *model.IPPool: - // For pools just track the whole data. - log.Debugf("Updated pool resource: %s", u.Key) - data = v - case *libapi.Node: - // For nodes, we only care about our own node, *and* we only care about the wireguard public key. - if v.Name != r.nodename { - continue - } - log.Debugf("Updated node resource: %s", u.Key) - data = wireguardData{ - publicKey: v.Status.WireguardPublicKey, - publicKeyV6: v.Status.WireguardPublicKeyV6, - } - default: - // We got an update for an unexpected resource type. Rather than ignore, just treat as updated so that - // we reconcile the addresses. - log.Warningf("Unexpected resource update: %s", u.Key) - updated = true + delete(r.data, key) + continue + } + + // Handle non-nil value updates (Add or Update) + var data any + switch v := u.Value.(type) { + case *model.IPPool: + log.Debugf("Updated IPPool resource: %s", key) + data = v + + case *internalapi.Node: + // Only process our own node + if v.Name != r.nodename { continue } + log.Debugf("Updated Node resource: %s", key) - if existing, ok := r.data[u.Key.String()]; !ok || !reflect.DeepEqual(existing, data) { - // Entry is new or associated data is modified. In either case update the data and flag as updated. - log.Debug("Stored data has been modified - trigger reconciliation") - updated = true - r.data[u.Key.String()] = data + // Track both IPv4 and IPv6 WireGuard public keys + data = wireguardData{ + publicKey: v.Status.WireguardPublicKey, + publicKeyV6: v.Status.WireguardPublicKeyV6, } + + default: + // We got an update for an unexpected resource type. Rather than ignore, just treat as updated so that + // we reconcile the addresses. + log.Warningf("Unexpected resource update: %s", key) + updated = true + continue + } + + if existing, ok := r.data[key]; !ok || !reflect.DeepEqual(existing, data) { + // Entry is new or associated data is modified. In either case update the data and flag as updated. + log.WithFields(log.Fields{ + "key": key, + "existing": existing, + "new": data, + }).Debug("Stored data has changed — trigger reconciliation") + r.data[key] = data + updated = true } } @@ -286,7 +300,7 @@ func reconcileTunnelAddrs( return nil } -func ensureHostTunnelAddress(ctx context.Context, c client.Interface, node *libapi.Node, cidrs []net.IPNet, attrType string) (string, error) { +func ensureHostTunnelAddress(ctx context.Context, c client.Interface, node *internalapi.Node, cidrs []net.IPNet, attrType string) (string, error) { logCtx := getLogger(attrType) logCtx.WithField("node", node.Name).Debug("Ensure tunnel address is set") @@ -332,8 +346,10 @@ func ensureHostTunnelAddress(ctx context.Context, c client.Interface, node *liba } // Check if we got correct assignment attributes. - attr, handle, err := c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: ipAddr}) + allocAttr, err := c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: ipAddr}) if err == nil { + attr := allocAttr.ActiveOwnerAttrs + handle := allocAttr.HandleID if attr[ipam.AttributeType] == attrType && attr[ipam.AttributeNode] == node.Name { // The tunnel address is still assigned to this node, but is it in the correct pool this time? if !isIpInPool(addr, cidrs) { @@ -468,7 +484,7 @@ func generateHandleAndAttributes(nodename string, attrType string) (string, map[ // assignHostTunnelAddr claims an IP address from the first pool // with some space. Stores the result in the host's config as its tunnel // address. It will assign a VXLAN address if vxlan is true, otherwise an IPIP address. -func assignHostTunnelAddr(ctx context.Context, c client.Interface, node *libapi.Node, cidrs []net.IPNet, attrType string) (string, error) { +func assignHostTunnelAddr(ctx context.Context, c client.Interface, node *internalapi.Node, cidrs []net.IPNet, attrType string) (string, error) { // Build attributes and handle for this allocation. handle, attrs := generateHandleAndAttributes(node.Name, attrType) logCtx := getLogger(attrType) @@ -523,7 +539,7 @@ func assignHostTunnelAddr(ctx context.Context, c client.Interface, node *libapi. return ip, nil } -func updateNodeWithAddress(node *libapi.Node, addr string, attrType string) { +func updateNodeWithAddress(node *internalapi.Node, addr string, attrType string) { switch attrType { case ipam.AttributeTypeVXLAN: node.Spec.IPv4VXLANTunnelAddr = addr @@ -531,17 +547,17 @@ func updateNodeWithAddress(node *libapi.Node, addr string, attrType string) { node.Spec.IPv6VXLANTunnelAddr = addr case ipam.AttributeTypeIPIP: if node.Spec.BGP == nil { - node.Spec.BGP = &libapi.NodeBGPSpec{} + node.Spec.BGP = &internalapi.NodeBGPSpec{} } node.Spec.BGP.IPv4IPIPTunnelAddr = addr case ipam.AttributeTypeWireguard: if node.Spec.Wireguard == nil { - node.Spec.Wireguard = &libapi.NodeWireguardSpec{} + node.Spec.Wireguard = &internalapi.NodeWireguardSpec{} } node.Spec.Wireguard.InterfaceIPv4Address = addr case ipam.AttributeTypeWireguardV6: if node.Spec.Wireguard == nil { - node.Spec.Wireguard = &libapi.NodeWireguardSpec{} + node.Spec.Wireguard = &internalapi.NodeWireguardSpec{} } node.Spec.Wireguard.InterfaceIPv6Address = addr } @@ -550,7 +566,7 @@ func updateNodeWithAddress(node *libapi.Node, addr string, attrType string) { // removeHostTunnelAddr removes any existing IP address for this host's // tunnel device and releases the IP from IPAM. If no IP is assigned this function // is a no-op. -func removeHostTunnelAddr(ctx context.Context, c client.Interface, node *libapi.Node, attrType string) error { +func removeHostTunnelAddr(ctx context.Context, c client.Interface, node *internalapi.Node, attrType string) error { logCtx := getLogger(attrType) logCtx.WithField("node", node.Name).Debug("Remove tunnel addresses") @@ -571,7 +587,7 @@ func removeHostTunnelAddr(ctx context.Context, c client.Interface, node *libapi. // If removing the tunnel address causes the BGP spec to be empty, then nil it out. // libcalico asserts that if a BGP spec is present, that it not be empty. - if reflect.DeepEqual(*node.Spec.BGP, libapi.NodeBGPSpec{}) { + if reflect.DeepEqual(*node.Spec.BGP, internalapi.NodeBGPSpec{}) { logCtx.Debug("BGP spec is now empty, setting to nil") node.Spec.BGP = nil } @@ -581,7 +597,7 @@ func removeHostTunnelAddr(ctx context.Context, c client.Interface, node *libapi. ipAddrStr = node.Spec.Wireguard.InterfaceIPv4Address node.Spec.Wireguard.InterfaceIPv4Address = "" - if reflect.DeepEqual(*node.Spec.Wireguard, libapi.NodeWireguardSpec{}) { + if reflect.DeepEqual(*node.Spec.Wireguard, internalapi.NodeWireguardSpec{}) { logCtx.Debug("Wireguard spec is now empty, setting to nil") node.Spec.Wireguard = nil } @@ -591,7 +607,7 @@ func removeHostTunnelAddr(ctx context.Context, c client.Interface, node *libapi. ipAddrStr = node.Spec.Wireguard.InterfaceIPv6Address node.Spec.Wireguard.InterfaceIPv6Address = "" - if reflect.DeepEqual(*node.Spec.Wireguard, libapi.NodeWireguardSpec{}) { + if reflect.DeepEqual(*node.Spec.Wireguard, internalapi.NodeWireguardSpec{}) { logCtx.Debug("Wireguard spec is now empty, setting to nil") node.Spec.Wireguard = nil } @@ -629,33 +645,37 @@ func removeHostTunnelAddr(ctx context.Context, c client.Interface, node *libapi. // belongs to us. If it has no handle and no attributes, then we can pretty confidently // say that it belongs to us rather than a pod and should be cleaned up. logCtx.WithField("handle", handle).Info("No IPs with handle, release exact IP") - attr, storedHandle, err := c.IPAM().GetAssignmentAttributes(ctx, *ipAddr) + allocAttr, err := c.IPAM().GetAssignmentAttributes(ctx, *ipAddr) if err != nil { if _, ok := err.(cerrors.ErrorResourceDoesNotExist); !ok { logCtx.WithError(err).Error("Failed to query attributes") return err } // Scenario #1: The allocation actually doesn't exist, we don't have anything to do. - } else if len(attr) == 0 && storedHandle == nil { - // Scenario #2: The allocation exists, but has no handle whatsoever. - // This is an ancient allocation and can be released. - if _, _, err := c.IPAM().ReleaseIPs(ctx, ipam.ReleaseOptions{Address: ipAddr.String()}); err != nil { - logCtx.WithError(err).WithField("IP", ipAddr.String()).Error("Error releasing address from IPAM") - return err - } - } else if storedHandle != nil && *storedHandle == handle { - // Scenario #3: The allocation exists, has a handle, and it matches the one we expect. - // This means the handle object itself was wrongfully deleted. We can clean it up - // by releasing the IP directly with both address and handle specified. - if _, _, err := c.IPAM().ReleaseIPs(ctx, ipam.ReleaseOptions{Address: ipAddr.String(), Handle: handle}); err != nil { - logCtx.WithError(err).WithField("IP", ipAddr.String()).Error("Error releasing address from IPAM") - return err - } } else { - // The final scenario: the IP on the node is allocated, but it is allocated to some other handle. - // It doesn't belong to us. We can't do anything here but it's worth logging. - fields := log.Fields{"attributes": attr, "IP": ipAddr.String()} - logCtx.WithFields(fields).Warnf("IP address has been reused by something else") + attr := allocAttr.ActiveOwnerAttrs + storedHandle := allocAttr.HandleID + if len(attr) == 0 && storedHandle == nil { + // Scenario #2: The allocation exists, but has no handle whatsoever. + // This is an ancient allocation and can be released. + if _, _, err := c.IPAM().ReleaseIPs(ctx, ipam.ReleaseOptions{Address: ipAddr.String()}); err != nil { + logCtx.WithError(err).WithField("IP", ipAddr.String()).Error("Error releasing address from IPAM") + return err + } + } else if storedHandle != nil && *storedHandle == handle { + // Scenario #3: The allocation exists, has a handle, and it matches the one we expect. + // This means the handle object itself was wrongfully deleted. We can clean it up + // by releasing the IP directly with both address and handle specified. + if _, _, err := c.IPAM().ReleaseIPs(ctx, ipam.ReleaseOptions{Address: ipAddr.String(), Handle: handle}); err != nil { + logCtx.WithError(err).WithField("IP", ipAddr.String()).Error("Error releasing address from IPAM") + return err + } + } else { + // The final scenario: the IP on the node is allocated, but it is allocated to some other handle. + // It doesn't belong to us. We can't do anything here but it's worth logging. + fields := log.Fields{"attributes": attr, "IP": ipAddr.String()} + logCtx.WithFields(fields).Warnf("IP address has been reused by something else") + } } } } @@ -665,7 +685,7 @@ func removeHostTunnelAddr(ctx context.Context, c client.Interface, node *libapi. // determineEnabledPools returns all enabled pools. If vxlan is true, then it will only return VXLAN pools. Otherwise // it will only return IPIP enabled pools. func determineEnabledPoolCIDRs( - node libapi.Node, ipPoolList api.IPPoolList, felixEnvConfig *felixconfig.Config, attrType string, + node internalapi.Node, ipPoolList api.IPPoolList, felixEnvConfig *felixconfig.Config, attrType string, ) []net.IPNet { // For wireguard, an IP is only allocated from a pool if wireguard is actually running (there will be a public // key configured on the node), and the cluster is not running in host encryption mode (which is required for @@ -722,30 +742,33 @@ func determineEnabledPoolCIDRs( continue } + // An IP pool is disabled either if explicitly configured as such, or if it is currently being deleted. + disabled := ipPool.Spec.Disabled || ipPool.GetDeletionTimestamp() != nil + // Check if desired encap is enabled in the IP pool, the IP pool is not disabled, and it is IPv4 pool since we // don't support encap with IPv6. switch attrType { case ipam.AttributeTypeVXLAN: - if (ipPool.Spec.VXLANMode == api.VXLANModeAlways || ipPool.Spec.VXLANMode == api.VXLANModeCrossSubnet) && !ipPool.Spec.Disabled && poolCidr.Version() == 4 { + if (ipPool.Spec.VXLANMode == api.VXLANModeAlways || ipPool.Spec.VXLANMode == api.VXLANModeCrossSubnet) && !disabled && poolCidr.Version() == 4 { cidrs = append(cidrs, *poolCidr) } case ipam.AttributeTypeVXLANV6: - if (ipPool.Spec.VXLANMode == api.VXLANModeAlways || ipPool.Spec.VXLANMode == api.VXLANModeCrossSubnet) && !ipPool.Spec.Disabled && poolCidr.Version() == 6 { + if (ipPool.Spec.VXLANMode == api.VXLANModeAlways || ipPool.Spec.VXLANMode == api.VXLANModeCrossSubnet) && !disabled && poolCidr.Version() == 6 { cidrs = append(cidrs, *poolCidr) } case ipam.AttributeTypeIPIP: // Check if IPIP is enabled in the IP pool, the IP pool is not disabled, and it is IPv4 pool since we don't support IPIP with IPv6. - if (ipPool.Spec.IPIPMode == api.IPIPModeCrossSubnet || ipPool.Spec.IPIPMode == api.IPIPModeAlways) && !ipPool.Spec.Disabled && poolCidr.Version() == 4 { + if (ipPool.Spec.IPIPMode == api.IPIPModeCrossSubnet || ipPool.Spec.IPIPMode == api.IPIPModeAlways) && !disabled && poolCidr.Version() == 4 { cidrs = append(cidrs, *poolCidr) } case ipam.AttributeTypeWireguard: // Wireguard does not require a specific encap configuration on the pool. - if !ipPool.Spec.Disabled && poolCidr.Version() == 4 { + if !disabled && poolCidr.Version() == 4 { cidrs = append(cidrs, *poolCidr) } case ipam.AttributeTypeWireguardV6: // Wireguard does not require a specific encap configuration on the pool. - if !ipPool.Spec.Disabled && poolCidr.Version() == 6 { + if !disabled && poolCidr.Version() == 6 { cidrs = append(cidrs, *poolCidr) } } diff --git a/node/pkg/allocateip/allocateip_suite_test.go b/node/pkg/allocateip/allocateip_suite_test.go index b867fcb12c6..816ec961eac 100644 --- a/node/pkg/allocateip/allocateip_suite_test.go +++ b/node/pkg/allocateip/allocateip_suite_test.go @@ -3,9 +3,8 @@ package allocateip_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -15,7 +14,8 @@ func init() { } func TestCommands(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/allocateipaddr_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "AllocateIPAddr Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/allocateipaddr_suite.xml" + ginkgo.RunSpecs(t, "AllocateIPAddr Suite", suiteConfig, reporterConfig) } diff --git a/node/pkg/allocateip/allocateip_test.go b/node/pkg/allocateip/allocateip_test.go index 058b0b163e1..d075a4691f0 100644 --- a/node/pkg/allocateip/allocateip_test.go +++ b/node/pkg/allocateip/allocateip_test.go @@ -20,15 +20,14 @@ import ( "fmt" gnet "net" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" felixconfig "github.com/projectcalico/calico/felix/config" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapi "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend" bapi "github.com/projectcalico/calico/libcalico-go/lib/backend/api" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" @@ -38,7 +37,7 @@ import ( "github.com/projectcalico/calico/libcalico-go/lib/options" ) -func setTunnelAddressForNode(tunnelType string, n *libapi.Node, addr string) { +func setTunnelAddressForNode(tunnelType string, n *internalapi.Node, addr string) { switch tunnelType { case ipam.AttributeTypeIPIP: n.Spec.BGP.IPv4IPIPTunnelAddr = addr @@ -49,7 +48,7 @@ func setTunnelAddressForNode(tunnelType string, n *libapi.Node, addr string) { case ipam.AttributeTypeWireguard: if addr != "" { if n.Spec.Wireguard == nil { - n.Spec.Wireguard = &libapi.NodeWireguardSpec{} + n.Spec.Wireguard = &internalapi.NodeWireguardSpec{} } n.Spec.Wireguard.InterfaceIPv4Address = addr } else if n.Spec.Wireguard != nil { @@ -61,7 +60,7 @@ func setTunnelAddressForNode(tunnelType string, n *libapi.Node, addr string) { case ipam.AttributeTypeWireguardV6: if addr != "" { if n.Spec.Wireguard == nil { - n.Spec.Wireguard = &libapi.NodeWireguardSpec{} + n.Spec.Wireguard = &internalapi.NodeWireguardSpec{} } n.Spec.Wireguard.InterfaceIPv6Address = addr } else if n.Spec.Wireguard != nil { @@ -150,7 +149,7 @@ func expectTunnelAddressEmptyForNodeName(c client.Interface, nodeName string, tu Expect(checkTunnelAddressEmptyForNodeName(c, nodeName, tunnelType)).NotTo(HaveOccurred()) } -func expectTunnelAddressEmpty(n *libapi.Node, tunnelType string) { +func expectTunnelAddressEmpty(n *internalapi.Node, tunnelType string) { Expect(checkTunnelAddressEmpty(n, tunnelType)).NotTo(HaveOccurred()) } @@ -160,7 +159,7 @@ func checkTunnelAddressEmptyForNodeName(c client.Interface, nodeName string, tun return checkTunnelAddressEmpty(n, tunnelType) } -func checkTunnelAddressEmpty(n *libapi.Node, tunnelType string) error { +func checkTunnelAddressEmpty(n *internalapi.Node, tunnelType string) error { var addr string switch tunnelType { case ipam.AttributeTypeIPIP: @@ -190,7 +189,7 @@ func expectTunnelAddressForNodeName(c client.Interface, nodeName string, tunnelT Expect(checkTunnelAddressForNodeName(c, nodeName, tunnelType, addr)).NotTo(HaveOccurred()) } -func expectTunnelAddressForNode(c client.Interface, n *libapi.Node, tunnelType string, addr string) { +func expectTunnelAddressForNode(c client.Interface, n *internalapi.Node, tunnelType string, addr string) { Expect(checkTunnelAddressForNode(c, n, tunnelType, addr)).NotTo(HaveOccurred()) } @@ -200,7 +199,7 @@ func checkTunnelAddressForNodeName(c client.Interface, nodeName string, tunnelTy return checkTunnelAddressForNode(c, n, tunnelType, expected) } -func checkTunnelAddressForNode(c client.Interface, n *libapi.Node, tunnelType string, expected string) error { +func checkTunnelAddressForNode(c client.Interface, n *internalapi.Node, tunnelType string, expected string) error { ctx := context.Background() // Check the address in the node is as expected. @@ -228,13 +227,13 @@ func checkTunnelAddressForNode(c client.Interface, n *libapi.Node, tunnelType st } // Also check the assignment attributes for this IP match. - attr, _, err := c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: gnet.ParseIP(expected)}) + allocAttr, err := c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: gnet.ParseIP(expected)}) if err != nil { return err - } else if attr[ipam.AttributeNode] != n.Name { - return fmt.Errorf("Unexpected node attribute %s, expected %s", attr[ipam.AttributeNode], n.Name) - } else if attr[ipam.AttributeType] != tunnelType { - return fmt.Errorf("Unexpected ipam type attribute %s, expected %s", attr[ipam.AttributeType], tunnelType) + } else if allocAttr.ActiveOwnerAttrs[ipam.AttributeNode] != n.Name { + return fmt.Errorf("Unexpected node attribute %s, expected %s", allocAttr.ActiveOwnerAttrs[ipam.AttributeNode], n.Name) + } else if allocAttr.ActiveOwnerAttrs[ipam.AttributeType] != tunnelType { + return fmt.Errorf("Unexpected ipam type attribute %s, expected %s", allocAttr.ActiveOwnerAttrs[ipam.AttributeType], tunnelType) } return nil @@ -289,11 +288,11 @@ var _ = Describe("FV tests", func() { Expect(newNode.Spec.BGP.IPv4IPIPTunnelAddr).To(Equal("172.16.0.1")) // Assert that the IPAM allocation has been updated to include a handle and attributes. - attrs, handle, err := c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: gnet.ParseIP("172.16.0.1")}) + allocAttr, err := c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: gnet.ParseIP("172.16.0.1")}) Expect(err).NotTo(HaveOccurred()) - Expect(len(attrs)).To(Equal(2)) - Expect(handle).NotTo(BeNil()) - Expect(*handle).To(Equal("ipip-tunnel-addr-my-test-node")) + Expect(len(allocAttr.ActiveOwnerAttrs)).To(Equal(2)) + Expect(allocAttr.HandleID).NotTo(BeNil()) + Expect(*allocAttr.HandleID).To(Equal("ipip-tunnel-addr-my-test-node")) }) It("should not claim an address that has a handle but no attributes", func() { @@ -337,10 +336,10 @@ var _ = Describe("FV tests", func() { Expect(err).NotTo(HaveOccurred()) // Assert that the IPAM allocation for the original address is still intact. - _, handle, err := c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: gnet.ParseIP("172.16.0.1")}) + allocAttr, err := c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: gnet.ParseIP("172.16.0.1")}) Expect(err).NotTo(HaveOccurred()) - Expect(handle).NotTo(BeNil()) - Expect(*handle).To(Equal("some-wep-handle")) + Expect(allocAttr.HandleID).NotTo(BeNil()) + Expect(*allocAttr.HandleID).To(Equal("some-wep-handle")) }) It("should release old IPAM addresses if they exist and the node has none", func() { @@ -380,7 +379,7 @@ var _ = Describe("FV tests", func() { Expect(err).NotTo(HaveOccurred()) // Assert that the IPAM allocation for the original leaked address is gone. - _, _, err = c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: gnet.ParseIP("172.16.0.1")}) + _, err = c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: gnet.ParseIP("172.16.0.1")}) Expect(err).To(HaveOccurred()) // Assert that exactly one address exists for the node and that it matches the node. @@ -427,7 +426,7 @@ var _ = Describe("FV tests", func() { Expect(err).NotTo(HaveOccurred()) // Assert that the IPAM allocation for the original leaked address is gone. - _, _, err = c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: gnet.ParseIP("172.16.0.1")}) + _, err = c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: gnet.ParseIP("172.16.0.1")}) Expect(err).To(HaveOccurred()) // Assert that exactly one address exists for the node and that it matches the node. @@ -439,7 +438,6 @@ var _ = Describe("FV tests", func() { }) var _ = Describe("ensureHostTunnelAddress", func() { - ctx := context.Background() cfg, _ := apiconfig.LoadClientConfigFromEnvironment() @@ -571,7 +569,7 @@ var _ = Describe("ensureHostTunnelAddress", func() { // Check old address // Verify 172.16.10.10 has been released. - _, _, err = c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: gnet.ParseIP(expectedAddr1)}) + _, err = c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: gnet.ParseIP(expectedAddr1)}) Expect(err).To(BeAssignableToTypeOf(cerrors.ErrorResourceDoesNotExist{})) // Check new address has been assigned in pool1. @@ -612,9 +610,9 @@ var _ = Describe("ensureHostTunnelAddress", func() { // Check old address. // Verify 172.16.10.10 has not been touched. - attr, _, err := c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: gnet.ParseIP(expectedAddr1)}) + allocAttr, err := c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: gnet.ParseIP(expectedAddr1)}) Expect(err).NotTo(HaveOccurred()) - Expect(attr).To(Equal(wepAttr)) + Expect(allocAttr.ActiveOwnerAttrs).To(Equal(wepAttr)) // Check new address has been assigned. expectTunnelAddressForNode(c, node, tunnelType, expectedAddr2) @@ -667,7 +665,7 @@ var _ = Describe("ensureHostTunnelAddress", func() { Expect(err).NotTo(HaveOccurred()) // Verify 172.16.0.1 is not properly assigned to tunnel address. - _, _, err = c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: gnet.ParseIP(expectedAddr2)}) + _, err = c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: gnet.ParseIP(expectedAddr2)}) Expect(err).To(BeAssignableToTypeOf(cerrors.ErrorResourceDoesNotExist{})) _, ipnet, _ := net.ParseCIDR(cidr2) @@ -714,9 +712,9 @@ var _ = Describe("ensureHostTunnelAddress", func() { expectTunnelAddressForNode(c, node, tunnelType, expectedAddr2) // Verify 172.16.0.0 has not been released. - attr, _, err := c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: gnet.ParseIP(expectedAddr1)}) + allocAttr, err := c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: gnet.ParseIP(expectedAddr1)}) Expect(err).NotTo(HaveOccurred()) - Expect(attr).To(Equal(wepAttr)) + Expect(allocAttr.ActiveOwnerAttrs).To(Equal(wepAttr)) }, Entry("IPIP", ipam.AttributeTypeIPIP), Entry("VXLAN", ipam.AttributeTypeVXLAN), @@ -757,7 +755,6 @@ var _ = Describe("ensureHostTunnelAddress", func() { }) var _ = Describe("removeHostTunnelAddress", func() { - ctx := context.Background() cfg, _ := apiconfig.LoadClientConfigFromEnvironment() @@ -849,7 +846,7 @@ var _ = Describe("removeHostTunnelAddress", func() { err = removeHostTunnelAddr(ctx, c, node, tunnelType) Expect(err).NotTo(HaveOccurred()) // Assert that the IPAM allocation is gone. - _, _, err = c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: gnet.ParseIP(allocationAddr)}) + _, err = c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: gnet.ParseIP(allocationAddr)}) Expect(err).To(HaveOccurred()) }, Entry("IPIP", ipam.AttributeTypeIPIP), @@ -882,7 +879,7 @@ var _ = Describe("removeHostTunnelAddress", func() { Expect(err).NotTo(HaveOccurred()) // Assert that the IPAM allocation is gone. - _, _, err = c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: gnet.ParseIP(allocationAddr)}) + _, err = c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: gnet.ParseIP(allocationAddr)}) Expect(err).To(HaveOccurred()) }, Entry("IPIP", ipam.AttributeTypeIPIP), @@ -917,7 +914,7 @@ var _ = Describe("removeHostTunnelAddress", func() { Expect(err).NotTo(HaveOccurred()) // Assert that the IPAM allocation is gone. - _, _, err = c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: gnet.ParseIP(allocationAddr)}) + _, err = c.IPAM().GetAssignmentAttributes(ctx, net.IP{IP: gnet.ParseIP(allocationAddr)}) Expect(err).NotTo(HaveOccurred()) }, Entry("IPIP", ipam.AttributeTypeIPIP), @@ -929,7 +926,6 @@ var _ = Describe("removeHostTunnelAddress", func() { }) var _ = Describe("Running as daemon", func() { - ctx := context.Background() cfg, _ := apiconfig.LoadClientConfigFromEnvironment() @@ -1032,11 +1028,10 @@ var _ = Describe("Running as daemon", func() { }) var _ = Describe("determineEnabledPoolCIDRs", func() { - Context("IPIP tests", func() { It("should match ip-pool-1 but not ip-pool-2", func() { // Mock out the node and ip pools - n := libapi.Node{ObjectMeta: metav1.ObjectMeta{Name: "bee-node", Labels: map[string]string{"foo": "bar"}}} + n := internalapi.Node{ObjectMeta: metav1.ObjectMeta{Name: "bee-node", Labels: map[string]string{"foo": "bar"}}} pl := api.IPPoolList{ Items: []api.IPPool{ { @@ -1055,7 +1050,9 @@ var _ = Describe("determineEnabledPoolCIDRs", func() { NodeSelector: `foo != "bar"`, IPIPMode: api.IPIPModeAlways, }, - }}} + }, + }, + } // Execute and test assertions. cidrs := determineEnabledPoolCIDRs(n, pl, felixconfig.New(), ipam.AttributeTypeIPIP) @@ -1067,7 +1064,7 @@ var _ = Describe("determineEnabledPoolCIDRs", func() { It("should ignore IP pools that do not allow tunnel IPs", func() { // Mock out the node and ip pools - n := libapi.Node{ObjectMeta: metav1.ObjectMeta{Name: "bee-node", Labels: map[string]string{"foo": "bar"}}} + n := internalapi.Node{ObjectMeta: metav1.ObjectMeta{Name: "bee-node", Labels: map[string]string{"foo": "bar"}}} pl := api.IPPoolList{ Items: []api.IPPool{ { @@ -1111,7 +1108,7 @@ var _ = Describe("determineEnabledPoolCIDRs", func() { Context("IPv4 VXLAN tests", func() { It("should match ip-pool-1 but not ip-pool-2", func() { // Mock out the node and ip pools - n := libapi.Node{ObjectMeta: metav1.ObjectMeta{Name: "bee-node", Labels: map[string]string{"foo": "bar"}}} + n := internalapi.Node{ObjectMeta: metav1.ObjectMeta{Name: "bee-node", Labels: map[string]string{"foo": "bar"}}} pl := api.IPPoolList{ Items: []api.IPPool{ { @@ -1130,7 +1127,9 @@ var _ = Describe("determineEnabledPoolCIDRs", func() { NodeSelector: `foo != "bar"`, VXLANMode: api.VXLANModeAlways, }, - }}} + }, + }, + } // Execute and test assertions. cidrs := determineEnabledPoolCIDRs(n, pl, felixconfig.New(), ipam.AttributeTypeVXLAN) @@ -1141,7 +1140,7 @@ var _ = Describe("determineEnabledPoolCIDRs", func() { }) It("should match ip-pool-1 but not ip-pool-2 for VXLANMode CrossSubnet", func() { // Mock out the node and ip pools - n := libapi.Node{ObjectMeta: metav1.ObjectMeta{Name: "bee-node", Labels: map[string]string{"foo": "bar"}}} + n := internalapi.Node{ObjectMeta: metav1.ObjectMeta{Name: "bee-node", Labels: map[string]string{"foo": "bar"}}} pl := api.IPPoolList{ Items: []api.IPPool{ { @@ -1160,7 +1159,9 @@ var _ = Describe("determineEnabledPoolCIDRs", func() { NodeSelector: `foo != "bar"`, VXLANMode: api.VXLANModeCrossSubnet, }, - }}} + }, + }, + } // Execute and test assertions. cidrs := determineEnabledPoolCIDRs(n, pl, felixconfig.New(), ipam.AttributeTypeVXLAN) @@ -1174,7 +1175,7 @@ var _ = Describe("determineEnabledPoolCIDRs", func() { Context("IPv6 VXLAN tests", func() { It("should match ip-pool-1 but not ip-pool-2", func() { // Mock out the node and ip pools - n := libapi.Node{ObjectMeta: metav1.ObjectMeta{Name: "bee-node", Labels: map[string]string{"foo": "bar"}}} + n := internalapi.Node{ObjectMeta: metav1.ObjectMeta{Name: "bee-node", Labels: map[string]string{"foo": "bar"}}} pl := api.IPPoolList{ Items: []api.IPPool{ { @@ -1193,7 +1194,9 @@ var _ = Describe("determineEnabledPoolCIDRs", func() { NodeSelector: `foo != "bar"`, VXLANMode: api.VXLANModeAlways, }, - }}} + }, + }, + } // Execute and test assertions. cidrs := determineEnabledPoolCIDRs(n, pl, felixconfig.New(), ipam.AttributeTypeVXLANV6) @@ -1204,7 +1207,7 @@ var _ = Describe("determineEnabledPoolCIDRs", func() { }) It("should match ip-pool-1 but not ip-pool-2 for VXLANMode CrossSubnet", func() { // Mock out the node and ip pools - n := libapi.Node{ObjectMeta: metav1.ObjectMeta{Name: "bee-node", Labels: map[string]string{"foo": "bar"}}} + n := internalapi.Node{ObjectMeta: metav1.ObjectMeta{Name: "bee-node", Labels: map[string]string{"foo": "bar"}}} pl := api.IPPoolList{ Items: []api.IPPool{ { @@ -1223,7 +1226,9 @@ var _ = Describe("determineEnabledPoolCIDRs", func() { NodeSelector: `foo != "bar"`, VXLANMode: api.VXLANModeCrossSubnet, }, - }}} + }, + }, + } // Execute and test assertions. cidrs := determineEnabledPoolCIDRs(n, pl, felixconfig.New(), ipam.AttributeTypeVXLANV6) @@ -1237,7 +1242,7 @@ var _ = Describe("determineEnabledPoolCIDRs", func() { Context("Dual stack VXLAN tests", func() { It("should match both IPv4 and IPv6 pools", func() { // Mock out the node and ip pools - n := libapi.Node{ObjectMeta: metav1.ObjectMeta{Name: "bee-node", Labels: map[string]string{"foo": "bar"}}} + n := internalapi.Node{ObjectMeta: metav1.ObjectMeta{Name: "bee-node", Labels: map[string]string{"foo": "bar"}}} pl := api.IPPoolList{ Items: []api.IPPool{ { @@ -1257,7 +1262,8 @@ var _ = Describe("determineEnabledPoolCIDRs", func() { VXLANMode: api.VXLANModeAlways, }, }, - }} + }, + } // Execute and test assertions. _, cidrV4, _ := net.ParseCIDR("172.0.0.1/9") @@ -1274,7 +1280,7 @@ var _ = Describe("determineEnabledPoolCIDRs", func() { It("should match both IPv4 and IPv6 pools for VXLANMode CrossSubnet", func() { // Mock out the node and ip pools - n := libapi.Node{ObjectMeta: metav1.ObjectMeta{Name: "bee-node", Labels: map[string]string{"foo": "bar"}}} + n := internalapi.Node{ObjectMeta: metav1.ObjectMeta{Name: "bee-node", Labels: map[string]string{"foo": "bar"}}} pl := api.IPPoolList{ Items: []api.IPPool{ { @@ -1294,7 +1300,8 @@ var _ = Describe("determineEnabledPoolCIDRs", func() { VXLANMode: api.VXLANModeCrossSubnet, }, }, - }} + }, + } // Execute and test assertions. _, cidrV4, _ := net.ParseCIDR("172.0.0.1/9") @@ -1313,9 +1320,9 @@ var _ = Describe("determineEnabledPoolCIDRs", func() { Context("IPv4 Wireguard tests", func() { It("node has public key - should match ip-pool-1 but not ip-pool-2", func() { // Mock out the node and ip pools - n := libapi.Node{ + n := internalapi.Node{ ObjectMeta: metav1.ObjectMeta{Name: "bee-node", Labels: map[string]string{"foo": "bar"}}, - Status: libapi.NodeStatus{WireguardPublicKey: "abcde"}, + Status: internalapi.NodeStatus{WireguardPublicKey: "abcde"}, } pl := api.IPPoolList{ Items: []api.IPPool{ @@ -1333,7 +1340,9 @@ var _ = Describe("determineEnabledPoolCIDRs", func() { CIDR: "172.128.0.0/9", NodeSelector: `foo != "bar"`, }, - }}} + }, + }, + } // Execute and test assertions. cidrs := determineEnabledPoolCIDRs(n, pl, felixconfig.New(), ipam.AttributeTypeWireguard) @@ -1344,7 +1353,7 @@ var _ = Describe("determineEnabledPoolCIDRs", func() { }) It("node has no public key - should match no pools", func() { // Mock out the node and ip pools - n := libapi.Node{ObjectMeta: metav1.ObjectMeta{Name: "bee-node", Labels: map[string]string{"foo": "bar"}}} + n := internalapi.Node{ObjectMeta: metav1.ObjectMeta{Name: "bee-node", Labels: map[string]string{"foo": "bar"}}} pl := api.IPPoolList{ Items: []api.IPPool{ { @@ -1361,7 +1370,9 @@ var _ = Describe("determineEnabledPoolCIDRs", func() { CIDR: "172.128.0.0/9", NodeSelector: `foo != "bar"`, }, - }}} + }, + }, + } // Execute and test assertions. cidrs := determineEnabledPoolCIDRs(n, pl, felixconfig.New(), ipam.AttributeTypeWireguard) @@ -1372,9 +1383,9 @@ var _ = Describe("determineEnabledPoolCIDRs", func() { Context("IPv6 Wireguard tests", func() { It("node has public key - should match ip-pool-1 but not ip-pool-2", func() { // Mock out the node and ip pools - n := libapi.Node{ + n := internalapi.Node{ ObjectMeta: metav1.ObjectMeta{Name: "bee-node", Labels: map[string]string{"foo": "bar"}}, - Status: libapi.NodeStatus{WireguardPublicKeyV6: "abcde"}, + Status: internalapi.NodeStatus{WireguardPublicKeyV6: "abcde"}, } pl := api.IPPoolList{ Items: []api.IPPool{ @@ -1392,7 +1403,9 @@ var _ = Describe("determineEnabledPoolCIDRs", func() { CIDR: "2001:db8:00:ff::/64", NodeSelector: `foo != "bar"`, }, - }}} + }, + }, + } // Execute and test assertions. cidrs := determineEnabledPoolCIDRs(n, pl, felixconfig.New(), ipam.AttributeTypeWireguardV6) @@ -1403,7 +1416,7 @@ var _ = Describe("determineEnabledPoolCIDRs", func() { }) It("node has no public key - should match no pools", func() { // Mock out the node and ip pools - n := libapi.Node{ObjectMeta: metav1.ObjectMeta{Name: "bee-node", Labels: map[string]string{"foo": "bar"}}} + n := internalapi.Node{ObjectMeta: metav1.ObjectMeta{Name: "bee-node", Labels: map[string]string{"foo": "bar"}}} pl := api.IPPoolList{ Items: []api.IPPool{ { @@ -1420,7 +1433,9 @@ var _ = Describe("determineEnabledPoolCIDRs", func() { CIDR: "2001:db8:00:ff::/64", NodeSelector: `foo != "bar"`, }, - }}} + }, + }, + } // Execute and test assertions. cidrs := determineEnabledPoolCIDRs(n, pl, felixconfig.New(), ipam.AttributeTypeWireguardV6) @@ -1431,9 +1446,9 @@ var _ = Describe("determineEnabledPoolCIDRs", func() { Context("Dual Stack Wireguard tests", func() { It("node has both IPv4 and IPv6 public keys - should match both pools", func() { // Mock out the node and ip pools - n := libapi.Node{ + n := internalapi.Node{ ObjectMeta: metav1.ObjectMeta{Name: "bee-node", Labels: map[string]string{"foo": "bar"}}, - Status: libapi.NodeStatus{ + Status: internalapi.NodeStatus{ WireguardPublicKey: "abcde", WireguardPublicKeyV6: "fghij", }, @@ -1455,7 +1470,8 @@ var _ = Describe("determineEnabledPoolCIDRs", func() { NodeSelector: `foo == "bar"`, }, }, - }} + }, + } // Execute and test assertions. _, cidrV4, _ := net.ParseCIDR("172.0.0.1/9") @@ -1543,6 +1559,11 @@ func (c shimClient) HostEndpoints() client.HostEndpointInterface { return c.client.HostEndpoints() } +// LiveMigrations returns an interface for managing live migration resources. +func (c shimClient) LiveMigrations() client.LiveMigrationInterface { + return c.client.LiveMigrations() +} + // WorkloadEndpoints returns an interface for managing workload endpoint resources. func (c shimClient) WorkloadEndpoints() client.WorkloadEndpointInterface { return c.client.WorkloadEndpoints() @@ -1589,8 +1610,8 @@ func (c shimClient) CalicoNodeStatus() client.CalicoNodeStatusInterface { } // IPAMConfig returns an interface for managing the IPAMConfig resource. -func (c shimClient) IPAMConfig() client.IPAMConfigInterface { - return c.client.IPAMConfig() +func (c shimClient) IPAMConfiguration() client.IPAMConfigurationInterface { + return c.client.IPAMConfiguration() } // BlockAffinities returns an interface for managing the block affinity resources. diff --git a/node/pkg/allocateip/test_utils.go b/node/pkg/allocateip/test_utils.go index 39d98577cd4..bbf61a96410 100644 --- a/node/pkg/allocateip/test_utils.go +++ b/node/pkg/allocateip/test_utils.go @@ -18,12 +18,12 @@ import ( api "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - libapi "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/net" ) // makeNode creates an api.Node with some BGPSpec info populated. -func makeNode(ipv4 string, ipv6 string) *libapi.Node { +func makeNode(ipv4 string, ipv6 string) *internalapi.Node { ip4, ip4net, _ := net.ParseCIDR(ipv4) ip4net.IP = ip4.IP @@ -37,13 +37,13 @@ func makeNode(ipv4 string, ipv6 string) *libapi.Node { ip6Addr = ip6net.String() } - n := &libapi.Node{ - Spec: libapi.NodeSpec{ - BGP: &libapi.NodeBGPSpec{ + n := &internalapi.Node{ + Spec: internalapi.NodeSpec{ + BGP: &internalapi.NodeBGPSpec{ IPv4Address: ip4net.String(), IPv6Address: ip6Addr, }, - Wireguard: &libapi.NodeWireguardSpec{}, + Wireguard: &internalapi.NodeWireguardSpec{}, }, } return n diff --git a/node/pkg/cni/token_watch.go b/node/pkg/cni/token_watch.go index 700e3b3f40a..e1ff585206e 100644 --- a/node/pkg/cni/token_watch.go +++ b/node/pkg/cni/token_watch.go @@ -215,7 +215,7 @@ func tokenUpdateFromFile() (TokenUpdate, error) { logrus.WithError(err).Error("Failed to decode service account token claims") return TokenUpdate{}, err } - var claimMap map[string]interface{} + var claimMap map[string]any err = json.Unmarshal(decodedClaims, &claimMap) if err != nil { logrus.WithError(err).Error("Failed to unmarshal service account token claims") diff --git a/node/pkg/cni/token_watch_suite_test.go b/node/pkg/cni/token_watch_suite_test.go index 5b815dd0ad3..e9a797737fa 100644 --- a/node/pkg/cni/token_watch_suite_test.go +++ b/node/pkg/cni/token_watch_suite_test.go @@ -3,9 +3,8 @@ package cni_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -15,7 +14,8 @@ func init() { } func TestCommands(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/cnitokenwatch_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "CNITokenWatch Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/cnitokenwatch_suite.xml" + ginkgo.RunSpecs(t, "CNITokenWatch Suite", suiteConfig, reporterConfig) } diff --git a/node/pkg/cni/token_watch_test.go b/node/pkg/cni/token_watch_test.go index a3c1a35623d..75193842884 100644 --- a/node/pkg/cni/token_watch_test.go +++ b/node/pkg/cni/token_watch_test.go @@ -8,7 +8,7 @@ import ( "strings" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -71,7 +71,7 @@ var _ = Describe("FV tests", func() { tokenChan := tr.TokenChan() go tr.Run() defer tr.Stop() - for i := 0; i < iterations; i++ { + for range iterations { var receivedToken cni.TokenUpdate Eventually(tokenChan, 5*time.Second).Should(Receive(&receivedToken)) Expect(receivedToken.Token).NotTo(BeEmpty()) diff --git a/node/pkg/health/health.go b/node/pkg/health/health.go index ec575230a59..2baad4745a6 100644 --- a/node/pkg/health/health.go +++ b/node/pkg/health/health.go @@ -17,6 +17,7 @@ import ( "context" "errors" "fmt" + "net" "net/http" "os" "os/exec" @@ -50,8 +51,8 @@ func init() { felixHost = "localhost" } - felixReadinessEp = "http://" + felixHost + ":" + felixPort + "/readiness" - felixLivenessEp = "http://" + felixHost + ":" + felixPort + "/liveness" + felixReadinessEp = "http://" + net.JoinHostPort(felixHost, felixPort) + "/readiness" + felixLivenessEp = "http://" + net.JoinHostPort(felixHost, felixPort) + "/liveness" } func Run(bird, bird6, felixReady, felixLive, birdLive, bird6Live bool, thresholdTime time.Duration) { diff --git a/node/pkg/lifecycle/startup/autodetection/addr_linux.go b/node/pkg/lifecycle/startup/autodetection/addr_linux.go new file mode 100644 index 00000000000..56208060c75 --- /dev/null +++ b/node/pkg/lifecycle/startup/autodetection/addr_linux.go @@ -0,0 +1,49 @@ +//go:build linux + +// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// +// 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 autodetection + +import ( + "net" + + "github.com/vishvananda/netlink" +) + +// Package-level variable for dependency injection (testing) +var netlinkAddrList = netlink.AddrList + +// getAllInterfaceAddrs retrieves all addresses from all interfaces in one netlink call, +// then groups them by interface index for O(1) lookup. +// This avoids the O(N^2) behavior of calling i.Addrs() for each interface, which +// internally performs a full RTM_GETADDR netlink dump of all addresses on the system +// for each call, then filters to one interface. +func getAllInterfaceAddrs() (map[int][]net.Addr, error) { + nlAddrs, err := netlinkAddrList(nil, netlink.FAMILY_ALL) + if err != nil { + return nil, err + } + + addrsByIndex := make(map[int][]net.Addr) + for _, nlAddr := range nlAddrs { + linkIndex := nlAddr.LinkIndex + netAddr := &net.IPNet{ + IP: nlAddr.IP, + Mask: nlAddr.Mask, + } + addrsByIndex[linkIndex] = append(addrsByIndex[linkIndex], netAddr) + } + + return addrsByIndex, nil +} diff --git a/libcalico-go/lib/upgrade/migrator/doc.go b/node/pkg/lifecycle/startup/autodetection/addr_windows.go similarity index 59% rename from libcalico-go/lib/upgrade/migrator/doc.go rename to node/pkg/lifecycle/startup/autodetection/addr_windows.go index 025828cb4b0..197759adf78 100644 --- a/libcalico-go/lib/upgrade/migrator/doc.go +++ b/node/pkg/lifecycle/startup/autodetection/addr_windows.go @@ -1,15 +1,26 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. +//go:build windows +// Copyright (c) 2025 Tigera, Inc. All rights reserved. +// // 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 +// 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 autodetection + +import ( + "net" +) -package migrator +// getAllInterfaceAddrs returns nil on Windows to signal fallback to i.Addrs(). +// netlink is Linux-only. +func getAllInterfaceAddrs() (map[int][]net.Addr, error) { + return nil, nil +} diff --git a/node/pkg/lifecycle/startup/autodetection/autodetection_methods.go b/node/pkg/lifecycle/startup/autodetection/autodetection_methods.go index 5f879638ba0..6930996a5b2 100644 --- a/node/pkg/lifecycle/startup/autodetection/autodetection_methods.go +++ b/node/pkg/lifecycle/startup/autodetection/autodetection_methods.go @@ -42,15 +42,15 @@ func AutoDetectCIDR(method string, version int, k8sNode *v1.Node, getInterfaces // Autodetect the IP by enumerating all interfaces (excluding // known internal interfaces). return autoDetectCIDRFirstFound(version) - } else if strings.HasPrefix(method, AUTODETECTION_METHOD_INTERFACE) { + } else if after, ok := strings.CutPrefix(method, AUTODETECTION_METHOD_INTERFACE); ok { // Autodetect the IP from the specified interface. - ifStr := strings.TrimPrefix(method, AUTODETECTION_METHOD_INTERFACE) + ifStr := after // Regexes are passed in as a string separated by "," ifRegexes := regexp.MustCompile(`\s*,\s*`).Split(ifStr, -1) return autoDetectCIDRByInterface(ifRegexes, version) - } else if strings.HasPrefix(method, AUTODETECTION_METHOD_CIDR) { + } else if after, ok := strings.CutPrefix(method, AUTODETECTION_METHOD_CIDR); ok { // Autodetect the IP by filtering interface by its address. - cidrStr := strings.TrimPrefix(method, AUTODETECTION_METHOD_CIDR) + cidrStr := after // CIDRs are passed in as a string separated by "," matches := []cnet.IPNet{} for _, r := range regexp.MustCompile(`\s*,\s*`).Split(cidrStr, -1) { @@ -62,15 +62,15 @@ func AutoDetectCIDR(method string, version int, k8sNode *v1.Node, getInterfaces matches = append(matches, *cidr) } return autoDetectCIDRByCIDR(matches, version) - } else if strings.HasPrefix(method, AUTODETECTION_METHOD_CAN_REACH) { + } else if after, ok := strings.CutPrefix(method, AUTODETECTION_METHOD_CAN_REACH); ok { // Autodetect the IP by connecting a UDP socket to a supplied address. - destStr := strings.TrimPrefix(method, AUTODETECTION_METHOD_CAN_REACH) + destStr := after return autoDetectCIDRByReach(destStr, version) - } else if strings.HasPrefix(method, AUTODETECTION_METHOD_SKIP_INTERFACE) { + } else if after, ok := strings.CutPrefix(method, AUTODETECTION_METHOD_SKIP_INTERFACE); ok { // Autodetect the Ip by enumerating all interfaces (excluding // known internal interfaces and any interfaces whose name // matches the given regexes). - ifStr := strings.TrimPrefix(method, AUTODETECTION_METHOD_SKIP_INTERFACE) + ifStr := after // Regexes are passed in as a string separated by "," ifRegexes := regexp.MustCompile(`\s*,\s*`).Split(ifStr, -1) return autoDetectCIDRBySkipInterface(ifRegexes, version) diff --git a/node/pkg/lifecycle/startup/autodetection/autodetection_suite_test.go b/node/pkg/lifecycle/startup/autodetection/autodetection_suite_test.go index a722f203113..711c10bd4d0 100644 --- a/node/pkg/lifecycle/startup/autodetection/autodetection_suite_test.go +++ b/node/pkg/lifecycle/startup/autodetection/autodetection_suite_test.go @@ -3,9 +3,8 @@ package autodetection_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -15,7 +14,8 @@ func init() { } func TestCommands(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../report/autodetection_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Autodetection Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../report/autodetection_suite.xml" + ginkgo.RunSpecs(t, "Autodetection Suite", suiteConfig, reporterConfig) } diff --git a/node/pkg/lifecycle/startup/autodetection/filtered_test.go b/node/pkg/lifecycle/startup/autodetection/filtered_test.go index 8fc8f657620..cd254ecd3b2 100644 --- a/node/pkg/lifecycle/startup/autodetection/filtered_test.go +++ b/node/pkg/lifecycle/startup/autodetection/filtered_test.go @@ -14,7 +14,7 @@ package autodetection_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/net" diff --git a/node/pkg/lifecycle/startup/autodetection/interfaces.go b/node/pkg/lifecycle/startup/autodetection/interfaces.go index 6b38895df7f..957dee80896 100644 --- a/node/pkg/lifecycle/startup/autodetection/interfaces.go +++ b/node/pkg/lifecycle/startup/autodetection/interfaces.go @@ -39,6 +39,13 @@ func GetInterfaces(getSystemInterfaces func() ([]net.Interface, error), includeR return nil, err } + // Single bulk address retrieval for all interfaces (Linux optimization) + addrsByIndex, err := getAllInterfaceAddrs() + if err != nil { + log.WithError(err).Debug("Failed to bulk-fetch addresses, falling back to per-interface queries") + addrsByIndex = nil // Fall back to i.Addrs() in convertInterface + } + var filteredIfaces []Interface var includeRegexp *regexp.Regexp var excludeRegexp *regexp.Regexp @@ -65,7 +72,14 @@ func GetInterfaces(getSystemInterfaces func() ([]net.Interface, error), includeR include := (includeRegexp == nil) || includeRegexp.MatchString(iface.Name) exclude := (excludeRegexp != nil) && excludeRegexp.MatchString(iface.Name) if include && !exclude { - if i, err := convertInterface(&iface, versions); err == nil { + // Look up pre-fetched addresses (or nil for fallback) + var addrs []net.Addr + if addrsByIndex != nil { + addrs = addrsByIndex[iface.Index] + } + + // Convert with pre-fetched or fallback addresses + if i, err := convertInterface(&iface, addrs, versions); err == nil { filteredIfaces = append(filteredIfaces, *i) } } @@ -75,12 +89,19 @@ func GetInterfaces(getSystemInterfaces func() ([]net.Interface, error), includeR // convertInterface converts a net.Interface to our Interface type (which has // converted address types). -func convertInterface(i *net.Interface, versions []int) (*Interface, error) { +// If addrs is non-nil, uses them instead of calling i.Addrs() (optimization). +// If addrs is nil, falls back to i.Addrs() (non-Linux or error case). +func convertInterface(i *net.Interface, addrs []net.Addr, versions []int) (*Interface, error) { log.WithField("Interface", i.Name).Debug("Querying interface addresses") - addrs, err := i.Addrs() - if err != nil { - log.Warnf("Cannot get interface address(es): %v", err) - return nil, err + + var err error + if addrs == nil { + // Fallback to per-interface query + addrs, err = i.Addrs() + if err != nil { + log.Warnf("Cannot get interface address(es): %v", err) + return nil, err + } } iface := &Interface{Name: i.Name} diff --git a/node/pkg/lifecycle/startup/autodetection/interfaces_linux_test.go b/node/pkg/lifecycle/startup/autodetection/interfaces_linux_test.go index 778043b862f..1f660bf0f17 100644 --- a/node/pkg/lifecycle/startup/autodetection/interfaces_linux_test.go +++ b/node/pkg/lifecycle/startup/autodetection/interfaces_linux_test.go @@ -14,10 +14,12 @@ package autodetection import ( + "errors" "net" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/vishvananda/netlink" ) type getInterfacesTestCase struct { @@ -96,3 +98,110 @@ var _ = DescribeTable("GetInterfaces", }, }), ) + +var _ = Describe("getAllInterfaceAddrs", func() { + It("should group addresses by interface index", func() { + originalAddrList := netlinkAddrList + defer func() { netlinkAddrList = originalAddrList }() + + netlinkAddrList = func(link netlink.Link, family int) ([]netlink.Addr, error) { + return []netlink.Addr{ + {LinkIndex: 1, IPNet: &net.IPNet{IP: net.ParseIP("192.168.1.10"), Mask: net.CIDRMask(24, 32)}}, + {LinkIndex: 1, IPNet: &net.IPNet{IP: net.ParseIP("fe80::1"), Mask: net.CIDRMask(64, 128)}}, + {LinkIndex: 2, IPNet: &net.IPNet{IP: net.ParseIP("10.0.0.5"), Mask: net.CIDRMask(8, 32)}}, + }, nil + } + + addrsByIndex, err := getAllInterfaceAddrs() + Expect(err).NotTo(HaveOccurred()) + Expect(addrsByIndex).To(HaveLen(2)) + Expect(addrsByIndex[1]).To(HaveLen(2)) + Expect(addrsByIndex[2]).To(HaveLen(1)) + + ipNet1 := addrsByIndex[1][0].(*net.IPNet) + Expect(ipNet1.IP.String()).To(Equal("192.168.1.10")) + + ipNet2 := addrsByIndex[2][0].(*net.IPNet) + Expect(ipNet2.IP.String()).To(Equal("10.0.0.5")) + }) + + It("should handle netlink errors", func() { + originalAddrList := netlinkAddrList + defer func() { netlinkAddrList = originalAddrList }() + + netlinkAddrList = func(link netlink.Link, family int) ([]netlink.Addr, error) { + return nil, errors.New("netlink error") + } + + _, err := getAllInterfaceAddrs() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("netlink error")) + }) + + It("should handle empty address list", func() { + originalAddrList := netlinkAddrList + defer func() { netlinkAddrList = originalAddrList }() + + netlinkAddrList = func(link netlink.Link, family int) ([]netlink.Addr, error) { + return []netlink.Addr{}, nil + } + + addrsByIndex, err := getAllInterfaceAddrs() + Expect(err).NotTo(HaveOccurred()) + Expect(addrsByIndex).To(HaveLen(0)) + }) +}) + +var _ = Describe("GetInterfaces with netlink optimization", func() { + It("should fall back to i.Addrs() when bulk fetch fails", func() { + originalAddrList := netlinkAddrList + defer func() { netlinkAddrList = originalAddrList }() + + // Force bulk fetch to fail + netlinkAddrList = func(link netlink.Link, family int) ([]netlink.Addr, error) { + return nil, errors.New("simulated netlink failure") + } + + // Should still work via i.Addrs() fallback + getInterfaces := func() ([]net.Interface, error) { + return net.Interfaces() + } + + _, err := GetInterfaces(getInterfaces, nil, DEFAULT_INTERFACES_TO_EXCLUDE, 4) + Expect(err).NotTo(HaveOccurred()) + // Just verify no error occurs - the fallback worked + }) + + It("should use bulk-fetched addresses when available", func() { + originalAddrList := netlinkAddrList + defer func() { netlinkAddrList = originalAddrList }() + + // Mock netlink to return specific addresses + netlinkAddrList = func(link netlink.Link, family int) ([]netlink.Addr, error) { + return []netlink.Addr{ + {LinkIndex: 1, IPNet: &net.IPNet{IP: net.ParseIP("192.168.1.10"), Mask: net.CIDRMask(24, 32)}}, + {LinkIndex: 2, IPNet: &net.IPNet{IP: net.ParseIP("10.0.0.5"), Mask: net.CIDRMask(16, 32)}}, + }, nil + } + + getInterfaces := func() ([]net.Interface, error) { + return []net.Interface{ + {Index: 1, Name: "eth0"}, + {Index: 2, Name: "eth1"}, + }, nil + } + + ifaces, err := GetInterfaces(getInterfaces, nil, nil, 4) + Expect(err).NotTo(HaveOccurred()) + Expect(ifaces).To(HaveLen(2)) + + // Verify addresses were attached correctly (reverse order due to loop) + Expect(ifaces[0].Name).To(Equal("eth1")) + Expect(ifaces[0].Cidrs).To(HaveLen(1)) + Expect(ifaces[0].Cidrs[0].IP.String()).To(Equal("10.0.0.5")) + + Expect(ifaces[1].Name).To(Equal("eth0")) + Expect(ifaces[1].Cidrs).To(HaveLen(1)) + Expect(ifaces[1].Cidrs[0].IP.String()).To(Equal("192.168.1.10")) + }) +}) diff --git a/node/pkg/lifecycle/startup/autodetection/ipv4/poolselector_test.go b/node/pkg/lifecycle/startup/autodetection/ipv4/poolselector_test.go index 4a24b43e2a8..4eb5aac6d9a 100644 --- a/node/pkg/lifecycle/startup/autodetection/ipv4/poolselector_test.go +++ b/node/pkg/lifecycle/startup/autodetection/ipv4/poolselector_test.go @@ -21,8 +21,7 @@ import ( "net" "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/vishvananda/netlink" @@ -35,8 +34,9 @@ func init() { func TestCommands(t *testing.T) { RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../../../report/autodetection_ipv4_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "IPv4 pool selector Suite", []Reporter{junitReporter}) + suiteConfig, reporterConfig := GinkgoConfiguration() + reporterConfig.JUnitReport = "../../../../report/autodetection_ipv4_suite.xml" + RunSpecs(t, "IPv4 pool selector Suite", suiteConfig, reporterConfig) } var _ = Describe("IPv4 pool selector tests", func() { diff --git a/node/pkg/lifecycle/startup/autodetection/reachaddr.go b/node/pkg/lifecycle/startup/autodetection/reachaddr.go index cbd180c31a4..4b3b0b2342f 100644 --- a/node/pkg/lifecycle/startup/autodetection/reachaddr.go +++ b/node/pkg/lifecycle/startup/autodetection/reachaddr.go @@ -30,7 +30,7 @@ func ReachDestination(dest string, version int) (*net.IPNet, error) { // Open a UDP connection to determine which external IP address is // used to access the supplied destination. protocol := fmt.Sprintf("udp%d", version) - address := fmt.Sprintf("[%s]:80", dest) + address := gonet.JoinHostPort(dest, "80") conn, err := gonet.Dial(protocol, address) if err != nil { return nil, err diff --git a/node/pkg/lifecycle/startup/startup.go b/node/pkg/lifecycle/startup/startup.go index 7581dd0f9b1..2d02aba7ab0 100644 --- a/node/pkg/lifecycle/startup/startup.go +++ b/node/pkg/lifecycle/startup/startup.go @@ -34,16 +34,15 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapi "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" cerrors "github.com/projectcalico/calico/libcalico-go/lib/errors" cnet "github.com/projectcalico/calico/libcalico-go/lib/net" "github.com/projectcalico/calico/libcalico-go/lib/options" "github.com/projectcalico/calico/libcalico-go/lib/selector" - "github.com/projectcalico/calico/libcalico-go/lib/upgrade/migrator" - "github.com/projectcalico/calico/libcalico-go/lib/upgrade/migrator/clients" "github.com/projectcalico/calico/libcalico-go/lib/winutils" "github.com/projectcalico/calico/node/pkg/calicoclient" "github.com/projectcalico/calico/node/pkg/health" @@ -124,13 +123,6 @@ func Run(opts ...RunOpt) { log.Info("Skipping datastore connection test") } - if cfg.Spec.DatastoreType == apiconfig.Kubernetes { - if err := ensureKDDMigrated(cfg, cli); err != nil { - log.WithError(err).Errorf("Unable to ensure datastore is migrated.") - utils.Terminate() - } - } - // Make sure that this host's BlockAffinity resources are upgraded to add // labels for efficient lookup. CNI plugin also does this on the first // allocation after upgrade. Doing it here too handles some corner cases, @@ -320,25 +312,27 @@ func MarkNetworkAvailable() error { } config, err := winutils.BuildConfigFromFlags("", os.Getenv("KUBECONFIG")) - if err != nil { - log.WithError(err).Error("Failed to build Kubernetes config") - return err - } - config.Timeout = 2 * time.Second - - // Create the k8s clientset. - clientset, err := kubernetes.NewForConfig(config) - if err != nil { - log.WithError(err).Error("Failed to create clientset") - return err - } + if err == nil { + // Create the k8s clientset. + config.Timeout = 2 * time.Second + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + log.WithError(err).Error("Failed to create clientset") + return err + } - // All done. Set NetworkUnavailable to false if using Calico for networking. - // We do it late in the process to avoid node resource update conflict because setting - // node condition will trigger node-controller updating node taints. - err = utils.SetNodeNetworkUnavailableCondition(*clientset, k8sNodeName, false, 30*time.Second) - if err != nil { - log.WithError(err).Error("Unable to set NetworkUnavailable to False") + // All done. Set NetworkUnavailable to false if using Calico for networking. + // We do it late in the process to avoid node resource update conflict because setting + // node condition will trigger node-controller updating node taints. + err = utils.SetNodeNetworkUnavailableCondition(*clientset, k8sNodeName, false, 30*time.Second) + if err != nil { + log.WithError(err).Error("Unable to set NetworkUnavailable to False") + return err + } + } else if clientcmd.IsEmptyConfig(err) && os.Getenv("DATASTORE_TYPE") != "kubernetes" { + log.Info("Kubernetes configuration not detected; skipping NetworkUnavailable condition update") + } else { + log.WithError(err).Error("Failed to build Kubernetes config") return err } @@ -368,7 +362,7 @@ func getMonitorPollInterval() time.Duration { return interval } -func configureAndCheckIPAddressSubnets(ctx context.Context, cli client.Interface, node *libapi.Node, k8sNode *v1.Node) bool { +func configureAndCheckIPAddressSubnets(ctx context.Context, cli client.Interface, node *internalapi.Node, k8sNode *v1.Node) bool { // If Calico is running in policy only mode we don't need to write BGP related // details to the Node. if os.Getenv("CALICO_NETWORKING_BACKEND") == "none" { @@ -399,7 +393,7 @@ func configureAndCheckIPAddressSubnets(ctx context.Context, cli client.Interface } else { log.Info("No IPv4 or IPv6 addresses configured or detected. Some features may not work properly.") // Bail here setting BGPSpec to nil (if empty) to pass validation. - if reflect.DeepEqual(node.Spec.BGP, &libapi.NodeBGPSpec{}) { + if reflect.DeepEqual(node.Spec.BGP, &internalapi.NodeBGPSpec{}) { node.Spec.BGP = nil } return checkConflicts @@ -436,7 +430,7 @@ func MonitorIPAddressSubnets() { var config *rest.Config var k8sNode *v1.Node var err error - var node *libapi.Node + var node *internalapi.Node // Determine the Kubernetes node name. Default to the Calico node name unless an explicit // value is provided. @@ -473,7 +467,7 @@ func MonitorIPAddressSubnets() { if updated { // Apply the updated node resource. // we try updating the resource up to 3 times, in case of transient issues. - for i := 0; i < 3; i++ { + for range 3 { _, err := CreateOrUpdate(ctx, cli, node) if err == nil { log.Info("Updated node IP addresses") @@ -488,7 +482,7 @@ func MonitorIPAddressSubnets() { // configureNodeRef will attempt to discover the cluster type it is running on, check to ensure we // have not already set it on this Node, and set it if need be. // Returns true if the node object needs to updated. -func configureNodeRef(node *libapi.Node) bool { +func configureNodeRef(node *internalapi.Node) bool { orchestrator := "k8s" nodeRef := "" @@ -497,13 +491,13 @@ func configureNodeRef(node *libapi.Node) bool { return false } - node.Spec.OrchRefs = []libapi.OrchRef{{NodeName: nodeRef, Orchestrator: orchestrator}} + node.Spec.OrchRefs = []internalapi.OrchRef{{NodeName: nodeRef, Orchestrator: orchestrator}} return true } // CreateOrUpdate creates the Node if ResourceVersion is not specified, // or Update if it's specified. -func CreateOrUpdate(ctx context.Context, client client.Interface, node *libapi.Node) (*libapi.Node, error) { +func CreateOrUpdate(ctx context.Context, client client.Interface, node *internalapi.Node) (*internalapi.Node, error) { if node.ResourceVersion != "" { return client.Nodes().Update(ctx, node, options.SetOptions{}) } @@ -511,7 +505,7 @@ func CreateOrUpdate(ctx context.Context, client client.Interface, node *libapi.N return client.Nodes().Create(ctx, node, options.SetOptions{}) } -func clearNodeIPs(ctx context.Context, client client.Interface, node *libapi.Node, clearv4, clearv6 bool) { +func clearNodeIPs(ctx context.Context, client client.Interface, node *internalapi.Node, clearv4, clearv6 bool) { if clearv4 { log.WithField("IP", node.Spec.BGP.IPv4Address).Info("Clearing out-of-date IPv4 address from this node") node.Spec.BGP.IPv4Address = "" @@ -522,7 +516,7 @@ func clearNodeIPs(ctx context.Context, client client.Interface, node *libapi.Nod } // If the BGP spec is empty, then set it to nil. - if node.Spec.BGP != nil && reflect.DeepEqual(*node.Spec.BGP, libapi.NodeBGPSpec{}) { + if node.Spec.BGP != nil && reflect.DeepEqual(*node.Spec.BGP, internalapi.NodeBGPSpec{}) { node.Spec.BGP = nil } @@ -582,7 +576,7 @@ func waitForConnection(ctx context.Context, c client.Interface) { // getNode returns the current node configuration. If this node has not yet // been created, it returns a blank node resource. -func getNode(ctx context.Context, client client.Interface, nodeName string) *libapi.Node { +func getNode(ctx context.Context, client client.Interface, nodeName string) *internalapi.Node { node, err := client.Nodes().Get(ctx, nodeName, options.GetOptions{}) if err != nil { if _, ok := err.(cerrors.ErrorResourceDoesNotExist); !ok { @@ -592,7 +586,7 @@ func getNode(ctx context.Context, client client.Interface, nodeName string) *lib } log.WithField("Name", nodeName).Info("Building new node resource") - node = libapi.NewNode() + node = internalapi.NewNode() node.Name = nodeName } @@ -601,13 +595,13 @@ func getNode(ctx context.Context, client client.Interface, nodeName string) *lib // configureIPsAndSubnets updates the supplied node resource with IP and Subnet // information to use for BGP. This returns true if we detect a change in Node IP address. -func configureIPsAndSubnets(node *libapi.Node, k8sNode *v1.Node, getInterfaces func([]string, []string, ...int) ([]autodetection.Interface, error)) (bool, error) { +func configureIPsAndSubnets(node *internalapi.Node, k8sNode *v1.Node, getInterfaces func([]string, []string, ...int) ([]autodetection.Interface, error)) (bool, error) { // If the node resource currently has no BGP configuration, add an empty // set of configuration as it makes the processing below easier, and we // must end up configuring some BGP fields before we complete. if node.Spec.BGP == nil { log.Info("Initialize BGP data") - node.Spec.BGP = &libapi.NodeBGPSpec{} + node.Spec.BGP = &internalapi.NodeBGPSpec{} } oldIpv4 := node.Spec.BGP.IPv4Address @@ -705,9 +699,9 @@ func configureIPsAndSubnets(node *libapi.Node, k8sNode *v1.Node, getInterfaces f return strings.Compare(i.Name, j.Name) }) - var nodeInterfaces []libapi.NodeInterface + var nodeInterfaces []internalapi.NodeInterface for _, iface := range interfaces { - nodeInterface := libapi.NodeInterface{ + nodeInterface := internalapi.NodeInterface{ Name: iface.Name, } for _, addr := range iface.Cidrs { @@ -856,7 +850,7 @@ func evaluateENVBool(envVar string, defaultValue bool) bool { // configureASNumber configures the Node resource with the AS number specified // in the environment, or is a no-op if not specified. // Returns true if the node object needs to be updated. -func configureASNumber(node *libapi.Node) bool { +func configureASNumber(node *internalapi.Node) bool { // If Calico is running in policy only mode we don't need to write BGP related // details to the Node. if os.Getenv("CALICO_NETWORKING_BACKEND") == "none" { @@ -1138,9 +1132,9 @@ func createIPPool(ctx context.Context, client client.Interface, cidr *cnet.IPNet // checkConflictingNodes checks whether any other nodes have been configured // with the same IP addresses. -func checkConflictingNodes(ctx context.Context, client client.Interface, node *libapi.Node) (v4conflict, v6conflict bool, retErr error) { +func checkConflictingNodes(ctx context.Context, client client.Interface, node *internalapi.Node) (v4conflict, v6conflict bool, retErr error) { // Get the full set of nodes. - var nodes []libapi.Node + var nodes []internalapi.Node if nodeList, err := client.Nodes().List(ctx, options.ListOptions{}); err != nil { log.WithError(err).Errorf("Unable to query node configuration") retErr = err @@ -1219,7 +1213,7 @@ func checkConflictingNodes(ctx context.Context, client client.Interface, node *l // ensureDefaultConfig ensures all of the required default settings are // configured. -func ensureDefaultConfig(ctx context.Context, cfg *apiconfig.CalicoAPIConfig, c client.Interface, node *libapi.Node, osType string, kubeadmConfig, rancherState *v1.ConfigMap) error { +func ensureDefaultConfig(ctx context.Context, cfg *apiconfig.CalicoAPIConfig, c client.Interface, node *internalapi.Node, osType string, kubeadmConfig, rancherState *v1.ConfigMap) error { // Ensure the ClusterInformation is populated. // Get the ClusterType from ENV var. This is set from the manifest. clusterType := os.Getenv("CLUSTER_TYPE") @@ -1356,29 +1350,6 @@ func ensureDefaultConfig(ctx context.Context, cfg *apiconfig.CalicoAPIConfig, c return nil } -// ensureKDDMigrated ensures any data migration needed is done. -func ensureKDDMigrated(cfg *apiconfig.CalicoAPIConfig, cv3 client.Interface) error { - cv1, err := clients.LoadKDDClientV1FromAPIConfigV3(cfg) - if err != nil { - return err - } - m := migrator.New(cv3, cv1, nil) - yes, err := m.ShouldMigrate() - if err != nil { - return err - } else if yes { - log.Infof("Running migration") - if _, err = m.Migrate(); err != nil { - return fmt.Errorf("migration failed: %v", err) - } - log.Infof("Migration successful") - } else { - log.Debugf("Migration is not needed") - } - - return nil -} - // extractKubeadmCIDRs looks through the config map and parses lines starting with 'podSubnet'. func extractKubeadmCIDRs(kubeadmConfig *v1.ConfigMap) (string, string, error) { var v4, v6 string @@ -1401,7 +1372,7 @@ func extractKubeadmCIDRs(kubeadmConfig *v1.ConfigMap) (string, string, error) { if len(line) != 0 { // IPv4 and IPv6 CIDRs will be separated by a comma in a dual stack setup. - for _, cidr := range strings.Split(line[1], ",") { + for cidr := range strings.SplitSeq(line[1], ",") { addr, _, err := net.ParseCIDR(cidr) if err != nil { break diff --git a/node/pkg/lifecycle/startup/startup_suite_test.go b/node/pkg/lifecycle/startup/startup_suite_test.go index 6df5e4e2f82..ede8a85305c 100644 --- a/node/pkg/lifecycle/startup/startup_suite_test.go +++ b/node/pkg/lifecycle/startup/startup_suite_test.go @@ -3,9 +3,8 @@ package startup_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -15,7 +14,8 @@ func init() { } func TestCommands(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/startup_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Startup Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/startup_suite.xml" + ginkgo.RunSpecs(t, "Startup Suite", suiteConfig, reporterConfig) } diff --git a/node/pkg/lifecycle/startup/startup_test.go b/node/pkg/lifecycle/startup/startup_test.go index ed470ca1b3d..779f2686be4 100644 --- a/node/pkg/lifecycle/startup/startup_test.go +++ b/node/pkg/lifecycle/startup/startup_test.go @@ -23,20 +23,21 @@ import ( "sync" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/sirupsen/logrus" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" crclient "sigs.k8s.io/controller-runtime/pkg/client" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend" + "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s" "github.com/projectcalico/calico/libcalico-go/lib/backend/k8s/rawcrdclient" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/ipam/ipamtestutils" @@ -55,7 +56,7 @@ func fakeExitFunction(ec int) { } // makeNode creates an libapi.Node with some BGPSpec info populated. -func makeNode(ipv4 string, ipv6 string) *libapiv3.Node { +func makeNode(ipv4 string, ipv6 string) *internalapi.Node { ip4, ip4net, _ := net.ParseCIDR(ipv4) ip4net.IP = ip4.IP @@ -69,9 +70,9 @@ func makeNode(ipv4 string, ipv6 string) *libapiv3.Node { ip6Addr = ip6net.String() } - n := &libapiv3.Node{ - Spec: libapiv3.NodeSpec{ - BGP: &libapiv3.NodeBGPSpec{ + n := &internalapi.Node{ + Spec: internalapi.NodeSpec{ + BGP: &internalapi.NodeBGPSpec{ IPv4Address: ip4net.String(), IPv6Address: ip6Addr, }, @@ -135,9 +136,9 @@ var _ = DescribeTable("Node IP detection failure cases", c, err := client.New(*cfg) Expect(err).NotTo(HaveOccurred()) - node := libapiv3.Node{} + node := internalapi.Node{} if networkingBackend != "none" && rrCId != "" { - node.Spec.BGP = &libapiv3.NodeBGPSpec{RouteReflectorClusterID: rrCId} + node.Spec.BGP = &internalapi.NodeBGPSpec{RouteReflectorClusterID: rrCId} } updated := configureAndCheckIPAddressSubnets(context.Background(), c, &node, &v1.Node{}) @@ -458,7 +459,7 @@ var _ = Describe("FV tests against a real etcd", func() { Expect(err).NotTo(HaveOccurred()) }) - var n *libapiv3.Node + var n *internalapi.Node By("getting the Node", func() { n, err = c.Nodes().Get(ctx, node.Name, options.GetOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -973,8 +974,8 @@ var _ = Describe("FV tests against a real etcd", func() { }) Describe("Test OrchRef configuration", func() { - DescribeTable("Should configure the OrchRef with the proper env var set", func(envs []EnvItem, expected libapiv3.OrchRef, isEqual bool) { - node := &libapiv3.Node{} + DescribeTable("Should configure the OrchRef with the proper env var set", func(envs []EnvItem, expected internalapi.OrchRef, isEqual bool) { + node := &internalapi.Node{} for _, env := range envs { defer temporarilySetEnv(env.key, env.value)() @@ -990,13 +991,13 @@ var _ = Describe("FV tests against a real etcd", func() { } }, - Entry("valid single k8s env var", []EnvItem{{"CALICO_K8S_NODE_REF", "node1"}}, libapiv3.OrchRef{NodeName: "node1", Orchestrator: "k8s"}, true), + Entry("valid single k8s env var", []EnvItem{{"CALICO_K8S_NODE_REF", "node1"}}, internalapi.OrchRef{NodeName: "node1", Orchestrator: "k8s"}, true), ) It("Should not configure any OrchRefs when no valid env vars are passed", func() { defer temporarilySetEnv("CALICO_UNKNOWN_NODE_REF", "node1")() - node := &libapiv3.Node{} + node := &internalapi.Node{} Expect(configureNodeRef(node)).To(Equal(false)) Expect(node.Spec.OrchRefs).To(HaveLen(0)) @@ -1005,8 +1006,8 @@ var _ = Describe("FV tests against a real etcd", func() { It("Should not set an OrchRef if it is already set", func() { defer temporarilySetEnv("CALICO_K8S_NODE_REF", "node1")() - node := &libapiv3.Node{} - node.Spec.OrchRefs = append(node.Spec.OrchRefs, libapiv3.OrchRef{NodeName: "node1", Orchestrator: "k8s"}) + node := &internalapi.Node{} + node.Spec.OrchRefs = append(node.Spec.OrchRefs, internalapi.OrchRef{NodeName: "node1", Orchestrator: "k8s"}) Expect(configureNodeRef(node)).To(Equal(true)) Expect(node.Spec.OrchRefs).To(HaveLen(1)) @@ -1069,7 +1070,7 @@ var _ = Describe("NetworkUnavailable condition", func() { var _ = Describe("UT for Node IP assignment and conflict checking.", func() { DescribeTable("Test variations on how IPs are detected.", - func(node *libapiv3.Node, items []EnvItem, expected bool) { + func(node *internalapi.Node, items []EnvItem, expected bool) { for _, item := range items { defer temporarilySetEnv(item.key, item.value)() } @@ -1084,12 +1085,12 @@ var _ = Describe("UT for Node IP assignment and conflict checking.", func() { Expect(err).NotTo(HaveOccurred()) }, - Entry("Test with no \"IP\" env var set", &libapiv3.Node{}, []EnvItem{{"IP", ""}}, true), - Entry("Test with \"IP\" env var set to IP", &libapiv3.Node{}, []EnvItem{{"IP", "192.168.1.10/24"}}, true), + Entry("Test with no \"IP\" env var set", &internalapi.Node{}, []EnvItem{{"IP", ""}}, true), + Entry("Test with \"IP\" env var set to IP", &internalapi.Node{}, []EnvItem{{"IP", "192.168.1.10/24"}}, true), Entry("Test with \"IP\" env var set to IP and BGP spec populated with same IP", makeNode("192.168.1.10/24", ""), []EnvItem{{"IP", "192.168.1.10/24"}}, false), Entry("Test with \"IP\" env var set to IP and BGP spec populated with different IP", makeNode("192.168.1.10/24", ""), []EnvItem{{"IP", "192.168.1.11/24"}}, true), - Entry("Test with no \"IP6\" env var set", &libapiv3.Node{}, []EnvItem{{"IP6", ""}}, true), - Entry("Test with \"IP6\" env var set to IP", &libapiv3.Node{}, []EnvItem{{"IP6", "2001:db8:85a3:8d3:1319:8a2e:370:7348/32"}}, true), + Entry("Test with no \"IP6\" env var set", &internalapi.Node{}, []EnvItem{{"IP6", ""}}, true), + Entry("Test with \"IP6\" env var set to IP", &internalapi.Node{}, []EnvItem{{"IP6", "2001:db8:85a3:8d3:1319:8a2e:370:7348/32"}}, true), Entry("Test with \"IP6\" env var set to IP and BGP spec populated with same IP", makeNode("192.168.1.10/24", "2001:db8:85a3:8d3:1319:8a2e:370:7348/32"), []EnvItem{{"IP", "192.168.1.10/24"}, {"IP6", "2001:db8:85a3:8d3:1319:8a2e:370:7348/32"}}, false), Entry("Test with \"IP6\" env var set to IP and BGP spec populated with different IP", makeNode("192.168.1.10/24", "2001:db8:85a3:8d3:1319:8a2e:370:7348/32"), []EnvItem{{"IP", "192.168.1.10/24"}, {"IP6", "2001:db8:85a3:8d3:1319:8a2e:370:7349/32"}}, true), ) @@ -1097,7 +1098,7 @@ var _ = Describe("UT for Node IP assignment and conflict checking.", func() { var _ = Describe("UT for autodetection method k8s-internal-ip", func() { DescribeTable("Test variations on k8s-internal-ip", - func(node *libapiv3.Node, k8sNode *v1.Node, items []EnvItem, expected bool) { + func(node *internalapi.Node, k8sNode *v1.Node, items []EnvItem, expected bool) { for _, item := range items { defer temporarilySetEnv(item.key, item.value)() } @@ -1116,9 +1117,9 @@ var _ = Describe("UT for autodetection method k8s-internal-ip", func() { _ = os.Unsetenv("IP_AUTODETECTION_METHOD") }, - Entry("Test with \"IP\" env = autodetect ,IP_AUTODETECTION_METHOD = k8s-internal-ip. k8snode = nil", &libapiv3.Node{}, nil, []EnvItem{{"IP", "autodetect"}, {"IP_AUTODETECTION_METHOD", "kubernetes-internal-ip"}}, false), - Entry("Test with \"IP\" env = autodetect ,IP_AUTODETECTION_METHOD = k8s-internal-ip. k8snode = valid addr", &libapiv3.Node{}, makeK8sNode("192.168.1.10", "2001:db8:85a3:8d3:1319:8a2e:370:7348"), []EnvItem{{"IP", "autodetect"}, {"IP_AUTODETECTION_METHOD", "kubernetes-internal-ip"}}, true), - Entry("Test with \"IP\" env = autodetect ,IP_AUTODETECTION_METHOD = k8s-internal-ip. k8snode = addr mismatch", &libapiv3.Node{}, makeK8sNode("192.168.1.1", "2001:db8:85a3:8d3:1319:8a2e:370:7349"), []EnvItem{{"IP", "autodetect"}, {"IP_AUTODETECTION_METHOD", "kubernetes-internal-ip"}}, false), + Entry("Test with \"IP\" env = autodetect ,IP_AUTODETECTION_METHOD = k8s-internal-ip. k8snode = nil", &internalapi.Node{}, nil, []EnvItem{{"IP", "autodetect"}, {"IP_AUTODETECTION_METHOD", "kubernetes-internal-ip"}}, false), + Entry("Test with \"IP\" env = autodetect ,IP_AUTODETECTION_METHOD = k8s-internal-ip. k8snode = valid addr", &internalapi.Node{}, makeK8sNode("192.168.1.10", "2001:db8:85a3:8d3:1319:8a2e:370:7348"), []EnvItem{{"IP", "autodetect"}, {"IP_AUTODETECTION_METHOD", "kubernetes-internal-ip"}}, true), + Entry("Test with \"IP\" env = autodetect ,IP_AUTODETECTION_METHOD = k8s-internal-ip. k8snode = addr mismatch", &internalapi.Node{}, makeK8sNode("192.168.1.1", "2001:db8:85a3:8d3:1319:8a2e:370:7349"), []EnvItem{{"IP", "autodetect"}, {"IP_AUTODETECTION_METHOD", "kubernetes-internal-ip"}}, false), ) }) @@ -1144,7 +1145,7 @@ var _ = Describe("UT for CIDR returned by IP address autodetection k8s-internal- var _ = Describe("UT for node interface autodetection", func() { DescribeTable("Test interface is correctly set on Node", - func(node *libapiv3.Node, k8sNode *v1.Node, items []EnvItem) { + func(node *internalapi.Node, k8sNode *v1.Node, items []EnvItem) { for _, item := range items { defer temporarilySetEnv(item.key, item.value)() } @@ -1158,7 +1159,7 @@ var _ = Describe("UT for node interface autodetection", func() { _, err := configureIPsAndSubnets(node, k8sNode, mockGetInterface) Expect(err).ToNot(HaveOccurred()) - Expect(node.Spec.Interfaces).To(Equal([]libapiv3.NodeInterface{{ + Expect(node.Spec.Interfaces).To(Equal([]internalapi.NodeInterface{{ Name: "eth1", Addresses: []string{"192.168.1.10", "2001:db8:85a3:8d3:1319:8a2e:370:7348"}, }})) @@ -1166,7 +1167,7 @@ var _ = Describe("UT for node interface autodetection", func() { _ = os.Unsetenv("IP") }, - Entry("Test with \"IP\" env = autodetect. k8snode = nil", &libapiv3.Node{}, nil, []EnvItem{{"IP", "autodetect"}}), + Entry("Test with \"IP\" env = autodetect. k8snode = nil", &internalapi.Node{}, nil, []EnvItem{{"IP", "autodetect"}}), ) }) @@ -1188,7 +1189,12 @@ var _ = Describe("FV tests against K8s API server.", func() { Fail(fmt.Sprintf("Could not create K8s client: %v", err)) } - crdClient, err = rawcrdclient.New(kcfg) + // Determine if we're running in v3 CRD mode. + cfg, err := apiconfig.LoadClientConfig("") + Expect(err).NotTo(HaveOccurred()) + v3CRD := k8s.UsingV3CRDs(&cfg.Spec) + + crdClient, err = rawcrdclient.New(kcfg, v3CRD) Expect(err).NotTo(HaveOccurred()) }) @@ -1207,7 +1213,7 @@ var _ = Describe("FV tests against K8s API server.", func() { Expect(err).NotTo(HaveOccurred()) // Create some Nodes using K8s client, Calico client does not support Node creation for KDD. - for i := 0; i < numNodes; i++ { + for i := range numNodes { n := &v1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: fmt.Sprintf("racenode%02d", i+1), @@ -1228,7 +1234,7 @@ var _ = Describe("FV tests against K8s API server.", func() { errors := []error{} for _, node := range nodes.Items { wg.Add(1) - go func(n libapiv3.Node) { + go func(n internalapi.Node) { defer wg.Done() err = ensureDefaultConfig(ctx, cfg, c, &n, OSTypeLinux, kubeadmConfig, rancherState) if err != nil { @@ -1261,20 +1267,35 @@ var _ = Describe("FV tests against K8s API server.", func() { Expect(ipamtestutils.CreateUnlabeledBlockAffinity(ctx, crdClient, "upgrade-test-node", "10.11.0.0/26")).To(Succeed()) Run(WithBailOutAfterUpgrade(true)) - // 4) Verify the previously unlabeled BA is now labeled. - list := libapiv3.BlockAffinityList{} - Expect(crdClient.List(ctx, &list)).To(Succeed()) - var found1 *libapiv3.BlockAffinity - for i := range list.Items { - if list.Items[i].Spec.Node == "upgrade-test-node" && list.Items[i].Spec.CIDR == "10.11.0.0/26" { - found1 = &list.Items[i] - break + // 4) Verify the previously unlabeled BA is now labeled. We need to try both API groups. + var labels map[string]string + list := internalapi.BlockAffinityList{} + err := crdClient.List(ctx, &list) + if runtime.IsNotRegisteredError(err) { + // try the other API group. + list := apiv3.BlockAffinityList{} + err = crdClient.List(ctx, &list) + Expect(err).NotTo(HaveOccurred()) + for i := range list.Items { + if list.Items[i].Spec.Node == "upgrade-test-node" && list.Items[i].Spec.CIDR == "10.11.0.0/26" { + labels = list.Items[i].Labels + break + } + } + } else if err != nil { + Expect(err).NotTo(HaveOccurred()) + } else { + for i := range list.Items { + if list.Items[i].Spec.Node == "upgrade-test-node" && list.Items[i].Spec.CIDR == "10.11.0.0/26" { + labels = list.Items[i].Labels + break + } } } - Expect(found1).NotTo(BeNil()) - Expect(found1.Labels).To(HaveKey(apiv3.LabelAffinityType)) - Expect(found1.Labels).To(HaveKey(apiv3.LabelIPVersion)) - Expect(found1.Labels).To(HaveKey(apiv3.LabelHostnameHash)) + Expect(labels).NotTo(BeNil()) + Expect(labels).To(HaveKey(apiv3.LabelAffinityType)) + Expect(labels).To(HaveKey(apiv3.LabelIPVersion)) + Expect(labels).To(HaveKey(apiv3.LabelHostnameHash)) }) }) @@ -1310,7 +1331,7 @@ var _ = Describe("UT for node name determination", func() { var _ = Describe("UT for GenerateIPv6ULAPrefix", func() { It("should generate a different address each time", func() { seen := set.New[string]() - for i := 0; i < 100; i++ { + for range 100 { newAddr, err := GenerateIPv6ULAPrefix() Expect(err).NotTo(HaveOccurred()) Expect(seen.Contains(newAddr)).To(BeFalse()) diff --git a/node/pkg/lifecycle/startup/startup_windows.go b/node/pkg/lifecycle/startup/startup_windows.go index 27f8c2706bb..fc7a523bbad 100644 --- a/node/pkg/lifecycle/startup/startup_windows.go +++ b/node/pkg/lifecycle/startup/startup_windows.go @@ -25,7 +25,7 @@ import ( "github.com/sirupsen/logrus" "github.com/projectcalico/calico/cni-plugin/pkg/dataplane/windows" - api "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" client "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/projectcalico/calico/libcalico-go/lib/ipam" ) @@ -44,7 +44,7 @@ func ipv6Supported() bool { } // configureCloudOrchRef does not do anything for windows -func configureCloudOrchRef(node *api.Node) { +func configureCloudOrchRef(node *internalapi.Node) { logrus.Debug("configureCloudOrchRef called on Windows; nothing to do.") } diff --git a/node/pkg/lifecycle/utils/utils.go b/node/pkg/lifecycle/utils/utils.go index 90a63ac00e7..4477e2c5b7a 100644 --- a/node/pkg/lifecycle/utils/utils.go +++ b/node/pkg/lifecycle/utils/utils.go @@ -215,7 +215,7 @@ func SetNodeNetworkUnavailableCondition( if err != nil { return err } - patch := []byte(fmt.Sprintf(`{"status":{"conditions":%s}}`, raw)) + patch := fmt.Appendf(nil, `{"status":{"conditions":%s}}`, raw) to := time.After(timeout) for { select { diff --git a/node/pkg/nodeinit/calico-init_linux.go b/node/pkg/nodeinit/calico-init_linux.go index 1d1f4548963..43f31b37e52 100644 --- a/node/pkg/nodeinit/calico-init_linux.go +++ b/node/pkg/nodeinit/calico-init_linux.go @@ -28,7 +28,7 @@ import ( "syscall" "time" - "github.com/google/gopacket/layers" + "github.com/gopacket/gopacket/layers" "github.com/sirupsen/logrus" "github.com/projectcalico/calico/felix/bpf/bpfdefs" @@ -126,7 +126,7 @@ func initBPFNetwork(serviceAddr, endpointAddrs string) (*bpfmap.Maps, error) { logrus.Infof("Included kubernetes service (%s) and endpoints (%s) in the Nat Maps.", serviceAddr, endpointAddrs) // Activate the connect-time load balancer. - err = bpfnat.InstallConnectTimeLoadBalancer(hasIPv4, hasIPv6, "", "debug", 60*time.Second, true, bpfMaps.CommonMaps.CTLBProgramsMap) + err = bpfnat.InstallConnectTimeLoadBalancer(hasIPv4, hasIPv6, "", "debug", 60*time.Second, true, bpfMaps.CommonMaps.CTLBProgramsMaps) if err != nil { logrus.WithError(err).Error("Failed to attach connect-time load balancer.") return nil, err diff --git a/node/pkg/status/nodestatus.go b/node/pkg/status/nodestatus.go index 7f91e2e0c65..a4f538ab27f 100644 --- a/node/pkg/status/nodestatus.go +++ b/node/pkg/status/nodestatus.go @@ -127,7 +127,7 @@ type NodeStatusReporter struct { client client.Interface // Channel for getting updates and status updates from syncer. - syncerC chan interface{} + syncerC chan any // cache for pending updates. // No lock needed for updating the cache since it is updated in main loop only. @@ -156,7 +156,7 @@ func NewNodeStatusReporter(node string, nodename: node, cfg: cfg, client: client, - syncerC: make(chan interface{}, 1), + syncerC: make(chan any, 1), reporter: make(map[string]*reporter), pendingUpdates: make(map[string]*apiv3.CalicoNodeStatus), populators: populators, diff --git a/node/pkg/status/nodestatus_suite_test.go b/node/pkg/status/nodestatus_suite_test.go index ecfe1913ca8..5990fd88557 100644 --- a/node/pkg/status/nodestatus_suite_test.go +++ b/node/pkg/status/nodestatus_suite_test.go @@ -3,9 +3,8 @@ package status_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -15,7 +14,8 @@ func init() { } func TestCommands(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/caliconodestatus_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "CalicoNodeStatus Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/caliconodestatus_suite.xml" + ginkgo.RunSpecs(t, "CalicoNodeStatus Suite", suiteConfig, reporterConfig) } diff --git a/node/pkg/status/nodestatus_test.go b/node/pkg/status/nodestatus_test.go index 0d01c7410f2..ce5a335cb53 100644 --- a/node/pkg/status/nodestatus_test.go +++ b/node/pkg/status/nodestatus_test.go @@ -20,7 +20,7 @@ import ( "os" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" log "github.com/sirupsen/logrus" @@ -52,7 +52,8 @@ var _ = Describe("Node status FV tests", func() { cfg.Spec = apiconfig.CalicoAPIConfigSpec{ DatastoreType: apiconfig.Kubernetes, KubeConfig: apiconfig.KubeConfig{ - Kubeconfig: os.Getenv("KUBECONFIG"), + Kubeconfig: os.Getenv("KUBECONFIG"), + CalicoAPIGroup: os.Getenv("CALICO_API_GROUP"), }, } @@ -143,7 +144,6 @@ var _ = Describe("Node status FV tests", func() { } Context("Mock bird connections", func() { - BeforeEach(func() { err = be.Clean() Expect(err).ToNot(HaveOccurred()) @@ -279,7 +279,6 @@ var _ = Describe("Node status FV tests", func() { Eventually(func() int { return r.GetNumberOfReporters() }, 2*time.Second, 500*time.Millisecond).Should(Equal(0)) - }) }) diff --git a/node/pkg/status/populators/bird_status.go b/node/pkg/status/populators/bird_status.go index bbaa7886c4b..2f8d7e78e6f 100644 --- a/node/pkg/status/populators/bird_status.go +++ b/node/pkg/status/populators/bird_status.go @@ -66,14 +66,14 @@ func (b *birdStatus) unmarshalBIRD(line string) bool { b.ready = true } else if strings.HasPrefix(line, "BIRD v") { b.version = strings.TrimPrefix(line, "BIRD ") - } else if strings.HasPrefix(line, "Router ID is ") { - b.routerID = strings.TrimPrefix(line, "Router ID is ") - } else if strings.HasPrefix(line, "Current server time is ") { - b.serverTime = strings.TrimPrefix(line, "Current server time is ") - } else if strings.HasPrefix(line, "Last reboot on ") { - b.lastBootTime = strings.TrimPrefix(line, "Last reboot on ") - } else if strings.HasPrefix(line, "Last reconfiguration on ") { - b.lastReconfigTime = strings.TrimPrefix(line, "Last reconfiguration on ") + } else if after, ok := strings.CutPrefix(line, "Router ID is "); ok { + b.routerID = after + } else if after, ok := strings.CutPrefix(line, "Current server time is "); ok { + b.serverTime = after + } else if after, ok := strings.CutPrefix(line, "Last reboot on "); ok { + b.lastBootTime = after + } else if after, ok := strings.CutPrefix(line, "Last reconfiguration on "); ok { + b.lastReconfigTime = after } else { return false } diff --git a/node/pkg/status/populators/bird_status_test.go b/node/pkg/status/populators/bird_status_test.go index 0bfea0a20d9..2a12935f82a 100644 --- a/node/pkg/status/populators/bird_status_test.go +++ b/node/pkg/status/populators/bird_status_test.go @@ -19,8 +19,7 @@ import ( "net" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" ) diff --git a/node/pkg/status/populators/peers_test.go b/node/pkg/status/populators/peers_test.go index 66290bd0893..d1a3fa7cda5 100644 --- a/node/pkg/status/populators/peers_test.go +++ b/node/pkg/status/populators/peers_test.go @@ -15,8 +15,7 @@ package populator import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" ) diff --git a/node/pkg/status/populators/populators_suite_test.go b/node/pkg/status/populators/populators_suite_test.go index ba7c58d220f..147d5abdc83 100644 --- a/node/pkg/status/populators/populators_suite_test.go +++ b/node/pkg/status/populators/populators_suite_test.go @@ -3,9 +3,8 @@ package populator import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -15,7 +14,8 @@ func init() { } func TestCommands(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/statuspopulators_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "StatusPopulators Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/statuspopulators_suite.xml" + ginkgo.RunSpecs(t, "StatusPopulators Suite", suiteConfig, reporterConfig) } diff --git a/node/pkg/status/populators/route_test.go b/node/pkg/status/populators/route_test.go index 43de48c51a7..c22cce845d8 100644 --- a/node/pkg/status/populators/route_test.go +++ b/node/pkg/status/populators/route_test.go @@ -15,8 +15,7 @@ package populator import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" ) diff --git a/node/pkg/status/reporter.go b/node/pkg/status/reporter.go index 9a24ce638ea..028ccaf2be7 100644 --- a/node/pkg/status/reporter.go +++ b/node/pkg/status/reporter.go @@ -219,7 +219,7 @@ func (r *reporter) reportStatus() error { var err error var updatedResource *apiv3.CalicoNodeStatus // Update resource - for i := 0; i < 3; i++ { + for range 3 { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() status.Status.LastUpdated = metav1.Time{Time: time.Now()} diff --git a/node/run-uts b/node/run-uts index eb40ef9a02d..c8967389a3b 100755 --- a/node/run-uts +++ b/node/run-uts @@ -1,9 +1,5 @@ #!/bin/bash -# Turn off the annoying ginkgo warning. -# TODO: We should actually upgrade ginkgo! -export ACK_GINKGO_RC=true - if [ -z "$1" ]; then echo "No packages need to be tested" exit 0 @@ -30,7 +26,7 @@ for PKG in "$@"; do HAS_TESTS=$(find ${PKG} -name "*_test.go") if [ ! -z "${HAS_TESTS}" ]; then echo "Running tests for package: ${PKG}"; - ginkgo -r -skipPackage=${SKIP} ${GINKGO_ARGS} ${PKG} || { + ginkgo -r -skip-package=${SKIP} ${GINKGO_ARGS} ${PKG} || { failed_packages+="${PKG} "; ((RC++)) } diff --git a/node/tests/k8st/deploy_resources_on_kind_cluster.sh b/node/tests/k8st/deploy_resources_on_kind_cluster.sh index 90e683ba719..25711061857 100755 --- a/node/tests/k8st/deploy_resources_on_kind_cluster.sh +++ b/node/tests/k8st/deploy_resources_on_kind_cluster.sh @@ -1,14 +1,76 @@ #!/bin/bash -e -# Clean up background jobs on exit. +# Clean up background jobs on exit, and collect diagnostics on failure. set -m function cleanup() { rc=$? + if [ $rc -ne 0 ]; then + collect_diags + fi jobs -p | xargs --no-run-if-empty kill exit $rc } trap 'cleanup' SIGINT SIGHUP SIGTERM EXIT +# collect_diags prints detailed cluster diagnostics on failure. +# It collects tigerastatus, tigera-operator logs, and logs from failing pods. +function collect_diags() { + # Guard against kubectl not being set yet (failure during variable init). + local kctl="${kubectl:-../hack/test/kind/kubectl}" + + echo "" + echo "========================================================================" + echo " DIAGNOSTICS: Collecting cluster state after failure" + echo "========================================================================" + + echo "" + echo "-------- TigeraStatus Resources (YAML) --------" + ${kctl} get tigerastatus -o yaml 2>&1 || true + + echo "" + echo "-------- Tigera Operator Logs --------" + ${kctl} logs -n tigera-operator -l k8s-app=tigera-operator --tail=200 2>&1 || true + + echo "" + echo "-------- All Pod Status --------" + ${kctl} get po -A -o wide 2>&1 || true + + echo "" + echo "-------- Logs from Non-Running / Non-Ready Pods --------" + ${kctl} get po -A --no-headers 2>/dev/null | while read -r ns name ready status rest; do + if [ "$status" != "Running" ] && [ "$status" != "Completed" ] && [ "$status" != "Succeeded" ]; then + echo "" + echo "---- Pod ${ns}/${name} (Status: ${status}) ----" + echo " -- Description --" + ${kctl} describe pod -n "${ns}" "${name}" 2>&1 || true + echo " -- Logs --" + ${kctl} logs -n "${ns}" "${name}" --all-containers --tail=100 2>&1 || true + fi + done + + # Also check for Running pods that aren't fully ready (e.g., 0/1, 1/2). + ${kctl} get po -A --no-headers 2>/dev/null | while read -r ns name ready status rest; do + if [ "$status" = "Running" ]; then + ready_count="${ready%%/*}" + total_count="${ready##*/}" + if [ "$ready_count" != "$total_count" ]; then + echo "" + echo "---- Pod ${ns}/${name} (Running but not Ready: ${ready}) ----" + echo " -- Description --" + ${kctl} describe pod -n "${ns}" "${name}" 2>&1 || true + echo " -- Logs --" + ${kctl} logs -n "${ns}" "${name}" --all-containers --tail=100 2>&1 || true + fi + fi + done + + echo "" + echo "========================================================================" + echo " END OF DIAGNOSTICS" + echo "========================================================================" + echo "" +} + function wait_pod_ready() { args="$@" @@ -33,11 +95,6 @@ function wait_pod_ready() { if [ $rc -ne 0 ]; then echo "Pod $args failed to become ready within 300s" - echo "collecting diags..." - ${kubectl} get po -A -o wide - ${kubectl} describe po $args - ${kubectl} logs $args - echo "Pod $args failed to become ready within 300s; diags above ^^" fi set -e @@ -51,6 +108,12 @@ GIT_VERSION=${GIT_VERSION:-`git describe --tags --dirty --always --abbrev=12`} HELM=../bin/helm CHART=../bin/tigera-operator-$GIT_VERSION.tgz +# Determine the helm values file to use based on the CALICO_API_GROUP env var. +VALUES_FILE=$TEST_DIR/infra/values.yaml +if [ "$CALICO_API_GROUP" == "projectcalico.org/v3" ]; then + VALUES_FILE=$TEST_DIR/infra/values-v3-crds.yaml +fi + # kubectl binary. : ${kubectl:=../hack/test/kind/kubectl} @@ -69,8 +132,10 @@ echo "Install additional permissions for BGP password" ${kubectl} apply -f $TEST_DIR/infra/additional-rbac.yaml echo +# CRDs are already created prior to reaching this script from within lib.Makefile as part +# of kind cluster creation. echo "Install Calico using the helm chart" -$HELM install calico $CHART -f $TEST_DIR/infra/values.yaml -n tigera-operator --create-namespace +$HELM install calico $CHART -f $VALUES_FILE -n tigera-operator --create-namespace echo "Install calicoctl as a pod" ${kubectl} apply -f $TEST_DIR/infra/calicoctl.yaml @@ -79,18 +144,18 @@ echo echo "Wait for tigera status to be ready" if ! ( ${kubectl} wait --for=create --timeout=60s tigerastatus/calico && ${kubectl} wait --for=condition=Available --timeout=300s tigerastatus/calico ); then - echo "TigeraStatus for Calico is down, collecting diags..." - ${kubectl} get -o yaml tigerastatus/calico - echo "Logs for tigera-operator:" - ${kubectl} logs -n tigera-operator -l k8s-app=tigera-operator - echo "Status of pods:" - ${kubectl} get po -A -o wide - ${kubectl} describe po -n calico-system + echo "TigeraStatus for Calico failed to become Available" exit 1 fi -if ! ${kubectl} wait --for=condition=Available --timeout=300s tigerastatus/apiserver; then - ${kubectl} get -o yaml tigerastatus/apiserver - exit 1 + +# Wait for the Calico API server to be available, if not using the projectcalico.org/v3 CRDs. +# If using the projectcalico.org/v3 CRDs, there is no Calico API server to wait for. +if [ "$CALICO_API_GROUP" != "projectcalico.org/v3" ]; then + echo "Wait for the Calico API server to be ready" + if ! ${kubectl} wait --for=condition=Available --timeout=300s tigerastatus/apiserver; then + echo "TigeraStatus for API server failed to become Available" + exit 1 + fi fi echo "Wait for Calico to be ready..." diff --git a/node/tests/k8st/infra/calico_versions.yml b/node/tests/k8st/infra/calico_versions.yml index 6453cffe143..08dd11dc3a2 100644 --- a/node/tests/k8st/infra/calico_versions.yml +++ b/node/tests/k8st/infra/calico_versions.yml @@ -1,49 +1,44 @@ -# Components defined here are required to be kept in sync with hack/gen-versions/calico.go.tpl in the tigera/operator repository. +# Components defined here are required to be kept in sync with config/calico_versions.yml in the tigera/operator repository. # Ideally we can remove this file and the need to re-build the operator altogether if we change how images are loaded into the test cluster. title: latest components: libcalico-go: version: master typha: - image: calico/typha version: test-build - calico/node: - image: library/node + node: version: test-build - calico/cni: - image: calico/cni + cni: version: test-build - calico/node-windows: + node-windows: version: test-build - calico/cni-windows: + cni-windows: version: test-build - calico/kube-controllers: - image: calico/kube-controllers + kube-controllers: version: test-build - calico/goldmane: + goldmane: version: test-build flexvol: - image: calico/pod2daemon-flexvol version: test-build - calico/apiserver: - image: calico/apiserver + apiserver: version: test-build - calico/csi: - image: calico/csi + csi: version: test-build csi-node-driver-registrar: version: test-build key-cert-provisioner: version: test-build - calico/whisker: + webhooks: version: test-build - calico/whisker-backend: + whisker: version: test-build - calico/envoy-gateway: + whisker-backend: version: test-build - calico/envoy-proxy: + envoy-gateway: version: test-build - calico/envoy-ratelimit: + envoy-proxy: version: test-build - calico/guardian: + envoy-ratelimit: + version: test-build + guardian: version: test-build diff --git a/node/tests/k8st/infra/calicoctl.yaml b/node/tests/k8st/infra/calicoctl.yaml index 5de8e6913c3..1b7a3432b2e 100644 --- a/node/tests/k8st/infra/calicoctl.yaml +++ b/node/tests/k8st/infra/calicoctl.yaml @@ -28,7 +28,7 @@ spec: serviceAccountName: calicoctl containers: - name: calicoctl - image: quay.io/calico/ctl:master + image: calico/ctl:test-build command: - calicoctl args: @@ -69,7 +69,9 @@ rules: - pods/status verbs: - update - - apiGroups: ["crd.projectcalico.org"] + - apiGroups: + - "crd.projectcalico.org" + - "projectcalico.org" resources: - bgppeers - bgpfilters diff --git a/node/tests/k8st/infra/values-v3-crds.yaml b/node/tests/k8st/infra/values-v3-crds.yaml new file mode 100644 index 00000000000..241000fd595 --- /dev/null +++ b/node/tests/k8st/infra/values-v3-crds.yaml @@ -0,0 +1,41 @@ +# This file contains a values.yaml configuration for installing Calico backed by +# projectcalico.org/v3 CRDs instead of using the API server. +installation: + enabled: true + registry: docker.io + + # Disable HA control plane. + controlPlaneReplicas: 1 + + # Diable CSI / flexVolume as they are not used by these tests. + kubeletVolumePluginPath: "None" + flexVolumePath: "None" + + # Configure CIDRs to match kubeadm cluster. + calicoNetwork: + # Enable policy setup timeout to increase test reliability. + linuxPolicySetupTimeoutSeconds: 5 + ipPools: + - cidr: 192.168.0.0/16 + - cidr: fd00:10:244::/64 + +apiServer: + enabled: false + +goldmane: + enabled: true + +whisker: + enabled: true + +# CRDs are installed by `make kind-cluster-create` in lib.Makefile ($(KUBECTL) create -f $(REPO_ROOT)/libcalico-go/config/crd;) +manageCRDs: false + +# Configuration for the tigera operator +tigeraOperator: + image: tigera/operator + version: test-build + registry: docker.io + env: + - name: CALICO_API_GROUP + value: "projectcalico.org/v3" diff --git a/node/tests/k8st/infra/values.yaml b/node/tests/k8st/infra/values.yaml index cf49c538470..fd46e143ee6 100644 --- a/node/tests/k8st/infra/values.yaml +++ b/node/tests/k8st/infra/values.yaml @@ -1,3 +1,5 @@ +# This file contains a values.yaml configuration for installing Calico backed by +# the API server instead of using projectcalico.org/v3 CRDs. installation: enabled: true registry: docker.io @@ -11,6 +13,8 @@ installation: # Configure CIDRs to match kubeadm cluster. calicoNetwork: + # Enable policy setup timeout to increase test reliability. + linuxPolicySetupTimeoutSeconds: 5 ipPools: - cidr: 192.168.0.0/16 - cidr: fd00:10:244::/64 diff --git a/node/tests/k8st/load_images_on_kind_cluster.sh b/node/tests/k8st/load_images_on_kind_cluster.sh index 5a1838beb72..87d3ae5dbf5 100755 --- a/node/tests/k8st/load_images_on_kind_cluster.sh +++ b/node/tests/k8st/load_images_on_kind_cluster.sh @@ -14,6 +14,7 @@ images=( pod2daemon kube-controllers goldmane + webhook whisker whisker-backend ) @@ -25,4 +26,4 @@ for image in "${images[@]}"; do exit 1 } done -echo "All images loaded successfully." \ No newline at end of file +echo "All images loaded successfully." diff --git a/node/tests/k8st/test_base.py b/node/tests/k8st/test_base.py index e1ae156761c..4c7d472fe3e 100644 --- a/node/tests/k8st/test_base.py +++ b/node/tests/k8st/test_base.py @@ -22,7 +22,7 @@ from deepdiff import DeepDiff from kubernetes import client, config -from utils.utils import retry_until_success, run, kubectl +from tests.k8st.utils.utils import retry_until_success, run, kubectl logger = logging.getLogger(__name__) @@ -52,6 +52,7 @@ def tearDown(self): try: cleanup() except Exception as e: + logger.error("Error during cleanup: %s", e) errors.append(e) super(TestBase, self).tearDown() if errors: @@ -66,7 +67,7 @@ def assert_same(thing1, thing2): Compares two things. Debug logs the differences between them before asserting that they are the same. """ - assert cmp(thing1, thing2) == 0, \ + assert thing1 == thing2, \ "Items are not the same. Difference is:\n %s" % \ pformat(DeepDiff(thing1, thing2), indent=2) @@ -92,8 +93,8 @@ def log_banner(msg, *args, **kwargs): if first_log_time is None: first_log_time = time_now time_now -= first_log_time - elapsed_hms = "%02d:%02d:%02d " % (time_now / 3600, - (time_now % 3600) / 60, + elapsed_hms = "%02d:%02d:%02d " % (time_now // 3600, + (time_now % 3600) // 60, time_now % 60) level = kwargs.pop("level", logging.INFO) @@ -121,7 +122,7 @@ def check_pod_status(self, ns): def create_namespace(self, ns_name): self.cluster.create_namespace(client.V1Namespace(metadata=client.V1ObjectMeta(name=ns_name))) - def deploy(self, image, name, ns, port, replicas=1, svc_type="NodePort", traffic_policy="Local", cluster_ip=None, ipv6=False): + def deploy(self, image, name, ns, port, replicas=1, svc_type="NodePort", traffic_policy="Local", cluster_ip=None, ext_ip=None, ipv6=False): """ Creates a deployment and corresponding service with the given parameters. @@ -166,7 +167,7 @@ def deploy(self, image, name, ns, port, replicas=1, svc_type="NodePort", traffic # Create a service called whose endpoints are the pods # with "app": ; i.e. those just created above. - self.create_service(name, name, ns, port, svc_type, traffic_policy, ipv6=ipv6) + self.create_service(name, name, ns, port, svc_type, traffic_policy, ext_ip=ext_ip, ipv6=ipv6) def wait_for_deployment(self, name, ns): """ @@ -176,7 +177,7 @@ def wait_for_deployment(self, name, ns): kubectl("-n %s rollout status deployment/%s" % (ns, name)) kubectl("get pods -n %s -o wide" % ns) - def create_service(self, name, app, ns, port, svc_type="NodePort", traffic_policy="Local", cluster_ip=None, ipv6=False): + def create_service(self, name, app, ns, port, svc_type="NodePort", traffic_policy="Local", cluster_ip=None, ext_ip=None, ipv6=False): service = client.V1Service( metadata=client.V1ObjectMeta( name=name, @@ -191,6 +192,8 @@ def create_service(self, name, app, ns, port, svc_type="NodePort", traffic_polic ) if cluster_ip: service.spec["clusterIP"] = cluster_ip + if ext_ip: + service.spec["externalIPs"] = [ext_ip] if ipv6: service.spec["ipFamilies"] = ["IPv6"] diff --git a/node/tests/k8st/tests/test_bgp_advert.py b/node/tests/k8st/tests/test_bgp_advert.py index d44968e7800..e9d81517bb3 100644 --- a/node/tests/k8st/tests/test_bgp_advert.py +++ b/node/tests/k8st/tests/test_bgp_advert.py @@ -112,11 +112,6 @@ def setUp(self): bird_peer_config=self.get_bird_conf(), ) - # Enable debug logging - self.update_ds_env("calico-node", - "calico-system", - {"BGP_LOGSEVERITYSCREEN": "debug"}) - # Establish BGPPeer from cluster nodes to node-extra calicoctl("""apply -f - << EOF apiVersion: projectcalico.org/v3 @@ -162,8 +157,12 @@ def tearDown(self): EOF """) - # Remove node-2's route-reflector config. - json_str = calicoctl("get node %s -o json" % self.nodes[2]) + # Remove node-2's route-reflector config, using a retry to avoid + # transient conflict errors. + retry_until_success(lambda: self.clear_rr_config(self.nodes[2])) + + def clear_rr_config(self, node): + json_str = calicoctl("get node %s -o json" % node) node_dict = json.loads(json_str) node_dict['metadata']['labels'].pop('i-am-a-route-reflector', '') node_dict['spec']['bgp'].pop('routeReflectorClusterID', '') @@ -280,45 +279,6 @@ def test_cluster_ip_advertisement(self): retry_until_success(lambda: self.assertIn(local_svc_ip, self.get_routes())) retry_until_success(lambda: self.assertNotIn(cluster_svc_ip, self.get_routes())) - # TODO: This assertion is actually incorrect. Kubernetes performs - # SNAT on all traffic destined to a service ClusterIP that doesn't - # originate from within the cluster's pod CIDR. This assertion - # pass for External / LoadBalancer IPs, though. - # - # Create a network policy that only accepts traffic from the external node. - # kubectl("""apply -f - << EOF -# apiVersion: networking.k8s.io/v1 -# kind: NetworkPolicy -# metadata: - # name: allow-tcp-80-ex - # namespace: bgp-test -# spec: - # podSelector: {} - # policyTypes: - # - Ingress - # ingress: - # - from: - # - ipBlock: { cidr: %s/32 } - # ports: - # - protocol: TCP - # port: 80 -# EOF -# """ % self.external_node_ip) - - # Connectivity to nginx-local should always succeed. - # for i in range(attempts): - # retry_until_success(curl, function_args=[local_svc_ip]) - - # # Connectivity to nginx-cluster will rarely succeed because it is load-balanced across all nodes. - # # When the traffic hits a node that doesn't host one of the service's pod, it will be re-routed - # # to another node and SNAT will cause the policy to drop the traffic. - # # Try to curl 10 times. - # try: - # for i in range(attempts): - # curl(cluster_svc_ip) - # self.fail("external node should not be able to consistently access the cluster svc") - # except subprocess.CalledProcessError: - # pass # Scale the local_svc to 4 replicas self.scale_deployment(local_svc, self.ns, 4) @@ -539,7 +499,7 @@ def test_external_ip_advertisement(self): self.wait_for_deployment(local_svc, self.ns) # Verify that we have ECMP routes for the external IP of the local service. - retry_until_success(lambda: self.assert_ecmp_routes(local_svc_external_ip, [self.ips[1], self.ips[2], self.ips[3]])) + self.assert_ecmp_routes(local_svc_external_ip, [self.ips[1], self.ips[2], self.ips[3]]) # Delete both services, assert only cluster CIDR route is advertised. self.delete_and_confirm(local_svc, "svc", self.ns) @@ -548,6 +508,36 @@ def test_external_ip_advertisement(self): # Assert that external IP is no longer an advertised route. retry_until_success(lambda: self.assertNotIn(local_svc_externalips_route, self.get_routes())) + def test_fully_qualified_service_ips(self): + """ + Test advertisement of /32 Service IPs. + """ + with DiagsCollector(): + + # Allow exact IPs for each type of service IP. We expect these to be properly advertised. + calicoctl("""apply -f - << EOF +apiVersion: projectcalico.org/v3 +kind: BGPConfiguration +metadata: + name: default +spec: + serviceExternalIPs: + - cidr: 90.15.0.1/32 +EOF +""") + + # Create a Service with the External IP above, using + # externalTrafficPolicy=Cluster. This should trigger advertisement + # from all nodes. + svc_name = "nginx-svc" + ext_ip = "90.15.0.1" + self.deploy(NGINX_IMAGE, svc_name, self.ns, 80, traffic_policy="Cluster", svc_type="ClusterIP", ext_ip=ext_ip) + self.wait_until_exists(svc_name, "svc", self.ns) + self.wait_for_deployment(svc_name, self.ns) + + # Verify the ext IP address is advertised from all nodes. + self.assert_ecmp_routes(ext_ip, [self.ips[0], self.ips[1], self.ips[2], self.ips[3]]) + def test_loadbalancer_ip_advertisement(self): """ Runs the tests for service LoadBalancer IP advertisement @@ -599,14 +589,14 @@ def test_loadbalancer_ip_advertisement(self): # The full range should be advertised from each node. lb_cidr = "80.15.0.0/24" - retry_until_success(lambda: self.assert_ecmp_routes(lb_cidr, [self.ips[0], self.ips[1], self.ips[2], self.ips[3]])) + self.assert_ecmp_routes(lb_cidr, [self.ips[0], self.ips[1], self.ips[2], self.ips[3]]) # Scale the local_svc to 4 replicas. self.scale_deployment(local_svc, self.ns, 4) self.wait_for_deployment(local_svc, self.ns) # Verify that we have ECMP routes for the LB IP of the local service from nodes running it. - retry_until_success(lambda: self.assert_ecmp_routes(local_lb_ip, [self.ips[1], self.ips[2], self.ips[3]])) + self.assert_ecmp_routes(local_lb_ip, [self.ips[1], self.ips[2], self.ips[3]]) # Apply a modified BGP config that no longer enables advertisement # for LoadBalancer IPs. @@ -649,7 +639,7 @@ def test_loadbalancer_ip_advertisement(self): EOF """) # Verify that we have ECMP routes for the LB IP of the local service from nodes running it. - retry_until_success(lambda: self.assert_ecmp_routes(local_lb_ip, [self.ips[1], self.ips[2], self.ips[3]])) + self.assert_ecmp_routes(local_lb_ip, [self.ips[1], self.ips[2], self.ips[3]]) retry_until_success(lambda: self.assertIn(lb_cidr, self.get_routes())) retry_until_success(lambda: self.assertNotIn(cluster_svc_lb_route, self.get_routes())) @@ -768,8 +758,8 @@ def test_bgp_filter_ip_advertisement(self): action: Reject EOF """) - kubectl("patch bgppeer node-extra.peer --patch '{\"spec\": {\"filters\": [\"test-filter-export-1\"]}}'") - self.add_cleanup(lambda: kubectl("patch bgppeer node-extra.peer --patch '{\"spec\": {\"filters\": []}}'")) + kubectl("patch --type=merge bgppeer node-extra.peer --patch '{\"spec\": {\"filters\": [\"test-filter-export-1\"]}}'") + self.add_cleanup(lambda: kubectl("patch --type=merge bgppeer node-extra.peer --patch '{\"spec\": {\"filters\": []}}'")) self.add_cleanup(lambda: kubectl("delete bgpfilter test-filter-export-1")) # Assert that local clusterIP is no longer advertised. diff --git a/node/tests/k8st/tests/test_bgp_advert_v6.py b/node/tests/k8st/tests/test_bgp_advert_v6.py index a407915479a..68fcb66187f 100644 --- a/node/tests/k8st/tests/test_bgp_advert_v6.py +++ b/node/tests/k8st/tests/test_bgp_advert_v6.py @@ -107,11 +107,6 @@ def setUp(self): bird6_peer_config=self.get_bird_conf(), ) - # Enable debug logging - self.update_ds_env("calico-node", - "calico-system", - {"BGP_LOGSEVERITYSCREEN": "debug"}) - # Establish BGPPeer from cluster nodes to node-extra calicoctl("""apply -f - << EOF apiVersion: projectcalico.org/v3 @@ -145,8 +140,12 @@ def tearDown(self): EOF """) - # Remove node-2's route-reflector config. - json_str = calicoctl("get node %s -o json" % self.nodes[2]) + # Remove node-2's route-reflector config, using a retry to avoid + # transient conflict errors. + retry_until_success(lambda: self.clear_rr_config(self.nodes[2])) + + def clear_rr_config(self, node): + json_str = calicoctl("get node %s -o json" % node) node_dict = json.loads(json_str) node_dict['metadata']['labels'].pop('i-am-a-route-reflector', '') node_dict['spec']['bgp'].pop('routeReflectorClusterID', '') @@ -155,6 +154,7 @@ def tearDown(self): EOF """ % json.dumps(node_dict)) + def get_svc_cluster_ip(self, svc, ns): return kubectl("get svc %s -n %s -o json | jq -r .spec.clusterIP" % (svc, ns)).strip() @@ -501,8 +501,8 @@ def test_bgp_filter_ip_advertisement(self): action: Reject EOF """) - kubectl("patch bgppeer node-extra.peer --patch '{\"spec\": {\"filters\": [\"test-filter-export-1\"]}}'") - self.add_cleanup(lambda: kubectl("patch bgppeer node-extra.peer --patch '{\"spec\": {\"filters\": []}}'")) + kubectl("patch --type=merge bgppeer node-extra.peer --patch '{\"spec\": {\"filters\": [\"test-filter-export-1\"]}}'") + self.add_cleanup(lambda: kubectl("patch --type=merge bgppeer node-extra.peer --patch '{\"spec\": {\"filters\": []}}'")) self.add_cleanup(lambda: kubectl("delete bgpfilter test-filter-export-1")) # Assert that local clusterIP is no longer advertised. diff --git a/node/tests/k8st/tests/test_bgp_filter.py b/node/tests/k8st/tests/test_bgp_filter.py index 61a502abdac..c51f979a1e2 100644 --- a/node/tests/k8st/tests/test_bgp_filter.py +++ b/node/tests/k8st/tests/test_bgp_filter.py @@ -4,7 +4,7 @@ import re from tests.k8st.test_base import Container, Pod, TestBase -from tests.k8st.utils.utils import DiagsCollector, calicoctl, kubectl, run, retry_until_success, node_info, start_external_node_with_bgp, update_ds_env +from tests.k8st.utils.utils import DiagsCollector, calicoctl, kubectl, run, retry_until_success, node_info, start_external_node_with_bgp _log = logging.getLogger(__name__) @@ -38,11 +38,6 @@ class TestBGPFilter(TestBase): def setUp(self): super(TestBGPFilter, self).setUp() - # Enable debug logging - update_ds_env("calico-node", - "calico-system", - {"BGP_LOGSEVERITYSCREEN": "debug"}) - # Create test namespace self.ns = "bgpfilter-test" self.create_namespace(self.ns) @@ -90,7 +85,15 @@ def setUp(self): """ % self.external_node_ip6) def tearDown(self): - super(TestBGPFilter, self).tearDown() + # Ensure that we always attempt to run the base class tearDown, storing the + # exception if it fails and re-raising it after we've done our own teardown. + teardown_exception = None + try: + super(TestBGPFilter, self).tearDown() + except Exception as e: + # Log the error, but don't block further teardown steps. + _log.error("Exception during tearDown: %s", e) + teardown_exception = e self.delete_and_confirm(self.ns, "ns") @@ -110,6 +113,9 @@ def tearDown(self): kubectl("delete bgppeer node-extra.peer", allow_fail=True) kubectl("delete bgppeer node-extra-v6.peer", allow_fail=True) + if teardown_exception: + raise teardown_exception + def _get_bird_conf(self, ipv6=False): if ipv6: return bird_conf % (self.egress_node_ip6) @@ -123,7 +129,7 @@ def fn(): birdPeer = "Global_" if globalPeer else "Node_" birdPeer += peerIP.replace(".", "_").replace(":","_") routes = kubectl("exec -n calico-system %s -- %s show route protocol %s" % (calicoPod, birdCmd, birdPeer)) - result = re.search("%s *via %s on .* \[%s" % (re.escape(route), re.escape(peerIP), birdPeer), routes) + result = re.search(r"%s *via %s on .* \[%s" % (re.escape(route), re.escape(peerIP), birdPeer), routes) if result is None and present: raise Exception('route not present when it should be') if result is not None and not present: @@ -143,7 +149,7 @@ def _check_route_in_external_bird(self, birdContainer, birdPeer, routeRegex, pee def fn(): birdCmd = "birdcl6" if ipv6 else "birdcl" routes = run("docker exec %s %s show route protocol %s" % (birdContainer, birdCmd, birdPeer)) - result = re.search("%s *via %s on .* \[%s" % (routeRegex, peerIPRegex, birdPeer), routes) + result = re.search(r"%s *via %s on .* \[%s" % (routeRegex, peerIPRegex, birdPeer), routes) if result is None and present: raise Exception('route not present when it should be') if result is not None and not present: @@ -162,7 +168,7 @@ def _patch_peer_filters(self, peer, filters): """Patch BGPFilters in a BGPPeer""" filterStr = "\"" + "\", \"".join(filters) + "\"" if len(filters) > 0 else "" patchStr = "{\"spec\": {\"filters\": [%s]}}" % filterStr - kubectl("patch bgppeer %s --patch '%s'" % (peer, patchStr)) + kubectl("patch --type merge bgppeer %s --patch '%s'" % (peer, patchStr)) def _test_bgp_filter_basic(self, ipv4, ipv6): @@ -175,11 +181,11 @@ def _test_bgp_filter_basic(self, ipv4, ipv6): """ with DiagsCollector(): external_route_v4 = "10.111.111.0/24" - cluster_route_regex_v4 = "192\.168\.\d+\.\d+/\d+" + cluster_route_regex_v4 = r"192\.168\.\d+\.\d+/\d+" export_filter_cidr_v4 = "192.168.0.0/16" external_route_v6 = "fd00:1111:1111:1111::/64" - cluster_route_regex_v6 = "fd00:10:244:.*/\d+" + cluster_route_regex_v6 = r"fd00:10:244:.*/\d+" export_filter_cidr_v6 = "fd00:10:244::/64" # Add static route bird config to external node @@ -300,11 +306,9 @@ def _test_bgp_filter_ordering(self, ipv4, ipv6): exhaust matchOperators and actions""" with DiagsCollector(): external_route_v4 = "10.111.111.0/24" - cluster_route_regex_v4 = "192\.168\.\d+\.\d+/\d+" export_filter_cidr_v4 = "192.168.0.0/16" external_route_v6 = "fd00:1111:1111:1111::/64" - cluster_route_regex_v6 = "fd00:10:244:.*/\d+" export_filter_cidr_v6 = "fd00:10:244::/64" # Add static route bird config @@ -479,11 +483,9 @@ def _test_bgp_filter_global_peer(self, ipv4, ipv6): """Test BGP import filters with global BGP peers""" with DiagsCollector(): external_route_v4 = "10.111.111.0/24" - cluster_route_regex_v4 = "192\.168\.\d+\.\d+/\d+" export_filter_cidr_v4 = "192.168.0.0/16" external_route_v6 = "fd00:1111:1111:1111::/64" - cluster_route_regex_v6 = "fd00:10:244:.*/\d+" export_filter_cidr_v6 = "fd00:10:244::/64" # Add static route bird config @@ -510,11 +512,11 @@ def _test_bgp_filter_global_peer(self, ipv4, ipv6): # Patch BGPPeer to make it global if ipv4: - kubectl("patch bgppeer node-extra.peer --type json --patch '[{\"op\": \"remove\", \"path\": \"/spec/nodeSelector\"}]'") - self.add_cleanup(lambda: kubectl("patch bgppeer node-extra.peer --patch '{\"spec\":{\"nodeSelector\":\"egress == \\\"true\\\"\"}}'")) + kubectl("patch --type=merge bgppeer node-extra.peer --type json --patch '[{\"op\": \"remove\", \"path\": \"/spec/nodeSelector\"}]'") + self.add_cleanup(lambda: kubectl("patch --type=merge bgppeer node-extra.peer --patch '{\"spec\":{\"nodeSelector\":\"egress == \\\"true\\\"\"}}'")) if ipv6: - kubectl("patch bgppeer node-extra-v6.peer --type json --patch '[{\"op\": \"remove\", \"path\": \"/spec/nodeSelector\"}]'") - self.add_cleanup(lambda: kubectl("patch bgppeer node-extra-v6.peer --patch '{\"spec\":{\"nodeSelector\":\"egress == \\\"true\\\"\"}}'")) + kubectl("patch --type=merge bgppeer node-extra-v6.peer --type json --patch '[{\"op\": \"remove\", \"path\": \"/spec/nodeSelector\"}]'") + self.add_cleanup(lambda: kubectl("patch --type=merge bgppeer node-extra-v6.peer --patch '{\"spec\":{\"nodeSelector\":\"egress == \\\"true\\\"\"}}'")) # Check that route is present if ipv4: @@ -562,71 +564,6 @@ def _test_bgp_filter_global_peer(self, ipv4, ipv6): self._assert_route_not_present_in_cluster_bird(self.egress_calico_pod, external_route_v6, self.external_node_ip6, globalPeer=True, ipv6=True) - def test_bgp_filter_validation(self): - with DiagsCollector(): - # Filter with various invalid fields - output = kubectl("""apply -f - <&1 || true") - lines = ip_changes.split("\n") - assert len(lines) >= 1, "No output from ip monitor" - expected_lines_following = 0 - for line in lines: - if expected_lines_following > 0: - expected_lines_following -= 1 - elif "Terminated" in line: - # "Terminated" - pass - elif "Timestamp" in line: - # e.g. "Timestamp: Wed Nov 1 18:02:38 2017 689895 usec" - pass - elif ": tunl0" in line: - # e.g. "2: tunl0 inet 192.168.128.1/32 scope global tunl0" - # " valid_lft forever preferred_lft forever" - # Indicates IP address being added to the tunl0 device. - expected_lines_following = 1 - elif line.startswith("local") and "dev tunl0" in line: - # e.g. "local 192.168.128.1 dev tunl0 table local ..." - # Local routing table entry associated with tunl0 - # device IP address. - pass - elif "172.17.0.1 dev eth0 lladdr" in line: - # Ex: "172.17.0.1 dev eth0 lladdr 02:03:04:05:06:07 REACHABLE" - # Indicates that the host just learned the MAC - # address of its default gateway. - pass - else: - assert False, "Unexpected ip monitor line: %r" % line - - else: - # Expect non-connectivity between workloads on different hosts. - self.assert_false( - workload_host1.check_can_ping(workload_host2.ip, retries=10)) - - if not rr_ip: - # Check the BGP status on each host. - check_bird_status(host1, [("node-to-node mesh", host2.ip, "Established")]) - check_bird_status(host2, [("node-to-node mesh", host1.ip, "Established")]) - - # Flip the IP-in-IP state for the next iteration. - with_ipip = not with_ipip - host1.set_ipip_enabled(with_ipip) - -TestIPIP.batchnumber = 4 # Add batch label to these tests for parallel running - diff --git a/node/tests/st/bgp/test_node_peers.py b/node/tests/st/bgp/test_node_peers.py deleted file mode 100644 index d21fccca3b5..00000000000 --- a/node/tests/st/bgp/test_node_peers.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright (c) 2015-2016 Tigera, Inc. All rights reserved. -# -# 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. -from nose.plugins.attrib import attr - -from tests.st.test_base import TestBase -from tests.st.utils.docker_host import DockerHost, CLUSTER_STORE_DOCKER_OPTIONS -from tests.st.utils.constants import (DEFAULT_IPV4_ADDR_1, DEFAULT_IPV4_ADDR_2, - DEFAULT_IPV4_POOL_CIDR, LARGE_AS_NUM) -from tests.st.utils.utils import check_bird_status, update_bgp_config - -from .peer import create_bgp_peer -from unittest import skip - -class TestNodePeers(TestBase): - - def _test_node_peers(self, backend='bird'): - """ - Test per-node BGP peer configuration. - - Test by turning off the mesh and configuring the mesh as - a set of per node peers. - """ - with DockerHost('host1', - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - start_calico=False) as host1, \ - DockerHost('host2', - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - start_calico=False) as host2: - - # Start both hosts using specific AS numbers. - host1.start_calico_node("--backend=%s --as=%s" % (backend, LARGE_AS_NUM)) - host2.start_calico_node("--backend=%s --as=%s" % (backend, LARGE_AS_NUM)) - - # Create a network and a couple of workloads on each host. - network1 = host1.create_network("subnet1") - workload_host1 = host1.create_workload("workload1", network=network1, - ip=DEFAULT_IPV4_ADDR_1) - workload_host2 = host2.create_workload("workload2", network=network1, - ip=DEFAULT_IPV4_ADDR_2) - - # Allow network to converge - self.assert_true(workload_host1.check_can_ping(DEFAULT_IPV4_ADDR_2, retries=10)) - - # Turn the node-to-node mesh off and wait for connectivity to drop. - update_bgp_config(host1, nodeMesh=False) - self.assert_true(workload_host1.check_cant_ping(DEFAULT_IPV4_ADDR_2, retries=10)) - - # Configure node specific peers to explicitly set up a mesh. - create_bgp_peer(host1, 'node', host2.ip, LARGE_AS_NUM, metadata={'name': "host1peer" }) - create_bgp_peer(host2, 'node', host1.ip, LARGE_AS_NUM, metadata={'name': "host2peer" }) - - # Allow network to converge - self.assert_true(workload_host1.check_can_ping(DEFAULT_IPV4_ADDR_2, retries=10)) - - # Check connectivity in both directions - self.assert_ip_connectivity(workload_list=[workload_host1, - workload_host2], - ip_pass_list=[DEFAULT_IPV4_ADDR_1, - DEFAULT_IPV4_ADDR_2]) - - # Check the BGP status on each host. - check_bird_status(host1, [("node specific", host2.ip, "Established")]) - check_bird_status(host2, [("node specific", host1.ip, "Established")]) - - @attr('slow') - def test_bird_node_peers(self): - self._test_node_peers(backend='bird') - -TestNodePeers.batchnumber = 1 # Adds a batch number for parallel testing diff --git a/node/tests/st/bgp/test_route_reflector_cluster.py b/node/tests/st/bgp/test_route_reflector_cluster.py deleted file mode 100644 index 54927fbce29..00000000000 --- a/node/tests/st/bgp/test_route_reflector_cluster.py +++ /dev/null @@ -1,132 +0,0 @@ -# Copyright (c) 2015-2016 Tigera, Inc. All rights reserved. -# -# 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. -from nose.plugins.attrib import attr -from unittest import skip - -from tests.st.test_base import TestBase -from tests.st.utils.docker_host import DockerHost, CLUSTER_STORE_DOCKER_OPTIONS -from tests.st.utils.route_reflector import RouteReflectorCluster - -from .peer import create_bgp_peer -from tests.st.utils.utils import update_bgp_config - -class TestRouteReflectorCluster(TestBase): - - def test_route_reflector_cluster_resilience(self): - """ - Runs a cluster of route reflectors, brings one node down, and ensures that traffic still flows - """ - with DockerHost('host1', - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - start_calico=False) as host1, \ - DockerHost('host2', - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - start_calico=False) as host2, \ - RouteReflectorCluster(2, 1) as rrc: - - # Start both hosts using specific backends. - host1.start_calico_node("--backend=bird") - host2.start_calico_node("--backend=bird") - update_bgp_config(host1, asNum=64513, nodeMesh=False) - - # Create a workload on each host in the same network. - network1 = host1.create_network("subnet1") - workload_host1 = host1.create_workload("workload1", network=network1) - workload_host2 = host2.create_workload("workload2", network=network1) - - # Assert no network connectivity - self.assert_false(workload_host1.check_can_ping(workload_host2.ip, retries=5)) - - # Peer the hosts with the route reflectors - for host in [host1, host2]: - for rr in rrc.get_redundancy_group(): - create_bgp_peer(host, "node", rr.ip, 64513, metadata={'name': host.name + rr.name.lower()}) - - # Assert network connectivity - self.assert_true(workload_host1.check_can_ping(workload_host2.ip, retries=10)) - self.assert_ip_connectivity(workload_list=[workload_host1, - workload_host2], - ip_pass_list=[workload_host1.ip, - workload_host2.ip]) - # Bring down a node - rrc.redundancy_groups[0][0].cleanup() - - # Assert that network is still connected - self.assert_true(workload_host1.check_can_ping(workload_host2.ip, retries=10)) - self.assert_ip_connectivity(workload_list=[workload_host1, - workload_host2], - ip_pass_list=[workload_host1.ip, - workload_host2.ip]) - - - def _test_route_reflector_cluster(self, backend='bird'): - """ - Run a multi-host test using a cluster of route reflectors and node - specific peerings. - """ - with DockerHost('host1', - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - start_calico=False) as host1, \ - DockerHost('host2', - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - start_calico=False) as host2, \ - DockerHost('host3', - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - start_calico=False) as host3, \ - RouteReflectorCluster(2, 2) as rrc: - - # Start both hosts using specific backends. - host1.start_calico_node("--backend=%s" % backend) - host2.start_calico_node("--backend=%s" % backend) - host3.start_calico_node("--backend=%s" % backend) - - # Set the default AS number - as this is used by the RR mesh, and - # turn off the node-to-node mesh (do this from any host). - update_bgp_config(host1, asNum=64513, nodeMesh=False) - - # Create a workload on each host in the same network. - network1 = host1.create_network("subnet1") - workload_host1 = host1.create_workload("workload1", network=network1) - workload_host2 = host2.create_workload("workload2", network=network1) - workload_host3 = host3.create_workload("workload3", network=network1) - - # Allow network to converge (which it won't) - self.assert_false(workload_host1.check_can_ping(workload_host2.ip, retries=5)) - self.assert_true(workload_host1.check_cant_ping(workload_host3.ip)) - self.assert_true(workload_host2.check_cant_ping(workload_host3.ip)) - - # Set distributed peerings between the hosts, each host peering - # with a different set of redundant route reflectors. - for host in [host1, host2, host3]: - for rr in rrc.get_redundancy_group(): - create_bgp_peer(host, "node", rr.ip, 64513, metadata={'name': host.name + rr.name.lower()}) - - # Allow network to converge (which it now will). - self.assert_true(workload_host1.check_can_ping(workload_host2.ip, retries=20)) - self.assert_true(workload_host1.check_can_ping(workload_host3.ip, retries=20)) - self.assert_true(workload_host2.check_can_ping(workload_host3.ip, retries=20)) - - # And check connectivity in both directions. - self.assert_ip_connectivity(workload_list=[workload_host1, - workload_host2, - workload_host3], - ip_pass_list=[workload_host1.ip, - workload_host2.ip, - workload_host3.ip]) - - @attr('slow') - def test_bird_route_reflector_cluster(self): - self._test_route_reflector_cluster(backend='bird') - -TestRouteReflectorCluster.batchnumber = 3 # Adds a batch number for parallel testing diff --git a/node/tests/st/bgp/test_single_route_reflector.py b/node/tests/st/bgp/test_single_route_reflector.py deleted file mode 100644 index 82adf8b7d57..00000000000 --- a/node/tests/st/bgp/test_single_route_reflector.py +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright (c) 2015-2016 Tigera, Inc. All rights reserved. -# -# 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. -from nose.plugins.attrib import attr -from unittest import skip - -from tests.st.test_base import TestBase -from tests.st.utils.docker_host import DockerHost, CLUSTER_STORE_DOCKER_OPTIONS -from tests.st.utils.route_reflector import RouteReflectorCluster -from tests.st.utils.utils import update_bgp_config, check_bird_status, retry_until_success - -from .peer import create_bgp_peer - -class TestSingleRouteReflector(TestBase): - - @attr('slow') - def _test_single_route_reflector(self, backend='bird', bgpconfig_as_num=64514, peer_as_num=64514): - """ - Run a multi-host test using a single route reflector and global - peering. - """ - with DockerHost('host1', - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - start_calico=False) as host1, \ - DockerHost('host2', - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - start_calico=False) as host2, \ - RouteReflectorCluster(1, 1) as rrc: - - # Start both hosts using specific backends. - host1.start_calico_node("--backend=%s" % backend) - host2.start_calico_node("--backend=%s" % backend) - - # Set the default AS number - as this is used by the RR mesh, and - # turn off the node-to-node mesh (do this from any host). - update_bgp_config(host1, nodeMesh=False, asNum=bgpconfig_as_num) - - # Create a workload on each host in the same network. - network1 = host1.create_network("subnet1") - workload_host1 = host1.create_workload("workload1", - network=network1) - workload_host2 = host2.create_workload("workload2", - network=network1) - - # Allow network to converge (which it won't) - self.assert_false(workload_host1.check_can_ping(workload_host2.ip, retries=5)) - - # Set global config telling all calico nodes to peer with the - # route reflector. This can be run from either host. - rg = rrc.get_redundancy_group() - assert len(rg) == 1 - create_bgp_peer(host1, "global", rg[0].ip, peer_as_num) - - # Allow network to converge (which it now will). - retry_until_success(host1.assert_is_ready, retries=30, felix=False) - retry_until_success(host2.assert_is_ready, retries=30, felix=False) - check_bird_status(host1, [("global", rg[0].ip, "Established")]) - check_bird_status(host2, [("global", rg[0].ip, "Established")]) - self.assert_true(workload_host1.check_can_ping(workload_host2.ip, retries=20)) - - # And check connectivity in both directions. - self.assert_ip_connectivity(workload_list=[workload_host1, - workload_host2], - ip_pass_list=[workload_host1.ip, - workload_host2.ip]) - - @attr('slow') - def test_bird_single_route_reflector(self): - self._test_single_route_reflector(backend='bird') - - @attr('slow') - def test_bird_single_route_reflector_default_as(self): - self._test_single_route_reflector(backend='bird', bgpconfig_as_num=None, peer_as_num=64512) - -TestSingleRouteReflector.batchnumber = 1 # Adds a batch number for parallel testing diff --git a/node/tests/st/bgp/test_switch_route_reflector.py b/node/tests/st/bgp/test_switch_route_reflector.py deleted file mode 100644 index ec20f85ae71..00000000000 --- a/node/tests/st/bgp/test_switch_route_reflector.py +++ /dev/null @@ -1,145 +0,0 @@ -# Copyright (c) 2021 Tigera, Inc. All rights reserved. -# -# 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. -import logging -import yaml - -from nose.plugins.attrib import attr -from multiprocessing.dummy import Pool as ThreadPool -from unittest import skip - -from tests.st.test_base import TestBase -from tests.st.utils.docker_host import DockerHost, CLUSTER_STORE_DOCKER_OPTIONS -from tests.st.utils.route_reflector import RouteReflectorCluster -from tests.st.utils.utils import ( - check_bird_status, - retry_until_success, - update_bgp_config -) - -from .peer import create_bgp_peer - -logger = logging.getLogger(__name__) - -class TestSwitchRouteReflector(TestBase): - - @attr('slow') - def _test_switch_route_reflector(self, backend='bird', bgpconfig_as_num=64514, peer_as_num=64514): - """ - Test that switching from node-to-node full mesh to route reflectors doesn't disrupt dataplane traffic if done as per - https://projectcalico.docs.tigera.io/networking/bgp#change-from-node-to-node-mesh-to-route-reflectors-without-any-traffic-disruption - """ - with DockerHost('host1', - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - start_calico=False) as host1, \ - DockerHost('host2', - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - start_calico=False) as host2, \ - DockerHost('host3', - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - start_calico=False) as host3: - - # Start all hosts using specific backends. - host1.start_calico_node("--backend=%s" % backend) - host2.start_calico_node("--backend=%s" % backend) - host3.start_calico_node("--backend=%s" % backend) - - # Create a workload on host1 and host2 in the same network. - network1 = host1.create_network("subnet1") - workload_host1 = host1.create_workload("workload1", - network=network1) - workload_host2 = host2.create_workload("workload2", - network=network1) - - # Set the default AS number - as this is used by the RR mesh - # (do this from any host). - update_bgp_config(host1, asNum=bgpconfig_as_num) - - # Allow network to converge - self.assert_true(workload_host1.check_can_ping(workload_host2.ip, retries=5)) - - # Start checking ping continuously from host1's to host2's workloads - # and vice-versa - t1 = ThreadPool(1) - ping1_result = t1.apply_async(workload_host1.check_can_ping_continuously, - (workload_host2.ip,), - {'timeout':60, 'retries': 3}) - t2 = ThreadPool(1) - ping2_result = t2.apply_async(workload_host2.check_can_ping_continuously, - (workload_host1.ip,), - {'timeout':60, 'retries': 3}) - - # Make host3 act as a route reflector. - node3 = host3.calicoctl("get Node %s -o yaml" % host3.get_hostname()) - node3cfg = yaml.safe_load(node3) - logger.info("host3 Node: %s", node3cfg) - node3cfg['spec']['bgp']['routeReflectorClusterID'] = '224.0.0.3' - node3cfg['metadata']['labels'] = { - 'routeReflectorClusterID': node3cfg['spec']['bgp']['routeReflectorClusterID'], - } - host3.add_resource(node3cfg) - - # Configure peerings - note, NOT a full mesh - from the - # other nodes to the route reflector. - host3.add_resource({ - 'apiVersion': 'projectcalico.org/v3', - 'kind': 'BGPPeer', - 'metadata': { - 'name': 'rr-peerings', - }, - 'spec': { - 'nodeSelector': '!has(routeReflectorClusterID)', - 'peerSelector': 'has(routeReflectorClusterID)', - }, - }) - - # Wait until the peers' BGP session to the route reflector are - # established - retry_until_success(check_bird_status, 30, AssertionError, - host1, [("node specific", host3.ip, "Established")]) - retry_until_success(check_bird_status, 30, AssertionError, - host2, [("node specific", host3.ip, "Established")]) - - # Turn off the node-to-node mesh (do this from any host). - update_bgp_config(host3, nodeMesh=False) - - # Allow network to converge - self.assert_true(workload_host1.check_can_ping(workload_host2.ip, retries=20)) - - # Verify results of continuous ping from host1's to host2's workloads - # and vice-versa - t1.close() - t1.join() - logger.info("ping1_result: %s", str(ping1_result.get())) - self.assert_true(ping1_result.get()) - t2.close() - t2.join() - logger.info("ping2_result: %s", str(ping2_result.get())) - self.assert_true(ping2_result.get()) - - # Check connectivity in both directions. - self.assert_ip_connectivity(workload_list=[workload_host1, - workload_host2], - ip_pass_list=[workload_host1.ip, - workload_host2.ip], - retries=5) - - @attr('slow') - def test_bird_switch_route_reflector(self): - self._test_switch_route_reflector(backend='bird') - - @attr('slow') - def test_bird_switch_route_reflector_default_as(self): - self._test_switch_route_reflector(backend='bird', bgpconfig_as_num=None, peer_as_num=64512) - -TestSwitchRouteReflector.batchnumber = 1 # Adds a batch number for parallel testing diff --git a/node/tests/st/bgp/test_update_ip_addr.py b/node/tests/st/bgp/test_update_ip_addr.py deleted file mode 100644 index 2f3096fcc67..00000000000 --- a/node/tests/st/bgp/test_update_ip_addr.py +++ /dev/null @@ -1,68 +0,0 @@ -# Copyright (c) 2017 Tigera, Inc. All rights reserved. -# -# 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. -import json -from nose.plugins.attrib import attr - -from tests.st.test_base import TestBase -from tests.st.utils.docker_host import DockerHost, CLUSTER_STORE_DOCKER_OPTIONS - -class TestUpdateIPAddress(TestBase): - - @attr('slow') - def test_update_ip_address(self): - """ - Test updating the IP address automatically updates and fixes the - Bird BGP config. - """ - with DockerHost('host1', - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - start_calico=False) as host1, \ - DockerHost('host2', - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - start_calico=False) as host2: - - # Start host1 and host2 using bogus IP addresses. The nodes should - # start although they won't be functional. - host1.start_calico_node("--ip=1.2.3.4") - host2.start_calico_node("--ip=2.3.4.5") - - # Create a network and a couple of workloads on each host. - network1 = host1.create_network("subnet1") - workload_host1 = host1.create_workload("workload1", network=network1) - workload_host2 = host2.create_workload("workload2", network=network1) - - # Fix the node resources to have the correct IP addresses. BIRD - # should automatically fix it's configuration and connectivity will - # be established. - self._fix_ip(host1) - self._fix_ip(host2) - - # Allow network to converge - self.assert_true(workload_host1.check_can_ping(workload_host2.ip, retries=10)) - - # Check connectivity in both directions - self.assert_ip_connectivity(workload_list=[workload_host1, - workload_host2], - ip_pass_list=[workload_host1.ip, - workload_host2.ip]) - - def _fix_ip(self, host): - """ - Update the calico node resource to have the correct IP for the host. - """ - noder = json.loads(host.calicoctl( - "get node %s --output=json" % host.get_hostname())) - noder["spec"]["bgp"]["ipv4Address"] = str(host.ip) - host.writejson("new_data", noder) - host.calicoctl("apply -f new_data") diff --git a/node/tests/st/calicoctl/__init__.py b/node/tests/st/calicoctl/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/node/tests/st/calicoctl/test_autodetection.py b/node/tests/st/calicoctl/test_autodetection.py deleted file mode 100644 index 797e3fb8dee..00000000000 --- a/node/tests/st/calicoctl/test_autodetection.py +++ /dev/null @@ -1,143 +0,0 @@ -# Copyright (c) 2015-2016 Tigera, Inc. All rights reserved. -# -# 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. -from nose.plugins.attrib import attr - -from tests.st.test_base import TestBase -from tests.st.utils.docker_host import DockerHost -from tests.st.utils.utils import ETCD_CA, ETCD_CERT, \ - ETCD_KEY, ETCD_HOSTNAME_SSL, ETCD_SCHEME, get_ip -from tests.st.utils.exceptions import CommandExecError - -if ETCD_SCHEME == "https": - ADDITIONAL_DOCKER_OPTIONS = "--cluster-store=etcd://%s:2379 " \ - "--cluster-store-opt kv.cacertfile=%s " \ - "--cluster-store-opt kv.certfile=%s " \ - "--cluster-store-opt kv.keyfile=%s " % \ - (ETCD_HOSTNAME_SSL, ETCD_CA, ETCD_CERT, - ETCD_KEY) -else: - ADDITIONAL_DOCKER_OPTIONS = "--cluster-store=etcd://%s:2379 " % \ - get_ip() - -class TestAutodetection(TestBase): - - @attr('slow') - def test_autodetection(self): - """ - Test using different IP autodetection methods. - - We run a multi-host test for this to test explicit selection of - "first-found" and also "interface" and "can-reach" detection methods. - """ - with DockerHost('host1', - additional_docker_options=ADDITIONAL_DOCKER_OPTIONS, - start_calico=False) as host1, \ - DockerHost('host2', - additional_docker_options=ADDITIONAL_DOCKER_OPTIONS, - start_calico=False) as host2, \ - DockerHost('host3', - additional_docker_options=ADDITIONAL_DOCKER_OPTIONS, - start_calico=False) as host3, \ - DockerHost('host4', - additional_docker_options=ADDITIONAL_DOCKER_OPTIONS, - start_calico=False) as host4, \ - DockerHost('host5', - additional_docker_options=ADDITIONAL_DOCKER_OPTIONS, - start_calico=False) as host5: - - # Start the node on host1 using first-found auto-detection - # method. - host1.start_calico_node( - "--ip=autodetect --ip-autodetection-method=first-found") - - # Attempt to start the node on host2 using can-reach auto-detection - # method using a bogus DNS name. This should fail. - try: - host2.start_calico_node( - "--ip=autodetect --ip-autodetection-method=can-reach=something.invalid") - except CommandExecError: - pass - else: - raise AssertionError("Command expected to fail but did not") - - # Start the node on host2 using can-reach auto-detection method - # using the IP address of host1. This should succeed. - host2.start_calico_node( - "--ip=autodetect --ip-autodetection-method=can-reach=" + host1.ip) - - # Attempt to start the node on host3 using interface auto-detection - # method using a bogus interface name. This should fail. - try: - host3.start_calico_node( - "--ip=autodetect --ip-autodetection-method=interface=BogusInterface", - with_ipv4pool_cidr_env_var=False - ) - except CommandExecError: - pass - else: - raise AssertionError("Command expected to fail but did not") - - # Start the node on host3 using interface auto-detection method - # using a working interface name. This should succeed. - host3.start_calico_node( - "--ip=autodetect --ip-autodetection-method=interface=eth0", - with_ipv4pool_cidr_env_var=False - ) - - # Start the node on host4 using interface auto-detection method - # using multiple interface names (1 fake, 1 working). This should succeed - host4.start_calico_node( - "--ip=autodetect --ip-autodetection-method=interface=BogusInterface,eth0", - with_ipv4pool_cidr_env_var=False - ) - - # Attempt to start the node on host5 using interface auto-detection - # method skipping the only working interface name. This should fail. - try: - host5.start_calico_node( - "--ip=autodetect --ip-autodetection-method=skip-interface=eth.*,enp0s.*", - with_ipv4pool_cidr_env_var=False - ) - except CommandExecError: - pass - else: - raise AssertionError("Command to skip eth0 expected to fail but did not") - - # Start the node on host5 using skip-interface auto-detection method - # using a bogus interface to skip. This should succeed. - host5.start_calico_node( - "--ip=autodetect --ip-autodetection-method=skip-interface=bogus", - with_ipv4pool_cidr_env_var=False - ) - - # Create a network and a workload on each host. - network1 = host1.create_network("subnet1") - workload_host1 = host1.create_workload("workload1", network=network1) - workload_host2 = host2.create_workload("workload2", network=network1) - workload_host3 = host3.create_workload("workload3", network=network1) - workload_host4 = host4.create_workload("workload4", network=network1) - workload_host5 = host5.create_workload("workload5", network=network1) - - # Check connectivity in both directions - self.assert_ip_connectivity(workload_list=[workload_host1, - workload_host2, - workload_host3, - workload_host4, - workload_host5], - ip_pass_list=[workload_host1.ip, - workload_host2.ip, - workload_host3.ip, - workload_host4.ip, - workload_host5.ip], - retries=10) diff --git a/node/tests/st/calicoctl/test_default_pools.py b/node/tests/st/calicoctl/test_default_pools.py deleted file mode 100644 index 4e3591dcaad..00000000000 --- a/node/tests/st/calicoctl/test_default_pools.py +++ /dev/null @@ -1,164 +0,0 @@ -# Copyright (c) 2015-2016 Tigera, Inc. All rights reserved. -# -# 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. -import functools -import logging - -import netaddr -import yaml -from nose_parameterized import parameterized - -from tests.st.test_base import TestBase -from tests.st.utils.docker_host import DockerHost, CLUSTER_STORE_DOCKER_OPTIONS, NODE_CONTAINER_NAME -from tests.st.utils.exceptions import CommandExecError -from tests.st.utils.utils import get_ip, wipe_etcd, retry_until_success - -_log = logging.getLogger(__name__) -_log.setLevel(logging.DEBUG) - - -class TestDefaultPools(TestBase): - @classmethod - def setUpClass(cls): - # First, create a (fake) host to run things in - cls.host = DockerHost("host", - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - start_calico=False, - dind=False) - - def setUp(self): - try: - self.host.execute("docker rm -f calico-node") - except CommandExecError: - # Presumably calico-node wasn't running - pass - wipe_etcd(get_ip()) - - @classmethod - def tearDownClass(cls): - cls.host.cleanup() - - @parameterized.expand([ - (False, "CALICO_IPV4POOL_CIDR", "10.0.0.0/27", 0, None, True, "Too small"), - (False, "CALICO_IPV4POOL_CIDR", "10.0.0.0/32", 0, None, True, "Too small, but legal CIDR"), - (False, "CALICO_IPV4POOL_CIDR", "10.0.0.0/33", 0, None, True, "Impossible CIDR"), - (False, "CALICO_IPV4POOL_CIDR", "256.0.0.0/24", 0, None, True, "Invalid IP"), - (True, "CALICO_IPV4POOL_CIDR", "10.0.0.0/24", 2, None, True, "Typical non-default pool"), - (True, "CALICO_IPV4POOL_CIDR", "10.0.0.0/26", 2, None, True, "Smallest legal pool"), - (True, "CALICO_IPV6POOL_CIDR", "fd00::/122", 2, None, False, "Smallest legal pool"), - (False, "CALICO_IPV6POOL_CIDR", "fd00::/123", 0, None, False, "Too small"), - (False, "CALICO_IPV6POOL_CIDR", "fd00::/128", 0, None, False, "Too small, but legal CIDR"), - (False, "CALICO_IPV6POOL_CIDR", "fd00::/129", 0, None, False, "Impossible CIDR"), - (True, "CALICO_IPV4POOL_CIDR", "10.0.0.0/24", 2, "CrossSubnet", True,"Typ. non-def pool, IPIP"), - (True, "CALICO_IPV4POOL_CIDR", "10.0.0.0/24", 2, "Always", True,"Typ. non-default pool, IPIP"), - (True, "CALICO_IPV4POOL_CIDR", "10.0.0.0/24", 2, "Never", True, "Typical pool, explicitly no IPIP"), - (True, "CALICO_IPV6POOL_CIDR", "fd00::/122", 2, "Always", False, "IPv6 - IPIP not permitted"), - (True, "CALICO_IPV6POOL_CIDR", "fd00::/122", 2, "CrossSubnet", False, "IPv6 - IPIP not allowed"), - (True, "CALICO_IPV6POOL_CIDR", "fd00::/122", 2, "Never", False, "IPv6, IPIP explicitly off"), - (False, "CALICO_IPV6POOL_CIDR", "fd00::/122", 0, "junk", False, "Invalid IPIP value"), - (False, "CALICO_IPV4POOL_CIDR", "10.0.0.0/24", 0, "reboot", True, "Invalid IPIP value"), - (False, "CALICO_IPV4POOL_CIDR", "0.0.0.0/0", 0, None, True, "Invalid, link local address"), - (False, "CALICO_IPV6POOL_CIDR", "::/0", 0, None, False, "Invalid, link local address"), - (True, "CALICO_IPV6POOL_CIDR", "fd80::0:0/120", 2, None, False, "Valid, but non-canonical form"), - (False, "CALICO_IPV6POOL_CIDR", "1.2.3.4/24", 0, None, False, "Wrong type"), - (False, "CALICO_IPV4POOL_CIDR", "fd00::/24", 0, None, True, "Wrong type"), - (True, "CALICO_IPV6POOL_CIDR", "::0:a:b:c:d:e:0/120", 2, None, False, "Valid, non-canonical form"), - (False, "CALICO_IPV4POOL_CIDR", "1.2/16", 0, None, True, "Valid, unusual form"), - ]) - def test_default_pools(self, success_expected, param, value, exp_num_pools, ipip, nat_outgoing, description): - """ - Test that the various options for default pools work correctly - """ - _log.debug("Test description: %s", description) - # Get command line for starting docker - output = self.host.calicoctl("node run --dryrun --node-image=%s" % NODE_CONTAINER_NAME) - base_command = output.split('\n')[-4].rstrip() - - # Modify command line to add the options we want to test - env_inserts = "-e %s=%s " % (param, value) - if ipip is not None: - env_inserts += "-e CALICO_IPV4POOL_IPIP=%s " % ipip - prefix, _, suffix = base_command.partition("-e") - command = prefix + env_inserts + "-e" + suffix - - # Start calico-docker - self.host.execute(command) - - if not success_expected: - # check for "Calico node failed to start" - self.wait_for_node_log("Calico node failed to start") - return - - # Check we started OK - self.wait_for_node_log("Calico node started successfully") - # check the expected pool is present - pools_output = self.host.calicoctl("get ippool -o yaml") - pools_dict = yaml.safe_load(pools_output)['items'] - cidrs = [pool['spec']['cidr'] for pool in pools_dict] - # Convert to canonical form - value = str(netaddr.IPNetwork(value)) - assert value in cidrs, "Didn't find %s in %s" % (value, cidrs) - - # Dump pools and attempt to load them with calicoctl (to confirm consistency) - self.host.calicoctl("get ippool -o json > testfile.json") - self.host.calicoctl("apply -f testfile.json") - - assert len(pools_dict) == exp_num_pools, \ - "Expected %s pools, found %s. %s" % (exp_num_pools, len(pools_dict), pools_dict) - - # Grab the pool of interest - pool = pools_dict[cidrs.index(value)] - other_pool = None - # And grab the other pool if any - if len(pools_dict) > 1: - pools_dict.remove(pool) - other_pool = pools_dict[0] - # Check IPIP setting if we're doing IPv4 - if ipip in ["CrossSubnet", "Always", "Never"] and param == "CALICO_IPV4POOL_CIDR": - assert pool['spec']['ipipMode'] == ipip, \ - "Didn't find ipip mode in pool %s" % pool - if ipip in [None] or param == "CALICO_IPV6POOL_CIDR": - assert pool['spec']['ipipMode'] == "Never", \ - "Didn't find ipip mode in pool %s" % pool - if ipip in ["CrossSubnet", "Always", "Never"] and param == "CALICO_IPV6POOL_CIDR": - assert other_pool['spec']['ipipMode'] == ipip, \ - "Didn't find ipip mode in pool %s" % pool - - # Check NAT setting - if 'natOutgoing' in pool['spec']: - assert pool['spec']['natOutgoing'] is nat_outgoing, \ - "Wrong NAT default in pool %s, expected natOutgoing to be %s" % (pool, nat_outgoing) - else: - assert nat_outgoing is False, \ - "Wrong NAT default in pool %s, expecting natOutgoing to be disabled" % pool - - def test_no_default_pools(self): - """ - Test that NO_DEFAULT_POOLS works correctly - """ - # Start calico-docker - self.host.start_calico_node(options="--no-default-ippools", - with_ipv4pool_cidr_env_var=False) - self.wait_for_node_log("Calico node started successfully") - # check the expected pool is present - pools_output = self.host.calicoctl("get ippool -o yaml") - pools_dict = yaml.safe_load(pools_output)['items'] - assert pools_dict == [], "Pools not empty: %s" % pools_dict - - def assert_calico_node_log_contains(self, expected_string): - assert expected_string in self.host.execute("docker logs calico-node"), \ - "Didn't find %s in start log" % expected_string - - def wait_for_node_log(self, expected_log): - check = functools.partial(self.assert_calico_node_log_contains, expected_log) - retry_until_success(check, 5, ex_class=AssertionError) diff --git a/node/tests/st/calicoctl/test_node_diags.py b/node/tests/st/calicoctl/test_node_diags.py deleted file mode 100644 index 21c4cacec64..00000000000 --- a/node/tests/st/calicoctl/test_node_diags.py +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright (c) 2015-2016 Tigera, Inc. All rights reserved. -# -# 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. -from tests.st.test_base import TestBase -from tests.st.utils.docker_host import DockerHost - -""" -Test calicoctl diags. - -It's worth testing that the command can be executed. It's debatable whether -it's worth testing the upload. - -We're not trying to assert on the contents of the diags package. - -TODO We could check that the file is actually written (and doesn't just appear -in the output) and is a decent size. -TODO We could check collecting diags when calico-node is actually running. -""" - - -class TestNodeDiags(TestBase): - def test_node_diags(self): - """ - Test that the diags command successfully creates a tar.gz file. - """ - with DockerHost('host', dind=False, start_calico=False) as host: - results = host.calicoctl("node diags") - self.assertIn(".tar.gz", results) diff --git a/node/tests/st/calicoctl/test_node_run.py b/node/tests/st/calicoctl/test_node_run.py deleted file mode 100644 index 8660fd5f959..00000000000 --- a/node/tests/st/calicoctl/test_node_run.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (c) 2015-2016 Tigera, Inc. All rights reserved. -# -# 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. -from tests.st.test_base import TestBase -from tests.st.utils.docker_host import DockerHost - -""" -Test calicoctl node run - -""" - -class TestNodeRun(TestBase): - def test_node_run_dryrun(self): - """ - Test that dryrun does not output ETCD_AUTHORITY or ETCD_SCHEME. - """ - with DockerHost('host', dind=False, start_calico=False) as host: - output = host.calicoctl("node run --dryrun") - assert "ETCD_AUTHORITY" not in output - assert "ETCD_SCHEME" not in output - assert "ETCD_ENDPOINTS" in output diff --git a/node/tests/st/calicoctl/test_node_status.py b/node/tests/st/calicoctl/test_node_status.py deleted file mode 100644 index 732a1ee29b1..00000000000 --- a/node/tests/st/calicoctl/test_node_status.py +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright (c) 2015-2016 Tigera, Inc. All rights reserved. -# -# 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. -from tests.st.test_base import TestBase -from tests.st.utils.docker_host import DockerHost -from tests.st.utils.exceptions import CommandExecError -from tests.st.utils.utils import retry_until_success - -""" -Test calicoctl status - -Most of the status output is checked by the BGP tests, so this module just -contains a simple return code check. -""" - -class TestNodeStatus(TestBase): - def test_node_status(self): - """ - Test that the status command can be executed. - """ - with DockerHost('host', dind=False, start_calico=True) as host: - def node_status(): - host.calicoctl("node status") - retry_until_success(node_status, retries=10, ex_class=Exception) - - def test_node_status_fails(self): - """ - Test that the status command fails when calico node is not running - """ - with DockerHost('host', dind=False, start_calico=False) as host: - try: - host.calicoctl("node status") - except CommandExecError as e: - self.assertEquals(e.returncode, 1) - self.assertEquals(e.output, - "Calico process is not running.\n") - else: - raise AssertionError("'calicoctl node status' did not exit" - " with code 1 when node was not running") diff --git a/node/tests/st/config/test_felix_config.py b/node/tests/st/config/test_felix_config.py deleted file mode 100644 index a12fb38f431..00000000000 --- a/node/tests/st/config/test_felix_config.py +++ /dev/null @@ -1,469 +0,0 @@ -# Copyright (c) 2017 Tigera, Inc. All rights reserved. -# -# 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. - -import logging -import time -from functools import partial - -from tests.st.test_base import TestBase, HOST_IPV4 -from tests.st.utils.docker_host import DockerHost, CLUSTER_STORE_DOCKER_OPTIONS -from tests.st.utils.utils import log_and_run, retry_until_success, \ - handle_failure, clear_on_failures, add_on_failure, wipe_etcd - -_log = logging.getLogger(__name__) -_log.setLevel(logging.DEBUG) - -POST_DOCKER_COMMANDS = [ - "docker load -q -i /code/calico-node.tar", -] - - -class TestFelixConfig(TestBase): - """ - Tests felix configurations setup by calicoctl. - """ - hosts = None - - @classmethod - def setUpClass(cls): - # Wipe etcd once before any test in this class runs. - _log.debug("Wiping etcd") - wipe_etcd(HOST_IPV4) - - # Test felix configurations. - - # Create two hosts. - cls.hosts = [] - cls.host1 = DockerHost("cali-host1", - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - post_docker_commands=POST_DOCKER_COMMANDS, - start_calico=False) - cls.host1_hostname = cls.host1.execute("hostname") - cls.host2 = DockerHost("cali-host2", - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - post_docker_commands=POST_DOCKER_COMMANDS, - start_calico=False) - cls.host2_hostname = cls.host2.execute("hostname") - cls.hosts.append(cls.host1) - cls.hosts.append(cls.host2) - - # Start calico node on hosts. - for host in cls.hosts: - host.start_calico_node() - - _log.info("host1 IP: %s , host2 IP: %s", cls.host1.ip, cls.host2.ip) - - clear_on_failures() - add_on_failure(cls.host1.log_extra_diags) - add_on_failure(cls.host2.log_extra_diags) - - @handle_failure - def test_failsafe_inbound(self): - """ - Test failsafe inbound configuration. - """ - random = {"udp": [70], "tcp": [3000, 5000]} - default = {"udp": [68], "tcp": [22, 179, 2379, 2380, 6666, 6667]} - - # Test random and default ports can be accessed with no host endpoints setup. - self.run_failsafe(default, True) - self.run_failsafe(random, True) - - # With host endpoints setup. Default ports can be accessed. - # But random ports cannot. - self.add_host_iface(self.host1_hostname, self.host1.ip) - self.run_failsafe(default, True) - self.run_failsafe(random, False) - - # Update felix config and check again. - self.add_felix_failsafe_config("default", random, "inbound") - self.run_failsafe(default, False) - self.run_failsafe(random, True) - - # Put default back for host1. - self.add_felix_failsafe_config("node.%s" % self.host1_hostname, default, "inbound") - self.run_failsafe(default, True) - self.run_failsafe(random, False) - - # Put default back for all. - self.add_felix_failsafe_config("default", default, "inbound") - return - - @handle_failure - def test_failsafe_outbound(self): - """ - Test failsafe outbound configuration. - """ - # We need to put 2379 into random because felix need to connect to etcd. - # Which means 2379 can be accessed all the time. - # We need to check 2379 when we expect access is True but need skip checking - # it if we expect access is False. - random = {"udp": [70], "tcp": [2379, 3000, 5000]} - random_no_2379 = {"udp": [70], "tcp": [3000, 5000]} - default = {"udp": [53, 67], "tcp": [179, 2379, 2380, 6666, 6667]} - default_no_2379 = {"udp": [53, 67], "tcp": [179, 2380, 6666, 6667]} - - # Test random and default ports can be accessed with no host endpoints setup. - self.run_failsafe(default, True) - self.run_failsafe(random, True) - - # With host endpoints setup. Default ports can be accessed. - # But random ports cannot. - self.add_host_iface(self.host2_hostname, self.host2.ip) - self.run_failsafe(default, True) - self.run_failsafe(random_no_2379, False) - - # Update felix config and check again. - self.add_felix_failsafe_config("default", random, "outbound") - self.run_failsafe(default_no_2379, False) - self.run_failsafe(random, True) - - # Put default back for host2. - self.add_felix_failsafe_config("node.%s" % self.host2_hostname, default, "outbound") - self.run_failsafe(default, True) - self.run_failsafe(random_no_2379, False) - - # Put default back for all. - self.add_felix_failsafe_config("default", default, "outbound") - return - - @handle_failure - def test_log_file(self): - """ - Test Log file configuration. - """ - host3 = DockerHost("cali-host3", - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - post_docker_commands=POST_DOCKER_COMMANDS, - start_calico=False) - host3_hostname = host3.execute("hostname") - host3.start_calico_node() - - conf_name = "node.%s" % host3_hostname - default = "/var/log/calico/felix/current" - - # Check default log file is good. - levels = {"DEBUG": False, "INFO": True} - self.check_log_levels(host3, default, levels, 3) - - # Change severity to DEBUG. - self.add_felix_config(conf_name, {'LogSeverityFile': 'DEBUG'}) - # Make sure new severity setting is taken by felix. - self.check_log_levels(host3, default, {"DEBUG": True}, 3) - # Empty log file and check again for DEBUG and INFO. - self.empty_log_file(host3, default) - levels = {"DEBUG": True, "INFO": True} - self.check_log_levels(host3, default, levels, 3) - - # Change log file and severity to FATAL. - new_path = "/var/log/calico/st_test0" - self.add_felix_config(conf_name, {'logFilePath': new_path, 'LogSeverityFile': 'FATAL'}) - self.restart_felix_no_config_file(host3) - - # Don't expect DEBUG INFO WARNING FATAL message from new log file. - levels = {"DEBUG": False, "INFO": False, "WARNING": False, "FATAL": False} - self.check_log_levels(host3, new_path, levels, 3) - - # Send a termination signal. We should see FATAL but not DEBUG INFO WARNING. - self.restart_felix_no_config_file(host3) - levels = {"DEBUG": False, "INFO": False, "WARNING": False, "FATAL": True} - self.check_log_levels(host3, new_path, levels, 3) - - # Change log file and severity to WARNING and send termination signal. - new_path = "/var/log/calico/st_test1" - self.add_felix_config(conf_name, {'logFilePath': new_path, 'LogSeverityFile': 'WARNING'}) - self.restart_felix_no_config_file(host3) - - # Dont expect DEBUG INFO WARNING FATAL messages from new log file. - levels = {"DEBUG": False, "INFO": False, "WARNING": False, "FATAL": False} - self.check_log_levels(host3, new_path, levels, 3) - - # Send a termination signal. Expect both FATAL WARNING messages but not DEBUG INFO. - self.restart_felix_no_config_file(host3) - levels = {"DEBUG": False, "INFO": False, "WARNING": True, "FATAL": True} - self.check_log_levels(host3, new_path, levels, 3) - - # Should have error message when log severity is WARNING. - # This could take up to 20 retries waiting for an ERROR messages. - self.remove_ipset_command(host3) - self.check_log_levels(host3, new_path, {"ERROR": True}, retries=20) - - # Should not expect DEBUG INFO messages. - levels = {"DEBUG": False, "INFO": False} - self.check_log_levels(host3, new_path, levels, 3) - self.restore_ipset_command(host3) - - # Restore log config - self.add_felix_config(conf_name, {'logFilePath': default, 'LogSeverityFile': 'INFO'}) - self.restart_felix_no_config_file(host3) - - host3.cleanup(ignore_list=["Received OS signal terminated", "ipsets"]) - - @handle_failure - def test_log_screen(self): - """ - Test Log screen configuration. - """ - host3 = DockerHost("cali-host3", - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - post_docker_commands=POST_DOCKER_COMMANDS, - start_calico=False) - host3_hostname = host3.execute("hostname") - - # Start calico node on host which disable file logging. - host3.start_calico_node(env_options="-e CALICO_DISABLE_FILE_LOGGING=true") - - # Wait for felix to start and get snapshot - conf_name = "node.%s" % host3_hostname - self.wait_for_felix(host3) - self.log_screen_snapshot(host3, "snapshot") - - # Check DEBUG and INFO in snapshot. - levels = {"DEBUG": False, "INFO": True} - self.check_log_levels(host3, "snapshot", levels, 3) - - # Change severity to DEBUG. Check DEBUG INFO again since last snapshot. - self.add_felix_config(conf_name, {'LogSeverityScreen': 'DEBUG'}) - levels = {"DEBUG": True, "INFO": True} - self.check_log_levels(host3, "log_diff", levels, 3, "snapshot") - - # Change severity to ERROR. - self.add_felix_config(conf_name, {'LogSeverityScreen': 'ERROR'}) - # Restart felix wait for FATAL message and take snapshot. - self.restart_felix_no_config_file(host3) - levels = {"FATAL": True} - self.check_log_levels(host3, "log_diff", levels, 3, "snapshot") - self.log_screen_snapshot(host3, "snapshot") - - # Remove ipset command. - self.remove_ipset_command(host3) - - # Don't expect DEBUG INFO WARNING FATAL message from last snapshot. - levels = {"DEBUG": False, "INFO": False, "WARNING": False, "FATAL": False} - self.check_log_levels(host3, "log_diff", levels, 3, "snapshot") - # Should have ERROR message. - self.check_log_levels(host3, "log_diff", {"ERROR": True}, 20, "snapshot") - - self.restore_ipset_command(host3) - host3.cleanup() - - @staticmethod - def log_screen_snapshot(host, snapshot): - cmd = "docker logs calico-node > %s 2>&1" % snapshot - host.execute(cmd) - - @staticmethod - def log_screen_diff(host, snapshot, diff_file): - cmd = "docker logs calico-node > tmp_snapshot 2>&1" - host.execute(cmd) - - # busybox diff does not support -c option. - cmd = "diff %s tmp_snapshot > tmp_diff || exit 0" % snapshot - host.execute(cmd) - cmd = "grep \"^+\" tmp_diff > %s || exit 0" % diff_file # Get real difference. - host.execute(cmd) - - @staticmethod - def remove_ipset_command(host): - cmd = "docker exec calico-node mv /usr/sbin/ipset /usr/sbin/ipset.backup" - host.execute(cmd) - - @staticmethod - def restore_ipset_command(host): - cmd = "docker exec calico-node mv /usr/sbin/ipset.backup /usr/sbin/ipset" - host.execute(cmd) - - @staticmethod - def empty_log_file(host, log_file): - cmd = "docker exec calico-node cat /dev/null > %s" % log_file - host.execute(cmd) - - def check_log_levels(self, host, log_file, levels, retries, snapshot=""): - for k in levels: # DEBUG INFO WARNING ERROR FATAL - retry_until_success(self._get_check_file_func(host, levels[k], log_file, k, snapshot), - retries=retries) - - def restart_felix_no_config_file(self, host): - _log.info("Try to remove default felix config file and restart felix") - cmd = "docker exec calico-node rm -f /etc/calico/felix.cfg" - host.execute(cmd) - cmd = "docker exec calico-node pkill -f calico-felix" - host.execute(cmd) - - self.wait_for_felix(host) - - def wait_for_felix(self, host): - for retry in range(5): - result = host.execute("docker exec calico-node ps -a | grep -c calico-felix || exit 0") - if result == "1": - _log.info("calico-felix restarted.") - return - else: - _log.info("retry [%s] calico-felix waiting for restart", retry) - time.sleep(1) - - self.fail("Felix failed to start in 5 seconds.") - - def _get_check_file_func(self, host, expect, log_file, keyword="", snapshot=""): - func = partial(self.log_check_file, host, expect, log_file, keyword, snapshot) - return func - - def log_check_file(self, host, expect, log_file, keyword="", snapshot=""): - _log.info("Check log file %s, keywords %s, expect %s", log_file, keyword, expect) - if not snapshot == "": - _log.info("Work out the log_file with snapshot") - # If snapshot is specified, log_file is a screen log. - self.log_screen_diff(host, snapshot, log_file) - - cmd = "test -e %s ; echo $?" % log_file - result = host.execute(cmd) - if not result == "0": - self.fail(("Log file %s not exists." % log_file)) - - if keyword != "": - cmd = "grep -c %s %s || exit 0" % (keyword, log_file) - result = host.execute(cmd, raise_exception_on_failure=False) - _log.info("Check keywords result is %s", result) - if result == "0" and expect: - self.fail(("Log file %s does not contain keyword %s which is not " - "expected." % (log_file, keyword))) - if result != "0" and not expect: - self.fail(("Log file %s contains keyword %s which is not " - "expected." % (log_file, keyword))) - - def setUp(self): - # Override the per-test setUp to avoid wiping etcd; instead only clean up the data we - # added. - self.remove_host_endpoint() - - def tearDown(self): - self.remove_host_endpoint() - super(TestFelixConfig, self).tearDown() - - @classmethod - def tearDownClass(cls): - # Tidy up - for host in cls.hosts: - host.remove_workloads() - for host in cls.hosts: - host.cleanup() - del host - - clear_on_failures() - - def add_host_iface(self, node_name, ip): - host_endpoint_data = { - 'apiVersion': 'projectcalico.org/v3', - 'kind': 'HostEndpoint', - 'metadata': { - 'name': 'host-int', - 'labels': {'nodeEth': 'host'} - }, - 'spec': { - 'node': '%s' % node_name, - 'interfaceName': 'eth0', - 'expectedIPs': [str(ip)], - } - } - self.host1.add_resource(host_endpoint_data) - - def add_felix_config(self, name, spec): - felix_config = { - 'apiVersion': 'projectcalico.org/v3', - 'kind': 'FelixConfiguration', - 'metadata': { - 'name': name, - }, - 'spec': spec, - } - self.host1.add_resource(felix_config) - - def add_felix_failsafe_config(self, name, failsafe, direction): - # Convert to map list - udp_ports = map(lambda x: {'protocol': 'UDP', 'port': x}, failsafe['udp']) - tcp_ports = map(lambda x: {'protocol': 'TCP', 'port': x}, failsafe['tcp']) - - if direction == "inbound": - spec = {'failsafeInboundHostPorts': udp_ports + tcp_ports} - elif direction == "outbound": - spec = {'failsafeOutboundHostPorts': udp_ports + tcp_ports} - else: - self.fail("Adding felix config with wrong 'direction' %s", direction) - - self.add_felix_config(name, spec) - - def run_failsafe(self, failsafe, expect_access): - for k in failsafe: - for port in failsafe[k]: - if port != 179: # port is already open by bird. - self.open_port(self.host1, port, k) - - for k in failsafe: - for port in failsafe[k]: - self.check_access_retry(expect_access, self.host2, self.host1, port, k) - - @staticmethod - def open_port(host, port, protocol="tcp"): - protocol_opt = "-u" if protocol == "udp" else "" - cmd = "nc -l %s -p %s -e /bin/sh" % (protocol_opt, port) - host.execute(cmd, raise_exception_on_failure=True, daemon_mode=True) - - @staticmethod - def close_port(host, port, protocol="tcp"): - protocol_opt = "-u" if protocol == "udp" else "" - cmd = "nc -l %s -p %s -e /bin/sh" % (protocol_opt, port) - host.execute(cmd, raise_exception_on_failure=True, daemon_mode=True) - - def check_access_retry(self, expect_access, host_src, host_target, port, protocol="tcp"): - msg = (" from %s to %s:%s %s, expect access %s" % - (host_src.ip, host_target.ip, port, protocol, expect_access)) - for retry in range(3): - passed = self.check_access(expect_access, host_src, host_target, port, protocol) - if passed: - _log.info("check access success!%s", msg) - return - else: - if not expect_access: - # If expect is False but result is True. The port has been - # closed. We need open it again for next round testing. - # If expect is True but result is False, the port is still open. - _log.info("Reopen port for target host.") - self.open_port(host_target, port, protocol) - _log.exception("retry [%s] check access failed!%s", - retry, msg) - time.sleep(1) - self.fail(("check access failed!%s", msg)) - - @staticmethod - def check_access(expect_access, host_src, host_target, port, protocol="tcp"): - protocol_opt = "-u" if protocol == "udp" else "" - cmd = "echo \"hostname && exit\" | timeout -t 3 nc %s -w 1 %s %s" % \ - (protocol_opt, host_target.ip, port) - remote_hostname = host_src.execute(cmd, raise_exception_on_failure=False) - hostname = host_target.execute("hostname") - _log.info("check access from %s to %s:%s %s, result is |%s|, " - "target hostname |%s|, expect access %s", - host_src.ip, host_target.ip, port, protocol, - remote_hostname, hostname, expect_access) - - # If port is 179, we are talking to bird. - # If bird returns with a valid string, this means connection is successful. - if port == 179 and remote_hostname is not None and len(remote_hostname) > 0: - remote_hostname = hostname - - result = (hostname == remote_hostname) - return result == expect_access - - def remove_host_endpoint(self): - self.host1.delete_all_resource("hostEndpoint") diff --git a/node/tests/st/ipam/__init__.py b/node/tests/st/ipam/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/node/tests/st/ipam/test_ipam.py b/node/tests/st/ipam/test_ipam.py deleted file mode 100644 index aee2b8791c7..00000000000 --- a/node/tests/st/ipam/test_ipam.py +++ /dev/null @@ -1,245 +0,0 @@ -# Copyright (c) 2015-2016 Tigera, Inc. All rights reserved. -# -# 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. -import logging -import random - -import netaddr -import time -import yaml -from nose_parameterized import parameterized - -from tests.st.test_base import TestBase -from tests.st.utils.docker_host import DockerHost, CLUSTER_STORE_DOCKER_OPTIONS - -POST_DOCKER_COMMANDS = ["docker load -q -i /code/calico-node.tar", - "docker load -q -i /code/busybox.tar", - "docker load -q -i /code/workload.tar"] - -logging.basicConfig(level=logging.DEBUG, format="%(message)s") -logger = logging.getLogger(__name__) - - -class MultiHostIpam(TestBase): - @classmethod - def setUpClass(cls): - super(MultiHostIpam, cls).setUpClass() - cls.hosts = [] - cls.hosts.append(DockerHost("host1", - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - post_docker_commands=POST_DOCKER_COMMANDS, - start_calico=False)) - cls.hosts.append(DockerHost("host2", - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - post_docker_commands=POST_DOCKER_COMMANDS, - start_calico=False)) - cls.hosts[0].start_calico_node() - cls.hosts[1].start_calico_node() - cls.network = cls.hosts[0].create_network("testnet1") - - @classmethod - def tearDownClass(cls): - # Tidy up - cls.network.delete() - for host in cls.hosts: - host.cleanup() - del host - - def setUp(self): - # Save off original pool if any, then wipe pools so we have a known ground state - response = self.hosts[0].calicoctl("get IPpool -o yaml") - self.orig_pools = yaml.safe_load(response)['items'] - if len(self.orig_pools) > 0: - self.hosts[0].writefile("orig_pools.yaml", response) - self.hosts[0].calicoctl("delete -f orig_pools.yaml") - - def tearDown(self): - # Replace original pool, if any - response = self.hosts[0].calicoctl("get IPpool -o yaml") - self.hosts[0].writefile("testpools.yaml", response) - self.hosts[0].calicoctl("delete -f testpools.yaml") - if len(self.orig_pools) > 0: - self.hosts[0].calicoctl("apply -f orig_pools.yaml") - # Remove all workloads - for host in self.hosts: - host.remove_workloads() - - def test_pools_add(self): - """ - (Add a pool), create containers, check IPs assigned from pool. - Then Delete that pool. - Add a new pool, create containers, check IPs assigned from NEW pool - """ - response = self.hosts[0].calicoctl("get IPpool -o yaml") - pools = yaml.safe_load(response) - if len(pools['items']) > 0: - self.hosts[0].writefile("pools.yaml", response) - self.hosts[0].calicoctl("delete -f pools.yaml") - - old_pool_workloads = [] - ipv4_subnet = netaddr.IPNetwork("192.168.11.0/24") - new_pool = {'apiVersion': 'projectcalico.org/v3', - 'kind': 'IPPool', - 'metadata': {'name': 'ippool-name-1'}, - 'spec': {'cidr': str(ipv4_subnet.ipv4())}, - } - self.hosts[0].writejson("newpool.json", new_pool) - self.hosts[0].calicoctl("create -f newpool.json") - - for host in self.hosts: - workload = host.create_workload("wlda-%s" % host.name, - image="workload", - network=self.network) - assert netaddr.IPAddress(workload.ip) in ipv4_subnet - old_pool_workloads.append((workload, host)) - - blackhole_cidr = netaddr.IPNetwork( - self.hosts[0].execute("ip r | grep blackhole").split()[1]) - assert blackhole_cidr in ipv4_subnet - # Check there's only one /32 present and that its within the pool - output = self.hosts[0].execute("ip r | grep cali").split('\n') - assert len(output) == 1, "Output should only be 1 line. Got: %s" % output - wl_ip = netaddr.IPNetwork(output[0].split()[0]) - assert wl_ip in ipv4_subnet - - self.hosts[0].remove_workloads() - - self.hosts[0].calicoctl("delete -f newpool.json") - self.hosts[0].calicoctl("get IPpool -o yaml") - - ipv4_subnet = netaddr.IPNetwork("10.0.1.0/24") - new_pool = {'apiVersion': 'projectcalico.org/v3', - 'kind': 'IPPool', - 'metadata': {'name': 'ippool-name-2'}, - 'spec': {'cidr': str(ipv4_subnet.ipv4())}, - } - self.hosts[0].writejson("pools.json", new_pool) - self.hosts[0].calicoctl("create -f pools.json") - - for host in self.hosts: - workload = host.create_workload("wlda2-%s" % host.name, - image="workload", - network=self.network) - assert netaddr.IPAddress(workload.ip) in ipv4_subnet, \ - "Workload IP in wrong pool. IP: %s, Pool: %s" % (workload.ip, ipv4_subnet.ipv4()) - - blackhole_cidr = netaddr.IPNetwork( - self.hosts[0].execute("ip r | grep blackhole").split()[1]) - assert blackhole_cidr in ipv4_subnet - # Check there's only one /32 present and that its within the pool - output = self.hosts[0].execute("ip r | grep cali").split('\n') - assert len(output) == 1, "Output should only be 1 line. Got: %s" % output - wl_ip = netaddr.IPNetwork(output[0].split()[0]) - assert wl_ip in ipv4_subnet - - def test_ipam_show(self): - """ - Create some workloads, then ask calicoctl to tell you about the IPs in the pool. - Check that the correct IPs are shown as in use. - """ - num_workloads = 2 - workload_ips = [] - - ipv4_subnet = netaddr.IPNetwork("192.168.45.0/25") - new_pool = {'apiVersion': 'projectcalico.org/v3', - 'kind': 'IPPool', - 'metadata': {'name': 'ippool-name-3'}, - 'spec': {'cidr': str(ipv4_subnet.ipv4())}, - } - self.hosts[0].writejson("newpool.json", new_pool) - self.hosts[0].calicoctl("create -f newpool.json") - - for i in range(num_workloads): - host = random.choice(self.hosts) - workload = host.create_workload("wlds-%s" % i, - image="workload", - network=self.network) - workload_ips.append(workload.ip) - - print workload_ips - - for ip in ipv4_subnet: - response = self.hosts[0].calicoctl("ipam show --ip=%s" % ip) - if "No attributes defined for" in response: - # This means the IP is assigned - assert str(ip) in workload_ips, "ipam show says IP %s " \ - "is assigned when it is not" % ip - if "not currently assigned in block" in response: - # This means the IP is not assigned - assert str(ip) not in workload_ips, \ - "ipam show says IP %s is not assigned when it is!" % ip - - @parameterized.expand([ - (False,), - (True,), - ]) - def test_pool_wrap(self, make_static_workload): - """ - Repeatedly create and delete workloads until the system re-assigns an IP. - """ - - ipv4_subnet = netaddr.IPNetwork("192.168.46.0/29") - - # Create a new IP pool, but use a smaller blocksize so that it doesn't - # take as many iterations to complete the test. - new_pool = {'apiVersion': 'projectcalico.org/v3', - 'kind': 'IPPool', - 'metadata': {'name': 'ippool-name-4'}, - 'spec': {'cidr': str(ipv4_subnet.ipv4()), 'blockSize': 30}, - } - self.hosts[0].writejson("newpool.json", new_pool) - self.hosts[0].calicoctl("create -f newpool.json") - - host = self.hosts[0] - i = 0 - if make_static_workload: - static_workload = host.create_workload("static", - image="workload", - network=self.network) - i += 1 - - new_workload = host.create_workload("wldw-%s" % i, - image="workload", - network=self.network) - assert netaddr.IPAddress(new_workload.ip) in ipv4_subnet - original_ip = new_workload.ip - while True: - self.delete_workload(host, new_workload) - i += 1 - new_workload = host.create_workload("wldw-%s" % i, - image="workload", - network=self.network) - assert netaddr.IPAddress(new_workload.ip) in ipv4_subnet - if make_static_workload: - assert new_workload.ip != static_workload.ip, "IPAM assigned an IP which is " \ - "still in use!" - - if new_workload.ip == original_ip: - # Used a /30 block size - so 4 addresses. - poolsize = 4 - # But if we're using one for a static workload, there will be one less - if make_static_workload: - poolsize -= 1 - assert i >= poolsize, "Original IP was re-assigned before entire host pool " \ - "was cycled through. Hit after %s times" % i - break - if i > (len(ipv4_subnet) * 2): - assert False, "Cycled twice through pool - original IP still not assigned." - - @staticmethod - def delete_workload(host, workload): - host.calicoctl("ipam release --ip=%s" % workload.ip) - host.execute("docker rm -f %s" % workload.name) - host.workloads.remove(workload) - -MultiHostIpam.batchnumber = 2 # Adds a batch number for parallel testing diff --git a/node/tests/st/policy/__init__.py b/node/tests/st/policy/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/node/tests/st/policy/test_felix_gateway.py b/node/tests/st/policy/test_felix_gateway.py deleted file mode 100644 index 833daf6bcd1..00000000000 --- a/node/tests/st/policy/test_felix_gateway.py +++ /dev/null @@ -1,874 +0,0 @@ -# Copyright (c) 2017 Tigera, Inc. All rights reserved. -# -# 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. - -import json -import logging -import subprocess - -from tests.st.test_base import TestBase, HOST_IPV4 -from tests.st.utils.docker_host import DockerHost, CLUSTER_STORE_DOCKER_OPTIONS -from tests.st.utils.utils import get_ip, log_and_run, retry_until_success, \ - ETCD_CA, ETCD_CERT, ETCD_KEY, ETCD_HOSTNAME_SSL, ETCD_SCHEME, \ - handle_failure, clear_on_failures, add_on_failure, wipe_etcd - -_log = logging.getLogger(__name__) -_log.setLevel(logging.DEBUG) - -POST_DOCKER_COMMANDS = [ - "docker load -q -i /code/calico-node.tar", - "docker load -q -i /code/busybox.tar", - "docker load -q -i /code/workload.tar", -] - - -class TestFelixOnGateway(TestBase): - """ - Tests that policy is correctly implemented when using Calico - on a gateway or router. In that scenario, Calico should - police forwarded (possibly NATted) traffic using the host endpoint - policy. - """ - hosts = None - gateway = None - host = None - - @classmethod - def setUpClass(cls): - # Wipe etcd once before any test in this class runs. - _log.debug("Wiping etcd") - wipe_etcd(HOST_IPV4) - - # We set up an additional docker network to act as the external - # network. The Gateway container is connected to both networks. - # and we configure it as a NAT gateway. - # - # "cali-st-ext" host - # container - # | - # "cali-st-ext" docker - # bridge - # | - # Gateway Host - # container container - # \ / - # default docker - # bridge - - # We are testing two host endpoints including - # gw_int connecting gateway with host through internal network. - # gw_ext connecting gateway with external server. - # - # We are testing five access patterns. - # Host to external server through gateway. - # Host -> gw_int(untracked ingress, preDNAT) -> gw_int(forward ingress) -> - # gw_ext(forward egress) -> gw_ext(untracked egress) -> external server. - # - # Host to workload running on gateway. - # Host -> gw_int(untracked ingress, preDNAT) -> gw_int(forward ingress) -> - # workload (workload ingress) - # - # Host to process running on gateway. - # Host -> gw_int(untracked ingress, preDNAT) -> gw_int(normal ingress) - # - # Process running on gateway to external server. - # Process -> gw_ext(normal egress) -> gw_ext(untracked egress) - # - # Workload running on gateway to external server. - # Workload (workload egress) -> gw_ext(forward egress) -> gw_ext(untracked egress) - - # First, create the hosts and the gateway. - cls.hosts = [] - cls.gateway = DockerHost("cali-st-gw", - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - post_docker_commands=POST_DOCKER_COMMANDS, - start_calico=False) - cls.gateway_hostname = cls.gateway.execute("hostname") - cls.host = DockerHost("cali-st-host", - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - post_docker_commands=POST_DOCKER_COMMANDS, - start_calico=False) - cls.host_hostname = cls.host.execute("hostname") - cls.hosts.append(cls.gateway) - cls.hosts.append(cls.host) - - # Delete the nginx container if it still exists. We need to do this - # before we try to remove the network. - log_and_run("docker rm -f cali-st-ext-nginx || true") - - # Create the external network. - log_and_run("docker network rm cali-st-ext || true") - # Use 172.19.0.0 to avoid clash with normal docker subnet and - # docker-in-docker subnet - log_and_run("docker network create --driver bridge --subnet 172.19.0.0/16 cali-st-ext") - - # And an nginx server on the external network only. - log_and_run("docker run" - " --network=cali-st-ext" - " -d" - " --name=cali-st-ext-nginx" - " nginx") - - for host in cls.hosts: - host.start_calico_node() - - # Run local httpd server on gateway. - cls.gateway.execute( - "echo ' Local process ' > $HOME/index.html && httpd -p 80 -h $HOME") - - # Get the internal IP of the gateway. We do this before we add the second - # network since it means we don't have to figure out which IP is which. - int_ip = str(cls.gateway.ip) - cls.gateway_int_ip = int_ip - _log.info("Gateway internal IP: %s", cls.gateway_int_ip) - - # Add the gateway to the external network. - log_and_run("docker network connect cali-st-ext cali-st-gw") - - # Get the external IP of the gateway. - ext_ip = log_and_run("docker inspect --format " - "'{{with index .NetworkSettings.Networks" - " \"cali-st-ext\"}}{{.IPAddress}}{{end}}' cali-st-gw") - cls.gateway_ext_ip = ext_ip - _log.info("Gateway external IP: %s", cls.gateway_ext_ip) - - # Get the IP of the external server. - ext_ip = cls.get_container_ip("cali-st-ext-nginx") - cls.ext_server_ip = ext_ip - _log.info("External server IP: %s", cls.ext_server_ip) - - # Configure the internal host to use the gateway for the external IP. - cls.host.execute("ip route add %s via %s" % - (cls.ext_server_ip, cls.gateway_int_ip)) - - # Configure the gateway to forward and NAT. - cls.gateway.execute("sysctl -w net.ipv4.ip_forward=1") - cls.gateway.execute("iptables -t nat -A POSTROUTING --destination %s -j MASQUERADE" % - cls.ext_server_ip) - - cls.calinet = cls.gateway.create_network("calinet") - cls.gateway_workload = cls.gateway.create_workload( - "gw-wl", - image="workload", - network=cls.calinet, - labels=["org.projectcalico.label.wep=gateway"]) - - cls.host_workload = cls.host.create_workload( - "host-wl", - image="workload", - network=cls.calinet, - labels=["org.projectcalico.label.wep=host"]) - - clear_on_failures() - add_on_failure(cls.host.log_extra_diags) - add_on_failure(cls.gateway.log_extra_diags) - - def setUp(self): - # Override the per-test setUp to avoid wiping etcd; instead only clean up the data we - # added. - self.remove_pol_and_endpoints() - - def tearDown(self): - self.remove_pol_and_endpoints() - super(TestFelixOnGateway, self).tearDown() - - @classmethod - def tearDownClass(cls): - # Tidy up - for host in cls.hosts: - host.remove_workloads() - for host in cls.hosts: - host.cleanup() - del host - cls.calinet.delete() - - log_and_run("docker rm -f cali-st-ext-nginx || true") - - clear_on_failures() - - @handle_failure - def test_can_connect_by_default(self): - """ - Test if traffic is allowed with no policy setup. - """ - retry_until_success(self.assert_host_can_curl_local, 3) - retry_until_success(self.assert_gateway_can_curl_ext, 3) - retry_until_success(self.assert_host_can_curl_ext, 3) - retry_until_success(self.assert_hostwl_can_access_workload, 3) - retry_until_success(self.assert_workload_can_curl_ext, 3) - - self.add_host_iface() - - # Adding the host endpoints should break connectivity until we add policy back in. - # Add allow policy for host, make sure it applies to forward and has order lower than - # empty forward. - self.add_policy({ - 'apiVersion': 'projectcalico.org/v3', - 'kind': 'GlobalNetworkPolicy', - 'metadata': { - 'name': 'host-out', - }, - 'spec': { - 'order': 100, - 'selector': 'nodeEth == "host"', - 'egress': [{'action': 'Allow'}], - 'ingress': [{'action': 'Allow'}], - 'applyOnForward': True, - } - }) - retry_until_success(self.assert_host_can_curl_ext, 3) - - @handle_failure - def test_default_deny_for_local_traffic(self): - """ - Test default deny for local traffic after host endpoint been created. - """ - self.test_can_connect_by_default() - - self.add_gateway_external_iface() - self.add_gateway_internal_iface() - - retry_until_success(self.assert_host_can_not_curl_local, 3) - retry_until_success(self.assert_gateway_can_not_curl_ext, 3) - retry_until_success(self.assert_host_can_curl_ext, 3) - retry_until_success(self.assert_hostwl_can_access_workload, 3) - retry_until_success(self.assert_workload_can_curl_ext, 3) - - @handle_failure - def test_empty_policy_for_forward_traffic(self): - """ - Test empty policy deny local and forward traffic. - """ - self.test_can_connect_by_default() - - self.add_gateway_external_iface() - self.add_gateway_internal_iface() - - # Add empty policy forward, but only to host endpoint. - self.add_policy({ - 'apiVersion': 'projectcalico.org/v3', - 'kind': 'GlobalNetworkPolicy', - 'metadata': { - 'name': 'empty-forward', - }, - 'spec': { - 'order': 500, - 'selector': 'has(nodeEth)', - 'ingress': [], - 'egress': [], - 'applyOnForward': True, - 'types': ['Ingress', 'Egress'] - } - }) - - retry_until_success(self.assert_host_can_not_curl_local, 3) - retry_until_success(self.assert_gateway_can_not_curl_ext, 3) - retry_until_success(self.assert_host_can_not_curl_ext, 3) - retry_until_success(self.assert_hostwl_can_not_access_workload, 3) - retry_until_success(self.assert_workload_can_not_curl_ext, 3) - - @handle_failure - def test_local_allow_with_forward_empty(self): - """ - Test local allow does not affect forward traffic with empty policy. - """ - self.test_empty_policy_for_forward_traffic() - - # Add local ingress/egress allow. - self.add_ingress_policy(200, 'Allow', False) - self.add_egress_policy(200, 'Allow', False) - - retry_until_success(self.assert_host_can_curl_local, 3) - retry_until_success(self.assert_gateway_can_curl_ext, 3) - retry_until_success(self.assert_host_can_not_curl_ext, 3) - retry_until_success(self.assert_hostwl_can_not_access_workload, 3) - retry_until_success(self.assert_workload_can_not_curl_ext, 3) - - # Add local&forward ingress/egress allow. - self.add_ingress_policy(200, 'Allow', True) - self.add_egress_policy(200, 'Allow', True) - - retry_until_success(self.assert_host_can_curl_local, 3) - retry_until_success(self.assert_gateway_can_curl_ext, 3) - retry_until_success(self.assert_host_can_curl_ext, 3) - retry_until_success(self.assert_hostwl_can_access_workload, 3) - retry_until_success(self.assert_workload_can_curl_ext, 3) - - @handle_failure - def test_local_deny_with_lower_forward_allow(self): - """ - Test local deny with lower order does not affect forward allow policy. - """ - self.test_empty_policy_for_forward_traffic() # setup a deny for all traffic - - # Add local&forward ingress/egress allow. - self.add_ingress_policy(300, 'Allow', True) - self.add_egress_policy(300, 'Allow', True) - - retry_until_success(self.assert_host_can_curl_local, 3) - retry_until_success(self.assert_gateway_can_curl_ext, 3) - retry_until_success(self.assert_host_can_curl_ext, 3) - retry_until_success(self.assert_hostwl_can_access_workload, 3) - retry_until_success(self.assert_workload_can_curl_ext, 3) - - # Add local ingress/egress deny. - self.add_ingress_policy(200, 'Deny', False) - self.add_egress_policy(200, 'Deny', False) - - retry_until_success(self.assert_host_can_not_curl_local, 3) - retry_until_success(self.assert_gateway_can_not_curl_ext, 3) - retry_until_success(self.assert_host_can_curl_ext, 3) - retry_until_success(self.assert_hostwl_can_access_workload, 3) - retry_until_success(self.assert_workload_can_curl_ext, 3) - - @handle_failure - def test_local_ingress_allow_with_lower_ingress_forward_deny(self): - """ - Test local ingress allow does not affect forward ingress deny with lower order. - """ - self.test_can_connect_by_default() - - self.add_gateway_external_iface() - self.add_gateway_internal_iface() - - # Add local ingress allow and forward ingress deny - self.add_ingress_policy(200, 'Allow', False) - self.add_ingress_policy(500, 'Deny', True) - - retry_until_success(self.assert_host_can_curl_local, 3) - retry_until_success(self.assert_gateway_can_not_curl_ext, 3) - retry_until_success(self.assert_host_can_not_curl_ext, 3) - retry_until_success(self.assert_hostwl_can_not_access_workload, 3) - retry_until_success(self.assert_workload_can_curl_ext, 3) - - # Add workload egress deny - self.add_workload_egress(800, 'Deny') - - retry_until_success(self.assert_host_can_curl_local, 3) - retry_until_success(self.assert_gateway_can_not_curl_ext, 3) - retry_until_success(self.assert_host_can_not_curl_ext, 3) - retry_until_success(self.assert_hostwl_can_not_access_workload, 3) - retry_until_success(self.assert_workload_can_not_curl_ext, 3) - - @handle_failure - def test_local_egress_allow_with_lower_egress_forward_deny(self): - """ - Test local egress allow does not affect forward egress deny with lower order. - """ - self.test_can_connect_by_default() - - self.add_gateway_external_iface() - self.add_gateway_internal_iface() - - # Add local egress allow and forward egress deny - self.add_egress_policy(200, 'Allow', False) - self.add_egress_policy(500, 'Deny', True) - - retry_until_success(self.assert_host_can_not_curl_local, 3) - retry_until_success(self.assert_gateway_can_curl_ext, 3) - retry_until_success(self.assert_host_can_not_curl_ext, 3) - retry_until_success(self.assert_hostwl_can_access_workload, 3) - retry_until_success(self.assert_workload_can_not_curl_ext, 3) - - # Add workload ingress deny - self.add_workload_ingress(800, 'Deny') - - retry_until_success(self.assert_host_can_not_curl_local, 3) - retry_until_success(self.assert_gateway_can_curl_ext, 3) - retry_until_success(self.assert_host_can_not_curl_ext, 3) - retry_until_success(self.assert_hostwl_can_not_access_workload, 3) - retry_until_success(self.assert_workload_can_not_curl_ext, 3) - - @handle_failure - def test_local_forward_opposite_policy_0(self): - """ - Test local and forward got opposite allow/deny rules. - """ - self.test_can_connect_by_default() - - self.add_gateway_external_iface() - self.add_gateway_internal_iface() - - # Add local ingress allow, egress deny and lower forward ingress deny, forward egress allow - self.add_ingress_policy(200, 'Allow', False) - self.add_ingress_policy(500, 'Deny', True) - self.add_egress_policy(200, 'Deny', False) - self.add_egress_policy(500, 'Allow', True) - - retry_until_success(self.assert_host_can_curl_local, 3) - retry_until_success(self.assert_gateway_can_not_curl_ext, 3) - retry_until_success(self.assert_host_can_not_curl_ext, 3) - retry_until_success(self.assert_hostwl_can_not_access_workload, 3) - retry_until_success(self.assert_workload_can_curl_ext, 3) - - @handle_failure - def test_local_forward_opposite_policy_1(self): - """ - Test local and forward got opposite allow/deny rules. - """ - self.test_can_connect_by_default() - - self.add_gateway_external_iface() - self.add_gateway_internal_iface() - - # Add local ingress deny, egress allow and lower forward ingress allow, forward egress deny - self.add_ingress_policy(200, 'Deny', False) - self.add_ingress_policy(500, 'Allow', True) - self.add_egress_policy(200, 'Allow', False) - self.add_egress_policy(500, 'Deny', True) - - retry_until_success(self.assert_host_can_not_curl_local, 3) - retry_until_success(self.assert_gateway_can_curl_ext, 3) - retry_until_success(self.assert_host_can_not_curl_ext, 3) - retry_until_success(self.assert_hostwl_can_access_workload, 3) - retry_until_success(self.assert_workload_can_not_curl_ext, 3) - - @handle_failure - def test_host_endpoint_combinations(self): - """ - Test combinations of untracked, preDNAT, normal and forward policies. - """ - self.test_can_connect_by_default() - - self.add_gateway_external_iface() - self.add_gateway_internal_iface() - - # Test untracked policy. - self.add_untrack_gw_int(500, 'Allow') - self.add_untrack_gw_ext(500, 'Allow') - - retry_until_success(self.assert_host_can_curl_local, 3) - retry_until_success(self.assert_gateway_can_curl_ext, 3) - - # Untracked packets skip masquerade rule for packet from host - # via gateway to ext server. - retry_until_success(self.assert_host_can_not_curl_ext, 3) - # Conntrack state invalid, default workload policy will drop packet. - retry_until_success(self.assert_hostwl_can_not_access_workload, 3) - # Packet from workload will be masqueraded by cali-nat-outgoing. It - # can reach external server but return packet will be dropped by not having - # a conntrack entry to do a reverse SNAT. - retry_until_success(self.assert_workload_can_not_curl_ext, 3) - - # Configure external server to use gateway as default gateway. - # So we dont need to masquerade internal ip. - # External server sees internal ip and knows how to send response - # back. - self.set_ext_container_default_route("cali-st-ext-nginx") - retry_until_success(self.assert_host_can_curl_ext, 3) - - self.del_untrack_gw_int() - self.del_untrack_gw_ext() - - # Deny host endpoint ingress. - # Ingress packet dropped. Egress packet accepted. - self.add_ingress_policy(200, 'Deny', True) - self.add_egress_policy(200, 'Allow', True) - retry_until_success(self.assert_host_can_not_curl_local, 3) - retry_until_success(self.assert_host_can_not_curl_ext, 3) - retry_until_success(self.assert_hostwl_can_not_access_workload, 3) - - retry_until_success(self.assert_gateway_can_curl_ext, 3) - retry_until_success(self.assert_workload_can_curl_ext, 3) - - # Skip normal and forward policy if preDNAT policy accept packet. - self.add_prednat_ingress(500, 'Allow') - retry_until_success(self.assert_host_can_curl_local, 3) - retry_until_success(self.assert_host_can_curl_ext, 3) - retry_until_success(self.assert_hostwl_can_access_workload, 3) - - retry_until_success(self.assert_gateway_can_curl_ext, 3) - retry_until_success(self.assert_workload_can_curl_ext, 3) - - self.add_prednat_ingress(200, 'Deny') - retry_until_success(self.assert_host_can_not_curl_local, 3) - retry_until_success(self.assert_host_can_not_curl_ext, 3) - retry_until_success(self.assert_hostwl_can_not_access_workload, 3) - - retry_until_success(self.assert_gateway_can_curl_ext, 3) - retry_until_success(self.assert_workload_can_curl_ext, 3) - - # Skip preDNAT, normal and forward policy if untracked policy accept packet. - self.add_untrack_gw_int(500, 'Allow') - retry_until_success(self.assert_host_can_curl_local, 3) - retry_until_success(self.assert_gateway_can_curl_ext, 3) - retry_until_success(self.assert_host_can_not_curl_ext, 3) - # We need to add egress allow because if host send request to external server, - # return traffic will not match any conntrack entry hence been dropped by - # cali-fhfw-eth1. An untracked egress allow skips normal forward policy. - self.add_untrack_gw_ext(500, 'Allow') - # Traffic to/from workload will be dropped by workload default policy - # since conntrack entry is invalid. - retry_until_success(self.assert_host_can_curl_ext, 3) - # Traffic to/from workload will be dropped by workload default policy - # since conntrack entry is invalid. - retry_until_success(self.assert_hostwl_can_not_access_workload, 3) - retry_until_success(self.assert_workload_can_not_curl_ext, 3) - - def add_workload_ingress(self, order, action): - self.add_policy({ - 'apiVersion': 'projectcalico.org/v3', - 'kind': 'GlobalNetworkPolicy', - 'metadata': { - 'name': 'workload-ingress', - }, - 'spec': { - 'order': order, - 'ingress': [ - { - 'protocol': 'TCP', - 'destination': { - 'ports': [80] - }, - 'action': action, - }, - ], - 'egress': [], - 'selector': '!has(nodeEth)' - } - }) - - def add_workload_egress(self, order, action): - self.add_policy({ - 'apiVersion': 'projectcalico.org/v3', - 'kind': 'GlobalNetworkPolicy', - 'metadata': { - 'name': 'workload-egress', - }, - 'spec': { - 'order': order, - 'ingress': [], - 'egress': [ - { - 'protocol': 'TCP', - 'destination': { - 'ports': [80], - 'nets': [self.ext_server_ip + "/32"], - }, - 'action': action - }, - ], - 'selector': '!has(nodeEth)' - } - }) - - def add_prednat_ingress(self, order, action): - self.add_policy({ - 'apiVersion': 'projectcalico.org/v3', - 'kind': 'GlobalNetworkPolicy', - 'metadata': { - 'name': 'prednat', - }, - 'spec': { - 'order': order, - 'ingress': [ - { - 'protocol': 'TCP', - 'destination': { - 'ports': [80] - }, - 'action': action - }, - ], - 'egress': [], - 'selector': 'nodeEth == "gateway-int"', - 'applyOnForward': True, - 'preDNAT': True - } - }) - - def del_prednat_ingress(self): - self.delete_all("globalnetworkpolicy prednat") - - def add_untrack_gw_int(self, order, action): - self.add_policy({ - 'apiVersion': 'projectcalico.org/v3', - 'kind': 'GlobalNetworkPolicy', - 'metadata': { - 'name': 'untrack-ingress', - }, - 'spec': { - 'order': order, - 'ingress': [ - { - 'protocol': 'TCP', - 'destination': { - 'ports': [80] - }, - 'action': action - }, - ], - 'egress': [ - { - 'protocol': 'TCP', - 'source': { - 'ports': [80] - }, - 'action': action - }, - ], - 'selector': 'nodeEth == "gateway-int"', - 'applyOnForward': True, - 'doNotTrack': True - } - }) - - def del_untrack_gw_int(self): - self.delete_all("globalnetworkpolicy untrack-ingress") - - def add_untrack_gw_ext(self, order, action): - self.add_policy({ - 'apiVersion': 'projectcalico.org/v3', - 'kind': 'GlobalNetworkPolicy', - 'metadata': { - 'name': 'untrack-egress', - }, - 'spec': { - 'order': order, - 'ingress': [ - { - 'protocol': 'TCP', - 'source': { - 'ports': [80], - 'nets': [self.ext_server_ip + "/32"], - }, - 'action': action - }, - ], - 'egress': [ - { - 'protocol': 'TCP', - 'destination': { - 'ports': [80], - 'nets': [self.ext_server_ip + "/32"], - }, - 'action': action - }, - ], - 'selector': 'nodeEth == "gateway-ext"', - 'applyOnForward': True, - 'doNotTrack': True - } - }) - - def del_untrack_gw_ext(self): - self.delete_all("globalnetworkpolicy untrack-egress") - - def add_ingress_policy(self, order, action, forward): - self.add_policy({ - 'apiVersion': 'projectcalico.org/v3', - 'kind': 'GlobalNetworkPolicy', - 'metadata': { - 'name': 'port80-int-%s' % str(forward).lower(), - }, - 'spec': { - 'order': order, - 'ingress': [ - { - 'protocol': 'TCP', - 'destination': { - 'ports': [80] - }, - 'action': action - }, - ], - 'egress': [], - 'selector': 'nodeEth == "gateway-int"', - 'applyOnForward': forward - } - }) - - def add_egress_policy(self, order, action, forward): - self.add_policy({ - 'apiVersion': 'projectcalico.org/v3', - 'kind': 'GlobalNetworkPolicy', - 'metadata': { - 'name': 'port80-ext-%s' % str(forward).lower(), - }, - 'spec': { - 'order': order, - 'ingress': [], - 'egress': [ - { - 'protocol': 'TCP', - 'destination': { - 'ports': [80], - 'nets': [self.ext_server_ip + "/32"], - }, - 'action': action - }, - ], - 'selector': 'nodeEth == "gateway-ext"', - 'applyOnForward': forward - } - }) - - def add_policy(self, policy_data): - self.gateway._apply_resources(policy_data) - - def add_gateway_internal_iface(self): - host_endpoint_data = { - 'apiVersion': 'projectcalico.org/v3', - 'kind': 'HostEndpoint', - 'metadata': { - 'name': 'gw-int', - 'labels': {'nodeEth': 'gateway-int'} - }, - 'spec': { - 'node': '%s' % self.gateway_hostname, - 'interfaceName': 'eth0' - } - } - self.gateway._apply_resources(host_endpoint_data) - - def add_gateway_external_iface(self): - host_endpoint_data = { - 'apiVersion': 'projectcalico.org/v3', - 'kind': 'HostEndpoint', - 'metadata': { - 'name': 'gw-ext', - 'labels': {'nodeEth': 'gateway-ext'} - }, - 'spec': { - 'node': '%s' % self.gateway_hostname, - 'interfaceName': 'eth1' - } - } - self.gateway._apply_resources(host_endpoint_data) - - def add_host_iface(self): - host_endpoint_data = { - 'apiVersion': 'projectcalico.org/v3', - 'kind': 'HostEndpoint', - 'metadata': { - 'name': 'host-int', - 'labels': {'nodeEth': 'host'} - }, - 'spec': { - 'node': '%s' % self.host_hostname, - 'interfaceName': 'eth0', - 'expectedIPs': [str(self.host.ip)], - } - } - self.gateway._apply_resources(host_endpoint_data) - - def assert_host_can_curl_local(self): - try: - self.host.execute("curl --fail -m 1 -o /tmp/local-index.html %s" % self.gateway_int_ip) - except subprocess.CalledProcessError: - _log.exception("Internal host failed to curl gateway internal IP: %s", - self.gateway_int_ip) - self.fail("Internal host failed to curl gateway internal IP: %s" % self.gateway_int_ip) - - def assert_host_can_not_curl_local(self): - try: - self.host.execute("curl --fail -m 1 -o /tmp/local-index.html %s" % self.gateway_int_ip) - except subprocess.CalledProcessError: - return - else: - self.fail("Internal host can curl gateway internal IP: %s" % self.gateway_int_ip) - - def assert_hostwl_can_access_workload(self): - if self.host_workload.check_can_tcp(self.gateway_workload.ip, 1): - return - _log.exception("Internal host workload failed to access gateway internal workload IP: %s", - self.gateway_workload.ip) - self.fail( - "Internal host workload failed to access gateway internal workload IP: %s" % - self.gateway_workload.ip) - - def assert_hostwl_can_not_access_workload(self): - if self.host_workload.check_cant_tcp(self.gateway_workload.ip, 1): - return - _log.exception("Internal host workload can access gateway internal workload IP: %s", - self.gateway_workload.ip) - self.fail( - "Internal host workload can access gateway internal workload IP: %s" % - self.gateway_workload.ip) - - def assert_workload_can_curl_ext(self): - try: - self.gateway_workload.execute("wget -q -T 1 %s -O /dev/null" % self.ext_server_ip) - except subprocess.CalledProcessError: - _log.exception("Gateway workload failed to curl external server IP: %s", - self.ext_server_ip) - self.fail("Gateway workload failed to curl external server IP: %s" % self.ext_server_ip) - - def assert_workload_can_not_curl_ext(self): - try: - self.gateway_workload.execute("wget -q -T 1 %s -O /dev/null" % self.ext_server_ip) - except subprocess.CalledProcessError: - return - else: - self.fail("Gateway workload can curl external server IP: %s" % self.ext_server_ip) - - def assert_gateway_can_curl_ext(self): - try: - self.gateway.execute( - "curl --fail -m 1 -o /tmp/nginx-index.html %s" % self.ext_server_ip) - except subprocess.CalledProcessError: - _log.exception("Gateway failed to curl external server IP: %s", - self.ext_server_ip) - self.fail("Gateway failed to curl external server IP: %s" % self.ext_server_ip) - - def assert_gateway_can_not_curl_ext(self): - try: - self.gateway.execute( - "curl --fail -m 1 -o /tmp/nginx-index.html %s" % self.ext_server_ip) - except subprocess.CalledProcessError: - return - else: - self.fail("Gateway can curl external server IP: %s" % self.ext_server_ip) - - def assert_host_can_curl_ext(self): - try: - self.host.execute("curl --fail -m 1 -o /tmp/nginx-index.html %s" % self.ext_server_ip) - except subprocess.CalledProcessError: - _log.exception("Internal host failed to curl external server IP: %s", - self.ext_server_ip) - self.fail("Internal host failed to curl external server IP: %s" % self.ext_server_ip) - - def assert_host_can_not_curl_ext(self): - try: - self.host.execute("curl --fail -m 1 -o /tmp/nginx-index.html %s" % self.ext_server_ip) - except subprocess.CalledProcessError: - return - else: - self.fail("Internal host can curl external server IP: %s" % self.ext_server_ip) - - def remove_pol_and_endpoints(self): - self.delete_all("globalnetworkpolicy") - self.delete_all("hostEndpoint") - # Wait for felix to remove the policy and allow traffic through the gateway. - retry_until_success(self.assert_host_can_curl_ext) - - def delete_all(self, resource): - self.hosts[0].delete_all_resource(resource) - - @classmethod - def get_container_ip(cls, container_name): - ip = log_and_run( - "docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' %s" % - container_name) - return ip.strip() - - @classmethod - def set_ext_container_default_route(cls, container_name): - pid = log_and_run("docker inspect -f '{{.State.Pid}}' %s" % - container_name) - _log.info("pid is %s", pid) - log_and_run("mkdir -p /var/run/netns; " - "ln -s /proc/%s/ns/net /var/run/netns/%s; " - "ip netns exec %s ip route del default; " - "ip netns exec %s ip route add default via %s" % - (pid, pid, pid, pid, cls.gateway_ext_ip)) diff --git a/node/tests/st/policy/test_namespace.py b/node/tests/st/policy/test_namespace.py deleted file mode 100644 index fc283340c26..00000000000 --- a/node/tests/st/policy/test_namespace.py +++ /dev/null @@ -1,445 +0,0 @@ -# Copyright (c) 2017 Tigera, Inc. All rights reserved. -# -# 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. - -import json -import logging -import subprocess - -from tests.st.test_base import TestBase, HOST_IPV4 -from tests.st.utils.docker_host import DockerHost, CLUSTER_STORE_DOCKER_OPTIONS -from tests.st.utils.utils import get_ip, log_and_run, retry_until_success, \ - ETCD_CA, ETCD_CERT, ETCD_KEY, ETCD_HOSTNAME_SSL, ETCD_SCHEME, \ - handle_failure, clear_on_failures, add_on_failure, wipe_etcd - -_log = logging.getLogger(__name__) -_log.setLevel(logging.DEBUG) - -POST_DOCKER_COMMANDS = [ - "docker load -q -i /code/calico-node.tar", - "docker load -q -i /code/workload.tar", -] -NAMESPACE_PREFIX = "pcns" - -class TestNamespace(TestBase): - """ - Tests that global network policy and namespaced network policy is correctly - implemented on namespaced workload endpoints. - """ - hosts = None - - @classmethod - def setUpClass(cls): - # Wipe etcd once before any test in this class runs. - _log.debug("Wiping etcd") - wipe_etcd(HOST_IPV4) - - # We set up 2 hosts on top of which running nine workloads in three namespaces. - # Host1 has 5 workloads. - # 2 in namespace nsa: [nsa_h1_wl0] [nsa_h1_wl1] - # 1 in namespace nsb: [nsb_h1_wl0] - # 2 in default namespace: [default_h1_wl0] [omit_h1_wl0] - # *omit* means 'namespace' field is not specified during workload setup. - # - # Host2 has 4 workloads. - # 1 in namespace nsa: [nsa_h2_wl0] - # 2 in namespace nsb: [nsb_h2_wl0] [nsb_h2_wl1] - # 1 in namespace default: [default_h2_wl0] - # - # Global network policies and network policies then apply on namespaced - # workload endpoints with mixed orders. The test checks connectivity of - # 4 workloads [nsa_h1_wl0, nsb_h2_wl0, default_h1_wl0, omit_h1_wl0] from - # other workloads. - - # Create two hosts. - cls.hosts = [] - cls.host1 = DockerHost("cali-host1", - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - post_docker_commands=POST_DOCKER_COMMANDS, - start_calico=False) - cls.host1_hostname = cls.host1.execute("hostname") - cls.host2 = DockerHost("cali-host2", - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - post_docker_commands=POST_DOCKER_COMMANDS, - start_calico=False) - cls.host2_hostname = cls.host2.execute("hostname") - cls.hosts.append(cls.host1) - cls.hosts.append(cls.host2) - - # Start calico node on hosts. - for host in cls.hosts: - host.start_calico_node(env_options=" -e FELIX_HEALTHENABLED=true ") - - handle_failure(lambda: retry_until_success(cls.host1.assert_is_ready, retries=30)) - handle_failure(lambda: retry_until_success(cls.host2.assert_is_ready, retries=30)) - - # Prepare namespace profile so that we can use namespaceSelector for non-k8s deployment. - # CNI will use the existing profile which is setup here instead of creating its own. - cls.add_ns_profile('nsa') - cls.add_ns_profile('nsb') - cls.add_ns_profile('default') - - # Create calico network. - cls.calinet = cls.host1.create_network("calinet") - - # Create workloads for host1 - # For CNI, network is used for cni_name but nothing else. - # We set network to same value as namespace name to let cni program a - # namespace profile for us. - cls.nsa_wl = cls.host1.create_workload( - "nsa_h1_wl0", - image="workload", - network="nsa", - labels=["wep=nsa_h1_wl0"], - namespace="nsa") - - cls.host1.create_workload( - "nsa_h1_wl1", - image="workload", - network="nsa", - labels=["wep=nsa_h1_wl1"], - namespace="nsa") - - cls.host1.create_workload( - "nsb_h1_wl0", - image="workload", - network="nsb", - labels=["wep=nsb_h1_wl0"], - namespace="nsb") - - cls.default_wl = cls.host1.create_workload( - "default_h1_wl0", - image="workload", - network="default", - labels=["wep=default_h1_wl0"], - namespace="default") - - cls.omit_wl = cls.host1.create_workload( - "omit_h1_wl0", - image="workload", - network="default", - labels=["wep=omit_h1_wl0"], - namespace=None) - - # Create workloads for host2 - cls.nsb_wl = cls.host2.create_workload( - "nsb_h2_wl0", - image="workload", - network="nsb", - labels=["wep=nsb_h2_wl0"], - namespace="nsb") - - cls.host2.create_workload( - "nsb_h2_wl1", - image="workload", - network="nsb", - labels=["wep=nsb_h2_wl1"], - namespace="nsb") - - cls.host2.create_workload( - "nsa_h2_wl0", - image="workload", - network="nsa", - labels=["wep=nsa_h2_wl0"], - namespace="nsa") - - cls.host2.create_workload( - "default_h2_wl0", - image="workload", - network="default", - labels=["wep=default_h2_wl0"], - namespace="default") - - # Work out workload set for different namespaces. - cls.all_workloads = cls.host1.workloads.union(cls.host2.workloads) - cls.wl_nsa = filter(lambda x: x.namespace == "nsa", cls.all_workloads) - cls.wl_nsb = filter(lambda x: x.namespace == "nsb", cls.all_workloads) - cls.wl_default = filter(lambda x: x.namespace == "default" or x.namespace is None, cls.all_workloads) - - clear_on_failures() - add_on_failure(cls.host1.log_extra_diags) - add_on_failure(cls.host2.log_extra_diags) - - @handle_failure - def test_can_access_without_policy(self): - """ - Test all workload can be accessed without policy. - """ - - self.check_namespace_access(self.nsa_wl, True, True, True) - self.check_namespace_access(self.nsb_wl, True, True, True) - self.check_namespace_access(self.default_wl, True, True, True) - self.check_namespace_access(self.omit_wl, True, True, True) - - @handle_failure - def test_global_policy(self): - """ - Test global network policy with different order. - """ - self.add_global_ingress(500, 'Deny', 'default') - self.add_global_ingress(200, 'Allow', 'nsa') - self.add_global_ingress(100, 'Deny', 'nsb') - - self.check_namespace_access(self.nsa_wl, True, False, False) - self.check_namespace_access(self.nsb_wl, True, False, False) - self.check_namespace_access(self.default_wl, True, False, False) - self.check_namespace_access(self.omit_wl, True, False, False) - - @handle_failure - def test_deny_nsa(self): - """ - Test network policy for namespace nsa. - """ - self.add_global_ingress(200, 'Allow') - self.add_namespace_ingress('nsa', 100, 'Deny', 'nsb') - - self.check_namespace_access(self.nsa_wl, True, False, True) - self.check_namespace_access(self.nsb_wl, True, True, True) - self.check_namespace_access(self.default_wl, True, True, True) - self.check_namespace_access(self.omit_wl, True, True, True) - - @handle_failure - def test_deny_nsa_with_two_policy(self): - """ - Test deny network policy for namespace nsa with two orders mixed with global network policy. - """ - self.add_global_ingress(200, 'Allow') - self.add_namespace_ingress('nsa', 300, 'Deny', 'nsb') - self.add_namespace_ingress('nsa', 100, 'Deny', 'default') - - self.check_namespace_access(self.nsa_wl, True, True, False) - self.check_namespace_access(self.nsb_wl, True, True, True) - self.check_namespace_access(self.default_wl, True, True, True) - self.check_namespace_access(self.omit_wl, True, True, True) - - @handle_failure - def test_deny_default_with_two_policy(self): - """ - Test deny network policy for namespace default with two orders mixed with global network policy. - """ - self.add_global_ingress(200, 'Allow') - self.add_namespace_ingress('default', 300, 'Deny', 'nsb') - self.add_namespace_ingress('default', 100, 'Deny', 'nsa') - - self.check_namespace_access(self.nsa_wl, True, True, True) - self.check_namespace_access(self.nsb_wl, True, True, True) - self.check_namespace_access(self.default_wl, False, True, True) - self.check_namespace_access(self.omit_wl, False, True, True) - - @handle_failure - def test_allow_nsb_with_two_policy(self): - """ - Test deny network policy for namespace nsb with two orders mixed with global network policy. - """ - self.add_global_ingress(200, 'Deny') - self.add_namespace_ingress('nsb', 300, 'Allow', 'nsa') - self.add_namespace_ingress('nsb', 100, 'Allow', 'default') - - self.check_namespace_access(self.nsa_wl, False, False, False) - self.check_namespace_access(self.nsb_wl, False, False, True) - self.check_namespace_access(self.default_wl, False, False, False) - self.check_namespace_access(self.omit_wl, False, False, False) - - @handle_failure - def test_allow_default_with_two_policy(self): - """ - Test deny network policy for namespace default with two orders mixed with global network policy. - """ - self.add_global_ingress(200, 'Deny') - self.add_namespace_ingress('default', 300, 'Allow', 'nsb') - self.add_namespace_ingress('default', 100, 'Allow', 'nsa') - - self.check_namespace_access(self.nsa_wl, False, False, False) - self.check_namespace_access(self.nsb_wl, False, False, False) - self.check_namespace_access(self.default_wl, True, False, False) - self.check_namespace_access(self.omit_wl, True, False, False) - - @handle_failure - def test_mixed_deny(self): - """ - Test mixed deny network policy for namespaces mixed with global network policy. - """ - self.add_global_ingress(200, 'Allow') - self.add_namespace_ingress('nsa', 300, 'Deny', 'default') - self.add_namespace_ingress('nsa', 100, 'Deny', 'nsb') - self.add_namespace_ingress('nsb', 300, 'Deny', 'default') - self.add_namespace_ingress('nsb', 100, 'Deny', 'nsa') - self.add_namespace_ingress('default', 300, 'Deny', 'nsa') - self.add_namespace_ingress('default', 100, 'Deny', 'default') - - self.check_namespace_access(self.nsa_wl, True, False, True) - self.check_namespace_access(self.nsb_wl, False, True, True) - self.check_namespace_access(self.default_wl, True, True, False) - self.check_namespace_access(self.omit_wl, True, True, False) - - def setUp(self): - # Override the per-test setUp to avoid wiping etcd; instead only clean up the data we - # added. - self.remove_policy() - - def tearDown(self): - self.remove_policy() - super(TestNamespace, self).tearDown() - - @classmethod - def tearDownClass(cls): - cls.delete_all("profile") - - # Tidy up - for host in cls.hosts: - host.remove_workloads() - for host in cls.hosts: - host.cleanup() - del host - cls.calinet.delete() - - clear_on_failures() - - def add_namespace_ingress(self, ns, order, action, from_ns): - ns_selector = "ns_profile == '%s'" % from_ns - - self.add_policy({ - 'apiVersion': 'projectcalico.org/v3', - 'kind': 'NetworkPolicy', - 'metadata': { - 'name': '%s-%s-%s-from-%s' % (ns, order, action.lower(), from_ns), - 'namespace': ns - }, - 'spec': { - 'order': order, - 'ingress': [ - { - 'protocol': 'TCP', - 'source': { - 'namespaceSelector': ns_selector, - }, - 'action': action.capitalize(), - }, - ], - 'egress': [], - } - }) - - def add_global_ingress(self, order, action, from_ns='all'): - if from_ns != 'all': - ingress_map = { - 'source': { - 'selector': "%s.ns_profile == '%s'" % (NAMESPACE_PREFIX, from_ns) - }, - 'action': action.capitalize(), - } - else: - ingress_map = { - 'action': action.capitalize(), - } - - self.add_policy({ - 'apiVersion': 'projectcalico.org/v3', - 'kind': 'GlobalNetworkPolicy', - 'metadata': { - 'name': 'global-%s-%s-from-%s' % (order, action.lower(), from_ns), - }, - 'spec': { - 'order': order, - 'ingress': [ - ingress_map, - ], - 'egress': [], - } - }) - - @classmethod - def add_ns_profile(cls, ns): - profile_data = { - 'apiVersion': 'projectcalico.org/v3', - 'kind': 'Profile', - 'metadata': { - 'name': ns, - }, - 'spec': { - 'labelsToApply': { - '%s.ns_profile' % NAMESPACE_PREFIX: ns - }, - 'ingress': [ - { - 'action': 'Allow', - }, - ], - 'egress': [ - { - 'action': 'Allow', - }, - ], - } - } - cls.host1._apply_resources(profile_data) - - def add_policy(self, policy_data): - self.host1._apply_resources(policy_data) - - def check_namespace_access(self, target, nsa_can, nsb_can, default_can): - assert_func = { - True: self.assert_workload_can_access_workload, - False: self.assert_workload_can_not_access_workload - } - - for src in self.wl_nsa: - if not src == target: - assert_func[nsa_can](src, target) - - for src in self.wl_nsb: - if not src == target: - assert_func[nsb_can](src, target) - - for src in self.wl_default: - if not src == target: - assert_func[default_can](src, target) - - def assert_workload_can_access_workload(self, src_workload, target_workload): - _log.info("Can access test from %s to %s", src_workload.name, target_workload.name) - - if src_workload.check_can_tcp(target_workload.ip, 1): - return - _log.exception("workload %s with IP:%s failed to access workload %s on IP:%s", - src_workload.name, src_workload.ip, target_workload.name, target_workload.ip) - msg = ("workload %s with IP:%s failed to access workload %s on IP:%s" % - (src_workload.name, src_workload.ip, target_workload.name, target_workload.ip)) - - self.fail(msg) - - def assert_workload_can_not_access_workload(self, src_workload, target_workload): - _log.info("Cannot access test from %s to %s", src_workload.name, target_workload.name) - - if src_workload.check_cant_tcp(target_workload.ip, 1): - return - _log.exception("workload %s with IP:%s can access workload %s on IP:%s", - src_workload.name, src_workload.ip, target_workload.name, target_workload.ip) - msg = ("workload %s with IP:%s can access workload %s on IP:%s" % - (src_workload.name, src_workload.ip, target_workload.name, target_workload.ip)) - - self.fail(msg) - - def remove_policy(self): - self.delete_all("globalnetworkpolicy") - self.delete_all("networkpolicy --all-namespaces") - - @classmethod - def delete_all(cls, resource): - cls.hosts[0].delete_all_resource(resource) - - @classmethod - def get_container_ip(cls, container_name): - ip = log_and_run( - "docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' %s" % - container_name) - return ip.strip() diff --git a/node/tests/st/policy/test_profile.py b/node/tests/st/policy/test_profile.py deleted file mode 100644 index fdf2d8c916d..00000000000 --- a/node/tests/st/policy/test_profile.py +++ /dev/null @@ -1,478 +0,0 @@ -# Copyright 2015 Tigera, Inc -# -# 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. -import copy -import netaddr -import logging -import yaml - -from tests.st.test_base import TestBase -from tests.st.utils.docker_host import DockerHost, CLUSTER_STORE_DOCKER_OPTIONS -from tests.st.utils.utils import assert_profile, \ - assert_number_endpoints, get_profile_name - -POST_DOCKER_COMMANDS = ["docker load -q -i /code/calico-node.tar", - "docker load -q -i /code/busybox.tar", - "docker load -q -i /code/workload.tar"] - - -_log = logging.getLogger(__name__) - - -class MultiHostMainline(TestBase): - host1 = None - host2 = None - - @classmethod - def setUpClass(cls): - super(MultiHostMainline, cls).setUpClass() - cls.host1 = DockerHost("host1", - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - post_docker_commands=POST_DOCKER_COMMANDS) - cls.host2 = DockerHost("host2", - additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS, - post_docker_commands=POST_DOCKER_COMMANDS) - - @classmethod - def tearDownClass(cls): - cls.host1.cleanup() - cls.host2.cleanup() - super(MultiHostMainline, cls).tearDownClass() - - def setUp(self): - super(MultiHostMainline, self).setUp(clear_etcd=False) - host1 = self.host1 - host2 = self.host2 - - (self.n1_workloads, self.n2_workloads, self.networks) = \ - self._setup_workloads(host1, host2) - - # Get the original profiles: - output = host1.calicoctl("get profile -o yaml") - self.original_profiles = yaml.safe_load(output)['items'] - - # Filter out the default-allow profile - temp = [] - for profile in self.original_profiles: - if profile['metadata']['name'] != "projectcalico-default-allow": - temp.append(profile) - - self.original_profiles = temp - - # Make a copy of the profiles to mess about with. - self.new_profiles = copy.deepcopy(self.original_profiles) - - def tearDown(self): - # Remove the network sets, if present - self.host1.calicoctl("delete globalnetworkset netset-1", raise_exception_on_failure=False) - self.host1.calicoctl("delete globalnetworkset netset-2", raise_exception_on_failure=False) - - # Delete conntrack state for UDP connections to the workloads. - # Otherwise a new UDP connection may be allowed where it should be - # denied. - self.host1.delete_conntrack_state_to_workloads("udp") - self.host2.delete_conntrack_state_to_workloads("udp") - - # Now restore the original profile and check it all works as before - self._apply_new_profile(self.original_profiles, self.host1) - self.host1.calicoctl("get profile -o yaml") - try: - self._check_original_connectivity(self.n1_workloads, self.n2_workloads) - finally: - # Tidy up - self.host1.remove_workloads() - self.host2.remove_workloads() - for network in self.networks: - network.delete() - - super(MultiHostMainline, self).tearDown() - - def test_tags(self): - # Update profiles so that they each include each other's labelsToApply. - _log.info("Profile 0 labelsToApply = %r", self.new_profiles[0]['spec']['labelsToApply']) - _log.info("Profile 1 labelsToApply = %r", self.new_profiles[1]['spec']['labelsToApply']) - self.new_profiles[0]['spec']['labelsToApply'].update(self.new_profiles[1]['spec']['labelsToApply']) - self.new_profiles[1]['spec']['labelsToApply'].update(self.new_profiles[0]['spec']['labelsToApply']) - _log.info("Merged profile 0 labelsToApply = %r", self.new_profiles[0]['spec']['labelsToApply']) - _log.info("Merged profile 1 labelsToApply = %r", self.new_profiles[1]['spec']['labelsToApply']) - self._apply_new_profile(self.new_profiles, self.host1) - # Check everything can contact everything else now - self.assert_connectivity(retries=2, - pass_list=self.n1_workloads + self.n2_workloads) - test_tags.batchnumber = 5 - - def test_rules_protocol_icmp(self): - rule = {'action': 'Allow', - 'protocol': 'ICMP'} - # The copy.deepcopy(rule) is needed to ensure that we don't - # end up with a yaml document with a reference to the same - # rule. While this is probably legal, it isn't main line. - self.new_profiles[0]['spec']['ingress'].append(rule) - self.new_profiles[1]['spec']['ingress'].append(copy.deepcopy(rule)) - self._apply_new_profile(self.new_profiles, self.host1) - # Check everything can contact everything else now - self.assert_connectivity(retries=2, - pass_list=self.n1_workloads + self.n2_workloads, - type_list=["icmp"]) - test_rules_protocol_icmp.batchnumber = 1 - - def test_rules_ip_addr(self): - prof_n1, prof_n2 = self._get_profiles(self.new_profiles) - for workload in self.n1_workloads: - ip = workload.ip - rule = {'action': 'Allow', - 'source': - {'nets': ['%s/32' % ip]}} - prof_n2['spec']['ingress'].append(rule) - for workload in self.n2_workloads: - ip = workload.ip - rule = {'action': 'Allow', - 'source': - {'nets': ['%s/32' % ip]}} - prof_n1['spec']['ingress'].append(rule) - self._apply_new_profile(self.new_profiles, self.host1) - self.assert_connectivity(retries=2, - pass_list=self.n1_workloads + self.n2_workloads) - test_rules_ip_addr.batchnumber = 1 - - def test_rules_ip_net(self): - prof_n1, prof_n2 = self._get_profiles(self.new_profiles) - n1_ips = [workload.ip for workload in self.n1_workloads] - n2_ips = [workload.ip for workload in self.n2_workloads] - n1_subnet = netaddr.spanning_cidr(n1_ips) - n2_subnet = netaddr.spanning_cidr(n2_ips) - rule = {'action': 'Allow', - 'source': - {'nets': [str(n1_subnet)]}} - prof_n2['spec']['ingress'].append(rule) - rule = {'action': 'Allow', - 'source': - {'nets': [str(n2_subnet)]}} - prof_n1['spec']['ingress'].append(rule) - self._apply_new_profile(self.new_profiles, self.host1) - self.assert_connectivity(retries=2, - pass_list=self.n1_workloads + self.n2_workloads) - test_rules_ip_net.batchnumber = 1 - - def test_rules_source_ip_nets(self): - # Add a rule to each profile that allows traffic from all the workloads in the *other* - # network (which would normally be blocked). - prof_n1, prof_n2 = self._get_profiles(self.new_profiles) - n1_ips = [str(workload.ip) + "/32" for workload in self.n1_workloads] - n2_ips = [str(workload.ip) + "/32" for workload in self.n2_workloads] - rule = {'action': 'Allow', - 'source': {'nets': n1_ips}} - prof_n2['spec']['ingress'].append(rule) - rule = {'action': 'Allow', - 'source': {'nets': n2_ips}} - prof_n1['spec']['ingress'].append(rule) - self._apply_new_profile(self.new_profiles, self.host1) - self.assert_connectivity(retries=2, - pass_list=self.n1_workloads + self.n2_workloads) - test_rules_source_ip_nets.batchnumber = 4 - - def test_rules_source_ip_sets(self): - """Test global network sets end-to-end""" - # Add a rule to each profile that allows traffic from all the workloads in the *other* - # network by means of a network set (which would normally be blocked). - prof_n1, prof_n2 = self._get_profiles(self.new_profiles) - n1_ips = [str(workload.ip) + "/32" for workload in self.n1_workloads] - n2_ips = [str(workload.ip) + "/32" for workload in self.n2_workloads] - - netset = { - 'apiVersion': 'projectcalico.org/v3', - 'kind': 'GlobalNetworkSet', - 'metadata': { - 'name': "netset-1", - 'labels': {"group": "n1"}, - }, - 'spec': { - 'nets': n1_ips, - } - } - self.host1.writejson("netset.json", netset) - self.host1.calicoctl("create -f netset.json") - netset = { - 'apiVersion': 'projectcalico.org/v3', - 'kind': 'GlobalNetworkSet', - 'metadata': { - 'name': "netset-2", - 'labels': {"group": "n2"}, - }, - 'spec': { - 'nets': n2_ips, - } - } - self.host1.writejson("netset.json", netset) - self.host1.calicoctl("create -f netset.json") - - # Check initial connectivity before we update the rules to reference the - # network sets. - self.assert_connectivity(retries=2, - pass_list=self.n1_workloads, - fail_list=self.n2_workloads) - self.assert_connectivity(retries=2, - pass_list=self.n2_workloads, - fail_list=self.n1_workloads) - - rule = {'action': 'Allow', - 'source': {'selector': 'group=="n1"'}} - prof_n2['spec']['ingress'].append(rule) - rule = {'action': 'Allow', - 'source': {'selector': 'group=="n2"'}} - prof_n1['spec']['ingress'].append(rule) - self._apply_new_profile(self.new_profiles, self.host1) - - self.assert_connectivity(retries=2, - pass_list=self.n1_workloads + self.n2_workloads) - test_rules_source_ip_sets.batchnumber = 2 - - def test_rules_source_ip_nets_2(self): - # Adjust each profile to allow traffic from all IPs in the other group but then exclude - # one of the IPs using a notNets match. The end result is that the first workload in - # each group should be blocked but the other should be allowed. - prof_n1, prof_n2 = self._get_profiles(self.new_profiles) - n1_ips = [str(workload.ip) + "/32" for workload in self.n1_workloads] - n1_denied_ips = n1_ips[:1] - _log.info("Network 1 IPs: %s; Denied IPs: %s", n1_ips, n1_denied_ips) - - n2_ips = [str(workload.ip) + "/32" for workload in self.n2_workloads] - rule = {'action': 'Allow', - 'source': {'nets': n1_ips, - 'notNets': n1_denied_ips}} - prof_n2['spec']['ingress'].append(rule) - _log.info("Profile for network 2: %s", prof_n2) - - rule = {'action': 'Allow', - 'source': {'nets': n2_ips, - 'notNets': n2_ips[:1]}} - prof_n1['spec']['ingress'].append(rule) - self._apply_new_profile(self.new_profiles, self.host1) - - # Check first workload in each group cannot ping the other group. - self.assert_connectivity(retries=2, - pass_list=self.n1_workloads[:1], - fail_list=self.n2_workloads) - self.assert_connectivity(retries=2, - pass_list=self.n2_workloads[:1], - fail_list=self.n1_workloads) - - # Check non-excluded workloads can all ping each other. - self.assert_connectivity(retries=2, - pass_list=self.n1_workloads[1:] + self.n2_workloads[1:]) - self.assert_connectivity(retries=2, - pass_list=self.n2_workloads[1:] + self.n1_workloads[1:]) - test_rules_source_ip_nets_2.batchnumber = 4 - - def test_rules_dest_ip_nets(self): - # Adjust the egress policies to drop all traffic - prof_n1, prof_n2 = self._get_profiles(self.new_profiles) - prof_n2['spec']['egress'] = [] - prof_n1['spec']['egress'] = [] - self._apply_new_profile(self.new_profiles, self.host1) - self.assert_connectivity(retries=2, - pass_list=self.n2_workloads[:1], - fail_list=self.n1_workloads + self.n2_workloads[1:]) - - # Add a destination allow to n2 that allows pods within it to reach other pods in n2. - n2_ips = [str(workload.ip) + "/32" for workload in self.n2_workloads] - rule = {'action': 'Allow', - 'destination': {'nets': n2_ips}} - prof_n2['spec']['egress'] = [rule] - self._apply_new_profile(self.new_profiles, self.host1) - self.assert_connectivity(retries=2, - pass_list=self.n2_workloads, - fail_list=self.n1_workloads) - - # Add some rules that have a single nets entry and multiple notNets entries. These are - # rendered a bit differently in Felix. - n1_ips = [str(workload.ip) + "/32" for workload in self.n1_workloads] - rule1 = {'action': 'Allow', - 'destination': {'nets': n1_ips[0:1], - 'notNets': n1_ips[1:]}} - rule2 = {'action': 'Allow', - 'destination': {'nets': n1_ips[1:2], - 'notNets': n1_ips[:1]}} - prof_n1['spec']['egress'] = [rule1, rule2] - self._apply_new_profile(self.new_profiles, self.host1) - self.assert_connectivity(retries=2, - pass_list=self.n1_workloads[:2], - fail_list=self.n1_workloads[2:]) - test_rules_dest_ip_nets.batchnumber = 5 - - def test_rules_selector(self): - self.new_profiles[0]['spec']['labelsToApply']['net'] = 'n1' - self.new_profiles[1]['spec']['labelsToApply']['net'] = 'n2' - rule = {'action': 'Allow', - 'source': - {'selector': 'net=="n2"'}} - self.new_profiles[0]['spec']['ingress'].append(rule) - rule = {'action': 'Allow', - 'source': - {'selector': "net=='n1'"}} - self.new_profiles[1]['spec']['ingress'].append(rule) - self._apply_new_profile(self.new_profiles, self.host1) - self.assert_connectivity(retries=2, - pass_list=self.n1_workloads + self.n2_workloads) - test_rules_selector.batchnumber = 5 - - def test_rules_tcp_port(self): - rule = {'action': 'Allow', - 'protocol': 'TCP', - 'destination': - {'ports': [80]}} - # The copy.deepcopy(rule) is needed to ensure that we don't - # end up with a yaml document with a reference to the same - # rule. While this is probably legal, it isn't main line. - self.new_profiles[0]['spec']['ingress'].append(rule) - self.new_profiles[1]['spec']['ingress'].append(copy.deepcopy(rule)) - self._apply_new_profile(self.new_profiles, self.host1) - self.assert_connectivity(retries=2, - pass_list=self.n1_workloads + self.n2_workloads, - type_list=['tcp']) - self.assert_connectivity(retries=2, - pass_list=self.n1_workloads, - fail_list=self.n2_workloads, - type_list=['icmp', 'udp']) - test_rules_tcp_port.batchnumber = 5 - - def test_rules_udp_port(self): - rule = {'action': 'Allow', - 'protocol': 'UDP', - 'destination': - {'ports': [69]}} - # The copy.deepcopy(rule) is needed to ensure that we don't - # end up with a yaml document with a reference to the same - # rule. While this is probably legal, it isn't main line. - self.new_profiles[0]['spec']['ingress'].append(rule) - self.new_profiles[1]['spec']['ingress'].append(copy.deepcopy(rule)) - self._apply_new_profile(self.new_profiles, self.host1) - self.assert_connectivity(retries=2, - pass_list=self.n1_workloads + self.n2_workloads, - type_list=['udp']) - self.assert_connectivity(retries=2, - pass_list=self.n1_workloads, - fail_list=self.n2_workloads, - type_list=['icmp', 'tcp']) - test_rules_udp_port.batchnumber = 5 - - @staticmethod - def _get_profiles(profiles): - """ - Sorts and returns the profiles for the networks. - :param profiles: the list of profiles - :return: tuple: profile for network1, profile for network2 - """ - prof_n1 = None - prof_n2 = None - for profile in profiles: - if profile['metadata']['name'] == "testnet1": - prof_n1 = profile - elif profile['metadata']['name'] == "testnet2": - prof_n2 = profile - assert prof_n1 is not None, "Could not find testnet1 profile" - assert prof_n2 is not None, "Could not find testnet2 profile" - return prof_n1, prof_n2 - - @staticmethod - def _apply_new_profile(new_profiles, host): - # Get profiles now, so we have up to date resource versions. - output = host.calicoctl("get profile -o yaml") - profiles_now = yaml.safe_load(output)['items'] - resource_version_map = { - p['metadata']['name']: p['metadata']['resourceVersion'] - for p in profiles_now - } - _log.info("resource_version_map = %r", resource_version_map) - - # Set current resource versions in the profiles we are about to apply. - for p in new_profiles: - p['metadata']['resourceVersion'] = resource_version_map[p['metadata']['name']] - if 'creationTimestamp' in p['metadata']: - del p['metadata']['creationTimestamp'] - - # Apply new profiles - host.writejson("new_profiles", new_profiles) - host.calicoctl("apply -f new_profiles") - - def _setup_workloads(self, host1, host2): - # Create the networks on host1, but they should be usable from all - # hosts. - network1 = host1.create_network("testnet1") - network2 = host1.create_network("testnet2") - networks = [network1, network2] - - n1_workloads = [] - n2_workloads = [] - - # Create two workloads on host1 and one on host2 all in network 1. - n1_workloads.append(host2.create_workload("workload_h2n1_1", - image="workload", - network=network1)) - n1_workloads.append(host1.create_workload("workload_h1n1_1", - image="workload", - network=network1)) - n1_workloads.append(host1.create_workload("workload_h1n1_2", - image="workload", - network=network1)) - - # Create similar workloads in network 2. - n2_workloads.append(host1.create_workload("workload_h1n2_1", - image="workload", - network=network2)) - n2_workloads.append(host1.create_workload("workload_h1n2_2", - image="workload", - network=network2)) - n2_workloads.append(host2.create_workload("workload_h2n2_1", - image="workload", - network=network2)) - print "*******************" - print "Network1 is:\n%s\n%s" % ( - [x.ip for x in n1_workloads], - [x.name for x in n1_workloads]) - print "Network2 is:\n%s\n%s" % ( - [x.ip for x in n2_workloads], - [x.name for x in n2_workloads]) - print "*******************" - - # Assert that endpoints are in Calico - assert_number_endpoints(host1, 4) - assert_number_endpoints(host2, 2) - - try: - self._check_original_connectivity(n1_workloads, n2_workloads) - except Exception as e: - _log.exception(e) - host1.log_extra_diags() - host2.log_extra_diags() - raise - - return n1_workloads, n2_workloads, networks - - def _check_original_connectivity(self, n1_workloads, n2_workloads, - types=None): - # Assert that workloads can communicate with each other on network - # 1, and not those on network 2. Ping using IP for all workloads, - # and by hostname for workloads on the same network (note that - # a workloads own hostname does not work). - if types is None: - types = ['icmp', 'tcp', 'udp'] - self.assert_connectivity(retries=10, - pass_list=n1_workloads, - fail_list=n2_workloads, - type_list=types) - - # Repeat with network 2. - self.assert_connectivity(pass_list=n2_workloads, - fail_list=n1_workloads, - type_list=types) diff --git a/node/tests/st/policy/test_tiered_policy.py b/node/tests/st/policy/test_tiered_policy.py deleted file mode 100644 index 6844e821518..00000000000 --- a/node/tests/st/policy/test_tiered_policy.py +++ /dev/null @@ -1,927 +0,0 @@ -# Copyright 2024 Tigera, Inc - -import copy -import functools -import json -import logging -import subprocess -import time -import yaml -from nose_parameterized import parameterized -from multiprocessing.dummy import Pool - -from tests.st.test_base import TestBase, HOST_IPV4 -from tests.st.utils.docker_host import DockerHost -from tests.st.utils.utils import assert_number_endpoints, get_ip, \ - ETCD_CA, ETCD_CERT, ETCD_KEY, ETCD_HOSTNAME_SSL, ETCD_SCHEME, \ - wipe_etcd - -_log = logging.getLogger(__name__) -_log.setLevel(logging.DEBUG) - -POST_DOCKER_COMMANDS = ["docker load -i /code/calico-node.tar", - "docker load -i /code/busybox.tar", - "docker load -i /code/workload.tar"] - -if ETCD_SCHEME == "https": - ADDITIONAL_DOCKER_OPTIONS = "--cluster-store=etcd://%s:2379 " \ - "--cluster-store-opt kv.cacertfile=%s " \ - "--cluster-store-opt kv.certfile=%s " \ - "--cluster-store-opt kv.keyfile=%s " % \ - (ETCD_HOSTNAME_SSL, ETCD_CA, ETCD_CERT, - ETCD_KEY) -else: - ADDITIONAL_DOCKER_OPTIONS = "--cluster-store=etcd://%s:2379 " % \ - get_ip() - - -def parallel_host_setup(num_hosts): - makehost = functools.partial(DockerHost, - additional_docker_options=ADDITIONAL_DOCKER_OPTIONS, - post_docker_commands=POST_DOCKER_COMMANDS, - start_calico=False) - hostnames = [] - for i in range(num_hosts): - hostnames.append("host%s" % i) - pool = Pool(num_hosts) - hosts = pool.map(makehost, hostnames) - pool.close() - pool.join() - return hosts - - -gnp_next_all = { - "apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "gnp_next_all"}, - "spec": { - "order": 10, - "ingress": [{"action": "Pass"}], - "egress": [{"action": "Pass"}] - } -} - -gnp_allow_all = { - "apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "gnp_allow_all"}, - "spec": { - "order": 10, - "ingress": [{"action": "Allow"}], - "egress": [{"action": "Allow"}] - } -} - -gnp_deny_all = { - "apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "gnp_deny_all"}, - "spec": { - "order": 10, - "ingress": [{"action": "Deny"}], - "egress": [{"action": "Deny"}]} -} -gnp_none_all = { - "apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "gnp_none_all"}, - "spec": { - "selector": "all()", - "order": 10, - "ingress": [], - "egress": []} -} - - -class TieredPolicyWorkloads(TestBase): - def setUp(self): - _log.debug("Override the TestBase setUp() method which wipes etcd. Do nothing.") - # Wipe policies and tiers before each test - self.delete_all("gnp") - self.delete_all("tier") - - def delete_all(self, resource): - # Grab all objects of a resource type - objects = yaml.load(self.hosts[0].calicoctl("get %s -o yaml" % resource)) - # and delete them (if there are any) - if len(objects) > 0: - _log.info("objects: %s", objects) - if 'items' in objects: - # Filter out object(s) representing the default tier. - objects['items'] = [x for x in objects['items'] - if (x.get('kind', '') != 'Tier' or - 'metadata' not in x or - x['metadata'].get('name', '') not in ['default', 'adminnetworkpolicy'])] - if 'items' in objects and len(objects['items']) == 0: - pass - else: - self._delete_data(objects, self.hosts[0]) - - @staticmethod - def sleep(length): - _log.debug("Sleeping for %s" % length) - time.sleep(length) - - @classmethod - def setUpClass(cls): - _log.debug("Wiping etcd") - wipe_etcd(HOST_IPV4) - - cls.policy_tier_name = "default" - cls.next_tier_allowed = False - cls.hosts = [] - cls.hosts.append(DockerHost("host1", - additional_docker_options=ADDITIONAL_DOCKER_OPTIONS, - post_docker_commands=POST_DOCKER_COMMANDS, - start_calico=False)) - cls.hosts.append(DockerHost("host2", - additional_docker_options=ADDITIONAL_DOCKER_OPTIONS, - post_docker_commands=POST_DOCKER_COMMANDS, - start_calico=False)) - - for host in cls.hosts: - host.start_calico_node() - # Allow time for calico-node to load - time.sleep(10) - - cls.networks = [] - cls.networks.append(cls.hosts[0].create_network("testnet2")) - cls.sleep(10) - - cls.n1_workloads = [] - # Create two workloads on cls.hosts[0] and one on cls.hosts[1] all in network 1. - cls.n1_workloads.append(cls.hosts[1].create_workload("workload_h2n1_1", - image="workload", - network=cls.networks[0])) - cls.sleep(2) - cls.n1_workloads.append(cls.hosts[0].create_workload("workload_h1n1_1", - image="workload", - network=cls.networks[0])) - # Assert that endpoints are in Calico - assert_number_endpoints(cls.hosts[0], 1) - assert_number_endpoints(cls.hosts[1], 1) - - @classmethod - def tearDownClass(cls): - # Tidy up - for host in cls.hosts: - host.remove_workloads() - for network in cls.networks: - network.delete() - for host in cls.hosts: - host.cleanup() - del host - - def test_tier_ordering_explicit(self): - """Check correct ordering of tiers by their explicit order field.""" - self.policy_tier_name = "the-tier" - self.next_tier_allowed = True - self._do_tier_order_test("tier-c", 1, - "tier-b", 2, - "tier-a", 3) - - def test_tier_ordering_implicit(self): - """Check correct ordering of tiers by name as tie-breaker.""" - self.policy_tier_name = "the-tier" - self.next_tier_allowed = True - self._do_tier_order_test("tier-1", 1, - "tier-2", 1, - "tier-3", 1) - - def set_tier(self, name=None, order=10): - _log.debug("Setting tier data: \n" - "name : %s\norder : %s", - name, order) - if name is None: - name = self.policy_tier_name - tier = {"apiVersion": "projectcalico.org/v3", - "kind": "Tier", - "metadata": {"name": name}, - "spec": {"order": order} - } - self._apply_data(tier, self.hosts[0]) - - def _do_tier_order_test(self, first_tier, first_tier_order, second_tier, - second_tier_order, third_tier, third_tier_order): - # Note that the following tests need to check that connectivity to - # the rpcbind service alternates between succeeding and failing so - # that we spot if felix hasn't actually changed anything. - # Check we start with connectivity. - _log.info("Starting tier order test %s (%s); %s (%s); %s (%s)", - first_tier, first_tier_order, second_tier, - second_tier_order, third_tier, third_tier_order) - - self.assert_connectivity(self.n1_workloads) - - # Create tiers and endpoints. - _log.info("Configuring tiers.") - self.set_tier(name=first_tier, order=first_tier_order) - self.set_tier(name=second_tier, order=second_tier_order) - self.set_tier(name=third_tier, order=third_tier_order) - - # Slip a deny into the third tier, just to alternate DENY/ALLOW. - self.set_policy(third_tier, "pol-1", gnp_deny_all) - # Check that access is blocked. - self.assert_no_connectivity(self.n1_workloads) - - # Allow in first tier only, should allow. - _log.info("Allow in first tier only, should allow.") - self.set_policy(first_tier, "pol-1", gnp_allow_all) - self.set_policy(second_tier, "pol-1", gnp_deny_all) - self.set_policy(third_tier, "pol-1", gnp_deny_all) - self.assert_connectivity(self.n1_workloads) - - # Deny in all tiers, should drop. - _log.info("Deny in all tiers, should drop.") - # Fix up second tier - self.set_policy(second_tier, "pol-1", gnp_deny_all) - self.set_policy(first_tier, "pol-1", gnp_deny_all) - self.assert_no_connectivity(self.n1_workloads) - - # Allow in first tier, should allow. - self.set_policy(first_tier, "pol-1", gnp_allow_all) - self.assert_connectivity(self.n1_workloads) - - # Switch, now the first tier drops but the later ones allow. - _log.info("Switch, now the first tier drops but the later ones " - "allow.") - self.set_policy(first_tier, "pol-1", gnp_deny_all) - self.set_policy(second_tier, "pol-1", gnp_allow_all) - self.set_policy(third_tier, "pol-1", gnp_allow_all) - self.assert_no_connectivity(self.n1_workloads) - - # Fall through via a next-tier policy in the first tier. - _log.info("Fall through via a next-tier policy in the first " - "tier.") - self.set_policy(first_tier, "pol-1", gnp_next_all) - self.assert_connectivity(self.n1_workloads) - - # Swap the second tier for a drop. - _log.info("Swap the second tier for a drop.") - self.set_policy(second_tier, "pol-1", gnp_deny_all) - self.assert_no_connectivity(self.n1_workloads) - - def _apply_data(self, data, host): - _log.debug("Applying data with calicoctl: %s", data) - self._use_calicoctl("apply", data, host) - - def _delete_data(self, data, host): - _log.debug("Deleting data with calicoctl: %s", data) - self._use_calicoctl("delete", data, host) - - @staticmethod - def _use_calicoctl(action, data, host): - # Delete creationTimestamp fields from the data that we're going to - # write. - _log.debug("Use calicoctl: %s", data) - if type(data) == list: - for d in data: - for obj in d.get('items', []): - if 'creationTimestamp' in obj['metadata']: - del obj['metadata']['creationTimestamp'] - if 'metadata' in data and 'creationTimestamp' in data['metadata']: - del data['metadata']['creationTimestamp'] - else: - for obj in data.get('items', []): - if 'creationTimestamp' in obj['metadata']: - del obj['metadata']['creationTimestamp'] - if 'metadata' in data and 'creationTimestamp' in data['metadata']: - del data['metadata']['creationTimestamp'] - - # use calicoctl with data - host.writefile("new_data", - yaml.dump(data, default_flow_style=False)) - host.calicoctl("%s -f new_data" % action) - - def set_policy(self, tier, policy_name, data, order=None): - data = copy.deepcopy(data) - if order is not None: - data["spec"]["order"] = order - - if not self.next_tier_allowed: - for dirn in ["ingress", "egress"]: - if dirn in data: - def f(rule): - return rule != {"action": "Pass"} - data[dirn] = filter(f, data[dirn]) - - data["metadata"]["name"] = policy_name - if tier != "default": - data["spec"]["tier"] = tier - data["metadata"]["name"] = "{tier}.{policy}".format(tier=tier, - policy=data["metadata"]["name"]) - elif tier == "default": - # TODO(doublek): This elif can be removed when proper tier - # validation has been added. - data["spec"]["tier"] = "default" - data["metadata"]["name"] = "default.{policy}".format(policy=data["metadata"]["name"]) - - self._apply_data(data, self.hosts[0]) - - def assert_no_connectivity(self, workload_list, retries=0, type_list=None): - """ - Checks that none of the workloads passed in can contact any of the others. - :param workload_list: - :param retries: - :param type_list: - :return: - """ - for workload in workload_list: - the_rest = [wl for wl in workload_list if wl is not workload] - self.assert_connectivity([workload], fail_list=the_rest, - retries=retries, type_list=type_list) - - def test_policy_ordering_explicit(self): - """Check correct ordering of policies by their explicit order - field.""" - self.policy_tier_name = "default" - self.next_tier_allowed = False - self._do_policy_order_test("pol-c", 1, - "pol-b", 2, - "pol-a", 3) - - def test_policy_ordering_implicit(self): - """Check correct ordering of policies by name as tie-breaker.""" - self.policy_tier_name = "default" - self.next_tier_allowed = False - self._do_policy_order_test("pol-1", 1, - "pol-2", 1, - "pol-3", 1) - - def _do_policy_order_test(self, - first_pol, first_pol_order, - second_pol, second_pol_order, - third_pol, third_pol_order): - """Checks that policies are ordered correctly.""" - # Note that the following tests need to check that connectivity to - # the rpcbind service alternates between succeeding and failing so - # that we spot if felix hasn't actually changed anything. - - _log.info("Check we start with connectivity.") - self.assert_connectivity(self.n1_workloads) - _log.info("Apply a single deny policy") - self.set_policy(self.policy_tier_name, first_pol, gnp_deny_all, - order=first_pol_order) - _log.info("Check we now cannot access tcp service") - self.assert_no_connectivity(self.n1_workloads) - _log.info("Allow in first tier only, should allow.") - self.set_policy(self.policy_tier_name, first_pol, gnp_allow_all, - order=first_pol_order) - self.set_policy(self.policy_tier_name, second_pol, gnp_deny_all, - order=second_pol_order) - self.set_policy(self.policy_tier_name, third_pol, gnp_deny_all, - order=third_pol_order) - self.assert_connectivity(self.n1_workloads) - - # Fix up second tier - self.set_policy(self.policy_tier_name, second_pol, gnp_deny_all, - order=second_pol_order) - - # Deny in all tiers, should drop. - _log.info("Deny in all tiers, should drop.") - self.set_policy(self.policy_tier_name, first_pol, gnp_deny_all, - order=first_pol_order) - self.assert_no_connectivity(self.n1_workloads) - - # Allow in first tier, should allow. - _log.info("Allow in first tier, should allow.") - self.set_policy(self.policy_tier_name, first_pol, gnp_allow_all, - order=first_pol_order) - self.assert_connectivity(self.n1_workloads) - - # Switch, now the first policy drops but the later ones allow. - _log.info("Switch, now the first tier drops but the later ones " - "allow.") - self.set_policy(self.policy_tier_name, first_pol, gnp_deny_all, - order=first_pol_order) - self.set_policy(self.policy_tier_name, second_pol, gnp_allow_all, - order=second_pol_order) - self.set_policy(self.policy_tier_name, third_pol, gnp_allow_all, - order=third_pol_order) - self.assert_no_connectivity(self.n1_workloads) - - # Fall through to second policy. - _log.info("Fall through to second policy.") - self.set_policy(self.policy_tier_name, first_pol, gnp_none_all, - order=first_pol_order) - self.assert_connectivity(self.n1_workloads) - - # Swap the second tier for a drop. - _log.info("Swap the second tier for a drop.") - self.set_policy(self.policy_tier_name, second_pol, gnp_deny_all, - order=second_pol_order) - self.assert_no_connectivity(self.n1_workloads) - - @parameterized.expand([ - ({"apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "default.deny-test-true1"}, - "spec": { - "tier": "default", - "ingress": [{ - "action": "Deny", - "source": {"selector": "test == 'True'"}, - }, - {"action": "Allow"} - ], - "egress": [ - {"action": "Deny", - "destination": {"selector": "test == 'True'"}}, - {"action": "Allow"} - ]}, - }, - {"test": "True"}, - True - ), - - ({"apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "default.deny-test-true2"}, - "spec": { - "tier": "default", - "ingress": [{ - "action": "Deny", - "source": {"selector": "test != 'True'"}, - }, - {"action": "Allow"} - ], - "egress": [ - {"action": "Deny", - "destination": {"selector": "test != 'True'"}}, - {"action": "Allow"} - ]}, - }, - {"test": "False"}, - False - ), - - ({"apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "default.deny-test-true3"}, - "spec": { - "tier": "default", - "ingress": [{ - "action": "Deny", - "source": {"selector": "has(test)"}, - }, - {"action": "Allow"} - ], - "egress": [ - {"action": "Deny", - "destination": {"selector": "has(test)"}}, - {"action": "Allow"} - ]}, - }, - {"test": "any_old_value"}, - True - ), - - ({"apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "default.deny-test-true4"}, - "spec": { - "tier": "default", - "ingress": [{ - "action": "Deny", - "source": {"selector": "!has(test)"}, - }, - {"action": "Allow"} - ], - "egress": [ - {"action": "Deny", - "destination": {"selector": "!has(test)"}}, - {"action": "Allow"} - ]}, - }, - {"test": "no_one_cares"}, - False - ), - - ({"apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "default.deny-test-true5"}, - "spec": { - "tier": "default", - "ingress": [{ - "action": "Deny", - "source": {"selector": "test in {'true', 'false'}"}, - }, - {"action": "Allow"} - ], - "egress": [ - {"action": "Deny", - "destination": {"selector": "test in {'true', 'false'}"}}, - {"action": "Allow"} - ]}, - }, - {"test": "true"}, - True - ), - - ({"apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "default.deny-test-true6"}, - "spec": { - "tier": "default", - "ingress": [{ - "action": "Deny", - "source": {"selector": "test in {'true', 'false'}"}, - }, - {"action": "Allow"} - ], - "egress": [ - {"action": "Deny", - "destination": {"selector": "test in {'true', 'false'}"}}, - {"action": "Allow"} - ]}, - }, - {"test": "false"}, - True - ), - - ({"apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "default.deny-test-true7"}, - "spec": { - "tier": "default", - "ingress": [{ - "action": "Deny", - "source": {"selector": "test not in {'true', 'false'}"}, - }, - {"action": "Allow"} - ], - "egress": [ - {"action": "Deny", - "destination": {"selector": "test not in {'true', 'false'}"}}, - {"action": "Allow"} - ]}, - }, - {"test": "neither"}, - False - ), - - ([{"apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "default.deny-test-true8a"}, - "spec": - { - "tier": "default", - "selector": "test == 'true'", - "ingress": [ - {"action": "Deny"}, - ], - "egress": [ - {"action": "Deny"}, - ] - } - }, - {"apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "default.deny-test-true8b"}, - "spec": - { - "tier": "default", - "ingress": [ - {"action": "Allow"}, - ], - "egress": [ - {"action": "Allow"}, - ] - } - } - ], - {"test": "true"}, - True - ), - - ([{"apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "default.deny-test-true9a"}, - "spec": - { - "tier": "default", - "selector": "test != 'true'", - "ingress": [ - {"action": "Deny"}, - ], - "egress": [ - {"action": "Deny"}, - ] - } - }, - {"apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "default.deny-test-true9b"}, - "spec": - { - "tier": "default", - "ingress": [ - {"action": "Allow"}, - ], - "egress": [ - {"action": "Allow"}, - ] - } - } - ], - {"test": "true"}, - False - ), - - ([{"apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "default.deny-test-true10a"}, - "spec": - { - "tier": "default", - "selector": "has(test)", - "ingress": [ - {"action": "Deny"}, - ], - "egress": [ - {"action": "Deny"}, - ] - } - }, - {"apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "default.deny-test-true10b"}, - "spec": - { - "tier": "default", - "ingress": [ - {"action": "Allow"}, - ], - "egress": [ - {"action": "Allow"}, - ] - } - } - ], - {"test": "true"}, - True - ), - - ([{"apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "default.deny-test-true11a"}, - "spec": - { - "tier": "default", - "selector": "!has(test)", - "ingress": [ - {"action": "Deny"}, - ], - "egress": [ - {"action": "Deny"}, - ] - } - }, - {"apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "default.deny-test-true11b"}, - "spec": - { - "tier": "default", - "ingress": [ - {"action": "Allow"}, - ], - "egress": [ - {"action": "Allow"}, - ] - } - } - ], - {"test": "true"}, - False - ), - - ([{"apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "default.deny-test-true12a"}, - "spec": - { - "tier": "default", - "selector": "test in {'true', 'false'}", - "ingress": [ - {"action": "Deny"}, - ], - "egress": [ - {"action": "Deny"}, - ] - } - }, - {"apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "default.deny-test-true12b"}, - "spec": - { - "tier": "default", - "ingress": [ - {"action": "Allow"}, - ], - "egress": [ - {"action": "Allow"}, - ] - } - } - ], - {"test": "true"}, - True - ), - - ([{"apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "default.deny-test-true13a"}, - "spec": - { - "tier": "default", - "selector": "test in {'true', 'false'}", - "ingress": [ - {"action": "Deny"}, - ], - "egress": [ - {"action": "Deny"}, - ] - } - }, - {"apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "default.deny-test-true13b"}, - "spec": - { - "tier": "default", - "ingress": [ - {"action": "Allow"}, - ], - "egress": [ - {"action": "Allow"}, - ] - } - } - ], - {"test": "false"}, - True - ), - - ([{"apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "default.deny-test-true14a"}, - "spec": - { - "tier": "default", - "selector": "test not in {'true', 'false'}", - "ingress": [ - {"action": "Deny"}, - ], - "egress": [ - {"action": "Deny"}, - ] - } - }, - {"apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "default.deny-test-true14b"}, - "spec": - { - "tier": "default", - "ingress": [ - {"action": "Allow"}, - ], - "egress": [ - {"action": "Allow"}, - ] - } - } - ], - {"test": "neither"}, - False - ), - - ([{"apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "default.deny-test-true15a"}, - "spec": - { - "tier": "default", - "selector": "has(test) && test in {'true', 'false'} && test == 'true'", - "ingress": [ - {"action": "Deny"}, - ], - "egress": [ - {"action": "Deny"}, - ] - } - }, - {"apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "default.deny-test-true15b"}, - "spec": - { - "tier": "default", - "ingress": [ - {"action": "Allow"}, - ], - "egress": [ - {"action": "Allow"}, - ] - } - } - ], - {"test": "true"}, - True - ), - - ({"apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "default.deny-test-true16"}, - "spec": { - "tier": "default", - "ingress": [{ - "action": "Deny", - "source": - {"selector": "has(test) && test in {'True', 'False'} && test == 'True'"}, - }, - {"action": "Allow"} - ], - "egress": [ - {"action": "Deny", - "destination": - {"selector": "has(test) && test in {'True', 'False'} && test == 'True'"}}, - {"action": "Allow"} - ]}, - }, - {"test": "True"}, - True - ), - - ({"apiVersion": "projectcalico.org/v3", - "kind": "GlobalNetworkPolicy", - "metadata": {"name": "default.deny-test-true17"}, - "spec": { - "tier": "default", - "ingress": [{ - "action": "Deny", - "source": - {"selector": - "has(test) && test not in {'True', 'False'} && test == 'Sausage'"}, - }, - {"action": "Allow"} - ], - "egress": [ - {"action": "Deny", - "destination": - {"selector": - "has(test) && test not in {'True', 'False'} && test == 'Sausage'"}}, - {"action": "Allow"} - ]}, - }, - {"test": "Sausage"}, - True - ), - ]) - def test_selectors(self, policy, workload_label, no_label_expected_result): - """ - Tests selectors in policy. - :param policy: The policy to apply - :param workload_label: The label to add to one of the workload endpoints - :param no_label_expected_result: Whether we'd expect the policy to block connectivity if - the workloads do not have the label. - :return: - """ - # set workload config - host = self.hosts[0] - weps = yaml.safe_load(host.calicoctl("get wep -o yaml")) - _log.info("n1_workloads 0 %s", self.n1_workloads[0].__dict__) - _log.info("n1_workloads 1 %s", self.n1_workloads[1].__dict__) - wep_old = weps['items'][0] - #for w in weps['items']: - # if w['spec']['ipNetworks'][0] == self.n1_workloads[1].ip + '/32': - # _log.info("Set wep_old to %s", w) - # wep_old = w - wep = copy.deepcopy(wep_old) - _log.info("Set new label %s", workload_label) - wep['metadata']['labels'] = workload_label - self._apply_data(wep, host) - updated_weps = yaml.safe_load(host.calicoctl("get workloadEndpoint -o yaml")) - # check connectivity OK - self.assert_connectivity(self.n1_workloads) - # set up policy - _log.info("Set up policy %s", policy) - self._apply_data(policy, host) - # check connectivity not OK - use retries to allow time to apply new policy - self.assert_no_connectivity(self.n1_workloads, retries=3) - # Restore workload config (i.e. remove the label) - wep_old['metadata']['resourceVersion'] = updated_weps['items'][0]['metadata']['resourceVersion'] - _log.info("Restore workload config %s", wep_old) - self._apply_data(wep_old, host) - updated_weps = yaml.safe_load(host.calicoctl("get workloadEndpoint -o yaml")) - if no_label_expected_result: - # check connectivity OK again - use retries to allow time to apply new policy - self.assert_connectivity(self.n1_workloads, retries=3) - else: - self.assert_no_connectivity(self.n1_workloads) - - -class IpNotFound(Exception): - pass diff --git a/node/tests/st/test_base.py b/node/tests/st/test_base.py deleted file mode 100644 index de8dc4b9199..00000000000 --- a/node/tests/st/test_base.py +++ /dev/null @@ -1,343 +0,0 @@ -# Copyright (c) 2015-2016 Tigera, Inc. All rights reserved. -# -# 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. -import hashlib -import json -import logging -import subprocess -import time -import types -from multiprocessing.dummy import Pool as ThreadPool -from pprint import pformat -from unittest import TestCase - -import yaml -from deepdiff import DeepDiff - -from tests.st.utils.utils import (get_ip, ETCD_SCHEME, ETCD_CA, ETCD_CERT, - ETCD_KEY, debug_failures, ETCD_HOSTNAME_SSL, - wipe_etcd, clear_on_failures) - -# The number of test batches used in CI. -NUM_BATCHES = 6 - -HOST_IPV6 = get_ip(v6=True) -HOST_IPV4 = get_ip() - -logging.basicConfig(level=logging.DEBUG, format="%(message)s") -logger = logging.getLogger(__name__) - -# Disable spammy logging from the sh module -sh_logger = logging.getLogger("sh") -sh_logger.setLevel(level=logging.CRITICAL) - -first_log_time = None - - -def calculate_batch(cname, mname): - combined = cname + "." + mname - m = hashlib.sha224() - m.update(combined) - batch = ord(m.digest()[0]) % NUM_BATCHES - print "Assigned %s to batch %s" % (combined, batch) - return batch - - -class AutoBatcher(type): - """ - AutoBatcher is a metaclass that makes sure every test_ method has a batchnumber. - - Batch numbers are assigned deterministically using a hash of class name and - method name. - """ - - def __init__(cls, name, bases, dct): - test_methods_with_no_batch = {} - has_batch = False - for k, v in dct.iteritems(): - if k == "batchnumber": - has_batch = True - elif k.startswith("test_"): - if not isinstance(v, types.FunctionType): - continue - if hasattr(v, "batchnumber"): - continue - test_methods_with_no_batch[k] = v - if not has_batch: - for k, v in test_methods_with_no_batch.iteritems(): - v.batchnumber = calculate_batch(name, k) - dct["batchnumber"] = calculate_batch(name, "__class__") - super(AutoBatcher, cls).__init__(name, bases, dct) - - -class TestBase(TestCase): - __metaclass__ = AutoBatcher - - """ - Base class for test-wide methods. - """ - @classmethod - def setUpClass(cls): - wipe_etcd(HOST_IPV4) - - def setUp(self, clear_etcd=True): - """ - Clean up before every test. - """ - self.ip = HOST_IPV4 - - if clear_etcd: - wipe_etcd(self.ip) - - # Log a newline to ensure that the first log appears on its own line. - logger.info("") - - clear_on_failures() - - @staticmethod - def _conn_checker(args): - source, dest, test_type, result, retries = args - if test_type == 'icmp': - if result: - return source.check_can_ping(dest, retries) - else: - return source.check_cant_ping(dest, retries) - elif test_type == 'tcp': - if result: - return source.check_can_tcp(dest, retries) - else: - return source.check_cant_tcp(dest, retries) - elif test_type == 'udp': - if result: - return source.check_can_udp(dest, retries) - else: - return source.check_cant_udp(dest, retries) - else: - logger.error("Unrecognised connectivity check test_type") - - @debug_failures - def assert_connectivity(self, pass_list, fail_list=None, retries=0, - type_list=None): - """ - Assert partial connectivity graphs between workloads. - - :param pass_list: Every workload in this list should be able to ping - every other workload in this list. - :param fail_list: Every workload in pass_list should *not* be able to - ping each workload in this list. Interconnectivity is not checked - *within* the fail_list. - :param retries: The number of retries. - :param type_list: list of types to test. If not specified, defaults to - icmp only. - """ - if type_list is None: - type_list = ['icmp', 'tcp', 'udp'] - if fail_list is None: - fail_list = [] - - # Wait (up to 20 retries) for each of the workloads to be able to ping - # _itself_. That will ensure that each workload is properly up and - # running. - self.wait_assert_self_connectivity(pass_list + fail_list, type_list) - - # Now check that connectivity _between_ the workloads is as expected. - conn_check_list = [] - for source in pass_list: - for dest in pass_list: - if source is dest: - continue - if 'icmp' in type_list: - conn_check_list.append((source, dest.ip, 'icmp', True, retries)) - if 'tcp' in type_list: - conn_check_list.append((source, dest.ip, 'tcp', True, retries)) - if 'udp' in type_list: - conn_check_list.append((source, dest.ip, 'udp', True, retries)) - for dest in fail_list: - if 'icmp' in type_list: - conn_check_list.append((source, dest.ip, 'icmp', False, retries)) - if 'tcp' in type_list: - conn_check_list.append((source, dest.ip, 'tcp', False, retries)) - if 'udp' in type_list: - conn_check_list.append((source, dest.ip, 'udp', False, retries)) - - results, diagstring = self.probe_connectivity(conn_check_list) - assert False not in results, ("Connectivity check error!\r\n" - "Results:\r\n %s\r\n" % diagstring) - - def wait_assert_self_connectivity(self, workloads, type_list): - # First check that each of the workloads that we're looking at can ping - # _itself_, allowing 20 retries for that. - self_conn_list = [] - for source in workloads: - if 'icmp' in type_list: - self_conn_list.append((source, source.ip, 'icmp', True, 20)) - if 'tcp' in type_list: - self_conn_list.append((source, source.ip, 'tcp', True, 20)) - if 'udp' in type_list: - self_conn_list.append((source, source.ip, 'udp', True, 20)) - - results, diagstring = self.probe_connectivity(self_conn_list) - assert False not in results, ("Self-connectivity check error!\r\n" - "Results:\r\n %s\r\n" % diagstring) - - - # Empirically, 18 threads works well on my machine! - check_pool = ThreadPool(18) - - def probe_connectivity(self, conn_check_list): - results = self.check_pool.map(self._conn_checker, conn_check_list) - # _conn_checker should only return None if there is an error in calling it - assert None not in results, ("_conn_checker error - returned None") - diagstring = "" - # Check that all tests passed - if False in results: - # We've failed, lets put together some diags. - header = ["source.ip", "dest.ip", "type", "expected", "actual"] - diagstring = "{: >18} {: >18} {: >7} {: >6} {: >6}\r\n".format(*header) - for i in range(len(conn_check_list)): - source, dest, test_type, exp_result, retries = conn_check_list[i] - pass_fail = results[i] - # Convert pass/fail into an actual result - if not pass_fail: - actual_result = not exp_result - else: - actual_result = exp_result - diag = [source.ip, dest, test_type, exp_result, actual_result] - diagline = "{: >18} {: >18} {: >7} {: >6} {: >6}\r\n".format(*diag) - diagstring += diagline - - return results, diagstring - - @debug_failures - def assert_ip_connectivity(self, workload_list, ip_pass_list, - ip_fail_list=None, type_list=None, retries=0): - """ - Assert partial connectivity graphs between workloads and given ips. - - This function is used for checking connectivity for ips that are - explicitly assigned to containers when added to calico networking. - - :param workload_list: List of workloads used to check connectivity. - :param ip_pass_list: Every workload in workload_list should be able to - ping every ip in this list. - :param ip_fail_list: Every workload in workload_list should *not* be - able to ping any ip in this list. Interconnectivity is not checked - *within* the fail_list. - :param type_list: list of types to test. If not specified, defaults to - icmp only. - """ - if type_list is None: - type_list = ['icmp'] - if ip_fail_list is None: - ip_fail_list = [] - - # Wait (up to 20 retries) for each of the workloads to be able to ping - # _itself_. That will ensure, at least, that each _source_ workload is - # properly up and running, before we start trying to ping from it to - # another IP. (Unfortunately we can't do anything here to ensure that - # the target IPs are actually available.) - self.wait_assert_self_connectivity(workload_list, type_list) - - conn_check_list = [] - for workload in workload_list: - for ip in ip_pass_list: - if 'icmp' in type_list: - conn_check_list.append((workload, ip, 'icmp', True, retries)) - if 'tcp' in type_list: - conn_check_list.append((workload, ip, 'tcp', True, retries)) - if 'udp' in type_list: - conn_check_list.append((workload, ip, 'udp', True, retries)) - - for ip in ip_fail_list: - if 'icmp' in type_list: - conn_check_list.append((workload, ip, 'icmp', False, retries)) - if 'tcp' in type_list: - conn_check_list.append((workload, ip, 'tcp', False, retries)) - if 'udp' in type_list: - conn_check_list.append((workload, ip, 'udp', False, retries)) - - results, diagstring = self.probe_connectivity(conn_check_list) - assert False not in results, ("Connectivity check error!\r\n" - "Results:\r\n %s\r\n" % diagstring) - - def check_data_in_datastore(self, host, data, resource, yaml_format=True): - if yaml_format: - out = host.calicoctl( - "get %s --output=yaml" % resource) - output = yaml.safe_load(out) - else: - out = host.calicoctl( - "get %s --output=json" % resource) - output = json.loads(out) - self.assert_same(data, output) - - @staticmethod - def assert_same(thing1, thing2): - """ - Compares two things. Debug logs the differences between them before - asserting that they are the same. - """ - assert cmp(thing1, thing2) == 0, \ - "Items are not the same. Difference is:\n %s" % \ - pformat(DeepDiff(thing1, thing2), indent=2) - - @staticmethod - def writejson(filename, data): - """ - Converts a python dict to json and outputs to a file. - :param filename: filename to write - :param data: dictionary to write out as json - """ - with open(filename, 'w') as f: - text = json.dumps(data, - sort_keys=True, - indent=2, - separators=(',', ': ')) - logger.debug("Writing %s: \n%s" % (filename, text)) - f.write(text) - - @debug_failures - def assert_false(self, b): - """ - Assert false, wrapped to allow debugging of failures. - """ - assert not b - - @debug_failures - def assert_true(self, b): - """ - Assert true, wrapped to allow debugging of failures. - """ - assert b - - @staticmethod - def log_banner(msg, *args, **kwargs): - global first_log_time - time_now = time.time() - if first_log_time is None: - first_log_time = time_now - time_now -= first_log_time - elapsed_hms = "%02d:%02d:%02d " % (time_now / 3600, - (time_now % 3600) / 60, - time_now % 60) - - level = kwargs.pop("level", logging.INFO) - msg = elapsed_hms + str(msg) % args - banner = "+" + ("-" * (len(msg) + 2)) + "+" - logger.log(level, "\n" + - banner + "\n" - "| " + msg + " |\n" + - banner) - -# Add a default batch number to all tests (used for running tests in parallel) -TestBase.batchnumber = 0 diff --git a/node/tests/st/utils/__init__.py b/node/tests/st/utils/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/node/tests/st/utils/constants.py b/node/tests/st/utils/constants.py deleted file mode 100644 index 0b8af370bff..00000000000 --- a/node/tests/st/utils/constants.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) 2015-2016 Tigera, Inc. All rights reserved. -# -# 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. - -DEFAULT_IPV4_POOL_CIDR = "192.168.128.0/17" -DEFAULT_IPV4_ADDR_1 = "192.168.128.1" -DEFAULT_IPV4_ADDR_2 = "192.168.128.2" -DEFAULT_IPV4_ADDR_3 = "192.168.128.3" -DEFAULT_IPV6_POOL_CIDR = "fd80:24e2:f998:72d6::/64" -LARGE_AS_NUM = "4294.566" diff --git a/node/tests/st/utils/docker_host.py b/node/tests/st/utils/docker_host.py deleted file mode 100644 index 714df13cfff..00000000000 --- a/node/tests/st/utils/docker_host.py +++ /dev/null @@ -1,709 +0,0 @@ -# Copyright (c) 2015-2017 Tigera, Inc. All rights reserved. -# -# 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. -import logging -import json -import os -import re -import random -import string -import tempfile -import uuid -import yaml -from functools import partial -from subprocess import CalledProcessError, check_output - -from log_analyzer import LogAnalyzer, FELIX_LOG_FORMAT, TIMESTAMP_FORMAT -from network import DummyNetwork -from tests.st.utils.constants import DEFAULT_IPV4_POOL_CIDR -from tests.st.utils.exceptions import CommandExecError -from utils import get_ip, log_and_run, retry_until_success, ETCD_SCHEME, \ - ETCD_CA, ETCD_KEY, ETCD_CERT, ETCD_HOSTNAME_SSL -from workload import Workload - -logger = logging.getLogger(__name__) -# We want to default CHECKOUT_DIR if either the ENV var is unset -# OR its set to an empty string. -CHECKOUT_DIR = os.getenv("HOST_CHECKOUT_DIR", "") -if CHECKOUT_DIR == "": - CHECKOUT_DIR = os.getcwd() - -NODE_CONTAINER_NAME = os.getenv("NODE_CONTAINER_NAME", "calico/node:latest") - -FELIX_LOGLEVEL = os.getenv("ST_FELIX_LOGLEVEL", "") - -if ETCD_SCHEME == "https": - CLUSTER_STORE_DOCKER_OPTIONS = "--cluster-store=etcd://%s:2379 " \ - "--cluster-store-opt kv.cacertfile=%s " \ - "--cluster-store-opt kv.certfile=%s " \ - "--cluster-store-opt kv.keyfile=%s " % \ - (ETCD_HOSTNAME_SSL, ETCD_CA, ETCD_CERT, - ETCD_KEY) -else: - CLUSTER_STORE_DOCKER_OPTIONS = "--cluster-store=etcd://%s:2379 " % \ - get_ip() - - -class DockerHost(object): - """ - A host container which will hold workload containers to be networked by - Calico. - - :param calico_node_autodetect_ip: When set to True, the test framework - will not perform IP detection, and will run `calicoctl node` without - explicitly passing in a value for --ip. This means calico-node will be - forced to do its IP detection. - :param override_hostname: When set to True, the test framework will - choose an alternate hostname for the host which it will pass to all - calicoctl components as the HOSTNAME environment variable. If set - to False, the HOSTNAME environment is not explicitly set. - """ - - def __init__(self, name, start_calico=True, dind=True, - additional_docker_options="", - post_docker_commands=["docker load -q -i /code/calico-node.tar", - "docker load -q -i /code/busybox.tar"], - calico_node_autodetect_ip=False, - simulate_gce_routing=False, - override_hostname=False): - self.name = name - self.dind = dind - self.workloads = set() - self.ip = None - self.log_analyzer = None - """ - An IP address value to pass to calicoctl as `--ip`. If left as None, - no value will be passed, forcing calicoctl to do auto-detection. - """ - - self.ip6 = None - """ - An IPv6 address value to pass to calicoctl as `--ipv6`. If left as - None, no value will be passed. - """ - - self.override_hostname = None if not override_hostname else \ - uuid.uuid1().hex[:16] - """ - Create an arbitrary hostname if we want to override. - """ - - # This variable is used to assert on destruction that this object was - # cleaned up. If not used as a context manager, users of this object - # must invoke cleanup. - self._cleaned = False - - docker_args = "--privileged -tid " \ - "-v /lib/modules:/lib/modules " \ - "-v %s/certs:%s/certs -v %s:/code --name %s" % \ - (CHECKOUT_DIR, CHECKOUT_DIR, CHECKOUT_DIR, - self.name) - if ETCD_SCHEME == "https": - docker_args += " --add-host %s:%s" % (ETCD_HOSTNAME_SSL, get_ip()) - - if dind: - log_and_run("docker rm -f %s || true" % self.name) - # Pass the certs directory as a volume since the etcd SSL/TLS - # environment variables use the full path on the host. - # Set iptables=false to prevent iptables error when using dind - # libnetwork - log_and_run("docker run %s " - "calico/dind:latest " - "--iptables=false " - "%s" % - (docker_args, additional_docker_options)) - - self.ip = log_and_run( - "docker inspect --format " - "'{{.NetworkSettings.Networks.bridge.IPAddress}}' %s" % - self.name) - - # Make sure docker is up - docker_ps = partial(self.execute, "docker ps") - try: - # Retry the docker ps. If we fail, we'll gather some - # information and log it out. - retry_until_success(docker_ps, ex_class=CalledProcessError, - retries=10) - except CommandExecError: - # Hit a problem waiting for the docker host to have access - # to docker. Log out some information to help with diagnostics - # before re-raising the exception. - logger.error("Error waiting for docker to be ready in DockerHost") - log_and_run("docker ps -a") - log_and_run("docker logs %s" % self.name) - raise - - if simulate_gce_routing: - # Simulate addressing and routing setup as on a GCE instance: - # the instance has a /32 address (which means that it appears - # not to be directly connected to anything) and a default route - # that does not have the 'onlink' flag to override that. - # - # First check that we can ping the Docker bridge, and trace out - # initial state. - self.execute("ping -c 1 -W 2 172.17.0.1") - self.execute("ip a") - self.execute("ip r") - - # Change the normal /16 IP address to /32. - self.execute("ip a del %s/16 dev eth0" % self.ip) - self.execute("ip a add %s/32 dev eth0" % self.ip) - - # Add a default route via the Docker bridge. - self.execute("ip r a 172.17.0.1 dev eth0") - self.execute("ip r a default via 172.17.0.1 dev eth0") - - # Trace out final state, and check that we can still ping the - # Docker bridge. - self.execute("ip a") - self.execute("ip r") - self.execute("ping -c 1 -W 2 172.17.0.1") - - for command in post_docker_commands: - self.execute(command) - elif not calico_node_autodetect_ip: - # Find the IP so it can be specified as `--ip` when launching - # node later. - self.ip = get_ip(v6=False) - self.ip6 = get_ip(v6=True) - - if start_calico: - self.start_calico_node(env_options=' -e FELIX_HEALTHENABLED=true ') - - def assert_is_ready(self, bird=True, felix=True): - cmd = "docker exec calico-node /bin/calico-node" - if bird: - cmd += " -bird-ready" - if felix: - cmd += " -felix-ready" - - self.execute(cmd) - - def assert_is_live(self, felix=True, bird=True, bird6=True): - cmd = "docker exec calico-node /bin/calico-node" - if felix: - cmd += " -felix-live" - if bird: - cmd += " -bird-live" - if bird6: - cmd += " -bird6-live" - - self.execute(cmd) - - def execute(self, command, raise_exception_on_failure=True, daemon_mode=False): - """ - Pass a command into a host container. - - Raises a CommandExecError() if the command returns a non-zero - return code if raise_exception_on_failure=True. - - :param command: The command to execute. - :param raise_exception_on_failure: Raises an exception if the command exits with - non-zero return code. - :param daemon_mode: The command will be executed as a daemon process. Useful - to start a background service. - - :return: The output from the command with leading and trailing - whitespace removed. - """ - if self.dind: - option = "-d" if daemon_mode else "-it" - command = self.escape_shell_single_quotes(command) - command = "docker exec %s %s sh -c '%s'" % (option, self.name, command) - - return log_and_run(command, raise_exception_on_failure=raise_exception_on_failure) - - def execute_readline(self, command, filename): - """ - Execute a command and return a list of the lines in the file. If - running dind, the file to read will be copied to the host before - reading since docker exec can be unreliable when piping large - amounts of data. - - Raises an exception if the return code is non-zero. Stderr is ignored. - - Use this rather than execute if the command outputs a large amount of - data that cannot be handled as a single string. - - :return: List of individual lines. - """ - logger.debug("Running command on %s", self.name) - logger.debug(" - Command: %s", command % filename) - - if self.dind: - # Copy file to host before reading. - logger.debug("Running dind, copy file to host first") - - # Make a tmp directory where we can place the file. - log_and_run("mkdir -p /tmp/%s" % self.name, raise_exception_on_failure=True) - - # Generate the filename to use on the host. - # /var/log/calico/felix/current becomes /tmp//var-log-calico-felix-current. - hostfile = "/tmp/%s/%s" % (self.name, filename.strip("/").replace("/", "-")) - - # Copy to the host. - c = "docker cp %s:%s %s" % (self.name, filename, hostfile) - log_and_run(c, raise_exception_on_failure=True) - logger.debug("Copied file from container to %s" % hostfile) - command = command % hostfile - else: - # Otherwise just run the provided command. - command = command % filename - - logger.debug("Final command: %s", command.split(" ")) - out = check_output(command.split(" ")) - return out.split("\n") - - def calicoctl(self, command, version=None, raise_exception_on_failure=True): - """ - Convenience function for abstracting away calling the calicoctl - command. - - Raises a CommandExecError() if the command returns a non-zero - return code. - - :param command: The calicoctl command line parms as a single string. - :param version: The calicoctl version to use (this is appended to the - executable name. It is assumed the Makefile will ensure - the required versions are downloaded. - :return: The output from the command with leading and trailing - whitespace removed. - """ - if not version: - calicoctl = os.environ.get("CALICOCTL", "/code/dist/calicoctl") - else: - calicoctl = "/code/dist/calicoctl-" + version - - if ETCD_SCHEME == "https": - etcd_auth = "%s:2379" % ETCD_HOSTNAME_SSL - else: - etcd_auth = "%s:2379" % get_ip() - # Export the environment, in case the command has multiple parts, e.g. - # use of | or ; - # - # Pass in all etcd params, the values will be empty if not set anyway - calicoctl = "export ETCD_ENDPOINTS=%s://%s; " \ - "export ETCD_CA_CERT_FILE=%s; " \ - "export ETCD_CERT_FILE=%s; " \ - "export ETCD_KEY_FILE=%s; %s" % \ - (ETCD_SCHEME, etcd_auth, ETCD_CA, ETCD_CERT, ETCD_KEY, - calicoctl) - # If the hostname is being overridden, then export the HOSTNAME - # environment. - if self.override_hostname: - calicoctl = "export HOSTNAME=%s; %s" % ( - self.override_hostname, calicoctl) - - return self.execute(calicoctl + " --allow-version-mismatch " + command, raise_exception_on_failure=raise_exception_on_failure) - - def start_calico_node(self, options="", with_ipv4pool_cidr_env_var=True, env_options=""): - """ - Start calico in a container inside a host by calling through to the - calicoctl node command. - :param env_options: Docker environment options. - :param options: calico node options. - :param with_ipv4pool_cidr_env_var: dryrun options. - """ - args = ['node', 'run'] - if with_ipv4pool_cidr_env_var: - args.append('--dryrun') - if "--node-image" not in options: - args.append('--node-image=%s' % NODE_CONTAINER_NAME) - - # Add the IP addresses if required and we aren't explicitly specifying - # them in the options. The --ip and --ip6 options can be specified - # using "=" or space-separated parms. - if self.ip and "--ip=" not in options and "--ip " not in options: - args.append('--ip=%s' % self.ip) - if self.ip6 and "--ip6=" not in options and "--ip6 " not in options: - args.append('--ip6=%s' % self.ip6) - args.append(options) - - cmd = ' '.join(args) - - if with_ipv4pool_cidr_env_var: - # Run the dryrun command, then modify and execute the command that - # that tells us. - assert "--dryrun" in cmd - output = self.calicoctl(cmd) - - # Look for the line in the output that includes "docker run", - # "--net=host" and "--name=calico-node". - for line in output.split('\n'): - if re.match(r'docker run .*--net=host .*--name=calico-node', line): - # This is the line we want to modify. - break - else: - raise AssertionError("No node run line in %s" % output) - - # Break the line at the first occurrence of " -e ". - prefix, _, suffix = line.rstrip().partition(" -e ") - - felix_logsetting = "" - if FELIX_LOGLEVEL != "": - felix_logsetting = " -e FELIX_LOGSEVERITYSCREEN=" + FELIX_LOGLEVEL - - # Construct the docker command that we want, including the - # CALICO_IPV4POOL_CIDR setting. - modified_cmd = ( - prefix + - (" -e CALICO_IPV4POOL_CIDR=%s " % DEFAULT_IPV4_POOL_CIDR) + - felix_logsetting + env_options + - " -e " + suffix - ) - - # Now run that. - self.execute(modified_cmd) - else: - # Run the non-dryrun calicoctl node run command. - self.calicoctl(cmd) - - self.attach_log_analyzer() - - def set_ipip_enabled(self, enabled): - pools_output = self.calicoctl("get ippool -o yaml") - pools_dict = yaml.safe_load(pools_output) - for pool in pools_dict['items']: - print "Pool is %s" % pool - if ':' not in pool['spec']['cidr']: - pool['spec']['ipipMode'] = 'Always' if enabled else 'Never' - if 'creationTimestamp' in pool['metadata']: - del pool['metadata']['creationTimestamp'] - self.writejson("ippools.json", pools_dict) - self.calicoctl("apply -f ippools.json") - - def attach_log_analyzer(self): - self.log_analyzer = LogAnalyzer(self, - "/var/log/calico/felix/current", - FELIX_LOG_FORMAT, - TIMESTAMP_FORMAT) - - def start_calico_node_with_docker(self): - """ - Start calico in a container inside a host by calling docker directly. - """ - if ETCD_SCHEME == "https": - etcd_auth = "%s:2379" % ETCD_HOSTNAME_SSL - ssl_args = "-e ETCD_CA_CERT_FILE=%s " \ - "-e ETCD_CERT_FILE=%s " \ - "-e ETCD_KEY_FILE=%s " \ - "-v %s/certs:%s/certs " \ - % (ETCD_CA, ETCD_CERT, ETCD_KEY, - CHECKOUT_DIR, CHECKOUT_DIR) - - else: - etcd_auth = "%s:2379" % get_ip() - ssl_args = "" - - # If the hostname has been overridden on this host, then pass it in - # as an environment variable. - if self.override_hostname: - hostname_args = "-e HOSTNAME=%s" % self.override_hostname - else: - hostname_args = "" - - self.execute("docker run -d --net=host --privileged " - "--name=calico-node " - "%s " - "-e IP=%s " - "-e ETCD_ENDPOINTS=%s://%s %s " - "-v /var/log/calico:/var/log/calico " - "-v /var/run/calico:/var/run/calico " - "%s" % (hostname_args, self.ip, ETCD_SCHEME, etcd_auth, - ssl_args, NODE_CONTAINER_NAME) - ) - - def remove_workloads(self): - """ - Remove all containers running on this host. - - Useful for test shut down to ensure the host is cleaned up. - :return: None - """ - for workload in self.workloads: - try: - workload.run_cni("DEL") - self.execute("docker rm -f %s" % workload.name) - except CalledProcessError: - # Make best effort attempt to clean containers. Don't fail the - # test if a container can't be removed. - pass - - def remove_images(self): - """ - Remove all images running on this host. - - Useful for test shut down to ensure the host is cleaned up. - :return: None - """ - cmd = "docker rmi $(docker images -qa)" - try: - self.execute(cmd) - except CalledProcessError: - # Best effort only. - pass - - def remove_containers(self): - """ - Remove all containers running on this host. - - Useful for test shut down to ensure the host is cleaned up. - :return: None - """ - cmd = "docker rm -f $(docker ps -qa)" - try: - self.execute(cmd) - except CalledProcessError: - # Best effort only. - pass - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - """ - Exit the context of this host. - :return: None - """ - self.cleanup(log_extra_diags=bool(exc_type)) - - def cleanup(self, log_extra_diags=False, err_words=None, ignore_list=[]): - """ - Clean up this host, including removing any containers created. This is - necessary especially for Docker-in-Docker so we don't leave dangling - volumes. - - Also, perform log analysis to check for any errors, raising an exception - if any were found. - - If log_extra_diags is set to True we will log some extra diagnostics (this is - set to True if the DockerHost context manager exits with an exception). - Extra logs will also be output if the log analyzer detects any errors. - """ - # Check for logs before tearing down, log extra diags if we spot an error. - log_exception = None - try: - if self.log_analyzer is not None: - self.log_analyzer.check_logs_for_exceptions(err_words, ignore_list) - except Exception, e: - log_exception = e - log_extra_diags = True - - # Log extra diags if we need to. - if log_extra_diags: - self.log_extra_diags() - - logger.info("# Cleaning up host %s", self.name) - if self.dind: - # For Docker-in-Docker, we need to remove all workloads, containers - # and images. - - self.remove_workloads() - self.remove_containers() - self.remove_images() - - # Remove the outer container for DinD. - log_and_run("docker rm -f %s || true" % self.name) - else: - # For non Docker-in-Docker, we can only remove the containers we - # created - so remove the workloads and delete the calico node. - self.remove_workloads() - log_and_run("docker rm -f calico-node || true") - - self._cleaned = True - - # Now that tidy-up is complete, re-raise any exceptions found in the logs. - if log_exception: - raise log_exception - - def __del__(self): - """ - This destructor asserts this object was cleaned up before being GC'd. - - Why not just clean up? This object is used in test scripts and we - can't guarantee that GC will happen between test runs. So, un-cleaned - objects may result in confusing behaviour since this object manipulates - Docker containers running on the system. - :return: - """ - assert self._cleaned - - def create_workload(self, base_name, - image="busybox", network="bridge", - ip=None, labels=[], namespace=None): - """ - Create a workload container inside this host container. - """ - name = base_name + "_" + \ - ''.join([random.choice(string.ascii_letters) for ii in range(6)]).lower() - workload = Workload(self, name, image=image, network=network, - ip=ip, labels=labels, namespace=namespace) - self.workloads.add(workload) - return workload - - @staticmethod - def create_network(name): - return DummyNetwork(name) - - @staticmethod - def escape_shell_single_quotes(command): - """ - Escape single quotes in shell strings. - - Replace ' (single-quote) in the command with an escaped version. - This needs to be done, since the command is passed to "docker - exec" to execute and needs to be single quoted. - Strictly speaking, it's impossible to escape single-quoted - shell script, but there is a workaround - end the single quoted - string, then concatenate a double quoted single quote, - and finally re-open the string with a single quote. Because - this is inside a single quoted python, string, the single - quotes also need escaping. - - :param command: The string to escape. - :return: The escaped string - """ - return command.replace('\'', '\'"\'"\'') - - def get_hostname(self): - """ - Get the hostname from Docker - The hostname is a randomly generated string. - Note, this function only works with a host with dind enabled. - Raises an exception if dind is not enabled. - - :return: hostname of DockerHost - """ - # If overriding the hostname, return that one. - if self.override_hostname: - return self.override_hostname - - command = "docker inspect --format {{.Config.Hostname}} %s" % self.name - return log_and_run(command) - - def writefile(self, filename, data): - """ - Writes a file on a host (e.g. a JSON file for loading into calicoctl). - :param filename: string, the filename to create - :param data: string, the data to put in the file - :return: Return code of execute operation. - """ - if self.dind: - with tempfile.NamedTemporaryFile() as tmp: - tmp.write(data) - tmp.flush() - log_and_run("docker cp %s %s:%s" % (tmp.name, self.name, filename)) - else: - with open(filename, 'w') as f: - f.write(data) - - self.execute("cat %s" % filename) - - def writejson(self, filename, data): - """ - Converts a python dict to json and outputs to a file. - :param filename: filename to write - :param data: dictionary to write out as json - """ - text = json.dumps(data, - sort_keys=True, - indent=2, - separators=(',', ': ')) - self.writefile(filename, text) - - def add_resource(self, resource_data): - """ - Add resource specified in resource_data object. - :param resource_data: object representing json data for the resource - to add - """ - self._apply_resources(resource_data) - - def delete_all_resource(self, resource): - """ - Delete all resources of the specified type. - :param resource: string, resource type to delete - """ - # Grab all objects of a resource type - # and delete them (if there are any) - # However, profiles are treated differently. We must first filter out - # the 'projectcalico-default-allow' profile that always exists and - # cannot be deleted. - objects = yaml.safe_load(self.calicoctl("get %s -o yaml" % resource)) - - # Filter out the default-allow profile - if resource == 'profile': - temp = [] - for profile in objects['items']: - if profile['metadata']['name'] != "projectcalico-default-allow": - temp.append(profile) - - objects['items'] = temp - - if len(objects) > 0: - if 'items' in objects and len(objects['items']) == 0: - pass - else: - self._delete_data(objects) - - def _delete_data(self, data): - logger.debug("Deleting data with calicoctl: %s", data) - self._exec_calicoctl("delete", data) - - def _apply_resources(self, resources): - self._exec_calicoctl("apply", resources) - - def _exec_calicoctl(self, action, data): - # Delete creationTimestamp fields from the data that we're going to - # write. - for obj in data.get('items', []): - if 'creationTimestamp' in obj['metadata']: - del obj['metadata']['creationTimestamp'] - if 'metadata' in data and 'creationTimestamp' in data['metadata']: - del data['metadata']['creationTimestamp'] - - # Use calicoctl with the modified data. - self.writejson("new_data", data) - self.calicoctl("%s -f new_data" % action) - - def log_extra_diags(self): - # Check for OOM kills. - log_and_run("dmesg | grep -i kill", raise_exception_on_failure=False) - # Run a set of commands to trace ip routes, iptables and ipsets. - self.execute("ip route", raise_exception_on_failure=False) - self.execute("iptables-save -c", raise_exception_on_failure=False) - self.execute("ip6tables-save -c", raise_exception_on_failure=False) - self.execute("ipset save", raise_exception_on_failure=False) - self.execute("ps waux", raise_exception_on_failure=False) - self.execute("docker logs calico-node", raise_exception_on_failure=False) - self.execute("docker exec calico-node ls -l /var/log/calico/felix", raise_exception_on_failure=False) - self.execute("docker exec calico-node cat /var/log/calico/felix/*", raise_exception_on_failure=False) - self.execute("docker exec calico-node ls -l /var/log/calico/confd", raise_exception_on_failure=False) - self.execute("docker exec calico-node cat /var/log/calico/confd/*", raise_exception_on_failure=False) - self.execute("docker exec calico-node ls -l /var/log/calico/bird", raise_exception_on_failure=False) - self.execute("docker exec calico-node cat /var/log/calico/bird/*", raise_exception_on_failure=False) - self.execute("docker exec calico-node cat /etc/calico/confd/config/bird.cfg", raise_exception_on_failure=False) - - self.execute("docker ps -a", raise_exception_on_failure=False) - for wl in self.workloads: - wl.host.execute("docker logs %s" % wl.name, raise_exception_on_failure=False) - log_and_run("docker logs %s" % self.name, raise_exception_on_failure=False) - - def delete_conntrack_state_to_ip(self, protocol, dst_ip): - self.execute("docker exec calico-node conntrack -D -p %s --orig-dst %s" % (protocol, dst_ip), - raise_exception_on_failure=False) - - def delete_conntrack_state_to_workloads(self, protocol): - for workload in self.workloads: - self.delete_conntrack_state_to_ip(protocol, workload.ip) diff --git a/node/tests/st/utils/exceptions.py b/node/tests/st/utils/exceptions.py deleted file mode 100644 index 45b278755de..00000000000 --- a/node/tests/st/utils/exceptions.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (c) 2015-2016 Tigera, Inc. All rights reserved. -# -# 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. -from subprocess import CalledProcessError - - -class CommandExecError(CalledProcessError): - """ - Wrapper for CalledProcessError with an Exception message that gives the - output captured from the failed command. - """ - - def __init__(self, called_process_error): - self.called_process_error = called_process_error - - @property - def returncode(self): - return self.called_process_error.returncode - - @property - def output(self): - return self.called_process_error.output - - @property - def cmd(self): - return self.called_process_error.cmd - - def __str__(self): - return "Command %s failed with RC %s and output:\n%s" % \ - (self.called_process_error.cmd, - self.called_process_error.returncode, - self.called_process_error.output) diff --git a/node/tests/st/utils/log_analyzer.py b/node/tests/st/utils/log_analyzer.py deleted file mode 100644 index 9c24fb4be7f..00000000000 --- a/node/tests/st/utils/log_analyzer.py +++ /dev/null @@ -1,364 +0,0 @@ -# Copyright (c) 2015-2017 Tigera, Inc. All rights reserved. -# -# 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.ra, Inc. All rights reserved. - -from collections import deque -from datetime import datetime -import logging -import re - -from tests.st.utils.exceptions import CommandExecError - -_log = logging.getLogger(__name__) - -FELIX_LOG_FORMAT = ( - "(?P\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).\d{0,3} " - "\\[(?P\w+)\\]" - "\\[(?P\d+)(/\d+)?\\] " - "(?P.*)" -) - -TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S" - -# The number of additional logs to trace out before the first error log, and -# the maximum number of errors to report. -NUM_CONTEXT_LOGS = 300 -MAX_NUM_ERRORS = 100 - -# This is the list of logs we should ignore for all tests. -LOGS_IGNORE_ALL_TESTS = [ - "Failed to connect to syslog error=Unix syslog delivery error level=", - "Exiting for config change", - "Exiting. reason=\"config changed\"", - "Exiting immediately reason=\"config changed\"", -] - - -class Log(object): - """ - Class encapsulating information about a log extracted from a log file. - """ - - def __init__(self, timestamp, level, pid, msg): - """ - :param timestamp: The log datetime. - :param level: The log level - :param pid: The PID of the process that created the log - :param msg: The log text. - """ - self.timestamp = timestamp - self.level = level - self.pid = pid - self.msg = msg - - def append(self, logtext): - """ - Append the text to the end of the current text with a newline - separator. - - :param logtext: The text to append to the log. - """ - self.msg += "\n" + logtext - - def detailed(self): - return "=== LOG %s %s [pid %s] ===\n%s" % (self.timestamp, self.level, - self.pid, self.msg) - - def __str__(self): - return "%s %s %s %s" % (self.timestamp, self.level, - self.pid, self.msg) - - def __repr__(self): - return self.__str__() - -class LogAnalyzer(object): - """ - LogAnalyzer class to check any new logs generated since the analyzer - was instantiated. - - This is a fairly simpler parser - it doesn't check flipped files. - """ - - def __init__(self, host, filename, log_format, timestamp_format, - continuation_level=None): - """ - :param host: the host running calico-node - :param filename: The log filename on the server - :param log_format: The format of the logs - :param timestamp_format: The date/time format in the logs. - :param continuation_level: An optional log level that indicates the - log is a continuation of the previous log (i.e. the text can be - extracted and appended to the previous log). - - The log format should be a regex string containing the following - named matches: - - timestamp (the extracted timestamp) - - loglevel (the log level) - - pid (the process ID) - - logtext (the actual log message) - - The timestamp format is the format of the extracted timestamp in - notation used by datetime.datetime.strptime(). - """ - self.host = host - self.filename = filename - self.log_regex = re.compile(log_format) - self.timestamp_format = timestamp_format - self.init_log_time = None - self.init_log_lines = None - self.continuation_level = continuation_level - - # Store the time of the last log in the file. - self.reset() - - def reset(self): - """ - Initialise the time of the first log in the log file and the number - of lines in the log file. - - This information is used to work out where to start from when looking - at new logs. - """ - _log.debug("Resetting log analyzer on %s", self.host.name) - # Grab the time of the first log. - self.init_log_time = self._get_first_log_time() - _log.debug("First log has timestamp: %s", self.init_log_time) - - self.init_log_lines = self._get_logs_num_lines() - _log.debug("Log file has %s lines", self.init_log_lines) - - def _get_first_log_time(self): - """ - Extract the time of the first log in the file. This is used to - determine whether a file has flipped during a test. - """ - cmd = "head -100 %s" - for log in self._parse_logs(cmd, self.filename): - return log.timestamp - return None - - def _get_logs_num_lines(self): - """ - Return the number of lines in the log file. - - :return: The number of lines in the log file or None if the file does - not exist or cannot be read. - """ - cmd = "wc -l %s" % self.filename - lines = None - stdout = None - try: - stdout = self.host.execute(cmd) - except CommandExecError: - _log.debug("Error running command: %s", cmd) - - _log.debug("Extract number of lines in file: %s", - self.filename) - try: - lines = int(stdout.split(" ")[0]) - except ValueError: - _log.error("Unable to parse output: %s", stdout) - except AttributeError: - _log.error("None output?: %s", stdout) - - return lines - - def get_latest_logs(self, logfilter=None): - """ - Get the latest (filtered) logs from the server. - - :param logfilter: An optional filter that determines whether a log - should be stored. This is a function that takes the log as the only - argument and returns True if the log should be filtered _out_ of the - list. - :return: A list of Log objects. - """ - return [log for log in self._parse_latest_logs() if not logfilter or not logfilter(log)] - - def _parse_latest_logs(self): - """ - Parse the latest logs from the server, returning a generator that - iterates through the logs. - - :return: A Log generator. - """ - # Use the entire log file if the file has flipped (i.e. the first log - # time is not the same, otherwise tail all but the first logs. - first_log_time = self._get_first_log_time() - _log.debug("First log has timestamp: %s", first_log_time) - - if first_log_time != self.init_log_time or \ - not self.init_log_lines: - _log.debug("Log file is new") - cmd = "cat %s" - else: - _log.debug("Check appended logs") - cmd = "tail -n +%s %s" % (self.init_log_lines + 1, self.filename) - return self._parse_logs(cmd, self.filename) - - def _parse_logs(self, cmd, filename): - """ - Parse the logs from the output of the supplied command, returning a - generator that iterates through the logs. - - :param cmd: The command to run to output the logs. - - :return: A Log generator. - """ - last_log = None - try: - for line in self.host.execute_readline(cmd, filename): - log = self._process_log_line(line, last_log) - - # Logs may be continued, in which case we only return the log - # when the parsing indicates a new log. - if last_log and last_log != log: - yield last_log - last_log = log - except Exception: - _log.exception( - "Hit exception getting logs from %s - skip logs", - self.host.name) - - # Yield the final log. - if last_log: - yield last_log - - def _process_log_line(self, line, last_log): - """ - Build up a list of logs from the supplied log line. - - If a line in the logs_text does not match the format of the log string - it is assumed it is a continuation of the previous log. Similarly, - a log with level "TRACE" is also treated as a continuation. - - :param line: The log line to process. This may either add a new log - or may be a continuation of a previous log, or may be filtered out. - :param last_log: The previous log that was processed by this command. - This may be None for the first line in the log file. - :return: The log that was added or updated by this method. This may - return None if no log was parsed. If this line was appended to the - previous log, it will return last_log. - """ - # Put the full text of the log into logtext, but strip off ending whitespace because - # we'll add \n back to it when we append to it - logtext = line.rstrip() - # Strip superfluous whitespace - line = line.strip() - - # Check the line for a log match. - log_match = self.log_regex.match(line) - - # If the line does not match the regex it will be a continuation - # of the previous log. If there was no previous log then we must - # have starting parsing in the middle of a multi-line log. - if not log_match: - if last_log: - last_log.append(line) - return last_log - - # Extract the parameters from the match object. - groupdict = log_match.groupdict() - loglevel = groupdict["loglevel"] - timestamp = datetime.strptime(groupdict["timestamp"], - self.timestamp_format) - pid = groupdict["pid"] - - # Neutron logs use a log level of TRACE to continue a multi-line - # log. If there was no previous log then we must have starting parsing - # in the middle of a multi-line log. - if self.continuation_level == loglevel: - if last_log: - last_log.append(logtext) - return last_log - - # Create and return the new log. We don't add it until we start the - # next log as we need to get the entire log before we can run it - # through the filter. - log = Log(timestamp, loglevel, pid, logtext) - return log - - def check_logs_for_exceptions(self, err_words=None, ignore_list=[]): - """ - Check the logs for any error level logs and raises an exception if - any are found. - """ - _log.info("Checking logs for exceptions") - _log.debug("Analyzing logs from %s on %s", - self.filename, self.host.name) - - # Store each error with a set of preceding context logs. - errors = [] - logs = deque(maxlen=NUM_CONTEXT_LOGS) - - # Iterate through the logs finding all error logs and keeping track - # of unfiltered context logs. - for log in self._parse_latest_logs(): - logs.append(log) - if self._is_error_log(log, err_words, ignore_list): - _log.info("Error found in node logs: %s", log) - errors.append(logs) - logs = deque(maxlen=NUM_CONTEXT_LOGS) - - # Limit the number of errors we report. - if len(errors) == MAX_NUM_ERRORS: - break - - if errors: - # Trace out the error logs (this is the last entry in each of the - # error deques). - _log.error("***** Start of errors in logs from %s on %s *****" - "\n\n%s\n\n", - self.filename, self.host.name, - "\n\n".join(map(lambda logs: logs[-1].detailed(), errors))) - _log.error("****** End of errors in logs from %s on %s ******", - self.filename, self.host.name) - - if len(errors) == MAX_NUM_ERRORS: - _log.error("Limited to %d errors reported" % MAX_NUM_ERRORS) - - # Trace out the unfiltered logs - each error stored above contains a set - # proceeding context logs followed by the error log. Join them all - # together to trace out, delimiting groups of logs with a "..." to - # indicate that some logs in between may be missing (because we only - # trace out a max number of proceeding logs). - _log.error("***** Start of context logs from %s on %s *****" - "\n\n%s\n\n", - self.filename, self.host.name, - "\n...\n".join(map(lambda logs: "\n".join(map(str, logs)), errors))) - _log.error("****** End of context logs from %s on %s ******", - self.filename, self.host.name) - - assert not errors, "Test suite failed due to errors raised in logs" - - def _is_error_log(self, log, err_words=None, ignore_list=[]): - """ - Return whether the log is an error log or not. - - :return: True if the log is an error log. - :param log: Log need to be checked. - :param err_words: The per test error words. - :param ignore_list: The per test ignore list. - Note that we are also skipping known failures as defined by the - LOGS_IGNORE_ALL_TESTS. - """ - - ignores = LOGS_IGNORE_ALL_TESTS + ignore_list - - if err_words is None: - err_words = {"ERROR", "PANIC", "FATAL", "CRITICAL"} - is_error = log.level in err_words - if is_error: - is_error = not any(log.msg.find(txt) > 0 for txt in ignores) - - return is_error diff --git a/node/tests/st/utils/route_reflector.py b/node/tests/st/utils/route_reflector.py deleted file mode 100644 index 254020a65cd..00000000000 --- a/node/tests/st/utils/route_reflector.py +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright (c) 2015-2016 Tigera, Inc. All rights reserved. -# -# 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. - -import os -from docker_host import DockerHost - -from netaddr import IPAddress - -class RouteReflectorCluster(object): - """ - Encapsulate the setting up and tearing down of a route reflector cluster. - """ - - def __init__(self, num_in_redundancy_group, num_redundancy_groups): - """ - :param num_rrs: The number of route reflectors in the cluster. - """ - self.num_in_redundancy_group = num_in_redundancy_group - self.num_redundancy_groups = num_redundancy_groups - self.redundancy_groups = [] - - def __enter__(self): - """ - Set up the route reflector clusters when entering context. - :return: self. - """ - # Create the route reflector hosts, grouped by redundancy. - for ii in range(self.num_redundancy_groups): - cluster_id = str(IPAddress(0xFF000001 + ii)) - redundancy_group = [] - for jj in range(self.num_in_redundancy_group): - rr = DockerHost('RR.%d.%d' % (ii, jj), start_calico=False) - rr.add_resource({ - 'apiVersion': 'projectcalico.org/v3', - 'kind': 'Node', - 'metadata': { - 'name': rr.get_hostname(), - 'labels': { - 'routeReflectorClusterID': cluster_id, - }, - }, - 'spec': { - 'bgp': { - 'routeReflectorClusterID': cluster_id, - }, - }, - }) - rr.start_calico_node() - - # Store the redundancy group. - redundancy_group.append(rr) - self.redundancy_groups.append(redundancy_group) - - # If there is more than one of them, configure full mesh - # peering between the route reflectors. - if self.num_redundancy_groups * self.num_in_redundancy_group > 1: - rr.add_resource({ - 'apiVersion': 'projectcalico.org/v3', - 'kind': 'BGPPeer', - 'metadata': { - 'name': 'rr-mesh', - }, - 'spec': { - 'nodeSelector': 'has(routeReflectorClusterID)', - 'peerSelector': 'has(routeReflectorClusterID)', - }, - }) - - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - """ - Tear down the route reflector hosts when exiting context. - :return: None - """ - # Try to clean up what we can before exiting. - for rg in self.redundancy_groups: - while rg: - try: - self.pop_and_cleanup_route_reflector(rg, log_extra_diags=bool(exc_type)) - except KeyboardInterrupt: - raise - except Exception: - pass - - def pop_and_cleanup_route_reflector(self, redundancy_group, log_extra_diags=False): - """ - Pop a route reflector off the stack and clean it up. - """ - rr = redundancy_group.pop() - rr.cleanup(log_extra_diags=log_extra_diags) - - def get_redundancy_group(self): - """ - Return a redundancy group to use. This iterates through redundancy - groups each invocation. - :return: A list of RRs in the redundancy group. - """ - rg = self.redundancy_groups.pop(0) - self.redundancy_groups.append(rg) - return rg diff --git a/node/tests/st/utils/utils.py b/node/tests/st/utils/utils.py deleted file mode 100644 index 45435e54ede..00000000000 --- a/node/tests/st/utils/utils.py +++ /dev/null @@ -1,480 +0,0 @@ -# Copyright (c) 2015-2016 Tigera, Inc. All rights reserved. -# -# 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. -import datetime -import functools -import json -import logging -import os -import pdb -import re -import socket -import sys -from subprocess import CalledProcessError -from subprocess import check_output, STDOUT -from time import sleep - -import termios -import yaml -from netaddr import IPNetwork, IPAddress -from exceptions import CommandExecError - -LOCAL_IP_ENV = "MY_IP" -LOCAL_IPv6_ENV = "MY_IPv6" -logger = logging.getLogger(__name__) - -ETCD_SCHEME = os.environ.get("ETCD_SCHEME", "http") -ETCD_CA = os.environ.get("ETCD_CA_CERT_FILE", "") -ETCD_CERT = os.environ.get("ETCD_CERT_FILE", "") -ETCD_KEY = os.environ.get("ETCD_KEY_FILE", "") -ETCD_HOSTNAME_SSL = "etcd-authority-ssl" - -""" -Compile Regexes -""" -# Splits into groups that start w/ no whitespace and contain all lines below -# that start w/ whitespace -INTERFACE_SPLIT_RE = re.compile(r'(\d+:.*(?:\n\s+.*)+)') -# Grabs interface name -IFACE_RE = re.compile(r'^\d+: (\S+):') -# Grabs v4 addresses -IPV4_RE = re.compile(r'inet ((?:\d+\.){3}\d+)/\d+') -# Grabs v6 addresses -IPV6_RE = re.compile(r'inet6 ([a-fA-F\d:]+)/\d{1,3}') - - -def get_ip(v6=False): - """ - Return a string of the IP of the hosts interface. - Try to get the local IP from the environment variables. This allows - testers to specify the IP address in cases where there is more than one - configured IP address for the test system. - """ - env = LOCAL_IPv6_ENV if v6 else LOCAL_IP_ENV - ip = os.environ.get(env) - if not ip: - try: - logger.debug("%s not set; try to auto detect IP.", env) - socket_type = socket.AF_INET6 if v6 else socket.AF_INET - s = socket.socket(socket_type, socket.SOCK_DGRAM) - remote_ip = "2001:4860:4860::8888" if v6 else "8.8.8.8" - s.connect((remote_ip, 0)) - ip = s.getsockname()[0] - s.close() - except BaseException: - # Failed to connect, just try to get the address from the interfaces - version = 6 if v6 else 4 - ips = get_host_ips(version) - if ips: - ip = str(ips[0]) - else: - logger.debug("Got local IP from %s=%s", env, ip) - - return ip - - -# Some of the commands we execute like to mess with the TTY configuration, which can break the -# output formatting. As a workaround, save off the terminal settings and restore them after -# each command. -_term_settings = termios.tcgetattr(sys.stdin.fileno()) - - -def log_and_run(command, raise_exception_on_failure=True): - def log_output(results): - if results is None: - logger.info(" # ") - - lines = results.split("\n") - for line in lines: - logger.info(" # %s", line.rstrip()) - - try: - logger.info("[%s] %s", datetime.datetime.now(), command) - try: - results = check_output(command, shell=True, stderr=STDOUT).rstrip() - finally: - # Restore terminal settings in case the command we ran manipulated them. Note: - # under concurrent access, this is still not a perfect solution since another thread's - # child process may break the settings again before we log below. - termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, _term_settings) - log_output(results) - return results - except CalledProcessError as e: - # Wrap the original exception with one that gives a better error - # message (including command output). - logger.info(" # Return code: %s", e.returncode) - log_output(e.output) - if raise_exception_on_failure: - raise CommandExecError(e) - - -def retry_until_success(function, retries=10, ex_class=Exception, *args, **kwargs): - """ - Retries function until no exception is thrown. If exception continues, - it is reraised. - - :param function: the function to be repeatedly called - :param retries: the maximum number of times to retry the function. - A value of 0 will run the function once with no retries. - :param ex_class: The class of expected exceptions. - :returns: the value returned by function - """ - # We used to wait one second for every retry. In order to speed things up, - # we now wait .1 seconds, but to keep overall wait time the same we need - # to make a corresponding increase in the number of retries. - retries = 10 * retries - for retry in range(retries + 1): - try: - result = function(*args, **kwargs) - except ex_class: - if retry < retries: - sleep(.1) - else: - raise - else: - # Successfully ran the function - return result - - -def debug_failures(fn): - """ - Decorator function to decorate assertion methods to pause the live system - when an assertion fails, allowing the user to debug the problem. - :param fn: The function to decorate. - :return: The decorated function. - """ - - def wrapped(*args, **kwargs): - try: - return fn(*args, **kwargs) - except KeyboardInterrupt: - raise - except Exception as e: - if (os.getenv("DEBUG_FAILURES") is not None and - os.getenv("DEBUG_FAILURES").lower() == "true"): - logger.error("TEST FAILED:\n%s\nEntering DEBUG mode." - % e.message) - pdb.set_trace() - else: - raise - - return wrapped - - -@debug_failures -def check_bird_status(host, expected): - """ - Check the BIRD status on a particular host to see if it contains the - expected BGP status. - - :param host: The host object to check. - :param expected: A list of tuples containing: - (peertype, ip address, state) - where 'peertype' is one of "Global", "Mesh", "Node", 'ip address' is - the IP address of the peer, and state is the expected BGP state (e.g. - "Established" or "Idle"). - """ - output = host.calicoctl("node status") - lines = output.split("\n") - for (peertype, ipaddr, state) in expected: - for line in lines: - # Status table format is of the form: - # +--------------+-------------------+-------+----------+-------------+ - # | Peer address | Peer type | State | Since | Info | - # +--------------+-------------------+-------+----------+-------------+ - # | 172.17.42.21 | node-to-node mesh | up | 16:17:25 | Established | - # | 10.20.30.40 | global | start | 16:28:38 | Connect | - # | 192.10.0.0 | node specific | start | 16:28:57 | Connect | - # +--------------+-------------------+-------+----------+-------------+ - # - # Splitting based on | separators results in an array of the - # form: - # ['', 'Peer address', 'Peer type', 'State', 'Since', 'Info', ''] - columns = re.split("\s*\|\s*", line.strip()) - if len(columns) != 7: - continue - - if type(state) is not list: - state = [state] - - # Find the entry matching this peer. - if columns[1] == ipaddr and columns[2] == peertype: - - # Check that the connection state is as expected. We check - # that the state starts with the expected value since there - # may be additional diagnostic information included in the - # info field. - if any(columns[5].startswith(s) for s in state): - break - else: - msg = "Error in BIRD status for peer %s:\n" \ - "Expected: %s; Actual: %s\n" \ - "Output:\n%s" % (ipaddr, state, columns[5], - output) - raise AssertionError(msg) - else: - msg = "Error in BIRD status for peer %s:\n" \ - "Type: %s\n" \ - "Expected: %s\n" \ - "Output: \n%s" % (ipaddr, peertype, state, output) - raise AssertionError(msg) - -@debug_failures -def update_bgp_config(host, nodeMesh=None, asNum=None, nodeMeshMaxRestartTime=None): - response = host.calicoctl("get BGPConfiguration -o yaml") - bgpcfg = yaml.safe_load(response) - - if len(bgpcfg['items']) == 0: - bgpcfg = { - 'apiVersion': 'projectcalico.org/v3', - 'kind': 'BGPConfigurationList', - 'items': [ { - 'apiVersion': 'projectcalico.org/v3', - 'kind': 'BGPConfiguration', - 'metadata': { 'name': 'default', }, - 'spec': {} - } - ] - } - - if 'creationTimestamp' in bgpcfg['items'][0]['metadata']: - del bgpcfg['items'][0]['metadata']['creationTimestamp'] - - if nodeMesh is not None: - bgpcfg['items'][0]['spec']['nodeToNodeMeshEnabled'] = nodeMesh - - if asNum is not None: - bgpcfg['items'][0]['spec']['asNumber'] = asNum - - if nodeMeshMaxRestartTime is not None: - bgpcfg['items'][0]['spec']['nodeMeshMaxRestartTime'] = nodeMeshMaxRestartTime - - host.writejson("bgpconfig", bgpcfg) - host.calicoctl("apply -f bgpconfig") - host.execute("rm -f bgpconfig") - -@debug_failures -def get_bgp_spec(host): - response = host.calicoctl("get BGPConfiguration -o yaml") - bgpcfg = yaml.safe_load(response) - - return bgpcfg['items'][0]['spec'] - -@debug_failures -def assert_number_endpoints(host, expected): - """ - Check that a host has the expected number of endpoints in Calico - Parses the "calicoctl endpoint show" command for number of endpoints. - Raises AssertionError if the number of endpoints does not match the - expected value. - - :param host: DockerHost object - :param expected: int, number of expected endpoints - :return: None - """ - hostname = host.get_hostname() - out = host.calicoctl("get workloadEndpoint -o yaml") - output = yaml.safe_load(out) - actual = 0 - for endpoint in output['items']: - if endpoint['spec']['node'] == hostname: - actual += 1 - - if int(actual) != int(expected): - raise AssertionError( - "Incorrect number of endpoints on host %s: \n" - "Expected: %s; Actual: %s" % (hostname, expected, actual) - ) - - -@debug_failures -def assert_profile(host, profile_name): - """ - Check that profile is registered in Calico - Parse "calicoctl profile show" for the given profilename - - :param host: DockerHost object - :param profile_name: String of the name of the profile - :return: Boolean: True if found, False if not found - """ - out = host.calicoctl("get -o yaml profile") - output = yaml.safe_load(out) - found = False - for profile in output['items']: - if profile['metadata']['name'] == profile_name: - found = True - break - - if not found: - raise AssertionError("Profile %s not found in Calico" % profile_name) - - -def get_profile_name(host, network): - """ - Get the profile name from Docker - A profile is created in Docker for each Network object. - The profile name is a randomly generated string. - - :param host: DockerHost object - :param network: Network object - :return: String: profile name - """ - info_raw = host.execute("docker network inspect %s" % network.name) - info = json.loads(info_raw) - - # Network inspect returns a list of dicts for each network being inspected. - # We are only inspecting 1, so use the first entry. - return info[0]["Id"] - - -@debug_failures -def get_host_ips(version=4, exclude=None): - """ - Gets all IP addresses assigned to this host. - - Ignores Loopback Addresses - - This function is fail-safe and will return an empty array instead of - raising any exceptions. - - :param version: Desired IP address version. Can be 4 or 6. defaults to 4 - :param exclude: list of interface name regular expressions to ignore - (ex. ["^lo$","docker0.*"]) - :return: List of IPAddress objects. - """ - exclude = exclude or [] - ip_addrs = [] - - # Select Regex for IPv6 or IPv4. - ip_re = IPV4_RE if version is 4 else IPV6_RE - - # Call `ip addr`. - try: - ip_addr_output = check_output(["ip", "-%d" % version, "addr"]) - except (CalledProcessError, OSError): - print("Call to 'ip addr' Failed") - sys.exit(1) - - # Separate interface blocks from ip addr output and iterate. - for iface_block in INTERFACE_SPLIT_RE.findall(ip_addr_output): - # Try to get the interface name from the block - match = IFACE_RE.match(iface_block) - iface = match.group(1) - # Ignore the interface if it is explicitly excluded - if match and not any(re.match(regex, iface) for regex in exclude): - # Iterate through Addresses on interface. - for address in ip_re.findall(iface_block): - # Append non-loopback addresses. - if not IPNetwork(address).ip.is_loopback(): - ip_addrs.append(IPAddress(address)) - - return ip_addrs - -def curl_etcd(path, options=None, recursive=True, ip=None): - """ - Perform a curl to etcd, returning JSON decoded response. - :param path: The key path to query - :param options: Additional options to include in the curl - :param recursive: Whether we want recursive query or not - :return: The JSON decoded response. - """ - if options is None: - options = [] - if ETCD_SCHEME == "https": - # Etcd is running with SSL/TLS, require key/certificates - rc = check_output( - "curl --cacert %s --cert %s --key %s " - "-sL https://%s:2379/v2/keys/%s?recursive=%s %s" - % (ETCD_CA, ETCD_CERT, ETCD_KEY, ETCD_HOSTNAME_SSL, - path, str(recursive).lower(), " ".join(options)), - shell=True) - else: - rc = check_output( - "curl -sL http://%s:2379/v2/keys/%s?recursive=%s %s" - % (ip, path, str(recursive).lower(), " ".join(options)), - shell=True) - - return json.loads(rc.strip()) - -def wipe_etcd(ip): - # Delete /calico if it exists. This ensures each test has an empty data - # store at start of day. - curl_etcd("calico", options=["-XDELETE"], ip=ip) - - # Disable Usage Reporting to usage.projectcalico.org - # We want to avoid polluting analytics data with unit test noise - curl_etcd("calico/v1/config/UsageReportingEnabled", - options=["-XPUT -d value=False"], ip=ip) - - etcd_container_name = "calico-etcd" - tls_vars = "" - if ETCD_SCHEME == "https": - # Etcd is running with SSL/TLS, require key/certificates - etcd_container_name = "calico-etcd-ssl" - tls_vars = ("ETCDCTL_CACERT=/etc/calico/certs/ca.pem " + - "ETCDCTL_CERT=/etc/calico/certs/client.pem " + - "ETCDCTL_KEY=/etc/calico/certs/client-key.pem ") - - check_output("docker exec " + etcd_container_name + " sh -c '" + tls_vars + - "ETCDCTL_API=3 etcdctl del --prefix /calico" + - "'", shell=True) - - -on_failure_fns = [] - - -def clear_on_failures(): - global on_failure_fns - on_failure_fns = [] - - -def add_on_failure(fn): - on_failure_fns.append(fn) - - -def handle_failure(fn): - """ - Decorator for test methods so that, if they fail, they immediately print - information about the problem and run any defined on_failure functions. - :param fn: The function to decorate. - :return: The decorated function. - """ - @functools.wraps(fn) - def wrapped(*args, **kwargs): - try: - return fn(*args, **kwargs) - except KeyboardInterrupt: - raise - except Exception as e: - logger.exception("TEST FAILED") - for handler in on_failure_fns: - logger.info("Calling failure fn %r", handler) - handler() - raise - - return wrapped - - -def dump_etcdv3(): - etcd_container_name = "calico-etcd" - tls_vars = "" - if ETCD_SCHEME == "https": - # Etcd is running with SSL/TLS, require key/certificates - etcd_container_name = "calico-etcd-ssl" - tls_vars = ("ETCDCTL_CACERT=/etc/calico/certs/ca.pem " + - "ETCDCTL_CERT=/etc/calico/certs/client.pem " + - "ETCDCTL_KEY=/etc/calico/certs/client-key.pem ") - - log_and_run("docker exec " + etcd_container_name + " sh -c '" + tls_vars + - "ETCDCTL_API=3 etcdctl get --prefix /calico" + - "'") diff --git a/node/tests/st/utils/workload.py b/node/tests/st/utils/workload.py deleted file mode 100644 index 426cece025b..00000000000 --- a/node/tests/st/utils/workload.py +++ /dev/null @@ -1,454 +0,0 @@ -# Copyright (c) 2015-2016 Tigera, Inc. All rights reserved. -# -# 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. -import logging -import json -from functools import partial -from time import time - -from netaddr import IPAddress - -from exceptions import CommandExecError -from utils import retry_until_success, debug_failures, get_ip -from utils import ETCD_SCHEME, ETCD_CA, ETCD_CERT, ETCD_KEY, ETCD_HOSTNAME_SSL - - -NET_NONE = "none" - -logger = logging.getLogger(__name__) - - -class Workload(object): - """ - A calico workload. - - These are the end-users containers that will run application-level - software. - """ - - def __init__(self, host, name, image="busybox", network="bridge", - ip=None, labels=[], namespace=None): - """ - Create the workload and detect its IPs. - - :param host: The host container on which this workload is instantiated. - All commands executed by this container will be passed through the host - via docker exec. - :param name: The name given to the workload container. This name is - passed to docker and can be used inside docker commands. - :param image: The docker image to be used to instantiate this - container. busybox used by default because it is extremely small and - has ping. - :param network: The name of the network to connect to. - :param ip: The ip address to assign to the container. - :param labels: List of labels '=' to add to workload. - :param namespace: The namespace this pod should be in. 'None' is valid and will cause - CNI to be called without the namespace being set (useful for checking that it - defaults correctly) - """ - self.host = host - self.name = name - self.network = network - assert self.network is not None - self.namespace = namespace - self.labels = labels - - lbl_args = "" - for label in labels: - lbl_args += " --label %s" % (label) - - net_options = "--net=none" - - command = "docker run -tid --name %s %s %s %s" % (name, - net_options, - lbl_args, - image) - docker_run_wl = partial(host.execute, command) - retry_until_success(docker_run_wl) - - self.run_cni("ADD", ip=ip) - - def run_cni(self, add_or_del, ip=None): - adding = (add_or_del == "ADD") - workload_pid = self.host.execute( - "docker inspect --format '{{.State.Pid}}' %s" % self.name) - container_id = self.host.execute( - "docker inspect --format '{{.Id}}' %s" % self.name) - ip_json = (',"args":{"ip":"%s"}' % ip) if (ip and adding) else '' - ip_args = ('CNI_ARGS=IP=%s ' % ip) if (ip and adding) else '' - etcd_json = '"etcd_endpoints":"http://%s:2379",' % get_ip() - if ETCD_SCHEME == "https": - etcd_json = ('"etcd_endpoints":"https://%s:2379",' % ETCD_HOSTNAME_SSL + - '"etcd_ca_cert_file":"%s",' % ETCD_CA + - '"etcd_cert_file":"%s",' % ETCD_CERT + - '"etcd_key_file":"%s",' % ETCD_KEY) - - # Workout the labels_json to pass it to CNI. - label_kvs = "[" - for label in self.labels: - kvs = label.split('=') - label_kvs += '{"key":"%s", "value":"%s"},' % (kvs[0], kvs[1]) - if label_kvs.endswith(','): - label_kvs = label_kvs[:-1] - label_kvs += ']' - labels_json = ('"args":{"org.apache.mesos":{"network_info":{"labels":' + - '{"labels":%s}}}},' % label_kvs) - - # For non-k8s cluster, CNI takes namespace args and attaches a default - # profile with network name. - if self.namespace: - cni_args = 'CNI_ARGS=CNI_TEST_NAMESPACE=%s ' % self.namespace - else: - cni_args = '' - - command = ('echo \'{' + - '"name":"%s",' % self.network + - '"type":"calico",' + - etcd_json + - labels_json + - '"ipam":{"type":"calico-ipam"%s}' % ip_json + - '}\' | ' + - 'CNI_COMMAND=%s ' % add_or_del + - 'CNI_CONTAINERID=%s ' % container_id + - 'CNI_NETNS=/proc/%s/ns/net ' % workload_pid + - 'CNI_IFNAME=eth0 ' + - cni_args + - 'CNI_PATH=/code/dist ') - - command = command + ip_args + '/code/dist/calico' - output = self.host.execute(command) - - if adding: - # The CNI plugin writes its logging to stderr and its JSON output - - # including the IP address that we need - to stdout, but - # unfortunately 'docker exec' combines these into its own stdout, - # and that is what 'output' contains here. So we need heuristics - # to ignore the logging lines and pick up the JSON. Writing out - # the JSON is the last thing that the CNI plugin does, so it should - # be robust to ignore everything before a line that begins with a - # curly bracket. - json_text = "" - json_started = False - for line in output.split('\n'): - if not json_started and line.strip() == "{": - json_started = True - if json_started: - json_text = json_text + line - logger.debug("JSON text from Calico CNI = %s", json_text) - result = json.loads(json_text) - self.ip = result["ip4"]["ip"].split('/')[0] - - def execute(self, command): - """ - Execute arbitrary commands on this workload. - """ - # Make sure we've been created in the context of a host. Done here - # instead of in __init__ as we can't exist in the host until we're - # created. - assert self in self.host.workloads - return self.host.execute("docker exec %s %s" % (self.name, command)) - - def _get_ping_function(self, ip): - """ - Return a function to ping the supplied IP address from this workload. - - :param ip: The IPAddress to ping. - :return: A partial function that can be executed to perform the ping. - The function raises a CommandExecError exception if the ping fails, - or returns the output of the ping. - """ - # Default to "ping" - ping = "ping" - - try: - version = IPAddress(ip).version - assert version in [4, 6] - if version == 6: - ping = "ping6" - except BaseException: - pass - - args = [ - ping, - "-c", "1", # Number of pings - "-W", "1", # Timeout for each ping - ip, - ] - command = ' '.join(args) - - ping = partial(self.execute, command) - return ping - - @debug_failures - def check_can_ping(self, ip, retries=0): - """ - Execute a ping from this workload to the ip. Assert than a workload - can ping an IP. Use retries to allow for convergence. - - Use of this method assumes the network will be transitioning from a - state where the destination is currently unreachable. - - :param ip: The IP address (str or IPAddress) to ping. - :param retries: The number of retries. - :return: None. - """ - try: - retry_until_success(self._get_ping_function(ip), - retries=retries, - ex_class=CommandExecError) - except CommandExecError: - return False - - return True - - @debug_failures - def check_can_ping_continuously(self, ip, retries=0, timeout=180.0): - """ - Execute ping continuously until it fails (after n retries) or until - it times out. This should usually be run in a separate thread. - """ - start_time = time() - while time() < start_time + timeout: - if not self.check_can_ping(ip, retries=retries): - return False - return True - - @debug_failures - def check_cant_ping(self, ip, retries=0): - """ - Execute a ping from this workload to the ip. Assert that the workload - cannot ping an IP. Use retries to allow for convergence. - - Use of this method assumes the network will be transitioning from a - state where the destination is currently reachable. - - :param ip: The IP address (str or IPAddress) to ping. - :param retries: The number of retries. - :return: None. - """ - ping = self._get_ping_function(ip) - - def cant_ping(): - try: - ping() - except CommandExecError: - pass - else: - raise _PingError() - - try: - retry_until_success(cant_ping, - retries=retries, - ex_class=_PingError) - except _PingError: - return False - - return True - - def _get_tcp_function(self, ip): - """ - Return a function to check tcp connectivity to another ip. - - :param ip: The ip to check against. - :return: A partial function that can be executed to perform the check. - The function raises a CommandExecError exception if the check fails, - or returns the output of the check. - """ - # test_string = "hello" - args = [ - "/code/tcpping.sh", - ip, - ] - - command = ' '.join(args) - - tcp_check = partial(self.execute, command) - return tcp_check - - def _get_tcp_asym_function(self, ip): - """ - Return a function to check tcp connectivity to another ip. - - :param ip: The ip to check against. - :return: A partial function that can be executed to perform the check. - The function raises a CommandExecError exception if the check fails, - or returns the output of the check. - """ - # test_string = "hello" - args = [ - "/code/tcppingasym.sh", - ip, - ] - - command = ' '.join(args) - - tcp_asym_check = partial(self.execute, command) - return tcp_asym_check - - @debug_failures - def check_can_tcp(self, ip, retries=0): - """ - Execute a tcp check from this ip to the other ip. - Assert that a ip can connect to another ip. - Use retries to allow for convergence. - - Use of this method assumes the network will be transitioning from a - state where the destination is currently unreachable. - - :param ip: The ip to check connectivity to. - :param retries: The number of retries. - :return: None. - """ - try: - retry_until_success(self._get_tcp_function(ip), - retries=retries, - ex_class=CommandExecError) - except CommandExecError: - return False - - return True - - @debug_failures - def check_can_tcp_asym(self, ip, retries=0): - """ - Execute a tcp check from this ip to the other ip. - Assert that a ip can connect to another ip. - Use retries to allow for convergence. - Use of this method assumes the network will be transitioning from a - state where the destination is currently unreachable. - :param ip: The ip to check connectivity to. - :param retries: The number of retries. - :return: None. - """ - try: - retry_until_success(self._get_tcp_asym_function(ip), - retries=retries, - ex_class=CommandExecError) - except CommandExecError: - return False - - return True - - @debug_failures - def check_cant_tcp(self, ip, retries=0): - """ - Execute a ping from this workload to an ip. - Assert that the workload cannot connect to an IP using tcp. - Use retries to allow for convergence. - - Use of this method assumes the network will be transitioning from a - state where the destination is currently reachable. - - :param ip: The ip to check connectivity to. - :param retries: The number of retries. - :return: None. - """ - tcp_check = self._get_tcp_function(ip) - - def cant_tcp(): - try: - tcp_check() - except CommandExecError: - pass - else: - raise _PingError() - - try: - retry_until_success(cant_tcp, - retries=retries, - ex_class=_PingError) - except _PingError: - return False - - return True - - def _get_udp_function(self, ip): - """ - Return a function to check udp connectivity to another ip. - - :param ip: The ip to check against. - :return: A partial function that can be executed to perform the check. - The function raises a CommandExecError exception if the check fails, - or returns the output of the check. - """ - args = [ - "/code/udpping.sh", - ip, - ] - - command = ' '.join(args) - - udp_check = partial(self.execute, command) - return udp_check - - @debug_failures - def check_can_udp(self, ip, retries=0): - """ - Execute a udp check from this workload to an ip. - Assert that this workload can connect to another ip. - Use retries to allow for convergence. - - Use of this method assumes the network will be transitioning from a - state where the destination is currently unreachable. - - :param ip: The ip to check connectivity to. - :param retries: The number of retries. - :return: None. - """ - try: - retry_until_success(self._get_udp_function(ip), - retries=retries, - ex_class=CommandExecError) - except CommandExecError: - return False - return True - - @debug_failures - def check_cant_udp(self, ip, retries=0): - """ - Execute a udp check from this workload to the ip. Assert that - the workload cannot connect via udp to an IP. - Use retries to allow for convergence. - - Use of this method assumes the network will be transitioning from a - state where the destination is currently reachable. - - :param ip: The ip to check connectivity to. - :param retries: The number of retries. - :return: None. - """ - udp_check = self._get_udp_function(ip) - - def cant_udp(): - try: - udp_check() - except CommandExecError: - pass - else: - raise _PingError() - - try: - retry_until_success(cant_udp, - retries=retries, - ex_class=_PingError) - except _PingError: - return False - - return True - - def __str__(self): - return self.name - - -class _PingError(Exception): - pass diff --git a/node/tests/ut/test_log_parsing.py b/node/tests/ut/test_log_parsing.py deleted file mode 100644 index e88059b0464..00000000000 --- a/node/tests/ut/test_log_parsing.py +++ /dev/null @@ -1,142 +0,0 @@ -# Copyright 2016 Tigera, Inc -# -# 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. -import logging - -from nose_parameterized import parameterized - -from tests.st.test_base import TestBase -from tests.st.utils.docker_host import DockerHost -from tests.st.utils.utils import ETCD_CA, ETCD_CERT, \ - ETCD_KEY, ETCD_HOSTNAME_SSL, ETCD_SCHEME, get_ip - -_log = logging.getLogger(__name__) -_log.setLevel(logging.DEBUG) - -POST_DOCKER_COMMANDS = [] - -if ETCD_SCHEME == "https": - ADDITIONAL_DOCKER_OPTIONS = "--cluster-store=etcd://%s:2379 " \ - "--cluster-store-opt kv.cacertfile=%s " \ - "--cluster-store-opt kv.certfile=%s " \ - "--cluster-store-opt kv.keyfile=%s " % \ - (ETCD_HOSTNAME_SSL, ETCD_CA, ETCD_CERT, - ETCD_KEY) -else: - ADDITIONAL_DOCKER_OPTIONS = "--cluster-store=etcd://%s:2379 " % \ - get_ip() - -felix_logfile = "/var/log/calico/felix/current" -before_data = """2017-01-12 19:19:04.419 [INFO][87] ipip_mgr.go 75: Setting local IPv4 address on link. addr=192.168.151.0 link="tunl0" -2017-01-12 19:19:04.419 [INFO][87] int_dataplane.go 389: Received interface update msg=&intdataplane.ifaceUpdate{Name:"lo", State:"up"} -2017-01-12 19:19:04.419 [INFO][87] ipip_mgr.go 95: Removing old address addr=192.168.151.0 link="tunl0" oldAddr=192.168.151.0/32 tunl0 -2017-01-12 19:19:04.416 [INFO][87] syncer.go 247: etcd watch thread started. -2017-01-12 19:19:04.419 [INFO][87] int_dataplane.go 378: Received update from calculation graph msg=config: config: config: config: config: config: config: config: config: config: config: config: config: config: config: config: config: config: config: config: -2017-01-12 19:19:04.419 [INFO][87] int_dataplane.go 398: Received interface addresses update msg=&intdataplane.ifaceAddrsUpdate{Name:"lo", Addrs:set.mapSet{"127.0.0.1":set.empty{}, "::1":set.empty{}}} -2017-01-12 19:19:04.419 [INFO][87] int_dataplane.go 398: Received interface addresses update msg=&intdataplane.ifaceAddrsUpdate{Name:"ens4", Addrs:set.mapSet{"fe80::4001:aff:fef0:30":set.empty{}, "10.240.0.48":set.empty{}}} -2017-01-12 19:19:04.419 [INFO][87] int_dataplane.go 398: Received interface addresses update msg=&intdataplane.ifaceAddrsUpdate{Name:"calic50350b9abf", Addrs:set.mapSet{"fe80::a077:a7ff:fe1c:8436":set.empty{}}} -2017-01-12 19:19:04.420 [INFO][87] int_dataplane.go 398: Received interface addresses update msg=&intdataplane.ifaceAddrsUpdate{Name:"calie9054722202", Addrs:set.mapSet{"fe80::d046:64ff:fe86:c21":set.empty{}}} -2017-01-12 19:19:04.420 [INFO][87] int_dataplane.go 398: Received interface addresses update msg=&intdataplane.ifaceAddrsUpdate{Name:"cali076a4d2f51a", Addrs:set.mapSet{"fe80::2c6e:f7ff:fe0d:2b86":set.empty{}}} -2017-01-12 19:19:04.420 [INFO][87] syncer.go 261: Polled etcd for initial watch index. index=0x3f85 -2017-01-12 19:19:04.420 [INFO][87] int_dataplane.go 398: Received interface addresses update msg=&intdataplane.ifaceAddrsUpdate{Name:"cali76b1299437f", Addrs:set.mapSet{"fe80::30f3:a9ff:fe6e:2d22":set.empty{}}} -2017-01-12 19:19:04.420 [INFO][87] int_dataplane.go 398: Received interface addresses update msg=&intdataplane.ifaceAddrsUpdate{Name:"cali477d4934e36", Addrs:set.mapSet{"fe80::70d6:51ff:feca:1b41":set.empty{}}} -2017-01-12 19:19:04.419 [INFO][87] iface_monitor.go 120: Netlink address update. addr="192.168.151.0" exists=false ifIndex=14 -2017-01-12 19:19:04.421 [INFO][87] int_dataplane.go 389: Received interface update msg=&intdataplane.ifaceUpdate{Name:"ens4", State:"up"} -2017-01-12 19:19:04.421 [INFO][87] int_dataplane.go 288: Linux interface addrs changed. addrs=set.mapSet{} ifaceName="tunl0" -2017-01-12 19:19:04.420 [INFO][87] syncer.go 461: Watcher out-of-sync, starting to track deletions -2017-01-12 19:19:04.421 [INFO][87] int_dataplane.go 389: Received interface update msg=&intdataplane.ifaceUpdate{Name:"calic50350b9abf", State:"up"} -2017-01-12 19:19:04.421 [INFO][87] ipip_mgr.go 103: Address wasn't present, adding it. addr=192.168.151.0 link="tunl0" -2017-01-12 19:19:04.421 [INFO][87] int_dataplane.go 398: Received interface addresses update msg=&intdataplane.ifaceAddrsUpdate{Name:"cali3b05e50a7e8", Addrs:set.mapSet{"fe80::94d3:2bff:fe26:e4fe":set.empty{}}} -2017-01-12 19:19:04.421 [INFO][87] iface_monitor.go 120: Netlink address update. addr="192.168.151.0" exists=true ifIndex=14 -2017-01-12 19:19:04.421 [INFO][87] syncer.go 500: Watcher is out-of-sync but no snapshot in progress, starting one. -""" - - -class LogParsing(TestBase): - @classmethod - def setUpClass(cls): - cls.log_banner("TEST SET UP STARTING: %s", cls.__name__) - - cls.hosts = [] - cls.hosts.append(DockerHost("host1", - additional_docker_options=ADDITIONAL_DOCKER_OPTIONS, - post_docker_commands=POST_DOCKER_COMMANDS, - start_calico=False)) - cls.hosts.append(DockerHost("host2", - additional_docker_options=ADDITIONAL_DOCKER_OPTIONS, - post_docker_commands=POST_DOCKER_COMMANDS, - start_calico=False)) - for host in cls.hosts: - host.execute("mkdir -p /var/log/calico/felix/") - host.writefile(felix_logfile, before_data) - host.attach_log_analyzer() - cls.expect_errors = False - - @classmethod - def tearDownClass(cls): - # Tidy up - for host in cls.hosts: - host.cleanup() - del host - - def setUp(self): - self.log_banner("starting %s", self._testMethodName) - - _log.debug("Reset Log Analyzers") - for host in self.hosts: - host.log_analyzer.reset() - - def tearDown(self): - _log.info("Checking logs for exceptions") - failed = False - if self.expect_errors: - try: - for host in self.hosts: - host.log_analyzer.check_logs_for_exceptions() - _log.debug("No exceptions found, setting failed=True") - failed = True - except AssertionError: - _log.debug("Hit the error we expected. Good.") - _log.debug("Failed = %s", failed) - assert not failed, "Did not hit error! Fail." - else: - for host in self.hosts: - host.log_analyzer.check_logs_for_exceptions() - - def test_no_logs(self): - """ - Tests that the scenario with no new logs works OK - """ - _log.debug("\n") - self.expect_errors = False - - @parameterized.expand([ - ("ERROR", - "2017-01-12 19:19:05.421 [ERROR][87] syncer.go 500: Watcher is out-of-sync.", - True), - ("INFO", - "2017-01-12 19:19:05.421 [INFO][87] syncer.go 500: Watcher is out-of-sync.", - False), - ]) - def test_newlog(self, name, log, expect_error): - """ - Tests that the scenario with a new log works OK - """ - self.__name__ = name - _log.debug("\n") - self.expect_errors = expect_error - self.hosts[0].execute("echo %s >> %s" % (log, felix_logfile)) - - -class IpNotFound(Exception): - pass diff --git a/node/windows-packaging/CalicoWindows/node/node-service.ps1 b/node/windows-packaging/CalicoWindows/node/node-service.ps1 index 3b84bf09e61..d062be9085b 100644 --- a/node/windows-packaging/CalicoWindows/node/node-service.ps1 +++ b/node/windows-packaging/CalicoWindows/node/node-service.ps1 @@ -198,7 +198,7 @@ while ($True) { Write-Host "Calico node initialisation succeeded; monitoring kubelet for restarts..." # Token refresher only needs to run in hostprocess containers - if ($env:CONTAINER_SANDBOX_MOUNT_POINT) { + if ($env:CONTAINER_SANDBOX_MOUNT_POINT -AND ("$env:CNI_PLUGIN_TYPE" -eq "Calico")) { Restart-TokenRefresher } break @@ -216,7 +216,7 @@ while ($True) } # Token refresher only needs to run in hostprocess containers - if ($env:CONTAINER_SANDBOX_MOUNT_POINT) { + if ($env:CONTAINER_SANDBOX_MOUNT_POINT -AND ("$env:CNI_PLUGIN_TYPE" -eq "Calico")) { Ensure-TokenRefresher } diff --git a/node/windows-packaging/CalicoWindows/uninstall-calico-hpc.ps1 b/node/windows-packaging/CalicoWindows/uninstall-calico-hpc.ps1 index d9d596438c0..034e76bbb32 100644 --- a/node/windows-packaging/CalicoWindows/uninstall-calico-hpc.ps1 +++ b/node/windows-packaging/CalicoWindows/uninstall-calico-hpc.ps1 @@ -38,43 +38,46 @@ function Remove-CalicoService($ServiceName) } } -if ("$env:CNI_NET_DIR" -eq $null) -{ - Write-Host "CNI_NET_DIR env var not set, skipping Calico CNI config cleanup" -} elseif (-not (Test-Path "$env:CONTAINER_SANDBOX_MOUNT_POINT/$env:CNI_NET_DIR")) -{ - Write-Host "$env:CNI_NET_DIR dir does not exist, skipping Calico CNI config cleanup" -} else -{ - $cniConfFile = "$env:CONTAINER_SANDBOX_MOUNT_POINT/$env:CNI_NET_DIR/10-calico.conf" - if (Test-Path $cniConfFile) { - Write-Host "Removing Calico CNI conf file at $cniConfFile ..." - rm $cniConfFile - } - - if (Test-Path "$env:CNI_CONF_NAME") { - $cniConfListFile = "$env:CONTAINER_SANDBOX_MOUNT_POINT/$env:CNI_NET_DIR/$env:CNI_CONF_NAME" - if (Test-Path $cniConfListFile) { - Write-Host "Removing Calico CNI conf file at $cniConfListFile ..." - rm $cniConfListFile +# Only clean up CNI dirs if using Calico CNI +if ("$env:CNI_PLUGIN_TYPE" -eq "Calico") { + if ("$env:CNI_NET_DIR" -eq $null) + { + Write-Host "CNI_NET_DIR env var not set, skipping Calico CNI config cleanup" + } elseif (-not (Test-Path "$env:CONTAINER_SANDBOX_MOUNT_POINT/$env:CNI_NET_DIR")) + { + Write-Host "$env:CNI_NET_DIR dir does not exist, skipping Calico CNI config cleanup" + } else + { + $cniConfFile = "$env:CONTAINER_SANDBOX_MOUNT_POINT/$env:CNI_NET_DIR/10-calico.conf" + if (Test-Path $cniConfFile) { + Write-Host "Removing Calico CNI conf file at $cniConfFile ..." + rm $cniConfFile } + + if (Test-Path "$env:CNI_CONF_NAME") { + $cniConfListFile = "$env:CONTAINER_SANDBOX_MOUNT_POINT/$env:CNI_NET_DIR/$env:CNI_CONF_NAME" + if (Test-Path $cniConfListFile) { + Write-Host "Removing Calico CNI conf file at $cniConfListFile ..." + rm $cniConfListFile + } + } } -} -if ("$env:CNI_BIN_DIR" -eq $null) -{ - Write-Host "CNI_BIN_DIR env var not set, skipping Calico CNI binary cleanup" -} elseif (-not (Test-Path "$env:CONTAINER_SANDBOX_MOUNT_POINT/$env:CNI_BIN_DIR")) -{ - Write-Host "$env:CNI_BIN_DIR dir does not exist, skipping Calico CNI binary cleanup" -} else -{ - $cniBinPath = "$env:CONTAINER_SANDBOX_MOUNT_POINT/$env:CNI_BIN_DIR/calico*.exe" - if (Test-Path $cniBinPath) { - Write-Host "Removing Calico CNI binaries at $cniBinPath ..." - rm $cniBinPath - } + if ("$env:CNI_BIN_DIR" -eq $null) + { + Write-Host "CNI_BIN_DIR env var not set, skipping Calico CNI binary cleanup" + } elseif (-not (Test-Path "$env:CONTAINER_SANDBOX_MOUNT_POINT/$env:CNI_BIN_DIR")) + { + Write-Host "$env:CNI_BIN_DIR dir does not exist, skipping Calico CNI binary cleanup" + } else + { + $cniBinPath = "$env:CONTAINER_SANDBOX_MOUNT_POINT/$env:CNI_BIN_DIR/calico*.exe" + if (Test-Path $cniBinPath) { + Write-Host "Removing Calico CNI binaries at $cniBinPath ..." + rm $cniBinPath + } + } } Write-Host "Stopping and removing Calico services if they are present..." @@ -83,12 +86,17 @@ Remove-CalicoService CalicoFelix Remove-CalicoService CalicoNode Remove-CalicoService CalicoUpgrade -Write-Host "Stopping and removing kube-proxy service if it is present..." -Write-Host "It is recommended to run kube-proxy as kubernetes daemonset instead" -Remove-CalicoService kube-proxy +# Only remove kube-proxy service if using Calico CNI (the recommended kube-proxy +# daemonset from sig-windows only supports Calico CNI) +if ("$env:CNI_PLUGIN_TYPE" -eq "Calico") { + Write-Host "Stopping and removing kube-proxy service if it is present..." + Write-Host "It is recommended to run kube-proxy as kubernetes daemonset instead" + Remove-CalicoService kube-proxy +} Write-Host "Logging containerd CNI bin and conf dir paths:" Get-Content "$env:ProgramFiles/containerd/config.toml" | Select-String -Pattern "^(\s)*bin_dir = (.)*$" +Get-Content "$env:ProgramFiles/containerd/config.toml" | Select-String -Pattern "^(\s)*bin_dirs = (.)*$" Get-Content "$env:ProgramFiles/containerd/config.toml" | Select-String -Pattern "^(\s)*conf_dir = (.)*$" Get-Module 'calico' | Remove-Module -Force diff --git a/node/windows-packaging/tests/calico_suite_test.go b/node/windows-packaging/tests/calico_suite_test.go index f3c6df8e60a..5a620fa41e2 100644 --- a/node/windows-packaging/tests/calico_suite_test.go +++ b/node/windows-packaging/tests/calico_suite_test.go @@ -17,9 +17,8 @@ package main_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestCommands(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/calico_cni_config_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Calico CNI Config Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/calico_cni_config_suite.xml" + ginkgo.RunSpecs(t, "Calico CNI Config Suite", suiteConfig, reporterConfig) } diff --git a/node/windows-packaging/tests/cni_conf_test.go b/node/windows-packaging/tests/cni_conf_test.go index 927b76aff48..2bc8052dce9 100644 --- a/node/windows-packaging/tests/cni_conf_test.go +++ b/node/windows-packaging/tests/cni_conf_test.go @@ -19,7 +19,7 @@ import ( "encoding/json" "os" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -35,7 +35,7 @@ var _ = Describe("Windows CNI config template tests", func() { f = bytes.ReplaceAll(f, []byte("__DNS_NAME_SERVERS__"), []byte("0")) f = bytes.ReplaceAll(f, []byte("__DSR_SUPPORT__"), []byte("0")) - var data map[string]interface{} + var data map[string]any err = json.Unmarshal(f, &data) Expect(err).NotTo(HaveOccurred()) }) diff --git a/pkg/cmdwrapper/cmdwrapper.go b/pkg/cmdwrapper/cmdwrapper.go index bec7a00a2db..e284b6c2381 100644 --- a/pkg/cmdwrapper/cmdwrapper.go +++ b/pkg/cmdwrapper/cmdwrapper.go @@ -64,7 +64,7 @@ func Run() { var wg sync.WaitGroup wg.Add(1) - stop := make(chan interface{}) + stop := make(chan any) go func() { defer wg.Done() for { diff --git a/pod2daemon/binder/creds.go b/pod2daemon/binder/creds.go index ea1dafa8956..4551f49f9d1 100644 --- a/pod2daemon/binder/creds.go +++ b/pod2daemon/binder/creds.go @@ -17,6 +17,7 @@ package binder import ( "context" "errors" + "maps" "net" "sync" @@ -63,9 +64,7 @@ func (s *workloadStore) Clone() credentials.TransportCredentials { other := make(map[string]workload) s.mu.RLock() defer s.mu.RUnlock() - for k, v := range s.creds { - other[k] = v - } + maps.Copy(other, s.creds) return &workloadStore{creds: other} } diff --git a/pod2daemon/binder/watcher.go b/pod2daemon/binder/watcher.go index b3564a349d7..8877c032252 100644 --- a/pod2daemon/binder/watcher.go +++ b/pod2daemon/binder/watcher.go @@ -16,6 +16,7 @@ package binder import ( "log" + "maps" "os" "path/filepath" "strings" @@ -117,8 +118,8 @@ func (p *pollWatcher) poll(events chan<- workloadEvent, stop <-chan bool) { } func parseFilename(name string) (isCred bool, uid string) { - if strings.HasSuffix(name, CredentialsExtension) { - return true, strings.TrimSuffix(name, CredentialsExtension) + if before, ok := strings.CutSuffix(name, CredentialsExtension); ok { + return true, before } else { return false, "" } @@ -126,8 +127,6 @@ func parseFilename(name string) (isCred bool, uid string) { func copyStringSet(original map[string]bool) map[string]bool { n := make(map[string]bool) - for k, v := range original { - n[k] = v - } + maps.Copy(n, original) return n } diff --git a/pod2daemon/csidriver/Dockerfile b/pod2daemon/csidriver/Dockerfile index 3a8ee45af03..04c2105bea5 100644 --- a/pod2daemon/csidriver/Dockerfile +++ b/pod2daemon/csidriver/Dockerfile @@ -33,6 +33,14 @@ LABEL org.opencontainers.image.vendor="Project Calico" LABEL org.opencontainers.image.version="${GIT_VERSION}" LABEL org.opencontainers.image.licenses="Apache-2.0" +LABEL description="Calico CSI driver to setup secure connections from Kubernetes pods to local daemons" +LABEL maintainer="maintainers@tigera.io" +LABEL name="Calico CSI Driver" +LABEL release=1 +LABEL summary="Calico CSI driver to setup secure connections from Kubernetes pods to local daemons" +LABEL vendor="Project Calico" +LABEL version="${GIT_VERSION}" + COPY --from=source / / ENTRYPOINT ["/usr/bin/csi-driver"] diff --git a/pod2daemon/deps.txt b/pod2daemon/deps.txt index 58fe50c1c50..1627ab96325 100644 --- a/pod2daemon/deps.txt +++ b/pod2daemon/deps.txt @@ -2,24 +2,25 @@ This file contains the list of modules that this package depends on in order to trigger CI on changes -go 1.25.1 +go 1.25.7 github.com/beorn7/perks v1.0.1 github.com/cespare/xxhash/v2 v2.3.0 github.com/container-storage-interface/spec v1.9.0 github.com/golang/protobuf v1.5.4 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 github.com/projectcalico/calico -github.com/prometheus/client_golang v1.23.0 +github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 -github.com/prometheus/common v0.65.0 -github.com/prometheus/procfs v0.17.0 +github.com/prometheus/common v0.67.2 +github.com/prometheus/procfs v0.19.2 github.com/sirupsen/logrus v1.9.3 -github.com/spf13/cobra v1.9.1 -github.com/spf13/pflag v1.0.7 -golang.org/x/net v0.43.0 -golang.org/x/sys v0.35.0 -golang.org/x/text v0.28.0 +github.com/spf13/cobra v1.10.1 +github.com/spf13/pflag v1.0.10 +go.yaml.in/yaml/v2 v2.4.3 +golang.org/x/net v0.49.0 +golang.org/x/sys v0.40.0 +golang.org/x/text v0.33.0 google.golang.org/genproto v0.0.0-20250603155806-513f23925822 -google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a -google.golang.org/grpc v1.72.2 -google.golang.org/protobuf v1.36.7 +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c +google.golang.org/grpc v1.76.0 +google.golang.org/protobuf v1.36.10 diff --git a/pod2daemon/flexvol/docker-image/Dockerfile b/pod2daemon/flexvol/docker-image/Dockerfile index e6588b77b76..ce6123ff5c1 100644 --- a/pod2daemon/flexvol/docker-image/Dockerfile +++ b/pod2daemon/flexvol/docker-image/Dockerfile @@ -34,9 +34,7 @@ COPY --from=ubi /usr/bin/rm /usr/bin/rm COPY --from=ubi /lib64/libacl.so.1 /lib64/libacl.so.1 COPY --from=ubi /lib64/libattr.so.1 /lib64/libattr.so.1 COPY --from=ubi /lib64/libcap.so.2 /lib64/libcap.so.2 -COPY --from=ubi /lib64/libdl.so.2 /lib64/libdl.so.2 COPY --from=ubi /lib64/libpcre2-8.so.0 /lib64/libpcre2-8.so.0 -COPY --from=ubi /lib64/librt.so.1 /lib64/librt.so.1 COPY --from=ubi /lib64/libselinux.so.1 /lib64/libselinux.so.1 COPY --from=ubi /lib64/libtinfo.so.6 /lib64/libtinfo.so.6 @@ -57,6 +55,14 @@ LABEL org.opencontainers.image.vendor="Project Calico" LABEL org.opencontainers.image.version="${GIT_VERSION}" LABEL org.opencontainers.image.licenses="Apache-2.0" +LABEL description="Calico FlexVolume driver installer to setup secure connections from Kubernetes pods to local daemons" +LABEL maintainer="maintainers@tigera.io" +LABEL name="Calico FlexVolume driver installer" +LABEL release=1 +LABEL summary="Calico FlexVolume driver installer to setup secure connections from Kubernetes pods to local daemons" +LABEL vendor="Project Calico" +LABEL version="${GIT_VERSION}" + COPY --from=source / / ENTRYPOINT ["/usr/local/bin/flexvol.sh"] diff --git a/pod2daemon/node-driver-registrar-docker/Dockerfile b/pod2daemon/node-driver-registrar-docker/Dockerfile index 9789b0b9c62..2a99b3d3bb3 100644 --- a/pod2daemon/node-driver-registrar-docker/Dockerfile +++ b/pod2daemon/node-driver-registrar-docker/Dockerfile @@ -34,6 +34,13 @@ LABEL org.opencontainers.image.vendor="Project Calico" LABEL org.opencontainers.image.version="${UPSTREAM_VER}+calico-${GIT_VERSION}" LABEL org.opencontainers.image.licenses="Apache-2.0" +LABEL description="CSI Node driver registrar handles registration of CSI drivers with the kubelet. This is the upstream version repackaged for Calico." +LABEL maintainer="maintainers@tigera.io" +LABEL name="CSI Node driver registrar" +LABEL release=1 +LABEL summary="CSI Node driver registrar handles registration of CSI drivers with the kubelet. This is the upstream version repackaged for Calico." +LABEL vendor="Project Calico" +LABEL version="${UPSTREAM_VER}+calico-${GIT_VERSION}" COPY --from=source / / diff --git a/pod2daemon/proto/udsver.pb.go b/pod2daemon/proto/udsver.pb.go index 51bab1e62a2..09789082941 100644 --- a/pod2daemon/proto/udsver.pb.go +++ b/pod2daemon/proto/udsver.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 -// protoc v6.32.0 +// protoc-gen-go v1.36.11 +// protoc v6.33.4 // source: udsver.proto package proto diff --git a/pod2daemon/proto/udsver_grpc.pb.go b/pod2daemon/proto/udsver_grpc.pb.go index 5fb50b59034..15892bc8417 100644 --- a/pod2daemon/proto/udsver_grpc.pb.go +++ b/pod2daemon/proto/udsver_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.5.1 -// - protoc v6.32.0 +// - protoc-gen-go-grpc v1.6.0 +// - protoc v6.33.4 // source: udsver.proto package proto @@ -64,7 +64,7 @@ type VerifyServer interface { type UnimplementedVerifyServer struct{} func (UnimplementedVerifyServer) Check(context.Context, *Request) (*Response, error) { - return nil, status.Errorf(codes.Unimplemented, "method Check not implemented") + return nil, status.Error(codes.Unimplemented, "method Check not implemented") } func (UnimplementedVerifyServer) mustEmbedUnimplementedVerifyServer() {} func (UnimplementedVerifyServer) testEmbeddedByValue() {} @@ -77,7 +77,7 @@ type UnsafeVerifyServer interface { } func RegisterVerifyServer(s grpc.ServiceRegistrar, srv VerifyServer) { - // If the following call pancis, it indicates UnimplementedVerifyServer was + // If the following call panics, it indicates UnimplementedVerifyServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. diff --git a/process/testing/aso/.gitignore b/process/testing/aso/.gitignore new file mode 100644 index 00000000000..7335a8e7ded --- /dev/null +++ b/process/testing/aso/.gitignore @@ -0,0 +1,26 @@ +.aso-installed +.calico-installed +.kubeadm-set-up +.vmss-created +.sshkey +.sshkey.pub +cert-manager.yaml +connect.txt +infra/manifests/ +EE/manifests/ +OSS/manifests/ +kind-kubeconfig +kubeadm-config.yaml +kubeconfig +operator-crds.yaml +password.txt +scp-from-windows.sh +scp-to-windows.sh +ssh-node-linux.sh +ssh-node-windows.sh +tigera-operator.yaml +tigera-prometheus-operator.yaml +windows/*.exe +windows/config +windows/kubeadm/ +windows/run-fv.ps1 diff --git a/process/testing/aso/EE/templates/custom-resources.bgp.yaml b/process/testing/aso/EE/templates/custom-resources.bgp.yaml new file mode 100644 index 00000000000..ce829cca0b8 --- /dev/null +++ b/process/testing/aso/EE/templates/custom-resources.bgp.yaml @@ -0,0 +1,100 @@ +# This section includes base Calico Enterprise installation configuration. +# For more information, see: https://docs.tigera.io/master/reference/installation/api#operator.tigera.io/v1.Installation +apiVersion: operator.tigera.io/v1 +kind: Installation +metadata: + name: default +spec: + # Install Calico Enterprise + variant: TigeraSecureEnterprise + + # List of image pull secrets to use when installing images from a container registry. + # If specified, secrets must be created in the `tigera-operator` namespace. + imagePullSecrets: + - name: tigera-pull-secret + # Optionally, a custom registry to use for pulling images. + # registry: + + cni: + type: Calico + calicoNetwork: + bgp: Enabled + linuxDataplane: {{$.Env.ASO_LINUX_DATAPLANE}} + ipPools: + - cidr: 192.168.0.0/16 + encapsulation: None + windowsDataplane: HNS + serviceCIDRs: + - 10.96.0.0/12 + windowsNodes: + cniBinDir: "/Program Files/containerd/cni/bin" + cniConfigDir: "/Program Files/containerd/cni/conf" + cniLogDir: /var/log/calico/cni +--- +# This section configures the Tigera web manager. +# Remove this section for a Managed cluster. +# For more information, see: https://docs.tigera.io/master/reference/installation/api#operator.tigera.io/v1.Manager +apiVersion: operator.tigera.io/v1 +kind: Manager +metadata: + name: tigera-secure + +--- +# This section installs and configures the Calico Enterprise API server. +# For more information, see: https://docs.tigera.io/master/reference/installation/api#operator.tigera.io/v1.APIServer +apiVersion: operator.tigera.io/v1 +kind: APIServer +metadata: + name: tigera-secure + +--- +# This section installs and configures Calico Enterprise compliance functionality. +# For more information, see: https://docs.tigera.io/master/reference/installation/api#operator.tigera.io/v1.Compliance +apiVersion: operator.tigera.io/v1 +kind: Compliance +metadata: + name: tigera-secure + +--- +# This section installs and configures Calico Enterprise intrusion detection functionality. +# For more information, see: https://docs.tigera.io/master/reference/installation/api#operator.tigera.io/v1.IntrusionDetection +apiVersion: operator.tigera.io/v1 +kind: IntrusionDetection +metadata: + name: tigera-secure + +--- +# This section configures the Elasticsearch cluster used by Calico Enterprise. +# Remove this section for a Managed cluster. +# For more information, see: https://docs.tigera.io/master/reference/installation/api#operator.tigera.io/v1.LogStorage +apiVersion: operator.tigera.io/v1 +kind: LogStorage +metadata: + name: tigera-secure +spec: + nodes: + count: 1 + +--- +# This section configures collection of Tigera flow, DNS, and audit logs. +# For more information, see: https://docs.tigera.io/master/reference/installation/api#operator.tigera.io/v1.LogCollector +apiVersion: operator.tigera.io/v1 +kind: LogCollector +metadata: + name: tigera-secure + +--- +# This section configures Prometheus for Calico Enterprise. +# For more information, see: https://docs.tigera.io/master/reference/installation/api#operator.tigera.io/v1.Monitor +apiVersion: operator.tigera.io/v1 +kind: Monitor +metadata: + name: tigera-secure + +--- +# This section installs and configures Calico Enterprise policy recommendation functionality. +# For more information, see: https://docs.tigera.io/master/reference/installation/api#operator.tigera.io/v1.PolicyRecommendation +apiVersion: operator.tigera.io/v1 +kind: PolicyRecommendation +metadata: + name: tigera-secure diff --git a/process/testing/aso/EE/templates/custom-resources.yaml b/process/testing/aso/EE/templates/custom-resources.yaml new file mode 100644 index 00000000000..e213f3e626f --- /dev/null +++ b/process/testing/aso/EE/templates/custom-resources.yaml @@ -0,0 +1,100 @@ +# This section includes base Calico Enterprise installation configuration. +# For more information, see: https://docs.tigera.io/master/reference/installation/api#operator.tigera.io/v1.Installation +apiVersion: operator.tigera.io/v1 +kind: Installation +metadata: + name: default +spec: + # Install Calico Enterprise + variant: TigeraSecureEnterprise + + # List of image pull secrets to use when installing images from a container registry. + # If specified, secrets must be created in the `tigera-operator` namespace. + imagePullSecrets: + - name: tigera-pull-secret + # Optionally, a custom registry to use for pulling images. + # registry: + + cni: + type: Calico + calicoNetwork: + bgp: Disabled + linuxDataplane: {{$.Env.ASO_LINUX_DATAPLANE}} + ipPools: + - cidr: 192.168.0.0/16 + encapsulation: VXLAN + windowsDataplane: HNS + serviceCIDRs: + - 10.96.0.0/12 + windowsNodes: + cniBinDir: "/Program Files/containerd/cni/bin" + cniConfigDir: "/Program Files/containerd/cni/conf" + cniLogDir: /var/log/calico/cni +--- +# This section configures the Tigera web manager. +# Remove this section for a Managed cluster. +# For more information, see: https://docs.tigera.io/master/reference/installation/api#operator.tigera.io/v1.Manager +apiVersion: operator.tigera.io/v1 +kind: Manager +metadata: + name: tigera-secure + +--- +# This section installs and configures the Calico Enterprise API server. +# For more information, see: https://docs.tigera.io/master/reference/installation/api#operator.tigera.io/v1.APIServer +apiVersion: operator.tigera.io/v1 +kind: APIServer +metadata: + name: tigera-secure + +--- +# This section installs and configures Calico Enterprise compliance functionality. +# For more information, see: https://docs.tigera.io/master/reference/installation/api#operator.tigera.io/v1.Compliance +apiVersion: operator.tigera.io/v1 +kind: Compliance +metadata: + name: tigera-secure + +--- +# This section installs and configures Calico Enterprise intrusion detection functionality. +# For more information, see: https://docs.tigera.io/master/reference/installation/api#operator.tigera.io/v1.IntrusionDetection +apiVersion: operator.tigera.io/v1 +kind: IntrusionDetection +metadata: + name: tigera-secure + +--- +# This section configures the Elasticsearch cluster used by Calico Enterprise. +# Remove this section for a Managed cluster. +# For more information, see: https://docs.tigera.io/master/reference/installation/api#operator.tigera.io/v1.LogStorage +apiVersion: operator.tigera.io/v1 +kind: LogStorage +metadata: + name: tigera-secure +spec: + nodes: + count: 1 + +--- +# This section configures collection of Tigera flow, DNS, and audit logs. +# For more information, see: https://docs.tigera.io/master/reference/installation/api#operator.tigera.io/v1.LogCollector +apiVersion: operator.tigera.io/v1 +kind: LogCollector +metadata: + name: tigera-secure + +--- +# This section configures Prometheus for Calico Enterprise. +# For more information, see: https://docs.tigera.io/master/reference/installation/api#operator.tigera.io/v1.Monitor +apiVersion: operator.tigera.io/v1 +kind: Monitor +metadata: + name: tigera-secure + +--- +# This section installs and configures Calico Enterprise policy recommendation functionality. +# For more information, see: https://docs.tigera.io/master/reference/installation/api#operator.tigera.io/v1.PolicyRecommendation +apiVersion: operator.tigera.io/v1 +kind: PolicyRecommendation +metadata: + name: tigera-secure diff --git a/process/testing/aso/EE/templates/persistent-volume.yaml b/process/testing/aso/EE/templates/persistent-volume.yaml new file mode 100644 index 00000000000..929f66fd885 --- /dev/null +++ b/process/testing/aso/EE/templates/persistent-volume.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: tigera-elasticsearch +spec: + capacity: + storage: 10Gi + accessModes: + - ReadWriteOnce + hostPath: + path: /mnt/tigera/elastic-data + persistentVolumeReclaimPolicy: Recycle + storageClassName: tigera-elasticsearch diff --git a/process/testing/aso/EE/templates/storage-class-azure-file.yaml b/process/testing/aso/EE/templates/storage-class-azure-file.yaml new file mode 100644 index 00000000000..a68b6d71970 --- /dev/null +++ b/process/testing/aso/EE/templates/storage-class-azure-file.yaml @@ -0,0 +1,16 @@ +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: tigera-elasticsearch +provisioner: kubernetes.io/azure-file +mountOptions: + - uid=1000 + - gid=1000 + - mfsymlinks + - nobrl + - cache=none +parameters: + skuName: Standard_LRS +reclaimPolicy: Retain +volumeBindingMode: WaitForFirstConsumer +allowVolumeExpansion: true diff --git a/process/testing/winfv-cni-plugin/aso/Makefile b/process/testing/aso/Makefile similarity index 58% rename from process/testing/winfv-cni-plugin/aso/Makefile rename to process/testing/aso/Makefile index 4013bdd6983..6431600b7ac 100644 --- a/process/testing/winfv-cni-plugin/aso/Makefile +++ b/process/testing/aso/Makefile @@ -1,16 +1,21 @@ BINDIR?=bin ARCH?=amd64 -KIND_VERSION?=v0.22.0 +KIND_VERSION?=v0.24.0 +KUBE_VERSION?=v1.33.7 +HELM_VERSION?=v3.16.3 +GOMPLATE_VERSION?=v3.11.7 + +KIND_CLUSTER_NAME?=kind ############################################################################### # ASO management ############################################################################### -ASO_INSTALLED_MARKER:=.aso_installed +ASO_INSTALLED_MARKER:=.aso-installed .PHONY: install-aso install-aso: $(ASO_INSTALLED_MARKER) -$(ASO_INSTALLED_MARKER): $(BINDIR)/kind $(BINDIR)/kubectl $(BINDIR)/cmctl $(BINDIR)/asoctl +$(ASO_INSTALLED_MARKER): $(BINDIR)/kind $(BINDIR)/kubectl $(BINDIR)/cmctl $(BINDIR)/asoctl $(BINDIR)/helm @echo "Creating kind cluster and installing aso ..." ./install-aso.sh touch $@ @@ -20,8 +25,8 @@ uninstall-aso: $(BINDIR)/kind $(BINDIR)/kubectl ifeq (,$(wildcard $(ASO_INSTALLED_MARKER))) @echo "ASO ready marker '$(ASO_INSTALLED_MARKER)' does not exist, doing nothing" else - @echo "Azure resources for cluster $(CLUSTER_NAME_CAPZ) will now be deleted, this can take up to 20 minutes" - -$(BINDIR)/kind delete cluster --name kind${SUFFIX} + @echo "ASO kind cluster will now be deleted" + -$(BINDIR)/kind delete cluster --name $(KIND_CLUSTER_NAME) -rm -f kubeconfig -rm -f $(ASO_INSTALLED_MARKER) endif @@ -29,7 +34,7 @@ endif ############################################################################### # Azure resource management ############################################################################### -# VMSS is azure VirtualMachineScaleSet. +# VMSS is azure VirtualMachineScaleSet. # Current two VMSSes will be created (vmss-linux and vmss-windows) # In this Makefile, we use the term "vmss" to represent all azure resources created by the process. VMSS_MARKER:=.vmss-created @@ -37,30 +42,37 @@ VMSS_MARKER:=.vmss-created .PHONY: create-vmss create-vmss: $(VMSS_MARKER) -$(VMSS_MARKER): $(BINDIR)/kubectl $(BINDIR)/gomplate +$(VMSS_MARKER): $(BINDIR)/kubectl $(BINDIR)/gomplate $(ASO_INSTALLED_MARKER) @echo "Creating azure resources include vmss-linux and vmss-windows ..." ./vmss.sh create - ./vmss.sh info ./vmss.sh confirm-ssh touch $@ .PHONY: delete-vmss delete-vmss: $(BINDIR)/kubectl -ifeq (,$(wildcard $(VMSS_MARKER))) - @echo "VMSS ready marker '$(VMSS_MARKER)' does not exist, doing nothing" -else @echo "Azure resources for cluster will be deleted..." - -$(BINDIR)/kubectl delete ns winfv - -rm -f $(VMSS_MARKER) -endif + ./vmss.sh delete + -rm -f $(VMSS_MARKER) $(KUBEADM_MARKER) $(CALICO_MARKER) -############################################################################### -# FV management -############################################################################### -.PHONY: run-fv -run-fv: $(ASO_INSTALLED_MARKER) $(VMSS_MARKER) - @echo "Running FV $(BACKEND) ..." - ./setup-fv.sh +KUBEADM_MARKER:=.kubeadm-set-up + +.PHONY: setup-kubeadm +setup-kubeadm: $(KUBEADM_MARKER) + +$(KUBEADM_MARKER): $(ASO_INSTALLED_MARKER) $(VMSS_MARKER) + @echo "Running setup-kubeadm ..." + ./install-kubeadm.sh + touch $@ + +CALICO_MARKER:=.calico-installed + +.PHONY: install-calico +install-calico: $(CALICO_MARKER) + +$(CALICO_MARKER): $(KUBEADM_MARKER) + @echo "Installing Calico ..." + ./install-calico.sh + touch $@ ############################################################################### # Utilities management @@ -75,26 +87,35 @@ $(BINDIR)/kind: $(BINDIR)/kubectl: mkdir -p $(@D) + @echo "Downloading kubectl version: $(KUBE_VERSION)" curl -sSf -L --retry 5 https://dl.k8s.io/release/$(KUBE_VERSION)/bin/linux/$(ARCH)/kubectl -o $@ chmod +x $@ touch $@ $(BINDIR)/cmctl: mkdir -p $(@D) - curl -sSf -L --retry 5 https://github.com/cert-manager/cmctl/releases/latest/download/cmctl_linux_amd64 -o $@ + curl -sSf -L --retry 5 https://github.com/cert-manager/cmctl/releases/latest/download/cmctl_linux_$(ARCH) -o $@ chmod +x $@ touch $@ $(BINDIR)/asoctl: mkdir -p $(@D) - curl -sSf -L --retry 5 https://github.com/Azure/azure-service-operator/releases/latest/download/asoctl-linux-amd64.gz -o $(BINDIR)/asoctl.gz + curl -sSf -L --retry 5 https://github.com/Azure/azure-service-operator/releases/latest/download/asoctl-linux-$(ARCH).gz -o $(BINDIR)/asoctl.gz gunzip $(BINDIR)/asoctl.gz chmod +x $@ touch $@ $(BINDIR)/gomplate: mkdir -p $(@D) - curl -sSf -L --retry 5 https://github.com/hairyhenderson/gomplate/releases/download/v3.11.7/gomplate_linux-amd64 -o $@ + curl -sSf -L --retry 5 https://github.com/hairyhenderson/gomplate/releases/download/$(GOMPLATE_VERSION)/gomplate_linux-$(ARCH) -o $@ + chmod +x $@ + touch $@ + +$(BINDIR)/helm: + mkdir -p $(@D) + curl -sSf -L --retry 5 https://get.helm.sh/helm-$(HELM_VERSION)-linux-$(ARCH).tar.gz -o $(BINDIR)/helm.tar.gz + tar -xzf $(BINDIR)/helm.tar.gz -C $(BINDIR) --strip-components=1 linux-$(ARCH)/helm + rm $(BINDIR)/helm.tar.gz chmod +x $@ touch $@ @@ -105,8 +126,15 @@ clean: delete-vmss uninstall-aso -rm -rf ./report -rm -f .sshkey .sshkey.pub -rm -f $(HELPERS) + -rm -f kubeadm-config.yaml + -rm -f operator-crds.yaml tigera-operator.yaml tigera-prometheus-operator.yaml cert-manager.yaml + -rm -rf infra/manifests/ + -rm -rf EE/manifests/ + -rm -rf OSS/manifests/ + -rm -rf windows/kubeadm/ + -rm -f windows/config .PHONY: dist-clean dist-clean: clean -rm -rf $(BINDIR) - -rm -f $(VMSS_MARKER) $(ASO_INSTALLED_MARKER) + -rm -f $(VMSS_MARKER) $(ASO_INSTALLED_MARKER) $(KUBEADM_MARKER) $(CALICO_MARKER) diff --git a/process/testing/winfv-felix/capz/OSS/custom-resources.yaml b/process/testing/aso/OSS/templates/custom-resources.yaml similarity index 71% rename from process/testing/winfv-felix/capz/OSS/custom-resources.yaml rename to process/testing/aso/OSS/templates/custom-resources.yaml index c9363ab7346..16bdabb2ec6 100644 --- a/process/testing/winfv-felix/capz/OSS/custom-resources.yaml +++ b/process/testing/aso/OSS/templates/custom-resources.yaml @@ -10,10 +10,17 @@ spec: type: Calico calicoNetwork: bgp: Disabled - linuxDataplane: Iptables + linuxDataplane: {{$.Env.ASO_LINUX_DATAPLANE}} ipPools: - cidr: 192.168.0.0/16 encapsulation: VXLAN + windowsDataplane: HNS + serviceCIDRs: + - 10.96.0.0/12 + windowsNodes: + cniBinDir: "/Program Files/containerd/cni/bin" + cniConfigDir: "/Program Files/containerd/cni/conf" + cniLogDir: /var/log/calico/cni --- # This section configures the Calico API server. # For more information, see: https://docs.tigera.io/calico/latest/reference/installation/api#operator.tigera.io/v1.APIServer diff --git a/process/testing/aso/README.md b/process/testing/aso/README.md new file mode 100644 index 00000000000..6edfc883f39 --- /dev/null +++ b/process/testing/aso/README.md @@ -0,0 +1,35 @@ +## Windows FV Infrastructure + +This directory contains scripts and manifests to set up a Windows kubeadm infrastructure on Azure using Azure Service Operator. It creates three Linux nodes and two Windows nodes by default. + +### Prerequisites + +- Azure CLI must be installed +- Required Azure environment variables must be set (see Step 1) + +### Steps + +1. Set the required environment variables in `export-env.sh`. + +2. Run `make setup-kubeadm` to create the kubeadm cluster. + +3. Run `make install-calico` to install Calico on the cluster. + +4. Export `KUBECONFIG=./kubeconfig` to access the cluster. + +### Access Linux or Windows Nodes + +Helper scripts will be generated to SSH or SCP into each node. See the individual scripts for details. + +For example: +```bash +# Usage: ./ssh-node-windows.sh [node_index] [command] +# Examples: +# ./ssh-node-windows.sh 0 "Get-Process" # SSH to first Windows node (index 0) +# ./ssh-node-windows.sh 1 "ipconfig /all" # SSH to second Windows node (index 1) +# ./ssh-node-windows.sh "Get-Process" # SSH to first node (default, backward compatible) +``` + +### Cleanup + +Run `make dist-clean`. diff --git a/process/testing/winfv-cni-plugin/aso/asoctl-generated-manifests-v2.6.0.yaml b/process/testing/aso/asoctl-generated-manifests-v2.6.0.yaml similarity index 100% rename from process/testing/winfv-cni-plugin/aso/asoctl-generated-manifests-v2.6.0.yaml rename to process/testing/aso/asoctl-generated-manifests-v2.6.0.yaml diff --git a/process/testing/aso/config-kubeadm b/process/testing/aso/config-kubeadm new file mode 100644 index 00000000000..462398b399c --- /dev/null +++ b/process/testing/aso/config-kubeadm @@ -0,0 +1,21 @@ +apiVersion: v1 +clusters: +- cluster: + certificate-authority: c:\k\kubeadm\ca.crt + server: https://{{.Env.LINUX_PIP}}:{{.Env.APISERVER_PORT}} + name: kubernetes +contexts: +- context: + cluster: kubernetes + namespace: default + user: kubernetes-admin + name: kubernetes-admin@kubernetes +current-context: kubernetes-admin@kubernetes +kind: Config +preferences: {} +users: +- name: kubernetes-admin + user: + client-certificate-data: {{.Env.CLIENT_CERT_DATA}} + client-key-data: {{.Env.CLIENT_KEY_DATA}} + diff --git a/process/testing/aso/export-env.sh b/process/testing/aso/export-env.sh new file mode 100755 index 00000000000..cf85a244da0 --- /dev/null +++ b/process/testing/aso/export-env.sh @@ -0,0 +1,50 @@ +# Get the directory where this script is located (ASO directory) +ASO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Verify required environment variables +: "${AZURE_SUBSCRIPTION_ID:?Environment variable empty or not defined.}" +: "${AZURE_TENANT_ID:?Environment variable empty or not defined.}" +: "${AZURE_CLIENT_ID:?Environment variable empty or not defined.}" +: "${AZURE_CLIENT_SECRET:?Environment variable empty or not defined.}" + +export SUFFIX="${SUFFIX:=${USER}-aso}" + +export AZURE_LOCATION="${AZURE_LOCATION:="eastus2"}" +export AZURE_RESOURCE_GROUP="${AZURE_RESOURCE_GROUP:=rg-winfv-${SUFFIX}}" + +# Windows Server 2022 (latest version as of Nov 2025) +export AZURE_WINDOWS_IMAGE_SKU="${AZURE_WINDOWS_IMAGE_SKU:="2022-datacenter-core-g2"}" +export AZURE_WINDOWS_IMAGE_VERSION="${AZURE_WINDOWS_IMAGE_VERSION:="20348.4405.251112"}" +export WINDOWS_SERVER_VERSION="${WINDOWS_SERVER_VERSION:="windows-2022"}" + +# Windows Server 2019 (legacy, use if 2022 has issues) +#export AZURE_WINDOWS_IMAGE_SKU="${AZURE_WINDOWS_IMAGE_SKU:="2019-datacenter-core-g2"}" +#export AZURE_WINDOWS_IMAGE_VERSION="${AZURE_WINDOWS_IMAGE_VERSION:="17763.5696.240406"}" +#export WINDOWS_SERVER_VERSION="${WINDOWS_SERVER_VERSION:="windows-2019"}" + +export LINUX_NODE_COUNT="${LINUX_NODE_COUNT:=3}" +export WINDOWS_NODE_COUNT="${WINDOWS_NODE_COUNT:=2}" + +# Verbose mode - set to "true" to see all command output, "false" to suppress output +export VERBOSE="${VERBOSE:="true"}" + +export KUBE_VERSION="${KUBE_VERSION:="v1.33.7"}" + +export KINDEST_NODE_VERSION="${KINDEST_NODE_VERSION:="v1.31.0"}" +export KIND_CLUSTER_NAME="${KIND_CLUSTER_NAME:="kind"}" + +export CERT_MANAGER_VERSION="${CERT_MANAGER_VERSION:="v1.14.1"}" + +export CONTAINERD_VERSION="${CONTAINERD_VERSION:="1.7.28"}" + +export SSH_KEY_FILE="${ASO_DIR}/.sshkey" + +export GCR_IO_PULL_SECRET="${GCR_IO_PULL_SECRET:="${HOME}/secrets/docker_cfg.json"}" +export TSEE_TEST_LICENSE="${TSEE_TEST_LICENSE:="${HOME}/secrets/license.yaml"}" + +export ASO_LINUX_DATAPLANE="${ASO_LINUX_DATAPLANE:="Iptables"}" # 'Iptables', 'Nftables' or 'BPF' +export ASO_KUBE_PROXY_MODE="${ASO_KUBE_PROXY_MODE:="iptables"}" # 'iptables' or 'nftables', use 'nftables' with BPF + +export PRODUCT="calico" +export RELEASE_STREAM="master" +export HASH_RELEASE="true" diff --git a/process/testing/winfv-cni-plugin/aso/get-aso-logs.sh b/process/testing/aso/get-aso-logs.sh similarity index 100% rename from process/testing/winfv-cni-plugin/aso/get-aso-logs.sh rename to process/testing/aso/get-aso-logs.sh diff --git a/process/testing/winfv-cni-plugin/aso/infra/templates/password.yaml b/process/testing/aso/infra/templates/password.yaml similarity index 82% rename from process/testing/winfv-cni-plugin/aso/infra/templates/password.yaml rename to process/testing/aso/infra/templates/password.yaml index ddd482dd95e..dfca79250db 100644 --- a/process/testing/winfv-cni-plugin/aso/infra/templates/password.yaml +++ b/process/testing/aso/infra/templates/password.yaml @@ -1,8 +1,8 @@ apiVersion: v1 kind: Secret metadata: - name: winfv-secret-windows - namespace: winfv + name: aso-secret-windows + namespace: aso type: Opaque data: # Admin password in Base64 format is used by Linux and Windows VMSS manifests. However login with user/password is disabled on Linux. diff --git a/process/testing/winfv-cni-plugin/aso/infra/templates/resource-group.yaml b/process/testing/aso/infra/templates/resource-group.yaml similarity index 65% rename from process/testing/winfv-cni-plugin/aso/infra/templates/resource-group.yaml rename to process/testing/aso/infra/templates/resource-group.yaml index 1c40d645e81..202aed0c26c 100644 --- a/process/testing/winfv-cni-plugin/aso/infra/templates/resource-group.yaml +++ b/process/testing/aso/infra/templates/resource-group.yaml @@ -1,13 +1,15 @@ apiVersion: v1 kind: Namespace metadata: - name: winfv + name: aso --- apiVersion: resources.azure.com/v1api20200601 kind: ResourceGroup metadata: name: {{.Env.AZURE_RESOURCE_GROUP}} - namespace: winfv + namespace: aso + annotations: + serviceoperator.azure.com/credential-from: aso-credential spec: location: {{.Env.AZURE_LOCATION}} diff --git a/process/testing/winfv-cni-plugin/aso/infra/templates/security-group.yaml b/process/testing/aso/infra/templates/security-group.yaml similarity index 59% rename from process/testing/winfv-cni-plugin/aso/infra/templates/security-group.yaml rename to process/testing/aso/infra/templates/security-group.yaml index f3f132606a2..8b1e786d9fe 100644 --- a/process/testing/winfv-cni-plugin/aso/infra/templates/security-group.yaml +++ b/process/testing/aso/infra/templates/security-group.yaml @@ -1,8 +1,8 @@ apiVersion: network.azure.com/v1api20201101 kind: NetworkSecurityGroup metadata: - name: winfv-sg - namespace: winfv + name: aso-sg + namespace: aso spec: location: {{.Env.AZURE_LOCATION}} owner: @@ -12,11 +12,11 @@ spec: apiVersion: network.azure.com/v1api20201101 kind: NetworkSecurityGroupsSecurityRule metadata: - name: winfv-sgrule-allow-ssh - namespace: winfv + name: aso-sgrule-allow-ssh + namespace: aso spec: owner: - name: winfv-sg + name: aso-sg protocol: Tcp sourcePortRange: "*" destinationPortRange: "22" @@ -31,11 +31,11 @@ spec: apiVersion: network.azure.com/v1api20201101 kind: NetworkSecurityGroupsSecurityRule metadata: - name: winfv-sgrule-allow-rdp - namespace: winfv + name: aso-sgrule-allow-rdp + namespace: aso spec: owner: - name: winfv-sg + name: aso-sg protocol: Tcp sourcePortRange: "*" destinationPortRange: "3389" @@ -45,3 +45,22 @@ spec: priority: 1001 direction: Inbound description: Allow RDP access + +--- +apiVersion: network.azure.com/v1api20201101 +kind: NetworkSecurityGroupsSecurityRule +metadata: + name: aso-sgrule-allow-k8s-api + namespace: aso +spec: + owner: + name: aso-sg + protocol: Tcp + sourcePortRange: "*" + destinationPortRange: "6443" + sourceAddressPrefix: "*" + destinationAddressPrefix: "*" + access: Allow + priority: 1002 + direction: Inbound + description: Allow Kubernetes API server access diff --git a/process/testing/aso/infra/templates/vmss-linux.yaml b/process/testing/aso/infra/templates/vmss-linux.yaml new file mode 100644 index 00000000000..04190272db9 --- /dev/null +++ b/process/testing/aso/infra/templates/vmss-linux.yaml @@ -0,0 +1,137 @@ +{{- $count := conv.ToInt .Env.LINUX_NODE_COUNT -}} +{{- range $i := seq 0 (sub $count 1) -}} +{{- $nodeNum := add $i 1 -}} +{{- $nodeName := printf "vm-linux-%d" $nodeNum -}} +{{- $nicName := printf "nic-linux-%d" $nodeNum -}} +{{- $pipName := printf "pip-linux-%d" $nodeNum -}} +{{- $extContainerdName := printf "vm-linux-%d-containerd" $nodeNum -}} +{{- $computerName := printf "asovm-linux-%d" $nodeNum -}}{{/* NOTE: asovm-linux-%d here would have the l2bridge cni-plugin hang, maybe there's a problem with the name being too short? */}} +--- +apiVersion: compute.azure.com/v1api20220301 +kind: VirtualMachine +metadata: + name: {{$nodeName}} + namespace: aso +spec: + location: {{$.Env.AZURE_LOCATION}} + owner: + name: {{$.Env.AZURE_RESOURCE_GROUP}} + hardwareProfile: + vmSize: Standard_D4s_v3 + networkProfile: + networkInterfaces: + - reference: + group: network.azure.com + kind: NetworkInterface + name: {{$nicName}} + osProfile: + computerName: {{$computerName}} + adminUsername: aso + adminPassword: + key: password + name: aso-secret-windows + linuxConfiguration: + disablePasswordAuthentication: true + ssh: + publicKeys: + - keyData: {{$.Env.PUBLIC_KEY}} + path: /home/aso/.ssh/authorized_keys + storageProfile: + imageReference: + publisher: Canonical + offer: 0001-com-ubuntu-server-jammy + sku: 22_04-lts + version: latest + osDisk: + createOption: FromImage + managedDisk: + storageAccountType: Premium_LRS +--- +apiVersion: compute.azure.com/v1api20220301 +kind: VirtualMachinesExtension +metadata: + name: {{$extContainerdName}} + namespace: aso +spec: + location: {{$.Env.AZURE_LOCATION}} + owner: + name: {{$nodeName}} + publisher: Microsoft.Azure.Extensions + type: CustomScript + typeHandlerVersion: "2.1" + autoUpgradeMinorVersion: true + settings: + commandToExecute: | + /bin/bash -c ' + set -e + echo "Installing containerd..." + + # Update package list + apt-get update + + # Install prerequisites + apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release + + # Install containerd from the Docker repo + curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null + apt-get update + apt-get install -y containerd.io + + # Configure containerd for Kubernetes + mkdir -p /etc/containerd + containerd config default | tee /etc/containerd/config.toml + + # Enable systemd cgroup driver (required for Kubernetes) + sed -i "s/SystemdCgroup = false/SystemdCgroup = true/g" /etc/containerd/config.toml + + # Restart containerd + systemctl restart containerd + systemctl enable containerd + + # Verify installations + containerd --version + + echo "Containerd installation completed successfully" + ' +--- +apiVersion: network.azure.com/v1api20240301 +kind: NetworkInterface +metadata: + name: {{$nicName}} + namespace: aso +spec: + location: {{$.Env.AZURE_LOCATION}} + owner: + name: {{$.Env.AZURE_RESOURCE_GROUP}} + networkSecurityGroup: + reference: + group: network.azure.com + kind: NetworkSecurityGroup + name: aso-sg + ipConfigurations: + - name: ipconfig-linux-{{$nodeNum}} + subnet: + reference: + group: network.azure.com + kind: VirtualNetworksSubnet + name: subnet-aso + publicIPAddress: + reference: + group: network.azure.com + kind: PublicIPAddress + name: {{$pipName}} +--- +apiVersion: network.azure.com/v1api20240301 +kind: PublicIPAddress +metadata: + name: {{$pipName}} + namespace: aso +spec: + location: {{$.Env.AZURE_LOCATION}} + owner: + name: {{$.Env.AZURE_RESOURCE_GROUP}} + publicIPAllocationMethod: Static + sku: + name: Standard +{{end}} diff --git a/process/testing/aso/infra/templates/vmss-windows.yaml b/process/testing/aso/infra/templates/vmss-windows.yaml new file mode 100644 index 00000000000..0886c10f875 --- /dev/null +++ b/process/testing/aso/infra/templates/vmss-windows.yaml @@ -0,0 +1,112 @@ +{{- $count := conv.ToInt .Env.WINDOWS_NODE_COUNT -}} +{{- range $i := seq 0 (sub $count 1) -}} +{{- $nodeNum := add $i 1 -}} +{{- $nodeName := printf "vm-windows-%d" $nodeNum -}} +{{- $nicName := printf "nic-windows-%d" $nodeNum -}} +{{- $pipName := printf "pip-windows-%d" $nodeNum -}} +{{- $extOpenSSHName := printf "vm-windows-%d-openssh" $nodeNum -}} +{{- $extCustomName := printf "vm-windows-%d-customextension" $nodeNum -}} +{{- $computerName := printf "asovm-win-%d" $nodeNum -}}{{/* NOTE: aso-win-%d here would have the l2bridge cni-plugin hang, maybe there's a problem with the name being too short? */}} +--- +apiVersion: compute.azure.com/v1api20220301 +kind: VirtualMachine +metadata: + name: {{$nodeName}} + namespace: aso +spec: + location: {{$.Env.AZURE_LOCATION}} + owner: + name: {{$.Env.AZURE_RESOURCE_GROUP}} + hardwareProfile: + vmSize: Standard_D4s_v3 + networkProfile: + networkInterfaces: + - reference: + group: network.azure.com + kind: NetworkInterface + name: {{$nicName}} + osProfile: + computerName: {{$computerName}} + adminUsername: aso + adminPassword: + key: password + name: aso-secret-windows + storageProfile: + imageReference: + publisher: MicrosoftWindowsServer + offer: WindowsServer + sku: {{$.Env.AZURE_WINDOWS_IMAGE_SKU}} + version: {{$.Env.AZURE_WINDOWS_IMAGE_VERSION}} + osDisk: + createOption: FromImage + managedDisk: + storageAccountType: Premium_LRS +--- +apiVersion: network.azure.com/v1api20240301 +kind: NetworkInterface +metadata: + name: {{$nicName}} + namespace: aso +spec: + location: {{$.Env.AZURE_LOCATION}} + owner: + name: {{$.Env.AZURE_RESOURCE_GROUP}} + networkSecurityGroup: + reference: + group: network.azure.com + kind: NetworkSecurityGroup + name: aso-sg + ipConfigurations: + - name: ipconfig-windows-{{$nodeNum}} + subnet: + reference: + group: network.azure.com + kind: VirtualNetworksSubnet + name: subnet-aso + publicIPAddress: + reference: + group: network.azure.com + kind: PublicIPAddress + name: {{$pipName}} +--- +apiVersion: network.azure.com/v1api20240301 +kind: PublicIPAddress +metadata: + name: {{$pipName}} + namespace: aso +spec: + location: {{$.Env.AZURE_LOCATION}} + owner: + name: {{$.Env.AZURE_RESOURCE_GROUP}} + publicIPAllocationMethod: Static + sku: + name: Standard +--- +apiVersion: compute.azure.com/v1api20220301 +kind: VirtualMachinesExtension +metadata: + name: {{$extOpenSSHName}} + namespace: aso +spec: + location: {{$.Env.AZURE_LOCATION}} + owner: + name: {{$nodeName}} + publisher: Microsoft.Azure.OpenSSH + type: WindowsOpenSSH + typeHandlerVersion: "3.0" +--- +apiVersion: compute.azure.com/v1api20220301 +kind: VirtualMachinesExtension +metadata: + name: {{$extCustomName}} + namespace: aso +spec: + location: {{$.Env.AZURE_LOCATION}} + owner: + name: {{$nodeName}} + publisher: Microsoft.Compute + type: CustomScriptExtension + typeHandlerVersion: "1.9" + settings: + commandToExecute: powershell -command "New-Item -ItemType Directory -Path C:\k -Force; echo \"{{$.Env.PUBLIC_KEY}}\" | Add-Content 'C:\ProgramData\ssh\administrators_authorized_keys' -Encoding UTF8;icacls.exe 'C:\ProgramData\ssh\administrators_authorized_keys' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'" +{{end}} diff --git a/process/testing/winfv-cni-plugin/aso/infra/templates/vnet.yaml b/process/testing/aso/infra/templates/vnet.yaml similarity index 80% rename from process/testing/winfv-cni-plugin/aso/infra/templates/vnet.yaml rename to process/testing/aso/infra/templates/vnet.yaml index c1a155f0734..c2772d22688 100644 --- a/process/testing/winfv-cni-plugin/aso/infra/templates/vnet.yaml +++ b/process/testing/aso/infra/templates/vnet.yaml @@ -1,8 +1,8 @@ apiVersion: network.azure.com/v1api20201101 kind: VirtualNetwork metadata: - name: vnet-winfv - namespace: winfv + name: vnet-aso + namespace: aso spec: addressSpace: addressPrefixes: @@ -15,10 +15,10 @@ spec: apiVersion: network.azure.com/v1api20201101 kind: VirtualNetworksSubnet metadata: - name: subnet-winfv - namespace: winfv + name: subnet-aso + namespace: aso spec: addressPrefix: 10.0.0.0/24 owner: - name: vnet-winfv + name: vnet-aso privateLinkServiceNetworkPolicies: Disabled diff --git a/process/testing/aso/install-aso.sh b/process/testing/aso/install-aso.sh new file mode 100755 index 00000000000..117e4a24478 --- /dev/null +++ b/process/testing/aso/install-aso.sh @@ -0,0 +1,56 @@ +#!/bin/bash +# Copyright (c) 2024 Tigera, Inc. All rights reserved. +# +# 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. + +set -o errexit +set -o nounset +set -o pipefail + +: "${ASO_DIR:="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"}" + +: "${KIND_CLUSTER_NAME:=kind}" +: "${KINDEST_NODE_VERSION:=v1.31.0}" +: "${CERT_MANAGER_VERSION:=v1.14.1}" +CRD_PATTERN="resources.azure.com/*;containerservice.azure.com/*;compute.azure.com/*;network.azure.com/*" + +# Utilities +: ${KIND:=./bin/kind} +: ${KUBECTL:=./bin/kubectl} +: ${CMCTL:=./bin/cmctl} +: ${ASOCTL:=./bin/asoctl} +: ${HELM:=./bin/helm} + +# Create management cluster +${KIND} create cluster --image "kindest/node:${KINDEST_NODE_VERSION}" --name "${KIND_CLUSTER_NAME}" --verbosity 5 +${KIND} get kubeconfig --name "${KIND_CLUSTER_NAME}" > "${ASO_DIR}/kind-kubeconfig" +${KUBECTL} wait node "${KIND_CLUSTER_NAME}-control-plane" --for=condition=ready --timeout=90s + +# Install cert-manager +echo; echo "Wait for cert manager to be installed ..." +curl -sSf -L --retry 5 "https://github.com/jetstack/cert-manager/releases/download/${CERT_MANAGER_VERSION}/cert-manager.yaml" -o cert-manager.yaml +${KUBECTL} apply -f ./cert-manager.yaml +${CMCTL} check api --wait=2m + +echo; echo "Installing ASO..." + +${HELM} repo add aso2 https://raw.githubusercontent.com/Azure/azure-service-operator/main/v2/charts +${HELM} upgrade --install aso2 aso2/azure-service-operator \ + --create-namespace \ + --namespace=azureserviceoperator-system \ + --set crdPattern=${CRD_PATTERN} + +# Wait for ASO deployments +echo "Wait for ASO controller manager to be ready (up to 5m) ..." +${KUBECTL} wait --for=condition=available --timeout=5m -n azureserviceoperator-system deployment azureserviceoperator-controller-manager +echo "ASO installed and the controller manager is ready." diff --git a/process/testing/aso/install-calico.sh b/process/testing/aso/install-calico.sh new file mode 100755 index 00000000000..193663dc187 --- /dev/null +++ b/process/testing/aso/install-calico.sh @@ -0,0 +1,223 @@ +#!/bin/bash +# Copyright (c) 2022-2024 Tigera, Inc. All rights reserved. +# +# 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. + +set -o errexit +set -o nounset +set -o pipefail + +. ../util/utils.sh +. ./export-env.sh + +# Trap to show pod status on failure for debugging +trap 'exit_code=$?; if [ $exit_code -ne 0 ]; then echo ""; echo "========================================"; echo "Script failed! Showing pod status for debugging:"; echo "========================================"; ./bin/kubectl get pod -A -o wide --kubeconfig=./kubeconfig 2>/dev/null || true; fi; exit $exit_code' EXIT + +# Use kubectl with kubeconfig from install-kubeadm.sh +: "${KUBECTL:=./bin/kubectl}" +: "${GOMPLATE:=./bin/gomplate}" + +# Use the kubeconfig that was copied from master node +KUBECONFIG_FILE="./kubeconfig" +if [ ! -f "${KUBECONFIG_FILE}" ]; then + echo "ERROR: kubeconfig file not found at ${KUBECONFIG_FILE}" + echo "Please run install-kubeadm.sh first to set up the cluster and copy kubeconfig" + exit 1 +fi + +export KUBECONFIG="${KUBECONFIG_FILE}" +echo "Using kubeconfig from: ${KUBECONFIG_FILE}" + +: ${PRODUCT:=calient} +: ${RELEASE_STREAM:="master"} # Default to master +: ${HASH_RELEASE:="true"} # Set to true to use hash release + +: "${KUBE_VERSION:?Environment variable empty or not defined.}" + +echo Settings: +echo ' PRODUCT='${PRODUCT} +echo ' RELEASE_STREAM='${RELEASE_STREAM} +echo ' HASH_RELEASE='${HASH_RELEASE} +echo ' KUBE_VERSION='${KUBE_VERSION} + + +if [ ${PRODUCT} == 'calient' ]; then + rm -rf "${ASO_DIR}/EE/manifests" || true + ${GOMPLATE} --input-dir "${ASO_DIR}/EE/templates" --output-dir "${ASO_DIR}/EE/manifests" +else + rm -rf "${ASO_DIR}/OSS/manifests" || true + ${GOMPLATE} --input-dir "${ASO_DIR}/OSS/templates" --output-dir "${ASO_DIR}/OSS/manifests" +fi + +if [ ${PRODUCT} == 'calient' ]; then + # Verify if the required variables are set for Calico EE + : "${GCR_IO_PULL_SECRET:?Environment variable empty or not defined.}" + : "${TSEE_TEST_LICENSE:?Environment variable empty or not defined.}" + echo ' GCR_IO_PULL_SECRET='"${GCR_IO_PULL_SECRET}" + echo ' TSEE_TEST_LICENSE='"${TSEE_TEST_LICENSE}" +fi + +SCRIPT_CURRENT_DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 && pwd -P )" +LOCAL_MANIFESTS_DIR="${SCRIPT_CURRENT_DIR}/../../../manifests" + +if [ ${PRODUCT} == 'calient' ]; then + RELEASE_BASE_URL="https://downloads.tigera.io/ee/${RELEASE_STREAM}" +else + RELEASE_BASE_URL="https://raw.githubusercontent.com/projectcalico/calico/${RELEASE_STREAM}" +fi + +if [ ${HASH_RELEASE} == 'true' ]; then + if [ -z ${RELEASE_STREAM} ]; then + echo "RELEASE_STREAM not set for HASH release" + exit 1 + fi + if [ ${PRODUCT} == 'calient' ]; then + URL_HASH="https://latest-cnx.docs.eng.tigera.net/${RELEASE_STREAM}.txt" + else + URL_HASH="https://latest-os.docs.eng.tigera.net/${RELEASE_STREAM}.txt" + fi + RELEASE_BASE_URL=$(curl -sS ${URL_HASH}) +fi + +if [[ ${RELEASE_STREAM} != 'local' ]]; then + # Check release url + echo ' RELEASE_BASE_URL='${RELEASE_BASE_URL} +fi + +# Create a storage class and persistent volume for Calico Enterprise. +if [ ${PRODUCT} == 'calient' ]; then + ${KUBECTL} create -f ./EE/manifests/storage-class-azure-file.yaml + ${KUBECTL} create -f ./EE/manifests/persistent-volume.yaml +fi + +# Install Calico on Linux nodes +if [[ ${RELEASE_STREAM} == 'local' ]]; then + # Use local manifests + ${KUBECTL} create -f ${LOCAL_MANIFESTS_DIR}/operator-crds.yaml + ${KUBECTL} create -f ${LOCAL_MANIFESTS_DIR}/tigera-operator.yaml +else + # Download and install from release + echo "Downloading Calico manifests from ${RELEASE_BASE_URL}" + curl -sSf -L --retry 5 ${RELEASE_BASE_URL}/manifests/operator-crds.yaml -o operator-crds.yaml + curl -sSf -L --retry 5 ${RELEASE_BASE_URL}/manifests/tigera-operator.yaml -o tigera-operator.yaml + ${KUBECTL} create -f ./operator-crds.yaml + ${KUBECTL} create -f ./tigera-operator.yaml +fi + +if [[ ${PRODUCT} == 'calient' ]]; then + # Install prometheus operator + if [[ ${RELEASE_STREAM} == 'local' ]]; then + ${KUBECTL} create -f ${LOCAL_MANIFESTS_DIR}/tigera-prometheus-operator.yaml + else + curl -sSf -L --retry 5 ${RELEASE_BASE_URL}/manifests/tigera-prometheus-operator.yaml -o tigera-prometheus-operator.yaml + ${KUBECTL} create -f ./tigera-prometheus-operator.yaml + fi + + # Install pull secret. + ${KUBECTL} create secret generic tigera-pull-secret \ + --type=kubernetes.io/dockerconfigjson -n tigera-operator \ + --from-file=.dockerconfigjson=${GCR_IO_PULL_SECRET} + + # When using an EE hash release, the operator has to have tigera-pull-secret in its imagePullSecrets. + if [[ ${HASH_RELEASE} == 'true' ]]; then + ${KUBECTL} patch deployment tigera-operator -n tigera-operator --patch '{"spec":{"template":{"spec":{"imagePullSecrets":[{"name":"tigera-pull-secret"}]}}}}' + fi + + # Create custom resources + ${KUBECTL} create -f ./EE/manifests/custom-resources.yaml + + # Install Calico EE license (after the Calico apiserver comes up) + echo "Wait for the Calico apiserver to be ready..." + timeout --foreground 300 bash -c "while ! ${KUBECTL} wait pod -l k8s-app=calico-apiserver --for=condition=Ready -n calico-system --timeout=30s; do sleep 5; done" + echo "Calico apiserver is ready, installing Calico EE license" + + retry_command 60 "${KUBECTL} create -f ${TSEE_TEST_LICENSE}" +else + # Create custom resources + ${KUBECTL} create -f ./OSS/manifests/custom-resources.yaml +fi + +echo "Wait for Calico to be ready on Linux nodes..." +timeout --foreground 300 bash -c "while ! ${KUBECTL} wait pod -l k8s-app=calico-node --for=condition=Ready -n calico-system --timeout=30s; do sleep 5; done" +echo "Calico is ready on Linux nodes" + +if [[ ${ASO_LINUX_DATAPLANE} == 'BPF' ]]; then + echo "Disabling kube-proxy since Calico is running on the BPF dataplane" + ${KUBECTL} patch ds -n kube-system kube-proxy -p '{"spec":{"template":{"spec":{"nodeSelector":{"non-calico": "true"}}}}}' +fi + +# Install Calico on Windows nodes +echo "" +echo "==========================================" +echo "Installing Calico on Windows nodes..." +echo "==========================================" + +echo "Enabling strict affinity in IPAMConfig (required for Windows)..." +${KUBECTL} patch ipamconfig default --type merge --patch='{"spec": {"strictAffinity": true}}' + +echo "Creating kubernetes-services-endpoint ConfigMap..." +APISERVER=$(${KUBECTL} get configmap -n kube-system kube-proxy -o yaml | awk -F'://' '/server: https:\/\// { print $2 }') +APISERVER_ADDR=$(echo "${APISERVER}" | awk -F':' '{ print $1 }') +APISERVER_PORT=$(echo "${APISERVER}" | awk -F':' '{ print $2 }') +${KUBECTL} apply -f - << EOF +kind: ConfigMap +apiVersion: v1 +metadata: + name: kubernetes-services-endpoint + namespace: tigera-operator +data: + KUBERNETES_SERVICE_HOST: "${APISERVER_ADDR}" + KUBERNETES_SERVICE_PORT: "${APISERVER_PORT}" +EOF + +echo "Enabling Windows dataplane (HNS) and configuring serviceCIDRs... (already configured in custom-resources.yaml)" +# ${KUBECTL} patch installation default --type merge --patch='{"spec": {"serviceCIDRs": ["10.96.0.0/12"], "calicoNetwork": {"windowsDataplane": "HNS"}}}' + +echo "Configuring Windows CNI paths to match containerd configuration... (already configured in custom-resources.yaml)" +# For some reason, patch not working with this. +#${KUBECTL} patch installation default --type merge --patch='{"spec": {"windowsNodes": {"cniBinDir": "/Program Files/containerd/cni/bin", "cniConfigDir": "/Program Files/containerd/cni/conf", "cniLogDir": "/var/log/calico/cni"}}}' + +echo "Wait for Calico to be ready on Windows nodes..." +timeout --foreground 600 bash -c "while ! ${KUBECTL} wait pod -l k8s-app=calico-node-windows --for=condition=Ready -n calico-system --timeout=30s; do sleep 5; done" +echo "Calico is ready on Windows nodes" + +# Create the kube-proxy-windows daemonset +echo "Install kube-proxy-windows ${KUBE_VERSION} from sig-windows-tools" +for iter in {1..5};do + curl -sSf -L https://raw.githubusercontent.com/kubernetes-sigs/sig-windows-tools/master/hostprocess/calico/kube-proxy/kube-proxy.yml | sed "s/KUBE_PROXY_VERSION/${KUBE_VERSION}/g" | ${KUBECTL} apply -f - && break || echo "download error: retry $iter in 5s" && sleep 5; +done; + +echo "Wait for kube-proxy to be ready on Windows nodes..." +timeout --foreground 1200 bash -c "while ! ${KUBECTL} wait pod -l k8s-app=kube-proxy-windows --for=condition=Ready -n kube-system --timeout=30s; do sleep 5; done" +echo "kube-proxy is ready on Windows nodes" + +echo "Wait for calico-node-windows pods to be ready..." +timeout --foreground 600 bash -c "while ! ${KUBECTL} wait pod -l k8s-app=calico-node-windows --for=condition=Ready -n calico-system --timeout=30s; do sleep 5; done" +echo "calico-node-windows pods are ready" + +if [[ ${PRODUCT} == 'calient' ]]; then + echo "Wait for fluentd-node-windows pods to be ready..." + echo "It takes a long time for fluentd-node-windows pods to pull images and start. Sleeping for 8 minutes..." + sleep 480 + timeout --foreground 600 bash -c "while ! ${KUBECTL} wait pod -l k8s-app=fluentd-node-windows --for=condition=Ready -n tigera-fluentd --timeout=30s; do sleep 5; done" + echo "fluentd-node-windows pods are ready" +fi + +echo "" +echo "==========================================" +echo "Calico installation completed successfully!" +echo "==========================================" +${KUBECTL} get nodes -o wide +echo "" +echo "Calico pods:" +${KUBECTL} get pods -n calico-system -o wide diff --git a/process/testing/aso/install-kubeadm.sh b/process/testing/aso/install-kubeadm.sh new file mode 100755 index 00000000000..df0116f1cd1 --- /dev/null +++ b/process/testing/aso/install-kubeadm.sh @@ -0,0 +1,416 @@ +#!/bin/bash +# Copyright (c) 2025 Tigera, Inc. All rights reserved. +# +# 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. + +set -e + +. ../util/utils.sh + +. ./vmss.sh node-ips + +: "${KUBECTL:=./bin/kubectl}" +: "${GOMPLATE:=./bin/gomplate}" + +: "${ASO_KUBE_PROXY_MODE:="iptables"}" + +# Reconstruct arrays from exported string variables +# Bash arrays cannot be exported across shells, so we export them as space-separated strings +read -ra LINUX_EIPS <<< "${LINUX_EIPS_STR}" +read -ra LINUX_PIPS <<< "${LINUX_PIPS_STR}" +read -ra WINDOWS_EIPS <<< "${WINDOWS_EIPS_STR}" +read -ra WINDOWS_PIPS <<< "${WINDOWS_PIPS_STR}" + +# Debug: Print available node information +echo "========================================" +echo "Node configuration loaded:" +echo " LINUX_NODE_COUNT: ${LINUX_NODE_COUNT}" +echo " WINDOWS_NODE_COUNT: ${WINDOWS_NODE_COUNT}" +echo " LINUX_EIPS (count: ${#LINUX_EIPS[@]}): ${LINUX_EIPS[*]}" +echo " LINUX_PIPS (count: ${#LINUX_PIPS[@]}): ${LINUX_PIPS[*]}" +echo " WINDOWS_EIPS (count: ${#WINDOWS_EIPS[@]}): ${WINDOWS_EIPS[*]}" +echo " WINDOWS_PIPS (count: ${#WINDOWS_PIPS[@]}): ${WINDOWS_PIPS[*]}" +echo "========================================" +echo + +function copy_scripts_to_linux_nodes() { + echo "Copying Linux setup scripts to all Linux nodes..." + + # Copy to all Linux nodes + for ((i=0; i<${LINUX_NODE_COUNT}; i++)); do + local node_num=$((i+1)) + local linux_eip="${LINUX_EIPS[$i]}" + + if [[ -z "$linux_eip" ]]; then + echo "ERROR: Linux node ${node_num} EIP is empty!" + return 1 + fi + + echo "Copying scripts to Linux node ${node_num} (${linux_eip})..." + scp -i "${SSH_KEY_FILE}" -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ + ./linux/*.sh "aso@${linux_eip}:~/" || { + echo "ERROR: Failed to copy scripts to Linux node ${node_num}" + return 1 + } + + echo "Scripts copied successfully to Linux node ${node_num}" + done + + echo "All Linux scripts copied successfully!" + echo +} + +function copy_scripts_to_windows_nodes() { + echo "Copying Windows setup scripts to all Windows nodes..." + + # Copy to all Windows nodes + for ((i=0; i<${WINDOWS_NODE_COUNT}; i++)); do + local node_num=$((i+1)) + local windows_eip="${WINDOWS_EIPS[$i]}" + + if [[ -z "$windows_eip" ]]; then + echo "ERROR: Windows node ${node_num} EIP is empty!" + return 1 + fi + + echo "Copying scripts to Windows node ${node_num} (${windows_eip})..." + scp -i "${SSH_KEY_FILE}" -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ + ./windows/*.ps1 "aso@${windows_eip}:c:\\k\\" || { + echo "ERROR: Failed to copy scripts to Windows node ${node_num}" + return 1 + } + + echo "Scripts copied successfully to Windows node ${node_num}" + done + + echo "All Windows scripts copied successfully!" + echo +} + +function setup_kubeadm_cluster() { + echo "Installing kubeadm and Kubernetes ${KUBE_VERSION} on Linux VM..." + + # Run prerequisites setup script + echo "Setting up prerequisites and installing kubeadm..." + ${MASTER_CONNECT_COMMAND} "~/setup-node.sh ${KUBE_VERSION}" + + # Initialize kubeadm cluster + echo "Initializing Kubernetes cluster..." + LOCAL_IP_ENV=${LINUX_PIP} + EXTERNAL_IP_ENV=${LINUX_EIP} + + echo "Kubeadm configuration:" + echo " API Server Internal IP: ${LOCAL_IP_ENV}" + echo " API Server External IP: ${EXTERNAL_IP_ENV}" + echo " Pod Network CIDR: 192.168.0.0/16" + echo " Service CIDR: 10.96.0.0/12" + + echo "Generating kubeadm config yaml..." + ADVERTISE_ADDRESS=${LOCAL_IP_ENV} + POD_NETWORK_CIDR="192.168.0.0/16" + SERVICE_CIDR="10.96.0.0/12" + EXTERNAL_IP=${EXTERNAL_IP_ENV} + CERT_SANS="${ADVERTISE_ADDRESS}" + if [ -n "$EXTERNAL_IP" ]; then + CERT_SANS="${CERT_SANS},${EXTERNAL_IP}" + echo " Certificate will include both internal and external IPs" + fi + + cat < ./kubeadm-config.yaml +apiVersion: kubeadm.k8s.io/v1beta4 +kind: InitConfiguration +localAPIEndpoint: + advertiseAddress: "${ADVERTISE_ADDRESS}" +--- +apiVersion: kubeadm.k8s.io/v1beta4 +kind: ClusterConfiguration +apiServer: + certSANs: +$(echo "${CERT_SANS}" | tr ',' '\n' | sed 's/^/ - /') +networking: + podSubnet: "${POD_NETWORK_CIDR}" + serviceSubnet: "${SERVICE_CIDR}" +--- +apiVersion: kubeproxy.config.k8s.io/v1alpha1 +kind: KubeProxyConfiguration +mode: "${ASO_KUBE_PROXY_MODE}" +EOF + + echo "Copying kubeadm config yaml to Linux node 0 (${LINUX_EIPS[0]})..." + scp -i "${SSH_KEY_FILE}" -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ + ./kubeadm-config.yaml "aso@${LINUX_EIPS[0]}:~/" || { + echo "ERROR: Failed to copy kubeadm config yaml to Linux node 0" + return 1 + } + + ${MASTER_CONNECT_COMMAND} "~/init-cluster.sh ~/kubeadm-config.yaml" + + # Get the API server port (default is 6443 for kubeadm) + APISERVER_PORT=6443 + export APISERVER_PORT + + # Note: The node will be in NotReady state until a CNI plugin is installed + # This is expected behavior for a fresh kubeadm cluster + echo "Waiting for API server to be responsive..." + ${MASTER_CONNECT_COMMAND} "kubectl wait --for=condition=Ready --timeout=60s pod -n kube-system -l component=kube-apiserver || true" + + # Verify cluster is accessible + echo "Verifying cluster accessibility..." + ${MASTER_CONNECT_COMMAND} "kubectl cluster-info" + + # Remove control plane taints to allow scheduling pods on master + echo "Removing control plane taints..." + ${MASTER_CONNECT_COMMAND} "kubectl taint nodes --all node-role.kubernetes.io/control-plane- || true" + + echo + echo "Kubernetes cluster info:" + ${MASTER_CONNECT_COMMAND} kubectl get nodes -o wide + + # Save the join command for Windows worker node + echo "Generating kubeadm join command for Windows node..." + ${MASTER_CONNECT_COMMAND} "kubeadm token create --print-join-command" > /tmp/kubeadm_join_command.txt + KUBEADM_JOIN_COMMAND=$(cat /tmp/kubeadm_join_command.txt) + export KUBEADM_JOIN_COMMAND + echo "Join command saved: ${KUBEADM_JOIN_COMMAND}" + + # Copy kubeconfig to local directory + echo "Copying kubeconfig from master node..." + scp -i "${SSH_KEY_FILE}" -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ + "aso@${LINUX_EIP}:/home/aso/.kube/config" ./kubeconfig + + # Fix the API server address in kubeconfig - replace internal IP with external IP + echo "Updating API server address in kubeconfig to use external IP ${LINUX_EIP}..." + INTERNAL_API_SERVER=$(grep 'server:' ./kubeconfig | awk '{print $2}') + echo " Original API server address: ${INTERNAL_API_SERVER}" + + # Extract port from the original server URL + API_PORT=$(echo "${INTERNAL_API_SERVER}" | sed -n 's/.*:\([0-9]*\)$/\1/p') + if [[ -z "${API_PORT}" ]]; then + API_PORT="6443" # Default Kubernetes API port + fi + + NEW_API_SERVER="https://${LINUX_EIP}:${API_PORT}" + echo " New API server address: ${NEW_API_SERVER}" + + # Update the kubeconfig with the external IP + sed -i "s|${INTERNAL_API_SERVER}|${NEW_API_SERVER}|g" ./kubeconfig + + echo "Kubeconfig saved to ./kubeconfig with external API server address" + + echo "Kubernetes cluster setup completed successfully!" +} + +function join_linux_worker_nodes() { + echo "Checking for additional Linux worker nodes to join..." + + if [[ ${LINUX_NODE_COUNT} -le 1 ]]; then + echo "Only one Linux node (control-plane), no additional workers to join" + return 0 + fi + + echo "Joining ${LINUX_NODE_COUNT} - 1 additional Linux worker node(s) to the cluster..." + + # Loop through Linux nodes starting from index 1 (node 2) + for ((i=1; i<${LINUX_NODE_COUNT}; i++)); do + local node_num=$((i+1)) + local linux_eip="${LINUX_EIPS[$i]}" + + if [[ -z "$linux_eip" ]]; then + echo "ERROR: Linux node ${node_num} EIP is empty!" + return 1 + fi + + # Get the connect command for this node + local connect_var="LINUX_NODE_${i}_CONNECT" + local linux_connect_command="${!connect_var}" + + echo "=====================================" + echo "Joining Linux Node ${node_num} (${linux_eip}) as worker" + echo "=====================================" + + # Install prerequisites and kubeadm on worker node + echo "Installing kubeadm, kubelet, and kubectl on Linux worker node ${node_num}..." + ${linux_connect_command} "~/setup-node.sh ${KUBE_VERSION}" + + # Join the node to the cluster + echo "Joining Linux worker node ${node_num} to the cluster..." + echo "Using join command: ${KUBEADM_JOIN_COMMAND}" + + if ! ${linux_connect_command} "~/join-worker.sh ${KUBEADM_JOIN_COMMAND}"; then + echo "ERROR: Failed to join Linux node ${node_num} to the cluster" + return 1 + fi + + echo "Linux worker node ${node_num} joined successfully!" + echo + done + + echo "All Linux worker nodes joined successfully!" + ${MASTER_CONNECT_COMMAND} kubectl get nodes -o wide +} + +function join_windows_worker_node() { + local windows_eip="$1" + local windows_pip="$2" + local node_index="$3" # Optional, for display purposes + + if [[ -z "$windows_eip" ]]; then + echo "ERROR: No Windows node EIP provided to join_windows_worker_node" + return 1 + fi + + if [[ -z "$windows_pip" ]]; then + echo "ERROR: No Windows node PIP provided to join_windows_worker_node" + return 1 + fi + + local display_name="${node_index:-$windows_eip}" + echo "=====================================" + echo "Joining Windows Node ${display_name} (${windows_eip})" + echo "=====================================" + + local windows_connect_command="ssh -i ${SSH_KEY_FILE} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o ConnectTimeout=10 -o ServerAliveInterval=5 -o ServerAliveCountMax=3 aso@${windows_eip} powershell" + + # Install kubeadm on Windows node + echo "Installing kubeadm on Windows node ${display_name}..." + ${windows_connect_command} "powershell -ExecutionPolicy Bypass -File c:\\k\\install-kubeadm.ps1 -K8sVersion ${KUBE_VERSION}" + + # Extract just the arguments after "kubeadm join" + JOIN_ARGS=$(echo "${KUBEADM_JOIN_COMMAND}" | sed 's/kubeadm join //') + + echo "Join arguments: ${JOIN_ARGS}" + + # Execute join script with join arguments + echo "Joining Windows node ${display_name} to cluster..." + ${windows_connect_command} "powershell -ExecutionPolicy Bypass -File c:\\k\\join-cluster.ps1 -JoinArgs '${JOIN_ARGS}' -WindowsEip '${windows_eip}'" + + echo "Windows worker node ${display_name} joined successfully!" + echo +} + +function copy_files_from_linux() { + echo "Copying Kubernetes certificates and config from Linux node..." + mkdir -p ./windows/kubeadm + + # Copy kubeconfig + scp -i "${SSH_KEY_FILE}" -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "aso@${LINUX_EIP}:/home/aso/.kube/config" ./windows/kubeadm/config + + # Copy Kubernetes PKI certificates (needed for authentication) + ${MASTER_CONNECT_COMMAND} "sudo cp /etc/kubernetes/pki/ca.crt /tmp/ca.crt && sudo chmod 644 /tmp/ca.crt" + scp -i "${SSH_KEY_FILE}" -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "aso@${LINUX_EIP}:/tmp/ca.crt" ./windows/kubeadm/ + + echo "Kubernetes certificates copied successfully" +} + +function prepare_windows_configuration() { + echo "Preparing Windows configuration files..." + + # Extract client certificate and key data from the copied kubeconfig + export CLIENT_CERT_DATA=$(grep 'client-certificate-data' ./windows/kubeadm/config | awk '{print $2}') + export CLIENT_KEY_DATA=$(grep 'client-key-data' ./windows/kubeadm/config | awk '{print $2}') + + # Generate Windows-specific scripts with templates + ${GOMPLATE} --file ./config-kubeadm --out ./windows/config + + echo "Windows configuration files prepared" +} + +function prepare_windows_node() { + local windows_eip="$1" + local node_index="$2" # Optional, for display purposes + + if [[ -z "$windows_eip" ]]; then + echo "ERROR: No Windows node IP provided to prepare_windows_node" + return 1 + fi + + local display_name="${node_index:-$windows_eip}" + echo "=====================================" + echo "Preparing Windows node ${display_name} (${windows_eip})" + echo "=====================================" + + # Create SSH connect command + local windows_connect_command="ssh -i ${SSH_KEY_FILE} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o ConnectTimeout=10 -o ServerAliveInterval=5 -o ServerAliveCountMax=3 aso@${windows_eip} powershell" + + # Create c:\k directory on Windows node if it doesn't exist + echo "Creating c:\\k directory..." + ${windows_connect_command} "if (-not (Test-Path c:\\k)) { New-Item -ItemType Directory -Path c:\\k -Force }" + + # Copy windows directory contents to c:\k\ + echo "Copying Windows files to node..." + scp -r -i "${SSH_KEY_FILE}" -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no ./windows/* "aso@${windows_eip}:c:\\k\\" + + # Enable containers feature (requires reboot) + echo "Enabling Windows Containers feature..." + ${windows_connect_command} "c:\\k\\enable-containers-with-reboot.ps1 -Force" + + # Wait for node to come back online after reboot + sleep 10 + echo "Waiting for node to be ready after reboot..." + retry_command 60 "${windows_connect_command} Write-Host 'Node is ready'" + + # Install containerd + echo "Installing containerd..." + if ! ${windows_connect_command} "c:\\k\\install-containerd.ps1 -ContainerDVersion ${CONTAINERD_VERSION} -Force"; then + echo "ERROR: Failed to install containerd on Windows node ${display_name}" + echo "You can SSH to the node to debug: ${windows_connect_command}" + return 1 + fi + + # Wait for node to come back online after reboot + sleep 10 + echo "Waiting for node to be ready after reboot..." + retry_command 60 "${windows_connect_command} Write-Host 'Node is ready'" + + echo "Windows node ${display_name} prepared successfully" + echo + return 0 +} + +copy_scripts_to_linux_nodes +copy_scripts_to_windows_nodes + +echo "Setting up Kubernetes cluster on Linux control plane..." +redirect_output setup_kubeadm_cluster +echo "✓ Kubernetes cluster initialized" + +echo "Joining Linux worker nodes..." +redirect_output join_linux_worker_nodes +echo "✓ Linux worker nodes joined" + +copy_files_from_linux +prepare_windows_configuration + +# Prepare each Windows node individually +echo "Preparing ${WINDOWS_NODE_COUNT} Windows node(s)..." +for ((win_idx=0; win_idx<${WINDOWS_NODE_COUNT}; win_idx++)); do + node_num=$((win_idx+1)) + echo " Preparing Windows node ${node_num}..." + redirect_output prepare_windows_node "${WINDOWS_EIPS[$win_idx]}" "${node_num}" + echo " ✓ Windows node ${node_num} prepared" +done +echo "✓ All Windows nodes prepared successfully" + +# Join each Windows node to the cluster +echo "Joining ${WINDOWS_NODE_COUNT} Windows node(s) to the cluster..." +for ((win_idx=0; win_idx<${WINDOWS_NODE_COUNT}; win_idx++)); do + node_num=$((win_idx+1)) + redirect_output join_windows_worker_node "${WINDOWS_EIPS[$win_idx]}" "${WINDOWS_PIPS[$win_idx]}" "${node_num}" +done +echo "All Windows nodes joined successfully!" +${MASTER_CONNECT_COMMAND} kubectl get nodes -o wide + +echo +echo "Cluster setup complete! Final status:" +${MASTER_CONNECT_COMMAND} kubectl get pod -A -o wide +echo diff --git a/process/testing/aso/linux/init-cluster.sh b/process/testing/aso/linux/init-cluster.sh new file mode 100755 index 00000000000..178c36a574a --- /dev/null +++ b/process/testing/aso/linux/init-cluster.sh @@ -0,0 +1,32 @@ +#!/bin/bash +set -e + +echo "=== Initializing Kubernetes cluster with kubeadm ===" + +KUBEADM_CONFIG=${1} + +if [ -z "${KUBEADM_CONFIG}" ]; then + echo "ERROR: No kubeadm config yaml provided" + exit 1 +fi + +if [ ! -f "${KUBEADM_CONFIG}" ]; then + echo "ERROR: kubeadm config yaml file ${KUBEADM_CONFIG} not found" + echo "Usage: $0 " + exit 1 +fi + +echo "Initializing cluster with:" +echo " kubeadm config yaml: ${KUBEADM_CONFIG}" + +# Initialize kubeadm cluster +sudo kubeadm init --config "${KUBEADM_CONFIG}" + +# Set up kubeconfig for current user +echo "Setting up kubeconfig..." +mkdir -p $HOME/.kube +sudo cp -f /etc/kubernetes/admin.conf $HOME/.kube/config +sudo chown $(id -u):$(id -g) $HOME/.kube/config + +echo "Kubernetes cluster initialized successfully!" +echo "You can now use kubectl to interact with the cluster." diff --git a/process/testing/aso/linux/join-worker.sh b/process/testing/aso/linux/join-worker.sh new file mode 100755 index 00000000000..db34e850635 --- /dev/null +++ b/process/testing/aso/linux/join-worker.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -e + +echo "=== Joining Kubernetes cluster as worker node ===" + +# Get the join command (should be passed as arguments) +JOIN_COMMAND="$*" + +if [ -z "$JOIN_COMMAND" ]; then + echo "ERROR: No join command provided" + echo "Usage: $0 " + exit 1 +fi + +echo "Executing join command..." +sudo $JOIN_COMMAND + +echo "Successfully joined the cluster as a worker node!" + diff --git a/process/testing/aso/linux/setup-node.sh b/process/testing/aso/linux/setup-node.sh new file mode 100755 index 00000000000..e2aa089bc0d --- /dev/null +++ b/process/testing/aso/linux/setup-node.sh @@ -0,0 +1,88 @@ +#!/bin/bash +set -e + +echo "=== Setting up Kubernetes prerequisites ===" + +# Disable swap (required for Kubernetes) +echo "Disabling swap..." +sudo swapoff -a +sudo sed -i '/ swap / s/^/#/' /etc/fstab + +# Load required kernel modules +echo "Loading kernel modules..." +cat < /dev/null; then + echo "ERROR: containerd is not installed!" + echo "Please ensure the VM extension in vmss-linux.yaml has installed containerd." + exit 1 +fi + +echo "Containerd found: $(containerd --version)" + +# Verify containerd configuration +if [ ! -f /etc/containerd/config.toml ]; then + echo "ERROR: containerd config file /etc/containerd/config.toml not found!" + exit 1 +fi + +# Verify systemd cgroup is enabled +if ! grep -q "SystemdCgroup = true" /etc/containerd/config.toml; then + echo "WARNING: SystemdCgroup is not enabled in containerd config" + + echo "Enabling SystemdCgroup in containerd config" + # Enable systemd cgroup driver (required for Kubernetes) + sudo sed -i "s/SystemdCgroup = false/SystemdCgroup = true/g" /etc/containerd/config.toml + sudo systemctl restart containerd +fi + +# Ensure containerd is running +sudo systemctl status containerd --no-pager || { + echo "ERROR: containerd service is not running!" + exit 1 +} + +# Install kubeadm, kubelet, and kubectl +KUBE_VERSION=${1:-"v1.33.7"} +# Extract major.minor version (e.g., v1.33.6 -> v1.33) +KUBE_MAJOR_MINOR=$(echo "${KUBE_VERSION}" | cut -d'.' -f1,2) +echo "Installing kubeadm, kubelet, and kubectl ${KUBE_VERSION}..." +echo "Using repository version: ${KUBE_MAJOR_MINOR}" +sudo apt-get update +sudo apt-get -o DPkg::Lock::Timeout=60 install -y apt-transport-https ca-certificates curl gpg + +# Create keyrings directory +sudo mkdir -p /etc/apt/keyrings + +# Add Kubernetes apt repository +curl -fsSL "https://pkgs.k8s.io/core:/stable:/${KUBE_MAJOR_MINOR}/deb/Release.key" | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg +echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/${KUBE_MAJOR_MINOR}/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list + +# Install Kubernetes components +sudo apt-get update +sudo apt-get -o DPkg::Lock::Timeout=60 install -y kubelet kubeadm kubectl +sudo apt-mark hold kubelet kubeadm kubectl + +# Enable kubelet +sudo systemctl enable kubelet + +echo "Kubeadm installation completed successfully!" + diff --git a/process/testing/aso/test-run.sh b/process/testing/aso/test-run.sh new file mode 100755 index 00000000000..a3c6e399df3 --- /dev/null +++ b/process/testing/aso/test-run.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -e +set -x + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${SCRIPT_DIR}" + +. ../util/utils.sh + +# Create cluster +make setup-kubeadm + +# Install Calico +make install-calico + +pause-for-debug diff --git a/process/testing/aso/vmss.sh b/process/testing/aso/vmss.sh new file mode 100755 index 00000000000..f3622a2be8d --- /dev/null +++ b/process/testing/aso/vmss.sh @@ -0,0 +1,754 @@ +#!/bin/bash +# Copyright (c) 2024-2025 Tigera, Inc. All rights reserved. +# +# 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. + +# Azure Service Operator v2 VirtualMachine Management Script +# +# This script manages Azure VirtualMachine resources using Azure Service Operator v2. +# It has been enhanced with ASO v2 best practices including: +# +# Usage: +# ./vmss.sh create - Create VirtualMachine resources with ASO v2 +# ./vmss.sh info - Show VM connection information +# ./vmss.sh confirm-ssh - Verify SSH connectivity +# ./vmss.sh diagnose - Diagnose ASO v2 resource status +# ./vmss.sh delete - Delete ASO v2 resources and cleanup namespace +# +# Requirements: +# - kubectl configured with cluster access +# - gomplate for template processing +# - Azure credentials (AZURE_SUBSCRIPTION_ID, AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET) +# + +: ${ASO_DIR:="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"} + +. ${ASO_DIR}/export-env.sh + +. ${ASO_DIR}/../util/utils.sh + +: ${KUBECTL:=${ASO_DIR}/bin/kubectl} +: ${GOMPLATE:=${ASO_DIR}/bin/gomplate} + +# Trap function to run diagnostics on non-zero exit +function exit_handler() { + local exit_code=$? + + # Only run diagnostics if: + # 1. Exit code is non-zero (failure) + # 2. We're not already in the diagnose command (avoid recursion) + if [[ $exit_code -ne 0 && "$1" != "diagnose" ]]; then + log_warning "Script failed with exit code $exit_code, running diagnostics..." + echo "==================================" + echo "AUTOMATIC DIAGNOSTICS ON FAILURE:" + echo "==================================" + diagnose_aso_resources + pause-for-debug + fi +} + +# Set up the trap to run on script exit +trap exit_handler EXIT + + +function ensure_aso_credentials() { + # Configure namespaced ASO credentials only + log_info "Configuring namespaced ASO v2 credentials..." + + # Create namespace for our resources + ${KUBECTL} create namespace aso --dry-run=client -o yaml | ${KUBECTL} apply -f - + + log_info "Creating namespaced ASO credentials in aso namespace..." + + # Create the namespaced credential secret + cat < ${ASO_DIR}/password.txt +-------------Connect to Windows Instances------------- +username: aso +password: $PASSWORD +password-base64: $PASSWORD_BASE64 +EOF + log_info "Generated Windows credentials: username=aso, password saved to password.txt" + + rm -f ${SSH_KEY_FILE} ${SSH_KEY_FILE}.pub + ssh-keygen -m PEM -t rsa -b 2048 -f "${SSH_KEY_FILE}" -N '' -C "" 1>/dev/null + log_info "Machine SSH key generated in ${SSH_KEY_FILE}" + export PUBLIC_KEY=$(cat ${SSH_KEY_FILE}.pub) + + rm -rf ${ASO_DIR}/infra/manifests || true + ${GOMPLATE} --input-dir ${ASO_DIR}/infra/templates --output-dir ${ASO_DIR}/infra/manifests + log_info "Generated manifests with gomplate" + + # Apply resources in dependency order with ASO v2 patterns + log_info "Applying Azure resources with ASO v2..." + + # Step 1: Resource Group (foundational) + log_info "Creating Resource Group..." + ${KUBECTL} apply -f ${ASO_DIR}/infra/manifests/resource-group.yaml + + if ! wait_for_aso_resource "resourcegroup" "$AZURE_RESOURCE_GROUP" "aso" "300s"; then + log_error "Failed to create Resource Group" + return 1 + fi + + # Step 2: Networking components + log_info "Creating networking components..." + ${KUBECTL} apply -f ${ASO_DIR}/infra/manifests/vnet.yaml + ${KUBECTL} apply -f ${ASO_DIR}/infra/manifests/security-group.yaml + + # Wait for VNet to be ready before proceeding + if ! wait_for_aso_resource "virtualnetwork" "vnet-aso" "aso" "300s"; then + log_error "Failed to create Virtual Network" + return 1 + fi + + # Step 3: Secrets + log_info "Creating secrets..." + ${KUBECTL} apply -f ${ASO_DIR}/infra/manifests/password.yaml + + # Step 4: Virtual Machines and Network Resources + log_info "Creating Virtual Machines and network resources..." + ${KUBECTL} apply -f ${ASO_DIR}/infra/manifests/vmss-linux.yaml + ${KUBECTL} apply -f ${ASO_DIR}/infra/manifests/vmss-windows.yaml + + # Build list of all VMs to wait for based on node counts + local vm_resources=() + for ((i=1; i<=${LINUX_NODE_COUNT}; i++)); do + vm_resources+=("vm-linux-${i}") + done + for ((i=1; i<=${WINDOWS_NODE_COUNT}; i++)); do + vm_resources+=("vm-windows-${i}") + done + + log_info "Waiting for ${#vm_resources[@]} VirtualMachines: ${vm_resources[*]}" + + # Wait for VMs with extended timeout + local failed_vms=() + + for vm in "${vm_resources[@]}"; do + log_info "Waiting for VirtualMachine: $vm" + if ! wait_for_aso_resource "virtualmachine" "$vm" "aso" "$ASO_TIMEOUT_DEFAULT"; then + log_error "VirtualMachine $vm failed to become ready" + failed_vms+=("$vm") + + # Provide diagnostic information + log_info "Diagnostic information for $vm:" + ${KUBECTL} describe virtualmachine "$vm" -n aso | tail -20 + else + log_info "VirtualMachine $vm is ready" + fi + done + + if [[ ${#failed_vms[@]} -gt 0 ]]; then + log_error "Failed VirtualMachine resources: ${failed_vms[*]}" + log_info "Use '${KUBECTL} describe virtualmachine -n aso' for more details" + return 1 + fi + + log_info "All ASO v2 resources applied and reconciled successfully" +} + +# Default timeout for ASO operations (15 minutes for VMs) +: ${ASO_TIMEOUT_DEFAULT:=900s} + +# Wait for ASO v2 resources to become ready using condition-based checking +function wait_for_aso_resource() { + local resource_type="$1" + local resource_name="$2" + local namespace="$3" + local timeout="${4:-$ASO_TIMEOUT_DEFAULT}" + + log_info "Waiting for $resource_type/$resource_name in namespace $namespace (timeout: $timeout)..." + + # First, wait for the resource to exist (up to 60 seconds) + local wait_count=0 + while ! ${KUBECTL} get "$resource_type/$resource_name" -n "$namespace" >/dev/null 2>&1; do + if [[ $wait_count -ge 60 ]]; then + log_fail "$resource_type/$resource_name does not exist after 60 seconds" + return 1 + fi + log_info "Waiting for $resource_type/$resource_name to be created... (${wait_count}s)" + sleep 1 + ((wait_count++)) + done + + # Now wait for the Ready condition + if ${KUBECTL} wait --for=condition=Ready "$resource_type/$resource_name" -n "$namespace" --timeout="$timeout"; then + log_info "$resource_type/$resource_name is ready" + return 0 + else + log_fail "$resource_type/$resource_name failed to become ready within $timeout" + + # Show diagnostic information + log_info "Resource status for debugging:" + ${KUBECTL} describe "$resource_type" "$resource_name" -n "$namespace" | tail -10 + return 1 + fi +} + +function get_and_export_node_ips() { + # Wait for vm deployments with ASO v2 patterns + log_info "Getting and exporting node IPs..." + + LINUX_PIPS=() + LINUX_EIPS=() + + for ((i=1; i<=${LINUX_NODE_COUNT}; i++)); do + local vm_name="vm-linux-${i}" + local nic_name="nic-linux-${i}" + local pip_name="pip-linux-${i}" + + log_info "Ensuring ${vm_name} is ready with ASO v2 status check..." + if ! wait_for_aso_resource "virtualmachine" "${vm_name}" "aso" "480s"; then + log_error "${vm_name} did not become ready in time" + return 1 + fi + + # Get IP addresses from ASO CRDs + log_info "Getting ${vm_name} IP addresses from ASO resources..." + local pip=$(${KUBECTL} get networkinterface ${nic_name} -n aso -o jsonpath='{.status.ipConfigurations[0].privateIPAddress}' 2>/dev/null || echo "") + local eip=$(${KUBECTL} get publicipaddress ${pip_name} -n aso -o jsonpath='{.status.ipAddress}' 2>/dev/null || echo "") + + if [[ -z "$eip" || -z "$pip" ]]; then + log_error "Failed to retrieve IP addresses for ${vm_name} from ASO resources" + log_info "Checking ASO resource status for debugging..." + ${KUBECTL} get networkinterface ${nic_name} -n aso -o yaml | grep -A 10 "status:" || true + ${KUBECTL} get publicipaddress ${pip_name} -n aso -o yaml | grep -A 10 "status:" || true + return 1 + fi + + LINUX_PIPS+=("$pip") + LINUX_EIPS+=("$eip") + log_info "${vm_name} is ready. PIP:$pip, EIP:$eip" + done + + # Export first Linux node as master + export LINUX_PIP="${LINUX_PIPS[0]}" + export LINUX_EIP="${LINUX_EIPS[0]}" + + # Export arrays as space-separated strings for cross-shell compatibility + # Bash arrays cannot be exported to child processes, so we export as strings + export LINUX_PIPS_STR="${LINUX_PIPS[*]}" + export LINUX_EIPS_STR="${LINUX_EIPS[*]}" + + WINDOWS_PIPS=() + WINDOWS_EIPS=() + + for ((i=1; i<=${WINDOWS_NODE_COUNT}; i++)); do + local vm_name="vm-windows-${i}" + local nic_name="nic-windows-${i}" + local pip_name="pip-windows-${i}" + + log_info "Ensuring ${vm_name} is ready with ASO v2 status check..." + if ! wait_for_aso_resource "virtualmachine" "${vm_name}" "aso" "480s"; then + log_error "${vm_name} did not become ready in time" + return 1 + fi + + # Get IP addresses from ASO CRDs for Windows VM + log_info "Getting ${vm_name} IP addresses from ASO resources..." + local pip=$(${KUBECTL} get networkinterface ${nic_name} -n aso -o jsonpath='{.status.ipConfigurations[0].privateIPAddress}' 2>/dev/null || echo "") + local eip=$(${KUBECTL} get publicipaddress ${pip_name} -n aso -o jsonpath='{.status.ipAddress}' 2>/dev/null || echo "") + + if [[ -z "$eip" || -z "$pip" ]]; then + log_error "Failed to retrieve IP addresses for ${vm_name} from ASO resources" + log_info "Checking ASO resource status for debugging..." + ${KUBECTL} get networkinterface ${nic_name} -n aso -o yaml | grep -A 10 "status:" || true + ${KUBECTL} get publicipaddress ${pip_name} -n aso -o yaml | grep -A 10 "status:" || true + return 1 + fi + + WINDOWS_PIPS+=("$pip") + WINDOWS_EIPS+=("$eip") + log_info "${vm_name} is ready. PIP:$pip, EIP:$eip" + done + + # Export first Windows node + export WINDOWS_PIP="${WINDOWS_PIPS[0]}" + export WINDOWS_EIP="${WINDOWS_EIPS[0]}" + + # Export arrays as space-separated strings for cross-shell compatibility + # Bash arrays cannot be exported to child processes, so we export as strings + export WINDOWS_PIPS_STR="${WINDOWS_PIPS[*]}" + export WINDOWS_EIPS_STR="${WINDOWS_EIPS[*]}" + + # Setup connection info + MASTER_CONNECT_COMMAND="ssh -i ${SSH_KEY_FILE} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o ConnectTimeout=10 -o ServerAliveInterval=5 -o ServerAliveCountMax=3 aso@${LINUX_EIP}" + WINDOWS_CONNECT_COMMAND="ssh -i ${SSH_KEY_FILE} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o ConnectTimeout=10 -o ServerAliveInterval=5 -o ServerAliveCountMax=3 aso@${WINDOWS_EIP} powershell" + + # Create individual connect commands for each Linux node + for ((i=0; i<${LINUX_NODE_COUNT}; i++)); do + local var_name="LINUX_NODE_${i}_CONNECT" + local cmd="ssh -i ${SSH_KEY_FILE} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o ConnectTimeout=10 -o ServerAliveInterval=5 -o ServerAliveCountMax=3 aso@${LINUX_EIPS[$i]}" + eval "export ${var_name}='${cmd}'" + done + + # Create individual connect commands for each Windows node (both regular and powershell) + for ((i=0; i<${WINDOWS_NODE_COUNT}; i++)); do + local var_name="WINDOWS_NODE_${i}_CONNECT" + local var_name_ps="WINDOWS_NODE_${i}_CONNECT_PS" + local cmd="ssh -i ${SSH_KEY_FILE} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o ConnectTimeout=10 -o ServerAliveInterval=5 -o ServerAliveCountMax=3 aso@${WINDOWS_EIPS[$i]}" + local cmd_ps="ssh -i ${SSH_KEY_FILE} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o ConnectTimeout=10 -o ServerAliveInterval=5 -o ServerAliveCountMax=3 aso@${WINDOWS_EIPS[$i]} powershell" + eval "export ${var_name}='${cmd}'" + eval "export ${var_name_ps}='${cmd_ps}'" + done + + # Export arrays for use in other scripts + export LINUX_PIPS LINUX_EIPS WINDOWS_PIPS WINDOWS_EIPS MASTER_CONNECT_COMMAND WINDOWS_CONNECT_COMMAND CONTAINERD_VERSION + + log_info "Node IPs retrieved and exported successfully" +} + +function generate_and_show_connect_file() { + log_info "Generating connect.txt..." + + WIN_PASSWORD=$(grep "password:" ${ASO_DIR}/password.txt | awk -F':' '{print $2}') + + cat << EOF > ${ASO_DIR}/connect.txt +-------------Connect to Linux Master Instance--------- +${MASTER_CONNECT_COMMAND} + +EOF + + # Add all Linux nodes to connect.txt + for ((i=1; i<=${LINUX_NODE_COUNT}; i++)); do + cat << EOF >> ${ASO_DIR}/connect.txt +-------------Connect to Linux Node ${i}---------------- +ssh -i ${SSH_KEY_FILE} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no aso@${LINUX_EIPS[$((i-1))]} +PIP: ${LINUX_PIPS[$((i-1))]} +EIP: ${LINUX_EIPS[$((i-1))]} + +EOF + done + + # Add all Windows nodes to connect.txt + for ((i=1; i<=${WINDOWS_NODE_COUNT}; i++)); do + cat << EOF >> ${ASO_DIR}/connect.txt +-------------Connect to Windows Node ${i}------------- +ssh -i ${SSH_KEY_FILE} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no aso@${WINDOWS_EIPS[$((i-1))]} powershell +PIP: ${WINDOWS_PIPS[$((i-1))]} +EIP: ${WINDOWS_EIPS[$((i-1))]} + +EOF + done + + echo + cat ${ASO_DIR}/connect.txt + + log_info "connect.txt generated successfully" +} + +function generate_helper_files() { + log_info "Generating helper files..." + + # Generate ssh-node-linux.sh with node index support + cat << EOF > ${ASO_DIR}/ssh-node-linux.sh +#!/bin/bash +# Usage: ./ssh-node-linux.sh [node_index] [command...] +# Examples: +# ./ssh-node-linux.sh 0 "hostname" # SSH to first Linux node (index 0) +# ./ssh-node-linux.sh 1 "kubectl get nodes" # SSH to second Linux node (index 1) +# ./ssh-node-linux.sh "kubectl get nodes" # SSH to first node (default, backward compatible) + +# IP addresses embedded at generation time from exported string +LINUX_EIPS=(${LINUX_EIPS_STR}) +SSH_KEY_FILE="${SSH_KEY_FILE}" + +# Check if first arg is a number (node index) +if [[ "\$1" =~ ^[0-9]+\$ ]]; then + NODE_INDEX=\$1 + shift # Remove the index from arguments +else + NODE_INDEX=0 # Default to first node for backward compatibility +fi + +# Validate node index +if [[ \$NODE_INDEX -ge \${#LINUX_EIPS[@]} ]]; then + echo "Error: Node index \$NODE_INDEX out of range. Available Linux nodes: 0-\$((\${#LINUX_EIPS[@]}-1))" + exit 1 +fi + +LINUX_EIP="\${LINUX_EIPS[\$NODE_INDEX]}" +ssh -i \${SSH_KEY_FILE} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o ConnectTimeout=10 -o ServerAliveInterval=5 -o ServerAliveCountMax=3 aso@\${LINUX_EIP} "\$@" +EOF + chmod +x ${ASO_DIR}/ssh-node-linux.sh + + # Generate ssh-node-windows.sh with node index support + cat << EOF > ${ASO_DIR}/ssh-node-windows.sh +#!/bin/bash +# Usage: ./ssh-node-windows.sh [node_index] [command] +# Examples: +# ./ssh-node-windows.sh 0 "Get-Process" # SSH to first Windows node (index 0) +# ./ssh-node-windows.sh 1 "ipconfig /all" # SSH to second Windows node (index 1) +# ./ssh-node-windows.sh "Get-Process" # SSH to first node (default, backward compatible) + +# IP addresses embedded at generation time from exported string +WINDOWS_EIPS=(${WINDOWS_EIPS_STR}) +SSH_KEY_FILE="${SSH_KEY_FILE}" + +# Check if first arg is a number (node index) +if [[ "\$1" =~ ^[0-9]+\$ ]]; then + NODE_INDEX=\$1 + shift # Remove the index from arguments +else + NODE_INDEX=0 # Default to first node for backward compatibility +fi + +# Validate node index +if [[ \$NODE_INDEX -ge \${#WINDOWS_EIPS[@]} ]]; then + echo "Error: Node index \$NODE_INDEX out of range. Available Windows nodes: 0-\$((\${#WINDOWS_EIPS[@]}-1))" + exit 1 +fi + +WINDOWS_EIP="\${WINDOWS_EIPS[\$NODE_INDEX]}" +ssh -i \${SSH_KEY_FILE} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o ConnectTimeout=10 -o ServerAliveInterval=5 -o ServerAliveCountMax=3 aso@\${WINDOWS_EIP} powershell "\$@" +EOF + chmod +x ${ASO_DIR}/ssh-node-windows.sh + + # Generate scp-to-windows.sh with node index support + cat << EOF > ${ASO_DIR}/scp-to-windows.sh +#!/bin/bash +# Usage: ./scp-to-windows.sh [node_index] +# Examples: +# ./scp-to-windows.sh 0 kubeconfig c:\\\\k\\\\kubeconfig # Copy to first Windows node (index 0) +# ./scp-to-windows.sh 1 test.zip 'c:\\\\' # Copy to second Windows node (index 1) +# ./scp-to-windows.sh kubeconfig c:\\\\k\\\\kubeconfig # Copy to first node (default, backward compatible) + +# IP addresses embedded at generation time from exported string +WINDOWS_EIPS=(${WINDOWS_EIPS_STR}) +SSH_KEY_FILE="${SSH_KEY_FILE}" + +# Check if first arg is a number (node index) +if [[ "\$1" =~ ^[0-9]+\$ ]]; then + NODE_INDEX=\$1 + shift # Remove the index from arguments + LOCAL_FILE=\$1 + REMOTE_PATH=\$2 +else + NODE_INDEX=0 # Default to first node for backward compatibility + LOCAL_FILE=\$1 + REMOTE_PATH=\$2 +fi + +# Validate arguments +if [[ -z "\$LOCAL_FILE" ]] || [[ -z "\$REMOTE_PATH" ]]; then + echo "Usage: \$0 [node_index] " + echo "Examples:" + echo " \$0 0 kubeconfig c:\\\\\\\\k\\\\\\\\kubeconfig" + echo " \$0 1 images/file.zip 'c:\\\\\\\\'" + echo " \$0 kubeconfig c:\\\\\\\\k\\\\\\\\kubeconfig # defaults to node 0" + exit 1 +fi + +# Validate node index +if [[ \$NODE_INDEX -ge \${#WINDOWS_EIPS[@]} ]]; then + echo "Error: Node index \$NODE_INDEX out of range. Available Windows nodes: 0-\$((\${#WINDOWS_EIPS[@]}-1))" + exit 1 +fi + +WINDOWS_EIP="\${WINDOWS_EIPS[\$NODE_INDEX]}" +scp -r -i \${SSH_KEY_FILE} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o ConnectTimeout=10 "\$LOCAL_FILE" aso@\${WINDOWS_EIP}:"\$REMOTE_PATH" +EOF + chmod +x ${ASO_DIR}/scp-to-windows.sh + + # Generate scp-from-windows.sh with node index support + cat << EOF > ${ASO_DIR}/scp-from-windows.sh +#!/bin/bash +# Usage: ./scp-from-windows.sh [node_index] +# Examples: +# ./scp-from-windows.sh 0 c:\\\\k\\\\calico.log ./calico.log # Copy from first Windows node (index 0) +# ./scp-from-windows.sh 1 c:\\\\k\\\\report . # Copy from second Windows node (index 1) +# ./scp-from-windows.sh c:\\\\k\\\\calico.log ./calico.log # Copy from first node (default, backward compatible) + +# IP addresses embedded at generation time from exported string +WINDOWS_EIPS=(${WINDOWS_EIPS_STR}) +SSH_KEY_FILE="${SSH_KEY_FILE}" + +# Check if first arg is a number (node index) +if [[ "\$1" =~ ^[0-9]+\$ ]]; then + NODE_INDEX=\$1 + shift # Remove the index from arguments + REMOTE_PATH=\$1 + LOCAL_FILE=\$2 +else + NODE_INDEX=0 # Default to first node for backward compatibility + REMOTE_PATH=\$1 + LOCAL_FILE=\$2 +fi + +# Validate arguments +if [[ -z "\$REMOTE_PATH" ]] || [[ -z "\$LOCAL_FILE" ]]; then + echo "Usage: \$0 [node_index] " + echo "Examples:" + echo " \$0 0 c:\\\\\\\\k\\\\\\\\calico.log ./calico.log" + echo " \$0 1 c:\\\\\\\\k\\\\\\\\report ." + echo " \$0 c:\\\\\\\\k\\\\\\\\calico.log ./calico.log # defaults to node 0" + exit 1 +fi + +# Validate node index +if [[ \$NODE_INDEX -ge \${#WINDOWS_EIPS[@]} ]]; then + echo "Error: Node index \$NODE_INDEX out of range. Available Windows nodes: 0-\$((\${#WINDOWS_EIPS[@]}-1))" + exit 1 +fi + +WINDOWS_EIP="\${WINDOWS_EIPS[\$NODE_INDEX]}" +scp -r -i \${SSH_KEY_FILE} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o ConnectTimeout=10 aso@\${WINDOWS_EIP}:"\$REMOTE_PATH" "\$LOCAL_FILE" +EOF + chmod +x ${ASO_DIR}/scp-from-windows.sh + + log_info "Helper files generated successfully" +} + +# With static IP allocation, VMs should have stable IP addresses once ready. +# This function attempts to SSH into the VM. +function confirm-nodes-ssh() { + log_info "Starting SSH connectivity confirmation for VMs..." + + get_and_export_node_ips + generate_and_show_connect_file + generate_helper_files + + log_info "Testing SSH connectivity with retry logic..." + + # Helper function to test SSH connectivity with retries using generated helper scripts + test_ssh_connectivity() { + local vm_type="$1" # "linux" or "windows" + local node_index="$2" + local node_num=$((node_index + 1)) + local timeout=900 # 15 minutes (increased from 5 minutes for Windows VM boot time) + local retry_delay=30 # Increased from 15 seconds for less noise + local start_time=$(date +%s) + + local vm_name="${vm_type} VM ${node_num}" + log_info "Testing ${vm_name} SSH connectivity..." + + while true; do + # Use the generated helper scripts with timeout + local test_result=0 + if [[ "$vm_type" == "linux" ]]; then + timeout 30 ${ASO_DIR}/ssh-node-linux.sh $node_index "echo 'SSH test successful'" >/dev/null 2>&1 + test_result=$? + else + timeout 30 ${ASO_DIR}/ssh-node-windows.sh $node_index "Write-Host 'SSH test successful'" >/dev/null 2>&1 + test_result=$? + fi + + if [[ $test_result -eq 0 ]]; then + log_info "${vm_name} SSH connectivity test passed" + return 0 + fi + + local current_time=$(date +%s) + local elapsed=$((current_time - start_time)) + + if [[ $elapsed -ge $timeout ]]; then + log_fail "SSH connectivity test to ${vm_name} failed after 15 minutes of retries" + return 1 + fi + + log_info "${vm_name} SSH test failed, retrying in ${retry_delay} seconds... (elapsed: ${elapsed}s)" + sleep $retry_delay + done + } + + # Test all Linux VMs + for ((i=0; i<${LINUX_NODE_COUNT}; i++)); do + if ! test_ssh_connectivity "linux" $i; then + exit 1 + fi + done + + # Test all Windows VMs + for ((i=0; i<${WINDOWS_NODE_COUNT}; i++)); do + if ! test_ssh_connectivity "windows" $i; then + exit 1 + fi + done + + log_info "All SSH connectivity tests completed successfully" +} + +function diagnose_aso_resources() { + log_info "Diagnosing ASO v2 resources..." + + # Check if ASO is running + log_info "Checking ASO v2 controller status..." + if ${KUBECTL} get deployment azureserviceoperator-controller-manager -n azureserviceoperator-system &>/dev/null; then + ${KUBECTL} get pods -n azureserviceoperator-system + else + log_warn "ASO v2 controller not found" + fi + + # Check namespace and resources + if ${KUBECTL} get namespace aso &>/dev/null; then + log_info "ASO resources in aso namespace:" + ${KUBECTL} get virtualmachines,publicipaddresses,networkinterfaces,networksecuritygroups,virtualnetworks,resourcegroups -n aso -o wide 2>/dev/null || log_warn "No ASO resources found" + + # Check for stuck finalizers + log_info "Checking resources with finalizers:" + echo "=== Main ASO Resources ===" + ${KUBECTL} get virtualmachines,publicipaddresses,networkinterfaces,networksecuritygroups,virtualnetworks,resourcegroups -n aso -o jsonpath='{range .items[*]}{.kind}/{.metadata.name}: {.metadata.finalizers}{"\n"}{end}' 2>/dev/null || true + + echo "=== VM Extensions ===" + ${KUBECTL} get virtualmachinesextensions -n aso -o jsonpath='{range .items[*]}{.kind}/{.metadata.name}: {.metadata.finalizers}{"\n"}{end}' 2>/dev/null || true + + echo "=== Security Rules and Subnets ===" + ${KUBECTL} get networksecuritygroupssecurityrules,virtualnetworkssubnets -n aso -o jsonpath='{range .items[*]}{.kind}/{.metadata.name}: {.metadata.finalizers}{"\n"}{end}' 2>/dev/null || true + + # Check credential status + log_info "Checking ASO credentials:" + if ${KUBECTL} get secret aso-credential -n aso &>/dev/null; then + echo "✓ aso-credential secret exists in aso namespace" + else + echo "✗ aso-credential secret NOT found in aso namespace - this explains the credential errors!" + echo " Re-run './vmss.sh create' to recreate the required credentials" + fi + + # Show detailed status for VMs + log_info "Detailed VirtualMachine status:" + for ((i=1; i<=${LINUX_NODE_COUNT}; i++)); do + local vm="vm-linux-${i}" + if ${KUBECTL} get virtualmachine $vm -n aso &>/dev/null; then + echo "--- VirtualMachine: $vm ---" + ${KUBECTL} get virtualmachine $vm -n aso -o yaml | grep -A 20 "status:" | grep -E "(conditions|ready|message|reason)" + fi + done + for ((i=1; i<=${WINDOWS_NODE_COUNT}; i++)); do + local vm="vm-windows-${i}" + if ${KUBECTL} get virtualmachine $vm -n aso &>/dev/null; then + echo "--- VirtualMachine: $vm ---" + ${KUBECTL} get virtualmachine $vm -n aso -o yaml | grep -A 20 "status:" | grep -E "(conditions|ready|message|reason)" + fi + done + else + log_info "Namespace aso does not exist" + fi +} + +function delete_rg() { + log_info "Deleting Azure Resource Group directly using az cli" + + # Verify required environment variables + : "${AZURE_RESOURCE_GROUP:?Environment variable empty or not defined.}" + + # Check if resource group exists + log_info "Checking if resource group '${AZURE_RESOURCE_GROUP}' exists..." + if ! az group show --name "${AZURE_RESOURCE_GROUP}" &>/dev/null; then + log_info "Resource group '${AZURE_RESOURCE_GROUP}' does not exist, nothing to delete" + return 0 + fi + + log_info "Deleting resource group '${AZURE_RESOURCE_GROUP}'..." + if ! az group delete --name "${AZURE_RESOURCE_GROUP}" --yes --no-wait; then + log_error "Failed to initiate resource group deletion" + return 1 + fi + + log_info "Resource group deletion initiated, waiting for completion..." + + # Wait for resource group to be completely deleted + local timeout=600 # 10 minutes timeout + local count=0 + local check_interval=10 + + while [[ $count -lt $timeout ]]; do + if ! az group show --name "${AZURE_RESOURCE_GROUP}" &>/dev/null; then + log_info "Resource group '${AZURE_RESOURCE_GROUP}' has been deleted successfully" + + # Also clean up the aso namespace if it exists + if ${KUBECTL} get namespace aso &>/dev/null; then + log_info "Cleaning up aso namespace..." + ${KUBECTL} delete namespace aso --timeout=60s || true + fi + + return 0 + fi + + sleep $check_interval + count=$((count + check_interval)) + + if (( count % 60 == 0 )); then + log_info "Still waiting for resource group deletion... (${count}s elapsed)" + fi + done + + log_error "Resource group deletion timed out after ${timeout}s" + log_warn "You may need to check Azure portal for the status of '${AZURE_RESOURCE_GROUP}'" + return 1 +} + +case $1 in + create) + apply_azure_crds + ;; + node-ips) + get_and_export_node_ips + ;; + info) + get_and_export_node_ips + generate_and_show_connect_file + generate_helper_files + ;; + confirm-ssh) + confirm-nodes-ssh + ;; + diagnose) + diagnose_aso_resources + ;; + delete) + delete_rg + ;; + *) + echo "vmss.sh [create|info|confirm-ssh|diagnose|delete] - manages VirtualMachine resources with ASO v2" + echo "" + echo "Commands:" + echo " create - Create Azure VirtualMachine resources using ASO v2" + echo " node-ips - Get and export node IPs" + echo " info - Show VM connection information" + echo " confirm-ssh - Verify SSH connectivity to VMs" + echo " diagnose - Diagnose ASO v2 resource status and finalizers" + echo " delete - Delete ASO v2 resources and cleanup namespace" + echo "" + ;; +esac + diff --git a/process/testing/winfv-cni-plugin/aso/windows/create-network.ps1 b/process/testing/aso/windows/create-network.ps1 similarity index 100% rename from process/testing/winfv-cni-plugin/aso/windows/create-network.ps1 rename to process/testing/aso/windows/create-network.ps1 diff --git a/process/testing/winfv-cni-plugin/aso/windows/enable-containers-with-reboot.ps1 b/process/testing/aso/windows/enable-containers-with-reboot.ps1 similarity index 100% rename from process/testing/winfv-cni-plugin/aso/windows/enable-containers-with-reboot.ps1 rename to process/testing/aso/windows/enable-containers-with-reboot.ps1 diff --git a/process/testing/winfv-cni-plugin/aso/windows/install-containerd.ps1 b/process/testing/aso/windows/install-containerd.ps1 similarity index 95% rename from process/testing/winfv-cni-plugin/aso/windows/install-containerd.ps1 rename to process/testing/aso/windows/install-containerd.ps1 index 475fa56e5d9..c5c9272ef0c 100644 --- a/process/testing/winfv-cni-plugin/aso/windows/install-containerd.ps1 +++ b/process/testing/aso/windows/install-containerd.ps1 @@ -35,10 +35,10 @@ .PARAMETER ExternalNetAdapter Specify a specific network adapter to bind to a DHCP network - .PARAMETER Force + .PARAMETER Force If a restart is required, forces an immediate restart. - - .PARAMETER HyperV + + .PARAMETER HyperV If passed, prepare the machine for Hyper-V containers .PARAMETER NoRestart @@ -60,7 +60,7 @@ param( [string] [ValidateNotNullOrEmpty()] - $ContainerDVersion = "1.6.6", + $ContainerDVersion = "1.7.22", [string] [ValidateNotNullOrEmpty()] @@ -252,7 +252,7 @@ New-ContainerTransparentNetwork } Write-Output "Creating container network (Transparent)..." - + # Download and Install powershell module HNS-Network $containerdPath='C:\Program Files\containerd\cni\bin\' if (-not (Test-Path $containerdPath)){ @@ -278,7 +278,7 @@ Install-ContainerDHost { Write-Output "Enabling Hyper-V containers by default for Client SKU" $HyperV = $true - } + } } # # Validate required Windows features @@ -338,7 +338,7 @@ Install-ContainerDHost else { Write-Output "Networking is already configured. Confirming configuration..." - + $transparentNetwork = $networks |Where-Object { $_.Mode -eq "Transparent" } if ($null -eq $transparentNetwork) @@ -402,16 +402,16 @@ Copy-File param( [string] $SourcePath, - + [string] $DestinationPath ) - + if ($SourcePath -eq $DestinationPath) { return } - + if (Test-Path $SourcePath) { Copy-Item -Path $SourcePath -Destination $DestinationPath @@ -423,7 +423,7 @@ Copy-File $handler = New-Object System.Net.Http.HttpClientHandler $client = New-Object System.Net.Http.HttpClient($handler) $client.Timeout = New-Object System.TimeSpan(0, 30, 0) - $cancelTokenSource = [System.Threading.CancellationTokenSource]::new() + $cancelTokenSource = [System.Threading.CancellationTokenSource]::new() $responseMsg = $client.GetAsync([System.Uri]::new($SourcePath), $cancelTokenSource.Token) $responseMsg.Wait() @@ -439,9 +439,9 @@ Copy-File if ($null -ne $copyStreamOp.Exception) { throw $copyStreamOp.Exception - } + } } - } + } } elseif ($PSVersionTable.PSVersion.Major -ge 5) { @@ -456,7 +456,7 @@ Copy-File { $webClient = New-Object System.Net.WebClient $webClient.DownloadFile($SourcePath, $DestinationPath) - } + } } else { @@ -465,16 +465,16 @@ Copy-File } -function +function Test-Admin() { # Get the ID and security principal of the current user account $myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent() $myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID) - + # Get the security principal for the Administrator role $adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator - + # Check to see if we are currently running "as Administrator" if ($myWindowsPrincipal.IsInRole($adminRole)) { @@ -487,31 +487,31 @@ Test-Admin() # We are not running "as Administrator" # Exit from the current, unelevated, process # - throw "You must run this script as administrator" + throw "You must run this script as administrator" } } -function +function Test-Client() { return (-not ((Get-Command Get-WindowsFeature -ErrorAction SilentlyContinue) -or (Test-Nano))) } -function +function Test-Nano() { $EditionId = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'EditionID').EditionId - return (($EditionId -eq "ServerStandardNano") -or - ($EditionId -eq "ServerDataCenterNano") -or - ($EditionId -eq "NanoServer") -or + return (($EditionId -eq "ServerStandardNano") -or + ($EditionId -eq "ServerDataCenterNano") -or + ($EditionId -eq "NanoServer") -or ($EditionId -eq "ServerTuva")) } -function +function Wait-Network() { $connectedAdapter = Get-NetAdapter | Where-Object ConnectorPresent @@ -520,7 +520,7 @@ Wait-Network() { throw "No connected network" } - + $startTime = Get-Date $timeElapsed = $(Get-Date) - $startTime @@ -543,14 +543,14 @@ Wait-Network() } -function +function Install-Containerd() { [CmdletBinding()] param( [string] [ValidateNotNullOrEmpty()] - $ContainerdVersion = "1.6.6", + $ContainerdVersion = "1.7.22", [string] [ValidateNotNullOrEmpty()] @@ -639,25 +639,25 @@ Install-Containerd() } Write-Output "The following images are present on this machine:" - + nerdctl images -a | Write-Output } -function +function Start-Containerd() { Start-Service -Name $global:ContainerdServiceName } -function +function Stop-Containerd() { Stop-Service -Name $global:ContainerdServiceName } function -Remove-Containerd() +Remove-Containerd() { Stop-Containerd (Get-WmiObject -Class Win32_Service -Filter "Name='containerd'").delete() @@ -665,7 +665,7 @@ Remove-Containerd() Remove-Item -r -Force "$Env:ProgramFiles\nerdctl" } -function +function Test-Containerd() { $service = Get-Service -Name $global:ContainerdServiceName -ErrorAction SilentlyContinue @@ -674,7 +674,7 @@ Test-Containerd() } -function +function Wait-Containerd() { Write-Output "Waiting for Containerd daemon..." @@ -694,14 +694,14 @@ Wait-Containerd() $containerdReady = $true } - catch + catch { $timeElapsed = $(Get-Date) - $startTime if ($($timeElapsed).TotalMinutes -ge 1) { throw "Containerd Daemon did not start successfully within 1 minute." - } + } # Swallow error and try again Start-Sleep -sec 1 @@ -722,8 +722,12 @@ try Copy-Item "$Env:ProgramFiles\containerd\bin\ctr.exe" "c:\bin" C:\bin\ctr.exe --version - Write-Output "Pulling servercore:1809 image..." - C:\bin\ctr.exe -n k8s.io images pull mcr.microsoft.com/windows/servercore:1809 | Out-Null + # NOTE: Servercore image pre-pulling is disabled to avoid CI timeouts + # - Semaphore jobs occasionally hang indefinitely when pulling large servercore images + # - Pre-pulling servercore was primarily needed for Docker runtime (now deprecated) + + # Write-Output "Pulling servercore:1809 image..." + # C:\bin\ctr.exe -n k8s.io images pull mcr.microsoft.com/windows/servercore:1809 | Out-Null Write-Output "Pulling pause image..." c:\bin\ctr.exe images pull k8s.gcr.io/pause:3.5 | Out-Null @@ -735,7 +739,7 @@ try Write-Output "All done." } -catch +catch { Write-Error $_ -} \ No newline at end of file +} diff --git a/process/testing/aso/windows/install-kubeadm.ps1 b/process/testing/aso/windows/install-kubeadm.ps1 new file mode 100644 index 00000000000..3d25e6fa2fe --- /dev/null +++ b/process/testing/aso/windows/install-kubeadm.ps1 @@ -0,0 +1,45 @@ +# Use PrepareNode.ps1 from sig-windows-tools to set up Kubernetes binaries +param( + [string]$K8sVersion = "v1.33.0" +) + +$KUBE_BIN_DIR = "C:\k" + +# Ensure C:\k directory exists +if (!(Test-Path $KUBE_BIN_DIR)) { + New-Item -ItemType Directory -Path $KUBE_BIN_DIR -Force +} + +Write-Host "Downloading PrepareNode.ps1 from sig-windows-tools..." +curl.exe -L -o PrepareNode.ps1 https://raw.githubusercontent.com/kubernetes-sigs/sig-windows-tools/master/hostprocess/PrepareNode.ps1 + +if (!(Test-Path ".\PrepareNode.ps1")) { + Write-Error "Failed to download PrepareNode.ps1" + exit 1 +} + +Write-Host "Running PrepareNode.ps1 to install Kubernetes binaries (version: $K8sVersion)..." +.\PrepareNode.ps1 -KubernetesVersion $K8sVersion + +if ($LASTEXITCODE -ne 0) { + Write-Error "PrepareNode.ps1 failed with exit code $LASTEXITCODE" + exit $LASTEXITCODE +} + +Write-Host "Kubernetes binaries installed successfully" +Write-Host "Verifying installations..." +if (Test-Path "$KUBE_BIN_DIR\kubeadm.exe") { + Write-Host "Kubeadm version: $(& $KUBE_BIN_DIR\kubeadm.exe version)" +} else { + Write-Error "kubeadm.exe not found at $KUBE_BIN_DIR\kubeadm.exe" + exit 1 +} + +if (Test-Path "$KUBE_BIN_DIR\kubelet.exe") { + Write-Host "Kubelet version: $(& $KUBE_BIN_DIR\kubelet.exe --version)" +} else { + Write-Error "kubelet.exe not found at $KUBE_BIN_DIR\kubelet.exe" + exit 1 +} + + diff --git a/process/testing/aso/windows/join-cluster.ps1 b/process/testing/aso/windows/join-cluster.ps1 new file mode 100644 index 00000000000..4b94cf01c10 --- /dev/null +++ b/process/testing/aso/windows/join-cluster.ps1 @@ -0,0 +1,56 @@ +# Join Windows node to Kubernetes cluster +param( + [Parameter(Mandatory=$true)] + [string]$JoinArgs, + + [string]$WindowsEip = "" +) + +$KUBE_BIN_DIR = "C:\k" + +# Ensure PATH includes C:\k +$env:Path = [Environment]::GetEnvironmentVariable("Path", "Machine") + +Write-Host "Joining cluster..." +Write-Host "Using kubeadm at: $KUBE_BIN_DIR\kubeadm.exe" +Write-Host "Kubelet location: $KUBE_BIN_DIR\kubelet.exe" + +# Verify kubelet exists +if (!(Test-Path "$KUBE_BIN_DIR\kubelet.exe")) { + Write-Error "kubelet.exe not found at $KUBE_BIN_DIR\kubelet.exe" + Start-Sleep -Seconds 600 + exit 1 +} + +# Print the actual join command +Write-Host "" +Write-Host "==========================================" +Write-Host "Executing kubeadm join command:" +Write-Host "$KUBE_BIN_DIR\kubeadm.exe join $JoinArgs --cri-socket npipe:////./pipe/containerd-containerd" +Write-Host "==========================================" +Write-Host "" + +# Split JoinArgs into an array for proper argument passing +$argsArray = $JoinArgs -split '\s+' + +# Run kubeadm join with full path - using Invoke-Expression for proper argument expansion +$joinCommand = "& `"$KUBE_BIN_DIR\kubeadm.exe`" join $argsArray --cri-socket npipe:////./pipe/containerd-containerd" +Write-Host "Executing: $joinCommand" +Invoke-Expression $joinCommand + +# Check if join failed +if ($LASTEXITCODE -ne 0) { + Write-Error "Kubeadm join failed with exit code $LASTEXITCODE" + Write-Host "" + Write-Host "==========================================" + Write-Host "Join failed! Sleeping for 10 minutes for debugging..." + if ($WindowsEip) { + Write-Host "You can SSH to this node at: $WindowsEip" + } + Write-Host "==========================================" + Start-Sleep -Seconds 600 + exit $LASTEXITCODE +} + +Write-Host "Successfully joined the cluster!" + diff --git a/process/testing/winfv-cni-plugin/aso/utils.sh b/process/testing/util/utils.sh old mode 100755 new mode 100644 similarity index 52% rename from process/testing/winfv-cni-plugin/aso/utils.sh rename to process/testing/util/utils.sh index e496f969399..e8cb374d948 --- a/process/testing/winfv-cni-plugin/aso/utils.sh +++ b/process/testing/util/utils.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright (c) 2024 Tigera, Inc. All rights reserved. +# Copyright (c) 2025 Tigera, Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,6 +13,42 @@ # See the License for the specific language governing permissions and # limitations under the License. +# Common utility functions for testing scripts + +# Logging functions with timestamps and colors +function log_info() { + local timestamp=$(date '+%Y-%m-%d %H:%M:%S') + echo -e "\033[32m[INFO]\033[0m [$timestamp] $*" +} + +function log_warning() { + local timestamp=$(date '+%Y-%m-%d %H:%M:%S') + echo -e "\033[33m[WARNING]\033[0m [$timestamp] $*" >&2 +} + +function log_fail() { + local timestamp=$(date '+%Y-%m-%d %H:%M:%S') + echo -e "\033[31m[FAIL]\033[0m [$timestamp] $*" >&2 +} + +# Alias for backwards compatibility +function log_warn() { + log_warning "$@" +} + +function log_error() { + log_fail "$@" +} + +# Helper function to redirect output based on VERBOSE setting +function redirect_output() { + if [[ "${VERBOSE}" == "true" ]]; then + "$@" + else + "$@" > /dev/null 2>&1 + fi +} + unset -f retry_command function retry_command() { local RETRY=$(($1/10)) @@ -31,10 +67,9 @@ unset -f pause-for-debug function pause-for-debug() { # Stop for debug echo "Check for pause file..." - while [ -f /home/semaphore/pause-for-debug ]; + while [ -f "${HOME}/pause-for-debug" ]; do echo "#" sleep 30 done } - diff --git a/process/testing/win-connections/README.md b/process/testing/win-connections/README.md new file mode 100644 index 00000000000..bc813a6590b --- /dev/null +++ b/process/testing/win-connections/README.md @@ -0,0 +1,57 @@ +# Windows Connectivity Tests + +This directory contains test resources and scripts for validating network connectivity between Linux and Windows nodes in a Kubernetes cluster with Calico CNI. + +## Prerequisites + +- A Kubernetes cluster with both Linux and Windows nodes +- Calico CNI installed and configured for Windows (see `../aso/` for setup) +- `kubeconfig` file available at `../aso/kubeconfig` + +## Test Components + +| Component | Platform | Description | +|-----------|----------|-------------| +| `nginx` | Linux | Nginx web server pod and service | +| `porter` | Windows | Porter web server pod and service | +| `client` | Linux | Busybox pod for testing connectivity | + +## Usage + +```bash +./run-tests.sh +``` + +The script will: +1. Create the `demo` namespace +2. Deploy nginx, porter, and client pods +3. Wait for all pods to be ready (Windows pods may take up to 10 minutes) +4. Run connectivity tests: + - **Test 1**: Linux client pod → Windows porter service (via DNS) + - **Test 2**: Windows porter pod → Linux nginx service (via DNS) + +## Test Topology + +``` +┌─────────────────┐ ┌─────────────────┐ +│ Linux Node │ │ Windows Node │ +│ │ │ │ +│ ┌───────────┐ │ │ ┌───────────┐ │ +│ │ client │──┼────────►│ │ porter │ │ +│ └───────────┘ │ Test 1 │ └───────────┘ │ +│ │ │ │ │ +│ ┌───────────┐ │ │ │ │ +│ │ nginx │◄─┼─────────┼────────┘ │ +│ └───────────┘ │ Test 2 │ │ +└─────────────────┘ └─────────────────┘ +``` + +## Expected Output + +On success: +``` +All connectivity tests passed! +``` + +On failure, the script will output which test failed and exit with code 1. + diff --git a/process/testing/win-connections/client.yaml b/process/testing/win-connections/client.yaml new file mode 100644 index 00000000000..a1ca4b887ae --- /dev/null +++ b/process/testing/win-connections/client.yaml @@ -0,0 +1,38 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + app: client + name: client + namespace: demo +spec: + containers: + - args: + - /bin/sh + - -c + - sleep 360000 + image: busybox + imagePullPolicy: Always + name: client + nodeSelector: + kubernetes.io/os: linux + +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app: client-b + name: client-b + namespace: demo +spec: + containers: + - args: + - /bin/sh + - -c + - sleep 360000 + image: busybox + imagePullPolicy: Always + name: client-b + nodeSelector: + kubernetes.io/os: linux diff --git a/process/testing/win-connections/nginx.yaml b/process/testing/win-connections/nginx.yaml new file mode 100644 index 00000000000..588e44da1a7 --- /dev/null +++ b/process/testing/win-connections/nginx.yaml @@ -0,0 +1,64 @@ +apiVersion: v1 +kind: Pod +metadata: + name: nginx + namespace: demo + labels: + app: nginx +spec: + containers: + - name: nginx + image: nginx:1 + ports: + - containerPort: 80 + nodeSelector: + kubernetes.io/os: linux + +--- +apiVersion: v1 +kind: Pod +metadata: + name: nginx-b + namespace: demo + labels: + app: nginx-b +spec: + containers: + - name: nginx + image: nginx:1 + ports: + - containerPort: 80 + nodeSelector: + kubernetes.io/os: linux + +--- +apiVersion: v1 +kind: Service +metadata: + name: nginx + namespace: demo + labels: + app: nginx +spec: + type: NodePort + ports: + - port: 80 + targetPort: 80 + selector: + app: nginx + +--- +apiVersion: v1 +kind: Service +metadata: + name: nginx-b + namespace: demo + labels: + app: nginx-b +spec: + type: NodePort + ports: + - port: 80 + targetPort: 80 + selector: + app: nginx-b diff --git a/process/testing/win-connections/porter.yaml b/process/testing/win-connections/porter.yaml new file mode 100644 index 00000000000..3ecb7eb8008 --- /dev/null +++ b/process/testing/win-connections/porter.yaml @@ -0,0 +1,33 @@ +apiVersion: v1 +kind: Pod +metadata: + name: porter + namespace: demo + labels: + app: porter +spec: + containers: + - name: porter + image: quay.io/calico/porter:latest + ports: + - containerPort: 80 + env: + - name: SERVE_PORT_80 + value: foobar + nodeSelector: + kubernetes.io/os: windows + +--- +apiVersion: v1 +kind: Service +metadata: + name: porter + namespace: demo + labels: + app: porter +spec: + ports: + - port: 80 + targetPort: 80 + selector: + app: porter diff --git a/process/testing/win-connections/run-tests.sh b/process/testing/win-connections/run-tests.sh new file mode 100755 index 00000000000..39c0a4c73c9 --- /dev/null +++ b/process/testing/win-connections/run-tests.sh @@ -0,0 +1,89 @@ +#!/bin/bash +# Copyright (c) 2025 Tigera, Inc. All rights reserved. +# +# 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. + +set -o errexit +set -o nounset +set -o pipefail + +CURRENT_DIR=$(dirname "$0") + +# Use ${KUBECTL} from ../aso/bin/ +KUBECTL="${CURRENT_DIR}/../aso/bin/kubectl" + +# Check if kubeconfig exists at ../aso/kubeconfig +ASO_KUBECONFIG="${CURRENT_DIR}/../aso/kubeconfig" +if [ -f "${ASO_KUBECONFIG}" ]; then + echo "Using kubeconfig from: ${ASO_KUBECONFIG}" + export KUBECONFIG="${ASO_KUBECONFIG}" +elif [ -n "${1:-}" ]; then + echo "Using kubeconfig from argument: $1" + export KUBECONFIG="$1" +else + echo "ERROR: No kubeconfig found. Either provide as argument or ensure ../aso/kubeconfig exists." + exit 1 +fi + +echo "Creating namespace demo..." +${KUBECTL} create ns demo || true + +echo "Applying nginx, porter, and client deployments..." +${KUBECTL} apply -f ${CURRENT_DIR}/nginx.yaml +${KUBECTL} apply -f ${CURRENT_DIR}/porter.yaml +${KUBECTL} apply -f ${CURRENT_DIR}/client.yaml + +echo "Waiting for linux pods to be ready..." +${KUBECTL} wait pod -l app=nginx --for=condition=Ready -n demo --timeout=30s +${KUBECTL} wait pod -l app=client --for=condition=Ready -n demo --timeout=30s + +echo "Windows pods can take a while to become ready... wait for up to 10 minutes..." +${KUBECTL} wait pod -l app=porter --for=condition=Ready -n demo --timeout=600s + +echo "" +echo "==========================================" +echo "Running connectivity tests..." +echo "==========================================" + +CLIENT_POD="client" +PORTER_POD="porter" + +TESTS_FAILED=0 + +echo "" +echo "Test 1: Client pod (Linux) -> Porter service (Windows) (DNS: porter)" +echo "---------------------------------------------------------------------" +if ! ${KUBECTL} exec -n demo -t ${CLIENT_POD} -- wget -q -O - --timeout=10 http://porter:80; then + echo "FAILED: Client cannot reach Porter" + TESTS_FAILED=1 +fi + +echo "" +echo "Test 2: Porter pod (Windows) -> Nginx service (Linux) (DNS: nginx)" +echo "-------------------------------------------------------------------" +if ! ${KUBECTL} exec -n demo -t ${PORTER_POD} -- powershell -Command "Invoke-WebRequest -Uri http://nginx:80 -UseBasicParsing -TimeoutSec 10"; then + echo "FAILED: Porter cannot reach Nginx" + TESTS_FAILED=1 +fi + +echo "" +if [ ${TESTS_FAILED} -eq 1 ]; then + echo "==========================================" + echo "ERROR: One or more connectivity tests failed!" + echo "==========================================" + exit 1 +fi + +echo "==========================================" +echo "All connectivity tests passed!" +echo "==========================================" diff --git a/process/testing/winfv-cni-plugin/.gitignore b/process/testing/winfv-cni-plugin/.gitignore new file mode 100644 index 00000000000..77d7ae8555f --- /dev/null +++ b/process/testing/winfv-cni-plugin/.gitignore @@ -0,0 +1 @@ +windows/run-fv.ps1 diff --git a/process/testing/winfv-cni-plugin/aso/README.md b/process/testing/winfv-cni-plugin/aso/README.md deleted file mode 100644 index f6df506b842..00000000000 --- a/process/testing/winfv-cni-plugin/aso/README.md +++ /dev/null @@ -1,17 +0,0 @@ -## Windows FV infrastructure -This directory contains scripts and manifests to setup Windows cni-plugin FV infrastructure. - -### Prerequisite -azure cli has been installed. - -### Steps -1. Set environment variables in `export-env.sh`. - -2. Run `make run-fv`. - -### Access Linux or Windows nodes - -Helper scripts will be generated to ssh or scp into each node. See individual script for details. - -### Cleanup -Run `make dist-clean`. diff --git a/process/testing/winfv-cni-plugin/aso/config-minikube b/process/testing/winfv-cni-plugin/aso/config-minikube deleted file mode 100644 index 00336cdfb1c..00000000000 --- a/process/testing/winfv-cni-plugin/aso/config-minikube +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: v1 -clusters: -- cluster: - certificate-authority: c:\k\minikube\ca.crt - server: https://{{.Env.LINUX_PIP}}:{{.Env.APISERVER_PORT}} - name: minikube -contexts: -- context: - cluster: minikube - namespace: default - user: minikube - name: minikube -current-context: minikube -kind: Config -preferences: {} -users: -- name: minikube - user: - client-certificate: c:\k\minikube\client.crt - client-key: c:\k\minikube\client.key diff --git a/process/testing/winfv-cni-plugin/aso/export-env.sh b/process/testing/winfv-cni-plugin/aso/export-env.sh deleted file mode 100755 index bea5a72896d..00000000000 --- a/process/testing/winfv-cni-plugin/aso/export-env.sh +++ /dev/null @@ -1,33 +0,0 @@ -export SUFFIX="${SUFFIX:=${USER}-${BACKEND}}" - -export AZURE_LOCATION="${AZURE_LOCATION:="eastus2"}" -export AZURE_RESOURCE_GROUP="${AZURE_RESOURCE_GROUP:=rg-winfv-${SUFFIX}}" - -#export AZURE_WINDOWS_IMAGE_SKU="${AZURE_WINDOWS_IMAGE_SKU:="2022-datacenter-core-g2"}" -#export AZURE_WINDOWS_IMAGE_VERSION="${AZURE_WINDOWS_IMAGE_VERSION:="20348.2402.240405"}" - -export AZURE_WINDOWS_IMAGE_SKU="${AZURE_WINDOWS_IMAGE_SKU:="2019-datacenter-core-g2"}" -export AZURE_WINDOWS_IMAGE_VERSION="${AZURE_WINDOWS_IMAGE_VERSION:="17763.5696.240406"}" - -export LINUX_NODE_COUNT="${LINUX_NODE_COUNT:=1}" -export WINDOWS_NODE_COUNT="${WINDOWS_NODE_COUNT:=1}" - - -# Get K8S_VERSION variable from metadata.mk, error out if it cannot be found -SCRIPT_CURRENT_DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" -METADATAMK=${SCRIPT_CURRENT_DIR}/../../../../metadata.mk -if [ -f ${METADATAMK} ]; then - K8S_VERSION_METADATA=$(grep K8S_VERSION ${METADATAMK} | cut -d "=" -f 2) - if [[ ! ${K8S_VERSION_METADATA} =~ ^v?[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "Failed to retrieve K8S_VERSION from ${METADATAMK}" - exit 1 - fi -else - echo "Failed to open ${METADATAMK}" - exit 1 -fi -export KUBE_VERSION="${KUBE_VERSION:=${K8S_VERSION_METADATA#v}}" - -export CONTAINERD_VERSION="${CONTAINERD_VERSION:="1.6.35"}" - -export SSH_KEY_FILE="$PWD/.sshkey" diff --git a/process/testing/winfv-cni-plugin/aso/infra/templates/vmss-linux.yaml b/process/testing/winfv-cni-plugin/aso/infra/templates/vmss-linux.yaml deleted file mode 100644 index ecef6047cee..00000000000 --- a/process/testing/winfv-cni-plugin/aso/infra/templates/vmss-linux.yaml +++ /dev/null @@ -1,62 +0,0 @@ -apiVersion: compute.azure.com/v1api20220301 -kind: VirtualMachineScaleSet -metadata: - name: vmss-linux - namespace: winfv -spec: - location: {{.Env.AZURE_LOCATION}} - owner: - name: {{.Env.AZURE_RESOURCE_GROUP}} - platformFaultDomainCount: 1 - singlePlacementGroup: false - sku: - capacity: {{.Env.LINUX_NODE_COUNT}} - name: Standard_D4s_v3 - upgradePolicy: - mode: Automatic - virtualMachineProfile: - extensionProfile: - extensions: - - name: mycustomextension - publisher: Microsoft.Azure.Extensions - settings: - commandToExecute: /bin/bash -c "echo hello winfv" - type: CustomScript - typeHandlerVersion: "2.0" - - name: install-docker - publisher: "Microsoft.Azure.Extensions" - type: "DockerExtension" - typeHandlerVersion: "1.0" - autoUpgradeMinorVersion: true - networkProfile: - networkInterfaceConfigurations: - - ipConfigurations: - - name: myipconfiguration - subnet: - reference: - group: network.azure.com - kind: VirtualNetworksSubnet - name: subnet-winfv - publicIPAddressConfiguration: - idleTimeoutInMinutes: 30 - name: winfv - name: mynicconfig - primary: true - osProfile: - computerNamePrefix: winfv - adminUsername: winfv - adminPassword: - key: password - name: winfv-secret-windows - linuxConfiguration: - disablePasswordAuthentication: true - ssh: - publicKeys: - - keyData: {{.Env.PUBLIC_KEY}} - path: /home/winfv/.ssh/authorized_keys - storageProfile: - imageReference: - publisher: Canonical - offer: 0001-com-ubuntu-server-jammy - sku: 22_04-lts - version: latest diff --git a/process/testing/winfv-cni-plugin/aso/infra/templates/vmss-windows.yaml b/process/testing/winfv-cni-plugin/aso/infra/templates/vmss-windows.yaml deleted file mode 100644 index 4221c1680b1..00000000000 --- a/process/testing/winfv-cni-plugin/aso/infra/templates/vmss-windows.yaml +++ /dev/null @@ -1,55 +0,0 @@ -apiVersion: compute.azure.com/v1api20220301 -kind: VirtualMachineScaleSet -metadata: - name: vmss-windows - namespace: winfv -spec: - location: {{.Env.AZURE_LOCATION}} - owner: - name: {{.Env.AZURE_RESOURCE_GROUP}} - platformFaultDomainCount: 1 - singlePlacementGroup: false - sku: - capacity: {{.Env.WINDOWS_NODE_COUNT}} - name: Standard_D4s_v3 - upgradePolicy: - mode: Automatic - virtualMachineProfile: - extensionProfile: - extensions: - - name: WindowsOpenSSH - publisher: Microsoft.Azure.OpenSSH - type: WindowsOpenSSH - typeHandlerVersion: "3.0" - - name: mycustomextension - publisher: Microsoft.Compute - settings: - commandToExecute: powershell -command "echo \"{{.Env.PUBLIC_KEY}}\" | Add-Content 'C:\ProgramData\ssh\administrators_authorized_keys' -Encoding UTF8;icacls.exe 'C:\ProgramData\ssh\administrators_authorized_keys' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'" - type: CustomScriptExtension - typeHandlerVersion: "1.9" - networkProfile: - networkInterfaceConfigurations: - - ipConfigurations: - - name: myipconfiguration - subnet: - reference: - group: network.azure.com - kind: VirtualNetworksSubnet - name: subnet-winfv - publicIPAddressConfiguration: - idleTimeoutInMinutes: 30 - name: winfv - name: mynicconfig - primary: true - osProfile: - computerNamePrefix: winfv - adminUsername: winfv - adminPassword: - key: password - name: winfv-secret-windows - storageProfile: - imageReference: - publisher: MicrosoftWindowsServer - offer: WindowsServer - sku: {{.Env.AZURE_WINDOWS_IMAGE_SKU}} - version: {{.Env.AZURE_WINDOWS_IMAGE_VERSION}} diff --git a/process/testing/winfv-cni-plugin/aso/install-aso.sh b/process/testing/winfv-cni-plugin/aso/install-aso.sh deleted file mode 100755 index cb4ed2b6ea6..00000000000 --- a/process/testing/winfv-cni-plugin/aso/install-aso.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/bin/bash -# Copyright (c) 2024 Tigera, Inc. All rights reserved. -# -# 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. - -set -o errexit -set -o nounset -set -o pipefail - -. ./utils.sh - -# Verify the required Environment Variables are present. -: "${AZURE_SUBSCRIPTION_ID:?Environment variable empty or not defined.}" -: "${AZURE_TENANT_ID:?Environment variable empty or not defined.}" -: "${AZURE_CLIENT_ID:?Environment variable empty or not defined.}" -: "${AZURE_CLIENT_SECRET:?Environment variable empty or not defined.}" - -CRD_PATTERN="resources.azure.com/*;containerservice.azure.com/*;compute.azure.com/*;network.azure.com/*" -SUFFIX="" - -# Utilities -: ${KIND:=./bin/kind} -: ${KUBECTL:=./bin/kubectl} -: ${CMCTL:=./bin/cmctl} -: ${ASOCTL:=./bin/asoctl} - -# Create management cluster -${KIND} create cluster --image kindest/node:${KUBE_VERSION} --name kind${SUFFIX} -${KUBECTL} wait node kind${SUFFIX}-control-plane --for=condition=ready --timeout=90s - -# Install cert-manager -echo; echo "Wait for cert manager to be installed ..." -${KUBECTL} apply -f https://github.com/jetstack/cert-manager/releases/download/v1.14.1/cert-manager.yaml -${CMCTL} check api --wait=2m - -echo; echo "Installing ASO ..." - -# We are not able to call asoctl in semaphore VM with ubuntu 20.04. -# bin/asoctl: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32' not found (required by bin/asoctl) -# The workaround is to have the manifests generated in advance. -file="asoctl-generated-manifests-v2.6.0.yaml" -# Check if the file exists in the current directory -if [ -f "$file" ]; then - echo "The file '$file' exists in the current directory." - ${KUBECTL} apply -f $file -else - # Install ASO https://azure.github.io/azure-service-operator/guide/installing-from-yaml/ - ${ASOCTL} export template --version v2.6.0 --crd-pattern "${CRD_PATTERN}" | ${KUBECTL} apply -f - -fi - - -# Create a secret to include the password of the Service Principal identity created in Azure -echo; echo "Creating secret with azure credentials..." -cat < password.txt --------------Connect to Windows Instances------------- -username: winfv -password: $PASSWORD -password-base64: $PASSWORD_BASE64 -EOF - - rm ${SSH_KEY_FILE} || true - ssh-keygen -m PEM -t rsa -b 2048 -f "${SSH_KEY_FILE}" -N '' -C "" 1>/dev/null - echo "Machine SSH key generated in ${SSH_KEY_FILE}" - export PUBLIC_KEY=$(cat ${SSH_KEY_FILE}.pub) - - rm -rf infra/manifests || true - ${GOMPLATE} --input-dir infra/templates --output-dir infra/manifests - - ${KUBECTL} apply -f infra/manifests/resource-group.yaml - ${KUBECTL} apply -f infra/manifests/password.yaml - ${KUBECTL} apply -f infra/manifests/vnet.yaml - ${KUBECTL} apply -f infra/manifests/security-group.yaml - ${KUBECTL} apply -f infra/manifests/vmss-linux.yaml - ${KUBECTL} apply -f infra/manifests/vmss-windows.yaml -} - -function delete_azure_crds() { - ${KUBECTL} delete ns winfv -} - -function show_connections() { - # Wait for vmss deployments - echo; echo "show_connections started..." - echo "Wait for vmss-linux to be ready ..." - ${KUBECTL} wait --for=condition=Ready --timeout=8m -n winfv virtualmachinescalesets vmss-linux - LINUX_INSTANCE_ID=$(az vmss list-instances --name vmss-linux --resource-group $AZURE_RESOURCE_GROUP --query "[0].instanceId" | sed 's/"//g') - LINUX_EIP=$(az vmss list-instance-public-ips --name vmss-linux --resource-group $AZURE_RESOURCE_GROUP --query "[0].ipAddress" | sed 's/"//g') - LINUX_PIP=$(az vmss nic list-vm-nics --vmss-name vmss-linux --resource-group $AZURE_RESOURCE_GROUP --instance-id $LINUX_INSTANCE_ID --query "[0].ipConfigurations[0].privateIPAddress" | sed 's/"//g') - echo "vmss-linux is ready. PIP:$LINUX_PIP, EIP:$LINUX_EIP" - - echo "Wait for vmss-windows to be ready ..." - ${KUBECTL} wait --for=condition=Ready --timeout=8m -n winfv virtualmachinescalesets vmss-windows - WINDOWS_INSTANCE_ID=$(az vmss list-instances --name vmss-windows --resource-group $AZURE_RESOURCE_GROUP --query "[0].instanceId" | sed 's/"//g') - WINDOWS_EIP=$(az vmss list-instance-public-ips --name vmss-windows --resource-group $AZURE_RESOURCE_GROUP --query "[0].ipAddress" | sed 's/"//g') - WINDOWS_PIP=$(az vmss nic list-vm-nics --vmss-name vmss-windows --resource-group $AZURE_RESOURCE_GROUP --instance-id $WINDOWS_INSTANCE_ID --query "[0].ipConfigurations[0].privateIPAddress" | sed 's/"//g') - echo "vmss-windows is ready. PIP:$WINDOWS_PIP, EIP:$WINDOWS_EIP" - - # Setup connection info - MASTER_CONNECT_COMMAND="ssh -i ${SSH_KEY_FILE} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no winfv@${LINUX_EIP}" - WINDOWS_CONNECT_COMMAND="ssh -i ${SSH_KEY_FILE} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no winfv@${WINDOWS_EIP} powershell" - - WIN_PASSWORD=$(grep "password:" ./password.txt | awk -F':' '{print $2}') - - cat << EOF > connect.txt --------------Connect to Linux Master Instances-------- -${MASTER_CONNECT_COMMAND} - --------------Connect to Windows Instances------------- -RDP://${WINDOWS_EIP} user: winfv password:$WIN_PASSWORD -${WINDOWS_CONNECT_COMMAND} -EOF - - export LINUX_EIP LINUX_PIP WINDOWS_EIP WINDOWS_PIP MASTER_CONNECT_COMMAND WINDOWS_CONNECT_COMMAND CONTAINERD_VERSION - - echo - echo "Generating helper files" - echo '${MASTER_CONNECT_COMMAND} "$@"' > ./ssh-node-linux.sh - chmod +x ./ssh-node-linux.sh - - cat << EOF > ssh-node-windows.sh -#usage: ./ssh-node-windows.sh "Restart-Computer -force" -${WINDOWS_CONNECT_COMMAND} \$1 -EOF - chmod +x ./ssh-node-windows.sh - - cat << EOF > scp-to-windows.sh -#---------Copy files to windows-------- -#usage: ./scp-to-windows.sh kubeconfig c:\\\\k\\\\kubeconfig -#usage: ./scp-to-windows.sh images/ebpf-for-windows-c-temp.zip 'c:\\' -scp -i ${SSH_KEY_FILE} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \$1 winfv@${WINDOWS_EIP}:\$2 -EOF -chmod +x ./scp-to-windows.sh - - - cat << EOF > scp-from-windows.sh -#---------Copy files from windows-------- -#usage: ./scp-from-windows.sh c:\\k\\calico.log ./calico.log -scp -i ${SSH_KEY_FILE} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no winfv@${WINDOWS_EIP}:\$1 \$2 -EOF -chmod +x ./scp-from-windows.sh - - pause-for-debug - echo "show_connections done."; echo -} - -function retry-ssh() { - local SSH_CMD="$@" - local RETRY_INTERVAL=30 # Seconds between retries - local MAX_DURATION=300 - - # Tracking time - START_TIME=$(date +%s) - - while true; do - echo "Attempting $SSH_CMD..." - if $SSH_CMD; then - echo "SSH command succeeded." - return 0 - else - echo "SSH command failed. Running show_connections and retrying in $RETRY_INTERVAL seconds..." - show_connections # Replace with your actual command or function - fi - - sleep $RETRY_INTERVAL - - CURRENT_TIME=$(date +%s) - ELAPSED_TIME=$((CURRENT_TIME - START_TIME)) - - if [ $ELAPSED_TIME -ge $MAX_DURATION ]; then - echo "Timeout reached after $((MAX_DURATION / 60)) minutes. Giving up." - exit 1 - fi - done -} - -# Azure may assign a different public IP to a VM even after it is marked as ready. -# This function attempts to SSH into the VM multiple times to ensure it is accessible. -function confirm-nodes-ssh() { - echo;echo "confirm nodes can be accessed by ssh..." - show_connections - retry-ssh ./ssh-node-linux.sh echo - retry-ssh ./ssh-node-windows.sh "-Help" - - # Azure may assign another public IP to the VM. - # So even the first batch of SSHes works, the ip could be updated later. - # Sleep and retry. - echo "sleep 30 seconds..." - sleep 30 - show_connections - retry-ssh ./ssh-node-linux.sh echo - retry-ssh ./ssh-node-windows.sh "-Help" - echo "VMs can be accessed by ssh.";echo -} - -function parse_options() { - usage() { - cat < /dev/null && \ +for log_file in /home/semaphore/fv.log/*.log; do + prefix="[$(basename ${log_file})]" + cat ${log_file} | iconv -f UTF-16 -t UTF-8 | sed 's/\r$//g' | grep --line-buffered --perl ${log_regexps} -B 2 -A 15 | sed 's/.*/'"${prefix}"' &/g' +done; + +# Search for the file indicates that the Windows node has completed the FV process +if [ ! -f ./report/done-marker ]; +then + echo "Windows node failed to complete the FV process." + exit 1 +fi + +popd +echo "Windows CNI-Plugin FV test completed." diff --git a/process/testing/winfv-cni-plugin/setup-fv.sh b/process/testing/winfv-cni-plugin/setup-fv.sh new file mode 100755 index 00000000000..7e27e1ffd42 --- /dev/null +++ b/process/testing/winfv-cni-plugin/setup-fv.sh @@ -0,0 +1,110 @@ +#!/bin/bash +# Copyright (c) 2024-2025 Tigera, Inc. All rights reserved. +# +# 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. + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${SCRIPT_DIR}" + +: ${ASO_DIR:=${SCRIPT_DIR}/../aso} +: ${UTILS_DIR:=${SCRIPT_DIR}/../util} + +. ${UTILS_DIR}/utils.sh +. ${ASO_DIR}/export-env.sh +. ${ASO_DIR}/vmss.sh info + +: ${KUBECTL:=${ASO_DIR}/bin/kubectl} +: ${GOMPLATE:=${ASO_DIR}/bin/gomplate} +: ${BACKEND:?Error: BACKEND is not set} + +: "${CALICO_HOME:=${SCRIPT_DIR}/../../..}" + +# Kubeadm API server runs on port 6443 +export APISERVER_PORT=6443 + +function setup_etcd() { + echo "Setting up etcd server on Linux node..." + + # Remove any existing etcd container and start fresh + ${MASTER_CONNECT_COMMAND} "sudo ctr -n k8s.io task rm -f calico-etcd" || true + ${MASTER_CONNECT_COMMAND} "sudo ctr -n k8s.io container delete calico-etcd" || true + + # Start etcd container + ETCD_CONTAINER="quay.io/coreos/etcd:v3.4.6" + ${MASTER_CONNECT_COMMAND} "sudo ctr -n k8s.io images pull ${ETCD_CONTAINER}" + ${MASTER_CONNECT_COMMAND} "sudo ctr -n k8s.io run --detach --net-host ${ETCD_CONTAINER} calico-etcd etcd --advertise-client-urls 'http://${LINUX_PIP}:2389,http://127.0.0.1:2389,http://${LINUX_PIP}:8001,http://127.0.0.1:8001' --listen-client-urls 'http://0.0.0.0:2389,http://0.0.0.0:8001'" + + echo "Waiting for etcd to be ready..." + sleep 5 + ${MASTER_CONNECT_COMMAND} "sudo ctr -n k8s.io containers list" + ${MASTER_CONNECT_COMMAND} "sudo ctr -n k8s.io tasks list" + + echo "etcd server is running at ${LINUX_PIP}:2389" +} + +function create_l2bridge_network() { + # Create external network will cause ssh session to hang. + # Use timeout and ignore error state to make sure the script will + # keep running. + set +e + timeout -s SIGTERM 20s ${WINDOWS_CONNECT_COMMAND} "c:\\k\\create-network.ps1 -Backend l2bridge" + set -e +} + +function create_overlay_network() { + set +e + timeout -s SIGTERM 20s ${WINDOWS_CONNECT_COMMAND} "c:\\k\\create-network.ps1 -Backend overlay" + set -e +} + +function run_fv_l2bridge() { + ${WINDOWS_CONNECT_COMMAND} "c:\\k\\run-fv.ps1 -Backend l2bridge" + echo +} + +function run_fv_overlay() { + ${WINDOWS_CONNECT_COMMAND} "c:\\k\\run-fv.ps1 -Backend overlay" + echo +} + +function copy_run_fv_script_to_windows() { + # Copy the run-fv script to Windows node + mkdir -p ./windows + ${GOMPLATE} --file ./run-fv-cni-plugin.ps1 --out ./windows/run-fv.ps1 + + # Copy run-fv.ps1 to Windows node using ASO helper + ${ASO_DIR}/scp-to-windows.sh 0 ./windows/run-fv.ps1 'c:\k\run-fv.ps1' + echo "Copied run-fv.ps1 to Windows node" + + make -C "${CALICO_HOME}/cni-plugin" bin/windows/win-fv.exe + + ${ASO_DIR}/scp-to-windows.sh 0 "${CALICO_HOME}/cni-plugin/bin/windows/win-fv.exe" 'c:\k\win-fv.exe' + echo "Copied win-fv.exe to Windows node" +} + +# Main execution +setup_etcd +copy_run_fv_script_to_windows + +if [[ "$BACKEND" == "overlay" ]]; then + create_overlay_network + run_fv_overlay +elif [[ "$BACKEND" = "l2bridge" ]]; then + create_l2bridge_network + run_fv_l2bridge +else + echo "Invalid network backend paramenter $BACKEND provided" + exit 1 +fi diff --git a/process/testing/winfv-felix/.gitignore b/process/testing/winfv-felix/.gitignore index b989bc01eb9..1506225c24f 100644 --- a/process/testing/winfv-felix/.gitignore +++ b/process/testing/winfv-felix/.gitignore @@ -1,2 +1,2 @@ -run-cni-fv.ps1 -run-felix-fv.ps1 +pod-logs/ +windows/run-fv.ps1 diff --git a/process/testing/winfv-felix/README.md b/process/testing/winfv-felix/README.md deleted file mode 100644 index e69521c403f..00000000000 --- a/process/testing/winfv-felix/README.md +++ /dev/null @@ -1,32 +0,0 @@ -FV framework for windows on aws. - - -From Jenkins job -- Jenkins jobs check out source repo and build windows binaries. -- Jenkins jobs check out process repo. -- Jenkins jobs copy over run-fv.ps1 from source repo for setup-fv.sh. -- Jenkins jobs setup parameters, copy over keys and docker credentials for setup-fv.sh. -- Call setup-fv.sh script. - -From setup-fv.sh -- Setup VPC resources. -- Create and bootstrap linux node (install docker) and windows node (setup etcd-endpoints, install pscp.exe and putty key to transfer files between windows and linux nodes). -- Copy over run-fv.ps1 to linux node, add a line to copy back report from windows node to linux node. -- Copy over docker credentials and log in to gcr.io. Start etcd, kube-apiserver, kube-controller-manager on linux nodes. -- Setup wait-report.sh on linux node. -- Windows node wait until it found a file named `file-ready` on linux node. - -From Jenkins Jobs -- Copy over windows binaries and run-fv.ps1 script to linux node. -- Touch a file `file-ready` which tells windows nodes to copy over windows binaries and run-fv.ps1. -- Call linux wait-report.sh to wait for report.xml file. - -From setup-fv.sh -- Windows detected the presence of file-ready on linux node and copy over binaries and run-fv.ps1. -- Windows node start to run run-fv.ps1. -- The last line of run-fv.ps1 will copy over report.xml to linux node. - -From Jenkins Jobs -- Wait-report.sh exit once report.xml is present on linux node. -- Copy back report.xml from linux to wavetank slave. -- Check report.xml for test result. diff --git a/process/testing/winfv-felix/calico-node-windows.yaml b/process/testing/winfv-felix/calico-node-windows.yaml index f9e49f74e78..bbb8b27ecf5 100644 --- a/process/testing/winfv-felix/calico-node-windows.yaml +++ b/process/testing/winfv-felix/calico-node-windows.yaml @@ -5,6 +5,8 @@ spec: - args: - $env:CONTAINER_SANDBOX_MOUNT_POINT/CalicoWindows/felix-service.ps1 env: + - name: CNI_PLUGIN_TYPE + value: Calico - name: FELIX_HEALTHENABLED value: "true" image: docker.io/library/node-windows:latest @@ -13,6 +15,8 @@ spec: - args: - $env:CONTAINER_SANDBOX_MOUNT_POINT/CalicoWindows/node-service.ps1 env: + - name: CNI_PLUGIN_TYPE + value: Calico - name: FELIX_HEALTHENABLED value: "true" image: docker.io/library/node-windows:latest @@ -27,6 +31,8 @@ spec: - command: - $env:CONTAINER_SANDBOX_MOUNT_POINT/opt/cni/bin/install.exe env: + - name: CNI_PLUGIN_TYPE + value: Calico - name: CNI_BIN_DIR value: /host/opt/cni/bin - name: CNI_CONF_NAME diff --git a/process/testing/winfv-felix/capz/.gitignore b/process/testing/winfv-felix/capz/.gitignore deleted file mode 100644 index fb2f0c7890d..00000000000 --- a/process/testing/winfv-felix/capz/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -bin/* -*.log -.cluster_created -.calico_installed -.sshkey -.sshkey.pub -kubeconfig -scp-from-node.sh -scp-to-node.sh -ssh-node.sh -win-capz.yaml -tigera-operator.yaml -tigera-prometheus-operator.yaml diff --git a/process/testing/winfv-felix/capz/Makefile b/process/testing/winfv-felix/capz/Makefile deleted file mode 100644 index b368b95172e..00000000000 --- a/process/testing/winfv-felix/capz/Makefile +++ /dev/null @@ -1,90 +0,0 @@ -BINDIR?=bin -ARCH?=amd64 - -############################################################################### -# Cluster management -############################################################################### -CLUSTER_CREATED_MARKER:=.cluster_created - -.PHONY: create-cluster -create-cluster: $(CLUSTER_CREATED_MARKER) - -$(CLUSTER_CREATED_MARKER): $(BINDIR)/kind $(BINDIR)/kubectl $(BINDIR)/clusterctl $(BINDIR)/yq - @echo "Creating cluster $(CLUSTER_NAME_CAPZ) ..." - ./create-cluster.sh - $(MAKE) generate-helpers - ./bootstrap-cluster-ips.sh - ./replace-win-containerd.sh - touch $@ - -.PHONY: delete-cluster -delete-cluster: $(BINDIR)/kind $(BINDIR)/kubectl - @echo "Azure resources for cluster $(CLUSTER_NAME_CAPZ) will now be deleted, this can take up to 20 minutes" - -$(BINDIR)/kubectl delete cluster $(CLUSTER_NAME_CAPZ) - -$(BINDIR)/kind delete cluster --name kind${SUFFIX} - -az group delete --name $(CI_RG) -y - -rm -f kubeconfig - -rm -f win-capz.yaml - -rm -f tigera-operator.yaml - -rm -f tigera-prometheus-operator.yaml - -rm -f $(HELPERS) - -rm -f $(CLUSTER_CREATED_MARKER) $(CALICO_INSTALLED_MARKER) - -CALICO_INSTALLED_MARKER:=.calico_installed - -.PHONY: install-calico -install-calico: $(CALICO_INSTALLED_MARKER) - -$(CALICO_INSTALLED_MARKER): $(CLUSTER_CREATED_MARKER) $(BINDIR)/kubectl - ./install-calico.sh - touch $@ - -############################################################################### -# Utilities management -############################################################################### -HELPERS = scp-to-node.sh ssh-node.sh scp-from-node.sh -$(HELPERS): generate-helpers - -.PHONY: generate-helpers -generate-helpers: .sshkey .sshkey.pub - ./generate-helpers.sh - -$(BINDIR)/kind: - mkdir -p $(@D) - curl -sSf -L --retry 5 https://kind.sigs.k8s.io/dl/$(KIND_VERSION)/kind-linux-$(ARCH) -o $@ - chmod +x $@ - touch $@ - -$(BINDIR)/kubectl: - mkdir -p $(@D) - curl -sSf -L --retry 5 https://dl.k8s.io/release/$(KUBE_VERSION)/bin/linux/$(ARCH)/kubectl -o $@ - chmod +x $@ - touch $@ - -$(BINDIR)/clusterctl: - mkdir -p $(@D) - curl -sSf -L --retry 5 https://github.com/kubernetes-sigs/cluster-api/releases/download/$(CLUSTER_API_VERSION)/clusterctl-linux-$(ARCH) -o $(BINDIR)/clusterctl - chmod +x $@ - touch $@ - $(BINDIR)/clusterctl version - -$(BINDIR)/yq: - mkdir -p $(@D) - curl -sSf -L --retry 5 https://github.com/mikefarah/yq/releases/download/$(YQ_VERSION)/yq_linux_$(ARCH) -o $(BINDIR)/yq - chmod +x $@ - touch $@ - -.PHONY: clean -clean: - -rm -f kubeconfig - -rm -f win-capz.yaml - -rm -f tigera-operator.yaml - -rm -f .sshkey .sshkey.pub - -rm -f $(HELPERS) - -rm -f az-output.log - -.PHONY: dist-clean -dist-clean: clean - -rm -rf $(BINDIR) - -rm -f $(CLUSTER_CREATED_MARKER) $(CALICO_INSTALLED_MARKER) - -rm -f *.log diff --git a/process/testing/winfv-felix/capz/README.md b/process/testing/winfv-felix/capz/README.md deleted file mode 100644 index 91787efd7eb..00000000000 --- a/process/testing/winfv-felix/capz/README.md +++ /dev/null @@ -1,62 +0,0 @@ -## Windows FV infrastructure -This directory contains scripts and manifests to setup Windows FV infrastructure. - -### Steps -1. Export Environment variables. See example below (or `export-env.sh`): -``` -export CLUSTER_NAME_CAPZ="my-win-capz-cluster" -export AZURE_LOCATION="westcentralus" - -export AZURE_CONTROL_PLANE_MACHINE_TYPE="Standard_D2s_v3" -export AZURE_NODE_MACHINE_TYPE="Standard_D2s_v3" - -export KUBE_VERSION="v1.30.4" -export CLUSTER_API_VERSION="v1.8.1" -export CAPI_KUBEADM_VERSION="v1.8.5" -export AZURE_PROVIDER_VERSION="v1.10.4" -export KIND_VERSION="v0.24.0" - -# run "az ad sp list --spn your-client-id" to get information. -export AZURE_SUBSCRIPTION_ID="" - -# Create an Azure Service Principal and paste the output here -export AZURE_TENANT_ID="" -export AZURE_CLIENT_ID="" -export AZURE_CLIENT_SECRET="" -``` - -When running Calico Enterprise, larger VMs are necessary, so use for example: -``` -export AZURE_NODE_MACHINE_TYPE="Standard_D4s_v3" -``` - -2. Create an azure cluster with 2 Linux nodes and 2 Windows nodes. -``` -make create-cluster -``` - -3. Install Calico -``` -make install-calico -``` - -Optionally, define `PRODUCT`, `RELEASE_STREAM` and/or `HASH_RELEASE`: -``` -make install-calico PRODUCT=calient RELEASE_STREAM=master HASH_RELEASE=true -``` - -(Use `RELEASE_STREAM=local` to use local manifests from the monorepo instead of pulling them) - -To access your cluster, run `kubectl --kubeconfig=./kubeconfig ...` - -### Access Linux or Windows nodes -``` -make generate-helpers -``` -Helper scripts which can be used to ssh or scp into each node are generated. See the individual scripts for details. - -### Cleanup -``` -make delete-cluster -make clean -``` diff --git a/process/testing/winfv-felix/capz/bootstrap-cluster-ips.sh b/process/testing/winfv-felix/capz/bootstrap-cluster-ips.sh deleted file mode 100755 index f49a6976459..00000000000 --- a/process/testing/winfv-felix/capz/bootstrap-cluster-ips.sh +++ /dev/null @@ -1,97 +0,0 @@ -#!/bin/bash -# Copyright (c) 2024 Tigera, Inc. All rights reserved. -# -# 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. - -set -o errexit -set -o nounset -set -o pipefail - -set -e - -: ${KUBECTL:=./bin/kubectl} -: ${KCAPZ:="${KUBECTL} --kubeconfig=./kubeconfig"} -: "${AZURE_RESOURCE_GROUP:?Environment variable empty or not defined.}" - -CAPZ_CONTROL_PLANE_IP=$(az vm list-ip-addresses -g "$AZURE_RESOURCE_GROUP" | jq -r .[0].virtualMachine.network.privateIpAddresses[0]) -echo; echo "Control Plane IP:" "$CAPZ_CONTROL_PLANE_IP" - -vmss_length=$(az vmss list -g "$AZURE_RESOURCE_GROUP" | jq 'length') -LINUX_NAMES="" -WINDOWS_NAMES="" -i=0 -while [[ $i -lt $vmss_length ]]; do - vm=$(az vmss list -g "$AZURE_RESOURCE_GROUP" | jq ".[$i] | .name, .virtualMachineProfile.osProfile.linuxConfiguration.provisionVMAgent, .virtualMachineProfile.osProfile.windowsConfiguration.provisionVMAgent") - name=$(echo $vm | cut -d" " -f1) - is_linux=$(echo $vm | cut -d" " -f2) - if [[ $is_linux == "true" ]]; then - LINUX_NAMES="$LINUX_NAMES $(echo "$name" | tr -d '"')" - fi - is_windows=$(echo $vm | cut -d" " -f3) - if [[ $is_windows == "true" ]]; then - WINDOWS_NAMES="$WINDOWS_NAMES $(echo "$name" | tr -d '"')" - fi - i=$((i+1)) -done - -CAPZ_LINUX_IPS="" -for name in $LINUX_NAMES; do - ip=$(az vmss nic list -g "$AZURE_RESOURCE_GROUP" --vmss-name "$name" | jq -r .[].ipConfigurations[].privateIPAddress) - CAPZ_LINUX_IPS="$CAPZ_LINUX_IPS $ip" -done - -echo; echo "Linux node IPs:" "$CAPZ_LINUX_IPS" - -CAPZ_WINDOWS_IPS="" -for name in $WINDOWS_NAMES; do - ip=$(az vmss nic list -g "$AZURE_RESOURCE_GROUP" --vmss-name "$name" | jq -r .[].ipConfigurations[].privateIPAddress) - CAPZ_WINDOWS_IPS="$CAPZ_WINDOWS_IPS $ip" -done - -echo; echo "Windows node IPs:" "$CAPZ_WINDOWS_IPS" - -if [ ! -f ./ssh-node.sh ]; then - echo "'./ssh-node.sh' helper not found" - exit 1 -fi - -CAPZ_K8S_VERSION_MAJOR=$(${KCAPZ} version -o json | jq -r ".serverVersion.major") -CAPZ_K8S_VERSION_MINOR=$(${KCAPZ} version -o json | jq -r ".serverVersion.minor") - -if [ "$CAPZ_K8S_VERSION_MAJOR" -eq 1 ] && [ "$CAPZ_K8S_VERSION_MINOR" -ge 29 ]; then - echo "In kubernetes v1.29+, kubelet no longer sets up node IPs when using external cloud-provider." - echo "See https://github.com/kubernetes/kubernetes/issues/120720 for more information." -else - echo "Kubernetes version is lower than v1.29, no need for bootstrapping node IPs, exiting" - exit 0 -fi - -echo; echo "Set up node IPs in kubelet config" - -for linux_node_ip in $CAPZ_CONTROL_PLANE_IP $CAPZ_LINUX_IPS; do - ./ssh-node.sh "$linux_node_ip" "cat /etc/default/kubelet" - ./ssh-node.sh "$linux_node_ip" "sudo sh -c 'sed -i -e \"s/\$/ --node-ip=$linux_node_ip/\" /etc/default/kubelet; systemctl restart kubelet'" - ./ssh-node.sh "$linux_node_ip" "cat /etc/default/kubelet" -done - -for windows_node_ip in $CAPZ_WINDOWS_IPS; do - ./ssh-node.sh "$windows_node_ip" "\$file = (get-content C:/k/StartKubelet.ps1); echo \$file" - if [ "$WINDOWS_SERVER_VERSION" == "windows-2022" ]; then - ./ssh-node.sh "$windows_node_ip" "\$regex = '^\\\$kubeletCommandLine = .*'; \$line = ((get-content C:/k/StartKubelet.ps1) | select-string \$regex); (get-content C:/k/StartKubelet.ps1) -replace \$regex, (\$line.ToString() + ' + \\\" --node-ip=$windows_node_ip\\\"') | set-content c:/k/StartKubelet.ps1; restart-service kubelet" - else #"$WINDOWS_SERVER_VERSION" == "windows-2019" - ./ssh-node.sh "$windows_node_ip" "\$regex = '^\\\$kubeletCommandLine = .*'; \$line = ((get-content C:/k/StartKubelet.ps1) | select-string \$regex); (get-content C:/k/StartKubelet.ps1) -replace \$regex, (\$line.ToString() + ' + \\\"\\\"\\\" --node-ip=$windows_node_ip\\\"\\\"\\\"') | set-content -path c:/k/StartKubelet.ps1; restart-service kubelet" - fi - ./ssh-node.sh "$windows_node_ip" "\$file = (get-content C:/k/StartKubelet.ps1); echo \$file" -done - -echo "Done bootstrapping node IPs" diff --git a/process/testing/winfv-felix/capz/create-cluster.sh b/process/testing/winfv-felix/capz/create-cluster.sh deleted file mode 100755 index 508aea985c1..00000000000 --- a/process/testing/winfv-felix/capz/create-cluster.sh +++ /dev/null @@ -1,163 +0,0 @@ -#!/bin/bash -# Copyright (c) 2022 Tigera, Inc. All rights reserved. -# Copyright 2020 The Kubernetes Authors. -# -# 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. - -set -o errexit -set -o nounset -set -o pipefail - -. ./utils.sh - -# Verify the required Environment Variables are present. -: "${CLUSTER_NAME_CAPZ:?Environment variable empty or not defined.}" -: "${AZURE_LOCATION:?Environment variable empty or not defined.}" -: "${AZURE_RESOURCE_GROUP:?Environment variable empty or not defined.}" -: "${WINDOWS_SERVER_VERSION:?Environment variable empty or not defined.}" -: "${KUBE_VERSION:?Environment variable empty or not defined.}" -: "${AZ_KUBE_VERSION:?Environment variable empty or not defined.}" -: "${CLUSTER_API_VERSION:?Environment variable empty or not defined.}" -: "${AZURE_PROVIDER_VERSION:?Environment variable empty or not defined.}" - -: "${AZURE_SUBSCRIPTION_ID:?Environment variable empty or not defined.}" -: "${AZURE_TENANT_ID:?Environment variable empty or not defined.}" -: "${AZURE_CLIENT_ID:?Environment variable empty or not defined.}" -: "${AZURE_CLIENT_SECRET:?Environment variable empty or not defined.}" - -# Set and export VM types. -: "${AZURE_CONTROL_PLANE_MACHINE_TYPE:=Standard_D2s_v3}" -: "${AZURE_NODE_MACHINE_TYPE:=Standard_D2s_v3}" -: "${SEMAPHORE:=false}" -export AZURE_CONTROL_PLANE_MACHINE_TYPE -export AZURE_NODE_MACHINE_TYPE - -export AZURE_CLIENT_ID_USER_ASSIGNED_IDENTITY=$AZURE_CLIENT_ID # for compatibility with CAPZ v1.16 templates - -# Create the resource group and managed identity for the cluster CI -rm az-output.log || true -{ -echo "az group create --name ${CI_RG} --location ${AZURE_LOCATION}" -az group create --name ${CI_RG} --location ${AZURE_LOCATION} -echo -echo "az identity create --name ${USER_IDENTITY} --resource-group ${CI_RG} --location ${AZURE_LOCATION}" -az identity create --name ${USER_IDENTITY} --resource-group ${CI_RG} --location ${AZURE_LOCATION} -sleep 10s -export USER_IDENTITY_ID=$(az identity show --resource-group "${CI_RG}" --name "${USER_IDENTITY}" | jq -r .principalId) -echo -echo az role assignment create --assignee-object-id "${USER_IDENTITY_ID}" --assignee-principal-type "ServicePrincipal" --role "Contributor" --scope "/subscriptions/${AZURE_SUBSCRIPTION_ID}/resourceGroups/${CI_RG}" -az role assignment create --assignee-object-id "${USER_IDENTITY_ID}" --assignee-principal-type "ServicePrincipal" --role "Contributor" --scope "/subscriptions/${AZURE_SUBSCRIPTION_ID}/resourceGroups/${CI_RG}" -} >> az-output.log 2>&1 - -# Number of Linux worker nodes is the same as number of Windows worker nodes -: ${WIN_NODE_COUNT:=2} -TOTAL_NODES=$((WIN_NODE_COUNT*2+1)) -SEMAPHORE="${SEMAPHORE:="false"}" -SUFFIX="" - -echo Settings: -echo ' CLUSTER_NAME_CAPZ='${CLUSTER_NAME_CAPZ} -echo ' AZURE_LOCATION='${AZURE_LOCATION} -echo ' KUBE_VERSION='${KUBE_VERSION} -echo ' WIN_NODE_COUNT='${WIN_NODE_COUNT} - -# Utilities -: ${KIND:=./bin/kind} -: ${KUBECTL:=./bin/kubectl} -: ${CLUSTERCTL:=./bin/clusterctl} -: ${YQ:=./bin/yq} -: ${KCAPZ:="${KUBECTL} --kubeconfig=./kubeconfig"} - -# Base64 encode the variables -if [[ $SEMAPHORE == "false" ]]; then - AZURE_SUBSCRIPTION_ID_B64="$(echo -n "$AZURE_SUBSCRIPTION_ID" | base64 | tr -d '\n')" - AZURE_TENANT_ID_B64="$(echo -n "$AZURE_TENANT_ID" | base64 | tr -d '\n')" - AZURE_CLIENT_ID_B64="$(echo -n "$AZURE_CLIENT_ID" | base64 | tr -d '\n')" - AZURE_CLIENT_SECRET_B64="$(echo -n "$AZURE_CLIENT_SECRET" | base64 | tr -d '\n')" -else - export SUFFIX="-${RAND}" -fi - -# Settings needed for AzureClusterIdentity used by the AzureCluster -export AZURE_CLUSTER_IDENTITY_SECRET_NAME="cluster-identity-secret" -export CLUSTER_IDENTITY_NAME="cluster-identity" -export AZURE_CLUSTER_IDENTITY_SECRET_NAMESPACE="default" - -export EXP_MACHINE_POOL=true -export EXP_AKS=true - -# Create management cluster -${KIND} create cluster --image kindest/node:${KUBE_VERSION} --name kind${SUFFIX} -${KUBECTL} wait node kind${SUFFIX}-control-plane --for=condition=ready --timeout=90s - -sleep 30 - -# Initialize cluster - -# Create a secret to include the password of the Service Principal identity created in Azure -# This secret will be referenced by the AzureClusterIdentity used by the AzureCluster -${KUBECTL} create secret generic "${AZURE_CLUSTER_IDENTITY_SECRET_NAME}" --from-literal=clientSecret="${AZURE_CLIENT_SECRET}" --namespace "${AZURE_CLUSTER_IDENTITY_SECRET_NAMESPACE}" - -# Finally, initialize the management cluster -${CLUSTERCTL} init --infrastructure azure:${AZURE_PROVIDER_VERSION} \ - --core cluster-api:${CLUSTER_API_VERSION} - -# Generate SSH key. -rm .sshkey* || true -SSH_KEY_FILE=${SSH_KEY_FILE:-""} -if [ -z "$SSH_KEY_FILE" ]; then - SSH_KEY_FILE=.sshkey - rm -f "${SSH_KEY_FILE}" 2>/dev/null - ssh-keygen -t rsa -b 2048 -f "${SSH_KEY_FILE}" -N '' -C "" 1>/dev/null - echo "Machine SSH key generated in ${SSH_KEY_FILE}" -fi - -AZURE_SSH_PUBLIC_KEY_B64=$(base64 "${SSH_KEY_FILE}.pub" | tr -d '\r\n') -export AZURE_SSH_PUBLIC_KEY_B64 - -# Windows sets the public key via cloudbase-init which take the raw text as input -AZURE_SSH_PUBLIC_KEY=$(< "${SSH_KEY_FILE}.pub" tr -d '\r\n') -export AZURE_SSH_PUBLIC_KEY - -${CLUSTERCTL} generate cluster ${CLUSTER_NAME_CAPZ} \ - --kubernetes-version ${AZ_KUBE_VERSION} \ - --control-plane-machine-count=1 \ - --worker-machine-count=${WIN_NODE_COUNT}\ - --flavor machinepool-windows \ - > win-capz.yaml - -# Cluster templates authenticate with Workload Identity by default. Modify the AzureClusterIdentity for ServicePrincipal authentication. -# See https://capz.sigs.k8s.io/topics/identities for more details. -${YQ} -i "with(. | select(.kind == \"AzureClusterIdentity\"); .spec.type |= \"ServicePrincipal\" | .spec.clientSecret.name |= \"${AZURE_CLUSTER_IDENTITY_SECRET_NAME}\" | .spec.clientSecret.namespace |= \"${AZURE_CLUSTER_IDENTITY_SECRET_NAMESPACE}\")" win-capz.yaml - -retry_command 600 "${KUBECTL} apply -f win-capz.yaml" - -# Wait for CAPZ deployments -timeout --foreground 600 bash -c "while ! ${KUBECTL} wait --for=condition=Available --timeout=30s -n capz-system deployment -l cluster.x-k8s.io/provider=infrastructure-azure; do sleep 5; done" - -# Wait for the kubeconfig to become available. -timeout --foreground 600 bash -c "while ! ${KUBECTL} get secrets | grep ${CLUSTER_NAME_CAPZ}-kubeconfig; do sleep 5; done" -# Get kubeconfig and store it locally. -${CLUSTERCTL} get kubeconfig ${CLUSTER_NAME_CAPZ} > ./kubeconfig -timeout --foreground 600 bash -c "while ! ${KUBECTL} --kubeconfig=./kubeconfig get nodes | grep control-plane; do sleep 5; done" -echo "Cluster config is ready at ./kubeconfig. Run '${KUBECTL} --kubeconfig=./kubeconfig ...' to work with the new target cluster" -echo "Waiting for ${TOTAL_NODES} nodes to have been provisioned..." -timeout --foreground 600 bash -c "while ! ${KCAPZ} get nodes | grep ${AZ_KUBE_VERSION} | wc -l | grep ${TOTAL_NODES}; do sleep 5; done" -echo "Seen all ${TOTAL_NODES} nodes" - -# Do NOT instal Azure cloud provider (clear taint instead) -retry_command 300 "${KCAPZ} taint nodes --all node.cloudprovider.kubernetes.io/uninitialized:NoSchedule-" -# This one is a workaround for https://github.com/kubernetes-sigs/cluster-api-provider-azure/issues/3472 -retry_command 300 "${KCAPZ} taint nodes --selector=!node-role.kubernetes.io/control-plane node.cluster.x-k8s.io/uninitialized:NoSchedule-" - -echo "Done creating cluster" diff --git a/process/testing/winfv-felix/capz/export-env.sh b/process/testing/winfv-felix/capz/export-env.sh deleted file mode 100755 index 7515543eaea..00000000000 --- a/process/testing/winfv-felix/capz/export-env.sh +++ /dev/null @@ -1,46 +0,0 @@ -export CLUSTER_NAME_CAPZ="${CLUSTER_NAME_CAPZ:=${USER}-capz-win}" -export AZURE_LOCATION="${AZURE_LOCATION:="westus2"}" - -# [Optional] Select resource group. The default value is ${CLUSTER_NAME_CAPZ}-rg. -export AZURE_RESOURCE_GROUP="${AZURE_RESOURCE_GROUP:=${CLUSTER_NAME_CAPZ}-rg}" - -# These are required by the machinepool-windows template -export CI_RG="${AZURE_RESOURCE_GROUP}-ci" -export USER_IDENTITY="cloud-provider-user-identity" - -# Optional, can be windows-2019 (default) or windows-2022 -# https://capz.sigs.k8s.io/developers/development.html -# https://github.com/kubernetes-sigs/cluster-api-provider-azure/blob/main/templates/flavors/machinepool-windows/machine-pool-deployment-windows.yaml#L29 -# Default changed to 2019 due to this 2022 issue: https://github.com/microsoft/Windows-Containers/issues/516 -export WINDOWS_SERVER_VERSION="${WINDOWS_SERVER_VERSION:="windows-2019"}" - -# Select VM types ("Standard_D2s_v3" is recommented for OSS Calico) -export AZURE_CONTROL_PLANE_MACHINE_TYPE="${AZURE_CONTROL_PLANE_MACHINE_TYPE:="Standard_D2s_v3"}" -export AZURE_NODE_MACHINE_TYPE="${AZURE_NODE_MACHINE_TYPE:="Standard_D2s_v3"}" - -# Retrieve KUBE_VERSION and KIND_VERSION from metadata.mk -SCRIPT_CURRENT_DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" -METADATAMK=${SCRIPT_CURRENT_DIR}/../../../metadata.mk -if [ -f ${METADATAMK} ]; then - KINDEST_NODE_VERSION_METADATA=$(grep 'KINDEST_NODE_VERSION=' ${METADATAMK} | cut -d "=" -f 2) - KIND_VERSION_METADATA=$(grep 'KIND_VERSION=' ${METADATAMK} | cut -d "=" -f 2) - if [[ ! ${KINDEST_NODE_VERSION_METADATA} =~ ^v?[0-9]+\.[0-9]+\.[0-9]+$ ]] || [[ ! ${KIND_VERSION_METADATA} =~ ^v?[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "Failed to retrieve KINDEST_NODE_VERSION and/or KIND_VERSION from ${METADATAMK}" - exit 1 - fi -else - echo "Failed to open ${METADATAMK}" - exit 1 -fi - -export KUBE_VERSION="${KINDEST_NODE_VERSION_METADATA}" -export KIND_VERSION="${KIND_VERSION_METADATA}" - -# Azure image versions use versions corresponding to kubernetes versions, e.g. 129.7.20240717 corresponds to k8s v1.29.7 -AZ_VERSION="$(az vm image list --publisher cncf-upstream --offer capi --all -o json | jq '.[-1].version' -r)" -export AZ_KUBE_VERSION="v${AZ_VERSION:0:1}"."${AZ_VERSION:1:2}".$(echo "${AZ_VERSION}" | cut -d'.' -f2) - -export CLUSTER_API_VERSION="${CLUSTER_API_VERSION:="v1.11.1"}" -export AZURE_PROVIDER_VERSION="${AZURE_PROVIDER_VERSION:="v1.21.0"}" -export CONTAINERD_VERSION="${CONTAINERD_VERSION:="v1.7.22"}" -export YQ_VERSION="${YQ_VERSION:="v4.44.5"}" diff --git a/process/testing/winfv-felix/capz/generate-helpers.sh b/process/testing/winfv-felix/capz/generate-helpers.sh deleted file mode 100755 index f5de52a7b8f..00000000000 --- a/process/testing/winfv-felix/capz/generate-helpers.sh +++ /dev/null @@ -1,68 +0,0 @@ -#!/bin/bash -# Copyright (c) 2024 Tigera, Inc. All rights reserved. -# -# 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. - -set -o errexit -set -o nounset -set -o pipefail - -set -e -LOCAL_PATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" - -# scp from OpenSSH versions 8.8 and newer requires a '-O' flag in order to work correctly -# with windows ssh servers, but older versions don't know about that flag. Only use -# it when necessary (i.e. supported). -OFLAG="-O " -if [ "$(scp -O 2>&1 | grep -c 'unknown option -- O')" -gt 0 ]; then - OFLAG="" -fi - -: ${KUBECTL:=${LOCAL_PATH}/bin/kubectl} -: ${WIN_NODE_COUNT:=2} - -KCAPZ="${KUBECTL} --kubeconfig=./kubeconfig" - -APISERVER=$(${KCAPZ} config view -o jsonpath="{.clusters[?(@.name == \"${CLUSTER_NAME_CAPZ}\")].cluster.server}" | awk -F/ '{print $3}' | awk -F: '{print $1}') -if [ -z "${APISERVER}" ] ; then - echo "Failed to get apiserver public ip" - exit 1 -fi -echo -echo APISERVER: ${APISERVER} - -${KCAPZ} get node -o wide - -echo -echo "Generating helper files" -CONNECT_FILE="ssh-node.sh" -echo "#---------Connect to Instance--------" | tee ${CONNECT_FILE} -echo "#usage: ./ssh-node.sh 10.1.0.6" | tee -a ${CONNECT_FILE} -echo "#usage: ./ssh-node.sh 10.1.0.6 'Get-Service -Name kubelet' > output" | tee -a ${CONNECT_FILE} -echo ssh -t -i ${LOCAL_PATH}/.sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o \'ProxyCommand ssh -i ${LOCAL_PATH}/.sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -W %h:%p capi@${APISERVER}\' capi@\$1 \$2 | tee -a ${CONNECT_FILE} -chmod +x ${CONNECT_FILE} -echo - -SCP_FILE="scp-to-node.sh" -echo "#---------Copy files to Instance--------" | tee ${SCP_FILE} -echo "#usage: ./scp-to-node.sh 10.1.0.6 kubeconfig c:\\\\k\\\\kubeconfig -- copy kubeconfig to 10.1.0.6" | tee -a ${SCP_FILE} -echo "#usage: ./scp-to-node.sh 10.1.0.6 images/ebpf-for-windows-c-temp.zip 'c:\\' -- copy temp zip to 10.1.0.6" | tee -a ${SCP_FILE} -echo scp ${OFLAG} -i ${LOCAL_PATH}/.sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o \'ProxyCommand ssh -i ${LOCAL_PATH}/.sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -W %h:%p capi@${APISERVER}\' \$2 capi@\$1:\$3 | tee -a ${SCP_FILE} -chmod +x ${SCP_FILE} -echo - -SCP_FROM_NODE="scp-from-node.sh" -echo "#---------Copy files from Instance--------" | tee ${SCP_FROM_NODE} -echo "#usage: ./scp-from-node.sh 10.1.0.6 c:/k/calico.log ./calico.log" | tee -a ${SCP_FROM_NODE} -echo scp ${OFLAG} -r -i ${LOCAL_PATH}/.sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o \'ProxyCommand ssh -i ${LOCAL_PATH}/.sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -W %h:%p capi@${APISERVER}\' capi@\$1:\$2 \$3 | tee -a ${SCP_FROM_NODE} -chmod +x ${SCP_FROM_NODE} diff --git a/process/testing/winfv-felix/capz/install-calico.sh b/process/testing/winfv-felix/capz/install-calico.sh deleted file mode 100755 index b2557475ca7..00000000000 --- a/process/testing/winfv-felix/capz/install-calico.sh +++ /dev/null @@ -1,165 +0,0 @@ -#!/bin/bash -# Copyright (c) 2022-2024 Tigera, Inc. All rights reserved. -# -# 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. - -set -o errexit -set -o nounset -set -o pipefail - -. ./utils.sh - -# Use KUBECTL to access the local kind management cluster. Use KCAPZ to -# access the CAPZ cluster. -: ${KUBECTL:=./bin/kubectl} -: ${KCAPZ:="${KUBECTL} --kubeconfig=./kubeconfig"} - -: ${PRODUCT:=calico} -: ${RELEASE_STREAM:="master"} # Default to master -: ${HASH_RELEASE:="false"} # Set to true to use hash release - -: "${AZ_KUBE_VERSION:?Environment variable empty or not defined.}" - -echo Settings: -echo ' PRODUCT='${PRODUCT} -echo ' RELEASE_STREAM='${RELEASE_STREAM} -echo ' HASH_RELEASE='${HASH_RELEASE} - -if [ ${PRODUCT} == 'calient' ]; then - # Verify if the required variables are set for Calico EE - : "${GCR_IO_PULL_SECRET:?Environment variable empty or not defined.}" - : "${TSEE_TEST_LICENSE:?Environment variable empty or not defined.}" -fi - -SCRIPT_CURRENT_DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 && pwd -P )" -LOCAL_MANIFESTS_DIR="${SCRIPT_CURRENT_DIR}/../../../../manifests" - -if [ ${PRODUCT} == 'calient' ]; then - RELEASE_BASE_URL="https://downloads.tigera.io/ee/${RELEASE_STREAM}" -else - RELEASE_BASE_URL="https://raw.githubusercontent.com/projectcalico/calico/${RELEASE_STREAM}" -fi - -if [ ${HASH_RELEASE} == 'true' ]; then - if [ -z ${RELEASE_STREAM} ]; then - echo "RELEASE_STREAM not set for HASH release" - exit 1 - fi - if [ ${PRODUCT} == 'calient' ]; then - URL_HASH="https://latest-cnx.docs.eng.tigera.net/${RELEASE_STREAM}.txt" - else - URL_HASH="https://latest-os.docs.eng.tigera.net/${RELEASE_STREAM}.txt" - fi - RELEASE_BASE_URL=$(curl -sS ${URL_HASH}) -fi - -if [[ ${RELEASE_STREAM} != 'local' ]]; then - # Check release url and installation scripts - echo "Set release base url ${RELEASE_BASE_URL}" - sed -i "s,export RELEASE_BASE_URL.*,export RELEASE_BASE_URL=\"${RELEASE_BASE_URL}\"," ./export-env.sh -fi - -# Create a storage class and persistent volume for Calico Enterprise. -if [ ${PRODUCT} == 'calient' ]; then - ${KCAPZ} create -f ./EE/storage-class-azure-file.yaml - ${KCAPZ} create -f ./EE/persistent-volume.yaml -fi - -# Install Calico on Linux nodes -if [[ ${RELEASE_STREAM} == 'local' ]]; then - # Use local manifests - ${KCAPZ} create -f ${LOCAL_MANIFESTS_DIR}/operator-crds.yaml - ${KCAPZ} create -f ${LOCAL_MANIFESTS_DIR}/tigera-operator.yaml -else - # Use release url - echo "Set release base url ${RELEASE_BASE_URL}" - sed -i "s,export RELEASE_BASE_URL.*,export RELEASE_BASE_URL=\"${RELEASE_BASE_URL}\"," ./export-env.sh - curl -sSf -L --retry 5 ${RELEASE_BASE_URL}/manifests/operator-crds.yaml -o operator-crds.yaml - curl -sSf -L --retry 5 ${RELEASE_BASE_URL}/manifests/tigera-operator.yaml -o tigera-operator.yaml - ${KCAPZ} create -f ./operator-crds.yaml - ${KCAPZ} create -f ./tigera-operator.yaml -fi - -if [[ ${PRODUCT} == 'calient' ]]; then - # Install prometheus operator - if [[ ${RELEASE_STREAM} == 'local' ]]; then - ${KCAPZ} create -f ${LOCAL_MANIFESTS_DIR}/tigera-prometheus-operator.yaml - else - curl -sSf -L --retry 5 ${RELEASE_BASE_URL}/manifests/tigera-prometheus-operator.yaml -o tigera-prometheus-operator.yaml - ${KCAPZ} create -f ./tigera-prometheus-operator.yaml - fi - - # Install pull secret. - ${KCAPZ} create secret generic tigera-pull-secret \ - --type=kubernetes.io/dockerconfigjson -n tigera-operator \ - --from-file=.dockerconfigjson=${GCR_IO_PULL_SECRET} - - # When using an EE hash release, the operator has to have tigera-pull-secret in its imagePullSecrets. - if [[ ${HASH_RELEASE} == 'true' ]]; then - ${KCAPZ} patch deployment tigera-operator -n tigera-operator --patch '{"spec":{"template":{"spec":{"imagePullSecrets":[{"name":"tigera-pull-secret"}]}}}}' - fi - - # Create custom resources - ${KCAPZ} create -f ./EE/custom-resources.yaml - - # Install Calico EE license (after the Tigera apiserver comes up) - echo "Wait for the Tigera apiserver to be ready..." - timeout --foreground 600 bash -c "while ! ${KCAPZ} wait pod -l k8s-app=tigera-apiserver --for=condition=Ready -n tigera-system --timeout=30s; do sleep 5; done" - echo "Tigera apiserver is ready, installing Calico EE license" - - retry_command 60 "${KCAPZ} create -f ${TSEE_TEST_LICENSE}" -else - # Create custom resources - ${KCAPZ} create -f ./OSS/custom-resources.yaml -fi - -echo "Wait for Calico to be ready on Linux nodes..." -timeout --foreground 600 bash -c "while ! ${KCAPZ} wait pod -l k8s-app=calico-node --for=condition=Ready -n calico-system --timeout=30s; do sleep 5; done" -echo "Calico is ready on Linux nodes" - -# Install Calico on Windows nodes - -# Turn strict affinity on in the default IPAMConfig (required for Windows) -${KCAPZ} patch ipamconfig default --type merge --patch='{"spec": {"strictAffinity": true}}' - -# Find out apiserver address and port and fill in 'kubernetes-services-endpoint' configmap (required for Windows) -APISERVER=$(${KCAPZ} get configmap -n kube-system kube-proxy -o yaml | awk -F'://' '/server: https:\/\// { print $2 }') -APISERVER_ADDR=$(echo ${APISERVER} | awk -F':' '{ print $1 }') -APISERVER_PORT=$(echo ${APISERVER} | awk -F':' '{ print $2 }') -${KCAPZ} apply -f - << EOF -kind: ConfigMap -apiVersion: v1 -metadata: - name: kubernetes-services-endpoint - namespace: tigera-operator -data: - KUBERNETES_SERVICE_HOST: "${APISERVER_ADDR}" - KUBERNETES_SERVICE_PORT: "${APISERVER_PORT}" -EOF - -# Patch installation to include required serviceCIDRs information and enable the Windows daemonset -${KCAPZ} patch installation default --type merge --patch='{"spec": {"serviceCIDRs": ["10.96.0.0/12"], "calicoNetwork": {"windowsDataplane": "HNS"}}}' - -echo "Wait for Calico to be ready on Windows nodes..." -timeout --foreground 600 bash -c "while ! ${KCAPZ} wait pod -l k8s-app=calico-node-windows --for=condition=Ready -n calico-system --timeout=30s; do sleep 5; done" -echo "Calico is ready on Windows nodes" - -# Create the kube-proxy-windows daemonset -echo "Install kube-proxy-windows ${AZ_KUBE_VERSION} from sig-windows-tools" -for iter in {1..5};do - curl -sSf -L https://raw.githubusercontent.com/kubernetes-sigs/sig-windows-tools/master/hostprocess/calico/kube-proxy/kube-proxy.yml | sed "s/KUBE_PROXY_VERSION/${AZ_KUBE_VERSION}/g" | ${KCAPZ} apply -f - && break || echo "download error: retry $iter in 5s" && sleep 5; -done; - -echo "Wait for kube-proxy to be ready on Windows nodes..." -timeout --foreground 1200 bash -c "while ! ${KCAPZ} wait pod -l k8s-app=kube-proxy-windows --for=condition=Ready -n kube-system --timeout=30s; do sleep 5; done" -echo "kube-proxy is ready on Windows nodes" diff --git a/process/testing/winfv-felix/capz/replace-win-containerd.ps1 b/process/testing/winfv-felix/capz/replace-win-containerd.ps1 deleted file mode 100644 index 6ef3ab73075..00000000000 --- a/process/testing/winfv-felix/capz/replace-win-containerd.ps1 +++ /dev/null @@ -1,60 +0,0 @@ -# Replace an existing containerd installation with a specific version of containerd. -# Adapted from https://github.com/kubernetes-sigs/windows-testing/blob/e841a06620a293ab2c286b53f562a97f3397595f/capz/templates/windows-base.yaml#L27-L61 - -Param( - [parameter(Mandatory = $false)] $ContainerdVersion="1.7.22" -) - -$ErrorActionPreference = 'Stop' -$CONTAINERD_URL="https://github.com/containerd/containerd/releases/download/v${ContainerdVersion}/containerd-${ContainerdVersion}-windows-amd64.tar.gz" -if($CONTAINERD_URL -ne ""){ - # Kubelet service depends on containerd service so make a best effort attempt to stop it - Stop-Service kubelet -Force -ErrorAction SilentlyContinue - Stop-Service containerd -Force - echo "downloading containerd: $CONTAINERD_URL" - curl.exe --retry 10 --retry-delay 5 -L "$CONTAINERD_URL" --output "c:/k/containerd.tar.gz" - # Log service state and if any files under containerd director are locked - Get-Service -Name containerd, kubelet - $dir = "c:/Program Files/containerd" - $files = Get-ChildItem $dir -Recurse - Write-Output "Checking if any files under $dir are locked" - foreach ($file in $files) { - $f = $file.FullName - Write-output "$f" - $fi = New-Object System.IO.FileInfo $f - try { - $fStream = $fi.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None) - if ($fStream) { - $fStream.Close() - } - } catch { - Write-Output "Unable to open file: $f" - } - } - Write-Output "Extracting new containerd binaries" - tar.exe -zxvf c:/k/containerd.tar.gz -C "c:/Program Files/containerd" --strip-components 1 - - Write-Output "Starting containerd and kubelet" - Start-Service containerd - Start-Service kubelet - Get-Service -Name containerd, kubelet -} -containerd.exe --version -containerd-shim-runhcs-v1.exe --version - -Write-Host Applying registry fix for https://github.com/microsoft/Windows-Containers/issues/516 if on Windows 2022 -$needToRestart = $false -if ((Get-ComputerInfo).OsBuildNumber -eq 20348 -and (Get-ItemProperty -Path "HKLM:SYSTEM\CurrentControlSet\Services\hns\State").FwPerfImprovementChange -ne 0) { - if ((Get-Item -Path 'HKLM:SYSTEM\\CurrentControlSet\\Services\\hns\\State') -eq $null ) { - New-Item -Path 'HKLM:SYSTEM\\CurrentControlSet\\Services\\hns\\State' - } - Remove-ItemProperty -Path 'HKLM:SYSTEM\\CurrentControlSet\\Services\\hns\\State' -Name 'FwPerfImprovementChange' - New-ItemProperty -Path 'HKLM:SYSTEM\\CurrentControlSet\\Services\\hns\\State' -Name 'FwPerfImprovementChange' -Value '0' -PropertyType 'DWORD' - $needToRestart = $true -} - -if ($needToRestart) -{ - Write-host Restarting computer - Restart-Computer -Force -} diff --git a/process/testing/winfv-felix/capz/replace-win-containerd.sh b/process/testing/winfv-felix/capz/replace-win-containerd.sh deleted file mode 100755 index e4d5720c426..00000000000 --- a/process/testing/winfv-felix/capz/replace-win-containerd.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -# Copyright (c) 2024 Tigera, Inc. All rights reserved. -# -# 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. - -set -o errexit -set -o nounset -set -o pipefail - -. ./utils.sh - -# Use KUBECTL to access the local kind management cluster. Use KCAPZ to -# access the CAPZ cluster. -: ${KUBECTL:=./bin/kubectl} -: ${KCAPZ:="${KUBECTL} --kubeconfig=./kubeconfig"} -: ${CONTAINERD_VERSION:="v1.7.22"} - -# Cordon+drain windows nodes, then run the powershell script that replaces -# containerd with the specific version wanted and finally uncordon the nodes - -echo "Installing containerd ${CONTAINERD_VERSION} on windows nodes" - -${KCAPZ} cordon -l kubernetes.io/os=windows -${KCAPZ} drain -l kubernetes.io/os=windows --ignore-daemonsets -WIN_NODES=$(${KCAPZ} get nodes -o wide -l kubernetes.io/os=windows --no-headers | awk '{print $6}' | sort) -for n in ${WIN_NODES} -do - ./scp-to-node.sh $n ./replace-win-containerd.ps1 c:\\k\\replace-win-containerd.ps1 - ./ssh-node.sh $n "c:\\k\\replace-win-containerd.ps1 -ContainerdVersion ${CONTAINERD_VERSION#"v"}" -done -${KCAPZ} uncordon -l kubernetes.io/os=windows - -echo "Done installing containerd ${CONTAINERD_VERSION} on windows nodes" diff --git a/process/testing/winfv-felix/capz/utils.sh b/process/testing/winfv-felix/capz/utils.sh deleted file mode 100755 index e0908f6e63f..00000000000 --- a/process/testing/winfv-felix/capz/utils.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash -# Copyright (c) 2023 Tigera, Inc. All rights reserved. -# Copyright 2020 The Kubernetes Authors. -# -# 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. - -unset -f retry_command -function retry_command() { - local RETRY=$(($1/10)) - local CMD=$2 - echo - - for i in $(seq 1 $RETRY); do - echo "Trying '$CMD', attempt ${i}" - $CMD && return 0 || sleep 10 - done - echo "Command '${CMD}' failed after $RETRY attempts" - return 1 -} - diff --git a/process/testing/winfv-felix/cloudformation-simple-vpc.json b/process/testing/winfv-felix/cloudformation-simple-vpc.json deleted file mode 100644 index 0b3a01d77b0..00000000000 --- a/process/testing/winfv-felix/cloudformation-simple-vpc.json +++ /dev/null @@ -1,127 +0,0 @@ -{ - "AWSTemplateFormatVersion" : "2010-09-09", - - "Description" : "AWS Template to create a a VPC with one subnet", - - "Parameters" : { - "ResourceName": { - "Description" : "Resource name", - "Type" : "String" - } - }, - - "Resources" : { - "VPC" : { - "Type" : "AWS::EC2::VPC", - "Properties" : { - "CidrBlock" : "172.20.0.0/16", - "EnableDnsSupport" : true, - "EnableDnsHostnames" : true, - "Tags" : [ {"Key" : "Name", "Value" : { "Ref" : "ResourceName"} } ] - } - }, - - "Subnet" : { - "Type" : "AWS::EC2::Subnet", - "Properties" : { - "AvailabilityZone" : { "Fn::Select" : [ "0", { "Fn::GetAZs" : "" } ] }, - "VpcId" : { "Ref" : "VPC" }, - "CidrBlock" : "172.20.32.0/19", - "MapPublicIpOnLaunch" : true, - "Tags" : [ {"Key" : "Name", "Value" : { "Ref" : "ResourceName"} } ] - } - }, - - "InternetGateway" : { - "Type" : "AWS::EC2::InternetGateway", - "Properties" : { - "Tags" : [ {"Key" : "Name", "Value" : { "Ref" : "ResourceName"} } ] - } - }, - - "AttachGateway" : { - "Type" : "AWS::EC2::VPCGatewayAttachment", - "Properties" : { - "VpcId" : { "Ref" : "VPC" }, - "InternetGatewayId" : { "Ref" : "InternetGateway" } - } - }, - - "RouteTable" : { - "Type" : "AWS::EC2::RouteTable", - "Properties" : { - "VpcId" : {"Ref" : "VPC"}, - "Tags" : [ {"Key" : "Name", "Value" : { "Ref" : "ResourceName"} } ] - } - }, - - "GatewayRoute" : { - "Type" : "AWS::EC2::Route", - "DependsOn" : ["InternetGateway", "AttachGateway"], - "Properties" : { - "RouteTableId" : { "Ref" : "RouteTable" }, - "DestinationCidrBlock" : "0.0.0.0/0", - "GatewayId" : { "Ref" : "InternetGateway" } - } - }, - - "SubnetRouteTableAssociation" : { - "Type" : "AWS::EC2::SubnetRouteTableAssociation", - "Properties" : { - "SubnetId" : { "Ref" : "Subnet" }, - "RouteTableId" : { "Ref" : "RouteTable" } - } - }, - - "sgnode": { - "Type": "AWS::EC2::SecurityGroup", - "DependsOn": "VPC", - "Properties": { - "GroupDescription": "vpc node sg", - "VpcId": { "Ref": "VPC" }, - "Tags" : [ {"Key" : "Name", "Value" : { "Ref" : "ResourceName"} } ] - } - }, - - "sgnodeingress0": { - "Type": "AWS::EC2::SecurityGroupIngress", - "DependsOn": "sgnode", - "Properties": { - "GroupId": { "Ref": "sgnode" }, - "IpProtocol": "-1", - "FromPort": "0", - "ToPort": "65535", - "SourceSecurityGroupId": { "Ref": "sgnode" } - } - }, - - "sgnodeingress1": { - "Type": "AWS::EC2::SecurityGroupIngress", - "DependsOn": "sgnode", - "Properties": { - "GroupId": { "Ref": "sgnode" }, - "IpProtocol": "tcp", - "FromPort": "22", - "ToPort": "22", - "CidrIp": "0.0.0.0/0" - } - } - }, - - "Outputs" : { - "VpcId" : { - "Value" : { "Ref" : "VPC" }, - "Description" : "VPC ID" - }, - - "SubnetId" : { - "Value" : { "Ref" : "Subnet" }, - "Description" : "Subnet ID" - }, - - "SgId" : { - "Value" : { "Ref" : "sgnode" }, - "Description" : "Sg ID" - } - } -} diff --git a/process/testing/winfv-felix/cloudformation-windows-server.json b/process/testing/winfv-felix/cloudformation-windows-server.json deleted file mode 100644 index 581fc57c293..00000000000 --- a/process/testing/winfv-felix/cloudformation-windows-server.json +++ /dev/null @@ -1,308 +0,0 @@ -{ - "AWSTemplateFormatVersion" : "2010-09-09", - - "Description" : "This template creates one linux and one windows server installation for running windows FVs.", - - "Parameters" : { - "CurrentVPC": { - "Description" : "Id of current VPC", - "Type" : "String" - }, - - "CurrentSubnet": { - "Description" : "current subnet from VPC", - "Type": "String" - }, - - "CurrentNodesSg": { - "Description" : "current kubernetes nodes security group", - "Type": "String" - }, - - "KubeVersion": { - "Description" : "version of kubernetes", - "Type": "String" - }, - - "BackEnd": { - "Description" : "calico-bgp, flannel-host-gw, or flannel-vxlan", - "Type": "String" - }, - - "PPKConfig": { - "Description" : "PPK key file for pscp", - "Type": "String" - }, - - "WindowsOS": { - "Description" : "windows OS", - "Type": "String" - }, - - "KeyName" : { - "Description" : "Name of an existing EC2 KeyPair", - "Type" : "AWS::EC2::KeyPair::KeyName", - "ConstraintDescription" : "must be the name of an existing EC2 KeyPair." - }, - - "SourceCidrForRDP" : { - "Description" : "IP Cidr from which you are likely to RDP into the instances. You can add rules later by modifying the created security groups e.g. 54.32.98.160/32", - "Type" : "String", - "MinLength" : "9", - "MaxLength" : "18", - "AllowedPattern" : "^([0-9]+\\.){3}[0-9]+\\/[0-9]+$" - } - }, - - "Mappings" : { - "AWSRegion2AMI" : { - "us-west-2" : {"Windows1809container" : "ami-0ea9f92e8a473c37f", "Windows2022container": "ami-0de8a8dd91337a7cc", "ubuntu2004": "ami-0d26e1fbc75664845"}, - "us-east-2" : {"Windows1809container" : "ami-0b2153301a21b82f0", "Windows2022container": "ami-0bb4ff38c83f26428", "ubuntu2004": "ami-09b0e7e86badffe0e"} - } - - }, - - "Resources" : { - "HybridClusterSecurityGroup" : { - "Type" : "AWS::EC2::SecurityGroup", - "Properties" : { - "VpcId" : {"Ref" : "CurrentVPC"}, - "GroupDescription" : "Enable RDP", - "SecurityGroupIngress" : [ - {"IpProtocol" : "tcp", "FromPort" : "3389", "ToPort" : "3389", "CidrIp" : { "Ref" : "SourceCidrForRDP" }} - ] - } - }, - - "HybridCluster0EIP" : { - "Type" : "AWS::EC2::EIP", - "Properties" : { - "InstanceId" : { "Ref" : "HybridCluster0" } - } - }, - - "HybridCluster1EIP" : { - "Type" : "AWS::EC2::EIP", - "Properties" : { - "InstanceId" : { "Ref" : "HybridCluster1" } - } - }, - - "HybridCluster0": { - "Type" : "AWS::EC2::Instance", - "Metadata" : { - "AWS::CloudFormation::Init" : { - "configSets": { - "calico": [ - "1-install" - ] - }, - - "1-install" : { - "commands" : { - "1-docker-pull" : { - "command" : "docker pull busybox", - "waitAfterCompletion": "0" - } - } - } - } - }, - "Properties": { - "InstanceType" : "t2.medium", - "ImageId" : { "Fn::FindInMap" : [ "AWSRegion2AMI", { "Ref" : "AWS::Region" }, "ubuntu2004" ]}, - "SecurityGroupIds" : [ {"Ref" : "CurrentNodesSg"}, {"Ref" : "HybridClusterSecurityGroup"} ], - "KeyName" : { "Ref" : "KeyName" }, - "SubnetId": { "Ref": "CurrentSubnet" }, - "SourceDestCheck": false, - "Tags" : [ {"Key" : "Name", "Value" : { "Fn::Join" : [ "-", [ { "Ref": "AWS::StackName" }, "linux" ]]}} ], - "UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [ - "#!/bin/bash -xe\n", - "apt-get update -y\n", - "apt-get -y install python3-pip\n", - "pip3 install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz\n", - - "curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -\n", - "sudo add-apt-repository \"deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable\"\n", - - "apt-get update -qq\n", - "apt-get install -y docker-ce\n", - "usermod -aG docker ubuntu\n", - - "cfn-init -v ", - " --stack ", { "Ref" : "AWS::StackName" }, - " --resource HybridCluster0 ", - " --configsets calico ", - " --region ", { "Ref" : "AWS::Region" }, "\n" - ]]}} - } - }, - - "HybridCluster1": { - "Type" : "AWS::EC2::Instance", - "Metadata" : { - "AWS::CloudFormation::Init" : { - "configSets": { - "calico": [ - "1-download", - "2-unzip", - "3-install" - ] - }, - "1-download" : { - "files" : { - "c:\\k\\linux-node.ppk" : { - "content" : "{{config0}}", - "context" : { "config0" : { "Ref" : "PPKConfig"} } - }, - "c:\\k\\backend" : { - "content" : "{{config0}}", - "context" : { "config0" : { "Ref" : "BackEnd"} } - }, - "c:\\k\\etcd-endpoints" : { - "content" : { "Fn::GetAtt" : [ "HybridCluster0", "PrivateIp" ] } - }, - "c:\\k\\cf-debug-copy-exe.ps1" : { - "content" : "echo y | c:\\k\\pscp.exe -r -2 -i c:\\k\\linux-node.ppk ubuntu@{{linuxip}}:/home/ubuntu/*.exe .", - "context" : { "linuxip" : { "Fn::GetAtt" : [ "HybridCluster0", "PrivateIp" ] } } - }, - "c:\\k\\cf-debug-copy-zip.ps1" : { - "content" : "echo y | c:\\k\\pscp.exe -r -2 -i c:\\k\\linux-node.ppk ubuntu@{{linuxip}}:/home/ubuntu/*.zip .", - "context" : { "linuxip" : { "Fn::GetAtt" : [ "HybridCluster0", "PrivateIp" ] } } - }, - "c:\\k\\cf-copy-all-from-linux.ps1" : { - "content" : "echo y | c:\\k\\pscp.exe -r -2 -i c:\\k\\linux-node.ppk ubuntu@{{linuxip}}:/home/ubuntu/winfv/* c:\\k", - "context" : { "linuxip" : { "Fn::GetAtt" : [ "HybridCluster0", "PrivateIp" ] } } - }, - "c:\\k\\cf-copy-ready-from-linux.ps1" : { - "content" : "echo y | c:\\k\\pscp.exe -2 -i c:\\k\\linux-node.ppk ubuntu@{{linuxip}}:/home/ubuntu/winfv/file-ready c:\\k", - "context" : { "linuxip" : { "Fn::GetAtt" : [ "HybridCluster0", "PrivateIp" ] } } - }, - "c:\\k\\cf-wait-till-ready.ps1" : { - "content" : "while (!(Test-Path 'c:\\k\\file-ready')) { Start-Sleep 3; c:\\k\\cf-copy-ready-from-linux.ps1 }" - }, - "C:\\k\\pscp.exe" : { - "source" : "https://the.earth.li/~sgtatham/putty/0.73/w64/pscp.exe" - } - } - }, - - "2-unzip" : { - "commands" : { - "1-config-script" : { - "command" : "powershell.exe -File c:\\k\\cf-wait-till-ready.ps1", - "waitAfterCompletion": "0" - }, - "2-config-script" : { - "command" : "powershell.exe -File c:\\k\\cf-copy-all-from-linux.ps1", - "waitAfterCompletion": "0" - } - } - }, - - "3-install" : { - "commands" : { - "1-install-bgp0" : { - "command" : "powershell.exe -Command \"Install-WindowsFeature RemoteAccess\"", - "test" : "findstr calico-bgp c:\\k\\backend", - "waitAfterCompletion": "3" - }, - "1-install-bgp1" : { - "command" : "powershell.exe -Command \"Install-WindowsFeature RSAT-RemoteAccess-PowerShell\"", - "test" : "findstr calico-bgp c:\\k\\backend", - "waitAfterCompletion": "3" - }, - "1-install-bgp2" : { - "command" : "powershell.exe -Command \"Install-WindowsFeature Routing\"", - "test" : "findstr calico-bgp c:\\k\\backend", - "waitAfterCompletion": "3" - }, - "1-install-bgp3-Restart-Computer" : { - "command" : "powershell.exe -Command \"Restart-Computer -Force\"", - "test" : "findstr calico-bgp c:\\k\\backend", - "waitAfterCompletion": "forever" - }, - "1-install-bgp4" : { - "command" : "powershell.exe -Command \"Start-Sleep 60; Install-RemoteAccess -ErrorAction Ignore -VpnType RoutingOnly\"", - "test" : "findstr calico-bgp c:\\k\\backend", - "waitAfterCompletion": "0" - }, - "2-install-docker1-enable-containers" : { - "command": "powershell.exe -Command \"Install-WindowsFeature -Name Containers\"", - "waitAfterCompletion": "3" - }, - "2-install-docker2-Restart-Computer" : { - "command": "powershell.exe -Command \"Restart-Computer -Force\"", - "waitAfterCompletion": "forever" - }, - "2-install-docker3" : { - "command": "powershell.exe -Command \"Invoke-WebRequest -UseBasicParsing 'https://raw.githubusercontent.com/microsoft/Windows-Containers/Main/helpful_tools/Install-DockerCE/install-docker-ce.ps1' -o install-docker-ce.ps1; ./install-docker-ce.ps1\"", - "waitAfterCompletion": "3" - }, - "3-run-fv" : { - "command": "powershell.exe -Command \"start-process powershell -argument 'C:\\k\\run-fv.ps1' -WindowStyle hidden -RedirectStandardOutput c:\\k\\cf-fv-log.txt\" ", - "waitAfterCompletion": "0" - } - } - } - } - }, - "Properties": { - "InstanceType" : "t2.large", - "ImageId" : { "Fn::FindInMap" : [ "AWSRegion2AMI", { "Ref" : "AWS::Region" }, { "Ref" : "WindowsOS" } ]}, - "SecurityGroupIds" : [ {"Ref" : "CurrentNodesSg"}, {"Ref" : "HybridClusterSecurityGroup"} ], - "KeyName" : { "Ref" : "KeyName" }, - "SubnetId": { "Ref": "CurrentSubnet" }, - "SourceDestCheck": false, - "Tags" : [ {"Key" : "Name", "Value" : { "Fn::Join" : [ "-", [ { "Ref": "AWS::StackName" }, "windows" ]]}} ], - "UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [ - "\n", - - "Set-ExecutionPolicy Unrestricted -Force\n", - "Invoke-WebRequest -Uri https://curl.se/ca/cacert.pem -OutFile \"C:\\Program Files\\Amazon\\cfn-bootstrap\\cacert.pem\"\n", - "cfn-init.exe -v -s ", { "Ref" : "AWS::StackId" }, - " -r HybridCluster1", " --configset calico", - " --region ", { "Ref" : "AWS::Region" }, "\n", - - "" - - ]]}} - }, - "DependsOn" : "HybridCluster0" - } - - }, - - "Outputs" : { - "InstanceId0" : { - "Value" : { "Ref" : "HybridCluster0" }, - "Description" : "linux InstanceId" - }, - - "InstanceId1" : { - "Value" : { "Ref" : "HybridCluster1" }, - "Description" : "Windows InstanceId" - }, - - "InstanceEIP0" : { - "Value" : { "Ref" : "HybridCluster0EIP" }, - "Description" : "Linux Instance PublicIP" - }, - - "InstancePIP0" : { - "Value" : { "Fn::GetAtt" : [ "HybridCluster0", "PrivateIp" ] }, - "Description" : "Linux Instance PrivateIP" - }, - - "InstanceEIP1" : { - "Value" : { "Ref" : "HybridCluster1EIP" }, - "Description" : "Windows Instance PublicIP" - }, - - "InstancePIP1" : { - "Value" : { "Fn::GetAtt" : [ "HybridCluster1", "PrivateIp" ] }, - "Description" : "Windows Instance PrivateIP" - } - - } -} diff --git a/process/testing/winfv-felix/create_kubeadm_cluster.sh b/process/testing/winfv-felix/create_kubeadm_cluster.sh deleted file mode 100755 index 3c97a243c60..00000000000 --- a/process/testing/winfv-felix/create_kubeadm_cluster.sh +++ /dev/null @@ -1,126 +0,0 @@ -#!/bin/bash - -KUBE_VERSION=$1 -BACKEND=$2 -FV_TYPE=$3 - -sudo mkdir -p /etc/docker -cat < /dev/null -sudo apt-get update -y -sudo apt remove containerd -sudo apt install containerd.io -sudo rm /etc/containerd/config.toml -sudo systemctl enable --now containerd -sudo systemctl daemon-reload -sudo systemctl restart containerd - -KUBE_REPO_VERSION=$(echo ${KUBE_VERSION} | cut -d '.' -f 1,2) -# Download the k8s repo signing key -curl -fsSL --retry 5 "https://pkgs.k8s.io/core:/stable:/v${KUBE_REPO_VERSION}/deb/Release.key" | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg -sudo chmod 644 /etc/apt/keyrings/kubernetes-apt-keyring.gpg -# Add the Kubernetes apt repository -echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v${KUBE_REPO_VERSION}/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list - -K8S_PKG_VERSION=${KUBE_VERSION}-1.1 -sudo apt-get update && sudo apt-get install -y kubelet=${K8S_PKG_VERSION} kubeadm=${K8S_PKG_VERSION} kubectl=${K8S_PKG_VERSION} -sudo swapoff -a - -K8S_VERSION=stable-$(echo ${KUBE_VERSION} | cut -d. -f1,2) -sudo kubeadm init --kubernetes-version ${K8S_VERSION} --pod-network-cidr=192.168.0.0/16 -mkdir -p $HOME/.kube -sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config -sudo chown $ubuntu:$ubuntu $HOME/.kube/config - -if [ "$FV_TYPE" == "cni-plugin" ]; then - exit 0 -fi - -function retry_kubectl() { - kubectl_command=$1 - kube=$(command -v "kubectl") - if [[ "${kube}" == "" ]]; then - echo "[ERROR] kubectl not found locally". - exit 1 - fi - kubectl_retries=$2 - kubectl_success=1 - kubectl_output="" - until [[ ${kubectl_success} -eq 0 ]] || [[ kubectl_retries -lt 1 ]]; do - echo "Attempting to run ${kube} $kubectl_command, attempts remaining=$kubectl_retries" - kubectl_output=$(eval "${kube} ${kubectl_command}") - kubectl_success=$? - ((kubectl_retries--)) - sleep 1 - done - echo "${kubectl_output}" - if [[ ${kubectl_success} -ne 0 ]]; then - echo "[ERROR] kubectl retry failed". - exit 1 - fi -} - -# install calico -ROOT="./winfv" -docs_url=`curl https://latest-os.docs.eng.tigera.net/master.txt` -kubectl create -f ${docs_url}/manifests/tigera-operator.yaml -sleep 5 - -# Deploy OS Calico but for EE FV, apply EE crds and RBAC later. -echo "Applying custom resources..." -kubectl apply -f ${ROOT}/infra/installation-${BACKEND}.yaml -kubectl get installation default -oyaml - -echo "Checking that kube-dns is up" -retry_kubectl "wait pod -l k8s-app=kube-dns --for=condition=Ready -n kube-system --timeout=300s" 30 -echo "Calico is running." - -kubectl taint nodes --all node-role.kubernetes.io/master- - -# strict affinity -curl -sSf -L --retry 5 https://github.com/projectcalico/calico/releases/download/v3.27.0/calicoctl-linux-amd64 -o calicoctl -chmod +x calicoctl -export CALICO_DATASTORE_TYPE=kubernetes -export CALICO_KUBECONFIG=~/.kube/config -./calicoctl --allow-version-mismatch get node -./calicoctl --allow-version-mismatch ipam configure --strictaffinity=true -echo "ipam configured" - -pushd $ROOT/infra -./setup.sh $CALICO_KUBECONFIG -if [ "$FV_TYPE" == "tigera-felix" ]; then - # Latest OS or EE crds is copied from felix's fv/infrastructure/crds. - kubectl apply -f ee/crd - # Stop operator setups OS RBAC role. - kubectl annotate clusterrole calico-node unsupported.operator.tigera.io/ignore="true" - sleep 5 - # Latest EE calico-node RBAC role is predefined. - kubectl apply -f ee/role.yaml - kubectl apply -f ee/license.yaml -fi -popd diff --git a/process/testing/winfv-felix/infra/ee/role.yaml b/process/testing/winfv-felix/infra/ee/role.yaml deleted file mode 100644 index 4c64b1d2ecb..00000000000 --- a/process/testing/winfv-felix/infra/ee/role.yaml +++ /dev/null @@ -1,183 +0,0 @@ -# Overwrite OS Calico RBAC role to EE Calico RBAC role for calico-node. -# Ref: https://github.com/tigera/node-private/blob/master/tests/k8st/infra/calico-kdd.yaml -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: calico-node -rules: - # The tests require that node has access to secrets for BGP peering. - - apiGroups: [""] - resources: - - secrets - verbs: - - get - - list - - watch - # The CNI plugin needs to get pods, nodes, and namespaces. - - apiGroups: [""] - resources: - - pods - - nodes - - namespaces - verbs: - - get - # EndpointSlices are used for Service-based network policy rule - # enforcement. - - apiGroups: ["discovery.k8s.io"] - resources: - - endpointslices - verbs: - - watch - - list - - apiGroups: [""] - resources: - - endpoints - - services - verbs: - # Used to discover service IPs for advertisement. - - watch - - list - # Used to discover Typhas. - - get - # Pod CIDR auto-detection on kubeadm needs access to config maps. - - apiGroups: [""] - resources: - - configmaps - verbs: - - get - - apiGroups: [""] - resources: - - nodes/status - verbs: - # Needed for clearing NodeNetworkUnavailable flag. - - patch - # Calico stores some configuration information in node annotations. - - update - # Watch for changes to Kubernetes NetworkPolicies. - - apiGroups: ["networking.k8s.io"] - resources: - - networkpolicies - verbs: - - watch - - list - # Used by Calico for policy information. - - apiGroups: [""] - resources: - - pods - - namespaces - - serviceaccounts - verbs: - - list - - watch - # The CNI plugin patches pods/status. - - apiGroups: [""] - resources: - - pods/status - verbs: - - patch - # Calico monitors various CRDs for config. - - apiGroups: ["crd.projectcalico.org"] - resources: - - globalfelixconfigs - - felixconfigurations - - bgppeers - - globalbgpconfigs - - bgpconfigurations - - ippools - - ipreservations - - ipamblocks - - globalnetworkpolicies - - globalnetworksets - - networkpolicies - - networksets - - clusterinformations - - hostendpoints - - blockaffinities - - caliconodestatuses - verbs: - - get - - list - - watch - # Calico must create and update some CRDs on startup. - - apiGroups: ["crd.projectcalico.org"] - resources: - - ippools - - felixconfigurations - - clusterinformations - verbs: - - create - - update - # Calico must update some CRDs. - - apiGroups: ["crd.projectcalico.org"] - resources: - - caliconodestatuses - verbs: - - update - # Calico stores some configuration information on the node. - - apiGroups: [""] - resources: - - nodes - verbs: - - get - - list - - watch - # These permissions are only required for upgrade from v2.6, and can - # be removed after upgrade or on fresh installations. - - apiGroups: ["crd.projectcalico.org"] - resources: - - bgpconfigurations - - bgppeers - verbs: - - create - - update - # Keep TSEE CRDs separate to facilitate easier merges from projectcalico/calico - - apiGroups: ["crd.projectcalico.org"] - resources: - - licensekeys - - remoteclusterconfigurations - - tiers - - stagedglobalnetworkpolicies - - stagedkubernetesnetworkpolicies - - stagednetworkpolicies - - packetcaptures - verbs: - - get - - list - - watch - # Typha needs to be able to create tiers - - apiGroups: ["crd.projectcalico.org"] - resources: - - tiers - verbs: - - update - - create - # These permissions are required for Calico CNI to perform IPAM allocations. - - apiGroups: ["crd.projectcalico.org"] - resources: - - blockaffinities - - ipamblocks - - ipamhandles - verbs: - - get - - list - - create - - update - - delete - - apiGroups: ["crd.projectcalico.org"] - resources: - - ipamconfigs - verbs: - - get - # Block affinities must also be watchable by confd for route aggregation. - - apiGroups: ["crd.projectcalico.org"] - resources: - - blockaffinities - verbs: - - watch - # The Calico IPAM migration needs to get daemonsets. These permissions can be - # removed if not upgrading from an installation using host-local IPAM. - - apiGroups: ["apps"] - resources: - - daemonsets - verbs: - - get diff --git a/process/testing/winfv-felix/infra/installation-bgp.yaml b/process/testing/winfv-felix/infra/installation-bgp.yaml deleted file mode 100644 index d850e435119..00000000000 --- a/process/testing/winfv-felix/infra/installation-bgp.yaml +++ /dev/null @@ -1,17 +0,0 @@ -# This section includes base Calico installation configuration. -# For more information, see: https://docs.projectcalico.org/v3.17/reference/installation/api#operator.tigera.io/v1.Installation -apiVersion: operator.tigera.io/v1 -kind: Installation -metadata: - name: default -spec: - # Configures Calico networking. - calicoNetwork: - # Note: The ipPools section cannot be modified post-install. - bgp: Enabled - ipPools: - - blockSize: 26 - cidr: 192.168.0.0/16 - encapsulation: None - natOutgoing: Enabled - nodeSelector: all() diff --git a/process/testing/winfv-felix/infra/installation-vxlan.yaml b/process/testing/winfv-felix/infra/installation-vxlan.yaml deleted file mode 100644 index c8b341bd0f5..00000000000 --- a/process/testing/winfv-felix/infra/installation-vxlan.yaml +++ /dev/null @@ -1,17 +0,0 @@ -# This section includes base Calico installation configuration. -# For more information, see: https://docs.projectcalico.org/v3.17/reference/installation/api#operator.tigera.io/v1.Installation -apiVersion: operator.tigera.io/v1 -kind: Installation -metadata: - name: default -spec: - # Configures Calico networking. - calicoNetwork: - # Note: The ipPools section cannot be modified post-install. - bgp: Disabled - ipPools: - - blockSize: 26 - cidr: 192.168.0.0/16 - encapsulation: VXLAN - natOutgoing: Enabled - nodeSelector: all() diff --git a/process/testing/winfv-felix/infra/setup.sh b/process/testing/winfv-felix/infra/setup.sh deleted file mode 100755 index 9b84a9026c1..00000000000 --- a/process/testing/winfv-felix/infra/setup.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash -# Copyright (c) 2024 Tigera, Inc. All rights reserved. -# -# 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. - -set -o errexit -set -o nounset -set -o pipefail - -CURRENT_DIR=$(dirname "$0") -kubectl create ns demo --kubeconfig $1 - -kubectl apply -f $CURRENT_DIR/porter.yaml --kubeconfig $1 -kubectl apply -f $CURRENT_DIR/nginx.yaml --kubeconfig $1 -kubectl apply -f $CURRENT_DIR/client.yaml --kubeconfig $1 -kubectl apply -f $CURRENT_DIR/ingress.yaml --kubeconfig $1 -kubectl apply -f $CURRENT_DIR/egress.yaml --kubeconfig $1 diff --git a/process/testing/winfv-felix/rerun-fv.sh b/process/testing/winfv-felix/rerun-fv.sh new file mode 100755 index 00000000000..82e1c881a15 --- /dev/null +++ b/process/testing/winfv-felix/rerun-fv.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# Copyright (c) 2025 Tigera, Inc. All rights reserved. +# +# 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. + +# Recopy run-fv-felix.ps1 to Windows nodes and rerun the FV test. +# Useful for iterating on tests without rebuilding the cluster. + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${SCRIPT_DIR}" + +export LINUX_NODE_COUNT=1 +export WINDOWS_NODE_COUNT=1 + +: "${ASO_DIR:=${SCRIPT_DIR}/../aso}" + +. "${ASO_DIR}/export-env.sh" +. ${ASO_DIR}/vmss.sh info + +: "${GOMPLATE:=${ASO_DIR}/bin/gomplate}" +: "${FV_TYPE:?Error: FV_TYPE is not set}" + +: "${CALICO_HOME:=${SCRIPT_DIR}/../../..}" + +${GOMPLATE} --file ./run-fv-felix.ps1 --out ./windows/run-fv.ps1 + +${ASO_DIR}/scp-to-windows.sh 0 ./windows/run-fv.ps1 'c:\k\run-fv.ps1' +echo "Copied run-fv.ps1 to Windows node" + +# Kill any existing win-fv.exe processes and clean up old reports +echo "Killing any existing win-fv.exe processes..." +${WINDOWS_CONNECT_COMMAND} "Stop-Process -Name win-fv -Force -ErrorAction SilentlyContinue; Remove-Item -Path c:\\k\\report\\* -Force -ErrorAction SilentlyContinue" + +make -C "${CALICO_HOME}/felix" fv/win-fv.exe + +${ASO_DIR}/scp-to-windows.sh 0 ${CALICO_HOME}/felix/fv/win-fv.exe 'c:\k\win-fv.exe' +echo "Copied win-fv.exe to Windows node" + +# Run the FV test +echo "Running FV test..." +${WINDOWS_CONNECT_COMMAND} "c:\\k\\run-fv.ps1" + +# Copy report from Windows node +echo "Copying report from Windows node..." +rm -rf ./report || true +${ASO_DIR}/scp-from-windows.sh 0 'c:/k/report' ./report || true + +echo "FV test completed. Results in ./report/" +ls -la ./report/ || true diff --git a/process/testing/winfv-felix/restart-felix.ps1 b/process/testing/winfv-felix/restart-felix.ps1 deleted file mode 100644 index 517ec1628dd..00000000000 --- a/process/testing/winfv-felix/restart-felix.ps1 +++ /dev/null @@ -1,34 +0,0 @@ -# Copy this file to c:\CalicoWindows and run it to restart felix. -# Make sure calico-felix.exe has been copied over to c:\CalicoWindows already. -ipmo "$PSScriptRoot\libs\calico\calico.psm1" -Force - -. $PSScriptRoot\config.ps1 - -$ErrorActionPreference = 'SilentlyContinue' - -# Remove CalicoFelix service -Stop-Service CalicoFelix -Remove-FelixService - -sleep 5 - -# you may add code here to copy latest calico-felix.exe - -# Remove CalicoFelix logs and flow logs -rm $PSScriptRoot\logs\calico-felix*.log -rm c:\TigeraCalico\flowlogs\* - -# Install and start CalicoFelix again -Install-FelixService -Write-Host "Starting CalicoFelix..." -Start-Service CalicoFelix - -while ((Get-Service | where Name -Like 'CalicoFelix' | where Status -NE Running) -NE $null) { - Write-Host "Waiting for the Calico services to be running..." - Start-Sleep 1 -} - -Write-Host "Done, the Calico services are running:" -Get-Service | where Name -Like 'CalicoFelix' - -sleep 5 diff --git a/process/testing/winfv-felix/run-fv-cni-plugin.ps1 b/process/testing/winfv-felix/run-fv-cni-plugin.ps1 deleted file mode 100644 index 0fe6e87e6af..00000000000 --- a/process/testing/winfv-felix/run-fv-cni-plugin.ps1 +++ /dev/null @@ -1,133 +0,0 @@ -Param( - [parameter(Mandatory = $false)] $LinuxPIP="", - [parameter(Mandatory = $false)] $KubeVersion="", - [parameter(Mandatory = $false)] $OSVersion="", - [parameter(Mandatory = $false)] $ContainerRuntime="", - [parameter(Mandatory = $false)] $ContainerdVersion="" -) - -# Force powershell to run in 64-bit mode . -if ([Environment]::Is64BitProcess -eq $false) { - write-warning "This script requires PowerShell 64-bit, relaunching..." - if (\$myInvocation.Line) { - &"\$env:SystemRoot\sysnative\windowspowershell\v1.0\powershell.exe" -NonInteractive -NoProfile \$myInvocation.Line - }else{ - &"\$env:SystemRoot\sysnative\windowspowershell\v1.0\powershell.exe" -NonInteractive -NoProfile -file "\$(\$myInvocation.InvocationName)" \$args - } - exit \$lastexitcode -} - -#Setting up Environment Variable -Set-Item -Path env:KUBECONFIG -Value "C:\\k\\config" -Set-Item -Path env:KUBERNETES_MASTER -Value "https://${LinuxPIP}:6443" -Set-Item -Path env:ETCD_ENDPOINTS -Value "http://${LinuxPIP}:2389" -Set-Item -Path env:BIN -Value "C:\\k" -Set-Item -Path env:PLUGIN -Value "calico.exe" -Set-Item -Path env:DATASTORE_TYPE -Value "etcdv3" -Set-Item -Path env:CONTAINER_RUNTIME -Value "$ContainerRuntime" -Set-Item -Path env:CNI_VERSION -Value "0.3.0" -Set-Item -Path env:MAC_PREFIX -Value "0E-2A" -Set-Item -Path env:VSID -Value "4096" -Set-Item -Path env:WINDOWS_OS -Value "$OSVersion" -Set-Item -Path env:REPORT -Value "C:\\k\\report\\report-l2bridge.xml" - -# Install containerd if not present -if (!(Test-Path "$Env:ProgramFiles\containerd")) -{ - curl.exe -L https://github.com/containerd/containerd/releases/download/v$ContainerdVersion/containerd-$ContainerdVersion-windows-amd64.tar.gz -o c:\containerd-windows-amd64.tar.gz - cd c:\ - tar.exe xvf c:\containerd-windows-amd64.tar.gz | Out-Null - - # containerd tarball contains a bin folder containing the exe files. - # containerd expects to be in c:\Program Files - Copy-Item -Path "c:\bin" -Destination "$Env:ProgramFiles\containerd" -Recurse -Force - cd $Env:ProgramFiles\containerd\ - - # Generate and save the config file. - .\containerd.exe config default | Out-File config.toml -Encoding ascii - - # Register but do not start the service. - .\containerd.exe --register-service - - # Exclude containerd from Windows Defender Scans - Add-MpPreference -ExclusionProcess "$Env:ProgramFiles\containerd\containerd.exe" - - # Go back to script root - cd $PSScriptRoot -} - -if ($ContainerRuntime -EQ "containerd") -{ - if ((Get-Service | where Name -EQ 'containerd' | where Status -EQ Running) -EQ $null) - { - Start-Service -Name containerd - } - if ((Get-Service | where Name -EQ 'docker' | where Status -EQ Running) -NE $null) - { - Stop-Service -Name docker - } - - if ( "$OSVersion" -eq "Windows1809container" ) { - C:\bin\ctr.exe -n k8s.io images pull mcr.microsoft.com/windows/servercore:1809 | Out-Null - } elseif ( "$OSVersion" -eq "Windows1903container" ) { - C:\bin\ctr.exe -n k8s.io images pull mcr.microsoft.com/windows/servercore/insider:10.0.18317.1000 | Out-Null - } elseif ( "$OSVersion" -eq "Windows2022") { - ctr -n k8s.io images pull mcr.microsoft.com/windows/servercore:ltsc2022 | Out-Null - } - -} -else -{ - if ( "$OSVersion" -eq "Windows1809container" ) { - docker pull mcr.microsoft.com/windows/servercore:1809 - } elseif ( "$OSVersion" -eq "Windows1903container" ) { - docker pull mcr.microsoft.com/windows/servercore/insider:10.0.18317.1000 - } elseif ( "$OSVersion" -eq "Windows2022") { - docker pull mcr.microsoft.com/windows/servercore:ltsc2022 | Out-Null - } -} - -#create external network -if (!(Test-Path C:\\k\\helper.psm1)) -{ - Invoke-WebRequest -UseBasicParsing https://raw.githubusercontent.com/Microsoft/SDN/master/Kubernetes/windows/helper.psm1 -OutFile C:\\k\\helper.psm1 -} -ipmo C:\\k\\helper.psm1 -DownloadFile -Url "https://raw.githubusercontent.com/Microsoft/SDN/master/Kubernetes/windows/hns.psm1" -Destination C:\\k\\hns.psm1 -ipmo C:\\k\\hns.psm1 -DownloadFile -Url "https://github.com/Microsoft/SDN/raw/master/Kubernetes/flannel/l2bridge/cni/host-local.exe" -Destination C:\\k\\host-local.exe -#create external network -New-HNSNetwork -Type "L2Bridge" -AddressPrefix "10.244.10.0/24" -Gateway "10.244.10.1" -Name "External" -Verbose -#sleep -Start-Sleep -s 15 - -#create report directory to generate result -mkdir -force C:\\k\\report -#executes FV test and generate report in report/result.xml -cd C:\\k -& .\$WinFvExecutable --ginkgo.focus "l2bridge network" > C:\k\report\fv-test-l2bridge.log 2>&1 -if ( $LastExitCode -ne 0 ){ - echo $LastExitCode > c:\k\report\error-codes -} - -#Delete l2bridge external network -Get-HNSNetwork | ? name -like External | Remove-HNSNetwork -Start-Sleep -s 20 -#Create overlay external network -New-HNSNetwork -Type "Overlay" -AddressPrefix "192.168.255.0/30" -Gateway "192.168.255.1" -Name "External" -SubnetPolicies @(@{Type = "VSID"; VSID = 9999; }) -Verbose -Start-Sleep -s 20 - -Set-Item -Path env:REPORT -Value "C:\\k\\report\\report-overlay.xml" -& .\$WinFvExecutable --ginkgo.focus "overlay network" > C:\k\report\fv-test-overlay.log 2>&1 -if ( $LastExitCode -ne 0 ){ - echo $LastExitCode >> c:\k\report\error-codes -} - -cp .\cf-fv-log c:\k\report - -cat C:\\k\\report\\report-l2bridge.xml C:\\k\\report\\report-overlay.xml | sc C:\\k\\report\\report.xml -echo y | c:\k\pscp.exe -2 -i c:\k\linux-node.ppk c:\k\report\* ubuntu@${LinuxPIP}:/home/ubuntu/report/ -echo done-marker > done-marker -echo y | c:\k\pscp.exe -2 -i c:\k\linux-node.ppk done-marker ubuntu@${LinuxPIP}:/home/ubuntu/report/done-marker - - diff --git a/process/testing/winfv-felix/run-fv-full.ps1 b/process/testing/winfv-felix/run-fv-felix.ps1 similarity index 73% rename from process/testing/winfv-felix/run-fv-full.ps1 rename to process/testing/winfv-felix/run-fv-felix.ps1 index bb45f525ebb..828683e19c4 100644 --- a/process/testing/winfv-felix/run-fv-full.ps1 +++ b/process/testing/winfv-felix/run-fv-felix.ps1 @@ -1,15 +1,13 @@ Param( - [parameter(Mandatory = $false)] $LinuxPIP="", - [parameter(Mandatory = $false)] $KubeVersion="", - [parameter(Mandatory = $false)] $OSVersion="", - [parameter(Mandatory = $false)] $ContainerRuntime="", - [parameter(Mandatory = $false)] $FVType="", - [parameter(Mandatory = $false)] $WinFvExecutable="win-fv.exe" + [parameter(Mandatory = $false)] $LinuxPIP="{{.Env.LINUX_PIP}}", + [parameter(Mandatory = $false)] $KubeVersion="{{.Env.KUBE_VERSION}}", + [parameter(Mandatory = $false)] $FVType="{{.Env.FV_TYPE}}", + [parameter(Mandatory = $false)] $WinFvExecutable="win-fv.exe" ) $Root="c:\\CalicoWindows" -# Set HPC environment variable (capz provisioner default) +# Set HPC environment variable (ASO provisioner default) Set-Item -Path env:HPC -Value "true" # Force powershell to run in 64-bit mode . @@ -43,17 +41,26 @@ Set-Item -Path env:KUBERNETES_MASTER -Value "https://${LinuxPIP}:6443" Set-Item -Path env:ETCD_ENDPOINTS -Value "http://${LinuxPIP}:2389" Set-Item -Path env:BIN -Value "C:\\k" Set-Item -Path env:PLUGIN -Value "calico" +Set-Item -Path env:CONTAINER_RUNTIME -Value "containerd" Set-Item -Path env:MAC_PREFIX -Value "0E-2A" Set-Item -Path env:VSID -Value "4096" -Set-Item -Path env:WINDOWS_OS -Value "$OSVersion" +Set-Item -Path env:WINDOWS_OS -Value "Windows2022container" Set-Item -Path env:REPORT -Value "C:\\k\\report\\report-full.xml" # create report directory to generate result -mkdir -p C:\\k\\report +if (!(Test-Path c:\k\report)) +{ + mkdir -p c:\k\report +} + # executes FV test and generate report in report/result.xml cd C:\\k & .\win-fv.exe --ginkgo.focus "Windows" --ginkgo.v > C:\k\report\fv-test.log 2>&1 if ( $LastExitCode -ne 0 ){ echo $LastExitCode > c:\k\report\error-codes - cp c:\CalicoWindows\logs\*.log c:\k\report + if (Test-Path C:\CalicoWindows\logs) { + cp c:\CalicoWindows\logs\*.log c:\k\report + } } + +echo "All done" > c:\k\report\done-marker diff --git a/process/testing/winfv-felix/run-win-fv.sh b/process/testing/winfv-felix/run-win-fv.sh new file mode 100755 index 00000000000..f2787473915 --- /dev/null +++ b/process/testing/winfv-felix/run-win-fv.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +# Copyright (c) 2025 Tigera, Inc. All rights reserved. +# +# 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. + +# This is the entry point for Windows Felix FV test. + +set -e +set -x + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +export REPO_DIR="${SCRIPT_DIR}/../../.." +export ASO_DIR="${SCRIPT_DIR}/../aso" +export UTILS_DIR="${SCRIPT_DIR}/../util" + +. ${UTILS_DIR}/utils.sh + +: "${FV_TYPE:?Error: FV_TYPE is not set}" + +# Create cluster with one Linux node and one Windows node. +export LINUX_NODE_COUNT=1 +export WINDOWS_NODE_COUNT=1 + +# Create kubeadm cluster +pushd "${ASO_DIR}" +make setup-kubeadm +make install-calico +popd + +# Setup and run FV test +EXIT_CODE=0 +pushd "${SCRIPT_DIR}" +FV_TYPE=${FV_TYPE} ./setup-fv.sh | tee setupfv.log; pstat=${PIPESTATUS[0]} +if [[ $pstat != 0 ]]; then + EXIT_CODE=$pstat +fi + +# Copy report directory from windows node. +rm -r ./report || true +${ASO_DIR}/scp-from-windows.sh 0 'c:/k/report' ./report || true + +pause-for-debug + +# Get results and logs +ls -ltr ./report +mkdir -p /home/semaphore/fv.log +cp setupfv.log /home/semaphore/fv.log/ || true +cp ./report/*.log /home/semaphore/fv.log/ || true +cp ./pod-logs/*.log /home/semaphore/fv.log/ || true + +# Print relevant snippets from logs +log_regexps='(? /dev/null && \ +for log_file in /home/semaphore/fv.log/*.log; do + prefix="[$(basename ${log_file})]" + cat ${log_file} | iconv -f UTF-16 -t UTF-8 | sed 's/\r$//g' | grep --line-buffered --perl ${log_regexps} -B 2 -A 15 | sed 's/.*/'"${prefix}"' &/g' +done; + +# Search for the file indicates that the Windows node has completed the FV process +if [ ! -f ./report/done-marker ]; +then + echo "Windows node failed to complete the FV process." + exit 1 +fi + +# Search for error code file +if [ -f ./report/error-codes ] || [ "$EXIT_CODE" != 0 ]; +then + echo "Windows FV returned error(s)." + exit 1 +fi + +popd +echo "Windows Felix FV test completed." diff --git a/process/testing/winfv-felix/setup-fv-capz.sh b/process/testing/winfv-felix/setup-fv-capz.sh deleted file mode 100644 index 1e08cfcefab..00000000000 --- a/process/testing/winfv-felix/setup-fv-capz.sh +++ /dev/null @@ -1,240 +0,0 @@ -#!/bin/bash -# Copyright (c) 2024 Tigera, Inc. All rights reserved. -# -# 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. - -# This script sets up a k8s cluster with one Linux node and one Windows node and run windows FV test. -# Usage - OS Felix fv, following variables need to be set -# AZURE_SUBSCRIPTION_ID -# AZURE_TENANT_ID -# AZURE_CLIENT_ID -# AZURE_CLIENT_SECRET -# FV_TYPE -# - -set -o errexit -set -o nounset -set -o pipefail - -GIT_VERSION=$(git describe --tags --dirty --long --always --abbrev=12) -CALICO_HOME=$(cd "$(dirname $0)"/../../../; pwd) -CAPZ_LOCATION=$CALICO_HOME/process/testing/winfv-felix/capz -KUBECONFIG=$CALICO_HOME/process/testing/winfv-felix/capz/kubeconfig -KUBECTL=$CAPZ_LOCATION/bin/kubectl -KCAPZ="${KUBECTL} --kubeconfig=${KUBECONFIG}" -REPORT_DIR=$CALICO_HOME/process/testing/winfv-felix/report -SSH_OUTPUT_FILE=$REPORT_DIR/ssh_output.log -SEMAPHORE="${SEMAPHORE:="false"}" -export RAND=$(tr -dc a-z0-9 > $SSH_OUTPUT_FILE - $CAPZ_LOCATION/ssh-node.sh $WIN_NODE_IP 'ctr --namespace k8s.io images import --base-name calico/cni-windows c:\calico-cni-plugin-windows.tar --all-platforms' >> $SSH_OUTPUT_FILE -} - -function upload_fv(){ - if [[ $FV_TYPE == "cni-plugin" ]]; then - $CAPZ_LOCATION/scp-to-node.sh $WIN_NODE_IP $CALICO_HOME/process/testing/winfv-cni-plugin/run-cni-fv.ps1 c:\\run-cni-fv.ps1 - $CAPZ_LOCATION/scp-to-node.sh $WIN_NODE_IP $CALICO_HOME/cni-plugin/bin/windows/win-fv.exe c:\\k\\win-cni-fv.exe - elif [[ $FV_TYPE == "calico-felix" ]]; then - $CAPZ_LOCATION/scp-to-node.sh $WIN_NODE_IP $CALICO_HOME/process/testing/winfv-felix/run-felix-fv.ps1 c:\\run-felix-fv.ps1 - $CAPZ_LOCATION/scp-to-node.sh $WIN_NODE_IP $CALICO_HOME/felix/fv/win-fv.exe c:\\k\\win-felix-fv.exe - fi -} - -function prepare_windows_images(){ - make -C $CALICO_HOME/node image-windows WINDOWS_IMAGE=node-windows - make -C $CALICO_HOME/cni-plugin image-windows WINDOWS_IMAGE=cni-windows - - if [[ $WINDOWS_SERVER_VERSION == "windows-2022" ]]; then - CALICO_NODE_IMAGE="node-windows-$GIT_VERSION-ltsc2022.tar" - CALICO_CNI_IMAGE="cni-windows-$GIT_VERSION-ltsc2022.tar" - else - CALICO_NODE_IMAGE="node-windows-$GIT_VERSION-1809.tar" - CALICO_CNI_IMAGE="cni-windows-$GIT_VERSION-1809.tar" - fi -} - -function prepare_fv(){ - if [[ $FV_TYPE == "cni-plugin" ]]; then - make -C $CALICO_HOME/cni-plugin bin/windows/win-fv.exe - FV_RUN_CNI=$CALICO_HOME/process/testing/winfv-cni-plugin/run-cni-fv.ps1 - cp $CALICO_HOME/process/testing/winfv-cni-plugin/run-fv-cni-plugin.ps1 $FV_RUN_CNI - sed -i "s??${KUBE_VERSION}?g" $FV_RUN_CNI - sed -i "s??${LINUX_NODE_IP}?g" $FV_RUN_CNI - sed -i "s??${WINDOWS_SERVER_VERSION}?g" $FV_RUN_CNI - sed -i "s??containerd?g" $FV_RUN_CNI - sed -i "s??${CONTAINERD_VERSION}?g" $FV_RUN_CNI - sed -i "s?win-fv.exe?win-cni-fv.exe?g" $FV_RUN_CNI - elif [[ $FV_TYPE == "calico-felix" ]]; then - make -C $CALICO_HOME/felix fv/win-fv.exe - FV_RUN_FELIX=$CALICO_HOME/process/testing/winfv-felix/run-felix-fv.ps1 - cp $CALICO_HOME/process/testing/winfv-felix/run-fv-full.ps1 $FV_RUN_FELIX - sed -i "s??${KUBE_VERSION}?g" $FV_RUN_FELIX - sed -i "s??${LINUX_NODE_IP}?g" $FV_RUN_FELIX - sed -i "s??${WINDOWS_SERVER_VERSION}?g" $FV_RUN_FELIX - sed -i "s??containerd?g" $FV_RUN_FELIX - sed -i "s??${CONTAINERD_VERSION}?g" $FV_RUN_FELIX - sed -i "s??tigera-felix?g" $FV_RUN_FELIX - sed -i "s?win-fv.exe?win-felix-fv.exe?g" $FV_RUN_FELIX - fi - - upload_fv ${WIN_NODE_IP} -} - -function wait_for_nodes(){ - #Wait for calico-node-windows daemon set to update - sleep 30 - for i in $(seq 1 30); do - if [[ $(${KCAPZ} get ds calico-node-windows -n calico-system --no-headers | awk -v OFS='\t\t' '{print $6}') = "$WIN_NODE_COUNT" ]] ; then - echo "Calico Node Windows is ready" - return - fi - echo "Waiting for Calico Node Windows to update" - sleep 30 - done - echo "Node windows did not start" - exit 1 -} - -function update_windows_node(){ - upload_calico_images - ${KCAPZ} annotate ds -n calico-system calico-node-windows unsupported.operator.tigera.io/ignore="true" - ${KCAPZ} patch ds -n calico-system calico-node-windows --patch-file $CALICO_HOME/process/testing/winfv-felix/calico-node-windows.yaml -} - -function start_test_infra(){ - $CALICO_HOME/process/testing/winfv-felix/infra/setup.sh $KUBECONFIG - - #Wait for porter pod to be running on windows node - for i in $(seq 1 60); do - if [[ $(${KCAPZ} -n demo get pods porter --no-headers -o custom-columns=NAMESPACE:metadata.namespace,POD:metadata.name,PodIP:status.podIP,READY-true:status.containerStatuses[*].ready | awk -v OFS='\t\t' '{print $4}') = "true" ]] ; then - echo "Porter is ready after $i tries" - return - fi - echo "Waiting for porter to be ready" - sleep 30 - done - echo "Porter windows did not start after $i tries" - exit 1 -} - -function run_windows_fv(){ - if [[ $FV_TYPE == "cni-plugin" ]]; then - $CAPZ_LOCATION/ssh-node.sh $WIN_NODE_IP 'c:\\run-cni-fv.ps1' >> $SSH_OUTPUT_FILE - elif [[ $FV_TYPE == "calico-felix" ]]; then - $CAPZ_LOCATION/ssh-node.sh $WIN_NODE_IP 'c:\\run-felix-fv.ps1' >> $SSH_OUTPUT_FILE - fi -} - -function get_test_results(){ - # Get test logs - $CAPZ_LOCATION/scp-from-node.sh $WIN_NODE_IP c:\\k\\report\\* $REPORT_DIR - if [[ $SEMAPHORE == "false" ]]; then - cat $REPORT_DIR/fv-test.log - fi - - # Get logs from windows pod - ${KCAPZ} logs -n calico-system -l k8s-app=calico-node-windows -c uninstall-calico > $REPORT_DIR/win-uninstall-calico.log - ${KCAPZ} logs -n calico-system -l k8s-app=calico-node-windows -c install-cni > $REPORT_DIR/win-install-cni.log - ${KCAPZ} logs -n calico-system -l k8s-app=calico-node-windows -c node > $REPORT_DIR/win-node.log - ${KCAPZ} logs -n calico-system -l k8s-app=calico-node-windows -c felix > $REPORT_DIR/win-felix.log - - # Get logs from linux pod - ${KCAPZ} logs -n calico-system -l k8s-app=calico-node -c calico-node > $REPORT_DIR/linux-calico-node.log -} - -prepare_env -start_cluster -prepare_windows_images -update_windows_node -wait_for_nodes -prepare_fv -start_test_infra -run_windows_fv -get_test_results -shutdown_cluster diff --git a/process/testing/winfv-felix/setup-fv.sh b/process/testing/winfv-felix/setup-fv.sh index ca3831aa815..6b5d063b616 100755 --- a/process/testing/winfv-felix/setup-fv.sh +++ b/process/testing/winfv-felix/setup-fv.sh @@ -1,577 +1,119 @@ #!/bin/bash - -# This script sets up a k8s cluster with one Linux node and one Windows node and run windows FV test. -# Usage - CNI plugin fv -# copy calico.exe, calico-ipam.exe and win-fv.exe to $PWD and run this script. +# Copyright (c) 2025 Tigera, Inc. All rights reserved. # -# - OS Felix fv -# copy calico-felix.exe and win-fv.exe to $PWD and run this script. +# 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 # -# - EE Felix fv -# copy calico-felix.exe, win-fv.exe to $PWD -# copy license.yaml, ~/go/src/github.com/projectcalico/libcalico-go/config/crd to $PWD/infra/ee -# run this script - -# Replace following parameters with your own value -# Prefix for cluster name -NAME_PREFIX="${NAME_PREFIX:=${USER}-win-fv}" - -# Get K8S_VERSION variable from metadata.mk, error out if it cannot be found -SCRIPT_CURRENT_DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" -METADATAMK=${SCRIPT_CURRENT_DIR}/../../../metadata.mk -if [ -f ${METADATAMK} ]; then - K8S_VERSION_METADATA=$(grep K8S_VERSION ${METADATAMK} | cut -d "=" -f 2) - if [[ ! ${K8S_VERSION_METADATA} =~ ^v?[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "Failed to retrieve K8S_VERSION from ${METADATAMK}" - exit 1 - fi -else - echo "Failed to open ${METADATAMK}" - exit 1 -fi - -# Kubernetes version -KUBE_VERSION="${KUBE_VERSION:=${K8S_VERSION_METADATA#v}}" - -# AWS keypair name for nodes -WINDOWS_KEYPAIR_NAME="${WINDOWS_KEYPAIR_NAME:=AWS-key-pair}" - -# Private key file for above AWS keypair -WINDOWS_PEM_FILE="${WINDOWS_PEM_FILE:=$HOME/AWS-key-pair.pem}" - -# Private putty key file for above AWS keypair. Generated by puttygen from WINDOWS_PEM_FILE. -WINDOWS_PPK_FILE="${WINDOWS_PPK_FILE:=$HOME/AWS-key-pair.ppk}" - -# Client Public IP to allow RDP into windows nodes. -# Normally it is your laptop public ip. -RDP_SOURCE_CIDR="${RDP_SOURCE_CIDR:=0.0.0.0/0}" - -# Timeout value for windows FV in seconds. Default 60 minutes. -FV_TIMEOUT="${FV_TIMEOUT:=3600}" - -# Backend could be 'bgp' or 'vxlan'. -BACKEND="${BACKEND:=bgp}" - -# Specify windows server version [Windows1809container, Windows20H2container] -WINDOWS_OS="${WINDOWS_OS:=Windows2022container}" - -# Specify container runtime to use: docker or containerd. -CONTAINER_RUNTIME="${CONTAINER_RUNTIME:=docker}" - -# Specify containerd version to use. -CONTAINERD_VERSION="${CONTAINERD_VERSION:=1.6.35}" - -#specify description of AMI,this would be a filter to search AMI. -#we can create a Json file for description and use it. TODO??? -AMI_1809_DESCRIPTION="Microsoft Windows Server 2019 with Containers Locale English AMI provided by Amazon" -AMI_1903_DESCRIPTION="Tigera-Windows Server 1903 image" +# 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. -REGION="us-west-2" -CF_WIN_JSON_FILE="cloudformation-windows-server.json" +set -e -################################################### -# Do not modify the following code -################################################### +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${SCRIPT_DIR}" -# when set to 1, don't prompt for agreement to proceed -QUIET=${QUIET:=0} +: "${ASO_DIR:=${SCRIPT_DIR}/../aso}" +: "${UTILS_DIR:=${SCRIPT_DIR}/../util}" -# when set to 1, uninstall everything -CLEANUP=${CLEANUP:=0} +. "${UTILS_DIR}/utils.sh" +. "${ASO_DIR}/export-env.sh" +. ${ASO_DIR}/vmss.sh info -# when set to 1, setup fv -SETUP_FV=${SETUP_FV:=1} +: "${KUBECTL:=${ASO_DIR}/bin/kubectl}" +: "${GOMPLATE:=${ASO_DIR}/bin/gomplate}" +: "${FV_TYPE:?Error: FV_TYPE is not set}" -CLUSTER_NAME="${NAME_PREFIX}-fv" +: "${CALICO_HOME:=${SCRIPT_DIR}/../../..}" -CF_VPC_STACK_NAME="win-${CLUSTER_NAME}-vpc" -CF_FV_STACK_NAME="win-${CLUSTER_NAME}" +: "${KUBECONFIG:=${ASO_DIR}/kubeconfig}" -# Get FV_TYPE -if [ -f "./calico.exe" ]; then - echo "./calico.exe exists, type cni-plugin" - FV_TYPE="cni-plugin" - cp ./run-fv-cni-plugin.ps1 ./run-fv.ps1 -fi +GIT_VERSION=$(git describe --tags --dirty --long --always --abbrev=12) -if [ -f "./calico-felix.exe" ]; then - echo "./calico-felix.exe exists, type calico-felix" - FV_TYPE="calico-felix" - cp ./run-fv-full.ps1 ./run-fv.ps1 -fi +function upload_fv_scripts() { + mkdir -p ./windows + ${GOMPLATE} --file ./run-fv-felix.ps1 --out ./windows/run-fv.ps1 -if [ -f "./infra/ee/license.yaml" ]; then - echo "./infra/ee/license.yaml exists, type updated to tigera-felix" - FV_TYPE="tigera-felix" - sed -i "s?projectcalico.org/v3?crd.projectcalico.org/v1?g" ./infra/ee/license.yaml - if [ ! -d "./infra/ee/crd" ]; then - echo "./infra/ee/crd" does not exist. - exit 1 - fi -fi + ${ASO_DIR}/scp-to-windows.sh 0 ./windows/run-fv.ps1 'c:\k\run-fv.ps1' + echo "Copied run-fv.ps1 to Windows node" -function prompt_to_continue() { - if [ "$QUIET" -eq 0 ]; then - read -n 1 -p "Proceed? (y/n): " answer + make -C "$CALICO_HOME/felix" fv/win-fv.exe - echo - if [ "$answer" != "y" ] && [ "$answer" != "Y" ]; then - echo Exiting. - exit 1 - fi - fi - echo Proceeding ... + ${ASO_DIR}/scp-to-windows.sh 0 $CALICO_HOME/felix/fv/win-fv.exe 'c:\k\win-fv.exe' + echo "Copied win-fv.exe to Windows node" } -function check_settings() { - echo Settings: - echo ' CLUSTER_NAME='${CLUSTER_NAME} - echo ' KUBE_VERSION='${KUBE_VERSION} - echo ' FV_TYPE='${FV_TYPE} - - if [ "$CLEANUP" -eq 1 ]; then - echo - echo -n "About to uninstall. " - else - echo ' WINDOWS_PEM_FILE='${WINDOWS_PEM_FILE} - echo ' WINDOWS_KEYPAIR_NAME='${WINDOWS_KEYPAIR_NAME} - echo ' RDP_SOURCE_CIDR='${RDP_SOURCE_CIDR} - - echo - echo -n "About to install with home directory $HOME " +function upload_calico_images(){ + make -C "$CALICO_HOME/node" image-windows WINDOWS_IMAGE=node-windows + make -C "$CALICO_HOME/cni-plugin" image-windows WINDOWS_IMAGE=cni-windows - if [ ! -f "./docker_auth.json" ]; then - echo "./docker_auth.json" does not exist. - exit 1 - fi - - if [ ! -f "./win-fv.exe" ]; then - echo "./win-fv.exe" does not exist. - exit 1 - fi + if [[ $WINDOWS_SERVER_VERSION == "windows-2022" ]]; then + CALICO_NODE_IMAGE="node-windows-$GIT_VERSION-ltsc2022.tar" + CALICO_CNI_IMAGE="cni-windows-$GIT_VERSION-ltsc2022.tar" + else # $WINDOWS_SERVER_VERSION == "windows-2019" + CALICO_NODE_IMAGE="node-windows-$GIT_VERSION-ltsc2019.tar" + CALICO_CNI_IMAGE="cni-windows-$GIT_VERSION-ltsc2019.tar" fi - prompt_to_continue -} - -function install_aws_vpc_resources() { - echo - echo "Creating aws VPC resources with name ${CLUSTER_NAME} ..." - - aws cloudformation create-stack \ - --output json \ - --stack-name ${CF_VPC_STACK_NAME} \ - --parameters ParameterKey=ResourceName,ParameterValue="${CLUSTER_NAME}" \ - --template-body file://cloudformation-simple-vpc.json \ - --capabilities CAPABILITY_IAM > cf-log-vpc 2>&1 - - # Wait for the stack to finish provisioning - local STATUS=null - for i in `seq 1 21`; do - sleep 10 - STATUS=$(aws cloudformation describe-stacks \ - --output json \ - --stack-name ${CF_VPC_STACK_NAME} \ - | jq -r '.Stacks[].StackStatus') - echo Checking stack status, attempt ${i}, got ${STATUS} - if [ ${STATUS} == 'CREATE_COMPLETE' ]; then - echo "aws vpc resources created." - return 0 - fi - if [ ${STATUS} == 'ROLLBACK_COMPLETE' ] ; then - break - fi - done - - echo "Error running cloudformation script" - cat cf-log-vpc - aws cloudformation describe-stack-events \ - --output json \ - --stack-name ${CF_VPC_STACK_NAME} - exit 1 -} + ${ASO_DIR}/scp-to-windows.sh 0 "${CALICO_HOME}/node/dist/windows/${CALICO_NODE_IMAGE}" 'c:\calico-node-windows.tar' + ${ASO_DIR}/scp-to-windows.sh 0 "${CALICO_HOME}/cni-plugin/dist/windows/${CALICO_CNI_IMAGE}" 'c:\calico-cni-plugin-windows.tar' -function get_aws_vpc_info() { - echo - echo "Reading aws vpc information..." - sleep 10 - local CF_OUTPUT=$(aws cloudformation describe-stacks --output json --stack-name ${CF_VPC_STACK_NAME}) + #Import images from locally built images + ${WINDOWS_CONNECT_COMMAND} 'c:\bin\ctr.exe --namespace k8s.io images import --base-name calico/node-windows c:\calico-node-windows.tar --all-platforms' + ${WINDOWS_CONNECT_COMMAND} 'c:\bin\ctr.exe --namespace k8s.io images import --base-name calico/cni-windows c:\calico-cni-plugin-windows.tar --all-platforms' - HYBRID_VPC_ID=$(echo ${CF_OUTPUT} | jq -r '.Stacks[].Outputs[] | select(.OutputKey=="VpcId").OutputValue') - HYBRID_SUBNET_ID=$(echo ${CF_OUTPUT} | jq -r '.Stacks[].Outputs[] | select(.OutputKey=="SubnetId").OutputValue') - K8S_NODE_SGS=$(echo ${CF_OUTPUT} | jq -r '.Stacks[].Outputs[] | select(.OutputKey=="SgId").OutputValue') + ${KUBECTL} --kubeconfig="${KUBECONFIG}" annotate ds -n calico-system calico-node-windows unsupported.operator.tigera.io/ignore="true" + ${KUBECTL} --kubeconfig="${KUBECONFIG}" patch ds -n calico-system calico-node-windows --patch-file "${SCRIPT_DIR}/calico-node-windows.yaml" } -function get_ppk() { - CONFIG_CONTENT=$(cat ${WINDOWS_PPK_FILE}) - PPK_CONFIG="${CONFIG_CONTENT}" -} +function start_test_infra(){ + # Enable felix debug logging, wait for felixconfiguration to exist first + timeout --foreground 180 bash -c "while ! ${KUBECTL} --kubeconfig=${KUBECONFIG} wait felixconfiguration default --for=jsonpath='{.spec}' --timeout=30s; do sleep 5; done" + ${KUBECTL} --kubeconfig="${KUBECONFIG}" patch felixconfiguration default --type merge --patch='{"spec":{"logSeverityScreen":"Debug"}}' -function install_aws_fv_windows_resources() { - get_ppk + ${KUBECTL} --kubeconfig="${KUBECONFIG}" create ns demo + ${KUBECTL} --kubeconfig="${KUBECONFIG}" apply -f "${SCRIPT_DIR}/infra/" - echo - echo "Creating aws linux and windows instances..." - echo -e "\n\ -VPC: ${HYBRID_VPC_ID}\n\ -SUBNET: ${HYBRID_SUBNET_ID}\n\ -NODE_SGS: ${K8S_NODE_SGS}\n\ -KUBE_VERSION: ${KUBE_VERSION}\n" - - aws cloudformation create-stack \ - --output json \ - --stack-name ${CF_FV_STACK_NAME} \ - --parameters ParameterKey=PPKConfig,ParameterValue="${PPK_CONFIG}" \ - ParameterKey=WindowsOS,ParameterValue="${WINDOWS_OS}" \ - ParameterKey=BackEnd,ParameterValue="calico-${BACKEND}" \ - ParameterKey=KubeVersion,ParameterValue="${KUBE_VERSION}" \ - ParameterKey=CurrentVPC,ParameterValue=${HYBRID_VPC_ID} \ - ParameterKey=CurrentSubnet,ParameterValue=${HYBRID_SUBNET_ID} \ - ParameterKey=CurrentNodesSg,ParameterValue=${K8S_NODE_SGS} \ - ParameterKey=KeyName,ParameterValue=${WINDOWS_KEYPAIR_NAME} \ - ParameterKey=SourceCidrForRDP,ParameterValue=${RDP_SOURCE_CIDR} \ - --template-body file://${CF_WIN_JSON_FILE} \ - --capabilities CAPABILITY_IAM > cf-log 2>&1 - - # Wait for the stack to finish provisioning - local STATUS=null - for i in `seq 1 21`; do - sleep 10 - STATUS=$(aws cloudformation describe-stacks \ - --output json \ - --stack-name ${CF_FV_STACK_NAME} \ - | jq -r '.Stacks[].StackStatus') - echo Checking stack status, attempt ${i}, got ${STATUS} - if [ ${STATUS} == 'CREATE_COMPLETE' ] ; then - echo "aws windows instances created." - return 0 + #Wait for porter pod to be running on windows node + for i in $(seq 1 40); do + if [[ $(${KUBECTL} --kubeconfig="${KUBECONFIG}" -n demo get pods porter --no-headers -o custom-columns=NAMESPACE:metadata.namespace,POD:metadata.name,PodIP:status.podIP,READY-true:status.containerStatuses[*].ready | awk -v OFS='\t\t' '{print $4}') = "true" ]] ; then + echo "Porter is ready after $i tries" + return fi + echo "Waiting for porter to be ready" + sleep 30 done - - echo "Error running cloudformation script" - cat cf-log - aws cloudformation describe-stack-events \ - --output json \ - --stack-name ${CF_FV_STACK_NAME} - + echo "Porter windows did not start after $i tries" exit 1 } -function ensure_windows_pem_file() { - # Check if key is in PEM format - AWS needs PEM formatted keys to get Windows - # passwords. If it isn't, make a copy, convert it, and use that instead. - if grep -q "BEGIN OPENSSH PRIVATE KEY" ${WINDOWS_PEM_FILE}; then - echo "WINDOWS_PEM_FILE (${WINDOWS_PEM_FILE}) is not a PEM file, making a copy and using it instead." - cp ${WINDOWS_PEM_FILE} ${WINDOWS_PEM_FILE}.copy - ssh-keygen -p -N "" -m PEM -f ${WINDOWS_PEM_FILE}.copy - WINDOWS_PEM_FILE="${WINDOWS_PEM_FILE}.copy" - fi -} - -function wait_for_instance_password() { - for i in `seq 1 60`; do - sleep 10 - echo Trying to get windows password, attempt ${i} - # Turn off trace so that the password is not echoed. - set +x - password=$(aws ec2 get-password-data --instance-id ${WINDOWS_INSTANCE} --priv-launch ${WINDOWS_PEM_FILE} | jq .PasswordData) - set -x - - if [ "${password}" != "" ] && [ "${password}" != "null" ]; then - echo Got windows password - return 0 - fi - done - return 1 -} - -function show_all_instances() { - echo - echo "Reading aws windows FV instances information..." - echo - sleep 10 - local CF_OUTPUT=$(aws cloudformation describe-stacks --output json --stack-name ${CF_FV_STACK_NAME}) - - LINUX_INSTANCE=$(echo ${CF_OUTPUT} | jq -r '.Stacks[].Outputs[] | select(.OutputKey=="InstanceId0").OutputValue') - LINUX_EIP=$(echo ${CF_OUTPUT} | jq -r '.Stacks[].Outputs[] | select(.OutputKey=="InstanceEIP0").OutputValue') - LINUX_PIP=$(echo ${CF_OUTPUT} | jq -r '.Stacks[].Outputs[] | select(.OutputKey=="InstancePIP0").OutputValue') - - MASTER_CONNECT_COMMAND="ssh -i ${WINDOWS_PEM_FILE} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no ubuntu@${LINUX_EIP}" - if [ "$SETUP_FV" -eq 1 ]; then - setup_fv - # Copy config to winfv folder and make ubuntu the owner. - ${MASTER_CONNECT_COMMAND} sudo cp /root/.kube/config /home/ubuntu/winfv - ${MASTER_CONNECT_COMMAND} sudo chown ubuntu:ubuntu /home/ubuntu/winfv/config - fi - - WINDOWS_INSTANCE=$(echo ${CF_OUTPUT} | jq -r '.Stacks[].Outputs[] | select(.OutputKey=="InstanceId1").OutputValue') - WINDOWS_EIP=$(echo ${CF_OUTPUT} | jq -r '.Stacks[].Outputs[] | select(.OutputKey=="InstanceEIP1").OutputValue') - WINDOWS_PIP=$(echo ${CF_OUTPUT} | jq -r '.Stacks[].Outputs[] | select(.OutputKey=="InstancePIP1").OutputValue') - - ensure_windows_pem_file - wait_for_instance_password - - # Turn off trace so that the password is not echoed. - set +x - PASSWORD=$(aws ec2 get-password-data --instance-id ${WINDOWS_INSTANCE} --priv-launch-key $WINDOWS_PEM_FILE | awk '{print $2}') - set -x - - echo "Linux node: ubuntu@${LINUX_EIP}" - echo "Windows node: ${WINDOWS_EIP}" - echo "For credentials and other info, attach into the running job and go to ~/calico/process/testing/winfv-felix and `cat connect`" - - CONNECT_FILE="connect" - echo - echo - echo - echo "-------------Connect to Linux Master Instances--------" >> ${CONNECT_FILE} - echo "${MASTER_CONNECT_COMMAND}" >> ${CONNECT_FILE} - echo "PrivateIP: ${LINUX_PIP}" >> ${CONNECT_FILE} - echo - echo "-------------Connect to Windows Instances-------------" >> ${CONNECT_FILE} - echo "InstanceID:${WINDOWS_INSTANCE} RDP://${WINDOWS_EIP} user: Administrator" >> ${CONNECT_FILE} - # Turn off trace so that the password is not echoed. - set +x - echo "password: ${PASSWORD}" >> ${CONNECT_FILE} - echo "PrivateIP: ${WINDOWS_PIP}" >> ${CONNECT_FILE} - set -x -} - -function wait_for_docker_installed() { - echo "Waiting for docker to have been installed on linux node" - for i in `seq 1 30`; do - sleep 2 - ${MASTER_CONNECT_COMMAND} docker images - - if [ $? -eq 0 ]; then - echo "Docker installed. Ready to setup linux node for FV" - return 0 - fi - done - return 1 -} - -function wait_for_containerd_installed() { - echo "Waiting for containerd to have been installed on linux node" - for i in `seq 1 30`; do - sleep 2 - ${MASTER_CONNECT_COMMAND} crictl images - - if [ $? -eq 0 ]; then - echo "containerd installed. Ready to setup linux node for FV" - return 0 - fi - done - return 1 -} - -function master_scp() { - local file=$1 - scp -i ${WINDOWS_PEM_FILE} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $file ubuntu@${LINUX_EIP}:/home/ubuntu -} - -function setup_linux() { - - # Prepare docker login script. This allow linux node to pull gcr.io images. - echo -n "Logging in to gcr.io ... " - - echo '#!/bin/bash' > gcr_login.sh - echo 'docker login -u _json_key -p "$(cat docker_auth.json)" https://gcr.io' >> gcr_login.sh - chmod +x gcr_login.sh - - # Prepare wait for result script. This allow linux node to wait until it gets FV result from windows. - cat << EOF > wait-report.sh -#!/bin/bash -echo "Wait for FV report... Timeout: ${FV_TIMEOUT} seconds" -for i in \$(seq 1 $FV_TIMEOUT) -do - if [ -f /home/ubuntu/report/done-marker ]; then - echo "FV result is done." - exit 0 - fi - if [ \$(( \$i % 20 )) -eq 0 ]; then - echo "checking for FV result..." - fi - sleep 1 -done -EOF - - chmod +x wait-report.sh - - # set parameters for run-fv.ps1 - sed -i "s??${KUBE_VERSION}?g" run-fv.ps1 - sed -i "s??${LINUX_PIP}?g" run-fv.ps1 - sed -i "s??${WINDOWS_OS}?g" run-fv.ps1 - sed -i "s??${CONTAINER_RUNTIME}?g" run-fv.ps1 - sed -i "s??${CONTAINERD_VERSION}?g" run-fv.ps1 - sed -i "s??${FV_TYPE}?g" run-fv.ps1 - - ${MASTER_CONNECT_COMMAND} mkdir -p winfv - # Copy every files under current directory to linux node. - # This include docker-auth, windows binaries, shell scripts. - scp -i ${WINDOWS_PEM_FILE} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -r ./* ubuntu@${LINUX_EIP}:/home/ubuntu/winfv/ - - ${MASTER_CONNECT_COMMAND} ./gcr_login.sh - ${MASTER_CONNECT_COMMAND} mkdir /home/ubuntu/report - - echo "done." -} -function setup_kubeadm_cluster(){ - scp -i ${WINDOWS_PEM_FILE} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no ./create_kubeadm_cluster.sh ubuntu@${LINUX_EIP}:/home/ubuntu/ - ${MASTER_CONNECT_COMMAND} sudo chmod +x /home/ubuntu/create_kubeadm_cluster.sh - ${MASTER_CONNECT_COMMAND} sudo bash /home/ubuntu/create_kubeadm_cluster.sh ${KUBE_VERSION} ${BACKEND} ${FV_TYPE} -} -function setup_fv() { - wait_for_docker_installed - wait_for_containerd_installed - - setup_linux - setup_kubeadm_cluster - #create etcd manually with http protocol - LOCAL_IP_ENV=${LINUX_PIP} - ETCD_CONTAINER=quay.io/coreos/etcd:v3.4.6 - ${MASTER_CONNECT_COMMAND} docker run --detach -p 2389:2389 --name calico-etcd ${ETCD_CONTAINER} etcd --advertise-client-urls "http://${LOCAL_IP_ENV}:2389,http://127.0.0.1:2389,http://${LOCAL_IP_ENV}:8001,http://127.0.0.1:8001" --listen-client-urls "http://0.0.0.0:2389,http://0.0.0.0:8001" - - echo - ${MASTER_CONNECT_COMMAND} docker ps -a - echo - echo "Setup linux is done." - echo - echo -} - -function output_connection() { - echo ' CLUSTER_NAME='${CLUSTER_NAME} - echo ' KUBE_VERSION='${KUBE_VERSION} +function run_windows_fv(){ + ${WINDOWS_CONNECT_COMMAND} "c:\\k\\run-fv.ps1" echo - SETUP_FV=0 - show_all_instances } -function parse_options() { - usage() { - cat < ./pod-logs/win-uninstall-calico.log || echo "Failed to get logs for win-uninstall-calico" + ${KUBECTL} --kubeconfig="${KUBECONFIG}" logs -n calico-system -l k8s-app=calico-node-windows -c install-cni > ./pod-logs/win-install-cni.log || echo "Failed to get logs for win-install-cni" + ${KUBECTL} --kubeconfig="${KUBECONFIG}" logs -n calico-system -l k8s-app=calico-node-windows -c node > ./pod-logs/win-node.log || echo "Failed to get logs for win-node" + ${KUBECTL} --kubeconfig="${KUBECONFIG}" logs -n calico-system -l k8s-app=calico-node-windows -c felix > ./pod-logs/win-felix.log || echo "Failed to get logs for win-felix" - local OPTIND - while getopts "hoqu" opt; do - case ${opt} in - o ) - output_connection - exit - ;; - q ) QUIET=1;; - u ) CLEANUP=1;; - h ) usage;; - \? ) usage;; - esac - done - shift $((OPTIND -1)) + # Get logs from linux pod + ${KUBECTL} --kubeconfig="${KUBECONFIG}" logs -n calico-system -l k8s-app=calico-node -c calico-node > ./pod-logs/linux-calico-node.log || echo "Failed to get logs for linux-calico-node" } -function uninstall_cluster() { - echo - echo "Removing windows and linux instances, please wait..." - - aws cloudformation delete-stack --output json --stack-name ${CF_FV_STACK_NAME} - - # Wait for the stack to finish provisioning - local STATUS=null - for i in `seq 1 21`; do - sleep 1 - STATUS=$(aws cloudformation describe-stacks \ - --output json \ - --stack-name ${CF_FV_STACK_NAME} \ - | jq -r '.Stacks[].StackStatus') - if [ "${STATUS}" == 'DELETE_COMPLETE' ] ; then - echo "aws windows instances deleted." - return 0 - fi - done - - echo - echo "Removing vpc resource, please wait..." - - aws cloudformation delete-stack --output json --stack-name ${CF_VPC_STACK_NAME} - - # Wait for the stack to finish provisioning - local STATUS=null - for i in `seq 1 21`; do - sleep 1 - STATUS=$(aws cloudformation describe-stacks \ - --output json \ - --stack-name ${CF_VPC_STACK_NAME} \ - | jq -r '.Stacks[].StackStatus') - if [ "${STATUS}" == 'DELETE_COMPLETE' ] ; then - echo "aws vpc resources deleted." - return 0 - fi - done - - rm -f cf-log* - echo "FV cluster removed." -} - -# function to lookup AMI, if not present then update with latest one. -function lookup_win_cf_ami() { - # get ami id from cloudformation - local WIN_AMI=`cat ${CF_WIN_JSON_FILE} | jq --arg region "${REGION}" --arg windows_os "${WINDOWS_OS}" -r '.Mappings.AWSRegion2AMI[$region][$windows_os]'` - - # windows AMI name filter strings - local AMI_1809_NAME="Windows_Server-2019-English-Core-Base-*" - local AMI_2022_NAME="Windows_Server-2022-English-Core-Base-*" - - AMI_NAME="" - if [ $WINDOWS_OS == "Windows1809container" ];then - AMI_NAME="${AMI_1809_NAME}" - elif [ $WINDOWS_OS == "Windows2022container" ];then - AMI_NAME="${AMI_2022_NAME}" - fi - - # search latest AMI based on AMI name - local AMI=`aws ec2 describe-images --owners self amazon --filters "Name=name,Values=${AMI_NAME}" --query 'sort_by(Images, &CreationDate)[].ImageId' --output json | jq '(reverse)[0]'` - REPLACE_AMI="${AMI//\"}" - - # check if we get empty AMI (in case of 1903 for now), then continue with AMI in CF json file - if [ "${REPLACE_AMI}" != "" ] && [ "${REPLACE_AMI}" != "null" ] && [ "${REPLACE_AMI}" != "${WIN_AMI}" ];then - echo "ec2 $WINDOWS_OS AMI ID $WIN_AMI updated to ${REPLACE_AMI}" - sed -i "s?\"${WIN_AMI}\"?\"${REPLACE_AMI}\"?g" ${CF_WIN_JSON_FILE} - else - echo "No AMI update, continue with $WIN_AMI" - fi -} - -# -# Main() -# -parse_options "$@" - -check_settings - -if [ "$CLEANUP" -eq 1 ]; then - uninstall_cluster - exit -fi -echo "[INFO] lookup for update on windows AMI" -lookup_win_cf_ami - -echo "[INFO] going to create a FV cluster" - -install_aws_vpc_resources - -get_aws_vpc_info - -install_aws_fv_windows_resources - -show_all_instances - -echo -echo "All done." +# Main execution +upload_fv_scripts +upload_calico_images +start_test_infra +run_windows_fv +get_logs diff --git a/release/Makefile b/release/Makefile index e59ef6c8a3b..861bbc52463 100644 --- a/release/Makefile +++ b/release/Makefile @@ -6,18 +6,22 @@ include ../lib.Makefile export ORGANIZATION export GIT_REPO -export GIT_REPO_SLUG +export RELEASE_BRANCH_PREFIX +export DEV_TAG_SUFFIX export DEV_REGISTRIES +export OPERATOR_ORGANIZATION +export OPERATOR_GIT_REPO export OPERATOR_BRANCH .PHONY: build build: bin/release clean: - @rm -rf ./bin - @rm -rf ./output ./_output ./report ./tmp ./*.log + @rm -rf ./bin ./output ./_output ./report ./tmp + @find ../ -name '*release*.log' -delete + @find . -name '*.received.*' -delete -bin/release: $(shell find . -name "*.go") +bin/release: $(shell find . -name "*.go" -or -name "*.gotmpl") @mkdir -p bin && \ $(call build_binary, ./cmd, bin/release) @@ -40,10 +44,14 @@ ci: static-checks ut .PHONY: hashrelease hashrelease-build hashrelease-publish hashrelease: hashrelease-build hashrelease-publish +# Ensures helm is in the bin dir relative to repo root +../bin/helm: + @$(MAKE) -C ../ helm + hashrelease-build: bin/release var-require-all-GITHUB_TOKEN @bin/release hashrelease build -hashrelease-publish: bin/release var-require-all-GITHUB_TOKEN +hashrelease-publish: bin/release var-require-all-GITHUB_TOKEN ../bin/helm @bin/release hashrelease publish hashrelease-svr-gc: bin/release diff --git a/release/RELEASING.md b/release/RELEASING.md index 75fe79d0769..224d6a23183 100644 --- a/release/RELEASING.md +++ b/release/RELEASING.md @@ -1,74 +1,54 @@ # Releasing Calico -> **NOTE:** These instructions apply only to Calico versions v3.21 or greater. +> **NOTE:** These instructions apply only to this branch. > For older releases, refer to the instructions in the corresponding `release-vX.Y` branch. ## Table of contents -1. [Prerequisites](#1-prerequisites) -1. [Verify the code is ready for release](#2-verify-the-code-is-ready-for-release) -1. [Create a release branch](#3-create-a-release-branch) -1. [Performing a release](#4-performing-a-release) -1. [Post-release](#5-post-release) +- [Releasing Calico](#releasing-calico) + - [Table of contents](#table-of-contents) + - [1. Prerequisites](#1-prerequisites) + - [2. Verify the code is ready for release](#2-verify-the-code-is-ready-for-release) + - [3. Create a release branch](#3-create-a-release-branch) + - [Setting up the branch](#setting-up-the-branch) + - [Updating milestones for the new branch](#updating-milestones-for-the-new-branch) + - [4. Performing a release](#4-performing-a-release) + - [4.a Create a temporary branch for this release against origin](#4a-create-a-temporary-branch-for-this-release-against-origin) + - [4.b Build and publish the repository in Semaphore](#4b-build-and-publish-the-repository-in-semaphore) + - [4.c Build and publish tigera/operator](#4c-build-and-publish-tigeraoperator) + - [4.d Publish the release on Github](#4d-publish-the-release-on-github) + - [4.e Update the docs with the new version](#4e-update-the-docs-with-the-new-version) + - [5. Post-release](#5-post-release) + - [Update milestones](#update-milestones) + - [Post-release verification](#post-release-verification) + - [Update API repository](#update-api-repository) +- [Release notes](#release-notes) ## 1. Prerequisites -Generally, the release environment is managed through Semaphore and will already meet these requirements. However, if you must run -the process locally then the following requirements must be met. +Generally, the release environment is managed through Semaphore and will already meet these requirements. -To publish Calico, you need **the following permissions**: - -- Write access to the projectcalico/calico GitHub repository. You can create a [personal access token](https://github.com/settings/tokens) for GitHub and export it as the `GITHUB_TOKEN` env var (for example by adding it to your `.profile`). - -- Push access to the Calico DockerHub repositories. Assuming you've been granted access by an admin: - - ```sh - docker login - ``` - -- Push access to the Calico quay.io repositories. Assuming you've been granted access by an admin: - - ```sh - docker login quay.io - ``` - -- Push access to the gcr.io/projectcalico-org repositories. **Note:** Some of the repos do not yet support credential helpers, you must use one of the token-based logins. For example, assuming you've been granted access, this will configure a short-lived auth token: +However, parts of the process will still require that the following requirements must be met. - ```sh - gcloud auth print-access-token | docker login -u oauth2accesstoken --password-stdin https://gcr.io - ``` +To publish Calico, you need **the following permissions**: +- Write access to the projectcalico/calico GitHub repository. + - Generate a GitHub personal access token and export it as `GITHUB_TOKEN` in your environment. +- Push access to the following repositories + - Calico DockerHub + - Calico Quay + - Tigera Quay + - projectcalico-org GAR - You must be a member of the Project Calico team on Launchpad, and have uploaded a GPG identity to your account, for which you have the secret key. -- You must be able to access binaries.projectcalico.org. - -- To publish the helm release to the repo, you’ll need an AWS helm profile: - Add this to your `~/.aws/config` - - ```sh - [profile helm] - role_arn = arn:aws:iam:::role/CalicoDevHelmAdmin - mfa_serial = arn:aws:iam:::mfa/myusername - source_profile = default - region = us-east-2 - ``` - - Your user will need permission for assuming the helm admin role in the production account. - -You'll also need **several GB of disk space**. - -Finally, the release process **assumes that your repos are checked out with name `origin`** as the git remote -for the main Calico repo. - ## 2. Verify the code is ready for release -To verify that the code and GitHub are in the right state for releasing the chosen version. +To verify that the code and GitHub are in the right state for releasing the chosen version (vX.Y.Z). -1. [Make sure the Milestone is empty](https://github.com/projectcalico/calico/pulls?q=is%3Aopen+is%3Apr+milestone%3A%22Calico+v3.21.3%22) either by merging PRs, or kicking them out of the Milestone. -1. [Make sure that there are no pending cherry-pick PRs](https://github.com/projectcalico/calico/pulls?q=is%3Aopen+is%3Apr+label%3Acherry-pick-candidate+) relevant to the release. -1. [Make sure there are no pending PRs which need docs](https://github.com/projectcalico/calico/pulls?q=is%3Apr+label%3Adocs-pr-required+is%3Aclosed+milestone%3A%22Calico+v3.21.3%22) for this release. -1. [Make sure each PR with a release note is within a Milestone](https://github.com/projectcalico/calico/pulls?q=is%3Apr+label%3Arelease-note-required+is%3Aclosed+milestone%3A%22Calico+v3.21.3%22+). -1. [Make sure CI is passing](https://tigera.semaphoreci.com/projects/calico) for the target release branch. +1. [Make sure the Milestone is empty](https://github.com/projectcalico/calico/pulls?q=is:open+is:pr+milestone:%22Calico+vX.Y.Z%22) either by merging PRs, or kicking them out of the Milestone. +2. [Make sure that there are no pending cherry-pick PRs](https://github.com/projectcalico/calico/pulls?q=is%3Aopen+is%3Apr+label%3Acherry-pick-candidate+) relevant to the release. +3. [Make sure CI is passing](https://tigera.semaphoreci.com/projects/calico) for the target release branch. +4. Make sure release notes has been drafted and reviewed. See [Release notes](#release-notes) for more information. Check the status of each of these items daily in the week leading up to the release. @@ -80,45 +60,17 @@ When starting development on a new minor release, the first step is to create a ### Setting up the branch -1. Create a new branch off of the latest master and publish it, along with a dev tag for the next release. - - ```sh - git checkout master && git pull origin master - ``` - - ```sh - make create-release-branch - ``` - -1. Checkout the newly created branch. - - ```sh - git checkout release-vX.Y - ``` - -1. Update manifests to use the new release branch instead of master. Update versions in the following files: - - - charts/calico/values.yaml - - charts/tigera-operator/values.yaml - - metadata.mk (OPERATOR_BRANCH) - - Then, run manifest generation +Create a new branch off of the latest master and publish it, along with a dev tag for the next release. +The new release branch is typically `release-vX.Y` where `X.Y` is the new minor version and all the files +in the repository are updated to reflect the new version. - ```sh - make generate - ``` - - Commit your changes - - ```sh - Update manifests for release-vX.Y - ``` - - Then, push your changes to the branch. + ```sh + git checkout master && git pull origin master + ``` - ```sh - git push origin release-vX.Y - ``` + ```sh + make create-release-branch + ``` ### Updating milestones for the new branch @@ -128,6 +80,8 @@ Once a new branch is cut, we need to ensure a new milestone exists to represent 1. Create a new release of the form `Calico vX.Y+1.0`. Leave the existing `Calico vX.Y.0` milestone open. +1. Move any open PRs in the `Calico vX.Y.0` milestone into the new milestone `Calico vX.Y+1.0`. + ## 4. Performing a release ### 4.a Create a temporary branch for this release against origin @@ -161,10 +115,13 @@ Once a new branch is cut, we need to ensure a new milestone exists to represent git add release-notes/-release-notes.md ``` + > [!TIP] + > You likely have a draft from [step 2.4](#2-verify-the-code-is-ready-for-release) that you can edit and finalize. + 1. Commit your changes. For example: ```sh - git commit -m "Updates for vX.Y.Z" + git commit -m "build: vX.Y.Z release" ``` 1. Push the branch to `github.com/projectcalico/calico` and create a pull request. Get it reviewed and ensure it passes CI before moving to the next step. @@ -180,7 +137,11 @@ Wait for this job to complete before moving on to the next step. ### 4.c Build and publish tigera/operator -Follow [the tigera/operator release instructions](https://github.com/tigera/operator/blob/master/RELEASING.md). +Follow the tigera/operator release instructions in the Operator version (vA.B.C) corresponding to the release + +```txt +https://github.com/tigera/operator/blob/release-vA.B/RELEASING.md +``` ### 4.d Publish the release on Github @@ -190,23 +151,23 @@ Go to the [Calico release page](https://github.com/projectcalico/calico/releases 1. Merge the PR branch created in step 4.a - `build-vX.Y.Z` and delete the branch from the repository. + > [!WARNING] + > Do not merge using "Squash and merge" as this will lose the version bump commit history. + ## 5. Post-release ### Update milestones 1. Go to the [Calico milestones page](https://github.com/projectcalico/calico/milestones) -1. Open a new milestone of the form `Calico vX.Y.Z` for the next patch release in the series if it does not yet exist. +1. Open a new milestone of the form `Calico vX.Y.Z+1` for the next patch release in the series if it does not yet exist. 1. Close out the milestone for the release that was just published, moving any remaining open issues and PRs to the newly created milestone. ### Post-release verification -1. Run the post-release checks. The release validation checks will run - they check for the presence of all the required binaries tarballs, tags, etc. - - ```sh - make VERSION=... FLANNEL_VERSION=... OPERATOR_VERSION=... postrelease-checks - ``` +1. Using the [post-release task in Semaphore](https://tigera.semaphoreci.com/projects/calico/schedulers/5eedc2d9-fbbb-4595-b7a5-50fac8068cf2/just_run), + Specify either the tag `vX.Y.Z` or the `build-vX.Y.Z` branch and click "Run". 1. Check the output of the tests - if any test failed, dig in and understand why. @@ -268,9 +229,9 @@ Release notes for a Calico release contain notable changes across Calico reposit - It is in the correct `Calico vX.Y.Z` GitHub milestone - It has the `release-note-required` label - - It has one or more release notes included in the description (Optional). + - It has one or more release notes included in the description. -1. Run the following command to collect all release notes for the given version. +2. Run the following command to collect all release notes for the given version. ```sh make release-notes @@ -278,17 +239,16 @@ Release notes for a Calico release contain notable changes across Calico reposit A file called `release-notes/-release-notes.md` will be created with the raw release note content. -1. Edit the generated file. +3. Edit the generated file. - The release notes should be edited to highlight a few major enhancements and their value to the user. Bug fixes and other changes should be summarized in a - bulleted list at the end of the release notes. Any limitations or incompatible changes in behavior should be explicitly noted. + The release notes should be edited to highlight a few major enhancements and their value to the user. Bug fixes and other changes should be summarized in a bulleted list at the end of the release notes. Any breaking changes, limitations or incompatible changes in behavior should be explicitly noted. Consistent release note formatting is important. Here are some examples for reference: - - [Example release notes for a major/minor release](https://github.com/projectcalico/calico/blob/v3.28.0/release-notes/v3.28.0-release-notes.md) - - [Example release notes for a patch release](https://github.com/projectcalico/calico/blob/v3.28.2/release-notes/v3.28.1-release-notes.md) + - [Example release notes for a major/minor release](https://github.com/projectcalico/calico/blob/release-v3.30/release-notes/v3.30.0-release-notes.md) + - [Example release notes for a patch release](https://github.com/projectcalico/calico/blob/release-v3.30/release-notes/v3.30.4-release-notes.md) -1. Add the generated file to git. +4. Add the generated file to git. ```sh git add release-notes/ diff --git a/release/cmd/branch.go b/release/cmd/branch.go index 04fb7560eac..e6fab3dab04 100644 --- a/release/cmd/branch.go +++ b/release/cmd/branch.go @@ -23,6 +23,7 @@ import ( "github.com/projectcalico/calico/release/internal/utils" "github.com/projectcalico/calico/release/pkg/manager/branch" + "github.com/projectcalico/calico/release/pkg/manager/calico" "github.com/projectcalico/calico/release/pkg/manager/operator" ) @@ -49,20 +50,32 @@ func branchSubCommands(cfg *Config) []*cli.Command { baseBranchFlag, releaseBranchPrefixFlag, devTagSuffixFlag, - publishBranchFlag, + operatorBranchFlag, + gitPublishFlag, skipValidationFlag, }, Action: func(_ context.Context, c *cli.Command) error { configureLogging("branch-cut.log") + calicoManager := calico.NewManager( + calico.WithGithubOrg(c.String(orgFlag.Name)), + calico.WithRepoName(c.String(repoFlag.Name)), + calico.WithRepoRemote(c.String(repoRemoteFlag.Name)), + calico.WithRepoRoot(cfg.RepoRootDir), + calico.WithReleaseBranchPrefix(c.String(releaseBranchPrefixFlag.Name)), + calico.WithOperatorBranch(c.String(operatorBranchFlag.Name)), + calico.WithValidate(!c.Bool(skipValidationFlag.Name)), + ) + m := branch.NewManager( branch.WithRepoRoot(cfg.RepoRootDir), branch.WithRepoRemote(c.String(repoRemoteFlag.Name)), branch.WithMainBranch(c.String(baseBranchFlag.Name)), branch.WithDevTagIdentifier(c.String(devTagSuffixFlag.Name)), branch.WithReleaseBranchPrefix(c.String(releaseBranchPrefixFlag.Name)), + branch.WithRepoManager(calicoManager), branch.WithValidate(!c.Bool(skipValidationFlag.Name)), - branch.WithPublish(c.Bool(publishBranchFlag.Name))) + branch.WithPublish(c.Bool(gitPublishFlag.Name))) return m.CutReleaseBranch() }, }, @@ -75,7 +88,7 @@ func branchSubCommands(cfg *Config) []*cli.Command { operatorReleaseBranchPrefixFlag, operatorDevTagSuffixFlag, newBranchFlag, - publishBranchFlag, + gitPublishFlag, skipValidationFlag, ), Action: func(_ context.Context, c *cli.Command) error { @@ -97,7 +110,7 @@ func branchSubCommands(cfg *Config) []*cli.Command { operator.WithDevTagIdentifier(operatorDevTagSuffixFlag.Name), operator.WithReleaseBranchPrefix(c.String(operatorReleaseBranchPrefixFlag.Name)), operator.WithValidate(!c.Bool(skipValidationFlag.Name)), - operator.WithPublish(c.Bool(publishBranchFlag.Name)), + operator.WithPublish(c.Bool(gitPublishFlag.Name)), ) return m.CutBranch(c.String(newBranchFlag.Name)) diff --git a/release/cmd/flags.go b/release/cmd/flags.go index 2879f6cb34c..ebc13ff2629 100644 --- a/release/cmd/flags.go +++ b/release/cmd/flags.go @@ -17,6 +17,7 @@ package main import ( "context" "fmt" + "slices" "github.com/sirupsen/logrus" cli "github.com/urfave/cli/v3" @@ -81,15 +82,17 @@ var ( Sources: cli.EnvVars("DEV_TAG_SUFFIX"), Value: "0.dev", } - publishBranchFlag = &cli.BoolFlag{ + gitPublishFlag = &cli.BoolFlag{ Name: "git-publish", - Aliases: []string{"publish-branch"}, - Usage: "Push branch git. If false, all changes are local.", + Aliases: []string{"publish-git"}, + Usage: "Push git changes to remote. If false, all changes are local.", + Sources: cli.EnvVars("PUBLISH_GIT"), Value: true, } newBranchFlag = &cli.StringFlag{ - Name: "branch-stream", - Usage: fmt.Sprintf("The new major and minor versions for the branch to create e.g. vX.Y to create a -vX.Y branch e.g. v1.37 for %s-v1.37 branch", releaseBranchPrefixFlag.Value), + Name: "branch-stream", + Sources: cli.EnvVars("RELEASE_BRANCH_STREAM"), + Usage: fmt.Sprintf("The new major and minor versions for the branch to create e.g. vX.Y to create a -vX.Y branch e.g. v1.37 for %s-v1.37 branch", releaseBranchPrefixFlag.Value), } baseBranchFlag = &cli.StringFlag{ Name: "base-branch", @@ -123,6 +126,11 @@ var ( Usage: "Override default registries for the release. Repeat for multiple registries.", Sources: cli.EnvVars("REGISTRIES"), // avoid DEV_REGISTRIES as it is already used by the build system (lib.Makefile). } + helmRegistryFlag = &cli.StringSliceFlag{ + Name: "helm-registry", + Usage: "Override default OCI-based helm chart registries for the release. Repeat for multiple registries.", + Sources: cli.EnvVars("HELM_REGISTRIES"), + } archOptions = []string{"amd64", "arm64", "ppc64le", "s390x"} archFlag = &cli.StringSliceFlag{ @@ -133,7 +141,7 @@ var ( Value: archOptions, Action: func(_ context.Context, c *cli.Command, values []string) error { for _, arch := range values { - if !utils.Contains(archOptions, arch) { + if !slices.Contains(archOptions, arch) { return fmt.Errorf("invalid architecture %s", arch) } } @@ -147,7 +155,7 @@ var ( Sources: cli.EnvVars("BUILD_CONTAINER_IMAGES"), // avoid BUILD_IMAGES as it is already used by the build system (lib.Makefile). Value: true, } - buildHashreleaseImageFlag = &cli.BoolFlag{ + buildHashreleaseImagesFlag = &cli.BoolFlag{ Name: buildImagesFlag.Name, Usage: buildImagesFlag.Usage, Sources: buildImagesFlag.Sources, @@ -160,18 +168,53 @@ var ( Sources: cli.EnvVars("PUBLISH_IMAGES"), Value: true, } - publishHashreleaseImageFlag = &cli.BoolFlag{ + publishHashreleaseImagesFlag = &cli.BoolFlag{ Name: publishImagesFlag.Name, Usage: publishImagesFlag.Usage, Sources: publishImagesFlag.Sources, Value: false, } + publishChartsFlag = &cli.BoolFlag{ + Name: "publish-charts", + Usage: "Publish Helm charts to the registry", + Sources: cli.EnvVars("PUBLISH_CHARTS"), + Value: true, + } + awsProfileFlag = &cli.StringFlag{ + Name: "aws-profile", + Usage: "The AWS profile to use", + Sources: cli.EnvVars("AWS_PROFILE"), + } + s3BucketFlag = &cli.StringFlag{ + Name: "s3-bucket", + Usage: "The S3 bucket to publish release artifacts to.", + Sources: cli.EnvVars("S3_BUCKET"), + } + archiveImagesFlag = &cli.BoolFlag{ Name: "archive-images", Usage: "Archive images in the release tarball", Sources: cli.EnvVars("ARCHIVE_IMAGES"), Value: true, + Action: func(_ context.Context, c *cli.Command, b bool) error { + if b && !c.Bool(buildImagesFlag.Name) { + return fmt.Errorf("cannot archive images without building them; set --%s to 'true'", buildImagesFlag.Name) + } + return nil + }, + } + archiveHashreleaseImagesFlag = &cli.BoolFlag{ + Name: archiveImagesFlag.Name, + Usage: archiveImagesFlag.Usage, + Sources: archiveImagesFlag.Sources, + Value: false, + Action: func(_ context.Context, c *cli.Command, b bool) error { + if b && !c.Bool(buildHashreleaseImagesFlag.Name) { + return fmt.Errorf("cannot archive images without building them; set --%s to 'true'", buildHashreleaseImagesFlag.Name) + } + return nil + }, } ) @@ -345,14 +388,19 @@ var ( Sources: cli.EnvVars("IMAGE_SCANNER_SELECT"), Value: "all", } - skipImageScanFlag = &cli.BoolFlag{ - Name: "skip-image-scan", + skipImageScanFlagName = "skip-image-scan" + skipImageScanFlag = &cli.BoolFlag{ + Name: skipImageScanFlagName, Usage: "Skip sending the image to the image scan service", Sources: cli.EnvVars("SKIP_IMAGE_SCAN"), Value: false, Action: func(_ context.Context, c *cli.Command, b bool) error { - if !b && (c.String(imageScannerAPIFlag.Name) == "" || c.String(imageScannerTokenFlag.Name) == "") { - return fmt.Errorf("image scanner configuration is required, ensure %s and %s flags are set", imageScannerAPIFlag.Name, imageScannerTokenFlag.Name) + logrus.WithField(skipImageScanFlagName, b).Info("Image scanning configuration") + if !b && !imageScanningAPIConfig(c).Valid() { + if !c.Bool(ciFlag.Name) { + logrus.Warn("Image scanning configuration is incomplete") + } + return fmt.Errorf("invalid configuration for image scanning. Either set --%s and --%s or set --%s to 'true'", imageScannerAPIFlag.Name, imageScannerTokenFlag.Name, skipImageScanFlagName) } return nil }, @@ -379,14 +427,16 @@ var ( var ( // Publishing flags. publishGitTagFlag = &cli.BoolFlag{ - Name: "publish-git-tag", - Usage: "Push the git tag to the remote", - Value: true, + Name: "publish-git-tag", + Usage: "Push the git tag to the remote", + Sources: cli.EnvVars("PUBLISH_GIT_TAG"), + Value: true, } publishGitHubReleaseFlag = &cli.BoolFlag{ - Name: "publish-github-release", - Usage: "Publish the release to GitHub", - Value: true, + Name: "publish-github-release", + Usage: "Publish the release to GitHub", + Sources: cli.EnvVars("PUBLISH_GITHUB_RELEASE"), + Value: true, Action: func(_ context.Context, c *cli.Command, b bool) error { if b && c.String(githubTokenFlag.Name) == "" { return fmt.Errorf("GitHub token is required to publish release") @@ -412,11 +462,12 @@ var ( } // Hashrelease server configuration flags. - hashreleaseServerFlags = []cli.Flag{hashreleaseServerCredentialsFlag, hashreleaseServerBucketFlag} + hashreleaseServerFlags = []cli.Flag{hashreleaseServerBucketFlag} publishHashreleaseFlag = &cli.BoolFlag{ - Name: "publish-to-hashrelease-server", - Usage: "Publish the hashrelease to the hashrelease server", - Value: true, + Name: "publish-to-hashrelease-server", + Usage: "Publish the hashrelease to the hashrelease server", + Sources: cli.EnvVars("PUBLISH_TO_HASHRELEASE_SERVER"), + Value: true, } latestFlag = &cli.BoolFlag{ Name: "latest", @@ -424,11 +475,6 @@ var ( Sources: cli.EnvVars("LATEST"), Value: true, } - hashreleaseServerCredentialsFlag = &cli.StringFlag{ - Name: "hashrelease-server-credentials", - Usage: "The absolute path to the credentials file for the hashrelease server", - Sources: cli.EnvVars("HASHRELEASE_SERVER_CREDENTIALS"), - } hashreleaseServerBucketFlag = &cli.StringFlag{ Name: "hashrelease-server-bucket", Usage: "The bucket name for the hashrelease server", diff --git a/release/cmd/hashrelease.go b/release/cmd/hashrelease.go index 3cac91ce26b..5f931fed814 100644 --- a/release/cmd/hashrelease.go +++ b/release/cmd/hashrelease.go @@ -78,9 +78,6 @@ func hashreleaseSubCommands(cfg *Config) []*cli.Command { return fmt.Errorf("failed to clone operator repository: %v", err) } - // Define the base hashrelease directory. - baseHashreleaseDir := baseHashreleaseOutputDir(cfg.RepoRootDir) - // Create the pinned config. pinned := pinnedversion.CalicoPinnedVersions{ Dir: cfg.TmpDir, @@ -99,7 +96,8 @@ func hashreleaseSubCommands(cfg *Config) []*cli.Command { } // Check if the hashrelease has already been published. - if published, err := tasks.HashreleasePublished(hashreleaseServerConfig(c), data.Hash(), c.Bool(ciFlag.Name)); err != nil { + serverCfg := hashreleaseServerConfig(c) + if published, err := tasks.HashreleasePublished(serverCfg, data.Hash(), c.Bool(ciFlag.Name)); err != nil { return fmt.Errorf("failed to check if hashrelease has been published: %v", err) } else if published { // On CI, if the hashrelease has already been published, we exit successfully (return nil). @@ -119,6 +117,7 @@ func hashreleaseSubCommands(cfg *Config) []*cli.Command { operator.WithOperatorDirectory(operatorDir), operator.WithReleaseBranchPrefix(c.String(operatorReleaseBranchPrefixFlag.Name)), operator.IsHashRelease(), + operator.WithImage(c.String(operatorImageFlag.Name)), operator.WithArchitectures(c.StringSlice(archFlag.Name)), operator.WithValidate(!c.Bool(skipValidationFlag.Name)), operator.WithReleaseBranchValidation(!c.Bool(skipBranchCheckFlag.Name)), @@ -139,19 +138,24 @@ func hashreleaseSubCommands(cfg *Config) []*cli.Command { } } - // Define the hashrelease directory using the hash from the pinned file. - hashreleaseDir := filepath.Join(baseHashreleaseDir, data.Hash()) + // Extract the pinned version as a hashrelease. + hashrel, err := pinnedversion.LoadHashrelease(cfg.RepoRootDir, cfg.TmpDir, baseHashreleaseOutputDir(cfg.RepoRootDir), false) + if err != nil { + return fmt.Errorf("load hashrelease from pinned file: %v", err) + } opts := []calico.Option{ calico.WithVersion(data.ProductVersion()), calico.WithOperator(c.String(operatorRegistryFlag.Name), c.String(operatorImageFlag.Name), data.OperatorVersion()), + calico.WithOperatorGit(c.String(operatorOrgFlag.Name), c.String(operatorRepoFlag.Name), c.String(operatorBranchFlag.Name)), calico.WithRepoRoot(cfg.RepoRootDir), calico.WithReleaseBranchPrefix(c.String(releaseBranchPrefixFlag.Name)), calico.IsHashRelease(), - calico.WithOutputDir(hashreleaseDir), + calico.WithHashrelease(*hashrel, *serverCfg), + calico.WithOutputDir(hashrel.Source), calico.WithTmpDir(cfg.TmpDir), - calico.WithBuildImages(c.Bool(buildHashreleaseImageFlag.Name)), - calico.WithArchiveImages(c.Bool(archiveImagesFlag.Name)), + calico.WithBuildImages(c.Bool(buildHashreleaseImagesFlag.Name)), + calico.WithArchiveImages(c.Bool(archiveHashreleaseImagesFlag.Name)), calico.WithValidate(!c.Bool(skipValidationFlag.Name)), calico.WithReleaseBranchValidation(!c.Bool(skipBranchCheckFlag.Name)), calico.WithGithubOrg(c.String(orgFlag.Name)), @@ -176,13 +180,13 @@ func hashreleaseSubCommands(cfg *Config) []*cli.Command { if c.String(orgFlag.Name) == utils.TigeraOrg { logrus.Warn("Release notes are not supported for Tigera releases, skipping...") } else { - if _, err := outputs.ReleaseNotes(utils.ProjectCalicoOrg, c.String(githubTokenFlag.Name), cfg.RepoRootDir, filepath.Join(hashreleaseDir, releaseNotesDir), releaseVersion); err != nil { + if _, err := outputs.ReleaseNotes(utils.ProjectCalicoOrg, c.String(githubTokenFlag.Name), cfg.RepoRootDir, filepath.Join(hashrel.Source, releaseNotesDir), releaseVersion); err != nil { return err } } // Adjsut the formatting of the generated outputs to match the legacy hashrelease format. - return tasks.ReformatHashrelease(hashreleaseDir, cfg.TmpDir) + return tasks.ReformatHashrelease(hashrel.Source, cfg.TmpDir) }, }, @@ -245,8 +249,10 @@ func hashreleaseSubCommands(cfg *Config) []*cli.Command { calico.WithRepoRemote(c.String(repoRemoteFlag.Name)), calico.WithValidate(!c.Bool(skipValidationFlag.Name)), calico.WithTmpDir(cfg.TmpDir), + calico.WithOutputDir(hashrel.Source), calico.WithHashrelease(*hashrel, *serverCfg), - calico.WithPublishImages(c.Bool(publishHashreleaseImageFlag.Name)), + calico.WithPublishImages(c.Bool(publishHashreleaseImagesFlag.Name)), + calico.WithPublishCharts(c.Bool(publishChartsFlag.Name)), calico.WithPublishHashrelease(c.Bool(publishHashreleaseFlag.Name)), } if reg := c.StringSlice(registryFlag.Name); len(reg) > 0 { @@ -257,14 +263,13 @@ func hashreleaseSubCommands(cfg *Config) []*cli.Command { } else { opts = append(opts, calico.WithImageScanning(!c.Bool(skipImageScanFlag.Name), *imageScanningAPIConfig(c))) } - // Note: We only need to check that the correct images exist if we haven't built them ourselves. - // So, skip this check if we're configured to build and publish images from the local codebase. - if !c.Bool(publishHashreleaseImageFlag.Name) { - components, err := pinnedversion.RetrieveImageComponents(cfg.TmpDir) - if err != nil { - return fmt.Errorf("failed to retrieve images for the hashrelease: %v", err) - } - opts = append(opts, calico.WithComponents(components)) + components, err := pinnedversion.RetrieveImageComponents(cfg.TmpDir) + if err != nil { + return fmt.Errorf("failed to retrieve images for hashrelease: %w", err) + } + opts = append(opts, calico.WithComponents(components)) + if reg := c.StringSlice(helmRegistryFlag.Name); len(reg) > 0 { + opts = append(opts, calico.WithHelmRegistries(reg)) } r := calico.NewManager(opts...) if err := r.PublishRelease(); err != nil { @@ -285,7 +290,9 @@ func hashreleaseSubCommands(cfg *Config) []*cli.Command { // Send a slack message to notify that the hashrelease has been published. if c.Bool(publishHashreleaseFlag.Name) && c.Bool(notifyFlag.Name) { - return tasks.AnnounceHashrelease(slackConfig(c), hashrel, ciJobURL(c)) + if _, err := tasks.AnnounceHashrelease(slackConfig(c), hashrel, ciJobURL(c)); err != nil { + logrus.WithError(err).Warn("Failed to send hashrelease announcement to Slack") + } } return nil }, @@ -297,13 +304,14 @@ func hashreleaseSubCommands(cfg *Config) []*cli.Command { func hashreleaseBuildFlags() []cli.Flag { f := append(productFlags, registryFlag, + buildHashreleaseImagesFlag, + archiveHashreleaseImagesFlag, archFlag) f = append(f, operatorBuildFlags...) f = append(f, skipOperatorFlag, skipBranchCheckFlag, skipValidationFlag, - buildHashreleaseImageFlag, githubTokenFlag) return f } @@ -315,18 +323,25 @@ func validateHashreleaseBuildFlags(c *cli.Command) error { return fmt.Errorf("%s must be set if %s is set", operatorRegistryFlag, registryFlag) } - // CI condtional checks. + if c.Bool(archiveHashreleaseImagesFlag.Name) && !c.Bool(buildHashreleaseImagesFlag.Name) { + return fmt.Errorf("cannot archive images without building them; set --%s to 'true'", buildHashreleaseImagesFlag.Name) + } + if !c.Bool(archiveHashreleaseImagesFlag.Name) && c.Bool(buildHashreleaseImagesFlag.Name) { + logrus.Warnf("Images are built but not archived; to archive images set --%s to 'true'", archiveHashreleaseImagesFlag.Name) + } + + // CI conditional checks. if c.Bool(ciFlag.Name) { if !hashreleaseServerConfig(c).Valid() { - return fmt.Errorf("missing hashrelease publishing configuration, ensure --%s and --%s are set", - hashreleaseServerBucketFlag.Name, hashreleaseServerCredentialsFlag.Name) + return fmt.Errorf("missing hashrelease publishing configuration, ensure --%s is set", + hashreleaseServerBucketFlag.Name) } if c.String(ciTokenFlag.Name) == "" { return fmt.Errorf("%s API token must be set when running on CI, either set \"SEMAPHORE_API_TOKEN\" or use %s flag", semaphoreCI, ciTokenFlag.Name) } } else { // If building images, log a warning if no registry is specified. - if c.Bool(buildHashreleaseImageFlag.Name) && len(c.StringSlice(registryFlag.Name)) == 0 { + if c.Bool(buildHashreleaseImagesFlag.Name) && len(c.StringSlice(registryFlag.Name)) == 0 { logrus.Warn("Building images without specifying a registry will result in images being built with the default registries") } @@ -343,8 +358,10 @@ func validateHashreleaseBuildFlags(c *cli.Command) error { func hashreleasePublishFlags() []cli.Flag { f := append(gitFlags, registryFlag, + helmRegistryFlag, + publishHashreleaseImagesFlag, + publishChartsFlag, archFlag, - publishHashreleaseImageFlag, publishHashreleaseFlag, latestFlag, skipOperatorFlag, @@ -360,8 +377,8 @@ func validateHashreleasePublishFlags(c *cli.Command) error { if c.Bool(publishHashreleaseFlag.Name) { // check that hashrelease server configuration is set. if !hashreleaseServerConfig(c).Valid() { - return fmt.Errorf("missing hashrelease publishing configuration, ensure --%s and --%s are set", - hashreleaseServerBucketFlag.Name, hashreleaseServerCredentialsFlag.Name) + return fmt.Errorf("missing hashrelease publishing configuration, ensure --%s is set", + hashreleaseServerBucketFlag.Name) } if c.Bool(latestFlag.Name) { // If using a custom registry, do not allow setting the hashrelease as latest. @@ -392,8 +409,7 @@ func ciJobURL(c *cli.Command) string { func hashreleaseServerConfig(c *cli.Command) *hashreleaseserver.Config { return &hashreleaseserver.Config{ - BucketName: c.String(hashreleaseServerBucketFlag.Name), - CredentialsFile: c.String(hashreleaseServerCredentialsFlag.Name), + BucketName: c.String(hashreleaseServerBucketFlag.Name), } } @@ -409,7 +425,7 @@ func validateCIBuildRequirements(c *cli.Command, repoRootDir string) error { if !c.Bool(ciFlag.Name) { return nil } - if c.Bool(buildImagesFlag.Name) { + if c.Bool(buildHashreleaseImagesFlag.Name) { logrus.Info("Building images in hashrelease, skipping images promotions check...") return nil } diff --git a/release/cmd/release.go b/release/cmd/release.go index 7e5c76f17f3..226bb6834c9 100644 --- a/release/cmd/release.go +++ b/release/cmd/release.go @@ -147,10 +147,20 @@ func releaseSubCommands(cfg *Config) []*cli.Command { calico.WithPublishGitTag(c.Bool(publishGitTagFlag.Name)), calico.WithPublishGithubRelease(c.Bool(publishGitHubReleaseFlag.Name)), calico.WithGithubToken(c.String(githubTokenFlag.Name)), + calico.WithPublishCharts(c.Bool(publishChartsFlag.Name)), } if reg := c.StringSlice(registryFlag.Name); len(reg) > 0 { opts = append(opts, calico.WithImageRegistries(reg)) } + if reg := c.StringSlice(helmRegistryFlag.Name); len(reg) > 0 { + opts = append(opts, calico.WithHelmRegistries(reg)) + } + if v := c.String(awsProfileFlag.Name); v != "" { + opts = append(opts, calico.WithAWSProfile(v)) + } + if v := c.String(s3BucketFlag.Name); v != "" { + opts = append(opts, calico.WithS3Bucket(v)) + } r := calico.NewManager(opts...) return r.PublishRelease() }, @@ -213,6 +223,7 @@ func releaseBuildFlags() []cli.Flag { archFlag, registryFlag, buildImagesFlag, + archiveImagesFlag, githubTokenFlag, skipValidationFlag) return f @@ -222,10 +233,14 @@ func releaseBuildFlags() []cli.Flag { func releasePublishFlags() []cli.Flag { f := append(productFlags, registryFlag, + helmRegistryFlag, publishImagesFlag, publishGitTagFlag, publishGitHubReleaseFlag, githubTokenFlag, + publishChartsFlag, + awsProfileFlag, + s3BucketFlag, skipValidationFlag) return f } @@ -249,39 +264,17 @@ func releaseValidationSubCommand(cfg *Config) *cli.Command { return err } - pinnedCfg := pinnedversion.CalicoReleaseVersions{ - Dir: cfg.TmpDir, - ProductVersion: ver.FormattedString(), - ReleaseBranchPrefix: c.String(releaseBranchPrefixFlag.Name), - OperatorVersion: operatorVer.FormattedString(), - OperatorCfg: pinnedversion.OperatorConfig{ - Image: operator.DefaultImage, - Registry: operator.DefaultRegistry, - }, - } - if _, err := pinnedCfg.GenerateFile(); err != nil { - return fmt.Errorf("failed to generate pinned version file: %w", err) - } - images, err := pinnedCfg.ImageList() - if err != nil { - return fmt.Errorf("failed to get image list: %w", err) - } - flannelVer, err := pinnedCfg.FlannelVersion() - if err != nil { - return fmt.Errorf("failed to get flannel version: %w", err) - } - postreleaseDir := filepath.Join(cfg.RepoRootDir, utils.ReleaseFolderName, "pkg", "postrelease") args := []string{ "--format=testname", "--", "-v", "./...", fmt.Sprintf("-release-version=%s", ver.FormattedString()), fmt.Sprintf("-operator-version=%s", operatorVer.FormattedString()), - fmt.Sprintf("-flannel-version=%s", flannelVer), + fmt.Sprintf("-flannel-version=%s", pinnedversion.FlannelComponent.Version), fmt.Sprintf("-github-org=%s", c.String(orgFlag.Name)), fmt.Sprintf("-github-repo=%s", c.String(repoFlag.Name)), fmt.Sprintf("-github-repo-remote=%s", c.String(repoRemoteFlag.Name)), - fmt.Sprintf("-images=%s", strings.Join(images, " ")), + fmt.Sprintf("-images=%s", strings.Join(utils.ReleaseImages(), " ")), } if c.String(githubTokenFlag.Name) != "" { args = append(args, fmt.Sprintf("-github-token=%s", c.String(githubTokenFlag.Name))) diff --git a/release/deps.txt b/release/deps.txt index a1190b56a6b..a69f1a25161 100644 --- a/release/deps.txt +++ b/release/deps.txt @@ -2,50 +2,49 @@ This file contains the list of modules that this package depends on in order to trigger CI on changes -go 1.25.1 -cel.dev/expr v0.20.0 -cloud.google.com/go v0.120.0 -cloud.google.com/go/auth v0.16.1 +go 1.25.7 +cel.dev/expr v0.24.0 +cloud.google.com/go v0.121.6 +cloud.google.com/go/auth v0.16.5 cloud.google.com/go/auth/oauth2adapt v0.2.8 cloud.google.com/go/compute v1.38.0 -cloud.google.com/go/compute/metadata v0.7.0 +cloud.google.com/go/compute/metadata v0.8.0 cloud.google.com/go/iam v1.5.2 cloud.google.com/go/monitoring v1.24.2 -cloud.google.com/go/storage v1.50.0 -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0 -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.50.0 +cloud.google.com/go/storage v1.57.1 +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 github.com/Masterminds/semver/v3 v3.4.0 github.com/ProtonMail/go-crypto v1.0.0 github.com/beorn7/perks v1.0.1 github.com/cespare/xxhash/v2 v2.3.0 github.com/cloudflare/circl v1.6.1 -github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 github.com/containerd/errdefs v1.0.0 github.com/containerd/errdefs/pkg v0.3.0 github.com/containerd/stargz-snapshotter/estargz v0.16.3 github.com/distribution/reference v0.6.0 github.com/docker/cli v27.5.0+incompatible github.com/docker/distribution v2.8.3+incompatible -github.com/docker/docker v28.3.3+incompatible +github.com/docker/docker v28.5.1+incompatible github.com/docker/docker-credential-helpers v0.8.2 github.com/docker/go-connections v0.5.0 github.com/docker/go-units v0.5.0 github.com/envoyproxy/go-control-plane v0.13.4 -github.com/envoyproxy/go-control-plane/envoy v1.32.4 +github.com/envoyproxy/go-control-plane/envoy v1.35.0 github.com/envoyproxy/protoc-gen-validate v1.2.1 github.com/felixge/httpsnoop v1.0.4 -github.com/go-jose/go-jose/v4 v4.0.5 +github.com/go-jose/go-jose/v4 v4.1.2 github.com/go-logr/logr v1.4.3 github.com/go-logr/stdr v1.2.2 -github.com/gogo/protobuf v1.3.2 github.com/google/go-containerregistry v0.20.3 github.com/google/go-github/v53 v53.2.0 github.com/google/go-querystring v1.1.0 github.com/google/s2a-go v0.1.9 github.com/google/uuid v1.6.0 github.com/googleapis/enterprise-certificate-proxy v0.3.6 -github.com/googleapis/gax-go/v2 v2.14.2 +github.com/googleapis/gax-go/v2 v2.15.0 github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 github.com/klauspost/compress v1.18.0 github.com/mitchellh/go-homedir v1.1.0 @@ -55,10 +54,10 @@ github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 github.com/pkg/errors v0.9.1 github.com/projectcalico/calico -github.com/prometheus/client_golang v1.23.0 +github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 -github.com/prometheus/common v0.65.0 -github.com/prometheus/procfs v0.17.0 +github.com/prometheus/common v0.67.2 +github.com/prometheus/procfs v0.19.2 github.com/sirupsen/logrus v1.9.3 github.com/slack-go/slack v0.17.3 github.com/snowzach/rotatefilehook v0.0.0-20220211133110-53752135082d @@ -68,27 +67,29 @@ github.com/urfave/cli/v3 v3.3.8 github.com/vbatts/tar-split v0.11.6 github.com/zeebo/errs v1.4.0 go.opentelemetry.io/auto/sdk v1.1.0 -go.opentelemetry.io/contrib/detectors/gcp v1.34.0 -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 -go.opentelemetry.io/otel v1.35.0 -go.opentelemetry.io/otel/metric v1.35.0 -go.opentelemetry.io/otel/sdk v1.35.0 -go.opentelemetry.io/otel/sdk/metric v1.35.0 -go.opentelemetry.io/otel/trace v1.35.0 +go.opentelemetry.io/contrib/detectors/gcp v1.36.0 +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 +go.opentelemetry.io/otel v1.37.0 +go.opentelemetry.io/otel/metric v1.37.0 +go.opentelemetry.io/otel/sdk v1.37.0 +go.opentelemetry.io/otel/sdk/metric v1.37.0 +go.opentelemetry.io/otel/trace v1.37.0 +go.yaml.in/yaml/v2 v2.4.3 go.yaml.in/yaml/v3 v3.0.4 -golang.org/x/crypto v0.41.0 -golang.org/x/net v0.43.0 -golang.org/x/oauth2 v0.30.0 -golang.org/x/sync v0.16.0 -golang.org/x/sys v0.35.0 -golang.org/x/text v0.28.0 -golang.org/x/time v0.12.0 -google.golang.org/api v0.236.0 +golang.org/x/crypto v0.47.0 +golang.org/x/net v0.49.0 +golang.org/x/oauth2 v0.34.0 +golang.org/x/sync v0.19.0 +golang.org/x/sys v0.40.0 +golang.org/x/text v0.33.0 +golang.org/x/time v0.14.0 +google.golang.org/api v0.247.0 google.golang.org/genproto v0.0.0-20250603155806-513f23925822 -google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 -google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a -google.golang.org/grpc v1.72.2 +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c +google.golang.org/grpc v1.76.0 google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a -google.golang.org/protobuf v1.36.7 +google.golang.org/protobuf v1.36.10 gopkg.in/natefinch/lumberjack.v2 v2.2.1 +gopkg.in/yaml.v3 v3.0.1 diff --git a/release/internal/aptrepo/apt.go b/release/internal/aptrepo/apt.go new file mode 100644 index 00000000000..f9879d6237e --- /dev/null +++ b/release/internal/aptrepo/apt.go @@ -0,0 +1,208 @@ +// Package aptrepo contains functionality for creating and managing apt repositories +package aptrepo + +import ( + "bytes" + _ "embed" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "text/template" + + "github.com/sirupsen/logrus" +) + +// A brief note on Ubuntu/Debian/apt repo terminology: +// +// For Ubuntu and Debian, releases are numbered and codenamed; Ubuntu releases follow +// a fixed schedule and are numbered by release year and month, e.g. 24.04 was released +// in April 2024. Debian releases do not follow a fixed schedule and are numbered +// sequentially, e.g. 12.10, 12.11, etc. +// +// The 'codename' is the one-word name of the release, such as 'noble', 'trixie', +// etc. If you run `lsb_release -a` it will give you these names in the 'Codename' +// field. +// +// Here is some terminology and how it's used for these distros and how that relates to +// use in apt repositories. +// +// Suite +// In Debian, the 'suite' refers to a category of release 'oldstable', 'stable', 'testing', +// etc., and allows users to float their version to the current 'stable' or 'testing' release +// for example; when a release is promoted to 'stable' then 'stable' refers to that new +// release (whichever it is) and users will now start to get packages from that new release; +// 'oldstable' now refers to the former 'stable'. +// +// In apt, however, this distinction is not made, and the 'suite' field can contain the codename +// of the Debian or Ubuntu release, such as 'noble', 'bookworm', etc., or the Debian 'suite' +// such as 'stable' or 'testing'. +// +// Some third party repositories will create a separate suite for their own releases; for example, +// LLVM has suites for 'llvm-toolchain-noble-18', 'llvm-toolchain-noble-19', etc. We may consider +// doing something similar, e.g. 'calico-enterprise-v3.23-noble'. +// +// Component +// Which 'part' of the release it is. Most common in Ubuntu are 'main', 'restricted', +// 'universe', and 'multiverse'; for Debian the equivalents are 'main', 'non-free-firmware', +// 'contrib', and 'non-free'. +// +// While these terms have specific meaning for these releases, we can just use 'main' +// for everything. +// +// Hopefully this explains why 'suite' and 'codename' are used mostly interchangeably in +// this code depending on what they're actually being used for! + +type aptSourcesData struct { + // RepoName is the name of the repository as might be shown by repolib (e.g. in a UI) + RepoName string + // RepoURL is the base URL of the repository (i.e. where pool/ and dists/ are) + RepoURL string + // Suite is the 'suite' field, e.g. noble, bookworm, etc. + Suite string + // GpgKey is the ascii-armored GPG public key + GpgKey string + // Architectures is the list of architectures this sources file will claim support for + Architectures []string +} + +//go:embed templates/repo.sources.gotmpl +var aptSourcesTemplate string + +// writeAptSourcesFile creates a deb822-style sources file for a given set +// of parameters, and writes it to .sources under +// For more info on the format: https://repolib.readthedocs.io/en/latest/deb822-format.html +func (asd *aptSourcesData) writeAptSourcesFile(rootPath string) error { + logrus.WithField("suite", asd.Suite).Info("Generating apt .sources file") + sourcesFilePath := filepath.Join(rootPath, fmt.Sprintf("%s.sources", asd.Suite)) + sourcesFile, err := os.OpenFile(sourcesFilePath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0o644) + if err != nil { + return fmt.Errorf("opening %s: %w", sourcesFilePath, err) + } + defer func() { _ = sourcesFile.Close() }() + + funcMap := template.FuncMap{ + "join": strings.Join, + } + + tmpl, err := template.New("apt.sources").Funcs(funcMap).Parse(aptSourcesTemplate) + if err != nil { + return fmt.Errorf("failed to parse apt sources template: %w", err) + } + + if err := tmpl.Execute(sourcesFile, asd); err != nil { + logrus.WithField("suite", asd.Suite).WithError(err).Error("failed to write apt sources file") + return fmt.Errorf("failed to write apt sources file: %w", err) + } + + logrus.WithField("file", sourcesFilePath).Info("Wrote apt .sources file") + + return nil +} + +func getVersionFromDebfile(debfilePath string) (string, error) { + logrus.WithField("debfile", debfilePath).Debug("Getting version information from debian package") + cmd := exec.Command("dpkg-deb", "--show", "--showformat", "${Version}", "--", debfilePath) + out, err := cmd.Output() + if err != nil { + return "", fmt.Errorf("getting version for %s: %w", debfilePath, err) + } + return string(out), nil +} + +func getComponentNameFromVersion(version string) (string, error) { + if lastIdx := strings.LastIndex(version, "~"); lastIdx != -1 { + return version[lastIdx+1:], nil + } + return "", fmt.Errorf("version %s does not contain a tilde separator", version) +} + +func getSuiteNameFromDebFile(debfilePath string) (string, error) { + version, err := getVersionFromDebfile(debfilePath) + if err != nil { + return "", fmt.Errorf("getting version for %s: %w", debfilePath, err) + } + + suite, err := getComponentNameFromVersion(version) + if err != nil { + return "", fmt.Errorf("getting component name for %s: %w", debfilePath, err) + } + + return suite, nil +} + +// formatGPGKeyForSourcesFile formats a GPG public key into a format suitable to +// be appended into a sources file template (indented one space, blank +// lines replaced with '.') +func formatGPGKeyForSourcesFile(gpgKey string) string { + // To make it easier to insert the GPG key into the sources file, we want to + // 1. Replace every blank line (there should only be one) with a '.' + // 2. Indent each line with a single space + var processedKey bytes.Buffer + lines := strings.Split(gpgKey, "\n") + // The split might result in a trailing empty string if the output ends in newline, which is typical. + // We should be careful not to add extra newlines if not present, but `gpg` output usually has a trailing newline. + if len(lines) > 0 && lines[len(lines)-1] == "" { + lines = lines[:len(lines)-1] + } + + for _, line := range lines { + if line == "" { + line = "." + } + processedKey.WriteString(" " + line + "\n") + } + + return processedKey.String() +} + +func getRecursiveDebsBySuite(searchPaths []string) (map[string][]string, error) { + debsBySuite := make(map[string][]string, 0) + + files, err := getRecursiveDebs(searchPaths) + if err != nil { + return map[string][]string{}, err + } + + logrus.Debugf("Found %d debian package files to process", len(files)) + for _, debFile := range files { + suite, err := getSuiteNameFromDebFile(debFile) + if err != nil { + return map[string][]string{}, fmt.Errorf("getting suite name for %s: %w", debFile, err) + } + debsBySuite[suite] = append(debsBySuite[suite], debFile) + } + + return debsBySuite, nil +} + +func getRecursiveDebs(searchPaths []string) ([]string, error) { + // Find .deb and .ddeb files + var files []string + for _, searchPath := range searchPaths { + logrus.Infof("Scanning for debian packages in %s", searchPath) + err := filepath.WalkDir(searchPath, func(path string, d os.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + // Avoid walking into .git or .aptly to save time/confusion, + // though bash script doesn't explicitly exclude them (it relies on glob). + if d.Name() == ".git" || d.Name() == "pool" { + return filepath.SkipDir + } + return nil + } + if strings.HasSuffix(path, ".deb") || strings.HasSuffix(path, ".ddeb") { + logrus.Debug(fmt.Sprintf("Found debian package %s", path)) + files = append(files, path) + } + return nil + }) + if err != nil { + return []string{}, fmt.Errorf("walking directory: %w", err) + } + } + return files, nil +} diff --git a/release/internal/aptrepo/repo.go b/release/internal/aptrepo/repo.go new file mode 100644 index 00000000000..2bb434706c0 --- /dev/null +++ b/release/internal/aptrepo/repo.go @@ -0,0 +1,257 @@ +package aptrepo + +import ( + _ "embed" + "errors" + "fmt" + "os" + "path/filepath" + "slices" + "strings" + "text/template" + + "github.com/sirupsen/logrus" + + "github.com/projectcalico/calico/release/internal/command" + "github.com/projectcalico/calico/release/internal/utils" +) + +// Reprepro is a terrible name but it's what we have + +// RepoConfig is the information we'll use to generate Reprepro's 'distributions' configuration file +type RepoConfig struct { + // Architectures is the list of architectures we'll publish + Architectures []string + // Origin is a freeform text field that admins can use to filter on; probably should be 'Tigera' + Origin string + // Label is another freeform text field to filter on; probably should be 'Calico Enterprise' + Label string + // Components is the list of 'components' (releases) we intend to publish, e.g. noble, jammy, bookworm + Components []string + // ProductName is the full name of our product that will show in the description field of the repo; e.g. + // "Calico Enterprise v3.21", or maybe "Calico Enterprise v3.21 hashrelease" + ProductName string + // GPGKeyID is the GPG key ID that we'll sign the repository with + GPGKeyID string +} + +// Repo defines the core information about a local (on-disk) repo that we want to create/manipulate +type Repo struct { + // TempDir is where we're going to store our files while we do our generation + TempDir string + // BaseDirectory is the absolute path to the repo base (where our configs and db are stored) + BaseDirectory string + // OutputDirectory is the absolute path to the output directory, where our pool and dists will be stored) + OutputDirectory string + // RepoConfig is the RepoConfig object representing the information about the repo we'll be publishing + Config RepoConfig + // PublishingURL is the full URL to the root of the published repository, e.g. https://host.com/ubuntu + PublishingURL string +} + +//go:embed templates/reprepro-conf.gotmpl +var repoDistributionsTemplate string + +// NewRepo creates a new Repo instance with the appropriate fields populated +func NewRepo(tempDir, outputDir string, repoConfig RepoConfig, url string) (*Repo, error) { + if outputDir == "" { + outputDir = filepath.Join(tempDir, "_apt_output_dir") + } + repo := Repo{ + TempDir: tempDir, + BaseDirectory: filepath.Join(tempDir, "_apt_repo_conf"), + OutputDirectory: outputDir, + Config: repoConfig, + PublishingURL: url, + } + return &repo, nil +} + +// RepositoryDBExists checks to see if the configured repository path already has +// a repo database; we need this to update existing remote repositories. +func (repo *Repo) RepositoryDBExists() (bool, error) { + dbPath := filepath.Join(repo.BaseDirectory, "db") + exist, err := utils.DirExists(dbPath) + if err != nil || !exist { + return exist, err + } + entries, err := os.ReadDir(dbPath) + if err != nil || len(entries) == 0 { + return false, err + } + return true, nil +} + +// exec 'wrapper' commands that we can use for later + +// exec runs a reprepro command but discards the output +func (repo *Repo) exec(args ...string) error { + _, err := repo.execWithOutput(args...) + return err +} + +// execWithOutput executes a reprepro command using the existing configuration and returns the output +func (repo *Repo) execWithOutput(args ...string) (string, error) { + cmdArgs := []string{ + "--basedir", + repo.BaseDirectory, + "--outdir", + repo.OutputDirectory, + "--ignore=extension", + } + cmdArgs = append(cmdArgs, args...) + logrus.Debugf("running reprepro command %s", strings.Join(cmdArgs, " ")) + out, err := command.Run("reprepro", cmdArgs) + if err != nil { + logrus.Error(out) + return "", fmt.Errorf("running 'reprepro %s': %w", strings.Join(args, " "), err) + } + return out, nil +} + +// Functions that handle configuration, setup, etc. + +// configDirPath returns the path to the configuration directory; does not guarantee it exists +func (repo *Repo) configDirPath() string { + return filepath.Join(repo.BaseDirectory, "conf") +} + +// configFilePath returns the path to the configuration file; does not guarantee it exists +func (repo *Repo) configFilePath() string { + return filepath.Join(repo.configDirPath(), "distributions") +} + +// CleanBaseDir removes the repo's configured base directory +func (repo *Repo) cleanBaseDir() error { + logrus.Debugf("removing repo base directory %s", repo.BaseDirectory) + if err := os.RemoveAll(repo.BaseDirectory); err != nil { + return fmt.Errorf("could not clean repo base directory %s: %w", repo.BaseDirectory, err) + } + return nil +} + +// CleanOutputDir removes the repo's configured output directory +func (repo *Repo) cleanOutputDir() error { + logrus.Debugf("removing repo output directory %s", repo.OutputDirectory) + if err := os.RemoveAll(repo.OutputDirectory); err != nil { + return fmt.Errorf("could not clean repo output directory %s: %w", repo.OutputDirectory, err) + } + return nil +} + +// Clean removes the configured base and output directories. Must be run before creating +// the repository configuration with Repo.WriteRepoConfig() +func (repo *Repo) clean() error { + return errors.Join( + repo.cleanBaseDir(), + repo.cleanOutputDir(), + ) +} + +// PrepareForBuild sets up the configured paths to be ready to build an +// apt repo. If we're building a repository from packages, this should +// be run before we start touching the filesystem. +func (repo *Repo) PrepareForBuild() error { + // We need to run clean() to ensure that we don't have leftover files + // from a previous build, leftover repo configuration or database, etc. + return repo.clean() +} + +// WriteRepoConfig generates and writes the config file for the repo software to the appropriate path +func (repo *Repo) WriteRepoConfig() error { + if err := os.MkdirAll(repo.configDirPath(), utils.DirPerms); err != nil { + return fmt.Errorf("failed to create config dir: %w", err) + } + repoConfigFile, err := os.OpenFile(repo.configFilePath(), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644) + if err != nil { + return fmt.Errorf("failed to create config file: %w", err) + } + defer func() { _ = repoConfigFile.Close() }() + + funcMap := template.FuncMap{ + "join": strings.Join, + } + tmpl, err := template.New("repo/config/distributions").Funcs(funcMap).Parse(repoDistributionsTemplate) + if err != nil { + return fmt.Errorf("failed to parse repo's distributions template: %w", err) + } + + if err := tmpl.Execute(repoConfigFile, repo.Config); err != nil { + return fmt.Errorf("failed to write repo distributions file: %w", err) + } + + return nil +} + +// Functions that expose reprepro's functionality (e.g. adding debian packages) + +// IncludeDeb adds a specified debian file to the specified component in the repo +func (repo *Repo) IncludeDeb(component, debFile string) error { + if !slices.Contains(repo.Config.Components, component) { + return fmt.Errorf("specified component %s not present in configured components list %s", component, strings.Join(repo.Config.Components, ", ")) + } + + err := repo.exec("includedeb", component, debFile) + if err != nil { + return fmt.Errorf("could not add file %s to component %s: %w", debFile, component, err) + } + return nil +} + +// RecursiveAddDebsFromDirectories takes a list of paths to search and finds all debian packages +// under those paths, gets their suite/component name, and adds them to the repo +func (repo *Repo) RecursiveAddDebsFromDirectories(searchPaths []string) error { + debsBySuite, err := getRecursiveDebsBySuite(searchPaths) + if err != nil { + return fmt.Errorf("could not scan for debian packages: %w", err) + } + + var publishingErrors []error + + for suite, filesList := range debsBySuite { + for _, filename := range filesList { + if err := repo.IncludeDeb(suite, filename); err != nil { + publishingErrors = append(publishingErrors, err) + } + } + } + if err := errors.Join(publishingErrors...); err != nil { + return fmt.Errorf("encountered errors publishing Apt repository: %w", err) + } + return nil +} + +// WriteSourcesFile writes out the .sources file for a given codename to the repo's output directory +func (repo *Repo) WriteSourcesFile(codename string) error { + if !slices.Contains(repo.Config.Components, codename) { + return fmt.Errorf("specified codename %s does not exist in defined codenames (%s)", codename, strings.Join(repo.Config.Components, ", ")) + } + gpgPubKey, err := utils.GetGPGPubKey(repo.Config.GPGKeyID) + if err != nil { + return fmt.Errorf("could not fetch GPG key %s: %w", repo.Config.GPGKeyID, err) + } + gpgPubKeyFormatted := formatGPGKeyForSourcesFile(gpgPubKey) + + sourcesFields := aptSourcesData{ + RepoName: repo.Config.ProductName, + RepoURL: repo.PublishingURL, + Suite: codename, + GpgKey: gpgPubKeyFormatted, + Architectures: repo.Config.Architectures, + } + + if err := sourcesFields.writeAptSourcesFile(repo.OutputDirectory); err != nil { + return fmt.Errorf("unable to write sources file for %s: %w", codename, err) + } + return nil +} + +// WriteAllSourcesFiles creates a .sources in the repo's output directory for +// each configured codename/suite. +func (repo *Repo) WriteAllSourcesFiles() error { + var errs []error + for _, codename := range repo.Config.Components { + errs = append(errs, repo.WriteSourcesFile(codename)) + } + return errors.Join(errs...) +} diff --git a/release/internal/aptrepo/templates/repo.sources.gotmpl b/release/internal/aptrepo/templates/repo.sources.gotmpl new file mode 100644 index 00000000000..64d6490489b --- /dev/null +++ b/release/internal/aptrepo/templates/repo.sources.gotmpl @@ -0,0 +1,7 @@ +X-Repolib-Name: {{.RepoName}} +Types: deb +URIs: {{.RepoURL}} +Suites: {{.Suite}} +Components: main +Architectures: {{ join .Architectures " " }} +Signed-By: {{.GpgKey}} \ No newline at end of file diff --git a/release/internal/aptrepo/templates/reprepro-conf.gotmpl b/release/internal/aptrepo/templates/reprepro-conf.gotmpl new file mode 100644 index 00000000000..15d79a0b1f8 --- /dev/null +++ b/release/internal/aptrepo/templates/reprepro-conf.gotmpl @@ -0,0 +1,12 @@ +{{ $config := . }} +{{ range $codename := .Components }} +Origin: {{ $config.Origin }} +Label: {{ $config.Label }} +Codename: {{ $codename }} +Suite: {{ $codename }} +Architectures: {{ join $config.Architectures " "}} +Components: main +Description: {{ $config.ProductName }} packages for Debian and Ubuntu systems +SignWith: {{ $config.GPGKeyID }} +Contents: +{{ end }} \ No newline at end of file diff --git a/release/internal/hashreleaseserver/config.go b/release/internal/hashreleaseserver/config.go index c997d4c4004..e397f135f7d 100644 --- a/release/internal/hashreleaseserver/config.go +++ b/release/internal/hashreleaseserver/config.go @@ -16,18 +16,13 @@ package hashreleaseserver import ( "context" - "encoding/json" "fmt" - "os" "cloud.google.com/go/storage" - "google.golang.org/api/option" ) // Config holds the configuration for hashrelease publishing. type Config struct { - // credentials file for GCS access - CredentialsFile string // cloud storage bucket name BucketName string @@ -36,12 +31,12 @@ type Config struct { } func (s *Config) Valid() bool { - return s.BucketName != "" && s.CredentialsFile != "" + return s.BucketName != "" } func (s *Config) Bucket() (*storage.BucketHandle, error) { if s.gcsClient == nil { - cli, err := storage.NewClient(context.Background(), option.WithCredentialsFile(s.CredentialsFile)) + cli, err := storage.NewClient(context.Background()) if err != nil { return nil, fmt.Errorf("failed to create storage client: %w", err) } @@ -49,20 +44,3 @@ func (s *Config) Bucket() (*storage.BucketHandle, error) { } return s.gcsClient.Bucket(s.BucketName), nil } - -type serviceAccountCredentials struct { - ClientEmail string `json:"client_email"` -} - -func (s *Config) credentialsAccount() (string, error) { - // return the email address of the service account used for GCS access - data, err := os.ReadFile(s.CredentialsFile) - if err != nil { - return "", fmt.Errorf("failed to read credentials file: %w", err) - } - var creds serviceAccountCredentials - if err := json.Unmarshal(data, &creds); err != nil { - return "", fmt.Errorf("failed to unmarshal credentials file: %w", err) - } - return creds.ClientEmail, nil -} diff --git a/release/internal/hashreleaseserver/config_test.go b/release/internal/hashreleaseserver/config_test.go index 4bd1a80b10e..8534a61054a 100644 --- a/release/internal/hashreleaseserver/config_test.go +++ b/release/internal/hashreleaseserver/config_test.go @@ -11,25 +11,8 @@ func TestConfigValid(t *testing.T) { expectedValid bool }{ { - name: "all fields set", - config: Config{ - CredentialsFile: "/path/to/credentials.json", - BucketName: "bucket-name", - }, - expectedValid: true, - }, - { - name: "missing GCS credentials", - config: Config{ - BucketName: "bucket-name", - }, - expectedValid: false, - }, - { - name: "missing bucket name", - config: Config{ - CredentialsFile: "/path/to/credentials.json", - }, + name: "missing bucket name", + config: Config{}, expectedValid: false, }, } { diff --git a/release/internal/hashreleaseserver/server.go b/release/internal/hashreleaseserver/server.go index 3f12cc8c311..998f4e6ad3b 100644 --- a/release/internal/hashreleaseserver/server.go +++ b/release/internal/hashreleaseserver/server.go @@ -42,36 +42,30 @@ type Hashrelease struct { // Name is the name of the hashrelease. // When publishing a hashrelease, this is the name of the folder in the server. // When getting a hashrelease, this is the full path of the hashrelease folder. - Name string + Name string `yaml:"name"` // Hash is the hash of the hashrelease - Hash string + Hash string `yaml:"hash"` // Note is the info about the hashrelease - Note string + Note string `yaml:"note,omitempty"` // Stream is the version the hashrelease is for (e.g master, v3.19) - Stream string + Stream string `yaml:"stream"` // ProductVersion is the product version in the hashrelease - ProductVersion string + ProductVersion string `yaml:"version"` - // OperatorVersion is the operator version for the hashreleaseq - OperatorVersion string + // OperatorVersion is the operator version for the hashrelease + OperatorVersion string `yaml:"operator"` - // Source is the source of hashrelease content - Source string - - // Dest is the path to the hashrelease dir on the server - Dest string - - // Time is the modified time of the hashrelease - Time time.Time + // Source is the source of hashrelease content on the local filesystem + Source string `yaml:"source,omitempty"` // Latest is if the hashrelease is the latest for the stream - Latest bool + Latest bool `yaml:"latest,omitempty"` - ImageScanResultURL string + ImageScanResultURL string `yaml:"iss_url,omitempty"` } func (h *Hashrelease) URL() string { @@ -114,16 +108,7 @@ func Publish(productCode string, h *Hashrelease, cfg *Config) error { } func publishFiles(h *Hashrelease, cfg *Config) error { - logrus.WithFields(logrus.Fields{ - "hashrelease": h.Name, - "srcDir": h.Source, - }).Info("Publishing hashrelease files") // publish to cloud storage - account, err := cfg.credentialsAccount() - if err != nil { - logrus.WithError(err).Error("Failed to get credentials email for hashrelease server") - return fmt.Errorf("failed to get credentials email for hashrelease publishing: %w", err) - } logrus.WithFields(logrus.Fields{ "hashrelease": h.Name, "srcDir": h.Source, @@ -132,7 +117,6 @@ func publishFiles(h *Hashrelease, cfg *Config) error { "storage", "rsync", h.Source, fmt.Sprintf("gs://%s/%s", cfg.BucketName, h.Name), "--recursive", "--delete-unmatched-destination-objects", - fmt.Sprintf("--account=%s", account), } if logrus.IsLevelEnabled(logrus.DebugLevel) { args = append(args, "--verbosity=debug") diff --git a/release/internal/imagescanner/scanner.go b/release/internal/imagescanner/scanner.go index 596e8981610..5d6e6d7d0df 100644 --- a/release/internal/imagescanner/scanner.go +++ b/release/internal/imagescanner/scanner.go @@ -67,9 +67,12 @@ func New(cfg Config) *Scanner { // Scan sends a request to the image scanner to scan the given images for the given product code and stream. func (i *Scanner) Scan(productCode string, images []string, stream string, release bool, outputDir string) error { if !i.config.Valid() { - logrus.Error("Invalid image scanner configuration") return fmt.Errorf("invalid image scanner configuration") } + if len(images) == 0 { + logrus.Warn("No images to send to scanner, skipping...") + return nil + } var bucketPath, scanType string if release { scanType = "release" @@ -78,7 +81,7 @@ func (i *Scanner) Scan(productCode string, images []string, stream string, relea scanType = "image" bucketPath = fmt.Sprintf("hashrelease/%s", stream) } - payload := map[string]interface{}{ + payload := map[string]any{ "images": images, "bucket_path": bucketPath, } diff --git a/release/internal/imagescanner/scanner_test.go b/release/internal/imagescanner/scanner_test.go index 8992f4d7f3d..f3956130d05 100644 --- a/release/internal/imagescanner/scanner_test.go +++ b/release/internal/imagescanner/scanner_test.go @@ -79,7 +79,7 @@ func TestScannerScan(t *testing.T) { t.Run("hashrelease scan", func(t *testing.T) { var capturedToken string - var capturedPayload map[string]interface{} + var capturedPayload map[string]any var capturedQuery url.Values mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { capturedToken = r.Header.Get("Authorization") @@ -131,7 +131,7 @@ func TestScannerScan(t *testing.T) { t.Run("release scan", func(t *testing.T) { var capturedToken string - var capturedPayload map[string]interface{} + var capturedPayload map[string]any var capturedQuery url.Values mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { capturedToken = r.Header.Get("Authorization") diff --git a/release/internal/outputs/hashrelease.go b/release/internal/outputs/hashrelease.go new file mode 100644 index 00000000000..4089d7e1fab --- /dev/null +++ b/release/internal/outputs/hashrelease.go @@ -0,0 +1,54 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// 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 outputs + +import ( + "fmt" + "os" + "path/filepath" + + "gopkg.in/yaml.v3" + + "github.com/projectcalico/calico/release/internal/hashreleaseserver" + "github.com/projectcalico/calico/release/internal/slack" +) + +var hashreleaseOutputFileName = "hashrelease.yaml" + +// PublishedHashrelease represents the output of a hashrelease publication. +type PublishedHashrelease struct { + Hashrelease *hashreleaseserver.Hashrelease `yaml:"-,inline"` + HashreleaseURL string `yaml:"url"` + SlackResponse *slack.MessageResponse `yaml:"slack,omitempty"` +} + +func (h *PublishedHashrelease) Write(outputDir string) (string, error) { + h.HashreleaseURL = h.Hashrelease.URL() + fqPath := filepath.Join(outputDir, hashreleaseOutputFileName) + f, err := os.Create(fqPath) + if err != nil { + return "", fmt.Errorf("creating hashrelease output file: %w", err) + } + defer func() { _ = f.Close() }() + + encoder := yaml.NewEncoder(f) + encoder.SetIndent(2) + defer func() { _ = encoder.Close() }() + + if err := encoder.Encode(h); err != nil { + return "", fmt.Errorf("writing hashrelease output to %s: %w", fqPath, err) + } + return fqPath, nil +} diff --git a/release/internal/outputs/hashrelease_test.TestPublishedHashrelease.no_slack_response.approved.txt b/release/internal/outputs/hashrelease_test.TestPublishedHashrelease.no_slack_response.approved.txt new file mode 100644 index 00000000000..0750a2f12d8 --- /dev/null +++ b/release/internal/outputs/hashrelease_test.TestPublishedHashrelease.no_slack_response.approved.txt @@ -0,0 +1,6 @@ +name: 2026-01-06-v3-32-vertigo +hash: v3.32.0-0.dev-527-g92e0cd84e375-v1.42.0-0.dev-16-g3a924017cc9f +stream: master +version: v3.32.0-0.dev-527-g92e0cd84e375 +operator: v1.42.0-0.dev-16-g3a924017cc9f +url: https://2026-01-06-v3-32-vertigo.docs.eng.tigera.net diff --git a/release/internal/outputs/hashrelease_test.TestPublishedHashrelease.with_slack_response.approved.txt b/release/internal/outputs/hashrelease_test.TestPublishedHashrelease.with_slack_response.approved.txt new file mode 100644 index 00000000000..d8407dd7ede --- /dev/null +++ b/release/internal/outputs/hashrelease_test.TestPublishedHashrelease.with_slack_response.approved.txt @@ -0,0 +1,9 @@ +name: 2026-01-06-v3-32-vertigo +hash: v3.32.0-0.dev-527-g92e0cd84e375-v1.42.0-0.dev-16-g3a924017cc9f +stream: master +version: v3.32.0-0.dev-527-g92e0cd84e375 +operator: v1.42.0-0.dev-16-g3a924017cc9f +url: https://2026-01-06-v3-32-vertigo.docs.eng.tigera.net +slack: + channel: C123ABC456 + timestamp: "1503435956.000247" diff --git a/release/internal/outputs/hashrelease_test.go b/release/internal/outputs/hashrelease_test.go new file mode 100644 index 00000000000..15d2dc65d78 --- /dev/null +++ b/release/internal/outputs/hashrelease_test.go @@ -0,0 +1,90 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// 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 outputs + +import ( + "os" + "path/filepath" + "testing" + + approvals "github.com/approvals/go-approval-tests" + + "github.com/projectcalico/calico/release/internal/hashreleaseserver" + "github.com/projectcalico/calico/release/internal/slack" +) + +func TestPublishedHashrelease(t *testing.T) { + t.Parallel() + + t.Run("no slack response", func(t *testing.T) { + t.Parallel() + td := t.TempDir() + h := &PublishedHashrelease{Hashrelease: &hashreleaseserver.Hashrelease{ + Name: "2026-01-06-v3-32-vertigo", + Hash: "v3.32.0-0.dev-527-g92e0cd84e375-v1.42.0-0.dev-16-g3a924017cc9f", + Stream: "master", + ProductVersion: "v3.32.0-0.dev-527-g92e0cd84e375", + OperatorVersion: "v1.42.0-0.dev-16-g3a924017cc9f", + }} + + gotPath, err := h.Write(td) + if err != nil { + t.Fatalf("Write() returned error: %v", err) + } + expected := filepath.Join(td, hashreleaseOutputFileName) + if gotPath != expected { + t.Fatalf("unexpected path: got %q want %q", gotPath, expected) + } + + content, err := os.ReadFile(gotPath) + if err != nil { + t.Fatalf("failed to read output file: %v", err) + } + approvals.VerifyString(t, string(content)) + }) + + t.Run("with slack response", func(t *testing.T) { + t.Parallel() + td := t.TempDir() + h := &PublishedHashrelease{ + Hashrelease: &hashreleaseserver.Hashrelease{ + Name: "2026-01-06-v3-32-vertigo", + Hash: "v3.32.0-0.dev-527-g92e0cd84e375-v1.42.0-0.dev-16-g3a924017cc9f", + Stream: "master", + ProductVersion: "v3.32.0-0.dev-527-g92e0cd84e375", + OperatorVersion: "v1.42.0-0.dev-16-g3a924017cc9f", + }, + SlackResponse: &slack.MessageResponse{ + Channel: "C123ABC456", + Timestamp: "1503435956.000247", + }, + } + + gotPath, err := h.Write(td) + if err != nil { + t.Fatalf("Write() returned error: %v", err) + } + expected := filepath.Join(td, hashreleaseOutputFileName) + if gotPath != expected { + t.Fatalf("unexpected path: got %q want %q", gotPath, expected) + } + + content, err := os.ReadFile(gotPath) + if err != nil { + t.Fatalf("failed to read output file: %v", err) + } + approvals.VerifyString(t, string(content)) + }) +} diff --git a/release/internal/pinnedversion/pinnedversion.go b/release/internal/pinnedversion/pinnedversion.go index 1d95d40b4e9..31b5afad740 100644 --- a/release/internal/pinnedversion/pinnedversion.go +++ b/release/internal/pinnedversion/pinnedversion.go @@ -15,12 +15,13 @@ package pinnedversion import ( - _ "embed" "fmt" - "html/template" + "maps" "os" "path/filepath" + "slices" "strings" + "sync" "time" "github.com/sirupsen/logrus" @@ -33,25 +34,58 @@ import ( "github.com/projectcalico/calico/release/internal/version" ) -//go:embed templates/calico-versions.yaml.gotmpl -var calicoTemplate string +var ( + // Components that do not produce images. + noImageComponents = []string{ + apiComponentName, + calicoComponentName, + networkingCalicoComponentName, + } + + // Components to ignore when generating the operator components file. + operatorIgnoreComponents = []string{ + flannelComponentName, + testSignerComponentName, + flannelMigrationController, + } +) + +var FlannelComponent = registry.Component{ + Registry: "quay.io", + Image: "coreos/flannel", + Version: "v0.12.0", +} + +var ( + // Map of component names to their image names. + componentToImageMap = map[string]string{ + "calicoctl": "ctl", + "flexvol": "pod2daemon-flexvol", + "csi-node-driver-registrar": "node-driver-registrar", + } + // Map of image names to their component names. + // It is initialized lazily and should be accessed via mapImageToComponent. + imageToComponentMap = map[string]string{} +) const ( pinnedVersionFileName = "pinned_versions.yml" operatorComponentsFileName = "pinned_components.yml" ) -const calicoImageNamespace = "calico/" +const ( + apiComponentName = "api" + calicoComponentName = "calico" + flannelComponentName = "flannel" + networkingCalicoComponentName = "networking-calico" + testSignerComponentName = "test-signer" + flannelMigrationController = "flannel-migration-controller" +) -var excludedComponents = []string{ - utils.Calico, - "calico/api", - "networking-calico", - "flannel", -} +var once sync.Once -type PinnedVersions interface { - GenerateFile() (version.Versions, error) +type PinnedVersions[T version.Versions] interface { + GenerateFile() (T, error) } type OperatorConfig struct { @@ -71,10 +105,6 @@ func (c OperatorConfig) GitVersion() (string, error) { return tag, nil } -func (c OperatorConfig) GitBranch() (string, error) { - return command.GitInDir(c.Dir, "rev-parse", "--abbrev-ref", "HEAD") -} - // PinnedVersion represents an entry in pinned version file. type PinnedVersion struct { Title string `yaml:"title"` @@ -86,22 +116,41 @@ type PinnedVersion struct { Components map[string]registry.Component `yaml:"components"` } -// calicoTemplateData is used to generate the pinned version file from the template. -type calicoTemplateData struct { - ReleaseName string - BaseDomain string - ProductVersion string - Operator registry.Component - Note string - Hash string - ReleaseBranch string +// operatorComponents returns a map of the Tigera operator and its init image components. +func (p *PinnedVersion) operatorComponents() map[string]registry.Component { + op := registry.OperatorComponent{Component: p.TigeraOperator} + opInit := op.InitImage() + return map[string]registry.Component{ + op.Image: op.Component, + opInit.Image: opInit, + } } -func (d *calicoTemplateData) ReleaseURL() string { - if d.ReleaseName == "" || d.BaseDomain == "" { - return "" +// ImageComponents returns a map of all components that produce images +// including Tigera operator and its init image if includeOperator is true. +// +// Images returned from this function are expected to eventually be in the format "/" +// e.g. "quay.io/calico/node" where is "quay.io/calico" and is "node". +// NOTE: this only sets the image name portion (i.e. "node"), the registry is set elsewhere. +func (p *PinnedVersion) ImageComponents(includeOperator bool) map[string]registry.Component { + components := make(map[string]registry.Component) + for name, component := range p.Components { + // Remove components that should be excluded. Either because they do not have an image, or not built by Calico. + if slices.Contains(noImageComponents, name) { + continue + } + if img, found := componentToImageMap[name]; found { + component.Image = img + } else if component.Image == "" { + component.Image = name + } + components[name] = component + } + + if includeOperator { + maps.Copy(components, p.operatorComponents()) } - return fmt.Sprintf("https://%s.%s", d.ReleaseName, d.BaseDomain) + return components } // PinnedVersionFilePath returns the path of the pinned version file. @@ -127,52 +176,38 @@ type CalicoPinnedVersions struct { // OperatorCfg is the configuration for the operator. OperatorCfg OperatorConfig + + releaseName string + productBranch string + versionData *version.HashreleaseVersions } // GenerateFile generates the pinned version file. -func (p *CalicoPinnedVersions) GenerateFile() (version.Versions, error) { +func (p *CalicoPinnedVersions) GenerateFile() (*version.HashreleaseVersions, error) { pinnedVersionPath := PinnedVersionFilePath(p.Dir) productBranch, err := utils.GitBranch(p.RootDir) if err != nil { - return nil, err + return nil, fmt.Errorf("cannot get current branch: %w", err) } + p.productBranch = productBranch productVer, err := command.GitVersion(p.RootDir, true) if err != nil { - logrus.WithError(err).Error("Failed to determine product git version") - return nil, err + return nil, fmt.Errorf("failed to determine product version: %w", err) } releaseName := fmt.Sprintf("%s-%s-%s", time.Now().Format("2006-01-02"), version.DeterminePublishStream(productBranch, productVer), RandomWord()) - releaseName = strings.ReplaceAll(releaseName, ".", "-") - operatorBranch, err := p.OperatorCfg.GitBranch() - if err != nil { - return nil, err - } + p.releaseName = strings.ReplaceAll(releaseName, ".", "-") operatorVer, err := p.OperatorCfg.GitVersion() if err != nil { - return nil, err + return nil, fmt.Errorf("failed to determine operator version: %w", err) } - versionData := version.NewHashreleaseVersions(version.New(productVer), operatorVer) - tmplData := &calicoTemplateData{ - ReleaseName: releaseName, - BaseDomain: hashreleaseserver.BaseDomain, - ProductVersion: versionData.ProductVersion(), - Operator: registry.Component{ - Version: versionData.OperatorVersion(), - Image: p.OperatorCfg.Image, - Registry: p.OperatorCfg.Registry, - }, - Hash: versionData.Hash(), - Note: fmt.Sprintf("%s - generated at %s using %s release branch with %s operator branch", - releaseName, time.Now().Format(time.RFC1123), productBranch, operatorBranch), - ReleaseBranch: versionData.ReleaseBranch(p.ReleaseBranchPrefix), - } - if err := generatePinnedVersionFile(tmplData, p.Dir); err != nil { + p.versionData = version.NewHashreleaseVersions(version.New(productVer), operatorVer) + if err := generatePinnedVersionFile(p); err != nil { return nil, err } if p.BaseHashreleaseDir != "" { - hashreleaseDir := filepath.Join(p.BaseHashreleaseDir, versionData.Hash()) + hashreleaseDir := filepath.Join(p.BaseHashreleaseDir, p.versionData.Hash()) if err := os.MkdirAll(hashreleaseDir, utils.DirPerms); err != nil { return nil, err } @@ -181,25 +216,71 @@ func (p *CalicoPinnedVersions) GenerateFile() (version.Versions, error) { } } - return versionData, nil + return p.versionData, nil } -func generatePinnedVersionFile(data *calicoTemplateData, outputDir string) error { - tmpl, err := template.New("pinnedversion").Parse(calicoTemplate) - if err != nil { - return fmt.Errorf("failed to parse template: %w", err) +func mapImageToComponent(imageName, version string) (string, registry.Component) { + once.Do(func() { + // Initialize the image to component map. + for c, img := range componentToImageMap { + imageToComponentMap[img] = c + } + }) + if compName, found := imageToComponentMap[imageName]; found { + return compName, registry.Component{ + Version: version, + Image: imageName, + } } - pinnedVersionPath := PinnedVersionFilePath(outputDir) + return imageName, registry.Component{Version: version} +} + +func generatePinnedVersionFile(p *CalicoPinnedVersions) error { + pinnedVersionPath := PinnedVersionFilePath(p.Dir) + components := map[string]registry.Component{ + apiComponentName: { + Version: p.versionData.ProductVersion(), + }, + calicoComponentName: { + Version: p.versionData.ProductVersion(), + }, + networkingCalicoComponentName: { + Version: p.versionData.ReleaseBranch(p.ReleaseBranchPrefix), + }, + flannelComponentName: FlannelComponent, + } + for _, img := range utils.ReleaseImages() { + name, c := mapImageToComponent(img, p.versionData.ProductVersion()) + components[name] = c + } + pinned := PinnedVersion{ + Title: p.versionData.ProductVersion(), + ManifestURL: fmt.Sprintf("https://%s.%s", p.releaseName, hashreleaseserver.BaseDomain), + ReleaseName: p.releaseName, + Note: fmt.Sprintf("%s - generated at %s using %s release branch with %s operator branch", + p.releaseName, time.Now().Format(time.RFC1123), p.productBranch, p.OperatorCfg.Branch), + Hash: p.versionData.Hash(), + TigeraOperator: registry.Component{ + Image: p.OperatorCfg.Image, + Registry: p.OperatorCfg.Registry, + Version: p.versionData.OperatorVersion(), + }, + Components: components, + } + logrus.WithField("file", pinnedVersionPath).Info("Creating pinned version file") pinnedVersionFile, err := os.Create(pinnedVersionPath) if err != nil { - return fmt.Errorf("failed to create pinned version file: %w", err) + return fmt.Errorf("cannot create pinned version file: %w", err) } defer func() { _ = pinnedVersionFile.Close() }() - if err := tmpl.Execute(pinnedVersionFile, data); err != nil { - return fmt.Errorf("failed to execute template: %w", err) + enc := yaml.NewEncoder(pinnedVersionFile) + enc.SetIndent(2) + defer func() { _ = enc.Close() }() + + if err := enc.Encode([]PinnedVersion{pinned}); err != nil { + return fmt.Errorf("failed to encode pinned version file: %w", err) } - logrus.WithField("file", pinnedVersionPath).Info("Pinned version file generated successfully") return nil } @@ -211,9 +292,13 @@ func GenerateOperatorComponents(srcDir, outputDir string) (registry.OperatorComp if err != nil { return op, "", err } - for name, component := range pinnedVersion.Components { - pinnedVersion.Components[name] = normalizeComponent(name, component) + + // Remove components that are not needed in the operator components file. + // These either do not produce images or are not used by the operator. + for _, c := range operatorIgnoreComponents { + delete(pinnedVersion.Components, c) } + logrus.Info("Generating operator components file") operatorComponentsFilePath := filepath.Join(srcDir, operatorComponentsFileName) operatorComponentsFile, err := os.Create(operatorComponentsFilePath) @@ -281,30 +366,18 @@ func LoadHashrelease(repoRootDir, outputDir, hashreleaseSrcBaseDir string, lates ProductVersion: pinnedVersion.Title, OperatorVersion: pinnedVersion.TigeraOperator.Version, Source: filepath.Join(hashreleaseSrcBaseDir, pinnedVersion.Hash), - Time: time.Now(), Latest: latest, }, nil } +// RetrieveImageComponents retrieves the images from Calico components in the pinned version file that produce images. +// It also adds the Tigera operator and its init image to the returned map. func RetrieveImageComponents(outputDir string) (map[string]registry.Component, error) { pinnedVersion, err := retrievePinnedVersion(outputDir) if err != nil { return nil, err } - components := pinnedVersion.Components - for name, component := range components { - // Remove components that do not produce images. - if utils.Contains(excludedComponents, name) { - delete(components, name) - continue - } - components[name] = normalizeComponent(name, component) - } - operator := registry.OperatorComponent{Component: pinnedVersion.TigeraOperator} - components[operator.Image] = operator.Component - initImage := operator.InitImage() - components[initImage.Image] = operator.InitImage() - return components, nil + return pinnedVersion.ImageComponents(true), nil } func RetrieveVersions(outputDir string) (version.Versions, error) { @@ -315,19 +388,3 @@ func RetrieveVersions(outputDir string) (version.Versions, error) { return version.NewHashreleaseVersions(version.New(pinnedVersion.Title), pinnedVersion.TigeraOperator.Version), nil } - -// normalizeComponent normalizes the component image name. -// It checks if the component name is in the registry.ImageMap and replaces it with the mapped value. -// If the image name is not found in the map, it sets it to the component name. -// The image name is also stripped of the calico namespace prefix. -func normalizeComponent(componentName string, c registry.Component) registry.Component { - img := registry.ImageMap[componentName] - if img == "" { - img = componentName - } - c.Image = img - if strings.HasPrefix(img, calicoImageNamespace) { - c.Image = strings.TrimPrefix(img, calicoImageNamespace) - } - return c -} diff --git a/release/internal/pinnedversion/pinnedversion_test.TestGenerateOperatorComponents.approved.txt b/release/internal/pinnedversion/pinnedversion_test.TestGenerateOperatorComponents.approved.txt index 81654b23731..81a4e1fd950 100644 --- a/release/internal/pinnedversion/pinnedversion_test.TestGenerateOperatorComponents.approved.txt +++ b/release/internal/pinnedversion/pinnedversion_test.TestGenerateOperatorComponents.approved.txt @@ -1,82 +1,61 @@ -title: vX.Y.Z +title: v3.31.0 +manifest_url: https://test-release.docs.eng.tigera.net release_name: test-release -note: Test note -full_hash: vX.Y.Z-vA.B.C +note: test-release - generated at [Date1] using release-v3.31 release branch with release-v1.40 operator branch +full_hash: v3.31.0-v1.40.0 tigera-operator: - version: vA.B.C + version: v1.40.0-v3.31.0 image: tigera/operator registry: docker.io components: + api: + version: v3.31.0 + apiserver: + version: v3.31.0 calico: - version: vX.Y.Z - image: calico - calico/api: - version: vX.Y.Z - image: api - calico/apiserver: - version: vX.Y.Z - image: apiserver - calico/cni: - version: vX.Y.Z - image: cni - calico/cni-windows: - version: vX.Y.Z - image: cni-windows - calico/csi: - version: vX.Y.Z - image: csi - calico/dikastes: - version: vX.Y.Z - image: dikastes - calico/envoy-gateway: - version: vX.Y.Z - image: envoy-gateway - calico/envoy-proxy: - version: vX.Y.Z - image: envoy-proxy - calico/envoy-ratelimit: - version: vX.Y.Z - image: envoy-ratelimit - calico/goldmane: - version: vX.Y.Z - image: goldmane - calico/guardian: - version: vX.Y.Z - image: guardian - calico/kube-controllers: - version: vX.Y.Z - image: kube-controllers - calico/node: - version: vX.Y.Z - image: node - calico/node-windows: - version: vX.Y.Z - image: node-windows - calico/whisker: - version: vX.Y.Z - image: whisker - calico/whisker-backend: - version: vX.Y.Z - image: whisker-backend + version: v3.31.0 calicoctl: - version: vX.Y.Z + version: v3.31.0 image: ctl + cni: + version: v3.31.0 + cni-windows: + version: v3.31.0 + csi: + version: v3.31.0 csi-node-driver-registrar: - version: vX.Y.Z + version: v3.31.0 image: node-driver-registrar - flannel: - version: v0.12.0 - image: coreos/flannel - registry: quay.io + dikastes: + version: v3.31.0 + envoy-gateway: + version: v3.31.0 + envoy-proxy: + version: v3.31.0 + envoy-ratelimit: + version: v3.31.0 flexvol: - version: vX.Y.Z + version: v3.31.0 image: pod2daemon-flexvol + goldmane: + version: v3.31.0 + guardian: + version: v3.31.0 key-cert-provisioner: - version: vX.Y.Z - image: key-cert-provisioner + version: v3.31.0 + kube-controllers: + version: v3.31.0 networking-calico: - version: release-v1.0 - image: networking-calico + version: release-v3.31 + node: + version: v3.31.0 + node-windows: + version: v3.31.0 typha: - version: vX.Y.Z - image: typha + version: v3.31.0 + webhooks: + version: v3.31.0 + whisker: + version: v3.31.0 + whisker-backend: + version: v3.31.0 diff --git a/release/internal/pinnedversion/pinnedversion_test.TestGeneratePinnedVersionFile.approved.txt b/release/internal/pinnedversion/pinnedversion_test.TestGeneratePinnedVersionFile.approved.txt new file mode 100644 index 00000000000..0786ac0da45 --- /dev/null +++ b/release/internal/pinnedversion/pinnedversion_test.TestGeneratePinnedVersionFile.approved.txt @@ -0,0 +1,69 @@ +- title: v3.31.0 + manifest_url: https://test-release.docs.eng.tigera.net + release_name: test-release + note: test-release - generated at [Date1] using release-v3.31 release branch with release-v1.40 operator branch + full_hash: v3.31.0-v1.40.0 + tigera-operator: + version: v1.40.0-v3.31.0 + image: tigera/operator + registry: docker.io + components: + api: + version: v3.31.0 + apiserver: + version: v3.31.0 + calico: + version: v3.31.0 + calicoctl: + version: v3.31.0 + image: ctl + cni: + version: v3.31.0 + cni-windows: + version: v3.31.0 + csi: + version: v3.31.0 + csi-node-driver-registrar: + version: v3.31.0 + image: node-driver-registrar + dikastes: + version: v3.31.0 + envoy-gateway: + version: v3.31.0 + envoy-proxy: + version: v3.31.0 + envoy-ratelimit: + version: v3.31.0 + flannel: + version: v0.12.0 + image: coreos/flannel + registry: quay.io + flannel-migration-controller: + version: v3.31.0 + flexvol: + version: v3.31.0 + image: pod2daemon-flexvol + goldmane: + version: v3.31.0 + guardian: + version: v3.31.0 + key-cert-provisioner: + version: v3.31.0 + kube-controllers: + version: v3.31.0 + networking-calico: + version: release-v3.31 + node: + version: v3.31.0 + node-windows: + version: v3.31.0 + test-signer: + version: v3.31.0 + typha: + version: v3.31.0 + webhooks: + version: v3.31.0 + whisker: + version: v3.31.0 + whisker-backend: + version: v3.31.0 diff --git a/release/internal/pinnedversion/pinnedversion_test.TestGeneratePinnedVersionFileFromTemplate.approved.txt b/release/internal/pinnedversion/pinnedversion_test.TestGeneratePinnedVersionFileFromTemplate.approved.txt deleted file mode 100644 index 66a29e80233..00000000000 --- a/release/internal/pinnedversion/pinnedversion_test.TestGeneratePinnedVersionFileFromTemplate.approved.txt +++ /dev/null @@ -1,59 +0,0 @@ -- title: vX.Y.Z - manifests_url: https://test-release.example.com - release_name: test-release - note: Test note - full_hash: vX.Y.Z-vA.B.C - tigera-operator: - version: vA.B.C - image: tigera/operator - registry: docker.io - components: - calico: - version: vX.Y.Z - typha: - version: vX.Y.Z - calicoctl: - version: vX.Y.Z - calico/node: - version: vX.Y.Z - calico/cni: - version: vX.Y.Z - calico/apiserver: - version: vX.Y.Z - calico/kube-controllers: - version: vX.Y.Z - calico/api: - version: vX.Y.Z - calico/goldmane: - version: vX.Y.Z - networking-calico: - version: release-v1.0 - flannel: - version: v0.12.0 - registry: quay.io - calico/dikastes: - version: vX.Y.Z - calico/envoy-gateway: - version: vX.Y.Z - calico/envoy-proxy: - version: vX.Y.Z - calico/envoy-ratelimit: - version: vX.Y.Z - flexvol: - version: vX.Y.Z - key-cert-provisioner: - version: vX.Y.Z - calico/csi: - version: vX.Y.Z - csi-node-driver-registrar: - version: vX.Y.Z - calico/cni-windows: - version: vX.Y.Z - calico/node-windows: - version: vX.Y.Z - calico/guardian: - version: vX.Y.Z - calico/whisker: - version: vX.Y.Z - calico/whisker-backend: - version: vX.Y.Z diff --git a/release/internal/pinnedversion/pinnedversion_test.go b/release/internal/pinnedversion/pinnedversion_test.go index fa862b08f8c..f040ce430f4 100644 --- a/release/internal/pinnedversion/pinnedversion_test.go +++ b/release/internal/pinnedversion/pinnedversion_test.go @@ -22,31 +22,131 @@ import ( approvals "github.com/approvals/go-approval-tests" "github.com/google/go-cmp/cmp" + "github.com/projectcalico/calico/release/internal/command" "github.com/projectcalico/calico/release/internal/registry" + "github.com/projectcalico/calico/release/internal/version" ) -func TestGeneratePinnedVersionFileFromTemplate(t *testing.T) { - data := &calicoTemplateData{ - ReleaseName: "test-release", - BaseDomain: "example.com", - ProductVersion: "vX.Y.Z", - Operator: registry.Component{ - Version: "vA.B.C", +var dateApprovalScrubber = approvals.NewDateScrubber(`[a-zA-Z]{3}, \d{1,2} [a-zA-Z]{3} \d{4} \d{2}:\d{2}:\d{2} [A-Z]{3}`) + +func TestImageComponents(t *testing.T) { + dir := t.TempDir() + rootDir, err := command.GitDir() + if err != nil { + t.Fatalf("failed to get git root dir: %v", err) + } + c := &CalicoPinnedVersions{ + Dir: dir, + RootDir: rootDir, + ReleaseBranchPrefix: "release", + OperatorCfg: OperatorConfig{ Image: "tigera/operator", Registry: "docker.io", + Branch: "release-v1.40", }, - Hash: "vX.Y.Z-vA.B.C", - Note: "Test note", - ReleaseBranch: "release-v1.0", + releaseName: "test-release", + productBranch: "release-v3.31", + versionData: version.NewHashreleaseVersions(version.New("v3.31.0"), "v1.40.0"), + } + if err := generatePinnedVersionFile(c); err != nil { + t.Fatalf("failed to generate pinned version file: %v", err) } - outputDir := t.TempDir() - err := generatePinnedVersionFile(data, outputDir) + p, err := retrievePinnedVersion(dir) if err != nil { - t.Fatalf("failed to generate pinned version file: %v", err) + t.Fatalf("failed to retrieve pinned version: %v", err) } + t.Run("without operator", func(t *testing.T) { + expectedComponents := map[string]registry.Component{ + "typha": {Version: "v3.31.0", Image: "typha"}, + "calicoctl": {Version: "v3.31.0", Image: "ctl"}, + "node": {Version: "v3.31.0", Image: "node"}, + "cni": {Version: "v3.31.0", Image: "cni"}, + "apiserver": {Version: "v3.31.0", Image: "apiserver"}, + "kube-controllers": {Version: "v3.31.0", Image: "kube-controllers"}, + "goldmane": {Version: "v3.31.0", Image: "goldmane"}, + "flannel": {Version: "v0.12.0", Image: "coreos/flannel", Registry: "quay.io"}, + "flannel-migration-controller": {Version: "v3.31.0", Image: "flannel-migration-controller"}, + "dikastes": {Version: "v3.31.0", Image: "dikastes"}, + "envoy-gateway": {Version: "v3.31.0", Image: "envoy-gateway"}, + "envoy-proxy": {Version: "v3.31.0", Image: "envoy-proxy"}, + "envoy-ratelimit": {Version: "v3.31.0", Image: "envoy-ratelimit"}, + "flexvol": {Version: "v3.31.0", Image: "pod2daemon-flexvol"}, + "key-cert-provisioner": {Version: "v3.31.0", Image: "key-cert-provisioner"}, + "test-signer": {Version: "v3.31.0", Image: "test-signer"}, + "csi": {Version: "v3.31.0", Image: "csi"}, + "csi-node-driver-registrar": {Version: "v3.31.0", Image: "node-driver-registrar"}, + "cni-windows": {Version: "v3.31.0", Image: "cni-windows"}, + "node-windows": {Version: "v3.31.0", Image: "node-windows"}, + "guardian": {Version: "v3.31.0", Image: "guardian"}, + "webhooks": {Version: "v3.31.0", Image: "webhooks"}, + "whisker": {Version: "v3.31.0", Image: "whisker"}, + "whisker-backend": {Version: "v3.31.0", Image: "whisker-backend"}, + } + actualComponents := p.ImageComponents(false) + if diff := cmp.Diff(expectedComponents, actualComponents); diff != "" { + t.Errorf("expected components to be same, but they differ: %s", diff) + } + }) + t.Run("with operator", func(t *testing.T) { + expectedComponents := map[string]registry.Component{ + "typha": {Version: "v3.31.0", Image: "typha"}, + "calicoctl": {Version: "v3.31.0", Image: "ctl"}, + "node": {Version: "v3.31.0", Image: "node"}, + "cni": {Version: "v3.31.0", Image: "cni"}, + "apiserver": {Version: "v3.31.0", Image: "apiserver"}, + "kube-controllers": {Version: "v3.31.0", Image: "kube-controllers"}, + "goldmane": {Version: "v3.31.0", Image: "goldmane"}, + "flannel": {Version: "v0.12.0", Image: "coreos/flannel", Registry: "quay.io"}, + "flannel-migration-controller": {Version: "v3.31.0", Image: "flannel-migration-controller"}, + "dikastes": {Version: "v3.31.0", Image: "dikastes"}, + "envoy-gateway": {Version: "v3.31.0", Image: "envoy-gateway"}, + "envoy-proxy": {Version: "v3.31.0", Image: "envoy-proxy"}, + "envoy-ratelimit": {Version: "v3.31.0", Image: "envoy-ratelimit"}, + "flexvol": {Version: "v3.31.0", Image: "pod2daemon-flexvol"}, + "key-cert-provisioner": {Version: "v3.31.0", Image: "key-cert-provisioner"}, + "test-signer": {Version: "v3.31.0", Image: "test-signer"}, + "csi": {Version: "v3.31.0", Image: "csi"}, + "csi-node-driver-registrar": {Version: "v3.31.0", Image: "node-driver-registrar"}, + "cni-windows": {Version: "v3.31.0", Image: "cni-windows"}, + "node-windows": {Version: "v3.31.0", Image: "node-windows"}, + "guardian": {Version: "v3.31.0", Image: "guardian"}, + "webhooks": {Version: "v3.31.0", Image: "webhooks"}, + "whisker": {Version: "v3.31.0", Image: "whisker"}, + "whisker-backend": {Version: "v3.31.0", Image: "whisker-backend"}, + "tigera/operator": {Version: "v1.40.0-v3.31.0", Image: "tigera/operator", Registry: "docker.io"}, + "tigera/operator-init": {Version: "v1.40.0-v3.31.0", Image: "tigera/operator-init", Registry: "docker.io"}, + } + actualComponents := p.ImageComponents(true) + if diff := cmp.Diff(expectedComponents, actualComponents); diff != "" { + t.Errorf("expected components to be same, but they differ: %s", diff) + } + }) +} - pinnedVersionPath := PinnedVersionFilePath(outputDir) +func TestGeneratePinnedVersionFile(t *testing.T) { + dir := t.TempDir() + rootDir, err := command.GitDir() + if err != nil { + t.Fatalf("failed to get git root dir: %v", err) + } + p := &CalicoPinnedVersions{ + Dir: dir, + RootDir: rootDir, + ReleaseBranchPrefix: "release", + OperatorCfg: OperatorConfig{ + Image: "tigera/operator", + Registry: "docker.io", + Branch: "release-v1.40", + }, + releaseName: "test-release", + productBranch: "release-v3.31", + versionData: version.NewHashreleaseVersions(version.New("v3.31.0"), "v1.40.0"), + } + if err := generatePinnedVersionFile(p); err != nil { + t.Fatalf("failed to generate pinned version file: %v", err) + } + pinnedVersionPath := PinnedVersionFilePath(dir) if _, err := os.Stat(pinnedVersionPath); err != nil { t.Fatalf("pinned version file not created: %v", err) } @@ -54,26 +154,29 @@ func TestGeneratePinnedVersionFileFromTemplate(t *testing.T) { if err != nil { t.Fatalf("failed to read pinned version file: %v", err) } - approvals.VerifyString(t, string(content)) + approvals.VerifyString(t, string(content), approvals.Options().WithScrubber(dateApprovalScrubber)) } func TestGenerateOperatorComponents(t *testing.T) { dir := t.TempDir() - data := &calicoTemplateData{ - ReleaseName: "test-release", - BaseDomain: "example.com", - ProductVersion: "vX.Y.Z", - Operator: registry.Component{ - Version: "vA.B.C", + rootDir, err := command.GitDir() + if err != nil { + t.Fatalf("failed to get git root dir: %v", err) + } + p := &CalicoPinnedVersions{ + Dir: dir, + RootDir: rootDir, + ReleaseBranchPrefix: "release", + OperatorCfg: OperatorConfig{ Image: "tigera/operator", Registry: "docker.io", + Branch: "release-v1.40", }, - Hash: "vX.Y.Z-vA.B.C", - Note: "Test note", - ReleaseBranch: "release-v1.0", + releaseName: "test-release", + productBranch: "release-v3.31", + versionData: version.NewHashreleaseVersions(version.New("v3.31.0"), "v1.40.0"), } - err := generatePinnedVersionFile(data, dir) - if err != nil { + if err := generatePinnedVersionFile(p); err != nil { t.Fatalf("failed to generate pinned version file: %v", err) } op, path, err := GenerateOperatorComponents(dir, dir) @@ -82,7 +185,7 @@ func TestGenerateOperatorComponents(t *testing.T) { } expectedOperator := registry.OperatorComponent{ Component: registry.Component{ - Version: "vA.B.C", + Version: "v1.40.0-v3.31.0", Image: "tigera/operator", Registry: "docker.io", }, @@ -98,201 +201,5 @@ func TestGenerateOperatorComponents(t *testing.T) { if err != nil { t.Fatalf("failed to read operator components file: %v", err) } - approvals.VerifyString(t, string(content)) -} - -func TestRetrieveImageComponents(t *testing.T) { - t.Run("default", func(t *testing.T) { - dir := t.TempDir() - data := &calicoTemplateData{ - ReleaseName: "test-release", - BaseDomain: "example.com", - ProductVersion: "vX.Y.Z", - Operator: registry.Component{ - Version: "vA.B.C", - Image: "tigera/operator", - Registry: "docker.io", - }, - Hash: "vX.Y.Z-vA.B.C", - Note: "Test note", - ReleaseBranch: "release-v1.0", - } - err := generatePinnedVersionFile(data, dir) - if err != nil { - t.Fatalf("failed to generate pinned version file: %v", err) - } - retrievedComponents, err := RetrieveImageComponents(dir) - if err != nil { - t.Fatalf("failed to retrieve image components: %v", err) - } - expectedComponents := map[string]registry.Component{ - "typha": {Version: "vX.Y.Z", Image: "typha"}, - "calicoctl": {Version: "vX.Y.Z", Image: "ctl"}, - "calico/node": {Version: "vX.Y.Z", Image: "node"}, - "calico/cni": {Version: "vX.Y.Z", Image: "cni"}, - "calico/apiserver": {Version: "vX.Y.Z", Image: "apiserver"}, - "calico/kube-controllers": {Version: "vX.Y.Z", Image: "kube-controllers"}, - "calico/goldmane": {Version: "vX.Y.Z", Image: "goldmane"}, - "calico/dikastes": {Version: "vX.Y.Z", Image: "dikastes"}, - "calico/envoy-gateway": {Version: "vX.Y.Z", Image: "envoy-gateway"}, - "calico/envoy-proxy": {Version: "vX.Y.Z", Image: "envoy-proxy"}, - "calico/envoy-ratelimit": {Version: "vX.Y.Z", Image: "envoy-ratelimit"}, - "flexvol": {Version: "vX.Y.Z", Image: "pod2daemon-flexvol"}, - "key-cert-provisioner": {Version: "vX.Y.Z", Image: "key-cert-provisioner"}, - "calico/csi": {Version: "vX.Y.Z", Image: "csi"}, - "calico/csi-node-driver-registrar": {Version: "vX.Y.Z", Image: "node-driver-registrar"}, - "calico/cni-windows": {Version: "vX.Y.Z", Image: "cni-windows"}, - "calico/node-windows": {Version: "vX.Y.Z", Image: "node-windows"}, - "calico/guardian": {Version: "vX.Y.Z", Image: "guardian"}, - "calico/whisker": {Version: "vX.Y.Z", Image: "whisker"}, - "calico/whisker-backend": {Version: "vX.Y.Z", Image: "whisker-backend"}, - "tigera/operator": {Version: "vA.B.C", Image: "tigera/operator", Registry: "docker.io"}, - "tigera/operator-init": {Version: "vA.B.C", Image: "tigera/operator-init", Registry: "docker.io"}, - } - if cmp.Equal(expectedComponents, retrievedComponents) { - t.Errorf("expected components to be same, but they differ: %s", cmp.Diff(expectedComponents, retrievedComponents)) - } - }) - - t.Run("operator in calico namespace", func(t *testing.T) { - dir := t.TempDir() - data := &calicoTemplateData{ - ReleaseName: "test-release", - BaseDomain: "example.com", - ProductVersion: "vX.Y.Z", - Operator: registry.Component{ - Version: "vA.B.C", - Image: "calico/operator", - }, - Hash: "vX.Y.Z-vA.B.C", - Note: "Test note", - ReleaseBranch: "release-v1.0", - } - err := generatePinnedVersionFile(data, dir) - if err != nil { - t.Fatalf("failed to generate pinned version file: %v", err) - } - retrievedComponents, err := RetrieveImageComponents(dir) - if err != nil { - t.Fatalf("failed to retrieve image components: %v", err) - } - expectedComponents := map[string]registry.Component{ - "typha": {Version: "vX.Y.Z", Image: "typha"}, - "calicoctl": {Version: "vX.Y.Z", Image: "ctl"}, - "calico/node": {Version: "vX.Y.Z", Image: "node"}, - "calico/cni": {Version: "vX.Y.Z", Image: "cni"}, - "calico/apiserver": {Version: "vX.Y.Z", Image: "apiserver"}, - "calico/kube-controllers": {Version: "vX.Y.Z", Image: "kube-controllers"}, - "calico/goldmane": {Version: "vX.Y.Z", Image: "goldmane"}, - "calico/dikastes": {Version: "vX.Y.Z", Image: "dikastes"}, - "calico/envoy-gateway": {Version: "vX.Y.Z", Image: "envoy-gateway"}, - "calico/envoy-proxy": {Version: "vX.Y.Z", Image: "envoy-proxy"}, - "calico/envoy-ratelimit": {Version: "vX.Y.Z", Image: "envoy-ratelimit"}, - "flexvol": {Version: "vX.Y.Z", Image: "pod2daemon-flexvol"}, - "key-cert-provisioner": {Version: "vX.Y.Z", Image: "key-cert-provisioner"}, - "calico/csi": {Version: "vX.Y.Z", Image: "csi"}, - "calico/csi-node-driver-registrar": {Version: "vX.Y.Z", Image: "node-driver-registrar"}, - "calico/cni-windows": {Version: "vX.Y.Z", Image: "cni-windows"}, - "calico/node-windows": {Version: "vX.Y.Z", Image: "node-windows"}, - "calico/guardian": {Version: "vX.Y.Z", Image: "guardian"}, - "calico/whisker": {Version: "vX.Y.Z", Image: "whisker"}, - "calico/whisker-backend": {Version: "vX.Y.Z", Image: "whisker-backend"}, - "calico/operator": {Version: "vA.B.C", Image: "calico/operator"}, - "calico/operator-init": {Version: "vA.B.C", Image: "calico/operator-init"}, - } - if cmp.Equal(expectedComponents, retrievedComponents) { - t.Errorf("expected components to be same, but they differ: %s", cmp.Diff(expectedComponents, retrievedComponents)) - } - }) -} - -func TestNormalizeComponent(t *testing.T) { - for _, tt := range []struct { - componentName string - component registry.Component - expected registry.Component - }{ - // components in registry.ImageMap - { - componentName: "typha", - component: registry.Component{ - Version: "v1.0.0", - }, - expected: registry.Component{ - Version: "v1.0.0", - Image: "typha", - }, - }, - { - componentName: "calicoctl", - component: registry.Component{ - Version: "v1.0.0", - }, - expected: registry.Component{ - Version: "v1.0.0", - Image: "ctl", - }, - }, - { - componentName: "flannel", - component: registry.Component{ - Version: "v0.14.0", - Registry: "quay.io", - }, - expected: registry.Component{ - Version: "v0.14.0", - Image: "coreos/flannel", - Registry: "quay.io", - }, - }, - { - componentName: "flexvol", - component: registry.Component{ - Version: "v1.0.0", - }, - expected: registry.Component{ - Version: "v1.0.0", - Image: "pod2daemon-flexvol", - }, - }, - { - componentName: "key-cert-provisioner", - component: registry.Component{ - Version: "v1.0.0", - }, - expected: registry.Component{ - Version: "v1.0.0", - Image: "key-cert-provisioner", - }, - }, - { - componentName: "csi-node-driver-registrar", - component: registry.Component{ - Version: "v1.0.0", - }, - expected: registry.Component{ - Version: "v1.0.0", - Image: "node-driver-registrar", - }, - }, - // components not in registry.ImageMap - { - componentName: "calico/cni", - component: registry.Component{ - Version: "v1.0.0", - Image: "cni", - }, - expected: registry.Component{ - Version: "v1.0.0", - Image: "cni", - }, - }, - } { - t.Run(tt.componentName, func(t *testing.T) { - got := normalizeComponent(tt.componentName, tt.component) - if got != tt.expected { - t.Errorf("expected %v, got %v", tt.expected, got) - } - }) - } + approvals.VerifyString(t, string(content), approvals.Options().WithScrubber(dateApprovalScrubber)) } diff --git a/release/internal/pinnedversion/release.go b/release/internal/pinnedversion/release.go deleted file mode 100644 index 41aacd29d68..00000000000 --- a/release/internal/pinnedversion/release.go +++ /dev/null @@ -1,81 +0,0 @@ -package pinnedversion - -import ( - "fmt" - "html/template" - "os" - "strings" - - "github.com/projectcalico/calico/release/internal/hashreleaseserver" - "github.com/projectcalico/calico/release/internal/registry" - "github.com/projectcalico/calico/release/internal/version" -) - -type CalicoReleaseVersions struct { - // Dir is the directory to store the pinned version file. - Dir string - - ProductVersion string - ReleaseBranchPrefix string - - OperatorCfg OperatorConfig - OperatorVersion string - - versionFilePath string -} - -func (p *CalicoReleaseVersions) GenerateFile() (version.Versions, error) { - ver := version.New(p.ProductVersion) - - tmplData := &calicoTemplateData{ - BaseDomain: hashreleaseserver.BaseDomain, - ProductVersion: p.ProductVersion, - Operator: registry.Component{ - Version: p.OperatorVersion, - Image: p.OperatorCfg.Image, - Registry: p.OperatorCfg.Registry, - }, - ReleaseBranch: fmt.Sprintf("%s-%s", p.ReleaseBranchPrefix, ver.Stream()), - } - - tmpl, err := template.New("versions").Parse(calicoTemplate) - if err != nil { - return nil, err - } - if err := os.MkdirAll(p.Dir, 0o755); err != nil { - return nil, err - } - p.versionFilePath = PinnedVersionFilePath(p.Dir) - pinnedVersionFile, err := os.Create(p.versionFilePath) - if err != nil { - return nil, err - } - defer func() { _ = pinnedVersionFile.Close() }() - if err := tmpl.Execute(pinnedVersionFile, tmplData); err != nil { - return nil, err - } - return nil, nil -} - -func (p *CalicoReleaseVersions) ImageList() ([]string, error) { - components, err := RetrieveImageComponents(p.Dir) - if err != nil { - return nil, err - } - componentNames := make([]string, 0, len(components)) - for _, component := range components { - if component.Image == registry.TigeraOperatorImage { - continue - } - componentNames = append(componentNames, strings.TrimPrefix(component.Image, calicoImageNamespace)) - } - return componentNames, nil -} - -func (p *CalicoReleaseVersions) FlannelVersion() (string, error) { - versions, err := retrievePinnedVersion(p.Dir) - if err != nil { - return "", err - } - return versions.Components["flannel"].Version, nil -} diff --git a/release/internal/pinnedversion/templates/calico-versions.yaml.gotmpl b/release/internal/pinnedversion/templates/calico-versions.yaml.gotmpl deleted file mode 100644 index 630baaa8b97..00000000000 --- a/release/internal/pinnedversion/templates/calico-versions.yaml.gotmpl +++ /dev/null @@ -1,59 +0,0 @@ -- title: {{.ProductVersion}} - manifests_url: {{.ReleaseURL}} - release_name: {{.ReleaseName}} - note: {{.Note}} - full_hash: {{.Hash}} - tigera-operator: - version: {{.Operator.Version}} - image: {{.Operator.Image}} - registry: {{.Operator.Registry}} - components: - calico: - version: {{.ProductVersion}} - typha: - version: {{.ProductVersion}} - calicoctl: - version: {{.ProductVersion}} - calico/node: - version: {{.ProductVersion}} - calico/cni: - version: {{.ProductVersion}} - calico/apiserver: - version: {{.ProductVersion}} - calico/kube-controllers: - version: {{.ProductVersion}} - calico/api: - version: {{.ProductVersion}} - calico/goldmane: - version: {{.ProductVersion}} - networking-calico: - version: {{.ReleaseBranch}} - flannel: - version: v0.12.0 - registry: quay.io - calico/dikastes: - version: {{.ProductVersion}} - calico/envoy-gateway: - version: {{.ProductVersion}} - calico/envoy-proxy: - version: {{.ProductVersion}} - calico/envoy-ratelimit: - version: {{.ProductVersion}} - flexvol: - version: {{.ProductVersion}} - key-cert-provisioner: - version: {{.ProductVersion}} - calico/csi: - version: {{.ProductVersion}} - csi-node-driver-registrar: - version: {{.ProductVersion}} - calico/cni-windows: - version: {{.ProductVersion}} - calico/node-windows: - version: {{.ProductVersion}} - calico/guardian: - version: {{.ProductVersion}} - calico/whisker: - version: {{.ProductVersion}} - calico/whisker-backend: - version: {{.ProductVersion}} diff --git a/release/internal/registry/component_test.go b/release/internal/registry/component_test.go index 525045389c2..cc98a2a142b 100644 --- a/release/internal/registry/component_test.go +++ b/release/internal/registry/component_test.go @@ -28,18 +28,27 @@ func TestComponentString(t *testing.T) { name: "without registry", component: Component{ Version: "1.2.3", - Image: "calico/node", + Image: "node", }, - expected: "calico/node:1.2.3", + expected: "node:1.2.3", }, { - name: "with registry", + name: "with registry - Calico component", component: Component{ Version: "1.2.3", - Image: "calico/node", - Registry: "docker.io", + Image: "node", + Registry: "quay.io/calico", }, - expected: "docker.io/calico/node:1.2.3", + expected: "quay.io/calico/node:1.2.3", + }, + { + name: "with registry - Tigera operator", + component: Component{ + Version: "1.2.3", + Image: "tigera/operator", + Registry: "quay.io", + }, + expected: "quay.io/tigera/operator:1.2.3", }, } { t.Run(tc.name, func(t *testing.T) { diff --git a/release/internal/registry/image.go b/release/internal/registry/image.go index e1b88db4898..97d6d0ed822 100644 --- a/release/internal/registry/image.go +++ b/release/internal/registry/image.go @@ -24,16 +24,6 @@ import ( const TigeraOperatorImage = "tigera/operator" -// ImageMap maps the image name to the repository. -var ImageMap = map[string]string{ - "typha": "calico/typha", - "calicoctl": "calico/ctl", - "flannel": "coreos/flannel", - "flexvol": "calico/pod2daemon-flexvol", - "key-cert-provisioner": "calico/key-cert-provisioner", - "csi-node-driver-registrar": "calico/node-driver-registrar", -} - func CheckImage(image string) (bool, error) { ref, err := name.ParseReference(image) if err != nil { diff --git a/release/internal/registry/registry.go b/release/internal/registry/registry.go index d5923ebe43e..86f6cb2b515 100644 --- a/release/internal/registry/registry.go +++ b/release/internal/registry/registry.go @@ -14,11 +14,17 @@ package registry +const DefaultCalicoRegistry = "quay.io/calico" + var DefaultCalicoRegistries = []string{ "docker.io/calico", - "quay.io/calico", + DefaultCalicoRegistry, "gcr.io/projectcalico-org", "eu.gcr.io/projectcalico-org", "asia.gcr.io/projectcalico-org", "us.gcr.io/projectcalico-org", } + +var DefaultHelmRegistries = []string{ + "quay.io/calico/charts", +} diff --git a/release/internal/slack/message.go b/release/internal/slack/message.go index 606bd8859b9..d9a337eab9f 100644 --- a/release/internal/slack/message.go +++ b/release/internal/slack/message.go @@ -40,12 +40,17 @@ type HashreleaseMessageData struct { ImageScanResultURL string } +type MessageResponse struct { + Channel string + Timestamp string +} + // PostHashreleaseAnnouncement sends a message to slack about a hashrelease being published. -func PostHashreleaseAnnouncement(cfg *Config, msg *HashreleaseMessageData) error { +func PostHashreleaseAnnouncement(cfg *Config, msg *HashreleaseMessageData) (*MessageResponse, error) { message, err := renderMessage(publishedMessageTemplateData, msg) if err != nil { logrus.WithError(err).Error("Failed to render message") - return err + return nil, err } return sendToSlack(cfg, message) } @@ -69,15 +74,15 @@ func renderMessage(text string, data any) ([]slack.Block, error) { } // sendToSlack sends a message to slack. -func sendToSlack(cfg *Config, message []slack.Block) error { +func sendToSlack(cfg *Config, message []slack.Block) (*MessageResponse, error) { if cfg == nil { - return fmt.Errorf("no configuration provided") + return nil, fmt.Errorf("no configuration provided") } if !cfg.Valid() { - return fmt.Errorf("invalid or missing configuration") + return nil, fmt.Errorf("invalid or missing configuration") } client := slack.New(cfg.Token, slack.OptionDebug(logrus.IsLevelEnabled(logrus.DebugLevel))) - _, _, err := client.PostMessage(cfg.Channel, slack.MsgOptionBlocks(message...)) - return err + ch, ts, err := client.PostMessage(cfg.Channel, slack.MsgOptionBlocks(message...)) + return &MessageResponse{Channel: ch, Timestamp: ts}, err } diff --git a/release/internal/utils/files.go b/release/internal/utils/files.go index 86ded5e673e..d39d153d95e 100644 --- a/release/internal/utils/files.go +++ b/release/internal/utils/files.go @@ -15,9 +15,14 @@ package utils import ( + "errors" "fmt" + "io/fs" "os" + "os/exec" "path/filepath" + + "github.com/sirupsen/logrus" ) const ( @@ -58,3 +63,40 @@ func CopyFile(src, dst string) error { } return nil } + +// PathExists validates if a given (relative or absolute) path exists +func PathExists(path string) (bool, error) { + _, err := os.Stat(path) + if err == nil { + return true, nil + } + if errors.Is(err, fs.ErrNotExist) { + return false, nil + } + return false, err +} + +// DirExists validates if a given (relative or absolute) path exists and +// is a directory (or as symlink to one) +func DirExists(path string) (bool, error) { + stat, err := os.Stat(path) + if err == nil { + return stat.IsDir(), nil + } + if errors.Is(err, fs.ErrNotExist) { + return false, nil + } + return false, err +} + +// CheckBinary searches the current PATH for a binary and returns an error if it's not found +func CheckBinary(binaryName, neededFor string) error { + if path, err := exec.LookPath(binaryName); err != nil { + logrus.WithError(err).Errorf("Error trying to find %s in PATH (needed for %s)", binaryName, neededFor) + return fmt.Errorf("unable to find %s in PATH (needed for %s)", binaryName, neededFor) + } else if path == "" { + logrus.Errorf("%s not found in PATH (needed for %s)", binaryName, neededFor) + return fmt.Errorf("%s not found in PATH (needed for %s)", binaryName, neededFor) + } + return nil +} diff --git a/release/internal/utils/gpg.go b/release/internal/utils/gpg.go new file mode 100644 index 00000000000..a31dfe821b7 --- /dev/null +++ b/release/internal/utils/gpg.go @@ -0,0 +1,37 @@ +// Copyright (c) 2026 Tigera, Inc. All rights reserved. + +// 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 ( + "fmt" + "strings" + + "github.com/sirupsen/logrus" + + "github.com/projectcalico/calico/release/internal/command" +) + +// GetGPGPubKey takes a GPG key ID and fetches the ascii-armored GPG public key +func GetGPGPubKey(gpgKeyID string) (string, error) { + logrus.Debugf("Getting ascii-armored public key for GPG key %s", gpgKeyID) + + cmdArgs := []string{"--armor", "--export", gpgKeyID} + logrus.Debugf("running gpg with args %s", strings.Join(cmdArgs, " ")) + gpgOut, err := command.Run("gpg", cmdArgs) + if err != nil { + return "", fmt.Errorf("exporting gpg key: %w", err) + } + return string(gpgOut), nil +} diff --git a/release/internal/utils/utils.go b/release/internal/utils/utils.go index cbfb7d1039b..768ebfe1a76 100644 --- a/release/internal/utils/utils.go +++ b/release/internal/utils/utils.go @@ -14,6 +14,19 @@ package utils +import ( + "fmt" + "os" + "path/filepath" + "slices" + "strings" + "sync" + + "github.com/sirupsen/logrus" + + "github.com/projectcalico/calico/release/internal/command" +) + const ( // ProductName is used in the release process to identify the product. ProductName = CalicoProductName @@ -38,14 +51,103 @@ const ( // TigeraOrg is the name of the Tigera organization. TigeraOrg = "tigera" + + // TigeraCompany is the short-form human-facing name of the company, for freeform text fields or branding + TigeraCompany = "Tigera" + + // TigeraOperatorChart is the name of the Tigera Operator Helm chart. + TigeraOperatorChart = "tigera-operator" + + // ProjectCalicoV1CRDsChart is the name of the crd.projectcalico.org/v1 CRD helm chart. + ProjectCalicoV1CRDsChart = "crd.projectcalico.org.v1" + + // ProjectCalicoV3CRDsChart is the name of the projectcalico.org/v3 CRD helm chart. + ProjectCalicoV3CRDsChart = "projectcalico.org.v3" + + // CalicoHelmRepoURL is the URL for the Calico Helm charts. + CalicoHelmRepoURL = "https://docs.tigera.io/calico/charts" ) -// Contains returns true if the a string is in a string slice. -func Contains(haystack []string, needle string) bool { - for _, item := range haystack { - if item == needle { - return true +// AllReleaseCharts returns a list of all Helm charts to be released. +func AllReleaseCharts() []string { + return []string{ + TigeraOperatorChart, + ProjectCalicoV1CRDsChart, + ProjectCalicoV3CRDsChart, + } +} + +var once sync.Once + +var ( + ImageReleaseDirs = []string{ + "apiserver", + "app-policy", + "calicoctl", + "cni-plugin", + "goldmane", + "guardian", + "key-cert-provisioner", + "kube-controllers", + "node", + "pod2daemon", + "third_party/envoy-gateway", + "third_party/envoy-proxy", + "third_party/envoy-ratelimit", + "typha", + "webhooks", + "whisker", + "whisker-backend", + } + releaseImages = []string{} +) + +func initReleaseImages() { + rootDir, err := command.GitDir() + if err != nil { + logrus.Panicf("Cannot determine root git dir: %v", err) + } + images, err := BuildReleaseImageList(rootDir, ImageReleaseDirs...) + if err != nil { + logrus.Panicf("Cannot build release images list for release dirs[%s]: %v", strings.Join(ImageReleaseDirs, ","), err) + } + releaseImages = images +} + +func ReleaseImages() []string { + once.Do(initReleaseImages) + return slices.Clone(releaseImages) +} + +// buildImages returns the list of images built by the given directory. +// It does this by calling a make target that returns the values of BUILD_IMAGES and WINDOWS_IMAGE (if set). +func buildImages(dir string, release bool) ([]string, error) { + env := os.Environ() + if release { + env = append(env, "RELEASE=true") + } + out, err := command.MakeInDir(dir, []string{"-s", "build-images"}, env) + if err != nil { + logrus.Error(out) + return nil, fmt.Errorf("failed to get images for release dir %s: %w", dir, err) + } + return strings.Fields(out), nil +} + +// BuildReleaseImageList builds a list of images to be released from the given directories. +func BuildReleaseImageList(rootDir string, dirs ...string) ([]string, error) { + if len(dirs) == 0 { + logrus.WithField("root_dir", rootDir).Warnf("No image release dirs specified, will get images from root dir instead") + return buildImages(rootDir, true) + } + combinedImages := []string{} + for _, d := range dirs { + dir := filepath.Join(rootDir, d) + images, err := buildImages(dir, true) + if err != nil { + return nil, err } + combinedImages = append(combinedImages, images...) } - return false + return combinedImages, nil } diff --git a/release/internal/utils/utils_test.go b/release/internal/utils/utils_test.go new file mode 100644 index 00000000000..6f36b69bd83 --- /dev/null +++ b/release/internal/utils/utils_test.go @@ -0,0 +1,101 @@ +// Copyright (c) 2024 Tigera, Inc. All rights reserved. + +// 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 ( + "path/filepath" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/projectcalico/calico/release/internal/command" +) + +func TestReleaseDirsImages(t *testing.T) { + repoRoot, err := command.GitDir() + if err != nil { + t.Fatalf("failed to get repo root dir: %v", err) + } + for _, tc := range []struct { + name string + repoRoot string + releaseDirs []string + wantErr bool + expectedImages []string + }{ + { + name: "no release dirs", + repoRoot: filepath.Join(repoRoot, "apiserver"), + wantErr: false, + expectedImages: []string{ + "apiserver", + }, + }, + { + name: "single release dir", + releaseDirs: []string{ + "apiserver", + }, + expectedImages: []string{ + "apiserver", + }, + }, + { + name: "release dir with windows image", + releaseDirs: []string{ + "cni-plugin", + }, + expectedImages: []string{ + "cni", + "cni-windows", + }, + }, + { + name: "release dir with no windows image", + releaseDirs: []string{ + "apiserver", + }, + expectedImages: []string{ + "apiserver", + }, + }, + { + name: "release dir with no output", + releaseDirs: []string{ + "api", + }, + wantErr: true, + }, + } { + t.Run(tc.name, func(t *testing.T) { + if tc.repoRoot == "" { + tc.repoRoot = repoRoot + } + images, err := BuildReleaseImageList(tc.repoRoot, tc.releaseDirs...) + if err != nil && !tc.wantErr { + t.Fatalf("unexpected error: %v", err) + } + if err == nil && tc.wantErr { + t.Fatal("expected error but got none") + } + if diff := cmp.Diff(tc.expectedImages, images, cmpopts.SortSlices(func(a, b string) bool { + return a < b + })); diff != "" { + t.Errorf("images mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/release/internal/version/version.go b/release/internal/version/version.go index 3629c57acc8..c4d7db8d55b 100644 --- a/release/internal/version/version.go +++ b/release/internal/version/version.go @@ -37,7 +37,7 @@ type Versions interface { ReleaseBranch(releaseBranchPrefix string) string } -func NewHashreleaseVersions(calico Version, operator string) Versions { +func NewHashreleaseVersions(calico Version, operator string) *HashreleaseVersions { return &HashreleaseVersions{ calico: calico, operator: operator, @@ -289,8 +289,8 @@ func versionFromManifest(repoRoot, manifest, imgMatch string) (Version, error) { return "", fmt.Errorf("failed to grep for image in manifest %s: %s", manifest, err) } - imgs := strings.Split(out, "\n") - for _, i := range imgs { + imgs := strings.SplitSeq(out, "\n") + for i := range imgs { if strings.Contains(i, imgMatch) { splits := strings.SplitAfter(i, ":") ver := splits[len(splits)-1] diff --git a/release/pkg/manager/branch/manager.go b/release/pkg/manager/branch/manager.go index 0347288eb9f..b9fc9f62313 100644 --- a/release/pkg/manager/branch/manager.go +++ b/release/pkg/manager/branch/manager.go @@ -24,6 +24,10 @@ import ( "github.com/projectcalico/calico/release/internal/version" ) +type RepoManager interface { + SetupReleaseBranch(branch string) error +} + type BranchManager struct { // repoRoot is the absolute path to the root directory of the repository repoRoot string @@ -45,6 +49,10 @@ type BranchManager struct { // publish indicates if we should push the branch changes to the remote repository publish bool + + // repoManager is responsible for handling repository specific operations + // required during branch cut. If none specified, no repository specific operations will be performed. + repoManager RepoManager } func NewManager(opts ...Option) *BranchManager { @@ -101,7 +109,14 @@ func (b *BranchManager) CutVersionedBranch(stream string) error { if _, err := b.git("checkout", "-b", newBranchName); err != nil { return err } + if b.repoManager != nil { + logrus.Infof("Performing setup necessary for %s branch", newBranchName) + if err := b.repoManager.SetupReleaseBranch(newBranchName); err != nil { + return fmt.Errorf("failed to set up release branch: %s", err) + } + } if b.publish { + logrus.WithField("branch", newBranchName).Infof("Pushing new release branch to remote '%s'", b.remote) if _, err := b.git("push", b.remote, newBranchName); err != nil { return err } @@ -109,11 +124,17 @@ func (b *BranchManager) CutVersionedBranch(stream string) error { return nil } +// CutReleaseBranch creates a new release branch from the main branch, +// run branch cut setup by the RepoManager, +// and updates the main branch to the next development version. func (b *BranchManager) CutReleaseBranch() error { if b.validate { if err := b.PreBranchCutValidation(); err != nil { return fmt.Errorf("pre-branch cut validation failed: %s", err) } + if b.repoManager == nil { + return fmt.Errorf("no repository manager configured") + } } if _, err := b.git("fetch", b.remote); err != nil { return fmt.Errorf("failed to fetch remote %s: %s", b.remote, err) @@ -142,6 +163,10 @@ func (b *BranchManager) CutReleaseBranch() error { return err } if b.publish { + logrus.WithFields(logrus.Fields{ + "branch": b.mainBranch, + "tag": nextVersionTag, + }).Infof("Pushing updated main branch and new development tag to remote '%s'", b.remote) if _, err := b.git("push", b.remote, b.mainBranch); err != nil { return err } diff --git a/release/pkg/manager/branch/options.go b/release/pkg/manager/branch/options.go index 5e5362dc5d3..cff01c50e6a 100644 --- a/release/pkg/manager/branch/options.go +++ b/release/pkg/manager/branch/options.go @@ -64,3 +64,10 @@ func WithPublish(publish bool) Option { return nil } } + +func WithRepoManager(m RepoManager) Option { + return func(b *BranchManager) error { + b.repoManager = m + return nil + } +} diff --git a/release/pkg/manager/calico/manager.go b/release/pkg/manager/calico/manager.go index f28a08c45dc..bf31af92746 100644 --- a/release/pkg/manager/calico/manager.go +++ b/release/pkg/manager/calico/manager.go @@ -18,6 +18,7 @@ import ( "errors" "fmt" "maps" + "net/url" "os" "path/filepath" "reflect" @@ -43,79 +44,42 @@ var ( defaultRegistries = registry.DefaultCalicoRegistries // Directories that publish images. - imageReleaseDirs = []string{ - "third_party/envoy-gateway", - "third_party/envoy-proxy", - "third_party/envoy-ratelimit", - "apiserver", - "app-policy", - "calicoctl", - "cni-plugin", - "key-cert-provisioner", - "kube-controllers", - "node", - "pod2daemon", - "typha", - "goldmane", - "whisker", - "whisker-backend", - "guardian", - } + imageReleaseDirs = utils.ImageReleaseDirs - // Directories for Windows. + // Directories that publish windows images. windowsReleaseDirs = []string{ "node", "cni-plugin", } - // images that should be expected for a release. - // This list needs to be kept up-to-date - // with the actual release artifacts produced for a release - // as images are added or removed. - images = []string{ - "apiserver", - "cni", - "csi", - "ctl", - "dikastes", - "envoy-gateway", - "envoy-proxy", - "envoy-ratelimit", - "guardian", - "key-cert-provisioner", - "kube-controllers", - "node", - "node-driver-registrar", - "pod2daemon-flexvol", - "test-signer", - "typha", - "goldmane", - "whisker", - "whisker-backend", - } - windowsImages = []string{ - "cni-windows", - "node-windows", - } - metadataFileName = "metadata.yaml" + + helmIndexFileName = "index.yaml" + + s3ACLPublicRead = []string{"--acl", "public-read"} ) func NewManager(opts ...Option) *CalicoManager { // Configure defaults here. b := &CalicoManager{ - runner: &command.RealCommandRunner{}, - productCode: utils.CalicoProductCode, - validate: true, - validateBranch: true, - buildImages: true, - publishImages: true, - archiveImages: true, - publishTag: true, - publishGithub: true, - imageRegistries: defaultRegistries, - operatorRegistry: operator.DefaultRegistry, - operatorImage: operator.DefaultImage, + runner: &command.RealCommandRunner{}, + productCode: utils.CalicoProductCode, + validate: true, + validateBranch: true, + buildImages: true, + publishImages: true, + publishCharts: true, + archiveImages: true, + publishTag: true, + publishGithub: true, + imageRegistries: defaultRegistries, + helmRegistries: registry.DefaultHelmRegistries, + helmRepoURL: utils.CalicoHelmRepoURL, + operatorRegistry: operator.DefaultRegistry, + operatorImage: operator.DefaultImage, + operatorGithubOrg: operator.DefaultOrg, + operatorRepo: operator.DefaultRepoName, + operatorBranch: operator.DefaultBranchName, } // Run through provided options. @@ -129,6 +93,7 @@ func NewManager(opts ...Option) *CalicoManager { if b.repoRoot == "" { logrus.Fatal("No repo root specified") } + logrus.WithField("repoRoot", b.repoRoot).Info("Using repo root") if b.githubOrg == "" { logrus.Fatal("GitHub organization not specified") } @@ -138,20 +103,12 @@ func NewManager(opts ...Option) *CalicoManager { if b.remote == "" { logrus.Fatal("No git remote specified") } - logrus.WithField("repoRoot", b.repoRoot).Info("Using repo root") - - if b.calicoVersion == "" { - logrus.Fatal("No calico version specified") - } - logrus.WithField("version", b.calicoVersion).Info("Using product version") + logrus.WithFields(logrus.Fields{ + "org": b.githubOrg, + "repo": b.repo, + "remote": b.remote, + }).Info("Using GitHub configuration") - if b.operatorVersion == "" { - logrus.Fatal("No operator version specified") - } - if (b.buildImages || b.publishImages || b.archiveImages) && len(b.imageRegistries) == 0 { - logrus.Fatal("No image registries specified") - } - logrus.WithField("operatorVersion", b.operatorVersion).Info("Using operator version") return b } @@ -184,9 +141,12 @@ type CalicoManager struct { calicoVersion string // operator variables - operatorImage string - operatorRegistry string - operatorVersion string + operatorImage string + operatorRegistry string + operatorVersion string + operatorGithubOrg string + operatorRepo string + operatorBranch string // outputDir is the directory to which we should write release artifacts, and from // which we should read them for publishing. @@ -197,12 +157,21 @@ type CalicoManager struct { // Fine-tuning configuration for publishing. publishImages bool + publishCharts bool publishTag bool publishGithub bool + awsProfile string + s3Bucket string // imageRegistries is the list of imageRegistries to which we should publish images. imageRegistries []string + // helmRegistries is the list of OCI-based registries to which we should publish charts. + helmRegistries []string + + // helmRepoURL is the URL of the helm chart repository. + helmRepoURL string + // githubOrg is the GitHub organization to which we should publish releases. githubOrg string @@ -246,6 +215,23 @@ func (r *CalicoManager) helmChartVersion() string { } func (r *CalicoManager) PreBuildValidation() error { + var errStack error + if r.calicoVersion == "" { + errStack = errors.Join(errStack, fmt.Errorf("no calico version specified")) + } + + if r.operatorVersion == "" { + errStack = errors.Join(errStack, fmt.Errorf("no operator version specified")) + } + if (r.buildImages || r.archiveImages) && len(r.imageRegistries) == 0 { + errStack = errors.Join(errStack, fmt.Errorf("no image registries specified")) + } + if errStack != nil { + return errStack + } + logrus.WithField("version", r.calicoVersion).Info("Using product version") + logrus.WithField("operatorVersion", r.operatorVersion).Info("Using operator version") + logrus.WithField("registries", r.imageRegistries).Info("Using image registries for release") if r.isHashRelease { return r.PreHashreleaseValidate() } @@ -347,13 +333,13 @@ type metadata struct { func (r *CalicoManager) BuildMetadata(dir string) error { registry, err := r.getRegistryFromManifests() if err != nil { - return err + return fmt.Errorf("failed to get registry from manifests: %w", err) } m := metadata{ Version: r.calicoVersion, OperatorVersion: r.operatorVersion, - Images: releaseImages(append(images, windowsImages...), r.calicoVersion, registry, r.operatorImage, r.operatorVersion, r.operatorRegistry), + Images: releaseImages(utils.ReleaseImages(), r.calicoVersion, registry, r.operatorImage, r.operatorVersion, r.operatorRegistry), HelmChartVersion: r.helmChartVersion(), } @@ -377,8 +363,8 @@ func (r *CalicoManager) getRegistryFromManifests() (string, error) { if err != nil { return "", fmt.Errorf("error getting registry from calicoctl.yaml manifest: %w", err) } - imgs := strings.Split(out, "\n") - for _, i := range imgs { + imgs := strings.SplitSeq(out, "\n") + for i := range imgs { parts := strings.Split(i, "/") if len(parts) > 1 { return strings.Join(parts[:len(parts)-1], "/"), nil @@ -414,7 +400,12 @@ func (r *CalicoManager) PreHashreleaseValidate() error { } func (r *CalicoManager) checkCodeGeneration() error { - if err := r.makeInDirectoryIgnoreOutput(r.repoRoot, "get-operator-crds generate check-dirty"); err != nil { + env := append(os.Environ(), + fmt.Sprintf("OPERATOR_ORGANIZATION=%s", r.operatorGithubOrg), + fmt.Sprintf("OPERATOR_GIT_REPO=%s", r.operatorRepo), + fmt.Sprintf("OPERATOR_BRANCH=%s", r.operatorBranch), + ) + if err := r.makeInDirectoryIgnoreOutput(r.repoRoot, "get-operator-crds generate check-dirty", env...); err != nil { logrus.WithError(err).Error("Failed to check code generation") return fmt.Errorf("code generation error, try 'make get-operator-crds generate' to fix") } @@ -422,7 +413,7 @@ func (r *CalicoManager) checkCodeGeneration() error { } func (r *CalicoManager) PreReleaseValidate() error { - // Cheeck that we are on a release branch + // Check that we are on a release branch if r.validateBranch { branch, err := utils.GitBranch(r.repoRoot) if err != nil { @@ -463,6 +454,7 @@ func (r *CalicoManager) PreReleaseValidate() error { return err } + // Assert that release notes are present. err = r.assertReleaseNotesPresent(r.calicoVersion) if err != nil { return err @@ -493,40 +485,111 @@ func (r *CalicoManager) TagRelease(ver string) error { } // modifyHelmChartsValues modifies values in helm charts to use the correct version. -// This is only necessary for hashreleases. +// This is only necessary for hashreleases or new branch cut. func (r *CalicoManager) modifyHelmChartsValues() error { - valuesYAML := filepath.Join(r.repoRoot, "charts", "tigera-operator", "values.yaml") - if _, err := r.runner.Run("sed", []string{"-i", fmt.Sprintf(`s/version: .*/version: %s/g`, r.operatorVersion), valuesYAML}, nil); err != nil { - logrus.WithError(err).Error("Failed to update operator version in values.yaml") - return fmt.Errorf("failed to update operator version in %s: %w", valuesYAML, err) + operatorChartFilePath := filepath.Join(r.repoRoot, "charts", "tigera-operator", "values.yaml") + if _, err := r.runner.Run("sed", []string{"-i", fmt.Sprintf(`s/version: .*/version: %s/g`, r.operatorVersion), operatorChartFilePath}, nil); err != nil { + logrus.WithError(err).Errorf("Failed to update operator version in %s", operatorChartFilePath) + return fmt.Errorf("failed to update operator version in %s: %w", operatorChartFilePath, err) } - if _, err := r.runner.Run("sed", []string{"-i", fmt.Sprintf(`s/tag: .*/tag: %s/g`, r.calicoVersion), valuesYAML}, nil); err != nil { - logrus.WithError(err).Error("Failed to update calicoctl version in values.yaml") - return fmt.Errorf("failed to update calicoctl version in %s: %w", valuesYAML, err) + if _, err := r.runner.Run("sed", []string{"-i", fmt.Sprintf(`s/tag: .*/tag: %s/g`, r.calicoVersion), operatorChartFilePath}, nil); err != nil { + logrus.WithError(err).Errorf("Failed to update calicoctl version in %s", operatorChartFilePath) + return fmt.Errorf("failed to update calicoctl version in %s: %w", operatorChartFilePath, err) } return nil } +func (r *CalicoManager) resetCharts() { + if _, err := r.runner.RunInDir(r.repoRoot, "git", []string{"checkout", "charts/"}, nil); err != nil { + logrus.WithError(err).Error("Failed to reset changes to charts") + } +} + func (r *CalicoManager) BuildHelm() error { if r.isHashRelease { if err := r.modifyHelmChartsValues(); err != nil { return fmt.Errorf("failed to modify helm chart values: %s", err) } + defer r.resetCharts() } // Build the helm chart, passing the version to use. - env := append(os.Environ(), fmt.Sprintf("GIT_VERSION=%s", r.calicoVersion)) + env := append(os.Environ(), + fmt.Sprintf("GIT_VERSION=%s", r.calicoVersion), + fmt.Sprintf("CHART_DESTINATION=%s", r.uploadDir()), + ) if err := r.makeInDirectoryIgnoreOutput(r.repoRoot, "chart", env...); err != nil { return fmt.Errorf("failed to build helm chart: %w", err) } + // Create helm index for the chart. + chartURL := fmt.Sprintf("https://github.com/%s/%s/releases/download/%s", r.githubOrg, r.repo, r.calicoVersion) if r.isHashRelease { - // If we modified the repo above, reset it. - if _, err := r.runner.RunInDir(r.repoRoot, "git", []string{"checkout", "charts/"}, nil); err != nil { - logrus.WithError(err).Error("Failed to reset changes to charts") - return fmt.Errorf("failed to reset changes to charts: %w", err) + chartURL = r.hashrelease.URL() + } + if err := r.buildHelmIndex(r.uploadDir(), chartURL); err != nil { + return err + } + return nil +} + +// buildHelmIndex builds the helm index for the given charts directory. +// It downloads the existing helm index, merges in the new chart to create an updated index. +// +// For hashreleases, it copies the helm index to charts/ in the upload directory. +func (r *CalicoManager) buildHelmIndex(chartDir, chartURL string) error { + // Download existing helm index. + indexURL, err := url.JoinPath(r.helmRepoURL, helmIndexFileName) + if err != nil { + return fmt.Errorf("construct helm index url: %w", err) + } + downloadedHelmIndexPath := filepath.Join(r.tmpDir, helmIndexFileName) + if out, err := r.runner.Run("curl", []string{"-fsSL", "--retry", "3", indexURL, "-o", downloadedHelmIndexPath}, nil); err != nil { + logrus.Error(out) + return fmt.Errorf("download previous helm index from %s: %w", indexURL, err) + } + + // Create tmp directory for building index and copy chart there. + tmpChartsDir := filepath.Join(filepath.Dir(r.uploadDir()), fmt.Sprintf("charts-%s", r.helmChartVersion())) + if err := os.MkdirAll(tmpChartsDir, utils.DirPerms); err != nil { + return fmt.Errorf("create temp dir for building helm index: %w", err) + } + + // Copy charts to temp dir. + for _, chart := range utils.AllReleaseCharts() { + srcChart := filepath.Join(chartDir, fmt.Sprintf("%s-%s.tgz", chart, r.helmChartVersion())) + destChart := filepath.Join(tmpChartsDir, fmt.Sprintf("%s-%s.tgz", chart, r.helmChartVersion())) + if err := utils.CopyFile(srcChart, destChart); err != nil { + return fmt.Errorf("error copying %s chart to temp dir for building helm index: %w", chart, err) } } + + // Build the new helm index. + args := []string{ + "repo", "index", tmpChartsDir, + "--url", chartURL, + "--merge", downloadedHelmIndexPath, + } + env := append(os.Environ(), "TZ=UTC") + if out, err := r.runner.RunInDir(r.repoRoot, "./bin/helm", args, env); err != nil { + logrus.Error(out) + return fmt.Errorf("build helm index: %w", err) + } + + if r.isHashRelease { + // For hashreleases, copy the helm index to the upload dir. + srcIndex := filepath.Join(tmpChartsDir, helmIndexFileName) + destIndex := filepath.Join(r.uploadDir(), "charts", helmIndexFileName) + + // Ensure destination directory exists. + if err := os.MkdirAll(filepath.Dir(destIndex), utils.DirPerms); err != nil { + return fmt.Errorf("create dest dir for helm index: %w", err) + } + if err := utils.CopyFile(srcIndex, destIndex); err != nil { + return fmt.Errorf("copy helm index to upload dir: %w", err) + } + return nil + } return nil } @@ -566,13 +629,18 @@ func (r *CalicoManager) PublishRelease() error { if r.imageScanning { logrus.Info("Sending images to ISS") imageScanner := imagescanner.New(r.imageScanningConfig) - err := imageScanner.Scan(r.productCode, slices.Collect(maps.Values(r.componentImages())), r.hashrelease.Stream, false, r.tmpDir) + err := imageScanner.Scan(r.productCode, slices.Collect(maps.Values(r.componentImages())), r.hashrelease.Stream, !r.isHashRelease, r.tmpDir) if err != nil { - // Error is logged and ignored as a failure fron ISS should not halt the release process. + // Error is logged and ignored as a failure from ISS should not halt the release process. logrus.WithError(err).Error("Failed to scan images") } } + // Publish helm charts. + if err := r.publishHelmCharts(); err != nil { + return fmt.Errorf("failed to publish helm charts: %s", err) + } + if r.isHashRelease { if err := r.publishToHashreleaseServer(); err != nil { return fmt.Errorf("failed to publish hashrelease: %s", err) @@ -587,6 +655,11 @@ func (r *CalicoManager) PublishRelease() error { if err := r.publishGithubRelease(); err != nil { return fmt.Errorf("failed to publish github release: %s", err) } + + // Update helm chart index + if err := r.updateHelmChartIndex(); err != nil { + return fmt.Errorf("update helm chart index: %s", err) + } } return nil @@ -720,14 +793,14 @@ func (r *CalicoManager) hashreleasePrereqs() error { func (r *CalicoManager) assertImageVersions() error { logrus.Info("Checking built images exists with the correct version") buildInfoVersionRegex := regexp.MustCompile(`(?m)^Version:\s+(.*)$`) - for _, img := range images { + for _, img := range utils.ReleaseImages() { switch img { case "apiserver": for _, reg := range r.imageRegistries { out, err := r.runner.Run("docker", []string{"run", "--rm", fmt.Sprintf("%s/%s:%s", reg, img, r.calicoVersion)}, nil) - // apiserver always returns an error because there is no kubeconfig, log and ignore it. + // apiserver always returns an error because there is no kubeconfig, log but do not fail here if err != nil { - logrus.WithError(err).WithField("image", img).Warn("error getting version from image") + logrus.WithError(err).WithField("image", img).Error("error while getting version from apiserver image, continuing") } if len(buildInfoVersionRegex.FindStringSubmatch(out)) == 0 { return fmt.Errorf("version does not match for image %s/%s:%s", reg, img, r.calicoVersion) @@ -744,7 +817,9 @@ func (r *CalicoManager) assertImageVersions() error { } } } - case "csi", "dikastes", "envoy-gateway", "envoy-proxy", "envoy-ratelimit", "goldmane", "node-driver-registrar", "pod2daemon-flexvol", "whisker", "whisker-backend": + case "cni-windows", "node-windows": + // Skip windows images + case "csi", "dikastes", "envoy-gateway", "envoy-proxy", "envoy-ratelimit", "flannel-migration-controller", "goldmane", "node-driver-registrar", "pod2daemon-flexvol", "whisker", "whisker-backend": for _, reg := range r.imageRegistries { out, err := r.runner.Run("docker", []string{"inspect", `--format='{{ index .Config.Labels "org.opencontainers.image.version" }}'`, fmt.Sprintf("%s/%s:%s", reg, img, r.calicoVersion)}, nil) if err != nil { @@ -791,6 +866,15 @@ func (r *CalicoManager) assertImageVersions() error { return fmt.Errorf("version does not match for image %s/%s:%s", reg, img, r.calicoVersion) } } + case "webhooks": + for _, reg := range r.imageRegistries { + out, err := r.runner.Run("docker", []string{"run", "--rm", fmt.Sprintf("%s/%s:%s", reg, img, r.calicoVersion), "version"}, nil) + if err != nil { + return fmt.Errorf("failed to run get version from %s image: %s", img, err) + } else if !strings.Contains(out, r.calicoVersion) { + return fmt.Errorf("version does not match for image %s/%s:%s", reg, img, r.calicoVersion) + } + } default: return fmt.Errorf("unknown image: %s, update assertion to include validating image", img) } @@ -804,9 +888,29 @@ func (r *CalicoManager) publishPrereqs() error { logrus.Warn("Skipping pre-publish validation") return nil } + var errStack error + if r.calicoVersion == "" { + errStack = errors.Join(errStack, fmt.Errorf("no calico version specified")) + } + if r.publishImages && len(r.imageRegistries) == 0 { + errStack = errors.Join(errStack, fmt.Errorf("no image registries specified")) + } + if r.publishCharts { + if len(r.helmRegistries) == 0 { + errStack = errors.Join(errStack, fmt.Errorf("no helm chart registries specified")) + } + if !r.isHashRelease && r.s3Bucket == "" { + errStack = errors.Join(errStack, fmt.Errorf("no S3 bucket specified for pushing helm index")) + } + } if dirty, err := utils.GitIsDirty(r.repoRoot); dirty || err != nil { - return fmt.Errorf("there are uncommitted changes in the repository, please commit or stash them before publishing the release") + errStack = errors.Join(errStack, fmt.Errorf("there are uncommitted changes in the repository, please commit or stash them before publishing the release")) } + if errStack != nil { + return errStack + } + logrus.WithField("version", r.calicoVersion).Info("Using product version") + logrus.WithField("registries", r.imageRegistries).Info("Using image registries for publishing") if r.isHashRelease { return r.hashreleasePrereqs() } @@ -820,16 +924,11 @@ func (r *CalicoManager) publishPrereqs() error { // Collect artifacts to be included on each release to GitHub. // It builds the metadata file. // It assumes that all other artifacts already been built, and simply wraps them up. -// -// - release-vX.Y.Z.tgz: contains images, manifests, and binaries. -// -// - ocp-vX.Y.Z.tgz: contains the OCP bundle. -// -// - tigera-operator-vX.Y.Z.tgz: contains the helm v3 chart. -// -// - calico-windows-vX.Y.Z.zip: Calico for Windows zip archive for non-HPC installation. -// -// - calicoctl/bin: All calicoctl binaries. +// - release-vX.Y.Z.tgz: contains images, manifests, and binaries. +// - ocp-vX.Y.Z.tgz: contains the OCP bundle. +// - tigera-operator-vX.Y.Z.tgz: contains the helm v3 chart. +// - calico-windows-vX.Y.Z.zip: Calico for Windows zip archive for non-HPC installation. +// - calicoctl/bin: All calicoctl binaries. // // For hashreleases, include the manifests directly. // @@ -873,9 +972,6 @@ func (r *CalicoManager) collectGithubArtifacts() error { if _, err := r.runner.RunInDir(r.repoRoot, "cp", []string{"bin/ocp.tgz", uploadDir}, nil); err != nil { return fmt.Errorf("failed to copy OCP bundle: %w", err) } - if _, err := r.runner.RunInDir(r.repoRoot, "cp", []string{fmt.Sprintf("bin/tigera-operator-%s.tgz", r.calicoVersion), uploadDir}, nil); err != nil { - return fmt.Errorf("failed to copy tigera-operator bundle: %w", err) - } // Generate a SHA256SUMS file containing the checksums for each artifact // that we attach to the release. These can be confirmed by end users via the following command: @@ -920,7 +1016,7 @@ func (r *CalicoManager) generateManifests() error { } func (r *CalicoManager) resetManifests() { - if _, err := r.runner.RunInDir(r.repoRoot, "git", []string{"checkout", "manifests"}, nil); err != nil { + if _, err := r.runner.RunInDir(r.repoRoot, "git", []string{"checkout", "manifests", "test-tools/mocknode/mock-node.yaml"}, nil); err != nil { logrus.WithError(err).Error("Failed to reset manifests") } } @@ -1054,15 +1150,15 @@ func (r *CalicoManager) buildContainerImages() error { return fmt.Errorf("failed to build %s: %s", dir, err) } logrus.Info(out) - } + if slices.Contains(windowsReleaseDirs, dir) { + out, err := r.makeInDirectoryWithOutput(filepath.Join(r.repoRoot, dir), "image-windows", env...) + if err != nil { + logrus.Error(out) + return fmt.Errorf("failed to build %s windows images: %s", dir, err) + } + logrus.Info(out) - for _, dir := range windowsReleaseDirs { - out, err := r.makeInDirectoryWithOutput(filepath.Join(r.repoRoot, dir), "image-windows", env...) - if err != nil { - logrus.Error(out) - return fmt.Errorf("failed to build %s: %s", dir, err) } - logrus.Info(out) } return nil } @@ -1092,7 +1188,9 @@ Attached to this release are the following artifacts: - {release_tar}: container images, binaries, and kubernetes manifests. - {calico_windows_zip}: Calico for Windows. -- {helm_chart}: Calico Helm v3 chart. +- {helm_chart}: Calico Helm 3 chart (also hosted at oci://quay.io/calico/charts/tigera-operator). +- {helm_v1_crd_chart}: Calico crd.projectcalico.org/v1 CRD chart. +- {helm_v3_crd_chart}: Calico projectcalico.org/v3 CRD chart (tech-preview). - ocp.tgz: Manifest bundle for OpenShift. Additional links: @@ -1110,7 +1208,9 @@ Additional links: "{release_stream}", fmt.Sprintf("v%d.%d", sv.Major(), sv.Minor()), "{release_tar}", fmt.Sprintf("`release-%s.tgz`", r.calicoVersion), "{calico_windows_zip}", fmt.Sprintf("`calico-windows-%s.zip`", r.calicoVersion), - "{helm_chart}", fmt.Sprintf("`tigera-operator-%s.tgz`", r.calicoVersion), + "{helm_chart}", fmt.Sprintf("`%s-%s.tgz`", utils.TigeraOperatorChart, r.calicoVersion), + "{helm_v1_crd_chart}", fmt.Sprintf("`%s-%s.tgz`", utils.ProjectCalicoV1CRDsChart, r.calicoVersion), + "{helm_v3_crd_chart}", fmt.Sprintf("`%s-%s.tgz`", utils.ProjectCalicoV3CRDsChart, r.calicoVersion), } replacer := strings.NewReplacer(formatters...) releaseNote := replacer.Replace(releaseNoteTemplate) @@ -1166,25 +1266,79 @@ func (r *CalicoManager) publishContainerImages() error { logrus.Info(out) break } - } - for _, dir := range windowsReleaseDirs { - attempt := 0 - for { - out, err := r.makeInDirectoryWithOutput(filepath.Join(r.repoRoot, dir), "release-windows", env...) - if err != nil { - if attempt < maxRetries { - logrus.WithField("attempt", attempt).WithError(err).Warn("Publish failed, retrying") - attempt++ - continue + if slices.Contains(windowsReleaseDirs, dir) { + attempt := 0 + for { + out, err := r.makeInDirectoryWithOutput(filepath.Join(r.repoRoot, dir), "release-windows", env...) + if err != nil { + if attempt < maxRetries { + logrus.WithField("attempt", attempt).WithError(err).Warn("Publish failed, retrying") + attempt++ + continue + } + logrus.Error(out) + return fmt.Errorf("failed to publish %s windows images: %s", dir, err) } - logrus.Error(out) - return fmt.Errorf("failed to publish %s: %s", dir, err) + + // Success - move on to the next directory. + logrus.Info(out) + break } + } + } + return nil +} - // Success - move on to the next directory. - logrus.Info(out) - break +func (r *CalicoManager) publishHelmCharts() error { + if !r.publishCharts { + logrus.Info("Skipping publishing helm charts") + return nil + } + for _, reg := range r.helmRegistries { + for _, chart := range utils.AllReleaseCharts() { + if err := r.publishHelmChart(filepath.Join(r.uploadDir(), fmt.Sprintf("%s-%s.tgz", chart, r.helmChartVersion())), reg); err != nil { + return err + } + } + } + return nil +} + +func (r *CalicoManager) publishHelmChart(chart, registry string) error { + // We allow for a certain number of retries when publishing each chart to a registry, since + // network flakes can occasionally result in images failing to push. + maxRetries := 1 + attempt := 0 + args := []string{"push", chart, fmt.Sprintf("oci://%s", registry)} + if logrus.IsLevelEnabled(logrus.DebugLevel) { + args = append(args, "--debug") + } + for { + out, err := r.runner.RunInDir(r.repoRoot, "./bin/helm", args, nil) + if err != nil { + if attempt < maxRetries { + logrus.WithField("attempt", attempt).WithError(err).Warn("Publish failed, retrying") + attempt++ + continue + } + logrus.Error(out) + return fmt.Errorf("publish %s to %s: %s", chart, registry, err) } + + // Success - move on to the next. + logrus.Info(out) + break + } + return nil +} + +func (r *CalicoManager) updateHelmChartIndex() error { + if !r.publishCharts { + logrus.Info("Skipping updating helm index") + return nil + } + if err := r.s3Cp(filepath.Join(filepath.Dir(r.uploadDir()), fmt.Sprintf("charts-%s", r.helmChartVersion()), helmIndexFileName), fmt.Sprintf("s3://%s/charts/", r.s3Bucket), s3ACLPublicRead...); err != nil { + return fmt.Errorf("update helm index: %w", err) } return nil } @@ -1192,13 +1346,12 @@ func (r *CalicoManager) publishContainerImages() error { func (r *CalicoManager) assertReleaseNotesPresent(ver string) error { // Validate that the release notes for this version are present, // fail if not. - releaseNotesPath := filepath.Join(r.repoRoot, "release-notes", fmt.Sprintf("%s-release-notes.md", ver)) releaseNotesStat, err := os.Stat(releaseNotesPath) - // If we got an error, handle that? if err != nil { return fmt.Errorf("release notes file is invalid: %s", err.Error()) } + if releaseNotesStat.Size() == 0 { return fmt.Errorf("release notes file is invalid: file is 0 bytes") } else if releaseNotesStat.IsDir() { @@ -1219,8 +1372,8 @@ func (r *CalicoManager) assertManifestVersions(ver string) error { if err != nil { return fmt.Errorf("failed to get images from manifest %s: %w", m, err) } - imgs := strings.Split(out, "\n") - for _, i := range imgs { + imgs := strings.SplitSeq(out, "\n") + for i := range imgs { if strings.Contains(i, "operator") { // We don't handle the operator image here yet, since // the version is different. @@ -1280,3 +1433,127 @@ func (r *CalicoManager) makeInDirectoryIgnoreOutput(dir, target string, env ...s _, err := r.makeInDirectoryWithOutput(dir, target, env...) return err } + +func (r *CalicoManager) s3Cp(src, dest string, additionalFlags ...string) error { + args := []string{ + "s3", "cp", + src, dest, + } + if r.awsProfile != "" { + args = append(args, "--profile", r.awsProfile) + } + if strings.HasSuffix(src, "/") { + args = append(args, "--recursive") + } + if logrus.IsLevelEnabled(logrus.DebugLevel) { + args = append(args, "--debug") + } + if len(additionalFlags) > 0 { + args = append(args, additionalFlags...) + } + if _, err := r.runner.Run("aws", args, nil); err != nil { + return err + } + return nil +} + +func (r *CalicoManager) releaseBranchPrereqs(branch string) error { + if !r.validate { + logrus.Warn("Skipping pre-release branch validation") + return nil + } + var errStack error + if dirty, err := utils.GitIsDirty(r.repoRoot); err != nil { + errStack = errors.Join(errStack, fmt.Errorf("failed to check if git is dirty: %s", err)) + } else if dirty { + errStack = errors.Join(errStack, fmt.Errorf("there are uncommitted changes in the repository, please commit or stash them before cutting a release branch")) + } + if branch == "" { + errStack = errors.Join(errStack, fmt.Errorf("release branch not specified")) + } + if r.operatorBranch == "" { + errStack = errors.Join(errStack, fmt.Errorf("operator branch not specified")) + } + return errStack +} + +// SetupReleaseBranch runs the steps necessary when cutting a new release branch +// +// For a newly created release branch, it will: +// - Update the versions in the helm charts, metadata.mk. +// - Run code generation to update generated files based on the new versions. +// - Commit the changes. +func (r *CalicoManager) SetupReleaseBranch(branch string) error { + if err := r.releaseBranchPrereqs(branch); err != nil { + return err + } + + // Set calico version and operator version to their respective branches for pre-release branch. + r.calicoVersion = branch + r.operatorVersion = r.operatorBranch + + // Modify values in charts + logrus.WithFields(logrus.Fields{ + "calico_version": r.calicoVersion, + "operator_version": r.operatorVersion, + }).Debug("Updating versions in helm charts to release branches") + calicoChartFilePath := filepath.Join(r.repoRoot, "charts", "calico", "values.yaml") + if out, err := r.runner.Run("sed", []string{"-i", fmt.Sprintf(`s/version: .*/version: %s/g`, branch), calicoChartFilePath}, nil); err != nil { + logrus.Error(out) + return fmt.Errorf("failed to update version in %s: %w", calicoChartFilePath, err) + } + if err := r.modifyHelmChartsValues(); err != nil { + return err + } + + // Modify values in metadata.mk + logrus.WithField("operator_branch", r.operatorBranch).Debug("Updating variables in metadata.mk") + makeMetadataFilePath := filepath.Join(r.repoRoot, "metadata.mk") + if out, err := r.runner.Run("sed", []string{"-i", fmt.Sprintf(`s/^OPERATOR_BRANCH.*/OPERATOR_BRANCH ?= %s/g`, r.operatorBranch), makeMetadataFilePath}, nil); err != nil { + logrus.Error(out) + return fmt.Errorf("failed to update operator branch in %s: %w", makeMetadataFilePath, err) + } + + // Update release stream used for CAPZ - Windows FV tests. + releaseStream := strings.TrimPrefix(branch, r.releaseBranchPrefix+"-") + logrus.WithField("releaseStream", releaseStream).Debug("Updating release stream in setup script for CAPZ Windows FV tests") + scriptFilePath := filepath.Join(r.repoRoot, "process", "testing", "winfv-felix", "setup-fv-capz.sh") + if out, err := r.runner.Run("sed", []string{"-i", fmt.Sprintf(`s/RELEASE_STREAM=.*HASH_RELEASE/RELEASE_STREAM=%s HASH_RELEASE/g`, releaseStream), scriptFilePath}, nil); err != nil { + logrus.Error(out) + return fmt.Errorf("failed to update release stream in %s: %w", scriptFilePath, err) + } + + // Update mocknode test tool to use the correct branch tag. + logrus.WithField("branch", branch).Debug("Updating mocknode test tool to use the correct branch tag") + mockNodeFilePath := filepath.Join(r.repoRoot, "test-tools", "mocknode", "mock-node.yaml") + if out, err := r.runner.Run("sed", []string{"-Ei", fmt.Sprintf(`s#([a-zA-Z .]+)([a-zA-Z.]+/mock-node:)[^[:space:]]+#\1\2%s#g`, branch), mockNodeFilePath}, nil); err != nil { + logrus.Error(out) + return fmt.Errorf("failed to update mocknode image in %s: %w", mockNodeFilePath, err) + } + + // Run code generation. + logrus.Debug("Running code generation") + env := append(os.Environ(), fmt.Sprintf("DEFAULT_BRANCH_OVERRIDE=%s", branch)) + if err := r.makeInDirectoryIgnoreOutput(r.repoRoot, "generate", env...); err != nil { + return fmt.Errorf("failed to run code generation: %w", err) + } + + // Commit the changes. + if out, err := r.git("add", + filepath.Join(r.repoRoot, ".semaphore"), + filepath.Join(r.repoRoot, "charts"), + filepath.Join(r.repoRoot, "manifests"), + filepath.Join(r.repoRoot, "metadata.mk"), + filepath.Join(r.repoRoot, "process", "testing", "winfv-felix", "setup-fv-capz.sh"), + filepath.Join(r.repoRoot, "test-tools", "mocknode"), + ); err != nil { + logrus.Error(out) + return fmt.Errorf("failed to add files to git: %s", err) + } + if out, err := r.git("commit", "-m", fmt.Sprintf("Updates for %s release branch", branch)); err != nil { + logrus.Error(out) + return fmt.Errorf("failed to commit changes: %s", err) + } + + return nil +} diff --git a/release/pkg/manager/calico/options.go b/release/pkg/manager/calico/options.go index d477ea25f56..974623bc009 100644 --- a/release/pkg/manager/calico/options.go +++ b/release/pkg/manager/calico/options.go @@ -74,6 +74,15 @@ func WithOperator(registry, image, version string) Option { } } +func WithOperatorGit(org, repo, branch string) Option { + return func(r *CalicoManager) error { + r.operatorGithubOrg = org + r.operatorRepo = repo + r.operatorBranch = branch + return nil + } +} + func WithOperatorVersion(version string) Option { return func(r *CalicoManager) error { r.operatorVersion = version @@ -88,6 +97,20 @@ func WithOutputDir(outputDir string) Option { } } +func WithAWSProfile(profile string) Option { + return func(r *CalicoManager) error { + r.awsProfile = profile + return nil + } +} + +func WithS3Bucket(bucket string) Option { + return func(r *CalicoManager) error { + r.s3Bucket = bucket + return nil + } +} + func WithPublishImages(publish bool) Option { return func(r *CalicoManager) error { r.publishImages = publish @@ -95,6 +118,13 @@ func WithPublishImages(publish bool) Option { } } +func WithPublishCharts(publish bool) Option { + return func(r *CalicoManager) error { + r.publishCharts = publish + return nil + } +} + func WithPublishGitTag(publish bool) Option { return func(r *CalicoManager) error { r.publishTag = publish @@ -130,6 +160,20 @@ func WithImageRegistries(registries []string) Option { } } +func WithHelmRegistries(registries []string) Option { + return func(r *CalicoManager) error { + r.helmRegistries = registries + return nil + } +} + +func WithHelmRepo(url string) Option { + return func(r *CalicoManager) error { + r.helmRepoURL = url + return nil + } +} + func WithArchitectures(architectures []string) Option { return func(r *CalicoManager) error { r.architectures = architectures @@ -208,3 +252,10 @@ func WithArchiveImages(archive bool) Option { return nil } } + +func WithOperatorBranch(branch string) Option { + return func(r *CalicoManager) error { + r.operatorBranch = branch + return nil + } +} diff --git a/release/pkg/manager/operator/manager.go b/release/pkg/manager/operator/manager.go index 47c9139b2f6..ea8ff2d2a67 100644 --- a/release/pkg/manager/operator/manager.go +++ b/release/pkg/manager/operator/manager.go @@ -36,7 +36,7 @@ import ( ) var ( - //go:embed template/images.go.gotmpl + //go:embed templates/images.go.gotmpl componentImagesFileTemplate string componentImagesFilePath = filepath.Join("pkg", "components", "images.go") @@ -75,10 +75,13 @@ type OperatorManager struct { // outputDir is the absolute path to the output directory outputDir string - // productRegistry is the registry to use for product images + // image is the name of the operator image (e.g. tigera/operator) + image string + + // registry is the registry to use for operator (e.g. quay.io) registry string - // productRegistry is the registry to use for product images + // productRegistry is the registry to use for product images (e.g. quay.io/calico) productRegistry string // origin remote repository @@ -121,6 +124,7 @@ func NewManager(opts ...Option) *OperatorManager { runner: &command.RealCommandRunner{}, docker: registry.MustDockerRunner(), registry: DefaultRegistry, + image: DefaultImage, productRegistry: registry.DefaultCalicoRegistries[0], validate: true, publish: true, @@ -134,6 +138,34 @@ func NewManager(opts ...Option) *OperatorManager { return o } +// imageParts splits the operator image into registry and image name. +// Typically the operator image is something like "tigera/operator". +// This function splits it into "tigera" and "operator". +func (o *OperatorManager) imageParts() (imagePath string, imageName string, err error) { + parts := strings.Split(o.image, "/") + if len(parts) != 2 { + err = fmt.Errorf("failed to parse operator image: %s", o.image) + return + } + imagePath = strings.TrimSuffix(parts[0], "/") + imageName = strings.TrimPrefix(parts[1], "/") + return +} + +// productRegistryParts splits the product registry into registry and image path. +// Typically the product registry is something like "docker.io/calico" or "quay.io/calico". +// This function splits it into "docker.io" and "calico" or "quay.io" and "calico". +func (o *OperatorManager) productRegistryParts() (registry string, imagePath string, err error) { + parts := strings.Split(o.productRegistry, "/") + if len(parts) < 2 { + err = fmt.Errorf("failed to parse product registry: %s", o.productRegistry) + return + } + registry = strings.Join(parts[:len(parts)-1], "/") + imagePath = parts[len(parts)-1] + return +} + // modifyComponentsImagesFile overwrites the pkg/components/images.go file // with the contents of the embedded file to ensure that operator has the right registries. // This is ONLY used by hashreleases because the operator uses the images.go file to determine the registry. @@ -141,23 +173,31 @@ func (o *OperatorManager) modifyComponentsImagesFile() error { destFilePath := filepath.Join(o.dir, componentImagesFilePath) dest, err := os.OpenFile(destFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644) if err != nil { - logrus.WithError(err).Errorf("Failed to open file %s", destFilePath) - return err + return fmt.Errorf("failed to open file %s: %w", destFilePath, err) } defer func() { _ = dest.Close() }() tmpl, err := template.New("pkg/components/images.go").Parse(componentImagesFileTemplate) if err != nil { - logrus.WithError(err).Errorf("Failed to parse template to overwrite %s file", destFilePath) + return fmt.Errorf("failed to parse template to overwrite %s file: %w", destFilePath, err) + } + + imagePath, _, err := o.imageParts() + if err != nil { + return err + } + productRegistry, productImagePath, err := o.productRegistryParts() + if err != nil { return err } if err := tmpl.Execute(dest, map[string]string{ - "Registry": o.registry, - "ProductRegistry": o.productRegistry, - "Year": time.Now().Format("2006"), + "ImagePath": imagePath, + "Registry": o.registry, + "ProductImagePath": productImagePath, + "ProductRegistry": productRegistry, + "Year": time.Now().Format("2006"), }); err != nil { - logrus.WithError(err).Errorf("Failed to write to file %s", destFilePath) - return err + return fmt.Errorf("failed to write to file %s: %w", destFilePath, err) } return nil } @@ -167,7 +207,7 @@ func (o *OperatorManager) Build() error { return fmt.Errorf("operator manager builds only for hash releases") } if o.validate { - if err := o.PreBuildValidation(o.tmpDir); err != nil { + if err := o.PreBuildValidation(); err != nil { return err } } @@ -175,6 +215,9 @@ func (o *OperatorManager) Build() error { if err != nil { return err } + if component.Image != o.image { + return fmt.Errorf("operator image mismatch: expected %s, got %s", o.image, component.Image) + } defer func() { if _, err := o.runner.RunInDir(o.dir, "git", []string{"reset", "--hard"}, nil); err != nil { logrus.WithError(err).Error("Failed to reset repository") @@ -185,23 +228,22 @@ func (o *OperatorManager) Build() error { } env := os.Environ() env = append(env, fmt.Sprintf("OS_VERSIONS=%s", componentsVersionPath)) - env = append(env, fmt.Sprintf("COMMON_VERSIONS=%s", componentsVersionPath)) env = append(env, fmt.Sprintf("CALICO_CRDS_DIR=%s", o.calicoDir)) if _, err := o.make("gen-versions", env); err != nil { - return err + return fmt.Errorf("failed to generate versions: %w", err) } env = os.Environ() env = append(env, fmt.Sprintf("ARCHES=%s", strings.Join(o.architectures, " "))) env = append(env, fmt.Sprintf("GIT_VERSION=%s", component.Version)) env = append(env, fmt.Sprintf("BUILD_IMAGE=%s", component.Image)) if _, err := o.make("image-all", env); err != nil { - return err + return fmt.Errorf("failed to build images: %w", err) } for _, arch := range o.architectures { currentTag := fmt.Sprintf("%s:latest-%s", component.Image, arch) newTag := fmt.Sprintf("%s-%s", component.String(), arch) if err := o.docker.TagImage(currentTag, newTag); err != nil { - return err + return fmt.Errorf("failed to tag image %q as %q: %w", currentTag, newTag, err) } } env = os.Environ() @@ -209,21 +251,24 @@ func (o *OperatorManager) Build() error { env = append(env, fmt.Sprintf("BUILD_IMAGE=%s", component.Image)) env = append(env, fmt.Sprintf("BUILD_INIT_IMAGE=%s", component.InitImage().Image)) if _, err := o.make("image-init", env); err != nil { - return err + return fmt.Errorf("failed to create init image: %w", err) } currentTag := fmt.Sprintf("%s:latest", component.InitImage().Image) newTag := component.InitImage().String() - return o.docker.TagImage(currentTag, newTag) + if err := o.docker.TagImage(currentTag, newTag); err != nil { + return fmt.Errorf("failed to tag image %q as %q: %w", currentTag, newTag, err) + } + return nil } -func (o *OperatorManager) PreBuildValidation(outputDir string) error { - if o.dir == "" { - logrus.Fatal("No repository root specified") - } +func (o *OperatorManager) PreBuildValidation() error { if !o.isHashRelease { return fmt.Errorf("operator manager builds only for hash releases") } var errStack error + if o.dir == "" { + errStack = errors.Join(errStack, fmt.Errorf("no repository root specified")) + } if o.validateBranch { branch, err := utils.GitBranch(o.dir) if err != nil { @@ -246,7 +291,7 @@ func (o *OperatorManager) PreBuildValidation(outputDir string) error { if len(o.architectures) == 0 { errStack = errors.Join(errStack, fmt.Errorf("no architectures specified")) } - operatorComponent, err := pinnedversion.RetrievePinnedOperator(outputDir) + operatorComponent, err := pinnedversion.RetrievePinnedOperator(o.tmpDir) if err != nil { return fmt.Errorf("failed to get operator component: %s", err) } @@ -270,7 +315,7 @@ func (o *OperatorManager) Publish() error { operatorComponent, err := pinnedversion.RetrievePinnedOperator(o.tmpDir) if err != nil { logrus.WithError(err).Error("Failed to get operator component") - return err + return fmt.Errorf("failed to get operator component: %w", err) } var imageList []string for _, arch := range o.architectures { @@ -289,7 +334,7 @@ func (o *OperatorManager) Publish() error { fields["manifest"] = manifestListName if o.publish { if err = o.docker.ManifestPush(manifestListName, imageList); err != nil { - return err + return fmt.Errorf("failed to push manifest list: %w", err) } } logrus.WithFields(fields).Info("Pushed operator manifest") @@ -298,7 +343,7 @@ func (o *OperatorManager) Publish() error { fields["image"] = initImage if o.publish { if err := o.docker.PushImage(initImage.String()); err != nil { - return err + return fmt.Errorf("failed to push init image: %w", err) } } logrus.WithFields(fields).Info("Pushed operator init image") @@ -319,19 +364,20 @@ func (o *OperatorManager) PrePublishValidation() error { } func (o *OperatorManager) PreReleasePublicValidation() error { + var errStack error if o.githubOrg == "" { - logrus.Fatal("GitHub organization not specified") + errStack = errors.Join(errStack, fmt.Errorf("GitHub organization not specified")) } if o.repoName == "" { - logrus.Fatal("GitHub repository not specified") + errStack = errors.Join(errStack, fmt.Errorf("GitHub repository not specified")) } if o.remote == "" { - logrus.Fatal("No git remote specified") + errStack = errors.Join(errStack, fmt.Errorf("no git remote specified")) } if o.version == "" { - logrus.Fatal("No operator version specified") + errStack = errors.Join(errStack, fmt.Errorf("no operator version specified")) } - return nil + return errStack } // ReleasePublic publishes the current draft release of the operator to make it publicly available. diff --git a/release/pkg/manager/operator/manager_test.go b/release/pkg/manager/operator/manager_test.go new file mode 100644 index 00000000000..0b07e7762f1 --- /dev/null +++ b/release/pkg/manager/operator/manager_test.go @@ -0,0 +1,113 @@ +// Copyright (c) 2025 Tigera, Inc. All rights reserved. + +// 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 operator + +import "testing" + +func TestImageParts(t *testing.T) { + for _, tc := range []struct { + image string + expImagePath string + expImageName string + shouldErr bool + }{ + { + image: "tigera/operator", + expImagePath: "tigera", + expImageName: "operator", + }, + { + image: "tigera/extra/operator", + shouldErr: true, + }, + { + image: "operator", + shouldErr: true, + }, + } { + t.Run(tc.image, func(t *testing.T) { + m := &OperatorManager{ + image: tc.image, + } + imagePath, imageName, err := m.imageParts() + if tc.shouldErr { + if err == nil { + t.Fatalf("expected error but got none") + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if imagePath != tc.expImagePath { + t.Errorf("expected image path %s but got %s", tc.expImagePath, imagePath) + } + if imageName != tc.expImageName { + t.Errorf("expected image name %s but got %s", tc.expImageName, imageName) + } + }) + } +} + +func TestProductRegistryParts(t *testing.T) { + for _, tc := range []struct { + registry string + expRegistry string + expNamespace string + shouldErr bool + }{ + { + registry: "my-registry/my-namespace", + expRegistry: "my-registry", + expNamespace: "my-namespace", + }, + { + registry: "my-registry", + shouldErr: true, + }, + { + registry: "my-registry/extra/my-namespace", + expRegistry: "my-registry/extra", + expNamespace: "my-namespace", + }, + { + registry: "my-registry/extra/more/my-namespace", + expRegistry: "my-registry/extra/more", + expNamespace: "my-namespace", + }, + } { + t.Run(tc.registry, func(t *testing.T) { + m := &OperatorManager{ + productRegistry: tc.registry, + } + registry, namespace, err := m.productRegistryParts() + if tc.shouldErr { + if err == nil { + t.Fatalf("expected error but got none") + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if registry != tc.expRegistry { + t.Errorf("expected registry %s but got %s", tc.expRegistry, registry) + } + if namespace != tc.expNamespace { + t.Errorf("expected namespace %s but got %s", tc.expNamespace, namespace) + } + }) + } +} diff --git a/release/pkg/manager/operator/options.go b/release/pkg/manager/operator/options.go index c6955a97bb4..5e0cb4960b6 100644 --- a/release/pkg/manager/operator/options.go +++ b/release/pkg/manager/operator/options.go @@ -141,3 +141,10 @@ func WithProductRegistry(registry string) Option { return nil } } + +func WithImage(image string) Option { + return func(o *OperatorManager) error { + o.image = image + return nil + } +} diff --git a/release/pkg/manager/operator/template/images.go.gotmpl b/release/pkg/manager/operator/templates/images.go.gotmpl similarity index 79% rename from release/pkg/manager/operator/template/images.go.gotmpl rename to release/pkg/manager/operator/templates/images.go.gotmpl index bd52db3755a..af84fafbc17 100644 --- a/release/pkg/manager/operator/template/images.go.gotmpl +++ b/release/pkg/manager/operator/templates/images.go.gotmpl @@ -17,10 +17,17 @@ package components const ( CalicoRegistry = "{{.ProductRegistry}}/" TigeraRegistry = "gcr.io/unique-caldron-775/cnx/" - // For production InitRegistry should match TigeraRegistry. + // For production OperatorRegistry should match TigeraRegistry. // For the master branch and other testing scenarios we switch TigeraRegistry to // point to a testing repo but the init image will be pushed to quay, so having // these separate allows pulling the proper test images for the Tigera components // and Init image when testing. - InitRegistry = "{{.Registry}}/" + OperatorRegistry = "{{.Registry}}/" +) + +// Default image paths for components. +const ( + CalicoImagePath = "{{.ProductImagePath}}/" + TigeraImagePath = "tigera/" + OperatorImagePath = "{{.ImagePath}}/" ) diff --git a/release/pkg/postrelease/github_test.go b/release/pkg/postrelease/github_test.go index 62d63579bd4..e855977cd9e 100644 --- a/release/pkg/postrelease/github_test.go +++ b/release/pkg/postrelease/github_test.go @@ -7,6 +7,8 @@ import ( "slices" "testing" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/go-github/v53/github" "github.com/projectcalico/calico/release/internal/utils" @@ -54,35 +56,27 @@ func TestGitHubRelease(t *testing.T) { t.Run("check release assets", func(t *testing.T) { expectedAssets := append(calicoctlBinaryList(), metadataFileName, - "install-calicoctl-windows.ps1", + "install-calico-windows.ps1", fmt.Sprintf("calico-windows-%s.zip", releaseVersion), fmt.Sprintf("release-%s.tgz", releaseVersion), fmt.Sprintf("tigera-operator-%s.tgz", releaseVersion), "SHA256SUMS", "ocp.tgz", + "LICENSE", ) - if release.Assets == nil { - t.Fatalf("%s release has no assets", releaseVersion) - } - var diff []string - for _, asset := range expectedAssets { - if !isAssetPresent(release.Assets, asset) { - diff = append(diff, asset) - } - } - if len(diff) > 0 { - t.Errorf("release %s is missing the following assets: %v", releaseVersion, diff) + actualAssets := getAssets(release) + if diff := cmp.Diff(expectedAssets, actualAssets, cmpopts.SortSlices(func(a, b string) bool { return a < b })); diff != "" { + t.Errorf("release assets mismatch (-expected +actual):\n%s", diff) } }) } -func isAssetPresent(assets []*github.ReleaseAsset, name string) bool { - for _, asset := range assets { - if asset.GetName() == name { - return true - } +func getAssets(release *github.RepositoryRelease) []string { + var assets []string + for _, asset := range release.Assets { + assets = append(assets, asset.GetName()) } - return false + return assets } func TestGitHubReleaseNotes(t *testing.T) { diff --git a/release/pkg/postrelease/helm_test.go b/release/pkg/postrelease/helm_test.go index 82525fad1cf..73f7f28d9f9 100644 --- a/release/pkg/postrelease/helm_test.go +++ b/release/pkg/postrelease/helm_test.go @@ -2,21 +2,29 @@ package postrelease import ( "fmt" - "io" "net/http" - "os" + "net/url" + "path/filepath" "slices" "testing" "github.com/spf13/cast" "go.yaml.in/yaml/v3" + "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" -) -const helmIndexURL = "https://projectcalico.docs.tigera.io/charts/index.yaml" + "github.com/projectcalico/calico/release/internal/command" + "github.com/projectcalico/calico/release/internal/registry" + "github.com/projectcalico/calico/release/internal/utils" +) -func chartURL(githubOrg, githubRepo, version string) string { - return fmt.Sprintf("https://github.com/%s/%s/releases/download/%s/tigera-operator-%s.tgz", githubOrg, githubRepo, version, version) +func chartURLs(githubOrg, githubRepo, version string) []string { + urls := []string{} + for _, chart := range utils.AllReleaseCharts() { + u := fmt.Sprintf("https://github.com/%s/%s/releases/download/%s/%s-%s.tgz", githubOrg, githubRepo, version, chart, version) + urls = append(urls, u) + } + return urls } func TestHelmChart(t *testing.T) { @@ -24,31 +32,57 @@ func TestHelmChart(t *testing.T) { checkVersion(t, releaseVersion) - resp, err := http.Get(chartURL(githubOrg, githubRepo, releaseVersion)) - if err != nil { - t.Fatalf("failed to fetch helm chart: %v", err) - } - if resp.StatusCode != http.StatusOK { - t.Fatalf("failed to fetch helm chart: server returned %s", resp.Status) - } - defer func() { _ = resp.Body.Close() }() + t.Run("github", func(t *testing.T) { + t.Parallel() - tmpFile, err := os.CreateTemp("", "*.tgz") - if err != nil { - t.Fatalf("failed to create temp file: %v", err) - } - defer func() { _ = os.Remove(tmpFile.Name()) }() + for _, url := range chartURLs(githubOrg, githubRepo, releaseVersion) { + resp, err := http.Get(url) + if err != nil { + t.Fatalf("failed to fetch helm chart: %v", err) + } + if resp.StatusCode != http.StatusOK { + t.Fatalf("failed to fetch helm chart: server returned %s", resp.Status) + } + defer func() { _ = resp.Body.Close() }() - if _, err := io.Copy(tmpFile, resp.Body); err != nil { - t.Fatalf("failed to write helm chart to temp file: %v", err) - } + chart, err := loader.LoadArchive(resp.Body) + if err != nil { + t.Fatalf("load helm chart: %v", err) + } + validateChart(t, chart) + } + }) - chart, err := loader.Load(tmpFile.Name()) - if err != nil { - t.Fatalf("failed to load helm chart: %v", err) - } + t.Run("OCI registry", func(t *testing.T) { + t.Parallel() + + for _, reg := range registry.DefaultHelmRegistries { + t.Run(reg, func(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + args := []string{ + "pull", fmt.Sprintf("oci://%s/%s", reg, utils.TigeraOperatorChart), + "--version", releaseVersion, + } + out, err := command.RunInDir(dir, "helm", args) + if err != nil { + t.Fatalf("pull %s %s helm chart from %s: %v\nOutput: %s", utils.TigeraOperatorChart, releaseVersion, reg, err, out) + } + chart, err := loader.Load(filepath.Join(dir, fmt.Sprintf("%s-%s.tgz", utils.TigeraOperatorChart, releaseVersion))) + if err != nil { + t.Fatalf("load helm chart from %s: %v", reg, err) + } + validateChart(t, chart) + }) + } + }) +} + +func validateChart(t testing.TB, chart *chart.Chart) { + t.Helper() if err := chart.Validate(); err != nil { - t.Fatalf("failed to validate helm chart: %v", err) + t.Fatalf("invalid helm chart: %v", err) } if chart.AppVersion() != releaseVersion { t.Fatalf("expected helm chart app version %s, got %s", releaseVersion, chart.AppVersion()) @@ -64,7 +98,11 @@ func TestHelmIndex(t *testing.T) { checkVersion(t, releaseVersion) - resp, err := http.Get(helmIndexURL) + indexURL, err := url.JoinPath(utils.CalicoHelmRepoURL, "index.yaml") + if err != nil { + t.Fatalf("construct helm index url: %v", err) + } + resp, err := http.Get(indexURL) if err != nil { t.Fatalf("failed to fetch helm index: %v", err) } @@ -100,7 +138,10 @@ func TestHelmIndex(t *testing.T) { if !ok || len(urls.([]any)) == 0 { t.Fatalf("helm index entry for version %s does not contain urls", releaseVersion) } - if !slices.Contains(cast.ToStringSlice(urls), chartURL(githubOrg, githubRepo, releaseVersion)) { - t.Fatalf("helm index entry for version %s does not contain expected URL", releaseVersion) + + for _, url := range chartURLs(githubOrg, githubRepo, releaseVersion) { + if !slices.Contains(cast.ToStringSlice(urls), url) { + t.Fatalf("helm index entry for version %s does not contain expected URL: %s", releaseVersion, url) + } } } diff --git a/release/pkg/postrelease/images_test.go b/release/pkg/postrelease/images_test.go index c749459114c..1e1911f943c 100644 --- a/release/pkg/postrelease/images_test.go +++ b/release/pkg/postrelease/images_test.go @@ -7,6 +7,8 @@ import ( "strings" "testing" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "go.yaml.in/yaml/v3" "github.com/projectcalico/calico/release/internal/registry" @@ -14,8 +16,8 @@ import ( ) var excludeImageArch = map[string][]string{ - "calico/envoy-proxy": {"ppc64le", "s390x"}, - "calico/whisker": {"ppc64le", "s390x"}, + "envoy-proxy": {"ppc64le", "s390x"}, + "whisker": {"ppc64le", "s390x"}, } func TestImagesPublished(t *testing.T) { @@ -26,17 +28,13 @@ func TestImagesPublished(t *testing.T) { checkImages(t, images) for _, reg := range registry.DefaultCalicoRegistries { - for _, image := range strings.Split(images, " ") { - if strings.Contains(image, operator.DefaultImage) { - // Operator images are checked separately - continue - } - fqImage := fmt.Sprintf("%s/%s:%s", reg, image, releaseVersion) - t.Run(fqImage, func(t *testing.T) { + for image := range strings.SplitSeq(images, " ") { + t.Run(image, func(t *testing.T) { + fqImage := fmt.Sprintf("%s/%s:%s", reg, image, releaseVersion) if ok, err := registry.CheckImage(fqImage); err != nil { t.Fatalf("failed to check image %s: %v", fqImage, err) } else if !ok { - t.Fatalf("image (%s) not found", fqImage) + t.Fatalf("image %q not found", fqImage) } if !strings.HasSuffix(image, "windows") { for _, arch := range linuxArches { @@ -103,20 +101,17 @@ func TestImagesInMetadata(t *testing.T) { checkImages(t, images) var expectedImages []string - addOperator := true - for _, image := range strings.Split(images, " ") { - if strings.Contains(image, operator.DefaultImage) { - addOperator = false - expectedImages = append(expectedImages, fmt.Sprintf("%s/%s:%s", operator.DefaultRegistry, operator.DefaultImage, operatorVersion)) + for image := range strings.SplitSeq(images, " ") { + registry := registry.DefaultCalicoRegistry + if registry != "" { + registry += "/" } - expectedImages = append(expectedImages, fmt.Sprintf("%s:%s", image, releaseVersion)) + expectedImages = append(expectedImages, fmt.Sprintf("%s%s:%s", registry, image, releaseVersion)) } if len(expectedImages) == 0 { t.Fatal("no images provided") } - if addOperator { - expectedImages = append(expectedImages, fmt.Sprintf("%s/%s:%s", operator.DefaultRegistry, operator.DefaultImage, operatorVersion)) - } + expectedImages = append(expectedImages, fmt.Sprintf("%s/%s:%s", operator.DefaultRegistry, operator.DefaultImage, operatorVersion)) t.Logf("expected images: %v", expectedImages) metadataImages, err := getMetadataImages() @@ -125,17 +120,8 @@ func TestImagesInMetadata(t *testing.T) { } t.Logf("metadata images: %v", metadataImages) - if len(metadataImages) != len(expectedImages) { - t.Errorf("expected %d images in metadata, got %d", len(expectedImages), len(metadataImages)) - } - var diff []string - for _, expectedImage := range expectedImages { - if !slices.Contains(metadataImages, expectedImage) { - diff = append(diff, expectedImage) - } - } - if len(diff) > 0 { - t.Fatalf("images in metadata missing the following: %v", diff) + if diff := cmp.Diff(expectedImages, metadataImages, cmpopts.SortSlices(func(a, b string) bool { return a < b })); diff != "" { + t.Errorf("images in metadata do not match (-expected +actual):\n%s", diff) } } diff --git a/release/pkg/postrelease/setup_test.go b/release/pkg/postrelease/setup_test.go index 9a6b58db8f7..e20b81500ed 100644 --- a/release/pkg/postrelease/setup_test.go +++ b/release/pkg/postrelease/setup_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/projectcalico/calico/release/internal/utils" + "github.com/projectcalico/calico/release/pkg/manager/operator" ) var ( @@ -46,4 +47,9 @@ func checkImages(t testing.TB, images string) { if len(list) == 0 { t.Fatal("No images provided") } + for _, image := range list { + if strings.Contains(image, operator.DefaultImage) { + t.Fatal("Operator images are checked separately, do not include in list of images to check") + } + } } diff --git a/release/pkg/tasks/hashrelease.go b/release/pkg/tasks/hashrelease.go index 5702e5971ff..9c89b7c9543 100644 --- a/release/pkg/tasks/hashrelease.go +++ b/release/pkg/tasks/hashrelease.go @@ -44,9 +44,9 @@ func HashreleasePublished(cfg *hashreleaseserver.Config, hash string, ci bool) ( // ReformatHashrelease modifies the generated release output to match // the "legacy" format our CI tooling expects. This should be temporary until // we can update the tooling to expect the new format. -// Specifically, we need to do two things: +// Specifically, we need to do the following: // - Copy the windows zip file to files/windows/calico-windows-.zip -// - Copy tigera-operator-.tgz to tigera-operator.tgz +// - Add a copy of the tigera operator chart without version in its name i.e. tigera-operator-.tgz to tigera-operator.tgz // - Copy ocp.tgz to manifests/ocp.tgz func ReformatHashrelease(hashreleaseOutputDir, tmpDir string) error { logrus.Info("Modifying hashrelease output to match legacy format") @@ -72,9 +72,9 @@ func ReformatHashrelease(hashreleaseOutputDir, tmpDir string) error { return err } - // Copy the operator tarball to tigera-operator.tgz - operatorTarball := filepath.Join(hashreleaseOutputDir, fmt.Sprintf("tigera-operator-%s.tgz", versions.HelmChartVersion())) - operatorTarballDst := filepath.Join(hashreleaseOutputDir, "tigera-operator.tgz") + // Add copy of the Tigera operator chart without version in name. + operatorTarball := filepath.Join(hashreleaseOutputDir, fmt.Sprintf("%s-%s.tgz", utils.TigeraOperatorChart, versions.HelmChartVersion())) + operatorTarballDst := filepath.Join(hashreleaseOutputDir, fmt.Sprintf("%s.tgz", utils.TigeraOperatorChart)) if err := utils.CopyFile(operatorTarball, operatorTarballDst); err != nil { return err } diff --git a/release/pkg/tasks/notification.go b/release/pkg/tasks/notification.go index 30f8dda1b60..65283da5891 100644 --- a/release/pkg/tasks/notification.go +++ b/release/pkg/tasks/notification.go @@ -25,7 +25,7 @@ import ( var product = utils.ProductName // AnnounceHashrelease sends a slack notification for a new hashrelease. -func AnnounceHashrelease(cfg *slack.Config, hashrel *hashreleaseserver.Hashrelease, ciURL string) error { +func AnnounceHashrelease(cfg *slack.Config, hashrel *hashreleaseserver.Hashrelease, ciURL string) (*slack.MessageResponse, error) { logrus.WithField("hashrelease", hashrel.Name).Info("Sending hashrelease announcement to Slack") msgData := &slack.HashreleaseMessageData{ ReleaseName: hashrel.Name, diff --git a/test-tools/deps.txt b/test-tools/deps.txt index 97ecffa8305..315053f5b62 100644 --- a/test-tools/deps.txt +++ b/test-tools/deps.txt @@ -2,57 +2,57 @@ This file contains the list of modules that this package depends on in order to trigger CI on changes -go 1.25.1 +go 1.25.7 github.com/beorn7/perks v1.0.1 github.com/cespare/xxhash/v2 v2.3.0 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc -github.com/emicklei/go-restful/v3 v3.11.0 -github.com/fxamacker/cbor/v2 v2.7.0 +github.com/emicklei/go-restful v2.15.0+incompatible +github.com/emicklei/go-restful/v3 v3.12.2 +github.com/fxamacker/cbor/v2 v2.9.0 github.com/go-logr/logr v1.4.3 github.com/go-openapi/jsonpointer v0.21.0 github.com/go-openapi/jsonreference v0.20.2 github.com/go-openapi/swag v0.23.0 github.com/gogo/protobuf v1.3.2 github.com/golang/snappy v1.0.0 -github.com/google/gnostic-models v0.6.9 -github.com/google/go-cmp v0.7.0 +github.com/google/gnostic-models v0.7.0 github.com/google/uuid v1.6.0 github.com/jinzhu/copier v0.4.0 github.com/josharian/intern v1.0.0 github.com/json-iterator/go v1.1.12 github.com/mailru/easyjson v0.7.7 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd -github.com/modern-go/reflect2 v1.0.2 +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 github.com/pkg/errors v0.9.1 github.com/projectcalico/calico -github.com/projectcalico/calico/lib/std v0.0.0-00010101000000-000000000000 -github.com/prometheus/client_golang v1.23.0 +github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 -github.com/prometheus/common v0.65.0 -github.com/prometheus/procfs v0.17.0 +github.com/prometheus/common v0.67.2 +github.com/prometheus/procfs v0.19.2 github.com/sirupsen/logrus v1.9.3 -github.com/spf13/pflag v1.0.7 +github.com/spf13/pflag v1.0.10 github.com/x448/float16 v0.8.4 -go.yaml.in/yaml/v2 v2.4.2 -golang.org/x/net v0.43.0 -golang.org/x/oauth2 v0.30.0 -golang.org/x/sys v0.35.0 -golang.org/x/term v0.34.0 -golang.org/x/text v0.28.0 -golang.org/x/time v0.12.0 -google.golang.org/protobuf v1.36.7 +go.yaml.in/yaml/v2 v2.4.3 +go.yaml.in/yaml/v3 v3.0.4 +golang.org/x/net v0.49.0 +golang.org/x/oauth2 v0.34.0 +golang.org/x/sys v0.40.0 +golang.org/x/term v0.39.0 +golang.org/x/text v0.33.0 +golang.org/x/time v0.14.0 +google.golang.org/protobuf v1.36.10 gopkg.in/evanphx/json-patch.v4 v4.12.0 gopkg.in/inf.v0 v0.9.1 gopkg.in/yaml.v3 v3.0.1 -k8s.io/api v0.33.5 -k8s.io/apimachinery v0.33.5 -k8s.io/client-go v0.33.5 +k8s.io/api v0.34.3 +k8s.io/apimachinery v0.34.3 +k8s.io/client-go v0.34.3 k8s.io/klog v0.2.0 k8s.io/klog/v2 v2.130.1 -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff -k8s.io/utils v0.0.0-20241210054802-24370beab758 +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 sigs.k8s.io/randfill v1.0.0 -sigs.k8s.io/structured-merge-diff/v4 v4.6.0 +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 sigs.k8s.io/yaml v1.6.0 diff --git a/test-tools/mocknode/Dockerfile b/test-tools/mocknode/Dockerfile index 6bc60be2f3e..ccf73935245 100644 --- a/test-tools/mocknode/Dockerfile +++ b/test-tools/mocknode/Dockerfile @@ -27,13 +27,21 @@ ARG GIT_VERSION=unknown # Required labels for certification LABEL org.opencontainers.image.description="Calico mock node for scale testing" -LABEL org.opencontainers.image.authors="shaun@tigera.io" +LABEL org.opencontainers.image.authors="maintainers@tigera.io" LABEL org.opencontainers.image.source="https://github.com/projectcalico/calico" LABEL org.opencontainers.image.title="Calico mock node" LABEL org.opencontainers.image.vendor="Project Calico" LABEL org.opencontainers.image.version="${GIT_VERSION}" LABEL org.opencontainers.image.licenses="Apache-2.0" +LABEL description="Calico mock node for scale testing" +LABEL maintainer="maintainers@tigera.io" +LABEL name="Calico mock node" +LABEL release=1 +LABEL summary="Calico mock node for scale testing" +LABEL vendor="Project Calico" +LABEL version="${GIT_VERSION}" + COPY --from=source / / ENTRYPOINT ["/usr/bin/mocknode"] diff --git a/test-tools/mocknode/mock-node.yaml b/test-tools/mocknode/mock-node.yaml index 947f9de122d..4bea7b6013f 100644 --- a/test-tools/mocknode/mock-node.yaml +++ b/test-tools/mocknode/mock-node.yaml @@ -28,7 +28,7 @@ spec: fieldPath: metadata.namespace - name: FIPS_MODE_ENABLED value: "false" - image: calico/mock-node:master + image: quay.io/calico/mock-node:master imagePullPolicy: IfNotPresent name: mock-calico-node volumeMounts: diff --git a/third_party/envoy-gateway/Makefile b/third_party/envoy-gateway/Makefile index 502a6e35063..1791860d9da 100644 --- a/third_party/envoy-gateway/Makefile +++ b/third_party/envoy-gateway/Makefile @@ -7,7 +7,7 @@ BUILD_IMAGES ?= $(ENVOY_GATEWAY_IMAGE) # For updating this version please see # https://github.com/tigera/operator/blob/master/docs/common_tasks.md#updating-the-bundled-version-of-envoy-gateway -ENVOY_GATEWAY_VERSION=v1.3.2 +ENVOY_GATEWAY_VERSION=v1.5.7 ############################################################################## # Include lib.Makefile before anything else @@ -26,7 +26,17 @@ init-source: $(ENVOY_GATEWAY_DOWNLOADED) $(ENVOY_GATEWAY_DOWNLOADED): mkdir -p envoy-gateway curl -sfL https://github.com/envoyproxy/gateway/archive/refs/tags/$(ENVOY_GATEWAY_VERSION).tar.gz | tar xz --strip-components 1 -C envoy-gateway - patch -d envoy-gateway -p1 < patches/0001-Bumping-x-net-and-containerd.patch + +# Apply patches for the specified Envoy Gateway version if patches directory exists. +# This code checks for the presence of a patches directory corresponding to ENVOY_GATEWAY_VERSION. +# If the directory exists, it iterates over all .patch files within it. +# For each valid patch file, it applies the patch to the envoy-gateway directory using patch -p1. + for patch in patches/*.patch; do \ + if [ -f "$$patch" ]; then \ + patch -d envoy-gateway -p1 < "$$patch"; \ + fi; \ + done; \ + touch $@ .PHONY: build diff --git a/third_party/envoy-gateway/patches/0001-Bump-containerd-to-v1.7.29.patch b/third_party/envoy-gateway/patches/0001-Bump-containerd-to-v1.7.29.patch new file mode 100644 index 00000000000..e79eef60237 --- /dev/null +++ b/third_party/envoy-gateway/patches/0001-Bump-containerd-to-v1.7.29.patch @@ -0,0 +1,41 @@ +From 43de629ad1ebeec479e9c4e4b3cd0dd9d5ae2f6b Mon Sep 17 00:00:00 2001 +From: Nell Jerram +Date: Fri, 12 Dec 2025 16:26:08 +0000 +Subject: [PATCH] Bump containerd to v1.7.29 + +--- + go.mod | 2 +- + go.sum | 4 ++-- + 2 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/go.mod b/go.mod +index f7e18a6d2..20ddad105 100644 +--- a/go.mod ++++ b/go.mod +@@ -180,7 +180,7 @@ require ( + github.com/cilium/ebpf v0.19.0 // indirect + github.com/ckaznocha/intrange v0.3.1 // indirect + github.com/containerd/cgroups/v3 v3.0.5 // indirect +- github.com/containerd/containerd v1.7.27 // indirect ++ github.com/containerd/containerd v1.7.29 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/containerd/log v0.1.0 // indirect +diff --git a/go.sum b/go.sum +index 1e681262a..a4eebbafb 100644 +--- a/go.sum ++++ b/go.sum +@@ -223,8 +223,8 @@ github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv + github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= + github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo= + github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins= +-github.com/containerd/containerd v1.7.27 h1:yFyEyojddO3MIGVER2xJLWoCIn+Up4GaHFquP7hsFII= +-github.com/containerd/containerd v1.7.27/go.mod h1:xZmPnl75Vc+BLGt4MIfu6bp+fy03gdHAn9bz+FreFR0= ++github.com/containerd/containerd v1.7.29 h1:90fWABQsaN9mJhGkoVnuzEY+o1XDPbg9BTC9QTAHnuE= ++github.com/containerd/containerd v1.7.29/go.mod h1:azUkWcOvHrWvaiUjSQH0fjzuHIwSPg1WL5PshGP4Szs= + github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= + github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= + github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +-- +2.43.0 + diff --git a/third_party/envoy-gateway/patches/0001-Bumping-x-net-and-containerd.patch b/third_party/envoy-gateway/patches/0001-Bumping-x-net-and-containerd.patch deleted file mode 100644 index b87817c6c08..00000000000 --- a/third_party/envoy-gateway/patches/0001-Bumping-x-net-and-containerd.patch +++ /dev/null @@ -1,168 +0,0 @@ -diff --git a/go.mod b/go.mod -index ff70a2a..dd51dd3 100644 ---- a/go.mod -+++ b/go.mod -@@ -59,9 +59,9 @@ require ( - go.opentelemetry.io/otel/sdk/metric v1.34.0 - go.opentelemetry.io/proto/otlp v1.5.0 - go.uber.org/zap v1.27.0 -- golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e -- golang.org/x/net v0.35.0 -- golang.org/x/sys v0.30.0 -+ golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc -+ golang.org/x/net v0.42.0 -+ golang.org/x/sys v0.34.0 - google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f - google.golang.org/grpc v1.69.4 - google.golang.org/protobuf v1.36.3 -@@ -114,7 +114,7 @@ require ( - github.com/chai2010/gettext-go v1.0.2 // indirect - github.com/cilium/ebpf v0.16.0 // indirect - github.com/containerd/cgroups/v3 v3.0.3 // indirect -- github.com/containerd/containerd v1.7.23 // indirect -+ github.com/containerd/containerd v1.7.27 // indirect - github.com/containerd/errdefs v0.3.0 // indirect - github.com/containerd/log v0.1.0 // indirect - github.com/containerd/platforms v0.2.1 // indirect -@@ -275,15 +275,15 @@ require ( - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect - go.opentelemetry.io/otel/trace v1.34.0 // indirect - go.uber.org/multierr v1.11.0 // indirect -- golang.org/x/crypto v0.35.0 // indirect -+ golang.org/x/crypto v0.40.0 // indirect - golang.org/x/crypto/x509roots/fallback v0.0.0-20240904212608-c9da6b9a4008 // indirect -- golang.org/x/mod v0.23.0 // indirect -- golang.org/x/oauth2 v0.27.0 // indirect -- golang.org/x/sync v0.11.0 // indirect -- golang.org/x/term v0.29.0 // indirect -- golang.org/x/text v0.22.0 // indirect -- golang.org/x/time v0.9.0 // indirect -- golang.org/x/tools v0.30.0 // indirect -+ golang.org/x/mod v0.26.0 // indirect -+ golang.org/x/oauth2 v0.30.0 // indirect -+ golang.org/x/sync v0.16.0 // indirect -+ golang.org/x/term v0.33.0 // indirect -+ golang.org/x/text v0.27.0 // indirect -+ golang.org/x/time v0.12.0 // indirect -+ golang.org/x/tools v0.35.0 // indirect - gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect -diff --git a/go.sum b/go.sum -index d23202d..02e317a 100644 ---- a/go.sum -+++ b/go.sum -@@ -130,10 +130,10 @@ github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1Ig - github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= - github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= - github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0= --github.com/containerd/containerd v1.7.23 h1:H2CClyUkmpKAGlhQp95g2WXHfLYc7whAuvZGBNYOOwQ= --github.com/containerd/containerd v1.7.23/go.mod h1:7QUzfURqZWCZV7RLNEn1XjUCQLEf0bkaK4GjUaZehxw= --github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= --github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= -+github.com/containerd/containerd v1.7.27 h1:yFyEyojddO3MIGVER2xJLWoCIn+Up4GaHFquP7hsFII= -+github.com/containerd/containerd v1.7.27/go.mod h1:xZmPnl75Vc+BLGt4MIfu6bp+fy03gdHAn9bz+FreFR0= -+github.com/containerd/continuity v0.4.4 h1:/fNVfTJ7wIl/YPMHjf+5H32uFhl63JucB34PlCpMKII= -+github.com/containerd/continuity v0.4.4/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= - github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= - github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= - github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -@@ -966,21 +966,21 @@ golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8U - golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= - golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= --golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= --golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= -+golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -+golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= - golang.org/x/crypto/x509roots/fallback v0.0.0-20240904212608-c9da6b9a4008 h1:vKHSxFhPLnBEYu9R8DcQ4gXq9EqU0VVhC9pq9wmtYsg= - golang.org/x/crypto/x509roots/fallback v0.0.0-20240904212608-c9da6b9a4008/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8= - golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= --golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= --golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= -+golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc= -+golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc= - golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= - golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= - golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= - golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= - golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= - golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= --golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= --golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -+golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= -+golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= - golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= - golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= - golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -@@ -1004,13 +1004,13 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL - golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= - golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= - golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= --golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= --golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= -+golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -+golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= - 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= --golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= --golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= -+golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -+golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= - golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= - golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= - golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -@@ -1018,8 +1018,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ - golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= - golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= - golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= --golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= --golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -+golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -+golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= - golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= - golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= - golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -@@ -1056,22 +1056,22 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= - golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= - golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= - golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= --golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= --golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= --golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= --golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= -+golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -+golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -+golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= -+golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= - golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/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= - golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= - golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= --golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= --golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -+golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -+golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= - golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= - 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.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= --golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -+golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -+golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= - golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= - golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= - golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -@@ -1088,8 +1088,8 @@ golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtn - golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= - golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= - golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= --golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= --golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= -+golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= -+golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= - golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= - golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= - golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/third_party/envoy-proxy/Dockerfile b/third_party/envoy-proxy/Dockerfile index df0c0c426e0..cc33b625bf8 100644 --- a/third_party/envoy-proxy/Dockerfile +++ b/third_party/envoy-proxy/Dockerfile @@ -26,19 +26,14 @@ COPY --from=ubi /lib64/libaudit.so.1 /lib64/libaudit.so.1 COPY --from=ubi /lib64/libbz2.so.1 /lib64/libbz2.so.1 COPY --from=ubi /lib64/libcap-ng.so.0 /lib64/libcap-ng.so.0 COPY --from=ubi /lib64/libcap.so.2 /lib64/libcap.so.2 -COPY --from=ubi /lib64/libdl.so.2 /lib64/libdl.so.2 -COPY --from=ubi /lib64/libm.so.6 /lib64/libm.so.6 COPY --from=ubi /lib64/libpcre2-8.so.0 /lib64/libpcre2-8.so.0 -COPY --from=ubi /lib64/librt.so.1 /lib64/librt.so.1 COPY --from=ubi /lib64/libselinux.so.1 /lib64/libselinux.so.1 COPY --from=ubi /lib64/libsemanage.so.2 /lib64/libsemanage.so.2 COPY --from=ubi /lib64/libsepol.so.2 /lib64/libsepol.so.2 COPY --from=ubi /lib64/libtinfo.so.6 /lib64/libtinfo.so.6 -COPY --from=envoybinary --chown=0:0 --chmod=644 \ - /etc/envoy/envoy.yaml /etc/envoy/envoy.yaml -COPY --from=envoybinary --chown=0:0 --chmod=755 \ - /usr/local/bin/envoy /usr/local/bin/envoy +COPY --from=envoybinary /etc/envoy/envoy.yaml /etc/envoy/envoy.yaml +COPY --from=envoybinary --chmod=755 /usr/local/bin/envoy /usr/local/bin/envoy FROM ${CALICO_BASE} diff --git a/third_party/envoy-proxy/Makefile b/third_party/envoy-proxy/Makefile index f0cc2313d63..de9827a953b 100644 --- a/third_party/envoy-proxy/Makefile +++ b/third_party/envoy-proxy/Makefile @@ -7,7 +7,7 @@ BUILD_IMAGES ?= $(ENVOY_PROXY_IMAGE) # For updating this version please see # https://github.com/tigera/operator/blob/master/docs/common_tasks.md#updating-the-bundled-version-of-envoy-gateway -ENVOYBINARY_IMAGE ?= quay.io/tigera/envoybinary:d09f1c3f01d046ddcd1a1beb1249a178795d3c49 +ENVOYBINARY_IMAGE ?= quay.io/tigera/envoybinary:v1.35.8-6ddb700081 EXCLUDEARCH ?= ppc64le s390x diff --git a/third_party/envoy-ratelimit/Makefile b/third_party/envoy-ratelimit/Makefile index 0c269103a31..711a8246da7 100644 --- a/third_party/envoy-ratelimit/Makefile +++ b/third_party/envoy-ratelimit/Makefile @@ -7,7 +7,7 @@ BUILD_IMAGES ?= $(ENVOY_RATELIMIT_IMAGE) # For updating this version please see # https://github.com/tigera/operator/blob/master/docs/common_tasks.md#updating-the-bundled-version-of-envoy-gateway -ENVOY_RATELIMIT_VERSION=0141a24f +ENVOY_RATELIMIT_VERSION=e74a664a ############################################################################## # Include lib.Makefile before anything else @@ -26,7 +26,6 @@ init-source: $(ENVOY_RATELIMIT_DOWNLOADED) $(ENVOY_RATELIMIT_DOWNLOADED): git clone -n https://github.com/envoyproxy/ratelimit.git envoy-ratelimit cd envoy-ratelimit && git checkout $(ENVOY_RATELIMIT_VERSION) - patch -d envoy-ratelimit -p1 < patches/0001-Bump-golang.org-x-deps.patch touch $@ .PHONY: build diff --git a/third_party/envoy-ratelimit/patches/0001-Bump-golang.org-x-deps.patch b/third_party/envoy-ratelimit/patches/0001-Bump-golang.org-x-deps.patch deleted file mode 100644 index cf802ed5f01..00000000000 --- a/third_party/envoy-ratelimit/patches/0001-Bump-golang.org-x-deps.patch +++ /dev/null @@ -1,126 +0,0 @@ -diff --git a/go.mod b/go.mod -index 18643a8..4db1ffe 100644 ---- a/go.mod -+++ b/go.mod -@@ -1,6 +1,8 @@ - module github.com/envoyproxy/ratelimit - --go 1.22.8 -+go 1.23.0 -+ -+toolchain go1.24.5 - - require ( - github.com/DataDog/datadog-go/v5 v5.5.0 -@@ -31,7 +33,7 @@ require ( - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 - go.opentelemetry.io/otel/sdk v1.28.0 - go.opentelemetry.io/otel/trace v1.28.0 -- golang.org/x/net v0.33.0 -+ golang.org/x/net v0.42.0 - google.golang.org/grpc v1.65.0 - google.golang.org/protobuf v1.34.2 - gopkg.in/yaml.v2 v2.4.0 -@@ -43,7 +45,6 @@ require ( - github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect -- github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b // indirect - github.com/davecgh/go-spew v1.1.1 // indirect -@@ -52,7 +53,6 @@ require ( - github.com/go-logfmt/logfmt v0.6.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect - github.com/go-logr/stdr v1.2.2 // indirect -- github.com/golang/protobuf v1.5.4 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect - github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect -@@ -62,8 +62,8 @@ require ( - github.com/yuin/gopher-lua v1.1.1 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect -- golang.org/x/sys v0.28.0 // indirect -- golang.org/x/text v0.21.0 // indirect -+ golang.org/x/sys v0.34.0 // indirect -+ golang.org/x/text v0.27.0 // indirect - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect -diff --git a/go.sum b/go.sum -index bd32624..09bfb8b 100644 ---- a/go.sum -+++ b/go.sum -@@ -18,8 +18,6 @@ github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xu - github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= - github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= - github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= --github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= --github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= - github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= - github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= - github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -@@ -35,15 +33,9 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs - github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= - github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= - github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= --github.com/envoyproxy/go-control-plane v0.12.1-0.20240123181358-841e293a220b h1:M0BhcNaW04UV1haQO8IFSDB64dAeiBSsTMZks/sYDcQ= --github.com/envoyproxy/go-control-plane v0.12.1-0.20240123181358-841e293a220b/go.mod h1:lFu6itz1hckLR2A3aJ+ZKf3lu8HpjTsJSsqvVF6GL6g= --github.com/envoyproxy/go-control-plane v0.13.1 h1:vPfJZCkob6yTMEgS+0TwfTUfbHjfy/6vOJ8hUWX/uXE= --github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw= - github.com/envoyproxy/go-control-plane v0.13.2-0.20241219025321-f011ad88ec17 h1:vJbk97KFgBX0QdyydT18FDmwqCeRZzUYUdm/o338h8I= - github.com/envoyproxy/go-control-plane v0.13.2-0.20241219025321-f011ad88ec17/go.mod h1:lHUJZHyVI6Q4Vr6qjD60ZHBybFRLzqoKVZGIJi0/i8s= - github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= --github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= --github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= - github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= - github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= - github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -@@ -111,8 +103,6 @@ github.com/mediocregopher/radix/v3 v3.8.1/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i - github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= - github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= - github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= --github.com/planetscale/vtprotobuf v0.5.1-0.20231212170721-e7d721933795 h1:pH+U6pJP0BhxqQ4njBUjOg0++WMMvv3eByWzB+oATBY= --github.com/planetscale/vtprotobuf v0.5.1-0.20231212170721-e7d721933795/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= - github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= - github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= - github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -@@ -148,8 +138,6 @@ 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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= --github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= - github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= - github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= - github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -@@ -201,8 +189,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL - golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= - golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= - golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= --golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= --golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -+golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -+golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= - golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= - golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= - golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -@@ -224,13 +212,13 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w - golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= - golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= - golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= --golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= --golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -+golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -+golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= - golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= - golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= - golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= --golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= --golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -+golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -+golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= - golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= - golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= - golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= diff --git a/typha/Makefile b/typha/Makefile index d66365c53c0..23b1e4ee303 100644 --- a/typha/Makefile +++ b/typha/Makefile @@ -45,7 +45,7 @@ clean: report/*.xml \ release-notes-* \ .calico_typha.created-* - find . -name "*.coverprofile" -type f -delete + find . -name "coverprofile.out" -type f -delete find . -name "coverage.xml" -type f -delete find . -name ".coverage" -type f -delete find . -name "*.pyc" -type f -delete diff --git a/typha/README.md b/typha/README.md index a842a87311d..5d1320c7667 100644 --- a/typha/README.md +++ b/typha/README.md @@ -52,7 +52,7 @@ your contribution. ## How do I build Typha? -Typha mostly uses Docker for builds. We develop on Ubuntu 16.04 but other +Typha mostly uses Docker for builds. We develop on Ubuntu 24.04 but other Linux distributions should work (there are known Makefile that prevent building on OS X). To build Typha, you will need: diff --git a/typha/deps.txt b/typha/deps.txt index 1f90ceb66a0..3f78c9221a5 100644 --- a/typha/deps.txt +++ b/typha/deps.txt @@ -2,16 +2,19 @@ This file contains the list of modules that this package depends on in order to trigger CI on changes -go 1.25.1 +go 1.25.7 github.com/beorn7/perks v1.0.1 github.com/cespare/xxhash/v2 v2.3.0 github.com/coreos/go-semver v0.3.1 -github.com/coreos/go-systemd/v22 v22.5.0 +github.com/coreos/go-systemd/v22 v22.6.0 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 -github.com/emicklei/go-restful/v3 v3.11.0 -github.com/fxamacker/cbor/v2 v2.7.0 +github.com/emicklei/go-restful v2.15.0+incompatible +github.com/emicklei/go-restful/v3 v3.12.2 +github.com/fxamacker/cbor/v2 v2.9.0 github.com/go-ini/ini v1.67.0 +github.com/go-kit/log v0.2.1 +github.com/go-logfmt/logfmt v0.6.0 github.com/go-logr/logr v1.4.3 github.com/go-openapi/jsonpointer v0.21.0 github.com/go-openapi/jsonreference v0.20.2 @@ -22,11 +25,10 @@ github.com/gogo/protobuf v1.3.2 github.com/golang/protobuf v1.5.4 github.com/golang/snappy v1.0.0 github.com/google/btree v1.1.3 -github.com/google/gnostic-models v0.6.9 -github.com/google/go-cmp v0.7.0 +github.com/google/gnostic-models v0.7.0 github.com/google/uuid v1.6.0 -github.com/grpc-ecosystem/grpc-gateway v1.16.0 -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 github.com/jinzhu/copier v0.4.0 github.com/josharian/intern v1.0.0 github.com/json-iterator/go v1.1.12 @@ -37,57 +39,60 @@ github.com/mailru/easyjson v0.7.7 github.com/mattn/go-runewidth v0.0.16 github.com/mipearson/rfw v0.0.0-20170619235010-6f0a6f3266ba github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd -github.com/modern-go/reflect2 v1.0.2 +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 github.com/olekukonko/tablewriter v0.0.5 +github.com/openshift/custom-resource-status v1.1.2 github.com/pkg/errors v0.9.1 +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/projectcalico/calico -github.com/projectcalico/calico/lib/std v0.0.0-00010101000000-000000000000 -github.com/projectcalico/go-json v0.0.0-20161128004156-6219dc7339ba -github.com/projectcalico/go-yaml-wrapper v0.0.0-20191112210931-090425220c54 -github.com/prometheus/client_golang v1.23.0 +github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 -github.com/prometheus/common v0.65.0 -github.com/prometheus/procfs v0.17.0 +github.com/prometheus/common v0.67.2 +github.com/prometheus/procfs v0.19.2 github.com/rivo/uniseg v0.4.7 github.com/sirupsen/logrus v1.9.3 -github.com/spf13/pflag v1.0.7 +github.com/spf13/pflag v1.0.10 github.com/x448/float16 v0.8.4 -go.etcd.io/etcd/api/v3 v3.6.4 -go.etcd.io/etcd/client/pkg/v3 v3.6.4 -go.etcd.io/etcd/client/v3 v3.6.4 +go.etcd.io/etcd/api/v3 v3.6.5 +go.etcd.io/etcd/client/pkg/v3 v3.6.5 +go.etcd.io/etcd/client/v3 v3.6.5 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 -go.yaml.in/yaml/v2 v2.4.2 -golang.org/x/crypto v0.41.0 -golang.org/x/net v0.43.0 -golang.org/x/oauth2 v0.30.0 -golang.org/x/sync v0.16.0 -golang.org/x/sys v0.35.0 -golang.org/x/term v0.34.0 -golang.org/x/text v0.28.0 -golang.org/x/time v0.12.0 +go.yaml.in/yaml/v2 v2.4.3 +go.yaml.in/yaml/v3 v3.0.4 +golang.org/x/crypto v0.47.0 +golang.org/x/net v0.49.0 +golang.org/x/oauth2 v0.34.0 +golang.org/x/sync v0.19.0 +golang.org/x/sys v0.40.0 +golang.org/x/term v0.39.0 +golang.org/x/text v0.33.0 +golang.org/x/time v0.14.0 golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 google.golang.org/genproto v0.0.0-20250603155806-513f23925822 -google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 -google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a -google.golang.org/grpc v1.72.2 -google.golang.org/protobuf v1.36.7 +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c +google.golang.org/grpc v1.76.0 +google.golang.org/protobuf v1.36.10 gopkg.in/evanphx/json-patch.v4 v4.12.0 gopkg.in/go-playground/validator.v9 v9.30.2 gopkg.in/inf.v0 v0.9.1 -gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 -k8s.io/api v0.33.5 -k8s.io/apimachinery v0.33.5 -k8s.io/client-go v0.33.5 +k8s.io/api v0.34.3 +k8s.io/apiextensions-apiserver v0.34.3 +k8s.io/apimachinery v0.34.3 +k8s.io/client-go v0.34.3 k8s.io/klog v0.2.0 k8s.io/klog/v2 v2.130.1 -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff -k8s.io/utils v0.0.0-20241210054802-24370beab758 +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 +kubevirt.io/api v1.8.0-alpha.0 +kubevirt.io/containerized-data-importer-api v1.63.1 +kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 -sigs.k8s.io/network-policy-api v0.1.5 +sigs.k8s.io/network-policy-api v0.1.8-0.20260212153203-412bf65729a5 sigs.k8s.io/randfill v1.0.0 -sigs.k8s.io/structured-merge-diff/v4 v4.6.0 +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 sigs.k8s.io/yaml v1.6.0 diff --git a/typha/docker-image/Dockerfile b/typha/docker-image/Dockerfile index 56ae983116a..a71997c2a60 100644 --- a/typha/docker-image/Dockerfile +++ b/typha/docker-image/Dockerfile @@ -35,6 +35,14 @@ LABEL org.opencontainers.image.vendor="Project Calico" LABEL org.opencontainers.image.version="${GIT_VERSION}" LABEL org.opencontainers.image.licenses="Apache-2.0" +LABEL description="Calico Typha is a fan-out datastore proxy" +LABEL maintainer="maintainers@tigera.io" +LABEL name="Calico Typha" +LABEL release=1 +LABEL summary="Calico Typha is a fan-out datastore proxy" +LABEL vendor="Project Calico" +LABEL version="${GIT_VERSION}" + COPY --from=source / / USER 999 diff --git a/typha/fv-tests/fv_tests_suite_test.go b/typha/fv-tests/fv_tests_suite_test.go index f12b2db07da..d8e0e1192a8 100644 --- a/typha/fv-tests/fv_tests_suite_test.go +++ b/typha/fv-tests/fv_tests_suite_test.go @@ -17,9 +17,8 @@ package fvtests_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestFvTests(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/fv_tests_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "FV Tests Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../report/fv_tests_suite.xml" + ginkgo.RunSpecs(t, "FV Tests Suite", suiteConfig, reporterConfig) } diff --git a/typha/fv-tests/reconnection_test.go b/typha/fv-tests/reconnection_test.go index 79955824ac1..5a1047bff69 100644 --- a/typha/fv-tests/reconnection_test.go +++ b/typha/fv-tests/reconnection_test.go @@ -76,9 +76,7 @@ func TestReconnectionNewServerNotInSync(t *testing.T) { // keep the extra value from h[0]. tweakUpdateTypes(h1ConfigUpdates, h0ConfigUpdates) h0Withh1Overlay := maps.Clone(h0ConfigUpdates) - for k, v := range h1ConfigUpdates { - h0Withh1Overlay[k] = v - } + maps.Copy(h0Withh1Overlay, h1ConfigUpdates) Eventually(recorder.KVs).Should(Equal(h0Withh1Overlay)) Consistently(recorder.Status).Should(Equal(api.ResyncInProgress)) @@ -120,7 +118,7 @@ func setUpReconnectionTest(t *testing.T) ( logutils.RedirectLogrusToTestingT(t) var h [2]*ServerHarness - for i := 0; i < 2; i++ { + for i := range 2 { h[i] = NewHarness() t.Cleanup(h[i].Stop) h[i].Start() diff --git a/typha/fv-tests/server_harness_test.go b/typha/fv-tests/server_harness_test.go index 7e025844cce..5cf97ab36ba 100644 --- a/typha/fv-tests/server_harness_test.go +++ b/typha/fv-tests/server_harness_test.go @@ -23,7 +23,7 @@ import ( "sync" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" @@ -169,13 +169,13 @@ func (h *ServerHarness) Stop() { } } -func (h *ServerHarness) CreateNoOpClient(id interface{}, syncType syncproto.SyncerType) *ClientState { +func (h *ServerHarness) CreateNoOpClient(id any, syncType syncproto.SyncerType) *ClientState { c := h.createClient(id, syncclient.Options{SyncerType: syncType, DebugDiscardKVUpdates: true}, NoOpCallbacks{}) h.NoOpClientStates = append(h.ClientStates, c) return c } -func (h *ServerHarness) CreateClient(id interface{}, syncType syncproto.SyncerType) *ClientState { +func (h *ServerHarness) CreateClient(id any, syncType syncproto.SyncerType) *ClientState { recorder := NewRecorder() c := h.createClient(id, syncclient.Options{SyncerType: syncType}, recorder) c.recorder = recorder @@ -184,7 +184,7 @@ func (h *ServerHarness) CreateClient(id interface{}, syncType syncproto.SyncerTy return c } -func (h *ServerHarness) CreateClientNoDecodeRestart(id interface{}, syncType syncproto.SyncerType) *ClientState { +func (h *ServerHarness) CreateClientNoDecodeRestart(id any, syncType syncproto.SyncerType) *ClientState { recorder := NewRecorder() c := h.createClient(id, syncclient.Options{SyncerType: syncType, DisableDecoderRestart: true}, recorder) c.recorder = recorder @@ -193,7 +193,7 @@ func (h *ServerHarness) CreateClientNoDecodeRestart(id interface{}, syncType syn return c } -func (h *ServerHarness) CreateNoOpClientNoDecodeRestart(id interface{}, syncType syncproto.SyncerType) *ClientState { +func (h *ServerHarness) CreateNoOpClientNoDecodeRestart(id any, syncType syncproto.SyncerType) *ClientState { c := h.createClient(id, syncclient.Options{SyncerType: syncType, DisableDecoderRestart: true, DebugDiscardKVUpdates: true}, NoOpCallbacks{}) h.NoOpClientStates = append(h.ClientStates, c) return c @@ -214,7 +214,7 @@ func (h *ServerHarness) ExpectAllClientsToReachState(status api.SyncStatus, kvs wg.Wait() } -func (h *ServerHarness) createClient(id interface{}, options syncclient.Options, callbacks api.SyncerCallbacks) *ClientState { +func (h *ServerHarness) createClient(id any, options syncclient.Options, callbacks api.SyncerCallbacks) *ClientState { client := syncclient.New( discovery.New(discovery.WithAddrOverride(h.Addr())), "test-version", @@ -241,19 +241,19 @@ func (h *ServerHarness) createClient(id interface{}, options syncclient.Options, } func (h *ServerHarness) CreateNoOpClients(n int) { - for i := 0; i < n; i++ { + for i := range n { h.CreateNoOpClient(i, syncproto.SyncerTypeFelix) } } func (h *ServerHarness) CreateNoOpClientsNoDecodeRestart(n int) { - for i := 0; i < n; i++ { + for i := range n { h.CreateNoOpClientNoDecodeRestart(i, syncproto.SyncerTypeFelix) } } func (h *ServerHarness) CreateClients(n int) { - for i := 0; i < n; i++ { + for i := range n { h.CreateClient(i, syncproto.SyncerTypeFelix) } } @@ -277,7 +277,7 @@ func (h *ServerHarness) SendInitialSnapshotPodsNoInSync(numPods int) map[string] func (h *ServerHarness) SendPodUpdates(numPods int) map[string]api.Update { expectedEndState := map[string]api.Update{} conv := conversion.NewConverter() - for i := 0; i < numPods; i++ { + for range numPods { pod := generatePod(h.updIdx) h.updIdx++ weps, err := conv.PodToWorkloadEndpoints(pod) @@ -308,7 +308,7 @@ func (h *ServerHarness) SendInitialSnapshotConfigsNoInSync(numConfigs int) map[s func (h *ServerHarness) SendConfigUpdates(n int) map[string]api.Update { expectedEndState := map[string]api.Update{} - for i := 0; i < n; i++ { + for range n { update := api.Update{ KVPair: model.KVPair{ Key: model.GlobalConfigKey{ diff --git a/typha/fv-tests/server_test.go b/typha/fv-tests/server_test.go index b42e6c157be..78a0f5ef4d5 100644 --- a/typha/fv-tests/server_test.go +++ b/typha/fv-tests/server_test.go @@ -21,14 +21,14 @@ import ( "errors" "fmt" "io" + "maps" "net" "os" "path/filepath" "sync" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/prometheus/client_golang/prometheus" @@ -36,7 +36,7 @@ import ( log "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - libapiv3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/encap" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" @@ -120,8 +120,8 @@ var ( } v3Node = api.Update{ KVPair: model.KVPair{ - Key: model.ResourceKey{Name: "node1", Kind: libapiv3.KindNode}, - Value: &libapiv3.Node{ + Key: model.ResourceKey{Name: "node1", Kind: internalapi.KindNode}, + Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ ResourceVersion: "1237", }, @@ -266,7 +266,8 @@ var _ = Describe("With an in-process Server", func() { Revision: "1234", }, UpdateType: api.UpdateTypeKVNew, - }}) + }, + }) }) It("should pass through a KV and status", func() { @@ -359,7 +360,7 @@ var _ = Describe("With an in-process Server", func() { expectedState := map[string]api.Update{} const numKeys = 10000 const halfNumKeys = numKeys / 2 - for i := 0; i < numKeys; i++ { + for i := range numKeys { upd := api.Update{ KVPair: model.KVPair{ Key: model.GlobalConfigKey{Name: fmt.Sprintf("foobar%d", i)}, @@ -374,7 +375,7 @@ var _ = Describe("With an in-process Server", func() { expectedState[fmt.Sprintf("/calico/v1/config/foobar%d", i)] = upd } } - for i := 0; i < halfNumKeys; i++ { + for i := range halfNumKeys { h.Decoupler.OnUpdates([]api.Update{{ KVPair: model.KVPair{ Key: model.GlobalConfigKey{Name: fmt.Sprintf("foobar%d", i)}, @@ -413,9 +414,7 @@ var _ = Describe("With an in-process Server", func() { expState := h.SendInitialSnapshotPods(10) h.ExpectAllClientsToReachState(api.InSync, expState) expState2 := h.SendPodUpdates(10) - for k, v := range expState2 { - expState[k] = v - } + maps.Copy(expState, expState2) h.ExpectAllClientsToReachState(api.InSync, expState) }) }) @@ -516,11 +515,9 @@ var _ = Describe("With an in-process Server", func() { It("with churn, it should report the correct number of connections after killing the clients", func() { // Generate some churn while we disconnect the clients. var wg sync.WaitGroup - wg.Add(1) - go func() { + wg.Go(func() { h.SendInitialSnapshotPods(1000) - wg.Done() - }() + }) defer wg.Wait() for _, c := range h.ClientStates { c.clientCancel() @@ -744,10 +741,31 @@ var _ = Describe("With an in-process Server with short ping timeout", func() { BeforeEach(func() { err := w.Encode(syncproto.Envelope{ Message: syncproto.MsgClientHello{ - Hostname: "me", - Version: "test", - Info: "test info", - SyncerType: syncproto.SyncerType("garbage"), + Hostname: "me", + Version: "test", + Info: "test info", + SyncerType: syncproto.SyncerType("garbage"), + SupportsModernPolicyKeys: true, + }, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should disconnect the client", func() { + expectDisconnection(100 * time.Millisecond) + expectGlobalGaugeValue("typha_connections_active", 0.0) + }) + }) + + Describe("After sending Hello with no policy key support", func() { + BeforeEach(func() { + err := w.Encode(syncproto.Envelope{ + Message: syncproto.MsgClientHello{ + Hostname: "me", + Version: "test", + Info: "test info", + SyncerType: syncproto.SyncerTypeFelix, + SupportsModernPolicyKeys: false, }, }) Expect(err).NotTo(HaveOccurred()) @@ -779,9 +797,10 @@ var _ = Describe("With an in-process Server with short ping timeout", func() { BeforeEach(func() { err := w.Encode(syncproto.Envelope{ Message: syncproto.MsgClientHello{ - Hostname: "me", - Version: "test", - Info: "test info", + Hostname: "me", + Version: "test", + Info: "test info", + SupportsModernPolicyKeys: true, }, }) Expect(err).NotTo(HaveOccurred()) @@ -925,9 +944,7 @@ var _ = Describe("With an in-process Server with long ping interval", func() { }) var _ = Describe("With an in-process Server with short grace period", func() { - var ( - h *ServerHarness - ) + var h *ServerHarness BeforeEach(func() { h = NewHarness() @@ -1137,9 +1154,7 @@ var _ = Describe("With an in-process Server with short grace period", func() { }) var _ = Describe("With an in-process Server with short write timeout", func() { - var ( - h *ServerHarness - ) + var h *ServerHarness BeforeEach(func() { // Default to debug but some more aggressive tests override this below. @@ -1196,7 +1211,6 @@ var _ = Describe("With an in-process Server with short write timeout", func() { }) for _, disabledDecoderRestart := range []bool{true, false} { - disabledDecoderRestart := disabledDecoderRestart It(fmt.Sprintf("client (DisabledDecoderRestart=%v) that blocks while reading snapshot should get disconnected", disabledDecoderRestart), func() { clientCxt, clientCancel := context.WithCancel(context.Background()) @@ -1329,9 +1343,16 @@ const ( serverURISAN = "spiffe://k8s.example.com/typha-server" ) -var _ = Describe("with server requiring TLS", func() { - var certDir string +var certDir string +var _ = AfterSuite(func() { + // Remove TLS keys and certificates. + if certDir != "" { + _ = os.RemoveAll(certDir) + } +}) + +var _ = Describe("with server requiring TLS", func() { BeforeEach(func() { // Generating certs is expensive, so we defer it until this BeforeEach and then reuse the certs for all the // tests. @@ -1388,13 +1409,6 @@ var _ = Describe("with server requiring TLS", func() { tlsutils.WriteCert(clientCert, filepath.Join(certDir, "client-untrusted.crt")) }) - var _ = AfterSuite(func() { - // Remove TLS keys and certificates. - if certDir != "" { - _ = os.RemoveAll(certDir) - } - }) - // We'll create this pipeline for updates to flow through: // // This goroutine -> callback -chan-> validation -> snapshot -> server @@ -1497,7 +1511,6 @@ var _ = Describe("with server requiring TLS", func() { }) testConnection := func(clientCertName string, expectConnection bool) { - var options *syncclient.Options = nil if clientCertName != "" { options = &syncclient.Options{ @@ -1728,5 +1741,4 @@ var _ = Describe("with server requiring TLS", func() { Eventually(server.NumActiveConnections).Should(Equal(0)) }) }) - }) diff --git a/typha/fv-tests/state_recorder.go b/typha/fv-tests/state_recorder.go index 4633fe42141..4b337165516 100644 --- a/typha/fv-tests/state_recorder.go +++ b/typha/fv-tests/state_recorder.go @@ -17,6 +17,7 @@ package fvtests import ( "context" "fmt" + "maps" "reflect" "sync" "time" @@ -76,9 +77,7 @@ func (r *StateRecorder) KVs() map[string]api.Update { defer r.L.Unlock() kvsCpy := map[string]api.Update{} - for k, v := range r.kvs { - kvsCpy[k] = v - } + maps.Copy(kvsCpy, r.kvs) return kvsCpy } diff --git a/typha/pkg/calc/async_decoupler.go b/typha/pkg/calc/async_decoupler.go index 7e5aedadb60..d560c3d54a1 100644 --- a/typha/pkg/calc/async_decoupler.go +++ b/typha/pkg/calc/async_decoupler.go @@ -24,13 +24,13 @@ import ( func NewSyncerCallbacksDecoupler() *SyncerCallbacksDecoupler { return &SyncerCallbacksDecoupler{ - c: make(chan interface{}), + c: make(chan any), Done: make(chan struct{}), } } type SyncerCallbacksDecoupler struct { - c chan interface{} + c chan any Done chan struct{} } diff --git a/typha/pkg/calc/calc_suite_test.go b/typha/pkg/calc/calc_suite_test.go index 663fff1b98e..d02870ce4e2 100644 --- a/typha/pkg/calc/calc_suite_test.go +++ b/typha/pkg/calc/calc_suite_test.go @@ -17,9 +17,8 @@ package calc_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestCalc(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/calc_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Calc Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/calc_suite.xml" + ginkgo.RunSpecs(t, "Calc Suite", suiteConfig, reporterConfig) } diff --git a/typha/pkg/calc/calc_test.go b/typha/pkg/calc/calc_test.go index 26974ff4315..0d158be59b3 100644 --- a/typha/pkg/calc/calc_test.go +++ b/typha/pkg/calc/calc_test.go @@ -15,7 +15,7 @@ package calc_test import ( - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -27,7 +27,7 @@ import ( type testSink struct { countUpdates int - values []interface{} + values []any } func (s *testSink) OnStatusUpdated(status api.SyncStatus) {} diff --git a/typha/pkg/calc/node_counter.go b/typha/pkg/calc/node_counter.go index 1c4a2a31d3e..29c5c0d64b6 100644 --- a/typha/pkg/calc/node_counter.go +++ b/typha/pkg/calc/node_counter.go @@ -18,7 +18,7 @@ import ( "fmt" "sync" - v3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" ) @@ -50,7 +50,7 @@ func (c *NodeCounter) OnUpdates(updates []api.Update) { for _, update := range updates { switch k := update.Key.(type) { case model.ResourceKey: - if k.Kind == v3.KindNode { + if k.Kind == internalapi.KindNode { name := k.Name switch update.UpdateType { case api.UpdateTypeKVNew: diff --git a/typha/pkg/calc/validation_filter.go b/typha/pkg/calc/validation_filter.go index 6b8c1ff01b1..d15d781261c 100644 --- a/typha/pkg/calc/validation_filter.go +++ b/typha/pkg/calc/validation_filter.go @@ -58,7 +58,7 @@ func (v *ValidationFilter) OnUpdates(updates []api.Update) { } if update.Value != nil { val := reflect.ValueOf(update.Value) - if val.Kind() == reflect.Ptr { + if val.Kind() == reflect.Pointer { elem := val.Elem() if elem.Kind() == reflect.Struct { if err := validatorFunc(elem.Interface()); err != nil { diff --git a/typha/pkg/config/config_params.go b/typha/pkg/config/config_params.go index 771d2a89fd7..645094899a2 100644 --- a/typha/pkg/config/config_params.go +++ b/typha/pkg/config/config_params.go @@ -112,6 +112,11 @@ type Config struct { PrometheusGoMetricsEnabled bool `config:"bool;true"` PrometheusProcessMetricsEnabled bool `config:"bool;true"` + PrometheusMetricsCAFile string `config:"string;"` + PrometheusMetricsCertFile string `config:"string;"` + PrometheusMetricsKeyFile string `config:"string;"` + PrometheusMetricsClientAuth string `config:"oneof(RequireAndVerifyClientCert,RequireAnyClientCert,VerifyClientCertIfGiven,NoClientCert);RequireAndVerifyClientCert"` + SnapshotCacheMaxBatchSize int `config:"int(1,);100"` ServerMaxMessageSize int `config:"int(1,);100"` @@ -221,7 +226,7 @@ func (config *Config) resolve() (changed bool, err error) { log.Infof("Parsing value for %v: %v (from %v)", name, rawValue, source) - var value interface{} + var value any if strings.ToLower(rawValue) == "none" { // Special case: we allow a value of "none" to force the value to // the zero value for a field. The zero value often differs from @@ -344,7 +349,7 @@ var knownParams map[string]param func loadParams() { knownParams = make(map[string]param) config := Config{} - kind := reflect.TypeOf(config) + kind := reflect.TypeFor[Config]() metaRegexp := regexp.MustCompile(`^([^;(]+)(?:\(([^)]*)\))?;` + `([^;]*)(?:;` + `([^;]*))?$`) @@ -485,6 +490,6 @@ func New() *Config { type param interface { GetMetadata() *Metadata - Parse(raw string) (result interface{}, err error) + Parse(raw string) (result any, err error) setDefault(*Config) } diff --git a/typha/pkg/config/config_params_test.go b/typha/pkg/config/config_params_test.go index 86cf254f61d..54f4bc27321 100644 --- a/typha/pkg/config/config_params_test.go +++ b/typha/pkg/config/config_params_test.go @@ -17,7 +17,7 @@ package config_test import ( "reflect" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" @@ -25,7 +25,7 @@ import ( ) var _ = DescribeTable("Config parsing", - func(key, value string, expected interface{}, errorExpected ...bool) { + func(key, value string, expected any, errorExpected ...bool) { cfg := config.New() _, err := cfg.UpdateFrom(map[string]string{key: value}, config.EnvironmentVariable) diff --git a/typha/pkg/config/config_suite_test.go b/typha/pkg/config/config_suite_test.go index d06c2b6ce75..6b1817ee031 100644 --- a/typha/pkg/config/config_suite_test.go +++ b/typha/pkg/config/config_suite_test.go @@ -17,9 +17,8 @@ package config_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestConfig(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/config_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Config Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/config_suite.xml" + ginkgo.RunSpecs(t, "Config Suite", suiteConfig, reporterConfig) } diff --git a/typha/pkg/config/env_var_loader_test.go b/typha/pkg/config/env_var_loader_test.go index 50ada06884b..3f2a7505ff8 100644 --- a/typha/pkg/config/env_var_loader_test.go +++ b/typha/pkg/config/env_var_loader_test.go @@ -15,7 +15,7 @@ package config_test import ( - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/typha/pkg/config" diff --git a/typha/pkg/config/file_loader_test.go b/typha/pkg/config/file_loader_test.go index 552c0e1a12c..9ee2e684497 100644 --- a/typha/pkg/config/file_loader_test.go +++ b/typha/pkg/config/file_loader_test.go @@ -18,7 +18,7 @@ import ( "path" "runtime" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/typha/pkg/config" diff --git a/typha/pkg/config/param_types.go b/typha/pkg/config/param_types.go index 5dfc7a0db35..c5a7640615d 100644 --- a/typha/pkg/config/param_types.go +++ b/typha/pkg/config/param_types.go @@ -36,8 +36,8 @@ const ( type Metadata struct { Name string - Default interface{} - ZeroValue interface{} + Default any + ZeroValue any NonZero bool DieOnParseFailure bool Local bool @@ -63,7 +63,7 @@ type BoolParam struct { Metadata } -func (p *BoolParam) Parse(raw string) (interface{}, error) { +func (p *BoolParam) Parse(raw string) (any, error) { switch strings.ToLower(raw) { case "true", "1", "yes", "y", "t": return true, nil @@ -79,7 +79,7 @@ type IntParam struct { Max int } -func (p *IntParam) Parse(raw string) (interface{}, error) { +func (p *IntParam) Parse(raw string) (any, error) { value, err := strconv.ParseInt(raw, 0, 32) if err != nil { err = p.parseFailed(raw, "invalid int") @@ -100,7 +100,7 @@ type Int32Param struct { Metadata } -func (p *Int32Param) Parse(raw string) (interface{}, error) { +func (p *Int32Param) Parse(raw string) (any, error) { value, err := strconv.ParseInt(raw, 0, 32) if err != nil { err = p.parseFailed(raw, "invalid 32-bit int") @@ -114,7 +114,7 @@ type FloatParam struct { Metadata } -func (p *FloatParam) Parse(raw string) (result interface{}, err error) { +func (p *FloatParam) Parse(raw string) (result any, err error) { result, err = strconv.ParseFloat(raw, 64) if err != nil { err = p.parseFailed(raw, "invalid float") @@ -127,7 +127,7 @@ type SecondsParam struct { Metadata } -func (p *SecondsParam) Parse(raw string) (result interface{}, err error) { +func (p *SecondsParam) Parse(raw string) (result any, err error) { seconds, err := strconv.ParseFloat(raw, 64) if err != nil { err = p.parseFailed(raw, "invalid float") @@ -143,7 +143,7 @@ type RegexpParam struct { Msg string } -func (p *RegexpParam) Parse(raw string) (result interface{}, err error) { +func (p *RegexpParam) Parse(raw string) (result any, err error) { if !p.Regexp.MatchString(raw) { err = p.parseFailed(raw, p.Msg) } else { @@ -158,7 +158,7 @@ type FileParam struct { Executable bool } -func (p *FileParam) Parse(raw string) (interface{}, error) { +func (p *FileParam) Parse(raw string) (any, error) { if p.Executable { // Special case: for executable files, we search our directory // and the system path. @@ -211,7 +211,7 @@ type Ipv4Param struct { Metadata } -func (p *Ipv4Param) Parse(raw string) (result interface{}, err error) { +func (p *Ipv4Param) Parse(raw string) (result any, err error) { res := net.ParseIP(raw) if res == nil { err = p.parseFailed(raw, "invalid IP") @@ -224,7 +224,7 @@ type PortParam struct { Metadata } -func (p *PortParam) Parse(raw string) (interface{}, error) { +func (p *PortParam) Parse(raw string) (any, error) { port, err := strconv.Atoi(strings.Trim(raw, " ")) if err != nil { err = p.parseFailed(raw, "ports should be integers") @@ -241,9 +241,9 @@ type PortListParam struct { Metadata } -func (p *PortListParam) Parse(raw string) (interface{}, error) { +func (p *PortListParam) Parse(raw string) (any, error) { var result []ProtoPort - for _, portStr := range strings.Split(raw, ",") { + for portStr := range strings.SplitSeq(raw, ",") { portStr = strings.Trim(portStr, " ") if portStr == "" { continue @@ -284,7 +284,7 @@ type EndpointListParam struct { Metadata } -func (p *EndpointListParam) Parse(raw string) (result interface{}, err error) { +func (p *EndpointListParam) Parse(raw string) (result any, err error) { value := strings.Split(raw, ",") scheme := "" resultSlice := []string{} @@ -328,7 +328,7 @@ type MarkBitmaskParam struct { Metadata } -func (p *MarkBitmaskParam) Parse(raw string) (interface{}, error) { +func (p *MarkBitmaskParam) Parse(raw string) (any, error) { value, err := strconv.ParseUint(raw, 0, 32) if err != nil { log.Warningf("Failed to parse %#v as an int: %v", raw, err) @@ -337,7 +337,7 @@ func (p *MarkBitmaskParam) Parse(raw string) (interface{}, error) { } result := uint32(value) bitCount := uint32(0) - for i := uint(0); i < 32; i++ { + for i := range uint(32) { bit := (result >> i) & 1 bitCount += bit } @@ -354,7 +354,7 @@ type OneofListParam struct { lowerCaseOptionsToCanonical map[string]string } -func (p *OneofListParam) Parse(raw string) (result interface{}, err error) { +func (p *OneofListParam) Parse(raw string) (result any, err error) { result, ok := p.lowerCaseOptionsToCanonical[strings.ToLower(raw)] if !ok { err = p.parseFailed(raw, "unknown option") diff --git a/typha/pkg/config/param_types_test.go b/typha/pkg/config/param_types_test.go index b12c83bd983..f1a42e897c7 100644 --- a/typha/pkg/config/param_types_test.go +++ b/typha/pkg/config/param_types_test.go @@ -15,14 +15,14 @@ package config_test import ( - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/typha/pkg/config" ) var _ = DescribeTable("Endpoint list parameter parsing", - func(raw string, expected interface{}) { + func(raw string, expected any) { p := config.EndpointListParam{config.Metadata{ Name: "Endpoints", }} diff --git a/typha/pkg/daemon/daemon.go b/typha/pkg/daemon/daemon.go index effebe995f9..29f7682c110 100644 --- a/typha/pkg/daemon/daemon.go +++ b/typha/pkg/daemon/daemon.go @@ -40,8 +40,6 @@ import ( "github.com/projectcalico/calico/libcalico-go/lib/debugserver" "github.com/projectcalico/calico/libcalico-go/lib/health" "github.com/projectcalico/calico/libcalico-go/lib/metricsserver" - "github.com/projectcalico/calico/libcalico-go/lib/upgrade/migrator" - "github.com/projectcalico/calico/libcalico-go/lib/upgrade/migrator/clients" "github.com/projectcalico/calico/pkg/buildinfo" "github.com/projectcalico/calico/typha/pkg/calc" "github.com/projectcalico/calico/typha/pkg/config" @@ -247,56 +245,6 @@ configRetry: t.BuildInfoLogCxt.WithField("config", configParams).Info( "Successfully loaded configuration.") - if datastoreConfig.Spec.DatastoreType == apiconfig.Kubernetes { - // Special case: for KDD v1 datamodel to v3 datamodel upgrade, we need to ensure that the datastore migration - // has completed before we start serving requests. Otherwise, we might serve partially-migrated data to - // Felix. - - // Get a v1 client, so we can check if there's any data there to migrate. - log.Info("Using Kubernetes API datastore, checking if we need to migrate v1 -> v3") - var civ1 clients.V1ClientInterface - var err error - for { - if err := ctx.Err(); err != nil { - log.WithError(err).Warn("Context canceled.") - return err - } - civ1, err = clients.LoadKDDClientV1FromAPIConfigV3(&datastoreConfig) - if err != nil { - log.WithError(err).Error("Failed to connect to Kubernetes datastore (Calico v1 API)") - time.Sleep(1 * time.Second) - continue - } - break - } - - // Use the migration helper to determine if need to perform a migration, and if so - // perform the migration. - mh := migrator.New(t.DatastoreClient, civ1, nil) - for { - if err := ctx.Err(); err != nil { - log.WithError(err).Warn("Context canceled.") - return err - } - if migrate, err := mh.ShouldMigrate(); err != nil { - log.WithError(err).Error("Failed to determine migration requirements") - time.Sleep(1 * time.Second) - continue - } else if migrate { - log.Info("Need to migrate Kubernetes v1 configuration to v3") - if _, err := mh.Migrate(); err != nil { - log.WithError(err).Error("Failed to migrate Kubernetes v1 configuration to v3") - time.Sleep(1 * time.Second) - continue - } - log.Info("Successfully migrated Kubernetes v1 configuration to v3") - break - } - log.Info("Migration not required.") - break - } - } - // Ensure that, as soon as we are able to connect to the datastore at all, it is initialized. // Note: we block further start-up while we do this, which means, if we're stuck here for long enough, // the liveness healthcheck will time out and start to fail. That's fairly reasonable, being stuck here @@ -427,12 +375,32 @@ func (t *TyphaDaemon) Start(cxt context.Context) { debugserver.StartDebugPprofServer(t.ConfigParams.DebugHost, t.ConfigParams.DebugPort) } if t.ConfigParams.PrometheusMetricsEnabled { - log.Info("Prometheus metrics enabled. Starting server.") + log.Info("Prometheus metrics enabled.") t.configurePrometheusMetrics() - go metricsserver.ServePrometheusMetricsForever( - t.ConfigParams.PrometheusMetricsHost, - t.ConfigParams.PrometheusMetricsPort, - ) + if t.ConfigParams.PrometheusMetricsKeyFile != "" || t.ConfigParams.PrometheusMetricsCertFile != "" { + log.Info("Trying to start metrics https server.") + go func() { + err := metricsserver.ServePrometheusMetricsHTTPS( + prometheus.DefaultGatherer, + t.ConfigParams.PrometheusMetricsHost, + t.ConfigParams.PrometheusMetricsPort, + t.ConfigParams.PrometheusMetricsCertFile, + t.ConfigParams.PrometheusMetricsKeyFile, + t.ConfigParams.PrometheusMetricsClientAuth, + t.ConfigParams.PrometheusMetricsCAFile, + ) + if err != nil { + log.Info("Error starting metrics https server.", err) + } + }() + } else { + log.Info("Starting metrics http server.") + go metricsserver.ServePrometheusMetricsHTTP( + prometheus.DefaultGatherer, + t.ConfigParams.PrometheusMetricsHost, + t.ConfigParams.PrometheusMetricsPort, + ) + } } if t.ConfigParams.HealthEnabled { diff --git a/typha/pkg/daemon/daemon_suite_test.go b/typha/pkg/daemon/daemon_suite_test.go index eb82fd5f4ed..5236456d966 100644 --- a/typha/pkg/daemon/daemon_suite_test.go +++ b/typha/pkg/daemon/daemon_suite_test.go @@ -17,9 +17,8 @@ package daemon_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestDaemon(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/daemon_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Daemon Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/daemon_suite.xml" + ginkgo.RunSpecs(t, "Daemon Suite", suiteConfig, reporterConfig) } diff --git a/typha/pkg/daemon/daemon_test.go b/typha/pkg/daemon/daemon_test.go index 818d6939e8a..80046ee8ccb 100644 --- a/typha/pkg/daemon/daemon_test.go +++ b/typha/pkg/daemon/daemon_test.go @@ -23,7 +23,7 @@ import ( "sync" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/apiconfig" @@ -346,6 +346,11 @@ func (b *mockDatastore) HostEndpoints() clientv3.HostEndpointInterface { panic("not implemented") } +// LiveMigrations returns an interface for managing live migration resources. +func (b *mockDatastore) LiveMigrations() clientv3.LiveMigrationInterface { + panic("not implemented") +} + // WorkloadEndpoints returns an interface for managing workload endpoint resources. func (b *mockDatastore) WorkloadEndpoints() clientv3.WorkloadEndpointInterface { panic("not implemented") @@ -397,7 +402,7 @@ func (b *mockDatastore) CalicoNodeStatus() clientv3.CalicoNodeStatusInterface { } // IPAMConfig returns an interface for managing the IPAMConfig resources. -func (b *mockDatastore) IPAMConfig() clientv3.IPAMConfigInterface { +func (c *mockDatastore) IPAMConfiguration() clientv3.IPAMConfigurationInterface { panic("not implemented") } @@ -422,13 +427,10 @@ func (b *mockDatastore) getNumInitCalls() int { var _ RealClientV3 = (*mockDatastore)(nil) -type dummySyncer struct { -} +type dummySyncer struct{} func (*dummySyncer) Start() { - } func (*dummySyncer) Stop() { - } diff --git a/typha/pkg/discovery/discovery_suite_test.go b/typha/pkg/discovery/discovery_suite_test.go index dc6e1555854..27445899a44 100644 --- a/typha/pkg/discovery/discovery_suite_test.go +++ b/typha/pkg/discovery/discovery_suite_test.go @@ -17,9 +17,8 @@ package discovery import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestDiscovery(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/discovery_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Discovery Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/discovery_suite.xml" + ginkgo.RunSpecs(t, "Discovery Suite", suiteConfig, reporterConfig) } diff --git a/typha/pkg/discovery/discovery_test.go b/typha/pkg/discovery/discovery_test.go index 33be1eaa4d6..26eeae0a9b9 100644 --- a/typha/pkg/discovery/discovery_test.go +++ b/typha/pkg/discovery/discovery_test.go @@ -20,7 +20,7 @@ import ( "reflect" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" discoveryv1 "k8s.io/api/discovery/v1" @@ -274,7 +274,7 @@ var _ = Describe("Typha address discovery", func() { // Check that multiple calls to discover the addresses shuffles the order. var shuffledLocal bool var shuffledRemote bool - for i := 0; i < 10; i++ { + for range 10 { newTyphaAddr, err := DiscoverTyphaAddrs( WithKubeService("kube-system", "calico-typha-service"), WithKubeClient(k8sClient), diff --git a/typha/pkg/jitter/jitter_suite_test.go b/typha/pkg/jitter/jitter_suite_test.go index 265dc3f758b..c945c34c5c1 100644 --- a/typha/pkg/jitter/jitter_suite_test.go +++ b/typha/pkg/jitter/jitter_suite_test.go @@ -17,9 +17,8 @@ package jitter import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestJitter(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/jitter_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Jitter Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/jitter_suite.xml" + ginkgo.RunSpecs(t, "Jitter Suite", suiteConfig, reporterConfig) } diff --git a/typha/pkg/jitter/jittered_ticker_test.go b/typha/pkg/jitter/jittered_ticker_test.go index df9304bc15b..b73acd2e4a4 100644 --- a/typha/pkg/jitter/jittered_ticker_test.go +++ b/typha/pkg/jitter/jittered_ticker_test.go @@ -18,7 +18,7 @@ import ( "fmt" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/sirupsen/logrus" ) @@ -39,12 +39,12 @@ var _ = Describe("Real 20ms + 10ms Ticker", func() { now := time.Now() duration := now.Sub(startTime) Expect(duration).To(BeNumerically(">=", 20*time.Millisecond)) - }, 1) + }) It("should produce longer and shorter ticks", func() { lastTime := startTime foundLT5 := false foundGT5 := false - for i := 0; i < 40; i++ { + for range 40 { <-ticker.C now := time.Now() duration := time.Since(lastTime) @@ -61,7 +61,7 @@ var _ = Describe("Real 20ms + 10ms Ticker", func() { } Expect(foundLT5).To(BeTrue()) Expect(foundGT5).To(BeTrue()) - }, 1) + }) }) var _ = Describe("Delay calculation", func() { @@ -70,7 +70,7 @@ var _ = Describe("Delay calculation", func() { ticker = NewTicker(20*time.Millisecond, 10*time.Millisecond) ticker.Stop() }) - for i := 0; i < 10; i++ { + for i := range 10 { It(fmt.Sprintf("should tick before max delay, trial: %v", i), func() { duration := ticker.calculateDelay() Expect(duration).To(BeNumerically("<=", 30*time.Millisecond)) @@ -84,7 +84,7 @@ var _ = Describe("Delay calculation", func() { It("should produce longer and shorter ticks", func() { foundLT5 := false foundGT5 := false - for i := 0; i < 40; i++ { + for range 40 { duration := ticker.calculateDelay() logrus.WithField("duration", duration).Debug("Tick") if duration < 25*time.Millisecond { @@ -98,7 +98,7 @@ var _ = Describe("Delay calculation", func() { } Expect(foundLT5).To(BeTrue()) Expect(foundGT5).To(BeTrue()) - }, 1) + }) }) var _ = Describe("Ticker constructor", func() { diff --git a/typha/pkg/k8s/k8s_suite_test.go b/typha/pkg/k8s/k8s_suite_test.go index 6f42cef6997..f70225c7e11 100644 --- a/typha/pkg/k8s/k8s_suite_test.go +++ b/typha/pkg/k8s/k8s_suite_test.go @@ -17,9 +17,8 @@ package k8s_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestK8s(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/k8s_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "K8s Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/k8s_suite.xml" + ginkgo.RunSpecs(t, "K8s Suite", suiteConfig, reporterConfig) } diff --git a/typha/pkg/k8s/rebalance_test.go b/typha/pkg/k8s/rebalance_test.go index 12ae1f50fe5..88e18717117 100644 --- a/typha/pkg/k8s/rebalance_test.go +++ b/typha/pkg/k8s/rebalance_test.go @@ -20,8 +20,7 @@ import ( "sync" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/typha/pkg/config" diff --git a/typha/pkg/logutils/logutils.go b/typha/pkg/logutils/logutils.go index 9cfef524193..469f717dbfd 100644 --- a/typha/pkg/logutils/logutils.go +++ b/typha/pkg/logutils/logutils.go @@ -89,10 +89,7 @@ func ConfigureLogging(configParams *config.Config) { logLevelSyslog := logutils.SafeParseLogLevel(configParams.LogSeveritySys) // Work out the most verbose level that is being logged. - mostVerboseLevel := logLevelScreen - if logLevelFile > mostVerboseLevel { - mostVerboseLevel = logLevelFile - } + mostVerboseLevel := max(logLevelFile, logLevelScreen) if logLevelSyslog > mostVerboseLevel { mostVerboseLevel = logLevelScreen } diff --git a/typha/pkg/snapcache/cache.go b/typha/pkg/snapcache/cache.go index 1c7359c98e3..c1f4e772cba 100644 --- a/typha/pkg/snapcache/cache.go +++ b/typha/pkg/snapcache/cache.go @@ -121,7 +121,7 @@ func init() { type Cache struct { config Config - inputC chan interface{} + inputC chan any pendingStatus api.SyncStatus pendingUpdates []api.Update @@ -206,7 +206,7 @@ func New(config Config) *Cache { c := &Cache{ config: config, - inputC: make(chan interface{}, config.MaxBatchSize*2), + inputC: make(chan any, config.MaxBatchSize*2), breadcrumbCond: cond, kvs: kvs, wakeUpTicker: jitter.NewTicker(config.WakeUpInterval, config.WakeUpInterval/10), @@ -296,7 +296,7 @@ func (c *Cache) loop(ctx context.Context, done chan struct{}) { func (c *Cache) fillBatchFromInputQueue(ctx context.Context) error { somethingToSend := false batchSize := 0 - storePendingUpdate := func(obj interface{}) { + storePendingUpdate := func(obj any) { somethingToSend = true switch obj := obj.(type) { case api.SyncStatus: diff --git a/typha/pkg/snapcache/cache_test.go b/typha/pkg/snapcache/cache_test.go index 63fb6d43370..12d154734cb 100644 --- a/typha/pkg/snapcache/cache_test.go +++ b/typha/pkg/snapcache/cache_test.go @@ -21,12 +21,12 @@ import ( "sync" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - v3 "github.com/projectcalico/calico/libcalico-go/lib/apis/v3" + "github.com/projectcalico/calico/libcalico-go/lib/apis/internalapi" "github.com/projectcalico/calico/libcalico-go/lib/backend/api" "github.com/projectcalico/calico/libcalico-go/lib/backend/model" "github.com/projectcalico/calico/libcalico-go/lib/health" @@ -47,7 +47,6 @@ type healthRecorder struct { } func (r *healthRecorder) RegisterReporter(name string, reports *health.HealthReport, timeout time.Duration) { - } func (r *healthRecorder) Report(name string, report *health.HealthReport) { @@ -123,13 +122,13 @@ var _ = Describe("Snapshot cache FV tests", func() { BeforeEach(func() { kvNodeRev10 = model.KVPair{ - Key: model.ResourceKey{Name: "node1", Kind: v3.KindNode}, - Value: &v3.Node{ + Key: model.ResourceKey{Name: "node1", Kind: internalapi.KindNode}, + Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ ResourceVersion: "10", Name: "foo", }, - Spec: v3.NodeSpec{ + Spec: internalapi.NodeSpec{ IPv4VXLANTunnelAddr: "10.0.0.1", }, }, @@ -162,13 +161,13 @@ var _ = Describe("Snapshot cache FV tests", func() { // Then send in another update with same value, and another value to make sure we generate another // crumb. kvNodeRev11 := model.KVPair{ - Key: model.ResourceKey{Name: "node1", Kind: v3.KindNode}, - Value: &v3.Node{ + Key: model.ResourceKey{Name: "node1", Kind: internalapi.KindNode}, + Value: &internalapi.Node{ ObjectMeta: metav1.ObjectMeta{ ResourceVersion: "11", Name: "foo", }, - Spec: v3.NodeSpec{ + Spec: internalapi.NodeSpec{ IPv4VXLANTunnelAddr: "10.0.0.1", }, }, @@ -348,8 +347,8 @@ var _ = Describe("Snapshot cache FV tests", func() { generateUpdates := func(num int) []api.Update { var updates []api.Update - var seenKeys = set.New[string]() - for i := 0; i < num; i++ { + seenKeys := set.New[string]() + for i := range num { configIdx := i % 100 value := fmt.Sprintf("config%v", i%55) name := fmt.Sprintf("config%v", configIdx) @@ -384,10 +383,7 @@ var _ = Describe("Snapshot cache FV tests", func() { expectedEndResult[upd.Key] = upd } for i := 0; i < len(updates); i += blockSize { - end := i + blockSize - if end > len(updates) { - end = len(updates) - } + end := min(i+blockSize, len(updates)) cache.OnUpdates(updates[i:end]) if i == 0 { // Cover the "skip empty updates" branch. diff --git a/typha/pkg/snapcache/snapcache_suite_test.go b/typha/pkg/snapcache/snapcache_suite_test.go index 95c6d28f9fd..02628a2f55c 100644 --- a/typha/pkg/snapcache/snapcache_suite_test.go +++ b/typha/pkg/snapcache/snapcache_suite_test.go @@ -17,9 +17,8 @@ package snapcache_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestSnapcache(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/snapcache_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Snapcache Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/snapcache_suite.xml" + ginkgo.RunSpecs(t, "Snapcache Suite", suiteConfig, reporterConfig) } diff --git a/typha/pkg/syncclient/sync_client.go b/typha/pkg/syncclient/sync_client.go index 3290b1daa4b..405f933aae9 100644 --- a/typha/pkg/syncclient/sync_client.go +++ b/typha/pkg/syncclient/sync_client.go @@ -75,6 +75,7 @@ type Options struct { // DebugLogReads tells the client to wrap each connection with a Reader that // logs every read. Intended only for use in tests! DebugLogReads bool + // DebugDiscardKVUpdates discards all KV updates from typha without decoding them. // Useful for load testing Typha without having to run a "full" client. DebugDiscardKVUpdates bool @@ -268,10 +269,8 @@ func (s *SyncerClient) startOneConnection(cxt context.Context, connFinished *syn connFinished.Add(1) go s.loop(connCtx, cancelFn, connFinished) - connFinished.Add(1) - go func() { + connFinished.Go(func() { // Broadcast that we're finished. - defer connFinished.Done() // Wait for the context to finish, either due to external cancel or our own loop // exiting. @@ -283,17 +282,15 @@ func (s *SyncerClient) startOneConnection(cxt context.Context, connFinished *syn if err != nil { log.WithError(err).Warn("Ignoring error from Close during shut-down of client.") } - }() + }) return nil } func (s *SyncerClient) calculateConnectionAttemptLimit(numDiscoveredTyphas int) int { - expectedNumTyphas := numDiscoveredTyphas - if expectedNumTyphas < 3 { + expectedNumTyphas := max(numDiscoveredTyphas, // Most clusters have at least 3 Typha instances so, if we discovered fewer instances, // assume that there may be more starting up. - expectedNumTyphas = 3 - } + 3) // During upgrade, we expect all Typha instances to be replaced one by one so, for a safe // upper bound on the number of potential connection attempts, assume that we try to connect // to double the number of instances that we detected. @@ -463,6 +460,7 @@ func (s *SyncerClient) loop(cxt context.Context, cancelFn context.CancelFunc, co Info: s.myInfo, SyncerType: ourSyncerType, SupportsDecoderRestart: !s.options.DisableDecoderRestart, + SupportsModernPolicyKeys: true, SupportedCompressionAlgorithms: compAlgs, ClientConnID: s.connID, }, @@ -591,7 +589,7 @@ func (s *SyncerClient) restartDecoder(cxt context.Context, logCxt *log.Entry, ms // sendMessageToServer sends a single value-type MsgXYZ object to the server. It updates the connection's // write deadline to ensure we don't block forever. Logs errors via logConnectionFailure. -func (s *SyncerClient) sendMessageToServer(cxt context.Context, logCxt *log.Entry, op string, message interface{}) error { +func (s *SyncerClient) sendMessageToServer(cxt context.Context, logCxt *log.Entry, op string, message any) error { err := s.connection.SetWriteDeadline(time.Now().Add(s.options.writeTimeout())) if err != nil { s.logConnectionFailure(cxt, logCxt, err, "set timeout before "+op) @@ -609,7 +607,7 @@ func (s *SyncerClient) sendMessageToServer(cxt context.Context, logCxt *log.Entr // readMessageFromServer reads a single value-type MsgXYZ object from the server. It updates the connection's // read deadline to ensure we don't block forever. Logs errors via logConnectionFailure. -func (s *SyncerClient) readMessageFromServer(cxt context.Context, logCxt *log.Entry) (interface{}, error) { +func (s *SyncerClient) readMessageFromServer(cxt context.Context, logCxt *log.Entry) (any, error) { var envelope syncproto.Envelope // Update the read deadline before we try to read, otherwise we could block for a very long time if the // TCP connection was severed without being cleanly shut down. Typha sends regular pings so we should receive diff --git a/typha/pkg/syncclientutils/config.go b/typha/pkg/syncclientutils/config.go index 81e6088a984..5c895235726 100644 --- a/typha/pkg/syncclientutils/config.go +++ b/typha/pkg/syncclientutils/config.go @@ -52,7 +52,7 @@ type TyphaConfig struct { // TYPHA, e.g. CONFD_TYPHAADDR func ReadTyphaConfig(supportedPrefixes []string) TyphaConfig { typhaConfig := &TyphaConfig{} - kind := reflect.TypeOf(*typhaConfig) + kind := reflect.TypeFor[TyphaConfig]() for ii := 0; ii < kind.NumField(); ii++ { field := kind.Field(ii) nameUpper := strings.ToUpper(field.Name) diff --git a/typha/pkg/syncclientutils/config_test.go b/typha/pkg/syncclientutils/config_test.go index f86ffd921ce..9bd68b5be11 100644 --- a/typha/pkg/syncclientutils/config_test.go +++ b/typha/pkg/syncclientutils/config_test.go @@ -17,7 +17,7 @@ package syncclientutils_test import ( "os" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/projectcalico/calico/typha/pkg/syncclientutils" diff --git a/typha/pkg/syncclientutils/startsyncerclient_suite_test.go b/typha/pkg/syncclientutils/startsyncerclient_suite_test.go index 5c756739ba6..ab689801beb 100644 --- a/typha/pkg/syncclientutils/startsyncerclient_suite_test.go +++ b/typha/pkg/syncclientutils/startsyncerclient_suite_test.go @@ -17,9 +17,8 @@ package syncclientutils_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestSyncClientUtils(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/startsyncerclient_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "TestSyncClientUtils Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/startsyncerclient_suite.xml" + ginkgo.RunSpecs(t, "TestSyncClientUtils Suite", suiteConfig, reporterConfig) } diff --git a/typha/pkg/syncproto/sync_proto.go b/typha/pkg/syncproto/sync_proto.go index ecbcdb2edf2..40f69796e8f 100644 --- a/typha/pkg/syncproto/sync_proto.go +++ b/typha/pkg/syncproto/sync_proto.go @@ -208,7 +208,7 @@ Typha->Felix: KVs * n const DefaultPort = 5473 type Envelope struct { - Message interface{} + Message any } // logrus only looks for a String() method on the top-level object, make sure we call through to the wrapped object. @@ -229,16 +229,14 @@ const ( NumSyncerTypes = iota ) -var ( - // AllSyncerTypes contains each of the SyncerType constants. We use an array rather than a slice for a - // compile-time length check. - AllSyncerTypes = [NumSyncerTypes]SyncerType{ - SyncerTypeFelix, - SyncerTypeBGP, - SyncerTypeTunnelIPAllocation, - SyncerTypeNodeStatus, - } -) +// AllSyncerTypes contains each of the SyncerType constants. We use an array rather than a slice for a +// compile-time length check. +var AllSyncerTypes = [NumSyncerTypes]SyncerType{ + SyncerTypeFelix, + SyncerTypeBGP, + SyncerTypeTunnelIPAllocation, + SyncerTypeNodeStatus, +} type CompressionAlgorithm string @@ -260,6 +258,11 @@ type MsgClientHello struct { SupportsDecoderRestart bool SupportedCompressionAlgorithms []CompressionAlgorithm + // SupportsModernPolicyKeys tells the server whether this client supports modern PolicyKey + // syntax, i.e., using Kind/Namespace/Name instead of Tier/Name. If the client does not set this field, + // Typha will reject the connection attempt and wait for the client to be upgraded. + SupportsModernPolicyKeys bool + ClientConnID uint64 } @@ -286,18 +289,21 @@ type MsgDecoderRestart struct { // MsgACK is a general-purpose ACK message, currently used during the initial handshake to acknowledge the // switch to compressed mode. -type MsgACK struct { -} +type MsgACK struct{} + type MsgSyncStatus struct { SyncStatus api.SyncStatus } + type MsgPing struct { Timestamp time.Time } + type MsgPong struct { PingTimestamp time.Time PongTimestamp time.Time } + type MsgKVs struct { KVs []SerializedUpdate } @@ -376,7 +382,7 @@ func SerializeUpdate(u api.Update) (su SerializedUpdate, err error) { type SerializedUpdate struct { Key string Value []byte - Revision interface{} + Revision any V3ResourceVersion string TTL time.Duration UpdateType api.UpdateType @@ -395,7 +401,7 @@ func (s SerializedUpdate) ToUpdate() (api.Update, error) { "is the same version as this component.") return api.Update{}, ErrBadKey } - var parsedValue interface{} + var parsedValue any if s.Value != nil { var err error parsedValue, err = model.ParseValue(parsedKey, s.Value) @@ -410,6 +416,13 @@ func (s SerializedUpdate) ToUpdate() (api.Update, error) { } } } + + // If the key supports upgrade, do it now that we've used the key to parse the value. + if k, ok := parsedKey.(model.LegacyKey); ok { + parsedKey = k.Upgrade() + log.Debugf("Upgraded legacy key %+v to %+v", k, parsedKey) + } + revStr := "" switch r := s.Revision.(type) { case string: diff --git a/typha/pkg/syncproto/sync_proto_test.go b/typha/pkg/syncproto/sync_proto_test.go index 9578bdc97f5..9754453ce9b 100644 --- a/typha/pkg/syncproto/sync_proto_test.go +++ b/typha/pkg/syncproto/sync_proto_test.go @@ -18,9 +18,13 @@ import ( "bytes" "encoding/base64" "encoding/gob" + "encoding/json" "testing" . "github.com/onsi/gomega" + + "github.com/projectcalico/calico/libcalico-go/lib/backend/api" + "github.com/projectcalico/calico/libcalico-go/lib/backend/model" ) const cannedEnvelopeWithHello = "Iv+BAwEBCEVudmVsb3BlAf+CAAEBAQdNZXNzYWdlARAAAAD/jP+CATtnaXRodWIuY29tL3Byb2plY3R" + @@ -78,3 +82,29 @@ func TestEncodeCanned(t *testing.T) { t.Logf("%q", b2.String()) } + +// Create a SerializedUpdate with a legacy policy key and verify that we upgrade it to a modern key. +func TestLegacyKeyUpgrade(t *testing.T) { + RegisterTestingT(t) + + pol := &model.Policy{} + polBytes, err := json.Marshal(pol) + Expect(err).NotTo(HaveOccurred()) + + su := SerializedUpdate{ + Key: "/calico/v1/policy/tier/mytier/policy/ns%2fname", + Value: polBytes, + Revision: "20", + UpdateType: api.UpdateTypeKVNew, + } + + upd, err := su.ToUpdate() + Expect(err).NotTo(HaveOccurred()) + + pk, ok := upd.Key.(model.PolicyKey) + Expect(ok).To(BeTrue(), "Expected upgraded key to be of type PolicyKey") + Expect(pk.Name).To(Equal("name")) + Expect(pk.Namespace).To(Equal("ns")) + Expect(pk.Kind).To(Equal("NetworkPolicy")) + Expect(upd.Value.(*model.Policy).Tier).To(Equal("mytier")) +} diff --git a/typha/pkg/syncserver/sync_server.go b/typha/pkg/syncserver/sync_server.go index 9dcfc18bdb8..40a32d4003a 100644 --- a/typha/pkg/syncserver/sync_server.go +++ b/typha/pkg/syncserver/sync_server.go @@ -338,6 +338,8 @@ func (s *Server) serve(cxt context.Context) { l net.Listener err error ) + + laddr := net.JoinHostPort(s.config.Host, fmt.Sprint(s.config.ListenPort())) if s.config.requiringTLS() { pwd, _ := os.Getwd() logCxt.WithField("pwd", pwd).Info("Opening TLS listen socket") @@ -373,12 +375,9 @@ func (s *Server) serve(cxt context.Context) { s.config.ClientCN, s.config.ClientURISAN, ) - - laddr := fmt.Sprintf("[%v]:%v", s.config.Host, s.config.ListenPort()) l, err = tls.Listen("tcp", laddr, tlsConfig) } else { logCxt.Info("Opening listen socket") - laddr := fmt.Sprintf("[%v]:%v", s.config.Host, s.config.ListenPort()) l, err = net.Listen("tcp", laddr) } if err != nil { @@ -386,8 +385,7 @@ func (s *Server) serve(cxt context.Context) { } logCxt.Info("Opened listen socket") - s.Finished.Add(1) - go func() { + s.Finished.Go(func() { select { case <-cxt.Done(): log.Info("Context finished, closing listen socket.") @@ -398,8 +396,7 @@ func (s *Server) serve(cxt context.Context) { if err != nil { log.WithError(err).Warn("Ignoring error from socket Close during shut-down.") } - s.Finished.Done() - }() + }) chosenPort := l.Addr().(*net.TCPAddr).Port @@ -461,7 +458,7 @@ func (s *Server) serve(cxt context.Context) { encoder: gob.NewEncoder(connW), flushWriter: func() error { return nil }, - readC: make(chan interface{}), + readC: make(chan any), allMetrics: s.perSyncerConnMetrics, } @@ -688,7 +685,7 @@ type connection struct { currentWriteDeadline time.Time encoder *gob.Encoder flushWriter func() error - readC chan interface{} + readC chan any logCxt *log.Entry chosenCompression syncproto.CompressionAlgorithm @@ -876,7 +873,7 @@ func (h *connection) readFromClient(logCxt *log.Entry) { } // waitForMessage blocks, waiting for a message on the h.readC channel. It imposes a timeout. -func (h *connection) waitForMessage(logCxt *log.Entry, timeout time.Duration) (interface{}, error) { +func (h *connection) waitForMessage(logCxt *log.Entry, timeout time.Duration) (any, error) { timer := time.NewTimer(timeout) defer timer.Stop() select { @@ -939,6 +936,13 @@ func (h *connection) doHandshake() error { h.chosenCompression = "" } + if !hello.SupportsModernPolicyKeys { + // The client is too old and we cannot support it. Reject the connection and wait for the + // client to be upgraded. + h.logCxt.Info("Client does not support modern policy keys, disconnecting.") + return ErrUnsupportedClientFeature + } + // Respond to client's hello. err = h.sendMsg(syncproto.MsgServerHello{ Version: buildinfo.Version, @@ -1013,7 +1017,7 @@ func (h *connection) waitForAckAndRestartEncoder() error { } // sendMsg sends a message to the client. It may be called from multiple goroutines. -func (h *connection) sendMsg(msg interface{}) error { +func (h *connection) sendMsg(msg any) error { if h.cxt.Err() != nil { // Optimisation, don't bother to send if we're being torn down. return h.cxt.Err() diff --git a/typha/pkg/syncserver/syncserver_suite_test.go b/typha/pkg/syncserver/syncserver_suite_test.go index b3803453612..acec32ebe83 100644 --- a/typha/pkg/syncserver/syncserver_suite_test.go +++ b/typha/pkg/syncserver/syncserver_suite_test.go @@ -17,9 +17,8 @@ package syncserver_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestSyncserver(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../../report/syncserver_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "Syncserver Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/syncserver_suite.xml" + ginkgo.RunSpecs(t, "Syncserver Suite", suiteConfig, reporterConfig) } diff --git a/typha/pkg/syncserver/syncserver_test.go b/typha/pkg/syncserver/syncserver_test.go index 286cc789e1f..57621c3afe1 100644 --- a/typha/pkg/syncserver/syncserver_test.go +++ b/typha/pkg/syncserver/syncserver_test.go @@ -18,7 +18,7 @@ import ( "math" "time" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/projectcalico/calico/typha/pkg/syncserver" diff --git a/typha/pkg/tlsutils/tlsutils_suite_test.go b/typha/pkg/tlsutils/tlsutils_suite_test.go index 9dd2a9d08c0..6cbc671d771 100644 --- a/typha/pkg/tlsutils/tlsutils_suite_test.go +++ b/typha/pkg/tlsutils/tlsutils_suite_test.go @@ -17,9 +17,8 @@ package tlsutils_test import ( "testing" - . "github.com/onsi/ginkgo" - "github.com/onsi/ginkgo/reporters" - . "github.com/onsi/gomega" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" "github.com/projectcalico/calico/libcalico-go/lib/testutils" ) @@ -29,7 +28,8 @@ func init() { } func TestTLSUtils(t *testing.T) { - RegisterFailHandler(Fail) - junitReporter := reporters.NewJUnitReporter("../report/tls_utils_suite.xml") - RunSpecsWithDefaultAndCustomReporters(t, "TLS utils Suite", []Reporter{junitReporter}) + gomega.RegisterFailHandler(ginkgo.Fail) + suiteConfig, reporterConfig := ginkgo.GinkgoConfiguration() + reporterConfig.JUnitReport = "../../report/tls_utils_suite.xml" + ginkgo.RunSpecs(t, "TLS utils Suite", suiteConfig, reporterConfig) } diff --git a/typha/pkg/tlsutils/tlsutils_test.go b/typha/pkg/tlsutils/tlsutils_test.go index 009f9268771..01299af7ae0 100644 --- a/typha/pkg/tlsutils/tlsutils_test.go +++ b/typha/pkg/tlsutils/tlsutils_test.go @@ -19,7 +19,7 @@ import ( "crypto/x509" "fmt" - . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" log "github.com/sirupsen/logrus" @@ -201,7 +201,7 @@ var _ = DescribeTable("CertificateVerifier", err := verifier([][]byte{peerCertBytes}, nil) errChecker(err) }, - genTestCases()...) + genTestCases()) func makePeerCert(cfg *certConfig) []byte { log.WithField("cfg", cfg).Info("Make peer cert") diff --git a/typha/utils/run-coverage b/typha/utils/run-coverage index 0ccf8a29e25..decedf63992 100755 --- a/typha/utils/run-coverage +++ b/typha/utils/run-coverage @@ -4,7 +4,7 @@ set -e set -x echo "Removing old coverprofiles..." -find . -name "*.coverprofile" -type f -delete +find . -name "coverprofile.out" -type f -delete echo "Calculating packages to cover..." go_dirs=$(find -type f -name '*.go' | \ @@ -22,7 +22,7 @@ test ! -z "$test_pkgs" echo "Packages with tests: $test_pkgs" ginkgo -cover -covermode=count -coverpkg=${go_dirs} ${GINKGO_ARGS} -r -gocovmerge $(find . -name '*.coverprofile') > combined.coverprofile +gocovmerge $(find . -name 'coverprofile.out') > combined.coverprofile # Print the coverage. We use sed to remove the verbose prefix and trim down # the whitespace. diff --git a/webhooks/.gitignore b/webhooks/.gitignore new file mode 100644 index 00000000000..f440c832931 --- /dev/null +++ b/webhooks/.gitignore @@ -0,0 +1 @@ +coverage.profile diff --git a/webhooks/Makefile b/webhooks/Makefile new file mode 100644 index 00000000000..56a74ebe84f --- /dev/null +++ b/webhooks/Makefile @@ -0,0 +1,69 @@ +include ../metadata.mk + +PACKAGE_NAME = github.com/projectcalico/calico/webhooks +IMAGE_BUILD_MARKER = webhook_container-$(ARCH).created + +# Configure variables used by ci/cd common targets from lib.Makefile. +BUILD_IMAGES=webhooks + +############################################################################### +# include ../lib.Makefile +# Additions to EXTRA_DOCKER_ARGS need to happen before the include since +# that variable is evaluated when we declare DOCKER_RUN and siblings. +############################################################################### +include ../lib.Makefile + +# Full set of binaries to build. +BINARIES=bin/webhook-$(ARCH) + +.PHONY: image build +image: $(IMAGE_BUILD_MARKER) +build: $(BINARIES) +clean: + rm -rf bin + rm -f $(IMAGE_BUILD_MARKER) + +image-all: $(addprefix sub-image-,$(VALIDARCHES)) +sub-image-%: + $(MAKE) image ARCH=$* + +# Build webhook image. +webhooks calico/webhooks: $(IMAGE_BUILD_MARKER) +$(IMAGE_BUILD_MARKER): $(BINARIES) bin/LICENSE + $(DOCKER_BUILD) --build-arg TARGETARCH=$(ARCH) --build-arg GIT_VERSION=$(GIT_VERSION) -t webhooks:latest-$(ARCH) -f docker/Dockerfile . + $(MAKE) retag-build-images-with-registries BUILD_IMAGES=$(BUILD_IMAGES) VALIDARCHES=$(ARCH) IMAGETAG=latest + touch $@ + +bin/webhook-$(ARCH): $(shell find . -name '*.go') $(shell find ../apiserver -name '*.go') + $(call build_binary, $(PACKAGE_NAME)/cmd/, $@) + +bin/LICENSE: ../LICENSE.md + cp ../LICENSE.md $@ + +############################################################################### +# UTs +############################################################################### +ci: static-checks ut +ut: + $(DOCKER_GO_BUILD) go test ./... -coverprofile coverage.profile -race -count 1 + +gen-mocks: + $(DOCKER_RUN) $(CALICO_BUILD) sh -c 'mockery' + +############################################################################### +# Release +############################################################################### +## Deploys images to registry +cd: image-all cd-common + +release-build: .release-$(VERSION).created +.release-$(VERSION).created: + $(MAKE) clean image-all RELEASE=true + $(MAKE) retag-build-images-with-registries RELEASE=true IMAGETAG=$(VERSION) + $(MAKE) retag-build-images-with-registries RELEASE=true IMAGETAG=latest + touch $@ + +release-publish: release-prereqs .release-$(VERSION).published +.release-$(VERSION).published: + $(MAKE) push-images-to-registries push-manifests IMAGETAG=$(VERSION) RELEASE=$(RELEASE) CONFIRM=$(CONFIRM) + touch $@ diff --git a/webhooks/cmd/main.go b/webhooks/cmd/main.go new file mode 100644 index 00000000000..9f79628c537 --- /dev/null +++ b/webhooks/cmd/main.go @@ -0,0 +1,223 @@ +// Copyright 2026 Tigera, Inc. +// +// Copyright 2018 The Kubernetes Authors. +// +// 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 main + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "strings" + + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + v1 "k8s.io/api/admission/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/component-base/cli" + + "github.com/projectcalico/calico/crypto/pkg/tls" + "github.com/projectcalico/calico/libcalico-go/lib/logutils" + "github.com/projectcalico/calico/pkg/buildinfo" + "github.com/projectcalico/calico/webhooks/pkg/rbac" + "github.com/projectcalico/calico/webhooks/pkg/utils" +) + +var ( + certFile string + keyFile string + logLevel string + port int +) + +var WebhookCommand = &cobra.Command{ + Use: "webhook", + Short: "Starts an HTTP server for Calico admission webhooks.", + Long: `Starts an HTTP server for Calico admission webhooks.`, + Args: cobra.MaximumNArgs(0), + Run: serveWebhookTLS, +} + +var VersionCommand = &cobra.Command{ + Use: "version", + Short: "Prints version information about the webhook server.", + Long: `Prints version information about the webhook server.`, + Run: func(cmd *cobra.Command, args []string) { + buildinfo.PrintVersion() + }, +} + +func init() { + WebhookCommand.Flags().StringVar(&certFile, "tls-cert-file", "", "File containing the default x509 Certificate for HTTPS. (CA cert, if any, concatenated after server cert).") + WebhookCommand.Flags().StringVar(&keyFile, "tls-private-key-file", "", "File containing the default x509 private key matching --tls-cert-file.") + WebhookCommand.Flags().IntVar(&port, "port", 6443, "Secure port that the webhook listens on") + WebhookCommand.Flags().StringVar(&logLevel, "log-level", "info", "Logrus log level to output (trace, debug, info, warning, error, fatal, panic)") +} + +func main() { + // Create the root command and add the webhook command to it. + rootCmd := &cobra.Command{Use: "webhook"} + rootCmd.AddCommand(WebhookCommand) + rootCmd.AddCommand(VersionCommand) + os.Exit(cli.Run(rootCmd)) +} + +func configureLogging() { + // Set up logging. + l, err := logrus.ParseLevel(logLevel) + if err != nil { + logrus.WithError(err).Fatalf("Invalid log level: %s", logLevel) + } + logrus.SetLevel(l) + logutils.ConfigureFormatter("webhook") + logrus.SetOutput(os.Stdout) + logrus.Infof("Log level set to %s", logLevel) +} + +func serveWebhookTLS(cmd *cobra.Command, args []string) { + configureLogging() + logrus.Info("Starting Calico admission webhook server") + + // Create a clientset to interact with the Kubernetes API. + rc, err := rest.InClusterConfig() + if err != nil { + logrus.WithError(err).Fatal("Failed to create in-cluster config") + } + cs, err := kubernetes.NewForConfig(rc) + if err != nil { + logrus.WithError(err).Fatal("Failed to create clientset") + } + + // Register webhook handlers. + registerHooks(cs) + + // Create and run the server. + cfg, err := tls.NewTLSConfig() + if err != nil { + logrus.WithError(err).Fatal("Failed to create TLS config") + } + server := &http.Server{ + Addr: fmt.Sprintf(":%d", port), + TLSConfig: cfg, + } + + logrus.Infof("Listening on port %d", port) + err = server.ListenAndServeTLS(certFile, keyFile) + if err != nil { + logrus.WithError(err).Fatalf("Failed to start webhook server on port %d", port) + } +} + +func registerHooks(cs kubernetes.Interface) { + rbac.RegisterHook(cs, utils.HandleFn(handleFn)) + + // Register a readiness endpoint that can be used by Kubernetes to check the health of the webhook server. + http.HandleFunc("/readyz", readyFn()) +} + +func readyFn() func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + if _, err := w.Write([]byte("ok")); err != nil { + logrus.WithError(err).Error("Failed to write readiness response") + } + } +} + +// handleFn implements utils.HandleFn to allow registration of webhooks. +func handleFn(handler utils.AdmissionReviewHandler) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + handleRequest(w, r, handler) + } +} + +// handleRequest handles an incoming HTTP request, decodes the AdmissionReview, processes it, and writes the response. +func handleRequest(w http.ResponseWriter, r *http.Request, handler utils.AdmissionReviewHandler) { + // Decode the AdmissionReview request. + obj, gvk, err := decodeAdmissionReview(r) + if err != nil { + logrus.Error(err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // Process the AdmissionReview request. + responseObj, err := processAdmissionReview(obj, gvk, handler) + if err != nil { + logrus.Error(err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // Encode and send the AdmissionReview response. + respBytes, err := json.Marshal(responseObj) + if err != nil { + logrus.Error(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + if _, err := w.Write(respBytes); err != nil { + logrus.Error(err) + } +} + +func decodeAdmissionReview(r *http.Request) (runtime.Object, *schema.GroupVersionKind, error) { + var body []byte + if r.Body != nil { + if data, err := io.ReadAll(r.Body); err == nil { + body = data + } else { + return nil, nil, fmt.Errorf("could not read request body: %v", err) + } + } else { + return nil, nil, fmt.Errorf("empty body") + } + + // Verify the content type is accurate + if ct := r.Header.Get("Content-Type"); !strings.Contains(ct, "application/json") { + return nil, nil, fmt.Errorf("invalid Content-Type '%s', expected `application/json`", ct) + } + + // Decode the body into an AdmissionReview object, and check the + // GroupVersionKind to ensure it's something we support. + deserializer := utils.Codecs.UniversalDeserializer() + obj, gvk, err := deserializer.Decode(body, nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("request could not be decoded: %v", err) + } + return obj, gvk, nil +} + +func processAdmissionReview(obj runtime.Object, gvk *schema.GroupVersionKind, handler utils.AdmissionReviewHandler) (*v1.AdmissionReview, error) { + switch *gvk { + case v1.SchemeGroupVersion.WithKind("AdmissionReview"): + requestedAdmissionReview, ok := obj.(*v1.AdmissionReview) + if !ok { + return nil, fmt.Errorf("expected v1.AdmissionReview but got: %T", obj) + } + responseAdmissionReview := &v1.AdmissionReview{} + responseAdmissionReview.SetGroupVersionKind(*gvk) + responseAdmissionReview.Response = handler.ProcessV1Review(*requestedAdmissionReview) + responseAdmissionReview.Response.UID = requestedAdmissionReview.Request.UID + return responseAdmissionReview, nil + default: + return nil, fmt.Errorf("unsupported group version kind: %v", gvk) + } +} diff --git a/webhooks/deps.txt b/webhooks/deps.txt new file mode 100644 index 00000000000..06a32144803 --- /dev/null +++ b/webhooks/deps.txt @@ -0,0 +1,90 @@ +!!! GENERATED FILE, DO NOT EDIT !!! +This file contains the list of modules that this package depends on +in order to trigger CI on changes + +go 1.25.7 +cel.dev/expr v0.24.0 +github.com/antlr4-go/antlr/v4 v4.13.0 +github.com/beorn7/perks v1.0.1 +github.com/blang/semver/v4 v4.0.0 +github.com/cenkalti/backoff/v5 v5.0.2 +github.com/cespare/xxhash/v2 v2.3.0 +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc +github.com/emicklei/go-restful v2.15.0+incompatible +github.com/emicklei/go-restful/v3 v3.12.2 +github.com/evanphx/json-patch v5.9.11+incompatible +github.com/evanphx/json-patch/v5 v5.9.11 +github.com/felixge/httpsnoop v1.0.4 +github.com/fsnotify/fsnotify v1.9.0 +github.com/fxamacker/cbor/v2 v2.9.0 +github.com/go-logr/logr v1.4.3 +github.com/go-logr/stdr v1.2.2 +github.com/go-openapi/jsonpointer v0.21.0 +github.com/go-openapi/jsonreference v0.20.2 +github.com/go-openapi/swag v0.23.0 +github.com/gogo/protobuf v1.3.2 +github.com/google/cel-go v0.26.0 +github.com/google/gnostic-models v0.7.0 +github.com/google/uuid v1.6.0 +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 +github.com/jinzhu/copier v0.4.0 +github.com/josharian/intern v1.0.0 +github.com/json-iterator/go v1.1.12 +github.com/mailru/easyjson v0.7.7 +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 +github.com/pkg/errors v0.9.1 +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 +github.com/projectcalico/calico +github.com/prometheus/client_golang v1.23.2 +github.com/prometheus/client_model v0.6.2 +github.com/prometheus/common v0.67.2 +github.com/prometheus/procfs v0.19.2 +github.com/sirupsen/logrus v1.9.3 +github.com/spf13/cobra v1.10.1 +github.com/spf13/pflag v1.0.10 +github.com/stoewer/go-strcase v1.3.0 +github.com/x448/float16 v0.8.4 +go.opentelemetry.io/auto/sdk v1.1.0 +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 +go.opentelemetry.io/otel v1.37.0 +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 +go.opentelemetry.io/otel/metric v1.37.0 +go.opentelemetry.io/otel/sdk v1.37.0 +go.opentelemetry.io/otel/trace v1.37.0 +go.opentelemetry.io/proto/otlp v1.7.0 +go.yaml.in/yaml/v2 v2.4.3 +go.yaml.in/yaml/v3 v3.0.4 +golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 +golang.org/x/net v0.49.0 +golang.org/x/oauth2 v0.34.0 +golang.org/x/sync v0.19.0 +golang.org/x/sys v0.40.0 +golang.org/x/term v0.39.0 +golang.org/x/text v0.33.0 +golang.org/x/time v0.14.0 +google.golang.org/genproto v0.0.0-20250603155806-513f23925822 +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c +google.golang.org/grpc v1.76.0 +google.golang.org/protobuf v1.36.10 +gopkg.in/evanphx/json-patch.v4 v4.12.0 +gopkg.in/inf.v0 v0.9.1 +gopkg.in/yaml.v3 v3.0.1 +k8s.io/api v0.34.3 +k8s.io/apimachinery v0.34.3 +k8s.io/apiserver v0.34.3 +k8s.io/client-go v0.34.3 +k8s.io/component-base v0.34.3 +k8s.io/klog v0.2.0 +k8s.io/klog/v2 v2.130.1 +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 +sigs.k8s.io/controller-runtime v0.22.3 +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 +sigs.k8s.io/randfill v1.0.0 +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 +sigs.k8s.io/yaml v1.6.0 diff --git a/webhooks/docker/Dockerfile b/webhooks/docker/Dockerfile new file mode 100644 index 00000000000..5271031b856 --- /dev/null +++ b/webhooks/docker/Dockerfile @@ -0,0 +1,41 @@ +# Copyright 2015-2025 Tigera, Inc +# +# 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. + +FROM scratch + +ARG TARGETARCH +ARG GIT_VERSION +COPY ./bin/webhook-${TARGETARCH} /webhook + +COPY ./bin/LICENSE /licenses/LICENSE + +LABEL org.opencontainers.image.description="Webhook is a validating admission webhook for Calico APIs." +LABEL org.opencontainers.image.authors="maintainers@tigera.io" +LABEL org.opencontainers.image.source="https://github.com/projectcalico/calico" +LABEL org.opencontainers.image.title="Webhook" +LABEL org.opencontainers.image.vendor="Project Calico" +LABEL org.opencontainers.image.version="${GIT_VERSION}" +LABEL org.opencontainers.image.licenses="Apache-2.0" + +LABEL description="Webhook is a validating admission webhook for Calico APIs." +LABEL maintainer="maintainers@tigera.io" +LABEL name="Webhook" +LABEL release=1 +LABEL summary="Webhook is a validating admission webhook for Calico APIs." +LABEL vendor="Project Calico" +LABEL version="${GIT_VERSION}" + +USER 10001:10001 + +ENTRYPOINT ["/webhook"] diff --git a/webhooks/pkg/rbac/rbac.go b/webhooks/pkg/rbac/rbac.go new file mode 100644 index 00000000000..a11ba899584 --- /dev/null +++ b/webhooks/pkg/rbac/rbac.go @@ -0,0 +1,282 @@ +// Copyright 2026 Tigera, Inc. +// +// Copyright 2018 The Kubernetes Authors. +// +// 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 rbac + +import ( + "context" + "fmt" + "net/http" + "reflect" + "strings" + "time" + + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "github.com/sirupsen/logrus" + v1 "k8s.io/api/admission/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apiserver/pkg/authentication/user" + kauth "k8s.io/apiserver/pkg/authorization/authorizer" + "k8s.io/apiserver/pkg/authorization/cel" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/plugin/pkg/authorizer/webhook" + "k8s.io/apiserver/plugin/pkg/authorizer/webhook/metrics" + "k8s.io/client-go/kubernetes" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/projectcalico/calico/apiserver/pkg/registry/projectcalico/authorizer" + "github.com/projectcalico/calico/webhooks/pkg/utils" +) + +// RegisterHook creates a new tiered RBAC admission webhook authorizer and registers the necessary HTTP handler. +func RegisterHook(cs kubernetes.Interface, handleFn utils.HandleFn) { + logrus.WithFields(logrus.Fields{ + "path": "/rbac", + }).Info("Registering RBAC admission webhook") + + // Create a new Kubernetes authorizer. + bo := webhook.DefaultRetryBackoff() + m := &metrics.NoopAuthorizerMetrics{} + compl := cel.NewDefaultCompiler() + + authz, err := webhook.NewFromInterface(cs.AuthorizationV1(), 5*time.Second, 5*time.Second, *bo, kauth.DecisionDeny, m, compl) + if err != nil { + logrus.WithError(err).Fatal("Failed to create webhook authorizer") + } + handler := NewTieredRBACHook(authorizer.NewTierAuthorizer(authz)).Handler() + + // Register the webhook handlers and a readiness endpoint. + http.HandleFunc("/rbac", handleFn(handler)) +} + +// NewTieredRBACHook returns a new instance of the tiered RBAC admission webhook backend, which uses +// the provided TierAuthorizer to perform authorization checks. Note that this hook is implemented as an admission webhook, which +// has some limitations and benefits compared to a full authorization webhook. Namely: +// - It can be installed on a cluster without needing to modify Kubernetes API server configuration, making it easier to deploy and manage. +// - It can only be used to authorize admission requests, so it won't be able to handle authorization for non-admission requests (e.g., kubectl auth can-i). +// - It does not have access to GET / LIST / WATCH requests, and so cannot make authorization decisions on those requests. +func NewTieredRBACHook(authz authorizer.TierAuthorizer) utils.HandlerProvider { + return &tieredRBACHook{authz: authz} +} + +// tieredRBACHook is an admission webhook that uses RBAC to authorize requests based on tier. +type tieredRBACHook struct { + authz authorizer.TierAuthorizer +} + +// Handler returns an AdmissionReviewHandler that processes admission reviewes for tiered policies and checks whether the user is authorized to +// perform the operation. It is the main entry point for the webhook. +func (h *tieredRBACHook) Handler() utils.AdmissionReviewHandler { + return utils.NewDelegateToV1AdmitHandler(h.authorize) +} + +func (h *tieredRBACHook) authorize(ar v1.AdmissionReview) *v1.AdmissionResponse { + logCtx := logrus.WithFields(logrus.Fields{ + "uid": ar.Request.UID, + "kind": ar.Request.Kind, + "resource": ar.Request.Resource, + "operation": ar.Request.Operation, + "name": ar.Request.Name, + "namespace": ar.Request.Namespace, + "user": ar.Request.UserInfo.Username, + }) + logCtx.Debug("Handling admission review") + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + // Extract the raw object from the admission request. In some cases (e.g., DELETE), the object may be in + // the OldObject field instead of the Object field, so we check both. + raw := ar.Request.Object.Raw + if len(raw) == 0 { + raw = ar.Request.OldObject.Raw + } + if len(raw) == 0 { + logCtx.Warn("No object in admission request") + return &v1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Status: metav1.StatusFailure, + Message: "No object in admission request", + Reason: metav1.StatusReasonBadRequest, + }, + } + } + + // Parse the raw JSON. + obj, tier, err := h.parsePolicy(ar.Request.Kind.Kind, raw) + if err != nil { + logCtx.WithError(err).Error("Failed to parse policy metadata") + return &v1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Status: metav1.StatusFailure, + Message: fmt.Sprintf("Failed to parse policy metadata: %v", err), + Reason: metav1.StatusReasonInvalid, + }, + } + } + + // Log the tier being used for authorization. + logCtx = logCtx.WithField("tier", tier) + + // Create a context with the necessary information to pass to the RBAC authorizer. + // This includes the user info from the admission request. + ctx, err = augmentContextWithUserInfo(ctx, ar.Request, obj) + if err != nil { + logCtx.WithError(err).Error("Failed to build authorization context") + return &v1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Status: metav1.StatusFailure, + Message: fmt.Sprintf("Failed to build authorization context: %v", err), + Reason: metav1.StatusReasonInternalError, + }, + } + } + + // Run the RBAC authorizer to check if the user is authorized to perform the operation. + if err = h.authz.AuthorizeTierOperation(ctx, obj.GetName(), tier); err != nil { + logCtx.WithError(err).Warn("User is not authorized") + return &v1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Status: metav1.StatusFailure, + Message: fmt.Sprintf("Authorization failed: %v", err), + Reason: metav1.StatusReasonForbidden, + }, + } + } + + // If validation passes, return an allowed response + logCtx.Debug("User is authorized") + return &v1.AdmissionResponse{Allowed: true} +} + +// parsePolicy decodes the raw JSON of the policy object from the admission request and extracts the tier information. +func (h *tieredRBACHook) parsePolicy(kind string, body []byte) (client.Object, string, error) { + // Create an empty object of the appropriate type. + var obj client.Object + switch kind { + case v3.KindNetworkPolicy: + obj = &v3.NetworkPolicy{} + case v3.KindGlobalNetworkPolicy: + obj = &v3.GlobalNetworkPolicy{} + case v3.KindStagedNetworkPolicy: + obj = &v3.StagedNetworkPolicy{} + case v3.KindStagedGlobalNetworkPolicy: + obj = &v3.StagedGlobalNetworkPolicy{} + case v3.KindStagedKubernetesNetworkPolicy: + obj = &v3.StagedKubernetesNetworkPolicy{} + default: + return nil, "", fmt.Errorf("unsupported kind: %s", kind) + } + + // Decode the object into the appropriate type. + deserializer := utils.Codecs.UniversalDeserializer() + _, _, err := deserializer.Decode(body, nil, obj) + if err != nil { + return nil, "", fmt.Errorf("failed to decode object: %v", err) + } + + // Use reflection to access the Spec.Tier field. + if tier, ok := getTier(obj); ok { + return obj, tier, nil + } + return nil, "", fmt.Errorf("object does not have a Spec.Tier field") +} + +// getTier uses reflection to access the Spec.Tier field of the given object, if it has one. +// It returns the tier and a boolean indicating whether the tier was successfully retrieved. +func getTier(obj any) (string, bool) { + v := reflect.ValueOf(obj) + if v.Kind() == reflect.Pointer { + v = v.Elem() + } + spec := v.FieldByName("Spec") + if !spec.IsValid() { + return "", false + } + tier := spec.FieldByName("Tier") + if !tier.IsValid() { + return "", false + } + if tier.Kind() != reflect.String { + return "", false + } + return tier.String(), true +} + +// augmentContextWithUserInfo adds the necessary user and request information from the admission request to the context so that it can +// be accessed by the RBAC authorizer. +func augmentContextWithUserInfo(ctx context.Context, req *v1.AdmissionRequest, obj client.Object) (context.Context, error) { + // Create a user.Info object from the AdmissionRequest's UserInfo. + extra := map[string][]string{} + for k, v := range req.UserInfo.Extra { + extra[k] = v + } + info := user.DefaultInfo{ + Name: req.UserInfo.Username, + UID: req.UserInfo.UID, + Groups: req.UserInfo.Groups, + Extra: extra, + } + + var resource string + switch obj.(type) { + case *v3.NetworkPolicy: + resource = "networkpolicies" + case *v3.GlobalNetworkPolicy: + resource = "globalnetworkpolicies" + case *v3.StagedNetworkPolicy: + resource = "stagednetworkpolicies" + case *v3.StagedGlobalNetworkPolicy: + resource = "stagedglobalnetworkpolicies" + case *v3.StagedKubernetesNetworkPolicy: + resource = "stagedkubernetesnetworkpolicies" + default: + return nil, fmt.Errorf("unsupported object type: %T", obj) + } + + // Get the resource path. + path := fmt.Sprintf("/apis/projectcalico.org/v3/%s/%s", resource, obj.GetName()) + if obj.GetNamespace() != "" { + path = fmt.Sprintf("/apis/projectcalico.org/v3/namespaces/%s/%s/%s", obj.GetNamespace(), resource, obj.GetName()) + } + + // Create a RequestInfo object from the AdmissionRequest. + ri := &genericapirequest.RequestInfo{ + IsResourceRequest: true, + Path: path, + Verb: strings.ToLower(string(req.Operation)), + APIGroup: v3.SchemeGroupVersion.Group, + APIVersion: v3.SchemeGroupVersion.Version, + Resource: resource, + Name: obj.GetName(), + Namespace: obj.GetNamespace(), + } + if req.Operation == v1.Connect { + ri.Name = "" + } + + // Create a context with the user info and request info. + if obj.GetNamespace() != "" { + ctx = genericapirequest.WithNamespace(ctx, obj.GetNamespace()) + } + ctx = genericapirequest.WithUser(ctx, &info) + ctx = genericapirequest.WithRequestInfo(ctx, ri) + return ctx, nil +} diff --git a/webhooks/pkg/rbac/rbac_test.go b/webhooks/pkg/rbac/rbac_test.go new file mode 100644 index 00000000000..293542c0b51 --- /dev/null +++ b/webhooks/pkg/rbac/rbac_test.go @@ -0,0 +1,444 @@ +// Copyright 2026 Tigera, Inc. +// +// 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 rbac + +import ( + "context" + "encoding/json" + "fmt" + "testing" + + v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + v1 "k8s.io/api/admission/v1" + authv1 "k8s.io/api/authentication/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + + "github.com/projectcalico/calico/webhooks/pkg/utils" +) + +// MockTierAuthorizer is a mock implementation of authorizer.TierAuthorizer +type MockTierAuthorizer struct { + mock.Mock +} + +func (m *MockTierAuthorizer) AuthorizeTierOperation(ctx context.Context, policyName string, tierName string) error { + args := m.Called(ctx, policyName, tierName) + return args.Error(0) +} + +func init() { + // Add Calico v3 types to the scheme for parsePolicy to work. + utilruntime.Must(v3.AddToScheme(utils.Scheme)) +} + +func TestGetTier(t *testing.T) { + testCases := []struct { + name string + obj any + expected string + ok bool + }{ + { + name: "NetworkPolicy with tier", + obj: &v3.NetworkPolicy{ + Spec: v3.NetworkPolicySpec{ + Tier: "default", + }, + }, + expected: "default", + ok: true, + }, + { + name: "GlobalNetworkPolicy with tier", + obj: &v3.GlobalNetworkPolicy{ + Spec: v3.GlobalNetworkPolicySpec{ + Tier: "admin", + }, + }, + expected: "admin", + ok: true, + }, + { + name: "StagedNetworkPolicy with tier", + obj: &v3.StagedNetworkPolicy{ + Spec: v3.StagedNetworkPolicySpec{ + Tier: "trusted", + }, + }, + expected: "trusted", + ok: true, + }, + { + name: "StagedGlobalNetworkPolicy with tier", + obj: &v3.StagedGlobalNetworkPolicy{ + Spec: v3.StagedGlobalNetworkPolicySpec{ + Tier: "trusted-global", + }, + }, + expected: "trusted-global", + ok: true, + }, + { + name: "StagedKubernetesNetworkPolicy without tier", + obj: &v3.StagedKubernetesNetworkPolicy{ + Spec: v3.StagedKubernetesNetworkPolicySpec{}, + }, + ok: false, + }, + { + name: "Object without Spec", + obj: &struct{}{}, + ok: false, + }, + { + name: "Object with Spec but no Tier", + obj: &struct { + Spec struct{} + }{}, + ok: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + tier, ok := getTier(tc.obj) + assert.Equal(t, tc.ok, ok) + if ok { + assert.Equal(t, tc.expected, tier) + } + }) + } +} + +func TestParsePolicy(t *testing.T) { + h := &tieredRBACHook{} + + np := &v3.NetworkPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: v3.KindNetworkPolicy, + APIVersion: "projectcalico.org/v3", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-np", + }, + Spec: v3.NetworkPolicySpec{ + Tier: "default", + }, + } + npRaw, err := json.Marshal(np) + assert.NoError(t, err) + + gnp := &v3.GlobalNetworkPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: v3.KindGlobalNetworkPolicy, + APIVersion: "projectcalico.org/v3", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-gnp", + }, + Spec: v3.GlobalNetworkPolicySpec{ + Tier: "admin", + }, + } + gnpRaw, err := json.Marshal(gnp) + assert.NoError(t, err) + + snp := &v3.StagedNetworkPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: v3.KindStagedNetworkPolicy, + APIVersion: "projectcalico.org/v3", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-snp", + }, + Spec: v3.StagedNetworkPolicySpec{ + Tier: "trusted", + }, + } + snpRaw, err := json.Marshal(snp) + assert.NoError(t, err) + + sgnp := &v3.StagedGlobalNetworkPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: v3.KindStagedGlobalNetworkPolicy, + APIVersion: "projectcalico.org/v3", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-sgnp", + }, + Spec: v3.StagedGlobalNetworkPolicySpec{ + Tier: "trusted-global", + }, + } + sgnpRaw, err := json.Marshal(sgnp) + assert.NoError(t, err) + + sknp := &v3.StagedKubernetesNetworkPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: v3.KindStagedKubernetesNetworkPolicy, + APIVersion: "projectcalico.org/v3", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-sknp", + }, + Spec: v3.StagedKubernetesNetworkPolicySpec{}, + } + sknpRaw, err := json.Marshal(sknp) + assert.NoError(t, err) + + testCases := []struct { + name string + kind string + body []byte + expectedTier string + expectError bool + }{ + { + name: "Valid NetworkPolicy", + kind: v3.KindNetworkPolicy, + body: npRaw, + expectedTier: "default", + expectError: false, + }, + { + name: "Valid GlobalNetworkPolicy", + kind: v3.KindGlobalNetworkPolicy, + body: gnpRaw, + expectedTier: "admin", + expectError: false, + }, + { + name: "Valid StagedNetworkPolicy", + kind: v3.KindStagedNetworkPolicy, + body: snpRaw, + expectedTier: "trusted", + expectError: false, + }, + { + name: "Valid StagedGlobalNetworkPolicy", + kind: v3.KindStagedGlobalNetworkPolicy, + body: sgnpRaw, + expectedTier: "trusted-global", + expectError: false, + }, + { + name: "StagedKubernetesNetworkPolicy (no tier)", + kind: v3.KindStagedKubernetesNetworkPolicy, + body: sknpRaw, + expectError: true, + }, + { + name: "Unsupported kind", + kind: "Unknown", + body: npRaw, + expectError: true, + }, + { + name: "Invalid JSON", + kind: v3.KindNetworkPolicy, + body: []byte("{invalid}"), + expectError: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + obj, tier, err := h.parsePolicy(tc.kind, tc.body) + if tc.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.expectedTier, tier) + assert.NotNil(t, obj) + } + }) + } +} + +func TestNewTieredRBACHook(t *testing.T) { + mockAuthz := &MockTierAuthorizer{} + h := NewTieredRBACHook(mockAuthz) + assert.NotNil(t, h) + + handler := h.Handler() + assert.NotNil(t, handler.ProcessV1Review) +} + +func TestAugmentContextWithUserInfo(t *testing.T) { + ctx := context.Background() + req := &v1.AdmissionRequest{ + UserInfo: authv1.UserInfo{ + Username: "test-user", + UID: "user-123", + Groups: []string{"group1", "group2"}, + Extra: map[string]authv1.ExtraValue{"foo": {"bar"}}, + }, + Operation: v1.Create, + } + obj := &v3.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-np", + Namespace: "test-ns", + }, + } + + newCtx, err := augmentContextWithUserInfo(ctx, req, obj) + assert.NoError(t, err) + assert.NotNil(t, newCtx) + + u, ok := genericapirequest.UserFrom(newCtx) + assert.True(t, ok) + assert.Equal(t, "test-user", u.GetName()) + assert.Equal(t, "user-123", u.GetUID()) + assert.ElementsMatch(t, []string{"group1", "group2"}, u.GetGroups()) + + ri, ok := genericapirequest.RequestInfoFrom(newCtx) + assert.True(t, ok) + assert.Equal(t, "networkpolicies", ri.Resource) + assert.Equal(t, "test-np", ri.Name) + assert.Equal(t, "test-ns", ri.Namespace) + assert.Equal(t, "create", ri.Verb) +} + +func TestAuthorize(t *testing.T) { + mockAuthz := &MockTierAuthorizer{} + h := NewTieredRBACHook(mockAuthz).(*tieredRBACHook) + + np := &v3.NetworkPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: v3.KindNetworkPolicy, + APIVersion: "projectcalico.org/v3", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-np", + Namespace: "test-ns", + }, + Spec: v3.NetworkPolicySpec{ + Tier: "default", + }, + } + npRaw, _ := json.Marshal(np) + + testCases := []struct { + name string + ar v1.AdmissionReview + setupMock func() + expectedAllow bool + expectedReason metav1.StatusReason + }{ + { + name: "Authorized request", + ar: v1.AdmissionReview{ + Request: &v1.AdmissionRequest{ + UID: "123", + Kind: metav1.GroupVersionKind{Group: "projectcalico.org", Version: "v3", Kind: v3.KindNetworkPolicy}, + Object: runtime.RawExtension{ + Raw: npRaw, + }, + Operation: v1.Create, + UserInfo: authv1.UserInfo{Username: "test-user"}, + }, + }, + setupMock: func() { + mockAuthz.On("AuthorizeTierOperation", mock.Anything, "test-np", "default").Return(nil).Once() + }, + expectedAllow: true, + }, + { + name: "Authorized request (DELETE uses OldObject)", + ar: v1.AdmissionReview{ + Request: &v1.AdmissionRequest{ + UID: "1234", + Kind: metav1.GroupVersionKind{Group: "projectcalico.org", Version: "v3", Kind: v3.KindNetworkPolicy}, + OldObject: runtime.RawExtension{ + Raw: npRaw, + }, + Operation: v1.Delete, + UserInfo: authv1.UserInfo{Username: "test-user"}, + }, + }, + setupMock: func() { + mockAuthz.On("AuthorizeTierOperation", mock.Anything, "test-np", "default").Return(nil).Once() + }, + expectedAllow: true, + }, + { + name: "Unauthorized request", + ar: v1.AdmissionReview{ + Request: &v1.AdmissionRequest{ + UID: "123", + Kind: metav1.GroupVersionKind{Group: "projectcalico.org", Version: "v3", Kind: v3.KindNetworkPolicy}, + Object: runtime.RawExtension{ + Raw: npRaw, + }, + Operation: v1.Create, + UserInfo: authv1.UserInfo{Username: "test-user"}, + }, + }, + setupMock: func() { + mockAuthz.On("AuthorizeTierOperation", mock.Anything, "test-np", "default").Return(fmt.Errorf("unauthorized")).Once() + }, + expectedAllow: false, + expectedReason: metav1.StatusReasonForbidden, + }, + { + name: "No object in request", + ar: v1.AdmissionReview{ + Request: &v1.AdmissionRequest{ + UID: "123", + Operation: v1.Create, + UserInfo: authv1.UserInfo{Username: "test-user"}, + }, + }, + setupMock: func() {}, + expectedAllow: false, + expectedReason: metav1.StatusReasonBadRequest, + }, + { + name: "Invalid kind in request", + ar: v1.AdmissionReview{ + Request: &v1.AdmissionRequest{ + UID: "123", + Kind: metav1.GroupVersionKind{Group: "projectcalico.org", Version: "v3", Kind: "InvalidKind"}, + Object: runtime.RawExtension{ + Raw: npRaw, + }, + Operation: v1.Create, + UserInfo: authv1.UserInfo{Username: "test-user"}, + }, + }, + setupMock: func() {}, + expectedAllow: false, + expectedReason: metav1.StatusReasonInvalid, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + tc.setupMock() + resp := h.authorize(tc.ar) + assert.Equal(t, tc.expectedAllow, resp.Allowed) + if !tc.expectedAllow && tc.expectedReason != "" { + assert.Equal(t, tc.expectedReason, resp.Result.Reason) + } + mockAuthz.AssertExpectations(t) + }) + } +} diff --git a/libcalico-go/lib/upgrade/converters/selectors.go b/webhooks/pkg/utils/scheme.go similarity index 50% rename from libcalico-go/lib/upgrade/converters/selectors.go rename to webhooks/pkg/utils/scheme.go index bd1fe599628..454ba405109 100644 --- a/libcalico-go/lib/upgrade/converters/selectors.go +++ b/webhooks/pkg/utils/scheme.go @@ -1,5 +1,5 @@ -// Copyright (c) 2017 Tigera, Inc. All rights reserved. - +// Copyright 2026 Tigera, Inc. +// // 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 @@ -12,19 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -package converters +package utils import ( - "strings" + admissionv1 "k8s.io/api/admission/v1" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" ) var ( - v1NamespaceSelector = "calico/k8s_ns" - v3NamespaceSelector = "projectcalico.org/namespace" + Scheme = runtime.NewScheme() + Codecs = serializer.NewCodecFactory(Scheme) ) -// convertSelector converts a v1 selector to a v3 selector. -func convertSelector(sel string) string { - // v1 selectors used calico/k8s_ns, v3 instead uses projectcalico.org/namespace - return strings.Replace(sel, v1NamespaceSelector, v3NamespaceSelector, -1) +func init() { + utilruntime.Must(corev1.AddToScheme(Scheme)) + utilruntime.Must(admissionv1.AddToScheme(Scheme)) + utilruntime.Must(admissionregistrationv1.AddToScheme(Scheme)) } diff --git a/webhooks/pkg/utils/utils.go b/webhooks/pkg/utils/utils.go new file mode 100644 index 00000000000..2fad7810567 --- /dev/null +++ b/webhooks/pkg/utils/utils.go @@ -0,0 +1,42 @@ +// Copyright 2026 Tigera, Inc. +// +// Copyright 2018 The Kubernetes Authors. +// +// 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 ( + "net/http" + + v1 "k8s.io/api/admission/v1" +) + +type HandlerProvider interface { + Handler() AdmissionReviewHandler +} + +type V1AdmissionFunc func(v1.AdmissionReview) *v1.AdmissionResponse + +// AdmissionReviewHandler is a handler, for both validators and mutators, that supports multiple admission review versions +type AdmissionReviewHandler struct { + ProcessV1Review V1AdmissionFunc +} + +func NewDelegateToV1AdmitHandler(f V1AdmissionFunc) AdmissionReviewHandler { + return AdmissionReviewHandler{ProcessV1Review: f} +} + +// HandleFn is a function type that takes an AdmissionReviewHandler and returns an http.HandlerFunc to help with +// webhook registration. +type HandleFn func(handler AdmissionReviewHandler) func(http.ResponseWriter, *http.Request) diff --git a/whisker-backend/deps.txt b/whisker-backend/deps.txt index 58c00aa7eac..4102725739e 100644 --- a/whisker-backend/deps.txt +++ b/whisker-backend/deps.txt @@ -2,67 +2,78 @@ This file contains the list of modules that this package depends on in order to trigger CI on changes -go 1.25.1 +go 1.25.7 github.com/beorn7/perks v1.0.1 github.com/blang/semver/v4 v4.0.0 github.com/cespare/xxhash/v2 v2.3.0 +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/distribution/reference v0.6.0 -github.com/fxamacker/cbor/v2 v2.7.0 -github.com/gabriel-vasile/mimetype v1.4.8 +github.com/emicklei/go-restful v2.15.0+incompatible +github.com/emicklei/go-restful/v3 v3.12.2 +github.com/fxamacker/cbor/v2 v2.9.0 +github.com/gabriel-vasile/mimetype v1.4.10 github.com/go-logr/logr v1.4.3 +github.com/go-openapi/jsonpointer v0.21.0 +github.com/go-openapi/jsonreference v0.20.2 +github.com/go-openapi/swag v0.23.0 github.com/go-playground/form v3.1.4+incompatible github.com/go-playground/locales v0.14.1 github.com/go-playground/universal-translator v0.18.1 -github.com/go-playground/validator/v10 v10.27.0 +github.com/go-playground/validator/v10 v10.28.0 github.com/gogo/protobuf v1.3.2 +github.com/google/gnostic-models v0.7.0 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 +github.com/jinzhu/copier v0.4.0 +github.com/josharian/intern v1.0.0 github.com/json-iterator/go v1.1.12 github.com/kelseyhightower/envconfig v1.4.0 github.com/leodido/go-urn v1.4.0 +github.com/mailru/easyjson v0.7.7 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd -github.com/modern-go/reflect2 v1.0.2 +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 -github.com/onsi/gomega v1.38.0 +github.com/onsi/gomega v1.39.1 github.com/opencontainers/go-digest v1.0.0 +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/projectcalico/calico -github.com/projectcalico/calico/lib/httpmachinery v0.0.0-00010101000000-000000000000 -github.com/projectcalico/calico/lib/std v0.0.0-00010101000000-000000000000 -github.com/prometheus/client_golang v1.23.0 +github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 -github.com/prometheus/common v0.65.0 -github.com/prometheus/procfs v0.17.0 +github.com/prometheus/common v0.67.2 +github.com/prometheus/procfs v0.19.2 github.com/sirupsen/logrus v1.9.3 -github.com/spf13/pflag v1.0.7 +github.com/spf13/pflag v1.0.10 github.com/x448/float16 v0.8.4 -go.opentelemetry.io/otel v1.35.0 -go.opentelemetry.io/otel/trace v1.35.0 -go.yaml.in/yaml/v2 v2.4.2 -golang.org/x/crypto v0.41.0 -golang.org/x/net v0.43.0 -golang.org/x/sys v0.35.0 -golang.org/x/text v0.28.0 +go.opentelemetry.io/otel v1.37.0 +go.opentelemetry.io/otel/trace v1.37.0 +go.yaml.in/yaml/v2 v2.4.3 +go.yaml.in/yaml/v3 v3.0.4 +golang.org/x/crypto v0.47.0 +golang.org/x/net v0.49.0 +golang.org/x/sys v0.40.0 +golang.org/x/text v0.33.0 google.golang.org/genproto v0.0.0-20250603155806-513f23925822 -google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a -google.golang.org/grpc v1.72.2 -google.golang.org/protobuf v1.36.7 +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c +google.golang.org/grpc v1.76.0 +google.golang.org/protobuf v1.36.10 gopkg.in/inf.v0 v0.9.1 gopkg.in/yaml.v3 v3.0.1 -k8s.io/api v0.33.5 -k8s.io/apiextensions-apiserver v0.33.5 -k8s.io/apimachinery v0.33.5 -k8s.io/apiserver v0.33.5 -k8s.io/client-go v0.33.5 -k8s.io/component-base v0.33.5 -k8s.io/component-helpers v0.33.5 -k8s.io/controller-manager v0.33.5 +k8s.io/api v0.34.3 +k8s.io/apiextensions-apiserver v0.34.3 +k8s.io/apimachinery v0.34.3 +k8s.io/apiserver v0.34.3 +k8s.io/client-go v0.34.3 +k8s.io/component-base v0.34.3 +k8s.io/component-helpers v0.34.3 +k8s.io/controller-manager v0.34.3 k8s.io/klog v0.2.0 k8s.io/klog/v2 v2.130.1 -k8s.io/kubelet v0.33.5 -k8s.io/kubernetes v1.33.5 -k8s.io/utils v0.0.0-20241210054802-24370beab758 +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b +k8s.io/kubelet v0.34.3 +k8s.io/kubernetes v1.34.3 +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 sigs.k8s.io/randfill v1.0.0 -sigs.k8s.io/structured-merge-diff/v4 v4.6.0 +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 sigs.k8s.io/yaml v1.6.0 diff --git a/whisker-backend/pkg/apis/v1/flows.go b/whisker-backend/pkg/apis/v1/flows.go index 6b731682044..afd80e84338 100644 --- a/whisker-backend/pkg/apis/v1/flows.go +++ b/whisker-backend/pkg/apis/v1/flows.go @@ -68,7 +68,7 @@ func init() { } func marshalToBytes(str interface{ String() string }) ([]byte, error) { - return []byte(fmt.Sprintf("\"%s\"", str)), nil + return fmt.Appendf(nil, "\"%s\"", str), nil } // unmarshalProtoEnum unmarshals the bytes into the given int32 generic type (representing a proto enum) using the map @@ -102,6 +102,7 @@ const ( ) type Actions []Action +type PendingActions []Action func (p Action) String() string { return proto.Action(p).String() } func (p Action) MarshalJSON() ([]byte, error) { return marshalToBytes(p) } @@ -116,6 +117,14 @@ func (a Actions) AsProtos() []proto.Action { return protos } +func (a PendingActions) AsProtos() []proto.Action { + var protos []proto.Action + for _, a1 := range a { + protos = append(protos, a1.AsProto()) + } + return protos +} + type ( SortBy proto.SortBy SortBys []SortBy @@ -150,6 +159,11 @@ func (p MatchType) AsProto() proto.MatchType { return proto.MatchType(p) } type Reporter proto.Reporter +const ( + ReporterSrc = Reporter(proto.Reporter_Src) + ReporterDst = Reporter(proto.Reporter_Dst) +) + func (p Reporter) String() string { return proto.Reporter(p).String() } func (p Reporter) MarshalJSON() ([]byte, error) { return marshalToBytes(p) } func (p *Reporter) UnmarshalJSON(b []byte) error { @@ -166,9 +180,8 @@ const ( PolicyKindStagedGlobalNetworkPolicy = PolicyKind(proto.PolicyKind_StagedGlobalNetworkPolicy) PolicyKindStagedKubernetesNetworkPolicy = PolicyKind(proto.PolicyKind_StagedKubernetesNetworkPolicy) - PolicyKindNetworkPolicy = PolicyKind(proto.PolicyKind_NetworkPolicy) - PolicyKindAdminNetworkPolicy = PolicyKind(proto.PolicyKind_AdminNetworkPolicy) - PolicyKindBaselineAdminNetworkPolicy = PolicyKind(proto.PolicyKind_BaselineAdminNetworkPolicy) + PolicyKindNetworkPolicy = PolicyKind(proto.PolicyKind_NetworkPolicy) + PolicyKindClusterNetworkPolicy = PolicyKind(proto.PolicyKind_ClusterNetworkPolicy) PolicyKindProfile = PolicyKind(proto.PolicyKind_Profile) PolicyKindEndOfTier = PolicyKind(proto.PolicyKind_EndOfTier) @@ -224,7 +237,9 @@ type Filters struct { Protocols FilterMatches[string] `json:"protocols,omitempty"` DestPorts FilterMatches[int64] `json:"dest_ports,omitempty"` Actions Actions `json:"actions,omitempty"` + PendingActions PendingActions `json:"pending_actions,omitempty"` Policies []PolicyMatch `json:"policies,omitempty"` + Reporter Reporter `json:"reporter,omitempty"` } type PolicyMatch struct { diff --git a/whisker-backend/pkg/config/api.go b/whisker-backend/pkg/config/api.go index 4caf59ae390..ecc6e94e450 100644 --- a/whisker-backend/pkg/config/api.go +++ b/whisker-backend/pkg/config/api.go @@ -16,7 +16,7 @@ package config import ( "encoding/json" - "fmt" + "net" "os" "github.com/kelseyhightower/envconfig" @@ -56,7 +56,7 @@ func (cfg *Config) String() string { } func (cfg *Config) HostAddr() string { - return fmt.Sprintf("%s:%s", cfg.Host, cfg.Port) + return net.JoinHostPort(cfg.Host, cfg.Port) } func (cfg *Config) ConfigureLogging() { diff --git a/whisker-backend/pkg/handlers/v1/flows_test.go b/whisker-backend/pkg/handlers/v1/flows_test.go index 873e72e608c..bd6fbe75fdd 100644 --- a/whisker-backend/pkg/handlers/v1/flows_test.go +++ b/whisker-backend/pkg/handlers/v1/flows_test.go @@ -155,7 +155,7 @@ func TestWatchFlows(t *testing.T) { zerotime := time.Unix(0, 0) var flows []whiskerv1.FlowResponse - for _, data := range strings.Split(recorder.Body.String(), "\n\n") { + for data := range strings.SplitSeq(recorder.Body.String(), "\n\n") { if len(data) == 0 { continue } @@ -207,6 +207,8 @@ func TestWatchFlowsParameterConversion(t *testing.T) { Protocols: []whiskerv1.FilterMatch[string]{{V: "tcp"}}, DestPorts: []whiskerv1.FilterMatch[int64]{{V: 6060}}, Actions: whiskerv1.Actions{whiskerv1.Action(proto.Action_Pass), whiskerv1.Action(proto.Action_Allow)}, + PendingActions: whiskerv1.PendingActions{whiskerv1.Action(proto.Action_Pass), whiskerv1.Action(proto.Action_Allow)}, + Reporter: whiskerv1.ReporterSrc, Policies: []whiskerv1.PolicyMatch{{ Kind: whiskerv1.PolicyKindCalicoNetworkPolicy, Tier: whiskerv1.NewFilterMatch("default-tier", whiskerv1.MatchTypeExact), @@ -226,6 +228,8 @@ func TestWatchFlowsParameterConversion(t *testing.T) { Protocols: []*proto.StringMatch{{Value: "tcp"}}, DestPorts: []*proto.PortMatch{{Port: 6060}}, Actions: []proto.Action{proto.Action_Pass, proto.Action_Allow}, + PendingActions: []proto.Action{proto.Action_Pass, proto.Action_Allow}, + Reporter: proto.Reporter_Src, Policies: []*proto.PolicyMatch{{ Kind: proto.PolicyKind_CalicoNetworkPolicy, Tier: "default-tier", @@ -281,6 +285,12 @@ func TestListFlowsParameterConversion(t *testing.T) { }, StartTimeGte: now.Unix(), Filters: whiskerv1.Filters{ + Policies: []whiskerv1.PolicyMatch{{ + Kind: whiskerv1.PolicyKindCalicoNetworkPolicy, + Tier: whiskerv1.NewFilterMatch("default-tier", whiskerv1.MatchTypeExact), + Name: whiskerv1.NewFilterMatch("name", whiskerv1.MatchTypeExact), + Namespace: whiskerv1.NewFilterMatch("namespace", whiskerv1.MatchTypeExact), + }}, SourceNamespaces: []whiskerv1.FilterMatch[string]{{V: "src-ns"}}, SourceNames: []whiskerv1.FilterMatch[string]{{V: "src-name"}}, DestNamespaces: []whiskerv1.FilterMatch[string]{{V: "dst-ns"}}, @@ -288,6 +298,8 @@ func TestListFlowsParameterConversion(t *testing.T) { Protocols: []whiskerv1.FilterMatch[string]{{V: "tcp"}}, DestPorts: []whiskerv1.FilterMatch[int64]{{V: 6060}}, Actions: whiskerv1.Actions{whiskerv1.Action(proto.Action_Pass), whiskerv1.Action(proto.Action_Allow)}, + PendingActions: whiskerv1.PendingActions{whiskerv1.Action(proto.Action_Pass), whiskerv1.Action(proto.Action_Allow)}, + Reporter: whiskerv1.ReporterSrc, }, }, expected: &proto.FlowListRequest{ @@ -298,6 +310,12 @@ func TestListFlowsParameterConversion(t *testing.T) { }, StartTimeGte: now.Unix(), Filter: &proto.Filter{ + Policies: []*proto.PolicyMatch{{ + Kind: proto.PolicyKind_CalicoNetworkPolicy, + Tier: "default-tier", + Name: "name", + Namespace: "namespace", + }}, SourceNamespaces: []*proto.StringMatch{{Value: "src-ns"}}, SourceNames: []*proto.StringMatch{{Value: "src-name"}}, DestNamespaces: []*proto.StringMatch{{Value: "dst-ns"}}, @@ -305,6 +323,8 @@ func TestListFlowsParameterConversion(t *testing.T) { Protocols: []*proto.StringMatch{{Value: "tcp"}}, DestPorts: []*proto.PortMatch{{Port: 6060}}, Actions: []proto.Action{proto.Action_Pass, proto.Action_Allow}, + PendingActions: []proto.Action{proto.Action_Pass, proto.Action_Allow}, + Reporter: proto.Reporter_Src, }, }, configureFlowsCli: func(fsCli *climocks.FlowsClient) { diff --git a/whisker-backend/pkg/handlers/v1/protoconvert.go b/whisker-backend/pkg/handlers/v1/protoconvert.go index 9d44f4615f7..de765618fb2 100644 --- a/whisker-backend/pkg/handlers/v1/protoconvert.go +++ b/whisker-backend/pkg/handlers/v1/protoconvert.go @@ -84,7 +84,9 @@ func toProtoFilter(filters whiskerv1.Filters) *proto.Filter { Protocols: toProtoStringMatches(filters.Protocols, nil), DestPorts: toProtoPorts(filters.DestPorts), Actions: filters.Actions.AsProtos(), + PendingActions: filters.PendingActions.AsProtos(), Policies: toProtoPolicyMatch(filters.Policies), + Reporter: filters.Reporter.AsProto(), } } diff --git a/whisker-backend/test/fv/goldmaneintegration_test.go b/whisker-backend/test/fv/goldmaneintegration_test.go index 415fbf86d44..54ce34a5245 100644 --- a/whisker-backend/test/fv/goldmaneintegration_test.go +++ b/whisker-backend/test/fv/goldmaneintegration_test.go @@ -78,11 +78,9 @@ func TestGoldmaneIntegration_FlowWatching(t *testing.T) { CACertPath: clientCertFile.Name(), } - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { gmdaemon.Run(ctx, cfg) - }() + }) // We want to actually wait 5 seconds, not use our fake time for this. realtime.Sleep(time.Second * 5) @@ -96,11 +94,9 @@ func TestGoldmaneIntegration_FlowWatching(t *testing.T) { TLSKeyPath: clientKeyFile.Name(), } whiskerCfg.ConfigureLogging() - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { app.Run(ctx, whiskerCfg) - }() + }) cli, err := client.NewFlowClient("localhost:5444", clientCertFile.Name(), clientKeyFile.Name(), certFile.Name()) Expect(err).ShouldNot(HaveOccurred()) @@ -193,11 +189,9 @@ func TestGoldmaneIntegration_FilterHints(t *testing.T) { CACertPath: clientCertFile.Name(), } - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { gmdaemon.Run(ctx, cfg) - }() + }) whiskerCfg := &wconfig.Config{ Port: "8080", @@ -209,11 +203,9 @@ func TestGoldmaneIntegration_FilterHints(t *testing.T) { } whiskerCfg.ConfigureLogging() - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { app.Run(ctx, whiskerCfg) - }() + }) // We want to actually wait 5 seconds, not use our fake time for this. realtime.Sleep(time.Second * 5) diff --git a/whisker-backend/test/fv/util.go b/whisker-backend/test/fv/util.go index 8369dc713bf..bbf54979542 100644 --- a/whisker-backend/test/fv/util.go +++ b/whisker-backend/test/fv/util.go @@ -52,8 +52,8 @@ func newSSEScanner[E any](t *testing.T, r io.Reader) <-chan ObjWithErr[*E] { for scanner.Scan() { line := scanner.Text() - if strings.HasPrefix(line, "data:") { - data := strings.TrimPrefix(line, "data:") + if after, ok := strings.CutPrefix(line, "data:"); ok { + data := after fmt.Println("Event Data: ", strings.TrimSpace(data)) responseChan <- ObjWithErr[*E]{Obj: jsontestutil.MustUnmarshal[E](t, []byte(data))} diff --git a/whisker/.semaphore/semaphore.yml b/whisker/.semaphore/semaphore.yml index 93bd9cb542e..95052c414e5 100644 --- a/whisker/.semaphore/semaphore.yml +++ b/whisker/.semaphore/semaphore.yml @@ -3,7 +3,7 @@ name: Calico OSS UI CI Build agent: machine: type: f1-standard-4 - os_image: ubuntu2004 + os_image: ubuntu2204 blocks: - name: CI Build task: diff --git a/whisker/package.json b/whisker/package.json index ed8a585c38e..41de5c1fc33 100644 --- a/whisker/package.json +++ b/whisker/package.json @@ -36,16 +36,16 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "framer-motion": "^10.12.16", - "lodash": "^4.17.21", + "lodash": "^4.17.23", "lucide-react": "^0.545.0", "motion": "^12.4.10", "postcss": "^8.5.6", "prettier-plugin-tailwindcss": "^0.7.0", - "react": "^19.0.0", - "react-dom": "^19.0.0", + "react": "^19.2.1", + "react-dom": "^19.2.1", "react-hook-form": "^7.54.2", "react-json-view-lite": "^1.2.0", - "react-router-dom": "6.28.2", + "react-router-dom": "6.30.3", "react-table": "^7.7.0", "react-window": "^1.8.6", "tailwind-merge": "^3.3.1", @@ -71,7 +71,7 @@ "@types/react-dom": "^18.3.1", "@types/react-router-dom": "^5.3.3", "@types/react-window": "^1.8.2", - "eslint": "^9.31.0", + "eslint": "^9.39.1", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "globals": "^15.12.0", @@ -86,7 +86,8 @@ }, "resolutions": { "@eslint/plugin-kit": "^0.3.4", - "brace-expansion": "^2.0.2" + "brace-expansion": "^2.0.2", + "js-yaml": "^4.1.1" }, "packageManager": "yarn@1.22.19" } diff --git a/whisker/public/favicon.ico b/whisker/public/favicon.ico index a11ff055888..57a5ff7d7c1 100644 Binary files a/whisker/public/favicon.ico and b/whisker/public/favicon.ico differ diff --git a/whisker/src/components/layout/AppHeader/index.tsx b/whisker/src/components/layout/AppHeader/index.tsx index 054503ce845..6ed922aad18 100644 --- a/whisker/src/components/layout/AppHeader/index.tsx +++ b/whisker/src/components/layout/AppHeader/index.tsx @@ -32,7 +32,7 @@ const AppHeader: React.FC = () => { - + diff --git a/whisker/src/icons/CalicoCatIcon.tsx b/whisker/src/icons/CalicoCatIcon.tsx index e840cb5c098..1830a3cc040 100644 --- a/whisker/src/icons/CalicoCatIcon.tsx +++ b/whisker/src/icons/CalicoCatIcon.tsx @@ -2,217 +2,41 @@ import { createIcon } from '@chakra-ui/icons'; const CalicoCatIcon = createIcon({ displayName: 'CalicoCatIcon', - viewBox: '0 0 35 35', + viewBox: '0 0 104.05 113.1', path: ( <> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + ), }); diff --git a/whisker/src/libs/tigera/ui-components/components/common/DataTable/ActionMenu/__tests__/__snapshots__/index.test.tsx.snap b/whisker/src/libs/tigera/ui-components/components/common/DataTable/ActionMenu/__tests__/__snapshots__/index.test.tsx.snap index e0484b85506..efde4017281 100644 --- a/whisker/src/libs/tigera/ui-components/components/common/DataTable/ActionMenu/__tests__/__snapshots__/index.test.tsx.snap +++ b/whisker/src/libs/tigera/ui-components/components/common/DataTable/ActionMenu/__tests__/__snapshots__/index.test.tsx.snap @@ -3,14 +3,14 @@ exports[` it renders 1`] = `